Additional options when muted for the PulseAudio-Slider module:

- Add "muted" class for styling the muted state.
- Add "zero-on-mute" boolean option to control slider position
  when muted.
- Add "unmute-on-volume-change" boolean option to control whether
  to automatically unmute when the volume changes.
This commit is contained in:
finitemonkey
2026-05-17 08:22:17 +01:00
parent 05945748dc
commit 0351ac8fb2
5 changed files with 98 additions and 65 deletions
+9 -5
View File
@@ -6,10 +6,6 @@
#include "util/audio_backend.hpp" #include "util/audio_backend.hpp"
namespace waybar::modules { namespace waybar::modules {
enum class PulseaudioSliderTarget {
Sink,
Source,
};
class PulseaudioSlider : public ASlider { class PulseaudioSlider : public ASlider {
public: public:
@@ -21,7 +17,15 @@ class PulseaudioSlider : public ASlider {
private: private:
std::shared_ptr<util::AudioBackend> backend = nullptr; std::shared_ptr<util::AudioBackend> backend = nullptr;
PulseaudioSliderTarget target = PulseaudioSliderTarget::Sink; util::PulseaudioTarget target = util::PulseaudioTarget::Sink;
bool zero_on_mute = true;
bool unmute_on_volume_change = true;
// zero_on_mute and unmute_on_volume_change default to true
// in order to maintain previous behaviour when using a
// config in which these values are undefined
bool previously_muted = false;
}; };
} // namespace waybar::modules } // namespace waybar::modules
+9
View File
@@ -14,6 +14,11 @@
namespace waybar::util { namespace waybar::util {
enum class PulseaudioTarget {
Sink,
Source,
};
class AudioBackend { class AudioBackend {
private: private:
static void subscribeCb(pa_context*, pa_subscription_event_type_t, uint32_t, void*); static void subscribeCb(pa_context*, pa_subscription_event_type_t, uint32_t, void*);
@@ -92,6 +97,10 @@ class AudioBackend {
void toggleSourceMute(); void toggleSourceMute();
void toggleSourceMute(bool); void toggleSourceMute(bool);
uint16_t getVolume(PulseaudioTarget) const;
bool getMuted(PulseaudioTarget) const;
void unmute(PulseaudioTarget);
bool isBluetooth(); bool isBluetooth();
}; };
+21 -1
View File
@@ -32,6 +32,17 @@ The volume can be controlled by dragging the slider across the bar or clicking o
default: false ++ default: false ++
Enables this module to consume all left over space dynamically. Enables this module to consume all left over space dynamically.
*zero-on-mute*: ++
typeof: bool ++
default: true ++
`true` = The slider will be set to `min` when the source/sink is muted. ++
`false` = The slider will continue to show the unmuted volume level when the source/sink is muted.
*unmute-on-volume-change*: ++
typeof: bool ++
default: true ++
Specifies whether to unmute a muted souce/sink when its volume is changed by the user moving the slider.
# EXAMPLES # EXAMPLES
``` ```
@@ -41,7 +52,9 @@ The volume can be controlled by dragging the slider across the bar or clicking o
"pulseaudio/slider": { "pulseaudio/slider": {
"min": 0, "min": 0,
"max": 100, "max": 100,
"orientation": "horizontal" "orientation": "horizontal",
"zero-on-mute": false,
"unmute-on-volume-change": false
} }
``` ```
@@ -52,6 +65,9 @@ The slider is a component with multiple CSS Nodes, of which the following are ex
*#pulseaudio-slider*: ++ *#pulseaudio-slider*: ++
Controls the style of the box *around* the slider and bar. Controls the style of the box *around* the slider and bar.
*#pulseaudio-slider.muted*: ++
Controls the style when the audio source/sink is muted.
*#pulseaudio-slider slider*: ++ *#pulseaudio-slider slider*: ++
Controls the style of the slider handle. Controls the style of the slider handle.
@@ -85,4 +101,8 @@ The slider is a component with multiple CSS Nodes, of which the following are ex
border-radius: 5px; border-radius: 5px;
background: green; background: green;
} }
#pulseaudio-slider.muted highlight {
background-color: orange;
}
``` ```
+28 -52
View File
@@ -10,73 +10,49 @@ PulseaudioSlider::PulseaudioSlider(const std::string& id, const Json::Value& con
if (config_["target"].isString()) { if (config_["target"].isString()) {
std::string target = config_["target"].asString(); std::string target = config_["target"].asString();
if (target == "sink") { if (target == "sink") {
this->target = PulseaudioSliderTarget::Sink; this->target = util::PulseaudioTarget::Sink;
} else if (target == "source") { } else if (target == "source") {
this->target = PulseaudioSliderTarget::Source; this->target = util::PulseaudioTarget::Source;
} }
} }
if (config_["zero-on-mute"].isBool()) {
zero_on_mute = config_["zero-on-mute"].asBool();
}
if (config_["unmute-on-volume-change"].isBool()) {
unmute_on_volume_change = config_["unmute-on-volume-change"].asBool();
}
} }
void PulseaudioSlider::update() { void PulseaudioSlider::update() {
switch (target) { uint16_t display_value = backend->getVolume(target);
case PulseaudioSliderTarget::Sink: bool is_muted = backend->getMuted(target);
if (backend->getSinkMuted()) {
scale_.set_value(min_);
} else {
scale_.set_value(backend->getSinkVolume());
}
break;
case PulseaudioSliderTarget::Source: if (is_muted && !previously_muted) {
if (backend->getSourceMuted()) { if (zero_on_mute) {
scale_.set_value(min_); display_value = min_;
} else {
scale_.set_value(backend->getSourceVolume());
} }
break; scale_.get_style_context()->add_class("muted");
} else if (previously_muted && !is_muted) {
scale_.get_style_context()->remove_class("muted");
} }
scale_.set_value(display_value);
previously_muted = is_muted;
} }
void PulseaudioSlider::onValueChanged() { void PulseaudioSlider::onValueChanged() {
bool is_mute = false; uint16_t slider_value = scale_.get_value();
switch (target) { // Avoid setting sink/source to volume 0 if the user muted it via other means.
case PulseaudioSliderTarget::Sink: if (!backend->getMuted(target) || slider_value != 0) {
if (backend->getSinkMuted()) { if (unmute_on_volume_change) {
is_mute = true; backend->unmute(target);
} }
break; backend->changeVolume(slider_value, min_, max_);
case PulseaudioSliderTarget::Source:
if (backend->getSourceMuted()) {
is_mute = true;
} }
break;
}
uint16_t volume = scale_.get_value();
if (is_mute) {
// Avoid setting sink/source to volume 0 if the user muted if via another mean.
if (volume == 0) {
return;
}
// If the sink/source is mute, but the user clicked the slider, unmute it!
else {
switch (target) {
case PulseaudioSliderTarget::Sink:
backend->toggleSinkMute(false);
break;
case PulseaudioSliderTarget::Source:
backend->toggleSourceMute(false);
break;
}
}
}
backend->changeVolume(volume, min_, max_);
} }
} // namespace waybar::modules } // namespace waybar::modules
+24
View File
@@ -259,6 +259,14 @@ void AudioBackend::serverInfoCb(pa_context* context, const pa_server_info* i, vo
pa_context_get_source_info_list(context, sourceInfoCb, data); pa_context_get_source_info_list(context, sourceInfoCb, data);
} }
uint16_t AudioBackend::getVolume(PulseaudioTarget target) const {
if (target == PulseaudioTarget::Source) {
return source_volume_;
} else {
return volume_;
}
}
void AudioBackend::changeVolume(uint16_t volume, uint16_t min_volume, uint16_t max_volume) { void AudioBackend::changeVolume(uint16_t volume, uint16_t min_volume, uint16_t max_volume) {
// Early return if context is not ready // Early return if context is not ready
if ((context_ == nullptr) || pa_context_get_state(context_) != PA_CONTEXT_READY) { if ((context_ == nullptr) || pa_context_get_state(context_) != PA_CONTEXT_READY) {
@@ -368,6 +376,14 @@ void AudioBackend::changeVolume(ChangeType change_type, double step, uint16_t ma
pa_threaded_mainloop_unlock(mainloop_); pa_threaded_mainloop_unlock(mainloop_);
} }
bool AudioBackend::getMuted(PulseaudioTarget target) const {
if (target == PulseaudioTarget::Source) {
return source_muted_;
} else {
return muted_;
}
}
void AudioBackend::toggleSinkMute() { void AudioBackend::toggleSinkMute() {
if (context_ == nullptr || pa_context_get_state(context_) != PA_CONTEXT_READY) return; if (context_ == nullptr || pa_context_get_state(context_) != PA_CONTEXT_READY) return;
muted_ = !muted_; muted_ = !muted_;
@@ -404,6 +420,14 @@ void AudioBackend::toggleSourceMute(bool mute) {
pa_threaded_mainloop_unlock(mainloop_); pa_threaded_mainloop_unlock(mainloop_);
} }
void AudioBackend::unmute(PulseaudioTarget target) {
if (target == PulseaudioTarget::Source) {
toggleSourceMute(false);
} else {
toggleSinkMute(false);
}
}
bool AudioBackend::isBluetooth() { bool AudioBackend::isBluetooth() {
return monitor_.find("a2dp_sink") != std::string::npos || // PulseAudio return monitor_.find("a2dp_sink") != std::string::npos || // PulseAudio
monitor_.find("a2dp-sink") != std::string::npos || // PipeWire monitor_.find("a2dp-sink") != std::string::npos || // PipeWire