pidgin-2.10.11-add-dtmf-support.patch 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. # HG changeset patch
  2. # User David Woodhouse <David.Woodhouse@intel.com>
  3. # Date 1425675783 0
  4. # Node ID 6b4576edf2a694ab55d0d06d3643c44601a75b15
  5. # Parent 714ba418d0aa5ba0cc4cc3b9db37296cd2bbf041
  6. Add out-of-band DTMF support and dialpad to use it
  7. This is a backport of e4c122196b08 from the trunk. It adds the UI and
  8. farstream backend support for sending DTMF.
  9. Fixes #15575
  10. diff --git a/libpurple/media.c b/libpurple/media.c
  11. --- a/libpurple/media.c
  12. +++ b/libpurple/media.c
  13. @@ -1439,3 +1439,46 @@
  14. }
  15. #endif /* USE_GSTREAMER */
  16. +gboolean
  17. +purple_media_send_dtmf(PurpleMedia *media, const gchar *session_id,
  18. + gchar dtmf, guint8 volume, guint16 duration)
  19. +{
  20. +#ifdef USE_VV
  21. + PurpleAccount *account = NULL;
  22. + PurpleConnection *gc = NULL;
  23. + PurplePlugin *prpl = NULL;
  24. + PurplePluginProtocolInfo *prpl_info = NULL;
  25. + PurpleMediaBackendIface *backend_iface = NULL;
  26. +
  27. + if (media)
  28. + {
  29. + account = purple_media_get_account(media);
  30. + backend_iface = PURPLE_MEDIA_BACKEND_GET_INTERFACE(media->priv->backend);
  31. + }
  32. + if (account)
  33. + gc = purple_account_get_connection(account);
  34. + if (gc)
  35. + prpl = purple_connection_get_prpl(gc);
  36. + if (prpl)
  37. + prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
  38. +
  39. + if (dtmf == 'a')
  40. + dtmf = 'A';
  41. + else if (dtmf == 'b')
  42. + dtmf = 'B';
  43. + else if (dtmf == 'c')
  44. + dtmf = 'C';
  45. + else if (dtmf == 'd')
  46. + dtmf = 'D';
  47. +
  48. + g_return_val_if_fail(strchr("0123456789ABCD#*", dtmf), FALSE);
  49. +
  50. + if (backend_iface && backend_iface->send_dtmf
  51. + && backend_iface->send_dtmf(media->priv->backend,
  52. + session_id, dtmf, volume, duration))
  53. + {
  54. + return TRUE;
  55. + }
  56. +#endif
  57. + return FALSE;
  58. +}
  59. diff --git a/libpurple/media.h b/libpurple/media.h
  60. --- a/libpurple/media.h
  61. +++ b/libpurple/media.h
  62. @@ -437,6 +437,21 @@
  63. */
  64. void purple_media_remove_output_windows(PurpleMedia *media);
  65. +/**
  66. + * Sends a DTMF signal out-of-band.
  67. + *
  68. + * @param media The media instance to send a DTMF signal to.
  69. + * @param sess_id The session id of the session to send the DTMF signal on.
  70. + * @param dtmf The character representing the DTMF in the range [0-9#*A-D].
  71. + * @param volume The power level expressed in dBm0 after dropping the sign
  72. + * in the range of 0 to 63. A larger value represents a lower volume.
  73. + * @param duration The duration of the tone in milliseconds.
  74. + *
  75. + * @since 2.11
  76. + */
  77. +gboolean purple_media_send_dtmf(PurpleMedia *media, const gchar *session_id,
  78. + gchar dtmf, guint8 volume, guint16 duration);
  79. +
  80. #ifdef __cplusplus
  81. }
  82. #endif
  83. diff --git a/libpurple/media/backend-fs2.c b/libpurple/media/backend-fs2.c
  84. --- a/libpurple/media/backend-fs2.c
  85. +++ b/libpurple/media/backend-fs2.c
  86. @@ -94,6 +94,9 @@
  87. static void purple_media_backend_fs2_set_params(PurpleMediaBackend *self,
  88. guint num_params, GParameter *params);
  89. static const gchar **purple_media_backend_fs2_get_available_params(void);
  90. +static gboolean purple_media_backend_fs2_send_dtmf(
  91. + PurpleMediaBackend *self, const gchar *sess_id,
  92. + gchar dtmf, guint8 volume, guint16 duration);
  93. static void free_stream(PurpleMediaBackendFs2Stream *stream);
  94. static void free_session(PurpleMediaBackendFs2Session *session);
  95. @@ -499,6 +502,7 @@
  96. iface->set_send_codec = purple_media_backend_fs2_set_send_codec;
  97. iface->set_params = purple_media_backend_fs2_set_params;
  98. iface->get_available_params = purple_media_backend_fs2_get_available_params;
  99. + iface->send_dtmf = purple_media_backend_fs2_send_dtmf;
  100. }
  101. static FsMediaType
  102. @@ -2436,6 +2440,65 @@
  103. return supported_params;
  104. }
  105. +static gboolean
  106. +send_dtmf_callback(gpointer userdata)
  107. +{
  108. + FsSession *session = userdata;
  109. +
  110. + fs_session_stop_telephony_event(session);
  111. +
  112. + return FALSE;
  113. +}
  114. +static gboolean
  115. +purple_media_backend_fs2_send_dtmf(PurpleMediaBackend *self,
  116. + const gchar *sess_id, gchar dtmf, guint8 volume,
  117. + guint16 duration)
  118. +{
  119. + PurpleMediaBackendFs2Session *session;
  120. + FsDTMFEvent event;
  121. +
  122. + g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), FALSE);
  123. +
  124. + session = get_session(PURPLE_MEDIA_BACKEND_FS2(self), sess_id);
  125. + if (session == NULL)
  126. + return FALSE;
  127. +
  128. + /* Convert DTMF char into FsDTMFEvent enum */
  129. + switch(dtmf) {
  130. + case '0': event = FS_DTMF_EVENT_0; break;
  131. + case '1': event = FS_DTMF_EVENT_1; break;
  132. + case '2': event = FS_DTMF_EVENT_2; break;
  133. + case '3': event = FS_DTMF_EVENT_3; break;
  134. + case '4': event = FS_DTMF_EVENT_4; break;
  135. + case '5': event = FS_DTMF_EVENT_5; break;
  136. + case '6': event = FS_DTMF_EVENT_6; break;
  137. + case '7': event = FS_DTMF_EVENT_7; break;
  138. + case '8': event = FS_DTMF_EVENT_8; break;
  139. + case '9': event = FS_DTMF_EVENT_9; break;
  140. + case '*': event = FS_DTMF_EVENT_STAR; break;
  141. + case '#': event = FS_DTMF_EVENT_POUND; break;
  142. + case 'A': event = FS_DTMF_EVENT_A; break;
  143. + case 'B': event = FS_DTMF_EVENT_B; break;
  144. + case 'C': event = FS_DTMF_EVENT_C; break;
  145. + case 'D': event = FS_DTMF_EVENT_D; break;
  146. + default:
  147. + return FALSE;
  148. + }
  149. +
  150. + if (!fs_session_start_telephony_event(session->session,
  151. + event, volume)) {
  152. + return FALSE;
  153. + }
  154. +
  155. + if (duration <= 50) {
  156. + fs_session_stop_telephony_event(session->session);
  157. + } else {
  158. + purple_timeout_add(duration, send_dtmf_callback,
  159. + session->session);
  160. + }
  161. +
  162. + return TRUE;
  163. +}
  164. #else
  165. GType
  166. purple_media_backend_fs2_get_type(void)
  167. diff --git a/libpurple/media/backend-iface.h b/libpurple/media/backend-iface.h
  168. --- a/libpurple/media/backend-iface.h
  169. +++ b/libpurple/media/backend-iface.h
  170. @@ -71,6 +71,9 @@
  171. void (*set_params) (PurpleMediaBackend *self,
  172. guint num_params, GParameter *params);
  173. const gchar **(*get_available_params) (void);
  174. + gboolean (*send_dtmf) (PurpleMediaBackend *self,
  175. + const gchar *sess_id, gchar dtmf, guint8 volume,
  176. + guint16 duration);
  177. };
  178. /**
  179. diff --git a/pidgin/gtkmedia.c b/pidgin/gtkmedia.c
  180. --- a/pidgin/gtkmedia.c
  181. +++ b/pidgin/gtkmedia.c
  182. @@ -41,6 +41,7 @@
  183. #ifdef _WIN32
  184. #include <gdk/gdkwin32.h>
  185. #endif
  186. +#include <gdk/gdkkeysyms.h>
  187. #include <gst/interfaces/xoverlay.h>
  188. @@ -759,6 +760,136 @@
  189. }
  190. static void
  191. +phone_dtmf_pressed_cb(GtkButton *button, gpointer user_data)
  192. +{
  193. + PidginMedia *gtkmedia = user_data;
  194. + gint num;
  195. + gchar *sid;
  196. +
  197. + num = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(button), "dtmf-digit"));
  198. + sid = g_object_get_data(G_OBJECT(button), "session-id");
  199. +
  200. + purple_media_send_dtmf(gtkmedia->priv->media, sid, num, 25, 50);
  201. +}
  202. +
  203. +static inline GtkWidget *
  204. +phone_create_button(const gchar *text_hi, const gchar *text_lo)
  205. +{
  206. + GtkWidget *button;
  207. + GtkWidget *label_hi;
  208. + GtkWidget *label_lo;
  209. + GtkWidget *grid;
  210. + const gchar *text_hi_local;
  211. +
  212. + if (text_hi)
  213. + text_hi_local = _(text_hi);
  214. + else
  215. + text_hi_local = "";
  216. +
  217. + grid = gtk_vbox_new(TRUE, 0);
  218. +
  219. + button = gtk_button_new();
  220. + label_hi = gtk_label_new(text_hi_local);
  221. + gtk_misc_set_alignment(GTK_MISC(label_hi), 0.5, 0.5);
  222. + gtk_box_pack_end(GTK_BOX(grid), label_hi, FALSE, TRUE, 0);
  223. + label_lo = gtk_label_new(text_lo);
  224. + gtk_misc_set_alignment(GTK_MISC(label_lo), 0.5, 0.5);
  225. + gtk_label_set_use_markup(GTK_LABEL(label_lo), TRUE);
  226. + gtk_box_pack_end(GTK_BOX(grid), label_lo, FALSE, TRUE, 0);
  227. + gtk_container_add(GTK_CONTAINER(button), grid);
  228. +
  229. + return button;
  230. +}
  231. +
  232. +static struct phone_label {
  233. + gchar *subtext;
  234. + gchar *text;
  235. + gchar chr;
  236. +} phone_labels[] = {
  237. + {"<b>1</b>", NULL, '1'},
  238. + /* Translators note: These are the letters on the keys of a numeric
  239. + keypad; translate according to §7.2.4 of
  240. + http://www.etsi.org/deliver/etsi_es/202100_202199/202130/01.01.01_60/es_20213 */
  241. + /* Letters on the '2' key of a numeric keypad */
  242. + {"<b>2</b>", N_("ABC"), '2'},
  243. + /* Letters on the '3' key of a numeric keypad */
  244. + {"<b>3</b>", N_("DEF"), '3'},
  245. + /* Letters on the '4' key of a numeric keypad */
  246. + {"<b>4</b>", N_("GHI"), '4'},
  247. + /* Letters on the '5' key of a numeric keypad */
  248. + {"<b>5</b>", N_("JKL"), '5'},
  249. + /* Letters on the '6' key of a numeric keypad */
  250. + {"<b>6</b>", N_("MNO"), '6'},
  251. + /* Letters on the '7' key of a numeric keypad */
  252. + {"<b>7</b>", N_("PQRS"), '7'},
  253. + /* Letters on the '8' key of a numeric keypad */
  254. + {"<b>8</b>", N_("TUV"), '8'},
  255. + /* Letters on the '9' key of a numeric keypad */
  256. + {"<b>9</b>", N_("WXYZ"), '9'},
  257. + {"<b>*</b>", NULL, '*'},
  258. + {"<b>0</b>", NULL, '0'},
  259. + {"<b>#</b>", NULL, '#'},
  260. + {NULL, NULL, 0}
  261. +};
  262. +
  263. +static gboolean
  264. +pidgin_media_dtmf_key_press_event_cb(GtkWidget *widget,
  265. + GdkEvent *event, gpointer user_data)
  266. +{
  267. + PidginMedia *gtkmedia = user_data;
  268. + GdkEventKey *key = (GdkEventKey *) event;
  269. +
  270. + if (event->type != GDK_KEY_PRESS) {
  271. + return FALSE;
  272. + }
  273. +
  274. + if ((key->keyval >= GDK_KEY_0 && key->keyval <= GDK_KEY_9) ||
  275. + key->keyval == GDK_KEY_asterisk ||
  276. + key->keyval == GDK_KEY_numbersign) {
  277. + gchar *sid = g_object_get_data(G_OBJECT(widget), "session-id");
  278. +
  279. + purple_media_send_dtmf(gtkmedia->priv->media, sid, key->keyval, 25, 50);
  280. + }
  281. +
  282. + return FALSE;
  283. +}
  284. +
  285. +static GtkWidget *
  286. +pidgin_media_add_dtmf_widget(PidginMedia *gtkmedia,
  287. + PurpleMediaSessionType type, const gchar *_sid)
  288. +{
  289. + GtkWidget *grid = gtk_table_new(4, 3, TRUE);
  290. + GtkWidget *button;
  291. + gint index = 0;
  292. + GtkWindow *win = &gtkmedia->parent;
  293. +
  294. + /* Add buttons */
  295. + for (index = 0; phone_labels[index].subtext != NULL; index++) {
  296. + button = phone_create_button(phone_labels[index].text,
  297. + phone_labels[index].subtext);
  298. + g_signal_connect(button, "pressed",
  299. + G_CALLBACK(phone_dtmf_pressed_cb), gtkmedia);
  300. + g_object_set_data(G_OBJECT(button), "dtmf-digit",
  301. + GINT_TO_POINTER(phone_labels[index].chr));
  302. + g_object_set_data_full(G_OBJECT(button), "session-id",
  303. + g_strdup(_sid), g_free);
  304. + gtk_table_attach(GTK_TABLE(grid), button, index % 3,
  305. + index % 3 + 1, index / 3, index / 3 + 1,
  306. + GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND,
  307. + 2, 2);
  308. + }
  309. +
  310. + g_signal_connect(G_OBJECT(win), "key-press-event",
  311. + G_CALLBACK(pidgin_media_dtmf_key_press_event_cb), gtkmedia);
  312. + g_object_set_data_full(G_OBJECT(win), "session-id",
  313. + g_strdup(_sid), g_free);
  314. +
  315. + gtk_widget_show_all(grid);
  316. +
  317. + return grid;
  318. +}
  319. +
  320. +static void
  321. pidgin_media_ready_cb(PurpleMedia *media, PidginMedia *gtkmedia, const gchar *sid)
  322. {
  323. GtkWidget *send_widget = NULL, *recv_widget = NULL, *button_widget = NULL;
  324. @@ -888,7 +1019,11 @@
  325. gtk_box_pack_end(GTK_BOX(recv_widget),
  326. pidgin_media_add_audio_widget(gtkmedia,
  327. - PURPLE_MEDIA_SEND_AUDIO, NULL), FALSE, FALSE, 0);
  328. + PURPLE_MEDIA_SEND_AUDIO, sid), FALSE, FALSE, 0);
  329. +
  330. + gtk_box_pack_end(GTK_BOX(recv_widget),
  331. + pidgin_media_add_dtmf_widget(gtkmedia,
  332. + PURPLE_MEDIA_SEND_AUDIO, sid), FALSE, FALSE, 0);
  333. }
  334. if (type & PURPLE_MEDIA_AUDIO &&