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
}
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> {
if self.search_active {
self.search_active = false;

View file

@ -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<OutputKey, OutputKey>,
mirror_menu: widget::dropdown::multi::Model<String, Mirroring>,
active_display: OutputKey,
background_service: Option<tokio::task::JoinHandle<()>>,
background_service_cancel: Option<oneshot::Sender<()>>,
config: Config,
cache: ViewCache,
// context: Option<ContextDrawer>,
@ -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<crate::pages::Message> for Page {
) -> Task<crate::pages::Message> {
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<crate::pages::Message> {
if let Some(task) = self.background_service.take() {
task.abort();
if let Some(canceller) = self.background_service_cancel.take() {
_ = canceller.send(());
}
Task::none()