Skip to content

Conversation

@AKArien
Copy link
Contributor

@AKArien AKArien commented Nov 2, 2025

The widget gives an interface for controlling the volume and muting of sinks, sources and output streams, as well as selection of the default sinks and sources (input/output devices).
By default, upon changes to a volume / mute, it pops up an interactible bubble where volume/mute can be changed again.
Has scroll to change volume and possibility of showing the controls of only a single track at a time, through the « face/quick action target »
Has the following settings :

  • Popup on change : enable or disable the « popup on change » behavior. If on, the bubble described above will appear when the « face » volume or mute changes.
  • Timeout for hiding popups
  • Length of sliders
  • Scroll sensitivity (defaults to 1), and inversion
  • The buttons to mute and select default are on the right by default, but can be switched to the left
  • Selection of what should be the « quick target » for muting and to be shown for a quick control with the corresponding actions : last changed volume, default sink, default source.
  • Assignment of view of the full mixer, quick target and muting between left, middle and right click (mute not available on left click)
  • icon size

It’s build is behind a new build option and uses the wireplumber C library.
Code for icon selection and the custom gtk scale have been moved out of the pulseaudio volume widget for re-use.
This PR slightly alters the volume widget, by changing the « volume out of range » icon the « emblem-unreadable » icon instead of « audio-volume-muted »
Additionally, the compile-time warning that was emitted if libpulse was not found and the volume widget not built has been moved to a meson notice.

@AKArien AKArien marked this pull request as draft November 2, 2025 11:41
@AKArien AKArien changed the title (DRAFT) Added wireplumber mixer widget Added wireplumber mixer widget Nov 3, 2025
@AKArien AKArien marked this pull request as ready for review November 3, 2025 12:43
@AKArien AKArien marked this pull request as draft November 4, 2025 15:24
@AKArien
Copy link
Contributor Author

AKArien commented Nov 4, 2025

Actually, the audio sources controls seem very broken. Re-marking as draft.

@AKArien
Copy link
Contributor Author

AKArien commented Nov 6, 2025

Should be good !'m not running into bugs in my own use anymore.
The one thing i’m not quite happy about is the hacky way of « keeping the popover out but swapping it’s content » on click by closing it right before, so it gets opened again by the click event (line 469/490).
It might also help with readability to split it into multiple files ?

@AKArien AKArien marked this pull request as ready for review November 6, 2025 16:29
Copy link
Collaborator

@NamorNiradnug NamorNiradnug left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, thank for your PR! Please consider fixing the issues.


static std::vector<WayfireWireplumber*> widgets;

static void init_wp();
Copy link
Collaborator

@NamorNiradnug NamorNiradnug Nov 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do not define static functions and objects in headers. These functions should go into wireplumber.cpp.

Also I don't like the global variable above...

Copy link
Contributor Author

@AKArien AKArien Nov 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also I don't like the global variable above...

As far as i see, the alternative is connect the signals with the WayfireWireplumber as user data, for each widget ? Is that better or worse ?

#include <gtkmm.h>
#include <iostream>
#include <glibmm.h>
#include "gio/gio.h"
Copy link
Collaborator

@NamorNiradnug NamorNiradnug Nov 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use <...> for library headers.

mute_conn = button.signal_toggled().connect(
[this, id] ()
{
GVariantBuilder gvb = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE_VARDICT);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use Glib::Variant instead of C glib functions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, I’m sorry but for some reason i can’t seem to figure it out… Do you know how to do it ?
My best shot is something like this :

        auto dict = Glib::VariantDict::create();
        dict->insert_value_variant("mute", Glib::Variant<bool>::create(button.get_active()));
        gboolean res FALSE;
        g_signal_emit_by_name(WpCommon::mixer_api, "set-volume", id, dict->gobj(), &res);

But it aborts on a type info check assertion and i seem to phenomenally fail at looking anything up.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am no dbus expert but take a look at this other PR, maybe it would give you an inspiration: #311 (comment)

return;
}

