feat(power): add battery status

This commit is contained in:
Piotr 2024-08-29 21:18:38 +02:00 committed by GitHub
parent 69e03bc414
commit fe52744387
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 182 additions and 5 deletions

View file

@ -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"] }

View file

@ -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<ppdaemon::PowerProfilesProxy<'
}
}
}
#[derive(Default, Debug, Clone)]
pub struct Battery {
pub icon_name: String,
pub is_present: bool,
pub percent: f64,
pub on_battery: bool,
pub remaining_duration: Duration,
pub remaining_time: String,
}
async fn get_device_proxy<'a>() -> Result<upower_dbus::DeviceProxy<'a>, 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<bool, 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.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()
}
}

View file

@ -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<crate::pages::Message> for Page {
fn info(&self) -> page::Info {
@ -22,13 +27,29 @@ impl page::Page<crate::pages::Message> for Page {
&self,
sections: &mut SlotMap<section::Entity, Section<crate::pages::Message>>,
) -> Option<page::Content> {
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<crate::pages::Message>,
) -> cosmic::Command<crate::pages::Message> {
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<crate::pages::Message> {
let descriptions = Slab::new();
Section::default()
.title(fl!("battery"))
.descriptions(descriptions)
.show_while::<Page>(|page| page.battery.is_present)
.view::<Page>(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(&section.title))
.push(row!(battery_icon, battery_percent, battery_time).spacing(8))
.into()
})
}
fn profiles() -> Section<crate::pages::Message> {
let mut descriptions = Slab::new();

View file

@ -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.

View file

@ -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