diff options
Diffstat (limited to 'scripts')
| -rw-r--r-- | scripts/windows_check.ps1 | 68 | ||||
| -rw-r--r-- | scripts/windows_conpty_enhanced_input_probe.ps1 | 169 |
2 files changed, 191 insertions, 46 deletions
diff --git a/scripts/windows_check.ps1 b/scripts/windows_check.ps1 index 7c225301..1907d66b 100644 --- a/scripts/windows_check.ps1 +++ b/scripts/windows_check.ps1 @@ -29,6 +29,53 @@ function Invoke-CargoWithZigCacheRecovery { Invoke-Checked cargo $Arguments } +function Invoke-CargoTestFilter { + param( + [Parameter(Mandatory)] + [string]$Filter, + [switch]$Exact + ) + + $commonArguments = @( + "test", + "--locked", + "--target", + "x86_64-pc-windows-msvc", + "--bin", + "herdr", + $Filter + ) + $harnessArguments = @("--list") + if ($Exact) { + $harnessArguments += "--exact" + } + + $listArguments = $commonArguments + @("--") + $harnessArguments + $listOutput = @(& cargo @listArguments) + if ($LASTEXITCODE -ne 0) { + throw "could not enumerate tests for filter '$Filter': $($listOutput -join [Environment]::NewLine)" + } + + $testNames = @( + foreach ($line in $listOutput) { + $match = [regex]::Match([string]$line, '^\s*(\S+): test\s*$') + if ($match.Success) { + $match.Groups[1].Value + } + } + ) + if ($testNames.Count -eq 0) { + throw "test filter '$Filter' selected zero tests" + } + + Write-Host "Running $($testNames.Count) test(s) for '$Filter'" + $runArguments = $commonArguments + if ($Exact) { + $runArguments += @("--", "--exact") + } + Invoke-Checked cargo $runArguments +} + Invoke-Checked rustup @("target", "add", "x86_64-pc-windows-msvc") Invoke-Checked cargo @("fmt", "--check") Invoke-CargoWithZigCacheRecovery @( @@ -47,22 +94,7 @@ if ($Mode -eq "lint") { return } -Invoke-Checked cargo @( - "test", - "--locked", - "--target", - "x86_64-pc-windows-msvc", - "--bin", - "herdr", - "windows_" -) -Invoke-Checked cargo @( - "test", - "--locked", - "--target", - "x86_64-pc-windows-msvc", - "--bin", - "herdr", - "server::client_transport::tests" -) +Invoke-CargoTestFilter "windows_" +Invoke-CargoTestFilter "server::client_transport::tests" +Invoke-CargoTestFilter "app::tests::native_repeats_and_releases_follow_the_pressed_pane" -Exact Invoke-Checked cargo @("build", "--locked", "--target", "x86_64-pc-windows-msvc") diff --git a/scripts/windows_conpty_enhanced_input_probe.ps1 b/scripts/windows_conpty_enhanced_input_probe.ps1 index bbd2b40b..1e452521 100644 --- a/scripts/windows_conpty_enhanced_input_probe.ps1 +++ b/scripts/windows_conpty_enhanced_input_probe.ps1 @@ -132,6 +132,34 @@ function Send-RawAndObserve { } } +function Send-NativeRecordAndObserve { + param( + [string] $PaneId, + [string] $Text, + [string] $ExpectedRecord + ) + $needle = "PROBE_RECORD:$ExpectedRecord" + $before = ([regex]::Matches((Read-Pane -PaneId $PaneId), [regex]::Escape($needle))).Count + & $script:Exe pane send-text $PaneId $Text | Out-Null + if ($LASTEXITCODE -ne 0) { + throw "pane send-text failed with exit code $LASTEXITCODE" + } + $deadline = (Get-Date).AddSeconds(10) + do { + $paneText = Read-Pane -PaneId $PaneId + $after = ([regex]::Matches($paneText, [regex]::Escape($needle))).Count + if ($after -gt $before) { + break + } + Start-Sleep -Milliseconds 200 + } while ((Get-Date) -lt $deadline) + return [ordered]@{ + expected_record = $needle + delivered = $after -gt $before + pane = $paneText + } +} + $script:Exe = (Resolve-Path $ExePath).Path $workDir = Join-Path ([System.IO.Path]::GetTempPath()) "herdr-conpty-input-$([guid]::NewGuid().ToString('N'))" $probeSource = Join-Path $workDir "probe.rs" @@ -175,6 +203,12 @@ extern "system" { bytes_read: *mut u32, overlapped: *mut c_void, ) -> i32; + fn ReadConsoleInputW( + handle: Handle, + records: *mut InputRecord, + length: u32, + records_read: *mut u32, + ) -> i32; fn WriteFile( handle: Handle, buffer: *const c_void, @@ -184,6 +218,31 @@ extern "system" { ) -> i32; } +#[repr(C)] +#[derive(Clone, Copy)] +struct KeyEventRecord { + key_down: i32, + repeat_count: u16, + virtual_key_code: u16, + virtual_scan_code: u16, + unicode_char: u16, + control_key_state: u32, +} + +#[repr(C)] +#[derive(Clone, Copy)] +union InputRecordEvent { + key: KeyEventRecord, +} + +#[repr(C)] +struct InputRecord { + event_type: u16, + event: InputRecordEvent, +} + +const KEY_EVENT: u16 = 0x0001; + fn write_all(handle: Handle, mut bytes: &[u8]) { while !bytes.is_empty() { let mut written = 0; @@ -212,43 +271,88 @@ fn main() { write_all(output, b"PROBE_ERROR_GET_MODE\r\n"); std::process::exit(1); } - let raw_vt_mode = (console_mode | ENABLE_VIRTUAL_TERMINAL_INPUT) - & !(ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT); - if unsafe { SetConsoleMode(input, raw_vt_mode) } == 0 { - write_all(output, b"PROBE_ERROR_SET_MODE\r\n"); - std::process::exit(2); + if mode != "native" { + let raw_vt_mode = (console_mode | ENABLE_VIRTUAL_TERMINAL_INPUT) + & !(ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT); + if unsafe { SetConsoleMode(input, raw_vt_mode) } == 0 { + write_all(output, b"PROBE_ERROR_SET_MODE\r\n"); + std::process::exit(2); + } } - if mode == "kitty" { + if mode == "native" { + write_all(output, b"PROBE_READY_NATIVE\r\n"); + } else if mode == "kitty" { write_all(output, b"\x1b[>7u\x1b[?u\x1b[cPROBE_READY_KITTY\r\n"); } else { write_all(output, b"PROBE_READY_LEGACY\r\n"); } - let mut all = Vec::new(); - let mut buffer = [0u8; 256]; - loop { - let mut read = 0; - let ok = unsafe { - ReadFile( - input, - buffer.as_mut_ptr().cast(), - buffer.len() as u32, - &mut read, - std::ptr::null_mut(), - ) - }; - if ok == 0 || read == 0 { - break; + if mode == "native" { + let mut records: [InputRecord; 16] = std::array::from_fn(|_| InputRecord { + event_type: 0, + event: InputRecordEvent { + key: KeyEventRecord { + key_down: 0, + repeat_count: 0, + virtual_key_code: 0, + virtual_scan_code: 0, + unicode_char: 0, + control_key_state: 0, + }, + }, + }); + loop { + let mut read = 0; + let ok = unsafe { + ReadConsoleInputW(input, records.as_mut_ptr(), records.len() as u32, &mut read) + }; + if ok == 0 || read == 0 { + break; + } + for record in &records[..read as usize] { + if record.event_type != KEY_EVENT { + continue; + } + let key = unsafe { &record.event.key }; + let line = format!( + "PROBE_RECORD:{};{};{};{};{};{}\r\n", + if key.key_down != 0 { 1 } else { 0 }, + key.repeat_count, + key.virtual_key_code, + key.virtual_scan_code, + key.unicode_char, + key.control_key_state, + ); + write_all(output, line.as_bytes()); + } } - all.extend_from_slice(&buffer[..read as usize]); - let mut line = String::from("PROBE_ALL:"); - for byte in &all { - use std::fmt::Write as _; - let _ = write!(&mut line, "{byte:02x}"); + } else { + let mut all = Vec::new(); + let mut buffer = [0u8; 256]; + loop { + let mut read = 0; + let ok = unsafe { + ReadFile( + input, + buffer.as_mut_ptr().cast(), + buffer.len() as u32, + &mut read, + std::ptr::null_mut(), + ) + }; + if ok == 0 || read == 0 { + break; + } + all.extend_from_slice(&buffer[..read as usize]); + let mut line = String::from("PROBE_ALL:"); + for byte in &all { + use std::fmt::Write as _; + let _ = write!(&mut line, "{byte:02x}"); + } + line.push_str("\r\n"); + write_all(output, line.as_bytes()); } - line.push_str("\r\n"); - write_all(output, line.as_bytes()); } } '@ | Set-Content -NoNewline -Encoding utf8 $probeSource @@ -311,6 +415,13 @@ fn main() { $report.raw_alt_v = Send-RawAndObserve -PaneId $kittyPane -Text ([char]27 + "v") -ExpectedHex "1b76" $report.raw_ctrl_u = Send-RawAndObserve -PaneId $kittyPane -Text ([string][char]0x15) -ExpectedHex "15" + $nativePane = New-ProbePane -Mode "native" + # Win32 input mode records use CSI Vk;Scan;Unicode;Down;Control;Repeat _. + $nativeEscapeDown = [char]27 + "[27;1;27;1;0;3_" + $nativeEscapeRelease = [char]27 + "[27;1;27;0;0;1_" + $report.native_escape_down = Send-NativeRecordAndObserve -PaneId $nativePane -Text $nativeEscapeDown -ExpectedRecord "1;3;27;1;27;0" + $report.native_escape_release = Send-NativeRecordAndObserve -PaneId $nativePane -Text $nativeEscapeRelease -ExpectedRecord "0;1;27;1;27;0" + $consoleHosts = @(Get-Process -Name conhost, OpenConsole -ErrorAction SilentlyContinue | Where-Object { $initialConsoleHostIds -notcontains $_.Id } | ForEach-Object { @@ -354,6 +465,8 @@ fn main() { -or -not $report.raw_kitty_ctrl_delete.delivered ` -or -not $report.raw_alt_v.delivered ` -or -not $report.raw_ctrl_u.delivered ` + -or -not $report.native_escape_down.delivered ` + -or -not $report.native_escape_release.delivered ` -or ($appLocalHostRequired -and -not $report.app_local_console_host) } finally { if ($null -ne $server) { |
