From 132e887efa9bf41f0097b26a6e3860314decf87b Mon Sep 17 00:00:00 2001 From: bmh129 <11478682+bmh129@users.noreply.github.com> Date: Mon, 19 Oct 2026 23:45:59 -0500 Subject: [PATCH] Linux: time out the desktop theme's portal read The theme watcher (DesktopTheme) reads the desktop's light/dark choice from the XDG Desktop Portal over a blocking D-Bus call with no timeout. zbus waits forever by default, so a portal that accepts the call and never answers stalled the watcher's thread on its first read, and "Use system setting" stopped following the desktop for the rest of the session, even after the portal recovered. The session connection is now built with a 2 s method timeout (REPLY, shorter than the 4 s POLL). A timed-out call returns InputOutput, which read() already handles by dropping the connection, so the next poll asks again on a fresh one. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0115HyvML9WnuFRdmtMcLP5t --- ROADMAP.md | 4 +++- crates/ui-egui/src/desktop_theme.rs | 8 +++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 070552d07..ae2c5c6f2 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -2,7 +2,7 @@ **Stage: alpha** · next: beta, ~25 points of "ready for real work" and ~160–240 h away -> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-12 · **Change:** minor (rows auto-fit oversized fonts on open and whole-row/column formatting) · **Target:** Microsoft Excel (Microsoft 365) +> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-10-19 · **Change:** trivial (desktop theme watcher survives a portal that never answers) · **Target:** Microsoft Excel (Microsoft 365) GridCraft aims at full Microsoft Excel parity, and at being better in places: faster, open (XLSX is the native format), fully scriptable by agents (MCP, control channel, CLI), and on the web. @@ -132,6 +132,7 @@ Ranked; estimates in Opus 5.5 agent-hours. Detail and the full beta-gate list in | Date | What landed | |---|---| +| 2026-10-19 | Linux: "Use system setting" for the theme keeps following the desktop when the XDG Desktop Portal stops answering: each read gives up after 2 s and the next poll reconnects, where one unanswered call stalled the watcher for the rest of the session | | 2026-10-12 | Rows auto-fit oversized fonts: opening a file and whole-row/column formatting grow non-custom rows to their tallest font (#833) | | 2026-10-12 | Number formats: parsing a format code is linear in its length (long runs of `[`, `,` or `/` no longer take quadratic time), and an unclosed `[` is a literal when splitting sections as well as when tokenizing (#810) | | 2026-10-12 | Number formats: a fraction denominator with 65,536 zeros no longer crashes; format codes are capped at 255 characters like Excel (longer codes display as General, `TEXT` gives `#VALUE!`) (#807) | @@ -193,6 +194,7 @@ Ranked; estimates in Opus 5.5 agent-hours. Detail and the full beta-gate list in | Date | Change | Summary | |---|---|---| +| 2026-10-19 | trivial | Progress log: desktop theme portal read timeout | | 2026-10-12 | minor | Rows auto-fit oversized fonts on open and whole-row/column formatting (#833); progress log row | | 2026-10-12 | minor | Progress log: multi-area row/column delete (#758) | | 2026-10-12 | trivial | Progress log: XLSX chart sheets kept on save (#727) | diff --git a/crates/ui-egui/src/desktop_theme.rs b/crates/ui-egui/src/desktop_theme.rs index 9d67c337a..63cf8d3e4 100644 --- a/crates/ui-egui/src/desktop_theme.rs +++ b/crates/ui-egui/src/desktop_theme.rs @@ -12,6 +12,12 @@ use std::time::Duration; /// round trip every few seconds is cheap and reacts soon enough after a theme switch. const POLL: Duration = Duration::from_secs(4); +/// How long one read waits for the portal's reply. Without it a portal that never answers blocks +/// the watcher for good, and the theme stops following the desktop. Shorter than [`POLL`]: a read +/// that times out drops the connection, and the next poll asks again on a fresh one. +#[cfg(target_os = "linux")] +const REPLY: Duration = Duration::from_secs(2); + /// Whether the desktop asks applications to draw dark. `None` when it says nothing, and when /// there is no session bus or no portal to ask (a bare X server, a container), so the caller /// keeps whatever it chose itself. @@ -82,7 +88,7 @@ fn read(link: &mut Link) -> Option { const SETTINGS: &str = "org.freedesktop.portal.Settings"; const SCHEMAS: [(&str, &str); 2] = [("org.gnome.desktop.interface", "color-scheme"), ("org.freedesktop.interface.color-scheme", "color-scheme")]; if link.is_none() { - *link = Some(zbus::blocking::Connection::session().ok()?); + *link = Some(zbus::blocking::connection::Builder::session().ok()?.method_timeout(REPLY).build().ok()?); } let connection = link.as_ref()?; for (schema, key) in SCHEMAS {