mirror of
https://github.com/herdrdev/herdr.git
synced 2026-10-07 16:00:41 +00:00
fix(windows): preserve mouse capture during refresh (#4319)
* fix(windows): preserve mouse capture during refresh refs #4284 * test(windows): fix default input gauntlet invocation * fix(windows): preserve pixel mouse transitions refs #4284
This commit is contained in:
@@ -29,8 +29,10 @@ $root = [IO.Directory]::CreateDirectory($OutputDirectory).FullName
|
||||
$null = Invoke-GauntletProcess $python @($reportScript, 'matrix', '--output', (Join-Path $root 'matrix.json'))
|
||||
$matrix = Read-GauntletJson (Join-Path $root 'matrix.json')
|
||||
if ($MatrixOnly) { Write-Host "Matrix: $root/matrix.json"; exit 0 }
|
||||
$caseSelectionProvided = $PSBoundParameters.ContainsKey('Cases')
|
||||
$Cases = @($Cases | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })
|
||||
$knownCases = @($matrix.cases | ForEach-Object id)
|
||||
if (@($Cases).Count -and @($Cases | Where-Object { $_ -notin $knownCases }).Count) { throw 'Unknown case selection' }
|
||||
if (($caseSelectionProvided -and -not @($Cases).Count) -or @($Cases | Where-Object { $_ -notin $knownCases }).Count) { throw 'Unknown case selection' }
|
||||
$selectedCases = if (@($Cases).Count) { @($matrix.cases | Where-Object id -in $Cases) } else { @($matrix.cases) }
|
||||
if (-not $IsWindows) { throw 'Real-host qualification requires Windows and an interactive desktop; no tests passed' }
|
||||
if (-not $AllowInputInjection) { throw 'Read scripts/windows_input/README.md, then explicitly pass -AllowInputInjection on an isolated desktop' }
|
||||
|
||||
@@ -51,6 +51,16 @@ The full catalogue normally exits `2` because operator-assisted and explicitly
|
||||
unimplemented qualification cases remain. That is incomplete coverage, not an
|
||||
automated test failure; inspect the printed matrix and retained `report.json`.
|
||||
|
||||
### Expected outcome
|
||||
|
||||
A healthy run builds and identifies the current checkout, reaches the result
|
||||
matrix, and reports no `FAIL` rows or cleanup errors. The default Herdr column
|
||||
should identify itself as `Win32 (Herdr)*` from captured runtime evidence. A full
|
||||
run may still exit `2` and show `MANUAL`, `NOT TESTED`, or a documented host
|
||||
capability limitation when a terminal channel, physical gesture, or oracle is
|
||||
unavailable. A focused campaign exits `0` when every selected observation passes;
|
||||
exit `1` means an assertion, harness, or cleanup failure and needs investigation.
|
||||
|
||||
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
|
||||
|
||||
@@ -348,7 +348,9 @@ fn set_windows_native_mouse_capture<W: io::Write>(
|
||||
sgr_pixels: bool,
|
||||
set_console_capture: impl FnOnce(bool) -> io::Result<()>,
|
||||
) -> io::Result<()> {
|
||||
crate::terminal_modes::clear_host_mouse_reporting(writer)?;
|
||||
if !enabled {
|
||||
crate::terminal_modes::clear_host_mouse_reporting(writer)?;
|
||||
}
|
||||
set_console_capture(enabled)?;
|
||||
if enabled {
|
||||
crate::terminal_modes::set_windows_mouse_reporting(writer, true, sgr_pixels)?;
|
||||
@@ -365,7 +367,9 @@ fn windows_uses_vt_mouse_reporting() -> bool {
|
||||
pub(super) fn set_mouse_capture(enabled: bool, sgr_pixels: bool) -> io::Result<()> {
|
||||
#[cfg(windows)]
|
||||
if windows_uses_vt_mouse_reporting() {
|
||||
crate::terminal_modes::clear_host_mouse_reporting(&mut io::stdout())?;
|
||||
if !enabled {
|
||||
crate::terminal_modes::clear_host_mouse_reporting(&mut io::stdout())?;
|
||||
}
|
||||
return crate::terminal_modes::set_windows_mouse_reporting(
|
||||
&mut io::stdout(),
|
||||
enabled,
|
||||
@@ -642,7 +646,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn windows_native_mouse_capture_never_resets_encoding_after_native_enable() {
|
||||
fn windows_native_mouse_capture_reasserts_final_encoding_after_native_enable() {
|
||||
let mut output = SharedOutput::default();
|
||||
for sgr_pixels in [false, false, true, false] {
|
||||
let start = output.0.borrow().len();
|
||||
@@ -663,8 +667,8 @@ mod tests {
|
||||
let bytes = output.0.borrow();
|
||||
let before = std::str::from_utf8(&bytes[start..native_boundary.get()]).unwrap();
|
||||
let after = std::str::from_utf8(&bytes[native_boundary.get()..]).unwrap();
|
||||
assert!(before.contains("\x1b[?1016l"));
|
||||
for reset in ["\x1b[?1005l", "\x1b[?1006l", "\x1b[?1016l"] {
|
||||
assert!(!before.contains("\x1b[?1016l"));
|
||||
for reset in ["\x1b[?1005l", "\x1b[?1006l"] {
|
||||
assert!(
|
||||
!after.contains(reset),
|
||||
"mouse format reset after native capture (sgr_pixels={sgr_pixels}): {after:?}"
|
||||
@@ -672,22 +676,25 @@ mod tests {
|
||||
}
|
||||
assert!(after.contains("\x1b[?1003h\x1b[?1006h"));
|
||||
assert_eq!(after.contains("\x1b[?1016h"), sgr_pixels);
|
||||
if !sgr_pixels {
|
||||
assert!(after.starts_with("\x1b[?1016l"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn windows_native_mouse_capture_restores_reporting_after_reset() {
|
||||
fn windows_native_mouse_capture_disable_clears_reporting() {
|
||||
let mut output = Vec::new();
|
||||
|
||||
set_windows_native_mouse_capture(&mut output, true, false, |enabled| {
|
||||
assert!(enabled);
|
||||
set_windows_native_mouse_capture(&mut output, false, false, |enabled| {
|
||||
assert!(!enabled);
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
output,
|
||||
b"\x1b[?1006l\x1b[?1016l\x1b[?1015l\x1b[?1005l\x1b[?1003l\x1b[?1002l\x1b[?1000l\x1b[?9l\x1b[?1000h\x1b[?1002h\x1b[?1003h\x1b[?1006h"
|
||||
b"\x1b[?1006l\x1b[?1016l\x1b[?1015l\x1b[?1005l\x1b[?1003l\x1b[?1002l\x1b[?1000l\x1b[?9l"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+12
-9
@@ -21,15 +21,18 @@ pub(crate) fn set_windows_mouse_reporting<W: Write>(
|
||||
enabled: bool,
|
||||
sgr_pixels: bool,
|
||||
) -> io::Result<()> {
|
||||
writer.write_all(if enabled {
|
||||
WINDOWS_MOUSE_REPORTING_ENABLE_SEQUENCE
|
||||
if enabled {
|
||||
// Keep a true-to-false pixel transition in the same buffered reassertion,
|
||||
// before the final SGR enable, so refresh never exposes a disabled gap.
|
||||
if !sgr_pixels {
|
||||
writer.write_all(b"\x1b[?1016l")?;
|
||||
}
|
||||
writer.write_all(WINDOWS_MOUSE_REPORTING_ENABLE_SEQUENCE)?;
|
||||
if sgr_pixels {
|
||||
writer.write_all(b"\x1b[?1016h")?;
|
||||
}
|
||||
} else {
|
||||
WINDOWS_MOUSE_REPORTING_DISABLE_SEQUENCE
|
||||
})?;
|
||||
// The caller clears modes before native capture. Resetting 1016 here can
|
||||
// undo the SGR encoding that ConPTY has already enabled on the host.
|
||||
if enabled && sgr_pixels {
|
||||
writer.write_all(b"\x1b[?1016h")?;
|
||||
writer.write_all(WINDOWS_MOUSE_REPORTING_DISABLE_SEQUENCE)?;
|
||||
}
|
||||
writer.flush()
|
||||
}
|
||||
@@ -195,7 +198,7 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
output,
|
||||
b"\x1b[?1000h\x1b[?1002h\x1b[?1003h\x1b[?1006h\x1b[?1016h\x1b[?1000h\x1b[?1002h\x1b[?1003h\x1b[?1006h\x1b[?1016l\x1b[?1006l\x1b[?1003l\x1b[?1002l\x1b[?1000l"
|
||||
b"\x1b[?1000h\x1b[?1002h\x1b[?1003h\x1b[?1006h\x1b[?1016h\x1b[?1016l\x1b[?1000h\x1b[?1002h\x1b[?1003h\x1b[?1006h\x1b[?1016l\x1b[?1006l\x1b[?1003l\x1b[?1002l\x1b[?1000l"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user