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