diff --git a/Cargo.lock b/Cargo.lock index 1cc5689..3683fb8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -821,6 +821,7 @@ dependencies = [ "pwd", "shlex", "tokio", + "upower_dbus", "wayland-client 0.31.1", "zbus", ] @@ -4271,6 +4272,16 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +[[package]] +name = "upower_dbus" +version = "0.3.2" +source = "git+https://github.com/pop-os/dbus-settings-bindings#3644bc909984842f35adb1057ef6e0a277c1aa6a" +dependencies = [ + "serde", + "serde_repr", + "zbus", +] + [[package]] name = "url" version = "2.5.0" @@ -4726,7 +4737,7 @@ dependencies = [ "js-sys", "log", "naga", - "parking_lot 0.12.1", + "parking_lot 0.11.2", "profiling", "raw-window-handle", "smallvec", @@ -4751,7 +4762,7 @@ dependencies = [ "codespan-reporting", "log", "naga", - "parking_lot 0.12.1", + "parking_lot 0.11.2", "profiling", "raw-window-handle", "rustc-hash", @@ -4791,7 +4802,7 @@ dependencies = [ "naga", "objc", "once_cell", - "parking_lot 0.12.1", + "parking_lot 0.11.2", "profiling", "range-alloc", "raw-window-handle", diff --git a/Cargo.toml b/Cargo.toml index 5319d83..3befaf5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,8 @@ wayland-client = "0.31.1" cosmic-dbus-networkmanager = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true } # For logind integration using logind feature logind-zbus = { version = "3.1.2", optional = true } +# For power status with upower feature +upower_dbus = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true } # Required for some features zbus = { version = "3.14.1", optional = true } @@ -39,6 +41,7 @@ version = "1.33.0" features = ["full"] [features] -default = ["logind", "networkmanager"] +default = ["logind", "networkmanager", "upower"] logind = ["logind-zbus", "zbus"] networkmanager = ["cosmic-dbus-networkmanager", "zbus"] +upower = ["upower_dbus", "zbus"] diff --git a/src/locker.rs b/src/locker.rs index 2d2f6b2..000c7dd 100644 --- a/src/locker.rs +++ b/src/locker.rs @@ -25,6 +25,7 @@ use std::{ ffi::{CStr, CString}, fs, path::Path, + process, }; use tokio::{sync::mpsc, task, time}; use wayland_client::{protocol::wl_output::WlOutput, Proxy}; @@ -195,6 +196,7 @@ pub enum Message { Channel(mpsc::Sender), BackgroundState(cosmic_bg_config::state::State), NetworkIcon(Option<&'static str>), + PowerInfo(Option<(String, f64)>), Prompt(String, bool, Option), Submit, Suspend, @@ -212,6 +214,7 @@ pub struct App { surface_names: HashMap, text_input_ids: HashMap, network_icon_opt: Option<&'static str>, + power_info_opt: Option<(String, f64)>, value_tx_opt: Option>, prompt_opt: Option<(String, bool, Option)>, error_opt: Option, @@ -305,6 +308,7 @@ impl cosmic::Application for App { surface_names: HashMap::new(), text_input_ids: HashMap::new(), network_icon_opt: None, + power_info_opt: None, value_tx_opt: None, prompt_opt: None, error_opt: None, @@ -388,7 +392,8 @@ impl cosmic::Application for App { } } SessionLockEvent::Unlocked => { - return iced::window::close(SurfaceId::MAIN); + //TODO: cleaner method to exit? + process::exit(0); } _ => {} }, @@ -403,6 +408,9 @@ impl cosmic::Application for App { Message::NetworkIcon(network_icon_opt) => { self.network_icon_opt = network_icon_opt; } + Message::PowerInfo(power_info_opt) => { + self.power_info_opt = power_info_opt; + } Message::Prompt(prompt, secret, value_opt) => { let prompt_was_none = self.prompt_opt.is_none(); self.prompt_opt = Some((prompt, secret, value_opt)); @@ -501,11 +509,12 @@ impl cosmic::Application for App { status_row = status_row.push(widget::icon::from_name(network_icon)); } - //TODO: get actual status - status_row = status_row.push(iced::widget::row![ - widget::icon::from_name("battery-level-50-symbolic"), - widget::text("50%"), - ]); + if let Some((power_icon, power_percent)) = &self.power_info_opt { + status_row = status_row.push(iced::widget::row![ + widget::icon::from_name(power_icon.clone()), + widget::text(format!("{:.0}%", power_percent)), + ]); + } //TODO: implement these buttons let button_row = iced::widget::row![ @@ -651,16 +660,22 @@ impl cosmic::Application for App { struct PamSubscription; //TODO: just use one vec for all subscriptions - let mut network_subscriptions = Vec::with_capacity(1); + let mut extra_suscriptions = Vec::with_capacity(1); #[cfg(feature = "networkmanager")] { - network_subscriptions.push( + extra_suscriptions.push( crate::networkmanager::subscription() - .map(|network_icon_opt| Message::NetworkIcon(network_icon_opt)), + .map(|icon_opt| Message::NetworkIcon(icon_opt)), ); } + #[cfg(feature = "upower")] + { + extra_suscriptions + .push(crate::upower::subscription().map(|info_opt| Message::PowerInfo(info_opt))); + } + //TODO: how to avoid cloning this on every time subscription is called? let username = self.flags.current_user.name.clone(); Subscription::batch([ @@ -736,7 +751,7 @@ impl cosmic::Application for App { } }, ), - Subscription::batch(network_subscriptions), + Subscription::batch(extra_suscriptions), ]) } } diff --git a/src/main.rs b/src/main.rs index 88cee55..5c8f026 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,6 +11,9 @@ mod logind; #[cfg(feature = "networkmanager")] mod networkmanager; +#[cfg(feature = "upower")] +mod upower; + fn main() -> Result<(), Box> { env_logger::init(); diff --git a/src/networkmanager.rs b/src/networkmanager.rs index e19f638..bf1b672 100644 --- a/src/networkmanager.rs +++ b/src/networkmanager.rs @@ -1,5 +1,5 @@ use cosmic::iced::{ - futures::{channel::mpsc, SinkExt}, + futures::{channel::mpsc, SinkExt, StreamExt}, subscription, Subscription, }; use cosmic_dbus_networkmanager::{device::SpecificDevice, nm::NetworkManager}; @@ -65,7 +65,7 @@ pub async fn handler(msg_tx: &mut mpsc::Sender>) -> Result< let zbus = Connection::system().await?; let nm = NetworkManager::new(&zbus).await?; - //TOOD: use receive_active_connections_changed + let mut active_conns_changed = nm.receive_active_connections_changed().await; loop { let mut icon = NetworkIcon::None; @@ -100,7 +100,10 @@ pub async fn handler(msg_tx: &mut mpsc::Sender>) -> Result< msg_tx.send(Some(icon.name())).await.unwrap(); - //TODO: select best timeout - time::sleep(time::Duration::new(5, 0)).await; + // Waits until active connections have changed and at least one second has passed + tokio::join!( + active_conns_changed.next(), + time::sleep(time::Duration::from_secs(1)) + ); } } diff --git a/src/upower.rs b/src/upower.rs new file mode 100644 index 0000000..a9adc59 --- /dev/null +++ b/src/upower.rs @@ -0,0 +1,60 @@ +use cosmic::iced::{ + futures::{channel::mpsc, SinkExt, StreamExt}, + subscription, Subscription, +}; +use std::any::TypeId; +use tokio::time; +use upower_dbus::UPowerProxy; +use zbus::{Connection, Result}; + +pub fn subscription() -> Subscription> { + struct PowerSubscription; + + subscription::channel( + TypeId::of::(), + 16, + |mut msg_tx| async move { + match handler(&mut msg_tx).await { + Ok(()) => {} + Err(err) => { + log::warn!("upower error: {}", err); + //TODO: send error + } + } + + // If reading power status failed, clear power 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 upower = UPowerProxy::new(&zbus).await?; + let dev = upower.get_display_device().await?; + + let mut icon_name_changed = dev.receive_icon_name_changed().await; + let mut percentage_changed = dev.receive_percentage_changed().await; + loop { + let mut info_opt = None; + + if let Ok(percent) = dev.percentage().await { + if let Ok(icon_name) = dev.icon_name().await { + if !icon_name.is_empty() { + info_opt = Some((icon_name, percent)); + } + } + } + + msg_tx.send(info_opt).await.unwrap(); + + // Waits until icon or percentage have changed + tokio::select!(_ = icon_name_changed.next() => (), _ = percentage_changed.next() => ()); + } +}