diff --git a/include/modules/backlight.hpp b/include/modules/backlight.hpp index ade4bc78..110cd434 100644 --- a/include/modules/backlight.hpp +++ b/include/modules/backlight.hpp @@ -1,14 +1,14 @@ #pragma once +#include #include #include #include #include #include "ALabel.hpp" -#include "giomm/dbusproxy.h" +#include "util/backlight_backend.hpp" #include "util/json.hpp" -#include "util/sleeper_thread.hpp" struct udev; struct udev_device; @@ -16,54 +16,17 @@ struct udev_device; namespace waybar::modules { 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: Backlight(const std::string &, const Json::Value &); - virtual ~Backlight(); + virtual ~Backlight() = default; auto update() -> void override; - private: - template - static const BacklightDev *best_device(ForwardIt first, ForwardIt last, std::string_view); - template - static void upsert_device(ForwardIt first, ForwardIt last, Inserter inserter, udev_device *dev); - template - static void enumerate_devices(ForwardIt first, ForwardIt last, Inserter inserter, udev *udev); - bool handleScroll(GdkEventScroll *e) override; const std::string preferred_device_; - static constexpr int EPOLL_MAX_EVENTS = 16; - std::optional previous_best_; std::string previous_format_; - std::mutex udev_thread_mutex_; - std::vector devices_; - // thread must destruct before shared data - util::SleeperThread udev_thread_; - - Glib::RefPtr login_proxy_; + util::BacklightBackend backend; }; } // namespace waybar::modules diff --git a/include/util/audio_backend.hpp b/include/util/audio_backend.hpp index 1a882cc5..9d043612 100644 --- a/include/util/audio_backend.hpp +++ b/include/util/audio_backend.hpp @@ -10,12 +10,10 @@ #include #include +#include "util/backend_common.hpp" + namespace waybar::util { -enum class ChangeType : char { Increase, Decrease }; - -void noop(); - class AudioBackend { private: static void subscribeCb(pa_context*, pa_subscription_event_type_t, uint32_t, void*); @@ -50,7 +48,7 @@ class AudioBackend { std::vector ignored_sinks_; - std::function on_updated_cb_ = noop; + std::function on_updated_cb_ = NOOP; /* Hack to keep constructor inaccessible but still public. * This is required to be able to use std::make_shared. @@ -61,7 +59,7 @@ class AudioBackend { struct private_constructor_tag {}; public: - static std::shared_ptr getInstance(std::function on_updated_cb = noop); + static std::shared_ptr getInstance(std::function on_updated_cb = NOOP); AudioBackend(std::function on_updated_cb, private_constructor_tag tag); ~AudioBackend(); diff --git a/include/util/backend_common.hpp b/include/util/backend_common.hpp new file mode 100644 index 00000000..dda6ac57 --- /dev/null +++ b/include/util/backend_common.hpp @@ -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 \ No newline at end of file diff --git a/include/util/backlight_backend.hpp b/include/util/backlight_backend.hpp new file mode 100644 index 00000000..1f7bddc8 --- /dev/null +++ b/include/util/backlight_backend.hpp @@ -0,0 +1,91 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include + +#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 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 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 + static void upsert_device(ForwardIt first, ForwardIt last, Inserter inserter, udev_device *dev); + + template + static void enumerate_devices(ForwardIt first, ForwardIt last, Inserter inserter, udev *udev); + + bool is_login_proxy_initialized() const { return static_cast(login_proxy_); } + + template + static const BacklightDevice *best_device(ForwardIt first, ForwardIt last, std::string_view); + + std::vector devices_; + std::mutex udev_thread_mutex_; + + private: + void set_brightness_internal(std::string device_name, int brightness, int max_brightness); + + std::function on_updated_cb_; + std::chrono::milliseconds polling_interval_; + + std::optional previous_best_; + // thread must destruct before shared data + util::SleeperThread udev_thread_; + + Glib::RefPtr login_proxy_; + + static constexpr int EPOLL_MAX_EVENTS = 16; +}; + +} // namespace waybar::util \ No newline at end of file diff --git a/meson.build b/meson.build index d2d3e17a..3e8951c5 100644 --- a/meson.build +++ b/meson.build @@ -301,6 +301,7 @@ endif if libudev.found() and (is_linux or libepoll.found()) add_project_arguments('-DHAVE_LIBUDEV', language: 'cpp') src_files += 'src/modules/backlight.cpp' + src_files += 'src/util/backlight_backend.cpp' endif if libevdev.found() and (is_linux or libepoll.found()) and libinput.found() and (is_linux or libinotify.found()) diff --git a/src/modules/backlight.cpp b/src/modules/backlight.cpp index b3ca85fc..759bbd13 100644 --- a/src/modules/backlight.cpp +++ b/src/modules/backlight.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -9,179 +10,26 @@ #include #include -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 - -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; } +#include "util/backend_common.hpp" +#include "util/backlight_backend.hpp" waybar::modules::Backlight::Backlight(const std::string &id, const Json::Value &config) : ALabel(config, "backlight", id, "{percent}%", 2), - preferred_device_(config["device"].isString() ? config["device"].asString() : "") { - // Get initial state - { - std::unique_ptr 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(); - } + preferred_device_(config["device"].isString() ? config["device"].asString() : ""), + backend(interval_, [this] { dp.emit(); }) { + dp.emit(); // Set up scroll handler event_box_.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK); 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{udev_new()}; - check_nn(udev.get(), "Udev new failed"); - - std::unique_ptr 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 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 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 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 lock(udev_thread_mutex_); - devices = devices_; - } + GET_BEST_DEVICE(best, backend, preferred_device_); - 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 (previous_best_.has_value() && previous_best_.value() == *best && + if (previous_best_device != nullptr && *previous_best_device == *best && !previous_format_.empty() && previous_format_ == format_) { return; } @@ -211,84 +59,16 @@ auto waybar::modules::Backlight::update() -> void { event_box_.hide(); } } else { - if (!previous_best_.has_value()) { + if (previous_best_device == nullptr) { return; } label_.set_markup(""); } - previous_best_ = best == nullptr ? std::nullopt : std::optional{*best}; + backend.set_previous_best_device(best); previous_format_ = format_; - // Call parent update ALabel::update(); } -template -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 -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 -void waybar::modules::Backlight::enumerate_devices(ForwardIt first, ForwardIt last, - Inserter inserter, udev *udev) { - std::unique_ptr 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 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) { // Check if the user has set a custom command for scrolling 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 - if (!login_proxy_) { + if (!backend.is_login_proxy_initialized()) { return true; } // Check scroll direction auto dir = AModule::getScrollDir(e); - if (dir == SCROLL_DIR::NONE) { - return true; + + 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; + break; } // Get scroll step @@ -313,38 +110,7 @@ bool waybar::modules::Backlight::handleScroll(GdkEventScroll *e) { step = config_["scroll-step"].asDouble(); } - // Get the best device - decltype(devices_) devices; - { - std::scoped_lock 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(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); + backend.set_brightness(preferred_device_, ct, step); return true; } diff --git a/src/util/audio_backend.cpp b/src/util/audio_backend.cpp index 4600d80c..eb2cfaff 100644 --- a/src/util/audio_backend.cpp +++ b/src/util/audio_backend.cpp @@ -11,8 +11,6 @@ namespace waybar::util { -void noop() {} - AudioBackend::AudioBackend(std::function on_updated_cb, private_constructor_tag tag) : mainloop_(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(PA_VOLUME_NORM) / 100; pa_cvolume pa_volume = pa_volume_; - volume = std::min(volume, max_volume); - volume = std::max(volume, min_volume); + volume = std::clamp(volume, min_volume, max_volume); 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); diff --git a/src/util/backlight_backend.cpp b/src/util/backlight_backend.cpp new file mode 100644 index 00000000..7123ee3a --- /dev/null +++ b/src/util/backlight_backend.cpp @@ -0,0 +1,276 @@ +#include "util/backlight_backend.hpp" + +#include +#include +#include + +#include + +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 on_updated_cb) + : on_updated_cb_(on_updated_cb), polling_interval_(interval), previous_best_({}) { + std::unique_ptr 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{udev_new()}; + check_nn(udev.get(), "Udev new failed"); + + std::unique_ptr 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 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 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 lock(udev_thread_mutex_); + devices_ = devices; + } + this->on_updated_cb_(); + } + }; +} + +template +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(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 +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 +void BacklightBackend::enumerate_devices(ForwardIt first, ForwardIt last, Inserter inserter, + udev *udev) { + std::unique_ptr 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 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 \ No newline at end of file