use std::fs; use std::io; use std::path::{Path, PathBuf}; use std::process::{Command, Child, Stdio}; use std::sync::{Mutex, Arc}; use systray; use dialog::{self, DialogBox}; use tempfile::tempdir; use crate::error::*; mod error; const ICON_EXCLAM_PNG: &[u8] = include_bytes!("../resources/exclam.png"); const ICON_OK_PNG: &[u8] = include_bytes!("../resources/ok.png"); fn join(sep: &str, v: &[String]) -> String { let mut res = String::new(); let mut first = true; for item in v { if first { first = false; } else { res.push_str(sep); } res.push_str(item); } res } struct State { // Child is 'cat >/dev/null', with stdin piped. // Close stdin to terminate. inhibitor: Option<(Child, Vec)>, ok_path: String, exclam_path: String, } impl State { fn new(ok_path: &str, exclam_path: &str) -> State { State { inhibitor: None, ok_path: ok_path.to_string(), exclam_path: exclam_path.to_string(), } } fn what_inhibited(&self) -> Option<&[String]> { match &self.inhibitor { Some((_, v)) => Some(v), None => None, } } fn inhibit(&mut self, what: &[String]) -> io::Result<()> { self.uninhibit()?; let mut whatarg = "--what=".to_string(); whatarg.push_str(&join(":", what)); let child = Command::new("systemd-inhibit") .arg(whatarg) .arg("--who=Tom's acpi-inhibitor") .arg("--why=User request") .arg("cat") .stdin(Stdio::piped()) .stdout(Stdio::null()) .spawn()?; self.inhibitor = Some((child, what.to_vec())); Ok(()) } fn uninhibit(&mut self) -> io::Result<()> { if let Some((ch, _)) = &mut self.inhibitor { // This automatically closes the child's stdin ch.wait()?; self.inhibitor = None; } Ok(()) } fn poll(&mut self) -> io::Result<()> { if let Some((ch, _)) = &mut self.inhibitor { if ch.try_wait()?.is_some() { self.inhibitor = None; } } Ok(()) } } fn show_dialog(title: &str, body: &str) -> io::Result<()> { dialog::Message::new(body).title(title).show() .map_err(|e| io::Error::new(io::ErrorKind::Other, format!("{}", e))) } fn status_message(state: &mut State) -> io::Result { state.poll()?; let mut msg; match state.what_inhibited() { Some(what) => { msg = "Currently inhibiting: ".to_string(); msg.push_str(&join(", ", what)); } None => { msg = "Currently functioning normally (not inhibited)".to_string(); } } Ok(msg) } fn update_icon(app: &mut systray::Application, state: &mut State) -> io::Result<()> { match state.what_inhibited() { Some(_) => app.set_icon_from_file(&state.exclam_path).iores(), None => app.set_icon_from_file(&state.ok_path).iores(), } } fn materialise_icon(dir: &Path, fname: &str, bytes: &[u8]) -> io::Result { let mut pathbuf = dir.to_path_buf(); pathbuf.push(fname); fs::write(&pathbuf, bytes)?; Ok(pathbuf) } fn main() -> io::Result<()> { let resources_dir = tempdir()?; let resources_path = resources_dir.path(); let icon_ok_path = materialise_icon(&resources_path, "ok.png", ICON_OK_PNG)?; let icon_exclam_path = materialise_icon(&resources_path, "exclam.png", ICON_EXCLAM_PNG)?; let mut app = systray::Application::new().iores()?; let state = Arc::new(Mutex::new(State::new( icon_ok_path.to_str().unwrap(), icon_exclam_path.to_str().unwrap() ))); app.set_icon_from_file(icon_ok_path.to_str().unwrap()).iores()?; { let state = state.clone(); app.add_menu_item("Status", move |window| { let mut state = state.as_ref().lock().unwrap(); let msg = status_message(&mut state)?; update_icon(window, &mut state)?; show_dialog("acpi-inhibitor status", &msg) }).iores()?; } app.add_menu_separator().iores()?; macro_rules! switcher_menu { ($label:expr, $keys:expr) => {{ let state = state.clone(); app.add_menu_item($label, move |mut window| { let mut state = state.as_ref().lock().unwrap(); let keys = $keys; if state.what_inhibited() == Some(keys) { state.uninhibit()?; } else { state.inhibit(keys)?; } update_icon(&mut window, &mut state)?; Ok::<_, io::Error>(()) }).iores()?; }} } switcher_menu!("Shutdown/sleep/idle", &["shutdown".to_string(), "sleep".to_string(), "idle".to_string()]); switcher_menu!("Lid switch", &["handle-lid-switch".to_string()]); app.add_menu_separator().iores()?; app.add_menu_item("Quit", |window| { window.quit(); Ok::<_, systray::Error>(()) }).iores()?; app.wait_for_message().iores() }