src/mainwindow.cc  |   18 ++++++++++++++++++
 src/mainwindow.h   |    3 +++
 src/mainwindow.ui  |   31 +++++++++++++++++++++++++++++++
 src/pavucontrol.cc |   19 +++++++++++++++++++
 4 files changed, 71 insertions(+)

New commits:
commit f52656b10e713bd27e443e2d82547975c35df0ed
Author: Arun Raghavan <[email protected]>
Date:   Fri Jan 23 12:50:18 2026 -0800

    Expose a setting for forcing mono audio
    
    Requires libpulse with message support, and pipewire-pulse from 1.4.10
    or 1.6.

diff --git a/src/mainwindow.cc b/src/mainwindow.cc
index dc6c78d..c9bbcdf 100644
--- a/src/mainwindow.cc
+++ b/src/mainwindow.cc
@@ -98,6 +98,8 @@ MainWindow::MainWindow(BaseObjectType* cobject, const 
Glib::RefPtr<Gtk::Builder>
     notebook = x->get_widget<Gtk::Notebook>("notebook");
     showVolumeMetersCheckButton = 
x->get_widget<Gtk::CheckButton>("showVolumeMetersCheckButton");
     hideUnavailableCardProfilesCheckButton = 
x->get_widget<Gtk::CheckButton>("hideUnavailableCardProfilesCheckButton");
+    monoAudioLabel = x->get_widget<Gtk::Label>("monoAudioLabel");
+    monoAudioSwitch = x->get_widget<Gtk::Switch>("monoAudioSwitch");
 
     sinkInputTypeComboBox->set_active((int) showSinkInputType);
     sourceOutputTypeComboBox->set_active((int) showSourceOutputType);
@@ -111,6 +113,10 @@ MainWindow::MainWindow(BaseObjectType* cobject, const 
Glib::RefPtr<Gtk::Builder>
     showVolumeMetersCheckButton->signal_toggled().connect(sigc::mem_fun(*this, 
&MainWindow::onShowVolumeMetersCheckButtonToggled));
     
hideUnavailableCardProfilesCheckButton->signal_toggled().connect(sigc::mem_fun(*this,
 &MainWindow::onHideUnavailableCardProfilesCheckButtonToggled));
 
+#if HAVE_PULSE_MESSAGING_API
+    monoAudioSwitch->signal_state_set().connect(sigc::mem_fun(*this, 
&MainWindow::onMonoAudioStateSet), false);
+#endif
+
     auto event_controller_key = Gtk::EventControllerKey::create();
     event_controller_key->signal_key_pressed().connect(sigc::mem_fun(*this, 
&MainWindow::on_key_press_event), false);
     this->add_controller(event_controller_key);
@@ -1475,3 +1481,15 @@ void 
MainWindow::onHideUnavailableCardProfilesCheckButtonToggled() {
         cw->prepareMenu();
     }
 }
+
+bool MainWindow::onMonoAudioStateSet(bool state) {
+    pa_context *c = get_context();
+    pa_operation *o;
+    const char *value = state ? "true" : "false";
+
+    o = pa_context_send_message_to_object(c, "/core", 
"pipewire-pulse:force-mono-output", value, NULL, NULL);
+    if (o)
+        pa_operation_unref(o);
+
+    return false;
+}
diff --git a/src/mainwindow.h b/src/mainwindow.h
index cc84c26..6fb1de5 100644
--- a/src/mainwindow.h
+++ b/src/mainwindow.h
@@ -91,6 +91,8 @@ class MainWindow : public Gtk::Window {
         *sinkTypeComboBox, *sourceTypeComboBox;
     Gtk::CheckButton *showVolumeMetersCheckButton,
         *hideUnavailableCardProfilesCheckButton;
+    Gtk::Label *monoAudioLabel;
+    Gtk::Switch *monoAudioSwitch;
 
     std::map<uint32_t, CardWidget *> cardWidgets;
     std::map<uint32_t, SinkWidget *> sinkWidgets;
@@ -110,6 +112,7 @@ class MainWindow : public Gtk::Window {
     virtual void onSourceTypeComboBoxChanged();
     virtual void onShowVolumeMetersCheckButtonToggled();
     virtual void onHideUnavailableCardProfilesCheckButtonToggled();
+    virtual bool onMonoAudioStateSet(bool);
 
     void setConnectionState(gboolean connected);
     void updateDeviceVisibility();
diff --git a/src/mainwindow.ui b/src/mainwindow.ui
index d6046a5..b8519c3 100644
--- a/src/mainwindow.ui
+++ b/src/mainwindow.ui
@@ -405,6 +405,37 @@
                             <property name="child">
                               <object class="GtkBox">
                                 <property 
name="orientation">vertical</property>
+                                <child>
+                                  <object class="GtkBox" id="optionsBox">
+                                    <property 
name="orientation">vertical</property>
+                                    <property name="spacing">12</property>
+                                    <property name="margin-top">18</property>
+                                    <property 
name="margin-bottom">18</property>
+                                    <property name="margin-start">12</property>
+                                    <property name="margin-end">12</property>
+                                    <child>
+                                      <object class="GtkBox" id="monoAudioBox">
+                                        <property 
name="orientation">horizontal</property>
+                                        <property name="spacing">12</property>
+                                        <property name="tooltip-text">Requires 
PipeWire 1.4.10 or later</property>
+                                        <child>
+                                          <object class="GtkSwitch" 
id="monoAudioSwitch">
+                                            <property 
name="sensitive">false</property>
+                                          </object>
+                                        </child>
+                                        <child>
+                                          <object class="GtkLabel" 
id="monoAudioLabel">
+                                            <property name="label" 
translatable="yes">Configure all output devices as mono</property>
+                                            <property 
name="sensitive">false</property>
+                                          </object>
+                                        </child>
+                                      </object>
+                                    </child>
+                                  </object>
+                                </child>
+                                <child>
+                                  <object class="GtkSeparator"/>
+                                </child>
                                 <child>
                                   <object class="GtkBox" id="cardsVBox">
                                     <property name="vexpand">1</property>
diff --git a/src/pavucontrol.cc b/src/pavucontrol.cc
index 86d7ddd..5788b3e 100644
--- a/src/pavucontrol.cc
+++ b/src/pavucontrol.cc
@@ -613,6 +613,19 @@ static void ext_device_manager_subscribe_cb(pa_context *c, 
void *userdata) {
     pa_operation_unref(o);
 }
 
+static void probe_force_mono_output_cb(pa_context *c, int success, char 
*response, void *userdata)
+{
+    MainWindow *w = static_cast<MainWindow*>(userdata);
+
+    if (success && response && strcmp(response, "null") != 0) {
+        /* We know the setting is supported */
+        w->monoAudioLabel->set_sensitive(true);
+        w->monoAudioSwitch->set_sensitive(true);
+    }
+
+    dec_outstanding(w);
+}
+
 void subscribe_cb(pa_context *c, pa_subscription_event_type_t t, uint32_t 
index, void *userdata) {
     MainWindow *w = static_cast<MainWindow*>(userdata);
 
@@ -838,6 +851,12 @@ void context_state_callback(pa_context *c, void *userdata) 
{
             } else
                 g_debug(_("Failed to initialize device manager extension: 
%s"), pa_strerror(pa_context_errno(context)));
 
+#if HAVE_PULSE_MESSAGING_API
+            if ((o = pa_context_send_message_to_object(c, "/core", 
"pipewire-pulse:force-mono-output", NULL, probe_force_mono_output_cb, w))) {
+                n_outstanding++;
+            } else
+                g_debug(_("Failed to check if we can force mono audio: %s"), 
pa_strerror(pa_context_errno(context)));
+#endif
 
             break;
         }

Reply via email to