1use std::fs;
2use std::path::Path;
3use std::path::PathBuf;
4
5use log::debug;
6
7const WAKE_LOCK_PATH: &str = "/sys/power/wake_lock";
8const WAKE_UNLOCK_PATH: &str = "/sys/power/wake_unlock";
9const WAKE_LOCK_NAME: &str = "dev_probe";
10
11pub struct WakeLock {
12 held: bool,
13}
14
15impl WakeLock {
16 pub fn acquire() -> Self {
17 let _ = fs::write(WAKE_LOCK_PATH, WAKE_LOCK_NAME);
19 debug!("pwr: wakelock ACQUIRED");
20 WakeLock { held: true }
21 }
22}
23
24impl Drop for WakeLock {
25 fn drop(&mut self) {
26 if self.held {
27 let _ = fs::write(WAKE_UNLOCK_PATH, WAKE_LOCK_NAME);
29 }
30 }
31}
32
33pub fn known_brightness_path(fl_cfg: &crate::device::config::FrontlightConfig) -> PathBuf {
35 PathBuf::from(&fl_cfg.brightness_path)
36}
37
38pub fn pick_backlight(entries: &[(PathBuf, u32)]) -> Option<PathBuf> {
41 entries
42 .iter()
43 .filter(|(_, v)| *v > 0)
44 .max_by_key(|(_, v)| *v)
45 .map(|(p, _)| p.clone())
46}
47
48pub fn frontlight_path(fl_cfg: &crate::device::config::FrontlightConfig) -> Option<PathBuf> {
49 let known = known_brightness_path(fl_cfg);
50 if known.exists() {
51 return Some(known);
52 }
53 let mut candidates: Vec<(PathBuf, u32)> = Vec::new();
54 for entry in fs::read_dir("/sys/class/backlight").ok()?.flatten() {
55 let path = entry.path().join("brightness");
56 if path.exists() {
57 if let Some(val) = frontlight_get(&path) {
58 candidates.push((path, val));
59 }
60 }
61 }
62 pick_backlight(&candidates)
63}
64
65pub fn frontlight_get(path: &Path) -> Option<u32> {
66 fs::read_to_string(path)
67 .ok()
68 .and_then(|s| s.trim().parse().ok())
69}
70
71pub fn frontlight_set(path: &Path, val: u32) {
72 let _ = fs::write(path, val.to_string());
74}
75
76pub const FL_RESTORE_RETRIES: u32 = 5;
80pub const FL_RESTORE_INTERVAL_MS: u64 = 30;
81
82pub fn restore_frontlight_with(
87 fl: &dyn crate::device::traits::Frontlight,
88 brightness: u32,
89 retries: u32,
90 sleep: impl Fn(),
91) -> u32 {
92 let mut attempts = 0u32;
93 for attempt in 0..retries {
94 attempts += 1;
95 fl.set(brightness);
96 sleep();
97 if fl.get() == brightness {
98 debug!(
99 "pwr: frontlight restored to {} (attempt {})",
100 brightness,
101 attempt + 1
102 );
103 break;
104 }
105 debug!(
106 "pwr: frontlight retry {}/{} , got {}",
107 attempt + 1,
108 retries,
109 fl.get()
110 );
111 }
112 attempts
113}
114
115struct PathFrontlight<'a> {
116 path: &'a Path,
117}
118
119impl<'a> crate::device::traits::Frontlight for PathFrontlight<'a> {
120 fn set(&self, val: u32) {
121 frontlight_set(self.path, val);
122 }
123 fn get(&self) -> u32 {
124 frontlight_get(self.path).unwrap_or(0)
125 }
126 fn restore(&self, brightness: u32) {
127 restore_frontlight(self.path, brightness);
128 }
129}
130
131pub fn restore_frontlight(path: &Path, brightness: u32) {
132 let fl = PathFrontlight { path };
133 restore_frontlight_with(&fl, brightness, FL_RESTORE_RETRIES, || {
134 std::thread::sleep(std::time::Duration::from_millis(FL_RESTORE_INTERVAL_MS));
135 });
136}
137
138#[allow(dead_code)]
139pub fn kernel_suspend(state: &str) {
140 debug!("pwr: entering kernel suspend ({})", state);
141 let _ = fs::write("/sys/power/state", state);
143 debug!("pwr: resumed from kernel suspend");
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149 use crate::device::config::FrontlightConfig;
150
151 #[test]
152 fn known_brightness_path_returns_configured() {
153 let cfg = FrontlightConfig {
154 brightness_path: "/sys/class/backlight/lm3630a_led/brightness".into(),
155 mixer_path: None,
156 nl_min: 0,
157 nl_max: 100,
158 nl_inverted: false,
159 };
160 assert_eq!(
161 known_brightness_path(&cfg),
162 PathBuf::from(&cfg.brightness_path)
163 );
164 }
165
166 #[test]
167 fn pick_backlight_picks_highest_nonzero() {
168 let e = [
169 (PathBuf::from("/sys/a"), 0),
170 (PathBuf::from("/sys/b"), 42),
171 (PathBuf::from("/sys/c"), 99),
172 (PathBuf::from("/sys/d"), 7),
173 ];
174 assert_eq!(pick_backlight(&e), Some(PathBuf::from("/sys/c")));
175 }
176
177 #[test]
178 fn pick_backlight_none_when_all_zero() {
179 let e = [(PathBuf::from("/sys/a"), 0), (PathBuf::from("/sys/b"), 0)];
180 assert_eq!(pick_backlight(&e), None);
181 assert_eq!(pick_backlight(&[]), None);
182 }
183
184 use std::cell::{Cell, RefCell};
185
186 struct MockFl {
189 sets: RefCell<Vec<u32>>,
190 gets: Cell<usize>,
191 scripted: Vec<u32>,
192 }
193
194 impl MockFl {
195 fn new(gets: Vec<u32>) -> Self {
196 MockFl {
197 sets: RefCell::new(Vec::new()),
198 gets: Cell::new(0),
199 scripted: gets,
200 }
201 }
202 }
203
204 impl crate::device::traits::Frontlight for &MockFl {
205 fn set(&self, val: u32) {
206 self.sets.borrow_mut().push(val);
207 }
208 fn get(&self) -> u32 {
209 let i = self.gets.get();
210 let v = self
211 .scripted
212 .get(i)
213 .copied()
214 .unwrap_or_else(|| self.scripted.last().copied().unwrap_or(0));
215 self.gets.set(i + 1);
216 v
217 }
218 fn restore(&self, brightness: u32) {
219 self.set(brightness);
220 }
221 }
222
223 #[test]
224 fn restore_frontlight_succeeds_first_try() {
225 let fl = MockFl::new(vec![60]);
226 let attempts = restore_frontlight_with(&(&fl), 60, FL_RESTORE_RETRIES, || {});
227 assert_eq!(attempts, 1, "converged on the first attempt");
228 assert_eq!(*fl.sets.borrow(), vec![60]);
229 }
230
231 #[test]
232 fn restore_frontlight_retries_until_convergence() {
233 let fl = MockFl::new(vec![0, 0, 60]);
234 let attempts = restore_frontlight_with(&(&fl), 60, FL_RESTORE_RETRIES, || {});
235 assert_eq!(attempts, 3, "two failures then success");
236 assert_eq!(
237 *fl.sets.borrow(),
238 vec![60, 60, 60],
239 "set retried each attempt"
240 );
241 }
242
243 #[test]
244 fn restore_frontlight_gives_up_after_max_retries() {
245 let fl = MockFl::new(vec![0]);
246 let attempts = restore_frontlight_with(&(&fl), 60, FL_RESTORE_RETRIES, || {});
247 assert_eq!(
248 attempts, FL_RESTORE_RETRIES,
249 "never converges -> retries exhausted"
250 );
251 assert_eq!(fl.sets.borrow().len(), FL_RESTORE_RETRIES as usize);
252 }
253
254 #[test]
255 fn restore_frontlight_zero_retries_is_single_attempt() {
256 let fl = MockFl::new(vec![60]);
257 let attempts = restore_frontlight_with(&(&fl), 60, 0, || {});
258 assert_eq!(attempts, 0, "zero retries means the loop never runs");
259 assert!(fl.sets.borrow().is_empty());
260 }
261}