#![feature( import_trait_associated_functions, string_deref_patterns, iter_array_chunks, portable_simd )] use anyhow::*; use grapher::{truncwrite, Grapher}; use std::array; use std::convert::identity; use std::fs::read_to_string as read; use std::io::Write; use std::io::{stdout, Read}; use std::path::PathBuf; use std::result::Result::Ok as Rok; use std::sync::OnceLock; use std::thread::sleep; use std::time::Duration; use termion::color::*; use termion::cursor::Hide; use termion::raw::IntoRawMode; use termion::screen::IntoAlternateScreen; use termion::{async_stdin, clear, cursor, style}; fn fetch>(find: &str, f: impl FnOnce(&str) -> Result) -> Result { f(read(path.get().unwrap().join("uevent"))? .lines() .filter_map(|x| x.split_once('=')) .find(|x| x.0 == find) .ok_or(anyhow!("no power??"))? .1) .map_err(Into::into) } static path: OnceLock = OnceLock::new(); fn main() -> Result<()> { let x = std::fs::read_dir("/sys/class/power_supply")? .filter_map(Result::ok) .find(|x| matches!(read(x.path().join("type")), Rok(x) if x.trim_ascii() == "Battery")) .ok_or(anyhow!("batless"))? .path(); path.set(x.clone()).unwrap(); let manufacturer = fetch("POWER_SUPPLY_MANUFACTURER", |x| Ok(x.to_string()))?; let model = fetch("POWER_SUPPLY_MODEL_NAME", |x| Ok(x.to_string()))?; let cap = fetch("POWER_SUPPLY_ENERGY_FULL_DESIGN", str::parse::)? / 1e6; let charge = match &*std::env::args().nth(1).unwrap_or("charge".into()) { x @ ("charge" | "usage") => x == "charge", _ => bail!("arg must be {{usage, charge}}"), }; let mut g = Grapher::new()?; let mut d = 1; let mut stdout = stdout().into_raw_mode()?.into_alternate_screen()?; let mut stdin = async_stdin(); write!(stdout, "{}{}{}", Hide, clear::All, style::Reset).unwrap(); let mut second = 0; 'out: loop { let mut key = 0; while stdin.read(array::from_mut(&mut key)).unwrap() != 0 { match key { b'q' => break 'out, b'+' => d = (d + 1).min(100), b'-' if d > 1 => d -= 1, b'-' => d = 1, _ => (), } } g.draw(|_| None, identity)?; let current = fetch("POWER_SUPPLY_CAPACITY", str::parse::)?; let whs = fetch("POWER_SUPPLY_ENERGY_NOW", str::parse::)? / 1e6; let usage = fetch("POWER_SUPPLY_POWER_NOW", str::parse::)? / 1e6; write!(g.buffer, "{}{}", White.fg_str(), cursor::Goto(1, 1))?; truncwrite!( g.buffer, " {d}s/f ──── {usage:.2}W → ── {current}% {whs:.1}Wh / {cap:.0}Wh ── {manufacturer} {model}" )?; write!(stdout, "{}", Rgb(40, 185, 119).fg_string())?; stdout.write_all(&g.buffer)?; stdout.flush()?; if second % (d * 2) == 0 { g.push_point(if charge { current / 1e2 } else { usage / 40. }); } sleep(Duration::from_millis(500)); second += 1; } println!("\x1B[?7l"); Ok(()) }