Merge pull request #5045 from finitemonkey/master

Additional options when muted for the PulseAudio-Slider module
This commit is contained in:
Alexis Rouillard
2026-07-03 22:09:42 +02:00
committed by GitHub
5 changed files with 100 additions and 66 deletions
+9 -6
View File
@@ -6,11 +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:
PulseaudioSlider(const std::string&, const Json::Value&); PulseaudioSlider(const std::string&, const Json::Value&);
@@ -21,7 +16,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();
}; };
+24 -4
View File
@@ -28,9 +28,20 @@ The volume can be controlled by dragging the slider across the bar or clicking o
The orientation of the slider. Can be either `horizontal` or `vertical`. The orientation of the slider. Can be either `horizontal` or `vertical`.
*expand*: ++ *expand*: ++
typeof: bool ++ typeof: bool ++
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;
}
``` ```
+31 -53
View File
@@ -10,73 +10,51 @@ 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) {
if (backend->getSourceMuted()) { if (zero_on_mute) {
scale_.set_value(min_); display_value = min_;
} else { }
scale_.set_value(backend->getSourceVolume()); if (!previously_muted) {
} scale_.get_style_context()->add_class("muted");
break; }
} else if (previously_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;
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(slider_value, min_, max_);
} }
backend->changeVolume(volume, min_, max_);
} }
} // namespace waybar::modules } // namespace waybar::modules
+24
View File
@@ -261,6 +261,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) {
@@ -370,6 +378,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_;
@@ -406,6 +422,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