fix(pf-vdisplay/driver): report cursor_excluded adapter-wide — the declare's exclusion is not per-target
ci / web (push) Successful in 51s
ci / docs-site (push) Successful in 1m5s
apple / swift (push) Successful in 1m20s
windows-drivers / probe-and-proto (push) Successful in 1m2s
decky / build-publish (push) Successful in 28s
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 16s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 11s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 8s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 10s
windows-drivers / driver-build (push) Successful in 2m23s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 1m5s
ci / bench (push) Successful in 6m3s
apple / screenshots (push) Successful in 6m41s
deb / build-publish (push) Successful in 12m20s
arch / build-publish (push) Successful in 12m47s
deb / build-publish-host (push) Successful in 13m7s
android / android (push) Successful in 16m14s
windows-host / package (push) Successful in 15m53s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 16m23s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 16m8s
ci / rust (push) Successful in 26m6s
docker / deploy-docs (push) Canceled after 25s
ci / web (push) Successful in 51s
ci / docs-site (push) Successful in 1m5s
apple / swift (push) Successful in 1m20s
windows-drivers / probe-and-proto (push) Successful in 1m2s
decky / build-publish (push) Successful in 28s
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 16s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 11s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 8s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 10s
windows-drivers / driver-build (push) Successful in 2m23s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 1m5s
ci / bench (push) Successful in 6m3s
apple / screenshots (push) Successful in 6m41s
deb / build-publish (push) Successful in 12m20s
arch / build-publish (push) Successful in 12m47s
deb / build-publish-host (push) Successful in 13m7s
android / android (push) Successful in 16m14s
windows-host / package (push) Successful in 15m53s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 16m23s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 16m8s
ci / rust (push) Successful in 26m6s
docker / deploy-docs (push) Canceled after 25s
Field report (iPad via GameStream, 2026-07-23): cursor missing from the stream. The session's fresh target 257 reported cursor_excluded=false (DECLARED_TARGETS is keyed per target and only 259 had declared), so the host skipped forced-composite — but DWM's pointer exclusion after an IddCx hardware-cursor declare reaches EVERY later monitor of the adapter, not just the declaring target (proven on-glass: declare on 259 under WUDFHost 2192, then 257's frames streamed pointer-free with CURSOR_SHOWING set and SM_MOUSEPRESENT true). GameStream/Moonlight clients cannot draw a forwarded cursor, so those sessions were silently cursor-less. ADD replies now report any-declare-this-WUDFHost-life (the state's real scope — it dies with the adapter reset, same as before); per-target ids stay only as the dbglog audit trail. Channel-less sessions on a declared adapter then hit the existing forced-composite gate (GDI poller + blend) and carry the pointer in-video. Host code unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -80,12 +80,13 @@ pub const fn interface_guid_fields() -> (u32, u16, u16, [u8; 8]) {
|
||||
/// and the desktop model gets exclusion + forwarding. Nothing existing changed; against a v5
|
||||
/// driver the unknown IOCTL fails and the host logs + keeps the declared-at-ADD behavior.
|
||||
/// v6 tail ext (no bump, the `AddRequest` luminance-tail discipline):
|
||||
/// [`control::AddReply::cursor_excluded`] — the driver reports whether the ADDed monitor's OS
|
||||
/// target already carries a hardware-cursor declare from an earlier session. A declare is
|
||||
/// IRREVOCABLE for the target's life (remote-desktop-sweep §8.6, proven on-glass): DWM never
|
||||
/// composites the software cursor back into that target's frames, and the sticky state survives
|
||||
/// monitor REMOVE→ADD because the host hands every client a STABLE target id. The host uses the
|
||||
/// flag to self-composite the pointer (GDI poller + blend) in sessions that never negotiate the
|
||||
/// [`control::AddReply::cursor_excluded`] — the driver reports whether its ADAPTER already
|
||||
/// carries a hardware-cursor declare from an earlier session. A declare is IRREVOCABLE
|
||||
/// (remote-desktop-sweep §8.6, proven on-glass) and its exclusion reaches EVERY later monitor of
|
||||
/// the adapter, not just the declaring target (on-glass 2026-07-23: a declare on one target left
|
||||
/// a different client's fresh target cursor-less): DWM never composites the software cursor back
|
||||
/// into any of the adapter's frames until the adapter resets. The host uses the flag to
|
||||
/// self-composite the pointer (GDI poller + blend) in sessions that never negotiate the
|
||||
/// cursor channel — without it those sessions are silently cursor-less. Both skews degrade
|
||||
/// cleanly: an old driver writes only the 20-byte reply prefix (host reads `0` = unknown/clean),
|
||||
/// an old host retrieves a 20-byte buffer (driver writes just the prefix).
|
||||
@@ -217,9 +218,10 @@ pub mod control {
|
||||
/// `DuplicateHandle`, then [`IOCTL_SET_FRAME_CHANNEL`]). Reported per-ADD, not per-open, so a
|
||||
/// WUDFHost restart between sessions can never leave the host duplicating into a dead process.
|
||||
pub wudf_pid: u32,
|
||||
/// Non-zero = this monitor's OS target already carries an IRREVOCABLE hardware-cursor
|
||||
/// declare from an earlier session (remote-desktop-sweep §8.6): DWM excludes the pointer
|
||||
/// from every frame on this target, forever, and a session without the cursor channel must
|
||||
/// Non-zero = the ADAPTER already carries an IRREVOCABLE hardware-cursor declare from an
|
||||
/// earlier session (remote-desktop-sweep §8.6; reach is adapter-wide, not per-target —
|
||||
/// on-glass 2026-07-23): DWM excludes the pointer from every frame on every monitor until
|
||||
/// the adapter resets, and a session without the cursor channel must
|
||||
/// self-composite (GDI poller + blend) or stream a cursor-less desktop. Appended after
|
||||
/// [`ADD_REPLY_LEGACY_SIZE`] under the same dual-size discipline as the `AddRequest`
|
||||
/// luminance tail: an un-upgraded driver writes only the legacy prefix (the host's
|
||||
|
||||
@@ -32,10 +32,12 @@ pub struct WinCaptureTarget {
|
||||
/// duplicates the sealed frame channel's handles INTO (`idd_push::ChannelBroker`). `0` = unknown
|
||||
/// (a pre-v2 pairing can't occur — the version handshake is hard — so this only guards misuse).
|
||||
pub wudf_pid: u32,
|
||||
/// The ADD reply flagged this target as carrying an IRREVOCABLE IddCx hardware-cursor declare
|
||||
/// from an earlier session (remote-desktop-sweep §8.6): DWM excludes the pointer from its
|
||||
/// frames forever, so a session WITHOUT the cursor channel must self-composite (the IDD-push
|
||||
/// capturer's forced-composite gate) or the streamed desktop has no cursor at all.
|
||||
/// The ADD reply flagged the ADAPTER as carrying an IRREVOCABLE IddCx hardware-cursor declare
|
||||
/// from an earlier session (remote-desktop-sweep §8.6; reach is adapter-wide, not per-target —
|
||||
/// on-glass 2026-07-23, a GameStream session's fresh target streamed cursor-less): DWM
|
||||
/// excludes the pointer from its frames until adapter reset, so a session WITHOUT the cursor
|
||||
/// channel must self-composite (the IDD-push capturer's forced-composite gate) or the
|
||||
/// streamed desktop has no cursor at all.
|
||||
pub cursor_excluded: bool,
|
||||
}
|
||||
|
||||
|
||||
@@ -165,9 +165,11 @@ unsafe fn add(request: WDFREQUEST) {
|
||||
// This WUDFHost's pid — where the host duplicates the sealed frame channel's handles INTO
|
||||
// (`ProcessSharingDisabled`: this process is exclusively ours and dies with the device).
|
||||
wudf_pid: std::process::id(),
|
||||
// The target already carries an irrevocable hardware-cursor declare from an earlier
|
||||
// session — a channel-less session must self-composite the pointer (§8.6 gap).
|
||||
cursor_excluded: crate::monitor::target_declared(target_id) as u32,
|
||||
// The ADAPTER already carries an irrevocable hardware-cursor declare from an earlier
|
||||
// session — DWM's exclusion reaches every later monitor, not just the declaring target
|
||||
// (on-glass 2026-07-23: declare on 259 left a fresh GameStream 257 cursor-less) — so a
|
||||
// channel-less session must self-composite the pointer (§8.6 gap, adapter-wide).
|
||||
cursor_excluded: crate::monitor::any_declared() as u32,
|
||||
};
|
||||
// Dual-size reply (the `cursor_excluded` tail ext): an un-upgraded host retrieves only the
|
||||
// legacy 20-byte buffer — write the prefix it asked for instead of failing its ADD.
|
||||
|
||||
@@ -200,15 +200,18 @@ fn cursor_forward_desired(target_id: u32) -> bool {
|
||||
}
|
||||
|
||||
/// OS target ids on which `IddCxMonitorSetupHardwareCursor` ever SUCCEEDED. A declare is
|
||||
/// IRREVOCABLE for the target's life (no un-declare DDI; empty-caps re-setup is rejected; even a
|
||||
/// successful same-mode re-commit never brings DWM's composited pointer back — all proven
|
||||
/// on-glass, remote-desktop-sweep §8.6) and it survives monitor REMOVE→ADD because the host hands
|
||||
/// each client a STABLE target id. Reported back on every ADD
|
||||
/// ([`AddReply::cursor_excluded`](pf_driver_proto::control::AddReply)) so a session that never
|
||||
/// negotiates the cursor channel knows it must self-composite the pointer instead of streaming a
|
||||
/// cursor-less desktop. Never cleared: the state's true scope is this WUDFHost's life
|
||||
/// (`ProcessSharingDisabled` — the process, and with it the adapter and every sticky declare,
|
||||
/// dies on adapter reset), which is exactly when this static resets too. Bounded: ≤ 16 ids.
|
||||
/// IRREVOCABLE (no un-declare DDI; empty-caps re-setup is rejected; even a successful same-mode
|
||||
/// re-commit never brings DWM's composited pointer back — all proven on-glass,
|
||||
/// remote-desktop-sweep §8.6) and its EXCLUSION REACH IS THE WHOLE ADAPTER, not the declaring
|
||||
/// target: a declare on one target leaves every LATER monitor's frames pointer-free too, even a
|
||||
/// fresh never-declared target id (proven on-glass 2026-07-23: declare on 259, then a GameStream
|
||||
/// session's fresh 257 streamed a cursor-less desktop). ADD replies therefore report
|
||||
/// [`AddReply::cursor_excluded`](pf_driver_proto::control::AddReply) from [`any_declared`] —
|
||||
/// a session that never negotiates the cursor channel must self-composite the pointer instead
|
||||
/// of streaming a cursor-less desktop. Never cleared: the state's true scope is this WUDFHost's
|
||||
/// life (`ProcessSharingDisabled` — the process, and with it the adapter and every sticky
|
||||
/// declare, dies on adapter reset), which is exactly when this static resets too. Per-target
|
||||
/// ids are kept purely for the dbglog audit trail. Bounded: ≤ 16 ids.
|
||||
static DECLARED_TARGETS: Mutex<Vec<u32>> = Mutex::new(Vec::new());
|
||||
|
||||
/// Record a SUCCESSFUL hardware-cursor declare on `target_id` (see [`DECLARED_TARGETS`]).
|
||||
@@ -220,12 +223,13 @@ fn mark_declared(target_id: u32) {
|
||||
}
|
||||
}
|
||||
|
||||
/// True if `target_id` ever had a hardware cursor declared (see [`DECLARED_TARGETS`]).
|
||||
pub fn target_declared(target_id: u32) -> bool {
|
||||
DECLARED_TARGETS
|
||||
/// True if ANY target ever had a hardware cursor declared in this WUDFHost's life — the
|
||||
/// adapter-wide exclusion reach (see [`DECLARED_TARGETS`]).
|
||||
pub fn any_declared() -> bool {
|
||||
!DECLARED_TARGETS
|
||||
.lock()
|
||||
.unwrap_or_else(|e| e.into_inner())
|
||||
.contains(&target_id)
|
||||
.is_empty()
|
||||
}
|
||||
|
||||
/// Record a departing monitor's advertised list for its id ([`MODE_HISTORY`]).
|
||||
|
||||
Reference in New Issue
Block a user