Skip to main content

netget/server/spice/
keys.rs

1//! PC/AT scancode set 1, as SPICE's inputs channel carries keys, in both directions: the
2//! server decodes what a client pressed into a key name and the character it types (US
3//! layout), and the client turns the model's text and key names back into scancodes.
4//!
5//! On the wire a key is a sequence of scancode bytes: `0xE0` prefixes an extended key, and bit
6//! 7 marks a release. `SPICE_MSGC_INPUTS_KEY_DOWN`/`KEY_UP` pack the sequence into a `u32`,
7//! first byte lowest (spice-server pushes `code & 0xff` and shifts until nothing is left);
8//! `SPICE_MSGC_INPUTS_KEY_SCANCODE` carries the bytes as they are.
9
10/// One decoded key transition.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub struct Stroke {
13    pub scancode: u8,
14    pub extended: bool,
15    pub released: bool,
16}
17
18/// Turns scancode bytes into [`Stroke`]s. `0xE1` (Pause's prefix) and the bytes of its
19/// sequence are dropped: Pause is the one key with no release and an eight-byte make code.
20#[derive(Debug, Default)]
21pub struct Decoder {
22    extended: bool,
23    pause_bytes: u8,
24}
25
26impl Decoder {
27    pub fn feed(&mut self, byte: u8) -> Option<Stroke> {
28        if self.pause_bytes > 0 {
29            self.pause_bytes -= 1;
30            return None;
31        }
32        match byte {
33            0xE0 => {
34                self.extended = true;
35                None
36            }
37            0xE1 => {
38                self.pause_bytes = 5;
39                None
40            }
41            0 => None,
42            b => {
43                let stroke = Stroke {
44                    scancode: b & 0x7F,
45                    extended: std::mem::take(&mut self.extended),
46                    released: b & 0x80 != 0,
47                };
48                Some(stroke)
49            }
50        }
51    }
52
53    /// The bytes of a `KEY_DOWN`/`KEY_UP` code, lowest first, until a zero byte.
54    pub fn feed_code(&mut self, code: u32) -> Vec<Stroke> {
55        let mut out = Vec::new();
56        for b in code.to_le_bytes() {
57            if b == 0 {
58                break;
59            }
60            out.extend(self.feed(b));
61        }
62        out
63    }
64}
65
66/// The `KEY_DOWN` / `KEY_UP` code for a key: `0xE0` first for an extended key.
67pub fn key_code(scancode: u8, extended: bool, release: bool) -> u32 {
68    let sc = u32::from(scancode & 0x7F) | if release { 0x80 } else { 0 };
69    if extended {
70        0xE0 | (sc << 8)
71    } else {
72        sc
73    }
74}
75
76/// (scancode, unshifted, shifted) for every key that types a character on a US keyboard.
77const CHARS: &[(u8, char, char)] = &[
78    (0x02, '1', '!'),
79    (0x03, '2', '@'),
80    (0x04, '3', '#'),
81    (0x05, '4', '$'),
82    (0x06, '5', '%'),
83    (0x07, '6', '^'),
84    (0x08, '7', '&'),
85    (0x09, '8', '*'),
86    (0x0A, '9', '('),
87    (0x0B, '0', ')'),
88    (0x0C, '-', '_'),
89    (0x0D, '=', '+'),
90    (0x0F, '\t', '\t'),
91    (0x10, 'q', 'Q'),
92    (0x11, 'w', 'W'),
93    (0x12, 'e', 'E'),
94    (0x13, 'r', 'R'),
95    (0x14, 't', 'T'),
96    (0x15, 'y', 'Y'),
97    (0x16, 'u', 'U'),
98    (0x17, 'i', 'I'),
99    (0x18, 'o', 'O'),
100    (0x19, 'p', 'P'),
101    (0x1A, '[', '{'),
102    (0x1B, ']', '}'),
103    (0x1C, '\n', '\n'),
104    (0x1E, 'a', 'A'),
105    (0x1F, 's', 'S'),
106    (0x20, 'd', 'D'),
107    (0x21, 'f', 'F'),
108    (0x22, 'g', 'G'),
109    (0x23, 'h', 'H'),
110    (0x24, 'j', 'J'),
111    (0x25, 'k', 'K'),
112    (0x26, 'l', 'L'),
113    (0x27, ';', ':'),
114    (0x28, '\'', '"'),
115    (0x29, '`', '~'),
116    (0x2B, '\\', '|'),
117    (0x2C, 'z', 'Z'),
118    (0x2D, 'x', 'X'),
119    (0x2E, 'c', 'C'),
120    (0x2F, 'v', 'V'),
121    (0x30, 'b', 'B'),
122    (0x31, 'n', 'N'),
123    (0x32, 'm', 'M'),
124    (0x33, ',', '<'),
125    (0x34, '.', '>'),
126    (0x35, '/', '?'),
127    (0x39, ' ', ' '),
128];
129
130/// (scancode, extended, name) for keys with a name rather than a character. The names are the
131/// X11 keysym names a VNC event uses, so a model sees one vocabulary across both servers.
132const NAMED: &[(u8, bool, &str)] = &[
133    (0x01, false, "Escape"),
134    (0x0E, false, "BackSpace"),
135    (0x0F, false, "Tab"),
136    (0x1C, false, "Return"),
137    (0x1D, false, "Control_L"),
138    (0x2A, false, "Shift_L"),
139    (0x36, false, "Shift_R"),
140    (0x37, false, "KP_Multiply"),
141    (0x38, false, "Alt_L"),
142    (0x39, false, "space"),
143    (0x3A, false, "Caps_Lock"),
144    (0x3B, false, "F1"),
145    (0x3C, false, "F2"),
146    (0x3D, false, "F3"),
147    (0x3E, false, "F4"),
148    (0x3F, false, "F5"),
149    (0x40, false, "F6"),
150    (0x41, false, "F7"),
151    (0x42, false, "F8"),
152    (0x43, false, "F9"),
153    (0x44, false, "F10"),
154    (0x45, false, "Num_Lock"),
155    (0x46, false, "Scroll_Lock"),
156    (0x47, false, "KP_7"),
157    (0x48, false, "KP_8"),
158    (0x49, false, "KP_9"),
159    (0x4A, false, "KP_Subtract"),
160    (0x4B, false, "KP_4"),
161    (0x4C, false, "KP_5"),
162    (0x4D, false, "KP_6"),
163    (0x4E, false, "KP_Add"),
164    (0x4F, false, "KP_1"),
165    (0x50, false, "KP_2"),
166    (0x51, false, "KP_3"),
167    (0x52, false, "KP_0"),
168    (0x53, false, "KP_Decimal"),
169    (0x57, false, "F11"),
170    (0x58, false, "F12"),
171    (0x1C, true, "KP_Enter"),
172    (0x1D, true, "Control_R"),
173    (0x35, true, "KP_Divide"),
174    (0x38, true, "Alt_R"),
175    (0x47, true, "Home"),
176    (0x48, true, "Up"),
177    (0x49, true, "Page_Up"),
178    (0x4B, true, "Left"),
179    (0x4D, true, "Right"),
180    (0x4F, true, "End"),
181    (0x50, true, "Down"),
182    (0x51, true, "Page_Down"),
183    (0x52, true, "Insert"),
184    (0x53, true, "Delete"),
185    (0x5B, true, "Super_L"),
186    (0x5C, true, "Super_R"),
187    (0x5D, true, "Menu"),
188];
189
190/// Modifier keys, which change the state rather than type anything.
191#[derive(Debug, Clone, Copy, PartialEq, Eq)]
192pub enum Modifier {
193    Shift,
194    Ctrl,
195    Alt,
196    Super,
197}
198
199pub fn modifier(scancode: u8, extended: bool) -> Option<Modifier> {
200    match (scancode, extended) {
201        (0x2A, false) | (0x36, false) => Some(Modifier::Shift),
202        (0x1D, _) => Some(Modifier::Ctrl),
203        (0x38, _) => Some(Modifier::Alt),
204        (0x5B, true) | (0x5C, true) => Some(Modifier::Super),
205        _ => None,
206    }
207}
208
209/// The key's name: its character for a printable key (shifted when `shift`), otherwise its
210/// keysym name, otherwise `scancode_0x..`.
211pub fn key_name(scancode: u8, extended: bool, shift: bool) -> String {
212    if let Some((_, _, name)) = NAMED
213        .iter()
214        .find(|(s, e, _)| *s == scancode && *e == extended)
215    {
216        return (*name).to_string();
217    }
218    if !extended {
219        if let Some(c) = char_for(scancode, shift) {
220            return c.to_string();
221        }
222    }
223    format!(
224        "scancode_{}{scancode:#04x}",
225        if extended { "e0_" } else { "" }
226    )
227}
228
229/// The character a non-extended key types on a US keyboard.
230pub fn char_for(scancode: u8, shift: bool) -> Option<char> {
231    CHARS
232        .iter()
233        .find(|(s, _, _)| *s == scancode)
234        .map(|(_, plain, shifted)| if shift { *shifted } else { *plain })
235}
236
237/// The key and shift state that type `c` on a US keyboard.
238pub fn scancode_for_char(c: char) -> Option<(u8, bool)> {
239    CHARS.iter().find_map(|(s, plain, shifted)| {
240        if *plain == c {
241            Some((*s, false))
242        } else if *shifted == c {
243            Some((*s, true))
244        } else {
245            None
246        }
247    })
248}
249
250/// A key named by the model: `enter`, `esc`, `ctrl`, `f5`, `page_down`, `a`, `Return`, ….
251/// Returns (scancode, extended).
252pub fn named_key(name: &str) -> Option<(u8, bool)> {
253    let lower = name.trim().to_ascii_lowercase();
254    let alias = match lower.as_str() {
255        "enter" | "return" => Some((0x1C, false)),
256        "esc" | "escape" => Some((0x01, false)),
257        "tab" => Some((0x0F, false)),
258        "backspace" => Some((0x0E, false)),
259        "space" => Some((0x39, false)),
260        "ctrl" | "control" | "control_l" => Some((0x1D, false)),
261        "shift" | "shift_l" => Some((0x2A, false)),
262        "alt" | "alt_l" => Some((0x38, false)),
263        "win" | "super" | "meta" | "super_l" => Some((0x5B, true)),
264        "delete" | "del" => Some((0x53, true)),
265        "insert" | "ins" => Some((0x52, true)),
266        "home" => Some((0x47, true)),
267        "end" => Some((0x4F, true)),
268        "pageup" | "page_up" | "pgup" => Some((0x49, true)),
269        "pagedown" | "page_down" | "pgdn" => Some((0x51, true)),
270        "up" => Some((0x48, true)),
271        "down" => Some((0x50, true)),
272        "left" => Some((0x4B, true)),
273        "right" => Some((0x4D, true)),
274        "capslock" | "caps_lock" => Some((0x3A, false)),
275        "menu" => Some((0x5D, true)),
276        _ => None,
277    };
278    if alias.is_some() {
279        return alias;
280    }
281    if let Some(n) = lower.strip_prefix('f').and_then(|n| n.parse::<u8>().ok()) {
282        return match n {
283            1..=10 => Some((0x3A + n, false)),
284            11 => Some((0x57, false)),
285            12 => Some((0x58, false)),
286            _ => None,
287        };
288    }
289    if let Some((s, e, _)) = NAMED
290        .iter()
291        .find(|(_, _, n)| n.eq_ignore_ascii_case(name.trim()))
292    {
293        return Some((*s, *e));
294    }
295    let mut chars = name.chars();
296    match (chars.next(), chars.next()) {
297        (Some(c), None) => scancode_for_char(c.to_ascii_lowercase()).map(|(s, _)| (s, false)),
298        _ => None,
299    }
300}