General Dev

This commit is contained in:
Breadway 2026-05-18 23:01:52 +08:00
parent 255be18ca2
commit e233750b24
5 changed files with 61 additions and 22 deletions

BIN
Pasted image.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

View file

@ -10,7 +10,11 @@ pub fn spawn_ticker(sender: ComponentSender<App>) {
relm4::spawn(async move { relm4::spawn(async move {
loop { loop {
sender.input(AppInput::ClockTick); 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;
} }
}); });
} }

View file

@ -1,11 +1,18 @@
use crate::{App, AppInput}; use crate::{App, AppInput};
use relm4::ComponentSender; 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"); pub 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"); pub 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"); pub 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_OFF: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/assets/WiFi Connecting.svg");
#[derive(Debug)] #[derive(Debug)]
pub struct Stats { pub struct Stats {
@ -22,7 +29,11 @@ struct CpuSnapshot {
idle: u64, idle: u64,
} }
static PREV_CPU: std::sync::OnceLock<Mutex<CpuSnapshot>> = std::sync::OnceLock::new(); static PREV_CPU: OnceLock<Mutex<CpuSnapshot>> = OnceLock::new();
static BAT_PATH: OnceLock<Option<PathBuf>> = OnceLock::new();
static WIFI_CACHE: LazyLock<Mutex<(String, &'static str)>> =
LazyLock::new(|| Mutex::new(("".to_string(), WIFI_OFF)));
static WIFI_TICK: AtomicU8 = AtomicU8::new(0);
fn read_cpu() -> f32 { fn read_cpu() -> f32 {
let text = fs::read_to_string("/proc/stat").unwrap_or_default(); let text = fs::read_to_string("/proc/stat").unwrap_or_default();
@ -65,15 +76,16 @@ fn read_ram() -> u64 {
total.saturating_sub(avail) total.saturating_sub(avail)
} }
fn bat_path() -> Option<PathBuf> { fn bat_path() -> Option<&'static PathBuf> {
fs::read_dir("/sys/class/power_supply") BAT_PATH
.ok()? .get_or_init(|| {
.filter_map(|e| e.ok()) fs::read_dir("/sys/class/power_supply")
.map(|e| e.path()) .ok()?
.find(|p| { .filter_map(|e| e.ok())
p.file_name() .map(|e| e.path())
.map_or(false, |n| n.to_string_lossy().starts_with("BAT")) .find(|p| p.file_name().map_or(false, |n| n.to_string_lossy().starts_with("BAT")))
}) })
.as_ref()
} }
fn read_power() -> Option<f32> { fn read_power() -> Option<f32> {
@ -161,7 +173,17 @@ pub async fn poll() -> Stats {
let mem = read_ram(); let mem = read_ram();
let power = read_power().map_or_else(|| " —W".into(), |w| format!("{w:4.1}W")); 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 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 { Stats {
cpu: format!("{cpu:3.0}%"), cpu: format!("{cpu:3.0}%"),
mem: if mem >= 1024 * 1024 { mem: if mem >= 1024 * 1024 {

View file

@ -25,6 +25,8 @@ pub struct App {
bat_lbl: gtk4::Label, bat_lbl: gtk4::Label,
wifi_lbl: gtk4::Label, wifi_lbl: gtk4::Label,
wifi_img: gtk4::Image, wifi_img: gtk4::Image,
// Pre-loaded textures indexed by the WIFI_* constant pointer values.
wifi_textures: std::collections::HashMap<usize, gtk4::gdk::Texture>,
} }
#[derive(Debug)] #[derive(Debug)]
@ -53,7 +55,6 @@ impl SimpleComponent for App {
set_start_widget = &gtk::Box { set_start_widget = &gtk::Box {
set_orientation: gtk::Orientation::Horizontal, set_orientation: gtk::Orientation::Horizontal,
set_spacing: 0, set_spacing: 0,
set_margin_start: 8,
#[name = "workspace_box"] #[name = "workspace_box"]
gtk::Box { gtk::Box {
@ -92,6 +93,12 @@ impl SimpleComponent for App {
wifi_lbl.set_max_width_chars(12); wifi_lbl.set_max_width_chars(12);
let wifi_img = gtk4::Image::from_paintable(Some(&svg_texture(asset!("WiFi Connecting.svg")))); 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 { let mut model = App {
workspaces: vec![], workspaces: vec![],
active_ws: 1, active_ws: 1,
@ -103,6 +110,7 @@ impl SimpleComponent for App {
bat_lbl: bat_lbl.clone(), bat_lbl: bat_lbl.clone(),
wifi_lbl: wifi_lbl.clone(), wifi_lbl: wifi_lbl.clone(),
wifi_img: wifi_img.clone(), wifi_img: wifi_img.clone(),
wifi_textures,
}; };
let widgets = view_output!(); let widgets = view_output!();
model.workspace_box = widgets.workspace_box.clone(); model.workspace_box = widgets.workspace_box.clone();
@ -149,7 +157,9 @@ impl SimpleComponent for App {
self.pwr_lbl.set_label(&stats.power); self.pwr_lbl.set_label(&stats.power);
self.bat_lbl.set_label(&stats.bat); self.bat_lbl.set_label(&stats.bat);
self.wifi_lbl.set_label(&stats.wifi_ssid); 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));
}
} }
} }
} }

View file

@ -44,10 +44,13 @@ fn load_css() -> String {
}; };
format!( format!(
"window.breadbar {{ background-color: {bg_rgba}; }}\ "* {{ font-family: 'JetBrainsMono Nerd Font Mono', monospace; font-size: 12px; }}\
.workspace-btn {{ background: {surface}; color: {fg}; border-radius: 4px;\ window.breadbar {{ background-color: {bg_rgba}; border-radius: 0; }}\
border: none; min-width: 24px; padding: 0 8px; }}\ .workspace-btn {{ background: transparent; color: {fg}; opacity: 0.45;\
.workspace-btn:hover, .workspace-btn.active {{ background: {accent}; }}\ 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}; }}\ label {{ color: {fg}; }}\
window.breadbar-notification {{ background-color: alpha({bg_plain}, 0.95); }}\ window.breadbar-notification {{ background-color: alpha({bg_plain}, 0.95); }}\
.notification-card {{ background: {surface}; border-radius: 6px;\ .notification-card {{ background: {surface}; border-radius: 6px;\