1 /*
2 * Configures microphones and webcams for voice and video
3 * Copyright (C) 2009 Mike Ruprecht <cmaiku@gmail.com>
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA.
18 */
19 #include "internal.h"
20
21 #include "debug.h"
22 #include "mediamanager.h"
23 #include "media-gst.h"
24 #include "version.h"
25 #include "gtkplugin.h"
26 #include "gtkutils.h"
27 #include "gtkprefs.h"
28
29 #if GST_CHECK_VERSION(1,0,0)
30 #include <gst/video/videooverlay.h>
31 #else
32 #include <gst/interfaces/propertyprobe.h>
33 #endif
34
35 /* container window for showing a stand-alone configurator */
36 static GtkWidget *window = NULL;
37
38 static PurpleMediaElementInfo *old_video_src = NULL, *old_video_sink = NULL,
39 *old_audio_src = NULL, *old_audio_sink = NULL;
40
41 static const gchar *AUDIO_SRC_PLUGINS[] = {
42 "alsasrc", N_("ALSA"),
43 /* "esdmon", "ESD", ? */
44 "osssrc", N_("OSS"),
45 "pulsesrc", N_("PulseAudio"),
46 "sndiosrc", N_("sndio"),
47 /* "audiotestsrc wave=silence", "Silence", */
48 "audiotestsrc", N_("Test Sound"),
49 NULL
50 };
51
52 static const gchar *AUDIO_SINK_PLUGINS[] = {
53 "alsasink", N_("ALSA"),
54 "artsdsink", N_("aRts"),
55 "esdsink", N_("ESD"),
56 "osssink", N_("OSS"),
57 "pulsesink", N_("PulseAudio"),
58 "sndiosink", N_("sndio"),
59 NULL
60 };
61
62 static const gchar *VIDEO_SRC_PLUGINS[] = {
63 "videotestsrc", N_("Test Input"),
64 "dshowvideosrc", N_("DirectDraw"),
65 "ksvideosrc", N_("KS Video"),
66 "qcamsrc", N_("Quickcam"),
67 "v4lsrc", N_("Video4Linux"),
68 "v4l2src", N_("Video4Linux2"),
69 "v4lmjpegsrc", N_("Video4Linux MJPEG"),
70 NULL
71 };
72
73 static const gchar *VIDEO_SINK_PLUGINS[] = {
74 /* "aasink", "AALib", Didn't work for me */
75 "directdrawsink", N_("DirectDraw"),
76 "glimagesink", N_("OpenGL"),
77 "ximagesink", N_("X Window System"),
78 "xvimagesink", N_("X Window System (Xv)"),
79 NULL
80 };
81
82 static GList *
get_element_devices(const gchar * element_name)83 get_element_devices(const gchar *element_name)
84 {
85 GList *ret = NULL;
86 GstElement *element;
87 GObjectClass *klass;
88 #if !GST_CHECK_VERSION(1,0,0)
89 GstPropertyProbe *probe;
90 const GParamSpec *pspec;
91 #endif
92
93 ret = g_list_prepend(ret, (gpointer)_("Default"));
94 ret = g_list_prepend(ret, "");
95
96 if (purple_strequal(element_name, "<custom>") || (*element_name == '\0')) {
97 return g_list_reverse(ret);
98 }
99
100 element = gst_element_factory_make(element_name, "test");
101 if(!element) {
102 purple_debug_info("vvconfig", "'%s' - unable to find element\n", element_name);
103 return g_list_reverse(ret);
104 }
105
106 klass = G_OBJECT_GET_CLASS (element);
107 if(!klass) {
108 purple_debug_info("vvconfig", "'%s' - unable to find G_Object Class\n", element_name);
109 return g_list_reverse(ret);
110 }
111
112 #if GST_CHECK_VERSION(1,0,0)
113 purple_debug_info("vvconfig", "'%s' - gstreamer-1.0 doesn't support "
114 "property probing\n", element_name);
115 #else
116 if (!g_object_class_find_property(klass, "device") ||
117 !GST_IS_PROPERTY_PROBE(element) ||
118 !(probe = GST_PROPERTY_PROBE(element)) ||
119 !(pspec = gst_property_probe_get_property(probe, "device"))) {
120 purple_debug_info("vvconfig", "'%s' - no device\n", element_name);
121 } else {
122 gsize n;
123 GValueArray *array;
124
125 /* Set autoprobe[-fps] to FALSE to avoid delays when probing. */
126 if (g_object_class_find_property (klass, "autoprobe")) {
127 g_object_set (G_OBJECT (element), "autoprobe", FALSE, NULL);
128 if (g_object_class_find_property (klass, "autoprobe-fps")) {
129 g_object_set (G_OBJECT (element), "autoprobe-fps", FALSE, NULL);
130 }
131 }
132
133 array = gst_property_probe_probe_and_get_values (probe, pspec);
134 if (array == NULL) {
135 purple_debug_info("vvconfig", "'%s' has no devices\n", element_name);
136 return g_list_reverse(ret);
137 }
138
139 for (n=0; n < array->n_values; ++n) {
140 GValue *device;
141 const gchar *name;
142 const gchar *device_name;
143
144 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
145 /* GValueArray is in gstreamer-0.10 API */
146 device = g_value_array_get_nth(array, n);
147 G_GNUC_END_IGNORE_DEPRECATIONS
148 g_object_set_property(G_OBJECT(element), "device", device);
149 if (gst_element_set_state(element, GST_STATE_READY)
150 != GST_STATE_CHANGE_SUCCESS) {
151 purple_debug_warning("vvconfig",
152 "Error changing state of %s\n",
153 element_name);
154 continue;
155 }
156
157 g_object_get(G_OBJECT(element), "device-name", &name, NULL);
158 device_name = g_value_get_string(device);
159 if (name == NULL)
160 name = _("Unknown");
161 purple_debug_info("vvconfig", "Found device %s : %s for %s\n",
162 device_name, name, element_name);
163 ret = g_list_prepend(ret, (gpointer)name);
164 ret = g_list_prepend(ret, (gpointer)device_name);
165 gst_element_set_state(element, GST_STATE_NULL);
166 }
167 }
168 #endif
169 gst_object_unref(element);
170
171 return g_list_reverse(ret);
172 }
173
174 static GList *
get_element_plugins(const gchar ** plugins)175 get_element_plugins(const gchar **plugins)
176 {
177 GList *ret = NULL;
178
179 ret = g_list_prepend(ret, "Default");
180 ret = g_list_prepend(ret, "");
181 for (; plugins[0] && plugins[1]; plugins += 2) {
182 #if GST_CHECK_VERSION(1,0,0)
183 if (gst_registry_check_feature_version(gst_registry_get(),
184 plugins[0], 0, 0, 0)) {
185 #else
186 if (gst_default_registry_check_feature_version(
187 plugins[0], 0, 0, 0)) {
188 #endif
189 ret = g_list_prepend(ret, (gpointer)plugins[1]);
190 ret = g_list_prepend(ret, (gpointer)plugins[0]);
191 }
192 }
193 ret = g_list_reverse(ret);
194 return ret;
195 }
196
197 static void
198 device_changed_cb(const gchar *name, PurplePrefType type,
199 gconstpointer value, gpointer data)
200 {
201 GtkSizeGroup *sg = data;
202 GtkWidget *parent, *widget;
203 GSList *widgets;
204 GList *devices;
205 GValue gvalue;
206 gint position;
207 gchar *label, *pref;
208
209 widgets = gtk_size_group_get_widgets(GTK_SIZE_GROUP(sg));
210 for (; widgets; widgets = g_slist_next(widgets)) {
211 const gchar *widget_name =
212 gtk_widget_get_name(GTK_WIDGET(widgets->data));
213 if (purple_strequal(widget_name, name)) {
214 gchar *temp_str;
215 gchar delimiters[3] = {0, 0, 0};
216 const gchar *text;
217 gint keyval, pos;
218
219 widget = widgets->data;
220 /* Get label with _ from the GtkLabel */
221 text = gtk_label_get_text(GTK_LABEL(widget));
222 keyval = gtk_label_get_mnemonic_keyval(GTK_LABEL(widget));
223 delimiters[0] = g_ascii_tolower(keyval);
224 delimiters[1] = g_ascii_toupper(keyval);
225 pos = strcspn(text, delimiters);
226 if (pos != -1) {
227 temp_str = g_strndup(text, pos);
228 label = g_strconcat(temp_str, "_",
229 text + pos, NULL);
230 g_free(temp_str);
231 } else {
232 label = g_strdup(text);
233 }
234 break;
235 }
236 }
237
238 if (widgets == NULL)
239 return;
240
241 parent = gtk_widget_get_parent(widget);
242 widget = parent;
243 parent = gtk_widget_get_parent(GTK_WIDGET(widget));
244 gvalue.g_type = 0;
245 g_value_init(&gvalue, G_TYPE_INT);
246 gtk_container_child_get_property(GTK_CONTAINER(parent),
247 GTK_WIDGET(widget), "position", &gvalue);
248 position = g_value_get_int(&gvalue);
249 g_value_unset(&gvalue);
250 gtk_widget_destroy(widget);
251
252 pref = g_strdup(name);
253 strcpy(pref + strlen(pref) - strlen("plugin"), "device");
254 devices = get_element_devices(value);
255 if (g_list_find_custom(devices, purple_prefs_get_string(pref),
256 (GCompareFunc)strcmp) == NULL)
257 {
258 GList *next = g_list_next(devices);
259
260 if(next != NULL) {
261 purple_prefs_set_string(pref, next->data);
262 }
263 }
264 widget = pidgin_prefs_dropdown_from_list(parent,
265 label, PURPLE_PREF_STRING,
266 pref, devices);
267 g_list_free(devices);
268 g_signal_connect_swapped(widget, "destroy",
269 G_CALLBACK(g_free), pref);
270 g_free(label);
271 gtk_misc_set_alignment(GTK_MISC(widget), 0, 0.5);
272 gtk_widget_set_name(widget, name);
273 gtk_size_group_add_widget(sg, widget);
274 gtk_box_reorder_child(GTK_BOX(parent),
275 gtk_widget_get_parent(GTK_WIDGET(widget)), position);
276 }
277
278 static void
279 get_plugin_frame(GtkWidget *parent, GtkSizeGroup *sg,
280 const gchar *name, const gchar *plugin_label,
281 const gchar **plugin_strs, const gchar *plugin_pref,
282 const gchar *device_label, const gchar *device_pref)
283 {
284 GtkWidget *vbox, *widget;
285 GList *plugins, *devices;
286
287 vbox = pidgin_make_frame(parent, name);
288
289 /* Setup plugin preference */
290 plugins = get_element_plugins(plugin_strs);
291 widget = pidgin_prefs_dropdown_from_list(vbox, plugin_label,
292 PURPLE_PREF_STRING, plugin_pref, plugins);
293 g_list_free(plugins);
294 gtk_size_group_add_widget(sg, widget);
295 gtk_misc_set_alignment(GTK_MISC(widget), 0, 0.5);
296
297 /* Setup device preference */
298 devices = get_element_devices(purple_prefs_get_string(plugin_pref));
299 if (g_list_find_custom(devices, purple_prefs_get_string(device_pref),
300 (GCompareFunc) strcmp) == NULL)
301 {
302 GList *next = g_list_next(devices);
303 if(next != NULL) {
304 purple_prefs_set_string(device_pref, next->data);
305 }
306 }
307 widget = pidgin_prefs_dropdown_from_list(vbox, device_label,
308 PURPLE_PREF_STRING, device_pref, devices);
309 g_list_free(devices);
310 gtk_widget_set_name(widget, plugin_pref);
311 gtk_size_group_add_widget(sg, widget);
312 gtk_misc_set_alignment(GTK_MISC(widget), 0, 0.5);
313
314 purple_prefs_connect_callback(vbox, plugin_pref,
315 device_changed_cb, sg);
316 g_signal_connect_swapped(vbox, "destroy",
317 G_CALLBACK(purple_prefs_disconnect_by_handle), vbox);
318 }
319
320 static GtkWidget *
321 get_plugin_config_frame(PurplePlugin *plugin) {
322 GtkWidget *notebook, *vbox_audio, *vbox_video;
323 GtkSizeGroup *sg;
324
325 notebook = gtk_notebook_new();
326 gtk_container_set_border_width(GTK_CONTAINER(notebook),
327 PIDGIN_HIG_BORDER);
328 gtk_widget_show(notebook);
329
330 vbox_audio = gtk_vbox_new(FALSE, PIDGIN_HIG_CAT_SPACE);
331 vbox_video = gtk_vbox_new(FALSE, PIDGIN_HIG_CAT_SPACE);
332 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
333 vbox_audio, gtk_label_new(_("Audio")));
334 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
335 vbox_video, gtk_label_new(_("Video")));
336 gtk_container_set_border_width(GTK_CONTAINER (vbox_audio),
337 PIDGIN_HIG_BORDER);
338 gtk_container_set_border_width(GTK_CONTAINER (vbox_video),
339 PIDGIN_HIG_BORDER);
340
341 gtk_widget_show(vbox_audio);
342 gtk_widget_show(vbox_video);
343
344 sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
345
346 get_plugin_frame(vbox_audio, sg, _("Output"), _("_Plugin"), AUDIO_SINK_PLUGINS,
347 "/plugins/core/vvconfig/audio/sink/plugin", _("_Device"),
348 "/plugins/core/vvconfig/audio/sink/device");
349 get_plugin_frame(vbox_audio, sg, _("Input"), _("P_lugin"), AUDIO_SRC_PLUGINS,
350 "/plugins/core/vvconfig/audio/src/plugin", _("D_evice"),
351 "/plugins/core/vvconfig/audio/src/device");
352
353 get_plugin_frame(vbox_video, sg, _("Output"), _("_Plugin"), VIDEO_SINK_PLUGINS,
354 "/plugins/gtk/vvconfig/video/sink/plugin", _("_Device"),
355 "/plugins/gtk/vvconfig/video/sink/device");
356 get_plugin_frame(vbox_video, sg, _("Input"), _("P_lugin"), VIDEO_SRC_PLUGINS,
357 "/plugins/core/vvconfig/video/src/plugin", _("D_evice"),
358 "/plugins/core/vvconfig/video/src/device");
359
360 return notebook;
361 }
362
363 static GstElement *
364 create_video_src(PurpleMedia *media,
365 const gchar *session_id, const gchar *participant)
366 {
367 const gchar *plugin = purple_prefs_get_string(
368 "/plugins/core/vvconfig/video/src/plugin");
369 const gchar *device = purple_prefs_get_string(
370 "/plugins/core/vvconfig/video/src/device");
371 GstElement *ret;
372
373 if (plugin[0] == '\0')
374 return purple_media_element_info_call_create(old_video_src,
375 media, session_id, participant);
376
377 ret = gst_element_factory_make(plugin, "vvconfig-videosrc");
378 if (device[0] != '\0')
379 g_object_set(G_OBJECT(ret), "device", device, NULL);
380 if (purple_strequal(plugin, "videotestsrc"))
381 g_object_set(G_OBJECT(ret), "is-live", 1, NULL);
382 return ret;
383 }
384
385 static void
386 videosink_disable_last_sample(GstElement *sink)
387 {
388 GObjectClass *klass = G_OBJECT_GET_CLASS(sink);
389
390 if (g_object_class_find_property(klass, "enable-last-sample")) {
391 g_object_set(sink, "enable-last-sample", FALSE, NULL);
392 }
393 }
394
395 static void
396 autovideosink_child_added_cb(GstChildProxy *child_proxy, GObject *object,
397 #if GST_CHECK_VERSION(1,0,0)
398 gchar *name,
399 #endif
400 gpointer user_data)
401 {
402 videosink_disable_last_sample(GST_ELEMENT(object));
403 }
404
405 static GstElement *
406 create_video_sink(PurpleMedia *media,
407 const gchar *session_id, const gchar *participant)
408 {
409 const gchar *plugin = purple_prefs_get_string(
410 "/plugins/gtk/vvconfig/video/sink/plugin");
411 const gchar *device = purple_prefs_get_string(
412 "/plugins/gtk/vvconfig/video/sink/device");
413 GstElement *ret;
414
415 if (plugin[0] == '\0')
416 return purple_media_element_info_call_create(old_video_sink,
417 media, session_id, participant);
418
419 ret = gst_element_factory_make(plugin, NULL);
420 if (device[0] != '\0')
421 g_object_set(G_OBJECT(ret), "device", device, NULL);
422
423 if (purple_strequal(plugin, "autovideosink")) {
424 g_signal_connect(ret, "child-added",
425 G_CALLBACK(autovideosink_child_added_cb), NULL);
426 } else {
427 videosink_disable_last_sample(ret);
428 }
429
430 return ret;
431 }
432
433 static GstElement *
434 create_audio_src(PurpleMedia *media,
435 const gchar *session_id, const gchar *participant)
436 {
437 const gchar *plugin = purple_prefs_get_string(
438 "/plugins/core/vvconfig/audio/src/plugin");
439 const gchar *device = purple_prefs_get_string(
440 "/plugins/core/vvconfig/audio/src/device");
441 GstElement *ret;
442
443 if (plugin[0] == '\0')
444 return purple_media_element_info_call_create(old_audio_src,
445 media, session_id, participant);
446
447 ret = gst_element_factory_make(plugin, NULL);
448 if (device[0] != '\0')
449 g_object_set(G_OBJECT(ret), "device", device, NULL);
450 return ret;
451 }
452
453 static GstElement *
454 create_audio_sink(PurpleMedia *media,
455 const gchar *session_id, const gchar *participant)
456 {
457 const gchar *plugin = purple_prefs_get_string(
458 "/plugins/core/vvconfig/audio/sink/plugin");
459 const gchar *device = purple_prefs_get_string(
460 "/plugins/core/vvconfig/audio/sink/device");
461 GstElement *ret;
462
463 if (plugin[0] == '\0')
464 return purple_media_element_info_call_create(old_audio_sink,
465 media, session_id, participant);
466
467 ret = gst_element_factory_make(plugin, NULL);
468 if (device[0] != '\0')
469 g_object_set(G_OBJECT(ret), "device", device, NULL);
470 return ret;
471 }
472
473 static void
474 set_element_info_cond(PurpleMediaElementInfo *old_info,
475 PurpleMediaElementInfo *new_info, const gchar *id)
476 {
477 gchar *element_id = purple_media_element_info_get_id(old_info);
478 if (purple_strequal(element_id, id))
479 purple_media_manager_set_active_element(
480 purple_media_manager_get(), new_info);
481 g_free(element_id);
482 }
483
484 static gboolean
485 plugin_load(PurplePlugin *plugin)
486 {
487 PurpleMediaManager *manager;
488 PurpleMediaElementInfo *video_src, *video_sink,
489 *audio_src, *audio_sink;
490
491 /* Disable the plugin if the UI doesn't support VV */
492 if (purple_media_manager_get_ui_caps(purple_media_manager_get()) ==
493 PURPLE_MEDIA_CAPS_NONE)
494 return FALSE;
495
496 purple_prefs_add_none("/plugins/core/vvconfig");
497 purple_prefs_add_none("/plugins/core/vvconfig/audio");
498 purple_prefs_add_none("/plugins/core/vvconfig/audio/src");
499 purple_prefs_add_string("/plugins/core/vvconfig/audio/src/plugin", "");
500 purple_prefs_add_string("/plugins/core/vvconfig/audio/src/device", "");
501 purple_prefs_add_none("/plugins/core/vvconfig/audio/sink");
502 purple_prefs_add_string("/plugins/core/vvconfig/audio/sink/plugin", "");
503 purple_prefs_add_string("/plugins/core/vvconfig/audio/sink/device", "");
504 purple_prefs_add_none("/plugins/core/vvconfig/video");
505 purple_prefs_add_none("/plugins/core/vvconfig/video/src");
506 purple_prefs_add_string("/plugins/core/vvconfig/video/src/plugin", "");
507 purple_prefs_add_string("/plugins/core/vvconfig/video/src/device", "");
508 purple_prefs_add_none("/plugins/gtk/vvconfig");
509 purple_prefs_add_none("/plugins/gtk/vvconfig/video");
510 purple_prefs_add_none("/plugins/gtk/vvconfig/video/sink");
511 purple_prefs_add_string("/plugins/gtk/vvconfig/video/sink/plugin", "");
512 purple_prefs_add_string("/plugins/gtk/vvconfig/video/sink/device", "");
513
514 video_src = g_object_new(PURPLE_TYPE_MEDIA_ELEMENT_INFO,
515 "id", "vvconfig-videosrc",
516 "name", "VV Conf Plugin Video Source",
517 "type", PURPLE_MEDIA_ELEMENT_VIDEO
518 | PURPLE_MEDIA_ELEMENT_SRC
519 | PURPLE_MEDIA_ELEMENT_ONE_SRC
520 | PURPLE_MEDIA_ELEMENT_UNIQUE,
521 "create-cb", create_video_src, NULL);
522 video_sink = g_object_new(PURPLE_TYPE_MEDIA_ELEMENT_INFO,
523 "id", "vvconfig-videosink",
524 "name", "VV Conf Plugin Video Sink",
525 "type", PURPLE_MEDIA_ELEMENT_VIDEO
526 | PURPLE_MEDIA_ELEMENT_SINK
527 | PURPLE_MEDIA_ELEMENT_ONE_SINK,
528 "create-cb", create_video_sink, NULL);
529 audio_src = g_object_new(PURPLE_TYPE_MEDIA_ELEMENT_INFO,
530 "id", "vvconfig-audiosrc",
531 "name", "VV Conf Plugin Audio Source",
532 "type", PURPLE_MEDIA_ELEMENT_AUDIO
533 | PURPLE_MEDIA_ELEMENT_SRC
534 | PURPLE_MEDIA_ELEMENT_ONE_SRC
535 | PURPLE_MEDIA_ELEMENT_UNIQUE,
536 "create-cb", create_audio_src, NULL);
537 audio_sink = g_object_new(PURPLE_TYPE_MEDIA_ELEMENT_INFO,
538 "id", "vvconfig-audiosink",
539 "name", "VV Conf Plugin Audio Sink",
540 "type", PURPLE_MEDIA_ELEMENT_AUDIO
541 | PURPLE_MEDIA_ELEMENT_SINK
542 | PURPLE_MEDIA_ELEMENT_ONE_SINK,
543 "create-cb", create_audio_sink, NULL);
544
545 purple_debug_info("gtkmedia", "Registering media element types\n");
546 manager = purple_media_manager_get();
547
548 old_video_src = purple_media_manager_get_active_element(manager,
549 PURPLE_MEDIA_ELEMENT_VIDEO | PURPLE_MEDIA_ELEMENT_SRC);
550 old_video_sink = purple_media_manager_get_active_element(manager,
551 PURPLE_MEDIA_ELEMENT_VIDEO | PURPLE_MEDIA_ELEMENT_SINK);
552 old_audio_src = purple_media_manager_get_active_element(manager,
553 PURPLE_MEDIA_ELEMENT_AUDIO | PURPLE_MEDIA_ELEMENT_SRC);
554 old_audio_sink = purple_media_manager_get_active_element(manager,
555 PURPLE_MEDIA_ELEMENT_AUDIO | PURPLE_MEDIA_ELEMENT_SINK);
556
557 set_element_info_cond(old_video_src, video_src, "pidgindefaultvideosrc");
558 set_element_info_cond(old_video_sink, video_sink, "pidgindefaultvideosink");
559 set_element_info_cond(old_audio_src, audio_src, "pidgindefaultaudiosrc");
560 set_element_info_cond(old_audio_sink, audio_sink, "pidgindefaultaudiosink");
561
562 return TRUE;
563 }
564
565 static void
566 config_destroy(GtkObject *w, gpointer nul)
567 {
568 purple_debug_info("vvconfig", "closing vv configuration window\n");
569 window = NULL;
570 }
571
572 static void
573 config_close(GtkObject *w, gpointer nul)
574 {
575 gtk_widget_destroy(GTK_WIDGET(window));
576 }
577
578 typedef GtkWidget *(*FrameCreateCb)(PurplePlugin *plugin);
579
580 static void
581 show_config(PurplePluginAction *action)
582 {
583 if (!window) {
584 FrameCreateCb create_frame = action->user_data;
585 GtkWidget *vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER);
586 GtkWidget *hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BORDER);
587 GtkWidget *config_frame = create_frame(NULL);
588 GtkWidget *close = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
589
590 gtk_container_add(GTK_CONTAINER(vbox), config_frame);
591 gtk_container_add(GTK_CONTAINER(vbox), hbox);
592 window = pidgin_create_window(action->label,
593 PIDGIN_HIG_BORDER, NULL, FALSE);
594 g_signal_connect(G_OBJECT(window), "destroy",
595 G_CALLBACK(config_destroy), NULL);
596 g_signal_connect(G_OBJECT(close), "clicked",
597 G_CALLBACK(config_close), NULL);
598 gtk_box_pack_end(GTK_BOX(hbox), close, FALSE, FALSE, PIDGIN_HIG_BORDER);
599 gtk_container_add(GTK_CONTAINER(window), vbox);
600 gtk_widget_show(GTK_WIDGET(close));
601 gtk_widget_show(GTK_WIDGET(vbox));
602 gtk_widget_show(GTK_WIDGET(hbox));
603 }
604 gtk_window_present(GTK_WINDOW(window));
605 }
606
607 static GstElement *
608 create_pipeline()
609 {
610 GstElement *pipeline = gst_pipeline_new("voicetest");
611 GstElement *src = create_audio_src(NULL, NULL, NULL);
612 GstElement *sink = create_audio_sink(NULL, NULL, NULL);
613 GstElement *volume = gst_element_factory_make("volume", "volume");
614 GstElement *level = gst_element_factory_make("level", "level");
615 GstElement *valve = gst_element_factory_make("valve", "valve");
616
617 gst_bin_add_many(GST_BIN(pipeline), src, volume, level, valve, sink, NULL);
618 gst_element_link_many(src, volume, level, valve, sink, NULL);
619
620 gst_element_set_state(GST_ELEMENT(pipeline), GST_STATE_PLAYING);
621
622 return pipeline;
623 }
624
625 static void
626 on_volume_change_cb(GtkRange *range, GstBin *pipeline)
627 {
628 GstElement *volume;
629
630 g_return_if_fail(pipeline != NULL);
631
632 volume = gst_bin_get_by_name(pipeline, "volume");
633 g_object_set(volume, "volume", gtk_range_get_value(range) / 10.0, NULL);
634 }
635
636 static gdouble
637 gst_msg_db_to_percent(GstMessage *msg, gchar *value_name)
638 {
639 const GValue *list;
640 const GValue *value;
641 gdouble value_db;
642 gdouble percent;
643
644 list = gst_structure_get_value(
645 gst_message_get_structure(msg), value_name);
646 #if GST_CHECK_VERSION(1,0,0)
647 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
648 value = g_value_array_get_nth(g_value_get_boxed(list), 0);
649 G_GNUC_END_IGNORE_DEPRECATIONS
650 #else
651 value = gst_value_list_get_value(list, 0);
652 #endif
653 value_db = g_value_get_double(value);
654 percent = pow(10, value_db / 20);
655 return (percent > 1.0) ? 1.0 : percent;
656 }
657
658 typedef struct
659 {
660 GtkProgressBar *level;
661 GtkRange *threshold;
662 } BusCbCtx;
663
664 static gboolean
665 gst_bus_cb(GstBus *bus, GstMessage *msg, BusCbCtx *ctx)
666 {
667 if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_ELEMENT &&
668 gst_structure_has_name(gst_message_get_structure(msg), "level")) {
669
670 GstElement *src = GST_ELEMENT(GST_MESSAGE_SRC(msg));
671 gchar *name = gst_element_get_name(src);
672
673 if (purple_strequal(name, "level")) {
674 gdouble percent;
675 gdouble threshold;
676 GstElement *valve;
677
678 percent = gst_msg_db_to_percent(msg, "rms");
679 percent *= 5;
680 if (percent > 1.0)
681 percent = 1.0;
682 gtk_progress_bar_set_fraction(ctx->level, percent);
683
684 percent = gst_msg_db_to_percent(msg, "decay");
685 threshold = gtk_range_get_value(ctx->threshold) / 100.0;
686 valve = gst_bin_get_by_name(GST_BIN(GST_ELEMENT_PARENT(src)), "valve");
687 g_object_set(valve, "drop", (percent < threshold), NULL);
688 g_object_set(ctx->level,
689 "text", (percent < threshold) ? _("DROP") : " ", NULL);
690 }
691
692 g_free(name);
693 }
694
695 return TRUE;
696 }
697
698 static void
699 voice_test_frame_destroy_cb(GtkObject *w, GstElement *pipeline)
700 {
701 g_return_if_fail(GST_IS_ELEMENT(pipeline));
702
703 gst_element_set_state(pipeline, GST_STATE_NULL);
704 gst_object_unref(pipeline);
705 }
706
707 static void
708 volume_scale_destroy_cb(GtkRange *volume, gpointer nul)
709 {
710 purple_prefs_set_int("/purple/media/audio/volume/input",
711 gtk_range_get_value(volume));
712 }
713
714 static gchar*
715 threshold_value_format_cb(GtkScale *scale, gdouble value)
716 {
717 return g_strdup_printf ("%.*f%%", gtk_scale_get_digits(scale), value);
718 }
719
720 static void
721 threshold_scale_destroy_cb(GtkRange *threshold, gpointer nul)
722 {
723 purple_prefs_set_int("/purple/media/audio/silence_threshold",
724 gtk_range_get_value(threshold));
725 }
726
727 static GtkWidget *
728 get_voice_test_frame(PurplePlugin *plugin)
729 {
730 GtkWidget *vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER);
731 GtkWidget *level = gtk_progress_bar_new();
732 GtkWidget *volume = gtk_hscale_new_with_range(0, 100, 1);
733 GtkWidget *threshold = gtk_hscale_new_with_range(0, 100, 1);
734 GtkWidget *label;
735 GtkTable *table = GTK_TABLE(gtk_table_new(2, 2, FALSE));
736
737 GstElement *pipeline;
738 GstBus *bus;
739 BusCbCtx *ctx;
740
741 g_object_set(vbox, "width-request", 500, NULL);
742
743 gtk_table_set_row_spacings(table, PIDGIN_HIG_BOX_SPACE);
744 gtk_table_set_col_spacings(table, PIDGIN_HIG_BOX_SPACE);
745
746 label = gtk_label_new(_("Volume:"));
747 g_object_set(label, "xalign", 0.0, NULL);
748 gtk_table_attach(table, label, 0, 1, 0, 1, GTK_FILL, 0, 0, 0);
749 gtk_table_attach_defaults(table, volume, 1, 2, 0, 1);
750 label = gtk_label_new(_("Silence threshold:"));
751 g_object_set(label, "xalign", 0.0, "yalign", 1.0, NULL);
752 gtk_table_attach(table, label, 0, 1, 1, 2, GTK_FILL, GTK_FILL, 0, 0);
753 gtk_table_attach_defaults(table, threshold, 1, 2, 1, 2);
754
755 gtk_container_add(GTK_CONTAINER(vbox), level);
756 gtk_container_add(GTK_CONTAINER(vbox), GTK_WIDGET(table));
757 gtk_widget_show_all(vbox);
758
759 pipeline = create_pipeline();
760 bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline));
761 gst_bus_add_signal_watch(bus);
762 ctx = g_new(BusCbCtx, 1);
763 ctx->level = GTK_PROGRESS_BAR(level);
764 ctx->threshold = GTK_RANGE(threshold);
765 g_signal_connect_data(bus, "message", G_CALLBACK(gst_bus_cb),
766 ctx, (GClosureNotify)g_free, 0);
767 gst_object_unref(bus);
768
769 g_signal_connect(volume, "value-changed",
770 (GCallback)on_volume_change_cb, pipeline);
771
772 gtk_range_set_value(GTK_RANGE(volume),
773 purple_prefs_get_int("/purple/media/audio/volume/input"));
774 gtk_widget_set(volume, "draw-value", FALSE, NULL);
775
776 gtk_range_set_value(GTK_RANGE(threshold),
777 purple_prefs_get_int("/purple/media/audio/silence_threshold"));
778
779 g_signal_connect(vbox, "destroy",
780 G_CALLBACK(voice_test_frame_destroy_cb), pipeline);
781 g_signal_connect(volume, "destroy",
782 G_CALLBACK(volume_scale_destroy_cb), NULL);
783 g_signal_connect(threshold, "format-value",
784 G_CALLBACK(threshold_value_format_cb), NULL);
785 g_signal_connect(threshold, "destroy",
786 G_CALLBACK(threshold_scale_destroy_cb), NULL);
787
788 return vbox;
789 }
790
791 static GList *
792 actions(PurplePlugin *plugin, gpointer context)
793 {
794 GList *l = NULL;
795 PurplePluginAction *act = NULL;
796
797 act = purple_plugin_action_new(_("Input and Output Settings"),
798 show_config);
799 act->user_data = get_plugin_config_frame;
800 l = g_list_append(l, act);
801
802 act = purple_plugin_action_new(_("Microphone Test"),
803 show_config);
804 act->user_data = get_voice_test_frame;
805 l = g_list_append(l, act);
806
807 return l;
808 }
809
810 static gboolean
811 plugin_unload(PurplePlugin *plugin)
812 {
813 PurpleMediaManager *manager = purple_media_manager_get();
814 purple_media_manager_set_active_element(manager, old_video_src);
815 purple_media_manager_set_active_element(manager, old_video_sink);
816 purple_media_manager_set_active_element(manager, old_audio_src);
817 purple_media_manager_set_active_element(manager, old_audio_sink);
818 return TRUE;
819 }
820
821 static PidginPluginUiInfo ui_info = {
822 get_plugin_config_frame,
823 0, /* page_num (Reserved) */
824 /* Padding */
825 NULL,
826 NULL,
827 NULL,
828 NULL
829 };
830
831 static PurplePluginInfo info =
832 {
833 PURPLE_PLUGIN_MAGIC, /**< magic */
834 PURPLE_MAJOR_VERSION, /**< major version */
835 PURPLE_MINOR_VERSION, /**< minor version */
836 PURPLE_PLUGIN_STANDARD, /**< type */
837 PIDGIN_PLUGIN_TYPE, /**< ui_requirement */
838 0, /**< flags */
839 NULL, /**< dependencies */
840 PURPLE_PRIORITY_DEFAULT, /**< priority */
841
842 "gtk-maiku-vvconfig", /**< id */
843 N_("Voice/Video Settings"), /**< name */
844 DISPLAY_VERSION, /**< version */
845 N_("Configure your microphone and webcam."), /**< summary */
846 N_("Configure microphone and webcam "
847 "settings for voice/video calls."), /**< description */
848 "Mike Ruprecht <cmaiku@gmail.com>", /**< author */
849 PURPLE_WEBSITE, /**< homepage */
850
851 plugin_load, /**< load */
852 plugin_unload, /**< unload */
853 NULL, /**< destroy */
854
855 &ui_info, /**< ui_info */
856 NULL, /**< extra_info */
857 NULL, /**< prefs_info */
858 actions, /**< actions */
859
860 /* padding */
861 NULL,
862 NULL,
863 NULL,
864 NULL
865 };
866
867 static void
868 init_plugin(PurplePlugin *plugin) {
869 }
870
871 PURPLE_INIT_PLUGIN(vvconfig, init_plugin, info)
872