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://raw.githubusercontent.com/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://raw.githubusercontent.com/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://raw.githubusercontent.com/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://raw.githubusercontent.com/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::(); }