Skip to main content

denise_macos/
keymap.rs

1//! `NSEvent` virtual key codes to [`KeyCode`].
2//!
3//! These are the ANSI virtual key codes from `<Carbon/HIToolbox/Events.h>`, which
4//! are *positions* on a US keyboard and have been since 1984 — which is exactly
5//! what [`KeyCode`] means, so this is a rename rather than a translation.
6//!
7//! The characters are a separate matter. AppKit hands those over as
8//! `[NSEvent characters]`, already through the user's layout and already through
9//! any dead key or IME, and they arrive as [`InputEvent::Text`](denise::InputEvent).
10//! A build that mapped `kVK_ANSI_Semicolon` to `;` would be unable to type `ø`.
11
12use denise::KeyCode;
13
14/// The key position for an `NSEvent` `keyCode`, or `Unidentified` carrying the
15/// raw code.
16///
17/// Nothing here can fail: an unknown code is still a key the user pressed, and a
18/// host that can distinguish two of them by their raw value is better off than
19/// one that received nothing.
20pub fn key_code(virtual_key: u16) -> KeyCode {
21    match virtual_key {
22        0x00 => KeyCode::A,
23        0x0B => KeyCode::B,
24        0x08 => KeyCode::C,
25        0x02 => KeyCode::D,
26        0x0E => KeyCode::E,
27        0x03 => KeyCode::F,
28        0x05 => KeyCode::G,
29        0x04 => KeyCode::H,
30        0x22 => KeyCode::I,
31        0x26 => KeyCode::J,
32        0x28 => KeyCode::K,
33        0x25 => KeyCode::L,
34        0x2E => KeyCode::M,
35        0x2D => KeyCode::N,
36        0x1F => KeyCode::O,
37        0x23 => KeyCode::P,
38        0x0C => KeyCode::Q,
39        0x0F => KeyCode::R,
40        0x01 => KeyCode::S,
41        0x11 => KeyCode::T,
42        0x20 => KeyCode::U,
43        0x09 => KeyCode::V,
44        0x0D => KeyCode::W,
45        0x07 => KeyCode::X,
46        0x10 => KeyCode::Y,
47        0x06 => KeyCode::Z,
48
49        0x1D => KeyCode::Digit0,
50        0x12 => KeyCode::Digit1,
51        0x13 => KeyCode::Digit2,
52        0x14 => KeyCode::Digit3,
53        0x15 => KeyCode::Digit4,
54        0x17 => KeyCode::Digit5,
55        0x16 => KeyCode::Digit6,
56        0x1A => KeyCode::Digit7,
57        0x1C => KeyCode::Digit8,
58        0x19 => KeyCode::Digit9,
59
60        0x1B => KeyCode::Minus,
61        0x18 => KeyCode::Equal,
62        0x21 => KeyCode::BracketLeft,
63        0x1E => KeyCode::BracketRight,
64        0x2A => KeyCode::Backslash,
65        0x29 => KeyCode::Semicolon,
66        0x27 => KeyCode::Quote,
67        0x32 => KeyCode::Backquote,
68        0x2B => KeyCode::Comma,
69        0x2F => KeyCode::Period,
70        0x2C => KeyCode::Slash,
71        // `kVK_ISO_Section`: the extra key an ISO keyboard has and an ANSI one
72        // does not. It carries `<`, `>` and `\` on a Norwegian layout, so a build
73        // that never names it cannot type a backslash.
74        0x0A => KeyCode::IntlBackslash,
75
76        0x31 => KeyCode::Space,
77        0x24 => KeyCode::Enter,
78        0x30 => KeyCode::Tab,
79        0x33 => KeyCode::Backspace,
80        0x35 => KeyCode::Escape,
81        0x75 => KeyCode::Delete,
82        0x72 => KeyCode::Insert,
83        0x73 => KeyCode::Home,
84        0x77 => KeyCode::End,
85        0x74 => KeyCode::PageUp,
86        0x79 => KeyCode::PageDown,
87        0x7E => KeyCode::ArrowUp,
88        0x7D => KeyCode::ArrowDown,
89        0x7B => KeyCode::ArrowLeft,
90        0x7C => KeyCode::ArrowRight,
91
92        0x38 => KeyCode::ShiftLeft,
93        0x3C => KeyCode::ShiftRight,
94        0x3B => KeyCode::ControlLeft,
95        0x3E => KeyCode::ControlRight,
96        0x3A => KeyCode::AltLeft,
97        0x3D => KeyCode::AltRight,
98        0x37 => KeyCode::SuperLeft,
99        0x36 => KeyCode::SuperRight,
100        0x39 => KeyCode::CapsLock,
101        0x47 => KeyCode::NumLock,
102
103        0x7A => KeyCode::F1,
104        0x78 => KeyCode::F2,
105        0x63 => KeyCode::F3,
106        0x76 => KeyCode::F4,
107        0x60 => KeyCode::F5,
108        0x61 => KeyCode::F6,
109        0x62 => KeyCode::F7,
110        0x64 => KeyCode::F8,
111        0x65 => KeyCode::F9,
112        0x6D => KeyCode::F10,
113        0x67 => KeyCode::F11,
114        0x6F => KeyCode::F12,
115
116        0x52 => KeyCode::Numpad0,
117        0x53 => KeyCode::Numpad1,
118        0x54 => KeyCode::Numpad2,
119        0x55 => KeyCode::Numpad3,
120        0x56 => KeyCode::Numpad4,
121        0x57 => KeyCode::Numpad5,
122        0x58 => KeyCode::Numpad6,
123        0x59 => KeyCode::Numpad7,
124        0x5B => KeyCode::Numpad8,
125        0x5C => KeyCode::Numpad9,
126        0x4C => KeyCode::NumpadEnter,
127        0x45 => KeyCode::NumpadAdd,
128        0x4E => KeyCode::NumpadSubtract,
129        0x43 => KeyCode::NumpadMultiply,
130        0x4B => KeyCode::NumpadDivide,
131        0x41 => KeyCode::NumpadDecimal,
132
133        other => KeyCode::Unidentified(other as u32),
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140
141    /// Two positions mapping to the same key would make one of them untypeable
142    /// and the other ambiguous, and neither shows up as an error anywhere.
143    #[test]
144    fn no_two_virtual_keys_name_the_same_position() {
145        let mut seen: Vec<(u16, KeyCode)> = Vec::new();
146        for virtual_key in 0u16..=0x7F {
147            let code = key_code(virtual_key);
148            if matches!(code, KeyCode::Unidentified(_)) {
149                continue;
150            }
151            if let Some((other, _)) = seen.iter().find(|(_, c)| *c == code) {
152                panic!("{virtual_key:#04X} and {other:#04X} both name {code:?}");
153            }
154            seen.push((virtual_key, code));
155        }
156        assert_eq!(seen.len(), 101, "the table changed size");
157    }
158
159    /// Spot checks against `<Carbon/HIToolbox/Events.h>`, chosen for the ones that
160    /// are surprising: the letters are not alphabetical, `Z` is not near `Y`, and
161    /// the ISO key is a position an ANSI keyboard does not have at all.
162    #[test]
163    fn the_awkward_codes_are_the_documented_ones() {
164        assert_eq!(key_code(0x00), KeyCode::A); // kVK_ANSI_A
165        assert_eq!(key_code(0x06), KeyCode::Z); // kVK_ANSI_Z, not near Y
166        assert_eq!(key_code(0x10), KeyCode::Y); // kVK_ANSI_Y
167        assert_eq!(key_code(0x0A), KeyCode::IntlBackslash); // kVK_ISO_Section
168        assert_eq!(key_code(0x1D), KeyCode::Digit0); // 0 comes after 9
169        assert_eq!(key_code(0x24), KeyCode::Enter); // kVK_Return
170        assert_eq!(key_code(0x33), KeyCode::Backspace); // kVK_Delete, confusingly
171        assert_eq!(key_code(0x75), KeyCode::Delete); // kVK_ForwardDelete
172    }
173
174    #[test]
175    fn an_unknown_code_keeps_its_number() {
176        assert_eq!(key_code(0xFF), KeyCode::Unidentified(0xFF));
177    }
178}