updated with custom configuration

This commit is contained in:
2026-03-13 17:07:05 +01:00
parent e19fb69c72
commit 4cf61b6197
11 changed files with 363 additions and 127 deletions

44
example.config.toml Normal file
View File

@@ -0,0 +1,44 @@
# fluxo-rs example configuration
# place this at ~/.config/fluxo/config.toml
# network module (net)
# tokens: {interface}, {ip}, {rx}, {tx}, {rx:>5.2}, {tx:>5.2}
[network]
format = "{interface} ({ip}):  {rx:>5.2} MB/s  {tx:>5.2} MB/s"
# cpu module (cpu)
# tokens: {usage}, {temp}, {usage:>4.1}, {temp:>4.1}
[cpu]
format = "CPU: {usage:>4.1}% {temp:>4.1}C"
# memory module (mem)
# tokens: {used}, {total}, {used:>5.2}, {total:>5.2}
[memory]
format = "{used:>5.2}/{total:>5.2}GB"
# gpu module (gpu)
# tokens: {usage}, {vram_used}, {vram_total}, {temp}, {usage:>3.0}, {vram_used:>4.1}, {vram_total:>4.1}, {temp:>4.1}
[gpu]
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"
# system module (sys)
# tokens: {uptime}, {load1}, {load5}, {load15}, {load1:>4.2}, {load5:>4.2}, {load15:>4.2}
[sys]
format = "UP: {uptime} | LOAD: {load1:>4.2} {load5:>4.2} {load15:>4.2}"
# disk module (disk <path>)
# tokens: {mount}, {used}, {total}, {used:>5.1}, {total:>5.1}
[disk]
format = "{mount} {used:>5.1}/{total:>5.1}G"
# pool module (pool / btrfs)
# tokens: {used}, {total}, {used:>4.0}, {total:>4.0}
[pool]
format = "{used:>4.0}G / {total:>4.0}G"
# power/battery module (power)
# tokens: {percentage}, {icon}, {percentage:>3}
[power]
format = "{percentage:>3}% {icon}"

View File

