fix: preserve physical escape on windows

refs #1736
This commit is contained in:
Ogulcan Celik
2026-07-23 01:49:54 +03:00
parent 7e821ba7a3
commit 2a20e90a02
2 changed files with 63 additions and 5 deletions
+1
View File
@@ -7,6 +7,7 @@
### Fixed
- OMP integration install, status, and uninstall now respect `PI_CONFIG_DIR` when `PI_CODING_AGENT_DIR` is not set, and installation refuses extension-directory collisions with Pi. (#1696)
- Physical Escape key records on native Windows now bypass raw VT report framing, so pane applications receive Escape immediately and reliably. (#1736)
## [0.7.5] - 2026-07-21
+62 -5
View File
@@ -389,6 +389,7 @@ impl WindowsInputMapper {
key_down = key.key_down,
repeat_count = key.repeat_count,
virtual_key_code = key.virtual_key_code,
virtual_scan_code = key.virtual_scan_code,
unicode = key.unicode,
control_key_state = key.control_key_state,
"windows input trace: console key record"
@@ -452,8 +453,11 @@ impl WindowsInputMapper {
return false;
}
// Physical Escape carries a scan code. Scan-code-zero Escape can
// introduce raw VT reports and must stay in the framer.
let bare_escape = modifiers.is_empty()
&& (key.virtual_key_code == 0x1b || (key.virtual_key_code == 0 && key.unicode == 0x1b));
&& ((key.virtual_key_code == 0x1b && key.virtual_scan_code == 0)
|| (key.virtual_key_code == 0 && key.unicode == 0x1b));
let ctrl_bracket = key.virtual_key_code == 0xdb
&& key.unicode == 0x1b
&& modifiers == crossterm::event::KeyModifiers::CONTROL;
@@ -1338,9 +1342,11 @@ mod tests {
}
#[test]
fn vti_escape_key_record_flushes_to_escape_after_idle() {
fn vti_scan_code_zero_escape_key_record_flushes_after_idle() {
let mut translator = WindowsInputTranslator::default();
assert!(translator.translate(key_vk(0x1b, 0)).is_empty());
assert!(translator
.translate(key_vk_with_scan_unicode(0x1b, 0, '\0', 0))
.is_empty());
assert_eq!(
translator.idle(),
vec![crate::protocol::ClientInputEvent::Key {
@@ -1351,6 +1357,19 @@ mod tests {
);
}
#[test]
fn vti_physical_escape_key_record_is_immediately_semantic() {
let mut translator = WindowsInputTranslator::default();
assert_eq!(
translator.translate(key_vk_with_scan_unicode(0x1b, 0x01, '\x1b', 0)),
vec![crate::protocol::ClientInputEvent::Key {
code: crate::protocol::ClientKeyCode::Esc,
modifiers: 0,
kind: crate::protocol::ClientKeyKind::Press,
}]
);
}
#[test]
fn vti_repeated_escape_record_stays_semantic() {
assert_eq!(
@@ -1752,8 +1771,8 @@ mod tests {
}
#[test]
fn vti_escape_key_record_without_unicode_starts_mouse_sequence() {
let records = [key_vk(0x1b, 0)]
fn vti_scan_code_zero_escape_starts_mouse_sequence() {
let records = [key_vk_with_scan_unicode(0x1b, 0, '\0', 0)]
.into_iter()
.chain("[<35;48;26M".chars().map(key_char));
@@ -1768,6 +1787,44 @@ mod tests {
);
}
#[test]
fn vti_nonzero_scan_escape_is_immediately_semantic() {
let mut translator = WindowsInputTranslator::default();
assert_eq!(
translator.translate(key_vk_with_scan_unicode(0x1b, 0x02, '\0', 0)),
vec![crate::protocol::ClientInputEvent::Key {
code: crate::protocol::ClientKeyCode::Esc,
modifiers: 0,
kind: crate::protocol::ClientKeyKind::Press,
}]
);
}
#[test]
fn vti_win32_input_mode_physical_escape_is_immediately_semantic() {
let records = win32_input_mode_encoded_record(WindowsKeyRecord {
key_down: true,
repeat_count: 1,
virtual_key_code: 0x1b,
virtual_scan_code: 0x01,
unicode: 0x1b,
control_key_state: 0,
});
let mut translator = WindowsInputTranslator::default();
assert_eq!(
records
.into_iter()
.flat_map(|record| translator.translate(record))
.collect::<Vec<_>>(),
vec![crate::protocol::ClientInputEvent::Key {
code: crate::protocol::ClientKeyCode::Esc,
modifiers: 0,
kind: crate::protocol::ClientKeyKind::Press,
}]
);
}
#[test]
fn vti_win32_input_mode_encoded_mouse_sequence() {
assert_eq!(