diff --git a/include/adapters/alsa.hpp b/include/adapters/alsa.hpp deleted file mode 100644 index 2c51a7cf..00000000 --- a/include/adapters/alsa.hpp +++ /dev/null @@ -1,121 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifndef __GNUC__ -#define __inline__ inline -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "common.hpp" -#include "config.hpp" -#include "errors.hpp" -#include "utils/concurrency.hpp" - -#define MAX_LINEAR_DB_SCALE 24 - -POLYBAR_NS - -DEFINE_ERROR(alsa_exception); -DEFINE_CHILD_ERROR(alsa_ctl_interface_error, alsa_exception); -DEFINE_CHILD_ERROR(alsa_mixer_error, alsa_exception); - -// class definition : alsa_ctl_interface {{{ - -template -void throw_exception(string&& message, int error_code) { - const char* snd_error = snd_strerror(error_code); - if (snd_error != nullptr) - message += ": " + string{snd_error}; - throw T(message.c_str()); -} - -class alsa_ctl_interface { - public: - explicit alsa_ctl_interface(int numid); - ~alsa_ctl_interface(); - - int get_numid(); - bool wait(int timeout = -1); - bool test_device_plugged(); - void process_events(); - - private: - std::mutex m_lock; - - int m_numid{0}; - - snd_hctl_t* m_hctl{nullptr}; - snd_hctl_elem_t* m_elem{nullptr}; - - snd_ctl_t* m_ctl{nullptr}; - snd_ctl_elem_info_t* m_info{nullptr}; - snd_ctl_elem_value_t* m_value{nullptr}; - snd_ctl_elem_id_t* m_id{nullptr}; -}; - -// }}} -// class definition : alsa_mixer {{{ - -class alsa_mixer { - public: - explicit alsa_mixer(string mixer_control_name); - ~alsa_mixer(); - - string get_name(); - - bool wait(int timeout = -1); - int process_events(); - - int get_volume(); - int get_normalized_volume(); - void set_volume(float percentage); - void set_normalized_volume(float percentage); - void set_mute(bool mode); - void toggle_mute(); - bool is_muted(); - - private: - std::mutex m_lock; - - string m_name; - - snd_mixer_selem_id_t* m_mixerid{nullptr}; - snd_mixer_t* m_hardwaremixer{nullptr}; - snd_mixer_elem_t* m_mixerelement{nullptr}; -}; - -// }}} - -POLYBAR_NS_END diff --git a/include/adapters/alsa/control.hpp b/include/adapters/alsa/control.hpp new file mode 100644 index 00000000..b4b23cef --- /dev/null +++ b/include/adapters/alsa/control.hpp @@ -0,0 +1,49 @@ +#pragma once + +#include + +#include "common.hpp" + +// fwd +struct _snd_ctl_elem_id; +struct _snd_ctl_elem_info; +struct _snd_ctl_elem_value; +struct _snd_ctl; +struct _snd_hctl_elem; +struct _snd_hctl; +typedef struct _snd_ctl_elem_id snd_ctl_elem_id_t; +typedef struct _snd_ctl_elem_info snd_ctl_elem_info_t; +typedef struct _snd_ctl_elem_value snd_ctl_elem_value_t; +typedef struct _snd_ctl snd_ctl_t; +typedef struct _snd_hctl_elem snd_hctl_elem_t; +typedef struct _snd_hctl snd_hctl_t; + +POLYBAR_NS + +namespace alsa { + class control { + public: + explicit control(int numid); + ~control(); + + int get_numid(); + bool wait(int timeout = -1); + bool test_device_plugged(); + void process_events(); + + private: + std::mutex m_lock; + + int m_numid{0}; + + snd_hctl_t* m_hctl{nullptr}; + snd_hctl_elem_t* m_elem{nullptr}; + + snd_ctl_t* m_ctl{nullptr}; + snd_ctl_elem_info_t* m_info{nullptr}; + snd_ctl_elem_value_t* m_value{nullptr}; + snd_ctl_elem_id_t* m_id{nullptr}; + }; +} + +POLYBAR_NS_END diff --git a/include/adapters/alsa/generic.hpp b/include/adapters/alsa/generic.hpp new file mode 100644 index 00000000..a070b34a --- /dev/null +++ b/include/adapters/alsa/generic.hpp @@ -0,0 +1,60 @@ +#pragma once + +#ifdef USE_ALSALIB_H +#include +#else +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef __GNUC__ +#define __inline__ inline +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +#include "common.hpp" +#include "errors.hpp" + +POLYBAR_NS + +namespace alsa { + DEFINE_ERROR(alsa_exception); + DEFINE_CHILD_ERROR(mixer_error, alsa_exception); + DEFINE_CHILD_ERROR(control_error, alsa_exception); + + template + void throw_exception(string&& message, int error_code) { + const char* snd_error = snd_strerror(error_code); + if (snd_error != nullptr) + message += ": " + string{snd_error}; + throw T(message.c_str()); + } +} + +POLYBAR_NS_END diff --git a/include/adapters/alsa/mixer.hpp b/include/adapters/alsa/mixer.hpp new file mode 100644 index 00000000..8185be22 --- /dev/null +++ b/include/adapters/alsa/mixer.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include + +#include "common.hpp" + +// fwd +struct _snd_mixer; +struct _snd_mixer_elem; +struct _snd_mixer_selem_id; +typedef struct _snd_mixer snd_mixer_t; +typedef struct _snd_mixer_elem snd_mixer_elem_t; +typedef struct _snd_mixer_selem_id snd_mixer_selem_id_t; + +POLYBAR_NS + +namespace alsa { + class mixer { + public: + explicit mixer(string mixer_control_name); + ~mixer(); + + string get_name(); + + bool wait(int timeout = -1); + int process_events(); + + int get_volume(); + int get_normalized_volume(); + void set_volume(float percentage); + void set_normalized_volume(float percentage); + void set_mute(bool mode); + void toggle_mute(); + bool is_muted(); + + private: + std::mutex m_lock; + + string m_name; + + snd_mixer_selem_id_t* m_mixerid{nullptr}; + snd_mixer_t* m_hardwaremixer{nullptr}; + snd_mixer_elem_t* m_mixerelement{nullptr}; + }; +} + +POLYBAR_NS_END diff --git a/include/modules/volume.hpp b/include/modules/volume.hpp index 7903f09f..382315ef 100644 --- a/include/modules/volume.hpp +++ b/include/modules/volume.hpp @@ -1,18 +1,22 @@ #pragma once -#include "adapters/alsa.hpp" -#include "components/config.hpp" #include "config.hpp" #include "modules/meta/event_module.hpp" POLYBAR_NS +// fwd +namespace alsa { + class mixer; + class control; +} + namespace modules { enum class mixer { NONE = 0, MASTER, SPEAKER, HEADPHONE }; enum class control { NONE = 0, HEADPHONE }; - using mixer_t = shared_ptr; - using control_t = shared_ptr; + using mixer_t = shared_ptr; + using control_t = shared_ptr; class volume_module : public event_module { public: diff --git a/include/utils/factory.hpp b/include/utils/factory.hpp index 8b270374..6db4185d 100644 --- a/include/utils/factory.hpp +++ b/include/utils/factory.hpp @@ -32,7 +32,7 @@ namespace factory_util { template shared_ptr singleton(Deps&&... deps) { - static auto instance = make_shared(forward(deps)...); + static shared_ptr instance{make_shared(forward(deps)...)}; return instance; } } diff --git a/src/adapters/alsa.cpp b/src/adapters/alsa.cpp deleted file mode 100644 index 5fd14623..00000000 --- a/src/adapters/alsa.cpp +++ /dev/null @@ -1,376 +0,0 @@ -#include "adapters/alsa.hpp" -#include "utils/math.hpp" - -POLYBAR_NS - -// class : alsa_ctl_interface {{{ - -alsa_ctl_interface::alsa_ctl_interface(int numid) : m_numid(numid) { - snd_ctl_elem_info_malloc(&m_info); - - if (m_info == nullptr) { - throw alsa_ctl_interface_error("Failed to allocate alsa_ctl info"); - } - - snd_ctl_elem_value_malloc(&m_value); - - if (m_value == nullptr) { - throw alsa_ctl_interface_error("Failed to allocate alsa_ctl value"); - } - - snd_ctl_elem_id_malloc(&m_id); - - if (m_id == nullptr) { - throw alsa_ctl_interface_error("Failed to allocate alsa_ctl id"); - } - - snd_ctl_elem_id_set_numid(m_id, m_numid); - snd_ctl_elem_info_set_id(m_info, m_id); - - int err = 0; - - if ((err = snd_ctl_open(&m_ctl, ALSA_SOUNDCARD, SND_CTL_NONBLOCK | SND_CTL_READONLY)) == -1) { - throw_exception("Could not open control '" + string{ALSA_SOUNDCARD} + "'", err); - } - - snd_config_update_free_global(); - - if ((err = snd_ctl_elem_info(m_ctl, m_info)) < 0) { - throw_exception("Could not get control datal", err); - } - - snd_ctl_elem_info_get_id(m_info, m_id); - - if ((err = snd_hctl_open(&m_hctl, ALSA_SOUNDCARD, 0)) == -1) { - throw_exception("Failed to open hctl", err); - } - - snd_config_update_free_global(); - - if (m_hctl == nullptr || (err = snd_hctl_load(m_hctl)) < 0) { - throw_exception("Failed to load hctl", err); - } - - if ((m_elem = snd_hctl_find_elem(m_hctl, m_id)) == nullptr) { - throw alsa_ctl_interface_error("Could not find control with id " + to_string(snd_ctl_elem_id_get_numid(m_id))); - } - - if ((err = snd_ctl_subscribe_events(m_ctl, 1)) < 0) { - throw alsa_ctl_interface_error("Could not subscribe to events: " + to_string(snd_ctl_elem_id_get_numid(m_id))); - } -} - -alsa_ctl_interface::~alsa_ctl_interface() { - std::lock_guard guard(m_lock); - if (m_info != nullptr) { - snd_ctl_elem_info_free(m_info); - } - if (m_value != nullptr) { - snd_ctl_elem_value_free(m_value); - } - if (m_id != nullptr) { - snd_ctl_elem_id_free(m_id); - } - if (m_ctl != nullptr) { - snd_ctl_close(m_ctl); - } - if (m_hctl != nullptr) { - snd_hctl_close(m_hctl); - } -} - -int alsa_ctl_interface::get_numid() { - return m_numid; -} - -bool alsa_ctl_interface::wait(int timeout) { - assert(m_ctl); - - if (!m_lock.try_lock()) { - return false; - } - - std::lock_guard guard(m_lock, std::adopt_lock); - - int err = 0; - - if ((err = snd_ctl_wait(m_ctl, timeout)) == -1) { - throw_exception("Failed to wait for events", err); - } - - snd_ctl_event_t* event; - snd_ctl_event_alloca(&event); - - if ((err = snd_ctl_read(m_ctl, event)) == -1) { - return false; - } - - if (snd_ctl_event_get_type(event) == SND_CTL_EVENT_ELEM) { - return snd_ctl_event_elem_get_mask(event) & SND_CTL_EVENT_MASK_VALUE; - } - - return false; -} - -bool alsa_ctl_interface::test_device_plugged() { - assert(m_elem); - assert(m_value); - - if (!m_lock.try_lock()) { - return false; - } - - std::lock_guard guard(m_lock, std::adopt_lock); - - int err = 0; - if ((err = snd_hctl_elem_read(m_elem, m_value)) < 0) { - throw_exception("Could not read control value", err); - } - return snd_ctl_elem_value_get_boolean(m_value, 0); -} - -void alsa_ctl_interface::process_events() { - wait(0); -} - -// }}} -// class : alsa_mixer {{{ - -alsa_mixer::alsa_mixer(string mixer_control_name) : m_name(move(mixer_control_name)) { - if (m_name.empty()) { - throw alsa_mixer_error("Invalid control name"); - } - - snd_mixer_selem_id_malloc(&m_mixerid); - - if (m_mixerid == nullptr) { - throw alsa_mixer_error("Failed to allocate mixer id"); - } - - int err = 0; - if ((err = snd_mixer_open(&m_hardwaremixer, 1)) == -1) { - throw_exception("Failed to open hardware mixer", err); - } - - snd_config_update_free_global(); - - if ((err = snd_mixer_attach(m_hardwaremixer, ALSA_SOUNDCARD)) == -1) { - throw_exception("Failed to attach hardware mixer control", err); - } - if ((err = snd_mixer_selem_register(m_hardwaremixer, nullptr, nullptr)) == -1) { - throw_exception("Failed to register simple mixer element", err); - } - if ((err = snd_mixer_load(m_hardwaremixer)) == -1) { - throw_exception("Failed to load mixer", err); - } - - snd_mixer_selem_id_set_index(m_mixerid, 0); - snd_mixer_selem_id_set_name(m_mixerid, m_name.c_str()); - - if ((m_mixerelement = snd_mixer_find_selem(m_hardwaremixer, m_mixerid)) == nullptr) { - throw alsa_mixer_error("Cannot find simple element"); - } - - // log_trace("Successfully initialized mixer: "+ string{m_name}); -} - -alsa_mixer::~alsa_mixer() { - std::lock_guard guard(m_lock); - if (m_mixerid != nullptr) { - snd_mixer_selem_id_free(m_mixerid); - } - if (m_mixerelement != nullptr) { - snd_mixer_elem_remove(m_mixerelement); - } - if (m_hardwaremixer != nullptr) { - snd_mixer_detach(m_hardwaremixer, ALSA_SOUNDCARD); - snd_mixer_close(m_hardwaremixer); - } -} - -string alsa_mixer::get_name() { - return m_name; -} - -bool alsa_mixer::wait(int timeout) { - assert(m_hardwaremixer); - - if (!m_lock.try_lock()) { - return false; - } - - std::unique_lock guard(m_lock, std::adopt_lock); - - int err = 0; - - if ((err = snd_mixer_wait(m_hardwaremixer, timeout)) == -1) { - throw_exception("Failed to wait for events", err); - } - - guard.unlock(); - - return process_events() > 0; -} - -int alsa_mixer::process_events() { - if (!m_lock.try_lock()) { - return false; - } - - std::lock_guard guard(m_lock, std::adopt_lock); - - int num_events = snd_mixer_handle_events(m_hardwaremixer); - if (num_events < 0) { - throw_exception("Failed to process pending events", num_events); - } - - return num_events; -} - -int alsa_mixer::get_volume() { - if (!m_lock.try_lock()) { - return 0; - } - - std::lock_guard guard(m_lock, std::adopt_lock); - - long chan_n = 0, vol_total = 0, vol, vol_min, vol_max; - - snd_mixer_selem_get_playback_volume_range(m_mixerelement, &vol_min, &vol_max); - - for (int i = 0; i <= SND_MIXER_SCHN_LAST; i++) { - if (snd_mixer_selem_has_playback_channel(m_mixerelement, static_cast(i))) { - snd_mixer_selem_get_playback_volume(m_mixerelement, static_cast(i), &vol); - vol_total += vol; - chan_n++; - } - } - - return math_util::percentage(vol_total / chan_n, vol_min, vol_max); -} - -int alsa_mixer::get_normalized_volume() { - if (!m_lock.try_lock()) { - return 0; - } - - std::lock_guard guard(m_lock, std::adopt_lock); - - long chan_n = 0, vol_total = 0, vol, vol_min, vol_max; - double normalized, min_norm; - - snd_mixer_selem_get_playback_dB_range(m_mixerelement, &vol_min, &vol_max); - - for (int i = 0; i <= SND_MIXER_SCHN_LAST; i++) { - if (snd_mixer_selem_has_playback_channel(m_mixerelement, static_cast(i))) { - snd_mixer_selem_get_playback_dB(m_mixerelement, static_cast(i), &vol); - vol_total += vol; - chan_n++; - } - } - - if (vol_max - vol_min <= MAX_LINEAR_DB_SCALE * 100) { - return math_util::percentage(vol_total / chan_n, vol_min, vol_max); - } - - normalized = pow10((vol_total / chan_n - vol_max) / 6000.0); - if (vol_min != SND_CTL_TLV_DB_GAIN_MUTE) { - min_norm = pow10((vol_min - vol_max) / 6000.0); - normalized = (normalized - min_norm) / (1 - min_norm); - } - - return 100.0f * normalized + 0.5f; -} - -void alsa_mixer::set_volume(float percentage) { - if (is_muted()) { - return; - } - - if (!m_lock.try_lock()) { - return; - } - - std::lock_guard guard(m_lock, std::adopt_lock); - - long vol_min, vol_max; - snd_mixer_selem_get_playback_volume_range(m_mixerelement, &vol_min, &vol_max); - snd_mixer_selem_set_playback_volume_all( - m_mixerelement, math_util::percentage_to_value(percentage, vol_min, vol_max)); -} - -void alsa_mixer::set_normalized_volume(float percentage) { - if (is_muted()) { - return; - } - - if (!m_lock.try_lock()) { - return; - } - - std::lock_guard guard(m_lock, std::adopt_lock); - - long vol_min, vol_max; - double min_norm; - percentage = percentage / 100.0f; - - snd_mixer_selem_get_playback_dB_range(m_mixerelement, &vol_min, &vol_max); - - if (vol_max - vol_min <= MAX_LINEAR_DB_SCALE * 100) { - snd_mixer_selem_set_playback_dB_all(m_mixerelement, lrint(percentage * (vol_max - vol_min)) + vol_min, 0); - return; - } - - if (vol_min != SND_CTL_TLV_DB_GAIN_MUTE) { - min_norm = pow10((vol_min - vol_max) / 6000.0); - percentage = percentage * (1 - min_norm) + min_norm; - } - - snd_mixer_selem_set_playback_dB_all(m_mixerelement, lrint(6000.0 * log10(percentage)) + vol_max, 0); -} - -void alsa_mixer::set_mute(bool mode) { - if (!m_lock.try_lock()) { - return; - } - - std::lock_guard guard(m_lock, std::adopt_lock); - - snd_mixer_selem_set_playback_switch_all(m_mixerelement, mode); -} - -void alsa_mixer::toggle_mute() { - if (!m_lock.try_lock()) { - return; - } - - std::lock_guard guard(m_lock, std::adopt_lock); - - int state; - - snd_mixer_selem_get_playback_switch(m_mixerelement, SND_MIXER_SCHN_MONO, &state); - snd_mixer_selem_set_playback_switch_all(m_mixerelement, !state); -} - -bool alsa_mixer::is_muted() { - if (!m_lock.try_lock()) { - return false; - } - - std::lock_guard guard(m_lock, std::adopt_lock); - - int state = 0; - - for (int i = 0; i <= SND_MIXER_SCHN_LAST; i++) { - if (snd_mixer_selem_has_playback_channel(m_mixerelement, static_cast(i))) { - int state_ = 0; - snd_mixer_selem_get_playback_switch(m_mixerelement, static_cast(i), &state_); - state = state || state_; - } - } - return !state; -} - -// }}} - -POLYBAR_NS_END diff --git a/src/adapters/alsa/control.cpp b/src/adapters/alsa/control.cpp new file mode 100644 index 00000000..339a7c86 --- /dev/null +++ b/src/adapters/alsa/control.cpp @@ -0,0 +1,154 @@ +#include "adapters/alsa/control.hpp" +#include "adapters/alsa/generic.hpp" + +POLYBAR_NS + +namespace alsa { + /** + * Construct control object + */ + control::control(int numid) : m_numid(numid) { + snd_ctl_elem_info_malloc(&m_info); + + if (m_info == nullptr) { + throw control_error("Failed to allocate alsa_ctl info"); + } + + snd_ctl_elem_value_malloc(&m_value); + + if (m_value == nullptr) { + throw control_error("Failed to allocate alsa_ctl value"); + } + + snd_ctl_elem_id_malloc(&m_id); + + if (m_id == nullptr) { + throw control_error("Failed to allocate alsa_ctl id"); + } + + snd_ctl_elem_id_set_numid(m_id, m_numid); + snd_ctl_elem_info_set_id(m_info, m_id); + + int err = 0; + + if ((err = snd_ctl_open(&m_ctl, ALSA_SOUNDCARD, SND_CTL_NONBLOCK | SND_CTL_READONLY)) == -1) { + throw_exception("Could not open control '" + string{ALSA_SOUNDCARD} + "'", err); + } + + snd_config_update_free_global(); + + if ((err = snd_ctl_elem_info(m_ctl, m_info)) == -1) { + throw_exception("Could not get control datal", err); + } + + snd_ctl_elem_info_get_id(m_info, m_id); + + if ((err = snd_hctl_open(&m_hctl, ALSA_SOUNDCARD, 0)) == -1) { + throw_exception("Failed to open hctl", err); + } + + snd_config_update_free_global(); + + if (m_hctl == nullptr || (err = snd_hctl_load(m_hctl)) == -1) { + throw_exception("Failed to load hctl", err); + } + + if ((m_elem = snd_hctl_find_elem(m_hctl, m_id)) == nullptr) { + throw control_error("Could not find control with id " + to_string(snd_ctl_elem_id_get_numid(m_id))); + } + + if ((err = snd_ctl_subscribe_events(m_ctl, 1)) == -1) { + throw control_error("Could not subscribe to events: " + to_string(snd_ctl_elem_id_get_numid(m_id))); + } + } + + /** + * Deconstruct control object + */ + control::~control() { + std::lock_guard guard(m_lock); + if (m_info != nullptr) { + snd_ctl_elem_info_free(m_info); + } + if (m_value != nullptr) { + snd_ctl_elem_value_free(m_value); + } + if (m_id != nullptr) { + snd_ctl_elem_id_free(m_id); + } + if (m_ctl != nullptr) { + snd_ctl_close(m_ctl); + } + if (m_hctl != nullptr) { + snd_hctl_close(m_hctl); + } + } + + /** + * Get the id number + */ + int control::get_numid() { + return m_numid; + } + + /** + * Wait for events + */ + bool control::wait(int timeout) { + assert(m_ctl); + + if (!m_lock.try_lock()) { + return false; + } + + std::lock_guard guard(m_lock, std::adopt_lock); + + int err = 0; + + if ((err = snd_ctl_wait(m_ctl, timeout)) == -1) { + throw_exception("Failed to wait for events", err); + } + + snd_ctl_event_t* event; + snd_ctl_event_alloca(&event); + + if ((err = snd_ctl_read(m_ctl, event)) == -1) { + return false; + } + + if (snd_ctl_event_get_type(event) == SND_CTL_EVENT_ELEM) { + return snd_ctl_event_elem_get_mask(event) & SND_CTL_EVENT_MASK_VALUE; + } + + return false; + } + + /** + * Check if the interface is in use + */ + bool control::test_device_plugged() { + assert(m_elem); + assert(m_value); + + if (!m_lock.try_lock()) { + return false; + } + + std::lock_guard guard(m_lock, std::adopt_lock); + + int err = 0; + if ((err = snd_hctl_elem_read(m_elem, m_value)) == -1) { + throw_exception("Could not read control value", err); + } + return snd_ctl_elem_value_get_boolean(m_value, 0); + } + + /** + * Process queued events + */ + void control::process_events() { + wait(0); + } +} + +POLYBAR_NS_END diff --git a/src/adapters/alsa/mixer.cpp b/src/adapters/alsa/mixer.cpp new file mode 100644 index 00000000..2632dcb2 --- /dev/null +++ b/src/adapters/alsa/mixer.cpp @@ -0,0 +1,282 @@ +#include + +#include "adapters/alsa/generic.hpp" +#include "adapters/alsa/mixer.hpp" +#include "utils/math.hpp" + +#define MAX_LINEAR_DB_SCALE 24 + +POLYBAR_NS + +namespace alsa { + /** + * Construct mixer object + */ + mixer::mixer(string mixer_control_name) : m_name(move(mixer_control_name)) { + if (m_name.empty()) { + throw mixer_error("Invalid control name"); + } + + snd_mixer_selem_id_malloc(&m_mixerid); + + if (m_mixerid == nullptr) { + throw mixer_error("Failed to allocate mixer id"); + } + + int err = 0; + if ((err = snd_mixer_open(&m_hardwaremixer, 1)) == -1) { + throw_exception("Failed to open hardware mixer", err); + } + + snd_config_update_free_global(); + + if ((err = snd_mixer_attach(m_hardwaremixer, ALSA_SOUNDCARD)) == -1) { + throw_exception("Failed to attach hardware mixer control", err); + } + if ((err = snd_mixer_selem_register(m_hardwaremixer, nullptr, nullptr)) == -1) { + throw_exception("Failed to register simple mixer element", err); + } + if ((err = snd_mixer_load(m_hardwaremixer)) == -1) { + throw_exception("Failed to load mixer", err); + } + + snd_mixer_selem_id_set_index(m_mixerid, 0); + snd_mixer_selem_id_set_name(m_mixerid, m_name.c_str()); + + if ((m_mixerelement = snd_mixer_find_selem(m_hardwaremixer, m_mixerid)) == nullptr) { + throw mixer_error("Cannot find simple element"); + } + } + + /** + * Deconstruct mixer + */ + mixer::~mixer() { + std::lock_guard guard(m_lock); + if (m_mixerid != nullptr) { + snd_mixer_selem_id_free(m_mixerid); + } + if (m_mixerelement != nullptr) { + snd_mixer_elem_remove(m_mixerelement); + } + if (m_hardwaremixer != nullptr) { + snd_mixer_detach(m_hardwaremixer, ALSA_SOUNDCARD); + snd_mixer_close(m_hardwaremixer); + } + } + + /** + * Get mixer name + */ + string mixer::get_name() { + return m_name; + } + + /** + * Wait for events + */ + bool mixer::wait(int timeout) { + assert(m_hardwaremixer); + + if (!m_lock.try_lock()) { + return false; + } + + std::unique_lock guard(m_lock, std::adopt_lock); + + int err = 0; + + if ((err = snd_mixer_wait(m_hardwaremixer, timeout)) == -1) { + throw_exception("Failed to wait for events", err); + } + + guard.unlock(); + + return process_events() > 0; + } + + /** + * Process queued mixer events + */ + int mixer::process_events() { + if (!m_lock.try_lock()) { + return false; + } + + std::lock_guard guard(m_lock, std::adopt_lock); + + int num_events = snd_mixer_handle_events(m_hardwaremixer); + if (num_events == -1) { + throw_exception("Failed to process pending events", num_events); + } + + return num_events; + } + + /** + * Get volume in percentage + */ + int mixer::get_volume() { + if (!m_lock.try_lock()) { + return 0; + } + + std::lock_guard guard(m_lock, std::adopt_lock); + + long chan_n = 0, vol_total = 0, vol, vol_min, vol_max; + + snd_mixer_selem_get_playback_volume_range(m_mixerelement, &vol_min, &vol_max); + + for (int i = 0; i <= SND_MIXER_SCHN_LAST; i++) { + if (snd_mixer_selem_has_playback_channel(m_mixerelement, static_cast(i))) { + snd_mixer_selem_get_playback_volume(m_mixerelement, static_cast(i), &vol); + vol_total += vol; + chan_n++; + } + } + + return math_util::percentage(vol_total / chan_n, vol_min, vol_max); + } + + /** + * Get normalized volume in percentage + */ + int mixer::get_normalized_volume() { + if (!m_lock.try_lock()) { + return 0; + } + + std::lock_guard guard(m_lock, std::adopt_lock); + + long chan_n = 0, vol_total = 0, vol, vol_min, vol_max; + double normalized, min_norm; + + snd_mixer_selem_get_playback_dB_range(m_mixerelement, &vol_min, &vol_max); + + for (int i = 0; i <= SND_MIXER_SCHN_LAST; i++) { + if (snd_mixer_selem_has_playback_channel(m_mixerelement, static_cast(i))) { + snd_mixer_selem_get_playback_dB(m_mixerelement, static_cast(i), &vol); + vol_total += vol; + chan_n++; + } + } + + if (vol_max - vol_min <= MAX_LINEAR_DB_SCALE * 100) { + return math_util::percentage(vol_total / chan_n, vol_min, vol_max); + } + + normalized = pow10((vol_total / chan_n - vol_max) / 6000.0); + if (vol_min != SND_CTL_TLV_DB_GAIN_MUTE) { + min_norm = pow10((vol_min - vol_max) / 6000.0); + normalized = (normalized - min_norm) / (1 - min_norm); + } + + return 100.0f * normalized + 0.5f; + } + + /** + * Set volume to given percentage + */ + void mixer::set_volume(float percentage) { + if (is_muted()) { + return; + } + + if (!m_lock.try_lock()) { + return; + } + + std::lock_guard guard(m_lock, std::adopt_lock); + + long vol_min, vol_max; + snd_mixer_selem_get_playback_volume_range(m_mixerelement, &vol_min, &vol_max); + snd_mixer_selem_set_playback_volume_all( + m_mixerelement, math_util::percentage_to_value(percentage, vol_min, vol_max)); + } + + /** + * Set normalized volume to given percentage + */ + void mixer::set_normalized_volume(float percentage) { + if (is_muted()) { + return; + } + + if (!m_lock.try_lock()) { + return; + } + + std::lock_guard guard(m_lock, std::adopt_lock); + + long vol_min, vol_max; + double min_norm; + percentage = percentage / 100.0f; + + snd_mixer_selem_get_playback_dB_range(m_mixerelement, &vol_min, &vol_max); + + if (vol_max - vol_min <= MAX_LINEAR_DB_SCALE * 100) { + snd_mixer_selem_set_playback_dB_all(m_mixerelement, lrint(percentage * (vol_max - vol_min)) + vol_min, 0); + return; + } + + if (vol_min != SND_CTL_TLV_DB_GAIN_MUTE) { + min_norm = pow10((vol_min - vol_max) / 6000.0); + percentage = percentage * (1 - min_norm) + min_norm; + } + + snd_mixer_selem_set_playback_dB_all(m_mixerelement, lrint(6000.0 * log10(percentage)) + vol_max, 0); + } + + /** + * Set mute state + */ + void mixer::set_mute(bool mode) { + if (!m_lock.try_lock()) { + return; + } + + std::lock_guard guard(m_lock, std::adopt_lock); + + snd_mixer_selem_set_playback_switch_all(m_mixerelement, mode); + } + + /** + * Toggle mute state + */ + void mixer::toggle_mute() { + if (!m_lock.try_lock()) { + return; + } + + std::lock_guard guard(m_lock, std::adopt_lock); + + int state; + + snd_mixer_selem_get_playback_switch(m_mixerelement, SND_MIXER_SCHN_MONO, &state); + snd_mixer_selem_set_playback_switch_all(m_mixerelement, !state); + } + + /** + * Get current mute state + */ + bool mixer::is_muted() { + if (!m_lock.try_lock()) { + return false; + } + + std::lock_guard guard(m_lock, std::adopt_lock); + + int state = 0; + + for (int i = 0; i <= SND_MIXER_SCHN_LAST; i++) { + if (snd_mixer_selem_has_playback_channel(m_mixerelement, static_cast(i))) { + int state_ = 0; + snd_mixer_selem_get_playback_switch(m_mixerelement, static_cast(i), &state_); + state = state || state_; + } + } + return !state; + } +} + +POLYBAR_NS_END diff --git a/src/modules/volume.cpp b/src/modules/volume.cpp index d0af265c..f28590bc 100644 --- a/src/modules/volume.cpp +++ b/src/modules/volume.cpp @@ -1,5 +1,7 @@ #include "modules/volume.hpp" - +#include "adapters/alsa/control.hpp" +#include "adapters/alsa/generic.hpp" +#include "adapters/alsa/mixer.hpp" #include "drawtypes/label.hpp" #include "drawtypes/progressbar.hpp" #include "drawtypes/ramp.hpp" @@ -10,6 +12,8 @@ POLYBAR_NS +using namespace alsa; + namespace modules { template class module; template class event_module; @@ -53,9 +57,9 @@ namespace modules { if (m_mixer.empty()) { throw module_error("No configured mixers"); } - } catch (const alsa_mixer_error& err) { + } catch (const mixer_error& err) { throw module_error(err.what()); - } catch (const alsa_ctl_interface_error& err) { + } catch (const control_error& err) { throw module_error(err.what()); }