1
0
Fork 0
mirror of https://github.com/Zedfrigg/ironbar.git synced 2026-01-08 12:16:44 +01:00

Compare commits

..

7 commits

8 changed files with 299 additions and 347 deletions

76
Cargo.lock generated
View file

@ -736,6 +736,12 @@ dependencies = [
"windows-sys 0.60.2",
]
[[package]]
name = "discard"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0"
[[package]]
name = "displaydoc"
version = "0.2.5"
@ -1012,6 +1018,22 @@ dependencies = [
"syn 2.0.99",
]
[[package]]
name = "futures-signals"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70abe9c40a0dccd69bf7c59ba58714ebeb6c15a88143a10c6be7130e895f1696"
dependencies = [
"discard",
"futures-channel",
"futures-core",
"futures-util",
"gensym",
"log",
"pin-project",
"serde",
]
[[package]]
name = "futures-sink"
version = "0.3.31"
@ -1123,6 +1145,18 @@ dependencies = [
"version_check",
]
[[package]]
name = "gensym"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "913dce4c5f06c2ea40fc178c06f777ac89fc6b1383e90c254fafb1abe4ba3c82"
dependencies = [
"proc-macro2",
"quote 1.0.39",
"syn 2.0.99",
"uuid",
]
[[package]]
name = "getrandom"
version = "0.2.15"
@ -1811,6 +1845,7 @@ dependencies = [
"dirs",
"evdev-rs",
"futures-lite",
"futures-signals",
"glib",
"gtk",
"gtk-layer-shell",
@ -1834,7 +1869,6 @@ dependencies = [
"sysinfo",
"system-tray",
"tokio",
"tokio-stream",
"tracing",
"tracing-appender",
"tracing-error",
@ -2507,6 +2541,26 @@ dependencies = [
"siphasher",
]
[[package]]
name = "pin-project"
version = "1.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "1.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
dependencies = [
"proc-macro2",
"quote 1.0.39",
"syn 2.0.99",
]
[[package]]
name = "pin-project-lite"
version = "0.2.16"
@ -3503,17 +3557,6 @@ dependencies = [
"tokio",
]
[[package]]
name = "tokio-stream"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047"
dependencies = [
"futures-core",
"pin-project-lite",
"tokio",
]
[[package]]
name = "tokio-util"
version = "0.7.13"
@ -3822,6 +3865,15 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uuid"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0f540e3240398cce6128b64ba83fdbdd86129c16a3aa1a3a252efd66eb3d587"
dependencies = [
"getrandom 0.3.1",
]
[[package]]
name = "valuable"
version = "0.1.1"

View file

@ -84,7 +84,7 @@ music = ["dep:regex"]
"music+mpris" = ["music", "mpris"]
"music+mpd" = ["music", "mpd-utils"]
network_manager = ["futures-lite", "tokio-stream", "zbus"]
network_manager = ["futures-lite", "futures-signals", "zbus"]
notifications = ["zbus"]
@ -171,7 +171,7 @@ regex = { version = "1.11.1", default-features = false, features = [
], optional = true }
# network_manager
tokio-stream = { version = "0.1.17", optional = true }
futures-signals = { version = "0.3.34", optional = true }
# sys_info
sysinfo = { version = "0.36.1", optional = true }

View file

@ -190,7 +190,7 @@ impl Clients {
if let Some(client) = &self.network_manager {
Ok(client.clone())
} else {
let client = networkmanager::create_client()?;
let client = await_sync(async move { networkmanager::create_client().await })?;
self.network_manager = Some(client.clone());
Ok(client)
}

View file

@ -0,0 +1,171 @@
use std::sync::Arc;
use crate::{register_fallible_client, spawn};
use color_eyre::Result;
use futures_signals::signal::{Mutable, MutableSignalCloned};
use tracing::error;
use zbus::export::ordered_stream::OrderedStreamExt;
use zbus::fdo::PropertiesProxy;
use zbus::{
Connection,
names::InterfaceName,
proxy,
zvariant::{ObjectPath, Str},
};
const DBUS_BUS: &str = "org.freedesktop.NetworkManager";
const DBUS_PATH: &str = "/org/freedesktop/NetworkManager";
const DBUS_INTERFACE: &str = "org.freedesktop.NetworkManager";
#[derive(Debug)]
pub struct Client {
client_state: Mutable<ClientState>,
interface_name: InterfaceName<'static>,
dbus_connection: Connection,
props_proxy: PropertiesProxy<'static>,
}
#[derive(Clone, Debug)]
pub enum ClientState {
WiredConnected,
WifiConnected,
CellularConnected,
VpnConnected,
WifiDisconnected,
Offline,
Unknown,
}
#[proxy(
default_service = "org.freedesktop.NetworkManager",
interface = "org.freedesktop.NetworkManager",
default_path = "/org/freedesktop/NetworkManager"
)]
trait NetworkManagerDbus {
#[zbus(property)]
fn active_connections(&self) -> Result<Vec<ObjectPath>>;
#[zbus(property)]
fn devices(&self) -> Result<Vec<ObjectPath>>;
#[zbus(property)]
fn networking_enabled(&self) -> Result<bool>;
#[zbus(property)]
fn primary_connection(&self) -> Result<ObjectPath>;
#[zbus(property)]
fn primary_connection_type(&self) -> Result<Str>;
#[zbus(property)]
fn wireless_enabled(&self) -> Result<bool>;
}
impl Client {
async fn new() -> Result<Self> {
let client_state = Mutable::new(ClientState::Unknown);
let dbus_connection = Connection::system().await?;
let interface_name = InterfaceName::from_static_str(DBUS_INTERFACE)?;
let props_proxy = PropertiesProxy::builder(&dbus_connection)
.destination(DBUS_BUS)?
.path(DBUS_PATH)?
.build()
.await?;
Ok(Self {
client_state,
interface_name,
dbus_connection,
props_proxy,
})
}
async fn run(&self) -> Result<()> {
let proxy = NetworkManagerDbusProxy::new(&self.dbus_connection).await?;
let mut primary_connection = proxy.primary_connection().await?;
let mut primary_connection_type = proxy.primary_connection_type().await?;
let mut wireless_enabled = proxy.wireless_enabled().await?;
self.client_state.set(determine_state(
&primary_connection,
&primary_connection_type,
wireless_enabled,
));
let mut stream = self.props_proxy.receive_properties_changed().await?;
while let Some(change) = stream.next().await {
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().await?;
relevant_prop_changed = true;
}
if changed_props.contains_key("PrimaryConnectionType") {
primary_connection_type = proxy.primary_connection_type().await?;
relevant_prop_changed = true;
}
if changed_props.contains_key("WirelessEnabled") {
wireless_enabled = proxy.wireless_enabled().await?;
relevant_prop_changed = true;
}
if relevant_prop_changed {
self.client_state.set(determine_state(
&primary_connection,
&primary_connection_type,
wireless_enabled,
));
}
}
Ok(())
}
pub fn subscribe(&self) -> MutableSignalCloned<ClientState> {
self.client_state.signal_cloned()
}
}
pub async fn create_client() -> Result<Arc<Client>> {
let client = Arc::new(Client::new().await?);
{
let client = client.clone();
spawn(async move {
if let Err(error) = client.run().await {
error!("{}", error);
}
});
}
Ok(client)
}
fn determine_state(
primary_connection: &str,
primary_connection_type: &str,
wireless_enabled: bool,
) -> ClientState {
if primary_connection == "/" {
if wireless_enabled {
ClientState::WifiDisconnected
} else {
ClientState::Offline
}
} 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,
}
}
}
register_fallible_client!(Client, network_manager);

