summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorTom Smeding <tom.smeding@gmail.com>2020-05-04 22:33:14 +0200
committerTom Smeding <tom.smeding@gmail.com>2020-05-04 22:33:24 +0200
commit982fb3227ad438e8c7a16ac75ae6f296df2c8bd9 (patch)
tree7ab2fdd70d633454772d2d568505dc486ed69783 /src
Initial working versionHEADmaster
Diffstat (limited to 'src')
-rw-r--r--src/error.rs27
-rw-r--r--src/main.rs184
2 files changed, 211 insertions, 0 deletions
diff --git a/src/error.rs b/src/error.rs
new file mode 100644
index 0000000..f25bc5d
--- /dev/null
+++ b/src/error.rs
@@ -0,0 +1,27 @@
+use std::io;
+
+pub trait IntoIOError {
+ fn ioerr(self) -> io::Error;
+ fn perror(self, parent: io::Error) -> io::Error;
+}
+
+// This impl bound is taken directly from the io::Error::new function.
+impl<E: Into<Box<dyn std::error::Error + Send + Sync>>> IntoIOError for E {
+ fn ioerr(self) -> io::Error {
+ io::Error::new(io::ErrorKind::Other, self)
+ }
+
+ fn perror(self, parent: io::Error) -> io::Error {
+ io::Error::new(parent.kind(), format!("{}: {}", self.into(), parent))
+ }
+}
+
+pub trait IntoIOResult<T> {
+ fn iores(self) -> io::Result<T>;
+}
+
+impl<T, E: IntoIOError> IntoIOResult<T> for Result<T, E> {
+ fn iores(self) -> io::Result<T> {
+ self.map_err(|e| e.ioerr())
+ }
+}
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..7eb4a41
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,184 @@
+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<String>)>,
+ 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<String> {
+ 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<PathBuf> {
+ 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()
+}