fix(windows/capture): stand the IddCx hardware cursor down while the secure desktop is up
apple / swift (push) Successful in 1m22s
apple / screenshots (push) Successful in 6m41s
ci / web (push) Successful in 1m7s
ci / docs-site (push) Successful in 1m18s
android / android (push) Successful in 12m44s
arch / build-publish (push) Successful in 12m36s
decky / build-publish (push) Successful in 26s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 21s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 19s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 20s
ci / bench (push) Successful in 6m14s
windows-host / package (push) Successful in 9m46s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 21s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 19s
deb / build-publish (push) Successful in 9m38s
deb / build-publish-host (push) Successful in 9m58s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 7m0s
ci / rust (push) Successful in 24m1s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 22m7s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 21m55s
docker / deploy-docs (push) Successful in 12s

0.18.0 regression: UAC consent and Winlogon (lock/logon) stopped appearing
in streams. The cursor channel's IddCx hardware-cursor declare — re-issued
by the driver on every swap-chain assign — keeps the path out of the OS's
software-cursor mode, which is the only mode the secure desktop renders
through; DWM then never presents UAC/Winlogon into our swap-chain and the
stream repeats the last normal-desktop frame for the whole interaction.

The GDI cursor poller now classifies the input desktop on its reattach
cadence (UOI_NAME != Default = secure, 250 ms cadence instead of 2 s), and
the capturer edge-triggers the existing-but-unused proto-v6
IOCTL_SET_CURSOR_FORWARD flip: OFF at secure entry (the driver stops its
per-assign re-declare; the host facade forces the same-mode re-commit that
actualises the software-cursor default, under the vdisplay manager lock via
the new force_recommit()), back ON at dismissal for channel sessions.
Channel-session open resets the driver's persisted desired state to ON so
a session that died mid-secure-desktop can't leave the next one adopting
UNdeclared (the §8.6 cross-session composite trap); orderly teardown does
the same. The flip closure is built for every Windows session — a
channel-less session can reuse a driver monitor whose earlier-session
cursor worker still re-declares, and NOT_FOUND from never-declared targets
is ignored.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-23 14:13:15 +02:00
co-authored by Claude Fable 5
parent 588a7077e8
commit 326d6e17c8
5 changed files with 234 additions and 12 deletions
+31
View File
@@ -175,6 +175,36 @@ pub fn capture_virtual_output(
},
) as pf_capture::CursorChannelSender
});
// The secure-desktop guard's actuator (`IOCTL_SET_CURSOR_FORWARD`): the capturer flips the
// driver's hardware-cursor declare off while UAC/Winlogon is up (the secure desktop renders
// only through the OS's software-cursor path) and back on at dismissal. The stand-down needs
// the same-mode re-commit that actualises the software-cursor default — driven here because
// topology commits belong under the vdisplay manager's lock, which pf-capture cannot take.
// Built for EVERY session (not just `want.hw_cursor`): a channel-less session can reuse a
// driver monitor whose cursor worker (an earlier session's) is still live and re-declaring —
// the flip is the only way to stop it; on a never-declared target the driver answers
// NOT_FOUND, which the capturer logs and ignores.
let target_id = target.target_id;
let cursor_forward: Option<pf_capture::CursorForwardSender> = Some({
std::sync::Arc::new(move |enable: bool| {
let req = pf_driver_proto::control::SetCursorForwardRequest {
target_id,
enable: enable as u32,
};
// SAFETY: `control_raw` is the pf-vdisplay control handle resolved above; it is
// never closed for the process lifetime (`send_cursor_forward`'s precondition).
unsafe {
crate::vdisplay::driver::send_cursor_forward(
windows::Win32::Foundation::HANDLE(control_raw as *mut core::ffi::c_void),
&req,
)?;
}
if !enable {
crate::vdisplay::manager::force_recommit();
}
Ok(())
}) as pf_capture::CursorForwardSender
});
pf_capture::open_idd_push(
target,
pref,
@@ -184,6 +214,7 @@ pub fn capture_virtual_output(
keep,
sender,
cursor_sender,
cursor_forward,
)
.map_err(|(e, _keep)| e.context("IDD-push capture open (no fallback)"))
}