From 6b54370a48287662daae5721782978662095a9e4 Mon Sep 17 00:00:00 2001 From: mbattista Date: Fri, 7 May 2021 18:17:01 +0200 Subject: [PATCH] Attended Transfer on GTK (#1435) * attended transfer on gtk * fix bug in clean number * cleanup duplicated code and add ToolTip on how to use it. Co-authored-by: Marcel Battista --- include/baresip.h | 1 + modules/gtk/call_window.c | 71 +++++++++++++++++++++++++++++++--- modules/gtk/dial_dialog.c | 25 +++++++++--- modules/gtk/gtk_mod.c | 78 +++++++++++++++++++++++++++++++++++++- modules/gtk/gtk_mod.h | 17 ++++++++- modules/menu/static_menu.c | 12 ++++-- src/call.c | 29 ++++++++++++++ 7 files changed, 216 insertions(+), 17 deletions(-) diff --git a/include/baresip.h b/include/baresip.h index 965bd99d4..a626c9dc3 100644 --- a/include/baresip.h +++ b/include/baresip.h @@ -198,6 +198,7 @@ int call_send_digit(struct call *call, char key); bool call_has_audio(const struct call *call); bool call_has_video(const struct call *call); int call_transfer(struct call *call, const char *uri); +int call_replace_transfer(struct call *target_call, struct call *source_call); int call_status(struct re_printf *pf, const struct call *call); int call_debug(struct re_printf *pf, const struct call *call); int call_notify_sipfrag(struct call *call, uint16_t scode, diff --git a/modules/gtk/call_window.c b/modules/gtk/call_window.c index bb2f5ecfe..451b56256 100644 --- a/modules/gtk/call_window.c +++ b/modules/gtk/call_window.c @@ -23,11 +23,12 @@ struct call_window { struct vumeter_enc *enc; } vu; struct transfer_dialog *transfer_dialog; + struct dial_dialog *attended_transfer_dial; GtkWidget *window; GtkLabel *status; GtkLabel *duration; struct { - GtkWidget *hangup, *transfer, *hold, *mute; + GtkWidget *hangup, *transfer, *hold, *mute, *attended_transfer; } buttons; struct { GtkProgressBar *enc, *dec; @@ -37,6 +38,7 @@ struct call_window { bool closed; int cur_key; struct play *play_dtmf_tone; + struct call *attended_call; }; enum call_window_events { @@ -45,6 +47,7 @@ enum call_window_events { MQ_HOLD, MQ_MUTE, MQ_TRANSFER, + MQ_ATTTRANSFER, }; static pthread_mutex_t last_data_mut = PTHREAD_MUTEX_INITIALIZER; @@ -196,10 +199,17 @@ static void call_on_hangup(GtkToggleButton *btn, struct call_window *win) static void call_on_hold_toggle(GtkToggleButton *btn, struct call_window *win) { bool hold = gtk_toggle_button_get_active(btn); - if (hold) + if (hold) { + gtk_widget_set_sensitive(win->buttons.attended_transfer, + TRUE); vumeter_timer_stop(win); + } else + { + gtk_widget_set_sensitive(win->buttons.attended_transfer, + FALSE); vumeter_timer_start(win); + } mqueue_push(win->mq, MQ_HOLD, (void *)(size_t)hold); } @@ -221,6 +231,25 @@ static void call_on_transfer(GtkToggleButton *btn, struct call_window *win) } +static void call_window_transfer_attended_call(GtkToggleButton *btn, + struct call_window *win) +{ + (void)btn; + mqueue_push(win->mq, MQ_ATTTRANSFER, win); +} + + +static void call_on_attended_transfer(GtkToggleButton *btn, + struct call_window *win) +{ + (void)btn; + if (!win->attended_transfer_dial) + win->attended_transfer_dial = + dial_dialog_alloc(win->mod, win->call); + dial_dialog_show(win->attended_transfer_dial); +} + + static gboolean call_on_window_close(GtkWidget *widget, GdkEventAny *event, struct call_window *win) { @@ -319,6 +348,10 @@ static void mqueue_handler(int id, void *data, void *arg) case MQ_TRANSFER: call_transfer(win->call, data); break; + + case MQ_ATTTRANSFER: + call_replace_transfer(win->attended_call, win->call); + break; } } @@ -331,12 +364,14 @@ static void call_window_destructor(void *arg) gtk_mod_call_window_closed(window->mod, window); gtk_widget_destroy(window->window); mem_deref(window->transfer_dialog); + mem_deref(window->attended_transfer_dial); gdk_threads_leave(); mem_deref(window->call); mem_deref(window->mq); mem_deref(window->vu.enc); mem_deref(window->vu.dec); + mem_deref(window->attended_call); if (window->duration_timer_tag) g_source_remove(window->duration_timer_tag); @@ -349,7 +384,8 @@ static void call_window_destructor(void *arg) } -struct call_window *call_window_new(struct call *call, struct gtk_mod *mod) +struct call_window *call_window_new(struct call *call, struct gtk_mod *mod, + struct call *attended_call) { struct call_window *win; GtkWidget *window, *label, *status, *button, *progress, *image; @@ -428,13 +464,33 @@ struct call_window *call_window_new(struct call *call, struct gtk_mod *mod) GTK_ICON_SIZE_BUTTON); gtk_button_set_image(GTK_BUTTON(button), image); - /* Transfer */ + /* Blind 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); + 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); + + /* Attended Transfer */ + button = gtk_button_new_with_label("Att. Transfer"); + win->buttons.attended_transfer = button; + gtk_box_pack_end(GTK_BOX(button_box), button, FALSE, TRUE, 0); + if (!attended_call) { + g_signal_connect(button, "clicked", + G_CALLBACK(call_on_attended_transfer), win); + } + else { + g_signal_connect(button, "clicked", + G_CALLBACK(call_window_transfer_attended_call), win); + } image = gtk_image_new_from_icon_name("forward", GTK_ICON_SIZE_BUTTON); gtk_button_set_image(GTK_BUTTON(button), image); + gtk_widget_set_sensitive (button, FALSE); + gtk_widget_set_tooltip_text(button, + "Please put the call on 'Hold' to enable attended transfer"); /* Hold */ button = gtk_toggle_button_new_with_label("Hold"); @@ -467,9 +523,11 @@ struct call_window *call_window_new(struct call *call, struct gtk_mod *mod) G_CALLBACK(call_on_key_release), win); win->call = mem_ref(call); + win->attended_call = mem_ref(attended_call); win->mod = mod; win->window = window; win->transfer_dialog = NULL; + win->attended_transfer_dial = NULL; win->status = GTK_LABEL(status); win->duration = GTK_LABEL(duration); win->closed = false; @@ -509,6 +567,7 @@ void call_window_closed(struct call_window *win, const char *reason) win->duration_timer_tag = 0; } gtk_widget_set_sensitive(win->buttons.transfer, FALSE); + gtk_widget_set_sensitive(win->buttons.attended_transfer, FALSE); gtk_widget_set_sensitive(win->buttons.hold, FALSE); gtk_widget_set_sensitive(win->buttons.mute, FALSE); @@ -522,6 +581,7 @@ void call_window_closed(struct call_window *win, const char *reason) call_window_set_status(win, status); win->transfer_dialog = mem_deref(win->transfer_dialog); + win->attended_transfer_dial = mem_deref(win->attended_transfer_dial); win->closed = true; if (reason && strncmp(reason, user_trigger_reason, @@ -588,3 +648,4 @@ 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 index 8590702f5..22abefdd4 100644 --- a/modules/gtk/dial_dialog.c +++ b/modules/gtk/dial_dialog.c @@ -10,12 +10,14 @@ #include #include #include "gtk_mod.h" +#include struct dial_dialog { struct gtk_mod *mod; GtkWidget *dialog; GtkComboBox *uri_combobox; + struct call *attended_call; }; @@ -26,9 +28,14 @@ static int clean_number(char* str) /* only clean numeric numbers * In other cases trust the user input */ - int err = re_regex(str, str_len(str), "[A-Za-z]"); - if (err == 0) - return -1; + while (str[i]) { + if (isalpha(str[i] != 0)) + return -1; + else if (str[i] == '@') + return -1; + ++i; + } + i = 0; /* remove (0) which is in some mal-formated numbers * but only if trailed by another character @@ -77,7 +84,13 @@ static void dial_dialog_on_response(GtkDialog *dialog, gint response_id, uri_combo_box_set_text(dd->uri_combobox, uri, length); } - gtk_mod_connect(dd->mod, uri); + if (!dd->attended_call) { + gtk_mod_connect(dd->mod, uri); + } + else { + gtk_mod_connect_attended(dd->mod, uri, + dd->attended_call); + } } gtk_widget_hide(GTK_WIDGET(dialog)); @@ -92,7 +105,8 @@ static void destructor(void *arg) } -struct dial_dialog *dial_dialog_alloc(struct gtk_mod *mod) +struct dial_dialog *dial_dialog_alloc(struct gtk_mod *mod, + struct call *attended_call) { struct dial_dialog *dd; GtkWidget *dial; @@ -138,6 +152,7 @@ struct dial_dialog *dial_dialog_alloc(struct gtk_mod *mod) dd->dialog = dial; dd->uri_combobox = GTK_COMBO_BOX(uri_combobox); dd->mod = mod; + dd->attended_call = attended_call; return dd; } diff --git a/modules/gtk/gtk_mod.c b/modules/gtk/gtk_mod.c index 55a806620..5cfa07830 100644 --- a/modules/gtk/gtk_mod.c +++ b/modules/gtk/gtk_mod.c @@ -68,6 +68,7 @@ static struct gtk_mod mod_obj; enum gtk_mod_events { MQ_POPUP, MQ_CONNECT, + MQ_CONNECTATTENDED, MQ_QUIT, MQ_ANSWER, MQ_HANGUP, @@ -143,7 +144,7 @@ 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); + mod->dial_dialog = dial_dialog_alloc(mod, NULL); dial_dialog_show(mod->dial_dialog); } @@ -156,6 +157,7 @@ static void menu_on_dial_contact(GtkMenuItem *menuItem, gpointer arg) gtk_mod_connect(mod, uri); } + static void menu_on_dial_history(GtkMenuItem *menuItem, gpointer arg) { struct gtk_mod *mod = arg; @@ -557,7 +559,19 @@ static void reject_activated(GSimpleAction *action, GVariant *parameter, static struct call_window *new_call_window(struct gtk_mod *mod, struct call *call) { - struct call_window *win = call_window_new(call, mod); + struct call_window *win = call_window_new(call, mod, NULL); + if (call) { + mod->call_windows = g_slist_append(mod->call_windows, win); + } + return win; +} + + +static struct call_window *new_call_transfer_window(struct gtk_mod *mod, + struct call *call, + struct call *attended_call) +{ + struct call_window *win = call_window_new(call, mod, attended_call); if (call) { mod->call_windows = g_slist_append(mod->call_windows, win); } @@ -785,6 +799,42 @@ int gtk_mod_connect(struct gtk_mod *mod, const char *uri) } +int gtk_mod_connect_attended(struct gtk_mod *mod, const char *uri, + struct call *attended_call) +{ + struct attended_transfer_store *ats; + struct mbuf *uribuf = NULL; + char *uri_copy = NULL; + int err = 0; + + if (!mod) + return ENOMEM; + + uribuf = mbuf_alloc(64); + ats = mem_zalloc(sizeof(struct attended_transfer_store), NULL); + if (!uribuf) + return ENOMEM; + + err = account_uri_complete(ua_account(mod->ua_cur), uribuf, uri); + if (err) + return EINVAL; + + uribuf->pos = 0; + err = mbuf_strdup(uribuf, &uri_copy, uribuf->end); + if (err) + goto out; + + ats->uri = (char *)uri_copy; + ats->attended_call = attended_call; + + err = mqueue_push(mod->mq, MQ_CONNECTATTENDED, ats); + +out: + mem_deref(uribuf); + return err; +} + + bool gtk_mod_clean_number(struct gtk_mod *mod) { if (!mod) @@ -819,6 +869,7 @@ static void mqueue_handler(int id, void *data, void *arg) struct gtk_mod *mod = arg; const char *uri; struct call *call; + struct attended_transfer_store *ats; int err; struct ua *ua = gtk_current_ua(); @@ -851,6 +902,29 @@ static void mqueue_handler(int id, void *data, void *arg) mem_deref(data); break; + case MQ_CONNECTATTENDED: + ats = data; + err = ua_connect(ua, &call, NULL, ats->uri, VIDMODE_ON); + add_history_menu_item(mod, ats->uri, CALL_OUTGOING, ""); + if (err) { + gdk_threads_enter(); + warning_dialog("Call failed", + "Connecting to \"%s\" failed.\n" + "Error: %m", ats->uri, err); + gdk_threads_leave(); + break; + } + gdk_threads_enter(); + err = new_call_transfer_window(mod, call, + ats->attended_call) == NULL; + gdk_threads_leave(); + if (err) { + ua_hangup(ua, call, 500, "Server Error"); + } + mem_deref(ats->uri); + mem_deref(data); + break; + case MQ_HANGUP: call = data; ua_hangup(ua, call, 0, NULL); diff --git a/modules/gtk/gtk_mod.h b/modules/gtk/gtk_mod.h index 2f3bfb4f4..522202d26 100644 --- a/modules/gtk/gtk_mod.h +++ b/modules/gtk/gtk_mod.h @@ -26,15 +26,27 @@ struct vumeter_dec { volatile bool started; }; +struct attended_transfer_store { + struct call *attended_call; + char *uri; +}; + + /* Main menu */ int gtk_mod_connect(struct gtk_mod *, const char *uri); +int gtk_mod_connect_attended(struct gtk_mod *, const char *uri, + struct call *attended_call); +int gtk_mod_transfer(struct gtk_mod *, const char *uri, + struct call *attended_call); 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); +struct call_window *call_window_new(struct call *call, struct gtk_mod *mod, + struct call *attended_call); 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_atttransfer(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 *); @@ -43,7 +55,8 @@ 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 *); +struct dial_dialog *dial_dialog_alloc(struct gtk_mod *, + struct call *attended_call); void dial_dialog_show(struct dial_dialog *); /* Call transfer dialog */ diff --git a/modules/menu/static_menu.c b/modules/menu/static_menu.c index 91bbfd948..1fcc25ca0 100644 --- a/modules/menu/static_menu.c +++ b/modules/menu/static_menu.c @@ -6,6 +6,7 @@ #include #include #include +#include #include "menu.h" @@ -395,9 +396,14 @@ static void clean_number(char *str) /* only clean numeric numbers * In other cases trust the user input */ - int err = re_regex(str, str_len(str), "[A-Za-z]"); - if (err == 0) - return; + while (str[i]) { + if (isalpha(str[i] != 0)) + return; + else if (str[i] == '@') + return; + ++i; + } + i = 0; /* remove (0) which is in some mal-formated numbers * but only if trailed by another character diff --git a/src/call.c b/src/call.c index d9f35248a..496fcecdb 100644 --- a/src/call.c +++ b/src/call.c @@ -2291,6 +2291,35 @@ int call_transfer(struct call *call, const char *uri) } +/** + * Transfer the call to a target SIP uri and replace the source call + * + * @param call Call object + * @param uri Target SIP uri + * + * @return 0 if success, otherwise errorcode + */ +int call_replace_transfer(struct call *call, struct call *source_call) +{ + int err; + + info("transferring call to %s\n", source_call->peer_uri); + + call->sub = mem_deref(call->sub); + err = sipevent_drefer(&call->sub, uag_sipevent_sock(), + sipsess_dialog(call->sess), ua_cuser(call->ua), + auth_handler, call->acc, true, + sipsub_notify_handler, sipsub_close_handler, + call, "Refer-To: %s?Replaces=%s\r\n", + source_call->peer_uri, source_call->id); + if (err) { + warning("call: sipevent_drefer: %m\n", err); + } + + return err; +} + + int call_af(const struct call *call) { return call ? call->af : AF_UNSPEC;