use crate::types::{Key, Modifiers};
#[allow(dead_code)]
mod vk {
pub const BACK: u16 = 0x08;
pub const TAB: u16 = 0x09;
pub const RETURN: u16 = 0x0D;
pub const SHIFT: u16 = 0x10;
pub const CONTROL: u16 = 0x11;
pub const MENU: u16 = 0x12; pub const CAPITAL: u16 = 0x14; pub const ESCAPE: u16 = 0x1B;
pub const SPACE: u16 = 0x20;
pub const NUMLOCK: u16 = 0x90;
pub const SCROLL: u16 = 0x91;
pub const PRIOR: u16 = 0x21; pub const NEXT: u16 = 0x22; pub const END: u16 = 0x23;
pub const HOME: u16 = 0x24;
pub const LEFT: u16 = 0x25;
pub const UP: u16 = 0x26;
pub const RIGHT: u16 = 0x27;
pub const DOWN: u16 = 0x28;
pub const INSERT: u16 = 0x2D;
pub const DELETE: u16 = 0x2E;
pub const LWIN: u16 = 0x5B;
pub const RWIN: u16 = 0x5C;
pub const NUMPAD0: u16 = 0x60;
pub const NUMPAD1: u16 = 0x61;
pub const NUMPAD2: u16 = 0x62;
pub const NUMPAD3: u16 = 0x63;
pub const NUMPAD4: u16 = 0x64;
pub const NUMPAD5: u16 = 0x65;
pub const NUMPAD6: u16 = 0x66;
pub const NUMPAD7: u16 = 0x67;
pub const NUMPAD8: u16 = 0x68;
pub const NUMPAD9: u16 = 0x69;
pub const MULTIPLY: u16 = 0x6A;
pub const ADD: u16 = 0x6B;
pub const SUBTRACT: u16 = 0x6D;
pub const DECIMAL: u16 = 0x6E;
pub const DIVIDE: u16 = 0x6F;
pub const F1: u16 = 0x70;
pub const F2: u16 = 0x71;
pub const F3: u16 = 0x72;
pub const F4: u16 = 0x73;
pub const F5: u16 = 0x74;
pub const F6: u16 = 0x75;
pub const F7: u16 = 0x76;
pub const F8: u16 = 0x77;
pub const F9: u16 = 0x78;
pub const F10: u16 = 0x79;
pub const F11: u16 = 0x7A;
pub const F12: u16 = 0x7B;
pub const F13: u16 = 0x7C;
pub const F14: u16 = 0x7D;
pub const F15: u16 = 0x7E;
pub const F16: u16 = 0x7F;
pub const F17: u16 = 0x80;
pub const F18: u16 = 0x81;
pub const F19: u16 = 0x82;
pub const F20: u16 = 0x83;
pub const F21: u16 = 0x84;
pub const F22: u16 = 0x85;
pub const F23: u16 = 0x86;
pub const F24: u16 = 0x87;
pub const LSHIFT: u16 = 0xA0;
pub const RSHIFT: u16 = 0xA1;
pub const LCONTROL: u16 = 0xA2;
pub const RCONTROL: u16 = 0xA3;
pub const LMENU: u16 = 0xA4; pub const RMENU: u16 = 0xA5;
pub const OEM_1: u16 = 0xBA; pub const OEM_PLUS: u16 = 0xBB; pub const OEM_COMMA: u16 = 0xBC; pub const OEM_MINUS: u16 = 0xBD; pub const OEM_PERIOD: u16 = 0xBE; pub const OEM_2: u16 = 0xBF; pub const OEM_3: u16 = 0xC0; pub const OEM_4: u16 = 0xDB; pub const OEM_5: u16 = 0xDC; pub const OEM_6: u16 = 0xDD; pub const OEM_7: u16 = 0xDE; pub const OEM_8: u16 = 0xDF;
pub const OEM_102: u16 = 0xE2; }
#[allow(dead_code)]
mod sc {
pub const GRAVE: u32 = 0x29; pub const MINUS: u32 = 0x0C;
pub const EQUAL: u32 = 0x0D;
pub const LEFT_BRACKET: u32 = 0x1A;
pub const RIGHT_BRACKET: u32 = 0x1B;
pub const BACKSLASH: u32 = 0x2B; pub const SEMICOLON: u32 = 0x27;
pub const QUOTE: u32 = 0x28; pub const COMMA: u32 = 0x33;
pub const PERIOD: u32 = 0x34;
pub const SLASH: u32 = 0x35;
pub const ISO_SECTION: u32 = 0x56; pub const LCONTROL: u32 = 0x1D;
}
pub const ALTGR_PHANTOM_CTRL_SCANCODE: u32 = 0x21D;
pub fn is_altgr_phantom_ctrl(vk_code: u16, scan_code: u32) -> bool {
vk_code == vk::LCONTROL && scan_code == ALTGR_PHANTOM_CTRL_SCANCODE
}
fn is_punctuation_vk(vk_code: u16) -> bool {
matches!(
vk_code,
vk::OEM_1
| vk::OEM_PLUS
| vk::OEM_COMMA
| vk::OEM_MINUS
| vk::OEM_PERIOD
| vk::OEM_2
| vk::OEM_3
| vk::OEM_4
| vk::OEM_5
| vk::OEM_6
| vk::OEM_7
| vk::OEM_8
| vk::OEM_102
)
}
fn punctuation_scancode_to_key(scan_code: u32) -> Option<Key> {
match scan_code {
sc::GRAVE => Some(Key::Grave),
sc::MINUS => Some(Key::Minus),
sc::EQUAL => Some(Key::Equal),
sc::LEFT_BRACKET => Some(Key::LeftBracket),
sc::RIGHT_BRACKET => Some(Key::RightBracket),
sc::BACKSLASH => Some(Key::Backslash),
sc::SEMICOLON => Some(Key::Semicolon),
sc::QUOTE => Some(Key::Quote),
sc::COMMA => Some(Key::Comma),
sc::PERIOD => Some(Key::Period),
sc::SLASH => Some(Key::Slash),
sc::ISO_SECTION => Some(Key::Section),
_ => None,
}
}
pub fn map_key(vk_code: u16, scan_code: u32, is_extended: bool) -> Option<Key> {
if !is_extended && is_punctuation_vk(vk_code) {
if let Some(key) = punctuation_scancode_to_key(scan_code) {
return Some(key);
}
}
vk_to_key(vk_code, is_extended)
}
fn vk_to_key(vk_code: u16, is_extended: bool) -> Option<Key> {
match vk_code {
0x41 => Some(Key::A),
0x42 => Some(Key::B),
0x43 => Some(Key::C),
0x44 => Some(Key::D),
0x45 => Some(Key::E),
0x46 => Some(Key::F),
0x47 => Some(Key::G),
0x48 => Some(Key::H),
0x49 => Some(Key::I),
0x4A => Some(Key::J),
0x4B => Some(Key::K),
0x4C => Some(Key::L),
0x4D => Some(Key::M),
0x4E => Some(Key::N),
0x4F => Some(Key::O),
0x50 => Some(Key::P),
0x51 => Some(Key::Q),
0x52 => Some(Key::R),
0x53 => Some(Key::S),
0x54 => Some(Key::T),
0x55 => Some(Key::U),
0x56 => Some(Key::V),
0x57 => Some(Key::W),
0x58 => Some(Key::X),
0x59 => Some(Key::Y),
0x5A => Some(Key::Z),
0x30 => Some(Key::Num0),
0x31 => Some(Key::Num1),
0x32 => Some(Key::Num2),
0x33 => Some(Key::Num3),
0x34 => Some(Key::Num4),
0x35 => Some(Key::Num5),
0x36 => Some(Key::Num6),
0x37 => Some(Key::Num7),
0x38 => Some(Key::Num8),
0x39 => Some(Key::Num9),
0xB3 => Some(Key::PlayPause),
0xB2 => Some(Key::Stop),
0xB1 => Some(Key::PrevTrack),
0xB0 => Some(Key::NextTrack),
vk::NUMPAD0 => Some(Key::Keypad0),
vk::NUMPAD1 => Some(Key::Keypad1),
vk::NUMPAD2 => Some(Key::Keypad2),
vk::NUMPAD3 => Some(Key::Keypad3),
vk::NUMPAD4 => Some(Key::Keypad4),
vk::NUMPAD5 => Some(Key::Keypad5),
vk::NUMPAD6 => Some(Key::Keypad6),
vk::NUMPAD7 => Some(Key::Keypad7),
vk::NUMPAD8 => Some(Key::Keypad8),
vk::NUMPAD9 => Some(Key::Keypad9),
vk::MULTIPLY => Some(Key::KeypadMultiply),
vk::ADD => Some(Key::KeypadPlus),
vk::SUBTRACT => Some(Key::KeypadMinus),
vk::DECIMAL => Some(Key::KeypadDecimal),
vk::DIVIDE => Some(Key::KeypadDivide),
vk::RETURN if is_extended => Some(Key::KeypadEnter),
vk::RETURN => Some(Key::Return),
vk::F1 => Some(Key::F1),
vk::F2 => Some(Key::F2),
vk::F3 => Some(Key::F3),
vk::F4 => Some(Key::F4),
vk::F5 => Some(Key::F5),
vk::F6 => Some(Key::F6),
vk::F7 => Some(Key::F7),
vk::F8 => Some(Key::F8),
vk::F9 => Some(Key::F9),
vk::F10 => Some(Key::F10),
vk::F11 => Some(Key::F11),
vk::F12 => Some(Key::F12),
vk::F13 => Some(Key::F13),
vk::F14 => Some(Key::F14),
vk::F15 => Some(Key::F15),
vk::F16 => Some(Key::F16),
vk::F17 => Some(Key::F17),
vk::F18 => Some(Key::F18),
vk::F19 => Some(Key::F19),
vk::F20 => Some(Key::F20),
vk::F21 => Some(Key::F21),
vk::F22 => Some(Key::F22),
vk::F23 => Some(Key::F23),
vk::F24 => Some(Key::F24),
vk::BACK => Some(Key::Delete), vk::DELETE => Some(Key::ForwardDelete),
vk::INSERT => Some(Key::Insert),
vk::TAB => Some(Key::Tab),
vk::ESCAPE => Some(Key::Escape),
vk::SPACE => Some(Key::Space),
vk::PRIOR => Some(Key::PageUp),
vk::NEXT => Some(Key::PageDown),
vk::END => Some(Key::End),
vk::HOME => Some(Key::Home),
vk::LEFT => Some(Key::LeftArrow),
vk::UP => Some(Key::UpArrow),
vk::RIGHT => Some(Key::RightArrow),
vk::DOWN => Some(Key::DownArrow),
vk::OEM_1 => Some(Key::Semicolon),
vk::OEM_PLUS => Some(Key::Equal),
vk::OEM_COMMA => Some(Key::Comma),
vk::OEM_MINUS => Some(Key::Minus),
vk::OEM_PERIOD => Some(Key::Period),
vk::OEM_2 => Some(Key::Slash),
vk::OEM_3 => Some(Key::Grave),
vk::OEM_4 => Some(Key::LeftBracket),
vk::OEM_5 => Some(Key::Backslash),
vk::OEM_6 => Some(Key::RightBracket),
vk::OEM_7 => Some(Key::Quote),
vk::OEM_8 => Some(Key::Grave),
vk::OEM_102 => Some(Key::Section),
vk::CAPITAL => Some(Key::CapsLock),
vk::NUMLOCK => Some(Key::NumLock),
vk::SCROLL => Some(Key::ScrollLock),
_ => None,
}
}
pub fn vk_to_modifier(vk_code: u16) -> Option<Modifiers> {
match vk_code {
vk::LSHIFT => Some(Modifiers::SHIFT_LEFT),
vk::RSHIFT => Some(Modifiers::SHIFT_RIGHT),
vk::SHIFT => Some(Modifiers::SHIFT_LEFT), vk::LCONTROL => Some(Modifiers::CTRL_LEFT),
vk::RCONTROL => Some(Modifiers::CTRL_RIGHT),
vk::CONTROL => Some(Modifiers::CTRL_LEFT),
vk::LMENU => Some(Modifiers::OPT_LEFT),
vk::RMENU => Some(Modifiers::OPT_RIGHT),
vk::MENU => Some(Modifiers::OPT_LEFT),
vk::LWIN => Some(Modifiers::CMD_LEFT),
vk::RWIN => Some(Modifiers::CMD_RIGHT),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn grave_position_maps_to_grave_on_us_and_uk() {
assert_eq!(map_key(vk::OEM_3, sc::GRAVE, false), Some(Key::Grave));
assert_eq!(map_key(vk::OEM_8, sc::GRAVE, false), Some(Key::Grave));
}
#[test]
fn quote_position_maps_to_quote_on_us_and_uk() {
assert_eq!(map_key(vk::OEM_7, sc::QUOTE, false), Some(Key::Quote));
assert_eq!(map_key(vk::OEM_3, sc::QUOTE, false), Some(Key::Quote));
}
#[test]
fn iso_hash_key_maps_to_backslash() {
assert_eq!(
map_key(vk::OEM_7, sc::BACKSLASH, false),
Some(Key::Backslash)
);
assert_eq!(
map_key(vk::OEM_5, sc::BACKSLASH, false),
Some(Key::Backslash)
);
}
#[test]
fn remaining_punctuation_positions_map_positionally() {
assert_eq!(map_key(vk::OEM_MINUS, sc::MINUS, false), Some(Key::Minus));
assert_eq!(map_key(vk::OEM_PLUS, sc::EQUAL, false), Some(Key::Equal));
assert_eq!(
map_key(vk::OEM_4, sc::LEFT_BRACKET, false),
Some(Key::LeftBracket)
);
assert_eq!(
map_key(vk::OEM_6, sc::RIGHT_BRACKET, false),
Some(Key::RightBracket)
);
assert_eq!(
map_key(vk::OEM_1, sc::SEMICOLON, false),
Some(Key::Semicolon)
);
assert_eq!(map_key(vk::OEM_COMMA, sc::COMMA, false), Some(Key::Comma));
assert_eq!(
map_key(vk::OEM_PERIOD, sc::PERIOD, false),
Some(Key::Period)
);
assert_eq!(map_key(vk::OEM_2, sc::SLASH, false), Some(Key::Slash));
assert_eq!(
map_key(vk::OEM_102, sc::ISO_SECTION, false),
Some(Key::Section)
);
}
#[test]
fn letters_stay_on_vk_mapping_even_on_punctuation_positions() {
assert_eq!(map_key(0x4D, sc::SEMICOLON, false), Some(Key::M));
assert_eq!(map_key(0x5A, 0x15, false), Some(Key::Z));
}
#[test]
fn oem_vk_on_unknown_position_falls_back_to_vk_mapping() {
assert_eq!(map_key(vk::OEM_1, 0x0B, false), Some(Key::Semicolon));
}
#[test]
fn extended_events_skip_the_positional_table() {
assert_eq!(map_key(vk::OEM_2, sc::SLASH, true), Some(Key::Slash));
assert_eq!(
map_key(vk::DIVIDE, sc::SLASH, true),
Some(Key::KeypadDivide)
);
}
#[test]
fn f21_to_f24_map() {
assert_eq!(map_key(vk::F21, 0, false), Some(Key::F21));
assert_eq!(map_key(vk::F22, 0, false), Some(Key::F22));
assert_eq!(map_key(vk::F23, 0, false), Some(Key::F23));
assert_eq!(map_key(vk::F24, 0, false), Some(Key::F24));
}
#[test]
fn altgr_phantom_ctrl_is_detected() {
assert!(is_altgr_phantom_ctrl(
vk::LCONTROL,
ALTGR_PHANTOM_CTRL_SCANCODE
));
}
#[test]
fn real_ctrl_events_are_not_phantom() {
assert!(!is_altgr_phantom_ctrl(vk::LCONTROL, sc::LCONTROL));
assert!(!is_altgr_phantom_ctrl(
vk::RCONTROL,
ALTGR_PHANTOM_CTRL_SCANCODE
));
}
}