parent
885dbcd19e
commit
f4d216b63d
|
|
@ -497,6 +497,10 @@ impl WindowsInputMapper {
|
|||
return None;
|
||||
}
|
||||
|
||||
if record.virtual_key_code != 0 && (record.unicode < 0x20 || record.unicode == 0x7f) {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.synthetic_utf16_unit_to_bytes(record.unicode)
|
||||
}
|
||||
|
||||
|
|
@ -1566,6 +1570,27 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vti_win32_input_mode_backspace_stays_backspace() {
|
||||
let records = "\x1b[8;14;8;1;0;1_\x1b[8;14;8;0;0;1_".chars().map(key_char);
|
||||
|
||||
assert_eq!(
|
||||
translate(records),
|
||||
vec![
|
||||
crate::protocol::ClientInputEvent::Key {
|
||||
code: crate::protocol::ClientKeyCode::Backspace,
|
||||
modifiers: 0,
|
||||
kind: crate::protocol::ClientKeyKind::Press,
|
||||
},
|
||||
crate::protocol::ClientInputEvent::Key {
|
||||
code: crate::protocol::ClientKeyCode::Backspace,
|
||||
modifiers: 0,
|
||||
kind: crate::protocol::ClientKeyKind::Release,
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vti_win32_input_mode_ctrl_j_preserves_lf_control_key() {
|
||||
let records = "\x1b[74;36;10;1;8;1_\x1b[74;36;10;0;8;1_"
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ use super::{KeyboardProtocol, MouseProtocolEncoding, TerminalKey};
|
|||
|
||||
const KITTY_FLAG_REPORT_EVENT_TYPES: u16 = 0b0000_0010;
|
||||
const KITTY_FLAG_REPORT_ALTERNATE_KEYS: u16 = 0b0000_0100;
|
||||
const KITTY_FLAG_REPORT_ALL_KEYS: u16 = 0b0000_1000;
|
||||
|
||||
/// Encode a key event for a PTY child using the pane's negotiated keyboard protocol.
|
||||
#[allow(dead_code)] // exercised in input unit tests; production uses TerminalRuntime helpers
|
||||
|
|
@ -158,9 +159,17 @@ fn push_mouse_codepoint(bytes: &mut Vec<u8>, value: u32) -> Option<()> {
|
|||
fn try_encode_csi_u(key: &TerminalKey, flags: u16) -> Option<Vec<u8>> {
|
||||
let mods = key.modifiers;
|
||||
let event_suffix = kitty_event_suffix(key, flags);
|
||||
let report_all_keys = flags & KITTY_FLAG_REPORT_ALL_KEYS != 0;
|
||||
|
||||
if !report_all_keys
|
||||
&& key.modifiers.is_empty()
|
||||
&& matches!(key.code, KeyCode::Enter | KeyCode::Tab | KeyCode::Backspace)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
// Unmodified keys use legacy encoding (more compatible)
|
||||
if mods.is_empty() && event_suffix.is_none() {
|
||||
if mods.is_empty() && event_suffix.is_none() && !report_all_keys {
|
||||
return None;
|
||||
}
|
||||
|
||||
|
|
@ -180,7 +189,7 @@ fn try_encode_csi_u(key: &TerminalKey, flags: u16) -> Option<Vec<u8>> {
|
|||
| KeyCode::Insert
|
||||
| KeyCode::Delete
|
||||
| KeyCode::F(_)
|
||||
if event_suffix.is_none() =>
|
||||
if event_suffix.is_none() && !report_all_keys =>
|
||||
{
|
||||
return None; // let legacy handle these
|
||||
}
|
||||
|
|
@ -707,6 +716,83 @@ mod tests {
|
|||
assert_eq!(encode_key(key, KeyboardProtocol::Kitty { flags: 1 }), b"a");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn kitty_report_event_types_keeps_basic_compatibility_keys_legacy() {
|
||||
let cases = [
|
||||
(KeyCode::Enter, b"\r".as_slice()),
|
||||
(KeyCode::Tab, b"\t".as_slice()),
|
||||
(KeyCode::Backspace, b"\x7f".as_slice()),
|
||||
];
|
||||
|
||||
for (code, expected) in cases {
|
||||
let press = KeyEvent::new_with_kind(
|
||||
code,
|
||||
KeyModifiers::empty(),
|
||||
crossterm::event::KeyEventKind::Press,
|
||||
);
|
||||
assert_eq!(
|
||||
encode_key(press, KeyboardProtocol::Kitty { flags: 3 }),
|
||||
expected,
|
||||
"{code:?} press should stay legacy-compatible without REPORT_ALL_KEYS"
|
||||
);
|
||||
|
||||
let repeat = KeyEvent::new_with_kind(
|
||||
code,
|
||||
KeyModifiers::empty(),
|
||||
crossterm::event::KeyEventKind::Repeat,
|
||||
);
|
||||
assert_eq!(
|
||||
encode_key(repeat, KeyboardProtocol::Kitty { flags: 3 }),
|
||||
expected,
|
||||
"{code:?} repeat should stay legacy-compatible without REPORT_ALL_KEYS"
|
||||
);
|
||||
|
||||
let release = KeyEvent::new_with_kind(
|
||||
code,
|
||||
KeyModifiers::empty(),
|
||||
crossterm::event::KeyEventKind::Release,
|
||||
);
|
||||
assert_eq!(
|
||||
encode_key(release, KeyboardProtocol::Kitty { flags: 3 }),
|
||||
b"",
|
||||
"{code:?} release should not fall back to legacy bytes"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn kitty_report_all_keys_encodes_basic_compatibility_keys_with_events() {
|
||||
let enter_press = KeyEvent::new_with_kind(
|
||||
KeyCode::Enter,
|
||||
KeyModifiers::empty(),
|
||||
crossterm::event::KeyEventKind::Press,
|
||||
);
|
||||
assert_eq!(
|
||||
encode_key(enter_press, KeyboardProtocol::Kitty { flags: 9 }),
|
||||
b"\x1b[13;1u"
|
||||
);
|
||||
|
||||
let backspace_press = KeyEvent::new_with_kind(
|
||||
KeyCode::Backspace,
|
||||
KeyModifiers::empty(),
|
||||
crossterm::event::KeyEventKind::Press,
|
||||
);
|
||||
assert_eq!(
|
||||
encode_key(backspace_press, KeyboardProtocol::Kitty { flags: 11 }),
|
||||
b"\x1b[127;1:1u"
|
||||
);
|
||||
|
||||
let backspace_release = KeyEvent::new_with_kind(
|
||||
KeyCode::Backspace,
|
||||
KeyModifiers::empty(),
|
||||
crossterm::event::KeyEventKind::Release,
|
||||
);
|
||||
assert_eq!(
|
||||
encode_key(backspace_release, KeyboardProtocol::Kitty { flags: 11 }),
|
||||
b"\x1b[127;1:3u"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn kitty_shift_tab() {
|
||||
let key = KeyEvent::new(KeyCode::Tab, KeyModifiers::SHIFT);
|
||||
|
|
|
|||
|
|
@ -2689,30 +2689,36 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn ghostty_release_still_encoded_for_report_event_pane() {
|
||||
fn ghostty_report_event_pane_keeps_basic_compatibility_keys_legacy() {
|
||||
let (tx, _rx) = mpsc::channel(4);
|
||||
// Push kitty flags including REPORT_EVENT_TYPES (0b10) + DISAMBIGUATE (0b1).
|
||||
let mut terminal = crate::ghostty::Terminal::new(80, 24, 0).unwrap();
|
||||
terminal.write(b"\x1b[>3u");
|
||||
let pane = GhosttyPaneTerminal::new(terminal, tx).unwrap();
|
||||
|
||||
let release = pane.encode_terminal_key(
|
||||
crate::input::TerminalKey::new(
|
||||
crossterm::event::KeyCode::Enter,
|
||||
crossterm::event::KeyModifiers::empty(),
|
||||
)
|
||||
.with_kind(crossterm::event::KeyEventKind::Release),
|
||||
pane.keyboard_protocol().unwrap(),
|
||||
);
|
||||
let parsed =
|
||||
crate::input::parse_terminal_key_sequence(std::str::from_utf8(&release).unwrap())
|
||||
.unwrap();
|
||||
assert_eq!(parsed.code, crossterm::event::KeyCode::Enter);
|
||||
assert_eq!(
|
||||
parsed.kind,
|
||||
crossterm::event::KeyEventKind::Release,
|
||||
"report-event pane should encode a release, got {release:?}"
|
||||
);
|
||||
for (code, expected) in [
|
||||
(crossterm::event::KeyCode::Enter, b"\r".as_slice()),
|
||||
(crossterm::event::KeyCode::Backspace, b"\x7f".as_slice()),
|
||||
] {
|
||||
let press = pane.encode_terminal_key(
|
||||
crate::input::TerminalKey::new(code, crossterm::event::KeyModifiers::empty()),
|
||||
pane.keyboard_protocol().unwrap(),
|
||||
);
|
||||
assert_eq!(
|
||||
press, expected,
|
||||
"{code:?} press should stay legacy-compatible without REPORT_ALL_KEYS"
|
||||
);
|
||||
|
||||
let release = pane.encode_terminal_key(
|
||||
crate::input::TerminalKey::new(code, crossterm::event::KeyModifiers::empty())
|
||||
.with_kind(crossterm::event::KeyEventKind::Release),
|
||||
pane.keyboard_protocol().unwrap(),
|
||||
);
|
||||
assert!(
|
||||
release.is_empty(),
|
||||
"{code:?} release should not fall back to legacy bytes, got {release:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -2938,6 +2944,47 @@ mod tests {
|
|||
assert_eq!(encoded, b"\x1b[127;3u");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ghostty_pane_characterizes_ctrl_backspace_encoding() {
|
||||
let (tx, _rx) = mpsc::channel(4);
|
||||
let legacy = GhosttyPaneTerminal::new(
|
||||
crate::ghostty::Terminal::new(80, 24, 0).unwrap(),
|
||||
tx.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let ctrl_backspace = crate::input::TerminalKey::new(
|
||||
crossterm::event::KeyCode::Backspace,
|
||||
crossterm::event::KeyModifiers::CONTROL,
|
||||
);
|
||||
assert_eq!(
|
||||
legacy.encode_terminal_key(ctrl_backspace, crate::input::KeyboardProtocol::Legacy),
|
||||
b"\x08"
|
||||
);
|
||||
|
||||
let plain_backspace = crate::input::TerminalKey::new(
|
||||
crossterm::event::KeyCode::Backspace,
|
||||
crossterm::event::KeyModifiers::empty(),
|
||||
);
|
||||
assert_eq!(
|
||||
legacy.encode_terminal_key(plain_backspace, crate::input::KeyboardProtocol::Legacy),
|
||||
b"\x7f"
|
||||
);
|
||||
|
||||
let kitty = GhosttyPaneTerminal::new(
|
||||
crate::ghostty::Terminal::new(80, 24, 0).unwrap(),
|
||||
tx.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
let pane_id = PaneId::from_raw(1);
|
||||
kitty.process_pty_bytes(pane_id, 0, b"\x1b[>1u", &tx);
|
||||
|
||||
assert_eq!(
|
||||
kitty.encode_terminal_key(ctrl_backspace, crate::input::KeyboardProtocol::Legacy),
|
||||
b"\x1b[127;5u"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ghostty_key_encoders_are_isolated_per_pane() {
|
||||
let (tx, _rx) = mpsc::channel(4);
|
||||
|
|
|
|||
Loading…
Reference in New Issue