refactor: move backlight backend out of backlight module

This commit is contained in:
Brenno Lemos 2023-10-15 16:14:06 -03:00
parent 442a4b0da0
commit c3779dd16e
8 changed files with 419 additions and 317 deletions

View File

@ -1,14 +1,14 @@
#pragma once #pragma once
#include <memory>
#include <optional> #include <optional>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <vector> #include <vector>
#include "ALabel.hpp" #include "ALabel.hpp"
#include "giomm/dbusproxy.h" #include "util/backlight_backend.hpp"
#include "util/json.hpp" #include "util/json.hpp"
#include "util/sleeper_thread.hpp"
struct udev; struct udev;
struct udev_device; struct udev_device;
@ -16,54 +16,17 @@ struct udev_device;
namespace waybar::modules { namespace waybar::modules {
class Backlight : public ALabel { class Backlight : public ALabel {
class BacklightDev {
public:
BacklightDev() = default;
BacklightDev(std::string name, int actual, int max, bool powered);
std::string_view name() const;
int get_actual() const;
void set_actual(int actual);
int get_max() const;
void set_max(int max);
bool get_powered() const;
void set_powered(bool powered);
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;
bool powered_ = true;
};
public: public:
Backlight(const std::string &, const Json::Value &); Backlight(const std::string &, const Json::Value &);
virtual ~Backlight(); virtual ~Backlight() = default;
auto update() -> void override; auto update() -> void override;
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);
bool handleScroll(GdkEventScroll *e) override; bool handleScroll(GdkEventScroll *e) override;
const std::string preferred_device_; const std::string preferred_device_;
static constexpr int EPOLL_MAX_EVENTS = 16;
std::optional<BacklightDev> previous_best_;
std::string previous_format_; std::string previous_format_;
std::mutex udev_thread_mutex_; util::BacklightBackend backend;
std::vector<BacklightDev> devices_;
// thread must destruct before shared data
util::SleeperThread udev_thread_;
Glib::RefPtr<Gio::DBus::Proxy> login_proxy_;
}; };
} // namespace waybar::modules } // namespace waybar::modules

View File

