From 459ee4b34747b90bec71a6af3f041365400d7f80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Art=C3=BAr=20Kov=C3=A1cs?= Date: Sat, 10 Dec 2022 14:42:32 +0100 Subject: [PATCH] Implement the new keyboard API for macOS --- src/platform/macos.rs | 263 ++++++++++ src/platform_impl/ios/mod.rs | 2 - src/platform_impl/macos/app_state.rs | 2 +- src/platform_impl/macos/event.rs | 527 +++++++++++---------- src/platform_impl/macos/ffi.rs | 48 +- src/platform_impl/macos/mod.rs | 1 + src/platform_impl/macos/util/mod.rs | 15 +- src/platform_impl/macos/view.rs | 344 ++++++++------ src/platform_impl/macos/window.rs | 4 + src/platform_impl/macos/window_delegate.rs | 3 +- 10 files changed, 816 insertions(+), 393 deletions(-) diff --git a/src/platform/macos.rs b/src/platform/macos.rs index c2be433fbcd..3399c4d468b 100644 --- a/src/platform/macos.rs +++ b/src/platform/macos.rs @@ -3,7 +3,9 @@ use std::os::raw::c_void; use crate::{ event_loop::{EventLoopBuilder, EventLoopWindowTarget}, + keyboard::{KeyCode, NativeKeyCode}, monitor::MonitorHandle, + platform::scancode::KeyCodeExtScancode, window::{Window, WindowBuilder}, }; @@ -311,3 +313,264 @@ impl EventLoopWindowTargetExtMacOS for EventLoopWindowTarget { self.p.hide_other_applications() } } + +impl KeyCodeExtScancode for KeyCode { + fn to_scancode(self) -> Option { + match self { + KeyCode::KeyA => Some(0x00), + KeyCode::KeyS => Some(0x01), + KeyCode::KeyD => Some(0x02), + KeyCode::KeyF => Some(0x03), + KeyCode::KeyH => Some(0x04), + KeyCode::KeyG => Some(0x05), + KeyCode::KeyZ => Some(0x06), + KeyCode::KeyX => Some(0x07), + KeyCode::KeyC => Some(0x08), + KeyCode::KeyV => Some(0x09), + KeyCode::KeyB => Some(0x0b), + KeyCode::KeyQ => Some(0x0c), + KeyCode::KeyW => Some(0x0d), + KeyCode::KeyE => Some(0x0e), + KeyCode::KeyR => Some(0x0f), + KeyCode::KeyY => Some(0x10), + KeyCode::KeyT => Some(0x11), + KeyCode::Digit1 => Some(0x12), + KeyCode::Digit2 => Some(0x13), + KeyCode::Digit3 => Some(0x14), + KeyCode::Digit4 => Some(0x15), + KeyCode::Digit6 => Some(0x16), + KeyCode::Digit5 => Some(0x17), + KeyCode::Equal => Some(0x18), + KeyCode::Digit9 => Some(0x19), + KeyCode::Digit7 => Some(0x1a), + KeyCode::Minus => Some(0x1b), + KeyCode::Digit8 => Some(0x1c), + KeyCode::Digit0 => Some(0x1d), + KeyCode::BracketRight => Some(0x1e), + KeyCode::KeyO => Some(0x1f), + KeyCode::KeyU => Some(0x20), + KeyCode::BracketLeft => Some(0x21), + KeyCode::KeyI => Some(0x22), + KeyCode::KeyP => Some(0x23), + KeyCode::Enter => Some(0x24), + KeyCode::KeyL => Some(0x25), + KeyCode::KeyJ => Some(0x26), + KeyCode::Quote => Some(0x27), + KeyCode::KeyK => Some(0x28), + KeyCode::Semicolon => Some(0x29), + KeyCode::Backslash => Some(0x2a), + KeyCode::Comma => Some(0x2b), + KeyCode::Slash => Some(0x2c), + KeyCode::KeyN => Some(0x2d), + KeyCode::KeyM => Some(0x2e), + KeyCode::Period => Some(0x2f), + KeyCode::Tab => Some(0x30), + KeyCode::Space => Some(0x31), + KeyCode::Backquote => Some(0x32), + KeyCode::Backspace => Some(0x33), + KeyCode::Escape => Some(0x35), + KeyCode::SuperRight => Some(0x36), + KeyCode::SuperLeft => Some(0x37), + KeyCode::ShiftLeft => Some(0x38), + KeyCode::AltLeft => Some(0x3a), + KeyCode::ControlLeft => Some(0x3b), + KeyCode::ShiftRight => Some(0x3c), + KeyCode::AltRight => Some(0x3d), + KeyCode::ControlRight => Some(0x3e), + KeyCode::F17 => Some(0x40), + KeyCode::NumpadDecimal => Some(0x41), + KeyCode::NumpadMultiply => Some(0x43), + KeyCode::NumpadAdd => Some(0x45), + KeyCode::NumLock => Some(0x47), + KeyCode::AudioVolumeUp => Some(0x49), + KeyCode::AudioVolumeDown => Some(0x4a), + KeyCode::NumpadDivide => Some(0x4b), + KeyCode::NumpadEnter => Some(0x4c), + KeyCode::NumpadSubtract => Some(0x4e), + KeyCode::F18 => Some(0x4f), + KeyCode::F19 => Some(0x50), + KeyCode::NumpadEqual => Some(0x51), + KeyCode::Numpad0 => Some(0x52), + KeyCode::Numpad1 => Some(0x53), + KeyCode::Numpad2 => Some(0x54), + KeyCode::Numpad3 => Some(0x55), + KeyCode::Numpad4 => Some(0x56), + KeyCode::Numpad5 => Some(0x57), + KeyCode::Numpad6 => Some(0x58), + KeyCode::Numpad7 => Some(0x59), + KeyCode::F20 => Some(0x5a), + KeyCode::Numpad8 => Some(0x5b), + KeyCode::Numpad9 => Some(0x5c), + KeyCode::IntlYen => Some(0x5d), + KeyCode::F5 => Some(0x60), + KeyCode::F6 => Some(0x61), + KeyCode::F7 => Some(0x62), + KeyCode::F3 => Some(0x63), + KeyCode::F8 => Some(0x64), + KeyCode::F9 => Some(0x65), + KeyCode::F11 => Some(0x67), + KeyCode::F13 => Some(0x69), + KeyCode::F16 => Some(0x6a), + KeyCode::F14 => Some(0x6b), + KeyCode::F10 => Some(0x6d), + KeyCode::F12 => Some(0x6f), + KeyCode::F15 => Some(0x71), + KeyCode::Insert => Some(0x72), + KeyCode::Home => Some(0x73), + KeyCode::PageUp => Some(0x74), + KeyCode::Delete => Some(0x75), + KeyCode::F4 => Some(0x76), + KeyCode::End => Some(0x77), + KeyCode::F2 => Some(0x78), + KeyCode::PageDown => Some(0x79), + KeyCode::F1 => Some(0x7a), + KeyCode::ArrowLeft => Some(0x7b), + KeyCode::ArrowRight => Some(0x7c), + KeyCode::ArrowDown => Some(0x7d), + KeyCode::ArrowUp => Some(0x7e), + _ => None, + } + } + + fn from_scancode(scancode: u32) -> KeyCode { + match scancode { + 0x00 => KeyCode::KeyA, + 0x01 => KeyCode::KeyS, + 0x02 => KeyCode::KeyD, + 0x03 => KeyCode::KeyF, + 0x04 => KeyCode::KeyH, + 0x05 => KeyCode::KeyG, + 0x06 => KeyCode::KeyZ, + 0x07 => KeyCode::KeyX, + 0x08 => KeyCode::KeyC, + 0x09 => KeyCode::KeyV, + //0x0a => World 1, + 0x0b => KeyCode::KeyB, + 0x0c => KeyCode::KeyQ, + 0x0d => KeyCode::KeyW, + 0x0e => KeyCode::KeyE, + 0x0f => KeyCode::KeyR, + 0x10 => KeyCode::KeyY, + 0x11 => KeyCode::KeyT, + 0x12 => KeyCode::Digit1, + 0x13 => KeyCode::Digit2, + 0x14 => KeyCode::Digit3, + 0x15 => KeyCode::Digit4, + 0x16 => KeyCode::Digit6, + 0x17 => KeyCode::Digit5, + 0x18 => KeyCode::Equal, + 0x19 => KeyCode::Digit9, + 0x1a => KeyCode::Digit7, + 0x1b => KeyCode::Minus, + 0x1c => KeyCode::Digit8, + 0x1d => KeyCode::Digit0, + 0x1e => KeyCode::BracketRight, + 0x1f => KeyCode::KeyO, + 0x20 => KeyCode::KeyU, + 0x21 => KeyCode::BracketLeft, + 0x22 => KeyCode::KeyI, + 0x23 => KeyCode::KeyP, + 0x24 => KeyCode::Enter, + 0x25 => KeyCode::KeyL, + 0x26 => KeyCode::KeyJ, + 0x27 => KeyCode::Quote, + 0x28 => KeyCode::KeyK, + 0x29 => KeyCode::Semicolon, + 0x2a => KeyCode::Backslash, + 0x2b => KeyCode::Comma, + 0x2c => KeyCode::Slash, + 0x2d => KeyCode::KeyN, + 0x2e => KeyCode::KeyM, + 0x2f => KeyCode::Period, + 0x30 => KeyCode::Tab, + 0x31 => KeyCode::Space, + 0x32 => KeyCode::Backquote, + 0x33 => KeyCode::Backspace, + //0x34 => unknown, + 0x35 => KeyCode::Escape, + 0x36 => KeyCode::SuperRight, + 0x37 => KeyCode::SuperLeft, + 0x38 => KeyCode::ShiftLeft, + 0x39 => KeyCode::CapsLock, + 0x3a => KeyCode::AltLeft, + 0x3b => KeyCode::ControlLeft, + 0x3c => KeyCode::ShiftRight, + 0x3d => KeyCode::AltRight, + 0x3e => KeyCode::ControlRight, + 0x3f => KeyCode::Fn, + 0x40 => KeyCode::F17, + 0x41 => KeyCode::NumpadDecimal, + //0x42 -> unknown, + 0x43 => KeyCode::NumpadMultiply, + //0x44 => unknown, + 0x45 => KeyCode::NumpadAdd, + //0x46 => unknown, + 0x47 => KeyCode::NumLock, + //0x48 => KeyCode::NumpadClear, + + // TODO: (Artur) for me, kVK_VolumeUp is 0x48 + // macOS 10.11 + // /System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/Versions/A/Headers/Events.h + 0x49 => KeyCode::AudioVolumeUp, + 0x4a => KeyCode::AudioVolumeDown, + 0x4b => KeyCode::NumpadDivide, + 0x4c => KeyCode::NumpadEnter, + //0x4d => unknown, + 0x4e => KeyCode::NumpadSubtract, + 0x4f => KeyCode::F18, + 0x50 => KeyCode::F19, + 0x51 => KeyCode::NumpadEqual, + 0x52 => KeyCode::Numpad0, + 0x53 => KeyCode::Numpad1, + 0x54 => KeyCode::Numpad2, + 0x55 => KeyCode::Numpad3, + 0x56 => KeyCode::Numpad4, + 0x57 => KeyCode::Numpad5, + 0x58 => KeyCode::Numpad6, + 0x59 => KeyCode::Numpad7, + 0x5a => KeyCode::F20, + 0x5b => KeyCode::Numpad8, + 0x5c => KeyCode::Numpad9, + 0x5d => KeyCode::IntlYen, + //0x5e => JIS Ro, + //0x5f => unknown, + 0x60 => KeyCode::F5, + 0x61 => KeyCode::F6, + 0x62 => KeyCode::F7, + 0x63 => KeyCode::F3, + 0x64 => KeyCode::F8, + 0x65 => KeyCode::F9, + //0x66 => JIS Eisuu (macOS), + 0x67 => KeyCode::F11, + //0x68 => JIS Kanna (macOS), + 0x69 => KeyCode::F13, + 0x6a => KeyCode::F16, + 0x6b => KeyCode::F14, + //0x6c => unknown, + 0x6d => KeyCode::F10, + //0x6e => unknown, + 0x6f => KeyCode::F12, + //0x70 => unknown, + 0x71 => KeyCode::F15, + 0x72 => KeyCode::Insert, + 0x73 => KeyCode::Home, + 0x74 => KeyCode::PageUp, + 0x75 => KeyCode::Delete, + 0x76 => KeyCode::F4, + 0x77 => KeyCode::End, + 0x78 => KeyCode::F2, + 0x79 => KeyCode::PageDown, + 0x7a => KeyCode::F1, + 0x7b => KeyCode::ArrowLeft, + 0x7c => KeyCode::ArrowRight, + 0x7d => KeyCode::ArrowDown, + 0x7e => KeyCode::ArrowUp, + //0x7f => unknown, + + // 0xA is the caret (^) an macOS's German QERTZ layout. This key is at the same location as + // backquote (`) on Windows' US layout. + 0xa => KeyCode::Backquote, + _ => KeyCode::Unidentified(NativeKeyCode::MacOS(scancode as u16)), + } + } +} diff --git a/src/platform_impl/ios/mod.rs b/src/platform_impl/ios/mod.rs index d94681547d9..3f0a22213e9 100644 --- a/src/platform_impl/ios/mod.rs +++ b/src/platform_impl/ios/mod.rs @@ -95,7 +95,6 @@ pub(self) use crate::platform_impl::Fullscreen; pub struct DeviceId { uiscreen: *const UIScreen, } - impl DeviceId { pub const unsafe fn dummy() -> Self { DeviceId { @@ -103,7 +102,6 @@ impl DeviceId { } } } - unsafe impl Send for DeviceId {} unsafe impl Sync for DeviceId {} diff --git a/src/platform_impl/macos/app_state.rs b/src/platform_impl/macos/app_state.rs index 1a85b40f63f..2187e2669b6 100644 --- a/src/platform_impl/macos/app_state.rs +++ b/src/platform_impl/macos/app_state.rs @@ -39,7 +39,7 @@ impl<'a, Never> Event<'a, Never> { self.map_nonuser_event() // `Never` can't be constructed, so the `UserEvent` variant can't // be present here. - .unwrap_or_else(|_| unreachable!()) + .unwrap_or_else(|| unreachable!()) } } diff --git a/src/platform_impl/macos/event.rs b/src/platform_impl/macos/event.rs index 2c5fe5a0c44..4eb695825d3 100644 --- a/src/platform_impl/macos/event.rs +++ b/src/platform_impl/macos/event.rs @@ -1,15 +1,31 @@ -use std::os::raw::c_ushort; +use std::{collections::HashSet, ffi::c_void, sync::Mutex}; +use core_foundation::{base::CFRelease, data::CFDataGetBytePtr}; use objc2::rc::{Id, Shared}; +use once_cell::sync::Lazy; use super::appkit::{NSEvent, NSEventModifierFlags}; use super::window::WinitWindow; use crate::{ dpi::LogicalSize, - event::{ElementState, Event, KeyboardInput, ModifiersState, VirtualKeyCode, WindowEvent}, - platform_impl::platform::{util::Never, DEVICE_ID}, + event::{ElementState, Event, KeyEvent}, + keyboard::{Key, KeyCode, KeyLocation, ModifiersState, NativeKeyCode}, + platform::{modifier_supplement::KeyEventExtModifierSupplement, scancode::KeyCodeExtScancode}, + platform_impl::platform::{ffi, util::Never}, }; +static KEY_STRINGS: Lazy>> = Lazy::new(|| Mutex::new(HashSet::new())); + +fn insert_or_get_key_str(string: String) -> &'static str { + let mut string_set = KEY_STRINGS.lock().unwrap(); + if let Some(contained) = string_set.get(string.as_str()) { + return contained; + } + let static_str = Box::leak(string.into_boxed_str()); + string_set.insert(static_str); + static_str +} + #[derive(Debug)] pub(crate) enum EventWrapper { StaticEvent(Event<'static, Never>), @@ -25,216 +41,283 @@ pub(crate) enum EventProxy { }, } -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, - }) +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct KeyEventExtra { + pub text_with_all_modifiers: Option<&'static str>, + pub key_without_modifiers: Key<'static>, +} + +impl KeyEventExtModifierSupplement for KeyEvent { + fn text_with_all_modifiers(&self) -> Option<&str> { + self.platform_specific.text_with_all_modifiers + } + + fn key_without_modifiers(&self) -> Key<'static> { + self.platform_specific.key_without_modifiers.clone() + } +} + +pub fn get_modifierless_char(scancode: u16) -> Key<'static> { + let mut string = [0; 16]; + let input_source; + let layout; + unsafe { + input_source = ffi::TISCopyCurrentKeyboardLayoutInputSource(); + if input_source.is_null() { + log::error!("`TISCopyCurrentKeyboardLayoutInputSource` returned null ptr"); + return Key::Unidentified(NativeKeyCode::MacOS(scancode)); + } + let layout_data = + ffi::TISGetInputSourceProperty(input_source, ffi::kTISPropertyUnicodeKeyLayoutData); + if layout_data.is_null() { + CFRelease(input_source as *mut c_void); + log::error!("`TISGetInputSourceProperty` returned null ptr"); + return Key::Unidentified(NativeKeyCode::MacOS(scancode)); + } + layout = CFDataGetBytePtr(layout_data) as *const ffi::UCKeyboardLayout; + } + let keyboard_type = unsafe { ffi::LMGetKbdType() }; + + let mut result_len = 0; + let mut dead_keys = 0; + let modifiers = 0; + let translate_result = unsafe { + ffi::UCKeyTranslate( + layout, + scancode, + ffi::kUCKeyActionDisplay, + modifiers, + keyboard_type as u32, + ffi::kUCKeyTranslateNoDeadKeysMask, + &mut dead_keys, + string.len() as ffi::UniCharCount, + &mut result_len, + string.as_mut_ptr(), + ) + }; + unsafe { + CFRelease(input_source as *mut c_void); + } + if translate_result != 0 { + log::error!( + "`UCKeyTranslate` returned with the non-zero value: {}", + translate_result + ); + return Key::Unidentified(NativeKeyCode::MacOS(scancode)); + } + if result_len == 0 { + log::error!("`UCKeyTranslate` was succesful but gave a string of 0 length."); + return Key::Unidentified(NativeKeyCode::MacOS(scancode)); + } + let chars = String::from_utf16_lossy(&string[0..result_len as usize]); + Key::Character(insert_or_get_key_str(chars)) +} + +fn get_logical_key_char(ns_event: &NSEvent, modifierless_chars: &str) -> Key<'static> { + let string = ns_event + .charactersIgnoringModifiers() + .map(|s| s.to_string()) + .unwrap_or_else(String::new); + if string.is_empty() { + // Probably a dead key + let first_char = modifierless_chars.chars().next(); + return Key::Dead(first_char); + } + Key::Character(insert_or_get_key_str(string)) +} + +pub(crate) fn create_key_event( + ns_event: &NSEvent, + is_press: bool, + is_repeat: bool, + in_ime: bool, + key_override: Option, +) -> KeyEvent { + use ElementState::{Pressed, Released}; + let state = if is_press { Pressed } else { Released }; + + let scancode = ns_event.scancode(); + let mut physical_key = key_override + .clone() + .unwrap_or_else(|| KeyCode::from_scancode(scancode as u32)); + + let text_with_all_modifiers: Option<&'static str> = { + if key_override.is_some() { + None + } else { + let characters = ns_event + .characters() + .map(|s| s.to_string()) + .unwrap_or_else(String::new); + if characters.is_empty() { + None + } else { + if matches!(physical_key, KeyCode::Unidentified(_)) { + // The key may be one of the funky function keys + physical_key = extra_function_key_to_code(scancode, &characters); + } + Some(insert_or_get_key_str(characters)) + } + } + }; + let key_from_code = code_to_key(physical_key.clone(), scancode); + let logical_key; + let key_without_modifiers; + if !matches!(key_from_code, Key::Unidentified(_)) { + logical_key = key_from_code.clone(); + key_without_modifiers = key_from_code.clone(); + } else { + //println!("Couldn't get key from code: {:?}", physical_key); + key_without_modifiers = get_modifierless_char(scancode); + + let modifiers = NSEvent::modifierFlags(ns_event); + let has_ctrl = modifiers.contains(NSEventModifierFlags::NSControlKeyMask); + + match text_with_all_modifiers { + Some(text) if !has_ctrl => { + // Only checking for ctrl here, not checking for alt because we DO want to + // include its effect in the key. For example if -on the Germay layout- one + // presses alt+8, the logical key should be "{" + // Also not checking if this is a release event because then this issue would + // still affect the key release. + logical_key = Key::Character(text); + } + _ => { + let modifierless_chars = match key_without_modifiers { + Key::Character(ch) => ch, + _ => "", + }; + logical_key = get_logical_key_char(ns_event, modifierless_chars); + } + } + } + let text = if in_ime || !is_press { + None + } else { + logical_key.to_text() + }; + let location = code_to_location(physical_key.clone()); + KeyEvent { + location, + logical_key, + physical_key, + repeat: is_repeat, + state, + text, + platform_specific: KeyEventExtra { + key_without_modifiers, + text_with_all_modifiers, + }, + } +} + +pub fn code_to_key(code: KeyCode, scancode: u16) -> Key<'static> { + match code { + KeyCode::Enter => Key::Enter, + KeyCode::Tab => Key::Tab, + KeyCode::Space => Key::Space, + KeyCode::Backspace => Key::Backspace, + KeyCode::Escape => Key::Escape, + KeyCode::SuperRight => Key::Super, + KeyCode::SuperLeft => Key::Super, + KeyCode::ShiftLeft => Key::Shift, + KeyCode::AltLeft => Key::Alt, + KeyCode::ControlLeft => Key::Control, + KeyCode::ShiftRight => Key::Shift, + KeyCode::AltRight => Key::Alt, + KeyCode::ControlRight => Key::Control, + + KeyCode::NumLock => Key::NumLock, + KeyCode::AudioVolumeUp => Key::AudioVolumeUp, + KeyCode::AudioVolumeDown => Key::AudioVolumeDown, + + // Other numpad keys all generate text on macOS (if I understand correctly) + KeyCode::NumpadEnter => Key::Enter, + + KeyCode::F1 => Key::F1, + KeyCode::F2 => Key::F2, + KeyCode::F3 => Key::F3, + KeyCode::F4 => Key::F4, + KeyCode::F5 => Key::F5, + KeyCode::F6 => Key::F6, + KeyCode::F7 => Key::F7, + KeyCode::F8 => Key::F8, + KeyCode::F9 => Key::F9, + KeyCode::F10 => Key::F10, + KeyCode::F11 => Key::F11, + KeyCode::F12 => Key::F12, + KeyCode::F13 => Key::F13, + KeyCode::F14 => Key::F14, + KeyCode::F15 => Key::F15, + KeyCode::F16 => Key::F16, + KeyCode::F17 => Key::F17, + KeyCode::F18 => Key::F18, + KeyCode::F19 => Key::F19, + KeyCode::F20 => Key::F20, + + KeyCode::Insert => Key::Insert, + KeyCode::Home => Key::Home, + KeyCode::PageUp => Key::PageUp, + KeyCode::Delete => Key::Delete, + KeyCode::End => Key::End, + KeyCode::PageDown => Key::PageDown, + KeyCode::ArrowLeft => Key::ArrowLeft, + KeyCode::ArrowRight => Key::ArrowRight, + KeyCode::ArrowDown => Key::ArrowDown, + KeyCode::ArrowUp => Key::ArrowUp, + _ => Key::Unidentified(NativeKeyCode::MacOS(scancode)), + } } -pub fn scancode_to_keycode(scancode: c_ushort) -> Option { - Some(match scancode { - 0x00 => VirtualKeyCode::A, - 0x01 => VirtualKeyCode::S, - 0x02 => VirtualKeyCode::D, - 0x03 => VirtualKeyCode::F, - 0x04 => VirtualKeyCode::H, - 0x05 => VirtualKeyCode::G, - 0x06 => VirtualKeyCode::Z, - 0x07 => VirtualKeyCode::X, - 0x08 => VirtualKeyCode::C, - 0x09 => VirtualKeyCode::V, - //0x0a => World 1, - 0x0b => VirtualKeyCode::B, - 0x0c => VirtualKeyCode::Q, - 0x0d => VirtualKeyCode::W, - 0x0e => VirtualKeyCode::E, - 0x0f => VirtualKeyCode::R, - 0x10 => VirtualKeyCode::Y, - 0x11 => VirtualKeyCode::T, - 0x12 => VirtualKeyCode::Key1, - 0x13 => VirtualKeyCode::Key2, - 0x14 => VirtualKeyCode::Key3, - 0x15 => VirtualKeyCode::Key4, - 0x16 => VirtualKeyCode::Key6, - 0x17 => VirtualKeyCode::Key5, - 0x18 => VirtualKeyCode::Equals, - 0x19 => VirtualKeyCode::Key9, - 0x1a => VirtualKeyCode::Key7, - 0x1b => VirtualKeyCode::Minus, - 0x1c => VirtualKeyCode::Key8, - 0x1d => VirtualKeyCode::Key0, - 0x1e => VirtualKeyCode::RBracket, - 0x1f => VirtualKeyCode::O, - 0x20 => VirtualKeyCode::U, - 0x21 => VirtualKeyCode::LBracket, - 0x22 => VirtualKeyCode::I, - 0x23 => VirtualKeyCode::P, - 0x24 => VirtualKeyCode::Return, - 0x25 => VirtualKeyCode::L, - 0x26 => VirtualKeyCode::J, - 0x27 => VirtualKeyCode::Apostrophe, - 0x28 => VirtualKeyCode::K, - 0x29 => VirtualKeyCode::Semicolon, - 0x2a => VirtualKeyCode::Backslash, - 0x2b => VirtualKeyCode::Comma, - 0x2c => VirtualKeyCode::Slash, - 0x2d => VirtualKeyCode::N, - 0x2e => VirtualKeyCode::M, - 0x2f => VirtualKeyCode::Period, - 0x30 => VirtualKeyCode::Tab, - 0x31 => VirtualKeyCode::Space, - 0x32 => VirtualKeyCode::Grave, - 0x33 => VirtualKeyCode::Back, - //0x34 => unkown, - 0x35 => VirtualKeyCode::Escape, - 0x36 => VirtualKeyCode::RWin, - 0x37 => VirtualKeyCode::LWin, - 0x38 => VirtualKeyCode::LShift, - //0x39 => Caps lock, - 0x3a => VirtualKeyCode::LAlt, - 0x3b => VirtualKeyCode::LControl, - 0x3c => VirtualKeyCode::RShift, - 0x3d => VirtualKeyCode::RAlt, - 0x3e => VirtualKeyCode::RControl, - //0x3f => Fn key, - 0x40 => VirtualKeyCode::F17, - 0x41 => VirtualKeyCode::NumpadDecimal, - //0x42 -> unkown, - 0x43 => VirtualKeyCode::NumpadMultiply, - //0x44 => unkown, - 0x45 => VirtualKeyCode::NumpadAdd, - //0x46 => unkown, - 0x47 => VirtualKeyCode::Numlock, - //0x48 => KeypadClear, - 0x49 => VirtualKeyCode::VolumeUp, - 0x4a => VirtualKeyCode::VolumeDown, - 0x4b => VirtualKeyCode::NumpadDivide, - 0x4c => VirtualKeyCode::NumpadEnter, - //0x4d => unkown, - 0x4e => VirtualKeyCode::NumpadSubtract, - 0x4f => VirtualKeyCode::F18, - 0x50 => VirtualKeyCode::F19, - 0x51 => VirtualKeyCode::NumpadEquals, - 0x52 => VirtualKeyCode::Numpad0, - 0x53 => VirtualKeyCode::Numpad1, - 0x54 => VirtualKeyCode::Numpad2, - 0x55 => VirtualKeyCode::Numpad3, - 0x56 => VirtualKeyCode::Numpad4, - 0x57 => VirtualKeyCode::Numpad5, - 0x58 => VirtualKeyCode::Numpad6, - 0x59 => VirtualKeyCode::Numpad7, - 0x5a => VirtualKeyCode::F20, - 0x5b => VirtualKeyCode::Numpad8, - 0x5c => VirtualKeyCode::Numpad9, - 0x5d => VirtualKeyCode::Yen, - //0x5e => JIS Ro, - //0x5f => unkown, - 0x60 => VirtualKeyCode::F5, - 0x61 => VirtualKeyCode::F6, - 0x62 => VirtualKeyCode::F7, - 0x63 => VirtualKeyCode::F3, - 0x64 => VirtualKeyCode::F8, - 0x65 => VirtualKeyCode::F9, - //0x66 => JIS Eisuu (macOS), - 0x67 => VirtualKeyCode::F11, - //0x68 => JIS Kanna (macOS), - 0x69 => VirtualKeyCode::F13, - 0x6a => VirtualKeyCode::F16, - 0x6b => VirtualKeyCode::F14, - //0x6c => unkown, - 0x6d => VirtualKeyCode::F10, - //0x6e => unkown, - 0x6f => VirtualKeyCode::F12, - //0x70 => unkown, - 0x71 => VirtualKeyCode::F15, - 0x72 => VirtualKeyCode::Insert, - 0x73 => VirtualKeyCode::Home, - 0x74 => VirtualKeyCode::PageUp, - 0x75 => VirtualKeyCode::Delete, - 0x76 => VirtualKeyCode::F4, - 0x77 => VirtualKeyCode::End, - 0x78 => VirtualKeyCode::F2, - 0x79 => VirtualKeyCode::PageDown, - 0x7a => VirtualKeyCode::F1, - 0x7b => VirtualKeyCode::Left, - 0x7c => VirtualKeyCode::Right, - 0x7d => VirtualKeyCode::Down, - 0x7e => VirtualKeyCode::Up, - //0x7f => unkown, - 0xa => VirtualKeyCode::Caret, - _ => return None, - }) +pub fn code_to_location(code: KeyCode) -> KeyLocation { + match code { + KeyCode::SuperRight => KeyLocation::Right, + KeyCode::SuperLeft => KeyLocation::Left, + KeyCode::ShiftLeft => KeyLocation::Left, + KeyCode::AltLeft => KeyLocation::Left, + KeyCode::ControlLeft => KeyLocation::Left, + KeyCode::ShiftRight => KeyLocation::Right, + KeyCode::AltRight => KeyLocation::Right, + KeyCode::ControlRight => KeyLocation::Right, + + KeyCode::NumLock => KeyLocation::Numpad, + KeyCode::NumpadDecimal => KeyLocation::Numpad, + KeyCode::NumpadMultiply => KeyLocation::Numpad, + KeyCode::NumpadAdd => KeyLocation::Numpad, + KeyCode::NumpadDivide => KeyLocation::Numpad, + KeyCode::NumpadEnter => KeyLocation::Numpad, + KeyCode::NumpadSubtract => KeyLocation::Numpad, + KeyCode::NumpadEqual => KeyLocation::Numpad, + KeyCode::Numpad0 => KeyLocation::Numpad, + KeyCode::Numpad1 => KeyLocation::Numpad, + KeyCode::Numpad2 => KeyLocation::Numpad, + KeyCode::Numpad3 => KeyLocation::Numpad, + KeyCode::Numpad4 => KeyLocation::Numpad, + KeyCode::Numpad5 => KeyLocation::Numpad, + KeyCode::Numpad6 => KeyLocation::Numpad, + KeyCode::Numpad7 => KeyLocation::Numpad, + KeyCode::Numpad8 => KeyLocation::Numpad, + KeyCode::Numpad9 => KeyLocation::Numpad, + + _ => KeyLocation::Standard, + } } // 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: &str) -> Option { +pub fn extra_function_key_to_code(scancode: u16, string: &str) -> KeyCode { 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, - }); + match ch { + 0xf718 => KeyCode::F21, + 0xf719 => KeyCode::F22, + 0xf71a => KeyCode::F23, + 0xf71b => KeyCode::F24, + _ => KeyCode::Unidentified(NativeKeyCode::MacOS(scancode)), + } + } else { + KeyCode::Unidentified(NativeKeyCode::MacOS(scancode)) } - - None } pub(super) fn event_mods(event: &NSEvent) -> ModifiersState { @@ -245,7 +328,7 @@ pub(super) fn event_mods(event: &NSEvent) -> ModifiersState { flags.contains(NSEventModifierFlags::NSShiftKeyMask), ); m.set( - ModifiersState::CTRL, + ModifiersState::CONTROL, flags.contains(NSEventModifierFlags::NSControlKeyMask), ); m.set( @@ -253,40 +336,8 @@ pub(super) fn event_mods(event: &NSEvent) -> ModifiersState { flags.contains(NSEventModifierFlags::NSAlternateKeyMask), ); m.set( - ModifiersState::LOGO, + ModifiersState::SUPER, flags.contains(NSEventModifierFlags::NSCommandKeyMask), ); m } - -pub(super) fn modifier_event( - event: &NSEvent, - keymask: NSEventModifierFlags, - was_key_pressed: bool, -) -> Option> { - if !was_key_pressed && event.modifierFlags().contains(keymask) - || was_key_pressed && !event.modifierFlags().contains(keymask) - { - let state = if was_key_pressed { - ElementState::Released - } else { - ElementState::Pressed - }; - - let scancode = event.scancode(); - let virtual_keycode = scancode_to_keycode(scancode); - #[allow(deprecated)] - Some(WindowEvent::KeyboardInput { - device_id: DEVICE_ID, - input: KeyboardInput { - state, - scancode: scancode as _, - virtual_keycode, - modifiers: event_mods(event), - }, - is_synthetic: false, - }) - } else { - None - } -} diff --git a/src/platform_impl/macos/ffi.rs b/src/platform_impl/macos/ffi.rs index f0803310580..a1e0e624257 100644 --- a/src/platform_impl/macos/ffi.rs +++ b/src/platform_impl/macos/ffi.rs @@ -5,7 +5,8 @@ use std::ffi::c_void; use core_foundation::{ - array::CFArrayRef, dictionary::CFDictionaryRef, string::CFStringRef, uuid::CFUUIDRef, + array::CFArrayRef, data::CFDataRef, dictionary::CFDictionaryRef, string::CFStringRef, + uuid::CFUUIDRef, }; use core_graphics::{ base::CGError, @@ -156,3 +157,48 @@ mod core_video { } pub use core_video::*; +#[repr(transparent)] +pub struct TISInputSource(std::ffi::c_void); +pub type TISInputSourceRef = *mut TISInputSource; + +#[repr(transparent)] +pub struct UCKeyboardLayout(std::ffi::c_void); + +pub type OptionBits = u32; +pub type UniCharCount = std::os::raw::c_ulong; +pub type UniChar = u16; +pub type OSStatus = i32; + +#[allow(non_upper_case_globals)] +pub const kUCKeyActionDisplay: u16 = 3; +#[allow(non_upper_case_globals)] +pub const kUCKeyTranslateNoDeadKeysMask: OptionBits = 1; + +#[link(name = "Carbon", kind = "framework")] +extern "C" { + pub static kTISPropertyUnicodeKeyLayoutData: CFStringRef; + + #[allow(non_snake_case)] + pub fn TISGetInputSourceProperty( + inputSource: TISInputSourceRef, + propertyKey: CFStringRef, + ) -> CFDataRef; + + pub fn TISCopyCurrentKeyboardLayoutInputSource() -> TISInputSourceRef; + + pub fn LMGetKbdType() -> u8; + + #[allow(non_snake_case)] + pub fn UCKeyTranslate( + keyLayoutPtr: *const UCKeyboardLayout, + virtualKeyCode: u16, + keyAction: u16, + modifierKeyState: u32, + keyboardType: u32, + keyTranslateOptions: OptionBits, + deadKeyState: *mut u32, + maxStringLength: UniCharCount, + actualStringLength: *mut UniCharCount, + unicodeString: *mut UniChar, + ) -> OSStatus; +} diff --git a/src/platform_impl/macos/mod.rs b/src/platform_impl/macos/mod.rs index 80bf89522e0..4559e0c9fb7 100644 --- a/src/platform_impl/macos/mod.rs +++ b/src/platform_impl/macos/mod.rs @@ -22,6 +22,7 @@ use std::{fmt, ops::Deref}; use self::window::WinitWindow; use self::window_delegate::WinitWindowDelegate; pub(crate) use self::{ + event::KeyEventExtra, event_loop::{ EventLoop, EventLoopProxy, EventLoopWindowTarget, PlatformSpecificEventLoopAttributes, }, diff --git a/src/platform_impl/macos/util/mod.rs b/src/platform_impl/macos/util/mod.rs index 2cfdc47e849..e583ba36552 100644 --- a/src/platform_impl/macos/util/mod.rs +++ b/src/platform_impl/macos/util/mod.rs @@ -7,7 +7,10 @@ pub(crate) use self::r#async::*; use core_graphics::display::CGDisplay; use objc2::foundation::{CGFloat, NSNotFound, NSPoint, NSRange, NSRect, NSUInteger}; -use crate::dpi::LogicalPosition; +use crate::{ + dpi::LogicalPosition, + keyboard::{Key, ModifiersState}, +}; // Replace with `!` once stable #[derive(Debug)] @@ -63,3 +66,13 @@ pub fn window_position(position: LogicalPosition) -> NSPoint { CGDisplay::main().pixels_high() as CGFloat - position.y as CGFloat, ) } + +pub fn key_to_modifier(key: &Key<'static>) -> ModifiersState { + match key { + Key::Alt => ModifiersState::ALT, + Key::Control => ModifiersState::CONTROL, + Key::Super => ModifiersState::SUPER, + Key::Shift => ModifiersState::SHIFT, + _ => unreachable!(), + } +} diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index c51f049f1c7..be925d75cfe 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -1,6 +1,12 @@ #![allow(clippy::unnecessary_cast)] -use std::{boxed::Box, os::raw::*, ptr, str, sync::Mutex}; +use std::{ + boxed::Box, + collections::{HashMap, VecDeque}, + os::raw::*, + ptr, str, + sync::Mutex, +}; use objc2::declare::{Ivar, IvarDrop}; use objc2::foundation::{ @@ -11,22 +17,22 @@ use objc2::rc::{Id, Owned, Shared}; use objc2::runtime::{Object, Sel}; use objc2::{class, declare_class, msg_send, msg_send_id, sel, ClassType}; -use super::appkit::{ - NSApp, NSCursor, NSEvent, NSEventModifierFlags, NSEventPhase, NSResponder, NSTrackingRectTag, - NSView, +use super::{ + appkit::{NSApp, NSCursor, NSEvent, NSEventPhase, NSResponder, NSTrackingRectTag, NSView}, + event::{code_to_key, code_to_location}, + util::key_to_modifier, }; use crate::{ dpi::{LogicalPosition, LogicalSize}, event::{ - DeviceEvent, ElementState, Event, Ime, KeyboardInput, ModifiersState, MouseButton, - MouseScrollDelta, TouchPhase, VirtualKeyCode, WindowEvent, + DeviceEvent, ElementState, Event, Ime, MouseButton, MouseScrollDelta, TouchPhase, + WindowEvent, }, + keyboard::{Key, KeyCode, KeyLocation, ModifiersState}, + platform::scancode::KeyCodeExtScancode, platform_impl::platform::{ app_state::AppState, - event::{ - char_to_keycode, check_function_keys, event_mods, modifier_event, scancode_to_keycode, - EventWrapper, - }, + event::{create_key_event, event_mods, EventWrapper}, util, window::WinitWindow, DEVICE_ID, @@ -64,12 +70,50 @@ enum ImeState { Commited, } +bitflags! { + struct ModLocationMask: u8 { + const LEFT = 1; + const RIGHT = 2; + } +} +impl ModLocationMask { + fn from_location(loc: KeyLocation) -> ModLocationMask { + match loc { + KeyLocation::Left => ModLocationMask::LEFT, + KeyLocation::Right => ModLocationMask::RIGHT, + _ => unreachable!(), + } + } +} + +fn get_right_modifier_code(key: &Key<'static>) -> KeyCode { + match key { + Key::Alt => KeyCode::AltRight, + Key::Control => KeyCode::ControlRight, + Key::Shift => KeyCode::ShiftRight, + Key::Super => KeyCode::SuperRight, + _ => unreachable!(), + } +} + +fn get_left_modifier_code(key: &Key<'static>) -> KeyCode { + match key { + Key::Alt => KeyCode::AltLeft, + Key::Control => KeyCode::ControlLeft, + Key::Shift => KeyCode::ShiftLeft, + Key::Super => KeyCode::SuperLeft, + _ => unreachable!(), + } +} + #[derive(Debug)] pub(super) struct ViewState { pub cursor_state: Mutex, ime_position: LogicalPosition, pub(super) modifiers: ModifiersState, + phys_modifiers: HashMap, ModLocationMask>, tracking_rect: Option, + // phys_modifiers: HashSet, ime_state: ImeState, input_source: String, @@ -83,54 +127,6 @@ pub(super) struct ViewState { forward_key_to_app: bool, } -fn get_characters(event: &NSEvent, ignore_modifiers: bool) -> String { - if ignore_modifiers { - event.charactersIgnoringModifiers() - } else { - event.characters() - } - .expect("expected characters to be non-null") - .to_string() -} - -// As defined in: https://www.unicode.org/Public/MAPPINGS/VENDORS/APPLE/CORPCHAR.TXT -fn is_corporate_character(c: char) -> bool { - matches!(c, - '\u{F700}'..='\u{F747}' - | '\u{F802}'..='\u{F84F}' - | '\u{F850}' - | '\u{F85C}' - | '\u{F85D}' - | '\u{F85F}' - | '\u{F860}'..='\u{F86B}' - | '\u{F870}'..='\u{F8FF}' - ) -} - -// Retrieves a layout-independent keycode given an event. -fn retrieve_keycode(event: &NSEvent) -> Option { - #[inline] - fn get_code(ev: &NSEvent, raw: bool) -> Option { - let characters = get_characters(ev, raw); - characters.chars().next().and_then(char_to_keycode) - } - - // 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 = event.scancode(); - scancode_to_keycode(scancode).or_else(|| check_function_keys(&get_characters(event, true))) - }) -} - declare_class!( #[derive(Debug)] #[allow(non_snake_case)] @@ -159,6 +155,7 @@ declare_class!( cursor_state: Default::default(), ime_position: LogicalPosition::new(0.0, 0.0), modifiers: Default::default(), + phys_modifiers: Default::default(), tracking_rect: None, ime_state: ImeState::Disabled, input_source: String::new(), @@ -463,7 +460,9 @@ declare_class!( } let was_in_preedit = self.state.ime_state == ImeState::Preedit; - let characters = get_characters(event, false); + // let characters = get_characters(event, false); + let is_repeat: bool = unsafe { msg_send![event, isARepeat] }; + self.state.forward_key_to_app = false; // The `interpretKeyEvents` function might call @@ -485,51 +484,37 @@ declare_class!( } let now_in_preedit = self.state.ime_state == ImeState::Preedit; - - let scancode = event.scancode() as u32; - let virtual_keycode = retrieve_keycode(event); - - self.update_potentially_stale_modifiers(event); - + self.update_modifiers(event, false); let ime_related = was_in_preedit || now_in_preedit || text_commited; if !ime_related || self.state.forward_key_to_app || !self.state.ime_allowed { - #[allow(deprecated)] - self.queue_event(WindowEvent::KeyboardInput { - device_id: DEVICE_ID, - input: KeyboardInput { - state: ElementState::Pressed, - scancode, - virtual_keycode, - modifiers: event_mods(event), + let in_ime = self.is_ime_enabled(); + let key_event = create_key_event(event, true, is_repeat, in_ime, None); + self.queue_event(Event::WindowEvent { + window_id, + event: WindowEvent::KeyboardInput { + device_id: DEVICE_ID, + event: key_event, + is_synthetic: false, }, - is_synthetic: false, }); - - for character in characters.chars().filter(|c| !is_corporate_character(*c)) { - self.queue_event(WindowEvent::ReceivedCharacter(character)); - } } } #[sel(keyUp:)] fn key_up(&mut self, event: &NSEvent) { trace_scope!("keyUp:"); - let scancode = event.scancode() as u32; - let virtual_keycode = retrieve_keycode(event); - self.update_potentially_stale_modifiers(event); + self.update_modifiers(event, false); // We want to send keyboard input when we are not currently in preedit if self.state.ime_state != ImeState::Preedit { - #[allow(deprecated)] - self.queue_event(WindowEvent::KeyboardInput { - device_id: DEVICE_ID, - input: KeyboardInput { - state: ElementState::Released, - scancode, - virtual_keycode, - modifiers: event_mods(event), + self.queue_event(Event::WindowEvent { + window_id: self.window_id(), + event: WindowEvent::KeyboardInput { + device_id: DEVICE_ID, + event: create_key_event(event, false, false, false, None), + is_synthetic: false, }, is_synthetic: false, }); @@ -537,46 +522,10 @@ declare_class!( } #[sel(flagsChanged:)] - fn flags_changed(&mut self, event: &NSEvent) { + fn flags_changed(&mut self, ns_event: &NSEvent) { trace_scope!("flagsChanged:"); - if let Some(window_event) = modifier_event( - event, - NSEventModifierFlags::NSShiftKeyMask, - self.state.modifiers.shift(), - ) { - self.state.modifiers.toggle(ModifiersState::SHIFT); - self.queue_event(window_event); - } - - if let Some(window_event) = modifier_event( - event, - NSEventModifierFlags::NSControlKeyMask, - self.state.modifiers.ctrl(), - ) { - self.state.modifiers.toggle(ModifiersState::CTRL); - self.queue_event(window_event); - } - - if let Some(window_event) = modifier_event( - event, - NSEventModifierFlags::NSCommandKeyMask, - self.state.modifiers.logo(), - ) { - self.state.modifiers.toggle(ModifiersState::LOGO); - self.queue_event(window_event); - } - - if let Some(window_event) = modifier_event( - event, - NSEventModifierFlags::NSAlternateKeyMask, - self.state.modifiers.alt(), - ) { - self.state.modifiers.toggle(ModifiersState::ALT); - self.queue_event(window_event); - } - - self.queue_event(WindowEvent::ModifiersChanged(self.state.modifiers)); + self.update_modifiers(ns_event, true); } #[sel(insertTab:)] @@ -606,24 +555,21 @@ declare_class!( #[sel(cancelOperation:)] fn cancel_operation(&mut self, _sender: *const Object) { trace_scope!("cancelOperation:"); - let scancode = 0x2f; - let virtual_keycode = scancode_to_keycode(scancode); - debug_assert_eq!(virtual_keycode, Some(VirtualKeyCode::Period)); let event = NSApp() .currentEvent() .expect("could not find current event"); - self.update_potentially_stale_modifiers(&event); + self.update_modifiers(&event, false); + let is_repeat = unsafe { msg_send![&event, isARepeat] }; + let event = create_key_event(&event, true, is_repeat, false, None); - #[allow(deprecated)] - self.queue_event(WindowEvent::KeyboardInput { - device_id: DEVICE_ID, - input: KeyboardInput { - state: ElementState::Pressed, - scancode: scancode as _, - virtual_keycode, - modifiers: event_mods(&event), + self.queue_event(Event::WindowEvent { + window_id: self.window_id(), + event: WindowEvent::KeyboardInput { + device_id: DEVICE_ID, + event, + is_synthetic: false, }, is_synthetic: false, }); @@ -748,7 +694,7 @@ declare_class!( }, }; - self.update_potentially_stale_modifiers(event); + self.update_modifiers(event, false); self.queue_device_event(DeviceEvent::MouseWheel { delta }); self.queue_event(WindowEvent::MouseWheel { @@ -917,19 +863,119 @@ impl WinitView { } // Update `state.modifiers` if `event` has something different - fn update_potentially_stale_modifiers(&mut self, event: &NSEvent) { - let event_modifiers = event_mods(event); - if self.state.modifiers != event_modifiers { - self.state.modifiers = event_modifiers; + fn update_modifiers(&mut self, ns_event: &NSEvent, is_flags_changed_event: bool) { + use ElementState::{Pressed, Released}; + + let current_modifiers = event_mods(ns_event); + let prev_modifiers = self.state.modifiers; + + self.state.modifiers = current_modifiers; + + // This function was called form the flagsChanged event, which is triggered + // when the user presses/releases a modifier even if the same kind of modifier + // has already been pressed + if is_flags_changed_event { + let scancode = ns_event.scancode(); + let keycode = KeyCode::from_scancode(scancode as u32); + + // We'll correct the `is_press` later. + let mut event = create_key_event(ns_event, false, false, false, Some(keycode.clone())); + + let key = code_to_key(keycode.clone(), scancode); + let event_modifier = key_to_modifier(&key); + event.physical_key = keycode.clone(); + event.logical_key = key.clone(); + event.location = code_to_location(keycode); + let location_mask = ModLocationMask::from_location(event.location); + + let phys_mod = self + .state + .phys_modifiers + .entry(key) + .or_insert(ModLocationMask::empty()); + + let is_active = current_modifiers.contains(event_modifier); + let mut events = VecDeque::with_capacity(2); + + // There is no API for getting whether the button was pressed or released + // during this event. For this reason we have to do a bit of magic below + // to come up with a good guess whether this key was pressed or released. + // (This is not trivial because there are multiple buttons that may affect + // the same modifier) + if !is_active { + event.state = Released; + if phys_mod.contains(ModLocationMask::LEFT) { + let mut event = event.clone(); + event.location = KeyLocation::Left; + event.physical_key = get_left_modifier_code(&event.logical_key); + events.push_back(WindowEvent::KeyboardInput { + device_id: DEVICE_ID, + event, + is_synthetic: false, + }); + } + if phys_mod.contains(ModLocationMask::RIGHT) { + event.location = KeyLocation::Right; + event.physical_key = get_right_modifier_code(&event.logical_key); + events.push_back(WindowEvent::KeyboardInput { + device_id: DEVICE_ID, + event, + is_synthetic: false, + }); + } + *phys_mod = ModLocationMask::empty(); + } else { + // is_active + if *phys_mod == location_mask { + // Here we hit a contradiction: + // The modifier state was "changed" to active, + // yet the only pressed modifier key was the one that we + // just got a change event for. + // This seemingly means that the only pressed modifier is now released, + // but at the same time the modifier became active. + // + // But this scenario is possible if we released modifiers + // while the application was not in focus. (Because we don't + // get informed of modifier key events while the application + // is not focused) + + // In this case we prioritize the information + // about the current modifier state which means + // that the button was pressed. + event.state = Pressed; + } else { + phys_mod.toggle(location_mask); + let is_pressed = phys_mod.contains(location_mask); + event.state = if is_pressed { Pressed } else { Released }; + } + + events.push_back(WindowEvent::KeyboardInput { + device_id: DEVICE_ID, + event, + is_synthetic: false, + }); + } - self.queue_event(WindowEvent::ModifiersChanged(self.state.modifiers)); + let window_id = self.window_id(); + for event in events { + self.queue_event(Event::WindowEvent { window_id, event }); + } + } + + if prev_modifiers == current_modifiers { + return; } + + self.queue_event(Event::WindowEvent { + window_id: self.window_id(), + event: WindowEvent::ModifiersChanged(self.state.modifiers), + }); } - fn mouse_click(&mut self, event: &NSEvent, button_state: ElementState) { - let button = mouse_button(event); + fn mouse_click(&mut self, event: &NSEvent, button: MouseButton, button_state: ElementState) { + let buttom = mouse_button(event); - self.update_potentially_stale_modifiers(event); + self.update_modifiers(event, false); self.queue_event(WindowEvent::MouseInput { device_id: DEVICE_ID, @@ -960,7 +1006,7 @@ impl WinitView { let y = view_rect.size.height as f64 - view_point.y as f64; let logical_position = LogicalPosition::new(x, y); - self.update_potentially_stale_modifiers(event); + self.update_modifiers(event, false); self.queue_event(WindowEvent::CursorMoved { device_id: DEVICE_ID, diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index 68f12508167..a0aa680b289 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -1251,6 +1251,10 @@ impl WinitWindow { pub fn title(&self) -> String { self.title_().to_string() } + + pub fn reset_dead_keys(&self) { + // (Artur) I couldn't find a way to implement this. + } } impl WindowExtMacOS for WinitWindow { diff --git a/src/platform_impl/macos/window_delegate.rs b/src/platform_impl/macos/window_delegate.rs index 6a8f2f13909..c0994ce3d2c 100644 --- a/src/platform_impl/macos/window_delegate.rs +++ b/src/platform_impl/macos/window_delegate.rs @@ -13,7 +13,8 @@ use super::appkit::{ }; use crate::{ dpi::{LogicalPosition, LogicalSize}, - event::{Event, ModifiersState, WindowEvent}, + event::{Event, WindowEvent}, + keyboard::ModifiersState, platform_impl::platform::{ app_state::AppState, event::{EventProxy, EventWrapper},