Files
codex/codex-rs/utils/pty/src/windows_input.rs
iceweasel-oai a781761eda [codex] fix Windows ConPTY input handling (#29734)
## Why

Windows unified-exec TTY input did not behave like the non-Windows PTY
path. ConPTY sessions could receive the wrong line ending or mishandle
backspace, especially when sending input to a foreground program through
PowerShell or cmd. The local, legacy restricted, and elevated paths also
handled this normalization separately.

## What changed

- share one stateful Windows TTY input normalizer across local, legacy
restricted, and elevated runner paths
- translate LF and split CRLF into one Windows terminal Enter, encode
backspace as DEL, and preserve UTF-8 and control bytes such as Ctrl-C
- add Windows integration coverage for Unicode input, backspace, Enter,
and PowerShell foreground-child Ctrl-C behavior

## Validation

- `just test -p codex-utils-pty` (13 tests passed; the Unicode
integration test retried once)
- the Unicode integration test passed five consecutive runs with retries
disabled
- integration coverage sends `cafeé 漢字` through cmd and PowerShell and
verifies that Ctrl-C interrupts a running PowerShell foreground child
2026-06-24 11:27:44 -07:00

36 lines
1.2 KiB
Rust

/// Stateful normalizer for bytes written to a Windows pseudoconsole.
///
/// ConPTY accepts UTF-8 input, but an Enter key is represented by a carriage
/// return on Windows. This converts line feeds and collapses existing CRLF
/// sequences, including when the two bytes arrive in separate writes, so each
/// requested newline submits exactly one line. Backspace is encoded as DEL,
/// which ConPTY translates to `VK_BACK`. All other bytes, including UTF-8 and
/// terminal control characters, pass through unchanged.
#[derive(Default)]
pub struct WindowsTtyInputNormalizer {
previous_was_cr: bool,
}
impl WindowsTtyInputNormalizer {
pub fn normalize(&mut self, bytes: &[u8]) -> Vec<u8> {
let mut normalized = Vec::with_capacity(bytes.len());
for &byte in bytes {
match byte {
b'\x08' => normalized.push(b'\x7f'),
b'\n' => {
if !self.previous_was_cr {
normalized.push(b'\r');
}
}
_ => normalized.push(byte),
}
self.previous_was_cr = byte == b'\r';
}
normalized
}
}
#[cfg(test)]
#[path = "windows_input_tests.rs"]
mod tests;