Skip to main content

denise_evdev/
keymap.rs

1//! evdev key codes to [`KeyCode`] positions.
2//!
3//! evdev codes name physical key *positions* using US-layout labels, which is
4//! exactly what [`KeyCode`] means, so this is a straight table with no layout
5//! interpretation. What the key produces is a separate question answered by
6//! [`denise::InputEvent::Text`]: `KeyCode::Semicolon` is where `ø` lives on a
7//! Norwegian keyboard, and mapping it to a character here would be wrong on every
8//! layout but one.
9
10use denise::KeyCode;
11
12/// Translates a raw evdev key code.
13///
14/// Unknown codes become [`KeyCode::Unidentified`] carrying the raw value, so a key
15/// this table does not name is still distinguishable and still reportable.
16pub fn key_code(code: u16) -> KeyCode {
17    use KeyCode as K;
18
19    match code {
20        // Letters, in evdev's keyboard-row order rather than alphabetical.
21        30 => K::A,
22        48 => K::B,
23        46 => K::C,
24        32 => K::D,
25        18 => K::E,
26        33 => K::F,
27        34 => K::G,
28        35 => K::H,
29        23 => K::I,
30        36 => K::J,
31        37 => K::K,
32        38 => K::L,
33        50 => K::M,
34        49 => K::N,
35        24 => K::O,
36        25 => K::P,
37        16 => K::Q,
38        19 => K::R,
39        31 => K::S,
40        20 => K::T,
41        22 => K::U,
42        47 => K::V,
43        17 => K::W,
44        45 => K::X,
45        21 => K::Y,
46        44 => K::Z,
47
48        // Digit row. Note evdev puts 0 at the end, after 9.
49        11 => K::Digit0,
50        2 => K::Digit1,
51        3 => K::Digit2,
52        4 => K::Digit3,
53        5 => K::Digit4,
54        6 => K::Digit5,
55        7 => K::Digit6,
56        8 => K::Digit7,
57        9 => K::Digit8,
58        10 => K::Digit9,
59
60        59 => K::F1,
61        60 => K::F2,
62        61 => K::F3,
63        62 => K::F4,
64        63 => K::F5,
65        64 => K::F6,
66        65 => K::F7,
67        66 => K::F8,
68        67 => K::F9,
69        68 => K::F10,
70        87 => K::F11,
71        88 => K::F12,
72
73        1 => K::Escape,
74        28 => K::Enter,
75        15 => K::Tab,
76        57 => K::Space,
77        14 => K::Backspace,
78        110 => K::Insert,
79        111 => K::Delete,
80        102 => K::Home,
81        107 => K::End,
82        104 => K::PageUp,
83        109 => K::PageDown,
84        103 => K::ArrowUp,
85        108 => K::ArrowDown,
86        105 => K::ArrowLeft,
87        106 => K::ArrowRight,
88
89        42 => K::ShiftLeft,
90        54 => K::ShiftRight,
91        29 => K::ControlLeft,
92        97 => K::ControlRight,
93        56 => K::AltLeft,
94        // AltGr. Reported as a distinct position, which is what makes the third
95        // level of a Norwegian layout reachable at all.
96        100 => K::AltRight,
97        125 => K::SuperLeft,
98        126 => K::SuperRight,
99        58 => K::CapsLock,
100        69 => K::NumLock,
101        70 => K::ScrollLock,
102
103        12 => K::Minus,
104        13 => K::Equal,
105        26 => K::BracketLeft,
106        27 => K::BracketRight,
107        43 => K::Backslash,
108        39 => K::Semicolon,
109        40 => K::Quote,
110        41 => K::Backquote,
111        // The 102nd key, present on ISO keyboards and absent on ANSI ones.
112        86 => K::IntlBackslash,
113        51 => K::Comma,
114        52 => K::Period,
115        53 => K::Slash,
116
117        96 => K::NumpadEnter,
118        78 => K::NumpadAdd,
119        74 => K::NumpadSubtract,
120        55 => K::NumpadMultiply,
121        98 => K::NumpadDivide,
122        83 => K::NumpadDecimal,
123        82 => K::Numpad0,
124        79 => K::Numpad1,
125        80 => K::Numpad2,
126        81 => K::Numpad3,
127        75 => K::Numpad4,
128        76 => K::Numpad5,
129        77 => K::Numpad6,
130        71 => K::Numpad7,
131        72 => K::Numpad8,
132        73 => K::Numpad9,
133
134        other => K::Unidentified(u32::from(other)),
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141
142    #[test]
143    fn home_row_maps_to_the_right_letters() {
144        assert_eq!(key_code(30), KeyCode::A);
145        assert_eq!(key_code(31), KeyCode::S);
146        assert_eq!(key_code(32), KeyCode::D);
147        assert_eq!(key_code(33), KeyCode::F);
148    }
149
150    #[test]
151    fn digit_zero_sits_after_nine_not_before_one() {
152        // The off-by-one that a loop over the digit row would introduce.
153        assert_eq!(key_code(2), KeyCode::Digit1);
154        assert_eq!(key_code(10), KeyCode::Digit9);
155        assert_eq!(key_code(11), KeyCode::Digit0);
156    }
157
158    #[test]
159    fn altgr_is_distinct_from_left_alt() {
160        // Collapsing these breaks the third level of a Norwegian layout, where
161        // AltGr is how you reach @, $ and the braces.
162        assert_eq!(key_code(56), KeyCode::AltLeft);
163        assert_eq!(key_code(100), KeyCode::AltRight);
164        assert_ne!(key_code(56), key_code(100));
165    }
166
167    #[test]
168    fn the_keys_norwegian_letters_live_on_are_positions_not_characters() {
169        // On a Norwegian layout these three positions carry ø, æ and å. The map
170        // must report the position; the character comes from text input.
171        assert_eq!(key_code(39), KeyCode::Semicolon);
172        assert_eq!(key_code(40), KeyCode::Quote);
173        assert_eq!(key_code(26), KeyCode::BracketLeft);
174    }
175
176    #[test]
177    fn unknown_codes_keep_their_raw_value() {
178        assert_eq!(key_code(0xFFF), KeyCode::Unidentified(0xFFF));
179    }
180
181    #[test]
182    fn no_two_named_codes_collide() {
183        // A duplicated arm would silently shadow a key, and the compiler only
184        // warns about unreachable patterns for literals it can prove overlap.
185        let mut seen = std::collections::HashMap::new();
186        for code in 0u16..=255 {
187            let mapped = key_code(code);
188            if matches!(mapped, KeyCode::Unidentified(_)) {
189                continue;
190            }
191            if let Some(previous) = seen.insert(mapped, code) {
192                panic!("codes {previous} and {code} both map to {mapped:?}");
193            }
194        }
195    }
196}