From 004b8796720723ab0767bca485e019855fc81cd6 Mon Sep 17 00:00:00 2001 From: RGBCube Date: Thu, 22 May 2025 19:49:45 +0300 Subject: [PATCH] power_supply: add more stuff and store them --- src/config.rs | 8 +-- src/core.rs | 1 - src/cpu.rs | 108 +++++++++++++++------------- src/fs.rs | 25 ++++--- src/monitor.rs | 123 -------------------------------- src/power_supply.rs | 169 ++++++++++++++++++++++++++++++++------------ src/system.rs | 10 +-- 7 files changed, 207 insertions(+), 237 deletions(-) diff --git a/src/config.rs b/src/config.rs index b68485a..4de0ac3 100644 --- a/src/config.rs +++ b/src/config.rs @@ -119,7 +119,7 @@ pub struct PowerDelta { impl PowerDelta { pub fn apply(&self) -> anyhow::Result<()> { - let power_supplies = match &self.for_ { + let mut power_supplies = match &self.for_ { Some(names) => { let mut power_supplies = Vec::with_capacity(names.len()); @@ -136,13 +136,13 @@ impl PowerDelta { .collect(), }; - for power_supply in power_supplies { + for power_supply in &mut power_supplies { if let Some(threshold_start) = self.charge_threshold_start { - power_supply.set_charge_threshold_start(threshold_start)?; + power_supply.set_charge_threshold_start(threshold_start as f64 / 100.0)?; } if let Some(threshold_end) = self.charge_threshold_end { - power_supply.set_charge_threshold_end(threshold_end)?; + power_supply.set_charge_threshold_end(threshold_end as f64 / 100.0)?; } } diff --git a/src/core.rs b/src/core.rs index bdd7a24..2e32854 100644 --- a/src/core.rs +++ b/src/core.rs @@ -13,7 +13,6 @@ pub struct CpuGlobalInfo { // System-wide CPU settings pub epp: Option, // Energy Performance Preference pub epb: Option, // Energy Performance Bias - pub platform_profile: Option, pub average_temperature_celsius: Option, // Average temperature across all cores } diff --git a/src/cpu.rs b/src/cpu.rs index 89e9ea9..6712cdf 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -160,8 +160,8 @@ impl Cpu { // TODO: Don't read this per CPU. Share the read or // find something in /sys/.../cpu{N} that does it. let content = fs::read("/proc/stat") - .context("/proc/stat does not exist")? - .context("failed to read CPU stat")?; + .context("failed to read CPU stat")? + .context("/proc/stat does not exist")?; let cpu_name = format!("cpu{number}", number = self.number); @@ -175,44 +175,44 @@ impl Cpu { self.time_user = stats .next() - .with_context(|| format!("failed to find {self} user time"))? + .with_context(|| format!("failed to parse {self} user time"))? .parse() - .with_context(|| format!("failed to parse {self} user time"))?; + .with_context(|| format!("failed to find {self} user time"))?; self.time_nice = stats .next() - .with_context(|| format!("failed to find {self} nice time"))? + .with_context(|| format!("failed to parse {self} nice time"))? .parse() - .with_context(|| format!("failed to parse {self} nice time"))?; + .with_context(|| format!("failed to find {self} nice time"))?; self.time_system = stats .next() - .with_context(|| format!("failed to find {self} system time"))? + .with_context(|| format!("failed to parse {self} system time"))? .parse() - .with_context(|| format!("failed to parse {self} system time"))?; + .with_context(|| format!("failed to find {self} system time"))?; self.time_idle = stats .next() - .with_context(|| format!("failed to find {self} idle time"))? + .with_context(|| format!("failed to parse {self} idle time"))? .parse() - .with_context(|| format!("failed to parse {self} idle time"))?; + .with_context(|| format!("failed to find {self} idle time"))?; self.time_iowait = stats .next() - .with_context(|| format!("failed to find {self} iowait time"))? + .with_context(|| format!("failed to parse {self} iowait time"))? .parse() - .with_context(|| format!("failed to parse {self} iowait time"))?; + .with_context(|| format!("failed to find {self} iowait time"))?; self.time_irq = stats .next() - .with_context(|| format!("failed to find {self} irq time"))? + .with_context(|| format!("failed to parse {self} irq time"))? .parse() - .with_context(|| format!("failed to parse {self} irq time"))?; + .with_context(|| format!("failed to find {self} irq time"))?; self.time_softirq = stats .next() - .with_context(|| format!("failed to find {self} softirq time"))? + .with_context(|| format!("failed to parse {self} softirq time"))? .parse() - .with_context(|| format!("failed to parse {self} softirq time"))?; + .with_context(|| format!("failed to find {self} softirq time"))?; self.time_steal = stats .next() - .with_context(|| format!("failed to find {self} steal time"))? + .with_context(|| format!("failed to parse {self} steal time"))? .parse() - .with_context(|| format!("failed to parse {self} steal time"))?; + .with_context(|| format!("failed to find {self} steal time"))?; Ok(()) } @@ -221,9 +221,11 @@ impl Cpu { let Self { number, .. } = *self; self.available_governors = 'available_governors: { - let Some(Ok(content)) = fs::read(format!( + let Some(content) = fs::read(format!( "/sys/devices/system/cpu/cpu{number}/cpufreq/scaling_available_governors" - )) else { + )) + .with_context(|| format!("failed to read {self} available governors"))? + else { break 'available_governors Vec::new(); }; @@ -237,8 +239,8 @@ impl Cpu { fs::read(format!( "/sys/devices/system/cpu/cpu{number}/cpufreq/scaling_governor" )) - .with_context(|| format!("failed to find {self} scaling governor"))? - .with_context(|| format!("failed to read {self} scaling governor"))?, + .with_context(|| format!("failed to read {self} scaling governor"))? + .with_context(|| format!("failed to find {self} scaling governor"))?, ); Ok(()) @@ -247,21 +249,21 @@ impl Cpu { fn rescan_frequency(&mut self) -> anyhow::Result<()> { let Self { number, .. } = *self; - let frequency_khz = fs::read_u64(format!( + let frequency_khz = fs::read_n::(format!( "/sys/devices/system/cpu/cpu{number}/cpufreq/scaling_cur_freq" )) - .with_context(|| format!("failed to find {self} frequency"))? - .with_context(|| format!("failed to parse {self} frequency"))?; - let frequency_khz_minimum = fs::read_u64(format!( + .with_context(|| format!("failed to parse {self} frequency"))? + .with_context(|| format!("failed to find {self} frequency"))?; + let frequency_khz_minimum = fs::read_n::(format!( "/sys/devices/system/cpu/cpu{number}/cpufreq/scaling_min_freq" )) - .with_context(|| format!("failed to find {self} frequency minimum"))? - .with_context(|| format!("failed to parse {self} frequency"))?; - let frequency_khz_maximum = fs::read_u64(format!( + .with_context(|| format!("failed to parse {self} frequency minimum"))? + .with_context(|| format!("failed to find {self} frequency"))?; + let frequency_khz_maximum = fs::read_n::(format!( "/sys/devices/system/cpu/cpu{number}/cpufreq/scaling_max_freq" )) - .with_context(|| format!("failed to find {self} frequency maximum"))? - .with_context(|| format!("failed to parse {self} frequency"))?; + .with_context(|| format!("failed to parse {self} frequency maximum"))? + .with_context(|| format!("failed to find {self} frequency"))?; self.frequency_mhz = Some(frequency_khz / 1000); self.frequency_mhz_minimum = Some(frequency_khz_minimum / 1000); @@ -271,12 +273,12 @@ impl Cpu { } fn rescan_epp(&mut self) -> anyhow::Result<()> { - let Self { number, .. } = self; + let Self { number, .. } = *self; self.available_epps = 'available_epps: { - let Some(Ok(content)) = fs::read(format!( + let Some(content) = fs::read(format!( "/sys/devices/system/cpu/cpu{number}/cpufreq/energy_performance_available_preferences" - )) else { + )).with_context(|| format!("failed to read {self} available EPPs"))? else { break 'available_epps Vec::new(); }; @@ -290,8 +292,8 @@ impl Cpu { fs::read(format!( "/sys/devices/system/cpu/cpu{number}/cpufreq/energy_performance_preference" )) - .with_context(|| format!("failed to find {self} EPP"))? - .with_context(|| format!("failed to read {self} EPP"))?, + .with_context(|| format!("failed to read {self} EPP"))? + .with_context(|| format!("failed to find {self} EPP"))?, ); Ok(()) @@ -332,8 +334,8 @@ impl Cpu { fs::read(format!( "/sys/devices/system/cpu/cpu{number}/cpufreq/energy_performance_bias" )) - .with_context(|| format!("failed to find {self} EPB"))? - .with_context(|| format!("failed to read {self} EPB"))?, + .with_context(|| format!("failed to read {self} EPB"))? + .with_context(|| format!("failed to find {self} EPB"))?, ); Ok(()) @@ -450,9 +452,11 @@ impl Cpu { fn validate_frequency_mhz_minimum(&self, new_frequency_mhz: u64) -> anyhow::Result<()> { let Self { number, .. } = self; - let Some(Ok(minimum_frequency_khz)) = fs::read_u64(format!( + let Some(minimum_frequency_khz) = fs::read_n::(format!( "/sys/devices/system/cpu/cpu{number}/cpufreq/scaling_min_freq" - )) else { + )) + .with_context(|| format!("failed to read {self} minimum frequency"))? + else { // Just let it pass if we can't find anything. return Ok(()); }; @@ -492,9 +496,11 @@ impl Cpu { fn validate_frequency_mhz_maximum(&self, new_frequency_mhz: u64) -> anyhow::Result<()> { let Self { number, .. } = self; - let Some(Ok(maximum_frequency_khz)) = fs::read_u64(format!( - "/sys/devices/system/cpu/cpu{number}/cpufreq/scaling_min_freq" - )) else { + let Some(maximum_frequency_khz) = fs::read_n::(format!( + "/sys/devices/system/cpu/cpu{number}/cpufreq/scaling_max_freq" + )) + .with_context(|| format!("failed to read {self} maximum frequency"))? + else { // Just let it pass if we can't find anything. return Ok(()); }; @@ -558,15 +564,19 @@ impl Cpu { bail!("no supported CPU boost control mechanism found"); } - pub fn turbo() -> Option { - if let Some(Ok(content)) = fs::read_u64("/sys/devices/system/cpu/intel_pstate/no_turbo") { - return Some(content == 0); + pub fn turbo() -> anyhow::Result> { + if let Some(content) = fs::read_n::("/sys/devices/system/cpu/intel_pstate/no_turbo") + .context("failed to read CPU turbo boost status")? + { + return Ok(Some(content == 0)); } - if let Some(Ok(content)) = fs::read_u64("/sys/devices/system/cpu/cpufreq/boost") { - return Some(content == 1); + if let Some(content) = fs::read_n::("/sys/devices/system/cpu/cpufreq/boost") + .context("failed to read CPU turbo boost status")? + { + return Ok(Some(content == 1)); } - None + Ok(None) } } diff --git a/src/fs.rs b/src/fs.rs index 4c11178..526856d 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -1,4 +1,4 @@ -use std::{fs, io, path::Path}; +use std::{error, fs, io, path::Path, str}; use anyhow::Context; @@ -15,32 +15,35 @@ pub fn read_dir(path: impl AsRef) -> anyhow::Result { .with_context(|| format!("failed to read directory '{path}'", path = path.display())) } -pub fn read(path: impl AsRef) -> Option> { +pub fn read(path: impl AsRef) -> anyhow::Result> { let path = path.as_ref(); match fs::read_to_string(path) { - Ok(string) => Some(Ok(string.trim().to_owned())), + Ok(string) => Ok(Some(string.trim().to_owned())), - Err(error) if error.kind() == io::ErrorKind::NotFound => None, + Err(error) if error.kind() == io::ErrorKind::NotFound => Ok(None), - Err(error) => Some( - Err(error).with_context(|| format!("failed to read '{path}", path = path.display())), - ), + Err(error) => { + Err(error).with_context(|| format!("failed to read '{path}", path = path.display())) + } } } -pub fn read_u64(path: impl AsRef) -> Option> { +pub fn read_n(path: impl AsRef) -> anyhow::Result> +where + N::Err: error::Error + Send + Sync + 'static, +{ let path = path.as_ref(); match read(path)? { - Ok(content) => Some(content.trim().parse().with_context(|| { + Some(content) => Ok(Some(content.trim().parse().with_context(|| { format!( "failed to parse contents of '{path}' as a unsigned number", path = path.display(), ) - })), + })?)), - Err(error) => Some(Err(error)), + None => Ok(None), } } diff --git a/src/monitor.rs b/src/monitor.rs index 7ab1bb0..19cc69e 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -218,8 +218,6 @@ pub fn get_all_cpu_core_info() -> anyhow::Result> { } pub fn get_cpu_global_info(cpu_cores: &[CpuCoreInfo]) -> CpuGlobalInfo { - let platform_profile = read_sysfs_file_trimmed("/sys/firmware/acpi/platform_profile").ok(); - // Calculate average CPU temperature from the core temperatures let average_temperature_celsius = if cpu_cores.is_empty() { None @@ -244,120 +242,16 @@ pub fn get_cpu_global_info(cpu_cores: &[CpuCoreInfo]) -> CpuGlobalInfo { // Return the constructed CpuGlobalInfo CpuGlobalInfo { - platform_profile, average_temperature_celsius, } } pub fn get_battery_info(config: &AppConfig) -> anyhow::Result> { - let mut batteries = Vec::new(); - let power_supply_path = Path::new("/sys/class/power_supply"); - - if !power_supply_path.exists() { - return Ok(batteries); // no power supply directory - } - - let ignored_supplies = config.ignored_power_supplies.clone().unwrap_or_default(); - - // Determine overall AC connection status - let mut overall_ac_connected = false; - for entry in fs::read_dir(power_supply_path)? { - let entry = entry?; - let ps_path = entry.path(); - let name = entry.file_name().into_string().unwrap_or_default(); - - // Check for AC adapter type (common names: AC, ACAD, ADP) - if let Ok(ps_type) = read_sysfs_file_trimmed(ps_path.join("type")) { - if ps_type == "Mains" - || ps_type == "USB_PD_DRP" - || ps_type == "USB_PD" - || ps_type == "USB_DCP" - || ps_type == "USB_CDP" - || ps_type == "USB_ACA" - { - // USB types can also provide power - if let Ok(online) = read_sysfs_value::(ps_path.join("online")) { - if online == 1 { - overall_ac_connected = true; - break; - } - } - } - } else if name.starts_with("AC") || name.contains("ACAD") || name.contains("ADP") { - // Fallback for type file missing - if let Ok(online) = read_sysfs_value::(ps_path.join("online")) { - if online == 1 { - overall_ac_connected = true; - break; - } - } - } - } - // No AC adapter detected but we're on a desktop system // Default to AC power for desktops if !overall_ac_connected { overall_ac_connected = is_likely_desktop_system(); } - - for entry in fs::read_dir(power_supply_path)? { - let entry = entry?; - let ps_path = entry.path(); - let name = entry.file_name().into_string().unwrap_or_default(); - - if ignored_supplies.contains(&name) { - continue; - } - - if let Ok(ps_type) = read_sysfs_file_trimmed(ps_path.join("type")) { - if ps_type == "Battery" { - // Skip peripheral batteries that aren't real laptop batteries - if is_peripheral_battery(&ps_path, &name) { - log::debug!("Skipping peripheral battery: {name}"); - continue; - } - - let status_str = read_sysfs_file_trimmed(ps_path.join("status")).ok(); - let capacity_percent = read_sysfs_value::(ps_path.join("capacity")).ok(); - - let power_rate_watts = if ps_path.join("power_now").exists() { - read_sysfs_value::(ps_path.join("power_now")) // uW - .map(|uw| uw as f32 / 1_000_000.0) - .ok() - } else if ps_path.join("current_now").exists() - && ps_path.join("voltage_now").exists() - { - let current_ua = read_sysfs_value::(ps_path.join("current_now")).ok(); // uA - let voltage_uv = read_sysfs_value::(ps_path.join("voltage_now")).ok(); // uV - if let (Some(c), Some(v)) = (current_ua, voltage_uv) { - // Power (W) = (Voltage (V) * Current (A)) - // (v / 1e6 V) * (c / 1e6 A) = (v * c / 1e12) W - Some((f64::from(c) * f64::from(v) / 1_000_000_000_000.0) as f32) - } else { - None - } - } else { - None - }; - - let charge_start_threshold = - read_sysfs_value::(ps_path.join("charge_control_start_threshold")).ok(); - let charge_stop_threshold = - read_sysfs_value::(ps_path.join("charge_control_end_threshold")).ok(); - - batteries.push(BatteryInfo { - name: name.clone(), - ac_connected: overall_ac_connected, - charging_state: status_str, - capacity_percent, - power_rate_watts, - charge_start_threshold, - charge_stop_threshold, - }); - } - } - } - // If we found no batteries but have power supplies, we're likely on a desktop if batteries.is_empty() && overall_ac_connected { log::debug!("No laptop batteries found, likely a desktop system"); @@ -366,23 +260,6 @@ pub fn get_battery_info(config: &AppConfig) -> anyhow::Result> Ok(batteries) } -pub fn collect_system_report(config: &AppConfig) -> anyhow::Result { - let system_info = get_system_info(); - let cpu_cores = get_all_cpu_core_info()?; - let cpu_global = get_cpu_global_info(&cpu_cores); - let batteries = get_battery_info(config)?; - let system_load = get_system_load()?; - - Ok(SystemReport { - system_info, - cpu_cores, - cpu_global, - batteries, - system_load, - timestamp: SystemTime::now(), - }) -} - pub fn get_cpu_model() -> anyhow::Result { let path = Path::new("/proc/cpuinfo"); let content = fs::read_to_string(path).map_err(|_| { diff --git a/src/power_supply.rs b/src/power_supply.rs index 5bbcebc..f213e5b 100644 --- a/src/power_supply.rs +++ b/src/power_supply.rs @@ -44,16 +44,38 @@ const POWER_SUPPLY_THRESHOLD_CONFIGS: &[PowerSupplyThresholdConfig] = &[ ]; /// Represents a power supply that supports charge threshold control. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq)] pub struct PowerSupply { pub name: String, pub path: PathBuf, + pub type_: String, pub is_from_peripheral: bool, + pub charge_state: Option, + pub charge_percent: Option, + + pub charge_threshold_start: f64, + pub charge_threshold_end: f64, + + pub drain_rate_watts: Option, + pub threshold_config: Option, } +impl PowerSupply { + pub fn is_ac(&self) -> bool { + !self.is_from_peripheral + && matches!( + &*self.type_, + "Mains" | "USB_PD_DRP" | "USB_PD" | "USB_DCP" | "USB_CDP" | "USB_ACA" + ) + || self.type_.starts_with("AC") + || self.type_.contains("ACAD") + || self.type_.contains("ADP") + } +} + impl fmt::Display for PowerSupply { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "power supply '{name}'", name = self.name.yellow())?; @@ -77,6 +99,15 @@ impl PowerSupply { let mut power_supply = Self { path: Path::new(POWER_SUPPLY_PATH).join(&name), name, + type_: String::new(), + + charge_state: None, + charge_percent: None, + + charge_threshold_start: 0.0, + charge_threshold_end: 1.0, + + drain_rate_watts: None, is_from_peripheral: false, @@ -99,6 +130,15 @@ impl PowerSupply { .to_string(), path, + type_: String::new(), + + charge_state: None, + charge_percent: None, + + charge_threshold_start: 0.0, + charge_threshold_end: 1.0, + + drain_rate_watts: None, is_from_peripheral: false, @@ -131,37 +171,18 @@ impl PowerSupply { Ok(power_supplies) } - fn get_type(&self) -> anyhow::Result { - let type_path = self.path.join("type"); - - let type_ = fs::read(&type_path) - .with_context(|| format!("'{path}' doesn't exist", path = type_path.display()))? - .with_context(|| format!("failed to read '{path}'", path = type_path.display()))?; - - Ok(type_) - } - pub fn rescan(&mut self) -> anyhow::Result<()> { if !self.path.exists() { bail!("{self} does not exist"); } - self.threshold_config = self - .get_type() - .with_context(|| format!("failed to determine what type of power supply '{self}' is"))? - .eq("Battery") - .then(|| { - for config in POWER_SUPPLY_THRESHOLD_CONFIGS { - if self.path.join(config.path_start).exists() - && self.path.join(config.path_end).exists() - { - return Some(*config); - } - } + self.type_ = { + let type_path = self.path.join("type"); - None - }) - .flatten(); + fs::read(&type_path) + .with_context(|| format!("failed to read '{path}'", path = type_path.display()))? + .with_context(|| format!("'{path}' doesn't exist", path = type_path.display()))? + }; self.is_from_peripheral = 'is_from_peripheral: { let name_lower = self.name.to_lowercase(); @@ -179,10 +200,9 @@ impl PowerSupply { } // Small capacity batteries are likely not laptop batteries. - if let Some(energy_full) = fs::read_u64(self.path.join("energy_full")) { - let energy_full = energy_full - .with_context(|| format!("failed to read the max charge '{self}' can hold"))?; - + if let Some(energy_full) = fs::read_n::(self.path.join("energy_full")) + .with_context(|| format!("failed to read the max charge {self} can hold"))? + { // Most laptop batteries are at least 20,000,000 µWh (20 Wh). // Peripheral batteries are typically much smaller. if energy_full < 10_000_000 { @@ -191,10 +211,9 @@ impl PowerSupply { } } // Check for model name that indicates a peripheral - if let Some(model) = fs::read(self.path.join("model_name")) { - let model = - model.with_context(|| format!("failed to read the model name of '{self}'"))?; - + if let Some(model) = fs::read(self.path.join("model_name")) + .with_context(|| format!("failed to read the model name of {self}"))? + { if model.contains("bluetooth") || model.contains("wireless") { break 'is_from_peripheral true; } @@ -203,6 +222,53 @@ impl PowerSupply { false }; + if self.type_ == "Battery" { + self.charge_state = fs::read(self.path.join("status")) + .with_context(|| format!("failed to read {self} charge status"))?; + + self.charge_percent = fs::read_n::(self.path.join("capacity")) + .with_context(|| format!("failed to read {self} charge percent"))? + .map(|percent| percent as f64 / 100.0); + + self.charge_threshold_start = + fs::read_n::(self.path.join("charge_control_start_threshold")) + .with_context(|| format!("failed to read {self} charge threshold start"))? + .map_or(0.0, |percent| percent as f64 / 100.0); + + self.charge_threshold_end = + fs::read_n::(self.path.join("charge_control_end_threshold")) + .with_context(|| format!("failed to read {self} charge threshold end"))? + .map_or(100.0, |percent| percent as f64 / 100.0); + + self.drain_rate_watts = match fs::read_n::(self.path.join("power_now")) + .with_context(|| format!("failed to read {self} power drain"))? + { + Some(drain) => Some(drain as f64), + + None => { + let current_ua = fs::read_n::(self.path.join("current_now")) + .with_context(|| format!("failed to read {self} current"))?; + + let voltage_uv = fs::read_n::(self.path.join("voltage_now")) + .with_context(|| format!("failed to read {self} voltage"))?; + + current_ua.zip(voltage_uv).map(|(current, voltage)| { + // Power (W) = Voltage (V) * Current (A) + // (v / 1e6 V) * (c / 1e6 A) = (v * c / 1e12) W + current as f64 * voltage as f64 / 1e12 + }) + } + }; + + self.threshold_config = POWER_SUPPLY_THRESHOLD_CONFIGS + .iter() + .find(|config| { + self.path.join(config.path_start).exists() + && self.path.join(config.path_end).exists() + }) + .copied(); + } + Ok(()) } @@ -216,7 +282,10 @@ impl PowerSupply { .map(|config| self.path.join(config.path_end)) } - pub fn set_charge_threshold_start(&self, charge_threshold_start: u8) -> anyhow::Result<()> { + pub fn set_charge_threshold_start( + &mut self, + charge_threshold_start: f64, + ) -> anyhow::Result<()> { fs::write( &self.charge_threshold_path_start().ok_or_else(|| { anyhow!( @@ -224,16 +293,18 @@ impl PowerSupply { name = self.name, ) })?, - &charge_threshold_start.to_string(), + &((charge_threshold_start * 100.0) as u8).to_string(), ) .with_context(|| format!("failed to set charge threshold start for {self}"))?; + self.charge_threshold_start = charge_threshold_start; + log::info!("set battery threshold start for {self} to {charge_threshold_start}%"); Ok(()) } - pub fn set_charge_threshold_end(&self, charge_threshold_end: u8) -> anyhow::Result<()> { + pub fn set_charge_threshold_end(&mut self, charge_threshold_end: f64) -> anyhow::Result<()> { fs::write( &self.charge_threshold_path_end().ok_or_else(|| { anyhow!( @@ -241,26 +312,30 @@ impl PowerSupply { name = self.name, ) })?, - &charge_threshold_end.to_string(), + &((charge_threshold_end * 100.0) as u8).to_string(), ) .with_context(|| format!("failed to set charge threshold end for {self}"))?; + self.charge_threshold_end = charge_threshold_end; + log::info!("set battery threshold end for {self} to {charge_threshold_end}%"); Ok(()) } - pub fn get_available_platform_profiles() -> Vec { + pub fn get_available_platform_profiles() -> anyhow::Result> { let path = "/sys/firmware/acpi/platform_profile_choices"; - let Some(Ok(content)) = fs::read(path) else { - return Vec::new(); + let Some(content) = + fs::read(path).context("failed to read available ACPI platform profiles")? + else { + return Ok(Vec::new()); }; - content + Ok(content .split_whitespace() .map(ToString::to_string) - .collect() + .collect()) } /// Sets the platform profile. @@ -270,7 +345,7 @@ impl PowerSupply { /// /// [`The Kernel docs`]: pub fn set_platform_profile(profile: &str) -> anyhow::Result<()> { - let profiles = Self::get_available_platform_profiles(); + let profiles = Self::get_available_platform_profiles()?; if !profiles .iter() @@ -285,4 +360,10 @@ impl PowerSupply { fs::write("/sys/firmware/acpi/platform_profile", profile) .context("this probably means that your system does not support changing ACPI profiles") } + + pub fn platform_profile() -> anyhow::Result { + fs::read("/sys/firmware/acpi/platform_profile") + .context("failed to read platform profile")? + .context("failed to find platform profile") + } } diff --git a/src/system.rs b/src/system.rs index f8820b1..5781c3c 100644 --- a/src/system.rs +++ b/src/system.rs @@ -33,9 +33,9 @@ impl System { } fn rescan_is_desktop(&mut self) -> anyhow::Result<()> { - if let Some(chassis_type) = fs::read("/sys/class/dmi/id/chassis_type") { - let chassis_type = chassis_type.context("failed to read chassis type")?; - + if let Some(chassis_type) = + fs::read("/sys/class/dmi/id/chassis_type").context("failed to read chassis type")? + { // 3=Desktop, 4=Low Profile Desktop, 5=Pizza Box, 6=Mini Tower // 7=Tower, 8=Portable, 9=Laptop, 10=Notebook, 11=Hand Held, 13=All In One // 14=Sub Notebook, 15=Space-saving, 16=Lunch Box, 17=Main Server Chassis @@ -86,8 +86,8 @@ impl System { fn rescan_load_average(&mut self) -> anyhow::Result<()> { let content = fs::read("/proc/loadavg") - .context("load average file doesn't exist, are you on linux?")? - .context("failed to read load average")?; + .context("failed to read load average")? + .context("load average file doesn't exist, are you on linux?")?; let mut parts = content.split_whitespace();