diff --git a/src/bar.rs b/src/bar.rs index d15bd73..242e1a4 100644 --- a/src/bar.rs +++ b/src/bar.rs @@ -386,6 +386,7 @@ fn add_modules( ModuleConfig::Launcher(mut module) => add_module!(module, id), #[cfg(feature = "music")] ModuleConfig::Music(mut module) => add_module!(module, id), + ModuleConfig::Networkmanager(mut module) => add_module!(module, id), ModuleConfig::Script(mut module) => add_module!(module, id), #[cfg(feature = "sys_info")] ModuleConfig::SysInfo(mut module) => add_module!(module, id), diff --git a/src/config/mod.rs b/src/config/mod.rs index bb0d61f..34bd1b8 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -14,6 +14,7 @@ use crate::modules::label::LabelModule; use crate::modules::launcher::LauncherModule; #[cfg(feature = "music")] use crate::modules::music::MusicModule; +use crate::modules::networkmanager::NetworkmanagerModule; use crate::modules::script::ScriptModule; #[cfg(feature = "sys_info")] use crate::modules::sysinfo::SysInfoModule; @@ -45,6 +46,7 @@ pub enum ModuleConfig { Launcher(Box), #[cfg(feature = "music")] Music(Box), + Networkmanager(Box), Script(Box), #[cfg(feature = "sys_info")] SysInfo(Box), diff --git a/src/modules/mod.rs b/src/modules/mod.rs index a293d0f..b3b6762 100644 --- a/src/modules/mod.rs +++ b/src/modules/mod.rs @@ -35,6 +35,7 @@ pub mod label; pub mod launcher; #[cfg(feature = "music")] pub mod music; +pub mod networkmanager; pub mod script; #[cfg(feature = "sys_info")] pub mod sysinfo; diff --git a/src/modules/networkmanager.rs b/src/modules/networkmanager.rs new file mode 100644 index 0000000..ea026a1 --- /dev/null +++ b/src/modules/networkmanager.rs @@ -0,0 +1,142 @@ +use color_eyre::Result; +use futures_lite::StreamExt; +use gtk::prelude::*; +use gtk::{Image, Orientation}; +use serde::Deserialize; +use tokio::sync::mpsc::Receiver; +use zbus::fdo::PropertiesProxy; +use zbus::names::InterfaceName; +use zbus::zvariant::ObjectPath; + +use crate::config::CommonConfig; +use crate::image::ImageProvider; +use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext}; +use crate::{glib_recv, send_async, spawn}; + +// TODO: Add icon size option +#[derive(Debug, Deserialize, Clone)] +pub struct NetworkmanagerModule { + #[serde(flatten)] + pub common: Option, +} + +#[derive(Clone, Debug)] +pub enum NetworkmanagerState { + Wireless, + WirelessDisconnected, + Wired, + Offline, +} + +impl Module for NetworkmanagerModule { + type SendMessage = NetworkmanagerState; + type ReceiveMessage = (); + + fn name() -> &'static str { + "networkmanager" + } + + fn spawn_controller( + &self, + _: &ModuleInfo, + context: &WidgetContext, + _: Receiver<()>, + ) -> Result<()> { + let tx = context.tx.clone(); + + spawn(async move { + // TODO: Maybe move this into a `client` à la `upower`? + let nm_proxy = { + let dbus = zbus::Connection::system().await?; + PropertiesProxy::builder(&dbus) + .destination("org.freedesktop.NetworkManager")? + .path("/org/freedesktop/NetworkManager")? + .build() + .await? + }; + let device_interface_name = + InterfaceName::from_static_str("org.freedesktop.NetworkManager")?; + + let state = get_network_state(&nm_proxy, &device_interface_name).await?; + send_async!(tx, ModuleUpdateEvent::Update(state)); + + let mut prop_changed_stream = nm_proxy.receive_properties_changed().await?; + while let Some(signal) = prop_changed_stream.next().await { + let args = signal.args()?; + if args.interface_name != device_interface_name { + continue; + } + + let state = get_network_state(&nm_proxy, &device_interface_name).await?; + send_async!(tx, ModuleUpdateEvent::Update(state)); + } + + Result::<()>::Ok(()) + }); + + Ok(()) + } + + fn into_widget( + self, + context: WidgetContext, + info: &ModuleInfo, + ) -> Result> { + let container = gtk::Box::new(Orientation::Horizontal, 0); + let icon = Image::new(); + container.add(&icon); + + let icon_theme = info.icon_theme.clone(); + + let initial_icon_name = "icon:content-loading-symbolic"; + ImageProvider::parse(initial_icon_name, &icon_theme, false, 18) + .map(|provider| provider.load_into_image(icon.clone())); + + let rx = context.subscribe(); + glib_recv!(rx, state => { + let icon_name = match state { + NetworkmanagerState::Wireless => "network-wireless-symbolic", + NetworkmanagerState::WirelessDisconnected => "network-wireless-acquiring-symbolic", + NetworkmanagerState::Wired => "network-wired-symbolic", + NetworkmanagerState::Offline => "network-wireless-disabled-symbolic", + }; + ImageProvider::parse(icon_name, &icon_theme, false, 18) + .map(|provider| provider.load_into_image(icon.clone())); + }); + + Ok(ModuleParts::new(container, None)) + } +} + +async fn get_network_state( + nm_proxy: &PropertiesProxy<'_>, + device_interface_name: &InterfaceName<'_>, +) -> Result { + let properties = nm_proxy.get_all(device_interface_name.clone()).await?; + + let primary_connection_path = properties["PrimaryConnection"] + .downcast_ref::() + .unwrap(); + + if primary_connection_path != "/" { + let primary_connection_type = properties["PrimaryConnectionType"] + .downcast_ref::() + .unwrap() + .to_string(); + + match primary_connection_type.as_str() { + "802-11-wireless" => Ok(NetworkmanagerState::Wireless), + "802-3-ethernet" => Ok(NetworkmanagerState::Wired), + _ => panic!("Unknown primary connection type"), + } + } else { + let wireless_enabled = *properties["WirelessEnabled"] + .downcast_ref::() + .unwrap(); + if wireless_enabled { + Ok(NetworkmanagerState::WirelessDisconnected) + } else { + Ok(NetworkmanagerState::Offline) + } + } +}