From 34ff5086fd54ef36ab19d1b3d5bebd1f43397801 Mon Sep 17 00:00:00 2001 From: Dusan Date: Tue, 11 Feb 2025 11:33:15 +0100 Subject: [PATCH 1/2] feat(power): implement periodic update for connected devices Signed-off-by: Dusan --- Cargo.lock | 11 ++++ cosmic-settings/Cargo.toml | 1 + .../src/pages/power/backend/mod.rs | 54 +++++++++++++++++- cosmic-settings/src/pages/power/mod.rs | 55 +++++++++++++++++++ 4 files changed, 118 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 147af08d..b875b2ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -460,6 +460,16 @@ dependencies = [ "slab", ] +[[package]] +name = "async-fn-stream" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e71711442f1016c768c259bec59300a10efe753bc3e686ec19e2c6a54a97c29b" +dependencies = [ + "futures-util", + "pin-project-lite", +] + [[package]] name = "async-fs" version = "2.1.2" @@ -1624,6 +1634,7 @@ dependencies = [ "as-result", "ashpd 0.9.2", "async-channel", + "async-fn-stream", "bluez-zbus", "chrono", "clap", diff --git a/cosmic-settings/Cargo.toml b/cosmic-settings/Cargo.toml index a02f14e4..67f739f5 100644 --- a/cosmic-settings/Cargo.toml +++ b/cosmic-settings/Cargo.toml @@ -74,6 +74,7 @@ rustix = "0.38.41" gettext-rs = { version = "0.7.2", features = [ "gettext-system", ], optional = true } +async-fn-stream = "0.2.2" [dependencies.cosmic-settings-subscriptions] git = "https://github.com/pop-os/cosmic-settings-subscriptions" diff --git a/cosmic-settings/src/pages/power/backend/mod.rs b/cosmic-settings/src/pages/power/backend/mod.rs index b6d60d78..a5359f3d 100644 --- a/cosmic-settings/src/pages/power/backend/mod.rs +++ b/cosmic-settings/src/pages/power/backend/mod.rs @@ -1,8 +1,7 @@ use chrono::{Duration, TimeDelta}; -use futures::future::join_all; -use futures::FutureExt; +use futures::{future::join_all, FutureExt, Stream, StreamExt}; use upower_dbus::{BatteryState, BatteryType, DeviceProxy}; -use zbus::Connection; +use zbus::{zvariant::ObjectPath, Connection}; mod ppdaemon; mod s76powerdaemon; @@ -239,6 +238,7 @@ pub struct ConnectedDevice { pub model: String, pub device_icon: &'static str, pub battery: Battery, + pub device_path: String, } async fn get_device_proxy<'a>() -> Result, zbus::Error> { @@ -406,6 +406,7 @@ impl Battery { impl ConnectedDevice { async fn from_device_maybe(proxy: DeviceProxy<'_>) -> Option { let device_type = proxy.type_().await.unwrap_or(BatteryType::Unknown); + let device_path = proxy.clone().into_inner().path().to_string(); if matches!( device_type, BatteryType::Unknown | BatteryType::LinePower | BatteryType::Battery @@ -445,9 +446,56 @@ impl ConnectedDevice { model, device_icon, battery, + device_path, }) } + pub async fn device_removed_stream( + connection: &'_ Connection, + ) -> Result + '_, zbus::Error> { + let proxy = upower_dbus::UPowerProxy::new(connection).await?; + let stream = proxy.receive_device_removed().await?; + + let transformed_stream = stream.filter_map(move |device_removed| async move { + let device_path: ObjectPath<'static> = match device_removed.args() { + Ok(args) => args.device().to_owned(), + Err(e) => { + tracing::error!("Failed to get DeviceRemoved arguments: {e}"); + return None; + } + }; + Some(device_path.to_string()) + }); + + Ok(transformed_stream) + } + + pub async fn device_added_stream<'a>( + connection: &'_ Connection, + ) -> Result + '_, zbus::Error> { + let proxy = upower_dbus::UPowerProxy::new(connection).await?; + let stream = proxy.receive_device_added().await?; + + let transformed_stream = stream.filter_map(move |device_added| async move { + let device_path: ObjectPath<'static> = match device_added.args() { + Ok(args) => args.device().to_owned(), + Err(e) => { + tracing::error!("Failed to get DeviceAdded arguments: {e}"); + return None; + } + }; + match DeviceProxy::new(connection, &device_path).await { + Ok(device) => ConnectedDevice::from_device_maybe(device).await, + Err(e) => { + tracing::error!("Failed to create DeviceProxy from {device_path}: {e}"); + None + } + } + }); + + Ok(transformed_stream) + } + pub async fn update_connected_devices() -> Vec { let proxy = enumerate_devices().await; diff --git a/cosmic-settings/src/pages/power/mod.rs b/cosmic-settings/src/pages/power/mod.rs index aef47233..7fe8e06c 100644 --- a/cosmic-settings/src/pages/power/mod.rs +++ b/cosmic-settings/src/pages/power/mod.rs @@ -12,6 +12,7 @@ use cosmic::Task; use cosmic_config::{Config, CosmicConfigEntry}; use cosmic_idle_config::CosmicIdleConfig; use cosmic_settings_page::{self as page, section, Section}; +use futures::StreamExt; use itertools::Itertools; use slab::Slab; use slotmap::SlotMap; @@ -123,6 +124,52 @@ impl page::Page for Page { let devices = ConnectedDevice::update_connected_devices().await; Message::UpdateConnectedDevices(devices) }), + cosmic::Task::run( + async_fn_stream::fn_stream(|emitter| async move { + let span = tracing::span!(tracing::Level::INFO, "power::device_stream task"); + let _span_handle = span.enter(); + + let Ok(connection) = zbus::Connection::system().await else { + tracing::error!("could not established zbus connection to system"); + return; + }; + + let added_stream = ConnectedDevice::device_added_stream(&connection).await; + let removed_stream = ConnectedDevice::device_removed_stream(&connection).await; + + let added_future = async { + match added_stream { + Ok(stream) => { + futures::pin_mut!(stream); + while let Some(device) = stream.next().await { + tracing::info!(device = device.model, "device added"); + emitter.emit(Message::DeviceConnect(device)).await; + } + } + Err(err) => tracing::error!(?err, "cannot establish added stream"), + } + }; + + let removed_future = async { + match removed_stream { + Ok(stream) => { + futures::pin_mut!(stream); + while let Some(device_path) = stream.next().await { + tracing::info!(device_path, "device removed"); + emitter.emit(Message::DeviceDisconnect(device_path)).await; + } + } + Err(err) => tracing::error!(?err, "cannot establish removed stream"), + } + }; + + futures::pin_mut!(added_future); + futures::pin_mut!(removed_future); + + futures::future::select(added_future, removed_future).await; + }), + |msg| msg, + ), ]; let (task, handle) = cosmic::Task::batch(futures) @@ -147,6 +194,8 @@ pub enum Message { PowerProfileChange(PowerProfile), UpdateBattery(Battery), UpdateConnectedDevices(Vec), + DeviceDisconnect(String), + DeviceConnect(ConnectedDevice), ScreenOffTimeChange(Option), SuspendOnAcTimeChange(Option), SuspendOnBatteryTimeChange(Option), @@ -192,6 +241,12 @@ impl Page { tracing::error!("failed to set suspend on battery time: {}", err) } } + Message::DeviceDisconnect(device_path) => self + .connected_devices + .retain(|device| device.device_path != device_path), + Message::DeviceConnect(connected_device) => { + self.connected_devices.push(connected_device) + } }; } } From e22fa27f01192270a286bee8a6d8ffa03eb5b958 Mon Sep 17 00:00:00 2001 From: Dusan Date: Wed, 12 Feb 2025 16:33:18 +0100 Subject: [PATCH 2/2] remove leftover lifetime parameter Signed-off-by: Dusan --- cosmic-settings/src/pages/power/backend/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cosmic-settings/src/pages/power/backend/mod.rs b/cosmic-settings/src/pages/power/backend/mod.rs index a5359f3d..7ee01d55 100644 --- a/cosmic-settings/src/pages/power/backend/mod.rs +++ b/cosmic-settings/src/pages/power/backend/mod.rs @@ -470,7 +470,7 @@ impl ConnectedDevice { Ok(transformed_stream) } - pub async fn device_added_stream<'a>( + pub async fn device_added_stream( connection: &'_ Connection, ) -> Result + '_, zbus::Error> { let proxy = upower_dbus::UPowerProxy::new(connection).await?;