@@ -6,6 +6,20 @@ use std::path::PathBuf;
pub struct Config {
#[serde(default)]
pub network: NetworkConfig,
#[serde(default)]
pub cpu: CpuConfig,
#[serde(default)]
pub memory: MemoryConfig,
#[serde(default)]
pub gpu: GpuConfig,
#[serde(default)]
pub sys: SysConfig,
#[serde(default)]
pub disk: DiskConfig,
#[serde(default)]
pub pool: PoolConfig,
#[serde(default)]
pub power: PowerConfig,
}
#[derive(Deserialize)]
@@ -16,7 +30,102 @@ pub struct NetworkConfig {
impl Default for NetworkConfig {
fn default() -> Self {
Self {
format: "{interface} ({ip}):  {rx} MB/s  {tx} MB/s".to_string(),
format: "{interface} ({ip}):  {rx:>5.2} MB/s  {tx:>5.2} MB/s".to_string(),
}
}
}
#[derive(Deserialize)]
pub struct CpuConfig {
pub format: String,
}
impl Default for CpuConfig {
fn default() -> Self {
Self {
format: "CPU: {usage:>4.1}% {temp:>4.1}C".to_string(),
}
}
}
#[derive(Deserialize)]
pub struct MemoryConfig {
pub format: String,
}
impl Default for MemoryConfig {
fn default() -> Self {
Self {
format: "{used:>5.2}/{total:>5.2}GB".to_string(),
}
}
}
#[derive(Deserialize)]
pub struct GpuConfig {
pub format_amd: String,
pub format_intel: String,
pub format_nvidia: String,
}
impl Default for GpuConfig {
fn default() -> Self {
Self {
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(),
format_nvidia: "NV: {usage:>3.0}% {vram_used:>4.1}/{vram_total:>4.1}GB {temp:>4.1}C".to_string(),
}
}
}
#[derive(Deserialize)]
pub struct SysConfig {
pub format: String,
}
impl Default for SysConfig {
fn default() -> Self {
Self {
format: "UP: {uptime} | LOAD: {load1:>4.2} {load5:>4.2} {load15:>4.2}".to_string(),
}
}
}
#[derive(Deserialize)]
pub struct DiskConfig {
pub format: String,
}
impl Default for DiskConfig {
fn default() -> Self {
Self {
format: "{mount} {used:>5.1}/{total:>5.1}G".to_string(),
}
}
}
#[derive(Deserialize)]
pub struct PoolConfig {
pub format: String,
}
impl Default for PoolConfig {
fn default() -> Self {
Self {
format: "{used:>4.0}G / {total:>4.0}G".to_string(),
}
}
}
#[derive(Deserialize)]
pub struct PowerConfig {
pub format: String,
}
impl Default for PowerConfig {
fn default() -> Self {
Self {
format: "{percentage:>3}% {icon}".to_string(),
}
}
}

View File

@@ -8,7 +8,7 @@ use sysinfo::Disks;
pub struct BtrfsModule;
impl WaybarModule for BtrfsModule {
fn run(&self, _config: &Config, _state: &SharedState, _args: &[&str]) -> Result<WaybarOutput> {
fn run(&self, config: &Config, _state: &SharedState, _args: &[&str]) -> Result<WaybarOutput> {
let disks = Disks::new_with_refreshed_list();
let mut total_used: f64 = 0.0;
let mut total_size: f64 = 0.0;
@@ -43,8 +43,14 @@ impl WaybarModule for BtrfsModule {
"normal"
};
let text = config.pool.format
.replace("{used:>4.0}", &format!("{:>4.0}", used_gb))
.replace("{total:>4.0}", &format!("{:>4.0}", size_gb))
.replace("{used}", &format!("{:.0}", used_gb))
.replace("{total}", &format!("{:.0}", size_gb));
Ok(WaybarOutput {
text: format!("{:.0}G / {:.0}G", used_gb, size_gb),
text,
tooltip: Some(format!("BTRFS Usage: {:.1}%", percentage)),
class: Some(class.to_string()),
percentage: Some(percentage as u8),

View File

@@ -7,7 +7,7 @@ use anyhow::Result;
pub struct CpuModule;
impl WaybarModule for CpuModule {
fn run(&self, _config: &Config, state: &SharedState, _args: &[&str]) -> Result<WaybarOutput> {
fn run(&self, config: &Config, state: &SharedState, _args: &[&str]) -> Result<WaybarOutput> {
let (usage, temp, model) = {
if let Ok(state_lock) = state.read() {
(
@@ -20,7 +20,11 @@ impl WaybarModule for CpuModule {
}
};
let text = format!("{:.1}% {:.1}C", usage, temp);
let text = config.cpu.format
.replace("{usage:>4.1}", &format!("{:>4.1}", usage))
.replace("{temp:>4.1}", &format!("{:>4.1}", temp))
.replace("{usage}", &format!("{:.1}", usage))
.replace("{temp}", &format!("{:.1}", temp));
let class = if usage > 95.0 {
"max"
@@ -31,7 +35,7 @@ impl WaybarModule for CpuModule {
};
Ok(WaybarOutput {
text: format!("CPU: {}", text),
text,
tooltip: Some(model),
class: Some(class.to_string()),
percentage: Some(usage as u8),

View File

@@ -8,7 +8,7 @@ use sysinfo::Disks;
pub struct DiskModule;
impl WaybarModule for DiskModule {
fn run(&self, _config: &Config, _state: &SharedState, args: &[&str]) -> Result<WaybarOutput> {
fn run(&self, config: &Config, _state: &SharedState, args: &[&str]) -> Result<WaybarOutput> {
let mountpoint = args.first().unwrap_or(&"/");
let disks = Disks::new_with_refreshed_list();
@@ -32,8 +32,15 @@ impl WaybarModule for DiskModule {
"normal"
};
let text = config.disk.format
.replace("{mount}", mountpoint)
.replace("{used:>5.1}", &format!("{:>5.1}", used_gb))
.replace("{total:>5.1}", &format!("{:>5.1}", total_gb))
.replace("{used}", &format!("{:.1}", used_gb))
.replace("{total}", &format!("{:.1}", total_gb));
return Ok(WaybarOutput {
text: format!("{} {:.1}G/{:.1}G", mountpoint, used_gb, total_gb),
text,
tooltip: Some(format!("Used: {:.1}G\nTotal: {:.1}G\nFree: {:.1}G", used_gb, total_gb, free_gb)),
class: Some(class.to_string()),
percentage: Some(percentage as u8),

View File

@@ -7,7 +7,7 @@ use anyhow::Result;
pub struct GpuModule;
impl WaybarModule for GpuModule {
fn run(&self, _config: &Config, state: &SharedState, _args: &[&str]) -> Result<WaybarOutput> {
fn run(&self, config: &Config, state: &SharedState, _args: &[&str]) -> Result<WaybarOutput> {
let (active, vendor, usage, vram_used, vram_total, temp, model) = {
if let Ok(state_lock) = state.read() {
(
@@ -41,13 +41,22 @@ impl WaybarModule for GpuModule {
"normal"
};
let text = if vendor == "Intel" {
// Intel usually doesn't expose easy VRAM or Temp without root
format!("iGPU: {:.0}%", usage)
} else {
format!("{}: {:.0}% {:.1}/{:.1}GB {:.1}C", vendor, usage, vram_used, vram_total, temp)
let format_str = match vendor.as_str() {
"Intel" => &config.gpu.format_intel,
"NVIDIA" => &config.gpu.format_nvidia,
_ => &config.gpu.format_amd,
};
let text = format_str
.replace("{usage:>3.0}", &format!("{:>3.0}", usage))
.replace("{vram_used:>4.1}", &format!("{:>4.1}", vram_used))
.replace("{vram_total:>4.1}", &format!("{:>4.1}", vram_total))
.replace("{temp:>4.1}", &format!("{:>4.1}", temp))
.replace("{usage}", &format!("{:.0}", usage))
.replace("{vram_used}", &format!("{:.1}", vram_used))
.replace("{vram_total}", &format!("{:.1}", vram_total))
.replace("{temp}", &format!("{:.1}", temp));
let tooltip = if vendor == "Intel" {
format!("Model: {}\nApprox Usage: {:.0}%", model, usage)
} else {

View File

@@ -4,6 +4,7 @@ use sysinfo::{Components, System};
pub struct HardwareDaemon {
sys: System,
components: Components,
gpu_vendor: Option<String>,
}
impl HardwareDaemon {
@@ -11,19 +12,17 @@ impl HardwareDaemon {
let mut sys = System::new_all();
sys.refresh_all();
let components = Components::new_with_refreshed_list();
Self { sys, components }
Self { sys, components, gpu_vendor: None }
}
pub fn poll(&mut self, state: SharedState) {
self.sys.refresh_cpu_usage();
self.sys.refresh_memory();
self.sys.refresh_processes(sysinfo::ProcessesToUpdate::All, true);
self.components.refresh(true);
let cpu_usage = self.sys.global_cpu_usage();
let cpu_model = self.sys.cpus().first().map(|c| c.brand().to_string()).unwrap_or_else(|| "Unknown".to_string());
// Try to find a reasonable CPU temperature
let mut cpu_temp = 0.0;
for component in &self.components {
let label = component.label().to_lowercase();
@@ -41,7 +40,17 @@ impl HardwareDaemon {
let load_avg = System::load_average();
let uptime = System::uptime();
let process_count = self.sys.processes().len();
// 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();
if parts.len() >= 4 {
if let Some(total_procs) = parts[3].split('/').nth(1) {
process_count = total_procs.parse().unwrap_or(0);
}
}
}
if let Ok(mut state_lock) = state.write() {
state_lock.cpu.usage = cpu_usage as f64;
@@ -61,92 +70,100 @@ impl HardwareDaemon {
}
}
fn poll_gpu(&self, gpu: &mut crate::state::GpuState) {
fn poll_gpu(&mut self, gpu: &mut crate::state::GpuState) {
gpu.active = false;
// 1. Try NVIDIA via nvidia-smi
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"])
.output()
{
if output.status.success() {
let stdout = String::from_utf8_lossy(&output.stdout);
if let Some(line) = stdout.lines().next() {
let parts: Vec<&str> = line.split(',').collect();
if parts.len() >= 5 {
gpu.active = true;
gpu.vendor = "NVIDIA".to_string();
gpu.usage = parts[0].trim().parse().unwrap_or(0.0);
gpu.vram_used = parts[1].trim().parse::<f64>().unwrap_or(0.0) / 1024.0; // from MB to GB
gpu.vram_total = parts[2].trim().parse::<f64>().unwrap_or(0.0) / 1024.0;
gpu.temp = parts[3].trim().parse().unwrap_or(0.0);
gpu.model = parts[4].trim().to_string();
return;
// 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"])
.output()
{
if output.status.success() {
let stdout = String::from_utf8_lossy(&output.stdout);
if let Some(line) = stdout.lines().next() {
let parts: Vec<&str> = line.split(',').collect();
if parts.len() >= 5 {
gpu.active = true;
gpu.vendor = "NVIDIA".to_string();
gpu.usage = parts[0].trim().parse().unwrap_or(0.0);
gpu.vram_used = parts[1].trim().parse::<f64>().unwrap_or(0.0) / 1024.0;
gpu.vram_total = parts[2].trim().parse::<f64>().unwrap_or(0.0) / 1024.0;
gpu.temp = parts[3].trim().parse().unwrap_or(0.0);
gpu.model = parts[4].trim().to_string();
self.gpu_vendor = Some("NVIDIA".to_string());
return;
}
}
}
}
}
// Iterate over sysfs for AMD or Intel
for i in 0..=3 {
let base = format!("/sys/class/drm/card{}/device", i);
// 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);
// 2. Try AMD
if let Ok(usage_str) = std::fs::read_to_string(format!("{}/gpu_busy_percent", base)) {
gpu.active = true;
gpu.vendor = "AMD".to_string();
gpu.usage = usage_str.trim().parse().unwrap_or(0.0);
if self.gpu_vendor.as_deref() == Some("AMD") || self.gpu_vendor.is_none() {
if let Ok(usage_str) = std::fs::read_to_string(format!("{}/gpu_busy_percent", base)) {
gpu.active = true;
gpu.vendor = "AMD".to_string();
gpu.usage = usage_str.trim().parse().unwrap_or(0.0);
if let Ok(mem_used) = std::fs::read_to_string(format!("{}/mem_info_vram_used", base)) {
gpu.vram_used = mem_used.trim().parse::<f64>().unwrap_or(0.0) / 1024.0 / 1024.0 / 1024.0;
}
if let Ok(mem_total) = std::fs::read_to_string(format!("{}/mem_info_vram_total", base)) {
gpu.vram_total = mem_total.trim().parse::<f64>().unwrap_or(0.0) / 1024.0 / 1024.0 / 1024.0;
if let Ok(mem_used) = std::fs::read_to_string(format!("{}/mem_info_vram_used", base)) {
gpu.vram_used = mem_used.trim().parse::<f64>().unwrap_or(0.0) / 1024.0 / 1024.0 / 1024.0;
}
if let Ok(mem_total) = std::fs::read_to_string(format!("{}/mem_info_vram_total", base)) {
gpu.vram_total = mem_total.trim().parse::<f64>().unwrap_or(0.0) / 1024.0 / 1024.0 / 1024.0;
}
if let Ok(entries) = std::fs::read_dir(format!("{}/hwmon", base)) {
for entry in entries.flatten() {
let temp_path = entry.path().join("temp1_input");
if let Ok(temp_str) = std::fs::read_to_string(temp_path) {
gpu.temp = temp_str.trim().parse::<f64>().unwrap_or(0.0) / 1000.0;
break;
}
}
}
gpu.model = "AMD GPU".to_string();
self.gpu_vendor = Some("AMD".to_string());
return;
}
}
if let Ok(entries) = std::fs::read_dir(format!("{}/hwmon", base)) {
for entry in entries.flatten() {
let temp_path = entry.path().join("temp1_input");
if let Ok(temp_str) = std::fs::read_to_string(temp_path) {
gpu.temp = temp_str.trim().parse::<f64>().unwrap_or(0.0) / 1000.0;
break;
if self.gpu_vendor.as_deref() == Some("Intel") || self.gpu_vendor.is_none() {
let freq_path = if std::path::Path::new(&format!("{}/gt_cur_freq_mhz", base)).exists() {
Some(format!("{}/gt_cur_freq_mhz", base))
} else if std::path::Path::new(&format!("/sys/class/drm/card{}/gt_cur_freq_mhz", i)).exists() {
Some(format!("/sys/class/drm/card{}/gt_cur_freq_mhz", i))
} else {
None
};
if let Some(path) = freq_path {
if let Ok(freq_str) = std::fs::read_to_string(&path) {
gpu.active = true;
gpu.vendor = "Intel".to_string();
let cur_freq = freq_str.trim().parse::<f64>().unwrap_or(0.0);
let mut max_freq = 0.0;
let max_path = path.replace("gt_cur_freq_mhz", "gt_max_freq_mhz");
if let Ok(max_str) = std::fs::read_to_string(max_path) {
max_freq = max_str.trim().parse::<f64>().unwrap_or(0.0);
}
gpu.usage = if max_freq > 0.0 { (cur_freq / max_freq) * 100.0 } else { 0.0 };
gpu.temp = 0.0;
gpu.vram_used = 0.0;
gpu.vram_total = 0.0;
gpu.model = format!("Intel iGPU ({}MHz)", cur_freq);
self.gpu_vendor = Some("Intel".to_string());
return;
}
}
}
gpu.model = "AMD GPU".to_string();
return;
}
// 3. Try Intel (iGPU)
let freq_path = if std::path::Path::new(&format!("{}/gt_cur_freq_mhz", base)).exists() {
Some(format!("{}/gt_cur_freq_mhz", base))
} else if std::path::Path::new(&format!("/sys/class/drm/card{}/gt_cur_freq_mhz", i)).exists() {
Some(format!("/sys/class/drm/card{}/gt_cur_freq_mhz", i))
} else {
None
};
if let Some(path) = freq_path {
if let Ok(freq_str) = std::fs::read_to_string(&path) {
gpu.active = true;
gpu.vendor = "Intel".to_string();
let cur_freq = freq_str.trim().parse::<f64>().unwrap_or(0.0);
let mut max_freq = 0.0;
let max_path = path.replace("gt_cur_freq_mhz", "gt_max_freq_mhz");
if let Ok(max_str) = std::fs::read_to_string(max_path) {
max_freq = max_str.trim().parse::<f64>().unwrap_or(0.0);
}
// Approximate usage via frequency scaling
gpu.usage = if max_freq > 0.0 { (cur_freq / max_freq) * 100.0 } else { 0.0 };
gpu.temp = 0.0; // Intel iGPU temps are usually tied to CPU package temp
gpu.vram_used = 0.0; // iGPU shares system memory
gpu.vram_total = 0.0;
gpu.model = format!("Intel iGPU ({}MHz)", cur_freq);
return;
}
}
}
}

View File

@@ -7,7 +7,7 @@ use anyhow::Result;
pub struct MemoryModule;
impl WaybarModule for MemoryModule {
fn run(&self, _config: &Config, state: &SharedState, _args: &[&str]) -> Result<WaybarOutput> {
fn run(&self, config: &Config, state: &SharedState, _args: &[&str]) -> Result<WaybarOutput> {
let (used_gb, total_gb) = {
if let Ok(state_lock) = state.read() {
(
@@ -21,6 +21,12 @@ impl WaybarModule for MemoryModule {
let ratio = if total_gb > 0.0 { (used_gb / total_gb) * 100.0 } else { 0.0 };
let text = config.memory.format
.replace("{used:>5.2}", &format!("{:>5.2}", used_gb))
.replace("{total:>5.2}", &format!("{:>5.2}", total_gb))
.replace("{used}", &format!("{:.2}", used_gb))
.replace("{total}", &format!("{:.2}", total_gb));
let class = if ratio > 95.0 {
"max"
} else if ratio > 75.0 {
@@ -30,7 +36,7 @@ impl WaybarModule for MemoryModule {
};
Ok(WaybarOutput {
text: format!("{:.2}/{:.2}GB", used_gb, total_gb),
text,
tooltip: None,
class: Some(class.to_string()),
percentage: Some(ratio as u8),

View File

@@ -13,6 +13,8 @@ pub struct NetworkDaemon {
last_time: u64,
last_rx_bytes: u64,
last_tx_bytes: u64,
cached_interface: Option<String>,
cached_ip: Option<String>,
}
impl NetworkDaemon {
@@ -21,40 +23,57 @@ impl NetworkDaemon {
last_time: 0,
last_rx_bytes: 0,
last_tx_bytes: 0,
cached_interface: None,
cached_ip: None,
}
}
pub fn poll(&mut self, state: SharedState) {
if let Ok(interface) = get_primary_interface() {
if !interface.is_empty() {
let time_now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
// Cache invalidation: if the interface directory doesn't exist, clear cache
if let Some(ref iface) = self.cached_interface {
if !std::path::Path::new(&format!("/sys/class/net/{}", iface)).exists() {
self.cached_interface = None;
self.cached_ip = None;
}
}
if let Ok((rx_bytes_now, tx_bytes_now)) = get_bytes(&interface) {
if self.last_time > 0 && time_now > self.last_time {
let time_diff = time_now - self.last_time;
let rx_bps = (rx_bytes_now.saturating_sub(self.last_rx_bytes)) / time_diff;
let tx_bps = (tx_bytes_now.saturating_sub(self.last_tx_bytes)) / time_diff;
let rx_mbps = (rx_bps as f64) / 1024.0 / 1024.0;
let tx_mbps = (tx_bps as f64) / 1024.0 / 1024.0;
debug!(interface, rx = rx_mbps, tx = tx_mbps, "Network stats updated");
if let Ok(mut state_lock) = state.write() {
state_lock.network.rx_mbps = rx_mbps;
state_lock.network.tx_mbps = tx_mbps;
}
}
self.last_time = time_now;
self.last_rx_bytes = rx_bytes_now;
self.last_tx_bytes = tx_bytes_now;
// Re-detect interface if needed
if self.cached_interface.is_none() {
if let Ok(iface) = get_primary_interface() {
if !iface.is_empty() {
self.cached_interface = Some(iface.clone());
self.cached_ip = get_ip_address(&iface);
}
}
}
if let Some(ref interface) = self.cached_interface {
let time_now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
if let Ok((rx_bytes_now, tx_bytes_now)) = get_bytes(interface) {
if self.last_time > 0 && time_now > self.last_time {
let time_diff = time_now - self.last_time;
let rx_bps = (rx_bytes_now.saturating_sub(self.last_rx_bytes)) / time_diff;
let tx_bps = (tx_bytes_now.saturating_sub(self.last_tx_bytes)) / time_diff;
let rx_mbps = (rx_bps as f64) / 1024.0 / 1024.0;
let tx_mbps = (tx_bps as f64) / 1024.0 / 1024.0;
if let Ok(mut state_lock) = state.write() {
state_lock.network.rx_mbps = rx_mbps;
state_lock.network.tx_mbps = tx_mbps;
}
}
self.last_time = time_now;
self.last_rx_bytes = rx_bytes_now;
self.last_tx_bytes = tx_bytes_now;
} else {
warn!("No primary network interface found during poll");
// Read failed, might be down
self.cached_interface = None;
}
}
}
@@ -87,6 +106,8 @@ impl WaybarModule for NetworkModule {
.format
.replace("{interface}", &interface)
.replace("{ip}", &ip)
.replace("{rx:>5.2}", &format!("{:>5.2}", rx_mbps))
.replace("{tx:>5.2}", &format!("{:>5.2}", tx_mbps))
.replace("{rx}", &format!("{:.2}", rx_mbps))
.replace("{tx}", &format!("{:.2}", tx_mbps));

View File

@@ -8,7 +8,7 @@ use std::fs;
pub struct PowerModule;
impl WaybarModule for PowerModule {
fn run(&self, _config: &Config, _state: &SharedState, _args: &[&str]) -> Result<WaybarOutput> {
fn run(&self, config: &Config, _state: &SharedState, _args: &[&str]) -> Result<WaybarOutput> {
let critical_threshold = 15;
let warning_threshold = 50;
@@ -24,7 +24,7 @@ impl WaybarModule for PowerModule {
}
}
// Check AC status as fallback or TLP proxy
// Check AC status
let mut ac_online = false;
if let Ok(entries) = fs::read_dir("/sys/class/power_supply") {
for entry in entries.flatten() {
@@ -59,7 +59,6 @@ impl WaybarModule for PowerModule {
}
};
// Read battery capacity and status
let capacity_str = fs::read_to_string(bat_path.join("capacity")).unwrap_or_else(|_| "0".to_string());
let percentage: u8 = capacity_str.trim().parse().unwrap_or(0);
let status_str = fs::read_to_string(bat_path.join("status")).unwrap_or_else(|_| "Unknown".to_string());
@@ -90,8 +89,13 @@ impl WaybarModule for PowerModule {
)
};
let text = config.power.format
.replace("{percentage:>3}", &format!("{:>3}", percentage))
.replace("{percentage}", &format!("{}", percentage))
.replace("{icon}", icon);
Ok(WaybarOutput {
text: format!("{}% {}", percentage, icon),
text,
tooltip: Some(tooltip),
class: Some(class.to_string()),
percentage: Some(percentage),

View File

@@ -7,7 +7,7 @@ use anyhow::Result;
pub struct SysModule;
impl WaybarModule for SysModule {
fn run(&self, _config: &Config, state: &SharedState, _args: &[&str]) -> Result<WaybarOutput> {
fn run(&self, config: &Config, state: &SharedState, _args: &[&str]) -> Result<WaybarOutput> {
let (load1, load5, load15, uptime_secs, process_count) = {
if let Ok(state_lock) = state.read() {
(
@@ -30,8 +30,17 @@ impl WaybarModule for SysModule {
format!("{}m", minutes)
};
let text = config.sys.format
.replace("{uptime}", &uptime_str)
.replace("{load1:>4.2}", &format!("{:>4.2}", load1))
.replace("{load5:>4.2}", &format!("{:>4.2}", load5))
.replace("{load15:>4.2}", &format!("{:>4.2}", load15))
.replace("{load1}", &format!("{:.2}", load1))
.replace("{load5}", &format!("{:.2}", load5))
.replace("{load15}", &format!("{:.2}", load15));
Ok(WaybarOutput {
text: format!("UP: {} | LOAD: {:.2} {:.2} {:.2}", uptime_str, load1, load5, load15),
text,
tooltip: Some(format!(
"Uptime: {}\nProcesses: {}\nLoad Avg: {:.2}, {:.2}, {:.2}",
uptime_str, process_count, load1, load5, load15