diff --git a/cosmic-settings/src/app.rs b/cosmic-settings/src/app.rs index 0e89631..0dca0af 100644 --- a/cosmic-settings/src/app.rs +++ b/cosmic-settings/src/app.rs @@ -243,6 +243,11 @@ impl cosmic::Application for SettingsApp { widgets } + fn on_app_exit(&mut self) -> Option { + self.pages.on_leave(self.active_page); + None + } + fn on_escape(&mut self) -> Task { if self.search_active { self.search_active = false; diff --git a/cosmic-settings/src/pages/display/mod.rs b/cosmic-settings/src/pages/display/mod.rs index 89a052e..90d464b 100644 --- a/cosmic-settings/src/pages/display/mod.rs +++ b/cosmic-settings/src/pages/display/mod.rs @@ -21,6 +21,8 @@ use once_cell::sync::Lazy; use slab::Slab; use slotmap::{Key, SecondaryMap, SlotMap}; use std::{collections::BTreeMap, process::ExitStatus, sync::Arc}; +use tokio::sync::oneshot; +use tokio::task::JoinHandle; use tracing::error; static DPI_SCALES: &[u32] = &[50, 75, 100, 125, 150, 175, 200, 225, 250, 275, 300]; @@ -132,7 +134,7 @@ pub struct Page { mirror_map: SecondaryMap, mirror_menu: widget::dropdown::multi::Model, active_display: OutputKey, - background_service: Option>, + background_service_cancel: Option>, config: Config, cache: ViewCache, // context: Option, @@ -166,7 +168,7 @@ impl Default for Page { mirror_map: SecondaryMap::new(), mirror_menu: widget::dropdown::multi::model(), active_display: OutputKey::default(), - background_service: None, + background_service_cancel: None, config: Config::default(), cache: ViewCache::default(), // context: None, @@ -246,51 +248,68 @@ impl page::Page for Page { ) -> Task { use std::time::Duration; - if let Some(task) = self.background_service.take() { - task.abort(); + use futures::pin_mut; + + if let Some(canceller) = self.background_service_cancel.take() { + _ = canceller.send(()); } #[cfg(feature = "wayland")] { + let (tx, mut rx) = tachyonix::channel(4); + let (canceller, cancelled) = oneshot::channel(); + let runtime = tokio::runtime::Handle::current(); + // Spawns a background service to monitor for display state changes. // This must be spawned onto its own thread because `*mut wayland_sys::client::wl_display` is not Send-able. - let runtime = tokio::runtime::Handle::current(); - self.background_service = Some(tokio::task::spawn_blocking(move || { - runtime.block_on(async move { - let (tx, mut rx) = tachyonix::channel(200); + tokio::task::spawn_blocking(move || { + let dispatcher = async move { let Ok((mut context, mut event_queue)) = cosmic_randr::connect(tx) else { return; }; - while context.dispatch(&mut event_queue).await.is_ok() { - if sender.is_closed() { - break; - } - 'outer: while let Ok(message) = rx.try_recv() { - if let cosmic_randr::Message::ManagerDone = message { - if matches!( - tokio::time::timeout( - Duration::from_secs(1), - sender.send(pages::Message::Displays(Message::Refresh)) - ) - .await, - Err(_) | Ok(Err(_)) - ) { - break 'outer; - } - } + loop { + if context.dispatch(&mut event_queue).await.is_err() { + return; } } - }); - })); + }; + + pin_mut!(dispatcher); + runtime.block_on(futures::future::select(cancelled, dispatcher)); + }); + + // Forward messages from another thread to prevent the monitoring thread from blocking. + tokio::task::spawn(async move { + while let Ok(message) = rx.recv().await { + if sender.is_closed() { + return; + } + + if let cosmic_randr::Message::ManagerDone = message { + if matches!( + tokio::time::timeout( + Duration::from_secs(1), + sender.send(pages::Message::Displays(Message::Refresh)) + ) + .await, + Err(_) | Ok(Err(_)) + ) { + return; + } + } + } + }); + + self.background_service_cancel = Some(canceller); } cosmic::task::future(on_enter()) } fn on_leave(&mut self) -> Task { - if let Some(task) = self.background_service.take() { - task.abort(); + if let Some(canceller) = self.background_service_cancel.take() { + _ = canceller.send(()); } Task::none()