205 lines
4.9 KiB
C++
205 lines
4.9 KiB
C++
#include "modules/sndio.hpp"
|
|
|
|
#include <fmt/format.h>
|
|
#include <poll.h>
|
|
#include <spdlog/spdlog.h>
|
|
|
|
#include <algorithm>
|
|
#include <cstdlib>
|
|
|
|
namespace waybar::modules {
|
|
|
|
void ondesc(void *arg, struct sioctl_desc *d, int curval) {
|
|
auto self = static_cast<Sndio *>(arg);
|
|
if (d == NULL) {
|
|
// d is NULL when the list is done
|
|
return;
|
|
}
|
|
self->set_desc(d, curval);
|
|
}
|
|
|
|
void onval(void *arg, unsigned int addr, unsigned int val) {
|
|
auto self = static_cast<Sndio *>(arg);
|
|
self->put_val(addr, val);
|
|
}
|
|
|
|
auto Sndio::connect_to_sndio() -> void {
|
|
hdl_ = sioctl_open(SIO_DEVANY, SIOCTL_READ | SIOCTL_WRITE, 0);
|
|
if (hdl_ == nullptr) {
|
|
throw std::runtime_error("sioctl_open() failed.");
|
|
}
|
|
|
|
if (sioctl_ondesc(hdl_, ondesc, this) == 0) {
|
|
throw std::runtime_error("sioctl_ondesc() failed.");
|
|
}
|
|
|
|
if (sioctl_onval(hdl_, onval, this) == 0) {
|
|
throw std::runtime_error("sioctl_onval() failed.");
|
|
}
|
|
|
|
pfds_.reserve(sioctl_nfds(hdl_));
|
|
}
|
|
|
|
Sndio::Sndio(const std::string &id, const Json::Value &config)
|
|
: ALabel(config, "sndio", id, "{volume}%", 1, false, true),
|
|
hdl_(nullptr),
|
|
pfds_(0),
|
|
addr_(0),
|
|
volume_(0),
|
|
old_volume_(0),
|
|
maxval_(0),
|
|
muted_(false) {
|
|
connect_to_sndio();
|
|
|
|
event_box_.show();
|
|
|
|
event_box_.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK | Gdk::BUTTON_PRESS_MASK);
|
|
event_box_.signal_scroll_event().connect(sigc::mem_fun(*this, &Sndio::handleScroll));
|
|
event_box_.signal_button_press_event().connect(sigc::mem_fun(*this, &Sndio::handleToggle));
|
|
|
|
thread_ = [this] {
|
|
dp.emit();
|
|
|
|
int nfds = sioctl_pollfd(hdl_, pfds_.data(), POLLIN);
|
|
if (nfds == 0) {
|
|
throw std::runtime_error("sioctl_pollfd() failed.");
|
|
}
|
|
while (poll(pfds_.data(), nfds, -1) < 0) {
|
|
if (errno != EINTR) {
|
|
throw std::runtime_error("poll() failed.");
|
|
}
|
|
}
|
|
|
|
int revents = sioctl_revents(hdl_, pfds_.data());
|
|
if (revents & POLLHUP) {
|
|
spdlog::warn("sndio disconnected!");
|
|
sioctl_close(hdl_);
|
|
hdl_ = nullptr;
|
|
|
|
// reconnection loop
|
|
while (thread_.isRunning()) {
|
|
try {
|
|
connect_to_sndio();
|
|
} catch (std::runtime_error const &e) {
|
|
// avoid leaking hdl_
|
|
if (hdl_) {
|
|
sioctl_close(hdl_);
|
|
hdl_ = nullptr;
|
|
}
|
|
// rate limiting for the retries
|
|
thread_.sleep_for(interval_);
|
|
continue;
|
|
}
|
|
|
|
spdlog::warn("sndio reconnected!");
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
Sndio::~Sndio() { sioctl_close(hdl_); }
|
|
|
|
auto Sndio::update() -> void {
|
|
auto format = format_;
|
|
unsigned int vol = 100. * static_cast<double>(volume_) / static_cast<double>(maxval_);
|
|
|
|
if (volume_ == 0) {
|
|
label_.get_style_context()->add_class("muted");
|
|
} else {
|
|
label_.get_style_context()->remove_class("muted");
|
|
}
|
|
|
|
auto text =
|
|
fmt::format(fmt::runtime(format), fmt::arg("volume", vol), fmt::arg("raw_value", volume_));
|
|
if (text.empty()) {
|
|
label_.hide();
|
|
} else {
|
|
label_.set_markup(text);
|
|
label_.show();
|
|
}
|
|
|
|
ALabel::update();
|
|
}
|
|
|
|
auto Sndio::set_desc(struct sioctl_desc *d, unsigned int val) -> void {
|
|
std::string name{d->func};
|
|
std::string node_name{d->node0.name};
|
|
|
|
if (name == "level" && node_name == "output" && d->type == SIOCTL_NUM) {
|
|
// store addr for output.level value, used in put_val
|
|
addr_ = d->addr;
|
|
maxval_ = d->maxval;
|
|
volume_ = val;
|
|
}
|
|
}
|
|
|
|
auto Sndio::put_val(unsigned int addr, unsigned int val) -> void {
|
|
if (addr == addr_) {
|
|
volume_ = val;
|
|
}
|
|
}
|
|
|
|
bool Sndio::handleScroll(GdkEventScroll *e) {
|
|
// change the volume only when no user provided
|
|
// events are configured
|
|
if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString()) {
|
|
return AModule::handleScroll(e);
|
|
}
|
|
|
|
// only try to talk to sndio if connected
|
|
if (hdl_ == nullptr) return true;
|
|
|
|
auto dir = AModule::getScrollDir(e);
|
|
if (dir == SCROLL_DIR::NONE) {
|
|
return true;
|
|
}
|
|
|
|
int step = 5;
|
|
if (config_["scroll-step"].isInt()) {
|
|
step = config_["scroll-step"].asInt();
|
|
}
|
|
|
|
int new_volume = volume_;
|
|
if (muted_) {
|
|
new_volume = old_volume_;
|
|
}
|
|
|
|
if (dir == SCROLL_DIR::UP) {
|
|
new_volume += step;
|
|
} else if (dir == SCROLL_DIR::DOWN) {
|
|
new_volume -= step;
|
|
}
|
|
new_volume = std::clamp(new_volume, 0, static_cast<int>(maxval_));
|
|
|
|
// quits muted mode if volume changes
|
|
muted_ = false;
|
|
|
|
sioctl_setval(hdl_, addr_, new_volume);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Sndio::handleToggle(GdkEventButton *const &e) {
|
|
// toggle mute only when no user provided events are configured
|
|
if (config_["on-click"].isString()) {
|
|
return AModule::handleToggle(e);
|
|
}
|
|
|
|
// only try to talk to sndio if connected
|
|
if (hdl_ == nullptr) return true;
|
|
|
|
muted_ = !muted_;
|
|
if (muted_) {
|
|
// store old volume to be able to restore it later
|
|
old_volume_ = volume_;
|
|
sioctl_setval(hdl_, addr_, 0);
|
|
} else {
|
|
sioctl_setval(hdl_, addr_, old_volume_);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
} /* namespace waybar::modules */
|