View file

@ -1,84 +0,0 @@
use color_eyre::Result;
use zbus::proxy;
use zbus::zvariant::{ObjectPath, OwnedValue, Str};
#[proxy(
default_service = "org.freedesktop.NetworkManager",
interface = "org.freedesktop.NetworkManager",
default_path = "/org/freedesktop/NetworkManager"
)]
pub(super) trait Dbus {
#[zbus(property)]
fn all_devices(&self) -> Result<Vec<ObjectPath<'_>>>;
}
#[proxy(
default_service = "org.freedesktop.NetworkManager",
interface = "org.freedesktop.NetworkManager.Device"
)]
pub(super) trait DeviceDbus {
#[zbus(property)]
fn device_type(&self) -> Result<DeviceType>;
#[zbus(property)]
fn interface(&self) -> Result<Str<'_>>;
#[zbus(property)]
fn state(&self) -> Result<DeviceState>;
}
// For reference: https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/blob/e1a7d5ac062f4f23ce3a6b33c62e856056161ad8/src/libnm-core-public/nm-dbus-interface.h#L212-L253
#[derive(Clone, Debug, Eq, Hash, OwnedValue, PartialEq)]
#[repr(u32)]
pub enum DeviceType {
Unknown = 0,
Ethernet = 1,
Wifi = 2,
Bluetooth = 5,
OlpcMesh = 6,
Wimax = 7,
Modem = 8,
Infiniband = 9,
Bond = 10,
Vlan = 11,
Adsl = 12,
Bridge = 13,
Team = 15,
Tun = 16,
IpTunnel = 17,
Macvlan = 18,
Vxlan = 19,
Veth = 20,
Macsec = 21,
Dummy = 22,
Ppp = 23,
OvsInterface = 24,
OvsPort = 25,
OvsBridge = 26,
Wpan = 27,
Lowpan = 28,
Wireguard = 29,
WifiP2p = 30,
Vrf = 31,
Loopback = 32,
Hsr = 33,
}
// For reference: https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/blob/e1a7d5ac062f4f23ce3a6b33c62e856056161ad8/src/libnm-core-public/nm-dbus-interface.h#L501-L538
#[derive(Clone, Debug, OwnedValue, PartialEq)]
#[repr(u32)]
pub enum DeviceState {
Unknown = 0,
Unmanaged = 10,
Unavailable = 20,
Disconnected = 30,
Prepare = 40,
Config = 50,
NeedAuth = 60,
IpConfig = 70,
IpCheck = 80,
Secondaries = 90,
Activated = 100,
Deactivating = 110,
Failed = 120,
}

