From fe527443872456b233722f0f93920b6ec1047127 Mon Sep 17 00:00:00 2001 From: Piotr <114903054+elevenhsoft@users.noreply.github.com> Date: Thu, 29 Aug 2024 21:18:38 +0200 Subject: [PATCH] feat(power): add battery status --- cosmic-settings/Cargo.toml | 1 + .../src/pages/power/backend/mod.rs | 122 ++++++++++++++++++ cosmic-settings/src/pages/power/mod.rs | 58 ++++++++- i18n/en/cosmic_settings.ftl | 3 + i18n/pl/cosmic_settings.ftl | 3 + 5 files changed, 182 insertions(+), 5 deletions(-) diff --git a/cosmic-settings/Cargo.toml b/cosmic-settings/Cargo.toml index f26657d..234d86f 100644 --- a/cosmic-settings/Cargo.toml +++ b/cosmic-settings/Cargo.toml @@ -51,6 +51,7 @@ tokio.workspace = true tracing = "0.1.40" tracing-subscriber = "0.3.18" udev = "0.9.0" +upower_dbus = { git = "https://github.com/pop-os/dbus-settings-bindings" } url = "2.5.2" xkb-data = "0.2.1" zbus = { version = "4.4.0", features = ["tokio"] } diff --git a/cosmic-settings/src/pages/power/backend/mod.rs b/cosmic-settings/src/pages/power/backend/mod.rs index dd94411..58cc5b1 100644 --- a/cosmic-settings/src/pages/power/backend/mod.rs +++ b/cosmic-settings/src/pages/power/backend/mod.rs @@ -1,3 +1,5 @@ +use chrono::Duration; +use futures::FutureExt; use zbus::Connection; mod ppdaemon; @@ -226,3 +228,123 @@ async fn get_power_profiles_proxy<'a>() -> Result() -> Result, zbus::Error> { + let connection = match Connection::system().await { + Ok(c) => c, + Err(e) => { + tracing::error!("zbus connection failed. {e}"); + return Err(e); + } + }; + + match upower_dbus::UPowerProxy::new(&connection).await { + Ok(p) => p.get_display_device().await, + Err(e) => Err(e), + } +} + +async fn get_on_battery_status() -> Result { + let connection = match Connection::system().await { + Ok(c) => c, + Err(e) => { + tracing::error!("zbus connection failed. {e}"); + return Err(e); + } + }; + + match upower_dbus::UPowerProxy::new(&connection).await { + Ok(p) => p.on_battery().await, + Err(e) => Err(e), + } +} + +impl Battery { + pub async fn update_battery() -> Self { + let proxy = get_device_proxy().await; + + if let Ok(proxy) = proxy { + let mut remaining_duration: Duration = Duration::default(); + + let (is_present, percentage, on_battery) = futures::join!( + proxy.is_present().map(Result::unwrap_or_default), + proxy.percentage().map(Result::unwrap_or_default), + get_on_battery_status().map(Result::unwrap_or_default) + ); + + let percent = percentage.clamp(0.0, 100.0); + + if on_battery { + if let Ok(time) = proxy.time_to_empty().await { + if let Ok(dur) = Duration::from_std(std::time::Duration::from_secs(time as u64)) + { + remaining_duration = dur; + } + } + } else if let Ok(time) = proxy.time_to_full().await { + if let Ok(dur) = Duration::from_std(std::time::Duration::from_secs(time as u64)) { + remaining_duration = dur; + } + } + + let battery_percent = if percent > 95.0 { + 100 + } else if percent > 80.0 { + 90 + } else if percent > 65.0 { + 80 + } else if percent > 35.0 { + 50 + } else if percent > 20.0 { + 35 + } else if percent > 14.0 { + 20 + } else if percent > 9.0 { + 10 + } else if percent > 5.0 { + 5 + } else { + 0 + }; + let charging = if on_battery { "" } else { "charging-" }; + + let icon_name = + format!("cosmic-applet-battery-level-{battery_percent}-{charging}symbolic",); + + let remaining_time = |duration: Duration| { + let total_seconds = duration.num_seconds(); + + let hours = total_seconds / 3600; + let minutes = (total_seconds % 3600) / 60; + let seconds = total_seconds % 60; + + fl!( + "battery", + "remaining-time", + time = format!("{:02}:{:02}:{:02}", hours, minutes, seconds) + ) + }; + + return Battery { + icon_name, + is_present, + percent, + on_battery, + remaining_duration, + remaining_time: remaining_time(remaining_duration), + }; + } + + Battery::default() + } +} diff --git a/cosmic-settings/src/pages/power/mod.rs b/cosmic-settings/src/pages/power/mod.rs index e0fb85d..00ba360 100644 --- a/cosmic-settings/src/pages/power/mod.rs +++ b/cosmic-settings/src/pages/power/mod.rs @@ -1,15 +1,20 @@ mod backend; -use self::backend::{GetCurrentPowerProfile, SetPowerProfile}; -use backend::PowerProfile; -use cosmic::widget; +use self::backend::{GetCurrentPowerProfile, SetPowerProfile}; +use backend::{Battery, PowerProfile}; + +use chrono::TimeDelta; +use cosmic::iced_widget::row; +use cosmic::widget::{self, column, text}; use cosmic::{widget::settings, Apply}; use cosmic_settings_page::{self as page, section, Section}; use slab::Slab; use slotmap::SlotMap; #[derive(Default)] -pub struct Page; +pub struct Page { + battery: Battery, +} impl page::Page for Page { fn info(&self) -> page::Info { @@ -22,13 +27,29 @@ impl page::Page for Page { &self, sections: &mut SlotMap>, ) -> Option { - Some(vec![sections.insert(profiles())]) + Some(vec![ + sections.insert(battery_info()), + sections.insert(profiles()), + ]) + } + + fn on_enter( + &mut self, + _page: cosmic_settings_page::Entity, + _sender: tokio::sync::mpsc::Sender, + ) -> cosmic::Command { + cosmic::command::future(async move { + let battery = Battery::update_battery().await; + Message::UpdateBattery(battery) + }) + .map(crate::pages::Message::Power) } } #[derive(Clone, Debug)] pub enum Message { PowerProfileChange(PowerProfile), + UpdateBattery(Battery), } impl Page { @@ -43,10 +64,37 @@ impl Page { runtime.block_on(b.set_power_profile(p)); } } + Message::UpdateBattery(battery) => self.battery = battery, }; } } +fn battery_info() -> Section { + let descriptions = Slab::new(); + + Section::default() + .title(fl!("battery")) + .descriptions(descriptions) + .show_while::(|page| page.battery.is_present) + .view::(move |_binder, page, section| { + let battery_icon = widget::icon::from_name(page.battery.icon_name.clone()); + let battery_percent = widget::text(format!("{}%", page.battery.percent)); + + let battery_time = + widget::text(if page.battery.remaining_duration > TimeDelta::zero() { + &page.battery.remaining_time + } else { + "" + }); + + column::with_capacity(2) + .spacing(8) + .push(text::heading(§ion.title)) + .push(row!(battery_icon, battery_percent, battery_time).spacing(8)) + .into() + }) +} + fn profiles() -> Section { let mut descriptions = Slab::new(); diff --git a/i18n/en/cosmic_settings.ftl b/i18n/en/cosmic_settings.ftl index a267ce3..63defa8 100644 --- a/i18n/en/cosmic_settings.ftl +++ b/i18n/en/cosmic_settings.ftl @@ -293,6 +293,9 @@ profile = Profile power = Power & Battery .desc = Manage power settings +battery = Battery + .remaining-time = ({ $time } left) + power-mode = Power Mode .battery = Extended battery life .battery-desc = Reduced power usage and silent performance. diff --git a/i18n/pl/cosmic_settings.ftl b/i18n/pl/cosmic_settings.ftl index db0c39c..c96883a 100644 --- a/i18n/pl/cosmic_settings.ftl +++ b/i18n/pl/cosmic_settings.ftl @@ -307,6 +307,9 @@ profile = Profil power = Zasilanie .desc = Zarządzaj ustawieniami zasilania +battery = Bateria + .remaining-time = ({ $time } pozostało) + power-mode = Profile Zasilania .performance = Tryb Wysokowydajny .balanced = Tryb Zbalansowany