use crate::CtrlTask; use crate::{config::Config, error::RogError, GetSupported}; use async_trait::async_trait; use log::{info, warn}; use logind_zbus::manager::ManagerProxy; use rog_supported::ChargeSupportedFunctions; use smol::stream::StreamExt; use smol::Executor; use std::fs::OpenOptions; use std::io::Write; use std::path::Path; use std::sync::Arc; use std::sync::Mutex; use zbus::dbus_interface; use zbus::Connection; use zbus::SignalContext; static BAT_CHARGE_PATH0: &str = "/sys/class/power_supply/BAT0/charge_control_end_threshold"; static BAT_CHARGE_PATH1: &str = "/sys/class/power_supply/BAT1/charge_control_end_threshold"; static BAT_CHARGE_PATH2: &str = "/sys/class/power_supply/BAT2/charge_control_end_threshold"; impl GetSupported for CtrlCharge { type A = ChargeSupportedFunctions; fn get_supported() -> Self::A { ChargeSupportedFunctions { charge_level_set: CtrlCharge::get_battery_path().is_ok(), } } } pub struct CtrlCharge { config: Arc>, } #[dbus_interface(name = "org.asuslinux.Daemon")] impl CtrlCharge { async fn set_limit( &mut self, #[zbus(signal_context)] ctxt: SignalContext<'_>, limit: u8, ) -> zbus::fdo::Result<()> { if !(20..=100).contains(&limit) { return Err(RogError::ChargeLimit(limit))?; } if let Ok(mut config) = self.config.try_lock() { Self::set(limit, &mut config) .map_err(|err| { warn!("CtrlCharge: set_limit {}", err); err }) .ok(); } Self::notify_charge(&ctxt, limit).await?; Ok(()) } fn limit(&self) -> i8 { if let Ok(config) = self.config.try_lock() { return config.bat_charge_limit as i8; } -1 } #[dbus_interface(signal)] async fn notify_charge(ctxt: &SignalContext<'_>, limit: u8) -> zbus::Result<()>; } #[async_trait] impl crate::ZbusAdd for CtrlCharge { async fn add_to_server(self, server: &mut Connection) { Self::add_to_server_helper(self, "/org/asuslinux/Charge", server).await; } } impl crate::Reloadable for CtrlCharge { fn reload(&mut self) -> Result<(), RogError> { if let Ok(mut config) = self.config.try_lock() { config.read(); Self::set(config.bat_charge_limit, &mut config)?; } Ok(()) } } impl CtrlCharge { pub fn new(config: Arc>) -> Result { CtrlCharge::get_battery_path()?; Ok(CtrlCharge { config }) } fn get_battery_path() -> Result<&'static str, RogError> { if Path::new(BAT_CHARGE_PATH0).exists() { Ok(BAT_CHARGE_PATH0) } else if Path::new(BAT_CHARGE_PATH1).exists() { Ok(BAT_CHARGE_PATH1) } else if Path::new(BAT_CHARGE_PATH2).exists() { Ok(BAT_CHARGE_PATH2) } else { Err(RogError::MissingFunction( "Charge control not available, you may require a v5.8.10 series kernel or newer" .into(), )) } } pub(super) fn set(limit: u8, config: &mut Config) -> Result<(), RogError> { if !(20..=100).contains(&limit) { return Err(RogError::ChargeLimit(limit)); } let path = Self::get_battery_path()?; let mut file = OpenOptions::new() .write(true) .open(path) .map_err(|err| RogError::Path(path.into(), err))?; file.write_all(limit.to_string().as_bytes()) .map_err(|err| RogError::Write(path.into(), err))?; info!("Battery charge limit: {}", limit); config.read(); config.bat_charge_limit = limit; config.write(); Ok(()) } } #[async_trait] impl CtrlTask for CtrlCharge { async fn create_tasks(&self, executor: &mut Executor) -> Result<(), RogError> { let connection = Connection::system() .await .expect("CtrlCharge could not create dbus connection"); let manager = ManagerProxy::new(&connection) .await .expect("CtrlCharge could not create ManagerProxy"); let config1 = self.config.clone(); executor .spawn(async move { if let Ok(notif) = manager.receive_prepare_for_sleep().await { notif .for_each(|event| { if let Ok(args) = event.args() { // If waking up if !args.start { info!("CtrlCharge reloading charge limit"); if let Ok(mut lock) = config1.try_lock() { Self::set(lock.bat_charge_limit, &mut lock) .map_err(|err| { warn!("CtrlCharge: set_limit {}", err); err }) .ok(); } } } }) .await; } }) .detach(); let manager = ManagerProxy::new(&connection) .await .expect("CtrlCharge could not create ManagerProxy"); let config = self.config.clone(); executor .spawn(async move { if let Ok(notif) = manager.receive_prepare_for_shutdown().await { notif .for_each(|event| { if let Ok(args) = event.args() { // If waking up - intention is to catch hibernation event if !args.start { info!("CtrlCharge reloading charge limit"); loop { if let Ok(mut lock) = config.clone().try_lock() { Self::set(lock.bat_charge_limit, &mut lock) .map_err(|err| { warn!("CtrlCharge: set_limit {}", err); err }) .ok(); break; } } } } }) .await; } }) .detach(); Ok(()) } }