1 /**
2 * @file gtk/gtk_mod.c GTK+ UI module
3 *
4 * Copyright (C) 2015 Charles E. Lehner
5 * Copyright (C) 2010 - 2015 Creytiv.com
6 */
7 #include <re.h>
8 #include <baresip.h>
9 #include <stdlib.h>
10 #include <pthread.h>
11 #include <gtk/gtk.h>
12 #include <gio/gio.h>
13 #include "gtk_mod.h"
14
15 #ifdef USE_LIBNOTIFY
16 #include <libnotify/notify.h>
17 #endif
18
19 #if GLIB_CHECK_VERSION(2,40,0) || defined(USE_LIBNOTIFY)
20 #define USE_NOTIFICATIONS 1
21 #endif
22
23 /* About */
24 #define COPYRIGHT " Copyright (C) 2010 - 2015 Alfred E. Heggestad et al."
25 #define COMMENTS "A modular SIP User-Agent with audio and video support"
26 #define WEBSITE "http://www.creytiv.com/baresip.html"
27 #define LICENSE "BSD"
28
29 /**
30 * @defgroup gtk_mod gtk_mod
31 *
32 * GTK+ Menu-based User-Interface module
33 *
34 * Creates a tray icon with a menu for making calls.
35 *
36 */
37
38 struct gtk_mod {
39 pthread_t thread;
40 bool run;
41 bool contacts_inited;
42 bool accounts_inited;
43 struct message_lsnr *message;
44 struct mqueue *mq;
45 GApplication *app;
46 GtkStatusIcon *status_icon;
47 GtkWidget *app_menu;
48 GtkWidget *contacts_menu;
49 GtkWidget *accounts_menu;
50 GtkWidget *status_menu;
51 GSList *accounts_menu_group;
52 struct dial_dialog *dial_dialog;
53 GSList *call_windows;
54 GSList *incoming_call_menus;
55 };
56
57 static struct gtk_mod mod_obj;
58
59 enum gtk_mod_events {
60 MQ_POPUP,
61 MQ_CONNECT,
62 MQ_QUIT,
63 MQ_ANSWER,
64 MQ_HANGUP,
65 MQ_SELECT_UA,
66 };
67
68 static void answer_activated(GSimpleAction *, GVariant *, gpointer);
69 static void reject_activated(GSimpleAction *, GVariant *, gpointer);
70 static void denotify_incoming_call(struct gtk_mod *, struct call *);
71
72 static GActionEntry app_entries[] = {
73 {"answer", answer_activated, "x", NULL, NULL, {0} },
74 {"reject", reject_activated, "x", NULL, NULL, {0} },
75 };
76
get_call_from_gvariant(GVariant * param)77 static struct call *get_call_from_gvariant(GVariant *param)
78 {
79 gint64 call_ptr;
80 struct call *call;
81 struct list *calls = ua_calls(uag_current());
82 struct le *le;
83
84 call_ptr = g_variant_get_int64(param);
85 call = GINT_TO_POINTER(call_ptr);
86
87 for (le = list_head(calls); le; le = le->next)
88 if (le->data == call)
89 return call;
90
91 return NULL;
92 }
93
94
menu_on_about(GtkMenuItem * menuItem,gpointer arg)95 static void menu_on_about(GtkMenuItem *menuItem, gpointer arg)
96 {
97 (void)menuItem;
98 (void)arg;
99
100 gtk_show_about_dialog(NULL,
101 "program-name", "baresip",
102 "version", BARESIP_VERSION,
103 "logo-icon-name", "call-start",
104 "copyright", COPYRIGHT,
105 "comments", COMMENTS,
106 "website", WEBSITE,
107 "license", LICENSE,
108 NULL);
109 }
110
111
menu_on_quit(GtkMenuItem * menuItem,gpointer arg)112 static void menu_on_quit(GtkMenuItem *menuItem, gpointer arg)
113 {
114 struct gtk_mod *mod = arg;
115 (void)menuItem;
116
117 gtk_widget_destroy(GTK_WIDGET(mod->app_menu));
118 g_object_unref(G_OBJECT(mod->status_icon));
119
120 mqueue_push(mod->mq, MQ_QUIT, 0);
121 info("quit from gtk\n");
122 }
123
124
menu_on_dial(GtkMenuItem * menuItem,gpointer arg)125 static void menu_on_dial(GtkMenuItem *menuItem, gpointer arg)
126 {
127 struct gtk_mod *mod = arg;
128 (void)menuItem;
129 if (!mod->dial_dialog)
130 mod->dial_dialog = dial_dialog_alloc(mod);
131 dial_dialog_show(mod->dial_dialog);
132 }
133
134
menu_on_dial_contact(GtkMenuItem * menuItem,gpointer arg)135 static void menu_on_dial_contact(GtkMenuItem *menuItem, gpointer arg)
136 {
137 struct gtk_mod *mod = arg;
138 const char *uri = gtk_menu_item_get_label(menuItem);
139 /* Queue dial from the main thread */
140 mqueue_push(mod->mq, MQ_CONNECT, (char *)uri);
141 }
142
143
init_contacts_menu(struct gtk_mod * mod)144 static void init_contacts_menu(struct gtk_mod *mod)
145 {
146 struct contacts *contacts = baresip_contacts();
147 struct le *le;
148 GtkWidget *item;
149 GtkMenuShell *contacts_menu = GTK_MENU_SHELL(mod->contacts_menu);
150
151 /* Add contacts to submenu */
152 for (le = list_head(contact_list(contacts)); le; le = le->next) {
153 struct contact *c = le->data;
154 item = gtk_menu_item_new_with_label(contact_str(c));
155 gtk_menu_shell_append(contacts_menu, item);
156 g_signal_connect(G_OBJECT(item), "activate",
157 G_CALLBACK(menu_on_dial_contact), mod);
158 }
159 }
160
161
menu_on_account_toggled(GtkCheckMenuItem * menu_item,struct gtk_mod * mod)162 static void menu_on_account_toggled(GtkCheckMenuItem *menu_item,
163 struct gtk_mod *mod)
164 {
165 struct ua *ua = g_object_get_data(G_OBJECT(menu_item), "ua");
166 if (menu_item->active)
167 mqueue_push(mod->mq, MQ_SELECT_UA, ua);
168 }
169
170
menu_on_presence_set(GtkMenuItem * item,struct gtk_mod * mod)171 static void menu_on_presence_set(GtkMenuItem *item, struct gtk_mod *mod)
172 {
173 struct le *le;
174 void *type = g_object_get_data(G_OBJECT(item), "presence");
175 enum presence_status status = GPOINTER_TO_UINT(type);
176 (void)mod;
177
178 for (le = list_head(uag_list()); le; le = le->next) {
179 struct ua *ua = le->data;
180 ua_presence_status_set(ua, status);
181 }
182 }
183
184
185 #ifdef USE_NOTIFICATIONS
menu_on_incoming_call_answer(GtkMenuItem * menuItem,struct gtk_mod * mod)186 static void menu_on_incoming_call_answer(GtkMenuItem *menuItem,
187 struct gtk_mod *mod)
188 {
189 struct call *call = g_object_get_data(G_OBJECT(menuItem), "call");
190 denotify_incoming_call(mod, call);
191 mqueue_push(mod->mq, MQ_ANSWER, call);
192 }
193
194
menu_on_incoming_call_reject(GtkMenuItem * menuItem,struct gtk_mod * mod)195 static void menu_on_incoming_call_reject(GtkMenuItem *menuItem,
196 struct gtk_mod *mod)
197 {
198 struct call *call = g_object_get_data(G_OBJECT(menuItem), "call");
199 denotify_incoming_call(mod, call);
200 mqueue_push(mod->mq, MQ_HANGUP, call);
201 }
202 #endif
203
204
accounts_menu_add_item(struct gtk_mod * mod,struct ua * ua)205 static GtkMenuItem *accounts_menu_add_item(struct gtk_mod *mod,
206 struct ua *ua)
207 {
208 GtkMenuShell *accounts_menu = GTK_MENU_SHELL(mod->accounts_menu);
209 GtkWidget *item;
210 GSList *group = mod->accounts_menu_group;
211 struct ua *ua_current = uag_current();
212 char buf[256];
213
214 re_snprintf(buf, sizeof buf, "%s%s", ua_aor(ua),
215 ua_isregistered(ua) ? " (OK)" : "");
216 item = gtk_radio_menu_item_new_with_label(group, buf);
217 group = gtk_radio_menu_item_get_group(
218 GTK_RADIO_MENU_ITEM (item));
219 if (ua == ua_current)
220 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item),
221 TRUE);
222 g_object_set_data(G_OBJECT(item), "ua", ua);
223 g_signal_connect(item, "toggled",
224 G_CALLBACK(menu_on_account_toggled), mod);
225 gtk_menu_shell_append(accounts_menu, item);
226 mod->accounts_menu_group = group;
227
228 return GTK_MENU_ITEM(item);
229 }
230
231
accounts_menu_get_item(struct gtk_mod * mod,struct ua * ua)232 static GtkMenuItem *accounts_menu_get_item(struct gtk_mod *mod,
233 struct ua *ua)
234 {
235 GtkMenuItem *item;
236 GtkMenuShell *accounts_menu = GTK_MENU_SHELL(mod->accounts_menu);
237 GList *items = accounts_menu->children;
238
239 for (; items; items = items->next) {
240 item = items->data;
241 if (ua == g_object_get_data(G_OBJECT(item), "ua"))
242 return item;
243 }
244
245 /* Add new account not yet in menu */
246 return accounts_menu_add_item(mod, ua);
247 }
248
249
update_current_accounts_menu_item(struct gtk_mod * mod)250 static void update_current_accounts_menu_item(struct gtk_mod *mod)
251 {
252 GtkMenuItem *item = accounts_menu_get_item(mod,
253 uag_current());
254 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
255 }
256
257
update_ua_presence(struct gtk_mod * mod)258 static void update_ua_presence(struct gtk_mod *mod)
259 {
260 GtkCheckMenuItem *item = 0;
261 enum presence_status cur_status;
262 void *status;
263 GtkMenuShell *status_menu = GTK_MENU_SHELL(mod->status_menu);
264 GList *items = status_menu->children;
265
266 cur_status = ua_presence_status(uag_current());
267
268 for (; items; items = items->next) {
269 item = items->data;
270 status = g_object_get_data(G_OBJECT(item), "presence");
271 if (cur_status == GPOINTER_TO_UINT(status))
272 break;
273 }
274 if (!item)
275 return;
276
277 gtk_check_menu_item_set_active(item, TRUE);
278 }
279
280
ua_event_reg_str(enum ua_event ev)281 static const char *ua_event_reg_str(enum ua_event ev)
282 {
283 switch (ev) {
284
285 case UA_EVENT_REGISTERING: return "registering";
286 case UA_EVENT_REGISTER_OK: return "OK";
287 case UA_EVENT_REGISTER_FAIL: return "ERR";
288 case UA_EVENT_UNREGISTERING: return "unregistering";
289 default: return "?";
290 }
291 }
292
293
accounts_menu_set_status(struct gtk_mod * mod,struct ua * ua,enum ua_event ev)294 static void accounts_menu_set_status(struct gtk_mod *mod,
295 struct ua *ua, enum ua_event ev)
296 {
297 GtkMenuItem *item = accounts_menu_get_item(mod, ua);
298 char buf[256];
299 re_snprintf(buf, sizeof buf, "%s (%s)", ua_aor(ua),
300 ua_event_reg_str(ev));
301 gtk_menu_item_set_label(item, buf);
302 }
303
304
305 #ifdef USE_NOTIFICATIONS
notify_incoming_call(struct gtk_mod * mod,struct call * call)306 static void notify_incoming_call(struct gtk_mod *mod,
307 struct call *call)
308 {
309 static const char *title = "Incoming call";
310 const char *msg = call_peeruri(call);
311 GtkWidget *call_menu;
312 GtkWidget *menu_item;
313 #if defined(USE_LIBNOTIFY)
314 NotifyNotification *notification;
315
316 if (!notify_is_initted())
317 return;
318 notification = notify_notification_new(title, msg, "baresip");
319 notify_notification_set_urgency(notification, NOTIFY_URGENCY_CRITICAL);
320 notify_notification_show(notification, NULL);
321 g_object_unref(notification);
322
323 #elif GLIB_CHECK_VERSION(2,40,0)
324 char id[64];
325 GVariant *target;
326 GNotification *notification = g_notification_new(title);
327
328 re_snprintf(id, sizeof id, "incoming-call-%p", call);
329 id[sizeof id - 1] = '\0';
330
331 #if GLIB_CHECK_VERSION(2,42,0)
332 g_notification_set_priority(notification,
333 G_NOTIFICATION_PRIORITY_URGENT);
334 #else
335 g_notification_set_urgent(notification, TRUE);
336 #endif
337
338 target = g_variant_new_int64(GPOINTER_TO_INT(call));
339 g_notification_set_body(notification, msg);
340 g_notification_add_button_with_target_value(notification,
341 "Answer", "app.answer", target);
342 g_notification_add_button_with_target_value(notification,
343 "Reject", "app.reject", target);
344 g_application_send_notification(mod->app, id, notification);
345 g_object_unref(notification);
346
347 #else
348 (void)msg;
349 (void)title;
350 #endif
351
352 /* Add incoming call to the app menu */
353 call_menu = gtk_menu_new();
354 menu_item = gtk_menu_item_new_with_mnemonic("_Incoming call");
355 g_object_set_data(G_OBJECT(menu_item), "call", call);
356 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item),
357 call_menu);
358 gtk_menu_shell_prepend(GTK_MENU_SHELL(mod->app_menu), menu_item);
359 mod->incoming_call_menus = g_slist_append(mod->incoming_call_menus,
360 menu_item);
361
362 menu_item = gtk_menu_item_new_with_label(call_peeruri(call));
363 gtk_widget_set_sensitive(menu_item, FALSE);
364 gtk_menu_shell_append(GTK_MENU_SHELL(call_menu), menu_item);
365
366 menu_item = gtk_menu_item_new_with_mnemonic("_Accept");
367 g_object_set_data(G_OBJECT(menu_item), "call", call);
368 g_signal_connect(G_OBJECT(menu_item), "activate",
369 G_CALLBACK(menu_on_incoming_call_answer), mod);
370 gtk_menu_shell_append(GTK_MENU_SHELL(call_menu), menu_item);
371
372 menu_item = gtk_menu_item_new_with_mnemonic("_Reject");
373 g_object_set_data(G_OBJECT(menu_item), "call", call);
374 g_signal_connect(G_OBJECT(menu_item), "activate",
375 G_CALLBACK(menu_on_incoming_call_reject), mod);
376 gtk_menu_shell_append(GTK_MENU_SHELL(call_menu), menu_item);
377 }
378 #endif
379
380
denotify_incoming_call(struct gtk_mod * mod,struct call * call)381 static void denotify_incoming_call(struct gtk_mod *mod, struct call *call)
382 {
383 GSList *item, *next;
384
385 #if GLIB_CHECK_VERSION(2,40,0)
386 char id[64];
387
388 re_snprintf(id, sizeof id, "incoming-call-%p", call);
389 id[sizeof id - 1] = '\0';
390 g_application_withdraw_notification(mod->app, id);
391 #endif
392
393 /* Remove call submenu */
394 for (item = mod->incoming_call_menus; item; item = next) {
395 GtkWidget *menu_item = item->data;
396 next = item->next;
397
398 if (call == g_object_get_data(G_OBJECT(menu_item), "call")) {
399 gtk_widget_destroy(menu_item);
400 mod->incoming_call_menus =
401 g_slist_delete_link(mod->incoming_call_menus,
402 item);
403 }
404 }
405 }
406
407
answer_activated(GSimpleAction * action,GVariant * parameter,gpointer arg)408 static void answer_activated(GSimpleAction *action, GVariant *parameter,
409 gpointer arg)
410 {
411 struct gtk_mod *mod = arg;
412 struct call *call = get_call_from_gvariant(parameter);
413 (void)action;
414
415 if (call) {
416 denotify_incoming_call(mod, call);
417 mqueue_push(mod->mq, MQ_ANSWER, call);
418 }
419 }
420
421
reject_activated(GSimpleAction * action,GVariant * parameter,gpointer arg)422 static void reject_activated(GSimpleAction *action, GVariant *parameter,
423 gpointer arg)
424 {
425 struct gtk_mod *mod = arg;
426 struct call *call = get_call_from_gvariant(parameter);
427 (void)action;
428
429 if (call) {
430 denotify_incoming_call(mod, call);
431 mqueue_push(mod->mq, MQ_HANGUP, call);
432 }
433 }
434
435
new_call_window(struct gtk_mod * mod,struct call * call)436 static struct call_window *new_call_window(struct gtk_mod *mod,
437 struct call *call)
438 {
439 struct call_window *win = call_window_new(call, mod);
440 if (call) {
441 mod->call_windows = g_slist_append(mod->call_windows, win);
442 }
443 return win;
444 }
445
446
get_call_window(struct gtk_mod * mod,struct call * call)447 static struct call_window *get_call_window(struct gtk_mod *mod,
448 struct call *call)
449 {
450 GSList *wins;
451
452 for (wins = mod->call_windows; wins; wins = wins->next) {
453 struct call_window *win = wins->data;
454 if (call_window_is_for_call(win, call))
455 return win;
456 }
457 return NULL;
458 }
459
460
get_create_call_window(struct gtk_mod * mod,struct call * call)461 static struct call_window *get_create_call_window(struct gtk_mod *mod,
462 struct call *call)
463 {
464 struct call_window *win = get_call_window(mod, call);
465 if (!win)
466 win = new_call_window(mod, call);
467 return win;
468 }
469
470
gtk_mod_call_window_closed(struct gtk_mod * mod,struct call_window * win)471 void gtk_mod_call_window_closed(struct gtk_mod *mod, struct call_window *win)
472 {
473 if (!mod)
474 return;
475 mod->call_windows = g_slist_remove(mod->call_windows, win);
476 }
477
478
ua_event_handler(struct ua * ua,enum ua_event ev,struct call * call,const char * prm,void * arg)479 static void ua_event_handler(struct ua *ua,
480 enum ua_event ev,
481 struct call *call,
482 const char *prm,
483 void *arg )
484 {
485 struct gtk_mod *mod = arg;
486 struct call_window *win;
487
488 gdk_threads_enter();
489
490 switch (ev) {
491
492 case UA_EVENT_REGISTERING:
493 case UA_EVENT_UNREGISTERING:
494 case UA_EVENT_REGISTER_OK:
495 case UA_EVENT_REGISTER_FAIL:
496 accounts_menu_set_status(mod, ua, ev);
497 break;
498
499 #ifdef USE_NOTIFICATIONS
500 case UA_EVENT_CALL_INCOMING:
501 notify_incoming_call(mod, call);
502 break;
503 #endif
504
505 case UA_EVENT_CALL_CLOSED:
506 win = get_call_window(mod, call);
507 if (win)
508 call_window_closed(win, prm);
509 else
510 denotify_incoming_call(mod, call);
511 break;
512
513 case UA_EVENT_CALL_RINGING:
514 win = get_create_call_window(mod, call);
515 if (win)
516 call_window_ringing(win);
517 break;
518
519 case UA_EVENT_CALL_PROGRESS:
520 win = get_create_call_window(mod, call);
521 if (win)
522 call_window_progress(win);
523 break;
524
525 case UA_EVENT_CALL_ESTABLISHED:
526 win = get_create_call_window(mod, call);
527 if (win)
528 call_window_established(win);
529 break;
530
531 case UA_EVENT_CALL_TRANSFER_FAILED:
532 win = get_create_call_window(mod, call);
533 if (win)
534 call_window_transfer_failed(win, prm);
535 break;
536
537 default:
538 break;
539 }
540
541 gdk_threads_leave();
542 }
543
544
545 #ifdef USE_NOTIFICATIONS
message_handler(const struct pl * peer,const struct pl * ctype,struct mbuf * body,void * arg)546 static void message_handler(const struct pl *peer, const struct pl *ctype,
547 struct mbuf *body, void *arg)
548 {
549 struct gtk_mod *mod = arg;
550 char title[128];
551 char msg[512];
552
553 #if GLIB_CHECK_VERSION(2,40,0)
554 GNotification *notification;
555 #elif defined(USE_LIBNOTIFY)
556 NotifyNotification *notification;
557 #endif
558
559 (void)ctype;
560
561
562 /* Display notification of chat */
563
564 re_snprintf(title, sizeof title, "Chat from %r", peer);
565 title[sizeof title - 1] = '\0';
566
567 re_snprintf(msg, sizeof msg, "%b",
568 mbuf_buf(body), mbuf_get_left(body));
569
570 #if GLIB_CHECK_VERSION(2,40,0)
571 notification = g_notification_new(title);
572 g_notification_set_body(notification, msg);
573 g_application_send_notification(mod->app, NULL, notification);
574 g_object_unref(notification);
575
576 #elif defined(USE_LIBNOTIFY)
577 (void)mod;
578
579 if (!notify_is_initted())
580 return;
581 notification = notify_notification_new(title, msg, "baresip");
582 notify_notification_show(notification, NULL);
583 g_object_unref(notification);
584 #endif
585 }
586 #endif
587
588
popup_menu(struct gtk_mod * mod,GtkMenuPositionFunc position,gpointer position_arg,guint button,guint32 activate_time)589 static void popup_menu(struct gtk_mod *mod, GtkMenuPositionFunc position,
590 gpointer position_arg, guint button, guint32 activate_time)
591 {
592 if (!mod->contacts_inited) {
593 init_contacts_menu(mod);
594 mod->contacts_inited = TRUE;
595 }
596
597 /* Update things that may have been changed through another UI */
598 update_current_accounts_menu_item(mod);
599 update_ua_presence(mod);
600
601 gtk_widget_show_all(mod->app_menu);
602
603 gtk_menu_popup(GTK_MENU(mod->app_menu), NULL, NULL,
604 position, position_arg,
605 button, activate_time);
606 }
607
608
status_icon_on_button_press(GtkStatusIcon * status_icon,GdkEventButton * event,struct gtk_mod * mod)609 static gboolean status_icon_on_button_press(GtkStatusIcon *status_icon,
610 GdkEventButton *event,
611 struct gtk_mod *mod)
612 {
613 popup_menu(mod, gtk_status_icon_position_menu, status_icon,
614 event->button, event->time);
615 return TRUE;
616 }
617
618
gtk_mod_connect(struct gtk_mod * mod,const char * uri)619 void gtk_mod_connect(struct gtk_mod *mod, const char *uri)
620 {
621 if (!mod)
622 return;
623 mqueue_push(mod->mq, MQ_CONNECT, (char *)uri);
624 }
625
626
warning_dialog(const char * title,const char * fmt,...)627 static void warning_dialog(const char *title, const char *fmt, ...)
628 {
629 va_list ap;
630 char msg[512];
631 GtkWidget *dialog;
632
633 va_start(ap, fmt);
634 (void)re_vsnprintf(msg, sizeof msg, fmt, ap);
635 va_end(ap);
636
637 dialog = gtk_message_dialog_new(NULL, 0, GTK_MESSAGE_ERROR,
638 GTK_BUTTONS_CLOSE, "%s", title);
639 gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
640 "%s", msg);
641 g_signal_connect_swapped(G_OBJECT(dialog), "response",
642 G_CALLBACK(gtk_widget_destroy), dialog);
643 gtk_window_set_title(GTK_WINDOW(dialog), title);
644 gtk_widget_show(dialog);
645 }
646
647
mqueue_handler(int id,void * data,void * arg)648 static void mqueue_handler(int id, void *data, void *arg)
649 {
650 struct gtk_mod *mod = arg;
651 const char *uri;
652 struct call *call;
653 int err;
654 struct ua *ua = uag_current();
655 (void)mod;
656
657 switch ((enum gtk_mod_events)id) {
658
659 case MQ_POPUP:
660 gdk_threads_enter();
661 popup_menu(mod, NULL, NULL, 0, GPOINTER_TO_UINT(data));
662 gdk_threads_leave();
663 break;
664
665 case MQ_CONNECT:
666 uri = data;
667 err = ua_connect(ua, &call, NULL, uri, NULL, VIDMODE_ON);
668 if (err) {
669 gdk_threads_enter();
670 warning_dialog("Call failed",
671 "Connecting to \"%s\" failed.\n"
672 "Error: %m", uri, err);
673 gdk_threads_leave();
674 break;
675 }
676 gdk_threads_enter();
677 err = new_call_window(mod, call) == NULL;
678 gdk_threads_leave();
679 if (err) {
680 ua_hangup(ua, call, 500, "Server Error");
681 }
682 break;
683
684 case MQ_HANGUP:
685 call = data;
686 ua_hangup(ua, call, 0, NULL);
687 break;
688
689 case MQ_QUIT:
690 ua_stop_all(false);
691 break;
692
693 case MQ_ANSWER:
694 call = data;
695 err = ua_answer(ua, call);
696 if (err) {
697 gdk_threads_enter();
698 warning_dialog("Call failed",
699 "Answering the call "
700 "from \"%s\" failed.\n"
701 "Error: %m",
702 call_peername(call), err);
703 gdk_threads_leave();
704 break;
705 }
706
707 gdk_threads_enter();
708 err = new_call_window(mod, call) == NULL;
709 gdk_threads_leave();
710 if (err) {
711 ua_hangup(ua, call, 500, "Server Error");
712 }
713 break;
714
715 case MQ_SELECT_UA:
716 ua = data;
717 uag_current_set(ua);
718 break;
719 }
720 }
721
722
gtk_thread(void * arg)723 static void *gtk_thread(void *arg)
724 {
725 struct gtk_mod *mod = arg;
726 GtkMenuShell *app_menu;
727 GtkWidget *item;
728 GError *err = NULL;
729 struct le *le;
730
731 gdk_threads_init();
732 gtk_init(0, NULL);
733
734 g_set_application_name("baresip");
735 mod->app = g_application_new ("com.creytiv.baresip",
736 G_APPLICATION_FLAGS_NONE);
737
738 g_application_register (G_APPLICATION (mod->app), NULL, &err);
739 if (err != NULL) {
740 warning ("Unable to register GApplication: %s",
741 err->message);
742 g_error_free (err);
743 err = NULL;
744 }
745
746 #ifdef USE_LIBNOTIFY
747 notify_init("baresip");
748 #endif
749
750 mod->status_icon = gtk_status_icon_new_from_icon_name("call-start");
751 gtk_status_icon_set_tooltip_text (mod->status_icon, "baresip");
752
753 g_signal_connect(G_OBJECT(mod->status_icon),
754 "button_press_event",
755 G_CALLBACK(status_icon_on_button_press), mod);
756 gtk_status_icon_set_visible(mod->status_icon, TRUE);
757
758 mod->contacts_inited = false;
759 mod->dial_dialog = NULL;
760 mod->call_windows = NULL;
761 mod->incoming_call_menus = NULL;
762
763 /* App menu */
764 mod->app_menu = gtk_menu_new();
765 app_menu = GTK_MENU_SHELL(mod->app_menu);
766
767 /* Account submenu */
768 mod->accounts_menu = gtk_menu_new();
769 mod->accounts_menu_group = NULL;
770 item = gtk_menu_item_new_with_mnemonic("_Account");
771 gtk_menu_shell_append(app_menu, item);
772 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item),
773 mod->accounts_menu);
774
775 /* Add accounts to submenu */
776 for (le = list_head(uag_list()); le; le = le->next) {
777 struct ua *ua = le->data;
778 accounts_menu_add_item(mod, ua);
779 }
780
781 /* Status submenu */
782 mod->status_menu = gtk_menu_new();
783 item = gtk_menu_item_new_with_mnemonic("_Status");
784 gtk_menu_shell_append(GTK_MENU_SHELL(app_menu), item);
785 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), mod->status_menu);
786
787 /* Open */
788 item = gtk_radio_menu_item_new_with_label(NULL, "Open");
789 g_object_set_data(G_OBJECT(item), "presence",
790 GINT_TO_POINTER(PRESENCE_OPEN));
791 g_signal_connect(item, "activate",
792 G_CALLBACK(menu_on_presence_set), mod);
793 gtk_menu_shell_append(GTK_MENU_SHELL(mod->status_menu), item);
794 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
795
796 /* Closed */
797 item = gtk_radio_menu_item_new_with_label_from_widget(
798 GTK_RADIO_MENU_ITEM(item), "Closed");
799 g_object_set_data(G_OBJECT(item), "presence",
800 GINT_TO_POINTER(PRESENCE_CLOSED));
801 g_signal_connect(item, "activate",
802 G_CALLBACK(menu_on_presence_set), mod);
803 gtk_menu_shell_append(GTK_MENU_SHELL(mod->status_menu), item);
804
805 gtk_menu_shell_append(app_menu, gtk_separator_menu_item_new());
806
807 /* Dial */
808 item = gtk_menu_item_new_with_mnemonic("_Dial...");
809 gtk_menu_shell_append(app_menu, item);
810 g_signal_connect(G_OBJECT(item), "activate",
811 G_CALLBACK(menu_on_dial), mod);
812
813 /* Dial contact */
814 mod->contacts_menu = gtk_menu_new();
815 item = gtk_menu_item_new_with_mnemonic("Dial _contact");
816 gtk_menu_shell_append(app_menu, item);
817 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item),
818 mod->contacts_menu);
819
820 gtk_menu_shell_append(app_menu, gtk_separator_menu_item_new());
821
822 /* About */
823 item = gtk_menu_item_new_with_mnemonic("A_bout");
824 g_signal_connect(G_OBJECT(item), "activate",
825 G_CALLBACK(menu_on_about), mod);
826 gtk_menu_shell_append(app_menu, item);
827
828 gtk_menu_shell_append(app_menu, gtk_separator_menu_item_new());
829
830 /* Quit */
831 item = gtk_menu_item_new_with_mnemonic("_Quit");
832 g_signal_connect(G_OBJECT(item), "activate",
833 G_CALLBACK(menu_on_quit), mod);
834 gtk_menu_shell_append(app_menu, item);
835
836 g_action_map_add_action_entries(G_ACTION_MAP(mod->app),
837 app_entries, G_N_ELEMENTS(app_entries), mod);
838
839 info("gtk_menu starting\n");
840
841 uag_event_register( ua_event_handler, mod );
842 mod->run = true;
843 gtk_main();
844 mod->run = false;
845 uag_event_unregister(ua_event_handler);
846
847 if (mod->dial_dialog) {
848 mem_deref(mod->dial_dialog);
849 mod->dial_dialog = NULL;
850 }
851
852 return NULL;
853 }
854
855
vu_enc_destructor(void * arg)856 static void vu_enc_destructor(void *arg)
857 {
858 struct vumeter_enc *st = arg;
859
860 list_unlink(&st->af.le);
861 }
862
863
vu_dec_destructor(void * arg)864 static void vu_dec_destructor(void *arg)
865 {
866 struct vumeter_dec *st = arg;
867
868 list_unlink(&st->af.le);
869 }
870
871
calc_avg_s16(const int16_t * sampv,size_t sampc)872 static int16_t calc_avg_s16(const int16_t *sampv, size_t sampc)
873 {
874 int32_t v = 0;
875 size_t i;
876
877 if (!sampv || !sampc)
878 return 0;
879
880 for (i=0; i<sampc; i++)
881 v += abs(sampv[i]);
882
883 return v/sampc;
884 }
885
886
vu_encode_update(struct aufilt_enc_st ** stp,void ** ctx,const struct aufilt * af,struct aufilt_prm * prm)887 static int vu_encode_update(struct aufilt_enc_st **stp, void **ctx,
888 const struct aufilt *af, struct aufilt_prm *prm)
889 {
890 struct vumeter_enc *st;
891 (void)ctx;
892 (void)prm;
893
894 if (!stp || !af)
895 return EINVAL;
896
897 if (*stp)
898 return 0;
899
900 st = mem_zalloc(sizeof(*st), vu_enc_destructor);
901 if (!st)
902 return ENOMEM;
903
904 gdk_threads_enter();
905 call_window_got_vu_enc(st);
906 gdk_threads_leave();
907
908 *stp = (struct aufilt_enc_st *)st;
909
910 return 0;
911 }
912
913
vu_decode_update(struct aufilt_dec_st ** stp,void ** ctx,const struct aufilt * af,struct aufilt_prm * prm)914 static int vu_decode_update(struct aufilt_dec_st **stp, void **ctx,
915 const struct aufilt *af, struct aufilt_prm *prm)
916 {
917 struct vumeter_dec *st;
918 (void)ctx;
919 (void)prm;
920
921 if (!stp || !af)
922 return EINVAL;
923
924 if (*stp)
925 return 0;
926
927 st = mem_zalloc(sizeof(*st), vu_dec_destructor);
928 if (!st)
929 return ENOMEM;
930
931 gdk_threads_enter();
932 call_window_got_vu_dec(st);
933 gdk_threads_leave();
934
935 *stp = (struct aufilt_dec_st *)st;
936
937 return 0;
938 }
939
940
vu_encode(struct aufilt_enc_st * st,int16_t * sampv,size_t * sampc)941 static int vu_encode(struct aufilt_enc_st *st, int16_t *sampv, size_t *sampc)
942 {
943 struct vumeter_enc *vu = (struct vumeter_enc *)st;
944
945 vu->avg_rec = calc_avg_s16(sampv, *sampc);
946 vu->started = true;
947
948 return 0;
949 }
950
951
vu_decode(struct aufilt_dec_st * st,int16_t * sampv,size_t * sampc)952 static int vu_decode(struct aufilt_dec_st *st, int16_t *sampv, size_t *sampc)
953 {
954 struct vumeter_dec *vu = (struct vumeter_dec *)st;
955
956 vu->avg_play = calc_avg_s16(sampv, *sampc);
957 vu->started = true;
958
959 return 0;
960 }
961
962
963 static struct aufilt vumeter = {
964 LE_INIT, "gtk_vumeter",
965 vu_encode_update, vu_encode,
966 vu_decode_update, vu_decode
967 };
968
969
cmd_popup_menu(struct re_printf * pf,void * unused)970 static int cmd_popup_menu(struct re_printf *pf, void *unused)
971 {
972 (void)pf;
973 (void)unused;
974
975 mqueue_push(mod_obj.mq, MQ_POPUP, GUINT_TO_POINTER(GDK_CURRENT_TIME));
976
977 return 0;
978 }
979
980
981 static const struct cmd cmdv[] = {
982 {"gtk", 'G', 0, "Pop up GTK+ menu", cmd_popup_menu },
983 };
984
985
module_init(void)986 static int module_init(void)
987 {
988 int err = 0;
989
990 err = mqueue_alloc(&mod_obj.mq, mqueue_handler, &mod_obj);
991 if (err)
992 return err;
993
994 aufilt_register(baresip_aufiltl(), &vumeter);
995
996 #ifdef USE_NOTIFICATIONS
997 err = message_listen(&mod_obj.message, baresip_message(),
998 message_handler, &mod_obj);
999 if (err) {
1000 warning("gtk: message_init failed (%m)\n", err);
1001 return err;
1002 }
1003 #endif
1004
1005 err = cmd_register(baresip_commands(), cmdv, ARRAY_SIZE(cmdv));
1006 if (err)
1007 return err;
1008
1009 /* start the thread last */
1010 err = pthread_create(&mod_obj.thread, NULL, gtk_thread,
1011 &mod_obj);
1012 if (err)
1013 return err;
1014
1015 return err;
1016 }
1017
1018
module_close(void)1019 static int module_close(void)
1020 {
1021 cmd_unregister(baresip_commands(), cmdv);
1022 if (mod_obj.run) {
1023 gdk_threads_enter();
1024 gtk_main_quit();
1025 gdk_threads_leave();
1026 }
1027 if (mod_obj.thread)
1028 pthread_join(mod_obj.thread, NULL);
1029 mod_obj.mq = mem_deref(mod_obj.mq);
1030 aufilt_unregister(&vumeter);
1031 mod_obj.message = mem_deref(mod_obj.message);
1032
1033 #ifdef USE_LIBNOTIFY
1034 if (notify_is_initted())
1035 notify_uninit();
1036 #endif
1037
1038 g_slist_free(mod_obj.accounts_menu_group);
1039 g_slist_free(mod_obj.call_windows);
1040 g_slist_free(mod_obj.incoming_call_menus);
1041
1042 uag_event_unregister(ua_event_handler);
1043
1044 return 0;
1045 }
1046
1047
1048 EXPORT_SYM const struct mod_export DECL_EXPORTS(gtk) = {
1049 "gtk",
1050 "application",
1051 module_init,
1052 module_close,
1053 };
1054