diff --git a/example.config.toml b/example.config.toml index ca14178..f3e9e68 100644 --- a/example.config.toml +++ b/example.config.toml @@ -29,40 +29,49 @@ backlight = 13 dnd = 14 [network] +# enabled = false # set to false to disable this module at runtime # tokens: {interface}, {ip}, {rx}, {tx} format = "{interface} ({ip}):  {rx:^4.1} MB/s  {tx:^4.1} MB/s" [cpu] +# enabled = false # set to false to disable this module at runtime # tokens: {usage}, {temp}, {model} format = "CPU: {usage:^4.1}% {temp:^4.1}C" [memory] +# enabled = false # set to false to disable this module at runtime # tokens: {used}, {total} format = "MEM: {used:^4.1}/{total:^4.1}GB" [gpu] +# enabled = false # set to false to disable this module at runtime # tokens: {usage}, {vram_used}, {vram_total}, {temp} format_amd = "AMD: {usage:>3.0}% {vram_used:>4.1}/{vram_total:>4.1}GB {temp:>4.1}C" format_intel = "iGPU: {usage:>3.0}%" format_nvidia = "NV: {usage:>3.0}% {vram_used:>4.1}/{vram_total:>4.1}GB {temp:>4.1}C" [sys] +# enabled = false # set to false to disable this module at runtime # tokens: {uptime}, {load1}, {load5}, {load15}, {procs} format = "UP: {uptime} LOAD: {load1:^3.1} " [disk] +# enabled = false # set to false to disable this module at runtime # tokens: {mount}, {used}, {total} format = "{mount} {used:^3.0}/{total:^3.0}G" [pool] +# enabled = false # set to false to disable this module at runtime # tokens: {used}, {total} format = "{used:>4.0}G / {total:>4.0}G" [power] +# enabled = false # set to false to disable this module at runtime # tokens: {percentage}, {icon} format = "{percentage:>3}% {icon}" [audio] +# enabled = false # set to false to disable this module at runtime # tokens: {name}, {volume}, {icon} format_sink_unmuted = "{name} {volume:>3}% {icon}" format_sink_muted = "{name} {icon}" @@ -70,6 +79,7 @@ format_source_unmuted = "{name} {volume:>3}% {icon}" format_source_muted = "{name} {icon}" [bt] +# enabled = false # set to false to disable this module at runtime # tokens: {alias}, {mac}, {left}, {right}, {anc} format_plugin = "{alias} [{left}|{right}] {anc} 󰂰" format_connected = "󰂰 {alias}" @@ -77,21 +87,26 @@ format_disconnected = "󰂯 Disconnected" format_disabled = "󰂲 Off" [game] +# enabled = false # set to false to disable this module at runtime format_active = "󰊖" format_inactive = "" [mpris] +# enabled = false # set to false to disable this module at runtime # tokens: {artist}, {title}, {album}, {status_icon} format = "{status_icon} {artist} - {title}" [backlight] +# enabled = false # set to false to disable this module at runtime # tokens: {percentage}, {icon} format = "{percentage:>3}% {icon}" [keyboard] +# enabled = false # set to false to disable this module at runtime # tokens: {layout} format = "{layout}" [dnd] +# enabled = false # set to false to disable this module at runtime format_dnd = "󰂛" format_normal = "󰂚" diff --git a/src/config.rs b/src/config.rs index 2d38aab..eb3e33a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -91,12 +91,15 @@ pub struct SignalsConfig { #[derive(Deserialize, Clone)] pub struct NetworkConfig { + #[serde(default = "default_true")] + pub enabled: bool, pub format: String, } impl Default for NetworkConfig { fn default() -> Self { Self { + enabled: true, format: "{interface} ({ip}):  {rx:>5.2} MB/s  {tx:>5.2} MB/s".to_string(), } } @@ -104,12 +107,15 @@ impl Default for NetworkConfig { #[derive(Deserialize, Clone)] pub struct CpuConfig { + #[serde(default = "default_true")] + pub enabled: bool, pub format: String, } impl Default for CpuConfig { fn default() -> Self { Self { + enabled: true, format: "CPU: {usage:>4.1}% {temp:>4.1}C".to_string(), } } @@ -117,12 +123,15 @@ impl Default for CpuConfig { #[derive(Deserialize, Clone)] pub struct MemoryConfig { + #[serde(default = "default_true")] + pub enabled: bool, pub format: String, } impl Default for MemoryConfig { fn default() -> Self { Self { + enabled: true, format: "{used:>5.2}/{total:>5.2}GB".to_string(), } } @@ -130,6 +139,8 @@ impl Default for MemoryConfig { #[derive(Deserialize, Clone)] pub struct GpuConfig { + #[serde(default = "default_true")] + pub enabled: bool, pub format_amd: String, pub format_intel: String, pub format_nvidia: String, @@ -138,6 +149,7 @@ pub struct GpuConfig { impl Default for GpuConfig { fn default() -> Self { Self { + enabled: true, format_amd: "AMD: {usage:>3.0}% {vram_used:>4.1}/{vram_total:>4.1}GB {temp:>4.1}C" .to_string(), format_intel: "iGPU: {usage:>3.0}%".to_string(), @@ -149,12 +161,15 @@ impl Default for GpuConfig { #[derive(Deserialize, Clone)] pub struct SysConfig { + #[serde(default = "default_true")] + pub enabled: bool, pub format: String, } impl Default for SysConfig { fn default() -> Self { Self { + enabled: true, format: "UP: {uptime} | LOAD: {load1:>4.2} {load5:>4.2} {load15:>4.2}".to_string(), } } @@ -162,12 +177,15 @@ impl Default for SysConfig { #[derive(Deserialize, Clone)] pub struct DiskConfig { + #[serde(default = "default_true")] + pub enabled: bool, pub format: String, } impl Default for DiskConfig { fn default() -> Self { Self { + enabled: true, format: "{mount} {used:>5.1}/{total:>5.1}G".to_string(), } } @@ -175,12 +193,15 @@ impl Default for DiskConfig { #[derive(Deserialize, Clone)] pub struct PoolConfig { + #[serde(default = "default_true")] + pub enabled: bool, pub format: String, } impl Default for PoolConfig { fn default() -> Self { Self { + enabled: true, format: "{used:>4.0}G / {total:>4.0}G".to_string(), } } @@ -188,12 +209,15 @@ impl Default for PoolConfig { #[derive(Deserialize, Clone)] pub struct PowerConfig { + #[serde(default = "default_true")] + pub enabled: bool, pub format: String, } impl Default for PowerConfig { fn default() -> Self { Self { + enabled: true, format: "{percentage:>3}% {icon}".to_string(), } } @@ -201,6 +225,8 @@ impl Default for PowerConfig { #[derive(Deserialize, Clone)] pub struct AudioConfig { + #[serde(default = "default_true")] + pub enabled: bool, pub format_sink_unmuted: String, pub format_sink_muted: String, pub format_source_unmuted: String, @@ -210,6 +236,7 @@ pub struct AudioConfig { impl Default for AudioConfig { fn default() -> Self { Self { + enabled: true, format_sink_unmuted: "{name} {volume:>3}% {icon}".to_string(), format_sink_muted: "{name} {icon}".to_string(), format_source_unmuted: "{name} {volume:>3}% {icon}".to_string(), @@ -220,6 +247,8 @@ impl Default for AudioConfig { #[derive(Deserialize, Clone)] pub struct BtConfig { + #[serde(default = "default_true")] + pub enabled: bool, pub format_connected: String, pub format_plugin: String, pub format_disconnected: String, @@ -229,6 +258,7 @@ pub struct BtConfig { impl Default for BtConfig { fn default() -> Self { Self { + enabled: true, format_connected: "{alias} 󰂰".to_string(), format_plugin: "{alias} [{left}|{right}] {anc} 󰂰".to_string(), format_disconnected: "󰂯".to_string(), @@ -239,6 +269,8 @@ impl Default for BtConfig { #[derive(Deserialize, Clone)] pub struct GameConfig { + #[serde(default = "default_true")] + pub enabled: bool, pub format_active: String, pub format_inactive: String, } @@ -246,6 +278,7 @@ pub struct GameConfig { impl Default for GameConfig { fn default() -> Self { Self { + enabled: true, format_active: "󰊖".to_string(), format_inactive: "".to_string(), } @@ -254,12 +287,15 @@ impl Default for GameConfig { #[derive(Deserialize, Clone)] pub struct MprisConfig { + #[serde(default = "default_true")] + pub enabled: bool, pub format: String, } impl Default for MprisConfig { fn default() -> Self { Self { + enabled: true, format: "{status_icon} {artist} - {title}".to_string(), } } @@ -267,12 +303,15 @@ impl Default for MprisConfig { #[derive(Deserialize, Clone)] pub struct BacklightConfig { + #[serde(default = "default_true")] + pub enabled: bool, pub format: String, } impl Default for BacklightConfig { fn default() -> Self { Self { + enabled: true, format: "{percentage:>3}% {icon}".to_string(), } } @@ -280,12 +319,15 @@ impl Default for BacklightConfig { #[derive(Deserialize, Clone)] pub struct KeyboardConfig { + #[serde(default = "default_true")] + pub enabled: bool, pub format: String, } impl Default for KeyboardConfig { fn default() -> Self { Self { + enabled: true, format: "{layout}".to_string(), } } @@ -293,13 +335,20 @@ impl Default for KeyboardConfig { #[derive(Deserialize, Clone)] pub struct DndConfig { + #[serde(default = "default_true")] + pub enabled: bool, pub format_dnd: String, pub format_normal: String, } +fn default_true() -> bool { + true +} + impl Default for DndConfig { fn default() -> Self { Self { + enabled: true, format_dnd: "󰂛".to_string(), format_normal: "󰂚".to_string(), } @@ -325,6 +374,44 @@ fn validate_format(label: &str, format_str: &str, known_tokens: &[&str]) { } impl Config { + /// Check if a module is enabled in the configuration. + /// Returns false if the module is explicitly disabled; true if enabled or unknown. + pub fn is_module_enabled(&self, module_name: &str) -> bool { + match module_name { + #[cfg(feature = "mod-network")] + "net" | "network" => self.network.enabled, + #[cfg(feature = "mod-hardware")] + "cpu" => self.cpu.enabled, + #[cfg(feature = "mod-hardware")] + "mem" | "memory" => self.memory.enabled, + #[cfg(feature = "mod-hardware")] + "gpu" => self.gpu.enabled, + #[cfg(feature = "mod-hardware")] + "sys" => self.sys.enabled, + #[cfg(feature = "mod-hardware")] + "disk" => self.disk.enabled, + #[cfg(feature = "mod-hardware")] + "pool" | "btrfs" => self.pool.enabled, + #[cfg(feature = "mod-hardware")] + "power" => self.power.enabled, + #[cfg(feature = "mod-hardware")] + "game" => self.game.enabled, + #[cfg(feature = "mod-audio")] + "vol" | "audio" | "mic" => self.audio.enabled, + #[cfg(feature = "mod-bt")] + "bt" | "bluetooth" => self.bt.enabled, + #[cfg(feature = "mod-dbus")] + "mpris" => self.mpris.enabled, + #[cfg(feature = "mod-dbus")] + "backlight" => self.backlight.enabled, + #[cfg(feature = "mod-dbus")] + "kbd" | "keyboard" => self.keyboard.enabled, + #[cfg(feature = "mod-dbus")] + "dnd" => self.dnd.enabled, + _ => true, + } + } + pub fn validate(&self) { #[cfg(feature = "mod-network")] validate_format( diff --git a/src/daemon.rs b/src/daemon.rs index 7b7d120..7df1990 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -183,7 +183,7 @@ pub async fn run_daemon(config_path: Option) -> Result<()> { // 1. Network Task #[cfg(feature = "mod-network")] - { + if config.read().await.network.enabled { let token = cancel_token.clone(); let net_health = Arc::clone(&health); tokio::spawn(async move { @@ -206,48 +206,58 @@ pub async fn run_daemon(config_path: Option) -> Result<()> { // 2. Fast Hardware Task (CPU, Mem, Load) #[cfg(feature = "mod-hardware")] { - let token = cancel_token.clone(); - let hw_health = Arc::clone(&health); - tokio::spawn(async move { - info!("Starting Fast Hardware polling task"); - let mut daemon = HardwareDaemon::new(); - loop { - if !crate::health::is_poll_in_backoff("cpu", &hw_health).await { - daemon.poll_fast(&cpu_tx, &mem_tx, &sys_tx).await; + let cfg = config.read().await; + let fast_enabled = cfg.cpu.enabled || cfg.memory.enabled || cfg.sys.enabled; + drop(cfg); + if fast_enabled { + let token = cancel_token.clone(); + let hw_health = Arc::clone(&health); + tokio::spawn(async move { + info!("Starting Fast Hardware polling task"); + let mut daemon = HardwareDaemon::new(); + loop { + if !crate::health::is_poll_in_backoff("cpu", &hw_health).await { + daemon.poll_fast(&cpu_tx, &mem_tx, &sys_tx).await; + } + tokio::select! { + _ = token.cancelled() => break, + _ = sleep(Duration::from_secs(1)) => {} + } } - tokio::select! { - _ = token.cancelled() => break, - _ = sleep(Duration::from_secs(1)) => {} - } - } - info!("Fast Hardware task shut down."); - }); + info!("Fast Hardware task shut down."); + }); + } } // 3. Slow Hardware Task (GPU, Disks) #[cfg(feature = "mod-hardware")] { - let token = cancel_token.clone(); - let slow_health = Arc::clone(&health); - tokio::spawn(async move { - info!("Starting Slow Hardware polling task"); - let mut daemon = HardwareDaemon::new(); - loop { - if !crate::health::is_poll_in_backoff("gpu", &slow_health).await { - daemon.poll_slow(&gpu_tx, &disks_tx).await; + let cfg = config.read().await; + let slow_enabled = cfg.gpu.enabled || cfg.disk.enabled; + drop(cfg); + if slow_enabled { + let token = cancel_token.clone(); + let slow_health = Arc::clone(&health); + tokio::spawn(async move { + info!("Starting Slow Hardware polling task"); + let mut daemon = HardwareDaemon::new(); + loop { + if !crate::health::is_poll_in_backoff("gpu", &slow_health).await { + daemon.poll_slow(&gpu_tx, &disks_tx).await; + } + tokio::select! { + _ = token.cancelled() => break, + _ = sleep(Duration::from_secs(5)) => {} + } } - tokio::select! { - _ = token.cancelled() => break, - _ = sleep(Duration::from_secs(5)) => {} - } - } - info!("Slow Hardware task shut down."); - }); + info!("Slow Hardware task shut down."); + }); + } } // 4. Bluetooth Task #[cfg(feature = "mod-bt")] - { + if config.read().await.bt.enabled { let token = cancel_token.clone(); let bt_health = Arc::clone(&health); let poll_config = Arc::clone(&config); @@ -272,35 +282,35 @@ pub async fn run_daemon(config_path: Option) -> Result<()> { // 5. Audio Thread (Event driven) #[cfg(feature = "mod-audio")] - { + if config.read().await.audio.enabled { let audio_daemon = AudioDaemon::new(); audio_daemon.start(&audio_tx, audio_cmd_rx); } // 5.1 Backlight Thread (Event driven) #[cfg(feature = "mod-dbus")] - { + if config.read().await.backlight.enabled { let backlight_daemon = BacklightDaemon::new(); backlight_daemon.start(backlight_tx); } // 5.2 Keyboard Thread (Event driven) #[cfg(feature = "mod-dbus")] - { + if config.read().await.keyboard.enabled { let keyboard_daemon = KeyboardDaemon::new(); keyboard_daemon.start(keyboard_tx); } // 5.3 DND Thread (Event driven) #[cfg(feature = "mod-dbus")] - { + if config.read().await.dnd.enabled { let dnd_daemon = DndDaemon::new(); dnd_daemon.start(dnd_tx); } // 5.4 MPRIS Thread #[cfg(feature = "mod-dbus")] - { + if config.read().await.mpris.enabled { let mpris_daemon = MprisDaemon::new(); mpris_daemon.start(mpris_tx); } @@ -416,6 +426,9 @@ async fn handle_request( match result { Ok(output) => serde_json::to_string(&output).unwrap_or_else(|_| "{}".to_string()), + Err(crate::error::FluxoError::Disabled(_)) => { + "{\"text\":\"\",\"tooltip\":\"Module disabled\",\"class\":\"disabled\"}".to_string() + } Err(e) => crate::health::error_response(module_name, &e, cached_output), } } diff --git a/src/error.rs b/src/error.rs index a8c669c..4a8c78b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -33,6 +33,9 @@ pub enum FluxoError { #[error("Serialization error: {0}")] Serialization(#[from] serde_json::Error), + #[error("Module disabled: {0}")] + Disabled(String), + #[error("Other error: {0}")] Other(#[from] anyhow::Error), } diff --git a/src/health.rs b/src/health.rs index a1260ad..bff2e45 100644 --- a/src/health.rs +++ b/src/health.rs @@ -36,6 +36,7 @@ pub async fn update_health( health.backoff_until = None; health.last_successful_output = Some(output.clone()); } + Err(crate::error::FluxoError::Disabled(_)) => {} Err(e) => { health.consecutive_failures += 1; health.last_failure = Some(Instant::now()); diff --git a/src/registry.rs b/src/registry.rs index 006f42f..d70dda0 100644 --- a/src/registry.rs +++ b/src/registry.rs @@ -12,6 +12,10 @@ pub async fn dispatch( #[allow(unused)] state: &AppReceivers, #[allow(unused)] args: &[&str], ) -> FluxoResult { + if !config.is_module_enabled(module_name) { + return Err(FluxoError::Disabled(module_name.to_string())); + } + match module_name { #[cfg(feature = "mod-network")] "net" | "network" => {