i3monkit/widgets/
battery.rs1use 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
104pub struct BatteryWidget(u32);
110
111impl BatteryWidget {
112 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}