2025-08-10 16:37:55 +02:00
|
|
|
use crate::clients::networkmanager::Client;
|
2025-08-16 15:31:06 +02:00
|
|
|
use crate::clients::networkmanager::dbus::{DeviceState, DeviceType};
|
2025-08-14 18:14:31 +02:00
|
|
|
use crate::clients::networkmanager::event::Event;
|
2024-02-10 17:12:07 +01:00
|
|
|
use crate::config::CommonConfig;
|
|
|
|
use crate::gtk_helpers::IronbarGtkExt;
|
2025-08-16 15:31:06 +02:00
|
|
|
use crate::image::Provider;
|
|
|
|
use crate::modules::{Module, ModuleInfo, ModuleParts, WidgetContext};
|
2025-08-10 16:37:55 +02:00
|
|
|
use crate::{module_impl, spawn};
|
2025-08-16 15:31:06 +02:00
|
|
|
use color_eyre::{Result, eyre::Ok};
|
|
|
|
use glib::spawn_future_local;
|
|
|
|
use gtk::prelude::{ContainerExt, WidgetExt};
|
|
|
|
use gtk::{Image, Orientation};
|
|
|
|
use serde::Deserialize;
|
|
|
|
use std::collections::HashMap;
|
|
|
|
use tokio::sync::{broadcast, mpsc};
|
2024-02-10 17:12:07 +01:00
|
|
|
|
|
|
|
#[derive(Debug, Deserialize, Clone)]
|
|
|
|
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
|
|
|
pub struct NetworkManagerModule {
|
|
|
|
#[serde(default = "default_icon_size")]
|
|
|
|
icon_size: i32,
|
|
|
|
|
|
|
|
#[serde(flatten)]
|
|
|
|
pub common: Option<CommonConfig>,
|
|
|
|
}
|
|
|
|
|
|
|
|
const fn default_icon_size() -> i32 {
|
|
|
|
24
|
|
|
|
}
|
|
|
|
|
2025-08-16 15:31:06 +02:00
|
|
|
impl Module<gtk::Box> for NetworkManagerModule {
|
2025-08-14 18:14:31 +02:00
|
|
|
type SendMessage = Event;
|
2024-02-10 17:12:07 +01:00
|
|
|
type ReceiveMessage = ();
|
|
|
|
|
|
|
|
module_impl!("network_manager");
|
|
|
|
|
|
|
|
fn spawn_controller(
|
|
|
|
&self,
|
2025-08-10 16:37:55 +02:00
|
|
|
_info: &ModuleInfo,
|
2025-08-14 18:14:31 +02:00
|
|
|
context: &WidgetContext<Event, ()>,
|
2025-08-16 15:31:06 +02:00
|
|
|
_rx: mpsc::Receiver<()>,
|
2024-02-10 17:12:07 +01:00
|
|
|
) -> Result<()> {
|
|
|
|
let client = context.try_client::<Client>()?;
|
2025-08-16 15:31:06 +02:00
|
|
|
// Should we be using context.tx with ModuleUpdateEvent::Update instead?
|
|
|
|
let tx = context.update_tx.clone();
|
2025-08-17 20:57:40 +02:00
|
|
|
// Must be done here synchronously to avoid race condition
|
|
|
|
let mut client_rx = client.subscribe();
|
2025-08-16 15:31:06 +02:00
|
|
|
spawn(async move {
|
|
|
|
while let Result::Ok(event) = client_rx.recv().await {
|
|
|
|
tx.send(event)?;
|
|
|
|
}
|
2024-02-10 17:12:07 +01:00
|
|
|
|
2025-08-16 15:31:06 +02:00
|
|
|
Ok(())
|
|
|
|
});
|
2024-02-10 17:12:07 +01:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn into_widget(
|
|
|
|
self,
|
2025-08-14 18:14:31 +02:00
|
|
|
context: WidgetContext<Event, ()>,
|
2025-08-10 16:37:55 +02:00
|
|
|
_info: &ModuleInfo,
|
2025-08-16 15:31:06 +02:00
|
|
|
) -> Result<ModuleParts<gtk::Box>> {
|
|
|
|
let container = gtk::Box::new(Orientation::Horizontal, 0);
|
|
|
|
|
2025-08-17 20:57:40 +02:00
|
|
|
// Must be done here synchronously to avoid race condition
|
|
|
|
let rx = context.subscribe();
|
|
|
|
// We cannot use recv_glib_async here because the lifetimes don't work out
|
2025-08-16 15:31:06 +02:00
|
|
|
spawn_future_local(handle_update_events(
|
2025-08-17 20:57:40 +02:00
|
|
|
rx,
|
2025-08-16 15:31:06 +02:00
|
|
|
container.clone(),
|
|
|
|
self.icon_size,
|
|
|
|
context.ironbar.image_provider(),
|
|
|
|
));
|
2024-02-10 17:12:07 +01:00
|
|
|
|
|
|
|
Ok(ModuleParts::new(container, None))
|
|
|
|
}
|
|
|
|
}
|
2025-08-16 15:31:06 +02:00
|
|
|
|
|
|
|
async fn handle_update_events(
|
|
|
|
mut rx: broadcast::Receiver<Event>,
|
|
|
|
container: gtk::Box,
|
|
|
|
icon_size: i32,
|
|
|
|
image_provider: Provider,
|
|
|
|
) {
|
|
|
|
let mut icons = HashMap::<String, Image>::new();
|
|
|
|
|
|
|
|
while let Result::Ok(event) = rx.recv().await {
|
|
|
|
match event {
|
2025-08-17 20:57:40 +02:00
|
|
|
Event::DeviceAdded { interface, .. } => {
|
2025-08-16 15:31:06 +02:00
|
|
|
let icon = Image::new();
|
|
|
|
icon.add_class("icon");
|
|
|
|
container.add(&icon);
|
|
|
|
icons.insert(interface, icon);
|
|
|
|
}
|
|
|
|
Event::DeviceStateChanged {
|
|
|
|
interface,
|
|
|
|
r#type,
|
|
|
|
state,
|
|
|
|
} => {
|
|
|
|
let icon = icons
|
|
|
|
.get(&interface)
|
|
|
|
.expect("the icon for the interface to be present");
|
2025-08-17 21:15:10 +02:00
|
|
|
// TODO: Make this configurable at runtime
|
2025-08-16 15:31:06 +02:00
|
|
|
let icon_name = get_icon_for_device_state(&r#type, &state);
|
|
|
|
match icon_name {
|
|
|
|
Some(icon_name) => {
|
|
|
|
image_provider
|
|
|
|
.load_into_image_silent(icon_name, icon_size, false, icon)
|
|
|
|
.await;
|
|
|
|
icon.show();
|
|
|
|
}
|
|
|
|
None => {
|
|
|
|
icon.hide();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_icon_for_device_state(r#type: &DeviceType, state: &DeviceState) -> Option<&'static str> {
|
|
|
|
match r#type {
|
|
|
|
DeviceType::Ethernet => match state {
|
2025-08-20 18:49:20 +02:00
|
|
|
DeviceState::Unavailable
|
|
|
|
| DeviceState::Disconnected
|
|
|
|
| DeviceState::Prepare
|
|
|
|
| DeviceState::Config
|
|
|
|
| DeviceState::NeedAuth
|
|
|
|
| DeviceState::IpConfig
|
|
|
|
| DeviceState::IpCheck
|
|
|
|
| DeviceState::Secondaries
|
|
|
|
| DeviceState::Deactivating
|
|
|
|
| DeviceState::Failed => Some("icon:network-wired-disconnected-symbolic"),
|
2025-08-16 15:31:06 +02:00
|
|
|
DeviceState::Activated => Some("icon:network-wired-symbolic"),
|
|
|
|
_ => None,
|
|
|
|
},
|
|
|
|
DeviceType::Wifi => match state {
|
|
|
|
DeviceState::Unavailable => Some("icon:network-wireless-hardware-disabled-symbolic"),
|
2025-08-20 18:49:20 +02:00
|
|
|
DeviceState::Disconnected
|
|
|
|
| DeviceState::Prepare
|
|
|
|
| DeviceState::Config
|
|
|
|
| DeviceState::NeedAuth
|
|
|
|
| DeviceState::IpConfig
|
|
|
|
| DeviceState::IpCheck
|
|
|
|
| DeviceState::Secondaries
|
|
|
|
| DeviceState::Deactivating
|
|
|
|
| DeviceState::Failed => Some("icon:network-wireless-offline-symbolic"),
|
2025-08-16 15:31:06 +02:00
|
|
|
DeviceState::Activated => Some("icon:network-wireless-connected-symbolic"),
|
|
|
|
_ => None,
|
|
|
|
},
|
2025-08-20 18:49:20 +02:00
|
|
|
DeviceType::Tun | DeviceType::Wireguard => match state {
|
2025-08-16 15:31:06 +02:00
|
|
|
DeviceState::Activated => Some("icon:network-vpn-symbolic"),
|
|
|
|
_ => None,
|
|
|
|
},
|
2025-08-17 20:57:40 +02:00
|
|
|
_ => None,
|
2025-08-16 15:31:06 +02:00
|
|
|
}
|
|
|
|
}
|