fix(brightness): use actual_brightness and prefer internal panel device
Switch from reading `brightness` (software-requested) to `actual_brightness` (hardware-reported), which is the correct file on AMD GPU backlights where the two values can diverge. Replace the first-found device selection with a scored preference so that when multiple backlight devices are present (e.g. amdgpu_bl2 and nvidia_0) the internal laptop panel is chosen rather than a dGPU output that may report a fixed ceiling value. Priority: amdgpu/intel (3) > acpi/platform (2) > unknown (1) > nvidia (0).
This commit is contained in:
+47
-14
@@ -3,19 +3,21 @@
|
||||
|
||||
//! Screen brightness monitor.
|
||||
//!
|
||||
//! Reads from `/sys/class/backlight/<device>/brightness` and
|
||||
//! Reads from `/sys/class/backlight/<device>/actual_brightness` and
|
||||
//! `/sys/class/backlight/<device>/max_brightness`, polling every 2 seconds
|
||||
//! and sending `CMD_BRIGHTNESS` packets to all keyboards when the value
|
||||
//! changes.
|
||||
//!
|
||||
//! On a KDE6 / Wayland desktop the backlight sysfs path is typically one of:
|
||||
//! - `/sys/class/backlight/intel_backlight`
|
||||
//! - `/sys/class/backlight/amdgpu_bl0`
|
||||
//! - `/sys/class/backlight/acpi_video0`
|
||||
//! `actual_brightness` is preferred over `brightness` because it reflects the
|
||||
//! real hardware state; on AMD GPU panels these can diverge.
|
||||
//!
|
||||
//! When multiple backlight devices are present (e.g. `amdgpu_bl2` and
|
||||
//! `nvidia_0`), the device most likely to represent the internal laptop panel
|
||||
//! is selected automatically (amdgpu/intel > acpi/platform > other > nvidia).
|
||||
//!
|
||||
//! # Future improvement
|
||||
//! Replace polling with an inotify watch on the `brightness` file so changes
|
||||
//! are reflected immediately.
|
||||
//! Replace polling with an inotify watch on the `actual_brightness` file so
|
||||
//! changes are reflected immediately.
|
||||
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
@@ -65,23 +67,54 @@ pub fn monitor(keyboards: Vec<Keyboard>) {
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Return the sysfs path to the first available backlight device.
|
||||
/// Preference score for a backlight device name (higher = more preferred).
|
||||
///
|
||||
/// Internal GPU panels (amdgpu, intel) are preferred over discrete GPU
|
||||
/// outputs (nvidia), which often report a fixed ceiling value rather than the
|
||||
/// actual adjustable panel brightness.
|
||||
fn backlight_score(name: &str) -> u8 {
|
||||
if name.starts_with("amdgpu_") || name.starts_with("intel_") {
|
||||
3
|
||||
} else if name.starts_with("acpi_") || name.starts_with("platform_") {
|
||||
2
|
||||
} else if name.starts_with("nvidia_") {
|
||||
0
|
||||
} else {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the sysfs path to the best available backlight device.
|
||||
///
|
||||
/// Scores each candidate by name prefix and returns the highest-scoring one.
|
||||
/// Requires both `actual_brightness` and `max_brightness` to be present.
|
||||
fn find_backlight() -> Option<PathBuf> {
|
||||
let read_dir = fs::read_dir("/sys/class/backlight").ok()?;
|
||||
|
||||
let mut best: Option<(u8, PathBuf)> = None;
|
||||
|
||||
for entry in read_dir.flatten() {
|
||||
let path = entry.path();
|
||||
// Verify the required files exist before committing to this device.
|
||||
if path.join("brightness").exists() && path.join("max_brightness").exists() {
|
||||
return Some(path);
|
||||
if !path.join("actual_brightness").exists() || !path.join("max_brightness").exists() {
|
||||
continue;
|
||||
}
|
||||
let name = path.file_name()?.to_string_lossy().into_owned();
|
||||
let score = backlight_score(&name);
|
||||
if best.as_ref().map_or(true, |(best_score, _)| score > *best_score) {
|
||||
best = Some((score, path));
|
||||
}
|
||||
}
|
||||
None
|
||||
|
||||
best.map(|(_, path)| path)
|
||||
}
|
||||
|
||||
/// Read the current brightness as a percentage (0-100) from a sysfs backlight
|
||||
/// directory, or `None` if the files cannot be read / parsed.
|
||||
/// directory, or `None` if the files cannot be read or parsed.
|
||||
///
|
||||
/// Uses `actual_brightness` (hardware-reported) rather than `brightness`
|
||||
/// (software-requested) for accuracy on AMD GPU panels.
|
||||
fn read_brightness(backlight: &Path) -> Option<u8> {
|
||||
let current: u64 = fs::read_to_string(backlight.join("brightness"))
|
||||
let current: u64 = fs::read_to_string(backlight.join("actual_brightness"))
|
||||
.ok()?
|
||||
.trim()
|
||||
.parse()
|
||||
|
||||
Reference in New Issue
Block a user