parent
737da87e22
commit
0e9900db74
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// }}}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// }}}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
this->builder->cmd(Cmd::SCROLL_DOWN, EVENT_VOLUME_DOWN);
|
if (this->volume() > 0)
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user