std::map<VolumeLevel, std::string> icon_name_from_state = {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

icon_name_from_state can be made static const.


WfWpControl*WfWpControl::copy()
{
WfWpControl *copy = new WfWpControl(object, parent);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do not return objects by raw pointers. Either use std::unique_ptr or return by value.

Also why don't you just use the default copy constructor? Ah, because there are callbacks that store a pointer to the object. Then you probably want to make the object non-movable.

if (g_strcmp0(type, "Audio/Sink") == 0)
{
which_box = &(widget->sinks_box);
control = new WfWpControlDevice(obj, widget);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't manage memory with new/delete.

Probably objects_to_control should own the objects through std::unique_ptr.

{
// adds a new widget to the appropriate section

WpPipewireObject *obj = (WpPipewireObject*)object;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

auto *


WpPipewireObject *obj = (WpPipewireObject*)object;

const gchar *type = wp_pipewire_object_get_property(obj, PW_KEY_MEDIA_CLASS);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

std::string_view

WfOption<std::string> str_wp_right_click_action{"panel/wp_right_click_action"};
WfOption<std::string> str_wp_middle_click_action{"panel/wp_middle_click_action"};

if ((std::string)str_face_choice == (std::string)"last_change")
Copy link
Collaborator

@NamorNiradnug NamorNiradnug Nov 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't cast a string constant to std::string for comparison. std::string has operator== with const char *.

Also please use .value() method of WfOption instead casting it to std::string: it looks better and would allow to optimize unnecessary copy if value() would return a reference instead of copy (it currently doesn't, but I think we should fix it).

reload_config();

// boxes hierarchy and labeling
auto r1 = Gtk::Orientation::HORIZONTAL;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't use such aliases. It decreases code readability.

@ammen99
Copy link
Member

ammen99 commented Nov 15, 2025

It might also help with readability to split it into multiple files ?

Feel free to do so if you find it better. We already do that for window-list, simply create a subdirectory and place all files in it.

@AKArien AKArien force-pushed the extra-widgets-clean branch from 196b586 to 7f49733 Compare December 15, 2025 09:54
AKArien added a commit to AKArien/wf-shell that referenced this pull request Dec 15, 2025
applied to not only the changes here but also in general
WayfireWM#307 (comment)
Copy link
Member

@ammen99 ammen99 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few things I noticed. I haven't read all the wireplumber bits in detail, I will test instead once the PR is finalized ;)

#ifdef HAVE_WIREPLUMBER
return Widget(new WayfireWireplumber());
#else
#warning "Wireplumber not found, mixer widget will not be available."
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this warning, if emitted at all, should be emitted from meson.build, not from the source file.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, based myself on the pulseaudio volume widget. Should i change it as well ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good!

Copy link
Contributor Author

@AKArien AKArien Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if emitted at all

Changed it to be just a message, assuming that’s what you’d want.


/* Setup popover */
popover->set_child(master_box);
popover->get_style_context()->add_class("volume-popover");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here, should be a different name for proper css styling support.

virtual ~WayfireWireplumber();
};

namespace WpCommon
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder whether this really has to be in the .hpp file, it is just an implementation detail and used only in wireplumber.cpp, right?

</option>
<option name="wp_scroll_sensitivity" type="double">
<_short>Scroll sensitivity</_short>
<default>1</default>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could set a value for this (0?) Otherwise, we could allow negative values but then we can remove the invert option.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m sorry, i’m not quite sure what you mean ?
As for invert, we could indeed remove it, but i included it because i can’t help but feel it is way nicer as a user to have a toggle rather than making the value negative, at least when using wcm. Should it go ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m sorry, i’m not quite sure what you mean ?

You can set minimum and maximum values for integer options. <min>0</min> for example. I am fine with either retaining both options and setting the minimum to 0, or merging them together (in which case minimum makes no sense). Pick whichever one you like more.

#include "volume-level.hpp"
#include "wireplumber.hpp"

namespace WpCommon
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one seems like it should be a singleton class.

* We re-use the core, manager and all other objects
*/

WpCommon::widgets.push_back(this);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once you have a singleton class for the WpCommon stuff, you can move all this code in a method there (add_widget / remove_widget)

"Audio/Source",
};

static VolumeLevel volume_icon_for(double volume)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the same code as in volume.cpp, I think we can avoid duplication here. pa_volume_t is a uint32_t, so we can simply use double for the type everywhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i’ve actually been writing a display brightness control widget that ends up having roughly the same thing again. Should i preemptively make it even more generic to account for it (or other stuff that might have be similar), and if it’s done soon enough, should i add it to this pr or make another one ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better leave it for later, lets finish this PR first and then worry about the next one.

void WayfireWireplumber::reload_config()
{
// big matching operation
WfOption<std::string> str_face_choice{"panel/wp_face_choice"};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These can be marked as static to avoid reloading them every time. Their values will change automatically when the config changes.

@ammen99
Copy link
Member

ammen99 commented Jan 6, 2026

@AKArien I see you have already done most of the changes, when you are ready with the rest, let me know to test this PR and hopefully merge afterwards.

@AKArien
Copy link
Contributor Author

AKArien commented Jan 6, 2026

@ammen99 i think i have addressed everything pointed out here, but i have a few little ui bugs to fix.

There’s also an issue that i really don’t understand and would like you to keep an eye on in your testing / review : sometimes, under circumstances i cannot nail down, the tray or the menu widgets will segfault, with gdb showing a backtrace in the Watcher destructor for the tray and the toggle_menu method for menu. This never happens without wireplumber in my config, and removing it is always an immediate fix. My widgets are usually like this :

widgets_center = separator1 menu separator1
widgets_left = tray wireplumber
widgets_right = notifications launchers battery clock

I hardly see how this could be related to my widget, but it seems i’m missing something ? Since removing wireplumber always stops it from happening. Again, i’m sorry if this has very little detail, but it is litterally all that i could find to be consistent.

@ammen99
Copy link
Member

ammen99 commented Jan 6, 2026

@AKArien I would strongly recommend compiling wf-shell with address sanitizer in this case. It is likely that you see the result of a memory corruption somewhere and address sanitizer very often catches issues like these.

@ammen99
Copy link
Member

ammen99 commented Jan 6, 2026

Also these are probably the UI things you mentioned need improvement, but: when I start the panel, the widget icon is empty and I need to change the volume once for an icon to show. Probably should choose a default face at startup? Also the button should have the flat style to match the rest of the panel's widgets. Look at the menu widget, it sets the flat class twice because the GtkMenuButton used here is special.

@AKArien
Copy link
Contributor Author

AKArien commented Jan 6, 2026

Also these are probably the UI things you mentioned need improvement, but: when I start the panel, the widget icon is empty and I need to change the volume once for an icon to show. Probably should choose a default face at startup? Also the button should have the flat style to match the rest of the panel's widgets. Look at the menu widget, it sets the flat class twice because the GtkMenuButton used here is special.

Yup, i had added something that would make the widget try to have a face, but it seems to not work. There’s also an issue with the face not actually being changed on external changes (not anymore !), and the popover sometimes closing prematurely (presumably due to fleeting call to check_set_popover_timeout) (neither !). Other bugs, i’m not aware of.

For the style, will do.

@AKArien
Copy link
Contributor Author

AKArien commented Jan 8, 2026

@ammen99 things are starting to feel pretty good here ! at least to me. I think it should be ready for testing. Updated the initial message, which should now list everything.

@AKArien
Copy link
Contributor Author

AKArien commented Jan 8, 2026

Also, i’ve noticed the wayfire github wiki, under contributing, says to use #pragma once instead of include ifdefs. With all of wf-shell seeming to use ifdefs, is it only a policy for wayfire itself ?

@ammen99
Copy link
Member

ammen99 commented Jan 9, 2026

Also, i’ve noticed the wayfire github wiki, under contributing, says to use #pragma once instead of include ifdefs. With all of wf-shell seeming to use ifdefs, is it only a policy for wayfire itself ?

In the beginning we used ifdefs, nowadays moving towards pragmas :)

Copy link
Member

@ammen99 ammen99 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM now, thanks for your excellent work! I tested and the basics work fine. If there are any leftover bugs, we can fix them later. One last typo I mentioned. Other things to consider now:

@AKArien
Copy link
Contributor Author

AKArien commented Jan 13, 2026

Thanks ! I’m glad you like it :)

maybe commented out

I mean, i’d be honored if it wasn’t :P

One last thing i’d really like to figure out is to limit the names widgets sizes, as soon as i figure out how to use contraints (currently, they can stretch out their control if they’re too long). There’s also the linear mode for the mixer api directly that i would like to figure out, as calculating it ourselves rarely leads to the slider recieving some enormous number and being visually to the max, while the volume is actually to 0.

Also, i think it would be good to unify and make more descriptive the naming of face/quick target (latter is the naming in metadata), and potentially a more representative one than « controls » ? I’m not sure they are quite understandable when not already knowing the whole features of the widget. Any ideas/oppinions ?

Finally, for documenting, should the example config file present the default configuration explicitely, or show another one one could use ?

@ammen99
Copy link
Member

ammen99 commented Jan 13, 2026

One last thing i’d really like to figure out is to limit the names widgets sizes, as soon as i figure out how to use contraints (currently, they can stretch out their control if they’re too long). There’s also the linear mode for the mixer api directly that i would like to figure out, as calculating it ourselves rarely leads to the slider recieving some enormous number and being visually to the max, while the volume is actually to 0.

Alright, if you want to work more on this before merging, I can wait.

Also, i think it would be good to unify and make more descriptive the naming of face/quick target (latter is the naming in metadata), and potentially a more representative one than « controls » ? I’m not sure they are quite understandable when not already knowing the whole features of the widget. Any ideas/oppinions ?

Indeed, the 'face' thing had me confused at first. I think quick target makes more sense. Controls seems ok to me.

Finally, for documenting, should the example config file present the default configuration explicitely, or show another one one could use ?

I think showcasing the default configuration would be fine.

@AKArien
Copy link
Contributor Author

AKArien commented Jan 18, 2026

@ammen99 This time should be final. Hopefully.
Updated the initial description again, but basically : fixed long names strecthing the widget, added tooltips, some margins and spacing, an option to put the icons on the left, and changed naming.
Also updated the icons, which applies back to the volume widget, OOR volume is now the emblem-unreadable icon.
I have also added wireplumber-dev to the build part of the CI, so that the compilation step actually builds the wireplumber widget.

Finally, i was wondering if you would like this widget to replace volume in the default configuration ?
With the adoption of pipewire out of the box on pretty much all major distros, it should work on the vast majority of systems, and i would like to think this widget is more polished than the pulseaudio one.
Against that stand the systems on which it will not work out of the box, how non-battle-tested it is, plus that some of the functionalities might be confusing the manual by the user’s side.

@AKArien AKArien force-pushed the extra-widgets-clean branch from b38560a to 27b203f Compare January 21, 2026 09:42
@ammen99
Copy link
Member

ammen99 commented Jan 21, 2026

Finally, i was wondering if you would like this widget to replace volume in the default configuration ?
With the adoption of pipewire out of the box on pretty much all major distros, it should work on the vast majority of systems, and i would like to think this widget is more polished than the pulseaudio one.
Against that stand the systems on which it will not work out of the box, how non-battle-tested it is, plus that some of the functionalities might be confusing the manual by the user’s side.

Would be best to first let the people test it for a while :) Otherwise I agree that this widget is in pretty much all ways superior to the old widget.

@AKArien
Copy link
Contributor Author

AKArien commented Jan 21, 2026

Alright then ! Glad you like it :)
As long as it’s the default by the time 1.0 comes around, ego will be fueled enough :P

I’ll fix the default, and then probably wait a few weeks before i’m 100% absolutely sure i don’t have anything to add or fix anymore.

@ammen99
Copy link
Member

ammen99 commented Jan 21, 2026

I think we are ready to merge, provided you fix the conflicts that GitHub seems to see in this PR ..

@AKArien
Copy link
Contributor Author

AKArien commented Jan 21, 2026

Excuse me, but i'm really confused ? Github shows me that there's no conflicts, i rebased on latest master earlier today

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants