Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
63ce587
Added wireplumber mixer widget
AKArien Dec 15, 2025
3f0f0d1
Added scroll control in full mixer
AKArien Nov 2, 2025
f6b9237
Added middle click to mute
AKArien Nov 2, 2025
92440cf
add configuration options for wireplumber widget
AKArien Nov 2, 2025
e3d6f90
uncrustify and style compliance
AKArien Dec 15, 2025
57d5537
uncrustify ?
AKArien Nov 3, 2025
c4aac9b
remove section of the code pending on pr 306 (extra options)
AKArien Nov 3, 2025
a6bfee9
uncrustify ; though i cannot agree oops lol
AKArien Nov 3, 2025
8cd51f9
Revert "remove section of the code pending on pr 306 (extra options)"
AKArien Nov 3, 2025
089c88f
remove logic pertaining to pr 306 (vertical panel layouts)
AKArien Nov 3, 2025
7325642
fixed include in animated-scale.cpp
AKArien Nov 3, 2025
6885c29
add popup on change option
AKArien Nov 3, 2025
fbfc51b
added support for mute gestures to individual control
AKArien Nov 3, 2025
ea746c3
added defaults, fallbacks and corrected compliance with config options
AKArien Nov 3, 2025
a2a9eed
fix scroll gesture, malformed panel.xml
AKArien Nov 3, 2025
77a6c62
cleaned up a bit middle click to mute
AKArien Nov 3, 2025
1f0a3c8
fixed improper naming of on -> handle_config_reload
AKArien Nov 3, 2025
287536d
removed obsolete functions
AKArien Nov 3, 2025
21306b7
fixed left click actions on widget ; comments
AKArien Nov 3, 2025
6bf91e8
added deselection guard for default + explanation
AKArien Nov 4, 2025
13fdf9e
adjust logic of updates in on_mixer_changed ; removed unused enum ; c…
AKArien Nov 4, 2025
86ebf6e
cleaup
AKArien Nov 6, 2025
86cb8bb
fix popover jank and incorrect settings reloading
AKArien Nov 6, 2025
1d427f9
uncrustify
AKArien Nov 6, 2025
91f7074
fixed gestures connection both in control and widget ; cleanup
AKArien Nov 6, 2025
3da844f
lil forgotten
AKArien Nov 6, 2025
848fa40
uncrustify
AKArien Nov 6, 2025
62bb72d
fixed doubling in existing widgets when catching up new one
AKArien Nov 16, 2025
1cb5f06
de-duplicated icon_name_from_state
AKArien Nov 16, 2025
2dd2dee
removed non breaking space and french quotes from comments
AKArien Nov 16, 2025
709fa32
use std::string_view for string comparison
AKArien Nov 16, 2025
dbb30bc
fixed configuration to use .value() missed it oops
AKArien Nov 16, 2025
afbf13e
unique instead of raw pointers
AKArien Nov 16, 2025
bdb2a2e
separators and labels not by new
AKArien Nov 16, 2025
69af698
add autos, make construction less barbaric
AKArien Nov 16, 2025
a47ab40
cleanup
AKArien Nov 16, 2025
650e254
exported volumelevel and icons to be common between volume and wirepl…
AKArien Nov 16, 2025
384e94a
fixed broken setting of default node
AKArien Dec 15, 2025
71503d2
added explanation to volume-level.hpp
AKArien Dec 15, 2025
44f38e9
fix indentation in util meson
AKArien Dec 15, 2025
7f49733
uncrustify
AKArien Dec 15, 2025
6cd0410
fix headers
AKArien Dec 15, 2025
333881e
removed stuff from old pulseaudio volume code
AKArien Dec 21, 2025
b82e1e8
added and cleaned up comments, declarations and small moves
AKArien Jan 4, 2026
5b6ab53
separate wireplumber styles from volume and add size option
AKArien Jan 4, 2026
966d2d2
uncrustify
AKArien Jan 4, 2026
e3b5ad7
now uses a singleton, split into multiple files
AKArien Jan 5, 2026
c1b097d
fix face choice for default sink/source, static for config
AKArien Jan 5, 2026
4316146
remove warningns for volume and wireplumber + ipc header
AKArien Jan 6, 2026
ef3a4fe
minimum value of 0 for wireplumber scroll sensitifivy
AKArien Jan 6, 2026
503d744
improved icons handling and made it in common between volume and wp
AKArien Jan 6, 2026
5bdf3a7
added volume-level to build and build messages for volume/wp being un…
AKArien Jan 6, 2026
9c46f51
uncrustify
AKArien Jan 6, 2026
776b4df
use c++ glib instead of C glib functions
AKArien Jan 6, 2026
27dee56
fixed on_mixer_changed logic
AKArien Jan 6, 2026
dae23d6
fixed popups and ignore control that just did the change
AKArien Jan 6, 2026
6bc0fd2
add uniform styles
AKArien Jan 6, 2026
156e8ee
cancel hiding of the popover after user interaction
AKArien Jan 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ wfconfig = dependency('wf-config', version: '>=0.7.0') #TODO fallback submodule
epoxy = dependency('epoxy')
gtklayershell = dependency('gtk4-layer-shell-0', fallback: ['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'))
xkbregistry = dependency('xkbregistry')
Expand All @@ -35,6 +36,10 @@ if libpulse.found()
add_project_arguments('-DHAVE_PULSE=1', language: 'cpp')
endif

if wireplumber.found()
add_project_arguments('-DHAVE_WIREPLUMBER=1', language: 'cpp')
Copy link
Collaborator

Choose a reason for hiding this comment

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

Use spaces here

Copy link
Author

Choose a reason for hiding this comment

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

Now, apologies if i am somehow getting this wrong, but it seems other indentation in this file uses tabs ? such as the above libpulse check

endif

needs_libinotify = ['freebsd', 'dragonfly'].contains(host_machine.system())
libinotify = dependency('libinotify', required: needs_libinotify)

Expand Down
8 changes: 7 additions & 1 deletion meson_options.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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',
)
)
94 changes: 94 additions & 0 deletions metadata/panel.xml
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,100 @@
</option>
</group>
<group>
<_short>Wireplumber mixer</_short>
<option name="wp_popup_on_change" type="bool">
<_short>Pop up on change</_short>
<_long>
Wether to show a popup with the new values upon a change to the quick action target
</_long>
<default>true</default>
</option>
<option name="wp_display_timeout" type="double">
<_short>Display timeout</_short>
<default>2.5</default>
</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
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.

<min>0</min>
</option>
<option name="wp_invert_scroll" type="bool">
<_short>Invert scroll</_short>
<_long>
Inverts which scroll direction raises and lowers volume
</_long>
<default>false</default>
</option>
<option name="wp_face_choice" type="string">
<_short>Quick action target</_short>
<_long>
Which audio control will be the target of the "Quick action" (choice for the click action)
</_long>
<default>last_change</default>
<desc>
<value>last_change</value>
<_name>Last changed volume</_name>
</desc>
<desc>
<value>default_sink</value>
<_name>Default sink</_name>
</desc>
<desc>
<value>default_source</value>
<_name>Default source</_name>
</desc>
</option>
<option name="wp_left_click_action" type="string">
<_short>Left click action</_short>
<default>show_mixer</default>
<desc>
<value>show_mixer</value>
<_name>Show full mixer</_name>
</desc>
<desc>
<value>show_face</value>
<_name>Show quick action target</_name>
</desc>
</option>
<option name="wp_middle_click_action" type="string">
<_short>Middle click action</_short>
<default>mute_face</default>
<desc>
<value>show_mixer</value>
<_name>Show full mixer</_name>
</desc>
<desc>
<value>show_face</value>
<_name>Show quick action target</_name>
</desc>
<desc>
<value>mute_face</value>
<_name>Mute quick action target</_name>
</desc>
</option>
<option name="wp_right_click_action" type="string">
<_short>Right click action</_short>
<default>show_face</default>
<desc>
<value>show_mixer</value>
<_name>Show full mixer</_name>
</desc>
<desc>
<value>show_face</value>
<_name>Show quick action target</_name>
</desc>
<desc>
<value>mute_face</value>
<_name>Mute quick action target</_name>
</desc>
</option>
<option name="wp_icon_size" type="int">
<_short>Volume Icon Size</_short>
<default>32</default>
<min>1</min>
</option>
</group>
<group>
<_short>Notifications</_short>
<option name="notifications_autohide_timeout" type="double">
<_short>Notifications Display Timeout</_short>
Expand Down
19 changes: 18 additions & 1 deletion src/panel/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,25 @@ deps = [
]

if libpulse.found()
widget_sources += 'widgets/volume.cpp'
widget_sources += [
'widgets/volume.cpp',
'widgets/volume-level.cpp',
]
deps += [libpulse, libgvc]
else
message('Pulse not found, volume widget will not be available.')
endif

if wireplumber.found()
widget_sources += [
'widgets/wireplumber/wireplumber.cpp',
'widgets/wireplumber/wf-wp-control.cpp',
'widgets/wireplumber/wp-common.cpp',
'widgets/volume-level.cpp',
]
deps += wireplumber
else
message('Wireplumber not found, mixer widget will not be available.')
endif

executable(
Expand Down
16 changes: 14 additions & 2 deletions src/panel/panel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
#include <css-config.hpp>
#include "panel.hpp"

#include "wf-ipc.hpp"
#include "widgets/battery.hpp"
#include "widgets/command-output.hpp"
#include "widgets/language.hpp"
Expand All @@ -27,6 +26,9 @@
#ifdef HAVE_PULSE
#include "widgets/volume.hpp"
#endif
#ifdef HAVE_WIREPLUMBER
#include "widgets/wireplumber/wireplumber.hpp"
#endif
#include "widgets/window-list/window-list.hpp"
#include "widgets/notifications/notification-center.hpp"
#include "widgets/tray/tray.hpp"
Expand Down Expand Up @@ -161,12 +163,21 @@ class WayfirePanel::impl
#ifdef HAVE_PULSE
return Widget(new WayfireVolume());
#else
#warning "Pulse not found, volume widget will not be available."
std::cerr << "Built without pulse support, volume widget "
" is not available." << std::endl;
#endif
}

