From ecc3bc65c4b62c5690e5289caa3113f1c4332a24 Mon Sep 17 00:00:00 2001 From: Hue Date: Sun, 2 Nov 2025 12:34:17 +0100 Subject: [PATCH 01/37] Added wireplumber mixer widget --- meson.build | 5 + meson_options.txt | 8 +- src/panel/meson.build | 7 +- src/panel/panel.cpp | 14 + src/panel/widgets/volume.cpp | 38 -- src/panel/widgets/volume.hpp | 29 +- src/panel/widgets/wireplumber.cpp | 643 ++++++++++++++++++++++++++++++ src/panel/widgets/wireplumber.hpp | 130 ++++++ src/util/animated_scale.cpp | 43 ++ src/util/animated_scale.hpp | 32 ++ src/util/meson.build | 1 + 11 files changed, 883 insertions(+), 67 deletions(-) create mode 100644 src/panel/widgets/wireplumber.cpp create mode 100644 src/panel/widgets/wireplumber.hpp create mode 100644 src/util/animated_scale.cpp create mode 100644 src/util/animated_scale.hpp diff --git a/meson.build b/meson.build index d026d85d..0aedd442 100644 --- a/meson.build +++ b/meson.build @@ -21,6 +21,7 @@ wfconfig = dependency('wf-config', version: '>=0.7.0') #TODO fallback submodule epoxy = dependency('epoxy') gtklayershell = dependency('gtk4-layer-shell') libpulse = dependency('libpulse', required: get_option('pulse')) +wireplumber = dependency('wireplumber-0.5', required: get_option('wireplumber')) dbusmenu_gtk = dependency('dbusmenu-glib-0.4') libgvc = subproject('gvc', default_options: ['static=true'], required: get_option('pulse')) @@ -33,6 +34,10 @@ if libpulse.found() add_project_arguments('-DHAVE_PULSE=1', language: 'cpp') endif +if wireplumber.found() + add_project_arguments('-DHAVE_WIREPLUMBER=1', language: 'cpp') +endif + needs_libinotify = ['freebsd', 'dragonfly'].contains(host_machine.system()) libinotify = dependency('libinotify', required: needs_libinotify) diff --git a/meson_options.txt b/meson_options.txt index aca93f47..8ecd76c0 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -4,9 +4,15 @@ option( value: 'auto', description: 'Build pulseaudio volume widget', ) +option( + 'wireplumber', + type: 'feature', + value: 'auto', + description: 'Build wireplumber and mixer widget', +) option( 'wayland-logout', type: 'boolean', value: true, description: 'Install wayland-logout', -) \ No newline at end of file +) diff --git a/src/panel/meson.build b/src/panel/meson.build index f086c614..9078c5d5 100644 --- a/src/panel/meson.build +++ b/src/panel/meson.build @@ -36,9 +36,14 @@ if libpulse.found() deps += [libpulse, libgvc] endif +if wireplumber.found() + widget_sources += 'widgets/wireplumber.cpp' + deps += wireplumber +endif + executable( 'wf-panel', ['panel.cpp'] + widget_sources, dependencies: deps, install: true, -) \ No newline at end of file +) diff --git a/src/panel/panel.cpp b/src/panel/panel.cpp index d684d17c..86b06359 100644 --- a/src/panel/panel.cpp +++ b/src/panel/panel.cpp @@ -24,6 +24,9 @@ #ifdef HAVE_PULSE #include "widgets/volume.hpp" #endif +#ifdef HAVE_WIREPLUMBER + #include "widgets/wireplumber.hpp" +#endif #include "widgets/window-list/window-list.hpp" #include "widgets/notifications/notification-center.hpp" #include "widgets/tray/tray.hpp" @@ -164,6 +167,17 @@ class WayfirePanel::impl #endif } + if (name == "wireplumber") + { +#ifdef HAVE_WIREPLUMBER + return Widget(new WayfireWireplumber()); +#else + #warning "Wireplumber not found, mixer widget will not be available." + std::cerr << "Built without wireplumber support, mixer widget " + " is not available." << std::endl; +#endif + } + if (name == "window-list") { return Widget(new WayfireWindowList(output)); diff --git a/src/panel/widgets/volume.cpp b/src/panel/widgets/volume.cpp index d6bd8034..5893737c 100644 --- a/src/panel/widgets/volume.cpp +++ b/src/panel/widgets/volume.cpp @@ -5,44 +5,6 @@ #include "launchers.hpp" #include "gtk-utils.hpp" -WayfireVolumeScale::WayfireVolumeScale() -{ - value_changed = this->signal_value_changed().connect([=] () - { - this->current_volume.animate(this->get_value(), this->get_value()); - if (this->user_changed_callback) - { - this->user_changed_callback(); - } - }); -} - -void WayfireVolumeScale::set_target_value(double value) -{ - this->current_volume.animate(value); - add_tick_callback(sigc::mem_fun(*this, &WayfireVolumeScale::update_animation)); -} - -gboolean WayfireVolumeScale::update_animation(Glib::RefPtr frame_clock) -{ - value_changed.block(); - this->set_value(this->current_volume); - value_changed.unblock(); - // Once we've finished fading, stop this callback - return this->current_volume.running() ? G_SOURCE_CONTINUE : G_SOURCE_REMOVE; -} - -double WayfireVolumeScale::get_target_value() const -{ - return this->current_volume.end; -} - -void WayfireVolumeScale::set_user_changed_callback( - std::function callback) -{ - this->user_changed_callback = callback; -} - enum VolumeLevel { VOLUME_LEVEL_MUTE = 0, diff --git a/src/panel/widgets/volume.hpp b/src/panel/widgets/volume.hpp index 39d3dd25..4dc63a18 100644 --- a/src/panel/widgets/volume.hpp +++ b/src/panel/widgets/volume.hpp @@ -2,40 +2,15 @@ #define WIDGETS_VOLUME_HPP #include "../widget.hpp" -#include "wf-popover.hpp" #include -#include +#include "../../util/animated_scale.hpp" #include #include "gvc-mixer-control.h" -#include - -/** - * A custom scale which animates transitions when its value is - * changed programatically. - */ -class WayfireVolumeScale : public Gtk::Scale -{ - wf::animation::simple_animation_t current_volume{wf::create_option(200)}; - sigc::connection value_changed; - std::function user_changed_callback; - - public: - WayfireVolumeScale(); - - /* Gets the current target value */ - double get_target_value() const; - /* Set a target value to animate towards */ - void set_target_value(double value); - /** Set the callback when the user changes the scale value */ - void set_user_changed_callback(std::function callback); - /** Callback to animate volume control */ - gboolean update_animation(Glib::RefPtr clock); -}; class WayfireVolume : public WayfireWidget { Gtk::Image main_image; - WayfireVolumeScale volume_scale; + WayfireAnimatedScale volume_scale; Gtk::Button button; Gtk::Popover popover; diff --git a/src/panel/widgets/wireplumber.cpp b/src/panel/widgets/wireplumber.cpp new file mode 100644 index 00000000..f20bda3f --- /dev/null +++ b/src/panel/widgets/wireplumber.cpp @@ -0,0 +1,643 @@ +#include +#include +#include +#include "gio/gio.h" +#include "glib-object.h" +#include "glib.h" +#include "gtkmm/enums.h" +#include "gtkmm/label.h" +#include "gtkmm/separator.h" +#include "gtkmm/togglebutton.h" +#include "animated_scale.hpp" +#include "widget.hpp" +#include "wp/proxy-interfaces.h" +#include "wp/proxy.h" + +#include + +#include "wireplumber.hpp" + + +enum VolumeLevel +{ + VOLUME_LEVEL_MUTE = 0, + VOLUME_LEVEL_LOW, + VOLUME_LEVEL_MED, + VOLUME_LEVEL_HIGH, + VOLUME_LEVEL_OOR, /* Out of range */ +}; + +static const gchar *DEFAULT_NODE_MEDIA_CLASSES[] = { + "Audio/Sink", + "Audio/Source", +}; + +static VolumeLevel volume_icon_for(double volume){ + double max = 1.0; + auto third = max / 3; + if (volume == 0) + { + return VOLUME_LEVEL_MUTE; + } else if ((volume > 0) && (volume <= third)) + { + return VOLUME_LEVEL_LOW; + } else if ((volume > third) && (volume <= (third * 2))) + { + return VOLUME_LEVEL_MED; + } else if ((volume > (third * 2)) && (volume <= max)) + { + return VOLUME_LEVEL_HIGH; + } + + return VOLUME_LEVEL_OOR; +} + +WfWpControl::WfWpControl(WpPipewireObject* obj, WayfireWireplumber* parent_widget){ + + object = obj; + parent = parent_widget; + + guint32 id = wp_proxy_get_bound_id(WP_PROXY(object)); + + scale.set_range(0.0, 1.0); + scale.set_target_value(0.5); + scale.set_size_request(300, 0); + + const gchar* name; + + // try to find a name to display + name = wp_pipewire_object_get_property(object, PW_KEY_NODE_NICK); + if (!name){ + name = wp_pipewire_object_get_property(object, PW_KEY_NODE_NAME); + } + if (!name){ + name = wp_pipewire_object_get_property(object, PW_KEY_NODE_DESCRIPTION); + } + if (!name){ + name = "Unnamed"; + } + + name = g_strdup_printf("%s %d", name, id); + + label.set_text(Glib::ustring(name)); + + button.set_child(volume_icon); + + attach(label, 0, 0, 2, 1); + attach(button, 1, 1, 1, 1); + attach(scale, 0, 1, 1, 1); + + mute_conn = button.signal_toggled().connect( + [this, id](){ + GVariantBuilder gvb = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE_VARDICT); + g_variant_builder_add(&gvb, "{sv}", "mute", g_variant_new_boolean(button.get_active())); + GVariant* v = g_variant_builder_end(&gvb); + gboolean res FALSE; + g_signal_emit_by_name(WpCommon::mixer_api, "set-volume", id, v, &res); + } + ); + + scale.set_user_changed_callback( + [this, id](){ + GVariantBuilder gvb = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE_VARDICT); + g_variant_builder_add(&gvb, "{sv}", "volume", g_variant_new_double(std::pow(scale.get_target_value(), 3))); // see line x + GVariant* v = g_variant_builder_end(&gvb); + gboolean res FALSE; + g_signal_emit_by_name(WpCommon::mixer_api, "set-volume", id, v, &res); + } + ); + + // initialise the values + GVariant* v = NULL; + g_signal_emit_by_name(WpCommon::mixer_api, "get-volume", id, &v); + if (!v) { + return; + } + gboolean mute = FALSE; + gdouble volume = 0.0; + g_variant_lookup(v, "volume", "d", &volume); + g_variant_lookup(v, "mute", "b", &mute); + g_clear_pointer(&v, g_variant_unref); + set_btn_status_no_callbk(mute); + set_scale_target_value(std::cbrt(volume)); // see line x + + update_icon(); +} + +Glib::ustring WfWpControl::get_name(){ + return label.get_text(); +} + +void WfWpControl::set_btn_status_no_callbk(bool state){ + mute_conn.block(true); + button.set_active(state); + mute_conn.block(false); +} + +void WfWpControl::set_scale_target_value(double volume){ + scale.set_target_value(volume); +} + +void WfWpControl::update_icon(){ + + VolumeLevel current = volume_icon_for(get_scale_target_value()); + + if (is_muted()){ + volume_icon.set_from_icon_name("audio-volume-muted"); + return; + } + + std::map icon_name_from_state = { + {VOLUME_LEVEL_MUTE, "audio-volume-muted"}, + {VOLUME_LEVEL_LOW, "audio-volume-low"}, + {VOLUME_LEVEL_MED, "audio-volume-medium"}, + {VOLUME_LEVEL_HIGH, "audio-volume-high"}, + {VOLUME_LEVEL_OOR, "audio-volume-muted"}, + }; + + volume_icon.set_from_icon_name(icon_name_from_state.at(current)); +} + +double WfWpControl::get_scale_target_value(){ + return scale.get_target_value(); +} + +bool WfWpControl::is_muted(){ + return button.get_active(); +} + +WfWpControl* WfWpControl::copy(){ + WfWpControl* copy = new WfWpControl(object, parent); + return copy; +} + +WfWpControlDevice::WfWpControlDevice(WpPipewireObject* obj, WayfireWireplumber* parent_widget) : WfWpControl(obj, parent_widget){ + // for devices (sinks and sources), we determine if they are the default + + attach(default_btn, 1, 0, 1, 1); + + WpProxy* proxy = WP_PROXY(object); + + is_def_icon.set_from_icon_name("emblem-default"); + default_btn.set_child(is_def_icon); + + def_conn = default_btn.signal_clicked().connect( + [proxy](){ + const gchar* media_class = wp_pipewire_object_get_property( + WP_PIPEWIRE_OBJECT(proxy), + PW_KEY_MEDIA_CLASS + ); + for (guint i = 0; i < G_N_ELEMENTS(DEFAULT_NODE_MEDIA_CLASSES); i++) { + if (g_strcmp0(media_class, DEFAULT_NODE_MEDIA_CLASSES[i])){ + continue; + } + gboolean res = FALSE; + const gchar *name = wp_pipewire_object_get_property( + WP_PIPEWIRE_OBJECT(proxy), + PW_KEY_NODE_NAME + ); + if (!name) continue; + + g_signal_emit_by_name (WpCommon::default_nodes_api, "set-default-configured-node-name", + DEFAULT_NODE_MEDIA_CLASSES[i], name, &res); + if (!res) continue; + + wp_core_sync(WpCommon::core, NULL, NULL, NULL); + } + } + ); + +} + +void WfWpControlDevice::set_def_status_no_callbk(bool state){ + def_conn.block(true); + default_btn.set_active(state); + def_conn.block(false); +} + +WfWpControlDevice* WfWpControlDevice::copy(){ + WfWpControlDevice* copy = new WfWpControlDevice(object, parent); + return copy; +} + +bool WayfireWireplumber::on_popover_timeout(int timer) +{ + popover_timeout.disconnect(); + popover->popdown(); + return false; +} + +void WayfireWireplumber::check_set_popover_timeout() +{ + popover_timeout.disconnect(); + + popover_timeout = Glib::signal_timeout().connect(sigc::bind(sigc::mem_fun(*this, + &WayfireWireplumber::on_popover_timeout), 0), timeout * 1000); +} + +void WayfireWireplumber::init(Gtk::Box *container){ + // sets up the « widget part » + + button = std::make_unique("panel"); + button->get_style_context()->add_class("volume"); + button->get_style_context()->add_class("flat"); + button->set_child(main_image); + button->show(); + button->get_popover()->set_child(master_box); + popover = button->get_popover(); + popover->set_autohide(false); + popover->signal_closed().connect( + [&]{ + // when the widget is clicked during the « small » popup, replace by full mixer + // if the popover child is the master box, it was already closed and we don’t interfere + if (popover->get_child() != (Gtk::Widget*)&master_box){ + popover->set_child(master_box); + popover_timeout.disconnect(); + } + } + ); + + auto scroll_gesture = Gtk::EventControllerScroll::create(); + scroll_gesture->signal_scroll().connect([=] (double dx, double dy) + { + if (!face) return false; // no face means we have nothing to change by scrolling + dy = dy * -1; // for the same scrolling as volume widget, which we will agree it is more intuitive for more people. TODO : make this configurable + double change = 0; + if (scroll_gesture->get_unit() == Gdk::ScrollUnit::WHEEL) + { + // +- number of clicks. + change = (dy * scroll_sensitivity) / 10; + } else + { + // Number of pixels expected to have scrolled. usually in 100s + change = (dy * scroll_sensitivity) / 100; + } + guint32 id = wp_proxy_get_bound_id(WP_PROXY(face->object)); + GVariantBuilder gvb = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE_VARDICT); + g_variant_builder_add(&gvb, "{sv}", "volume", g_variant_new_double(std::pow(face->get_scale_target_value() + change, 3))); // see line x + GVariant* v = g_variant_builder_end(&gvb); + gboolean res FALSE; + g_signal_emit_by_name(WpCommon::mixer_api, "set-volume", id, v, &res); + return true; + }, true); + scroll_gesture->set_flags(Gtk::EventControllerScroll::Flags::VERTICAL); + button->add_controller(scroll_gesture); + + // boxes hierarchy and labeling + Gtk::Orientation r1, r2; + // if (config::is_horizontal){ // todo : make this configurable + + r1 = Gtk::Orientation::HORIZONTAL; + r2 = Gtk::Orientation::VERTICAL; + // } + // else { + // r1 = Gtk::Orientation::VERTICAL; + // r2 = Gtk::Orientation::HORIZONTAL; + // } + master_box.set_orientation(r1); + // TODO : only show the boxes which have stuff in them + master_box.append(sinks_box); + master_box.append(*new Gtk::Separator(r1)); + master_box.append(sources_box); + master_box.append(*new Gtk::Separator(r1)); + master_box.append(streams_box); + sinks_box.set_orientation(r2); + sinks_box.append(*new Gtk::Label("Output devices")); + sinks_box.append(*new Gtk::Separator(r2)); + sources_box.set_orientation(r2); + sources_box.append(*new Gtk::Label("Input devices")); + sources_box.append(*new Gtk::Separator(r2)); + streams_box.set_orientation(r2); + streams_box.append(*new Gtk::Label("Audio streams")); + streams_box.append(*new Gtk::Separator(r2)); + + /* Setup popover */ + popover->set_child(master_box); + popover->get_style_context()->add_class("volume-popover"); + popover->add_controller(scroll_gesture); + + /* Setup layout */ + container->append(*button); + button->set_child(main_image); + + /* + if the core is already set, we are another widget, wether on another monitor + or on the same wf-panel. We re-use the core, manager and all other objects + */ + + WpCommon::widgets.push_back(this); + + if (WpCommon::core != nullptr){ + WpCommon::catch_up_to_current_state(this); + return; + } + WpCommon::init_wp(); +} + +void WayfireWireplumber::update_icon() +{ + VolumeLevel current = volume_icon_for(face->get_scale_target_value()); + + if (!face || face->is_muted()){ + main_image.set_from_icon_name("audio-volume-muted"); + return; + } + + std::map icon_name_from_state = { + {VOLUME_LEVEL_MUTE, "audio-volume-muted"}, + {VOLUME_LEVEL_LOW, "audio-volume-low"}, + {VOLUME_LEVEL_MED, "audio-volume-medium"}, + {VOLUME_LEVEL_HIGH, "audio-volume-high"}, + {VOLUME_LEVEL_OOR, "audio-volume-muted"}, + }; + + main_image.set_from_icon_name(icon_name_from_state.at(current)); +} + +void WpCommon::init_wp(){ + // creates the core, object interests and connects signals + + std::cout << "Initialising wireplumber\n"; + wp_init(WP_INIT_PIPEWIRE); + core = wp_core_new(NULL, NULL, NULL); + object_manager = wp_object_manager_new(); + + // register interests in sinks, sources and streams + WpObjectInterest* sink_interest = wp_object_interest_new_type(WP_TYPE_NODE); + wp_object_interest_add_constraint( + sink_interest, + WP_CONSTRAINT_TYPE_PW_PROPERTY, + "media.class", + WP_CONSTRAINT_VERB_EQUALS, + g_variant_new_string("Audio/Sink") + ); + + wp_object_manager_add_interest_full(object_manager, sink_interest); + + WpObjectInterest* source_interest = wp_object_interest_new_type(WP_TYPE_NODE); + wp_object_interest_add_constraint( + source_interest, + WP_CONSTRAINT_TYPE_PW_PROPERTY, + "media.class", + WP_CONSTRAINT_VERB_EQUALS, + g_variant_new_string("Audio/Source") + ); + + wp_object_manager_add_interest_full(object_manager, source_interest); + + WpObjectInterest* stream_interest = wp_object_interest_new_type(WP_TYPE_NODE); + wp_object_interest_add_constraint( + stream_interest, + WP_CONSTRAINT_TYPE_PW_PROPERTY, + "media.class", + WP_CONSTRAINT_VERB_EQUALS, + g_variant_new_string("Stream/Output/Audio") + ); + wp_object_manager_add_interest_full(object_manager, stream_interest); + + wp_core_load_component( + core, + "libwireplumber-module-mixer-api", + "module", + NULL, + NULL, + NULL, + (GAsyncReadyCallback)on_mixer_plugin_loaded, + NULL + ); + + wp_core_connect(core); +} + +void WpCommon::catch_up_to_current_state(WayfireWireplumber *widget){ + // catch up to objects already registered by the manager + WpIterator* reg_objs = wp_object_manager_new_iterator(object_manager); + GValue item = G_VALUE_INIT; + while (wp_iterator_next(reg_objs, &item)){ + on_object_added(object_manager, (gpointer)g_value_get_object(&item), &widget); + g_value_unset(&item); + } + +} + +void WpCommon::on_mixer_plugin_loaded(WpCore* core, GAsyncResult* res, void* data){ + mixer_api = wp_plugin_find(core, "mixer-api"); + // as far as i understand, this should set the mixer api to use linear scale + // however, it doesn’t and i can’t find what is wrong. + // for now, we calculate manually + // g_object_set(mixer_api, "scale", 0, NULL); // set to linear + + wp_core_load_component( + core, + "libwireplumber-module-default-nodes-api", + "module", + NULL, + NULL, + NULL, + (GAsyncReadyCallback)on_default_nodes_plugin_loaded, + NULL + ); + +} + +void WpCommon::on_default_nodes_plugin_loaded(WpCore* core, GAsyncResult* res, void* data){ + default_nodes_api = wp_plugin_find(core, "default-nodes-api"); + + on_all_plugins_loaded(); +} + +void WpCommon::on_all_plugins_loaded(){ + g_signal_connect( + mixer_api, + "changed", + G_CALLBACK(WpCommon::on_mixer_changed), + NULL + ); + + g_signal_connect( + default_nodes_api, + "changed", + G_CALLBACK(WpCommon::on_default_nodes_changed), + NULL + ); + + g_signal_connect( + object_manager, + "object_added", + G_CALLBACK(on_object_added), + NULL + ); + + g_signal_connect( + object_manager, + "object-removed", + G_CALLBACK(on_object_removed), + NULL + ); + + wp_core_install_object_manager(core, object_manager); +} + +void WpCommon::on_object_added(WpObjectManager* manager, gpointer object, gpointer data){ + // adds a new widget to the appropriate section + + WpPipewireObject* obj = (WpPipewireObject*)object; + + const gchar* type = wp_pipewire_object_get_property(obj, PW_KEY_MEDIA_CLASS); + + for (auto widget : widgets){ + + WfWpControl* control; + Gtk::Box* which_box; + if (g_strcmp0(type, "Audio/Sink") == 0){ + which_box = &(widget->sinks_box); + control = new WfWpControlDevice(obj, widget); + } + else if (g_strcmp0(type, "Audio/Source") == 0){ + which_box = &(widget->sources_box); + control = new WfWpControlDevice(obj, widget); + } + else if (g_strcmp0(type, "Stream/Output/Audio") == 0){ + which_box = &(widget->streams_box); + control = new WfWpControl(obj, (WayfireWireplumber*)widget); + } + else { + std::cout << "Could not match pipewire object media class, ignoring\n"; + return; + } + + widget->objects_to_controls.insert({obj, control}); + which_box->append((Gtk::Widget&)*control); + } +} + +void WpCommon::on_mixer_changed(gpointer mixer_api, guint id, gpointer data){ + // update the visual of the appropriate WfWpControl according to external changes + + GVariant* v = NULL; + // ask the mixer-api for the up-to-date data + g_signal_emit_by_name(WpCommon::mixer_api, "get-volume", id, &v); + if (!v) { + return; + } + + gboolean mute = FALSE; + gdouble volume = 0.0; + g_variant_lookup(v, "volume", "d", &volume); + g_variant_lookup(v, "mute", "b", &mute); + g_clear_pointer(&v, g_variant_unref); + + for (auto widget : widgets){ + + WfWpControl* control; + + for (auto &it : widget->objects_to_controls){ + WpPipewireObject* obj = it.first; + control = it.second; + if (wp_proxy_get_bound_id(WP_PROXY(obj)) == id){ + break; + } + } + + control->set_btn_status_no_callbk(mute); + control->set_scale_target_value(std::cbrt(volume)); // see line x + control->update_icon(); + + // if we have the *full* mixer in the popover + if ((Gtk::Widget*)&(widget->master_box) + == + (Gtk::Widget*)widget->popover->get_child() + ){ + // if shown, stop there + if (widget->popover->is_visible()) return; + + // if hidden, replace by the popover + widget->face = control->copy(); + widget->popover->set_child(*widget->face); + } + + // ensure the control in the popover is the one that was updated + if ( + control->object + != + ((WfWpControl*)(widget->popover->get_child()))->object + ){ + widget->face = control->copy(); + widget->popover->set_child(*widget->face); + } + else { + // update the face’s values ; else because the WfWpControl constructor already syncs the values + widget->face->set_btn_status_no_callbk(mute); + widget->face->set_scale_target_value(std::cbrt(volume)); // see line x + widget->update_icon(); + } + + // if it was hidden, show it + if (!widget->popover->is_visible()){ + widget->button->set_active(true); + } + + // in all cases, (re-)schedule hiding + widget->check_set_popover_timeout(); + } +} + +void WpCommon::on_default_nodes_changed(gpointer default_nodes_api, gpointer data){ + std::cout << "default nodes changed\n"; + std::vector defaults; + guint32 id = SPA_ID_INVALID; + + for (guint32 i = 0; i < G_N_ELEMENTS(DEFAULT_NODE_MEDIA_CLASSES); i++) { + g_signal_emit_by_name(default_nodes_api, "get-default-node", DEFAULT_NODE_MEDIA_CLASSES[i], &id); + if (id != SPA_ID_INVALID) { + defaults.push_back(id); + } + } + + for (auto widget : widgets){ + + for (const auto &entry : widget->objects_to_controls) { + auto obj = WP_PIPEWIRE_OBJECT(entry.first); + auto ctrl = (WfWpControlDevice*) entry.second; + + guint32 bound_id = wp_proxy_get_bound_id(WP_PROXY(obj)); + if (bound_id == SPA_ID_INVALID){ + continue; + } + + bool is_default = std::any_of(defaults.begin(), defaults.end(), [bound_id](guint32 def_id){ return def_id == bound_id; }); + if (is_default) { + ctrl->set_def_status_no_callbk(true); + continue; + } + + // if the control is not for a sink or source (non WfWpControlDevice), don’t try to set status + const gchar* type = wp_pipewire_object_get_property(obj, PW_KEY_MEDIA_CLASS); + if (g_strcmp0(type, "Audio/Sink") == 0 || g_strcmp0(type, "Audio/Source") == 0){ + ctrl->set_def_status_no_callbk(false); + } + } + } +} + +void WpCommon::on_object_removed(WpObjectManager* manager, gpointer object, gpointer data){ + for (auto widget : widgets){ + auto it = widget->objects_to_controls.find((WpPipewireObject*)object); + if (it == widget->objects_to_controls.end()){ + // shouldn’t happen, but checking to avoid weird situations + return; + } + WfWpControl* control = it->second; + Gtk::Box* box = (Gtk::Box*)control->get_parent(); + box->remove(*control); + + delete control; + widget->objects_to_controls.erase((WpPipewireObject*)object); + } +} + +WayfireWireplumber::~WayfireWireplumber(){ + WpCommon::widgets.erase(std::find(WpCommon::widgets.begin(), WpCommon::widgets.end(), this)); + gtk_widget_unparent(GTK_WIDGET(popover->gobj())); + popover_timeout.disconnect(); +} diff --git a/src/panel/widgets/wireplumber.hpp b/src/panel/widgets/wireplumber.hpp new file mode 100644 index 00000000..2d419535 --- /dev/null +++ b/src/panel/widgets/wireplumber.hpp @@ -0,0 +1,130 @@ +#ifndef WIDGETS_PIPEWIRE_HPP +#define WIDGETS_PIPEWIRE_HPP + +#include "../widget.hpp" +#include "gtkmm/togglebutton.h" +#include "wf-popover.hpp" +#include "wf-shell-app.hpp" +#include "wp/proxy-interfaces.h" +#include +#include +extern "C" { + #include + #include + #include +} +#include +#include + +class WayfireWireplumber; + +class WfWpControl : public Gtk::Grid{ + // Custom grid to facilitate handling + protected: + WayfireAnimatedScale scale; + Gtk::Label label; + Gtk::ToggleButton button; + Gtk::Image volume_icon; + sigc::connection mute_conn; + WayfireWireplumber* parent; + + public: + WfWpControl(WpPipewireObject* obj, WayfireWireplumber* parent_widget); + WpPipewireObject* object; + Glib::ustring get_name(); + void set_btn_status_no_callbk(bool state); + void set_scale_target_value(double volume); + double get_scale_target_value(); + void update_icon(); + bool is_muted(); + WfWpControl* copy(); +}; + +// todo : add a WfWpControlStream class that presents a dropdown to select which sink a stream goes to + +// sinks and sources +class WfWpControlDevice : public WfWpControl{ + private: + // todo : add port stuff + sigc::connection def_conn; + Gtk::Image is_def_icon; + + public: + WfWpControlDevice(WpPipewireObject* obj, WayfireWireplumber* parent_widget); + Gtk::ToggleButton default_btn; + void set_def_status_no_callbk(bool state); + WfWpControlDevice* copy(); +}; + +class wayfire_config; +class WayfireWireplumber : public WayfireWidget{ + private: + Gtk::Image main_image; + + WfOption timeout{"panel/volume_display_timeout"}; + WfOption scroll_sensitivity{"panel/volume_scroll_sensitivity"}; + + void on_volume_value_changed(); + bool on_popover_timeout(int timer); + + gulong notify_volume_signal = 0; + gulong notify_is_muted_signal = 0; + gulong notify_default_sink_changed = 0; + sigc::connection popover_timeout; + sigc::connection volume_changed_signal; + + public: + void init(Gtk::Box *container) override; + + std::unique_ptr button; + Gtk::Popover* popover; + + /* + the « face » is the representation of the audio channel that shows it’s + volume level on the widget icon and is concerned by the quick actions. + currently, it is the last channel to have been updated. + TODO : make this configurable, other ideas : default source/sink. Pinning ? + */ + WfWpControl* face; + + Gtk::Box master_box, sinks_box, sources_box, streams_box; + // TODO : add a category for stuff that listens to an audio source + + std::map objects_to_controls; + + /** Update the icon based on volume and muted state of the face widget */ + void update_icon(); + + /** Called when the volume changed from outside of the widget */ + void on_volume_changed_external(); + + /** + * Check whether the popover should be auto-hidden, and if yes, start + * a timer to hide it + */ + void check_set_popover_timeout(); + + virtual ~WayfireWireplumber(); +}; + +namespace WpCommon{ + static WpCore* core = nullptr; + static WpObjectManager* object_manager; + static WpPlugin* mixer_api; + static WpPlugin* default_nodes_api; + + static std::vector widgets; + + static void init_wp(); + static void catch_up_to_current_state(WayfireWireplumber* widget); + static void on_mixer_plugin_loaded(WpCore* core, GAsyncResult* res, gpointer data); + static void on_default_nodes_plugin_loaded(WpCore* core, GAsyncResult* res, gpointer data); + static void on_all_plugins_loaded(); + static void on_om_installed(WpObjectManager* manager, gpointer data); + static void on_object_added(WpObjectManager* manager, gpointer object, gpointer data); + static void on_mixer_changed(gpointer mixer_api, guint id, gpointer data); + static void on_default_nodes_changed(gpointer default_nodes_api, gpointer data); + static void on_object_removed(WpObjectManager* manager, gpointer node, gpointer data); +} + +#endif // WIDGETS_PIPEWIRE_HPP diff --git a/src/util/animated_scale.cpp b/src/util/animated_scale.cpp new file mode 100644 index 00000000..2594982d --- /dev/null +++ b/src/util/animated_scale.cpp @@ -0,0 +1,43 @@ +#include +#include +#include "gtk-utils.hpp" +#include "animated_scale.hpp" + +WayfireAnimatedScale::WayfireAnimatedScale() +{ + value_changed = this->signal_value_changed().connect([=] () + { + this->current_volume.animate(this->get_value(), this->get_value()); + if (this->user_changed_callback) + { + this->user_changed_callback(); + } + }); +} + +void WayfireAnimatedScale::set_target_value(double value) +{ + this->current_volume.animate(value); + add_tick_callback(sigc::mem_fun(*this, &WayfireAnimatedScale::update_animation)); +} + +gboolean WayfireAnimatedScale::update_animation(Glib::RefPtr frame_clock) +{ + value_changed.block(); + this->set_value(this->current_volume); + value_changed.unblock(); + // Once we've finished fading, stop this callback + return this->current_volume.running() ? G_SOURCE_CONTINUE : G_SOURCE_REMOVE; +} + +double WayfireAnimatedScale::get_target_value() const +{ + return this->current_volume.end; +} + +void WayfireAnimatedScale::set_user_changed_callback( + std::function callback) +{ + this->user_changed_callback = callback; +} + diff --git a/src/util/animated_scale.hpp b/src/util/animated_scale.hpp new file mode 100644 index 00000000..271abb8d --- /dev/null +++ b/src/util/animated_scale.hpp @@ -0,0 +1,32 @@ +#ifndef ANIMATED_SCALE_HPP +#define ANIMATED_SCALE_HPP + +#include "wf-popover.hpp" +#include +#include +#include + +/** + * A custom scale which animates transitions when its value is + * changed programatically. + */ +class WayfireAnimatedScale : public Gtk::Scale +{ + wf::animation::simple_animation_t current_volume{wf::create_option(200)}; + sigc::connection value_changed; + std::function user_changed_callback; + + public: + WayfireAnimatedScale(); + + /* Gets the current target value */ + double get_target_value() const; + /* Set a target value to animate towards */ + void set_target_value(double value); + /** Set the callback when the user changes the scale value */ + void set_user_changed_callback(std::function callback); + /** Callback to animate volume control */ + gboolean update_animation(Glib::RefPtr clock); +}; + +#endif // ANIMATED_SCALE_HPP diff --git a/src/util/meson.build b/src/util/meson.build index 552635be..ecb44f75 100644 --- a/src/util/meson.build +++ b/src/util/meson.build @@ -6,6 +6,7 @@ util = static_library( 'wf-autohide-window.cpp', 'wf-popover.cpp', 'css-config.cpp', + 'animated_scale.cpp' ], dependencies: [wf_protos, wayland_client, gtkmm, wfconfig, libinotify, gtklayershell], ) From 43f4a71fe57432b84a1cf52ba0121d22c73eb0d2 Mon Sep 17 00:00:00 2001 From: Hue Date: Sun, 2 Nov 2025 13:00:12 +0100 Subject: [PATCH 02/37] Added scroll control in full mixer --- src/panel/widgets/wireplumber.cpp | 29 ++++++++++++++++++++++++++--- src/panel/widgets/wireplumber.hpp | 1 + 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/panel/widgets/wireplumber.cpp b/src/panel/widgets/wireplumber.cpp index f20bda3f..4367d5fa 100644 --- a/src/panel/widgets/wireplumber.cpp +++ b/src/panel/widgets/wireplumber.cpp @@ -107,6 +107,31 @@ WfWpControl::WfWpControl(WpPipewireObject* obj, WayfireWireplumber* parent_widge } ); + auto scroll_gesture = Gtk::EventControllerScroll::create(); + scroll_gesture->signal_scroll().connect([=] (double dx, double dy) + { + dy = dy * -1; // for the same scrolling as volume widget, which we will agree it is more intuitive for more people. TODO : make this configurable + double change = 0; + if (scroll_gesture->get_unit() == Gdk::ScrollUnit::WHEEL) + { + // +- number of clicks. + change = (dy * scroll_sensitivity) / 10; + } else + { + // Number of pixels expected to have scrolled. usually in 100s + change = (dy * scroll_sensitivity) / 100; + } + guint32 id = wp_proxy_get_bound_id(WP_PROXY(object)); + GVariantBuilder gvb = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE_VARDICT); + g_variant_builder_add(&gvb, "{sv}", "volume", g_variant_new_double(std::pow(get_scale_target_value() + change, 3))); // see line x + GVariant* v = g_variant_builder_end(&gvb); + gboolean res FALSE; + g_signal_emit_by_name(WpCommon::mixer_api, "set-volume", id, v, &res); + return true; + }, true); + scroll_gesture->set_flags(Gtk::EventControllerScroll::Flags::VERTICAL); + add_controller(scroll_gesture); + // initialise the values GVariant* v = NULL; g_signal_emit_by_name(WpCommon::mixer_api, "get-volume", id, &v); @@ -206,7 +231,6 @@ WfWpControlDevice::WfWpControlDevice(WpPipewireObject* obj, WayfireWireplumber* } } ); - } void WfWpControlDevice::set_def_status_no_callbk(bool state){ @@ -331,6 +355,7 @@ void WayfireWireplumber::init(Gtk::Box *container){ WpCommon::catch_up_to_current_state(this); return; } + WpCommon::init_wp(); } @@ -417,7 +442,6 @@ void WpCommon::catch_up_to_current_state(WayfireWireplumber *widget){ on_object_added(object_manager, (gpointer)g_value_get_object(&item), &widget); g_value_unset(&item); } - } void WpCommon::on_mixer_plugin_loaded(WpCore* core, GAsyncResult* res, void* data){ @@ -437,7 +461,6 @@ void WpCommon::on_mixer_plugin_loaded(WpCore* core, GAsyncResult* res, void* dat (GAsyncReadyCallback)on_default_nodes_plugin_loaded, NULL ); - } void WpCommon::on_default_nodes_plugin_loaded(WpCore* core, GAsyncResult* res, void* data){ diff --git a/src/panel/widgets/wireplumber.hpp b/src/panel/widgets/wireplumber.hpp index 2d419535..9a44f1e6 100644 --- a/src/panel/widgets/wireplumber.hpp +++ b/src/panel/widgets/wireplumber.hpp @@ -27,6 +27,7 @@ class WfWpControl : public Gtk::Grid{ Gtk::Image volume_icon; sigc::connection mute_conn; WayfireWireplumber* parent; + WfOption scroll_sensitivity{"panel/volume_scroll_sensitivity"}; public: WfWpControl(WpPipewireObject* obj, WayfireWireplumber* parent_widget); From f8d67cd636ce93ec6461d4f7bd4a6da2d74a9db2 Mon Sep 17 00:00:00 2001 From: Hue Date: Sun, 2 Nov 2025 13:11:07 +0100 Subject: [PATCH 03/37] Added middle click to mute --- src/panel/widgets/wireplumber.cpp | 16 ++++++++++++++++ src/panel/widgets/wireplumber.hpp | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/panel/widgets/wireplumber.cpp b/src/panel/widgets/wireplumber.cpp index 4367d5fa..92c50ab2 100644 --- a/src/panel/widgets/wireplumber.cpp +++ b/src/panel/widgets/wireplumber.cpp @@ -132,6 +132,14 @@ WfWpControl::WfWpControl(WpPipewireObject* obj, WayfireWireplumber* parent_widge scroll_gesture->set_flags(Gtk::EventControllerScroll::Flags::VERTICAL); add_controller(scroll_gesture); + auto middle_click_gesture = Gtk::GestureClick::create(); + middle_click_gesture->set_button(2); + middle_click_gesture->signal_pressed().connect([=] (int count, double x, double y) + { + button.set_active(!button.get_active()); + }); + add_controller(middle_click_gesture); + // initialise the values GVariant* v = NULL; g_signal_emit_by_name(WpCommon::mixer_api, "get-volume", id, &v); @@ -307,6 +315,14 @@ void WayfireWireplumber::init(Gtk::Box *container){ scroll_gesture->set_flags(Gtk::EventControllerScroll::Flags::VERTICAL); button->add_controller(scroll_gesture); + auto middle_click_gesture = Gtk::GestureClick::create(); + middle_click_gesture->set_button(2); + middle_click_gesture->signal_pressed().connect([=] (int count, double x, double y) + { + face->button.set_active(!face->button.get_active()); + }); + button->add_controller(middle_click_gesture); + // boxes hierarchy and labeling Gtk::Orientation r1, r2; // if (config::is_horizontal){ // todo : make this configurable diff --git a/src/panel/widgets/wireplumber.hpp b/src/panel/widgets/wireplumber.hpp index 9a44f1e6..df917054 100644 --- a/src/panel/widgets/wireplumber.hpp +++ b/src/panel/widgets/wireplumber.hpp @@ -23,7 +23,6 @@ class WfWpControl : public Gtk::Grid{ protected: WayfireAnimatedScale scale; Gtk::Label label; - Gtk::ToggleButton button; Gtk::Image volume_icon; sigc::connection mute_conn; WayfireWireplumber* parent; @@ -33,6 +32,7 @@ class WfWpControl : public Gtk::Grid{ WfWpControl(WpPipewireObject* obj, WayfireWireplumber* parent_widget); WpPipewireObject* object; Glib::ustring get_name(); + Gtk::ToggleButton button; void set_btn_status_no_callbk(bool state); void set_scale_target_value(double volume); double get_scale_target_value(); From 87a03076100b75e6d411a1c6f357a63de154fdc6 Mon Sep 17 00:00:00 2001 From: Hue Date: Sun, 2 Nov 2025 22:53:15 +0100 Subject: [PATCH 04/37] add configuration options for wireplumber widget --- metadata/panel.xml | 82 +++++++++ src/panel/widgets/wireplumber.cpp | 292 ++++++++++++++++++++++++------ src/panel/widgets/wireplumber.hpp | 26 ++- 3 files changed, 337 insertions(+), 63 deletions(-) diff --git a/metadata/panel.xml b/metadata/panel.xml index bfe391f0..abab05bc 100644 --- a/metadata/panel.xml +++ b/metadata/panel.xml @@ -249,6 +249,88 @@ + <_short>Wireplumber mixer + + + + + + + + + + <_short>Notifications <_short>Wireplumber mixer + - - - diff --git a/src/panel/widgets/wireplumber.cpp b/src/panel/widgets/wireplumber.cpp index 364f3c82..bc794dda 100644 --- a/src/panel/widgets/wireplumber.cpp +++ b/src/panel/widgets/wireplumber.cpp @@ -130,10 +130,10 @@ WfWpControl::WfWpControl(WpPipewireObject *obj, WayfireWireplumber *parent_widge auto scroll_gesture = Gtk::EventControllerScroll::create(); scroll_gesture->signal_scroll().connect([=] (double dx, double dy) { - dy = parent->invert_scroll ? dy * -1 : dy; // for the same scrolling as volume widget, which we will + dy = parent->invert_scroll ? dy : dy * -1; // for the same scrolling as volume widget, which we will // agree it is more intuitive for more people double change = 0; - const double SCROLL_MULT = 20; // corrects the scrolling to have the default scroll sensitivity as 1 + const double SCROLL_MULT = 0.2; // corrects the scrolling to have the default scroll sensitivity as 1 if (scroll_gesture->get_unit() == Gdk::ScrollUnit::WHEEL) { // +- number of clicks. @@ -405,11 +405,11 @@ void WayfireWireplumber::reload_config() face_choice = FaceChoice::LAST_CHANGE; } - auto left_click_gesture = Gtk::GestureClick::create(); + static auto left_click_gesture = Gtk::GestureClick::create(); left_click_gesture->set_button(1); - auto right_click_gesture = Gtk::GestureClick::create(); + static auto right_click_gesture = Gtk::GestureClick::create(); right_click_gesture->set_button(3); - auto middle_click_gesture = Gtk::GestureClick::create(); + static auto middle_click_gesture = Gtk::GestureClick::create(); middle_click_gesture->set_button(2); button->remove_controller(left_click_gesture); @@ -572,10 +572,10 @@ void WayfireWireplumber::init(Gtk::Box *container) return false; // no face means we have nothing to change by scrolling } - dy = invert_scroll ? dy * -1 : dy; // for the same scrolling as volume widget, which we will agree it + dy = invert_scroll ? dy : dy * -1; // for the same scrolling as volume widget, which we will agree it // is more intuitive for more people double change = 0; - const double SCROLL_MULT = 20; // corrects the scrolling to have the default scroll sensitivity as 1 + const double SCROLL_MULT = 0.2; // corrects the scrolling to have the default scroll sensitivity as 1 if (scroll_gesture->get_unit() == Gdk::ScrollUnit::WHEEL) { // +- number of clicks. From d1dca46c7b114de81a5c769abdee6f6eaa99b77c Mon Sep 17 00:00:00 2001 From: Hue Date: Mon, 3 Nov 2025 13:15:21 +0100 Subject: [PATCH 16/37] cleaned up a bit middle click to mute --- src/panel/widgets/wireplumber.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/panel/widgets/wireplumber.cpp b/src/panel/widgets/wireplumber.cpp index bc794dda..f6d7658c 100644 --- a/src/panel/widgets/wireplumber.cpp +++ b/src/panel/widgets/wireplumber.cpp @@ -230,12 +230,15 @@ void WfWpControl::update_gestures() WfOption str_wp_right_click_action{"panel/wp_right_click_action"}; WfOption str_wp_middle_click_action{"panel/wp_middle_click_action"}; - static auto right_click_mute = Gtk::GestureClick::create(); - right_click_mute->set_button(3); - right_click_mute->signal_pressed().connect([=] (int count, double x, double y) + auto mute_action = + [&] (int count, double x, double y) { button.set_active(!button.get_active()); - }); + }; + + static auto right_click_mute = Gtk::GestureClick::create(); + right_click_mute->set_button(3); + right_click_mute->signal_pressed().connect(mute_action); static auto middle_click_mute = Gtk::GestureClick::create(); middle_click_mute->set_button(2); From d64f5ac15fafdd94c9b61f433ed00256587bba3d Mon Sep 17 00:00:00 2001 From: Hue Date: Mon, 3 Nov 2025 13:18:36 +0100 Subject: [PATCH 17/37] fixed improper naming of on -> handle_config_reload --- src/panel/widgets/wireplumber.cpp | 6 +++--- src/panel/widgets/wireplumber.hpp | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/panel/widgets/wireplumber.cpp b/src/panel/widgets/wireplumber.cpp index f6d7658c..4583a914 100644 --- a/src/panel/widgets/wireplumber.cpp +++ b/src/panel/widgets/wireplumber.cpp @@ -261,7 +261,7 @@ void WfWpControl::update_gestures() } } -void WfWpControl::on_config_reload() +void WfWpControl::handle_config_reload() { update_gestures(); } @@ -544,12 +544,12 @@ void WayfireWireplumber::reload_config() } } -void WayfireWireplumber::on_config_reload() +void WayfireWireplumber::handle_config_reload() { reload_config(); for (auto [name, control] : objects_to_controls) { - control->on_config_reload(); + control->handle_config_reload(); } } diff --git a/src/panel/widgets/wireplumber.hpp b/src/panel/widgets/wireplumber.hpp index 709b4f68..b5cee527 100644 --- a/src/panel/widgets/wireplumber.hpp +++ b/src/panel/widgets/wireplumber.hpp @@ -42,7 +42,7 @@ class WfWpControl : public Gtk::Grid bool is_muted(); void update_gestures(); - void on_config_reload(); + void handle_config_reload(); WfWpControl *copy(); }; @@ -122,7 +122,7 @@ class WayfireWireplumber : public WayfireWidget void check_set_popover_timeout(); void reload_config(); - void on_config_reload(); + void handle_config_reload(); virtual ~WayfireWireplumber(); }; From fd41700eeaeaee852dccdc34efe9b5ed463a8284 Mon Sep 17 00:00:00 2001 From: Hue Date: Mon, 3 Nov 2025 13:22:29 +0100 Subject: [PATCH 18/37] removed obsolete functions --- src/panel/widgets/wireplumber.cpp | 37 ------------------------------- src/panel/widgets/wireplumber.hpp | 4 ---- 2 files changed, 41 deletions(-) diff --git a/src/panel/widgets/wireplumber.cpp b/src/panel/widgets/wireplumber.cpp index 4583a914..0c891f5a 100644 --- a/src/panel/widgets/wireplumber.cpp +++ b/src/panel/widgets/wireplumber.cpp @@ -346,43 +346,6 @@ void WayfireWireplumber::check_set_popover_timeout() &WayfireWireplumber::on_popover_timeout), 0), timeout * 1000); } -void WayfireWireplumber::show_mixer_action() -{ - // lambdas for the click actions - std::cout << "here\n"; - if (popover->get_child() != (Gtk::Widget*)&master_box) - { - popover->set_child(master_box); - popover_timeout.disconnect(); - } -} - -void WayfireWireplumber::show_face_action() -{ - std::cout << "there\n"; - if (!face) - { - return; // no face means we have nothing to show - } - - if (popover->get_child() != face) - { - popover->set_child(*face); - popover_timeout.disconnect(); - } -} - -void WayfireWireplumber::mute_face_action() -{ - std::cout << "everywhere\n"; - if (!face) - { - return; // no face means we have nothing to change by clicking - } - - face->button.set_active(!face->button.get_active()); -} - void WayfireWireplumber::reload_config() { WfOption str_face_choice{"panel/wp_face_choice"}; diff --git a/src/panel/widgets/wireplumber.hpp b/src/panel/widgets/wireplumber.hpp index b5cee527..5f986f3a 100644 --- a/src/panel/widgets/wireplumber.hpp +++ b/src/panel/widgets/wireplumber.hpp @@ -72,10 +72,6 @@ class WayfireWireplumber : public WayfireWidget WfOption timeout{"panel/wp_display_timeout"}; - void show_mixer_action(); - void show_face_action(); - void mute_face_action(); - void on_volume_value_changed(); bool on_popover_timeout(int timer); From 679f144d96a8d7341962afc5dac19281009cc91e Mon Sep 17 00:00:00 2001 From: Hue Date: Mon, 3 Nov 2025 13:36:46 +0100 Subject: [PATCH 19/37] =?UTF-8?q?fixed=20left=20click=20actions=20on=20wid?= =?UTF-8?q?get=C2=A0;=20comments?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/panel/widgets/wireplumber.cpp | 37 +++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/src/panel/widgets/wireplumber.cpp b/src/panel/widgets/wireplumber.cpp index 0c891f5a..eaede8d9 100644 --- a/src/panel/widgets/wireplumber.cpp +++ b/src/panel/widgets/wireplumber.cpp @@ -121,7 +121,7 @@ WfWpControl::WfWpControl(WpPipewireObject *obj, WayfireWireplumber *parent_widge { GVariantBuilder gvb = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE_VARDICT); g_variant_builder_add(&gvb, "{sv}", "volume", - g_variant_new_double(std::pow(scale.get_target_value(), 3))); // see line 612 + g_variant_new_double(std::pow(scale.get_target_value(), 3))); // see on_mixer_plugin_loaded GVariant *v = g_variant_builder_end(&gvb); gboolean res FALSE; g_signal_emit_by_name(WpCommon::mixer_api, "set-volume", id, v, &res); @@ -147,7 +147,8 @@ WfWpControl::WfWpControl(WpPipewireObject *obj, WayfireWireplumber *parent_widge guint32 id = wp_proxy_get_bound_id(WP_PROXY(object)); GVariantBuilder gvb = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE_VARDICT); g_variant_builder_add(&gvb, "{sv}", "volume", - g_variant_new_double(std::pow(get_scale_target_value() + change, 3))); // see line 612 + g_variant_new_double(std::pow(get_scale_target_value() + change, 3))); // see + // on_mixer_plugin_loaded GVariant *v = g_variant_builder_end(&gvb); gboolean res FALSE; g_signal_emit_by_name(WpCommon::mixer_api, "set-volume", id, v, &res); @@ -172,7 +173,7 @@ WfWpControl::WfWpControl(WpPipewireObject *obj, WayfireWireplumber *parent_widge g_variant_lookup(v, "mute", "b", &mute); g_clear_pointer(&v, g_variant_unref); set_btn_status_no_callbk(mute); - set_scale_target_value(std::cbrt(volume)); // see line 612 + set_scale_target_value(std::cbrt(volume)); // see on_mixer_plugin_loaded update_icon(); } @@ -433,7 +434,17 @@ void WayfireWireplumber::reload_config() if ((std::string)str_wp_left_click_action == (std::string)"show_mixer") { left_click_gesture->signal_pressed().connect( - [&] (int c, double x, double y) {popover->set_child(master_box);}); + [&] (int c, double x, double y) + { + if (popover->get_child() != (Gtk::Widget*)&master_box) + { + popover->set_child(master_box); + // popdown so that when the click is processed, the popover is down, and thus pops up + // not the prettiest result, as it visibly closes instead of just replacing, but i’m not sure + // how to make it better + popover->popdown(); + } + }); button->add_controller(left_click_gesture); } @@ -447,7 +458,14 @@ void WayfireWireplumber::reload_config() return; } - popover->set_child(*face); + if (popover->get_child() != face) + { + popover->set_child(*face); + // popdown so that when the click is processed, the popover is down, and thus pops up + // not the prettiest result, as it visibly closes instead of just replacing, but i’m not sure + // how to make it better + popover->popdown(); + } }); button->add_controller(left_click_gesture); } @@ -555,7 +573,8 @@ void WayfireWireplumber::init(Gtk::Box *container) guint32 id = wp_proxy_get_bound_id(WP_PROXY(face->object)); GVariantBuilder gvb = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE_VARDICT); g_variant_builder_add(&gvb, "{sv}", "volume", - g_variant_new_double(std::pow(face->get_scale_target_value() + change, 3))); // see line 612 + g_variant_new_double(std::pow(face->get_scale_target_value() + change, 3))); // see + // on_mixer_plugin_loaded GVariant *v = g_variant_builder_end(&gvb); gboolean res FALSE; g_signal_emit_by_name(WpCommon::mixer_api, "set-volume", id, v, &res); @@ -821,7 +840,7 @@ void WpCommon::on_mixer_changed(gpointer mixer_api, guint id, gpointer data) } control->set_btn_status_no_callbk(mute); - control->set_scale_target_value(std::cbrt(volume)); // see line 612 + control->set_scale_target_value(std::cbrt(volume)); // see on_mixer_plugin_loaded control->update_icon(); if ((widget->face_choice == FaceChoice::DEFAULT_SINK) @@ -834,7 +853,7 @@ void WpCommon::on_mixer_changed(gpointer mixer_api, guint id, gpointer data) ((WfWpControl*)(widget->popover->get_child()))->object) { widget->face->set_btn_status_no_callbk(mute); - widget->face->set_scale_target_value(std::cbrt(volume)); // see line 612 + widget->face->set_scale_target_value(std::cbrt(volume)); // see on_mixer_plugin_loaded widget->update_icon(); } @@ -882,7 +901,7 @@ void WpCommon::on_mixer_changed(gpointer mixer_api, guint id, gpointer data) { // update the face’s values ; else because the WfWpControl constructor already syncs the values widget->face->set_btn_status_no_callbk(mute); - widget->face->set_scale_target_value(std::cbrt(volume)); // see line 612 + widget->face->set_scale_target_value(std::cbrt(volume)); // see on_mixer_plugin_loaded widget->update_icon(); } From 8f61bcc73847bd6f4babdde1c5653a5688c7add0 Mon Sep 17 00:00:00 2001 From: Hue Date: Tue, 4 Nov 2025 17:17:54 +0100 Subject: [PATCH 20/37] added deselection guard for default + explanation --- src/panel/widgets/wireplumber.cpp | 9 ++++++++- src/panel/widgets/wireplumber.hpp | 1 - 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/panel/widgets/wireplumber.cpp b/src/panel/widgets/wireplumber.cpp index eaede8d9..f3dcca1d 100644 --- a/src/panel/widgets/wireplumber.cpp +++ b/src/panel/widgets/wireplumber.cpp @@ -285,9 +285,16 @@ WfWpControlDevice::WfWpControlDevice(WpPipewireObject *obj, is_def_icon.set_from_icon_name("emblem-default"); default_btn.set_child(is_def_icon); + // we are not using ToggleButton groups because on_default_nodes_changed will be called anyway to set the status of all devices def_conn = default_btn.signal_clicked().connect( - [proxy] () + [this, proxy] () { + // keep the button down when it is selected to prevent inconsistency of visuals with actual status + if (default_btn.get_active() == false) + { + set_def_status_no_callbk(true); + return; + } const gchar *media_class = wp_pipewire_object_get_property( WP_PIPEWIRE_OBJECT(proxy), PW_KEY_MEDIA_CLASS); diff --git a/src/panel/widgets/wireplumber.hpp b/src/panel/widgets/wireplumber.hpp index 5f986f3a..63d60734 100644 --- a/src/panel/widgets/wireplumber.hpp +++ b/src/panel/widgets/wireplumber.hpp @@ -53,7 +53,6 @@ class WfWpControl : public Gtk::Grid class WfWpControlDevice : public WfWpControl { private: - // todo : add port stuff sigc::connection def_conn; Gtk::Image is_def_icon; From 43281bace93d66883ffc195cde713781a3cb8a14 Mon Sep 17 00:00:00 2001 From: Hue Date: Wed, 5 Nov 2025 00:51:56 +0100 Subject: [PATCH 21/37] =?UTF-8?q?adjust=20logic=20of=20updates=20in=20on?= =?UTF-8?q?=5Fmixer=5Fchanged=C2=A0;=20removed=20unused=20enum=C2=A0;=20co?= =?UTF-8?q?mments?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/panel/widgets/wireplumber.cpp | 71 +++++++++---------------------- src/panel/widgets/wireplumber.hpp | 1 - 2 files changed, 19 insertions(+), 53 deletions(-) diff --git a/src/panel/widgets/wireplumber.cpp b/src/panel/widgets/wireplumber.cpp index f3dcca1d..bc141663 100644 --- a/src/panel/widgets/wireplumber.cpp +++ b/src/panel/widgets/wireplumber.cpp @@ -33,13 +33,6 @@ enum class FaceChoice DEFAULT_SOURCE, }; -enum class ClickAction -{ - SHOW_MIXER, - SHOW_FACE, - MUTE_FACE, -}; - static const gchar *DEFAULT_NODE_MEDIA_CLASSES[] = { "Audio/Sink", "Audio/Source", @@ -285,7 +278,8 @@ WfWpControlDevice::WfWpControlDevice(WpPipewireObject *obj, is_def_icon.set_from_icon_name("emblem-default"); default_btn.set_child(is_def_icon); - // we are not using ToggleButton groups because on_default_nodes_changed will be called anyway to set the status of all devices + // we are not using ToggleButton groups because on_default_nodes_changed + // will be called anyway to set the status of all devices def_conn = default_btn.signal_clicked().connect( [this, proxy] () { @@ -850,37 +844,27 @@ void WpCommon::on_mixer_changed(gpointer mixer_api, guint id, gpointer data) control->set_scale_target_value(std::cbrt(volume)); // see on_mixer_plugin_loaded control->update_icon(); - if ((widget->face_choice == FaceChoice::DEFAULT_SINK) - || - (widget->face_choice == FaceChoice::DEFAULT_SOURCE)) + // correct the face + // ensure the control in the popover is the one that was updated + if ( + control->object + != + ((WfWpControl*)(widget->popover->get_child()))->object) { - if ( - control->object - != - ((WfWpControl*)(widget->popover->get_child()))->object) + // in the other two cases, the face choice, if existant, is already in the face + if (widget->face_choice == FaceChoice::LAST_CHANGE) { - widget->face->set_btn_status_no_callbk(mute); - widget->face->set_scale_target_value(std::cbrt(volume)); // see on_mixer_plugin_loaded - widget->update_icon(); + widget->face = control->copy(); } - - if (!widget->popup_on_change) - { - return; - } - - // if it was hidden, show it - if (!widget->popover->is_visible()) - { - widget->button->set_active(true); - } - - return; - // in all cases, (re-)schedule hiding - widget->check_set_popover_timeout(); + } else + { + // update the face’s values ; note the WfWpControl constructor already syncs the values + widget->face->set_btn_status_no_callbk(mute); + widget->face->set_scale_target_value(std::cbrt(volume)); // see on_mixer_plugin_loaded } + widget->update_icon(); - // if we have the *full* mixer in the popover + // if we have the full mixer in the popover if ((Gtk::Widget*)&(widget->master_box) == (Gtk::Widget*)widget->popover->get_child()) @@ -891,27 +875,10 @@ void WpCommon::on_mixer_changed(gpointer mixer_api, guint id, gpointer data) return; } - // if hidden, replace by the popover - widget->face = control->copy(); + // if hidden, replace by the face widget->popover->set_child(*widget->face); } - // ensure the control in the popover is the one that was updated - if ( - control->object - != - ((WfWpControl*)(widget->popover->get_child()))->object) - { - widget->face = control->copy(); - widget->popover->set_child(*widget->face); - } else - { - // update the face’s values ; else because the WfWpControl constructor already syncs the values - widget->face->set_btn_status_no_callbk(mute); - widget->face->set_scale_target_value(std::cbrt(volume)); // see on_mixer_plugin_loaded - widget->update_icon(); - } - // if it was hidden, show it if (widget->popup_on_change && !widget->popover->is_visible()) { diff --git a/src/panel/widgets/wireplumber.hpp b/src/panel/widgets/wireplumber.hpp index 63d60734..270e793f 100644 --- a/src/panel/widgets/wireplumber.hpp +++ b/src/panel/widgets/wireplumber.hpp @@ -15,7 +15,6 @@ extern "C" { #include enum class FaceChoice; -enum class ClickAction; class WayfireWireplumber; From 1afacc0380dd9f03cf9469195f564fa80a018fb7 Mon Sep 17 00:00:00 2001 From: Hue Date: Thu, 6 Nov 2025 14:57:42 +0100 Subject: [PATCH 22/37] cleaup --- src/panel/widgets/wireplumber.cpp | 102 ++++++++++++++++-------------- src/panel/widgets/wireplumber.hpp | 4 +- 2 files changed, 55 insertions(+), 51 deletions(-) diff --git a/src/panel/widgets/wireplumber.cpp b/src/panel/widgets/wireplumber.cpp index bc141663..203a646d 100644 --- a/src/panel/widgets/wireplumber.cpp +++ b/src/panel/widgets/wireplumber.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -224,7 +225,7 @@ void WfWpControl::update_gestures() WfOption str_wp_right_click_action{"panel/wp_right_click_action"}; WfOption str_wp_middle_click_action{"panel/wp_middle_click_action"}; - auto mute_action = + static auto mute_action = [&] (int count, double x, double y) { button.set_active(!button.get_active()); @@ -380,12 +381,24 @@ void WayfireWireplumber::reload_config() static auto middle_click_gesture = Gtk::GestureClick::create(); middle_click_gesture->set_button(2); - button->remove_controller(left_click_gesture); - button->remove_controller(right_click_gesture); - button->remove_controller(middle_click_gesture); + // run only the first time around + static auto done = false; + if (done) + { + done = true; + button->add_controller(left_click_gesture); + button->add_controller(right_click_gesture); + button->add_controller(middle_click_gesture); + } + + static auto dud = [](int c, double x, double y){}; + left_click_gesture->signal_pressed().connect(dud); // dud + middle_click_gesture->signal_pressed().connect(dud); // dud + right_click_gesture->signal_pressed().connect(dud); // dud - auto show_mixer_action = [&] (int count, double x, double y) + static auto show_mixer_action = [&] (int c, double x, double y) { + std::cout << "here\n"; if ((popover->get_child() == (Gtk::Widget*)&master_box) && popover->is_visible()) { popover->popdown(); @@ -404,8 +417,9 @@ void WayfireWireplumber::reload_config() } }; - auto show_face_action = [&] (int count, double x, double y) + static auto show_face_action = [&] (int c, double x, double y) { + std::cout << "there\n"; if (!face) { return; // no face means we have nothing to show @@ -429,6 +443,18 @@ void WayfireWireplumber::reload_config() } }; + static auto mute_action = [&] (int c, double x, double y) + { + std::cout << "everywhere\n"; + if (!face) + { + return; // no face means we have nothing to change by clicking + } + + face->button.set_active(!face->button.get_active()); + }; + + // the left click case is a bit special, since it’s supposed to show the popover. // (this is also why the mute action is not available for the left click) // this is not the prettiest, but the alternative is way worse @@ -443,10 +469,10 @@ void WayfireWireplumber::reload_config() // popdown so that when the click is processed, the popover is down, and thus pops up // not the prettiest result, as it visibly closes instead of just replacing, but i’m not sure // how to make it better - popover->popdown(); + popover->signal_hide().emission_stop(); + // popover->popdown(); } }); - button->add_controller(left_click_gesture); } if ((std::string)str_wp_left_click_action == (std::string)"show_face") @@ -465,64 +491,40 @@ void WayfireWireplumber::reload_config() // popdown so that when the click is processed, the popover is down, and thus pops up // not the prettiest result, as it visibly closes instead of just replacing, but i’m not sure // how to make it better - popover->popdown(); + popover->signal_hide().emission_stop(); + // popover->popdown(); } }); - button->add_controller(left_click_gesture); } if ((std::string)str_wp_right_click_action == (std::string)"show_mixer") { right_click_gesture->signal_pressed().connect(show_mixer_action); - button->add_controller(right_click_gesture); } if ((std::string)str_wp_right_click_action == (std::string)"show_face") { right_click_gesture->signal_pressed().connect(show_face_action); - button->add_controller(right_click_gesture); } if ((std::string)str_wp_right_click_action == (std::string)"mute_face") { - right_click_gesture->signal_pressed().connect( - [&] (int count, double x, double y) - { - if (!face) - { - return; // no face means we have nothing to change by clicking - } - - face->button.set_active(!face->button.get_active()); - }); - button->add_controller(right_click_gesture); + right_click_gesture->signal_pressed().connect(mute_action); } if ((std::string)str_wp_middle_click_action == (std::string)"show_mixer") { middle_click_gesture->signal_pressed().connect(show_mixer_action); - button->add_controller(middle_click_gesture); } if ((std::string)str_wp_middle_click_action == (std::string)"show_face") { middle_click_gesture->signal_pressed().connect(show_face_action); - button->add_controller(middle_click_gesture); } if ((std::string)str_wp_middle_click_action == (std::string)"mute_face") { - middle_click_gesture->signal_pressed().connect( - [&] (int count, double x, double y) - { - if (!face) - { - return; // no face means we have nothing to change by clicking - } - - face->button.set_active(!face->button.get_active()); - }); - button->add_controller(middle_click_gesture); + middle_click_gesture->signal_pressed().connect(mute_action); } } @@ -618,7 +620,7 @@ void WayfireWireplumber::init(Gtk::Box *container) button->set_child(main_image); /* - * if the core is already set, we are another widget, wether on another monitor or on the same wf-panel. + * If the core is already set, we are another widget, wether on another monitor or on the same wf-panel. * We re-use the core, manager and all other objects */ @@ -805,6 +807,10 @@ void WpCommon::on_object_added(WpObjectManager *manager, gpointer object, gpoint widget->objects_to_controls.insert({obj, control}); which_box->append((Gtk::Widget&)*control); + // try to not be faceless + if (!widget->face){ + widget->face = control->copy(); + } } } @@ -813,7 +819,7 @@ void WpCommon::on_mixer_changed(gpointer mixer_api, guint id, gpointer data) // update the visual of the appropriate WfWpControl according to external changes GVariant *v = NULL; - // ask the mixer-api for the up-to-date data + // query the new data g_signal_emit_by_name(WpCommon::mixer_api, "get-volume", id, &v); if (!v) { @@ -897,6 +903,7 @@ void WpCommon::on_default_nodes_changed(gpointer default_nodes_api, gpointer dat for (guint32 i = 0; i < G_N_ELEMENTS(DEFAULT_NODE_MEDIA_CLASSES); i++) { + // query the new data g_signal_emit_by_name(default_nodes_api, "get-default-node", DEFAULT_NODE_MEDIA_CLASSES[i], &id); if (id != SPA_ID_INVALID) { @@ -935,18 +942,17 @@ void WpCommon::on_default_nodes_changed(gpointer default_nodes_api, gpointer dat ctrl->set_def_status_no_callbk(false); } - if ((widget->face_choice == FaceChoice::DEFAULT_SINK) - && - (g_strcmp0(type, "Audio/Sink") == 0)) - { - widget->face = ctrl; - } - - if ((widget->face_choice == FaceChoice::DEFAULT_SOURCE) + if ( // if the settings call for it, refresh the face + ( + ((widget->face_choice == FaceChoice::DEFAULT_SINK) && (g_strcmp0(type, "Audio/Sink") == 0)) + || + ((widget->face_choice == FaceChoice::DEFAULT_SOURCE) && (g_strcmp0(type, "Audio/Source") == 0)) + ) && - (g_strcmp0(type, "Audio/Source") == 0)) + widget->face->object == ctrl->object + ) { - widget->face = ctrl; + widget->face = ctrl->copy(); } } } diff --git a/src/panel/widgets/wireplumber.hpp b/src/panel/widgets/wireplumber.hpp index 270e793f..3a474004 100644 --- a/src/panel/widgets/wireplumber.hpp +++ b/src/panel/widgets/wireplumber.hpp @@ -20,8 +20,7 @@ class WayfireWireplumber; class WfWpControl : public Gtk::Grid { - // Custom grid to facilitate handling - + // Custom grid to facilitate handling protected: WayfireAnimatedScale scale; Gtk::Label label; @@ -87,7 +86,6 @@ class WayfireWireplumber : public WayfireWidget WfOption popup_on_change{"panel/wp_popup_on_change"}; FaceChoice face_choice; - ClickAction right_click_action, middle_click_action; std::unique_ptr button; Gtk::Popover *popover; From b006c9df59220b220feee3f8bb4fd9b31c80fb44 Mon Sep 17 00:00:00 2001 From: Hue Date: Thu, 6 Nov 2025 16:16:33 +0100 Subject: [PATCH 23/37] fix popover jank and incorrect settings reloading --- src/panel/widgets/wireplumber.cpp | 67 ++++++++++++++++--------------- src/panel/widgets/wireplumber.hpp | 1 + 2 files changed, 36 insertions(+), 32 deletions(-) diff --git a/src/panel/widgets/wireplumber.cpp b/src/panel/widgets/wireplumber.cpp index 203a646d..8738ef6d 100644 --- a/src/panel/widgets/wireplumber.cpp +++ b/src/panel/widgets/wireplumber.cpp @@ -10,9 +10,11 @@ #include "gtkmm/label.h" #include "gtkmm/separator.h" #include "gtkmm/togglebutton.h" +#include "sigc++/connection.h" #include "wp/proxy-interfaces.h" #include "wp/proxy.h" +#include #include #include "wireplumber.hpp" @@ -374,27 +376,29 @@ void WayfireWireplumber::reload_config() face_choice = FaceChoice::LAST_CHANGE; } - static auto left_click_gesture = Gtk::GestureClick::create(); - left_click_gesture->set_button(1); - static auto right_click_gesture = Gtk::GestureClick::create(); - right_click_gesture->set_button(3); - static auto middle_click_gesture = Gtk::GestureClick::create(); - middle_click_gesture->set_button(2); + static std::shared_ptr left_click_gesture, middle_click_gesture, right_click_gesture; + static sigc::connection left_conn, middle_conn, right_conn; // run only the first time around static auto done = false; - if (done) + if (!done) { done = true; + left_click_gesture = Gtk::GestureClick::create(); + right_click_gesture = Gtk::GestureClick::create(); + middle_click_gesture = Gtk::GestureClick::create(); + left_click_gesture->set_button(1); + middle_click_gesture->set_button(2); + right_click_gesture->set_button(3); button->add_controller(left_click_gesture); button->add_controller(right_click_gesture); button->add_controller(middle_click_gesture); } - - static auto dud = [](int c, double x, double y){}; - left_click_gesture->signal_pressed().connect(dud); // dud - middle_click_gesture->signal_pressed().connect(dud); // dud - right_click_gesture->signal_pressed().connect(dud); // dud + else { + left_conn.disconnect(); + middle_conn.disconnect(); + right_conn.disconnect(); + } static auto show_mixer_action = [&] (int c, double x, double y) { @@ -407,7 +411,7 @@ void WayfireWireplumber::reload_config() if (!popover->is_visible()) { - popover->popup(); + button->set_active(true); } if (popover->get_child() != (Gtk::Widget*)&master_box) @@ -433,7 +437,7 @@ void WayfireWireplumber::reload_config() if (!popover->is_visible()) { - popover->popup(); + button->set_active(true); } if (popover->get_child() != face) @@ -460,7 +464,7 @@ void WayfireWireplumber::reload_config() // this is not the prettiest, but the alternative is way worse if ((std::string)str_wp_left_click_action == (std::string)"show_mixer") { - left_click_gesture->signal_pressed().connect( + left_conn = left_click_gesture->signal_pressed().connect( [&] (int c, double x, double y) { if (popover->get_child() != (Gtk::Widget*)&master_box) @@ -469,15 +473,14 @@ void WayfireWireplumber::reload_config() // popdown so that when the click is processed, the popover is down, and thus pops up // not the prettiest result, as it visibly closes instead of just replacing, but i’m not sure // how to make it better - popover->signal_hide().emission_stop(); - // popover->popdown(); + button->set_active(false); } }); } if ((std::string)str_wp_left_click_action == (std::string)"show_face") { - left_click_gesture->signal_pressed().connect( + left_conn = left_click_gesture->signal_pressed().connect( [&] (int c, double x, double y) { if (!face) @@ -491,40 +494,40 @@ void WayfireWireplumber::reload_config() // popdown so that when the click is processed, the popover is down, and thus pops up // not the prettiest result, as it visibly closes instead of just replacing, but i’m not sure // how to make it better - popover->signal_hide().emission_stop(); - // popover->popdown(); + button->signal_activate().emission_stop(); + button->set_active(false); } }); } - if ((std::string)str_wp_right_click_action == (std::string)"show_mixer") + if ((std::string)str_wp_middle_click_action == (std::string)"show_mixer") { - right_click_gesture->signal_pressed().connect(show_mixer_action); + middle_conn = middle_click_gesture->signal_pressed().connect(show_mixer_action); } - if ((std::string)str_wp_right_click_action == (std::string)"show_face") + if ((std::string)str_wp_middle_click_action == (std::string)"show_face") { - right_click_gesture->signal_pressed().connect(show_face_action); + middle_conn = middle_click_gesture->signal_pressed().connect(show_face_action); } - if ((std::string)str_wp_right_click_action == (std::string)"mute_face") + if ((std::string)str_wp_middle_click_action == (std::string)"mute_face") { - right_click_gesture->signal_pressed().connect(mute_action); + middle_conn = middle_click_gesture->signal_pressed().connect(mute_action); } - if ((std::string)str_wp_middle_click_action == (std::string)"show_mixer") + if ((std::string)str_wp_right_click_action == (std::string)"show_mixer") { - middle_click_gesture->signal_pressed().connect(show_mixer_action); + right_conn = right_click_gesture->signal_pressed().connect(show_mixer_action); } - if ((std::string)str_wp_middle_click_action == (std::string)"show_face") + if ((std::string)str_wp_right_click_action == (std::string)"show_face") { - middle_click_gesture->signal_pressed().connect(show_face_action); + right_conn = right_click_gesture->signal_pressed().connect(show_face_action); } - if ((std::string)str_wp_middle_click_action == (std::string)"mute_face") + if ((std::string)str_wp_right_click_action == (std::string)"mute_face") { - middle_click_gesture->signal_pressed().connect(mute_action); + right_conn = right_click_gesture->signal_pressed().connect(mute_action); } } diff --git a/src/panel/widgets/wireplumber.hpp b/src/panel/widgets/wireplumber.hpp index 3a474004..7a1a27a2 100644 --- a/src/panel/widgets/wireplumber.hpp +++ b/src/panel/widgets/wireplumber.hpp @@ -2,6 +2,7 @@ #define WIDGETS_PIPEWIRE_HPP #include "../widget.hpp" +#include "gtkmm/gestureclick.h" #include "gtkmm/togglebutton.h" #include "wf-popover.hpp" #include "wp/proxy-interfaces.h" From 6f8cd5bd9ed63e7a4eb47e95d3f2b2459a059b8c Mon Sep 17 00:00:00 2001 From: Hue Date: Thu, 6 Nov 2025 16:26:47 +0100 Subject: [PATCH 24/37] uncrustify --- src/panel/widgets/wireplumber.cpp | 37 +++++++++++++++++-------------- src/panel/widgets/wireplumber.hpp | 3 ++- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/panel/widgets/wireplumber.cpp b/src/panel/widgets/wireplumber.cpp index 8738ef6d..438ae9cd 100644 --- a/src/panel/widgets/wireplumber.cpp +++ b/src/panel/widgets/wireplumber.cpp @@ -292,6 +292,7 @@ WfWpControlDevice::WfWpControlDevice(WpPipewireObject *obj, set_def_status_no_callbk(true); return; } + const gchar *media_class = wp_pipewire_object_get_property( WP_PIPEWIRE_OBJECT(proxy), PW_KEY_MEDIA_CLASS); @@ -384,8 +385,8 @@ void WayfireWireplumber::reload_config() if (!done) { done = true; - left_click_gesture = Gtk::GestureClick::create(); - right_click_gesture = Gtk::GestureClick::create(); + left_click_gesture = Gtk::GestureClick::create(); + right_click_gesture = Gtk::GestureClick::create(); middle_click_gesture = Gtk::GestureClick::create(); left_click_gesture->set_button(1); middle_click_gesture->set_button(2); @@ -393,8 +394,8 @@ void WayfireWireplumber::reload_config() button->add_controller(left_click_gesture); button->add_controller(right_click_gesture); button->add_controller(middle_click_gesture); - } - else { + } else + { left_conn.disconnect(); middle_conn.disconnect(); right_conn.disconnect(); @@ -448,16 +449,15 @@ void WayfireWireplumber::reload_config() }; static auto mute_action = [&] (int c, double x, double y) - { + { std::cout << "everywhere\n"; - if (!face) - { - return; // no face means we have nothing to change by clicking - } - - face->button.set_active(!face->button.get_active()); - }; + if (!face) + { + return; // no face means we have nothing to change by clicking + } + face->button.set_active(!face->button.get_active()); + }; // the left click case is a bit special, since it’s supposed to show the popover. // (this is also why the mute action is not available for the left click) @@ -811,7 +811,8 @@ void WpCommon::on_object_added(WpObjectManager *manager, gpointer object, gpoint widget->objects_to_controls.insert({obj, control}); which_box->append((Gtk::Widget&)*control); // try to not be faceless - if (!widget->face){ + if (!widget->face) + { widget->face = control->copy(); } } @@ -871,6 +872,7 @@ void WpCommon::on_mixer_changed(gpointer mixer_api, guint id, gpointer data) widget->face->set_btn_status_no_callbk(mute); widget->face->set_scale_target_value(std::cbrt(volume)); // see on_mixer_plugin_loaded } + widget->update_icon(); // if we have the full mixer in the popover @@ -947,13 +949,14 @@ void WpCommon::on_default_nodes_changed(gpointer default_nodes_api, gpointer dat if ( // if the settings call for it, refresh the face ( - ((widget->face_choice == FaceChoice::DEFAULT_SINK) && (g_strcmp0(type, "Audio/Sink") == 0)) + ((widget->face_choice == FaceChoice::DEFAULT_SINK) && + (g_strcmp0(type, "Audio/Sink") == 0)) || - ((widget->face_choice == FaceChoice::DEFAULT_SOURCE) && (g_strcmp0(type, "Audio/Source") == 0)) + ((widget->face_choice == FaceChoice::DEFAULT_SOURCE) && + (g_strcmp0(type, "Audio/Source") == 0)) ) && - widget->face->object == ctrl->object - ) + (widget->face->object == ctrl->object)) { widget->face = ctrl->copy(); } diff --git a/src/panel/widgets/wireplumber.hpp b/src/panel/widgets/wireplumber.hpp index 7a1a27a2..a662ae3e 100644 --- a/src/panel/widgets/wireplumber.hpp +++ b/src/panel/widgets/wireplumber.hpp @@ -21,7 +21,8 @@ class WayfireWireplumber; class WfWpControl : public Gtk::Grid { - // Custom grid to facilitate handling + // Custom grid to facilitate handling + protected: WayfireAnimatedScale scale; Gtk::Label label; From 4435efa3ef14177004506ca9ed7d7e1239e2d580 Mon Sep 17 00:00:00 2001 From: Hue Date: Thu, 6 Nov 2025 16:59:24 +0100 Subject: [PATCH 25/37] =?UTF-8?q?fixed=20gestures=20connection=20both=20in?= =?UTF-8?q?=20control=20and=20widget=C2=A0;=20cleanup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/panel/widgets/wireplumber.cpp | 50 +++++++++++++------------------ src/panel/widgets/wireplumber.hpp | 7 +++++ 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/src/panel/widgets/wireplumber.cpp b/src/panel/widgets/wireplumber.cpp index 438ae9cd..7906e6ff 100644 --- a/src/panel/widgets/wireplumber.cpp +++ b/src/panel/widgets/wireplumber.cpp @@ -227,34 +227,33 @@ void WfWpControl::update_gestures() WfOption str_wp_right_click_action{"panel/wp_right_click_action"}; WfOption str_wp_middle_click_action{"panel/wp_middle_click_action"}; - static auto mute_action = + auto mute_action = [&] (int count, double x, double y) { button.set_active(!button.get_active()); }; - static auto right_click_mute = Gtk::GestureClick::create(); - right_click_mute->set_button(3); - right_click_mute->signal_pressed().connect(mute_action); - - static auto middle_click_mute = Gtk::GestureClick::create(); - middle_click_mute->set_button(2); - middle_click_mute->signal_pressed().connect([=] (int count, double x, double y) + if (!gestures_initialised){ + middle_click_mute = Gtk::GestureClick::create(); + right_click_mute = Gtk::GestureClick::create(); + middle_click_mute->set_button(2); + right_click_mute->set_button(3); + add_controller(middle_click_mute); + add_controller(right_click_mute); + } else { - button.set_active(!button.get_active()); - }); - - remove_controller(right_click_mute); - remove_controller(middle_click_mute); + middle_conn.disconnect(); + right_conn.disconnect(); + } - if ((std::string)str_wp_right_click_action == (std::string)"mute_face") + if ((std::string)str_wp_middle_click_action == (std::string)"mute_face") { - add_controller(right_click_mute); + middle_conn = middle_click_mute->signal_pressed().connect(mute_action); } - if ((std::string)str_wp_middle_click_action == (std::string)"mute_face") + if ((std::string)str_wp_right_click_action == (std::string)"mute_face") { - add_controller(middle_click_mute); + right_conn = right_click_mute->signal_pressed().connect(mute_action); } } @@ -377,14 +376,10 @@ void WayfireWireplumber::reload_config() face_choice = FaceChoice::LAST_CHANGE; } - static std::shared_ptr left_click_gesture, middle_click_gesture, right_click_gesture; - static sigc::connection left_conn, middle_conn, right_conn; - // run only the first time around - static auto done = false; - if (!done) + if (!gestures_initialised) { - done = true; + gestures_initialised = true; left_click_gesture = Gtk::GestureClick::create(); right_click_gesture = Gtk::GestureClick::create(); middle_click_gesture = Gtk::GestureClick::create(); @@ -401,9 +396,8 @@ void WayfireWireplumber::reload_config() right_conn.disconnect(); } - static auto show_mixer_action = [&] (int c, double x, double y) + auto show_mixer_action = [&] (int c, double x, double y) { - std::cout << "here\n"; if ((popover->get_child() == (Gtk::Widget*)&master_box) && popover->is_visible()) { popover->popdown(); @@ -422,9 +416,8 @@ void WayfireWireplumber::reload_config() } }; - static auto show_face_action = [&] (int c, double x, double y) + auto show_face_action = [&] (int c, double x, double y) { - std::cout << "there\n"; if (!face) { return; // no face means we have nothing to show @@ -448,9 +441,8 @@ void WayfireWireplumber::reload_config() } }; - static auto mute_action = [&] (int c, double x, double y) + auto mute_action = [&] (int c, double x, double y) { - std::cout << "everywhere\n"; if (!face) { return; // no face means we have nothing to change by clicking diff --git a/src/panel/widgets/wireplumber.hpp b/src/panel/widgets/wireplumber.hpp index a662ae3e..e502a16e 100644 --- a/src/panel/widgets/wireplumber.hpp +++ b/src/panel/widgets/wireplumber.hpp @@ -29,6 +29,9 @@ class WfWpControl : public Gtk::Grid Gtk::Image volume_icon; sigc::connection mute_conn; WayfireWireplumber *parent; + std::shared_ptr middle_click_mute, right_click_mute; + sigc::connection middle_conn, right_conn; + bool gestures_initialised = false; public: WfWpControl(WpPipewireObject *obj, WayfireWireplumber *parent_widget); @@ -80,6 +83,10 @@ class WayfireWireplumber : public WayfireWidget sigc::connection popover_timeout; sigc::connection volume_changed_signal; + bool gestures_initialised = false; + std::shared_ptr left_click_gesture, middle_click_gesture, right_click_gesture; + sigc::connection left_conn, middle_conn, right_conn; + public: void init(Gtk::Box *container) override; From af82be89c1456120c9be76fac0b2b7761d6c49fa Mon Sep 17 00:00:00 2001 From: Hue Date: Thu, 6 Nov 2025 17:07:58 +0100 Subject: [PATCH 26/37] lil forgotten --- src/panel/widgets/wireplumber.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/panel/widgets/wireplumber.cpp b/src/panel/widgets/wireplumber.cpp index 7906e6ff..7539faff 100644 --- a/src/panel/widgets/wireplumber.cpp +++ b/src/panel/widgets/wireplumber.cpp @@ -486,7 +486,6 @@ void WayfireWireplumber::reload_config() // popdown so that when the click is processed, the popover is down, and thus pops up // not the prettiest result, as it visibly closes instead of just replacing, but i’m not sure // how to make it better - button->signal_activate().emission_stop(); button->set_active(false); } }); From 5c914506265b858224de7d0b99c5068b5f03ff09 Mon Sep 17 00:00:00 2001 From: Hue Date: Thu, 6 Nov 2025 17:09:10 +0100 Subject: [PATCH 27/37] uncrustify --- src/panel/widgets/wireplumber.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/panel/widgets/wireplumber.cpp b/src/panel/widgets/wireplumber.cpp index 7539faff..4601993b 100644 --- a/src/panel/widgets/wireplumber.cpp +++ b/src/panel/widgets/wireplumber.cpp @@ -233,9 +233,10 @@ void WfWpControl::update_gestures() button.set_active(!button.get_active()); }; - if (!gestures_initialised){ + if (!gestures_initialised) + { middle_click_mute = Gtk::GestureClick::create(); - right_click_mute = Gtk::GestureClick::create(); + right_click_mute = Gtk::GestureClick::create(); middle_click_mute->set_button(2); right_click_mute->set_button(3); add_controller(middle_click_mute); From 400e8958fd51f2629355fe30f17208cf6166ff6c Mon Sep 17 00:00:00 2001 From: Hue Date: Sun, 16 Nov 2025 14:48:05 +0100 Subject: [PATCH 28/37] fixed doubling in existing widgets when catching up new one --- src/panel/widgets/wireplumber.cpp | 89 ++++++++++++++++++++----------- src/panel/widgets/wireplumber.hpp | 21 -------- 2 files changed, 57 insertions(+), 53 deletions(-) diff --git a/src/panel/widgets/wireplumber.cpp b/src/panel/widgets/wireplumber.cpp index 4601993b..5f414905 100644 --- a/src/panel/widgets/wireplumber.cpp +++ b/src/panel/widgets/wireplumber.cpp @@ -19,6 +19,27 @@ #include "wireplumber.hpp" +namespace WpCommon +{ + WpCore *core = nullptr; + WpObjectManager *object_manager; + WpPlugin *mixer_api; + WpPlugin *default_nodes_api; + + std::vector widgets; + + void init_wp(); + void catch_up_to_current_state(WayfireWireplumber *widget); + void on_mixer_plugin_loaded(WpCore *core, GAsyncResult *res, gpointer data); + void on_default_nodes_plugin_loaded(WpCore *core, GAsyncResult *res, gpointer data); + void on_all_plugins_loaded(); + void on_om_installed(WpObjectManager *manager, gpointer data); + void add_object_to_widget(WpPipewireObject* object, WayfireWireplumber* widget); + void on_object_added(WpObjectManager *manager, gpointer object, gpointer data); + void on_mixer_changed(gpointer mixer_api, guint id, gpointer data); + void on_default_nodes_changed(gpointer default_nodes_api, gpointer data); + void on_object_removed(WpObjectManager *manager, gpointer node, gpointer data); +} enum VolumeLevel { @@ -710,7 +731,7 @@ void WpCommon::catch_up_to_current_state(WayfireWireplumber *widget) GValue item = G_VALUE_INIT; while (wp_iterator_next(reg_objs, &item)) { - on_object_added(object_manager, (gpointer)g_value_get_object(&item), &widget); + add_object_to_widget((WpPipewireObject*)g_value_get_object(&item), widget); g_value_unset(&item); } } @@ -770,43 +791,47 @@ void WpCommon::on_all_plugins_loaded() wp_core_install_object_manager(core, object_manager); } -void WpCommon::on_object_added(WpObjectManager *manager, gpointer object, gpointer data) -{ +void WpCommon::add_object_to_widget(WpPipewireObject* object, WayfireWireplumber* widget){ // adds a new widget to the appropriate section - WpPipewireObject *obj = (WpPipewireObject*)object; + const gchar *type = wp_pipewire_object_get_property(object, PW_KEY_MEDIA_CLASS); - const gchar *type = wp_pipewire_object_get_property(obj, PW_KEY_MEDIA_CLASS); + WfWpControl *control; + Gtk::Box *which_box; + if (g_strcmp0(type, "Audio/Sink") == 0) + { + which_box = &(widget->sinks_box); + control = new WfWpControlDevice(object, widget); + } else if (g_strcmp0(type, "Audio/Source") == 0) + { + which_box = &(widget->sources_box); + control = new WfWpControlDevice(object, widget); + } else if (g_strcmp0(type, "Stream/Output/Audio") == 0) + { + which_box = &(widget->streams_box); + control = new WfWpControl(object, (WayfireWireplumber*)widget); + } else + { + std::cout << "Could not match pipewire object media class, ignoring\n"; + return; + } - for (auto widget : widgets) + widget->objects_to_controls.insert({object, control}); + which_box->append((Gtk::Widget&)*control); + // try to not be faceless + if (!widget->face) { - WfWpControl *control; - Gtk::Box *which_box; - if (g_strcmp0(type, "Audio/Sink") == 0) - { - which_box = &(widget->sinks_box); - control = new WfWpControlDevice(obj, widget); - } else if (g_strcmp0(type, "Audio/Source") == 0) - { - which_box = &(widget->sources_box); - control = new WfWpControlDevice(obj, widget); - } else if (g_strcmp0(type, "Stream/Output/Audio") == 0) - { - which_box = &(widget->streams_box); - control = new WfWpControl(obj, (WayfireWireplumber*)widget); - } else - { - std::cout << "Could not match pipewire object media class, ignoring\n"; - return; - } + widget->face = control->copy(); + } +} - widget->objects_to_controls.insert({obj, control}); - which_box->append((Gtk::Widget&)*control); - // try to not be faceless - if (!widget->face) - { - widget->face = control->copy(); - } +void WpCommon::on_object_added(WpObjectManager *manager, gpointer object, gpointer data) +{ + WpPipewireObject *obj = (WpPipewireObject*)object; + + for (auto widget : widgets) + { + add_object_to_widget(obj, widget); } } diff --git a/src/panel/widgets/wireplumber.hpp b/src/panel/widgets/wireplumber.hpp index e502a16e..77a998c5 100644 --- a/src/panel/widgets/wireplumber.hpp +++ b/src/panel/widgets/wireplumber.hpp @@ -128,25 +128,4 @@ class WayfireWireplumber : public WayfireWidget virtual ~WayfireWireplumber(); }; -namespace WpCommon -{ -static WpCore *core = nullptr; -static WpObjectManager *object_manager; -static WpPlugin *mixer_api; -static WpPlugin *default_nodes_api; - -static std::vector widgets; - -static void init_wp(); -static void catch_up_to_current_state(WayfireWireplumber *widget); -static void on_mixer_plugin_loaded(WpCore *core, GAsyncResult *res, gpointer data); -static void on_default_nodes_plugin_loaded(WpCore *core, GAsyncResult *res, gpointer data); -static void on_all_plugins_loaded(); -static void on_om_installed(WpObjectManager *manager, gpointer data); -static void on_object_added(WpObjectManager *manager, gpointer object, gpointer data); -static void on_mixer_changed(gpointer mixer_api, guint id, gpointer data); -static void on_default_nodes_changed(gpointer default_nodes_api, gpointer data); -static void on_object_removed(WpObjectManager *manager, gpointer node, gpointer data); -} - #endif // WIDGETS_PIPEWIRE_HPP From a110040d44bf36696eb3faf1099e3bd247c3bd71 Mon Sep 17 00:00:00 2001 From: Hue Date: Sun, 16 Nov 2025 15:07:53 +0100 Subject: [PATCH 29/37] de-duplicated icon_name_from_state --- src/panel/widgets/wireplumber.cpp | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/src/panel/widgets/wireplumber.cpp b/src/panel/widgets/wireplumber.cpp index 5f414905..e0b26b87 100644 --- a/src/panel/widgets/wireplumber.cpp +++ b/src/panel/widgets/wireplumber.cpp @@ -62,6 +62,15 @@ static const gchar *DEFAULT_NODE_MEDIA_CLASSES[] = { "Audio/Source", }; +const std::map icon_name_from_state = { + {VOLUME_LEVEL_MUTE, "audio-volume-muted"}, + {VOLUME_LEVEL_LOW, "audio-volume-low"}, + {VOLUME_LEVEL_MED, "audio-volume-medium"}, + {VOLUME_LEVEL_HIGH, "audio-volume-high"}, + {VOLUME_LEVEL_OOR, "audio-volume-muted"}, +}; + + static VolumeLevel volume_icon_for(double volume) { double max = 1.0; @@ -222,14 +231,6 @@ void WfWpControl::update_icon() return; } - std::map icon_name_from_state = { - {VOLUME_LEVEL_MUTE, "audio-volume-muted"}, - {VOLUME_LEVEL_LOW, "audio-volume-low"}, - {VOLUME_LEVEL_MED, "audio-volume-medium"}, - {VOLUME_LEVEL_HIGH, "audio-volume-high"}, - {VOLUME_LEVEL_OOR, "audio-volume-muted"}, - }; - volume_icon.set_from_icon_name(icon_name_from_state.at(current)); } @@ -661,14 +662,6 @@ void WayfireWireplumber::update_icon() return; } - std::map icon_name_from_state = { - {VOLUME_LEVEL_MUTE, "audio-volume-muted"}, - {VOLUME_LEVEL_LOW, "audio-volume-low"}, - {VOLUME_LEVEL_MED, "audio-volume-medium"}, - {VOLUME_LEVEL_HIGH, "audio-volume-high"}, - {VOLUME_LEVEL_OOR, "audio-volume-muted"}, - }; - main_image.set_from_icon_name(icon_name_from_state.at(current)); } From c51f510b7d4c76a0c70d82846d644aa51b3642da Mon Sep 17 00:00:00 2001 From: Hue Date: Sun, 16 Nov 2025 15:19:39 +0100 Subject: [PATCH 30/37] removed non breaking space and french quotes from comments --- src/panel/widgets/wireplumber.cpp | 6 +++--- src/panel/widgets/wireplumber.hpp | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/panel/widgets/wireplumber.cpp b/src/panel/widgets/wireplumber.cpp index e0b26b87..62648163 100644 --- a/src/panel/widgets/wireplumber.cpp +++ b/src/panel/widgets/wireplumber.cpp @@ -556,7 +556,7 @@ void WayfireWireplumber::handle_config_reload() void WayfireWireplumber::init(Gtk::Box *container) { - // sets up the « widget part » + // sets up the "widget part" button = std::make_unique("panel"); button->get_style_context()->add_class("volume"); @@ -611,7 +611,7 @@ void WayfireWireplumber::init(Gtk::Box *container) auto r2 = Gtk::Orientation::VERTICAL; master_box.set_orientation(r1); - // TODO : only show the boxes which have stuff in them + // TODO: only show the boxes which have stuff in them master_box.append(sinks_box); master_box.append(*new Gtk::Separator(r1)); master_box.append(sources_box); @@ -878,7 +878,7 @@ void WpCommon::on_mixer_changed(gpointer mixer_api, guint id, gpointer data) } } else { - // update the face’s values ; note the WfWpControl constructor already syncs the values + // update the face’s values; note the WfWpControl constructor already syncs the values widget->face->set_btn_status_no_callbk(mute); widget->face->set_scale_target_value(std::cbrt(volume)); // see on_mixer_plugin_loaded } diff --git a/src/panel/widgets/wireplumber.hpp b/src/panel/widgets/wireplumber.hpp index 77a998c5..cbda8ce4 100644 --- a/src/panel/widgets/wireplumber.hpp +++ b/src/panel/widgets/wireplumber.hpp @@ -50,7 +50,7 @@ class WfWpControl : public Gtk::Grid WfWpControl *copy(); }; -// todo : add a WfWpControlStream class that presents a dropdown to select which sink a stream goes to +// todo: add a WfWpControlStream class that presents a dropdown to select which sink a stream goes to // sinks and sources class WfWpControlDevice : public WfWpControl @@ -100,14 +100,14 @@ class WayfireWireplumber : public WayfireWidget Gtk::Popover *popover; /* - * the « face » is the representation of the audio channel that shows it’s volume level on the widget + * the "face" is the representation of the audio channel that shows it’s volume level on the widget * icon and is concerned by the quick actions. - * currently, it is the last channel to have been updated. TODO : add pinning ? + * currently, it is the last channel to have been updated. TODO: add pinning? */ WfWpControl *face; Gtk::Box master_box, sinks_box, sources_box, streams_box; - // TODO : add a category for stuff that listens to an audio source + // TODO: add a category for stuff that listens to an audio source std::map objects_to_controls; From 22b4bdb195bdff2b304f09fb12483c7719accc4c Mon Sep 17 00:00:00 2001 From: Hue Date: Sun, 16 Nov 2025 15:30:14 +0100 Subject: [PATCH 31/37] use std::string_view for string comparison --- src/panel/widgets/wireplumber.cpp | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/panel/widgets/wireplumber.cpp b/src/panel/widgets/wireplumber.cpp index 62648163..c5355642 100644 --- a/src/panel/widgets/wireplumber.cpp +++ b/src/panel/widgets/wireplumber.cpp @@ -16,6 +16,7 @@ #include #include +#include #include "wireplumber.hpp" @@ -315,12 +316,12 @@ WfWpControlDevice::WfWpControlDevice(WpPipewireObject *obj, return; } - const gchar *media_class = wp_pipewire_object_get_property( + const std::string_view media_class{wp_pipewire_object_get_property( WP_PIPEWIRE_OBJECT(proxy), - PW_KEY_MEDIA_CLASS); + PW_KEY_MEDIA_CLASS)}; for (guint i = 0; i < G_N_ELEMENTS(DEFAULT_NODE_MEDIA_CLASSES); i++) { - if (g_strcmp0(media_class, DEFAULT_NODE_MEDIA_CLASSES[i])) + if (media_class == DEFAULT_NODE_MEDIA_CLASSES[i]) { continue; } @@ -787,19 +788,19 @@ void WpCommon::on_all_plugins_loaded() void WpCommon::add_object_to_widget(WpPipewireObject* object, WayfireWireplumber* widget){ // adds a new widget to the appropriate section - const gchar *type = wp_pipewire_object_get_property(object, PW_KEY_MEDIA_CLASS); + const std::string_view type {wp_pipewire_object_get_property(object, PW_KEY_MEDIA_CLASS)}; WfWpControl *control; Gtk::Box *which_box; - if (g_strcmp0(type, "Audio/Sink") == 0) + if (type == "Audio/Sink") { which_box = &(widget->sinks_box); control = new WfWpControlDevice(object, widget); - } else if (g_strcmp0(type, "Audio/Source") == 0) + } else if (type == "Audio/Source") { which_box = &(widget->sources_box); control = new WfWpControlDevice(object, widget); - } else if (g_strcmp0(type, "Stream/Output/Audio") == 0) + } else if (type == "Stream/Output/Audio") { which_box = &(widget->streams_box); control = new WfWpControl(object, (WayfireWireplumber*)widget); @@ -950,20 +951,20 @@ void WpCommon::on_default_nodes_changed(gpointer default_nodes_api, gpointer dat } // if the control is not for a sink or source (non WfWpControlDevice), don’t try to set status - const gchar *type = wp_pipewire_object_get_property(obj, PW_KEY_MEDIA_CLASS); + const std::string_view type{wp_pipewire_object_get_property(obj, PW_KEY_MEDIA_CLASS)}; - if ((g_strcmp0(type, "Audio/Sink") == 0) || (g_strcmp0(type, "Audio/Source") == 0)) + if (type == "Audio/Sink" || type == "Audio/Source") { ctrl->set_def_status_no_callbk(false); } if ( // if the settings call for it, refresh the face ( - ((widget->face_choice == FaceChoice::DEFAULT_SINK) && - (g_strcmp0(type, "Audio/Sink") == 0)) + (widget->face_choice == FaceChoice::DEFAULT_SINK) && + (type == "Audio/Sink") || - ((widget->face_choice == FaceChoice::DEFAULT_SOURCE) && - (g_strcmp0(type, "Audio/Source") == 0)) + (widget->face_choice == FaceChoice::DEFAULT_SOURCE) && + (type == "Audio/Source") ) && (widget->face->object == ctrl->object)) From affc7b9446d8bb55f68fcf3cd5dd16b2f1da774d Mon Sep 17 00:00:00 2001 From: Hue Date: Sun, 16 Nov 2025 15:42:21 +0100 Subject: [PATCH 32/37] fixed configuration to use .value() missed it oops --- src/panel/widgets/wireplumber.cpp | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/panel/widgets/wireplumber.cpp b/src/panel/widgets/wireplumber.cpp index c5355642..9ecc5657 100644 --- a/src/panel/widgets/wireplumber.cpp +++ b/src/panel/widgets/wireplumber.cpp @@ -270,12 +270,12 @@ void WfWpControl::update_gestures() right_conn.disconnect(); } - if ((std::string)str_wp_middle_click_action == (std::string)"mute_face") + if (str_wp_middle_click_action.value() == "mute_face") { middle_conn = middle_click_mute->signal_pressed().connect(mute_action); } - if ((std::string)str_wp_right_click_action == (std::string)"mute_face") + if (str_wp_right_click_action.value() == "mute_face") { right_conn = right_click_mute->signal_pressed().connect(mute_action); } @@ -382,15 +382,15 @@ void WayfireWireplumber::reload_config() WfOption str_wp_right_click_action{"panel/wp_right_click_action"}; WfOption str_wp_middle_click_action{"panel/wp_middle_click_action"}; - if ((std::string)str_face_choice == (std::string)"last_change") + if (str_face_choice.value() == "last_change") { face_choice = FaceChoice::LAST_CHANGE; - } else if ((std::string)str_face_choice == (std::string)"default_sink") + } else if (str_face_choice.value() == "default_sink") { face_choice = FaceChoice::DEFAULT_SINK; face = nullptr; WpCommon::on_default_nodes_changed(WpCommon::default_nodes_api, NULL); - } else if ((std::string)str_face_choice == (std::string)"default_source") + } else if (str_face_choice.value() == "default_source") { face_choice = FaceChoice::DEFAULT_SOURCE; face = nullptr; @@ -478,7 +478,7 @@ void WayfireWireplumber::reload_config() // the left click case is a bit special, since it’s supposed to show the popover. // (this is also why the mute action is not available for the left click) // this is not the prettiest, but the alternative is way worse - if ((std::string)str_wp_left_click_action == (std::string)"show_mixer") + if (str_wp_left_click_action.value() == "show_mixer") { left_conn = left_click_gesture->signal_pressed().connect( [&] (int c, double x, double y) @@ -494,7 +494,7 @@ void WayfireWireplumber::reload_config() }); } - if ((std::string)str_wp_left_click_action == (std::string)"show_face") + if (str_wp_left_click_action.value() == "show_face") { left_conn = left_click_gesture->signal_pressed().connect( [&] (int c, double x, double y) @@ -515,32 +515,32 @@ void WayfireWireplumber::reload_config() }); } - if ((std::string)str_wp_middle_click_action == (std::string)"show_mixer") + if (str_wp_middle_click_action.value() == "show_mixer") { middle_conn = middle_click_gesture->signal_pressed().connect(show_mixer_action); } - if ((std::string)str_wp_middle_click_action == (std::string)"show_face") + if (str_wp_middle_click_action.value() == "show_face") { middle_conn = middle_click_gesture->signal_pressed().connect(show_face_action); } - if ((std::string)str_wp_middle_click_action == (std::string)"mute_face") + if (str_wp_middle_click_action.value() == "mute_face") { middle_conn = middle_click_gesture->signal_pressed().connect(mute_action); } - if ((std::string)str_wp_right_click_action == (std::string)"show_mixer") + if (str_wp_right_click_action.value() == "show_mixer") { right_conn = right_click_gesture->signal_pressed().connect(show_mixer_action); } - if ((std::string)str_wp_right_click_action == (std::string)"show_face") + if (str_wp_right_click_action.value() == "show_face") { right_conn = right_click_gesture->signal_pressed().connect(show_face_action); } - if ((std::string)str_wp_right_click_action == (std::string)"mute_face") + if (str_wp_right_click_action.value() == "mute_face") { right_conn = right_click_gesture->signal_pressed().connect(mute_action); } From b7ee36794baea71520d7c40d8d2d1ddb0249366f Mon Sep 17 00:00:00 2001 From: Hue Date: Sun, 16 Nov 2025 19:50:47 +0100 Subject: [PATCH 33/37] unique instead of raw pointers --- src/panel/widgets/wireplumber.cpp | 40 ++++++++++++++++++------------- src/panel/widgets/wireplumber.hpp | 8 +++---- 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/src/panel/widgets/wireplumber.cpp b/src/panel/widgets/wireplumber.cpp index 9ecc5657..fd19426a 100644 --- a/src/panel/widgets/wireplumber.cpp +++ b/src/panel/widgets/wireplumber.cpp @@ -286,10 +286,9 @@ void WfWpControl::handle_config_reload() update_gestures(); } -WfWpControl*WfWpControl::copy() +std::unique_ptr WfWpControl::copy() { - WfWpControl *copy = new WfWpControl(object, parent); - return copy; + return std::make_unique(object, parent); } WfWpControlDevice::WfWpControlDevice(WpPipewireObject *obj, @@ -354,10 +353,9 @@ void WfWpControlDevice::set_def_status_no_callbk(bool state) def_conn.block(false); } -WfWpControlDevice*WfWpControlDevice::copy() +std::unique_ptr WfWpControlDevice::copy() { - WfWpControlDevice *copy = new WfWpControlDevice(object, parent); - return copy; + return std::make_unique(object, parent); } bool WayfireWireplumber::on_popover_timeout(int timer) @@ -447,7 +445,7 @@ void WayfireWireplumber::reload_config() return; // no face means we have nothing to show } - if ((popover->get_child() == face) && popover->is_visible()) + if ((popover->get_child() == face.get()) && popover->is_visible()) { popover->popdown(); return; @@ -458,7 +456,7 @@ void WayfireWireplumber::reload_config() button->set_active(true); } - if (popover->get_child() != face) + if (popover->get_child() != face.get()) { popover->set_child(*face); popover_timeout.disconnect(); @@ -504,7 +502,7 @@ void WayfireWireplumber::reload_config() return; } - if (popover->get_child() != face) + if (popover->get_child() != face.get()) { popover->set_child(*face); // popdown so that when the click is processed, the popover is down, and thus pops up @@ -549,8 +547,9 @@ void WayfireWireplumber::reload_config() void WayfireWireplumber::handle_config_reload() { reload_config(); - for (auto [name, control] : objects_to_controls) + for (auto &entry : objects_to_controls) { + auto &control = entry.second; control->handle_config_reload(); } } @@ -810,7 +809,7 @@ void WpCommon::add_object_to_widget(WpPipewireObject* object, WayfireWireplumber return; } - widget->objects_to_controls.insert({object, control}); + widget->objects_to_controls.insert({object, std::unique_ptr(control)}); which_box->append((Gtk::Widget&)*control); // try to not be faceless if (!widget->face) @@ -854,7 +853,7 @@ void WpCommon::on_mixer_changed(gpointer mixer_api, guint id, gpointer data) for (auto & it : widget->objects_to_controls) { WpPipewireObject *obj = it.first; - control = it.second; + control = it.second.get(); if (wp_proxy_get_bound_id(WP_PROXY(obj)) == id) { break; @@ -932,7 +931,7 @@ void WpCommon::on_default_nodes_changed(gpointer default_nodes_api, gpointer dat for (const auto & entry : widget->objects_to_controls) { auto obj = WP_PIPEWIRE_OBJECT(entry.first); - auto ctrl = (WfWpControlDevice*)entry.second; + auto ctrl = static_cast(entry.second.get()); guint32 bound_id = wp_proxy_get_bound_id(WP_PROXY(obj)); if (bound_id == SPA_ID_INVALID) @@ -986,12 +985,19 @@ void WpCommon::on_object_removed(WpObjectManager *manager, gpointer object, gpoi return; } - WfWpControl *control = it->second; + WfWpControl *control = it->second.get(); Gtk::Box *box = (Gtk::Box*)control->get_parent(); - box->remove(*control); + if (box) + box->remove(*control); - delete control; - widget->objects_to_controls.erase((WpPipewireObject*)object); + // if face points to the removed control we should handle it. + if (widget->face && widget->face->object == it->first) + { + // reset face to nullptr + widget->face = nullptr; + } + + widget->objects_to_controls.erase(it); } } diff --git a/src/panel/widgets/wireplumber.hpp b/src/panel/widgets/wireplumber.hpp index cbda8ce4..d4818b58 100644 --- a/src/panel/widgets/wireplumber.hpp +++ b/src/panel/widgets/wireplumber.hpp @@ -47,7 +47,7 @@ class WfWpControl : public Gtk::Grid void update_gestures(); void handle_config_reload(); - WfWpControl *copy(); + std::unique_ptr copy(); }; // todo: add a WfWpControlStream class that presents a dropdown to select which sink a stream goes to @@ -63,7 +63,7 @@ class WfWpControlDevice : public WfWpControl WfWpControlDevice(WpPipewireObject *obj, WayfireWireplumber *parent_widget); Gtk::ToggleButton default_btn; void set_def_status_no_callbk(bool state); - WfWpControlDevice *copy(); + std::unique_ptr copy(); }; class wayfire_config; @@ -104,12 +104,12 @@ class WayfireWireplumber : public WayfireWidget * icon and is concerned by the quick actions. * currently, it is the last channel to have been updated. TODO: add pinning? */ - WfWpControl *face; + std::unique_ptr face; Gtk::Box master_box, sinks_box, sources_box, streams_box; // TODO: add a category for stuff that listens to an audio source - std::map objects_to_controls; + std::map> objects_to_controls; /** Update the icon based on volume and muted state of the face widget */ void update_icon(); From e04857880ef28a5afd46d62a795260edff94506e Mon Sep 17 00:00:00 2001 From: Hue Date: Sun, 16 Nov 2025 20:26:12 +0100 Subject: [PATCH 34/37] separators and labels not by new --- src/panel/widgets/wireplumber.cpp | 21 +++++++++++++-------- src/panel/widgets/wireplumber.hpp | 3 +++ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/panel/widgets/wireplumber.cpp b/src/panel/widgets/wireplumber.cpp index fd19426a..43baafe7 100644 --- a/src/panel/widgets/wireplumber.cpp +++ b/src/panel/widgets/wireplumber.cpp @@ -613,19 +613,24 @@ void WayfireWireplumber::init(Gtk::Box *container) master_box.set_orientation(r1); // TODO: only show the boxes which have stuff in them master_box.append(sinks_box); - master_box.append(*new Gtk::Separator(r1)); + master_box.append(out_in_wall); + out_in_wall.set_orientation(r2); master_box.append(sources_box); - master_box.append(*new Gtk::Separator(r1)); + in_streams_wall.set_orientation(r2); + master_box.append(in_streams_wall); master_box.append(streams_box); sinks_box.set_orientation(r2); - sinks_box.append(*new Gtk::Label("Output devices")); - sinks_box.append(*new Gtk::Separator(r2)); + sinks_box.append(output); + out_sep.set_orientation(r1); + sinks_box.append(out_sep); sources_box.set_orientation(r2); - sources_box.append(*new Gtk::Label("Input devices")); - sources_box.append(*new Gtk::Separator(r2)); + sources_box.append(input); + in_sep.set_orientation(r1); + sources_box.append(in_sep); streams_box.set_orientation(r2); - streams_box.append(*new Gtk::Label("Audio streams")); - streams_box.append(*new Gtk::Separator(r2)); + streams_box.append(streams); + streams_sep.set_orientation(r1); + streams_box.append(streams_sep); /* Setup popover */ popover->set_child(master_box); diff --git a/src/panel/widgets/wireplumber.hpp b/src/panel/widgets/wireplumber.hpp index d4818b58..36cbebd5 100644 --- a/src/panel/widgets/wireplumber.hpp +++ b/src/panel/widgets/wireplumber.hpp @@ -87,6 +87,9 @@ class WayfireWireplumber : public WayfireWidget std::shared_ptr left_click_gesture, middle_click_gesture, right_click_gesture; sigc::connection left_conn, middle_conn, right_conn; + Gtk::Label output, input, streams; + Gtk::Separator out_in_wall, in_streams_wall, out_sep, in_sep, streams_sep; + public: void init(Gtk::Box *container) override; From c38b0b9554b068c97334d3db1f287f2466fe5b04 Mon Sep 17 00:00:00 2001 From: Hue Date: Sun, 16 Nov 2025 21:33:27 +0100 Subject: [PATCH 35/37] add autos, make construction less barbaric --- src/panel/widgets/wireplumber.cpp | 32 ++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/src/panel/widgets/wireplumber.cpp b/src/panel/widgets/wireplumber.cpp index 43baafe7..56820a5e 100644 --- a/src/panel/widgets/wireplumber.cpp +++ b/src/panel/widgets/wireplumber.cpp @@ -607,31 +607,37 @@ void WayfireWireplumber::init(Gtk::Box *container) reload_config(); // boxes hierarchy and labeling - auto r1 = Gtk::Orientation::HORIZONTAL; - auto r2 = Gtk::Orientation::VERTICAL; + master_box.set_orientation(Gtk::Orientation::HORIZONTAL); - master_box.set_orientation(r1); - // TODO: only show the boxes which have stuff in them + // column orientations + out_in_wall.set_orientation(Gtk::Orientation::VERTICAL); + in_streams_wall.set_orientation(Gtk::Orientation::VERTICAL); + sinks_box.set_orientation(Gtk::Orientation::VERTICAL); + sources_box.set_orientation(Gtk::Orientation::VERTICAL); + streams_box.set_orientation(Gtk::Orientation::VERTICAL); + + // assemble master box (TODO: only show boxes that have contents) master_box.append(sinks_box); master_box.append(out_in_wall); - out_in_wall.set_orientation(r2); master_box.append(sources_box); - in_streams_wall.set_orientation(r2); master_box.append(in_streams_wall); master_box.append(streams_box); - sinks_box.set_orientation(r2); + + // sinks sinks_box.append(output); - out_sep.set_orientation(r1); + out_sep.set_orientation(Gtk::Orientation::HORIZONTAL); sinks_box.append(out_sep); - sources_box.set_orientation(r2); + + // sources sources_box.append(input); - in_sep.set_orientation(r1); + in_sep.set_orientation(Gtk::Orientation::HORIZONTAL); sources_box.append(in_sep); - streams_box.set_orientation(r2); + + // streams streams_box.append(streams); - streams_sep.set_orientation(r1); + streams_sep.set_orientation(Gtk::Orientation::HORIZONTAL); streams_box.append(streams_sep); - + /* Setup popover */ popover->set_child(master_box); popover->get_style_context()->add_class("volume-popover"); From a35d60b61e0fa51a9edc913749ed7d7fcd1c8146 Mon Sep 17 00:00:00 2001 From: Hue Date: Sun, 16 Nov 2025 21:47:57 +0100 Subject: [PATCH 36/37] cleanup --- src/panel/widgets/wireplumber.cpp | 23 +++++++---------------- src/panel/widgets/wireplumber.hpp | 18 ++++++++++-------- 2 files changed, 17 insertions(+), 24 deletions(-) diff --git a/src/panel/widgets/wireplumber.cpp b/src/panel/widgets/wireplumber.cpp index 56820a5e..0ca82c84 100644 --- a/src/panel/widgets/wireplumber.cpp +++ b/src/panel/widgets/wireplumber.cpp @@ -205,11 +205,6 @@ WfWpControl::WfWpControl(WpPipewireObject *obj, WayfireWireplumber *parent_widge update_icon(); } -Glib::ustring WfWpControl::get_name() -{ - return label.get_text(); -} - void WfWpControl::set_btn_status_no_callbk(bool state) { mute_conn.block(true); @@ -226,7 +221,7 @@ void WfWpControl::update_icon() { VolumeLevel current = volume_icon_for(get_scale_target_value()); - if (is_muted()) + if (button.get_active()) { volume_icon.set_from_icon_name("audio-volume-muted"); return; @@ -240,11 +235,6 @@ double WfWpControl::get_scale_target_value() return scale.get_target_value(); } -bool WfWpControl::is_muted() -{ - return button.get_active(); -} - void WfWpControl::update_gestures() { WfOption str_wp_right_click_action{"panel/wp_right_click_action"}; @@ -616,7 +606,7 @@ void WayfireWireplumber::init(Gtk::Box *container) sources_box.set_orientation(Gtk::Orientation::VERTICAL); streams_box.set_orientation(Gtk::Orientation::VERTICAL); - // assemble master box (TODO: only show boxes that have contents) + // assemble master box (todo: only show boxes that have contents) master_box.append(sinks_box); master_box.append(out_in_wall); master_box.append(sources_box); @@ -637,7 +627,7 @@ void WayfireWireplumber::init(Gtk::Box *container) streams_box.append(streams); streams_sep.set_orientation(Gtk::Orientation::HORIZONTAL); streams_box.append(streams_sep); - + /* Setup popover */ popover->set_child(master_box); popover->get_style_context()->add_class("volume-popover"); @@ -667,7 +657,7 @@ void WayfireWireplumber::update_icon() { VolumeLevel current = volume_icon_for(face->get_scale_target_value()); - if (!face || face->is_muted()) + if (!face || face->button.get_active()) { main_image.set_from_icon_name("audio-volume-muted"); return; @@ -805,6 +795,7 @@ void WpCommon::add_object_to_widget(WpPipewireObject* object, WayfireWireplumber if (type == "Audio/Sink") { which_box = &(widget->sinks_box); + control = new WfWpControlDevice(object, widget); } else if (type == "Audio/Source") { @@ -831,7 +822,7 @@ void WpCommon::add_object_to_widget(WpPipewireObject* object, WayfireWireplumber void WpCommon::on_object_added(WpObjectManager *manager, gpointer object, gpointer data) { - WpPipewireObject *obj = (WpPipewireObject*)object; + auto *obj = (WpPipewireObject*)object; for (auto widget : widgets) { @@ -997,7 +988,7 @@ void WpCommon::on_object_removed(WpObjectManager *manager, gpointer object, gpoi } WfWpControl *control = it->second.get(); - Gtk::Box *box = (Gtk::Box*)control->get_parent(); + auto *box = (Gtk::Box*)control->get_parent(); if (box) box->remove(*control); diff --git a/src/panel/widgets/wireplumber.hpp b/src/panel/widgets/wireplumber.hpp index 36cbebd5..855745b2 100644 --- a/src/panel/widgets/wireplumber.hpp +++ b/src/panel/widgets/wireplumber.hpp @@ -32,19 +32,17 @@ class WfWpControl : public Gtk::Grid std::shared_ptr middle_click_mute, right_click_mute; sigc::connection middle_conn, right_conn; bool gestures_initialised = false; + void update_gestures(); public: WfWpControl(WpPipewireObject *obj, WayfireWireplumber *parent_widget); WpPipewireObject *object; - Glib::ustring get_name(); Gtk::ToggleButton button; void set_btn_status_no_callbk(bool state); void set_scale_target_value(double volume); double get_scale_target_value(); void update_icon(); - bool is_muted(); - void update_gestures(); void handle_config_reload(); std::unique_ptr copy(); @@ -70,6 +68,8 @@ class wayfire_config; class WayfireWireplumber : public WayfireWidget { private: + void init(Gtk::Box *container) override; + Gtk::Image main_image; WfOption timeout{"panel/wp_display_timeout"}; @@ -77,6 +77,7 @@ class WayfireWireplumber : public WayfireWidget void on_volume_value_changed(); bool on_popover_timeout(int timer); + // signals for the configurable actions on click gulong notify_volume_signal = 0; gulong notify_is_muted_signal = 0; gulong notify_default_sink_changed = 0; @@ -87,11 +88,13 @@ class WayfireWireplumber : public WayfireWidget std::shared_ptr left_click_gesture, middle_click_gesture, right_click_gesture; sigc::connection left_conn, middle_conn, right_conn; + // mixer display widgets Gtk::Label output, input, streams; Gtk::Separator out_in_wall, in_streams_wall, out_sep, in_sep, streams_sep; + void reload_config(); + public: - void init(Gtk::Box *container) override; WfOption scroll_sensitivity{"panel/wp_scroll_sensitivity"}; WfOption invert_scroll{"panel/wp_invert_scroll"}; @@ -105,12 +108,12 @@ class WayfireWireplumber : public WayfireWidget /* * the "face" is the representation of the audio channel that shows it’s volume level on the widget * icon and is concerned by the quick actions. - * currently, it is the last channel to have been updated. TODO: add pinning? + * configured by panel/wp_face_choice. idea: add pinning? */ std::unique_ptr face; Gtk::Box master_box, sinks_box, sources_box, streams_box; - // TODO: add a category for stuff that listens to an audio source + // possible: add a category for stuff that listens to an audio source std::map> objects_to_controls; @@ -125,8 +128,7 @@ class WayfireWireplumber : public WayfireWidget */ void check_set_popover_timeout(); - void reload_config(); - void handle_config_reload(); + void handle_config_reload() override; virtual ~WayfireWireplumber(); }; From 45342f0025267c771f4c29b57466d6755800bb35 Mon Sep 17 00:00:00 2001 From: Hue Date: Sun, 16 Nov 2025 23:17:01 +0100 Subject: [PATCH 37/37] exported volumelevel and icons to be common between volume and wireplumber --- src/panel/widgets/volume-level.hpp | 21 ++++++++++++++++ src/panel/widgets/volume.cpp | 17 +------------ src/panel/widgets/wireplumber.cpp | 39 +++--------------------------- 3 files changed, 26 insertions(+), 51 deletions(-) create mode 100644 src/panel/widgets/volume-level.hpp diff --git a/src/panel/widgets/volume-level.hpp b/src/panel/widgets/volume-level.hpp new file mode 100644 index 00000000..58af1f14 --- /dev/null +++ b/src/panel/widgets/volume-level.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +enum VolumeLevel +{ + VOLUME_LEVEL_MUTE = 0, + VOLUME_LEVEL_LOW, + VOLUME_LEVEL_MED, + VOLUME_LEVEL_HIGH, + VOLUME_LEVEL_OOR, /* Out of range */ +}; + +const std::map icon_name_from_state = { + {VOLUME_LEVEL_MUTE, "audio-volume-muted"}, + {VOLUME_LEVEL_LOW, "audio-volume-low"}, + {VOLUME_LEVEL_MED, "audio-volume-medium"}, + {VOLUME_LEVEL_HIGH, "audio-volume-high"}, + {VOLUME_LEVEL_OOR, "audio-volume-muted"}, +}; diff --git a/src/panel/widgets/volume.cpp b/src/panel/widgets/volume.cpp index 8d78b872..15b90a4f 100644 --- a/src/panel/widgets/volume.cpp +++ b/src/panel/widgets/volume.cpp @@ -2,14 +2,7 @@ #include #include "volume.hpp" -enum VolumeLevel -{ - VOLUME_LEVEL_MUTE = 0, - VOLUME_LEVEL_LOW, - VOLUME_LEVEL_MED, - VOLUME_LEVEL_HIGH, - VOLUME_LEVEL_OOR, /* Out of range */ -}; +#include "volume-level.hpp" static VolumeLevel get_volume_level(pa_volume_t volume, pa_volume_t max) { @@ -42,14 +35,6 @@ void WayfireVolume::update_icon() return; } - std::map icon_name_from_state = { - {VOLUME_LEVEL_MUTE, "audio-volume-muted"}, - {VOLUME_LEVEL_LOW, "audio-volume-low"}, - {VOLUME_LEVEL_MED, "audio-volume-medium"}, - {VOLUME_LEVEL_HIGH, "audio-volume-high"}, - {VOLUME_LEVEL_OOR, "audio-volume-muted"}, - }; - main_image.set_from_icon_name(icon_name_from_state.at(current)); } diff --git a/src/panel/widgets/wireplumber.cpp b/src/panel/widgets/wireplumber.cpp index 0ca82c84..fe6e255b 100644 --- a/src/panel/widgets/wireplumber.cpp +++ b/src/panel/widgets/wireplumber.cpp @@ -1,23 +1,12 @@ -#include #include #include -#include -#include "gio/gio.h" -#include "glib-object.h" -#include "glib.h" -#include "gtkmm/enums.h" -#include "gtkmm/gestureclick.h" -#include "gtkmm/label.h" -#include "gtkmm/separator.h" -#include "gtkmm/togglebutton.h" -#include "sigc++/connection.h" -#include "wp/proxy-interfaces.h" -#include "wp/proxy.h" - -#include +#include +#include + #include #include +#include "volume-level.hpp" #include "wireplumber.hpp" namespace WpCommon @@ -42,15 +31,6 @@ namespace WpCommon void on_object_removed(WpObjectManager *manager, gpointer node, gpointer data); } -enum VolumeLevel -{ - VOLUME_LEVEL_MUTE = 0, - VOLUME_LEVEL_LOW, - VOLUME_LEVEL_MED, - VOLUME_LEVEL_HIGH, - VOLUME_LEVEL_OOR, /* Out of range */ -}; - enum class FaceChoice { LAST_CHANGE, @@ -63,15 +43,6 @@ static const gchar *DEFAULT_NODE_MEDIA_CLASSES[] = { "Audio/Source", }; -const std::map icon_name_from_state = { - {VOLUME_LEVEL_MUTE, "audio-volume-muted"}, - {VOLUME_LEVEL_LOW, "audio-volume-low"}, - {VOLUME_LEVEL_MED, "audio-volume-medium"}, - {VOLUME_LEVEL_HIGH, "audio-volume-high"}, - {VOLUME_LEVEL_OOR, "audio-volume-muted"}, -}; - - static VolumeLevel volume_icon_for(double volume) { double max = 1.0; @@ -123,8 +94,6 @@ WfWpControl::WfWpControl(WpPipewireObject *obj, WayfireWireplumber *parent_widge name = "Unnamed"; } - name = g_strdup_printf("%s %d", name, id); - label.set_text(Glib::ustring(name)); button.set_child(volume_icon);