Blob Blame History Raw
From 6b54370a48287662daae5721782978662095a9e4 Mon Sep 17 00:00:00 2001
From: mbattista <m0battista@gmail.com>
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 <marcel.battista@etes.de>
---
 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 <pthread.h>
 #include <gtk/gtk.h>
 #include "gtk_mod.h"
+#include <ctype.h>
 
 
 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 <stdlib.h>
 #include <re.h>
 #include <baresip.h>
+#include <ctype.h>
 
 #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;