if (name == "wireplumber")
{
#ifdef HAVE_WIREPLUMBER
return Widget(new WayfireWireplumber());
#else
std::cerr << "Built without wireplumber support, mixer widget "
" is not available." << std::endl;
#endif
}

if (name == "window-list")
{
return Widget(new WayfireWindowList(output));
Expand Down Expand Up @@ -364,6 +375,7 @@ void WayfirePanelApp::on_activate()
new CssFromConfigInt("panel/battery_icon_size", ".battery image{-gtk-icon-size:", "px;}");
new CssFromConfigInt("panel/network_icon_size", ".network{-gtk-icon-size:", "px;}");
new CssFromConfigInt("panel/volume_icon_size", ".volume{-gtk-icon-size:", "px;}");
new CssFromConfigInt("panel/wp_icon_size", ".wireplumber{-gtk-icon-size:", "px;}");
new CssFromConfigInt("panel/notifications_icon_size", ".notification-center{-gtk-icon-size:", "px;}");
new CssFromConfigInt("panel/tray_icon_size", ".tray-button{-gtk-icon-size:", "px;}");
new CssFromConfigString("panel/background_color", ".wf-panel{background-color:", ";}");
Expand Down
42 changes: 42 additions & 0 deletions src/panel/widgets/volume-level.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#include <map>

#include "volume-level.hpp"

enum VolumeLevel
{
VOLUME_LEVEL_MUTE = 0,
VOLUME_LEVEL_LOW,
VOLUME_LEVEL_MED,
VOLUME_LEVEL_HIGH,
VOLUME_LEVEL_OOR, /* Out of range */
};

const std::map<VolumeLevel, std::string> volume_icons = {
{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 is expected to be from 0 to 1
std::string volume_icon_for(double volume)
{
double max = 1.0;
auto third = max / 3;
if (volume == 0)
{
return volume_icons.at(VOLUME_LEVEL_MUTE);
} else if ((volume > 0) && (volume <= third))
{
return volume_icons.at(VOLUME_LEVEL_LOW);
} else if ((volume > third) && (volume <= (third * 2)))
{
return volume_icons.at(VOLUME_LEVEL_MED);
} else if ((volume > (third * 2)) && (volume <= max))
{
return volume_icons.at(VOLUME_LEVEL_HIGH);
}

return volume_icons.at(VOLUME_LEVEL_OOR);
}
6 changes: 6 additions & 0 deletions src/panel/widgets/volume-level.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#pragma once

#include <string>

// helper in common for volume and wireplumber widgets icons handling
std::string volume_icon_for(double volume);
88 changes: 4 additions & 84 deletions src/panel/widgets/volume.cpp
Original file line number Diff line number Diff line change
@@ -1,97 +1,18 @@
#include <gtkmm.h>
#include <iostream>
#include <glibmm.h>
#include "volume.hpp"
#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<Gdk::FrameClock> 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<void()> callback)
{
this->user_changed_callback = callback;
}

enum VolumeLevel
{
VOLUME_LEVEL_MUTE = 0,
VOLUME_LEVEL_LOW,
VOLUME_LEVEL_MED,
VOLUME_LEVEL_HIGH,
VOLUME_LEVEL_OOR, /* Out of range */
};

static VolumeLevel get_volume_level(pa_volume_t volume, pa_volume_t max)
{
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;
}
#include "volume-level.hpp"

void WayfireVolume::update_icon()
{
VolumeLevel current =
get_volume_level(volume_scale.get_target_value(), max_norm);

if (gvc_stream && gvc_mixer_stream_get_is_muted(gvc_stream))
{
main_image.set_from_icon_name("audio-volume-muted");
main_image.set_from_icon_name(volume_icon_for(0)); // mute
return;
}

std::map<VolumeLevel, std::string> 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));
main_image.set_from_icon_name(volume_icon_for(volume_scale.get_target_value() / (double)max_norm));
}

bool WayfireVolume::on_popover_timeout(int timer)
Expand Down Expand Up @@ -195,8 +116,7 @@ void WayfireVolume::on_default_sink_changed()
volume_scale.set_increments(max_norm * scroll_sensitivity,
max_norm * scroll_sensitivity * 2);

/* Finally, update the displayed volume. However, do not show the
* popup */
/* Finally, update the displayed volume. However, do not show the popup */
set_volume(gvc_mixer_stream_get_volume(gvc_stream), VOLUME_FLAG_NO_ACTION);
}

Expand Down
Loading