refactor(alsa): Forward alsa structs and split classes

This commit is contained in:
Michael Carlberg 2016-12-13 14:26:09 +01:00
parent a33c15b3ad
commit 5e2a0bd298
10 changed files with 608 additions and 505 deletions

View File

@ -1,121 +0,0 @@
#pragma once
#include <assert.h>
#include <endian.h>
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <poll.h>
#include <sys/types.h>
#include <unistd.h>
#ifndef __GNUC__
#define __inline__ inline
#endif
#include <alsa/asoundef.h>
#include <alsa/version.h>
#include <alsa/global.h>
#include <alsa/input.h>
#include <alsa/output.h>
#include <alsa/error.h>
#include <alsa/conf.h>
#include <alsa/pcm.h>
#include <alsa/rawmidi.h>
#include <alsa/timer.h>
#include <alsa/hwdep.h>
#include <alsa/control.h>
#include <alsa/mixer.h>
#include <alsa/seq_event.h>
#include <alsa/seq.h>
#include <alsa/seqmid.h>
#include <alsa/seq_midi_event.h>
#include <cmath>
#include <functional>
#include <string>
#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 <typename T>
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

View File

@ -0,0 +1,49 @@
#pragma once
#include <mutex>
#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

View File

@ -0,0 +1,60 @@
#pragma once
#ifdef USE_ALSALIB_H
#include <alsa/asoundlib.h>
#else
#include <assert.h>
#include <endian.h>
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <poll.h>
#include <sys/types.h>
#include <unistd.h>
#ifndef __GNUC__
#define __inline__ inline
#endif
#include <alsa/asoundef.h>
#include <alsa/version.h>
#include <alsa/global.h>
#include <alsa/input.h>
#include <alsa/output.h>
#include <alsa/error.h>
#include <alsa/conf.h>
#include <alsa/pcm.h>
#include <alsa/rawmidi.h>
#include <alsa/timer.h>
#include <alsa/hwdep.h>
#include <alsa/control.h>
#include <alsa/mixer.h>
#include <alsa/seq_event.h>
#include <alsa/seq.h>
#include <alsa/seqmid.h>
#include <alsa/seq_midi_event.h>
#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 <typename T>
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

View File

@ -0,0 +1,47 @@
#pragma once
#include <mutex>
#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

View File

