fix(vdisplay/mutter): stop the teardown layout-restore from SIGSEGVing gnome-shell

After a session ends, the Mutter backend (with PUNKTFUNK_MUTTER_VIRTUAL_PRIMARY=1)
re-asserted the physical monitor layout with an explicit ApplyMonitorsConfig. On
Mutter 50 + NVIDIA that monitor reconfig — issued while the just-removed high-refresh
virtual output is still tearing down — SIGSEGVs gnome-shell. Observed live on
home-worker-3: the teardown ApplyMonitorsConfig returns "recipient disconnected from
message bus" (the shell died mid-call), GDM's crash-loop guard then drops to the
greeter and STAYS there, so org.gnome.Mutter.RemoteDesktop/DisplayConfig vanish and
every subsequent reconnect fails with RemoteDesktop.CreateSession ServiceUnknown —
i.e. "after a disconnect I can't reconnect anymore."

make_virtual_primary applies an APPLY_TEMPORARY config, which Mutter reverts on its
own once the virtual output disappears and our DisplayConfig connection closes. So the
explicit restore was both redundant and the crash trigger: drop it, drop the dc_pre
connection at teardown, and let Mutter revert the temporary config itself. Setup is
unchanged (the virtual output is still made primary so the desktop lands on the
streamed surface). Removes the now-unused to_apply_logicals/apply_config helpers.

Verified live on home-worker-3 (5120x1440@240, VIRTUAL_PRIMARY=1): 6/6 back-to-back
connect/disconnect cycles streamed cleanly with gnome-shell holding the same PID
throughout (previously it crashed within the first few disconnects).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-13 12:37:07 +00:00
parent 47112f44b7
commit 55dfb4800f
+11 -55
View File
@@ -151,13 +151,9 @@ fn session_thread(setup_tx: Sender<Result<u32, String>>, stop: Arc<AtomicBool>,
// windows land on the surface we stream. Without this, on a host that also has a physical
// monitor attached, the virtual output is an empty extended desktop — you stream only the
// wallpaper. Best-effort: any failure just logs and streaming continues unchanged.
let mut restore: Option<(zbus::Proxy<'static>, Vec<ApplyLogical>)> = None;
if let Some((dc, pre)) = &dc_pre {
match make_virtual_primary(dc, mode, pre).await {
Ok(()) => {
restore = Some((dc.clone(), to_apply_logicals(pre)));
tracing::info!("mutter: virtual output set as the primary monitor");
}
Ok(()) => tracing::info!("mutter: virtual output set as the primary monitor"),
Err(e) => tracing::warn!(
"mutter: could not set the virtual output primary ({e:#}); streaming continues — the desktop may render on the physical monitor"
),
@@ -169,19 +165,17 @@ fn session_thread(setup_tx: Sender<Result<u32, String>>, stop: Arc<AtomicBool>,
tokio::time::sleep(Duration::from_millis(200)).await;
}
// Tear down: STOP the screencast FIRST so Mutter removes the virtual output and auto-reverts
// the temporary monitor config (physical → primary). Reconfiguring an *actively-captured*
// high-refresh virtual output via ApplyMonitorsConfig was SIGSEGVing gnome-shell on teardown,
// so we never touch the layout while the virtual output is still live.
// Tear down: STOP the screencast so Mutter removes the virtual output. We deliberately do NOT
// re-assert the physical layout with our own ApplyMonitorsConfig. Issuing a monitor reconfig
// while the just-removed high-refresh virtual output is still tearing down SIGSEGVs gnome-shell
// on Mutter 50 + NVIDIA — observed live on home-worker-3: the teardown ApplyMonitorsConfig
// returned "recipient disconnected from message bus" because the shell crashed mid-call, after
// which GDM's crash-loop guard dropped to the greeter and wedged EVERY subsequent reconnect.
// make_virtual_primary applied an APPLY_TEMPORARY config; Mutter reverts that on its own once
// the virtual output disappears and our DisplayConfig connection (`dc_pre`) closes — so we just
// drop it here and let the revert happen Mutter-side, never touching the layout ourselves.
let _ = session.rd_session.call_method("Stop", &()).await;
if let Some((dc, original)) = restore {
// Let Mutter drop the virtual output, then re-assert the physical layout deterministically
// (a no-op if the temporary config already auto-reverted) — safe now: no live virtual.
tokio::time::sleep(Duration::from_millis(300)).await;
if let Err(e) = apply_config(&dc, &original).await {
tracing::warn!("mutter: monitor-layout restore after stop failed ({e:#}); Mutter auto-reverts the temporary config on teardown");
}
}
drop(dc_pre);
});
}
@@ -481,41 +475,3 @@ fn build_primary_config(vconn: &str, vmode: &str) -> Vec<ApplyLogical> {
)]
}
/// Convert a captured `GetCurrentState` layout back into an `ApplyMonitorsConfig` argument (used
/// to restore the physical-primary layout on teardown).
fn to_apply_logicals(state: &CurrentState) -> Vec<ApplyLogical> {
state
.2
.iter()
.filter_map(|lm| {
let mons: Vec<ApplyMon> = lm
.5
.iter()
.filter_map(|s| {
current_mode(state, &s.0).map(|(id, _, _)| (s.0.clone(), id, HashMap::new()))
})
.collect();
if mons.is_empty() {
return None;
}
Some((lm.0, lm.1, lm.2, lm.3, lm.4, mons))
})
.collect()
}
async fn apply_config(dc: &zbus::Proxy<'_>, logicals: &[ApplyLogical]) -> Result<()> {
let state = get_state(dc).await?;
let _: () = dc
.call(
"ApplyMonitorsConfig",
&(
state.0,
APPLY_TEMPORARY,
logicals.to_vec(),
HashMap::<String, Value<'static>>::new(),
),
)
.await
.context("DisplayConfig.ApplyMonitorsConfig (restore)")?;
Ok(())
}