diff --git a/Cargo.lock b/Cargo.lock index 4ac1b6f..6afa2b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -38,7 +38,7 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -49,9 +49,15 @@ checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" dependencies = [ "anstyle", "once_cell", - "windows-sys", + "windows-sys 0.59.0", ] +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.9.0" @@ -123,7 +129,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46f93780a459b7d656ef7f071fe699c4d3d2cb201c4b24d085b6ddc505276e73" dependencies = [ "nix", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -144,7 +150,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -153,6 +159,27 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "filetime" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" +dependencies = [ + "cfg-if", + "libc", + "libredox", + "windows-sys 0.59.0", +] + +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + [[package]] name = "getrandom" version = "0.2.16" @@ -192,12 +219,52 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "inotify" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" +dependencies = [ + "bitflags 2.9.0", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "kqueue" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + [[package]] name = "libc" version = "0.2.172" @@ -210,28 +277,75 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags", + "bitflags 2.9.0", "libc", + "redox_syscall", ] +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.52.0", +] + [[package]] name = "nix" version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags", + "bitflags 2.9.0", "cfg-if", "cfg_aliases", "libc", ] +[[package]] +name = "notify" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fee8403b3d66ac7b26aee6e40a897d85dc5ce26f44da36b8b73e987cc52e943" +dependencies = [ + "bitflags 2.9.0", + "filetime", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio", + "notify-types", + "walkdir", + "windows-sys 0.59.0", +] + +[[package]] +name = "notify-types" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d" +dependencies = [ + "serde", +] + [[package]] name = "num_cpus" version = "1.16.0" @@ -272,6 +386,15 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "redox_syscall" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" +dependencies = [ + "bitflags 2.9.0", +] + [[package]] name = "redox_users" version = "0.5.0" @@ -283,6 +406,15 @@ dependencies = [ "thiserror", ] +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "serde" version = "1.0.219" @@ -325,6 +457,7 @@ dependencies = [ "clap", "ctrlc", "dirs", + "notify", "num_cpus", "serde", "toml", @@ -414,12 +547,40 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.59.0" diff --git a/Cargo.toml b/Cargo.toml index 97f5926..d53a8b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,3 +10,4 @@ dirs = "6.0" clap = { version = "4.0", features = ["derive"] } num_cpus = "1.16" ctrlc = "3.4" +notify = { version = "8.0.0", features = ["serde"] } diff --git a/src/config/load.rs b/src/config/load.rs new file mode 100644 index 0000000..4e463b5 --- /dev/null +++ b/src/config/load.rs @@ -0,0 +1,76 @@ +// Configuration loading functionality +use std::fs; +use std::path::PathBuf; + +use crate::config::types::{AppConfig, AppConfigToml, ConfigError, DaemonConfig, ProfileConfig}; + +// The primary function to load application configuration. +// It tries user-specific and then system-wide TOML files. +// Falls back to default settings if no file is found or if parsing fails. +pub fn load_config() -> Result { + let mut config_paths: Vec = Vec::new(); + + // User-specific path + if let Some(home_dir) = dirs::home_dir() { + let user_config_path = home_dir.join(".config/auto_cpufreq_rs/config.toml"); + config_paths.push(user_config_path); + } else { + eprintln!( + "Warning: Could not determine home directory. User-specific config will not be loaded." + ); + } + + // System-wide path + let system_config_path = PathBuf::from("/etc/auto_cpufreq_rs/config.toml"); + config_paths.push(system_config_path); + + for path in config_paths { + if path.exists() { + println!("Attempting to load config from: {}", path.display()); + match fs::read_to_string(&path) { + Ok(contents) => { + match toml::from_str::(&contents) { + Ok(toml_app_config) => { + // Convert AppConfigToml to AppConfig + let app_config = AppConfig { + charger: ProfileConfig::from(toml_app_config.charger), + battery: ProfileConfig::from(toml_app_config.battery), + battery_charge_thresholds: toml_app_config.battery_charge_thresholds, + ignored_power_supplies: toml_app_config.ignored_power_supplies, + poll_interval_sec: toml_app_config.poll_interval_sec, + daemon: DaemonConfig { + poll_interval_sec: toml_app_config.daemon.poll_interval_sec, + adaptive_interval: toml_app_config.daemon.adaptive_interval, + min_poll_interval_sec: toml_app_config.daemon.min_poll_interval_sec, + max_poll_interval_sec: toml_app_config.daemon.max_poll_interval_sec, + throttle_on_battery: toml_app_config.daemon.throttle_on_battery, + log_level: toml_app_config.daemon.log_level, + stats_file_path: toml_app_config.daemon.stats_file_path, + }, + }; + return Ok(app_config); + } + Err(e) => { + eprintln!("Error parsing config file {}: {}", path.display(), e); + } + } + } + Err(e) => { + eprintln!("Error reading config file {}: {}", path.display(), e); + } + } + } + } + + println!("No configuration file found or all failed to parse. Using default configuration."); + // Construct default AppConfig by converting default AppConfigToml + let default_toml_config = AppConfigToml::default(); + Ok(AppConfig { + charger: ProfileConfig::from(default_toml_config.charger), + battery: ProfileConfig::from(default_toml_config.battery), + battery_charge_thresholds: default_toml_config.battery_charge_thresholds, + ignored_power_supplies: default_toml_config.ignored_power_supplies, + poll_interval_sec: default_toml_config.poll_interval_sec, + daemon: DaemonConfig::default(), + }) +} \ No newline at end of file diff --git a/src/config/mod.rs b/src/config/mod.rs new file mode 100644 index 0000000..3524422 --- /dev/null +++ b/src/config/mod.rs @@ -0,0 +1,9 @@ +pub mod watcher; + +// Re-export all configuration types and functions +pub use self::types::*; +pub use self::load::*; + +// Internal organization of config submodules +mod types; +mod load; \ No newline at end of file diff --git a/src/config.rs b/src/config/types.rs similarity index 69% rename from src/config.rs rename to src/config/types.rs index 9cc1eac..8342552 100644 --- a/src/config.rs +++ b/src/config/types.rs @@ -1,7 +1,6 @@ +// Configuration types and structures for superfreq use crate::core::TurboSetting; use serde::Deserialize; -use std::fs; -use std::path::PathBuf; // Structs for configuration using serde::Deserialize #[derive(Deserialize, Debug, Clone)] @@ -83,82 +82,6 @@ impl std::fmt::Display for ConfigError { impl std::error::Error for ConfigError {} -// The primary function to load application configuration. -// It tries user-specific and then system-wide TOML files. -// Falls back to default settings if no file is found or if parsing fails. -pub fn load_config() -> Result { - let mut config_paths: Vec = Vec::new(); - - // User-specific path - if let Some(home_dir) = dirs::home_dir() { - let user_config_path = home_dir.join(".config/auto_cpufreq_rs/config.toml"); - config_paths.push(user_config_path); - } else { - eprintln!( - "Warning: Could not determine home directory. User-specific config will not be loaded." - ); - } - - // System-wide path - let system_config_path = PathBuf::from("/etc/auto_cpufreq_rs/config.toml"); - config_paths.push(system_config_path); - - for path in config_paths { - if path.exists() { - println!("Attempting to load config from: {}", path.display()); - match fs::read_to_string(&path) { - Ok(contents) => { - match toml::from_str::(&contents) { - Ok(toml_app_config) => { - // Convert AppConfigToml to AppConfig - let app_config = AppConfig { - charger: ProfileConfig::from(toml_app_config.charger), - battery: ProfileConfig::from(toml_app_config.battery), - battery_charge_thresholds: toml_app_config - .battery_charge_thresholds, - ignored_power_supplies: toml_app_config.ignored_power_supplies, - poll_interval_sec: toml_app_config.poll_interval_sec, - daemon: DaemonConfig { - poll_interval_sec: toml_app_config.daemon.poll_interval_sec, - adaptive_interval: toml_app_config.daemon.adaptive_interval, - min_poll_interval_sec: toml_app_config - .daemon - .min_poll_interval_sec, - max_poll_interval_sec: toml_app_config - .daemon - .max_poll_interval_sec, - throttle_on_battery: toml_app_config.daemon.throttle_on_battery, - log_level: toml_app_config.daemon.log_level, - stats_file_path: toml_app_config.daemon.stats_file_path, - }, - }; - return Ok(app_config); - } - Err(e) => { - eprintln!("Error parsing config file {}: {}", path.display(), e); - } - } - } - Err(e) => { - eprintln!("Error reading config file {}: {}", path.display(), e); - } - } - } - } - - println!("No configuration file found or all failed to parse. Using default configuration."); - // Construct default AppConfig by converting default AppConfigToml - let default_toml_config = AppConfigToml::default(); - Ok(AppConfig { - charger: ProfileConfig::from(default_toml_config.charger), - battery: ProfileConfig::from(default_toml_config.battery), - battery_charge_thresholds: default_toml_config.battery_charge_thresholds, - ignored_power_supplies: default_toml_config.ignored_power_supplies, - poll_interval_sec: default_toml_config.poll_interval_sec, - daemon: DaemonConfig::default(), - }) -} - // Intermediate structs for TOML parsing #[derive(Deserialize, Debug, Clone)] pub struct ProfileConfigToml { @@ -350,4 +273,4 @@ impl Default for DaemonConfigToml { stats_file_path: default_stats_file_path(), } } -} +} \ No newline at end of file diff --git a/src/cpu.rs b/src/cpu.rs index 30165bd..85c10cf 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -3,8 +3,6 @@ use crate::util::error::ControlError; use core::str; use std::{fs, io, path::Path, string::ToString}; -impl std::error::Error for ControlError {} - pub type Result = std::result::Result; // Write a value to a sysfs file diff --git a/src/daemon.rs b/src/daemon.rs index bcf00ee..c966af6 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -1,4 +1,5 @@ use crate::config::{AppConfig, LogLevel}; +use crate::config::watcher::ConfigWatcher; use crate::conflict; use crate::core::SystemReport; use crate::engine; @@ -10,7 +11,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::time::{Duration, Instant}; /// Run the daemon -pub fn run_daemon(config: AppConfig, verbose: bool) -> Result<(), Box> { +pub fn run_daemon(mut config: AppConfig, verbose: bool) -> Result<(), Box> { // Set effective log level based on config and verbose flag let effective_log_level = if verbose { LogLevel::Debug @@ -63,6 +64,34 @@ pub fn run_daemon(config: AppConfig, verbose: bool) -> Result<(), Box = match config_file_path { + Some(path) => { + match ConfigWatcher::new(&path) { + Ok(watcher) => { + println!("Watching config file: {path}"); + Some(watcher) + }, + Err(e) => { + eprintln!("Failed to initialize config file watcher: {e}"); + None + } + } + }, + None => None, + }; + // Variables for adaptive polling let mut current_poll_interval = config.daemon.poll_interval_sec; let mut last_settings_change = Instant::now(); @@ -72,6 +101,24 @@ pub fn run_daemon(config: AppConfig, verbose: bool) -> Result<(), Box { + if verbose { + println!("Config file changed, updating configuration"); + } + config = new_config; + }, + Err(e) => { + eprintln!("Error loading new configuration: {e}"); + // Continue with existing config + } + } + } + } + match monitor::collect_system_report(&config) { Ok(report) => { log_message( diff --git a/src/engine.rs b/src/engine.rs index 4f031d2..768a192 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,37 +1,7 @@ use crate::config::{AppConfig, ProfileConfig}; use crate::core::{OperationalMode, SystemReport, TurboSetting}; use crate::cpu::{self}; -use crate::util::error::ControlError; - -#[derive(Debug)] -pub enum EngineError { - ControlError(ControlError), - ConfigurationError(String), -} - -impl From for EngineError { - fn from(err: ControlError) -> Self { - Self::ControlError(err) - } -} - -impl std::fmt::Display for EngineError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::ControlError(e) => write!(f, "CPU control error: {e}"), - Self::ConfigurationError(s) => write!(f, "Configuration error: {s}"), - } - } -} - -impl std::error::Error for EngineError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - Self::ControlError(e) => Some(e), - Self::ConfigurationError(_) => None, - } - } -} +use crate::util::error::EngineError; /// Determines the appropriate CPU profile based on power status or forced mode, /// and applies the settings using functions from the `cpu` module. @@ -158,6 +128,13 @@ fn manage_auto_turbo(report: &SystemReport, config: &ProfileConfig) -> Result<() } }; + // Validate the configuration to ensure it's usable + if turbo_settings.load_threshold_high <= turbo_settings.load_threshold_low { + return Err(EngineError::ConfigurationError( + "Invalid turbo auto settings: high threshold must be greater than low threshold".to_string() + )); + } + // Decision logic for enabling/disabling turbo let enable_turbo = match (cpu_temp, avg_cpu_usage) { // If temperature is too high, disable turbo regardless of load diff --git a/src/monitor.rs b/src/monitor.rs index 18795d8..8d1b3d1 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -1,11 +1,10 @@ use crate::config::AppConfig; use crate::core::{BatteryInfo, CpuCoreInfo, CpuGlobalInfo, SystemInfo, SystemLoad, SystemReport}; -use crate::cpu::{get_logical_core_count, get_platform_profiles}; -use crate::util::error::ControlError; +use crate::cpu::get_logical_core_count; use crate::util::error::SysMonitorError; use std::{ collections::HashMap, - fs, io, + fs, path::{Path, PathBuf}, str::FromStr, thread, @@ -13,8 +12,6 @@ use std::{ time::SystemTime, }; -impl std::error::Error for SysMonitorError {} - pub type Result = std::result::Result; // Read a sysfs file to a string, trimming whitespace @@ -417,8 +414,8 @@ pub fn get_cpu_global_info(cpu_cores: &[CpuCoreInfo]) -> CpuGlobalInfo { None }; - let available_governors = if cpufreq_base.join("scaling_available_governors").exists() { - read_sysfs_file_trimmed(cpufreq_base.join("scaling_available_governors")).map_or_else( + let available_governors = if cpufreq_base_path.join("scaling_available_governors").exists() { + read_sysfs_file_trimmed(cpufreq_base_path.join("scaling_available_governors")).map_or_else( |_| vec![], |s| s.split_whitespace().map(String::from).collect(), ) diff --git a/src/util/error.rs b/src/util/error.rs index 1dc49a9..3ee0a86 100644 --- a/src/util/error.rs +++ b/src/util/error.rs @@ -39,6 +39,8 @@ impl std::fmt::Display for ControlError { } } +impl std::error::Error for ControlError {} + #[derive(Debug)] pub enum SysMonitorError { Io(io::Error), @@ -67,3 +69,35 @@ impl std::fmt::Display for SysMonitorError { } } } + +impl std::error::Error for SysMonitorError {} + +#[derive(Debug)] +pub enum EngineError { + ControlError(ControlError), + ConfigurationError(String), +} + +impl From for EngineError { + fn from(err: ControlError) -> Self { + Self::ControlError(err) + } +} + +impl std::fmt::Display for EngineError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::ControlError(e) => write!(f, "CPU control error: {e}"), + Self::ConfigurationError(s) => write!(f, "Configuration error: {s}"), + } + } +} + +impl std::error::Error for EngineError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::ControlError(e) => Some(e), + Self::ConfigurationError(_) => None, + } + } +}