From 742ff7b92cc559cc48a6bcaa625e1846297ed244 Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Tue, 6 Sep 2022 14:07:33 -0700 Subject: [PATCH] Add sysfs fallback for screen brightness Some drivers/displays don't support the xbacklight stuff, so fall back to sysfs if needed. This usually will require some extra permissions setup on the user's part. --- README.md | 23 ++++++++ locker/src/monitor.rs | 119 +++++++++++++++++++++++++++++------------- 2 files changed, 107 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 9834b29..4ebd86e 100644 --- a/README.md +++ b/README.md @@ -146,3 +146,26 @@ resuming. The settings component is a standalone GTK app that presents a settings dialog, which reads from and writes to your configuration file. + +#### Screen Brightness + +If you enable the setting that allows `bscreensaver` to make your screen +brightness keys work while the screen is locked, this should hopefully +work without further intervention, if your display driver supports the +`XBACKLIGHT` protocol. If not, `bscreensaver` will attempt to use the +backlight controls in sysfs, which probably will not work without extra +setup, as those controls are usually only accessible to root. You can +give yourself (well, anyone in the `video` group) access by adding a +udev rules file, say at `/etc/udev/rules.d/90-backlight.rules`: + +``` +SUBSYSTEM=="backlight", ACTION=="add", \ + RUN+="/bin/chgrp video /sys/class/backlight/%k/brightness", \ + RUN+="/bin/chmod g+w /sys/class/backlight/%k/brightness" +``` + +You may need to restart to get those settings applied, or you can just +run those commands yourself (replacing `%k` with whatever directories +happen to be located there). Your user account will also need to be in +the `video` group; if it isn't, you'll probably need to log out and in +again before any changes take effect. diff --git a/locker/src/monitor.rs b/locker/src/monitor.rs index 7c9d56d..fcf7e4a 100644 --- a/locker/src/monitor.rs +++ b/locker/src/monitor.rs @@ -1,5 +1,6 @@ use log::{debug, error, warn}; -use std::{cmp, thread, time::Duration}; +use nix::unistd::{access, AccessFlags}; +use std::{cmp, thread, time::Duration, path::PathBuf, fs, io::Write}; use xcb::{x, randr, Xid}; use bscreensaver_util::{BSCREENSAVER_WM_CLASS, create_atom, destroy_cursor, destroy_pixmap, destroy_gc, destroy_window}; @@ -7,8 +8,13 @@ use bscreensaver_util::{BSCREENSAVER_WM_CLASS, create_atom, destroy_cursor, dest const BACKLIGHT_ATOM_NAME: &[u8] = b"Backlight"; const BACKLIGHT_FALLBACK_ATOM_NAME: &[u8] = b"BACKLIGHT"; +enum BacklightLocation { + XBacklight(x::Atom), + Sysfs(PathBuf), +} + struct BacklightControl { - property: x::Atom, + location: BacklightLocation, min_level: i32, max_level: i32, step: u32, @@ -361,32 +367,46 @@ impl<'a> Monitor<'a> { } fn get_current_brightness(&self, backlight_control: &BacklightControl) -> anyhow::Result> { - let cookie = self.conn.send_request(&randr::GetOutputProperty { - output: self.properties.output, - property: backlight_control.property, - r#type: x::ATOM_INTEGER, - long_offset: 0, - long_length: 4, - delete: false, - pending: false, - }); - let reply = self.conn.wait_for_reply(cookie)?; - let data = reply.data::(); - if data.len() == 1 { - Ok(Some(data[0])) - } else { - Ok(None) + match &backlight_control.location { + BacklightLocation::XBacklight(property) => { + let cookie = self.conn.send_request(&randr::GetOutputProperty { + output: self.properties.output, + property: *property, + r#type: x::ATOM_INTEGER, + long_offset: 0, + long_length: 4, + delete: false, + pending: false, + }); + let reply = self.conn.wait_for_reply(cookie)?; + let data = reply.data::(); + if data.len() == 1 { + Ok(Some(data[0])) + } else { + Ok(None) + } + }, + BacklightLocation::Sysfs(path) => Ok(Some(fs::read_to_string(path)?.trim().parse::()?)), } } fn set_brightness(&self, backlight_control: &BacklightControl, level: i32) -> anyhow::Result<()> { - self.conn.send_and_check_request(&randr::ChangeOutputProperty { - output: self.properties.output, - property: backlight_control.property, - r#type: x::ATOM_INTEGER, - mode: x::PropMode::Replace, - data: &[level as u32], - })?; + match &backlight_control.location { + BacklightLocation::XBacklight(property) => { + self.conn.send_and_check_request(&randr::ChangeOutputProperty { + output: self.properties.output, + property: *property, + r#type: x::ATOM_INTEGER, + mode: x::PropMode::Replace, + data: &[level as u32], + })? + }, + BacklightLocation::Sysfs(path) => { + let mut f = fs::OpenOptions::new().write(true).open(path)?; + let level_str = level.to_string(); + f.write_all(level_str.as_bytes())?; + }, + } Ok(()) } } @@ -513,28 +533,57 @@ fn create_blank_cursor(conn: &xcb::Connection, root: x::Window) -> xcb::Result xcb::Result> { +fn find_backlight_control(conn: &xcb::Connection, output: randr::Output) -> anyhow::Result> { + fn calc_step(min: i32, max: i32) -> u32 { + cmp::min((max - min) as u32, cmp::max(10, (max - min) / 10) as u32) + } + for prop_name in [BACKLIGHT_ATOM_NAME, BACKLIGHT_FALLBACK_ATOM_NAME] { let property = create_atom(conn, prop_name)?; let cookie = conn.send_request(&randr::QueryOutputProperty { output, property, }); - let reply = conn.wait_for_reply(cookie)?; - let values = reply.valid_values(); - if reply.range() && values.len() == 2 { - let min_level = values[0]; - let max_level = values[1]; - let range = max_level - min_level; - if range > 0 { + if let Ok(reply) = conn.wait_for_reply(cookie) { + let values = reply.valid_values(); + if reply.range() && values.len() == 2 { + let min_level = values[0]; + let max_level = values[1]; + let range = max_level - min_level; + if range > 0 { + debug!("Found xbacklight control"); + return Ok(Some(BacklightControl { + location: BacklightLocation::XBacklight(property), + min_level, + max_level, + step: calc_step(min_level, max_level), + })); + } + } + } + } + + if let Ok(entries) = fs::read_dir("/sys/class/backlight") { + for entry in entries { + let entry = entry?; + let mut path = entry.path(); + path.push("brightness"); + if path.exists() && access(&path, AccessFlags::W_OK).is_ok() { + let mut max_path = entry.path(); + max_path.push("max_brightness"); + let max_level = fs::read_to_string(max_path)?.trim().parse::()?; + let mut cur_path = entry.path(); + cur_path.push("brightness"); + debug!("Found sysfs backlight control at {:?}", cur_path); return Ok(Some(BacklightControl { - property, - min_level, + location: BacklightLocation::Sysfs(cur_path), + min_level: 0, max_level, - step: cmp::min(range as u32, cmp::max(10, (max_level - min_level) / 10) as u32), + step: calc_step(0, max_level), })); } } } + Ok(None) }