fixed some overhead + updated readme

This commit is contained in:
2026-03-13 17:27:17 +01:00
parent 231a1b66c8
commit 5c942aebd2
6 changed files with 117 additions and 73 deletions

View File

@@ -20,6 +20,8 @@ pub struct Config {
pub pool: PoolConfig,
#[serde(default)]
pub power: PowerConfig,
#[serde(default)]
pub buds: BudsConfig,
}
#[derive(Deserialize)]
@@ -130,6 +132,19 @@ impl Default for PowerConfig {
}
}
#[derive(Deserialize)]
pub struct BudsConfig {
pub mac: String,
}
impl Default for BudsConfig {
fn default() -> Self {
Self {
mac: "B4:23:A2:09:D3:53".to_string(),
}
}
}
pub fn load_config() -> Config {
let config_dir = std::env::var("XDG_CONFIG_HOME")
.map(PathBuf::from)

View File

@@ -21,8 +21,7 @@ pub fn run_daemon() -> Result<()> {
let state: SharedState = Arc::new(RwLock::new(AppState::default()));
let listener = UnixListener::bind(SOCKET_PATH)?;
let config = crate::config::load_config();
let config = Arc::new(config);
let config = Arc::new(RwLock::new(crate::config::load_config()));
// Spawn the background polling thread
let poll_state = Arc::clone(&state);
@@ -57,6 +56,16 @@ pub fn run_daemon() -> Result<()> {
let parts: Vec<&str> = request.split_whitespace().collect();
if let Some(module_name) = parts.first() {
if *module_name == "reload" {
info!("Reloading configuration...");
let new_config = crate::config::load_config();
if let Ok(mut config_lock) = config_clone.write() {
*config_lock = new_config;
let _ = stream.write_all(b"{\"text\":\"ok\"}");
}
return;
}
debug!(module = module_name, args = ?&parts[1..], "Handling IPC request");
let response = handle_request(*module_name, &parts[1..], &state_clone, &config_clone);
if let Err(e) = stream.write_all(response.as_bytes()) {
@@ -72,23 +81,30 @@ pub fn run_daemon() -> Result<()> {
Ok(())
}
fn handle_request(module_name: &str, args: &[&str], state: &SharedState, config: &Config) -> String {
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)
return "{\"text\":\"error: config lock failed\"}".to_string();
};
let result = match module_name {
"net" | "network" => crate::modules::network::NetworkModule.run(config, state, args),
"cpu" => crate::modules::cpu::CpuModule.run(config, state, args),
"mem" | "memory" => crate::modules::memory::MemoryModule.run(config, state, args),
"disk" => crate::modules::disk::DiskModule.run(config, state, args),
"pool" | "btrfs" => crate::modules::btrfs::BtrfsModule.run(config, state, args),
"vol" => crate::modules::audio::AudioModule.run(config, state, &["sink", args.get(0).unwrap_or(&"show")]),
"mic" => crate::modules::audio::AudioModule.run(config, state, &["source", args.get(0).unwrap_or(&"show")]),
"gpu" => crate::modules::gpu::GpuModule.run(config, state, args),
"sys" => crate::modules::sys::SysModule.run(config, state, args),
"bt" | "bluetooth" => crate::modules::bt::BtModule.run(config, state, args),
"buds" => crate::modules::buds::BudsModule.run(config, state, args),
"power" => crate::modules::power::PowerModule.run(config, state, args),
"game" => crate::modules::game::GameModule.run(config, state, args),
"net" | "network" => crate::modules::network::NetworkModule.run(&config, state, args),
"cpu" => crate::modules::cpu::CpuModule.run(&config, state, args),
"mem" | "memory" => crate::modules::memory::MemoryModule.run(&config, state, args),
"disk" => crate::modules::disk::DiskModule.run(&config, state, args),
"pool" | "btrfs" => crate::modules::btrfs::BtrfsModule.run(&config, state, args),
"vol" => crate::modules::audio::AudioModule.run(&config, state, &["sink", args.get(0).unwrap_or(&"show")]),
"mic" => crate::modules::audio::AudioModule.run(&config, state, &["source", args.get(0).unwrap_or(&"show")]),
"gpu" => crate::modules::gpu::GpuModule.run(&config, state, args),
"sys" => crate::modules::sys::SysModule.run(&config, state, args),
"bt" | "bluetooth" => crate::modules::bt::BtModule.run(&config, state, args),
"buds" => crate::modules::buds::BudsModule.run(&config, state, args),
"power" => crate::modules::power::PowerModule.run(&config, state, args),
"game" => crate::modules::game::GameModule.run(&config, state, args),
_ => {
warn!("Received request for unknown module: '{}'", module_name);
Err(anyhow::anyhow!("Unknown module: {}", module_name))

View File

@@ -22,6 +22,8 @@ struct Cli {
enum Commands {
/// Start the background polling daemon
Daemon,
/// Reload the daemon configuration
Reload,
/// Network speed module
#[command(alias = "network")]
Net,
@@ -90,6 +92,15 @@ fn main() {
process::exit(1);
}
}
Commands::Reload => {
match ipc::request_data("reload", &[]) {
Ok(_) => info!("Reload signal sent to daemon"),
Err(e) => {
error!("Failed to send reload signal: {}", e);
process::exit(1);
}
}
}
Commands::Net => handle_ipc_response(ipc::request_data("net", &[])),
Commands::Cpu => handle_ipc_response(ipc::request_data("cpu", &[])),
Commands::Mem => handle_ipc_response(ipc::request_data("mem", &[])),
@@ -118,8 +129,6 @@ fn handle_ipc_response(response: anyhow::Result<String>) {
match serde_json::from_str::<serde_json::Value>(&json_str) {
Ok(mut val) => {
if let Some(text) = val.get_mut("text").and_then(|t| t.as_str()) {
// 1. Replace regular spaces with Figure Spaces (\u2007) for perfect numeric alignment
// 2. Wrap the text in Zero-Width Spaces (\u200B) to prevent Waybar from trimming
let fixed_text = format!("\u{200B}{}\u{200B}", text.replace(' ', "\u{2007}"));
val["text"] = serde_json::Value::String(fixed_text);
}

View File

@@ -7,11 +7,10 @@ use std::process::Command;
pub struct BudsModule;
const MAC_ADDRESS: &str = "B4:23:A2:09:D3:53";
impl WaybarModule for BudsModule {
fn run(&self, _config: &Config, _state: &SharedState, args: &[&str]) -> Result<WaybarOutput> {
fn run(&self, config: &Config, _state: &SharedState, args: &[&str]) -> Result<WaybarOutput> {
let action = args.first().unwrap_or(&"show");
let mac = &config.buds.mac;
match *action {
"cycle_anc" => {
@@ -33,7 +32,7 @@ impl WaybarModule for BudsModule {
});
}
"connect" => {
Command::new("bluetoothctl").args(["connect", MAC_ADDRESS]).status()?;
Command::new("bluetoothctl").args(["connect", mac]).status()?;
return Ok(WaybarOutput {
text: String::new(),
tooltip: None,
@@ -42,7 +41,7 @@ impl WaybarModule for BudsModule {
});
}
"disconnect" => {
Command::new("bluetoothctl").args(["disconnect", MAC_ADDRESS]).status()?;
Command::new("bluetoothctl").args(["disconnect", mac]).status()?;
return Ok(WaybarOutput {
text: String::new(),
tooltip: None,
@@ -54,7 +53,7 @@ impl WaybarModule for BudsModule {
}
// Check if connected
let bt_info = Command::new("bluetoothctl").args(["info", MAC_ADDRESS]).output()?;
let bt_info = Command::new("bluetoothctl").args(["info", mac]).output()?;
let bt_str = String::from_utf8_lossy(&bt_info.stdout);
if !bt_str.contains("Connected: yes") {

View File

@@ -41,7 +41,6 @@ impl HardwareDaemon {
let load_avg = System::load_average();
let uptime = System::uptime();
// Fast O(1) process count by reading loadavg instead of heavy sysinfo process refresh
let mut process_count = 0;
if let Ok(loadavg_str) = std::fs::read_to_string("/proc/loadavg") {
let parts: Vec<&str> = loadavg_str.split_whitespace().collect();
@@ -73,7 +72,6 @@ impl HardwareDaemon {
fn poll_gpu(&mut self, gpu: &mut crate::state::GpuState) {
gpu.active = false;
// Fast path: if we already detected NVIDIA, don't fallback to AMD/Intel scanning
if self.gpu_vendor.as_deref() == Some("NVIDIA") || self.gpu_vendor.is_none() {
if let Ok(output) = std::process::Command::new("nvidia-smi")
.args(["--query-gpu=utilization.gpu,memory.used,memory.total,temperature.gpu,name", "--format=csv,noheader,nounits"])
@@ -99,7 +97,6 @@ impl HardwareDaemon {
}
}
// Fast path: if we detected AMD or Intel, scan sysfs
if self.gpu_vendor.as_deref() == Some("AMD") || self.gpu_vendor.as_deref() == Some("Intel") || self.gpu_vendor.is_none() {
for i in 0..=3 {
let base = format!("/sys/class/drm/card{}/device", i);