feat(power): add battery status
This commit is contained in:
parent
69e03bc414
commit
fe52744387
5 changed files with 182 additions and 5 deletions
|
|
@ -51,6 +51,7 @@ tokio.workspace = true
|
||||||
tracing = "0.1.40"
|
tracing = "0.1.40"
|
||||||
tracing-subscriber = "0.3.18"
|
tracing-subscriber = "0.3.18"
|
||||||
udev = "0.9.0"
|
udev = "0.9.0"
|
||||||
|
upower_dbus = { git = "https://github.com/pop-os/dbus-settings-bindings" }
|
||||||
url = "2.5.2"
|
url = "2.5.2"
|
||||||
xkb-data = "0.2.1"
|
xkb-data = "0.2.1"
|
||||||
zbus = { version = "4.4.0", features = ["tokio"] }
|
zbus = { version = "4.4.0", features = ["tokio"] }
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
use chrono::Duration;
|
||||||
|
use futures::FutureExt;
|
||||||
use zbus::Connection;
|
use zbus::Connection;
|
||||||
|
|
||||||
mod ppdaemon;
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,20 @@
|
||||||
mod backend;
|
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::{widget::settings, Apply};
|
||||||
use cosmic_settings_page::{self as page, section, Section};
|
use cosmic_settings_page::{self as page, section, Section};
|
||||||
use slab::Slab;
|
use slab::Slab;
|
||||||
use slotmap::SlotMap;
|
use slotmap::SlotMap;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Page;
|
pub struct Page {
|
||||||
|
battery: Battery,
|
||||||
|
}
|
||||||
|
|
||||||
impl page::Page<crate::pages::Message> for Page {
|
impl page::Page<crate::pages::Message> for Page {
|
||||||
fn info(&self) -> page::Info {
|
fn info(&self) -> page::Info {
|
||||||
|
|
@ -22,13 +27,29 @@ impl page::Page<crate::pages::Message> for Page {
|
||||||
&self,
|
&self,
|
||||||
sections: &mut SlotMap<section::Entity, Section<crate::pages::Message>>,
|
sections: &mut SlotMap<section::Entity, Section<crate::pages::Message>>,
|
||||||
) -> Option<page::Content> {
|
) -> 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)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum Message {
|
pub enum Message {
|
||||||
PowerProfileChange(PowerProfile),
|
PowerProfileChange(PowerProfile),
|
||||||
|
UpdateBattery(Battery),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Page {
|
impl Page {
|
||||||
|
|
@ -43,10 +64,37 @@ impl Page {
|
||||||
runtime.block_on(b.set_power_profile(p));
|
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(§ion.title))
|
||||||
|
.push(row!(battery_icon, battery_percent, battery_time).spacing(8))
|
||||||
|
.into()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn profiles() -> Section<crate::pages::Message> {
|
fn profiles() -> Section<crate::pages::Message> {
|
||||||
let mut descriptions = Slab::new();
|
let mut descriptions = Slab::new();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -293,6 +293,9 @@ profile = Profile
|
||||||
power = Power & Battery
|
power = Power & Battery
|
||||||
.desc = Manage power settings
|
.desc = Manage power settings
|
||||||
|
|
||||||
|
battery = Battery
|
||||||
|
.remaining-time = ({ $time } left)
|
||||||
|
|
||||||
power-mode = Power Mode
|
power-mode = Power Mode
|
||||||
.battery = Extended battery life
|
.battery = Extended battery life
|
||||||
.battery-desc = Reduced power usage and silent performance.
|
.battery-desc = Reduced power usage and silent performance.
|
||||||
|
|
|
||||||
|
|
@ -307,6 +307,9 @@ profile = Profil
|
||||||
power = Zasilanie
|
power = Zasilanie
|
||||||
.desc = Zarządzaj ustawieniami zasilania
|
.desc = Zarządzaj ustawieniami zasilania
|
||||||
|
|
||||||
|
battery = Bateria
|
||||||
|
.remaining-time = ({ $time } pozostało)
|
||||||
|
|
||||||
power-mode = Profile Zasilania
|
power-mode = Profile Zasilania
|
||||||
.performance = Tryb Wysokowydajny
|
.performance = Tryb Wysokowydajny
|
||||||
.balanced = Tryb Zbalansowany
|
.balanced = Tryb Zbalansowany
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue