feat(probe): --cursor-hold — stop the wiggle so the pointer can be parked on a target

The capture-model repro flags (`--cursor-capture` / `--cursor-nochannel`) drive
relative pointer motion in circles for the whole dump, which is right for
keeping a damage-driven desktop publishing frames but makes the pointer
impossible to aim: at radius 10 every 25 ms it walks several hundred pixels a
second, so a `SetCursorPos` on the host is undone before the next frame.

That mattered because the shape UNDER the pointer is the whole question. The
arrow is a colour cursor and proves nothing about the monochrome path — the
I-beam is the only common Windows system cursor with `hbmColor == null`, so it
is the only one that exercises `mono_planes_to_rgba`. Without being able to
park the pointer on a text field there is no way to photograph the case that
matters.

`--cursor-hold` primes the wiggle for ~3 s (enough to clear CURSOR_SUPPRESSED
and get metadata flowing) and then stops, leaving the pointer wherever the host
puts it for the rest of the dump.

Used it to settle the question on .173: a `--cursor-nochannel` session — the
byte-identical wire behaviour of an iPad/Android/tvOS client, which never
advertises CLIENT_CAP_CURSOR — receives a correctly rendered monochrome I-beam
in the video. Decoded from the dump with ffmpeg; the host log shows the session
took the forced-composite path with the GDI poller live.

Verified: `scripts/xcheck.sh windows` green; `cargo fmt --all` clean.
This commit is contained in:
2026-08-08 17:03:33 +02:00
parent 7f6d1622ee
commit 62624c1daf
+24 -3
View File
@@ -129,6 +129,16 @@ struct Args {
/// host must composite the metadata cursor on its own; decode the dump and look for the
/// pointer.
cursor_nochannel: bool,
/// `--cursor-hold` — with `--cursor-capture`/`--cursor-nochannel`, stop the relative wiggle
/// after a short priming burst instead of circling forever. The wiggle exists to keep a
/// damage-driven desktop publishing frames, but it also DRAGS the host pointer several hundred
/// pixels a second, which makes it impossible to hold the pointer over a chosen target — and
/// the shape under the pointer is the whole point when the question is "does the MONOCHROME
/// I-beam survive compositing?" (the arrow is a colour cursor and proves nothing about the
/// mono path). With this flag: prime for ~3 s so the pointer is un-suppressed and metadata is
/// flowing, then hold still so a `SetCursorPos` on the host can park it on a text field for
/// the rest of the dump.
cursor_hold: bool,
/// `--discover [SECS]` — browse the LAN for native (`_punktfunk._udp`) hosts for `SECS`
/// seconds (default 4), print what's found, and exit. No connection is made.
discover: Option<u64>,
@@ -309,6 +319,7 @@ fn parse_args() -> Args {
clock_resync: argv.iter().any(|a| a == "--clock-resync"),
cursor_capture: argv.iter().any(|a| a == "--cursor-capture"),
cursor_nochannel: argv.iter().any(|a| a == "--cursor-nochannel"),
cursor_hold: argv.iter().any(|a| a == "--cursor-hold"),
}
}
@@ -900,13 +911,23 @@ async fn session(args: Args) -> Result<()> {
}
});
let wiggle_conn = conn.clone();
let hold = args.cursor_hold;
tokio::spawn(async move {
// Relative circles, forever: keeps the host pointer moving (and, on metadata-cursor
// compositors, keeps cursor updates flowing) for the whole dump.
// Relative circles: keeps the host pointer moving (and, on metadata-cursor
// compositors, keeps cursor updates flowing) for the whole dump — unless
// `--cursor-hold`, which primes and then stops so the pointer can be parked.
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
tracing::info!("cursor-capture: relative pointer wiggle running");
tracing::info!(hold, "cursor-capture: relative pointer wiggle running");
let prime_until = std::time::Instant::now() + std::time::Duration::from_secs(3);
let mut t = 0.0f64;
loop {
if hold && std::time::Instant::now() >= prime_until {
tracing::info!(
"cursor-capture: wiggle primed and STOPPED (--cursor-hold) — the pointer \
now stays where the host puts it"
);
return;
}
let e = InputEvent {
kind: InputKind::MouseMove,
_pad: [0; 3],