use nosleep::{NoSleep, NoSleepType};
trait SleepBackend {
fn start(&mut self) -> Result<(), String>;
fn stop(&mut self) -> Result<(), String>;
}
struct NosleepBackend {
inner: NoSleep,
}
impl SleepBackend for NosleepBackend {
fn start(&mut self) -> Result<(), String> {
self.inner
.start(NoSleepType::PreventUserIdleDisplaySleep)
.map_err(|e| format!("{e:?}"))
}
fn stop(&mut self) -> Result<(), String> {
self.inner.stop().map_err(|e| format!("{e:?}"))
}
}
pub struct SleepInhibitor {
backend: Box<dyn SleepBackend>,
active: bool,
permanently_failed: bool,
}
impl SleepInhibitor {
pub fn new() -> Result<Self, String> {
let inner =
NoSleep::new().map_err(|e| format!("failed to create sleep inhibitor: {e:?}"))?;
Ok(Self {
backend: Box::new(NosleepBackend { inner }),
active: false,
permanently_failed: false,
})
}
#[cfg(test)]
fn with_backend(backend: Box<dyn SleepBackend>) -> Self {
Self {
backend,
active: false,
permanently_failed: false,
}
}
pub fn activate(&mut self) {
if self.active || self.permanently_failed {
return;
}
if let Err(e) = self.backend.start() {
crate::platform::debugging::log_info(format!(
"Failed to inhibit display sleep (will not retry): {e}"
));
self.permanently_failed = true;
return;
}
self.active = true;
}
pub fn deactivate(&mut self) {
if !self.active {
return;
}
if let Err(e) = self.backend.stop() {
crate::platform::debugging::log_info(format!(
"Failed to release display sleep inhibition: {e}"
));
return;
}
self.active = false;
}
}
impl Drop for SleepInhibitor {
fn drop(&mut self) {
if self.active {
let _ = self.backend.stop();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::cell::RefCell;
use std::rc::Rc;
struct MockBackend {
start_results: Vec<Result<(), String>>,
stop_results: Vec<Result<(), String>>,
call_log: Rc<RefCell<Vec<&'static str>>>,
}
impl MockBackend {
fn new(
start_results: Vec<Result<(), String>>,
stop_results: Vec<Result<(), String>>,
call_log: Rc<RefCell<Vec<&'static str>>>,
) -> Self {
Self {
start_results,
stop_results,
call_log,
}
}
}
impl SleepBackend for MockBackend {
fn start(&mut self) -> Result<(), String> {
self.call_log.borrow_mut().push("start");
if self.start_results.is_empty() {
Ok(())
} else {
self.start_results.remove(0)
}
}
fn stop(&mut self) -> Result<(), String> {
self.call_log.borrow_mut().push("stop");
if self.stop_results.is_empty() {
Ok(())
} else {
self.stop_results.remove(0)
}
}
}
fn make_inhibitor(
start_results: Vec<Result<(), String>>,
stop_results: Vec<Result<(), String>>,
) -> (SleepInhibitor, Rc<RefCell<Vec<&'static str>>>) {
let log = Rc::new(RefCell::new(Vec::new()));
let backend = MockBackend::new(start_results, stop_results, Rc::clone(&log));
(SleepInhibitor::with_backend(Box::new(backend)), log)
}
#[test]
fn new_inhibitor_is_not_active() {
let (inhibitor, _) = make_inhibitor(vec![], vec![]);
assert!(!inhibitor.active);
}
#[test]
fn activate_calls_backend_start_and_sets_active() {
let (mut inhibitor, log) = make_inhibitor(vec![Ok(())], vec![]);
inhibitor.activate();
assert!(inhibitor.active);
assert_eq!(*log.borrow(), vec!["start"]);
}
#[test]
fn deactivate_calls_backend_stop_and_clears_active() {
let (mut inhibitor, log) = make_inhibitor(vec![Ok(())], vec![Ok(())]);
inhibitor.activate();
inhibitor.deactivate();
assert!(!inhibitor.active);
assert_eq!(*log.borrow(), vec!["start", "stop"]);
}
#[test]
fn activate_is_idempotent() {
let (mut inhibitor, log) = make_inhibitor(vec![Ok(()), Ok(())], vec![]);
inhibitor.activate();
inhibitor.activate();
assert!(inhibitor.active);
assert_eq!(*log.borrow(), vec!["start"]);
}
#[test]
fn deactivate_when_inactive_is_noop() {
let (mut inhibitor, log) = make_inhibitor(vec![], vec![]);
inhibitor.deactivate();
assert!(!inhibitor.active);
assert!(log.borrow().is_empty());
}
#[test]
fn activate_deactivate_cycle() {
let (mut inhibitor, log) = make_inhibitor(vec![Ok(()), Ok(())], vec![Ok(()), Ok(())]);
inhibitor.activate();
assert!(inhibitor.active);
inhibitor.deactivate();
assert!(!inhibitor.active);
inhibitor.activate();
assert!(inhibitor.active);
assert_eq!(*log.borrow(), vec!["start", "stop", "start"]);
}
#[test]
fn activate_failure_sets_permanently_failed() {
let (mut inhibitor, log) = make_inhibitor(vec![Err("no D-Bus".into())], vec![]);
inhibitor.activate();
assert!(!inhibitor.active);
assert!(inhibitor.permanently_failed);
inhibitor.activate();
assert_eq!(*log.borrow(), vec!["start"]);
}
#[test]
fn deactivate_failure_keeps_active_state() {
let (mut inhibitor, log) = make_inhibitor(vec![Ok(())], vec![Err("stop failed".into())]);
inhibitor.activate();
inhibitor.deactivate();
assert!(inhibitor.active);
assert_eq!(*log.borrow(), vec!["start", "stop"]);
}
#[test]
fn drop_calls_stop_when_active() {
let log = Rc::new(RefCell::new(Vec::new()));
let backend = MockBackend::new(vec![Ok(())], vec![Ok(())], Rc::clone(&log));
let mut inhibitor = SleepInhibitor::with_backend(Box::new(backend));
inhibitor.activate();
drop(inhibitor);
assert_eq!(*log.borrow(), vec!["start", "stop"]);
}
#[test]
fn drop_does_not_call_stop_when_inactive() {
let log = Rc::new(RefCell::new(Vec::new()));
let backend = MockBackend::new(vec![], vec![], Rc::clone(&log));
let inhibitor = SleepInhibitor::with_backend(Box::new(backend));
drop(inhibitor);
assert!(log.borrow().is_empty());
}
}