added config option
This commit is contained in:
11
README.md
11
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.
|
||||
|
||||
@@ -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<PathBuf>) -> 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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<PathBuf>) -> 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<RwLock<Config>>) -> 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,
|
||||
|
||||
18
src/main.rs
18
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<PathBuf>,
|
||||
},
|
||||
/// 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()]));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user