1use muxr_core::ClientKey;
2use muxr_core::ClientKeyCode;
3use muxr_core::ClientKeyModifiers;
4use muxr_core::ClientMouseEvent;
5use muxr_core::ClientMouseEventPhase;
6use muxr_core::ClientMousePosition;
7
8const CTRL_N: u8 = 0x0e;
9const CTRL_P: u8 = 0x10;
10const ESC: u8 = 0x1b;
11const MAX_PENDING_ESCAPE_BYTES: usize = 64;
12const BRACKETED_PASTE_END: &[u8] = b"\x1b[201~";
13const BRACKETED_PASTE_START: &[u8] = b"\x1b[200~";
14
15#[derive(Clone, Debug, Eq, PartialEq)]
16pub enum DecodedInput {
17 CopySelection,
18 CopySelectionInline,
19 Input(Vec<u8>),
20 Key(ClientKey),
21 Mouse(ClientMouseEvent),
22 Paste(Vec<u8>),
23}
24
25#[derive(Clone, Debug, Default, Eq, PartialEq)]
26enum PendingInput {
27 #[default]
28 None,
29 EscapeSequence(Vec<u8>),
30 Paste(Vec<u8>),
31}
32
33#[derive(Clone, Copy, Debug, Eq, PartialEq)]
34enum SgrMouseEvent {
35 Event(ClientMouseEvent),
36 Ignored,
37}
38
39impl SgrMouseEvent {
40 fn from_bytes(bytes: &[u8]) -> Option<Self> {
41 if bytes.first() != Some(&ESC) || bytes.get(1) != Some(&b'[') || bytes.get(2) != Some(&b'<') {
42 return None;
43 }
44 let release = match bytes.last() {
45 Some(b'M') => false,
46 Some(b'm') => true,
47 Some(_) | None => return Some(Self::Ignored),
48 };
49 let phase = if release {
50 ClientMouseEventPhase::Release
51 } else {
52 ClientMouseEventPhase::Press
53 };
54 let Some((button, position)) = self::sgr_mouse_button_and_position(bytes) else {
55 return Some(Self::Ignored);
56 };
57 Some(Self::Event(ClientMouseEvent {
58 button,
59 phase,
60 position,
61 }))
62 }
63}
64
65#[derive(Clone, Copy, Debug, Eq, PartialEq)]
66enum KittyKeyModifiers {
67 Supported(ClientKeyModifiers),
68 Unsupported,
69}
70
71impl KittyKeyModifiers {
72 fn from_raw(raw: &[u8]) -> Option<Self> {
73 let flags = self::parse_mouse_number(raw)?.checked_sub(1)?;
74 if flags & !0b111 != 0 {
75 return Some(Self::Unsupported);
76 }
77 Some(Self::Supported(ClientKeyModifiers {
78 alt: flags & 0b010 != 0,
79 ctrl: flags & 0b100 != 0,
80 shift: flags & 0b001 != 0,
81 }))
82 }
83}
84
85#[derive(Clone, Debug, Default, Eq, PartialEq)]
86pub struct InputDecoder {
87 pending: PendingInput,
88}
89
90impl InputDecoder {
91 #[must_use]
92 pub fn decode(&mut self, bytes: &[u8]) -> Vec<DecodedInput> {
93 let mut decoded = Vec::new();
94 let mut input = Vec::new();
95
96 for byte in bytes {
97 self.push_byte(*byte, &mut input, &mut decoded);
98 }
99
100 self::push_input(&mut decoded, &mut input);
101 decoded
102 }
103
104 #[must_use]
105 pub fn finalize(&mut self) -> Vec<DecodedInput> {
106 let mut decoded = Vec::new();
107 let mut input = Vec::new();
108
109 match std::mem::take(&mut self.pending) {
110 PendingInput::None => {}
111 PendingInput::EscapeSequence(bytes) if bytes.as_slice() == [ESC] => {
112 self::push_key(
113 &mut decoded,
114 &mut input,
115 self::key(ClientKeyCode::Esc, ClientKeyModifiers::NONE, &bytes),
116 );
117 }
118 PendingInput::EscapeSequence(bytes) => input.extend(bytes),
119 PendingInput::Paste(bytes) => {
120 input.extend(BRACKETED_PASTE_START);
121 input.extend(bytes);
122 }
123 }
124
125 self::push_input(&mut decoded, &mut input);
126 decoded
127 }
128
129 #[must_use]
130 pub const fn idle_timeout(&self) -> InputIdleTimeout {
131 match self.pending {
132 PendingInput::EscapeSequence(_) => InputIdleTimeout::Needed,
133 PendingInput::None | PendingInput::Paste(_) => InputIdleTimeout::NotNeeded,
134 }
135 }
136
137 fn push_byte(&mut self, byte: u8, input: &mut Vec<u8>, decoded: &mut Vec<DecodedInput>) {
138 if let PendingInput::Paste(bytes) = &mut self.pending {
139 bytes.push(byte);
140 if bytes.ends_with(BRACKETED_PASTE_END) {
141 let paste_len = bytes.len().saturating_sub(BRACKETED_PASTE_END.len());
142 bytes.truncate(paste_len);
143 let PendingInput::Paste(bytes) = std::mem::take(&mut self.pending) else {
144 return;
145 };
146 decoded.push(DecodedInput::Paste(bytes));
147 }
148 return;
149 }
150
151 if let PendingInput::EscapeSequence(bytes) = &mut self.pending {
152 bytes.push(byte);
153 if PendingEscapeStatus::from(bytes.as_slice()) == PendingEscapeStatus::Incomplete {
154 return;
155 }
156
157 let PendingInput::EscapeSequence(bytes) = std::mem::take(&mut self.pending) else {
158 return;
159 };
160 if bytes == BRACKETED_PASTE_START {
161 self::push_input(decoded, input);
162 self.pending = PendingInput::Paste(Vec::new());
163 } else {
164 self::finish_escape_sequence(bytes, input, decoded);
165 }
166 return;
167 }
168
169 if byte == ESC {
170 self.pending = PendingInput::EscapeSequence(vec![ESC]);
171 return;
172 }
173
174 if let Some(key) = self::key_for_plain_byte(byte) {
175 self::push_key(decoded, input, key);
176 return;
177 }
178
179 input.push(byte);
180 }
181}
182
183fn finish_escape_sequence(bytes: Vec<u8>, input: &mut Vec<u8>, decoded: &mut Vec<DecodedInput>) {
184 if let [ESC, byte] = bytes.as_slice()
185 && let Some(selection_input) = match *byte {
186 b'C' => Some(DecodedInput::CopySelection),
187 b'X' => Some(DecodedInput::CopySelectionInline),
188 _ => None,
189 }
190 {
191 self::push_input(decoded, input);
192 decoded.push(selection_input);
193 return;
194 }
195
196 if let [ESC, byte] = bytes.as_slice()
197 && let Some(key) = self::key_for_escaped_byte(*byte)
198 {
199 self::push_key(decoded, input, key);
200 return;
201 }
202
203 if let Some(key) = self::key_for_csi_sequence(&bytes) {
204 let selection_input =
205 if key.modifiers == ClientKeyModifiers::SHIFT_ALT && matches!(key.code, ClientKeyCode::Char('C')) {
206 Some(DecodedInput::CopySelection)
207 } else if key.modifiers == ClientKeyModifiers::SHIFT_ALT && matches!(key.code, ClientKeyCode::Char('X')) {
208 Some(DecodedInput::CopySelectionInline)
209 } else {
210 None
211 };
212 if let Some(selection_input) = selection_input {
213 self::push_input(decoded, input);
214 decoded.push(selection_input);
215 } else {
216 self::push_key(decoded, input, key);
217 }
218 return;
219 }
220
221 if let Some(event) = SgrMouseEvent::from_bytes(&bytes) {
222 self::push_input(decoded, input);
223 match event {
224 SgrMouseEvent::Ignored => {}
225 SgrMouseEvent::Event(event) => decoded.push(DecodedInput::Mouse(event)),
226 }
227 return;
228 }
229
230 input.extend(bytes);
231}
232
233fn key_for_plain_byte(byte: u8) -> Option<ClientKey> {
234 let code = match byte {
235 b'h' | b'j' | b'k' | b'l' => ClientKeyCode::Char(char::from(byte)),
236 _ => return None,
237 };
238
239 Some(self::key(code, ClientKeyModifiers::NONE, &[byte]))
240}
241
242fn key_for_escaped_byte(byte: u8) -> Option<ClientKey> {
243 let (code, modifiers) = match byte {
244 CTRL_N => (ClientKeyCode::Char('n'), ClientKeyModifiers::CTRL_ALT),
245 CTRL_P => (ClientKeyCode::Char('p'), ClientKeyModifiers::CTRL_ALT),
246 b'D' | b'E' | b'F' | b'H' | b'J' | b'K' | b'L' | b'N' | b'P' | b'R' | b'S' | b'V' | b'W' => {
247 (ClientKeyCode::Char(char::from(byte)), ClientKeyModifiers::SHIFT_ALT)
248 }
249 _ => return None,
250 };
251
252 Some(self::key(code, modifiers, &[ESC, byte]))
253}
254
255fn key_for_csi_sequence(bytes: &[u8]) -> Option<ClientKey> {
256 if let Some(key) = self::key_for_kitty_keyboard_sequence(bytes) {
257 return Some(key);
258 }
259
260 let [ESC, b'[', byte] = bytes else {
261 return None;
262 };
263
264 match byte {
265 b'A' => Some(self::key(ClientKeyCode::Up, ClientKeyModifiers::NONE, bytes)),
266 b'B' => Some(self::key(ClientKeyCode::Down, ClientKeyModifiers::NONE, bytes)),
267 b'C' => Some(self::key(ClientKeyCode::Right, ClientKeyModifiers::NONE, bytes)),
268 b'D' => Some(self::key(ClientKeyCode::Left, ClientKeyModifiers::NONE, bytes)),
269 _ => None,
270 }
271}
272
273fn key_for_kitty_keyboard_sequence(bytes: &[u8]) -> Option<ClientKey> {
274 if bytes.first() != Some(&ESC) || bytes.get(1) != Some(&b'[') || bytes.last() != Some(&b'u') {
275 return None;
276 }
277
278 let body_end = bytes.len().checked_sub(1)?;
279 let body = bytes.get(2..body_end)?;
280 let mut parts = body.split(|byte| *byte == b';');
281 let key_number = parts.next().and_then(self::parse_mouse_number)?;
282 let modifiers = match parts.next() {
283 Some(raw) => KittyKeyModifiers::from_raw(raw)?,
284 None => KittyKeyModifiers::Supported(ClientKeyModifiers::NONE),
285 };
286 if parts.next().is_some() {
287 return None;
288 }
289 let KittyKeyModifiers::Supported(modifiers) = modifiers else {
290 return Some(self::key(ClientKeyCode::Unknown, ClientKeyModifiers::NONE, bytes));
293 };
294
295 let code = match key_number {
296 9 => ClientKeyCode::Tab,
297 13 => ClientKeyCode::Enter,
298 27 => ClientKeyCode::Esc,
299 127 => ClientKeyCode::Backspace,
300 32..=126 => ClientKeyCode::Char(self::kitty_ascii_character(key_number, modifiers)?),
301 _ => ClientKeyCode::Unknown,
302 };
303
304 Some(self::key(code, modifiers, bytes))
305}
306
307fn kitty_ascii_character(key_number: u16, modifiers: ClientKeyModifiers) -> Option<char> {
308 let character = char::from(u8::try_from(key_number).ok()?);
309 if modifiers.shift && character.is_ascii_lowercase() {
312 Some(character.to_ascii_uppercase())
313 } else {
314 Some(character)
315 }
316}
317
318fn sgr_mouse_button_and_position(bytes: &[u8]) -> Option<(u16, ClientMousePosition)> {
319 let body_end = bytes.len().checked_sub(1)?;
320 let body = bytes.get(3..body_end)?;
321 let mut parts = body.split(|byte| *byte == b';');
322 let button = parts.next().and_then(self::parse_mouse_number)?;
323 let col = parts
324 .next()
325 .and_then(self::parse_mouse_number)
326 .and_then(|col| col.checked_sub(1))?;
327 let row = parts
328 .next()
329 .and_then(self::parse_mouse_number)
330 .and_then(|row| row.checked_sub(1))?;
331 if parts.next().is_some() {
332 return None;
333 }
334
335 Some((button, ClientMousePosition { row, col }))
336}
337
338fn parse_mouse_number(raw: &[u8]) -> Option<u16> {
339 if raw.is_empty() {
340 return None;
341 }
342
343 let mut value = 0_u16;
344 for byte in raw {
345 if !byte.is_ascii_digit() {
346 return None;
347 }
348 let digit = u16::from(byte.saturating_sub(b'0'));
349 value = value.checked_mul(10)?.checked_add(digit)?;
350 }
351 Some(value)
352}
353
354fn push_key(decoded: &mut Vec<DecodedInput>, input: &mut Vec<u8>, key: ClientKey) {
355 self::push_input(decoded, input);
356 decoded.push(DecodedInput::Key(key));
357}
358
359#[derive(Clone, Copy, Debug, Eq, PartialEq)]
360pub enum InputIdleTimeout {
361 Needed,
362 NotNeeded,
363}
364
365#[derive(Clone, Copy, Debug, Eq, PartialEq)]
366enum PendingEscapeStatus {
367 Complete,
368 Incomplete,
369}
370
371impl From<&[u8]> for PendingEscapeStatus {
372 fn from(bytes: &[u8]) -> Self {
373 if bytes.len() > MAX_PENDING_ESCAPE_BYTES {
374 return Self::Complete;
375 }
376
377 let complete = match bytes {
378 [ESC] | [ESC, b'['] | [ESC, b'[', b'<'] => return Self::Incomplete,
379 [ESC, b'[', rest @ ..] => rest.last().is_some_and(|byte| (0x40..=0x7e).contains(byte)),
380 _ => true,
381 };
382 if complete { Self::Complete } else { Self::Incomplete }
383 }
384}
385
386fn push_input(decoded: &mut Vec<DecodedInput>, input: &mut Vec<u8>) {
387 if input.is_empty() {
388 return;
389 }
390
391 decoded.push(DecodedInput::Input(std::mem::take(input)));
392}
393
394fn key(code: ClientKeyCode, modifiers: ClientKeyModifiers, raw_bytes: &[u8]) -> ClientKey {
395 ClientKey {
396 code,
397 modifiers,
398 raw_bytes: raw_bytes.to_vec(),
399 }
400}
401
402#[cfg(test)]
403mod tests {
404 use rstest::rstest;
405 use test_that::prelude::*;
406
407 use super::*;
408
409 #[test]
410 fn test_input_decoder_decode_when_bytes_are_plain_returns_input() {
411 let mut decoder = InputDecoder::default();
412
413 assert_that!(decoder.decode(b"abc"), eq(vec![DecodedInput::Input(b"abc".to_vec())]));
414 }
415
416 #[test]
417 fn test_input_decoder_decode_when_bare_enter_arrives_preserves_input_bytes() {
418 let mut decoder = InputDecoder::default();
419
420 assert_that!(decoder.decode(b"\r"), eq(vec![DecodedInput::Input(b"\r".to_vec())]));
421 }
422
423 #[rstest]
424 #[case::create_tab(b"\x1bE", ClientKeyCode::Char('E'), ClientKeyModifiers::SHIFT_ALT)]
425 #[case::focus_previous_tab(b"\x1bP", ClientKeyCode::Char('P'), ClientKeyModifiers::SHIFT_ALT)]
426 #[case::focus_next_tab(b"\x1bN", ClientKeyCode::Char('N'), ClientKeyModifiers::SHIFT_ALT)]
427 #[case::move_tab_previous(b"\x1b\x10", ClientKeyCode::Char('p'), ClientKeyModifiers::CTRL_ALT)]
428 #[case::move_tab_next(b"\x1b\x0e", ClientKeyCode::Char('n'), ClientKeyModifiers::CTRL_ALT)]
429 #[case::focus_pane_left(b"\x1bH", ClientKeyCode::Char('H'), ClientKeyModifiers::SHIFT_ALT)]
430 #[case::focus_pane_down(b"\x1bJ", ClientKeyCode::Char('J'), ClientKeyModifiers::SHIFT_ALT)]
431 #[case::focus_pane_up(b"\x1bK", ClientKeyCode::Char('K'), ClientKeyModifiers::SHIFT_ALT)]
432 #[case::focus_pane_right(b"\x1bL", ClientKeyCode::Char('L'), ClientKeyModifiers::SHIFT_ALT)]
433 #[case::split_pane_vertical(b"\x1bV", ClientKeyCode::Char('V'), ClientKeyModifiers::SHIFT_ALT)]
434 #[case::split_pane_horizontal(b"\x1bD", ClientKeyCode::Char('D'), ClientKeyModifiers::SHIFT_ALT)]
435 #[case::toggle_pane_fullscreen(b"\x1bF", ClientKeyCode::Char('F'), ClientKeyModifiers::SHIFT_ALT)]
436 #[case::close_pane(b"\x1bW", ClientKeyCode::Char('W'), ClientKeyModifiers::SHIFT_ALT)]
437 #[case::enter_resize_mode(b"\x1bR", ClientKeyCode::Char('R'), ClientKeyModifiers::SHIFT_ALT)]
438 #[case::open_scrollback_editor(b"\x1bS", ClientKeyCode::Char('S'), ClientKeyModifiers::SHIFT_ALT)]
439 #[case::kitty_create_tab(b"\x1b[101;4u", ClientKeyCode::Char('E'), ClientKeyModifiers::SHIFT_ALT)]
440 #[case::kitty_split_pane_vertical(b"\x1b[118;4u", ClientKeyCode::Char('V'), ClientKeyModifiers::SHIFT_ALT)]
441 #[case::kitty_move_tab_previous(b"\x1b[112;7u", ClientKeyCode::Char('p'), ClientKeyModifiers::CTRL_ALT)]
442 fn test_input_decoder_decode_when_shortcut_arrives_returns_key(
443 #[case] bytes: &[u8],
444 #[case] code: ClientKeyCode,
445 #[case] modifiers: ClientKeyModifiers,
446 ) {
447 let mut decoder = InputDecoder::default();
448
449 assert_that!(
450 decoder.decode(bytes),
451 eq(vec![DecodedInput::Key(key(code, modifiers, bytes))])
452 );
453 }
454
455 #[rstest]
456 #[case::legacy(b"\x1bC")]
457 #[case::kitty(b"\x1b[99;4u")]
458 fn test_input_decoder_decode_when_copy_shortcut_arrives_returns_copy_selection(#[case] bytes: &[u8]) {
459 let mut decoder = InputDecoder::default();
460
461 assert_that!(decoder.decode(bytes), eq(vec![DecodedInput::CopySelection]));
462 }
463
464 #[rstest]
465 #[case::legacy(b"\x1bX")]
466 #[case::kitty(b"\x1b[120;4u")]
467 fn test_input_decoder_decode_when_inline_copy_shortcut_arrives_returns_inline_copy_selection(#[case] bytes: &[u8]) {
468 let mut decoder = InputDecoder::default();
469
470 assert_that!(decoder.decode(bytes), eq(vec![DecodedInput::CopySelectionInline]));
471 }
472
473 #[test]
474 fn test_input_decoder_decode_when_shortcut_is_between_input_splits_actions() {
475 let mut decoder = InputDecoder::default();
476
477 assert_that!(
478 decoder.decode(b"a\x1bEb"),
479 eq(vec![
480 DecodedInput::Input(b"a".to_vec()),
481 DecodedInput::Key(key(ClientKeyCode::Char('E'), ClientKeyModifiers::SHIFT_ALT, b"\x1bE",)),
482 DecodedInput::Input(b"b".to_vec()),
483 ])
484 );
485 }
486
487 #[rstest]
488 #[case::unknown_escape(b"\x1bY")]
489 #[case::unknown_csi(b"\x1b[1~")]
490 fn test_input_decoder_decode_when_escape_is_not_muxr_cmd_preserves_bytes(#[case] bytes: &[u8]) {
491 let mut decoder = InputDecoder::default();
492
493 assert_that!(decoder.decode(bytes), eq(vec![DecodedInput::Input(bytes.to_vec())]));
494 }
495
496 #[test]
497 fn test_input_decoder_decode_when_shortcut_is_split_preserves_pending_prefix() {
498 let mut decoder = InputDecoder::default();
499
500 assert_that!(decoder.decode(b"\x1b"), eq(Vec::<DecodedInput>::new()));
501 assert_that!(decoder.idle_timeout(), eq(InputIdleTimeout::Needed));
502 assert_that!(
503 decoder.decode(b"E"),
504 eq(vec![DecodedInput::Key(key(
505 ClientKeyCode::Char('E'),
506 ClientKeyModifiers::SHIFT_ALT,
507 b"\x1bE",
508 ))])
509 );
510 assert_that!(decoder.idle_timeout(), eq(InputIdleTimeout::NotNeeded));
511 }
512
513 #[test]
514 fn test_input_decoder_finalize_when_bare_escape_arrives_returns_key() {
515 let mut decoder = InputDecoder::default();
516
517 assert_that!(decoder.decode(b"\x1b"), eq(Vec::<DecodedInput>::new()));
518 assert_that!(decoder.idle_timeout(), eq(InputIdleTimeout::Needed));
519 assert_that!(
520 decoder.finalize(),
521 eq(vec![DecodedInput::Key(key(
522 ClientKeyCode::Esc,
523 ClientKeyModifiers::NONE,
524 b"\x1b",
525 ))])
526 );
527 assert_that!(decoder.idle_timeout(), eq(InputIdleTimeout::NotNeeded));
528 }
529
530 #[test]
531 fn test_input_decoder_finalize_when_pending_unknown_sequence_arrives_preserves_bytes() {
532 let mut decoder = InputDecoder::default();
533 let bytes = b"\x1b[1";
534
535 assert_that!(decoder.decode(bytes), eq(Vec::<DecodedInput>::new()));
536 assert_that!(decoder.idle_timeout(), eq(InputIdleTimeout::Needed));
537 assert_that!(decoder.finalize(), eq(vec![DecodedInput::Input(bytes.to_vec())]));
538 assert_that!(decoder.idle_timeout(), eq(InputIdleTimeout::NotNeeded));
539 }
540
541 #[rstest]
542 #[case::left(b"h", ClientKeyCode::Char('h'))]
543 #[case::down(b"j", ClientKeyCode::Char('j'))]
544 #[case::up(b"k", ClientKeyCode::Char('k'))]
545 #[case::right(b"l", ClientKeyCode::Char('l'))]
546 #[case::arrow_left(b"\x1b[D", ClientKeyCode::Left)]
547 #[case::arrow_down(b"\x1b[B", ClientKeyCode::Down)]
548 #[case::arrow_up(b"\x1b[A", ClientKeyCode::Up)]
549 #[case::arrow_right(b"\x1b[C", ClientKeyCode::Right)]
550 fn test_input_decoder_decode_when_server_mode_key_arrives_returns_key(
551 #[case] bytes: &[u8],
552 #[case] code: ClientKeyCode,
553 ) {
554 let mut decoder = InputDecoder::default();
555
556 assert_that!(
557 decoder.decode(bytes),
558 eq(vec![DecodedInput::Key(key(code, ClientKeyModifiers::NONE, bytes))])
559 );
560 }
561
562 #[test]
563 fn test_input_decoder_decode_when_arrow_is_split_preserves_pending_prefix() {
564 let mut decoder = InputDecoder::default();
565
566 assert_that!(decoder.decode(b"\x1b["), eq(Vec::<DecodedInput>::new()));
567 assert_that!(decoder.idle_timeout(), eq(InputIdleTimeout::Needed));
568 assert_that!(
569 decoder.decode(b"D"),
570 eq(vec![DecodedInput::Key(key(
571 ClientKeyCode::Left,
572 ClientKeyModifiers::NONE,
573 b"\x1b[D",
574 ))])
575 );
576 assert_that!(decoder.idle_timeout(), eq(InputIdleTimeout::NotNeeded));
577 }
578
579 #[rstest]
580 #[case::plain_enter(b"\x1b[13u", ClientKeyCode::Enter, ClientKeyModifiers::NONE)]
581 #[case::shift_enter(b"\x1b[13;2u", ClientKeyCode::Enter, ClientKeyModifiers::SHIFT)]
582 #[case::shift_tab(b"\x1b[9;2u", ClientKeyCode::Tab, ClientKeyModifiers::SHIFT)]
583 #[case::alt_backspace(b"\x1b[127;3u", ClientKeyCode::Backspace, ClientKeyModifiers::ALT)]
584 #[case::shift_backspace(b"\x1b[127;2u", ClientKeyCode::Backspace, ClientKeyModifiers::SHIFT)]
585 #[case::ctrl_l(b"\x1b[108;5u", ClientKeyCode::Char('l'), self::modifiers(false, false, true))]
586 #[case::ctrl_k(b"\x1b[107;5u", ClientKeyCode::Char('k'), self::modifiers(false, false, true))]
587 #[case::unknown_modified_key(b"\x1b[999;2u", ClientKeyCode::Unknown, ClientKeyModifiers::SHIFT)]
588 #[case::unsupported_modifier_bits(b"\x1b[118;12u", ClientKeyCode::Unknown, ClientKeyModifiers::NONE)]
589 fn test_input_decoder_decode_when_kitty_key_arrives_returns_key(
590 #[case] bytes: &[u8],
591 #[case] code: ClientKeyCode,
592 #[case] modifiers: ClientKeyModifiers,
593 ) {
594 let mut decoder = InputDecoder::default();
595
596 assert_that!(
597 decoder.decode(bytes),
598 eq(vec![DecodedInput::Key(key(code, modifiers, bytes))])
599 );
600 }
601
602 #[test]
603 fn test_input_decoder_decode_when_kitty_key_is_split_preserves_pending_prefix() {
604 let mut decoder = InputDecoder::default();
605
606 assert_that!(decoder.decode(b"\x1b[13"), eq(Vec::<DecodedInput>::new()));
607 assert_that!(decoder.idle_timeout(), eq(InputIdleTimeout::Needed));
608 assert_that!(
609 decoder.decode(b";2u"),
610 eq(vec![DecodedInput::Key(key(
611 ClientKeyCode::Enter,
612 ClientKeyModifiers::SHIFT,
613 b"\x1b[13;2u",
614 ))])
615 );
616 assert_that!(decoder.idle_timeout(), eq(InputIdleTimeout::NotNeeded));
617 }
618
619 #[test]
620 fn test_input_decoder_decode_when_bracketed_paste_arrives_returns_single_paste() {
621 let mut decoder = InputDecoder::default();
622
623 assert_that!(
624 decoder.decode(b"\x1b[200~echo hi\n\x1b[201~"),
625 eq(vec![DecodedInput::Paste(b"echo hi\n".to_vec())])
626 );
627 }
628
629 #[test]
630 fn test_input_decoder_decode_when_bracketed_paste_is_split_preserves_pending_paste() {
631 let mut decoder = InputDecoder::default();
632
633 assert_that!(decoder.decode(b"\x1b[200~echo"), eq(Vec::<DecodedInput>::new()));
634 assert_that!(decoder.idle_timeout(), eq(InputIdleTimeout::NotNeeded));
635 assert_that!(
636 decoder.decode(b" hi\n\x1b[201~"),
637 eq(vec![DecodedInput::Paste(b"echo hi\n".to_vec())])
638 );
639 assert_that!(decoder.idle_timeout(), eq(InputIdleTimeout::NotNeeded));
640 }
641
642 #[rstest]
643 #[case::bare_escape(b"\x1b")]
644 #[case::incomplete_csi(b"\x1b[")]
645 fn test_input_decoder_needs_idle_timeout_when_escape_prefix_is_pending(#[case] bytes: &[u8]) {
646 let mut decoder = InputDecoder::default();
647
648 assert_that!(decoder.decode(bytes), eq(Vec::<DecodedInput>::new()));
649
650 assert_that!(decoder.idle_timeout(), eq(InputIdleTimeout::Needed));
651 }
652
653 #[rstest]
654 #[case::wheel_up(b"\x1b[<64;10;5M", 64)]
655 #[case::wheel_down(b"\x1b[<65;10;5M", 65)]
656 fn test_input_decoder_decode_when_mouse_wheel_arrives_returns_mouse_event(
657 #[case] bytes: &[u8],
658 #[case] button: u16,
659 ) {
660 let mut decoder = InputDecoder::default();
661
662 assert_that!(
663 decoder.decode(bytes),
664 eq(vec![DecodedInput::Mouse(ClientMouseEvent {
665 button,
666 phase: ClientMouseEventPhase::Press,
667 position: ClientMousePosition { row: 4, col: 9 },
668 })])
669 );
670 }
671
672 #[test]
673 fn test_input_decoder_decode_when_mouse_click_arrives_returns_mouse_event() {
674 let mut decoder = InputDecoder::default();
675
676 assert_that!(
677 decoder.decode(b"\x1b[<0;10;5M"),
678 eq(vec![DecodedInput::Mouse(ClientMouseEvent {
679 button: 0,
680 phase: ClientMouseEventPhase::Press,
681 position: ClientMousePosition { row: 4, col: 9 },
682 })])
683 );
684 }
685
686 #[test]
687 fn test_input_decoder_decode_when_sgr_alt_mouse_click_arrives_returns_alt_mouse_event() {
688 let mut decoder = InputDecoder::default();
689
690 assert_that!(
691 decoder.decode(b"\x1b[<8;10;5M"),
692 eq(vec![DecodedInput::Mouse(ClientMouseEvent {
693 button: 8,
694 phase: ClientMouseEventPhase::Press,
695 position: ClientMousePosition { row: 4, col: 9 },
696 })])
697 );
698 }
699
700 #[test]
701 fn test_input_decoder_decode_when_sgr_alt_mouse_release_arrives_returns_alt_mouse_event() {
702 let mut decoder = InputDecoder::default();
703
704 assert_that!(
705 decoder.decode(b"\x1b[<8;10;5m"),
706 eq(vec![DecodedInput::Mouse(ClientMouseEvent {
707 button: 8,
708 phase: ClientMouseEventPhase::Release,
709 position: ClientMousePosition { row: 4, col: 9 },
710 })])
711 );
712 }
713
714 #[test]
715 fn test_input_decoder_decode_when_mouse_drag_arrives_returns_mouse_event() {
716 let mut decoder = InputDecoder::default();
717
718 assert_that!(
719 decoder.decode(b"\x1b[<32;10;5M"),
720 eq(vec![DecodedInput::Mouse(ClientMouseEvent {
721 button: 32,
722 phase: ClientMouseEventPhase::Press,
723 position: ClientMousePosition { row: 4, col: 9 },
724 })])
725 );
726 }
727
728 #[test]
729 fn test_input_decoder_decode_when_mouse_release_arrives_returns_mouse_event() {
730 let mut decoder = InputDecoder::default();
731
732 assert_that!(
733 decoder.decode(b"\x1b[<0;10;5m"),
734 eq(vec![DecodedInput::Mouse(ClientMouseEvent {
735 button: 0,
736 phase: ClientMouseEventPhase::Release,
737 position: ClientMousePosition { row: 4, col: 9 },
738 })])
739 );
740 }
741
742 const fn modifiers(shift: bool, alt: bool, ctrl: bool) -> ClientKeyModifiers {
743 ClientKeyModifiers { alt, ctrl, shift }
744 }
745}