pidgin/prefs/pidginvvprefs.c

changeset 41394
1327e58acce3
child 41552
c9157099adf8
equal deleted inserted replaced
41393:f4faf0d6ab26 41394:1327e58acce3
1 /*
2 * Pidgin - Internet Messenger
3 * Copyright (C) Pidgin Developers <devel@pidgin.im>
4 *
5 * Pidgin is the legal property of its developers, whose names are too numerous
6 * to list here. Please refer to the COPYRIGHT file distributed with this
7 * source distribution.
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, see <https://www.gnu.org/licenses/>.
21 */
22
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26
27 #include <math.h>
28
29 #include <glib/gi18n-lib.h>
30
31 #include <purple.h>
32
33 #include <handy.h>
34
35 #include "pidginvvprefs.h"
36 #include "pidgincore.h"
37 #include "pidginprefsinternal.h"
38
39 struct _PidginVVPrefs {
40 HdyPreferencesPage parent;
41
42 struct {
43 PidginPrefCombo input;
44 PidginPrefCombo output;
45 GtkWidget *level;
46 GtkWidget *threshold_label;
47 GtkWidget *threshold;
48 GtkWidget *volume;
49 GtkWidget *test;
50 GstElement *pipeline;
51 } voice;
52
53 struct {
54 PidginPrefCombo input;
55 PidginPrefCombo output;
56 GtkWidget *frame;
57 GtkWidget *sink_widget;
58 GtkWidget *test;
59 GstElement *pipeline;
60 } video;
61 };
62
63 G_DEFINE_TYPE(PidginVVPrefs, pidgin_vv_prefs, HDY_TYPE_PREFERENCES_PAGE)
64
65 /******************************************************************************
66 * Helpers
67 *****************************************************************************/
68 static void
69 populate_vv_device_menuitems(PurpleMediaElementType type, GtkListStore *store)
70 {
71 PurpleMediaManager *manager = NULL;
72 GList *devices;
73
74 gtk_list_store_clear(store);
75
76 manager = purple_media_manager_get();
77 devices = purple_media_manager_enumerate_elements(manager, type);
78 for (; devices; devices = g_list_delete_link(devices, devices)) {
79 PurpleMediaElementInfo *info = devices->data;
80 GtkTreeIter iter;
81 const gchar *name, *id;
82
83 name = purple_media_element_info_get_name(info);
84 id = purple_media_element_info_get_id(info);
85
86 gtk_list_store_append(store, &iter);
87 gtk_list_store_set(store, &iter, PIDGIN_PREF_COMBO_TEXT, name,
88 PIDGIN_PREF_COMBO_VALUE, id, -1);
89
90 g_object_unref(info);
91 }
92 }
93
94 static GstElement *
95 create_test_element(PurpleMediaElementType type)
96 {
97 PurpleMediaElementInfo *element_info;
98
99 element_info = purple_media_manager_get_active_element(purple_media_manager_get(), type);
100
101 g_return_val_if_fail(element_info, NULL);
102
103 return purple_media_element_info_call_create(element_info,
104 NULL, NULL, NULL);
105 }
106
107 static GstElement *
108 create_voice_pipeline(void)
109 {
110 GstElement *pipeline;
111 GstElement *src, *sink;
112 GstElement *volume;
113 GstElement *level;
114 GstElement *valve;
115
116 pipeline = gst_pipeline_new("voicetest");
117
118 src = create_test_element(PURPLE_MEDIA_ELEMENT_AUDIO | PURPLE_MEDIA_ELEMENT_SRC);
119 sink = create_test_element(PURPLE_MEDIA_ELEMENT_AUDIO | PURPLE_MEDIA_ELEMENT_SINK);
120 volume = gst_element_factory_make("volume", "volume");
121 level = gst_element_factory_make("level", "level");
122 valve = gst_element_factory_make("valve", "valve");
123
124 gst_bin_add_many(GST_BIN(pipeline), src, volume, level, valve, sink, NULL);
125 gst_element_link_many(src, volume, level, valve, sink, NULL);
126
127 purple_debug_info("gtkprefs", "create_voice_pipeline: setting pipeline "
128 "state to GST_STATE_PLAYING - it may hang here on win32\n");
129 gst_element_set_state(GST_ELEMENT(pipeline), GST_STATE_PLAYING);
130 purple_debug_info("gtkprefs", "create_voice_pipeline: state is set\n");
131
132 return pipeline;
133 }
134
135 static void
136 on_volume_change_cb(GtkWidget *w, gdouble value, gpointer data)
137 {
138 PidginVVPrefs *prefs = PIDGIN_VV_PREFS(data);
139 GstElement *volume;
140
141 if (!prefs->voice.pipeline) {
142 return;
143 }
144
145 volume = gst_bin_get_by_name(GST_BIN(prefs->voice.pipeline), "volume");
146 g_object_set(volume, "volume",
147 gtk_scale_button_get_value(GTK_SCALE_BUTTON(w)) / 100.0, NULL);
148 }
149
150 static gdouble
151 gst_msg_db_to_percent(GstMessage *msg, gchar *value_name)
152 {
153 const GValue *list;
154 const GValue *value;
155 gdouble value_db;
156 gdouble percent;
157
158 list = gst_structure_get_value(gst_message_get_structure(msg), value_name);
159 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
160 value = g_value_array_get_nth(g_value_get_boxed(list), 0);
161 G_GNUC_END_IGNORE_DEPRECATIONS
162 value_db = g_value_get_double(value);
163 percent = pow(10, value_db / 20);
164 return (percent > 1.0) ? 1.0 : percent;
165 }
166
167 static gboolean
168 gst_bus_cb(GstBus *bus, GstMessage *msg, gpointer data)
169 {
170 PidginVVPrefs *prefs = PIDGIN_VV_PREFS(data);
171
172 if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_ELEMENT &&
173 gst_structure_has_name(gst_message_get_structure(msg), "level")) {
174
175 GstElement *src = GST_ELEMENT(GST_MESSAGE_SRC(msg));
176 gchar *name = gst_element_get_name(src);
177
178 if (purple_strequal(name, "level")) {
179 gdouble percent;
180 gdouble threshold;
181 GstElement *valve;
182
183 percent = gst_msg_db_to_percent(msg, "rms");
184 gtk_progress_bar_set_fraction(
185 GTK_PROGRESS_BAR(prefs->voice.level), percent);
186
187 percent = gst_msg_db_to_percent(msg, "decay");
188 threshold = gtk_range_get_value(GTK_RANGE(
189 prefs->voice.threshold)) /
190 100.0;
191 valve = gst_bin_get_by_name(GST_BIN(GST_ELEMENT_PARENT(src)), "valve");
192 g_object_set(valve, "drop", (percent < threshold), NULL);
193 g_object_set(prefs->voice.level, "text",
194 (percent < threshold) ? _("DROP") : " ",
195 NULL);
196 }
197
198 g_free(name);
199 }
200
201 return TRUE;
202 }
203
204 static void
205 voice_test_destroy_cb(GtkWidget *w, gpointer data)
206 {
207 PidginVVPrefs *prefs = PIDGIN_VV_PREFS(data);
208
209 if (!prefs->voice.pipeline) {
210 return;
211 }
212
213 gst_element_set_state(prefs->voice.pipeline, GST_STATE_NULL);
214 g_clear_pointer(&prefs->voice.pipeline, gst_object_unref);
215 }
216
217 static void
218 enable_voice_test(PidginVVPrefs *prefs)
219 {
220 GstBus *bus;
221
222 prefs->voice.pipeline = create_voice_pipeline();
223 bus = gst_pipeline_get_bus(GST_PIPELINE(prefs->voice.pipeline));
224 gst_bus_add_signal_watch(bus);
225 g_signal_connect(bus, "message", G_CALLBACK(gst_bus_cb), prefs);
226 gst_object_unref(bus);
227 }
228
229 static void
230 toggle_voice_test_cb(GtkToggleButton *test, gpointer data)
231 {
232 PidginVVPrefs *prefs = PIDGIN_VV_PREFS(data);
233
234 if (gtk_toggle_button_get_active(test)) {
235 gtk_widget_set_sensitive(prefs->voice.level, TRUE);
236 enable_voice_test(prefs);
237
238 g_signal_connect(prefs->voice.volume, "value-changed",
239 G_CALLBACK(on_volume_change_cb), prefs);
240 g_signal_connect(test, "destroy",
241 G_CALLBACK(voice_test_destroy_cb), prefs);
242 } else {
243 gtk_progress_bar_set_fraction(
244 GTK_PROGRESS_BAR(prefs->voice.level), 0.0);
245 gtk_widget_set_sensitive(prefs->voice.level, FALSE);
246 g_object_disconnect(prefs->voice.volume,
247 "any-signal::value-changed",
248 G_CALLBACK(on_volume_change_cb), prefs, NULL);
249 g_object_disconnect(test, "any-signal::destroy",
250 G_CALLBACK(voice_test_destroy_cb), prefs,
251 NULL);
252 voice_test_destroy_cb(NULL, prefs);
253 }
254 }
255
256 static void
257 volume_changed_cb(GtkScaleButton *button, gdouble value, gpointer data)
258 {
259 purple_prefs_set_int("/purple/media/audio/volume/input", value * 100);
260 }
261
262 static void
263 threshold_value_changed_cb(GtkScale *scale, gpointer data)
264 {
265 PidginVVPrefs *prefs = data;
266 int value;
267 char *tmp;
268
269 value = (int)gtk_range_get_value(GTK_RANGE(scale));
270 tmp = g_strdup_printf(_("Silence threshold: %d%%"), value);
271 gtk_label_set_label(GTK_LABEL(prefs->voice.threshold_label), tmp);
272 g_free(tmp);
273
274 purple_prefs_set_int("/purple/media/audio/silence_threshold", value);
275 }
276
277 static void
278 bind_voice_test(PidginVVPrefs *prefs)
279 {
280 char *tmp;
281
282 gtk_scale_button_set_value(GTK_SCALE_BUTTON(prefs->voice.volume),
283 purple_prefs_get_int("/purple/media/audio/volume/input") / 100.0);
284
285 tmp = g_strdup_printf(_("Silence threshold: %d%%"),
286 purple_prefs_get_int("/purple/media/audio/silence_threshold"));
287 gtk_label_set_text(GTK_LABEL(prefs->voice.threshold_label), tmp);
288 g_free(tmp);
289
290 gtk_range_set_value(GTK_RANGE(prefs->voice.threshold),
291 purple_prefs_get_int("/purple/media/audio/silence_threshold"));
292 }
293
294 static GstElement *
295 create_video_pipeline(void)
296 {
297 GstElement *pipeline;
298 GstElement *src, *sink;
299 GstElement *videoconvert;
300 GstElement *videoscale;
301
302 pipeline = gst_pipeline_new("videotest");
303 src = create_test_element(PURPLE_MEDIA_ELEMENT_VIDEO | PURPLE_MEDIA_ELEMENT_SRC);
304 sink = create_test_element(PURPLE_MEDIA_ELEMENT_VIDEO | PURPLE_MEDIA_ELEMENT_SINK);
305 videoconvert = gst_element_factory_make("videoconvert", NULL);
306 videoscale = gst_element_factory_make("videoscale", NULL);
307
308 g_object_set_data(G_OBJECT(pipeline), "sink", sink);
309
310 gst_bin_add_many(GST_BIN(pipeline), src, videoconvert, videoscale, sink,
311 NULL);
312 gst_element_link_many(src, videoconvert, videoscale, sink, NULL);
313
314 return pipeline;
315 }
316
317 static void
318 video_test_destroy_cb(GtkWidget *w, gpointer data)
319 {
320 PidginVVPrefs *prefs = PIDGIN_VV_PREFS(data);
321
322 if (!prefs->video.pipeline) {
323 return;
324 }
325
326 gst_element_set_state(prefs->video.pipeline, GST_STATE_NULL);
327 g_clear_pointer(&prefs->video.pipeline, gst_object_unref);
328 }
329
330 static void
331 enable_video_test(PidginVVPrefs *prefs)
332 {
333 GtkWidget *video = NULL;
334 GstElement *sink = NULL;
335
336 prefs->video.pipeline = create_video_pipeline();
337
338 sink = g_object_get_data(G_OBJECT(prefs->video.pipeline), "sink");
339 g_object_get(sink, "widget", &video, NULL);
340 gtk_widget_show(video);
341
342 g_clear_pointer(&prefs->video.sink_widget, gtk_widget_destroy);
343 gtk_widget_set_size_request(prefs->video.frame, 400, 300);
344 gtk_container_add(GTK_CONTAINER(prefs->video.frame), video);
345 prefs->video.sink_widget = video;
346
347 gst_element_set_state(GST_ELEMENT(prefs->video.pipeline),
348 GST_STATE_PLAYING);
349 }
350
351 static void
352 toggle_video_test_cb(GtkToggleButton *test, gpointer data)
353 {
354 PidginVVPrefs *prefs = PIDGIN_VV_PREFS(data);
355
356 if (gtk_toggle_button_get_active(test)) {
357 enable_video_test(prefs);
358 g_signal_connect(test, "destroy",
359 G_CALLBACK(video_test_destroy_cb), prefs);
360 } else {
361 g_object_disconnect(test, "any-signal::destroy",
362 G_CALLBACK(video_test_destroy_cb), prefs,
363 NULL);
364 video_test_destroy_cb(NULL, prefs);
365 }
366 }
367
368 static void
369 vv_device_changed_cb(const gchar *name, PurplePrefType type,
370 gconstpointer value, gpointer data)
371 {
372 PidginVVPrefs *prefs = PIDGIN_VV_PREFS(data);
373
374 PurpleMediaManager *manager;
375 PurpleMediaElementInfo *info;
376
377 manager = purple_media_manager_get();
378 info = purple_media_manager_get_element_info(manager, value);
379 purple_media_manager_set_active_element(manager, info);
380
381 /* Refresh test viewers */
382 if (strstr(name, "audio") && prefs->voice.pipeline) {
383 voice_test_destroy_cb(NULL, prefs);
384 enable_voice_test(prefs);
385 } else if (strstr(name, "video") && prefs->video.pipeline) {
386 video_test_destroy_cb(NULL, prefs);
387 enable_video_test(prefs);
388 }
389 }
390
391 static const char *
392 purple_media_type_to_preference_key(PurpleMediaElementType type)
393 {
394 if (type & PURPLE_MEDIA_ELEMENT_AUDIO) {
395 if (type & PURPLE_MEDIA_ELEMENT_SRC) {
396 return PIDGIN_PREFS_ROOT "/vvconfig/audio/src/device";
397 } else if (type & PURPLE_MEDIA_ELEMENT_SINK) {
398 return PIDGIN_PREFS_ROOT "/vvconfig/audio/sink/device";
399 }
400 } else if (type & PURPLE_MEDIA_ELEMENT_VIDEO) {
401 if (type & PURPLE_MEDIA_ELEMENT_SRC) {
402 return PIDGIN_PREFS_ROOT "/vvconfig/video/src/device";
403 } else if (type & PURPLE_MEDIA_ELEMENT_SINK) {
404 return PIDGIN_PREFS_ROOT "/vvconfig/video/sink/device";
405 }
406 }
407
408 return NULL;
409 }
410
411 static void
412 bind_vv_dropdown(PidginPrefCombo *combo, PurpleMediaElementType element_type)
413 {
414 const gchar *preference_key;
415 GtkTreeModel *model;
416
417 preference_key = purple_media_type_to_preference_key(element_type);
418 model = gtk_combo_box_get_model(GTK_COMBO_BOX(combo->combo));
419 populate_vv_device_menuitems(element_type, GTK_LIST_STORE(model));
420
421 combo->type = PURPLE_PREF_STRING;
422 combo->key = preference_key;
423 pidgin_prefs_bind_dropdown(combo);
424 }
425
426 static void
427 bind_vv_frame(PidginVVPrefs *prefs, PidginPrefCombo *combo,
428 PurpleMediaElementType type)
429 {
430 bind_vv_dropdown(combo, type);
431
432 purple_prefs_connect_callback(combo->combo,
433 purple_media_type_to_preference_key(type),
434 vv_device_changed_cb, prefs);
435 g_signal_connect_swapped(combo->combo, "destroy",
436 G_CALLBACK(purple_prefs_disconnect_by_handle),
437 combo->combo);
438
439 g_object_set_data(G_OBJECT(combo->combo), "vv_media_type",
440 (gpointer)type);
441 g_object_set_data(G_OBJECT(combo->combo), "vv_combo", combo);
442 }
443
444 static void
445 device_list_changed_cb(PurpleMediaManager *manager, GtkWidget *widget)
446 {
447 PidginPrefCombo *combo;
448 PurpleMediaElementType media_type;
449 const gchar *preference_key;
450 guint signal_id;
451 GtkTreeModel *model;
452
453 combo = g_object_get_data(G_OBJECT(widget), "vv_combo");
454 media_type = (PurpleMediaElementType)GPOINTER_TO_INT(g_object_get_data(
455 G_OBJECT(widget),
456 "vv_media_type"));
457 preference_key = purple_media_type_to_preference_key(media_type);
458
459 /* Block signals so pref doesn't get re-saved while changing UI. */
460 signal_id = g_signal_lookup("changed", GTK_TYPE_COMBO_BOX);
461 g_signal_handlers_block_matched(combo->combo, G_SIGNAL_MATCH_ID, signal_id,
462 0, NULL, NULL, NULL);
463
464 model = gtk_combo_box_get_model(GTK_COMBO_BOX(combo->combo));
465 populate_vv_device_menuitems(media_type, GTK_LIST_STORE(model));
466 gtk_combo_box_set_active_id(GTK_COMBO_BOX(combo->combo),
467 purple_prefs_get_string(preference_key));
468
469 g_signal_handlers_unblock_matched(combo->combo, G_SIGNAL_MATCH_ID,
470 signal_id, 0, NULL, NULL, NULL);
471 }
472
473 /******************************************************************************
474 * GObject Implementation
475 *****************************************************************************/
476 static void
477 pidgin_vv_prefs_class_init(PidginVVPrefsClass *klass)
478 {
479 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
480
481 gtk_widget_class_set_template_from_resource(
482 widget_class,
483 "/im/pidgin/Pidgin3/Prefs/vv.ui"
484 );
485
486 gtk_widget_class_bind_template_child(widget_class, PidginVVPrefs,
487 voice.input.combo);
488 gtk_widget_class_bind_template_child(widget_class, PidginVVPrefs,
489 voice.output.combo);
490 gtk_widget_class_bind_template_child(widget_class, PidginVVPrefs,
491 voice.volume);
492 gtk_widget_class_bind_template_child(widget_class, PidginVVPrefs,
493 voice.threshold_label);
494 gtk_widget_class_bind_template_child(widget_class, PidginVVPrefs,
495 voice.threshold);
496 gtk_widget_class_bind_template_child(widget_class, PidginVVPrefs,
497 voice.level);
498 gtk_widget_class_bind_template_child(widget_class, PidginVVPrefs,
499 voice.test);
500 gtk_widget_class_bind_template_callback(widget_class, volume_changed_cb);
501 gtk_widget_class_bind_template_callback(widget_class,
502 threshold_value_changed_cb);
503 gtk_widget_class_bind_template_callback(widget_class,
504 toggle_voice_test_cb);
505
506 gtk_widget_class_bind_template_child(widget_class, PidginVVPrefs,
507 video.input.combo);
508 gtk_widget_class_bind_template_child(widget_class, PidginVVPrefs,
509 video.output.combo);
510 gtk_widget_class_bind_template_child(widget_class, PidginVVPrefs,
511 video.frame);
512 gtk_widget_class_bind_template_child(widget_class, PidginVVPrefs,
513 video.test);
514 gtk_widget_class_bind_template_callback(widget_class,
515 toggle_video_test_cb);
516 }
517
518 static void
519 pidgin_vv_prefs_init(PidginVVPrefs *prefs)
520 {
521 PurpleMediaManager *manager = NULL;
522
523 gtk_widget_init_template(GTK_WIDGET(prefs));
524
525 manager = purple_media_manager_get();
526
527 bind_vv_frame(prefs, &prefs->voice.input,
528 PURPLE_MEDIA_ELEMENT_AUDIO | PURPLE_MEDIA_ELEMENT_SRC);
529 g_signal_connect_object(manager, "elements-changed::audiosrc",
530 G_CALLBACK(device_list_changed_cb),
531 prefs->voice.input.combo, 0);
532
533 bind_vv_frame(prefs, &prefs->voice.output,
534 PURPLE_MEDIA_ELEMENT_AUDIO | PURPLE_MEDIA_ELEMENT_SINK);
535 g_signal_connect_object(manager, "elements-changed::audiosink",
536 G_CALLBACK(device_list_changed_cb),
537 prefs->voice.output.combo, 0);
538
539 bind_voice_test(prefs);
540
541 bind_vv_frame(prefs, &prefs->video.input,
542 PURPLE_MEDIA_ELEMENT_VIDEO | PURPLE_MEDIA_ELEMENT_SRC);
543 g_signal_connect_object(manager, "elements-changed::videosrc",
544 G_CALLBACK(device_list_changed_cb),
545 prefs->video.input.combo, 0);
546
547 bind_vv_frame(prefs, &prefs->video.output,
548 PURPLE_MEDIA_ELEMENT_VIDEO | PURPLE_MEDIA_ELEMENT_SINK);
549 g_signal_connect_object(manager, "elements-changed::videosink",
550 G_CALLBACK(device_list_changed_cb),
551 prefs->video.output.combo, 0);
552 }
553
554 /******************************************************************************
555 * API
556 *****************************************************************************/
557 GtkWidget *
558 pidgin_vv_prefs_new(void) {
559 return GTK_WIDGET(g_object_new(PIDGIN_TYPE_VV_PREFS, NULL));
560 }
561
562 void
563 pidgin_vv_prefs_disable_test_pipelines(PidginVVPrefs *prefs) {
564 g_return_if_fail(PIDGIN_IS_VV_PREFS(prefs));
565
566 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(prefs->voice.test), FALSE);
567 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(prefs->video.test), FALSE);
568 }

mercurial