diff --git a/icons/cover.svg b/icons/cover.svg new file mode 100644 index 0000000..f7df12b --- /dev/null +++ b/icons/cover.svg @@ -0,0 +1 @@ + diff --git a/src/app.rs b/src/app.rs index d36ba60..b3d474f 100644 --- a/src/app.rs +++ b/src/app.rs @@ -671,7 +671,7 @@ pub fn run() -> Result<(), Error> { task.id == TaskId::Suspend) { continue; } - if let Ok(v) = context.battery.capacity() { + if let Ok(v) = context.battery.capacity().map(|v| v[0]) { if v < context.settings.battery.power_off { power_off(view.as_mut(), &mut history, &mut updating, &mut context); exit_status = ExitStatus::PowerOff; diff --git a/src/battery/fake.rs b/src/battery/fake.rs index 08b4a78..1449f2c 100644 --- a/src/battery/fake.rs +++ b/src/battery/fake.rs @@ -13,11 +13,11 @@ impl FakeBattery { } impl Battery for FakeBattery { - fn capacity(&mut self) -> Result { - Ok(self.capacity) + fn capacity(&mut self) -> Result, Error> { + Ok(vec![self.capacity]) } - fn status(&mut self) -> Result { - Ok(self.status) + fn status(&mut self) -> Result, Error> { + Ok(vec![self.status]) } } diff --git a/src/battery/kobo.rs b/src/battery/kobo.rs index 04031e4..3b12329 100644 --- a/src/battery/kobo.rs +++ b/src/battery/kobo.rs @@ -1,20 +1,32 @@ use std::fs::File; use std::path::Path; use std::io::{Read, Seek, SeekFrom}; -use anyhow::{Error, format_err}; +use anyhow::Error; use crate::device::CURRENT_DEVICE; use super::{Battery, Status}; const BATTERY_INTERFACE_A: &str = "/sys/class/power_supply/mc13892_bat"; const BATTERY_INTERFACE_B: &str = "/sys/class/power_supply/battery"; +const POWER_COVER_INTERFACE: &str = "/sys/class/misc/cilix"; const BATTERY_CAPACITY: &str = "capacity"; const BATTERY_STATUS: &str = "status"; +const POWER_COVER_CAPACITY: &str = "cilix_bat_capacity"; +const POWER_COVER_STATUS: &str = "charge_status"; +const POWER_COVER_CONNECTED: &str = "cilix_conn"; + +pub struct PowerCover { + capacity: File, + status: File, + connected: File, +} + // TODO: health, technology, time_to_full_now, time_to_empty_now pub struct KoboBattery { capacity: File, status: File, + power_cover: Option, } impl KoboBattery { @@ -26,28 +38,79 @@ impl KoboBattery { }; let capacity = File::open(base.join(BATTERY_CAPACITY))?; let status = File::open(base.join(BATTERY_STATUS))?; - Ok(KoboBattery { capacity, status }) + let power_cover = if CURRENT_DEVICE.has_power_cover() { + let base = Path::new(POWER_COVER_INTERFACE); + let capacity = File::open(base.join(POWER_COVER_CAPACITY))?; + let status = File::open(base.join(POWER_COVER_STATUS))?; + let connected = File::open(base.join(POWER_COVER_CONNECTED))?; + Some(PowerCover { capacity, status, connected }) + } else { + None + }; + Ok(KoboBattery { capacity, status, power_cover }) + } +} + +impl KoboBattery { + fn is_power_cover_connected(&mut self) -> Result { + if let Some(power_cover) = self.power_cover.as_mut() { + let mut buf = String::new(); + power_cover.connected.seek(SeekFrom::Start(0))?; + power_cover.connected.read_to_string(&mut buf)?; + Ok(buf.trim_end().parse::().map_or(false, |v| v == 1)) + } else { + Ok(false) + } } } impl Battery for KoboBattery { - fn capacity(&mut self) -> Result { + fn capacity(&mut self) -> Result, Error> { let mut buf = String::new(); self.capacity.seek(SeekFrom::Start(0))?; self.capacity.read_to_string(&mut buf)?; - Ok(buf.trim_end().parse::().unwrap_or(0.0)) + let capacity = buf.trim_end().parse::() + .unwrap_or(0.0); + if matches!(self.is_power_cover_connected(), Ok(true)) { + let mut buf = String::new(); + self.power_cover.iter_mut().for_each(|power_cover| { + power_cover.capacity.seek(SeekFrom::Start(0)).ok(); + power_cover.capacity.read_to_string(&mut buf).ok(); + }); + let aux_capacity = buf.trim_end().parse::() + .unwrap_or(0.0); + Ok(vec![capacity, aux_capacity]) + } else { + Ok(vec![capacity]) + } } - fn status(&mut self) -> Result { + fn status(&mut self) -> Result, Error> { let mut buf = String::new(); self.status.seek(SeekFrom::Start(0))?; self.status.read_to_string(&mut buf)?; - match buf.trim_end() { - "Discharging" => Ok(Status::Discharging), - "Charging" => Ok(Status::Charging), - "Not charging" | "Full" => Ok(Status::Charged), - _ => Err(format_err!("unknown battery status")), + let status = match buf.trim_end() { + "Discharging" => Status::Discharging, + "Charging" => Status::Charging, + "Not charging" | "Full" => Status::Charged, + _ => Status::Unknown, + }; + if matches!(self.is_power_cover_connected(), Ok(true)) { + let mut buf = String::new(); + self.power_cover.iter_mut().for_each(|power_cover| { + power_cover.status.seek(SeekFrom::Start(0)).ok(); + power_cover.status.read_to_string(&mut buf).ok(); + }); + let aux_status = match buf.trim_end().parse::() { + Ok(0) => Status::Discharging, + Ok(2) => Status::Charging, + Ok(3) => Status::Charged, + _ => Status::Unknown, + }; + Ok(vec![status, aux_status]) + } else { + Ok(vec![status]) } } } diff --git a/src/battery/mod.rs b/src/battery/mod.rs index c876c09..d63f392 100644 --- a/src/battery/mod.rs +++ b/src/battery/mod.rs @@ -11,11 +11,11 @@ pub enum Status { Discharging, Charging, Charged, + Unknown // Full, - // Unknown } pub trait Battery { - fn capacity(&mut self) -> Result; - fn status(&mut self) -> Result; + fn capacity(&mut self) -> Result, Error>; + fn status(&mut self) -> Result, Error>; } diff --git a/src/device.rs b/src/device.rs index 36c2e38..da74b74 100644 --- a/src/device.rs +++ b/src/device.rs @@ -232,6 +232,10 @@ impl Device { Model::Sage | Model::Libra2) } + pub fn has_power_cover(&self) -> bool { + matches!(self.model, Model::Sage) + } + pub fn has_removable_storage(&self) -> bool { matches!(self.model, Model::AuraH2O | Model::Aura | Model::AuraHD | diff --git a/src/view/battery.rs b/src/view/battery.rs index 0571908..1570949 100644 --- a/src/view/battery.rs +++ b/src/view/battery.rs @@ -36,8 +36,8 @@ impl Battery { } pub fn update(&mut self, rq: &mut RenderQueue, context: &mut Context) { - self.capacity = context.battery.capacity().unwrap_or(self.capacity); - self.status = context.battery.status().unwrap_or(self.status); + self.capacity = context.battery.capacity().map_or(self.capacity, |v| v[0]); + self.status = context.battery.status().map_or(self.status, |v| v[0]); rq.add(RenderData::new(self.id, self.rect, UpdateMode::Gui)); } } diff --git a/src/view/common.rs b/src/view/common.rs index 7feffdb..3122c65 100644 --- a/src/view/common.rs +++ b/src/view/common.rs @@ -156,13 +156,21 @@ pub fn toggle_battery_menu(view: &mut dyn View, rect: Rectangle, enable: Option< if let Some(false) = enable { return; } - let text = match (context.battery.status(), context.battery.capacity()) { - (Ok(status), Ok(capacity)) => format!("{:?} {}%", status, capacity), - (Ok(status), Err(..)) => format!("{:?}", status), - (Err(..), Ok(capacity)) => format!("{} %", capacity), - _ => "Unknown".to_string(), - }; - let entries = vec![EntryKind::Message(text)]; + + let mut entries = Vec::new(); + + match context.battery.status().ok().zip(context.battery.capacity().ok()) { + Some((status, capacity)) => { + for (i, (s, c)) in status.iter().zip(capacity.iter()).enumerate() { + entries.push(EntryKind::Message(format!("{:?} {}%", s, c), + if i > 0 { Some("cover".to_string()) } else { None })); + } + }, + _ => { + entries.push(EntryKind::Message("Information Unavailable".to_string(), None)); + }, + } + let battery_menu = Menu::new(rect, ViewId::BatteryMenu, MenuKind::DropDown, entries, context); rq.add(RenderData::new(battery_menu.id(), *battery_menu.rect(), UpdateMode::Gui)); view.children_mut().push(Box::new(battery_menu) as Box); @@ -181,7 +189,7 @@ pub fn toggle_clock_menu(view: &mut dyn View, rect: Rectangle, enable: Option); diff --git a/src/view/icon.rs b/src/view/icon.rs index 7bf32de..d5a136a 100644 --- a/src/view/icon.rs +++ b/src/view/icon.rs @@ -27,7 +27,7 @@ lazy_static! { "close", "check_mark-small", "check_mark", "check_mark-large", "bullet", "arrow-left", "arrow-right", "angle-down", "angle-up", "crop", "toc", "font_family", "font_size", "line_height", "align-justify", "align-left", "align-right", - "align-center", "margin", "plug", "enclosed_menu", "contrast", "gray"].iter().cloned() { + "align-center", "margin", "plug", "cover", "enclosed_menu", "contrast", "gray"].iter().cloned() { let path = dir.join(&format!("{}.svg", name)); let doc = PdfOpener::new().and_then(|o| o.open(path)).unwrap(); let pixmap = doc.page(0).and_then(|p| p.pixmap(scale)).unwrap(); diff --git a/src/view/menu_entry.rs b/src/view/menu_entry.rs index 27564dd..19681c1 100644 --- a/src/view/menu_entry.rs +++ b/src/view/menu_entry.rs @@ -86,7 +86,7 @@ impl View for MenuEntry { EntryKind::SubMenu(_, ref entries) | EntryKind::More(ref entries) => { bus.push_back(Event::SubMenu(self.anchor, entries.clone())); }, - EntryKind::Message(_) => { + EntryKind::Message(..) => { bus.push_back(Event::Validate); }, _ => (), @@ -145,6 +145,7 @@ impl View for MenuEntry { let (icon_name, x_offset) = match self.kind { EntryKind::CheckBox(_, _, value) if value => ("check_mark", 0), EntryKind::RadioButton(_, _, value) if value => ("bullet", 0), + EntryKind::Message(_, Some(ref name)) => (name.as_str(), 0), EntryKind::SubMenu(..) | EntryKind::More(..) => ("angle-right-small", self.rect.width() as i32 - padding / 2), diff --git a/src/view/mod.rs b/src/view/mod.rs index 61cad7e..fa4dd2a 100644 --- a/src/view/mod.rs +++ b/src/view/mod.rs @@ -494,7 +494,7 @@ pub enum TextKind { #[derive(Debug, Clone)] pub enum EntryKind { - Message(String), + Message(String, Option), Command(String, EntryId), CheckBox(String, EntryId, bool), RadioButton(String, EntryId, bool), @@ -585,7 +585,7 @@ impl EntryKind { pub fn text(&self) -> &str { match *self { - EntryKind::Message(ref s) | + EntryKind::Message(ref s, ..) | EntryKind::Command(ref s, ..) | EntryKind::CheckBox(ref s, ..) | EntryKind::RadioButton(ref s, ..) | diff --git a/src/view/top_bar.rs b/src/view/top_bar.rs index da28f98..bfa1185 100644 --- a/src/view/top_bar.rs +++ b/src/view/top_bar.rs @@ -43,8 +43,8 @@ impl TopBar { children.push(Box::new(title_label) as Box); children.push(Box::new(clock_label) as Box); - let capacity = context.battery.capacity().unwrap_or(0.0); - let status = context.battery.status().unwrap_or(crate::battery::Status::Discharging); + let capacity = context.battery.capacity().map_or(0.0, |v| v[0]); + let status = context.battery.status().map_or(crate::battery::Status::Discharging, |v| v[0]); let battery_widget = Battery::new(rect![rect.max - pt!(3*side, side), rect.max - pt!(2*side, 0)], capacity,