#![allow(
dead_code,
reason = "unused when the mock-loader feature selects the DLL backend instead"
)]
pub(crate) type KeyCode = u16;
pub(crate) const VK_COMMAND: KeyCode = 0x37;
pub(crate) const VK_SHIFT: KeyCode = 0x38;
pub(crate) const VK_OPTION: KeyCode = 0x3A;
pub(crate) const VK_CONTROL: KeyCode = 0x3B;
pub(crate) const VK_RIGHT_SHIFT: KeyCode = 0x3C;
pub(crate) const VK_RIGHT_OPTION: KeyCode = 0x3D;
pub(crate) const VK_RIGHT_CONTROL: KeyCode = 0x3E;
const TABLE: &[(&str, KeyCode)] = &[
("ENTER", 0x24),
("TAB", 0x30),
("SPACE", 0x31),
("BACKSPACE", 0x33),
("BS", 0x33),
("ESCAPE", 0x35),
("ESC", 0x35),
("DELETE", 0x75),
("DEL", 0x75),
("HOME", 0x73),
("END", 0x77),
("PGUP", 0x74),
("PGDN", 0x79),
("LEFT", 0x7B),
("RIGHT", 0x7C),
("DOWN", 0x7D),
("UP", 0x7E),
("INSERT", 0x72),
("INS", 0x72),
("F1", 0x7A),
("F2", 0x78),
("F3", 0x63),
("F4", 0x76),
("F5", 0x60),
("F6", 0x61),
("F7", 0x62),
("F8", 0x64),
("F9", 0x65),
("F10", 0x6D),
("F11", 0x67),
("F12", 0x6F),
("CAPSLOCK", 0x39),
("SHIFT", VK_SHIFT),
("LSHIFT", VK_SHIFT),
("RSHIFT", VK_RIGHT_SHIFT),
("CTRL", VK_CONTROL),
("LCTRL", VK_CONTROL),
("RCTRL", VK_RIGHT_CONTROL),
("ALT", VK_OPTION),
("LALT", VK_OPTION),
("RALT", VK_RIGHT_OPTION),
("LWIN", VK_COMMAND),
("RWIN", VK_COMMAND),
("NUMPAD0", 0x52),
("NUMPAD1", 0x53),
("NUMPAD2", 0x54),
("NUMPAD3", 0x55),
("NUMPAD4", 0x56),
("NUMPAD5", 0x57),
("NUMPAD6", 0x58),
("NUMPAD7", 0x59),
("NUMPAD8", 0x5B),
("NUMPAD9", 0x5C),
("NUMPADMULT", 0x43),
("NUMPADADD", 0x45),
("NUMPADSUB", 0x4E),
("NUMPADDIV", 0x4B),
("NUMPADDOT", 0x41),
("NUMPADENTER", 0x4C),
("VOLUME_UP", 0x48),
("VOLUME_DOWN", 0x49),
("VOLUME_MUTE", 0x4A),
];
#[must_use]
pub(crate) fn ansi(c: char) -> Option<KeyCode> {
Some(match c.to_ascii_lowercase() {
'a' => 0x00,
's' => 0x01,
'd' => 0x02,
'f' => 0x03,
'h' => 0x04,
'g' => 0x05,
'z' => 0x06,
'x' => 0x07,
'c' => 0x08,
'v' => 0x09,
'b' => 0x0B,
'q' => 0x0C,
'w' => 0x0D,
'e' => 0x0E,
'r' => 0x0F,
'y' => 0x10,
't' => 0x11,
'1' => 0x12,
'2' => 0x13,
'3' => 0x14,
'4' => 0x15,
'6' => 0x16,
'5' => 0x17,
'=' => 0x18,
'9' => 0x19,
'7' => 0x1A,
'-' => 0x1B,
'8' => 0x1C,
'0' => 0x1D,
']' => 0x1E,
'o' => 0x1F,
'u' => 0x20,
'[' => 0x21,
'i' => 0x22,
'p' => 0x23,
'l' => 0x25,
'j' => 0x26,
'\'' => 0x27,
'k' => 0x28,
';' => 0x29,
'\\' => 0x2A,
',' => 0x2B,
'/' => 0x2C,
'n' => 0x2D,
'm' => 0x2E,
'.' => 0x2F,
'`' => 0x32,
' ' => 0x31,
_ => return None,
})
}
#[must_use]
pub(crate) fn lookup(name: &str) -> Option<KeyCode> {
TABLE
.iter()
.find(|(n, _)| n.eq_ignore_ascii_case(name))
.map(|(_, code)| *code)
}
#[must_use]
pub(crate) fn is_modifier(code: KeyCode) -> bool {
matches!(
code,
VK_COMMAND
| VK_SHIFT
| VK_OPTION
| VK_CONTROL
| VK_RIGHT_SHIFT
| VK_RIGHT_OPTION
| VK_RIGHT_CONTROL
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_keys_production_automation_actually_sends_are_all_present() {
for name in [
"TAB",
"ENTER",
"ESC",
"SPACE",
"BACKSPACE",
"HOME",
"END",
"UP",
"DOWN",
"PGUP",
"F6",
"SHIFT",
"CTRL",
"ALT",
"LWIN",
] {
assert!(lookup(name).is_some(), "{name} has no macOS key code");
}
}
#[test]
fn lookup_is_case_insensitive_like_autoit() {
assert_eq!(lookup("tab"), lookup("TAB"));
assert_eq!(lookup("Enter"), lookup("ENTER"));
}
#[test]
fn function_keys_are_not_sequential() {
assert_eq!(lookup("F1"), Some(0x7A));
assert_eq!(lookup("F2"), Some(0x78));
assert_eq!(lookup("F5"), Some(0x60));
assert_ne!(lookup("F2"), lookup("F1").map(|c| c + 1));
}
#[test]
fn aliases_agree_with_their_canonical_names() {
assert_eq!(lookup("ESC"), lookup("ESCAPE"));
assert_eq!(lookup("BS"), lookup("BACKSPACE"));
assert_eq!(lookup("DEL"), lookup("DELETE"));
assert_eq!(lookup("INS"), lookup("INSERT"));
}
#[test]
fn keys_macos_does_not_have_report_absence_rather_than_a_wrong_code() {
for name in ["PRINTSCREEN", "BROWSER_BACK", "SCROLLLOCK", "APPSKEY"] {
assert!(lookup(name).is_none(), "{name} should not resolve");
}
}
#[test]
fn every_modifier_name_maps_to_a_code_recognised_as_a_modifier() {
for name in [
"SHIFT", "LSHIFT", "RSHIFT", "CTRL", "LCTRL", "RCTRL", "ALT", "LALT", "RALT", "LWIN",
"RWIN",
] {
let code = lookup(name).unwrap_or_else(|| panic!("{name} missing"));
assert!(
is_modifier(code),
"{name} -> {code:#x} not seen as modifier"
);
}
assert!(!is_modifier(lookup("TAB").unwrap()));
}
#[test]
fn the_shortcut_letters_automation_uses_have_ansi_codes() {
for c in "acvxszfyjnptw0123456789".chars() {
assert!(ansi(c).is_some(), "{c} has no ANSI key code");
}
assert_eq!(ansi('A'), ansi('a'));
assert_eq!(ansi('A'), Some(0x00));
assert_eq!(ansi('c'), Some(0x08));
assert_eq!(ansi('v'), Some(0x09));
}
#[test]
fn a_character_with_no_ansi_position_reports_absence() {
for c in ['ç', 'ã', '€', '→'] {
assert!(ansi(c).is_none(), "{c} should have no ANSI code");
}
}
#[test]
fn no_duplicate_names() {
let mut seen = std::collections::BTreeSet::new();
for (name, _) in TABLE {
assert!(
seen.insert(name.to_ascii_uppercase()),
"{name} appears twice"
);
}
}
}