From d9bda3e985aad1e5364318597443c1a02ede5212 Mon Sep 17 00:00:00 2001 From: Osspial Date: Mon, 30 Dec 2019 14:11:11 -0500 Subject: [PATCH] Implement ModifiersChanged on Windows, and fix bugs discovered in implementation process (#1344) * Move DeviceEvent handling to the message target window. Previously, device events seem to have only been sent to one particular window, and when that window was closed Winit would stop receiving device events. This also allows users to create windowless event loops that process device events - an intriguing idea, to say the least. * Emit LWin and RWin VirtualKeyCodes on Windows * Implement ModifiersChanged on Windows * Make ModifiersChanged a tuple variant instead of a struct variant * Add changelog entries * Format * Update changelog entry * Fix AltGr handling * Reformat * Publicly expose ModifiersChanged and deprecate misc. modifiers fields --- CHANGELOG.md | 4 + examples/cursor_grab.rs | 6 +- src/event.rs | 18 +- src/platform_impl/linux/wayland/keyboard.rs | 2 +- .../linux/x11/event_processor.rs | 6 +- src/platform_impl/macos/view.rs | 4 +- src/platform_impl/windows/event.rs | 53 +++- src/platform_impl/windows/event_loop.rs | 284 ++++++++++-------- src/platform_impl/windows/window.rs | 7 +- 9 files changed, 243 insertions(+), 141 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92a8dfa2..d0459d48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,11 @@ - `RedrawEventsCleared` is issued after each set of `RedrawRequested` events. - Implement synthetic window focus key events on Windows. - **Breaking**: Change `ModifiersState` to a `bitflags` struct. +- On Windows, implement `VirtualKeyCode` translation for `LWin` and `RWin`. +- On Windows, fix closing the last opened window causing `DeviceEvent`s to stop getting emitted. - On Windows, fix `Window::set_visible` not setting internal flags correctly. This resulted in some weird behavior. +- Add `DeviceEvent::ModifiersChanged`. + - Deprecate `modifiers` fields in other events in favor of `ModifiersChanged`. # 0.20.0 Alpha 5 (2019-12-09) diff --git a/examples/cursor_grab.rs b/examples/cursor_grab.rs index ac3a0f27..13f14282 100644 --- a/examples/cursor_grab.rs +++ b/examples/cursor_grab.rs @@ -1,5 +1,5 @@ use winit::{ - event::{DeviceEvent, ElementState, Event, KeyboardInput, WindowEvent}, + event::{DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, WindowEvent}, event_loop::{ControlFlow, EventLoop}, window::WindowBuilder, }; @@ -12,6 +12,8 @@ fn main() { .build(&event_loop) .unwrap(); + let mut modifiers = ModifiersState::default(); + event_loop.run(move |event, _, control_flow| { *control_flow = ControlFlow::Wait; match event { @@ -22,7 +24,6 @@ fn main() { KeyboardInput { state: ElementState::Released, virtual_keycode: Some(key), - modifiers, .. }, .. @@ -43,6 +44,7 @@ fn main() { ElementState::Pressed => println!("mouse button {} pressed", button), ElementState::Released => println!("mouse button {} released", button), }, + DeviceEvent::ModifiersChanged(m) => modifiers = m, _ => (), }, _ => (), diff --git a/src/event.rs b/src/event.rs index b66ca992..d93324f7 100644 --- a/src/event.rs +++ b/src/event.rs @@ -167,6 +167,7 @@ pub enum WindowEvent { /// limited by the display area and it may have been transformed by the OS to implement effects such as cursor /// acceleration, it should not be used to implement non-cursor-like interactions such as 3D camera control. position: LogicalPosition, + #[deprecated = "Deprecated in favor of DeviceEvent::ModifiersChanged"] modifiers: ModifiersState, }, @@ -181,6 +182,7 @@ pub enum WindowEvent { device_id: DeviceId, delta: MouseScrollDelta, phase: TouchPhase, + #[deprecated = "Deprecated in favor of DeviceEvent::ModifiersChanged"] modifiers: ModifiersState, }, @@ -189,6 +191,7 @@ pub enum WindowEvent { device_id: DeviceId, state: ElementState, button: MouseButton, + #[deprecated = "Deprecated in favor of DeviceEvent::ModifiersChanged"] modifiers: ModifiersState, }, @@ -295,11 +298,15 @@ pub enum DeviceEvent { Key(KeyboardInput), - /// Keyboard modifiers have changed - #[doc(hidden)] - ModifiersChanged { - modifiers: ModifiersState, - }, + /// The keyboard modifiers have changed. + /// + /// 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. + /// + /// Platform-specific behavior: + /// - **Web**: This API is currently unimplemented on the web. This isn't by design - it's an + /// issue, and it should get fixed - but it's the current state of the API. + ModifiersChanged(ModifiersState), Text { codepoint: char, @@ -329,6 +336,7 @@ pub struct KeyboardInput { /// /// 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 DeviceEvent::ModifiersChanged"] pub modifiers: ModifiersState, } diff --git a/src/platform_impl/linux/wayland/keyboard.rs b/src/platform_impl/linux/wayland/keyboard.rs index b361cb1b..7e0d2e3e 100644 --- a/src/platform_impl/linux/wayland/keyboard.rs +++ b/src/platform_impl/linux/wayland/keyboard.rs @@ -105,7 +105,7 @@ pub fn init_keyboard( my_sink .send(Event::DeviceEvent { device_id: device_id(), - event: DeviceEvent::ModifiersChanged { modifiers }, + event: DeviceEvent::ModifiersChanged(modifiers), }) .unwrap(); } diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index 762066ba..162b09df 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -137,7 +137,7 @@ impl EventProcessor { let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD); callback(Event::DeviceEvent { device_id, - event: DeviceEvent::ModifiersChanged { modifiers }, + event: DeviceEvent::ModifiersChanged(modifiers), }); } } @@ -1114,9 +1114,7 @@ impl EventProcessor { if modifiers != new_modifiers { callback(Event::DeviceEvent { device_id, - event: DeviceEvent::ModifiersChanged { - modifiers: new_modifiers, - }, + event: DeviceEvent::ModifiersChanged(new_modifiers), }); } } diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index eb373d98..daf3a6f9 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -688,9 +688,7 @@ extern "C" fn flags_changed(this: &Object, _sel: Sel, event: id) { AppState::queue_event(Event::DeviceEvent { device_id: DEVICE_ID, - event: DeviceEvent::ModifiersChanged { - modifiers: state.modifiers, - }, + event: DeviceEvent::ModifiersChanged(state.modifiers), }); } trace!("Completed `flagsChanged`"); diff --git a/src/platform_impl/windows/event.rs b/src/platform_impl/windows/event.rs index 04ef7692..49501f1a 100644 --- a/src/platform_impl/windows/event.rs +++ b/src/platform_impl/windows/event.rs @@ -36,6 +36,55 @@ pub fn get_key_mods() -> ModifiersState { mods } +bitflags! { + #[derive(Default)] + pub struct ModifiersStateSide: u32 { + const LSHIFT = 0b010 << 0; + const RSHIFT = 0b001 << 0; + + 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()) }; @@ -196,8 +245,8 @@ pub fn vkey_to_winit_vkey(vkey: c_int) -> Option { 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_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), diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 9d6b7eff..88de492c 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -53,10 +53,10 @@ use crate::{ become_dpi_aware, dpi_to_scale_factor, enable_non_client_dpi_scaling, hwnd_scale_factor, }, drop_handler::FileDropHandler, - event::{self, handle_extended_keys, process_key_params, vkey_to_winit_vkey}, - monitor, - raw_input::{get_raw_input_data, get_raw_mouse_button_state}, - util, + event::{ + self, handle_extended_keys, process_key_params, vkey_to_winit_vkey, ModifiersStateSide, + }, + monitor, raw_input, util, window::adjust_size, window_state::{CursorFlags, WindowFlags, WindowState}, wrap_device_id, WindowId, DEVICE_ID, @@ -112,6 +112,7 @@ impl SubclassInput { struct ThreadMsgTargetSubclassInput { event_loop_runner: EventLoopRunnerShared, user_event_receiver: Receiver, + modifiers_state: ModifiersStateSide, } impl ThreadMsgTargetSubclassInput { @@ -169,6 +170,7 @@ impl EventLoop { let runner_shared = Rc::new(ELRShared::new()); let (thread_msg_target, thread_msg_sender) = thread_event_target_window(runner_shared.clone()); + raw_input::register_all_mice_and_keyboards_for_raw_input(thread_msg_target); EventLoop { thread_msg_sender, @@ -535,6 +537,7 @@ fn thread_event_target_window(event_loop_runner: EventLoopRunnerShared) -> let subclass_input = ThreadMsgTargetSubclassInput { event_loop_runner, user_event_receiver: rx, + modifiers_state: ModifiersStateSide::default(), }; let input_ptr = Box::into_raw(Box::new(subclass_input)); let subclass_result = commctrl::SetWindowSubclass( @@ -1115,120 +1118,6 @@ unsafe extern "system" fn public_window_callback( 0 } - winuser::WM_INPUT_DEVICE_CHANGE => { - let event = match wparam as _ { - winuser::GIDC_ARRIVAL => DeviceEvent::Added, - winuser::GIDC_REMOVAL => DeviceEvent::Removed, - _ => unreachable!(), - }; - - subclass_input.send_event(Event::DeviceEvent { - device_id: wrap_device_id(lparam as _), - event, - }); - - 0 - } - - winuser::WM_INPUT => { - use crate::event::{ - DeviceEvent::{Button, Key, Motion, MouseMotion, MouseWheel}, - ElementState::{Pressed, Released}, - MouseScrollDelta::LineDelta, - }; - - if let Some(data) = 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 { - subclass_input.send_event(Event::DeviceEvent { - device_id, - event: Motion { axis: 0, value: x }, - }); - } - - if y != 0.0 { - subclass_input.send_event(Event::DeviceEvent { - device_id, - event: Motion { axis: 1, value: y }, - }); - } - - if x != 0.0 || y != 0.0 { - subclass_input.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 / winuser::WHEEL_DELTA; - subclass_input.send_event(Event::DeviceEvent { - device_id, - event: MouseWheel { - delta: LineDelta(0.0, delta as f32), - }, - }); - } - - let button_state = 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 _; - subclass_input.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); - - subclass_input.send_event(Event::DeviceEvent { - device_id, - event: Key(KeyboardInput { - scancode, - state, - virtual_keycode, - modifiers: event::get_key_mods(), - }), - }); - } - } - } - } - - commctrl::DefSubclassProc(window, msg, wparam, lparam) - } - winuser::WM_TOUCH => { let pcount = LOWORD(wparam as DWORD) as usize; let mut inputs = Vec::with_capacity(pcount); @@ -1731,6 +1620,165 @@ unsafe extern "system" fn thread_event_target_callback( } 0 } + + winuser::WM_INPUT_DEVICE_CHANGE => { + let event = match wparam as _ { + winuser::GIDC_ARRIVAL => DeviceEvent::Added, + winuser::GIDC_REMOVAL => DeviceEvent::Removed, + _ => unreachable!(), + }; + + subclass_input.send_event(Event::DeviceEvent { + device_id: wrap_device_id(lparam as _), + event, + }); + + 0 + } + + winuser::WM_INPUT => { + use crate::event::{ + DeviceEvent::{Button, Key, ModifiersChanged, Motion, MouseMotion, MouseWheel}, + ElementState::{Pressed, Released}, + MouseScrollDelta::LineDelta, + VirtualKeyCode, + }; + + 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 { + subclass_input.send_event(Event::DeviceEvent { + device_id, + event: Motion { axis: 0, value: x }, + }); + } + + if y != 0.0 { + subclass_input.send_event(Event::DeviceEvent { + device_id, + event: Motion { axis: 1, value: y }, + }); + } + + if x != 0.0 || y != 0.0 { + subclass_input.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 / winuser::WHEEL_DELTA; + subclass_input.send_event(Event::DeviceEvent { + device_id, + event: MouseWheel { + delta: LineDelta(0.0, delta as f32), + }, + }); + } + + 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 _; + subclass_input.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); + + // If we ever change the DeviceEvent API to only emit events when a + // window is focused, we'll need to emit synthetic `ModifiersChanged` + // events when Winit windows lose focus so that these don't drift out + // of sync with the actual modifier state. + let old_modifiers_state = + subclass_input.modifiers_state.filter_out_altgr().into(); + match virtual_keycode { + Some(VirtualKeyCode::LShift) => subclass_input + .modifiers_state + .set(ModifiersStateSide::LSHIFT, pressed), + Some(VirtualKeyCode::RShift) => subclass_input + .modifiers_state + .set(ModifiersStateSide::RSHIFT, pressed), + Some(VirtualKeyCode::LControl) => subclass_input + .modifiers_state + .set(ModifiersStateSide::LCTRL, pressed), + Some(VirtualKeyCode::RControl) => subclass_input + .modifiers_state + .set(ModifiersStateSide::RCTRL, pressed), + Some(VirtualKeyCode::LAlt) => subclass_input + .modifiers_state + .set(ModifiersStateSide::LALT, pressed), + Some(VirtualKeyCode::RAlt) => subclass_input + .modifiers_state + .set(ModifiersStateSide::RALT, pressed), + Some(VirtualKeyCode::LWin) => subclass_input + .modifiers_state + .set(ModifiersStateSide::LLOGO, pressed), + Some(VirtualKeyCode::RWin) => subclass_input + .modifiers_state + .set(ModifiersStateSide::RLOGO, pressed), + _ => (), + } + let new_modifiers_state = + subclass_input.modifiers_state.filter_out_altgr().into(); + if new_modifiers_state != old_modifiers_state { + subclass_input.send_event(Event::DeviceEvent { + device_id, + event: ModifiersChanged(new_modifiers_state), + }); + } + + subclass_input.send_event(Event::DeviceEvent { + device_id, + event: Key(KeyboardInput { + scancode, + state, + virtual_keycode, + modifiers: new_modifiers_state, + }), + }); + } + } + } + } + + commctrl::DefSubclassProc(window, msg, wparam, lparam) + } + _ if msg == *USER_EVENT_MSG_ID => { if let Ok(event) = subclass_input.user_event_receiver.recv() { subclass_input.send_event(Event::UserEvent(event)); diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index d99f8645..012b8402 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -39,9 +39,7 @@ use crate::{ drop_handler::FileDropHandler, event_loop::{self, EventLoopWindowTarget, DESTROY_MSG_ID, INITIAL_DPI_MSG_ID}, icon::{self, IconType, WinIcon}, - monitor, - raw_input::register_all_mice_and_keyboards_for_raw_input, - util, + monitor, util, window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState}, PlatformSpecificWindowBuilderAttributes, WindowId, }, @@ -845,9 +843,6 @@ unsafe fn init( WindowWrapper(handle) }; - // Set up raw input - register_all_mice_and_keyboards_for_raw_input(real_window.0); - // Register for touch events if applicable { let digitizer = winuser::GetSystemMetrics(winuser::SM_DIGITIZER) as u32;