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.
This commit is contained in:
Brian Tarricone 2022-09-06 14:07:33 -07:00
parent 1ace254163
commit 742ff7b92c
2 changed files with 107 additions and 35 deletions

View File

@ -146,3 +146,26 @@ resuming.
The settings component is a standalone GTK app that presents a settings The settings component is a standalone GTK app that presents a settings
dialog, which reads from and writes to your configuration file. 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.

View File

@ -1,5 +1,6 @@
use log::{debug, error, warn}; 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 xcb::{x, randr, Xid};
use bscreensaver_util::{BSCREENSAVER_WM_CLASS, create_atom, destroy_cursor, destroy_pixmap, destroy_gc, destroy_window}; 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_ATOM_NAME: &[u8] = b"Backlight";
const BACKLIGHT_FALLBACK_ATOM_NAME: &[u8] = b"BACKLIGHT"; const BACKLIGHT_FALLBACK_ATOM_NAME: &[u8] = b"BACKLIGHT";
enum BacklightLocation {
XBacklight(x::Atom),
Sysfs(PathBuf),
}
struct BacklightControl { struct BacklightControl {
property: x::Atom, location: BacklightLocation,
min_level: i32, min_level: i32,
max_level: i32, max_level: i32,
step: u32, step: u32,
@ -361,32 +367,46 @@ impl<'a> Monitor<'a> {
} }
fn get_current_brightness(&self, backlight_control: &BacklightControl) -> anyhow::Result<Option<u32>> { fn get_current_brightness(&self, backlight_control: &BacklightControl) -> anyhow::Result<Option<u32>> {
let cookie = self.conn.send_request(&randr::GetOutputProperty { match &backlight_control.location {
output: self.properties.output, BacklightLocation::XBacklight(property) => {
property: backlight_control.property, let cookie = self.conn.send_request(&randr::GetOutputProperty {
r#type: x::ATOM_INTEGER, output: self.properties.output,
long_offset: 0, property: *property,
long_length: 4, r#type: x::ATOM_INTEGER,
delete: false, long_offset: 0,
pending: false, long_length: 4,
}); delete: false,
let reply = self.conn.wait_for_reply(cookie)?; pending: false,
let data = reply.data::<u32>(); });
if data.len() == 1 { let reply = self.conn.wait_for_reply(cookie)?;
Ok(Some(data[0])) let data = reply.data::<u32>();
} else { if data.len() == 1 {
Ok(None) Ok(Some(data[0]))
} else {
Ok(None)
}
},
BacklightLocation::Sysfs(path) => Ok(Some(fs::read_to_string(path)?.trim().parse::<u32>()?)),
} }
} }
fn set_brightness(&self, backlight_control: &BacklightControl, level: i32) -> anyhow::Result<()> { fn set_brightness(&self, backlight_control: &BacklightControl, level: i32) -> anyhow::Result<()> {
self.conn.send_and_check_request(&randr::ChangeOutputProperty { match &backlight_control.location {
output: self.properties.output, BacklightLocation::XBacklight(property) => {
property: backlight_control.property, self.conn.send_and_check_request(&randr::ChangeOutputProperty {
r#type: x::ATOM_INTEGER, output: self.properties.output,
mode: x::PropMode::Replace, property: *property,
data: &[level as u32], 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(()) Ok(())
} }
} }
@ -513,28 +533,57 @@ fn create_blank_cursor(conn: &xcb::Connection, root: x::Window) -> xcb::Result<x
Ok(blank_cursor) Ok(blank_cursor)
} }
fn find_backlight_control(conn: &xcb::Connection, output: randr::Output) -> xcb::Result<Option<BacklightControl>> { fn find_backlight_control(conn: &xcb::Connection, output: randr::Output) -> anyhow::Result<Option<BacklightControl>> {
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] { for prop_name in [BACKLIGHT_ATOM_NAME, BACKLIGHT_FALLBACK_ATOM_NAME] {
let property = create_atom(conn, prop_name)?; let property = create_atom(conn, prop_name)?;
let cookie = conn.send_request(&randr::QueryOutputProperty { let cookie = conn.send_request(&randr::QueryOutputProperty {
output, output,
property, property,
}); });
let reply = conn.wait_for_reply(cookie)?; if let Ok(reply) = conn.wait_for_reply(cookie) {
let values = reply.valid_values(); let values = reply.valid_values();
if reply.range() && values.len() == 2 { if reply.range() && values.len() == 2 {
let min_level = values[0]; let min_level = values[0];
let max_level = values[1]; let max_level = values[1];
let range = max_level - min_level; let range = max_level - min_level;
if range > 0 { 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::<i32>()?;
let mut cur_path = entry.path();
cur_path.push("brightness");
debug!("Found sysfs backlight control at {:?}", cur_path);
return Ok(Some(BacklightControl { return Ok(Some(BacklightControl {
property, location: BacklightLocation::Sysfs(cur_path),
min_level, min_level: 0,
max_level, 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) Ok(None)
} }