use std::{ collections::HashMap, env, fs::File, io::Read, process::Command, thread::sleep, time::Duration, }; static SLEEP: u64 = 1; 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"); 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 { sleep(Duration::from_secs(SLEEP)); let cur_cap = read_file(&cap_path); if cur_cap != cap_cache { cap_cache.clone_from(&cur_cap); let bat_status = &read_file(&status_path); if bat_status == "Charging" { continue; }; if cur_cap == "100" && (bat_status == "Discharging" || bat_status == "Not charging") { notify("n", None); continue; } let val = cur_cap .parse::() .expect("Couldn't parse capacity value"); if let Some(lvl) = warnings.get(&val) { notify(lvl, Some(val)); }; } } } 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: Option) { let urgency = match lvl { "l" => "low", "n" => "normal", "c" => "critical", _ => unreachable!(), }; let notif = if let Some(remaining) = remaining { &format!("Remaining battery capacity: {remaining}") } else { "Battery is full" }; 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"); }