fix(volume): Improve mixer event handling

Fixes jaagr/lemonbuddy#14
This commit is contained in:
Michael Carlberg 2016-06-10 01:09:54 +02:00
parent 737da87e22
commit 0e9900db74
8 changed files with 191 additions and 105 deletions

View File

@ -749,18 +749,20 @@ See [the bspwm module](#user-content-dependencies) for details on `label:dimmed`
This module is still WIP. This module is still WIP.
Mute and volume changes should affect the appropriate mixers depending on Mute and volume changes should affect the appropriate mixers depending on
if the headphones are plugged in or not. Still need to add separate output formats weather the headphones are plugged in or not. Still need to add separate output formats
to indicate it. to indicate it.
~~~ ini ~~~ ini
[module/volume] [module/volume]
type = internal/volume type = internal/volume
;master_mixer = Master
; Use the following command to list available mixer controls: ; Use the following command to list available mixer controls:
; $ amixer scontrols | sed -nr "s/.*'([[:alnum:]]+)'.*/\1/p" ; $ amixer scontrols | sed -nr "s/.*'([[:alnum:]]+)'.*/\1/p"
speaker_mixer = Speaker speaker_mixer = Speaker
headphone_mixer = Headphone headphone_mixer = Headphone
; NOTE: This is required if headphone_mixer is defined
; Use the following command to list available device controls ; Use the following command to list available device controls
; $ amixer controls | sed -r "/CARD/\!d; s/.*=([0-9]+).*name='([^']+)'.*/printf '%3.0f: %s\n' '\1' '\2'/e" | sort ; $ amixer controls | sed -r "/CARD/\!d; s/.*=([0-9]+).*name='([^']+)'.*/printf '%3.0f: %s\n' '\1' '\2'/e" | sort
headphone_control_numid = 9 headphone_control_numid = 9

View File

@ -24,7 +24,7 @@ font:0 = sans:size=8;0
font:1 = font awesome:size=10:weight=heavy;0 font:1 = font awesome:size=10:weight=heavy;0
modules:left = label modules:left = label
modules:right = cpu ram clock modules:right = volume cpu ram clock
[module/label] [module/label]
type = custom/text type = custom/text
@ -58,4 +58,22 @@ format:underline = #7a6
format:overline = #7a6 format:overline = #7a6
format:padding = 2 format:padding = 2
[module/volume]
type = internal/volume
;speaker_mixer = Speaker
;headphone_mixer = Headphone
;headphone_control_numid = 9
format:volume:background = #933484
format:volume:underline = #9d6294
format:volume:overline = #9d6294
format:volume:padding = 2
format:muted:background = #933484
format:muted:underline = #9d6294
format:muted:overline = #9d6294
format:muted:padding = 2
label:volume = Volume: %percentage%
label:muted = Sound is muted
; vim:ft=dosini ; vim:ft=dosini

View File

@ -7,11 +7,15 @@
#include <mutex> #include <mutex>
#include "exception.hpp" #include "exception.hpp"
#include "utils/concurrency.hpp"
#include "utils/macros.hpp"
#define STRSNDERR(s) std::string(snd_strerror(s)) #define StrSndErr(s) ToStr(snd_strerror(s))
namespace alsa namespace alsa
{ {
// Errors {{{
class Exception : public ::Exception class Exception : public ::Exception
{ {
public: public:
@ -25,9 +29,16 @@ namespace alsa
: Exception(msg +" ["+ std::to_string(code) +"]") {} : Exception(msg +" ["+ std::to_string(code) +"]") {}
}; };
class MixerError : public Exception {
using Exception::Exception;
};
// }}}
// ControlInterface {{{
class ControlInterface class ControlInterface
{ {
std::mutex mtx; concurrency::SpinLock lock;
snd_hctl_t *hctl; snd_hctl_t *hctl;
snd_hctl_elem_t *elem; snd_hctl_elem_t *elem;
@ -40,20 +51,21 @@ namespace alsa
public: public:
explicit ControlInterface(int numid); explicit ControlInterface(int numid);
~ControlInterface(); ~ControlInterface();
ControlInterface(const ControlInterface &) = delete;
ControlInterface &operator=(const ControlInterface &) = delete;
bool wait(int timeout = -1); bool wait(int timeout = -1);
void process_events();
bool test_device_plugged(); bool test_device_plugged();
}; };
// }}}
class MixerError : public Exception { // Mixer {{{
using Exception::Exception;
};
class Mixer class Mixer
{ {
std::mutex mtx; concurrency::SpinLock lock;
snd_mixer_t *hardware_mixer = nullptr; snd_mixer_t *hardware_mixer = nullptr;
snd_mixer_elem_t *mixer_element = nullptr; snd_mixer_elem_t *mixer_element = nullptr;
@ -61,8 +73,11 @@ namespace alsa
public: public:
explicit Mixer(const std::string& mixer_control_name); explicit Mixer(const std::string& mixer_control_name);
~Mixer(); ~Mixer();
Mixer(const Mixer &) = delete;
Mixer &operator=(const Mixer &) = delete;
bool wait(int timeout = -1); bool wait(int timeout = -1);
int process_events();
int get_volume(); int get_volume();
void set_volume(float percentage); void set_volume(float percentage);
@ -73,4 +88,6 @@ namespace alsa
protected: protected:
void error_handler(const std::string& message); void error_handler(const std::string& message);
}; };
// }}}
} }

View File

@ -19,18 +19,11 @@ namespace modules
static constexpr auto TAG_LABEL_VOLUME = "<label:volume>"; static constexpr auto TAG_LABEL_VOLUME = "<label:volume>";
static constexpr auto TAG_LABEL_MUTED = "<label:muted>"; static constexpr auto TAG_LABEL_MUTED = "<label:muted>";
static constexpr auto EVENT_PREFIX = "vol";
static constexpr auto EVENT_VOLUME_UP = "volup"; static constexpr auto EVENT_VOLUME_UP = "volup";
static constexpr auto EVENT_VOLUME_DOWN = "voldown"; static constexpr auto EVENT_VOLUME_DOWN = "voldown";
static constexpr auto EVENT_TOGGLE_MUTE = "volmute"; static constexpr auto EVENT_TOGGLE_MUTE = "volmute";
std::unique_ptr<alsa::Mixer> master_mixer;
std::unique_ptr<alsa::Mixer> speaker_mixer;
std::unique_ptr<alsa::Mixer> headphone_mixer;
std::unique_ptr<alsa::ControlInterface> headphone_ctrl;
int headphone_ctrl_numid;
std::unique_ptr<Builder> builder;
std::unique_ptr<drawtypes::Bar> bar_volume; std::unique_ptr<drawtypes::Bar> bar_volume;
std::unique_ptr<drawtypes::Ramp> ramp_volume; std::unique_ptr<drawtypes::Ramp> ramp_volume;
std::unique_ptr<drawtypes::Label> label_volume; std::unique_ptr<drawtypes::Label> label_volume;
@ -38,8 +31,16 @@ namespace modules
std::unique_ptr<drawtypes::Label> label_muted; std::unique_ptr<drawtypes::Label> label_muted;
std::unique_ptr<drawtypes::Label> label_muted_tokenized; std::unique_ptr<drawtypes::Label> label_muted_tokenized;
int volume = 0; std::unique_ptr<alsa::Mixer> master_mixer;
bool muted = false; std::unique_ptr<alsa::Mixer> speaker_mixer;
std::unique_ptr<alsa::Mixer> headphone_mixer;
std::unique_ptr<alsa::ControlInterface> headphone_ctrl;
int headphone_ctrl_numid;
concurrency::Atomic<int> volume;
concurrency::Atomic<bool> muted;
concurrency::Atomic<bool> has_changed;
public: public:
explicit VolumeModule(const std::string& name); explicit VolumeModule(const std::string& name);

View File

@ -56,7 +56,6 @@ namespace concurrency
template<typename T> template<typename T>
class Atomic class Atomic
{ {
concurrency::SpinLock lock;
std::atomic<T> value; std::atomic<T> value;
public: public:
@ -67,25 +66,21 @@ namespace concurrency
void operator=(T value) void operator=(T value)
{ {
std::lock_guard<concurrency::SpinLock> lck(this->lock);
this->value = value; this->value = value;
} }
T operator()() T operator()()
{ {
std::lock_guard<concurrency::SpinLock> lck(this->lock);
return this->value; return this->value;
} }
operator bool() operator bool()
{ {
std::lock_guard<concurrency::SpinLock> lck(this->lock);
return this->value; return this->value;
} }
bool operator==(T const& b) bool operator==(T const& b)
{ {
std::lock_guard<concurrency::SpinLock> lck(this->lock);
return this->value == b; return this->value == b;
} }
}; };

View File

@ -164,7 +164,7 @@ void EventLoop::read_stdin()
std::string input; std::string input;
while ((input = io::readline(this->fd_stdin)).empty() == false) { while ((input = io::readline(this->fd_stdin)).empty() == false) {
this->logger->debug("Input value: \'"+ input +"\""); this->logger->debug("Input value: \""+ input +"\"");
bool input_processed = false; bool input_processed = false;

View File

@ -8,6 +8,8 @@
namespace alsa namespace alsa
{ {
// ControlInterface {{{
ControlInterface::ControlInterface(int numid) ControlInterface::ControlInterface(int numid)
{ {
int err; int err;
@ -20,49 +22,48 @@ namespace alsa
snd_ctl_elem_info_set_id(this->info, this->id); snd_ctl_elem_info_set_id(this->info, this->id);
if ((err = snd_ctl_open(&this->ctl, ALSA_SOUNDCARD, SND_CTL_NONBLOCK | SND_CTL_READONLY)) < 0) if ((err = snd_ctl_open(&this->ctl, ALSA_SOUNDCARD, SND_CTL_NONBLOCK | SND_CTL_READONLY)) < 0)
throw ControlInterfaceError(err, "Could not open control \""+ ToStr(ALSA_SOUNDCARD) +"\": "+ STRSNDERR(err)); throw ControlInterfaceError(err, "Could not open control \""+ ToStr(ALSA_SOUNDCARD) +"\": "+ StrSndErr(err));
if ((err = snd_ctl_elem_info(this->ctl, this->info)) < 0) if ((err = snd_ctl_elem_info(this->ctl, this->info)) < 0)
throw ControlInterfaceError(err, "Could not get control data: "+ STRSNDERR(err)); throw ControlInterfaceError(err, "Could not get control data: "+ StrSndErr(err));
snd_ctl_elem_info_get_id(this->info, this->id); snd_ctl_elem_info_get_id(this->info, this->id);
if ((err = snd_hctl_open(&this->hctl, ALSA_SOUNDCARD, 0)) < 0) if ((err = snd_hctl_open(&this->hctl, ALSA_SOUNDCARD, 0)) < 0)
throw ControlInterfaceError(err, STRSNDERR(err)); throw ControlInterfaceError(err, StrSndErr(err));
if ((err = snd_hctl_load(this->hctl)) < 0) if ((err = snd_hctl_load(this->hctl)) < 0)
throw ControlInterfaceError(err, STRSNDERR(err)); throw ControlInterfaceError(err, StrSndErr(err));
if ((elem = snd_hctl_find_elem(this->hctl, this->id)) == nullptr) if ((elem = snd_hctl_find_elem(this->hctl, this->id)) == nullptr)
throw ControlInterfaceError(err, "Could not find control with id "+ IntToStr(snd_ctl_elem_id_get_numid(this->id))); throw ControlInterfaceError(err, "Could not find control with id "+ IntToStr(snd_ctl_elem_id_get_numid(this->id)));
if ((err = snd_ctl_subscribe_events(this->ctl, 1)) < 0) if ((err = snd_ctl_subscribe_events(this->ctl, 1)) < 0)
throw ControlInterfaceError(err, "Could not subscribe to events: "+ IntToStr(snd_ctl_elem_id_get_numid(this->id))); throw ControlInterfaceError(err, "Could not subscribe to events: "+ IntToStr(snd_ctl_elem_id_get_numid(this->id)));
log_trace("Successfully initialized control interface"); log_trace("Successfully initialized control interface with ID: "+ IntToStr(numid));
} }
ControlInterface::~ControlInterface() { ControlInterface::~ControlInterface()
std::lock_guard<std::mutex> lck(this->mtx); {
std::lock_guard<concurrency::SpinLock> lck(this->lock);
snd_ctl_close(this->ctl); snd_ctl_close(this->ctl);
snd_hctl_close(this->hctl); snd_hctl_close(this->hctl);
} }
bool ControlInterface::wait(int timeout) bool ControlInterface::wait(int timeout)
{ {
std::lock_guard<std::mutex> lck(this->mtx); std::lock_guard<concurrency::SpinLock> lck(this->lock);
int err; int err;
if ((err = snd_ctl_wait(this->ctl, timeout)) < 0) if ((err = snd_ctl_wait(this->ctl, timeout)) < 0)
throw ControlInterfaceError(err, "Failed to wait for events: "+ STRSNDERR(err)); throw ControlInterfaceError(err, "Failed to wait for events: "+ StrSndErr(err));
snd_ctl_event_t *event; snd_ctl_event_t *event;
snd_ctl_event_alloca(&event); snd_ctl_event_alloca(&event);
if ((err = snd_ctl_read(this->ctl, event)) < 0) { if ((err = snd_ctl_read(this->ctl, event)) < 0)
log_trace(err);
return false; return false;
}
if (snd_ctl_event_get_type(event) != SND_CTL_EVENT_ELEM) if (snd_ctl_event_get_type(event) != SND_CTL_EVENT_ELEM)
return false; return false;
@ -73,14 +74,18 @@ namespace alsa
bool ControlInterface::test_device_plugged() bool ControlInterface::test_device_plugged()
{ {
std::lock_guard<concurrency::SpinLock> lck(this->lock);
int err; int err;
if ((err = snd_hctl_elem_read(this->elem, this->value)) < 0) if ((err = snd_hctl_elem_read(this->elem, this->value)) < 0)
throw ControlInterfaceError(err, "Could not read control value: "+ STRSNDERR(err)); throw ControlInterfaceError(err, "Could not read control value: "+ StrSndErr(err));
return snd_ctl_elem_value_get_boolean(this->value, 0); return snd_ctl_elem_value_get_boolean(this->value, 0);
} }
// }}}
// Mixer {{{
Mixer::Mixer(const std::string& mixer_control_name) Mixer::Mixer(const std::string& mixer_control_name)
{ {
@ -103,33 +108,46 @@ namespace alsa
if ((this->mixer_element = snd_mixer_find_selem(this->hardware_mixer, mixer_id)) == nullptr) if ((this->mixer_element = snd_mixer_find_selem(this->hardware_mixer, mixer_id)) == nullptr)
throw MixerError("Cannot find simple element"); throw MixerError("Cannot find simple element");
log_trace("Successfully initialized mixer"); log_trace("Successfully initialized mixer: "+ mixer_control_name);
} }
Mixer::~Mixer() { Mixer::~Mixer()
std::lock_guard<std::mutex> lck(this->mtx); {
std::lock_guard<concurrency::SpinLock> lck(this->lock);
snd_mixer_elem_remove(this->mixer_element); snd_mixer_elem_remove(this->mixer_element);
snd_mixer_detach(this->hardware_mixer, ALSA_SOUNDCARD); snd_mixer_detach(this->hardware_mixer, ALSA_SOUNDCARD);
snd_mixer_close(this->hardware_mixer); snd_mixer_close(this->hardware_mixer);
} }
int Mixer::process_events()
{
int num_events = snd_mixer_handle_events(this->hardware_mixer);
if (num_events < 0)
throw MixerError("Failed to process pending events: "+ StrSndErr(num_events));
return num_events;
}
bool Mixer::wait(int timeout) bool Mixer::wait(int timeout)
{ {
std::lock_guard<std::mutex> lck(this->mtx); assert(this->hardware_mixer);
int err, pend_n = 0; std::lock_guard<concurrency::SpinLock> lck(this->lock);
if (this->hardware_mixer != nullptr && (err = snd_mixer_wait(this->hardware_mixer, timeout)) < 0) int err = snd_mixer_wait(this->hardware_mixer, timeout);
throw MixerError("Failed to wait for events: "+ STRSNDERR(err));
if (this->hardware_mixer != nullptr && (pend_n = snd_mixer_handle_events(this->hardware_mixer)) < 0) if (err < 0)
throw MixerError("Failed to process pending events: "+ STRSNDERR(err)); throw MixerError("Failed to wait for events: "+ StrSndErr(err));
return pend_n > 0; return this->process_events() > 0;
} }
int Mixer::get_volume() int Mixer::get_volume()
{ {
std::lock_guard<concurrency::SpinLock> lck(this->lock);
long chan_n = 0, vol_total = 0, vol, vol_min, vol_max; long chan_n = 0, vol_total = 0, vol, vol_min, vol_max;
snd_mixer_selem_get_playback_volume_range(this->mixer_element, &vol_min, &vol_max); snd_mixer_selem_get_playback_volume_range(this->mixer_element, &vol_min, &vol_max);
@ -143,7 +161,7 @@ namespace alsa
} }
} }
return (int) 100 * (vol_total / chan_n) / vol_max + 0.5f; return (int) 100.0f * (vol_total / chan_n) / vol_max + 0.5f;
} }
void Mixer::set_volume(float percentage) void Mixer::set_volume(float percentage)
@ -151,18 +169,25 @@ namespace alsa
if (this->is_muted()) if (this->is_muted())
return; return;
std::lock_guard<concurrency::SpinLock> lck(this->lock);
long vol_min, vol_max; long vol_min, vol_max;
snd_mixer_selem_get_playback_volume_range(this->mixer_element, &vol_min, &vol_max); snd_mixer_selem_get_playback_volume_range(this->mixer_element, &vol_min, &vol_max);
snd_mixer_selem_set_playback_volume_all(this->mixer_element, vol_max * percentage / 100); snd_mixer_selem_set_playback_volume_all(this->mixer_element, vol_max * percentage / 100);
} }
void Mixer::set_mute(bool mode) { void Mixer::set_mute(bool mode)
{
std::lock_guard<concurrency::SpinLock> lck(this->lock);
snd_mixer_selem_set_playback_switch_all(this->mixer_element, mode); snd_mixer_selem_set_playback_switch_all(this->mixer_element, mode);
} }
void Mixer::toggle_mute() void Mixer::toggle_mute()
{ {
std::lock_guard<concurrency::SpinLock> lck(this->lock);
int state; int state;
snd_mixer_selem_get_playback_switch(this->mixer_element, SND_MIXER_SCHN_FRONT_LEFT, &state); snd_mixer_selem_get_playback_switch(this->mixer_element, SND_MIXER_SCHN_FRONT_LEFT, &state);
snd_mixer_selem_set_playback_switch_all(this->mixer_element, !state); snd_mixer_selem_set_playback_switch_all(this->mixer_element, !state);
@ -170,6 +195,8 @@ namespace alsa
bool Mixer::is_muted() bool Mixer::is_muted()
{ {
std::lock_guard<concurrency::SpinLock> lck(this->lock);
int state = 0; int state = 0;
repeat(SND_MIXER_SCHN_LAST) repeat(SND_MIXER_SCHN_LAST)
{ {
@ -179,6 +206,9 @@ namespace alsa
return true; return true;
} }
} }
return false; return false;
} }
// }}}
} }