View file

@ -1,13 +0,0 @@
use crate::clients::networkmanager::dbus::{DeviceState, DeviceType};
#[derive(Debug, Clone)]
pub enum Event {
DeviceAdded {
interface: String,
},
DeviceStateChanged {
interface: String,
r#type: DeviceType,
state: DeviceState,
},
}

View file

@ -1,118 +0,0 @@
use color_eyre::Result;
use color_eyre::eyre::Ok;
use futures_lite::StreamExt;
use std::collections::HashSet;
use std::sync::Arc;
use tokio::sync::broadcast;
use zbus::Connection;
use zbus::zvariant::{ObjectPath, Str};
use crate::clients::ClientResult;
use crate::clients::networkmanager::dbus::{DbusProxy, DeviceDbusProxy};
use crate::clients::networkmanager::event::Event;
use crate::{register_fallible_client, spawn};
pub mod dbus;
pub mod event;
#[derive(Debug)]
pub struct Client {
tx: broadcast::Sender<Event>,
}
impl Client {
fn new() -> Result<Client> {
let (tx, _) = broadcast::channel(64);
Ok(Client { tx })
}
fn run(&self) -> Result<()> {
let tx = self.tx.clone();
spawn(async move {
let dbus_connection = Connection::system().await?;
let root = DbusProxy::new(&dbus_connection).await?;
let mut devices = HashSet::new();
let mut devices_changes = root.receive_all_devices_changed().await;
while let Some(devices_change) = devices_changes.next().await {
// The new list of devices from dbus, not to be confused with the added devices below
let new_devices = HashSet::from_iter(devices_change.get().await?);
let added_devices = new_devices.difference(&devices);
for added_device in added_devices {
spawn(watch_device(added_device.to_owned(), tx.clone()));
}
let _removed_devices = devices.difference(&new_devices);
// TODO: Cook up some way to notify closures for removed devices to exit
devices = new_devices;
}
Ok(())
});
Ok(())
}
pub fn subscribe(&self) -> broadcast::Receiver<Event> {
self.tx.subscribe()
}
}
pub fn create_client() -> ClientResult<Client> {
let client = Arc::new(Client::new()?);
client.run()?;
Ok(client)
}
async fn watch_device(device_path: ObjectPath<'_>, tx: broadcast::Sender<Event>) -> Result<()> {
let dbus_connection = Connection::system().await?;
let device = DeviceDbusProxy::new(&dbus_connection, device_path.to_owned()).await?;
let interface = device.interface().await?;
tx.send(Event::DeviceAdded {
interface: interface.to_string(),
})?;
spawn(watch_device_state(
device_path.to_owned(),
interface.to_owned(),
tx.clone(),
));
Ok(())
}
async fn watch_device_state(
device_path: ObjectPath<'_>,
interface: Str<'_>,
tx: broadcast::Sender<Event>,
) -> Result<()> {
let dbus_connection = Connection::system().await?;
let device = DeviceDbusProxy::new(&dbus_connection, &device_path).await?;
let r#type = device.device_type().await?;
// Send an event communicating the initial state
let state = device.state().await?;
tx.send(Event::DeviceStateChanged {
interface: interface.to_string(),
r#type: r#type.clone(),
state,
})?;
let mut state_changes = device.receive_state_changed().await;
while let Some(state_change) = state_changes.next().await {
let state = state_change.get().await?;
tx.send(Event::DeviceStateChanged {
interface: interface.to_string(),
r#type: r#type.clone(),
state,
})?;
}
Ok(())
}
register_fallible_client!(Client, network_manager);

View file

