diff --git a/src/clients/networkmanager.rs b/src/clients/networkmanager.rs index 6257780..62ab8e6 100644 --- a/src/clients/networkmanager.rs +++ b/src/clients/networkmanager.rs @@ -1,47 +1,80 @@ -use std::sync::Arc; +use std::ops::Deref; +use std::sync::{Arc, RwLock}; use color_eyre::Result; use futures_signals::signal::{Mutable, MutableSignalCloned}; use tracing::error; -use zbus::blocking::fdo::PropertiesProxy; use zbus::blocking::Connection; use zbus::{ dbus_proxy, - names::InterfaceName, zvariant::{ObjectPath, Str}, }; -use crate::{register_fallible_client, spawn_blocking}; +use crate::{ + read_lock, register_fallible_client, spawn_blocking, spawn_blocking_result, write_lock, +}; -const DBUS_BUS: &str = "org.freedesktop.NetworkManager"; -const DBUS_PATH: &str = "/org/freedesktop/NetworkManager"; -const DBUS_INTERFACE: &str = "org.freedesktop.NetworkManager"; +// States -#[derive(Debug)] -pub struct Client { - client_state: Mutable, - interface_name: InterfaceName<'static>, - dbus_connection: Connection, - props_proxy: PropertiesProxy<'static>, +#[derive(Clone, Debug)] +pub struct State { + pub wired: WiredState, + pub wifi: WifiState, + pub cellular: CellularState, + pub vpn: VpnState, } #[derive(Clone, Debug)] -pub enum ClientState { - WiredConnected, - WifiConnected, - CellularConnected, - VpnConnected, - WifiDisconnected, - Offline, +pub enum WiredState { + Connected, + Disconnected, + NotPresent, Unknown, } +#[derive(Clone, Debug)] +pub enum WifiState { + Connected(WifiConnectedState), + Disconnected, + Disabled, + NotPresent, + Unknown, +} + +#[derive(Clone, Debug)] +pub struct WifiConnectedState { + pub ssid: String, +} + +#[derive(Clone, Debug)] +pub enum CellularState { + Connected, + Disconnected, + Disabled, + NotPresent, + Unknown, +} + +#[derive(Clone, Debug)] +pub enum VpnState { + Connected(VpnConnectedState), + Disconnected, + Unknown, +} + +#[derive(Clone, Debug)] +pub struct VpnConnectedState { + pub name: String, +} + +// D-Bus interfaces + #[dbus_proxy( default_service = "org.freedesktop.NetworkManager", interface = "org.freedesktop.NetworkManager", default_path = "/org/freedesktop/NetworkManager" )] -trait NetworkManagerDbus { +trait Dbus { #[dbus_proxy(property)] fn active_connections(&self) -> Result>; @@ -61,73 +94,115 @@ trait NetworkManagerDbus { fn wireless_enabled(&self) -> Result; } -impl Client { - fn new() -> Result { - let client_state = Mutable::new(ClientState::Unknown); - let dbus_connection = Connection::system()?; - let interface_name = InterfaceName::from_static_str(DBUS_INTERFACE)?; - let props_proxy = PropertiesProxy::builder(&dbus_connection) - .destination(DBUS_BUS)? - .path(DBUS_PATH)? - .build()?; +#[dbus_proxy( + default_service = "org.freedesktop.NetworkManager", + interface = "org.freedesktop.NetworkManager.Connection.Active" +)] +trait ActiveConnectionDbus { + #[dbus_proxy(property)] + fn connection(&self) -> Result; - Ok(Self { - client_state, - interface_name, - dbus_connection, - props_proxy, - }) + #[dbus_proxy(property)] + fn devices(&self) -> Result>; + + #[dbus_proxy(property)] + fn id(&self) -> Result; + + #[dbus_proxy(property)] + fn specific_object(&self) -> Result; + + #[dbus_proxy(property)] + fn type_(&self) -> Result; + + #[dbus_proxy(property)] + fn uuid(&self) -> Result; +} + +// Ironbar client & helpers + +#[derive(Debug)] +pub struct Client(Arc>); + +#[derive(Debug)] +struct ClientInner<'a> { + state: Mutable, + + dbus_proxy: &'a DbusProxyBlocking<'a>, + + primary_connection: RwLock>, + primary_connection_type: RwLock>, + wireless_enabled: RwLock, +} + +impl Client { + fn new() -> Result { + let state = Mutable::new(State { + wired: WiredState::Unknown, + wifi: WifiState::Unknown, + cellular: CellularState::Unknown, + vpn: VpnState::Unknown, + }); + let dbus_connection = Connection::system()?; + let dbus_proxy = DbusProxyBlocking::new(&dbus_connection)?; + let primary_connection = dbus_proxy.primary_connection()?.to_owned(); + let primary_connection_type = dbus_proxy.primary_connection_type()?.to_owned(); + let wireless_enabled = dbus_proxy.wireless_enabled()?; + + Ok(Client(Arc::new(ClientInner { + state, + dbus_proxy: Box::leak(Box::new(dbus_proxy)), + primary_connection: RwLock::new(primary_connection), + primary_connection_type: RwLock::new(primary_connection_type), + wireless_enabled: RwLock::new(wireless_enabled), + }))) } fn run(&self) -> Result<()> { - let proxy = NetworkManagerDbusProxyBlocking::new(&self.dbus_connection)?; + macro_rules! spawn_property_watcher { + ($client:expr, $property:ident, $property_changes:ident) => { + let client = $client.clone(); + spawn_blocking_result!({ + while let Some(change) = client.dbus_proxy.$property_changes().next() { + { + let new_value = change.get()?; + let mut value_guard = write_lock!(client.$property); + *value_guard = new_value; + } + client.state.set(determine_state( + read_lock!(client.primary_connection).deref(), + read_lock!(client.primary_connection_type).deref(), + *read_lock!(client.wireless_enabled), + )); + } + Ok(()) + }); + }; + } - let mut primary_connection = proxy.primary_connection()?; - let mut primary_connection_type = proxy.primary_connection_type()?; - let mut wireless_enabled = proxy.wireless_enabled()?; - - self.client_state.set(determine_state( - &primary_connection, - &primary_connection_type, - wireless_enabled, + // Initial state + self.0.state.set(determine_state( + &read_lock!(self.0.primary_connection), + &read_lock!(self.0.primary_connection_type), + *read_lock!(self.0.wireless_enabled), )); - for change in self.props_proxy.receive_properties_changed()? { - let args = change.args()?; - if args.interface_name != self.interface_name { - continue; - } - - let changed_props = args.changed_properties; - let mut relevant_prop_changed = false; - - if changed_props.contains_key("PrimaryConnection") { - primary_connection = proxy.primary_connection()?; - relevant_prop_changed = true; - } - if changed_props.contains_key("PrimaryConnectionType") { - primary_connection_type = proxy.primary_connection_type()?; - relevant_prop_changed = true; - } - if changed_props.contains_key("WirelessEnabled") { - wireless_enabled = proxy.wireless_enabled()?; - relevant_prop_changed = true; - } - - if relevant_prop_changed { - self.client_state.set(determine_state( - &primary_connection, - &primary_connection_type, - wireless_enabled, - )); - } - } + spawn_property_watcher!( + self.0, + primary_connection, + receive_primary_connection_changed + ); + spawn_property_watcher!( + self.0, + primary_connection_type, + receive_primary_connection_type_changed + ); + spawn_property_watcher!(self.0, wireless_enabled, receive_wireless_enabled_changed); Ok(()) } - pub fn subscribe(&self) -> MutableSignalCloned { - self.client_state.signal_cloned() + pub fn subscribe(&self) -> MutableSignalCloned { + self.0.state.signal_cloned() } } @@ -135,35 +210,87 @@ pub fn create_client() -> Result> { let client = Arc::new(Client::new()?); { let client = client.clone(); - spawn_blocking(move || { - if let Err(error) = client.run() { - error!("{}", error); - }; + spawn_blocking_result!({ + client.run()?; + Ok(()) }); } Ok(client) } fn determine_state( - primary_connection: &str, - primary_connection_type: &str, + primary_connection: &ObjectPath, + primary_connection_type: &Str, wireless_enabled: bool, -) -> ClientState { +) -> State { if primary_connection == "/" { if wireless_enabled { - ClientState::WifiDisconnected + State { + wired: WiredState::Unknown, + wifi: WifiState::Disconnected, + cellular: CellularState::Unknown, + vpn: VpnState::Unknown, + } } else { - ClientState::Offline + State { + wired: WiredState::Unknown, + wifi: WifiState::Disabled, + cellular: CellularState::Unknown, + vpn: VpnState::Unknown, + } } } else { - match primary_connection_type { - "802-3-ethernet" | "adsl" | "pppoe" => ClientState::WiredConnected, - "802-11-olpc-mesh" | "802-11-wireless" | "wifi-p2p" => ClientState::WifiConnected, - "cdma" | "gsm" | "wimax" => ClientState::CellularConnected, - "vpn" | "wireguard" => ClientState::VpnConnected, - _ => ClientState::Unknown, + match primary_connection_type.as_str() { + "802-3-ethernet" | "adsl" | "pppoe" => State { + wired: WiredState::Connected, + wifi: WifiState::Unknown, + cellular: CellularState::Unknown, + vpn: VpnState::Unknown, + }, + "802-11-olpc-mesh" | "802-11-wireless" | "wifi-p2p" => State { + wired: WiredState::Unknown, + wifi: WifiState::Connected(WifiConnectedState { + ssid: String::new(), + }), + cellular: CellularState::Unknown, + vpn: VpnState::Unknown, + }, + "cdma" | "gsm" | "wimax" => State { + wired: WiredState::Unknown, + wifi: WifiState::Unknown, + cellular: CellularState::Connected, + vpn: VpnState::Unknown, + }, + "vpn" | "wireguard" => State { + wired: WiredState::Unknown, + wifi: WifiState::Unknown, + cellular: CellularState::Unknown, + vpn: VpnState::Connected(VpnConnectedState { + name: String::new(), + }), + }, + _ => State { + wired: WiredState::Unknown, + wifi: WifiState::Unknown, + cellular: CellularState::Unknown, + vpn: VpnState::Unknown, + }, } } } +// fn instantiate_active_connections<'a>( +// dbus_connection: &Connection, +// active_connection_paths: Vec, +// ) -> Result>> { +// let mut active_connections = Vec::new(); +// for active_connection_path in active_connection_paths { +// let active_connection_proxy = ActiveConnectionDbusProxyBlocking::builder(dbus_connection) +// .path(active_connection_path)? +// .build()?; +// active_connections.push(active_connection_proxy); +// } +// Ok(active_connections) +// } + register_fallible_client!(Client, networkmanager); diff --git a/src/macros.rs b/src/macros.rs index 2e92619..8182d35 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -222,3 +222,14 @@ macro_rules! rc_mut { std::rc::Rc::new(std::cell::RefCell::new($val)) }; } + +#[macro_export] +macro_rules! spawn_blocking_result { + ($body:block) => { + spawn_blocking(move || { + if let Err(error) = (|| -> Result<()> { $body })() { + error!("Error in fallible spawned closure: {}", error); + } + }); + }; +} diff --git a/src/modules/networkmanager.rs b/src/modules/networkmanager.rs index 1f9debd..f608110 100644 --- a/src/modules/networkmanager.rs +++ b/src/modules/networkmanager.rs @@ -1,12 +1,12 @@ use color_eyre::Result; use futures_lite::StreamExt; use futures_signals::signal::SignalExt; -use gtk::prelude::ContainerExt; +use gtk::prelude::{ContainerExt, WidgetExt}; use gtk::{Box as GtkBox, Image, Orientation}; use serde::Deserialize; use tokio::sync::mpsc::Receiver; -use crate::clients::networkmanager::{Client, ClientState}; +use crate::clients::networkmanager::{CellularState, Client, State, VpnState, WifiState, WiredState}; use crate::config::CommonConfig; use crate::gtk_helpers::IronbarGtkExt; use crate::image::ImageProvider; @@ -28,13 +28,13 @@ const fn default_icon_size() -> i32 { } impl Module for NetworkManagerModule { - type SendMessage = ClientState; + type SendMessage = State; type ReceiveMessage = (); fn spawn_controller( &self, _: &ModuleInfo, - context: &WidgetContext, + context: &WidgetContext, _: Receiver<()>, ) -> Result<()> { let client = context.try_client::()?; @@ -52,33 +52,69 @@ impl Module for NetworkManagerModule { fn into_widget( self, - context: WidgetContext, + context: WidgetContext, info: &ModuleInfo, ) -> Result> { let container = GtkBox::new(Orientation::Horizontal, 0); - let icon = Image::new(); - icon.add_class("icon"); - container.add(&icon); + + // Wired icon + let wired_icon = Image::new(); + wired_icon.add_class("wired-icon"); + container.add(&wired_icon); + + // Wifi icon + let wifi_icon = Image::new(); + wifi_icon.add_class("wifi-icon"); + container.add(&wifi_icon); + + // Cellular icon + let cellular_icon = Image::new(); + cellular_icon.add_class("cellular-icon"); + container.add(&cellular_icon); + + // VPN icon + let vpn_icon = Image::new(); + vpn_icon.add_class("vpn-icon"); + container.add(&vpn_icon); let icon_theme = info.icon_theme.clone(); + glib_recv!(context.subscribe(), state => { + macro_rules! update_icon { + ($icon_var:expr, $state_type:ident, {$($state:pat => $icon_name:expr,)+}) => { + let icon_name = match state.$state_type { + $($state => $icon_name,)+ + }; + if icon_name.is_empty() { + $icon_var.hide(); + } else { + ImageProvider::parse(icon_name, &icon_theme, false, self.icon_size) + .map(|provider| provider.load_into_image($icon_var.clone())); + $icon_var.show(); + } + }; + } - let initial_icon_name = "content-loading-symbolic"; - ImageProvider::parse(initial_icon_name, &icon_theme, false, self.icon_size) - .map(|provider| provider.load_into_image(icon.clone())); - - let widget_receiver = context.subscribe(); - glib_recv!(widget_receiver, state => { - let icon_name = match state { - ClientState::WiredConnected => "network-wired-symbolic", - ClientState::WifiConnected => "network-wireless-symbolic", - ClientState::CellularConnected => "network-cellular-symbolic", - ClientState::VpnConnected => "network-vpn-symbolic", - ClientState::WifiDisconnected => "network-wireless-acquiring-symbolic", - ClientState::Offline => "network-wireless-disabled-symbolic", - ClientState::Unknown => "dialog-question-symbolic", - }; - ImageProvider::parse(icon_name, &icon_theme, false, self.icon_size) - .map(|provider| provider.load_into_image(icon.clone())); + update_icon!(wired_icon, wired, { + WiredState::Connected => "icon:network-wired-symbolic", + WiredState::Disconnected => "icon:network-wired-disconnected-symbolic", + WiredState::NotPresent | WiredState::Unknown => "", + }); + update_icon!(wifi_icon, wifi, { + WifiState::Connected(_) => "icon:network-wireless-connected-symbolic", + WifiState::Disconnected => "icon:network-wireless-offline-symbolic", + WifiState::Disabled => "icon:network-wireless-hardware-disabled-symbolic", + WifiState::NotPresent | WifiState::Unknown => "", + }); + update_icon!(cellular_icon, cellular, { + CellularState::Connected => "icon:network-cellular-connected-symbolic", + CellularState::Disconnected => "icon:network-cellular-offline-symbolic", + CellularState::Disabled => "icon:network-cellular-hardware-disabled-symbolic", + CellularState::NotPresent | CellularState::Unknown => "", + }); + update_icon!(vpn_icon, vpn, { + VpnState::Connected(_) => "icon:network-vpn-symbolic", + VpnState::Disconnected | VpnState::Unknown => "", + }); }); Ok(ModuleParts::new(container, None))