diff --git a/crates/punktfunk-host/src/vdisplay/sudovda.rs b/crates/punktfunk-host/src/vdisplay/sudovda.rs index fb1472e..f85b95e 100644 --- a/crates/punktfunk-host/src/vdisplay/sudovda.rs +++ b/crates/punktfunk-host/src/vdisplay/sudovda.rs @@ -53,6 +53,9 @@ const IOCTL_ADD: u32 = ctl(0x800); const IOCTL_REMOVE: u32 = ctl(0x801); const IOCTL_SET_RENDER_ADAPTER: u32 = ctl(0x802); // == 0x0022_2008 const IOCTL_GET_WATCHDOG: u32 = ctl(0x803); +/// pf-vdisplay extension (NOT in SudoVDA): tear down every virtual monitor. Sent once on host startup +/// to reap monitors orphaned by a crashed/killed previous host. SudoVDA returns invalid (ignored). +const IOCTL_CLEAR_ALL: u32 = ctl(0x804); const IOCTL_DRIVER_PING: u32 = ctl(0x888); const IOCTL_GET_VERSION: u32 = ctl(0x8FF); @@ -722,10 +725,20 @@ unsafe fn create_monitor(device: isize, mode: Mode, watchdog_s: u32) -> Result warned = false, + // A persistently failing PING means the cached control handle went invalid — the + // driver watchdog will then tear the monitor down mid-session. Surface it once + // (the old `let _ =` swallowed it, which masked exactly this during the bad-state churn). + Err(e) => { + if !warned { + tracing::warn!("SudoVDA keepalive PING failed (control handle lost?): {e:#}"); + warned = true; + } + } } thread::sleep(interval); } @@ -848,6 +861,16 @@ fn mgr_ensure_device(g: &mut Mgr) -> Result { 3 }; tracing::info!("SudoVDA watchdog timeout {}s", g.watchdog_s); + // Reap monitors orphaned by a crashed/killed previous host instance before we create ours. + // pf-vdisplay honors IOCTL_CLEAR_ALL; SudoVDA returns invalid (ignored). Without it an orphan + // lingers until the driver watchdog fires — but a still-pinging new session keeps resetting that + // watchdog, so orphans could accumulate (the "5-6 stale monitors that never tear down" failure). + { + let mut none: [u8; 0] = []; + if unsafe { ioctl(device, IOCTL_CLEAR_ALL, &[], &mut none) }.is_ok() { + tracing::info!("cleared orphaned virtual monitors on host startup"); + } + } let raw = device.0 as isize; g.device = Some(raw); Ok(raw) diff --git a/packaging/windows/vdisplay-driver/pf-vdisplay/src/control.rs b/packaging/windows/vdisplay-driver/pf-vdisplay/src/control.rs index 8a3c63f..91e38da 100644 --- a/packaging/windows/vdisplay-driver/pf-vdisplay/src/control.rs +++ b/packaging/windows/vdisplay-driver/pf-vdisplay/src/control.rs @@ -30,6 +30,10 @@ const IOCTL_ADD: u32 = ctl(0x800); const IOCTL_REMOVE: u32 = ctl(0x801); const IOCTL_SET_RENDER_ADAPTER: u32 = ctl(0x802); const IOCTL_GET_WATCHDOG: u32 = ctl(0x803); +/// pf-vdisplay extension (NOT in SudoVDA): tear down every monitor. The host issues this on startup to +/// reap monitors orphaned by a crashed/killed previous host instance. SudoVDA returns invalid for it +/// (harmlessly ignored), so the host can send it unconditionally. +const IOCTL_CLEAR_ALL: u32 = ctl(0x804); const IOCTL_PING: u32 = ctl(0x888); const IOCTL_GET_VERSION: u32 = ctl(0x8FF); @@ -112,6 +116,10 @@ pub extern "C-unwind" fn device_io_control( IOCTL_SET_RENDER_ADAPTER => do_set_render_adapter(request, input_len), IOCTL_GET_WATCHDOG => do_get_watchdog(request, output_len, &mut bytes), IOCTL_PING => NTSTATUS::STATUS_SUCCESS, + IOCTL_CLEAR_ALL => { + disconnect_all_monitors(); + NTSTATUS::STATUS_SUCCESS + } IOCTL_GET_VERSION => do_get_version(request, output_len, &mut bytes), _ => NTSTATUS::STATUS_INVALID_DEVICE_REQUEST, }