@ -10,12 +10,10 @@
#include <memory> #include <memory>
#include <string> #include <string>
#include "util/backend_common.hpp"
namespace waybar::util { namespace waybar::util {
enum class ChangeType : char { Increase, Decrease };
void noop();
class AudioBackend { class AudioBackend {
private: private:
static void subscribeCb(pa_context*, pa_subscription_event_type_t, uint32_t, void*); static void subscribeCb(pa_context*, pa_subscription_event_type_t, uint32_t, void*);
@ -50,7 +48,7 @@ class AudioBackend {
std::vector<std::string> ignored_sinks_; std::vector<std::string> ignored_sinks_;
std::function<void()> on_updated_cb_ = noop; std::function<void()> on_updated_cb_ = NOOP;
/* Hack to keep constructor inaccessible but still public. /* Hack to keep constructor inaccessible but still public.
* This is required to be able to use std::make_shared. * This is required to be able to use std::make_shared.
@ -61,7 +59,7 @@ class AudioBackend {
struct private_constructor_tag {}; struct private_constructor_tag {};
public: public:
static std::shared_ptr<AudioBackend> getInstance(std::function<void()> on_updated_cb = noop); static std::shared_ptr<AudioBackend> getInstance(std::function<void()> on_updated_cb = NOOP);
AudioBackend(std::function<void()> on_updated_cb, private_constructor_tag tag); AudioBackend(std::function<void()> on_updated_cb, private_constructor_tag tag);
~AudioBackend(); ~AudioBackend();

View File

@ -0,0 +1,10 @@
#pragma once
#include "AModule.hpp"
namespace waybar::util {
const static auto NOOP = []() {};
enum class ChangeType : char { Increase, Decrease };
} // namespace waybar::util

View File

@ -0,0 +1,91 @@
#pragma once
#include <libudev.h>
#include <spdlog/spdlog.h>
#include <chrono>
#include <mutex>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
#include "giomm/dbusproxy.h"
#include "util/backend_common.hpp"
#include "util/sleeper_thread.hpp"
#define GET_BEST_DEVICE(varname, backend, preferred_device) \
decltype((backend).devices_) __devices; \
{ \
std::scoped_lock<std::mutex> lock((backend).udev_thread_mutex_); \
__devices = (backend).devices_; \
} \
auto varname = (backend).best_device(__devices.cbegin(), __devices.cend(), preferred_device);
namespace waybar::util {
class BacklightDevice {
public:
BacklightDevice() = default;
BacklightDevice(std::string name, int actual, int max, bool powered);
std::string name() const;
int get_actual() const;
void set_actual(int actual);
int get_max() const;
void set_max(int max);
bool get_powered() const;
void set_powered(bool powered);
friend inline bool operator==(const BacklightDevice &lhs, const BacklightDevice &rhs) {
return lhs.name_ == rhs.name_ && lhs.actual_ == rhs.actual_ && lhs.max_ == rhs.max_;
}
private:
std::string name_;
int actual_ = 1;
int max_ = 1;
bool powered_ = true;
};
class BacklightBackend {
public:
BacklightBackend(std::chrono::milliseconds interval, std::function<void()> on_updated_cb = NOOP);
// const inline BacklightDevice *get_best_device(std::string_view preferred_device);
const BacklightDevice *get_previous_best_device();
void set_previous_best_device(const BacklightDevice *device);
void set_brightness(std::string preferred_device, int brightness);
void set_brightness(std::string preferred_device, ChangeType change_type, double step);
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);
bool is_login_proxy_initialized() const { return static_cast<bool>(login_proxy_); }
template <class ForwardIt>
static const BacklightDevice *best_device(ForwardIt first, ForwardIt last, std::string_view);
std::vector<BacklightDevice> devices_;
std::mutex udev_thread_mutex_;
private:
void set_brightness_internal(std::string device_name, int brightness, int max_brightness);
std::function<void()> on_updated_cb_;
std::chrono::milliseconds polling_interval_;
std::optional<BacklightDevice> previous_best_;
// thread must destruct before shared data
util::SleeperThread udev_thread_;
Glib::RefPtr<Gio::DBus::Proxy> login_proxy_;
static constexpr int EPOLL_MAX_EVENTS = 16;
};
} // namespace waybar::util

View File

@ -301,6 +301,7 @@ endif
if libudev.found() and (is_linux or libepoll.found()) if libudev.found() and (is_linux or libepoll.found())
add_project_arguments('-DHAVE_LIBUDEV', language: 'cpp') add_project_arguments('-DHAVE_LIBUDEV', language: 'cpp')
src_files += 'src/modules/backlight.cpp' src_files += 'src/modules/backlight.cpp'
src_files += 'src/util/backlight_backend.cpp'
endif endif
if libevdev.found() and (is_linux or libepoll.found()) and libinput.found() and (is_linux or libinotify.found()) if libevdev.found() and (is_linux or libepoll.found()) and libinput.found() and (is_linux or libinotify.found())

View File

@ -2,6 +2,7 @@
#include <fmt/format.h> #include <fmt/format.h>
#include <libudev.h> #include <libudev.h>
#include <spdlog/spdlog.h>
#include <sys/epoll.h> #include <sys/epoll.h>
#include <unistd.h> #include <unistd.h>
@ -9,179 +10,26 @@
#include <chrono> #include <chrono>
#include <memory> #include <memory>
namespace { #include "util/backend_common.hpp"
class FileDescriptor { #include "util/backlight_backend.hpp"
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(fmt::runtime(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(fmt::runtime(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(fmt::runtime(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,
bool powered)
: name_(std::move(name)), actual_(actual), max_(max), powered_(powered) {}
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; }
bool waybar::modules::Backlight::BacklightDev::get_powered() const { return powered_; }
void waybar::modules::Backlight::BacklightDev::set_powered(bool powered) { powered_ = powered; }
waybar::modules::Backlight::Backlight(const std::string &id, const Json::Value &config) waybar::modules::Backlight::Backlight(const std::string &id, const Json::Value &config)
: ALabel(config, "backlight", id, "{percent}%", 2), : ALabel(config, "backlight", id, "{percent}%", 2),
preferred_device_(config["device"].isString() ? config["device"].asString() : "") { preferred_device_(config["device"].isString() ? config["device"].asString() : ""),
// Get initial state backend(interval_, [this] { dp.emit(); }) {
{
std::unique_ptr<udev, UdevDeleter> udev_check{udev_new()};
check_nn(udev_check.get(), "Udev check new failed");
enumerate_devices(devices_.begin(), devices_.end(), std::back_inserter(devices_),
udev_check.get());
if (devices_.empty()) {
throw std::runtime_error("No backlight found");
}
dp.emit(); dp.emit();
}
// Set up scroll handler // Set up scroll handler
event_box_.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK); event_box_.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
event_box_.signal_scroll_event().connect(sigc::mem_fun(*this, &Backlight::handleScroll)); event_box_.signal_scroll_event().connect(sigc::mem_fun(*this, &Backlight::handleScroll));
// Connect to the login interface
login_proxy_ = Gio::DBus::Proxy::create_for_bus_sync(
Gio::DBus::BusType::BUS_TYPE_SYSTEM, "org.freedesktop.login1",
"/org/freedesktop/login1/session/self", "org.freedesktop.login1.Session");
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(EPOLL_CLOEXEC)};
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];
while (udev_thread_.isRunning()) {
const int event_count = epoll_wait(epoll_fd.get(), events, EPOLL_MAX_EVENTS,
std::chrono::milliseconds{interval_}.count());
if (!udev_thread_.isRunning()) {
break;
}
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 { auto waybar::modules::Backlight::update() -> void {
decltype(devices_) devices; GET_BEST_DEVICE(best, backend, preferred_device_);
{
std::scoped_lock<std::mutex> lock(udev_thread_mutex_);
devices = devices_;
}
const auto best = best_device(devices.cbegin(), devices.cend(), preferred_device_); const auto previous_best_device = backend.get_previous_best_device();
if (best != nullptr) { if (best != nullptr) {
if (previous_best_.has_value() && previous_best_.value() == *best && if (previous_best_device != nullptr && *previous_best_device == *best &&
!previous_format_.empty() && previous_format_ == format_) { !previous_format_.empty() && previous_format_ == format_) {
return; return;
} }
@ -211,84 +59,16 @@ auto waybar::modules::Backlight::update() -> void {
event_box_.hide(); event_box_.hide();
} }
} else { } else {
if (!previous_best_.has_value()) { if (previous_best_device == nullptr) {
return; return;
} }
label_.set_markup(""); label_.set_markup("");
} }
previous_best_ = best == nullptr ? std::nullopt : std::optional{*best}; backend.set_previous_best_device(best);
previous_format_ = format_; previous_format_ = format_;
// Call parent update
ALabel::update(); ALabel::update();
} }
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_brightness_attr =
strncmp(name, "amdgpu_bl", 9) == 0 || strcmp(name, "apple-panel-bl") == 0
? "brightness"
: "actual_brightness";
const char *actual = udev_device_get_sysattr_value(dev, actual_brightness_attr);
const char *max = udev_device_get_sysattr_value(dev, "max_brightness");
const char *power = udev_device_get_sysattr_value(dev, "bl_power");
auto found =
std::find_if(first, last, [name](const auto &device) { return device.name() == name; });
if (found != last) {
if (actual != nullptr) {
found->set_actual(std::stoi(actual));
}
if (max != nullptr) {
found->set_max(std::stoi(max));
}
if (power != nullptr) {
found->set_powered(std::stoi(power) == 0);
}
} else {
const int actual_int = actual == nullptr ? 0 : std::stoi(actual);
const int max_int = max == nullptr ? 0 : std::stoi(max);
const bool power_bool = power == nullptr ? true : std::stoi(power) == 0;
*inserter = BacklightDev{name, actual_int, max_int, power_bool};
++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());
}
}
bool waybar::modules::Backlight::handleScroll(GdkEventScroll *e) { bool waybar::modules::Backlight::handleScroll(GdkEventScroll *e) {
// Check if the user has set a custom command for scrolling // Check if the user has set a custom command for scrolling
if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString()) { if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString()) {
@ -296,14 +76,31 @@ bool waybar::modules::Backlight::handleScroll(GdkEventScroll *e) {
} }
// Fail fast if the proxy could not be initialized // Fail fast if the proxy could not be initialized
if (!login_proxy_) { if (!backend.is_login_proxy_initialized()) {
return true; return true;
} }
// Check scroll direction // Check scroll direction
auto dir = AModule::getScrollDir(e); auto dir = AModule::getScrollDir(e);
if (dir == SCROLL_DIR::NONE) {
util::ChangeType ct;
switch (dir) {
case SCROLL_DIR::UP:
[[fallthrough]];
case SCROLL_DIR::RIGHT:
ct = util::ChangeType::Increase;
break;
case SCROLL_DIR::DOWN:
[[fallthrough]];
case SCROLL_DIR::LEFT:
ct = util::ChangeType::Decrease;
break;
case SCROLL_DIR::NONE:
return true; return true;
break;
} }
// Get scroll step // Get scroll step
@ -313,38 +110,7 @@ bool waybar::modules::Backlight::handleScroll(GdkEventScroll *e) {
step = config_["scroll-step"].asDouble(); step = config_["scroll-step"].asDouble();
} }
// Get the best device backend.set_brightness(preferred_device_, ct, step);
decltype(devices_) devices;
{
std::scoped_lock<std::mutex> lock(udev_thread_mutex_);
devices = devices_;
}
const auto best = best_device(devices.cbegin(), devices.cend(), preferred_device_);
if (best == nullptr) {
return true;
}
// Compute the absolute step
const auto abs_step = static_cast<int>(round(step * best->get_max() / 100.0f));
// Compute the new value
int new_value = best->get_actual();
if (dir == SCROLL_DIR::UP) {
new_value += abs_step;
} else if (dir == SCROLL_DIR::DOWN) {
new_value -= abs_step;
}
// Clamp the value
new_value = std::clamp(new_value, 0, best->get_max());
// Set the new value
auto call_args = Glib::VariantContainerBase(
g_variant_new("(ssu)", "backlight", std::string(best->name()).c_str(), new_value));
login_proxy_->call_sync("SetBrightness", call_args);
return true; return true;
} }

View File

@ -11,8 +11,6 @@
namespace waybar::util { namespace waybar::util {
void noop() {}
AudioBackend::AudioBackend(std::function<void()> on_updated_cb, private_constructor_tag tag) AudioBackend::AudioBackend(std::function<void()> on_updated_cb, private_constructor_tag tag)
: mainloop_(nullptr), : mainloop_(nullptr),
mainloop_api_(nullptr), mainloop_api_(nullptr),
@ -208,8 +206,7 @@ void AudioBackend::changeVolume(uint16_t volume, uint16_t min_volume, uint16_t m
double volume_tick = static_cast<double>(PA_VOLUME_NORM) / 100; double volume_tick = static_cast<double>(PA_VOLUME_NORM) / 100;
pa_cvolume pa_volume = pa_volume_; pa_cvolume pa_volume = pa_volume_;
volume = std::min(volume, max_volume); volume = std::clamp(volume, min_volume, max_volume);
volume = std::max(volume, min_volume);
pa_cvolume_set(&pa_volume, pa_volume_.channels, volume * volume_tick); pa_cvolume_set(&pa_volume, pa_volume_.channels, volume * volume_tick);
pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, volumeModifyCb, this); pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, volumeModifyCb, this);

View File

@ -0,0 +1,276 @@
#include "util/backlight_backend.hpp"
#include <fmt/core.h>
#include <spdlog/spdlog.h>
#include <sys/epoll.h>
#include <optional>
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(fmt::runtime(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(fmt::runtime(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(fmt::runtime(message), rc));
}
}
void check_nn(const void *ptr, const char *message = "ptr was null") {
if (ptr == nullptr) {
throw std::runtime_error(message);
}
}
} // namespace
namespace waybar::util {
BacklightDevice::BacklightDevice(std::string name, int actual, int max, bool powered)
: name_(name), actual_(actual), max_(max), powered_(powered) {}
std::string BacklightDevice::name() const { return name_; }
int BacklightDevice::get_actual() const { return actual_; }
void BacklightDevice::set_actual(int actual) { actual_ = actual; }
int BacklightDevice::get_max() const { return max_; }
void BacklightDevice::set_max(int max) { max_ = max; }
bool BacklightDevice::get_powered() const { return powered_; }
void BacklightDevice::set_powered(bool powered) { powered_ = powered; }
BacklightBackend::BacklightBackend(std::chrono::milliseconds interval,
std::function<void()> on_updated_cb)
: on_updated_cb_(on_updated_cb), polling_interval_(interval), previous_best_({}) {
std::unique_ptr<udev, UdevDeleter> udev_check{udev_new()};
check_nn(udev_check.get(), "Udev check new failed");
enumerate_devices(devices_.begin(), devices_.end(), std::back_inserter(devices_),
udev_check.get());
if (devices_.empty()) {
throw std::runtime_error("No backlight found");
}
// Connect to the login interface
login_proxy_ = Gio::DBus::Proxy::create_for_bus_sync(
Gio::DBus::BusType::BUS_TYPE_SYSTEM, "org.freedesktop.login1",
"/org/freedesktop/login1/session/self", "org.freedesktop.login1.Session");
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(EPOLL_CLOEXEC)};
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];
while (udev_thread_.isRunning()) {
const int event_count =
epoll_wait(epoll_fd.get(), events, EPOLL_MAX_EVENTS, this->polling_interval_.count());
if (!udev_thread_.isRunning()) {
break;
}
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;
}
this->on_updated_cb_();
}
};
}
template <class ForwardIt>
const BacklightDevice *BacklightBackend::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);
}
const BacklightDevice *BacklightBackend::get_previous_best_device() {
return previous_best_.has_value() ? &(*previous_best_) : nullptr;
}
void BacklightBackend::set_previous_best_device(const BacklightDevice *device) {
if (device == nullptr) {
previous_best_ = std::nullopt;
} else {
previous_best_ = std::optional{*device};
}
}
void BacklightBackend::set_brightness(std::string preferred_device, int brightness) {
GET_BEST_DEVICE(best, (*this), preferred_device);
if (best != nullptr) {
set_brightness_internal(best->name(), brightness, best->get_max());
}
}
void BacklightBackend::set_brightness(std::string preferred_device, ChangeType change_type,
double step) {
GET_BEST_DEVICE(best, (*this), preferred_device);
if (best != nullptr) {
const auto max = best->get_max();
const auto abs_step = static_cast<int>(round(step * max / 100.0f));
const int new_brightness = change_type == ChangeType::Increase ? best->get_actual() + abs_step
: best->get_actual() - abs_step;
set_brightness_internal(best->name(), new_brightness, max);
}
}
void BacklightBackend::set_brightness_internal(std::string device_name, int brightness,
int max_brightness) {
brightness = std::clamp(brightness, 0, max_brightness);
auto call_args = Glib::VariantContainerBase(
g_variant_new("(ssu)", "backlight", device_name.c_str(), brightness));
login_proxy_->call_sync("SetBrightness", call_args);
}
template <class ForwardIt, class Inserter>
void BacklightBackend::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_brightness_attr =
strncmp(name, "amdgpu_bl", 9) == 0 || strcmp(name, "apple-panel-bl") == 0
? "brightness"
: "actual_brightness";
const char *actual = udev_device_get_sysattr_value(dev, actual_brightness_attr);
const char *max = udev_device_get_sysattr_value(dev, "max_brightness");
const char *power = udev_device_get_sysattr_value(dev, "bl_power");
auto found =
std::find_if(first, last, [name](const auto &device) { return device.name() == name; });
if (found != last) {
if (actual != nullptr) {
found->set_actual(std::stoi(actual));
}
if (max != nullptr) {
found->set_max(std::stoi(max));
}
if (power != nullptr) {
found->set_powered(std::stoi(power) == 0);
}
} else {
const int actual_int = actual == nullptr ? 0 : std::stoi(actual);
const int max_int = max == nullptr ? 0 : std::stoi(max);
const bool power_bool = power == nullptr ? true : std::stoi(power) == 0;
*inserter = BacklightDevice{name, actual_int, max_int, power_bool};
++inserter;
}
}
template <class ForwardIt, class Inserter>
void BacklightBackend::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());
}
}
} // namespace waybar::util