diff --git a/cosmic-applet-battery/src/app.rs b/cosmic-applet-battery/src/app.rs index c249d379..02e8426d 100644 --- a/cosmic-applet-battery/src/app.rs +++ b/cosmic-applet-battery/src/app.rs @@ -1,12 +1,10 @@ +use crate::backend::{power_profile_subscription, Power, PowerProfileRequest, PowerProfileUpdate}; use crate::backlight::{ screen_backlight_subscription, ScreenBacklightRequest, ScreenBacklightUpdate, }; use crate::config; use crate::dgpu::{dgpu_subscription, Entry, GpuUpdate}; use crate::fl; -use crate::power_daemon::{ - power_profile_subscription, Power, PowerProfileRequest, PowerProfileUpdate, -}; use crate::upower_device::{device_subscription, DeviceDbusEvent}; use crate::upower_kbdbacklight::{ kbd_backlight_subscription, KeyboardBacklightRequest, KeyboardBacklightUpdate, diff --git a/cosmic-applet-battery/src/backend/mod.rs b/cosmic-applet-battery/src/backend/mod.rs new file mode 100644 index 00000000..98383e07 --- /dev/null +++ b/cosmic-applet-battery/src/backend/mod.rs @@ -0,0 +1,220 @@ +use cosmic::iced::{self, futures::SinkExt, subscription}; +use std::fmt::Debug; +use std::hash::Hash; +use tokio::sync::mpsc::UnboundedReceiver; +use tokio::sync::mpsc::UnboundedSender; +use zbus::Connection; +use zbus::Result; + +use self::power_daemon::PowerDaemonProxy; +use self::power_profiles::PowerProfilesProxy; + +mod power_daemon; +mod power_profiles; + +#[derive(PartialEq, Eq, Copy, Clone, Debug, Default)] +pub enum Power { + Battery, + #[default] + Balanced, + Performance, +} + +#[derive(Debug)] +pub enum Backend<'a> { + S76PowerDaemon(PowerDaemonProxy<'a>), + PowerProfilesDaemon(PowerProfilesProxy<'a>), +} + +#[derive(Clone, Debug, PartialEq, Eq, Default)] +pub enum BackendType { + #[default] + S76PowerDaemon, + PowerProfilesDaemon, +} + +impl BackendType { + fn next(self) -> Self { + match self { + Self::S76PowerDaemon => Self::PowerProfilesDaemon, + Self::PowerProfilesDaemon => Self::S76PowerDaemon, + } + } +} + +pub async fn get_power_profile(daemon: Backend<'_>) -> Result { + match daemon { + Backend::S76PowerDaemon(p) => { + let power = p.get_profile().await?; + match power.as_str() { + "Battery" => Ok(Power::Battery), + "Balanced" => Ok(Power::Balanced), + "Performance" => Ok(Power::Performance), + _ => panic!("Unknown power profile: {}", power), + } + } + Backend::PowerProfilesDaemon(ppd) => { + let power = ppd.active_profile().await?; + match power.as_str() { + "power-saver" => Ok(Power::Battery), + "balanced" => Ok(Power::Balanced), + "performance" => Ok(Power::Performance), + _ => panic!("Unknown power profile: {}", power), + } + } + } +} + +pub async fn set_power_profile(daemon: Backend<'_>, power: Power) -> Result<()> { + match daemon { + Backend::S76PowerDaemon(p) => match power { + Power::Battery => p.battery().await, + Power::Balanced => p.balanced().await, + Power::Performance => p.performance().await, + }, + Backend::PowerProfilesDaemon(ppd) => match power { + Power::Battery => ppd.set_active_profile("power-saver").await, + Power::Balanced => ppd.set_active_profile("balanced").await, + Power::Performance => ppd.set_active_profile("performance").await, + }, + } +} + +pub fn power_profile_subscription( + id: I, +) -> iced::Subscription { + subscription::channel(id, 50, move |mut output| async move { + let mut state = State::Ready; + + loop { + state = start_listening(state, &mut output).await; + } + }) +} + +#[derive(Debug)] +pub enum State { + Ready, + Connecting(BackendType), + Waiting( + Connection, + UnboundedReceiver, + BackendType, + ), + Finished, +} + +async fn start_listening( + state: State, + output: &mut futures::channel::mpsc::Sender, +) -> State { + match state { + State::Ready => { + // Default to s76 powerdaemon + State::Connecting(BackendType::default()) + } + State::Connecting(backend_type) => { + let conn = match Connection::system().await.map_err(|e| e.to_string()) { + Ok(conn) => conn, + Err(e) => { + _ = output.send(PowerProfileUpdate::Error(e)).await; + return State::Finished; + } + }; + let backend = match backend_type { + BackendType::S76PowerDaemon => { + match PowerDaemonProxy::new(&conn) + .await + .map_err(|e| e.to_string()) + { + Ok(p) => Backend::S76PowerDaemon(p), + Err(e) => { + _ = output.send(PowerProfileUpdate::Error(e)).await; + return State::Connecting(backend_type.next()); + } + } + } + BackendType::PowerProfilesDaemon => { + match PowerProfilesProxy::new(&conn) + .await + .map_err(|e| e.to_string()) + { + Ok(p) => Backend::PowerProfilesDaemon(p), + Err(e) => { + _ = output.send(PowerProfileUpdate::Error(e)).await; + return State::Connecting(backend_type.next()); + } + } + } + }; + // Successful connection + let profile = match get_power_profile(backend).await.map_err(|e| e.to_string()) { + Ok(p) => p, + Err(e) => { + _ = output.send(PowerProfileUpdate::Error(e)).await; + return State::Connecting(backend_type.next()); + } + }; + let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); + _ = output.send(PowerProfileUpdate::Init(profile, tx)).await; + State::Waiting(conn, rx, backend_type) + } + State::Waiting(conn, mut rx, backend_type) => { + let backend = match backend_type { + BackendType::S76PowerDaemon => { + match PowerDaemonProxy::new(&conn) + .await + .map_err(|e| e.to_string()) + { + Ok(p) => Backend::S76PowerDaemon(p), + Err(e) => { + _ = output.send(PowerProfileUpdate::Error(e)).await; + return State::Connecting(backend_type); + } + } + } + BackendType::PowerProfilesDaemon => { + match PowerProfilesProxy::new(&conn) + .await + .map_err(|e| e.to_string()) + { + Ok(p) => Backend::PowerProfilesDaemon(p), + Err(e) => { + _ = output.send(PowerProfileUpdate::Error(e)).await; + return State::Connecting(backend_type); + } + } + } + }; + + match rx.recv().await { + Some(PowerProfileRequest::Get) => { + if let Ok(profile) = get_power_profile(backend).await { + _ = output.send(PowerProfileUpdate::Update { profile }).await; + } + State::Waiting(conn, rx, backend_type) + } + Some(PowerProfileRequest::Set(profile)) => { + let _ = set_power_profile(backend, profile).await; + _ = output.send(PowerProfileUpdate::Update { profile }).await; + State::Waiting(conn, rx, backend_type) + } + None => State::Finished, + } + } + State::Finished => iced::futures::future::pending().await, + } +} + +#[derive(Debug, Clone, Copy)] +pub enum PowerProfileRequest { + Get, + Set(Power), +} + +#[derive(Debug, Clone)] +pub enum PowerProfileUpdate { + Init(Power, UnboundedSender), + Update { profile: Power }, + Error(String), +} diff --git a/cosmic-applet-battery/src/backend/power_daemon.rs b/cosmic-applet-battery/src/backend/power_daemon.rs new file mode 100644 index 00000000..ebe23799 --- /dev/null +++ b/cosmic-applet-battery/src/backend/power_daemon.rs @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +//! # DBus interface proxy for: `com.system76.PowerDaemon` +//! +//! This code was generated by `zbus-xmlgen` `3.0.0` from DBus introspection data. +//! Source: `Interface '/com/system76/PowerDaemon' from service 'com.system76.PowerDaemon' on system bus`. +//! +//! You may prefer to adapt it, instead of using it verbatim. +//! +//! More information can be found in the +//! [Writing a client proxy](https://dbus.pages.freedesktop.org/zbus/client.html) +//! section of the zbus documentation. +//! +//! This DBus object implements +//! [standard DBus interfaces](https://dbus.freedesktop.org/doc/dbus-specification.html), +//! (`org.freedesktop.DBus.*`) for which the following zbus proxies can be used: +//! +//! * [`zbus::fdo::IntrospectableProxy`] +//! +//! …consequently `zbus-xmlgen` did not generate code for the above interfaces. + +use zbus::dbus_proxy; + +#[dbus_proxy( + interface = "com.system76.PowerDaemon", + default_path = "/com/system76/PowerDaemon" +)] +trait PowerDaemon { + /// Balanced method + fn balanced(&self) -> zbus::Result<()>; + + /// Battery method + fn battery(&self) -> zbus::Result<()>; + + /// GetChargeProfiles method + fn get_charge_profiles( + &self, + ) -> zbus::Result>>; + + /// GetChargeThresholds method + fn get_charge_thresholds(&self) -> zbus::Result<(u8, u8)>; + + /// GetDefaultGraphics method + fn get_default_graphics(&self) -> zbus::Result; + + /// GetExternalDisplaysRequireDGPU method + fn get_external_displays_require_dgpu(&self) -> zbus::Result; + + /// GetGraphics method + fn get_graphics(&self) -> zbus::Result; + + /// GetGraphicsPower method + fn get_graphics_power(&self) -> zbus::Result; + + /// GetProfile method + fn get_profile(&self) -> zbus::Result; + + /// GetSwitchable method + fn get_switchable(&self) -> zbus::Result; + + /// Performance method + fn performance(&self) -> zbus::Result<()>; + + /// SetChargeThresholds method + fn set_charge_thresholds(&self, thresholds: &(u8, u8)) -> zbus::Result<()>; + + /// SetGraphics method + fn set_graphics(&self, vendor: &str) -> zbus::Result<()>; + + /// SetGraphicsPower method + fn set_graphics_power(&self, power: bool) -> zbus::Result<()>; + + /// HotPlugDetect signal + #[dbus_proxy(signal)] + fn hot_plug_detect(&self, port: u64) -> zbus::Result<()>; + + /// PowerProfileSwitch signal + #[dbus_proxy(signal)] + fn power_profile_switch(&self, profile: &str) -> zbus::Result<()>; +} diff --git a/cosmic-applet-battery/src/backend/power_profiles.rs b/cosmic-applet-battery/src/backend/power_profiles.rs new file mode 100644 index 00000000..d94e0f54 --- /dev/null +++ b/cosmic-applet-battery/src/backend/power_profiles.rs @@ -0,0 +1,64 @@ +//! # DBus interface proxy for: `org.freedesktop.UPower.PowerProfiles` +//! +//! This code was generated by `zbus-xmlgen` `3.1.1` from DBus introspection data. +//! Source: `powerprofilesdaemon.xml`. +//! +//! You may prefer to adapt it, instead of using it verbatim. +//! +//! More information can be found in the +//! [Writing a client proxy](https://dbus.pages.freedesktop.org/zbus/client.html) +//! section of the zbus documentation. +//! + +use zbus::dbus_proxy; + +#[dbus_proxy( + interface = "org.freedesktop.UPower.PowerProfiles", + default_path = "/org/freedesktop/UPower/PowerProfiles", + assume_defaults = true +)] +trait PowerProfiles { + /// HoldProfile method + fn hold_profile(&self, profile: &str, reason: &str, application_id: &str) -> zbus::Result; + + /// ReleaseProfile method + fn release_profile(&self, cookie: u32) -> zbus::Result<()>; + + /// ProfileReleased signal + #[dbus_proxy(signal)] + fn profile_released(&self, cookie: u32) -> zbus::Result<()>; + + /// Actions property + #[dbus_proxy(property)] + fn actions(&self) -> zbus::Result>; + + /// ActiveProfile property + #[dbus_proxy(property)] + fn active_profile(&self) -> zbus::Result; + #[dbus_proxy(property)] + fn set_active_profile(&self, value: &str) -> zbus::Result<()>; + + /// ActiveProfileHolds property + #[dbus_proxy(property)] + fn active_profile_holds( + &self, + ) -> zbus::Result>>; + + /// PerformanceDegraded property + #[dbus_proxy(property)] + fn performance_degraded(&self) -> zbus::Result; + + /// PerformanceInhibited property + #[dbus_proxy(property)] + fn performance_inhibited(&self) -> zbus::Result; + + /// Profiles property + #[dbus_proxy(property)] + fn profiles( + &self, + ) -> zbus::Result>>; + + /// Version property + #[dbus_proxy(property)] + fn version(&self) -> zbus::Result; +} diff --git a/cosmic-applet-battery/src/lib.rs b/cosmic-applet-battery/src/lib.rs index ab11dae0..9b1dd664 100644 --- a/cosmic-applet-battery/src/lib.rs +++ b/cosmic-applet-battery/src/lib.rs @@ -1,10 +1,10 @@ #[rustfmt::skip] mod backlight; mod app; +mod backend; mod config; mod dgpu; mod localize; -mod power_daemon; mod upower; mod upower_device; mod upower_kbdbacklight; diff --git a/cosmic-applet-battery/src/power_daemon.rs b/cosmic-applet-battery/src/power_daemon.rs deleted file mode 100644 index bbf65daa..00000000 --- a/cosmic-applet-battery/src/power_daemon.rs +++ /dev/null @@ -1,213 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -//! # DBus interface proxy for: `com.system76.PowerDaemon` -//! -//! This code was generated by `zbus-xmlgen` `3.0.0` from DBus introspection data. -//! Source: `Interface '/com/system76/PowerDaemon' from service 'com.system76.PowerDaemon' on system bus`. -//! -//! You may prefer to adapt it, instead of using it verbatim. -//! -//! More information can be found in the -//! [Writing a client proxy](https://dbus.pages.freedesktop.org/zbus/client.html) -//! section of the zbus documentation. -//! -//! This DBus object implements -//! [standard DBus interfaces](https://dbus.freedesktop.org/doc/dbus-specification.html), -//! (`org.freedesktop.DBus.*`) for which the following zbus proxies can be used: -//! -//! * [`zbus::fdo::IntrospectableProxy`] -//! -//! …consequently `zbus-xmlgen` did not generate code for the above interfaces. - -use cosmic::iced::{self, futures::SinkExt, subscription}; -use std::fmt::Debug; -use std::hash::Hash; -use tokio::sync::mpsc::UnboundedReceiver; -use tokio::sync::mpsc::UnboundedSender; -use zbus::Result; -use zbus::{dbus_proxy, Connection}; - -#[dbus_proxy( - interface = "com.system76.PowerDaemon", - default_path = "/com/system76/PowerDaemon" -)] -trait PowerDaemon { - /// Balanced method - fn balanced(&self) -> zbus::Result<()>; - - /// Battery method - fn battery(&self) -> zbus::Result<()>; - - /// GetChargeProfiles method - fn get_charge_profiles( - &self, - ) -> zbus::Result>>; - - /// GetChargeThresholds method - fn get_charge_thresholds(&self) -> zbus::Result<(u8, u8)>; - - /// GetDefaultGraphics method - fn get_default_graphics(&self) -> zbus::Result; - - /// GetExternalDisplaysRequireDGPU method - fn get_external_displays_require_dgpu(&self) -> zbus::Result; - - /// GetGraphics method - fn get_graphics(&self) -> zbus::Result; - - /// GetGraphicsPower method - fn get_graphics_power(&self) -> zbus::Result; - - /// GetProfile method - fn get_profile(&self) -> zbus::Result; - - /// GetSwitchable method - fn get_switchable(&self) -> zbus::Result; - - /// Performance method - fn performance(&self) -> zbus::Result<()>; - - /// SetChargeThresholds method - fn set_charge_thresholds(&self, thresholds: &(u8, u8)) -> zbus::Result<()>; - - /// SetGraphics method - fn set_graphics(&self, vendor: &str) -> zbus::Result<()>; - - /// SetGraphicsPower method - fn set_graphics_power(&self, power: bool) -> zbus::Result<()>; - - /// HotPlugDetect signal - #[dbus_proxy(signal)] - fn hot_plug_detect(&self, port: u64) -> zbus::Result<()>; - - /// PowerProfileSwitch signal - #[dbus_proxy(signal)] - fn power_profile_switch(&self, profile: &str) -> zbus::Result<()>; -} - -#[derive(PartialEq, Eq, Copy, Clone, Debug, Default)] -pub enum Power { - Battery, - #[default] - Balanced, - Performance, -} - -pub async fn get_power_profile(daemon: PowerDaemonProxy<'_>) -> Result { - let power = daemon.get_profile().await?; - match power.as_str() { - "Battery" => Ok(Power::Battery), - "Balanced" => Ok(Power::Balanced), - "Performance" => Ok(Power::Performance), - _ => panic!("Unknown power profile: {}", power), - } -} - -pub async fn set_power_profile(daemon: PowerDaemonProxy<'_>, power: Power) -> Result<()> { - match power { - Power::Battery => daemon.battery().await, - Power::Balanced => daemon.balanced().await, - Power::Performance => daemon.performance().await, - } -} - -pub fn power_profile_subscription( - id: I, -) -> iced::Subscription { - subscription::channel(id, 50, move |mut output| async move { - let mut state = State::Ready; - - loop { - state = start_listening(state, &mut output).await; - } - }) -} - -#[derive(Debug)] -pub enum State { - Ready, - Waiting(Connection, UnboundedReceiver), - Finished, -} - -async fn start_listening( - state: State, - output: &mut futures::channel::mpsc::Sender, -) -> State { - match state { - State::Ready => { - let conn = match Connection::system().await.map_err(|e| e.to_string()) { - Ok(conn) => conn, - Err(e) => { - _ = output.send(PowerProfileUpdate::Error(e)).await; - return State::Finished; - } - }; - let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); - - let power_proxy = match PowerDaemonProxy::new(&conn) - .await - .map_err(|e| e.to_string()) - { - Ok(p) => p, - Err(e) => { - _ = output.send(PowerProfileUpdate::Error(e)).await; - - return State::Waiting(conn, rx); - } - }; - let profile = match get_power_profile(power_proxy) - .await - .map_err(|e| e.to_string()) - { - Ok(p) => p, - Err(e) => { - _ = output.send(PowerProfileUpdate::Error(e)).await; - return State::Waiting(conn, rx); - } - }; - _ = output.send(PowerProfileUpdate::Init(profile, tx)).await; - State::Waiting(conn, rx) - } - State::Waiting(conn, mut rx) => { - let power_proxy = match PowerDaemonProxy::new(&conn) - .await - .map_err(|e| e.to_string()) - { - Ok(p) => p, - Err(e) => { - _ = output.send(PowerProfileUpdate::Error(e)).await; - return State::Waiting(conn, rx); - } - }; - - match rx.recv().await { - Some(PowerProfileRequest::Get) => { - if let Ok(profile) = get_power_profile(power_proxy).await { - _ = output.send(PowerProfileUpdate::Update { profile }).await; - } - State::Waiting(conn, rx) - } - Some(PowerProfileRequest::Set(profile)) => { - let _ = set_power_profile(power_proxy, profile).await; - _ = output.send(PowerProfileUpdate::Update { profile }).await; - State::Waiting(conn, rx) - } - None => State::Finished, - } - } - State::Finished => iced::futures::future::pending().await, - } -} - -#[derive(Debug, Clone, Copy)] -pub enum PowerProfileRequest { - Get, - Set(Power), -} - -#[derive(Debug, Clone)] -pub enum PowerProfileUpdate { - Init(Power, UnboundedSender), - Update { profile: Power }, - Error(String), -}