use std::{ collections::HashMap, env, fs::File, io::Read, process::Command, thread::sleep, time::Duration, }; fn main() { let mut args = env::args(); assert!( args.len() > 2, "Too few arguments, provide battery id and values with warning levels" ); // Skip process (argc[0]) args.next(); // 1: Battery ID let bat = args.next().expect("Invalid Battery id"); let bat_path = format!("/sys/class/power_supply/{bat}"); let cap_path = format!("{bat_path}/capacity"); let status_path = format!("{bat_path}/status"); // 2: Sleep let sleep_secs = args .next() .expect("Invalid sleep duration") .parse::() .expect("Invalid sleep value"); let mut warnings: HashMap = HashMap::new(); for arg in args { let (lvl, value) = arg.split_at(1); assert!( lvl == "l" || lvl == "n" || lvl == "c", "Unknown notification level" ); warnings.insert( value.parse::().expect("Invalid battery value"), lvl.to_string(), ); } let mut cap_cache = String::new(); loop { let cur_cap = read_file(&cap_path); if cur_cap != cap_cache { cap_cache.clone_from(&cur_cap); if &read_file(&status_path) == "Charging" { continue; }; let val = cur_cap .parse::() .expect("Couldn't parse capacity value"); if let Some(lvl) = warnings.get(&val) { notify(lvl, val); }; } sleep(Duration::from_secs(sleep_secs)); } } fn read_file(path: &str) -> String { let mut f = File::open(path).expect("Could't open file"); let mut buf = String::new(); f.read_to_string(&mut buf).expect("Couldn't read file"); buf.trim().to_string() } fn notify(lvl: &str, remaining: u8) { let urgency = match lvl { "l" => "low", "n" => "normal", "c" => "critical", _ => unreachable!(), }; let notif = format!("Remaining battery capacity: {remaining}"); Command::new("notify-send") .arg("--app-name=esbnd") .arg("--category=device") .arg("--icon=battery-low-symbolic") .arg("-u") .arg(urgency) .arg(notif) .spawn() .expect("Couldn't send notification"); }