From 41cc4d1db7aa35af44b66e5d446e1f91d4b6677f Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Wed, 17 Jan 2024 11:18:58 -0700 Subject: [PATCH] Implement network icon --- src/locker.rs | 41 +++++++++++----- src/main.rs | 3 ++ src/networkmanager.rs | 106 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 139 insertions(+), 11 deletions(-) create mode 100644 src/networkmanager.rs diff --git a/src/locker.rs b/src/locker.rs index 36cfd3d..2d2f6b2 100644 --- a/src/locker.rs +++ b/src/locker.rs @@ -194,6 +194,7 @@ pub enum Message { SessionLockEvent(SessionLockEvent), Channel(mpsc::Sender), BackgroundState(cosmic_bg_config::state::State), + NetworkIcon(Option<&'static str>), Prompt(String, bool, Option), Submit, Suspend, @@ -210,6 +211,7 @@ pub struct App { surface_images: HashMap, surface_names: HashMap, text_input_ids: HashMap, + network_icon_opt: Option<&'static str>, value_tx_opt: Option>, prompt_opt: Option<(String, bool, Option)>, error_opt: Option, @@ -302,6 +304,7 @@ impl cosmic::Application for App { surface_images: HashMap::new(), surface_names: HashMap::new(), text_input_ids: HashMap::new(), + network_icon_opt: None, value_tx_opt: None, prompt_opt: None, error_opt: None, @@ -397,6 +400,9 @@ impl cosmic::Application for App { self.surface_images.clear(); self.update_wallpapers(); } + Message::NetworkIcon(network_icon_opt) => { + self.network_icon_opt = network_icon_opt; + } Message::Prompt(prompt, secret, value_opt) => { let prompt_was_none = self.prompt_opt.is_none(); self.prompt_opt = Some((prompt, secret, value_opt)); @@ -489,16 +495,17 @@ impl cosmic::Application for App { column }; + let mut status_row = widget::row::with_capacity(2).padding(16.0).spacing(12.0); + + if let Some(network_icon) = self.network_icon_opt { + status_row = status_row.push(widget::icon::from_name(network_icon)); + } + //TODO: get actual status - let status_row = iced::widget::row![ - widget::icon::from_name("network-wireless-signal-ok-symbolic",), - iced::widget::row![ - widget::icon::from_name("battery-level-50-symbolic"), - widget::text("50%"), - ] - ] - .padding(16.0) - .spacing(12.0); + status_row = status_row.push(iced::widget::row![ + widget::icon::from_name("battery-level-50-symbolic"), + widget::text("50%"), + ]); //TODO: implement these buttons let button_row = iced::widget::row![ @@ -643,6 +650,17 @@ impl cosmic::Application for App { struct HeartbeatSubscription; struct PamSubscription; + //TODO: just use one vec for all subscriptions + let mut network_subscriptions = Vec::with_capacity(1); + + #[cfg(feature = "networkmanager")] + { + network_subscriptions.push( + crate::networkmanager::subscription() + .map(|network_icon_opt| Message::NetworkIcon(network_icon_opt)), + ); + } + //TODO: how to avoid cloning this on every time subscription is called? let username = self.flags.current_user.name.clone(); Subscription::batch([ @@ -707,17 +725,18 @@ impl cosmic::Application for App { break; } Err(err) => { - log::info!("authentication error: {:?}", err); + log::warn!("authentication error: {}", err); msg_tx.send(Message::Error(err.to_string())).await.unwrap(); } } } loop { - time::sleep(time::Duration::new(1, 0)).await; + time::sleep(time::Duration::new(60, 0)).await; } }, ), + Subscription::batch(network_subscriptions), ]) } } diff --git a/src/main.rs b/src/main.rs index 33df238..88cee55 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,6 +8,9 @@ mod locker; #[cfg(feature = "logind")] mod logind; +#[cfg(feature = "networkmanager")] +mod networkmanager; + fn main() -> Result<(), Box> { env_logger::init(); diff --git a/src/networkmanager.rs b/src/networkmanager.rs new file mode 100644 index 0000000..e19f638 --- /dev/null +++ b/src/networkmanager.rs @@ -0,0 +1,106 @@ +use cosmic::iced::{ + futures::{channel::mpsc, SinkExt}, + subscription, Subscription, +}; +use cosmic_dbus_networkmanager::{device::SpecificDevice, nm::NetworkManager}; +use std::{any::TypeId, cmp}; +use tokio::time; +use zbus::{Connection, Result}; + +#[derive(Clone, Copy, Debug)] +pub enum NetworkIcon { + None, + Wired, + Wireless(u8), +} + +impl NetworkIcon { + pub fn name(&self) -> &'static str { + match self { + NetworkIcon::None => "network-wired-disconnected-symbolic", + NetworkIcon::Wired => "network-wired-symbolic", + NetworkIcon::Wireless(strength) => { + if *strength < 25 { + "network-wireless-signal-weak-symbolic" + } else if *strength < 50 { + "network-wireless-signal-ok-symbolic" + } else if *strength < 75 { + "network-wireless-signal-good-symbolic" + } else { + "network-wireless-signal-excellent-symbolic" + } + } + } + } +} + +pub fn subscription() -> Subscription> { + struct NetworkSubscription; + + subscription::channel( + TypeId::of::(), + 16, + |mut msg_tx| async move { + match handler(&mut msg_tx).await { + Ok(()) => {} + Err(err) => { + log::warn!("networkmanager error: {}", err); + //TODO: send error + } + } + + // If reading network status failed, clear network icon + msg_tx.send(None).await.unwrap(); + + //TODO: should we retry on error? + loop { + time::sleep(time::Duration::new(60, 0)).await; + } + }, + ) +} + +//TODO: use never type? +pub async fn handler(msg_tx: &mut mpsc::Sender>) -> Result<()> { + let zbus = Connection::system().await?; + let nm = NetworkManager::new(&zbus).await?; + + //TOOD: use receive_active_connections_changed + loop { + let mut icon = NetworkIcon::None; + + for conn in nm.active_connections().await.unwrap_or_default() { + for dev in conn.devices().await.unwrap_or_default() { + match dev.downcast_to_device().await.unwrap_or_default() { + //TODO: more specific devices + Some(SpecificDevice::Wired(_)) => { + // Wired only overrides None + icon = match icon { + NetworkIcon::None => NetworkIcon::Wired, + other => other, + }; + } + Some(SpecificDevice::Wireless(wireless)) => { + if let Ok(ap) = wireless.active_access_point().await { + if let Ok(strength) = ap.strength().await { + // Wireless always overrides with the highest strength + icon = match icon { + NetworkIcon::Wireless(other_strength) => { + NetworkIcon::Wireless(cmp::max(strength, other_strength)) + } + _ => NetworkIcon::Wireless(strength), + }; + } + } + } + _ => {} + } + } + } + + msg_tx.send(Some(icon.name())).await.unwrap(); + + //TODO: select best timeout + time::sleep(time::Duration::new(5, 0)).await; + } +}