Merge pull request #4908 from 7FM/master

pulseaudio: add target option to control source (microphone) volume
This commit is contained in:
Alexis Rouillard
2026-07-03 23:12:36 +02:00
committed by GitHub
7 changed files with 102 additions and 29 deletions
+1
View File
@@ -22,6 +22,7 @@ class Pulseaudio : public ALabel {
const std::vector<std::string> getPulseIcon() const;
std::shared_ptr<util::AudioBackend> backend = nullptr;
util::PulseaudioTarget target = util::PulseaudioTarget::Sink;
};
} // namespace waybar::modules
+6 -2
View File
@@ -27,12 +27,14 @@ class AudioBackend {
static void sourceInfoCb(pa_context*, const pa_source_info* i, int, void* data);
static void serverInfoCb(pa_context*, const pa_server_info*, void*);
static void volumeModifyCb(pa_context*, int, void*);
static void sourceVolumeModifyCb(pa_context*, int, void*);
void connectContext();
pa_threaded_mainloop* mainloop_;
pa_mainloop_api* mainloop_api_;
pa_context* context_;
pa_cvolume pa_volume_;
pa_cvolume pa_source_volume_;
// SINK
uint32_t sink_idx_{0};
@@ -72,8 +74,10 @@ class AudioBackend {
AudioBackend(std::function<void()> on_updated_cb, private_constructor_tag tag);
~AudioBackend();
void changeVolume(uint16_t volume, uint16_t min_volume = 0, uint16_t max_volume = 100);
void changeVolume(ChangeType change_type, double step = 1, uint16_t max_volume = 100);
void changeVolume(uint16_t volume, uint16_t min_volume = 0, uint16_t max_volume = 100,
PulseaudioTarget target = PulseaudioTarget::Sink);
void changeVolume(ChangeType change_type, double step = 1, uint16_t max_volume = 100,
PulseaudioTarget target = PulseaudioTarget::Sink);
void setIgnoredSinks(const Json::Value& config);
+14 -2
View File
@@ -43,18 +43,30 @@ The volume can be controlled by dragging the slider across the bar or clicking o
default: true ++
Specifies whether to unmute a muted souce/sink when its volume is changed by the user moving the slider.
*target*: ++
typeof: string ++
default: sink ++
The audio target to control. Can be either `sink` (output/speakers) or `source` (input/microphone).
# EXAMPLES
```
"modules-right": [
"pulseaudio/slider",
"pulseaudio/slider#out",
"pulseaudio/slider#in",
],
"pulseaudio/slider": {
"pulseaudio/slider#out": {
"min": 0,
"max": 100,
"orientation": "horizontal",
"zero-on-mute": false,
"unmute-on-volume-change": false
},
"pulseaudio/slider#in": {
"min": 0,
"max": 100,
"orientation": "horizontal",
"target": "source"
}
```
+5
View File
@@ -135,6 +135,11 @@ Additionally, you can control the volume by scrolling *up* or *down* while the c
default: false ++
Enables this module to consume all left over space dynamically.
*target*: ++
typeof: string ++
default: sink ++
The audio target to control when scrolling. Can be either `sink` (output/speakers) or `source` (input/microphone).
# FORMAT REPLACEMENTS
*{desc}*: Pulseaudio port's description, for bluetooth it'll be the device name.
+5 -1
View File
@@ -7,6 +7,10 @@ waybar::modules::Pulseaudio::Pulseaudio(const std::string& id, const Json::Value
backend = util::AudioBackend::getInstance([this] { this->dp.emit(); });
backend->setIgnoredSinks(config_["ignored-sinks"]);
if (config_["target"].isString() && config_["target"].asString() == "source") {
target = util::PulseaudioTarget::Source;
}
}
bool waybar::modules::Pulseaudio::handleScroll(GdkEventScroll* e) {
@@ -33,7 +37,7 @@ bool waybar::modules::Pulseaudio::handleScroll(GdkEventScroll* e) {
? util::ChangeType::Increase
: util::ChangeType::Decrease;
backend->changeVolume(change_type, step, max_volume);
backend->changeVolume(change_type, step, max_volume, target);
return true;
}
+1 -1
View File
@@ -53,7 +53,7 @@ void PulseaudioSlider::onValueChanged() {
if (unmute_on_volume_change) {
backend->unmute(target);
}
backend->changeVolume(slider_value, min_, max_);
backend->changeVolume(slider_value, min_, max_, target);
}
}
+67 -20
View File
@@ -24,8 +24,9 @@ AudioBackend::AudioBackend(std::function<void()> on_updated_cb, private_construc
source_volume_(0),
source_muted_(false),
on_updated_cb_(std::move(on_updated_cb)) {
// Initialize pa_volume_ with safe defaults
// Initialize pa_volume_ and pa_source_volume_ with safe defaults
pa_cvolume_init(&pa_volume_);
pa_cvolume_init(&pa_source_volume_);
mainloop_ = pa_threaded_mainloop_new();
if (mainloop_ == nullptr) {
throw std::runtime_error("pa_mainloop_new() failed.");
@@ -157,6 +158,19 @@ void AudioBackend::volumeModifyCb(pa_context* c, int success, void* data) {
}
}
void AudioBackend::sourceVolumeModifyCb(pa_context* c, int success, void* data) {
auto* backend = static_cast<AudioBackend*>(data);
if (success != 0) {
if ((backend->context_ != nullptr) &&
pa_context_get_state(backend->context_) == PA_CONTEXT_READY) {
pa_context_get_source_info_by_index(backend->context_, backend->source_idx_, sourceInfoCb,
data);
}
} else {
spdlog::debug("Source volume modification failed");
}
}
/*
* Called when the requested sink information is ready.
*/
@@ -236,6 +250,11 @@ void AudioBackend::sourceInfoCb(pa_context* /*context*/, const pa_source_info* i
void* data) {
auto* backend = static_cast<AudioBackend*>(data);
if (i != nullptr && backend->default_source_name_ == i->name) {
if (pa_cvolume_valid(&i->volume) != 0) {
backend->pa_source_volume_ = i->volume;
} else {
pa_cvolume_init(&backend->pa_source_volume_);
}
auto source_volume = static_cast<float>(pa_cvolume_avg(&(i->volume))) / float{PA_VOLUME_NORM};
backend->source_volume_ = std::round(source_volume * 100.0F);
backend->source_idx_ = i->index;
@@ -269,25 +288,31 @@ uint16_t AudioBackend::getVolume(PulseaudioTarget target) const {
}
}
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,
PulseaudioTarget target) {
// Early return if context is not ready
if ((context_ == nullptr) || pa_context_get_state(context_) != PA_CONTEXT_READY) {
spdlog::error("PulseAudio context not ready");
return;
}
bool is_source = target == PulseaudioTarget::Source;
// Select the appropriate stored volume structure
auto& ref_volume = is_source ? pa_source_volume_ : pa_volume_;
// Prepare volume structure
pa_cvolume pa_volume;
pa_cvolume_init(&pa_volume);
// Use existing volume structure if valid, otherwise create a safe default
if ((pa_cvolume_valid(&pa_volume_) != 0) && (pa_channels_valid(pa_volume_.channels) != 0)) {
pa_volume = pa_volume_;
if ((pa_cvolume_valid(&ref_volume) != 0) && (pa_channels_valid(ref_volume.channels) != 0)) {
pa_volume = ref_volume;
} else {
// Set stereo as a safe default
pa_volume.channels = 2;
spdlog::debug("Using default stereo volume structure");
// Mono for sources (microphones), stereo for sinks
pa_volume.channels = is_source ? 1 : 2;
spdlog::debug("Using default volume structure ({} channels)", pa_volume.channels);
}
// Set the volume safely
@@ -301,39 +326,56 @@ void AudioBackend::changeVolume(uint16_t volume, uint16_t min_volume, uint16_t m
// Apply the volume change
pa_threaded_mainloop_lock(mainloop_);
if (is_source) {
pa_context_set_source_volume_by_index(context_, source_idx_, &pa_volume, sourceVolumeModifyCb,
this);
} else {
pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, volumeModifyCb, this);
}
pa_threaded_mainloop_unlock(mainloop_);
}
void AudioBackend::changeVolume(ChangeType change_type, double step, uint16_t max_volume) {
void AudioBackend::changeVolume(ChangeType change_type, double step, uint16_t max_volume,
PulseaudioTarget target) {
// Early return if context is not ready
if ((context_ == nullptr) || pa_context_get_state(context_) != PA_CONTEXT_READY) {
spdlog::error("PulseAudio context not ready");
return;
}
bool is_source = target == PulseaudioTarget::Source;
// Select the appropriate stored volume structure and current volume
auto& ref_volume = is_source ? pa_source_volume_ : pa_volume_;
auto current_volume = is_source ? source_volume_ : volume_;
// Prepare volume structure
pa_cvolume pa_volume;
pa_cvolume_init(&pa_volume);
// Use existing volume structure if valid, otherwise create a safe default
if ((pa_cvolume_valid(&pa_volume_) != 0) && (pa_channels_valid(pa_volume_.channels) != 0)) {
pa_volume = pa_volume_;
if ((pa_cvolume_valid(&ref_volume) != 0) && (pa_channels_valid(ref_volume.channels) != 0)) {
pa_volume = ref_volume;
} else {
// Set stereo as a safe default
pa_volume.channels = 2;
spdlog::debug("Using default stereo volume structure");
// Mono for sources (microphones), stereo for sinks
pa_volume.channels = is_source ? 1 : 2;
spdlog::debug("Using default volume structure ({} channels)", pa_volume.channels);
// Initialize all channels to current volume level
double volume_tick = static_cast<double>(PA_VOLUME_NORM) / 100;
pa_volume_t vol = volume_ * volume_tick;
pa_volume_t vol = current_volume * volume_tick;
for (uint8_t i = 0; i < pa_volume.channels; i++) {
pa_volume.values[i] = vol;
}
// No need to continue with volume change if we had to create a new structure
pa_threaded_mainloop_lock(mainloop_);
if (is_source) {
pa_context_set_source_volume_by_index(context_, source_idx_, &pa_volume, sourceVolumeModifyCb,
this);
} else {
pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, volumeModifyCb, this);
}
pa_threaded_mainloop_unlock(mainloop_);
return;
}
@@ -343,10 +385,10 @@ void AudioBackend::changeVolume(ChangeType change_type, double step, uint16_t ma
pa_volume_t change;
max_volume = std::min(max_volume, static_cast<uint16_t>(PA_VOLUME_UI_MAX));
if (change_type == ChangeType::Increase && volume_ < max_volume) {
if (change_type == ChangeType::Increase && current_volume < max_volume) {
// Calculate how much to increase
if (volume_ + step > max_volume) {
change = round((max_volume - volume_) * volume_tick);
if (current_volume + step > max_volume) {
change = round((max_volume - current_volume) * volume_tick);
} else {
change = round(step * volume_tick);
}
@@ -355,10 +397,10 @@ void AudioBackend::changeVolume(ChangeType change_type, double step, uint16_t ma
for (uint8_t i = 0; i < pa_volume.channels; i++) {
pa_volume.values[i] = std::min(pa_volume.values[i] + change, PA_VOLUME_MAX);
}
} else if (change_type == ChangeType::Decrease && volume_ > 0) {
} else if (change_type == ChangeType::Decrease && current_volume > 0) {
// Calculate how much to decrease
if (volume_ - step < 0) {
change = round(volume_ * volume_tick);
if (current_volume - step < 0) {
change = round(current_volume * volume_tick);
} else {
change = round(step * volume_tick);
}
@@ -374,7 +416,12 @@ void AudioBackend::changeVolume(ChangeType change_type, double step, uint16_t ma
// Apply the volume change
pa_threaded_mainloop_lock(mainloop_);
if (is_source) {
pa_context_set_source_volume_by_index(context_, source_idx_, &pa_volume, sourceVolumeModifyCb,
this);
} else {
pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, volumeModifyCb, this);
}
pa_threaded_mainloop_unlock(mainloop_);
}