Local Windows input gauntlet (experimental)
This is local Windows qualification infrastructure, not a claim that all Windows
input works. It sends real scan-code gestures through a new Windows Terminal
window, both directly to an observer and through an attached Herdr client. It
does not substitute pane send-keys for host input.
Safety and prerequisites
Use an unlocked, isolated interactive desktop where you will not do other
work during automated input. The runner changes foreground focus and window
size. SendInput has an unavoidable check-to-use focus race; a foreground check
is not an OS security boundary. The runner rejects an elevated controller and
checks the target Terminal process token before focus, resize, and injection.
An elevation-query failure is also a refusal, not permission to proceed. Start
PowerShell and Terminal without Run as administrator, on a dedicated desktop. There is no unattended CI job or runner permission
change in this implementation.
Required: Windows, PowerShell 7 (pwsh), Python 3, Rust/Cargo, and both
Windows Terminal channels for the default run. Stable and Preview are discovered independently
through their installed packages. Explicit paths are available when discovery
does not work. No software is installed or updated. The actual Terminal process path/version is recorded,
not inferred from the requested channel or bundled OpenConsole version. Stable
and Preview must resolve to distinct installations: the runner compares Windows
file/directory identities before launch, then checks actual image, installation,
and PID/start-time identities after activation. Aliases cannot count one install
twice. The portable report validator rejects duplicate identities too.
F12 aborts before the next automatic gesture. Moving focus away also aborts further automatic input. Each injected chord contains its own releases; there are no intentionally held modifiers between calls. Do not hold physical modifiers or mouse buttons while starting a run. Normal controller cancellation runs cleanup; bootstrap/probe leases also expire if the controller disappears. Lease expiry is not a replacement for a secure isolated desktop.
just test-windows-input clears the clipboard once before the build when the
selected cases use it. Save anything you need first: existing text, images,
files, and other formats are discarded and cannot be restored. Direct script
calls need -ClearClipboard for the same behavior; without it, a nonempty
clipboard stops the run before the build. The runner writes its synthetic data
while holding the clipboard lock and clears it afterward only if its sequence
number is unchanged. A later user clipboard update is left alone. No original
clipboard contents are logged.
Run
From the repository in PowerShell 7:
just test-windows-input
The default runs the original input matrix as three targeted observer runs: Stable WT through Herdr with legacy and modifyOtherKeys observers, and Preview WT through Herdr with Kitty. Herdr's outer reader stays on its default Win32 policy. It tests the full selected cases at 120×30, then the existing input, paste, and mouse sentinels at 80×24 and 80×30. PageUp/PageDown scrolling, AltGr, and IME remain explicit manual checks. The printed matrix has one column per host path; it does not combine Stable and Preview into one verdict.
Expected outcome
A healthy default run prints PASS for every automated cell, MANUAL for the
physical checks, and no cleanup errors. The Stable column identifies itself as
Win32 only from captured runtime evidence. A missing host, missing Win32 reader
evidence, unreachable required size, or unavailable assertion exits 2; an
assertion or cleanup failure exits 1. Inspect the retained report.json for
exact observations.
The recipe itself is the explicit opt-in to foreground input injection. It builds
the current checkout in release mode, stages that exact binary with the pinned
ConPTY runtime, and prints its path and hash. Use -ExePath only to compare a
specific old/new packaged binary.
Or invoke the script directly:
pwsh -NoProfile -File scripts/test_windows_input.ps1 `
-ExePath 'C:\test-app\herdr.exe' -AllowInputInjection -ClearClipboard `
-StablePath 'C:\TerminalStable\WindowsTerminal.exe' `
-PreviewPath 'C:\TerminalPreview\WindowsTerminal.exe'
The default tests Herdr's default input policy. Pass -Full for the full
channel × path × mode × geometry diagnostic catalogue. Any explicit -Profile,
-Modes, -Channels, -Paths, -Cases, -Widths, -Heights, or -Manual
selection also runs a diagnostic campaign. In a direct PowerShell invocation,
supply arrays normally:
.\scripts\test_windows_input.ps1 -ExePath 'C:\test-app\herdr.exe' `
-AllowInputInjection -ClearClipboard -Modes legacy,kitty -Cases mouse-interleave,mode-transitions `
-Widths 120 -Heights 30
-Paths herdr-remote runs the current client against its disposable local
server with the remote clipboard bridge active. It needs no SSH host. A focused
clipboard-image qualification is:
.\scripts\test_windows_input.ps1 -AllowInputInjection -ClearClipboard `
-Channels stable -Paths direct,herdr-remote -Modes legacy `
-Cases clipboard-image,clipboard-mixed -Widths 80 -Heights 24
Dead-key acute composition is automatic when the target Terminal thread's
active layout exposes that physical mapping. The runner discovers and injects
the real scan-code chord; it never substitutes pasted or Unicode-packet text.
-Manual enables AltGr, IME, and the remaining guided composition cases. The operator must activate the
indicated layout/IME, perform the gesture in the test window, then return to the
controller and press Enter. Finish each prompt within the 90-second lease.
Record the layout you actually selected in your qualification notes; the report
also includes the observed foreground thread layout handle. Do not paste text
for a composition case. The runner does not install/change global layouts.
You can inspect the full catalogue without Windows or desktop interaction:
pwsh -NoProfile -File scripts/test_windows_input.ps1 -ExePath unused -MatrixOnly
Every run needs a new output directory. By default it is
.local/windows-input/<unique-id>/. Do not reuse an old report directory.
What the implementation measures
- Native console records, including down/up, repeat, virtual/scan codes, Unicode, modifier state, and raw payloads for other native record types.
- Legacy VT, modifyOtherKeys level 2, and Kitty disambiguation observer modes. These are pane consumer modes, not names for Herdr's outer reader.
- Physical-style Enter/Shift+Enter and other modifier chords, navigation and word movement chords, control keys, and editing keys. The oracle checks bytes or records; it does not claim that a particular editor implements word selection correctly merely because Ctrl+Shift+Right reached it.
- Actual Windows clipboard paste via Ctrl+V, including LF/CRLF/CR, whitespace, BMP Unicode/combining characters, and escape-looking text. A host binding or multiline-paste confirmation dialog can intercept the gesture; the runner does not dismiss unexpected dialogs or rebind Terminal shortcuts.
- Image-only Ctrl+V through the remote clipboard bridge, plus a mixed image/text
clipboard check that proves Windows Terminal and Herdr preserve the text paste.
The report records
paste_origin, classified from the client's own mapper trace (terminal-paste,empty-paste,key-event, ornone). A staged image only passes when the origin is a positively observedempty-paste: a paste the terminal issued for clipboard text fails, and a missing or inconclusive trace isinconclusiverather than a qualification (#4314). - A full case pass at an observed 120×30 host size; the full diagnostic campaign also runs keyboard/paste sentinels at 80, 119, 120, 121, 132, 160, 240 columns, at 24 and 50 rows; then return to 80 columns. This exercises narrow→wide→narrow resizing of the actual outer window. Both outer and pane dimensions are captured. Herdr chrome means pane width is not the same as outer width. An unreachable size is not a pass.
- Automatic active-layout dead-key acute input, plus guided AltGr, extended accent, and IME commits, with exact expected committed text.
- Ordered typing, mouse-motion reports, and bracketed paste in one capture, plus click/wheel reporting before and after a real focus cycle, the same mouse sentinel after resize, and legacy→modifyOtherKeys→Kitty→modifyOtherKeys→legacy transitions without restart.
The focus/resize mouse check runs in Windows Terminal. It guards Herdr's recovery
sequence but does not certify the Tabby/Alacritty host-specific report in #4284.
The catalogue also lists explicit qualification gaps: mouse drag and
right-edge coordinate mapping; visual reflow/wrapping; native held-key repeat;
lock/keypad combinations; dead-key cancellation; IME cancellation; capture/config
reload and attach cycles; injected setup/recovery faults; supplementary-plane and
confirmation-triggering burst paste; non-image file clipboard integrations; and
positive host-scrollback evidence for native PageUp/PageDown. These are recorded
not_run or inconclusive, not fabricated successes. They need
separate fixtures/oracles before becoming automated assertions. The catalogue is
broad; the automated run does not cover every row.
Injected PageUp/PageDown remains inconclusive through Herdr. On Windows
Terminal 1.24, both SendInput and keybd_event produced zeroed non-key records
even in the direct-host baseline, so absence of pane input cannot prove that
Herdr consumed the key. Conclusive qualification requires literal physical input
plus a positive pane scroll-offset change; that guided check is not automated yet.
Evidence and verdicts
The retained directory contains:
matrix.json: all cases, profiles, and declared byte/record expectations;observations.json: raw captures, per-case identity, measured dimensions, readiness/focus flags, errors, and cleanup results;report.json: interpreted verdicts and counts;- per-window plans, nonce-bound observer acknowledgements, initial/final console modes, and bootstrap/probe error records.
The console ends with a capability matrix derived only from that run's captured
observations. The default pairs Stable/Win32 with Preview/Kitty; diagnostic
campaigns retain the direct-host Plain VT column. Filtered, unavailable, or
operator-assisted cases remain NOT TESTED or MANUAL; the full evidence and
reasons remain in report.json.
The Herdr column is labelled Win32 only after the current binary's selected
reader decodes a real nonce-owned Win32 serialized record. Configuration defaults
alone do not select the label; missing runtime evidence prints UNKNOWN.
Direct-host and through-Herdr observations are labelled separately. A direct-host
failure is not automatically a Herdr bug. Each row labels its failure scope as
direct_host or through_herdr_not_yet_attributed. For example, a terminal may
intercept Alt+Enter or use a different control-key encoding.
One explicit known-gap rule labels nonce-bound, complete WT 1.24/1.25
direct-host mOK captures of Shift/Ctrl/Ctrl+Shift+Enter or Shift+Tab as
unsupported only when they contain the exact legacy bytes. It does not exempt
other tests or versions, an empty/malformed capture, Kitty, or any through-Herdr
failure. Through-Herdr still must preserve the requested gesture.
Unsupported remains incomplete coverage, not green. Review captures before
adding other capability exceptions; never bless lost Shift information in Herdr
just to make a report green.
Comparisons consume the complete captured sequence, including a quiet interval to catch trailing duplicates/releases. Paste must have one intact bracketed wrapper and the complete payload. CR/LF variants are compared as logical newlines; this is not proof of byte-exact line-ending policy. Native keys require their non-modifier down/up pair, modifier state, and repeat count.
Exit codes:
0: all observed assertions passed and no recorded coverage gaps/errors;1: an assertion, harness, or cleanup failed;2: selected coverage is incomplete (for example, a missing host or unavailable geometry). The full diagnostic campaign also selects unimplemented catalogue rows.
An empty or missing report never means success. None of these statuses certifies an entire Windows host.
Cleanup and native handoff
Every window has a fresh nonce, named session, isolated config root, known PowerShell pane shell, and observer acknowledgement. Inherited Herdr socket, backend, session, and SSH variables are cleared in the actual launched process, not just in the controller. Setup APIs only create/focus the observer pane.
Cleanup targets the named session, retained child-process handles, and the exact nonce-bearing Terminal window. There is no process-name-wide kill or sweep of newly appeared OpenConsole processes. Artifacts are retained. Forced cleanup and console-mode mismatches are reported, not hidden behind a passing key test.
For release-risk input changes, qualify Stable and Preview, real focus-loss/F12 interruption, partial startup, clipboard changes, cleanup, and measured width boundaries. Use old and new Herdr binaries in separate runs to establish that a known regression is caught. Do not overwrite installations or reuse an earlier server. Portable unit tests validate the catalogue and verdict logic; they do not replace native Windows evidence.
The existing windows_conpty_enhanced_input_probe.ps1 remains the downstream API
control. Existing Rust keyboard/Windows-translator tests remain the deterministic
parser controls. This runner complements them rather than cross-producting their
fixtures or pretending they establish host behavior.