fix(display): avoid block on dispatch; fixing app close

This commit is contained in:
Michael Aaron Murphy 2025-01-11 11:47:23 +01:00
parent f0dd262d5d
commit 7576ad1af7
No known key found for this signature in database
GPG key ID: B2732D4240C9212C
2 changed files with 53 additions and 29 deletions

View file

@ -243,6 +243,11 @@ impl cosmic::Application for SettingsApp {
widgets widgets
} }
fn on_app_exit(&mut self) -> Option<Self::Message> {
self.pages.on_leave(self.active_page);
None
}
fn on_escape(&mut self) -> Task<Self::Message> { fn on_escape(&mut self) -> Task<Self::Message> {
if self.search_active { if self.search_active {
self.search_active = false; self.search_active = false;

View file

@ -21,6 +21,8 @@ use once_cell::sync::Lazy;
use slab::Slab; use slab::Slab;
use slotmap::{Key, SecondaryMap, SlotMap}; use slotmap::{Key, SecondaryMap, SlotMap};
use std::{collections::BTreeMap, process::ExitStatus, sync::Arc}; use std::{collections::BTreeMap, process::ExitStatus, sync::Arc};
use tokio::sync::oneshot;
use tokio::task::JoinHandle;
use tracing::error; use tracing::error;
static DPI_SCALES: &[u32] = &[50, 75, 100, 125, 150, 175, 200, 225, 250, 275, 300]; 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<OutputKey, OutputKey>, mirror_map: SecondaryMap<OutputKey, OutputKey>,
mirror_menu: widget::dropdown::multi::Model<String, Mirroring>, mirror_menu: widget::dropdown::multi::Model<String, Mirroring>,
active_display: OutputKey, active_display: OutputKey,
background_service: Option<tokio::task::JoinHandle<()>>, background_service_cancel: Option<oneshot::Sender<()>>,
config: Config, config: Config,
cache: ViewCache, cache: ViewCache,
// context: Option<ContextDrawer>, // context: Option<ContextDrawer>,
@ -166,7 +168,7 @@ impl Default for Page {
mirror_map: SecondaryMap::new(), mirror_map: SecondaryMap::new(),
mirror_menu: widget::dropdown::multi::model(), mirror_menu: widget::dropdown::multi::model(),
active_display: OutputKey::default(), active_display: OutputKey::default(),
background_service: None, background_service_cancel: None,
config: Config::default(), config: Config::default(),
cache: ViewCache::default(), cache: ViewCache::default(),
// context: None, // context: None,
@ -246,51 +248,68 @@ impl page::Page<crate::pages::Message> for Page {
) -> Task<crate::pages::Message> { ) -> Task<crate::pages::Message> {
use std::time::Duration; use std::time::Duration;
if let Some(task) = self.background_service.take() { use futures::pin_mut;
task.abort();
if let Some(canceller) = self.background_service_cancel.take() {
_ = canceller.send(());
} }
#[cfg(feature = "wayland")] #[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. // 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. // 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(); tokio::task::spawn_blocking(move || {
self.background_service = Some(tokio::task::spawn_blocking(move || { let dispatcher = async move {
runtime.block_on(async move {
let (tx, mut rx) = tachyonix::channel(200);
let Ok((mut context, mut event_queue)) = cosmic_randr::connect(tx) else { let Ok((mut context, mut event_queue)) = cosmic_randr::connect(tx) else {
return; return;
}; };
while context.dispatch(&mut event_queue).await.is_ok() { loop {
if sender.is_closed() { if context.dispatch(&mut event_queue).await.is_err() {
break; return;
}
'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;
}
}
} }
} }
}); };
}));
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()) cosmic::task::future(on_enter())
} }
fn on_leave(&mut self) -> Task<crate::pages::Message> { fn on_leave(&mut self) -> Task<crate::pages::Message> {
if let Some(task) = self.background_service.take() { if let Some(canceller) = self.background_service_cancel.take() {
task.abort(); _ = canceller.send(());
} }
Task::none() Task::none()