pidgin/pidgin

Split VV prefs into a separate widget

24 months ago, Elliott Sales de Andrade
1327e58acce3
Parents f4faf0d6ab26
Children fdc509b6587b
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