From 4fe03031ddc1225c33c6c7eeb10ee7e208dfb879 Mon Sep 17 00:00:00 2001 From: Rodrigo Batista de Moraes Date: Sat, 13 Jul 2024 19:05:22 -0300 Subject: [PATCH] feat(networkmanager): implement WiFi strenght levels Also collect SSID information. Althought nothing is being done with it for now. This was done by interfacing with `NetworkManager.Device.Wifi` and `NetworkManager.AccessPoint` dbus interfaces. --- src/clients/networkmanager/dbus.rs | 22 +++++++++++++ src/clients/networkmanager/mod.rs | 7 ++-- src/clients/networkmanager/state.rs | 31 ++++++++++++++---- src/modules/networkmanager.rs | 50 ++++++++++++++++++++++++++++- 4 files changed, 100 insertions(+), 10 deletions(-) diff --git a/src/clients/networkmanager/dbus.rs b/src/clients/networkmanager/dbus.rs index 5ab3889..25c289a 100644 --- a/src/clients/networkmanager/dbus.rs +++ b/src/clients/networkmanager/dbus.rs @@ -69,6 +69,28 @@ trait DeviceDbus { fn state(&self) -> Result; } +#[dbus_proxy( + default_service = "org.freedesktop.NetworkManager", + interface = "org.freedesktop.NetworkManager.Device.Wireless" +)] +trait DeviceWirelessDbus { + #[dbus_proxy(property)] + fn active_access_point(&self) -> zbus::Result; +} + +// based on code generated by `zbus-xmlgen system org.freedesktop.NetworkManager /org/freedesktop/NetworkManager/AccessPoint/1` +#[dbus_proxy( + default_service = "org.freedesktop.NetworkManager", + interface = "org.freedesktop.NetworkManager.AccessPoint" +)] +trait AccessPointDbus { + #[dbus_proxy(property)] + fn ssid(&self) -> zbus::Result>; + + #[dbus_proxy(property)] + fn strength(&self) -> Result; +} + #[derive(Clone, Debug, OwnedValue, PartialEq)] #[repr(u32)] pub(super) enum DeviceType { diff --git a/src/clients/networkmanager/mod.rs b/src/clients/networkmanager/mod.rs index b0ac6a2..57bed1b 100644 --- a/src/clients/networkmanager/mod.rs +++ b/src/clients/networkmanager/mod.rs @@ -64,7 +64,10 @@ impl Client { ($client:ident) => { $client.state.set(State { wired: determine_wired_state(&read_lock!($client.devices))?, - wifi: determine_wifi_state(&read_lock!($client.devices))?, + wifi: determine_wifi_state( + &$client.dbus_connection, + &read_lock!($client.devices), + )?, cellular: determine_cellular_state(&read_lock!($client.devices))?, vpn: $client.state.get_cloned().vpn, }); @@ -178,7 +181,7 @@ impl Client { }); self.0.state.set(State { wired: determine_wired_state(&read_lock!(self.0.devices))?, - wifi: determine_wifi_state(&read_lock!(self.0.devices))?, + wifi: determine_wifi_state(&self.0.dbus_connection, &read_lock!(self.0.devices))?, cellular: determine_cellular_state(&read_lock!(self.0.devices))?, vpn: determine_vpn_state(&read_lock!(self.0.active_connections))?, }); diff --git a/src/clients/networkmanager/state.rs b/src/clients/networkmanager/state.rs index e5e226e..8bafead 100644 --- a/src/clients/networkmanager/state.rs +++ b/src/clients/networkmanager/state.rs @@ -1,7 +1,8 @@ use color_eyre::Result; use crate::clients::networkmanager::dbus::{ - ActiveConnectionDbusProxyBlocking, DeviceDbusProxyBlocking, DeviceState, DeviceType, + AccessPointDbusProxyBlocking, ActiveConnectionDbusProxyBlocking, DeviceDbusProxyBlocking, + DeviceState, DeviceType, DeviceWirelessDbusProxyBlocking, }; use crate::clients::networkmanager::PathMap; @@ -33,6 +34,8 @@ pub enum WifiState { #[derive(Clone, Debug)] pub struct WifiConnectedState { pub ssid: String, + /// Strength in percentage, from 0 to 100. + pub strength: u8, } #[derive(Clone, Debug)] @@ -82,29 +85,43 @@ pub(super) fn determine_wired_state( } pub(super) fn determine_wifi_state( + dbus_connection: &zbus::blocking::Connection, devices: &PathMap, ) -> Result { let mut present = false; let mut enabled = false; - let mut connected = false; + let mut connected = None; for device in devices.values() { if device.device_type()? == DeviceType::Wifi { present = true; if device.state()?.is_enabled() { enabled = true; - if device.state()? == DeviceState::Activated { - connected = true; + + let wireless_device = DeviceWirelessDbusProxyBlocking::builder(dbus_connection) + .path(device.path().clone())? + .build()?; + let primary_access_point_path = wireless_device.active_access_point()?; + if primary_access_point_path.as_str() != "/" { + connected = Some( + AccessPointDbusProxyBlocking::builder(dbus_connection) + .path(primary_access_point_path)? + .build()?, + ); break; } } } } - if connected { + if let Some(access_point) = connected { + let ssid = access_point + .ssid() + .map(|x| String::from_utf8_lossy(&x).to_string()) + .unwrap_or_else(|_| "unkown".into()); Ok(WifiState::Connected(WifiConnectedState { - // TODO: Implement obtaining SSID - ssid: "unknown".into(), + ssid, + strength: access_point.strength().unwrap_or(0), })) } else if enabled { Ok(WifiState::Disconnected) diff --git a/src/modules/networkmanager.rs b/src/modules/networkmanager.rs index c53f57a..e5be89e 100644 --- a/src/modules/networkmanager.rs +++ b/src/modules/networkmanager.rs @@ -111,7 +111,17 @@ impl Module for NetworkManagerModule { WiredState::NotPresent | WiredState::Unknown => "", }); update_icon!(wifi_icon, wifi, { - WifiState::Connected(_) => "icon:network-wireless-connected-symbolic", + WifiState::Connected(state) => { + let icons = [ + "icon:network-wireless-signal-none-symbolic", + "icon:network-wireless-signal-weak-symbolic", + "icon:network-wireless-signal-ok-symbolic", + "icon:network-wireless-signal-good-symbolic", + "icon:network-wireless-signal-excellent-symbolic", + ]; + let n = strengh_to_level(state.strength, icons.len()); + icons[n] + }, WifiState::Disconnected => "icon:network-wireless-offline-symbolic", WifiState::Disabled => "icon:network-wireless-hardware-disabled-symbolic", WifiState::NotPresent | WifiState::Unknown => "", @@ -133,3 +143,41 @@ impl Module for NetworkManagerModule { module_impl!("networkmanager"); } + +/// Convert strength level (from 0-100), to a level (from 0 to `number_of_levels-1`). +const fn strengh_to_level(strength: u8, number_of_levels: usize) -> usize { + // Strength levels based for the one show by [`nmcli dev wifi list`](https://github.com/NetworkManager/NetworkManager/blob/83a259597000a88217f3ccbdfe71c8114242e7a6/src/libnmc-base/nm-client-utils.c#L700-L727): + // match strength { + // 0..=4 => 0, + // 5..=29 => 1, + // 30..=54 => 2, + // 55..=79 => 3, + // 80.. => 4, + // } + + // to make it work with a custom number of levels, we approach the logic above with the logic + // below (0 for < 5, and a linear interpolation for 5 to 105). + // TODO: if there are more than 20 levels, the last level will be out of scale, and never be + // reach. + if strength < 5 { + return 0; + } + (strength as usize - 5) * (number_of_levels - 1) / 100 + 1 +} + +// Just to make sure my implementation still follow the original logic +#[cfg(test)] +#[test] +fn test_strength_to_level() { + assert_eq!(strengh_to_level(0, 5), 0); + assert_eq!(strengh_to_level(4, 5), 0); + assert_eq!(strengh_to_level(5, 5), 1); + assert_eq!(strengh_to_level(6, 5), 1); + assert_eq!(strengh_to_level(29, 5), 1); + assert_eq!(strengh_to_level(30, 5), 2); + assert_eq!(strengh_to_level(54, 5), 2); + assert_eq!(strengh_to_level(55, 5), 3); + assert_eq!(strengh_to_level(79, 5), 3); + assert_eq!(strengh_to_level(80, 5), 4); + assert_eq!(strengh_to_level(100, 5), 4); +}