View File

@ -8,6 +8,8 @@ using namespace modules;
VolumeModule::VolumeModule(const std::string& name_) : EventModule(name_) VolumeModule::VolumeModule(const std::string& name_) : EventModule(name_)
{ {
// Load configuration values {{{
auto master_mixer = config::get<std::string>(name(), "master_mixer", "Master");
auto speaker_mixer = config::get<std::string>(name(), "speaker_mixer", ""); auto speaker_mixer = config::get<std::string>(name(), "speaker_mixer", "");
auto headphone_mixer = config::get<std::string>(name(), "headphone_mixer", ""); auto headphone_mixer = config::get<std::string>(name(), "headphone_mixer", "");
@ -15,14 +17,16 @@ VolumeModule::VolumeModule(const std::string& name_) : EventModule(name_)
if (!headphone_mixer.empty() && this->headphone_ctrl_numid == -1) if (!headphone_mixer.empty() && this->headphone_ctrl_numid == -1)
throw ModuleError("[VolumeModule] Missing required property value for \"headphone_control_numid\"..."); throw ModuleError("[VolumeModule] Missing required property value for \"headphone_control_numid\"...");
else if (headphone_mixer.empty()) else if (headphone_mixer.empty() && this->headphone_ctrl_numid != -1)
throw ModuleError("[VolumeModule] Missing required property value for \"headphone_mixer\"..."); throw ModuleError("[VolumeModule] Missing required property value for \"headphone_mixer\"...");
if (string::lower(speaker_mixer) == "master") if (string::lower(speaker_mixer) == "master")
throw ModuleError("[VolumeModule] The \"Master\" mixer is already processed internally. Specify another mixer or comment out the \"speaker_mixer\" parameter..."); throw ModuleError("[VolumeModule] The \"Master\" mixer is already processed internally. Specify another mixer or comment out the \"speaker_mixer\" parameter...");
if (string::lower(headphone_mixer) == "master") if (string::lower(headphone_mixer) == "master")
throw ModuleError("[VolumeModule] The \"Master\" mixer is already processed internally. Specify another mixer or comment out the \"headphone_mixer\" parameter..."); throw ModuleError("[VolumeModule] The \"Master\" mixer is already processed internally. Specify another mixer or comment out the \"headphone_mixer\" parameter...");
// }}}
// Setup mixers {{{
auto create_mixer = [](std::string mixer_name) auto create_mixer = [](std::string mixer_name)
{ {
std::unique_ptr<alsa::Mixer> mixer; std::unique_ptr<alsa::Mixer> mixer;
@ -37,7 +41,7 @@ VolumeModule::VolumeModule(const std::string& name_) : EventModule(name_)
return mixer; return mixer;
}; };
this->master_mixer = create_mixer("Master"); this->master_mixer = create_mixer(master_mixer);
if (!speaker_mixer.empty()) if (!speaker_mixer.empty())
this->speaker_mixer = create_mixer(speaker_mixer); this->speaker_mixer = create_mixer(speaker_mixer);
@ -57,9 +61,9 @@ VolumeModule::VolumeModule(const std::string& name_) : EventModule(name_)
this->headphone_ctrl.reset(); this->headphone_ctrl.reset();
} }
} }
// }}}
this->builder = std::make_unique<Builder>(); // Add formats and elements {{{
this->formatter->add(FORMAT_VOLUME, TAG_LABEL_VOLUME, this->formatter->add(FORMAT_VOLUME, TAG_LABEL_VOLUME,
{ TAG_RAMP_VOLUME, TAG_LABEL_VOLUME, TAG_BAR_VOLUME }); { TAG_RAMP_VOLUME, TAG_LABEL_VOLUME, TAG_BAR_VOLUME });
this->formatter->add(FORMAT_MUTED, TAG_LABEL_MUTED, this->formatter->add(FORMAT_MUTED, TAG_LABEL_MUTED,
@ -77,14 +81,16 @@ VolumeModule::VolumeModule(const std::string& name_) : EventModule(name_)
this->label_muted = drawtypes::get_optional_config_label(name(), get_tag_name(TAG_LABEL_MUTED), "%percentage%"); this->label_muted = drawtypes::get_optional_config_label(name(), get_tag_name(TAG_LABEL_MUTED), "%percentage%");
this->label_muted_tokenized = this->label_muted->clone(); this->label_muted_tokenized = this->label_muted->clone();
} }
// }}}
// Sign up for stdin events {{{
register_command_handler(name()); register_command_handler(name());
// }}}
} }
VolumeModule::~VolumeModule() VolumeModule::~VolumeModule()
{ {
std::lock_guard<concurrency::SpinLock> lck(this->update_lock); std::lock_guard<concurrency::SpinLock> lck(this->update_lock);
this->master_mixer.reset(); this->master_mixer.reset();
this->speaker_mixer.reset(); this->speaker_mixer.reset();
this->headphone_mixer.reset(); this->headphone_mixer.reset();
@ -95,14 +101,17 @@ bool VolumeModule::has_event()
{ {
bool has_event = false; bool has_event = false;
if (this->has_changed())
has_event = true;
try { try {
if (this->master_mixer) if (!has_event && this->master_mixer)
has_event |= this->master_mixer->wait(25); has_event |= this->master_mixer->wait(25);
if (this->speaker_mixer) if (!has_event && this->speaker_mixer)
has_event |= this->speaker_mixer->wait(25); has_event |= this->speaker_mixer->wait(25);
if (this->headphone_mixer) if (!has_event && this->headphone_mixer)
has_event |= this->headphone_mixer->wait(25); has_event |= this->headphone_mixer->wait(25);
if (this->headphone_ctrl) if (!has_event && this->headphone_ctrl)
has_event |= this->headphone_ctrl->wait(25); has_event |= this->headphone_ctrl->wait(25);
} catch (alsa::Exception &e) { } catch (alsa::Exception &e) {
log_error(e.what()); log_error(e.what());
@ -113,21 +122,26 @@ bool VolumeModule::has_event()
bool VolumeModule::update() bool VolumeModule::update()
{ {
int volume = 0; // Consume any other pending events
this->has_changed = false;
if (this->master_mixer)
this->master_mixer->process_events();
if (this->speaker_mixer)
this->speaker_mixer->process_events();
if (this->headphone_mixer)
this->headphone_mixer->process_events();
if (this->headphone_ctrl)
this->headphone_ctrl->wait(0);
int volume = 100;
bool muted = false; bool muted = false;
auto headphones_connected = false;
if (this->master_mixer) { if (this->master_mixer) {
volume = this->master_mixer->get_volume(); volume *= this->master_mixer->get_volume() / 100.0f;
muted |= this->master_mixer->is_muted(); muted |= this->master_mixer->is_muted();
} else {
volume = 100;
} }
if (this->headphone_ctrl && this->headphone_mixer) if (this->headphone_mixer && this->headphone_ctrl && this->headphone_ctrl->test_device_plugged()) {
headphones_connected = this->headphone_ctrl->test_device_plugged();
if (headphones_connected) {
volume *= this->headphone_mixer->get_volume() / 100.0f; volume *= this->headphone_mixer->get_volume() / 100.0f;
muted |= this->headphone_mixer->is_muted(); muted |= this->headphone_mixer->is_muted();
} else if (this->speaker_mixer) { } else if (this->speaker_mixer) {
@ -139,26 +153,29 @@ bool VolumeModule::update()
this->muted = muted; this->muted = muted;
this->label_volume_tokenized->text = this->label_volume->text; this->label_volume_tokenized->text = this->label_volume->text;
this->label_volume_tokenized->replace_token("%percentage%", std::to_string(this->volume) +"%"); this->label_volume_tokenized->replace_token("%percentage%", std::to_string(this->volume()) +"%");
this->label_muted_tokenized->text = this->label_muted->text; this->label_muted_tokenized->text = this->label_muted->text;
this->label_muted_tokenized->replace_token("%percentage%", std::to_string(this->volume) +"%"); this->label_muted_tokenized->replace_token("%percentage%", std::to_string(this->volume()) +"%");
return true; return true;
} }
std::string VolumeModule::get_format() { std::string VolumeModule::get_format()
return this->muted ? FORMAT_MUTED : FORMAT_VOLUME; {
return this->muted() == true ? FORMAT_MUTED : FORMAT_VOLUME;
} }
std::string VolumeModule::get_output() std::string VolumeModule::get_output()
{ {
this->builder->cmd(Cmd::LEFT_CLICK, EVENT_TOGGLE_MUTE); this->builder->cmd(Cmd::LEFT_CLICK, EVENT_TOGGLE_MUTE);
if (volume < 100) if (!this->muted()) {
this->builder->cmd(Cmd::SCROLL_UP, EVENT_VOLUME_UP, volume < 100); if (this->volume() < 100)
if (volume > 0) this->builder->cmd(Cmd::SCROLL_UP, EVENT_VOLUME_UP);
if (this->volume() > 0)
this->builder->cmd(Cmd::SCROLL_DOWN, EVENT_VOLUME_DOWN); this->builder->cmd(Cmd::SCROLL_DOWN, EVENT_VOLUME_DOWN);
}
this->builder->node(this->Module::get_output()); this->builder->node(this->Module::get_output());
@ -167,8 +184,6 @@ std::string VolumeModule::get_output()
bool VolumeModule::build(Builder *builder, const std::string& tag) bool VolumeModule::build(Builder *builder, const std::string& tag)
{ {
bool built = true;
if (tag == TAG_BAR_VOLUME) if (tag == TAG_BAR_VOLUME)
builder->node(this->bar_volume, volume); builder->node(this->bar_volume, volume);
else if (tag == TAG_RAMP_VOLUME) else if (tag == TAG_RAMP_VOLUME)
@ -178,52 +193,60 @@ bool VolumeModule::build(Builder *builder, const std::string& tag)
else if (tag == TAG_LABEL_MUTED) else if (tag == TAG_LABEL_MUTED)
builder->node(this->label_muted_tokenized); builder->node(this->label_muted_tokenized);
else else
built = false; return false;
return built; return true;
} }
bool VolumeModule::handle_command(const std::string& cmd) bool VolumeModule::handle_command(const std::string& cmd)
{ {
if (cmd.length() < 3 || cmd.substr(0, 3) != "vol") if (cmd.length() < std::strlen(EVENT_PREFIX))
return false; return false;
if (std::strncmp(cmd.c_str(), EVENT_PREFIX, 3) != 0)
return false;
std::lock_guard<concurrency::SpinLock> lck(this->update_lock);
alsa::Mixer *master_mixer = nullptr; alsa::Mixer *master_mixer = nullptr;
alsa::Mixer *other_mixer = nullptr; alsa::Mixer *other_mixer = nullptr;
bool headphones_connected = false; if (this->master_mixer)
master_mixer = this->master_mixer.get();
if (this->headphone_ctrl && this->headphone_mixer) if (master_mixer == nullptr)
headphones_connected = this->headphone_ctrl->test_device_plugged(); return false;
if (headphones_connected) if (this->headphone_mixer && this->headphone_ctrl && this->headphone_ctrl->test_device_plugged())
other_mixer = this->headphone_mixer.get(); other_mixer = this->headphone_mixer.get();
else if (this->speaker_mixer) else if (this->speaker_mixer)
other_mixer = this->speaker_mixer.get(); other_mixer = this->speaker_mixer.get();
if (this->master_mixer) // Toggle mute state
master_mixer = this->master_mixer.get(); if (std::strncmp(cmd.c_str(), EVENT_TOGGLE_MUTE, std::strlen(EVENT_TOGGLE_MUTE)) == 0) {
master_mixer->set_mute(this->muted());
if (other_mixer != nullptr)
other_mixer->set_mute(this->muted());
// Increase volume
} else if (std::strncmp(cmd.c_str(), EVENT_VOLUME_UP, std::strlen(EVENT_VOLUME_UP)) == 0) {
master_mixer->set_volume(math::cap<float>(master_mixer->get_volume() + 5, 0, 100));
if (other_mixer != nullptr)
other_mixer->set_volume(math::cap<float>(other_mixer->get_volume() + 5, 0, 100));
// Decrease volume
} else if (std::strncmp(cmd.c_str(), EVENT_VOLUME_DOWN, std::strlen(EVENT_VOLUME_DOWN)) == 0) {
master_mixer->set_volume(math::cap<float>(master_mixer->get_volume() - 5, 0, 100));
if (other_mixer != nullptr)
other_mixer->set_volume(math::cap<float>(other_mixer->get_volume() - 5, 0, 100));
if (cmd == EVENT_VOLUME_UP) {
auto vol = math::cap<float>(this->master_mixer->get_volume() + 5, 0, 100);
if (master_mixer != nullptr)
master_mixer->set_volume(vol);
} else if (cmd == EVENT_VOLUME_DOWN) {
auto vol = math::cap<float>(this->master_mixer->get_volume() - 5, 0, 100);
if (master_mixer != nullptr)
master_mixer->set_volume(vol);
} else if (cmd == EVENT_TOGGLE_MUTE) {
if (master_mixer != nullptr)
master_mixer->toggle_mute();
if (other_mixer != nullptr) {
if (master_mixer != nullptr)
other_mixer->set_mute(!master_mixer->is_muted());
else
other_mixer->toggle_mute();
}
} else { } else {
return false; return false;
} }
this->has_changed = true;
return true; return true;
} }