> for Event {
- type Error = Report;
-
- fn try_from(data: KeyData) -> Result {
- let key = Key::try_from(data.key)?;
-
- key.get_state(data.device_path)
- .map(|state| KeyEvent { key, state })
- .map(Event::Key)
- }
-}
-
-#[derive(Debug)]
-pub struct Client {
- tx: broadcast::Sender,
- _rx: broadcast::Receiver,
-
- seat: String,
- known_devices: Arc>>,
-}
-
-impl Client {
- pub fn init(seat: String) -> Arc {
- let client = Arc::new(Self::new(seat));
-
- {
- let client = client.clone();
-
- std::thread::spawn(move || {
- let local = LocalSet::new();
-
- local.spawn_local(async move {
- if let Err(err) = client.run().await {
- error!("{err:?}");
- }
- });
-
- Ironbar::runtime().block_on(local);
- });
- }
-
- client
- }
-
- fn new(seat: String) -> Self {
- let (tx, rx) = broadcast::channel(4);
-
- Self {
- tx,
- _rx: rx,
- seat,
- known_devices: arc_rw!(vec![]),
- }
- }
-
- fn open_restricted(path: &CStr, flags: c_int) -> std::result::Result {
- open(path, OFlags::from_bits_retain(flags as u32), Mode::empty())
- .map(IntoRawFd::into_raw_fd)
- .map_err(Errno::raw_os_error)
- }
-
- fn close_restricted(fd: c_int) {
- drop(unsafe { OwnedFd::from_raw_fd(fd) });
- }
-
- async fn run(&self) -> Result<()> {
- let mut libinput = Libinput::with_tracing(Self::open_restricted, Self::close_restricted)?;
-
- libinput.udev_assign_seat(CString::new(&*self.seat)?.as_c_str())?;
-
- let mut stream = libinput.event_stream()?;
- while let Some(event) = stream.try_next().await? {
- match event {
- colpetto::Event::Device(DeviceEvent::Added(event)) => {
- let device = event.device();
- if !device.has_capability(DeviceCapability::Keyboard) {
- continue;
- }
-
- let name = device.name();
- let Some(device) = event.device().udev_device() else {
- continue;
- };
-
- if let Some(device_path) = device.devnode() {
- // not all devices which report as keyboards actually are one -
- // fire test event so we can figure out if it is
- let caps_event: Result = KeyData {
- device_path,
- key: EV_KEY::KEY_CAPSLOCK,
- }
- .try_into();
-
- if caps_event.is_ok() {
- debug!(
- "new keyboard device: {} | {}",
- name.to_string_lossy(),
- device_path.display()
- );
- write_lock!(self.known_devices).push(device_path.to_path_buf());
- self.tx.send_expect(Event::Device);
- }
- }
- }
- colpetto::Event::Keyboard(KeyboardEvent::Key(event))
- if event.key_state() == KeyState::Released =>
- {
- let Some(device) = event.device().udev_device() else {
- continue;
- };
-
- let Some(
- key @ (EV_KEY::KEY_CAPSLOCK | EV_KEY::KEY_NUMLOCK | EV_KEY::KEY_SCROLLLOCK),
- ) = int_to_ev_key(event.key())
- else {
- continue;
- };
-
- if let Some(device_path) = device.devnode().map(PathBuf::from) {
- let tx = self.tx.clone();
-
- // need to spawn a task to avoid blocking
- spawn(async move {
- // wait for kb to change
- sleep(Duration::from_millis(50)).await;
-
- let data = KeyData { device_path, key };
-
- if let Ok(event) = data.try_into() {
- tx.send_expect(event);
- }
- });
- }
- }
- _ => {}
- }
- }
-
- Err(Report::msg("unexpected end of stream"))
- }
-
- pub fn get_state(&self, key: Key) -> bool {
- read_lock!(self.known_devices)
- .iter()
- .map(|device_path| key.get_state(device_path))
- .filter_map(Result::ok)
- .reduce(|state, curr| state || curr)
- .unwrap_or_default()
- }
-
- pub fn subscribe(&self) -> broadcast::Receiver {
- self.tx.subscribe()
- }
-}
diff --git a/src/clients/mod.rs b/src/clients/mod.rs
index cbacaa3..ebe2bda 100644
--- a/src/clients/mod.rs
+++ b/src/clients/mod.rs
@@ -1,33 +1,21 @@
-use crate::{Ironbar, await_sync};
+use crate::{await_sync, Ironbar};
use color_eyre::Result;
-use std::collections::HashMap;
use std::path::Path;
use std::rc::Rc;
use std::sync::Arc;
#[cfg(feature = "clipboard")]
pub mod clipboard;
-#[cfg(any(
- feature = "bindmode",
- feature = "hyprland",
- feature = "keyboard",
- feature = "workspaces",
-))]
+#[cfg(feature = "workspaces")]
pub mod compositor;
-#[cfg(feature = "keyboard")]
-pub mod libinput;
#[cfg(feature = "cairo")]
pub mod lua;
#[cfg(feature = "music")]
pub mod music;
#[cfg(feature = "network_manager")]
pub mod networkmanager;
-#[cfg(feature = "sway")]
-pub mod sway;
#[cfg(feature = "notifications")]
pub mod swaync;
-#[cfg(feature = "sys_info")]
-pub mod sysinfo;
#[cfg(feature = "tray")]
pub mod tray;
#[cfg(feature = "upower")]
@@ -43,28 +31,16 @@ pub struct Clients {
wayland: Option>,
#[cfg(feature = "workspaces")]
workspaces: Option>,
- #[cfg(feature = "sway")]
- sway: Option>,
- #[cfg(feature = "hyprland")]
- hyprland: Option>,
- #[cfg(feature = "bindmode")]
- bindmode: Option>,
#[cfg(feature = "clipboard")]
clipboard: Option>,
- #[cfg(feature = "keyboard")]
- libinput: HashMap, Arc>,
- #[cfg(feature = "keyboard")]
- keyboard_layout: Option>,
#[cfg(feature = "cairo")]
lua: Option>,
#[cfg(feature = "music")]
- music: HashMap>,
+ music: std::collections::HashMap>,
#[cfg(feature = "network_manager")]
network_manager: Option>,
#[cfg(feature = "notifications")]
notifications: Option>,
- #[cfg(feature = "sys_info")]
- sys_info: Option>,
#[cfg(feature = "tray")]
tray: Option>,
#[cfg(feature = "upower")]
@@ -97,68 +73,18 @@ impl Clients {
#[cfg(feature = "workspaces")]
pub fn workspaces(&mut self) -> ClientResult {
- let client = if let Some(workspaces) = &self.workspaces {
- workspaces.clone()
- } else {
- let client = compositor::Compositor::create_workspace_client(self)?;
- self.workspaces.replace(client.clone());
- client
+ let client = match &self.workspaces {
+ Some(workspaces) => workspaces.clone(),
+ None => {
+ let client = compositor::Compositor::create_workspace_client()?;
+ self.workspaces.replace(client.clone());
+ client
+ }
};
Ok(client)
}
- #[cfg(feature = "keyboard")]
- pub fn keyboard_layout(&mut self) -> ClientResult {
- let client = if let Some(keyboard_layout) = &self.keyboard_layout {
- keyboard_layout.clone()
- } else {
- let client = compositor::Compositor::create_keyboard_layout_client(self)?;
- self.keyboard_layout.replace(client.clone());
- client
- };
-
- Ok(client)
- }
-
- #[cfg(feature = "bindmode")]
- pub fn bindmode(&mut self) -> ClientResult {
- let client = if let Some(client) = &self.bindmode {
- client.clone()
- } else {
- let client = compositor::Compositor::create_bindmode_client(self)?;
- self.bindmode.replace(client.clone());
- client
- };
-
- Ok(client)
- }
-
- #[cfg(feature = "sway")]
- pub fn sway(&mut self) -> ClientResult {
- let client = if let Some(client) = &self.sway {
- client.clone()
- } else {
- let client = await_sync(async { sway::Client::new().await })?;
- let client = Arc::new(client);
- self.sway.replace(client.clone());
- client
- };
-
- Ok(client)
- }
-
- #[cfg(feature = "hyprland")]
- pub fn hyprland(&mut self) -> Arc {
- if let Some(client) = &self.hyprland {
- client.clone()
- } else {
- let client = Arc::new(compositor::hyprland::Client::new());
- self.hyprland.replace(client.clone());
- client
- }
- }
-
#[cfg(feature = "cairo")]
pub fn lua(&mut self, config_dir: &Path) -> Rc {
self.lua
@@ -166,17 +92,6 @@ impl Clients {
.clone()
}
- #[cfg(feature = "keyboard")]
- pub fn libinput(&mut self, seat: &str) -> Arc {
- if let Some(client) = self.libinput.get(seat) {
- client.clone()
- } else {
- let client = libinput::Client::init(seat.to_string());
- self.libinput.insert(seat.into(), client.clone());
- client
- }
- }
-
#[cfg(feature = "music")]
pub fn music(&mut self, client_type: music::ClientType) -> Arc {
self.music
@@ -187,68 +102,55 @@ impl Clients {
#[cfg(feature = "network_manager")]
pub fn network_manager(&mut self) -> ClientResult {
- if let Some(client) = &self.network_manager {
- Ok(client.clone())
- } else {
- let client = networkmanager::create_client()?;
- self.network_manager = Some(client.clone());
- Ok(client)
+ match &self.network_manager {
+ Some(client) => Ok(client.clone()),
+ None => {
+ let client = networkmanager::create_client()?;
+ self.network_manager = Some(client.clone());
+ Ok(client)
+ }
}
}
#[cfg(feature = "notifications")]
pub fn notifications(&mut self) -> ClientResult {
- let client = if let Some(client) = &self.notifications {
- client.clone()
- } else {
- let client = await_sync(async { swaync::Client::new().await })?;
- let client = Arc::new(client);
- self.notifications.replace(client.clone());
- client
+ let client = match &self.notifications {
+ Some(client) => client.clone(),
+ None => {
+ let client = await_sync(async { swaync::Client::new().await })?;
+ let client = Arc::new(client);
+ self.notifications.replace(client.clone());
+ client
+ }
};
Ok(client)
}
- #[cfg(feature = "sys_info")]
- pub fn sys_info(&mut self) -> Arc {
- self.sys_info
- .get_or_insert_with(|| {
- let client = Arc::new(sysinfo::Client::new());
-
- #[cfg(feature = "ipc")]
- Ironbar::variable_manager().register_namespace("sysinfo", client.clone());
-
- client
- })
- .clone()
- }
-
#[cfg(feature = "tray")]
pub fn tray(&mut self) -> ClientResult {
- let client = if let Some(client) = &self.tray {
- client.clone()
- } else {
- let client = await_sync(async { tray::Client::new().await })?;
- let client = Arc::new(client);
- self.tray.replace(client.clone());
- client
+ let client = match &self.tray {
+ Some(client) => client.clone(),
+ None => {
+ let service_name = format!("{}-{}", env!("CARGO_CRATE_NAME"), Ironbar::unique_id());
+
+ let client = await_sync(async { tray::Client::new(&service_name).await })?;
+ let client = Arc::new(client);
+ self.tray.replace(client.clone());
+ client
+ }
};
Ok(client)
}
#[cfg(feature = "upower")]
- pub fn upower(&mut self) -> ClientResult> {
- let client = if let Some(client) = &self.upower {
- client.clone()
- } else {
- let client = await_sync(async { upower::create_display_proxy().await })?;
- self.upower.replace(client.clone());
- client
- };
-
- Ok(client)
+ pub fn upower(&mut self) -> Arc> {
+ self.upower
+ .get_or_insert_with(|| {
+ crate::await_sync(async { upower::create_display_proxy().await })
+ })
+ .clone()
}
#[cfg(feature = "volume")]
diff --git a/src/clients/music/mod.rs b/src/clients/music/mod.rs
index 52895b7..e42b60f 100644
--- a/src/clients/music/mod.rs
+++ b/src/clients/music/mod.rs
@@ -70,17 +70,13 @@ pub trait MusicClient: Debug + Send + Sync {
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum ClientType {
- #[cfg(feature = "music+mpd")]
Mpd { host: String, music_dir: PathBuf },
- #[cfg(feature = "music+mpris")]
Mpris,
}
pub fn create_client(client_type: ClientType) -> Arc {
match client_type {
- #[cfg(feature = "music+mpd")]
ClientType::Mpd { host, music_dir } => Arc::new(mpd::Client::new(host, music_dir)),
- #[cfg(feature = "music+mpris")]
ClientType::Mpris => Arc::new(mpris::Client::new()),
}
}
diff --git a/src/clients/music/mpd.rs b/src/clients/music/mpd.rs
index 790d6ba..f1239c9 100644
--- a/src/clients/music/mpd.rs
+++ b/src/clients/music/mpd.rs
@@ -1,15 +1,14 @@
use super::{
- MusicClient, PlayerState, PlayerUpdate, ProgressTick, Status, TICK_INTERVAL_MS, Track,
+ MusicClient, PlayerState, PlayerUpdate, ProgressTick, Status, Track, TICK_INTERVAL_MS,
};
-use crate::channels::SyncSenderExt;
-use crate::{Ironbar, await_sync, spawn};
+use crate::{await_sync, send, spawn, Ironbar};
use color_eyre::Report;
use color_eyre::Result;
use mpd_client::client::{ConnectionEvent, Subsystem};
use mpd_client::commands::{self, SeekMode};
use mpd_client::responses::{PlayState, Song};
use mpd_client::tag::Tag;
-use mpd_utils::{PersistentClient, mpd_client};
+use mpd_utils::{mpd_client, PersistentClient};
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time::Duration;
@@ -98,7 +97,7 @@ impl Client {
let status = Status::from(status);
let update = PlayerUpdate::Update(Box::new(track), status);
- tx.send_expect(update);
+ send!(tx, update);
}
Ok(())
@@ -114,7 +113,7 @@ impl Client {
elapsed: status.elapsed,
});
- tx.send_expect(update);
+ send!(tx, update);
}
}
}
diff --git a/src/clients/music/mpris.rs b/src/clients/music/mpris.rs
index 96af8cd..d505560 100644
--- a/src/clients/music/mpris.rs
+++ b/src/clients/music/mpris.rs
@@ -1,7 +1,6 @@
-use super::{MusicClient, PlayerState, PlayerUpdate, Status, TICK_INTERVAL_MS, Track};
-use crate::channels::SyncSenderExt;
+use super::{MusicClient, PlayerState, PlayerUpdate, Status, Track, TICK_INTERVAL_MS};
use crate::clients::music::ProgressTick;
-use crate::{arc_mut, lock, spawn_blocking};
+use crate::{arc_mut, lock, send, spawn_blocking};
use color_eyre::Result;
use mpris::{DBusError, Event, Metadata, PlaybackStatus, Player, PlayerFinder};
use std::cmp;
@@ -48,14 +47,10 @@ impl Client {
)) if transport_error.name() == Some(NO_ACTIVE_PLAYER)
|| transport_error.name() == Some(NO_REPLY) =>
{
- vec![]
- }
- _ => {
- error!("D-Bus error getting MPRIS players: {e:?}");
- vec![]
+ Vec::new()
}
+ _ => panic!("Failed to connect to D-Bus"),
});
-
// Acquire the lock of current_player before players to avoid deadlock.
// There are places where we lock on current_player and players, but we always lock on current_player first.
// This is because we almost never need to lock on players without locking on current_player.
@@ -138,7 +133,7 @@ impl Client {
let mut players_locked = lock!(players);
players_locked.remove(identity);
if players_locked.is_empty() {
- tx.send_expect(PlayerUpdate::Update(Box::new(None), Status::default()));
+ send!(tx, PlayerUpdate::Update(Box::new(None), Status::default()));
}
};
@@ -213,7 +208,7 @@ impl Client {
let track = Track::from(metadata);
let player_update = PlayerUpdate::Update(Box::new(Some(track)), status);
- tx.send_expect(player_update);
+ send!(tx, player_update);
Ok(())
}
@@ -243,7 +238,7 @@ impl Client {
duration: metadata.length(),
});
- tx.send_expect(update);
+ send!(tx, update);
}
}
}
@@ -291,22 +286,14 @@ impl MusicClient for Client {
fn seek(&self, duration: Duration) -> Result<()> {
if let Some(player) = Self::get_player(self) {
- // if possible, use `set_position` instead of `seek` because some players have issues with seeking
- // see https://github.com/JakeStanger/ironbar/issues/970
- if let Ok(metadata) = player.get_metadata() {
- if let Some(track_id) = metadata.track_id() {
- player.set_position(track_id, &duration)?;
- } else {
- let pos = player.get_position().unwrap_or_default();
+ let pos = player.get_position().unwrap_or_default();
- let duration = duration.as_micros() as i64;
- let position = pos.as_micros() as i64;
+ let duration = duration.as_micros() as i64;
+ let position = pos.as_micros() as i64;
- let seek = cmp::max(duration, 0) - position;
+ let seek = cmp::max(duration, 0) - position;
- player.seek(seek)?;
- }
- }
+ player.seek(seek)?;
} else {
error!("Could not find player");
}
@@ -328,9 +315,7 @@ impl MusicClient for Client {
state: PlayerState::Stopped,
volume_percent: None,
};
-
- self.tx
- .send_expect(PlayerUpdate::Update(Box::new(None), status));
+ send!(self.tx, PlayerUpdate::Update(Box::new(None), status));
}
rx
diff --git a/src/clients/networkmanager/dbus.rs b/src/clients/networkmanager/dbus.rs
index 0f1e36a..5ab3889 100644
--- a/src/clients/networkmanager/dbus.rs
+++ b/src/clients/networkmanager/dbus.rs
@@ -1,36 +1,77 @@
use color_eyre::Result;
-use zbus::proxy;
+use zbus::dbus_proxy;
use zbus::zvariant::{ObjectPath, OwnedValue, Str};
-#[proxy(
+#[dbus_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>>;
+trait Dbus {
+ #[dbus_proxy(property)]
+ fn active_connections(&self) -> Result>;
+
+ #[dbus_proxy(property)]
+ fn devices(&self) -> Result>;
+
+ // #[dbus_proxy(property)]
+ // fn networking_enabled(&self) -> Result;
+
+ // #[dbus_proxy(property)]
+ // fn primary_connection(&self) -> Result;
+
+ // #[dbus_proxy(property)]
+ // fn primary_connection_type(&self) -> Result;
+
+ // #[dbus_proxy(property)]
+ // fn wireless_enabled(&self) -> Result;
}
-#[proxy(
+#[dbus_proxy(
+ default_service = "org.freedesktop.NetworkManager",
+ interface = "org.freedesktop.NetworkManager.Connection.Active"
+)]
+trait ActiveConnectionDbus {
+ // #[dbus_proxy(property)]
+ // fn connection(&self) -> Result;
+
+ // #[dbus_proxy(property)]
+ // fn default(&self) -> Result;
+
+ // #[dbus_proxy(property)]
+ // fn default6(&self) -> Result;
+
+ #[dbus_proxy(property)]
+ fn devices(&self) -> Result>;
+
+ // #[dbus_proxy(property)]
+ // fn id(&self) -> Result;
+
+ #[dbus_proxy(property)]
+ fn type_(&self) -> Result;
+
+ // #[dbus_proxy(property)]
+ // fn uuid(&self) -> Result;
+}
+
+#[dbus_proxy(
default_service = "org.freedesktop.NetworkManager",
interface = "org.freedesktop.NetworkManager.Device"
)]
-pub(super) trait DeviceDbus {
- #[zbus(property)]
+trait DeviceDbus {
+ // #[dbus_proxy(property)]
+ // fn active_connection(&self) -> Result;
+
+ #[dbus_proxy(property)]
fn device_type(&self) -> Result;
- #[zbus(property)]
- fn interface(&self) -> Result>;
-
- #[zbus(property)]
+ #[dbus_proxy(property)]
fn state(&self) -> Result;
}
-// 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)]
+#[derive(Clone, Debug, OwnedValue, PartialEq)]
#[repr(u32)]
-pub enum DeviceType {
+pub(super) enum DeviceType {
Unknown = 0,
Ethernet = 1,
Wifi = 2,
@@ -64,10 +105,9 @@ pub enum DeviceType {
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 {
+pub(super) enum DeviceState {
Unknown = 0,
Unmanaged = 10,
Unavailable = 20,
@@ -82,3 +122,12 @@ pub enum DeviceState {
Deactivating = 110,
Failed = 120,
}
+
+impl DeviceState {
+ pub(super) fn is_enabled(&self) -> bool {
+ !matches!(
+ self,
+ DeviceState::Unknown | DeviceState::Unmanaged | DeviceState::Unavailable,
+ )
+ }
+}
diff --git a/src/clients/networkmanager/event.rs b/src/clients/networkmanager/event.rs
deleted file mode 100644
index 4963f6e..0000000
--- a/src/clients/networkmanager/event.rs
+++ /dev/null
@@ -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,
- },
-}
diff --git a/src/clients/networkmanager/mod.rs b/src/clients/networkmanager/mod.rs
index 1a25183..40c64e1 100644
--- a/src/clients/networkmanager/mod.rs
+++ b/src/clients/networkmanager/mod.rs
@@ -1,118 +1,235 @@
+use std::collections::HashMap;
+use std::sync::{Arc, RwLock};
+
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 futures_signals::signal::{Mutable, MutableSignalCloned};
+use tracing::error;
+use zbus::blocking::Connection;
+use zbus::zvariant::ObjectPath;
-use crate::clients::ClientResult;
-use crate::clients::networkmanager::dbus::{DbusProxy, DeviceDbusProxy};
-use crate::clients::networkmanager::event::Event;
-use crate::{register_fallible_client, spawn};
+use crate::clients::networkmanager::dbus::{
+ ActiveConnectionDbusProxyBlocking, DbusProxyBlocking, DeviceDbusProxyBlocking,
+};
+use crate::clients::networkmanager::state::{
+ determine_cellular_state, determine_vpn_state, determine_wifi_state, determine_wired_state,
+ CellularState, State, VpnState, WifiState, WiredState,
+};
+use crate::{
+ read_lock, register_fallible_client, spawn_blocking, spawn_blocking_result, write_lock,
+};
-pub mod dbus;
-pub mod event;
+mod dbus;
+pub mod state;
+
+type PathMap<'l, ValueType> = HashMap, ValueType>;
#[derive(Debug)]
-pub struct Client {
- tx: broadcast::Sender,
+pub struct Client(Arc>);
+
+#[derive(Debug)]
+struct ClientInner<'l> {
+ state: Mutable,
+ root_object: &'l DbusProxyBlocking<'l>,
+ active_connections: RwLock>>,
+ devices: RwLock>>,
+ dbus_connection: Connection,
}
impl Client {
fn new() -> Result {
- let (tx, _) = broadcast::channel(64);
- Ok(Client { tx })
+ let state = Mutable::new(State {
+ wired: WiredState::Unknown,
+ wifi: WifiState::Unknown,
+ cellular: CellularState::Unknown,
+ vpn: VpnState::Unknown,
+ });
+ let dbus_connection = Connection::system()?;
+ let root_object = {
+ let root_object = DbusProxyBlocking::new(&dbus_connection)?;
+ // Workaround for the fact that zbus (unnecessarily) requires a static lifetime here
+ Box::leak(Box::new(root_object))
+ };
+
+ Ok(Client(Arc::new(ClientInner {
+ state,
+ root_object,
+ active_connections: RwLock::new(HashMap::new()),
+ devices: RwLock::new(HashMap::new()),
+ dbus_connection,
+ })))
}
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?;
+ macro_rules! update_state_for_device_change {
+ ($client:ident) => {
+ $client.state.set(State {
+ wired: determine_wired_state(&read_lock!($client.devices))?,
+ wifi: determine_wifi_state(&read_lock!($client.devices))?,
+ cellular: determine_cellular_state(&read_lock!($client.devices))?,
+ vpn: $client.state.get_cloned().vpn,
+ });
+ };
+ }
- 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()));
+ macro_rules! initialise_path_map {
+ (
+ $client:expr,
+ $path_map:ident,
+ $proxy_type:ident
+ $(, |$new_path:ident| $property_watcher:expr)*
+ ) => {
+ let new_paths = $client.root_object.$path_map()?;
+ let mut path_map = HashMap::new();
+ for new_path in new_paths {
+ let new_proxy = $proxy_type::builder(&$client.dbus_connection)
+ .path(new_path.clone())?
+ .build()?;
+ path_map.insert(new_path.clone(), new_proxy);
+ $({
+ let $new_path = &new_path;
+ $property_watcher;
+ })*
}
+ *write_lock!($client.$path_map) = path_map;
+ };
+ }
- let removed_devices = devices.difference(&new_devices);
- // TODO: Cook up some way to notify closures for removed devices to exit
-
- devices = new_devices;
+ macro_rules! spawn_path_list_watcher {
+ (
+ $client:expr,
+ $property:ident,
+ $property_changes:ident,
+ $proxy_type:ident,
+ |$state_client:ident| $state_update:expr
+ $(, |$property_client:ident, $new_path:ident| $property_watcher:expr)*
+ ) => {
+ let client = $client.clone();
+ spawn_blocking_result!({
+ let changes = client.root_object.$property_changes();
+ for _ in changes {
+ let mut new_path_map = HashMap::new();
+ {
+ let new_paths = client.root_object.$property()?;
+ let path_map = read_lock!(client.$property);
+ for new_path in new_paths {
+ if path_map.contains_key(&new_path) {
+ let proxy = path_map
+ .get(&new_path)
+ .expect("Should contain the key, guarded by runtime check");
+ new_path_map.insert(new_path, proxy.to_owned());
+ } else {
+ let new_proxy = $proxy_type::builder(&client.dbus_connection)
+ .path(new_path.clone())?
+ .build()?;
+ new_path_map.insert(new_path.clone(), new_proxy);
+ $({
+ let $property_client = &client;
+ let $new_path = &new_path;
+ $property_watcher;
+ })*
+ }
+ }
+ }
+ *write_lock!(client.$property) = new_path_map;
+ let $state_client = &client;
+ $state_update;
+ }
+ Ok(())
+ });
}
+ }
- Ok(())
+ macro_rules! spawn_property_watcher {
+ (
+ $client:expr,
+ $path:expr,
+ $property_changes:ident,
+ $containing_list:ident,
+ |$inner_client:ident| $state_update:expr
+ ) => {
+ let client = $client.clone();
+ let path = $path.clone();
+ spawn_blocking_result!({
+ let changes = read_lock!(client.$containing_list)
+ .get(&path)
+ .expect("Should contain the key upon watcher start")
+ .$property_changes();
+ for _ in changes {
+ if !read_lock!(client.$containing_list).contains_key(&path) {
+ break;
+ }
+ let $inner_client = &client;
+ $state_update;
+ }
+ Ok(())
+ });
+ };
+ }
+
+ initialise_path_map!(
+ self.0,
+ active_connections,
+ ActiveConnectionDbusProxyBlocking
+ );
+ initialise_path_map!(self.0, devices, DeviceDbusProxyBlocking, |path| {
+ spawn_property_watcher!(self.0, path, receive_state_changed, devices, |client| {
+ update_state_for_device_change!(client);
+ });
});
+ self.0.state.set(State {
+ wired: determine_wired_state(&read_lock!(self.0.devices))?,
+ wifi: determine_wifi_state(&read_lock!(self.0.devices))?,
+ cellular: determine_cellular_state(&read_lock!(self.0.devices))?,
+ vpn: determine_vpn_state(&read_lock!(self.0.active_connections))?,
+ });
+
+ spawn_path_list_watcher!(
+ self.0,
+ active_connections,
+ receive_active_connections_changed,
+ ActiveConnectionDbusProxyBlocking,
+ |client| {
+ client.state.set(State {
+ wired: client.state.get_cloned().wired,
+ wifi: client.state.get_cloned().wifi,
+ cellular: client.state.get_cloned().cellular,
+ vpn: determine_vpn_state(&read_lock!(client.active_connections))?,
+ });
+ }
+ );
+ spawn_path_list_watcher!(
+ self.0,
+ devices,
+ receive_devices_changed,
+ DeviceDbusProxyBlocking,
+ |client| {
+ update_state_for_device_change!(client);
+ },
+ |client, path| {
+ spawn_property_watcher!(client, path, receive_state_changed, devices, |client| {
+ update_state_for_device_change!(client);
+ });
+ }
+ );
Ok(())
}
- pub fn subscribe(&self) -> broadcast::Receiver {
- self.tx.subscribe()
+ pub fn subscribe(&self) -> MutableSignalCloned {
+ self.0.state.signal_cloned()
}
}
-pub fn create_client() -> ClientResult {
+pub fn create_client() -> Result> {
let client = Arc::new(Client::new()?);
- client.run()?;
+ {
+ let client = client.clone();
+ spawn_blocking_result!({
+ client.run()?;
+ Ok(())
+ });
+ }
Ok(client)
}
-async fn watch_device(device_path: ObjectPath<'_>, tx: broadcast::Sender) -> 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,
-) -> 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);
diff --git a/src/clients/networkmanager/state.rs b/src/clients/networkmanager/state.rs
index 9e5c4e4..e5e226e 100644
--- a/src/clients/networkmanager/state.rs
+++ b/src/clients/networkmanager/state.rs
@@ -1,11 +1,9 @@
-use crate::clients::networkmanager::dbus::{
- ActiveConnectionDbusProxy, DeviceDbusProxy, DeviceState, DeviceType,
-};
use color_eyre::Result;
-use std::collections::HashMap;
-use zbus::zvariant::ObjectPath;
-type PathMap<'l, ValueType> = HashMap, ValueType>;
+use crate::clients::networkmanager::dbus::{
+ ActiveConnectionDbusProxyBlocking, DeviceDbusProxyBlocking, DeviceState, DeviceType,
+};
+use crate::clients::networkmanager::PathMap;
#[derive(Clone, Debug)]
pub struct State {
@@ -58,16 +56,16 @@ pub struct VpnConnectedState {
pub name: String,
}
-pub(super) async fn determine_wired_state(
- devices: &PathMap<'_, DeviceDbusProxy<'_>>,
+pub(super) fn determine_wired_state(
+ devices: &PathMap,
) -> Result {
let mut present = false;
let mut connected = false;
for device in devices.values() {
- if device.device_type().await? == DeviceType::Ethernet {
+ if device.device_type()? == DeviceType::Ethernet {
present = true;
- if device.state().await?.is_enabled() {
+ if device.state()?.is_enabled() {
connected = true;
break;
}
@@ -83,19 +81,19 @@ pub(super) async fn determine_wired_state(
}
}
-pub(super) async fn determine_wifi_state(
- devices: &PathMap<'_, DeviceDbusProxy<'_>>,
+pub(super) fn determine_wifi_state(
+ devices: &PathMap,
) -> Result {
let mut present = false;
let mut enabled = false;
let mut connected = false;
for device in devices.values() {
- if device.device_type().await? == DeviceType::Wifi {
+ if device.device_type()? == DeviceType::Wifi {
present = true;
- if device.state().await?.is_enabled() {
+ if device.state()?.is_enabled() {
enabled = true;
- if device.state().await? == DeviceState::Activated {
+ if device.state()? == DeviceState::Activated {
connected = true;
break;
}
@@ -117,19 +115,19 @@ pub(super) async fn determine_wifi_state(
}
}
-pub(super) async fn determine_cellular_state(
- devices: &PathMap<'_, DeviceDbusProxy<'_>>,
+pub(super) fn determine_cellular_state(
+ devices: &PathMap,
) -> Result {
let mut present = false;
let mut enabled = false;
let mut connected = false;
for device in devices.values() {
- if device.device_type().await? == DeviceType::Modem {
+ if device.device_type()? == DeviceType::Modem {
present = true;
- if device.state().await?.is_enabled() {
+ if device.state()?.is_enabled() {
enabled = true;
- if device.state().await? == DeviceState::Activated {
+ if device.state()? == DeviceState::Activated {
connected = true;
break;
}
@@ -148,11 +146,11 @@ pub(super) async fn determine_cellular_state(
}
}
-pub(super) async fn determine_vpn_state(
- active_connections: &PathMap<'_, ActiveConnectionDbusProxy<'_>>,
+pub(super) fn determine_vpn_state(
+ active_connections: &PathMap,
) -> Result {
for connection in active_connections.values() {
- match connection.type_().await?.as_str() {
+ match connection.type_()?.as_str() {
"vpn" | "wireguard" => {
return Ok(VpnState::Connected(VpnConnectedState {
name: "unknown".into(),
diff --git a/src/clients/sway.rs b/src/clients/sway.rs
deleted file mode 100644
index 492709e..0000000
--- a/src/clients/sway.rs
+++ /dev/null
@@ -1,181 +0,0 @@
-use crate::spawn;
-use color_eyre::{Report, Result};
-use futures_lite::StreamExt;
-use std::sync::Arc;
-use swayipc_async::{Connection, Event, EventType};
-use tokio::sync::Mutex;
-use tracing::{info, trace};
-
-type SyncFn = dyn Fn(&T) + Sync + Send;
-
-struct TaskState {
- join_handle: Option>>,
- // could have been a `HashMap>>`, but we don't
- // expect enough listeners to justify the constant overhead of a hashmap.
- listeners: Arc>)>>,
-}
-
-pub struct Client {
- connection: Arc>,
- task_state: Mutex,
-}
-
-impl std::fmt::Debug for Client {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- f.debug_struct("Client")
- .field("client", &"Connection")
- .field("task_state", &format_args!("<...>"))
- .finish()
- }
-}
-
-impl Client {
- pub(crate) async fn new() -> Result {
- // Avoid using `arc_mut!` here because we need tokio Mutex.
- let client = Arc::new(Mutex::new(Connection::new().await?));
- info!("Sway IPC subscription client connected");
-
- Ok(Self {
- connection: client,
- task_state: Mutex::new(TaskState {
- listeners: Arc::new(Vec::new()),
- join_handle: None,
- }),
- })
- }
-
- pub fn connection(&self) -> &Arc> {
- &self.connection
- }
-
- pub async fn add_listener(
- &self,
- f: impl Fn(&T) + Sync + Send + 'static,
- ) -> Result<()> {
- self.add_listener_type(
- T::EVENT_TYPE,
- Box::new(move |event| {
- let event = T::from_event(event).expect("event type mismatch");
- f(event);
- }),
- )
- .await
- }
-
- pub async fn add_listener_type(
- &self,
- event_type: EventType,
- f: Box>,
- ) -> Result<()> {
- // abort current running task
- let TaskState {
- join_handle,
- listeners,
- } = &mut *self.task_state.lock().await;
-
- if let Some(handle) = join_handle.take() {
- handle.abort();
- let _ = handle.await;
- }
-
- // Only the task and self have a reference to listeners, and we just abort the task. This
- // is the only reference to listeners, so we can safely get a mutable reference.
- let listeners_mut = Arc::get_mut(listeners)
- .ok_or_else(|| Report::msg("Failed to get mutable reference to listeners"))?;
-
- listeners_mut.push((event_type, f));
-
- // create new client as subscription takes ownership
- let client = Connection::new().await?;
-
- let event_types = listeners.iter().map(|(t, _)| *t).collect::>();
- let listeners = listeners.clone();
-
- let handle = spawn(async move {
- let mut events = client.subscribe(&event_types).await?;
-
- while let Some(event) = events.next().await {
- trace!("event: {:?}", event);
- let event = event?;
- let ty = sway_event_to_event_type(&event);
- for (t, f) in listeners.iter() {
- if *t == ty {
- f(&event);
- }
- }
- }
-
- Ok::<(), Report>(())
- });
-
- *join_handle = Some(handle);
-
- Ok(())
- }
-}
-
-fn sway_event_to_event_type(event: &Event) -> EventType {
- match event {
- Event::Workspace(_) => EventType::Workspace,
- Event::Mode(_) => EventType::Mode,
- Event::Window(_) => EventType::Window,
- Event::BarConfigUpdate(_) => EventType::BarConfigUpdate,
- Event::Binding(_) => EventType::Binding,
- Event::Shutdown(_) => EventType::Shutdown,
- Event::Tick(_) => EventType::Tick,
- Event::BarStateUpdate(_) => EventType::BarStateUpdate,
- Event::Input(_) => EventType::Input,
- _ => todo!(),
- }
-}
-
-pub trait SwayIpcEvent {
- const EVENT_TYPE: EventType;
- fn from_event(e: &Event) -> Option<&Self>;
-}
-macro_rules! sway_ipc_event_impl {
- (@ $($t:tt)*) => { $($t)* };
- ($t:ty, $v:expr, $($m:tt)*) => {
- sway_ipc_event_impl! {@
- impl SwayIpcEvent for $t {
- const EVENT_TYPE: EventType = $v;
- fn from_event(e: &Event) -> Option<&Self> {
- match e {
- $($m)* (x) => Some(x),
- _ => None,
- }
- }
- }
- }
- };
-}
-
-sway_ipc_event_impl!(
- swayipc_async::WorkspaceEvent,
- EventType::Workspace,
- Event::Workspace
-);
-sway_ipc_event_impl!(swayipc_async::ModeEvent, EventType::Mode, Event::Mode);
-sway_ipc_event_impl!(swayipc_async::WindowEvent, EventType::Window, Event::Window);
-sway_ipc_event_impl!(
- swayipc_async::BarConfig,
- EventType::BarConfigUpdate,
- Event::BarConfigUpdate
-);
-sway_ipc_event_impl!(
- swayipc_async::BindingEvent,
- EventType::Binding,
- Event::Binding
-);
-sway_ipc_event_impl!(
- swayipc_async::ShutdownEvent,
- EventType::Shutdown,
- Event::Shutdown
-);
-sway_ipc_event_impl!(swayipc_async::TickEvent, EventType::Tick, Event::Tick);
-sway_ipc_event_impl!(
- swayipc_async::BarStateUpdateEvent,
- EventType::BarStateUpdate,
- Event::BarStateUpdate
-);
-sway_ipc_event_impl!(swayipc_async::InputEvent, EventType::Input, Event::Input);
diff --git a/src/clients/swaync/dbus.rs b/src/clients/swaync/dbus.rs
index 1909163..1bc292b 100644
--- a/src/clients/swaync/dbus.rs
+++ b/src/clients/swaync/dbus.rs
@@ -20,14 +20,12 @@
//! [Writing a client proxy]: https://dbus2.github.io/zbus/client.html
//! [D-Bus standard interfaces]: https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces,
-use zbus::proxy;
-
-#[proxy(
+#[zbus::dbus_proxy(
interface = "org.erikreider.swaync.cc",
default_service = "org.erikreider.swaync.cc",
default_path = "/org/erikreider/swaync/cc"
)]
-pub trait SwayNc {
+trait SwayNc {
/// AddInhibitor method
fn add_inhibitor(&self, application_id: &str) -> zbus::Result;
@@ -92,11 +90,11 @@ pub trait SwayNc {
fn toggle_visibility(&self) -> zbus::Result<()>;
/// Subscribe signal
- #[zbus(signal)]
+ #[dbus_proxy(signal)]
fn subscribe(&self, count: u32, dnd: bool, cc_open: bool) -> zbus::Result<()>;
/// SubscribeV2 signal
- #[zbus(signal)]
+ #[dbus_proxy(signal)]
fn subscribe_v2(
&self,
count: u32,
@@ -106,8 +104,8 @@ pub trait SwayNc {
) -> zbus::Result<()>;
/// Inhibited property
- #[zbus(property)]
+ #[dbus_proxy(property)]
fn inhibited(&self) -> zbus::Result;
- #[zbus(property)]
+ #[dbus_proxy(property)]
fn set_inhibited(&self, value: bool) -> zbus::Result<()>;
}
diff --git a/src/clients/swaync/mod.rs b/src/clients/swaync/mod.rs
index e1ee1db..9b5c116 100644
--- a/src/clients/swaync/mod.rs
+++ b/src/clients/swaync/mod.rs
@@ -1,7 +1,6 @@
mod dbus;
-use crate::channels::SyncSenderExt;
-use crate::{register_fallible_client, spawn};
+use crate::{register_fallible_client, send, spawn};
use color_eyre::{Report, Result};
use dbus::SwayNcProxy;
use serde::Deserialize;
@@ -55,13 +54,9 @@ impl Client {
spawn(async move {
while let Some(ev) = stream.next().await {
- let ev = ev
- .message()
- .body()
- .deserialize::()
- .expect("to deserialize");
+ let ev = ev.body::().expect("to deserialize");
debug!("Received event: {ev:?}");
- tx.send_expect(ev);
+ send!(tx, ev);
}
});
}
diff --git a/src/clients/sysinfo.rs b/src/clients/sysinfo.rs
deleted file mode 100644
index a9466c0..0000000
--- a/src/clients/sysinfo.rs
+++ /dev/null
@@ -1,621 +0,0 @@
-use crate::modules::sysinfo::Interval;
-use crate::{lock, register_client};
-use color_eyre::{Report, Result};
-use std::cmp::Ordering;
-use std::collections::HashMap;
-use std::fmt::Debug;
-use std::str::FromStr;
-use std::sync::{Arc, Mutex};
-use sysinfo::{Components, Disks, LoadAvg, Networks, RefreshKind, System};
-
-#[repr(u64)]
-#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
-pub enum Prefix {
- #[default]
- None = 1,
-
- Kilo = 1000,
- Mega = Prefix::Kilo as u64 * 1000,
- Giga = Prefix::Mega as u64 * 1000,
- Tera = Prefix::Giga as u64 * 1000,
- Peta = Prefix::Tera as u64 * 1000,
-
- Kibi = 1024,
- Mebi = Prefix::Kibi as u64 * 1024,
- Gibi = Prefix::Mebi as u64 * 1024,
- Tebi = Prefix::Gibi as u64 * 1024,
- Pebi = Prefix::Tebi as u64 * 1024,
-
- // # Units
- // These are special cases
- // where you'd actually want to do slightly more than a prefix alone.
- // Included as part of the prefix system for simplicity.
- KiloBit = 128,
- MegaBit = Prefix::KiloBit as u64 * 1024,
- GigaBit = Prefix::MegaBit as u64 * 1024,
-}
-
-#[derive(Debug, Clone)]
-pub enum Function {
- None,
- Sum,
- Min,
- Max,
- Mean,
- Name(String),
-}
-
-impl FromStr for Function {
- type Err = ();
-
- fn from_str(s: &str) -> Result {
- match s.to_lowercase().as_str() {
- "sum" => Ok(Self::Sum),
- "min" => Ok(Self::Min),
- "max" => Ok(Self::Max),
- "mean" => Ok(Self::Mean),
- "" => Err(()),
- _ => Ok(Self::Name(s.to_string())),
- }
- }
-}
-
-#[derive(Debug)]
-pub struct ValueSet {
- values: HashMap, Value>,
-}
-
-impl FromIterator<(Box, Value)> for ValueSet {
- fn from_iter, Value)>>(iter: T) -> Self {
- Self {
- values: iter.into_iter().collect(),
- }
- }
-}
-
-impl ValueSet {
- fn values(&self, prefix: Prefix) -> impl Iterator- + use<'_> {
- self.values
- .values()
- .map(move |v| v.get(prefix))
- .filter(|v| !v.is_nan())
- }
-
- pub fn apply(&self, function: &Function, prefix: Prefix) -> f64 {
- match function {
- Function::None => 0.0,
- Function::Sum => self.sum(prefix),
- Function::Min => self.min(prefix),
- Function::Max => self.max(prefix),
- Function::Mean => self.mean(prefix),
- Function::Name(name) => self
- .values
- .get(&Box::from(name.as_str()))
- .map(|v| v.get(prefix))
- .unwrap_or_default(),
- }
- }
-
- fn sum(&self, prefix: Prefix) -> f64 {
- self.values(prefix).sum()
- }
-
- fn min(&self, prefix: Prefix) -> f64 {
- self.values(prefix)
- .min_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal))
- .unwrap_or_default()
- }
-
- fn max(&self, prefix: Prefix) -> f64 {
- self.values(prefix)
- .max_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal))
- .unwrap_or_default()
- }
-
- fn mean(&self, prefix: Prefix) -> f64 {
- self.sum(prefix) / self.values.len() as f64
- }
-}
-
-#[derive(Debug, Default, Clone, Copy, PartialEq)]
-pub struct Value {
- value: f64,
- prefix: Prefix,
-}
-
-impl Value {
- pub fn new(value: f64) -> Self {
- Self::new_with_prefix(value, Prefix::None)
- }
-
- pub fn new_with_prefix(value: f64, prefix: Prefix) -> Self {
- Self { value, prefix }
- }
-
- pub fn get(self, prefix: Prefix) -> f64 {
- if prefix == self.prefix {
- self.value
- } else {
- let scale = self.prefix as u64 as f64 / prefix as u64 as f64;
- self.value * scale
- }
- }
-}
-
-#[derive(Debug)]
-pub struct Client {
- system: Mutex,
- disks: Mutex,
- components: Mutex,
- networks: Mutex,
- load_average: Mutex,
-}
-
-impl Client {
- pub fn new() -> Self {
- let refresh_kind = RefreshKind::everything().without_processes();
-
- let system = System::new_with_specifics(refresh_kind);
- let disks = Disks::new_with_refreshed_list();
- let components = Components::new_with_refreshed_list();
- let networks = Networks::new_with_refreshed_list();
- let load_average = System::load_average();
-
- Self {
- system: Mutex::new(system),
- disks: Mutex::new(disks),
- components: Mutex::new(components),
- networks: Mutex::new(networks),
- load_average: Mutex::new(load_average),
- }
- }
-
- pub fn refresh_cpu(&self) {
- lock!(self.system).refresh_cpu_all();
- }
-
- pub fn refresh_memory(&self) {
- lock!(self.system).refresh_memory();
- }
-
- pub fn refresh_network(&self) {
- lock!(self.networks).refresh(true);
- }
-
- pub fn refresh_temps(&self) {
- lock!(self.components).refresh(true);
- }
-
- pub fn refresh_disks(&self) {
- lock!(self.disks).refresh(true);
- }
-
- pub fn refresh_load_average(&self) {
- *lock!(self.load_average) = System::load_average();
- }
-
- pub fn cpu_frequency(&self) -> ValueSet {
- lock!(self.system)
- .cpus()
- .iter()
- .map(|cpu| {
- (
- cpu.name().into(),
- Value::new_with_prefix(cpu.frequency() as f64, Prefix::Mega),
- )
- })
- .collect()
- }
-
- pub fn cpu_percent(&self) -> ValueSet {
- lock!(self.system)
- .cpus()
- .iter()
- .map(|cpu| (cpu.name().into(), Value::new(cpu.cpu_usage() as f64)))
- .collect()
- }
-
- pub fn memory_free(&self) -> Value {
- Value::new(lock!(self.system).free_memory() as f64)
- }
-
- pub fn memory_available(&self) -> Value {
- Value::new(lock!(self.system).available_memory() as f64)
- }
-
- pub fn memory_total(&self) -> Value {
- Value::new(lock!(self.system).total_memory() as f64)
- }
-
- pub fn memory_used(&self) -> Value {
- Value::new(lock!(self.system).used_memory() as f64)
- }
-
- pub fn memory_percent(&self) -> Value {
- let total = lock!(self.system).total_memory() as f64;
- let used = lock!(self.system).used_memory() as f64;
-
- Value::new(used / total * 100.0)
- }
-
- pub fn swap_free(&self) -> Value {
- Value::new(lock!(self.system).free_swap() as f64)
- }
-
- pub fn swap_total(&self) -> Value {
- Value::new(lock!(self.system).total_swap() as f64)
- }
-
- pub fn swap_used(&self) -> Value {
- Value::new(lock!(self.system).used_swap() as f64)
- }
- pub fn swap_percent(&self) -> Value {
- let total = lock!(self.system).total_swap() as f64;
- let used = lock!(self.system).used_swap() as f64;
-
- Value::new(used / total * 100.0)
- }
-
- pub fn temp_c(&self) -> ValueSet {
- lock!(self.components)
- .iter()
- .map(|comp| {
- (
- comp.label().into(),
- Value::new(comp.temperature().unwrap_or_default() as f64),
- )
- })
- .collect()
- }
-
- pub fn temp_f(&self) -> ValueSet {
- lock!(self.components)
- .iter()
- .map(|comp| {
- (
- comp.label().into(),
- Value::new(c_to_f(comp.temperature().unwrap_or_default() as f64)),
- )
- })
- .collect()
- }
-
- pub fn disk_free(&self) -> ValueSet {
- lock!(self.disks)
- .iter()
- .map(|disk| {
- (
- disk.mount_point().to_string_lossy().into(),
- Value::new(disk.available_space() as f64),
- )
- })
- .collect()
- }
-
- pub fn disk_total(&self) -> ValueSet {
- lock!(self.disks)
- .iter()
- .map(|disk| {
- (
- disk.mount_point().to_string_lossy().into(),
- Value::new(disk.total_space() as f64),
- )
- })
- .collect()
- }
-
- pub fn disk_used(&self) -> ValueSet {
- lock!(self.disks)
- .iter()
- .map(|disk| {
- (
- disk.mount_point().to_string_lossy().into(),
- Value::new((disk.total_space() - disk.available_space()) as f64),
- )
- })
- .collect()
- }
-
- pub fn disk_percent(&self) -> ValueSet {
- lock!(self.disks)
- .iter()
- .map(|disk| {
- (
- disk.mount_point().to_string_lossy().into(),
- Value::new(
- (disk.total_space() - disk.available_space()) as f64
- / disk.total_space() as f64
- * 100.0,
- ),
- )
- })
- .collect()
- }
-
- pub fn disk_read(&self, interval: Interval) -> ValueSet {
- lock!(self.disks)
- .iter()
- .map(|disk| {
- (
- disk.mount_point().to_string_lossy().into(),
- Value::new(disk.usage().read_bytes as f64 / interval.disks() as f64),
- )
- })
- .collect()
- }
-
- pub fn disk_write(&self, interval: Interval) -> ValueSet {
- lock!(self.disks)
- .iter()
- .map(|disk| {
- (
- disk.mount_point().to_string_lossy().into(),
- Value::new(disk.usage().written_bytes as f64 / interval.disks() as f64),
- )
- })
- .collect()
- }
-
- pub fn net_down(&self, interval: Interval) -> ValueSet {
- lock!(self.networks)
- .iter()
- .map(|(name, net)| {
- (
- name.as_str().into(),
- Value::new(net.received() as f64 / interval.networks() as f64),
- )
- })
- .collect()
- }
-
- pub fn net_up(&self, interval: Interval) -> ValueSet {
- lock!(self.networks)
- .iter()
- .map(|(name, net)| {
- (
- name.as_str().into(),
- Value::new(net.transmitted() as f64 / interval.networks() as f64),
- )
- })
- .collect()
- }
-
- pub fn load_average_1(&self) -> Value {
- Value::new(lock!(self.load_average).one)
- }
-
- pub fn load_average_5(&self) -> Value {
- Value::new(lock!(self.load_average).five)
- }
-
- pub fn load_average_15(&self) -> Value {
- Value::new(lock!(self.load_average).fifteen)
- }
-
- /// Gets system uptime formatted as `HH:mm`.
- pub fn uptime() -> String {
- let uptime = System::uptime();
- let hours = uptime / 3600;
- format!("{:0>2}:{:0>2}", hours, (uptime % 3600) / 60)
- }
-}
-
-register_client!(Client, sys_info);
-
-const fn c_to_f(c: f64) -> f64 {
- c / 5.0 * 9.0 + 32.0
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum TokenType {
- CpuFrequency,
- CpuPercent,
-
- MemoryFree,
- MemoryAvailable,
- MemoryTotal,
- MemoryUsed,
- MemoryPercent,
-
- SwapFree,
- SwapTotal,
- SwapUsed,
- SwapPercent,
-
- TempC,
- TempF,
-
- DiskFree,
- DiskTotal,
- DiskUsed,
- DiskPercent,
- DiskRead,
- DiskWrite,
-
- NetDown,
- NetUp,
-
- LoadAverage1,
- LoadAverage5,
- LoadAverage15,
- Uptime,
-}
-
-impl FromStr for TokenType {
- type Err = Report;
-
- fn from_str(s: &str) -> Result {
- match s {
- "cpu_frequency" => Ok(Self::CpuFrequency),
- "cpu_percent" => Ok(Self::CpuPercent),
-
- "memory_free" => Ok(Self::MemoryFree),
- "memory_available" => Ok(Self::MemoryAvailable),
- "memory_total" => Ok(Self::MemoryTotal),
- "memory_used" => Ok(Self::MemoryUsed),
- "memory_percent" => Ok(Self::MemoryPercent),
-
- "swap_free" => Ok(Self::SwapFree),
- "swap_total" => Ok(Self::SwapTotal),
- "swap_used" => Ok(Self::SwapUsed),
- "swap_percent" => Ok(Self::SwapPercent),
-
- "temp_c" => Ok(Self::TempC),
- "temp_f" => Ok(Self::TempF),
-
- "disk_free" => Ok(Self::DiskFree),
- "disk_total" => Ok(Self::DiskTotal),
- "disk_used" => Ok(Self::DiskUsed),
- "disk_percent" => Ok(Self::DiskPercent),
- "disk_read" => Ok(Self::DiskRead),
- "disk_write" => Ok(Self::DiskWrite),
-
- "net_down" => Ok(Self::NetDown),
- "net_up" => Ok(Self::NetUp),
-
- "load_average_1" => Ok(Self::LoadAverage1),
- "load_average_5" => Ok(Self::LoadAverage5),
- "load_average_15" => Ok(Self::LoadAverage15),
- "uptime" => Ok(Self::Uptime),
- _ => Err(Report::msg(format!("invalid token type: '{s}'"))),
- }
- }
-}
-
-#[cfg(feature = "ipc")]
-use crate::ironvar::Namespace;
-
-#[cfg(feature = "ipc")]
-impl Namespace for Client {
- fn get(&self, key: &str) -> Option {
- let get = |value: Value| Some(value.get(Prefix::None).to_string());
-
- let token = TokenType::from_str(key).ok()?;
- match token {
- TokenType::CpuFrequency => None,
- TokenType::CpuPercent => None,
- TokenType::MemoryFree => get(self.memory_free()),
- TokenType::MemoryAvailable => get(self.memory_available()),
- TokenType::MemoryTotal => get(self.memory_total()),
- TokenType::MemoryUsed => get(self.memory_used()),
- TokenType::MemoryPercent => get(self.memory_percent()),
- TokenType::SwapFree => get(self.swap_free()),
- TokenType::SwapTotal => get(self.swap_total()),
- TokenType::SwapUsed => get(self.swap_used()),
- TokenType::SwapPercent => get(self.swap_percent()),
- TokenType::TempC => None,
- TokenType::TempF => None,
- TokenType::DiskFree => None,
- TokenType::DiskTotal => None,
- TokenType::DiskUsed => None,
- TokenType::DiskPercent => None,
- TokenType::DiskRead => None,
- TokenType::DiskWrite => None,
- TokenType::NetDown => None,
- TokenType::NetUp => None,
- TokenType::LoadAverage1 => get(self.load_average_1()),
- TokenType::LoadAverage5 => get(self.load_average_5()),
- TokenType::LoadAverage15 => get(self.load_average_15()),
- TokenType::Uptime => Some(Client::uptime()),
- }
- }
-
- fn list(&self) -> Vec {
- vec![
- "memory_free",
- "memory_available",
- "memory_total",
- "memory_used",
- "memory_percent",
- "swap_free",
- "swap_total",
- "swap_used",
- "swap_percent",
- "load_average_1",
- "load_average_5",
- "load_average_15",
- "uptime",
- ]
- .into_iter()
- .map(ToString::to_string)
- .collect()
- }
-
- fn namespaces(&self) -> Vec {
- vec![
- "cpu_frequency",
- "cpu_percent",
- "temp_c",
- "temp_f",
- "disk_free",
- "disk_total",
- "disk_used",
- "disk_percent",
- "disk_read",
- "disk_write",
- "net_down",
- "net_up",
- ]
- .into_iter()
- .map(ToString::to_string)
- .collect()
- }
-
- fn get_namespace(&self, key: &str) -> Option> {
- let token = TokenType::from_str(key).ok()?;
-
- match token {
- TokenType::CpuFrequency => Some(Arc::new(self.cpu_frequency())),
- TokenType::CpuPercent => Some(Arc::new(self.cpu_percent())),
- TokenType::MemoryFree => None,
- TokenType::MemoryAvailable => None,
- TokenType::MemoryTotal => None,
- TokenType::MemoryUsed => None,
- TokenType::MemoryPercent => None,
- TokenType::SwapFree => None,
- TokenType::SwapTotal => None,
- TokenType::SwapUsed => None,
- TokenType::SwapPercent => None,
- TokenType::TempC => Some(Arc::new(self.temp_c())),
- TokenType::TempF => Some(Arc::new(self.temp_f())),
- TokenType::DiskFree => Some(Arc::new(self.disk_free())),
- TokenType::DiskTotal => Some(Arc::new(self.disk_total())),
- TokenType::DiskUsed => Some(Arc::new(self.disk_used())),
- TokenType::DiskPercent => Some(Arc::new(self.disk_percent())),
- TokenType::DiskRead => Some(Arc::new(self.disk_read(Interval::All(1)))),
- TokenType::DiskWrite => Some(Arc::new(self.disk_write(Interval::All(1)))),
- TokenType::NetDown => Some(Arc::new(self.net_down(Interval::All(1)))),
- TokenType::NetUp => Some(Arc::new(self.net_up(Interval::All(1)))),
- TokenType::LoadAverage1 => None,
- TokenType::LoadAverage5 => None,
- TokenType::LoadAverage15 => None,
- TokenType::Uptime => None,
- }
- }
-}
-
-#[cfg(feature = "ipc")]
-impl Namespace for ValueSet {
- fn get(&self, key: &str) -> Option {
- let function = Function::from_str(key).ok()?;
- Some(self.apply(&function, Prefix::None).to_string())
- }
-
- fn list(&self) -> Vec {
- let mut vec = vec!["sum", "min", "max", "mean"]
- .into_iter()
- .map(ToString::to_string)
- .collect::>();
-
- vec.extend(self.values.keys().map(ToString::to_string));
- vec
- }
-
- fn namespaces(&self) -> Vec {
- vec![]
- }
-
- fn get_namespace(&self, _key: &str) -> Option> {
- None
- }
-}
diff --git a/src/clients/upower.rs b/src/clients/upower.rs
new file mode 100644
index 0000000..f3cade1
--- /dev/null
+++ b/src/clients/upower.rs
@@ -0,0 +1,35 @@
+use crate::register_client;
+use std::sync::Arc;
+use upower_dbus::UPowerProxy;
+use zbus::fdo::PropertiesProxy;
+
+pub async fn create_display_proxy() -> Arc> {
+ let dbus = Box::pin(zbus::Connection::system())
+ .await
+ .expect("failed to create connection to system bus");
+
+ let device_proxy = UPowerProxy::new(&dbus)
+ .await
+ .expect("failed to create upower proxy");
+
+ let display_device = device_proxy
+ .get_display_device()
+ .await
+ .unwrap_or_else(|_| panic!("failed to get display device for {device_proxy:?}"));
+
+ let path = display_device.path().to_owned();
+
+ let proxy = PropertiesProxy::builder(&dbus)
+ .destination("org.freedesktop.UPower")
+ .expect("failed to set proxy destination address")
+ .path(path)
+ .expect("failed to set proxy path")
+ .cache_properties(zbus::CacheProperties::No)
+ .build()
+ .await
+ .expect("failed to build proxy");
+
+ Arc::new(proxy)
+}
+
+register_client!(PropertiesProxy<'static>, upower);
diff --git a/src/clients/upower/dbus.rs b/src/clients/upower/dbus.rs
deleted file mode 100644
index c3733d6..0000000
--- a/src/clients/upower/dbus.rs
+++ /dev/null
@@ -1,159 +0,0 @@
-/// Originally taken from `upower-dbus` crate
-///
-// Copyright 2021 System76