From f71474ca4c30e6f85ac2215a99e85db7becba4b3 Mon Sep 17 00:00:00 2001 From: Mariano Abad Date: Thu, 23 Jul 2026 23:04:01 -0300 Subject: [PATCH] drm: drop to Unavailable when the background refresh finds no displays The review asked for two things when the last CRTC disappears: push the empty topology to consumers, and stop advertising the removed displays. Only the first was done. The positive-TTL refresh still discarded an empty probe result and kept the previous list, so on an idle host -- where there is no live stream to carry the hotplug push -- enumeration kept reporting displays that were gone, exactly as described. It now transitions to Unavailable on an empty result, matching the hotplug path, while a failed probe (transient open/EACCES, not evidence the displays are gone) keeps the verdict and only restamps it. --- src/server/drm_capturer.rs | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/server/drm_capturer.rs b/src/server/drm_capturer.rs index ac09e322c..086361b6f 100644 --- a/src/server/drm_capturer.rs +++ b/src/server/drm_capturer.rs @@ -805,11 +805,25 @@ fn refresh_available_async() { let _in_flight = ProbeInFlightGuard; let result = query_displays(); let mut st = DRM_STATE.lock().unwrap(); - if let ProbeState::Available(since, list) = &mut *st { - *since = Instant::now(); - if let Ok(fresh) = result { - if !fresh.is_empty() { - *list = fresh; + if !matches!(&*st, ProbeState::Available(..)) { + return; + } + match result { + Ok(fresh) if fresh.is_empty() => { + // No active CRTC left: every monitor is gone. Keeping the previous list here is + // what leaves enumeration advertising removed displays indefinitely on an idle + // host, where there is no live stream to deliver the hotplug push. Drop to + // Unavailable exactly as swap_available_displays does; a later probe restores + // Available when a monitor comes back. + log::info!("drm: refresh -> 0 displays, marking DRM unavailable"); + *st = ProbeState::Unavailable(Instant::now()); + } + Ok(fresh) => *st = ProbeState::Available(Instant::now(), fresh), + // A failed probe is not evidence that the displays are gone (a transient open or + // EACCES); keep the verdict, just restamp so we retry after the next TTL. + Err(_) => { + if let ProbeState::Available(since, _) = &mut *st { + *since = Instant::now(); } } }