From ffc909c01cff3170025afc6bcc9ad46eaa523ae8 Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Fri, 13 Mar 2026 19:23:01 +0100 Subject: [PATCH] added config option --- README.md | 11 ++++++++--- src/config.rs | 34 ++++++++++++++++++++++++---------- src/daemon.rs | 22 ++++++++++++++-------- src/main.rs | 18 ++++++++++++------ 4 files changed, 58 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index c1eb117..37c2f69 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ This approach eliminates process spawning overhead and temporary file locking, r - **Ultra-lightweight**: Background polling is highly optimized (e.g., O(1) process counting). - **Jitter-free**: Uses zero-width sentinels and figure spaces to prevent waybar from trimming padding. - **Configurable**: Fully customizable output formats via toml config. -- **Interactive Menus**: Integrated support for selecting items (like Bluetooth devices) via external menus (e.g., Rofi, Wofi). +- **Interactive Menus**: Integrated support for selecting items (like Bluetooth devices) via external menus (e.g., Rofi, Wofi, Fuzzel). - **Live Reload**: Configuration can be reloaded without restarting the daemon. - **Multi-vendor GPU**: Native support for intel (igpu), amd, and nvidia. @@ -47,7 +47,11 @@ This approach eliminates process spawning overhead and temporary file locking, r 2. Start the daemon: ```bash + # Starts the daemon using the default config path (~/.config/fluxo/config.toml) ./target/release/fluxo-rs daemon & + + # Or provide a custom path + ./target/release/fluxo-rs daemon --config /path/to/your/config.toml & ``` 3. Configuration: @@ -84,8 +88,9 @@ The daemon can reload its configuration live: fluxo-rs reload ``` -### Logs -Run the daemon with debug logs for troubleshooting: +### Logging & Debugging +`fluxo-rs` uses the `tracing` ecosystem. If a module isn't behaving properly or your configuration isn't applying, start the daemon with debug logging enabled in the foreground: ```bash RUST_LOG=debug fluxo-rs daemon ``` +This will print verbose information regarding config parsing errors, subprocess failures, and IPC requests. diff --git a/src/config.rs b/src/config.rs index e33f01a..e87a461 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,7 @@ use serde::Deserialize; use std::fs; use std::path::PathBuf; +use tracing::{debug, info, warn}; #[derive(Deserialize, Default)] pub struct Config { @@ -221,18 +222,31 @@ impl Default for GameConfig { } } -pub fn load_config() -> Config { - let config_dir = std::env::var("XDG_CONFIG_HOME") - .map(PathBuf::from) - .unwrap_or_else(|_| { - let home = std::env::var("HOME").unwrap_or_else(|_| String::from("/")); - PathBuf::from(home).join(".config") - }); - let config_path = config_dir.join("fluxo/config.toml"); +pub fn load_config(custom_path: Option) -> Config { + let config_path = custom_path.unwrap_or_else(|| { + let config_dir = std::env::var("XDG_CONFIG_HOME") + .map(PathBuf::from) + .unwrap_or_else(|_| { + let home = std::env::var("HOME").unwrap_or_else(|_| String::from("/")); + PathBuf::from(home).join(".config") + }); + config_dir.join("fluxo/config.toml") + }); - if let Ok(content) = fs::read_to_string(config_path) { - toml::from_str(&content).unwrap_or_default() + if let Ok(content) = fs::read_to_string(&config_path) { + match toml::from_str(&content) { + Ok(cfg) => { + info!("Successfully loaded configuration from {:?}", config_path); + cfg + } + Err(e) => { + warn!("Failed to parse config at {:?}: {}", config_path, e); + warn!("Falling back to default configuration."); + Config::default() + } + } } else { + debug!("No config file found at {:?}, using default settings.", config_path); Config::default() } } diff --git a/src/daemon.rs b/src/daemon.rs index 6084e1e..4193c62 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -12,8 +12,9 @@ use std::sync::{Arc, RwLock}; use std::thread; use std::time::Duration; use tracing::{info, warn, error, debug}; +use std::path::PathBuf; -pub fn run_daemon() -> Result<()> { +pub fn run_daemon(config_path: Option) -> Result<()> { if fs::metadata(SOCKET_PATH).is_ok() { debug!("Removing stale socket file: {}", SOCKET_PATH); fs::remove_file(SOCKET_PATH)?; @@ -21,9 +22,11 @@ pub fn run_daemon() -> Result<()> { let state: SharedState = Arc::new(RwLock::new(AppState::default())); let listener = UnixListener::bind(SOCKET_PATH)?; - let config = Arc::new(RwLock::new(crate::config::load_config())); + + // We store the original config_path to allow proper reloading later + let config_path_clone = config_path.clone(); + let config = Arc::new(RwLock::new(crate::config::load_config(config_path))); - // Spawn the background polling thread let poll_state = Arc::clone(&state); thread::spawn(move || { info!("Starting background polling thread"); @@ -43,6 +46,7 @@ pub fn run_daemon() -> Result<()> { Ok(mut stream) => { let state_clone = Arc::clone(&state); let config_clone = Arc::clone(&config); + let cp_clone = config_path_clone.clone(); thread::spawn(move || { let mut reader = BufReader::new(stream.try_clone().unwrap()); let mut request = String::new(); @@ -58,10 +62,13 @@ pub fn run_daemon() -> Result<()> { if let Some(module_name) = parts.first() { if *module_name == "reload" { info!("Reloading configuration..."); - let new_config = crate::config::load_config(); + let new_config = crate::config::load_config(cp_clone); if let Ok(mut config_lock) = config_clone.write() { *config_lock = new_config; let _ = stream.write_all(b"{\"text\":\"ok\"}"); + info!("Configuration reloaded successfully."); + } else { + error!("Failed to acquire write lock for configuration reload."); } return; } @@ -82,12 +89,10 @@ pub fn run_daemon() -> Result<()> { } fn handle_request(module_name: &str, args: &[&str], state: &SharedState, config_lock: &Arc>) -> String { - debug!(module = module_name, args = ?args, "Handling request"); - let config = if let Ok(c) = config_lock.read() { c } else { - // Fallback to default if lock fails (should not happen normally) + error!("Failed to acquire read lock for configuration."); return "{\"text\":\"error: config lock failed\"}".to_string(); }; @@ -114,8 +119,9 @@ fn handle_request(module_name: &str, args: &[&str], state: &SharedState, config_ match result { Ok(output) => serde_json::to_string(&output).unwrap_or_else(|_| "{}".to_string()), Err(e) => { + error!(module = module_name, error = %e, "Module execution failed"); let err_out = crate::output::WaybarOutput { - text: "Error".to_string(), + text: format!("\u{200B}Error\u{200B}"), tooltip: Some(e.to_string()), class: Some("error".to_string()), percentage: None, diff --git a/src/main.rs b/src/main.rs index 14ab820..dfcd3e1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,7 @@ mod state; mod utils; use clap::{Parser, Subcommand}; +use std::path::PathBuf; use std::process; use tracing::{error, info}; use tracing_subscriber::{fmt, prelude::*, EnvFilter}; @@ -22,7 +23,11 @@ struct Cli { #[derive(Subcommand)] enum Commands { /// Start the background polling daemon - Daemon, + Daemon { + /// Optional custom path to config.toml + #[arg(short, long)] + config: Option, + }, /// Reload the daemon configuration Reload, /// Network speed module @@ -86,9 +91,9 @@ fn main() { let cli = Cli::parse(); match &cli.command { - Commands::Daemon => { + Commands::Daemon { config } => { info!("Starting Fluxo daemon..."); - if let Err(e) = daemon::run_daemon() { + if let Err(e) = daemon::run_daemon(config.clone()) { error!("Daemon failed: {}", e); process::exit(1); } @@ -120,7 +125,7 @@ fn main() { Commands::Bt { action } => { if action == "menu" { // Client-side execution of the menu - let config = config::load_config(); + let config = config::load_config(None); let devices_out = std::process::Command::new("bluetoothctl") .args(["devices"]) @@ -133,7 +138,6 @@ fn main() { if line.starts_with("Device ") { let parts: Vec<&str> = line.splitn(3, ' ').collect(); if parts.len() == 3 { - // Format: "Alias (MAC)" items.push(format!("{} ({})", parts[2], parts[1])); } } @@ -150,8 +154,10 @@ fn main() { } } } + } else { + info!("No paired Bluetooth devices found."); } - return; // Exit client after menu + return; } handle_ipc_response(ipc::request_data("bt", &[action.clone()])); }