diff options
author | Charles Lehner <cel@celehner.com> | 2015-07-05 13:55:32 -0400 |
---|---|---|
committer | Charles Lehner <cel@celehner.com> | 2015-07-05 15:11:49 -0400 |
commit | 9ef06aa7fb2a236e6a41cd9c5cb34c73a4a7a378 (patch) | |
tree | 3ff77340f4ef6e58d18b52d81d8d8c18ce71a2ce /modules/gtk | |
parent | 5c6821f742c2b3751fff5574bba3666826d723f1 (diff) |
Add gtk module
Diffstat (limited to 'modules/gtk')
-rw-r--r-- | modules/gtk/call_window.c | 489 | ||||
-rw-r--r-- | modules/gtk/dial_dialog.c | 96 | ||||
-rw-r--r-- | modules/gtk/gtk_mod.c | 853 | ||||
-rw-r--r-- | modules/gtk/gtk_mod.h | 51 | ||||
-rw-r--r-- | modules/gtk/module.mk | 14 | ||||
-rw-r--r-- | modules/gtk/transfer_dialog.c | 131 | ||||
-rw-r--r-- | modules/gtk/uri_entry.c | 44 |
7 files changed, 1678 insertions, 0 deletions
diff --git a/modules/gtk/call_window.c b/modules/gtk/call_window.c new file mode 100644 index 0000000..11677ff --- /dev/null +++ b/modules/gtk/call_window.c @@ -0,0 +1,489 @@ +/** + * @file gtk/call_window.c GTK+ call window + * + * Copyright (C) 2015 Charles E. Lehner + */ + +#include <re.h> +#include <baresip.h> +#include <gtk/gtk.h> +#include "gtk_mod.h" + +struct call_window { + struct gtk_mod *mod; + struct call *call; + struct mqueue *mq; /* for communicating from gtk thread to main thread */ + struct { + struct vumeter_dec *dec; + struct vumeter_enc *enc; + } vu; + struct transfer_dialog *transfer_dialog; + GtkWidget *window; + GtkLabel *status; + GtkLabel *duration; + struct { + GtkWidget *hangup, *transfer, *hold, *mute; + } buttons; + struct { + GtkProgressBar *enc, *dec; + } progress; + guint duration_timer_tag; + guint vumeter_timer_tag; + bool closed; + int cur_key; +}; + +enum call_window_events { + MQ_HANGUP, + MQ_CLOSE, + MQ_HOLD, + MQ_MUTE, + MQ_TRANSFER, +}; + +static struct call_window *last_call_win = NULL; +static struct vumeter_dec *last_dec = NULL; +static struct vumeter_enc *last_enc = NULL; + + +static void call_window_update_duration(struct call_window *win) { + gchar buf[32]; + + const uint32_t dur = call_duration(win->call); + const uint32_t sec = dur%60%60; + const uint32_t min = dur/60%60; + const uint32_t hrs = dur/60/60; + + re_snprintf(buf, sizeof buf, "%u:%02u:%02u", hrs, min, sec); + gtk_label_set_text(win->duration, buf); +} + + +static void call_window_update_vumeters(struct call_window *win) { + double value; + + if (win->vu.enc && win->vu.enc->started) { + value = min((double)win->vu.enc->avg_rec / 0x4000, 1); + gtk_progress_bar_set_fraction(win->progress.enc, value); + } + if (win->vu.dec && win->vu.dec->started) { + value = min((double)win->vu.dec->avg_play / 0x4000, 1); + gtk_progress_bar_set_fraction(win->progress.dec, value); + } +} + + +static gboolean call_timer(gpointer arg) { + struct call_window *win = arg; + call_window_update_duration(win); + return G_SOURCE_CONTINUE; +} + + +static gboolean vumeter_timer(gpointer arg) { + struct call_window *win = arg; + call_window_update_vumeters(win); + return G_SOURCE_CONTINUE; +} + +static void vumeter_timer_start(struct call_window *win) { + if (!win->vumeter_timer_tag) + win->vumeter_timer_tag = + g_timeout_add(100, vumeter_timer, win); + if (win->vu.enc) + win->vu.enc->avg_rec = 0; + if (win->vu.dec) + win->vu.dec->avg_play = 0; +} + +static void vumeter_timer_stop(struct call_window *win) { + if (win->vumeter_timer_tag) { + g_source_remove(win->vumeter_timer_tag); + win->vumeter_timer_tag = 0; + } + gtk_progress_bar_set_fraction(win->progress.enc, 0); + gtk_progress_bar_set_fraction(win->progress.dec, 0); +} + +static void call_window_set_vu_dec(struct call_window *win, + struct vumeter_dec *dec) +{ + if (win->vu.dec) + mem_deref(win->vu.dec); + win->vu.dec = mem_ref(dec); + vumeter_timer_start(win); +} + +static void call_window_set_vu_enc(struct call_window *win, + struct vumeter_enc *enc) +{ + if (win->vu.enc) + mem_deref(win->vu.enc); + win->vu.enc = mem_ref(enc); + vumeter_timer_start(win); +} + +/* This is a hack to associate a call with its vumeters */ + +void call_window_got_vu_dec(struct vumeter_dec *dec) +{ + if (last_call_win) + call_window_set_vu_dec(last_call_win, dec); + else + last_dec = dec; +} + +void call_window_got_vu_enc(struct vumeter_enc *enc) +{ + if (last_call_win) + call_window_set_vu_enc(last_call_win, enc); + else + last_enc = enc; +} + +static void got_call_window(struct call_window *win) +{ + if (last_enc) + call_window_set_vu_enc(win, last_enc); + if (last_dec) + call_window_set_vu_dec(win, last_dec); + if (!last_enc || !last_dec) + last_call_win = win; +} + + +static void call_on_hangup(GtkToggleButton *btn, struct call_window *win) +{ + (void)btn; + mqueue_push(win->mq, MQ_CLOSE, win); +} + +static void call_on_hold_toggle(GtkToggleButton *btn, struct call_window *win) +{ + bool hold = gtk_toggle_button_get_active(btn); + if (hold) + vumeter_timer_stop(win); + else + vumeter_timer_start(win); + mqueue_push(win->mq, MQ_HOLD, (void *)(size_t)hold); +} + +static void call_on_mute_toggle(GtkToggleButton *btn, struct call_window *win) +{ + bool mute = gtk_toggle_button_get_active(btn); + mqueue_push(win->mq, MQ_MUTE, (void *)(size_t)mute); +} + +static void call_on_transfer(GtkToggleButton *btn, struct call_window *win) +{ + (void)btn; + if (!win->transfer_dialog) + win->transfer_dialog = transfer_dialog_alloc(win); + else + transfer_dialog_show(win->transfer_dialog); +} + +static gboolean call_on_window_close(GtkWidget *widget, GdkEventAny *event, + struct call_window *win) +{ + (void)event; + (void)widget; + mqueue_push(win->mq, MQ_CLOSE, NULL); + return TRUE; +} + + +static gboolean call_on_key_press(GtkWidget *window, GdkEvent *ev, + struct call_window *win) +{ + gchar key = ev->key.string[0]; + (void)window; + + switch (key) { + + case '1': case '2': case '3': + case '4': case '5': case '6': + case '7': case '8': case '9': + case '*': case '0': case '#': + win->cur_key = key; + call_send_digit(win->call, key); + return TRUE; + + default: + return FALSE; + } +} + + +static gboolean call_on_key_release(GtkWidget *window, GdkEvent *ev, + struct call_window *win) +{ + (void)window; + + if (win->cur_key && win->cur_key == ev->key.string[0]) { + win->cur_key = 0; + call_send_digit(win->call, 0); + return TRUE; + } + + return FALSE; +} + + +static void call_window_set_status(struct call_window *win, + const char *status) +{ + gtk_label_set_text(win->status, status); +} + + +static void mqueue_handler(int id, void *data, void *arg) +{ + struct call_window *win = arg; + + switch ((enum call_window_events)id) { + + case MQ_HANGUP: + ua_hangup(uag_current(), win->call, 0, NULL); + break; + + case MQ_CLOSE: + ua_hangup(uag_current(), win->call, 0, NULL); + win->closed = true; + mem_deref(win); + break; + + case MQ_MUTE: + audio_mute(call_audio(win->call), (size_t)data); + break; + + case MQ_HOLD: + call_hold(win->call, (size_t)data); + break; + + case MQ_TRANSFER: + call_transfer(win->call, data); + break; + } +} + +static void call_window_destructor(void *arg) +{ + struct call_window *window = arg; + + gdk_threads_enter(); + gtk_mod_call_window_closed(window->mod, window); + gtk_widget_destroy(window->window); + mem_deref(window->transfer_dialog); + gdk_threads_leave(); + + mem_deref(window->call); + mem_deref(window->mq); + mem_deref(window->vu.enc); + mem_deref(window->vu.dec); + if (window->duration_timer_tag) + g_source_remove(window->duration_timer_tag); + if (window->vumeter_timer_tag) + g_source_remove(window->vumeter_timer_tag); + /* TODO: avoid race conditions here */ + last_call_win = NULL; +} + +struct call_window *call_window_new(struct call *call, struct gtk_mod *mod) +{ + struct call_window *win; + GtkWidget *window, *label, *status, *button, *progress, *image; + GtkWidget *button_box, *vbox, *hbox; + GtkWidget *duration; + int err = 0; + + win = mem_zalloc(sizeof(*win), call_window_destructor); + if (!win) + return NULL; + + mqueue_alloc(&win->mq, mqueue_handler, win); + if (err) + goto out; + + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_title(GTK_WINDOW(window), call_peeruri(call)); + gtk_window_set_type_hint(GTK_WINDOW(window), + GDK_WINDOW_TYPE_HINT_DIALOG); + + vbox = gtk_vbox_new (FALSE, 0); + gtk_container_add(GTK_CONTAINER(window), vbox); + + /* Peer name and URI */ + label = gtk_label_new(call_peername(call)); + gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0); + + label = gtk_label_new(call_peeruri(call)); + gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0); + + /* Call duration */ + duration = gtk_label_new(NULL); + gtk_box_pack_start(GTK_BOX(vbox), duration, FALSE, FALSE, 0); + + /* Status */ + status = gtk_label_new(NULL); + gtk_box_pack_start(GTK_BOX(vbox), status, FALSE, FALSE, 0); + + /* Progress bars */ + hbox = gtk_hbox_new(FALSE, 0); + gtk_box_set_spacing(GTK_BOX(hbox), 6); + gtk_container_set_border_width(GTK_CONTAINER(hbox), 5); + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); + + /* Encoding vumeter */ + image = gtk_image_new_from_icon_name("audio-input-microphone", + GTK_ICON_SIZE_BUTTON); + progress = gtk_progress_bar_new(); + win->progress.enc = GTK_PROGRESS_BAR(progress); + gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(hbox), progress, FALSE, FALSE, 0); + + /* Decoding vumeter */ + image = gtk_image_new_from_icon_name("audio-headphones", + GTK_ICON_SIZE_BUTTON); + progress = gtk_progress_bar_new(); + win->progress.dec = GTK_PROGRESS_BAR(progress); + gtk_box_pack_end(GTK_BOX(hbox), progress, FALSE, FALSE, 0); + gtk_box_pack_end(GTK_BOX(hbox), image, FALSE, FALSE, 0); + + /* Buttons */ + button_box = gtk_hbutton_box_new(); + gtk_button_box_set_layout(GTK_BUTTON_BOX(button_box), + GTK_BUTTONBOX_END); + gtk_box_set_spacing(GTK_BOX(button_box), 6); + gtk_container_set_border_width(GTK_CONTAINER(button_box), 5); + gtk_box_pack_end(GTK_BOX(vbox), button_box, FALSE, TRUE, 0); + + /* Hang up */ + button = gtk_button_new_with_label("Hangup"); + win->buttons.hangup = button; + gtk_box_pack_end(GTK_BOX(button_box), button, FALSE, TRUE, 0); + g_signal_connect(button, "clicked", + G_CALLBACK(call_on_hangup), win); + image = gtk_image_new_from_icon_name("call-stop", + GTK_ICON_SIZE_BUTTON); + gtk_button_set_image(GTK_BUTTON(button), image); + + /* Transfer */ + button = gtk_button_new_with_label("Transfer"); + win->buttons.transfer = button; + gtk_box_pack_end(GTK_BOX(button_box), button, FALSE, TRUE, 0); + g_signal_connect(button, "clicked", G_CALLBACK(call_on_transfer), win); + image = gtk_image_new_from_icon_name("forward", GTK_ICON_SIZE_BUTTON); + gtk_button_set_image(GTK_BUTTON(button), image); + + /* Hold */ + button = gtk_toggle_button_new_with_label("Hold"); + win->buttons.hold = button; + gtk_box_pack_end(GTK_BOX(button_box), button, FALSE, TRUE, 0); + g_signal_connect(button, "toggled", + G_CALLBACK(call_on_hold_toggle), win); + image = gtk_image_new_from_icon_name("player_pause", + GTK_ICON_SIZE_BUTTON); + gtk_button_set_image(GTK_BUTTON(button), image); + + /* Mute */ + button = gtk_toggle_button_new_with_label("Mute"); + win->buttons.mute = button; + gtk_box_pack_end(GTK_BOX(button_box), button, FALSE, TRUE, 0); + g_signal_connect(button, "toggled", + G_CALLBACK(call_on_mute_toggle), win); + image = gtk_image_new_from_icon_name("microphone-sensitivity-muted", + GTK_ICON_SIZE_BUTTON); + gtk_button_set_image(GTK_BUTTON(button), image); + + gtk_widget_show_all(window); + gtk_window_present(GTK_WINDOW(window)); + + g_signal_connect(window, "delete_event", + G_CALLBACK(call_on_window_close), win); + g_signal_connect(window, "key-press-event", + G_CALLBACK(call_on_key_press), win); + g_signal_connect(window, "key-release-event", + G_CALLBACK(call_on_key_release), win); + + win->call = mem_ref(call); + win->mod = mod; + win->window = window; + win->transfer_dialog = NULL; + win->status = GTK_LABEL(status); + win->duration = GTK_LABEL(duration); + win->closed = false; + win->duration_timer_tag = 0; + win->vumeter_timer_tag = 0; + win->vu.enc = NULL; + win->vu.dec = NULL; + + got_call_window(win); + +out: + if (err) + mem_deref(win); + + return win; +} + +void call_window_transfer(struct call_window *win, const char *uri) +{ + mqueue_push(win->mq, MQ_TRANSFER, (char *)uri); +} + +void call_window_closed(struct call_window *win, const char *reason) +{ + char buf[256]; + const char *status; + + vumeter_timer_stop(win); + if (win->duration_timer_tag) { + g_source_remove(win->duration_timer_tag); + win->duration_timer_tag = 0; + } + gtk_widget_set_sensitive(win->buttons.transfer, FALSE); + gtk_widget_set_sensitive(win->buttons.hold, FALSE); + gtk_widget_set_sensitive(win->buttons.mute, FALSE); + + if (reason && reason[0]) { + re_snprintf(buf, sizeof buf, "closed: %s", reason); + status = buf; + } else { + status = "closed"; + } + call_window_set_status(win, status); + win->transfer_dialog = mem_deref(win->transfer_dialog); +} + +void call_window_ringing(struct call_window *win) +{ + call_window_set_status(win, "ringing"); +} + +void call_window_progress(struct call_window *win) +{ + win->duration_timer_tag = g_timeout_add_seconds(1, call_timer, win); + last_call_win = win; + call_window_set_status(win, "progress"); +} + +void call_window_established(struct call_window *win) +{ + call_window_update_duration(win); + win->duration_timer_tag = g_timeout_add_seconds(1, call_timer, win); + last_call_win = win; + call_window_set_status(win, "established"); +} + +void call_window_transfer_failed(struct call_window *win, const char *reason) +{ + if (win->transfer_dialog) { + transfer_dialog_fail(win->transfer_dialog, reason); + } +} + +bool call_window_is_for_call(struct call_window *win, struct call *call) +{ + return win->call == call; +} diff --git a/modules/gtk/dial_dialog.c b/modules/gtk/dial_dialog.c new file mode 100644 index 0000000..340362b --- /dev/null +++ b/modules/gtk/dial_dialog.c @@ -0,0 +1,96 @@ +/** + * @file gtk/dial_dialog.c GTK+ dial dialog + * + * Copyright (C) 2015 Charles E. Lehner + */ + +#include <re.h> +#include <baresip.h> +#include <stdlib.h> +#include <pthread.h> +#include <gtk/gtk.h> +#include "gtk_mod.h" + +struct dial_dialog { + struct gtk_mod *mod; + GtkWidget *dialog; + GtkComboBox *uri_combobox; +}; + +static void dial_dialog_on_response(GtkDialog *dialog, gint response_id, + gpointer arg) +{ + struct dial_dialog *dd = arg; + char *uri; + + if (response_id == GTK_RESPONSE_ACCEPT) { + uri = (char *)uri_combo_box_get_text(dd->uri_combobox); + gtk_mod_connect(dd->mod, uri); + } + + gtk_widget_hide(GTK_WIDGET(dialog)); +} + + +static void destructor(void *arg) +{ + struct dial_dialog *dd = arg; + + gtk_widget_destroy(dd->dialog); +} + +struct dial_dialog *dial_dialog_alloc(struct gtk_mod *mod) +{ + struct dial_dialog *dd; + GtkWidget *dial; + GtkWidget *content, *button, *image; + GtkWidget *uri_combobox; + + dd = mem_zalloc(sizeof(*dd), destructor); + if (!dd) + return NULL; + + dial = gtk_dialog_new_with_buttons("Dial", NULL, 0, NULL); + + /* Cancel */ + button = gtk_button_new_with_label("Cancel"); + image = gtk_image_new_from_icon_name("call-stop", + GTK_ICON_SIZE_BUTTON); + gtk_button_set_image(GTK_BUTTON(button), image); + gtk_dialog_add_action_widget(GTK_DIALOG(dial), button, + GTK_RESPONSE_REJECT); + + /* Call */ + button = gtk_button_new_with_label("Call"); + image = gtk_image_new_from_icon_name("call-start", + GTK_ICON_SIZE_BUTTON); + gtk_button_set_image(GTK_BUTTON(button), image); + gtk_dialog_add_action_widget(GTK_DIALOG(dial), button, + GTK_RESPONSE_ACCEPT); + gtk_widget_set_can_default (button, TRUE); + + gtk_dialog_set_default_response(GTK_DIALOG(dial), + GTK_RESPONSE_ACCEPT); + uri_combobox = uri_combo_box_new(); + + content = gtk_dialog_get_content_area(GTK_DIALOG(dial)); + gtk_box_pack_start(GTK_BOX(content), uri_combobox, FALSE, FALSE, 5); + gtk_widget_show_all(content); + + g_signal_connect(G_OBJECT(dial), "response", + G_CALLBACK(dial_dialog_on_response), dd); + g_signal_connect(G_OBJECT(dial), "delete-event", + G_CALLBACK(gtk_widget_hide_on_delete), dd); + + dd->dialog = dial; + dd->uri_combobox = GTK_COMBO_BOX(uri_combobox); + dd->mod = mod; + + return dd; +} + +void dial_dialog_show(struct dial_dialog *dd) +{ + gtk_window_present(GTK_WINDOW(dd->dialog)); + gtk_widget_grab_focus(gtk_bin_get_child(GTK_BIN(dd->uri_combobox))); +} diff --git a/modules/gtk/gtk_mod.c b/modules/gtk/gtk_mod.c new file mode 100644 index 0000000..813c0f1 --- /dev/null +++ b/modules/gtk/gtk_mod.c @@ -0,0 +1,853 @@ +/** + * @file gtk/gtk_mod.c GTK+ UI module + * + * Copyright (C) 2015 Charles E. Lehner + * Copyright (C) 2010 - 2015 Creytiv.com + */ +#include <re.h> +#include <baresip.h> +#include <stdlib.h> +#include <pthread.h> +#include <gtk/gtk.h> +#include <gio/gio.h> +#include "gtk_mod.h" + +/* About */ +#define COPYRIGHT " Copyright (C) 2010 - 2015 Alfred E. Heggestad et al." +#define COMMENTS "A modular SIP User-Agent with audio and video support" +#define WEBSITE "http://www.creytiv.com/baresip.html" +#define LICENSE "BSD" + +/** + * @defgroup gtk_mod gtk_mod + * + * GTK+ Menu-based User-Interface module + * + * Creates a tray icon with a menu for making calls. + * + */ + +struct gtk_mod { + pthread_t thread; + bool run; + bool contacts_inited; + bool accounts_inited; + struct mqueue *mq; + GApplication *app; + GtkStatusIcon *status_icon; + GtkWidget *app_menu; + GtkWidget *contacts_menu; + GtkWidget *accounts_menu; + GtkWidget *status_menu; + GSList *accounts_menu_group; + struct dial_dialog *dial_dialog; + GSList *call_windows; +}; + +struct gtk_mod mod_obj; + +enum gtk_mod_events { + MQ_CONNECT, + MQ_QUIT, + MQ_ANSWER, + MQ_HANGUP, + MQ_SELECT_UA, +}; + +static void answer_activated(GSimpleAction *, GVariant *, gpointer); +static void reject_activated(GSimpleAction *, GVariant *, gpointer); + +static GActionEntry app_entries[] = { + {"answer", answer_activated, "x", NULL, NULL, {0} }, + {"reject", reject_activated, "x", NULL, NULL, {0} }, +}; + +static struct call *get_call_from_gvariant(GVariant *param) +{ + gint64 call_ptr; + struct call *call; + struct list *calls = ua_calls(uag_current()); + struct le *le; + + call_ptr = g_variant_get_int64(param); + call = GINT_TO_POINTER(call_ptr); + + for (le = list_head(calls); le; le = le->next) + if (le->data == call) + return call; + + return NULL; +} + + +static void menu_on_about(GtkMenuItem *menuItem, gpointer arg) +{ + (void)menuItem; + (void)arg; + + gtk_show_about_dialog(NULL, + "program-name", "baresip", + "version", BARESIP_VERSION, + "logo-icon-name", "call-start", + "copyright", COPYRIGHT, + "comments", COMMENTS, + "website", WEBSITE, + "license", LICENSE, + NULL); +} + +static void menu_on_quit(GtkMenuItem *menuItem, gpointer arg) +{ + struct gtk_mod *mod = arg; + (void)menuItem; + + gtk_widget_destroy(GTK_WIDGET(mod->app_menu)); + g_object_unref(G_OBJECT(mod->status_icon)); + + mqueue_push(mod->mq, MQ_QUIT, 0); + info("quit from gtk\n"); +} + +static void menu_on_dial(GtkMenuItem *menuItem, gpointer arg) +{ + struct gtk_mod *mod = arg; + (void)menuItem; + if (!mod->dial_dialog) + mod->dial_dialog = dial_dialog_alloc(mod); + dial_dialog_show(mod->dial_dialog); +} + +static void menu_on_dial_contact(GtkMenuItem *menuItem, gpointer arg) +{ + struct gtk_mod *mod = arg; + const char *uri = gtk_menu_item_get_label(menuItem); + /* Queue dial from the main thread */ + mqueue_push(mod->mq, MQ_CONNECT, (char *)uri); +} + +static void init_contacts_menu(struct gtk_mod *mod) +{ + struct le *le; + GtkWidget *item; + GtkMenuShell *contacts_menu = GTK_MENU_SHELL(mod->contacts_menu); + + /* Add contacts to submenu */ + for (le = list_head(contact_list()); le; le = le->next) { + struct contact *c = le->data; + item = gtk_menu_item_new_with_label(contact_str(c)); + gtk_menu_shell_append(contacts_menu, item); + g_signal_connect(G_OBJECT(item), "activate", + G_CALLBACK(menu_on_dial_contact), mod); + } +} + + +static void menu_on_account_toggled(GtkCheckMenuItem *menu_item, + struct gtk_mod *mod) +{ + struct ua *ua = g_object_get_data(G_OBJECT(menu_item), "ua"); + if (menu_item->active) + mqueue_push(mod->mq, MQ_SELECT_UA, ua); +} + + +static void menu_on_presence_set(GtkMenuItem *item, struct gtk_mod *mod) +{ + struct le *le; + void *type = g_object_get_data(G_OBJECT(item), "presence"); + enum presence_status status = GPOINTER_TO_UINT(type); + (void)mod; + + for (le = list_head(uag_list()); le; le = le->next) { + struct ua *ua = le->data; + ua_presence_status_set(ua, status); + } +} + + +static GtkMenuItem *accounts_menu_add_item(struct gtk_mod *mod, + struct ua *ua) +{ + GtkMenuShell *accounts_menu = GTK_MENU_SHELL(mod->accounts_menu); + GtkWidget *item; + GSList *group = mod->accounts_menu_group; + struct ua *ua_current = uag_current(); + char buf[256]; + + re_snprintf(buf, sizeof buf, "%s%s", ua_aor(ua), + ua_isregistered(ua) ? " (OK)" : ""); + item = gtk_radio_menu_item_new_with_label(group, buf); + group = gtk_radio_menu_item_get_group( + GTK_RADIO_MENU_ITEM (item)); + if (ua == ua_current) + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), + TRUE); + g_object_set_data(G_OBJECT(item), "ua", ua); + g_signal_connect(item, "toggled", + G_CALLBACK(menu_on_account_toggled), mod); + gtk_menu_shell_append(accounts_menu, item); + mod->accounts_menu_group = group; + + return GTK_MENU_ITEM(item); +} + +static GtkMenuItem *accounts_menu_get_item(struct gtk_mod *mod, + struct ua *ua) +{ + GtkMenuItem *item; + GtkMenuShell *accounts_menu = GTK_MENU_SHELL(mod->accounts_menu); + GList *items = accounts_menu->children; + + for (; items; items = items->next) { + item = items->data; + if (ua == g_object_get_data(G_OBJECT(item), "ua")) + return item; + } + + /* Add new account not yet in menu */ + return accounts_menu_add_item(mod, ua); +} + + +static void update_current_accounts_menu_item(struct gtk_mod *mod) +{ + GtkMenuItem *item = accounts_menu_get_item(mod, + uag_current()); + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE); +} + + +static void update_ua_presence(struct gtk_mod *mod) +{ + GtkCheckMenuItem *item; + enum presence_status cur_status; + void *status; + GtkMenuShell *status_menu = GTK_MENU_SHELL(mod->status_menu); + GList *items = status_menu->children; + + cur_status = ua_presence_status(uag_current()); + + for (; items; items = items->next) { + item = items->data; + status = g_object_get_data(G_OBJECT(item), "presence"); + if (cur_status == GPOINTER_TO_UINT(status)) + break; + } + if (!item) + return; + + gtk_check_menu_item_set_active(item, TRUE); +} + + +static const char *ua_event_reg_str(enum ua_event ev) +{ + switch (ev) { + + case UA_EVENT_REGISTERING: return "registering"; + case UA_EVENT_REGISTER_OK: return "OK"; + case UA_EVENT_REGISTER_FAIL: return "ERR"; + case UA_EVENT_UNREGISTERING: return "unregistering"; + default: return "?"; + } +} + + +static void accounts_menu_set_status(struct gtk_mod *mod, + struct ua *ua, enum ua_event ev) +{ + GtkMenuItem *item = accounts_menu_get_item(mod, ua); + char buf[256]; + re_snprintf(buf, sizeof buf, "%s (%s)", ua_aor(ua), + ua_event_reg_str(ev)); + gtk_menu_item_set_label(item, buf); +} + + +static void notify_incoming_call(struct gtk_mod *mod, + struct call *call) +{ + GNotification *notification; + GVariant *target; + char id[64]; + + re_snprintf(id, sizeof id, "incoming-call-%p", call); + id[sizeof id - 1] = '\0'; + + notification = g_notification_new("Incoming call"); + g_notification_set_priority(notification, + G_NOTIFICATION_PRIORITY_URGENT); + target = g_variant_new_int64(GPOINTER_TO_INT(call)); + g_notification_set_body(notification, call_peeruri(call)); + g_notification_add_button_with_target_value(notification, + "Answer", "app.answer", target); + g_notification_add_button_with_target_value(notification, + "Reject", "app.reject", target); + g_application_send_notification(mod->app, id, notification); + g_object_unref(notification); +} + + +static void denotify_incoming_call(struct gtk_mod *mod, + struct call *call) +{ + char id[64]; + + re_snprintf(id, sizeof id, "incoming-call-%p", call); + id[sizeof id - 1] = '\0'; + g_application_withdraw_notification(mod->app, id); +} + + +static void answer_activated(GSimpleAction *action, GVariant *parameter, + gpointer arg) +{ + struct gtk_mod *mod = arg; + struct call *call = get_call_from_gvariant(parameter); + (void)action; + + if (call) { + denotify_incoming_call(mod, call); + mqueue_push(mod->mq, MQ_ANSWER, call); + } +} + +static void reject_activated(GSimpleAction *action, GVariant *parameter, + gpointer arg) +{ + struct gtk_mod *mod = arg; + struct call *call = get_call_from_gvariant(parameter); + (void)action; + + if (call) { + denotify_incoming_call(mod, call); + mqueue_push(mod->mq, MQ_HANGUP, call); + } +} + +static struct call_window *new_call_window(struct gtk_mod *mod, + struct call *call) +{ + struct call_window *win = call_window_new(call, mod); + if (call) { + mod->call_windows = g_slist_append(mod->call_windows, win); + } + return win; +} + + +static struct call_window *get_call_window(struct gtk_mod *mod, + struct call *call) +{ + GSList *wins; + + for (wins = mod->call_windows; wins; wins = wins->next) { + struct call_window *win = wins->data; + if (call_window_is_for_call(win, call)) + return win; + } + return NULL; +} + + +static struct call_window *get_create_call_window(struct gtk_mod *mod, + struct call *call) +{ + struct call_window *win = get_call_window(mod, call); + if (!win) + win = new_call_window(mod, call); + return win; +} + + +void gtk_mod_call_window_closed(struct gtk_mod *mod, struct call_window *win) +{ + mod->call_windows = g_slist_remove(mod->call_windows, win); +} + +static void ua_event_handler(struct ua *ua, + enum ua_event ev, + struct call *call, + const char *prm, + void *arg ) +{ + struct gtk_mod *mod = arg; + struct call_window *win; + + gdk_threads_enter(); + + switch (ev) { + + case UA_EVENT_REGISTERING: + case UA_EVENT_UNREGISTERING: + case UA_EVENT_REGISTER_OK: + case UA_EVENT_REGISTER_FAIL: + accounts_menu_set_status(mod, ua, ev); + break; + + case UA_EVENT_CALL_INCOMING: + notify_incoming_call(mod, call); + break; + + case UA_EVENT_CALL_CLOSED: + win = get_call_window(mod, call); + if (win) + call_window_closed(win, prm); + else + denotify_incoming_call(mod, call); + break; + + case UA_EVENT_CALL_RINGING: + win = get_create_call_window(mod, call); + if (win) + call_window_ringing(win); + break; + + case UA_EVENT_CALL_PROGRESS: + win = get_create_call_window(mod, call); + if (win) + call_window_progress(win); + break; + + case UA_EVENT_CALL_ESTABLISHED: + win = get_create_call_window(mod, call); + if (win) + call_window_established(win); + break; + + case UA_EVENT_CALL_TRANSFER_FAILED: + win = get_create_call_window(mod, call); + if (win) + call_window_transfer_failed(win, prm); + break; + + default: + break; + } + + gdk_threads_leave(); +} + +static void message_handler(const struct pl *peer, const struct pl *ctype, + struct mbuf *body, void *arg) +{ + struct gtk_mod *mod = arg; + GNotification *notification; + char title[128]; + char msg[512]; + (void)ctype; + + /* Display notification of chat */ + + re_snprintf(title, sizeof title, "Chat from %r", peer); + title[sizeof title - 1] = '\0'; + + re_snprintf(msg, sizeof msg, "%b", + mbuf_buf(body), mbuf_get_left(body)); + + notification = g_notification_new(title); + g_notification_set_body(notification, msg); + g_application_send_notification(mod->app, NULL, notification); + g_object_unref(notification); +} + + +static gboolean status_icon_on_button_press(GtkStatusIcon *status_icon, + GdkEventButton *event, + struct gtk_mod *mod) +{ + if (!mod->contacts_inited) { + init_contacts_menu(mod); + mod->contacts_inited = TRUE; + } + + /* If the current UA was changed through another UI, update it here */ + update_current_accounts_menu_item(mod); + + update_ua_presence(mod); + + gtk_widget_show_all(mod->app_menu); + + gtk_menu_popup(GTK_MENU(mod->app_menu), NULL, NULL, + gtk_status_icon_position_menu, status_icon, + event->button, event->time); + + return TRUE; +} + + +void gtk_mod_connect(struct gtk_mod *mod, const char *uri) +{ + mqueue_push(mod->mq, MQ_CONNECT, (char *)uri); +} + +static void warning_dialog(const char *title, const char *fmt, ...) +{ + va_list ap; + char msg[512]; + GtkWidget *dialog; + + va_start(ap, fmt); + (void)re_vsnprintf(msg, sizeof msg, fmt, ap); + va_end(ap); + + dialog = gtk_message_dialog_new(NULL, 0, GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, "%s", title); + gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), + "%s", msg); + g_signal_connect_swapped(G_OBJECT(dialog), "response", + G_CALLBACK(gtk_widget_destroy), dialog); + gtk_window_set_title(GTK_WINDOW(dialog), title); + gtk_widget_show(dialog); +} + + +static void mqueue_handler(int id, void *data, void *arg) +{ + struct gtk_mod *mod = arg; + const char *uri; + struct call *call; + int err; + struct ua *ua = uag_current(); + (void)mod; + + switch ((enum gtk_mod_events)id) { + + case MQ_CONNECT: + uri = data; + err = ua_connect(ua, &call, NULL, uri, NULL, VIDMODE_ON); + if (err) { + gdk_threads_enter(); + warning_dialog("Call failed", + "Connecting to \"%s\" failed.\n" + "Error: %m", uri, err); + gdk_threads_leave(); + break; + } + gdk_threads_enter(); + err = new_call_window(mod, call) == NULL; + gdk_threads_leave(); + if (err) { + ua_hangup(ua, call, 500, "Server Error"); + } + break; + + case MQ_HANGUP: + call = data; + ua_hangup(ua, call, 0, NULL); + break; + + case MQ_QUIT: + ua_stop_all(false); + break; + + case MQ_ANSWER: + call = data; + err = ua_answer(ua, call); + if (err) { + gdk_threads_enter(); + warning_dialog("Call failed", + "Answering the call " + "from \"%s\" failed.\n" + "Error: %m", + call_peername(call), err); + gdk_threads_leave(); + break; + } + + gdk_threads_enter(); + err = new_call_window(mod, call) == NULL; + gdk_threads_leave(); + if (err) { + ua_hangup(ua, call, 500, "Server Error"); + } + break; + + case MQ_SELECT_UA: + ua = data; + uag_current_set(ua); + break; + } +} + +static void *gtk_thread(void *arg) +{ + struct gtk_mod *mod = arg; + GtkMenuShell *app_menu; + GtkWidget *item; + GError *error = NULL; + + gdk_threads_init(); + gtk_init(0, NULL); + + g_set_application_name("baresip"); + mod->app = g_application_new ("com.creytiv.baresip", + G_APPLICATION_FLAGS_NONE); + + g_application_register (G_APPLICATION (mod->app), NULL, &error); + if (error != NULL) { + warning ("Unable to register GApplication: %s", + error->message); + g_error_free (error); + error = NULL; + } + + mod->status_icon = gtk_status_icon_new_from_icon_name("call-start"); + gtk_status_icon_set_tooltip_text (mod->status_icon, "baresip"); + + g_signal_connect(G_OBJECT(mod->status_icon), + "button_press_event", + G_CALLBACK(status_icon_on_button_press), mod); + gtk_status_icon_set_visible(mod->status_icon, TRUE); + + mod->contacts_inited = false; + mod->dial_dialog = NULL; + mod->call_windows = NULL; + + /* App menu */ + mod->app_menu = gtk_menu_new(); + app_menu = GTK_MENU_SHELL(mod->app_menu); + + /* Account submenu */ + mod->accounts_menu = gtk_menu_new(); + mod->accounts_menu_group = NULL; + item = gtk_menu_item_new_with_mnemonic("_Account"); + gtk_menu_shell_append(app_menu, item); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), + mod->accounts_menu); + + /* Add accounts to submenu */ + for (struct le *le = list_head(uag_list()); le; le = le->next) { + struct ua *ua = le->data; + accounts_menu_add_item(mod, ua); + } + + /* Status submenu */ + mod->status_menu = gtk_menu_new(); + item = gtk_menu_item_new_with_mnemonic("_Status"); + gtk_menu_shell_append(GTK_MENU_SHELL(app_menu), item); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), mod->status_menu); + + /* Open */ + item = gtk_radio_menu_item_new_with_label(NULL, "Open"); + g_object_set_data(G_OBJECT(item), "presence", + GINT_TO_POINTER(PRESENCE_OPEN)); + g_signal_connect(item, "activate", + G_CALLBACK(menu_on_presence_set), mod); + gtk_menu_shell_append(GTK_MENU_SHELL(mod->status_menu), item); + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE); + + /* Closed */ + item = gtk_radio_menu_item_new_with_label_from_widget( + GTK_RADIO_MENU_ITEM(item), "Closed"); + g_object_set_data(G_OBJECT(item), "presence", + GINT_TO_POINTER(PRESENCE_CLOSED)); + g_signal_connect(item, "activate", + G_CALLBACK(menu_on_presence_set), mod); + gtk_menu_shell_append(GTK_MENU_SHELL(mod->status_menu), item); + + gtk_menu_shell_append(app_menu, gtk_separator_menu_item_new()); + + /* Dial */ + item = gtk_menu_item_new_with_mnemonic("_Dial..."); + gtk_menu_shell_append(app_menu, item); + g_signal_connect(G_OBJECT(item), "activate", + G_CALLBACK(menu_on_dial), mod); + + /* Dial contact */ + mod->contacts_menu = gtk_menu_new(); + item = gtk_menu_item_new_with_mnemonic("Dial _contact"); + gtk_menu_shell_append(app_menu, item); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), + mod->contacts_menu); + + gtk_menu_shell_append(app_menu, gtk_separator_menu_item_new()); + + /* About */ + item = gtk_menu_item_new_with_mnemonic("A_bout"); + g_signal_connect(G_OBJECT(item), "activate", + G_CALLBACK(menu_on_about), mod); + gtk_menu_shell_append(app_menu, item); + + gtk_menu_shell_append(app_menu, gtk_separator_menu_item_new()); + + /* Quit */ + item = gtk_menu_item_new_with_mnemonic("_Quit"); + g_signal_connect(G_OBJECT(item), "activate", + G_CALLBACK(menu_on_quit), mod); + gtk_menu_shell_append(app_menu, item); + + g_action_map_add_action_entries(G_ACTION_MAP(mod->app), + app_entries, G_N_ELEMENTS(app_entries), mod); + + info("gtk_menu starting\n"); + + uag_event_register( ua_event_handler, mod ); + mod->run = true; + gtk_main(); + mod->run = false; + uag_event_unregister(ua_event_handler); + + if (mod->dial_dialog) { + mem_deref(mod->dial_dialog); + mod->dial_dialog = NULL; + } + + return NULL; +} + + +static void vu_enc_destructor(void *arg) +{ + struct vumeter_enc *st = arg; + + list_unlink(&st->af.le); +} + + +static void vu_dec_destructor(void *arg) +{ + struct vumeter_dec *st = arg; + + list_unlink(&st->af.le); +} + + +static int16_t calc_avg_s16(const int16_t *sampv, size_t sampc) +{ + int32_t v = 0; + size_t i; + + if (!sampv || !sampc) + return 0; + + for (i=0; i<sampc; i++) + v += abs(sampv[i]); + + return v/sampc; +} + + +static int vu_encode_update(struct aufilt_enc_st **stp, void **ctx, + const struct aufilt *af, struct aufilt_prm *prm) +{ + struct vumeter_enc *st; + (void)ctx; + (void)prm; + + if (!stp || !af) + return EINVAL; + + if (*stp) + return 0; + + st = mem_zalloc(sizeof(*st), vu_enc_destructor); + if (!st) + return ENOMEM; + + gdk_threads_enter(); + call_window_got_vu_enc(st); + gdk_threads_leave(); + + *stp = (struct aufilt_enc_st *)st; + + return 0; +} + + +static int vu_decode_update(struct aufilt_dec_st **stp, void **ctx, + const struct aufilt *af, struct aufilt_prm *prm) +{ + struct vumeter_dec *st; + (void)ctx; + (void)prm; + + if (!stp || !af) + return EINVAL; + + if (*stp) + return 0; + + st = mem_zalloc(sizeof(*st), vu_dec_destructor); + if (!st) + return ENOMEM; + + gdk_threads_enter(); + call_window_got_vu_dec(st); + gdk_threads_leave(); + + *stp = (struct aufilt_dec_st *)st; + + return 0; +} + + +static int vu_encode(struct aufilt_enc_st *st, int16_t *sampv, size_t *sampc) +{ + struct vumeter_enc *vu = (struct vumeter_enc *)st; + + vu->avg_rec = calc_avg_s16(sampv, *sampc); + vu->started = true; + + return 0; +} + + +static int vu_decode(struct aufilt_dec_st *st, int16_t *sampv, size_t *sampc) +{ + struct vumeter_dec *vu = (struct vumeter_dec *)st; + + vu->avg_play = calc_avg_s16(sampv, *sampc); + vu->started = true; + + return 0; +} + + +static struct aufilt vumeter = { + LE_INIT, "gtk_vumeter", + vu_encode_update, vu_encode, + vu_decode_update, vu_decode +}; + + +static int module_init(void) +{ + int err = 0; + + err = mqueue_alloc(&mod_obj.mq, mqueue_handler, &mod_obj); + if (err) + return err; + err = pthread_create(&mod_obj.thread, NULL, gtk_thread, + &mod_obj); + if (err) + return err; + + aufilt_register(&vumeter); + err = message_init(message_handler, &mod_obj); + + return err; +} + +static int module_close(void) +{ + if (mod_obj.run) { + gdk_threads_enter(); + gtk_main_quit(); + gdk_threads_leave(); + } + pthread_join(mod_obj.thread, NULL); + mem_deref(mod_obj.mq); + aufilt_unregister(&vumeter); + message_close(); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(gtk) = { + "gtk", + "application", + module_init, + module_close, +}; diff --git a/modules/gtk/gtk_mod.h b/modules/gtk/gtk_mod.h new file mode 100644 index 0000000..ab123ca --- /dev/null +++ b/modules/gtk/gtk_mod.h @@ -0,0 +1,51 @@ +/** + * @file gtk/gtk_mod.h GTK+ UI module -- internal API + * + * Copyright (C) 2015 Charles E. Lehner + */ + +struct gtk_mod; +struct call_window; +struct dial_dialog; +struct transfer_dialog; + +struct vumeter_enc { + struct aufilt_enc_st af; /* inheritance */ + int16_t avg_rec; + volatile bool started; +}; + +struct vumeter_dec { + struct aufilt_dec_st af; /* inheritance */ + int16_t avg_play; + volatile bool started; +}; + +/* Main menu */ +void gtk_mod_connect(struct gtk_mod *, const char *uri); +void gtk_mod_call_window_closed(struct gtk_mod *, struct call_window *); + +/* Call Window */ +struct call_window *call_window_new(struct call *call, struct gtk_mod *mod); +void call_window_got_vu_dec(struct vumeter_dec *); +void call_window_got_vu_enc(struct vumeter_enc *); +void call_window_transfer(struct call_window *, const char *uri); +void call_window_closed(struct call_window *, const char *reason); +void call_window_ringing(struct call_window *); +void call_window_progress(struct call_window *); +void call_window_established(struct call_window *); +void call_window_transfer_failed(struct call_window *, const char *reason); +bool call_window_is_for_call(struct call_window *, struct call *); + +/* Dial Dialog */ +struct dial_dialog *dial_dialog_alloc(struct gtk_mod *); +void dial_dialog_show(struct dial_dialog *); + +/* Call transfer dialog */ +struct transfer_dialog *transfer_dialog_alloc(struct call_window *); +void transfer_dialog_show(struct transfer_dialog *); +void transfer_dialog_fail(struct transfer_dialog *, const char *reason); + +/* URI entry combo box */ +GtkWidget *uri_combo_box_new(void); +const char *uri_combo_box_get_text(GtkComboBox *box); diff --git a/modules/gtk/module.mk b/modules/gtk/module.mk new file mode 100644 index 0000000..2273d42 --- /dev/null +++ b/modules/gtk/module.mk @@ -0,0 +1,14 @@ +# +# module.mk - GTK+ Menu-based UI +# +# Copyright (C) 2010 Creytiv.com +# Copyright (C) 2015 Charles E. Lehner +# + +MOD := gtk +$(MOD)_SRCS += gtk_mod.c call_window.c dial_dialog.c transfer_dialog.c \ + uri_entry.c +$(MOD)_LFLAGS += `pkg-config --libs gtk+-2.0 ` +CFLAGS += `pkg-config --cflags gtk+-2.0 ` + +include mk/mod.mk diff --git a/modules/gtk/transfer_dialog.c b/modules/gtk/transfer_dialog.c new file mode 100644 index 0000000..165c1c2 --- /dev/null +++ b/modules/gtk/transfer_dialog.c @@ -0,0 +1,131 @@ +/** + * @file transfer_dialog.c GTK+ call transfer dialog + * + * Copyright (C) 2015 Charles E. Lehner + */ +#include <re.h> +#include <baresip.h> +#include <gtk/gtk.h> +#include "gtk_mod.h" + +struct transfer_dialog { + struct call_window *call_win; + GtkWidget *dialog; + GtkComboBox *uri_combobox; + GtkLabel *status_label; + GtkWidget *spinner; +}; + +static const char *status_progress = "progress"; + + +static void set_status(struct transfer_dialog *td, const char *status) +{ + if (status == status_progress) { + gtk_widget_show(td->spinner); + gtk_spinner_start(GTK_SPINNER(td->spinner)); + gtk_label_set_text(td->status_label, NULL); + } else { + gtk_widget_hide(td->spinner); + gtk_spinner_stop(GTK_SPINNER(td->spinner)); + gtk_label_set_text(td->status_label, status); + } +} + + +static void on_dialog_response(GtkDialog *dialog, gint response_id, + struct transfer_dialog *win) +{ + char *uri; + + if (response_id == GTK_RESPONSE_ACCEPT) { + uri = (char *)uri_combo_box_get_text(win->uri_combobox); + set_status(win, status_progress); + call_window_transfer(win->call_win, uri); + } else { + set_status(win, NULL); + gtk_widget_hide(GTK_WIDGET(dialog)); + } +} + + +static void destructor(void *arg) +{ + struct transfer_dialog *td = arg; + + gtk_widget_destroy(td->dialog); +} + + +struct transfer_dialog *transfer_dialog_alloc(struct call_window *call_win) +{ + struct transfer_dialog *win; + GtkWidget *dialog, *content, *button, *image, *hbox, *spinner, *label; + GtkWidget *uri_combobox; + + win = mem_zalloc(sizeof(*win), destructor); + if (!win) + return NULL; + + dialog = gtk_dialog_new_with_buttons("Transfer", NULL, 0, + GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL); + + /* Transfer button */ + button = gtk_button_new_with_label("Transfer"); + image = gtk_image_new_from_icon_name("forward", + GTK_ICON_SIZE_BUTTON); + gtk_button_set_image(GTK_BUTTON(button), image); + gtk_dialog_add_action_widget(GTK_DIALOG(dialog), button, + GTK_RESPONSE_ACCEPT); + gtk_widget_set_can_default(button, TRUE); + + gtk_dialog_set_default_response(GTK_DIALOG(dialog), + GTK_RESPONSE_ACCEPT); + /* Label */ + content = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); + label = gtk_label_new("Transfer call to:"); + gtk_box_pack_start(GTK_BOX(content), label, FALSE, FALSE, 0); + + /* URI entry */ + uri_combobox = uri_combo_box_new(); + gtk_box_pack_start(GTK_BOX(content), uri_combobox, FALSE, FALSE, 5); + + g_signal_connect(dialog, "response", G_CALLBACK(on_dialog_response), win); + g_signal_connect(dialog, "delete-event", + G_CALLBACK(gtk_widget_hide_on_delete), win); + + /* Spinner and status */ + hbox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(content), hbox, FALSE, FALSE, 0); + + spinner = gtk_spinner_new(); + gtk_box_pack_start(GTK_BOX(hbox), spinner, TRUE, TRUE, 0); + + label = gtk_label_new(NULL); + gtk_box_pack_start(GTK_BOX(content), label, FALSE, FALSE, 0); + win->status_label = GTK_LABEL(label); + + win->dialog = dialog; + win->uri_combobox = GTK_COMBO_BOX(uri_combobox); + win->call_win = call_win; + win->spinner = spinner; + + gtk_widget_show_all(dialog); + gtk_widget_hide(spinner); + + return win; +} + +void transfer_dialog_show(struct transfer_dialog *td) +{ + gtk_window_present(GTK_WINDOW(td->dialog)); + gtk_widget_grab_focus(gtk_bin_get_child(GTK_BIN(td->uri_combobox))); + set_status(td, NULL); +} + +void transfer_dialog_fail(struct transfer_dialog *td, const char *reason) +{ + char buf[256]; + re_snprintf(buf, sizeof buf, "Transfer failed: %s", reason); + set_status(td, buf); +} diff --git a/modules/gtk/uri_entry.c b/modules/gtk/uri_entry.c new file mode 100644 index 0000000..601bf83 --- /dev/null +++ b/modules/gtk/uri_entry.c @@ -0,0 +1,44 @@ +/** + * @file uri_entry.c GTK+ URI entry combo box + * + * Copyright (C) 2015 Charles E. Lehner + */ + +#include <re.h> +#include <baresip.h> +#include <gtk/gtk.h> +#include "gtk_mod.h" + +/** + * Create a URI combox box. + * + * The combo box has a menu of contacts, and a text entry for a URI. + * + * @return the combo box + */ +GtkWidget *uri_combo_box_new(void) +{ + struct le *le; + GtkEntry *uri_entry; + GtkWidget *uri_combobox; + + uri_combobox = gtk_combo_box_text_new_with_entry(); + uri_entry = GTK_ENTRY(gtk_bin_get_child(GTK_BIN(uri_combobox))); + gtk_entry_set_activates_default(uri_entry, TRUE); + + for (le = list_head(contact_list()); le; le = le->next) { + struct contact *c = le->data; + gtk_combo_box_text_append_text( + GTK_COMBO_BOX_TEXT(uri_combobox), + contact_str(c)); + } + + return uri_combobox; +} + +const char *uri_combo_box_get_text(GtkComboBox *box) +{ + GtkEntry *entry = GTK_ENTRY(gtk_bin_get_child(GTK_BIN(box))); + GtkEntryBuffer *buf = gtk_entry_get_buffer(entry); + return gtk_entry_buffer_get_text(buf); +} |