Fri, 20 May 2022 02:24:05 -0500
Split VV prefs into a separate widget
This is not a strict port of the original code as that used `GtkBuilder`, while this is now its own widget.
Additionally, moving to `HdyPreferencesGroup` means the Audio/Video sections are now vertically boxed, but that seems better as it was fairly wide before.
Testing Done:
Opened Prefs, changed VV ones a bit to make sure it didn't break. Enabled test pipelines, then switch stacks to make sure they auto-disabled. Unplugged/plugged in a mic to check that the device list re-population did not lose the configured device.
Reviewed at https://reviews.imfreedom.org/r/1459/
--- a/pidgin/meson.build Fri May 20 01:37:47 2022 -0500 +++ b/pidgin/meson.build Fri May 20 02:24:05 2022 -0500 @@ -67,6 +67,11 @@ 'prefs/pidginnetworkprefs.c', 'prefs/pidginproxyprefs.c', ] +if enable_vv + libpidgin_SOURCES += [ + 'prefs/pidginvvprefs.c', + ] +endif libpidgin_headers = [ 'gtkaccount.h', @@ -139,6 +144,11 @@ 'prefs/pidginnetworkprefs.h', 'prefs/pidginproxyprefs.h', ] +if enable_vv + libpidgin_prefs_headers += [ + 'prefs/pidginvvprefs.h', + ] +endif libpidgin_enum_headers = [ 'gtkaccount.h',
--- a/pidgin/prefs/pidginprefs.c Fri May 20 01:37:47 2022 -0500 +++ b/pidgin/prefs/pidginprefs.c Fri May 20 02:24:05 2022 -0500 @@ -42,6 +42,9 @@ #include "pidgindebug.h" #include "pidginprefs.h" #include "pidginprefsinternal.h" +#ifdef USE_VV +#include "pidginvvprefs.h" +#endif #include <libsoup/soup.h> #define PREFS_OPTIMAL_ICON_SIZE 32 @@ -54,30 +57,6 @@ /* Stack */ GtkWidget *stack; - -#ifdef USE_VV - /* Voice/Video page */ - struct { - struct { - PidginPrefCombo input; - PidginPrefCombo output; - GtkWidget *level; - GtkWidget *threshold; - GtkWidget *volume; - GtkWidget *test; - GstElement *pipeline; - } voice; - - struct { - PidginPrefCombo input; - PidginPrefCombo output; - GtkWidget *frame; - GtkWidget *sink_widget; - GtkWidget *test; - GstElement *pipeline; - } video; - } vv; -#endif }; /* Main dialog */ @@ -494,530 +473,19 @@ #ifdef USE_VV static void -populate_vv_device_menuitems(PurpleMediaElementType type, GtkListStore *store) -{ - PurpleMediaManager *manager = NULL; - GList *devices; - - gtk_list_store_clear(store); - - manager = purple_media_manager_get(); - devices = purple_media_manager_enumerate_elements(manager, type); - for (; devices; devices = g_list_delete_link(devices, devices)) { - PurpleMediaElementInfo *info = devices->data; - GtkTreeIter iter; - const gchar *name, *id; - - name = purple_media_element_info_get_name(info); - id = purple_media_element_info_get_id(info); - - gtk_list_store_append(store, &iter); - gtk_list_store_set(store, &iter, PIDGIN_PREF_COMBO_TEXT, name, - PIDGIN_PREF_COMBO_VALUE, id, -1); - - g_object_unref(info); - } -} - -static GstElement * -create_test_element(PurpleMediaElementType type) -{ - PurpleMediaElementInfo *element_info; - - element_info = purple_media_manager_get_active_element(purple_media_manager_get(), type); - - g_return_val_if_fail(element_info, NULL); - - return purple_media_element_info_call_create(element_info, - NULL, NULL, NULL); -} - -static void vv_test_switch_page_cb(GtkStack *stack, G_GNUC_UNUSED GParamSpec *pspec, gpointer data) { - PidginPrefsWindow *win = data; + PidginVVPrefs *vv_prefs = data; if (!g_str_equal(gtk_stack_get_visible_child_name(stack), "vv")) { /* Disable any running test pipelines. */ - gtk_toggle_button_set_active( - GTK_TOGGLE_BUTTON(win->vv.voice.test), FALSE); - gtk_toggle_button_set_active( - GTK_TOGGLE_BUTTON(win->vv.video.test), FALSE); - } -} - -static GstElement * -create_voice_pipeline(void) -{ - GstElement *pipeline; - GstElement *src, *sink; - GstElement *volume; - GstElement *level; - GstElement *valve; - - pipeline = gst_pipeline_new("voicetest"); - - src = create_test_element(PURPLE_MEDIA_ELEMENT_AUDIO | PURPLE_MEDIA_ELEMENT_SRC); - sink = create_test_element(PURPLE_MEDIA_ELEMENT_AUDIO | PURPLE_MEDIA_ELEMENT_SINK); - volume = gst_element_factory_make("volume", "volume"); - level = gst_element_factory_make("level", "level"); - valve = gst_element_factory_make("valve", "valve"); - - gst_bin_add_many(GST_BIN(pipeline), src, volume, level, valve, sink, NULL); - gst_element_link_many(src, volume, level, valve, sink, NULL); - - purple_debug_info("gtkprefs", "create_voice_pipeline: setting pipeline " - "state to GST_STATE_PLAYING - it may hang here on win32\n"); - gst_element_set_state(GST_ELEMENT(pipeline), GST_STATE_PLAYING); - purple_debug_info("gtkprefs", "create_voice_pipeline: state is set\n"); - - return pipeline; -} - -static void -on_volume_change_cb(GtkWidget *w, gdouble value, gpointer data) -{ - PidginPrefsWindow *win = PIDGIN_PREFS_WINDOW(data); - GstElement *volume; - - if (!win->vv.voice.pipeline) { - return; - } - - volume = gst_bin_get_by_name(GST_BIN(win->vv.voice.pipeline), "volume"); - g_object_set(volume, "volume", - gtk_scale_button_get_value(GTK_SCALE_BUTTON(w)) / 100.0, NULL); -} - -static gdouble -gst_msg_db_to_percent(GstMessage *msg, gchar *value_name) -{ - const GValue *list; - const GValue *value; - gdouble value_db; - gdouble percent; - - list = gst_structure_get_value(gst_message_get_structure(msg), value_name); -G_GNUC_BEGIN_IGNORE_DEPRECATIONS - value = g_value_array_get_nth(g_value_get_boxed(list), 0); -G_GNUC_END_IGNORE_DEPRECATIONS - value_db = g_value_get_double(value); - percent = pow(10, value_db / 20); - return (percent > 1.0) ? 1.0 : percent; -} - -static gboolean -gst_bus_cb(GstBus *bus, GstMessage *msg, gpointer data) -{ - PidginPrefsWindow *win = PIDGIN_PREFS_WINDOW(data); - - if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_ELEMENT && - gst_structure_has_name(gst_message_get_structure(msg), "level")) { - - GstElement *src = GST_ELEMENT(GST_MESSAGE_SRC(msg)); - gchar *name = gst_element_get_name(src); - - if (purple_strequal(name, "level")) { - gdouble percent; - gdouble threshold; - GstElement *valve; - - percent = gst_msg_db_to_percent(msg, "rms"); - gtk_progress_bar_set_fraction( - GTK_PROGRESS_BAR(win->vv.voice.level), percent); - - percent = gst_msg_db_to_percent(msg, "decay"); - threshold = gtk_range_get_value(GTK_RANGE( - win->vv.voice.threshold)) / - 100.0; - valve = gst_bin_get_by_name(GST_BIN(GST_ELEMENT_PARENT(src)), "valve"); - g_object_set(valve, "drop", (percent < threshold), NULL); - g_object_set(win->vv.voice.level, "text", - (percent < threshold) ? _("DROP") : " ", - NULL); - } - - g_free(name); - } - - return TRUE; -} - -static void -voice_test_destroy_cb(GtkWidget *w, gpointer data) -{ - PidginPrefsWindow *win = PIDGIN_PREFS_WINDOW(data); - - if (!win->vv.voice.pipeline) { - return; - } - - gst_element_set_state(win->vv.voice.pipeline, GST_STATE_NULL); - g_clear_pointer(&win->vv.voice.pipeline, gst_object_unref); -} - -static void -enable_voice_test(PidginPrefsWindow *win) -{ - GstBus *bus; - - win->vv.voice.pipeline = create_voice_pipeline(); - bus = gst_pipeline_get_bus(GST_PIPELINE(win->vv.voice.pipeline)); - gst_bus_add_signal_watch(bus); - g_signal_connect(bus, "message", G_CALLBACK(gst_bus_cb), win); - gst_object_unref(bus); -} - -static void -toggle_voice_test_cb(GtkToggleButton *test, gpointer data) -{ - PidginPrefsWindow *win = PIDGIN_PREFS_WINDOW(data); - - if (gtk_toggle_button_get_active(test)) { - gtk_widget_set_sensitive(win->vv.voice.level, TRUE); - enable_voice_test(win); - - g_signal_connect(win->vv.voice.volume, "value-changed", - G_CALLBACK(on_volume_change_cb), win); - g_signal_connect(test, "destroy", - G_CALLBACK(voice_test_destroy_cb), win); - } else { - gtk_progress_bar_set_fraction( - GTK_PROGRESS_BAR(win->vv.voice.level), 0.0); - gtk_widget_set_sensitive(win->vv.voice.level, FALSE); - g_object_disconnect(win->vv.voice.volume, - "any-signal::value-changed", - G_CALLBACK(on_volume_change_cb), win, NULL); - g_object_disconnect(test, "any-signal::destroy", - G_CALLBACK(voice_test_destroy_cb), win, - NULL); - voice_test_destroy_cb(NULL, win); + pidgin_vv_prefs_disable_test_pipelines(vv_prefs); } } - -static void -volume_changed_cb(GtkScaleButton *button, gdouble value, gpointer data) -{ - purple_prefs_set_int("/purple/media/audio/volume/input", value * 100); -} - -static void -threshold_value_changed_cb(GtkScale *scale, GtkWidget *label) -{ - int value; - char *tmp; - - value = (int)gtk_range_get_value(GTK_RANGE(scale)); - tmp = g_strdup_printf(_("Silence threshold: %d%%"), value); - gtk_label_set_label(GTK_LABEL(label), tmp); - g_free(tmp); - - purple_prefs_set_int("/purple/media/audio/silence_threshold", value); -} - -static void -bind_voice_test(PidginPrefsWindow *win, GtkBuilder *builder) -{ - GObject *test; - GObject *label; - GObject *volume; - GObject *threshold; - char *tmp; - - volume = gtk_builder_get_object(builder, "vv.voice.volume"); - win->vv.voice.volume = GTK_WIDGET(volume); - gtk_scale_button_set_value(GTK_SCALE_BUTTON(volume), - purple_prefs_get_int("/purple/media/audio/volume/input") / 100.0); - g_signal_connect(volume, "value-changed", - G_CALLBACK(volume_changed_cb), NULL); - - label = gtk_builder_get_object(builder, "vv.voice.threshold_label"); - tmp = g_strdup_printf(_("Silence threshold: %d%%"), - purple_prefs_get_int("/purple/media/audio/silence_threshold")); - gtk_label_set_text(GTK_LABEL(label), tmp); - g_free(tmp); - - threshold = gtk_builder_get_object(builder, "vv.voice.threshold"); - win->vv.voice.threshold = GTK_WIDGET(threshold); - gtk_range_set_value(GTK_RANGE(threshold), - purple_prefs_get_int("/purple/media/audio/silence_threshold")); - g_signal_connect(threshold, "value-changed", - G_CALLBACK(threshold_value_changed_cb), label); - - win->vv.voice.level = - GTK_WIDGET(gtk_builder_get_object(builder, "vv.voice.level")); - - test = gtk_builder_get_object(builder, "vv.voice.test"); - g_signal_connect(test, "toggled", - G_CALLBACK(toggle_voice_test_cb), win); - win->vv.voice.test = GTK_WIDGET(test); -} - -static GstElement * -create_video_pipeline(void) -{ - GstElement *pipeline; - GstElement *src, *sink; - GstElement *videoconvert; - GstElement *videoscale; - - pipeline = gst_pipeline_new("videotest"); - src = create_test_element(PURPLE_MEDIA_ELEMENT_VIDEO | PURPLE_MEDIA_ELEMENT_SRC); - sink = create_test_element(PURPLE_MEDIA_ELEMENT_VIDEO | PURPLE_MEDIA_ELEMENT_SINK); - videoconvert = gst_element_factory_make("videoconvert", NULL); - videoscale = gst_element_factory_make("videoscale", NULL); - - g_object_set_data(G_OBJECT(pipeline), "sink", sink); - - gst_bin_add_many(GST_BIN(pipeline), src, videoconvert, videoscale, sink, - NULL); - gst_element_link_many(src, videoconvert, videoscale, sink, NULL); - - return pipeline; -} - -static void -video_test_destroy_cb(GtkWidget *w, gpointer data) -{ - PidginPrefsWindow *win = PIDGIN_PREFS_WINDOW(data); - - if (!win->vv.video.pipeline) { - return; - } - - gst_element_set_state(win->vv.video.pipeline, GST_STATE_NULL); - g_clear_pointer(&win->vv.video.pipeline, gst_object_unref); -} - -static void -enable_video_test(PidginPrefsWindow *win) -{ - GtkWidget *video = NULL; - GstElement *sink = NULL; - - win->vv.video.pipeline = create_video_pipeline(); - - sink = g_object_get_data(G_OBJECT(win->vv.video.pipeline), "sink"); - g_object_get(sink, "widget", &video, NULL); - gtk_widget_show(video); - - g_clear_pointer(&win->vv.video.sink_widget, gtk_widget_destroy); - gtk_container_add(GTK_CONTAINER(win->vv.video.frame), video); - win->vv.video.sink_widget = video; - - gst_element_set_state(GST_ELEMENT(win->vv.video.pipeline), - GST_STATE_PLAYING); -} - -static void -toggle_video_test_cb(GtkToggleButton *test, gpointer data) -{ - PidginPrefsWindow *win = PIDGIN_PREFS_WINDOW(data); - - if (gtk_toggle_button_get_active(test)) { - enable_video_test(win); - g_signal_connect(test, "destroy", - G_CALLBACK(video_test_destroy_cb), win); - } else { - g_object_disconnect(test, "any-signal::destroy", - G_CALLBACK(video_test_destroy_cb), win, - NULL); - video_test_destroy_cb(NULL, win); - } -} - -static void -bind_video_test(PidginPrefsWindow *win, GtkBuilder *builder) -{ - GObject *test; - - win->vv.video.frame = GTK_WIDGET( - gtk_builder_get_object(builder, "vv.video.frame")); - test = gtk_builder_get_object(builder, "vv.video.test"); - g_signal_connect(test, "toggled", - G_CALLBACK(toggle_video_test_cb), win); - win->vv.video.test = GTK_WIDGET(test); -} - -static void -vv_device_changed_cb(const gchar *name, PurplePrefType type, - gconstpointer value, gpointer data) -{ - PidginPrefsWindow *win = PIDGIN_PREFS_WINDOW(data); - - PurpleMediaManager *manager; - PurpleMediaElementInfo *info; - - manager = purple_media_manager_get(); - info = purple_media_manager_get_element_info(manager, value); - purple_media_manager_set_active_element(manager, info); - - /* Refresh test viewers */ - if (strstr(name, "audio") && win->vv.voice.pipeline) { - voice_test_destroy_cb(NULL, win); - enable_voice_test(win); - } else if (strstr(name, "video") && win->vv.video.pipeline) { - video_test_destroy_cb(NULL, win); - enable_video_test(win); - } -} - -static const char * -purple_media_type_to_preference_key(PurpleMediaElementType type) -{ - if (type & PURPLE_MEDIA_ELEMENT_AUDIO) { - if (type & PURPLE_MEDIA_ELEMENT_SRC) { - return PIDGIN_PREFS_ROOT "/vvconfig/audio/src/device"; - } else if (type & PURPLE_MEDIA_ELEMENT_SINK) { - return PIDGIN_PREFS_ROOT "/vvconfig/audio/sink/device"; - } - } else if (type & PURPLE_MEDIA_ELEMENT_VIDEO) { - if (type & PURPLE_MEDIA_ELEMENT_SRC) { - return PIDGIN_PREFS_ROOT "/vvconfig/video/src/device"; - } else if (type & PURPLE_MEDIA_ELEMENT_SINK) { - return PIDGIN_PREFS_ROOT "/vvconfig/video/sink/device"; - } - } - - return NULL; -} - -static void -bind_vv_dropdown(PidginPrefCombo *combo, PurpleMediaElementType element_type) -{ - const gchar *preference_key; - GtkTreeModel *model; - - preference_key = purple_media_type_to_preference_key(element_type); - model = gtk_combo_box_get_model(GTK_COMBO_BOX(combo->combo)); - populate_vv_device_menuitems(element_type, GTK_LIST_STORE(model)); - - combo->type = PURPLE_PREF_STRING; - combo->key = preference_key; - pidgin_prefs_bind_dropdown(combo); -} - -static void -bind_vv_frame(PidginPrefsWindow *win, PidginPrefCombo *combo, - PurpleMediaElementType type) -{ - bind_vv_dropdown(combo, type); - - purple_prefs_connect_callback(combo->combo, - purple_media_type_to_preference_key(type), - vv_device_changed_cb, win); - g_signal_connect_swapped(combo->combo, "destroy", - G_CALLBACK(purple_prefs_disconnect_by_handle), - combo->combo); - - g_object_set_data(G_OBJECT(combo->combo), "vv_media_type", - (gpointer)type); - g_object_set_data(G_OBJECT(combo->combo), "vv_combo", combo); -} - -static void -device_list_changed_cb(PurpleMediaManager *manager, GtkWidget *widget) -{ - PidginPrefCombo *combo; - PurpleMediaElementType media_type; - guint signal_id; - GtkTreeModel *model; - - combo = g_object_get_data(G_OBJECT(widget), "vv_combo"); - media_type = (PurpleMediaElementType)g_object_get_data(G_OBJECT(widget), - "vv_media_type"); - - /* Block signals so pref doesn't get re-saved while changing UI. */ - signal_id = g_signal_lookup("changed", GTK_TYPE_COMBO_BOX); - g_signal_handlers_block_matched(combo->combo, G_SIGNAL_MATCH_ID, signal_id, - 0, NULL, NULL, NULL); - - model = gtk_combo_box_get_model(GTK_COMBO_BOX(combo->combo)); - populate_vv_device_menuitems(media_type, GTK_LIST_STORE(model)); - - g_signal_handlers_unblock_matched(combo->combo, G_SIGNAL_MATCH_ID, - signal_id, 0, NULL, NULL, NULL); -} - -static GtkWidget * -vv_page(PidginPrefsWindow *win) -{ - GtkBuilder *builder; - GtkWidget *ret; - PurpleMediaManager *manager; - - builder = gtk_builder_new_from_resource("/im/pidgin/Pidgin3/Prefs/vv.ui"); - gtk_builder_set_translation_domain(builder, PACKAGE); - - ret = GTK_WIDGET(gtk_builder_get_object(builder, "vv.page")); - - manager = purple_media_manager_get(); - - win->vv.voice.input.combo = GTK_WIDGET( - gtk_builder_get_object(builder, "vv.voice.input.combo")); - bind_vv_frame(win, &win->vv.voice.input, - PURPLE_MEDIA_ELEMENT_AUDIO | PURPLE_MEDIA_ELEMENT_SRC); - g_signal_connect_object(manager, "elements-changed::audiosrc", - G_CALLBACK(device_list_changed_cb), - win->vv.voice.input.combo, 0); - - win->vv.voice.output.combo = GTK_WIDGET( - gtk_builder_get_object(builder, "vv.voice.output.combo")); - bind_vv_frame(win, &win->vv.voice.output, - PURPLE_MEDIA_ELEMENT_AUDIO | PURPLE_MEDIA_ELEMENT_SINK); - g_signal_connect_object(manager, "elements-changed::audiosink", - G_CALLBACK(device_list_changed_cb), - win->vv.voice.output.combo, 0); - - bind_voice_test(win, builder); - - win->vv.video.input.combo = GTK_WIDGET( - gtk_builder_get_object(builder, "vv.video.input.combo")); - bind_vv_frame(win, &win->vv.video.input, - PURPLE_MEDIA_ELEMENT_VIDEO | PURPLE_MEDIA_ELEMENT_SRC); - g_signal_connect_object(manager, "elements-changed::videosrc", - G_CALLBACK(device_list_changed_cb), - win->vv.video.input.combo, 0); - - win->vv.video.output.combo = GTK_WIDGET( - gtk_builder_get_object(builder, "vv.video.output.combo")); - bind_vv_frame(win, &win->vv.video.output, - PURPLE_MEDIA_ELEMENT_VIDEO | PURPLE_MEDIA_ELEMENT_SINK); - g_signal_connect_object(manager, "elements-changed::videosink", - G_CALLBACK(device_list_changed_cb), - win->vv.video.output.combo, 0); - - bind_video_test(win, builder); - - g_signal_connect(win->stack, "notify::visible-child", - G_CALLBACK(vv_test_switch_page_cb), win); - - g_object_ref(ret); - g_object_unref(builder); - - return ret; -} #endif static void -prefs_stack_init(PidginPrefsWindow *win) -{ -#ifdef USE_VV - GtkStack *stack = GTK_STACK(win->stack); - GtkWidget *vv; -#endif - -#ifdef USE_VV - vv = vv_page(win); - gtk_container_add_with_properties(GTK_CONTAINER(stack), vv, "name", - "vv", "title", _("Voice/Video"), - NULL); - g_object_unref(vv); -#endif -} - -static void pidgin_prefs_window_class_init(PidginPrefsWindowClass *klass) { GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass); @@ -1036,6 +504,9 @@ static void pidgin_prefs_window_init(PidginPrefsWindow *win) { +#ifdef USE_VV + GtkWidget *vv = NULL; +#endif /* copy the preferences to tmp values... * I liked "take affect immediately" Oh well :-( */ /* (that should have been "effect," right?) */ @@ -1045,7 +516,12 @@ /* Create the window */ gtk_widget_init_template(GTK_WIDGET(win)); - prefs_stack_init(win); +#ifdef USE_VV + vv = pidgin_vv_prefs_new(); + gtk_stack_add_titled(GTK_STACK(win->stack), vv, "vv", _("Voice/Video")); + g_signal_connect(win->stack, "notify::visible-child", + G_CALLBACK(vv_test_switch_page_cb), vv); +#endif } void
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/prefs/pidginvvprefs.c Fri May 20 02:24:05 2022 -0500 @@ -0,0 +1,568 @@ +/* + * Pidgin - Internet Messenger + * Copyright (C) Pidgin Developers <devel@pidgin.im> + * + * Pidgin is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <math.h> + +#include <glib/gi18n-lib.h> + +#include <purple.h> + +#include <handy.h> + +#include "pidginvvprefs.h" +#include "pidgincore.h" +#include "pidginprefsinternal.h" + +struct _PidginVVPrefs { + HdyPreferencesPage parent; + + struct { + PidginPrefCombo input; + PidginPrefCombo output; + GtkWidget *level; + GtkWidget *threshold_label; + GtkWidget *threshold; + GtkWidget *volume; + GtkWidget *test; + GstElement *pipeline; + } voice; + + struct { + PidginPrefCombo input; + PidginPrefCombo output; + GtkWidget *frame; + GtkWidget *sink_widget; + GtkWidget *test; + GstElement *pipeline; + } video; +}; + +G_DEFINE_TYPE(PidginVVPrefs, pidgin_vv_prefs, HDY_TYPE_PREFERENCES_PAGE) + +/****************************************************************************** + * Helpers + *****************************************************************************/ +static void +populate_vv_device_menuitems(PurpleMediaElementType type, GtkListStore *store) +{ + PurpleMediaManager *manager = NULL; + GList *devices; + + gtk_list_store_clear(store); + + manager = purple_media_manager_get(); + devices = purple_media_manager_enumerate_elements(manager, type); + for (; devices; devices = g_list_delete_link(devices, devices)) { + PurpleMediaElementInfo *info = devices->data; + GtkTreeIter iter; + const gchar *name, *id; + + name = purple_media_element_info_get_name(info); + id = purple_media_element_info_get_id(info); + + gtk_list_store_append(store, &iter); + gtk_list_store_set(store, &iter, PIDGIN_PREF_COMBO_TEXT, name, + PIDGIN_PREF_COMBO_VALUE, id, -1); + + g_object_unref(info); + } +} + +static GstElement * +create_test_element(PurpleMediaElementType type) +{ + PurpleMediaElementInfo *element_info; + + element_info = purple_media_manager_get_active_element(purple_media_manager_get(), type); + + g_return_val_if_fail(element_info, NULL); + + return purple_media_element_info_call_create(element_info, + NULL, NULL, NULL); +} + +static GstElement * +create_voice_pipeline(void) +{ + GstElement *pipeline; + GstElement *src, *sink; + GstElement *volume; + GstElement *level; + GstElement *valve; + + pipeline = gst_pipeline_new("voicetest"); + + src = create_test_element(PURPLE_MEDIA_ELEMENT_AUDIO | PURPLE_MEDIA_ELEMENT_SRC); + sink = create_test_element(PURPLE_MEDIA_ELEMENT_AUDIO | PURPLE_MEDIA_ELEMENT_SINK); + volume = gst_element_factory_make("volume", "volume"); + level = gst_element_factory_make("level", "level"); + valve = gst_element_factory_make("valve", "valve"); + + gst_bin_add_many(GST_BIN(pipeline), src, volume, level, valve, sink, NULL); + gst_element_link_many(src, volume, level, valve, sink, NULL); + + purple_debug_info("gtkprefs", "create_voice_pipeline: setting pipeline " + "state to GST_STATE_PLAYING - it may hang here on win32\n"); + gst_element_set_state(GST_ELEMENT(pipeline), GST_STATE_PLAYING); + purple_debug_info("gtkprefs", "create_voice_pipeline: state is set\n"); + + return pipeline; +} + +static void +on_volume_change_cb(GtkWidget *w, gdouble value, gpointer data) +{ + PidginVVPrefs *prefs = PIDGIN_VV_PREFS(data); + GstElement *volume; + + if (!prefs->voice.pipeline) { + return; + } + + volume = gst_bin_get_by_name(GST_BIN(prefs->voice.pipeline), "volume"); + g_object_set(volume, "volume", + gtk_scale_button_get_value(GTK_SCALE_BUTTON(w)) / 100.0, NULL); +} + +static gdouble +gst_msg_db_to_percent(GstMessage *msg, gchar *value_name) +{ + const GValue *list; + const GValue *value; + gdouble value_db; + gdouble percent; + + list = gst_structure_get_value(gst_message_get_structure(msg), value_name); +G_GNUC_BEGIN_IGNORE_DEPRECATIONS + value = g_value_array_get_nth(g_value_get_boxed(list), 0); +G_GNUC_END_IGNORE_DEPRECATIONS + value_db = g_value_get_double(value); + percent = pow(10, value_db / 20); + return (percent > 1.0) ? 1.0 : percent; +} + +static gboolean +gst_bus_cb(GstBus *bus, GstMessage *msg, gpointer data) +{ + PidginVVPrefs *prefs = PIDGIN_VV_PREFS(data); + + if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_ELEMENT && + gst_structure_has_name(gst_message_get_structure(msg), "level")) { + + GstElement *src = GST_ELEMENT(GST_MESSAGE_SRC(msg)); + gchar *name = gst_element_get_name(src); + + if (purple_strequal(name, "level")) { + gdouble percent; + gdouble threshold; + GstElement *valve; + + percent = gst_msg_db_to_percent(msg, "rms"); + gtk_progress_bar_set_fraction( + GTK_PROGRESS_BAR(prefs->voice.level), percent); + + percent = gst_msg_db_to_percent(msg, "decay"); + threshold = gtk_range_get_value(GTK_RANGE( + prefs->voice.threshold)) / + 100.0; + valve = gst_bin_get_by_name(GST_BIN(GST_ELEMENT_PARENT(src)), "valve"); + g_object_set(valve, "drop", (percent < threshold), NULL); + g_object_set(prefs->voice.level, "text", + (percent < threshold) ? _("DROP") : " ", + NULL); + } + + g_free(name); + } + + return TRUE; +} + +static void +voice_test_destroy_cb(GtkWidget *w, gpointer data) +{ + PidginVVPrefs *prefs = PIDGIN_VV_PREFS(data); + + if (!prefs->voice.pipeline) { + return; + } + + gst_element_set_state(prefs->voice.pipeline, GST_STATE_NULL); + g_clear_pointer(&prefs->voice.pipeline, gst_object_unref); +} + +static void +enable_voice_test(PidginVVPrefs *prefs) +{ + GstBus *bus; + + prefs->voice.pipeline = create_voice_pipeline(); + bus = gst_pipeline_get_bus(GST_PIPELINE(prefs->voice.pipeline)); + gst_bus_add_signal_watch(bus); + g_signal_connect(bus, "message", G_CALLBACK(gst_bus_cb), prefs); + gst_object_unref(bus); +} + +static void +toggle_voice_test_cb(GtkToggleButton *test, gpointer data) +{ + PidginVVPrefs *prefs = PIDGIN_VV_PREFS(data); + + if (gtk_toggle_button_get_active(test)) { + gtk_widget_set_sensitive(prefs->voice.level, TRUE); + enable_voice_test(prefs); + + g_signal_connect(prefs->voice.volume, "value-changed", + G_CALLBACK(on_volume_change_cb), prefs); + g_signal_connect(test, "destroy", + G_CALLBACK(voice_test_destroy_cb), prefs); + } else { + gtk_progress_bar_set_fraction( + GTK_PROGRESS_BAR(prefs->voice.level), 0.0); + gtk_widget_set_sensitive(prefs->voice.level, FALSE); + g_object_disconnect(prefs->voice.volume, + "any-signal::value-changed", + G_CALLBACK(on_volume_change_cb), prefs, NULL); + g_object_disconnect(test, "any-signal::destroy", + G_CALLBACK(voice_test_destroy_cb), prefs, + NULL); + voice_test_destroy_cb(NULL, prefs); + } +} + +static void +volume_changed_cb(GtkScaleButton *button, gdouble value, gpointer data) +{ + purple_prefs_set_int("/purple/media/audio/volume/input", value * 100); +} + +static void +threshold_value_changed_cb(GtkScale *scale, gpointer data) +{ + PidginVVPrefs *prefs = data; + int value; + char *tmp; + + value = (int)gtk_range_get_value(GTK_RANGE(scale)); + tmp = g_strdup_printf(_("Silence threshold: %d%%"), value); + gtk_label_set_label(GTK_LABEL(prefs->voice.threshold_label), tmp); + g_free(tmp); + + purple_prefs_set_int("/purple/media/audio/silence_threshold", value); +} + +static void +bind_voice_test(PidginVVPrefs *prefs) +{ + char *tmp; + + gtk_scale_button_set_value(GTK_SCALE_BUTTON(prefs->voice.volume), + purple_prefs_get_int("/purple/media/audio/volume/input") / 100.0); + + tmp = g_strdup_printf(_("Silence threshold: %d%%"), + purple_prefs_get_int("/purple/media/audio/silence_threshold")); + gtk_label_set_text(GTK_LABEL(prefs->voice.threshold_label), tmp); + g_free(tmp); + + gtk_range_set_value(GTK_RANGE(prefs->voice.threshold), + purple_prefs_get_int("/purple/media/audio/silence_threshold")); +} + +static GstElement * +create_video_pipeline(void) +{ + GstElement *pipeline; + GstElement *src, *sink; + GstElement *videoconvert; + GstElement *videoscale; + + pipeline = gst_pipeline_new("videotest"); + src = create_test_element(PURPLE_MEDIA_ELEMENT_VIDEO | PURPLE_MEDIA_ELEMENT_SRC); + sink = create_test_element(PURPLE_MEDIA_ELEMENT_VIDEO | PURPLE_MEDIA_ELEMENT_SINK); + videoconvert = gst_element_factory_make("videoconvert", NULL); + videoscale = gst_element_factory_make("videoscale", NULL); + + g_object_set_data(G_OBJECT(pipeline), "sink", sink); + + gst_bin_add_many(GST_BIN(pipeline), src, videoconvert, videoscale, sink, + NULL); + gst_element_link_many(src, videoconvert, videoscale, sink, NULL); + + return pipeline; +} + +static void +video_test_destroy_cb(GtkWidget *w, gpointer data) +{ + PidginVVPrefs *prefs = PIDGIN_VV_PREFS(data); + + if (!prefs->video.pipeline) { + return; + } + + gst_element_set_state(prefs->video.pipeline, GST_STATE_NULL); + g_clear_pointer(&prefs->video.pipeline, gst_object_unref); +} + +static void +enable_video_test(PidginVVPrefs *prefs) +{ + GtkWidget *video = NULL; + GstElement *sink = NULL; + + prefs->video.pipeline = create_video_pipeline(); + + sink = g_object_get_data(G_OBJECT(prefs->video.pipeline), "sink"); + g_object_get(sink, "widget", &video, NULL); + gtk_widget_show(video); + + g_clear_pointer(&prefs->video.sink_widget, gtk_widget_destroy); + gtk_widget_set_size_request(prefs->video.frame, 400, 300); + gtk_container_add(GTK_CONTAINER(prefs->video.frame), video); + prefs->video.sink_widget = video; + + gst_element_set_state(GST_ELEMENT(prefs->video.pipeline), + GST_STATE_PLAYING); +} + +static void +toggle_video_test_cb(GtkToggleButton *test, gpointer data) +{ + PidginVVPrefs *prefs = PIDGIN_VV_PREFS(data); + + if (gtk_toggle_button_get_active(test)) { + enable_video_test(prefs); + g_signal_connect(test, "destroy", + G_CALLBACK(video_test_destroy_cb), prefs); + } else { + g_object_disconnect(test, "any-signal::destroy", + G_CALLBACK(video_test_destroy_cb), prefs, + NULL); + video_test_destroy_cb(NULL, prefs); + } +} + +static void +vv_device_changed_cb(const gchar *name, PurplePrefType type, + gconstpointer value, gpointer data) +{ + PidginVVPrefs *prefs = PIDGIN_VV_PREFS(data); + + PurpleMediaManager *manager; + PurpleMediaElementInfo *info; + + manager = purple_media_manager_get(); + info = purple_media_manager_get_element_info(manager, value); + purple_media_manager_set_active_element(manager, info); + + /* Refresh test viewers */ + if (strstr(name, "audio") && prefs->voice.pipeline) { + voice_test_destroy_cb(NULL, prefs); + enable_voice_test(prefs); + } else if (strstr(name, "video") && prefs->video.pipeline) { + video_test_destroy_cb(NULL, prefs); + enable_video_test(prefs); + } +} + +static const char * +purple_media_type_to_preference_key(PurpleMediaElementType type) +{ + if (type & PURPLE_MEDIA_ELEMENT_AUDIO) { + if (type & PURPLE_MEDIA_ELEMENT_SRC) { + return PIDGIN_PREFS_ROOT "/vvconfig/audio/src/device"; + } else if (type & PURPLE_MEDIA_ELEMENT_SINK) { + return PIDGIN_PREFS_ROOT "/vvconfig/audio/sink/device"; + } + } else if (type & PURPLE_MEDIA_ELEMENT_VIDEO) { + if (type & PURPLE_MEDIA_ELEMENT_SRC) { + return PIDGIN_PREFS_ROOT "/vvconfig/video/src/device"; + } else if (type & PURPLE_MEDIA_ELEMENT_SINK) { + return PIDGIN_PREFS_ROOT "/vvconfig/video/sink/device"; + } + } + + return NULL; +} + +static void +bind_vv_dropdown(PidginPrefCombo *combo, PurpleMediaElementType element_type) +{ + const gchar *preference_key; + GtkTreeModel *model; + + preference_key = purple_media_type_to_preference_key(element_type); + model = gtk_combo_box_get_model(GTK_COMBO_BOX(combo->combo)); + populate_vv_device_menuitems(element_type, GTK_LIST_STORE(model)); + + combo->type = PURPLE_PREF_STRING; + combo->key = preference_key; + pidgin_prefs_bind_dropdown(combo); +} + +static void +bind_vv_frame(PidginVVPrefs *prefs, PidginPrefCombo *combo, + PurpleMediaElementType type) +{ + bind_vv_dropdown(combo, type); + + purple_prefs_connect_callback(combo->combo, + purple_media_type_to_preference_key(type), + vv_device_changed_cb, prefs); + g_signal_connect_swapped(combo->combo, "destroy", + G_CALLBACK(purple_prefs_disconnect_by_handle), + combo->combo); + + g_object_set_data(G_OBJECT(combo->combo), "vv_media_type", + (gpointer)type); + g_object_set_data(G_OBJECT(combo->combo), "vv_combo", combo); +} + +static void +device_list_changed_cb(PurpleMediaManager *manager, GtkWidget *widget) +{ + PidginPrefCombo *combo; + PurpleMediaElementType media_type; + const gchar *preference_key; + guint signal_id; + GtkTreeModel *model; + + combo = g_object_get_data(G_OBJECT(widget), "vv_combo"); + media_type = (PurpleMediaElementType)GPOINTER_TO_INT(g_object_get_data( + G_OBJECT(widget), + "vv_media_type")); + preference_key = purple_media_type_to_preference_key(media_type); + + /* Block signals so pref doesn't get re-saved while changing UI. */ + signal_id = g_signal_lookup("changed", GTK_TYPE_COMBO_BOX); + g_signal_handlers_block_matched(combo->combo, G_SIGNAL_MATCH_ID, signal_id, + 0, NULL, NULL, NULL); + + model = gtk_combo_box_get_model(GTK_COMBO_BOX(combo->combo)); + populate_vv_device_menuitems(media_type, GTK_LIST_STORE(model)); + gtk_combo_box_set_active_id(GTK_COMBO_BOX(combo->combo), + purple_prefs_get_string(preference_key)); + + g_signal_handlers_unblock_matched(combo->combo, G_SIGNAL_MATCH_ID, + signal_id, 0, NULL, NULL, NULL); +} + +/****************************************************************************** + * GObject Implementation + *****************************************************************************/ +static void +pidgin_vv_prefs_class_init(PidginVVPrefsClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass); + + gtk_widget_class_set_template_from_resource( + widget_class, + "/im/pidgin/Pidgin3/Prefs/vv.ui" + ); + + gtk_widget_class_bind_template_child(widget_class, PidginVVPrefs, + voice.input.combo); + gtk_widget_class_bind_template_child(widget_class, PidginVVPrefs, + voice.output.combo); + gtk_widget_class_bind_template_child(widget_class, PidginVVPrefs, + voice.volume); + gtk_widget_class_bind_template_child(widget_class, PidginVVPrefs, + voice.threshold_label); + gtk_widget_class_bind_template_child(widget_class, PidginVVPrefs, + voice.threshold); + gtk_widget_class_bind_template_child(widget_class, PidginVVPrefs, + voice.level); + gtk_widget_class_bind_template_child(widget_class, PidginVVPrefs, + voice.test); + gtk_widget_class_bind_template_callback(widget_class, volume_changed_cb); + gtk_widget_class_bind_template_callback(widget_class, + threshold_value_changed_cb); + gtk_widget_class_bind_template_callback(widget_class, + toggle_voice_test_cb); + + gtk_widget_class_bind_template_child(widget_class, PidginVVPrefs, + video.input.combo); + gtk_widget_class_bind_template_child(widget_class, PidginVVPrefs, + video.output.combo); + gtk_widget_class_bind_template_child(widget_class, PidginVVPrefs, + video.frame); + gtk_widget_class_bind_template_child(widget_class, PidginVVPrefs, + video.test); + gtk_widget_class_bind_template_callback(widget_class, + toggle_video_test_cb); +} + +static void +pidgin_vv_prefs_init(PidginVVPrefs *prefs) +{ + PurpleMediaManager *manager = NULL; + + gtk_widget_init_template(GTK_WIDGET(prefs)); + + manager = purple_media_manager_get(); + + bind_vv_frame(prefs, &prefs->voice.input, + PURPLE_MEDIA_ELEMENT_AUDIO | PURPLE_MEDIA_ELEMENT_SRC); + g_signal_connect_object(manager, "elements-changed::audiosrc", + G_CALLBACK(device_list_changed_cb), + prefs->voice.input.combo, 0); + + bind_vv_frame(prefs, &prefs->voice.output, + PURPLE_MEDIA_ELEMENT_AUDIO | PURPLE_MEDIA_ELEMENT_SINK); + g_signal_connect_object(manager, "elements-changed::audiosink", + G_CALLBACK(device_list_changed_cb), + prefs->voice.output.combo, 0); + + bind_voice_test(prefs); + + bind_vv_frame(prefs, &prefs->video.input, + PURPLE_MEDIA_ELEMENT_VIDEO | PURPLE_MEDIA_ELEMENT_SRC); + g_signal_connect_object(manager, "elements-changed::videosrc", + G_CALLBACK(device_list_changed_cb), + prefs->video.input.combo, 0); + + bind_vv_frame(prefs, &prefs->video.output, + PURPLE_MEDIA_ELEMENT_VIDEO | PURPLE_MEDIA_ELEMENT_SINK); + g_signal_connect_object(manager, "elements-changed::videosink", + G_CALLBACK(device_list_changed_cb), + prefs->video.output.combo, 0); +} + +/****************************************************************************** + * API + *****************************************************************************/ +GtkWidget * +pidgin_vv_prefs_new(void) { + return GTK_WIDGET(g_object_new(PIDGIN_TYPE_VV_PREFS, NULL)); +} + +void +pidgin_vv_prefs_disable_test_pipelines(PidginVVPrefs *prefs) { + g_return_if_fail(PIDGIN_IS_VV_PREFS(prefs)); + + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(prefs->voice.test), FALSE); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(prefs->video.test), FALSE); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/prefs/pidginvvprefs.h Fri May 20 02:24:05 2022 -0500 @@ -0,0 +1,73 @@ +/* + * Pidgin - Internet Messenger + * Copyright (C) Pidgin Developers <devel@pidgin.im> + * + * Pidgin is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#if !defined(PIDGIN_GLOBAL_HEADER_INSIDE) && !defined(PIDGIN_COMPILATION) +# error "only <pidgin.h> may be included directly" +#endif + +#ifndef PIDGIN_VV_PREFS_H +#define PIDGIN_VV_PREFS_H + +#include <glib.h> + +#include <gtk/gtk.h> +#include <handy.h> + +G_BEGIN_DECLS + +/** + * PidginVVPrefs: + * + * #PidginVVPrefs is a widget for the preferences window to let users + * choose and configure their voice and video settings. + * + * Since: 3.0.0 + */ +#define PIDGIN_TYPE_VV_PREFS (pidgin_vv_prefs_get_type()) +G_DECLARE_FINAL_TYPE(PidginVVPrefs, pidgin_vv_prefs, + PIDGIN, VV_PREFS, HdyPreferencesPage) + +/** + * pidgin_vv_prefs_new: + * + * Creates a new #PidginVVPrefs instance. + * + * Returns: (transfer full): The new #PidginVVPrefs instance. + * + * Since: 3.0.0 + */ +GtkWidget *pidgin_vv_prefs_new(void); + +/** + * pidgin_vv_prefs_disable_test_pipelines: + * @prefs: The #PidginVVPrefs instance. + * + * Disable any test pipelines that may be playing on this widget. This may be + * used when switching focus or views to a different widget. + * + * Since: 3.0.0 + */ +void pidgin_vv_prefs_disable_test_pipelines(PidginVVPrefs *prefs); + +G_END_DECLS + +#endif /* PIDGIN_VV_PREFS_H */
--- a/pidgin/resources/Prefs/vv.ui Fri May 20 01:37:47 2022 -0500 +++ b/pidgin/resources/Prefs/vv.ui Fri May 20 02:24:05 2022 -0500 @@ -35,7 +35,7 @@ <property name="step-increment">1</property> <property name="page-increment">10</property> </object> - <object class="GtkListStore" id="vv.video.input.store"> + <object class="GtkListStore" id="video.input.store"> <columns> <!-- column-name text --> <column type="gchararray"/> @@ -43,7 +43,7 @@ <column type="gchararray"/> </columns> </object> - <object class="GtkListStore" id="vv.video.output.store"> + <object class="GtkListStore" id="video.output.store"> <columns> <!-- column-name text --> <column type="gchararray"/> @@ -51,7 +51,7 @@ <column type="gchararray"/> </columns> </object> - <object class="GtkListStore" id="vv.voice.input.store"> + <object class="GtkListStore" id="voice.input.store"> <columns> <!-- column-name text --> <column type="gchararray"/> @@ -59,7 +59,7 @@ <column type="gchararray"/> </columns> </object> - <object class="GtkListStore" id="vv.voice.output.store"> + <object class="GtkListStore" id="voice.output.store"> <columns> <!-- column-name text --> <column type="gchararray"/> @@ -67,95 +67,173 @@ <column type="gchararray"/> </columns> </object> - <object class="GtkBox" id="vv.page"> + <template class="PidginVVPrefs" parent="HdyPreferencesPage"> <property name="visible">True</property> <property name="can-focus">False</property> - <property name="border-width">12</property> - <property name="orientation">vertical</property> - <property name="spacing">18</property> <child> - <object class="GtkBox"> + <object class="HdyPreferencesGroup"> <property name="visible">True</property> <property name="can-focus">False</property> - <property name="spacing">6</property> + <property name="title" translatable="yes">Audio</property> <child> - <object class="GtkFrame"> + <object class="GtkAlignment"> <property name="visible">True</property> <property name="can-focus">False</property> - <property name="label-xalign">0</property> - <property name="shadow-type">none</property> + <property name="left-padding">12</property> <child> - <object class="GtkAlignment"> + <object class="GtkBox"> <property name="visible">True</property> <property name="can-focus">False</property> - <property name="left-padding">12</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <child> + <object class="GtkFrame"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label-xalign">0</property> + <property name="shadow-type">none</property> + <child> + <object class="GtkAlignment"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="left-padding">12</property> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel" id="label1"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes" context="Device for Audio Input">Device</property> + <property name="xalign">0</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkComboBox" id="voice.input.combo"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="id-column">1</property> + <property name="model">voice.input.store</property> + <child> + <object class="GtkCellRendererText"/> + <attributes> + <attribute name="text">0</attribute> + </attributes> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + </object> + </child> + <child type="label"> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes" context="Input for Audio">Input</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkFrame"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label-xalign">0</property> + <property name="shadow-type">none</property> + <child> + <object class="GtkAlignment"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="left-padding">12</property> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel" id="label2"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes" context="Device for Audio Output">Device</property> + <property name="xalign">0</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkComboBox" id="voice.output.combo"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="id-column">1</property> + <property name="model">voice.output.store</property> + <child> + <object class="GtkCellRendererText"/> + <attributes> + <attribute name="text">0</attribute> + </attributes> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + </object> + </child> + <child type="label"> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes" context="Output for Audio">Output</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> <child> <object class="GtkBox"> <property name="visible">True</property> <property name="can-focus">False</property> - <property name="orientation">vertical</property> <property name="spacing">6</property> <child> - <object class="GtkFrame"> + <object class="GtkLabel"> <property name="visible">True</property> <property name="can-focus">False</property> - <property name="label-xalign">0</property> - <property name="shadow-type">none</property> - <child> - <object class="GtkAlignment"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="left-padding">12</property> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="spacing">6</property> - <child> - <object class="GtkLabel" id="label1"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label" translatable="yes" context="Device for Audio Input">Device</property> - <property name="xalign">0</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkComboBox" id="vv.voice.input.combo"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="model">vv.voice.input.store</property> - <child> - <object class="GtkCellRendererText"/> - <attributes> - <attribute name="text">0</attribute> - </attributes> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - </child> - </object> - </child> - <child type="label"> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label" translatable="yes" context="Input for Audio">Input</property> - <attributes> - <attribute name="weight" value="bold"/> - </attributes> - </object> - </child> + <property name="label" translatable="yes">Volume:</property> </object> <packing> <property name="expand">False</property> @@ -164,64 +242,31 @@ </packing> </child> <child> - <object class="GtkFrame"> + <object class="GtkVolumeButton" id="voice.volume"> <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label-xalign">0</property> - <property name="shadow-type">none</property> - <child> - <object class="GtkAlignment"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="left-padding">12</property> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="spacing">6</property> - <child> - <object class="GtkLabel" id="label2"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label" translatable="yes" context="Device for Audio Output">Device</property> - <property name="xalign">0</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkComboBox" id="vv.voice.output.combo"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="model">vv.voice.output.store</property> - <child> - <object class="GtkCellRendererText"/> - <attributes> - <attribute name="text">0</attribute> - </attributes> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - </child> + <property name="can-focus">True</property> + <property name="focus-on-click">False</property> + <property name="receives-default">True</property> + <property name="relief">none</property> + <property name="orientation">vertical</property> + <property name="adjustment">adjustment2</property> + <signal name="value-changed" handler="volume_changed_cb" swapped="no"/> + <child internal-child="plus_button"> + <object class="GtkButton"> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="halign">center</property> + <property name="valign">center</property> + <property name="relief">none</property> </object> </child> - <child type="label"> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label" translatable="yes" context="Output for Audio">Output</property> - <attributes> - <attribute name="weight" value="bold"/> - </attributes> + <child internal-child="minus_button"> + <object class="GtkButton"> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="halign">center</property> + <property name="valign">center</property> + <property name="relief">none</property> </object> </child> </object> @@ -231,355 +276,277 @@ <property name="position">1</property> </packing> </child> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="spacing">6</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label" translatable="yes">Volume:</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkVolumeButton" id="vv.voice.volume"> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="focus-on-click">False</property> - <property name="receives-default">True</property> - <property name="relief">none</property> - <property name="orientation">vertical</property> - <property name="adjustment">adjustment2</property> - <property name="icons">audio-volume-muted-symbolic -audio-volume-high-symbolic -audio-volume-low-symbolic -audio-volume-medium-symbolic</property> - <child internal-child="plus_button"> - <object class="GtkButton"> - <property name="can-focus">True</property> - <property name="receives-default">True</property> - <property name="halign">center</property> - <property name="valign">center</property> - <property name="relief">none</property> - </object> - </child> - <child internal-child="minus_button"> - <object class="GtkButton"> - <property name="can-focus">True</property> - <property name="receives-default">True</property> - <property name="halign">center</property> - <property name="valign">center</property> - <property name="relief">none</property> - </object> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">2</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="vv.voice.threshold_label"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label" translatable="yes">Silence threshold:</property> - <property name="xalign">0</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">3</property> - </packing> - </child> - <child> - <object class="GtkScale" id="vv.voice.threshold"> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="adjustment">adjustment1</property> - <property name="round-digits">0</property> - <property name="digits">0</property> - <property name="draw-value">False</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">4</property> - </packing> - </child> - <child> - <object class="GtkToggleButton" id="vv.voice.test"> - <property name="label" translatable="yes">Test Audio</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">True</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">5</property> - </packing> - </child> - <child> - <object class="GtkProgressBar" id="vv.voice.level"> - <property name="visible">True</property> - <property name="sensitive">False</property> - <property name="can-focus">False</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">6</property> - </packing> - </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="voice.threshold_label"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes">Silence threshold:</property> + <property name="xalign">0</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">3</property> + </packing> + </child> + <child> + <object class="GtkScale" id="voice.threshold"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="adjustment">adjustment1</property> + <property name="round-digits">0</property> + <property name="digits">0</property> + <property name="draw-value">False</property> + <signal name="value-changed" handler="threshold_value_changed_cb" object="PidginVVPrefs" swapped="no"/> </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">4</property> + </packing> </child> - </object> - </child> - <child type="label"> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label" translatable="yes">Audio</property> - <attributes> - <attribute name="weight" value="bold"/> - </attributes> + <child> + <object class="GtkToggleButton" id="voice.test"> + <property name="label" translatable="yes">Test Audio</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <signal name="toggled" handler="toggle_voice_test_cb" object="PidginVVPrefs" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">5</property> + </packing> + </child> + <child> + <object class="GtkProgressBar" id="voice.level"> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="can-focus">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">6</property> + </packing> + </child> </object> </child> </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="HdyPreferencesGroup"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="title" translatable="yes">Video</property> <child> - <object class="GtkFrame"> + <object class="GtkAlignment"> <property name="visible">True</property> <property name="can-focus">False</property> - <property name="label-xalign">0</property> - <property name="shadow-type">none</property> + <property name="left-padding">12</property> <child> - <object class="GtkAlignment"> + <object class="GtkBox"> <property name="visible">True</property> <property name="can-focus">False</property> - <property name="left-padding">12</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> <child> - <object class="GtkBox"> + <object class="GtkFrame"> <property name="visible">True</property> <property name="can-focus">False</property> - <property name="orientation">vertical</property> - <property name="spacing">6</property> + <property name="label-xalign">0</property> + <property name="shadow-type">none</property> <child> - <object class="GtkFrame"> + <object class="GtkAlignment"> <property name="visible">True</property> <property name="can-focus">False</property> - <property name="label-xalign">0</property> - <property name="shadow-type">none</property> + <property name="left-padding">12</property> <child> - <object class="GtkAlignment"> + <object class="GtkBox"> <property name="visible">True</property> <property name="can-focus">False</property> - <property name="left-padding">12</property> + <property name="spacing">6</property> <child> - <object class="GtkBox"> + <object class="GtkLabel" id="label3"> <property name="visible">True</property> <property name="can-focus">False</property> - <property name="spacing">6</property> - <child> - <object class="GtkLabel" id="label3"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label" translatable="yes" context="Device for Video Input">Device</property> - <property name="xalign">0</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> + <property name="label" translatable="yes" context="Device for Video Input">Device</property> + <property name="xalign">0</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkComboBox" id="video.input.combo"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="id-column">1</property> + <property name="model">video.input.store</property> <child> - <object class="GtkComboBox" id="vv.video.input.combo"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="model">vv.video.input.store</property> - <child> - <object class="GtkCellRendererText"/> - <attributes> - <attribute name="text">0</attribute> - </attributes> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> + <object class="GtkCellRendererText"/> + <attributes> + <attribute name="text">0</attribute> + </attributes> </child> </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> </child> </object> </child> - <child type="label"> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label" translatable="yes" context="Input for Video">Input</property> - <attributes> - <attribute name="weight" value="bold"/> - </attributes> - </object> - </child> </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> </child> - <child> - <object class="GtkFrame"> + <child type="label"> + <object class="GtkLabel"> <property name="visible">True</property> <property name="can-focus">False</property> - <property name="label-xalign">0</property> - <property name="shadow-type">none</property> + <property name="label" translatable="yes" context="Input for Video">Input</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkFrame"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label-xalign">0</property> + <property name="shadow-type">none</property> + <child> + <object class="GtkAlignment"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="left-padding">12</property> <child> - <object class="GtkAlignment"> + <object class="GtkBox"> <property name="visible">True</property> <property name="can-focus">False</property> - <property name="left-padding">12</property> + <property name="spacing">6</property> <child> - <object class="GtkBox"> + <object class="GtkLabel" id="label4"> <property name="visible">True</property> <property name="can-focus">False</property> - <property name="spacing">6</property> - <child> - <object class="GtkLabel" id="label4"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label" translatable="yes" context="Device for Video Output">Device</property> - <property name="xalign">0</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> + <property name="label" translatable="yes" context="Device for Video Output">Device</property> + <property name="xalign">0</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkComboBox" id="video.output.combo"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="id-column">1</property> + <property name="model">video.output.store</property> <child> - <object class="GtkComboBox" id="vv.video.output.combo"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="model">vv.video.output.store</property> - <child> - <object class="GtkCellRendererText"/> - <attributes> - <attribute name="text">0</attribute> - </attributes> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> + <object class="GtkCellRendererText"/> + <attributes> + <attribute name="text">0</attribute> + </attributes> </child> </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> </child> </object> </child> - <child type="label"> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label" translatable="yes" context="Output for Video">Output</property> - <attributes> - <attribute name="weight" value="bold"/> - </attributes> - </object> - </child> </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> </child> - <child> - <object class="GtkAspectFrame" id="vv.video.frame"> + <child type="label"> + <object class="GtkLabel"> <property name="visible">True</property> <property name="can-focus">False</property> - <property name="label-xalign">0</property> - <property name="shadow-type">none</property> - <property name="ratio">1.3300000429153442</property> - <child> - <placeholder/> - </child> + <property name="label" translatable="yes" context="Output for Video">Output</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">2</property> - </packing> - </child> - <child> - <object class="GtkToggleButton" id="vv.video.test"> - <property name="label" translatable="yes">Test Video</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">True</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">3</property> - </packing> </child> </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkAspectFrame" id="video.frame"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label-xalign">0</property> + <property name="shadow-type">none</property> + <property name="ratio">1.33</property> + <child> + <placeholder/> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkToggleButton" id="video.test"> + <property name="label" translatable="yes">Test Video</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <signal name="toggled" handler="toggle_video_test_cb" object="PidginVVPrefs" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">3</property> + </packing> </child> </object> </child> - <child type="label"> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label" translatable="yes">Video</property> - <attributes> - <attribute name="weight" value="bold"/> - </attributes> - </object> - </child> </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> </child> </object> <packing> <property name="expand">True</property> <property name="fill">True</property> - <property name="position">0</property> + <property name="position">1</property> </packing> </child> - </object> - <object class="GtkSizeGroup" id="vv.sg"> + </template> + <object class="GtkSizeGroup" id="sg"> <widgets> <widget name="label1"/> <widget name="label2"/>
--- a/po/POTFILES.in Fri May 20 01:37:47 2022 -0500 +++ b/po/POTFILES.in Fri May 20 02:24:05 2022 -0500 @@ -394,6 +394,7 @@ pidgin/prefs/pidginnetworkpage.c pidgin/prefs/pidginprefs.c pidgin/prefs/pidginproxyprefs.c +pidgin/prefs/pidginvvprefs.c pidgin/resources/About/about.ui pidgin/resources/Accounts/actionsmenu.ui pidgin/resources/Accounts/chooser.ui