From 02d1aae4db27df054b703aa935ca118f31e17123 Mon Sep 17 00:00:00 2001 From: Hal Gentz Date: Wed, 24 Apr 2019 23:39:19 -0600 Subject: [PATCH] Backport https://github.com/rust-windowing/winit/commit/9a23ec3c373d096e5e9dd0cc0a23f7baf0b64135#diff-1d95fe39cdbaa708c975380a16c314cb Signed-off-by: Hal Gentz --- src/platform_impl/macos/event.rs | 101 ++++++++++++++++++++++++++----- src/platform_impl/macos/view.rs | 85 +++++++++++++++----------- 2 files changed, 136 insertions(+), 50 deletions(-) diff --git a/src/platform_impl/macos/event.rs b/src/platform_impl/macos/event.rs index a488ebb0bea..c19c5300def 100644 --- a/src/platform_impl/macos/event.rs +++ b/src/platform_impl/macos/event.rs @@ -8,7 +8,67 @@ use event::{ }; use platform_impl::platform::DEVICE_ID; -pub fn to_virtual_keycode(scancode: c_ushort) -> Option { +pub fn char_to_keycode(c: char) -> Option { + // We only translate keys that are affected by keyboard layout. + // + // Note that since keys are translated in a somewhat "dumb" way (reading character) + // there is a concern that some combination, i.e. Cmd+char, causes the wrong + // letter to be received, and so we receive the wrong key. + // + // Implementation reference: https://github.com/WebKit/webkit/blob/82bae82cf0f329dbe21059ef0986c4e92fea4ba6/Source/WebCore/platform/cocoa/KeyEventCocoa.mm#L626 + Some(match c { + 'a' | 'A' => VirtualKeyCode::A, + 'b' | 'B' => VirtualKeyCode::B, + 'c' | 'C' => VirtualKeyCode::C, + 'd' | 'D' => VirtualKeyCode::D, + 'e' | 'E' => VirtualKeyCode::E, + 'f' | 'F' => VirtualKeyCode::F, + 'g' | 'G' => VirtualKeyCode::G, + 'h' | 'H' => VirtualKeyCode::H, + 'i' | 'I' => VirtualKeyCode::I, + 'j' | 'J' => VirtualKeyCode::J, + 'k' | 'K' => VirtualKeyCode::K, + 'l' | 'L' => VirtualKeyCode::L, + 'm' | 'M' => VirtualKeyCode::M, + 'n' | 'N' => VirtualKeyCode::N, + 'o' | 'O' => VirtualKeyCode::O, + 'p' | 'P' => VirtualKeyCode::P, + 'q' | 'Q' => VirtualKeyCode::Q, + 'r' | 'R' => VirtualKeyCode::R, + 's' | 'S' => VirtualKeyCode::S, + 't' | 'T' => VirtualKeyCode::T, + 'u' | 'U' => VirtualKeyCode::U, + 'v' | 'V' => VirtualKeyCode::V, + 'w' | 'W' => VirtualKeyCode::W, + 'x' | 'X' => VirtualKeyCode::X, + 'y' | 'Y' => VirtualKeyCode::Y, + 'z' | 'Z' => VirtualKeyCode::Z, + '1' | '!' => VirtualKeyCode::Key1, + '2' | '@' => VirtualKeyCode::Key2, + '3' | '#' => VirtualKeyCode::Key3, + '4' | '$' => VirtualKeyCode::Key4, + '5' | '%' => VirtualKeyCode::Key5, + '6' | '^' => VirtualKeyCode::Key6, + '7' | '&' => VirtualKeyCode::Key7, + '8' | '*' => VirtualKeyCode::Key8, + '9' | '(' => VirtualKeyCode::Key9, + '0' | ')' => VirtualKeyCode::Key0, + '=' | '+' => VirtualKeyCode::Equals, + '-' | '_' => VirtualKeyCode::Minus, + ']' | '}' => VirtualKeyCode::RBracket, + '[' | '{' => VirtualKeyCode::LBracket, + '\''| '"' => VirtualKeyCode::Apostrophe, + ';' | ':' => VirtualKeyCode::Semicolon, + '\\'| '|' => VirtualKeyCode::Backslash, + ',' | '<' => VirtualKeyCode::Comma, + '/' | '?' => VirtualKeyCode::Slash, + '.' | '>' => VirtualKeyCode::Period, + '`' | '~' => VirtualKeyCode::Grave, + _ => return None, + }) +} + +pub fn scancode_to_keycode(scancode: c_ushort) -> Option { Some(match scancode { 0x00 => VirtualKeyCode::A, 0x01 => VirtualKeyCode::S, @@ -147,17 +207,18 @@ pub fn to_virtual_keycode(scancode: c_ushort) -> Option { // While F1-F20 have scancodes we can match on, we have to check against UTF-16 // constants for the rest. // https://developer.apple.com/documentation/appkit/1535851-function-key_unicodes?preferredLanguage=occ -pub fn check_function_keys(string: &Option) -> Option { - string - .as_ref() - .and_then(|string| string.encode_utf16().next()) - .and_then(|character| match character { - 0xf718 => Some(VirtualKeyCode::F21), - 0xf719 => Some(VirtualKeyCode::F22), - 0xf71a => Some(VirtualKeyCode::F23), - 0xf71b => Some(VirtualKeyCode::F24), - _ => None, +pub fn check_function_keys(string: &String) -> Option { + if let Some(ch) = string.encode_utf16().next() { + return Some(match ch { + 0xf718 => VirtualKeyCode::F21, + 0xf719 => VirtualKeyCode::F22, + 0xf71a => VirtualKeyCode::F23, + 0xf71b => VirtualKeyCode::F24, + _ => return None, }) + } + + None } pub fn event_mods(event: id) -> ModifiersState { @@ -172,6 +233,16 @@ pub fn event_mods(event: id) -> ModifiersState { } } +pub fn get_scancode(event: cocoa::base::id) -> c_ushort { + // In AppKit, `keyCode` refers to the position (scancode) of a key rather than its character, + // and there is no easy way to navtively retrieve the layout-dependent character. + // In winit, we use keycode to refer to the key's character, and so this function aligns + // AppKit's terminology with ours. + unsafe { + msg_send![event, keyCode] + } +} + pub unsafe fn modifier_event( ns_event: id, keymask: NSEventModifierFlags, @@ -184,14 +255,14 @@ pub unsafe fn modifier_event( } else { ElementState::Pressed }; - let keycode = NSEvent::keyCode(ns_event); - let scancode = keycode as u32; - let virtual_keycode = to_virtual_keycode(keycode); + + let scancode = get_scancode(ns_event); + let virtual_keycode = scancode_to_keycode(scancode); Some(WindowEvent::KeyboardInput { device_id: DEVICE_ID, input: KeyboardInput { state, - scancode, + scancode: scancode as _, virtual_keycode, modifiers: event_mods(ns_event), }, diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index cceba6eeeeb..914962c6c6f 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -18,7 +18,7 @@ use { }; use platform_impl::platform::{ app_state::AppState, DEVICE_ID, - event::{check_function_keys, event_mods, modifier_event, to_virtual_keycode}, + event::{check_function_keys, event_mods, modifier_event, char_to_keycode, get_scancode, scancode_to_keycode}, util::{self, IdRef}, ffi::*, window::get_window_id, }; @@ -504,16 +504,53 @@ extern fn do_command_by_selector(this: &Object, _sel: Sel, command: Sel) { trace!("Completed `doCommandBySelector`"); } -fn get_characters(event: id) -> Option { +fn get_characters(event: id, ignore_modifiers: bool) -> String { unsafe { - let characters: id = msg_send![event, characters]; + let characters: id = if ignore_modifiers { + msg_send![event, charactersIgnoringModifiers] + } else { + msg_send![event, characters] + }; + + assert_ne!(characters, nil); let slice = slice::from_raw_parts( characters.UTF8String() as *const c_uchar, characters.len(), ); + let string = str::from_utf8_unchecked(slice); - Some(string.to_owned()) + string.to_owned() + } +} + +// Retrieves a layout-independent keycode given an event. +fn retrieve_keycode(event: id) -> Option { + #[inline] + fn get_code(ev: id, raw: bool) -> Option { + let characters = get_characters(ev, raw); + characters.chars().next().map_or(None, |c| char_to_keycode(c)) } + + // Cmd switches Roman letters for Dvorak-QWERTY layout, so we try modified characters first. + // If we don't get a match, then we fall back to unmodified characters. + let code = get_code(event, false) + .or_else(|| { + get_code(event, true) + }); + + // We've checked all layout related keys, so fall through to scancode. + // Reaching this code means that the key is layout-independent (e.g. Backspace, Return). + // + // We're additionally checking here for F21-F24 keys, since their keycode + // can vary, but we know that they are encoded + // in characters property. + code.or_else(|| { + let scancode = get_scancode(event); + scancode_to_keycode(scancode) + .or_else(|| { + check_function_keys(&get_characters(event, true)) + }) + }) } extern fn key_down(this: &Object, _sel: Sel, event: id) { @@ -522,17 +559,13 @@ extern fn key_down(this: &Object, _sel: Sel, event: id) { let state_ptr: *mut c_void = *this.get_ivar("winitState"); let state = &mut *(state_ptr as *mut ViewState); let window_id = WindowId(get_window_id(state.nswindow)); + let characters = get_characters(event, false); - state.raw_characters = get_characters(event); + state.raw_characters = Some(characters.clone()); + + let scancode = get_scancode(event) as u32; + let virtual_keycode = retrieve_keycode(event); - let keycode: c_ushort = msg_send![event, keyCode]; - // We are checking here for F21-F24 keys, since their keycode - // can vary, but we know that they are encoded - // in characters property. - let virtual_keycode = to_virtual_keycode(keycode).or_else(|| { - check_function_keys(&state.raw_characters) - }); - let scancode = keycode as u32; let is_repeat = msg_send![event, isARepeat]; let window_event = Event::WindowEvent { @@ -548,22 +581,11 @@ extern fn key_down(this: &Object, _sel: Sel, event: id) { }, }; - let characters: id = msg_send![event, characters]; - let slice = slice::from_raw_parts( - characters.UTF8String() as *const c_uchar, - characters.len(), - ); - let string = str::from_utf8_unchecked(slice); - - state.raw_characters = { - Some(string.to_owned()) - }; - let pass_along = { AppState::queue_event(window_event); // Emit `ReceivedCharacter` for key repeats if is_repeat && state.is_key_down { - for character in string.chars() { + for character in characters.chars() { AppState::queue_event(Event::WindowEvent { window_id, event: WindowEvent::ReceivedCharacter(character), @@ -594,16 +616,9 @@ extern fn key_up(this: &Object, _sel: Sel, event: id) { state.is_key_down = false; - // We need characters here to check for additional keys such as - // F21-F24. - let characters = get_characters(event); + let scancode = get_scancode(event) as u32; + let virtual_keycode = retrieve_keycode(event); - let keycode: c_ushort = msg_send![event, keyCode]; - let virtual_keycode = to_virtual_keycode(keycode) - .or_else(|| { - check_function_keys(&characters) - }); - let scancode = keycode as u32; let window_event = Event::WindowEvent { window_id: WindowId(get_window_id(state.nswindow)), event: WindowEvent::KeyboardInput { @@ -707,7 +722,7 @@ extern fn cancel_operation(this: &Object, _sel: Sel, _sender: id) { let state = &mut *(state_ptr as *mut ViewState); let scancode = 0x2f; - let virtual_keycode = to_virtual_keycode(scancode); + let virtual_keycode = scancode_to_keycode(scancode); debug_assert_eq!(virtual_keycode, Some(VirtualKeyCode::Period)); let event: id = msg_send![NSApp(), currentEvent];