Skip to main content

muxr_client/
input.rs

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