/pidgin/main: e4c122196b08: Add out-of-band DTMF support and dia...

David Woodhouse <[email protected]>
Newsgroups gmane.comp.gnome.gaim.cvs
Message-ID <[email protected]>
Changeset: e4c122196b08fa0596351ac000f5440128a13449
Author:	 David Woodhouse <[email protected]>
Date:	 2014-10-08 23:10 +0100
Branch:	 default
URL: https://hg.pidgin.im/pidgin/main/rev/e4c122196b08

Description:

Add out-of-band DTMF support and dialpad to use it

This resolves the non-Jabber-specific part of ticket 12617, by adding
purple_media_send_dtmf(), and a corresponding method to the
PurpleMediaBackendIface and the farstream implementation thereof.

Fixes #15575
Refs #12617

diffstat:

 libpurple/media.c               |   43 ++++++++++++
 libpurple/media.h               |   20 +++++-
 libpurple/media/backend-fs2.c   |   63 ++++++++++++++++++
 libpurple/media/backend-iface.h |    3 +
 pidgin/gtkmedia.c               |  137 +++++++++++++++++++++++++++++++++++++++-
 5 files changed, 263 insertions(+), 3 deletions(-)

diffs (truncated from 355 to 300 lines):

diff --git a/libpurple/media.c b/libpurple/media.c
--- a/libpurple/media.c
+++ b/libpurple/media.c
@@ -1420,3 +1420,46 @@ purple_media_get_tee(PurpleMedia *media,
 }
 #endif /* USE_GSTREAMER */
 
+gboolean
+purple_media_send_dtmf(PurpleMedia *media, const gchar *session_id,
+		gchar dtmf, guint8 volume, guint16 duration)
+{
+#ifdef USE_VV
+	PurpleAccount *account = NULL;
+	PurpleConnection *gc = NULL;
+	PurplePlugin *prpl = NULL;
+	PurplePluginProtocolInfo *prpl_info = NULL;
+	PurpleMediaBackendIface *backend_iface = NULL;
+
+	if (media)
+	{
+		account = purple_media_get_account(media);
+		backend_iface = PURPLE_MEDIA_BACKEND_GET_INTERFACE(media->priv->backend);
+	}
+	if (account)
+		gc = purple_account_get_connection(account);
+	if (gc)
+		prpl = purple_connection_get_prpl(gc);
+	if (prpl)
+		prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
+
+	if (dtmf == 'a')
+		dtmf = 'A';
+	else if (dtmf == 'b')
+		dtmf = 'B';
+	else if (dtmf == 'c')
+		dtmf = 'C';
+	else if (dtmf == 'd')
+		dtmf = 'D';
+
+	g_return_val_if_fail(strchr("0123456789ABCD#*", dtmf), FALSE);
+
+	if (backend_iface && backend_iface->send_dtmf
+		&& backend_iface->send_dtmf(media->priv->backend,
+				session_id, dtmf, volume, duration))
+	{
+		return TRUE;
+	}
+#endif
+	return FALSE;
+}
diff --git a/libpurple/media.h b/libpurple/media.h
--- a/libpurple/media.h
+++ b/libpurple/media.h
@@ -31,6 +31,8 @@
 #include <glib.h>
 #include <glib-object.h>
 
+typedef struct _PurpleMedia PurpleMedia;
+
 #include "media/candidate.h"
 #include "media/codec.h"
 #include "media/enum-types.h"
@@ -42,8 +44,6 @@
 #define PURPLE_IS_MEDIA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), PURPLE_TYPE_MEDIA))
 #define PURPLE_MEDIA_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), PURPLE_TYPE_MEDIA, PurpleMediaClass))
 
-typedef struct _PurpleMedia PurpleMedia;
-
 #include "signals.h"
 #include "util.h"
 
