From 61f7020fd353f0d9c0ce2feb1c594489fbb1ea43 Mon Sep 17 00:00:00 2001 From: Candifloss Date: Sun, 23 Nov 2025 12:14:40 +0530 Subject: [PATCH] Add: Wifi widget - Use `nmcli` --- src/widgets/mod.rs | 4 +- src/widgets/wifiwidget.rs | 104 ++++++++++++++++++++++++++++++++++++++ ui/topbar.slint | 23 ++++++++- 3 files changed, 129 insertions(+), 2 deletions(-) create mode 100644 src/widgets/wifiwidget.rs diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index 0f4a8ba..c0f0dd2 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -4,6 +4,7 @@ mod common; mod datewidget; mod timewidget; mod volumewidget; +mod wifiwidget; use crate::TopBar; @@ -13,7 +14,8 @@ pub fn install_callbacks(ui: &TopBar) { batterywidget::install(ui); volumewidget::install(ui); brightnesswidget::install(ui); + wifiwidget::install(ui); // In the future: - // networkwidget::install(ui); + // weatherwidget::install(ui); } diff --git a/src/widgets/wifiwidget.rs b/src/widgets/wifiwidget.rs new file mode 100644 index 0000000..c023ece --- /dev/null +++ b/src/widgets/wifiwidget.rs @@ -0,0 +1,104 @@ +// Wi-Fi widget backend. Polls NetworkManager every second using `nmcli`. +use super::common::run_cmd; +use crate::TopBar; +use slint::{ComponentHandle, SharedString, Timer, TimerMode}; +use std::process::Command; +use std::time::Duration; + +/// Run a shell command and capture stdout as a string +fn run_cmd_output(cmd: &str, args: &[&str]) -> Option { + let out = Command::new(cmd).args(args).output().ok()?; + if !out.status.success() { + return None; + } + Some(String::from_utf8_lossy(&out.stdout).trim().to_string()) +} + +/// Return (connected: bool, ssid: String, signal: u8) +fn read_wifi_status() -> Option<(bool, String, u8)> { + // Fields: ACTIVE:SSID:SIGNAL + let out = run_cmd_output("nmcli", &["-t", "-f", "ACTIVE,SSID,SIGNAL", "dev", "wifi"])?; + for line in out.lines() { + let mut parts = line.split(':'); + let active = parts.next()?.trim(); + let ssid = parts.next().unwrap_or("").trim().to_string(); + let signal = parts.next().unwrap_or("0").trim().parse().unwrap_or(0); + + if active == "yes" { + return Some((true, ssid, signal)); + } + } + Some((false, String::new(), 0)) +} + +/// True if Wi-Fi hardware is enabled +fn wifi_enabled() -> bool { + // nmcli -t -f WIFI g → "enabled" or "disabled" + if let Some(out) = run_cmd_output("nmcli", &["-t", "-f", "WIFI", "g"]) { + return out.trim() == "enabled"; + } + false +} + +/// Choose icon based on Wi-Fi signal +fn wifi_icon(signal: u8, connected: bool, enabled: bool) -> SharedString { + let icon = if !enabled { + "󰤫" // wifi off + } else if !connected { + "󰤪" // wifi on, no connection + } else { + match signal { + 0..=20 => "󰤟", + 21..=40 => "󰤢", + 41..=60 => "󰤥", + 61..=100 => "󰤨", + _ => "󰤯", // error/unexpected + } + }; + SharedString::from(icon) +} + +/// Tooltip shown on hover +fn wifi_tooltip(connected: bool, ssid: &str, signal: u8, enabled: bool) -> String { + if !enabled { + "Wi-Fi: disabled".into() + } else if !connected { + "Wi-Fi: no internet".into() + } else { + format!("Wi-Fi: {ssid} ({signal}%)") + } +} + +/// Periodic Wi-Fi updater +fn start_wifi_updater(ui: &TopBar) { + let weak = ui.as_weak(); + + let timer = Box::leak(Box::new(Timer::default())); + + // Update every second (connection quality can change quickly) + timer.start(TimerMode::Repeated, Duration::from_secs(1), move || { + if let Some(ui) = weak.upgrade() { + let enabled = wifi_enabled(); + let (connected, ssid, signal) = read_wifi_status().unwrap_or((false, String::new(), 0)); + + let icon = wifi_icon(signal, connected, enabled); + let tooltip = wifi_tooltip(connected, &ssid, signal, enabled); + + ui.set_wifi_icon(icon); + ui.set_wifi_tooltip(tooltip.into()); + } + }); +} + +/// Install Wi-Fi click action + updater +pub fn install(ui: &TopBar) { + let weak = ui.as_weak(); + + ui.on_show_wifi(move || { + if weak.upgrade().is_some() { + run_cmd("xclock"); // Fix later + } + }); + + start_wifi_updater(ui); +} diff --git a/ui/topbar.slint b/ui/topbar.slint index 8cee5c4..7c2b325 100644 --- a/ui/topbar.slint +++ b/ui/topbar.slint @@ -36,7 +36,7 @@ export component TopBar inherits Window { in property volume_bg_clicked;*/ callback show_volume(); - // brightness widget + // Brightness widget in property brightness_tooltip; in property brightness_icon; /*in property brightness_icon_color; @@ -44,6 +44,15 @@ export component TopBar inherits Window { in property brightness_bg_hover; in property brightness_bg_clicked;*/ callback show_brightness(); + + // Wifi widget + in property wifi_tooltip; + in property wifi_icon; + /*in property wifi_icon_color: #ffffff; + in property wifi_bg_normal: #8e9162; + in property wifi_bg_hover: #6d8a4d; + in property wifi_bg_clicked: #4a5f70;*/ + callback show_wifi(); title: "chocobar"; width: bar_width *1px; @@ -88,6 +97,18 @@ export component TopBar inherits Window { HorizontalLayout { alignment: end; // Right-align + + // SquareIconWidget - Wifi + SquareIconWidget { + bar_height: root.bar_height; + icon_text: root.wifi_icon; + tooltip_text: root.wifi_tooltip; + /*icon_color: root.wifi_icon_color; + bg_normal: root.wifi_bg_normal; + bg_hover: root.wifi_bg_hover; + bg_clicked: root.wifi_bg_clicked;*/ + square_btn_callback => root.show_wifi(); + } // SquareIconWidget - brightness SquareIconWidget {