refactor: move backlight backend out of backlight module
This commit is contained in:
		
							parent
							
								
									442a4b0da0
								
							
						
					
					
						commit
						c3779dd16e
					
				|  | @ -1,14 +1,14 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include <memory> | ||||
| #include <optional> | ||||
| #include <string> | ||||
| #include <string_view> | ||||
| #include <vector> | ||||
| 
 | ||||
| #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 <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; | ||||
| 
 | ||||
|   const std::string preferred_device_; | ||||
|   static constexpr int EPOLL_MAX_EVENTS = 16; | ||||
| 
 | ||||
|   std::optional<BacklightDev> previous_best_; | ||||
|   std::string previous_format_; | ||||
| 
 | ||||
|   std::mutex udev_thread_mutex_; | ||||
|   std::vector<BacklightDev> devices_; | ||||
|   // thread must destruct before shared data
 | ||||
|   util::SleeperThread udev_thread_; | ||||
| 
 | ||||
|   Glib::RefPtr<Gio::DBus::Proxy> login_proxy_; | ||||
|   util::BacklightBackend backend; | ||||
| }; | ||||
| }  // namespace waybar::modules
 | ||||
|  |  | |||
|  | @ -10,12 +10,10 @@ | |||
| #include <memory> | ||||
| #include <string> | ||||
| 
 | ||||
| #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<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.
 | ||||
|    * 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<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(); | ||||
|  |  | |||
|  | @ -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
 | ||||
|  | @ -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
 | ||||
|  | @ -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()) | ||||
|  |  | |||
|  | @ -2,6 +2,7 @@ | |||
| 
 | ||||
| #include <fmt/format.h> | ||||
| #include <libudev.h> | ||||
| #include <spdlog/spdlog.h> | ||||
| #include <sys/epoll.h> | ||||
| #include <unistd.h> | ||||
| 
 | ||||
|  | @ -9,179 +10,26 @@ | |||
| #include <chrono> | ||||
| #include <memory> | ||||
| 
 | ||||
| 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, 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(); | ||||
|   } | ||||
|       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, 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 { | ||||
|   decltype(devices_) devices; | ||||
|   { | ||||
|     std::scoped_lock<std::mutex> 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 <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) { | ||||
|   // 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<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); | ||||
|   backend.set_brightness(preferred_device_, ct, step); | ||||
| 
 | ||||
|   return true; | ||||
| } | ||||
|  |  | |||
|  | @ -11,8 +11,6 @@ | |||
| 
 | ||||
| namespace waybar::util { | ||||
| 
 | ||||
| void noop() {} | ||||
| 
 | ||||
| AudioBackend::AudioBackend(std::function<void()> 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<double>(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); | ||||
|  |  | |||
|  | @ -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
 | ||||
		Loading…
	
		Reference in New Issue