@@ -441,6 +441,22 @@ gulong purple_media_set_output_window(Pu
  */
 void purple_media_remove_output_windows(PurpleMedia *media);
 
+/**
+ * purple_media_send_dtmf:
+ * @media: The media instance to send a DTMF signal to.
+ * @sess_id: The session id of the session to send the DTMF signal on.
+ * @dtmf: The character representing the DTMF in the range [0-9#*A-D].
+ * @volume: The power level expressed in dBm0 after dropping the sign in the
+ *          range of 0 to 63.  A larger value represents a lower volume.
+ * @duration: The duration of the tone in milliseconds.
+ *
+ * Sends a DTMF signal out-of-band.
+ *
+ * Returns: %TRUE DTMF sent successfully, or %FALSE otherwise.
+ */
+gboolean purple_media_send_dtmf(PurpleMedia *media, const gchar *session_id,
+		gchar dtmf, guint8 volume, guint16 duration);
+
 G_END_DECLS
 
 #endif  /* _PURPLE_MEDIA_H_ */
diff --git a/libpurple/media/backend-fs2.c b/libpurple/media/backend-fs2.c
--- a/libpurple/media/backend-fs2.c
+++ b/libpurple/media/backend-fs2.c
@@ -91,6 +91,9 @@ static gboolean purple_media_backend_fs2
 static void purple_media_backend_fs2_set_params(PurpleMediaBackend *self,
 		guint num_params, GParameter *params);
 static const gchar **purple_media_backend_fs2_get_available_params(void);
+static gboolean purple_media_backend_fs2_send_dtmf(
+		PurpleMediaBackend *self, const gchar *sess_id,
+		gchar dtmf, guint8 volume, guint16 duration);
 
 static void free_stream(PurpleMediaBackendFs2Stream *stream);
 static void free_session(PurpleMediaBackendFs2Session *session);
@@ -531,6 +534,7 @@ purple_media_backend_iface_init(PurpleMe
 	iface->set_send_codec = purple_media_backend_fs2_set_send_codec;
 	iface->set_params = purple_media_backend_fs2_set_params;
 	iface->get_available_params = purple_media_backend_fs2_get_available_params;
+	iface->send_dtmf = purple_media_backend_fs2_send_dtmf;
 }
 
 static FsMediaType
@@ -2557,6 +2561,65 @@ purple_media_backend_fs2_set_params(Purp
 	gst_structure_free(sdes);
 #endif /* HAVE_FARSIGHT */
 }
+static gboolean
+send_dtmf_callback(gpointer userdata)
+{
+	FsSession *session = userdata;
+
+	fs_session_stop_telephony_event(session);
+
+	return FALSE;
+}
+static gboolean
+purple_media_backend_fs2_send_dtmf(PurpleMediaBackend *self,
+		const gchar *sess_id, gchar dtmf, guint8 volume,
+		guint16 duration)
+{
+	PurpleMediaBackendFs2Session *session;
+	FsDTMFEvent event;
+
+	g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), FALSE);
+
+	session = get_session(PURPLE_MEDIA_BACKEND_FS2(self), sess_id);
+	if (session == NULL)
+		return FALSE;
+
+	/* Convert DTMF char into FsDTMFEvent enum */
+	switch(dtmf) {
+		case '0': event = FS_DTMF_EVENT_0; break;
+		case '1': event = FS_DTMF_EVENT_1; break;
+		case '2': event = FS_DTMF_EVENT_2; break;
+		case '3': event = FS_DTMF_EVENT_3; break;
+		case '4': event = FS_DTMF_EVENT_4; break;
+		case '5': event = FS_DTMF_EVENT_5; break;
+		case '6': event = FS_DTMF_EVENT_6; break;
+		case '7': event = FS_DTMF_EVENT_7; break;
+		case '8': event = FS_DTMF_EVENT_8; break;
+		case '9': event = FS_DTMF_EVENT_9; break;
+		case '*': event = FS_DTMF_EVENT_STAR; break;
+		case '#': event = FS_DTMF_EVENT_POUND; break;
+		case 'A': event = FS_DTMF_EVENT_A; break;
+		case 'B': event = FS_DTMF_EVENT_B; break;
+		case 'C': event = FS_DTMF_EVENT_C; break;
+		case 'D': event = FS_DTMF_EVENT_D; break;
+		default:
+			return FALSE;
+	}
+
+	if (!fs_session_start_telephony_event(session->session,
+			event, volume)) {
+		return FALSE;
+	}
+
+	if (duration <= 50) {
+		fs_session_stop_telephony_event(session->session);
+	} else {
+		purple_timeout_add(duration, send_dtmf_callback,
+				session->session);
+	}
+
+	return TRUE;
+}
 #else
 GType
 purple_media_backend_fs2_get_type(void)
diff --git a/libpurple/media/backend-iface.h b/libpurple/media/backend-iface.h
--- a/libpurple/media/backend-iface.h
+++ b/libpurple/media/backend-iface.h
@@ -83,6 +83,9 @@ struct _PurpleMediaBackendIface
 	void (*set_params) (PurpleMediaBackend *self,
 		guint num_params, GParameter *params);
 	const gchar **(*get_available_params) (void);
