fix(backlight): also match the leds subsystem for keyboard backlights

The backlight module only enumerated and monitored the udev "backlight"
subsystem, so keyboard-backlight LEDs in the "leds" class (e.g.
white:kbd_backlight, platform::kbd_backlight) were never discovered and
the module fell back to the default when pointed at one.

Enumerate and monitor the "leds" subsystem in addition to "backlight".
Those LEDs expose the same brightness/max_brightness attributes, so the
read path is unchanged. Each device now records its subsystem so the
login1 SetBrightness call targets the correct one. Automatic device
selection still prefers a "backlight" device and only falls back to a
"leds" device when named explicitly or when no screen backlight exists.

Fixes #2848.
This commit is contained in:
Alex
2026-07-04 13:43:01 +02:00
parent 4c55819888
commit 8ff8ceeca2
4 changed files with 50 additions and 14 deletions
+6 -2
View File
@@ -27,9 +27,11 @@ namespace waybar::util {
class BacklightDevice { class BacklightDevice {
public: public:
BacklightDevice() = default; BacklightDevice() = default;
BacklightDevice(std::string name, int actual, int max, bool powered); BacklightDevice(std::string name, int actual, int max, bool powered,
std::string subsystem = "backlight");
std::string name() const; std::string name() const;
std::string subsystem() const;
int get_actual() const; int get_actual() const;
void set_actual(int actual); void set_actual(int actual);
int get_max() const; int get_max() const;
@@ -45,6 +47,7 @@ class BacklightDevice {
int actual_ = 1; int actual_ = 1;
int max_ = 1; int max_ = 1;
bool powered_ = true; bool powered_ = true;
std::string subsystem_ = "backlight";
}; };
class BacklightBackend { class BacklightBackend {
@@ -70,7 +73,8 @@ class BacklightBackend {
std::mutex udev_thread_mutex_; std::mutex udev_thread_mutex_;
private: private:
void set_brightness_internal(const std::string& device_name, int brightness, int max_brightness); void set_brightness_internal(const std::string& device_name, int brightness, int max_brightness,
const std::string& subsystem = "backlight");
std::function<void()> on_updated_cb_; std::function<void()> on_updated_cb_;
std::chrono::milliseconds polling_interval_; std::chrono::milliseconds polling_interval_;
+2 -1
View File
@@ -29,7 +29,8 @@ The brightness can be controlled by dragging the slider across the bar or clicki
*device*: ++ *device*: ++
typeof: string ++ typeof: string ++
The name of the preferred device to control. If left empty, a device will be chosen automatically. The name of the preferred device to control. If left empty, a device will be chosen automatically. ++
Both screen backlights (the udev *backlight* subsystem) and keyboard backlights (LEDs in the udev *leds* subsystem, e.g. *white:kbd_backlight*) are supported; name such an LED here to control it. When left empty, a screen backlight is always preferred for automatic selection.
*interval*: ++ *interval*: ++
typeof: uint ++ typeof: uint ++
+2 -1
View File
@@ -17,7 +17,8 @@ The *backlight* module displays the current backlight level.
*device*: ++ *device*: ++
typeof: string ++ typeof: string ++
The name of the preferred backlight device to display. If left empty, a device will be chosen automatically. The name of the preferred backlight device to display. If left empty, a device will be chosen automatically. ++
Both screen backlights (the udev *backlight* subsystem) and keyboard backlights (LEDs in the udev *leds* subsystem, e.g. *white:kbd_backlight*) are supported; name such an LED here to control it. When left empty, a screen backlight is always preferred for automatic selection.
*format*: ++ *format*: ++
typeof: string ++ typeof: string ++
+40 -10
View File
@@ -73,6 +73,7 @@ static void upsert_device(std::vector<BacklightDevice>& devices, udev_device* de
const char* actual = udev_device_get_sysattr_value(dev, actual_brightness_attr); 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* max = udev_device_get_sysattr_value(dev, "max_brightness");
const char* power = udev_device_get_sysattr_value(dev, "bl_power"); const char* power = udev_device_get_sysattr_value(dev, "bl_power");
const char* subsystem = udev_device_get_subsystem(dev);
auto found = std::find_if(devices.begin(), devices.end(), [name](const BacklightDevice& device) { auto found = std::find_if(devices.begin(), devices.end(), [name](const BacklightDevice& device) {
return device.name() == name; return device.name() == name;
@@ -111,13 +112,18 @@ static void upsert_device(std::vector<BacklightDevice>& devices, udev_device* de
if (power != nullptr) power_bool = std::stoi(power) == 0; if (power != nullptr) power_bool = std::stoi(power) == 0;
} catch (const std::exception&) { } catch (const std::exception&) {
} }
devices.emplace_back(name, actual_int, max_int, power_bool); devices.emplace_back(name, actual_int, max_int, power_bool,
subsystem != nullptr ? subsystem : "backlight");
} }
} }
static void enumerate_devices(std::vector<BacklightDevice>& devices, udev* udev) { static void enumerate_devices(std::vector<BacklightDevice>& devices, udev* udev) {
std::unique_ptr<udev_enumerate, UdevEnumerateDeleter> enumerate{udev_enumerate_new(udev)}; std::unique_ptr<udev_enumerate, UdevEnumerateDeleter> enumerate{udev_enumerate_new(udev)};
udev_enumerate_add_match_subsystem(enumerate.get(), "backlight"); udev_enumerate_add_match_subsystem(enumerate.get(), "backlight");
// Also enumerate keyboard-backlight LEDs (e.g. "white:kbd_backlight"), which
// live in the "leds" subsystem but expose the same brightness/max_brightness
// attributes the read path uses.
udev_enumerate_add_match_subsystem(enumerate.get(), "leds");
udev_enumerate_scan_devices(enumerate.get()); udev_enumerate_scan_devices(enumerate.get());
udev_list_entry* enum_devices = udev_enumerate_get_list_entry(enumerate.get()); udev_list_entry* enum_devices = udev_enumerate_get_list_entry(enumerate.get());
udev_list_entry* dev_list_entry; udev_list_entry* dev_list_entry;
@@ -129,11 +135,18 @@ static void enumerate_devices(std::vector<BacklightDevice>& devices, udev* udev)
} }
} }
BacklightDevice::BacklightDevice(std::string name, int actual, int max, bool powered) BacklightDevice::BacklightDevice(std::string name, int actual, int max, bool powered,
: name_(std::move(name)), actual_(actual), max_(max), powered_(powered) {} std::string subsystem)
: name_(std::move(name)),
actual_(actual),
max_(max),
powered_(powered),
subsystem_(std::move(subsystem)) {}
std::string BacklightDevice::name() const { return name_; } std::string BacklightDevice::name() const { return name_; }
std::string BacklightDevice::subsystem() const { return subsystem_; }
int BacklightDevice::get_actual() const { return actual_; } int BacklightDevice::get_actual() const { return actual_; }
void BacklightDevice::set_actual(int actual) { actual_ = actual; } void BacklightDevice::set_actual(int actual) { actual_ = actual; }
@@ -178,6 +191,10 @@ BacklightBackend::BacklightBackend(std::chrono::milliseconds interval,
check_nn(mon.get(), "udev monitor new failed"); check_nn(mon.get(), "udev monitor new failed");
check_gte(udev_monitor_filter_add_match_subsystem_devtype(mon.get(), "backlight", nullptr), 0, check_gte(udev_monitor_filter_add_match_subsystem_devtype(mon.get(), "backlight", nullptr), 0,
"udev failed to add monitor filter: "); "udev failed to add monitor filter: ");
// Also monitor the "leds" subsystem so keyboard-backlight changes are
// reflected live, mirroring the enumeration above.
check_gte(udev_monitor_filter_add_match_subsystem_devtype(mon.get(), "leds", nullptr), 0,
"udev failed to add monitor filter: ");
udev_monitor_enable_receiving(mon.get()); udev_monitor_enable_receiving(mon.get());
auto udev_fd = udev_monitor_get_fd(mon.get()); auto udev_fd = udev_monitor_get_fd(mon.get());
@@ -235,9 +252,22 @@ const BacklightDevice* BacklightBackend::best_device(const std::vector<Backlight
return &(*found); return &(*found);
} }
const auto max = std::max_element( // No device was explicitly configured (or the configured name did not match).
devices.begin(), devices.end(), // Automatic selection must keep preferring a screen backlight (the "backlight"
[](const BacklightDevice& l, const BacklightDevice& r) { return l.get_max() < r.get_max(); }); // subsystem) so that a keyboard-backlight LED, now that the "leds" subsystem is
// also enumerated, is never accidentally picked as the default. Only fall back
// to other subsystems (e.g. "leds") when no "backlight" device exists at all.
const auto max = std::max_element(devices.begin(), devices.end(),
[](const BacklightDevice& l, const BacklightDevice& r) {
const bool l_backlight = l.subsystem() == "backlight";
const bool r_backlight = r.subsystem() == "backlight";
if (l_backlight != r_backlight) {
// Rank any non-backlight device below every backlight
// device.
return r_backlight;
}
return l.get_max() < r.get_max();
});
return max == devices.end() ? nullptr : &(*max); return max == devices.end() ? nullptr : &(*max);
} }
@@ -260,7 +290,7 @@ void BacklightBackend::set_scaled_brightness(const std::string& preferred_device
if (best != nullptr) { if (best != nullptr) {
const auto max = best->get_max(); const auto max = best->get_max();
const auto abs_val = static_cast<int>(std::round(brightness * max / 100.0F)); const auto abs_val = static_cast<int>(std::round(brightness * max / 100.0F));
set_brightness_internal(best->name(), abs_val, best->get_max()); set_brightness_internal(best->name(), abs_val, best->get_max(), best->subsystem());
} }
} }
@@ -275,12 +305,12 @@ void BacklightBackend::set_brightness(const std::string& preferred_device, Chang
const int new_brightness = change_type == ChangeType::Increase ? best->get_actual() + abs_step const int new_brightness = change_type == ChangeType::Increase ? best->get_actual() + abs_step
: best->get_actual() - abs_step; : best->get_actual() - abs_step;
set_brightness_internal(best->name(), new_brightness, max); set_brightness_internal(best->name(), new_brightness, max, best->subsystem());
} }
} }
void BacklightBackend::set_brightness_internal(const std::string& device_name, int brightness, void BacklightBackend::set_brightness_internal(const std::string& device_name, int brightness,
int max_brightness) { int max_brightness, const std::string& subsystem) {
if (!login_proxy_) { if (!login_proxy_) {
spdlog::error("Login proxy not available, cannot set brightness"); spdlog::error("Login proxy not available, cannot set brightness");
return; return;
@@ -289,7 +319,7 @@ void BacklightBackend::set_brightness_internal(const std::string& device_name, i
brightness = std::clamp(brightness, 0, max_brightness); brightness = std::clamp(brightness, 0, max_brightness);
auto call_args = Glib::VariantContainerBase( auto call_args = Glib::VariantContainerBase(
g_variant_new("(ssu)", "backlight", device_name.c_str(), brightness)); g_variant_new("(ssu)", subsystem.c_str(), device_name.c_str(), brightness));
login_proxy_->call_sync("SetBrightness", call_args); login_proxy_->call_sync("SetBrightness", call_args);
} }