1use muxr_config::KeybindingMode;
2use muxr_config::KeybindingsConfig;
3use muxr_core::ClientKey;
4use muxr_core::ClientKeyCode;
5use muxr_core::ClientKeyModifiers;
6use muxr_core::ClientMouseEvent;
7use muxr_core::ClientMouseEventPhase;
8use muxr_core::ClientMousePosition;
9
10const CTRL_N: u8 = 0x0e;
11const CTRL_P: u8 = 0x10;
12const ESC: u8 = 0x1b;
13const MAX_PENDING_ESCAPE_BYTES: usize = 64;
14const MAX_PENDING_CONTROL_STRING_BYTES: usize = 4096;
15const BRACKETED_PASTE_END: &[u8] = b"\x1b[201~";
16const BRACKETED_PASTE_START: &[u8] = b"\x1b[200~";
17
18#[derive(Clone, Debug, Eq, PartialEq)]
19pub enum DecodedInput {
20 Input(Vec<u8>),
21 Key(ClientKey),
22 Mouse(ClientMouseEvent),
23 Paste(Vec<u8>),
24}
25
26#[derive(Clone, Debug, Default, Eq, PartialEq)]
27enum PendingInput {
28 #[default]
29 None,
30 EscapeSequence(Vec<u8>),
31 AmbiguousControlString(Vec<u8>),
32 ControlString {
33 bytes: Vec<u8>,
34 kind: ControlStringKind,
35 },
36 Paste(Vec<u8>),
37}
38
39#[derive(Clone, Copy, Debug, Eq, PartialEq)]
40enum ControlStringKind {
41 Osc,
42 Other,
43}
44
45impl ControlStringKind {
46 const fn from_prefix(byte: u8) -> Option<Self> {
47 match byte {
48 b']' => Some(Self::Osc),
49 b'P' | b'X' | b'^' | b'_' => Some(Self::Other),
50 _ => None,
51 }
52 }
53}
54
55#[derive(Clone, Copy, Debug, Eq, PartialEq)]
56enum ControlStringStatus {
57 Complete,
58 Incomplete,
59}
60
61#[derive(Clone, Copy, Debug, Eq, PartialEq)]
62enum LegacyAltCharacter {
63 Shifted(char),
64 Unshifted(char),
65}
66
67#[derive(Clone, Copy, Debug, Eq, PartialEq)]
68enum SgrMouseEvent {
69 Event(ClientMouseEvent),
70 Ignored,
71}
72
73impl SgrMouseEvent {
74 fn from_bytes(bytes: &[u8]) -> Option<Self> {
75 if bytes.first() != Some(&ESC) || bytes.get(1) != Some(&b'[') || bytes.get(2) != Some(&b'<') {
76 return None;
77 }
78 let release = match bytes.last() {
79 Some(b'M') => false,
80 Some(b'm') => true,
81 Some(_) | None => return Some(Self::Ignored),
82 };
83 let phase = if release {
84 ClientMouseEventPhase::Release
85 } else {
86 ClientMouseEventPhase::Press
87 };
88 let Some((button, position)) = self::sgr_mouse_button_and_position(bytes) else {
89 return Some(Self::Ignored);
90 };
91 Some(Self::Event(ClientMouseEvent {
92 button,
93 phase,
94 position,
95 }))
96 }
97}
98
99#[derive(Clone, Copy, Debug, Eq, PartialEq)]
100enum KittyKeyModifiers {
101 Supported(ClientKeyModifiers),
102 Unsupported,
103}
104
105impl KittyKeyModifiers {
106 fn from_raw(raw: &[u8]) -> Option<Self> {
107 let flags = self::parse_mouse_number(raw)?.checked_sub(1)?;
108 if flags & !0b111 != 0 {
109 return Some(Self::Unsupported);
110 }
111 Some(Self::Supported(ClientKeyModifiers {
112 alt: flags & 0b010 != 0,
113 ctrl: flags & 0b100 != 0,
114 shift: flags & 0b001 != 0,
115 }))
116 }
117}
118
119#[derive(Clone, Debug, Eq, PartialEq)]
120pub struct InputDecoder {
121 pending: PendingInput,
122 keybindings: KeybindingsConfig,
123}
124
125impl Default for InputDecoder {
126 fn default() -> Self {
127 Self::with_keybindings(KeybindingsConfig::default())
128 }
129}
130
131impl InputDecoder {
132 pub(crate) const fn with_keybindings(keybindings: KeybindingsConfig) -> Self {
133 Self {
134 pending: PendingInput::None,
135 keybindings,
136 }
137 }
138
139 #[must_use]
140 pub fn decode(&mut self, bytes: &[u8]) -> Vec<DecodedInput> {
141 let mut decoded = Vec::new();
142 let mut input = Vec::new();
143
144 for byte in bytes {
145 self.push_byte(*byte, &mut input, &mut decoded);
146 }
147
148 self::push_input(&mut decoded, &mut input);
149 decoded
150 }
151
152 #[must_use]
153 pub fn finalize(&mut self) -> Vec<DecodedInput> {
154 let mut decoded = Vec::new();
155 let mut input = Vec::new();
156
157 self::finalize_pending_input(self, &mut input, &mut decoded);
158
159 self::push_input(&mut decoded, &mut input);
160 decoded
161 }
162
163 #[must_use]
164 pub const fn idle_timeout(&self) -> InputIdleTimeout {
165 match self.pending {
166 PendingInput::EscapeSequence(_) | PendingInput::AmbiguousControlString(_) => InputIdleTimeout::Needed,
167 PendingInput::None | PendingInput::ControlString { .. } | PendingInput::Paste(_) => {
168 InputIdleTimeout::NotNeeded
169 }
170 }
171 }
172
173 fn push_byte(&mut self, byte: u8, input: &mut Vec<u8>, decoded: &mut Vec<DecodedInput>) {
174 match std::mem::take(&mut self.pending) {
175 PendingInput::None => {
176 if byte == ESC {
177 self.pending = PendingInput::EscapeSequence(vec![ESC]);
178 } else if let Some(key) = self::key_for_plain_byte(byte) {
179 self::push_key(decoded, input, key);
180 } else {
181 input.push(byte);
182 }
183 }
184 PendingInput::EscapeSequence(mut bytes) => {
185 bytes.push(byte);
186 if bytes.len() == 2
187 && let Some(kind) = ControlStringKind::from_prefix(byte)
188 {
189 self.pending = if self::key_for_escaped_byte(byte, &self.keybindings).is_some() {
190 PendingInput::AmbiguousControlString(bytes)
191 } else {
192 PendingInput::ControlString { bytes, kind }
193 };
194 } else if PendingEscapeStatus::from(bytes.as_slice()) == PendingEscapeStatus::Incomplete {
195 self.pending = PendingInput::EscapeSequence(bytes);
196 } else if bytes == BRACKETED_PASTE_START {
197 self::push_input(decoded, input);
198 self.pending = PendingInput::Paste(Vec::new());
199 } else {
200 self::finish_escape_sequence(bytes, &self.keybindings, input, decoded);
201 }
202 }
203 PendingInput::AmbiguousControlString(mut bytes) => {
204 bytes.push(byte);
205 if let Some(&prefix) = bytes.get(1)
206 && let Some(kind) = ControlStringKind::from_prefix(prefix)
207 {
208 match self::control_string_status(&bytes, kind) {
209 ControlStringStatus::Complete => input.extend(bytes),
210 ControlStringStatus::Incomplete if bytes.len() >= MAX_PENDING_CONTROL_STRING_BYTES => {
211 self::flush_control_string(bytes, input, decoded);
212 self.pending = PendingInput::ControlString {
213 bytes: Vec::new(),
214 kind,
215 };
216 }
217 ControlStringStatus::Incomplete => {
218 self.pending = PendingInput::AmbiguousControlString(bytes);
219 }
220 }
221 } else {
222 self.pending = PendingInput::AmbiguousControlString(bytes);
223 }
224 }
225 PendingInput::ControlString { mut bytes, kind } => {
226 bytes.push(byte);
227 match self::control_string_status(&bytes, kind) {
228 ControlStringStatus::Complete => input.extend(bytes),
229 ControlStringStatus::Incomplete if bytes.len() >= MAX_PENDING_CONTROL_STRING_BYTES => {
230 self::flush_control_string(bytes, input, decoded);
231 self.pending = PendingInput::ControlString {
232 bytes: Vec::new(),
233 kind,
234 };
235 }
236 ControlStringStatus::Incomplete => {
237 self.pending = PendingInput::ControlString { bytes, kind };
238 }
239 }
240 }
241 PendingInput::Paste(mut bytes) => {
242 bytes.push(byte);
243 if bytes.ends_with(BRACKETED_PASTE_END) {
244 let paste_len = bytes.len().saturating_sub(BRACKETED_PASTE_END.len());
245 bytes.truncate(paste_len);
246 decoded.push(DecodedInput::Paste(bytes));
247 } else {
248 self.pending = PendingInput::Paste(bytes);
249 }
250 }
251 }
252 }
253}
254
255fn finish_ambiguous_control_string(
256 decoder: &mut InputDecoder,
257 bytes: Vec<u8>,
258 input: &mut Vec<u8>,
259 events: &mut Vec<DecodedInput>,
260) {
261 let Some(&byte) = bytes.get(1) else {
262 input.extend(bytes);
263 return;
264 };
265 let Some(rest) = bytes.get(2..) else {
266 input.extend(bytes);
267 return;
268 };
269 let Some(key) = self::key_for_escaped_byte(byte, &decoder.keybindings) else {
270 input.extend(bytes);
271 return;
272 };
273
274 self::push_key(events, input, key);
275 for byte in rest {
276 decoder.push_byte(*byte, input, events);
277 }
278}
279
280fn finalize_pending_input(decoder: &mut InputDecoder, input: &mut Vec<u8>, events: &mut Vec<DecodedInput>) {
281 loop {
282 match std::mem::take(&mut decoder.pending) {
283 PendingInput::None => return,
284 PendingInput::EscapeSequence(bytes) if bytes.as_slice() == [ESC] => {
285 self::push_key(
286 events,
287 input,
288 self::key(ClientKeyCode::Esc, ClientKeyModifiers::NONE, &bytes),
289 );
290 return;
291 }
292 PendingInput::AmbiguousControlString(bytes) => {
293 self::finish_ambiguous_control_string(decoder, bytes, input, events);
294 }
295 PendingInput::EscapeSequence(bytes) | PendingInput::ControlString { bytes, .. } => {
296 input.extend(bytes);
297 return;
298 }
299 PendingInput::Paste(bytes) => {
300 input.extend(BRACKETED_PASTE_START);
301 input.extend(bytes);
302 return;
303 }
304 }
305 }
306}
307
308fn finish_escape_sequence(
309 bytes: Vec<u8>,
310 keybindings: &KeybindingsConfig,
311 input: &mut Vec<u8>,
312 decoded: &mut Vec<DecodedInput>,
313) {
314 if let [ESC, byte] = bytes.as_slice()
315 && let Some(key) = self::key_for_escaped_byte(*byte, keybindings)
316 {
317 self::push_key(decoded, input, key);
318 return;
319 }
320
321 if let Some(key) = self::key_for_csi_sequence(&bytes) {
322 self::push_key(decoded, input, key);
323 return;
324 }
325
326 if let Some(event) = SgrMouseEvent::from_bytes(&bytes) {
327 self::push_input(decoded, input);
328 match event {
329 SgrMouseEvent::Ignored => {}
330 SgrMouseEvent::Event(event) => decoded.push(DecodedInput::Mouse(event)),
331 }
332 return;
333 }
334
335 input.extend(bytes);
336}
337
338fn key_for_plain_byte(byte: u8) -> Option<ClientKey> {
339 (byte.is_ascii() && !byte.is_ascii_control())
340 .then(|| self::key(ClientKeyCode::Char(char::from(byte)), ClientKeyModifiers::NONE, &[byte]))
341}
342
343fn key_for_escaped_byte(byte: u8, keybindings: &KeybindingsConfig) -> Option<ClientKey> {
344 let (code, modifiers) = match byte {
345 CTRL_N => (ClientKeyCode::Char('n'), ClientKeyModifiers::CTRL_ALT),
346 CTRL_P => (ClientKeyCode::Char('p'), ClientKeyModifiers::CTRL_ALT),
347 _ => match self::legacy_alt_character(byte)? {
348 LegacyAltCharacter::Shifted(character) => (ClientKeyCode::Char(character), ClientKeyModifiers::SHIFT_ALT),
349 LegacyAltCharacter::Unshifted(character) => (ClientKeyCode::Char(character), ClientKeyModifiers::ALT),
350 },
351 };
352
353 if byte == b']' {
354 return None;
355 }
356
357 let key = self::key(code, modifiers, &[ESC, byte]);
358 (keybindings.resolve_local(&key).is_some()
359 || keybindings.resolve(KeybindingMode::Normal, &key).is_some()
360 || keybindings.resolve(KeybindingMode::Resize, &key).is_some())
361 .then_some(key)
362}
363
364fn control_string_status(bytes: &[u8], kind: ControlStringKind) -> ControlStringStatus {
365 if bytes.ends_with(b"\x1b\\") || (kind == ControlStringKind::Osc && bytes.last() == Some(&b'\x07')) {
366 ControlStringStatus::Complete
367 } else {
368 ControlStringStatus::Incomplete
369 }
370}
371
372fn flush_control_string(bytes: Vec<u8>, input: &mut Vec<u8>, decoded: &mut Vec<DecodedInput>) {
373 self::push_input(decoded, input);
374 decoded.push(DecodedInput::Input(bytes));
375}
376
377fn legacy_alt_character(byte: u8) -> Option<LegacyAltCharacter> {
378 let shifted_character = match byte {
379 b'!' => Some(LegacyAltCharacter::Shifted('1')),
380 b'@' => Some(LegacyAltCharacter::Shifted('2')),
381 b'#' => Some(LegacyAltCharacter::Shifted('3')),
382 b'$' => Some(LegacyAltCharacter::Shifted('4')),
383 b'%' => Some(LegacyAltCharacter::Shifted('5')),
384 b'^' => Some(LegacyAltCharacter::Shifted('6')),
385 b'&' => Some(LegacyAltCharacter::Shifted('7')),
386 b'*' => Some(LegacyAltCharacter::Shifted('8')),
387 b'(' => Some(LegacyAltCharacter::Shifted('9')),
388 b')' => Some(LegacyAltCharacter::Shifted('0')),
389 b'_' => Some(LegacyAltCharacter::Shifted('-')),
390 b'+' => Some(LegacyAltCharacter::Shifted('=')),
391 b'{' => Some(LegacyAltCharacter::Shifted('[')),
392 b'}' => Some(LegacyAltCharacter::Shifted(']')),
393 b'|' => Some(LegacyAltCharacter::Shifted('\\')),
394 b':' => Some(LegacyAltCharacter::Shifted(';')),
395 b'"' => Some(LegacyAltCharacter::Shifted('\'')),
396 b'<' => Some(LegacyAltCharacter::Shifted(',')),
397 b'>' => Some(LegacyAltCharacter::Shifted('.')),
398 b'?' => Some(LegacyAltCharacter::Shifted('/')),
399 b'~' => Some(LegacyAltCharacter::Shifted('`')),
400 _ => None,
401 };
402 if shifted_character.is_some() {
403 return shifted_character;
404 }
405 if byte.is_ascii_graphic() || byte == b' ' {
406 let character = char::from(byte);
407 return Some(if character.is_ascii_uppercase() {
408 LegacyAltCharacter::Shifted(character)
409 } else {
410 LegacyAltCharacter::Unshifted(character)
411 });
412 }
413 None
414}
415
416fn key_for_csi_sequence(bytes: &[u8]) -> Option<ClientKey> {
417 if let Some(key) = self::key_for_kitty_keyboard_sequence(bytes) {
418 return Some(key);
419 }
420
421 let [ESC, b'[', byte] = bytes else {
422 return None;
423 };
424
425 match byte {
426 b'A' => Some(self::key(ClientKeyCode::Up, ClientKeyModifiers::NONE, bytes)),
427 b'B' => Some(self::key(ClientKeyCode::Down, ClientKeyModifiers::NONE, bytes)),
428 b'C' => Some(self::key(ClientKeyCode::Right, ClientKeyModifiers::NONE, bytes)),
429 b'D' => Some(self::key(ClientKeyCode::Left, ClientKeyModifiers::NONE, bytes)),
430 _ => None,
431 }
432}
433
434fn key_for_kitty_keyboard_sequence(bytes: &[u8]) -> Option<ClientKey> {
435 if bytes.first() != Some(&ESC) || bytes.get(1) != Some(&b'[') || bytes.last() != Some(&b'u') {
436 return None;
437 }
438
439 let body_end = bytes.len().checked_sub(1)?;
440 let body = bytes.get(2..body_end)?;
441 let mut parts = body.split(|byte| *byte == b';');
442 let key_number = parts.next().and_then(self::parse_mouse_number)?;
443 let modifiers = match parts.next() {
444 Some(raw) => KittyKeyModifiers::from_raw(raw)?,
445 None => KittyKeyModifiers::Supported(ClientKeyModifiers::NONE),
446 };
447 if parts.next().is_some() {
448 return None;
449 }
450 let KittyKeyModifiers::Supported(modifiers) = modifiers else {
451 return Some(self::key(ClientKeyCode::Unknown, ClientKeyModifiers::NONE, bytes));
454 };
455
456 let code = match key_number {
457 9 => ClientKeyCode::Tab,
458 13 => ClientKeyCode::Enter,
459 27 => ClientKeyCode::Esc,
460 127 => ClientKeyCode::Backspace,
461 32..=126 => ClientKeyCode::Char(self::kitty_ascii_character(key_number, modifiers)?),
462 _ => ClientKeyCode::Unknown,
463 };
464
465 Some(self::key(code, modifiers, bytes))
466}
467
468fn kitty_ascii_character(key_number: u16, modifiers: ClientKeyModifiers) -> Option<char> {
469 let character = char::from(u8::try_from(key_number).ok()?);
470 if modifiers.shift && character.is_ascii_lowercase() {
473 Some(character.to_ascii_uppercase())
474 } else {
475 Some(character)
476 }
477}
478
479fn sgr_mouse_button_and_position(bytes: &[u8]) -> Option<(u16, ClientMousePosition)> {
480 let body_end = bytes.len().checked_sub(1)?;
481 let body = bytes.get(3..body_end)?;
482 let mut parts = body.split(|byte| *byte == b';');
483 let button = parts.next().and_then(self::parse_mouse_number)?;
484 let col = parts
485 .next()
486 .and_then(self::parse_mouse_number)
487 .and_then(|col| col.checked_sub(1))?;
488 let row = parts
489 .next()
490 .and_then(self::parse_mouse_number)
491 .and_then(|row| row.checked_sub(1))?;
492 if parts.next().is_some() {
493 return None;
494 }
495
496 Some((button, ClientMousePosition { row, col }))
497}
498
499fn parse_mouse_number(raw: &[u8]) -> Option<u16> {
500 if raw.is_empty() {
501 return None;
502 }
503
504 let mut value = 0_u16;
505 for byte in raw {
506 if !byte.is_ascii_digit() {
507 return None;
508 }
509 let digit = u16::from(byte.saturating_sub(b'0'));
510 value = value.checked_mul(10)?.checked_add(digit)?;
511 }
512 Some(value)
513}
514
515fn push_key(decoded: &mut Vec<DecodedInput>, input: &mut Vec<u8>, key: ClientKey) {
516 self::push_input(decoded, input);
517 decoded.push(DecodedInput::Key(key));
518}
519
520#[derive(Clone, Copy, Debug, Eq, PartialEq)]
521pub enum InputIdleTimeout {
522 Needed,
523 NotNeeded,
524}
525
526#[derive(Clone, Copy, Debug, Eq, PartialEq)]
527enum PendingEscapeStatus {
528 Complete,
529 Incomplete,
530}
531
532impl From<&[u8]> for PendingEscapeStatus {
533 fn from(bytes: &[u8]) -> Self {
534 if bytes.len() > MAX_PENDING_ESCAPE_BYTES {
535 return Self::Complete;
536 }
537
538 let complete = match bytes {
539 [ESC] | [ESC, b'['] | [ESC, b'[', b'<'] => return Self::Incomplete,
540 [ESC, b'[', rest @ ..] => rest.last().is_some_and(|byte| (0x40..=0x7e).contains(byte)),
541 _ => true,
542 };
543 if complete { Self::Complete } else { Self::Incomplete }
544 }
545}
546
547fn push_input(decoded: &mut Vec<DecodedInput>, input: &mut Vec<u8>) {
548 if input.is_empty() {
549 return;
550 }
551
552 decoded.push(DecodedInput::Input(std::mem::take(input)));
553}
554
555fn key(code: ClientKeyCode, modifiers: ClientKeyModifiers, raw_bytes: &[u8]) -> ClientKey {
556 ClientKey {
557 code,
558 modifiers,
559 raw_bytes: raw_bytes.to_vec(),
560 }
561}
562
563#[cfg(test)]
564mod tests {
565 use rstest::rstest;
566 use test_that::prelude::*;
567
568 use super::*;
569
570 #[test]
571 fn test_input_decoder_decode_when_printable_bytes_are_plain_returns_keys() {
572 let mut decoder = InputDecoder::default();
573
574 assert_that!(
575 decoder.decode(b"abc"),
576 eq(vec![
577 DecodedInput::Key(key(ClientKeyCode::Char('a'), ClientKeyModifiers::NONE, b"a")),
578 DecodedInput::Key(key(ClientKeyCode::Char('b'), ClientKeyModifiers::NONE, b"b")),
579 DecodedInput::Key(key(ClientKeyCode::Char('c'), ClientKeyModifiers::NONE, b"c")),
580 ])
581 );
582 }
583
584 #[test]
585 fn test_input_decoder_decode_when_bare_enter_arrives_preserves_input_bytes() {
586 let mut decoder = InputDecoder::default();
587
588 assert_that!(decoder.decode(b"\r"), eq(vec![DecodedInput::Input(b"\r".to_vec())]));
589 }
590
591 #[rstest]
592 #[case::create_tab(b"\x1bE", ClientKeyCode::Char('E'), ClientKeyModifiers::SHIFT_ALT)]
593 #[case::focus_previous_tab(b"\x1bP", ClientKeyCode::Char('P'), ClientKeyModifiers::SHIFT_ALT)]
594 #[case::focus_next_tab(b"\x1bN", ClientKeyCode::Char('N'), ClientKeyModifiers::SHIFT_ALT)]
595 #[case::move_tab_previous(b"\x1b\x10", ClientKeyCode::Char('p'), ClientKeyModifiers::CTRL_ALT)]
596 #[case::move_tab_next(b"\x1b\x0e", ClientKeyCode::Char('n'), ClientKeyModifiers::CTRL_ALT)]
597 #[case::focus_pane_left(b"\x1bH", ClientKeyCode::Char('H'), ClientKeyModifiers::SHIFT_ALT)]
598 #[case::focus_pane_down(b"\x1bJ", ClientKeyCode::Char('J'), ClientKeyModifiers::SHIFT_ALT)]
599 #[case::focus_pane_up(b"\x1bK", ClientKeyCode::Char('K'), ClientKeyModifiers::SHIFT_ALT)]
600 #[case::focus_pane_right(b"\x1bL", ClientKeyCode::Char('L'), ClientKeyModifiers::SHIFT_ALT)]
601 #[case::split_pane_vertical(b"\x1bV", ClientKeyCode::Char('V'), ClientKeyModifiers::SHIFT_ALT)]
602 #[case::split_pane_horizontal(b"\x1bD", ClientKeyCode::Char('D'), ClientKeyModifiers::SHIFT_ALT)]
603 #[case::toggle_pane_fullscreen(b"\x1bF", ClientKeyCode::Char('F'), ClientKeyModifiers::SHIFT_ALT)]
604 #[case::close_pane(b"\x1bW", ClientKeyCode::Char('W'), ClientKeyModifiers::SHIFT_ALT)]
605 #[case::enter_resize_mode(b"\x1bR", ClientKeyCode::Char('R'), ClientKeyModifiers::SHIFT_ALT)]
606 #[case::open_scrollback_editor(b"\x1bS", ClientKeyCode::Char('S'), ClientKeyModifiers::SHIFT_ALT)]
607 #[case::kitty_create_tab(b"\x1b[101;4u", ClientKeyCode::Char('E'), ClientKeyModifiers::SHIFT_ALT)]
608 #[case::kitty_split_pane_vertical(b"\x1b[118;4u", ClientKeyCode::Char('V'), ClientKeyModifiers::SHIFT_ALT)]
609 #[case::kitty_move_tab_previous(b"\x1b[112;7u", ClientKeyCode::Char('p'), ClientKeyModifiers::CTRL_ALT)]
610 fn test_input_decoder_decode_when_shortcut_arrives_returns_key(
611 #[case] bytes: &[u8],
612 #[case] code: ClientKeyCode,
613 #[case] modifiers: ClientKeyModifiers,
614 ) {
615 let mut decoder = InputDecoder::default();
616
617 assert_that!(
618 self::decode_and_finalize(&mut decoder, bytes),
619 eq(vec![DecodedInput::Key(key(code, modifiers, bytes))])
620 );
621 }
622
623 #[rstest]
624 #[case::legacy_copy(b"\x1bC", 'C')]
625 #[case::kitty_copy(b"\x1b[99;4u", 'C')]
626 #[case::legacy_inline_copy(b"\x1bX", 'X')]
627 #[case::kitty_inline_copy(b"\x1b[120;4u", 'X')]
628 fn test_input_decoder_decode_when_local_shortcut_arrives_returns_key(
629 #[case] bytes: &[u8],
630 #[case] character: char,
631 ) {
632 let mut decoder = InputDecoder::default();
633
634 assert_that!(
635 self::decode_and_finalize(&mut decoder, bytes),
636 eq(vec![DecodedInput::Key(key(
637 ClientKeyCode::Char(character),
638 ClientKeyModifiers::SHIFT_ALT,
639 bytes,
640 ))])
641 );
642 }
643
644 #[test]
645 fn test_input_decoder_decode_when_shortcut_is_between_input_splits_actions() {
646 let mut decoder = InputDecoder::default();
647
648 assert_that!(
649 decoder.decode(b"a\x1bEb"),
650 eq(vec![
651 DecodedInput::Key(key(ClientKeyCode::Char('a'), ClientKeyModifiers::NONE, b"a")),
652 DecodedInput::Key(key(ClientKeyCode::Char('E'), ClientKeyModifiers::SHIFT_ALT, b"\x1bE",)),
653 DecodedInput::Key(key(ClientKeyCode::Char('b'), ClientKeyModifiers::NONE, b"b")),
654 ])
655 );
656 }
657
658 #[test]
659 fn test_input_decoder_decode_when_unknown_legacy_alt_key_arrives_preserves_input_bytes() {
660 let mut decoder = InputDecoder::default();
661 let bytes = b"\x1bY";
662
663 assert_that!(decoder.decode(bytes), eq(vec![DecodedInput::Input(bytes.to_vec())]));
664 }
665
666 #[rstest]
667 #[case::bel_terminated(b"\x1b]0;title\x07")]
668 #[case::st_terminated(b"\x1b]0;title\x1b\\")]
669 #[case::contains_muxr_prefix(b"\x1b]0;\x1bC\x1b\\")]
670 fn test_input_decoder_decode_when_osc_arrives_preserves_control_string_bytes(#[case] bytes: &[u8]) {
671 let mut decoder = InputDecoder::default();
672
673 assert_that!(decoder.decode(bytes), eq(vec![DecodedInput::Input(bytes.to_vec())]));
674 }
675
676 #[rstest::rstest]
677 #[case::dcs(b"\x1bP1;2\x1b\\")]
678 #[case::sos(b"\x1bX1;2\x1b\\")]
679 #[case::pm(b"\x1b^1;2\x1b\\")]
680 fn test_input_decoder_decode_when_legacy_shortcut_prefix_is_control_string_preserves_bytes(#[case] bytes: &[u8]) {
681 let mut decoder = InputDecoder::default();
682
683 assert_that!(decoder.decode(bytes), eq(vec![DecodedInput::Input(bytes.to_vec())]));
684 assert_that!(decoder.idle_timeout(), eq(InputIdleTimeout::NotNeeded));
685 }
686
687 #[test]
688 fn test_input_decoder_finalize_when_ambiguous_legacy_shortcut_arrives_returns_key() {
689 let mut decoder = InputDecoder::default();
690 let bytes = b"\x1bP";
691
692 assert_that!(decoder.decode(bytes), eq(Vec::<DecodedInput>::new()));
693 assert_that!(decoder.idle_timeout(), eq(InputIdleTimeout::Needed));
694 assert_that!(
695 decoder.finalize(),
696 eq(vec![DecodedInput::Key(key(
697 ClientKeyCode::Char('P'),
698 ClientKeyModifiers::SHIFT_ALT,
699 bytes,
700 ))])
701 );
702 }
703
704 #[test]
705 fn test_input_decoder_finalize_when_ambiguous_legacy_shortcut_has_suffix_replays_suffix() {
706 let mut decoder = InputDecoder::default();
707
708 assert_that!(decoder.decode(b"\x1bPa"), eq(Vec::<DecodedInput>::new()));
709 assert_that!(
710 decoder.finalize(),
711 eq(vec![
712 DecodedInput::Key(key(ClientKeyCode::Char('P'), ClientKeyModifiers::SHIFT_ALT, b"\x1bP")),
713 DecodedInput::Key(key(ClientKeyCode::Char('a'), ClientKeyModifiers::NONE, b"a")),
714 ])
715 );
716 }
717
718 #[test]
719 fn test_input_decoder_finalize_when_ambiguous_suffix_ends_in_escape_drains_pending_key() {
720 let mut decoder = InputDecoder::default();
721
722 assert_that!(decoder.decode(b"\x1bP\x1b"), eq(Vec::<DecodedInput>::new()));
723 assert_that!(
724 decoder.finalize(),
725 eq(vec![
726 DecodedInput::Key(key(ClientKeyCode::Char('P'), ClientKeyModifiers::SHIFT_ALT, b"\x1bP")),
727 DecodedInput::Key(key(ClientKeyCode::Esc, ClientKeyModifiers::NONE, b"\x1b")),
728 ])
729 );
730 }
731
732 #[test]
733 fn test_input_decoder_decode_when_control_string_exceeds_buffer_limit_flushes_raw_chunks() {
734 let mut decoder = InputDecoder::default();
735 let mut bytes = vec![ESC, b']'];
736 bytes.extend(std::iter::repeat_n(b'a', MAX_PENDING_CONTROL_STRING_BYTES));
737
738 let mut events = decoder.decode(&bytes);
739 assert_that!(events.len(), eq(1));
740 assert_that!(
741 events.first(),
742 some(eq(&DecodedInput::Input(
743 bytes[..MAX_PENDING_CONTROL_STRING_BYTES].to_vec()
744 )))
745 );
746 assert_that!(decoder.idle_timeout(), eq(InputIdleTimeout::NotNeeded));
747 events.extend(decoder.finalize());
748 assert_that!(
749 events.iter().all(|event| matches!(event, DecodedInput::Input(_))),
750 eq(true)
751 );
752 let preserved = events
753 .into_iter()
754 .flat_map(|decoded| match decoded {
755 DecodedInput::Input(bytes) => bytes,
756 DecodedInput::Key(_) | DecodedInput::Mouse(_) | DecodedInput::Paste(_) => Vec::new(),
757 })
758 .collect::<Vec<_>>();
759 assert_that!(preserved, eq(bytes));
760 assert_that!(decoder.idle_timeout(), eq(InputIdleTimeout::NotNeeded));
761 }
762
763 #[test]
764 fn test_input_decoder_decode_when_flushed_osc_reaches_bel_terminator_returns_following_key() {
765 let mut decoder = InputDecoder::default();
766 let mut bytes = vec![ESC, b']'];
767 bytes.extend(std::iter::repeat_n(b'a', MAX_PENDING_CONTROL_STRING_BYTES));
768 bytes.extend(*b"\x07z");
769
770 assert_that!(
771 decoder.decode(&bytes),
772 eq(vec![
773 DecodedInput::Input(bytes[..MAX_PENDING_CONTROL_STRING_BYTES].to_vec()),
774 DecodedInput::Input(
775 bytes[MAX_PENDING_CONTROL_STRING_BYTES..MAX_PENDING_CONTROL_STRING_BYTES + 3].to_vec()
776 ),
777 DecodedInput::Key(key(ClientKeyCode::Char('z'), ClientKeyModifiers::NONE, b"z")),
778 ])
779 );
780 assert_that!(decoder.idle_timeout(), eq(InputIdleTimeout::NotNeeded));
781 }
782
783 #[test]
784 fn test_input_decoder_decode_when_unknown_csi_arrives_preserves_bytes() {
785 let mut decoder = InputDecoder::default();
786 let bytes = b"\x1b[1~";
787
788 assert_that!(decoder.decode(bytes), eq(vec![DecodedInput::Input(bytes.to_vec())]));
789 }
790
791 #[test]
792 fn test_input_decoder_decode_when_shortcut_is_split_preserves_pending_prefix() {
793 let mut decoder = InputDecoder::default();
794
795 assert_that!(decoder.decode(b"\x1b"), eq(Vec::<DecodedInput>::new()));
796 assert_that!(decoder.idle_timeout(), eq(InputIdleTimeout::Needed));
797 assert_that!(
798 decoder.decode(b"E"),
799 eq(vec![DecodedInput::Key(key(
800 ClientKeyCode::Char('E'),
801 ClientKeyModifiers::SHIFT_ALT,
802 b"\x1bE",
803 ))])
804 );
805 assert_that!(decoder.idle_timeout(), eq(InputIdleTimeout::NotNeeded));
806 }
807
808 #[test]
809 fn test_input_decoder_finalize_when_bare_escape_arrives_returns_key() {
810 let mut decoder = InputDecoder::default();
811
812 assert_that!(decoder.decode(b"\x1b"), eq(Vec::<DecodedInput>::new()));
813 assert_that!(decoder.idle_timeout(), eq(InputIdleTimeout::Needed));
814 assert_that!(
815 decoder.finalize(),
816 eq(vec![DecodedInput::Key(key(
817 ClientKeyCode::Esc,
818 ClientKeyModifiers::NONE,
819 b"\x1b",
820 ))])
821 );
822 assert_that!(decoder.idle_timeout(), eq(InputIdleTimeout::NotNeeded));
823 }
824
825 #[test]
826 fn test_input_decoder_finalize_when_pending_unknown_sequence_arrives_preserves_bytes() {
827 let mut decoder = InputDecoder::default();
828 let bytes = b"\x1b[1";
829
830 assert_that!(decoder.decode(bytes), eq(Vec::<DecodedInput>::new()));
831 assert_that!(decoder.idle_timeout(), eq(InputIdleTimeout::Needed));
832 assert_that!(decoder.finalize(), eq(vec![DecodedInput::Input(bytes.to_vec())]));
833 assert_that!(decoder.idle_timeout(), eq(InputIdleTimeout::NotNeeded));
834 }
835
836 #[rstest]
837 #[case::left(b"h", ClientKeyCode::Char('h'))]
838 #[case::down(b"j", ClientKeyCode::Char('j'))]
839 #[case::up(b"k", ClientKeyCode::Char('k'))]
840 #[case::right(b"l", ClientKeyCode::Char('l'))]
841 #[case::arrow_left(b"\x1b[D", ClientKeyCode::Left)]
842 #[case::arrow_down(b"\x1b[B", ClientKeyCode::Down)]
843 #[case::arrow_up(b"\x1b[A", ClientKeyCode::Up)]
844 #[case::arrow_right(b"\x1b[C", ClientKeyCode::Right)]
845 fn test_input_decoder_decode_when_server_mode_key_arrives_returns_key(
846 #[case] bytes: &[u8],
847 #[case] code: ClientKeyCode,
848 ) {
849 let mut decoder = InputDecoder::default();
850
851 assert_that!(
852 decoder.decode(bytes),
853 eq(vec![DecodedInput::Key(key(code, ClientKeyModifiers::NONE, bytes))])
854 );
855 }
856
857 #[test]
858 fn test_input_decoder_decode_when_arrow_is_split_preserves_pending_prefix() {
859 let mut decoder = InputDecoder::default();
860
861 assert_that!(decoder.decode(b"\x1b["), eq(Vec::<DecodedInput>::new()));
862 assert_that!(decoder.idle_timeout(), eq(InputIdleTimeout::Needed));
863 assert_that!(
864 decoder.decode(b"D"),
865 eq(vec![DecodedInput::Key(key(
866 ClientKeyCode::Left,
867 ClientKeyModifiers::NONE,
868 b"\x1b[D",
869 ))])
870 );
871 assert_that!(decoder.idle_timeout(), eq(InputIdleTimeout::NotNeeded));
872 }
873
874 #[rstest]
875 #[case::plain_enter(b"\x1b[13u", ClientKeyCode::Enter, ClientKeyModifiers::NONE)]
876 #[case::shift_enter(b"\x1b[13;2u", ClientKeyCode::Enter, ClientKeyModifiers::SHIFT)]
877 #[case::shift_tab(b"\x1b[9;2u", ClientKeyCode::Tab, ClientKeyModifiers::SHIFT)]
878 #[case::alt_backspace(b"\x1b[127;3u", ClientKeyCode::Backspace, ClientKeyModifiers::ALT)]
879 #[case::shift_backspace(b"\x1b[127;2u", ClientKeyCode::Backspace, ClientKeyModifiers::SHIFT)]
880 #[case::ctrl_l(b"\x1b[108;5u", ClientKeyCode::Char('l'), self::modifiers(false, false, true))]
881 #[case::ctrl_k(b"\x1b[107;5u", ClientKeyCode::Char('k'), self::modifiers(false, false, true))]
882 #[case::shift_alt_one(b"\x1b[49;4u", ClientKeyCode::Char('1'), ClientKeyModifiers::SHIFT_ALT)]
883 #[case::unknown_modified_key(b"\x1b[999;2u", ClientKeyCode::Unknown, ClientKeyModifiers::SHIFT)]
884 #[case::unsupported_modifier_bits(b"\x1b[118;12u", ClientKeyCode::Unknown, ClientKeyModifiers::NONE)]
885 fn test_input_decoder_decode_when_kitty_key_arrives_returns_key(
886 #[case] bytes: &[u8],
887 #[case] code: ClientKeyCode,
888 #[case] modifiers: ClientKeyModifiers,
889 ) {
890 let mut decoder = InputDecoder::default();
891
892 assert_that!(
893 decoder.decode(bytes),
894 eq(vec![DecodedInput::Key(key(code, modifiers, bytes))])
895 );
896 }
897
898 #[rstest]
899 #[case::one(b"\x1b!", '1')]
900 #[case::two(b"\x1b@", '2')]
901 #[case::three(b"\x1b#", '3')]
902 #[case::four(b"\x1b$", '4')]
903 #[case::five(b"\x1b%", '5')]
904 #[case::six(b"\x1b^", '6')]
905 #[case::seven(b"\x1b&", '7')]
906 #[case::eight(b"\x1b*", '8')]
907 #[case::nine(b"\x1b(", '9')]
908 fn test_input_decoder_decode_when_legacy_shift_alt_digit_arrives_returns_key(
909 #[case] bytes: &[u8],
910 #[case] character: char,
911 ) {
912 let mut decoder = InputDecoder::default();
913
914 assert_that!(
915 self::decode_and_finalize(&mut decoder, bytes),
916 eq(vec![DecodedInput::Key(key(
917 ClientKeyCode::Char(character),
918 ClientKeyModifiers::SHIFT_ALT,
919 bytes,
920 ))])
921 );
922 }
923
924 #[test]
925 fn test_input_decoder_decode_when_kitty_key_is_split_preserves_pending_prefix() {
926 let mut decoder = InputDecoder::default();
927
928 assert_that!(decoder.decode(b"\x1b[13"), eq(Vec::<DecodedInput>::new()));
929 assert_that!(decoder.idle_timeout(), eq(InputIdleTimeout::Needed));
930 assert_that!(
931 decoder.decode(b";2u"),
932 eq(vec![DecodedInput::Key(key(
933 ClientKeyCode::Enter,
934 ClientKeyModifiers::SHIFT,
935 b"\x1b[13;2u",
936 ))])
937 );
938 assert_that!(decoder.idle_timeout(), eq(InputIdleTimeout::NotNeeded));
939 }
940
941 #[test]
942 fn test_input_decoder_decode_when_bracketed_paste_arrives_returns_single_paste() {
943 let mut decoder = InputDecoder::default();
944
945 assert_that!(
946 decoder.decode(b"\x1b[200~echo hi\n\x1b[201~"),
947 eq(vec![DecodedInput::Paste(b"echo hi\n".to_vec())])
948 );
949 }
950
951 #[test]
952 fn test_input_decoder_decode_when_bracketed_paste_is_split_preserves_pending_paste() {
953 let mut decoder = InputDecoder::default();
954
955 assert_that!(decoder.decode(b"\x1b[200~echo"), eq(Vec::<DecodedInput>::new()));
956 assert_that!(decoder.idle_timeout(), eq(InputIdleTimeout::NotNeeded));
957 assert_that!(
958 decoder.decode(b" hi\n\x1b[201~"),
959 eq(vec![DecodedInput::Paste(b"echo hi\n".to_vec())])
960 );
961 assert_that!(decoder.idle_timeout(), eq(InputIdleTimeout::NotNeeded));
962 }
963
964 #[rstest]
965 #[case::bare_escape(b"\x1b")]
966 #[case::incomplete_csi(b"\x1b[")]
967 fn test_input_decoder_needs_idle_timeout_when_escape_prefix_is_pending(#[case] bytes: &[u8]) {
968 let mut decoder = InputDecoder::default();
969
970 assert_that!(decoder.decode(bytes), eq(Vec::<DecodedInput>::new()));
971
972 assert_that!(decoder.idle_timeout(), eq(InputIdleTimeout::Needed));
973 }
974
975 #[test]
976 fn test_input_decoder_when_osc_payload_is_split_after_idle_preserves_payload() {
977 let mut decoder = InputDecoder::default();
978
979 assert_that!(decoder.decode(b"\x1b]0;"), eq(Vec::<DecodedInput>::new()));
980 assert_that!(decoder.idle_timeout(), eq(InputIdleTimeout::NotNeeded));
981 assert_that!(
982 decoder.decode(b"\x1bC\x07"),
983 eq(vec![DecodedInput::Input(b"\x1b]0;\x1bC\x07".to_vec())])
984 );
985 assert_that!(decoder.idle_timeout(), eq(InputIdleTimeout::NotNeeded));
986 }
987
988 #[rstest]
989 #[case::wheel_up(b"\x1b[<64;10;5M", 64)]
990 #[case::wheel_down(b"\x1b[<65;10;5M", 65)]
991 fn test_input_decoder_decode_when_mouse_wheel_arrives_returns_mouse_event(
992 #[case] bytes: &[u8],
993 #[case] button: u16,
994 ) {
995 let mut decoder = InputDecoder::default();
996
997 assert_that!(
998 decoder.decode(bytes),
999 eq(vec![DecodedInput::Mouse(ClientMouseEvent {
1000 button,
1001 phase: ClientMouseEventPhase::Press,
1002 position: ClientMousePosition { row: 4, col: 9 },
1003 })])
1004 );
1005 }
1006
1007 #[test]
1008 fn test_input_decoder_decode_when_mouse_click_arrives_returns_mouse_event() {
1009 let mut decoder = InputDecoder::default();
1010
1011 assert_that!(
1012 decoder.decode(b"\x1b[<0;10;5M"),
1013 eq(vec![DecodedInput::Mouse(ClientMouseEvent {
1014 button: 0,
1015 phase: ClientMouseEventPhase::Press,
1016 position: ClientMousePosition { row: 4, col: 9 },
1017 })])
1018 );
1019 }
1020
1021 #[test]
1022 fn test_input_decoder_decode_when_sgr_alt_mouse_click_arrives_returns_alt_mouse_event() {
1023 let mut decoder = InputDecoder::default();
1024
1025 assert_that!(
1026 decoder.decode(b"\x1b[<8;10;5M"),
1027 eq(vec![DecodedInput::Mouse(ClientMouseEvent {
1028 button: 8,
1029 phase: ClientMouseEventPhase::Press,
1030 position: ClientMousePosition { row: 4, col: 9 },
1031 })])
1032 );
1033 }
1034
1035 #[test]
1036 fn test_input_decoder_decode_when_sgr_alt_mouse_release_arrives_returns_alt_mouse_event() {
1037 let mut decoder = InputDecoder::default();
1038
1039 assert_that!(
1040 decoder.decode(b"\x1b[<8;10;5m"),
1041 eq(vec![DecodedInput::Mouse(ClientMouseEvent {
1042 button: 8,
1043 phase: ClientMouseEventPhase::Release,
1044 position: ClientMousePosition { row: 4, col: 9 },
1045 })])
1046 );
1047 }
1048
1049 #[test]
1050 fn test_input_decoder_decode_when_mouse_drag_arrives_returns_mouse_event() {
1051 let mut decoder = InputDecoder::default();
1052
1053 assert_that!(
1054 decoder.decode(b"\x1b[<32;10;5M"),
1055 eq(vec![DecodedInput::Mouse(ClientMouseEvent {
1056 button: 32,
1057 phase: ClientMouseEventPhase::Press,
1058 position: ClientMousePosition { row: 4, col: 9 },
1059 })])
1060 );
1061 }
1062
1063 #[test]
1064 fn test_input_decoder_decode_when_mouse_release_arrives_returns_mouse_event() {
1065 let mut decoder = InputDecoder::default();
1066
1067 assert_that!(
1068 decoder.decode(b"\x1b[<0;10;5m"),
1069 eq(vec![DecodedInput::Mouse(ClientMouseEvent {
1070 button: 0,
1071 phase: ClientMouseEventPhase::Release,
1072 position: ClientMousePosition { row: 4, col: 9 },
1073 })])
1074 );
1075 }
1076
1077 fn decode_and_finalize(decoder: &mut InputDecoder, bytes: &[u8]) -> Vec<DecodedInput> {
1078 let mut events = decoder.decode(bytes);
1079 events.extend(decoder.finalize());
1080 events
1081 }
1082
1083 const fn modifiers(shift: bool, alt: bool, ctrl: bool) -> ClientKeyModifiers {
1084 ClientKeyModifiers { alt, ctrl, shift }
1085 }
1086}