Add backlight module
Monitor the backlight level via udev. Poll every `interval` as well, in case backlight udev events aren't supported.
This commit is contained in:
parent
6bf4f65228
commit
875306804c
|
@ -19,6 +19,7 @@
|
||||||
#ifdef HAVE_LIBPULSE
|
#ifdef HAVE_LIBPULSE
|
||||||
#include "modules/pulseaudio.hpp"
|
#include "modules/pulseaudio.hpp"
|
||||||
#endif
|
#endif
|
||||||
|
#include "modules/backlight.hpp"
|
||||||
#include "modules/custom.hpp"
|
#include "modules/custom.hpp"
|
||||||
|
|
||||||
namespace waybar {
|
namespace waybar {
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "ALabel.hpp"
|
||||||
|
#include "util/json.hpp"
|
||||||
|
#include "util/sleeper_thread.hpp"
|
||||||
|
|
||||||
|
struct udev;
|
||||||
|
struct udev_device;
|
||||||
|
|
||||||
|
namespace waybar::modules {
|
||||||
|
|
||||||
|
class Backlight : public ALabel {
|
||||||
|
class BacklightDev {
|
||||||
|
public:
|
||||||
|
BacklightDev() = default;
|
||||||
|
BacklightDev(std::string name, int actual, int max);
|
||||||
|
std::string_view name() const;
|
||||||
|
int get_actual() const;
|
||||||
|
void set_actual(int actual);
|
||||||
|
int get_max() const;
|
||||||
|
void set_max(int max);
|
||||||
|
friend inline bool operator==(const BacklightDev &lhs,
|
||||||
|
const BacklightDev &rhs) {
|
||||||
|
return lhs.name_ == rhs.name_ && lhs.actual_ == rhs.actual_ &&
|
||||||
|
lhs.max_ == rhs.max_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string name_;
|
||||||
|
int actual_ = 1;
|
||||||
|
int max_ = 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
Backlight(const std::string &, const Json::Value &);
|
||||||
|
~Backlight();
|
||||||
|
auto update() -> void;
|
||||||
|
|
||||||
|
private:
|
||||||
|
template <class ForwardIt>
|
||||||
|
static const BacklightDev *best_device(ForwardIt first, ForwardIt last,
|
||||||
|
std::string_view);
|
||||||
|
template <class ForwardIt, class Inserter>
|
||||||
|
static void upsert_device(ForwardIt first, ForwardIt last, Inserter inserter,
|
||||||
|
udev_device *dev);
|
||||||
|
template <class ForwardIt, class Inserter>
|
||||||
|
static void enumerate_devices(ForwardIt first, ForwardIt last,
|
||||||
|
Inserter inserter, udev *udev);
|
||||||
|
|
||||||
|
const std::string name_;
|
||||||
|
const std::string preferred_device_;
|
||||||
|
static constexpr int EPOLL_MAX_EVENTS = 16;
|
||||||
|
|
||||||
|
waybar::util::SleeperThread udev_thread_;
|
||||||
|
std::mutex udev_thread_mutex_;
|
||||||
|
std::vector<BacklightDev> devices_;
|
||||||
|
|
||||||
|
std::optional<BacklightDev> previous_best_;
|
||||||
|
};
|
||||||
|
} // namespace waybar::modules
|
|
@ -55,6 +55,7 @@ sigcpp = dependency('sigc++-2.0')
|
||||||
libnl = dependency('libnl-3.0', required: get_option('libnl'))
|
libnl = dependency('libnl-3.0', required: get_option('libnl'))
|
||||||
libnlgen = dependency('libnl-genl-3.0', required: get_option('libnl'))
|
libnlgen = dependency('libnl-genl-3.0', required: get_option('libnl'))
|
||||||
libpulse = dependency('libpulse', required: get_option('pulseaudio'))
|
libpulse = dependency('libpulse', required: get_option('pulseaudio'))
|
||||||
|
libudev = dependency('libudev')
|
||||||
|
|
||||||
src_files = files(
|
src_files = files(
|
||||||
'src/factory.cpp',
|
'src/factory.cpp',
|
||||||
|
@ -64,6 +65,7 @@ src_files = files(
|
||||||
'src/modules/clock.cpp',
|
'src/modules/clock.cpp',
|
||||||
'src/modules/custom.cpp',
|
'src/modules/custom.cpp',
|
||||||
'src/modules/cpu.cpp',
|
'src/modules/cpu.cpp',
|
||||||
|
'src/modules/backlight.cpp',
|
||||||
'src/main.cpp',
|
'src/main.cpp',
|
||||||
'src/bar.cpp',
|
'src/bar.cpp',
|
||||||
'src/client.cpp'
|
'src/client.cpp'
|
||||||
|
@ -119,7 +121,8 @@ executable(
|
||||||
giounix,
|
giounix,
|
||||||
libnl,
|
libnl,
|
||||||
libnlgen,
|
libnlgen,
|
||||||
libpulse
|
libpulse,
|
||||||
|
libudev
|
||||||
],
|
],
|
||||||
include_directories: [include_directories('include')],
|
include_directories: [include_directories('include')],
|
||||||
install: true,
|
install: true,
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
// Choose the order of the modules
|
// Choose the order of the modules
|
||||||
"modules-left": ["sway/workspaces", "sway/mode", "custom/spotify"],
|
"modules-left": ["sway/workspaces", "sway/mode", "custom/spotify"],
|
||||||
"modules-center": ["sway/window"],
|
"modules-center": ["sway/window"],
|
||||||
"modules-right": ["pulseaudio", "network", "cpu", "memory", "battery", "battery#bat2", "clock", "tray"],
|
"modules-right": ["pulseaudio", "network", "cpu", "memory", "backlight", "battery", "battery#bat2", "clock", "tray"],
|
||||||
// Modules configuration
|
// Modules configuration
|
||||||
// "sway/workspaces": {
|
// "sway/workspaces": {
|
||||||
// "disable-scroll": true,
|
// "disable-scroll": true,
|
||||||
|
@ -39,6 +39,11 @@
|
||||||
"memory": {
|
"memory": {
|
||||||
"format": "{}% "
|
"format": "{}% "
|
||||||
},
|
},
|
||||||
|
"backlight": {
|
||||||
|
// "device": "acpi_video1",
|
||||||
|
"format": "{percent}% {icon}",
|
||||||
|
"format-icons": ["", ""]
|
||||||
|
},
|
||||||
"battery": {
|
"battery": {
|
||||||
"states": {
|
"states": {
|
||||||
// "good": 95,
|
// "good": 95,
|
||||||
|
|
|
@ -29,7 +29,7 @@ window#waybar {
|
||||||
border-bottom: 3px solid white;
|
border-bottom: 3px solid white;
|
||||||
}
|
}
|
||||||
|
|
||||||
#clock, #battery, #cpu, #memory, #network, #pulseaudio, #custom-spotify, #tray, #mode {
|
#clock, #battery, #cpu, #memory, #backlight, #network, #pulseaudio, #custom-spotify, #tray, #mode {
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
margin: 0 5px;
|
margin: 0 5px;
|
||||||
}
|
}
|
||||||
|
@ -74,6 +74,10 @@ window#waybar {
|
||||||
background: #9b59b6;
|
background: #9b59b6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#backlight {
|
||||||
|
background: #90b1b1;
|
||||||
|
}
|
||||||
|
|
||||||
#network {
|
#network {
|
||||||
background: #2980b9;
|
background: #2980b9;
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,9 @@ waybar::IModule* waybar::Factory::makeModule(const std::string &name) const
|
||||||
return new waybar::modules::Pulseaudio(id, config_[name]);
|
return new waybar::modules::Pulseaudio(id, config_[name]);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
if (ref == "backlight") {
|
||||||
|
return new waybar::modules::Backlight(id, config_[name]);
|
||||||
|
}
|
||||||
if (ref.compare(0, 7, "custom/") == 0 && ref.size() > 7) {
|
if (ref.compare(0, 7, "custom/") == 0 && ref.size() > 7) {
|
||||||
return new waybar::modules::Custom(ref.substr(7), config_[name]);
|
return new waybar::modules::Custom(ref.substr(7), config_[name]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,285 @@
|
||||||
|
#include "modules/backlight.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include <libudev.h>
|
||||||
|
|
||||||
|
#include <sys/epoll.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
class FileDescriptor {
|
||||||
|
public:
|
||||||
|
explicit FileDescriptor(int fd) : fd_(fd) {}
|
||||||
|
FileDescriptor(const FileDescriptor &other) = delete;
|
||||||
|
FileDescriptor(FileDescriptor &&other) noexcept = delete;
|
||||||
|
FileDescriptor &operator=(const FileDescriptor &other) = delete;
|
||||||
|
FileDescriptor &operator=(FileDescriptor &&other) noexcept = delete;
|
||||||
|
~FileDescriptor() {
|
||||||
|
if (fd_ != -1) {
|
||||||
|
if (close(fd_) != 0) {
|
||||||
|
fmt::print(stderr, "Failed to close fd: {}\n", errno);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int get() const { return fd_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
int fd_;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UdevDeleter {
|
||||||
|
void operator()(udev *ptr) { udev_unref(ptr); }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UdevDeviceDeleter {
|
||||||
|
void operator()(udev_device *ptr) { udev_device_unref(ptr); }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UdevEnumerateDeleter {
|
||||||
|
void operator()(udev_enumerate *ptr) { udev_enumerate_unref(ptr); }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UdevMonitorDeleter {
|
||||||
|
void operator()(udev_monitor *ptr) { udev_monitor_unref(ptr); }
|
||||||
|
};
|
||||||
|
|
||||||
|
void check_eq(int rc, int expected, const char *message = "eq, rc was: ") {
|
||||||
|
if (rc != expected) {
|
||||||
|
throw std::runtime_error(fmt::format(message, rc));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void check_neq(int rc, int bad_rc, const char *message = "neq, rc was: ") {
|
||||||
|
if (rc == bad_rc) {
|
||||||
|
throw std::runtime_error(fmt::format(message, rc));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void check0(int rc, const char *message = "rc wasn't 0") {
|
||||||
|
check_eq(rc, 0, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void check_gte(int rc, int gte, const char *message = "rc was: ") {
|
||||||
|
if (rc < gte) {
|
||||||
|
throw std::runtime_error(fmt::format(message, rc));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void check_nn(const void *ptr, const char *message = "ptr was null") {
|
||||||
|
if (ptr == nullptr) {
|
||||||
|
throw std::runtime_error(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
waybar::modules::Backlight::BacklightDev::BacklightDev(std::string name,
|
||||||
|
int actual, int max)
|
||||||
|
: name_(std::move(name)), actual_(actual), max_(max) {}
|
||||||
|
|
||||||
|
std::string_view waybar::modules::Backlight::BacklightDev::name() const {
|
||||||
|
return name_;
|
||||||
|
}
|
||||||
|
|
||||||
|
int waybar::modules::Backlight::BacklightDev::get_actual() const {
|
||||||
|
return actual_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void waybar::modules::Backlight::BacklightDev::set_actual(int actual) {
|
||||||
|
actual_ = actual;
|
||||||
|
}
|
||||||
|
|
||||||
|
int waybar::modules::Backlight::BacklightDev::get_max() const { return max_; }
|
||||||
|
|
||||||
|
void waybar::modules::Backlight::BacklightDev::set_max(int max) { max_ = max; }
|
||||||
|
|
||||||
|
waybar::modules::Backlight::Backlight(const std::string &name,
|
||||||
|
const Json::Value &config)
|
||||||
|
: ALabel(config, "{}", 2), name_(name),
|
||||||
|
preferred_device_(
|
||||||
|
config["device"].isString() ? config["device"].asString() : "") {
|
||||||
|
label_.set_name("backlight");
|
||||||
|
|
||||||
|
udev_thread_ = [this] {
|
||||||
|
std::unique_ptr<udev, UdevDeleter> udev{udev_new()};
|
||||||
|
check_nn(udev.get(), "Udev new failed");
|
||||||
|
|
||||||
|
std::unique_ptr<udev_monitor, UdevMonitorDeleter> mon{
|
||||||
|
udev_monitor_new_from_netlink(udev.get(), "udev")};
|
||||||
|
check_nn(mon.get(), "udev monitor new failed");
|
||||||
|
check_gte(udev_monitor_filter_add_match_subsystem_devtype(
|
||||||
|
mon.get(), "backlight", nullptr),
|
||||||
|
0, "udev failed to add monitor filter: ");
|
||||||
|
udev_monitor_enable_receiving(mon.get());
|
||||||
|
|
||||||
|
auto udev_fd = udev_monitor_get_fd(mon.get());
|
||||||
|
|
||||||
|
auto epoll_fd = FileDescriptor{epoll_create1(0)};
|
||||||
|
check_neq(epoll_fd.get(), -1, "epoll init failed: ");
|
||||||
|
epoll_event ctl_event;
|
||||||
|
ctl_event.events = EPOLLIN;
|
||||||
|
ctl_event.data.fd = udev_fd;
|
||||||
|
|
||||||
|
check0(
|
||||||
|
epoll_ctl(epoll_fd.get(), EPOLL_CTL_ADD, ctl_event.data.fd, &ctl_event),
|
||||||
|
"epoll_ctl failed: {}");
|
||||||
|
epoll_event events[EPOLL_MAX_EVENTS];
|
||||||
|
|
||||||
|
// Get initial state
|
||||||
|
{
|
||||||
|
decltype(devices_) devices;
|
||||||
|
{
|
||||||
|
std::scoped_lock<std::mutex> lock(udev_thread_mutex_);
|
||||||
|
devices = devices_;
|
||||||
|
}
|
||||||
|
enumerate_devices(devices.begin(), devices.end(),
|
||||||
|
std::back_inserter(devices), udev.get());
|
||||||
|
{
|
||||||
|
std::scoped_lock<std::mutex> lock(udev_thread_mutex_);
|
||||||
|
devices_ = devices;
|
||||||
|
}
|
||||||
|
dp.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
while (udev_thread_.isRunning()) {
|
||||||
|
const int event_count =
|
||||||
|
epoll_wait(epoll_fd.get(), events, EPOLL_MAX_EVENTS,
|
||||||
|
std::chrono::milliseconds{interval_}.count());
|
||||||
|
decltype(devices_) devices;
|
||||||
|
{
|
||||||
|
std::scoped_lock<std::mutex> lock(udev_thread_mutex_);
|
||||||
|
devices = devices_;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < event_count; ++i) {
|
||||||
|
const auto &event = events[i];
|
||||||
|
check_eq(event.data.fd, udev_fd, "unexpected udev fd");
|
||||||
|
std::unique_ptr<udev_device, UdevDeviceDeleter> dev{
|
||||||
|
udev_monitor_receive_device(mon.get())};
|
||||||
|
check_nn(dev.get(), "epoll dev was null");
|
||||||
|
upsert_device(devices.begin(), devices.end(),
|
||||||
|
std::back_inserter(devices), dev.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh state if timed out
|
||||||
|
if (event_count == 0) {
|
||||||
|
enumerate_devices(devices.begin(), devices.end(),
|
||||||
|
std::back_inserter(devices), udev.get());
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::scoped_lock<std::mutex> lock(udev_thread_mutex_);
|
||||||
|
devices_ = devices;
|
||||||
|
}
|
||||||
|
dp.emit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
waybar::modules::Backlight::~Backlight() = default;
|
||||||
|
|
||||||
|
auto waybar::modules::Backlight::update() -> void {
|
||||||
|
decltype(devices_) devices;
|
||||||
|
{
|
||||||
|
std::scoped_lock<std::mutex> lock(udev_thread_mutex_);
|
||||||
|
devices = devices_;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string markup_fmt;
|
||||||
|
if (config_["format"].isString()) {
|
||||||
|
markup_fmt = config_["format"].asString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (markup_fmt.empty()) {
|
||||||
|
markup_fmt = "{percent}%";
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto best =
|
||||||
|
best_device(devices.cbegin(), devices.cend(), preferred_device_);
|
||||||
|
if (best != nullptr) {
|
||||||
|
if (previous_best_.has_value() && previous_best_.value() == *best) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto percent =
|
||||||
|
best->get_max() == 0 ? 100 : best->get_actual() * 100 / best->get_max();
|
||||||
|
label_.set_markup(fmt::format(markup_fmt,
|
||||||
|
fmt::arg("percent", std::to_string(percent)),
|
||||||
|
fmt::arg("icon", getIcon(percent))));
|
||||||
|
} else {
|
||||||
|
if (!previous_best_.has_value()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
label_.set_markup("");
|
||||||
|
}
|
||||||
|
previous_best_ = best == nullptr ? std::nullopt : std::optional{*best};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class ForwardIt>
|
||||||
|
const waybar::modules::Backlight::BacklightDev *
|
||||||
|
waybar::modules::Backlight::best_device(ForwardIt first, ForwardIt last,
|
||||||
|
std::string_view preferred_device) {
|
||||||
|
const auto found =
|
||||||
|
std::find_if(first, last, [preferred_device](const auto &dev) {
|
||||||
|
return dev.name() == preferred_device;
|
||||||
|
});
|
||||||
|
if (found != last) {
|
||||||
|
return &(*found);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto max =
|
||||||
|
std::max_element(first, last, [](const auto &l, const auto &r) {
|
||||||
|
return l.get_max() < r.get_max();
|
||||||
|
});
|
||||||
|
|
||||||
|
return max == last ? nullptr : &(*max);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class ForwardIt, class Inserter>
|
||||||
|
void waybar::modules::Backlight::upsert_device(ForwardIt first, ForwardIt last,
|
||||||
|
Inserter inserter,
|
||||||
|
udev_device *dev) {
|
||||||
|
const char *name = udev_device_get_sysname(dev);
|
||||||
|
check_nn(name);
|
||||||
|
|
||||||
|
const char *actual = udev_device_get_sysattr_value(dev, "actual_brightness");
|
||||||
|
check_nn(actual);
|
||||||
|
const int actual_int = std::stoi(actual);
|
||||||
|
|
||||||
|
const char *max = udev_device_get_sysattr_value(dev, "max_brightness");
|
||||||
|
check_nn(max);
|
||||||
|
const int max_int = std::stoi(max);
|
||||||
|
|
||||||
|
auto found = std::find_if(first, last, [name](const auto &device) {
|
||||||
|
return device.name() == name;
|
||||||
|
});
|
||||||
|
if (found != last) {
|
||||||
|
found->set_actual(actual_int);
|
||||||
|
found->set_max(max_int);
|
||||||
|
} else {
|
||||||
|
*inserter = BacklightDev{name, actual_int, max_int};
|
||||||
|
++inserter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class ForwardIt, class Inserter>
|
||||||
|
void waybar::modules::Backlight::enumerate_devices(ForwardIt first,
|
||||||
|
ForwardIt last,
|
||||||
|
Inserter inserter,
|
||||||
|
udev *udev) {
|
||||||
|
std::unique_ptr<udev_enumerate, UdevEnumerateDeleter> enumerate{
|
||||||
|
udev_enumerate_new(udev)};
|
||||||
|
udev_enumerate_add_match_subsystem(enumerate.get(), "backlight");
|
||||||
|
udev_enumerate_scan_devices(enumerate.get());
|
||||||
|
udev_list_entry *enum_devices =
|
||||||
|
udev_enumerate_get_list_entry(enumerate.get());
|
||||||
|
udev_list_entry *dev_list_entry;
|
||||||
|
udev_list_entry_foreach(dev_list_entry, enum_devices) {
|
||||||
|
const char *path = udev_list_entry_get_name(dev_list_entry);
|
||||||
|
std::unique_ptr<udev_device, UdevDeviceDeleter> dev{
|
||||||
|
udev_device_new_from_syspath(udev, path)};
|
||||||
|
check_nn(dev.get(), "dev new failed");
|
||||||
|
upsert_device(first, last, inserter, dev.get());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue