use std::path::PathBuf; use log::{info, warn}; use crate::error::{PlatformError, Result}; use crate::{attr_num, to_device}; /// The "platform" device provides access to things like: /// - `dgpu_disable` /// - `egpu_enable` /// - `panel_od` /// - `gpu_mux` /// - `keyboard_mode`, set keyboard RGB mode and speed /// - `keyboard_state`, set keyboard power states #[derive(Debug, PartialEq, Eq, PartialOrd, Clone)] pub struct AsusPower { mains: PathBuf, battery: PathBuf, usb: Option, } impl AsusPower { attr_num!("charge_control_end_threshold", battery, u8); attr_num!("online", mains, u8); /// When checking for battery this will look in order: /// - if attr `manufacturer` contains `asus` /// - if attr `charge_control_end_threshold` exists and `energy_full_design` /// >= 50 watt /// - if syspath end conatins `BAT` /// - if attr `type` is `battery` (last resort) pub fn new() -> Result { let mut mains = PathBuf::new(); let mut battery = None; let mut usb = None; let mut enumerator = udev::Enumerator::new().map_err(|err| { warn!("{}", err); PlatformError::Udev("enumerator failed".into(), err) })?; enumerator.match_subsystem("power_supply").map_err(|err| { warn!("{}", err); PlatformError::Udev("match_subsystem failed".into(), err) })?; for device in enumerator.scan_devices().map_err(|err| { warn!("{}", err); PlatformError::Udev("scan_devices failed".into(), err) })? { if let Some(attr) = device.attribute_value("type") { info!("Power: Checking {:?}", device.syspath()); match attr.to_string_lossy().to_ascii_lowercase().trim() { "mains" => { info!("Found mains power at {:?}", device.sysname()); mains = device.syspath().to_path_buf(); } "battery" => { // Priortised list of checks info!("Found a battery"); if battery.is_none() { info!("Checking battery attributes"); if let Some(current) = device.attribute_value("charge_control_end_threshold") { info!( "Found battery power at {:?}, matched \ charge_control_end_threshold. Current level: {current:?}", device.sysname() ); battery = Some(device.syspath().to_path_buf()); } else if device.sysname().to_string_lossy().starts_with("BAT") { info!( "Found battery power at {:?}, sysfs path ended with BAT", device.sysname() ); battery = Some(device.syspath().to_path_buf()); } else { info!( "Last resort: Found battery power at {:?} using type = Battery", device.sysname() ); battery = Some(device.syspath().to_path_buf()); } } } "usb" => { info!("Found USB-C power at {:?}", device.sysname()); usb = Some(device.syspath().to_path_buf()); } _ => {} }; } } if let Some(battery) = battery { return Ok(Self { mains, battery, usb, }); } // No battery found. Return an AsusPower with an empty battery path so // callers can still be constructed and query `has_*` methods which // will correctly report absence. This avoids hard-failing on systems // where the asus-nb-wmi driver loads on desktops with no battery. info!("Did not find a battery, continuing without battery support"); Ok(Self { mains, battery: PathBuf::new(), usb, }) } }