refactor(alsa): Forward alsa structs and split classes
This commit is contained in:
parent
a33c15b3ad
commit
5e2a0bd298
@ -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
|
49
include/adapters/alsa/control.hpp
Normal file
49
include/adapters/alsa/control.hpp
Normal 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
|
60
include/adapters/alsa/generic.hpp
Normal file
60
include/adapters/alsa/generic.hpp
Normal 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
|
47
include/adapters/alsa/mixer.hpp
Normal file
47
include/adapters/alsa/mixer.hpp
Normal 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
|
@ -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<alsa_mixer>;
|
||||
using control_t = shared_ptr<alsa_ctl_interface>;
|
||||
using mixer_t = shared_ptr<alsa::mixer>;
|
||||
using control_t = shared_ptr<alsa::control>;
|
||||
|
||||
class volume_module : public event_module<volume_module> {
|
||||
public:
|
||||
|
@ -32,7 +32,7 @@ namespace factory_util {
|
||||
|
||||
template <class T, class... 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;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
154
src/adapters/alsa/control.cpp
Normal file
154
src/adapters/alsa/control.cpp
Normal 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
282
src/adapters/alsa/mixer.cpp
Normal 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
|
@ -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<volume_module>;
|
||||
template class event_module<volume_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());
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user