diff --git a/Cargo.lock b/Cargo.lock index 76da554..78b84c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1010,6 +1010,8 @@ dependencies = [ "pwd", "ron", "rust-embed", + "serde", + "serde_json", "shlex", "tokio", "upower_dbus", @@ -1017,6 +1019,7 @@ dependencies = [ "xdg", "xkb-data", "zbus 4.4.0", + "zbus_systemd", ] [[package]] @@ -5921,6 +5924,17 @@ dependencies = [ "zvariant 4.2.0", ] +[[package]] +name = "zbus_systemd" +version = "0.25600.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14a94191447de6983b8c81f6d57d759501e03e59be7970ab0dc069ac9e36f786" +dependencies = [ + "futures", + "serde", + "zbus 4.4.0", +] + [[package]] name = "zeno" version = "0.2.3" diff --git a/Cargo.toml b/Cargo.toml index bfb12ba..ca71122 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,15 +41,19 @@ i18n-embed = { version = "0.14", features = [ i18n-embed-fl = "0.7" rust-embed = "8" futures-util = "0.3.30" +serde = { workspace = true, features = ["derive"] } +serde_json = "1" +zbus_systemd = { version = "0.25600", features = ["network1"], optional = true } [dependencies.greetd_ipc] version = "0.10.3" features = ["tokio-codec"] [features] -default = ["logind", "networkmanager", "upower"] +default = ["logind", "networkmanager", "systemd", "upower"] logind = ["logind-zbus", "zbus"] networkmanager = ["cosmic-dbus-networkmanager", "zbus"] +systemd = ["zbus_systemd", "zbus"] upower = ["upower_dbus", "zbus"] zbus = ["dep:zbus", "nix"] diff --git a/src/greeter.rs b/src/greeter.rs index 48f1922..e28573d 100644 --- a/src/greeter.rs +++ b/src/greeter.rs @@ -1408,16 +1408,17 @@ impl cosmic::Application for App { #[cfg(feature = "networkmanager")] { - subscriptions.push( - crate::networkmanager::subscription() - .map(|icon_opt| Message::NetworkIcon(icon_opt)), - ); + subscriptions.push(crate::networkmanager::subscription().map(Message::NetworkIcon)); + } + + #[cfg(feature = "systemd")] + { + subscriptions.push(crate::systemd::subscription().map(Message::NetworkIcon)); } #[cfg(feature = "upower")] { - subscriptions - .push(crate::upower::subscription().map(|info_opt| Message::PowerInfo(info_opt))); + subscriptions.push(crate::upower::subscription().map(Message::PowerInfo)); } Subscription::batch(subscriptions) diff --git a/src/lib.rs b/src/lib.rs index bbc4be1..ef86e70 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,5 +13,8 @@ mod logind; #[cfg(feature = "networkmanager")] mod networkmanager; +#[cfg(feature = "systemd")] +mod systemd; + #[cfg(feature = "upower")] mod upower; diff --git a/src/locker.rs b/src/locker.rs index e892d4e..35b5569 100644 --- a/src/locker.rs +++ b/src/locker.rs @@ -827,16 +827,17 @@ impl cosmic::Application for App { #[cfg(feature = "networkmanager")] { - subscriptions.push( - crate::networkmanager::subscription() - .map(|icon_opt| Message::NetworkIcon(icon_opt)), - ); + subscriptions.push(crate::networkmanager::subscription().map(Message::NetworkIcon)); + } + + #[cfg(feature = "systemd")] + { + subscriptions.push(crate::systemd::subscription().map(Message::NetworkIcon)); } #[cfg(feature = "upower")] { - subscriptions - .push(crate::upower::subscription().map(|info_opt| Message::PowerInfo(info_opt))); + subscriptions.push(crate::upower::subscription().map(Message::PowerInfo)); } Subscription::batch(subscriptions) diff --git a/src/logind.rs b/src/logind.rs index 912e10e..9979020 100644 --- a/src/logind.rs +++ b/src/logind.rs @@ -7,7 +7,6 @@ use logind_zbus::{ session::SessionProxy, }; use std::{any::TypeId, error::Error, os::fd::OwnedFd, process, sync::Arc}; -use tokio::time; use zbus::Connection; use crate::locker::Message; diff --git a/src/networkmanager.rs b/src/networkmanager.rs index bf1b672..c5b9128 100644 --- a/src/networkmanager.rs +++ b/src/networkmanager.rs @@ -7,7 +7,7 @@ use std::{any::TypeId, cmp}; use tokio::time; use zbus::{Connection, Result}; -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] pub enum NetworkIcon { None, Wired, diff --git a/src/systemd.rs b/src/systemd.rs new file mode 100644 index 0000000..7dd2a6d --- /dev/null +++ b/src/systemd.rs @@ -0,0 +1,114 @@ +use std::any::TypeId; + +use cosmic::iced::{ + futures::{channel::mpsc, SinkExt, StreamExt}, + subscription, Subscription, +}; +use serde::Deserialize; +use tokio::time; +use zbus::{Connection, Result}; +use zbus_systemd::network1::ManagerProxy; + +use crate::networkmanager::NetworkIcon; + +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!("systemd-networkd 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 mp = ManagerProxy::new(&zbus).await?; + + let mut online_state_changed = mp.receive_online_state_changed().await; + loop { + match mp.online_state().await.unwrap_or_default().as_str() { + "online" | "partial" => { + // "partial" mean some links are online, let's assume this is good enough + // see: https://www.freedesktop.org/software/systemd/man/latest/networkctl.html + } + _ => { + continue; + } + }; + + let mut icon = NetworkIcon::None; + + for (link_id, _, _) in mp.list_links().await.unwrap_or_default() { + let link_json = mp.describe_link(link_id).await.unwrap_or_default(); + if let Ok(link) = serde_json::from_str::(&link_json) { + if link.online_state != OnlineState::Online { + continue; + } + // Wired only overrides None + if icon == NetworkIcon::None && link.r#type == LinkType::Ether { + icon = NetworkIcon::Wired; + } + // Wireless always overrides with the highest strength + if link.r#type == LinkType::Wlan { + icon = NetworkIcon::Wireless(100); + // TODO: determine wireless signal strength + } + } + } + + msg_tx.send(Some(icon.name())).await.unwrap(); + + // Waits until active connections have changed and at least one second has passed + tokio::join!( + online_state_changed.next(), + time::sleep(time::Duration::from_secs(3)) + ); + } +} + +#[derive(Debug, Deserialize, PartialEq)] +#[serde(rename_all = "PascalCase")] +struct Link { + online_state: OnlineState, + r#type: LinkType, +} + +#[derive(Debug, Deserialize, PartialEq)] +#[serde(rename_all = "lowercase")] +#[non_exhaustive] +enum LinkType { + Ether, + Loopback, + Wlan, + Other(String), +} + +// see: https://www.freedesktop.org/software/systemd/man/latest/networkctl.html +#[derive(Debug, Deserialize, PartialEq)] +#[serde(rename_all = "lowercase")] +#[non_exhaustive] +enum OnlineState { + Partial, + Offline, + Online, + Unknown, + Other(String), +}