diff --git a/Cargo.toml b/Cargo.toml index b1e4159ba4..d65620d493 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ serde = { version = "1", optional = true, features = ["serde_derive"] } raw-window-handle = "0.4.2" bitflags = "1" mint = { version = "0.5.6", optional = true } +nameof = "1" [dev-dependencies] image = "0.23.12" @@ -56,6 +57,7 @@ features = ["display_link"] [target.'cfg(target_os = "windows")'.dependencies] parking_lot = "0.11" +unicode-segmentation = "1.7.1" [target.'cfg(target_os = "windows")'.dependencies.winapi] version = "0.3.9" @@ -79,6 +81,7 @@ features = [ "winerror", "wingdi", "winnt", + "winnls", "winuser", "mmsystem", "timeapi" diff --git a/examples/control_flow.rs b/examples/control_flow.rs index cbb445eebb..9835c436f2 100644 --- a/examples/control_flow.rs +++ b/examples/control_flow.rs @@ -2,8 +2,9 @@ use std::{thread, time}; use simple_logger::SimpleLogger; use winit::{ - event::{Event, KeyboardInput, WindowEvent}, + event::{ElementState, Event, KeyEvent, WindowEvent}, event_loop::{ControlFlow, EventLoop}, + keyboard::Key, window::WindowBuilder, }; @@ -38,7 +39,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) => { @@ -52,31 +53,33 @@ fn main() { close_requested = true; } WindowEvent::KeyboardInput { - input: - KeyboardInput { - virtual_keycode: Some(virtual_code), + event: + KeyEvent { + logical_key: key, state: ElementState::Pressed, .. }, .. - } => match virtual_code { - VirtualKeyCode::Key1 => { + } => match key { + // WARNING: Consider using `key_without_modifers()` if available on your platform. + // See the `key_binding` example + Key::Character("1") => { mode = Mode::Wait; println!("\nmode: {:?}\n", mode); } - VirtualKeyCode::Key2 => { + Key::Character("2") => { mode = Mode::WaitUntil; println!("\nmode: {:?}\n", mode); } - VirtualKeyCode::Key3 => { + Key::Character("3") => { mode = Mode::Poll; println!("\nmode: {:?}\n", mode); } - VirtualKeyCode::R => { + Key::Character("r") => { request_redraw = !request_redraw; println!("\nrequest_redraw: {}\n", request_redraw); } - VirtualKeyCode::Escape => { + Key::Escape => { close_requested = true; } _ => (), diff --git a/examples/cursor.rs b/examples/cursor.rs index 6ec8a05933..d9230e32e6 100644 --- a/examples/cursor.rs +++ b/examples/cursor.rs @@ -1,6 +1,6 @@ use simple_logger::SimpleLogger; use winit::{ - event::{ElementState, Event, KeyboardInput, WindowEvent}, + event::{ElementState, Event, KeyEvent, WindowEvent}, event_loop::{ControlFlow, EventLoop}, window::{CursorIcon, WindowBuilder}, }; @@ -21,8 +21,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 90a94764de..746bf426ba 100644 --- a/examples/cursor_grab.rs +++ b/examples/cursor_grab.rs @@ -1,7 +1,8 @@ use simple_logger::SimpleLogger; use winit::{ - event::{DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, WindowEvent}, + event::{DeviceEvent, ElementState, Event, KeyEvent, WindowEvent}, event_loop::{ControlFlow, EventLoop}, + keyboard::{Key, ModifiersState}, window::WindowBuilder, }; @@ -23,19 +24,23 @@ fn main() { Event::WindowEvent { event, .. } => match event { WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, WindowEvent::KeyboardInput { - input: - KeyboardInput { + event: + KeyEvent { + logical_key: key, state: ElementState::Released, - virtual_keycode: Some(key), .. }, .. } => { - use winit::event::VirtualKeyCode::*; + // WARNING: Consider using `key_without_modifers()` if available on your platform. + // See the `key_binding` example match key { - Escape => *control_flow = ControlFlow::Exit, - G => window.set_cursor_grab(!modifiers.shift()).unwrap(), - H => window.set_cursor_visible(modifiers.shift()), + Key::Escape => *control_flow = ControlFlow::Exit, + Key::Character(ch) => match ch.to_lowercase().as_str() { + "g" => window.set_cursor_grab(!modifiers.shift_key()).unwrap(), + "h" => window.set_cursor_visible(modifiers.shift_key()), + _ => (), + }, _ => (), } } diff --git a/examples/fullscreen.rs b/examples/fullscreen.rs index 90d41ece12..d9d9876383 100644 --- a/examples/fullscreen.rs +++ b/examples/fullscreen.rs @@ -1,8 +1,9 @@ use std::io::{stdin, stdout, Write}; use simple_logger::SimpleLogger; -use winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}; +use winit::event::{ElementState, Event, KeyEvent, WindowEvent}; use winit::event_loop::{ControlFlow, EventLoop}; +use winit::keyboard::Key; use winit::monitor::{MonitorHandle, VideoMode}; use winit::window::{Fullscreen, WindowBuilder}; @@ -38,30 +39,33 @@ fn main() { Event::WindowEvent { event, .. } => match event { WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, WindowEvent::KeyboardInput { - input: - KeyboardInput { - virtual_keycode: Some(virtual_code), - state, + event: + KeyEvent { + logical_key: key, + state: ElementState::Pressed, .. }, .. - } => match (virtual_code, state) { - (VirtualKeyCode::Escape, _) => *control_flow = ControlFlow::Exit, - (VirtualKeyCode::F, ElementState::Pressed) => { + } => match key { + Key::Escape => *control_flow = ControlFlow::Exit, + + // WARNING: Consider using `key_without_modifers()` if available on your platform. + // See the `key_binding` example + Key::Character("f") => { if window.fullscreen().is_some() { window.set_fullscreen(None); } else { window.set_fullscreen(fullscreen.clone()); } } - (VirtualKeyCode::S, ElementState::Pressed) => { + Key::Character("s") => { println!("window.fullscreen {:?}", window.fullscreen()); } - (VirtualKeyCode::M, ElementState::Pressed) => { + Key::Character("m") => { let is_maximized = window.is_maximized(); window.set_maximized(!is_maximized); } - (VirtualKeyCode::D, ElementState::Pressed) => { + Key::Character("d") => { decorations = !decorations; window.set_decorations(decorations); } diff --git a/examples/handling_close.rs b/examples/handling_close.rs index 8334c1773f..283cf48195 100644 --- a/examples/handling_close.rs +++ b/examples/handling_close.rs @@ -1,7 +1,8 @@ use simple_logger::SimpleLogger; use winit::{ - event::{Event, KeyboardInput, WindowEvent}, + event::{ElementState, Event, KeyEvent, WindowEvent}, event_loop::{ControlFlow, EventLoop}, + keyboard::Key, window::WindowBuilder, }; @@ -17,10 +18,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 = ControlFlow::Wait; match event { @@ -44,16 +41,18 @@ fn main() { // the Y key. } WindowEvent::KeyboardInput { - input: - KeyboardInput { - virtual_keycode: Some(virtual_code), - state: Released, + event: + KeyEvent { + logical_key: key, + state: ElementState::Released, .. }, .. } => { - match virtual_code { - Y => { + // WARNING: Consider using `key_without_modifers()` if available on your platform. + // See the `key_binding` example + match key { + Key::Character("y") => { if close_requested { // This is where you'll want to do any cleanup you need. println!("Buh-bye!"); @@ -66,7 +65,7 @@ fn main() { *control_flow = ControlFlow::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/key_binding.rs b/examples/key_binding.rs new file mode 100644 index 0000000000..6f9bb1fe83 --- /dev/null +++ b/examples/key_binding.rs @@ -0,0 +1,58 @@ +use simple_logger::SimpleLogger; +use winit::{ + dpi::LogicalSize, + event::{ElementState, Event, KeyEvent, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + keyboard::{Key, ModifiersState}, + window::WindowBuilder, +}; + +///////////////////////////////////////////////////////////////////////////// +// WARNING: This is not available on all platforms (for example on the web). +use winit::platform::modifier_supplement::KeyEventExtModifierSupplement; +///////////////////////////////////////////////////////////////////////////// + +fn main() { + SimpleLogger::new().init().unwrap(); + let event_loop = EventLoop::new(); + + let _window = WindowBuilder::new() + .with_inner_size(LogicalSize::new(400.0, 200.0)) + .build(&event_loop) + .unwrap(); + + let mut modifiers = ModifiersState::default(); + + event_loop.run(move |event, _, control_flow| { + *control_flow = ControlFlow::Wait; + + match event { + Event::WindowEvent { event, .. } => match event { + WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, + WindowEvent::ModifiersChanged(new_state) => { + modifiers = new_state; + } + WindowEvent::KeyboardInput { event, .. } => { + handle_key_event(modifiers, event); + } + _ => (), + }, + _ => (), + }; + }); +} + +fn handle_key_event(modifiers: ModifiersState, event: KeyEvent) { + if event.state == ElementState::Pressed && !event.repeat { + match event.key_without_modifiers() { + Key::Character("1") => { + if modifiers.shift_key() { + println!("Shift + 1 | logical_key: {:?}", event.logical_key); + } else { + println!("1"); + } + } + _ => (), + } + } +} diff --git a/examples/minimize.rs b/examples/minimize.rs index eb02a752c9..3c8b7f84ed 100644 --- a/examples/minimize.rs +++ b/examples/minimize.rs @@ -1,8 +1,10 @@ extern crate winit; use simple_logger::SimpleLogger; -use winit::event::{Event, VirtualKeyCode, WindowEvent}; + +use winit::event::{Event, WindowEvent}; use winit::event_loop::{ControlFlow, EventLoop}; +use winit::keyboard::Key; use winit::window::WindowBuilder; fn main() { @@ -25,12 +27,14 @@ fn main() { // Keyboard input event to handle minimize via a hotkey Event::WindowEvent { - event: WindowEvent::KeyboardInput { input, .. }, + event: WindowEvent::KeyboardInput { event, .. }, window_id, } => { if window_id == window.id() { - // Pressing the 'M' key will minimize the window - if input.virtual_keycode == Some(VirtualKeyCode::M) { + // Pressing the 'm' key will minimize the window + // WARNING: Consider using `key_without_modifers()` if available on your platform. + // See the `key_binding` example + if let Key::Character("m") = event.logical_key { window.set_minimized(true); } } diff --git a/examples/multithreaded.rs b/examples/multithreaded.rs index ba2b9ad240..6af0699772 100644 --- a/examples/multithreaded.rs +++ b/examples/multithreaded.rs @@ -5,8 +5,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::{ControlFlow, EventLoop}, + keyboard::{Key, ModifiersState}, window::{CursorIcon, Fullscreen, WindowBuilder}, }; @@ -27,6 +28,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 { @@ -49,91 +51,96 @@ fn main() { ); } } - #[allow(deprecated)] + WindowEvent::ModifiersChanged(mod_state) => { + modifiers = mod_state; + } WindowEvent::KeyboardInput { - input: - KeyboardInput { + event: + KeyEvent { state: ElementState::Released, - virtual_keycode: Some(key), - modifiers, + logical_key: key, .. }, .. } => { + use Key::{ArrowLeft, ArrowRight, Character}; window.set_title(&format!("{:?}", key)); - let state = !modifiers.shift(); - use VirtualKeyCode::*; - match key { - A => window.set_always_on_top(state), - C => window.set_cursor_icon(match state { - true => CursorIcon::Progress, - false => CursorIcon::Default, - }), - D => window.set_decorations(!state), - // Cycle through video modes - Right | Left => { - video_mode_id = match key { - Left => video_mode_id.saturating_sub(1), - Right => (video_modes.len() - 1).min(video_mode_id + 1), + let state = !modifiers.shift_key(); + match &key { + // WARNING: Consider using `key_without_modifers()` if available on your platform. + // See the `key_binding` example + Character(string) => match string.to_lowercase().as_str() { + "a" => window.set_always_on_top(state), + "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, + }), + "g" => window.set_cursor_grab(state).unwrap(), + "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() + } + } + "z" => { + window.set_visible(false); + thread::sleep(Duration::from_secs(1)); + window.set_visible(true); + } + _ => (), + }, + ArrowRight | ArrowLeft => { + video_mode_id = match &key { + 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())) - } - (false, _) => None, - }), - G => window.set_cursor_grab(state).unwrap(), - 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() - } - } - Z => { - window.set_visible(false); - thread::sleep(Duration::from_secs(1)); - window.set_visible(true); - } _ => (), } } @@ -152,10 +159,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 e946537296..2e6a84cfd8 100644 --- a/examples/multiwindow.rs +++ b/examples/multiwindow.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use simple_logger::SimpleLogger; use winit::{ - event::{ElementState, Event, KeyboardInput, WindowEvent}, + event::{ElementState, Event, KeyEvent, WindowEvent}, event_loop::{ControlFlow, EventLoop}, window::Window, }; @@ -34,9 +34,9 @@ fn main() { } } WindowEvent::KeyboardInput { - input: - KeyboardInput { - state: ElementState::Pressed, + event: + KeyEvent { + state: ElementState::Released, .. }, .. diff --git a/examples/resizable.rs b/examples/resizable.rs index 17892d8741..8d3387cabb 100644 --- a/examples/resizable.rs +++ b/examples/resizable.rs @@ -1,8 +1,9 @@ use simple_logger::SimpleLogger; use winit::{ dpi::LogicalSize, - event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, + event::{ElementState, Event, KeyEvent, WindowEvent}, event_loop::{ControlFlow, EventLoop}, + keyboard::KeyCode, window::WindowBuilder, }; @@ -26,9 +27,9 @@ fn main() { Event::WindowEvent { event, .. } => match event { WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, WindowEvent::KeyboardInput { - input: - KeyboardInput { - virtual_keycode: Some(VirtualKeyCode::Space), + event: + KeyEvent { + physical_key: KeyCode::Space, state: ElementState::Released, .. }, diff --git a/examples/window_debug.rs b/examples/window_debug.rs index 577ad5cd73..4a12cf47b9 100644 --- a/examples/window_debug.rs +++ b/examples/window_debug.rs @@ -3,8 +3,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::{ControlFlow, EventLoop}, + keyboard::{Key, KeyCode}, window::{Fullscreen, WindowBuilder}, }; @@ -34,22 +35,24 @@ fn main() { *control_flow = ControlFlow::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); } } - VirtualKeyCode::V => { + KeyCode::KeyV => { if !visible { visible = !visible; window.set_visible(visible); @@ -58,61 +61,65 @@ fn main() { _ => (), }, Event::WindowEvent { - event: WindowEvent::KeyboardInput { input, .. }, + event: + WindowEvent::KeyboardInput { + event: + KeyEvent { + logical_key: Key::Character(key_str), + state: ElementState::Released, + .. + }, + .. + }, .. - } => match input { - KeyboardInput { - virtual_keycode: Some(key), - state: ElementState::Pressed, - .. - } => match key { - VirtualKeyCode::E => { - fn area(size: PhysicalSize) -> u32 { - size.width * size.height - } - - let monitor = window.current_monitor().unwrap(); - if let Some(mode) = monitor - .video_modes() - .max_by(|a, b| area(a.size()).cmp(&area(b.size()))) - { - window.set_fullscreen(Some(Fullscreen::Exclusive(mode))); - } else { - eprintln!("no video modes available"); - } - } - VirtualKeyCode::F => { - if window.fullscreen().is_some() { - window.set_fullscreen(None); - } else { - let monitor = window.current_monitor(); - window.set_fullscreen(Some(Fullscreen::Borderless(monitor))); - } - } - VirtualKeyCode::P => { - if window.fullscreen().is_some() { - window.set_fullscreen(None); - } else { - window.set_fullscreen(Some(Fullscreen::Borderless(None))); - } + } => match key_str { + // WARNING: Consider using `key_without_modifers()` if available on your platform. + // See the `key_binding` example + "e" => { + fn area(size: PhysicalSize) -> u32 { + size.width * size.height } - VirtualKeyCode::M => { - minimized = !minimized; - window.set_minimized(minimized); - } - VirtualKeyCode::Q => { - *control_flow = ControlFlow::Exit; + + let monitor = window.current_monitor().unwrap(); + if let Some(mode) = monitor + .video_modes() + .max_by(|a, b| area(a.size()).cmp(&area(b.size()))) + { + window.set_fullscreen(Some(Fullscreen::Exclusive(mode))); + } else { + eprintln!("no video modes available"); } - VirtualKeyCode::V => { - visible = !visible; - window.set_visible(visible); + } + "f" => { + if window.fullscreen().is_some() { + window.set_fullscreen(None); + } else { + let monitor = window.current_monitor(); + window.set_fullscreen(Some(Fullscreen::Borderless(monitor))); } - VirtualKeyCode::X => { - let is_maximized = window.is_maximized(); - window.set_maximized(!is_maximized); + } + "p" => { + if window.fullscreen().is_some() { + window.set_fullscreen(None); + } else { + window.set_fullscreen(Some(Fullscreen::Borderless(None))); } - _ => (), - }, + } + "m" => { + minimized = !minimized; + window.set_minimized(minimized); + } + "q" => { + *control_flow = ControlFlow::Exit; + } + "v" => { + visible = !visible; + window.set_visible(visible); + } + "x" => { + let is_maximized = window.is_maximized(); + window.set_maximized(!is_maximized); + } _ => (), }, Event::WindowEvent { diff --git a/src/event.rs b/src/event.rs index 6660e00688..7e172cad47 100644 --- a/src/event.rs +++ b/src/event.rs @@ -38,6 +38,7 @@ use std::path::PathBuf; use crate::{ dpi::{PhysicalPosition, PhysicalSize}, + keyboard::{self, ModifiersState}, platform_impl, window::{Theme, WindowId}, }; @@ -239,8 +240,12 @@ pub enum WindowEvent<'a> { /// hovered. HoveredFileCancelled, - /// The window received a unicode character. - ReceivedCharacter(char), + /// The user commited an IME string for this window. + /// + /// This is a temporary API until [#1497] gets completed. + /// + /// [#1497]: https://github.com/rust-windowing/winit/issues/1497 + ReceivedImeText(String), /// The window gained or lost focus. /// @@ -248,9 +253,15 @@ pub enum WindowEvent<'a> { 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: /// @@ -365,15 +376,15 @@ impl Clone for WindowEvent<'static> { DroppedFile(file) => DroppedFile(file.clone()), HoveredFile(file) => HoveredFile(file.clone()), HoveredFileCancelled => HoveredFileCancelled, - ReceivedCharacter(c) => ReceivedCharacter(*c), + ReceivedImeText(s) => ReceivedImeText(s.clone()), 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, }, @@ -456,15 +467,15 @@ impl<'a> WindowEvent<'a> { DroppedFile(file) => Some(DroppedFile(file)), HoveredFile(file) => Some(HoveredFile(file)), HoveredFileCancelled => Some(HoveredFileCancelled), - ReceivedCharacter(c) => Some(ReceivedCharacter(c)), + ReceivedImeText(s) => Some(ReceivedImeText(s)), 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)), @@ -593,38 +604,82 @@ 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, Copy, Clone, Eq, PartialEq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct KeyboardInput { - /// Identifies the physical key pressed - /// - /// This should not change if the user adjusts the host's keyboard map. Use when the physical location of the - /// key is more important than the key's host GUI semantics, such as for movement controls in a first-person - /// game. - pub scancode: ScanCode, - +pub struct RawKeyEvent { + pub physical_key: keyboard::KeyCode, pub state: ElementState, +} + +/// Describes a keyboard input targeting a window. +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct KeyEvent { + /// Represents the position of a key independent of the currently active layout. + /// + /// It also uniquely identifies the physical key (i.e. it's mostly synonymous with a scancode). + /// The most prevalent use case for this is games. For example the default keys for the player + /// to move around might be the W, A, S, and D keys on a US layout. The position of these keys + /// is more important than their label, so they should map to Z, Q, S, and D on an "AZERTY" + /// layout. (This value is `KeyCode::KeyW` for the Z key on an AZERTY layout.) + /// + /// Note that `Fn` and `FnLock` key events are not guaranteed to be emitted by `winit`. These + /// keys are usually handled at the hardware or OS level. + pub physical_key: keyboard::KeyCode, - /// Identifies the semantic meaning of the key + /// This value is affected by all modifiers except Ctrl. + /// + /// This has two use cases: + /// - Allows querying whether the current input is a Dead key. + /// - Allows handling key-bindings on platforms which don't + /// support [`key_without_modifiers`]. + /// + /// ## Platform-specific + /// - **Web:** Dead keys might be reported as the real key instead + /// of `Dead` depending on the browser/OS. /// - /// Use when the semantics of the key are more important than the physical location of the key, such as when - /// implementing appropriate behavior for "page up." - pub virtual_keycode: Option, + /// [`key_without_modifiers`]: crate::platform::modifier_supplement::KeyEventExtModifierSupplement::key_without_modifiers + pub logical_key: keyboard::Key<'static>, - /// Modifier keys active at the time of this input. + /// Contains the text produced by this keypress. + /// + /// In most cases this is identical to the content + /// of the `Character` variant of `logical_key`. + /// However, on Windows when a dead key was pressed earlier + /// but cannot be combined with the character from this + /// keypress, the produced text will consist of two characters: + /// the dead-key-character followed by the character resulting + /// from this keypress. + /// + /// An additional difference from `logical_key` is that + /// this field stores the text representation of any key + /// that has such a representation. For example when + /// `logical_key` is `Key::Enter`, this field is `Some("\r")`. /// - /// This is tracked internally to avoid tracking errors arising from modifier key state changes when events from - /// this device are not being delivered to the application, e.g. due to keyboard focus being elsewhere. - #[deprecated = "Deprecated in favor of WindowEvent::ModifiersChanged"] - pub modifiers: ModifiersState, + /// This is `None` if the current keypress cannot + /// be interpreted as text. + /// + /// See also: `text_with_all_modifiers()` + pub text: Option<&'static str>, + + pub location: keyboard::KeyLocation, + pub state: ElementState, + pub repeat: bool, + + pub(crate) platform_specific: platform_impl::KeyEventExtra, } /// Describes touch-screen input state. @@ -725,9 +780,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; @@ -770,303 +822,3 @@ pub enum MouseScrollDelta { /// platform. 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..28983cfaf4 --- /dev/null +++ b/src/keyboard.rs @@ -0,0 +1,1417 @@ +//! Types related to the keyboard. + +// This file contains a substantial portion of the UI Events Specification by the W3C. In +// particular, the variant names within `Key` and `KeyCode` and their documentation are modified +// versions of contents of the aforementioned specification. +// +// The original documents are: +// +// ### For `Key` +// UI Events KeyboardEvent key Values +// https://www.w3.org/TR/2017/CR-uievents-key-20170601/ +// Copyright © 2017 W3C® (MIT, ERCIM, Keio, Beihang). +// +// ### For `KeyCode` +// UI Events KeyboardEvent code Values +// https://www.w3.org/TR/2017/CR-uievents-code-20170601/ +// Copyright © 2017 W3C® (MIT, ERCIM, Keio, Beihang). +// +// These documents were used under the terms of the following license. This W3C license as well as +// the W3C short notice apply to the `Key` and `KeyCode` enums and their variants and the +// documentation attached to their variants. + +// --------- BEGGINING OF W3C LICENSE -------------------------------------------------------------- +// +// License +// +// By obtaining and/or copying this work, you (the licensee) agree that you have read, understood, +// and will comply with the following terms and conditions. +// +// Permission to copy, modify, and distribute this work, with or without modification, for any +// purpose and without fee or royalty is hereby granted, provided that you include the following on +// ALL copies of the work or portions thereof, including modifications: +// +// - The full text of this NOTICE in a location viewable to users of the redistributed or derivative +// work. +// - Any pre-existing intellectual property disclaimers, notices, or terms and conditions. If none +// exist, the W3C Software and Document Short Notice should be included. +// - Notice of any changes or modifications, through a copyright statement on the new code or +// document such as "This software or document includes material copied from or derived from +// [title and URI of the W3C document]. Copyright © [YEAR] W3C® (MIT, ERCIM, Keio, Beihang)." +// +// Disclaimers +// +// THIS WORK IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS MAKE NO REPRESENTATIONS OR WARRANTIES, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY OR FITNESS FOR +// ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE OR DOCUMENT WILL NOT INFRINGE ANY THIRD +// PARTY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS. +// +// COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES +// ARISING OUT OF ANY USE OF THE SOFTWARE OR DOCUMENT. +// +// The name and trademarks of copyright holders may NOT be used in advertising or publicity +// pertaining to the work without specific, written prior permission. Title to copyright in this +// work will at all times remain with copyright holders. +// +// --------- END OF W3C LICENSE -------------------------------------------------------------------- + +// --------- BEGGINING OF W3C SHORT NOTICE --------------------------------------------------------- +// +// winit: https://github.com/rust-windowing/winit +// +// Copyright © 2021 World Wide Web Consortium, (Massachusetts Institute of Technology, European +// Research Consortium for Informatics and Mathematics, Keio University, Beihang). All Rights +// Reserved. This work is distributed under the W3C® Software License [1] in the hope that it will +// be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// [1] http://www.w3.org/Consortium/Legal/copyright-software +// +// --------- END OF W3C SHORT NOTICE --------------------------------------------------------------- + +use nameof::name_of; + +impl ModifiersState { + /// Returns `true` if the shift key is pressed. + pub fn shift_key(&self) -> bool { + self.intersects(Self::SHIFT) + } + /// Returns `true` if the control key is pressed. + pub fn control_key(&self) -> bool { + self.intersects(Self::CONTROL) + } + /// Returns `true` if the alt key is pressed. + pub fn alt_key(&self) -> bool { + self.intersects(Self::ALT) + } + /// Returns `true` if the super key is pressed. + pub fn super_key(&self) -> bool { + self.intersects(Self::SUPER) + } +} + +bitflags! { + /// Represents the current state of the keyboard modifiers + /// + /// Each flag represents a modifier and is set if this modifier is active. + #[derive(Default)] + pub struct ModifiersState: u32 { + // left and right modifiers are currently commented out, but we should be able to support + // them in a future release + /// The "shift" key. + const SHIFT = 0b100 << 0; + // const LSHIFT = 0b010 << 0; + // const RSHIFT = 0b001 << 0; + /// The "control" key. + const CONTROL = 0b100 << 3; + // const LCTRL = 0b010 << 3; + // const RCTRL = 0b001 << 3; + /// The "alt" key. + const ALT = 0b100 << 6; + // const LALT = 0b010 << 6; + // const RALT = 0b001 << 6; + /// This is the "windows" key on PC and "command" key on Mac. + const SUPER = 0b100 << 9; + // const LSUPER = 0b010 << 9; + // const RSUPER = 0b001 << 9; + } +} + +#[cfg(feature = "serde")] +mod modifiers_serde { + use super::ModifiersState; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + #[derive(Default, Serialize, Deserialize)] + #[serde(default)] + #[serde(rename = "ModifiersState")] + pub struct ModifiersStateSerialize { + pub shift_key: bool, + pub control_key: bool, + pub alt_key: bool, + pub super_key: bool, + } + + impl Serialize for ModifiersState { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let s = ModifiersStateSerialize { + shift_key: self.shift_key(), + control_key: self.control_key(), + alt_key: self.alt_key(), + super_key: self.super_key(), + }; + s.serialize(serializer) + } + } + + impl<'de> Deserialize<'de> for ModifiersState { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let ModifiersStateSerialize { + shift_key, + control_key, + alt_key, + super_key, + } = ModifiersStateSerialize::deserialize(deserializer)?; + let mut m = ModifiersState::empty(); + m.set(ModifiersState::SHIFT, shift_key); + m.set(ModifiersState::CONTROL, control_key); + m.set(ModifiersState::ALT, alt_key); + m.set(ModifiersState::SUPER, super_key); + Ok(m) + } + } +} + +/// Contains the platform-native physical key identifier (aka scancode) +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum NativeKeyCode { + Unidentified, + Windows(u16), + MacOS(u32), + XKB(u32), +} +impl std::fmt::Debug for NativeKeyCode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use NativeKeyCode::{MacOS, Unidentified, Windows, XKB}; + let mut debug_tuple; + match self { + Unidentified => { + debug_tuple = f.debug_tuple(name_of!(Unidentified)); + } + Windows(v) => { + debug_tuple = f.debug_tuple(name_of!(Windows)); + debug_tuple.field(&format_args!("0x{:04X}", v)); + } + MacOS(v) => { + debug_tuple = f.debug_tuple(name_of!(MacOS)); + debug_tuple.field(v); + } + XKB(v) => { + debug_tuple = f.debug_tuple(name_of!(XKB)); + debug_tuple.field(v); + } + } + debug_tuple.finish() + } +} + +/// Represents the location of a physical key. +/// +/// This mostly conforms to the UI Events Specification's [`KeyboardEvent.code`] with a few +/// exceptions: +/// - The keys that the specification calls "MetaLeft" and "MetaRight" are named "SuperLeft" and +/// "SuperRight" here. +/// - The key that the specification calls "Super" is reported as `Unidentified` here. +/// - The `Unidentified` variant here, can still identifiy a key through it's `NativeKeyCode`. +/// +/// [`KeyboardEvent.code`]: https://w3c.github.io/uievents-code/#code-value-tables +#[non_exhaustive] +#[derive(Debug, Clone, 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 scancode is provided (if available) in order + /// to allow the user to specify keybindings for keys which + /// are not defined by this API. + Unidentified(NativeKeyCode), + /// ` on a US keyboard. 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. + BrowserBack, + BrowserFavorites, + /// Some laptops place this key to the right of the key. + BrowserForward, + 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, + 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, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum Key<'a> { + /// A key string that corresponds to the character typed by the user, taking into account the + /// user’s current locale setting, and any system-level keyboard mapping overrides that are in + /// effect. + Character(&'a str), + + /// This variant is used when the key cannot be translated to any other variant. + /// + /// The native scancode is provided (if available) in order to allow the user to specify + /// keybindings for keys which are not defined by this API. + Unidentified(NativeKeyCode), + + /// 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, + Hyper, + /// Used to enable "super" modifier function for interpreting concurrent or subsequent keyboard + /// input. This key value is used for the "Windows Logo" key and the Apple `Command` or `⌘` key. + /// + /// Note: In some contexts (e.g. the Web) this is referred to as the "Meta" key. + Super, + /// The `Enter` or `↵` key. Used to activate current selection or accept current input. This key + /// value is also used for the `Return` (Macintosh numpad) key. This key value is also used for + /// the Android `KEYCODE_DPAD_CENTER`. + Enter, + /// The Horizontal Tabulation `Tab` key. + Tab, + /// Used in text to insert a space between words. Usually located below the character keys. + Space, + /// Navigate or traverse downward. (`KEYCODE_DPAD_DOWN`) + ArrowDown, + /// Navigate or traverse leftward. (`KEYCODE_DPAD_LEFT`) + ArrowLeft, + /// Navigate or traverse rightward. (`KEYCODE_DPAD_RIGHT`) + ArrowRight, + /// Navigate or traverse upward. (`KEYCODE_DPAD_UP`) + ArrowUp, + /// The End key, used with keyboard entry to go to the end of content (`KEYCODE_MOVE_END`). + End, + /// The Home key, used with keyboard entry, to go to start of content (`KEYCODE_MOVE_HOME`). + /// For the mobile phone `Home` key (which goes to the phone’s main screen), use [`GoHome`]. + /// + /// [`GoHome`]: Self::GoHome + Home, + /// Scroll down or display next page of content. + PageDown, + /// Scroll up or display previous page of content. + PageUp, + /// Used to remove the character to the left of the cursor. This key value is also used for + /// the key labeled `Delete` on MacOS keyboards. + Backspace, + /// Remove the currently selected input. + Clear, + /// Copy the current selection. (`APPCOMMAND_COPY`) + Copy, + /// The Cursor Select key. + CrSel, + /// Cut the current selection. (`APPCOMMAND_CUT`) + Cut, + /// Used to delete the character to the right of the cursor. This key value is also used for the + /// key labeled `Delete` on MacOS keyboards when `Fn` is active. + Delete, + /// The Erase to End of Field key. This key deletes all characters from the current cursor + /// position to the end of the current field. + EraseEof, + /// The Extend Selection (Exsel) key. + ExSel, + /// Toggle between text modes for insertion or overtyping. + /// (`KEYCODE_INSERT`) + Insert, + /// The Paste key. (`APPCOMMAND_PASTE`) + Paste, + /// Redo the last action. (`APPCOMMAND_REDO`) + Redo, + /// Undo the last action. (`APPCOMMAND_UNDO`) + Undo, + /// The Accept (Commit, OK) key. Accept current option or input method sequence conversion. + Accept, + /// Redo or repeat an action. + Again, + /// The Attention (Attn) key. + Attn, + Cancel, + /// Show the application’s context menu. + /// This key is commonly found between the right `Super` key and the right `Control` key. + ContextMenu, + /// The `Esc` key. This key was originally used to initiate an escape sequence, but is + /// now more generally used to exit or "escape" the current context, such as closing a dialog + /// or exiting full screen mode. + Escape, + Execute, + /// Open the Find dialog. (`APPCOMMAND_FIND`) + Find, + /// Open a help dialog or toggle display of help information. (`APPCOMMAND_HELP`, + /// `KEYCODE_HELP`) + Help, + /// Pause the current state or application (as appropriate). + /// + /// Note: Do not use this value for the `Pause` button on media controllers. Use `"MediaPause"` + /// instead. + Pause, + /// Play or resume the current state or application (as appropriate). + /// + /// Note: Do not use this value for the `Play` button on media controllers. Use `"MediaPlay"` + /// instead. + Play, + /// The properties (Props) key. + Props, + Select, + /// The ZoomIn key. (`KEYCODE_ZOOM_IN`) + ZoomIn, + /// The ZoomOut key. (`KEYCODE_ZOOM_OUT`) + ZoomOut, + /// The Brightness Down key. Typically controls the display brightness. + /// (`KEYCODE_BRIGHTNESS_DOWN`) + BrightnessDown, + /// The Brightness Up key. Typically controls the display brightness. (`KEYCODE_BRIGHTNESS_UP`) + BrightnessUp, + /// Toggle removable media to eject (open) and insert (close) state. (`KEYCODE_MEDIA_EJECT`) + Eject, + LogOff, + /// Toggle power state. (`KEYCODE_POWER`) + /// Note: Note: Some devices might not expose this key to the operating environment. + Power, + /// The `PowerOff` key. Sometime called `PowerDown`. + PowerOff, + /// Initiate print-screen function. + PrintScreen, + /// The Hibernate key. This key saves the current state of the computer to disk so that it can + /// be restored. The computer will then shutdown. + Hibernate, + /// The Standby key. This key turns off the display and places the computer into a low-power + /// mode without completely shutting down. It is sometimes labelled `Suspend` or `Sleep` key. + /// (`KEYCODE_SLEEP`) + Standby, + /// The WakeUp key. (`KEYCODE_WAKEUP`) + WakeUp, + /// Initate the multi-candidate mode. + AllCandidates, + Alphanumeric, + /// Initiate the Code Input mode to allow characters to be entered by + /// their code points. + CodeInput, + /// The Compose key, also known as "Multi_key" on the X Window System. This key acts in a + /// manner similar to a dead key, triggering a mode where subsequent key presses are combined to + /// produce a different character. + Compose, + /// Convert the current input method sequence. + Convert, + /// The Final Mode `Final` key used on some Asian keyboards, to enable the final mode for IMEs. + FinalMode, + /// Switch to the first character group. (ISO/IEC 9995) + GroupFirst, + /// Switch to the last character group. (ISO/IEC 9995) + GroupLast, + /// Switch to the next character group. (ISO/IEC 9995) + GroupNext, + /// Switch to the previous character group. (ISO/IEC 9995) + GroupPrevious, + /// Toggle between or cycle through input modes of IMEs. + ModeChange, + NextCandidate, + /// Accept current input method sequence without + /// conversion in IMEs. + NonConvert, + PreviousCandidate, + Process, + SingleCandidate, + /// Toggle between Hangul and English modes. + HangulMode, + HanjaMode, + JunjaMode, + /// The Eisu key. This key may close the IME, but its purpose is defined by the current IME. + /// (`KEYCODE_EISU`) + Eisu, + /// The (Half-Width) Characters key. + Hankaku, + /// The Hiragana (Japanese Kana characters) key. + Hiragana, + /// The Hiragana/Katakana toggle key. (`KEYCODE_KATAKANA_HIRAGANA`) + HiraganaKatakana, + /// The Kana Mode (Kana Lock) key. This key is used to enter hiragana mode (typically from + /// romaji mode). + KanaMode, + /// The Kanji (Japanese name for ideographic characters of Chinese origin) Mode key. This key is + /// typically used to switch to a hiragana keyboard for the purpose of converting input into + /// kanji. (`KEYCODE_KANA`) + KanjiMode, + /// The Katakana (Japanese Kana characters) key. + Katakana, + /// The Roman characters function key. + Romaji, + /// The Zenkaku (Full-Width) Characters key. + Zenkaku, + /// The Zenkaku/Hankaku (full-width/half-width) toggle key. (`KEYCODE_ZENKAKU_HANKAKU`) + ZenkakuHankaku, + /// General purpose virtual function key, as index 1. + Soft1, + /// General purpose virtual function key, as index 2. + Soft2, + /// General purpose virtual function key, as index 3. + Soft3, + /// General purpose virtual function key, as index 4. + Soft4, + /// Select next (numerically or logically) lower channel. (`APPCOMMAND_MEDIA_CHANNEL_DOWN`, + /// `KEYCODE_CHANNEL_DOWN`) + ChannelDown, + /// Select next (numerically or logically) higher channel. (`APPCOMMAND_MEDIA_CHANNEL_UP`, + /// `KEYCODE_CHANNEL_UP`) + ChannelUp, + /// Close the current document or message (Note: This doesn’t close the application). + /// (`APPCOMMAND_CLOSE`) + Close, + /// Open an editor to forward the current message. (`APPCOMMAND_FORWARD_MAIL`) + MailForward, + /// Open an editor to reply to the current message. (`APPCOMMAND_REPLY_TO_MAIL`) + MailReply, + /// Send the current message. (`APPCOMMAND_SEND_MAIL`) + MailSend, + /// Close the current media, for example to close a CD or DVD tray. (`KEYCODE_MEDIA_CLOSE`) + MediaClose, + /// Initiate or continue forward playback at faster than normal speed, or increase speed if + /// already fast forwarding. (`APPCOMMAND_MEDIA_FAST_FORWARD`, `KEYCODE_MEDIA_FAST_FORWARD`) + MediaFastForward, + /// Pause the currently playing media. (`APPCOMMAND_MEDIA_PAUSE`, `KEYCODE_MEDIA_PAUSE`) + /// + /// Note: Media controller devices should use this value rather than `"Pause"` for their pause + /// keys. + MediaPause, + /// Initiate or continue media playback at normal speed, if not currently playing at normal + /// speed. (`APPCOMMAND_MEDIA_PLAY`, `KEYCODE_MEDIA_PLAY`) + MediaPlay, + /// Toggle media between play and pause states. (`APPCOMMAND_MEDIA_PLAY_PAUSE`, + /// `KEYCODE_MEDIA_PLAY_PAUSE`) + MediaPlayPause, + /// Initiate or resume recording of currently selected media. (`APPCOMMAND_MEDIA_RECORD`, + /// `KEYCODE_MEDIA_RECORD`) + MediaRecord, + /// Initiate or continue reverse playback at faster than normal speed, or increase speed if + /// already rewinding. (`APPCOMMAND_MEDIA_REWIND`, `KEYCODE_MEDIA_REWIND`) + MediaRewind, + /// Stop media playing, pausing, forwarding, rewinding, or recording, if not already stopped. + /// (`APPCOMMAND_MEDIA_STOP`, `KEYCODE_MEDIA_STOP`) + MediaStop, + /// Seek to next media or program track. (`APPCOMMAND_MEDIA_NEXTTRACK`, `KEYCODE_MEDIA_NEXT`) + MediaTrackNext, + /// Seek to previous media or program track. (`APPCOMMAND_MEDIA_PREVIOUSTRACK`, + /// `KEYCODE_MEDIA_PREVIOUS`) + MediaTrackPrevious, + /// Open a new document or message. (`APPCOMMAND_NEW`) + New, + /// Open an existing document or message. (`APPCOMMAND_OPEN`) + Open, + /// Print the current document or message. (`APPCOMMAND_PRINT`) + Print, + /// Save the current document or message. (`APPCOMMAND_SAVE`) + Save, + /// Spellcheck the current document or selection. (`APPCOMMAND_SPELL_CHECK`) + SpellCheck, + /// The `11` key found on media numpads that + /// have buttons from `1` ... `12`. + Key11, + /// The `12` key found on media numpads that + /// have buttons from `1` ... `12`. + Key12, + /// Adjust audio balance leftward. (`VK_AUDIO_BALANCE_LEFT`) + AudioBalanceLeft, + /// Adjust audio balance rightward. (`VK_AUDIO_BALANCE_RIGHT`) + AudioBalanceRight, + /// Decrease audio bass boost or cycle down through bass boost states. (`APPCOMMAND_BASS_DOWN`, + /// `VK_BASS_BOOST_DOWN`) + AudioBassBoostDown, + /// Toggle bass boost on/off. (`APPCOMMAND_BASS_BOOST`) + AudioBassBoostToggle, + /// Increase audio bass boost or cycle up through bass boost states. (`APPCOMMAND_BASS_UP`, + /// `VK_BASS_BOOST_UP`) + AudioBassBoostUp, + /// Adjust audio fader towards front. (`VK_FADER_FRONT`) + AudioFaderFront, + /// Adjust audio fader towards rear. (`VK_FADER_REAR`) + AudioFaderRear, + /// Advance surround audio mode to next available mode. (`VK_SURROUND_MODE_NEXT`) + AudioSurroundModeNext, + /// Decrease treble. (`APPCOMMAND_TREBLE_DOWN`) + AudioTrebleDown, + /// Increase treble. (`APPCOMMAND_TREBLE_UP`) + AudioTrebleUp, + /// Decrease audio volume. (`APPCOMMAND_VOLUME_DOWN`, `KEYCODE_VOLUME_DOWN`) + AudioVolumeDown, + /// Increase audio volume. (`APPCOMMAND_VOLUME_UP`, `KEYCODE_VOLUME_UP`) + AudioVolumeUp, + /// Toggle between muted state and prior volume level. (`APPCOMMAND_VOLUME_MUTE`, + /// `KEYCODE_VOLUME_MUTE`) + AudioVolumeMute, + /// Toggle the microphone on/off. (`APPCOMMAND_MIC_ON_OFF_TOGGLE`) + MicrophoneToggle, + /// Decrease microphone volume. (`APPCOMMAND_MICROPHONE_VOLUME_DOWN`) + MicrophoneVolumeDown, + /// Increase microphone volume. (`APPCOMMAND_MICROPHONE_VOLUME_UP`) + MicrophoneVolumeUp, + /// Mute the microphone. (`APPCOMMAND_MICROPHONE_VOLUME_MUTE`, `KEYCODE_MUTE`) + MicrophoneVolumeMute, + /// Show correction list when a word is incorrectly identified. (`APPCOMMAND_CORRECTION_LIST`) + SpeechCorrectionList, + /// Toggle between dictation mode and command/control mode. + /// (`APPCOMMAND_DICTATE_OR_COMMAND_CONTROL_TOGGLE`) + SpeechInputToggle, + /// The first generic "LaunchApplication" key. This is commonly associated with launching "My + /// Computer", and may have a computer symbol on the key. (`APPCOMMAND_LAUNCH_APP1`) + LaunchApplication1, + /// The second generic "LaunchApplication" key. This is commonly associated with launching + /// "Calculator", and may have a calculator symbol on the key. (`APPCOMMAND_LAUNCH_APP2`, + /// `KEYCODE_CALCULATOR`) + LaunchApplication2, + /// The "Calendar" key. (`KEYCODE_CALENDAR`) + LaunchCalendar, + /// The "Contacts" key. (`KEYCODE_CONTACTS`) + LaunchContacts, + /// The "Mail" key. (`APPCOMMAND_LAUNCH_MAIL`) + LaunchMail, + /// The "Media Player" key. (`APPCOMMAND_LAUNCH_MEDIA_SELECT`) + LaunchMediaPlayer, + LaunchMusicPlayer, + LaunchPhone, + LaunchScreenSaver, + LaunchSpreadsheet, + LaunchWebBrowser, + LaunchWebCam, + LaunchWordProcessor, + /// Navigate to previous content or page in current history. (`APPCOMMAND_BROWSER_BACKWARD`) + BrowserBack, + /// Open the list of browser favorites. (`APPCOMMAND_BROWSER_FAVORITES`) + BrowserFavorites, + /// Navigate to next content or page in current history. (`APPCOMMAND_BROWSER_FORWARD`) + BrowserForward, + /// Go to the user’s preferred home page. (`APPCOMMAND_BROWSER_HOME`) + BrowserHome, + /// Refresh the current page or content. (`APPCOMMAND_BROWSER_REFRESH`) + BrowserRefresh, + /// Call up the user’s preferred search page. (`APPCOMMAND_BROWSER_SEARCH`) + BrowserSearch, + /// Stop loading the current page or content. (`APPCOMMAND_BROWSER_STOP`) + BrowserStop, + /// The Application switch key, which provides a list of recent apps to switch between. + /// (`KEYCODE_APP_SWITCH`) + AppSwitch, + /// The Call key. (`KEYCODE_CALL`) + Call, + /// The Camera key. (`KEYCODE_CAMERA`) + Camera, + /// The Camera focus key. (`KEYCODE_FOCUS`) + CameraFocus, + /// The End Call key. (`KEYCODE_ENDCALL`) + EndCall, + /// The Back key. (`KEYCODE_BACK`) + GoBack, + /// The Home key, which goes to the phone’s main screen. (`KEYCODE_HOME`) + GoHome, + /// The Headset Hook key. (`KEYCODE_HEADSETHOOK`) + HeadsetHook, + LastNumberRedial, + /// The Notification key. (`KEYCODE_NOTIFICATION`) + Notification, + /// Toggle between manner mode state: silent, vibrate, ring, ... (`KEYCODE_MANNER_MODE`) + MannerMode, + VoiceDial, + /// Switch to viewing TV. (`KEYCODE_TV`) + TV, + /// TV 3D Mode. (`KEYCODE_3D_MODE`) + TV3DMode, + /// Toggle between antenna and cable input. (`KEYCODE_TV_ANTENNA_CABLE`) + TVAntennaCable, + /// Audio description. (`KEYCODE_TV_AUDIO_DESCRIPTION`) + TVAudioDescription, + /// Audio description mixing volume down. (`KEYCODE_TV_AUDIO_DESCRIPTION_MIX_DOWN`) + TVAudioDescriptionMixDown, + /// Audio description mixing volume up. (`KEYCODE_TV_AUDIO_DESCRIPTION_MIX_UP`) + TVAudioDescriptionMixUp, + /// Contents menu. (`KEYCODE_TV_CONTENTS_MENU`) + TVContentsMenu, + /// Contents menu. (`KEYCODE_TV_DATA_SERVICE`) + TVDataService, + /// Switch the input mode on an external TV. (`KEYCODE_TV_INPUT`) + TVInput, + /// Switch to component input #1. (`KEYCODE_TV_INPUT_COMPONENT_1`) + TVInputComponent1, + /// Switch to component input #2. (`KEYCODE_TV_INPUT_COMPONENT_2`) + TVInputComponent2, + /// Switch to composite input #1. (`KEYCODE_TV_INPUT_COMPOSITE_1`) + TVInputComposite1, + /// Switch to composite input #2. (`KEYCODE_TV_INPUT_COMPOSITE_2`) + TVInputComposite2, + /// Switch to HDMI input #1. (`KEYCODE_TV_INPUT_HDMI_1`) + TVInputHDMI1, + /// Switch to HDMI input #2. (`KEYCODE_TV_INPUT_HDMI_2`) + TVInputHDMI2, + /// Switch to HDMI input #3. (`KEYCODE_TV_INPUT_HDMI_3`) + TVInputHDMI3, + /// Switch to HDMI input #4. (`KEYCODE_TV_INPUT_HDMI_4`) + TVInputHDMI4, + /// Switch to VGA input #1. (`KEYCODE_TV_INPUT_VGA_1`) + TVInputVGA1, + /// Media context menu. (`KEYCODE_TV_MEDIA_CONTEXT_MENU`) + TVMediaContext, + /// Toggle network. (`KEYCODE_TV_NETWORK`) + TVNetwork, + /// Number entry. (`KEYCODE_TV_NUMBER_ENTRY`) + TVNumberEntry, + /// Toggle the power on an external TV. (`KEYCODE_TV_POWER`) + TVPower, + /// Radio. (`KEYCODE_TV_RADIO_SERVICE`) + TVRadioService, + /// Satellite. (`KEYCODE_TV_SATELLITE`) + TVSatellite, + /// Broadcast Satellite. (`KEYCODE_TV_SATELLITE_BS`) + TVSatelliteBS, + /// Communication Satellite. (`KEYCODE_TV_SATELLITE_CS`) + TVSatelliteCS, + /// Toggle between available satellites. (`KEYCODE_TV_SATELLITE_SERVICE`) + TVSatelliteToggle, + /// Analog Terrestrial. (`KEYCODE_TV_TERRESTRIAL_ANALOG`) + TVTerrestrialAnalog, + /// Digital Terrestrial. (`KEYCODE_TV_TERRESTRIAL_DIGITAL`) + TVTerrestrialDigital, + /// Timer programming. (`KEYCODE_TV_TIMER_PROGRAMMING`) + TVTimer, + /// Switch the input mode on an external AVR (audio/video receiver). (`KEYCODE_AVR_INPUT`) + AVRInput, + /// Toggle the power on an external AVR (audio/video receiver). (`KEYCODE_AVR_POWER`) + AVRPower, + /// General purpose color-coded media function key, as index 0 (red). (`VK_COLORED_KEY_0`, + /// `KEYCODE_PROG_RED`) + ColorF0Red, + /// General purpose color-coded media function key, as index 1 (green). (`VK_COLORED_KEY_1`, + /// `KEYCODE_PROG_GREEN`) + ColorF1Green, + /// General purpose color-coded media function key, as index 2 (yellow). (`VK_COLORED_KEY_2`, + /// `KEYCODE_PROG_YELLOW`) + ColorF2Yellow, + /// General purpose color-coded media function key, as index 3 (blue). (`VK_COLORED_KEY_3`, + /// `KEYCODE_PROG_BLUE`) + ColorF3Blue, + /// General purpose color-coded media function key, as index 4 (grey). (`VK_COLORED_KEY_4`) + ColorF4Grey, + /// General purpose color-coded media function key, as index 5 (brown). (`VK_COLORED_KEY_5`) + ColorF5Brown, + /// Toggle the display of Closed Captions. (`VK_CC`, `KEYCODE_CAPTIONS`) + ClosedCaptionToggle, + /// Adjust brightness of device, by toggling between or cycling through states. (`VK_DIMMER`) + Dimmer, + /// Swap video sources. (`VK_DISPLAY_SWAP`) + DisplaySwap, + /// Select Digital Video Rrecorder. (`KEYCODE_DVR`) + DVR, + /// Exit the current application. (`VK_EXIT`) + Exit, + /// Clear program or content stored as favorite 0. (`VK_CLEAR_FAVORITE_0`) + FavoriteClear0, + /// Clear program or content stored as favorite 1. (`VK_CLEAR_FAVORITE_1`) + FavoriteClear1, + /// Clear program or content stored as favorite 2. (`VK_CLEAR_FAVORITE_2`) + FavoriteClear2, + /// Clear program or content stored as favorite 3. (`VK_CLEAR_FAVORITE_3`) + FavoriteClear3, + /// Select (recall) program or content stored as favorite 0. (`VK_RECALL_FAVORITE_0`) + FavoriteRecall0, + /// Select (recall) program or content stored as favorite 1. (`VK_RECALL_FAVORITE_1`) + FavoriteRecall1, + /// Select (recall) program or content stored as favorite 2. (`VK_RECALL_FAVORITE_2`) + FavoriteRecall2, + /// Select (recall) program or content stored as favorite 3. (`VK_RECALL_FAVORITE_3`) + FavoriteRecall3, + /// Store current program or content as favorite 0. (`VK_STORE_FAVORITE_0`) + FavoriteStore0, + /// Store current program or content as favorite 1. (`VK_STORE_FAVORITE_1`) + FavoriteStore1, + /// Store current program or content as favorite 2. (`VK_STORE_FAVORITE_2`) + FavoriteStore2, + /// Store current program or content as favorite 3. (`VK_STORE_FAVORITE_3`) + FavoriteStore3, + /// Toggle display of program or content guide. (`VK_GUIDE`, `KEYCODE_GUIDE`) + Guide, + /// If guide is active and displayed, then display next day’s content. (`VK_NEXT_DAY`) + GuideNextDay, + /// If guide is active and displayed, then display previous day’s content. (`VK_PREV_DAY`) + GuidePreviousDay, + /// Toggle display of information about currently selected context or media. (`VK_INFO`, + /// `KEYCODE_INFO`) + Info, + /// Toggle instant replay. (`VK_INSTANT_REPLAY`) + InstantReplay, + /// Launch linked content, if available and appropriate. (`VK_LINK`) + Link, + /// List the current program. (`VK_LIST`) + ListProgram, + /// Toggle display listing of currently available live content or programs. (`VK_LIVE`) + LiveContent, + /// Lock or unlock current content or program. (`VK_LOCK`) + Lock, + /// Show a list of media applications: audio/video players and image viewers. (`VK_APPS`) + /// + /// Note: Do not confuse this key value with the Windows' `VK_APPS` / `VK_CONTEXT_MENU` key, + /// which is encoded as `"ContextMenu"`. + MediaApps, + /// Audio track key. (`KEYCODE_MEDIA_AUDIO_TRACK`) + MediaAudioTrack, + /// Select previously selected channel or media. (`VK_LAST`, `KEYCODE_LAST_CHANNEL`) + MediaLast, + /// Skip backward to next content or program. (`KEYCODE_MEDIA_SKIP_BACKWARD`) + MediaSkipBackward, + /// Skip forward to next content or program. (`VK_SKIP`, `KEYCODE_MEDIA_SKIP_FORWARD`) + MediaSkipForward, + /// Step backward to next content or program. (`KEYCODE_MEDIA_STEP_BACKWARD`) + MediaStepBackward, + /// Step forward to next content or program. (`KEYCODE_MEDIA_STEP_FORWARD`) + MediaStepForward, + /// Media top menu. (`KEYCODE_MEDIA_TOP_MENU`) + MediaTopMenu, + /// Navigate in. (`KEYCODE_NAVIGATE_IN`) + NavigateIn, + /// Navigate to next key. (`KEYCODE_NAVIGATE_NEXT`) + NavigateNext, + /// Navigate out. (`KEYCODE_NAVIGATE_OUT`) + NavigateOut, + /// Navigate to previous key. (`KEYCODE_NAVIGATE_PREVIOUS`) + NavigatePrevious, + /// Cycle to next favorite channel (in favorites list). (`VK_NEXT_FAVORITE_CHANNEL`) + NextFavoriteChannel, + /// Cycle to next user profile (if there are multiple user profiles). (`VK_USER`) + NextUserProfile, + /// Access on-demand content or programs. (`VK_ON_DEMAND`) + OnDemand, + /// Pairing key to pair devices. (`KEYCODE_PAIRING`) + Pairing, + /// Move picture-in-picture window down. (`VK_PINP_DOWN`) + PinPDown, + /// Move picture-in-picture window. (`VK_PINP_MOVE`) + PinPMove, + /// Toggle display of picture-in-picture window. (`VK_PINP_TOGGLE`) + PinPToggle, + /// Move picture-in-picture window up. (`VK_PINP_UP`) + PinPUp, + /// Decrease media playback speed. (`VK_PLAY_SPEED_DOWN`) + PlaySpeedDown, + /// Reset playback to normal speed. (`VK_PLAY_SPEED_RESET`) + PlaySpeedReset, + /// Increase media playback speed. (`VK_PLAY_SPEED_UP`) + PlaySpeedUp, + /// Toggle random media or content shuffle mode. (`VK_RANDOM_TOGGLE`) + RandomToggle, + /// Not a physical key, but this key code is sent when the remote control battery is low. + /// (`VK_RC_LOW_BATTERY`) + RcLowBattery, + /// Toggle or cycle between media recording speeds. (`VK_RECORD_SPEED_NEXT`) + RecordSpeedNext, + /// Toggle RF (radio frequency) input bypass mode (pass RF input directly to the RF output). + /// (`VK_RF_BYPASS`) + RfBypass, + /// Toggle scan channels mode. (`VK_SCAN_CHANNELS_TOGGLE`) + ScanChannelsToggle, + /// Advance display screen mode to next available mode. (`VK_SCREEN_MODE_NEXT`) + ScreenModeNext, + /// Toggle display of device settings screen. (`VK_SETTINGS`, `KEYCODE_SETTINGS`) + Settings, + /// Toggle split screen mode. (`VK_SPLIT_SCREEN_TOGGLE`) + SplitScreenToggle, + /// Switch the input mode on an external STB (set top box). (`KEYCODE_STB_INPUT`) + STBInput, + /// Toggle the power on an external STB (set top box). (`KEYCODE_STB_POWER`) + STBPower, + /// Toggle display of subtitles, if available. (`VK_SUBTITLE`) + Subtitle, + /// Toggle display of teletext, if available (`VK_TELETEXT`, `KEYCODE_TV_TELETEXT`). + Teletext, + /// Advance video mode to next available mode. (`VK_VIDEO_MODE_NEXT`) + VideoModeNext, + /// Cause device to identify itself in some manner, e.g., audibly or visibly. (`VK_WINK`) + Wink, + /// Toggle between full-screen and scaled content, or alter magnification level. (`VK_ZOOM`, + /// `KEYCODE_TV_ZOOM_MODE`) + ZoomToggle, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F1, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F2, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F3, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F4, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F5, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F6, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F7, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F8, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F9, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F10, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F11, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F12, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F13, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F14, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F15, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F16, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F17, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F18, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F19, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F20, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F21, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F22, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F23, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F24, + /// General-purpose function key. + F25, + /// General-purpose function key. + F26, + /// General-purpose function key. + F27, + /// General-purpose function key. + F28, + /// General-purpose function key. + F29, + /// General-purpose function key. + F30, + /// General-purpose function key. + F31, + /// General-purpose function key. + F32, + /// General-purpose function key. + F33, + /// General-purpose function key. + F34, + /// General-purpose function key. + F35, +} + +impl<'a> Key<'a> { + pub fn to_text(&self) -> Option<&'a str> { + match self { + Key::Character(ch) => Some(*ch), + Key::Enter => Some("\r"), + Key::Backspace => Some("\x08"), + Key::Tab => Some("\t"), + Key::Space => Some(" "), + Key::Escape => Some("\x1b"), + _ => None, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum KeyLocation { + Standard, + Left, + Right, + Numpad, +} diff --git a/src/lib.rs b/src/lib.rs index 9b54930213..6b98130b98 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -153,6 +153,7 @@ pub mod error; pub mod event; pub mod event_loop; mod icon; +pub mod keyboard; pub mod monitor; mod platform_impl; pub mod window; diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 1ee5fce2bb..84f016c2d2 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -21,5 +21,7 @@ pub mod macos; pub mod unix; pub mod windows; +pub mod modifier_supplement; pub mod run_return; +pub mod scancode; pub mod web; diff --git a/src/platform/modifier_supplement.rs b/src/platform/modifier_supplement.rs new file mode 100644 index 0000000000..361ed05d04 --- /dev/null +++ b/src/platform/modifier_supplement.rs @@ -0,0 +1,32 @@ +#![cfg(any( + target_os = "windows", + target_os = "macos", + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" +))] + +use crate::keyboard::Key; + +/// Additional methods for the `KeyEvent` which cannot be implemented on all +/// platforms. +pub trait KeyEventExtModifierSupplement { + /// Identical to `KeyEvent::text` but this is affected by Ctrl. + /// + /// For example, pressing Ctrl+a produces `Some("\x01")`. + fn text_with_all_modifiers(&self) -> Option<&str>; + + /// This value ignores all modifiers including, + /// but not limited to Shift, Caps Lock, + /// and Ctrl. In most cases this means that the + /// unicode character in the resulting string is lowercase. + /// + /// This is useful for key-bindings / shortcut key combinations. + /// + /// In case `logical_key` reports `Dead`, this will still report the + /// key as `Character` according to the current keyboard layout. This value + /// cannot be `Dead`. + fn key_without_modifiers(&self) -> Key<'static>; +} diff --git a/src/platform/scancode.rs b/src/platform/scancode.rs new file mode 100644 index 0000000000..078fe74dea --- /dev/null +++ b/src/platform/scancode.rs @@ -0,0 +1,31 @@ +#![cfg(any( + target_os = "windows", + target_os = "macos", + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" +))] + +// TODO: Maybe merge this with `modifier_supplement` if the two are indeed supported on the same +// set of platforms + +use crate::keyboard::KeyCode; + +pub trait KeyCodeExtScancode { + /// The raw value of the platform-specific physical key identifier. + /// + /// Returns `Some(key_id)` if the conversion was succesful; returns `None` otherwise. + /// + /// ## Platform-specific + /// - **Windows:** A 16bit extended scancode + // 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 3499ba77fd..826e6cba7f 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -3,14 +3,24 @@ use std::os::raw::c_void; use std::path::Path; -use winapi::shared::minwindef::WORD; -use winapi::shared::windef::{HMENU, HWND}; +use winapi::{ + shared::{ + minwindef::{LOWORD, WORD}, + windef::{HMENU, HWND}, + }, + um::{ + winnt::{LANG_KOREAN, PRIMARYLANGID}, + winuser::GetKeyboardLayout, + }, +}; use crate::{ dpi::PhysicalSize, - event::DeviceId, + event::{DeviceId, KeyEvent}, event_loop::EventLoop, + keyboard::{Key, KeyCode, NativeKeyCode}, monitor::MonitorHandle, + platform::{modifier_supplement::KeyEventExtModifierSupplement, scancode::KeyCodeExtScancode}, platform_impl::{EventLoop as WindowsEventLoop, Parent, WinIcon}, window::{BadIcon, Icon, Theme, Window, WindowBuilder}, }; @@ -291,3 +301,349 @@ impl IconExtWindows for Icon { Ok(Icon { inner: win_icon }) } } + +impl KeyEventExtModifierSupplement for KeyEvent { + #[inline] + fn text_with_all_modifiers(&self) -> Option<&str> { + self.platform_specific.text_with_all_modifers + } + + #[inline] + fn key_without_modifiers(&self) -> Key<'static> { + self.platform_specific.key_without_modifiers + } +} + +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 == LANG_KOREAN; + + match self { + KeyCode::Backquote => Some(0x0029), + KeyCode::Backslash => Some(0x002B), + KeyCode::Backspace => Some(0x000E), + KeyCode::BracketLeft => Some(0x001A), + KeyCode::BracketRight => Some(0x001B), + KeyCode::Comma => Some(0x0033), + KeyCode::Digit0 => Some(0x000B), + KeyCode::Digit1 => Some(0x0002), + KeyCode::Digit2 => Some(0x0003), + KeyCode::Digit3 => Some(0x0004), + KeyCode::Digit4 => Some(0x0005), + KeyCode::Digit5 => Some(0x0006), + KeyCode::Digit6 => Some(0x0007), + KeyCode::Digit7 => Some(0x0008), + KeyCode::Digit8 => Some(0x0009), + KeyCode::Digit9 => Some(0x000A), + KeyCode::Equal => Some(0x000D), + KeyCode::IntlBackslash => Some(0x0056), + KeyCode::IntlRo => Some(0x0073), + KeyCode::IntlYen => Some(0x007D), + KeyCode::KeyA => Some(0x001E), + KeyCode::KeyB => Some(0x0030), + KeyCode::KeyC => Some(0x002E), + KeyCode::KeyD => Some(0x0020), + KeyCode::KeyE => Some(0x0012), + KeyCode::KeyF => Some(0x0021), + KeyCode::KeyG => Some(0x0022), + KeyCode::KeyH => Some(0x0023), + KeyCode::KeyI => Some(0x0017), + KeyCode::KeyJ => Some(0x0024), + KeyCode::KeyK => Some(0x0025), + KeyCode::KeyL => Some(0x0026), + KeyCode::KeyM => Some(0x0032), + KeyCode::KeyN => Some(0x0031), + KeyCode::KeyO => Some(0x0018), + KeyCode::KeyP => Some(0x0019), + KeyCode::KeyQ => Some(0x0010), + KeyCode::KeyR => Some(0x0013), + KeyCode::KeyS => Some(0x001F), + KeyCode::KeyT => Some(0x0014), + KeyCode::KeyU => Some(0x0016), + KeyCode::KeyV => Some(0x002F), + KeyCode::KeyW => Some(0x0011), + KeyCode::KeyX => Some(0x002D), + KeyCode::KeyY => Some(0x0015), + KeyCode::KeyZ => Some(0x002C), + KeyCode::Minus => Some(0x000C), + KeyCode::Period => Some(0x0034), + KeyCode::Quote => Some(0x0028), + KeyCode::Semicolon => Some(0x0027), + KeyCode::Slash => Some(0x0035), + KeyCode::AltLeft => Some(0x0038), + KeyCode::AltRight => Some(0xE038), + KeyCode::CapsLock => Some(0x003A), + KeyCode::ContextMenu => Some(0xE05D), + KeyCode::ControlLeft => Some(0x001D), + KeyCode::ControlRight => Some(0xE01D), + KeyCode::Enter => Some(0x001C), + KeyCode::SuperLeft => Some(0xE05B), + KeyCode::SuperRight => Some(0xE05C), + KeyCode::ShiftLeft => Some(0x002A), + KeyCode::ShiftRight => Some(0x0036), + KeyCode::Space => Some(0x0039), + KeyCode::Tab => Some(0x000F), + KeyCode::Convert => Some(0x0079), + KeyCode::Lang1 => { + if is_korean { + Some(0xE0F2) + } else { + Some(0x0072) + } + } + KeyCode::Lang2 => { + if is_korean { + Some(0xE0F1) + } else { + Some(0x0071) + } + } + KeyCode::KanaMode => Some(0x0070), + KeyCode::NonConvert => Some(0x007B), + KeyCode::Delete => Some(0xE053), + KeyCode::End => Some(0xE04F), + KeyCode::Home => Some(0xE047), + KeyCode::Insert => Some(0xE052), + KeyCode::PageDown => Some(0xE051), + KeyCode::PageUp => Some(0xE049), + KeyCode::ArrowDown => Some(0xE050), + KeyCode::ArrowLeft => Some(0xE04B), + KeyCode::ArrowRight => Some(0xE04D), + KeyCode::ArrowUp => Some(0xE048), + KeyCode::NumLock => Some(0xE045), + KeyCode::Numpad0 => Some(0x0052), + KeyCode::Numpad1 => Some(0x004F), + KeyCode::Numpad2 => Some(0x0050), + KeyCode::Numpad3 => Some(0x0051), + KeyCode::Numpad4 => Some(0x004B), + KeyCode::Numpad5 => Some(0x004C), + KeyCode::Numpad6 => Some(0x004D), + KeyCode::Numpad7 => Some(0x0047), + KeyCode::Numpad8 => Some(0x0048), + KeyCode::Numpad9 => Some(0x0049), + KeyCode::NumpadAdd => Some(0x004E), + KeyCode::NumpadComma => Some(0x007E), + KeyCode::NumpadDecimal => Some(0x0053), + KeyCode::NumpadDivide => Some(0xE035), + KeyCode::NumpadEnter => Some(0xE01C), + KeyCode::NumpadEqual => Some(0x0059), + KeyCode::NumpadMultiply => Some(0x0037), + KeyCode::NumpadSubtract => Some(0x004A), + KeyCode::Escape => Some(0x0001), + KeyCode::F1 => Some(0x003B), + KeyCode::F2 => Some(0x003C), + KeyCode::F3 => Some(0x003D), + KeyCode::F4 => Some(0x003E), + KeyCode::F5 => Some(0x003F), + KeyCode::F6 => Some(0x0040), + KeyCode::F7 => Some(0x0041), + KeyCode::F8 => Some(0x0042), + KeyCode::F9 => Some(0x0043), + KeyCode::F10 => Some(0x0044), + KeyCode::F11 => Some(0x0057), + KeyCode::F12 => Some(0x0058), + KeyCode::F13 => Some(0x0064), + KeyCode::F14 => Some(0x0065), + KeyCode::F15 => Some(0x0066), + KeyCode::F16 => Some(0x0067), + KeyCode::F17 => Some(0x0068), + KeyCode::F18 => Some(0x0069), + KeyCode::F19 => Some(0x006A), + KeyCode::F20 => Some(0x006B), + KeyCode::F21 => Some(0x006C), + KeyCode::F22 => Some(0x006D), + KeyCode::F23 => Some(0x006E), + KeyCode::F24 => Some(0x0076), + KeyCode::PrintScreen => Some(0xE037), + //KeyCode::PrintScreen => Some(0x0054), // Alt + PrintScreen + KeyCode::ScrollLock => Some(0x0046), + KeyCode::Pause => Some(0x0045), + //KeyCode::Pause => Some(0xE046), // Ctrl + Pause + KeyCode::BrowserBack => Some(0xE06A), + KeyCode::BrowserFavorites => Some(0xE066), + KeyCode::BrowserForward => Some(0xE069), + KeyCode::BrowserHome => Some(0xE032), + KeyCode::BrowserRefresh => Some(0xE067), + KeyCode::BrowserSearch => Some(0xE065), + KeyCode::BrowserStop => Some(0xE068), + KeyCode::LaunchApp1 => Some(0xE06B), + KeyCode::LaunchApp2 => Some(0xE021), + KeyCode::LaunchMail => Some(0xE06C), + KeyCode::MediaPlayPause => Some(0xE022), + KeyCode::MediaSelect => Some(0xE06D), + KeyCode::MediaStop => Some(0xE024), + KeyCode::MediaTrackNext => Some(0xE019), + KeyCode::MediaTrackPrevious => Some(0xE010), + KeyCode::Power => Some(0xE05E), + KeyCode::AudioVolumeDown => Some(0xE02E), + KeyCode::AudioVolumeMute => Some(0xE020), + KeyCode::AudioVolumeUp => Some(0xE030), + KeyCode::Unidentified(NativeKeyCode::Windows(scancode)) => Some(scancode as u32), + _ => None, + } + } + + fn from_scancode(scancode: u32) -> KeyCode { + // See: https://www.win.tue.nl/~aeb/linux/kbd/scancodes-1.html + // and: https://www.w3.org/TR/uievents-code/ + // and: The widget/NativeKeyToDOMCodeName.h file in the firefox source + + match scancode { + 0x0029 => KeyCode::Backquote, + 0x002B => KeyCode::Backslash, + 0x000E => KeyCode::Backspace, + 0x001A => KeyCode::BracketLeft, + 0x001B => KeyCode::BracketRight, + 0x0033 => KeyCode::Comma, + 0x000B => KeyCode::Digit0, + 0x0002 => KeyCode::Digit1, + 0x0003 => KeyCode::Digit2, + 0x0004 => KeyCode::Digit3, + 0x0005 => KeyCode::Digit4, + 0x0006 => KeyCode::Digit5, + 0x0007 => KeyCode::Digit6, + 0x0008 => KeyCode::Digit7, + 0x0009 => KeyCode::Digit8, + 0x000A => KeyCode::Digit9, + 0x000D => KeyCode::Equal, + 0x0056 => KeyCode::IntlBackslash, + 0x0073 => KeyCode::IntlRo, + 0x007D => KeyCode::IntlYen, + 0x001E => KeyCode::KeyA, + 0x0030 => KeyCode::KeyB, + 0x002E => KeyCode::KeyC, + 0x0020 => KeyCode::KeyD, + 0x0012 => KeyCode::KeyE, + 0x0021 => KeyCode::KeyF, + 0x0022 => KeyCode::KeyG, + 0x0023 => KeyCode::KeyH, + 0x0017 => KeyCode::KeyI, + 0x0024 => KeyCode::KeyJ, + 0x0025 => KeyCode::KeyK, + 0x0026 => KeyCode::KeyL, + 0x0032 => KeyCode::KeyM, + 0x0031 => KeyCode::KeyN, + 0x0018 => KeyCode::KeyO, + 0x0019 => KeyCode::KeyP, + 0x0010 => KeyCode::KeyQ, + 0x0013 => KeyCode::KeyR, + 0x001F => KeyCode::KeyS, + 0x0014 => KeyCode::KeyT, + 0x0016 => KeyCode::KeyU, + 0x002F => KeyCode::KeyV, + 0x0011 => KeyCode::KeyW, + 0x002D => KeyCode::KeyX, + 0x0015 => KeyCode::KeyY, + 0x002C => KeyCode::KeyZ, + 0x000C => KeyCode::Minus, + 0x0034 => KeyCode::Period, + 0x0028 => KeyCode::Quote, + 0x0027 => KeyCode::Semicolon, + 0x0035 => KeyCode::Slash, + 0x0038 => KeyCode::AltLeft, + 0xE038 => KeyCode::AltRight, + 0x003A => KeyCode::CapsLock, + 0xE05D => KeyCode::ContextMenu, + 0x001D => KeyCode::ControlLeft, + 0xE01D => KeyCode::ControlRight, + 0x001C => KeyCode::Enter, + 0xE05B => KeyCode::SuperLeft, + 0xE05C => KeyCode::SuperRight, + 0x002A => KeyCode::ShiftLeft, + 0x0036 => KeyCode::ShiftRight, + 0x0039 => KeyCode::Space, + 0x000F => KeyCode::Tab, + 0x0079 => KeyCode::Convert, + 0x0072 => KeyCode::Lang1, // for non-Korean layout + 0xE0F2 => KeyCode::Lang1, // for Korean layout + 0x0071 => KeyCode::Lang2, // for non-Korean layout + 0xE0F1 => KeyCode::Lang2, // for Korean layout + 0x0070 => KeyCode::KanaMode, + 0x007B => KeyCode::NonConvert, + 0xE053 => KeyCode::Delete, + 0xE04F => KeyCode::End, + 0xE047 => KeyCode::Home, + 0xE052 => KeyCode::Insert, + 0xE051 => KeyCode::PageDown, + 0xE049 => KeyCode::PageUp, + 0xE050 => KeyCode::ArrowDown, + 0xE04B => KeyCode::ArrowLeft, + 0xE04D => KeyCode::ArrowRight, + 0xE048 => KeyCode::ArrowUp, + 0xE045 => KeyCode::NumLock, + 0x0052 => KeyCode::Numpad0, + 0x004F => KeyCode::Numpad1, + 0x0050 => KeyCode::Numpad2, + 0x0051 => KeyCode::Numpad3, + 0x004B => KeyCode::Numpad4, + 0x004C => KeyCode::Numpad5, + 0x004D => KeyCode::Numpad6, + 0x0047 => KeyCode::Numpad7, + 0x0048 => KeyCode::Numpad8, + 0x0049 => KeyCode::Numpad9, + 0x004E => KeyCode::NumpadAdd, + 0x007E => KeyCode::NumpadComma, + 0x0053 => KeyCode::NumpadDecimal, + 0xE035 => KeyCode::NumpadDivide, + 0xE01C => KeyCode::NumpadEnter, + 0x0059 => KeyCode::NumpadEqual, + 0x0037 => KeyCode::NumpadMultiply, + 0x004A => KeyCode::NumpadSubtract, + 0x0001 => KeyCode::Escape, + 0x003B => KeyCode::F1, + 0x003C => KeyCode::F2, + 0x003D => KeyCode::F3, + 0x003E => KeyCode::F4, + 0x003F => KeyCode::F5, + 0x0040 => KeyCode::F6, + 0x0041 => KeyCode::F7, + 0x0042 => KeyCode::F8, + 0x0043 => KeyCode::F9, + 0x0044 => KeyCode::F10, + 0x0057 => KeyCode::F11, + 0x0058 => KeyCode::F12, + 0x0064 => KeyCode::F13, + 0x0065 => KeyCode::F14, + 0x0066 => KeyCode::F15, + 0x0067 => KeyCode::F16, + 0x0068 => KeyCode::F17, + 0x0069 => KeyCode::F18, + 0x006A => KeyCode::F19, + 0x006B => KeyCode::F20, + 0x006C => KeyCode::F21, + 0x006D => KeyCode::F22, + 0x006E => KeyCode::F23, + 0x0076 => KeyCode::F24, + 0xE037 => KeyCode::PrintScreen, + 0x0054 => KeyCode::PrintScreen, // Alt + PrintScreen + 0x0046 => KeyCode::ScrollLock, + 0x0045 => KeyCode::Pause, + 0xE046 => KeyCode::Pause, // Ctrl + Pause + 0xE06A => KeyCode::BrowserBack, + 0xE066 => KeyCode::BrowserFavorites, + 0xE069 => KeyCode::BrowserForward, + 0xE032 => KeyCode::BrowserHome, + 0xE067 => KeyCode::BrowserRefresh, + 0xE065 => KeyCode::BrowserSearch, + 0xE068 => KeyCode::BrowserStop, + 0xE06B => KeyCode::LaunchApp1, + 0xE021 => KeyCode::LaunchApp2, + 0xE06C => KeyCode::LaunchMail, + 0xE022 => KeyCode::MediaPlayPause, + 0xE06D => KeyCode::MediaSelect, + 0xE024 => KeyCode::MediaStop, + 0xE019 => KeyCode::MediaTrackNext, + 0xE010 => KeyCode::MediaTrackPrevious, + 0xE05E => KeyCode::Power, + 0xE02E => KeyCode::AudioVolumeDown, + 0xE020 => KeyCode::AudioVolumeMute, + 0xE030 => KeyCode::AudioVolumeUp, + _ => KeyCode::Unidentified(NativeKeyCode::Windows(scancode as u16)), + } + } +} diff --git a/src/platform_impl/windows/event.rs b/src/platform_impl/windows/event.rs deleted file mode 100644 index 77bd31d2ae..0000000000 --- a/src/platform_impl/windows/event.rs +++ /dev/null @@ -1,417 +0,0 @@ -use std::{ - char, - os::raw::c_int, - ptr, - sync::atomic::{AtomicBool, AtomicPtr, Ordering}, -}; - -use crate::event::{ModifiersState, ScanCode, VirtualKeyCode}; - -use winapi::{ - shared::minwindef::{HKL, HKL__, LPARAM, UINT, WPARAM}, - um::winuser, -}; - -fn key_pressed(vkey: c_int) -> bool { - unsafe { (winuser::GetKeyState(vkey) & (1 << 15)) == (1 << 15) } -} - -pub fn get_key_mods() -> ModifiersState { - let filter_out_altgr = layout_uses_altgr() && key_pressed(winuser::VK_RMENU); - - let mut mods = ModifiersState::empty(); - mods.set(ModifiersState::SHIFT, key_pressed(winuser::VK_SHIFT)); - mods.set( - ModifiersState::CTRL, - key_pressed(winuser::VK_CONTROL) && !filter_out_altgr, - ); - mods.set( - ModifiersState::ALT, - key_pressed(winuser::VK_MENU) && !filter_out_altgr, - ); - mods.set( - ModifiersState::LOGO, - key_pressed(winuser::VK_LWIN) || key_pressed(winuser::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 { winuser::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 c_int) -} - -unsafe fn get_char(keyboard_state: &[u8; 256], v_key: u32, hkl: HKL) -> Option { - let mut unicode_bytes = [0u16; 5]; - let len = winuser::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: AtomicPtr = AtomicPtr::new(ptr::null_mut()); - static USES_ALTGR: AtomicBool = AtomicBool::new(false); - - let hkl = winuser::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[winuser::VK_MENU as usize] = 0x80; - keyboard_state_altgr[winuser::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: c_int) -> Option { - // VK_* codes are documented here https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx - match vkey { - //winuser::VK_LBUTTON => Some(VirtualKeyCode::Lbutton), - //winuser::VK_RBUTTON => Some(VirtualKeyCode::Rbutton), - //winuser::VK_CANCEL => Some(VirtualKeyCode::Cancel), - //winuser::VK_MBUTTON => Some(VirtualKeyCode::Mbutton), - //winuser::VK_XBUTTON1 => Some(VirtualKeyCode::Xbutton1), - //winuser::VK_XBUTTON2 => Some(VirtualKeyCode::Xbutton2), - winuser::VK_BACK => Some(VirtualKeyCode::Back), - winuser::VK_TAB => Some(VirtualKeyCode::Tab), - //winuser::VK_CLEAR => Some(VirtualKeyCode::Clear), - winuser::VK_RETURN => Some(VirtualKeyCode::Return), - winuser::VK_LSHIFT => Some(VirtualKeyCode::LShift), - winuser::VK_RSHIFT => Some(VirtualKeyCode::RShift), - winuser::VK_LCONTROL => Some(VirtualKeyCode::LControl), - winuser::VK_RCONTROL => Some(VirtualKeyCode::RControl), - winuser::VK_LMENU => Some(VirtualKeyCode::LAlt), - winuser::VK_RMENU => Some(VirtualKeyCode::RAlt), - winuser::VK_PAUSE => Some(VirtualKeyCode::Pause), - winuser::VK_CAPITAL => Some(VirtualKeyCode::Capital), - winuser::VK_KANA => Some(VirtualKeyCode::Kana), - //winuser::VK_HANGUEL => Some(VirtualKeyCode::Hanguel), - //winuser::VK_HANGUL => Some(VirtualKeyCode::Hangul), - //winuser::VK_JUNJA => Some(VirtualKeyCode::Junja), - //winuser::VK_FINAL => Some(VirtualKeyCode::Final), - //winuser::VK_HANJA => Some(VirtualKeyCode::Hanja), - winuser::VK_KANJI => Some(VirtualKeyCode::Kanji), - winuser::VK_ESCAPE => Some(VirtualKeyCode::Escape), - winuser::VK_CONVERT => Some(VirtualKeyCode::Convert), - winuser::VK_NONCONVERT => Some(VirtualKeyCode::NoConvert), - //winuser::VK_ACCEPT => Some(VirtualKeyCode::Accept), - //winuser::VK_MODECHANGE => Some(VirtualKeyCode::Modechange), - winuser::VK_SPACE => Some(VirtualKeyCode::Space), - winuser::VK_PRIOR => Some(VirtualKeyCode::PageUp), - winuser::VK_NEXT => Some(VirtualKeyCode::PageDown), - winuser::VK_END => Some(VirtualKeyCode::End), - winuser::VK_HOME => Some(VirtualKeyCode::Home), - winuser::VK_LEFT => Some(VirtualKeyCode::Left), - winuser::VK_UP => Some(VirtualKeyCode::Up), - winuser::VK_RIGHT => Some(VirtualKeyCode::Right), - winuser::VK_DOWN => Some(VirtualKeyCode::Down), - //winuser::VK_SELECT => Some(VirtualKeyCode::Select), - //winuser::VK_PRINT => Some(VirtualKeyCode::Print), - //winuser::VK_EXECUTE => Some(VirtualKeyCode::Execute), - winuser::VK_SNAPSHOT => Some(VirtualKeyCode::Snapshot), - winuser::VK_INSERT => Some(VirtualKeyCode::Insert), - winuser::VK_DELETE => Some(VirtualKeyCode::Delete), - //winuser::VK_HELP => Some(VirtualKeyCode::Help), - 0x30 => Some(VirtualKeyCode::Key0), - 0x31 => Some(VirtualKeyCode::Key1), - 0x32 => Some(VirtualKeyCode::Key2), - 0x33 => Some(VirtualKeyCode::Key3), - 0x34 => Some(VirtualKeyCode::Key4), - 0x35 => Some(VirtualKeyCode::Key5), - 0x36 => Some(VirtualKeyCode::Key6), - 0x37 => Some(VirtualKeyCode::Key7), - 0x38 => Some(VirtualKeyCode::Key8), - 0x39 => Some(VirtualKeyCode::Key9), - 0x41 => Some(VirtualKeyCode::A), - 0x42 => Some(VirtualKeyCode::B), - 0x43 => Some(VirtualKeyCode::C), - 0x44 => Some(VirtualKeyCode::D), - 0x45 => Some(VirtualKeyCode::E), - 0x46 => Some(VirtualKeyCode::F), - 0x47 => Some(VirtualKeyCode::G), - 0x48 => Some(VirtualKeyCode::H), - 0x49 => Some(VirtualKeyCode::I), - 0x4A => Some(VirtualKeyCode::J), - 0x4B => Some(VirtualKeyCode::K), - 0x4C => Some(VirtualKeyCode::L), - 0x4D => Some(VirtualKeyCode::M), - 0x4E => Some(VirtualKeyCode::N), - 0x4F => Some(VirtualKeyCode::O), - 0x50 => Some(VirtualKeyCode::P), - 0x51 => Some(VirtualKeyCode::Q), - 0x52 => Some(VirtualKeyCode::R), - 0x53 => Some(VirtualKeyCode::S), - 0x54 => Some(VirtualKeyCode::T), - 0x55 => Some(VirtualKeyCode::U), - 0x56 => Some(VirtualKeyCode::V), - 0x57 => Some(VirtualKeyCode::W), - 0x58 => Some(VirtualKeyCode::X), - 0x59 => Some(VirtualKeyCode::Y), - 0x5A => Some(VirtualKeyCode::Z), - winuser::VK_LWIN => Some(VirtualKeyCode::LWin), - winuser::VK_RWIN => Some(VirtualKeyCode::RWin), - winuser::VK_APPS => Some(VirtualKeyCode::Apps), - winuser::VK_SLEEP => Some(VirtualKeyCode::Sleep), - winuser::VK_NUMPAD0 => Some(VirtualKeyCode::Numpad0), - winuser::VK_NUMPAD1 => Some(VirtualKeyCode::Numpad1), - winuser::VK_NUMPAD2 => Some(VirtualKeyCode::Numpad2), - winuser::VK_NUMPAD3 => Some(VirtualKeyCode::Numpad3), - winuser::VK_NUMPAD4 => Some(VirtualKeyCode::Numpad4), - winuser::VK_NUMPAD5 => Some(VirtualKeyCode::Numpad5), - winuser::VK_NUMPAD6 => Some(VirtualKeyCode::Numpad6), - winuser::VK_NUMPAD7 => Some(VirtualKeyCode::Numpad7), - winuser::VK_NUMPAD8 => Some(VirtualKeyCode::Numpad8), - winuser::VK_NUMPAD9 => Some(VirtualKeyCode::Numpad9), - winuser::VK_MULTIPLY => Some(VirtualKeyCode::NumpadMultiply), - winuser::VK_ADD => Some(VirtualKeyCode::NumpadAdd), - //winuser::VK_SEPARATOR => Some(VirtualKeyCode::Separator), - winuser::VK_SUBTRACT => Some(VirtualKeyCode::NumpadSubtract), - winuser::VK_DECIMAL => Some(VirtualKeyCode::NumpadDecimal), - winuser::VK_DIVIDE => Some(VirtualKeyCode::NumpadDivide), - winuser::VK_F1 => Some(VirtualKeyCode::F1), - winuser::VK_F2 => Some(VirtualKeyCode::F2), - winuser::VK_F3 => Some(VirtualKeyCode::F3), - winuser::VK_F4 => Some(VirtualKeyCode::F4), - winuser::VK_F5 => Some(VirtualKeyCode::F5), - winuser::VK_F6 => Some(VirtualKeyCode::F6), - winuser::VK_F7 => Some(VirtualKeyCode::F7), - winuser::VK_F8 => Some(VirtualKeyCode::F8), - winuser::VK_F9 => Some(VirtualKeyCode::F9), - winuser::VK_F10 => Some(VirtualKeyCode::F10), - winuser::VK_F11 => Some(VirtualKeyCode::F11), - winuser::VK_F12 => Some(VirtualKeyCode::F12), - winuser::VK_F13 => Some(VirtualKeyCode::F13), - winuser::VK_F14 => Some(VirtualKeyCode::F14), - winuser::VK_F15 => Some(VirtualKeyCode::F15), - winuser::VK_F16 => Some(VirtualKeyCode::F16), - winuser::VK_F17 => Some(VirtualKeyCode::F17), - winuser::VK_F18 => Some(VirtualKeyCode::F18), - winuser::VK_F19 => Some(VirtualKeyCode::F19), - winuser::VK_F20 => Some(VirtualKeyCode::F20), - winuser::VK_F21 => Some(VirtualKeyCode::F21), - winuser::VK_F22 => Some(VirtualKeyCode::F22), - winuser::VK_F23 => Some(VirtualKeyCode::F23), - winuser::VK_F24 => Some(VirtualKeyCode::F24), - winuser::VK_NUMLOCK => Some(VirtualKeyCode::Numlock), - winuser::VK_SCROLL => Some(VirtualKeyCode::Scroll), - winuser::VK_BROWSER_BACK => Some(VirtualKeyCode::NavigateBackward), - winuser::VK_BROWSER_FORWARD => Some(VirtualKeyCode::NavigateForward), - winuser::VK_BROWSER_REFRESH => Some(VirtualKeyCode::WebRefresh), - winuser::VK_BROWSER_STOP => Some(VirtualKeyCode::WebStop), - winuser::VK_BROWSER_SEARCH => Some(VirtualKeyCode::WebSearch), - winuser::VK_BROWSER_FAVORITES => Some(VirtualKeyCode::WebFavorites), - winuser::VK_BROWSER_HOME => Some(VirtualKeyCode::WebHome), - winuser::VK_VOLUME_MUTE => Some(VirtualKeyCode::Mute), - winuser::VK_VOLUME_DOWN => Some(VirtualKeyCode::VolumeDown), - winuser::VK_VOLUME_UP => Some(VirtualKeyCode::VolumeUp), - winuser::VK_MEDIA_NEXT_TRACK => Some(VirtualKeyCode::NextTrack), - winuser::VK_MEDIA_PREV_TRACK => Some(VirtualKeyCode::PrevTrack), - winuser::VK_MEDIA_STOP => Some(VirtualKeyCode::MediaStop), - winuser::VK_MEDIA_PLAY_PAUSE => Some(VirtualKeyCode::PlayPause), - winuser::VK_LAUNCH_MAIL => Some(VirtualKeyCode::Mail), - winuser::VK_LAUNCH_MEDIA_SELECT => Some(VirtualKeyCode::MediaSelect), - /*winuser::VK_LAUNCH_APP1 => Some(VirtualKeyCode::Launch_app1), - winuser::VK_LAUNCH_APP2 => Some(VirtualKeyCode::Launch_app2),*/ - winuser::VK_OEM_PLUS => Some(VirtualKeyCode::Equals), - winuser::VK_OEM_COMMA => Some(VirtualKeyCode::Comma), - winuser::VK_OEM_MINUS => Some(VirtualKeyCode::Minus), - winuser::VK_OEM_PERIOD => Some(VirtualKeyCode::Period), - winuser::VK_OEM_1 => map_text_keys(vkey), - winuser::VK_OEM_2 => map_text_keys(vkey), - winuser::VK_OEM_3 => map_text_keys(vkey), - winuser::VK_OEM_4 => map_text_keys(vkey), - winuser::VK_OEM_5 => map_text_keys(vkey), - winuser::VK_OEM_6 => map_text_keys(vkey), - winuser::VK_OEM_7 => map_text_keys(vkey), - /* winuser::VK_OEM_8 => Some(VirtualKeyCode::Oem_8), */ - winuser::VK_OEM_102 => Some(VirtualKeyCode::OEM102), - /*winuser::VK_PROCESSKEY => Some(VirtualKeyCode::Processkey), - winuser::VK_PACKET => Some(VirtualKeyCode::Packet), - winuser::VK_ATTN => Some(VirtualKeyCode::Attn), - winuser::VK_CRSEL => Some(VirtualKeyCode::Crsel), - winuser::VK_EXSEL => Some(VirtualKeyCode::Exsel), - winuser::VK_EREOF => Some(VirtualKeyCode::Ereof), - winuser::VK_PLAY => Some(VirtualKeyCode::Play), - winuser::VK_ZOOM => Some(VirtualKeyCode::Zoom), - winuser::VK_NONAME => Some(VirtualKeyCode::Noname), - winuser::VK_PA1 => Some(VirtualKeyCode::Pa1), - winuser::VK_OEM_CLEAR => Some(VirtualKeyCode::Oem_clear),*/ - _ => None, - } -} - -pub fn handle_extended_keys( - vkey: c_int, - mut scancode: UINT, - extended: bool, -) -> Option<(c_int, UINT)> { - // 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 { - winuser::VK_SHIFT => unsafe { - winuser::MapVirtualKeyA(scancode, winuser::MAPVK_VSC_TO_VK_EX) as _ - }, - winuser::VK_CONTROL => { - if extended { - winuser::VK_RCONTROL - } else { - winuser::VK_LCONTROL - } - } - winuser::VK_MENU => { - if extended { - winuser::VK_RMENU - } else { - winuser::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 == winuser::VK_PAUSE => return None, - // ...and emit the Pause event for the second event in the pair. - 0x45 if vkey == winuser::VK_PAUSE || vkey == 0xFF => { - scancode = 0xE059; - winuser::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; - winuser::VK_PAUSE - } - // VK_SCROLL has an incorrect vkey value when used with modifiers. - 0x46 => winuser::VK_SCROLL, - _ => vkey, - } - } - }; - Some((vkey, scancode)) -} - -pub fn process_key_params( - wparam: WPARAM, - lparam: LPARAM, -) -> Option<(ScanCode, Option)> { - let scancode = ((lparam >> 16) & 0xff) as UINT; - let extended = (lparam & 0x01000000) != 0; - handle_extended_keys(wparam as _, 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: i32) -> Option { - let char_key = - unsafe { winuser::MapVirtualKeyA(win_virtual_key as u32, winuser::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 b7bc1cd42b..2684017ad5 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -18,6 +18,7 @@ use std::{ }; use winapi::{ + ctypes::c_int, shared::{ basetsd::LONG_PTR, minwindef::{BOOL, DWORD, HIWORD, INT, LOWORD, LPARAM, LRESULT, UINT, WORD, WPARAM}, @@ -27,20 +28,24 @@ use winapi::{ um::{ libloaderapi, mmsystem, ole2, processthreadsapi, timeapi, winbase, winnt::{HANDLE, LONG, LPCSTR, SHORT}, - winuser, + winuser::{self, RAWINPUT}, }, }; use crate::{ dpi::{PhysicalPosition, PhysicalSize}, - event::{DeviceEvent, Event, Force, KeyboardInput, Touch, TouchPhase, WindowEvent}, + event::{DeviceEvent, Event, Force, RawKeyEvent, Touch, TouchPhase, WindowEvent}, event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW}, + keyboard::{KeyCode, ModifiersState}, monitor::MonitorHandle as RootMonitorHandle, + 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}, + keyboard::is_msg_keyboard_related, + keyboard_layout::LAYOUT_CACHE, + minimal_ime::is_msg_ime_related, monitor::{self, MonitorHandle}, raw_input, util, window::InitData, @@ -108,6 +113,18 @@ impl ThreadMsgTargetData { } } +/// The result of a subclass procedure (the message handling callback) +#[derive(Clone, Copy)] +pub(crate) enum ProcResult { + DefWindowProc, + Value(isize), +} +impl Default for ProcResult { + fn default() -> Self { + ProcResult::DefWindowProc + } +} + pub struct EventLoop { thread_msg_sender: Sender, window_target: RootELW, @@ -768,10 +785,15 @@ unsafe fn process_control_flow(runner: &EventLoopRunner) { } /// Emit a `ModifiersChanged` event whenever modifiers have changed. -fn update_modifiers(window: HWND, userdata: &WindowData) { +/// Returns the current modifier state +fn update_modifiers(window: HWND, userdata: &WindowData) -> ModifiersState { use crate::event::WindowEvent::ModifiersChanged; - let modifiers = event::get_key_mods(); + let modifiers = { + let mut layouts = LAYOUT_CACHE.lock().unwrap(); + layouts.get_agnostic_mods() + }; + let mut window_state = userdata.window_state.lock(); if window_state.modifiers_state != modifiers { window_state.modifiers_state = modifiers; @@ -786,6 +808,7 @@ fn update_modifiers(window: HWND, userdata: &WindowData) { }); } } + modifiers } #[cfg(target_arch = "x86_64")] @@ -873,6 +896,75 @@ unsafe fn public_window_callback_inner( winuser::RDW_INTERNALPAINT, ); + let mut result = ProcResult::DefWindowProc; + + // Send new modifiers before sending key events. + let mods_changed_callback = || match msg { + winuser::WM_KEYDOWN | winuser::WM_SYSKEYDOWN | winuser::WM_KEYUP | winuser::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 is_keyboard_related = is_msg_keyboard_related(msg); + if !is_keyboard_related { + // We return early to avoid a deadlock from locking the window state + // when not appropriate. + return; + } + let events = { + let mut window_state = userdata.window_state.lock(); + window_state + .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)); + + let ime_callback = || { + use crate::event::WindowEvent::ReceivedImeText; + let is_ime_related = is_msg_ime_related(msg); + if !is_ime_related { + return; + } + let text = { + let mut window_state = userdata.window_state.lock(); + window_state + .ime_handler + .process_message(window, msg, wparam, lparam, &mut result) + }; + if let Some(str) = text { + userdata.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window)), + event: ReceivedImeText(str), + }); + } + }; + userdata + .event_loop_runner + .catch_unwind(ime_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. @@ -882,7 +974,7 @@ unsafe fn public_window_callback_inner( .window_state .lock() .set_window_flags_in_place(|f| f.insert(WindowFlags::MARKER_IN_SIZE_MOVE)); - 0 + result = ProcResult::Value(0); } winuser::WM_EXITSIZEMOVE => { @@ -890,14 +982,14 @@ unsafe fn public_window_callback_inner( .window_state .lock() .set_window_flags_in_place(|f| f.remove(WindowFlags::MARKER_IN_SIZE_MOVE)); - 0 + result = ProcResult::Value(0); } winuser::WM_NCLBUTTONDOWN => { if wparam == winuser::HTCAPTION as _ { winuser::PostMessageW(window, winuser::WM_MOUSEMOVE, 0, lparam); } - winuser::DefWindowProcW(window, msg, wparam, lparam) + result = ProcResult::DefWindowProc; } winuser::WM_CLOSE => { @@ -906,7 +998,7 @@ unsafe fn public_window_callback_inner( window_id: RootWindowId(WindowId(window)), event: CloseRequested, }); - 0 + result = ProcResult::Value(0); } winuser::WM_DESTROY => { @@ -917,13 +1009,13 @@ unsafe fn public_window_callback_inner( event: Destroyed, }); userdata.event_loop_runner.remove_window(window); - 0 + result = ProcResult::Value(0); } winuser::WM_NCDESTROY => { winuser::SetWindowLongPtrW(window, winuser::GWL_USERDATA, 0); userdata.userdata_removed.set(true); - 0 + result = ProcResult::Value(0); } winuser::WM_PAINT => { @@ -945,8 +1037,7 @@ unsafe fn public_window_callback_inner( process_control_flow(&userdata.event_loop_runner); } } - - winuser::DefWindowProcW(window, msg, wparam, lparam) + result = ProcResult::DefWindowProc; } winuser::WM_WINDOWPOSCHANGING => { @@ -994,7 +1085,7 @@ unsafe fn public_window_callback_inner( } } - 0 + result = ProcResult::Value(0); } // WM_MOVE supplies client area positions, so we send Moved here instead. @@ -1012,7 +1103,7 @@ unsafe fn public_window_callback_inner( } // This is necessary for us to still get sent WM_SIZE. - winuser::DefWindowProcW(window, msg, wparam, lparam) + result = ProcResult::DefWindowProc; } winuser::WM_SIZE => { @@ -1037,42 +1128,8 @@ unsafe fn public_window_callback_inner( w.set_window_flags_in_place(|f| f.set(WindowFlags::MAXIMIZED, maximized)); } } - userdata.send_event(event); - 0 - } - - winuser::WM_CHAR | winuser::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), - }); - } - } - 0 + result = ProcResult::Value(0); } // this is necessary for us to maintain minimize/restore state @@ -1090,11 +1147,12 @@ unsafe fn public_window_callback_inner( if wparam == winuser::SC_SCREENSAVE { let window_state = userdata.window_state.lock(); if window_state.fullscreen.is_some() { - return 0; + result = ProcResult::Value(0); + return; } } - winuser::DefWindowProcW(window, msg, wparam, lparam) + result = ProcResult::DefWindowProc; } winuser::WM_MOUSEMOVE => { @@ -1139,19 +1197,19 @@ unsafe fn public_window_callback_inner( w.mouse.last_position = Some(position); } if cursor_moved { - update_modifiers(window, userdata); + let modifiers = update_modifiers(window, userdata); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: CursorMoved { device_id: DEVICE_ID, position, - modifiers: event::get_key_mods(), + modifiers, }, }); } - 0 + result = ProcResult::Value(0); } winuser::WM_MOUSELEAVE => { @@ -1170,7 +1228,7 @@ unsafe fn public_window_callback_inner( }, }); - 0 + result = ProcResult::Value(0); } winuser::WM_MOUSEWHEEL => { @@ -1180,7 +1238,7 @@ unsafe fn public_window_callback_inner( let value = value as i32; let value = value as f32 / winuser::WHEEL_DELTA as f32; - update_modifiers(window, userdata); + let modifiers = update_modifiers(window, userdata); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), @@ -1188,11 +1246,11 @@ unsafe fn public_window_callback_inner( device_id: DEVICE_ID, delta: LineDelta(0.0, value), phase: TouchPhase::Moved, - modifiers: event::get_key_mods(), + modifiers, }, }); - 0 + result = ProcResult::Value(0); } winuser::WM_MOUSEHWHEEL => { @@ -1202,7 +1260,7 @@ unsafe fn public_window_callback_inner( let value = value as i32; let value = value as f32 / winuser::WHEEL_DELTA as f32; - update_modifiers(window, userdata); + let modifiers = update_modifiers(window, userdata); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), @@ -1210,69 +1268,17 @@ unsafe fn public_window_callback_inner( device_id: DEVICE_ID, delta: LineDelta(value, 0.0), phase: TouchPhase::Moved, - modifiers: event::get_key_mods(), + modifiers, }, }); - 0 + result = ProcResult::Value(0); } winuser::WM_KEYDOWN | winuser::WM_SYSKEYDOWN => { - use crate::event::{ElementState::Pressed, VirtualKeyCode}; if msg == winuser::WM_SYSKEYDOWN && wparam as i32 == winuser::VK_F4 { - winuser::DefWindowProcW(window, msg, wparam, lparam) - } else { - 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}'), - }); - } - } - 0 - } - } - - winuser::WM_KEYUP | winuser::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, - }, - }); + result = ProcResult::DefWindowProc; } - 0 } winuser::WM_LBUTTONDOWN => { @@ -1280,7 +1286,7 @@ unsafe fn public_window_callback_inner( capture_mouse(window, &mut *userdata.window_state.lock()); - update_modifiers(window, userdata); + let modifiers = update_modifiers(window, userdata); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), @@ -1288,10 +1294,10 @@ unsafe fn public_window_callback_inner( device_id: DEVICE_ID, state: Pressed, button: Left, - modifiers: event::get_key_mods(), + modifiers, }, }); - 0 + result = ProcResult::Value(0); } winuser::WM_LBUTTONUP => { @@ -1301,7 +1307,7 @@ unsafe fn public_window_callback_inner( release_mouse(userdata.window_state.lock()); - update_modifiers(window, userdata); + let modifiers = update_modifiers(window, userdata); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), @@ -1309,10 +1315,10 @@ unsafe fn public_window_callback_inner( device_id: DEVICE_ID, state: Released, button: Left, - modifiers: event::get_key_mods(), + modifiers, }, }); - 0 + result = ProcResult::Value(0); } winuser::WM_RBUTTONDOWN => { @@ -1322,7 +1328,7 @@ unsafe fn public_window_callback_inner( capture_mouse(window, &mut *userdata.window_state.lock()); - update_modifiers(window, userdata); + let modifiers = update_modifiers(window, userdata); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), @@ -1330,10 +1336,10 @@ unsafe fn public_window_callback_inner( device_id: DEVICE_ID, state: Pressed, button: Right, - modifiers: event::get_key_mods(), + modifiers, }, }); - 0 + result = ProcResult::Value(0); } winuser::WM_RBUTTONUP => { @@ -1343,7 +1349,7 @@ unsafe fn public_window_callback_inner( release_mouse(userdata.window_state.lock()); - update_modifiers(window, userdata); + let modifiers = update_modifiers(window, userdata); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), @@ -1351,10 +1357,10 @@ unsafe fn public_window_callback_inner( device_id: DEVICE_ID, state: Released, button: Right, - modifiers: event::get_key_mods(), + modifiers, }, }); - 0 + result = ProcResult::Value(0); } winuser::WM_MBUTTONDOWN => { @@ -1364,7 +1370,7 @@ unsafe fn public_window_callback_inner( capture_mouse(window, &mut *userdata.window_state.lock()); - update_modifiers(window, userdata); + let modifiers = update_modifiers(window, userdata); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), @@ -1372,10 +1378,10 @@ unsafe fn public_window_callback_inner( device_id: DEVICE_ID, state: Pressed, button: Middle, - modifiers: event::get_key_mods(), + modifiers, }, }); - 0 + result = ProcResult::Value(0); } winuser::WM_MBUTTONUP => { @@ -1385,7 +1391,7 @@ unsafe fn public_window_callback_inner( release_mouse(userdata.window_state.lock()); - update_modifiers(window, userdata); + let modifiers = update_modifiers(window, userdata); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), @@ -1393,10 +1399,10 @@ unsafe fn public_window_callback_inner( device_id: DEVICE_ID, state: Released, button: Middle, - modifiers: event::get_key_mods(), + modifiers, }, }); - 0 + result = ProcResult::Value(0); } winuser::WM_XBUTTONDOWN => { @@ -1407,7 +1413,7 @@ unsafe fn public_window_callback_inner( capture_mouse(window, &mut *userdata.window_state.lock()); - update_modifiers(window, userdata); + let modifiers = update_modifiers(window, userdata); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), @@ -1415,10 +1421,10 @@ unsafe fn public_window_callback_inner( device_id: DEVICE_ID, state: Pressed, button: Other(xbutton), - modifiers: event::get_key_mods(), + modifiers, }, }); - 0 + result = ProcResult::Value(0); } winuser::WM_XBUTTONUP => { @@ -1429,7 +1435,7 @@ unsafe fn public_window_callback_inner( release_mouse(userdata.window_state.lock()); - update_modifiers(window, userdata); + let modifiers = update_modifiers(window, userdata); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), @@ -1437,10 +1443,10 @@ unsafe fn public_window_callback_inner( device_id: DEVICE_ID, state: Released, button: Other(xbutton), - modifiers: event::get_key_mods(), + modifiers, }, }); - 0 + result = ProcResult::Value(0); } winuser::WM_CAPTURECHANGED => { @@ -1451,7 +1457,7 @@ unsafe fn public_window_callback_inner( if lparam != window as isize { userdata.window_state.lock().mouse.capture_count = 0; } - 0 + result = ProcResult::Value(0); } winuser::WM_TOUCH => { @@ -1500,7 +1506,7 @@ unsafe fn public_window_callback_inner( } } winuser::CloseTouchInputHandle(htouch); - 0 + result = ProcResult::Value(0); } winuser::WM_POINTERDOWN | winuser::WM_POINTERUPDATE | winuser::WM_POINTERUP => { @@ -1523,7 +1529,8 @@ unsafe fn public_window_callback_inner( std::ptr::null_mut(), ) == 0 { - return 0; + result = ProcResult::Value(0); + return; } let pointer_info_count = (entries_count * pointers_count) as usize; @@ -1536,7 +1543,8 @@ unsafe fn public_window_callback_inner( pointer_infos.as_mut_ptr(), ) == 0 { - return 0; + result = ProcResult::Value(0); + return; } // https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-getpointerframeinfohistory @@ -1640,68 +1648,23 @@ unsafe fn public_window_callback_inner( SkipPointerFrameMessages(pointer_id); } - 0 + result = ProcResult::Value(0); } winuser::WM_SETFOCUS => { - use crate::event::{ElementState::Released, WindowEvent::Focused}; - for windows_keycode in event::get_pressed_keys() { - let scancode = - winuser::MapVirtualKeyA(windows_keycode as _, winuser::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)), event: Focused(true), }); - 0 + result = ProcResult::Value(0); } winuser::WM_KILLFOCUS => { - use crate::event::{ - ElementState::Released, - ModifiersState, - WindowEvent::{Focused, ModifiersChanged}, - }; - for windows_keycode in event::get_pressed_keys() { - let scancode = - winuser::MapVirtualKeyA(windows_keycode as _, winuser::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 { @@ -1713,7 +1676,7 @@ unsafe fn public_window_callback_inner( window_id: RootWindowId(WindowId(window)), event: Focused(false), }); - 0 + result = ProcResult::Value(0); } winuser::WM_SETCURSOR => { @@ -1734,17 +1697,12 @@ unsafe fn public_window_callback_inner( Some(cursor) => { let cursor = winuser::LoadCursorW(ptr::null_mut(), cursor.to_windows_cursor()); winuser::SetCursor(cursor); - 0 + result = ProcResult::Value(0); } - None => winuser::DefWindowProcW(window, msg, wparam, lparam), + None => result = ProcResult::DefWindowProc, } } - winuser::WM_DROPFILES => { - // See `FileDropHandler` for implementation. - 0 - } - winuser::WM_GETMINMAXINFO => { let mmi = lparam as *mut winuser::MINMAXINFO; @@ -1769,7 +1727,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 @@ -1791,7 +1749,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; } window_state.fullscreen.is_none() @@ -1986,7 +1945,7 @@ unsafe fn public_window_callback_inner( winuser::SWP_NOZORDER | winuser::SWP_NOACTIVATE, ); - 0 + result = ProcResult::Value(0); } winuser::WM_SETTINGCHANGE => { @@ -2007,22 +1966,21 @@ unsafe fn public_window_callback_inner( }); } } - - winuser::DefWindowProcW(window, msg, wparam, lparam) + result = ProcResult::DefWindowProc; } _ => { if msg == *DESTROY_MSG_ID { winuser::DestroyWindow(window); - 0 + result = ProcResult::Value(0); } else if msg == *SET_RETAIN_STATE_ON_SIZE_MSG_ID { 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 { - winuser::DefWindowProcW(window, msg, wparam, lparam) + result = ProcResult::DefWindowProc; } } }; @@ -2030,7 +1988,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 => winuser::DefWindowProcW(window, msg, wparam, lparam), + ProcResult::Value(val) => val, + } } unsafe extern "system" fn thread_event_target_callback( @@ -2114,102 +2077,8 @@ unsafe extern "system" fn thread_event_target_callback( } winuser::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 as _) { - let device_id = wrap_device_id(data.header.hDevice as _); - - if data.header.dwType == winuser::RIM_TYPEMOUSE { - let mouse = data.data.mouse(); - - if util::has_flag(mouse.usFlags, winuser::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) }, - }); - } - } - - if util::has_flag(mouse.usButtonFlags, winuser::RI_MOUSE_WHEEL) { - let delta = - mouse.usButtonData as SHORT as f32 / winuser::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.usButtonFlags); - // 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 == winuser::RIM_TYPEKEYBOARD { - let keyboard = data.data.keyboard(); - - let pressed = keyboard.Message == winuser::WM_KEYDOWN - || keyboard.Message == winuser::WM_SYSKEYDOWN; - let released = keyboard.Message == winuser::WM_KEYUP - || keyboard.Message == winuser::WM_SYSKEYUP; - - if pressed || released { - let state = if pressed { Pressed } else { Released }; - - let scancode = keyboard.MakeCode as _; - let extended = util::has_flag(keyboard.Flags, winuser::RI_KEY_E0 as _) - | util::has_flag(keyboard.Flags, winuser::RI_KEY_E1 as _); - - if let Some((vkey, scancode)) = - handle_extended_keys(keyboard.VKey as _, scancode, 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(), - }), - }); - } - } - } + handle_raw_input(&userdata, data); } winuser::DefWindowProcW(window, msg, wparam, lparam) @@ -2279,3 +2148,184 @@ 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 == winuser::RIM_TYPEMOUSE { + let mouse = data.data.mouse(); + + if util::has_flag(mouse.usFlags, winuser::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) }, + }); + } + } + + if util::has_flag(mouse.usButtonFlags, winuser::RI_MOUSE_WHEEL) { + // We must cast to SHORT first, becaues `usButtonData` must be interpreted as signed. + let delta = mouse.usButtonData as SHORT as f32 / winuser::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.usButtonFlags); + // 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 == winuser::RIM_TYPEKEYBOARD { + let keyboard = data.data.keyboard(); + + let pressed = + keyboard.Message == winuser::WM_KEYDOWN || keyboard.Message == winuser::WM_SYSKEYDOWN; + let released = + keyboard.Message == winuser::WM_KEYUP || keyboard.Message == winuser::WM_SYSKEYUP; + + if !pressed && !released { + return; + } + + let state = if pressed { Pressed } else { Released }; + let extension = { + if util::has_flag(keyboard.Flags, winuser::RI_KEY_E0 as _) { + 0xE000 + } else if util::has_flag(keyboard.Flags, winuser::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. + scancode = + winuser::MapVirtualKeyW(keyboard.VKey as u32, winuser::MAPVK_VK_TO_VSC_EX) as u16; + } else { + scancode = 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 as c_int == winuser::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 + code = KeyCode::NumLock; + } else { + code = KeyCode::from_scancode(scancode as u32); + } + if keyboard.VKey as c_int == winuser::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..00235ed018 --- /dev/null +++ b/src/platform_impl/windows/keyboard.rs @@ -0,0 +1,802 @@ +use std::{ + char, collections::HashSet, ffi::OsString, mem::MaybeUninit, os::raw::c_int, + os::windows::ffi::OsStringExt, sync::MutexGuard, +}; + +use winapi::{ + shared::{ + minwindef::{HKL, LPARAM, UINT, WPARAM}, + windef::HWND, + }, + um::winuser, +}; + +use unicode_segmentation::UnicodeSegmentation; + +use crate::{ + event::{ElementState, KeyEvent}, + keyboard::{Key, KeyCode, KeyLocation, NativeKeyCode}, + platform::scancode::KeyCodeExtScancode, + platform_impl::platform::{ + event_loop::ProcResult, + keyboard_layout::{get_or_insert_str, Layout, LayoutCache, WindowsModifiers, LAYOUT_CACHE}, + KeyEventExtra, + }, +}; + +pub fn is_msg_keyboard_related(msg: u32) -> bool { + use winuser::{WM_KEYFIRST, WM_KEYLAST, WM_KILLFOCUS, WM_SETFOCUS}; + let is_keyboard_msg = WM_KEYFIRST <= msg && msg <= WM_KEYLAST; + + is_keyboard_msg || msg == WM_SETFOCUS || msg == WM_KILLFOCUS +} + +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: Option, +} +impl Default for KeyEventBuilder { + fn default() -> Self { + KeyEventBuilder { event_info: None } + } +} +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( + &mut self, + hwnd: HWND, + msg_kind: u32, + wparam: WPARAM, + lparam: LPARAM, + result: &mut ProcResult, + ) -> Vec { + match msg_kind { + winuser::WM_SETFOCUS => { + // synthesize keydown events + let kbd_state = get_async_kbd_state(); + let key_events = self.synthesize_kbd_state(ElementState::Pressed, &kbd_state); + if !key_events.is_empty() { + return key_events; + } + } + winuser::WM_KILLFOCUS => { + // sythesize keyup events + let kbd_state = get_kbd_state(); + let key_events = self.synthesize_kbd_state(ElementState::Released, &kbd_state); + if !key_events.is_empty() { + return key_events; + } + } + winuser::WM_KEYDOWN | winuser::WM_SYSKEYDOWN => { + if msg_kind == winuser::WM_SYSKEYDOWN && wparam as i32 == winuser::VK_F4 { + // Don't dispatch Alt+F4 to the application. + // This is handled in `event_loop.rs` + return vec![]; + } + *result = ProcResult::Value(0); + + let mut layouts = LAYOUT_CACHE.lock().unwrap(); + let event_info = PartialKeyEventInfo::from_message( + wparam, + lparam, + ElementState::Pressed, + &mut layouts, + ); + + let mut next_msg = MaybeUninit::uninit(); + let peek_retval = unsafe { + winuser::PeekMessageW( + next_msg.as_mut_ptr(), + hwnd, + winuser::WM_KEYFIRST, + winuser::WM_KEYLAST, + winuser::PM_NOREMOVE, + ) + }; + let has_next_key_message = peek_retval != 0; + self.event_info = None; + let mut finished_event_info = Some(event_info); + if has_next_key_message { + let next_msg = unsafe { next_msg.assume_init() }; + let next_msg_kind = next_msg.message; + let next_belongs_to_this = !matches!( + next_msg_kind, + winuser::WM_KEYDOWN + | winuser::WM_SYSKEYDOWN + | winuser::WM_KEYUP + | winuser::WM_SYSKEYUP + ); + if next_belongs_to_this { + self.event_info = finished_event_info.take(); + } else { + let (_, layout) = layouts.get_current_layout(); + let is_fake = { + let curr_event = finished_event_info.as_ref().unwrap(); + is_current_fake(curr_event, next_msg, layout) + }; + if is_fake { + finished_event_info = None; + } + } + } + if let Some(event_info) = finished_event_info { + let ev = event_info.finalize(&mut layouts.strings); + return vec![MessageAsKeyEvent { + event: ev, + is_synthetic: false, + }]; + } + } + winuser::WM_DEADCHAR | winuser::WM_SYSDEADCHAR => { + *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.take().unwrap(); + let mut layouts = LAYOUT_CACHE.lock().unwrap(); + let ev = event_info.finalize(&mut layouts.strings); + return vec![MessageAsKeyEvent { + event: ev, + is_synthetic: false, + }]; + } + winuser::WM_CHAR | winuser::WM_SYSCHAR => { + if self.event_info.is_none() { + trace!("Received a CHAR message but no `event_info` was available. The message is probably IME, returning."); + return vec![]; + } + *result = ProcResult::Value(0); + let is_high_surrogate = 0xD800 <= wparam && wparam <= 0xDBFF; + let is_low_surrogate = 0xDC00 <= wparam && wparam <= 0xDFFF; + + let is_utf16 = is_high_surrogate || is_low_surrogate; + + let more_char_coming; + unsafe { + let mut next_msg = MaybeUninit::uninit(); + let has_message = winuser::PeekMessageW( + next_msg.as_mut_ptr(), + hwnd, + winuser::WM_KEYFIRST, + winuser::WM_KEYLAST, + winuser::PM_NOREMOVE, + ); + let has_message = has_message != 0; + if !has_message { + more_char_coming = false; + } else { + let next_msg = next_msg.assume_init().message; + if next_msg == winuser::WM_CHAR || next_msg == winuser::WM_SYSCHAR { + more_char_coming = true; + } else { + more_char_coming = false; + } + } + } + + if is_utf16 { + if let Some(ev_info) = self.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 self.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 vec![]; + } + }; + 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); + } + } + if !more_char_coming { + let mut event_info = match self.event_info.take() { + Some(ev_info) => ev_info, + None => { + warn!("The event_info was None when it was expected to be some"); + return vec![]; + } + }; + 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); + ctrl_on = !alt_on && mod_state.contains(WindowsModifiers::CONTROL) + } else { + ctrl_on = 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[winuser::VK_NUMLOCK as usize] & 1 != 0; + let vkey = event_info.vkey; + let scancode = event_info.scancode; + let keycode = event_info.code; + let key = layout.get_key(mod_no_ctrl, num_lock_on, vkey, scancode, keycode); + event_info.text = PartialText::Text(key.to_text()); + } + let ev = event_info.finalize(&mut layouts.strings); + return vec![MessageAsKeyEvent { + event: ev, + is_synthetic: false, + }]; + } + } + winuser::WM_KEYUP | winuser::WM_SYSKEYUP => { + *result = ProcResult::Value(0); + + let mut layouts = LAYOUT_CACHE.lock().unwrap(); + let event_info = PartialKeyEventInfo::from_message( + wparam, + lparam, + ElementState::Released, + &mut layouts, + ); + let mut next_msg = MaybeUninit::uninit(); + let peek_retval = unsafe { + winuser::PeekMessageW( + next_msg.as_mut_ptr(), + hwnd, + winuser::WM_KEYFIRST, + winuser::WM_KEYLAST, + winuser::PM_NOREMOVE, + ) + }; + let has_next_key_message = peek_retval != 0; + let mut valid_event_info = Some(event_info); + if has_next_key_message { + let next_msg = unsafe { next_msg.assume_init() }; + let (_, layout) = layouts.get_current_layout(); + let is_fake = { + let event_info = valid_event_info.as_ref().unwrap(); + is_current_fake(&event_info, next_msg, layout) + }; + if is_fake { + valid_event_info = None; + } + } + if let Some(event_info) = valid_event_info { + let event = event_info.finalize(&mut layouts.strings); + return vec![MessageAsKeyEvent { + event, + is_synthetic: false, + }]; + } + } + _ => (), + } + + Vec::new() + } + + fn synthesize_kbd_state( + &mut self, + 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[winuser::VK_CAPITAL as usize] & 1 != 0; + let num_lock_on = kbd_state[winuser::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!(winuser::VK_CAPITAL) { + let event = self.create_synthetic( + winuser::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 { + winuser::VK_CONTROL + | winuser::VK_LCONTROL + | winuser::VK_RCONTROL + | winuser::VK_SHIFT + | winuser::VK_LSHIFT + | winuser::VK_RSHIFT + | winuser::VK_MENU + | winuser::VK_LMENU + | winuser::VK_RMENU + | winuser::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: [i32; 6] = [ + winuser::VK_LCONTROL, + winuser::VK_LSHIFT, + winuser::VK_LMENU, + winuser::VK_RCONTROL, + winuser::VK_RSHIFT, + winuser::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( + &self, + vk: i32, + key_state: ElementState, + caps_lock_on: bool, + num_lock_on: bool, + locale_id: HKL, + layouts: &mut MutexGuard<'_, LayoutCache>, + ) -> Option { + let scancode = unsafe { + winuser::MapVirtualKeyExW(vk as UINT, winuser::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, scancode, code); + let key_without_modifiers = + layout.get_key(WindowsModifiers::empty(), false, vk, scancode, code); + let text; + if key_state == ElementState::Pressed { + text = logical_key.to_text(); + } else { + text = None; + } + let event_info = PartialKeyEventInfo { + vkey: vk, + logical_key: PartialLogicalKey::This(logical_key), + key_without_modifiers, + key_state, + scancode, + is_repeat: false, + code, + location: get_location(scancode, locale_id), + utf16parts: Vec::with_capacity(8), + text: PartialText::Text(text), + }; + + let mut event = event_info.finalize(&mut layouts.strings); + event.logical_key = logical_key; + event.platform_specific.text_with_all_modifers = text; + Some(MessageAsKeyEvent { + event, + is_synthetic: true, + }) + } +} + +enum PartialText { + // Unicode + System(Vec), + Text(Option<&'static str>), +} + +enum PartialLogicalKey { + /// Use the text provided by the WM_CHAR messages and report that as a `Character` variant. If + /// the text consists of multiple grapheme clusters (user-precieved characters) that means that + /// dead key could not be combined with the second input, and in that case we should fall back + /// to using what would have without a dead-key input. + TextOr(Key<'static>), + + /// Use the value directly provided by this variant + This(Key<'static>), +} + +struct PartialKeyEventInfo { + vkey: c_int, + scancode: ExScancode, + key_state: ElementState, + is_repeat: bool, + code: KeyCode, + location: KeyLocation, + logical_key: PartialLogicalKey, + + key_without_modifiers: Key<'static>, + + /// The UTF-16 code units of the text that was produced by the keypress event. + /// This take all modifiers into account. Including CTRL + utf16parts: Vec, + + text: PartialText, +} + +impl PartialKeyEventInfo { + fn from_message( + wparam: WPARAM, + lparam: LPARAM, + state: ElementState, + layouts: &mut MutexGuard<'_, LayoutCache>, + ) -> Self { + const NO_MODS: WindowsModifiers = WindowsModifiers::empty(); + + let (_, layout) = layouts.get_current_layout(); + let lparam_struct = destructure_key_lparam(lparam); + let scancode; + let vkey = wparam as c_int; + 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. + scancode = unsafe { + winuser::MapVirtualKeyExW( + vkey as u32, + winuser::MAPVK_VK_TO_VSC_EX, + layout.hkl as HKL, + ) as u16 + }; + } else { + scancode = 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[winuser::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, scancode, 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 { + 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, scancode, code) { + // We convert dead keys into their character. + // The reason for this is that `key_without_modifiers` is designed for key-bindings, + // but the US International layout treats `'` (apostrophe) as a dead key and the + // reguar US layout treats it a character. In order for a single binding + // configuration to work with both layouts, we forward each dead key as a character. + Key::Dead(k) => { + if let Some(ch) = k { + // I'm avoiding the heap allocation. I don't want to talk about it :( + let mut utf8 = [0; 4]; + let s = ch.encode_utf8(&mut utf8); + let static_str = get_or_insert_str(&mut layouts.strings, s); + Key::Character(static_str) + } else { + Key::Unidentified(NativeKeyCode::Unidentified) + } + } + key => key, + } + }; + + PartialKeyEventInfo { + vkey, + scancode, + key_state: state, + logical_key, + key_without_modifiers, + is_repeat: lparam_struct.is_repeat, + code, + location, + utf16parts: Vec::with_capacity(8), + text: PartialText::System(Vec::new()), + } + } + + fn finalize(self, strings: &mut HashSet<&'static str>) -> KeyEvent { + let mut char_with_all_modifiers = None; + if !self.utf16parts.is_empty() { + let os_string = OsString::from_wide(&self.utf16parts); + if let Ok(string) = os_string.into_string() { + let static_str = get_or_insert_str(strings, string); + char_with_all_modifiers = Some(static_str); + } + } + + // The text without Ctrl + let mut text = None; + match self.text { + PartialText::System(wide) => { + if !wide.is_empty() { + let os_string = OsString::from_wide(&wide); + if let Ok(string) = os_string.into_string() { + let static_str = get_or_insert_str(strings, string); + text = Some(static_str); + } + } + } + PartialText::Text(s) => { + text = s; + } + } + + let logical_key = match self.logical_key { + PartialLogicalKey::TextOr(fallback) => match text { + Some(s) => { + if s.grapheme_indices(true).count() > 1 { + fallback + } else { + Key::Character(s) + } + } + None => Key::Unidentified(NativeKeyCode::Windows(self.scancode)), + }, + 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(); + winuser::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 +fn get_async_kbd_state() -> [u8; 256] { + unsafe { + let mut kbd_state: [u8; 256] = MaybeUninit::uninit().assume_init(); + for (vk, state) in kbd_state.iter_mut().enumerate() { + let vk = vk as c_int; + let async_state = winuser::GetAsyncKeyState(vk as c_int); + let is_down = (async_state & (1 << 15)) != 0; + *state = if is_down { 0x80 } else { 0 }; + + if matches!( + vk, + winuser::VK_CAPITAL | winuser::VK_NUMLOCK | winuser::VK_SCROLL + ) { + // Toggle states aren't reported by `GetAsyncKeyState` + let toggle_state = winuser::GetKeyState(vk); + let is_active = (toggle_state & 1) != 0; + *state |= if is_active { 1 } else { 0 }; + } + } + 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: winuser::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 +} + +fn get_location(scancode: ExScancode, hkl: HKL) -> KeyLocation { + use winuser::*; + const VK_ABNT_C2: c_int = 0xc2; + + let extension = 0xE000; + let extended = (scancode & extension) == extension; + let vkey = unsafe { + winuser::MapVirtualKeyExW(scancode as u32, winuser::MAPVK_VSC_TO_VK_EX, hkl) as i32 + }; + + // 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 | VK_ABNT_C2 => KeyLocation::Numpad, + _ => KeyLocation::Standard, + } +} diff --git a/src/platform_impl/windows/keyboard_layout.rs b/src/platform_impl/windows/keyboard_layout.rs new file mode 100644 index 0000000000..3f1100b9b4 --- /dev/null +++ b/src/platform_impl/windows/keyboard_layout.rs @@ -0,0 +1,993 @@ +use std::{ + collections::{hash_map::Entry, HashMap, HashSet}, + ffi::OsString, + os::windows::ffi::OsStringExt, + sync::Mutex, +}; + +use lazy_static::lazy_static; + +use winapi::{ + ctypes::c_int, + shared::minwindef::{HKL, LOWORD}, + um::{ + winnt::{LANG_JAPANESE, LANG_KOREAN, PRIMARYLANGID}, + winuser, + }, +}; + +use crate::{ + keyboard::{Key, KeyCode, ModifiersState, NativeKeyCode}, + platform::scancode::KeyCodeExtScancode, + platform_impl::platform::keyboard::ExScancode, +}; + +lazy_static! { + pub(crate) static ref LAYOUT_CACHE: Mutex = Mutex::new(LayoutCache::default()); +} + +fn key_pressed(vkey: c_int) -> bool { + unsafe { (winuser::GetKeyState(vkey) & (1 << 15)) == (1 << 15) } +} + +const NUMPAD_VKEYS: [c_int; 16] = [ + winuser::VK_NUMPAD0, + winuser::VK_NUMPAD1, + winuser::VK_NUMPAD2, + winuser::VK_NUMPAD3, + winuser::VK_NUMPAD4, + winuser::VK_NUMPAD5, + winuser::VK_NUMPAD6, + winuser::VK_NUMPAD7, + winuser::VK_NUMPAD8, + winuser::VK_NUMPAD9, + winuser::VK_MULTIPLY, + winuser::VK_ADD, + winuser::VK_SEPARATOR, + winuser::VK_SUBTRACT, + winuser::VK_DECIMAL, + winuser::VK_DIVIDE, +]; + +lazy_static! { + static ref NUMPAD_KEYCODES: HashSet = { + 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[winuser::VK_SHIFT as usize] & 0x80 != 0; + let lshift = key_state[winuser::VK_LSHIFT as usize] & 0x80 != 0; + let rshift = key_state[winuser::VK_RSHIFT as usize] & 0x80 != 0; + + let control = key_state[winuser::VK_CONTROL as usize] & 0x80 != 0; + let lcontrol = key_state[winuser::VK_LCONTROL as usize] & 0x80 != 0; + let rcontrol = key_state[winuser::VK_RCONTROL as usize] & 0x80 != 0; + + let alt = key_state[winuser::VK_MENU as usize] & 0x80 != 0; + let lalt = key_state[winuser::VK_LMENU as usize] & 0x80 != 0; + let ralt = key_state[winuser::VK_RMENU as usize] & 0x80 != 0; + + let caps = key_state[winuser::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[winuser::VK_SHIFT as usize] |= 0x80; + } else { + key_state[winuser::VK_SHIFT as usize] &= !0x80; + key_state[winuser::VK_LSHIFT as usize] &= !0x80; + key_state[winuser::VK_RSHIFT as usize] &= !0x80; + } + if self.intersects(Self::CONTROL) { + key_state[winuser::VK_CONTROL as usize] |= 0x80; + } else { + key_state[winuser::VK_CONTROL as usize] &= !0x80; + key_state[winuser::VK_LCONTROL as usize] &= !0x80; + key_state[winuser::VK_RCONTROL as usize] &= !0x80; + } + if self.intersects(Self::ALT) { + key_state[winuser::VK_MENU as usize] |= 0x80; + } else { + key_state[winuser::VK_MENU as usize] &= !0x80; + key_state[winuser::VK_LMENU as usize] &= !0x80; + key_state[winuser::VK_RMENU as usize] &= !0x80; + } + if self.intersects(Self::CAPS_LOCK) { + key_state[winuser::VK_CAPITAL as usize] |= 0x01; + } else { + key_state[winuser::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: c_int, + scancode: ExScancode, + keycode: KeyCode, + ) -> Key<'static> { + let native_code = NativeKeyCode::Windows(scancode); + + let unknown_alt = vkey == winuser::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, 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; + } + } else { + if let Some(key) = self.numlock_off_keys.get(&vkey) { + return *key; + } + } + if let Some(keys) = self.keys.get(&mods) { + if let Some(key) = keys.get(&keycode) { + return *key; + } + } + Key::Unidentified(native_code) + } +} + +#[derive(Default)] +pub(crate) struct LayoutCache { + /// Maps locale identifiers (HKL) to layouts + pub layouts: HashMap, + pub strings: HashSet<&'static str>, +} + +impl LayoutCache { + /// Checks whether the current layout is already known and + /// prepares the layout if it isn't known. + /// The current layout is then returned. + pub fn get_current_layout<'a>(&'a mut self) -> (u64, &'a Layout) { + let locale_id = unsafe { winuser::GetKeyboardLayout(0) } as u64; + match self.layouts.entry(locale_id) { + Entry::Occupied(entry) => (locale_id, entry.into_mut()), + Entry::Vacant(entry) => { + let layout = Self::prepare_layout(&mut self.strings, locale_id); + (locale_id, entry.insert(layout)) + } + } + } + + pub fn get_agnostic_mods(&mut self) -> ModifiersState { + let (_, layout) = self.get_current_layout(); + let filter_out_altgr = layout.has_alt_graph && key_pressed(winuser::VK_RMENU); + let mut mods = ModifiersState::empty(); + mods.set(ModifiersState::SHIFT, key_pressed(winuser::VK_SHIFT)); + mods.set( + ModifiersState::CONTROL, + key_pressed(winuser::VK_CONTROL) && !filter_out_altgr, + ); + mods.set( + ModifiersState::ALT, + key_pressed(winuser::VK_MENU) && !filter_out_altgr, + ); + mods.set( + ModifiersState::SUPER, + key_pressed(winuser::VK_LWIN) || key_pressed(winuser::VK_RWIN), + ); + mods + } + + fn prepare_layout(strings: &mut HashSet<&'static str>, locale_id: u64) -> Layout { + let mut layout = Layout { + hkl: locale_id, + numlock_on_keys: Default::default(), + numlock_off_keys: Default::default(), + keys: Default::default(), + has_alt_graph: false, + }; + + // We initialize the keyboard state with all zeros to + // simulate a scenario when no modifier is active. + let mut key_state = [0u8; 256]; + + // `MapVirtualKeyExW` maps (non-numpad-specific) virtual keys to scancodes as if numlock + // was off. We rely on this behavior to find all virtual keys which are not numpad-specific + // but map to the numpad. + // + // src_vkey: VK ==> scancode: u16 (on the numpad) + // + // Then we convert the source virtual key into a `Key` and the scancode into a virtual key + // to get the reverse mapping. + // + // src_vkey: VK ==> scancode: u16 (on the numpad) + // || || + // \/ \/ + // map_value: Key <- map_vkey: VK + layout.numlock_off_keys.reserve(NUMPAD_KEYCODES.len()); + for vk in 0..256 { + let scancode = unsafe { + winuser::MapVirtualKeyExW(vk, winuser::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 i32) && NUMPAD_KEYCODES.contains(&keycode) { + let native_code = NativeKeyCode::Windows(scancode as u16); + let map_vkey = keycode_to_vkey(keycode, locale_id); + if map_vkey == 0 { + continue; + } + let map_value = vkey_to_non_char_key(vk as i32, 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 { + winuser::MapVirtualKeyExW(vk, winuser::MAPVK_VK_TO_VSC_EX, locale_id as HKL) + }; + let unicode = Self::to_unicode_string(&key_state, vk, scancode, locale_id); + if let ToUnicodeResult::Str(s) = unicode { + let static_str = get_or_insert_str(strings, s); + layout + .numlock_on_keys + .insert(vk as i32, Key::Character(static_str)); + } + } + + // Iterate through every combination of modifiers + let mods_end = WindowsModifiers::FLAGS_END.bits; + for mod_state in 0..mods_end { + let mut keys_for_this_mod = HashMap::with_capacity(256); + + let mod_state = unsafe { WindowsModifiers::from_bits_unchecked(mod_state) }; + mod_state.apply_to_kbd_state(&mut key_state); + + // Virtual key values are in the domain [0, 255]. + // This is reinforced by the fact that the keyboard state array has 256 + // elements. This array is allowed to be indexed by virtual key values + // giving the key state for the virtual key used for indexing. + for vk in 0..256 { + let scancode = unsafe { + winuser::MapVirtualKeyExW(vk, winuser::MAPVK_VK_TO_VSC_EX, locale_id as HKL) + }; + if scancode == 0 { + continue; + } + + let native_code = NativeKeyCode::Windows(scancode as ExScancode); + 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 i32, native_code, locale_id, false); + match preliminary_key { + Key::Unidentified(_) => (), + _ => { + keys_for_this_mod.insert(key_code, preliminary_key); + continue; + } + } + + let unicode = Self::to_unicode_string(&key_state, vk, scancode, locale_id); + let key = match unicode { + ToUnicodeResult::Str(str) => { + let static_str = get_or_insert_str(strings, str); + Key::Character(static_str) + } + ToUnicodeResult::Dead(dead_char) => { + //println!("{:?} - {:?} produced dead {:?}", key_code, mod_state, dead_char); + Key::Dead(dead_char) + } + ToUnicodeResult::None => { + let has_alt = mod_state.contains(WindowsModifiers::ALT); + let has_ctrl = mod_state.contains(WindowsModifiers::CONTROL); + // HACK: `ToUnicodeEx` seems to fail getting the string for the numpad + // divide key, so we handle that explicitly here + if !has_alt && !has_ctrl && key_code == KeyCode::NumpadDivide { + Key::Character("/") + } else { + // Just use the unidentified key, we got earlier + preliminary_key + } + } + }; + + // Check for alt graph. + // The logic is that if a key pressed with no modifier produces + // a different `Character` from when it's pressed with CTRL+ALT then the layout + // has AltGr. + let ctrl_alt: WindowsModifiers = WindowsModifiers::CONTROL | WindowsModifiers::ALT; + let is_in_ctrl_alt = mod_state == ctrl_alt; + if !layout.has_alt_graph && is_in_ctrl_alt { + // Unwrapping here because if we are in the ctrl+alt modifier state + // then the alt modifier state must have come before. + let simple_keys = layout.keys.get(&WindowsModifiers::empty()).unwrap(); + if let Some(Key::Character(key_no_altgr)) = simple_keys.get(&key_code) { + if let Key::Character(key) = key { + layout.has_alt_graph = key != *key_no_altgr; + } + } + } + + keys_for_this_mod.insert(key_code, key); + } + layout.keys.insert(mod_state, keys_for_this_mod); + } + + // Second pass: replace right alt keys with AltGr if the layout has alt graph + if layout.has_alt_graph { + for mod_state in 0..mods_end { + let mod_state = unsafe { WindowsModifiers::from_bits_unchecked(mod_state) }; + if let Some(keys) = layout.keys.get_mut(&mod_state) { + if let Some(key) = keys.get_mut(&KeyCode::AltRight) { + *key = Key::AltGraph; + } + } + } + } + + layout + } + + fn to_unicode_string( + key_state: &[u8; 256], + vkey: u32, + scancode: u32, + locale_id: u64, + ) -> ToUnicodeResult { + unsafe { + let mut label_wide = [0u16; 8]; + let mut wide_len = winuser::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 = winuser::ToUnicodeEx( + vkey, + scancode, + (&key_state[0]) as *const _, + (&mut label_wide[0]) as *mut _, + label_wide.len() as i32, + 0, + locale_id as HKL, + ); + if wide_len > 0 { + let os_string = OsString::from_wide(&label_wide[0..wide_len as usize]); + if let Ok(label_str) = os_string.into_string() { + if let Some(ch) = label_str.chars().next() { + return ToUnicodeResult::Dead(Some(ch)); + } + } + } + return ToUnicodeResult::Dead(None); + } + if wide_len > 0 { + let os_string = OsString::from_wide(&label_wide[0..wide_len as usize]); + if let Ok(label_str) = os_string.into_string() { + return ToUnicodeResult::Str(label_str); + } + } + } + ToUnicodeResult::None + } +} + +pub fn get_or_insert_str(strings: &mut HashSet<&'static str>, string: T) -> &'static str +where + T: AsRef, + String: From, +{ + { + let str_ref = string.as_ref(); + if let Some(&existing) = strings.get(str_ref) { + return existing; + } + } + let leaked = Box::leak(Box::from(String::from(string))); + strings.insert(leaked); + leaked +} + +#[derive(Debug, Clone, Eq, PartialEq)] +enum ToUnicodeResult { + Str(String), + Dead(Option), + None, +} + +fn is_numpad_specific(vk: i32) -> bool { + match vk { + winuser::VK_NUMPAD0 => true, + winuser::VK_NUMPAD1 => true, + winuser::VK_NUMPAD2 => true, + winuser::VK_NUMPAD3 => true, + winuser::VK_NUMPAD4 => true, + winuser::VK_NUMPAD5 => true, + winuser::VK_NUMPAD6 => true, + winuser::VK_NUMPAD7 => true, + winuser::VK_NUMPAD8 => true, + winuser::VK_NUMPAD9 => true, + winuser::VK_ADD => true, + winuser::VK_SUBTRACT => true, + winuser::VK_DIVIDE => true, + winuser::VK_DECIMAL => true, + winuser::VK_SEPARATOR => true, + _ => false, + } +} + +fn keycode_to_vkey(keycode: KeyCode, hkl: u64) -> i32 { + let primary_lang_id = PRIMARYLANGID(LOWORD(hkl as u32)); + let is_korean = primary_lang_id == LANG_KOREAN; + let is_japanese = primary_lang_id == 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 => winuser::VK_LMENU, + KeyCode::AltRight => winuser::VK_RMENU, + KeyCode::Backspace => winuser::VK_BACK, + KeyCode::CapsLock => winuser::VK_CAPITAL, + KeyCode::ContextMenu => winuser::VK_APPS, + KeyCode::ControlLeft => winuser::VK_LCONTROL, + KeyCode::ControlRight => winuser::VK_RCONTROL, + KeyCode::Enter => winuser::VK_RETURN, + KeyCode::SuperLeft => winuser::VK_LWIN, + KeyCode::SuperRight => winuser::VK_RWIN, + KeyCode::ShiftLeft => winuser::VK_RSHIFT, + KeyCode::ShiftRight => winuser::VK_LSHIFT, + KeyCode::Space => winuser::VK_SPACE, + KeyCode::Tab => winuser::VK_TAB, + KeyCode::Convert => winuser::VK_CONVERT, + KeyCode::KanaMode => winuser::VK_KANA, + KeyCode::Lang1 if is_korean => winuser::VK_HANGUL, + KeyCode::Lang1 if is_japanese => winuser::VK_KANA, + KeyCode::Lang2 if is_korean => winuser::VK_HANJA, + KeyCode::Lang2 if is_japanese => 0, + KeyCode::Lang3 if is_japanese => winuser::VK_OEM_FINISH, + KeyCode::Lang4 if is_japanese => 0, + KeyCode::Lang5 if is_japanese => 0, + KeyCode::NonConvert => winuser::VK_NONCONVERT, + KeyCode::Delete => winuser::VK_DELETE, + KeyCode::End => winuser::VK_END, + KeyCode::Help => winuser::VK_HELP, + KeyCode::Home => winuser::VK_HOME, + KeyCode::Insert => winuser::VK_INSERT, + KeyCode::PageDown => winuser::VK_NEXT, + KeyCode::PageUp => winuser::VK_PRIOR, + KeyCode::ArrowDown => winuser::VK_DOWN, + KeyCode::ArrowLeft => winuser::VK_LEFT, + KeyCode::ArrowRight => winuser::VK_RIGHT, + KeyCode::ArrowUp => winuser::VK_UP, + KeyCode::NumLock => winuser::VK_NUMLOCK, + KeyCode::Numpad0 => winuser::VK_NUMPAD0, + KeyCode::Numpad1 => winuser::VK_NUMPAD1, + KeyCode::Numpad2 => winuser::VK_NUMPAD2, + KeyCode::Numpad3 => winuser::VK_NUMPAD3, + KeyCode::Numpad4 => winuser::VK_NUMPAD4, + KeyCode::Numpad5 => winuser::VK_NUMPAD5, + KeyCode::Numpad6 => winuser::VK_NUMPAD6, + KeyCode::Numpad7 => winuser::VK_NUMPAD7, + KeyCode::Numpad8 => winuser::VK_NUMPAD8, + KeyCode::Numpad9 => winuser::VK_NUMPAD9, + KeyCode::NumpadAdd => winuser::VK_ADD, + KeyCode::NumpadBackspace => winuser::VK_BACK, + KeyCode::NumpadClear => winuser::VK_CLEAR, + KeyCode::NumpadClearEntry => 0, + KeyCode::NumpadComma => winuser::VK_SEPARATOR, + KeyCode::NumpadDecimal => winuser::VK_DECIMAL, + KeyCode::NumpadDivide => winuser::VK_DIVIDE, + KeyCode::NumpadEnter => winuser::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 => winuser::VK_MULTIPLY, + KeyCode::NumpadParenLeft => 0, + KeyCode::NumpadParenRight => 0, + KeyCode::NumpadStar => 0, + KeyCode::NumpadSubtract => winuser::VK_SUBTRACT, + KeyCode::Escape => winuser::VK_ESCAPE, + KeyCode::Fn => 0, + KeyCode::FnLock => 0, + KeyCode::PrintScreen => winuser::VK_SNAPSHOT, + KeyCode::ScrollLock => winuser::VK_SCROLL, + KeyCode::Pause => winuser::VK_PAUSE, + KeyCode::BrowserBack => winuser::VK_BROWSER_BACK, + KeyCode::BrowserFavorites => winuser::VK_BROWSER_FAVORITES, + KeyCode::BrowserForward => winuser::VK_BROWSER_FORWARD, + KeyCode::BrowserHome => winuser::VK_BROWSER_HOME, + KeyCode::BrowserRefresh => winuser::VK_BROWSER_REFRESH, + KeyCode::BrowserSearch => winuser::VK_BROWSER_SEARCH, + KeyCode::BrowserStop => winuser::VK_BROWSER_STOP, + KeyCode::Eject => 0, + KeyCode::LaunchApp1 => winuser::VK_LAUNCH_APP1, + KeyCode::LaunchApp2 => winuser::VK_LAUNCH_APP2, + KeyCode::LaunchMail => winuser::VK_LAUNCH_MAIL, + KeyCode::MediaPlayPause => winuser::VK_MEDIA_PLAY_PAUSE, + KeyCode::MediaSelect => winuser::VK_LAUNCH_MEDIA_SELECT, + KeyCode::MediaStop => winuser::VK_MEDIA_STOP, + KeyCode::MediaTrackNext => winuser::VK_MEDIA_NEXT_TRACK, + KeyCode::MediaTrackPrevious => winuser::VK_MEDIA_PREV_TRACK, + KeyCode::Power => 0, + KeyCode::Sleep => 0, + KeyCode::AudioVolumeDown => winuser::VK_VOLUME_DOWN, + KeyCode::AudioVolumeMute => winuser::VK_VOLUME_MUTE, + KeyCode::AudioVolumeUp => winuser::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 => winuser::VK_SELECT, + KeyCode::Undo => 0, + KeyCode::Hiragana => 0, + KeyCode::Katakana => 0, + KeyCode::F1 => winuser::VK_F1, + KeyCode::F2 => winuser::VK_F2, + KeyCode::F3 => winuser::VK_F3, + KeyCode::F4 => winuser::VK_F4, + KeyCode::F5 => winuser::VK_F5, + KeyCode::F6 => winuser::VK_F6, + KeyCode::F7 => winuser::VK_F7, + KeyCode::F8 => winuser::VK_F8, + KeyCode::F9 => winuser::VK_F9, + KeyCode::F10 => winuser::VK_F10, + KeyCode::F11 => winuser::VK_F11, + KeyCode::F12 => winuser::VK_F12, + KeyCode::F13 => winuser::VK_F13, + KeyCode::F14 => winuser::VK_F14, + KeyCode::F15 => winuser::VK_F15, + KeyCode::F16 => winuser::VK_F16, + KeyCode::F17 => winuser::VK_F17, + KeyCode::F18 => winuser::VK_F18, + KeyCode::F19 => winuser::VK_F19, + KeyCode::F20 => winuser::VK_F20, + KeyCode::F21 => winuser::VK_F21, + KeyCode::F22 => winuser::VK_F22, + KeyCode::F23 => winuser::VK_F23, + KeyCode::F24 => winuser::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: i32, + native_code: NativeKeyCode, + hkl: u64, + has_alt_graph: bool, +) -> Key<'static> { + // List of the Web key names and their corresponding platform-native key names: + // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values + + let primary_lang_id = PRIMARYLANGID(LOWORD(hkl as u32)); + let is_korean = primary_lang_id == LANG_KOREAN; + let is_japanese = primary_lang_id == LANG_JAPANESE; + + match vkey { + winuser::VK_LBUTTON => Key::Unidentified(NativeKeyCode::Unidentified), // Mouse + winuser::VK_RBUTTON => Key::Unidentified(NativeKeyCode::Unidentified), // Mouse + + // I don't think this can be represented with a Key + winuser::VK_CANCEL => Key::Unidentified(native_code), + + winuser::VK_MBUTTON => Key::Unidentified(NativeKeyCode::Unidentified), // Mouse + winuser::VK_XBUTTON1 => Key::Unidentified(NativeKeyCode::Unidentified), // Mouse + winuser::VK_XBUTTON2 => Key::Unidentified(NativeKeyCode::Unidentified), // Mouse + winuser::VK_BACK => Key::Backspace, + winuser::VK_TAB => Key::Tab, + winuser::VK_CLEAR => Key::Clear, + winuser::VK_RETURN => Key::Enter, + winuser::VK_SHIFT => Key::Shift, + winuser::VK_CONTROL => Key::Control, + winuser::VK_MENU => Key::Alt, + winuser::VK_PAUSE => Key::Pause, + winuser::VK_CAPITAL => Key::CapsLock, + + //winuser::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 + winuser::VK_HANGUL if is_korean => Key::HangulMode, + winuser::VK_KANA if is_japanese => Key::KanaMode, + + winuser::VK_JUNJA => Key::JunjaMode, + winuser::VK_FINAL => Key::FinalMode, + + // VK_HANJA and VK_KANJI are defined as the same constant, therefore + // we use appropriate conditions to differentate between them + winuser::VK_HANJA if is_korean => Key::HanjaMode, + winuser::VK_KANJI if is_japanese => Key::KanjiMode, + + winuser::VK_ESCAPE => Key::Escape, + winuser::VK_CONVERT => Key::Convert, + winuser::VK_NONCONVERT => Key::NonConvert, + winuser::VK_ACCEPT => Key::Accept, + winuser::VK_MODECHANGE => Key::ModeChange, + winuser::VK_SPACE => Key::Space, + winuser::VK_PRIOR => Key::PageUp, + winuser::VK_NEXT => Key::PageDown, + winuser::VK_END => Key::End, + winuser::VK_HOME => Key::Home, + winuser::VK_LEFT => Key::ArrowLeft, + winuser::VK_UP => Key::ArrowUp, + winuser::VK_RIGHT => Key::ArrowRight, + winuser::VK_DOWN => Key::ArrowDown, + winuser::VK_SELECT => Key::Select, + winuser::VK_PRINT => Key::Print, + winuser::VK_EXECUTE => Key::Execute, + winuser::VK_SNAPSHOT => Key::PrintScreen, + winuser::VK_INSERT => Key::Insert, + winuser::VK_DELETE => Key::Delete, + winuser::VK_HELP => Key::Help, + winuser::VK_LWIN => Key::Super, + winuser::VK_RWIN => Key::Super, + winuser::VK_APPS => Key::ContextMenu, + winuser::VK_SLEEP => Key::Standby, + + // Numpad keys produce characters + winuser::VK_NUMPAD0 => Key::Unidentified(native_code), + winuser::VK_NUMPAD1 => Key::Unidentified(native_code), + winuser::VK_NUMPAD2 => Key::Unidentified(native_code), + winuser::VK_NUMPAD3 => Key::Unidentified(native_code), + winuser::VK_NUMPAD4 => Key::Unidentified(native_code), + winuser::VK_NUMPAD5 => Key::Unidentified(native_code), + winuser::VK_NUMPAD6 => Key::Unidentified(native_code), + winuser::VK_NUMPAD7 => Key::Unidentified(native_code), + winuser::VK_NUMPAD8 => Key::Unidentified(native_code), + winuser::VK_NUMPAD9 => Key::Unidentified(native_code), + winuser::VK_MULTIPLY => Key::Unidentified(native_code), + winuser::VK_ADD => Key::Unidentified(native_code), + winuser::VK_SEPARATOR => Key::Unidentified(native_code), + winuser::VK_SUBTRACT => Key::Unidentified(native_code), + winuser::VK_DECIMAL => Key::Unidentified(native_code), + winuser::VK_DIVIDE => Key::Unidentified(native_code), + + winuser::VK_F1 => Key::F1, + winuser::VK_F2 => Key::F2, + winuser::VK_F3 => Key::F3, + winuser::VK_F4 => Key::F4, + winuser::VK_F5 => Key::F5, + winuser::VK_F6 => Key::F6, + winuser::VK_F7 => Key::F7, + winuser::VK_F8 => Key::F8, + winuser::VK_F9 => Key::F9, + winuser::VK_F10 => Key::F10, + winuser::VK_F11 => Key::F11, + winuser::VK_F12 => Key::F12, + winuser::VK_F13 => Key::F13, + winuser::VK_F14 => Key::F14, + winuser::VK_F15 => Key::F15, + winuser::VK_F16 => Key::F16, + winuser::VK_F17 => Key::F17, + winuser::VK_F18 => Key::F18, + winuser::VK_F19 => Key::F19, + winuser::VK_F20 => Key::F20, + winuser::VK_F21 => Key::F21, + winuser::VK_F22 => Key::F22, + winuser::VK_F23 => Key::F23, + winuser::VK_F24 => Key::F24, + winuser::VK_NAVIGATION_VIEW => Key::Unidentified(native_code), + winuser::VK_NAVIGATION_MENU => Key::Unidentified(native_code), + winuser::VK_NAVIGATION_UP => Key::Unidentified(native_code), + winuser::VK_NAVIGATION_DOWN => Key::Unidentified(native_code), + winuser::VK_NAVIGATION_LEFT => Key::Unidentified(native_code), + winuser::VK_NAVIGATION_RIGHT => Key::Unidentified(native_code), + winuser::VK_NAVIGATION_ACCEPT => Key::Unidentified(native_code), + winuser::VK_NAVIGATION_CANCEL => Key::Unidentified(native_code), + winuser::VK_NUMLOCK => Key::NumLock, + winuser::VK_SCROLL => Key::ScrollLock, + winuser::VK_OEM_NEC_EQUAL => Key::Unidentified(native_code), + //winuser::VK_OEM_FJ_JISHO => Key::Unidentified(native_code), // Conflicts with `VK_OEM_NEC_EQUAL` + winuser::VK_OEM_FJ_MASSHOU => Key::Unidentified(native_code), + winuser::VK_OEM_FJ_TOUROKU => Key::Unidentified(native_code), + winuser::VK_OEM_FJ_LOYA => Key::Unidentified(native_code), + winuser::VK_OEM_FJ_ROYA => Key::Unidentified(native_code), + winuser::VK_LSHIFT => Key::Shift, + winuser::VK_RSHIFT => Key::Shift, + winuser::VK_LCONTROL => Key::Control, + winuser::VK_RCONTROL => Key::Control, + winuser::VK_LMENU => Key::Alt, + winuser::VK_RMENU => { + if has_alt_graph { + Key::AltGraph + } else { + Key::Alt + } + } + winuser::VK_BROWSER_BACK => Key::BrowserBack, + winuser::VK_BROWSER_FORWARD => Key::BrowserForward, + winuser::VK_BROWSER_REFRESH => Key::BrowserRefresh, + winuser::VK_BROWSER_STOP => Key::BrowserStop, + winuser::VK_BROWSER_SEARCH => Key::BrowserSearch, + winuser::VK_BROWSER_FAVORITES => Key::BrowserFavorites, + winuser::VK_BROWSER_HOME => Key::BrowserHome, + winuser::VK_VOLUME_MUTE => Key::AudioVolumeMute, + winuser::VK_VOLUME_DOWN => Key::AudioVolumeDown, + winuser::VK_VOLUME_UP => Key::AudioVolumeUp, + winuser::VK_MEDIA_NEXT_TRACK => Key::MediaTrackNext, + winuser::VK_MEDIA_PREV_TRACK => Key::MediaTrackPrevious, + winuser::VK_MEDIA_STOP => Key::MediaStop, + winuser::VK_MEDIA_PLAY_PAUSE => Key::MediaPlayPause, + winuser::VK_LAUNCH_MAIL => Key::LaunchMail, + winuser::VK_LAUNCH_MEDIA_SELECT => Key::LaunchMediaPlayer, + winuser::VK_LAUNCH_APP1 => Key::LaunchApplication1, + winuser::VK_LAUNCH_APP2 => Key::LaunchApplication2, + + // This function only converts "non-printable" + winuser::VK_OEM_1 => Key::Unidentified(native_code), + winuser::VK_OEM_PLUS => Key::Unidentified(native_code), + winuser::VK_OEM_COMMA => Key::Unidentified(native_code), + winuser::VK_OEM_MINUS => Key::Unidentified(native_code), + winuser::VK_OEM_PERIOD => Key::Unidentified(native_code), + winuser::VK_OEM_2 => Key::Unidentified(native_code), + winuser::VK_OEM_3 => Key::Unidentified(native_code), + + winuser::VK_GAMEPAD_A => Key::Unidentified(native_code), + winuser::VK_GAMEPAD_B => Key::Unidentified(native_code), + winuser::VK_GAMEPAD_X => Key::Unidentified(native_code), + winuser::VK_GAMEPAD_Y => Key::Unidentified(native_code), + winuser::VK_GAMEPAD_RIGHT_SHOULDER => Key::Unidentified(native_code), + winuser::VK_GAMEPAD_LEFT_SHOULDER => Key::Unidentified(native_code), + winuser::VK_GAMEPAD_LEFT_TRIGGER => Key::Unidentified(native_code), + winuser::VK_GAMEPAD_RIGHT_TRIGGER => Key::Unidentified(native_code), + winuser::VK_GAMEPAD_DPAD_UP => Key::Unidentified(native_code), + winuser::VK_GAMEPAD_DPAD_DOWN => Key::Unidentified(native_code), + winuser::VK_GAMEPAD_DPAD_LEFT => Key::Unidentified(native_code), + winuser::VK_GAMEPAD_DPAD_RIGHT => Key::Unidentified(native_code), + winuser::VK_GAMEPAD_MENU => Key::Unidentified(native_code), + winuser::VK_GAMEPAD_VIEW => Key::Unidentified(native_code), + winuser::VK_GAMEPAD_LEFT_THUMBSTICK_BUTTON => Key::Unidentified(native_code), + winuser::VK_GAMEPAD_RIGHT_THUMBSTICK_BUTTON => Key::Unidentified(native_code), + winuser::VK_GAMEPAD_LEFT_THUMBSTICK_UP => Key::Unidentified(native_code), + winuser::VK_GAMEPAD_LEFT_THUMBSTICK_DOWN => Key::Unidentified(native_code), + winuser::VK_GAMEPAD_LEFT_THUMBSTICK_RIGHT => Key::Unidentified(native_code), + winuser::VK_GAMEPAD_LEFT_THUMBSTICK_LEFT => Key::Unidentified(native_code), + winuser::VK_GAMEPAD_RIGHT_THUMBSTICK_UP => Key::Unidentified(native_code), + winuser::VK_GAMEPAD_RIGHT_THUMBSTICK_DOWN => Key::Unidentified(native_code), + winuser::VK_GAMEPAD_RIGHT_THUMBSTICK_RIGHT => Key::Unidentified(native_code), + winuser::VK_GAMEPAD_RIGHT_THUMBSTICK_LEFT => Key::Unidentified(native_code), + + // This function only converts "non-printable" + winuser::VK_OEM_4 => Key::Unidentified(native_code), + winuser::VK_OEM_5 => Key::Unidentified(native_code), + winuser::VK_OEM_6 => Key::Unidentified(native_code), + winuser::VK_OEM_7 => Key::Unidentified(native_code), + winuser::VK_OEM_8 => Key::Unidentified(native_code), + winuser::VK_OEM_AX => Key::Unidentified(native_code), + winuser::VK_OEM_102 => Key::Unidentified(native_code), + + winuser::VK_ICO_HELP => Key::Unidentified(native_code), + winuser::VK_ICO_00 => Key::Unidentified(native_code), + + winuser::VK_PROCESSKEY => Key::Process, + + winuser::VK_ICO_CLEAR => Key::Unidentified(native_code), + winuser::VK_PACKET => Key::Unidentified(native_code), + winuser::VK_OEM_RESET => Key::Unidentified(native_code), + winuser::VK_OEM_JUMP => Key::Unidentified(native_code), + winuser::VK_OEM_PA1 => Key::Unidentified(native_code), + winuser::VK_OEM_PA2 => Key::Unidentified(native_code), + winuser::VK_OEM_PA3 => Key::Unidentified(native_code), + winuser::VK_OEM_WSCTRL => Key::Unidentified(native_code), + winuser::VK_OEM_CUSEL => Key::Unidentified(native_code), + + winuser::VK_OEM_ATTN => Key::Attn, + winuser::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) + } + } + winuser::VK_OEM_COPY => Key::Copy, + winuser::VK_OEM_AUTO => Key::Hankaku, + winuser::VK_OEM_ENLW => Key::Zenkaku, + winuser::VK_OEM_BACKTAB => Key::Romaji, + winuser::VK_ATTN => Key::KanaMode, + winuser::VK_CRSEL => Key::CrSel, + winuser::VK_EXSEL => Key::ExSel, + winuser::VK_EREOF => Key::EraseEof, + winuser::VK_PLAY => Key::Play, + winuser::VK_ZOOM => Key::ZoomToggle, + winuser::VK_NONAME => Key::Unidentified(native_code), + winuser::VK_PA1 => Key::Unidentified(native_code), + winuser::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..cbf754eecc --- /dev/null +++ b/src/platform_impl/windows/minimal_ime.rs @@ -0,0 +1,93 @@ +use std::mem::MaybeUninit; + +use winapi::{ + shared::{ + minwindef::{LPARAM, WPARAM}, + windef::HWND, + }, + um::winuser, +}; + +use crate::platform_impl::platform::event_loop::ProcResult; + +pub fn is_msg_ime_related(msg_kind: u32) -> bool { + match msg_kind { + winuser::WM_IME_COMPOSITION + | winuser::WM_IME_COMPOSITIONFULL + | winuser::WM_IME_STARTCOMPOSITION + | winuser::WM_IME_ENDCOMPOSITION + | winuser::WM_IME_CHAR + | winuser::WM_CHAR + | winuser::WM_SYSCHAR => true, + _ => false, + } +} + +pub struct MinimalIme { + // True if we're currently receiving messages belonging to a finished IME session. + getting_ime_text: bool, + + utf16parts: Vec, +} +impl Default for MinimalIme { + fn default() -> Self { + MinimalIme { + getting_ime_text: false, + utf16parts: Vec::with_capacity(16), + } + } +} +impl MinimalIme { + pub(crate) fn process_message( + &mut 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 = true; + } + winuser::WM_CHAR | winuser::WM_SYSCHAR => { + if self.getting_ime_text { + *result = ProcResult::Value(0); + self.utf16parts.push(wparam as u16); + + let more_char_coming; + unsafe { + let mut next_msg = MaybeUninit::uninit(); + let has_message = winuser::PeekMessageW( + next_msg.as_mut_ptr(), + hwnd, + winuser::WM_KEYFIRST, + winuser::WM_KEYLAST, + winuser::PM_NOREMOVE, + ); + let has_message = has_message != 0; + if !has_message { + more_char_coming = false; + } else { + let next_msg = next_msg.assume_init().message; + if next_msg == winuser::WM_CHAR || next_msg == winuser::WM_SYSCHAR { + more_char_coming = true; + } else { + more_char_coming = false; + } + } + } + if !more_char_coming { + let result = String::from_utf16(&self.utf16parts).ok(); + self.utf16parts.clear(); + self.getting_ime_text = false; + return result; + } + } + } + _ => (), + } + + None + } +} diff --git a/src/platform_impl/windows/mod.rs b/src/platform_impl/windows/mod.rs index 9215a92306..6feef4fafa 100644 --- a/src/platform_impl/windows/mod.rs +++ b/src/platform_impl/windows/mod.rs @@ -13,6 +13,7 @@ pub use self::icon::WinIcon as PlatformIcon; use crate::event::DeviceId as RootDeviceId; use crate::icon::Icon; +use crate::keyboard::Key; use crate::window::Theme; #[derive(Clone)] @@ -82,6 +83,12 @@ fn wrap_device_id(id: u32) -> RootDeviceId { pub type OsError = std::io::Error; +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct KeyEventExtra { + pub text_with_all_modifers: Option<&'static str>, + pub key_without_modifiers: Key<'static>, +} + #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct WindowId(HWND); unsafe impl Send for WindowId {} @@ -100,9 +107,11 @@ mod util; mod dark_mode; mod dpi; mod drop_handler; -mod event; mod event_loop; mod icon; +mod keyboard; +mod keyboard_layout; +mod minimal_ime; mod monitor; mod raw_input; mod window; diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index f0ea7fbd04..893af39de2 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -1,5 +1,6 @@ #![cfg(target_os = "windows")] +use mem::MaybeUninit; use parking_lot::Mutex; use raw_window_handle::{RawWindowHandle, Win32Handle}; use std::{ @@ -652,6 +653,26 @@ impl Window { unsafe { force_window_active(window.0) }; } } + + #[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 = winuser::VK_SPACE as u32; + let scancode = winuser::MapVirtualKeyW(vk, winuser::MAPVK_VK_TO_VSC); + let kbd_state = [0; 256]; + let mut char_buff = [MaybeUninit::uninit(); 8]; + winuser::ToUnicode( + vk, + scancode, + kbd_state.as_ptr(), + char_buff[0].as_mut_ptr(), + char_buff.len() as i32, + 0, + ); + } + } } impl Drop for Window { diff --git a/src/platform_impl/windows/window_state.rs b/src/platform_impl/windows/window_state.rs index 9e41bd7925..bca61f05e0 100644 --- a/src/platform_impl/windows/window_state.rs +++ b/src/platform_impl/windows/window_state.rs @@ -1,8 +1,10 @@ use crate::{ dpi::{PhysicalPosition, Size}, - event::ModifiersState, icon::Icon, - platform_impl::platform::{event_loop, util}, + keyboard::ModifiersState, + platform_impl::platform::{ + event_loop, keyboard::KeyEventBuilder, minimal_ime::MinimalIme, util, + }, window::{CursorIcon, Fullscreen, Theme, WindowAttributes}, }; use parking_lot::MutexGuard; @@ -34,6 +36,10 @@ pub struct WindowState { pub current_theme: Theme, pub preferred_theme: Option, pub high_surrogate: Option, + + pub key_event_builder: KeyEventBuilder, + pub ime_handler: MinimalIme, + pub window_flags: WindowFlags, } @@ -122,6 +128,8 @@ impl WindowState { current_theme, preferred_theme, high_surrogate: None, + key_event_builder: KeyEventBuilder::default(), + ime_handler: MinimalIme::default(), window_flags: WindowFlags::empty(), } } diff --git a/src/window.rs b/src/window.rs index 9410421d57..2f53bb778c 100644 --- a/src/window.rs +++ b/src/window.rs @@ -447,6 +447,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:** 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. diff --git a/tests/serde_objects.rs b/tests/serde_objects.rs index ad729dcd1b..b0333fa410 100644 --- a/tests/serde_objects.rs +++ b/tests/serde_objects.rs @@ -3,10 +3,8 @@ use serde::{Deserialize, Serialize}; use winit::{ dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize}, - event::{ - ElementState, KeyboardInput, ModifiersState, MouseButton, MouseScrollDelta, TouchPhase, - VirtualKeyCode, - }, + event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase}, + keyboard::{Key, KeyCode, KeyLocation, ModifiersState}, window::CursorIcon, }; @@ -20,12 +18,13 @@ fn window_serde() { #[test] fn events_serde() { - needs_serde::(); needs_serde::(); needs_serde::(); needs_serde::(); needs_serde::(); - needs_serde::(); + needs_serde::>(); + needs_serde::(); + needs_serde::(); needs_serde::(); }