@ -1,18 +1,16 @@
use crate::clients::networkmanager::Client;
use crate::clients::networkmanager::dbus::{DeviceState, DeviceType};
use crate::clients::networkmanager::event::Event;
use crate::channels::{AsyncSenderExt, BroadcastReceiverExt};
use crate::clients::networkmanager::{Client, ClientState};
use crate::config::CommonConfig;
use crate::gtk_helpers::IronbarGtkExt;
use crate::image::Provider;
use crate::modules::{Module, ModuleInfo, ModuleParts, WidgetContext};
use crate::{module_impl, spawn};
use color_eyre::{Result, eyre::Ok};
use glib::spawn_future_local;
use gtk::prelude::{ContainerExt, WidgetExt};
use gtk::{Image, Orientation};
use color_eyre::Result;
use futures_lite::StreamExt;
use futures_signals::signal::SignalExt;
use gtk::prelude::ContainerExt;
use gtk::{Box as GtkBox, Image};
use serde::Deserialize;
use std::collections::HashMap;
use tokio::sync::{broadcast, mpsc};
use tokio::sync::mpsc::Receiver;
#[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
@ -28,29 +26,26 @@ const fn default_icon_size() -> i32 {
24
}
impl Module<gtk::Box> for NetworkManagerModule {
type SendMessage = Event;
impl Module<GtkBox> for NetworkManagerModule {
type SendMessage = ClientState;
type ReceiveMessage = ();
module_impl!("network_manager");
fn spawn_controller(
&self,
_info: &ModuleInfo,
context: &WidgetContext<Event, ()>,
_rx: mpsc::Receiver<()>,
_: &ModuleInfo,
context: &WidgetContext<ClientState, ()>,
_: Receiver<()>,
) -> Result<()> {
let client = context.try_client::<Client>()?;
// Should we be using context.tx with ModuleUpdateEvent::Update instead?
let tx = context.update_tx.clone();
// Must be done here synchronously to avoid race condition
let mut client_rx = client.subscribe();
spawn(async move {
while let Result::Ok(event) = client_rx.recv().await {
tx.send(event)?;
}
let mut client_signal = client.subscribe().to_stream();
let tx = context.tx.clone();
Ok(())
spawn(async move {
while let Some(state) = client_signal.next().await {
tx.send_update(state).await;
}
});
Ok(())
@ -58,101 +53,50 @@ impl Module<gtk::Box> for NetworkManagerModule {
fn into_widget(
self,
context: WidgetContext<Event, ()>,
_info: &ModuleInfo,
) -> Result<ModuleParts<gtk::Box>> {
let container = gtk::Box::new(Orientation::Horizontal, 0);
context: WidgetContext<ClientState, ()>,
info: &ModuleInfo,
) -> Result<ModuleParts<GtkBox>> {
const INITIAL_ICON_NAME: &str = "content-loading-symbolic";
// 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
spawn_future_local(handle_update_events(
rx,
container.clone(),
self.icon_size,
context.ironbar.image_provider(),
));
let container = GtkBox::new(info.bar_position.orientation(), 0);
let icon = Image::new();
icon.add_class("icon");
container.add(&icon);
let image_provider = context.ironbar.image_provider();
glib::spawn_future_local({
let image_provider = image_provider.clone();
let icon = icon.clone();
async move {
image_provider
.load_into_image_silent(INITIAL_ICON_NAME, self.icon_size, false, &icon)
.await;
}
});
context.subscribe().recv_glib_async((), move |(), state| {
let image_provider = image_provider.clone();
let icon = icon.clone();
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",
};
async move {
image_provider
.load_into_image_silent(icon_name, self.icon_size, false, &icon)
.await;
}
});
Ok(ModuleParts::new(container, None))
}
}
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 {
Event::DeviceAdded { interface, .. } => {
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");
// TODO: Make this configurable at runtime
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 {
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"),
DeviceState::Activated => Some("icon:network-wired-symbolic"),
_ => None,
},
DeviceType::Wifi => match state {
DeviceState::Unavailable => Some("icon:network-wireless-hardware-disabled-symbolic"),
DeviceState::Disconnected
| DeviceState::Prepare
| DeviceState::Config
| DeviceState::NeedAuth
| DeviceState::IpConfig
| DeviceState::IpCheck
| DeviceState::Secondaries
| DeviceState::Deactivating
| DeviceState::Failed => Some("icon:network-wireless-offline-symbolic"),
DeviceState::Activated => Some("icon:network-wireless-connected-symbolic"),
_ => None,
},
DeviceType::Tun | DeviceType::Wireguard => match state {
DeviceState::Activated => Some("icon:network-vpn-symbolic"),
_ => None,
},
_ => None,
}
}