+	gboolean (*send_dtmf) (PurpleMediaBackend *self,
+		const gchar *sess_id, gchar dtmf, guint8 volume,
+		guint16 duration);
 };
 
 /**
diff --git a/pidgin/gtkmedia.c b/pidgin/gtkmedia.c
--- a/pidgin/gtkmedia.c
+++ b/pidgin/gtkmedia.c
@@ -43,6 +43,7 @@
 #ifdef GDK_WINDOWING_QUARTZ
 #include <gdk/gdkquartz.h>
 #endif
+#include <gdk/gdkkeysyms.h>
 
 #include "gtk3compat.h"
 
@@ -744,6 +745,136 @@ pidgin_media_add_audio_widget(PidginMedi
 }
 
 static void
+phone_dtmf_pressed_cb(GtkButton *button, gpointer user_data)
+{
+	PidginMedia *gtkmedia = user_data;
+	gint num;
+	gchar *sid;
+
+	num = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(button), "dtmf-digit"));
+	sid = g_object_get_data(G_OBJECT(button), "session-id");
+
+	purple_media_send_dtmf(gtkmedia->priv->media, sid, num, 25, 50);
+}
+
+static inline GtkWidget *
+phone_create_button(const gchar *text_hi, const gchar *text_lo)
+{
+	GtkWidget *button;
+	GtkWidget *label_hi;
+	GtkWidget *label_lo;
+	GtkWidget *grid;
+	gchar *text_hi_local;
+
+	if (text_hi)
+		text_hi_local = _(text_hi);
+	else
+		text_hi_local = "";
+
+	grid = gtk_vbox_new(TRUE, 0);
+
+	button = gtk_button_new();
+	label_hi = gtk_label_new(text_hi_local);
+	gtk_misc_set_alignment(GTK_MISC(label_hi), 0.5, 0.5);
+	gtk_box_pack_end(GTK_BOX(grid), label_hi, FALSE, TRUE, 0);
+	label_lo = gtk_label_new(text_lo);
+	gtk_misc_set_alignment(GTK_MISC(label_lo), 0.5, 0.5);
+	gtk_label_set_use_markup(GTK_LABEL(label_lo), TRUE);
+	gtk_box_pack_end(GTK_BOX(grid), label_lo, FALSE, TRUE, 0);
+	gtk_container_add(GTK_CONTAINER(button), grid);
+
+	return button;
+}
+
+static struct phone_label {
+	gchar *subtext;
+	gchar *text;
+	gchar chr;
+} phone_labels[] = {
+	{"<b>1</b>", NULL, '1'},
+	/* Translators note: These are the letters on the keys of a numeric
+	   keypad; translate according to §7.2.4 of
+	 http://www.etsi.org/deliver/etsi_es/202100_202199/202130/01.01.01_60/es_20213 */
+	 /* Letters on the '2' key of a numeric keypad */
+	{"<b>2</b>", N_("ABC"), '2'},
+	 /* Letters on the '3' key of a numeric keypad */
+	{"<b>3</b>", N_("DEF"), '3'},
+	 /* Letters on the '4' key of a numeric keypad */
+	{"<b>4</b>", N_("GHI"), '4'},
+	 /* Letters on the '5' key of a numeric keypad */
+	{"<b>5</b>", N_("JKL"), '5'},
+	 /* Letters on the '6' key of a numeric keypad */
+	{"<b>6</b>", N_("MNO"), '6'},
+	 /* Letters on the '7' key of a numeric keypad */
+	{"<b>7</b>", N_("PQRS"), '7'},
+	 /* Letters on the '8' key of a numeric keypad */
+	{"<b>8</b>", N_("TUV"), '8'},
+	 /* Letters on the '9' key of a numeric keypad */
+	{"<b>9</b>", N_("WXYZ"), '9'},
+	{"<b>*</b>", NULL, '*'},
+	{"<b>0</b>", NULL, '0'},
+	{"<b>#</b>", NULL, '#'},
+	{NULL, NULL, 0}
+};
+
+static gboolean
+pidgin_media_dtmf_key_press_event_cb(GtkWidget *widget,
+		GdkEvent *event, gpointer user_data)
+{
+	PidginMedia *gtkmedia = user_data;
+	GdkEventKey *key = (GdkEventKey *) event;
+
+	if (event->type != GDK_KEY_PRESS) {
+		return FALSE;
+	}
+
+	if ((key->keyval >= GDK_KEY_0 && key->keyval <= GDK_KEY_9) ||
+		key->keyval == GDK_KEY_asterisk ||
+		key->keyval == GDK_KEY_numbersign) {
+		gchar *sid = g_object_get_data(G_OBJECT(widget), "session-id");
+
+		purple_media_send_dtmf(gtkmedia->priv->media, sid, key->keyval, 25, 50);
+	}
+
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.