From a8701478a0de18a67eca8d2634cf7abb900822f5 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] Overhaul the Keyboard API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Overhaul the keyboard API in winit to mimic the W3C specification to achieve better crossplatform parity. The `KeyboardInput` event is now uses `KeyEvent` which consists of: - `physical_key` - a cross platform way to refer to scancodes; - `logical_key` - keysym value, which shows your key respecting the layout; - `text` - the text produced by this keypress; - `location` - the location of the key on the keyboard; - `repeat` - whether the key was produced by the repeat. And also a `platform_specific` field which encapsulates extra information on desktop platforms, like key without modifiers and text with all modifiers. The `Modifiers` were also slightly reworked as in, the information whether the left or right modifier is pressed is now also exposed on platforms where it could be queried reliably. The support was also added for the web and orbital platforms finishing the API change. This change made the `OptionAsAlt` API on macOS redundant thus it was removed all together. Co-Authored-By: Artúr Kovács Co-Authored-By: Kirill Chibisov Co-Authored-By: daxpedda Fixes: #2631. Fixes: #2055. Fixes: #2032. Fixes: #1904. Fixes: #1810. Fixes: #1700. Fixes: #1443. Fixes: #1343. Fixes: #1208. Fixes: #1151. Fixes: #812. Fixes: #600. Fixes: #361. Fixes: #343. --- .github/workflows/ci.yml | 6 +- CHANGELOG.md | 28 + Cargo.toml | 15 +- FEATURES.md | 8 +- docs/res/ATTRIBUTION.md | 11 + docs/res/keyboard_left_shift_key.svg | 1 + docs/res/keyboard_numpad_1_key.svg | 1 + docs/res/keyboard_right_shift_key.svg | 1 + docs/res/keyboard_standard_1_key.svg | 1 + examples/child_window.rs | 6 +- examples/control_flow.rs | 25 +- examples/cursor.rs | 6 +- examples/cursor_grab.rs | 31 +- examples/drag_window.rs | 13 +- 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 | 11 +- examples/resizable.rs | 9 +- examples/theme.rs | 17 +- examples/window_buttons.rs | 17 +- examples/window_debug.rs | 41 +- examples/window_drag_resize.rs | 13 +- examples/window_option_as_alt.rs | 67 - examples/window_resize_increments.rs | 9 +- src/event.rs | 578 +++--- src/keyboard.rs | 1688 +++++++++++++++++ src/lib.rs | 1 + src/platform/macos.rs | 52 - src/platform/mod.rs | 2 + src/platform/modifier_supplement.rs | 24 + src/platform/scancode.rs | 28 + src/platform/windows.rs | 19 +- src/platform_impl/android/mod.rs | 597 ++++-- src/platform_impl/ios/mod.rs | 3 + src/platform_impl/ios/window.rs | 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 | 668 +++++++ src/platform_impl/linux/mod.rs | 42 +- .../linux/wayland/seat/keyboard/keymap.rs | 192 -- .../linux/wayland/seat/keyboard/mod.rs | 513 +++-- src/platform_impl/linux/wayland/seat/mod.rs | 34 +- .../linux/wayland/seat/pointer/mod.rs | 5 - .../linux/x11/event_processor.rs | 345 ++-- src/platform_impl/linux/x11/mod.rs | 44 +- src/platform_impl/linux/x11/util/input.rs | 26 +- 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 +- src/platform_impl/macos/appkit/event.rs | 71 +- src/platform_impl/macos/event.rs | 812 +++++--- src/platform_impl/macos/ffi.rs | 45 + src/platform_impl/macos/mod.rs | 1 + src/platform_impl/macos/util/async.rs | 4 + src/platform_impl/macos/view.rs | 360 ++-- src/platform_impl/macos/window.rs | 23 +- src/platform_impl/macos/window_delegate.rs | 11 +- src/platform_impl/orbital/event_loop.rs | 305 +-- src/platform_impl/orbital/mod.rs | 3 + src/platform_impl/orbital/window.rs | 5 + .../web/event_loop/window_target.rs | 343 ++-- src/platform_impl/web/keyboard.rs | 522 +++++ src/platform_impl/web/mod.rs | 2 + src/platform_impl/web/web_sys/canvas.rs | 78 +- .../web/web_sys/canvas/mouse_handler.rs | 65 +- .../web/web_sys/canvas/pointer_handler.rs | 27 +- src/platform_impl/web/web_sys/event.rs | 252 +-- src/platform_impl/web/window.rs | 12 +- src/platform_impl/windows/event.rs | 436 ----- src/platform_impl/windows/event_loop.rs | 649 ++++--- src/platform_impl/windows/keyboard.rs | 1272 +++++++++++++ src/platform_impl/windows/keyboard_layout.rs | 997 ++++++++++ src/platform_impl/windows/minimal_ime.rs | 67 + src/platform_impl/windows/mod.rs | 18 +- src/platform_impl/windows/window.rs | 32 +- src/platform_impl/windows/window_state.rs | 5 +- src/window.rs | 27 +- tests/serde_objects.rs | 11 +- 81 files changed, 9570 insertions(+), 3412 deletions(-) create mode 100644 docs/res/ATTRIBUTION.md create mode 100644 docs/res/keyboard_left_shift_key.svg create mode 100644 docs/res/keyboard_numpad_1_key.svg create mode 100644 docs/res/keyboard_right_shift_key.svg create mode 100644 docs/res/keyboard_standard_1_key.svg create mode 100644 examples/key_binding.rs delete mode 100644 examples/window_option_as_alt.rs create mode 100644 src/keyboard.rs create mode 100644 src/platform/modifier_supplement.rs create mode 100644 src/platform/scancode.rs 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 delete mode 100644 src/platform_impl/linux/wayland/seat/keyboard/keymap.rs create mode 100644 src/platform_impl/web/keyboard.rs 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/.github/workflows/ci.yml b/.github/workflows/ci.yml index 46e695fae2..38cf83f1d2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -75,7 +75,6 @@ jobs: env: RUST_BACKTRACE: 1 CARGO_INCREMENTAL: 0 - PKG_CONFIG_ALLOW_CROSS: 1 RUSTFLAGS: "-C debuginfo=0 --deny warnings" OPTIONS: ${{ matrix.platform.options }} FEATURES: ${{ format(',{0}', matrix.platform.features ) }} @@ -98,12 +97,9 @@ jobs: targets: ${{ matrix.platform.target }} components: clippy - - name: Install Linux dependencies - if: (matrix.platform.os == 'ubuntu-latest') - run: sudo apt-get update && sudo apt-get install pkg-config libxkbcommon-dev - name: Install GCC Multilib if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686') - run: sudo dpkg --add-architecture i386 && sudo apt-get update && sudo apt-get install g++-multilib gcc-multilib libxkbcommon-dev:i386 + run: sudo apt-get update && sudo apt-get install gcc-multilib - name: Install cargo-apk if: contains(matrix.platform.target, 'android') run: cargo install cargo-apk diff --git a/CHANGELOG.md b/CHANGELOG.md index e7c1facc4b..3e50ccab76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,34 @@ And please only add new entries to the top of this list, right below the `# Unre # Unreleased +- **Breaking:** Remove all deprecated `modifiers` fields. +- **Breaking:** Overhaul keyboard input handling. + - Replace `KeyboardInput` with `KeyEvent` and `RawKeyEvent`. + - Change `WindowEvent::KeyboardInput` to contain a `KeyEvent`. + - Change `Event::Key` to contain a `RawKeyEvent`. + - Remove `Event::ReceivedCharacter`. In its place, you should use + `KeyEvent.text` in combination with `WindowEvent::Ime`. + - Replace `VirtualKeyCode` with the `Key` enum. + - Replace `ScanCode` with the `KeyCode` enum. + - Rename `ModifiersState::LOGO` to `SUPER` and `ModifiersState::CTRL` to `CONTROL`. + - Add `KeyCode` to refer to keys (roughly) by their physical location. + - Add `NativeKeyCode` to represent raw `KeyCode`s which Winit doesn't + understand. + - Add `Key` to represent the keys after they've been interpreted by the + active (software) keyboard layout. + - Add `NativeKey` to represent raw `Key`s which Winit doesn't understand. + - Add `KeyLocation` to tell apart `Key`s which usually "mean" the same thing, + but can appear simultanesouly in different spots on the same keyboard + layout. + - Add `Window::reset_dead_keys` to enable application-controlled cancellation + of dead key sequences. + - Add `KeyEventExtModifierSupplement` to expose additional (and less + portable) interpretations of a given key-press. + - Add `KeyCodeExtScancode`, which lets you convert between raw keycodes and + `KeyCode`. + - Remove `WindowExtMacOS::option_as_alt` and `WindowExtMacOS::set_option_as_alt`. + - `ModifiersChanged` now uses dedicated `Modifiers` struct. +- On Orbital, fix `ModifiersChanged` not being sent. - **Breaking:** `CursorIcon` is now used from the `cursor-icon` crate. - **Breaking:** `CursorIcon::Hand` is now named `CursorIcon::Pointer`. - **Breaking:** `CursorIcon::Arrow` was removed. diff --git a/Cargo.toml b/Cargo.toml index c5f9f4066d..5d9523bdd3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,15 +36,15 @@ rustdoc-args = ["--cfg", "docsrs"] [features] default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"] -x11 = ["x11-dl", "mio", "percent-encoding"] -wayland = ["wayland-client", "wayland-backend", "wayland-protocols", "sctk", "fnv"] +x11 = ["x11-dl", "mio", "percent-encoding", "xkbcommon-dl/x11"] +wayland = ["wayland-client", "wayland-backend", "wayland-protocols", "sctk", "fnv", "memmap2"] wayland-dlopen = ["wayland-backend/dlopen"] wayland-csd-adwaita = ["sctk-adwaita", "sctk-adwaita/ab_glyph"] wayland-csd-adwaita-crossfont = ["sctk-adwaita", "sctk-adwaita/crossfont"] wayland-csd-adwaita-notitle = ["sctk-adwaita"] android-native-activity = ["android-activity/native-activity"] android-game-activity = ["android-activity/game-activity"] -serde = ["dep:serde", "cursor-icon/serde"] +serde = ["dep:serde", "cursor-icon/serde", "smol_str/serde"] [build-dependencies] cfg_aliases = "0.1.1" @@ -58,6 +58,7 @@ mint = { version = "0.5.6", optional = true } once_cell = "1.12" raw_window_handle = { package = "raw-window-handle", version = "0.5" } serde = { version = "1", optional = true, features = ["serde_derive"] } +smol_str = "0.2.0" [dev-dependencies] image = { version = "0.24.0", default-features = false, features = ["png"] } @@ -67,6 +68,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" @@ -76,6 +78,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 = [ @@ -110,13 +115,15 @@ libc = "0.2.64" mio = { version = "0.8", features = ["os-ext"], 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 } +sctk = { package = "smithay-client-toolkit", version = "0.17.0", default-features = false, features = ["calloop"], optional = true } sctk-adwaita = { version = "0.6.0", default_features = false, optional = true } wayland-client = { version = "0.30.0", optional = true } wayland-backend = { version = "0.1.0", default_features = false, features = ["client_system"], optional = true } 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" } +memmap2 = { version = "0.5.0", optional = true } [target.'cfg(target_os = "redox")'.dependencies] orbclient = { version = "0.3.42", default-features = false } diff --git a/FEATURES.md b/FEATURES.md index e2693e1964..04155b5dc8 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -222,9 +222,9 @@ Changes in the API that have been agreed upon but aren't implemented across all |Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |Redox OS| |------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ | -|New API for HiDPI ([#315] [#319]) |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |❓ | -|Event Loop 2.0 ([#459]) |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |❓ | -|Keyboard Input ([#812]) |❌ |❌ |❌ |❌ |❌ |❌ |❓ |❓ | +|New API for HiDPI ([#315] [#319]) |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |❓ | +|Event Loop 2.0 ([#459]) |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |✔️ | +|Keyboard Input 2.0 ([#753]) |✔️ |✔️ |✔️ |✔️ |✔️ |❌ |✔️ |✔️ | ### Completed API Reworks |Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |Redox OS| @@ -243,5 +243,5 @@ Changes in the API that have been agreed upon but aren't implemented across all [#720]: https://github.com/rust-windowing/winit/issues/720 [#721]: https://github.com/rust-windowing/winit/issues/721 [#750]: https://github.com/rust-windowing/winit/issues/750 +[#753]: https://github.com/rust-windowing/winit/issues/753 [#804]: https://github.com/rust-windowing/winit/issues/804 -[#812]: https://github.com/rust-windowing/winit/issues/812 diff --git a/docs/res/ATTRIBUTION.md b/docs/res/ATTRIBUTION.md new file mode 100644 index 0000000000..25f51d683e --- /dev/null +++ b/docs/res/ATTRIBUTION.md @@ -0,0 +1,11 @@ +# Image Attribution + +These images are used in the documentation of `winit`. + +## keyboard_*.svg + +These files are a modified version of "[ANSI US QWERTY (Windows)](https://commons.wikimedia.org/wiki/File:ANSI_US_QWERTY_(Windows).svg)" +by [Tomiĉo] (https://commons.wikimedia.org/wiki/User:Tomi%C4%89o). It is +originally released under the [CC-BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/deed.en) +License. Minor modifications have been made by [John Nunley](https://github.com/notgull), +which have been released under the same license as a derivative work. diff --git a/docs/res/keyboard_left_shift_key.svg b/docs/res/keyboard_left_shift_key.svg new file mode 100644 index 0000000000..bae6f9af1f --- /dev/null +++ b/docs/res/keyboard_left_shift_key.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/res/keyboard_numpad_1_key.svg b/docs/res/keyboard_numpad_1_key.svg new file mode 100644 index 0000000000..d5957581c8 --- /dev/null +++ b/docs/res/keyboard_numpad_1_key.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/res/keyboard_right_shift_key.svg b/docs/res/keyboard_right_shift_key.svg new file mode 100644 index 0000000000..dc016f05ca --- /dev/null +++ b/docs/res/keyboard_right_shift_key.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/res/keyboard_standard_1_key.svg b/docs/res/keyboard_standard_1_key.svg new file mode 100644 index 0000000000..3520d5500a --- /dev/null +++ b/docs/res/keyboard_standard_1_key.svg @@ -0,0 +1 @@ + \ No newline at end of file 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..541f13ad8d 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.as_ref() { + // 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 93b69d77ff..ebfa816c5f 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..9759fa9852 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(()), }; @@ -53,7 +56,7 @@ fn main() { println!("error: {err}"); } } - WindowEvent::ModifiersChanged(m) => modifiers = m, + WindowEvent::ModifiersChanged(new) => modifiers = new.state(), _ => (), }, Event::DeviceEvent { event, .. } => match event { diff --git a/examples/drag_window.rs b/examples/drag_window.rs index 813e9b00c9..3bc691523c 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,14 +44,14 @@ 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(c), .. }, .. - } => { + } if c == "x" => { switched = !switched; name_windows(entered_id, switched, &window_1, &window_2); println!("Switched!") 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..11d6fc5848 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.as_ref() { + 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..a768111592 --- /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) => { + modifiers = new.state(); + } + WindowEvent::KeyboardInput { event, .. } => { + if event.state == ElementState::Pressed && !event.repeat { + match event.key_without_modifiers().as_ref() { + 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..d1bff70f85 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(new) => { + modifiers = new.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..0cd505b2fe 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,15 +40,15 @@ fn main() { } } WindowEvent::KeyboardInput { - input: - KeyboardInput { + event: + KeyEvent { state: ElementState::Pressed, - virtual_keycode: Some(VirtualKeyCode::N), + logical_key: Key::Character(c), .. }, is_synthetic: false, .. - } => { + } if matches!(c.as_ref(), "n" | "N") => { let window = Window::new(event_loop).unwrap(); println!("Opened a new window: {:?}", window.id()); windows.insert(window.id(), window); 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..a521752a0a 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,25 +42,25 @@ fn main() { Event::WindowEvent { event: WindowEvent::KeyboardInput { - input: - KeyboardInput { - virtual_keycode: Some(key), + event: + KeyEvent { + logical_key: key, state: ElementState::Pressed, .. }, .. }, .. - } => match key { - VirtualKeyCode::A => { + } => match key.as_ref() { + 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..b7245f5a4f 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,25 +35,25 @@ fn main() { Event::WindowEvent { event: WindowEvent::KeyboardInput { - input: - KeyboardInput { - virtual_keycode: Some(key), + event: + KeyEvent { + logical_key: key, state: ElementState::Pressed, .. }, .. }, .. - } => match key { - VirtualKeyCode::F => { + } => match key.as_ref() { + 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..1a16f513d8 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.as_ref() { + // 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..9bc9ba741d 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,14 +52,14 @@ fn main() { } } WindowEvent::KeyboardInput { - input: - KeyboardInput { + event: + KeyEvent { state: ElementState::Released, - virtual_keycode: Some(VirtualKeyCode::B), + logical_key: Key::Character(c), .. }, .. - } => { + } if matches!(c.as_ref(), "B" | "b") => { border = !border; window.set_decorations(border); } diff --git a/examples/window_option_as_alt.rs b/examples/window_option_as_alt.rs deleted file mode 100644 index b7d288d5bb..0000000000 --- a/examples/window_option_as_alt.rs +++ /dev/null @@ -1,67 +0,0 @@ -#![allow(clippy::single_match)] - -#[cfg(target_os = "macos")] -use winit::platform::macos::{OptionAsAlt, WindowExtMacOS}; - -#[cfg(target_os = "macos")] -use winit::{ - event::ElementState, - event::{Event, MouseButton, WindowEvent}, - event_loop::EventLoop, - window::WindowBuilder, -}; - -/// Prints the keyboard events characters received when option_is_alt is true versus false. -/// A left mouse click will toggle option_is_alt. -#[cfg(target_os = "macos")] -fn main() { - let event_loop = EventLoop::new(); - - let window = WindowBuilder::new() - .with_title("A fantastic window!") - .with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0)) - .build(&event_loop) - .unwrap(); - - let mut option_as_alt = window.option_as_alt(); - - event_loop.run(move |event, _, control_flow| { - control_flow.set_wait(); - - match event { - Event::WindowEvent { - event: WindowEvent::CloseRequested, - window_id, - } if window_id == window.id() => control_flow.set_exit(), - Event::WindowEvent { event, .. } => match event { - WindowEvent::MouseInput { - state: ElementState::Pressed, - button: MouseButton::Left, - .. - } => { - option_as_alt = match option_as_alt { - OptionAsAlt::None => OptionAsAlt::OnlyLeft, - OptionAsAlt::OnlyLeft => OptionAsAlt::OnlyRight, - OptionAsAlt::OnlyRight => OptionAsAlt::Both, - OptionAsAlt::Both => OptionAsAlt::None, - }; - - 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:?}"), - _ => (), - }, - Event::MainEventsCleared => { - window.request_redraw(); - } - _ => (), - } - }); -} - -#[cfg(not(target_os = "macos"))] -fn main() { - println!("This example is only supported on MacOS"); -} 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/src/event.rs b/src/event.rs index 9612368345..cd7944c163 100644 --- a/src/event.rs +++ b/src/event.rs @@ -35,12 +35,14 @@ //! [`EventLoop::run(...)`]: crate::event_loop::EventLoop::run //! [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil use instant::Instant; +use smol_str::SmolStr; use std::path::PathBuf; #[cfg(doc)] use crate::window::Window; use crate::{ dpi::{PhysicalPosition, PhysicalSize}, + keyboard::{self, ModifiersKeyState, ModifiersKeys, ModifiersState}, platform_impl, window::{Theme, WindowId}, }; @@ -256,6 +258,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 +363,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: /// @@ -387,12 +391,7 @@ pub enum WindowEvent<'a> { }, /// The keyboard modifiers have changed. - /// - /// ## Platform-specific - /// - /// - **Web:** This API is currently unimplemented on the web. This isn't by design - it's an - /// issue, and it should get fixed - but it's the current state of the API. - ModifiersChanged(ModifiersState), + ModifiersChanged(Modifiers), /// An event from an input method. /// @@ -411,8 +410,6 @@ pub enum WindowEvent<'a> { /// limited by the display area and it may have been transformed by the OS to implement effects such as cursor /// acceleration, it should not be used to implement non-cursor-like interactions such as 3D camera control. position: PhysicalPosition, - #[deprecated = "Deprecated in favor of WindowEvent::ModifiersChanged"] - modifiers: ModifiersState, }, /// The cursor has entered the window. @@ -426,8 +423,6 @@ pub enum WindowEvent<'a> { device_id: DeviceId, delta: MouseScrollDelta, phase: TouchPhase, - #[deprecated = "Deprecated in favor of WindowEvent::ModifiersChanged"] - modifiers: ModifiersState, }, /// An mouse button press has been received. @@ -435,8 +430,6 @@ pub enum WindowEvent<'a> { device_id: DeviceId, state: ElementState, button: MouseButton, - #[deprecated = "Deprecated in favor of WindowEvent::ModifiersChanged"] - modifiers: ModifiersState, }, /// Touchpad magnification event with two-finger pinch gesture. @@ -560,28 +553,24 @@ 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()), ModifiersChanged(modifiers) => ModifiersChanged(*modifiers), - #[allow(deprecated)] CursorMoved { device_id, position, - modifiers, } => CursorMoved { device_id: *device_id, position: *position, - modifiers: *modifiers, }, CursorEntered { device_id } => CursorEntered { device_id: *device_id, @@ -589,29 +578,23 @@ impl Clone for WindowEvent<'static> { CursorLeft { device_id } => CursorLeft { device_id: *device_id, }, - #[allow(deprecated)] MouseWheel { device_id, delta, phase, - modifiers, } => MouseWheel { device_id: *device_id, delta: *delta, phase: *phase, - modifiers: *modifiers, }, - #[allow(deprecated)] MouseInput { device_id, state, button, - modifiers, } => MouseInput { device_id: *device_id, state: *state, button: *button, - modifiers: *modifiers, }, TouchpadMagnify { device_id, @@ -673,54 +656,44 @@ 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)), + ModifiersChanged(modifers) => Some(ModifiersChanged(modifers)), Ime(event) => Some(Ime(event)), - #[allow(deprecated)] CursorMoved { device_id, position, - modifiers, } => Some(CursorMoved { device_id, position, - modifiers, }), CursorEntered { device_id } => Some(CursorEntered { device_id }), CursorLeft { device_id } => Some(CursorLeft { device_id }), - #[allow(deprecated)] MouseWheel { device_id, delta, phase, - modifiers, } => Some(MouseWheel { device_id, delta, phase, - modifiers, }), - #[allow(deprecated)] MouseInput { device_id, state, button, - modifiers, } => Some(MouseInput { device_id, state, button, - modifiers, }), TouchpadMagnify { device_id, @@ -831,50 +804,224 @@ 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 +pub struct RawKeyEvent { + pub physical_key: keyboard::KeyCode, + pub state: ElementState, +} + +/// 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.) + /// + /// ## Caveats + /// + /// - Certain niche hardware will shuffle around physical key positions, e.g. a keyboard that + /// implements DVORAK in hardware (or firmware) + /// - Your application will likely have to handle keyboards which are missing keys that your + /// own keyboard has. + /// - Certain `KeyCode`s will move between a couple of different positions depending on what + /// layout the keyboard was manufactured to support. + /// + /// **Because of these caveats, it is important that you provide users with a way to configure + /// most (if not all) keybinds in your application.** + /// + /// ## `Fn` and `FnLock` + /// + /// `Fn` and `FnLock` key events are *exceedingly unlikely* to be emitted by Winit. These keys + /// are usually handled at the hardware or OS level, and aren't surfaced to applications. If + /// you somehow see this in the wild, we'd like to know :) + 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`]. + /// + /// If you use this field (or [`key_without_modifiers`] for that matter) for keyboard + /// shortcuts, **it is important that you provide users with a way to configure your + /// application's shortcuts so you don't render your application unusable for users with an + /// incompatible keyboard layout.** + /// + /// ## Platform-specific + /// - **Web:** Dead keys might be reported as the real key instead + /// of `Dead` depending on the browser/OS. + /// + /// [`key_without_modifiers`]: crate::platform::modifier_supplement::KeyEventExtModifierSupplement::key_without_modifiers + pub logical_key: keyboard::Key, + + /// 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 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, + /// This is `None` if the current keypress cannot + /// be interpreted as text. + /// + /// See also: `text_with_all_modifiers()` + pub text: Option, + /// Contains the location of this key on the keyboard. + /// + /// Certain keys on the keyboard may appear in more than once place. For example, the "Shift" key + /// appears on the left side of the QWERTY keyboard as well as the right side. However, both keys + /// have the same symbolic value. Another example of this phenomenon is the "1" key, which appears + /// both above the "Q" key and as the "Keypad 1" key. + /// + /// This field allows the user to differentiate between keys like this that have the same symbolic + /// value but different locations on the keyboard. + /// + /// See the [`KeyLocation`] type for more details. + /// + /// [`KeyLocation`]: crate::keyboard::KeyLocation + pub location: keyboard::KeyLocation, + + /// Whether the key is being pressed or released. + /// + /// See the [`ElementState`] type for more details. pub state: ElementState, - /// Identifies the semantic meaning of the key + /// Whether or not this key is a key repeat event. /// - /// 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, + /// On some systems, holding down a key for some period of time causes that key to be repeated + /// as though it were being pressed and released repeatedly. This field is `true` if and only if + /// this event is the result of one of those repeats. + pub repeat: bool, - /// Modifier keys active at the time of this input. + /// Platform-specific key event information. /// - /// 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, + /// On Windows, Linux and macOS, this type contains the key without modifiers and the text with all + /// modifiers applied. + /// + /// On Android, iOS, Redox and Web, this type is a no-op. + pub(crate) platform_specific: platform_impl::KeyEventExtra, +} + +/// Describes keyboard modifiers event. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +pub struct Modifiers { + pub(crate) state: ModifiersState, + + // NOTE: Currently pressed modifiers keys. + // + // The field providing a metadata, it shouldn't be used as a source of truth. + pub(crate) pressed_mods: ModifiersKeys, +} + +impl Modifiers { + /// The state of the modifiers. + pub fn state(&self) -> ModifiersState { + self.state + } + + /// The state of the left shift key. + pub fn lshift_state(&self) -> ModifiersKeyState { + self.mod_state(ModifiersKeys::LSHIFT) + } + + /// The state of the right shift key. + pub fn rshift_state(&self) -> ModifiersKeyState { + self.mod_state(ModifiersKeys::RSHIFT) + } + + /// The state of the left alt key. + pub fn lalt_state(&self) -> ModifiersKeyState { + self.mod_state(ModifiersKeys::LALT) + } + + /// The state of the right alt key. + pub fn ralt_state(&self) -> ModifiersKeyState { + self.mod_state(ModifiersKeys::RALT) + } + + /// The state of the left control key. + pub fn lcontrol_state(&self) -> ModifiersKeyState { + self.mod_state(ModifiersKeys::LCONTROL) + } + + /// The state of the right control key. + pub fn rcontrol_state(&self) -> ModifiersKeyState { + self.mod_state(ModifiersKeys::RCONTROL) + } + + /// The state of the left super key. + pub fn lsuper_state(&self) -> ModifiersKeyState { + self.mod_state(ModifiersKeys::LSUPER) + } + + /// The state of the right super key. + pub fn rsuper_state(&self) -> ModifiersKeyState { + self.mod_state(ModifiersKeys::RSUPER) + } + + fn mod_state(&self, modifier: ModifiersKeys) -> ModifiersKeyState { + if self.pressed_mods.contains(modifier) { + ModifiersKeyState::Pressed + } else { + ModifiersKeyState::Unknown + } + } +} + +impl From for Modifiers { + fn from(value: ModifiersState) -> Self { + Self { + state: value, + pressed_mods: Default::default(), + } + } } /// 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 +1033,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 +1184,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 +1234,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..15f39702ef --- /dev/null +++ b/src/keyboard.rs @@ -0,0 +1,1688 @@ +//! 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 smol_str::SmolStr; + +/// 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, Copy, 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("Unidentified"); + } + Android(code) => { + debug_tuple = f.debug_tuple("Android"); + debug_tuple.field(&format_args!("0x{code:04X}")); + } + MacOS(code) => { + debug_tuple = f.debug_tuple("MacOS"); + debug_tuple.field(&format_args!("0x{code:04X}")); + } + Windows(code) => { + debug_tuple = f.debug_tuple("Windows"); + debug_tuple.field(&format_args!("0x{code:04X}")); + } + Xkb(code) => { + debug_tuple = f.debug_tuple("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(SmolStr), +} + +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("Unidentified"); + } + Android(code) => { + debug_tuple = f.debug_tuple("Android"); + debug_tuple.field(&format_args!("0x{code:04X}")); + } + MacOS(code) => { + debug_tuple = f.debug_tuple("MacOS"); + debug_tuple.field(&format_args!("0x{code:04X}")); + } + Windows(code) => { + debug_tuple = f.debug_tuple("Windows"); + debug_tuple.field(&format_args!("0x{code:04X}")); + } + Xkb(code) => { + debug_tuple = f.debug_tuple("Xkb"); + debug_tuple.field(&format_args!("0x{code:04X}")); + } + Web(code) => { + debug_tuple = f.debug_tuple("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 identify a key through it's `NativeKeyCode`. +/// +/// [`KeyboardEvent.code`]: https://w3c.github.io/uievents-code/#code-value-tables +#[non_exhaustive] +#[derive(Debug, Clone, Copy, 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 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(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, +} + +macro_rules! map_match { + ( + $to_match:expr, + // Custom match arms + { $( $from:pat => $to:expr ),* }, + // The enum's name + $prefix:path, + // Trivial match arms for unit variants + { $( $t:tt ),* }) => { + match $to_match { + $( $from => $to, )* + $( Key::$t => Key::$t, )* + } + }; +} + +impl Key { + /// Convert `Key::Character(SmolStr)` to `Key::Character(&str)` so you can more easily match on + /// `Key`. All other variants remain unchanged. + pub fn as_ref(&self) -> Key<&str> { + map_match!( + self, + { + Key::Character(ch) => Key::Character(ch.as_str()), + Key::Dead(d) => Key::Dead(*d), + Key::Unidentified(u) => Key::Unidentified(u.clone()) + }, + Key, + { + Alt, AltGraph, CapsLock, Control, Fn, FnLock, NumLock, ScrollLock, Shift, Symbol, + SymbolLock, Meta, Hyper, Super, Enter, Tab, Space, ArrowDown, ArrowLeft, + ArrowRight, ArrowUp, End, Home, PageDown, PageUp, Backspace, Clear, Copy, CrSel, + Cut, Delete, EraseEof, ExSel, Insert, Paste, Redo, Undo, Accept, Again, Attn, + Cancel, ContextMenu, Escape, Execute, Find, Help, Pause, Play, Props, Select, + ZoomIn, ZoomOut, BrightnessDown, BrightnessUp, Eject, LogOff, Power, PowerOff, + PrintScreen, Hibernate, Standby, WakeUp, AllCandidates, Alphanumeric, CodeInput, + Compose, Convert, FinalMode, GroupFirst, GroupLast, GroupNext, GroupPrevious, + ModeChange, NextCandidate, NonConvert, PreviousCandidate, Process, SingleCandidate, + HangulMode, HanjaMode, JunjaMode, Eisu, Hankaku, Hiragana, HiraganaKatakana, + KanaMode, KanjiMode, Katakana, Romaji, Zenkaku, ZenkakuHankaku, Soft1, Soft2, + Soft3, Soft4, ChannelDown, ChannelUp, Close, MailForward, MailReply, MailSend, + MediaClose, MediaFastForward, MediaPause, MediaPlay, MediaPlayPause, MediaRecord, + MediaRewind, MediaStop, MediaTrackNext, MediaTrackPrevious, New, Open, Print, Save, + SpellCheck, Key11, Key12, AudioBalanceLeft, AudioBalanceRight, AudioBassBoostDown, + AudioBassBoostToggle, AudioBassBoostUp, AudioFaderFront, AudioFaderRear, + AudioSurroundModeNext, AudioTrebleDown, AudioTrebleUp, AudioVolumeDown, + AudioVolumeUp, AudioVolumeMute, MicrophoneToggle, MicrophoneVolumeDown, + MicrophoneVolumeUp, MicrophoneVolumeMute, SpeechCorrectionList, SpeechInputToggle, + LaunchApplication1, LaunchApplication2, LaunchCalendar, LaunchContacts, LaunchMail, + LaunchMediaPlayer, LaunchMusicPlayer, LaunchPhone, LaunchScreenSaver, + LaunchSpreadsheet, LaunchWebBrowser, LaunchWebCam, LaunchWordProcessor, + BrowserBack, BrowserFavorites, BrowserForward, BrowserHome, BrowserRefresh, + BrowserSearch, BrowserStop, AppSwitch, Call, Camera, CameraFocus, EndCall, GoBack, + GoHome, HeadsetHook, LastNumberRedial, Notification, MannerMode, VoiceDial, TV, + TV3DMode, TVAntennaCable, TVAudioDescription, TVAudioDescriptionMixDown, + TVAudioDescriptionMixUp, TVContentsMenu, TVDataService, TVInput, TVInputComponent1, + TVInputComponent2, TVInputComposite1, TVInputComposite2, TVInputHDMI1, + TVInputHDMI2, TVInputHDMI3, TVInputHDMI4, TVInputVGA1, TVMediaContext, TVNetwork, + TVNumberEntry, TVPower, TVRadioService, TVSatellite, TVSatelliteBS, TVSatelliteCS, + TVSatelliteToggle, TVTerrestrialAnalog, TVTerrestrialDigital, TVTimer, AVRInput, + AVRPower, ColorF0Red, ColorF1Green, ColorF2Yellow, ColorF3Blue, ColorF4Grey, + ColorF5Brown, ClosedCaptionToggle, Dimmer, DisplaySwap, DVR, Exit, FavoriteClear0, + FavoriteClear1, FavoriteClear2, FavoriteClear3, FavoriteRecall0, FavoriteRecall1, + FavoriteRecall2, FavoriteRecall3, FavoriteStore0, FavoriteStore1, FavoriteStore2, + FavoriteStore3, Guide, GuideNextDay, GuidePreviousDay, Info, InstantReplay, Link, + ListProgram, LiveContent, Lock, MediaApps, MediaAudioTrack, MediaLast, + MediaSkipBackward, MediaSkipForward, MediaStepBackward, MediaStepForward, + MediaTopMenu, NavigateIn, NavigateNext, NavigateOut, NavigatePrevious, + NextFavoriteChannel, NextUserProfile, OnDemand, Pairing, PinPDown, PinPMove, + PinPToggle, PinPUp, PlaySpeedDown, PlaySpeedReset, PlaySpeedUp, RandomToggle, + RcLowBattery, RecordSpeedNext, RfBypass, ScanChannelsToggle, ScreenModeNext, + Settings, SplitScreenToggle, STBInput, STBPower, Subtitle, Teletext, VideoModeNext, + Wink, ZoomToggle, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, + F16, F17, F18, F19, F20, F21, F22, F23, F24, F25, F26, F27, F28, F29, F30, F31, + F32, F33, F34, F35 + } + ) + } +} + +impl Key { + /// Convert a key to its approximate textual equivalent. + /// + /// # Examples + /// + /// ``` + /// use winit::keyboard::Key; + /// + /// assert_eq!(Key::Character("a".into()).to_text(), Some("a")); + /// assert_eq!(Key::Enter.to_text(), Some("\r")); + /// assert_eq!(Key::F20.to_text(), None); + /// ``` + pub fn to_text(&self) -> Option<&str> { + match self { + Key::Character(ch) => Some(ch.as_str()), + Key::Enter => Some("\r"), + Key::Backspace => Some("\x08"), + Key::Tab => Some("\t"), + Key::Space => Some(" "), + Key::Escape => Some("\x1b"), + _ => None, + } + } +} + +/// The location of the key on the keyboard. +/// +/// Certain physical keys on the keyboard can have the same value, but are in different locations. +/// For instance, the Shift key can be on the left or right side of the keyboard, or the number +/// keys can be above the letters or on the numpad. This enum allows the user to differentiate +/// them. +/// +/// See the documentation for the [`location`] field on the [`KeyEvent`] struct for more information. +/// +/// [`location`]: ../event/struct.KeyEvent.html#structfield.location +/// [`KeyEvent`]: crate::event::KeyEvent +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum KeyLocation { + /// The key is in its "normal" location on the keyboard. + /// + /// For instance, the "1" key above the "Q" key on a QWERTY keyboard will use this location. This + /// invariant is also returned when the location of the key cannot be identified. + /// + /// ![Standard 1 key](https://github.com/raw/rust-windowing/winit/master/docs/res/keyboard_standard_1_key.svg) + /// + /// + /// For image attribution, see the + /// + /// ATTRIBUTION.md + /// + /// file. + /// + Standard, + + /// The key is on the left side of the keyboard. + /// + /// For instance, the left Shift key below the Caps Lock key on a QWERTY keyboard will use this + /// location. + /// + /// ![Left Shift key](https://github.com/raw/rust-windowing/winit/master/docs/res/keyboard_left_shift_key.svg) + /// + /// + /// For image attribution, see the + /// + /// ATTRIBUTION.md + /// + /// file. + /// + Left, + + /// The key is on the right side of the keyboard. + /// + /// For instance, the right Shift key below the Enter key on a QWERTY keyboard will use this + /// location. + /// + /// ![Right Shift key](https://github.com/raw/rust-windowing/winit/master/docs/res/keyboard_right_shift_key.svg) + /// + /// + /// For image attribution, see the + /// + /// ATTRIBUTION.md + /// + /// file. + /// + Right, + + /// The key is on the numpad. + /// + /// For instance, the "1" key on the numpad will use this location. + /// + /// ![Numpad 1 key](https://github.com/raw/rust-windowing/winit/master/docs/res/keyboard_numpad_1_key.svg) + /// + /// + /// For image attribution, see the + /// + /// ATTRIBUTION.md + /// + /// file. + /// + Numpad, +} + +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 { + /// The "shift" key. + const SHIFT = 0b100; + /// The "control" key. + const CONTROL = 0b100 << 3; + /// The "alt" key. + const ALT = 0b100 << 6; + /// This is the "windows" key on PC and "command" key on Mac. + const SUPER = 0b100 << 9; + } +} + +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) + } +} + +/// The state of the particular modifiers key. +#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] +pub enum ModifiersKeyState { + /// The particular key is pressed. + Pressed, + /// The state of the key is unknown. + #[default] + Unknown, +} + +// NOTE: the exact modifier key is not used to represent modifiers state in the +// first place due to a fact that modifiers state could be changed without any +// key being pressed and on some platforms like Wayland/X11 which key resulted +// in modifiers change is hidden, also, not that it really matters. +// +// The reason this API is even exposed is mostly to provide a way for users +// to treat modifiers differently based on their position, which is required +// on macOS due to their AltGr/Option situation. +bitflags! { + #[derive(Default)] + pub(crate) struct ModifiersKeys: u8 { + const LSHIFT = 0b0000_0001; + const RSHIFT = 0b0000_0010; + const LCONTROL = 0b0000_0100; + const RCONTROL = 0b0000_1000; + const LALT = 0b0001_0000; + const RALT = 0b0010_0000; + const LSUPER = 0b0100_0000; + const RSUPER = 0b1000_0000; + } +} + +#[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) + } + } +} 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/macos.rs b/src/platform/macos.rs index 0b6ebf11ed..e4da83bd1c 100644 --- a/src/platform/macos.rs +++ b/src/platform/macos.rs @@ -56,17 +56,6 @@ pub trait WindowExtMacOS { /// Put the window in a state which indicates a file save is required. fn set_document_edited(&self, edited: bool); - - /// Set option as alt behavior as described in [`OptionAsAlt`]. - /// - /// This will ignore diacritical marks and accent characters from - /// being processed as received characters. Instead, the input - /// device's raw character will be placed in event queues with the - /// Alt modifier set. - fn set_option_as_alt(&self, option_as_alt: OptionAsAlt); - - /// Getter for the [`WindowExtMacOS::set_option_as_alt`]. - fn option_as_alt(&self) -> OptionAsAlt; } impl WindowExtMacOS for Window { @@ -109,16 +98,6 @@ impl WindowExtMacOS for Window { fn set_document_edited(&self, edited: bool) { self.window.set_document_edited(edited) } - - #[inline] - fn set_option_as_alt(&self, option_as_alt: OptionAsAlt) { - self.window.set_option_as_alt(option_as_alt) - } - - #[inline] - fn option_as_alt(&self) -> OptionAsAlt { - self.window.option_as_alt() - } } /// Corresponds to `NSApplicationActivationPolicy`. @@ -161,11 +140,6 @@ pub trait WindowBuilderExtMacOS { fn with_has_shadow(self, has_shadow: bool) -> WindowBuilder; /// Window accepts click-through mouse events. fn with_accepts_first_mouse(self, accepts_first_mouse: bool) -> WindowBuilder; - - /// Set whether the `OptionAsAlt` key is interpreted as the `Alt` modifier. - /// - /// See [`WindowExtMacOS::set_option_as_alt`] for details on what this means if set. - fn with_option_as_alt(self, option_as_alt: OptionAsAlt) -> WindowBuilder; } impl WindowBuilderExtMacOS for WindowBuilder { @@ -225,12 +199,6 @@ impl WindowBuilderExtMacOS for WindowBuilder { self.platform_specific.accepts_first_mouse = accepts_first_mouse; self } - - #[inline] - fn with_option_as_alt(mut self, option_as_alt: OptionAsAlt) -> WindowBuilder { - self.platform_specific.option_as_alt = option_as_alt; - self - } } pub trait EventLoopBuilderExtMacOS { @@ -341,23 +309,3 @@ impl EventLoopWindowTargetExtMacOS for EventLoopWindowTarget { self.p.hide_other_applications() } } - -/// Option as alt behavior. -/// -/// The default is `None`. -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub enum OptionAsAlt { - /// The left `Option` key is treated as `Alt`. - OnlyLeft, - - /// The right `Option` key is treated as `Alt`. - OnlyRight, - - /// Both `Option` keys are treated as `Alt`. - Both, - - /// No special handling is applied for `Option` key. - #[default] - None, -} 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..2b0302bf78 --- /dev/null +++ b/src/platform/modifier_supplement.rs @@ -0,0 +1,24 @@ +#![cfg(any(windows_platform, macos_platform, x11_platform, wayland_platform))] + +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; +} diff --git a/src/platform/scancode.rs b/src/platform/scancode.rs new file mode 100644 index 0000000000..25109a04af --- /dev/null +++ b/src/platform/scancode.rs @@ -0,0 +1,28 @@ +#![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. + /// + /// 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/platform/windows.rs b/src/platform/windows.rs index 94d4f4040d..626e2fba6f 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -2,9 +2,11 @@ use std::{ffi::c_void, path::Path}; use crate::{ dpi::PhysicalSize, - event::DeviceId, + event::{DeviceId, KeyEvent}, event_loop::EventLoopBuilder, + keyboard::Key, monitor::MonitorHandle, + platform::modifier_supplement::KeyEventExtModifierSupplement, platform_impl::WinIcon, window::{BadIcon, Icon, Window, WindowBuilder}, }; @@ -344,3 +346,18 @@ 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 + .as_ref() + .map(|s| s.as_str()) + } + + #[inline] + fn key_without_modifiers(&self) -> Key { + self.platform_specific.key_without_modifiers.clone() + } +} diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index 2e1924e27b..228a951085 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 { + 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, + } +} 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 { diff --git a/src/platform_impl/linux/common/keymap.rs b/src/platform_impl/linux/common/keymap.rs new file mode 100644 index 0000000000..830cef9c7d --- /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 { + 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..55e2119a99 --- /dev/null +++ b/src/platform_impl/linux/common/xkb_state.rs @@ -0,0 +1,668 @@ +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}; + +use smol_str::SmolStr; +use xkbcommon_dl::{ + self as ffi, xkb_state_component, XKBCOMMON_COMPOSE_HANDLE as XKBCH, XKBCOMMON_HANDLE as XKBH, +}; +#[cfg(feature = "wayland")] +use {memmap2::MmapOptions, wayland_backend::io_lifetimes::OwnedFd}; +#[cfg(feature = "x11")] +use {x11_dl::xlib_xcb::xcb_connection_t, xkbcommon_dl::x11::XKBCOMMON_X11_HANDLE as XKBXH}; + +use crate::event::KeyEvent; +use crate::platform_impl::common::keymap; +use crate::platform_impl::KeyEventExtra; +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(always)] +pub fn reset_dead_keys() { + RESET_DEAD_KEYS.store(true, Ordering::SeqCst); +} + +#[derive(Debug)] +pub struct KbdState { + #[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 = "x11")] + pub core_keyboard_id: i32, + scratch_buffer: Vec, +} + +impl KbdState { + pub 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 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 fn get_utf8_raw(&mut self, keycode: u32) -> Option { + if !self.ready() { + return None; + } + let xkb_state = self.xkb_state; + self.make_string_with({ + |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) + } + + 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 { + self.compose_get_utf8(self.xkb_compose_state) + } + + fn compose_get_utf8_2(&mut self) -> Option { + self.compose_get_utf8(self.xkb_compose_state_2) + } + + fn compose_get_utf8( + &mut self, + xkb_compose_state: *mut ffi::xkb_compose_state, + ) -> Option { + if !self.ready() || xkb_compose_state.is_null() { + return None; + } + self.make_string_with(|ptr, len| unsafe { + (XKBCH.xkb_compose_state_get_utf8)(xkb_compose_state, ptr, len) + }) + } + + /// Shared logic for constructing a string with `xkb_compose_state_get_utf8` and + /// `xkb_state_key_get_utf8`. + fn make_string_with(&mut self, mut f: F) -> Option + 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); + }; + byte_slice_to_smol_str(&self.scratch_buffer) + } + + pub 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 = "x11")] + core_keyboard_id: 0, + scratch_buffer: Vec::new(), + }; + + unsafe { me.init_compose() }; + + Ok(me) + } + + #[cfg(feature = "x11")] + pub 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 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, + ); + if keymap.is_null() { + panic!("Failed to get keymap from X11 server."); + } + + 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 unsafe fn init_with_fd(&mut self, fd: OwnedFd, size: usize) { + if !self.xkb_keymap.is_null() { + self.de_init(); + } + + let map = MmapOptions::new() + .len(size) + .map_copy_read_only(&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); + } + + #[cfg(feature = "wayland")] + pub fn key_repeats(&mut self, keycode: ffi::xkb_keycode_t) -> bool { + unsafe { (XKBH.xkb_keymap_key_repeats)(self.xkb_keymap, keycode) == 1 } + } + + #[inline] + pub fn ready(&self) -> bool { + !self.xkb_state.is_null() + } + + #[inline] + pub fn mods_state(&self) -> ModifiersState { + self.mods_state + } + + pub fn process_key_event( + &mut self, + keycode: u32, + state: ElementState, + repeat: bool, + ) -> KeyEvent { + let mut event = + KeyEventResults::new(self, keycode, !repeat && state == ElementState::Pressed); + let physical_key = event.keycode(); + let (logical_key, location) = event.key(); + let text = event.text(); + let (key_without_modifiers, _) = event.key_without_modifiers(); + let text_with_all_modifiers = event.text_with_all_modifiers(); + + let platform_specific = KeyEventExtra { + key_without_modifiers, + text_with_all_modifiers, + }; + + KeyEvent { + physical_key, + logical_key, + text, + location, + state, + repeat, + platform_specific, + } + } + + fn keysym_to_utf8_raw(&mut self, keysym: u32) -> Option { + 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(); + byte_slice_to_smol_str(&self.scratch_buffer) + } +} + +impl Drop for KbdState { + 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); + } + } +} + +struct KeyEventResults<'a> { + state: &'a mut KbdState, + keycode: u32, + keysym: u32, + compose: Option, +} + +impl<'a> KeyEventResults<'a> { + fn new(state: &'a mut KbdState, 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, + } + } + + fn keycode(&mut self) -> KeyCode { + keymap::raw_keycode_to_keycode(self.keycode) + } + + pub fn key(&mut self) -> (Key, 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, 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, KeyLocation), (Key, 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 { + self.composed_text() + .unwrap_or_else(|_| self.state.keysym_to_utf8_raw(self.keysym)) + } + + pub fn text_with_all_modifiers(&mut self) -> Option { + // 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(()) + } + } +} + +/// 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 + } +} + +#[derive(Debug)] +pub enum Error { + /// libxkbcommon is not available + XKBNotFound, +} + +#[derive(Copy, Clone, Debug)] +enum XkbCompose { + Accepted(ffi::xkb_compose_status), + Ignored, + Uninitialized, +} + +// Note: This is track_caller so we can have more informative line numbers when logging +#[track_caller] +fn byte_slice_to_smol_str(bytes: &[u8]) -> Option { + std::str::from_utf8(bytes) + .map(SmolStr::new) + .map_err(|e| { + warn!( + "UTF-8 received from libxkbcommon ({:?}) was invalid: {e}", + bytes + ) + }) + .ok() +} diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index 0a90d085ee..119143a56c 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -18,6 +18,7 @@ use std::{ #[cfg(x11_platform)] use once_cell::sync::Lazy; use raw_window_handle::{RawDisplayHandle, RawWindowHandle}; +use smol_str::SmolStr; #[cfg(x11_platform)] pub use self::x11::XNotSupported; @@ -28,11 +29,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, @@ -42,6 +45,7 @@ use crate::{ pub(crate) use crate::icon::RgbaIcon as PlatformIcon; pub(self) use crate::platform_impl::Fullscreen; +pub mod common; #[cfg(wayland_platform)] pub mod wayland; #[cfg(x11_platform)] @@ -514,6 +518,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 +633,37 @@ impl Window { } } +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct KeyEventExtra { + pub key_without_modifiers: Key, + pub text_with_all_modifiers: Option, +} + +impl KeyEventExtModifierSupplement for KeyEvent { + #[inline] + fn text_with_all_modifiers(&self) -> Option<&str> { + self.platform_specific + .text_with_all_modifiers + .as_ref() + .map(|s| s.as_str()) + } + + #[inline] + fn key_without_modifiers(&self) -> Key { + 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/wayland/seat/keyboard/keymap.rs b/src/platform_impl/linux/wayland/seat/keyboard/keymap.rs deleted file mode 100644 index e13488e229..0000000000 --- a/src/platform_impl/linux/wayland/seat/keyboard/keymap.rs +++ /dev/null @@ -1,192 +0,0 @@ -use sctk::seat::keyboard::keysyms; - -use crate::event::VirtualKeyCode; - -/// Convert xkb keysym into winit's VirtualKeyCode. -pub fn keysym_to_vkey(keysym: u32) -> Option { - match keysym { - // Numbers. - keysyms::XKB_KEY_1 => Some(VirtualKeyCode::Key1), - keysyms::XKB_KEY_2 => Some(VirtualKeyCode::Key2), - keysyms::XKB_KEY_3 => Some(VirtualKeyCode::Key3), - keysyms::XKB_KEY_4 => Some(VirtualKeyCode::Key4), - keysyms::XKB_KEY_5 => Some(VirtualKeyCode::Key5), - keysyms::XKB_KEY_6 => Some(VirtualKeyCode::Key6), - keysyms::XKB_KEY_7 => Some(VirtualKeyCode::Key7), - keysyms::XKB_KEY_8 => Some(VirtualKeyCode::Key8), - keysyms::XKB_KEY_9 => Some(VirtualKeyCode::Key9), - keysyms::XKB_KEY_0 => Some(VirtualKeyCode::Key0), - // Letters. - keysyms::XKB_KEY_A | keysyms::XKB_KEY_a => Some(VirtualKeyCode::A), - keysyms::XKB_KEY_B | keysyms::XKB_KEY_b => Some(VirtualKeyCode::B), - keysyms::XKB_KEY_C | keysyms::XKB_KEY_c => Some(VirtualKeyCode::C), - keysyms::XKB_KEY_D | keysyms::XKB_KEY_d => Some(VirtualKeyCode::D), - keysyms::XKB_KEY_E | keysyms::XKB_KEY_e => Some(VirtualKeyCode::E), - keysyms::XKB_KEY_F | keysyms::XKB_KEY_f => Some(VirtualKeyCode::F), - keysyms::XKB_KEY_G | keysyms::XKB_KEY_g => Some(VirtualKeyCode::G), - keysyms::XKB_KEY_H | keysyms::XKB_KEY_h => Some(VirtualKeyCode::H), - keysyms::XKB_KEY_I | keysyms::XKB_KEY_i => Some(VirtualKeyCode::I), - keysyms::XKB_KEY_J | keysyms::XKB_KEY_j => Some(VirtualKeyCode::J), - keysyms::XKB_KEY_K | keysyms::XKB_KEY_k => Some(VirtualKeyCode::K), - keysyms::XKB_KEY_L | keysyms::XKB_KEY_l => Some(VirtualKeyCode::L), - keysyms::XKB_KEY_M | keysyms::XKB_KEY_m => Some(VirtualKeyCode::M), - keysyms::XKB_KEY_N | keysyms::XKB_KEY_n => Some(VirtualKeyCode::N), - keysyms::XKB_KEY_O | keysyms::XKB_KEY_o => Some(VirtualKeyCode::O), - keysyms::XKB_KEY_P | keysyms::XKB_KEY_p => Some(VirtualKeyCode::P), - keysyms::XKB_KEY_Q | keysyms::XKB_KEY_q => Some(VirtualKeyCode::Q), - keysyms::XKB_KEY_R | keysyms::XKB_KEY_r => Some(VirtualKeyCode::R), - keysyms::XKB_KEY_S | keysyms::XKB_KEY_s => Some(VirtualKeyCode::S), - keysyms::XKB_KEY_T | keysyms::XKB_KEY_t => Some(VirtualKeyCode::T), - keysyms::XKB_KEY_U | keysyms::XKB_KEY_u => Some(VirtualKeyCode::U), - keysyms::XKB_KEY_V | keysyms::XKB_KEY_v => Some(VirtualKeyCode::V), - keysyms::XKB_KEY_W | keysyms::XKB_KEY_w => Some(VirtualKeyCode::W), - keysyms::XKB_KEY_X | keysyms::XKB_KEY_x => Some(VirtualKeyCode::X), - keysyms::XKB_KEY_Y | keysyms::XKB_KEY_y => Some(VirtualKeyCode::Y), - keysyms::XKB_KEY_Z | keysyms::XKB_KEY_z => Some(VirtualKeyCode::Z), - // Escape. - keysyms::XKB_KEY_Escape => Some(VirtualKeyCode::Escape), - // Function keys. - keysyms::XKB_KEY_F1 => Some(VirtualKeyCode::F1), - keysyms::XKB_KEY_F2 => Some(VirtualKeyCode::F2), - keysyms::XKB_KEY_F3 => Some(VirtualKeyCode::F3), - keysyms::XKB_KEY_F4 => Some(VirtualKeyCode::F4), - keysyms::XKB_KEY_F5 => Some(VirtualKeyCode::F5), - keysyms::XKB_KEY_F6 => Some(VirtualKeyCode::F6), - keysyms::XKB_KEY_F7 => Some(VirtualKeyCode::F7), - keysyms::XKB_KEY_F8 => Some(VirtualKeyCode::F8), - keysyms::XKB_KEY_F9 => Some(VirtualKeyCode::F9), - keysyms::XKB_KEY_F10 => Some(VirtualKeyCode::F10), - keysyms::XKB_KEY_F11 => Some(VirtualKeyCode::F11), - keysyms::XKB_KEY_F12 => Some(VirtualKeyCode::F12), - keysyms::XKB_KEY_F13 => Some(VirtualKeyCode::F13), - keysyms::XKB_KEY_F14 => Some(VirtualKeyCode::F14), - keysyms::XKB_KEY_F15 => Some(VirtualKeyCode::F15), - keysyms::XKB_KEY_F16 => Some(VirtualKeyCode::F16), - keysyms::XKB_KEY_F17 => Some(VirtualKeyCode::F17), - keysyms::XKB_KEY_F18 => Some(VirtualKeyCode::F18), - keysyms::XKB_KEY_F19 => Some(VirtualKeyCode::F19), - keysyms::XKB_KEY_F20 => Some(VirtualKeyCode::F20), - keysyms::XKB_KEY_F21 => Some(VirtualKeyCode::F21), - keysyms::XKB_KEY_F22 => Some(VirtualKeyCode::F22), - keysyms::XKB_KEY_F23 => Some(VirtualKeyCode::F23), - keysyms::XKB_KEY_F24 => Some(VirtualKeyCode::F24), - // Flow control. - keysyms::XKB_KEY_Print => Some(VirtualKeyCode::Snapshot), - keysyms::XKB_KEY_Scroll_Lock => Some(VirtualKeyCode::Scroll), - keysyms::XKB_KEY_Pause => Some(VirtualKeyCode::Pause), - keysyms::XKB_KEY_Insert => Some(VirtualKeyCode::Insert), - keysyms::XKB_KEY_Home => Some(VirtualKeyCode::Home), - keysyms::XKB_KEY_Delete => Some(VirtualKeyCode::Delete), - keysyms::XKB_KEY_End => Some(VirtualKeyCode::End), - keysyms::XKB_KEY_Page_Down => Some(VirtualKeyCode::PageDown), - keysyms::XKB_KEY_Page_Up => Some(VirtualKeyCode::PageUp), - // Arrows. - keysyms::XKB_KEY_Left => Some(VirtualKeyCode::Left), - keysyms::XKB_KEY_Up => Some(VirtualKeyCode::Up), - keysyms::XKB_KEY_Right => Some(VirtualKeyCode::Right), - keysyms::XKB_KEY_Down => Some(VirtualKeyCode::Down), - - keysyms::XKB_KEY_BackSpace => Some(VirtualKeyCode::Back), - keysyms::XKB_KEY_Return => Some(VirtualKeyCode::Return), - keysyms::XKB_KEY_space => Some(VirtualKeyCode::Space), - - keysyms::XKB_KEY_Multi_key => Some(VirtualKeyCode::Compose), - keysyms::XKB_KEY_caret => Some(VirtualKeyCode::Caret), - - // Keypad. - keysyms::XKB_KEY_Num_Lock => Some(VirtualKeyCode::Numlock), - keysyms::XKB_KEY_KP_0 => Some(VirtualKeyCode::Numpad0), - keysyms::XKB_KEY_KP_1 => Some(VirtualKeyCode::Numpad1), - keysyms::XKB_KEY_KP_2 => Some(VirtualKeyCode::Numpad2), - keysyms::XKB_KEY_KP_3 => Some(VirtualKeyCode::Numpad3), - keysyms::XKB_KEY_KP_4 => Some(VirtualKeyCode::Numpad4), - keysyms::XKB_KEY_KP_5 => Some(VirtualKeyCode::Numpad5), - keysyms::XKB_KEY_KP_6 => Some(VirtualKeyCode::Numpad6), - keysyms::XKB_KEY_KP_7 => Some(VirtualKeyCode::Numpad7), - keysyms::XKB_KEY_KP_8 => Some(VirtualKeyCode::Numpad8), - keysyms::XKB_KEY_KP_9 => Some(VirtualKeyCode::Numpad9), - // Misc. - // => Some(VirtualKeyCode::AbntC1), - // => Some(VirtualKeyCode::AbntC2), - keysyms::XKB_KEY_plus => Some(VirtualKeyCode::Plus), - keysyms::XKB_KEY_apostrophe => Some(VirtualKeyCode::Apostrophe), - // => Some(VirtualKeyCode::Apps), - keysyms::XKB_KEY_at => Some(VirtualKeyCode::At), - // => Some(VirtualKeyCode::Ax), - keysyms::XKB_KEY_backslash => Some(VirtualKeyCode::Backslash), - keysyms::XKB_KEY_XF86Calculator => Some(VirtualKeyCode::Calculator), - // => Some(VirtualKeyCode::Capital), - keysyms::XKB_KEY_colon => Some(VirtualKeyCode::Colon), - keysyms::XKB_KEY_comma => Some(VirtualKeyCode::Comma), - // => Some(VirtualKeyCode::Convert), - keysyms::XKB_KEY_equal => Some(VirtualKeyCode::Equals), - keysyms::XKB_KEY_grave => Some(VirtualKeyCode::Grave), - // => Some(VirtualKeyCode::Kana), - keysyms::XKB_KEY_Kanji => Some(VirtualKeyCode::Kanji), - keysyms::XKB_KEY_Alt_L => Some(VirtualKeyCode::LAlt), - keysyms::XKB_KEY_bracketleft => Some(VirtualKeyCode::LBracket), - keysyms::XKB_KEY_Control_L => Some(VirtualKeyCode::LControl), - keysyms::XKB_KEY_Shift_L => Some(VirtualKeyCode::LShift), - keysyms::XKB_KEY_Super_L => Some(VirtualKeyCode::LWin), - keysyms::XKB_KEY_XF86Mail => Some(VirtualKeyCode::Mail), - // => Some(VirtualKeyCode::MediaSelect), - // => Some(VirtualKeyCode::MediaStop), - keysyms::XKB_KEY_minus => Some(VirtualKeyCode::Minus), - keysyms::XKB_KEY_asterisk => Some(VirtualKeyCode::Asterisk), - keysyms::XKB_KEY_XF86AudioMute => Some(VirtualKeyCode::Mute), - // => Some(VirtualKeyCode::MyComputer), - keysyms::XKB_KEY_XF86AudioNext => Some(VirtualKeyCode::NextTrack), - // => Some(VirtualKeyCode::NoConvert), - keysyms::XKB_KEY_KP_Separator => Some(VirtualKeyCode::NumpadComma), - keysyms::XKB_KEY_KP_Enter => Some(VirtualKeyCode::NumpadEnter), - keysyms::XKB_KEY_KP_Equal => Some(VirtualKeyCode::NumpadEquals), - keysyms::XKB_KEY_KP_Add => Some(VirtualKeyCode::NumpadAdd), - keysyms::XKB_KEY_KP_Subtract => Some(VirtualKeyCode::NumpadSubtract), - keysyms::XKB_KEY_KP_Multiply => Some(VirtualKeyCode::NumpadMultiply), - keysyms::XKB_KEY_KP_Divide => Some(VirtualKeyCode::NumpadDivide), - keysyms::XKB_KEY_KP_Decimal => Some(VirtualKeyCode::NumpadDecimal), - keysyms::XKB_KEY_KP_Page_Up => Some(VirtualKeyCode::PageUp), - keysyms::XKB_KEY_KP_Page_Down => Some(VirtualKeyCode::PageDown), - keysyms::XKB_KEY_KP_Home => Some(VirtualKeyCode::Home), - keysyms::XKB_KEY_KP_End => Some(VirtualKeyCode::End), - keysyms::XKB_KEY_KP_Left => Some(VirtualKeyCode::Left), - keysyms::XKB_KEY_KP_Up => Some(VirtualKeyCode::Up), - keysyms::XKB_KEY_KP_Right => Some(VirtualKeyCode::Right), - keysyms::XKB_KEY_KP_Down => Some(VirtualKeyCode::Down), - // => Some(VirtualKeyCode::OEM102), - keysyms::XKB_KEY_period => Some(VirtualKeyCode::Period), - // => Some(VirtualKeyCode::Playpause), - keysyms::XKB_KEY_XF86PowerOff => Some(VirtualKeyCode::Power), - keysyms::XKB_KEY_XF86AudioPrev => Some(VirtualKeyCode::PrevTrack), - keysyms::XKB_KEY_Alt_R => Some(VirtualKeyCode::RAlt), - keysyms::XKB_KEY_bracketright => Some(VirtualKeyCode::RBracket), - keysyms::XKB_KEY_Control_R => Some(VirtualKeyCode::RControl), - keysyms::XKB_KEY_Shift_R => Some(VirtualKeyCode::RShift), - keysyms::XKB_KEY_Super_R => Some(VirtualKeyCode::RWin), - keysyms::XKB_KEY_semicolon => Some(VirtualKeyCode::Semicolon), - keysyms::XKB_KEY_slash => Some(VirtualKeyCode::Slash), - keysyms::XKB_KEY_XF86Sleep => Some(VirtualKeyCode::Sleep), - // => Some(VirtualKeyCode::Stop), - // => Some(VirtualKeyCode::Sysrq), - keysyms::XKB_KEY_Tab => Some(VirtualKeyCode::Tab), - keysyms::XKB_KEY_ISO_Left_Tab => Some(VirtualKeyCode::Tab), - keysyms::XKB_KEY_underscore => Some(VirtualKeyCode::Underline), - // => Some(VirtualKeyCode::Unlabeled), - keysyms::XKB_KEY_XF86AudioLowerVolume => Some(VirtualKeyCode::VolumeDown), - keysyms::XKB_KEY_XF86AudioRaiseVolume => Some(VirtualKeyCode::VolumeUp), - // => Some(VirtualKeyCode::Wake), - // => Some(VirtualKeyCode::Webback), - // => Some(VirtualKeyCode::WebFavorites), - // => Some(VirtualKeyCode::WebForward), - // => Some(VirtualKeyCode::WebHome), - // => Some(VirtualKeyCode::WebRefresh), - // => Some(VirtualKeyCode::WebSearch), - // => Some(VirtualKeyCode::WebStop), - keysyms::XKB_KEY_yen => Some(VirtualKeyCode::Yen), - keysyms::XKB_KEY_XF86Copy => Some(VirtualKeyCode::Copy), - keysyms::XKB_KEY_XF86Paste => Some(VirtualKeyCode::Paste), - keysyms::XKB_KEY_XF86Cut => Some(VirtualKeyCode::Cut), - // Fallback. - _ => None, - } -} diff --git a/src/platform_impl/linux/wayland/seat/keyboard/mod.rs b/src/platform_impl/linux/wayland/seat/keyboard/mod.rs index e2ac88c2c3..d9569a779e 100644 --- a/src/platform_impl/linux/wayland/seat/keyboard/mod.rs +++ b/src/platform_impl/linux/wayland/seat/keyboard/mod.rs @@ -1,242 +1,373 @@ //! The keyboard input handling. use std::sync::Mutex; +use std::time::Duration; + +use calloop::timer::{TimeoutAction, Timer}; +use calloop::{LoopHandle, RegistrationToken}; +use log::warn; -use sctk::reexports::client::delegate_dispatch; use sctk::reexports::client::protocol::wl_keyboard::WlKeyboard; +use sctk::reexports::client::protocol::wl_keyboard::{ + Event as WlKeyboardEvent, KeyState as WlKeyState, KeymapFormat as WlKeymapFormat, +}; use sctk::reexports::client::protocol::wl_seat::WlSeat; -use sctk::reexports::client::protocol::wl_surface::WlSurface; -use sctk::reexports::client::{Connection, Proxy, QueueHandle}; - -use sctk::seat::keyboard::{KeyEvent, KeyboardData, KeyboardDataExt, KeyboardHandler, Modifiers}; -use sctk::seat::SeatState; +use sctk::reexports::client::{Connection, Dispatch, Proxy, QueueHandle, WEnum}; -use crate::event::{ElementState, ModifiersState, WindowEvent}; +use crate::event::{ElementState, WindowEvent}; +use crate::keyboard::ModifiersState; +use crate::platform_impl::common::xkb_state::KbdState; +use crate::platform_impl::wayland::event_loop::sink::EventSink; +use crate::platform_impl::wayland::seat::WinitSeatState; use crate::platform_impl::wayland::state::WinitState; use crate::platform_impl::wayland::{self, DeviceId, WindowId}; -mod keymap; - -impl WinitState { - pub fn handle_key_input( - &mut self, - keyboard: &WlKeyboard, - event: KeyEvent, - state: ElementState, +impl Dispatch for WinitState { + fn event( + state: &mut WinitState, + wl_keyboard: &WlKeyboard, + event: ::Event, + data: &KeyboardData, + _: &Connection, + _: &QueueHandle, ) { - let window_id = match *keyboard.winit_data().window_id.lock().unwrap() { - Some(window_id) => window_id, + let seat_state = match state.seats.get_mut(&data.seat.id()) { + Some(seat_state) => seat_state, None => return, }; - let virtual_keycode = keymap::keysym_to_vkey(event.keysym); - - let seat_state = self.seats.get(&keyboard.seat().id()).unwrap(); - let modifiers = seat_state.modifiers; - - self.events_sink.push_window_event( - #[allow(deprecated)] - WindowEvent::KeyboardInput { - device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland( - DeviceId, - )), - input: crate::event::KeyboardInput { - state, - scancode: event.raw_code, - virtual_keycode, - modifiers, + match event { + WlKeyboardEvent::Keymap { format, fd, size } => match format { + WEnum::Value(format) => match format { + WlKeymapFormat::NoKeymap => { + warn!("non-xkb compatible keymap") + } + WlKeymapFormat::XkbV1 => unsafe { + seat_state + .keyboard_state + .as_mut() + .unwrap() + .xkb_state + .init_with_fd(fd, size as usize); + }, + _ => unreachable!(), }, - is_synthetic: false, + WEnum::Unknown(value) => { + warn!("unknown keymap format 0x{:x}", value) + } }, - window_id, - ); - - // Don't send utf8 chars on release. - if state == ElementState::Released { - return; - } - - if let Some(txt) = event.utf8 { - for ch in txt.chars() { - self.events_sink - .push_window_event(WindowEvent::ReceivedCharacter(ch), window_id); + WlKeyboardEvent::Enter { surface, .. } => { + let window_id = wayland::make_wid(&surface); + + // Mark the window as focused. + match state.windows.get_mut().get(&window_id) { + Some(window) => window.lock().unwrap().set_has_focus(true), + None => return, + }; + + // Drop the repeat, if there were any. + seat_state.keyboard_state.as_mut().unwrap().current_repeat = None; + + // The keyboard focus is considered as general focus. + state + .events_sink + .push_window_event(WindowEvent::Focused(true), window_id); + + *data.window_id.lock().unwrap() = Some(window_id); + + // HACK: this is just for GNOME not fixing their ordering issue of modifiers. + if std::mem::take(&mut seat_state.modifiers_pending) { + state.events_sink.push_window_event( + WindowEvent::ModifiersChanged(seat_state.modifiers.into()), + window_id, + ); + } + } + WlKeyboardEvent::Leave { surface, .. } => { + let window_id = wayland::make_wid(&surface); + + // NOTE: we should drop the repeat regardless whethere it was for the present + // window of for the window which just went gone. + seat_state.keyboard_state.as_mut().unwrap().current_repeat = None; + + // NOTE: The check whether the window exists is essential as we might get a + // nil surface, regardless of what protocol says. + match state.windows.get_mut().get(&window_id) { + Some(window) => window.lock().unwrap().set_has_focus(false), + None => return, + }; + + // Notify that no modifiers are being pressed. + state.events_sink.push_window_event( + WindowEvent::ModifiersChanged(ModifiersState::empty().into()), + window_id, + ); + + // We don't need to update it above, because the next `Enter` will overwrite + // anyway. + *data.window_id.lock().unwrap() = None; + + state + .events_sink + .push_window_event(WindowEvent::Focused(false), window_id); } + WlKeyboardEvent::Key { + key, + state: key_state, + .. + } if key_state == WEnum::Value(WlKeyState::Pressed) => { + let key = key + 8; + + key_input( + seat_state, + &mut state.events_sink, + data, + key, + ElementState::Pressed, + false, + ); + + let keyboard_state = seat_state.keyboard_state.as_mut().unwrap(); + let delay = match keyboard_state.repeat_info { + RepeatInfo::Repeat { delay, .. } => delay, + RepeatInfo::Disable => return, + }; + + if !keyboard_state.xkb_state.key_repeats(key) { + return; + } + + keyboard_state.current_repeat = Some(key); + + // NOTE terminate ongoing timer and start a new timer. + + if let Some(token) = keyboard_state.repeat_token.take() { + keyboard_state.loop_handle.remove(token); + } + + let timer = Timer::from_duration(delay); + let wl_keyboard = wl_keyboard.clone(); + keyboard_state.repeat_token = keyboard_state + .loop_handle + .insert_source(timer, move |_, _, state| { + let data = wl_keyboard.data::().unwrap(); + let seat_state = state.seats.get_mut(&data.seat.id()).unwrap(); + + // NOTE: The removed on event source is batched, but key change to + // `None` is instant. + let repeat_keycode = + match seat_state.keyboard_state.as_ref().unwrap().current_repeat { + Some(repeat_keycode) => repeat_keycode, + None => return TimeoutAction::Drop, + }; + + key_input( + seat_state, + &mut state.events_sink, + data, + repeat_keycode, + ElementState::Pressed, + true, + ); + + // NOTE: the gap could change dynamically while repeat is going. + match seat_state.keyboard_state.as_ref().unwrap().repeat_info { + RepeatInfo::Repeat { gap, .. } => TimeoutAction::ToDuration(gap), + RepeatInfo::Disable => TimeoutAction::Drop, + } + }) + .ok(); + } + WlKeyboardEvent::Key { + key, + state: key_state, + .. + } if key_state == WEnum::Value(WlKeyState::Released) => { + let key = key + 8; + + key_input( + seat_state, + &mut state.events_sink, + data, + key, + ElementState::Released, + false, + ); + + let keyboard_state = seat_state.keyboard_state.as_mut().unwrap(); + if keyboard_state.repeat_info != RepeatInfo::Disable + && keyboard_state.xkb_state.key_repeats(key) + && Some(key) == keyboard_state.current_repeat + { + keyboard_state.current_repeat = None; + } + } + WlKeyboardEvent::Modifiers { + mods_depressed, + mods_latched, + mods_locked, + group, + .. + } => { + let xkb_state = &mut seat_state.keyboard_state.as_mut().unwrap().xkb_state; + xkb_state.update_modifiers(mods_depressed, mods_latched, mods_locked, 0, 0, group); + seat_state.modifiers = xkb_state.mods_state().into(); + + // HACK: part of the workaround from `WlKeyboardEvent::Enter`. + let window_id = match *data.window_id.lock().unwrap() { + Some(window_id) => window_id, + None => { + seat_state.modifiers_pending = true; + return; + } + }; + + state.events_sink.push_window_event( + WindowEvent::ModifiersChanged(seat_state.modifiers.into()), + window_id, + ); + } + WlKeyboardEvent::RepeatInfo { rate, delay } => { + let keyboard_state = seat_state.keyboard_state.as_mut().unwrap(); + keyboard_state.repeat_info = if rate == 0 { + // Stop the repeat once we get a disable event. + keyboard_state.current_repeat = None; + if let Some(repeat_token) = keyboard_state.repeat_token.take() { + keyboard_state.loop_handle.remove(repeat_token); + } + RepeatInfo::Disable + } else { + let gap = Duration::from_micros(1_000_000 / rate as u64); + let delay = Duration::from_millis(delay as u64); + RepeatInfo::Repeat { gap, delay } + }; + } + _ => unreachable!(), } } } -impl KeyboardHandler for WinitState { - fn enter( - &mut self, - _: &Connection, - _: &QueueHandle, - keyboard: &WlKeyboard, - surface: &WlSurface, - _serial: u32, - _raw: &[u32], - _keysyms: &[u32], - ) { - let window_id = wayland::make_wid(surface); +/// The state of the keyboard on the current seat. +#[derive(Debug)] +pub struct KeyboardState { + /// The underlying WlKeyboard. + pub keyboard: WlKeyboard, - // Mark the window as focused. - match self.windows.get_mut().get(&window_id) { - Some(window) => window.lock().unwrap().set_has_focus(true), - None => return, - }; + /// Loop handle to handle key repeat. + pub loop_handle: LoopHandle<'static, WinitState>, - // Window gained focus. - self.events_sink - .push_window_event(WindowEvent::Focused(true), window_id); + /// The state of the keyboard. + pub xkb_state: KbdState, - *keyboard.winit_data().window_id.lock().unwrap() = Some(window_id); + /// The information about the repeat rate obtained from the compositor. + pub repeat_info: RepeatInfo, - // NOTE: GNOME still hasn't violates the specification wrt ordering of such - // events. See https://gitlab.gnome.org/GNOME/mutter/-/issues/2231. - let seat_state = self.seats.get_mut(&keyboard.seat().id()).unwrap(); - if std::mem::take(&mut seat_state.modifiers_pending) { - self.events_sink.push_window_event( - WindowEvent::ModifiersChanged(seat_state.modifiers), - window_id, - ); - } - } + /// The token of the current handle inside the calloop's event loop. + pub repeat_token: Option, - fn leave( - &mut self, - _: &Connection, - _: &QueueHandle, - keyboard: &WlKeyboard, - surface: &WlSurface, - _serial: u32, - ) { - let window_id = wayland::make_wid(surface); - - // XXX The check whether the window exists is essential as we might get a nil surface... - match self.windows.get_mut().get(&window_id) { - Some(window) => window.lock().unwrap().set_has_focus(false), - None => return, - }; + /// The current repeat raw key. + pub current_repeat: Option, +} - // Notify that no modifiers are being pressed. - self.events_sink.push_window_event( - WindowEvent::ModifiersChanged(ModifiersState::empty()), - window_id, - ); +impl KeyboardState { + pub fn new(keyboard: WlKeyboard, loop_handle: LoopHandle<'static, WinitState>) -> Self { + Self { + keyboard, + loop_handle, + xkb_state: KbdState::new().unwrap(), + repeat_info: RepeatInfo::default(), + repeat_token: None, + current_repeat: None, + } + } +} - *keyboard.winit_data().window_id.lock().unwrap() = None; +impl Drop for KeyboardState { + fn drop(&mut self) { + if self.keyboard.version() >= 3 { + self.keyboard.release(); + } - // Window lost focus. - self.events_sink - .push_window_event(WindowEvent::Focused(false), window_id); + if let Some(token) = self.repeat_token.take() { + self.loop_handle.remove(token); + } } +} - fn press_key( - &mut self, - _: &Connection, - _: &QueueHandle, - keyboard: &WlKeyboard, - _serial: u32, - event: KeyEvent, - ) { - self.handle_key_input(keyboard, event, ElementState::Pressed); - } +/// The rate at which a pressed key is repeated. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum RepeatInfo { + /// Keys will be repeated at the specified rate and delay. + Repeat { + /// The time between the key repeats. + gap: Duration, - fn release_key( - &mut self, - _: &Connection, - _: &QueueHandle, - keyboard: &WlKeyboard, - _serial: u32, - event: KeyEvent, - ) { - self.handle_key_input(keyboard, event, ElementState::Released); - } + /// Delay (in milliseconds) between a key press and the start of repetition. + delay: Duration, + }, - // FIXME(kchibisov): recent spec suggested that this event could be sent - // without any focus to indicate modifiers for the pointer, so update - // for will be required once https://github.com/rust-windowing/winit/issues/2768 - // wrt winit design of the event is resolved. - fn update_modifiers( - &mut self, - _: &Connection, - _: &QueueHandle, - keyboard: &WlKeyboard, - _serial: u32, - modifiers: Modifiers, - ) { - let modifiers = ModifiersState::from(modifiers); - let seat_state = self.seats.get_mut(&keyboard.seat().id()).unwrap(); - seat_state.modifiers = modifiers; - - // NOTE: part of the workaround from `fn enter`, see it above. - let window_id = match *keyboard.winit_data().window_id.lock().unwrap() { - Some(window_id) => window_id, - None => { - seat_state.modifiers_pending = true; - return; - } - }; + /// Keys should not be repeated. + Disable, +} - self.events_sink - .push_window_event(WindowEvent::ModifiersChanged(modifiers), window_id); +impl Default for RepeatInfo { + /// The default repeat rate is 25 keys per second with the delay of 200ms. + /// + /// The values are picked based on the default in various compositors and Xorg. + fn default() -> Self { + Self::Repeat { + gap: Duration::from_millis(40), + delay: Duration::from_millis(200), + } } } -/// The extension to KeyboardData used to store the `window_id`. -pub struct WinitKeyboardData { +/// Keyboard user data. +#[derive(Debug)] +pub struct KeyboardData { /// The currently focused window surface. Could be `None` on bugged compositors, like mutter. window_id: Mutex>, - /// The original keyboard date. - keyboard_data: KeyboardData, + /// The seat used to create this keyboard. + seat: WlSeat, } -impl WinitKeyboardData { +impl KeyboardData { pub fn new(seat: WlSeat) -> Self { Self { window_id: Default::default(), - keyboard_data: KeyboardData::new(seat), + seat, } } } -impl KeyboardDataExt for WinitKeyboardData { - type State = WinitState; - - fn keyboard_data(&self) -> &KeyboardData { - &self.keyboard_data - } - - fn keyboard_data_mut(&mut self) -> &mut KeyboardData { - &mut self.keyboard_data - } -} - -pub trait WinitKeyboardDataExt { - fn winit_data(&self) -> &WinitKeyboardData; - - fn seat(&self) -> &WlSeat { - self.winit_data().keyboard_data().seat() - } +fn key_input( + seat_state: &mut WinitSeatState, + event_sink: &mut EventSink, + data: &KeyboardData, + keycode: u32, + state: ElementState, + repeat: bool, +) { + let window_id = match *data.window_id.lock().unwrap() { + Some(window_id) => window_id, + None => return, + }; + + let keyboard_state = seat_state.keyboard_state.as_mut().unwrap(); + + let device_id = crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(DeviceId)); + let event = keyboard_state + .xkb_state + .process_key_event(keycode, state, repeat); + + event_sink.push_window_event( + WindowEvent::KeyboardInput { + device_id, + event, + is_synthetic: false, + }, + window_id, + ); } - -impl WinitKeyboardDataExt for WlKeyboard { - fn winit_data(&self) -> &WinitKeyboardData { - self.data::() - .expect("failed to get keyboard data.") - } -} - -impl From for ModifiersState { - fn from(mods: Modifiers) -> ModifiersState { - let mut wl_mods = ModifiersState::empty(); - wl_mods.set(ModifiersState::SHIFT, mods.shift); - wl_mods.set(ModifiersState::CTRL, mods.ctrl); - wl_mods.set(ModifiersState::ALT, mods.alt); - wl_mods.set(ModifiersState::LOGO, mods.logo); - wl_mods - } -} - -delegate_dispatch!(WinitState: [ WlKeyboard: WinitKeyboardData] => SeatState); diff --git a/src/platform_impl/linux/wayland/seat/mod.rs b/src/platform_impl/linux/wayland/seat/mod.rs index 639005126d..1832e2af6a 100644 --- a/src/platform_impl/linux/wayland/seat/mod.rs +++ b/src/platform_impl/linux/wayland/seat/mod.rs @@ -4,7 +4,6 @@ use std::sync::Arc; use fnv::FnvHashMap; -use sctk::reexports::client::protocol::wl_keyboard::WlKeyboard; use sctk::reexports::client::protocol::wl_seat::WlSeat; use sctk::reexports::client::protocol::wl_touch::WlTouch; use sctk::reexports::client::{Connection, Proxy, QueueHandle}; @@ -14,7 +13,7 @@ use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3:: use sctk::seat::pointer::{ThemeSpec, ThemedPointer}; use sctk::seat::{Capability as SeatCapability, SeatHandler, SeatState}; -use crate::event::{ElementState, ModifiersState}; +use crate::keyboard::ModifiersState; use crate::platform_impl::wayland::state::WinitState; mod keyboard; @@ -26,7 +25,7 @@ pub use pointer::relative_pointer::RelativePointerState; pub use pointer::{PointerConstraintsState, WinitPointerData, WinitPointerDataExt}; pub use text_input::{TextInputState, ZwpTextInputV3Ext}; -use keyboard::WinitKeyboardData; +use keyboard::{KeyboardData, KeyboardState}; use text_input::TextInputData; use touch::TouchPoint; @@ -91,20 +90,9 @@ impl SeatHandler for WinitState { seat_state.touch = self.seat_state.get_touch(queue_handle, &seat).ok(); } SeatCapability::Keyboard if seat_state.keyboard_state.is_none() => { - let keyboard = self - .seat_state - .get_keyboard_with_repeat_with_data( - queue_handle, - &seat, - WinitKeyboardData::new(seat.clone()), - self.loop_handle.clone(), - Box::new(|state, keyboard, event| { - state.handle_key_input(keyboard, event, ElementState::Pressed); - }), - ) - .expect("failed to create keyboard with present capability."); - - seat_state.keyboard_state = Some(KeyboardState { keyboard }); + let keyboard = seat.get_keyboard(queue_handle, KeyboardData::new(seat.clone())); + seat_state.keyboard_state = + Some(KeyboardState::new(keyboard, self.loop_handle.clone())); } SeatCapability::Pointer if seat_state.pointer.is_none() => { let surface = self.compositor_state.create_surface(queue_handle); @@ -192,11 +180,7 @@ impl SeatHandler for WinitState { } } SeatCapability::Keyboard => { - if let Some(keyboard_state) = seat_state.keyboard_state.take() { - if keyboard_state.keyboard.version() >= 3 { - keyboard_state.keyboard.release(); - } - } + seat_state.keyboard_state = None; } _ => (), } @@ -225,10 +209,4 @@ impl SeatHandler for WinitState { } } -#[derive(Debug)] -pub struct KeyboardState { - /// The underlying WlKeyboard. - pub keyboard: WlKeyboard, -} - sctk::delegate_seat!(WinitState); diff --git a/src/platform_impl/linux/wayland/seat/pointer/mod.rs b/src/platform_impl/linux/wayland/seat/pointer/mod.rs index 3af7e8f0f1..1b933028e8 100644 --- a/src/platform_impl/linux/wayland/seat/pointer/mod.rs +++ b/src/platform_impl/linux/wayland/seat/pointer/mod.rs @@ -38,7 +38,6 @@ impl PointerHandler for WinitState { ) { let seat = pointer.winit_data().seat(); let seat_state = self.seats.get(&seat.id()).unwrap(); - let modifiers = seat_state.modifiers; let device_id = crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(DeviceId)); @@ -130,7 +129,6 @@ impl PointerHandler for WinitState { WindowEvent::CursorMoved { device_id, position, - modifiers, }, window_id, ); @@ -151,7 +149,6 @@ impl PointerHandler for WinitState { WindowEvent::CursorMoved { device_id, position, - modifiers, }, window_id, ); @@ -177,7 +174,6 @@ impl PointerHandler for WinitState { device_id, state, button, - modifiers, }, window_id, ); @@ -231,7 +227,6 @@ impl PointerHandler for WinitState { device_id, delta, phase, - modifiers, }, window_id, ) diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index 3056055b13..1638075494 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -1,23 +1,19 @@ 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, - }, + event::{DeviceEvent, ElementState, Event, Ime, RawKeyEvent, TouchPhase, WindowEvent}, event_loop::EventLoopWindowTarget as RootELW, + keyboard::ModifiersState, + platform_impl::platform::common::{keymap, xkb_state::KbdState}, }; /// The X11 documentation states: "Keycodes lie in the inclusive range `[8, 255]`". @@ -30,9 +26,9 @@ 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: KbdState, // Number of touch events currently in progress pub(super) num_touch: u32, pub(super) first_touch: Option, @@ -134,47 +130,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 +534,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,9 +598,6 @@ impl EventProcessor { return; } - let modifiers = ModifiersState::from_x11(&xev.mods); - update_modifiers!(modifiers, None); - let state = if xev.evtype == ffi::XI_ButtonPress { Pressed } else { @@ -711,7 +610,6 @@ impl EventProcessor { device_id, state, button: Left, - modifiers, }, }), ffi::Button2 => callback(Event::WindowEvent { @@ -720,7 +618,6 @@ impl EventProcessor { device_id, state, button: Middle, - modifiers, }, }), ffi::Button3 => callback(Event::WindowEvent { @@ -729,7 +626,6 @@ impl EventProcessor { device_id, state, button: Right, - modifiers, }, }), @@ -750,7 +646,6 @@ impl EventProcessor { _ => unreachable!(), }, phase: TouchPhase::Moved, - modifiers, }, }); } @@ -762,7 +657,6 @@ impl EventProcessor { device_id, state, button: Other(x as u16), - modifiers, }, }), } @@ -773,9 +667,6 @@ 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 cursor_moved = self.with_window(xev.event, |window| { let mut shared_state_lock = window.shared_state_lock(); util::maybe_change(&mut shared_state_lock.cursor_pos, new_cursor_pos) @@ -788,7 +679,6 @@ impl EventProcessor { event: CursorMoved { device_id, position, - modifiers, }, }); } else if cursor_moved.is_none() { @@ -835,7 +725,6 @@ impl EventProcessor { } }, phase: TouchPhase::Moved, - modifiers, }, }); } else { @@ -889,25 +778,11 @@ impl EventProcessor { let position = PhysicalPosition::new(xev.event_x, xev.event_y); - // The mods field on this event isn't actually populated, so query the - // pointer device. In the future, we can likely remove this round-trip by - // relying on `Xkb` for modifier values. - // - // This needs to only be done after confirming the window still exists, - // since otherwise we risk getting a `BadWindow` error if the window was - // dropped with queued events. - let modifiers = wt - .xconn - .query_pointer(xev.event, xev.deviceid) - .expect("Failed to query pointer device") - .get_modifier_state(); - callback(Event::WindowEvent { window_id, event: CursorMoved { device_id, position, - modifiers, }, }); } @@ -935,10 +810,6 @@ 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); - if self.active_window != Some(xev.event) { self.active_window = Some(xev.event); @@ -956,10 +827,12 @@ impl EventProcessor { event: Focused(true), }); + let modifiers: crate::keyboard::ModifiersState = + self.kb_state.mods_state().into(); if !modifiers.is_empty() { callback(Event::WindowEvent { window_id, - event: WindowEvent::ModifiersChanged(modifiers), + event: WindowEvent::ModifiersChanged(modifiers.into()), }); } @@ -977,7 +850,6 @@ impl EventProcessor { event: CursorMoved { device_id: mkdid(pointer_id), position, - modifiers, }, }); @@ -986,8 +858,7 @@ impl EventProcessor { wt, window_id, ElementState::Pressed, - &self.mod_keymap, - &mut self.device_mod_state, + &mut self.kb_state, &mut callback, ); } @@ -1013,14 +884,15 @@ impl EventProcessor { wt, window_id, ElementState::Released, - &self.mod_keymap, - &mut self.device_mod_state, + &mut self.kb_state, &mut callback, ); callback(Event::WindowEvent { window_id, - event: WindowEvent::ModifiersChanged(ModifiersState::empty()), + event: WindowEvent::ModifiersChanged( + ModifiersState::empty().into(), + ), }); if let Some(window) = self.with_window(xev.event, Arc::clone) { @@ -1045,7 +917,6 @@ impl EventProcessor { }; if self.window_exists(xev.event) { let id = xev.detail as u64; - let modifiers = self.device_mod_state.modifiers(); let location = PhysicalPosition::new(xev.event_x, xev.event_y); // Mouse cursor position changes when touch events are received. @@ -1057,7 +928,6 @@ impl EventProcessor { event: WindowEvent::CursorMoved { device_id: mkdid(util::VIRTUAL_CORE_POINTER), position: location.cast(), - modifiers, }, }); } @@ -1143,6 +1013,39 @@ 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 repeat = xkev.flags & ffi::XIKeyRepeat == ffi::XIKeyRepeat; + let event = self.kb_state.process_key_event(keycode, state, repeat); + + callback(Event::WindowEvent { + window_id, + event: WindowEvent::KeyboardInput { + device_id, + event, + is_synthetic: false, + }, + }); + } + } + ffi::XI_RawKeyPress | ffi::XI_RawKeyRelease => { let xev: &ffi::XIRawEvent = unsafe { &*(xev.data as *const _) }; @@ -1153,46 +1056,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 +1098,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( + Into::::into(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 +1270,12 @@ impl EventProcessor { wt: &super::EventLoopWindowTarget, window_id: crate::window::WindowId, state: ElementState, - mod_keymap: &ModifierKeymap, - device_mod_state: &mut ModifierKeyState, + kb_state: &mut KbdState, 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,29 +1284,13 @@ 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, - ); - } - - #[allow(deprecated)] + let keycode = keycode as u32; + let event = kb_state.process_key_event(keycode, state, false); callback(Event::WindowEvent { window_id, event: WindowEvent::KeyboardInput { device_id, - input: KeyboardInput { - scancode, - state, - virtual_keycode, - modifiers, - }, + event, is_synthetic: true, }, }); diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index 0ef073d9e4..51b8fce043 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::KbdState; 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 = + KbdState::from_x11_xkb(unsafe { (xconn.xlib_xcb.XGetXCBConnection)(xconn.display) }) + .unwrap(); + let window_target = EventLoopWindowTarget { ime, root, @@ -264,8 +285,8 @@ impl EventLoop { ime_receiver, ime_event_receiver, xi2ext, - mod_keymap, - device_mod_state: Default::default(), + xkbext, + kb_state, num_touch: 0, first_touch: None, active_window: None, @@ -279,6 +300,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..41108839f8 100644 --- a/src/platform_impl/linux/x11/util/input.rs +++ b/src/platform_impl/linux/x11/util/input.rs @@ -1,7 +1,6 @@ use std::{slice, str}; use super::*; -use crate::event::ModifiersState; pub const VIRTUAL_CORE_POINTER: c_int = 2; pub const VIRTUAL_CORE_KEYBOARD: c_int = 3; @@ -11,21 +10,6 @@ pub const VIRTUAL_CORE_KEYBOARD: c_int = 3; // To test if `lookup_utf8` works correctly, set this to 1. const TEXT_BUFFER_SIZE: usize = 1024; -impl ModifiersState { - pub(crate) fn from_x11(state: &ffi::XIModifierState) -> Self { - ModifiersState::from_x11_mask(state.effective as c_uint) - } - - pub(crate) fn from_x11_mask(mask: c_uint) -> Self { - 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 - } -} - // NOTE: Some of these fields are not used, but may be of use in the future. pub struct PointerState<'a> { xconn: &'a XConnection, @@ -36,17 +20,10 @@ pub struct PointerState<'a> { pub win_x: c_double, pub win_y: c_double, buttons: ffi::XIButtonState, - modifiers: ffi::XIModifierState, pub group: ffi::XIGroupState, pub relative_to_window: bool, } -impl<'a> PointerState<'a> { - pub fn get_modifier_state(&self) -> ModifiersState { - ModifiersState::from_x11(&self.modifiers) - } -} - impl<'a> Drop for PointerState<'a> { fn drop(&mut self) { if !self.buttons.mask.is_null() { @@ -81,12 +58,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 } } @@ -133,7 +110,6 @@ impl XConnection { win_x, win_y, buttons, - modifiers, group, relative_to_window, }) 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(); diff --git a/src/platform_impl/macos/appkit/event.rs b/src/platform_impl/macos/appkit/event.rs index 7924f420ad..c735ffaffa 100644 --- a/src/platform_impl/macos/appkit/event.rs +++ b/src/platform_impl/macos/appkit/event.rs @@ -67,35 +67,6 @@ extern_methods!( } } - pub fn keyEventWithType( - type_: NSEventType, - location: NSPoint, - modifier_flags: NSEventModifierFlags, - timestamp: NSTimeInterval, - window_num: NSInteger, - context: Option<&NSObject>, - characters: &NSString, - characters_ignoring_modifiers: &NSString, - is_a_repeat: bool, - scancode: c_ushort, - ) -> Id { - unsafe { - msg_send_id![ - Self::class(), - keyEventWithType: type_, - location: location, - modifierFlags: modifier_flags, - timestamp: timestamp, - windowNumber: window_num, - context: context, - characters: characters, - charactersIgnoringModifiers: characters_ignoring_modifiers, - isARepeat: is_a_repeat, - keyCode: scancode, - ] - } - } - #[sel(locationInWindow)] pub fn locationInWindow(&self) -> NSPoint; @@ -109,12 +80,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; @@ -169,6 +136,26 @@ extern_methods!( unsafe { msg_send_id![self, charactersIgnoringModifiers] } } + pub fn lshift_pressed(&self) -> bool { + let raw_modifiers = self.modifierFlags().bits() as u32; + raw_modifiers & NX_DEVICELSHIFTKEYMASK != 0 + } + + pub fn rshift_pressed(&self) -> bool { + let raw_modifiers = self.modifierFlags().bits() as u32; + raw_modifiers & NX_DEVICERSHIFTKEYMASK != 0 + } + + pub fn lctrl_pressed(&self) -> bool { + let raw_modifiers = self.modifierFlags().bits() as u32; + raw_modifiers & NX_DEVICELCTLKEYMASK != 0 + } + + pub fn rctrl_pressed(&self) -> bool { + let raw_modifiers = self.modifierFlags().bits() as u32; + raw_modifiers & NX_DEVICERCTLKEYMASK != 0 + } + pub fn lalt_pressed(&self) -> bool { let raw_modifiers = self.modifierFlags().bits() as u32; raw_modifiers & NX_DEVICELALTKEYMASK != 0 @@ -178,6 +165,16 @@ extern_methods!( let raw_modifiers = self.modifierFlags().bits() as u32; raw_modifiers & NX_DEVICERALTKEYMASK != 0 } + + pub fn lcmd_pressed(&self) -> bool { + let raw_modifiers = self.modifierFlags().bits() as u32; + raw_modifiers & NX_DEVICELCMDKEYMASK != 0 + } + + pub fn rcmd_pressed(&self) -> bool { + let raw_modifiers = self.modifierFlags().bits() as u32; + raw_modifiers & NX_DEVICERCMDKEYMASK != 0 + } } ); @@ -187,8 +184,14 @@ unsafe impl NSCopying for NSEvent { } // The values are from the https://github.com/apple-oss-distributions/IOHIDFamily/blob/19666c840a6d896468416ff0007040a10b7b46b8/IOHIDSystem/IOKit/hidsystem/IOLLEvent.h#L258-L259 +const NX_DEVICELCTLKEYMASK: u32 = 0x00000001; +const NX_DEVICELSHIFTKEYMASK: u32 = 0x00000002; +const NX_DEVICERSHIFTKEYMASK: u32 = 0x00000004; +const NX_DEVICELCMDKEYMASK: u32 = 0x00000008; +const NX_DEVICERCMDKEYMASK: u32 = 0x00000010; const NX_DEVICELALTKEYMASK: u32 = 0x00000020; const NX_DEVICERALTKEYMASK: u32 = 0x00000040; +const NX_DEVICERCTLKEYMASK: u32 = 0x00002000; bitflags! { pub struct NSEventModifierFlags: NSUInteger { diff --git a/src/platform_impl/macos/event.rs b/src/platform_impl/macos/event.rs index 2c5fe5a0c4..785647e3f2 100644 --- a/src/platform_impl/macos/event.rs +++ b/src/platform_impl/macos/event.rs @@ -1,13 +1,25 @@ -use std::os::raw::c_ushort; +use std::ffi::c_void; +use core_foundation::{ + base::CFRelease, + data::{CFDataGetBytePtr, CFDataRef}, +}; use objc2::rc::{Id, Shared}; +use smol_str::SmolStr; 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, Modifiers}, + keyboard::{ + Key, KeyCode, KeyLocation, ModifiersKeys, ModifiersState, NativeKey, NativeKeyCode, + }, + platform::{modifier_supplement::KeyEventExtModifierSupplement, scancode::KeyCodeExtScancode}, + platform_impl::platform::{ + ffi, + util::{get_kbd_type, Never}, + }, }; #[derive(Debug)] @@ -25,268 +37,586 @@ 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, + pub key_without_modifiers: Key, +} + +impl KeyEventExtModifierSupplement for KeyEvent { + fn text_with_all_modifiers(&self) -> Option<&str> { + self.platform_specific + .text_with_all_modifiers + .as_ref() + .map(|s| s.as_str()) + } + + fn key_without_modifiers(&self) -> Key { + self.platform_specific.key_without_modifiers.clone() + } +} + +pub fn get_modifierless_char(scancode: u16) -> Key { + 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 CFDataRef) as *const ffi::UCKeyboardLayout; + } + let keyboard_type = get_kbd_type(); + + 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(SmolStr::new(chars)) +} + +fn get_logical_key_char(ns_event: &NSEvent, modifierless_chars: &str) -> Key { + 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(SmolStr::new(string)) +} + +/// Create `KeyEvent` for the given `NSEvent`. +/// +/// This function shouldn't be called when the IME input is in process. +pub(crate) fn create_key_event( + ns_event: &NSEvent, + is_press: bool, + is_repeat: bool, + key_override: Option, +) -> KeyEvent { + use ElementState::{Pressed, Released}; + let state = if is_press { Pressed } else { Released }; + + let scancode = ns_event.key_code(); + let mut physical_key = key_override.unwrap_or_else(|| KeyCode::from_scancode(scancode as u32)); + + let text_with_all_modifiers: Option = 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(SmolStr::new(characters)) + } + }; + + let key_from_code = code_to_key(physical_key, scancode); + let (logical_key, key_without_modifiers) = if matches!(key_from_code, Key::Unidentified(_)) { + let key_without_modifiers = get_modifierless_char(scancode); + + let modifiers = NSEvent::modifierFlags(ns_event); + let has_ctrl = modifiers.contains(NSEventModifierFlags::NSControlKeyMask); + + let logical_key = match text_with_all_modifiers.as_ref() { + // 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. + Some(text) if !has_ctrl => Key::Character(text.clone()), + _ => { + let modifierless_chars = match key_without_modifiers.as_ref() { + Key::Character(ch) => ch, + _ => "", + }; + get_logical_key_char(ns_event, modifierless_chars) + } + }; + + (logical_key, key_without_modifiers) + } else { + (key_from_code.clone(), key_from_code) + }; + + let text = if is_press { + logical_key.to_text().map(SmolStr::new) + } else { + None + }; + + let location = code_to_location(physical_key); + + 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 { + 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 { +pub(super) fn event_mods(event: &NSEvent) -> Modifiers { let flags = event.modifierFlags(); - let mut m = ModifiersState::empty(); - m.set( + let mut state = ModifiersState::empty(); + let mut pressed_mods = ModifiersKeys::empty(); + + state.set( ModifiersState::SHIFT, flags.contains(NSEventModifierFlags::NSShiftKeyMask), ); - m.set( - ModifiersState::CTRL, + + pressed_mods.set(ModifiersKeys::LSHIFT, event.lshift_pressed()); + pressed_mods.set(ModifiersKeys::RSHIFT, event.rshift_pressed()); + + state.set( + ModifiersState::CONTROL, flags.contains(NSEventModifierFlags::NSControlKeyMask), ); - m.set( + + pressed_mods.set(ModifiersKeys::LCONTROL, event.lctrl_pressed()); + pressed_mods.set(ModifiersKeys::RCONTROL, event.rctrl_pressed()); + + state.set( ModifiersState::ALT, flags.contains(NSEventModifierFlags::NSAlternateKeyMask), ); - m.set( - ModifiersState::LOGO, + + pressed_mods.set(ModifiersKeys::LALT, event.lalt_pressed()); + pressed_mods.set(ModifiersKeys::RALT, event.ralt_pressed()); + + state.set( + ModifiersState::SUPER, flags.contains(NSEventModifierFlags::NSCommandKeyMask), ); - m + + pressed_mods.set(ModifiersKeys::LSUPER, event.lcmd_pressed()); + pressed_mods.set(ModifiersKeys::RSUPER, event.rcmd_pressed()); + + Modifiers { + state, + pressed_mods, + } } -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 - }; +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, + } + } - 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 + 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/ffi.rs b/src/platform_impl/macos/ffi.rs index f080331058..05397facf0 100644 --- a/src/platform_impl/macos/ffi.rs +++ b/src/platform_impl/macos/ffi.rs @@ -156,3 +156,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, + ) -> *mut c_void; + + 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/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 cacbf10e8c..cfcc91ccb0 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,21 @@ 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, Modifiers, 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 +70,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) -> 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) -> 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) -> 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, + pub(super) modifiers: Modifiers, + phys_modifiers: HashMap, tracking_rect: Option, + // phys_modifiers: HashSet, ime_state: ImeState, input_source: String, @@ -85,54 +137,6 @@ pub(super) struct ViewState { forward_key_to_app: bool, } -fn get_characters(event: &NSEvent, ignore_modifiers: bool) -> String { - if ignore_modifiers { - event.charactersIgnoringModifiers() - } else { - event.characters() - } - .expect("expected characters to be non-null") - .to_string() -} - -// As defined in: https://www.unicode.org/Public/MAPPINGS/VENDORS/APPLE/CORPCHAR.TXT -fn is_corporate_character(c: char) -> bool { - matches!(c, - '\u{F700}'..='\u{F747}' - | '\u{F802}'..='\u{F84F}' - | '\u{F850}' - | '\u{F85C}' - | '\u{F85D}' - | '\u{F85F}' - | '\u{F860}'..='\u{F86B}' - | '\u{F870}'..='\u{F8FF}' - ) -} - -// Retrieves a layout-independent keycode given an event. -fn retrieve_keycode(event: &NSEvent) -> Option { - #[inline] - fn get_code(ev: &NSEvent, raw: bool) -> Option { - let characters = get_characters(ev, raw); - characters.chars().next().and_then(char_to_keycode) - } - - // Cmd switches Roman letters for Dvorak-QWERTY layout, so we try modified characters first. - // If we don't get a match, then we fall back to unmodified characters. - let code = get_code(event, false).or_else(|| get_code(event, true)); - - // We've checked all layout related keys, so fall through to scancode. - // Reaching this code means that the key is layout-independent (e.g. Backspace, Return). - // - // We're additionally checking here for F21-F24 keys, since their keycode - // can vary, but we know that they are encoded - // in characters property. - code.or_else(|| { - let scancode = event.scancode(); - scancode_to_keycode(scancode).or_else(|| check_function_keys(&get_characters(event, true))) - }) -} - declare_class!( #[derive(Debug)] #[allow(non_snake_case)] @@ -162,6 +166,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(), @@ -471,16 +476,6 @@ declare_class!( } // Get the characters from the event. - let ev_mods = event_mods(event); - 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, - _ => false, - } && !ev_mods.ctrl() - && !ev_mods.logo(); - - let characters = get_characters(event, ignore_alt_characters); let old_ime_state = self.state.ime_state; self.state.forward_key_to_app = false; @@ -491,13 +486,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(&[event.copy()]); unsafe { self.interpretKeyEvents(&events_for_nsview) }; // If the text was commited we must treat the next keyboard event as IME related. @@ -507,10 +496,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 => { @@ -524,89 +510,36 @@ declare_class!( }; if !had_ime_input || self.state.forward_key_to_app { - #[allow(deprecated)] + let key_event = create_key_event(event, true, event.is_a_repeat(), 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 matches!(self.state.ime_state, ImeState::Ground | ImeState::Disabled) { - #[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, 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:)] @@ -636,25 +569,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(), 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, }); } @@ -778,14 +703,13 @@ 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 { device_id: DEVICE_ID, delta, phase, - modifiers: event_mods(event), }); } @@ -947,25 +871,120 @@ 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.key_code(); + 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, Some(keycode)); + + let key = code_to_key(keycode, scancode); + let event_modifier = key_to_modifier(&key); + event.physical_key = keycode; + 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.state().contains(event_modifier); + let mut events = VecDeque::with_capacity(2); + + // There is no API for getting whether the button was pressed or released + // during this event. For this reason we have to do a bit of magic below + // to come up with a good guess whether this key was pressed or released. + // (This is not trivial because there are multiple buttons that may affect + // the same modifier) + if !is_active { + event.state = Released; + if phys_mod.contains(ModLocationMask::LEFT) { + let mut event = event.clone(); + event.location = KeyLocation::Left; + event.physical_key = get_left_modifier_code(&event.logical_key); + events.push_back(WindowEvent::KeyboardInput { + device_id: DEVICE_ID, + event, + is_synthetic: false, + }); + } + if phys_mod.contains(ModLocationMask::RIGHT) { + event.location = KeyLocation::Right; + event.physical_key = get_right_modifier_code(&event.logical_key); + events.push_back(WindowEvent::KeyboardInput { + device_id: DEVICE_ID, + event, + is_synthetic: false, + }); + } + *phys_mod = ModLocationMask::empty(); + } else { + // is_active + if *phys_mod == location_mask { + // Here we hit a contradiction: + // The modifier state was "changed" to active, + // yet the only pressed modifier key was the one that we + // just got a change event for. + // This seemingly means that the only pressed modifier is now released, + // but at the same time the modifier became active. + // + // But this scenario is possible if we released modifiers + // while the application was not in focus. (Because we don't + // get informed of modifier key events while the application + // is not focused) + + // In this case we prioritize the information + // about the current modifier state which means + // that the button was pressed. + event.state = Pressed; + } else { + phys_mod.toggle(location_mask); + let is_pressed = phys_mod.contains(location_mask); + event.state = if is_pressed { Pressed } else { Released }; + } + + events.push_back(WindowEvent::KeyboardInput { + device_id: DEVICE_ID, + event, + is_synthetic: false, + }); + } - self.queue_event(WindowEvent::ModifiersChanged(self.state.modifiers)); + 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, state: button_state, button, - modifiers: event_mods(event), }); } @@ -990,12 +1009,11 @@ 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, position: logical_position.to_physical(self.scale_factor()), - modifiers: event_mods(event), }); } } @@ -1012,21 +1030,3 @@ fn mouse_button(event: &NSEvent) -> MouseButton { n => MouseButton::Other(n as u16), } } - -fn replace_event_chars(event: &NSEvent, characters: &str) -> Id { - let ns_chars = NSString::from_str(characters); - let chars_ignoring_mods = event.charactersIgnoringModifiers().unwrap(); - - NSEvent::keyEventWithType( - event.type_(), - event.locationInWindow(), - event.modifierFlags(), - event.timestamp(), - event.window_number(), - None, - &ns_chars, - &chars_ignoring_mods, - event.is_a_repeat(), - event.scancode(), - ) -} diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index 69921ba78f..05aad7b191 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -21,7 +21,7 @@ use crate::{ error::{ExternalError, NotSupportedError, OsError as RootOsError}, event::WindowEvent, icon::Icon, - platform::macos::{OptionAsAlt, WindowExtMacOS}, + platform::macos::WindowExtMacOS, platform_impl::platform::{ app_state::AppState, appkit::NSWindowOrderingMode, @@ -85,7 +85,6 @@ pub struct PlatformSpecificWindowBuilderAttributes { pub disallow_hidpi: bool, pub has_shadow: bool, pub accepts_first_mouse: bool, - pub option_as_alt: OptionAsAlt, } impl Default for PlatformSpecificWindowBuilderAttributes { @@ -101,7 +100,6 @@ impl Default for PlatformSpecificWindowBuilderAttributes { disallow_hidpi: false, has_shadow: true, accepts_first_mouse: true, - option_as_alt: Default::default(), } } } @@ -162,9 +160,6 @@ pub struct SharedState { /// The current resize incerments for the window content. pub(crate) resize_increments: NSSize, - - /// The state of the `Option` as `Alt`. - pub(crate) option_as_alt: OptionAsAlt, } impl SharedState { @@ -374,8 +369,6 @@ impl WinitWindow { this.center(); } - this.set_option_as_alt(pl_attrs.option_as_alt); - Id::into_shared(this) }) }) @@ -1271,6 +1264,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 { @@ -1379,16 +1376,6 @@ impl WindowExtMacOS for WinitWindow { fn set_document_edited(&self, edited: bool) { self.setDocumentEdited(edited) } - - fn set_option_as_alt(&self, option_as_alt: OptionAsAlt) { - let mut shared_state_lock = self.shared_state.lock().unwrap(); - shared_state_lock.option_as_alt = option_as_alt; - } - - fn option_as_alt(&self) -> OptionAsAlt { - let shared_state_lock = self.shared_state.lock().unwrap(); - shared_state_lock.option_as_alt - } } pub(super) fn get_ns_theme() -> Theme { diff --git a/src/platform_impl/macos/window_delegate.rs b/src/platform_impl/macos/window_delegate.rs index baa0fe80ae..ff3bbc78fa 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}, @@ -170,9 +171,11 @@ declare_class!( let mut view = unsafe { Id::from_shared(self.window.view()) }; // Both update the state and emit a ModifiersChanged event. - if !view.state.modifiers.is_empty() { - view.state.modifiers = ModifiersState::empty(); - self.queue_event(WindowEvent::ModifiersChanged(view.state.modifiers)); + if !view.state.modifiers.state().is_empty() { + view.state.modifiers = ModifiersState::empty().into(); + self.queue_event(WindowEvent::ModifiersChanged( + ModifiersState::empty().into(), + )); } self.queue_event(WindowEvent::Focused(false)); diff --git a/src/platform_impl/orbital/event_loop.rs b/src/platform_impl/orbital/event_loop.rs index f0ad43d5b3..c5e9c80985 100644 --- a/src/platform_impl/orbital/event_loop.rs +++ b/src/platform_impl/orbital/event_loop.rs @@ -12,99 +12,102 @@ use orbclient::{ use raw_window_handle::{OrbitalDisplayHandle, RawDisplayHandle}; use crate::{ - event::{self, StartCause, VirtualKeyCode}, + event::{self, Ime, Modifiers, StartCause}, event_loop::{self, ControlFlow}, + keyboard::{ + Key, KeyCode, KeyLocation, ModifiersKeys, 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 +150,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,33 +188,78 @@ impl EventState { None } - fn modifiers(&self) -> event::ModifiersState { - let mut modifiers = event::ModifiersState::empty(); + fn modifiers(&self) -> Modifiers { + let mut state = ModifiersState::empty(); + let mut pressed_mods = ModifiersKeys::empty(); + if self .keyboard .intersects(KeyboardModifierState::LSHIFT | KeyboardModifierState::RSHIFT) { - modifiers |= event::ModifiersState::SHIFT; + state |= ModifiersState::SHIFT; } + + pressed_mods.set( + ModifiersKeys::LSHIFT, + self.keyboard.contains(KeyboardModifierState::LSHIFT), + ); + pressed_mods.set( + ModifiersKeys::RSHIFT, + self.keyboard.contains(KeyboardModifierState::RSHIFT), + ); + if self .keyboard .intersects(KeyboardModifierState::LCTRL | KeyboardModifierState::RCTRL) { - modifiers |= event::ModifiersState::CTRL; + state |= ModifiersState::CONTROL; } + + pressed_mods.set( + ModifiersKeys::LCONTROL, + self.keyboard.contains(KeyboardModifierState::LCTRL), + ); + pressed_mods.set( + ModifiersKeys::RCONTROL, + self.keyboard.contains(KeyboardModifierState::RCTRL), + ); + if self .keyboard .intersects(KeyboardModifierState::LALT | KeyboardModifierState::RALT) { - modifiers |= event::ModifiersState::ALT; + state |= ModifiersState::ALT; } + + pressed_mods.set( + ModifiersKeys::LALT, + self.keyboard.contains(KeyboardModifierState::LALT), + ); + pressed_mods.set( + ModifiersKeys::RALT, + self.keyboard.contains(KeyboardModifierState::RALT), + ); + if self .keyboard .intersects(KeyboardModifierState::LSUPER | KeyboardModifierState::RSUPER) { - modifiers |= event::ModifiersState::LOGO + state |= ModifiersState::SUPER + } + + pressed_mods.set( + ModifiersKeys::LSUPER, + self.keyboard.contains(KeyboardModifierState::LSUPER), + ); + pressed_mods.set( + ModifiersKeys::RSUPER, + self.keyboard.contains(KeyboardModifierState::RSUPER), + ); + + Modifiers { + state, + pressed_mods, } - modifiers } } @@ -277,32 +325,44 @@ 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); + let modifiers_before = event_state.keyboard; + event_state.key(code, 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, }, - ); + }); + + // If the state of the modifiers has changed, send the event. + if modifiers_before != event_state.keyboard { + event_handler(event::Event::WindowEvent { + window_id: RootWindowId(window_id), + event: event::WindowEvent::ModifiersChanged(event_state.modifiers()), + }) + } } } 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 }) => { @@ -311,7 +371,6 @@ impl EventLoop { event: event::WindowEvent::CursorMoved { device_id: event::DeviceId(DeviceId), position: (x, y).into(), - modifiers: event_state.modifiers(), }, }); } @@ -327,7 +386,6 @@ impl EventLoop { device_id: event::DeviceId(DeviceId), state, button, - modifiers: event_state.modifiers(), }, }); } @@ -339,7 +397,6 @@ impl EventLoop { device_id: event::DeviceId(DeviceId), delta: event::MouseScrollDelta::LineDelta(x as f32, y as f32), phase: event::TouchPhase::Moved, - modifiers: event_state.modifiers(), }, }); } 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]; diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index 17aafd946d..0405f83a21 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -1,29 +1,56 @@ -use std::cell::RefCell; +use std::cell::{Cell, RefCell}; use std::clone::Clone; use std::collections::{vec_deque::IntoIter as VecDequeIter, VecDeque}; +use std::iter; 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 +59,7 @@ impl EventLoopWindowTarget { pub fn new() -> Self { Self { runner: runner::Shared::new(), + modifiers: ModifiersShared::default(), } } @@ -56,7 +84,7 @@ impl EventLoopWindowTarget { canvas: &Rc>, id: WindowId, prevent_default: bool, - has_focus: Rc>, + has_focus: Rc>, ) { self.runner.add_canvas(RootWindowId(id), canvas); let mut canvas = canvas.borrow_mut(); @@ -67,115 +95,185 @@ impl EventLoopWindowTarget { let runner = self.runner.clone(); let has_focus_clone = has_focus.clone(); + let modifiers = self.modifiers.clone(); canvas.on_blur(move || { - *has_focus_clone.borrow_mut() = false; - runner.send_event(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::Focused(false), + has_focus_clone.set(false); + + let clear_modifiers = (!modifiers.get().is_empty()).then(|| { + modifiers.set(ModifiersState::empty()); + Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::ModifiersChanged(ModifiersState::empty().into()), + } }); + + runner.send_events( + clear_modifiers + .into_iter() + .chain(iter::once(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::Focused(false), + })), + ); }); let runner = self.runner.clone(); let has_focus_clone = has_focus.clone(); canvas.on_focus(move || { - *has_focus_clone.borrow_mut() = true; - runner.send_event(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::Focused(true), - }); + if !has_focus_clone.replace(true) { + runner.send_event(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::Focused(true), + }); + } }); 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, - }, - is_synthetic: false, - }, + move |physical_key, logical_key, text, location, repeat, active_modifiers| { + let modifiers_changed = (modifiers.get() != active_modifiers).then(|| { + modifiers.set(active_modifiers); + Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::ModifiersChanged(active_modifiers.into()), + } }); + + runner.send_events( + 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, + }, + }) + .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, - }, + move |physical_key, logical_key, text, location, repeat, active_modifiers| { + let modifiers_changed = (modifiers.get() != active_modifiers).then(|| { + modifiers.set(active_modifiers); + Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::ModifiersChanged(active_modifiers.into()), + } }); + + runner.send_events( + 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, ); 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), + let modifiers = self.modifiers.clone(); + let has_focus_clone = has_focus.clone(); + canvas.on_cursor_leave(move |pointer_id, active_modifiers| { + let modifiers_changed = (has_focus_clone.get() && modifiers.get() != active_modifiers) + .then(|| { + modifiers.set(active_modifiers); + Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::ModifiersChanged(active_modifiers.into()), + } }); - }, - prevent_default, - ); - let runner = self.runner.clone(); - canvas.on_cursor_leave(move |pointer_id| { - runner.send_event(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::CursorLeft { - device_id: RootDeviceId(DeviceId(pointer_id)), + runner.send_events(modifiers_changed.into_iter().chain(iter::once( + Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::CursorLeft { + device_id: RootDeviceId(DeviceId(pointer_id)), + }, }, - }); + ))); }); let runner = self.runner.clone(); - canvas.on_cursor_enter(move |pointer_id| { - runner.send_event(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::CursorEntered { - device_id: RootDeviceId(DeviceId(pointer_id)), + let modifiers = self.modifiers.clone(); + let has_focus_clone = has_focus.clone(); + canvas.on_cursor_enter(move |pointer_id, active_modifiers| { + let modifiers_changed = (has_focus_clone.get() && modifiers.get() != active_modifiers) + .then(|| { + modifiers.set(active_modifiers); + Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::ModifiersChanged(active_modifiers.into()), + } + }); + + runner.send_events(modifiers_changed.into_iter().chain(iter::once( + Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::CursorEntered { + device_id: RootDeviceId(DeviceId(pointer_id)), + }, }, - }); + ))); }); let runner = self.runner.clone(); let runner_touch = self.runner.clone(); + let modifiers = self.modifiers.clone(); + let has_focus_clone = has_focus.clone(); canvas.on_cursor_move( - move |pointer_id, position, delta, modifiers| { - runner.send_event(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::CursorMoved { - device_id: RootDeviceId(DeviceId(pointer_id)), - position, - modifiers, + move |pointer_id, position, delta, active_modifiers| { + let modifiers_changed = + (has_focus_clone.get() && modifiers.get() != active_modifiers).then(|| { + modifiers.set(active_modifiers); + Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::ModifiersChanged(active_modifiers.into()), + } + }); + + runner.send_events(modifiers_changed.into_iter().chain([ + Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::CursorMoved { + device_id: RootDeviceId(DeviceId(pointer_id)), + position, + }, }, - }); - runner.send_event(Event::DeviceEvent { - device_id: RootDeviceId(DeviceId(pointer_id)), - event: DeviceEvent::MouseMotion { - delta: (delta.x, delta.y), + Event::DeviceEvent { + device_id: RootDeviceId(DeviceId(pointer_id)), + event: DeviceEvent::MouseMotion { + delta: (delta.x, delta.y), + }, }, - }); + ])); }, move |device_id, location, force| { runner_touch.send_event(Event::WindowEvent { @@ -194,36 +292,44 @@ impl EventLoopWindowTarget { let runner = self.runner.clone(); let runner_touch = self.runner.clone(); + let modifiers = self.modifiers.clone(); + let has_focus_clone = has_focus.clone(); canvas.on_mouse_press( - move |pointer_id, position, button, modifiers| { - *has_focus.borrow_mut() = true; + move |pointer_id, position, button, active_modifiers| { + let focus_changed = + (!has_focus_clone.replace(true)).then_some(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::Focused(true), + }); + + let modifiers_changed = (modifiers.get() != active_modifiers).then(|| { + modifiers.set(active_modifiers); + Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::ModifiersChanged(active_modifiers.into()), + } + }); // A mouse down event may come in without any prior CursorMoved events, // therefore we should send a CursorMoved event to make sure that the // user code has the correct cursor position. - runner.send_events( - std::iter::once(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::Focused(true), - }) - .chain(std::iter::once(Event::WindowEvent { + runner.send_events(focus_changed.into_iter().chain(modifiers_changed).chain([ + Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::CursorMoved { device_id: RootDeviceId(DeviceId(pointer_id)), position, - modifiers, }, - })) - .chain(std::iter::once(Event::WindowEvent { + }, + Event::WindowEvent { window_id: RootWindowId(id), event: WindowEvent::MouseInput { device_id: RootDeviceId(DeviceId(pointer_id)), state: ElementState::Pressed, button, - modifiers, }, - })), - ); + }, + ])); }, move |device_id, location, force| { runner_touch.send_event(Event::WindowEvent { @@ -241,17 +347,29 @@ impl EventLoopWindowTarget { let runner = self.runner.clone(); let runner_touch = self.runner.clone(); + let modifiers = self.modifiers.clone(); + let has_focus_clone = has_focus.clone(); canvas.on_mouse_release( - move |pointer_id, button, modifiers| { - runner.send_event(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::MouseInput { - device_id: RootDeviceId(DeviceId(pointer_id)), - state: ElementState::Released, - button, - modifiers, + move |pointer_id, button, active_modifiers| { + let modifiers_changed = + (has_focus_clone.get() && modifiers.get() != active_modifiers).then(|| { + modifiers.set(active_modifiers); + Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::ModifiersChanged(active_modifiers.into()), + } + }); + + runner.send_events(modifiers_changed.into_iter().chain(iter::once( + Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::MouseInput { + device_id: RootDeviceId(DeviceId(pointer_id)), + state: ElementState::Released, + button, + }, }, - }); + ))); }, move |device_id, location, force| { runner_touch.send_event(Event::WindowEvent { @@ -268,17 +386,28 @@ impl EventLoopWindowTarget { ); let runner = self.runner.clone(); + let modifiers = self.modifiers.clone(); canvas.on_mouse_wheel( - move |pointer_id, delta, modifiers| { - runner.send_event(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::MouseWheel { - device_id: RootDeviceId(DeviceId(pointer_id)), - delta, - phase: TouchPhase::Moved, - modifiers, + move |pointer_id, delta, active_modifiers| { + let modifiers_changed = (has_focus.get() && modifiers.get() != active_modifiers) + .then(|| { + modifiers.set(active_modifiers); + Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::ModifiersChanged(active_modifiers.into()), + } + }); + + runner.send_events(modifiers_changed.into_iter().chain(iter::once( + Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::MouseWheel { + device_id: RootDeviceId(DeviceId(pointer_id)), + delta, + phase: TouchPhase::Moved, + }, }, - }); + ))); }, prevent_default, ); diff --git a/src/platform_impl/web/keyboard.rs b/src/platform_impl/web/keyboard.rs new file mode 100644 index 0000000000..84c0003975 --- /dev/null +++ b/src/platform_impl/web/keyboard.rs @@ -0,0 +1,522 @@ +use smol_str::SmolStr; + +use crate::keyboard::{Key, KeyCode, NativeKey, NativeKeyCode}; + +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub(crate) struct KeyEventExtra; + +impl Key { + pub(crate) fn from_key_attribute_value(kav: &str) -> Self { + match kav { + "Unidentified" => Key::Unidentified(NativeKey::Web(SmolStr::new(kav))), + "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(SmolStr::new(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..89a2935a48 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -3,14 +3,14 @@ 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; use std::rc::Rc; +use smol_str::SmolStr; use wasm_bindgen::{closure::Closure, JsCast}; use web_sys::{ AddEventListenerOptions, Event, FocusEvent, HtmlCanvasElement, KeyboardEvent, @@ -29,7 +29,6 @@ pub struct Canvas { on_blur: Option>, on_keyboard_release: Option>, on_keyboard_press: Option>, - on_received_character: Option>, on_mouse_wheel: Option>, on_fullscreen_change: Option>, on_dark_mode: Option, @@ -89,7 +88,6 @@ impl Canvas { on_focus: None, on_keyboard_release: None, on_keyboard_press: None, - on_received_character: None, on_mouse_wheel: None, on_fullscreen_change: None, on_dark_mode: None, @@ -174,7 +172,7 @@ 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, Option, KeyLocation, bool, ModifiersState), { self.on_keyboard_release = Some(self.common.add_user_event( "keyup", @@ -182,11 +180,15 @@ impl Canvas { if prevent_default { event.prevent_default(); } - + let key = event::key(&event); + let modifiers = event::keyboard_modifiers(&event); 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,59 +196,31 @@ 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, Option, 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(&event); 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), + F: 'static + FnMut(i32, ModifiersState), { match &mut self.mouse_state { MouseState::HasPointerEvent(h) => h.on_cursor_leave(&self.common, handler), @@ -256,7 +230,7 @@ impl Canvas { pub fn on_cursor_enter(&mut self, handler: F) where - F: 'static + FnMut(i32), + F: 'static + FnMut(i32, ModifiersState), { match &mut self.mouse_state { MouseState::HasPointerEvent(h) => h.on_cursor_enter(&self.common, handler), @@ -326,7 +300,8 @@ impl Canvas { } if let Some(delta) = event::mouse_scroll_delta(&event) { - handler(0, delta, event::mouse_modifiers(&event)); + let modifiers = event::mouse_modifiers(&event); + handler(0, delta, modifiers); } })); } @@ -366,7 +341,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..117a67edce 100644 --- a/src/platform_impl/web/web_sys/canvas/mouse_handler.rs +++ b/src/platform_impl/web/web_sys/canvas/mouse_handler.rs @@ -1,14 +1,15 @@ 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; use web_sys::{EventTarget, MouseEvent}; -type MouseLeaveHandler = Rc>>>; +type MouseLeaveHandler = Rc>>>; #[allow(dead_code)] pub(super) struct MouseHandler { @@ -42,35 +43,43 @@ impl MouseHandler { } pub fn on_cursor_leave(&mut self, canvas_common: &super::Common, handler: F) where - F: 'static + FnMut(i32), + F: 'static + FnMut(i32, ModifiersState), { *self.on_mouse_leave_handler.borrow_mut() = Some(Box::new(handler)); let on_mouse_leave_handler = self.on_mouse_leave_handler.clone(); let mouse_capture_state = self.mouse_capture_state.clone(); - self.on_mouse_leave = Some(canvas_common.add_event("mouseout", move |_: MouseEvent| { - // If the mouse is being captured, it is always considered - // to be "within" the the canvas, until the capture has been - // released, therefore we don't send cursor leave events. - if *mouse_capture_state.borrow() != MouseCaptureState::Captured { - if let Some(handler) = on_mouse_leave_handler.borrow_mut().as_mut() { - handler(0); + self.on_mouse_leave = Some(canvas_common.add_event( + "mouseout", + move |event: MouseEvent| { + // If the mouse is being captured, it is always considered + // to be "within" the the canvas, until the capture has been + // released, therefore we don't send cursor leave events. + if *mouse_capture_state.borrow() != MouseCaptureState::Captured { + if let Some(handler) = on_mouse_leave_handler.borrow_mut().as_mut() { + let modifiers = event::mouse_modifiers(&event); + handler(0, modifiers); + } } - } - })); + }, + )); } pub fn on_cursor_enter(&mut self, canvas_common: &super::Common, mut handler: F) where - F: 'static + FnMut(i32), + F: 'static + FnMut(i32, ModifiersState), { let mouse_capture_state = self.mouse_capture_state.clone(); - self.on_mouse_enter = Some(canvas_common.add_event("mouseover", move |_: MouseEvent| { - // We don't send cursor leave events when the mouse is being - // captured, therefore we do the same with cursor enter events. - if *mouse_capture_state.borrow() != MouseCaptureState::Captured { - handler(0); - } - })); + self.on_mouse_enter = Some(canvas_common.add_event( + "mouseover", + move |event: MouseEvent| { + // We don't send cursor leave events when the mouse is being + // captured, therefore we do the same with cursor enter events. + if *mouse_capture_state.borrow() != MouseCaptureState::Captured { + let modifiers = event::mouse_modifiers(&event); + handler(0, modifiers); + } + }, + )); } pub fn on_mouse_release(&mut self, canvas_common: &super::Common, mut handler: F) @@ -99,11 +108,8 @@ impl MouseHandler { MouseCaptureState::Captured => {} } event.stop_propagation(); - handler( - 0, - event::mouse_button(&event), - event::mouse_modifiers(&event), - ); + let modifiers = event::mouse_modifiers(&event); + handler(0, event::mouse_button(&event), modifiers); if event .target() .map_or(false, |target| target != EventTarget::from(canvas)) @@ -112,7 +118,8 @@ impl MouseHandler { // cursor is being captured, we instead send it after // the capture has been released. if let Some(handler) = on_mouse_leave_handler.borrow_mut().as_mut() { - handler(0); + let modifiers = event::mouse_modifiers(&event); + handler(0, modifiers); } } if event.buttons() == 0 { @@ -151,11 +158,12 @@ impl MouseHandler { } *mouse_capture_state = MouseCaptureState::Captured; event.stop_propagation(); + let modifiers = event::mouse_modifiers(&event); handler( 0, event::mouse_position(&event).to_physical(super::super::scale_factor()), event::mouse_button(&event), - event::mouse_modifiers(&event), + modifiers, ); }, )); @@ -194,11 +202,12 @@ impl MouseHandler { event::mouse_position_by_client(&event, &canvas) }; let mouse_delta = event::mouse_delta(&event); + let modifiers = event::mouse_modifiers(&event); handler( 0, mouse_pos.to_physical(super::super::scale_factor()), mouse_delta.to_physical(super::super::scale_factor()), - event::mouse_modifiers(&event), + modifiers, ); } } 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..80f9e95336 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; @@ -30,7 +30,7 @@ impl PointerHandler { pub fn on_cursor_leave(&mut self, canvas_common: &super::Common, mut handler: F) where - F: 'static + FnMut(i32), + F: 'static + FnMut(i32, ModifiersState), { self.on_cursor_leave = Some(canvas_common.add_event( "pointerout", @@ -42,14 +42,15 @@ impl PointerHandler { return; } - handler(event.pointer_id()); + let modifiers = event::mouse_modifiers(&event); + handler(event.pointer_id(), modifiers); }, )); } pub fn on_cursor_enter(&mut self, canvas_common: &super::Common, mut handler: F) where - F: 'static + FnMut(i32), + F: 'static + FnMut(i32, ModifiersState), { self.on_cursor_enter = Some(canvas_common.add_event( "pointerover", @@ -61,7 +62,8 @@ impl PointerHandler { return; } - handler(event.pointer_id()); + let modifiers = event::mouse_modifiers(&event); + handler(event.pointer_id(), modifiers); }, )); } @@ -87,11 +89,8 @@ impl PointerHandler { Force::Normalized(event.pressure() as f64), ); } else { - mouse_handler( - event.pointer_id(), - event::mouse_button(&event), - event::mouse_modifiers(&event), - ); + let modifiers = event::mouse_modifiers(&event); + mouse_handler(event.pointer_id(), event::mouse_button(&event), modifiers); } }, )); @@ -118,11 +117,12 @@ impl PointerHandler { Force::Normalized(event.pressure() as f64), ); } else { + let modifiers = event::mouse_modifiers(&event); mouse_handler( event.pointer_id(), event::mouse_position(&event).to_physical(super::super::scale_factor()), event::mouse_button(&event), - event::mouse_modifiers(&event), + modifiers, ); // Error is swallowed here since the error would occur every time the mouse is @@ -160,11 +160,12 @@ impl PointerHandler { Force::Normalized(event.pressure() as f64), ); } else { + let modifiers = event::mouse_modifiers(&event); mouse_handler( event.pointer_id(), event::mouse_position(&event).to_physical(super::super::scale_factor()), event::mouse_delta(&event).to_physical(super::super::scale_factor()), - event::mouse_modifiers(&event), + modifiers, ); } }, diff --git a/src/platform_impl/web/web_sys/event.rs b/src/platform_impl/web/web_sys/event.rs index a75c0e1b82..cdfeea2c8e 100644 --- a/src/platform_impl/web/web_sys/event.rs +++ b/src/platform_impl/web/web_sys/event.rs @@ -1,6 +1,8 @@ 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 smol_str::SmolStr; use std::convert::TryInto; use web_sys::{HtmlCanvasElement, KeyboardEvent, MouseEvent, PointerEvent, WheelEvent}; @@ -13,15 +15,6 @@ 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::ALT, event.alt_key()); - m.set(ModifiersState::LOGO, event.meta_key()); - m -} - pub fn mouse_position(event: &MouseEvent) -> LogicalPosition { LogicalPosition { x: event.offset_x() as f64, @@ -61,190 +54,77 @@ 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 { + Key::from_key_attribute_value(&event.key()) +} + +pub fn key_text(event: &KeyboardEvent) -> Option { + let key = event.key(); + let key = Key::from_key_attribute_value(&key); + match &key { + Key::Character(text) => Some(text.clone()), + Key::Tab => Some(SmolStr::new("\t")), + Key::Enter => Some(SmolStr::new("\r")), + Key::Space => Some(SmolStr::new(" ")), + _ => None, } + .map(SmolStr::new) } -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, - }) +pub fn key_location(event: &KeyboardEvent) -> KeyLocation { + 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, + location => { + warn!("Unexpected key location: {location}"); + KeyLocation::Standard + } + } } 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 + let mut state = ModifiersState::empty(); + + if event.shift_key() { + state |= ModifiersState::SHIFT; + } + if event.ctrl_key() { + state |= ModifiersState::CONTROL; + } + if event.alt_key() { + state |= ModifiersState::ALT; + } + if event.meta_key() { + state |= ModifiersState::SUPER; + } + + state } -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 mouse_modifiers(event: &MouseEvent) -> ModifiersState { + let mut state = ModifiersState::empty(); + + if event.shift_key() { + state |= ModifiersState::SHIFT; + } + if event.ctrl_key() { + state |= ModifiersState::CONTROL; + } + if event.alt_key() { + state |= ModifiersState::ALT; + } + if event.meta_key() { + state |= ModifiersState::SUPER; + } + + state } pub fn touch_position(event: &PointerEvent, _canvas: &HtmlCanvasElement) -> LogicalPosition { diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index 55ddf9dc6a..6401fc0fa2 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -11,7 +11,7 @@ use raw_window_handle::{RawDisplayHandle, RawWindowHandle, WebDisplayHandle, Web use super::{backend, monitor::MonitorHandle, EventLoopWindowTarget, Fullscreen}; -use std::cell::{Ref, RefCell}; +use std::cell::{Cell, Ref, RefCell}; use std::collections::vec_deque::IntoIter as VecDequeIter; use std::collections::VecDeque; use std::rc::Rc; @@ -23,7 +23,7 @@ pub struct Window { register_redraw_request: Box, resize_notify_fn: Box)>, destroy_fn: Option>, - has_focus: Rc>, + has_focus: Rc>, } impl Window { @@ -43,7 +43,7 @@ impl Window { let register_redraw_request = Box::new(move || runner.request_redraw(RootWI(id))); - let has_focus = Rc::new(RefCell::new(false)); + let has_focus = Rc::new(Cell::new(false)); target.register(&canvas, id, prevent_default, has_focus.clone()); let runner = target.runner.clone(); @@ -388,12 +388,16 @@ impl Window { #[inline] pub fn has_focus(&self) -> bool { - *self.has_focus.borrow() + self.has_focus.get() } pub fn title(&self) -> String { String::new() } + + pub fn reset_dead_keys(&self) { + // Not supported + } } impl Drop for Window { 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 55341e2b56..f5a3d0053a 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. +/// Returns the current modifier state fn update_modifiers(window: HWND, userdata: &WindowData) { 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; @@ -867,35 +882,16 @@ fn update_modifiers(window: HWND, userdata: &WindowData) { unsafe { userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), - event: ModifiersChanged(modifiers), + event: ModifiersChanged(modifiers.into()), }); } } } 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,35 +900,12 @@ 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 { window_id: RootWindowId(WindowId(window)), - event: ModifiersChanged(ModifiersState::empty()), + event: ModifiersChanged(ModifiersState::empty().into()), }); userdata.send_event(Event::WindowEvent { @@ -1021,6 +994,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 +1038,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 +1071,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 +1089,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 +1105,7 @@ unsafe fn public_window_callback_inner( window_id: RootWindowId(WindowId(window)), event: CloseRequested, }); - 0 + result = ProcResult::Value(0); } WM_DESTROY => { @@ -1105,13 +1116,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 +1139,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 +1217,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 +1234,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 +1260,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 +1278,7 @@ unsafe fn public_window_callback_inner( }); } - DefWindowProcW(window, msg, wparam, lparam) + result = ProcResult::DefWindowProc(wparam); } WM_IME_COMPOSITION => { @@ -1363,7 +1330,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 +1363,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 +1387,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 => { @@ -1477,12 +1444,11 @@ unsafe fn public_window_callback_inner( event: CursorMoved { device_id: DEVICE_ID, position, - modifiers: event::get_key_mods(), }, }); } - 0 + result = ProcResult::Value(0); } WM_MOUSELEAVE => { @@ -1501,7 +1467,7 @@ unsafe fn public_window_callback_inner( }, }); - 0 + result = ProcResult::Value(0); } WM_MOUSEWHEEL => { @@ -1519,11 +1485,10 @@ unsafe fn public_window_callback_inner( device_id: DEVICE_ID, delta: LineDelta(0.0, value), phase: TouchPhase::Moved, - modifiers: event::get_key_mods(), }, }); - 0 + result = ProcResult::Value(0); } WM_MOUSEHWHEEL => { @@ -1541,75 +1506,23 @@ unsafe fn public_window_callback_inner( device_id: DEVICE_ID, delta: LineDelta(value, 0.0), phase: TouchPhase::Moved, - modifiers: event::get_key_mods(), }, }); - 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); } } @@ -1626,10 +1539,9 @@ unsafe fn public_window_callback_inner( device_id: DEVICE_ID, state: Pressed, button: Left, - modifiers: event::get_key_mods(), }, }); - 0 + result = ProcResult::Value(0); } WM_LBUTTONUP => { @@ -1647,10 +1559,9 @@ unsafe fn public_window_callback_inner( device_id: DEVICE_ID, state: Released, button: Left, - modifiers: event::get_key_mods(), }, }); - 0 + result = ProcResult::Value(0); } WM_RBUTTONDOWN => { @@ -1668,10 +1579,9 @@ unsafe fn public_window_callback_inner( device_id: DEVICE_ID, state: Pressed, button: Right, - modifiers: event::get_key_mods(), }, }); - 0 + result = ProcResult::Value(0); } WM_RBUTTONUP => { @@ -1689,10 +1599,9 @@ unsafe fn public_window_callback_inner( device_id: DEVICE_ID, state: Released, button: Right, - modifiers: event::get_key_mods(), }, }); - 0 + result = ProcResult::Value(0); } WM_MBUTTONDOWN => { @@ -1710,10 +1619,9 @@ unsafe fn public_window_callback_inner( device_id: DEVICE_ID, state: Pressed, button: Middle, - modifiers: event::get_key_mods(), }, }); - 0 + result = ProcResult::Value(0); } WM_MBUTTONUP => { @@ -1731,10 +1639,9 @@ unsafe fn public_window_callback_inner( device_id: DEVICE_ID, state: Released, button: Middle, - modifiers: event::get_key_mods(), }, }); - 0 + result = ProcResult::Value(0); } WM_XBUTTONDOWN => { @@ -1753,10 +1660,9 @@ unsafe fn public_window_callback_inner( device_id: DEVICE_ID, state: Pressed, button: Other(xbutton), - modifiers: event::get_key_mods(), }, }); - 0 + result = ProcResult::Value(0); } WM_XBUTTONUP => { @@ -1775,10 +1681,9 @@ unsafe fn public_window_callback_inner( device_id: DEVICE_ID, state: Released, button: Other(xbutton), - modifiers: event::get_key_mods(), }, }); - 0 + result = ProcResult::Value(0); } WM_CAPTURECHANGED => { @@ -1789,7 +1694,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 +1743,7 @@ unsafe fn public_window_callback_inner( } } CloseTouchInputHandle(htouch); - 0 + result = ProcResult::Value(0); } WM_POINTERDOWN | WM_POINTERUPDATE | WM_POINTERUP => { @@ -1861,7 +1766,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 +1779,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 +1885,7 @@ unsafe fn public_window_callback_inner( SkipPointerFrameMessages(pointer_id); } - 0 + result = ProcResult::Value(0); } WM_NCACTIVATE => { @@ -1991,7 +1898,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 +1906,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 +1914,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 +1935,12 @@ unsafe fn public_window_callback_inner( Some(cursor) => { let cursor = LoadCursorW(0, util::to_windows_cursor(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 +1968,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 +1990,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 +2175,7 @@ unsafe fn public_window_callback_inner( SWP_NOZORDER | SWP_NOACTIVATE, ); - 0 + result = ProcResult::Value(0); } WM_SETTINGCHANGE => { @@ -2293,26 +2196,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 +2222,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 +2300,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 +2366,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..595cd4c51e --- /dev/null +++ b/src/platform_impl/windows/keyboard.rs @@ -0,0 +1,1272 @@ +use std::{ + char, + ffi::OsString, + mem::MaybeUninit, + os::windows::ffi::OsStringExt, + sync::{ + atomic::{AtomicU32, Ordering::Relaxed}, + Mutex, MutexGuard, + }, +}; + +use windows_sys::Win32::{ + Foundation::{HWND, LPARAM, WPARAM}, + System::SystemServices::LANG_KOREAN, + UI::{ + Input::KeyboardAndMouse::{ + GetAsyncKeyState, GetKeyState, GetKeyboardLayout, 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 smol_str::SmolStr; +use unicode_segmentation::UnicodeSegmentation; + +use crate::{ + event::{ElementState, KeyEvent}, + keyboard::{Key, KeyCode, KeyLocation, NativeKey, NativeKeyCode}, + platform::scancode::KeyCodeExtScancode, + platform_impl::platform::{ + event_loop::ProcResult, + keyboard_layout::{Layout, LayoutCache, WindowsModifiers, LAYOUT_CACHE}, + loword, primarylangid, 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(); + 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 ev = event_info.finalize(); + 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().map(SmolStr::new)); + } + let ev = event_info.finalize(); + 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, + ); + // We MUST release the layout lock before calling `next_kbd_msg`, otherwise it may deadlock + drop(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 mut layouts = LAYOUT_CACHE.lock().unwrap(); + 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(); + 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().map(SmolStr::new) + } 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.clone()), + }; + + let mut event = event_info.finalize(); + 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), +} + +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), + + /// Use the value directly provided by this variant + This(Key), +} + +struct PartialKeyEventInfo { + vkey: VIRTUAL_KEY, + key_state: ElementState, + is_repeat: bool, + code: KeyCode, + location: KeyLocation, + logical_key: PartialLogicalKey, + + key_without_modifiers: Key, + + /// 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); + Key::Character(SmolStr::new(s)) + } 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) -> 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() { + char_with_all_modifiers = Some(SmolStr::new(string)); + } + } + + // 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() { + text = Some(SmolStr::new(string)); + } + } + } + PartialText::Text(s) => { + text = s.map(SmolStr::new); + } + } + + let logical_key = match self.logical_key { + PartialLogicalKey::TextOr(fallback) => match text.as_ref() { + Some(s) => { + if s.grapheme_indices(true).count() > 1 { + fallback + } else { + Key::Character(s.clone()) + } + } + 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, + } +} + +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/keyboard_layout.rs b/src/platform_impl/windows/keyboard_layout.rs new file mode 100644 index 0000000000..7b7db3b24e --- /dev/null +++ b/src/platform_impl/windows/keyboard_layout.rs @@ -0,0 +1,997 @@ +use std::{ + collections::{hash_map::Entry, HashMap, HashSet}, + ffi::OsString, + os::windows::ffi::OsStringExt, + sync::Mutex, +}; + +use once_cell::sync::Lazy; +use smol_str::SmolStr; +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 { + 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, +} + +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(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(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 { + layout + .numlock_on_keys + .insert(vk as VIRTUAL_KEY, Key::Character(SmolStr::new(s))); + } + } + + // 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) => Key::Character(SmolStr::new(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(SmolStr::new("/")) + } 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 + } +} + +#[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 { + // 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..ee256694c9 100644 --- a/src/platform_impl/windows/mod.rs +++ b/src/platform_impl/windows/mod.rs @@ -1,5 +1,6 @@ #![cfg(windows_platform)] +use smol_str::SmolStr; use windows_sys::Win32::{ Foundation::{HANDLE, HWND}, UI::WindowsAndMessaging::{HMENU, WINDOW_LONG_PTR_INDEX}, @@ -19,6 +20,7 @@ pub(self) use crate::platform_impl::Fullscreen; use crate::event::DeviceId as RootDeviceId; use crate::icon::Icon; +use crate::keyboard::Key; #[derive(Clone)] pub struct PlatformSpecificWindowBuilderAttributes { @@ -82,6 +84,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, + pub key_without_modifiers: Key, +} + #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct WindowId(HWND); unsafe impl Send for WindowId {} @@ -127,7 +135,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 } @@ -162,10 +175,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 162b92990f..7115b30650 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, diff --git a/src/window.rs b/src/window.rs index f34d62648b..36d9586ebf 100644 --- a/src/window.rs +++ b/src/window.rs @@ -547,6 +547,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. @@ -1033,14 +1049,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. /// @@ -1051,7 +1065,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); diff --git a/tests/serde_objects.rs b/tests/serde_objects.rs index ad729dcd1b..fccc202b1e 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::(); }