|
@@ -0,0 +1,180 @@
|
|
|
+use std::cmp;
|
|
|
+use std::fs::File;
|
|
|
+use std::io::prelude::*;
|
|
|
+use std::io;
|
|
|
+
|
|
|
+use style;
|
|
|
+use widgets::{Message, Update, Widget, WidgetParams};
|
|
|
+use ui::context::Context;
|
|
|
+
|
|
|
+#[derive(PartialEq)]
|
|
|
+enum Status {
|
|
|
+ DISCHARGING,
|
|
|
+ CHARGING,
|
|
|
+ FULL
|
|
|
+}
|
|
|
+
|
|
|
+struct StatFiles {
|
|
|
+ current: File,
|
|
|
+ full: File,
|
|
|
+ status: File
|
|
|
+}
|
|
|
+
|
|
|
+struct Stats {
|
|
|
+ current: u32,
|
|
|
+ full: u32,
|
|
|
+ status: Status
|
|
|
+}
|
|
|
+
|
|
|
+pub struct Battery {
|
|
|
+ context: Context,
|
|
|
+ stat_files: Vec<StatFiles>,
|
|
|
+ percentage: u32,
|
|
|
+ status: Status
|
|
|
+}
|
|
|
+
|
|
|
+pub fn battery(params: WidgetParams) -> Box<Widget> {
|
|
|
+ let config: BatteryConfig = params.config.try_into().unwrap();
|
|
|
+ let stat_files: Vec<StatFiles> = config.devices.iter()
|
|
|
+ .flat_map(|dev| open_stats(&dev).ok())
|
|
|
+ .collect();
|
|
|
+ let widget = Battery {
|
|
|
+ context: params.context,
|
|
|
+ stat_files: stat_files,
|
|
|
+ percentage: 0,
|
|
|
+ status: Status::DISCHARGING
|
|
|
+ };
|
|
|
+ Box::new(widget)
|
|
|
+}
|
|
|
+
|
|
|
+impl Battery {
|
|
|
+ fn icon(&self) -> &'static str {
|
|
|
+ match self.status {
|
|
|
+ Status::FULL => "",
|
|
|
+ Status::CHARGING => "",
|
|
|
+ Status::DISCHARGING => {
|
|
|
+ match (self.percentage+1) / 25 {
|
|
|
+ 0 => "",
|
|
|
+ 1 => "",
|
|
|
+ 2 => "",
|
|
|
+ _ => ""
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ fn status(&self) -> String {
|
|
|
+ format!("{}%", cmp::min(100, self.percentage))
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl Widget for Battery {
|
|
|
+ fn render(&mut self, x: u16, width: u16) {
|
|
|
+ style::render(&self.context, self.icon(), &self.status(), x, width);
|
|
|
+ }
|
|
|
+
|
|
|
+ fn width(&mut self) -> u16 {
|
|
|
+ style::width(&self.context, self.icon(), &self.status())
|
|
|
+ }
|
|
|
+
|
|
|
+ fn handle_event(&mut self, event: &Message) -> Update {
|
|
|
+ match event {
|
|
|
+ &Message::Update => {
|
|
|
+ let empty_stat = Stats {
|
|
|
+ current: 0,
|
|
|
+ full: 0,
|
|
|
+ status: Status::FULL
|
|
|
+ };
|
|
|
+ let stats = self.stat_files
|
|
|
+ .iter_mut()
|
|
|
+ .flat_map(|file| read_stats(file).ok())
|
|
|
+ .fold(empty_stat, |acc, elem| Stats {
|
|
|
+ current: acc.current + elem.current,
|
|
|
+ full: acc.full + elem.full,
|
|
|
+ status: combine_status(acc.status, elem.status)
|
|
|
+ });
|
|
|
+
|
|
|
+ let percentage = 100 * stats.current / stats.full;
|
|
|
+ let status = stats.status;
|
|
|
+
|
|
|
+ if self.percentage == percentage && self.status == status {
|
|
|
+ Update::Nothing
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ self.percentage = percentage;
|
|
|
+ self.status = status;
|
|
|
+ Update::Relayout
|
|
|
+ }
|
|
|
+ },
|
|
|
+ _ => Update::Nothing
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+fn combine_status(a: Status, b: Status) -> Status {
|
|
|
+ match (a, b) {
|
|
|
+ (_, Status::DISCHARGING) => Status::DISCHARGING,
|
|
|
+ (Status::DISCHARGING, _) => Status::DISCHARGING,
|
|
|
+ (_, Status::CHARGING) => Status::CHARGING,
|
|
|
+ (Status::CHARGING, _) => Status::CHARGING,
|
|
|
+ _ => Status::FULL
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+fn open_stats(device: &str) -> Result<StatFiles, io::Error> {
|
|
|
+ let path = format!("/sys/class/power_supply/{}", device);
|
|
|
+ let charge_now = File::open(format!("{}/charge_now", path));
|
|
|
+ let charge_full = File::open(format!("{}/charge_full", path));
|
|
|
+ let energy_now = File::open(format!("{}/energy_now", path));
|
|
|
+ let energy_full = File::open(format!("{}/energy_full", path));
|
|
|
+
|
|
|
+ let status_file = try!(File::open(format!("{}/status", path)));
|
|
|
+ let current_file = try!(charge_now.or(energy_now));
|
|
|
+ let full_file = try!(charge_full.or(energy_full));
|
|
|
+
|
|
|
+ Ok(StatFiles {
|
|
|
+ status: status_file,
|
|
|
+ current: current_file,
|
|
|
+ full: full_file
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+fn read_stats(files: &mut StatFiles) -> Result<Stats, io::Error> {
|
|
|
+ let mut s = String::new();
|
|
|
+
|
|
|
+ assert!(files.current.read_to_string(&mut s).is_ok());
|
|
|
+ let current: u32 = s.trim().parse().unwrap();
|
|
|
+ assert!(files.current.seek(io::SeekFrom::Start(0)).is_ok());
|
|
|
+
|
|
|
+ s.clear();
|
|
|
+
|
|
|
+ assert!(files.full.read_to_string(&mut s).is_ok());
|
|
|
+ let full: u32 = s.trim().parse().unwrap();
|
|
|
+ assert!(files.full.seek(io::SeekFrom::Start(0)).is_ok());
|
|
|
+
|
|
|
+ s.clear();
|
|
|
+
|
|
|
+ assert!(files.status.read_to_string(&mut s).is_ok());
|
|
|
+ assert!(files.status.seek(io::SeekFrom::Start(0)).is_ok());
|
|
|
+
|
|
|
+ Ok(Stats {
|
|
|
+ current: current,
|
|
|
+ full: full,
|
|
|
+ status: parse_status(s.trim())
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+fn parse_status(s: &str) -> Status {
|
|
|
+ match s {
|
|
|
+ "Full" => Status::FULL,
|
|
|
+ "Unknown" => Status::FULL,
|
|
|
+ "Charging" => Status::CHARGING,
|
|
|
+ _ => Status::DISCHARGING
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#[derive(Deserialize)]
|
|
|
+struct BatteryConfig {
|
|
|
+ devices: Vec<String>
|
|
|
+}
|