From e233750b245d9f9ae0c1391fe646c31a3dab88c7 Mon Sep 17 00:00:00 2001 From: Breadway Date: Mon, 18 May 2026 23:01:52 +0800 Subject: [PATCH] General Dev --- Pasted image.png | Bin 0 -> 5517 bytes src/bar/clock.rs | 6 +++++- src/bar/stats.rs | 52 +++++++++++++++++++++++++++++++++-------------- src/main.rs | 14 +++++++++++-- src/theme.rs | 11 ++++++---- 5 files changed, 61 insertions(+), 22 deletions(-) create mode 100644 Pasted image.png diff --git a/Pasted image.png b/Pasted image.png new file mode 100644 index 0000000000000000000000000000000000000000..0bff4e506488e37ac32894487be96daa97885b5c GIT binary patch literal 5517 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 { relm4::spawn(async move { loop { sender.input(AppInput::ClockTick); - tokio::time::sleep(std::time::Duration::from_secs(1)).await; + // Sleep until the top of the next minute — display is HH:MM only. + let secs = gtk4::glib::DateTime::now_local() + .map_or(0, |dt| dt.second()); + let wait = (60 - secs.rem_euclid(60)) as u64; + tokio::time::sleep(std::time::Duration::from_secs(wait.max(1))).await; } }); } diff --git a/src/bar/stats.rs b/src/bar/stats.rs index 18f12b8..7a73c85 100644 --- a/src/bar/stats.rs +++ b/src/bar/stats.rs @@ -1,11 +1,18 @@ use crate::{App, AppInput}; use relm4::ComponentSender; -use std::{fs, path::PathBuf, sync::Mutex}; +use std::{ + fs, + path::PathBuf, + sync::{ + atomic::{AtomicU8, Ordering}, + LazyLock, Mutex, OnceLock, + }, +}; -const WIFI_STRONG: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/assets/WiFi Strong.svg"); -const WIFI_MEDIUM: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/assets/WiFi Medium.svg"); -const WIFI_WEAK: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/assets/WiFi Weak.svg"); -const WIFI_OFF: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/assets/WiFi Connecting.svg"); +pub const WIFI_STRONG: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/assets/WiFi Strong.svg"); +pub const WIFI_MEDIUM: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/assets/WiFi Medium.svg"); +pub const WIFI_WEAK: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/assets/WiFi Weak.svg"); +pub const WIFI_OFF: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/assets/WiFi Connecting.svg"); #[derive(Debug)] pub struct Stats { @@ -22,7 +29,11 @@ struct CpuSnapshot { idle: u64, } -static PREV_CPU: std::sync::OnceLock> = std::sync::OnceLock::new(); +static PREV_CPU: OnceLock> = OnceLock::new(); +static BAT_PATH: OnceLock> = OnceLock::new(); +static WIFI_CACHE: LazyLock> = + LazyLock::new(|| Mutex::new(("—".to_string(), WIFI_OFF))); +static WIFI_TICK: AtomicU8 = AtomicU8::new(0); fn read_cpu() -> f32 { let text = fs::read_to_string("/proc/stat").unwrap_or_default(); @@ -65,15 +76,16 @@ fn read_ram() -> u64 { total.saturating_sub(avail) } -fn bat_path() -> Option { - fs::read_dir("/sys/class/power_supply") - .ok()? - .filter_map(|e| e.ok()) - .map(|e| e.path()) - .find(|p| { - p.file_name() - .map_or(false, |n| n.to_string_lossy().starts_with("BAT")) +fn bat_path() -> Option<&'static PathBuf> { + BAT_PATH + .get_or_init(|| { + fs::read_dir("/sys/class/power_supply") + .ok()? + .filter_map(|e| e.ok()) + .map(|e| e.path()) + .find(|p| p.file_name().map_or(false, |n| n.to_string_lossy().starts_with("BAT"))) }) + .as_ref() } fn read_power() -> Option { @@ -161,7 +173,17 @@ pub async fn poll() -> Stats { let mem = read_ram(); let power = read_power().map_or_else(|| " —W".into(), |w| format!("{w:4.1}W")); let bat = read_battery().map_or_else(|| " —".into(), |p| format!("{p:3}%")); - let (wifi_ssid, wifi_icon) = read_wifi().await; + // Refresh WiFi every 8 cycles (~16 s); cache the result in between. + let (wifi_ssid, wifi_icon) = { + let tick = WIFI_TICK.fetch_add(1, Ordering::Relaxed); + if tick % 8 == 0 { + let fresh = read_wifi().await; + *WIFI_CACHE.lock().unwrap() = fresh.clone(); + fresh + } else { + WIFI_CACHE.lock().unwrap().clone() + } + }; Stats { cpu: format!("{cpu:3.0}%"), mem: if mem >= 1024 * 1024 { diff --git a/src/main.rs b/src/main.rs index d1b5d6d..9e4827a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,6 +25,8 @@ pub struct App { bat_lbl: gtk4::Label, wifi_lbl: gtk4::Label, wifi_img: gtk4::Image, + // Pre-loaded textures indexed by the WIFI_* constant pointer values. + wifi_textures: std::collections::HashMap, } #[derive(Debug)] @@ -53,7 +55,6 @@ impl SimpleComponent for App { set_start_widget = >k::Box { set_orientation: gtk::Orientation::Horizontal, set_spacing: 0, - set_margin_start: 8, #[name = "workspace_box"] gtk::Box { @@ -92,6 +93,12 @@ impl SimpleComponent for App { wifi_lbl.set_max_width_chars(12); let wifi_img = gtk4::Image::from_paintable(Some(&svg_texture(asset!("WiFi Connecting.svg")))); + use bar::stats::{WIFI_OFF, WIFI_STRONG, WIFI_MEDIUM, WIFI_WEAK}; + let wifi_textures = [WIFI_STRONG, WIFI_MEDIUM, WIFI_WEAK, WIFI_OFF] + .into_iter() + .map(|p| (p.as_ptr() as usize, svg_texture(p))) + .collect(); + let mut model = App { workspaces: vec![], active_ws: 1, @@ -103,6 +110,7 @@ impl SimpleComponent for App { bat_lbl: bat_lbl.clone(), wifi_lbl: wifi_lbl.clone(), wifi_img: wifi_img.clone(), + wifi_textures, }; let widgets = view_output!(); model.workspace_box = widgets.workspace_box.clone(); @@ -149,7 +157,9 @@ impl SimpleComponent for App { self.pwr_lbl.set_label(&stats.power); self.bat_lbl.set_label(&stats.bat); self.wifi_lbl.set_label(&stats.wifi_ssid); - self.wifi_img.set_paintable(Some(&svg_texture(stats.wifi_icon))); + if let Some(tex) = self.wifi_textures.get(&(stats.wifi_icon.as_ptr() as usize)) { + self.wifi_img.set_paintable(Some(tex)); + } } } } diff --git a/src/theme.rs b/src/theme.rs index 5dcfb1d..e5db59c 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -44,10 +44,13 @@ fn load_css() -> String { }; format!( - "window.breadbar {{ background-color: {bg_rgba}; }}\ - .workspace-btn {{ background: {surface}; color: {fg}; border-radius: 4px;\ - border: none; min-width: 24px; padding: 0 8px; }}\ - .workspace-btn:hover, .workspace-btn.active {{ background: {accent}; }}\ + "* {{ font-family: 'JetBrainsMono Nerd Font Mono', monospace; font-size: 12px; }}\ + window.breadbar {{ background-color: {bg_rgba}; border-radius: 0; }}\ + .workspace-btn {{ background: transparent; color: {fg}; opacity: 0.45;\ + border-radius: 0 0 8px 8px; border: none; outline: none; box-shadow: none;\ + min-width: 24px; padding: 2px 8px; }}\ + .workspace-btn:hover {{ opacity: 0.8; }}\ + .workspace-btn.active {{ background: {accent}; opacity: 1; }}\ label {{ color: {fg}; }}\ window.breadbar-notification {{ background-color: alpha({bg_plain}, 0.95); }}\ .notification-card {{ background: {surface}; border-radius: 6px;\