use cocoa::appkit::{NSEvent, NSEventModifierFlags, NSEventType};
use cocoa::base::id;
use objc::{msg_send, sel, sel_impl};
use crate::keyboard::{Code, KbKey, KeyEvent, KeyState, Modifiers};
use super::super::shared;
use super::util::from_nsstring;
pub(crate) struct KeyboardState {
last_mods: NSEventModifierFlags,
}
fn key_code_to_code(key_code: u16) -> Code {
match key_code {
0x00 => Code::KeyA,
0x01 => Code::KeyS,
0x02 => Code::KeyD,
0x03 => Code::KeyF,
0x04 => Code::KeyH,
0x05 => Code::KeyG,
0x06 => Code::KeyZ,
0x07 => Code::KeyX,
0x08 => Code::KeyC,
0x09 => Code::KeyV,
0x0a => Code::IntlBackslash,
0x0b => Code::KeyB,
0x0c => Code::KeyQ,
0x0d => Code::KeyW,
0x0e => Code::KeyE,
0x0f => Code::KeyR,
0x10 => Code::KeyY,
0x11 => Code::KeyT,
0x12 => Code::Digit1,
0x13 => Code::Digit2,
0x14 => Code::Digit3,
0x15 => Code::Digit4,
0x16 => Code::Digit6,
0x17 => Code::Digit5,
0x18 => Code::Equal,
0x19 => Code::Digit9,
0x1a => Code::Digit7,
0x1b => Code::Minus,
0x1c => Code::Digit8,
0x1d => Code::Digit0,
0x1e => Code::BracketRight,
0x1f => Code::KeyO,
0x20 => Code::KeyU,
0x21 => Code::BracketLeft,
0x22 => Code::KeyI,
0x23 => Code::KeyP,
0x24 => Code::Enter,
0x25 => Code::KeyL,
0x26 => Code::KeyJ,
0x27 => Code::Quote,
0x28 => Code::KeyK,
0x29 => Code::Semicolon,
0x2a => Code::Backslash,
0x2b => Code::Comma,
0x2c => Code::Slash,
0x2d => Code::KeyN,
0x2e => Code::KeyM,
0x2f => Code::Period,
0x30 => Code::Tab,
0x31 => Code::Space,
0x32 => Code::Backquote,
0x33 => Code::Backspace,
0x34 => Code::NumpadEnter,
0x35 => Code::Escape,
0x36 => Code::MetaRight,
0x37 => Code::MetaLeft,
0x38 => Code::ShiftLeft,
0x39 => Code::CapsLock,
0x3a => Code::AltLeft,
0x3b => Code::ControlLeft,
0x3c => Code::ShiftRight,
0x3d => Code::AltRight,
0x3e => Code::ControlRight,
0x3f => Code::Fn,
0x40 => Code::F17,
0x41 => Code::NumpadDecimal,
0x43 => Code::NumpadMultiply,
0x45 => Code::NumpadAdd,
0x47 => Code::NumLock,
0x48 => Code::AudioVolumeUp,
0x49 => Code::AudioVolumeDown,
0x4a => Code::AudioVolumeMute,
0x4b => Code::NumpadDivide,
0x4c => Code::NumpadEnter,
0x4e => Code::NumpadSubtract,
0x4f => Code::F18,
0x50 => Code::F19,
0x51 => Code::NumpadEqual,
0x52 => Code::Numpad0,
0x53 => Code::Numpad1,
0x54 => Code::Numpad2,
0x55 => Code::Numpad3,
0x56 => Code::Numpad4,
0x57 => Code::Numpad5,
0x58 => Code::Numpad6,
0x59 => Code::Numpad7,
0x5a => Code::F20,
0x5b => Code::Numpad8,
0x5c => Code::Numpad9,
0x5d => Code::IntlYen,
0x5e => Code::IntlRo,
0x5f => Code::NumpadComma,
0x60 => Code::F5,
0x61 => Code::F6,
0x62 => Code::F7,
0x63 => Code::F3,
0x64 => Code::F8,
0x65 => Code::F9,
0x66 => Code::Lang2,
0x67 => Code::F11,
0x68 => Code::Lang1,
0x69 => Code::F13,
0x6a => Code::F16,
0x6b => Code::F14,
0x6d => Code::F10,
0x6e => Code::ContextMenu,
0x6f => Code::F12,
0x71 => Code::F15,
0x72 => Code::Insert,
0x73 => Code::Home,
0x74 => Code::PageUp,
0x75 => Code::Delete,
0x76 => Code::F4,
0x77 => Code::End,
0x78 => Code::F2,
0x79 => Code::PageDown,
0x7a => Code::F1,
0x7b => Code::ArrowLeft,
0x7c => Code::ArrowRight,
0x7d => Code::ArrowDown,
0x7e => Code::ArrowUp,
_ => Code::Unidentified,
}
}
fn code_to_key(code: Code) -> Option<KbKey> {
Some(match code {
Code::Escape => KbKey::Escape,
Code::ShiftLeft | Code::ShiftRight => KbKey::Shift,
Code::AltLeft | Code::AltRight => KbKey::Alt,
Code::MetaLeft | Code::MetaRight => KbKey::Meta,
Code::ControlLeft | Code::ControlRight => KbKey::Control,
Code::CapsLock => KbKey::CapsLock,
Code::NumLock => KbKey::Clear,
Code::Fn => KbKey::Fn,
Code::F1 => KbKey::F1,
Code::F2 => KbKey::F2,
Code::F3 => KbKey::F3,
Code::F4 => KbKey::F4,
Code::F5 => KbKey::F5,
Code::F6 => KbKey::F6,
Code::F7 => KbKey::F7,
Code::F8 => KbKey::F8,
Code::F9 => KbKey::F9,
Code::F10 => KbKey::F10,
Code::F11 => KbKey::F11,
Code::F12 => KbKey::F12,
Code::F13 => KbKey::F13,
Code::F14 => KbKey::F14,
Code::F15 => KbKey::F15,
Code::F16 => KbKey::F16,
Code::F17 => KbKey::F17,
Code::F18 => KbKey::F18,
Code::F19 => KbKey::F19,
Code::F20 => KbKey::F20,
Code::F21 => KbKey::F21,
Code::F22 => KbKey::F22,
Code::F23 => KbKey::F23,
Code::F24 => KbKey::F24,
Code::Pause => KbKey::Pause,
Code::ScrollLock => KbKey::ScrollLock,
Code::PrintScreen => KbKey::PrintScreen,
Code::Insert => KbKey::Insert,
Code::Delete => KbKey::Delete,
Code::Tab => KbKey::Tab,
Code::Backspace => KbKey::Backspace,
Code::ContextMenu => KbKey::ContextMenu,
Code::Lang1 => KbKey::KanjiMode,
Code::Lang2 => KbKey::Eisu,
Code::Home => KbKey::Home,
Code::End => KbKey::End,
Code::PageUp => KbKey::PageUp,
Code::PageDown => KbKey::PageDown,
Code::ArrowLeft => KbKey::ArrowLeft,
Code::ArrowRight => KbKey::ArrowRight,
Code::ArrowUp => KbKey::ArrowUp,
Code::ArrowDown => KbKey::ArrowDown,
Code::Enter => KbKey::Enter,
Code::NumpadEnter => KbKey::Enter,
Code::Help => KbKey::Help,
_ => return None,
})
}
fn is_valid_key(s: &str) -> bool {
match s.chars().next() {
None => false,
Some(c) => c >= ' ' && c != '\x7f' && !('\u{e000}'..'\u{f900}').contains(&c),
}
}
fn is_modifier_code(code: Code) -> bool {
matches!(
code,
Code::ShiftLeft
| Code::ShiftRight
| Code::AltLeft
| Code::AltRight
| Code::ControlLeft
| Code::ControlRight
| Code::MetaLeft
| Code::MetaRight
| Code::CapsLock
| Code::Help
)
}
impl KeyboardState {
pub(crate) fn new() -> KeyboardState {
let last_mods = NSEventModifierFlags::empty();
KeyboardState { last_mods }
}
pub(crate) fn process_native_event(&mut self, event: id) -> Option<KeyEvent> {
unsafe {
let event_type = event.eventType();
let key_code = event.keyCode();
let code = key_code_to_code(key_code);
let location = shared::code_to_location(code);
let raw_mods = event.modifierFlags();
let mods = make_modifiers(raw_mods);
let state = match event_type {
NSEventType::NSKeyDown => KeyState::Down,
NSEventType::NSKeyUp => KeyState::Up,
NSEventType::NSFlagsChanged => {
let any_down = raw_mods.bits() & !self.last_mods.bits();
self.last_mods = raw_mods;
if is_modifier_code(code) {
if any_down == 0 {
KeyState::Up
} else {
KeyState::Down
}
} else {
return None;
}
}
_ => unreachable!(),
};
let is_composing = false;
let repeat: bool = event_type == NSEventType::NSKeyDown && msg_send![event, isARepeat];
let key = if let Some(key) = code_to_key(code) {
key
} else {
let characters = from_nsstring(event.characters());
if is_valid_key(&characters) {
KbKey::Character(characters)
} else {
let chars_ignoring = from_nsstring(event.charactersIgnoringModifiers());
if is_valid_key(&chars_ignoring) {
KbKey::Character(chars_ignoring)
} else {
KbKey::Unidentified
}
}
};
let event = KeyEvent {
state,
key,
code,
location,
mods,
repeat,
is_composing,
};
Some(event)
}
}
}
const MODIFIER_MAP: &[(NSEventModifierFlags, Modifiers)] = &[
(NSEventModifierFlags::NSShiftKeyMask, Modifiers::SHIFT),
(NSEventModifierFlags::NSAlternateKeyMask, Modifiers::ALT),
(NSEventModifierFlags::NSControlKeyMask, Modifiers::CONTROL),
(NSEventModifierFlags::NSCommandKeyMask, Modifiers::META),
(
NSEventModifierFlags::NSAlphaShiftKeyMask,
Modifiers::CAPS_LOCK,
),
];
pub(crate) fn make_modifiers(raw: NSEventModifierFlags) -> Modifiers {
let mut modifiers = Modifiers::empty();
for &(flags, mods) in MODIFIER_MAP {
if raw.contains(flags) {
modifiers |= mods;
}
}
modifiers
}