1use std::borrow::Cow;
19use std::fmt;
20
21#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
27pub struct Cid(u16);
28
29impl Cid {
30 #[must_use]
32 pub const fn new(cid: u16) -> Self {
33 Self(cid)
34 }
35
36 #[must_use]
38 pub const fn raw(self) -> u16 {
39 self.0
40 }
41
42 #[must_use]
44 pub fn known(self) -> Option<&'static KnownControl> {
45 KNOWN_CONTROLS.iter().find(|control| control.id == self)
46 }
47
48 #[must_use]
51 pub fn from_config_name(name: &str) -> Option<Self> {
52 if let Some(hex) = name.strip_prefix(CONFIG_PREFIX) {
53 let digits = hex.strip_prefix("0x")?;
54 if digits.is_empty() || !digits.bytes().all(|b| b.is_ascii_hexdigit()) {
57 return None;
58 }
59 return u16::from_str_radix(digits, 16).ok().map(Self);
60 }
61 KNOWN_CONTROLS
62 .iter()
63 .find(|control| control.name == name)
64 .map(|control| control.id)
65 }
66
67 #[must_use]
71 pub fn config_name(self) -> Cow<'static, str> {
72 match self.known() {
73 Some(control) => Cow::Borrowed(control.name),
74 None => Cow::Owned(format!("{CONFIG_PREFIX}{:#06x}", self.0)),
75 }
76 }
77
78 #[must_use]
83 pub fn label(self) -> Cow<'static, str> {
84 match self.known() {
85 Some(control) => Cow::Borrowed(control.label),
86 None => Cow::Owned(format!("Control {}", self.hex())),
87 }
88 }
89
90 #[must_use]
94 pub fn translation_key(self) -> &'static str {
95 self.known()
96 .map_or(UNKNOWN_CONTROL_TRANSLATION_KEY, |control| {
97 control.translation_key
98 })
99 }
100
101 #[must_use]
103 pub fn hex(self) -> String {
104 format!("{:#06x}", self.0)
105 }
106}
107
108impl fmt::Display for Cid {
109 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110 f.write_str(&self.label())
111 }
112}
113
114const CONFIG_PREFIX: &str = "control:";
116
117pub const UNKNOWN_CONTROL_TRANSLATION_KEY: &str = "keyboard.control_key";
121
122#[derive(Debug, PartialEq, Eq)]
124pub struct KnownControl {
125 pub id: Cid,
127 pub name: &'static str,
131 pub label: &'static str,
133 pub translation_key: &'static str,
135}
136
137const fn known(
138 cid: u16,
139 name: &'static str,
140 label: &'static str,
141 translation_key: &'static str,
142) -> KnownControl {
143 KnownControl {
144 id: Cid(cid),
145 name,
146 label,
147 translation_key,
148 }
149}
150
151pub const KNOWN_CONTROLS: &[KnownControl] = &[
157 known(
158 0x00c7,
159 "KeyBrightnessDown",
160 "Brightness Down Key",
161 "keyboard.brightness_down_key",
162 ),
163 known(
164 0x00c8,
165 "KeyBrightnessUp",
166 "Brightness Up Key",
167 "keyboard.brightness_up_key",
168 ),
169 known(
170 0x00e2,
171 "KeyBacklightDown",
172 "Backlight Down Key",
173 "keyboard.backlight_down_key",
174 ),
175 known(
176 0x00e3,
177 "KeyBacklightUp",
178 "Backlight Up Key",
179 "keyboard.backlight_up_key",
180 ),
181 known(
182 0x00e0,
183 "KeyMissionControl",
184 "Mission Control Key",
185 "keyboard.mission_control_key",
186 ),
187 known(
188 0x00e1,
189 "KeyLaunchpad",
190 "Launchpad Key",
191 "keyboard.launchpad_key",
192 ),
193 known(
194 0x006e,
195 "KeyShowDesktop",
196 "Show Desktop Key",
197 "keyboard.show_desktop_key",
198 ),
199 known(0x00d4, "KeySearch", "Search Key", "keyboard.search_key"),
200 known(
201 0x0103,
202 "KeyDictation",
203 "Dictation Key",
204 "keyboard.dictation_key",
205 ),
206 known(0x0108, "KeyEmoji", "Emoji Key", "keyboard.emoji_key"),
207 known(
208 0x010a,
209 "KeyScreenCapture",
210 "Screen Capture Key",
211 "keyboard.screen_capture_key",
212 ),
213 known(
214 0x00bf,
215 "KeyPrintScreen",
216 "Print Screen Key",
217 "keyboard.print_screen_key",
218 ),
219 known(
220 0x011c,
221 "KeyMicMute",
222 "Mic Mute Key",
223 "keyboard.mic_mute_key",
224 ),
225 known(
226 0x00e4,
227 "KeyPreviousTrack",
228 "Previous Track Key",
229 "keyboard.previous_track_key",
230 ),
231 known(
232 0x00e5,
233 "KeyPlayPause",
234 "Play/Pause Key",
235 "keyboard.play_pause_key",
236 ),
237 known(
238 0x0141,
239 "KeyPlayPauseTrack",
240 "Play/Pause Key (track)",
241 "keyboard.play_pause_track_key",
242 ),
243 known(
244 0x00e6,
245 "KeyNextTrack",
246 "Next Track Key",
247 "keyboard.next_track_key",
248 ),
249 known(0x00e7, "KeyMute", "Mute Key", "keyboard.mute_key"),
250 known(
251 0x00e8,
252 "KeyVolumeDown",
253 "Volume Down Key",
254 "keyboard.volume_down_key",
255 ),
256 known(
257 0x00e9,
258 "KeyVolumeUp",
259 "Volume Up Key",
260 "keyboard.volume_up_key",
261 ),
262 known(
263 0x000a,
264 "KeyCalculator",
265 "Calculator Key",
266 "keyboard.calculator_key",
267 ),
268 known(0x006f, "KeyLock", "Lock Key", "keyboard.lock_key"),
269 known(
270 0x011d,
271 "KeyDoNotDisturb",
272 "Do Not Disturb Key",
273 "keyboard.do_not_disturb_key",
274 ),
275 known(
276 0x0104,
277 "KeyEmojiHeartEyes",
278 "Heart-Eyes Emoji Key",
279 "keyboard.emoji_heart_eyes_key",
280 ),
281 known(
282 0x0105,
283 "KeyEmojiCrying",
284 "Crying Emoji Key",
285 "keyboard.emoji_crying_key",
286 ),
287 known(
288 0x0106,
289 "KeyEmojiSmiley",
290 "Smiley Emoji Key",
291 "keyboard.emoji_smiley_key",
292 ),
293 known(
294 0x0107,
295 "KeyEmojiTears",
296 "Tears-of-Joy Emoji Key",
297 "keyboard.emoji_tears_key",
298 ),
299 known(0x00ea, "KeyLookUp", "Look Up Key", "keyboard.look_up_key"),
300 known(
301 0x0100,
302 "KeyAppSwitch",
303 "App Switch Key",
304 "keyboard.app_switch_key",
305 ),
306 known(
307 0x0109,
308 "KeyAppSwitchLaunchpad",
309 "App Switch / Launchpad Key",
310 "keyboard.app_switch_launchpad_key",
311 ),
312 known(0x0118, "KeyHome", "Home Key", "keyboard.home_key"),
313 known(0x0119, "KeyEnd", "End Key", "keyboard.end_key"),
314 known(0x011a, "KeyPageUp", "Page Up Key", "keyboard.page_up_key"),
315 known(
316 0x011b,
317 "KeyPageDown",
318 "Page Down Key",
319 "keyboard.page_down_key",
320 ),
321 known(
327 0x00ba,
328 "KeyMultiPlatformAppSwitch",
329 "App Switch Key (multiplatform)",
330 "keyboard.multiplatform_app_switch_key",
331 ),
332 known(
333 0x00bb,
334 "KeyMultiPlatformHome",
335 "Home Key (multiplatform)",
336 "keyboard.multiplatform_home_key",
337 ),
338 known(
339 0x00bc,
340 "KeyMultiPlatformMenu",
341 "Menu Key (multiplatform)",
342 "keyboard.multiplatform_menu_key",
343 ),
344 known(
345 0x00be,
346 "KeyMultiPlatformInsert",
347 "Insert Key (multiplatform)",
348 "keyboard.multiplatform_insert_key",
349 ),
350 known(
351 0x00dd,
352 "KeyLanguageSwitch",
353 "Language Switch Key",
354 "keyboard.language_switch_key",
355 ),
356 known(0x01a3, "KeyCircle", "Circle Key", "keyboard.circle_key"),
358 known(
359 0x01a4,
360 "KeyTriangle",
361 "Triangle Key",
362 "keyboard.triangle_key",
363 ),
364 known(0x01a5, "KeyDiamond", "Diamond Key", "keyboard.diamond_key"),
365 known(0x01a6, "KeyStar", "Star Key", "keyboard.star_key"),
366];
367
368#[cfg(test)]
369mod tests {
370 use std::collections::BTreeSet;
371
372 use super::*;
373
374 #[test]
375 fn catalog_names_and_ids_are_unique() {
376 let ids: BTreeSet<_> = KNOWN_CONTROLS.iter().map(|c| c.id).collect();
377 let names: BTreeSet<_> = KNOWN_CONTROLS.iter().map(|c| c.name).collect();
378 let keys: BTreeSet<_> = KNOWN_CONTROLS.iter().map(|c| c.translation_key).collect();
379 assert_eq!(ids.len(), KNOWN_CONTROLS.len());
380 assert_eq!(names.len(), KNOWN_CONTROLS.len());
381 assert_eq!(keys.len(), KNOWN_CONTROLS.len());
382 for control in KNOWN_CONTROLS {
383 assert!(
384 Cid::from_config_name(control.name).is_some(),
385 "{} must parse back as a config name",
386 control.name
387 );
388 }
389 }
390
391 #[test]
392 fn config_names_round_trip_for_catalogued_and_raw_controls() {
393 for control in KNOWN_CONTROLS {
394 assert_eq!(
395 Cid::from_config_name(&control.id.config_name()),
396 Some(control.id)
397 );
398 }
399 let raw = Cid::new(0x01f3);
400 assert_eq!(raw.config_name(), "control:0x01f3");
401 assert_eq!(Cid::from_config_name("control:0x01f3"), Some(raw));
402 assert_eq!(Cid::from_config_name("control:0x1F3"), Some(raw));
403 assert_eq!(
406 Cid::from_config_name("control:0x010a"),
407 Some(Cid::new(0x010a))
408 );
409 }
410
411 #[test]
412 fn config_name_parser_rejects_everything_else() {
413 for bogus in [
414 "",
415 "Key",
416 "KeyBogus",
417 "control:",
418 "control:0x",
419 "control:010a",
420 "control:0xZZ",
421 "control:0x+1a3",
422 "control:0x-1",
423 "control:0x1ffff",
424 "0x010a",
425 ] {
426 assert_eq!(Cid::from_config_name(bogus), None, "{bogus:?}");
427 }
428 }
429
430 #[test]
431 fn the_nine_original_key_names_are_preserved() {
432 for (cid, name) in [
435 (0x00d4, "KeySearch"),
436 (0x0103, "KeyDictation"),
437 (0x0108, "KeyEmoji"),
438 (0x010a, "KeyScreenCapture"),
439 (0x011c, "KeyMicMute"),
440 (0x00e5, "KeyPlayPause"),
441 (0x00e7, "KeyMute"),
442 (0x00e8, "KeyVolumeDown"),
443 (0x00e9, "KeyVolumeUp"),
444 ] {
445 assert_eq!(Cid::new(cid).config_name(), name);
446 }
447 }
448
449 #[test]
450 fn unknown_controls_label_and_localize_by_number() {
451 let raw = Cid::new(0x01f3);
452 assert_eq!(raw.label(), "Control 0x01f3");
453 assert_eq!(raw.to_string(), "Control 0x01f3");
454 assert_eq!(raw.hex(), "0x01f3");
455 assert_ne!(
456 raw.label(),
457 raw.config_name(),
458 "a label must not be a valid config name"
459 );
460 assert_eq!(raw.translation_key(), UNKNOWN_CONTROL_TRANSLATION_KEY);
461 assert_eq!(
462 Cid::new(0x010a).translation_key(),
463 "keyboard.screen_capture_key"
464 );
465 }
466}