diff --git a/Cargo.lock b/Cargo.lock index eb3bc861..d9fbbfe1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -111,6 +111,29 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "battery" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4b624268937c0e0a3edb7c27843f9e547c320d730c610d3b8e6e8e95b2026e4" +dependencies = [ + "cfg-if", + "core-foundation 0.7.0", + "lazycell", + "libc", + "mach", + "nix 0.19.1", + "num-traits", + "uom", + "winapi", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.10.0" @@ -305,16 +328,32 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "core-foundation" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" +dependencies = [ + "core-foundation-sys 0.7.0", + "libc", +] + [[package]] name = "core-foundation" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ - "core-foundation-sys", + "core-foundation-sys 0.8.7", "libc", ] +[[package]] +name = "core-foundation-sys" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -336,7 +375,7 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ - "bitflags", + "bitflags 2.10.0", "crossterm_winapi", "mio", "parking_lot", @@ -352,7 +391,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ - "bitflags", + "bitflags 2.10.0", "crossterm_winapi", "derive_more", "document-features", @@ -818,7 +857,7 @@ version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e2b37e2f62729cdada11f0e6b3b6fe383c69c29fc619e391223e12856af308c" dependencies = [ - "bitflags", + "bitflags 2.10.0", "libc", "libgit2-sys", "log", @@ -1008,7 +1047,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" dependencies = [ "android_system_properties", - "core-foundation-sys", + "core-foundation-sys 0.8.7", "iana-time-zone-haiku", "js-sys", "log", @@ -1181,6 +1220,7 @@ dependencies = [ "anyhow", "async-trait", "base64", + "battery", "chrono", "clap", "clap_complete", @@ -1195,7 +1235,7 @@ dependencies = [ "git2", "indicatif", "lazy_static", - "nix", + "nix 0.29.0", "once_cell", "pathdiff", "pulldown-cmark", @@ -1211,6 +1251,8 @@ dependencies = [ "serial_test", "sha2", "shellexpand", + "strum 0.27.2", + "strum_macros 0.27.2", "sudo", "tempfile", "thiserror 2.0.17", @@ -1306,6 +1348,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "libc" version = "0.2.177" @@ -1332,7 +1380,7 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ - "bitflags", + "bitflags 2.10.0", "libc", ] @@ -1421,6 +1469,15 @@ dependencies = [ "hashbrown 0.15.5", ] +[[package]] +name = "mach" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" +dependencies = [ + "libc", +] + [[package]] name = "memchr" version = "2.7.6" @@ -1472,13 +1529,25 @@ dependencies = [ "tempfile", ] +[[package]] +name = "nix" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ccba0cfe4fdf15982d1674c69b1fd80bad427d293849982668dfe454bd61f2" +dependencies = [ + "bitflags 1.3.2", + "cc", + "cfg-if", + "libc", +] + [[package]] name = "nix" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags", + "bitflags 2.10.0", "cfg-if", "cfg_aliases", "libc", @@ -1511,7 +1580,7 @@ version = "0.10.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" dependencies = [ - "bitflags", + "bitflags 2.10.0", "cfg-if", "foreign-types", "libc", @@ -1673,7 +1742,7 @@ version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f86ba2052aebccc42cbbb3ed234b8b13ce76f75c3551a303cb2bcffcff12bb14" dependencies = [ - "bitflags", + "bitflags 2.10.0", "getopts", "memchr", "pulldown-cmark-escape", @@ -1737,7 +1806,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" dependencies = [ - "bitflags", + "bitflags 2.10.0", "cassowary", "compact_str", "crossterm 0.28.1", @@ -1746,7 +1815,7 @@ dependencies = [ "itertools", "lru", "paste", - "strum", + "strum 0.26.3", "unicode-segmentation", "unicode-truncate", "unicode-width 0.2.0", @@ -1758,7 +1827,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags", + "bitflags 2.10.0", ] [[package]] @@ -1875,7 +1944,7 @@ version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1c93dd1c9683b438c392c492109cb702b8090b2bfc8fed6f6e4eb4523f17af3" dependencies = [ - "bitflags", + "bitflags 2.10.0", "fallible-iterator", "fallible-streaming-iterator", "hashlink", @@ -1890,7 +1959,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags", + "bitflags 2.10.0", "errno", "libc", "linux-raw-sys 0.4.15", @@ -1903,7 +1972,7 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ - "bitflags", + "bitflags 2.10.0", "errno", "libc", "linux-raw-sys 0.11.0", @@ -2000,9 +2069,9 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", + "bitflags 2.10.0", + "core-foundation 0.9.4", + "core-foundation-sys 0.8.7", "libc", "security-framework-sys", ] @@ -2013,7 +2082,7 @@ version = "2.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" dependencies = [ - "core-foundation-sys", + "core-foundation-sys 0.8.7", "libc", ] @@ -2274,9 +2343,15 @@ version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" dependencies = [ - "strum_macros", + "strum_macros 0.26.4", ] +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" + [[package]] name = "strum_macros" version = "0.26.4" @@ -2290,6 +2365,18 @@ dependencies = [ "syn", ] +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "subtle" version = "2.6.1" @@ -2343,8 +2430,8 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags", - "core-foundation", + "bitflags 2.10.0", + "core-foundation 0.9.4", "system-configuration-sys", ] @@ -2354,7 +2441,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" dependencies = [ - "core-foundation-sys", + "core-foundation-sys 0.8.7", "libc", ] @@ -2542,7 +2629,7 @@ version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" dependencies = [ - "bitflags", + "bitflags 2.10.0", "bytes", "futures-util", "http", @@ -2668,6 +2755,16 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "uom" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e76503e636584f1e10b9b3b9498538279561adcef5412927ba00c2b32c4ce5ed" +dependencies = [ + "num-traits", + "typenum", +] + [[package]] name = "url" version = "2.5.7" diff --git a/Cargo.toml b/Cargo.toml index 2f98090c..98dc1b10 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,6 +60,9 @@ serde_yaml = "0.9.34" nix = { version = "0.29", features = ["fs"] } pathdiff = "0.2" urlencoding = "2.1.3" +battery = "0.7.8" +strum = "0.27.2" +strum_macros = "0.27.2" [dev-dependencies] serial_test = "3" diff --git a/src/common/deps.rs b/src/common/deps.rs index 3bcf8bce..108f8198 100644 --- a/src/common/deps.rs +++ b/src/common/deps.rs @@ -108,3 +108,13 @@ pub static SMARTMONTOOLS: Dependency = Dependency { ], tests: &[InstallTest::WhichSucceeds("smartctl")], }; + +pub static POWERPROFILESDAEMON: Dependency = Dependency { + name: "power-profiles-daemon", + description: Some("Makes power profiles handling available over D-Bus"), + packages: &[ + PackageDefinition::new("power-profiles-daemon", PackageManager::Apt), + PackageDefinition::new("power-profiles-daemon", PackageManager::Pacman), + ], + tests: &[InstallTest::WhichSucceeds("powerprofilesctl")], +}; diff --git a/src/doctor/checks.rs b/src/doctor/checks.rs index 5a7d0c93..bdded64f 100644 --- a/src/doctor/checks.rs +++ b/src/doctor/checks.rs @@ -14,9 +14,11 @@ use crate::doctor::{CheckStatus, DoctorCheck, PrivilegeLevel}; pub mod completions; pub mod display; +pub mod energy; pub mod locale; pub mod nerdfont; pub mod network; +mod performance; pub mod security; pub mod storage; pub mod system; @@ -25,9 +27,11 @@ pub mod tools; // Re-export all check types for easy access pub use completions::ShellCompletionCheck; pub use display::SwayDisplayCheck; +pub use energy::PowerCheck; pub use locale::LocaleCheck; pub use nerdfont::NerdFontCheck; pub use network::{InstantRepoCheck, InternetCheck}; +pub use performance::PerformanceTest; pub use security::PolkitAgentCheck; pub use storage::{ PacmanCacheCheck, PacmanDbSyncCheck, PacmanStaleDownloadsCheck, SmartHealthCheck, YayCacheCheck, diff --git a/src/doctor/checks/energy.rs b/src/doctor/checks/energy.rs new file mode 100644 index 00000000..d426e7e9 --- /dev/null +++ b/src/doctor/checks/energy.rs @@ -0,0 +1,135 @@ +use crate::doctor::{CheckStatus, DoctorCheck}; +use async_trait::async_trait; +use battery::Battery; + +#[async_trait] +trait BatteryCheck: DoctorCheck { + /// Called asynchronously with batteries containing at least one item + fn check_parameter(&self, batteries: Vec) -> CheckStatus; + + /// Ensures that at least one battery is present + async fn execute(&self) -> CheckStatus { + if let Ok(manager) = battery::Manager::new() + && let Ok(maybe_batteries) = manager.batteries() + { + let batteries = maybe_batteries + .filter(|maybe_battery| maybe_battery.is_ok()) + .map(|battery| battery.unwrap()) + .collect::>(); + if batteries.is_empty() { + return CheckStatus::Skipped("No batteries found".into()); + } + + self.check_parameter(batteries) + } else { + CheckStatus::Fail { + message: "Could not initialize battery manager".to_string(), + fixable: false, + } + } + } + + fn format_battery(&self, battery: &Battery) -> String { + format!( + "{} {}", + battery.vendor().unwrap_or("Unknown vendor"), + battery.model().unwrap_or("Unknown model"), + ) + } +} + +#[derive(Default)] +pub struct PowerCheck; + +#[async_trait] +impl DoctorCheck for PowerCheck { + fn name(&self) -> &'static str { + "Power level".into() + } + + fn id(&self) -> &'static str { + "power".into() + } + + async fn execute(&self) -> CheckStatus { + BatteryCheck::execute(self).await + } +} + +impl BatteryCheck for PowerCheck { + fn check_parameter(&self, mut batteries: Vec) -> CheckStatus { + // Ordering by percentage + batteries.sort_by(|b1, b2| { + b1.state_of_charge() + .value + .total_cmp(&b2.state_of_charge().value) + }); + + // Get battery with the lowest charge + let lowest = batteries.first().unwrap(); + let lowest_charge = lowest.state_of_charge(); + let battery_str = self.format_battery(lowest); + let battery_status = lowest.state().to_string(); + let percent = (lowest_charge.value * 100.0) as u64; + match lowest_charge.value { + 0.0..0.25 => CheckStatus::Fail { + message: format!( + "{} - Critical power: {} % ({})", + battery_str, percent, battery_status + ), + fixable: false, + }, + 0.25..0.5 => CheckStatus::Warning { + message: format!( + "{} - Low power: {} %, ({})", + battery_str, percent, battery_status + ), + fixable: false, + }, + _ => CheckStatus::Pass(format!( + "{} - Power OK: {} % ({})", + battery_str, percent, battery_status + )), + } + } +} + +#[derive(Default)] +pub struct BatteryHealthCheck; + +#[async_trait] +impl DoctorCheck for BatteryHealthCheck { + fn name(&self) -> &'static str { + "Battery life" + } + + fn id(&self) -> &'static str { + "battery-life" + } + + async fn execute(&self) -> CheckStatus { + BatteryCheck::execute(self).await + } +} + +impl BatteryCheck for BatteryHealthCheck { + fn check_parameter(&self, mut batteries: Vec) -> CheckStatus { + batteries.sort_by(|b1, b2| { + b1.state_of_health() + .value + .total_cmp(&b2.state_of_health().value) + }); + let lowest = batteries.first().unwrap(); + let battery_str = self.format_battery(lowest); + let lowest_health = lowest.state_of_health(); + let percent = (lowest_health.value * 100.0) as u64; + if percent < 90 { + CheckStatus::Warning { + message: format!("{} health degraded - {} %", battery_str, percent), + fixable: false, + } + } else { + CheckStatus::Pass(format!("{} OK - {} %", battery_str, percent)) + } + } +} diff --git a/src/doctor/checks/performance.rs b/src/doctor/checks/performance.rs new file mode 100644 index 00000000..8f2a3bda --- /dev/null +++ b/src/doctor/checks/performance.rs @@ -0,0 +1,234 @@ +use crate::common::deps; +use crate::doctor::{CheckStatus, DoctorCheck, PrivilegeLevel}; +use anyhow::anyhow; +use async_trait::async_trait; +use std::str::FromStr; +use strum_macros::{Display, EnumString}; +use tokio::process::Command as TokioCommand; + +/// +/// Performance <-> Power-Saving +/// +/// Many laptops automatically activate power saving mode and sometimes do not +/// switch back to performance, even when plugged in. +/// This is a UNIX-only feature +/// We try to query the current power authority and get the mode +/// First we try "powerprofilesctl", if available, we manage power using this tool +/// Otherwise, we manually insert our preference: +/// The directory /sys/devices/system/cpu/cpu*/cpufreq/ provides information +/// about the current CPU power mode, * is to replace with the id of the core. +/// + +/// Adapts to one of the two possible ways described in the top comment +#[async_trait] +pub trait PowerHandle: Send + Sync { + /// Retrieves the current performance mode + async fn query_performance_mode(&self) -> anyhow::Result; + + /// Changes the performance mode, might require sudo + /// Returns true on success + async fn change_performance_mode(&self, mode: PowerMode) -> anyhow::Result<()>; + + /// Returns all available performance modes on the system + async fn available_modes(&self) -> Vec; +} + +/// Performance modes +#[derive(PartialEq, EnumString, Debug, Display)] +#[strum(ascii_case_insensitive)] +pub enum PowerMode { + #[strum(serialize = "performance")] + Performance, + + #[strum(serialize = "balanced")] + Balanced, + + #[strum(serialize = "power-saver", serialize = "powersave")] + PowerSaver, +} + +/// Implementation for `powerprofilesctl` +const PP_CTL: &str = "powerprofilesctl"; +#[derive(Default)] +pub struct GnomePowerHandle; + +#[async_trait] +impl PowerHandle for GnomePowerHandle { + async fn query_performance_mode(&self) -> anyhow::Result { + let output = TokioCommand::new(PP_CTL).arg("get").output().await?; + if output.status.success() { + let stdout = String::from_utf8_lossy(&output.stdout); + Ok(PowerMode::from_str(stdout.as_ref().trim())?) + } else { + Err(anyhow!("{} get returned non-zero value", PP_CTL)) + } + } + + async fn change_performance_mode(&self, mode: PowerMode) -> anyhow::Result<()> { + let gnome_identifier = match mode { + PowerMode::Performance => "performance", + PowerMode::Balanced => "balanced", + PowerMode::PowerSaver => "power-saver", + }; + + let success = TokioCommand::new(PP_CTL) + .args(["set", gnome_identifier]) + .output() + .await + .map(|output| output.status.success())?; + if !success { + Err(anyhow!("Failed to set power-saver mode")) + } else { + Ok(()) + } + } + + async fn available_modes(&self) -> Vec { + let output = TokioCommand::new(PP_CTL).arg("list").output().await; + + match output { + Ok(output) if output.status.success() => { + let stdout = String::from_utf8_lossy(&output.stdout); + stdout + .lines() + .filter_map(|line| { + let line = line.trim(); + if line.ends_with(':') { + let profile_name = + line.trim_end_matches(':').trim_start_matches('*').trim(); + profile_name.parse().ok() + } else { + None + } + }) + .collect() + } + _ => vec![], + } + } +} + +/// Uses `/sys/devices/system/cpu/cpu_/cpufreq` to control performance mode +#[derive(Default)] +pub struct LegacyPowerHandle; + +#[async_trait] +impl PowerHandle for LegacyPowerHandle { + async fn query_performance_mode(&self) -> anyhow::Result { + const GOVERNOR_PATH: &str = "/sys/devices/system/cpu/cpufreq/policy0/scaling_governor"; + + let content = tokio::fs::read_to_string(GOVERNOR_PATH).await?; + Ok(PowerMode::from_str(content.trim())?) + } + + async fn change_performance_mode(&self, mode: PowerMode) -> anyhow::Result<()> { + let available_modes = self.available_modes().await; + + if !available_modes.contains(&mode) { + return Err(anyhow!("Power mode {} is not available", mode)); + } + + let legacy_idenitfier = match mode { + PowerMode::Performance | PowerMode::Balanced => "performance", + PowerMode::PowerSaver => "powersave", + }; + + Ok(tokio::fs::write( + "/sys/devices/system/cpu/cpufreq/policy0/scaling_governor", + legacy_idenitfier, + ) + .await?) + } + + async fn available_modes(&self) -> Vec { + const AVAILABLE_GOVERNORS_PATH: &str = + "/sys/devices/system/cpu/cpufreq/policy0/scaling_available_governors"; + + match tokio::fs::read_to_string(AVAILABLE_GOVERNORS_PATH).await { + Ok(content) => content + .split_whitespace() + .filter_map(|governor| governor.parse().ok()) + .collect(), + Err(_) => vec![], + } + } +} + +/// Creates a power handle +#[derive(Default)] +pub struct PowerHandleFactory; + +impl PowerHandleFactory { + async fn build_power_handle(&self) -> anyhow::Result> { + // We check if powerprofilesctl is available + let profiled_power = deps::POWERPROFILESDAEMON.is_installed(); + if profiled_power { + let gnome_handle = GnomePowerHandle::default(); + if gnome_handle.query_performance_mode().await.is_ok() { + return Ok(Box::new(gnome_handle)); + } + } + + // If not, we check if we have access to the sysfiles + let sys_available = tokio::fs::try_exists( + "/sys/devices/system/cpu/cpufreq/policy0/scaling_available_governors", + ) + .await?; + if sys_available { + Ok(Box::new(LegacyPowerHandle::default())) + } else { + // Unfortunately we have no way to control power management + Err(anyhow!("Power management is not available")) + } + } +} + +#[derive(Default)] +pub struct PerformanceTest; + +impl PerformanceTest { + async fn try_execute(&self) -> anyhow::Result { + let handle = PowerHandleFactory::default().build_power_handle().await?; + let mode = handle.query_performance_mode().await?; + if mode != PowerMode::Performance { + Ok(CheckStatus::Warning { + message: format!("Power mode is not performance but {}", mode), + fixable: true, + }) + } else { + Ok(CheckStatus::Pass("Power mode is performance".into())) + } + } +} + +#[async_trait] +impl DoctorCheck for PerformanceTest { + fn name(&self) -> &'static str { + "Performance Mode" + } + + fn id(&self) -> &'static str { + "performance" + } + + async fn execute(&self) -> CheckStatus { + if let Ok(status) = self.try_execute().await { + status + } else { + CheckStatus::Skipped("Could not query performance mode".into()) + } + } + + fn fix_message(&self) -> Option { + Some("Set power mode to performance".into()) + } + + async fn fix(&self) -> anyhow::Result<()> { + let handle = PowerHandleFactory::default().build_power_handle().await?; + handle.change_performance_mode(PowerMode::Performance).await + } + + fn fix_privilege_level(&self) -> PrivilegeLevel { + PrivilegeLevel::Root + } +} diff --git a/src/doctor/registry.rs b/src/doctor/registry.rs index cddcd9b3..7655a733 100644 --- a/src/doctor/registry.rs +++ b/src/doctor/registry.rs @@ -1,4 +1,5 @@ use super::{DoctorCheck, checks::*}; +use crate::doctor::checks::energy::BatteryHealthCheck; use std::collections::HashMap; pub type CheckFactory = fn() -> Box; @@ -28,9 +29,11 @@ impl CheckRegistry { registry.register::("sway-display"); registry.register::("polkit-agent"); registry.register::("bat-cache"); + registry.register::("power"); + registry.register::("battery-life"); + registry.register::("performance"); registry.register::("git-config"); registry.register::("shell-completions"); - registry }