From 0600931abf1e7ab019ccbefea1b6dd8d8377c8be Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Thu, 10 Apr 2025 13:03:53 +0200 Subject: [PATCH] refactor: improve and fix subscriptions and async logic --- Cargo.lock | 107 +++++-------- Cargo.toml | 8 +- cosmic-greeter-config/src/lib.rs | 2 +- src/greeter.rs | 124 ++++++++------- src/greeter/ipc.rs | 13 +- src/locker.rs | 262 +++++++++++++++++-------------- src/logind.rs | 9 +- src/networkmanager.rs | 18 +-- src/upower.rs | 16 +- 9 files changed, 290 insertions(+), 269 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6204b67..8c04323 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -141,12 +141,6 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" -[[package]] -name = "allocator-api2" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" - [[package]] name = "almost" version = "0.2.0" @@ -728,39 +722,6 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" -[[package]] -name = "cached" -version = "0.55.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0839c297f8783316fcca9d90344424e968395413f0662a5481f79c6648bbc14" -dependencies = [ - "ahash", - "cached_proc_macro", - "cached_proc_macro_types", - "hashbrown 0.14.5", - "once_cell", - "thiserror 2.0.12", - "web-time", -] - -[[package]] -name = "cached_proc_macro" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "673992d934f0711b68ebb3e1b79cdc4be31634b37c98f26867ced0438ca5c603" -dependencies = [ - "darling 0.20.10", - "proc-macro2", - "quote", - "syn 2.0.100", -] - -[[package]] -name = "cached_proc_macro_types" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade8366b8bd5ba243f0a58f036cc0ca8a2f069cff1a2351ef1cac6b083e16fc0" - [[package]] name = "calloop" version = "0.13.0" @@ -1061,7 +1022,7 @@ dependencies = [ [[package]] name = "cosmic-bg-config" version = "0.1.0" -source = "git+https://github.com/pop-os/cosmic-bg#b6adf25075383c0e606658a7309919a9b092ee54" +source = "git+https://github.com/pop-os/cosmic-bg#d41c8506ed5c44afd51f74bdb56f620e1dec1ffc" dependencies = [ "colorgrad", "cosmic-config", @@ -1087,7 +1048,7 @@ dependencies = [ [[package]] name = "cosmic-comp-config" version = "0.1.0" -source = "git+https://github.com/pop-os/cosmic-comp#29a649541d527021e98090f2ccd486cf21335dab" +source = "git+https://github.com/pop-os/cosmic-comp#99bbd10168aed50a24db730cf20eb778e072c5e4" dependencies = [ "cosmic-config", "input", @@ -1097,7 +1058,7 @@ dependencies = [ [[package]] name = "cosmic-config" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic#0aa518984ec6bb29c368b879c20b971f04fbc0c7" +source = "git+https://github.com/pop-os/libcosmic#0ddde755ee922edcff1a3b11cc00753cb29fe3b0" dependencies = [ "atomicwrites", "calloop 0.14.2", @@ -1120,7 +1081,7 @@ dependencies = [ [[package]] name = "cosmic-config-derive" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic#0aa518984ec6bb29c368b879c20b971f04fbc0c7" +source = "git+https://github.com/pop-os/libcosmic#0ddde755ee922edcff1a3b11cc00753cb29fe3b0" dependencies = [ "quote", "syn 1.0.109", @@ -1165,7 +1126,7 @@ dependencies = [ "cosmic-dbus-networkmanager", "cosmic-greeter-config", "cosmic-greeter-daemon", - "dirs 5.0.1", + "dirs 6.0.0", "env_logger", "freedesktop_entry_parser", "futures-util", @@ -1178,7 +1139,7 @@ dependencies = [ "nix 0.29.0", "pam-client", "pwd", - "ron 0.8.1", + "ron 0.10.1", "rust-embed", "shlex", "tokio", @@ -1212,7 +1173,7 @@ dependencies = [ "log", "nix 0.29.0", "pwd", - "ron 0.8.1", + "ron 0.10.1", "serde", "tokio", "zbus 4.4.0", @@ -1738,7 +1699,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -2017,16 +1978,15 @@ dependencies = [ [[package]] name = "freedesktop-desktop-entry" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e5e2bd2e383df08a8439c2e096be16d5355aa00f976b295cf8e077ea5953d5d" +checksum = "2258b98a780699da05c858682498ceaf3942013d7d93ca7584e26fbacc58f2d9" dependencies = [ - "cached", "gettext-rs", "log", "memchr", - "strsim 0.11.1", "thiserror 2.0.12", + "unicase", "xdg", ] @@ -2386,10 +2346,6 @@ name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", - "allocator-api2", -] [[package]] name = "hashbrown" @@ -2580,7 +2536,7 @@ dependencies = [ [[package]] name = "iced_core" version = "0.14.0-dev" -source = "git+https://github.com/pop-os/libcosmic#0aa518984ec6bb29c368b879c20b971f04fbc0c7" +source = "git+https://github.com/pop-os/libcosmic#0ddde755ee922edcff1a3b11cc00753cb29fe3b0" dependencies = [ "bitflags 2.9.0", "bytes", @@ -2604,7 +2560,7 @@ dependencies = [ [[package]] name = "iced_futures" version = "0.14.0-dev" -source = "git+https://github.com/pop-os/libcosmic#0aa518984ec6bb29c368b879c20b971f04fbc0c7" +source = "git+https://github.com/pop-os/libcosmic#0ddde755ee922edcff1a3b11cc00753cb29fe3b0" dependencies = [ "futures", "iced_core", @@ -3049,7 +3005,7 @@ checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ "hermit-abi 0.5.0", "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -3258,7 +3214,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-targets 0.48.5", ] [[package]] @@ -3696,7 +3652,7 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ - "proc-macro-crate 3.3.0", + "proc-macro-crate 1.3.1", "proc-macro2", "quote", "syn 2.0.100", @@ -4648,6 +4604,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "ron" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "beceb6f7bf81c73e73aeef6dd1356d9a1b2b4909e1f0fc3e59b034f9572d7b7f" +dependencies = [ + "base64 0.22.1", + "bitflags 2.9.0", + "serde", + "serde_derive", + "unicode-ident", +] + [[package]] name = "roxmltree" version = "0.20.0" @@ -4742,7 +4711,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -4755,7 +4724,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.9.3", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -5220,7 +5189,7 @@ dependencies = [ "getrandom 0.3.2", "once_cell", "rustix 1.0.3", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -5371,9 +5340,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.44.1" +version = "1.44.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f382da615b842244d4b8738c82ed1275e6c5dd90c459a30941cd07080b06c91a" +checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" dependencies = [ "backtrace", "bytes", @@ -5554,6 +5523,12 @@ dependencies = [ "tinystr", ] +[[package]] +name = "unicase" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" + [[package]] name = "unicode-bidi" version = "0.3.18" @@ -6106,7 +6081,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.48.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 0a6ed54..21f3500 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ cosmic-comp-config.workspace = true cosmic-config = { workspace = true, features = ["calloop", "macro"] } cosmic-greeter-config.workspace = true cosmic-greeter-daemon = { path = "daemon" } -dirs = "5" +dirs = "6" env_logger.workspace = true freedesktop_entry_parser = "1.3.0" libcosmic = { workspace = true, features = [ @@ -34,7 +34,7 @@ xkb-data = "0.2" xdg = "2.5.2" #TODO: reduce features tokio = { workspace = true, features = ["full"] } -wayland-client = "0.31.5" +wayland-client = "0.31.8" # For network status using networkmanager feature cosmic-dbus-networkmanager = { git = "https://github.com/pop-os/dbus-settings-bindings", rev = "badfc6a", optional = true } # For logind integration using logind feature @@ -81,7 +81,7 @@ members = ["cosmic-greeter-config", "daemon"] resolver = "2" [workspace.package] -rust-version = "1.79.0" +rust-version = "1.85.0" [workspace.dependencies] env_logger = "0.10.2" @@ -89,7 +89,7 @@ log = "0.4.22" # Fix zbus compilation by manually adding nix with user feature nix = { version = "0.29", features = ["user"] } pwd = "1.4.0" -ron = "0.8" +ron = "0.10.1" serde = "1" tokio = "1.39.1" zbus = "4" diff --git a/cosmic-greeter-config/src/lib.rs b/cosmic-greeter-config/src/lib.rs index 7f52375..0327d08 100644 --- a/cosmic-greeter-config/src/lib.rs +++ b/cosmic-greeter-config/src/lib.rs @@ -33,7 +33,7 @@ where Ok(handler) => { let config = C::get_entry(&handler) .inspect_err(|(errors, _)| { - for err in errors { + for err in errors.iter().filter(|err| err.is_err()) { log::error!("{err}") } }) diff --git a/src/greeter.rs b/src/greeter.rs index e1a9709..7ca4731 100644 --- a/src/greeter.rs +++ b/src/greeter.rs @@ -6,37 +6,37 @@ mod ipc; use cosmic::app::{Core, Settings, Task}; use cosmic::cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity; use cosmic::iced::{Point, Size}; -use cosmic::iced_core::{image, window}; +use cosmic::iced_core::image; use cosmic::iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings; use cosmic::surface; use cosmic::widget::text; use cosmic::{ + Element, cosmic_config::{self, ConfigSet, CosmicConfigEntry}, executor, iced::{ - self, alignment, + self, Background, Border, Length, Subscription, alignment, event::{ self, - wayland::{Event as WaylandEvent, LayerEvent, OutputEvent}, + wayland::{Event as WaylandEvent, OutputEvent}, }, futures::SinkExt, platform_specific::{ runtime::wayland::layer_surface::{IcedMargin, IcedOutput, SctkLayerSurfaceSettings}, shell::wayland::commands::layer_surface::{ - destroy_layer_surface, get_layer_surface, Anchor, KeyboardInteractivity, Layer, + Anchor, KeyboardInteractivity, Layer, destroy_layer_surface, get_layer_surface, }, }, - Background, Border, Length, Subscription, }, iced_runtime::core::window::Id as SurfaceId, - style, theme, widget, Element, + style, theme, widget, }; use cosmic_comp_config::CosmicCompConfig; use cosmic_greeter_config::Config as CosmicGreeterConfig; use cosmic_greeter_daemon::{UserData, WallpaperData}; use greetd_ipc::Request; use std::{ - collections::{hash_map, HashMap}, + collections::{HashMap, hash_map}, error::Error, fs, io, num::NonZeroU32, @@ -46,8 +46,8 @@ use std::{ time::{Duration, Instant}, }; use tokio::time; -use wayland_client::{protocol::wl_output::WlOutput, Proxy}; -use zbus::{proxy, Connection}; +use wayland_client::{Proxy, protocol::wl_output::WlOutput}; +use zbus::{Connection, proxy}; use crate::fl; @@ -407,7 +407,6 @@ pub enum Message { KeyboardLayout(usize), Login, NetworkIcon(Option<&'static str>), - None, OutputEvent(OutputEvent, WlOutput), PowerInfo(Option<(String, f64)>), Prompt(String, bool, Option), @@ -444,10 +443,11 @@ pub struct App { dialog_page_opt: Option, dropdown_opt: Option, window_size: HashMap, + heartbeat_handle: Option, } impl App { - fn menu<'a>(&'a self, id: SurfaceId) -> Element<'a, Message> { + fn menu(&self, id: SurfaceId) -> Element { let left_element = { let date_time_column = { let mut column = widget::column::with_capacity(2).padding(16.0).spacing(12.0); @@ -666,7 +666,7 @@ impl App { } SocketState::Open => { for user_data in &self.flags.user_datas { - if &user_data.name == &self.selected_username.username { + if user_data.name == self.selected_username.username { match &user_data.icon_opt { Some(icon) => { column = column.push( @@ -832,16 +832,13 @@ impl App { } } /// Send a [`Request`] to the greetd IPC subscription. - fn send_request(&self, request: Request) -> Task { + fn send_request(&self, request: Request) { if let Some(ref sender) = self.greetd_sender { let sender = sender.clone(); - return cosmic::task::future(async move { + tokio::task::spawn(async move { _ = sender.send(request).await; - cosmic::action::none() }); } - - Task::none() } fn set_xkb_config(&self) { @@ -1073,6 +1070,7 @@ impl cosmic::Application for App { dialog_page_opt: None, dropdown_opt: None, window_size: HashMap::new(), + heartbeat_handle: None, }; (app, Task::none()) } @@ -1080,7 +1078,6 @@ impl cosmic::Application for App { /// Handle application events here. fn update(&mut self, message: Self::Message) -> Task { match message { - Message::None => {} Message::OutputEvent(output_event, output) => { match output_event { OutputEvent::Created(output_info_opt) => { @@ -1213,7 +1210,7 @@ impl cosmic::Application for App { match &self.socket_state { SocketState::Open => { // When socket is opened, send create session - return self.send_request(Request::CreateSession { + self.send_request(Request::CreateSession { username: self.selected_username.username.clone(), }); } @@ -1278,7 +1275,7 @@ impl cosmic::Application for App { match &self.socket_state { SocketState::Open => { self.prompt_opt = None; - return self.send_request(Request::CancelSession); + self.send_request(Request::CancelSession); } _ => {} } @@ -1351,55 +1348,56 @@ impl cosmic::Application for App { Message::Auth(response) => { self.prompt_opt = None; self.error_opt = None; - return self.send_request(Request::PostAuthMessageResponse { response }); + self.send_request(Request::PostAuthMessageResponse { response }); } Message::Login => { self.prompt_opt = None; self.error_opt = None; match self.flags.sessions.get(&self.selected_session).cloned() { Some((cmd, env)) => { - return Task::batch([ - self.update(Message::ConfigUpdateUser), - self.send_request(Request::StartSession { cmd, env }), - ]); + self.send_request(Request::StartSession { cmd, env }); + return self.update(Message::ConfigUpdateUser); } None => todo!("session {:?} not found", self.selected_session), } } Message::Error(error) => { self.error_opt = Some(error); - return self.send_request(Request::CancelSession); + self.send_request(Request::CancelSession); } Message::Reconnect => { return self.update_user_config(); } Message::DialogCancel => { self.dialog_page_opt = None; + if let Some(handle) = self.heartbeat_handle.take() { + handle.abort(); + } } Message::DialogConfirm => match self.dialog_page_opt.take() { Some(DialogPage::Restart(_)) => { #[cfg(feature = "logind")] - return cosmic::task::future(async move { + return cosmic::task::future::<(), ()>(async move { match crate::logind::reboot().await { Ok(()) => (), Err(err) => { log::error!("failed to reboot: {:?}", err); } } - cosmic::action::none() - }); + }) + .discard(); } Some(DialogPage::Shutdown(_)) => { #[cfg(feature = "logind")] - return cosmic::task::future(async move { + return cosmic::task::future::<(), ()>(async move { match crate::logind::power_off().await { Ok(()) => (), Err(err) => { log::error!("failed to power off: {:?}", err); } } - cosmic::action::none() - }); + }) + .discard(); } None => {} }, @@ -1421,21 +1419,46 @@ impl cosmic::Application for App { } Message::Suspend => { #[cfg(feature = "logind")] - return cosmic::task::future(async move { + return cosmic::task::future::<(), ()>(async move { match crate::logind::suspend().await { Ok(()) => (), Err(err) => { log::error!("failed to suspend: {:?}", err); } } - cosmic::action::none() + }) + .discard(); + } + Message::Restart | Message::Shutdown => { + let instant = Instant::now(); + + self.dialog_page_opt = Some(if matches!(message, Message::Restart) { + DialogPage::Restart(instant) + } else { + DialogPage::Shutdown(instant) }); - } - Message::Restart => { - self.dialog_page_opt = Some(DialogPage::Restart(Instant::now())); - } - Message::Shutdown => { - self.dialog_page_opt = Some(DialogPage::Shutdown(Instant::now())); + + if self.heartbeat_handle.is_none() { + let (heartbeat, handle) = cosmic::task::stream( + cosmic::iced_futures::stream::channel(1, |mut msg_tx| async move { + let mut interval = time::interval(Duration::from_secs(1)); + + loop { + // Send heartbeat once a second to update time + msg_tx + .send(cosmic::Action::App(Message::Heartbeat)) + .await + .unwrap(); + + interval.tick().await; + } + }), + ) + .abortable(); + + self.heartbeat_handle = Some(handle); + return heartbeat; + } } Message::Heartbeat => match self.dialog_page_opt { Some(DialogPage::Restart(instant)) | Some(DialogPage::Shutdown(instant)) => { @@ -1500,8 +1523,6 @@ impl cosmic::Application for App { } fn subscription(&self) -> Subscription { - struct HeartbeatSubscription; - let mut subscriptions = vec![ event::listen_with(|event, _, id| match event { iced::Event::PlatformSpecific(iced::event::PlatformSpecific::Wayland( @@ -1516,32 +1537,17 @@ impl cosmic::Application for App { iced::Event::Window(iced::window::Event::Focused) => Some(Message::Focus(id)), _ => None, }), - Subscription::run_with_id( - std::any::TypeId::of::(), - cosmic::iced_futures::stream::channel(16, |mut msg_tx| async move { - loop { - // Send heartbeat once a second to update time - //TODO: only send this when needed - msg_tx.send(Message::Heartbeat).await.unwrap(); - time::sleep(time::Duration::new(1, 0)).await; - } - }), - ), ipc::subscription(), ]; #[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 = "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/greeter/ipc.rs b/src/greeter/ipc.rs index 1751ba2..b58e66d 100644 --- a/src/greeter/ipc.rs +++ b/src/greeter/ipc.rs @@ -6,6 +6,7 @@ use cosmic::iced::Subscription; use futures_util::SinkExt; use greetd_ipc::codec::TokioCodec; use std::sync::Arc; +use std::time::Duration; use tokio::net::UnixStream; use tokio::sync::mpsc; @@ -20,12 +21,15 @@ pub fn subscription() -> Subscription { let socket_path = std::env::var_os("GREETD_SOCK").expect("GREETD_SOCK environment not set"); + let mut interval = tokio::time::interval(Duration::from_secs(1)); + loop { _ = sender.send(Message::Reconnect).await; let mut stream = match UnixStream::connect(&socket_path).await { Ok(stream) => stream, Err(why) => { + log::error!("greetd IPC socket connection failed: {why:?}"); _ = sender.send(Message::Socket(SocketState::Error(Arc::new(why)))); break; @@ -36,7 +40,7 @@ pub fn subscription() -> Subscription { while let Some(request) = rx.recv().await { if let Err(why) = request.write_to(&mut stream).await { - log::error!("error writing to GREETD_SOCK stream: {why}"); + log::error!("error writing to GREETD_SOCK stream: {why:?}"); break; } @@ -85,8 +89,9 @@ pub fn subscription() -> Subscription { "error while cancelling session: {}", description ); + // Reconnect to socket - _ = break + break; } _ => { _ = sender.send(Message::Error(description)).await; @@ -107,6 +112,7 @@ pub fn subscription() -> Subscription { _ = sender.send(Message::Exit).await; } greetd_ipc::Request::CancelSession => { + log::info!("greetd IPC session canceled"); // Reconnect to socket break; } @@ -119,6 +125,9 @@ pub fn subscription() -> Subscription { } } } + + log::info!("reconnecting to greetd IPC socket"); + interval.tick().await; } futures_util::future::pending().await diff --git a/src/locker.rs b/src/locker.rs index 16bc877..d5ec67c 100644 --- a/src/locker.rs +++ b/src/locker.rs @@ -7,9 +7,9 @@ use cosmic::iced::{Point, Rectangle, Size}; use cosmic::iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings; use cosmic::surface; use cosmic::{ - executor, + Element, executor, iced::{ - self, alignment, + self, Length, Subscription, alignment, event::{ self, wayland::{Event as WaylandEvent, OutputEvent, SessionLockEvent}, @@ -18,12 +18,12 @@ use cosmic::{ platform_specific::shell::wayland::commands::session_lock::{ destroy_lock_surface, get_lock_surface, lock, unlock, }, - Length, Subscription, }, iced_runtime::core::window::Id as SurfaceId, - style, widget, Element, + style, widget, }; use cosmic_config::CosmicConfigEntry; +use std::time::Duration; use std::{ any::TypeId, collections::HashMap, @@ -35,8 +35,8 @@ use std::{ process, sync::Arc, }; -use tokio::{sync::mpsc, task, time}; -use wayland_client::{protocol::wl_output::WlOutput, Proxy}; +use tokio::{sync::mpsc, task}; +use wayland_client::{Proxy, protocol::wl_output::WlOutput}; fn lockfile_opt() -> Option { let runtime_dir = dirs::runtime_dir()?; @@ -113,7 +113,7 @@ pub fn pam_thread(username: String, conversation: Conversation) -> Result<(), pa } pub struct Conversation { - msg_tx: futures::channel::mpsc::Sender, + msg_tx: futures::channel::mpsc::Sender>, value_rx: mpsc::Receiver, } @@ -127,24 +127,20 @@ impl Conversation { log::error!("failed to convert prompt to UTF-8: {:?}", err); pam_client::ErrorCode::CONV_ERR })?; - let runtime = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .unwrap(); - runtime - .block_on(async { - self.msg_tx - .send(Message::Prompt( - prompt.to_string(), - secret, - Some(String::new()), - )) - .await - }) - .map_err(|err| { - log::error!("failed to send prompt: {:?}", err); - pam_client::ErrorCode::CONV_ERR - })?; + + futures::executor::block_on(async { + self.msg_tx + .send(cosmic::Action::App(Message::Prompt( + prompt.to_string(), + secret, + Some(String::new()), + ))) + .await + }) + .map_err(|err| { + log::error!("failed to send prompt: {:?}", err); + pam_client::ErrorCode::CONV_ERR + })?; let value = self.value_rx.blocking_recv().ok_or_else(|| { log::error!("failed to receive value: channel closed"); @@ -165,7 +161,11 @@ impl Conversation { futures::executor::block_on(async { self.msg_tx - .send(Message::Prompt(prompt.to_string(), false, None)) + .send(cosmic::Action::App(Message::Prompt( + prompt.to_string(), + false, + None, + ))) .await }) .map_err(|err| { @@ -238,11 +238,23 @@ pub enum Message { #[derive(Clone, Debug)] enum State { Locking, - Locked, + Locked { + task_handle: cosmic::iced::task::Handle, + }, Unlocking, Unlocked, } +impl Drop for State { + fn drop(&mut self) { + // Abort the locked task when the state is changed. + if let Self::Locked { task_handle } = self { + log::info!("dropping lockscreen tasks"); + task_handle.abort(); + } + } +} + /// The [`App`] stores application-specific state. pub struct App { core: Core, @@ -264,7 +276,7 @@ pub struct App { } impl App { - fn menu<'a>(&'a self, surface_id: SurfaceId) -> Element<'a, Message> { + fn menu(&self, surface_id: SurfaceId) -> Element { let left_element = { let date_time_column = { let mut column = widget::column::with_capacity(2).padding(16.0); @@ -389,7 +401,7 @@ impl App { ) .id(text_input_id) .manage_value(true) - .on_submit(|v| Message::Submit(v)); + .on_submit(Message::Submit); if *secret { text_input = text_input.password() @@ -577,18 +589,18 @@ impl cosmic::Application for App { let surface_id = SurfaceId::unique(); let subsurface_id = SurfaceId::unique(); - match self.surface_ids.insert(output.clone(), surface_id) { - Some(old_surface_id) => { - //TODO: remove old surface? - log::warn!( - "output {}: already had surface ID {:?}", - output.id(), - old_surface_id - ); - return Task::none(); - } - None => {} + if let Some(old_surface_id) = + self.surface_ids.insert(output.clone(), surface_id) + { + //TODO: remove old surface? + log::warn!( + "output {}: already had surface ID {:?}", + output.id(), + old_surface_id + ); + return Task::none(); } + let size = if let Some((w, h)) = output_info_opt.as_ref().and_then(|info| info.logical_size) { @@ -654,7 +666,7 @@ impl cosmic::Application for App { })), ); - if matches!(self.state, State::Locked) { + if matches!(self.state, State::Locked { .. }) { return Task::batch([ get_lock_surface(surface_id, output), cosmic::task::message(cosmic::Action::Cosmic( @@ -672,7 +684,7 @@ impl cosmic::Application for App { if let Some(n) = self.surface_names.remove(&surface_id) { self.text_input_ids.remove(&n); } - if matches!(self.state, State::Locked) { + if matches!(self.state, State::Locked { .. }) { return destroy_lock_surface(surface_id); } } @@ -709,15 +721,89 @@ impl cosmic::Application for App { SessionLockEvent::Focused(..) => {} SessionLockEvent::Locked => { log::info!("session locked"); - if matches!(self.state, State::Locked) { + if matches!(self.state, State::Locked { .. }) { return Task::none(); } - self.state = State::Locked; + + let username = self.flags.current_user.name.clone(); + let (locked_task, locked_handle) = cosmic::task::stream( + cosmic::iced_futures::stream::channel(16, |mut msg_tx| async move { + // Send heartbeat once a second to update time. + let heartbeat_future = { + let mut output = msg_tx.clone(); + async move { + let mut interval = + tokio::time::interval(Duration::from_secs(1)); + + loop { + output + .send(cosmic::Action::App(Message::None)) + .await + .unwrap(); + + interval.tick().await; + } + } + }; + + let pam_future = async { + loop { + let (value_tx, value_rx) = mpsc::channel(16); + msg_tx + .send(cosmic::Action::App(Message::Channel(value_tx))) + .await + .unwrap(); + + let pam_res = { + let username = username.clone(); + let msg_tx = msg_tx.clone(); + task::spawn_blocking(move || { + pam_thread(username, Conversation { msg_tx, value_rx }) + }) + .await + .unwrap() + }; + + match pam_res { + Ok(()) => { + log::info!("successfully authenticated"); + msg_tx + .send(cosmic::Action::App(Message::Unlock)) + .await + .unwrap(); + break; + } + Err(err) => { + log::warn!("authentication error: {}", err); + msg_tx + .send(cosmic::Action::App(Message::Error( + err.to_string(), + ))) + .await + .unwrap(); + } + } + } + }; + + futures::pin_mut!(heartbeat_future); + futures::pin_mut!(pam_future); + futures::future::select(heartbeat_future, pam_future).await; + }), + ) + .abortable(); + + let mut commands = Vec::with_capacity(self.surface_ids.len() + 1); + commands.push(locked_task); + + self.state = State::Locked { + task_handle: locked_handle, + }; + // Allow suspend self.inhibit_opt = None; // Create lock surfaces - let mut commands = Vec::with_capacity(self.surface_ids.len()); for (output, surface_id) in self.surface_ids.iter() { commands.push(get_lock_surface(*surface_id, output.clone())); @@ -836,15 +922,11 @@ impl cosmic::Application for App { }, Message::Suspend => { #[cfg(feature = "logind")] - return cosmic::task::future(async move { - match crate::logind::suspend().await { - Ok(()) => cosmic::action::none(), - Err(err) => { - log::error!("failed to suspend: {:?}", err); - cosmic::Action::App(Message::Error(err.to_string())) - } - } - }); + return cosmic::Task::future(async move { crate::logind::suspend().await.err() }) + .and_then(|err| { + log::error!("failed to suspend: {:?}", err); + cosmic::task::message(cosmic::Action::App(Message::Error(err.to_string()))) + }); } Message::Error(error) => { self.error_opt = Some(error); @@ -869,13 +951,13 @@ impl cosmic::Application for App { State::Unlocking => { log::info!("session still unlocking"); } - State::Locking | State::Locked => { + State::Locking | State::Locked { .. } => { log::info!("session already locking or locked"); } }, Message::Unlock => { match self.state { - State::Locked => { + State::Locked { .. } => { log::info!("sessing unlocking"); self.state = State::Unlocking; // Clear errors @@ -891,6 +973,12 @@ impl cosmic::Application for App { // Destroy lock surfaces let mut commands = Vec::with_capacity(self.surface_ids.len() + 1); + + for (_output, surface_id) in self.surface_ids.iter() { + self.surface_names.remove(surface_id); + commands.push(destroy_lock_surface(*surface_id)); + } + // Tell compositor to unlock commands.push(unlock()); @@ -964,60 +1052,6 @@ impl cosmic::Application for App { }), ); - if matches!(self.state, State::Locked) { - struct HeartbeatSubscription; - subscriptions.push(Subscription::run_with_id( - TypeId::of::(), - cosmic::iced_futures::stream::channel(16, |mut msg_tx| async move { - loop { - // Send heartbeat once a second to update time - //TODO: only send this when needed - msg_tx.send(Message::None).await.unwrap(); - time::sleep(time::Duration::new(1, 0)).await; - } - }), - )); - - struct PamSubscription; - //TODO: how to avoid cloning this on every time subscription is called? - let username = self.flags.current_user.name.clone(); - subscriptions.push(Subscription::run_with_id( - TypeId::of::(), - cosmic::iced_futures::stream::channel(16, |mut msg_tx| async move { - loop { - let (value_tx, value_rx) = mpsc::channel(16); - msg_tx.send(Message::Channel(value_tx)).await.unwrap(); - - let pam_res = { - let username = username.clone(); - let msg_tx = msg_tx.clone(); - task::spawn_blocking(move || { - pam_thread(username, Conversation { msg_tx, value_rx }) - }) - .await - .unwrap() - }; - - match pam_res { - Ok(()) => { - log::info!("successfully authenticated"); - msg_tx.send(Message::Unlock).await.unwrap(); - break; - } - Err(err) => { - log::warn!("authentication error: {}", err); - msg_tx.send(Message::Error(err.to_string())).await.unwrap(); - } - } - } - - loop { - time::sleep(time::Duration::new(60, 0)).await; - } - }), - )); - } - #[cfg(feature = "logind")] { subscriptions.push(crate::logind::subscription()); @@ -1025,16 +1059,12 @@ 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 = "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 b185298..a35e813 100644 --- a/src/logind.rs +++ b/src/logind.rs @@ -1,12 +1,12 @@ use cosmic::iced::{ - futures::{channel::mpsc, SinkExt, StreamExt}, Subscription, + futures::{SinkExt, StreamExt, channel::mpsc}, }; use logind_zbus::{ manager::{InhibitType, ManagerProxy}, session::SessionProxy, }; -use std::{any::TypeId, error::Error, os::fd::OwnedFd, sync::Arc}; +use std::{any::TypeId, error::Error, os::fd::OwnedFd, sync::Arc, time::Duration}; use zbus::Connection; use crate::locker::Message; @@ -78,6 +78,9 @@ pub async fn handler(msg_tx: &mut mpsc::Sender) -> Result<(), Box) -> Result<(), Box Subscription> { msg_tx.send(None).await.unwrap(); //TODO: should we retry on error? - loop { - time::sleep(time::Duration::new(60, 0)).await; - } + futures_util::future::pending().await }), ) } @@ -63,8 +60,9 @@ pub fn subscription() -> Subscription> { pub async fn handler(msg_tx: &mut mpsc::Sender>) -> Result<()> { let zbus = Connection::system().await?; let nm = NetworkManager::new(&zbus).await?; - let mut active_conns_changed = nm.receive_active_connections_changed().await; + let mut interval = tokio::time::interval(Duration::from_secs(1)); + loop { let mut icon = NetworkIcon::None; @@ -100,9 +98,7 @@ pub async fn handler(msg_tx: &mut mpsc::Sender>) -> Result< msg_tx.send(Some(icon.name())).await.unwrap(); // 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)) - ); + active_conns_changed.next().await; + interval.tick().await; } } diff --git a/src/upower.rs b/src/upower.rs index 2610de3..0fdbe2c 100644 --- a/src/upower.rs +++ b/src/upower.rs @@ -1,9 +1,8 @@ use cosmic::iced::{ - futures::{channel::mpsc, SinkExt, StreamExt}, Subscription, + futures::{SinkExt, StreamExt, channel::mpsc}, }; -use std::any::TypeId; -use tokio::time; +use std::{any::TypeId, time::Duration}; use upower_dbus::UPowerProxy; use zbus::{Connection, Result}; @@ -25,9 +24,7 @@ pub fn subscription() -> Subscription> { msg_tx.send(None).await.unwrap(); //TODO: should we retry on error? - loop { - time::sleep(time::Duration::new(60, 0)).await; - } + futures_util::future::pending().await }), ) } @@ -40,6 +37,8 @@ pub async fn handler(msg_tx: &mut mpsc::Sender>) -> Result let mut icon_name_changed = dev.receive_icon_name_changed().await; let mut percentage_changed = dev.receive_percentage_changed().await; + let mut interval = tokio::time::interval(Duration::from_secs(1)); + loop { let mut info_opt = None; @@ -53,7 +52,8 @@ pub async fn handler(msg_tx: &mut mpsc::Sender>) -> Result msg_tx.send(info_opt).await.unwrap(); - // Waits until icon or percentage have changed - tokio::select!(_ = icon_name_changed.next() => (), _ = percentage_changed.next() => ()); + // Waits until icon or percentage have changed, and at least one second has passed. + futures_util::future::select(icon_name_changed.next(), percentage_changed.next()).await; + interval.tick().await; } }