// ABOUTME: Power actions — reboot and shutdown via systemctl. // ABOUTME: Wrappers around system commands for the lockscreen UI. use std::fmt; use std::process::Command; #[derive(Debug)] pub enum PowerError { CommandFailed { action: &'static str, message: String }, Timeout { action: &'static str }, } impl fmt::Display for PowerError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { PowerError::CommandFailed { action, message } => write!(f, "{action} failed: {message}"), PowerError::Timeout { action } => write!(f, "{action} timed out"), } } } impl std::error::Error for PowerError {} fn run_command(action: &'static str, program: &str, args: &[&str]) -> Result<(), PowerError> { let output = Command::new(program) .args(args) .stderr(std::process::Stdio::piped()) .output() .map_err(|e| PowerError::CommandFailed { action, message: e.to_string() })?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); return Err(PowerError::CommandFailed { action, message: format!("exit code {}: {}", output.status, stderr.trim()), }); } Ok(()) } pub fn reboot() -> Result<(), PowerError> { run_command("reboot", "/usr/bin/systemctl", &["--no-ask-password", "reboot"]) } pub fn shutdown() -> Result<(), PowerError> { run_command("shutdown", "/usr/bin/systemctl", &["--no-ask-password", "poweroff"]) } #[cfg(test)] mod tests { use super::*; #[test] fn power_error_display() { assert_eq!(PowerError::CommandFailed { action: "reboot", message: "fail".into() }.to_string(), "reboot failed: fail"); } #[test] fn timeout_display() { assert_eq!(PowerError::Timeout { action: "shutdown" }.to_string(), "shutdown timed out"); } #[test] fn missing_binary() { assert!(run_command("test", "nonexistent-xyz", &[]).is_err()); } #[test] fn nonzero_exit() { assert!(run_command("test", "false", &[]).is_err()); } #[test] fn success() { assert!(run_command("test", "true", &[]).is_ok()); } }