i3monkit/widgets/
battery.rs

1use crate::widget::{Widget, WidgetUpdate};
2use crate::protocol::{Block, ColorRGB};
3
4use chrono::Duration;
5
6use std::path::PathBuf;
7
8use std::fs::File;
9use std::io::{BufRead, BufReader};
10
11const BATTERY_STATUS_PREFIX:&'static str = "/sys/class/power_supply/BAT";
12
13#[derive(Debug)]
14enum BatteryStatus {
15    Unknown,
16    Charging,
17    Discharging,
18    Full,
19}
20
21impl BatteryStatus {
22    fn from_str(status:&str) -> BatteryStatus {
23        match status {
24            "Charging" => BatteryStatus::Charging,
25            "Discharging" => BatteryStatus::Discharging,
26            "Full" => BatteryStatus::Full,
27            _ => BatteryStatus::Unknown
28        }
29    }
30
31    fn get_status_text(&self) -> &'static str {
32        match self {
33            BatteryStatus::Unknown => "<span foreground=\"grey\">U</span> ",
34            BatteryStatus::Charging => "<span foreground=\"green\">C</span> ",
35            BatteryStatus::Discharging => "<span foreground=\"red\">D</span> ",
36            BatteryStatus::Full => "<span foreground=\"green\">F</span> "
37        }
38    }
39}
40
41#[derive(Debug)]
42struct BatteryState {
43    full: u32,
44    now : u32,
45    design: u32,
46    voltage: u32,
47    rate: u32,
48    stat: BatteryStatus,
49}
50
51impl BatteryState {
52    fn get(idx:u32) -> Option<Self> {
53        let mut root_path = PathBuf::new();
54        root_path.push(format!("{}{}", BATTERY_STATUS_PREFIX, idx));
55        root_path.push("dummy");
56        
57        let mut read_battery_status = move |status:&str| {
58            root_path.set_file_name(status);
59            if let Ok(file) = File::open(root_path.as_path()) {
60                let reader = BufReader::new(file);
61                for line in reader.lines() {
62                    if let Ok(line) = line {
63                        return Some(line.trim().to_string());
64                    }
65                }
66            } 
67            None
68        };
69
70        let stat = BatteryStatus::from_str(&read_battery_status("status")?);
71        let full = u32::from_str_radix(&read_battery_status("charge_full")?, 10).ok()?;
72        let now  = u32::from_str_radix(&read_battery_status("charge_now")?, 10).ok()?;
73        let rate = u32::from_str_radix(&read_battery_status("current_now")?, 10).ok()?;
74        let design = u32::from_str_radix(&read_battery_status("charge_full_design")?, 10).ok()?;
75        let voltage = u32::from_str_radix(&read_battery_status("voltage_now")?, 10).ok()?;
76
77        Some(Self {full, now, rate, stat, design, voltage})
78    }
79
80    fn percentage(&self) -> u8 {
81        ((self.now * 100) as f32 / self.design as f32).round() as u8
82    }
83
84    fn time_remaining(&self) -> Option<(Duration, f32)> {
85        let target = match self.stat {
86            BatteryStatus::Charging => self.full,
87            BatteryStatus::Discharging => 0,
88            _ => {return None; }
89        };
90
91        let remaning = if target < self.now { 
92            self.now - target 
93        } else {
94            target - self.now 
95        };
96
97        let time = Duration::seconds((3600.0 * (remaning as f32) / (self.rate as f32)).round() as i64);
98        let power = (self.voltage as f32/ 1_000_000.0) * (self.rate as f32 / 1_000_000.0);
99
100        return Some((time,power));
101    }
102}
103
104/// The battery status widget.
105///
106/// This widget shows the battery status of a laptop, such as, the percentage battery level,
107/// current status (charing, discharing, full, etc), current discharging/charing rate, estimated
108/// reminaing time, etc...
109pub struct BatteryWidget(u32);
110
111impl BatteryWidget {
112    /// Create a new widget for specified battery
113    ///
114    /// **idx** The index for the battery, for most of the system with only 1 battery, it should be
115    /// 0
116    pub fn new(idx:u32) -> Self { 
117        Self(idx)
118    }
119
120    fn render_batter_status(&self) -> (String, i32) {
121        if let Some(info) = BatteryState::get(self.0) {
122            let mut ret = format!("{} {}%", info.stat.get_status_text(), info.percentage());
123            if let Some((time,power)) = info.time_remaining() {
124                ret.push_str(&format!(" [{:02}:{:02}|{:3.1}W]", time.num_hours(), time.num_minutes() % 60, power)); 
125            } 
126
127            let sev = match info.percentage() {
128                x if x > 50 => 3,
129                x if x > 30 => 2,
130                x if x > 10 => 1,
131                _           => 0
132            };
133
134            return (ret, sev);
135        }
136
137        return ("Unknown".to_string(), -1);
138    }
139}
140
141impl Widget for BatteryWidget {
142    fn update(&mut self) -> Option<WidgetUpdate> {
143        let (msg, sev) = self.render_batter_status();
144
145        let mut data = Block::new();
146
147        data.use_pango();
148        data.append_full_text(&msg);
149
150        match sev {
151            0 => {data.color(ColorRGB::red());} ,
152            1 => {data.color(ColorRGB::yellow());},
153            _ => {}
154        }
155
156        return Some(WidgetUpdate {
157            refresh_interval: std::time::Duration::new(5,0),
158            data: Some(data)
159        });
160    }
161}