fix(windows-host): IDD-push compose kick — idle desktop no longer fails the attach gate
windows-drivers / probe-and-proto (push) Successful in 24s
apple / swift (push) Successful in 1m8s
ci / rust (push) Successful in 1m42s
windows-drivers / driver-build (push) Successful in 1m45s
ci / web (push) Successful in 54s
android / android (push) Successful in 3m39s
ci / docs-site (push) Successful in 1m8s
deb / build-publish (push) Successful in 4m40s
ci / bench (push) Successful in 4m58s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 5s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 5s
decky / build-publish (push) Successful in 25s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 4s
release / apple (push) Successful in 8m9s
windows-host / package (push) Successful in 7m35s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 53s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 1m11s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 2m27s
windows-msix / package (x64, C:\Users\Public\ffmpeg, x86_64-pc-windows-msvc, C:\t) (push) Successful in 1m11s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 50s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 54s
flatpak / build-publish (push) Successful in 4m26s
apple / screenshots (push) Successful in 5m29s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 9m46s
docker / deploy-docs (push) Successful in 24s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 9m21s
windows-drivers / probe-and-proto (push) Successful in 24s
apple / swift (push) Successful in 1m8s
ci / rust (push) Successful in 1m42s
windows-drivers / driver-build (push) Successful in 1m45s
ci / web (push) Successful in 54s
android / android (push) Successful in 3m39s
ci / docs-site (push) Successful in 1m8s
deb / build-publish (push) Successful in 4m40s
ci / bench (push) Successful in 4m58s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 5s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 5s
decky / build-publish (push) Successful in 25s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 4s
release / apple (push) Successful in 8m9s
windows-host / package (push) Successful in 7m35s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 53s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 1m11s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 2m27s
windows-msix / package (x64, C:\Users\Public\ffmpeg, x86_64-pc-windows-msvc, C:\t) (push) Successful in 1m11s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 50s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 54s
flatpak / build-publish (push) Successful in 4m26s
apple / screenshots (push) Successful in 5m29s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 9m46s
docker / deploy-docs (push) Successful in 24s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 9m21s
DWM presents a display only when something dirties it. On an idle desktop a perfectly healthy session sat at E_PENDING: the driver attached but no first frame ever landed, so wait_for_attach's 4 s gate failed the open (and a mid-session ring recreate hit the same stall against the 3 s recover-or-drop). A real client escaped only because its own input soon dirtied the desktop; a headless probe / input-less connect never did. kick_dwm_compose() injects two net-zero 1 px relative mouse moves via SendInput — pf-vdisplay has no hardware-cursor plane, so a cursor move is composited into the frame, a guaranteed real present onto the IDD swap-chain (the mechanism --input-test always relied on; the pointer ends where it started). Wired into wait_for_attach (first kick at 600 ms, then every 800 ms) and, rate-limited, into the GB1 recovery window. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -55,6 +55,9 @@ use windows::Win32::System::Threading::{
|
||||
CreateEventW, GetCurrentProcess, OpenProcess, QueryFullProcessImageNameW, WaitForSingleObject,
|
||||
PROCESS_DUP_HANDLE, PROCESS_NAME_WIN32, PROCESS_QUERY_LIMITED_INFORMATION, PROCESS_SYNCHRONIZE,
|
||||
};
|
||||
use windows::Win32::UI::Input::KeyboardAndMouse::{
|
||||
SendInput, INPUT, INPUT_0, INPUT_MOUSE, MOUSEEVENTF_MOVE, MOUSEINPUT,
|
||||
};
|
||||
|
||||
// The frame-transport contract — `SharedHeader` layout, `MAGIC`/`VERSION`/`RING_LEN`, the
|
||||
// `DRV_STATUS_*` codes and the channel-delivery struct — lives in `pf_driver_proto`; both sides
|
||||
@@ -188,6 +191,36 @@ impl Drop for KeyedMutexGuard<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Nudge DWM into composing the virtual display: two net-zero 1 px relative mouse moves via
|
||||
/// `SendInput`. DWM presents a display only when something DIRTIES it — an idle desktop never does,
|
||||
/// so a freshly-attached ring (session open, or a mid-session ring recreate) can sit at E_PENDING
|
||||
/// with no first frame even though everything is healthy. pf-vdisplay implements no hardware-cursor
|
||||
/// plane, so a cursor move is composited into the frame — a guaranteed real present onto the IDD
|
||||
/// swap-chain (empirically what `punktfunk-probe --input-test` always relied on). Net-zero: the
|
||||
/// pointer ends exactly where it started; the 1 px round trip is imperceptible, and each event still
|
||||
/// dirties the cursor layer. Best-effort — injection can be unavailable on the secure desktop, where
|
||||
/// a fresh compose just happened anyway.
|
||||
fn kick_dwm_compose() {
|
||||
let mk = |dx: i32| INPUT {
|
||||
r#type: INPUT_MOUSE,
|
||||
Anonymous: INPUT_0 {
|
||||
mi: MOUSEINPUT {
|
||||
dx,
|
||||
dy: 0,
|
||||
mouseData: 0,
|
||||
dwFlags: MOUSEEVENTF_MOVE,
|
||||
time: 0,
|
||||
dwExtraInfo: 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
// SAFETY: plain FFI; the input slice is valid, fully-initialized local data for this synchronous
|
||||
// call, and `cbsize` is the true element size.
|
||||
unsafe {
|
||||
let _ = SendInput(&[mk(1), mk(-1)], std::mem::size_of::<INPUT>() as i32);
|
||||
}
|
||||
}
|
||||
|
||||
/// Confirm the process is a genuine system WUDFHost — `%SystemRoot%\System32\WUDFHost.exe` — before a
|
||||
/// broker duplicates sensitive handles into it. The pid is driver-reported (the frame channel's
|
||||
/// [`control::AddReply::wudf_pid`], or the gamepad bootstrap's `driver_pid`); a spoofed devnode / a
|
||||
@@ -460,6 +493,8 @@ pub struct IddPushCapturer {
|
||||
last_fresh: Instant,
|
||||
/// Rate-limits the WUDFHost liveness probe (one 0 ms wait per second, and only while stale).
|
||||
last_liveness: Instant,
|
||||
/// Rate-limits the mid-session [`kick_dwm_compose`] nudge (recovery window only).
|
||||
last_kick: Instant,
|
||||
/// Host-owned ROTATING output ring NVENC encodes (one YUV texture per slot). Rotating it per frame
|
||||
/// is the precondition for pipelining the encode loop: while NVENC encodes frame N's texture on the
|
||||
/// ASIC, frame N+1's convert writes a DIFFERENT texture — the two overlap. Format = `out_format()`:
|
||||
@@ -778,6 +813,7 @@ impl IddPushCapturer {
|
||||
recovering_since: None,
|
||||
last_fresh: Instant::now(),
|
||||
last_liveness: Instant::now(),
|
||||
last_kick: Instant::now(),
|
||||
out_ring: Vec::new(),
|
||||
out_idx: 0,
|
||||
video_conv: None,
|
||||
@@ -810,6 +846,12 @@ impl IddPushCapturer {
|
||||
/// so this does not false-fail a normal (even idle) open; no frame within the window = genuinely broken.
|
||||
fn wait_for_attach(&self) -> Result<()> {
|
||||
let deadline = Instant::now() + Duration::from_secs(4);
|
||||
// Compose-kick schedule: DWM only presents a display something DIRTIED, so on an idle
|
||||
// desktop a perfectly healthy attach sees no first frame (E_PENDING forever) and this gate
|
||||
// used to fail the session — the "idle desktop → no frames" gotcha (a real client escaped
|
||||
// it only because its own input soon dirtied the desktop; a headless probe never did).
|
||||
// Give the natural post-activate compose a moment, then nudge.
|
||||
let mut next_kick = Instant::now() + Duration::from_millis(600);
|
||||
loop {
|
||||
// SAFETY: `self.header` points into the live shared-header mapping this capturer owns (sized
|
||||
// `>= size_of::<SharedHeader>()`, page-aligned), so the field read is in-bounds + aligned, and
|
||||
@@ -831,10 +873,15 @@ impl IddPushCapturer {
|
||||
if st == DRV_STATUS_OPENED && frame::FrameToken::unpack(self.latest()).seq != 0 {
|
||||
return Ok(());
|
||||
}
|
||||
if Instant::now() >= next_kick {
|
||||
kick_dwm_compose();
|
||||
next_kick = Instant::now() + Duration::from_millis(800);
|
||||
}
|
||||
if Instant::now() > deadline {
|
||||
bail!(
|
||||
"IDD-push: driver_status={st} but no frame published within 4s — the virtual display \
|
||||
is likely in a format/size the ring can't match (fullscreen game?); falling back"
|
||||
"IDD-push: driver_status={st} but no frame published within 4s (despite compose \
|
||||
kicks) — the virtual display is likely in a format/size the ring can't match \
|
||||
(fullscreen game?); falling back"
|
||||
);
|
||||
}
|
||||
std::thread::sleep(Duration::from_millis(20));
|
||||
@@ -1098,6 +1145,16 @@ impl IddPushCapturer {
|
||||
dropping the session so the client reconnects"
|
||||
);
|
||||
}
|
||||
// Same idle-desktop stall as the open-time attach gate: after a mid-session ring
|
||||
// recreate (HDR flip / mode change) an idle desktop composes nothing, so the fresh ring
|
||||
// never sees a frame and the 3 s recover-or-drop above kills a healthy session. Nudge
|
||||
// DWM (rate-limited) once the natural post-recreate compose has had its chance.
|
||||
if since.elapsed() > Duration::from_millis(600)
|
||||
&& self.last_kick.elapsed() > Duration::from_millis(800)
|
||||
{
|
||||
self.last_kick = Instant::now();
|
||||
kick_dwm_compose();
|
||||
}
|
||||
}
|
||||
// Driver-death watch (the SDR path has no other signal): a dead WUDFHost stops publishing,
|
||||
// which at the ring is indistinguishable from an idle desktop — the encode loop would repeat
|
||||
|
||||
Reference in New Issue
Block a user