From 69279c9ff850775a156d6cc803b2c81abbd811f0 Mon Sep 17 00:00:00 2001 From: Hue Date: Sun, 21 Dec 2025 02:13:29 +0100 Subject: [PATCH 01/48] new light widget --- src/panel/widgets/light/ddcutil.cpp | 0 src/panel/widgets/light/filesystem.cpp | 6 +++ src/panel/widgets/light/light.cpp | 57 ++++++++++++++++++++++++++ src/panel/widgets/light/light.hpp | 42 +++++++++++++++++++ 4 files changed, 105 insertions(+) create mode 100644 src/panel/widgets/light/ddcutil.cpp create mode 100644 src/panel/widgets/light/filesystem.cpp create mode 100644 src/panel/widgets/light/light.cpp create mode 100644 src/panel/widgets/light/light.hpp diff --git a/src/panel/widgets/light/ddcutil.cpp b/src/panel/widgets/light/ddcutil.cpp new file mode 100644 index 00000000..e69de29b diff --git a/src/panel/widgets/light/filesystem.cpp b/src/panel/widgets/light/filesystem.cpp new file mode 100644 index 00000000..137781bb --- /dev/null +++ b/src/panel/widgets/light/filesystem.cpp @@ -0,0 +1,6 @@ +#include "light.hpp" + +class WfFsLightControl : public WfLightControl +{ + +} diff --git a/src/panel/widgets/light/light.cpp b/src/panel/widgets/light/light.cpp new file mode 100644 index 00000000..8773f2b1 --- /dev/null +++ b/src/panel/widgets/light/light.cpp @@ -0,0 +1,57 @@ +#include "light.hpp" +#include "wf-popover.hpp" + +WfLightControl::WfLightControl(){ + // preparation + scale.set_range(0.0, 1.0); + scale.set_target_value(0.5); + scale.set_size_request(300); + + scale.set_user_changed_callback([this](){ + this->set_brightness(scale.get_target_value()); + }); + + const std::string name; + + label.set_text(get_name()); + + // layout + set_orientation(Gtk::Orientation::VERTICAL); + append(label); + append(scale); +} + +void WayfireLight::init(Gtk::Box *container){ + button = std::make_unique("panel"); + button->get_style_context()->add_class("flat"); + button->set_child(icon); + button->show(); + popover = button->get_popover(); + popover->set_autohide(false); + + // scroll to change brighten and dim all monitors + auto scroll_gesture = Gtk::EventControllerScroll::create(); + scroll_gesture->signal_scroll().connect([=] (double dx, double dy) + { + double change = 0; + if (scroll_gesture->get_unit() == Gdk::ScrollUnit::WHEEL) + { + // +- number of clicks. + change = (dy * scroll_sensitivity) / 10; + } else + { + // Number of pixels expected to have scrolled. usually in 100s + change = (dy * scroll_sensitivity) / 100; + } + // for (auto control: controls){ + // control->set_brightness(control->get_brightness() + change); + // } + }, true); + scroll_gesture->set_flags(Gtk::EventControllerScroll::Flags::VERTICAL); + button->add_controller(scroll_gesture); + + popover->set_child(box); + popover->get_style_context()->add_class("light-popover"); + + container->append(*button); +} diff --git a/src/panel/widgets/light/light.hpp b/src/panel/widgets/light/light.hpp new file mode 100644 index 00000000..585bbe08 --- /dev/null +++ b/src/panel/widgets/light/light.hpp @@ -0,0 +1,42 @@ +/* + abstract light control class, which are handled by a widget + other .cpp files in this directory are backends for different ways of handling them +*/ + +#include + +#include "widget.hpp" +#include "animated-scale.hpp" + +class WfLightControl : public Gtk::Box +{ + protected: + WayfireAnimatedScale scale; + Gtk::Label label; + + virtual std::string get_name(); + + public: + WfLightControl(); + + // a double from 0 to 1 for min to max + virtual void set_brightness(double brightness); + virtual double get_brightness(); +}; + +class WayfireLight : public WayfireWidget { + private: + void init(Gtk::Box *container) override; + Gtk::Image icon; + + std::unique_ptr button; + Gtk::Popover *popover; + Gtk::Box box; + + std::vector> controls; + + WfOption scroll_sensitivity{"panel/wp_scroll_sensitivity"}; + + public: + void update_icon(); +}; From affc56519a7bf1afd82001f5ea412383e119dc46 Mon Sep 17 00:00:00 2001 From: Hue Date: Sun, 25 Jan 2026 00:35:53 +0100 Subject: [PATCH 02/48] added light to panel.cpp --- src/panel/panel.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/panel/panel.cpp b/src/panel/panel.cpp index 80673018..87c754fa 100644 --- a/src/panel/panel.cpp +++ b/src/panel/panel.cpp @@ -29,6 +29,9 @@ #ifdef HAVE_WIREPLUMBER #include "widgets/wp-mixer/wp-mixer.hpp" #endif +// #ifdef HAVE_LIGHT + #include "widgets/light/light.hpp" +// #endif #include "widgets/window-list/window-list.hpp" #include "widgets/notifications/notification-center.hpp" #include "widgets/tray/tray.hpp" @@ -176,6 +179,15 @@ class WayfirePanel::impl std::cerr << "Built without wireplumber support, mixer widget " " is not available." << std::endl; #endif + if (name == "light") + { +// #ifdef HAVE_LIGHT + return Widget(new WayfireLight()); +// #else + // #warning "Light libraries not found, light widget will not be available." + // std::cerr << "Built without light support, light widget " + // " is not available." << std::endl; +// #endif } if (name == "window-list") From 25b50ff701cb48792b500daf19c3f21d9aa7a3f8 Mon Sep 17 00:00:00 2001 From: Hue Date: Sun, 21 Dec 2025 15:20:12 +0100 Subject: [PATCH 03/48] icon selection --- src/panel/widgets/light/light.cpp | 18 ++++++++++++++++++ src/panel/widgets/light/light.hpp | 18 +++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/panel/widgets/light/light.cpp b/src/panel/widgets/light/light.cpp index 8773f2b1..97a6454c 100644 --- a/src/panel/widgets/light/light.cpp +++ b/src/panel/widgets/light/light.cpp @@ -1,6 +1,24 @@ #include "light.hpp" #include "wf-popover.hpp" +static BrightnessLevel light_icon_for(double value) +{ + double max = 1.0; + auto third = max / 3; + if (value <= third) + { + return BRIGHTNESS_LEVEL_LOW; + } else if ((value > third) && (value <= (third * 2))) + { + return BRIGHTNESS_LEVEL_MEDIUM; + } else if ((value > (third * 2)) && (value <= max)) + { + return BRIGHTNESS_LEVEL_HIGH; + } + + return BRIGHTNESS_LEVEL_OOR; +} + WfLightControl::WfLightControl(){ // preparation scale.set_range(0.0, 1.0); diff --git a/src/panel/widgets/light/light.hpp b/src/panel/widgets/light/light.hpp index 585bbe08..594718d1 100644 --- a/src/panel/widgets/light/light.hpp +++ b/src/panel/widgets/light/light.hpp @@ -8,6 +8,22 @@ #include "widget.hpp" #include "animated-scale.hpp" +enum BrightnessLevel +{ + BRIGHTNESS_LEVEL_LOW, + BRIGHTNESS_LEVEL_MEDIUM, + BRIGHTNESS_LEVEL_HIGH, + BRIGHTNESS_LEVEL_OOR, /* Out of range */ +}; + +const std::map icon_name_from_state = { + {BRIGHTNESS_LEVEL_LOW, "display-brightness-low"}, + {BRIGHTNESS_LEVEL_MEDIUM, "display-brightness-medium"}, + {BRIGHTNESS_LEVEL_HIGH, "display-brightness-high"}, + // this icon seems rare, so probably best to have a generic failure + {BRIGHTNESS_LEVEL_OOR, "display-brightness-invalid"}, +}; + class WfLightControl : public Gtk::Box { protected: @@ -35,7 +51,7 @@ class WayfireLight : public WayfireWidget { std::vector> controls; - WfOption scroll_sensitivity{"panel/wp_scroll_sensitivity"}; + WfOption scroll_sensitivity{"panel/light_scroll_sensitivity"}; public: void update_icon(); From a4dd0989a74c1242c1521d30af775dd0b0b81871 Mon Sep 17 00:00:00 2001 From: Hue Date: Wed, 31 Dec 2025 13:27:51 +0100 Subject: [PATCH 04/48] squash me --- src/panel/meson.build | 1 + src/panel/widgets/light/light.cpp | 10 +++++++--- src/panel/widgets/light/light.hpp | 26 ++++++++++++++++++++++---- 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/src/panel/meson.build b/src/panel/meson.build index bff4e6b3..9c005579 100644 --- a/src/panel/meson.build +++ b/src/panel/meson.build @@ -20,6 +20,7 @@ widget_sources = [ 'widgets/tray/item.cpp', 'widgets/tray/host.cpp', 'widgets/tray/dbusmenu.cpp', + 'widgets/light/light.cpp' ] deps = [ diff --git a/src/panel/widgets/light/light.cpp b/src/panel/widgets/light/light.cpp index 97a6454c..8074c7ad 100644 --- a/src/panel/widgets/light/light.cpp +++ b/src/panel/widgets/light/light.cpp @@ -34,8 +34,8 @@ WfLightControl::WfLightControl(){ label.set_text(get_name()); // layout - set_orientation(Gtk::Orientation::VERTICAL); - append(label); + ((Gtk::Box*)this)->set_orientation(Gtk::Orientation::VERTICAL); + ((Gtk::Box*)this)->append(label); append(scale); } @@ -47,7 +47,7 @@ void WayfireLight::init(Gtk::Box *container){ popover = button->get_popover(); popover->set_autohide(false); - // scroll to change brighten and dim all monitors + // scroll to brighten and dim all monitors auto scroll_gesture = Gtk::EventControllerScroll::create(); scroll_gesture->signal_scroll().connect([=] (double dx, double dy) { @@ -72,4 +72,8 @@ void WayfireLight::init(Gtk::Box *container){ popover->get_style_context()->add_class("light-popover"); container->append(*button); + + } + +// void WayfireLight::update_icon(){} diff --git a/src/panel/widgets/light/light.hpp b/src/panel/widgets/light/light.hpp index 594718d1..fe4ff937 100644 --- a/src/panel/widgets/light/light.hpp +++ b/src/panel/widgets/light/light.hpp @@ -4,7 +4,9 @@ */ #include - +extern "C" { + #include "public/ddcutil_c_api.h" +} #include "widget.hpp" #include "animated-scale.hpp" @@ -29,6 +31,7 @@ class WfLightControl : public Gtk::Box protected: WayfireAnimatedScale scale; Gtk::Label label; + std::map icons; virtual std::string get_name(); @@ -36,15 +39,28 @@ class WfLightControl : public Gtk::Box WfLightControl(); // a double from 0 to 1 for min to max - virtual void set_brightness(double brightness); - virtual double get_brightness(); + virtual void set_brightness(double brightness) = 0; + virtual double get_brightness() = 0; +}; + +class WfLightDdcControl : public WfLightControl +{ + protected: + DDCA_Display_Handle dh; +}; + +class WfLightFsControl: public WfLightControl +{ + protected: + std::string path; + }; class WayfireLight : public WayfireWidget { private: void init(Gtk::Box *container) override; - Gtk::Image icon; + Gtk::Image icon; std::unique_ptr button; Gtk::Popover *popover; Gtk::Box box; @@ -53,6 +69,8 @@ class WayfireLight : public WayfireWidget { WfOption scroll_sensitivity{"panel/light_scroll_sensitivity"}; + + public: void update_icon(); }; From d2b002a961bd6b4f8ba97d6ff570c380bcc352be Mon Sep 17 00:00:00 2001 From: Hue Date: Thu, 1 Jan 2026 20:31:50 +0100 Subject: [PATCH 05/48] fixed iteration, calling fs interractions --- src/panel/widgets/light/light.cpp | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/panel/widgets/light/light.cpp b/src/panel/widgets/light/light.cpp index 8074c7ad..28e1b29e 100644 --- a/src/panel/widgets/light/light.cpp +++ b/src/panel/widgets/light/light.cpp @@ -29,10 +29,6 @@ WfLightControl::WfLightControl(){ this->set_brightness(scale.get_target_value()); }); - const std::string name; - - label.set_text(get_name()); - // layout ((Gtk::Box*)this)->set_orientation(Gtk::Orientation::VERTICAL); ((Gtk::Box*)this)->append(label); @@ -61,9 +57,10 @@ void WayfireLight::init(Gtk::Box *container){ // Number of pixels expected to have scrolled. usually in 100s change = (dy * scroll_sensitivity) / 100; } - // for (auto control: controls){ - // control->set_brightness(control->get_brightness() + change); - // } + for (int i = 0 ; i < controls.size() ; i++){ + controls[i]->set_brightness(controls[i]->get_brightness() + change); + } + return true; }, true); scroll_gesture->set_flags(Gtk::EventControllerScroll::Flags::VERTICAL); button->add_controller(scroll_gesture); @@ -73,7 +70,7 @@ void WayfireLight::init(Gtk::Box *container){ container->append(*button); - + setup_fs(); } // void WayfireLight::update_icon(){} From e93f55eaa1f6089d75f9159820d8a0742c3b40a6 Mon Sep 17 00:00:00 2001 From: Hue Date: Thu, 1 Jan 2026 20:35:42 +0100 Subject: [PATCH 06/48] initial filesystem interactions --- src/panel/widgets/light/filesystem.cpp | 139 ++++++++++++++++++++++++- 1 file changed, 136 insertions(+), 3 deletions(-) diff --git a/src/panel/widgets/light/filesystem.cpp b/src/panel/widgets/light/filesystem.cpp index 137781bb..cf5627cf 100644 --- a/src/panel/widgets/light/filesystem.cpp +++ b/src/panel/widgets/light/filesystem.cpp @@ -1,6 +1,139 @@ +#include +#include +#include +#include +#include + #include "light.hpp" -class WfFsLightControl : public WfLightControl -{ - +bool is_group_member(gid_t file_group_id) { + gid_t current_group = getgid(); + gid_t supplementary_groups[NGROUPS_MAX]; + int n_groups = getgroups(NGROUPS_MAX, supplementary_groups); + + if (current_group == file_group_id) + return true; + + for (int i = 0; i < n_groups; ++i) { + if (supplementary_groups[i] == file_group_id) + return true; + } + + return false; +} + +bool is_in_file_group(const std::filesystem::path& file_path) { + struct stat file_info; + if (stat(file_path.c_str(), &file_info) != 0) { + std::cerr << "Failed to stat " << file_path << ".\n"; + return false; + } + + // let’s assume the file is not owned by the user and only bother with groups + struct group *file_group = getgrgid(file_info.st_gid); + + if (!file_group) { + std::cerr << "Failed to fetch owner/group info for " << file_path << ".\n"; + return false; + } + + if (is_group_member(file_info.st_gid)) { + return true; + } else { + return false; + } +} + +void WayfireLight::setup_fs(){ + // look for integrated backlights + const auto path = "/sys/class/backlight"; + if (!std::filesystem::exists(path)){ + std::cout << "No backlight directory found for integrated screens, skipping.\n"; + return; + } + + for (const auto& entry : std::filesystem::directory_iterator(path)){ + const std::filesystem::path b_path = entry.path().string() + "brightness"; + const std::filesystem::path max_b_path = entry.path().string() + "max_brightness"; + + if (!std::filesystem::exists(b_path)){ + std::cout << "No brightness found for " << entry.path().string() << ", ignoring.\n"; + break; + } + if (!std::filesystem::exists(b_path)){ + std::cout << "No max_brightness found for " << entry.path().string() << ", ignoring.\n"; + break; + } + + auto max_perms = std::filesystem::status(max_b_path).permissions(); + // can the file be read? + if (!((max_perms & std::filesystem::perms::others_read) == std::filesystem::perms::none) + || !((is_in_file_group(max_b_path) && !((max_perms & std::filesystem::perms::group_read) == std::filesystem::perms::none)))) + break; + + auto perms = std::filesystem::status(b_path).permissions(); + // can the file be read? + if (!((perms & std::filesystem::perms::others_read) == std::filesystem::perms::none) + || !((is_in_file_group(b_path) && !((perms & std::filesystem::perms::group_read) == std::filesystem::perms::none)))) + break; + // and written? + if (!((perms & std::filesystem::perms::others_write) == std::filesystem::perms::none) + || !((is_in_file_group(b_path) && !((perms & std::filesystem::perms::group_write) == std::filesystem::perms::none)))) + std::cout << "Can read backlight, but cannot write. Control will only display brightness.\n"; + + controls.emplace_back(std::make_unique(entry.path())); + } +} + +// the permissions have already been checked, so we just read/write + +WfLightFsControl::WfLightFsControl(std::string path) : WfLightControl(){ + scale.set_target_value(get_brightness()); + + label.set_text(get_name()); + + icons = brightness_display_icons; +} + +std::string WfLightFsControl::get_name(){ + std::string name; + name = "Integrated display"; + return name; +} + +int WfLightFsControl::get_max(){ + std::ifstream max_file(path + "max_brightness"); + if (!max_file.is_open()){ + std::cerr << "Failed to get max brightness for device at " << path << '\n'; + return 0; + } + int max; + max_file >> max; + max_file.close(); + return max; +} + +void WfLightFsControl::set_brightness(double brightness){ + std::ofstream b_file(path + "/brightness"); + if (!b_file.is_open()){ + std::cerr << "Failed to open brightness for device at " << path << '\n'; + return; + } + b_file << brightness * get_max(); + if (b_file.fail()){ + std::cerr << "Failed to write brightness for device at " << path << '\n'; + } + +} + +double WfLightFsControl::get_brightness(){ + std::ifstream b_file(path + "max_brightness"); + if (!b_file.is_open()){ + std::cerr << "Failed to get brightness for device at " << path << '\n'; + return 0; + } + int brightness; + b_file >> brightness; + b_file.close(); + return brightness * get_max(); } From 195221888c5140731f0a5d22a953288c89dc8d1a Mon Sep 17 00:00:00 2001 From: Hue Date: Sun, 25 Jan 2026 00:38:00 +0100 Subject: [PATCH 07/48] ddcutil build --- meson.build | 5 +++++ meson_options.txt | 6 ++++++ src/panel/meson.build | 4 +++- src/panel/panel.cpp | 15 ++++++--------- src/panel/widgets/light/ddcutil.cpp | 3 +++ src/panel/widgets/light/light.hpp | 24 ++++++++++++++++-------- 6 files changed, 39 insertions(+), 18 deletions(-) diff --git a/meson.build b/meson.build index 50d6dc66..e1b72f57 100644 --- a/meson.build +++ b/meson.build @@ -22,6 +22,7 @@ epoxy = dependency('epoxy') gtklayershell = dependency('gtk4-layer-shell-0', fallback: ['gtk4-layer-shell']) libpulse = dependency('libpulse', required: get_option('pulse')) wireplumber = dependency('wireplumber-0.5', required: get_option('wireplumber')) +ddcutil = dependency('libddcutil-5', required: get_option('ddcutil')) dbusmenu_gtk = dependency('dbusmenu-glib-0.4') libgvc = subproject('gvc', default_options: ['static=true'], required: get_option('pulse')) xkbregistry = dependency('xkbregistry') @@ -40,6 +41,10 @@ if wireplumber.found() add_project_arguments('-DHAVE_WIREPLUMBER=1', language: 'cpp') endif +if ddcutil.found() + add_project_arguments('-DHAVE_DDCUTIL=1', language: 'cpp') +endif + needs_libinotify = ['freebsd', 'dragonfly'].contains(host_machine.system()) libinotify = dependency('libinotify', required: needs_libinotify) diff --git a/meson_options.txt b/meson_options.txt index 8ecd76c0..bb8c07fa 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -10,6 +10,12 @@ option( value: 'auto', description: 'Build wireplumber and mixer widget', ) +option( + 'ddcutil', + type: 'feature', + value: 'auto', + description: 'Build external monitor support for backlight widget' +) option( 'wayland-logout', type: 'boolean', diff --git a/src/panel/meson.build b/src/panel/meson.build index 9c005579..a15d586d 100644 --- a/src/panel/meson.build +++ b/src/panel/meson.build @@ -20,7 +20,9 @@ widget_sources = [ 'widgets/tray/item.cpp', 'widgets/tray/host.cpp', 'widgets/tray/dbusmenu.cpp', - 'widgets/light/light.cpp' + 'widgets/light/light.cpp', + 'widgets/light/ddcutil.cpp', + 'widgets/light/filesystem.cpp', ] deps = [ diff --git a/src/panel/panel.cpp b/src/panel/panel.cpp index 87c754fa..d517c392 100644 --- a/src/panel/panel.cpp +++ b/src/panel/panel.cpp @@ -29,9 +29,7 @@ #ifdef HAVE_WIREPLUMBER #include "widgets/wp-mixer/wp-mixer.hpp" #endif -// #ifdef HAVE_LIGHT - #include "widgets/light/light.hpp" -// #endif +#include "widgets/light/light.hpp" #include "widgets/window-list/window-list.hpp" #include "widgets/notifications/notification-center.hpp" #include "widgets/tray/tray.hpp" @@ -181,13 +179,12 @@ class WayfirePanel::impl #endif if (name == "light") { -// #ifdef HAVE_LIGHT return Widget(new WayfireLight()); -// #else - // #warning "Light libraries not found, light widget will not be available." - // std::cerr << "Built without light support, light widget " - // " is not available." << std::endl; -// #endif +#ifndef HAVE_DDCUTIL + #warning "Libddcutil not found, light widget will not support external monitors." + std::cerr << "Built without DDC/CI support, light widget " + " doesn’t support external monitors." << std::endl; +#endif } if (name == "window-list") diff --git a/src/panel/widgets/light/ddcutil.cpp b/src/panel/widgets/light/ddcutil.cpp index e69de29b..75e98ef7 100644 --- a/src/panel/widgets/light/ddcutil.cpp +++ b/src/panel/widgets/light/ddcutil.cpp @@ -0,0 +1,3 @@ +#ifdef HAVE_DDCUTIL + +#endif diff --git a/src/panel/widgets/light/light.hpp b/src/panel/widgets/light/light.hpp index fe4ff937..3b1ed60a 100644 --- a/src/panel/widgets/light/light.hpp +++ b/src/panel/widgets/light/light.hpp @@ -1,12 +1,9 @@ -/* - abstract light control class, which are handled by a widget - other .cpp files in this directory are backends for different ways of handling them -*/ - #include +#ifdef HAVE_DDCUTIL extern "C" { #include "public/ddcutil_c_api.h" } +#endif #include "widget.hpp" #include "animated-scale.hpp" @@ -18,7 +15,7 @@ enum BrightnessLevel BRIGHTNESS_LEVEL_OOR, /* Out of range */ }; -const std::map icon_name_from_state = { +const std::map brightness_display_icons = { {BRIGHTNESS_LEVEL_LOW, "display-brightness-low"}, {BRIGHTNESS_LEVEL_MEDIUM, "display-brightness-medium"}, {BRIGHTNESS_LEVEL_HIGH, "display-brightness-high"}, @@ -33,7 +30,7 @@ class WfLightControl : public Gtk::Box Gtk::Label label; std::map icons; - virtual std::string get_name(); + virtual std::string get_name() = 0; public: WfLightControl(); @@ -41,19 +38,29 @@ class WfLightControl : public Gtk::Box // a double from 0 to 1 for min to max virtual void set_brightness(double brightness) = 0; virtual double get_brightness() = 0; + }; +#ifdef HAVE_DDCUTIL class WfLightDdcControl : public WfLightControl { protected: DDCA_Display_Handle dh; }; +#endif class WfLightFsControl: public WfLightControl { protected: std::string path; + int get_max(); + + std::string get_name(); + public: + WfLightFsControl(std::string path); + void set_brightness(double brightness); + double get_brightness(); }; class WayfireLight : public WayfireWidget { @@ -69,7 +76,8 @@ class WayfireLight : public WayfireWidget { WfOption scroll_sensitivity{"panel/light_scroll_sensitivity"}; - + void setup_fs(); + void setup_ddc(); public: void update_icon(); From d28668dd56281ad06b507817a2dc3dea54802dbc Mon Sep 17 00:00:00 2001 From: Hue Date: Sun, 4 Jan 2026 13:09:28 +0100 Subject: [PATCH 08/48] fs slider working --- src/panel/widgets/light/filesystem.cpp | 35 ++++++++++++++++---------- src/panel/widgets/light/light.cpp | 15 +++++++---- src/panel/widgets/light/light.hpp | 4 ++- 3 files changed, 35 insertions(+), 19 deletions(-) diff --git a/src/panel/widgets/light/filesystem.cpp b/src/panel/widgets/light/filesystem.cpp index cf5627cf..036f6ad2 100644 --- a/src/panel/widgets/light/filesystem.cpp +++ b/src/panel/widgets/light/filesystem.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include "light.hpp" @@ -22,6 +23,7 @@ bool is_group_member(gid_t file_group_id) { return false; } +// let’s assume the file is not owned by the user and only bother with groups bool is_in_file_group(const std::filesystem::path& file_path) { struct stat file_info; if (stat(file_path.c_str(), &file_info) != 0) { @@ -29,7 +31,6 @@ bool is_in_file_group(const std::filesystem::path& file_path) { return false; } - // let’s assume the file is not owned by the user and only bother with groups struct group *file_group = getgrgid(file_info.st_gid); if (!file_group) { @@ -53,8 +54,8 @@ void WayfireLight::setup_fs(){ } for (const auto& entry : std::filesystem::directory_iterator(path)){ - const std::filesystem::path b_path = entry.path().string() + "brightness"; - const std::filesystem::path max_b_path = entry.path().string() + "max_brightness"; + const std::filesystem::path b_path = entry.path().string() + "/brightness"; + const std::filesystem::path max_b_path = entry.path().string() + "/max_brightness"; if (!std::filesystem::exists(b_path)){ std::cout << "No brightness found for " << entry.path().string() << ", ignoring.\n"; @@ -68,26 +69,32 @@ void WayfireLight::setup_fs(){ auto max_perms = std::filesystem::status(max_b_path).permissions(); // can the file be read? if (!((max_perms & std::filesystem::perms::others_read) == std::filesystem::perms::none) - || !((is_in_file_group(max_b_path) && !((max_perms & std::filesystem::perms::group_read) == std::filesystem::perms::none)))) + || !((is_in_file_group(max_b_path) && !((max_perms & std::filesystem::perms::group_read) == std::filesystem::perms::none)))){ + std::cout << "Cannot read max_brightness file.\n"; break; + } auto perms = std::filesystem::status(b_path).permissions(); // can the file be read? if (!((perms & std::filesystem::perms::others_read) == std::filesystem::perms::none) - || !((is_in_file_group(b_path) && !((perms & std::filesystem::perms::group_read) == std::filesystem::perms::none)))) + || !((is_in_file_group(b_path) && !((perms & std::filesystem::perms::group_read) == std::filesystem::perms::none)))){ + std::cout << "Cannot read brightness file.\n"; break; + } // and written? if (!((perms & std::filesystem::perms::others_write) == std::filesystem::perms::none) || !((is_in_file_group(b_path) && !((perms & std::filesystem::perms::group_write) == std::filesystem::perms::none)))) std::cout << "Can read backlight, but cannot write. Control will only display brightness.\n"; - controls.emplace_back(std::make_unique(entry.path())); + add_control(std::make_unique(entry.path())); } } -// the permissions have already been checked, so we just read/write +// the permissions have already been checked and *most likely* won’t have changed, so we just read/write + +WfLightFsControl::WfLightFsControl(std::string _path) : WfLightControl(){ + path = _path; -WfLightFsControl::WfLightFsControl(std::string path) : WfLightControl(){ scale.set_target_value(get_brightness()); label.set_text(get_name()); @@ -102,7 +109,7 @@ std::string WfLightFsControl::get_name(){ } int WfLightFsControl::get_max(){ - std::ifstream max_file(path + "max_brightness"); + std::ifstream max_file(path + "/max_brightness"); if (!max_file.is_open()){ std::cerr << "Failed to get max brightness for device at " << path << '\n'; return 0; @@ -119,7 +126,8 @@ void WfLightFsControl::set_brightness(double brightness){ std::cerr << "Failed to open brightness for device at " << path << '\n'; return; } - b_file << brightness * get_max(); + // something of the sort avoids formatting issues with locales + b_file << std::to_string((int)(brightness * (double)get_max())); if (b_file.fail()){ std::cerr << "Failed to write brightness for device at " << path << '\n'; } @@ -127,13 +135,14 @@ void WfLightFsControl::set_brightness(double brightness){ } double WfLightFsControl::get_brightness(){ - std::ifstream b_file(path + "max_brightness"); + std::ifstream b_file(path + "/brightness"); if (!b_file.is_open()){ std::cerr << "Failed to get brightness for device at " << path << '\n'; return 0; } - int brightness; + int brightness, max; b_file >> brightness; b_file.close(); - return brightness * get_max(); + max = get_max(); + return (((double)brightness + (double)max) / (double)max) - 1; } diff --git a/src/panel/widgets/light/light.cpp b/src/panel/widgets/light/light.cpp index 28e1b29e..413fd098 100644 --- a/src/panel/widgets/light/light.cpp +++ b/src/panel/widgets/light/light.cpp @@ -1,5 +1,6 @@ #include "light.hpp" #include "wf-popover.hpp" +#include static BrightnessLevel light_icon_for(double value) { @@ -22,7 +23,6 @@ static BrightnessLevel light_icon_for(double value) WfLightControl::WfLightControl(){ // preparation scale.set_range(0.0, 1.0); - scale.set_target_value(0.5); scale.set_size_request(300); scale.set_user_changed_callback([this](){ @@ -30,8 +30,8 @@ WfLightControl::WfLightControl(){ }); // layout - ((Gtk::Box*)this)->set_orientation(Gtk::Orientation::VERTICAL); - ((Gtk::Box*)this)->append(label); + set_orientation(Gtk::Orientation::VERTICAL); + append(label); append(scale); } @@ -51,11 +51,11 @@ void WayfireLight::init(Gtk::Box *container){ if (scroll_gesture->get_unit() == Gdk::ScrollUnit::WHEEL) { // +- number of clicks. - change = (dy * scroll_sensitivity) / 10; + change = (dy * 1/* scroll_sensitivity */) / 10; } else { // Number of pixels expected to have scrolled. usually in 100s - change = (dy * scroll_sensitivity) / 100; + change = (dy * 1/* scroll_sensitivity */) / 100; } for (int i = 0 ; i < controls.size() ; i++){ controls[i]->set_brightness(controls[i]->get_brightness() + change); @@ -73,4 +73,9 @@ void WayfireLight::init(Gtk::Box *container){ setup_fs(); } +void WayfireLight::add_control(std::unique_ptr control){ + box.append(*control); + controls.push_back(std::move(control)); +} + // void WayfireLight::update_icon(){} diff --git a/src/panel/widgets/light/light.hpp b/src/panel/widgets/light/light.hpp index 3b1ed60a..07c014bd 100644 --- a/src/panel/widgets/light/light.hpp +++ b/src/panel/widgets/light/light.hpp @@ -74,7 +74,9 @@ class WayfireLight : public WayfireWidget { std::vector> controls; - WfOption scroll_sensitivity{"panel/light_scroll_sensitivity"}; + // WfOption scroll_sensitivity{"panel/light_scroll_sensitivity"}; + + void add_control(std::unique_ptr control); void setup_fs(); void setup_ddc(); From 5325f2b6f25fd36399f7ce7ae4a6803a0bcba98a Mon Sep 17 00:00:00 2001 From: Hue Date: Sun, 4 Jan 2026 14:30:26 +0100 Subject: [PATCH 09/48] fix permissions checking --- src/panel/widgets/light/filesystem.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/panel/widgets/light/filesystem.cpp b/src/panel/widgets/light/filesystem.cpp index 036f6ad2..7d662d76 100644 --- a/src/panel/widgets/light/filesystem.cpp +++ b/src/panel/widgets/light/filesystem.cpp @@ -68,22 +68,25 @@ void WayfireLight::setup_fs(){ auto max_perms = std::filesystem::status(max_b_path).permissions(); // can the file be read? - if (!((max_perms & std::filesystem::perms::others_read) == std::filesystem::perms::none) - || !((is_in_file_group(max_b_path) && !((max_perms & std::filesystem::perms::group_read) == std::filesystem::perms::none)))){ + if (!((max_perms & std::filesystem::perms::others_read) != std::filesystem::perms::none + || (is_in_file_group(max_b_path) && (max_perms & std::filesystem::perms::group_read) != std::filesystem::perms::none) + )){ std::cout << "Cannot read max_brightness file.\n"; - break; + continue; } auto perms = std::filesystem::status(b_path).permissions(); // can the file be read? - if (!((perms & std::filesystem::perms::others_read) == std::filesystem::perms::none) - || !((is_in_file_group(b_path) && !((perms & std::filesystem::perms::group_read) == std::filesystem::perms::none)))){ + if (!((perms & std::filesystem::perms::others_read) != std::filesystem::perms::none + || (is_in_file_group(b_path) && (perms & std::filesystem::perms::group_read) != std::filesystem::perms::none) + )){ std::cout << "Cannot read brightness file.\n"; - break; + continue; } // and written? - if (!((perms & std::filesystem::perms::others_write) == std::filesystem::perms::none) - || !((is_in_file_group(b_path) && !((perms & std::filesystem::perms::group_write) == std::filesystem::perms::none)))) + if (!((perms & std::filesystem::perms::others_write) != std::filesystem::perms::none + || (is_in_file_group(b_path) && (perms & std::filesystem::perms::group_write) != std::filesystem::perms::none) + )) std::cout << "Can read backlight, but cannot write. Control will only display brightness.\n"; add_control(std::make_unique(entry.path())); From 45ba77a911d932695a2cfd559c8125827ff8b507 Mon Sep 17 00:00:00 2001 From: Hue Date: Sun, 4 Jan 2026 14:30:48 +0100 Subject: [PATCH 10/48] add invert scroll toggle --- src/panel/widgets/light/light.cpp | 4 ++++ src/panel/widgets/light/light.hpp | 1 + 2 files changed, 5 insertions(+) diff --git a/src/panel/widgets/light/light.cpp b/src/panel/widgets/light/light.cpp index 413fd098..cb36f1fe 100644 --- a/src/panel/widgets/light/light.cpp +++ b/src/panel/widgets/light/light.cpp @@ -48,6 +48,7 @@ void WayfireLight::init(Gtk::Box *container){ scroll_gesture->signal_scroll().connect([=] (double dx, double dy) { double change = 0; + if (scroll_gesture->get_unit() == Gdk::ScrollUnit::WHEEL) { // +- number of clicks. @@ -57,6 +58,9 @@ void WayfireLight::init(Gtk::Box *container){ // Number of pixels expected to have scrolled. usually in 100s change = (dy * 1/* scroll_sensitivity */) / 100; } + // if (!invert_scroll) + change *= -1; + for (int i = 0 ; i < controls.size() ; i++){ controls[i]->set_brightness(controls[i]->get_brightness() + change); } diff --git a/src/panel/widgets/light/light.hpp b/src/panel/widgets/light/light.hpp index 07c014bd..d138bd78 100644 --- a/src/panel/widgets/light/light.hpp +++ b/src/panel/widgets/light/light.hpp @@ -75,6 +75,7 @@ class WayfireLight : public WayfireWidget { std::vector> controls; // WfOption scroll_sensitivity{"panel/light_scroll_sensitivity"}; + // WfOption invert_scroll{"panel/light_invert_scroll"}; void add_control(std::unique_ptr control); From 7175300fbd6068666966257a43eae43341f8a935 Mon Sep 17 00:00:00 2001 From: Hue Date: Sun, 4 Jan 2026 15:51:50 +0100 Subject: [PATCH 11/48] config metadata --- metadata/panel.xml | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/metadata/panel.xml b/metadata/panel.xml index b59900e7..d8508b61 100644 --- a/metadata/panel.xml +++ b/metadata/panel.xml @@ -467,5 +467,38 @@ Set to -1 to only run it by clicking the button. 1 + + <_short>Light + + + + From f7d563b53b938c4dc67239dd20fb9a2dd96f6534 Mon Sep 17 00:00:00 2001 From: Hue Date: Sun, 4 Jan 2026 15:52:27 +0100 Subject: [PATCH 12/48] added icon --- src/panel/widgets/light/filesystem.cpp | 4 +-- src/panel/widgets/light/light.cpp | 42 ++++++++++++++++++++++++-- src/panel/widgets/light/light.hpp | 16 +++++++--- 3 files changed, 54 insertions(+), 8 deletions(-) diff --git a/src/panel/widgets/light/filesystem.cpp b/src/panel/widgets/light/filesystem.cpp index 7d662d76..40542c12 100644 --- a/src/panel/widgets/light/filesystem.cpp +++ b/src/panel/widgets/light/filesystem.cpp @@ -89,13 +89,13 @@ void WayfireLight::setup_fs(){ )) std::cout << "Can read backlight, but cannot write. Control will only display brightness.\n"; - add_control(std::make_unique(entry.path())); + add_control(std::make_unique(this, entry.path())); } } // the permissions have already been checked and *most likely* won’t have changed, so we just read/write -WfLightFsControl::WfLightFsControl(std::string _path) : WfLightControl(){ +WfLightFsControl::WfLightFsControl(WayfireLight *parent, std::string _path) : WfLightControl(parent){ path = _path; scale.set_target_value(get_brightness()); diff --git a/src/panel/widgets/light/light.cpp b/src/panel/widgets/light/light.cpp index cb36f1fe..b3c4537b 100644 --- a/src/panel/widgets/light/light.cpp +++ b/src/panel/widgets/light/light.cpp @@ -20,13 +20,16 @@ static BrightnessLevel light_icon_for(double value) return BRIGHTNESS_LEVEL_OOR; } -WfLightControl::WfLightControl(){ +WfLightControl::WfLightControl(WayfireLight *_parent){ + parent = _parent; + // preparation scale.set_range(0.0, 1.0); scale.set_size_request(300); scale.set_user_changed_callback([this](){ this->set_brightness(scale.get_target_value()); + parent->update_icon(); }); // layout @@ -75,6 +78,8 @@ void WayfireLight::init(Gtk::Box *container){ container->append(*button); setup_fs(); + + update_icon(); } void WayfireLight::add_control(std::unique_ptr control){ @@ -82,4 +87,37 @@ void WayfireLight::add_control(std::unique_ptr control){ controls.push_back(std::move(control)); } -// void WayfireLight::update_icon(){} +void WayfireLight::update_icon(){ + // if none, show unavailable + if (controls.size() == 0){ + icon.set_from_icon_name(brightness_display_icons.at(BRIGHTNESS_LEVEL_OOR)); + return; + } + if (icon_target/* .value() */ == ICON_TARGET_BRIGHTEST){ + // since brightness is between 0 and 1, we can just start at 0 + double max = 0; + for (int i = 0 ; i < controls.size() ; i++){ + if (controls[i]->get_brightness() > max){ + max = controls[i]->get_brightness(); + } + } + icon.set_from_icon_name(brightness_display_icons.at(light_icon_for(max))); + } + if (icon_target/* .value() */ == ICON_TARGET_DIMMEST){ + // same as before, but just start from 1 + double min = 1; + for (int i = 0 ; i < controls.size() ; i++){ + if (controls[i]->get_brightness() > min){ + min = controls[i]->get_brightness(); + } + } + icon.set_from_icon_name(brightness_display_icons.at(light_icon_for(min))); + } + if (icon_target/* .value() */ == ICON_TARGET_AVERAGE){ + double sum = 0; + for (int i = 0 ; i < controls.size() ; i++){ + sum += controls[i]->get_brightness(); + } + icon.set_from_icon_name(brightness_display_icons.at(light_icon_for(sum / controls.size()))); + } +} diff --git a/src/panel/widgets/light/light.hpp b/src/panel/widgets/light/light.hpp index d138bd78..b07f19d9 100644 --- a/src/panel/widgets/light/light.hpp +++ b/src/panel/widgets/light/light.hpp @@ -7,6 +7,10 @@ extern "C" { #include "widget.hpp" #include "animated-scale.hpp" +#define ICON_TARGET_BRIGHTEST "brightest" +#define ICON_TARGET_DIMMEST "dimmest" +#define ICON_TARGET_AVERAGE "average" + enum BrightnessLevel { BRIGHTNESS_LEVEL_LOW, @@ -23,17 +27,20 @@ const std::map brightness_display_icons = { {BRIGHTNESS_LEVEL_OOR, "display-brightness-invalid"}, }; +class WayfireLight; + class WfLightControl : public Gtk::Box { protected: WayfireAnimatedScale scale; Gtk::Label label; std::map icons; + WayfireLight *parent; virtual std::string get_name() = 0; public: - WfLightControl(); + WfLightControl(WayfireLight *parent); // a double from 0 to 1 for min to max virtual void set_brightness(double brightness) = 0; @@ -58,7 +65,7 @@ class WfLightFsControl: public WfLightControl std::string get_name(); public: - WfLightFsControl(std::string path); + WfLightFsControl(WayfireLight *parent, std::string path); void set_brightness(double brightness); double get_brightness(); }; @@ -74,8 +81,9 @@ class WayfireLight : public WayfireWidget { std::vector> controls; - // WfOption scroll_sensitivity{"panel/light_scroll_sensitivity"}; - // WfOption invert_scroll{"panel/light_invert_scroll"}; + WfOption icon_target{"panel/light_icon_target"}; + WfOption scroll_sensitivity{"panel/light_scroll_sensitivity"}; + WfOption invert_scroll{"panel/light_invert_scroll"}; void add_control(std::unique_ptr control); From 4c924ba1b01e22dfc32fde2f5a26239b36a4d3a9 Mon Sep 17 00:00:00 2001 From: Hue Date: Sun, 4 Jan 2026 17:13:05 +0100 Subject: [PATCH 13/48] light icon size config --- metadata/panel.xml | 5 +++++ src/panel/panel.cpp | 1 + src/panel/widgets/light/light.cpp | 1 + 3 files changed, 7 insertions(+) diff --git a/metadata/panel.xml b/metadata/panel.xml index d8508b61..47f93d9f 100644 --- a/metadata/panel.xml +++ b/metadata/panel.xml @@ -499,6 +499,11 @@ Set to -1 to only run it by clicking the button. <_name>Average + diff --git a/src/panel/panel.cpp b/src/panel/panel.cpp index d517c392..35e82cc1 100644 --- a/src/panel/panel.cpp +++ b/src/panel/panel.cpp @@ -401,6 +401,7 @@ void WayfirePanelApp::on_activate() new CssFromConfigString("panel/background_color", ".wf-panel{background-color:", ";}"); new CssFromConfigBool("panel/battery_icon_invert", ".battery image{filter:invert(100%);}", ""); new CssFromConfigBool("panel/network_icon_invert_color", ".network-icon{filter:invert(100%);}", ""); + new CssFromConfigInt("panel/light_icon_size", ".light{-gtk-icon-size:", "px;}"); new CssFromConfigFont("panel/battery_font", ".battery {", "}"); new CssFromConfigFont("panel/clock_font", ".clock {", "}"); diff --git a/src/panel/widgets/light/light.cpp b/src/panel/widgets/light/light.cpp index b3c4537b..64d48c4f 100644 --- a/src/panel/widgets/light/light.cpp +++ b/src/panel/widgets/light/light.cpp @@ -40,6 +40,7 @@ WfLightControl::WfLightControl(WayfireLight *_parent){ void WayfireLight::init(Gtk::Box *container){ button = std::make_unique("panel"); + button->get_style_context()->add_class("light"); button->get_style_context()->add_class("flat"); button->set_child(icon); button->show(); From 7bfa6beb3e817ba6f460797e7e3d869929d3d123 Mon Sep 17 00:00:00 2001 From: Hue Date: Sun, 4 Jan 2026 17:54:42 +0100 Subject: [PATCH 14/48] type fix --- src/panel/widgets/light/light.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/panel/widgets/light/light.cpp b/src/panel/widgets/light/light.cpp index 64d48c4f..668d988b 100644 --- a/src/panel/widgets/light/light.cpp +++ b/src/panel/widgets/light/light.cpp @@ -65,7 +65,7 @@ void WayfireLight::init(Gtk::Box *container){ // if (!invert_scroll) change *= -1; - for (int i = 0 ; i < controls.size() ; i++){ + for (int i = 0 ; i < (int)controls.size() ; i++){ controls[i]->set_brightness(controls[i]->get_brightness() + change); } return true; @@ -94,29 +94,29 @@ void WayfireLight::update_icon(){ icon.set_from_icon_name(brightness_display_icons.at(BRIGHTNESS_LEVEL_OOR)); return; } - if (icon_target/* .value() */ == ICON_TARGET_BRIGHTEST){ + if (icon_target.value() == ICON_TARGET_BRIGHTEST){ // since brightness is between 0 and 1, we can just start at 0 double max = 0; - for (int i = 0 ; i < controls.size() ; i++){ + for (int i = 0 ; i < (int)controls.size() ; i++){ if (controls[i]->get_brightness() > max){ max = controls[i]->get_brightness(); } } icon.set_from_icon_name(brightness_display_icons.at(light_icon_for(max))); } - if (icon_target/* .value() */ == ICON_TARGET_DIMMEST){ + if (icon_target.value() == ICON_TARGET_DIMMEST){ // same as before, but just start from 1 double min = 1; - for (int i = 0 ; i < controls.size() ; i++){ + for (int i = 0 ; i < (int)controls.size() ; i++){ if (controls[i]->get_brightness() > min){ min = controls[i]->get_brightness(); } } icon.set_from_icon_name(brightness_display_icons.at(light_icon_for(min))); } - if (icon_target/* .value() */ == ICON_TARGET_AVERAGE){ + if (icon_target.value() == ICON_TARGET_AVERAGE){ double sum = 0; - for (int i = 0 ; i < controls.size() ; i++){ + for (int i = 0 ; i < (int)controls.size() ; i++){ sum += controls[i]->get_brightness(); } icon.set_from_icon_name(brightness_display_icons.at(light_icon_for(sum / controls.size()))); From 3fe0b5cd44ef3ff65ba45aa56c9650a699fadd05 Mon Sep 17 00:00:00 2001 From: Hue Date: Mon, 5 Jan 2026 18:09:57 +0100 Subject: [PATCH 15/48] rename filesystem to sysfs --- src/panel/widgets/light/light.hpp | 6 +++--- .../widgets/light/{filesystem.cpp => sysfs.cpp} | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) rename src/panel/widgets/light/{filesystem.cpp => sysfs.cpp} (91%) diff --git a/src/panel/widgets/light/light.hpp b/src/panel/widgets/light/light.hpp index b07f19d9..3fffab4b 100644 --- a/src/panel/widgets/light/light.hpp +++ b/src/panel/widgets/light/light.hpp @@ -56,7 +56,7 @@ class WfLightDdcControl : public WfLightControl }; #endif -class WfLightFsControl: public WfLightControl +class WfLightSysfsControl: public WfLightControl { protected: std::string path; @@ -65,7 +65,7 @@ class WfLightFsControl: public WfLightControl std::string get_name(); public: - WfLightFsControl(WayfireLight *parent, std::string path); + WfLightSysfsControl(WayfireLight *parent, std::string path); void set_brightness(double brightness); double get_brightness(); }; @@ -87,7 +87,7 @@ class WayfireLight : public WayfireWidget { void add_control(std::unique_ptr control); - void setup_fs(); + void setup_sysfs(); void setup_ddc(); public: diff --git a/src/panel/widgets/light/filesystem.cpp b/src/panel/widgets/light/sysfs.cpp similarity index 91% rename from src/panel/widgets/light/filesystem.cpp rename to src/panel/widgets/light/sysfs.cpp index 40542c12..bfb08e73 100644 --- a/src/panel/widgets/light/filesystem.cpp +++ b/src/panel/widgets/light/sysfs.cpp @@ -45,7 +45,7 @@ bool is_in_file_group(const std::filesystem::path& file_path) { } } -void WayfireLight::setup_fs(){ +void WayfireLight::setup_sysfs(){ // look for integrated backlights const auto path = "/sys/class/backlight"; if (!std::filesystem::exists(path)){ @@ -89,13 +89,13 @@ void WayfireLight::setup_fs(){ )) std::cout << "Can read backlight, but cannot write. Control will only display brightness.\n"; - add_control(std::make_unique(this, entry.path())); + add_control(std::make_unique(this, entry.path())); } } // the permissions have already been checked and *most likely* won’t have changed, so we just read/write -WfLightFsControl::WfLightFsControl(WayfireLight *parent, std::string _path) : WfLightControl(parent){ +WfLightSysfsControl::WfLightSysfsControl(WayfireLight *parent, std::string _path) : WfLightControl(parent){ path = _path; scale.set_target_value(get_brightness()); @@ -105,13 +105,13 @@ WfLightFsControl::WfLightFsControl(WayfireLight *parent, std::string _path) : Wf icons = brightness_display_icons; } -std::string WfLightFsControl::get_name(){ +std::string WfLightSysfsControl::get_name(){ std::string name; name = "Integrated display"; return name; } -int WfLightFsControl::get_max(){ +int WfLightSysfsControl::get_max(){ std::ifstream max_file(path + "/max_brightness"); if (!max_file.is_open()){ std::cerr << "Failed to get max brightness for device at " << path << '\n'; @@ -123,7 +123,7 @@ int WfLightFsControl::get_max(){ return max; } -void WfLightFsControl::set_brightness(double brightness){ +void WfLightSysfsControl::set_brightness(double brightness){ std::ofstream b_file(path + "/brightness"); if (!b_file.is_open()){ std::cerr << "Failed to open brightness for device at " << path << '\n'; @@ -137,7 +137,7 @@ void WfLightFsControl::set_brightness(double brightness){ } -double WfLightFsControl::get_brightness(){ +double WfLightSysfsControl::get_brightness(){ std::ifstream b_file(path + "/brightness"); if (!b_file.is_open()){ std::cerr << "Failed to get brightness for device at " << path << '\n'; From 2ac73fc987dc9b31acb75de3087b84da0e0742a6 Mon Sep 17 00:00:00 2001 From: Hue Date: Tue, 6 Jan 2026 10:37:45 +0100 Subject: [PATCH 16/48] singleton master design, basic inotify code --- src/panel/meson.build | 2 +- src/panel/widgets/light/light.cpp | 13 ++- src/panel/widgets/light/light.hpp | 5 +- src/panel/widgets/light/sysfs.cpp | 182 ++++++++++++++++++++++++------ 4 files changed, 158 insertions(+), 44 deletions(-) diff --git a/src/panel/meson.build b/src/panel/meson.build index a15d586d..07379a41 100644 --- a/src/panel/meson.build +++ b/src/panel/meson.build @@ -22,7 +22,7 @@ widget_sources = [ 'widgets/tray/dbusmenu.cpp', 'widgets/light/light.cpp', 'widgets/light/ddcutil.cpp', - 'widgets/light/filesystem.cpp', + 'widgets/light/sysfs.cpp', ] deps = [ diff --git a/src/panel/widgets/light/light.cpp b/src/panel/widgets/light/light.cpp index 668d988b..247275ac 100644 --- a/src/panel/widgets/light/light.cpp +++ b/src/panel/widgets/light/light.cpp @@ -38,6 +38,11 @@ WfLightControl::WfLightControl(WayfireLight *_parent){ append(scale); } +void WfLightControl::set_scale_target_value(double brightness) +{ + scale.set_target_value(brightness); +} + void WayfireLight::init(Gtk::Box *container){ button = std::make_unique("panel"); button->get_style_context()->add_class("light"); @@ -56,13 +61,13 @@ void WayfireLight::init(Gtk::Box *container){ if (scroll_gesture->get_unit() == Gdk::ScrollUnit::WHEEL) { // +- number of clicks. - change = (dy * 1/* scroll_sensitivity */) / 10; + change = (dy * scroll_sensitivity) / 10; } else { // Number of pixels expected to have scrolled. usually in 100s - change = (dy * 1/* scroll_sensitivity */) / 100; + change = (dy * scroll_sensitivity) / 100; } - // if (!invert_scroll) + if (!invert_scroll) change *= -1; for (int i = 0 ; i < (int)controls.size() ; i++){ @@ -78,7 +83,7 @@ void WayfireLight::init(Gtk::Box *container){ container->append(*button); - setup_fs(); + setup_sysfs(); update_icon(); } diff --git a/src/panel/widgets/light/light.hpp b/src/panel/widgets/light/light.hpp index 3fffab4b..9538fef8 100644 --- a/src/panel/widgets/light/light.hpp +++ b/src/panel/widgets/light/light.hpp @@ -42,6 +42,7 @@ class WfLightControl : public Gtk::Box public: WfLightControl(WayfireLight *parent); + void set_scale_target_value(double value); // a double from 0 to 1 for min to max virtual void set_brightness(double brightness) = 0; virtual double get_brightness() = 0; @@ -85,11 +86,11 @@ class WayfireLight : public WayfireWidget { WfOption scroll_sensitivity{"panel/light_scroll_sensitivity"}; WfOption invert_scroll{"panel/light_invert_scroll"}; - void add_control(std::unique_ptr control); - void setup_sysfs(); void setup_ddc(); public: + void add_control(std::unique_ptr control); + void update_icon(); }; diff --git a/src/panel/widgets/light/sysfs.cpp b/src/panel/widgets/light/sysfs.cpp index bfb08e73..e05ad9fd 100644 --- a/src/panel/widgets/light/sysfs.cpp +++ b/src/panel/widgets/light/sysfs.cpp @@ -4,6 +4,10 @@ #include #include #include +extern "C"{ + #include +} +#include #include "light.hpp" @@ -45,52 +49,156 @@ bool is_in_file_group(const std::filesystem::path& file_path) { } } -void WayfireLight::setup_sysfs(){ - // look for integrated backlights - const auto path = "/sys/class/backlight"; - if (!std::filesystem::exists(path)){ - std::cout << "No backlight directory found for integrated screens, skipping.\n"; - return; - } +// singleton that monitors sysfs and calls the necessary functions +// monitors appearance and deletion of backlight devices +// and the brightness of each of them +class SysfsSurveillor { + private: + SysfsSurveillor(){ + fd = inotify_init(); + if (fd == -1){ + std::cerr << "Light widget: initialisation of inotify on sysfs failed.\n"; + return; + } + + // look for present integrated backlights + const auto path = "/sys/class/backlight"; + if (!std::filesystem::exists(path)){ + std::cout << "No backlight directory found for integrated screens, skipping.\n"; + return; + } + + for (const auto& entry : std::filesystem::directory_iterator(path)){ + add_dev(entry); + } + } + + static inline std::unique_ptr instance; + + int fd; // inotify file descriptor + + void handle_inotify_events(){ + // according to the inotify man page, aligning as such ensures + // proper function and avoid performance loss for "some systems" + char buf[2048] __attribute__((aligned(__alignof__(struct inotify_event)))); + const struct inotify_event *event; + ssize_t size; + + for (;;){ + size = read(fd, buf, sizeof(buf)); + if (size == -1 && errno != EAGAIN){ + std::cerr << "Light widget: error reading inotify event.\n"; + } + if (size <= 0) + break; + + for (char *ptr = buf ; ptr < buf + size ; ptr += sizeof(struct inotify_event) + event->len){ + event = (const struct inotify_event*) ptr; + + if (event->mask & IN_CLOSE_WRITE){ + // look for the watch descriptor + for (auto const& pair : wd_to_controls){ + if (pair.first == event->wd){ + // it changed, so refresh the value of the controls + for (auto const control : pair.second){ + control->set_scale_target_value(control->get_brightness()); + } + } + } + } + + if (event->mask & IN_CREATE){ - for (const auto& entry : std::filesystem::directory_iterator(path)){ - const std::filesystem::path b_path = entry.path().string() + "/brightness"; - const std::filesystem::path max_b_path = entry.path().string() + "/max_brightness"; + } + if (event->mask & IN_DELETE){ - if (!std::filesystem::exists(b_path)){ - std::cout << "No brightness found for " << entry.path().string() << ", ignoring.\n"; - break; + } + } + + } + } + + void add_dev(std::filesystem::path path){ + const std::filesystem::path b_path = path.string() + "/brightness"; + const std::filesystem::path max_b_path = path.string() + "/max_brightness"; + + if (!std::filesystem::exists(b_path)){ + std::cout << "No brightness found for " << path.string() << ", ignoring.\n"; + return; + } + if (!std::filesystem::exists(b_path)){ + std::cout << "No max_brightness found for " << path.string() << ", ignoring.\n"; + return; + } + + auto max_perms = std::filesystem::status(max_b_path).permissions(); + // can the file be read? + if (!((max_perms & std::filesystem::perms::others_read) != std::filesystem::perms::none + || (is_in_file_group(max_b_path) && (max_perms & std::filesystem::perms::group_read) != std::filesystem::perms::none) + )){ + std::cout << "Cannot read max_brightness file.\n"; + return; + } + + auto perms = std::filesystem::status(b_path).permissions(); + // can the file be read? + if (!((perms & std::filesystem::perms::others_read) != std::filesystem::perms::none + || (is_in_file_group(b_path) && (perms & std::filesystem::perms::group_read) != std::filesystem::perms::none) + )){ + std::cout << "Cannot read brightness file.\n"; + return; + } + // and written? + if (!((perms & std::filesystem::perms::others_write) != std::filesystem::perms::none + || (is_in_file_group(b_path) && (perms & std::filesystem::perms::group_write) != std::filesystem::perms::none) + )) + std::cout << "Can read backlight, but cannot write. Control will only display brightness.\n"; + + for (auto widget : widgets){ + widget->add_control(std::make_unique(widget, path)); + } + devices.push_back(path); + int wd = inotify_add_watch(fd, path.string().c_str(), IN_CLOSE_WRITE); + if (wd == -1){ + std::cerr << "Light widget: failed to register inotify watch descriptor.\n"; + return; + } + wd_to_controls.insert({wd, {}}); } - if (!std::filesystem::exists(b_path)){ - std::cout << "No max_brightness found for " << entry.path().string() << ", ignoring.\n"; - break; + + void catch_up_widget(WayfireLight* widget){ + for (auto device : devices){ + for (auto widget: widgets){ + widget->add_control(std::make_unique(widget, device)); + } + } } - auto max_perms = std::filesystem::status(max_b_path).permissions(); - // can the file be read? - if (!((max_perms & std::filesystem::perms::others_read) != std::filesystem::perms::none - || (is_in_file_group(max_b_path) && (max_perms & std::filesystem::perms::group_read) != std::filesystem::perms::none) - )){ - std::cout << "Cannot read max_brightness file.\n"; - continue; + std::vector devices; + std::vector widgets; + std::map> wd_to_controls; + + public: + + void add_widget(WayfireLight *widget){ + widgets.push_back(widget); + catch_up_widget(widget); } + void rem_widget(WayfireLight *widget){ - auto perms = std::filesystem::status(b_path).permissions(); - // can the file be read? - if (!((perms & std::filesystem::perms::others_read) != std::filesystem::perms::none - || (is_in_file_group(b_path) && (perms & std::filesystem::perms::group_read) != std::filesystem::perms::none) - )){ - std::cout << "Cannot read brightness file.\n"; - continue; } - // and written? - if (!((perms & std::filesystem::perms::others_write) != std::filesystem::perms::none - || (is_in_file_group(b_path) && (perms & std::filesystem::perms::group_write) != std::filesystem::perms::none) - )) - std::cout << "Can read backlight, but cannot write. Control will only display brightness.\n"; - add_control(std::make_unique(this, entry.path())); - } + static SysfsSurveillor& get(){ + if (!instance) + { + instance = std::unique_ptr(new SysfsSurveillor()); + } + return *instance; + } +}; + +void WayfireLight::setup_sysfs(){ + SysfsSurveillor::get().add_widget(this); } // the permissions have already been checked and *most likely* won’t have changed, so we just read/write From 140a35265132610d60becd1a3b7e32147e1af28b Mon Sep 17 00:00:00 2001 From: Hue Date: Wed, 21 Jan 2026 18:42:57 +0100 Subject: [PATCH 17/48] remove thing from when it was appended to the wireplumber branch --- meson.build | 1 - 1 file changed, 1 deletion(-) diff --git a/meson.build b/meson.build index e1b72f57..32ae8e4e 100644 --- a/meson.build +++ b/meson.build @@ -21,7 +21,6 @@ wfconfig = dependency('wf-config', version: '>=0.7.0') #TODO fallback submodule epoxy = dependency('epoxy') gtklayershell = dependency('gtk4-layer-shell-0', fallback: ['gtk4-layer-shell']) libpulse = dependency('libpulse', required: get_option('pulse')) -wireplumber = dependency('wireplumber-0.5', required: get_option('wireplumber')) ddcutil = dependency('libddcutil-5', required: get_option('ddcutil')) dbusmenu_gtk = dependency('dbusmenu-glib-0.4') libgvc = subproject('gvc', default_options: ['static=true'], required: get_option('pulse')) From c4bd2bf31453a25e8fa313f5e08172a6d4ffaea7 Mon Sep 17 00:00:00 2001 From: Hue Date: Wed, 21 Jan 2026 19:06:43 +0100 Subject: [PATCH 18/48] this had no buisness being here --- src/panel/widgets/light/light.hpp | 22 ------- src/panel/widgets/light/sysfs.cpp | 100 ++++++++++++++++-------------- 2 files changed, 53 insertions(+), 69 deletions(-) diff --git a/src/panel/widgets/light/light.hpp b/src/panel/widgets/light/light.hpp index 9538fef8..4fa5cd9d 100644 --- a/src/panel/widgets/light/light.hpp +++ b/src/panel/widgets/light/light.hpp @@ -49,28 +49,6 @@ class WfLightControl : public Gtk::Box }; -#ifdef HAVE_DDCUTIL -class WfLightDdcControl : public WfLightControl -{ - protected: - DDCA_Display_Handle dh; -}; -#endif - -class WfLightSysfsControl: public WfLightControl -{ - protected: - std::string path; - int get_max(); - - std::string get_name(); - - public: - WfLightSysfsControl(WayfireLight *parent, std::string path); - void set_brightness(double brightness); - double get_brightness(); -}; - class WayfireLight : public WayfireWidget { private: void init(Gtk::Box *container) override; diff --git a/src/panel/widgets/light/sysfs.cpp b/src/panel/widgets/light/sysfs.cpp index e05ad9fd..6e69aad8 100644 --- a/src/panel/widgets/light/sysfs.cpp +++ b/src/panel/widgets/light/sysfs.cpp @@ -203,57 +203,63 @@ void WayfireLight::setup_sysfs(){ // the permissions have already been checked and *most likely* won’t have changed, so we just read/write -WfLightSysfsControl::WfLightSysfsControl(WayfireLight *parent, std::string _path) : WfLightControl(parent){ - path = _path; +class WfLightSysfsControl: public WfLightControl +{ + protected: + std::string path; + + int get_max(){ + std::ifstream max_file(path + "/max_brightness"); + if (!max_file.is_open()){ + std::cerr << "Failed to get max brightness for device at " << path << '\n'; + return 0; + } + int max; + max_file >> max; + max_file.close(); + return max; + } - scale.set_target_value(get_brightness()); + std::string get_name(){ + std::string name; + name = "Integrated display"; + return name; + } - label.set_text(get_name()); + public: + WfLightSysfsControl(WayfireLight *parent, std::string _path) : WfLightControl(parent){ + path = _path; - icons = brightness_display_icons; -} + scale.set_target_value(get_brightness()); + label.set_text(get_name()); -std::string WfLightSysfsControl::get_name(){ - std::string name; - name = "Integrated display"; - return name; -} - -int WfLightSysfsControl::get_max(){ - std::ifstream max_file(path + "/max_brightness"); - if (!max_file.is_open()){ - std::cerr << "Failed to get max brightness for device at " << path << '\n'; - return 0; - } - int max; - max_file >> max; - max_file.close(); - return max; -} + icons = brightness_display_icons; + } -void WfLightSysfsControl::set_brightness(double brightness){ - std::ofstream b_file(path + "/brightness"); - if (!b_file.is_open()){ - std::cerr << "Failed to open brightness for device at " << path << '\n'; - return; - } - // something of the sort avoids formatting issues with locales - b_file << std::to_string((int)(brightness * (double)get_max())); - if (b_file.fail()){ - std::cerr << "Failed to write brightness for device at " << path << '\n'; - } + void set_brightness(double brightness){ + std::ofstream b_file(path + "/brightness"); + if (!b_file.is_open()){ + std::cerr << "Failed to open brightness for device at " << path << '\n'; + return; + } + // something of the sort avoids formatting issues with locales + b_file << std::to_string((int)(brightness * (double)get_max())); + if (b_file.fail()){ + std::cerr << "Failed to write brightness for device at " << path << '\n'; + } + } -} + double get_brightness(){ + std::ifstream b_file(path + "/brightness"); + if (!b_file.is_open()){ + std::cerr << "Failed to get brightness for device at " << path << '\n'; + return 0; + } -double WfLightSysfsControl::get_brightness(){ - std::ifstream b_file(path + "/brightness"); - if (!b_file.is_open()){ - std::cerr << "Failed to get brightness for device at " << path << '\n'; - return 0; - } - int brightness, max; - b_file >> brightness; - b_file.close(); - max = get_max(); - return (((double)brightness + (double)max) / (double)max) - 1; -} + int brightness, max; + b_file >> brightness; + b_file.close(); + max = get_max(); + return (((double)brightness + (double)max) / (double)max) - 1; + } +}; From b878e16e4c8bf3395eb35e46e49e7dd59eabdc20 Mon Sep 17 00:00:00 2001 From: Hue Date: Thu, 22 Jan 2026 18:40:39 +0100 Subject: [PATCH 19/48] =?UTF-8?q?let=E2=80=99s=20try=20that=20again=20oops?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/util/meson.build | 1 + 1 file changed, 1 insertion(+) diff --git a/src/util/meson.build b/src/util/meson.build index 5621206e..117a7174 100644 --- a/src/util/meson.build +++ b/src/util/meson.build @@ -1,6 +1,7 @@ util = static_library( 'util', [ + 'animated-scale.cpp', 'gtk-utils.cpp', 'wf-shell-app.cpp', 'wf-autohide-window.cpp', From c3e4d1559afc95a33574c599b508b829815517b1 Mon Sep 17 00:00:00 2001 From: Hue Date: Thu, 22 Jan 2026 18:40:52 +0100 Subject: [PATCH 20/48] works better this way around --- src/panel/widgets/light/sysfs.cpp | 127 +++++++++++++++--------------- 1 file changed, 64 insertions(+), 63 deletions(-) diff --git a/src/panel/widgets/light/sysfs.cpp b/src/panel/widgets/light/sysfs.cpp index 6e69aad8..c23ab460 100644 --- a/src/panel/widgets/light/sysfs.cpp +++ b/src/panel/widgets/light/sysfs.cpp @@ -11,6 +11,70 @@ extern "C"{ #include "light.hpp" +class WfLightSysfsControl: public WfLightControl +{ + protected: + std::string path; + + int get_max(){ + std::ifstream max_file(path + "/max_brightness"); + if (!max_file.is_open()){ + std::cerr << "Failed to get max brightness for device at " << path << '\n'; + return 0; + } + int max; + max_file >> max; + max_file.close(); + return max; + } + + std::string get_name(){ + std::string name; + name = "Integrated display"; + return name; + } + + public: + WfLightSysfsControl(WayfireLight *parent, std::string _path) : WfLightControl(parent){ + path = _path; + + scale.set_target_value(get_brightness()); + label.set_text(get_name()); + + icons = brightness_display_icons; + } + + // the permissions have already been checked and *most likely* won’t have changed, so we just read/write + + void set_brightness(double brightness){ + std::ofstream b_file(path + "/brightness"); + if (!b_file.is_open()){ + std::cerr << "Failed to open brightness for device at " << path << '\n'; + return; + } + // something of the sort avoids formatting issues with locales + b_file << std::to_string((int)(brightness * (double)get_max())); + if (b_file.fail()){ + std::cerr << "Failed to write brightness for device at " << path << '\n'; + } + } + + double get_brightness(){ + std::ifstream b_file(path + "/brightness"); + if (!b_file.is_open()){ + std::cerr << "Failed to get brightness for device at " << path << '\n'; + return 0; + } + + int brightness, max; + b_file >> brightness; + b_file.close(); + max = get_max(); + return (((double)brightness + (double)max) / (double)max) - 1; + } +}; + +// utilities to check permissions bool is_group_member(gid_t file_group_id) { gid_t current_group = getgid(); gid_t supplementary_groups[NGROUPS_MAX]; @@ -200,66 +264,3 @@ class SysfsSurveillor { void WayfireLight::setup_sysfs(){ SysfsSurveillor::get().add_widget(this); } - -// the permissions have already been checked and *most likely* won’t have changed, so we just read/write - -class WfLightSysfsControl: public WfLightControl -{ - protected: - std::string path; - - int get_max(){ - std::ifstream max_file(path + "/max_brightness"); - if (!max_file.is_open()){ - std::cerr << "Failed to get max brightness for device at " << path << '\n'; - return 0; - } - int max; - max_file >> max; - max_file.close(); - return max; - } - - std::string get_name(){ - std::string name; - name = "Integrated display"; - return name; - } - - public: - WfLightSysfsControl(WayfireLight *parent, std::string _path) : WfLightControl(parent){ - path = _path; - - scale.set_target_value(get_brightness()); - label.set_text(get_name()); - - icons = brightness_display_icons; - } - - void set_brightness(double brightness){ - std::ofstream b_file(path + "/brightness"); - if (!b_file.is_open()){ - std::cerr << "Failed to open brightness for device at " << path << '\n'; - return; - } - // something of the sort avoids formatting issues with locales - b_file << std::to_string((int)(brightness * (double)get_max())); - if (b_file.fail()){ - std::cerr << "Failed to write brightness for device at " << path << '\n'; - } - } - - double get_brightness(){ - std::ifstream b_file(path + "/brightness"); - if (!b_file.is_open()){ - std::cerr << "Failed to get brightness for device at " << path << '\n'; - return 0; - } - - int brightness, max; - b_file >> brightness; - b_file.close(); - max = get_max(); - return (((double)brightness + (double)max) / (double)max) - 1; - } -}; From e7998fafdbebf2126cef6a811d21e2e582c6a683 Mon Sep 17 00:00:00 2001 From: Hue Date: Fri, 23 Jan 2026 00:49:56 +0100 Subject: [PATCH 21/48] tweaks and fixes but not there yet --- metadata/panel.xml | 2 +- src/panel/widgets/light/light.cpp | 4 ++-- src/panel/widgets/light/light.hpp | 6 ++--- src/panel/widgets/light/sysfs.cpp | 39 +++++++++++++++++++------------ 4 files changed, 30 insertions(+), 21 deletions(-) diff --git a/metadata/panel.xml b/metadata/panel.xml index 47f93d9f..c47a85ce 100644 --- a/metadata/panel.xml +++ b/metadata/panel.xml @@ -500,7 +500,7 @@ Set to -1 to only run it by clicking the button. diff --git a/src/panel/widgets/light/light.cpp b/src/panel/widgets/light/light.cpp index 247275ac..80436f41 100644 --- a/src/panel/widgets/light/light.cpp +++ b/src/panel/widgets/light/light.cpp @@ -88,9 +88,9 @@ void WayfireLight::init(Gtk::Box *container){ update_icon(); } -void WayfireLight::add_control(std::unique_ptr control){ +void WayfireLight::add_control(WfLightControl *control){ box.append(*control); - controls.push_back(std::move(control)); + controls.push_back(control); } void WayfireLight::update_icon(){ diff --git a/src/panel/widgets/light/light.hpp b/src/panel/widgets/light/light.hpp index 4fa5cd9d..47418770 100644 --- a/src/panel/widgets/light/light.hpp +++ b/src/panel/widgets/light/light.hpp @@ -58,17 +58,17 @@ class WayfireLight : public WayfireWidget { Gtk::Popover *popover; Gtk::Box box; - std::vector> controls; + std::vector controls; WfOption icon_target{"panel/light_icon_target"}; WfOption scroll_sensitivity{"panel/light_scroll_sensitivity"}; - WfOption invert_scroll{"panel/light_invert_scroll"}; + WfOption invert_scroll{"panel/light_invert_scroll"}; void setup_sysfs(); void setup_ddc(); public: - void add_control(std::unique_ptr control); + void add_control(WfLightControl *control); void update_icon(); }; diff --git a/src/panel/widgets/light/sysfs.cpp b/src/panel/widgets/light/sysfs.cpp index c23ab460..186f6e7e 100644 --- a/src/panel/widgets/light/sysfs.cpp +++ b/src/panel/widgets/light/sysfs.cpp @@ -1,9 +1,10 @@ #include #include #include -#include #include +#include #include +#include extern "C"{ #include } @@ -135,6 +136,8 @@ class SysfsSurveillor { for (const auto& entry : std::filesystem::directory_iterator(path)){ add_dev(entry); } + + inotify_thread = std::thread(&SysfsSurveillor::handle_inotify_events, this); } static inline std::unique_ptr instance; @@ -161,10 +164,10 @@ class SysfsSurveillor { if (event->mask & IN_CLOSE_WRITE){ // look for the watch descriptor - for (auto const& pair : wd_to_controls){ - if (pair.first == event->wd){ + for (auto & pair : path_wd_to_controls){ + if (pair.first.second == event->wd){ // it changed, so refresh the value of the controls - for (auto const control : pair.second){ + for (auto control : pair.second){ control->set_scale_target_value(control->get_brightness()); } } @@ -218,29 +221,35 @@ class SysfsSurveillor { )) std::cout << "Can read backlight, but cannot write. Control will only display brightness.\n"; - for (auto widget : widgets){ - widget->add_control(std::make_unique(widget, path)); - } - devices.push_back(path); int wd = inotify_add_watch(fd, path.string().c_str(), IN_CLOSE_WRITE); if (wd == -1){ std::cerr << "Light widget: failed to register inotify watch descriptor.\n"; return; } - wd_to_controls.insert({wd, {}}); + + std::pair path_wd = {path, wd}; + path_wd_to_controls.insert({path_wd, {}}); + + for (auto widget : widgets) + { + auto control = new WfLightSysfsControl(widget, path); + path_wd_to_controls[path_wd].push_back(control); + widget->add_control(control); + } } void catch_up_widget(WayfireLight* widget){ - for (auto device : devices){ - for (auto widget: widgets){ - widget->add_control(std::make_unique(widget, device)); - } + for (auto it : path_wd_to_controls){ + auto control = new WfLightSysfsControl(widget, it.first.first.string()); + it.second.push_back(control); + widget->add_control(control); } } - std::vector devices; + // std::vector devices; + std::map, std::vector> path_wd_to_controls; std::vector widgets; - std::map> wd_to_controls; + std::thread inotify_thread; public: From ea1d613a378ef1202b356d3ed57421c69d8dd7a7 Mon Sep 17 00:00:00 2001 From: Hue Date: Fri, 23 Jan 2026 09:47:59 +0100 Subject: [PATCH 22/48] actually grab the reference --- src/panel/widgets/light/sysfs.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/panel/widgets/light/sysfs.cpp b/src/panel/widgets/light/sysfs.cpp index 186f6e7e..e2383a9e 100644 --- a/src/panel/widgets/light/sysfs.cpp +++ b/src/panel/widgets/light/sysfs.cpp @@ -239,14 +239,13 @@ class SysfsSurveillor { } void catch_up_widget(WayfireLight* widget){ - for (auto it : path_wd_to_controls){ + for (auto& it : path_wd_to_controls){ auto control = new WfLightSysfsControl(widget, it.first.first.string()); it.second.push_back(control); widget->add_control(control); } } - // std::vector devices; std::map, std::vector> path_wd_to_controls; std::vector widgets; std::thread inotify_thread; From 605b49a01d8ed66952c294d9e5989dd37afe79b1 Mon Sep 17 00:00:00 2001 From: Hue Date: Fri, 23 Jan 2026 16:16:37 +0100 Subject: [PATCH 23/48] =?UTF-8?q?hi=20soreau=20the=20code=20is=20the=20wor?= =?UTF-8?q?st=20mess=20rn=20but=20it=20works=20ty=C2=A0!?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/panel/widgets/light/light.cpp | 18 ++++++- src/panel/widgets/light/light.hpp | 6 +-- src/panel/widgets/light/sysfs.cpp | 85 +++++++++++++++++++++++++------ 3 files changed, 88 insertions(+), 21 deletions(-) diff --git a/src/panel/widgets/light/light.cpp b/src/panel/widgets/light/light.cpp index 80436f41..18d763dc 100644 --- a/src/panel/widgets/light/light.cpp +++ b/src/panel/widgets/light/light.cpp @@ -1,6 +1,9 @@ +#include +#include +#include + #include "light.hpp" #include "wf-popover.hpp" -#include static BrightnessLevel light_icon_for(double value) { @@ -83,13 +86,24 @@ void WayfireLight::init(Gtk::Box *container){ container->append(*button); + box.append(display_ctrl); + setup_sysfs(); update_icon(); } void WayfireLight::add_control(WfLightControl *control){ - box.append(*control); + auto monitors = button->get_display()->get_monitors(); + Glib::RefPtr monitor = monitors->get_typed_object(0); + auto connector = monitor->get_connector(); + if (control->get_name() == connector) + { + display_ctrl.append(*control); + } else + { + box.append(*control); + } controls.push_back(control); } diff --git a/src/panel/widgets/light/light.hpp b/src/panel/widgets/light/light.hpp index 47418770..81017248 100644 --- a/src/panel/widgets/light/light.hpp +++ b/src/panel/widgets/light/light.hpp @@ -37,11 +37,11 @@ class WfLightControl : public Gtk::Box std::map icons; WayfireLight *parent; - virtual std::string get_name() = 0; - public: WfLightControl(WayfireLight *parent); + virtual std::string get_name() = 0; + void set_scale_target_value(double value); // a double from 0 to 1 for min to max virtual void set_brightness(double brightness) = 0; @@ -56,7 +56,7 @@ class WayfireLight : public WayfireWidget { Gtk::Image icon; std::unique_ptr button; Gtk::Popover *popover; - Gtk::Box box; + Gtk::Box box, display_ctrl, other_ctrl; std::vector controls; diff --git a/src/panel/widgets/light/sysfs.cpp b/src/panel/widgets/light/sysfs.cpp index e2383a9e..10788b3c 100644 --- a/src/panel/widgets/light/sysfs.cpp +++ b/src/panel/widgets/light/sysfs.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include extern "C"{ @@ -15,7 +16,7 @@ extern "C"{ class WfLightSysfsControl: public WfLightControl { protected: - std::string path; + std::string path, connector_name; int get_max(){ std::ifstream max_file(path + "/max_brightness"); @@ -23,29 +24,65 @@ class WfLightSysfsControl: public WfLightControl std::cerr << "Failed to get max brightness for device at " << path << '\n'; return 0; } + int max; max_file >> max; max_file.close(); return max; } - std::string get_name(){ - std::string name; - name = "Integrated display"; - return name; - } - public: WfLightSysfsControl(WayfireLight *parent, std::string _path) : WfLightControl(parent){ path = _path; + std::string realpath = std::filesystem::canonical(path); + // this resolves to something of the sort : + // /sys/devices/pciXXXX:XX/XXXX:XX:XX.X/XXXX:XX:XX.X/drm/cardX-/ + // what we are intersted in here is the connector name + std::regex pattern(R"(/card\d+-([^/]+)/)"); + std::smatch match; + + if (std::regex_search(realpath, match, pattern)){ + connector_name = match[1].str(); + } else // we failed :( + { + connector_name = ""; + } + scale.set_target_value(get_brightness()); label.set_text(get_name()); icons = brightness_display_icons; } - // the permissions have already been checked and *most likely* won’t have changed, so we just read/write + std::string get_name(){ + return connector_name; + // std::regex pattern(R"(/card\d+-([^/]+)/)"); + // std::smatch match; + + // if (std::regex_search(realpath, match, pattern)){ + // return match[1].str(); + // } + + // return ""; + // std::string_view prefix = "card0-"; + // auto pos = path.rfind(prefix); + + // if (pos == std::string_view::npos){ + // return ""; + // } + + // pos += prefix.length(); + // auto end = path.find("/", pos); + + // if (end == std::string_view::npos){ + // end = path.length(); + // } + + // return path.substr(pos, end - pos); + } + + // the permissions have already been checked and are being monitored, so we just read/write void set_brightness(double brightness){ std::ofstream b_file(path + "/brightness"); @@ -76,7 +113,8 @@ class WfLightSysfsControl: public WfLightControl }; // utilities to check permissions -bool is_group_member(gid_t file_group_id) { +bool is_group_member(gid_t file_group_id) +{ gid_t current_group = getgid(); gid_t supplementary_groups[NGROUPS_MAX]; int n_groups = getgroups(NGROUPS_MAX, supplementary_groups); @@ -84,7 +122,8 @@ bool is_group_member(gid_t file_group_id) { if (current_group == file_group_id) return true; - for (int i = 0; i < n_groups; ++i) { + for (int i = 0; i < n_groups; ++i) + { if (supplementary_groups[i] == file_group_id) return true; } @@ -93,23 +132,28 @@ bool is_group_member(gid_t file_group_id) { } // let’s assume the file is not owned by the user and only bother with groups -bool is_in_file_group(const std::filesystem::path& file_path) { +bool is_in_file_group(const std::filesystem::path& file_path) +{ struct stat file_info; - if (stat(file_path.c_str(), &file_info) != 0) { + if (stat(file_path.c_str(), &file_info) != 0) + { std::cerr << "Failed to stat " << file_path << ".\n"; return false; } struct group *file_group = getgrgid(file_info.st_gid); - if (!file_group) { + if (!file_group) + { std::cerr << "Failed to fetch owner/group info for " << file_path << ".\n"; return false; } - if (is_group_member(file_info.st_gid)) { + if (is_group_member(file_info.st_gid)) + { return true; - } else { + } else + { return false; } } @@ -146,7 +190,7 @@ class SysfsSurveillor { void handle_inotify_events(){ // according to the inotify man page, aligning as such ensures - // proper function and avoid performance loss for "some systems" + // proper function and avoids performance loss for "some systems" char buf[2048] __attribute__((aligned(__alignof__(struct inotify_event)))); const struct inotify_event *event; ssize_t size; @@ -162,6 +206,7 @@ class SysfsSurveillor { for (char *ptr = buf ; ptr < buf + size ; ptr += sizeof(struct inotify_event) + event->len){ event = (const struct inotify_event*) ptr; + // a registered brightness file was changed if (event->mask & IN_CLOSE_WRITE){ // look for the watch descriptor for (auto & pair : path_wd_to_controls){ @@ -174,12 +219,20 @@ class SysfsSurveillor { } } + // a backlight device appeared if (event->mask & IN_CREATE){ } + + // a backlight device was removed if (event->mask & IN_DELETE){ } + + // metadata changed, so maybe permissions + if (event->mask & IN_ATTRIB){ + + } } } From db492a68185a03182c071fd07b1587b148b6626a Mon Sep 17 00:00:00 2001 From: Hue Date: Fri, 23 Jan 2026 16:54:02 +0100 Subject: [PATCH 24/48] basic layout, various cleanups --- metadata/panel.xml | 19 ----------- src/panel/widgets/light/light.cpp | 54 +++++++++++++------------------ src/panel/widgets/light/light.hpp | 10 +++--- src/panel/widgets/light/sysfs.cpp | 30 ++++------------- 4 files changed, 33 insertions(+), 80 deletions(-) diff --git a/metadata/panel.xml b/metadata/panel.xml index c47a85ce..6e1bc1fe 100644 --- a/metadata/panel.xml +++ b/metadata/panel.xml @@ -479,25 +479,6 @@ Set to -1 to only run it by clicking the button. Inverts which scroll direction raises and lowers display brightness false - -