Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions include/modules/pulseaudio.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class Pulseaudio : public ALabel {
const std::vector<std::string> getPulseIcon() const;

std::shared_ptr<util::AudioBackend> backend = nullptr;
util::AudioTarget target = util::AudioTarget::Sink;
};

} // namespace waybar::modules
7 changes: 1 addition & 6 deletions include/modules/pulseaudio_slider.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,6 @@
#include "util/audio_backend.hpp"
namespace waybar::modules {

enum class PulseaudioSliderTarget {
Sink,
Source,
};

class PulseaudioSlider : public ASlider {
public:
PulseaudioSlider(const std::string&, const Json::Value&);
Expand All @@ -21,7 +16,7 @@ class PulseaudioSlider : public ASlider {

private:
std::shared_ptr<util::AudioBackend> backend = nullptr;
PulseaudioSliderTarget target = PulseaudioSliderTarget::Sink;
util::AudioTarget target = util::AudioTarget::Sink;
};

} // namespace waybar::modules
13 changes: 11 additions & 2 deletions include/util/audio_backend.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@

namespace waybar::util {

enum class AudioTarget {
Sink,
Source,
};

class AudioBackend {
private:
static void subscribeCb(pa_context*, pa_subscription_event_type_t, uint32_t, void*);
Expand All @@ -22,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};
Expand Down Expand Up @@ -67,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,
AudioTarget target = AudioTarget::Sink);
void changeVolume(ChangeType change_type, double step = 1, uint16_t max_volume = 100,
AudioTarget target = AudioTarget::Sink);

void setIgnoredSinks(const Json::Value& config);

Expand Down
16 changes: 14 additions & 2 deletions man/waybar-pulseaudio-slider.5.scd
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,28 @@ The volume can be controlled by dragging the slider across the bar or clicking o
default: false ++
Enables this module to consume all left over space dynamically.

*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"
},
"pulseaudio/slider#in": {
"min": 0,
"max": 100,
"orientation": "horizontal",
"target": "source"
}
```

Expand Down
5 changes: 5 additions & 0 deletions man/waybar-pulseaudio.5.scd
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
6 changes: 5 additions & 1 deletion src/modules/pulseaudio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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::AudioTarget::Source;
}
}

bool waybar::modules::Pulseaudio::handleScroll(GdkEventScroll* e) {
Expand All @@ -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;
}

Expand Down
18 changes: 9 additions & 9 deletions src/modules/pulseaudio_slider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,24 @@ PulseaudioSlider::PulseaudioSlider(const std::string& id, const Json::Value& con
if (config_["target"].isString()) {
std::string target = config_["target"].asString();
if (target == "sink") {
this->target = PulseaudioSliderTarget::Sink;
this->target = util::AudioTarget::Sink;
} else if (target == "source") {
this->target = PulseaudioSliderTarget::Source;
this->target = util::AudioTarget::Source;
}
}
}

void PulseaudioSlider::update() {
switch (target) {
case PulseaudioSliderTarget::Sink:
case util::AudioTarget::Sink:
if (backend->getSinkMuted()) {
scale_.set_value(min_);
} else {
scale_.set_value(backend->getSinkVolume());
}
break;

case PulseaudioSliderTarget::Source:
case util::AudioTarget::Source:
if (backend->getSourceMuted()) {
scale_.set_value(min_);
} else {
Expand All @@ -41,13 +41,13 @@ void PulseaudioSlider::onValueChanged() {
bool is_mute = false;

switch (target) {
case PulseaudioSliderTarget::Sink:
case util::AudioTarget::Sink:
if (backend->getSinkMuted()) {
is_mute = true;
}
break;

case PulseaudioSliderTarget::Source:
case util::AudioTarget::Source:
if (backend->getSourceMuted()) {
is_mute = true;
}
Expand All @@ -65,18 +65,18 @@ void PulseaudioSlider::onValueChanged() {
// If the sink/source is mute, but the user clicked the slider, unmute it!
else {
switch (target) {
case PulseaudioSliderTarget::Sink:
case util::AudioTarget::Sink:
backend->toggleSinkMute(false);
break;

case PulseaudioSliderTarget::Source:
case util::AudioTarget::Source:
backend->toggleSourceMute(false);
break;
}
}
}

backend->changeVolume(volume, min_, max_);
backend->changeVolume(volume, min_, max_, target);
}

} // namespace waybar::modules
93 changes: 70 additions & 23 deletions src/util/audio_backend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.");
Expand Down Expand Up @@ -155,6 +156,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.
*/
Expand Down Expand Up @@ -234,6 +248,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;
Expand All @@ -259,25 +278,31 @@ void AudioBackend::serverInfoCb(pa_context* context, const pa_server_info* i, vo
pa_context_get_source_info_list(context, sourceInfoCb, data);
}

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,
AudioTarget 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 == AudioTarget::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
Expand All @@ -291,39 +316,56 @@ void AudioBackend::changeVolume(uint16_t volume, uint16_t min_volume, uint16_t m

// Apply the volume change
pa_threaded_mainloop_lock(mainloop_);
pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, volumeModifyCb, this);
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,
AudioTarget 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 == AudioTarget::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_);
pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, volumeModifyCb, this);
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;
}
Expand All @@ -333,10 +375,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);
}
Expand All @@ -345,10 +387,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);
}
Expand All @@ -364,7 +406,12 @@ void AudioBackend::changeVolume(ChangeType change_type, double step, uint16_t ma

// Apply the volume change
pa_threaded_mainloop_lock(mainloop_);
pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, volumeModifyCb, this);
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_);
}

Expand Down