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; }
