From 875306804c9786f7af1e30fcd450bdafff39534a Mon Sep 17 00:00:00 2001 From: Ian Hattendorf Date: Sun, 10 Feb 2019 02:35:19 -0700 Subject: [PATCH 1/2] Add backlight module Monitor the backlight level via udev. Poll every `interval` as well, in case backlight udev events aren't supported. --- include/factory.hpp | 1 + include/modules/backlight.hpp | 65 ++++++++ meson.build | 5 +- resources/config | 7 +- resources/style.css | 6 +- src/factory.cpp | 3 + src/modules/backlight.cpp | 285 ++++++++++++++++++++++++++++++++++ 7 files changed, 369 insertions(+), 3 deletions(-) create mode 100644 include/modules/backlight.hpp create mode 100644 src/modules/backlight.cpp diff --git a/include/factory.hpp b/include/factory.hpp index 5f659957..8b4f48d7 100644 --- a/include/factory.hpp +++ b/include/factory.hpp @@ -19,6 +19,7 @@ #ifdef HAVE_LIBPULSE #include "modules/pulseaudio.hpp" #endif +#include "modules/backlight.hpp" #include "modules/custom.hpp" namespace waybar { diff --git a/include/modules/backlight.hpp b/include/modules/backlight.hpp new file mode 100644 index 00000000..2217384a --- /dev/null +++ b/include/modules/backlight.hpp @@ -0,0 +1,65 @@ +#pragma once + +#include +#include +#include +#include + +#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 + 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); + + 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 devices_; + + std::optional previous_best_; +}; +} // namespace waybar::modules diff --git a/meson.build b/meson.build index b3c4d6ab..685cea57 100644 --- a/meson.build +++ b/meson.build @@ -55,6 +55,7 @@ sigcpp = dependency('sigc++-2.0') libnl = dependency('libnl-3.0', required: get_option('libnl')) libnlgen = dependency('libnl-genl-3.0', required: get_option('libnl')) libpulse = dependency('libpulse', required: get_option('pulseaudio')) +libudev = dependency('libudev') src_files = files( 'src/factory.cpp', @@ -64,6 +65,7 @@ src_files = files( 'src/modules/clock.cpp', 'src/modules/custom.cpp', 'src/modules/cpu.cpp', + 'src/modules/backlight.cpp', 'src/main.cpp', 'src/bar.cpp', 'src/client.cpp' @@ -119,7 +121,8 @@ executable( giounix, libnl, libnlgen, - libpulse + libpulse, + libudev ], include_directories: [include_directories('include')], install: true, diff --git a/resources/config b/resources/config index 180a192c..071a43de 100644 --- a/resources/config +++ b/resources/config @@ -6,7 +6,7 @@ // Choose the order of the modules "modules-left": ["sway/workspaces", "sway/mode", "custom/spotify"], "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 // "sway/workspaces": { // "disable-scroll": true, @@ -39,6 +39,11 @@ "memory": { "format": "{}% " }, + "backlight": { + // "device": "acpi_video1", + "format": "{percent}% {icon}", + "format-icons": ["", ""] + }, "battery": { "states": { // "good": 95, diff --git a/resources/style.css b/resources/style.css index 5f5af87d..86b03b15 100644 --- a/resources/style.css +++ b/resources/style.css @@ -29,7 +29,7 @@ window#waybar { 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; margin: 0 5px; } @@ -74,6 +74,10 @@ window#waybar { background: #9b59b6; } +#backlight { + background: #90b1b1; +} + #network { background: #2980b9; } diff --git a/src/factory.cpp b/src/factory.cpp index a9fdf0c5..1f7b41af 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -48,6 +48,9 @@ waybar::IModule* waybar::Factory::makeModule(const std::string &name) const return new waybar::modules::Pulseaudio(id, config_[name]); } #endif + if (ref == "backlight") { + return new waybar::modules::Backlight(id, config_[name]); + } if (ref.compare(0, 7, "custom/") == 0 && ref.size() > 7) { return new waybar::modules::Custom(ref.substr(7), config_[name]); } diff --git a/src/modules/backlight.cpp b/src/modules/backlight.cpp new file mode 100644 index 00000000..5a94e5b4 --- /dev/null +++ b/src/modules/backlight.cpp @@ -0,0 +1,285 @@ +#include "modules/backlight.hpp" + +#include +#include +#include + +#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(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{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(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 lock(udev_thread_mutex_); + devices = devices_; + } + enumerate_devices(devices.begin(), devices.end(), + std::back_inserter(devices), udev.get()); + { + std::scoped_lock 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 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_; + } + + 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 +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 = 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 +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()); + } +} From e67347f6ad9ac7f1b1c601427e75266bb7efda65 Mon Sep 17 00:00:00 2001 From: Ian Hattendorf Date: Mon, 18 Feb 2019 21:11:18 -0700 Subject: [PATCH 2/2] Gate backlight module behind libudev availability --- include/factory.hpp | 4 +++- meson.build | 8 ++++++-- meson_options.txt | 1 + src/factory.cpp | 8 +++++--- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/include/factory.hpp b/include/factory.hpp index 8b4f48d7..f496983d 100644 --- a/include/factory.hpp +++ b/include/factory.hpp @@ -16,10 +16,12 @@ #ifdef HAVE_LIBNL #include "modules/network.hpp" #endif +#ifdef HAVE_LIBUDEV +#include "modules/backlight.hpp" +#endif #ifdef HAVE_LIBPULSE #include "modules/pulseaudio.hpp" #endif -#include "modules/backlight.hpp" #include "modules/custom.hpp" namespace waybar { diff --git a/meson.build b/meson.build index 685cea57..6c9eeab6 100644 --- a/meson.build +++ b/meson.build @@ -55,7 +55,7 @@ sigcpp = dependency('sigc++-2.0') libnl = dependency('libnl-3.0', required: get_option('libnl')) libnlgen = dependency('libnl-genl-3.0', required: get_option('libnl')) libpulse = dependency('libpulse', required: get_option('pulseaudio')) -libudev = dependency('libudev') +libudev = dependency('libudev', required: get_option('libudev')) src_files = files( 'src/factory.cpp', @@ -65,7 +65,6 @@ src_files = files( 'src/modules/clock.cpp', 'src/modules/custom.cpp', 'src/modules/cpu.cpp', - 'src/modules/backlight.cpp', 'src/main.cpp', 'src/bar.cpp', 'src/client.cpp' @@ -101,6 +100,11 @@ if dbusmenu_gtk.found() ) endif +if libudev.found() + add_project_arguments('-DHAVE_LIBUDEV', language: 'cpp') + src_files += 'src/modules/backlight.cpp' +endif + subdir('protocol') executable( diff --git a/meson_options.txt b/meson_options.txt index 67fa5472..4c2f03a0 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -1,4 +1,5 @@ option('libnl', type: 'feature', value: 'auto', description: 'Enable libnl support for network related features') +option('libudev', type: 'feature', value: 'auto', description: 'Enable libudev support for udev related features') option('pulseaudio', type: 'feature', value: 'auto', description: 'Enable support for pulseaudio') option('dbusmenu-gtk', type: 'feature', value: 'auto', description: 'Enable support for tray') option('out', type: 'string', value : '/', description: 'output prefix directory') diff --git a/src/factory.cpp b/src/factory.cpp index 1f7b41af..1fe0867b 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -43,14 +43,16 @@ waybar::IModule* waybar::Factory::makeModule(const std::string &name) const return new waybar::modules::Network(id, config_[name]); } #endif + #ifdef HAVE_LIBUDEV + if (ref == "backlight") { + return new waybar::modules::Backlight(id, config_[name]); + } + #endif #ifdef HAVE_LIBPULSE if (ref == "pulseaudio") { return new waybar::modules::Pulseaudio(id, config_[name]); } #endif - if (ref == "backlight") { - return new waybar::modules::Backlight(id, config_[name]); - } if (ref.compare(0, 7, "custom/") == 0 && ref.size() > 7) { return new waybar::modules::Custom(ref.substr(7), config_[name]); }