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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0115HyvML9WnuFRdmtMcLP5t
This commit is contained in:
bmh129
2026-10-19 23:45:59 -05:00
co-authored by Claude Opus 5.5
parent 23829d8c67
commit 132e887efa
2 changed files with 10 additions and 2 deletions
+3 -1
View File
@@ -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) |
+7 -1
View File
@@ -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<bool> {
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 {