diff --git a/src/battery.rs b/src/battery.rs new file mode 100644 index 0000000..2c01e2f --- /dev/null +++ b/src/battery.rs @@ -0,0 +1,193 @@ +use crate::buffer::Buffer; +use crate::color::Color; +use crate::draw::{draw_bar, draw_text, draw_box, ROBOTO_REGULAR}; +use crate::module::{Input, ModuleImpl}; + +use std::thread; +use std::sync::{Arc, Mutex}; +use std::sync::mpsc::Sender; + +use dbus; +use chrono::{DateTime, Local}; + +fn get_upower_property(con: &dbus::Connection, device_path: &str, property: &str) -> Result { + let msg = dbus::Message::new_method_call( + "org.freedesktop.UPower", + device_path, + "org.freedesktop.DBus.Properties", + "Get", + ).map_err(|_| ::std::io::Error::new(::std::io::ErrorKind::Other, "could not send make dbus method call"))? + .append2( + dbus::MessageItem::Str("org.freedesktop.UPower.Device".to_string()), + dbus::MessageItem::Str(property.to_string()), + );; + + con.send_with_reply_and_block(msg, 1000).map_err(|_| {::std::io::Error::new(::std::io::ErrorKind::Other, "could not send dbus message")}) +} + +pub struct UpowerBattery { + device_path: String, + inner: Arc> +} + +pub struct UpowerBatteryInner { + state: UpowerBatteryState, + capacity: f64, + dirty: bool, +} + +enum UpowerBatteryState { + Charging, + Discharging, + Empty, + Full, + NotCharging, + Unknown, +} + +impl UpowerBattery { + pub fn from_device(device: &str) -> Result { + let device_path = format!("/org/freedesktop/UPower/devices/battery_{}", device); + let con = dbus::Connection::get_private(dbus::BusType::System) + .map_err(|_| ::std::io::Error::new(::std::io::ErrorKind::Other, "could not get dbus connection"))?; + + let upower_type: dbus::arg::Variant = match get_upower_property(&con, &device_path, "Type")?.get1() { + Some(v) => v, + None => { + return Err(::std::io::Error::new(::std::io::ErrorKind::Other, "no such upower device")); + } + }; + + // https://upower.freedesktop.org/docs/Device.html#Device:Type + if upower_type.0 != 2 { + return Err(::std::io::Error::new( + ::std::io::ErrorKind::Other, + "UPower device is not a battery.", + )); + } + + let capacity: f64 = match get_upower_property(&con, &device_path, "Percentage")?.get1::>() { + Some(v) => v.0, + None => { + return Err(::std::io::Error::new(::std::io::ErrorKind::Other, "no such upower device")); + } + }; + let state: UpowerBatteryState = match get_upower_property(&con, &device_path, "State")?.get1::>() { + Some(v) => match v.0 { + 1 => UpowerBatteryState::Charging, + 2 => UpowerBatteryState::Discharging, + 3 => UpowerBatteryState::Empty, + 4 => UpowerBatteryState::Full, + 5 => UpowerBatteryState::NotCharging, + 6 => UpowerBatteryState::Discharging, + _ => UpowerBatteryState::Unknown, + }, + None => { + return Err(::std::io::Error::new(::std::io::ErrorKind::Other, "no such upower device")); + } + }; + + Ok(UpowerBattery { + device_path, + inner: Arc::new(Mutex::new(UpowerBatteryInner{ + capacity: capacity, + state: state, + dirty: true + })), + }) + } + + pub fn new(listener: Sender) -> Result { + let d = UpowerBattery::from_device("BAT0")?; + let path = d.device_path.clone(); + let inner = d.inner.clone(); + thread::spawn(move || { + let con = dbus::Connection::get_private(dbus::BusType::System) + .expect("Failed to establish D-Bus connection."); + let rule = format!( + "type='signal',\ + path='{}',\ + interface='org.freedesktop.DBus.Properties',\ + member='PropertiesChanged'", + path + ); + + // First we're going to get an (irrelevant) NameAcquired event. + con.incoming(10_000).next(); + + con.add_match(&rule) + .expect("Failed to add D-Bus match rule."); + + loop { + if con.incoming(10_000).next().is_some() { + let capacity = get_upower_property(&con, &path, "Percentage").unwrap().get1::>().unwrap().0; + let state = match get_upower_property(&con, &path, "State").unwrap().get1::>().unwrap().0 { + 1 => UpowerBatteryState::Charging, + 2 => UpowerBatteryState::Discharging, + 3 => UpowerBatteryState::Empty, + 4 => UpowerBatteryState::Full, + 5 => UpowerBatteryState::NotCharging, + 6 => UpowerBatteryState::Discharging, + _ => UpowerBatteryState::Unknown, + }; + let mut inner = inner.lock().unwrap(); + inner.state = state; + inner.capacity = capacity; + inner.dirty = true; + listener.send(true).unwrap(); + } + } + }); + + Ok(d) + + } + +} + +impl ModuleImpl for UpowerBattery { + fn draw( + &self, + buf: &mut Buffer, + bg: &Color, + _time: &DateTime, + ) -> Result, ::std::io::Error> { + buf.memset(bg); + let c = Color::new(1.0, 1.0, 1.0, 1.0); + let inner = self.inner.lock().unwrap(); + draw_text( + ROBOTO_REGULAR, + &mut buf.subdimensions((0, 0, 128, 24)), + bg, + &c, + 24.0, + "battery", + )?; + draw_bar( + &mut buf.subdimensions((128, 0, 432, 24)), + &c, + 432, + 24, + (inner.capacity as f32) / 100.0, + )?; + + draw_box( + &mut buf.subdimensions((128, 0, 432, 24)), + &c, + (432, 24), + )?; + Ok(vec![buf.get_signed_bounds()]) + } + + fn update(&mut self, _time: &DateTime, force: bool) -> Result { + let mut inner = self.inner.lock().unwrap(); + if inner.dirty || force { + inner.dirty = false; + Ok(true) + } else { + Ok(false) + } + } + + fn input(&mut self, _input: Input) {} +} diff --git a/src/main.rs b/src/main.rs index 66b4c69..8dc3d4a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,6 +28,7 @@ mod color; mod draw; mod module; mod sound; +mod battery; use crate::backlight::Backlight; use crate::buffer::Buffer; @@ -36,6 +37,9 @@ use crate::clock::Clock; use crate::color::Color; use crate::module::{Input, Module}; use crate::sound::PulseAudio; +use crate::battery::UpowerBattery; + + enum Cmd { Exit, @@ -151,6 +155,10 @@ impl App { ]; let mut vert_off = 0; + if let Ok(m) = UpowerBattery::new(tx.clone()) { + modules.push(Module::new(Box::new(m), (720, vert_off, 512, 32))); + vert_off += 32; + } if let Ok(m) = Backlight::new() { modules.push(Module::new(Box::new(m), (720, vert_off, 512, 32))); vert_off += 32;