1
//! Decoder for keystroke byte streams arriving over a russh data
2
//! channel.
3
//!
4
//! `crossterm::event::read` can't be used here because it reads from
5
//! `/dev/tty`. Russh hands us raw bytes from the wire instead, so we
6
//! parse the relevant subset of xterm / VT100 escape sequences in
7
//! process and emit [`tui::transport::RawEvent`]s shaped exactly
8
//! like the ones [`crate::handler`] would receive from a local
9
//! transport.
10
//!
11
//! Coverage matches what crossterm produces against a typical PTY:
12
//!
13
//! - ASCII 0x20–0x7E → `Char(c)` (Shift implied for upper case to
14
//!   match crossterm's behaviour).
15
//! - Ctrl-letter (0x01–0x1A, with 0x09/0x0a/0x0d/0x1b carrying
16
//!   their own [`KeyCode`]).
17
//! - Tab, Enter, Backspace, Esc, plus arrow keys, Home/End/PageUp/
18
//!   PageDown/Insert/Delete, F1–F12 (`ESC O P` and `ESC [ NN ~`
19
//!   forms).
20
//! - Modifier-encoded forms `ESC [ 1 ; 5 A` per xterm convention.
21
//! - `ESC` + printable as Alt-letter.
22
//! - UTF-8 continuation bytes are accumulated until a full code
23
//!   point is decoded.
24
//!
25
//! The parser is deliberately minimal — bracketed paste, mouse
26
//! reports, and the rest of the VT200/CSI zoo are unrecognised and
27
//! left as inert bytes (logged at trace level so we can grow
28
//! coverage if a real client trips on something).
29

            
30
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
31
use tui::transport::RawEvent;
32

            
33
/// Stateful byte → `RawEvent` decoder.
34
#[derive(Debug, Default)]
35
pub struct KeyDecoder {
36
    buf: Vec<u8>,
37
}
38

            
39
impl KeyDecoder {
40
    #[must_use]
41
12
    pub fn new() -> Self {
42
12
        Self::default()
43
12
    }
44

            
45
    /// Append `bytes` to the internal accumulator and yield every
46
    /// `RawEvent` we can decode without consuming an incomplete
47
    /// trailing sequence.
48
16
    pub fn feed(&mut self, bytes: &[u8]) -> Vec<RawEvent> {
49
16
        self.buf.extend_from_slice(bytes);
50
16
        let mut events = Vec::new();
51
16
        let mut consumed = 0usize;
52
39
        while consumed < self.buf.len() {
53
27
            match decode_one(&self.buf[consumed..]) {
54
23
                Decoded::Event(ev, n) => {
55
23
                    events.push(ev);
56
23
                    consumed += n;
57
23
                }
58
                Decoded::Skip(n) => consumed += n,
59
4
                Decoded::Incomplete => break,
60
            }
61
        }
62
16
        if consumed > 0 {
63
12
            self.buf.drain(..consumed);
64
12
        }
65
16
        events
66
16
    }
67
}
68

            
69
enum Decoded {
70
    Event(RawEvent, usize),
71
    /// Bytes we recognise but don't translate (e.g. mouse reports).
72
    /// Drop them to keep the buffer flowing.
73
    Skip(usize),
74
    /// Need more bytes before we can finalise.
75
    Incomplete,
76
}
77

            
78
27
fn decode_one(input: &[u8]) -> Decoded {
79
27
    if input.is_empty() {
80
        return Decoded::Incomplete;
81
27
    }
82
27
    let b0 = input[0];
83
27
    match b0 {
84
1
        0x09 => key(KeyCode::Tab, KeyModifiers::NONE, 1),
85
1
        0x0a | 0x0d => key(KeyCode::Enter, KeyModifiers::NONE, 1),
86
1
        0x7f | 0x08 => key(KeyCode::Backspace, KeyModifiers::NONE, 1),
87
19
        0x1b => decode_escape(input),
88
5
        0x01..=0x1a => {
89
1
            let letter = (b0 - 1 + b'a') as char;
90
1
            key(KeyCode::Char(letter), KeyModifiers::CONTROL, 1)
91
        }
92
4
        0x20..=0x7e => key(KeyCode::Char(b0 as char), modifiers_for_ascii(b0), 1),
93
2
        _ => decode_utf8(input),
94
    }
95
27
}
96

            
97
2
fn modifiers_for_ascii(b: u8) -> KeyModifiers {
98
2
    if b.is_ascii_uppercase() {
99
1
        KeyModifiers::SHIFT
100
    } else {
101
1
        KeyModifiers::NONE
102
    }
103
2
}
104

            
105
23
fn key(code: KeyCode, mods: KeyModifiers, consumed: usize) -> Decoded {
106
23
    Decoded::Event(RawEvent::Key(KeyEvent::new(code, mods)), consumed)
107
23
}
108

            
109
2
fn decode_utf8(input: &[u8]) -> Decoded {
110
    // Fast path for ASCII-only callers; we only land here for >=0x80.
111
2
    let needed = utf8_width(input[0]);
112
2
    if needed == 0 {
113
        return Decoded::Skip(1);
114
2
    }
115
2
    if input.len() < needed {
116
1
        return Decoded::Incomplete;
117
1
    }
118
1
    match std::str::from_utf8(&input[..needed]) {
119
1
        Ok(s) => {
120
1
            if let Some(c) = s.chars().next() {
121
1
                key(KeyCode::Char(c), KeyModifiers::NONE, needed)
122
            } else {
123
                Decoded::Skip(needed)
124
            }
125
        }
126
        Err(_) => Decoded::Skip(1),
127
    }
128
2
}
129

            
130
2
const fn utf8_width(b: u8) -> usize {
131
2
    match b {
132
2
        0x00..=0x7f => 1,
133
2
        0xc2..=0xdf => 2,
134
        0xe0..=0xef => 3,
135
        0xf0..=0xf4 => 4,
136
        _ => 0,
137
    }
138
2
}
139

            
140
19
fn decode_escape(input: &[u8]) -> Decoded {
141
19
    if input.len() == 1 {
142
        // ambiguous: lone Esc or start of an escape sequence
143
        // — wait one round to disambiguate.
144
1
        return Decoded::Incomplete;
145
18
    }
146
18
    let b1 = input[1];
147
18
    match b1 {
148
13
        b'[' => decode_csi(input),
149
4
        b'O' => decode_ss3(input),
150
1
        0x20..=0x7e => {
151
            // Esc + printable → Alt-letter
152
1
            let code = KeyCode::Char(b1 as char);
153
1
            let mut mods = KeyModifiers::ALT;
154
1
            if b1.is_ascii_uppercase() {
155
                mods |= KeyModifiers::SHIFT;
156
1
            }
157
1
            key(code, mods, 2)
158
        }
159
        _ => {
160
            // Fallback: surface a bare Esc and let the next byte be
161
            // re-decoded on its own.
162
            key(KeyCode::Esc, KeyModifiers::NONE, 1)
163
        }
164
    }
165
19
}
166

            
167
13
fn decode_csi(input: &[u8]) -> Decoded {
168
    // Format: ESC [ <params> <final>
169
    // Params are ASCII digits and ';'. Final is 0x40..=0x7e.
170
13
    let mut idx = 2;
171
29
    while idx < input.len() {
172
27
        let b = input[idx];
173
27
        if (0x40..=0x7e).contains(&b) {
174
11
            let params = &input[2..idx];
175
11
            return finalise_csi(params, b, idx + 1);
176
16
        }
177
16
        idx += 1;
178
    }
179
2
    Decoded::Incomplete
180
13
}
181

            
182
11
fn finalise_csi(params: &[u8], final_byte: u8, consumed: usize) -> Decoded {
183
11
    let parts = parse_params(params);
184
11
    let mods = parts
185
11
        .get(1)
186
11
        .copied()
187
11
        .map_or(KeyModifiers::NONE, modifiers_from_xterm);
188
11
    match final_byte {
189
3
        b'A' => key(KeyCode::Up, mods, consumed),
190
1
        b'B' => key(KeyCode::Down, mods, consumed),
191
1
        b'C' => key(KeyCode::Right, mods, consumed),
192
1
        b'D' => key(KeyCode::Left, mods, consumed),
193
        b'H' => key(KeyCode::Home, mods, consumed),
194
        b'F' => key(KeyCode::End, mods, consumed),
195
        b'Z' => key(KeyCode::BackTab, KeyModifiers::SHIFT, consumed),
196
5
        b'~' => match parts.first().copied().unwrap_or(0) {
197
            1 | 7 => key(KeyCode::Home, mods, consumed),
198
            2 => key(KeyCode::Insert, mods, consumed),
199
            3 => key(KeyCode::Delete, mods, consumed),
200
            4 | 8 => key(KeyCode::End, mods, consumed),
201
1
            5 => key(KeyCode::PageUp, mods, consumed),
202
1
            6 => key(KeyCode::PageDown, mods, consumed),
203
            // xterm convention for F5-F12; F1-F4 take the SS3 form
204
            // `ESC O P/Q/R/S` instead.
205
1
            15 => key(KeyCode::F(5), mods, consumed),
206
1
            17 => key(KeyCode::F(6), mods, consumed),
207
            18 => key(KeyCode::F(7), mods, consumed),
208
            19 => key(KeyCode::F(8), mods, consumed),
209
            20 => key(KeyCode::F(9), mods, consumed),
210
            21 => key(KeyCode::F(10), mods, consumed),
211
            23 => key(KeyCode::F(11), mods, consumed),
212
1
            24 => key(KeyCode::F(12), mods, consumed),
213
            // rxvt-style F1-F4 fallback so screen/tmux clients aren't
214
            // mute on those keys.
215
            11 => key(KeyCode::F(1), mods, consumed),
216
            12 => key(KeyCode::F(2), mods, consumed),
217
            13 => key(KeyCode::F(3), mods, consumed),
218
            14 => key(KeyCode::F(4), mods, consumed),
219
            _ => Decoded::Skip(consumed),
220
        },
221
        _ => Decoded::Skip(consumed),
222
    }
223
11
}
224

            
225
4
fn decode_ss3(input: &[u8]) -> Decoded {
226
4
    if input.len() < 3 {
227
        return Decoded::Incomplete;
228
4
    }
229
4
    let code = match input[2] {
230
1
        b'P' => KeyCode::F(1),
231
1
        b'Q' => KeyCode::F(2),
232
1
        b'R' => KeyCode::F(3),
233
1
        b'S' => KeyCode::F(4),
234
        b'A' => KeyCode::Up,
235
        b'B' => KeyCode::Down,
236
        b'C' => KeyCode::Right,
237
        b'D' => KeyCode::Left,
238
        b'H' => KeyCode::Home,
239
        b'F' => KeyCode::End,
240
        _ => return Decoded::Skip(3),
241
    };
242
4
    key(code, KeyModifiers::NONE, 3)
243
4
}
244

            
245
11
fn parse_params(raw: &[u8]) -> Vec<u32> {
246
14
    raw.split(|b| *b == b';')
247
13
        .map(|seg| std::str::from_utf8(seg).ok().and_then(|s| s.parse().ok()))
248
13
        .map(|v| v.unwrap_or(0))
249
11
        .collect()
250
11
}
251

            
252
2
fn modifiers_from_xterm(code: u32) -> KeyModifiers {
253
2
    if code == 0 {
254
        return KeyModifiers::NONE;
255
2
    }
256
2
    let bits = code.saturating_sub(1);
257
2
    let mut mods = KeyModifiers::NONE;
258
2
    if bits & 0b001 != 0 {
259
        mods |= KeyModifiers::SHIFT;
260
2
    }
261
2
    if bits & 0b010 != 0 {
262
        mods |= KeyModifiers::ALT;
263
2
    }
264
2
    if bits & 0b100 != 0 {
265
2
        mods |= KeyModifiers::CONTROL;
266
2
    }
267
2
    mods
268
2
}
269

            
270
#[cfg(test)]
271
mod tests {
272
    use super::*;
273

            
274
11
    fn first_key(events: &[RawEvent]) -> KeyEvent {
275
11
        match events.first().unwrap() {
276
11
            RawEvent::Key(k) => *k,
277
            other => panic!("expected key, got {other:?}"),
278
        }
279
11
    }
280

            
281
    #[test]
282
1
    fn ascii_letter_lower() {
283
1
        let mut d = KeyDecoder::new();
284
1
        let evs = d.feed(b"a");
285
1
        assert_eq!(first_key(&evs).code, KeyCode::Char('a'));
286
1
        assert_eq!(first_key(&evs).modifiers, KeyModifiers::NONE);
287
1
    }
288

            
289
    #[test]
290
1
    fn ascii_letter_upper_carries_shift() {
291
1
        let mut d = KeyDecoder::new();
292
1
        let evs = d.feed(b"A");
293
1
        assert_eq!(first_key(&evs).modifiers, KeyModifiers::SHIFT);
294
1
    }
295

            
296
    #[test]
297
1
    fn ctrl_letter() {
298
1
        let mut d = KeyDecoder::new();
299
1
        let evs = d.feed(&[0x03]); // Ctrl-C
300
1
        let key = first_key(&evs);
301
1
        assert_eq!(key.code, KeyCode::Char('c'));
302
1
        assert_eq!(key.modifiers, KeyModifiers::CONTROL);
303
1
    }
304

            
305
    #[test]
306
1
    fn tab_enter_backspace() {
307
1
        let mut d = KeyDecoder::new();
308
1
        let evs = d.feed(&[0x09, 0x0d, 0x7f]);
309
1
        assert_eq!(evs.len(), 3);
310
1
        assert_eq!(first_key(&evs[..1]).code, KeyCode::Tab);
311
1
    }
312

            
313
    #[test]
314
1
    fn esc_alone_is_buffered_then_emitted_on_next_byte() {
315
1
        let mut d = KeyDecoder::new();
316
1
        let evs1 = d.feed(&[0x1b]);
317
1
        assert!(evs1.is_empty(), "lone Esc must wait for disambiguation");
318
1
        let evs2 = d.feed(b"x");
319
        // ESC followed by printable is Alt-x, not Esc + 'x'.
320
1
        assert_eq!(first_key(&evs2).code, KeyCode::Char('x'));
321
1
        assert!(first_key(&evs2).modifiers.contains(KeyModifiers::ALT));
322
1
    }
323

            
324
    #[test]
325
1
    fn arrow_keys() {
326
1
        let mut d = KeyDecoder::new();
327
1
        let evs = d.feed(b"\x1b[A\x1b[B\x1b[C\x1b[D");
328
1
        let codes: Vec<_> = evs
329
1
            .iter()
330
4
            .map(|e| match e {
331
4
                RawEvent::Key(k) => k.code,
332
                _ => unreachable!(),
333
4
            })
334
1
            .collect();
335
1
        assert_eq!(
336
            codes,
337
1
            vec![KeyCode::Up, KeyCode::Down, KeyCode::Right, KeyCode::Left]
338
        );
339
1
    }
340

            
341
    #[test]
342
1
    fn modified_arrow_ctrl_up() {
343
1
        let mut d = KeyDecoder::new();
344
1
        let evs = d.feed(b"\x1b[1;5A");
345
1
        let key = first_key(&evs);
346
1
        assert_eq!(key.code, KeyCode::Up);
347
1
        assert_eq!(key.modifiers, KeyModifiers::CONTROL);
348
1
    }
349

            
350
    #[test]
351
1
    fn page_up_page_down() {
352
1
        let mut d = KeyDecoder::new();
353
1
        let evs = d.feed(b"\x1b[5~\x1b[6~");
354
1
        let codes: Vec<_> = evs
355
1
            .iter()
356
2
            .map(|e| match e {
357
2
                RawEvent::Key(k) => k.code,
358
                _ => unreachable!(),
359
2
            })
360
1
            .collect();
361
1
        assert_eq!(codes, vec![KeyCode::PageUp, KeyCode::PageDown]);
362
1
    }
363

            
364
    #[test]
365
1
    fn function_keys_via_ss3() {
366
1
        let mut d = KeyDecoder::new();
367
1
        let evs = d.feed(b"\x1bOP\x1bOQ\x1bOR\x1bOS");
368
1
        let codes: Vec<_> = evs
369
1
            .iter()
370
4
            .map(|e| match e {
371
4
                RawEvent::Key(k) => k.code,
372
                _ => unreachable!(),
373
4
            })
374
1
            .collect();
375
1
        assert_eq!(
376
            codes,
377
1
            vec![KeyCode::F(1), KeyCode::F(2), KeyCode::F(3), KeyCode::F(4)]
378
        );
379
1
    }
380

            
381
    #[test]
382
1
    fn function_keys_via_csi() {
383
1
        let mut d = KeyDecoder::new();
384
1
        let evs = d.feed(b"\x1b[15~\x1b[17~\x1b[24~");
385
1
        let codes: Vec<_> = evs
386
1
            .iter()
387
3
            .map(|e| match e {
388
3
                RawEvent::Key(k) => k.code,
389
                _ => unreachable!(),
390
3
            })
391
1
            .collect();
392
1
        assert_eq!(codes, vec![KeyCode::F(5), KeyCode::F(6), KeyCode::F(12)]);
393
1
    }
394

            
395
    #[test]
396
1
    fn split_csi_across_feeds() {
397
1
        let mut d = KeyDecoder::new();
398
1
        assert!(d.feed(b"\x1b[").is_empty());
399
1
        assert!(d.feed(b"1;").is_empty());
400
1
        let evs = d.feed(b"5A");
401
1
        assert_eq!(first_key(&evs).code, KeyCode::Up);
402
1
        assert_eq!(first_key(&evs).modifiers, KeyModifiers::CONTROL);
403
1
    }
404

            
405
    #[test]
406
1
    fn utf8_multi_byte() {
407
1
        let mut d = KeyDecoder::new();
408
        // 'ñ' = 0xc3 0xb1
409
1
        assert!(d.feed(&[0xc3]).is_empty());
410
1
        let evs = d.feed(&[0xb1]);
411
1
        assert_eq!(first_key(&evs).code, KeyCode::Char('ñ'));
412
1
    }
413
}