use std::fs;
use std::path::Path;
use std::path::PathBuf;
const WAKE_LOCK_PATH: &str = "/sys/power/wake_lock";
const WAKE_UNLOCK_PATH: &str = "/sys/power/wake_unlock";
const WAKE_LOCK_NAME: &str = "dev_probe";
pub struct WakeLock {
held: bool,
}
impl WakeLock {
pub fn acquire() -> Self {
let _ = fs::write(WAKE_LOCK_PATH, WAKE_LOCK_NAME);
WakeLock { held: true }
}
}
impl Drop for WakeLock {
fn drop(&mut self) {
if self.held {
let _ = fs::write(WAKE_UNLOCK_PATH, WAKE_LOCK_NAME);
}
}
}
pub fn known_brightness_path(fl_cfg: &crate::device::config::FrontlightConfig) -> PathBuf {
PathBuf::from(&fl_cfg.brightness_path)
}
pub fn pick_backlight(entries: &[(PathBuf, u32)]) -> Option<PathBuf> {
entries
.iter()
.filter(|(_, v)| *v > 0)
.max_by_key(|(_, v)| *v)
.map(|(p, _)| p.clone())
}
pub fn frontlight_path(fl_cfg: &crate::device::config::FrontlightConfig) -> Option<PathBuf> {
let known = known_brightness_path(fl_cfg);
if known.exists() {
return Some(known);
}
let mut candidates: Vec<(PathBuf, u32)> = Vec::new();
for entry in fs::read_dir("/sys/class/backlight").ok()?.flatten() {
let path = entry.path().join("brightness");
if path.exists() {
if let Some(val) = frontlight_get(&path) {
candidates.push((path, val));
}
}
}
pick_backlight(&candidates)
}
pub fn frontlight_get(path: &Path) -> Option<u32> {
fs::read_to_string(path)
.ok()
.and_then(|s| s.trim().parse().ok())
}
pub fn frontlight_set(path: &Path, val: u32) {
let _ = fs::write(path, val.to_string());
}
pub const FL_RESTORE_RETRIES: u32 = 5;
pub const FL_RESTORE_INTERVAL_MS: u64 = 30;
pub fn restore_frontlight_with(
fl: &dyn crate::device::traits::Frontlight,
brightness: u32,
retries: u32,
sleep: impl Fn(),
) -> u32 {
let mut attempts = 0u32;
for _ in 0..retries {
attempts += 1;
fl.set(brightness);
sleep();
if fl.get() == brightness {
break;
}
}
attempts
}
struct PathFrontlight<'a> {
path: &'a Path,
}
impl<'a> crate::device::traits::Frontlight for PathFrontlight<'a> {
fn set(&self, val: u32) {
frontlight_set(self.path, val);
}
fn get(&self) -> u32 {
frontlight_get(self.path).unwrap_or(0)
}
fn restore(&self, brightness: u32) {
restore_frontlight(self.path, brightness);
}
}
pub fn restore_frontlight(path: &Path, brightness: u32) {
let fl = PathFrontlight { path };
restore_frontlight_with(&fl, brightness, FL_RESTORE_RETRIES, || {
std::thread::sleep(std::time::Duration::from_millis(FL_RESTORE_INTERVAL_MS));
});
}
#[allow(dead_code)]
pub fn kernel_suspend(state: &str) {
let _ = fs::write("/sys/power/state", state);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::device::config::FrontlightConfig;
#[test]
fn known_brightness_path_returns_configured() {
let cfg = FrontlightConfig {
brightness_path: "/sys/class/backlight/lm3630a_led/brightness".into(),
mixer_path: None,
nl_min: 0,
nl_max: 100,
nl_inverted: false,
};
assert_eq!(
known_brightness_path(&cfg),
PathBuf::from(&cfg.brightness_path)
);
}
#[test]
fn pick_backlight_picks_highest_nonzero() {
let e = [
(PathBuf::from("/sys/a"), 0),
(PathBuf::from("/sys/b"), 42),
(PathBuf::from("/sys/c"), 99),
(PathBuf::from("/sys/d"), 7),
];
assert_eq!(pick_backlight(&e), Some(PathBuf::from("/sys/c")));
}
#[test]
fn pick_backlight_none_when_all_zero() {
let e = [(PathBuf::from("/sys/a"), 0), (PathBuf::from("/sys/b"), 0)];
assert_eq!(pick_backlight(&e), None);
assert_eq!(pick_backlight(&[]), None);
}
use std::cell::{Cell, RefCell};
struct MockFl {
sets: RefCell<Vec<u32>>,
gets: Cell<usize>,
scripted: Vec<u32>,
}
impl MockFl {
fn new(gets: Vec<u32>) -> Self {
MockFl {
sets: RefCell::new(Vec::new()),
gets: Cell::new(0),
scripted: gets,
}
}
}
impl crate::device::traits::Frontlight for &MockFl {
fn set(&self, val: u32) {
self.sets.borrow_mut().push(val);
}
fn get(&self) -> u32 {
let i = self.gets.get();
let v = self
.scripted
.get(i)
.copied()
.unwrap_or_else(|| self.scripted.last().copied().unwrap_or(0));
self.gets.set(i + 1);
v
}
fn restore(&self, brightness: u32) {
self.set(brightness);
}
}
#[test]
fn restore_frontlight_succeeds_first_try() {
let fl = MockFl::new(vec![60]);
let attempts = restore_frontlight_with(&(&fl), 60, FL_RESTORE_RETRIES, || {});
assert_eq!(attempts, 1, "converged on the first attempt");
assert_eq!(*fl.sets.borrow(), vec![60]);
}
#[test]
fn restore_frontlight_retries_until_convergence() {
let fl = MockFl::new(vec![0, 0, 60]);
let attempts = restore_frontlight_with(&(&fl), 60, FL_RESTORE_RETRIES, || {});
assert_eq!(attempts, 3, "two failures then success");
assert_eq!(
*fl.sets.borrow(),
vec![60, 60, 60],
"set retried each attempt"
);
}
#[test]
fn restore_frontlight_gives_up_after_max_retries() {
let fl = MockFl::new(vec![0]);
let attempts = restore_frontlight_with(&(&fl), 60, FL_RESTORE_RETRIES, || {});
assert_eq!(
attempts, FL_RESTORE_RETRIES,
"never converges -> retries exhausted"
);
assert_eq!(fl.sets.borrow().len(), FL_RESTORE_RETRIES as usize);
}
#[test]
fn restore_frontlight_zero_retries_is_single_attempt() {
let fl = MockFl::new(vec![60]);
let attempts = restore_frontlight_with(&(&fl), 60, 0, || {});
assert_eq!(attempts, 0, "zero retries means the loop never runs");
assert!(fl.sets.borrow().is_empty());
}
}