@ -1,18 +1,22 @@
#pragma once #pragma once
#include "adapters/alsa.hpp"
#include "components/config.hpp"
#include "config.hpp" #include "config.hpp"
#include "modules/meta/event_module.hpp" #include "modules/meta/event_module.hpp"
POLYBAR_NS POLYBAR_NS
// fwd
namespace alsa {
class mixer;
class control;
}
namespace modules { namespace modules {
enum class mixer { NONE = 0, MASTER, SPEAKER, HEADPHONE }; enum class mixer { NONE = 0, MASTER, SPEAKER, HEADPHONE };
enum class control { NONE = 0, HEADPHONE }; enum class control { NONE = 0, HEADPHONE };
using mixer_t = shared_ptr<alsa_mixer>; using mixer_t = shared_ptr<alsa::mixer>;
using control_t = shared_ptr<alsa_ctl_interface>; using control_t = shared_ptr<alsa::control>;
class volume_module : public event_module<volume_module> { class volume_module : public event_module<volume_module> {
public: public:

View File

@ -32,7 +32,7 @@ namespace factory_util {
template <class T, class... Deps> template <class T, class... Deps>
shared_ptr<T> singleton(Deps&&... deps) { shared_ptr<T> singleton(Deps&&... deps) {
static auto instance = make_shared<T>(forward<Deps>(deps)...); static shared_ptr<T> instance{make_shared<T>(forward<Deps>(deps)...)};
return instance; return instance;
} }
} }

View File

@ -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<alsa_ctl_interface_error>("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<alsa_ctl_interface_error>("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<alsa_ctl_interface_error>("Failed to open hctl", err);
}
snd_config_update_free_global();
if (m_hctl == nullptr || (err = snd_hctl_load(m_hctl)) < 0) {
throw_exception<alsa_ctl_interface_error>("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<std::mutex> 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<std::mutex> guard(m_lock, std::adopt_lock);
int err = 0;
if ((err = snd_ctl_wait(m_ctl, timeout)) == -1) {
throw_exception<alsa_ctl_interface_error>("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<std::mutex> guard(m_lock, std::adopt_lock);
int err = 0;
if ((err = snd_hctl_elem_read(m_elem, m_value)) < 0) {
throw_exception<alsa_ctl_interface_error>("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<alsa_mixer_error>("Failed to open hardware mixer", err);
}
snd_config_update_free_global();
if ((err = snd_mixer_attach(m_hardwaremixer, ALSA_SOUNDCARD)) == -1) {
throw_exception<alsa_mixer_error>("Failed to attach hardware mixer control", err);
}
if ((err = snd_mixer_selem_register(m_hardwaremixer, nullptr, nullptr)) == -1) {
throw_exception<alsa_mixer_error>("Failed to register simple mixer element", err);
}
if ((err = snd_mixer_load(m_hardwaremixer)) == -1) {
throw_exception<alsa_mixer_error>("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<std::mutex> 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<std::mutex> guard(m_lock, std::adopt_lock);
int err = 0;
if ((err = snd_mixer_wait(m_hardwaremixer, timeout)) == -1) {
throw_exception<alsa_mixer_error>("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<std::mutex> guard(m_lock, std::adopt_lock);
int num_events = snd_mixer_handle_events(m_hardwaremixer);
if (num_events < 0) {
throw_exception<alsa_mixer_error>("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<std::mutex> 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<snd_mixer_selem_channel_id_t>(i))) {
snd_mixer_selem_get_playback_volume(m_mixerelement, static_cast<snd_mixer_selem_channel_id_t>(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<std::mutex> 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<snd_mixer_selem_channel_id_t>(i))) {
snd_mixer_selem_get_playback_dB(m_mixerelement, static_cast<snd_mixer_selem_channel_id_t>(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<std::mutex> 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<int>(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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<snd_mixer_selem_channel_id_t>(i))) {
int state_ = 0;
snd_mixer_selem_get_playback_switch(m_mixerelement, static_cast<snd_mixer_selem_channel_id_t>(i), &state_);
state = state || state_;
}
}
return !state;
}
// }}}
POLYBAR_NS_END

View File

@ -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<control_error>("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<control_error>("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<control_error>("Failed to open hctl", err);
}
snd_config_update_free_global();
if (m_hctl == nullptr || (err = snd_hctl_load(m_hctl)) == -1) {
throw_exception<control_error>("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<std::mutex> 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<std::mutex> guard(m_lock, std::adopt_lock);
int err = 0;
if ((err = snd_ctl_wait(m_ctl, timeout)) == -1) {
throw_exception<control_error>("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<std::mutex> guard(m_lock, std::adopt_lock);
int err = 0;
if ((err = snd_hctl_elem_read(m_elem, m_value)) == -1) {
throw_exception<control_error>("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

282
src/adapters/alsa/mixer.cpp Normal file
View File

@ -0,0 +1,282 @@
#include <math.h>
#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<mixer_error>("Failed to open hardware mixer", err);
}
snd_config_update_free_global();
if ((err = snd_mixer_attach(m_hardwaremixer, ALSA_SOUNDCARD)) == -1) {
throw_exception<mixer_error>("Failed to attach hardware mixer control", err);
}
if ((err = snd_mixer_selem_register(m_hardwaremixer, nullptr, nullptr)) == -1) {
throw_exception<mixer_error>("Failed to register simple mixer element", err);
}
if ((err = snd_mixer_load(m_hardwaremixer)) == -1) {
throw_exception<mixer_error>("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<std::mutex> 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<std::mutex> guard(m_lock, std::adopt_lock);
int err = 0;
if ((err = snd_mixer_wait(m_hardwaremixer, timeout)) == -1) {
throw_exception<mixer_error>("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<std::mutex> guard(m_lock, std::adopt_lock);
int num_events = snd_mixer_handle_events(m_hardwaremixer);
if (num_events == -1) {
throw_exception<mixer_error>("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<std::mutex> 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<snd_mixer_selem_channel_id_t>(i))) {
snd_mixer_selem_get_playback_volume(m_mixerelement, static_cast<snd_mixer_selem_channel_id_t>(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<std::mutex> 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<snd_mixer_selem_channel_id_t>(i))) {
snd_mixer_selem_get_playback_dB(m_mixerelement, static_cast<snd_mixer_selem_channel_id_t>(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<std::mutex> 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<int>(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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<snd_mixer_selem_channel_id_t>(i))) {
int state_ = 0;
snd_mixer_selem_get_playback_switch(m_mixerelement, static_cast<snd_mixer_selem_channel_id_t>(i), &state_);
state = state || state_;
}
}
return !state;
}
}
POLYBAR_NS_END

View File

@ -1,5 +1,7 @@
#include "modules/volume.hpp" #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/label.hpp"
#include "drawtypes/progressbar.hpp" #include "drawtypes/progressbar.hpp"
#include "drawtypes/ramp.hpp" #include "drawtypes/ramp.hpp"
@ -10,6 +12,8 @@
POLYBAR_NS POLYBAR_NS
using namespace alsa;
namespace modules { namespace modules {
template class module<volume_module>; template class module<volume_module>;
template class event_module<volume_module>; template class event_module<volume_module>;
@ -53,9 +57,9 @@ namespace modules {
if (m_mixer.empty()) { if (m_mixer.empty()) {
throw module_error("No configured mixers"); throw module_error("No configured mixers");
} }
} catch (const alsa_mixer_error& err) { } catch (const mixer_error& err) {
throw module_error(err.what()); throw module_error(err.what());
} catch (const alsa_ctl_interface_error& err) { } catch (const control_error& err) {
throw module_error(err.what()); throw module_error(err.what());
} }