From da299ac67e59e34f69f77e61cd0dddaf701c84f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20R=C3=B8yset?= Date: Thu, 8 Dec 2022 06:20:48 +0100 Subject: [PATCH 01/13] Define a new keyboard API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Artúr Kovács --- Cargo.toml | 1 + src/event.rs | 424 ++------ src/keyboard.rs | 1506 +++++++++++++++++++++++++++ src/lib.rs | 1 + src/platform/mod.rs | 2 + src/platform/modifier_supplement.rs | 32 + src/platform/scancode.rs | 32 + src/window.rs | 27 +- 8 files changed, 1680 insertions(+), 345 deletions(-) create mode 100644 src/keyboard.rs create mode 100644 src/platform/modifier_supplement.rs create mode 100644 src/platform/scancode.rs diff --git a/Cargo.toml b/Cargo.toml index cf34f22398..885cc9c52d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,6 +53,7 @@ bitflags = "1" instant = { version = "0.1", features = ["wasm-bindgen"] } log = "0.4" mint = { version = "0.5.6", optional = true } +nameof = "1.2.2" once_cell = "1.12" raw_window_handle = { package = "raw-window-handle", version = "0.5" } serde = { version = "1", optional = true, features = ["serde_derive"] } diff --git a/src/event.rs b/src/event.rs index 9612368345..bad3d00f7c 100644 --- a/src/event.rs +++ b/src/event.rs @@ -41,6 +41,7 @@ use std::path::PathBuf; use crate::window::Window; use crate::{ dpi::{PhysicalPosition, PhysicalSize}, + keyboard::{self, ModifiersState}, platform_impl, window::{Theme, WindowId}, }; @@ -256,6 +257,7 @@ impl Clone for Event<'static, T> { } impl<'a, T> Event<'a, T> { + #[allow(clippy::result_large_err)] pub fn map_nonuser_event(self) -> Result, Event<'a, T>> { use self::Event::*; match self { @@ -360,20 +362,21 @@ pub enum WindowEvent<'a> { /// hovered. HoveredFileCancelled, - /// The window received a unicode character. - /// - /// See also the [`Ime`](Self::Ime) event for more complex character sequences. - ReceivedCharacter(char), - /// The window gained or lost focus. /// /// The parameter is true if the window has gained focus, and false if it has lost focus. Focused(bool), /// An event from the keyboard has been received. + /// + /// ## Platform-specific + /// - **Windows:** The shift key overrides NumLock. In other words, while shift is held down, + /// numpad keys act as if NumLock wasn't active. When this is used, the OS sends fake key + /// events which are not marked as `is_synthetic`. KeyboardInput { device_id: DeviceId, - input: KeyboardInput, + event: KeyEvent, + /// If `true`, the event was generated synthetically by winit /// in one of the following circumstances: /// @@ -560,15 +563,14 @@ impl Clone for WindowEvent<'static> { DroppedFile(file) => DroppedFile(file.clone()), HoveredFile(file) => HoveredFile(file.clone()), HoveredFileCancelled => HoveredFileCancelled, - ReceivedCharacter(c) => ReceivedCharacter(*c), Focused(f) => Focused(*f), KeyboardInput { device_id, - input, + event, is_synthetic, } => KeyboardInput { device_id: *device_id, - input: *input, + event: event.clone(), is_synthetic: *is_synthetic, }, Ime(preedit_state) => Ime(preedit_state.clone()), @@ -673,15 +675,14 @@ impl<'a> WindowEvent<'a> { DroppedFile(file) => Some(DroppedFile(file)), HoveredFile(file) => Some(HoveredFile(file)), HoveredFileCancelled => Some(HoveredFileCancelled), - ReceivedCharacter(c) => Some(ReceivedCharacter(c)), Focused(focused) => Some(Focused(focused)), KeyboardInput { device_id, - input, + event, is_synthetic, } => Some(KeyboardInput { device_id, - input, + event, is_synthetic, }), ModifiersChanged(modifiers) => Some(ModifiersChanged(modifiers)), @@ -831,50 +832,100 @@ pub enum DeviceEvent { state: ElementState, }, - Key(KeyboardInput), + Key(RawKeyEvent), Text { codepoint: char, }, } -/// Describes a keyboard input event. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +/// Describes a keyboard input as a raw device event. +/// +/// Note that holding down a key may produce repeated `RawKeyEvent`s. The +/// operating system doesn't provide information whether such an event is a +/// repeat or the initial keypress. An application may emulate this by, for +/// example keeping a Map/Set of pressed keys and determining whether a keypress +/// corresponds to an already pressed key. +#[derive(Debug, Clone, Eq, PartialEq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct KeyboardInput { - /// Identifies the physical key pressed - /// - /// This should not change if the user adjusts the host's keyboard map. Use when the physical location of the - /// key is more important than the key's host GUI semantics, such as for movement controls in a first-person - /// game. - pub scancode: ScanCode, - +pub struct RawKeyEvent { + pub physical_key: keyboard::KeyCode, pub state: ElementState, +} - /// Identifies the semantic meaning of the key +/// Describes a keyboard input targeting a window. +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct KeyEvent { + /// Represents the position of a key independent of the currently active layout. + /// + /// It also uniquely identifies the physical key (i.e. it's mostly synonymous with a scancode). + /// The most prevalent use case for this is games. For example the default keys for the player + /// to move around might be the W, A, S, and D keys on a US layout. The position of these keys + /// is more important than their label, so they should map to Z, Q, S, and D on an "AZERTY" + /// layout. (This value is `KeyCode::KeyW` for the Z key on an AZERTY layout.) + /// + /// Note that `Fn` and `FnLock` key events are not guaranteed to be emitted by `winit`. These + /// keys are usually handled at the hardware or OS level. + pub physical_key: keyboard::KeyCode, + + // Allowing `broken_intra_doc_links` for `logical_key`, because + // `key_without_modifiers` is not available on all platforms + #[cfg_attr( + not(any(target_os = "macos", target_os = "windows", target_os = "linux")), + allow(rustdoc::broken_intra_doc_links) + )] + /// This value is affected by all modifiers except Ctrl. + /// + /// This has two use cases: + /// - Allows querying whether the current input is a Dead key. + /// - Allows handling key-bindings on platforms which don't + /// support [`key_without_modifiers`]. + /// + /// ## Platform-specific + /// - **Web:** Dead keys might be reported as the real key instead + /// of `Dead` depending on the browser/OS. /// - /// Use when the semantics of the key are more important than the physical location of the key, such as when - /// implementing appropriate behavior for "page up." - pub virtual_keycode: Option, + /// [`key_without_modifiers`]: crate::platform::modifier_supplement::KeyEventExtModifierSupplement::key_without_modifiers + pub logical_key: keyboard::Key<'static>, - /// Modifier keys active at the time of this input. + /// Contains the text produced by this keypress. + /// + /// In most cases this is identical to the content + /// of the `Character` variant of `logical_key`. + /// However, on Windows when a dead key was pressed earlier + /// but cannot be combined with the character from this + /// keypress, the produced text will consist of two characters: + /// the dead-key-character followed by the character resulting + /// from this keypress. + /// + /// An additional difference from `logical_key` is that + /// this field stores the text representation of any key + /// that has such a representation. For example when + /// `logical_key` is `Key::Enter`, this field is `Some("\r")`. /// - /// This is tracked internally to avoid tracking errors arising from modifier key state changes when events from - /// this device are not being delivered to the application, e.g. due to keyboard focus being elsewhere. - #[deprecated = "Deprecated in favor of WindowEvent::ModifiersChanged"] - pub modifiers: ModifiersState, + /// This is `None` if the current keypress cannot + /// be interpreted as text. + /// + /// See also: `text_with_all_modifiers()` + pub text: Option<&'static str>, + + pub location: keyboard::KeyLocation, + pub state: ElementState, + pub repeat: bool, + + pub(crate) platform_specific: platform_impl::KeyEventExtra, } /// Describes [input method](https://en.wikipedia.org/wiki/Input_method) events. /// /// This is also called a "composition event". /// -/// Most keypresses using a latin-like keyboard layout simply generate a [`WindowEvent::ReceivedCharacter`]. +/// Most keypresses using a latin-like keyboard layout simply generate a [`WindowEvent::KeyboardInput`]. /// However, one couldn't possibly have a key for every single unicode character that the user might want to type /// - so the solution operating systems employ is to allow the user to type these using _a sequence of keypresses_ instead. /// /// A prominent example of this is accents - many keyboard layouts allow you to first click the "accent key", and then -/// the character you want to apply the accent to. This will generate the following event sequence: +/// the character you want to apply the accent to. In this case, some platforms will generate the following event sequence: /// ```ignore /// // Press "`" key /// Ime::Preedit("`", Some((0, 0))) @@ -886,7 +937,7 @@ pub struct KeyboardInput { /// Additionally, certain input devices are configured to display a candidate box that allow the user to select the /// desired character interactively. (To properly position this box, you must use [`Window::set_ime_position`].) /// -/// An example of a keyboard layout which uses candidate boxes is pinyin. On a latin keybaord the following event +/// An example of a keyboard layout which uses candidate boxes is pinyin. On a latin keyboard the following event /// sequence could be obtained: /// ```ignore /// // Press "A" key @@ -1037,9 +1088,6 @@ impl Force { } } -/// Hardware-dependent keyboard scan code. -pub type ScanCode = u32; - /// Identifier for a specific analog axis on some device. pub type AxisId = u32; @@ -1090,303 +1138,3 @@ pub enum MouseScrollDelta { /// and move the content right and down (to reveal more things left and up). PixelDelta(PhysicalPosition), } - -/// Symbolic name for a keyboard key. -#[derive(Debug, Hash, Ord, PartialOrd, PartialEq, Eq, Clone, Copy)] -#[repr(u32)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub enum VirtualKeyCode { - /// The '1' key over the letters. - Key1, - /// The '2' key over the letters. - Key2, - /// The '3' key over the letters. - Key3, - /// The '4' key over the letters. - Key4, - /// The '5' key over the letters. - Key5, - /// The '6' key over the letters. - Key6, - /// The '7' key over the letters. - Key7, - /// The '8' key over the letters. - Key8, - /// The '9' key over the letters. - Key9, - /// The '0' key over the 'O' and 'P' keys. - Key0, - - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, - Z, - - /// The Escape key, next to F1. - Escape, - - F1, - F2, - F3, - F4, - F5, - F6, - F7, - F8, - F9, - F10, - F11, - F12, - F13, - F14, - F15, - F16, - F17, - F18, - F19, - F20, - F21, - F22, - F23, - F24, - - /// Print Screen/SysRq. - Snapshot, - /// Scroll Lock. - Scroll, - /// Pause/Break key, next to Scroll lock. - Pause, - - /// `Insert`, next to Backspace. - Insert, - Home, - Delete, - End, - PageDown, - PageUp, - - Left, - Up, - Right, - Down, - - /// The Backspace key, right over Enter. - // TODO: rename - Back, - /// The Enter key. - Return, - /// The space bar. - Space, - - /// The "Compose" key on Linux. - Compose, - - Caret, - - Numlock, - Numpad0, - Numpad1, - Numpad2, - Numpad3, - Numpad4, - Numpad5, - Numpad6, - Numpad7, - Numpad8, - Numpad9, - NumpadAdd, - NumpadDivide, - NumpadDecimal, - NumpadComma, - NumpadEnter, - NumpadEquals, - NumpadMultiply, - NumpadSubtract, - - AbntC1, - AbntC2, - Apostrophe, - Apps, - Asterisk, - At, - Ax, - Backslash, - Calculator, - Capital, - Colon, - Comma, - Convert, - Equals, - Grave, - Kana, - Kanji, - LAlt, - LBracket, - LControl, - LShift, - LWin, - Mail, - MediaSelect, - MediaStop, - Minus, - Mute, - MyComputer, - // also called "Next" - NavigateForward, - // also called "Prior" - NavigateBackward, - NextTrack, - NoConvert, - OEM102, - Period, - PlayPause, - Plus, - Power, - PrevTrack, - RAlt, - RBracket, - RControl, - RShift, - RWin, - Semicolon, - Slash, - Sleep, - Stop, - Sysrq, - Tab, - Underline, - Unlabeled, - VolumeDown, - VolumeUp, - Wake, - WebBack, - WebFavorites, - WebForward, - WebHome, - WebRefresh, - WebSearch, - WebStop, - Yen, - Copy, - Paste, - Cut, -} - -impl ModifiersState { - /// Returns `true` if the shift key is pressed. - pub fn shift(&self) -> bool { - self.intersects(Self::SHIFT) - } - /// Returns `true` if the control key is pressed. - pub fn ctrl(&self) -> bool { - self.intersects(Self::CTRL) - } - /// Returns `true` if the alt key is pressed. - pub fn alt(&self) -> bool { - self.intersects(Self::ALT) - } - /// Returns `true` if the logo key is pressed. - pub fn logo(&self) -> bool { - self.intersects(Self::LOGO) - } -} - -bitflags! { - /// Represents the current state of the keyboard modifiers - /// - /// Each flag represents a modifier and is set if this modifier is active. - #[derive(Default)] - pub struct ModifiersState: u32 { - // left and right modifiers are currently commented out, but we should be able to support - // them in a future release - /// The "shift" key. - const SHIFT = 0b100; - // const LSHIFT = 0b010; - // const RSHIFT = 0b001; - /// The "control" key. - const CTRL = 0b100 << 3; - // const LCTRL = 0b010 << 3; - // const RCTRL = 0b001 << 3; - /// The "alt" key. - const ALT = 0b100 << 6; - // const LALT = 0b010 << 6; - // const RALT = 0b001 << 6; - /// This is the "windows" key on PC and "command" key on Mac. - const LOGO = 0b100 << 9; - // const LLOGO = 0b010 << 9; - // const RLOGO = 0b001 << 9; - } -} - -#[cfg(feature = "serde")] -mod modifiers_serde { - use super::ModifiersState; - use serde::{Deserialize, Deserializer, Serialize, Serializer}; - - #[derive(Default, Serialize, Deserialize)] - #[serde(default)] - #[serde(rename = "ModifiersState")] - pub struct ModifiersStateSerialize { - pub shift: bool, - pub ctrl: bool, - pub alt: bool, - pub logo: bool, - } - - impl Serialize for ModifiersState { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let s = ModifiersStateSerialize { - shift: self.shift(), - ctrl: self.ctrl(), - alt: self.alt(), - logo: self.logo(), - }; - s.serialize(serializer) - } - } - - impl<'de> Deserialize<'de> for ModifiersState { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let ModifiersStateSerialize { - shift, - ctrl, - alt, - logo, - } = ModifiersStateSerialize::deserialize(deserializer)?; - let mut m = ModifiersState::empty(); - m.set(ModifiersState::SHIFT, shift); - m.set(ModifiersState::CTRL, ctrl); - m.set(ModifiersState::ALT, alt); - m.set(ModifiersState::LOGO, logo); - Ok(m) - } - } -} diff --git a/src/keyboard.rs b/src/keyboard.rs new file mode 100644 index 0000000000..219c57cb11 --- /dev/null +++ b/src/keyboard.rs @@ -0,0 +1,1506 @@ +//! Types related to the keyboard. + +// This file contains a substantial portion of the UI Events Specification by the W3C. In +// particular, the variant names within `Key` and `KeyCode` and their documentation are modified +// versions of contents of the aforementioned specification. +// +// The original documents are: +// +// ### For `Key` +// UI Events KeyboardEvent key Values +// https://www.w3.org/TR/2017/CR-uievents-key-20170601/ +// Copyright © 2017 W3C® (MIT, ERCIM, Keio, Beihang). +// +// ### For `KeyCode` +// UI Events KeyboardEvent code Values +// https://www.w3.org/TR/2017/CR-uievents-code-20170601/ +// Copyright © 2017 W3C® (MIT, ERCIM, Keio, Beihang). +// +// These documents were used under the terms of the following license. This W3C license as well as +// the W3C short notice apply to the `Key` and `KeyCode` enums and their variants and the +// documentation attached to their variants. + +// --------- BEGGINING OF W3C LICENSE -------------------------------------------------------------- +// +// License +// +// By obtaining and/or copying this work, you (the licensee) agree that you have read, understood, +// and will comply with the following terms and conditions. +// +// Permission to copy, modify, and distribute this work, with or without modification, for any +// purpose and without fee or royalty is hereby granted, provided that you include the following on +// ALL copies of the work or portions thereof, including modifications: +// +// - The full text of this NOTICE in a location viewable to users of the redistributed or derivative +// work. +// - Any pre-existing intellectual property disclaimers, notices, or terms and conditions. If none +// exist, the W3C Software and Document Short Notice should be included. +// - Notice of any changes or modifications, through a copyright statement on the new code or +// document such as "This software or document includes material copied from or derived from +// [title and URI of the W3C document]. Copyright © [YEAR] W3C® (MIT, ERCIM, Keio, Beihang)." +// +// Disclaimers +// +// THIS WORK IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS MAKE NO REPRESENTATIONS OR WARRANTIES, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY OR FITNESS FOR +// ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE OR DOCUMENT WILL NOT INFRINGE ANY THIRD +// PARTY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS. +// +// COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES +// ARISING OUT OF ANY USE OF THE SOFTWARE OR DOCUMENT. +// +// The name and trademarks of copyright holders may NOT be used in advertising or publicity +// pertaining to the work without specific, written prior permission. Title to copyright in this +// work will at all times remain with copyright holders. +// +// --------- END OF W3C LICENSE -------------------------------------------------------------------- + +// --------- BEGGINING OF W3C SHORT NOTICE --------------------------------------------------------- +// +// winit: https://github.com/rust-windowing/winit +// +// Copyright © 2021 World Wide Web Consortium, (Massachusetts Institute of Technology, European +// Research Consortium for Informatics and Mathematics, Keio University, Beihang). All Rights +// Reserved. This work is distributed under the W3C® Software License [1] in the hope that it will +// be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// [1] http://www.w3.org/Consortium/Legal/copyright-software +// +// --------- END OF W3C SHORT NOTICE --------------------------------------------------------------- + +use nameof::name_of; + +impl ModifiersState { + /// Returns `true` if the shift key is pressed. + pub fn shift_key(&self) -> bool { + self.intersects(Self::SHIFT) + } + /// Returns `true` if the control key is pressed. + pub fn control_key(&self) -> bool { + self.intersects(Self::CONTROL) + } + /// Returns `true` if the alt key is pressed. + pub fn alt_key(&self) -> bool { + self.intersects(Self::ALT) + } + /// Returns `true` if the super key is pressed. + pub fn super_key(&self) -> bool { + self.intersects(Self::SUPER) + } +} + +bitflags! { + /// Represents the current state of the keyboard modifiers + /// + /// Each flag represents a modifier and is set if this modifier is active. + #[derive(Default)] + pub struct ModifiersState: u32 { + // left and right modifiers are currently commented out, but we should be able to support + // them in a future release + /// The "shift" key. + const SHIFT = 0b100 << 0; + // const LSHIFT = 0b010 << 0; + // const RSHIFT = 0b001 << 0; + /// The "control" key. + const CONTROL = 0b100 << 3; + // const LCTRL = 0b010 << 3; + // const RCTRL = 0b001 << 3; + /// The "alt" key. + const ALT = 0b100 << 6; + // const LALT = 0b010 << 6; + // const RALT = 0b001 << 6; + /// This is the "windows" key on PC and "command" key on Mac. + const SUPER = 0b100 << 9; + // const LSUPER = 0b010 << 9; + // const RSUPER = 0b001 << 9; + } +} + +#[cfg(feature = "serde")] +mod modifiers_serde { + use super::ModifiersState; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + #[derive(Default, Serialize, Deserialize)] + #[serde(default)] + #[serde(rename = "ModifiersState")] + pub struct ModifiersStateSerialize { + pub shift_key: bool, + pub control_key: bool, + pub alt_key: bool, + pub super_key: bool, + } + + impl Serialize for ModifiersState { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let s = ModifiersStateSerialize { + shift_key: self.shift_key(), + control_key: self.control_key(), + alt_key: self.alt_key(), + super_key: self.super_key(), + }; + s.serialize(serializer) + } + } + + impl<'de> Deserialize<'de> for ModifiersState { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let ModifiersStateSerialize { + shift_key, + control_key, + alt_key, + super_key, + } = ModifiersStateSerialize::deserialize(deserializer)?; + let mut m = ModifiersState::empty(); + m.set(ModifiersState::SHIFT, shift_key); + m.set(ModifiersState::CONTROL, control_key); + m.set(ModifiersState::ALT, alt_key); + m.set(ModifiersState::SUPER, super_key); + Ok(m) + } + } +} + +/// Contains the platform-native physical key identifier +/// +/// The exact values vary from platform to platform (which is part of why this is a per-platform +/// enum), but the values are primarily tied to the key's physical location on the keyboard. +/// +/// This enum is primarily used to store raw keycodes when Winit doesn't map a given native +/// physical key identifier to a meaningful [`KeyCode`] variant. In the presence of identifiers we +/// haven't mapped for you yet, this lets you use use [`KeyCode`] to: +/// +/// - Correctly match key press and release events. +/// - On non-web platforms, support assigning keybinds to virtually any key through a UI. +#[derive(Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum NativeKeyCode { + Unidentified, + /// An Android "scancode". + Android(u32), + /// A macOS "scancode". + MacOS(u16), + /// A Windows "scancode". + Windows(u16), + /// An XKB "keycode". + Xkb(u32), +} + +impl std::fmt::Debug for NativeKeyCode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use NativeKeyCode::{Android, MacOS, Unidentified, Windows, Xkb}; + let mut debug_tuple; + match self { + Unidentified => { + debug_tuple = f.debug_tuple(name_of!(Unidentified)); + } + Android(code) => { + debug_tuple = f.debug_tuple(name_of!(Android)); + debug_tuple.field(&format_args!("0x{code:04X}")); + } + MacOS(code) => { + debug_tuple = f.debug_tuple(name_of!(MacOS)); + debug_tuple.field(&format_args!("0x{code:04X}")); + } + Windows(code) => { + debug_tuple = f.debug_tuple(name_of!(Windows)); + debug_tuple.field(&format_args!("0x{code:04X}")); + } + Xkb(code) => { + debug_tuple = f.debug_tuple(name_of!(Xkb)); + debug_tuple.field(&format_args!("0x{code:04X}")); + } + } + debug_tuple.finish() + } +} + +/// Contains the platform-native logical key identifier +/// +/// Exactly what that means differs from platform to platform, but the values are to some degree +/// tied to the currently active keyboard layout. The same key on the same keyboard may also report +/// different values on different platforms, which is one of the reasons this is a per-platform +/// enum. +/// +/// This enum is primarily used to store raw keysym when Winit doesn't map a given native logical +/// key identifier to a meaningful [`Key`] variant. This lets you use [`Key`], and let the user +/// define keybinds which work in the presence of identifiers we haven't mapped for you yet. +#[derive(Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum NativeKey { + Unidentified, + /// An Android "keycode", which is similar to a "virtual-key code" on Windows. + Android(u32), + /// A macOS "scancode". There does not appear to be any direct analogue to either keysyms or + /// "virtual-key" codes in macOS, so we report the scancode instead. + MacOS(u16), + /// A Windows "virtual-key code". + Windows(u16), + /// An XKB "keysym". + Xkb(u32), + /// A "key value string". + Web(String), +} + +impl std::fmt::Debug for NativeKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use NativeKey::{Android, MacOS, Unidentified, Web, Windows, Xkb}; + let mut debug_tuple; + match self { + Unidentified => { + debug_tuple = f.debug_tuple(name_of!(Unidentified)); + } + Android(code) => { + debug_tuple = f.debug_tuple(name_of!(Android)); + debug_tuple.field(&format_args!("0x{code:04X}")); + } + MacOS(code) => { + debug_tuple = f.debug_tuple(name_of!(MacOS)); + debug_tuple.field(&format_args!("0x{code:04X}")); + } + Windows(code) => { + debug_tuple = f.debug_tuple(name_of!(Windows)); + debug_tuple.field(&format_args!("0x{code:04X}")); + } + Xkb(code) => { + debug_tuple = f.debug_tuple(name_of!(Xkb)); + debug_tuple.field(&format_args!("0x{code:04X}")); + } + Web(code) => { + debug_tuple = f.debug_tuple(name_of!(Web)); + debug_tuple.field(code); + } + } + debug_tuple.finish() + } +} + +/// Represents the location of a physical key. +/// +/// This mostly conforms to the UI Events Specification's [`KeyboardEvent.code`] with a few +/// exceptions: +/// - The keys that the specification calls "MetaLeft" and "MetaRight" are named "SuperLeft" and +/// "SuperRight" here. +/// - The key that the specification calls "Super" is reported as `Unidentified` here. +/// - The `Unidentified` variant here, can still identifiy a key through it's `NativeKeyCode`. +/// +/// [`KeyboardEvent.code`]: https://w3c.github.io/uievents-code/#code-value-tables +#[non_exhaustive] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum KeyCode { + /// This variant is used when the key cannot be translated to any other variant. + /// + /// The native keycode is provided (if available) so you're able to more reliably match + /// key-press and key-release events by hashing the [`KeyCode`]. It is also possible to use + /// this for keybinds for non-standard keys, but such keybinds are tied to a given platform. + Unidentified(NativeKeyCode), + /// ` on a US keyboard. This is also called a backtick or grave. + /// This is the 半角/全角/漢字 + /// (hankaku/zenkaku/kanji) key on Japanese keyboards + Backquote, + /// Used for both the US \\ (on the 101-key layout) and also for the key + /// located between the " and Enter keys on row C of the 102-, + /// 104- and 106-key layouts. + /// Labeled # on a UK (102) keyboard. + Backslash, + /// [ on a US keyboard. + BracketLeft, + /// ] on a US keyboard. + BracketRight, + /// , on a US keyboard. + Comma, + /// 0 on a US keyboard. + Digit0, + /// 1 on a US keyboard. + Digit1, + /// 2 on a US keyboard. + Digit2, + /// 3 on a US keyboard. + Digit3, + /// 4 on a US keyboard. + Digit4, + /// 5 on a US keyboard. + Digit5, + /// 6 on a US keyboard. + Digit6, + /// 7 on a US keyboard. + Digit7, + /// 8 on a US keyboard. + Digit8, + /// 9 on a US keyboard. + Digit9, + /// = on a US keyboard. + Equal, + /// Located between the left Shift and Z keys. + /// Labeled \\ on a UK keyboard. + IntlBackslash, + /// Located between the / and right Shift keys. + /// Labeled \\ (ro) on a Japanese keyboard. + IntlRo, + /// Located between the = and Backspace keys. + /// Labeled ¥ (yen) on a Japanese keyboard. \\ on a + /// Russian keyboard. + IntlYen, + /// a on a US keyboard. + /// Labeled q on an AZERTY (e.g., French) keyboard. + KeyA, + /// b on a US keyboard. + KeyB, + /// c on a US keyboard. + KeyC, + /// d on a US keyboard. + KeyD, + /// e on a US keyboard. + KeyE, + /// f on a US keyboard. + KeyF, + /// g on a US keyboard. + KeyG, + /// h on a US keyboard. + KeyH, + /// i on a US keyboard. + KeyI, + /// j on a US keyboard. + KeyJ, + /// k on a US keyboard. + KeyK, + /// l on a US keyboard. + KeyL, + /// m on a US keyboard. + KeyM, + /// n on a US keyboard. + KeyN, + /// o on a US keyboard. + KeyO, + /// p on a US keyboard. + KeyP, + /// q on a US keyboard. + /// Labeled a on an AZERTY (e.g., French) keyboard. + KeyQ, + /// r on a US keyboard. + KeyR, + /// s on a US keyboard. + KeyS, + /// t on a US keyboard. + KeyT, + /// u on a US keyboard. + KeyU, + /// v on a US keyboard. + KeyV, + /// w on a US keyboard. + /// Labeled z on an AZERTY (e.g., French) keyboard. + KeyW, + /// x on a US keyboard. + KeyX, + /// y on a US keyboard. + /// Labeled z on a QWERTZ (e.g., German) keyboard. + KeyY, + /// z on a US keyboard. + /// Labeled w on an AZERTY (e.g., French) keyboard, and y on a + /// QWERTZ (e.g., German) keyboard. + KeyZ, + /// - on a US keyboard. + Minus, + /// . on a US keyboard. + Period, + /// ' on a US keyboard. + Quote, + /// ; on a US keyboard. + Semicolon, + /// / on a US keyboard. + Slash, + /// Alt, Option, or . + AltLeft, + /// Alt, Option, or . + /// This is labeled AltGr on many keyboard layouts. + AltRight, + /// Backspace or . + /// Labeled Delete on Apple keyboards. + Backspace, + /// CapsLock or + CapsLock, + /// The application context menu key, which is typically found between the right + /// Super key and the right Control key. + ContextMenu, + /// Control or + ControlLeft, + /// Control or + ControlRight, + /// Enter or . Labeled Return on Apple keyboards. + Enter, + /// The Windows, , Command, or other OS symbol key. + SuperLeft, + /// The Windows, , Command, or other OS symbol key. + SuperRight, + /// Shift or + ShiftLeft, + /// Shift or + ShiftRight, + ///   (space) + Space, + /// Tab or + Tab, + /// Japanese: (henkan) + Convert, + /// Japanese: カタカナ/ひらがな/ローマ字 (katakana/hiragana/romaji) + KanaMode, + /// Korean: HangulMode 한/영 (han/yeong) + /// + /// Japanese (Mac keyboard): (kana) + Lang1, + /// Korean: Hanja (hanja) + /// + /// Japanese (Mac keyboard): (eisu) + Lang2, + /// Japanese (word-processing keyboard): Katakana + Lang3, + /// Japanese (word-processing keyboard): Hiragana + Lang4, + /// Japanese (word-processing keyboard): Zenkaku/Hankaku + Lang5, + /// Japanese: 無変換 (muhenkan) + NonConvert, + /// . The forward delete key. + /// Note that on Apple keyboards, the key labelled Delete on the main part of + /// the keyboard is encoded as [`Backspace`]. + /// + /// [`Backspace`]: Self::Backspace + Delete, + /// Page Down, End, or + End, + /// Help. Not present on standard PC keyboards. + Help, + /// Home or + Home, + /// Insert or Ins. Not present on Apple keyboards. + Insert, + /// Page Down, PgDn, or + PageDown, + /// Page Up, PgUp, or + PageUp, + /// + ArrowDown, + /// + ArrowLeft, + /// + ArrowRight, + /// + ArrowUp, + /// On the Mac, this is used for the numpad Clear key. + NumLock, + /// 0 Ins on a keyboard. 0 on a phone or remote control + Numpad0, + /// 1 End on a keyboard. 1 or 1 QZ on a phone or remote control + Numpad1, + /// 2 ↓ on a keyboard. 2 ABC on a phone or remote control + Numpad2, + /// 3 PgDn on a keyboard. 3 DEF on a phone or remote control + Numpad3, + /// 4 ← on a keyboard. 4 GHI on a phone or remote control + Numpad4, + /// 5 on a keyboard. 5 JKL on a phone or remote control + Numpad5, + /// 6 → on a keyboard. 6 MNO on a phone or remote control + Numpad6, + /// 7 Home on a keyboard. 7 PQRS or 7 PRS on a phone + /// or remote control + Numpad7, + /// 8 ↑ on a keyboard. 8 TUV on a phone or remote control + Numpad8, + /// 9 PgUp on a keyboard. 9 WXYZ or 9 WXY on a phone + /// or remote control + Numpad9, + /// + + NumpadAdd, + /// Found on the Microsoft Natural Keyboard. + NumpadBackspace, + /// C or A (All Clear). Also for use with numpads that have a + /// Clear key that is separate from the NumLock key. On the Mac, the + /// numpad Clear key is encoded as [`NumLock`]. + /// + /// [`NumLock`]: Self::NumLock + NumpadClear, + /// C (Clear Entry) + NumpadClearEntry, + /// , (thousands separator). For locales where the thousands separator + /// is a "." (e.g., Brazil), this key may generate a .. + NumpadComma, + /// . Del. For locales where the decimal separator is "," (e.g., + /// Brazil), this key may generate a ,. + NumpadDecimal, + /// / + NumpadDivide, + NumpadEnter, + /// = + NumpadEqual, + /// # on a phone or remote control device. This key is typically found + /// below the 9 key and to the right of the 0 key. + NumpadHash, + /// M Add current entry to the value stored in memory. + NumpadMemoryAdd, + /// M Clear the value stored in memory. + NumpadMemoryClear, + /// M Replace the current entry with the value stored in memory. + NumpadMemoryRecall, + /// M Replace the value stored in memory with the current entry. + NumpadMemoryStore, + /// M Subtract current entry from the value stored in memory. + NumpadMemorySubtract, + /// * on a keyboard. For use with numpads that provide mathematical + /// operations (+, - * and /). + /// + /// Use `NumpadStar` for the * key on phones and remote controls. + NumpadMultiply, + /// ( Found on the Microsoft Natural Keyboard. + NumpadParenLeft, + /// ) Found on the Microsoft Natural Keyboard. + NumpadParenRight, + /// * on a phone or remote control device. + /// + /// This key is typically found below the 7 key and to the left of + /// the 0 key. + /// + /// Use "NumpadMultiply" for the * key on + /// numeric keypads. + NumpadStar, + /// - + NumpadSubtract, + /// Esc or + Escape, + /// Fn This is typically a hardware key that does not generate a separate code. + Fn, + /// FLock or FnLock. Function Lock key. Found on the Microsoft + /// Natural Keyboard. + FnLock, + /// PrtScr SysRq or Print Screen + PrintScreen, + /// Scroll Lock + ScrollLock, + /// Pause Break + Pause, + /// Some laptops place this key to the left of the key. + /// + /// This also the "back" button (triangle) on Android. + BrowserBack, + BrowserFavorites, + /// Some laptops place this key to the right of the key. + BrowserForward, + /// The "home" button on Android. + BrowserHome, + BrowserRefresh, + BrowserSearch, + BrowserStop, + /// Eject or . This key is placed in the function section on some Apple + /// keyboards. + Eject, + /// Sometimes labelled My Computer on the keyboard + LaunchApp1, + /// Sometimes labelled Calculator on the keyboard + LaunchApp2, + LaunchMail, + MediaPlayPause, + MediaSelect, + MediaStop, + MediaTrackNext, + MediaTrackPrevious, + /// This key is placed in the function section on some Apple keyboards, replacing the + /// Eject key. + Power, + Sleep, + AudioVolumeDown, + AudioVolumeMute, + AudioVolumeUp, + WakeUp, + // Legacy modifier key. Also called "Super" in certain places. + Meta, + // Legacy modifier key. + Hyper, + Turbo, + Abort, + Resume, + Suspend, + /// Found on Sun’s USB keyboard. + Again, + /// Found on Sun’s USB keyboard. + Copy, + /// Found on Sun’s USB keyboard. + Cut, + /// Found on Sun’s USB keyboard. + Find, + /// Found on Sun’s USB keyboard. + Open, + /// Found on Sun’s USB keyboard. + Paste, + /// Found on Sun’s USB keyboard. + Props, + /// Found on Sun’s USB keyboard. + Select, + /// Found on Sun’s USB keyboard. + Undo, + /// Use for dedicated ひらがな key found on some Japanese word processing keyboards. + Hiragana, + /// Use for dedicated カタカナ key found on some Japanese word processing keyboards. + Katakana, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F1, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F2, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F3, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F4, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F5, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F6, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F7, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F8, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F9, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F10, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F11, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F12, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F13, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F14, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F15, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F16, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F17, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F18, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F19, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F20, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F21, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F22, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F23, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F24, + /// General-purpose function key. + F25, + /// General-purpose function key. + F26, + /// General-purpose function key. + F27, + /// General-purpose function key. + F28, + /// General-purpose function key. + F29, + /// General-purpose function key. + F30, + /// General-purpose function key. + F31, + /// General-purpose function key. + F32, + /// General-purpose function key. + F33, + /// General-purpose function key. + F34, + /// General-purpose function key. + F35, +} + +/// Key represents the meaning of a keypress. +/// +/// This mostly conforms to the UI Events Specification's [`KeyboardEvent.key`] with a few +/// exceptions: +/// - The `Super` variant here, is named `Meta` in the aforementioned specification. (There's +/// another key which the specification calls `Super`. That does not exist here.) +/// - The `Space` variant here, can be identified by the character it generates in the +/// specificaiton. +/// - The `Unidentified` variant here, can still identifiy a key through it's `NativeKeyCode`. +/// - The `Dead` variant here, can specify the character which is inserted when pressing the +/// dead-key twice. +/// +/// [`KeyboardEvent.key`]: https://w3c.github.io/uievents-key/ +#[non_exhaustive] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum Key<'a> { + /// A key string that corresponds to the character typed by the user, taking into account the + /// user’s current locale setting, and any system-level keyboard mapping overrides that are in + /// effect. + Character(&'a str), + + /// This variant is used when the key cannot be translated to any other variant. + /// + /// The native key is provided (if available) in order to allow the user to specify keybindings + /// for keys which are not defined by this API, mainly through some sort of UI. + Unidentified(NativeKey), + + /// Contains the text representation of the dead-key when available. + /// + /// ## Platform-specific + /// - **Web:** Always contains `None` + Dead(Option), + + /// The `Alt` (Alternative) key. + /// + /// This key enables the alternate modifier function for interpreting concurrent or subsequent + /// keyboard input. This key value is also used for the Apple Option key. + Alt, + /// The Alternate Graphics (AltGr or AltGraph) key. + /// + /// This key is used enable the ISO Level 3 shift modifier (the standard `Shift` key is the + /// level 2 modifier). + AltGraph, + /// The `Caps Lock` (Capital) key. + /// + /// Toggle capital character lock function for interpreting subsequent keyboard input event. + CapsLock, + /// The `Control` or `Ctrl` key. + /// + /// Used to enable control modifier function for interpreting concurrent or subsequent keyboard + /// input. + Control, + /// The Function switch `Fn` key. Activating this key simultaneously with another key changes + /// that key’s value to an alternate character or function. This key is often handled directly + /// in the keyboard hardware and does not usually generate key events. + Fn, + /// The Function-Lock (`FnLock` or `F-Lock`) key. Activating this key switches the mode of the + /// keyboard to changes some keys' values to an alternate character or function. This key is + /// often handled directly in the keyboard hardware and does not usually generate key events. + FnLock, + /// The `NumLock` or Number Lock key. Used to toggle numpad mode function for interpreting + /// subsequent keyboard input. + NumLock, + /// Toggle between scrolling and cursor movement modes. + ScrollLock, + /// Used to enable shift modifier function for interpreting concurrent or subsequent keyboard + /// input. + Shift, + /// The Symbol modifier key (used on some virtual keyboards). + Symbol, + SymbolLock, + // Legacy modifier key. Also called "Super" in certain places. + Meta, + // Legacy modifier key. + Hyper, + /// Used to enable "super" modifier function for interpreting concurrent or subsequent keyboard + /// input. This key value is used for the "Windows Logo" key and the Apple `Command` or `⌘` key. + /// + /// Note: In some contexts (e.g. the Web) this is referred to as the "Meta" key. + Super, + /// The `Enter` or `↵` key. Used to activate current selection or accept current input. This key + /// value is also used for the `Return` (Macintosh numpad) key. This key value is also used for + /// the Android `KEYCODE_DPAD_CENTER`. + Enter, + /// The Horizontal Tabulation `Tab` key. + Tab, + /// Used in text to insert a space between words. Usually located below the character keys. + Space, + /// Navigate or traverse downward. (`KEYCODE_DPAD_DOWN`) + ArrowDown, + /// Navigate or traverse leftward. (`KEYCODE_DPAD_LEFT`) + ArrowLeft, + /// Navigate or traverse rightward. (`KEYCODE_DPAD_RIGHT`) + ArrowRight, + /// Navigate or traverse upward. (`KEYCODE_DPAD_UP`) + ArrowUp, + /// The End key, used with keyboard entry to go to the end of content (`KEYCODE_MOVE_END`). + End, + /// The Home key, used with keyboard entry, to go to start of content (`KEYCODE_MOVE_HOME`). + /// For the mobile phone `Home` key (which goes to the phone’s main screen), use [`GoHome`]. + /// + /// [`GoHome`]: Self::GoHome + Home, + /// Scroll down or display next page of content. + PageDown, + /// Scroll up or display previous page of content. + PageUp, + /// Used to remove the character to the left of the cursor. This key value is also used for + /// the key labeled `Delete` on MacOS keyboards. + Backspace, + /// Remove the currently selected input. + Clear, + /// Copy the current selection. (`APPCOMMAND_COPY`) + Copy, + /// The Cursor Select key. + CrSel, + /// Cut the current selection. (`APPCOMMAND_CUT`) + Cut, + /// Used to delete the character to the right of the cursor. This key value is also used for the + /// key labeled `Delete` on MacOS keyboards when `Fn` is active. + Delete, + /// The Erase to End of Field key. This key deletes all characters from the current cursor + /// position to the end of the current field. + EraseEof, + /// The Extend Selection (Exsel) key. + ExSel, + /// Toggle between text modes for insertion or overtyping. + /// (`KEYCODE_INSERT`) + Insert, + /// The Paste key. (`APPCOMMAND_PASTE`) + Paste, + /// Redo the last action. (`APPCOMMAND_REDO`) + Redo, + /// Undo the last action. (`APPCOMMAND_UNDO`) + Undo, + /// The Accept (Commit, OK) key. Accept current option or input method sequence conversion. + Accept, + /// Redo or repeat an action. + Again, + /// The Attention (Attn) key. + Attn, + Cancel, + /// Show the application’s context menu. + /// This key is commonly found between the right `Super` key and the right `Control` key. + ContextMenu, + /// The `Esc` key. This key was originally used to initiate an escape sequence, but is + /// now more generally used to exit or "escape" the current context, such as closing a dialog + /// or exiting full screen mode. + Escape, + Execute, + /// Open the Find dialog. (`APPCOMMAND_FIND`) + Find, + /// Open a help dialog or toggle display of help information. (`APPCOMMAND_HELP`, + /// `KEYCODE_HELP`) + Help, + /// Pause the current state or application (as appropriate). + /// + /// Note: Do not use this value for the `Pause` button on media controllers. Use `"MediaPause"` + /// instead. + Pause, + /// Play or resume the current state or application (as appropriate). + /// + /// Note: Do not use this value for the `Play` button on media controllers. Use `"MediaPlay"` + /// instead. + Play, + /// The properties (Props) key. + Props, + Select, + /// The ZoomIn key. (`KEYCODE_ZOOM_IN`) + ZoomIn, + /// The ZoomOut key. (`KEYCODE_ZOOM_OUT`) + ZoomOut, + /// The Brightness Down key. Typically controls the display brightness. + /// (`KEYCODE_BRIGHTNESS_DOWN`) + BrightnessDown, + /// The Brightness Up key. Typically controls the display brightness. (`KEYCODE_BRIGHTNESS_UP`) + BrightnessUp, + /// Toggle removable media to eject (open) and insert (close) state. (`KEYCODE_MEDIA_EJECT`) + Eject, + LogOff, + /// Toggle power state. (`KEYCODE_POWER`) + /// Note: Note: Some devices might not expose this key to the operating environment. + Power, + /// The `PowerOff` key. Sometime called `PowerDown`. + PowerOff, + /// Initiate print-screen function. + PrintScreen, + /// The Hibernate key. This key saves the current state of the computer to disk so that it can + /// be restored. The computer will then shutdown. + Hibernate, + /// The Standby key. This key turns off the display and places the computer into a low-power + /// mode without completely shutting down. It is sometimes labelled `Suspend` or `Sleep` key. + /// (`KEYCODE_SLEEP`) + Standby, + /// The WakeUp key. (`KEYCODE_WAKEUP`) + WakeUp, + /// Initate the multi-candidate mode. + AllCandidates, + Alphanumeric, + /// Initiate the Code Input mode to allow characters to be entered by + /// their code points. + CodeInput, + /// The Compose key, also known as "Multi_key" on the X Window System. This key acts in a + /// manner similar to a dead key, triggering a mode where subsequent key presses are combined to + /// produce a different character. + Compose, + /// Convert the current input method sequence. + Convert, + /// The Final Mode `Final` key used on some Asian keyboards, to enable the final mode for IMEs. + FinalMode, + /// Switch to the first character group. (ISO/IEC 9995) + GroupFirst, + /// Switch to the last character group. (ISO/IEC 9995) + GroupLast, + /// Switch to the next character group. (ISO/IEC 9995) + GroupNext, + /// Switch to the previous character group. (ISO/IEC 9995) + GroupPrevious, + /// Toggle between or cycle through input modes of IMEs. + ModeChange, + NextCandidate, + /// Accept current input method sequence without + /// conversion in IMEs. + NonConvert, + PreviousCandidate, + Process, + SingleCandidate, + /// Toggle between Hangul and English modes. + HangulMode, + HanjaMode, + JunjaMode, + /// The Eisu key. This key may close the IME, but its purpose is defined by the current IME. + /// (`KEYCODE_EISU`) + Eisu, + /// The (Half-Width) Characters key. + Hankaku, + /// The Hiragana (Japanese Kana characters) key. + Hiragana, + /// The Hiragana/Katakana toggle key. (`KEYCODE_KATAKANA_HIRAGANA`) + HiraganaKatakana, + /// The Kana Mode (Kana Lock) key. This key is used to enter hiragana mode (typically from + /// romaji mode). + KanaMode, + /// The Kanji (Japanese name for ideographic characters of Chinese origin) Mode key. This key is + /// typically used to switch to a hiragana keyboard for the purpose of converting input into + /// kanji. (`KEYCODE_KANA`) + KanjiMode, + /// The Katakana (Japanese Kana characters) key. + Katakana, + /// The Roman characters function key. + Romaji, + /// The Zenkaku (Full-Width) Characters key. + Zenkaku, + /// The Zenkaku/Hankaku (full-width/half-width) toggle key. (`KEYCODE_ZENKAKU_HANKAKU`) + ZenkakuHankaku, + /// General purpose virtual function key, as index 1. + Soft1, + /// General purpose virtual function key, as index 2. + Soft2, + /// General purpose virtual function key, as index 3. + Soft3, + /// General purpose virtual function key, as index 4. + Soft4, + /// Select next (numerically or logically) lower channel. (`APPCOMMAND_MEDIA_CHANNEL_DOWN`, + /// `KEYCODE_CHANNEL_DOWN`) + ChannelDown, + /// Select next (numerically or logically) higher channel. (`APPCOMMAND_MEDIA_CHANNEL_UP`, + /// `KEYCODE_CHANNEL_UP`) + ChannelUp, + /// Close the current document or message (Note: This doesn’t close the application). + /// (`APPCOMMAND_CLOSE`) + Close, + /// Open an editor to forward the current message. (`APPCOMMAND_FORWARD_MAIL`) + MailForward, + /// Open an editor to reply to the current message. (`APPCOMMAND_REPLY_TO_MAIL`) + MailReply, + /// Send the current message. (`APPCOMMAND_SEND_MAIL`) + MailSend, + /// Close the current media, for example to close a CD or DVD tray. (`KEYCODE_MEDIA_CLOSE`) + MediaClose, + /// Initiate or continue forward playback at faster than normal speed, or increase speed if + /// already fast forwarding. (`APPCOMMAND_MEDIA_FAST_FORWARD`, `KEYCODE_MEDIA_FAST_FORWARD`) + MediaFastForward, + /// Pause the currently playing media. (`APPCOMMAND_MEDIA_PAUSE`, `KEYCODE_MEDIA_PAUSE`) + /// + /// Note: Media controller devices should use this value rather than `"Pause"` for their pause + /// keys. + MediaPause, + /// Initiate or continue media playback at normal speed, if not currently playing at normal + /// speed. (`APPCOMMAND_MEDIA_PLAY`, `KEYCODE_MEDIA_PLAY`) + MediaPlay, + /// Toggle media between play and pause states. (`APPCOMMAND_MEDIA_PLAY_PAUSE`, + /// `KEYCODE_MEDIA_PLAY_PAUSE`) + MediaPlayPause, + /// Initiate or resume recording of currently selected media. (`APPCOMMAND_MEDIA_RECORD`, + /// `KEYCODE_MEDIA_RECORD`) + MediaRecord, + /// Initiate or continue reverse playback at faster than normal speed, or increase speed if + /// already rewinding. (`APPCOMMAND_MEDIA_REWIND`, `KEYCODE_MEDIA_REWIND`) + MediaRewind, + /// Stop media playing, pausing, forwarding, rewinding, or recording, if not already stopped. + /// (`APPCOMMAND_MEDIA_STOP`, `KEYCODE_MEDIA_STOP`) + MediaStop, + /// Seek to next media or program track. (`APPCOMMAND_MEDIA_NEXTTRACK`, `KEYCODE_MEDIA_NEXT`) + MediaTrackNext, + /// Seek to previous media or program track. (`APPCOMMAND_MEDIA_PREVIOUSTRACK`, + /// `KEYCODE_MEDIA_PREVIOUS`) + MediaTrackPrevious, + /// Open a new document or message. (`APPCOMMAND_NEW`) + New, + /// Open an existing document or message. (`APPCOMMAND_OPEN`) + Open, + /// Print the current document or message. (`APPCOMMAND_PRINT`) + Print, + /// Save the current document or message. (`APPCOMMAND_SAVE`) + Save, + /// Spellcheck the current document or selection. (`APPCOMMAND_SPELL_CHECK`) + SpellCheck, + /// The `11` key found on media numpads that + /// have buttons from `1` ... `12`. + Key11, + /// The `12` key found on media numpads that + /// have buttons from `1` ... `12`. + Key12, + /// Adjust audio balance leftward. (`VK_AUDIO_BALANCE_LEFT`) + AudioBalanceLeft, + /// Adjust audio balance rightward. (`VK_AUDIO_BALANCE_RIGHT`) + AudioBalanceRight, + /// Decrease audio bass boost or cycle down through bass boost states. (`APPCOMMAND_BASS_DOWN`, + /// `VK_BASS_BOOST_DOWN`) + AudioBassBoostDown, + /// Toggle bass boost on/off. (`APPCOMMAND_BASS_BOOST`) + AudioBassBoostToggle, + /// Increase audio bass boost or cycle up through bass boost states. (`APPCOMMAND_BASS_UP`, + /// `VK_BASS_BOOST_UP`) + AudioBassBoostUp, + /// Adjust audio fader towards front. (`VK_FADER_FRONT`) + AudioFaderFront, + /// Adjust audio fader towards rear. (`VK_FADER_REAR`) + AudioFaderRear, + /// Advance surround audio mode to next available mode. (`VK_SURROUND_MODE_NEXT`) + AudioSurroundModeNext, + /// Decrease treble. (`APPCOMMAND_TREBLE_DOWN`) + AudioTrebleDown, + /// Increase treble. (`APPCOMMAND_TREBLE_UP`) + AudioTrebleUp, + /// Decrease audio volume. (`APPCOMMAND_VOLUME_DOWN`, `KEYCODE_VOLUME_DOWN`) + AudioVolumeDown, + /// Increase audio volume. (`APPCOMMAND_VOLUME_UP`, `KEYCODE_VOLUME_UP`) + AudioVolumeUp, + /// Toggle between muted state and prior volume level. (`APPCOMMAND_VOLUME_MUTE`, + /// `KEYCODE_VOLUME_MUTE`) + AudioVolumeMute, + /// Toggle the microphone on/off. (`APPCOMMAND_MIC_ON_OFF_TOGGLE`) + MicrophoneToggle, + /// Decrease microphone volume. (`APPCOMMAND_MICROPHONE_VOLUME_DOWN`) + MicrophoneVolumeDown, + /// Increase microphone volume. (`APPCOMMAND_MICROPHONE_VOLUME_UP`) + MicrophoneVolumeUp, + /// Mute the microphone. (`APPCOMMAND_MICROPHONE_VOLUME_MUTE`, `KEYCODE_MUTE`) + MicrophoneVolumeMute, + /// Show correction list when a word is incorrectly identified. (`APPCOMMAND_CORRECTION_LIST`) + SpeechCorrectionList, + /// Toggle between dictation mode and command/control mode. + /// (`APPCOMMAND_DICTATE_OR_COMMAND_CONTROL_TOGGLE`) + SpeechInputToggle, + /// The first generic "LaunchApplication" key. This is commonly associated with launching "My + /// Computer", and may have a computer symbol on the key. (`APPCOMMAND_LAUNCH_APP1`) + LaunchApplication1, + /// The second generic "LaunchApplication" key. This is commonly associated with launching + /// "Calculator", and may have a calculator symbol on the key. (`APPCOMMAND_LAUNCH_APP2`, + /// `KEYCODE_CALCULATOR`) + LaunchApplication2, + /// The "Calendar" key. (`KEYCODE_CALENDAR`) + LaunchCalendar, + /// The "Contacts" key. (`KEYCODE_CONTACTS`) + LaunchContacts, + /// The "Mail" key. (`APPCOMMAND_LAUNCH_MAIL`) + LaunchMail, + /// The "Media Player" key. (`APPCOMMAND_LAUNCH_MEDIA_SELECT`) + LaunchMediaPlayer, + LaunchMusicPlayer, + LaunchPhone, + LaunchScreenSaver, + LaunchSpreadsheet, + LaunchWebBrowser, + LaunchWebCam, + LaunchWordProcessor, + /// Navigate to previous content or page in current history. (`APPCOMMAND_BROWSER_BACKWARD`) + BrowserBack, + /// Open the list of browser favorites. (`APPCOMMAND_BROWSER_FAVORITES`) + BrowserFavorites, + /// Navigate to next content or page in current history. (`APPCOMMAND_BROWSER_FORWARD`) + BrowserForward, + /// Go to the user’s preferred home page. (`APPCOMMAND_BROWSER_HOME`) + BrowserHome, + /// Refresh the current page or content. (`APPCOMMAND_BROWSER_REFRESH`) + BrowserRefresh, + /// Call up the user’s preferred search page. (`APPCOMMAND_BROWSER_SEARCH`) + BrowserSearch, + /// Stop loading the current page or content. (`APPCOMMAND_BROWSER_STOP`) + BrowserStop, + /// The Application switch key, which provides a list of recent apps to switch between. + /// (`KEYCODE_APP_SWITCH`) + AppSwitch, + /// The Call key. (`KEYCODE_CALL`) + Call, + /// The Camera key. (`KEYCODE_CAMERA`) + Camera, + /// The Camera focus key. (`KEYCODE_FOCUS`) + CameraFocus, + /// The End Call key. (`KEYCODE_ENDCALL`) + EndCall, + /// The Back key. (`KEYCODE_BACK`) + GoBack, + /// The Home key, which goes to the phone’s main screen. (`KEYCODE_HOME`) + GoHome, + /// The Headset Hook key. (`KEYCODE_HEADSETHOOK`) + HeadsetHook, + LastNumberRedial, + /// The Notification key. (`KEYCODE_NOTIFICATION`) + Notification, + /// Toggle between manner mode state: silent, vibrate, ring, ... (`KEYCODE_MANNER_MODE`) + MannerMode, + VoiceDial, + /// Switch to viewing TV. (`KEYCODE_TV`) + TV, + /// TV 3D Mode. (`KEYCODE_3D_MODE`) + TV3DMode, + /// Toggle between antenna and cable input. (`KEYCODE_TV_ANTENNA_CABLE`) + TVAntennaCable, + /// Audio description. (`KEYCODE_TV_AUDIO_DESCRIPTION`) + TVAudioDescription, + /// Audio description mixing volume down. (`KEYCODE_TV_AUDIO_DESCRIPTION_MIX_DOWN`) + TVAudioDescriptionMixDown, + /// Audio description mixing volume up. (`KEYCODE_TV_AUDIO_DESCRIPTION_MIX_UP`) + TVAudioDescriptionMixUp, + /// Contents menu. (`KEYCODE_TV_CONTENTS_MENU`) + TVContentsMenu, + /// Contents menu. (`KEYCODE_TV_DATA_SERVICE`) + TVDataService, + /// Switch the input mode on an external TV. (`KEYCODE_TV_INPUT`) + TVInput, + /// Switch to component input #1. (`KEYCODE_TV_INPUT_COMPONENT_1`) + TVInputComponent1, + /// Switch to component input #2. (`KEYCODE_TV_INPUT_COMPONENT_2`) + TVInputComponent2, + /// Switch to composite input #1. (`KEYCODE_TV_INPUT_COMPOSITE_1`) + TVInputComposite1, + /// Switch to composite input #2. (`KEYCODE_TV_INPUT_COMPOSITE_2`) + TVInputComposite2, + /// Switch to HDMI input #1. (`KEYCODE_TV_INPUT_HDMI_1`) + TVInputHDMI1, + /// Switch to HDMI input #2. (`KEYCODE_TV_INPUT_HDMI_2`) + TVInputHDMI2, + /// Switch to HDMI input #3. (`KEYCODE_TV_INPUT_HDMI_3`) + TVInputHDMI3, + /// Switch to HDMI input #4. (`KEYCODE_TV_INPUT_HDMI_4`) + TVInputHDMI4, + /// Switch to VGA input #1. (`KEYCODE_TV_INPUT_VGA_1`) + TVInputVGA1, + /// Media context menu. (`KEYCODE_TV_MEDIA_CONTEXT_MENU`) + TVMediaContext, + /// Toggle network. (`KEYCODE_TV_NETWORK`) + TVNetwork, + /// Number entry. (`KEYCODE_TV_NUMBER_ENTRY`) + TVNumberEntry, + /// Toggle the power on an external TV. (`KEYCODE_TV_POWER`) + TVPower, + /// Radio. (`KEYCODE_TV_RADIO_SERVICE`) + TVRadioService, + /// Satellite. (`KEYCODE_TV_SATELLITE`) + TVSatellite, + /// Broadcast Satellite. (`KEYCODE_TV_SATELLITE_BS`) + TVSatelliteBS, + /// Communication Satellite. (`KEYCODE_TV_SATELLITE_CS`) + TVSatelliteCS, + /// Toggle between available satellites. (`KEYCODE_TV_SATELLITE_SERVICE`) + TVSatelliteToggle, + /// Analog Terrestrial. (`KEYCODE_TV_TERRESTRIAL_ANALOG`) + TVTerrestrialAnalog, + /// Digital Terrestrial. (`KEYCODE_TV_TERRESTRIAL_DIGITAL`) + TVTerrestrialDigital, + /// Timer programming. (`KEYCODE_TV_TIMER_PROGRAMMING`) + TVTimer, + /// Switch the input mode on an external AVR (audio/video receiver). (`KEYCODE_AVR_INPUT`) + AVRInput, + /// Toggle the power on an external AVR (audio/video receiver). (`KEYCODE_AVR_POWER`) + AVRPower, + /// General purpose color-coded media function key, as index 0 (red). (`VK_COLORED_KEY_0`, + /// `KEYCODE_PROG_RED`) + ColorF0Red, + /// General purpose color-coded media function key, as index 1 (green). (`VK_COLORED_KEY_1`, + /// `KEYCODE_PROG_GREEN`) + ColorF1Green, + /// General purpose color-coded media function key, as index 2 (yellow). (`VK_COLORED_KEY_2`, + /// `KEYCODE_PROG_YELLOW`) + ColorF2Yellow, + /// General purpose color-coded media function key, as index 3 (blue). (`VK_COLORED_KEY_3`, + /// `KEYCODE_PROG_BLUE`) + ColorF3Blue, + /// General purpose color-coded media function key, as index 4 (grey). (`VK_COLORED_KEY_4`) + ColorF4Grey, + /// General purpose color-coded media function key, as index 5 (brown). (`VK_COLORED_KEY_5`) + ColorF5Brown, + /// Toggle the display of Closed Captions. (`VK_CC`, `KEYCODE_CAPTIONS`) + ClosedCaptionToggle, + /// Adjust brightness of device, by toggling between or cycling through states. (`VK_DIMMER`) + Dimmer, + /// Swap video sources. (`VK_DISPLAY_SWAP`) + DisplaySwap, + /// Select Digital Video Rrecorder. (`KEYCODE_DVR`) + DVR, + /// Exit the current application. (`VK_EXIT`) + Exit, + /// Clear program or content stored as favorite 0. (`VK_CLEAR_FAVORITE_0`) + FavoriteClear0, + /// Clear program or content stored as favorite 1. (`VK_CLEAR_FAVORITE_1`) + FavoriteClear1, + /// Clear program or content stored as favorite 2. (`VK_CLEAR_FAVORITE_2`) + FavoriteClear2, + /// Clear program or content stored as favorite 3. (`VK_CLEAR_FAVORITE_3`) + FavoriteClear3, + /// Select (recall) program or content stored as favorite 0. (`VK_RECALL_FAVORITE_0`) + FavoriteRecall0, + /// Select (recall) program or content stored as favorite 1. (`VK_RECALL_FAVORITE_1`) + FavoriteRecall1, + /// Select (recall) program or content stored as favorite 2. (`VK_RECALL_FAVORITE_2`) + FavoriteRecall2, + /// Select (recall) program or content stored as favorite 3. (`VK_RECALL_FAVORITE_3`) + FavoriteRecall3, + /// Store current program or content as favorite 0. (`VK_STORE_FAVORITE_0`) + FavoriteStore0, + /// Store current program or content as favorite 1. (`VK_STORE_FAVORITE_1`) + FavoriteStore1, + /// Store current program or content as favorite 2. (`VK_STORE_FAVORITE_2`) + FavoriteStore2, + /// Store current program or content as favorite 3. (`VK_STORE_FAVORITE_3`) + FavoriteStore3, + /// Toggle display of program or content guide. (`VK_GUIDE`, `KEYCODE_GUIDE`) + Guide, + /// If guide is active and displayed, then display next day’s content. (`VK_NEXT_DAY`) + GuideNextDay, + /// If guide is active and displayed, then display previous day’s content. (`VK_PREV_DAY`) + GuidePreviousDay, + /// Toggle display of information about currently selected context or media. (`VK_INFO`, + /// `KEYCODE_INFO`) + Info, + /// Toggle instant replay. (`VK_INSTANT_REPLAY`) + InstantReplay, + /// Launch linked content, if available and appropriate. (`VK_LINK`) + Link, + /// List the current program. (`VK_LIST`) + ListProgram, + /// Toggle display listing of currently available live content or programs. (`VK_LIVE`) + LiveContent, + /// Lock or unlock current content or program. (`VK_LOCK`) + Lock, + /// Show a list of media applications: audio/video players and image viewers. (`VK_APPS`) + /// + /// Note: Do not confuse this key value with the Windows' `VK_APPS` / `VK_CONTEXT_MENU` key, + /// which is encoded as `"ContextMenu"`. + MediaApps, + /// Audio track key. (`KEYCODE_MEDIA_AUDIO_TRACK`) + MediaAudioTrack, + /// Select previously selected channel or media. (`VK_LAST`, `KEYCODE_LAST_CHANNEL`) + MediaLast, + /// Skip backward to next content or program. (`KEYCODE_MEDIA_SKIP_BACKWARD`) + MediaSkipBackward, + /// Skip forward to next content or program. (`VK_SKIP`, `KEYCODE_MEDIA_SKIP_FORWARD`) + MediaSkipForward, + /// Step backward to next content or program. (`KEYCODE_MEDIA_STEP_BACKWARD`) + MediaStepBackward, + /// Step forward to next content or program. (`KEYCODE_MEDIA_STEP_FORWARD`) + MediaStepForward, + /// Media top menu. (`KEYCODE_MEDIA_TOP_MENU`) + MediaTopMenu, + /// Navigate in. (`KEYCODE_NAVIGATE_IN`) + NavigateIn, + /// Navigate to next key. (`KEYCODE_NAVIGATE_NEXT`) + NavigateNext, + /// Navigate out. (`KEYCODE_NAVIGATE_OUT`) + NavigateOut, + /// Navigate to previous key. (`KEYCODE_NAVIGATE_PREVIOUS`) + NavigatePrevious, + /// Cycle to next favorite channel (in favorites list). (`VK_NEXT_FAVORITE_CHANNEL`) + NextFavoriteChannel, + /// Cycle to next user profile (if there are multiple user profiles). (`VK_USER`) + NextUserProfile, + /// Access on-demand content or programs. (`VK_ON_DEMAND`) + OnDemand, + /// Pairing key to pair devices. (`KEYCODE_PAIRING`) + Pairing, + /// Move picture-in-picture window down. (`VK_PINP_DOWN`) + PinPDown, + /// Move picture-in-picture window. (`VK_PINP_MOVE`) + PinPMove, + /// Toggle display of picture-in-picture window. (`VK_PINP_TOGGLE`) + PinPToggle, + /// Move picture-in-picture window up. (`VK_PINP_UP`) + PinPUp, + /// Decrease media playback speed. (`VK_PLAY_SPEED_DOWN`) + PlaySpeedDown, + /// Reset playback to normal speed. (`VK_PLAY_SPEED_RESET`) + PlaySpeedReset, + /// Increase media playback speed. (`VK_PLAY_SPEED_UP`) + PlaySpeedUp, + /// Toggle random media or content shuffle mode. (`VK_RANDOM_TOGGLE`) + RandomToggle, + /// Not a physical key, but this key code is sent when the remote control battery is low. + /// (`VK_RC_LOW_BATTERY`) + RcLowBattery, + /// Toggle or cycle between media recording speeds. (`VK_RECORD_SPEED_NEXT`) + RecordSpeedNext, + /// Toggle RF (radio frequency) input bypass mode (pass RF input directly to the RF output). + /// (`VK_RF_BYPASS`) + RfBypass, + /// Toggle scan channels mode. (`VK_SCAN_CHANNELS_TOGGLE`) + ScanChannelsToggle, + /// Advance display screen mode to next available mode. (`VK_SCREEN_MODE_NEXT`) + ScreenModeNext, + /// Toggle display of device settings screen. (`VK_SETTINGS`, `KEYCODE_SETTINGS`) + Settings, + /// Toggle split screen mode. (`VK_SPLIT_SCREEN_TOGGLE`) + SplitScreenToggle, + /// Switch the input mode on an external STB (set top box). (`KEYCODE_STB_INPUT`) + STBInput, + /// Toggle the power on an external STB (set top box). (`KEYCODE_STB_POWER`) + STBPower, + /// Toggle display of subtitles, if available. (`VK_SUBTITLE`) + Subtitle, + /// Toggle display of teletext, if available (`VK_TELETEXT`, `KEYCODE_TV_TELETEXT`). + Teletext, + /// Advance video mode to next available mode. (`VK_VIDEO_MODE_NEXT`) + VideoModeNext, + /// Cause device to identify itself in some manner, e.g., audibly or visibly. (`VK_WINK`) + Wink, + /// Toggle between full-screen and scaled content, or alter magnification level. (`VK_ZOOM`, + /// `KEYCODE_TV_ZOOM_MODE`) + ZoomToggle, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F1, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F2, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F3, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F4, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F5, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F6, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F7, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F8, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F9, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F10, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F11, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F12, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F13, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F14, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F15, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F16, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F17, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F18, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F19, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F20, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F21, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F22, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F23, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F24, + /// General-purpose function key. + F25, + /// General-purpose function key. + F26, + /// General-purpose function key. + F27, + /// General-purpose function key. + F28, + /// General-purpose function key. + F29, + /// General-purpose function key. + F30, + /// General-purpose function key. + F31, + /// General-purpose function key. + F32, + /// General-purpose function key. + F33, + /// General-purpose function key. + F34, + /// General-purpose function key. + F35, +} + +impl<'a> Key<'a> { + pub fn to_text(&self) -> Option<&'a str> { + match self { + Key::Character(ch) => Some(*ch), + Key::Enter => Some("\r"), + Key::Backspace => Some("\x08"), + Key::Tab => Some("\t"), + Key::Space => Some(" "), + Key::Escape => Some("\x1b"), + _ => None, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum KeyLocation { + Standard, + Left, + Right, + Numpad, +} diff --git a/src/lib.rs b/src/lib.rs index f6276a2f5c..9d2a93b82f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -154,6 +154,7 @@ pub mod error; pub mod event; pub mod event_loop; mod icon; +pub mod keyboard; pub mod monitor; mod platform_impl; pub mod window; diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 91c5077739..1985b7d7d6 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -32,6 +32,7 @@ pub mod windows; #[cfg(x11_platform)] pub mod x11; +pub mod modifier_supplement; #[cfg(any( windows_platform, macos_platform, @@ -41,3 +42,4 @@ pub mod x11; orbital_platform ))] pub mod run_return; +pub mod scancode; diff --git a/src/platform/modifier_supplement.rs b/src/platform/modifier_supplement.rs new file mode 100644 index 0000000000..361ed05d04 --- /dev/null +++ b/src/platform/modifier_supplement.rs @@ -0,0 +1,32 @@ +#![cfg(any( + target_os = "windows", + target_os = "macos", + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" +))] + +use crate::keyboard::Key; + +/// Additional methods for the `KeyEvent` which cannot be implemented on all +/// platforms. +pub trait KeyEventExtModifierSupplement { + /// Identical to `KeyEvent::text` but this is affected by Ctrl. + /// + /// For example, pressing Ctrl+a produces `Some("\x01")`. + fn text_with_all_modifiers(&self) -> Option<&str>; + + /// This value ignores all modifiers including, + /// but not limited to Shift, Caps Lock, + /// and Ctrl. In most cases this means that the + /// unicode character in the resulting string is lowercase. + /// + /// This is useful for key-bindings / shortcut key combinations. + /// + /// In case `logical_key` reports `Dead`, this will still report the + /// key as `Character` according to the current keyboard layout. This value + /// cannot be `Dead`. + fn key_without_modifiers(&self) -> Key<'static>; +} diff --git a/src/platform/scancode.rs b/src/platform/scancode.rs new file mode 100644 index 0000000000..00188c6212 --- /dev/null +++ b/src/platform/scancode.rs @@ -0,0 +1,32 @@ +#![cfg(any( + target_os = "windows", + target_os = "macos", + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" +))] + +// TODO: Maybe merge this with `modifier_supplement` if the two are indeed supported on the same +// set of platforms + +use crate::keyboard::KeyCode; + +pub trait KeyCodeExtScancode { + /// The raw value of the platform-specific physical key identifier. + /// + /// Returns `Some(key_id)` if the conversion was succesful; returns `None` otherwise. + /// + /// ## Platform-specific + /// - **Windows:** A 16bit extended scancode + /// - **Wayland/X11**: A 32-bit X11-style keycode. + // TODO: Describe what this value contains for each platform + fn to_scancode(self) -> Option; + + /// Constructs a `KeyCode` from a platform-specific physical key identifier. + /// + /// Note that this conversion may be lossy, i.e. converting the returned `KeyCode` back + /// using `to_scancode` might not yield the original value. + fn from_scancode(scancode: u32) -> KeyCode; +} diff --git a/src/window.rs b/src/window.rs index 50d8cd2487..deaffb719f 100644 --- a/src/window.rs +++ b/src/window.rs @@ -544,6 +544,22 @@ impl Window { pub fn request_redraw(&self) { self.window.request_redraw() } + + /// Reset the dead key state of the keyboard. + /// + /// This is useful when a dead key is bound to trigger an action. Then + /// this function can be called to reset the dead key state so that + /// follow-up text input won't be affected by the dead key. + /// + /// ## Platform-specific + /// - **Web, macOS:** Does nothing + // --------------------------- + // Developers' Note: If this cannot be implemented on every desktop platform + // at least, then this function should be provided through a platform specific + // extension trait + pub fn reset_dead_keys(&self) { + self.window.reset_dead_keys(); + } } /// Position and size functions. @@ -1030,14 +1046,12 @@ impl Window { /// Sets whether the window should get IME events /// /// When IME is allowed, the window will receive [`Ime`] events, and during the - /// preedit phase the window will NOT get [`KeyboardInput`] or - /// [`ReceivedCharacter`] events. The window should allow IME while it is - /// expecting text input. + /// preedit phase the window will NOT get [`KeyboardInput`] events. The window + /// should allow IME while it is expecting text input. /// /// When IME is not allowed, the window won't receive [`Ime`] events, and will - /// receive [`KeyboardInput`] events for every keypress instead. Without - /// allowing IME, the window will also get [`ReceivedCharacter`] events for - /// certain keyboard input. Not allowing IME is useful for games for example. + /// receive [`KeyboardInput`] events for every keypress instead. Not allowing + /// IME is useful for games for example. /// /// IME is **not** allowed by default. /// @@ -1048,7 +1062,6 @@ impl Window { /// /// [`Ime`]: crate::event::WindowEvent::Ime /// [`KeyboardInput`]: crate::event::WindowEvent::KeyboardInput - /// [`ReceivedCharacter`]: crate::event::WindowEvent::ReceivedCharacter #[inline] pub fn set_ime_allowed(&self, allowed: bool) { self.window.set_ime_allowed(allowed); From ede27552bea57880e92e8c069a2217dd4bd4be31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20R=C3=B8yset?= Date: Fri, 9 Dec 2022 09:21:15 +0100 Subject: [PATCH 02/13] Add an example and adjust the rest and a test --- examples/child_window.rs | 6 +- examples/control_flow.rs | 25 ++-- examples/cursor.rs | 6 +- examples/cursor_grab.rs | 29 +++-- examples/drag_window.rs | 11 +- examples/fullscreen.rs | 115 +++++++++-------- examples/handling_close.rs | 23 ++-- examples/ime.rs | 21 +-- examples/key_binding.rs | 59 +++++++++ examples/multithreaded.rs | 183 ++++++++++++++------------- examples/multiwindow.rs | 9 +- examples/resizable.rs | 9 +- examples/theme.rs | 15 ++- examples/window_buttons.rs | 15 ++- examples/window_debug.rs | 41 +++--- examples/window_drag_resize.rs | 11 +- examples/window_option_as_alt.rs | 1 - examples/window_resize_increments.rs | 9 +- tests/serde_objects.rs | 11 +- 19 files changed, 339 insertions(+), 260 deletions(-) create mode 100644 examples/key_binding.rs diff --git a/examples/child_window.rs b/examples/child_window.rs index b1b8f95e82..bc103341a1 100644 --- a/examples/child_window.rs +++ b/examples/child_window.rs @@ -5,7 +5,7 @@ fn main() { use raw_window_handle::HasRawWindowHandle; use winit::{ dpi::{LogicalPosition, LogicalSize, Position}, - event::{ElementState, Event, KeyboardInput, WindowEvent}, + event::{ElementState, Event, KeyEvent, WindowEvent}, event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget}, window::{Window, WindowBuilder, WindowId}, }; @@ -59,8 +59,8 @@ fn main() { println!("cursor entered in the window {window_id:?}"); } WindowEvent::KeyboardInput { - input: - KeyboardInput { + event: + KeyEvent { state: ElementState::Pressed, .. }, diff --git a/examples/control_flow.rs b/examples/control_flow.rs index bade79d279..4360efa871 100644 --- a/examples/control_flow.rs +++ b/examples/control_flow.rs @@ -4,8 +4,9 @@ use std::{thread, time}; use simple_logger::SimpleLogger; use winit::{ - event::{Event, KeyboardInput, WindowEvent}, + event::{ElementState, Event, KeyEvent, WindowEvent}, event_loop::EventLoop, + keyboard::Key, window::WindowBuilder, }; @@ -40,7 +41,7 @@ fn main() { let mut close_requested = false; event_loop.run(move |event, _, control_flow| { - use winit::event::{ElementState, StartCause, VirtualKeyCode}; + use winit::event::StartCause; println!("{event:?}"); match event { Event::NewEvents(start_cause) => { @@ -54,31 +55,33 @@ fn main() { close_requested = true; } WindowEvent::KeyboardInput { - input: - KeyboardInput { - virtual_keycode: Some(virtual_code), + event: + KeyEvent { + logical_key: key, state: ElementState::Pressed, .. }, .. - } => match virtual_code { - VirtualKeyCode::Key1 => { + } => match key { + // WARNING: Consider using `key_without_modifers()` if available on your platform. + // See the `key_binding` example + Key::Character("1") => { mode = Mode::Wait; println!("\nmode: {mode:?}\n"); } - VirtualKeyCode::Key2 => { + Key::Character("2") => { mode = Mode::WaitUntil; println!("\nmode: {mode:?}\n"); } - VirtualKeyCode::Key3 => { + Key::Character("3") => { mode = Mode::Poll; println!("\nmode: {mode:?}\n"); } - VirtualKeyCode::R => { + Key::Character("r") => { request_redraw = !request_redraw; println!("\nrequest_redraw: {request_redraw}\n"); } - VirtualKeyCode::Escape => { + Key::Escape => { close_requested = true; } _ => (), diff --git a/examples/cursor.rs b/examples/cursor.rs index fdef7ef971..d7b5b82625 100644 --- a/examples/cursor.rs +++ b/examples/cursor.rs @@ -2,7 +2,7 @@ use simple_logger::SimpleLogger; use winit::{ - event::{ElementState, Event, KeyboardInput, WindowEvent}, + event::{ElementState, Event, KeyEvent, WindowEvent}, event_loop::EventLoop, window::{CursorIcon, WindowBuilder}, }; @@ -23,8 +23,8 @@ fn main() { Event::WindowEvent { event: WindowEvent::KeyboardInput { - input: - KeyboardInput { + event: + KeyEvent { state: ElementState::Pressed, .. }, diff --git a/examples/cursor_grab.rs b/examples/cursor_grab.rs index 79253e106a..90d6bbf3b5 100644 --- a/examples/cursor_grab.rs +++ b/examples/cursor_grab.rs @@ -2,8 +2,9 @@ use simple_logger::SimpleLogger; use winit::{ - event::{DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, WindowEvent}, + event::{DeviceEvent, ElementState, Event, KeyEvent, WindowEvent}, event_loop::EventLoop, + keyboard::{Key, ModifiersState}, window::{CursorGrabMode, WindowBuilder}, }; @@ -25,27 +26,29 @@ fn main() { Event::WindowEvent { event, .. } => match event { WindowEvent::CloseRequested => control_flow.set_exit(), WindowEvent::KeyboardInput { - input: - KeyboardInput { + event: + KeyEvent { + logical_key: key, state: ElementState::Released, - virtual_keycode: Some(key), .. }, .. } => { - use winit::event::VirtualKeyCode::*; let result = match key { - Escape => { + Key::Escape => { control_flow.set_exit(); Ok(()) } - G => window.set_cursor_grab(CursorGrabMode::Confined), - L => window.set_cursor_grab(CursorGrabMode::Locked), - A => window.set_cursor_grab(CursorGrabMode::None), - H => { - window.set_cursor_visible(modifiers.shift()); - Ok(()) - } + Key::Character(ch) => match ch.to_lowercase().as_str() { + "g" => window.set_cursor_grab(CursorGrabMode::Confined), + "l" => window.set_cursor_grab(CursorGrabMode::Locked), + "a" => window.set_cursor_grab(CursorGrabMode::None), + "h" => { + window.set_cursor_visible(modifiers.shift_key()); + Ok(()) + } + _ => Ok(()), + }, _ => Ok(()), }; diff --git a/examples/drag_window.rs b/examples/drag_window.rs index 813e9b00c9..7a8b3e7dcf 100644 --- a/examples/drag_window.rs +++ b/examples/drag_window.rs @@ -2,10 +2,9 @@ use simple_logger::SimpleLogger; use winit::{ - event::{ - ElementState, Event, KeyboardInput, MouseButton, StartCause, VirtualKeyCode, WindowEvent, - }, + event::{ElementState, Event, KeyEvent, MouseButton, StartCause, WindowEvent}, event_loop::EventLoop, + keyboard::Key, window::{Window, WindowBuilder, WindowId}, }; @@ -45,10 +44,10 @@ fn main() { name_windows(entered_id, switched, &window_1, &window_2) } WindowEvent::KeyboardInput { - input: - KeyboardInput { + event: + KeyEvent { state: ElementState::Released, - virtual_keycode: Some(VirtualKeyCode::X), + logical_key: Key::Character("x"), .. }, .. diff --git a/examples/fullscreen.rs b/examples/fullscreen.rs index a71d079f28..ab2d0582a0 100644 --- a/examples/fullscreen.rs +++ b/examples/fullscreen.rs @@ -1,8 +1,9 @@ #![allow(clippy::single_match)] use simple_logger::SimpleLogger; -use winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}; +use winit::event::{ElementState, Event, KeyEvent, WindowEvent}; use winit::event_loop::EventLoop; +use winit::keyboard::Key; use winit::window::{Fullscreen, WindowBuilder}; #[cfg(target_os = "macos")] @@ -50,68 +51,74 @@ fn main() { Event::WindowEvent { event, .. } => match event { WindowEvent::CloseRequested => control_flow.set_exit(), WindowEvent::KeyboardInput { - input: - KeyboardInput { - virtual_keycode: Some(virtual_code), + event: + KeyEvent { + logical_key: key, state: ElementState::Pressed, .. }, .. - } => match virtual_code { - VirtualKeyCode::Escape => control_flow.set_exit(), - VirtualKeyCode::F | VirtualKeyCode::B if window.fullscreen().is_some() => { - window.set_fullscreen(None); - } - VirtualKeyCode::F => { - let fullscreen = Some(Fullscreen::Exclusive(mode.clone())); - println!("Setting mode: {fullscreen:?}"); - window.set_fullscreen(fullscreen); - } - VirtualKeyCode::B => { - let fullscreen = Some(Fullscreen::Borderless(Some(monitor.clone()))); - println!("Setting mode: {fullscreen:?}"); - window.set_fullscreen(fullscreen); - } - #[cfg(target_os = "macos")] - VirtualKeyCode::C => { - window.set_simple_fullscreen(!window.simple_fullscreen()); - } - VirtualKeyCode::S => { - monitor_index += 1; - if let Some(mon) = elwt.available_monitors().nth(monitor_index) { - monitor = mon; - } else { - monitor_index = 0; - monitor = elwt.available_monitors().next().expect("no monitor found!"); + } => match key { + Key::Escape => control_flow.set_exit(), + // WARNING: Consider using `key_without_modifers()` if available on your platform. + // See the `key_binding` example + Key::Character(ch) => match ch.to_lowercase().as_str() { + "f" | "b" if window.fullscreen().is_some() => { + window.set_fullscreen(None); } - println!("Monitor: {:?}", monitor.name()); + "f" => { + let fullscreen = Some(Fullscreen::Exclusive(mode.clone())); + println!("Setting mode: {fullscreen:?}"); + window.set_fullscreen(fullscreen); + } + "b" => { + let fullscreen = Some(Fullscreen::Borderless(Some(monitor.clone()))); + println!("Setting mode: {fullscreen:?}"); + window.set_fullscreen(fullscreen); + } + #[cfg(target_os = "macos")] + "c" => { + window.set_simple_fullscreen(!window.simple_fullscreen()); + } + "s" => { + monitor_index += 1; + if let Some(mon) = elwt.available_monitors().nth(monitor_index) { + monitor = mon; + } else { + monitor_index = 0; + monitor = + elwt.available_monitors().next().expect("no monitor found!"); + } + println!("Monitor: {:?}", monitor.name()); - mode_index = 0; - mode = monitor.video_modes().next().expect("no mode found"); - println!("Mode: {mode}"); - } - VirtualKeyCode::M => { - mode_index += 1; - if let Some(m) = monitor.video_modes().nth(mode_index) { - mode = m; - } else { mode_index = 0; mode = monitor.video_modes().next().expect("no mode found"); + println!("Mode: {mode}"); + } + "m" => { + mode_index += 1; + if let Some(m) = monitor.video_modes().nth(mode_index) { + mode = m; + } else { + mode_index = 0; + mode = monitor.video_modes().next().expect("no mode found"); + } + println!("Mode: {mode}"); + } + "d" => { + decorations = !decorations; + window.set_decorations(decorations); + } + "x" => { + let is_maximized = window.is_maximized(); + window.set_maximized(!is_maximized); + } + "z" => { + minimized = !minimized; + window.set_minimized(minimized); } - println!("Mode: {mode}"); - } - VirtualKeyCode::D => { - decorations = !decorations; - window.set_decorations(decorations); - } - VirtualKeyCode::X => { - let is_maximized = window.is_maximized(); - window.set_maximized(!is_maximized); - } - VirtualKeyCode::Z => { - minimized = !minimized; - window.set_minimized(minimized); - } + _ => (), + }, _ => (), }, _ => (), diff --git a/examples/handling_close.rs b/examples/handling_close.rs index 1fe4ad3708..12a12da94e 100644 --- a/examples/handling_close.rs +++ b/examples/handling_close.rs @@ -2,8 +2,9 @@ use simple_logger::SimpleLogger; use winit::{ - event::{Event, KeyboardInput, WindowEvent}, + event::{ElementState, Event, KeyEvent, WindowEvent}, event_loop::EventLoop, + keyboard::Key, window::WindowBuilder, }; @@ -19,10 +20,6 @@ fn main() { let mut close_requested = false; event_loop.run(move |event, _, control_flow| { - use winit::event::{ - ElementState::Released, - VirtualKeyCode::{N, Y}, - }; control_flow.set_wait(); match event { @@ -46,16 +43,18 @@ fn main() { // the Y key. } WindowEvent::KeyboardInput { - input: - KeyboardInput { - virtual_keycode: Some(virtual_code), - state: Released, + event: + KeyEvent { + logical_key: key, + state: ElementState::Released, .. }, .. } => { - match virtual_code { - Y => { + // WARNING: Consider using `key_without_modifers()` if available on your platform. + // See the `key_binding` example + match key { + Key::Character("y") => { if close_requested { // This is where you'll want to do any cleanup you need. println!("Buh-bye!"); @@ -68,7 +67,7 @@ fn main() { control_flow.set_exit(); } } - N => { + Key::Character("n") => { if close_requested { println!("Your window will continue to stay by your side."); close_requested = false; diff --git a/examples/ime.rs b/examples/ime.rs index 59f43d4a33..d9833851db 100644 --- a/examples/ime.rs +++ b/examples/ime.rs @@ -4,8 +4,9 @@ use log::LevelFilter; use simple_logger::SimpleLogger; use winit::{ dpi::PhysicalPosition, - event::{ElementState, Event, Ime, VirtualKeyCode, WindowEvent}, + event::{ElementState, Event, Ime, WindowEvent}, event_loop::{ControlFlow, EventLoop}, + keyboard::{Key, KeyCode}, window::{ImePurpose, WindowBuilder}, }; @@ -76,27 +77,17 @@ fn main() { } } Event::WindowEvent { - event: WindowEvent::ReceivedCharacter(ch), + event: WindowEvent::KeyboardInput { event, .. }, .. } => { - println!("ch: {ch:?}"); - } - Event::WindowEvent { - event: WindowEvent::KeyboardInput { input, .. }, - .. - } => { - println!("key: {input:?}"); + println!("key: {event:?}"); - if input.state == ElementState::Pressed - && input.virtual_keycode == Some(VirtualKeyCode::F2) - { + if event.state == ElementState::Pressed && event.physical_key == KeyCode::F2 { ime_allowed = !ime_allowed; window.set_ime_allowed(ime_allowed); println!("\nIME allowed: {ime_allowed}\n"); } - if input.state == ElementState::Pressed - && input.virtual_keycode == Some(VirtualKeyCode::F3) - { + if event.state == ElementState::Pressed && event.logical_key == Key::F3 { ime_purpose = match ime_purpose { ImePurpose::Normal => ImePurpose::Password, ImePurpose::Password => ImePurpose::Terminal, diff --git a/examples/key_binding.rs b/examples/key_binding.rs new file mode 100644 index 0000000000..44a6b641f6 --- /dev/null +++ b/examples/key_binding.rs @@ -0,0 +1,59 @@ +#![allow(clippy::single_match)] + +#[cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))] +use winit::{ + dpi::LogicalSize, + event::{ElementState, Event, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + keyboard::{Key, ModifiersState}, + // WARNING: This is not available on all platforms (for example on the web). + platform::modifier_supplement::KeyEventExtModifierSupplement, + window::WindowBuilder, +}; + +#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))] +fn main() { + println!("This example is not supported on this platform"); +} + +#[cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))] +fn main() { + simple_logger::SimpleLogger::new().init().unwrap(); + let event_loop = EventLoop::new(); + + let _window = WindowBuilder::new() + .with_inner_size(LogicalSize::new(400.0, 200.0)) + .build(&event_loop) + .unwrap(); + + let mut modifiers = ModifiersState::default(); + + event_loop.run(move |event, _, control_flow| { + *control_flow = ControlFlow::Wait; + + match event { + Event::WindowEvent { event, .. } => match event { + WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, + WindowEvent::ModifiersChanged(new_state) => { + modifiers = new_state; + } + WindowEvent::KeyboardInput { event, .. } => { + if event.state == ElementState::Pressed && !event.repeat { + match event.key_without_modifiers() { + Key::Character("1") => { + if modifiers.shift_key() { + println!("Shift + 1 | logical_key: {:?}", event.logical_key); + } else { + println!("1"); + } + } + _ => (), + } + } + } + _ => (), + }, + _ => (), + }; + }); +} diff --git a/examples/multithreaded.rs b/examples/multithreaded.rs index 7e03329d83..006942774c 100644 --- a/examples/multithreaded.rs +++ b/examples/multithreaded.rs @@ -7,8 +7,9 @@ fn main() { use simple_logger::SimpleLogger; use winit::{ dpi::{PhysicalPosition, PhysicalSize, Position, Size}, - event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, + event::{ElementState, Event, KeyEvent, WindowEvent}, event_loop::EventLoop, + keyboard::{Key, ModifiersState}, window::{CursorGrabMode, CursorIcon, Fullscreen, WindowBuilder, WindowLevel}, }; @@ -29,6 +30,7 @@ fn main() { let (tx, rx) = mpsc::channel(); window_senders.insert(window.id(), tx); + let mut modifiers = ModifiersState::default(); thread::spawn(move || { while let Ok(event) = rx.recv() { match event { @@ -51,107 +53,116 @@ fn main() { ); } } - #[allow(deprecated)] + WindowEvent::ModifiersChanged(mod_state) => { + modifiers = mod_state; + } WindowEvent::KeyboardInput { - input: - KeyboardInput { + event: + KeyEvent { state: ElementState::Released, - virtual_keycode: Some(key), - modifiers, + logical_key: key, .. }, .. } => { + use Key::{ArrowLeft, ArrowRight}; window.set_title(&format!("{key:?}")); - let state = !modifiers.shift(); - use VirtualKeyCode::*; + let state = !modifiers.shift_key(); match key { - Key1 => window.set_window_level(WindowLevel::AlwaysOnTop), - Key2 => window.set_window_level(WindowLevel::AlwaysOnBottom), - Key3 => window.set_window_level(WindowLevel::Normal), - C => window.set_cursor_icon(match state { - true => CursorIcon::Progress, - false => CursorIcon::Default, - }), - D => window.set_decorations(!state), // Cycle through video modes - Right | Left => { + Key::ArrowRight | Key::ArrowLeft => { video_mode_id = match key { - Left => video_mode_id.saturating_sub(1), - Right => (video_modes.len() - 1).min(video_mode_id + 1), + ArrowLeft => video_mode_id.saturating_sub(1), + ArrowRight => (video_modes.len() - 1).min(video_mode_id + 1), _ => unreachable!(), }; println!("Picking video mode: {}", video_modes[video_mode_id]); } - F => window.set_fullscreen(match (state, modifiers.alt()) { - (true, false) => Some(Fullscreen::Borderless(None)), - (true, true) => { - Some(Fullscreen::Exclusive(video_modes[video_mode_id].clone())) + // WARNING: Consider using `key_without_modifers()` if available on your platform. + // See the `key_binding` example + Key::Character(ch) => match ch.to_lowercase().as_str() { + "1" => window.set_window_level(WindowLevel::AlwaysOnTop), + "2" => window.set_window_level(WindowLevel::AlwaysOnBottom), + "3" => window.set_window_level(WindowLevel::Normal), + "c" => window.set_cursor_icon(match state { + true => CursorIcon::Progress, + false => CursorIcon::Default, + }), + "d" => window.set_decorations(!state), + "f" => window.set_fullscreen(match (state, modifiers.alt_key()) { + (true, false) => Some(Fullscreen::Borderless(None)), + (true, true) => Some(Fullscreen::Exclusive( + video_modes[video_mode_id].clone(), + )), + (false, _) => None, + }), + "l" if state => { + if let Err(err) = window.set_cursor_grab(CursorGrabMode::Locked) + { + println!("error: {err}"); + } } - (false, _) => None, - }), - L if state => { - if let Err(err) = window.set_cursor_grab(CursorGrabMode::Locked) { - println!("error: {err}"); + "g" if state => { + if let Err(err) = + window.set_cursor_grab(CursorGrabMode::Confined) + { + println!("error: {err}"); + } } - } - G if state => { - if let Err(err) = window.set_cursor_grab(CursorGrabMode::Confined) { - println!("error: {err}"); + "g" | "l" if !state => { + if let Err(err) = window.set_cursor_grab(CursorGrabMode::None) { + println!("error: {err}"); + } } - } - G | L if !state => { - if let Err(err) = window.set_cursor_grab(CursorGrabMode::None) { - println!("error: {err}"); + "h" => window.set_cursor_visible(!state), + "i" => { + println!("Info:"); + println!("-> outer_position : {:?}", window.outer_position()); + println!("-> inner_position : {:?}", window.inner_position()); + println!("-> outer_size : {:?}", window.outer_size()); + println!("-> inner_size : {:?}", window.inner_size()); + println!("-> fullscreen : {:?}", window.fullscreen()); } - } - H => window.set_cursor_visible(!state), - I => { - println!("Info:"); - println!("-> outer_position : {:?}", window.outer_position()); - println!("-> inner_position : {:?}", window.inner_position()); - println!("-> outer_size : {:?}", window.outer_size()); - println!("-> inner_size : {:?}", window.inner_size()); - println!("-> fullscreen : {:?}", window.fullscreen()); - } - L => window.set_min_inner_size(match state { - true => Some(WINDOW_SIZE), - false => None, - }), - M => window.set_maximized(state), - P => window.set_outer_position({ - let mut position = window.outer_position().unwrap(); - let sign = if state { 1 } else { -1 }; - position.x += 10 * sign; - position.y += 10 * sign; - position - }), - Q => window.request_redraw(), - R => window.set_resizable(state), - S => window.set_inner_size(match state { - true => PhysicalSize::new( - WINDOW_SIZE.width + 100, - WINDOW_SIZE.height + 100, - ), - false => WINDOW_SIZE, - }), - W => { - if let Size::Physical(size) = WINDOW_SIZE.into() { - window - .set_cursor_position(Position::Physical( - PhysicalPosition::new( - size.width as i32 / 2, - size.height as i32 / 2, - ), - )) - .unwrap() + "l" => window.set_min_inner_size(match state { + true => Some(WINDOW_SIZE), + false => None, + }), + "m" => window.set_maximized(state), + "p" => window.set_outer_position({ + let mut position = window.outer_position().unwrap(); + let sign = if state { 1 } else { -1 }; + position.x += 10 * sign; + position.y += 10 * sign; + position + }), + "q" => window.request_redraw(), + "r" => window.set_resizable(state), + "s" => window.set_inner_size(match state { + true => PhysicalSize::new( + WINDOW_SIZE.width + 100, + WINDOW_SIZE.height + 100, + ), + false => WINDOW_SIZE, + }), + "w" => { + if let Size::Physical(size) = WINDOW_SIZE.into() { + window + .set_cursor_position(Position::Physical( + PhysicalPosition::new( + size.width as i32 / 2, + size.height as i32 / 2, + ), + )) + .unwrap() + } } - } - Z => { - window.set_visible(false); - thread::sleep(Duration::from_secs(1)); - window.set_visible(true); - } + "z" => { + window.set_visible(false); + thread::sleep(Duration::from_secs(1)); + window.set_visible(true); + } + _ => (), + }, _ => (), } } @@ -170,10 +181,10 @@ fn main() { WindowEvent::CloseRequested | WindowEvent::Destroyed | WindowEvent::KeyboardInput { - input: - KeyboardInput { + event: + KeyEvent { state: ElementState::Released, - virtual_keycode: Some(VirtualKeyCode::Escape), + logical_key: Key::Escape, .. }, .. diff --git a/examples/multiwindow.rs b/examples/multiwindow.rs index 3409af5a65..03cb55ef9f 100644 --- a/examples/multiwindow.rs +++ b/examples/multiwindow.rs @@ -4,8 +4,9 @@ use std::collections::HashMap; use simple_logger::SimpleLogger; use winit::{ - event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, + event::{ElementState, Event, KeyEvent, WindowEvent}, event_loop::EventLoop, + keyboard::Key, window::Window, }; @@ -39,10 +40,10 @@ fn main() { } } WindowEvent::KeyboardInput { - input: - KeyboardInput { + event: + KeyEvent { state: ElementState::Pressed, - virtual_keycode: Some(VirtualKeyCode::N), + logical_key: Key::Character("n" | "N"), .. }, is_synthetic: false, diff --git a/examples/resizable.rs b/examples/resizable.rs index 8f16172fd7..cf3dacbec2 100644 --- a/examples/resizable.rs +++ b/examples/resizable.rs @@ -3,8 +3,9 @@ use simple_logger::SimpleLogger; use winit::{ dpi::LogicalSize, - event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, + event::{ElementState, Event, KeyEvent, WindowEvent}, event_loop::EventLoop, + keyboard::KeyCode, window::WindowBuilder, }; @@ -30,9 +31,9 @@ fn main() { Event::WindowEvent { event, .. } => match event { WindowEvent::CloseRequested => control_flow.set_exit(), WindowEvent::KeyboardInput { - input: - KeyboardInput { - virtual_keycode: Some(VirtualKeyCode::Space), + event: + KeyEvent { + physical_key: KeyCode::Space, state: ElementState::Released, .. }, diff --git a/examples/theme.rs b/examples/theme.rs index ac8854e2e3..2138c66562 100644 --- a/examples/theme.rs +++ b/examples/theme.rs @@ -2,8 +2,9 @@ use simple_logger::SimpleLogger; use winit::{ - event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, + event::{ElementState, Event, KeyEvent, WindowEvent}, event_loop::{ControlFlow, EventLoop}, + keyboard::Key, window::{Theme, WindowBuilder}, }; @@ -41,9 +42,9 @@ fn main() { Event::WindowEvent { event: WindowEvent::KeyboardInput { - input: - KeyboardInput { - virtual_keycode: Some(key), + event: + KeyEvent { + logical_key: key, state: ElementState::Pressed, .. }, @@ -51,15 +52,15 @@ fn main() { }, .. } => match key { - VirtualKeyCode::A => { + Key::Character("A" | "a") => { println!("Theme was: {:?}", window.theme()); window.set_theme(None); } - VirtualKeyCode::L => { + Key::Character("L" | "l") => { println!("Theme was: {:?}", window.theme()); window.set_theme(Some(Theme::Light)); } - VirtualKeyCode::D => { + Key::Character("D" | "d") => { println!("Theme was: {:?}", window.theme()); window.set_theme(Some(Theme::Dark)); } diff --git a/examples/window_buttons.rs b/examples/window_buttons.rs index 5d41144dbd..a1d90853c9 100644 --- a/examples/window_buttons.rs +++ b/examples/window_buttons.rs @@ -5,8 +5,9 @@ use simple_logger::SimpleLogger; use winit::{ dpi::LogicalSize, - event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, + event::{ElementState, Event, KeyEvent, WindowEvent}, event_loop::{DeviceEventFilter, EventLoop}, + keyboard::Key, window::{WindowBuilder, WindowButtons}, }; @@ -34,9 +35,9 @@ fn main() { Event::WindowEvent { event: WindowEvent::KeyboardInput { - input: - KeyboardInput { - virtual_keycode: Some(key), + event: + KeyEvent { + logical_key: key, state: ElementState::Pressed, .. }, @@ -44,15 +45,15 @@ fn main() { }, .. } => match key { - VirtualKeyCode::F => { + Key::Character("F" | "f") => { let buttons = window.enabled_buttons(); window.set_enabled_buttons(buttons ^ WindowButtons::CLOSE); } - VirtualKeyCode::G => { + Key::Character("G" | "g") => { let buttons = window.enabled_buttons(); window.set_enabled_buttons(buttons ^ WindowButtons::MAXIMIZE); } - VirtualKeyCode::H => { + Key::Character("H" | "h") => { let buttons = window.enabled_buttons(); window.set_enabled_buttons(buttons ^ WindowButtons::MINIMIZE); } diff --git a/examples/window_debug.rs b/examples/window_debug.rs index 01077a8b6e..a274d2166b 100644 --- a/examples/window_debug.rs +++ b/examples/window_debug.rs @@ -5,8 +5,9 @@ use simple_logger::SimpleLogger; use winit::{ dpi::{LogicalSize, PhysicalSize}, - event::{DeviceEvent, ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, + event::{DeviceEvent, ElementState, Event, KeyEvent, RawKeyEvent, WindowEvent}, event_loop::{DeviceEventFilter, EventLoop}, + keyboard::{Key, KeyCode}, window::{Fullscreen, WindowBuilder}, }; @@ -38,23 +39,25 @@ fn main() { control_flow.set_wait(); match event { + // This used to use the virtual key, but the new API + // only provides the `physical_key` (`Code`). Event::DeviceEvent { event: - DeviceEvent::Key(KeyboardInput { - virtual_keycode: Some(key), - state: ElementState::Pressed, + DeviceEvent::Key(RawKeyEvent { + physical_key, + state: ElementState::Released, .. }), .. - } => match key { - VirtualKeyCode::M => { + } => match physical_key { + KeyCode::KeyM => { if minimized { minimized = !minimized; window.set_minimized(minimized); window.focus_window(); } } - VirtualKeyCode::V => { + KeyCode::KeyV => { if !visible { visible = !visible; window.set_visible(visible); @@ -65,17 +68,19 @@ fn main() { Event::WindowEvent { event: WindowEvent::KeyboardInput { - input: - KeyboardInput { - virtual_keycode: Some(key), + event: + KeyEvent { + logical_key: Key::Character(key_str), state: ElementState::Pressed, .. }, .. }, .. - } => match key { - VirtualKeyCode::E => { + } => match key_str { + // WARNING: Consider using `key_without_modifers()` if available on your platform. + // See the `key_binding` example + "e" => { fn area(size: PhysicalSize) -> u32 { size.width * size.height } @@ -90,7 +95,7 @@ fn main() { eprintln!("no video modes available"); } } - VirtualKeyCode::F => { + "f" => { if window.fullscreen().is_some() { window.set_fullscreen(None); } else { @@ -98,25 +103,25 @@ fn main() { window.set_fullscreen(Some(Fullscreen::Borderless(monitor))); } } - VirtualKeyCode::P => { + "p" => { if window.fullscreen().is_some() { window.set_fullscreen(None); } else { window.set_fullscreen(Some(Fullscreen::Borderless(None))); } } - VirtualKeyCode::M => { + "m" => { minimized = !minimized; window.set_minimized(minimized); } - VirtualKeyCode::Q => { + "q" => { control_flow.set_exit(); } - VirtualKeyCode::V => { + "v" => { visible = !visible; window.set_visible(visible); } - VirtualKeyCode::X => { + "x" => { let is_maximized = window.is_maximized(); window.set_maximized(!is_maximized); } diff --git a/examples/window_drag_resize.rs b/examples/window_drag_resize.rs index 95a6737791..93c21d2275 100644 --- a/examples/window_drag_resize.rs +++ b/examples/window_drag_resize.rs @@ -2,10 +2,9 @@ use simple_logger::SimpleLogger; use winit::{ - event::{ - ElementState, Event, KeyboardInput, MouseButton, StartCause, VirtualKeyCode, WindowEvent, - }, + event::{ElementState, Event, KeyEvent, MouseButton, StartCause, WindowEvent}, event_loop::{ControlFlow, EventLoop}, + keyboard::Key, window::{CursorIcon, ResizeDirection, WindowBuilder}, }; @@ -53,10 +52,10 @@ fn main() { } } WindowEvent::KeyboardInput { - input: - KeyboardInput { + event: + KeyEvent { state: ElementState::Released, - virtual_keycode: Some(VirtualKeyCode::B), + logical_key: Key::Character("B" | "b"), .. }, .. diff --git a/examples/window_option_as_alt.rs b/examples/window_option_as_alt.rs index b7d288d5bb..a25832128f 100644 --- a/examples/window_option_as_alt.rs +++ b/examples/window_option_as_alt.rs @@ -49,7 +49,6 @@ fn main() { println!("Received Mouse click, toggling option_as_alt to: {option_as_alt:?}"); window.set_option_as_alt(option_as_alt); } - WindowEvent::ReceivedCharacter(c) => println!("ReceivedCharacter: {c:?}"), WindowEvent::KeyboardInput { .. } => println!("KeyboardInput: {event:?}"), _ => (), }, diff --git a/examples/window_resize_increments.rs b/examples/window_resize_increments.rs index ce06daf22f..11522a2500 100644 --- a/examples/window_resize_increments.rs +++ b/examples/window_resize_increments.rs @@ -2,8 +2,9 @@ use log::debug; use simple_logger::SimpleLogger; use winit::{ dpi::LogicalSize, - event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, + event::{ElementState, Event, KeyEvent, WindowEvent}, event_loop::EventLoop, + keyboard::Key, window::WindowBuilder, }; @@ -31,9 +32,9 @@ fn main() { Event::WindowEvent { event: WindowEvent::KeyboardInput { - input: - KeyboardInput { - virtual_keycode: Some(VirtualKeyCode::Space), + event: + KeyEvent { + logical_key: Key::Space, state: ElementState::Released, .. }, diff --git a/tests/serde_objects.rs b/tests/serde_objects.rs index ad729dcd1b..b0333fa410 100644 --- a/tests/serde_objects.rs +++ b/tests/serde_objects.rs @@ -3,10 +3,8 @@ use serde::{Deserialize, Serialize}; use winit::{ dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize}, - event::{ - ElementState, KeyboardInput, ModifiersState, MouseButton, MouseScrollDelta, TouchPhase, - VirtualKeyCode, - }, + event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase}, + keyboard::{Key, KeyCode, KeyLocation, ModifiersState}, window::CursorIcon, }; @@ -20,12 +18,13 @@ fn window_serde() { #[test] fn events_serde() { - needs_serde::(); needs_serde::(); needs_serde::(); needs_serde::(); needs_serde::(); - needs_serde::(); + needs_serde::>(); + needs_serde::(); + needs_serde::(); needs_serde::(); } From e8c5a37f0f6b3823584374e7005dea5c8be64613 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Art=C3=BAr=20Kov=C3=A1cs?= Date: Fri, 9 Dec 2022 09:31:00 +0100 Subject: [PATCH 03/13] Implement the new keyboard API for Windows --- Cargo.toml | 3 + src/keyboard.rs | 2 +- src/platform/windows.rs | 356 +++++- src/platform_impl/windows/event.rs | 436 -------- src/platform_impl/windows/event_loop.rs | 681 ++++++------ src/platform_impl/windows/keyboard.rs | 937 ++++++++++++++++ src/platform_impl/windows/keyboard_layout.rs | 1017 ++++++++++++++++++ src/platform_impl/windows/minimal_ime.rs | 67 ++ src/platform_impl/windows/mod.rs | 24 +- src/platform_impl/windows/window.rs | 32 +- src/platform_impl/windows/window_state.rs | 5 +- 11 files changed, 2772 insertions(+), 788 deletions(-) create mode 100644 src/platform_impl/windows/keyboard.rs create mode 100644 src/platform_impl/windows/keyboard_layout.rs create mode 100644 src/platform_impl/windows/minimal_ime.rs diff --git a/Cargo.toml b/Cargo.toml index 885cc9c52d..3efb0d71d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -75,6 +75,9 @@ objc2 = ">=0.3.0-beta.3, <0.3.0-beta.4" # Allow `0.3.0-beta.3.patch-leaks` core-graphics = "0.22.3" dispatch = "0.2.0" +[target.'cfg(target_os = "windows")'.dependencies] +unicode-segmentation = "1.7.1" + [target.'cfg(target_os = "windows")'.dependencies.windows-sys] version = "0.45" features = [ diff --git a/src/keyboard.rs b/src/keyboard.rs index 219c57cb11..8c7af54d0a 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -99,7 +99,7 @@ bitflags! { // left and right modifiers are currently commented out, but we should be able to support // them in a future release /// The "shift" key. - const SHIFT = 0b100 << 0; + const SHIFT = 0b100; // const LSHIFT = 0b010 << 0; // const RSHIFT = 0b001 << 0; /// The "control" key. diff --git a/src/platform/windows.rs b/src/platform/windows.rs index 94d4f4040d..984567931d 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -1,11 +1,17 @@ use std::{ffi::c_void, path::Path}; +use windows_sys::Win32::{ + System::SystemServices::LANG_KOREAN, UI::Input::KeyboardAndMouse::GetKeyboardLayout, +}; + use crate::{ dpi::PhysicalSize, - event::DeviceId, + event::{DeviceId, KeyEvent}, event_loop::EventLoopBuilder, + keyboard::{Key, KeyCode, NativeKeyCode}, monitor::MonitorHandle, - platform_impl::WinIcon, + platform::{modifier_supplement::KeyEventExtModifierSupplement, scancode::KeyCodeExtScancode}, + platform_impl::{WinIcon, LOWORD, PRIMARYLANGID}, window::{BadIcon, Icon, Window, WindowBuilder}, }; @@ -344,3 +350,349 @@ impl IconExtWindows for Icon { Ok(Icon { inner: win_icon }) } } + +impl KeyEventExtModifierSupplement for KeyEvent { + #[inline] + fn text_with_all_modifiers(&self) -> Option<&str> { + self.platform_specific.text_with_all_modifers + } + + #[inline] + fn key_without_modifiers(&self) -> Key<'static> { + self.platform_specific.key_without_modifiers.clone() + } +} + +impl KeyCodeExtScancode for KeyCode { + fn to_scancode(self) -> Option { + // See `from_scancode` for more info + + let hkl = unsafe { GetKeyboardLayout(0) }; + + let primary_lang_id = PRIMARYLANGID(LOWORD(hkl as u32)); + let is_korean = primary_lang_id as u32 == LANG_KOREAN; + + match self { + KeyCode::Backquote => Some(0x0029), + KeyCode::Backslash => Some(0x002B), + KeyCode::Backspace => Some(0x000E), + KeyCode::BracketLeft => Some(0x001A), + KeyCode::BracketRight => Some(0x001B), + KeyCode::Comma => Some(0x0033), + KeyCode::Digit0 => Some(0x000B), + KeyCode::Digit1 => Some(0x0002), + KeyCode::Digit2 => Some(0x0003), + KeyCode::Digit3 => Some(0x0004), + KeyCode::Digit4 => Some(0x0005), + KeyCode::Digit5 => Some(0x0006), + KeyCode::Digit6 => Some(0x0007), + KeyCode::Digit7 => Some(0x0008), + KeyCode::Digit8 => Some(0x0009), + KeyCode::Digit9 => Some(0x000A), + KeyCode::Equal => Some(0x000D), + KeyCode::IntlBackslash => Some(0x0056), + KeyCode::IntlRo => Some(0x0073), + KeyCode::IntlYen => Some(0x007D), + KeyCode::KeyA => Some(0x001E), + KeyCode::KeyB => Some(0x0030), + KeyCode::KeyC => Some(0x002E), + KeyCode::KeyD => Some(0x0020), + KeyCode::KeyE => Some(0x0012), + KeyCode::KeyF => Some(0x0021), + KeyCode::KeyG => Some(0x0022), + KeyCode::KeyH => Some(0x0023), + KeyCode::KeyI => Some(0x0017), + KeyCode::KeyJ => Some(0x0024), + KeyCode::KeyK => Some(0x0025), + KeyCode::KeyL => Some(0x0026), + KeyCode::KeyM => Some(0x0032), + KeyCode::KeyN => Some(0x0031), + KeyCode::KeyO => Some(0x0018), + KeyCode::KeyP => Some(0x0019), + KeyCode::KeyQ => Some(0x0010), + KeyCode::KeyR => Some(0x0013), + KeyCode::KeyS => Some(0x001F), + KeyCode::KeyT => Some(0x0014), + KeyCode::KeyU => Some(0x0016), + KeyCode::KeyV => Some(0x002F), + KeyCode::KeyW => Some(0x0011), + KeyCode::KeyX => Some(0x002D), + KeyCode::KeyY => Some(0x0015), + KeyCode::KeyZ => Some(0x002C), + KeyCode::Minus => Some(0x000C), + KeyCode::Period => Some(0x0034), + KeyCode::Quote => Some(0x0028), + KeyCode::Semicolon => Some(0x0027), + KeyCode::Slash => Some(0x0035), + KeyCode::AltLeft => Some(0x0038), + KeyCode::AltRight => Some(0xE038), + KeyCode::CapsLock => Some(0x003A), + KeyCode::ContextMenu => Some(0xE05D), + KeyCode::ControlLeft => Some(0x001D), + KeyCode::ControlRight => Some(0xE01D), + KeyCode::Enter => Some(0x001C), + KeyCode::SuperLeft => Some(0xE05B), + KeyCode::SuperRight => Some(0xE05C), + KeyCode::ShiftLeft => Some(0x002A), + KeyCode::ShiftRight => Some(0x0036), + KeyCode::Space => Some(0x0039), + KeyCode::Tab => Some(0x000F), + KeyCode::Convert => Some(0x0079), + KeyCode::Lang1 => { + if is_korean { + Some(0xE0F2) + } else { + Some(0x0072) + } + } + KeyCode::Lang2 => { + if is_korean { + Some(0xE0F1) + } else { + Some(0x0071) + } + } + KeyCode::KanaMode => Some(0x0070), + KeyCode::NonConvert => Some(0x007B), + KeyCode::Delete => Some(0xE053), + KeyCode::End => Some(0xE04F), + KeyCode::Home => Some(0xE047), + KeyCode::Insert => Some(0xE052), + KeyCode::PageDown => Some(0xE051), + KeyCode::PageUp => Some(0xE049), + KeyCode::ArrowDown => Some(0xE050), + KeyCode::ArrowLeft => Some(0xE04B), + KeyCode::ArrowRight => Some(0xE04D), + KeyCode::ArrowUp => Some(0xE048), + KeyCode::NumLock => Some(0xE045), + KeyCode::Numpad0 => Some(0x0052), + KeyCode::Numpad1 => Some(0x004F), + KeyCode::Numpad2 => Some(0x0050), + KeyCode::Numpad3 => Some(0x0051), + KeyCode::Numpad4 => Some(0x004B), + KeyCode::Numpad5 => Some(0x004C), + KeyCode::Numpad6 => Some(0x004D), + KeyCode::Numpad7 => Some(0x0047), + KeyCode::Numpad8 => Some(0x0048), + KeyCode::Numpad9 => Some(0x0049), + KeyCode::NumpadAdd => Some(0x004E), + KeyCode::NumpadComma => Some(0x007E), + KeyCode::NumpadDecimal => Some(0x0053), + KeyCode::NumpadDivide => Some(0xE035), + KeyCode::NumpadEnter => Some(0xE01C), + KeyCode::NumpadEqual => Some(0x0059), + KeyCode::NumpadMultiply => Some(0x0037), + KeyCode::NumpadSubtract => Some(0x004A), + KeyCode::Escape => Some(0x0001), + KeyCode::F1 => Some(0x003B), + KeyCode::F2 => Some(0x003C), + KeyCode::F3 => Some(0x003D), + KeyCode::F4 => Some(0x003E), + KeyCode::F5 => Some(0x003F), + KeyCode::F6 => Some(0x0040), + KeyCode::F7 => Some(0x0041), + KeyCode::F8 => Some(0x0042), + KeyCode::F9 => Some(0x0043), + KeyCode::F10 => Some(0x0044), + KeyCode::F11 => Some(0x0057), + KeyCode::F12 => Some(0x0058), + KeyCode::F13 => Some(0x0064), + KeyCode::F14 => Some(0x0065), + KeyCode::F15 => Some(0x0066), + KeyCode::F16 => Some(0x0067), + KeyCode::F17 => Some(0x0068), + KeyCode::F18 => Some(0x0069), + KeyCode::F19 => Some(0x006A), + KeyCode::F20 => Some(0x006B), + KeyCode::F21 => Some(0x006C), + KeyCode::F22 => Some(0x006D), + KeyCode::F23 => Some(0x006E), + KeyCode::F24 => Some(0x0076), + KeyCode::PrintScreen => Some(0xE037), + //KeyCode::PrintScreen => Some(0x0054), // Alt + PrintScreen + KeyCode::ScrollLock => Some(0x0046), + KeyCode::Pause => Some(0x0045), + //KeyCode::Pause => Some(0xE046), // Ctrl + Pause + KeyCode::BrowserBack => Some(0xE06A), + KeyCode::BrowserFavorites => Some(0xE066), + KeyCode::BrowserForward => Some(0xE069), + KeyCode::BrowserHome => Some(0xE032), + KeyCode::BrowserRefresh => Some(0xE067), + KeyCode::BrowserSearch => Some(0xE065), + KeyCode::BrowserStop => Some(0xE068), + KeyCode::LaunchApp1 => Some(0xE06B), + KeyCode::LaunchApp2 => Some(0xE021), + KeyCode::LaunchMail => Some(0xE06C), + KeyCode::MediaPlayPause => Some(0xE022), + KeyCode::MediaSelect => Some(0xE06D), + KeyCode::MediaStop => Some(0xE024), + KeyCode::MediaTrackNext => Some(0xE019), + KeyCode::MediaTrackPrevious => Some(0xE010), + KeyCode::Power => Some(0xE05E), + KeyCode::AudioVolumeDown => Some(0xE02E), + KeyCode::AudioVolumeMute => Some(0xE020), + KeyCode::AudioVolumeUp => Some(0xE030), + KeyCode::Unidentified(NativeKeyCode::Windows(scancode)) => Some(scancode as u32), + _ => None, + } + } + + fn from_scancode(scancode: u32) -> KeyCode { + // See: https://www.win.tue.nl/~aeb/linux/kbd/scancodes-1.html + // and: https://www.w3.org/TR/uievents-code/ + // and: The widget/NativeKeyToDOMCodeName.h file in the firefox source + + match scancode { + 0x0029 => KeyCode::Backquote, + 0x002B => KeyCode::Backslash, + 0x000E => KeyCode::Backspace, + 0x001A => KeyCode::BracketLeft, + 0x001B => KeyCode::BracketRight, + 0x0033 => KeyCode::Comma, + 0x000B => KeyCode::Digit0, + 0x0002 => KeyCode::Digit1, + 0x0003 => KeyCode::Digit2, + 0x0004 => KeyCode::Digit3, + 0x0005 => KeyCode::Digit4, + 0x0006 => KeyCode::Digit5, + 0x0007 => KeyCode::Digit6, + 0x0008 => KeyCode::Digit7, + 0x0009 => KeyCode::Digit8, + 0x000A => KeyCode::Digit9, + 0x000D => KeyCode::Equal, + 0x0056 => KeyCode::IntlBackslash, + 0x0073 => KeyCode::IntlRo, + 0x007D => KeyCode::IntlYen, + 0x001E => KeyCode::KeyA, + 0x0030 => KeyCode::KeyB, + 0x002E => KeyCode::KeyC, + 0x0020 => KeyCode::KeyD, + 0x0012 => KeyCode::KeyE, + 0x0021 => KeyCode::KeyF, + 0x0022 => KeyCode::KeyG, + 0x0023 => KeyCode::KeyH, + 0x0017 => KeyCode::KeyI, + 0x0024 => KeyCode::KeyJ, + 0x0025 => KeyCode::KeyK, + 0x0026 => KeyCode::KeyL, + 0x0032 => KeyCode::KeyM, + 0x0031 => KeyCode::KeyN, + 0x0018 => KeyCode::KeyO, + 0x0019 => KeyCode::KeyP, + 0x0010 => KeyCode::KeyQ, + 0x0013 => KeyCode::KeyR, + 0x001F => KeyCode::KeyS, + 0x0014 => KeyCode::KeyT, + 0x0016 => KeyCode::KeyU, + 0x002F => KeyCode::KeyV, + 0x0011 => KeyCode::KeyW, + 0x002D => KeyCode::KeyX, + 0x0015 => KeyCode::KeyY, + 0x002C => KeyCode::KeyZ, + 0x000C => KeyCode::Minus, + 0x0034 => KeyCode::Period, + 0x0028 => KeyCode::Quote, + 0x0027 => KeyCode::Semicolon, + 0x0035 => KeyCode::Slash, + 0x0038 => KeyCode::AltLeft, + 0xE038 => KeyCode::AltRight, + 0x003A => KeyCode::CapsLock, + 0xE05D => KeyCode::ContextMenu, + 0x001D => KeyCode::ControlLeft, + 0xE01D => KeyCode::ControlRight, + 0x001C => KeyCode::Enter, + 0xE05B => KeyCode::SuperLeft, + 0xE05C => KeyCode::SuperRight, + 0x002A => KeyCode::ShiftLeft, + 0x0036 => KeyCode::ShiftRight, + 0x0039 => KeyCode::Space, + 0x000F => KeyCode::Tab, + 0x0079 => KeyCode::Convert, + 0x0072 => KeyCode::Lang1, // for non-Korean layout + 0xE0F2 => KeyCode::Lang1, // for Korean layout + 0x0071 => KeyCode::Lang2, // for non-Korean layout + 0xE0F1 => KeyCode::Lang2, // for Korean layout + 0x0070 => KeyCode::KanaMode, + 0x007B => KeyCode::NonConvert, + 0xE053 => KeyCode::Delete, + 0xE04F => KeyCode::End, + 0xE047 => KeyCode::Home, + 0xE052 => KeyCode::Insert, + 0xE051 => KeyCode::PageDown, + 0xE049 => KeyCode::PageUp, + 0xE050 => KeyCode::ArrowDown, + 0xE04B => KeyCode::ArrowLeft, + 0xE04D => KeyCode::ArrowRight, + 0xE048 => KeyCode::ArrowUp, + 0xE045 => KeyCode::NumLock, + 0x0052 => KeyCode::Numpad0, + 0x004F => KeyCode::Numpad1, + 0x0050 => KeyCode::Numpad2, + 0x0051 => KeyCode::Numpad3, + 0x004B => KeyCode::Numpad4, + 0x004C => KeyCode::Numpad5, + 0x004D => KeyCode::Numpad6, + 0x0047 => KeyCode::Numpad7, + 0x0048 => KeyCode::Numpad8, + 0x0049 => KeyCode::Numpad9, + 0x004E => KeyCode::NumpadAdd, + 0x007E => KeyCode::NumpadComma, + 0x0053 => KeyCode::NumpadDecimal, + 0xE035 => KeyCode::NumpadDivide, + 0xE01C => KeyCode::NumpadEnter, + 0x0059 => KeyCode::NumpadEqual, + 0x0037 => KeyCode::NumpadMultiply, + 0x004A => KeyCode::NumpadSubtract, + 0x0001 => KeyCode::Escape, + 0x003B => KeyCode::F1, + 0x003C => KeyCode::F2, + 0x003D => KeyCode::F3, + 0x003E => KeyCode::F4, + 0x003F => KeyCode::F5, + 0x0040 => KeyCode::F6, + 0x0041 => KeyCode::F7, + 0x0042 => KeyCode::F8, + 0x0043 => KeyCode::F9, + 0x0044 => KeyCode::F10, + 0x0057 => KeyCode::F11, + 0x0058 => KeyCode::F12, + 0x0064 => KeyCode::F13, + 0x0065 => KeyCode::F14, + 0x0066 => KeyCode::F15, + 0x0067 => KeyCode::F16, + 0x0068 => KeyCode::F17, + 0x0069 => KeyCode::F18, + 0x006A => KeyCode::F19, + 0x006B => KeyCode::F20, + 0x006C => KeyCode::F21, + 0x006D => KeyCode::F22, + 0x006E => KeyCode::F23, + 0x0076 => KeyCode::F24, + 0xE037 => KeyCode::PrintScreen, + 0x0054 => KeyCode::PrintScreen, // Alt + PrintScreen + 0x0046 => KeyCode::ScrollLock, + 0x0045 => KeyCode::Pause, + 0xE046 => KeyCode::Pause, // Ctrl + Pause + 0xE06A => KeyCode::BrowserBack, + 0xE066 => KeyCode::BrowserFavorites, + 0xE069 => KeyCode::BrowserForward, + 0xE032 => KeyCode::BrowserHome, + 0xE067 => KeyCode::BrowserRefresh, + 0xE065 => KeyCode::BrowserSearch, + 0xE068 => KeyCode::BrowserStop, + 0xE06B => KeyCode::LaunchApp1, + 0xE021 => KeyCode::LaunchApp2, + 0xE06C => KeyCode::LaunchMail, + 0xE022 => KeyCode::MediaPlayPause, + 0xE06D => KeyCode::MediaSelect, + 0xE024 => KeyCode::MediaStop, + 0xE019 => KeyCode::MediaTrackNext, + 0xE010 => KeyCode::MediaTrackPrevious, + 0xE05E => KeyCode::Power, + 0xE02E => KeyCode::AudioVolumeDown, + 0xE020 => KeyCode::AudioVolumeMute, + 0xE030 => KeyCode::AudioVolumeUp, + _ => KeyCode::Unidentified(NativeKeyCode::Windows(scancode as u16)), + } + } +} diff --git a/src/platform_impl/windows/event.rs b/src/platform_impl/windows/event.rs index 472315dcde..e69de29bb2 100644 --- a/src/platform_impl/windows/event.rs +++ b/src/platform_impl/windows/event.rs @@ -1,436 +0,0 @@ -use std::{ - char, - sync::atomic::{AtomicBool, AtomicIsize, Ordering}, -}; - -use windows_sys::Win32::{ - Foundation::{LPARAM, WPARAM}, - UI::{ - Input::KeyboardAndMouse::{ - GetKeyState, GetKeyboardLayout, GetKeyboardState, MapVirtualKeyA, ToUnicodeEx, - MAPVK_VK_TO_CHAR, MAPVK_VSC_TO_VK_EX, VIRTUAL_KEY, VK_0, VK_1, VK_2, VK_3, VK_4, VK_5, - VK_6, VK_7, VK_8, VK_9, VK_A, VK_ADD, VK_APPS, VK_B, VK_BACK, VK_BROWSER_BACK, - VK_BROWSER_FAVORITES, VK_BROWSER_FORWARD, VK_BROWSER_HOME, VK_BROWSER_REFRESH, - VK_BROWSER_SEARCH, VK_BROWSER_STOP, VK_C, VK_CAPITAL, VK_CONTROL, VK_CONVERT, VK_D, - VK_DECIMAL, VK_DELETE, VK_DIVIDE, VK_DOWN, VK_E, VK_END, VK_ESCAPE, VK_F, VK_F1, - VK_F10, VK_F11, VK_F12, VK_F13, VK_F14, VK_F15, VK_F16, VK_F17, VK_F18, VK_F19, VK_F2, - VK_F20, VK_F21, VK_F22, VK_F23, VK_F24, VK_F3, VK_F4, VK_F5, VK_F6, VK_F7, VK_F8, - VK_F9, VK_G, VK_H, VK_HOME, VK_I, VK_INSERT, VK_J, VK_K, VK_KANA, VK_KANJI, VK_L, - VK_LAUNCH_MAIL, VK_LAUNCH_MEDIA_SELECT, VK_LCONTROL, VK_LEFT, VK_LMENU, VK_LSHIFT, - VK_LWIN, VK_M, VK_MEDIA_NEXT_TRACK, VK_MEDIA_PLAY_PAUSE, VK_MEDIA_PREV_TRACK, - VK_MEDIA_STOP, VK_MENU, VK_MULTIPLY, VK_N, VK_NEXT, VK_NONCONVERT, VK_NUMLOCK, - VK_NUMPAD0, VK_NUMPAD1, VK_NUMPAD2, VK_NUMPAD3, VK_NUMPAD4, VK_NUMPAD5, VK_NUMPAD6, - VK_NUMPAD7, VK_NUMPAD8, VK_NUMPAD9, VK_O, VK_OEM_1, VK_OEM_102, VK_OEM_2, VK_OEM_3, - VK_OEM_4, VK_OEM_5, VK_OEM_6, VK_OEM_7, VK_OEM_COMMA, VK_OEM_MINUS, VK_OEM_PERIOD, - VK_OEM_PLUS, VK_P, VK_PAUSE, VK_PRIOR, VK_Q, VK_R, VK_RCONTROL, VK_RETURN, VK_RIGHT, - VK_RMENU, VK_RSHIFT, VK_RWIN, VK_S, VK_SCROLL, VK_SHIFT, VK_SLEEP, VK_SNAPSHOT, - VK_SPACE, VK_SUBTRACT, VK_T, VK_TAB, VK_U, VK_UP, VK_V, VK_VOLUME_DOWN, VK_VOLUME_MUTE, - VK_VOLUME_UP, VK_W, VK_X, VK_Y, VK_Z, - }, - TextServices::HKL, - }, -}; - -use crate::event::{ModifiersState, ScanCode, VirtualKeyCode}; - -use super::util::has_flag; - -fn key_pressed(vkey: VIRTUAL_KEY) -> bool { - unsafe { has_flag(GetKeyState(vkey as i32), 1 << 15) } -} - -pub fn get_key_mods() -> ModifiersState { - let filter_out_altgr = layout_uses_altgr() && key_pressed(VK_RMENU); - - let mut mods = ModifiersState::empty(); - mods.set(ModifiersState::SHIFT, key_pressed(VK_SHIFT)); - mods.set( - ModifiersState::CTRL, - key_pressed(VK_CONTROL) && !filter_out_altgr, - ); - mods.set( - ModifiersState::ALT, - key_pressed(VK_MENU) && !filter_out_altgr, - ); - mods.set( - ModifiersState::LOGO, - key_pressed(VK_LWIN) || key_pressed(VK_RWIN), - ); - mods -} - -bitflags! { - #[derive(Default)] - pub struct ModifiersStateSide: u32 { - const LSHIFT = 0b010; - const RSHIFT = 0b001; - - const LCTRL = 0b010 << 3; - const RCTRL = 0b001 << 3; - - const LALT = 0b010 << 6; - const RALT = 0b001 << 6; - - const LLOGO = 0b010 << 9; - const RLOGO = 0b001 << 9; - } -} - -impl ModifiersStateSide { - pub fn filter_out_altgr(&self) -> ModifiersStateSide { - match layout_uses_altgr() && self.contains(Self::RALT) { - false => *self, - true => *self & !(Self::LCTRL | Self::RCTRL | Self::LALT | Self::RALT), - } - } -} - -impl From for ModifiersState { - fn from(side: ModifiersStateSide) -> Self { - let mut state = ModifiersState::default(); - state.set( - Self::SHIFT, - side.intersects(ModifiersStateSide::LSHIFT | ModifiersStateSide::RSHIFT), - ); - state.set( - Self::CTRL, - side.intersects(ModifiersStateSide::LCTRL | ModifiersStateSide::RCTRL), - ); - state.set( - Self::ALT, - side.intersects(ModifiersStateSide::LALT | ModifiersStateSide::RALT), - ); - state.set( - Self::LOGO, - side.intersects(ModifiersStateSide::LLOGO | ModifiersStateSide::RLOGO), - ); - state - } -} - -pub fn get_pressed_keys() -> impl Iterator { - let mut keyboard_state = vec![0u8; 256]; - unsafe { GetKeyboardState(keyboard_state.as_mut_ptr()) }; - keyboard_state - .into_iter() - .enumerate() - .filter(|(_, p)| (*p & (1 << 7)) != 0) // whether or not a key is pressed is communicated via the high-order bit - .map(|(i, _)| i as u16) -} - -unsafe fn get_char(keyboard_state: &[u8; 256], v_key: u32, hkl: HKL) -> Option { - let mut unicode_bytes = [0u16; 5]; - let len = ToUnicodeEx( - v_key, - 0, - keyboard_state.as_ptr(), - unicode_bytes.as_mut_ptr(), - unicode_bytes.len() as _, - 0, - hkl, - ); - if len >= 1 { - char::decode_utf16(unicode_bytes.iter().cloned()) - .next() - .and_then(|c| c.ok()) - } else { - None - } -} - -/// Figures out if the keyboard layout has an AltGr key instead of an Alt key. -/// -/// Unfortunately, the Windows API doesn't give a way for us to conveniently figure that out. So, -/// we use a technique blatantly stolen from [the Firefox source code][source]: iterate over every -/// possible virtual key and compare the `char` output when AltGr is pressed vs when it isn't. If -/// pressing AltGr outputs characters that are different from the standard characters, the layout -/// uses AltGr. Otherwise, it doesn't. -/// -/// [source]: https://github.com/mozilla/gecko-dev/blob/265e6721798a455604328ed5262f430cfcc37c2f/widget/windows/KeyboardLayout.cpp#L4356-L4416 -fn layout_uses_altgr() -> bool { - unsafe { - static ACTIVE_LAYOUT: AtomicIsize = AtomicIsize::new(0); - static USES_ALTGR: AtomicBool = AtomicBool::new(false); - - let hkl = GetKeyboardLayout(0); - let old_hkl = ACTIVE_LAYOUT.swap(hkl, Ordering::SeqCst); - - if hkl == old_hkl { - return USES_ALTGR.load(Ordering::SeqCst); - } - - let mut keyboard_state_altgr = [0u8; 256]; - // AltGr is an alias for Ctrl+Alt for... some reason. Whatever it is, those are the keypresses - // we have to emulate to do an AltGr test. - keyboard_state_altgr[VK_MENU as usize] = 0x80; - keyboard_state_altgr[VK_CONTROL as usize] = 0x80; - - let keyboard_state_empty = [0u8; 256]; - - for v_key in 0..=255 { - let key_noaltgr = get_char(&keyboard_state_empty, v_key, hkl); - let key_altgr = get_char(&keyboard_state_altgr, v_key, hkl); - if let (Some(noaltgr), Some(altgr)) = (key_noaltgr, key_altgr) { - if noaltgr != altgr { - USES_ALTGR.store(true, Ordering::SeqCst); - return true; - } - } - } - - USES_ALTGR.store(false, Ordering::SeqCst); - false - } -} - -pub fn vkey_to_winit_vkey(vkey: VIRTUAL_KEY) -> Option { - // VK_* codes are documented here https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx - match vkey { - //VK_LBUTTON => Some(VirtualKeyCode::Lbutton), - //VK_RBUTTON => Some(VirtualKeyCode::Rbutton), - //VK_CANCEL => Some(VirtualKeyCode::Cancel), - //VK_MBUTTON => Some(VirtualKeyCode::Mbutton), - //VK_XBUTTON1 => Some(VirtualKeyCode::Xbutton1), - //VK_XBUTTON2 => Some(VirtualKeyCode::Xbutton2), - VK_BACK => Some(VirtualKeyCode::Back), - VK_TAB => Some(VirtualKeyCode::Tab), - //VK_CLEAR => Some(VirtualKeyCode::Clear), - VK_RETURN => Some(VirtualKeyCode::Return), - VK_LSHIFT => Some(VirtualKeyCode::LShift), - VK_RSHIFT => Some(VirtualKeyCode::RShift), - VK_LCONTROL => Some(VirtualKeyCode::LControl), - VK_RCONTROL => Some(VirtualKeyCode::RControl), - VK_LMENU => Some(VirtualKeyCode::LAlt), - VK_RMENU => Some(VirtualKeyCode::RAlt), - VK_PAUSE => Some(VirtualKeyCode::Pause), - VK_CAPITAL => Some(VirtualKeyCode::Capital), - VK_KANA => Some(VirtualKeyCode::Kana), - //VK_HANGUEL => Some(VirtualKeyCode::Hanguel), - //VK_HANGUL => Some(VirtualKeyCode::Hangul), - //VK_JUNJA => Some(VirtualKeyCode::Junja), - //VK_FINAL => Some(VirtualKeyCode::Final), - //VK_HANJA => Some(VirtualKeyCode::Hanja), - VK_KANJI => Some(VirtualKeyCode::Kanji), - VK_ESCAPE => Some(VirtualKeyCode::Escape), - VK_CONVERT => Some(VirtualKeyCode::Convert), - VK_NONCONVERT => Some(VirtualKeyCode::NoConvert), - //VK_ACCEPT => Some(VirtualKeyCode::Accept), - //VK_MODECHANGE => Some(VirtualKeyCode::Modechange), - VK_SPACE => Some(VirtualKeyCode::Space), - VK_PRIOR => Some(VirtualKeyCode::PageUp), - VK_NEXT => Some(VirtualKeyCode::PageDown), - VK_END => Some(VirtualKeyCode::End), - VK_HOME => Some(VirtualKeyCode::Home), - VK_LEFT => Some(VirtualKeyCode::Left), - VK_UP => Some(VirtualKeyCode::Up), - VK_RIGHT => Some(VirtualKeyCode::Right), - VK_DOWN => Some(VirtualKeyCode::Down), - //VK_SELECT => Some(VirtualKeyCode::Select), - //VK_PRINT => Some(VirtualKeyCode::Print), - //VK_EXECUTE => Some(VirtualKeyCode::Execute), - VK_SNAPSHOT => Some(VirtualKeyCode::Snapshot), - VK_INSERT => Some(VirtualKeyCode::Insert), - VK_DELETE => Some(VirtualKeyCode::Delete), - //VK_HELP => Some(VirtualKeyCode::Help), - VK_0 => Some(VirtualKeyCode::Key0), - VK_1 => Some(VirtualKeyCode::Key1), - VK_2 => Some(VirtualKeyCode::Key2), - VK_3 => Some(VirtualKeyCode::Key3), - VK_4 => Some(VirtualKeyCode::Key4), - VK_5 => Some(VirtualKeyCode::Key5), - VK_6 => Some(VirtualKeyCode::Key6), - VK_7 => Some(VirtualKeyCode::Key7), - VK_8 => Some(VirtualKeyCode::Key8), - VK_9 => Some(VirtualKeyCode::Key9), - VK_A => Some(VirtualKeyCode::A), - VK_B => Some(VirtualKeyCode::B), - VK_C => Some(VirtualKeyCode::C), - VK_D => Some(VirtualKeyCode::D), - VK_E => Some(VirtualKeyCode::E), - VK_F => Some(VirtualKeyCode::F), - VK_G => Some(VirtualKeyCode::G), - VK_H => Some(VirtualKeyCode::H), - VK_I => Some(VirtualKeyCode::I), - VK_J => Some(VirtualKeyCode::J), - VK_K => Some(VirtualKeyCode::K), - VK_L => Some(VirtualKeyCode::L), - VK_M => Some(VirtualKeyCode::M), - VK_N => Some(VirtualKeyCode::N), - VK_O => Some(VirtualKeyCode::O), - VK_P => Some(VirtualKeyCode::P), - VK_Q => Some(VirtualKeyCode::Q), - VK_R => Some(VirtualKeyCode::R), - VK_S => Some(VirtualKeyCode::S), - VK_T => Some(VirtualKeyCode::T), - VK_U => Some(VirtualKeyCode::U), - VK_V => Some(VirtualKeyCode::V), - VK_W => Some(VirtualKeyCode::W), - VK_X => Some(VirtualKeyCode::X), - VK_Y => Some(VirtualKeyCode::Y), - VK_Z => Some(VirtualKeyCode::Z), - VK_LWIN => Some(VirtualKeyCode::LWin), - VK_RWIN => Some(VirtualKeyCode::RWin), - VK_APPS => Some(VirtualKeyCode::Apps), - VK_SLEEP => Some(VirtualKeyCode::Sleep), - VK_NUMPAD0 => Some(VirtualKeyCode::Numpad0), - VK_NUMPAD1 => Some(VirtualKeyCode::Numpad1), - VK_NUMPAD2 => Some(VirtualKeyCode::Numpad2), - VK_NUMPAD3 => Some(VirtualKeyCode::Numpad3), - VK_NUMPAD4 => Some(VirtualKeyCode::Numpad4), - VK_NUMPAD5 => Some(VirtualKeyCode::Numpad5), - VK_NUMPAD6 => Some(VirtualKeyCode::Numpad6), - VK_NUMPAD7 => Some(VirtualKeyCode::Numpad7), - VK_NUMPAD8 => Some(VirtualKeyCode::Numpad8), - VK_NUMPAD9 => Some(VirtualKeyCode::Numpad9), - VK_MULTIPLY => Some(VirtualKeyCode::NumpadMultiply), - VK_ADD => Some(VirtualKeyCode::NumpadAdd), - //VK_SEPARATOR => Some(VirtualKeyCode::Separator), - VK_SUBTRACT => Some(VirtualKeyCode::NumpadSubtract), - VK_DECIMAL => Some(VirtualKeyCode::NumpadDecimal), - VK_DIVIDE => Some(VirtualKeyCode::NumpadDivide), - VK_F1 => Some(VirtualKeyCode::F1), - VK_F2 => Some(VirtualKeyCode::F2), - VK_F3 => Some(VirtualKeyCode::F3), - VK_F4 => Some(VirtualKeyCode::F4), - VK_F5 => Some(VirtualKeyCode::F5), - VK_F6 => Some(VirtualKeyCode::F6), - VK_F7 => Some(VirtualKeyCode::F7), - VK_F8 => Some(VirtualKeyCode::F8), - VK_F9 => Some(VirtualKeyCode::F9), - VK_F10 => Some(VirtualKeyCode::F10), - VK_F11 => Some(VirtualKeyCode::F11), - VK_F12 => Some(VirtualKeyCode::F12), - VK_F13 => Some(VirtualKeyCode::F13), - VK_F14 => Some(VirtualKeyCode::F14), - VK_F15 => Some(VirtualKeyCode::F15), - VK_F16 => Some(VirtualKeyCode::F16), - VK_F17 => Some(VirtualKeyCode::F17), - VK_F18 => Some(VirtualKeyCode::F18), - VK_F19 => Some(VirtualKeyCode::F19), - VK_F20 => Some(VirtualKeyCode::F20), - VK_F21 => Some(VirtualKeyCode::F21), - VK_F22 => Some(VirtualKeyCode::F22), - VK_F23 => Some(VirtualKeyCode::F23), - VK_F24 => Some(VirtualKeyCode::F24), - VK_NUMLOCK => Some(VirtualKeyCode::Numlock), - VK_SCROLL => Some(VirtualKeyCode::Scroll), - VK_BROWSER_BACK => Some(VirtualKeyCode::NavigateBackward), - VK_BROWSER_FORWARD => Some(VirtualKeyCode::NavigateForward), - VK_BROWSER_REFRESH => Some(VirtualKeyCode::WebRefresh), - VK_BROWSER_STOP => Some(VirtualKeyCode::WebStop), - VK_BROWSER_SEARCH => Some(VirtualKeyCode::WebSearch), - VK_BROWSER_FAVORITES => Some(VirtualKeyCode::WebFavorites), - VK_BROWSER_HOME => Some(VirtualKeyCode::WebHome), - VK_VOLUME_MUTE => Some(VirtualKeyCode::Mute), - VK_VOLUME_DOWN => Some(VirtualKeyCode::VolumeDown), - VK_VOLUME_UP => Some(VirtualKeyCode::VolumeUp), - VK_MEDIA_NEXT_TRACK => Some(VirtualKeyCode::NextTrack), - VK_MEDIA_PREV_TRACK => Some(VirtualKeyCode::PrevTrack), - VK_MEDIA_STOP => Some(VirtualKeyCode::MediaStop), - VK_MEDIA_PLAY_PAUSE => Some(VirtualKeyCode::PlayPause), - VK_LAUNCH_MAIL => Some(VirtualKeyCode::Mail), - VK_LAUNCH_MEDIA_SELECT => Some(VirtualKeyCode::MediaSelect), - /*VK_LAUNCH_APP1 => Some(VirtualKeyCode::Launch_app1), - VK_LAUNCH_APP2 => Some(VirtualKeyCode::Launch_app2),*/ - VK_OEM_PLUS => Some(VirtualKeyCode::Equals), - VK_OEM_COMMA => Some(VirtualKeyCode::Comma), - VK_OEM_MINUS => Some(VirtualKeyCode::Minus), - VK_OEM_PERIOD => Some(VirtualKeyCode::Period), - VK_OEM_1 => map_text_keys(vkey), - VK_OEM_2 => map_text_keys(vkey), - VK_OEM_3 => map_text_keys(vkey), - VK_OEM_4 => map_text_keys(vkey), - VK_OEM_5 => map_text_keys(vkey), - VK_OEM_6 => map_text_keys(vkey), - VK_OEM_7 => map_text_keys(vkey), - /* VK_OEM_8 => Some(VirtualKeyCode::Oem_8), */ - VK_OEM_102 => Some(VirtualKeyCode::OEM102), - /*VK_PROCESSKEY => Some(VirtualKeyCode::Processkey), - VK_PACKET => Some(VirtualKeyCode::Packet), - VK_ATTN => Some(VirtualKeyCode::Attn), - VK_CRSEL => Some(VirtualKeyCode::Crsel), - VK_EXSEL => Some(VirtualKeyCode::Exsel), - VK_EREOF => Some(VirtualKeyCode::Ereof), - VK_PLAY => Some(VirtualKeyCode::Play), - VK_ZOOM => Some(VirtualKeyCode::Zoom), - VK_NONAME => Some(VirtualKeyCode::Noname), - VK_PA1 => Some(VirtualKeyCode::Pa1), - VK_OEM_CLEAR => Some(VirtualKeyCode::Oem_clear),*/ - _ => None, - } -} - -pub fn handle_extended_keys( - vkey: VIRTUAL_KEY, - mut scancode: u32, - extended: bool, -) -> Option<(VIRTUAL_KEY, u32)> { - // Welcome to hell https://blog.molecular-matters.com/2011/09/05/properly-handling-keyboard-input/ - scancode |= if extended { 0xE000 } else { 0x0000 }; - let vkey = match vkey { - VK_SHIFT => (unsafe { MapVirtualKeyA(scancode, MAPVK_VSC_TO_VK_EX) } as u16), - VK_CONTROL => { - if extended { - VK_RCONTROL - } else { - VK_LCONTROL - } - } - VK_MENU => { - if extended { - VK_RMENU - } else { - VK_LMENU - } - } - _ => { - match scancode { - // When VK_PAUSE is pressed it emits a LeftControl + NumLock scancode event sequence, but reports VK_PAUSE - // as the virtual key on both events, or VK_PAUSE on the first event or 0xFF when using raw input. - // Don't emit anything for the LeftControl event in the pair... - 0xE01D if vkey == VK_PAUSE => return None, - // ...and emit the Pause event for the second event in the pair. - 0x45 if vkey == VK_PAUSE || vkey == 0xFF => { - scancode = 0xE059; - VK_PAUSE - } - // VK_PAUSE has an incorrect vkey value when used with modifiers. VK_PAUSE also reports a different - // scancode when used with modifiers than when used without - 0xE046 => { - scancode = 0xE059; - VK_PAUSE - } - // VK_SCROLL has an incorrect vkey value when used with modifiers. - 0x46 => VK_SCROLL, - _ => vkey, - } - } - }; - Some((vkey, scancode)) -} - -pub fn process_key_params( - wparam: WPARAM, - lparam: LPARAM, -) -> Option<(ScanCode, Option)> { - let scancode = ((lparam >> 16) & 0xff) as u32; - let extended = (lparam & 0x01000000) != 0; - handle_extended_keys(wparam as u16, scancode, extended) - .map(|(vkey, scancode)| (scancode, vkey_to_winit_vkey(vkey))) -} - -// This is needed as windows doesn't properly distinguish -// some virtual key codes for different keyboard layouts -fn map_text_keys(win_virtual_key: VIRTUAL_KEY) -> Option { - let char_key = unsafe { MapVirtualKeyA(win_virtual_key as u32, MAPVK_VK_TO_CHAR) } & 0x7FFF; - match char::from_u32(char_key) { - Some(';') => Some(VirtualKeyCode::Semicolon), - Some('/') => Some(VirtualKeyCode::Slash), - Some('`') => Some(VirtualKeyCode::Grave), - Some('[') => Some(VirtualKeyCode::LBracket), - Some(']') => Some(VirtualKeyCode::RBracket), - Some('\'') => Some(VirtualKeyCode::Apostrophe), - Some('\\') => Some(VirtualKeyCode::Backslash), - _ => None, - } -} diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 6d5f9d44a5..2c54bfb7f7 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -36,8 +36,8 @@ use windows_sys::Win32::{ Input::{ Ime::{GCS_COMPSTR, GCS_RESULTSTR, ISC_SHOWUICOMPOSITIONWINDOW}, KeyboardAndMouse::{ - MapVirtualKeyA, ReleaseCapture, SetCapture, TrackMouseEvent, MAPVK_VK_TO_VSC, - TME_LEAVE, TRACKMOUSEEVENT, + MapVirtualKeyW, ReleaseCapture, SetCapture, TrackMouseEvent, MAPVK_VK_TO_VSC_EX, + TME_LEAVE, TRACKMOUSEEVENT, VK_NUMLOCK, VK_SHIFT, }, Pointer::{ POINTER_FLAG_DOWN, POINTER_FLAG_UP, POINTER_FLAG_UPDATE, POINTER_INFO, @@ -47,7 +47,7 @@ use windows_sys::Win32::{ CloseTouchInputHandle, GetTouchInputInfo, TOUCHEVENTF_DOWN, TOUCHEVENTF_MOVE, TOUCHEVENTF_UP, TOUCHINPUT, }, - RIM_TYPEKEYBOARD, RIM_TYPEMOUSE, + RAWINPUT, RIM_TYPEKEYBOARD, RIM_TYPEMOUSE, }, WindowsAndMessaging::{ CreateWindowExW, DefWindowProcW, DestroyWindow, DispatchMessageW, GetCursorPos, @@ -58,34 +58,36 @@ use windows_sys::Win32::{ NCCALCSIZE_PARAMS, PM_NOREMOVE, PM_QS_PAINT, PM_REMOVE, PT_PEN, PT_TOUCH, QS_ALLEVENTS, RI_KEY_E0, RI_KEY_E1, RI_MOUSE_WHEEL, SC_MINIMIZE, SC_RESTORE, SIZE_MAXIMIZED, SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOSIZE, SWP_NOZORDER, WHEEL_DELTA, WINDOWPOS, - WM_CAPTURECHANGED, WM_CHAR, WM_CLOSE, WM_CREATE, WM_DESTROY, WM_DPICHANGED, - WM_DROPFILES, WM_ENTERSIZEMOVE, WM_EXITSIZEMOVE, WM_GETMINMAXINFO, WM_IME_COMPOSITION, - WM_IME_ENDCOMPOSITION, WM_IME_SETCONTEXT, WM_IME_STARTCOMPOSITION, WM_INPUT, - WM_INPUT_DEVICE_CHANGE, WM_KEYDOWN, WM_KEYUP, WM_KILLFOCUS, WM_LBUTTONDOWN, - WM_LBUTTONUP, WM_MBUTTONDOWN, WM_MBUTTONUP, WM_MENUCHAR, WM_MOUSEHWHEEL, WM_MOUSEMOVE, - WM_MOUSEWHEEL, WM_NCACTIVATE, WM_NCCALCSIZE, WM_NCCREATE, WM_NCDESTROY, - WM_NCLBUTTONDOWN, WM_PAINT, WM_POINTERDOWN, WM_POINTERUP, WM_POINTERUPDATE, - WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SETCURSOR, WM_SETFOCUS, WM_SETTINGCHANGE, WM_SIZE, - WM_SYSCHAR, WM_SYSCOMMAND, WM_SYSKEYDOWN, WM_SYSKEYUP, WM_TOUCH, WM_WINDOWPOSCHANGED, - WM_WINDOWPOSCHANGING, WM_XBUTTONDOWN, WM_XBUTTONUP, WNDCLASSEXW, WS_EX_LAYERED, - WS_EX_NOACTIVATE, WS_EX_TOOLWINDOW, WS_EX_TRANSPARENT, WS_OVERLAPPED, WS_POPUP, - WS_VISIBLE, + WM_CAPTURECHANGED, WM_CLOSE, WM_CREATE, WM_DESTROY, WM_DPICHANGED, WM_ENTERSIZEMOVE, + WM_EXITSIZEMOVE, WM_GETMINMAXINFO, WM_IME_COMPOSITION, WM_IME_ENDCOMPOSITION, + WM_IME_SETCONTEXT, WM_IME_STARTCOMPOSITION, WM_INPUT, WM_INPUT_DEVICE_CHANGE, + WM_KEYDOWN, WM_KEYUP, WM_KILLFOCUS, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN, + WM_MBUTTONUP, WM_MENUCHAR, WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCACTIVATE, + WM_NCCALCSIZE, WM_NCCREATE, WM_NCDESTROY, WM_NCLBUTTONDOWN, WM_PAINT, WM_POINTERDOWN, + WM_POINTERUP, WM_POINTERUPDATE, WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SETCURSOR, + WM_SETFOCUS, WM_SETTINGCHANGE, WM_SIZE, WM_SYSCOMMAND, WM_SYSKEYDOWN, WM_SYSKEYUP, + WM_TOUCH, WM_WINDOWPOSCHANGED, WM_WINDOWPOSCHANGING, WM_XBUTTONDOWN, WM_XBUTTONUP, + WNDCLASSEXW, WS_EX_LAYERED, WS_EX_NOACTIVATE, WS_EX_TOOLWINDOW, WS_EX_TRANSPARENT, + WS_OVERLAPPED, WS_POPUP, WS_VISIBLE, }, }, }; use crate::{ dpi::{PhysicalPosition, PhysicalSize}, - event::{DeviceEvent, Event, Force, Ime, KeyboardInput, Touch, TouchPhase, WindowEvent}, + event::{DeviceEvent, Event, Force, Ime, RawKeyEvent, Touch, TouchPhase, WindowEvent}, event_loop::{ ControlFlow, DeviceEventFilter, EventLoopClosed, EventLoopWindowTarget as RootELW, }, + keyboard::{KeyCode, ModifiersState}, + platform::scancode::KeyCodeExtScancode, platform_impl::platform::{ dark_mode::try_theme, dpi::{become_dpi_aware, dpi_to_scale_factor}, drop_handler::FileDropHandler, - event::{self, handle_extended_keys, process_key_params, vkey_to_winit_vkey}, ime::ImeContext, + keyboard::KeyEventBuilder, + keyboard_layout::LAYOUT_CACHE, monitor::{self, MonitorHandle}, raw_input, util, window::InitData, @@ -132,6 +134,7 @@ static GET_POINTER_PEN_INFO: Lazy> = pub(crate) struct WindowData { pub window_state: Arc>, pub event_loop_runner: EventLoopRunnerShared, + pub key_event_builder: KeyEventBuilder, pub _file_drop_handler: Option, pub userdata_removed: Cell, pub recurse_depth: Cell, @@ -158,6 +161,13 @@ impl ThreadMsgTargetData { } } +/// The result of a subclass procedure (the message handling callback) +#[derive(Clone, Copy)] +pub(crate) enum ProcResult { + DefWindowProc(WPARAM), + Value(isize), +} + pub struct EventLoop { thread_msg_sender: Sender, window_target: RootELW, @@ -853,11 +863,16 @@ unsafe fn process_control_flow(runner: &EventLoopRunner) { } /// Emit a `ModifiersChanged` event whenever modifiers have changed. -fn update_modifiers(window: HWND, userdata: &WindowData) { +/// Returns the current modifier state +fn update_modifiers(window: HWND, userdata: &WindowData) -> ModifiersState { use crate::event::WindowEvent::ModifiersChanged; - let modifiers = event::get_key_mods(); - let mut window_state = userdata.window_state_lock(); + let modifiers = { + let mut layouts = LAYOUT_CACHE.lock().unwrap(); + layouts.get_agnostic_mods() + }; + + let mut window_state = userdata.window_state.lock().unwrap(); if window_state.modifiers_state != modifiers { window_state.modifiers_state = modifiers; @@ -871,31 +886,13 @@ fn update_modifiers(window: HWND, userdata: &WindowData) { }); } } + modifiers } unsafe fn gain_active_focus(window: HWND, userdata: &WindowData) { - use crate::event::{ElementState::Released, WindowEvent::Focused}; - for windows_keycode in event::get_pressed_keys() { - let scancode = MapVirtualKeyA(windows_keycode as u32, MAPVK_VK_TO_VSC); - let virtual_keycode = event::vkey_to_winit_vkey(windows_keycode); - - update_modifiers(window, userdata); - - #[allow(deprecated)] - userdata.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window)), - event: WindowEvent::KeyboardInput { - device_id: DEVICE_ID, - input: KeyboardInput { - scancode, - virtual_keycode, - state: Released, - modifiers: event::get_key_mods(), - }, - is_synthetic: true, - }, - }) - } + use crate::event::WindowEvent::Focused; + + update_modifiers(window, userdata); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), @@ -904,30 +901,7 @@ unsafe fn gain_active_focus(window: HWND, userdata: &WindowData) { } unsafe fn lose_active_focus(window: HWND, userdata: &WindowData) { - use crate::event::{ - ElementState::Released, - ModifiersState, - WindowEvent::{Focused, ModifiersChanged}, - }; - for windows_keycode in event::get_pressed_keys() { - let scancode = MapVirtualKeyA(windows_keycode as u32, MAPVK_VK_TO_VSC); - let virtual_keycode = event::vkey_to_winit_vkey(windows_keycode); - - #[allow(deprecated)] - userdata.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window)), - event: WindowEvent::KeyboardInput { - device_id: DEVICE_ID, - input: KeyboardInput { - scancode, - virtual_keycode, - state: Released, - modifiers: event::get_key_mods(), - }, - is_synthetic: true, - }, - }) - } + use crate::event::WindowEvent::{Focused, ModifiersChanged}; userdata.window_state_lock().modifiers_state = ModifiersState::empty(); userdata.send_event(Event::WindowEvent { @@ -1021,6 +995,43 @@ unsafe fn public_window_callback_inner( RDW_INTERNALPAINT, ); + let mut result = ProcResult::DefWindowProc(wparam); + + // Send new modifiers before sending key events. + let mods_changed_callback = || match msg { + WM_KEYDOWN | WM_SYSKEYDOWN | WM_KEYUP | WM_SYSKEYUP => { + update_modifiers(window, userdata); + result = ProcResult::Value(0); + } + _ => (), + }; + userdata + .event_loop_runner + .catch_unwind(mods_changed_callback) + .unwrap_or_else(|| result = ProcResult::Value(-1)); + + let keyboard_callback = || { + use crate::event::WindowEvent::KeyboardInput; + let events = + userdata + .key_event_builder + .process_message(window, msg, wparam, lparam, &mut result); + for event in events { + userdata.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window)), + event: KeyboardInput { + device_id: DEVICE_ID, + event: event.event, + is_synthetic: event.is_synthetic, + }, + }); + } + }; + userdata + .event_loop_runner + .catch_unwind(keyboard_callback) + .unwrap_or_else(|| result = ProcResult::Value(-1)); + // I decided to bind the closure to `callback` and pass it to catch_unwind rather than passing // the closure to catch_unwind directly so that the match body indendation wouldn't change and // the git blame and history would be preserved. @@ -1028,7 +1039,8 @@ unsafe fn public_window_callback_inner( WM_NCCALCSIZE => { let window_flags = userdata.window_state_lock().window_flags; if wparam == 0 || window_flags.contains(WindowFlags::MARKER_DECORATIONS) { - return DefWindowProcW(window, msg, wparam, lparam); + result = ProcResult::DefWindowProc(wparam); + return; } let params = &mut *(lparam as *mut NCCALCSIZE_PARAMS); @@ -1060,14 +1072,14 @@ unsafe fn public_window_callback_inner( params.rgrc[0].bottom += 1; } - 0 + result = ProcResult::Value(0); } WM_ENTERSIZEMOVE => { userdata .window_state_lock() .set_window_flags_in_place(|f| f.insert(WindowFlags::MARKER_IN_SIZE_MOVE)); - 0 + result = ProcResult::Value(0); } WM_EXITSIZEMOVE => { @@ -1078,14 +1090,14 @@ unsafe fn public_window_callback_inner( } state.set_window_flags_in_place(|f| f.remove(WindowFlags::MARKER_IN_SIZE_MOVE)); - 0 + result = ProcResult::Value(0); } WM_NCLBUTTONDOWN => { if wparam == HTCAPTION as _ { PostMessageW(window, WM_MOUSEMOVE, 0, lparam); } - DefWindowProcW(window, msg, wparam, lparam) + result = ProcResult::DefWindowProc(wparam); } WM_CLOSE => { @@ -1094,7 +1106,7 @@ unsafe fn public_window_callback_inner( window_id: RootWindowId(WindowId(window)), event: CloseRequested, }); - 0 + result = ProcResult::Value(0); } WM_DESTROY => { @@ -1105,13 +1117,13 @@ unsafe fn public_window_callback_inner( event: Destroyed, }); userdata.event_loop_runner.remove_window(window); - 0 + result = ProcResult::Value(0); } WM_NCDESTROY => { super::set_window_long(window, GWL_USERDATA, 0); userdata.userdata_removed.set(true); - 0 + result = ProcResult::Value(0); } WM_PAINT => { @@ -1128,8 +1140,7 @@ unsafe fn public_window_callback_inner( process_control_flow(&userdata.event_loop_runner); } } - - DefWindowProcW(window, msg, wparam, lparam) + result = ProcResult::DefWindowProc(wparam); } WM_WINDOWPOSCHANGING => { @@ -1207,7 +1218,7 @@ unsafe fn public_window_callback_inner( } } - 0 + result = ProcResult::Value(0); } // WM_MOVE supplies client area positions, so we send Moved here instead. @@ -1224,7 +1235,7 @@ unsafe fn public_window_callback_inner( } // This is necessary for us to still get sent WM_SIZE. - DefWindowProcW(window, msg, wparam, lparam) + result = ProcResult::DefWindowProc(wparam); } WM_SIZE => { @@ -1250,56 +1261,13 @@ unsafe fn public_window_callback_inner( } } userdata.send_event(event); - 0 + result = ProcResult::Value(0); } - WM_CHAR | WM_SYSCHAR => { - use crate::event::WindowEvent::ReceivedCharacter; - use std::char; - let is_high_surrogate = (0xD800..=0xDBFF).contains(&wparam); - let is_low_surrogate = (0xDC00..=0xDFFF).contains(&wparam); - - if is_high_surrogate { - userdata.window_state_lock().high_surrogate = Some(wparam as u16); - } else if is_low_surrogate { - let high_surrogate = userdata.window_state_lock().high_surrogate.take(); - - if let Some(high_surrogate) = high_surrogate { - let pair = [high_surrogate, wparam as u16]; - if let Some(Ok(chr)) = char::decode_utf16(pair.iter().copied()).next() { - userdata.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window)), - event: ReceivedCharacter(chr), - }); - } - } - } else { - userdata.window_state_lock().high_surrogate = None; - - if let Some(chr) = char::from_u32(wparam as u32) { - userdata.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window)), - event: ReceivedCharacter(chr), - }); - } - } - - // todo(msiglreith): - // Ideally, `WM_SYSCHAR` shouldn't emit a `ReceivedChar` event - // indicating user text input. As we lack dedicated support - // accelerators/keybindings these events will be additionally - // emitted for downstream users. - // This means certain key combinations (ie Alt + Space) will - // trigger the default system behavior **and** emit a char event. - if msg == WM_SYSCHAR { - DefWindowProcW(window, msg, wparam, lparam) - } else { - 0 - } + WM_MENUCHAR => { + result = ProcResult::Value((MNC_CLOSE << 16) as isize); } - WM_MENUCHAR => (MNC_CLOSE << 16) as isize, - WM_IME_STARTCOMPOSITION => { let ime_allowed = userdata.window_state_lock().ime_allowed; if ime_allowed { @@ -1311,7 +1279,7 @@ unsafe fn public_window_callback_inner( }); } - DefWindowProcW(window, msg, wparam, lparam) + result = ProcResult::DefWindowProc(wparam); } WM_IME_COMPOSITION => { @@ -1363,7 +1331,7 @@ unsafe fn public_window_callback_inner( } // Not calling DefWindowProc to hide composing text drawn by IME. - 0 + result = ProcResult::Value(0); } WM_IME_ENDCOMPOSITION => { @@ -1396,14 +1364,13 @@ unsafe fn public_window_callback_inner( }); } - DefWindowProcW(window, msg, wparam, lparam) + result = ProcResult::DefWindowProc(wparam); } WM_IME_SETCONTEXT => { // Hide composing text drawn by IME. let wparam = wparam & (!ISC_SHOWUICOMPOSITIONWINDOW as usize); - - DefWindowProcW(window, msg, wparam, lparam) + result = ProcResult::DefWindowProc(wparam); } // this is necessary for us to maintain minimize/restore state @@ -1421,11 +1388,12 @@ unsafe fn public_window_callback_inner( if wparam == SC_SCREENSAVE as usize { let window_state = userdata.window_state_lock(); if window_state.fullscreen.is_some() { - return 0; + result = ProcResult::Value(0); + return; } } - DefWindowProcW(window, msg, wparam, lparam) + result = ProcResult::DefWindowProc(wparam); } WM_MOUSEMOVE => { @@ -1470,19 +1438,19 @@ unsafe fn public_window_callback_inner( w.mouse.last_position = Some(position); } if cursor_moved { - update_modifiers(window, userdata); + let modifiers = update_modifiers(window, userdata); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: CursorMoved { device_id: DEVICE_ID, position, - modifiers: event::get_key_mods(), + modifiers, }, }); } - 0 + result = ProcResult::Value(0); } WM_MOUSELEAVE => { @@ -1501,7 +1469,7 @@ unsafe fn public_window_callback_inner( }, }); - 0 + result = ProcResult::Value(0); } WM_MOUSEWHEEL => { @@ -1511,7 +1479,7 @@ unsafe fn public_window_callback_inner( let value = value as i32; let value = value as f32 / WHEEL_DELTA as f32; - update_modifiers(window, userdata); + let modifiers = update_modifiers(window, userdata); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), @@ -1519,11 +1487,11 @@ unsafe fn public_window_callback_inner( device_id: DEVICE_ID, delta: LineDelta(0.0, value), phase: TouchPhase::Moved, - modifiers: event::get_key_mods(), + modifiers, }, }); - 0 + result = ProcResult::Value(0); } WM_MOUSEHWHEEL => { @@ -1533,7 +1501,7 @@ unsafe fn public_window_callback_inner( let value = value as i32; let value = -value as f32 / WHEEL_DELTA as f32; // NOTE: inverted! See https://github.com/rust-windowing/winit/pull/2105/ - update_modifiers(window, userdata); + let modifiers = update_modifiers(window, userdata); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), @@ -1541,75 +1509,24 @@ unsafe fn public_window_callback_inner( device_id: DEVICE_ID, delta: LineDelta(value, 0.0), phase: TouchPhase::Moved, - modifiers: event::get_key_mods(), + modifiers, }, }); - 0 + result = ProcResult::Value(0); } WM_KEYDOWN | WM_SYSKEYDOWN => { - use crate::event::{ElementState::Pressed, VirtualKeyCode}; - if let Some((scancode, vkey)) = process_key_params(wparam, lparam) { - update_modifiers(window, userdata); - - #[allow(deprecated)] - userdata.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window)), - event: WindowEvent::KeyboardInput { - device_id: DEVICE_ID, - input: KeyboardInput { - state: Pressed, - scancode, - virtual_keycode: vkey, - modifiers: event::get_key_mods(), - }, - is_synthetic: false, - }, - }); - // Windows doesn't emit a delete character by default, but in order to make it - // consistent with the other platforms we'll emit a delete character here. - if vkey == Some(VirtualKeyCode::Delete) { - userdata.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window)), - event: WindowEvent::ReceivedCharacter('\u{7F}'), - }); - } - } - if msg == WM_SYSKEYDOWN { - DefWindowProcW(window, msg, wparam, lparam) - } else { - 0 + result = ProcResult::DefWindowProc(wparam); } } WM_KEYUP | WM_SYSKEYUP => { - use crate::event::ElementState::Released; - if let Some((scancode, vkey)) = process_key_params(wparam, lparam) { - update_modifiers(window, userdata); - - #[allow(deprecated)] - userdata.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window)), - event: WindowEvent::KeyboardInput { - device_id: DEVICE_ID, - input: KeyboardInput { - state: Released, - scancode, - virtual_keycode: vkey, - modifiers: event::get_key_mods(), - }, - is_synthetic: false, - }, - }); - } if msg == WM_SYSKEYUP && GetMenu(window) != 0 { // let Windows handle event if the window has a native menu, a modal event loop // is started here on Alt key up. - DefWindowProcW(window, msg, wparam, lparam) - } else { - 0 + result = ProcResult::DefWindowProc(wparam); } } @@ -1618,7 +1535,7 @@ unsafe fn public_window_callback_inner( capture_mouse(window, &mut userdata.window_state_lock()); - update_modifiers(window, userdata); + let modifiers = update_modifiers(window, userdata); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), @@ -1626,10 +1543,10 @@ unsafe fn public_window_callback_inner( device_id: DEVICE_ID, state: Pressed, button: Left, - modifiers: event::get_key_mods(), + modifiers, }, }); - 0 + result = ProcResult::Value(0); } WM_LBUTTONUP => { @@ -1639,7 +1556,7 @@ unsafe fn public_window_callback_inner( release_mouse(userdata.window_state_lock()); - update_modifiers(window, userdata); + let modifiers = update_modifiers(window, userdata); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), @@ -1647,10 +1564,10 @@ unsafe fn public_window_callback_inner( device_id: DEVICE_ID, state: Released, button: Left, - modifiers: event::get_key_mods(), + modifiers, }, }); - 0 + result = ProcResult::Value(0); } WM_RBUTTONDOWN => { @@ -1660,7 +1577,7 @@ unsafe fn public_window_callback_inner( capture_mouse(window, &mut userdata.window_state_lock()); - update_modifiers(window, userdata); + let modifiers = update_modifiers(window, userdata); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), @@ -1668,10 +1585,10 @@ unsafe fn public_window_callback_inner( device_id: DEVICE_ID, state: Pressed, button: Right, - modifiers: event::get_key_mods(), + modifiers, }, }); - 0 + result = ProcResult::Value(0); } WM_RBUTTONUP => { @@ -1681,7 +1598,7 @@ unsafe fn public_window_callback_inner( release_mouse(userdata.window_state_lock()); - update_modifiers(window, userdata); + let modifiers = update_modifiers(window, userdata); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), @@ -1689,10 +1606,10 @@ unsafe fn public_window_callback_inner( device_id: DEVICE_ID, state: Released, button: Right, - modifiers: event::get_key_mods(), + modifiers, }, }); - 0 + result = ProcResult::Value(0); } WM_MBUTTONDOWN => { @@ -1702,7 +1619,7 @@ unsafe fn public_window_callback_inner( capture_mouse(window, &mut userdata.window_state_lock()); - update_modifiers(window, userdata); + let modifiers = update_modifiers(window, userdata); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), @@ -1710,10 +1627,10 @@ unsafe fn public_window_callback_inner( device_id: DEVICE_ID, state: Pressed, button: Middle, - modifiers: event::get_key_mods(), + modifiers, }, }); - 0 + result = ProcResult::Value(0); } WM_MBUTTONUP => { @@ -1723,7 +1640,7 @@ unsafe fn public_window_callback_inner( release_mouse(userdata.window_state_lock()); - update_modifiers(window, userdata); + let modifiers = update_modifiers(window, userdata); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), @@ -1731,10 +1648,10 @@ unsafe fn public_window_callback_inner( device_id: DEVICE_ID, state: Released, button: Middle, - modifiers: event::get_key_mods(), + modifiers, }, }); - 0 + result = ProcResult::Value(0); } WM_XBUTTONDOWN => { @@ -1745,7 +1662,7 @@ unsafe fn public_window_callback_inner( capture_mouse(window, &mut userdata.window_state_lock()); - update_modifiers(window, userdata); + let modifiers = update_modifiers(window, userdata); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), @@ -1753,10 +1670,10 @@ unsafe fn public_window_callback_inner( device_id: DEVICE_ID, state: Pressed, button: Other(xbutton), - modifiers: event::get_key_mods(), + modifiers, }, }); - 0 + result = ProcResult::Value(0); } WM_XBUTTONUP => { @@ -1767,7 +1684,7 @@ unsafe fn public_window_callback_inner( release_mouse(userdata.window_state_lock()); - update_modifiers(window, userdata); + let modifiers = update_modifiers(window, userdata); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), @@ -1775,10 +1692,10 @@ unsafe fn public_window_callback_inner( device_id: DEVICE_ID, state: Released, button: Other(xbutton), - modifiers: event::get_key_mods(), + modifiers, }, }); - 0 + result = ProcResult::Value(0); } WM_CAPTURECHANGED => { @@ -1789,7 +1706,7 @@ unsafe fn public_window_callback_inner( if lparam != window { userdata.window_state_lock().mouse.capture_count = 0; } - 0 + result = ProcResult::Value(0); } WM_TOUCH => { @@ -1838,7 +1755,7 @@ unsafe fn public_window_callback_inner( } } CloseTouchInputHandle(htouch); - 0 + result = ProcResult::Value(0); } WM_POINTERDOWN | WM_POINTERUPDATE | WM_POINTERUP => { @@ -1861,7 +1778,8 @@ unsafe fn public_window_callback_inner( ptr::null_mut(), ) == false.into() { - return 0; + result = ProcResult::Value(0); + return; } let pointer_info_count = (entries_count * pointers_count) as usize; @@ -1873,7 +1791,8 @@ unsafe fn public_window_callback_inner( pointer_infos.as_mut_ptr(), ) == false.into() { - return 0; + result = ProcResult::Value(0); + return; } pointer_infos.set_len(pointer_info_count); @@ -1978,7 +1897,7 @@ unsafe fn public_window_callback_inner( SkipPointerFrameMessages(pointer_id); } - 0 + result = ProcResult::Value(0); } WM_NCACTIVATE => { @@ -1991,7 +1910,7 @@ unsafe fn public_window_callback_inner( lose_active_focus(window, userdata); } } - DefWindowProcW(window, msg, wparam, lparam) + result = ProcResult::DefWindowProc(wparam); } WM_SETFOCUS => { @@ -1999,7 +1918,7 @@ unsafe fn public_window_callback_inner( if active_focus_changed { gain_active_focus(window, userdata); } - 0 + result = ProcResult::Value(0); } WM_KILLFOCUS => { @@ -2007,7 +1926,7 @@ unsafe fn public_window_callback_inner( if active_focus_changed { lose_active_focus(window, userdata); } - 0 + result = ProcResult::Value(0); } WM_SETCURSOR => { @@ -2028,17 +1947,12 @@ unsafe fn public_window_callback_inner( Some(cursor) => { let cursor = LoadCursorW(0, cursor.to_windows_cursor()); SetCursor(cursor); - 0 + result = ProcResult::Value(0); } - None => DefWindowProcW(window, msg, wparam, lparam), + None => result = ProcResult::DefWindowProc(wparam), } } - WM_DROPFILES => { - // See `FileDropHandler` for implementation. - 0 - } - WM_GETMINMAXINFO => { let mmi = lparam as *mut MINMAXINFO; @@ -2066,7 +1980,7 @@ unsafe fn public_window_callback_inner( } } - 0 + result = ProcResult::Value(0); } // Only sent on Windows 8.1 or newer. On Windows 7 and older user has to log out to change @@ -2088,7 +2002,8 @@ unsafe fn public_window_callback_inner( window_state.scale_factor = new_scale_factor; if new_scale_factor == old_scale_factor { - return 0; + result = ProcResult::Value(0); + return; } let allow_resize = window_state.fullscreen.is_none() @@ -2272,7 +2187,7 @@ unsafe fn public_window_callback_inner( SWP_NOZORDER | SWP_NOACTIVATE, ); - 0 + result = ProcResult::Value(0); } WM_SETTINGCHANGE => { @@ -2293,26 +2208,25 @@ unsafe fn public_window_callback_inner( }); } } - - DefWindowProcW(window, msg, wparam, lparam) + result = ProcResult::DefWindowProc(wparam); } _ => { if msg == DESTROY_MSG_ID.get() { DestroyWindow(window); - 0 + result = ProcResult::Value(0); } else if msg == SET_RETAIN_STATE_ON_SIZE_MSG_ID.get() { let mut window_state = userdata.window_state_lock(); window_state.set_window_flags_in_place(|f| { f.set(WindowFlags::MARKER_RETAIN_STATE_ON_SIZE, wparam != 0) }); - 0 + result = ProcResult::Value(0); } else if msg == TASKBAR_CREATED.get() { let window_state = userdata.window_state_lock(); set_skip_taskbar(window, window_state.skip_taskbar); - DefWindowProcW(window, msg, wparam, lparam) + result = ProcResult::DefWindowProc(wparam); } else { - DefWindowProcW(window, msg, wparam, lparam) + result = ProcResult::DefWindowProc(wparam); } } }; @@ -2320,7 +2234,12 @@ unsafe fn public_window_callback_inner( userdata .event_loop_runner .catch_unwind(callback) - .unwrap_or(-1) + .unwrap_or_else(|| result = ProcResult::Value(-1)); + + match result { + ProcResult::DefWindowProc(wparam) => DefWindowProcW(window, msg, wparam, lparam), + ProcResult::Value(val) => val, + } } unsafe extern "system" fn thread_event_target_callback( @@ -2393,104 +2312,8 @@ unsafe extern "system" fn thread_event_target_callback( } WM_INPUT => { - use crate::event::{ - DeviceEvent::{Button, Key, Motion, MouseMotion, MouseWheel}, - ElementState::{Pressed, Released}, - MouseScrollDelta::LineDelta, - }; - - if let Some(data) = raw_input::get_raw_input_data(lparam) { - let device_id = wrap_device_id(data.header.hDevice as u32); - - if data.header.dwType == RIM_TYPEMOUSE { - let mouse = data.data.mouse; - - if util::has_flag(mouse.usFlags as u32, MOUSE_MOVE_RELATIVE) { - let x = mouse.lLastX as f64; - let y = mouse.lLastY as f64; - - if x != 0.0 { - userdata.send_event(Event::DeviceEvent { - device_id, - event: Motion { axis: 0, value: x }, - }); - } - - if y != 0.0 { - userdata.send_event(Event::DeviceEvent { - device_id, - event: Motion { axis: 1, value: y }, - }); - } - - if x != 0.0 || y != 0.0 { - userdata.send_event(Event::DeviceEvent { - device_id, - event: MouseMotion { delta: (x, y) }, - }); - } - } - - let mouse_button_flags = mouse.Anonymous.Anonymous.usButtonFlags; - - if util::has_flag(mouse_button_flags as u32, RI_MOUSE_WHEEL) { - let delta = mouse.Anonymous.Anonymous.usButtonData as i16 as f32 - / WHEEL_DELTA as f32; - userdata.send_event(Event::DeviceEvent { - device_id, - event: MouseWheel { - delta: LineDelta(0.0, delta), - }, - }); - } - - let button_state = - raw_input::get_raw_mouse_button_state(mouse_button_flags as u32); - // Left, middle, and right, respectively. - for (index, state) in button_state.iter().enumerate() { - if let Some(state) = *state { - // This gives us consistency with X11, since there doesn't - // seem to be anything else reasonable to do for a mouse - // button ID. - let button = (index + 1) as u32; - userdata.send_event(Event::DeviceEvent { - device_id, - event: Button { button, state }, - }); - } - } - } else if data.header.dwType == RIM_TYPEKEYBOARD { - let keyboard = data.data.keyboard; - - let pressed = - keyboard.Message == WM_KEYDOWN || keyboard.Message == WM_SYSKEYDOWN; - let released = keyboard.Message == WM_KEYUP || keyboard.Message == WM_SYSKEYUP; - - if pressed || released { - let state = if pressed { Pressed } else { Released }; - - let scancode = keyboard.MakeCode; - let extended = util::has_flag(keyboard.Flags, RI_KEY_E0 as u16) - | util::has_flag(keyboard.Flags, RI_KEY_E1 as u16); - - if let Some((vkey, scancode)) = - handle_extended_keys(keyboard.VKey, scancode as u32, extended) - { - let virtual_keycode = vkey_to_winit_vkey(vkey); - - #[allow(deprecated)] - userdata.send_event(Event::DeviceEvent { - device_id, - event: Key(KeyboardInput { - scancode, - state, - virtual_keycode, - modifiers: event::get_key_mods(), - }), - }); - } - } - } + if let Some(data) = raw_input::get_raw_input_data(lparam as _) { + handle_raw_input(&userdata, data); } DefWindowProcW(window, msg, wparam, lparam) @@ -2555,3 +2378,179 @@ unsafe extern "system" fn thread_event_target_callback( } result } + +unsafe fn handle_raw_input(userdata: &ThreadMsgTargetData, data: RAWINPUT) { + use crate::event::{ + DeviceEvent::{Button, Key, Motion, MouseMotion, MouseWheel}, + ElementState::{Pressed, Released}, + MouseScrollDelta::LineDelta, + }; + + let device_id = wrap_device_id(data.header.hDevice as _); + + if data.header.dwType == RIM_TYPEMOUSE { + let mouse = data.data.mouse; + + if util::has_flag(mouse.usFlags as u32, MOUSE_MOVE_RELATIVE) { + let x = mouse.lLastX as f64; + let y = mouse.lLastY as f64; + + if x != 0.0 { + userdata.send_event(Event::DeviceEvent { + device_id, + event: Motion { axis: 0, value: x }, + }); + } + + if y != 0.0 { + userdata.send_event(Event::DeviceEvent { + device_id, + event: Motion { axis: 1, value: y }, + }); + } + + if x != 0.0 || y != 0.0 { + userdata.send_event(Event::DeviceEvent { + device_id, + event: MouseMotion { delta: (x, y) }, + }); + } + } + + let button_flags = mouse.Anonymous.Anonymous.usButtonFlags; + + if util::has_flag(button_flags as u32, RI_MOUSE_WHEEL) { + let button_data = mouse.Anonymous.Anonymous.usButtonData; + // We must cast to i16 first, becaues `usButtonData` must be interpreted as signed. + let delta = button_data as i16 as f32 / WHEEL_DELTA as f32; + userdata.send_event(Event::DeviceEvent { + device_id, + event: MouseWheel { + delta: LineDelta(0.0, delta), + }, + }); + } + + let button_state = raw_input::get_raw_mouse_button_state(button_flags as u32); + // Left, middle, and right, respectively. + for (index, state) in button_state.iter().enumerate() { + if let Some(state) = *state { + // This gives us consistency with X11, since there doesn't + // seem to be anything else reasonable to do for a mouse + // button ID. + let button = (index + 1) as _; + userdata.send_event(Event::DeviceEvent { + device_id, + event: Button { button, state }, + }); + } + } + } else if data.header.dwType == RIM_TYPEKEYBOARD { + let keyboard = data.data.keyboard; + + let pressed = keyboard.Message == WM_KEYDOWN || keyboard.Message == WM_SYSKEYDOWN; + let released = keyboard.Message == WM_KEYUP || keyboard.Message == WM_SYSKEYUP; + + if !pressed && !released { + return; + } + + let state = if pressed { Pressed } else { Released }; + let extension = { + if util::has_flag(keyboard.Flags, RI_KEY_E0 as _) { + 0xE000 + } else if util::has_flag(keyboard.Flags, RI_KEY_E1 as _) { + 0xE100 + } else { + 0x0000 + } + }; + let scancode = if keyboard.MakeCode == 0 { + // In some cases (often with media keys) the device reports a scancode of 0 but a + // valid virtual key. In these cases we obtain the scancode from the virtual key. + MapVirtualKeyW(keyboard.VKey as u32, MAPVK_VK_TO_VSC_EX) as u16 + } else { + keyboard.MakeCode | extension + }; + if scancode == 0xE11D || scancode == 0xE02A { + // At the hardware (or driver?) level, pressing the Pause key is equivalent to pressing + // Ctrl+NumLock. + // This equvalence means that if the user presses Pause, the keyboard will emit two + // subsequent keypresses: + // 1, 0xE11D - Which is a left Ctrl (0x1D) with an extension flag (0xE100) + // 2, 0x0045 - Which on its own can be interpreted as Pause + // + // There's another combination which isn't quite an equivalence: + // PrtSc used to be Shift+Asterisk. This means that on some keyboards, presssing + // PrtSc (print screen) produces the following sequence: + // 1, 0xE02A - Which is a left shift (0x2A) with an extension flag (0xE000) + // 2, 0xE037 - Which is a numpad multiply (0x37) with an exteion flag (0xE000). This on + // its own it can be interpreted as PrtSc + // + // For this reason, if we encounter the first keypress, we simply ignore it, trusting + // that there's going to be another event coming, from which we can extract the + // appropriate key. + // For more on this, read the article by Raymond Chen, titled: + // "Why does Ctrl+ScrollLock cancel dialogs?" + // https://devblogs.microsoft.com/oldnewthing/20080211-00/?p=23503 + return; + } + let code = if keyboard.VKey == VK_NUMLOCK { + // Historically, the NumLock and the Pause key were one and the same physical key. + // The user could trigger Pause by pressing Ctrl+NumLock. + // Now these are often physically separate and the two keys can be differentiated by + // checking the extension flag of the scancode. NumLock is 0xE045, Pause is 0x0045. + // + // However in this event, both keys are reported as 0x0045 even on modern hardware. + // Therefore we use the virtual key instead to determine whether it's a NumLock and + // set the KeyCode accordingly. + // + // For more on this, read the article by Raymond Chen, titled: + // "Why does Ctrl+ScrollLock cancel dialogs?" + // https://devblogs.microsoft.com/oldnewthing/20080211-00/?p=23503 + KeyCode::NumLock + } else { + KeyCode::from_scancode(scancode as u32) + }; + if keyboard.VKey == VK_SHIFT { + match code { + KeyCode::NumpadDecimal + | KeyCode::Numpad0 + | KeyCode::Numpad1 + | KeyCode::Numpad2 + | KeyCode::Numpad3 + | KeyCode::Numpad4 + | KeyCode::Numpad5 + | KeyCode::Numpad6 + | KeyCode::Numpad7 + | KeyCode::Numpad8 + | KeyCode::Numpad9 => { + // On Windows, holding the Shift key makes numpad keys behave as if NumLock + // wasn't active. The way this is exposed to applications by the system is that + // the application receives a fake key release event for the shift key at the + // moment when the numpad key is pressed, just before receiving the numpad key + // as well. + // + // The issue is that in the raw device event (here), the fake shift release + // event reports the numpad key as the scancode. Unfortunately, the event doesn't + // have any information to tell whether it's the left shift or the right shift + // that needs to get the fake release (or press) event so we don't forward this + // event to the application at all. + // + // For more on this, read the article by Raymond Chen, titled: + // "The shift key overrides NumLock" + // https://devblogs.microsoft.com/oldnewthing/20040906-00/?p=37953 + return; + } + _ => (), + } + } + userdata.send_event(Event::DeviceEvent { + device_id, + event: Key(RawKeyEvent { + physical_key: code, + state, + }), + }); + } +} diff --git a/src/platform_impl/windows/keyboard.rs b/src/platform_impl/windows/keyboard.rs new file mode 100644 index 0000000000..1109162947 --- /dev/null +++ b/src/platform_impl/windows/keyboard.rs @@ -0,0 +1,937 @@ +use std::{ + char, + collections::HashSet, + ffi::OsString, + mem::MaybeUninit, + os::windows::ffi::OsStringExt, + sync::{ + atomic::{AtomicU32, Ordering::Relaxed}, + Mutex, MutexGuard, + }, +}; + +use windows_sys::Win32::{ + Foundation::{HWND, LPARAM, WPARAM}, + UI::{ + Input::KeyboardAndMouse::{ + GetAsyncKeyState, GetKeyState, GetKeyboardState, MapVirtualKeyExW, MAPVK_VK_TO_VSC_EX, + MAPVK_VSC_TO_VK_EX, VIRTUAL_KEY, VK_ABNT_C2, VK_ADD, VK_CAPITAL, VK_CLEAR, VK_CONTROL, + VK_DECIMAL, VK_DELETE, VK_DIVIDE, VK_DOWN, VK_END, VK_F4, VK_HOME, VK_INSERT, + VK_LCONTROL, VK_LEFT, VK_LMENU, VK_LSHIFT, VK_LWIN, VK_MENU, VK_MULTIPLY, VK_NEXT, + VK_NUMLOCK, VK_NUMPAD0, VK_NUMPAD1, VK_NUMPAD2, VK_NUMPAD3, VK_NUMPAD4, VK_NUMPAD5, + VK_NUMPAD6, VK_NUMPAD7, VK_NUMPAD8, VK_NUMPAD9, VK_PRIOR, VK_RCONTROL, VK_RETURN, + VK_RIGHT, VK_RMENU, VK_RSHIFT, VK_RWIN, VK_SCROLL, VK_SHIFT, VK_SUBTRACT, VK_UP, + }, + TextServices::HKL, + WindowsAndMessaging::{ + PeekMessageW, MSG, PM_NOREMOVE, WM_CHAR, WM_DEADCHAR, WM_KEYDOWN, WM_KEYFIRST, + WM_KEYLAST, WM_KEYUP, WM_KILLFOCUS, WM_SETFOCUS, WM_SYSCHAR, WM_SYSDEADCHAR, + WM_SYSKEYDOWN, WM_SYSKEYUP, + }, + }, +}; + +use unicode_segmentation::UnicodeSegmentation; + +use crate::{ + event::{ElementState, KeyEvent}, + keyboard::{Key, KeyCode, KeyLocation, NativeKey}, + platform::scancode::KeyCodeExtScancode, + platform_impl::platform::{ + event_loop::ProcResult, + keyboard_layout::{get_or_insert_str, Layout, LayoutCache, WindowsModifiers, LAYOUT_CACHE}, + KeyEventExtra, + }, +}; + +pub type ExScancode = u16; + +pub struct MessageAsKeyEvent { + pub event: KeyEvent, + pub is_synthetic: bool, +} + +/// Stores information required to make `KeyEvent`s. +/// +/// A single Winit `KeyEvent` contains information which the Windows API passes to the application +/// in multiple window messages. In other words: a Winit `KeyEvent` cannot be built from a single +/// window message. Therefore, this type keeps track of certain information from previous events so +/// that a `KeyEvent` can be constructed when the last event related to a keypress is received. +/// +/// `PeekMessage` is sometimes used to determine whether the next window message still belongs to the +/// current keypress. If it doesn't and the current state represents a key event waiting to be +/// dispatched, then said event is considered complete and is dispatched. +/// +/// The sequence of window messages for a key press event is the following: +/// - Exactly one WM_KEYDOWN / WM_SYSKEYDOWN +/// - Zero or one WM_DEADCHAR / WM_SYSDEADCHAR +/// - Zero or more WM_CHAR / WM_SYSCHAR. These messages each come with a UTF-16 code unit which when +/// put together in the sequence they arrived in, forms the text which is the result of pressing the +/// key. +/// +/// Key release messages are a bit different due to the fact that they don't contribute to +/// text input. The "sequence" only consists of one WM_KEYUP / WM_SYSKEYUP event. +pub struct KeyEventBuilder { + event_info: Mutex>, + pending: PendingEventQueue, +} +impl Default for KeyEventBuilder { + fn default() -> Self { + KeyEventBuilder { + event_info: Mutex::new(None), + pending: Default::default(), + } + } +} +impl KeyEventBuilder { + /// Call this function for every window message. + /// Returns Some() if this window message completes a KeyEvent. + /// Returns None otherwise. + pub(crate) fn process_message( + &self, + hwnd: HWND, + msg_kind: u32, + wparam: WPARAM, + lparam: LPARAM, + result: &mut ProcResult, + ) -> Vec { + enum MatchResult { + Nothing, + TokenToRemove(PendingMessageToken), + MessagesToDispatch(Vec), + } + + let mut matcher = || -> MatchResult { + match msg_kind { + WM_SETFOCUS => { + // synthesize keydown events + let kbd_state = get_async_kbd_state(); + let key_events = Self::synthesize_kbd_state(ElementState::Pressed, &kbd_state); + MatchResult::MessagesToDispatch(self.pending.complete_multi(key_events)) + } + WM_KILLFOCUS => { + // sythesize keyup events + let kbd_state = get_kbd_state(); + let key_events = Self::synthesize_kbd_state(ElementState::Released, &kbd_state); + MatchResult::MessagesToDispatch(self.pending.complete_multi(key_events)) + } + WM_KEYDOWN | WM_SYSKEYDOWN => { + if msg_kind == WM_SYSKEYDOWN && wparam as VIRTUAL_KEY == VK_F4 { + // Don't dispatch Alt+F4 to the application. + // This is handled in `event_loop.rs` + return MatchResult::Nothing; + } + let pending_token = self.pending.add_pending(); + *result = ProcResult::Value(0); + + let next_msg = next_kbd_msg(hwnd); + + let mut layouts = LAYOUT_CACHE.lock().unwrap(); + let mut finished_event_info = Some(PartialKeyEventInfo::from_message( + wparam, + lparam, + ElementState::Pressed, + &mut layouts, + )); + let mut event_info = self.event_info.lock().unwrap(); + *event_info = None; + if let Some(next_msg) = next_msg { + let next_msg_kind = next_msg.message; + let next_belongs_to_this = !matches!( + next_msg_kind, + WM_KEYDOWN | WM_SYSKEYDOWN | WM_KEYUP | WM_SYSKEYUP + ); + if next_belongs_to_this { + // The next OS event belongs to this Winit event, so let's just + // store the partial information, and add to it in the upcoming events + *event_info = finished_event_info.take(); + } else { + let (_, layout) = layouts.get_current_layout(); + let is_fake = { + let curr_event = finished_event_info.as_ref().unwrap(); + is_current_fake(curr_event, next_msg, layout) + }; + if is_fake { + finished_event_info = None; + } + } + } + if let Some(event_info) = finished_event_info { + let ev = event_info.finalize(&mut layouts.strings); + return MatchResult::MessagesToDispatch(self.pending.complete_pending( + pending_token, + MessageAsKeyEvent { + event: ev, + is_synthetic: false, + }, + )); + } + MatchResult::TokenToRemove(pending_token) + } + WM_DEADCHAR | WM_SYSDEADCHAR => { + let pending_token = self.pending.add_pending(); + *result = ProcResult::Value(0); + // At this point, we know that there isn't going to be any more events related to + // this key press + let event_info = self.event_info.lock().unwrap().take().unwrap(); + let mut layouts = LAYOUT_CACHE.lock().unwrap(); + let ev = event_info.finalize(&mut layouts.strings); + MatchResult::MessagesToDispatch(self.pending.complete_pending( + pending_token, + MessageAsKeyEvent { + event: ev, + is_synthetic: false, + }, + )) + } + WM_CHAR | WM_SYSCHAR => { + let mut event_info = self.event_info.lock().unwrap(); + if event_info.is_none() { + trace!("Received a CHAR message but no `event_info` was available. The message is probably IME, returning."); + return MatchResult::Nothing; + } + let pending_token = self.pending.add_pending(); + *result = ProcResult::Value(0); + let is_high_surrogate = (0xD800..=0xDBFF).contains(&wparam); + let is_low_surrogate = (0xDC00..=0xDFFF).contains(&wparam); + + let is_utf16 = is_high_surrogate || is_low_surrogate; + + if is_utf16 { + if let Some(ev_info) = event_info.as_mut() { + ev_info.utf16parts.push(wparam as u16); + } + } else { + // In this case, wparam holds a UTF-32 character. + // Let's encode it as UTF-16 and append it to the end of `utf16parts` + let utf16parts = match event_info.as_mut() { + Some(ev_info) => &mut ev_info.utf16parts, + None => { + warn!("The event_info was None when it was expected to be some"); + return MatchResult::TokenToRemove(pending_token); + } + }; + let start_offset = utf16parts.len(); + let new_size = utf16parts.len() + 2; + utf16parts.resize(new_size, 0); + if let Some(ch) = char::from_u32(wparam as u32) { + let encode_len = ch.encode_utf16(&mut utf16parts[start_offset..]).len(); + let new_size = start_offset + encode_len; + utf16parts.resize(new_size, 0); + } + } + // It's important that we unlock the mutex, and create the pending event token before + // calling `next_msg` + std::mem::drop(event_info); + let next_msg = next_kbd_msg(hwnd); + let more_char_coming = next_msg + .map(|m| matches!(m.message, WM_CHAR | WM_SYSCHAR)) + .unwrap_or(false); + if more_char_coming { + // No need to produce an event just yet, because there are still more characters that + // need to appended to this keyobard event + MatchResult::TokenToRemove(pending_token) + } else { + let mut event_info = self.event_info.lock().unwrap(); + let mut event_info = match event_info.take() { + Some(ev_info) => ev_info, + None => { + warn!("The event_info was None when it was expected to be some"); + return MatchResult::TokenToRemove(pending_token); + } + }; + let mut layouts = LAYOUT_CACHE.lock().unwrap(); + // It's okay to call `ToUnicode` here, because at this point the dead key + // is already consumed by the character. + let kbd_state = get_kbd_state(); + let mod_state = WindowsModifiers::active_modifiers(&kbd_state); + + let (_, layout) = layouts.get_current_layout(); + let ctrl_on = if layout.has_alt_graph { + let alt_on = mod_state.contains(WindowsModifiers::ALT); + !alt_on && mod_state.contains(WindowsModifiers::CONTROL) + } else { + mod_state.contains(WindowsModifiers::CONTROL) + }; + + // If Ctrl is not pressed, just use the text with all + // modifiers because that already consumed the dead key. Otherwise, + // we would interpret the character incorrectly, missing the dead key. + if !ctrl_on { + event_info.text = PartialText::System(event_info.utf16parts.clone()); + } else { + let mod_no_ctrl = mod_state.remove_only_ctrl(); + let num_lock_on = kbd_state[VK_NUMLOCK as usize] & 1 != 0; + let vkey = event_info.vkey; + let keycode = &event_info.code; + let key = layout.get_key(mod_no_ctrl, num_lock_on, vkey, keycode); + event_info.text = PartialText::Text(key.to_text()); + } + let ev = event_info.finalize(&mut layouts.strings); + MatchResult::MessagesToDispatch(self.pending.complete_pending( + pending_token, + MessageAsKeyEvent { + event: ev, + is_synthetic: false, + }, + )) + } + } + WM_KEYUP | WM_SYSKEYUP => { + let pending_token = self.pending.add_pending(); + *result = ProcResult::Value(0); + + let mut layouts = LAYOUT_CACHE.lock().unwrap(); + let event_info = PartialKeyEventInfo::from_message( + wparam, + lparam, + ElementState::Released, + &mut layouts, + ); + // It's important that we create the pending token before reading the next message. + let next_msg = next_kbd_msg(hwnd); + let mut valid_event_info = Some(event_info); + if let Some(next_msg) = next_msg { + let (_, layout) = layouts.get_current_layout(); + let is_fake = { + let event_info = valid_event_info.as_ref().unwrap(); + is_current_fake(event_info, next_msg, layout) + }; + if is_fake { + valid_event_info = None; + } + } + if let Some(event_info) = valid_event_info { + let event = event_info.finalize(&mut layouts.strings); + return MatchResult::MessagesToDispatch(self.pending.complete_pending( + pending_token, + MessageAsKeyEvent { + event, + is_synthetic: false, + }, + )); + } + MatchResult::TokenToRemove(pending_token) + } + _ => MatchResult::Nothing, + } + }; + let matcher_result = matcher(); + match matcher_result { + MatchResult::TokenToRemove(t) => self.pending.remove_pending(t), + MatchResult::MessagesToDispatch(m) => m, + MatchResult::Nothing => Vec::new(), + } + } + + // Alowing nominimal_bool lint because the `is_key_pressed` macro triggers this warning + // and I don't know of another way to resolve it and also keeping the macro + #[allow(clippy::nonminimal_bool)] + fn synthesize_kbd_state( + key_state: ElementState, + kbd_state: &[u8; 256], + ) -> Vec { + let mut key_events = Vec::new(); + + let mut layouts = LAYOUT_CACHE.lock().unwrap(); + let (locale_id, _) = layouts.get_current_layout(); + + macro_rules! is_key_pressed { + ($vk:expr) => { + kbd_state[$vk as usize] & 0x80 != 0 + }; + } + + // Is caps-lock active? Note that this is different from caps-lock + // being held down. + let caps_lock_on = kbd_state[VK_CAPITAL as usize] & 1 != 0; + let num_lock_on = kbd_state[VK_NUMLOCK as usize] & 1 != 0; + + // We are synthesizing the press event for caps-lock first for the following reasons: + // 1. If caps-lock is *not* held down but *is* active, then we have to + // synthesize all printable keys, respecting the caps-lock state. + // 2. If caps-lock is held down, we could choose to sythesize its + // keypress after every other key, in which case all other keys *must* + // be sythesized as if the caps-lock state was be the opposite + // of what it currently is. + // -- + // For the sake of simplicity we are choosing to always sythesize + // caps-lock first, and always use the current caps-lock state + // to determine the produced text + if is_key_pressed!(VK_CAPITAL) { + let event = Self::create_synthetic( + VK_CAPITAL, + key_state, + caps_lock_on, + num_lock_on, + locale_id as HKL, + &mut layouts, + ); + if let Some(event) = event { + key_events.push(event); + } + } + let do_non_modifier = |key_events: &mut Vec<_>, layouts: &mut _| { + for vk in 0..256 { + match vk { + VK_CONTROL | VK_LCONTROL | VK_RCONTROL | VK_SHIFT | VK_LSHIFT | VK_RSHIFT + | VK_MENU | VK_LMENU | VK_RMENU | VK_CAPITAL => continue, + _ => (), + } + if !is_key_pressed!(vk) { + continue; + } + let event = Self::create_synthetic( + vk, + key_state, + caps_lock_on, + num_lock_on, + locale_id as HKL, + layouts, + ); + if let Some(event) = event { + key_events.push(event); + } + } + }; + let do_modifier = |key_events: &mut Vec<_>, layouts: &mut _| { + const CLEAR_MODIFIER_VKS: [VIRTUAL_KEY; 6] = [ + VK_LCONTROL, + VK_LSHIFT, + VK_LMENU, + VK_RCONTROL, + VK_RSHIFT, + VK_RMENU, + ]; + for vk in CLEAR_MODIFIER_VKS.iter() { + if is_key_pressed!(*vk) { + let event = Self::create_synthetic( + *vk, + key_state, + caps_lock_on, + num_lock_on, + locale_id as HKL, + layouts, + ); + if let Some(event) = event { + key_events.push(event); + } + } + } + }; + + // Be cheeky and sequence modifier and non-modifier + // key events such that non-modifier keys are not affected + // by modifiers (except for caps-lock) + match key_state { + ElementState::Pressed => { + do_non_modifier(&mut key_events, &mut layouts); + do_modifier(&mut key_events, &mut layouts); + } + ElementState::Released => { + do_modifier(&mut key_events, &mut layouts); + do_non_modifier(&mut key_events, &mut layouts); + } + } + + key_events + } + + fn create_synthetic( + vk: VIRTUAL_KEY, + key_state: ElementState, + caps_lock_on: bool, + num_lock_on: bool, + locale_id: HKL, + layouts: &mut MutexGuard<'_, LayoutCache>, + ) -> Option { + let scancode = unsafe { MapVirtualKeyExW(vk as u32, MAPVK_VK_TO_VSC_EX, locale_id) }; + if scancode == 0 { + return None; + } + let scancode = scancode as ExScancode; + let code = KeyCode::from_scancode(scancode as u32); + let mods = if caps_lock_on { + WindowsModifiers::CAPS_LOCK + } else { + WindowsModifiers::empty() + }; + let layout = layouts.layouts.get(&(locale_id as u64)).unwrap(); + let logical_key = layout.get_key(mods, num_lock_on, vk, &code); + let key_without_modifiers = layout.get_key(WindowsModifiers::empty(), false, vk, &code); + let text = if key_state == ElementState::Pressed { + logical_key.to_text() + } else { + None + }; + let event_info = PartialKeyEventInfo { + vkey: vk, + logical_key: PartialLogicalKey::This(logical_key.clone()), + key_without_modifiers, + key_state, + is_repeat: false, + code, + location: get_location(scancode, locale_id), + utf16parts: Vec::with_capacity(8), + text: PartialText::Text(text), + }; + + let mut event = event_info.finalize(&mut layouts.strings); + event.logical_key = logical_key; + event.platform_specific.text_with_all_modifers = text; + Some(MessageAsKeyEvent { + event, + is_synthetic: true, + }) + } +} + +enum PartialText { + // Unicode + System(Vec), + Text(Option<&'static str>), +} + +enum PartialLogicalKey { + /// Use the text provided by the WM_CHAR messages and report that as a `Character` variant. If + /// the text consists of multiple grapheme clusters (user-precieved characters) that means that + /// dead key could not be combined with the second input, and in that case we should fall back + /// to using what would have without a dead-key input. + TextOr(Key<'static>), + + /// Use the value directly provided by this variant + This(Key<'static>), +} + +struct PartialKeyEventInfo { + vkey: VIRTUAL_KEY, + key_state: ElementState, + is_repeat: bool, + code: KeyCode, + location: KeyLocation, + logical_key: PartialLogicalKey, + + key_without_modifiers: Key<'static>, + + /// The UTF-16 code units of the text that was produced by the keypress event. + /// This take all modifiers into account. Including CTRL + utf16parts: Vec, + + text: PartialText, +} + +impl PartialKeyEventInfo { + fn from_message( + wparam: WPARAM, + lparam: LPARAM, + state: ElementState, + layouts: &mut MutexGuard<'_, LayoutCache>, + ) -> Self { + const NO_MODS: WindowsModifiers = WindowsModifiers::empty(); + + let (_, layout) = layouts.get_current_layout(); + let lparam_struct = destructure_key_lparam(lparam); + let vkey = wparam as VIRTUAL_KEY; + let scancode = if lparam_struct.scancode == 0 { + // In some cases (often with media keys) the device reports a scancode of 0 but a + // valid virtual key. In these cases we obtain the scancode from the virtual key. + unsafe { MapVirtualKeyExW(vkey as u32, MAPVK_VK_TO_VSC_EX, layout.hkl as HKL) as u16 } + } else { + new_ex_scancode(lparam_struct.scancode, lparam_struct.extended) + }; + let code = KeyCode::from_scancode(scancode as u32); + let location = get_location(scancode, layout.hkl as HKL); + + let kbd_state = get_kbd_state(); + let mods = WindowsModifiers::active_modifiers(&kbd_state); + let mods_without_ctrl = mods.remove_only_ctrl(); + let num_lock_on = kbd_state[VK_NUMLOCK as usize] & 1 != 0; + + // On Windows Ctrl+NumLock = Pause (and apparently Ctrl+Pause -> NumLock). In these cases + // the KeyCode still stores the real key, so in the name of consistency across platforms, we + // circumvent this mapping and force the key values to match the keycode. + // For more on this, read the article by Raymond Chen, titled: + // "Why does Ctrl+ScrollLock cancel dialogs?" + // https://devblogs.microsoft.com/oldnewthing/20080211-00/?p=23503 + let code_as_key = if mods.contains(WindowsModifiers::CONTROL) { + match code { + KeyCode::NumLock => Some(Key::NumLock), + KeyCode::Pause => Some(Key::Pause), + _ => None, + } + } else { + None + }; + + let preliminary_logical_key = layout.get_key(mods_without_ctrl, num_lock_on, vkey, &code); + let key_is_char = matches!(preliminary_logical_key, Key::Character(_)); + let is_pressed = state == ElementState::Pressed; + + let logical_key = if let Some(key) = code_as_key.clone() { + PartialLogicalKey::This(key) + } else if is_pressed && key_is_char && !mods.contains(WindowsModifiers::CONTROL) { + // In some cases we want to use the UNICHAR text for logical_key in order to allow + // dead keys to have an effect on the character reported by `logical_key`. + PartialLogicalKey::TextOr(preliminary_logical_key) + } else { + PartialLogicalKey::This(preliminary_logical_key) + }; + let key_without_modifiers = if let Some(key) = code_as_key { + key + } else { + match layout.get_key(NO_MODS, false, vkey, &code) { + // We convert dead keys into their character. + // The reason for this is that `key_without_modifiers` is designed for key-bindings, + // but the US International layout treats `'` (apostrophe) as a dead key and the + // reguar US layout treats it a character. In order for a single binding + // configuration to work with both layouts, we forward each dead key as a character. + Key::Dead(k) => { + if let Some(ch) = k { + // I'm avoiding the heap allocation. I don't want to talk about it :( + let mut utf8 = [0; 4]; + let s = ch.encode_utf8(&mut utf8); + let static_str = get_or_insert_str(&mut layouts.strings, s); + Key::Character(static_str) + } else { + Key::Unidentified(NativeKey::Unidentified) + } + } + key => key, + } + }; + + PartialKeyEventInfo { + vkey, + key_state: state, + logical_key, + key_without_modifiers, + is_repeat: lparam_struct.is_repeat, + code, + location, + utf16parts: Vec::with_capacity(8), + text: PartialText::System(Vec::new()), + } + } + + fn finalize(self, strings: &mut HashSet<&'static str>) -> KeyEvent { + let mut char_with_all_modifiers = None; + if !self.utf16parts.is_empty() { + let os_string = OsString::from_wide(&self.utf16parts); + if let Ok(string) = os_string.into_string() { + let static_str = get_or_insert_str(strings, string); + char_with_all_modifiers = Some(static_str); + } + } + + // The text without Ctrl + let mut text = None; + match self.text { + PartialText::System(wide) => { + if !wide.is_empty() { + let os_string = OsString::from_wide(&wide); + if let Ok(string) = os_string.into_string() { + let static_str = get_or_insert_str(strings, string); + text = Some(static_str); + } + } + } + PartialText::Text(s) => { + text = s; + } + } + + let logical_key = match self.logical_key { + PartialLogicalKey::TextOr(fallback) => match text { + Some(s) => { + if s.grapheme_indices(true).count() > 1 { + fallback + } else { + Key::Character(s) + } + } + None => Key::Unidentified(NativeKey::Windows(self.vkey)), + }, + PartialLogicalKey::This(v) => v, + }; + + KeyEvent { + physical_key: self.code, + logical_key, + text, + location: self.location, + state: self.key_state, + repeat: self.is_repeat, + platform_specific: KeyEventExtra { + text_with_all_modifers: char_with_all_modifiers, + key_without_modifiers: self.key_without_modifiers, + }, + } + } +} + +#[derive(Debug, Copy, Clone)] +struct KeyLParam { + pub scancode: u8, + pub extended: bool, + + /// This is `previous_state XOR transition_state`. See the lParam for WM_KEYDOWN and WM_KEYUP for further details. + pub is_repeat: bool, +} + +fn destructure_key_lparam(lparam: LPARAM) -> KeyLParam { + let previous_state = (lparam >> 30) & 0x01; + let transition_state = (lparam >> 31) & 0x01; + KeyLParam { + scancode: ((lparam >> 16) & 0xFF) as u8, + extended: ((lparam >> 24) & 0x01) != 0, + is_repeat: (previous_state ^ transition_state) != 0, + } +} + +#[inline] +fn new_ex_scancode(scancode: u8, extended: bool) -> ExScancode { + (scancode as u16) | (if extended { 0xE000 } else { 0 }) +} + +#[inline] +fn ex_scancode_from_lparam(lparam: LPARAM) -> ExScancode { + let lparam = destructure_key_lparam(lparam); + new_ex_scancode(lparam.scancode, lparam.extended) +} + +/// Gets the keyboard state as reported by messages that have been removed from the event queue. +/// See also: get_async_kbd_state +fn get_kbd_state() -> [u8; 256] { + unsafe { + let mut kbd_state: MaybeUninit<[u8; 256]> = MaybeUninit::uninit(); + GetKeyboardState(kbd_state.as_mut_ptr() as *mut u8); + kbd_state.assume_init() + } +} + +/// Gets the current keyboard state regardless of whether the corresponding keyboard events have +/// been removed from the event queue. See also: get_kbd_state +#[allow(clippy::uninit_assumed_init)] +fn get_async_kbd_state() -> [u8; 256] { + unsafe { + let mut kbd_state: [u8; 256] = [0; 256]; + for (vk, state) in kbd_state.iter_mut().enumerate() { + let vk = vk as VIRTUAL_KEY; + let async_state = GetAsyncKeyState(vk as i32); + let is_down = (async_state & (1 << 15)) != 0; + *state = if is_down { 0x80 } else { 0 }; + + if matches!(vk, VK_CAPITAL | VK_NUMLOCK | VK_SCROLL) { + // Toggle states aren't reported by `GetAsyncKeyState` + let toggle_state = GetKeyState(vk as i32); + let is_active = (toggle_state & 1) != 0; + *state |= u8::from(is_active); + } + } + kbd_state + } +} + +/// On windows, AltGr == Ctrl + Alt +/// +/// Due to this equivalence, the system generates a fake Ctrl key-press (and key-release) preceeding +/// every AltGr key-press (and key-release). We check if the current event is a Ctrl event and if +/// the next event is a right Alt (AltGr) event. If this is the case, the current event must be the +/// fake Ctrl event. +fn is_current_fake(curr_info: &PartialKeyEventInfo, next_msg: MSG, layout: &Layout) -> bool { + let curr_is_ctrl = matches!(curr_info.logical_key, PartialLogicalKey::This(Key::Control)); + if layout.has_alt_graph { + let next_code = ex_scancode_from_lparam(next_msg.lParam); + let next_is_altgr = next_code == 0xE038; // 0xE038 is right alt + if curr_is_ctrl && next_is_altgr { + return true; + } + } + false +} + +enum PendingMessage { + Incomplete, + Complete(T), +} +struct IdentifiedPendingMessage { + token: PendingMessageToken, + msg: PendingMessage, +} +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct PendingMessageToken(u32); + +/// While processing keyboard events, we sometimes need +/// to call `PeekMessageW` (`next_msg`). But `PeekMessageW` +/// can also call the event handler, which means that the new event +/// gets processed before finishing to process the one that came before. +/// +/// This would mean that the application receives events in the wrong order. +/// To avoid this, we keep track whether we are in the middle of processing +/// an event. Such an event is an "incomplete pending event". A +/// "complete pending event" is one that has already finished processing, but +/// hasn't been dispatched to the application because there still are incomplete +/// pending events that came before it. +/// +/// When we finish processing an event, we call `complete_pending`, +/// which returns an empty array if there are incomplete pending events, but +/// if all pending events are complete, then it returns all pending events in +/// the order they were encountered. These can then be dispatched to the application +pub struct PendingEventQueue { + pending: Mutex>>, + next_id: AtomicU32, +} +impl PendingEventQueue { + /// Add a new pending event to the "pending queue" + pub fn add_pending(&self) -> PendingMessageToken { + let token = self.next_token(); + let mut pending = self.pending.lock().unwrap(); + pending.push(IdentifiedPendingMessage { + token, + msg: PendingMessage::Incomplete, + }); + token + } + + /// Returns all finished pending events + /// + /// If the return value is non empty, it's guaranteed to contain `msg` + /// + /// See also: `add_pending` + pub fn complete_pending(&self, token: PendingMessageToken, msg: T) -> Vec { + let mut pending = self.pending.lock().unwrap(); + let mut target_is_first = false; + for (i, pending_msg) in pending.iter_mut().enumerate() { + if pending_msg.token == token { + pending_msg.msg = PendingMessage::Complete(msg); + if i == 0 { + target_is_first = true; + } + break; + } + } + if target_is_first { + // If the message that we just finished was the first one in the pending queue, + // then we can empty the queue, and dispatch all of the messages. + Self::drain_pending(&mut *pending) + } else { + Vec::new() + } + } + + pub fn complete_multi(&self, msgs: Vec) -> Vec { + let mut pending = self.pending.lock().unwrap(); + if pending.is_empty() { + return msgs; + } + pending.reserve(msgs.len()); + for msg in msgs { + pending.push(IdentifiedPendingMessage { + token: self.next_token(), + msg: PendingMessage::Complete(msg), + }); + } + Vec::new() + } + + /// Returns all finished pending events + /// + /// It's safe to call this even if the element isn't in the list anymore + /// + /// See also: `add_pending` + pub fn remove_pending(&self, token: PendingMessageToken) -> Vec { + let mut pending = self.pending.lock().unwrap(); + let mut was_first = false; + if let Some(m) = pending.first() { + if m.token == token { + was_first = true; + } + } + pending.retain(|m| m.token != token); + if was_first { + Self::drain_pending(&mut *pending) + } else { + Vec::new() + } + } + + fn drain_pending(pending: &mut Vec>) -> Vec { + pending.drain(..).map(|m| { + match m.msg { + PendingMessage::Complete(msg) => msg, + PendingMessage::Incomplete => { + panic!("Found an incomplete pending message when collecting messages. This indicates a bug in winit.") + } + } + }).collect() + } + fn next_token(&self) -> PendingMessageToken { + // It's okay for the u32 to overflow here. Yes, that could mean + // that two different messages have the same token, + // but that would only happen after having about 4 billion + // messages sitting in the pending queue. + // + // In that case, having two identical tokens is the least of your concerns. + let id = self.next_id.fetch_add(1, Relaxed); + PendingMessageToken(id) + } +} +impl Default for PendingEventQueue { + fn default() -> Self { + PendingEventQueue { + pending: Mutex::new(Vec::new()), + next_id: AtomicU32::new(0), + } + } +} + +/// WARNING: Due to using PeekMessage, the event handler +/// function may get called during this function. +/// (Re-entrance to the event handler) +/// +/// This can cause a deadlock if calling this function +/// while having a mutex locked. +/// +/// It can also cause code to get executed in a surprising order. +pub fn next_kbd_msg(hwnd: HWND) -> Option { + unsafe { + let mut next_msg = MaybeUninit::uninit(); + let peek_retval = PeekMessageW( + next_msg.as_mut_ptr(), + hwnd, + WM_KEYFIRST, + WM_KEYLAST, + PM_NOREMOVE, + ); + (peek_retval != 0).then(|| next_msg.assume_init()) + } +} + +fn get_location(scancode: ExScancode, hkl: HKL) -> KeyLocation { + const ABNT_C2: VIRTUAL_KEY = VK_ABNT_C2 as VIRTUAL_KEY; + + let extension = 0xE000; + let extended = (scancode & extension) == extension; + let vkey = unsafe { MapVirtualKeyExW(scancode as u32, MAPVK_VSC_TO_VK_EX, hkl) as VIRTUAL_KEY }; + + // Use the native VKEY and the extended flag to cover most cases + // This is taken from the `druid` GUI library, specifically + // druid-shell/src/platform/windows/keyboard.rs + match vkey { + VK_LSHIFT | VK_LCONTROL | VK_LMENU | VK_LWIN => KeyLocation::Left, + VK_RSHIFT | VK_RCONTROL | VK_RMENU | VK_RWIN => KeyLocation::Right, + VK_RETURN if extended => KeyLocation::Numpad, + VK_INSERT | VK_DELETE | VK_END | VK_DOWN | VK_NEXT | VK_LEFT | VK_CLEAR | VK_RIGHT + | VK_HOME | VK_UP | VK_PRIOR => { + if extended { + KeyLocation::Standard + } else { + KeyLocation::Numpad + } + } + VK_NUMPAD0 | VK_NUMPAD1 | VK_NUMPAD2 | VK_NUMPAD3 | VK_NUMPAD4 | VK_NUMPAD5 + | VK_NUMPAD6 | VK_NUMPAD7 | VK_NUMPAD8 | VK_NUMPAD9 | VK_DECIMAL | VK_DIVIDE + | VK_MULTIPLY | VK_SUBTRACT | VK_ADD | ABNT_C2 => KeyLocation::Numpad, + _ => KeyLocation::Standard, + } +} diff --git a/src/platform_impl/windows/keyboard_layout.rs b/src/platform_impl/windows/keyboard_layout.rs new file mode 100644 index 0000000000..bf3fa80b00 --- /dev/null +++ b/src/platform_impl/windows/keyboard_layout.rs @@ -0,0 +1,1017 @@ +use std::{ + collections::{hash_map::Entry, HashMap, HashSet}, + ffi::OsString, + os::windows::ffi::OsStringExt, + sync::Mutex, +}; + +use once_cell::sync::Lazy; +use windows_sys::Win32::{ + System::SystemServices::{LANG_JAPANESE, LANG_KOREAN}, + UI::{ + Input::KeyboardAndMouse::{ + GetKeyState, GetKeyboardLayout, MapVirtualKeyExW, ToUnicodeEx, MAPVK_VK_TO_VSC_EX, + VIRTUAL_KEY, VK_ACCEPT, VK_ADD, VK_APPS, VK_ATTN, VK_BACK, VK_BROWSER_BACK, + VK_BROWSER_FAVORITES, VK_BROWSER_FORWARD, VK_BROWSER_HOME, VK_BROWSER_REFRESH, + VK_BROWSER_SEARCH, VK_BROWSER_STOP, VK_CANCEL, VK_CAPITAL, VK_CLEAR, VK_CONTROL, + VK_CONVERT, VK_CRSEL, VK_DECIMAL, VK_DELETE, VK_DIVIDE, VK_DOWN, VK_END, VK_EREOF, + VK_ESCAPE, VK_EXECUTE, VK_EXSEL, VK_F1, VK_F10, VK_F11, VK_F12, VK_F13, VK_F14, VK_F15, + VK_F16, VK_F17, VK_F18, VK_F19, VK_F2, VK_F20, VK_F21, VK_F22, VK_F23, VK_F24, VK_F3, + VK_F4, VK_F5, VK_F6, VK_F7, VK_F8, VK_F9, VK_FINAL, VK_GAMEPAD_A, VK_GAMEPAD_B, + VK_GAMEPAD_DPAD_DOWN, VK_GAMEPAD_DPAD_LEFT, VK_GAMEPAD_DPAD_RIGHT, VK_GAMEPAD_DPAD_UP, + VK_GAMEPAD_LEFT_SHOULDER, VK_GAMEPAD_LEFT_THUMBSTICK_BUTTON, + VK_GAMEPAD_LEFT_THUMBSTICK_DOWN, VK_GAMEPAD_LEFT_THUMBSTICK_LEFT, + VK_GAMEPAD_LEFT_THUMBSTICK_RIGHT, VK_GAMEPAD_LEFT_THUMBSTICK_UP, + VK_GAMEPAD_LEFT_TRIGGER, VK_GAMEPAD_MENU, VK_GAMEPAD_RIGHT_SHOULDER, + VK_GAMEPAD_RIGHT_THUMBSTICK_BUTTON, VK_GAMEPAD_RIGHT_THUMBSTICK_DOWN, + VK_GAMEPAD_RIGHT_THUMBSTICK_LEFT, VK_GAMEPAD_RIGHT_THUMBSTICK_RIGHT, + VK_GAMEPAD_RIGHT_THUMBSTICK_UP, VK_GAMEPAD_RIGHT_TRIGGER, VK_GAMEPAD_VIEW, + VK_GAMEPAD_X, VK_GAMEPAD_Y, VK_HANGUL, VK_HANJA, VK_HELP, VK_HOME, VK_ICO_00, + VK_ICO_CLEAR, VK_ICO_HELP, VK_INSERT, VK_JUNJA, VK_KANA, VK_KANJI, VK_LAUNCH_APP1, + VK_LAUNCH_APP2, VK_LAUNCH_MAIL, VK_LAUNCH_MEDIA_SELECT, VK_LBUTTON, VK_LCONTROL, + VK_LEFT, VK_LMENU, VK_LSHIFT, VK_LWIN, VK_MBUTTON, VK_MEDIA_NEXT_TRACK, + VK_MEDIA_PLAY_PAUSE, VK_MEDIA_PREV_TRACK, VK_MEDIA_STOP, VK_MENU, VK_MODECHANGE, + VK_MULTIPLY, VK_NAVIGATION_ACCEPT, VK_NAVIGATION_CANCEL, VK_NAVIGATION_DOWN, + VK_NAVIGATION_LEFT, VK_NAVIGATION_MENU, VK_NAVIGATION_RIGHT, VK_NAVIGATION_UP, + VK_NAVIGATION_VIEW, VK_NEXT, VK_NONAME, VK_NONCONVERT, VK_NUMLOCK, VK_NUMPAD0, + VK_NUMPAD1, VK_NUMPAD2, VK_NUMPAD3, VK_NUMPAD4, VK_NUMPAD5, VK_NUMPAD6, VK_NUMPAD7, + VK_NUMPAD8, VK_NUMPAD9, VK_OEM_1, VK_OEM_102, VK_OEM_2, VK_OEM_3, VK_OEM_4, VK_OEM_5, + VK_OEM_6, VK_OEM_7, VK_OEM_8, VK_OEM_ATTN, VK_OEM_AUTO, VK_OEM_AX, VK_OEM_BACKTAB, + VK_OEM_CLEAR, VK_OEM_COMMA, VK_OEM_COPY, VK_OEM_CUSEL, VK_OEM_ENLW, VK_OEM_FINISH, + VK_OEM_FJ_LOYA, VK_OEM_FJ_MASSHOU, VK_OEM_FJ_ROYA, VK_OEM_FJ_TOUROKU, VK_OEM_JUMP, + VK_OEM_MINUS, VK_OEM_NEC_EQUAL, VK_OEM_PA1, VK_OEM_PA2, VK_OEM_PA3, VK_OEM_PERIOD, + VK_OEM_PLUS, VK_OEM_RESET, VK_OEM_WSCTRL, VK_PA1, VK_PACKET, VK_PAUSE, VK_PLAY, + VK_PRINT, VK_PRIOR, VK_PROCESSKEY, VK_RBUTTON, VK_RCONTROL, VK_RETURN, VK_RIGHT, + VK_RMENU, VK_RSHIFT, VK_RWIN, VK_SCROLL, VK_SELECT, VK_SEPARATOR, VK_SHIFT, VK_SLEEP, + VK_SNAPSHOT, VK_SPACE, VK_SUBTRACT, VK_TAB, VK_UP, VK_VOLUME_DOWN, VK_VOLUME_MUTE, + VK_VOLUME_UP, VK_XBUTTON1, VK_XBUTTON2, VK_ZOOM, + }, + TextServices::HKL, + }, +}; + +use crate::{ + keyboard::{Key, KeyCode, ModifiersState, NativeKey}, + platform::scancode::KeyCodeExtScancode, + platform_impl::{LOWORD, PRIMARYLANGID}, +}; + +pub(crate) static LAYOUT_CACHE: Lazy> = + Lazy::new(|| Mutex::new(LayoutCache::default())); + +fn key_pressed(vkey: VIRTUAL_KEY) -> bool { + unsafe { (GetKeyState(vkey as i32) & (1 << 15)) == (1 << 15) } +} + +const NUMPAD_VKEYS: [VIRTUAL_KEY; 16] = [ + VK_NUMPAD0, + VK_NUMPAD1, + VK_NUMPAD2, + VK_NUMPAD3, + VK_NUMPAD4, + VK_NUMPAD5, + VK_NUMPAD6, + VK_NUMPAD7, + VK_NUMPAD8, + VK_NUMPAD9, + VK_MULTIPLY, + VK_ADD, + VK_SEPARATOR, + VK_SUBTRACT, + VK_DECIMAL, + VK_DIVIDE, +]; + +static NUMPAD_KEYCODES: Lazy> = Lazy::new(|| { + let mut keycodes = HashSet::new(); + keycodes.insert(KeyCode::Numpad0); + keycodes.insert(KeyCode::Numpad1); + keycodes.insert(KeyCode::Numpad2); + keycodes.insert(KeyCode::Numpad3); + keycodes.insert(KeyCode::Numpad4); + keycodes.insert(KeyCode::Numpad5); + keycodes.insert(KeyCode::Numpad6); + keycodes.insert(KeyCode::Numpad7); + keycodes.insert(KeyCode::Numpad8); + keycodes.insert(KeyCode::Numpad9); + keycodes.insert(KeyCode::NumpadMultiply); + keycodes.insert(KeyCode::NumpadAdd); + keycodes.insert(KeyCode::NumpadComma); + keycodes.insert(KeyCode::NumpadSubtract); + keycodes.insert(KeyCode::NumpadDecimal); + keycodes.insert(KeyCode::NumpadDivide); + keycodes +}); + +bitflags! { + pub struct WindowsModifiers : u8 { + const SHIFT = 1 << 0; + const CONTROL = 1 << 1; + const ALT = 1 << 2; + const CAPS_LOCK = 1 << 3; + const FLAGS_END = 1 << 4; + } +} + +impl WindowsModifiers { + pub fn active_modifiers(key_state: &[u8; 256]) -> WindowsModifiers { + let shift = key_state[VK_SHIFT as usize] & 0x80 != 0; + let lshift = key_state[VK_LSHIFT as usize] & 0x80 != 0; + let rshift = key_state[VK_RSHIFT as usize] & 0x80 != 0; + + let control = key_state[VK_CONTROL as usize] & 0x80 != 0; + let lcontrol = key_state[VK_LCONTROL as usize] & 0x80 != 0; + let rcontrol = key_state[VK_RCONTROL as usize] & 0x80 != 0; + + let alt = key_state[VK_MENU as usize] & 0x80 != 0; + let lalt = key_state[VK_LMENU as usize] & 0x80 != 0; + let ralt = key_state[VK_RMENU as usize] & 0x80 != 0; + + let caps = key_state[VK_CAPITAL as usize] & 0x01 != 0; + + let mut result = WindowsModifiers::empty(); + if shift || lshift || rshift { + result.insert(WindowsModifiers::SHIFT); + } + if control || lcontrol || rcontrol { + result.insert(WindowsModifiers::CONTROL); + } + if alt || lalt || ralt { + result.insert(WindowsModifiers::ALT); + } + if caps { + result.insert(WindowsModifiers::CAPS_LOCK); + } + + result + } + + pub fn apply_to_kbd_state(self, key_state: &mut [u8; 256]) { + if self.intersects(Self::SHIFT) { + key_state[VK_SHIFT as usize] |= 0x80; + } else { + key_state[VK_SHIFT as usize] &= !0x80; + key_state[VK_LSHIFT as usize] &= !0x80; + key_state[VK_RSHIFT as usize] &= !0x80; + } + if self.intersects(Self::CONTROL) { + key_state[VK_CONTROL as usize] |= 0x80; + } else { + key_state[VK_CONTROL as usize] &= !0x80; + key_state[VK_LCONTROL as usize] &= !0x80; + key_state[VK_RCONTROL as usize] &= !0x80; + } + if self.intersects(Self::ALT) { + key_state[VK_MENU as usize] |= 0x80; + } else { + key_state[VK_MENU as usize] &= !0x80; + key_state[VK_LMENU as usize] &= !0x80; + key_state[VK_RMENU as usize] &= !0x80; + } + if self.intersects(Self::CAPS_LOCK) { + key_state[VK_CAPITAL as usize] |= 0x01; + } else { + key_state[VK_CAPITAL as usize] &= !0x01; + } + } + + /// Removes the control modifier if the alt modifier is not present. + /// This is useful because on Windows: (Control + Alt) == AltGr + /// but we don't want to interfere with the AltGr state. + pub fn remove_only_ctrl(mut self) -> WindowsModifiers { + if !self.contains(WindowsModifiers::ALT) { + self.remove(WindowsModifiers::CONTROL); + } + self + } +} + +pub(crate) struct Layout { + pub hkl: u64, + + /// Maps numpad keys from Windows virtual key to a `Key`. + /// + /// This is useful because some numpad keys generate different charcaters based on the locale. + /// For example `VK_DECIMAL` is sometimes "." and sometimes ",". Note: numpad-specific virtual + /// keys are only produced by Windows when the NumLock is active. + /// + /// Making this field separate from the `keys` field saves having to add NumLock as a modifier + /// to `WindowsModifiers`, which would double the number of items in keys. + pub numlock_on_keys: HashMap>, + /// Like `numlock_on_keys` but this will map to the key that would be produced if numlock was + /// off. The keys of this map are identical to the keys of `numlock_on_keys`. + pub numlock_off_keys: HashMap>, + + /// Maps a modifier state to group of key strings + /// We're not using `ModifiersState` here because that object cannot express caps lock, + /// but we need to handle caps lock too. + /// + /// This map shouldn't need to exist. + /// However currently this seems to be the only good way + /// of getting the label for the pressed key. Note that calling `ToUnicode` + /// just when the key is pressed/released would be enough if `ToUnicode` wouldn't + /// change the keyboard state (it clears the dead key). There is a flag to prevent + /// changing the state, but that flag requires Windows 10, version 1607 or newer) + pub keys: HashMap>>, + pub has_alt_graph: bool, +} + +impl Layout { + pub fn get_key( + &self, + mods: WindowsModifiers, + num_lock_on: bool, + vkey: VIRTUAL_KEY, + keycode: &KeyCode, + ) -> Key<'static> { + let native_code = NativeKey::Windows(vkey); + + let unknown_alt = vkey == VK_MENU; + if !unknown_alt { + // Here we try using the virtual key directly but if the virtual key doesn't distinguish + // between left and right alt, we can't report AltGr. Therefore, we only do this if the + // key is not the "unknown alt" key. + // + // The reason for using the virtual key directly is that `MapVirtualKeyExW` (used when + // building the keys map) sometimes maps virtual keys to odd scancodes that don't match + // the scancode coming from the KEYDOWN message for the same key. For example: `VK_LEFT` + // is mapped to `0x004B`, but the scancode for the left arrow is `0xE04B`. + let key_from_vkey = + vkey_to_non_char_key(vkey, native_code.clone(), self.hkl, self.has_alt_graph); + + if !matches!(key_from_vkey, Key::Unidentified(_)) { + return key_from_vkey; + } + } + if num_lock_on { + if let Some(key) = self.numlock_on_keys.get(&vkey) { + return key.clone(); + } + } else if let Some(key) = self.numlock_off_keys.get(&vkey) { + return key.clone(); + } + if let Some(keys) = self.keys.get(&mods) { + if let Some(key) = keys.get(keycode) { + return key.clone(); + } + } + Key::Unidentified(native_code) + } +} + +#[derive(Default)] +pub(crate) struct LayoutCache { + /// Maps locale identifiers (HKL) to layouts + pub layouts: HashMap, + pub strings: HashSet<&'static str>, +} + +impl LayoutCache { + /// Checks whether the current layout is already known and + /// prepares the layout if it isn't known. + /// The current layout is then returned. + pub fn get_current_layout(&mut self) -> (u64, &Layout) { + let locale_id = unsafe { GetKeyboardLayout(0) } as u64; + match self.layouts.entry(locale_id) { + Entry::Occupied(entry) => (locale_id, entry.into_mut()), + Entry::Vacant(entry) => { + let layout = Self::prepare_layout(&mut self.strings, locale_id); + (locale_id, entry.insert(layout)) + } + } + } + + pub fn get_agnostic_mods(&mut self) -> ModifiersState { + let (_, layout) = self.get_current_layout(); + let filter_out_altgr = layout.has_alt_graph && key_pressed(VK_RMENU); + let mut mods = ModifiersState::empty(); + mods.set(ModifiersState::SHIFT, key_pressed(VK_SHIFT)); + mods.set( + ModifiersState::CONTROL, + key_pressed(VK_CONTROL) && !filter_out_altgr, + ); + mods.set( + ModifiersState::ALT, + key_pressed(VK_MENU) && !filter_out_altgr, + ); + mods.set( + ModifiersState::SUPER, + key_pressed(VK_LWIN) || key_pressed(VK_RWIN), + ); + mods + } + + fn prepare_layout(strings: &mut HashSet<&'static str>, locale_id: u64) -> Layout { + let mut layout = Layout { + hkl: locale_id, + numlock_on_keys: Default::default(), + numlock_off_keys: Default::default(), + keys: Default::default(), + has_alt_graph: false, + }; + + // We initialize the keyboard state with all zeros to + // simulate a scenario when no modifier is active. + let mut key_state = [0u8; 256]; + + // `MapVirtualKeyExW` maps (non-numpad-specific) virtual keys to scancodes as if numlock + // was off. We rely on this behavior to find all virtual keys which are not numpad-specific + // but map to the numpad. + // + // src_vkey: VK ==> scancode: u16 (on the numpad) + // + // Then we convert the source virtual key into a `Key` and the scancode into a virtual key + // to get the reverse mapping. + // + // src_vkey: VK ==> scancode: u16 (on the numpad) + // || || + // \/ \/ + // map_value: Key <- map_vkey: VK + layout.numlock_off_keys.reserve(NUMPAD_KEYCODES.len()); + for vk in 0..256 { + let scancode = unsafe { MapVirtualKeyExW(vk, MAPVK_VK_TO_VSC_EX, locale_id as HKL) }; + if scancode == 0 { + continue; + } + let keycode = KeyCode::from_scancode(scancode); + if !is_numpad_specific(vk as VIRTUAL_KEY) && NUMPAD_KEYCODES.contains(&keycode) { + let native_code = NativeKey::Windows(vk as VIRTUAL_KEY); + let map_vkey = keycode_to_vkey(keycode, locale_id); + if map_vkey == 0 { + continue; + } + let map_value = + vkey_to_non_char_key(vk as VIRTUAL_KEY, native_code, locale_id, false); + if matches!(map_value, Key::Unidentified(_)) { + continue; + } + layout.numlock_off_keys.insert(map_vkey, map_value); + } + } + + layout.numlock_on_keys.reserve(NUMPAD_VKEYS.len()); + for vk in NUMPAD_VKEYS.iter() { + let vk = (*vk) as u32; + let scancode = unsafe { MapVirtualKeyExW(vk, MAPVK_VK_TO_VSC_EX, locale_id as HKL) }; + let unicode = Self::to_unicode_string(&key_state, vk, scancode, locale_id); + if let ToUnicodeResult::Str(s) = unicode { + let static_str = get_or_insert_str(strings, s); + layout + .numlock_on_keys + .insert(vk as VIRTUAL_KEY, Key::Character(static_str)); + } + } + + // Iterate through every combination of modifiers + let mods_end = WindowsModifiers::FLAGS_END.bits; + for mod_state in 0..mods_end { + let mut keys_for_this_mod = HashMap::with_capacity(256); + + let mod_state = unsafe { WindowsModifiers::from_bits_unchecked(mod_state) }; + mod_state.apply_to_kbd_state(&mut key_state); + + // Virtual key values are in the domain [0, 255]. + // This is reinforced by the fact that the keyboard state array has 256 + // elements. This array is allowed to be indexed by virtual key values + // giving the key state for the virtual key used for indexing. + for vk in 0..256 { + let scancode = + unsafe { MapVirtualKeyExW(vk, MAPVK_VK_TO_VSC_EX, locale_id as HKL) }; + if scancode == 0 { + continue; + } + + let native_code = NativeKey::Windows(vk as VIRTUAL_KEY); + let key_code = KeyCode::from_scancode(scancode); + // Let's try to get the key from just the scancode and vk + // We don't necessarily know yet if AltGraph is present on this layout so we'll + // assume it isn't. Then we'll do a second pass where we set the "AltRight" keys to + // "AltGr" in case we find out that there's an AltGraph. + let preliminary_key = + vkey_to_non_char_key(vk as VIRTUAL_KEY, native_code, locale_id, false); + match preliminary_key { + Key::Unidentified(_) => (), + _ => { + keys_for_this_mod.insert(key_code, preliminary_key); + continue; + } + } + + let unicode = Self::to_unicode_string(&key_state, vk, scancode, locale_id); + let key = match unicode { + ToUnicodeResult::Str(str) => { + let static_str = get_or_insert_str(strings, str); + Key::Character(static_str) + } + ToUnicodeResult::Dead(dead_char) => { + //println!("{:?} - {:?} produced dead {:?}", key_code, mod_state, dead_char); + Key::Dead(dead_char) + } + ToUnicodeResult::None => { + let has_alt = mod_state.contains(WindowsModifiers::ALT); + let has_ctrl = mod_state.contains(WindowsModifiers::CONTROL); + // HACK: `ToUnicodeEx` seems to fail getting the string for the numpad + // divide key, so we handle that explicitly here + if !has_alt && !has_ctrl && key_code == KeyCode::NumpadDivide { + Key::Character("/") + } else { + // Just use the unidentified key, we got earlier + preliminary_key + } + } + }; + + // Check for alt graph. + // The logic is that if a key pressed with no modifier produces + // a different `Character` from when it's pressed with CTRL+ALT then the layout + // has AltGr. + let ctrl_alt: WindowsModifiers = WindowsModifiers::CONTROL | WindowsModifiers::ALT; + let is_in_ctrl_alt = mod_state == ctrl_alt; + if !layout.has_alt_graph && is_in_ctrl_alt { + // Unwrapping here because if we are in the ctrl+alt modifier state + // then the alt modifier state must have come before. + let simple_keys = layout.keys.get(&WindowsModifiers::empty()).unwrap(); + if let Some(Key::Character(key_no_altgr)) = simple_keys.get(&key_code) { + if let Key::Character(key) = key { + layout.has_alt_graph = key != *key_no_altgr; + } + } + } + + keys_for_this_mod.insert(key_code, key); + } + layout.keys.insert(mod_state, keys_for_this_mod); + } + + // Second pass: replace right alt keys with AltGr if the layout has alt graph + if layout.has_alt_graph { + for mod_state in 0..mods_end { + let mod_state = unsafe { WindowsModifiers::from_bits_unchecked(mod_state) }; + if let Some(keys) = layout.keys.get_mut(&mod_state) { + if let Some(key) = keys.get_mut(&KeyCode::AltRight) { + *key = Key::AltGraph; + } + } + } + } + + layout + } + + fn to_unicode_string( + key_state: &[u8; 256], + vkey: u32, + scancode: u32, + locale_id: u64, + ) -> ToUnicodeResult { + unsafe { + let mut label_wide = [0u16; 8]; + let mut wide_len = ToUnicodeEx( + vkey, + scancode, + (&key_state[0]) as *const _, + (&mut label_wide[0]) as *mut _, + label_wide.len() as i32, + 0, + locale_id as HKL, + ); + if wide_len < 0 { + // If it's dead, we run `ToUnicode` again to consume the dead-key + wide_len = ToUnicodeEx( + vkey, + scancode, + (&key_state[0]) as *const _, + (&mut label_wide[0]) as *mut _, + label_wide.len() as i32, + 0, + locale_id as HKL, + ); + if wide_len > 0 { + let os_string = OsString::from_wide(&label_wide[0..wide_len as usize]); + if let Ok(label_str) = os_string.into_string() { + if let Some(ch) = label_str.chars().next() { + return ToUnicodeResult::Dead(Some(ch)); + } + } + } + return ToUnicodeResult::Dead(None); + } + if wide_len > 0 { + let os_string = OsString::from_wide(&label_wide[0..wide_len as usize]); + if let Ok(label_str) = os_string.into_string() { + return ToUnicodeResult::Str(label_str); + } + } + } + ToUnicodeResult::None + } +} + +pub fn get_or_insert_str(strings: &mut HashSet<&'static str>, string: T) -> &'static str +where + T: AsRef, + String: From, +{ + { + let str_ref = string.as_ref(); + if let Some(&existing) = strings.get(str_ref) { + return existing; + } + } + let leaked = Box::leak(Box::from(String::from(string))); + strings.insert(leaked); + leaked +} + +#[derive(Debug, Clone, Eq, PartialEq)] +enum ToUnicodeResult { + Str(String), + Dead(Option), + None, +} + +fn is_numpad_specific(vk: VIRTUAL_KEY) -> bool { + matches!( + vk, + VK_NUMPAD0 + | VK_NUMPAD1 + | VK_NUMPAD2 + | VK_NUMPAD3 + | VK_NUMPAD4 + | VK_NUMPAD5 + | VK_NUMPAD6 + | VK_NUMPAD7 + | VK_NUMPAD8 + | VK_NUMPAD9 + | VK_ADD + | VK_SUBTRACT + | VK_DIVIDE + | VK_DECIMAL + | VK_SEPARATOR + ) +} + +fn keycode_to_vkey(keycode: KeyCode, hkl: u64) -> VIRTUAL_KEY { + let primary_lang_id = PRIMARYLANGID(LOWORD(hkl as u32)); + let is_korean = primary_lang_id as u32 == LANG_KOREAN; + let is_japanese = primary_lang_id as u32 == LANG_JAPANESE; + + match keycode { + KeyCode::Backquote => 0, + KeyCode::Backslash => 0, + KeyCode::BracketLeft => 0, + KeyCode::BracketRight => 0, + KeyCode::Comma => 0, + KeyCode::Digit0 => 0, + KeyCode::Digit1 => 0, + KeyCode::Digit2 => 0, + KeyCode::Digit3 => 0, + KeyCode::Digit4 => 0, + KeyCode::Digit5 => 0, + KeyCode::Digit6 => 0, + KeyCode::Digit7 => 0, + KeyCode::Digit8 => 0, + KeyCode::Digit9 => 0, + KeyCode::Equal => 0, + KeyCode::IntlBackslash => 0, + KeyCode::IntlRo => 0, + KeyCode::IntlYen => 0, + KeyCode::KeyA => 0, + KeyCode::KeyB => 0, + KeyCode::KeyC => 0, + KeyCode::KeyD => 0, + KeyCode::KeyE => 0, + KeyCode::KeyF => 0, + KeyCode::KeyG => 0, + KeyCode::KeyH => 0, + KeyCode::KeyI => 0, + KeyCode::KeyJ => 0, + KeyCode::KeyK => 0, + KeyCode::KeyL => 0, + KeyCode::KeyM => 0, + KeyCode::KeyN => 0, + KeyCode::KeyO => 0, + KeyCode::KeyP => 0, + KeyCode::KeyQ => 0, + KeyCode::KeyR => 0, + KeyCode::KeyS => 0, + KeyCode::KeyT => 0, + KeyCode::KeyU => 0, + KeyCode::KeyV => 0, + KeyCode::KeyW => 0, + KeyCode::KeyX => 0, + KeyCode::KeyY => 0, + KeyCode::KeyZ => 0, + KeyCode::Minus => 0, + KeyCode::Period => 0, + KeyCode::Quote => 0, + KeyCode::Semicolon => 0, + KeyCode::Slash => 0, + KeyCode::AltLeft => VK_LMENU, + KeyCode::AltRight => VK_RMENU, + KeyCode::Backspace => VK_BACK, + KeyCode::CapsLock => VK_CAPITAL, + KeyCode::ContextMenu => VK_APPS, + KeyCode::ControlLeft => VK_LCONTROL, + KeyCode::ControlRight => VK_RCONTROL, + KeyCode::Enter => VK_RETURN, + KeyCode::SuperLeft => VK_LWIN, + KeyCode::SuperRight => VK_RWIN, + KeyCode::ShiftLeft => VK_RSHIFT, + KeyCode::ShiftRight => VK_LSHIFT, + KeyCode::Space => VK_SPACE, + KeyCode::Tab => VK_TAB, + KeyCode::Convert => VK_CONVERT, + KeyCode::KanaMode => VK_KANA, + KeyCode::Lang1 if is_korean => VK_HANGUL, + KeyCode::Lang1 if is_japanese => VK_KANA, + KeyCode::Lang2 if is_korean => VK_HANJA, + KeyCode::Lang2 if is_japanese => 0, + KeyCode::Lang3 if is_japanese => VK_OEM_FINISH, + KeyCode::Lang4 if is_japanese => 0, + KeyCode::Lang5 if is_japanese => 0, + KeyCode::NonConvert => VK_NONCONVERT, + KeyCode::Delete => VK_DELETE, + KeyCode::End => VK_END, + KeyCode::Help => VK_HELP, + KeyCode::Home => VK_HOME, + KeyCode::Insert => VK_INSERT, + KeyCode::PageDown => VK_NEXT, + KeyCode::PageUp => VK_PRIOR, + KeyCode::ArrowDown => VK_DOWN, + KeyCode::ArrowLeft => VK_LEFT, + KeyCode::ArrowRight => VK_RIGHT, + KeyCode::ArrowUp => VK_UP, + KeyCode::NumLock => VK_NUMLOCK, + KeyCode::Numpad0 => VK_NUMPAD0, + KeyCode::Numpad1 => VK_NUMPAD1, + KeyCode::Numpad2 => VK_NUMPAD2, + KeyCode::Numpad3 => VK_NUMPAD3, + KeyCode::Numpad4 => VK_NUMPAD4, + KeyCode::Numpad5 => VK_NUMPAD5, + KeyCode::Numpad6 => VK_NUMPAD6, + KeyCode::Numpad7 => VK_NUMPAD7, + KeyCode::Numpad8 => VK_NUMPAD8, + KeyCode::Numpad9 => VK_NUMPAD9, + KeyCode::NumpadAdd => VK_ADD, + KeyCode::NumpadBackspace => VK_BACK, + KeyCode::NumpadClear => VK_CLEAR, + KeyCode::NumpadClearEntry => 0, + KeyCode::NumpadComma => VK_SEPARATOR, + KeyCode::NumpadDecimal => VK_DECIMAL, + KeyCode::NumpadDivide => VK_DIVIDE, + KeyCode::NumpadEnter => VK_RETURN, + KeyCode::NumpadEqual => 0, + KeyCode::NumpadHash => 0, + KeyCode::NumpadMemoryAdd => 0, + KeyCode::NumpadMemoryClear => 0, + KeyCode::NumpadMemoryRecall => 0, + KeyCode::NumpadMemoryStore => 0, + KeyCode::NumpadMemorySubtract => 0, + KeyCode::NumpadMultiply => VK_MULTIPLY, + KeyCode::NumpadParenLeft => 0, + KeyCode::NumpadParenRight => 0, + KeyCode::NumpadStar => 0, + KeyCode::NumpadSubtract => VK_SUBTRACT, + KeyCode::Escape => VK_ESCAPE, + KeyCode::Fn => 0, + KeyCode::FnLock => 0, + KeyCode::PrintScreen => VK_SNAPSHOT, + KeyCode::ScrollLock => VK_SCROLL, + KeyCode::Pause => VK_PAUSE, + KeyCode::BrowserBack => VK_BROWSER_BACK, + KeyCode::BrowserFavorites => VK_BROWSER_FAVORITES, + KeyCode::BrowserForward => VK_BROWSER_FORWARD, + KeyCode::BrowserHome => VK_BROWSER_HOME, + KeyCode::BrowserRefresh => VK_BROWSER_REFRESH, + KeyCode::BrowserSearch => VK_BROWSER_SEARCH, + KeyCode::BrowserStop => VK_BROWSER_STOP, + KeyCode::Eject => 0, + KeyCode::LaunchApp1 => VK_LAUNCH_APP1, + KeyCode::LaunchApp2 => VK_LAUNCH_APP2, + KeyCode::LaunchMail => VK_LAUNCH_MAIL, + KeyCode::MediaPlayPause => VK_MEDIA_PLAY_PAUSE, + KeyCode::MediaSelect => VK_LAUNCH_MEDIA_SELECT, + KeyCode::MediaStop => VK_MEDIA_STOP, + KeyCode::MediaTrackNext => VK_MEDIA_NEXT_TRACK, + KeyCode::MediaTrackPrevious => VK_MEDIA_PREV_TRACK, + KeyCode::Power => 0, + KeyCode::Sleep => 0, + KeyCode::AudioVolumeDown => VK_VOLUME_DOWN, + KeyCode::AudioVolumeMute => VK_VOLUME_MUTE, + KeyCode::AudioVolumeUp => VK_VOLUME_UP, + KeyCode::WakeUp => 0, + KeyCode::Hyper => 0, + KeyCode::Turbo => 0, + KeyCode::Abort => 0, + KeyCode::Resume => 0, + KeyCode::Suspend => 0, + KeyCode::Again => 0, + KeyCode::Copy => 0, + KeyCode::Cut => 0, + KeyCode::Find => 0, + KeyCode::Open => 0, + KeyCode::Paste => 0, + KeyCode::Props => 0, + KeyCode::Select => VK_SELECT, + KeyCode::Undo => 0, + KeyCode::Hiragana => 0, + KeyCode::Katakana => 0, + KeyCode::F1 => VK_F1, + KeyCode::F2 => VK_F2, + KeyCode::F3 => VK_F3, + KeyCode::F4 => VK_F4, + KeyCode::F5 => VK_F5, + KeyCode::F6 => VK_F6, + KeyCode::F7 => VK_F7, + KeyCode::F8 => VK_F8, + KeyCode::F9 => VK_F9, + KeyCode::F10 => VK_F10, + KeyCode::F11 => VK_F11, + KeyCode::F12 => VK_F12, + KeyCode::F13 => VK_F13, + KeyCode::F14 => VK_F14, + KeyCode::F15 => VK_F15, + KeyCode::F16 => VK_F16, + KeyCode::F17 => VK_F17, + KeyCode::F18 => VK_F18, + KeyCode::F19 => VK_F19, + KeyCode::F20 => VK_F20, + KeyCode::F21 => VK_F21, + KeyCode::F22 => VK_F22, + KeyCode::F23 => VK_F23, + KeyCode::F24 => VK_F24, + KeyCode::F25 => 0, + KeyCode::F26 => 0, + KeyCode::F27 => 0, + KeyCode::F28 => 0, + KeyCode::F29 => 0, + KeyCode::F30 => 0, + KeyCode::F31 => 0, + KeyCode::F32 => 0, + KeyCode::F33 => 0, + KeyCode::F34 => 0, + KeyCode::F35 => 0, + KeyCode::Unidentified(_) => 0, + _ => 0, + } +} + +/// This converts virtual keys to `Key`s. Only virtual keys which can be unambiguously converted to +/// a `Key`, with only the information passed in as arguments, are converted. +/// +/// In other words: this function does not need to "prepare" the current layout in order to do +/// the conversion, but as such it cannot convert certain keys, like language-specific character keys. +/// +/// The result includes all non-character keys defined within `Key` plus characters from numpad keys. +/// For example, backspace and tab are included. +fn vkey_to_non_char_key( + vkey: VIRTUAL_KEY, + native_code: NativeKey, + hkl: u64, + has_alt_graph: bool, +) -> Key<'static> { + // List of the Web key names and their corresponding platform-native key names: + // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values + + let primary_lang_id = PRIMARYLANGID(LOWORD(hkl as u32)); + let is_korean = primary_lang_id as u32 == LANG_KOREAN; + let is_japanese = primary_lang_id as u32 == LANG_JAPANESE; + + match vkey { + VK_LBUTTON => Key::Unidentified(NativeKey::Unidentified), // Mouse + VK_RBUTTON => Key::Unidentified(NativeKey::Unidentified), // Mouse + + // I don't think this can be represented with a Key + VK_CANCEL => Key::Unidentified(native_code), + + VK_MBUTTON => Key::Unidentified(NativeKey::Unidentified), // Mouse + VK_XBUTTON1 => Key::Unidentified(NativeKey::Unidentified), // Mouse + VK_XBUTTON2 => Key::Unidentified(NativeKey::Unidentified), // Mouse + VK_BACK => Key::Backspace, + VK_TAB => Key::Tab, + VK_CLEAR => Key::Clear, + VK_RETURN => Key::Enter, + VK_SHIFT => Key::Shift, + VK_CONTROL => Key::Control, + VK_MENU => Key::Alt, + VK_PAUSE => Key::Pause, + VK_CAPITAL => Key::CapsLock, + + //VK_HANGEUL => Key::HangulMode, // Deprecated in favour of VK_HANGUL + + // VK_HANGUL and VK_KANA are defined as the same constant, therefore + // we use appropriate conditions to differentate between them + VK_HANGUL if is_korean => Key::HangulMode, + VK_KANA if is_japanese => Key::KanaMode, + + VK_JUNJA => Key::JunjaMode, + VK_FINAL => Key::FinalMode, + + // VK_HANJA and VK_KANJI are defined as the same constant, therefore + // we use appropriate conditions to differentate between them + VK_HANJA if is_korean => Key::HanjaMode, + VK_KANJI if is_japanese => Key::KanjiMode, + + VK_ESCAPE => Key::Escape, + VK_CONVERT => Key::Convert, + VK_NONCONVERT => Key::NonConvert, + VK_ACCEPT => Key::Accept, + VK_MODECHANGE => Key::ModeChange, + VK_SPACE => Key::Space, + VK_PRIOR => Key::PageUp, + VK_NEXT => Key::PageDown, + VK_END => Key::End, + VK_HOME => Key::Home, + VK_LEFT => Key::ArrowLeft, + VK_UP => Key::ArrowUp, + VK_RIGHT => Key::ArrowRight, + VK_DOWN => Key::ArrowDown, + VK_SELECT => Key::Select, + VK_PRINT => Key::Print, + VK_EXECUTE => Key::Execute, + VK_SNAPSHOT => Key::PrintScreen, + VK_INSERT => Key::Insert, + VK_DELETE => Key::Delete, + VK_HELP => Key::Help, + VK_LWIN => Key::Super, + VK_RWIN => Key::Super, + VK_APPS => Key::ContextMenu, + VK_SLEEP => Key::Standby, + + // Numpad keys produce characters + VK_NUMPAD0 => Key::Unidentified(native_code), + VK_NUMPAD1 => Key::Unidentified(native_code), + VK_NUMPAD2 => Key::Unidentified(native_code), + VK_NUMPAD3 => Key::Unidentified(native_code), + VK_NUMPAD4 => Key::Unidentified(native_code), + VK_NUMPAD5 => Key::Unidentified(native_code), + VK_NUMPAD6 => Key::Unidentified(native_code), + VK_NUMPAD7 => Key::Unidentified(native_code), + VK_NUMPAD8 => Key::Unidentified(native_code), + VK_NUMPAD9 => Key::Unidentified(native_code), + VK_MULTIPLY => Key::Unidentified(native_code), + VK_ADD => Key::Unidentified(native_code), + VK_SEPARATOR => Key::Unidentified(native_code), + VK_SUBTRACT => Key::Unidentified(native_code), + VK_DECIMAL => Key::Unidentified(native_code), + VK_DIVIDE => Key::Unidentified(native_code), + + VK_F1 => Key::F1, + VK_F2 => Key::F2, + VK_F3 => Key::F3, + VK_F4 => Key::F4, + VK_F5 => Key::F5, + VK_F6 => Key::F6, + VK_F7 => Key::F7, + VK_F8 => Key::F8, + VK_F9 => Key::F9, + VK_F10 => Key::F10, + VK_F11 => Key::F11, + VK_F12 => Key::F12, + VK_F13 => Key::F13, + VK_F14 => Key::F14, + VK_F15 => Key::F15, + VK_F16 => Key::F16, + VK_F17 => Key::F17, + VK_F18 => Key::F18, + VK_F19 => Key::F19, + VK_F20 => Key::F20, + VK_F21 => Key::F21, + VK_F22 => Key::F22, + VK_F23 => Key::F23, + VK_F24 => Key::F24, + VK_NAVIGATION_VIEW => Key::Unidentified(native_code), + VK_NAVIGATION_MENU => Key::Unidentified(native_code), + VK_NAVIGATION_UP => Key::Unidentified(native_code), + VK_NAVIGATION_DOWN => Key::Unidentified(native_code), + VK_NAVIGATION_LEFT => Key::Unidentified(native_code), + VK_NAVIGATION_RIGHT => Key::Unidentified(native_code), + VK_NAVIGATION_ACCEPT => Key::Unidentified(native_code), + VK_NAVIGATION_CANCEL => Key::Unidentified(native_code), + VK_NUMLOCK => Key::NumLock, + VK_SCROLL => Key::ScrollLock, + VK_OEM_NEC_EQUAL => Key::Unidentified(native_code), + //VK_OEM_FJ_JISHO => Key::Unidentified(native_code), // Conflicts with `VK_OEM_NEC_EQUAL` + VK_OEM_FJ_MASSHOU => Key::Unidentified(native_code), + VK_OEM_FJ_TOUROKU => Key::Unidentified(native_code), + VK_OEM_FJ_LOYA => Key::Unidentified(native_code), + VK_OEM_FJ_ROYA => Key::Unidentified(native_code), + VK_LSHIFT => Key::Shift, + VK_RSHIFT => Key::Shift, + VK_LCONTROL => Key::Control, + VK_RCONTROL => Key::Control, + VK_LMENU => Key::Alt, + VK_RMENU => { + if has_alt_graph { + Key::AltGraph + } else { + Key::Alt + } + } + VK_BROWSER_BACK => Key::BrowserBack, + VK_BROWSER_FORWARD => Key::BrowserForward, + VK_BROWSER_REFRESH => Key::BrowserRefresh, + VK_BROWSER_STOP => Key::BrowserStop, + VK_BROWSER_SEARCH => Key::BrowserSearch, + VK_BROWSER_FAVORITES => Key::BrowserFavorites, + VK_BROWSER_HOME => Key::BrowserHome, + VK_VOLUME_MUTE => Key::AudioVolumeMute, + VK_VOLUME_DOWN => Key::AudioVolumeDown, + VK_VOLUME_UP => Key::AudioVolumeUp, + VK_MEDIA_NEXT_TRACK => Key::MediaTrackNext, + VK_MEDIA_PREV_TRACK => Key::MediaTrackPrevious, + VK_MEDIA_STOP => Key::MediaStop, + VK_MEDIA_PLAY_PAUSE => Key::MediaPlayPause, + VK_LAUNCH_MAIL => Key::LaunchMail, + VK_LAUNCH_MEDIA_SELECT => Key::LaunchMediaPlayer, + VK_LAUNCH_APP1 => Key::LaunchApplication1, + VK_LAUNCH_APP2 => Key::LaunchApplication2, + + // This function only converts "non-printable" + VK_OEM_1 => Key::Unidentified(native_code), + VK_OEM_PLUS => Key::Unidentified(native_code), + VK_OEM_COMMA => Key::Unidentified(native_code), + VK_OEM_MINUS => Key::Unidentified(native_code), + VK_OEM_PERIOD => Key::Unidentified(native_code), + VK_OEM_2 => Key::Unidentified(native_code), + VK_OEM_3 => Key::Unidentified(native_code), + + VK_GAMEPAD_A => Key::Unidentified(native_code), + VK_GAMEPAD_B => Key::Unidentified(native_code), + VK_GAMEPAD_X => Key::Unidentified(native_code), + VK_GAMEPAD_Y => Key::Unidentified(native_code), + VK_GAMEPAD_RIGHT_SHOULDER => Key::Unidentified(native_code), + VK_GAMEPAD_LEFT_SHOULDER => Key::Unidentified(native_code), + VK_GAMEPAD_LEFT_TRIGGER => Key::Unidentified(native_code), + VK_GAMEPAD_RIGHT_TRIGGER => Key::Unidentified(native_code), + VK_GAMEPAD_DPAD_UP => Key::Unidentified(native_code), + VK_GAMEPAD_DPAD_DOWN => Key::Unidentified(native_code), + VK_GAMEPAD_DPAD_LEFT => Key::Unidentified(native_code), + VK_GAMEPAD_DPAD_RIGHT => Key::Unidentified(native_code), + VK_GAMEPAD_MENU => Key::Unidentified(native_code), + VK_GAMEPAD_VIEW => Key::Unidentified(native_code), + VK_GAMEPAD_LEFT_THUMBSTICK_BUTTON => Key::Unidentified(native_code), + VK_GAMEPAD_RIGHT_THUMBSTICK_BUTTON => Key::Unidentified(native_code), + VK_GAMEPAD_LEFT_THUMBSTICK_UP => Key::Unidentified(native_code), + VK_GAMEPAD_LEFT_THUMBSTICK_DOWN => Key::Unidentified(native_code), + VK_GAMEPAD_LEFT_THUMBSTICK_RIGHT => Key::Unidentified(native_code), + VK_GAMEPAD_LEFT_THUMBSTICK_LEFT => Key::Unidentified(native_code), + VK_GAMEPAD_RIGHT_THUMBSTICK_UP => Key::Unidentified(native_code), + VK_GAMEPAD_RIGHT_THUMBSTICK_DOWN => Key::Unidentified(native_code), + VK_GAMEPAD_RIGHT_THUMBSTICK_RIGHT => Key::Unidentified(native_code), + VK_GAMEPAD_RIGHT_THUMBSTICK_LEFT => Key::Unidentified(native_code), + + // This function only converts "non-printable" + VK_OEM_4 => Key::Unidentified(native_code), + VK_OEM_5 => Key::Unidentified(native_code), + VK_OEM_6 => Key::Unidentified(native_code), + VK_OEM_7 => Key::Unidentified(native_code), + VK_OEM_8 => Key::Unidentified(native_code), + VK_OEM_AX => Key::Unidentified(native_code), + VK_OEM_102 => Key::Unidentified(native_code), + + VK_ICO_HELP => Key::Unidentified(native_code), + VK_ICO_00 => Key::Unidentified(native_code), + + VK_PROCESSKEY => Key::Process, + + VK_ICO_CLEAR => Key::Unidentified(native_code), + VK_PACKET => Key::Unidentified(native_code), + VK_OEM_RESET => Key::Unidentified(native_code), + VK_OEM_JUMP => Key::Unidentified(native_code), + VK_OEM_PA1 => Key::Unidentified(native_code), + VK_OEM_PA2 => Key::Unidentified(native_code), + VK_OEM_PA3 => Key::Unidentified(native_code), + VK_OEM_WSCTRL => Key::Unidentified(native_code), + VK_OEM_CUSEL => Key::Unidentified(native_code), + + VK_OEM_ATTN => Key::Attn, + VK_OEM_FINISH => { + if is_japanese { + Key::Katakana + } else { + // This matches IE and Firefox behaviour according to + // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values + // At the time of writing, there is no `Key::Finish` variant as + // Finish is not mentionned at https://w3c.github.io/uievents-key/ + // Also see: https://github.com/pyfisch/keyboard-types/issues/9 + Key::Unidentified(native_code) + } + } + VK_OEM_COPY => Key::Copy, + VK_OEM_AUTO => Key::Hankaku, + VK_OEM_ENLW => Key::Zenkaku, + VK_OEM_BACKTAB => Key::Romaji, + VK_ATTN => Key::KanaMode, + VK_CRSEL => Key::CrSel, + VK_EXSEL => Key::ExSel, + VK_EREOF => Key::EraseEof, + VK_PLAY => Key::Play, + VK_ZOOM => Key::ZoomToggle, + VK_NONAME => Key::Unidentified(native_code), + VK_PA1 => Key::Unidentified(native_code), + VK_OEM_CLEAR => Key::Clear, + _ => Key::Unidentified(native_code), + } +} diff --git a/src/platform_impl/windows/minimal_ime.rs b/src/platform_impl/windows/minimal_ime.rs new file mode 100644 index 0000000000..71600abb88 --- /dev/null +++ b/src/platform_impl/windows/minimal_ime.rs @@ -0,0 +1,67 @@ +use std::sync::{ + atomic::{AtomicBool, Ordering::Relaxed}, + Mutex, +}; + +use winapi::{ + shared::{ + minwindef::{LPARAM, WPARAM}, + windef::HWND, + }, + um::winuser, +}; + +use crate::platform_impl::platform::{event_loop::ProcResult, keyboard::next_kbd_msg}; + +pub struct MinimalIme { + // True if we're currently receiving messages belonging to a finished IME session. + getting_ime_text: AtomicBool, + + utf16parts: Mutex>, +} +impl Default for MinimalIme { + fn default() -> Self { + MinimalIme { + getting_ime_text: AtomicBool::new(false), + utf16parts: Mutex::new(Vec::with_capacity(16)), + } + } +} +impl MinimalIme { + pub(crate) fn process_message( + &self, + hwnd: HWND, + msg_kind: u32, + wparam: WPARAM, + _lparam: LPARAM, + result: &mut ProcResult, + ) -> Option { + match msg_kind { + winuser::WM_IME_ENDCOMPOSITION => { + self.getting_ime_text.store(true, Relaxed); + } + winuser::WM_CHAR | winuser::WM_SYSCHAR => { + if self.getting_ime_text.load(Relaxed) { + *result = ProcResult::Value(0); + self.utf16parts.lock().unwrap().push(wparam as u16); + // It's important that we push the new character and release the lock + // before getting the next message + let next_msg = next_kbd_msg(hwnd); + let more_char_coming = next_msg + .map(|m| matches!(m.message, winuser::WM_CHAR | winuser::WM_SYSCHAR)) + .unwrap_or(false); + if !more_char_coming { + let mut utf16parts = self.utf16parts.lock().unwrap(); + let result = String::from_utf16(&utf16parts).ok(); + utf16parts.clear(); + self.getting_ime_text.store(false, Relaxed); + return result; + } + } + } + _ => (), + } + + None + } +} diff --git a/src/platform_impl/windows/mod.rs b/src/platform_impl/windows/mod.rs index 9c58d1c4bd..31b18de818 100644 --- a/src/platform_impl/windows/mod.rs +++ b/src/platform_impl/windows/mod.rs @@ -19,6 +19,21 @@ pub(self) use crate::platform_impl::Fullscreen; use crate::event::DeviceId as RootDeviceId; use crate::icon::Icon; +use crate::keyboard::Key; + +// --------------------------------------------- +// WINDOWS MACROS and constants +#[inline] +#[allow(non_snake_case)] +pub fn PRIMARYLANGID(lgid: u16) -> u16 { + lgid & 0x3ff +} +#[inline] +#[allow(non_snake_case)] +pub fn LOWORD(dword: u32) -> u16 { + (dword & 0xffff) as u16 +} +// --------------------------------------------- #[derive(Clone)] pub struct PlatformSpecificWindowBuilderAttributes { @@ -82,6 +97,12 @@ fn wrap_device_id(id: u32) -> RootDeviceId { pub type OsError = std::io::Error; +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct KeyEventExtra { + pub text_with_all_modifers: Option<&'static str>, + pub key_without_modifiers: Key<'static>, +} + #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct WindowId(HWND); unsafe impl Send for WindowId {} @@ -162,10 +183,11 @@ mod dark_mode; mod definitions; mod dpi; mod drop_handler; -mod event; mod event_loop; mod icon; mod ime; +mod keyboard; +mod keyboard_layout; mod monitor; mod raw_input; mod window; diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 21fa458e9d..948367a6cf 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -6,7 +6,9 @@ use raw_window_handle::{ use std::{ cell::Cell, ffi::c_void, - io, mem, panic, ptr, + io, + mem::{self, MaybeUninit}, + panic, ptr, sync::{mpsc::channel, Arc, Mutex, MutexGuard}, }; @@ -32,9 +34,9 @@ use windows_sys::Win32::{ UI::{ Input::{ KeyboardAndMouse::{ - EnableWindow, GetActiveWindow, MapVirtualKeyW, ReleaseCapture, SendInput, INPUT, - INPUT_0, INPUT_KEYBOARD, KEYBDINPUT, KEYEVENTF_EXTENDEDKEY, KEYEVENTF_KEYUP, - MAPVK_VK_TO_VSC, VK_LMENU, VK_MENU, + EnableWindow, GetActiveWindow, MapVirtualKeyW, ReleaseCapture, SendInput, + ToUnicode, INPUT, INPUT_0, INPUT_KEYBOARD, KEYBDINPUT, KEYEVENTF_EXTENDEDKEY, + KEYEVENTF_KEYUP, MAPVK_VK_TO_VSC, VIRTUAL_KEY, VK_LMENU, VK_MENU, VK_SPACE, }, Touch::{RegisterTouchWindow, TWF_WANTPALM}, }, @@ -65,6 +67,7 @@ use crate::{ event_loop::{self, EventLoopWindowTarget, DESTROY_MSG_ID}, icon::{self, IconType}, ime::ImeContext, + keyboard::KeyEventBuilder, monitor::{self, MonitorHandle}, util, window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState}, @@ -833,6 +836,26 @@ impl Window { ) }; } + + #[inline] + pub fn reset_dead_keys(&self) { + // `ToUnicode` consumes the dead-key by default, so we are constructing a fake (but valid) + // key input which we can call `ToUnicode` with. + unsafe { + let vk = VK_SPACE as VIRTUAL_KEY; + let scancode = MapVirtualKeyW(vk as u32, MAPVK_VK_TO_VSC); + let kbd_state = [0; 256]; + let mut char_buff = [MaybeUninit::uninit(); 8]; + ToUnicode( + vk as u32, + scancode, + kbd_state.as_ptr(), + char_buff[0].as_mut_ptr(), + char_buff.len() as i32, + 0, + ); + } + } } impl Drop for Window { @@ -950,6 +973,7 @@ impl<'a, T: 'static> InitData<'a, T> { event_loop::WindowData { window_state: win.window_state.clone(), event_loop_runner: self.event_loop.runner_shared.clone(), + key_event_builder: KeyEventBuilder::default(), _file_drop_handler: file_drop_handler, userdata_removed: Cell::new(false), recurse_depth: Cell::new(0), diff --git a/src/platform_impl/windows/window_state.rs b/src/platform_impl/windows/window_state.rs index 635f626c0c..1e4c17a8d2 100644 --- a/src/platform_impl/windows/window_state.rs +++ b/src/platform_impl/windows/window_state.rs @@ -1,7 +1,7 @@ use crate::{ dpi::{PhysicalPosition, PhysicalSize, Size}, - event::ModifiersState, icon::Icon, + keyboard::ModifiersState, platform_impl::platform::{event_loop, util, Fullscreen}, window::{CursorIcon, Theme, WindowAttributes}, }; @@ -42,7 +42,7 @@ pub(crate) struct WindowState { pub fullscreen: Option, pub current_theme: Theme, pub preferred_theme: Option, - pub high_surrogate: Option, + pub window_flags: WindowFlags, pub ime_state: ImeState, @@ -157,7 +157,6 @@ impl WindowState { fullscreen: None, current_theme, preferred_theme, - high_surrogate: None, window_flags: WindowFlags::empty(), ime_state: ImeState::Disabled, From 3d1d779213393dfd27d2b5dee0bfc33bc9dd01d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20R=C3=B8yset?= Date: Fri, 9 Dec 2022 09:29:12 +0100 Subject: [PATCH 04/13] Implement the new keyboard API for X11 --- Cargo.toml | 4 +- src/platform_impl/linux/common/keymap.rs | 882 ++++++++++++++++++ src/platform_impl/linux/common/mod.rs | 2 + src/platform_impl/linux/common/xkb_state.rs | 675 ++++++++++++++ src/platform_impl/linux/mod.rs | 40 +- .../linux/x11/event_processor.rs | 347 ++++--- src/platform_impl/linux/x11/mod.rs | 45 +- src/platform_impl/linux/x11/util/input.rs | 8 +- src/platform_impl/linux/x11/util/keys.rs | 16 +- src/platform_impl/linux/x11/util/mod.rs | 1 - src/platform_impl/linux/x11/window.rs | 23 +- 11 files changed, 1815 insertions(+), 228 deletions(-) create mode 100644 src/platform_impl/linux/common/keymap.rs create mode 100644 src/platform_impl/linux/common/mod.rs create mode 100644 src/platform_impl/linux/common/xkb_state.rs diff --git a/Cargo.toml b/Cargo.toml index 3efb0d71d2..141c459467 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,7 @@ rustdoc-args = ["--cfg", "docsrs"] [features] default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"] -x11 = ["x11-dl", "mio", "percent-encoding"] +x11 = ["x11-dl", "mio", "percent-encoding", "xkbcommon-dl/x11"] wayland = ["wayland-client", "wayland-backend", "wayland-protocols", "sctk", "fnv"] wayland-dlopen = ["wayland-backend/dlopen"] wayland-csd-adwaita = ["sctk-adwaita", "sctk-adwaita/ab_glyph"] @@ -110,6 +110,7 @@ features = [ [target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_os = "ios", target_os = "macos"))))'.dependencies] libc = "0.2.64" mio = { version = "0.8", features = ["os-ext"], optional = true } +memmap2 = { version = "0.2.1", optional = true } percent-encoding = { version = "2.0", optional = true } fnv = { version = "1.0.3", optional = true } sctk = { package = "smithay-client-toolkit", version = "0.17.0", optional = true } @@ -119,6 +120,7 @@ wayland-backend = { version = "0.1.0", default_features = false, features = ["cl wayland-protocols = { version = "0.30.0", features = [ "staging"], optional = true } calloop = "0.10.5" x11-dl = { version = "2.18.5", optional = true } +xkbcommon-dl = { git = "https://github.com/maroider/xkbcommon-dl", rev = "900832888ad6f11011d1369befb344a9aa8a9610" } [target.'cfg(target_os = "redox")'.dependencies] orbclient = { version = "0.3.42", default-features = false } diff --git a/src/platform_impl/linux/common/keymap.rs b/src/platform_impl/linux/common/keymap.rs new file mode 100644 index 0000000000..21228a6c38 --- /dev/null +++ b/src/platform_impl/linux/common/keymap.rs @@ -0,0 +1,882 @@ +//! Convert XKB keys to Winit keys. + +use crate::keyboard::{Key, KeyCode, KeyLocation, NativeKey, NativeKeyCode}; + +/// Map the raw X11-style keycode to the `KeyCode` enum. +/// +/// X11-style keycodes are offset by 8 from the keycodes the Linux kernel uses. +pub fn raw_keycode_to_keycode(keycode: u32) -> KeyCode { + let rawkey = keycode - 8; + // The keycode values are taken from linux/include/uapi/linux/input-event-codes.h, as + // libxkbcommon's documentation seems to suggest that the keycode values we're interested in + // are defined by the Linux kernel. If Winit programs end up being run on other Unix-likes, + // I can only hope they agree on what the keycodes mean. + // + // Some of the keycodes are likely superfluous for our purposes, and some are ones which are + // difficult to test the correctness of, or discover the purpose of. Because of this, they've + // either been commented out here, or not included at all. + match rawkey { + 0 => KeyCode::Unidentified(NativeKeyCode::Xkb(0)), + 1 => KeyCode::Escape, + 2 => KeyCode::Digit1, + 3 => KeyCode::Digit2, + 4 => KeyCode::Digit3, + 5 => KeyCode::Digit4, + 6 => KeyCode::Digit5, + 7 => KeyCode::Digit6, + 8 => KeyCode::Digit7, + 9 => KeyCode::Digit8, + 10 => KeyCode::Digit9, + 11 => KeyCode::Digit0, + 12 => KeyCode::Minus, + 13 => KeyCode::Equal, + 14 => KeyCode::Backspace, + 15 => KeyCode::Tab, + 16 => KeyCode::KeyQ, + 17 => KeyCode::KeyW, + 18 => KeyCode::KeyE, + 19 => KeyCode::KeyR, + 20 => KeyCode::KeyT, + 21 => KeyCode::KeyY, + 22 => KeyCode::KeyU, + 23 => KeyCode::KeyI, + 24 => KeyCode::KeyO, + 25 => KeyCode::KeyP, + 26 => KeyCode::BracketLeft, + 27 => KeyCode::BracketRight, + 28 => KeyCode::Enter, + 29 => KeyCode::ControlLeft, + 30 => KeyCode::KeyA, + 31 => KeyCode::KeyS, + 32 => KeyCode::KeyD, + 33 => KeyCode::KeyF, + 34 => KeyCode::KeyG, + 35 => KeyCode::KeyH, + 36 => KeyCode::KeyJ, + 37 => KeyCode::KeyK, + 38 => KeyCode::KeyL, + 39 => KeyCode::Semicolon, + 40 => KeyCode::Quote, + 41 => KeyCode::Backquote, + 42 => KeyCode::ShiftLeft, + 43 => KeyCode::Backslash, + 44 => KeyCode::KeyZ, + 45 => KeyCode::KeyX, + 46 => KeyCode::KeyC, + 47 => KeyCode::KeyV, + 48 => KeyCode::KeyB, + 49 => KeyCode::KeyN, + 50 => KeyCode::KeyM, + 51 => KeyCode::Comma, + 52 => KeyCode::Period, + 53 => KeyCode::Slash, + 54 => KeyCode::ShiftRight, + 55 => KeyCode::NumpadMultiply, + 56 => KeyCode::AltLeft, + 57 => KeyCode::Space, + 58 => KeyCode::CapsLock, + 59 => KeyCode::F1, + 60 => KeyCode::F2, + 61 => KeyCode::F3, + 62 => KeyCode::F4, + 63 => KeyCode::F5, + 64 => KeyCode::F6, + 65 => KeyCode::F7, + 66 => KeyCode::F8, + 67 => KeyCode::F9, + 68 => KeyCode::F10, + 69 => KeyCode::NumLock, + 70 => KeyCode::ScrollLock, + 71 => KeyCode::Numpad7, + 72 => KeyCode::Numpad8, + 73 => KeyCode::Numpad9, + 74 => KeyCode::NumpadSubtract, + 75 => KeyCode::Numpad4, + 76 => KeyCode::Numpad5, + 77 => KeyCode::Numpad6, + 78 => KeyCode::NumpadAdd, + 79 => KeyCode::Numpad1, + 80 => KeyCode::Numpad2, + 81 => KeyCode::Numpad3, + 82 => KeyCode::Numpad0, + 83 => KeyCode::NumpadDecimal, + 85 => KeyCode::Lang5, + 86 => KeyCode::IntlBackslash, + 87 => KeyCode::F11, + 88 => KeyCode::F12, + 89 => KeyCode::IntlRo, + 90 => KeyCode::Lang3, + 91 => KeyCode::Lang4, + 92 => KeyCode::Convert, + 93 => KeyCode::KanaMode, + 94 => KeyCode::NonConvert, + // 95 => KeyCode::KPJPCOMMA, + 96 => KeyCode::NumpadEnter, + 97 => KeyCode::ControlRight, + 98 => KeyCode::NumpadDivide, + 99 => KeyCode::PrintScreen, + 100 => KeyCode::AltRight, + // 101 => KeyCode::LINEFEED, + 102 => KeyCode::Home, + 103 => KeyCode::ArrowUp, + 104 => KeyCode::PageUp, + 105 => KeyCode::ArrowLeft, + 106 => KeyCode::ArrowRight, + 107 => KeyCode::End, + 108 => KeyCode::ArrowDown, + 109 => KeyCode::PageDown, + 110 => KeyCode::Insert, + 111 => KeyCode::Delete, + // 112 => KeyCode::MACRO, + 113 => KeyCode::AudioVolumeMute, + 114 => KeyCode::AudioVolumeDown, + 115 => KeyCode::AudioVolumeUp, + // 116 => KeyCode::POWER, + 117 => KeyCode::NumpadEqual, + // 118 => KeyCode::KPPLUSMINUS, + 119 => KeyCode::Pause, + // 120 => KeyCode::SCALE, + 121 => KeyCode::NumpadComma, + 122 => KeyCode::Lang1, + 123 => KeyCode::Lang2, + 124 => KeyCode::IntlYen, + 125 => KeyCode::SuperLeft, + 126 => KeyCode::SuperRight, + 127 => KeyCode::ContextMenu, + // 128 => KeyCode::STOP, + // 129 => KeyCode::AGAIN, + // 130 => KeyCode::PROPS, + // 131 => KeyCode::UNDO, + // 132 => KeyCode::FRONT, + // 133 => KeyCode::COPY, + // 134 => KeyCode::OPEN, + // 135 => KeyCode::PASTE, + // 136 => KeyCode::FIND, + // 137 => KeyCode::CUT, + // 138 => KeyCode::HELP, + // 139 => KeyCode::MENU, + // 140 => KeyCode::CALC, + // 141 => KeyCode::SETUP, + // 142 => KeyCode::SLEEP, + // 143 => KeyCode::WAKEUP, + // 144 => KeyCode::FILE, + // 145 => KeyCode::SENDFILE, + // 146 => KeyCode::DELETEFILE, + // 147 => KeyCode::XFER, + // 148 => KeyCode::PROG1, + // 149 => KeyCode::PROG2, + // 150 => KeyCode::WWW, + // 151 => KeyCode::MSDOS, + // 152 => KeyCode::COFFEE, + // 153 => KeyCode::ROTATE_DISPLAY, + // 154 => KeyCode::CYCLEWINDOWS, + // 155 => KeyCode::MAIL, + // 156 => KeyCode::BOOKMARKS, + // 157 => KeyCode::COMPUTER, + // 158 => KeyCode::BACK, + // 159 => KeyCode::FORWARD, + // 160 => KeyCode::CLOSECD, + // 161 => KeyCode::EJECTCD, + // 162 => KeyCode::EJECTCLOSECD, + 163 => KeyCode::MediaTrackNext, + 164 => KeyCode::MediaPlayPause, + 165 => KeyCode::MediaTrackPrevious, + 166 => KeyCode::MediaStop, + // 167 => KeyCode::RECORD, + // 168 => KeyCode::REWIND, + // 169 => KeyCode::PHONE, + // 170 => KeyCode::ISO, + // 171 => KeyCode::CONFIG, + // 172 => KeyCode::HOMEPAGE, + // 173 => KeyCode::REFRESH, + // 174 => KeyCode::EXIT, + // 175 => KeyCode::MOVE, + // 176 => KeyCode::EDIT, + // 177 => KeyCode::SCROLLUP, + // 178 => KeyCode::SCROLLDOWN, + // 179 => KeyCode::KPLEFTPAREN, + // 180 => KeyCode::KPRIGHTPAREN, + // 181 => KeyCode::NEW, + // 182 => KeyCode::REDO, + 183 => KeyCode::F13, + 184 => KeyCode::F14, + 185 => KeyCode::F15, + 186 => KeyCode::F16, + 187 => KeyCode::F17, + 188 => KeyCode::F18, + 189 => KeyCode::F19, + 190 => KeyCode::F20, + 191 => KeyCode::F21, + 192 => KeyCode::F22, + 193 => KeyCode::F23, + 194 => KeyCode::F24, + // 200 => KeyCode::PLAYCD, + // 201 => KeyCode::PAUSECD, + // 202 => KeyCode::PROG3, + // 203 => KeyCode::PROG4, + // 204 => KeyCode::DASHBOARD, + // 205 => KeyCode::SUSPEND, + // 206 => KeyCode::CLOSE, + // 207 => KeyCode::PLAY, + // 208 => KeyCode::FASTFORWARD, + // 209 => KeyCode::BASSBOOST, + // 210 => KeyCode::PRINT, + // 211 => KeyCode::HP, + // 212 => KeyCode::CAMERA, + // 213 => KeyCode::SOUND, + // 214 => KeyCode::QUESTION, + // 215 => KeyCode::EMAIL, + // 216 => KeyCode::CHAT, + // 217 => KeyCode::SEARCH, + // 218 => KeyCode::CONNECT, + // 219 => KeyCode::FINANCE, + // 220 => KeyCode::SPORT, + // 221 => KeyCode::SHOP, + // 222 => KeyCode::ALTERASE, + // 223 => KeyCode::CANCEL, + // 224 => KeyCode::BRIGHTNESSDOW, + // 225 => KeyCode::BRIGHTNESSU, + // 226 => KeyCode::MEDIA, + // 227 => KeyCode::SWITCHVIDEOMODE, + // 228 => KeyCode::KBDILLUMTOGGLE, + // 229 => KeyCode::KBDILLUMDOWN, + // 230 => KeyCode::KBDILLUMUP, + // 231 => KeyCode::SEND, + // 232 => KeyCode::REPLY, + // 233 => KeyCode::FORWARDMAIL, + // 234 => KeyCode::SAVE, + // 235 => KeyCode::DOCUMENTS, + // 236 => KeyCode::BATTERY, + // 237 => KeyCode::BLUETOOTH, + // 238 => KeyCode::WLAN, + // 239 => KeyCode::UWB, + 240 => KeyCode::Unidentified(NativeKeyCode::Unidentified), + // 241 => KeyCode::VIDEO_NEXT, + // 242 => KeyCode::VIDEO_PREV, + // 243 => KeyCode::BRIGHTNESS_CYCLE, + // 244 => KeyCode::BRIGHTNESS_AUTO, + // 245 => KeyCode::DISPLAY_OFF, + // 246 => KeyCode::WWAN, + // 247 => KeyCode::RFKILL, + // 248 => KeyCode::KEY_MICMUTE, + _ => KeyCode::Unidentified(NativeKeyCode::Xkb(rawkey)), + } +} + +pub fn keycode_to_raw(keycode: KeyCode) -> Option { + match keycode { + KeyCode::Unidentified(NativeKeyCode::Unidentified) => Some(240), + KeyCode::Unidentified(NativeKeyCode::Xkb(raw)) => Some(raw), + KeyCode::Escape => Some(1), + KeyCode::Digit1 => Some(2), + KeyCode::Digit2 => Some(3), + KeyCode::Digit3 => Some(4), + KeyCode::Digit4 => Some(5), + KeyCode::Digit5 => Some(6), + KeyCode::Digit6 => Some(7), + KeyCode::Digit7 => Some(8), + KeyCode::Digit8 => Some(9), + KeyCode::Digit9 => Some(10), + KeyCode::Digit0 => Some(11), + KeyCode::Minus => Some(12), + KeyCode::Equal => Some(13), + KeyCode::Backspace => Some(14), + KeyCode::Tab => Some(15), + KeyCode::KeyQ => Some(16), + KeyCode::KeyW => Some(17), + KeyCode::KeyE => Some(18), + KeyCode::KeyR => Some(19), + KeyCode::KeyT => Some(20), + KeyCode::KeyY => Some(21), + KeyCode::KeyU => Some(22), + KeyCode::KeyI => Some(23), + KeyCode::KeyO => Some(24), + KeyCode::KeyP => Some(25), + KeyCode::BracketLeft => Some(26), + KeyCode::BracketRight => Some(27), + KeyCode::Enter => Some(28), + KeyCode::ControlLeft => Some(29), + KeyCode::KeyA => Some(30), + KeyCode::KeyS => Some(31), + KeyCode::KeyD => Some(32), + KeyCode::KeyF => Some(33), + KeyCode::KeyG => Some(34), + KeyCode::KeyH => Some(35), + KeyCode::KeyJ => Some(36), + KeyCode::KeyK => Some(37), + KeyCode::KeyL => Some(38), + KeyCode::Semicolon => Some(39), + KeyCode::Quote => Some(40), + KeyCode::Backquote => Some(41), + KeyCode::ShiftLeft => Some(42), + KeyCode::Backslash => Some(43), + KeyCode::KeyZ => Some(44), + KeyCode::KeyX => Some(45), + KeyCode::KeyC => Some(46), + KeyCode::KeyV => Some(47), + KeyCode::KeyB => Some(48), + KeyCode::KeyN => Some(49), + KeyCode::KeyM => Some(50), + KeyCode::Comma => Some(51), + KeyCode::Period => Some(52), + KeyCode::Slash => Some(53), + KeyCode::ShiftRight => Some(54), + KeyCode::NumpadMultiply => Some(55), + KeyCode::AltLeft => Some(56), + KeyCode::Space => Some(57), + KeyCode::CapsLock => Some(58), + KeyCode::F1 => Some(59), + KeyCode::F2 => Some(60), + KeyCode::F3 => Some(61), + KeyCode::F4 => Some(62), + KeyCode::F5 => Some(63), + KeyCode::F6 => Some(64), + KeyCode::F7 => Some(65), + KeyCode::F8 => Some(66), + KeyCode::F9 => Some(67), + KeyCode::F10 => Some(68), + KeyCode::NumLock => Some(69), + KeyCode::ScrollLock => Some(70), + KeyCode::Numpad7 => Some(71), + KeyCode::Numpad8 => Some(72), + KeyCode::Numpad9 => Some(73), + KeyCode::NumpadSubtract => Some(74), + KeyCode::Numpad4 => Some(75), + KeyCode::Numpad5 => Some(76), + KeyCode::Numpad6 => Some(77), + KeyCode::NumpadAdd => Some(78), + KeyCode::Numpad1 => Some(79), + KeyCode::Numpad2 => Some(80), + KeyCode::Numpad3 => Some(81), + KeyCode::Numpad0 => Some(82), + KeyCode::NumpadDecimal => Some(83), + KeyCode::Lang5 => Some(85), + KeyCode::IntlBackslash => Some(86), + KeyCode::F11 => Some(87), + KeyCode::F12 => Some(88), + KeyCode::IntlRo => Some(89), + KeyCode::Lang3 => Some(90), + KeyCode::Lang4 => Some(91), + KeyCode::Convert => Some(92), + KeyCode::KanaMode => Some(93), + KeyCode::NonConvert => Some(94), + KeyCode::NumpadEnter => Some(96), + KeyCode::ControlRight => Some(97), + KeyCode::NumpadDivide => Some(98), + KeyCode::PrintScreen => Some(99), + KeyCode::AltRight => Some(100), + KeyCode::Home => Some(102), + KeyCode::ArrowUp => Some(103), + KeyCode::PageUp => Some(104), + KeyCode::ArrowLeft => Some(105), + KeyCode::ArrowRight => Some(106), + KeyCode::End => Some(107), + KeyCode::ArrowDown => Some(108), + KeyCode::PageDown => Some(109), + KeyCode::Insert => Some(110), + KeyCode::Delete => Some(111), + KeyCode::AudioVolumeMute => Some(113), + KeyCode::AudioVolumeDown => Some(114), + KeyCode::AudioVolumeUp => Some(115), + KeyCode::NumpadEqual => Some(117), + KeyCode::Pause => Some(119), + KeyCode::NumpadComma => Some(121), + KeyCode::Lang1 => Some(122), + KeyCode::Lang2 => Some(123), + KeyCode::IntlYen => Some(124), + KeyCode::SuperLeft => Some(125), + KeyCode::SuperRight => Some(126), + KeyCode::ContextMenu => Some(127), + KeyCode::MediaTrackNext => Some(163), + KeyCode::MediaPlayPause => Some(164), + KeyCode::MediaTrackPrevious => Some(165), + KeyCode::MediaStop => Some(166), + KeyCode::F13 => Some(183), + KeyCode::F14 => Some(184), + KeyCode::F15 => Some(185), + KeyCode::F16 => Some(186), + KeyCode::F17 => Some(187), + KeyCode::F18 => Some(188), + KeyCode::F19 => Some(189), + KeyCode::F20 => Some(190), + KeyCode::F21 => Some(191), + KeyCode::F22 => Some(192), + KeyCode::F23 => Some(193), + KeyCode::F24 => Some(194), + _ => None, + } + .map(|raw| raw + 8) +} + +pub fn keysym_to_key(keysym: u32) -> Key<'static> { + use xkbcommon_dl::keysyms; + match keysym { + // TTY function keys + keysyms::XKB_KEY_BackSpace => Key::Backspace, + keysyms::XKB_KEY_Tab => Key::Tab, + // keysyms::XKB_KEY_Linefeed => Key::Linefeed, + keysyms::XKB_KEY_Clear => Key::Clear, + keysyms::XKB_KEY_Return => Key::Enter, + keysyms::XKB_KEY_Pause => Key::Pause, + keysyms::XKB_KEY_Scroll_Lock => Key::ScrollLock, + keysyms::XKB_KEY_Sys_Req => Key::PrintScreen, + keysyms::XKB_KEY_Escape => Key::Escape, + keysyms::XKB_KEY_Delete => Key::Delete, + + // IME keys + keysyms::XKB_KEY_Multi_key => Key::Compose, + keysyms::XKB_KEY_Codeinput => Key::CodeInput, + keysyms::XKB_KEY_SingleCandidate => Key::SingleCandidate, + keysyms::XKB_KEY_MultipleCandidate => Key::AllCandidates, + keysyms::XKB_KEY_PreviousCandidate => Key::PreviousCandidate, + + // Japanese keys + keysyms::XKB_KEY_Kanji => Key::KanjiMode, + keysyms::XKB_KEY_Muhenkan => Key::NonConvert, + keysyms::XKB_KEY_Henkan_Mode => Key::Convert, + keysyms::XKB_KEY_Romaji => Key::Romaji, + keysyms::XKB_KEY_Hiragana => Key::Hiragana, + keysyms::XKB_KEY_Hiragana_Katakana => Key::HiraganaKatakana, + keysyms::XKB_KEY_Zenkaku => Key::Zenkaku, + keysyms::XKB_KEY_Hankaku => Key::Hankaku, + keysyms::XKB_KEY_Zenkaku_Hankaku => Key::ZenkakuHankaku, + // keysyms::XKB_KEY_Touroku => Key::Touroku, + // keysyms::XKB_KEY_Massyo => Key::Massyo, + keysyms::XKB_KEY_Kana_Lock => Key::KanaMode, + keysyms::XKB_KEY_Kana_Shift => Key::KanaMode, + keysyms::XKB_KEY_Eisu_Shift => Key::Alphanumeric, + keysyms::XKB_KEY_Eisu_toggle => Key::Alphanumeric, + // NOTE: The next three items are aliases for values we've already mapped. + // keysyms::XKB_KEY_Kanji_Bangou => Key::CodeInput, + // keysyms::XKB_KEY_Zen_Koho => Key::AllCandidates, + // keysyms::XKB_KEY_Mae_Koho => Key::PreviousCandidate, + + // Cursor control & motion + keysyms::XKB_KEY_Home => Key::Home, + keysyms::XKB_KEY_Left => Key::ArrowLeft, + keysyms::XKB_KEY_Up => Key::ArrowUp, + keysyms::XKB_KEY_Right => Key::ArrowRight, + keysyms::XKB_KEY_Down => Key::ArrowDown, + // keysyms::XKB_KEY_Prior => Key::PageUp, + keysyms::XKB_KEY_Page_Up => Key::PageUp, + // keysyms::XKB_KEY_Next => Key::PageDown, + keysyms::XKB_KEY_Page_Down => Key::PageDown, + keysyms::XKB_KEY_End => Key::End, + // keysyms::XKB_KEY_Begin => Key::Begin, + + // Misc. functions + keysyms::XKB_KEY_Select => Key::Select, + keysyms::XKB_KEY_Print => Key::PrintScreen, + keysyms::XKB_KEY_Execute => Key::Execute, + keysyms::XKB_KEY_Insert => Key::Insert, + keysyms::XKB_KEY_Undo => Key::Undo, + keysyms::XKB_KEY_Redo => Key::Redo, + keysyms::XKB_KEY_Menu => Key::ContextMenu, + keysyms::XKB_KEY_Find => Key::Find, + keysyms::XKB_KEY_Cancel => Key::Cancel, + keysyms::XKB_KEY_Help => Key::Help, + keysyms::XKB_KEY_Break => Key::Pause, + keysyms::XKB_KEY_Mode_switch => Key::ModeChange, + // keysyms::XKB_KEY_script_switch => Key::ModeChange, + keysyms::XKB_KEY_Num_Lock => Key::NumLock, + + // Keypad keys + // keysyms::XKB_KEY_KP_Space => Key::Character(" "), + keysyms::XKB_KEY_KP_Tab => Key::Tab, + keysyms::XKB_KEY_KP_Enter => Key::Enter, + keysyms::XKB_KEY_KP_F1 => Key::F1, + keysyms::XKB_KEY_KP_F2 => Key::F2, + keysyms::XKB_KEY_KP_F3 => Key::F3, + keysyms::XKB_KEY_KP_F4 => Key::F4, + keysyms::XKB_KEY_KP_Home => Key::Home, + keysyms::XKB_KEY_KP_Left => Key::ArrowLeft, + keysyms::XKB_KEY_KP_Up => Key::ArrowLeft, + keysyms::XKB_KEY_KP_Right => Key::ArrowRight, + keysyms::XKB_KEY_KP_Down => Key::ArrowDown, + // keysyms::XKB_KEY_KP_Prior => Key::PageUp, + keysyms::XKB_KEY_KP_Page_Up => Key::PageUp, + // keysyms::XKB_KEY_KP_Next => Key::PageDown, + keysyms::XKB_KEY_KP_Page_Down => Key::PageDown, + keysyms::XKB_KEY_KP_End => Key::End, + // This is the key labeled "5" on the numpad when NumLock is off. + // keysyms::XKB_KEY_KP_Begin => Key::Begin, + keysyms::XKB_KEY_KP_Insert => Key::Insert, + keysyms::XKB_KEY_KP_Delete => Key::Delete, + // keysyms::XKB_KEY_KP_Equal => Key::Equal, + // keysyms::XKB_KEY_KP_Multiply => Key::Multiply, + // keysyms::XKB_KEY_KP_Add => Key::Add, + // keysyms::XKB_KEY_KP_Separator => Key::Separator, + // keysyms::XKB_KEY_KP_Subtract => Key::Subtract, + // keysyms::XKB_KEY_KP_Decimal => Key::Decimal, + // keysyms::XKB_KEY_KP_Divide => Key::Divide, + + // keysyms::XKB_KEY_KP_0 => Key::Character("0"), + // keysyms::XKB_KEY_KP_1 => Key::Character("1"), + // keysyms::XKB_KEY_KP_2 => Key::Character("2"), + // keysyms::XKB_KEY_KP_3 => Key::Character("3"), + // keysyms::XKB_KEY_KP_4 => Key::Character("4"), + // keysyms::XKB_KEY_KP_5 => Key::Character("5"), + // keysyms::XKB_KEY_KP_6 => Key::Character("6"), + // keysyms::XKB_KEY_KP_7 => Key::Character("7"), + // keysyms::XKB_KEY_KP_8 => Key::Character("8"), + // keysyms::XKB_KEY_KP_9 => Key::Character("9"), + + // Function keys + keysyms::XKB_KEY_F1 => Key::F1, + keysyms::XKB_KEY_F2 => Key::F2, + keysyms::XKB_KEY_F3 => Key::F3, + keysyms::XKB_KEY_F4 => Key::F4, + keysyms::XKB_KEY_F5 => Key::F5, + keysyms::XKB_KEY_F6 => Key::F6, + keysyms::XKB_KEY_F7 => Key::F7, + keysyms::XKB_KEY_F8 => Key::F8, + keysyms::XKB_KEY_F9 => Key::F9, + keysyms::XKB_KEY_F10 => Key::F10, + keysyms::XKB_KEY_F11 => Key::F11, + keysyms::XKB_KEY_F12 => Key::F12, + keysyms::XKB_KEY_F13 => Key::F13, + keysyms::XKB_KEY_F14 => Key::F14, + keysyms::XKB_KEY_F15 => Key::F15, + keysyms::XKB_KEY_F16 => Key::F16, + keysyms::XKB_KEY_F17 => Key::F17, + keysyms::XKB_KEY_F18 => Key::F18, + keysyms::XKB_KEY_F19 => Key::F19, + keysyms::XKB_KEY_F20 => Key::F20, + keysyms::XKB_KEY_F21 => Key::F21, + keysyms::XKB_KEY_F22 => Key::F22, + keysyms::XKB_KEY_F23 => Key::F23, + keysyms::XKB_KEY_F24 => Key::F24, + keysyms::XKB_KEY_F25 => Key::F25, + keysyms::XKB_KEY_F26 => Key::F26, + keysyms::XKB_KEY_F27 => Key::F27, + keysyms::XKB_KEY_F28 => Key::F28, + keysyms::XKB_KEY_F29 => Key::F29, + keysyms::XKB_KEY_F30 => Key::F30, + keysyms::XKB_KEY_F31 => Key::F31, + keysyms::XKB_KEY_F32 => Key::F32, + keysyms::XKB_KEY_F33 => Key::F33, + keysyms::XKB_KEY_F34 => Key::F34, + keysyms::XKB_KEY_F35 => Key::F35, + + // Modifiers + keysyms::XKB_KEY_Shift_L => Key::Shift, + keysyms::XKB_KEY_Shift_R => Key::Shift, + keysyms::XKB_KEY_Control_L => Key::Control, + keysyms::XKB_KEY_Control_R => Key::Control, + keysyms::XKB_KEY_Caps_Lock => Key::CapsLock, + // keysyms::XKB_KEY_Shift_Lock => Key::ShiftLock, + + // keysyms::XKB_KEY_Meta_L => Key::Meta, + // keysyms::XKB_KEY_Meta_R => Key::Meta, + keysyms::XKB_KEY_Alt_L => Key::Alt, + keysyms::XKB_KEY_Alt_R => Key::Alt, + keysyms::XKB_KEY_Super_L => Key::Super, + keysyms::XKB_KEY_Super_R => Key::Super, + keysyms::XKB_KEY_Hyper_L => Key::Hyper, + keysyms::XKB_KEY_Hyper_R => Key::Hyper, + + // XKB function and modifier keys + // keysyms::XKB_KEY_ISO_Lock => Key::IsoLock, + // keysyms::XKB_KEY_ISO_Level2_Latch => Key::IsoLevel2Latch, + keysyms::XKB_KEY_ISO_Level3_Shift => Key::AltGraph, + keysyms::XKB_KEY_ISO_Level3_Latch => Key::AltGraph, + keysyms::XKB_KEY_ISO_Level3_Lock => Key::AltGraph, + // keysyms::XKB_KEY_ISO_Level5_Shift => Key::IsoLevel5Shift, + // keysyms::XKB_KEY_ISO_Level5_Latch => Key::IsoLevel5Latch, + // keysyms::XKB_KEY_ISO_Level5_Lock => Key::IsoLevel5Lock, + // keysyms::XKB_KEY_ISO_Group_Shift => Key::IsoGroupShift, + // keysyms::XKB_KEY_ISO_Group_Latch => Key::IsoGroupLatch, + // keysyms::XKB_KEY_ISO_Group_Lock => Key::IsoGroupLock, + keysyms::XKB_KEY_ISO_Next_Group => Key::GroupNext, + // keysyms::XKB_KEY_ISO_Next_Group_Lock => Key::GroupNextLock, + keysyms::XKB_KEY_ISO_Prev_Group => Key::GroupPrevious, + // keysyms::XKB_KEY_ISO_Prev_Group_Lock => Key::GroupPreviousLock, + keysyms::XKB_KEY_ISO_First_Group => Key::GroupFirst, + // keysyms::XKB_KEY_ISO_First_Group_Lock => Key::GroupFirstLock, + keysyms::XKB_KEY_ISO_Last_Group => Key::GroupLast, + // keysyms::XKB_KEY_ISO_Last_Group_Lock => Key::GroupLastLock, + // + keysyms::XKB_KEY_ISO_Left_Tab => Key::Tab, + // keysyms::XKB_KEY_ISO_Move_Line_Up => Key::IsoMoveLineUp, + // keysyms::XKB_KEY_ISO_Move_Line_Down => Key::IsoMoveLineDown, + // keysyms::XKB_KEY_ISO_Partial_Line_Up => Key::IsoPartialLineUp, + // keysyms::XKB_KEY_ISO_Partial_Line_Down => Key::IsoPartialLineDown, + // keysyms::XKB_KEY_ISO_Partial_Space_Left => Key::IsoPartialSpaceLeft, + // keysyms::XKB_KEY_ISO_Partial_Space_Right => Key::IsoPartialSpaceRight, + // keysyms::XKB_KEY_ISO_Set_Margin_Left => Key::IsoSetMarginLeft, + // keysyms::XKB_KEY_ISO_Set_Margin_Right => Key::IsoSetMarginRight, + // keysyms::XKB_KEY_ISO_Release_Margin_Left => Key::IsoReleaseMarginLeft, + // keysyms::XKB_KEY_ISO_Release_Margin_Right => Key::IsoReleaseMarginRight, + // keysyms::XKB_KEY_ISO_Release_Both_Margins => Key::IsoReleaseBothMargins, + // keysyms::XKB_KEY_ISO_Fast_Cursor_Left => Key::IsoFastCursorLeft, + // keysyms::XKB_KEY_ISO_Fast_Cursor_Right => Key::IsoFastCursorRight, + // keysyms::XKB_KEY_ISO_Fast_Cursor_Up => Key::IsoFastCursorUp, + // keysyms::XKB_KEY_ISO_Fast_Cursor_Down => Key::IsoFastCursorDown, + // keysyms::XKB_KEY_ISO_Continuous_Underline => Key::IsoContinuousUnderline, + // keysyms::XKB_KEY_ISO_Discontinuous_Underline => Key::IsoDiscontinuousUnderline, + // keysyms::XKB_KEY_ISO_Emphasize => Key::IsoEmphasize, + // keysyms::XKB_KEY_ISO_Center_Object => Key::IsoCenterObject, + keysyms::XKB_KEY_ISO_Enter => Key::Enter, + + // XKB_KEY_dead_grave..XKB_KEY_dead_currency + + // XKB_KEY_dead_lowline..XKB_KEY_dead_longsolidusoverlay + + // XKB_KEY_dead_a..XKB_KEY_dead_capital_schwa + + // XKB_KEY_dead_greek + + // XKB_KEY_First_Virtual_Screen..XKB_KEY_Terminate_Server + + // XKB_KEY_AccessX_Enable..XKB_KEY_AudibleBell_Enable + + // XKB_KEY_Pointer_Left..XKB_KEY_Pointer_Drag5 + + // XKB_KEY_Pointer_EnableKeys..XKB_KEY_Pointer_DfltBtnPrev + + // XKB_KEY_ch..XKB_KEY_C_H + + // 3270 terminal keys + // keysyms::XKB_KEY_3270_Duplicate => Key::Duplicate, + // keysyms::XKB_KEY_3270_FieldMark => Key::FieldMark, + // keysyms::XKB_KEY_3270_Right2 => Key::Right2, + // keysyms::XKB_KEY_3270_Left2 => Key::Left2, + // keysyms::XKB_KEY_3270_BackTab => Key::BackTab, + keysyms::XKB_KEY_3270_EraseEOF => Key::EraseEof, + // keysyms::XKB_KEY_3270_EraseInput => Key::EraseInput, + // keysyms::XKB_KEY_3270_Reset => Key::Reset, + // keysyms::XKB_KEY_3270_Quit => Key::Quit, + // keysyms::XKB_KEY_3270_PA1 => Key::Pa1, + // keysyms::XKB_KEY_3270_PA2 => Key::Pa2, + // keysyms::XKB_KEY_3270_PA3 => Key::Pa3, + // keysyms::XKB_KEY_3270_Test => Key::Test, + keysyms::XKB_KEY_3270_Attn => Key::Attn, + // keysyms::XKB_KEY_3270_CursorBlink => Key::CursorBlink, + // keysyms::XKB_KEY_3270_AltCursor => Key::AltCursor, + // keysyms::XKB_KEY_3270_KeyClick => Key::KeyClick, + // keysyms::XKB_KEY_3270_Jump => Key::Jump, + // keysyms::XKB_KEY_3270_Ident => Key::Ident, + // keysyms::XKB_KEY_3270_Rule => Key::Rule, + // keysyms::XKB_KEY_3270_Copy => Key::Copy, + keysyms::XKB_KEY_3270_Play => Key::Play, + // keysyms::XKB_KEY_3270_Setup => Key::Setup, + // keysyms::XKB_KEY_3270_Record => Key::Record, + // keysyms::XKB_KEY_3270_ChangeScreen => Key::ChangeScreen, + // keysyms::XKB_KEY_3270_DeleteWord => Key::DeleteWord, + keysyms::XKB_KEY_3270_ExSelect => Key::ExSel, + keysyms::XKB_KEY_3270_CursorSelect => Key::CrSel, + keysyms::XKB_KEY_3270_PrintScreen => Key::PrintScreen, + keysyms::XKB_KEY_3270_Enter => Key::Enter, + + keysyms::XKB_KEY_space => Key::Space, + // XKB_KEY_exclam..XKB_KEY_Sinh_kunddaliya + + // XFree86 + // keysyms::XKB_KEY_XF86ModeLock => Key::ModeLock, + + // XFree86 - Backlight controls + keysyms::XKB_KEY_XF86MonBrightnessUp => Key::BrightnessUp, + keysyms::XKB_KEY_XF86MonBrightnessDown => Key::BrightnessDown, + // keysyms::XKB_KEY_XF86KbdLightOnOff => Key::LightOnOff, + // keysyms::XKB_KEY_XF86KbdBrightnessUp => Key::KeyboardBrightnessUp, + // keysyms::XKB_KEY_XF86KbdBrightnessDown => Key::KeyboardBrightnessDown, + + // XFree86 - "Internet" + keysyms::XKB_KEY_XF86Standby => Key::Standby, + keysyms::XKB_KEY_XF86AudioLowerVolume => Key::AudioVolumeDown, + keysyms::XKB_KEY_XF86AudioRaiseVolume => Key::AudioVolumeUp, + keysyms::XKB_KEY_XF86AudioPlay => Key::MediaPlay, + keysyms::XKB_KEY_XF86AudioStop => Key::MediaStop, + keysyms::XKB_KEY_XF86AudioPrev => Key::MediaTrackPrevious, + keysyms::XKB_KEY_XF86AudioNext => Key::MediaTrackNext, + keysyms::XKB_KEY_XF86HomePage => Key::BrowserHome, + keysyms::XKB_KEY_XF86Mail => Key::LaunchMail, + // keysyms::XKB_KEY_XF86Start => Key::Start, + keysyms::XKB_KEY_XF86Search => Key::BrowserSearch, + keysyms::XKB_KEY_XF86AudioRecord => Key::MediaRecord, + + // XFree86 - PDA + keysyms::XKB_KEY_XF86Calculator => Key::LaunchApplication2, + // keysyms::XKB_KEY_XF86Memo => Key::Memo, + // keysyms::XKB_KEY_XF86ToDoList => Key::ToDoList, + keysyms::XKB_KEY_XF86Calendar => Key::LaunchCalendar, + keysyms::XKB_KEY_XF86PowerDown => Key::Power, + // keysyms::XKB_KEY_XF86ContrastAdjust => Key::AdjustContrast, + // keysyms::XKB_KEY_XF86RockerUp => Key::RockerUp, + // keysyms::XKB_KEY_XF86RockerDown => Key::RockerDown, + // keysyms::XKB_KEY_XF86RockerEnter => Key::RockerEnter, + + // XFree86 - More "Internet" + keysyms::XKB_KEY_XF86Back => Key::BrowserBack, + keysyms::XKB_KEY_XF86Forward => Key::BrowserForward, + // keysyms::XKB_KEY_XF86Stop => Key::Stop, + keysyms::XKB_KEY_XF86Refresh => Key::BrowserRefresh, + keysyms::XKB_KEY_XF86PowerOff => Key::Power, + keysyms::XKB_KEY_XF86WakeUp => Key::WakeUp, + keysyms::XKB_KEY_XF86Eject => Key::Eject, + keysyms::XKB_KEY_XF86ScreenSaver => Key::LaunchScreenSaver, + keysyms::XKB_KEY_XF86WWW => Key::LaunchWebBrowser, + keysyms::XKB_KEY_XF86Sleep => Key::Standby, + keysyms::XKB_KEY_XF86Favorites => Key::BrowserFavorites, + keysyms::XKB_KEY_XF86AudioPause => Key::MediaPause, + // keysyms::XKB_KEY_XF86AudioMedia => Key::AudioMedia, + keysyms::XKB_KEY_XF86MyComputer => Key::LaunchApplication1, + // keysyms::XKB_KEY_XF86VendorHome => Key::VendorHome, + // keysyms::XKB_KEY_XF86LightBulb => Key::LightBulb, + // keysyms::XKB_KEY_XF86Shop => Key::BrowserShop, + // keysyms::XKB_KEY_XF86History => Key::BrowserHistory, + // keysyms::XKB_KEY_XF86OpenURL => Key::OpenUrl, + // keysyms::XKB_KEY_XF86AddFavorite => Key::AddFavorite, + // keysyms::XKB_KEY_XF86HotLinks => Key::HotLinks, + // keysyms::XKB_KEY_XF86BrightnessAdjust => Key::BrightnessAdjust, + // keysyms::XKB_KEY_XF86Finance => Key::BrowserFinance, + // keysyms::XKB_KEY_XF86Community => Key::BrowserCommunity, + keysyms::XKB_KEY_XF86AudioRewind => Key::MediaRewind, + // keysyms::XKB_KEY_XF86BackForward => Key::???, + // XKB_KEY_XF86Launch0..XKB_KEY_XF86LaunchF + + // XKB_KEY_XF86ApplicationLeft..XKB_KEY_XF86CD + keysyms::XKB_KEY_XF86Calculater => Key::LaunchApplication2, // Nice typo, libxkbcommon :) + // XKB_KEY_XF86Clear + keysyms::XKB_KEY_XF86Close => Key::Close, + keysyms::XKB_KEY_XF86Copy => Key::Copy, + keysyms::XKB_KEY_XF86Cut => Key::Cut, + // XKB_KEY_XF86Display..XKB_KEY_XF86Documents + keysyms::XKB_KEY_XF86Excel => Key::LaunchSpreadsheet, + // XKB_KEY_XF86Explorer..XKB_KEY_XF86iTouch + keysyms::XKB_KEY_XF86LogOff => Key::LogOff, + // XKB_KEY_XF86Market..XKB_KEY_XF86MenuPB + keysyms::XKB_KEY_XF86MySites => Key::BrowserFavorites, + keysyms::XKB_KEY_XF86New => Key::New, + // XKB_KEY_XF86News..XKB_KEY_XF86OfficeHome + keysyms::XKB_KEY_XF86Open => Key::Open, + // XKB_KEY_XF86Option + keysyms::XKB_KEY_XF86Paste => Key::Paste, + keysyms::XKB_KEY_XF86Phone => Key::LaunchPhone, + // XKB_KEY_XF86Q + keysyms::XKB_KEY_XF86Reply => Key::MailReply, + keysyms::XKB_KEY_XF86Reload => Key::BrowserRefresh, + // XKB_KEY_XF86RotateWindows..XKB_KEY_XF86RotationKB + keysyms::XKB_KEY_XF86Save => Key::Save, + // XKB_KEY_XF86ScrollUp..XKB_KEY_XF86ScrollClick + keysyms::XKB_KEY_XF86Send => Key::MailSend, + keysyms::XKB_KEY_XF86Spell => Key::SpellCheck, + keysyms::XKB_KEY_XF86SplitScreen => Key::SplitScreenToggle, + // XKB_KEY_XF86Support..XKB_KEY_XF86User2KB + keysyms::XKB_KEY_XF86Video => Key::LaunchMediaPlayer, + // XKB_KEY_XF86WheelButton + keysyms::XKB_KEY_XF86Word => Key::LaunchWordProcessor, + // XKB_KEY_XF86Xfer + keysyms::XKB_KEY_XF86ZoomIn => Key::ZoomIn, + keysyms::XKB_KEY_XF86ZoomOut => Key::ZoomOut, + + // XKB_KEY_XF86Away..XKB_KEY_XF86Messenger + keysyms::XKB_KEY_XF86WebCam => Key::LaunchWebCam, + keysyms::XKB_KEY_XF86MailForward => Key::MailForward, + // XKB_KEY_XF86Pictures + keysyms::XKB_KEY_XF86Music => Key::LaunchMusicPlayer, + + // XKB_KEY_XF86Battery..XKB_KEY_XF86UWB + // + keysyms::XKB_KEY_XF86AudioForward => Key::MediaFastForward, + // XKB_KEY_XF86AudioRepeat + keysyms::XKB_KEY_XF86AudioRandomPlay => Key::RandomToggle, + keysyms::XKB_KEY_XF86Subtitle => Key::Subtitle, + keysyms::XKB_KEY_XF86AudioCycleTrack => Key::MediaAudioTrack, + // XKB_KEY_XF86CycleAngle..XKB_KEY_XF86Blue + // + keysyms::XKB_KEY_XF86Suspend => Key::Standby, + keysyms::XKB_KEY_XF86Hibernate => Key::Hibernate, + // XKB_KEY_XF86TouchpadToggle..XKB_KEY_XF86TouchpadOff + // + keysyms::XKB_KEY_XF86AudioMute => Key::AudioVolumeMute, + + // XKB_KEY_XF86Switch_VT_1..XKB_KEY_XF86Switch_VT_12 + + // XKB_KEY_XF86Ungrab..XKB_KEY_XF86ClearGrab + keysyms::XKB_KEY_XF86Next_VMode => Key::VideoModeNext, + // keysyms::XKB_KEY_XF86Prev_VMode => Key::VideoModePrevious, + // XKB_KEY_XF86LogWindowTree..XKB_KEY_XF86LogGrabInfo + + // XKB_KEY_SunFA_Grave..XKB_KEY_SunFA_Cedilla + + // keysyms::XKB_KEY_SunF36 => Key::F36 | Key::F11, + // keysyms::XKB_KEY_SunF37 => Key::F37 | Key::F12, + + // keysyms::XKB_KEY_SunSys_Req => Key::PrintScreen, + // The next couple of xkb (until XKB_KEY_SunStop) are already handled. + // XKB_KEY_SunPrint_Screen..XKB_KEY_SunPageDown + + // XKB_KEY_SunUndo..XKB_KEY_SunFront + keysyms::XKB_KEY_SunCopy => Key::Copy, + keysyms::XKB_KEY_SunOpen => Key::Open, + keysyms::XKB_KEY_SunPaste => Key::Paste, + keysyms::XKB_KEY_SunCut => Key::Cut, + + // XKB_KEY_SunPowerSwitch + keysyms::XKB_KEY_SunAudioLowerVolume => Key::AudioVolumeDown, + keysyms::XKB_KEY_SunAudioMute => Key::AudioVolumeMute, + keysyms::XKB_KEY_SunAudioRaiseVolume => Key::AudioVolumeUp, + // XKB_KEY_SunVideoDegauss + keysyms::XKB_KEY_SunVideoLowerBrightness => Key::BrightnessDown, + keysyms::XKB_KEY_SunVideoRaiseBrightness => Key::BrightnessUp, + // XKB_KEY_SunPowerSwitchShift + // + 0 => Key::Unidentified(NativeKey::Unidentified), + _ => Key::Unidentified(NativeKey::Xkb(keysym)), + } +} + +pub fn keysym_location(keysym: u32) -> KeyLocation { + use xkbcommon_dl::keysyms; + match keysym { + keysyms::XKB_KEY_Shift_L + | keysyms::XKB_KEY_Control_L + | keysyms::XKB_KEY_Meta_L + | keysyms::XKB_KEY_Alt_L + | keysyms::XKB_KEY_Super_L + | keysyms::XKB_KEY_Hyper_L => KeyLocation::Left, + keysyms::XKB_KEY_Shift_R + | keysyms::XKB_KEY_Control_R + | keysyms::XKB_KEY_Meta_R + | keysyms::XKB_KEY_Alt_R + | keysyms::XKB_KEY_Super_R + | keysyms::XKB_KEY_Hyper_R => KeyLocation::Right, + keysyms::XKB_KEY_KP_0 + | keysyms::XKB_KEY_KP_1 + | keysyms::XKB_KEY_KP_2 + | keysyms::XKB_KEY_KP_3 + | keysyms::XKB_KEY_KP_4 + | keysyms::XKB_KEY_KP_5 + | keysyms::XKB_KEY_KP_6 + | keysyms::XKB_KEY_KP_7 + | keysyms::XKB_KEY_KP_8 + | keysyms::XKB_KEY_KP_9 + | keysyms::XKB_KEY_KP_Space + | keysyms::XKB_KEY_KP_Tab + | keysyms::XKB_KEY_KP_Enter + | keysyms::XKB_KEY_KP_F1 + | keysyms::XKB_KEY_KP_F2 + | keysyms::XKB_KEY_KP_F3 + | keysyms::XKB_KEY_KP_F4 + | keysyms::XKB_KEY_KP_Home + | keysyms::XKB_KEY_KP_Left + | keysyms::XKB_KEY_KP_Up + | keysyms::XKB_KEY_KP_Right + | keysyms::XKB_KEY_KP_Down + | keysyms::XKB_KEY_KP_Page_Up + | keysyms::XKB_KEY_KP_Page_Down + | keysyms::XKB_KEY_KP_End + | keysyms::XKB_KEY_KP_Begin + | keysyms::XKB_KEY_KP_Insert + | keysyms::XKB_KEY_KP_Delete + | keysyms::XKB_KEY_KP_Equal + | keysyms::XKB_KEY_KP_Multiply + | keysyms::XKB_KEY_KP_Add + | keysyms::XKB_KEY_KP_Separator + | keysyms::XKB_KEY_KP_Subtract + | keysyms::XKB_KEY_KP_Decimal + | keysyms::XKB_KEY_KP_Divide => KeyLocation::Numpad, + _ => KeyLocation::Standard, + } +} diff --git a/src/platform_impl/linux/common/mod.rs b/src/platform_impl/linux/common/mod.rs new file mode 100644 index 0000000000..fa23919676 --- /dev/null +++ b/src/platform_impl/linux/common/mod.rs @@ -0,0 +1,2 @@ +pub mod keymap; +pub mod xkb_state; diff --git a/src/platform_impl/linux/common/xkb_state.rs b/src/platform_impl/linux/common/xkb_state.rs new file mode 100644 index 0000000000..7387d0aef6 --- /dev/null +++ b/src/platform_impl/linux/common/xkb_state.rs @@ -0,0 +1,675 @@ +use std::convert::TryInto; +use std::env; +use std::ffi::CString; +use std::os::raw::c_char; +use std::os::unix::ffi::OsStringExt; +use std::ptr; +use std::sync::atomic::{AtomicBool, Ordering}; + +#[cfg(feature = "wayland")] +use std::fs::File; + +#[cfg(feature = "wayland")] +use memmap2::MmapOptions; + +#[cfg(feature = "x11")] +use x11_dl::xlib_xcb::xcb_connection_t; +#[cfg(feature = "x11")] +use xkbcommon_dl::x11::XKBCOMMON_X11_HANDLE as XKBXH; + +use xkbcommon_dl::{ + self as ffi, xkb_state_component, XKBCOMMON_COMPOSE_HANDLE as XKBCH, XKBCOMMON_HANDLE as XKBH, +}; + +use crate::{ + event::ElementState, + keyboard::{Key, KeyCode, KeyLocation}, +}; + +// TODO: Wire this up without using a static `AtomicBool`. +static RESET_DEAD_KEYS: AtomicBool = AtomicBool::new(false); + +#[inline] +pub(crate) fn reset_dead_keys() { + RESET_DEAD_KEYS.store(true, Ordering::SeqCst); +} + +pub(crate) struct KbState { + #[cfg(feature = "x11")] + xcb_connection: *mut xcb_connection_t, + xkb_context: *mut ffi::xkb_context, + xkb_keymap: *mut ffi::xkb_keymap, + xkb_state: *mut ffi::xkb_state, + xkb_compose_table: *mut ffi::xkb_compose_table, + xkb_compose_state: *mut ffi::xkb_compose_state, + xkb_compose_state_2: *mut ffi::xkb_compose_state, + mods_state: ModifiersState, + #[cfg(feature = "wayland")] + locked: bool, + #[cfg(feature = "x11")] + pub(crate) core_keyboard_id: i32, + scratch_buffer: Vec, +} + +/// Represents the current state of the keyboard modifiers +/// +/// Each field of this struct represents a modifier and is `true` if this modifier is active. +/// +/// For some modifiers, this means that the key is currently pressed, others are toggled +/// (like caps lock). +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] +pub struct ModifiersState { + /// The "control" key + pub ctrl: bool, + /// The "alt" key + pub alt: bool, + /// The "shift" key + pub shift: bool, + /// The "Caps lock" key + pub caps_lock: bool, + /// The "logo" key + /// + /// Also known as the "windows" key on most keyboards + pub logo: bool, + /// The "Num lock" key + pub num_lock: bool, +} + +impl ModifiersState { + fn new() -> Self { + Self::default() + } + + fn update_with(&mut self, state: *mut ffi::xkb_state) { + let mod_name_is_active = |mod_name: &[u8]| unsafe { + (XKBH.xkb_state_mod_name_is_active)( + state, + mod_name.as_ptr() as *const c_char, + xkb_state_component::XKB_STATE_MODS_EFFECTIVE, + ) > 0 + }; + self.ctrl = mod_name_is_active(ffi::XKB_MOD_NAME_CTRL); + self.alt = mod_name_is_active(ffi::XKB_MOD_NAME_ALT); + self.shift = mod_name_is_active(ffi::XKB_MOD_NAME_SHIFT); + self.caps_lock = mod_name_is_active(ffi::XKB_MOD_NAME_CAPS); + self.logo = mod_name_is_active(ffi::XKB_MOD_NAME_LOGO); + self.num_lock = mod_name_is_active(ffi::XKB_MOD_NAME_NUM); + } +} + +impl From for crate::keyboard::ModifiersState { + fn from(mods: ModifiersState) -> crate::keyboard::ModifiersState { + let mut to_mods = crate::keyboard::ModifiersState::empty(); + to_mods.set(crate::keyboard::ModifiersState::SHIFT, mods.shift); + to_mods.set(crate::keyboard::ModifiersState::CONTROL, mods.ctrl); + to_mods.set(crate::keyboard::ModifiersState::ALT, mods.alt); + to_mods.set(crate::keyboard::ModifiersState::SUPER, mods.logo); + to_mods + } +} + +impl KbState { + pub(crate) fn update_modifiers( + &mut self, + mods_depressed: u32, + mods_latched: u32, + mods_locked: u32, + depressed_group: u32, + latched_group: u32, + locked_group: u32, + ) { + if !self.ready() { + return; + } + let mask = unsafe { + (XKBH.xkb_state_update_mask)( + self.xkb_state, + mods_depressed, + mods_latched, + mods_locked, + depressed_group, + latched_group, + locked_group, + ) + }; + if mask.contains(xkb_state_component::XKB_STATE_MODS_EFFECTIVE) { + // effective value of mods have changed, we need to update our state + self.mods_state.update_with(self.xkb_state); + } + } + + pub(crate) fn get_one_sym_raw(&mut self, keycode: u32) -> u32 { + if !self.ready() { + return 0; + } + unsafe { (XKBH.xkb_state_key_get_one_sym)(self.xkb_state, keycode) } + } + + pub(crate) fn get_utf8_raw(&mut self, keycode: u32) -> Option<&'static str> { + if !self.ready() { + return None; + } + let xkb_state = self.xkb_state; + self._get_utf8({ + |ptr, len| unsafe { (XKBH.xkb_state_key_get_utf8)(xkb_state, keycode, ptr, len) } + }) + } + + fn compose_feed_normal(&mut self, keysym: u32) -> Option { + self.compose_feed(self.xkb_compose_state, keysym) + } + + fn compose_feed_2(&mut self, keysym: u32) -> Option { + self.compose_feed(self.xkb_compose_state_2, keysym) + } + + fn compose_feed( + &mut self, + xkb_compose_state: *mut ffi::xkb_compose_state, + keysym: u32, + ) -> Option { + if !self.ready() || self.xkb_compose_state.is_null() { + return None; + } + if RESET_DEAD_KEYS.swap(false, Ordering::SeqCst) { + unsafe { self.init_compose() }; + } + Some(unsafe { (XKBCH.xkb_compose_state_feed)(xkb_compose_state, keysym) }) + } + + fn compose_status_normal(&mut self) -> Option { + self.compose_status(self.xkb_compose_state) + } + + #[allow(dead_code)] + fn compose_status_2(&mut self) -> Option { + self.compose_status(self.xkb_compose_state_2) + } + + fn compose_status( + &mut self, + xkb_compose_state: *mut ffi::xkb_compose_state, + ) -> Option { + if !self.ready() || xkb_compose_state.is_null() { + return None; + } + Some(unsafe { (XKBCH.xkb_compose_state_get_status)(xkb_compose_state) }) + } + + fn compose_get_utf8_normal(&mut self) -> Option<&'static str> { + self.compose_get_utf8(self.xkb_compose_state) + } + + fn compose_get_utf8_2(&mut self) -> Option<&'static str> { + self.compose_get_utf8(self.xkb_compose_state_2) + } + + fn compose_get_utf8( + &mut self, + xkb_compose_state: *mut ffi::xkb_compose_state, + ) -> Option<&'static str> { + if !self.ready() || xkb_compose_state.is_null() { + return None; + } + self._get_utf8(|ptr, len| unsafe { + (XKBCH.xkb_compose_state_get_utf8)(xkb_compose_state, ptr, len) + }) + } + + fn _get_utf8(&mut self, mut f: F) -> Option<&'static str> + where + F: FnMut(*mut i8, usize) -> i32, + { + let size = f(ptr::null_mut(), 0); + if size == 0 { + return None; + } + let size = usize::try_from(size).unwrap(); + self.scratch_buffer.clear(); + // The allocated buffer must include space for the null-terminator + self.scratch_buffer.reserve(size + 1); + unsafe { + let written = f( + self.scratch_buffer.as_mut_ptr().cast(), + self.scratch_buffer.capacity(), + ); + if usize::try_from(written).unwrap() != size { + // This will likely never happen + return None; + } + self.scratch_buffer.set_len(size); + }; + Some(byte_slice_to_cached_string(&self.scratch_buffer)) + } + + pub(crate) fn new() -> Result { + if ffi::XKBCOMMON_OPTION.as_ref().is_none() { + return Err(Error::XKBNotFound); + } + + let context = + unsafe { (XKBH.xkb_context_new)(ffi::xkb_context_flags::XKB_CONTEXT_NO_FLAGS) }; + if context.is_null() { + return Err(Error::XKBNotFound); + } + + let mut me = Self { + #[cfg(feature = "x11")] + xcb_connection: ptr::null_mut(), + xkb_context: context, + xkb_keymap: ptr::null_mut(), + xkb_state: ptr::null_mut(), + xkb_compose_table: ptr::null_mut(), + xkb_compose_state: ptr::null_mut(), + xkb_compose_state_2: ptr::null_mut(), + mods_state: ModifiersState::new(), + #[cfg(feature = "wayland")] + locked: false, + #[cfg(feature = "x11")] + core_keyboard_id: 0, + scratch_buffer: Vec::new(), + }; + + unsafe { me.init_compose() }; + + Ok(me) + } +} + +impl KbState { + #[cfg(feature = "x11")] + pub(crate) fn from_x11_xkb(connection: *mut xcb_connection_t) -> Result { + let mut me = Self::new()?; + me.xcb_connection = connection; + + let result = unsafe { + (XKBXH.xkb_x11_setup_xkb_extension)( + connection, + 1, + 2, + xkbcommon_dl::x11::xkb_x11_setup_xkb_extension_flags::XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS, + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + ) + }; + assert_eq!(result, 1, "Failed to initialize libxkbcommon"); + + unsafe { me.init_with_x11_keymap() }; + + Ok(me) + } + + unsafe fn init_compose(&mut self) { + let locale = env::var_os("LC_ALL") + .and_then(|v| if v.is_empty() { None } else { Some(v) }) + .or_else(|| env::var_os("LC_CTYPE")) + .and_then(|v| if v.is_empty() { None } else { Some(v) }) + .or_else(|| env::var_os("LANG")) + .and_then(|v| if v.is_empty() { None } else { Some(v) }) + .unwrap_or_else(|| "C".into()); + let locale = CString::new(locale.into_vec()).unwrap(); + + let compose_table = (XKBCH.xkb_compose_table_new_from_locale)( + self.xkb_context, + locale.as_ptr(), + ffi::xkb_compose_compile_flags::XKB_COMPOSE_COMPILE_NO_FLAGS, + ); + + if compose_table.is_null() { + // init of compose table failed, continue without compose + return; + } + + let compose_state = (XKBCH.xkb_compose_state_new)( + compose_table, + ffi::xkb_compose_state_flags::XKB_COMPOSE_STATE_NO_FLAGS, + ); + + if compose_state.is_null() { + // init of compose state failed, continue without compose + (XKBCH.xkb_compose_table_unref)(compose_table); + return; + } + + let compose_state_2 = (XKBCH.xkb_compose_state_new)( + compose_table, + ffi::xkb_compose_state_flags::XKB_COMPOSE_STATE_NO_FLAGS, + ); + + if compose_state_2.is_null() { + // init of compose state failed, continue without compose + (XKBCH.xkb_compose_table_unref)(compose_table); + (XKBCH.xkb_compose_state_unref)(compose_state); + return; + } + + self.xkb_compose_table = compose_table; + self.xkb_compose_state = compose_state; + self.xkb_compose_state_2 = compose_state_2; + } + + unsafe fn post_init(&mut self, state: *mut ffi::xkb_state, keymap: *mut ffi::xkb_keymap) { + self.xkb_keymap = keymap; + self.xkb_state = state; + self.mods_state.update_with(state); + } + + unsafe fn de_init(&mut self) { + (XKBH.xkb_state_unref)(self.xkb_state); + self.xkb_state = ptr::null_mut(); + (XKBH.xkb_keymap_unref)(self.xkb_keymap); + self.xkb_keymap = ptr::null_mut(); + } + + #[cfg(feature = "x11")] + pub(crate) unsafe fn init_with_x11_keymap(&mut self) { + if !self.xkb_keymap.is_null() { + self.de_init(); + } + + // TODO: Support keyboards other than the "virtual core keyboard device". + self.core_keyboard_id = (XKBXH.xkb_x11_get_core_keyboard_device_id)(self.xcb_connection); + let keymap = (XKBXH.xkb_x11_keymap_new_from_device)( + self.xkb_context, + self.xcb_connection, + self.core_keyboard_id, + xkbcommon_dl::xkb_keymap_compile_flags::XKB_KEYMAP_COMPILE_NO_FLAGS, + ); + assert_ne!(keymap, ptr::null_mut()); + + let state = (XKBXH.xkb_x11_state_new_from_device)( + keymap, + self.xcb_connection, + self.core_keyboard_id, + ); + self.post_init(state, keymap); + } + + #[cfg(feature = "wayland")] + pub(crate) unsafe fn init_with_fd(&mut self, fd: File, size: usize) { + if !self.xkb_keymap.is_null() { + self.de_init(); + } + + let map = MmapOptions::new().len(size).map(&fd).unwrap(); + + let keymap = (XKBH.xkb_keymap_new_from_string)( + self.xkb_context, + map.as_ptr() as *const _, + ffi::xkb_keymap_format::XKB_KEYMAP_FORMAT_TEXT_V1, + ffi::xkb_keymap_compile_flags::XKB_KEYMAP_COMPILE_NO_FLAGS, + ); + + if keymap.is_null() { + panic!("Received invalid keymap from compositor."); + } + + let state = (XKBH.xkb_state_new)(keymap); + self.post_init(state, keymap); + } +} + +impl KbState { + #[cfg(feature = "wayland")] + pub(crate) unsafe fn key_repeats(&mut self, keycode: ffi::xkb_keycode_t) -> bool { + (XKBH.xkb_keymap_key_repeats)(self.xkb_keymap, keycode) == 1 + } + + #[inline] + pub(crate) fn ready(&self) -> bool { + !self.xkb_state.is_null() + } + + #[inline] + #[cfg(feature = "wayland")] + pub(crate) fn locked(&self) -> bool { + self.locked + } + + #[inline] + pub(crate) fn mods_state(&self) -> ModifiersState { + self.mods_state + } +} + +impl Drop for KbState { + fn drop(&mut self) { + unsafe { + if !self.xkb_compose_state.is_null() { + (XKBCH.xkb_compose_state_unref)(self.xkb_compose_state); + } + if !self.xkb_compose_state_2.is_null() { + (XKBCH.xkb_compose_state_unref)(self.xkb_compose_state_2); + } + if !self.xkb_compose_table.is_null() { + (XKBCH.xkb_compose_table_unref)(self.xkb_compose_table); + } + if !self.xkb_state.is_null() { + (XKBH.xkb_state_unref)(self.xkb_state); + } + if !self.xkb_keymap.is_null() { + (XKBH.xkb_keymap_unref)(self.xkb_keymap); + } + (XKBH.xkb_context_unref)(self.xkb_context); + } + } +} + +#[derive(Debug)] +pub enum Error { + /// libxkbcommon is not available + XKBNotFound, +} + +impl KbState { + pub fn process_key_event(&mut self, keycode: u32, state: ElementState) -> KeyEventResults<'_> { + KeyEventResults::new(self, keycode, state == ElementState::Pressed) + } + + #[cfg(feature = "wayland")] + pub fn process_key_repeat_event(&mut self, keycode: u32) -> KeyEventResults<'_> { + KeyEventResults::new(self, keycode, false) + } + + fn keysym_to_utf8_raw(&mut self, keysym: u32) -> Option<&'static str> { + self.scratch_buffer.clear(); + self.scratch_buffer.reserve(8); + loop { + let bytes_written = unsafe { + (XKBH.xkb_keysym_to_utf8)( + keysym, + self.scratch_buffer.as_mut_ptr().cast(), + self.scratch_buffer.capacity(), + ) + }; + if bytes_written == 0 { + return None; + } else if bytes_written == -1 { + self.scratch_buffer.reserve(8); + } else { + unsafe { + self.scratch_buffer + .set_len(bytes_written.try_into().unwrap()) + }; + break; + } + } + + // Remove the null-terminator + self.scratch_buffer.pop(); + Some(byte_slice_to_cached_string(&self.scratch_buffer)) + } +} + +#[derive(Copy, Clone, Debug)] +enum XkbCompose { + Accepted(ffi::xkb_compose_status), + Ignored, + Uninitialized, +} + +pub(crate) struct KeyEventResults<'a> { + state: &'a mut KbState, + keycode: u32, + keysym: u32, + compose: Option, +} + +impl<'a> KeyEventResults<'a> { + fn new(state: &'a mut KbState, keycode: u32, compose: bool) -> Self { + let keysym = state.get_one_sym_raw(keycode); + + let compose = if compose { + Some(match state.compose_feed_normal(keysym) { + Some(ffi::xkb_compose_feed_result::XKB_COMPOSE_FEED_ACCEPTED) => { + // Unwrapping is safe here, as `compose_feed` returns `None` when composition is uninitialized. + XkbCompose::Accepted(state.compose_status_normal().unwrap()) + } + Some(ffi::xkb_compose_feed_result::XKB_COMPOSE_FEED_IGNORED) => XkbCompose::Ignored, + None => XkbCompose::Uninitialized, + }) + } else { + None + }; + + KeyEventResults { + state, + keycode, + keysym, + compose, + } + } + + pub fn keycode(&mut self) -> KeyCode { + super::keymap::raw_keycode_to_keycode(self.keycode) + } + + pub fn key(&mut self) -> (Key<'static>, KeyLocation) { + self.keysym_to_key(self.keysym) + .unwrap_or_else(|(key, location)| match self.compose { + Some(XkbCompose::Accepted(ffi::xkb_compose_status::XKB_COMPOSE_COMPOSING)) => { + // When pressing a dead key twice, the non-combining variant of that character will be + // produced. Since this function only concerns itself with a single keypress, we simulate + // this double press here by feeding the keysym to the compose state twice. + self.state.compose_feed_2(self.keysym); + match self.state.compose_feed_2(self.keysym) { + Some(ffi::xkb_compose_feed_result::XKB_COMPOSE_FEED_ACCEPTED) => ( + // Extracting only a single `char` here *should* be fine, assuming that no dead + // key's non-combining variant ever occupies more than one `char`. + Key::Dead( + self.state + .compose_get_utf8_2() + .map(|s| s.chars().next().unwrap()), + ), + location, + ), + _ => (key, location), + } + } + _ => ( + self.composed_text() + .unwrap_or_else(|_| self.state.keysym_to_utf8_raw(self.keysym)) + .map(Key::Character) + .unwrap_or(key), + location, + ), + }) + } + + pub fn key_without_modifiers(&mut self) -> (Key<'static>, KeyLocation) { + // This will become a pointer to an array which libxkbcommon owns, so we don't need to deallocate it. + let mut keysyms = ptr::null(); + let keysym_count = unsafe { + (XKBH.xkb_keymap_key_get_syms_by_level)( + self.state.xkb_keymap, + self.keycode, + 0, + 0, + &mut keysyms, + ) + }; + let keysym = if keysym_count == 1 { + unsafe { *keysyms } + } else { + 0 + }; + self.keysym_to_key(keysym) + .unwrap_or_else(|(key, location)| { + ( + self.state + .keysym_to_utf8_raw(keysym) + .map(Key::Character) + .unwrap_or(key), + location, + ) + }) + } + + fn keysym_to_key( + &mut self, + keysym: u32, + ) -> Result<(Key<'static>, KeyLocation), (Key<'static>, KeyLocation)> { + let location = super::keymap::keysym_location(keysym); + let key = super::keymap::keysym_to_key(keysym); + if matches!(key, Key::Unidentified(_)) { + Err((key, location)) + } else { + Ok((key, location)) + } + } + + pub fn text(&mut self) -> Option<&'static str> { + self.composed_text() + .unwrap_or_else(|_| self.state.keysym_to_utf8_raw(self.keysym)) + } + + pub fn text_with_all_modifiers(&mut self) -> Option<&'static str> { + // The current behaviour makes it so composing a character overrides attempts to input a + // control character with the `Ctrl` key. We can potentially add a configuration option + // if someone specifically wants the oppsite behaviour. + self.composed_text() + .unwrap_or_else(|_| self.state.get_utf8_raw(self.keycode)) + } + + fn composed_text(&mut self) -> Result, ()> { + if let Some(compose) = &self.compose { + match compose { + XkbCompose::Accepted(status) => match status { + ffi::xkb_compose_status::XKB_COMPOSE_COMPOSED => { + Ok(self.state.compose_get_utf8_normal()) + } + ffi::xkb_compose_status::XKB_COMPOSE_NOTHING => Err(()), + _ => Ok(None), + }, + XkbCompose::Ignored | XkbCompose::Uninitialized => Err(()), + } + } else { + Err(()) + } + } +} + +fn byte_slice_to_cached_string(bytes: &[u8]) -> &'static str { + use std::cell::RefCell; + use std::collections::HashSet; + + thread_local! { + static STRING_CACHE: RefCell> = RefCell::new(HashSet::new()); + } + + let string = std::str::from_utf8(bytes).unwrap(); + + STRING_CACHE.with(|cache| { + let mut cache = cache.borrow_mut(); + if let Some(string) = cache.get(string) { + *string + } else { + // borrowck couldn't quite figure out this one on its own + let string: &'static str = Box::leak(String::from(string).into_boxed_str()); + cache.insert(string); + string + } + }) +} diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index 0a90d085ee..95b01b2be1 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -28,11 +28,13 @@ use crate::platform::x11::XlibErrorHook; use crate::{ dpi::{PhysicalPosition, PhysicalSize, Position, Size}, error::{ExternalError, NotSupportedError, OsError as RootOsError}, - event::Event, + event::{Event, KeyEvent}, event_loop::{ ControlFlow, DeviceEventFilter, EventLoopClosed, EventLoopWindowTarget as RootELW, }, icon::Icon, + keyboard::{Key, KeyCode}, + platform::{modifier_supplement::KeyEventExtModifierSupplement, scancode::KeyCodeExtScancode}, window::{ CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType, WindowAttributes, WindowButtons, WindowLevel, @@ -47,6 +49,9 @@ pub mod wayland; #[cfg(x11_platform)] pub mod x11; +#[cfg(any(feature = "x11", feature = "wayland"))] +pub mod common; + /// Environment variable specifying which backend should be used on unix platform. /// /// Legal values are x11 and wayland. If this variable is set only the named backend @@ -514,6 +519,11 @@ impl Window { x11_or_wayland!(match self; Window(w) => w.set_ime_position(position)) } + #[inline] + pub fn reset_dead_keys(&self) { + common::xkb_state::reset_dead_keys() + } + #[inline] pub fn set_ime_allowed(&self, allowed: bool) { x11_or_wayland!(match self; Window(w) => w.set_ime_allowed(allowed)) @@ -624,6 +634,34 @@ impl Window { } } +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct KeyEventExtra { + pub key_without_modifiers: Key<'static>, + pub text_with_all_modifiers: Option<&'static str>, +} + +impl KeyEventExtModifierSupplement for KeyEvent { + #[inline] + fn text_with_all_modifiers(&self) -> Option<&str> { + self.platform_specific.text_with_all_modifiers + } + + #[inline] + fn key_without_modifiers(&self) -> Key<'static> { + self.platform_specific.key_without_modifiers.clone() + } +} + +impl KeyCodeExtScancode for KeyCode { + fn from_scancode(scancode: u32) -> KeyCode { + common::keymap::raw_keycode_to_keycode(scancode) + } + + fn to_scancode(self) -> Option { + common::keymap::keycode_to_raw(self) + } +} + /// Hooks for X11 errors. #[cfg(x11_platform)] pub(crate) static mut XLIB_ERROR_HOOKS: Lazy>> = diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index 3056055b13..74c56943c0 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -1,23 +1,24 @@ use std::{cell::RefCell, collections::HashMap, rc::Rc, slice, sync::Arc}; -use libc::{c_char, c_int, c_long, c_uint, c_ulong}; +use libc::{c_char, c_int, c_long, c_ulong}; use super::{ - events, ffi, get_xtarget, mkdid, mkwid, monitor, util, Device, DeviceId, DeviceInfo, Dnd, - DndState, GenericEventCookie, ImeReceiver, ScrollOrientation, UnownedWindow, WindowId, - XExtension, + ffi, get_xtarget, mkdid, mkwid, monitor, util, Device, DeviceId, DeviceInfo, Dnd, DndState, + GenericEventCookie, ImeReceiver, ScrollOrientation, UnownedWindow, WindowId, XExtension, }; -use util::modifiers::{ModifierKeyState, ModifierKeymap}; - use crate::platform_impl::platform::x11::ime::{ImeEvent, ImeEventReceiver, ImeRequest}; use crate::{ dpi::{PhysicalPosition, PhysicalSize}, event::{ - DeviceEvent, ElementState, Event, Ime, KeyboardInput, ModifiersState, TouchPhase, - WindowEvent, + DeviceEvent, ElementState, Event, Ime, KeyEvent, RawKeyEvent, TouchPhase, WindowEvent, }, event_loop::EventLoopWindowTarget as RootELW, + keyboard::ModifiersState, + platform_impl::platform::{ + common::{keymap, xkb_state::KbState}, + KeyEventExtra, + }, }; /// The X11 documentation states: "Keycodes lie in the inclusive range `[8, 255]`". @@ -30,9 +31,10 @@ pub(super) struct EventProcessor { pub(super) randr_event_offset: c_int, pub(super) devices: RefCell>, pub(super) xi2ext: XExtension, + pub(super) xkbext: XExtension, pub(super) target: Rc>, - pub(super) mod_keymap: ModifierKeymap, - pub(super) device_mod_state: ModifierKeyState, + pub(super) kb_state: KbState, + pub(super) pending_mod_change: Option, // Number of touch events currently in progress pub(super) num_touch: u32, pub(super) first_touch: Option, @@ -134,47 +136,8 @@ impl EventProcessor { return; } - // We can't call a `&mut self` method because of the above borrow, - // so we use this macro for repeated modifier state updates. - macro_rules! update_modifiers { - ( $state:expr , $modifier:expr ) => {{ - match ($state, $modifier) { - (state, modifier) => { - if let Some(modifiers) = - self.device_mod_state.update_state(&state, modifier) - { - if let Some(window_id) = self.active_window { - callback(Event::WindowEvent { - window_id: mkwid(window_id), - event: WindowEvent::ModifiersChanged(modifiers), - }); - } - } - } - } - }}; - } - let event_type = xev.get_type(); match event_type { - ffi::MappingNotify => { - let mapping: &ffi::XMappingEvent = xev.as_ref(); - - if mapping.request == ffi::MappingModifier - || mapping.request == ffi::MappingKeyboard - { - unsafe { - (wt.xconn.xlib.XRefreshKeyboardMapping)(xev.as_mut()); - } - wt.xconn - .check_errors() - .expect("Failed to call XRefreshKeyboardMapping"); - - self.mod_keymap.reset_from_x_connection(&wt.xconn); - self.device_mod_state.update_keymap(&self.mod_keymap); - } - } - ffi::ClientMessage => { let client_msg: &ffi::XClientMessageEvent = xev.as_ref(); @@ -577,90 +540,35 @@ impl EventProcessor { } } - ffi::KeyPress | ffi::KeyRelease => { - use crate::event::ElementState::{Pressed, Released}; - - // Note that in compose/pre-edit sequences, this will always be Released. - let state = if xev.get_type() == ffi::KeyPress { - Pressed - } else { - Released - }; - + // Note that in compose/pre-edit sequences, we'll always receive KeyRelease events + ffi::KeyPress => { let xkev: &mut ffi::XKeyEvent = xev.as_mut(); let window = xkev.window; let window_id = mkwid(window); - // Standard virtual core keyboard ID. XInput2 needs to be used to get a reliable - // value, though this should only be an issue under multiseat configurations. - let device = util::VIRTUAL_CORE_KEYBOARD; - let device_id = mkdid(device); - let keycode = xkev.keycode; - - // When a compose sequence or IME pre-edit is finished, it ends in a KeyPress with - // a keycode of 0. - if keycode != 0 && !self.is_composing { - let scancode = keycode - KEYCODE_OFFSET as u32; - let keysym = wt.xconn.lookup_keysym(xkev); - let virtual_keycode = events::keysym_to_element(keysym as c_uint); - - update_modifiers!( - ModifiersState::from_x11_mask(xkev.state), - self.mod_keymap.get_modifier(xkev.keycode as ffi::KeyCode) - ); - - let modifiers = self.device_mod_state.modifiers(); + let written = if let Some(ic) = wt.ime.borrow().get_context(window) { + wt.xconn.lookup_utf8(ic, xkev) + } else { + return; + }; - #[allow(deprecated)] - callback(Event::WindowEvent { + // If we're composing right now, send the string we've got from X11 via + // Ime::Commit. + if self.is_composing && xkev.keycode == 0 && !written.is_empty() { + let event = Event::WindowEvent { window_id, - event: WindowEvent::KeyboardInput { - device_id, - input: KeyboardInput { - state, - scancode, - virtual_keycode, - modifiers, - }, - is_synthetic: false, - }, - }); - } - - if state == Pressed { - let written = if let Some(ic) = wt.ime.borrow().get_context(window) { - wt.xconn.lookup_utf8(ic, xkev) - } else { - return; + event: WindowEvent::Ime(Ime::Preedit(String::new(), None)), }; + callback(event); - // If we're composing right now, send the string we've got from X11 via - // Ime::Commit. - if self.is_composing && keycode == 0 && !written.is_empty() { - let event = Event::WindowEvent { - window_id, - event: WindowEvent::Ime(Ime::Preedit(String::new(), None)), - }; - callback(event); - - let event = Event::WindowEvent { - window_id, - event: WindowEvent::Ime(Ime::Commit(written)), - }; - - self.is_composing = false; - callback(event); - } else { - for chr in written.chars() { - let event = Event::WindowEvent { - window_id, - event: WindowEvent::ReceivedCharacter(chr), - }; + let event = Event::WindowEvent { + window_id, + event: WindowEvent::Ime(Ime::Commit(written)), + }; - callback(event); - } - } + self.is_composing = false; + callback(event); } } @@ -696,8 +604,7 @@ impl EventProcessor { return; } - let modifiers = ModifiersState::from_x11(&xev.mods); - update_modifiers!(modifiers, None); + let modifiers = self.kb_state.mods_state().into(); let state = if xev.evtype == ffi::XI_ButtonPress { Pressed @@ -773,8 +680,7 @@ impl EventProcessor { let window_id = mkwid(xev.event); let new_cursor_pos = (xev.event_x, xev.event_y); - let modifiers = ModifiersState::from_x11(&xev.mods); - update_modifiers!(modifiers, None); + let modifiers = self.kb_state.mods_state().into(); let cursor_moved = self.with_window(xev.event, |window| { let mut shared_state_lock = window.shared_state_lock(); @@ -935,9 +841,7 @@ impl EventProcessor { .focus(xev.event) .expect("Failed to focus input context"); - let modifiers = ModifiersState::from_x11(&xev.mods); - - self.device_mod_state.update_state(&modifiers, None); + let modifiers = self.kb_state.mods_state().into(); if self.active_window != Some(xev.event) { self.active_window = Some(xev.event); @@ -956,7 +860,7 @@ impl EventProcessor { event: Focused(true), }); - if !modifiers.is_empty() { + if let Some(modifiers) = self.pending_mod_change.take() { callback(Event::WindowEvent { window_id, event: WindowEvent::ModifiersChanged(modifiers), @@ -986,8 +890,7 @@ impl EventProcessor { wt, window_id, ElementState::Pressed, - &self.mod_keymap, - &mut self.device_mod_state, + &mut self.kb_state, &mut callback, ); } @@ -1013,8 +916,7 @@ impl EventProcessor { wt, window_id, ElementState::Released, - &self.mod_keymap, - &mut self.device_mod_state, + &mut self.kb_state, &mut callback, ); @@ -1045,7 +947,7 @@ impl EventProcessor { }; if self.window_exists(xev.event) { let id = xev.detail as u64; - let modifiers = self.device_mod_state.modifiers(); + let modifiers = self.kb_state.mods_state().into(); let location = PhysicalPosition::new(xev.event_x, xev.event_y); // Mouse cursor position changes when touch events are received. @@ -1143,6 +1045,55 @@ impl EventProcessor { } } + // The regular KeyPress event has a problem where if you press a dead key, a KeyPress + // event won't be emitted. XInput 2 does not have this problem. + ffi::XI_KeyPress | ffi::XI_KeyRelease if !self.is_composing => { + if let Some(active_window) = self.active_window { + let state = if xev.evtype == ffi::XI_KeyPress { + Pressed + } else { + Released + }; + + let xkev: &ffi::XIDeviceEvent = unsafe { &*(xev.data as *const _) }; + + // We use `self.active_window` here as `xkev.event` has a completely different + // value for some reason. + let window_id = mkwid(active_window); + + let device_id = mkdid(xkev.deviceid); + let keycode = xkev.detail as u32; + + let mut ker = self.kb_state.process_key_event(keycode, state); + let physical_key = ker.keycode(); + let (logical_key, location) = ker.key(); + let text = ker.text(); + let (key_without_modifiers, _) = ker.key_without_modifiers(); + let text_with_all_modifiers = ker.text_with_all_modifiers(); + let repeat = xkev.flags & ffi::XIKeyRepeat == ffi::XIKeyRepeat; + + callback(Event::WindowEvent { + window_id, + event: WindowEvent::KeyboardInput { + device_id, + event: KeyEvent { + physical_key, + logical_key, + text, + location, + state, + repeat, + platform_specific: KeyEventExtra { + key_without_modifiers, + text_with_all_modifiers, + }, + }, + is_synthetic: false, + }, + }); + } + } + ffi::XI_RawKeyPress | ffi::XI_RawKeyRelease => { let xev: &ffi::XIRawEvent = unsafe { &*(xev.data as *const _) }; @@ -1153,46 +1104,19 @@ impl EventProcessor { }; let device_id = mkdid(xev.sourceid); - let keycode = xev.detail; - let scancode = keycode - KEYCODE_OFFSET as i32; - if scancode < 0 { + let keycode = xev.detail as u32; + if keycode < KEYCODE_OFFSET as u32 { return; } - let keysym = wt.xconn.keycode_to_keysym(keycode as ffi::KeyCode); - let virtual_keycode = events::keysym_to_element(keysym as c_uint); - let modifiers = self.device_mod_state.modifiers(); + let physical_key = keymap::raw_keycode_to_keycode(keycode); - #[allow(deprecated)] callback(Event::DeviceEvent { device_id, - event: DeviceEvent::Key(KeyboardInput { - scancode: scancode as u32, - virtual_keycode, + event: DeviceEvent::Key(RawKeyEvent { + physical_key, state, - modifiers, }), }); - - if let Some(modifier) = - self.mod_keymap.get_modifier(keycode as ffi::KeyCode) - { - self.device_mod_state.key_event( - state, - keycode as ffi::KeyCode, - modifier, - ); - - let new_modifiers = self.device_mod_state.modifiers(); - - if modifiers != new_modifiers { - if let Some(window_id) = self.active_window { - callback(Event::WindowEvent { - window_id: mkwid(window_id), - event: WindowEvent::ModifiersChanged(new_modifiers), - }); - } - } - } } ffi::XI_HierarchyChanged => { @@ -1222,6 +1146,55 @@ impl EventProcessor { } } _ => { + if event_type == self.xkbext.first_event_id { + let xev = unsafe { &*(xev as *const _ as *const ffi::XkbAnyEvent) }; + match xev.xkb_type { + ffi::XkbNewKeyboardNotify => { + let xev = unsafe { + &*(xev as *const _ as *const ffi::XkbNewKeyboardNotifyEvent) + }; + let keycodes_changed_flag = 0x1; + let geometry_changed_flag = 0x1 << 1; + + let keycodes_changed = + util::has_flag(xev.changed, keycodes_changed_flag); + let geometry_changed = + util::has_flag(xev.changed, geometry_changed_flag); + + if xev.device == self.kb_state.core_keyboard_id + && (keycodes_changed || geometry_changed) + { + unsafe { self.kb_state.init_with_x11_keymap() }; + } + } + ffi::XkbStateNotify => { + let xev = + unsafe { &*(xev as *const _ as *const ffi::XkbStateNotifyEvent) }; + + let prev_mods = self.kb_state.mods_state(); + self.kb_state.update_modifiers( + xev.base_mods, + xev.latched_mods, + xev.locked_mods, + xev.base_group as u32, + xev.latched_group as u32, + xev.locked_group as u32, + ); + let new_mods = self.kb_state.mods_state(); + if prev_mods != new_mods { + if let Some(window) = self.active_window { + callback(Event::WindowEvent { + window_id: mkwid(window), + event: WindowEvent::ModifiersChanged(new_mods.into()), + }); + } else { + self.pending_mod_change = Some(new_mods.into()); + } + } + } + _ => {} + } + } if event_type == self.randr_event_offset { // In the future, it would be quite easy to emit monitor hotplug events. let prev_list = monitor::invalidate_cached_monitor_list(); @@ -1345,14 +1318,12 @@ impl EventProcessor { wt: &super::EventLoopWindowTarget, window_id: crate::window::WindowId, state: ElementState, - mod_keymap: &ModifierKeymap, - device_mod_state: &mut ModifierKeyState, + kb_state: &mut KbState, callback: &mut F, ) where F: FnMut(Event<'_, T>), { let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD); - let modifiers = device_mod_state.modifiers(); // Update modifiers state and emit key events based on which keys are currently pressed. for keycode in wt @@ -1361,28 +1332,30 @@ impl EventProcessor { .into_iter() .filter(|k| *k >= KEYCODE_OFFSET) { - let scancode = (keycode - KEYCODE_OFFSET) as u32; - let keysym = wt.xconn.keycode_to_keysym(keycode); - let virtual_keycode = events::keysym_to_element(keysym as c_uint); - - if let Some(modifier) = mod_keymap.get_modifier(keycode as ffi::KeyCode) { - device_mod_state.key_event( - ElementState::Pressed, - keycode as ffi::KeyCode, - modifier, - ); - } + let keycode = keycode as u32; + + let mut ker = kb_state.process_key_event(keycode, state); + let physical_key = ker.keycode(); + let (logical_key, location) = ker.key(); + let text = ker.text(); + let (key_without_modifiers, _) = ker.key_without_modifiers(); + let text_with_all_modifiers = ker.text_with_all_modifiers(); - #[allow(deprecated)] callback(Event::WindowEvent { window_id, event: WindowEvent::KeyboardInput { device_id, - input: KeyboardInput { - scancode, + event: KeyEvent { + physical_key, + logical_key, + text, + location, state, - virtual_keycode, - modifiers, + repeat: false, + platform_specific: KeyEventExtra { + key_without_modifiers, + text_with_all_modifiers, + }, }, is_synthetic: true, }, diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index 0ef073d9e4..6e23a75db1 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -2,7 +2,6 @@ mod dnd; mod event_processor; -mod events; pub mod ffi; mod ime; mod monitor; @@ -42,8 +41,8 @@ use self::{ dnd::{Dnd, DndState}, event_processor::EventProcessor, ime::{Ime, ImeCreationError, ImeReceiver, ImeRequest, ImeSender}, - util::modifiers::ModifierKeymap, }; +use super::common::xkb_state::KbState; use crate::{ error::OsError as RootOsError, event::{Event, StartCause}, @@ -202,6 +201,27 @@ impl EventLoop { ext }; + let xkbext = { + let mut ext = XExtension::default(); + + let res = unsafe { + (xconn.xlib.XkbQueryExtension)( + xconn.display, + &mut ext.opcode, + &mut ext.first_event_id, + &mut ext.first_error_id, + &mut 1, + &mut 0, + ) + }; + + if res == ffi::False { + panic!("X server missing XKB extension"); + } + + ext + }; + unsafe { let mut xinput_major_ver = ffi::XI_2_Major; let mut xinput_minor_ver = ffi::XI_2_Minor; @@ -219,9 +239,6 @@ impl EventLoop { xconn.update_cached_wm_info(root); - let mut mod_keymap = ModifierKeymap::new(); - mod_keymap.reset_from_x_connection(&xconn); - let poll = Poll::new().unwrap(); let waker = Arc::new(Waker::new(poll.registry(), USER_REDRAW_TOKEN).unwrap()); @@ -232,6 +249,10 @@ impl EventLoop { let (user_sender, user_channel) = std::sync::mpsc::channel(); let (redraw_sender, redraw_channel) = std::sync::mpsc::channel(); + let kb_state = + KbState::from_x11_xkb(unsafe { (xconn.xlib_xcb.XGetXCBConnection)(xconn.display) }) + .unwrap(); + let window_target = EventLoopWindowTarget { ime, root, @@ -264,8 +285,9 @@ impl EventLoop { ime_receiver, ime_event_receiver, xi2ext, - mod_keymap, - device_mod_state: Default::default(), + xkbext, + kb_state, + pending_mod_change: Default::default(), num_touch: 0, first_touch: None, active_window: None, @@ -279,6 +301,15 @@ impl EventLoop { .select_xinput_events(root, ffi::XIAllDevices, ffi::XI_HierarchyChangedMask) .queue(); + get_xtarget(&target) + .xconn + .select_xkb_events( + 0x100, // Use the "core keyboard device" + ffi::XkbNewKeyboardNotifyMask | ffi::XkbStateNotifyMask, + ) + .unwrap() + .queue(); + event_processor.init_device(ffi::XIAllDevices); EventLoop { diff --git a/src/platform_impl/linux/x11/util/input.rs b/src/platform_impl/linux/x11/util/input.rs index e9f45aee1c..24aad57937 100644 --- a/src/platform_impl/linux/x11/util/input.rs +++ b/src/platform_impl/linux/x11/util/input.rs @@ -1,7 +1,7 @@ use std::{slice, str}; use super::*; -use crate::event::ModifiersState; +use crate::keyboard::ModifiersState; pub const VIRTUAL_CORE_POINTER: c_int = 2; pub const VIRTUAL_CORE_KEYBOARD: c_int = 3; @@ -20,8 +20,8 @@ impl ModifiersState { let mut m = ModifiersState::empty(); m.set(ModifiersState::ALT, mask & ffi::Mod1Mask != 0); m.set(ModifiersState::SHIFT, mask & ffi::ShiftMask != 0); - m.set(ModifiersState::CTRL, mask & ffi::ControlMask != 0); - m.set(ModifiersState::LOGO, mask & ffi::Mod4Mask != 0); + m.set(ModifiersState::CONTROL, mask & ffi::ControlMask != 0); + m.set(ModifiersState::SUPER, mask & ffi::Mod4Mask != 0); m } } @@ -81,12 +81,12 @@ impl XConnection { Flusher::new(self) } - #[allow(dead_code)] pub fn select_xkb_events(&self, device_id: c_uint, mask: c_ulong) -> Option> { let status = unsafe { (self.xlib.XkbSelectEvents)(self.display, device_id, mask, mask) }; if status == ffi::True { Some(Flusher::new(self)) } else { + error!("Could not select XKB events: The XKB extension is not initialized!"); None } } diff --git a/src/platform_impl/linux/x11/util/keys.rs b/src/platform_impl/linux/x11/util/keys.rs index e6b6e7a147..6fff11d775 100644 --- a/src/platform_impl/linux/x11/util/keys.rs +++ b/src/platform_impl/linux/x11/util/keys.rs @@ -1,4 +1,4 @@ -use std::{iter::Enumerate, ptr, slice::Iter}; +use std::{iter::Enumerate, slice::Iter}; use super::*; @@ -62,20 +62,6 @@ impl Iterator for KeymapIter<'_> { } impl XConnection { - pub fn keycode_to_keysym(&self, keycode: ffi::KeyCode) -> ffi::KeySym { - unsafe { (self.xlib.XKeycodeToKeysym)(self.display, keycode, 0) } - } - - pub fn lookup_keysym(&self, xkev: &mut ffi::XKeyEvent) -> ffi::KeySym { - let mut keysym = 0; - - unsafe { - (self.xlib.XLookupString)(xkev, ptr::null_mut(), 0, &mut keysym, ptr::null_mut()); - } - - keysym - } - pub fn query_keymap(&self) -> Keymap { let mut keys = [0; 32]; diff --git a/src/platform_impl/linux/x11/util/mod.rs b/src/platform_impl/linux/x11/util/mod.rs index 9b9ec96d63..ba7d7eaee2 100644 --- a/src/platform_impl/linux/x11/util/mod.rs +++ b/src/platform_impl/linux/x11/util/mod.rs @@ -11,7 +11,6 @@ mod icon; mod input; pub mod keys; mod memory; -pub mod modifiers; mod randr; mod window_property; mod wm; diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index c96300a5ef..c651ff8c4f 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -245,7 +245,6 @@ impl UnownedWindow { | ffi::StructureNotifyMask | ffi::VisibilityChangeMask | ffi::KeyPressMask - | ffi::KeyReleaseMask | ffi::KeymapStateMask | ffi::ButtonPressMask | ffi::ButtonReleaseMask @@ -448,17 +447,17 @@ impl UnownedWindow { // Select XInput2 events let mask = ffi::XI_MotionMask - | ffi::XI_ButtonPressMask - | ffi::XI_ButtonReleaseMask - //| ffi::XI_KeyPressMask - //| ffi::XI_KeyReleaseMask - | ffi::XI_EnterMask - | ffi::XI_LeaveMask - | ffi::XI_FocusInMask - | ffi::XI_FocusOutMask - | ffi::XI_TouchBeginMask - | ffi::XI_TouchUpdateMask - | ffi::XI_TouchEndMask; + | ffi::XI_ButtonPressMask + | ffi::XI_ButtonReleaseMask + | ffi::XI_KeyPressMask + | ffi::XI_KeyReleaseMask + | ffi::XI_EnterMask + | ffi::XI_LeaveMask + | ffi::XI_FocusInMask + | ffi::XI_FocusOutMask + | ffi::XI_TouchBeginMask + | ffi::XI_TouchUpdateMask + | ffi::XI_TouchEndMask; xconn .select_xinput_events(window.xwindow, ffi::XIAllMasterDevices, mask) .queue(); From 9f21eb29b3d8d296c4cd73ba0923bd67f4438ddd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20R=C3=B8yset?= Date: Fri, 9 Dec 2022 10:44:49 +0100 Subject: [PATCH 05/13] Implement the new keyboard API for the Web --- Cargo.toml | 1 + .../web/event_loop/window_target.rs | 134 +++-- src/platform_impl/web/keyboard.rs | 520 ++++++++++++++++++ src/platform_impl/web/mod.rs | 2 + src/platform_impl/web/web_sys/canvas.rs | 76 +-- .../web/web_sys/canvas/mouse_handler.rs | 3 +- .../web/web_sys/canvas/pointer_handler.rs | 4 +- src/platform_impl/web/web_sys/event.rs | 252 +++------ src/platform_impl/web/window.rs | 4 + 9 files changed, 721 insertions(+), 275 deletions(-) create mode 100644 src/platform_impl/web/keyboard.rs diff --git a/Cargo.toml b/Cargo.toml index 141c459467..71069abd03 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -133,6 +133,7 @@ features = [ 'console', "AddEventListenerOptions", 'CssStyleDeclaration', + 'CompositionEvent', 'BeforeUnloadEvent', 'Document', 'DomRect', diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index 17aafd946d..a5665523d5 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -1,4 +1,4 @@ -use std::cell::RefCell; +use std::cell::{Cell, RefCell}; use std::clone::Clone; use std::collections::{vec_deque::IntoIter as VecDequeIter, VecDeque}; use std::rc::Rc; @@ -6,24 +6,50 @@ use std::rc::Rc; use raw_window_handle::{RawDisplayHandle, WebDisplayHandle}; use super::{ - super::monitor::MonitorHandle, backend, device::DeviceId, proxy::EventLoopProxy, runner, + super::{monitor::MonitorHandle, KeyEventExtra}, + backend, + device::DeviceId, + proxy::EventLoopProxy, + runner, window::WindowId, }; use crate::dpi::{PhysicalSize, Size}; use crate::event::{ - DeviceEvent, DeviceId as RootDeviceId, ElementState, Event, KeyboardInput, Touch, TouchPhase, + DeviceEvent, DeviceId as RootDeviceId, ElementState, Event, KeyEvent, Touch, TouchPhase, WindowEvent, }; +use crate::keyboard::ModifiersState; use crate::window::{Theme, WindowId as RootWindowId}; +#[derive(Default)] +struct ModifiersShared(Rc>); + +impl ModifiersShared { + fn set(&self, new: ModifiersState) { + self.0.set(new) + } + + fn get(&self) -> ModifiersState { + self.0.get() + } +} + +impl Clone for ModifiersShared { + fn clone(&self) -> Self { + Self(Rc::clone(&self.0)) + } +} + pub struct EventLoopWindowTarget { pub(crate) runner: runner::Shared, + modifiers: ModifiersShared, } impl Clone for EventLoopWindowTarget { fn clone(&self) -> Self { Self { runner: self.runner.clone(), + modifiers: self.modifiers.clone(), } } } @@ -32,6 +58,7 @@ impl EventLoopWindowTarget { pub fn new() -> Self { Self { runner: runner::Shared::new(), + modifiers: ModifiersShared::default(), } } @@ -86,54 +113,77 @@ impl EventLoopWindowTarget { }); let runner = self.runner.clone(); + let modifiers = self.modifiers.clone(); canvas.on_keyboard_press( - move |scancode, virtual_keycode, modifiers| { - #[allow(deprecated)] - runner.send_event(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::KeyboardInput { - device_id: RootDeviceId(unsafe { DeviceId::dummy() }), - input: KeyboardInput { - scancode, - state: ElementState::Pressed, - virtual_keycode, - modifiers, + move |physical_key, logical_key, text, location, repeat, new_modifiers| { + let active_modifiers = modifiers.get() | new_modifiers; + let modifiers_changed = if modifiers.get() != active_modifiers { + modifiers.set(active_modifiers); + Some(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::ModifiersChanged(active_modifiers), + }) + } else { + None + }; + + runner.send_events( + std::iter::once(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::KeyboardInput { + device_id: RootDeviceId(unsafe { DeviceId::dummy() }), + event: KeyEvent { + physical_key, + logical_key, + text, + location, + state: ElementState::Pressed, + repeat, + platform_specific: KeyEventExtra, + }, + is_synthetic: false, }, - is_synthetic: false, - }, - }); + }) + .chain(modifiers_changed), + ); }, prevent_default, ); let runner = self.runner.clone(); + let modifiers = self.modifiers.clone(); canvas.on_keyboard_release( - move |scancode, virtual_keycode, modifiers| { - #[allow(deprecated)] - runner.send_event(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::KeyboardInput { - device_id: RootDeviceId(unsafe { DeviceId::dummy() }), - input: KeyboardInput { - scancode, - state: ElementState::Released, - virtual_keycode, - modifiers, - }, - is_synthetic: false, - }, - }); - }, - prevent_default, - ); + move |physical_key, logical_key, text, location, repeat, new_modifiers| { + let active_modifiers = modifiers.get() & !new_modifiers; + let modifiers_changed = if modifiers.get() != active_modifiers { + modifiers.set(active_modifiers); + Some(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::ModifiersChanged(active_modifiers), + }) + } else { + None + }; - let runner = self.runner.clone(); - canvas.on_received_character( - move |char_code| { - runner.send_event(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::ReceivedCharacter(char_code), - }); + runner.send_events( + std::iter::once(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::KeyboardInput { + device_id: RootDeviceId(unsafe { DeviceId::dummy() }), + event: KeyEvent { + physical_key, + logical_key, + text, + location, + state: ElementState::Released, + repeat, + platform_specific: KeyEventExtra, + }, + is_synthetic: false, + }, + }) + .chain(modifiers_changed), + ) }, prevent_default, ); diff --git a/src/platform_impl/web/keyboard.rs b/src/platform_impl/web/keyboard.rs new file mode 100644 index 0000000000..e13e297e69 --- /dev/null +++ b/src/platform_impl/web/keyboard.rs @@ -0,0 +1,520 @@ +use crate::keyboard::{Key, KeyCode, NativeKey, NativeKeyCode}; + +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub(crate) struct KeyEventExtra; + +impl<'a> Key<'a> { + pub(crate) fn from_key_attribute_value(kav: &'a str) -> Self { + match kav { + "Unidentified" => Key::Unidentified(NativeKey::Web(kav.to_string())), + "Dead" => Key::Dead(None), + "Alt" => Key::Alt, + "AltGraph" => Key::AltGraph, + "CapsLock" => Key::CapsLock, + "Control" => Key::Control, + "Fn" => Key::Fn, + "FnLock" => Key::FnLock, + "NumLock" => Key::NumLock, + "ScrollLock" => Key::ScrollLock, + "Shift" => Key::Shift, + "Symbol" => Key::Symbol, + "SymbolLock" => Key::SymbolLock, + "Hyper" => Key::Hyper, + "Meta" => Key::Super, + "Enter" => Key::Enter, + "Tab" => Key::Tab, + " " => Key::Space, + "ArrowDown" => Key::ArrowDown, + "ArrowLeft" => Key::ArrowLeft, + "ArrowRight" => Key::ArrowRight, + "ArrowUp" => Key::ArrowUp, + "End" => Key::End, + "Home" => Key::Home, + "PageDown" => Key::PageDown, + "PageUp" => Key::PageUp, + "Backspace" => Key::Backspace, + "Clear" => Key::Clear, + "Copy" => Key::Copy, + "CrSel" => Key::CrSel, + "Cut" => Key::Cut, + "Delete" => Key::Delete, + "EraseEof" => Key::EraseEof, + "ExSel" => Key::ExSel, + "Insert" => Key::Insert, + "Paste" => Key::Paste, + "Redo" => Key::Redo, + "Undo" => Key::Undo, + "Accept" => Key::Accept, + "Again" => Key::Again, + "Attn" => Key::Attn, + "Cancel" => Key::Cancel, + "ContextMenu" => Key::ContextMenu, + "Escape" => Key::Escape, + "Execute" => Key::Execute, + "Find" => Key::Find, + "Help" => Key::Help, + "Pause" => Key::Pause, + "Play" => Key::Play, + "Props" => Key::Props, + "Select" => Key::Select, + "ZoomIn" => Key::ZoomIn, + "ZoomOut" => Key::ZoomOut, + "BrightnessDown" => Key::BrightnessDown, + "BrightnessUp" => Key::BrightnessUp, + "Eject" => Key::Eject, + "LogOff" => Key::LogOff, + "Power" => Key::Power, + "PowerOff" => Key::PowerOff, + "PrintScreen" => Key::PrintScreen, + "Hibernate" => Key::Hibernate, + "Standby" => Key::Standby, + "WakeUp" => Key::WakeUp, + "AllCandidates" => Key::AllCandidates, + "Alphanumeric" => Key::Alphanumeric, + "CodeInput" => Key::CodeInput, + "Compose" => Key::Compose, + "Convert" => Key::Convert, + "FinalMode" => Key::FinalMode, + "GroupFirst" => Key::GroupFirst, + "GroupLast" => Key::GroupLast, + "GroupNext" => Key::GroupNext, + "GroupPrevious" => Key::GroupPrevious, + "ModeChange" => Key::ModeChange, + "NextCandidate" => Key::NextCandidate, + "NonConvert" => Key::NonConvert, + "PreviousCandidate" => Key::PreviousCandidate, + "Process" => Key::Process, + "SingleCandidate" => Key::SingleCandidate, + "HangulMode" => Key::HangulMode, + "HanjaMode" => Key::HanjaMode, + "JunjaMode" => Key::JunjaMode, + "Eisu" => Key::Eisu, + "Hankaku" => Key::Hankaku, + "Hiragana" => Key::Hiragana, + "HiraganaKatakana" => Key::HiraganaKatakana, + "KanaMode" => Key::KanaMode, + "KanjiMode" => Key::KanjiMode, + "Katakana" => Key::Katakana, + "Romaji" => Key::Romaji, + "Zenkaku" => Key::Zenkaku, + "ZenkakuHankaku" => Key::ZenkakuHankaku, + "Soft1" => Key::Soft1, + "Soft2" => Key::Soft2, + "Soft3" => Key::Soft3, + "Soft4" => Key::Soft4, + "ChannelDown" => Key::ChannelDown, + "ChannelUp" => Key::ChannelUp, + "Close" => Key::Close, + "MailForward" => Key::MailForward, + "MailReply" => Key::MailReply, + "MailSend" => Key::MailSend, + "MediaClose" => Key::MediaClose, + "MediaFastForward" => Key::MediaFastForward, + "MediaPause" => Key::MediaPause, + "MediaPlay" => Key::MediaPlay, + "MediaPlayPause" => Key::MediaPlayPause, + "MediaRecord" => Key::MediaRecord, + "MediaRewind" => Key::MediaRewind, + "MediaStop" => Key::MediaStop, + "MediaTrackNext" => Key::MediaTrackNext, + "MediaTrackPrevious" => Key::MediaTrackPrevious, + "New" => Key::New, + "Open" => Key::Open, + "Print" => Key::Print, + "Save" => Key::Save, + "SpellCheck" => Key::SpellCheck, + "Key11" => Key::Key11, + "Key12" => Key::Key12, + "AudioBalanceLeft" => Key::AudioBalanceLeft, + "AudioBalanceRight" => Key::AudioBalanceRight, + "AudioBassBoostDown" => Key::AudioBassBoostDown, + "AudioBassBoostToggle" => Key::AudioBassBoostToggle, + "AudioBassBoostUp" => Key::AudioBassBoostUp, + "AudioFaderFront" => Key::AudioFaderFront, + "AudioFaderRear" => Key::AudioFaderRear, + "AudioSurroundModeNext" => Key::AudioSurroundModeNext, + "AudioTrebleDown" => Key::AudioTrebleDown, + "AudioTrebleUp" => Key::AudioTrebleUp, + "AudioVolumeDown" => Key::AudioVolumeDown, + "AudioVolumeUp" => Key::AudioVolumeUp, + "AudioVolumeMute" => Key::AudioVolumeMute, + "MicrophoneToggle" => Key::MicrophoneToggle, + "MicrophoneVolumeDown" => Key::MicrophoneVolumeDown, + "MicrophoneVolumeUp" => Key::MicrophoneVolumeUp, + "MicrophoneVolumeMute" => Key::MicrophoneVolumeMute, + "SpeechCorrectionList" => Key::SpeechCorrectionList, + "SpeechInputToggle" => Key::SpeechInputToggle, + "LaunchApplication1" => Key::LaunchApplication1, + "LaunchApplication2" => Key::LaunchApplication2, + "LaunchCalendar" => Key::LaunchCalendar, + "LaunchContacts" => Key::LaunchContacts, + "LaunchMail" => Key::LaunchMail, + "LaunchMediaPlayer" => Key::LaunchMediaPlayer, + "LaunchMusicPlayer" => Key::LaunchMusicPlayer, + "LaunchPhone" => Key::LaunchPhone, + "LaunchScreenSaver" => Key::LaunchScreenSaver, + "LaunchSpreadsheet" => Key::LaunchSpreadsheet, + "LaunchWebBrowser" => Key::LaunchWebBrowser, + "LaunchWebCam" => Key::LaunchWebCam, + "LaunchWordProcessor" => Key::LaunchWordProcessor, + "BrowserBack" => Key::BrowserBack, + "BrowserFavorites" => Key::BrowserFavorites, + "BrowserForward" => Key::BrowserForward, + "BrowserHome" => Key::BrowserHome, + "BrowserRefresh" => Key::BrowserRefresh, + "BrowserSearch" => Key::BrowserSearch, + "BrowserStop" => Key::BrowserStop, + "AppSwitch" => Key::AppSwitch, + "Call" => Key::Call, + "Camera" => Key::Camera, + "CameraFocus" => Key::CameraFocus, + "EndCall" => Key::EndCall, + "GoBack" => Key::GoBack, + "GoHome" => Key::GoHome, + "HeadsetHook" => Key::HeadsetHook, + "LastNumberRedial" => Key::LastNumberRedial, + "Notification" => Key::Notification, + "MannerMode" => Key::MannerMode, + "VoiceDial" => Key::VoiceDial, + "TV" => Key::TV, + "TV3DMode" => Key::TV3DMode, + "TVAntennaCable" => Key::TVAntennaCable, + "TVAudioDescription" => Key::TVAudioDescription, + "TVAudioDescriptionMixDown" => Key::TVAudioDescriptionMixDown, + "TVAudioDescriptionMixUp" => Key::TVAudioDescriptionMixUp, + "TVContentsMenu" => Key::TVContentsMenu, + "TVDataService" => Key::TVDataService, + "TVInput" => Key::TVInput, + "TVInputComponent1" => Key::TVInputComponent1, + "TVInputComponent2" => Key::TVInputComponent2, + "TVInputComposite1" => Key::TVInputComposite1, + "TVInputComposite2" => Key::TVInputComposite2, + "TVInputHDMI1" => Key::TVInputHDMI1, + "TVInputHDMI2" => Key::TVInputHDMI2, + "TVInputHDMI3" => Key::TVInputHDMI3, + "TVInputHDMI4" => Key::TVInputHDMI4, + "TVInputVGA1" => Key::TVInputVGA1, + "TVMediaContext" => Key::TVMediaContext, + "TVNetwork" => Key::TVNetwork, + "TVNumberEntry" => Key::TVNumberEntry, + "TVPower" => Key::TVPower, + "TVRadioService" => Key::TVRadioService, + "TVSatellite" => Key::TVSatellite, + "TVSatelliteBS" => Key::TVSatelliteBS, + "TVSatelliteCS" => Key::TVSatelliteCS, + "TVSatelliteToggle" => Key::TVSatelliteToggle, + "TVTerrestrialAnalog" => Key::TVTerrestrialAnalog, + "TVTerrestrialDigital" => Key::TVTerrestrialDigital, + "TVTimer" => Key::TVTimer, + "AVRInput" => Key::AVRInput, + "AVRPower" => Key::AVRPower, + "ColorF0Red" => Key::ColorF0Red, + "ColorF1Green" => Key::ColorF1Green, + "ColorF2Yellow" => Key::ColorF2Yellow, + "ColorF3Blue" => Key::ColorF3Blue, + "ColorF4Grey" => Key::ColorF4Grey, + "ColorF5Brown" => Key::ColorF5Brown, + "ClosedCaptionToggle" => Key::ClosedCaptionToggle, + "Dimmer" => Key::Dimmer, + "DisplaySwap" => Key::DisplaySwap, + "DVR" => Key::DVR, + "Exit" => Key::Exit, + "FavoriteClear0" => Key::FavoriteClear0, + "FavoriteClear1" => Key::FavoriteClear1, + "FavoriteClear2" => Key::FavoriteClear2, + "FavoriteClear3" => Key::FavoriteClear3, + "FavoriteRecall0" => Key::FavoriteRecall0, + "FavoriteRecall1" => Key::FavoriteRecall1, + "FavoriteRecall2" => Key::FavoriteRecall2, + "FavoriteRecall3" => Key::FavoriteRecall3, + "FavoriteStore0" => Key::FavoriteStore0, + "FavoriteStore1" => Key::FavoriteStore1, + "FavoriteStore2" => Key::FavoriteStore2, + "FavoriteStore3" => Key::FavoriteStore3, + "Guide" => Key::Guide, + "GuideNextDay" => Key::GuideNextDay, + "GuidePreviousDay" => Key::GuidePreviousDay, + "Info" => Key::Info, + "InstantReplay" => Key::InstantReplay, + "Link" => Key::Link, + "ListProgram" => Key::ListProgram, + "LiveContent" => Key::LiveContent, + "Lock" => Key::Lock, + "MediaApps" => Key::MediaApps, + "MediaAudioTrack" => Key::MediaAudioTrack, + "MediaLast" => Key::MediaLast, + "MediaSkipBackward" => Key::MediaSkipBackward, + "MediaSkipForward" => Key::MediaSkipForward, + "MediaStepBackward" => Key::MediaStepBackward, + "MediaStepForward" => Key::MediaStepForward, + "MediaTopMenu" => Key::MediaTopMenu, + "NavigateIn" => Key::NavigateIn, + "NavigateNext" => Key::NavigateNext, + "NavigateOut" => Key::NavigateOut, + "NavigatePrevious" => Key::NavigatePrevious, + "NextFavoriteChannel" => Key::NextFavoriteChannel, + "NextUserProfile" => Key::NextUserProfile, + "OnDemand" => Key::OnDemand, + "Pairing" => Key::Pairing, + "PinPDown" => Key::PinPDown, + "PinPMove" => Key::PinPMove, + "PinPToggle" => Key::PinPToggle, + "PinPUp" => Key::PinPUp, + "PlaySpeedDown" => Key::PlaySpeedDown, + "PlaySpeedReset" => Key::PlaySpeedReset, + "PlaySpeedUp" => Key::PlaySpeedUp, + "RandomToggle" => Key::RandomToggle, + "RcLowBattery" => Key::RcLowBattery, + "RecordSpeedNext" => Key::RecordSpeedNext, + "RfBypass" => Key::RfBypass, + "ScanChannelsToggle" => Key::ScanChannelsToggle, + "ScreenModeNext" => Key::ScreenModeNext, + "Settings" => Key::Settings, + "SplitScreenToggle" => Key::SplitScreenToggle, + "STBInput" => Key::STBInput, + "STBPower" => Key::STBPower, + "Subtitle" => Key::Subtitle, + "Teletext" => Key::Teletext, + "VideoModeNext" => Key::VideoModeNext, + "Wink" => Key::Wink, + "ZoomToggle" => Key::ZoomToggle, + "F1" => Key::F1, + "F2" => Key::F2, + "F3" => Key::F3, + "F4" => Key::F4, + "F5" => Key::F5, + "F6" => Key::F6, + "F7" => Key::F7, + "F8" => Key::F8, + "F9" => Key::F9, + "F10" => Key::F10, + "F11" => Key::F11, + "F12" => Key::F12, + "F13" => Key::F13, + "F14" => Key::F14, + "F15" => Key::F15, + "F16" => Key::F16, + "F17" => Key::F17, + "F18" => Key::F18, + "F19" => Key::F19, + "F20" => Key::F20, + "F21" => Key::F21, + "F22" => Key::F22, + "F23" => Key::F23, + "F24" => Key::F24, + "F25" => Key::F25, + "F26" => Key::F26, + "F27" => Key::F27, + "F28" => Key::F28, + "F29" => Key::F29, + "F30" => Key::F30, + "F31" => Key::F31, + "F32" => Key::F32, + "F33" => Key::F33, + "F34" => Key::F34, + "F35" => Key::F35, + string => Key::Character(string), + } + } +} + +impl KeyCode { + pub fn from_key_code_attribute_value(kcav: &str) -> Self { + match kcav { + "Backquote" => KeyCode::Backquote, + "Backslash" => KeyCode::Backslash, + "BracketLeft" => KeyCode::BracketLeft, + "BracketRight" => KeyCode::BracketRight, + "Comma" => KeyCode::Comma, + "Digit0" => KeyCode::Digit0, + "Digit1" => KeyCode::Digit1, + "Digit2" => KeyCode::Digit2, + "Digit3" => KeyCode::Digit3, + "Digit4" => KeyCode::Digit4, + "Digit5" => KeyCode::Digit5, + "Digit6" => KeyCode::Digit6, + "Digit7" => KeyCode::Digit7, + "Digit8" => KeyCode::Digit8, + "Digit9" => KeyCode::Digit9, + "Equal" => KeyCode::Equal, + "IntlBackslash" => KeyCode::IntlBackslash, + "IntlRo" => KeyCode::IntlRo, + "IntlYen" => KeyCode::IntlYen, + "KeyA" => KeyCode::KeyA, + "KeyB" => KeyCode::KeyB, + "KeyC" => KeyCode::KeyC, + "KeyD" => KeyCode::KeyD, + "KeyE" => KeyCode::KeyE, + "KeyF" => KeyCode::KeyF, + "KeyG" => KeyCode::KeyG, + "KeyH" => KeyCode::KeyH, + "KeyI" => KeyCode::KeyI, + "KeyJ" => KeyCode::KeyJ, + "KeyK" => KeyCode::KeyK, + "KeyL" => KeyCode::KeyL, + "KeyM" => KeyCode::KeyM, + "KeyN" => KeyCode::KeyN, + "KeyO" => KeyCode::KeyO, + "KeyP" => KeyCode::KeyP, + "KeyQ" => KeyCode::KeyQ, + "KeyR" => KeyCode::KeyR, + "KeyS" => KeyCode::KeyS, + "KeyT" => KeyCode::KeyT, + "KeyU" => KeyCode::KeyU, + "KeyV" => KeyCode::KeyV, + "KeyW" => KeyCode::KeyW, + "KeyX" => KeyCode::KeyX, + "KeyY" => KeyCode::KeyY, + "KeyZ" => KeyCode::KeyZ, + "Minus" => KeyCode::Minus, + "Period" => KeyCode::Period, + "Quote" => KeyCode::Quote, + "Semicolon" => KeyCode::Semicolon, + "Slash" => KeyCode::Slash, + "AltLeft" => KeyCode::AltLeft, + "AltRight" => KeyCode::AltRight, + "Backspace" => KeyCode::Backspace, + "CapsLock" => KeyCode::CapsLock, + "ContextMenu" => KeyCode::ContextMenu, + "ControlLeft" => KeyCode::ControlLeft, + "ControlRight" => KeyCode::ControlRight, + "Enter" => KeyCode::Enter, + "MetaLeft" => KeyCode::SuperLeft, + "MetaRight" => KeyCode::SuperRight, + "ShiftLeft" => KeyCode::ShiftLeft, + "ShiftRight" => KeyCode::ShiftRight, + "Space" => KeyCode::Space, + "Tab" => KeyCode::Tab, + "Convert" => KeyCode::Convert, + "KanaMode" => KeyCode::KanaMode, + "Lang1" => KeyCode::Lang1, + "Lang2" => KeyCode::Lang2, + "Lang3" => KeyCode::Lang3, + "Lang4" => KeyCode::Lang4, + "Lang5" => KeyCode::Lang5, + "NonConvert" => KeyCode::NonConvert, + "Delete" => KeyCode::Delete, + "End" => KeyCode::End, + "Help" => KeyCode::Help, + "Home" => KeyCode::Home, + "Insert" => KeyCode::Insert, + "PageDown" => KeyCode::PageDown, + "PageUp" => KeyCode::PageUp, + "ArrowDown" => KeyCode::ArrowDown, + "ArrowLeft" => KeyCode::ArrowLeft, + "ArrowRight" => KeyCode::ArrowRight, + "ArrowUp" => KeyCode::ArrowUp, + "NumLock" => KeyCode::NumLock, + "Numpad0" => KeyCode::Numpad0, + "Numpad1" => KeyCode::Numpad1, + "Numpad2" => KeyCode::Numpad2, + "Numpad3" => KeyCode::Numpad3, + "Numpad4" => KeyCode::Numpad4, + "Numpad5" => KeyCode::Numpad5, + "Numpad6" => KeyCode::Numpad6, + "Numpad7" => KeyCode::Numpad7, + "Numpad8" => KeyCode::Numpad8, + "Numpad9" => KeyCode::Numpad9, + "NumpadAdd" => KeyCode::NumpadAdd, + "NumpadBackspace" => KeyCode::NumpadBackspace, + "NumpadClear" => KeyCode::NumpadClear, + "NumpadClearEntry" => KeyCode::NumpadClearEntry, + "NumpadComma" => KeyCode::NumpadComma, + "NumpadDecimal" => KeyCode::NumpadDecimal, + "NumpadDivide" => KeyCode::NumpadDivide, + "NumpadEnter" => KeyCode::NumpadEnter, + "NumpadEqual" => KeyCode::NumpadEqual, + "NumpadHash" => KeyCode::NumpadHash, + "NumpadMemoryAdd" => KeyCode::NumpadMemoryAdd, + "NumpadMemoryClear" => KeyCode::NumpadMemoryClear, + "NumpadMemoryRecall" => KeyCode::NumpadMemoryRecall, + "NumpadMemoryStore" => KeyCode::NumpadMemoryStore, + "NumpadMemorySubtract" => KeyCode::NumpadMemorySubtract, + "NumpadMultiply" => KeyCode::NumpadMultiply, + "NumpadParenLeft" => KeyCode::NumpadParenLeft, + "NumpadParenRight" => KeyCode::NumpadParenRight, + "NumpadStar" => KeyCode::NumpadStar, + "NumpadSubtract" => KeyCode::NumpadSubtract, + "Escape" => KeyCode::Escape, + "Fn" => KeyCode::Fn, + "FnLock" => KeyCode::FnLock, + "PrintScreen" => KeyCode::PrintScreen, + "ScrollLock" => KeyCode::ScrollLock, + "Pause" => KeyCode::Pause, + "BrowserBack" => KeyCode::BrowserBack, + "BrowserFavorites" => KeyCode::BrowserFavorites, + "BrowserForward" => KeyCode::BrowserForward, + "BrowserHome" => KeyCode::BrowserHome, + "BrowserRefresh" => KeyCode::BrowserRefresh, + "BrowserSearch" => KeyCode::BrowserSearch, + "BrowserStop" => KeyCode::BrowserStop, + "Eject" => KeyCode::Eject, + "LaunchApp1" => KeyCode::LaunchApp1, + "LaunchApp2" => KeyCode::LaunchApp2, + "LaunchMail" => KeyCode::LaunchMail, + "MediaPlayPause" => KeyCode::MediaPlayPause, + "MediaSelect" => KeyCode::MediaSelect, + "MediaStop" => KeyCode::MediaStop, + "MediaTrackNext" => KeyCode::MediaTrackNext, + "MediaTrackPrevious" => KeyCode::MediaTrackPrevious, + "Power" => KeyCode::Power, + "Sleep" => KeyCode::Sleep, + "AudioVolumeDown" => KeyCode::AudioVolumeDown, + "AudioVolumeMute" => KeyCode::AudioVolumeMute, + "AudioVolumeUp" => KeyCode::AudioVolumeUp, + "WakeUp" => KeyCode::WakeUp, + "Hyper" => KeyCode::Hyper, + "Turbo" => KeyCode::Turbo, + "Abort" => KeyCode::Abort, + "Resume" => KeyCode::Resume, + "Suspend" => KeyCode::Suspend, + "Again" => KeyCode::Again, + "Copy" => KeyCode::Copy, + "Cut" => KeyCode::Cut, + "Find" => KeyCode::Find, + "Open" => KeyCode::Open, + "Paste" => KeyCode::Paste, + "Props" => KeyCode::Props, + "Select" => KeyCode::Select, + "Undo" => KeyCode::Undo, + "Hiragana" => KeyCode::Hiragana, + "Katakana" => KeyCode::Katakana, + "F1" => KeyCode::F1, + "F2" => KeyCode::F2, + "F3" => KeyCode::F3, + "F4" => KeyCode::F4, + "F5" => KeyCode::F5, + "F6" => KeyCode::F6, + "F7" => KeyCode::F7, + "F8" => KeyCode::F8, + "F9" => KeyCode::F9, + "F10" => KeyCode::F10, + "F11" => KeyCode::F11, + "F12" => KeyCode::F12, + "F13" => KeyCode::F13, + "F14" => KeyCode::F14, + "F15" => KeyCode::F15, + "F16" => KeyCode::F16, + "F17" => KeyCode::F17, + "F18" => KeyCode::F18, + "F19" => KeyCode::F19, + "F20" => KeyCode::F20, + "F21" => KeyCode::F21, + "F22" => KeyCode::F22, + "F23" => KeyCode::F23, + "F24" => KeyCode::F24, + "F25" => KeyCode::F25, + "F26" => KeyCode::F26, + "F27" => KeyCode::F27, + "F28" => KeyCode::F28, + "F29" => KeyCode::F29, + "F30" => KeyCode::F30, + "F31" => KeyCode::F31, + "F32" => KeyCode::F32, + "F33" => KeyCode::F33, + "F34" => KeyCode::F34, + "F35" => KeyCode::F35, + _ => KeyCode::Unidentified(NativeKeyCode::Unidentified), + } + } +} diff --git a/src/platform_impl/web/mod.rs b/src/platform_impl/web/mod.rs index aab8f43398..01a27e9cbc 100644 --- a/src/platform_impl/web/mod.rs +++ b/src/platform_impl/web/mod.rs @@ -20,6 +20,7 @@ mod device; mod error; mod event_loop; +mod keyboard; mod monitor; mod window; @@ -34,6 +35,7 @@ pub(crate) use self::event_loop::{ pub use self::monitor::{MonitorHandle, VideoMode}; pub use self::window::{PlatformSpecificWindowBuilderAttributes, Window, WindowId}; +pub(crate) use self::keyboard::KeyEventExtra; pub(crate) use crate::icon::NoIcon as PlatformIcon; pub(self) use crate::platform_impl::Fullscreen; diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs index 1157a4f2d0..758ebac0e8 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -3,9 +3,8 @@ use super::event_handle::EventListenerHandle; use super::media_query_handle::MediaQueryListHandle; use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize}; use crate::error::OsError as RootOE; -use crate::event::{ - Force, ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode, -}; +use crate::event::{Force, MouseButton, MouseScrollDelta}; +use crate::keyboard::{Key, KeyCode, KeyLocation, ModifiersState}; use crate::platform_impl::{OsError, PlatformSpecificWindowBuilderAttributes}; use std::cell::RefCell; @@ -13,7 +12,7 @@ use std::rc::Rc; use wasm_bindgen::{closure::Closure, JsCast}; use web_sys::{ - AddEventListenerOptions, Event, FocusEvent, HtmlCanvasElement, KeyboardEvent, + AddEventListenerOptions, CompositionEvent, Event, FocusEvent, HtmlCanvasElement, KeyboardEvent, MediaQueryListEvent, MouseEvent, WheelEvent, }; @@ -29,7 +28,7 @@ pub struct Canvas { on_blur: Option>, on_keyboard_release: Option>, on_keyboard_press: Option>, - on_received_character: Option>, + on_composition_end: Option>, on_mouse_wheel: Option>, on_fullscreen_change: Option>, on_dark_mode: Option, @@ -89,7 +88,7 @@ impl Canvas { on_focus: None, on_keyboard_release: None, on_keyboard_press: None, - on_received_character: None, + on_composition_end: None, on_mouse_wheel: None, on_fullscreen_change: None, on_dark_mode: None, @@ -174,7 +173,8 @@ impl Canvas { pub fn on_keyboard_release(&mut self, mut handler: F, prevent_default: bool) where - F: 'static + FnMut(ScanCode, Option, ModifiersState), + F: 'static + + FnMut(KeyCode, Key<'static>, Option<&'static str>, KeyLocation, bool, ModifiersState), { self.on_keyboard_release = Some(self.common.add_user_event( "keyup", @@ -182,11 +182,15 @@ impl Canvas { if prevent_default { event.prevent_default(); } - + let key = event::key(&event); + let modifiers = event::keyboard_modifiers(&key); handler( - event::scan_code(&event), - event::virtual_key_code(&event), - event::keyboard_modifiers(&event), + event::key_code(&event), + key, + event::key_text(&event), + event::key_location(&event), + event.repeat(), + modifiers, ); }, )); @@ -194,56 +198,29 @@ impl Canvas { pub fn on_keyboard_press(&mut self, mut handler: F, prevent_default: bool) where - F: 'static + FnMut(ScanCode, Option, ModifiersState), + F: 'static + + FnMut(KeyCode, Key<'static>, Option<&'static str>, KeyLocation, bool, ModifiersState), { self.on_keyboard_press = Some(self.common.add_user_event( "keydown", move |event: KeyboardEvent| { - // event.prevent_default() would suppress subsequent on_received_character() calls. That - // suppression is correct for key sequences like Tab/Shift-Tab, Ctrl+R, PgUp/Down to - // scroll, etc. We should not do it for key sequences that result in meaningful character - // input though. if prevent_default { - let event_key = &event.key(); - let is_key_string = event_key.len() == 1 || !event_key.is_ascii(); - let is_shortcut_modifiers = - (event.ctrl_key() || event.alt_key()) && !event.get_modifier_state("AltGr"); - if !is_key_string || is_shortcut_modifiers { - event.prevent_default(); - } + event.prevent_default(); } - + let key = event::key(&event); + let modifiers = event::keyboard_modifiers(&key); handler( - event::scan_code(&event), - event::virtual_key_code(&event), - event::keyboard_modifiers(&event), + event::key_code(&event), + key, + event::key_text(&event), + event::key_location(&event), + event.repeat(), + modifiers, ); }, )); } - pub fn on_received_character(&mut self, mut handler: F, prevent_default: bool) - where - F: 'static + FnMut(char), - { - // TODO: Use `beforeinput`. - // - // The `keypress` event is deprecated, but there does not seem to be a - // viable/compatible alternative as of now. `beforeinput` is still widely - // unsupported. - self.on_received_character = Some(self.common.add_user_event( - "keypress", - move |event: KeyboardEvent| { - // Suppress further handling to stop keys like the space key from scrolling the page. - if prevent_default { - event.prevent_default(); - } - - handler(event::codepoint(&event)); - }, - )); - } - pub fn on_cursor_leave(&mut self, handler: F) where F: 'static + FnMut(i32), @@ -366,7 +343,6 @@ impl Canvas { self.on_blur = None; self.on_keyboard_release = None; self.on_keyboard_press = None; - self.on_received_character = None; self.on_mouse_wheel = None; self.on_fullscreen_change = None; self.on_dark_mode = None; diff --git a/src/platform_impl/web/web_sys/canvas/mouse_handler.rs b/src/platform_impl/web/web_sys/canvas/mouse_handler.rs index 1effb455c7..b7bca9ce12 100644 --- a/src/platform_impl/web/web_sys/canvas/mouse_handler.rs +++ b/src/platform_impl/web/web_sys/canvas/mouse_handler.rs @@ -1,7 +1,8 @@ use super::event; use super::EventListenerHandle; use crate::dpi::PhysicalPosition; -use crate::event::{ModifiersState, MouseButton}; +use crate::event::MouseButton; +use crate::keyboard::ModifiersState; use std::cell::RefCell; use std::rc::Rc; diff --git a/src/platform_impl/web/web_sys/canvas/pointer_handler.rs b/src/platform_impl/web/web_sys/canvas/pointer_handler.rs index 43412c3b8b..c6f4fbf320 100644 --- a/src/platform_impl/web/web_sys/canvas/pointer_handler.rs +++ b/src/platform_impl/web/web_sys/canvas/pointer_handler.rs @@ -1,8 +1,8 @@ use super::event; use super::EventListenerHandle; use crate::dpi::PhysicalPosition; -use crate::event::Force; -use crate::event::{ModifiersState, MouseButton}; +use crate::event::{Force, MouseButton}; +use crate::keyboard::ModifiersState; use web_sys::PointerEvent; diff --git a/src/platform_impl/web/web_sys/event.rs b/src/platform_impl/web/web_sys/event.rs index a75c0e1b82..20844e6f9c 100644 --- a/src/platform_impl/web/web_sys/event.rs +++ b/src/platform_impl/web/web_sys/event.rs @@ -1,5 +1,6 @@ use crate::dpi::LogicalPosition; -use crate::event::{ModifiersState, MouseButton, MouseScrollDelta, ScanCode, VirtualKeyCode}; +use crate::event::{MouseButton, MouseScrollDelta}; +use crate::keyboard::{Key, KeyCode, KeyLocation, ModifiersState}; use std::convert::TryInto; use web_sys::{HtmlCanvasElement, KeyboardEvent, MouseEvent, PointerEvent, WheelEvent}; @@ -16,9 +17,9 @@ pub fn mouse_button(event: &MouseEvent) -> MouseButton { pub fn mouse_modifiers(event: &MouseEvent) -> ModifiersState { let mut m = ModifiersState::empty(); m.set(ModifiersState::SHIFT, event.shift_key()); - m.set(ModifiersState::CTRL, event.ctrl_key()); + m.set(ModifiersState::CONTROL, event.ctrl_key()); m.set(ModifiersState::ALT, event.alt_key()); - m.set(ModifiersState::LOGO, event.meta_key()); + m.set(ModifiersState::SUPER, event.meta_key()); m } @@ -61,192 +62,83 @@ pub fn mouse_scroll_delta(event: &WheelEvent) -> Option { } } -pub fn scan_code(event: &KeyboardEvent) -> ScanCode { - match event.key_code() { - 0 => event.char_code(), - i => i, +pub fn key_code(event: &KeyboardEvent) -> KeyCode { + let code = event.code(); + KeyCode::from_key_code_attribute_value(&code) +} + +pub fn key(event: &KeyboardEvent) -> Key<'static> { + let key = event.key(); + let key = cached_string(key); + Key::from_key_attribute_value(key) +} + +pub fn key_text(event: &KeyboardEvent) -> Option<&'static str> { + let key = event.key(); + let key = Key::from_key_attribute_value(&key); + match &key { + Key::Character(text) => Some(*text), + Key::Tab => Some("\t"), + Key::Enter => Some("\r"), + Key::Space => Some(" "), + _ => None, } + .map(cached_string) } -pub fn virtual_key_code(event: &KeyboardEvent) -> Option { - Some(match &event.code()[..] { - "Digit1" => VirtualKeyCode::Key1, - "Digit2" => VirtualKeyCode::Key2, - "Digit3" => VirtualKeyCode::Key3, - "Digit4" => VirtualKeyCode::Key4, - "Digit5" => VirtualKeyCode::Key5, - "Digit6" => VirtualKeyCode::Key6, - "Digit7" => VirtualKeyCode::Key7, - "Digit8" => VirtualKeyCode::Key8, - "Digit9" => VirtualKeyCode::Key9, - "Digit0" => VirtualKeyCode::Key0, - "KeyA" => VirtualKeyCode::A, - "KeyB" => VirtualKeyCode::B, - "KeyC" => VirtualKeyCode::C, - "KeyD" => VirtualKeyCode::D, - "KeyE" => VirtualKeyCode::E, - "KeyF" => VirtualKeyCode::F, - "KeyG" => VirtualKeyCode::G, - "KeyH" => VirtualKeyCode::H, - "KeyI" => VirtualKeyCode::I, - "KeyJ" => VirtualKeyCode::J, - "KeyK" => VirtualKeyCode::K, - "KeyL" => VirtualKeyCode::L, - "KeyM" => VirtualKeyCode::M, - "KeyN" => VirtualKeyCode::N, - "KeyO" => VirtualKeyCode::O, - "KeyP" => VirtualKeyCode::P, - "KeyQ" => VirtualKeyCode::Q, - "KeyR" => VirtualKeyCode::R, - "KeyS" => VirtualKeyCode::S, - "KeyT" => VirtualKeyCode::T, - "KeyU" => VirtualKeyCode::U, - "KeyV" => VirtualKeyCode::V, - "KeyW" => VirtualKeyCode::W, - "KeyX" => VirtualKeyCode::X, - "KeyY" => VirtualKeyCode::Y, - "KeyZ" => VirtualKeyCode::Z, - "Escape" => VirtualKeyCode::Escape, - "F1" => VirtualKeyCode::F1, - "F2" => VirtualKeyCode::F2, - "F3" => VirtualKeyCode::F3, - "F4" => VirtualKeyCode::F4, - "F5" => VirtualKeyCode::F5, - "F6" => VirtualKeyCode::F6, - "F7" => VirtualKeyCode::F7, - "F8" => VirtualKeyCode::F8, - "F9" => VirtualKeyCode::F9, - "F10" => VirtualKeyCode::F10, - "F11" => VirtualKeyCode::F11, - "F12" => VirtualKeyCode::F12, - "F13" => VirtualKeyCode::F13, - "F14" => VirtualKeyCode::F14, - "F15" => VirtualKeyCode::F15, - "F16" => VirtualKeyCode::F16, - "F17" => VirtualKeyCode::F17, - "F18" => VirtualKeyCode::F18, - "F19" => VirtualKeyCode::F19, - "F20" => VirtualKeyCode::F20, - "F21" => VirtualKeyCode::F21, - "F22" => VirtualKeyCode::F22, - "F23" => VirtualKeyCode::F23, - "F24" => VirtualKeyCode::F24, - "PrintScreen" => VirtualKeyCode::Snapshot, - "ScrollLock" => VirtualKeyCode::Scroll, - "Pause" => VirtualKeyCode::Pause, - "Insert" => VirtualKeyCode::Insert, - "Home" => VirtualKeyCode::Home, - "Delete" => VirtualKeyCode::Delete, - "End" => VirtualKeyCode::End, - "PageDown" => VirtualKeyCode::PageDown, - "PageUp" => VirtualKeyCode::PageUp, - "ArrowLeft" => VirtualKeyCode::Left, - "ArrowUp" => VirtualKeyCode::Up, - "ArrowRight" => VirtualKeyCode::Right, - "ArrowDown" => VirtualKeyCode::Down, - "Backspace" => VirtualKeyCode::Back, - "Enter" => VirtualKeyCode::Return, - "Space" => VirtualKeyCode::Space, - "Compose" => VirtualKeyCode::Compose, - "Caret" => VirtualKeyCode::Caret, - "NumLock" => VirtualKeyCode::Numlock, - "Numpad0" => VirtualKeyCode::Numpad0, - "Numpad1" => VirtualKeyCode::Numpad1, - "Numpad2" => VirtualKeyCode::Numpad2, - "Numpad3" => VirtualKeyCode::Numpad3, - "Numpad4" => VirtualKeyCode::Numpad4, - "Numpad5" => VirtualKeyCode::Numpad5, - "Numpad6" => VirtualKeyCode::Numpad6, - "Numpad7" => VirtualKeyCode::Numpad7, - "Numpad8" => VirtualKeyCode::Numpad8, - "Numpad9" => VirtualKeyCode::Numpad9, - "AbntC1" => VirtualKeyCode::AbntC1, - "AbntC2" => VirtualKeyCode::AbntC2, - "NumpadAdd" => VirtualKeyCode::NumpadAdd, - "Quote" => VirtualKeyCode::Apostrophe, - "Apps" => VirtualKeyCode::Apps, - "At" => VirtualKeyCode::At, - "Ax" => VirtualKeyCode::Ax, - "Backslash" => VirtualKeyCode::Backslash, - "Calculator" => VirtualKeyCode::Calculator, - "Capital" => VirtualKeyCode::Capital, - "Semicolon" => VirtualKeyCode::Semicolon, - "Comma" => VirtualKeyCode::Comma, - "Convert" => VirtualKeyCode::Convert, - "NumpadDecimal" => VirtualKeyCode::NumpadDecimal, - "NumpadDivide" => VirtualKeyCode::NumpadDivide, - "Equal" => VirtualKeyCode::Equals, - "Backquote" => VirtualKeyCode::Grave, - "Kana" => VirtualKeyCode::Kana, - "Kanji" => VirtualKeyCode::Kanji, - "AltLeft" => VirtualKeyCode::LAlt, - "BracketLeft" => VirtualKeyCode::LBracket, - "ControlLeft" => VirtualKeyCode::LControl, - "ShiftLeft" => VirtualKeyCode::LShift, - "MetaLeft" => VirtualKeyCode::LWin, - "Mail" => VirtualKeyCode::Mail, - "MediaSelect" => VirtualKeyCode::MediaSelect, - "MediaStop" => VirtualKeyCode::MediaStop, - "Minus" => VirtualKeyCode::Minus, - "NumpadMultiply" => VirtualKeyCode::NumpadMultiply, - "Mute" => VirtualKeyCode::Mute, - "LaunchMyComputer" => VirtualKeyCode::MyComputer, - "NavigateForward" => VirtualKeyCode::NavigateForward, - "NavigateBackward" => VirtualKeyCode::NavigateBackward, - "NextTrack" => VirtualKeyCode::NextTrack, - "NoConvert" => VirtualKeyCode::NoConvert, - "NumpadComma" => VirtualKeyCode::NumpadComma, - "NumpadEnter" => VirtualKeyCode::NumpadEnter, - "NumpadEquals" => VirtualKeyCode::NumpadEquals, - "OEM102" => VirtualKeyCode::OEM102, - "Period" => VirtualKeyCode::Period, - "PlayPause" => VirtualKeyCode::PlayPause, - "Power" => VirtualKeyCode::Power, - "PrevTrack" => VirtualKeyCode::PrevTrack, - "AltRight" => VirtualKeyCode::RAlt, - "BracketRight" => VirtualKeyCode::RBracket, - "ControlRight" => VirtualKeyCode::RControl, - "ShiftRight" => VirtualKeyCode::RShift, - "MetaRight" => VirtualKeyCode::RWin, - "Slash" => VirtualKeyCode::Slash, - "Sleep" => VirtualKeyCode::Sleep, - "Stop" => VirtualKeyCode::Stop, - "NumpadSubtract" => VirtualKeyCode::NumpadSubtract, - "Sysrq" => VirtualKeyCode::Sysrq, - "Tab" => VirtualKeyCode::Tab, - "Underline" => VirtualKeyCode::Underline, - "Unlabeled" => VirtualKeyCode::Unlabeled, - "AudioVolumeDown" => VirtualKeyCode::VolumeDown, - "AudioVolumeUp" => VirtualKeyCode::VolumeUp, - "Wake" => VirtualKeyCode::Wake, - "WebBack" => VirtualKeyCode::WebBack, - "WebFavorites" => VirtualKeyCode::WebFavorites, - "WebForward" => VirtualKeyCode::WebForward, - "WebHome" => VirtualKeyCode::WebHome, - "WebRefresh" => VirtualKeyCode::WebRefresh, - "WebSearch" => VirtualKeyCode::WebSearch, - "WebStop" => VirtualKeyCode::WebStop, - "Yen" => VirtualKeyCode::Yen, - _ => return None, - }) +fn cached_string>(string: S) -> &'static str { + use std::collections::HashSet; + use std::sync::Mutex; + + use once_cell::sync::Lazy; + + static STRING_CACHE: Lazy>> = + Lazy::new(|| Mutex::new(HashSet::new())); + + let string = string.as_ref(); + let mut cache = STRING_CACHE.lock().unwrap(); + if let Some(string) = cache.get(string) { + string + } else { + // borrowck couldn't quite figure out this one on its own + let string: &'static str = Box::leak(String::from(string).into_boxed_str()); + cache.insert(string); + string + } } -pub fn keyboard_modifiers(event: &KeyboardEvent) -> ModifiersState { - let mut m = ModifiersState::empty(); - m.set(ModifiersState::SHIFT, event.shift_key()); - m.set(ModifiersState::CTRL, event.ctrl_key()); - m.set(ModifiersState::ALT, event.alt_key()); - m.set(ModifiersState::LOGO, event.meta_key()); - m +pub fn key_location(event: &KeyboardEvent) -> KeyLocation { + let location = event.location(); + // As defined in the UIEvents specification + // https://w3c.github.io/uievents/#idl-keyboardevent + match location { + 0 => KeyLocation::Standard, + 1 => KeyLocation::Left, + 2 => KeyLocation::Right, + 3 => KeyLocation::Numpad, + _ => KeyLocation::Standard, + } } -pub fn codepoint(event: &KeyboardEvent) -> char { - // `event.key()` always returns a non-empty `String`. Therefore, this should - // never panic. - // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key - event.key().chars().next().unwrap() +// TODO: What should be done about `KeyboardEvent.isComposing`? + +pub fn keyboard_modifiers(key: &Key<'_>) -> ModifiersState { + match key { + Key::Shift => ModifiersState::SHIFT, + Key::Control => ModifiersState::CONTROL, + Key::Alt => ModifiersState::ALT, + Key::Super => ModifiersState::SUPER, + _ => ModifiersState::empty(), + } } +// pub fn codepoint(event: &KeyboardEvent) -> char { +// // `event.key()` always returns a non-empty `String`. Therefore, this should +// // never panic. +// // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key +// event.key().chars().next().unwrap() +// } + pub fn touch_position(event: &PointerEvent, _canvas: &HtmlCanvasElement) -> LogicalPosition { // TODO: Should this handle more, like `mouse_position_by_client` does? LogicalPosition { diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index a46a0b80c6..ba603e8e6b 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -433,6 +433,10 @@ impl Window { pub fn title(&self) -> String { String::new() } + + pub fn reset_dead_keys(&self) { + // Not supported + } } impl Drop for Window { From f6a4df8540a9bd916366a646a96d30cf79eedb6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Art=C3=BAr=20Kov=C3=A1cs?= Date: Fri, 9 Dec 2022 11:53:24 +0100 Subject: [PATCH 06/13] Implement the new keyboard API for Android --- Cargo.toml | 1 + src/platform_impl/android/mod.rs | 597 ++++++++++++++++++++++--------- 2 files changed, 421 insertions(+), 177 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 71069abd03..e0a2458fcf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,6 +66,7 @@ simple_logger = { version = "2.1.0", default_features = false } # Coordinate the next winit release with android-ndk-rs: https://github.com/rust-windowing/winit/issues/1995 android-activity = "0.4.0" ndk = "0.7.0" +ndk-sys = "0.4.0" [target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies] core-foundation = "0.9.3" diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index 2e1924e27b..21aeafb08a 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -2,6 +2,7 @@ use std::{ collections::VecDeque, + convert::TryInto, hash::Hash, sync::{ atomic::{AtomicBool, Ordering}, @@ -10,7 +11,7 @@ use std::{ time::{Duration, Instant}, }; -use android_activity::input::{InputEvent, KeyAction, Keycode, MotionAction}; +use android_activity::input::{InputEvent, KeyAction, MotionAction}; use android_activity::{ AndroidApp, AndroidAppWaker, ConfigurationRef, InputStatus, MainEvent, Rect, }; @@ -19,12 +20,16 @@ use raw_window_handle::{ AndroidDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle, }; +#[cfg(feature = "android-native-activity")] +use ndk_sys::AKeyEvent_getKeyCode; + use crate::platform_impl::Fullscreen; use crate::{ dpi::{PhysicalPosition, PhysicalSize, Position, Size}, error, - event::{self, StartCause, VirtualKeyCode}, + event::{self, StartCause}, event_loop::{self, ControlFlow, EventLoopWindowTarget as RootELW}, + keyboard::{Key, KeyCode, KeyLocation, NativeKey, NativeKeyCode}, window::{ self, CursorGrabMode, ImePurpose, ResizeDirection, Theme, WindowButtons, WindowLevel, }, @@ -32,170 +37,6 @@ use crate::{ static HAS_FOCUS: Lazy> = Lazy::new(|| RwLock::new(true)); -fn ndk_keycode_to_virtualkeycode(keycode: Keycode) -> Option { - match keycode { - Keycode::A => Some(VirtualKeyCode::A), - Keycode::B => Some(VirtualKeyCode::B), - Keycode::C => Some(VirtualKeyCode::C), - Keycode::D => Some(VirtualKeyCode::D), - Keycode::E => Some(VirtualKeyCode::E), - Keycode::F => Some(VirtualKeyCode::F), - Keycode::G => Some(VirtualKeyCode::G), - Keycode::H => Some(VirtualKeyCode::H), - Keycode::I => Some(VirtualKeyCode::I), - Keycode::J => Some(VirtualKeyCode::J), - Keycode::K => Some(VirtualKeyCode::K), - Keycode::L => Some(VirtualKeyCode::L), - Keycode::M => Some(VirtualKeyCode::M), - Keycode::N => Some(VirtualKeyCode::N), - Keycode::O => Some(VirtualKeyCode::O), - Keycode::P => Some(VirtualKeyCode::P), - Keycode::Q => Some(VirtualKeyCode::Q), - Keycode::R => Some(VirtualKeyCode::R), - Keycode::S => Some(VirtualKeyCode::S), - Keycode::T => Some(VirtualKeyCode::T), - Keycode::U => Some(VirtualKeyCode::U), - Keycode::V => Some(VirtualKeyCode::V), - Keycode::W => Some(VirtualKeyCode::W), - Keycode::X => Some(VirtualKeyCode::X), - Keycode::Y => Some(VirtualKeyCode::Y), - Keycode::Z => Some(VirtualKeyCode::Z), - - Keycode::Keycode0 => Some(VirtualKeyCode::Key0), - Keycode::Keycode1 => Some(VirtualKeyCode::Key1), - Keycode::Keycode2 => Some(VirtualKeyCode::Key2), - Keycode::Keycode3 => Some(VirtualKeyCode::Key3), - Keycode::Keycode4 => Some(VirtualKeyCode::Key4), - Keycode::Keycode5 => Some(VirtualKeyCode::Key5), - Keycode::Keycode6 => Some(VirtualKeyCode::Key6), - Keycode::Keycode7 => Some(VirtualKeyCode::Key7), - Keycode::Keycode8 => Some(VirtualKeyCode::Key8), - Keycode::Keycode9 => Some(VirtualKeyCode::Key9), - - Keycode::Numpad0 => Some(VirtualKeyCode::Numpad0), - Keycode::Numpad1 => Some(VirtualKeyCode::Numpad1), - Keycode::Numpad2 => Some(VirtualKeyCode::Numpad2), - Keycode::Numpad3 => Some(VirtualKeyCode::Numpad3), - Keycode::Numpad4 => Some(VirtualKeyCode::Numpad4), - Keycode::Numpad5 => Some(VirtualKeyCode::Numpad5), - Keycode::Numpad6 => Some(VirtualKeyCode::Numpad6), - Keycode::Numpad7 => Some(VirtualKeyCode::Numpad7), - Keycode::Numpad8 => Some(VirtualKeyCode::Numpad8), - Keycode::Numpad9 => Some(VirtualKeyCode::Numpad9), - - Keycode::NumpadAdd => Some(VirtualKeyCode::NumpadAdd), - Keycode::NumpadSubtract => Some(VirtualKeyCode::NumpadSubtract), - Keycode::NumpadMultiply => Some(VirtualKeyCode::NumpadMultiply), - Keycode::NumpadDivide => Some(VirtualKeyCode::NumpadDivide), - Keycode::NumpadEnter => Some(VirtualKeyCode::NumpadEnter), - Keycode::NumpadEquals => Some(VirtualKeyCode::NumpadEquals), - Keycode::NumpadComma => Some(VirtualKeyCode::NumpadComma), - Keycode::NumpadDot => Some(VirtualKeyCode::NumpadDecimal), - Keycode::NumLock => Some(VirtualKeyCode::Numlock), - - Keycode::DpadLeft => Some(VirtualKeyCode::Left), - Keycode::DpadRight => Some(VirtualKeyCode::Right), - Keycode::DpadUp => Some(VirtualKeyCode::Up), - Keycode::DpadDown => Some(VirtualKeyCode::Down), - - Keycode::F1 => Some(VirtualKeyCode::F1), - Keycode::F2 => Some(VirtualKeyCode::F2), - Keycode::F3 => Some(VirtualKeyCode::F3), - Keycode::F4 => Some(VirtualKeyCode::F4), - Keycode::F5 => Some(VirtualKeyCode::F5), - Keycode::F6 => Some(VirtualKeyCode::F6), - Keycode::F7 => Some(VirtualKeyCode::F7), - Keycode::F8 => Some(VirtualKeyCode::F8), - Keycode::F9 => Some(VirtualKeyCode::F9), - Keycode::F10 => Some(VirtualKeyCode::F10), - Keycode::F11 => Some(VirtualKeyCode::F11), - Keycode::F12 => Some(VirtualKeyCode::F12), - - Keycode::Space => Some(VirtualKeyCode::Space), - Keycode::Escape => Some(VirtualKeyCode::Escape), - Keycode::Enter => Some(VirtualKeyCode::Return), // not on the Numpad - Keycode::Tab => Some(VirtualKeyCode::Tab), - - Keycode::PageUp => Some(VirtualKeyCode::PageUp), - Keycode::PageDown => Some(VirtualKeyCode::PageDown), - Keycode::MoveHome => Some(VirtualKeyCode::Home), - Keycode::MoveEnd => Some(VirtualKeyCode::End), - Keycode::Insert => Some(VirtualKeyCode::Insert), - - Keycode::Del => Some(VirtualKeyCode::Back), // Backspace (above Enter) - Keycode::ForwardDel => Some(VirtualKeyCode::Delete), // Delete (below Insert) - - Keycode::Copy => Some(VirtualKeyCode::Copy), - Keycode::Paste => Some(VirtualKeyCode::Paste), - Keycode::Cut => Some(VirtualKeyCode::Cut), - - Keycode::VolumeUp => Some(VirtualKeyCode::VolumeUp), - Keycode::VolumeDown => Some(VirtualKeyCode::VolumeDown), - Keycode::VolumeMute => Some(VirtualKeyCode::Mute), // ??? - Keycode::Mute => Some(VirtualKeyCode::Mute), // ??? - Keycode::MediaPlayPause => Some(VirtualKeyCode::PlayPause), - Keycode::MediaStop => Some(VirtualKeyCode::MediaStop), // ??? simple "Stop"? - Keycode::MediaNext => Some(VirtualKeyCode::NextTrack), - Keycode::MediaPrevious => Some(VirtualKeyCode::PrevTrack), - - Keycode::Plus => Some(VirtualKeyCode::Plus), - Keycode::Minus => Some(VirtualKeyCode::Minus), - Keycode::Equals => Some(VirtualKeyCode::Equals), - Keycode::Semicolon => Some(VirtualKeyCode::Semicolon), - Keycode::Slash => Some(VirtualKeyCode::Slash), - Keycode::Backslash => Some(VirtualKeyCode::Backslash), - Keycode::Comma => Some(VirtualKeyCode::Comma), - Keycode::Period => Some(VirtualKeyCode::Period), - Keycode::Apostrophe => Some(VirtualKeyCode::Apostrophe), - Keycode::Grave => Some(VirtualKeyCode::Grave), - Keycode::At => Some(VirtualKeyCode::At), - - // TODO: Maybe mapping this to Snapshot makes more sense? See: "PrtScr/SysRq" - Keycode::Sysrq => Some(VirtualKeyCode::Sysrq), - // These are usually the same (Pause/Break) - Keycode::Break => Some(VirtualKeyCode::Pause), - // These are exactly the same - Keycode::ScrollLock => Some(VirtualKeyCode::Scroll), - - Keycode::Yen => Some(VirtualKeyCode::Yen), - Keycode::Kana => Some(VirtualKeyCode::Kana), - - Keycode::CtrlLeft => Some(VirtualKeyCode::LControl), - Keycode::CtrlRight => Some(VirtualKeyCode::RControl), - - Keycode::ShiftLeft => Some(VirtualKeyCode::LShift), - Keycode::ShiftRight => Some(VirtualKeyCode::RShift), - - Keycode::AltLeft => Some(VirtualKeyCode::LAlt), - Keycode::AltRight => Some(VirtualKeyCode::RAlt), - - // Different names for the same keys - Keycode::MetaLeft => Some(VirtualKeyCode::LWin), - Keycode::MetaRight => Some(VirtualKeyCode::RWin), - - Keycode::LeftBracket => Some(VirtualKeyCode::LBracket), - Keycode::RightBracket => Some(VirtualKeyCode::RBracket), - - Keycode::Power => Some(VirtualKeyCode::Power), - Keycode::Sleep => Some(VirtualKeyCode::Sleep), // what about SoftSleep? - Keycode::Wakeup => Some(VirtualKeyCode::Wake), - - Keycode::NavigateNext => Some(VirtualKeyCode::NavigateForward), - Keycode::NavigatePrevious => Some(VirtualKeyCode::NavigateBackward), - - Keycode::Calculator => Some(VirtualKeyCode::Calculator), - Keycode::Explorer => Some(VirtualKeyCode::MyComputer), // "close enough" - Keycode::Envelope => Some(VirtualKeyCode::Mail), // "close enough" - - Keycode::Star => Some(VirtualKeyCode::Asterisk), // ??? - Keycode::AllApps => Some(VirtualKeyCode::Apps), // ??? - Keycode::AppSwitch => Some(VirtualKeyCode::Apps), // ??? - Keycode::Refresh => Some(VirtualKeyCode::WebRefresh), // ??? - - _ => None, - } -} - struct PeekableReceiver { recv: mpsc::Receiver, first: Option, @@ -287,6 +128,9 @@ impl RedrawRequester { } } +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct KeyEventExtra {} + pub struct EventLoop { android_app: AndroidApp, window_target: event_loop::EventLoopWindowTarget, @@ -551,25 +395,48 @@ impl EventLoop { } } InputEvent::KeyEvent(key) => { - let device_id = event::DeviceId(DeviceId); - let state = match key.action() { KeyAction::Down => event::ElementState::Pressed, KeyAction::Up => event::ElementState::Released, _ => event::ElementState::Released, }; - #[allow(deprecated)] + + #[cfg(feature = "android-native-activity")] + let (keycode_u32, scancode_u32) = unsafe { + // We abuse the fact that `android_activity`'s `KeyEvent` is `repr(transparent)` + let event = (key as *const android_activity::input::KeyEvent<'_>).cast::(); + // We use the unsafe function directly because we want to forward the + // keycode value even if it doesn't have a variant defined in the ndk + // crate. + ( + AKeyEvent_getKeyCode((*event).ptr().as_ptr()) as u32, + (*event).scan_code() as u32 + ) + }; + #[cfg(feature = "android-game-activity")] + let (keycode_u32, scancode_u32) = (key.keyCode as u32, key.scanCode as u32); + let keycode = keycode_u32 + .try_into() + .unwrap_or(ndk::event::Keycode::Unknown); + let physical_key = KeyCode::Unidentified( + NativeKeyCode::Android(scancode_u32), + ); + let native = NativeKey::Android(keycode_u32); + let logical_key = keycode_to_logical(keycode, native); + // TODO: maybe use getUnicodeChar to get the logical key + let event = event::Event::WindowEvent { window_id: window::WindowId(WindowId), event: event::WindowEvent::KeyboardInput { - device_id, - input: event::KeyboardInput { - scancode: key.scan_code() as u32, + device_id: event::DeviceId(DeviceId), + event: event::KeyEvent { state, - virtual_keycode: ndk_keycode_to_virtualkeycode( - key.key_code(), - ), - modifiers: event::ModifiersState::default(), + physical_key, + logical_key, + location: keycode_to_location(keycode), + repeat: key.repeat_count() > 0, + text: None, + platform_specific: KeyEventExtra {}, }, is_synthetic: false, }, @@ -578,7 +445,7 @@ impl EventLoop { event, self.window_target(), control_flow, - callback + callback, ); } _ => { @@ -1084,6 +951,8 @@ impl Window { pub fn title(&self) -> String { String::new() } + + pub fn reset_dead_keys(&self) {} } #[derive(Default, Clone, Debug)] @@ -1185,3 +1054,377 @@ impl VideoMode { self.monitor.clone() } } + +fn keycode_to_logical(keycode: ndk::event::Keycode, native: NativeKey) -> Key<'static> { + use ndk::event::Keycode::*; + + // The android `Keycode` is sort-of layout dependent. More specifically + // if I press the Z key using a US layout, then I get KEYCODE_Z, + // but if I press the same key after switching to a HUN layout, I get + // KEYCODE_Y. + // + // To prevents us from using this value to determine the `physical_key` + // (also know as winit's `KeyCode`) + // + // Unfortunately the documentation says that the scancode values + // "are not reliable and vary from device to device". Which seems to mean + // that there's no way to reliably get the physical_key on android. + + match keycode { + Unknown => Key::Unidentified(native), + + // Can be added on demand + SoftLeft => Key::Unidentified(native), + SoftRight => Key::Unidentified(native), + + // Using `BrowserHome` instead of `GoHome` according to + // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values + Home => Key::BrowserHome, + Back => Key::BrowserBack, + Call => Key::Call, + Endcall => Key::EndCall, + + //------------------------------------------------------------------------------- + // Reporting unidentified, because the specific character is layout dependent. + // (I'm not sure though) + Keycode0 => Key::Unidentified(native), + Keycode1 => Key::Unidentified(native), + Keycode2 => Key::Unidentified(native), + Keycode3 => Key::Unidentified(native), + Keycode4 => Key::Unidentified(native), + Keycode5 => Key::Unidentified(native), + Keycode6 => Key::Unidentified(native), + Keycode7 => Key::Unidentified(native), + Keycode8 => Key::Unidentified(native), + Keycode9 => Key::Unidentified(native), + Star => Key::Unidentified(native), + Pound => Key::Unidentified(native), + A => Key::Unidentified(native), + B => Key::Unidentified(native), + C => Key::Unidentified(native), + D => Key::Unidentified(native), + E => Key::Unidentified(native), + F => Key::Unidentified(native), + G => Key::Unidentified(native), + H => Key::Unidentified(native), + I => Key::Unidentified(native), + J => Key::Unidentified(native), + K => Key::Unidentified(native), + L => Key::Unidentified(native), + M => Key::Unidentified(native), + N => Key::Unidentified(native), + O => Key::Unidentified(native), + P => Key::Unidentified(native), + Q => Key::Unidentified(native), + R => Key::Unidentified(native), + S => Key::Unidentified(native), + T => Key::Unidentified(native), + U => Key::Unidentified(native), + V => Key::Unidentified(native), + W => Key::Unidentified(native), + X => Key::Unidentified(native), + Y => Key::Unidentified(native), + Z => Key::Unidentified(native), + Comma => Key::Unidentified(native), + Period => Key::Unidentified(native), + Grave => Key::Unidentified(native), + Minus => Key::Unidentified(native), + Equals => Key::Unidentified(native), + LeftBracket => Key::Unidentified(native), + RightBracket => Key::Unidentified(native), + Backslash => Key::Unidentified(native), + Semicolon => Key::Unidentified(native), + Apostrophe => Key::Unidentified(native), + Slash => Key::Unidentified(native), + At => Key::Unidentified(native), + Plus => Key::Unidentified(native), + //------------------------------------------------------------------------------- + DpadUp => Key::ArrowUp, + DpadDown => Key::ArrowDown, + DpadLeft => Key::ArrowLeft, + DpadRight => Key::ArrowRight, + DpadCenter => Key::Enter, + + VolumeUp => Key::AudioVolumeUp, + VolumeDown => Key::AudioVolumeDown, + Power => Key::Power, + Camera => Key::Camera, + Clear => Key::Clear, + + AltLeft => Key::Alt, + AltRight => Key::Alt, + ShiftLeft => Key::Shift, + ShiftRight => Key::Shift, + Tab => Key::Tab, + Space => Key::Space, + Sym => Key::Symbol, + Explorer => Key::LaunchWebBrowser, + Envelope => Key::LaunchMail, + Enter => Key::Enter, + Del => Key::Backspace, + + // According to https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_NUM + Num => Key::Alt, + + Headsethook => Key::HeadsetHook, + Focus => Key::CameraFocus, + + Menu => Key::Unidentified(native), + + Notification => Key::Notification, + Search => Key::BrowserSearch, + MediaPlayPause => Key::MediaPlayPause, + MediaStop => Key::MediaStop, + MediaNext => Key::MediaTrackNext, + MediaPrevious => Key::MediaTrackPrevious, + MediaRewind => Key::MediaRewind, + MediaFastForward => Key::MediaFastForward, + Mute => Key::MicrophoneVolumeMute, + PageUp => Key::PageUp, + PageDown => Key::PageDown, + Pictsymbols => Key::Unidentified(native), + SwitchCharset => Key::Unidentified(native), + + // ----------------------------------------------------------------- + // Gamepad events should be exposed through a separate API, not + // keyboard events + ButtonA => Key::Unidentified(native), + ButtonB => Key::Unidentified(native), + ButtonC => Key::Unidentified(native), + ButtonX => Key::Unidentified(native), + ButtonY => Key::Unidentified(native), + ButtonZ => Key::Unidentified(native), + ButtonL1 => Key::Unidentified(native), + ButtonR1 => Key::Unidentified(native), + ButtonL2 => Key::Unidentified(native), + ButtonR2 => Key::Unidentified(native), + ButtonThumbl => Key::Unidentified(native), + ButtonThumbr => Key::Unidentified(native), + ButtonStart => Key::Unidentified(native), + ButtonSelect => Key::Unidentified(native), + ButtonMode => Key::Unidentified(native), + // ----------------------------------------------------------------- + Escape => Key::Escape, + ForwardDel => Key::Delete, + CtrlLeft => Key::Control, + CtrlRight => Key::Control, + CapsLock => Key::CapsLock, + ScrollLock => Key::ScrollLock, + MetaLeft => Key::Super, + MetaRight => Key::Super, + Function => Key::Fn, + Sysrq => Key::PrintScreen, + Break => Key::Pause, + MoveHome => Key::Home, + MoveEnd => Key::End, + Insert => Key::Insert, + Forward => Key::BrowserForward, + MediaPlay => Key::MediaPlay, + MediaPause => Key::MediaPause, + MediaClose => Key::MediaClose, + MediaEject => Key::Eject, + MediaRecord => Key::MediaRecord, + F1 => Key::F1, + F2 => Key::F2, + F3 => Key::F3, + F4 => Key::F4, + F5 => Key::F5, + F6 => Key::F6, + F7 => Key::F7, + F8 => Key::F8, + F9 => Key::F9, + F10 => Key::F10, + F11 => Key::F11, + F12 => Key::F12, + NumLock => Key::NumLock, + Numpad0 => Key::Unidentified(native), + Numpad1 => Key::Unidentified(native), + Numpad2 => Key::Unidentified(native), + Numpad3 => Key::Unidentified(native), + Numpad4 => Key::Unidentified(native), + Numpad5 => Key::Unidentified(native), + Numpad6 => Key::Unidentified(native), + Numpad7 => Key::Unidentified(native), + Numpad8 => Key::Unidentified(native), + Numpad9 => Key::Unidentified(native), + NumpadDivide => Key::Unidentified(native), + NumpadMultiply => Key::Unidentified(native), + NumpadSubtract => Key::Unidentified(native), + NumpadAdd => Key::Unidentified(native), + NumpadDot => Key::Unidentified(native), + NumpadComma => Key::Unidentified(native), + NumpadEnter => Key::Unidentified(native), + NumpadEquals => Key::Unidentified(native), + NumpadLeftParen => Key::Unidentified(native), + NumpadRightParen => Key::Unidentified(native), + + VolumeMute => Key::AudioVolumeMute, + Info => Key::Info, + ChannelUp => Key::ChannelUp, + ChannelDown => Key::ChannelDown, + ZoomIn => Key::ZoomIn, + ZoomOut => Key::ZoomOut, + Tv => Key::TV, + Window => Key::Unidentified(native), + Guide => Key::Guide, + Dvr => Key::DVR, + Bookmark => Key::BrowserFavorites, + Captions => Key::ClosedCaptionToggle, + Settings => Key::Settings, + TvPower => Key::TVPower, + TvInput => Key::TVInput, + StbPower => Key::STBPower, + StbInput => Key::STBInput, + AvrPower => Key::AVRPower, + AvrInput => Key::AVRInput, + ProgRed => Key::ColorF0Red, + ProgGreen => Key::ColorF1Green, + ProgYellow => Key::ColorF2Yellow, + ProgBlue => Key::ColorF3Blue, + AppSwitch => Key::AppSwitch, + Button1 => Key::Unidentified(native), + Button2 => Key::Unidentified(native), + Button3 => Key::Unidentified(native), + Button4 => Key::Unidentified(native), + Button5 => Key::Unidentified(native), + Button6 => Key::Unidentified(native), + Button7 => Key::Unidentified(native), + Button8 => Key::Unidentified(native), + Button9 => Key::Unidentified(native), + Button10 => Key::Unidentified(native), + Button11 => Key::Unidentified(native), + Button12 => Key::Unidentified(native), + Button13 => Key::Unidentified(native), + Button14 => Key::Unidentified(native), + Button15 => Key::Unidentified(native), + Button16 => Key::Unidentified(native), + LanguageSwitch => Key::GroupNext, + MannerMode => Key::MannerMode, + Keycode3dMode => Key::TV3DMode, + Contacts => Key::LaunchContacts, + Calendar => Key::LaunchCalendar, + Music => Key::LaunchMusicPlayer, + Calculator => Key::LaunchApplication2, + ZenkakuHankaku => Key::ZenkakuHankaku, + Eisu => Key::Eisu, + Muhenkan => Key::NonConvert, + Henkan => Key::Convert, + KatakanaHiragana => Key::HiraganaKatakana, + Yen => Key::Unidentified(native), + Ro => Key::Unidentified(native), + Kana => Key::KanjiMode, + Assist => Key::Unidentified(native), + BrightnessDown => Key::BrightnessDown, + BrightnessUp => Key::BrightnessUp, + MediaAudioTrack => Key::MediaAudioTrack, + Sleep => Key::Standby, + Wakeup => Key::WakeUp, + Pairing => Key::Pairing, + MediaTopMenu => Key::MediaTopMenu, + Keycode11 => Key::Unidentified(native), + Keycode12 => Key::Unidentified(native), + LastChannel => Key::MediaLast, + TvDataService => Key::TVDataService, + VoiceAssist => Key::VoiceDial, + TvRadioService => Key::TVRadioService, + TvTeletext => Key::Teletext, + TvNumberEntry => Key::TVNumberEntry, + TvTerrestrialAnalog => Key::TVTerrestrialAnalog, + TvTerrestrialDigital => Key::TVTerrestrialDigital, + TvSatellite => Key::TVSatellite, + TvSatelliteBs => Key::TVSatelliteBS, + TvSatelliteCs => Key::TVSatelliteCS, + TvSatelliteService => Key::TVSatelliteToggle, + TvNetwork => Key::TVNetwork, + TvAntennaCable => Key::TVAntennaCable, + TvInputHdmi1 => Key::TVInputHDMI1, + TvInputHdmi2 => Key::TVInputHDMI2, + TvInputHdmi3 => Key::TVInputHDMI3, + TvInputHdmi4 => Key::TVInputHDMI4, + TvInputComposite1 => Key::TVInputComposite1, + TvInputComposite2 => Key::TVInputComposite2, + TvInputComponent1 => Key::TVInputComponent1, + TvInputComponent2 => Key::TVInputComponent2, + TvInputVga1 => Key::TVInputVGA1, + TvAudioDescription => Key::TVAudioDescription, + TvAudioDescriptionMixUp => Key::TVAudioDescriptionMixUp, + TvAudioDescriptionMixDown => Key::TVAudioDescriptionMixDown, + TvZoomMode => Key::ZoomToggle, + TvContentsMenu => Key::TVContentsMenu, + TvMediaContextMenu => Key::TVMediaContext, + TvTimerProgramming => Key::TVTimer, + Help => Key::Help, + NavigatePrevious => Key::NavigatePrevious, + NavigateNext => Key::NavigateNext, + NavigateIn => Key::NavigateIn, + NavigateOut => Key::NavigateOut, + StemPrimary => Key::Unidentified(native), + Stem1 => Key::Unidentified(native), + Stem2 => Key::Unidentified(native), + Stem3 => Key::Unidentified(native), + DpadUpLeft => Key::Unidentified(native), + DpadDownLeft => Key::Unidentified(native), + DpadUpRight => Key::Unidentified(native), + DpadDownRight => Key::Unidentified(native), + MediaSkipForward => Key::MediaSkipForward, + MediaSkipBackward => Key::MediaSkipBackward, + MediaStepForward => Key::MediaStepForward, + MediaStepBackward => Key::MediaStepBackward, + SoftSleep => Key::Unidentified(native), + Cut => Key::Cut, + Copy => Key::Copy, + Paste => Key::Paste, + SystemNavigationUp => Key::Unidentified(native), + SystemNavigationDown => Key::Unidentified(native), + SystemNavigationLeft => Key::Unidentified(native), + SystemNavigationRight => Key::Unidentified(native), + AllApps => Key::Unidentified(native), + Refresh => Key::BrowserRefresh, + ThumbsUp => Key::Unidentified(native), + ThumbsDown => Key::Unidentified(native), + ProfileSwitch => Key::Unidentified(native), + } +} + +fn keycode_to_location(keycode: ndk::event::Keycode) -> KeyLocation { + use ndk::event::Keycode::*; + + match keycode { + AltLeft => KeyLocation::Left, + AltRight => KeyLocation::Right, + ShiftLeft => KeyLocation::Left, + ShiftRight => KeyLocation::Right, + + // According to https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_NUM + Num => KeyLocation::Left, + + CtrlLeft => KeyLocation::Left, + CtrlRight => KeyLocation::Right, + MetaLeft => KeyLocation::Left, + MetaRight => KeyLocation::Right, + + NumLock => KeyLocation::Numpad, + Numpad0 => KeyLocation::Numpad, + Numpad1 => KeyLocation::Numpad, + Numpad2 => KeyLocation::Numpad, + Numpad3 => KeyLocation::Numpad, + Numpad4 => KeyLocation::Numpad, + Numpad5 => KeyLocation::Numpad, + Numpad6 => KeyLocation::Numpad, + Numpad7 => KeyLocation::Numpad, + Numpad8 => KeyLocation::Numpad, + Numpad9 => KeyLocation::Numpad, + NumpadDivide => KeyLocation::Numpad, + NumpadMultiply => KeyLocation::Numpad, + NumpadSubtract => KeyLocation::Numpad, + NumpadAdd => KeyLocation::Numpad, + NumpadDot => KeyLocation::Numpad, + NumpadComma => KeyLocation::Numpad, + NumpadEnter => KeyLocation::Numpad, + NumpadEquals => KeyLocation::Numpad, + NumpadLeftParen => KeyLocation::Numpad, + NumpadRightParen => KeyLocation::Numpad, + + _ => KeyLocation::Standard, + } +} From f7c42a0b4f64e409b5061cb8494b2de048f26d2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Art=C3=BAr=20Kov=C3=A1cs?= Date: Fri, 9 Dec 2022 11:54:52 +0100 Subject: [PATCH 07/13] Stub out the new keyboard API on iOS --- src/platform_impl/ios/mod.rs | 3 +++ src/platform_impl/ios/window.rs | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/src/platform_impl/ios/mod.rs b/src/platform_impl/ios/mod.rs index fc52ee9534..d94681547d 100644 --- a/src/platform_impl/ios/mod.rs +++ b/src/platform_impl/ios/mod.rs @@ -107,6 +107,9 @@ impl DeviceId { unsafe impl Send for DeviceId {} unsafe impl Sync for DeviceId {} +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct KeyEventExtra {} + #[derive(Debug)] pub enum OsError {} diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs index 880bd843ff..f773db5e6b 100644 --- a/src/platform_impl/ios/window.rs +++ b/src/platform_impl/ios/window.rs @@ -368,6 +368,10 @@ impl Inner { warn!("`Window::title` is ignored on iOS"); String::new() } + + pub fn reset_dead_keys(&self) { + // Noop + } } pub struct Window { From d022b211cfc5f39279e1449f7bf8e3c6c9b2db29 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 08/13] Implement the new keyboard API for macOS --- src/platform/macos.rs | 263 ++++++++++ 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/view.rs | 331 +++++++------ src/platform_impl/macos/window.rs | 4 + src/platform_impl/macos/window_delegate.rs | 3 +- 7 files changed, 797 insertions(+), 380 deletions(-) diff --git a/src/platform/macos.rs b/src/platform/macos.rs index 0b6ebf11ed..9c3efba374 100644 --- a/src/platform/macos.rs +++ b/src/platform/macos.rs @@ -4,7 +4,9 @@ use objc2::rc::Id; use crate::{ event_loop::{EventLoopBuilder, EventLoopWindowTarget}, + keyboard::{KeyCode, NativeKeyCode}, monitor::MonitorHandle, + platform::scancode::KeyCodeExtScancode, window::{Window, WindowBuilder}, }; @@ -361,3 +363,264 @@ pub enum OptionAsAlt { #[default] None, } + +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/macos/event.rs b/src/platform_impl/macos/event.rs index 2c5fe5a0c4..ef5849c3b2 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, NativeKey, 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(NativeKey::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(NativeKey::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(NativeKey::MacOS(scancode)); + } + if result_len == 0 { + log::error!("`UCKeyTranslate` was succesful but gave a string of 0 length."); + return Key::Unidentified(NativeKey::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(NativeKey::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 f080331058..969c723554 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 = std::os::raw::c_ushort; +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 80bf89522e..4559e0c9fb 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/view.rs b/src/platform_impl/macos/view.rs index 365f151e0d..8e2ebd1e4e 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,23 +17,22 @@ use objc2::rc::{Id, Owned, Shared, WeakId}; 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}, }; use crate::platform::macos::{OptionAsAlt, WindowExtMacOS}; 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, @@ -66,12 +71,60 @@ 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!(), + } + } +} + +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!(), + } +} + +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, @@ -95,44 +148,6 @@ fn get_characters(event: &NSEvent, ignore_modifiers: bool) -> String { .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)] @@ -162,6 +177,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(), @@ -475,12 +491,19 @@ declare_class!( let ignore_alt_characters = match self.window().option_as_alt() { OptionAsAlt::OnlyLeft if event.lalt_pressed() => true, OptionAsAlt::OnlyRight if event.ralt_pressed() => true, - OptionAsAlt::Both if ev_mods.alt() => true, + OptionAsAlt::Both if ev_mods.alt_key() => true, _ => false, - } && !ev_mods.ctrl() - && !ev_mods.logo(); + } && !ev_mods.control_key() + && !ev_mods.super_key(); let characters = get_characters(event, ignore_alt_characters); + let is_repeat = event.is_a_repeat(); + + let alt_aware_event = if ignore_alt_characters { + replace_event_chars(event, &characters) + } else { + event.copy() + }; self.state.forward_key_to_app = false; // The `interpretKeyEvents` function might call @@ -490,13 +513,7 @@ declare_class!( // `doCommandBySelector`. (doCommandBySelector means that the keyboard input // is not handled by IME and should be handled by the application) if self.state.ime_allowed { - let new_event = if ignore_alt_characters { - replace_event_chars(event, &characters) - } else { - event.copy() - }; - - let events_for_nsview = NSArray::from_slice(&[new_event]); + let events_for_nsview = NSArray::from_slice(&[alt_aware_event.clone()]); unsafe { self.interpretKeyEvents(&events_for_nsview) }; // If the text was commited we must treat the next keyboard event as IME related. @@ -506,10 +523,7 @@ declare_class!( } } - let scancode = event.scancode() as u32; - let virtual_keycode = retrieve_keycode(event); - - self.update_potentially_stale_modifiers(event); + self.update_modifiers(event, false); let had_ime_input = match self.state.ime_state { ImeState::Commited => { @@ -521,90 +535,39 @@ declare_class!( _ => false, }; + if !had_ime_input || self.state.forward_key_to_app { - #[allow(deprecated)] + let in_ime = self.is_ime_enabled(); + let key_event = create_key_event(&alt_aware_event, true, is_repeat, in_ime, None); self.queue_event(WindowEvent::KeyboardInput { device_id: DEVICE_ID, - input: KeyboardInput { - state: ElementState::Pressed, - scancode, - virtual_keycode, - modifiers: ev_mods, - }, + event: key_event, 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 currently in the ground state. if self.state.ime_state == ImeState::Ground { - #[allow(deprecated)] self.queue_event(WindowEvent::KeyboardInput { device_id: DEVICE_ID, - input: KeyboardInput { - state: ElementState::Released, - scancode, - virtual_keycode, - modifiers: event_mods(event), - }, + event: create_key_event(event, false, false, false, None), is_synthetic: false, }); } } #[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:)] @@ -634,25 +597,17 @@ 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 event = create_key_event(&event, true, event.is_a_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), - }, + event, is_synthetic: false, }); } @@ -776,7 +731,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 { @@ -945,19 +900,115 @@ 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 }; + } - self.queue_event(WindowEvent::ModifiersChanged(self.state.modifiers)); + events.push_back(WindowEvent::KeyboardInput { + device_id: DEVICE_ID, + event, + is_synthetic: false, + }); + } + + for event in events { + self.queue_event(event); + } } + + if prev_modifiers == current_modifiers { + return; + } + + self.queue_event(WindowEvent::ModifiersChanged(self.state.modifiers)); } fn mouse_click(&mut self, event: &NSEvent, button_state: ElementState) { let button = mouse_button(event); - self.update_potentially_stale_modifiers(event); + self.update_modifiers(event, false); self.queue_event(WindowEvent::MouseInput { device_id: DEVICE_ID, @@ -988,7 +1039,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 69921ba78f..5bc262e89f 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -1271,6 +1271,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 baa0fe80ae..bb123c4915 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}, From 07287e0e78c8556974cf18d9965014a1f8239a59 Mon Sep 17 00:00:00 2001 From: Artur Kovacs Date: Sun, 26 Feb 2023 14:31:09 +0100 Subject: [PATCH 09/13] Implement the new keyboard API for Redox --- src/platform_impl/orbital/event_loop.rs | 244 ++++++++++++------------ src/platform_impl/orbital/mod.rs | 3 + src/platform_impl/orbital/window.rs | 5 + 3 files changed, 132 insertions(+), 120 deletions(-) diff --git a/src/platform_impl/orbital/event_loop.rs b/src/platform_impl/orbital/event_loop.rs index f0ad43d5b3..258e2b4cc7 100644 --- a/src/platform_impl/orbital/event_loop.rs +++ b/src/platform_impl/orbital/event_loop.rs @@ -12,99 +12,100 @@ use orbclient::{ use raw_window_handle::{OrbitalDisplayHandle, RawDisplayHandle}; use crate::{ - event::{self, StartCause, VirtualKeyCode}, + event::{self, Ime, StartCause}, event_loop::{self, ControlFlow}, + keyboard::{Key, KeyCode, KeyLocation, ModifiersState, NativeKey, NativeKeyCode}, window::WindowId as RootWindowId, }; use super::{ - DeviceId, MonitorHandle, PlatformSpecificEventLoopAttributes, RedoxSocket, TimeSocket, - WindowId, WindowProperties, + DeviceId, KeyEventExtra, MonitorHandle, PlatformSpecificEventLoopAttributes, RedoxSocket, + TimeSocket, WindowId, WindowProperties, }; -fn convert_scancode(scancode: u8) -> Option { +fn convert_scancode(scancode: u8) -> KeyCode { match scancode { - orbclient::K_A => Some(VirtualKeyCode::A), - orbclient::K_B => Some(VirtualKeyCode::B), - orbclient::K_C => Some(VirtualKeyCode::C), - orbclient::K_D => Some(VirtualKeyCode::D), - orbclient::K_E => Some(VirtualKeyCode::E), - orbclient::K_F => Some(VirtualKeyCode::F), - orbclient::K_G => Some(VirtualKeyCode::G), - orbclient::K_H => Some(VirtualKeyCode::H), - orbclient::K_I => Some(VirtualKeyCode::I), - orbclient::K_J => Some(VirtualKeyCode::J), - orbclient::K_K => Some(VirtualKeyCode::K), - orbclient::K_L => Some(VirtualKeyCode::L), - orbclient::K_M => Some(VirtualKeyCode::M), - orbclient::K_N => Some(VirtualKeyCode::N), - orbclient::K_O => Some(VirtualKeyCode::O), - orbclient::K_P => Some(VirtualKeyCode::P), - orbclient::K_Q => Some(VirtualKeyCode::Q), - orbclient::K_R => Some(VirtualKeyCode::R), - orbclient::K_S => Some(VirtualKeyCode::S), - orbclient::K_T => Some(VirtualKeyCode::T), - orbclient::K_U => Some(VirtualKeyCode::U), - orbclient::K_V => Some(VirtualKeyCode::V), - orbclient::K_W => Some(VirtualKeyCode::W), - orbclient::K_X => Some(VirtualKeyCode::X), - orbclient::K_Y => Some(VirtualKeyCode::Y), - orbclient::K_Z => Some(VirtualKeyCode::Z), - orbclient::K_0 => Some(VirtualKeyCode::Key0), - orbclient::K_1 => Some(VirtualKeyCode::Key1), - orbclient::K_2 => Some(VirtualKeyCode::Key2), - orbclient::K_3 => Some(VirtualKeyCode::Key3), - orbclient::K_4 => Some(VirtualKeyCode::Key4), - orbclient::K_5 => Some(VirtualKeyCode::Key5), - orbclient::K_6 => Some(VirtualKeyCode::Key6), - orbclient::K_7 => Some(VirtualKeyCode::Key7), - orbclient::K_8 => Some(VirtualKeyCode::Key8), - orbclient::K_9 => Some(VirtualKeyCode::Key9), - - orbclient::K_TICK => Some(VirtualKeyCode::Grave), - orbclient::K_MINUS => Some(VirtualKeyCode::Minus), - orbclient::K_EQUALS => Some(VirtualKeyCode::Equals), - orbclient::K_BACKSLASH => Some(VirtualKeyCode::Backslash), - orbclient::K_BRACE_OPEN => Some(VirtualKeyCode::LBracket), - orbclient::K_BRACE_CLOSE => Some(VirtualKeyCode::RBracket), - orbclient::K_SEMICOLON => Some(VirtualKeyCode::Semicolon), - orbclient::K_QUOTE => Some(VirtualKeyCode::Apostrophe), - orbclient::K_COMMA => Some(VirtualKeyCode::Comma), - orbclient::K_PERIOD => Some(VirtualKeyCode::Period), - orbclient::K_SLASH => Some(VirtualKeyCode::Slash), - orbclient::K_BKSP => Some(VirtualKeyCode::Back), - orbclient::K_SPACE => Some(VirtualKeyCode::Space), - orbclient::K_TAB => Some(VirtualKeyCode::Tab), - //orbclient::K_CAPS => Some(VirtualKeyCode::CAPS), - orbclient::K_LEFT_SHIFT => Some(VirtualKeyCode::LShift), - orbclient::K_RIGHT_SHIFT => Some(VirtualKeyCode::RShift), - orbclient::K_CTRL => Some(VirtualKeyCode::LControl), - orbclient::K_ALT => Some(VirtualKeyCode::LAlt), - orbclient::K_ENTER => Some(VirtualKeyCode::Return), - orbclient::K_ESC => Some(VirtualKeyCode::Escape), - orbclient::K_F1 => Some(VirtualKeyCode::F1), - orbclient::K_F2 => Some(VirtualKeyCode::F2), - orbclient::K_F3 => Some(VirtualKeyCode::F3), - orbclient::K_F4 => Some(VirtualKeyCode::F4), - orbclient::K_F5 => Some(VirtualKeyCode::F5), - orbclient::K_F6 => Some(VirtualKeyCode::F6), - orbclient::K_F7 => Some(VirtualKeyCode::F7), - orbclient::K_F8 => Some(VirtualKeyCode::F8), - orbclient::K_F9 => Some(VirtualKeyCode::F9), - orbclient::K_F10 => Some(VirtualKeyCode::F10), - orbclient::K_HOME => Some(VirtualKeyCode::Home), - orbclient::K_UP => Some(VirtualKeyCode::Up), - orbclient::K_PGUP => Some(VirtualKeyCode::PageUp), - orbclient::K_LEFT => Some(VirtualKeyCode::Left), - orbclient::K_RIGHT => Some(VirtualKeyCode::Right), - orbclient::K_END => Some(VirtualKeyCode::End), - orbclient::K_DOWN => Some(VirtualKeyCode::Down), - orbclient::K_PGDN => Some(VirtualKeyCode::PageDown), - orbclient::K_DEL => Some(VirtualKeyCode::Delete), - orbclient::K_F11 => Some(VirtualKeyCode::F11), - orbclient::K_F12 => Some(VirtualKeyCode::F12), - - _ => None, + orbclient::K_A => KeyCode::KeyA, + orbclient::K_B => KeyCode::KeyB, + orbclient::K_C => KeyCode::KeyC, + orbclient::K_D => KeyCode::KeyD, + orbclient::K_E => KeyCode::KeyE, + orbclient::K_F => KeyCode::KeyF, + orbclient::K_G => KeyCode::KeyG, + orbclient::K_H => KeyCode::KeyH, + orbclient::K_I => KeyCode::KeyI, + orbclient::K_J => KeyCode::KeyJ, + orbclient::K_K => KeyCode::KeyK, + orbclient::K_L => KeyCode::KeyL, + orbclient::K_M => KeyCode::KeyM, + orbclient::K_N => KeyCode::KeyN, + orbclient::K_O => KeyCode::KeyO, + orbclient::K_P => KeyCode::KeyP, + orbclient::K_Q => KeyCode::KeyQ, + orbclient::K_R => KeyCode::KeyR, + orbclient::K_S => KeyCode::KeyS, + orbclient::K_T => KeyCode::KeyT, + orbclient::K_U => KeyCode::KeyU, + orbclient::K_V => KeyCode::KeyV, + orbclient::K_W => KeyCode::KeyW, + orbclient::K_X => KeyCode::KeyX, + orbclient::K_Y => KeyCode::KeyY, + orbclient::K_Z => KeyCode::KeyZ, + orbclient::K_0 => KeyCode::Digit0, + orbclient::K_1 => KeyCode::Digit1, + orbclient::K_2 => KeyCode::Digit2, + orbclient::K_3 => KeyCode::Digit3, + orbclient::K_4 => KeyCode::Digit4, + orbclient::K_5 => KeyCode::Digit5, + orbclient::K_6 => KeyCode::Digit6, + orbclient::K_7 => KeyCode::Digit7, + orbclient::K_8 => KeyCode::Digit8, + orbclient::K_9 => KeyCode::Digit9, + + orbclient::K_TICK => KeyCode::Backquote, + orbclient::K_MINUS => KeyCode::Minus, + orbclient::K_EQUALS => KeyCode::Equal, + orbclient::K_BACKSLASH => KeyCode::Backslash, + orbclient::K_BRACE_OPEN => KeyCode::BracketLeft, + orbclient::K_BRACE_CLOSE => KeyCode::BracketRight, + orbclient::K_SEMICOLON => KeyCode::Semicolon, + orbclient::K_QUOTE => KeyCode::Quote, + orbclient::K_COMMA => KeyCode::Comma, + orbclient::K_PERIOD => KeyCode::Period, + orbclient::K_SLASH => KeyCode::Slash, + orbclient::K_BKSP => KeyCode::Backspace, + orbclient::K_SPACE => KeyCode::Space, + orbclient::K_TAB => KeyCode::Tab, + //orbclient::K_CAPS => KeyCode::CAPS, + orbclient::K_LEFT_SHIFT => KeyCode::ShiftLeft, + orbclient::K_RIGHT_SHIFT => KeyCode::ShiftRight, + orbclient::K_CTRL => KeyCode::ControlLeft, + orbclient::K_ALT => KeyCode::AltLeft, + orbclient::K_ENTER => KeyCode::Enter, + orbclient::K_ESC => KeyCode::Escape, + orbclient::K_F1 => KeyCode::F1, + orbclient::K_F2 => KeyCode::F2, + orbclient::K_F3 => KeyCode::F3, + orbclient::K_F4 => KeyCode::F4, + orbclient::K_F5 => KeyCode::F5, + orbclient::K_F6 => KeyCode::F6, + orbclient::K_F7 => KeyCode::F7, + orbclient::K_F8 => KeyCode::F8, + orbclient::K_F9 => KeyCode::F9, + orbclient::K_F10 => KeyCode::F10, + orbclient::K_HOME => KeyCode::Home, + orbclient::K_UP => KeyCode::ArrowUp, + orbclient::K_PGUP => KeyCode::PageUp, + orbclient::K_LEFT => KeyCode::ArrowLeft, + orbclient::K_RIGHT => KeyCode::ArrowRight, + orbclient::K_END => KeyCode::End, + orbclient::K_DOWN => KeyCode::ArrowDown, + orbclient::K_PGDN => KeyCode::PageDown, + orbclient::K_DEL => KeyCode::Delete, + orbclient::K_F11 => KeyCode::F11, + orbclient::K_F12 => KeyCode::F12, + + _ => KeyCode::Unidentified(NativeKeyCode::Unidentified), } } @@ -147,16 +148,16 @@ struct EventState { } impl EventState { - fn key(&mut self, vk: VirtualKeyCode, pressed: bool) { - match vk { - VirtualKeyCode::LShift => self.keyboard.set(KeyboardModifierState::LSHIFT, pressed), - VirtualKeyCode::RShift => self.keyboard.set(KeyboardModifierState::RSHIFT, pressed), - VirtualKeyCode::LControl => self.keyboard.set(KeyboardModifierState::LCTRL, pressed), - VirtualKeyCode::RControl => self.keyboard.set(KeyboardModifierState::RCTRL, pressed), - VirtualKeyCode::LAlt => self.keyboard.set(KeyboardModifierState::LALT, pressed), - VirtualKeyCode::RAlt => self.keyboard.set(KeyboardModifierState::RALT, pressed), - VirtualKeyCode::LWin => self.keyboard.set(KeyboardModifierState::LSUPER, pressed), - VirtualKeyCode::RWin => self.keyboard.set(KeyboardModifierState::RSUPER, pressed), + fn key(&mut self, code: KeyCode, pressed: bool) { + match code { + KeyCode::ShiftLeft => self.keyboard.set(KeyboardModifierState::LSHIFT, pressed), + KeyCode::ShiftRight => self.keyboard.set(KeyboardModifierState::RSHIFT, pressed), + KeyCode::ControlLeft => self.keyboard.set(KeyboardModifierState::LCTRL, pressed), + KeyCode::ControlRight => self.keyboard.set(KeyboardModifierState::RCTRL, pressed), + KeyCode::AltLeft => self.keyboard.set(KeyboardModifierState::LALT, pressed), + KeyCode::AltRight => self.keyboard.set(KeyboardModifierState::RALT, pressed), + KeyCode::SuperLeft => self.keyboard.set(KeyboardModifierState::LSUPER, pressed), + KeyCode::SuperRight => self.keyboard.set(KeyboardModifierState::RSUPER, pressed), _ => (), } } @@ -185,31 +186,31 @@ impl EventState { None } - fn modifiers(&self) -> event::ModifiersState { - let mut modifiers = event::ModifiersState::empty(); + fn modifiers(&self) -> ModifiersState { + let mut modifiers = ModifiersState::empty(); if self .keyboard .intersects(KeyboardModifierState::LSHIFT | KeyboardModifierState::RSHIFT) { - modifiers |= event::ModifiersState::SHIFT; + modifiers |= ModifiersState::SHIFT; } if self .keyboard .intersects(KeyboardModifierState::LCTRL | KeyboardModifierState::RCTRL) { - modifiers |= event::ModifiersState::CTRL; + modifiers |= ModifiersState::CONTROL; } if self .keyboard .intersects(KeyboardModifierState::LALT | KeyboardModifierState::RALT) { - modifiers |= event::ModifiersState::ALT; + modifiers |= ModifiersState::ALT; } if self .keyboard .intersects(KeyboardModifierState::LSUPER | KeyboardModifierState::RSUPER) { - modifiers |= event::ModifiersState::LOGO + modifiers |= ModifiersState::SUPER } modifiers } @@ -277,32 +278,35 @@ impl EventLoop { pressed, }) => { if scancode != 0 { - let vk_opt = convert_scancode(scancode); - if let Some(vk) = vk_opt { - event_state.key(vk, pressed); - } - event_handler( - #[allow(deprecated)] - event::Event::WindowEvent { - window_id: RootWindowId(window_id), - event: event::WindowEvent::KeyboardInput { - device_id: event::DeviceId(DeviceId), - input: event::KeyboardInput { - scancode: scancode as u32, - state: element_state(pressed), - virtual_keycode: vk_opt, - modifiers: event_state.modifiers(), - }, - is_synthetic: false, + let code = convert_scancode(scancode); + event_state.key(code.clone(), pressed); + event_handler(event::Event::WindowEvent { + window_id: RootWindowId(window_id), + event: event::WindowEvent::KeyboardInput { + device_id: event::DeviceId(DeviceId), + event: event::KeyEvent { + logical_key: Key::Unidentified(NativeKey::Unidentified), + physical_key: code, + location: KeyLocation::Standard, + state: element_state(pressed), + repeat: false, + text: None, + + platform_specific: KeyEventExtra {}, }, + is_synthetic: false, }, - ); + }); } } EventOption::TextInput(TextInputEvent { character }) => { event_handler(event::Event::WindowEvent { window_id: RootWindowId(window_id), - event: event::WindowEvent::ReceivedCharacter(character), + event: event::WindowEvent::Ime(Ime::Preedit("".into(), None)), + }); + event_handler(event::Event::WindowEvent { + window_id: RootWindowId(window_id), + event: event::WindowEvent::Ime(Ime::Commit(character.into())), }); } EventOption::Mouse(MouseEvent { x, y }) => { diff --git a/src/platform_impl/orbital/mod.rs b/src/platform_impl/orbital/mod.rs index 97d1dc511a..961f6c4e31 100644 --- a/src/platform_impl/orbital/mod.rs +++ b/src/platform_impl/orbital/mod.rs @@ -251,3 +251,6 @@ impl VideoMode { self.monitor.clone() } } + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct KeyEventExtra {} diff --git a/src/platform_impl/orbital/window.rs b/src/platform_impl/orbital/window.rs index fa30bb10d4..1afe5cdd94 100644 --- a/src/platform_impl/orbital/window.rs +++ b/src/platform_impl/orbital/window.rs @@ -166,6 +166,11 @@ impl Window { } } + #[inline] + pub fn reset_dead_keys(&self) { + // TODO? + } + #[inline] pub fn inner_position(&self) -> Result, error::NotSupportedError> { let mut buf: [u8; 4096] = [0; 4096]; From 32b7e8d7371fe40cdcefa5ef778a259c052d2c29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Art=C3=BAr=20Kov=C3=A1cs?= Date: Sun, 26 Feb 2023 17:09:19 +0100 Subject: [PATCH 10/13] fixup! Implement the new keyboard API for macOS --- src/platform_impl/macos/appkit/event.rs | 10 +++------- src/platform_impl/macos/event.rs | 16 +++++++++++----- src/platform_impl/macos/ffi.rs | 5 ++--- src/platform_impl/macos/util/async.rs | 4 ++++ src/platform_impl/macos/view.rs | 4 ++-- 5 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/platform_impl/macos/appkit/event.rs b/src/platform_impl/macos/appkit/event.rs index 7924f420ad..b20915af6c 100644 --- a/src/platform_impl/macos/appkit/event.rs +++ b/src/platform_impl/macos/appkit/event.rs @@ -77,7 +77,7 @@ extern_methods!( characters: &NSString, characters_ignoring_modifiers: &NSString, is_a_repeat: bool, - scancode: c_ushort, + key_code: c_ushort, ) -> Id { unsafe { msg_send_id![ @@ -91,7 +91,7 @@ extern_methods!( characters: characters, charactersIgnoringModifiers: characters_ignoring_modifiers, isARepeat: is_a_repeat, - keyCode: scancode, + keyCode: key_code, ] } } @@ -109,12 +109,8 @@ extern_methods!( #[sel(type)] pub fn type_(&self) -> NSEventType; - // 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. #[sel(keyCode)] - pub fn scancode(&self) -> c_ushort; + pub fn key_code(&self) -> c_ushort; #[sel(magnification)] pub fn magnification(&self) -> CGFloat; diff --git a/src/platform_impl/macos/event.rs b/src/platform_impl/macos/event.rs index ef5849c3b2..4a81279ec9 100644 --- a/src/platform_impl/macos/event.rs +++ b/src/platform_impl/macos/event.rs @@ -1,6 +1,9 @@ use std::{collections::HashSet, ffi::c_void, sync::Mutex}; -use core_foundation::{base::CFRelease, data::CFDataGetBytePtr}; +use core_foundation::{ + base::CFRelease, + data::{CFDataGetBytePtr, CFDataRef}, +}; use objc2::rc::{Id, Shared}; use once_cell::sync::Lazy; @@ -11,7 +14,10 @@ use crate::{ event::{ElementState, Event, KeyEvent}, keyboard::{Key, KeyCode, KeyLocation, ModifiersState, NativeKey, NativeKeyCode}, platform::{modifier_supplement::KeyEventExtModifierSupplement, scancode::KeyCodeExtScancode}, - platform_impl::platform::{ffi, util::Never}, + platform_impl::platform::{ + ffi, + util::{get_kbd_type, Never}, + }, }; static KEY_STRINGS: Lazy>> = Lazy::new(|| Mutex::new(HashSet::new())); @@ -74,9 +80,9 @@ pub fn get_modifierless_char(scancode: u16) -> Key<'static> { log::error!("`TISGetInputSourceProperty` returned null ptr"); return Key::Unidentified(NativeKey::MacOS(scancode)); } - layout = CFDataGetBytePtr(layout_data) as *const ffi::UCKeyboardLayout; + layout = CFDataGetBytePtr(layout_data as CFDataRef) as *const ffi::UCKeyboardLayout; } - let keyboard_type = unsafe { ffi::LMGetKbdType() }; + let keyboard_type = get_kbd_type(); let mut result_len = 0; let mut dead_keys = 0; @@ -136,7 +142,7 @@ pub(crate) fn create_key_event( use ElementState::{Pressed, Released}; let state = if is_press { Pressed } else { Released }; - let scancode = ns_event.scancode(); + let scancode = ns_event.key_code(); let mut physical_key = key_override .clone() .unwrap_or_else(|| KeyCode::from_scancode(scancode as u32)); diff --git a/src/platform_impl/macos/ffi.rs b/src/platform_impl/macos/ffi.rs index 969c723554..05397facf0 100644 --- a/src/platform_impl/macos/ffi.rs +++ b/src/platform_impl/macos/ffi.rs @@ -5,8 +5,7 @@ use std::ffi::c_void; use core_foundation::{ - array::CFArrayRef, data::CFDataRef, dictionary::CFDictionaryRef, string::CFStringRef, - uuid::CFUUIDRef, + array::CFArrayRef, dictionary::CFDictionaryRef, string::CFStringRef, uuid::CFUUIDRef, }; use core_graphics::{ base::CGError, @@ -182,7 +181,7 @@ extern "C" { pub fn TISGetInputSourceProperty( inputSource: TISInputSourceRef, propertyKey: CFStringRef, - ) -> CFDataRef; + ) -> *mut c_void; pub fn TISCopyCurrentKeyboardLayoutInputSource() -> TISInputSourceRef; diff --git a/src/platform_impl/macos/util/async.rs b/src/platform_impl/macos/util/async.rs index 3cc9a4b46e..d3b10831b6 100644 --- a/src/platform_impl/macos/util/async.rs +++ b/src/platform_impl/macos/util/async.rs @@ -209,3 +209,7 @@ pub(crate) fn set_ime_position_sync(window: &WinitWindow, logical_spot: LogicalP unsafe { Id::from_shared(window.view()) }.set_ime_position(logical_spot); }); } + +pub(crate) fn get_kbd_type() -> u8 { + run_on_main(|| unsafe { ffi::LMGetKbdType() }) +} diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index 8e2ebd1e4e..a12c636f26 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -912,7 +912,7 @@ impl WinitView { // 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 scancode = ns_event.key_code(); let keycode = KeyCode::from_scancode(scancode as u32); // We'll correct the `is_press` later. @@ -1076,6 +1076,6 @@ fn replace_event_chars(event: &NSEvent, characters: &str) -> Id &ns_chars, &chars_ignoring_mods, event.is_a_repeat(), - event.scancode(), + event.key_code(), ) } From d7d94039e0e7b3519492b7dad73cdc494fc885f4 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Fri, 5 May 2023 20:59:45 +0300 Subject: [PATCH 11/13] windows: use already defined const functions --- src/platform/windows.rs | 4 ++-- src/platform_impl/windows/keyboard_layout.rs | 6 +++--- src/platform_impl/windows/mod.rs | 21 ++++++-------------- 3 files changed, 11 insertions(+), 20 deletions(-) diff --git a/src/platform/windows.rs b/src/platform/windows.rs index 984567931d..ad4ea0a331 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -11,7 +11,7 @@ use crate::{ keyboard::{Key, KeyCode, NativeKeyCode}, monitor::MonitorHandle, platform::{modifier_supplement::KeyEventExtModifierSupplement, scancode::KeyCodeExtScancode}, - platform_impl::{WinIcon, LOWORD, PRIMARYLANGID}, + platform_impl::{WinIcon, loword, primarylangid}, window::{BadIcon, Icon, Window, WindowBuilder}, }; @@ -369,7 +369,7 @@ impl KeyCodeExtScancode for KeyCode { let hkl = unsafe { GetKeyboardLayout(0) }; - let primary_lang_id = PRIMARYLANGID(LOWORD(hkl as u32)); + let primary_lang_id = primarylangid(loword(hkl as u32)); let is_korean = primary_lang_id as u32 == LANG_KOREAN; match self { diff --git a/src/platform_impl/windows/keyboard_layout.rs b/src/platform_impl/windows/keyboard_layout.rs index bf3fa80b00..4bb923ba96 100644 --- a/src/platform_impl/windows/keyboard_layout.rs +++ b/src/platform_impl/windows/keyboard_layout.rs @@ -53,7 +53,7 @@ use windows_sys::Win32::{ use crate::{ keyboard::{Key, KeyCode, ModifiersState, NativeKey}, platform::scancode::KeyCodeExtScancode, - platform_impl::{LOWORD, PRIMARYLANGID}, + platform_impl::{loword, primarylangid}, }; pub(crate) static LAYOUT_CACHE: Lazy> = @@ -552,7 +552,7 @@ fn is_numpad_specific(vk: VIRTUAL_KEY) -> bool { } fn keycode_to_vkey(keycode: KeyCode, hkl: u64) -> VIRTUAL_KEY { - let primary_lang_id = PRIMARYLANGID(LOWORD(hkl as u32)); + let primary_lang_id = primarylangid(loword(hkl as u32)); let is_korean = primary_lang_id as u32 == LANG_KOREAN; let is_japanese = primary_lang_id as u32 == LANG_JAPANESE; @@ -774,7 +774,7 @@ fn vkey_to_non_char_key( // List of the Web key names and their corresponding platform-native key names: // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values - let primary_lang_id = PRIMARYLANGID(LOWORD(hkl as u32)); + let primary_lang_id = primarylangid(loword(hkl as u32)); let is_korean = primary_lang_id as u32 == LANG_KOREAN; let is_japanese = primary_lang_id as u32 == LANG_JAPANESE; diff --git a/src/platform_impl/windows/mod.rs b/src/platform_impl/windows/mod.rs index 31b18de818..149378bec9 100644 --- a/src/platform_impl/windows/mod.rs +++ b/src/platform_impl/windows/mod.rs @@ -21,20 +21,6 @@ use crate::event::DeviceId as RootDeviceId; use crate::icon::Icon; use crate::keyboard::Key; -// --------------------------------------------- -// WINDOWS MACROS and constants -#[inline] -#[allow(non_snake_case)] -pub fn PRIMARYLANGID(lgid: u16) -> u16 { - lgid & 0x3ff -} -#[inline] -#[allow(non_snake_case)] -pub fn LOWORD(dword: u32) -> u16 { - (dword & 0xffff) as u16 -} -// --------------------------------------------- - #[derive(Clone)] pub struct PlatformSpecificWindowBuilderAttributes { pub owner: Option, @@ -148,7 +134,12 @@ const fn get_y_lparam(x: u32) -> i16 { } #[inline(always)] -const fn loword(x: u32) -> u16 { +pub(crate) const fn primarylangid(lgid: u16) -> u16 { + lgid & 0x3FF +} + +#[inline(always)] +pub(crate) const fn loword(x: u32) -> u16 { (x & 0xFFFF) as u16 } From 96a175b84c4742354dd85b7c37f7d10dd9b7580d Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Fri, 5 May 2023 21:07:53 +0300 Subject: [PATCH 12/13] web: remove left over handling --- src/platform_impl/web/web_sys/event.rs | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/src/platform_impl/web/web_sys/event.rs b/src/platform_impl/web/web_sys/event.rs index 20844e6f9c..2431fc66f9 100644 --- a/src/platform_impl/web/web_sys/event.rs +++ b/src/platform_impl/web/web_sys/event.rs @@ -108,14 +108,11 @@ fn cached_string>(string: S) -> &'static str { } pub fn key_location(event: &KeyboardEvent) -> KeyLocation { - let location = event.location(); - // As defined in the UIEvents specification - // https://w3c.github.io/uievents/#idl-keyboardevent - match location { - 0 => KeyLocation::Standard, - 1 => KeyLocation::Left, - 2 => KeyLocation::Right, - 3 => KeyLocation::Numpad, + match event.location() { + KeyboardEvent::DOM_KEY_LOCATION_LEFT => KeyLocation::Left, + KeyboardEvent::DOM_KEY_LOCATION_RIGHT => KeyLocation::Right, + KeyboardEvent::DOM_KEY_LOCATION_NUMPAD => KeyLocation::Numpad, + KeyboardEvent::DOM_KEY_LOCATION_STANDARD => KeyLocation::Standard, _ => KeyLocation::Standard, } } @@ -132,13 +129,6 @@ pub fn keyboard_modifiers(key: &Key<'_>) -> ModifiersState { } } -// pub fn codepoint(event: &KeyboardEvent) -> char { -// // `event.key()` always returns a non-empty `String`. Therefore, this should -// // never panic. -// // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key -// event.key().chars().next().unwrap() -// } - pub fn touch_position(event: &PointerEvent, _canvas: &HtmlCanvasElement) -> LogicalPosition { // TODO: Should this handle more, like `mouse_position_by_client` does? LogicalPosition { From b1655712be874d10e94152066f90b7197026793d Mon Sep 17 00:00:00 2001 From: John Nunley Date: Sun, 14 May 2023 11:45:41 -0700 Subject: [PATCH 13/13] chore: cleanup cfg's and features --- Cargo.toml | 4 ++-- src/platform/modifier_supplement.rs | 10 +--------- src/platform/scancode.rs | 14 +++++--------- src/platform/windows.rs | 2 +- src/platform_impl/macos/view.rs | 1 - 5 files changed, 9 insertions(+), 22 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e0a2458fcf..b43c038ba3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,7 @@ rustdoc-args = ["--cfg", "docsrs"] [features] default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"] -x11 = ["x11-dl", "mio", "percent-encoding", "xkbcommon-dl/x11"] +x11 = ["x11-dl", "memmap2", "mio", "percent-encoding", "xkbcommon-dl/x11"] wayland = ["wayland-client", "wayland-backend", "wayland-protocols", "sctk", "fnv"] wayland-dlopen = ["wayland-backend/dlopen"] wayland-csd-adwaita = ["sctk-adwaita", "sctk-adwaita/ab_glyph"] @@ -111,7 +111,7 @@ features = [ [target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_os = "ios", target_os = "macos"))))'.dependencies] libc = "0.2.64" mio = { version = "0.8", features = ["os-ext"], optional = true } -memmap2 = { version = "0.2.1", optional = true } +memmap2 = { version = "0.5.10", optional = true } percent-encoding = { version = "2.0", optional = true } fnv = { version = "1.0.3", optional = true } sctk = { package = "smithay-client-toolkit", version = "0.17.0", optional = true } diff --git a/src/platform/modifier_supplement.rs b/src/platform/modifier_supplement.rs index 361ed05d04..433846787e 100644 --- a/src/platform/modifier_supplement.rs +++ b/src/platform/modifier_supplement.rs @@ -1,12 +1,4 @@ -#![cfg(any( - target_os = "windows", - target_os = "macos", - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" -))] +#![cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform))] use crate::keyboard::Key; diff --git a/src/platform/scancode.rs b/src/platform/scancode.rs index 00188c6212..962622e7af 100644 --- a/src/platform/scancode.rs +++ b/src/platform/scancode.rs @@ -1,18 +1,14 @@ -#![cfg(any( - target_os = "windows", - target_os = "macos", - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" -))] +#![cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform))] // TODO: Maybe merge this with `modifier_supplement` if the two are indeed supported on the same // set of platforms use crate::keyboard::KeyCode; +/// Additional methods for the [`KeyCode`] type that allow the user to access the platform-specific +/// scancode. +/// +/// [`KeyCode`]: crate::keyboard::KeyCode pub trait KeyCodeExtScancode { /// The raw value of the platform-specific physical key identifier. /// diff --git a/src/platform/windows.rs b/src/platform/windows.rs index ad4ea0a331..7a27080ee6 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -11,7 +11,7 @@ use crate::{ keyboard::{Key, KeyCode, NativeKeyCode}, monitor::MonitorHandle, platform::{modifier_supplement::KeyEventExtModifierSupplement, scancode::KeyCodeExtScancode}, - platform_impl::{WinIcon, loword, primarylangid}, + platform_impl::{loword, primarylangid, WinIcon}, window::{BadIcon, Icon, Window, WindowBuilder}, }; diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index a12c636f26..f0a6fc178e 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -535,7 +535,6 @@ declare_class!( _ => false, }; - if !had_ime_input || self.state.forward_key_to_app { let in_ime = self.is_ime_enabled(); let key_event = create_key_event(&alt_aware_event, true, is_repeat, in_ime, None);