From 015f2ee47b76fa4b9e942e42d6287fe9c13713a8 Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Fri, 12 Jun 2026 13:59:29 +0000 Subject: [PATCH] fix(vdisplay/mutter): gate >60Hz virtual mode behind an env flag (teardown SIGSEGV) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pinning the virtual output to a high client refresh via RecordVirtual "modes" works mid-stream, but a high-refresh virtual CRTC SIGSEGVs gnome-shell on session TEARDOWN (observed at 5120x1440@240) — taking down the whole GNOME session, so subsequent connects fail with RemoteDesktop ServiceUnknown. Gate it behind PUNKTFUNK_MUTTER_VIRTUAL_REFRESH, default OFF — Mutter then derives the virtual monitor's refresh from the PipeWire framerate (60Hz, stable). The >60Hz path stays in-tree for investigation; re-enable once the teardown crash is understood. Co-Authored-By: Claude Opus 4.8 (1M context) --- crates/punktfunk-host/src/vdisplay/mutter.rs | 37 ++++++++++++++------ 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/crates/punktfunk-host/src/vdisplay/mutter.rs b/crates/punktfunk-host/src/vdisplay/mutter.rs index 0b58a3b..4c929e8 100644 --- a/crates/punktfunk-host/src/vdisplay/mutter.rs +++ b/crates/punktfunk-host/src/vdisplay/mutter.rs @@ -243,18 +243,21 @@ async fn connect(mode: Mode) -> Result { ) .await?; - // 3. The virtual monitor, pinned to the client's exact mode via RecordVirtual's "modes" - // (explicit size + refresh-rate; Mutter ≥ 47). WITHOUT it Mutter derives the virtual monitor's - // refresh from the PipeWire stream framerate and defaults to **60 Hz** — so a >60 Hz client - // mode (e.g. 240) renders at 60 and only the encoder pads to 240 (duplicate frames). Older - // Mutter that doesn't know the key just ignores it and falls back to the 60 Hz default. - let mut vmode: HashMap<&str, Value> = HashMap::new(); - vmode.insert("size", Value::from((mode.width, mode.height))); - vmode.insert("refresh-rate", Value::from(mode.refresh_hz as f64)); - vmode.insert("is-preferred", Value::from(true)); + // 3. The virtual monitor. By DEFAULT we let Mutter derive the refresh from the PipeWire + // framerate (it defaults the virtual monitor to **60 Hz**) — stable. PUNKTFUNK_MUTTER_VIRTUAL_REFRESH=1 + // pins the client's exact WxH@Hz via RecordVirtual's "modes" (explicit size + refresh-rate; + // Mutter ≥ 47) for true >60 Hz — BUT a high-refresh virtual CRTC has been observed to SIGSEGV + // gnome-shell on teardown (large modes, e.g. 5120×1440@240), so it is OFF by default until that + // teardown crash is resolved. let mut rec: HashMap<&str, Value> = HashMap::new(); rec.insert("cursor-mode", Value::from(CURSOR_EMBEDDED)); - rec.insert("modes", Value::from(vec![vmode])); + if virtual_refresh_enabled() && mode.refresh_hz > 60 { + let mut vmode: HashMap<&str, Value> = HashMap::new(); + vmode.insert("size", Value::from((mode.width, mode.height))); + vmode.insert("refresh-rate", Value::from(mode.refresh_hz as f64)); + vmode.insert("is-preferred", Value::from(true)); + rec.insert("modes", Value::from(vec![vmode])); + } let stream_path: OwnedObjectPath = sc_session .call("RecordVirtual", &(rec,)) .await @@ -348,6 +351,20 @@ fn virtual_primary_enabled() -> bool { .unwrap_or(false) } +/// Opt-in: pin the virtual output to the client's exact refresh via RecordVirtual "modes" (true +/// above-60 Hz). OFF by default — a high-refresh virtual CRTC currently SIGSEGVs gnome-shell on +/// session teardown (large modes), so don't risk the host's GNOME session until that's fixed. +fn virtual_refresh_enabled() -> bool { + std::env::var("PUNKTFUNK_MUTTER_VIRTUAL_REFRESH") + .map(|v| { + matches!( + v.trim().to_ascii_lowercase().as_str(), + "1" | "true" | "yes" | "on" + ) + }) + .unwrap_or(false) +} + /// A DisplayConfig proxy on its own session-bus connection (owned, so it stays alive for the /// session — independent of the RemoteDesktop/ScreenCast connection). async fn display_config() -> Result> {