1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub struct Stroke {
13 pub scancode: u8,
14 pub extended: bool,
15 pub released: bool,
16}
17
18#[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 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
66pub 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
76const 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
130const 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#[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
209pub 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
229pub 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
237pub 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
250pub 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}