brewery
notashelf /
8486c343d709df37d13fc930c34c5317109928b6

beer

public

A terminal worth pouring time into

Star0tarball
clone
ssh://git@git.notashelf.dev:33/notashelf/beer.git
src/input.rsRust186 lines5.5 KB
1
//! Keyboard encoding: translate decoded key events into the byte sequences a
2
//! terminal application expects (xterm/VT-style).
3
4
use smithay_client_toolkit::seat::keyboard::{KeyEvent, Keysym, Modifiers};
5
6
/// Encode a key press into bytes for the PTY, or `None` if it produces no input.
7
pub fn encode(event: &KeyEvent, mods: Modifiers, app_cursor: bool) -> Option<Vec<u8>> {
8
    let seq = match event.keysym {
9
        Keysym::Return | Keysym::KP_Enter => prefix_alt(b"\r".to_vec(), mods),
10
        Keysym::BackSpace => prefix_alt(b"\x7f".to_vec(), mods),
11
        Keysym::Tab if mods.shift => b"\x1b[Z".to_vec(),
12
        Keysym::Tab => b"\t".to_vec(),
13
        Keysym::Escape => b"\x1b".to_vec(),
14
15
        Keysym::Up => csi_letter(b'A', mods, app_cursor),
16
        Keysym::Down => csi_letter(b'B', mods, app_cursor),
17
        Keysym::Right => csi_letter(b'C', mods, app_cursor),
18
        Keysym::Left => csi_letter(b'D', mods, app_cursor),
19
        Keysym::Home => csi_letter(b'H', mods, app_cursor),
20
        Keysym::End => csi_letter(b'F', mods, app_cursor),
21
22
        Keysym::Insert => csi_tilde(2, mods),
23
        Keysym::Delete => csi_tilde(3, mods),
24
        Keysym::Page_Up => csi_tilde(5, mods),
25
        Keysym::Page_Down => csi_tilde(6, mods),
26
27
        // F1-F4 are SS3-introduced; F5+ use the CSI tilde forms.
28
        Keysym::F1 => fkey(b'P', mods),
29
        Keysym::F2 => fkey(b'Q', mods),
30
        Keysym::F3 => fkey(b'R', mods),
31
        Keysym::F4 => fkey(b'S', mods),
32
        Keysym::F5 => csi_tilde(15, mods),
33
        Keysym::F6 => csi_tilde(17, mods),
34
        Keysym::F7 => csi_tilde(18, mods),
35
        Keysym::F8 => csi_tilde(19, mods),
36
        Keysym::F9 => csi_tilde(20, mods),
37
        Keysym::F10 => csi_tilde(21, mods),
38
        Keysym::F11 => csi_tilde(23, mods),
39
        Keysym::F12 => csi_tilde(24, mods),
40
41
        // Everything else: the xkb-composed text (which already folds in Ctrl),
42
        // with Alt sending an ESC prefix (meta).
43
        _ => {
44
            let text = event.utf8.as_ref()?;
45
            if text.is_empty() {
46
                return None;
47
            }
48
            prefix_alt(text.as_bytes().to_vec(), mods)
49
        }
50
    };
51
    Some(seq)
52
}
53
54
fn prefix_alt(bytes: Vec<u8>, mods: Modifiers) -> Vec<u8> {
55
    if mods.alt {
56
        let mut out = Vec::with_capacity(bytes.len() + 1);
57
        out.push(0x1b);
58
        out.extend_from_slice(&bytes);
59
        out
60
    } else {
61
        bytes
62
    }
63
}
64
65
/// xterm modifier parameter: 1 + a bitfield of the held modifiers.
66
fn modifier_param(mods: Modifiers) -> u8 {
67
    1 + u8::from(mods.shift)
68
        + (u8::from(mods.alt) << 1)
69
        + (u8::from(mods.ctrl) << 2)
70
        + (u8::from(mods.logo) << 3)
71
}
72
73
/// Cursor/edit keys: `ESC [ X` (or `ESC O X` in application-cursor mode), and
74
/// `ESC [ 1 ; m X` when modifiers are held.
75
fn csi_letter(final_byte: u8, mods: Modifiers, app_cursor: bool) -> Vec<u8> {
76
    let m = modifier_param(mods);
77
    if m == 1 {
78
        vec![0x1b, if app_cursor { b'O' } else { b'[' }, final_byte]
79
    } else {
80
        let mut v = format!("\x1b[1;{m}").into_bytes();
81
        v.push(final_byte);
82
        v
83
    }
84
}
85
86
/// Keypad-style keys: `ESC [ n ~`, with `ESC [ n ; m ~` when modifiers are held.
87
fn csi_tilde(n: u8, mods: Modifiers) -> Vec<u8> {
88
    let m = modifier_param(mods);
89
    if m == 1 {
90
        format!("\x1b[{n}~").into_bytes()
91
    } else {
92
        format!("\x1b[{n};{m}~").into_bytes()
93
    }
94
}
95
96
fn fkey(final_byte: u8, mods: Modifiers) -> Vec<u8> {
97
    let m = modifier_param(mods);
98
    if m == 1 {
99
        vec![0x1b, b'O', final_byte]
100
    } else {
101
        let mut v = format!("\x1b[1;{m}").into_bytes();
102
        v.push(final_byte);
103
        v
104
    }
105
}
106
107
#[cfg(test)]
108
mod tests {
109
    use super::*;
110
111
    fn key(keysym: Keysym, utf8: Option<&str>) -> KeyEvent {
112
        KeyEvent {
113
            time: 0,
114
            raw_code: 0,
115
            keysym,
116
            utf8: utf8.map(str::to_owned),
117
        }
118
    }
119
120
    const NONE: Modifiers = Modifiers {
121
        ctrl: false,
122
        alt: false,
123
        shift: false,
124
        caps_lock: false,
125
        logo: false,
126
        num_lock: false,
127
    };
128
129
    #[test]
130
    fn plain_text_passes_through() {
131
        assert_eq!(
132
            encode(&key(Keysym::a, Some("a")), NONE, false),
133
            Some(b"a".to_vec())
134
        );
135
    }
136
137
    #[test]
138
    fn alt_prefixes_escape() {
139
        let mods = Modifiers { alt: true, ..NONE };
140
        assert_eq!(
141
            encode(&key(Keysym::a, Some("a")), mods, false),
142
            Some(b"\x1ba".to_vec())
143
        );
144
    }
145
146
    #[test]
147
    fn arrows_respect_application_mode() {
148
        assert_eq!(
149
            encode(&key(Keysym::Up, None), NONE, false),
150
            Some(b"\x1b[A".to_vec())
151
        );
152
        assert_eq!(
153
            encode(&key(Keysym::Up, None), NONE, true),
154
            Some(b"\x1bOA".to_vec())
155
        );
156
    }
157
158
    #[test]
159
    fn modified_arrow_uses_csi_param() {
160
        let mods = Modifiers { ctrl: true, ..NONE };
161
        assert_eq!(
162
            encode(&key(Keysym::Right, None), mods, false),
163
            Some(b"\x1b[1;5C".to_vec())
164
        );
165
    }
166
167
    #[test]
168
    fn special_keys() {
169
        assert_eq!(
170
            encode(&key(Keysym::Return, None), NONE, false),
171
            Some(b"\r".to_vec())
172
        );
173
        assert_eq!(
174
            encode(&key(Keysym::BackSpace, None), NONE, false),
175
            Some(b"\x7f".to_vec())
176
        );
177
        assert_eq!(
178
            encode(&key(Keysym::Delete, None), NONE, false),
179
            Some(b"\x1b[3~".to_vec())
180
        );
181
        assert_eq!(
182
            encode(&key(Keysym::F5, None), NONE, false),
183
            Some(b"\x1b[15~".to_vec())
184
        );
185
    }
186
}