Skip to main content

muxr_client/
input.rs

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        // The wire type cannot represent kitty's higher modifier bits. Preserve the raw sequence as an unknown key so
291        // muxr shortcuts do not accidentally fire after dropping unsupported bits.
292        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    // Kitty level 1 may report a base lowercase ASCII key plus the Shift flag. Muxr bindings historically match the
310    // shifted legacy byte, such as Alt-Shift-V -> Char('V'), so normalize letters before server shortcut resolution.
311    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}