From e707052f66045a096a391e8d23cc41cba100db5f Mon Sep 17 00:00:00 2001 From: Murarth Date: Fri, 6 Mar 2020 15:43:55 -0700 Subject: [PATCH] Move `ModifiersChanged` variant to `WindowEvent` (#1381) * Move `ModifiersChanged` variant to `WindowEvent` * macos: Fix flags_changed for ModifiersChanged variant move I haven't look too deep at what this does internally, but at least cargo-check is fully happy now. :) Signed-off-by: Kristofer Rye * macos: Fire a ModifiersChanged event on window_did_resign_key From debugging, I determined that macOS' emission of a flagsChanged around window switching is inconsistent. It is fair to assume, I think, that when the user switches windows, they do not expect their former modifiers state to remain effective; so I think it's best to clear that state by sending a ModifiersChanged(ModifiersState::empty()). Signed-off-by: Kristofer Rye * windows: Fix build I don't know enough about the code to implement the fix as it is done on this branch, but this commit at least fixes the build. Signed-off-by: Kristofer Rye * windows: Send ModifiersChanged(ModifiersState::empty) on KILLFOCUS Very similar to the changes made in [1], as focus is lost, send an event to the window indicating that the modifiers have been released. It's unclear to me (without a Windows device to test this on) whether this is necessary, but it certainly ensures that unfocused windows will have at least received this event, which is an improvement. [1]: f79f21641a31da3e4039d41be89047cdcc6028f7 Signed-off-by: Kristofer Rye * macos: Add a hook to update stale modifiers Sometimes, `ViewState` and `event` might have different values for their stored `modifiers` flags. These are internally stored as a bitmask in the latter and an enum in the former. We can check to see if they differ, and if they do, automatically dispatch an event to update consumers of modifier state as well as the stored `state.modifiers`. That's what the hook does. This hook is then called in the key_down, mouse_entered, mouse_exited, mouse_click, scroll_wheel, and pressure_change_with_event callbacks, which each will contain updated modifiers. Signed-off-by: Kristofer Rye * Only call event_mods once when determining whether to update state Signed-off-by: Kristofer Rye * flags_changed: Memoize window_id collection Signed-off-by: Kristofer Rye * window_did_resign_key: Remove synthetic ModifiersChanged event We no longer need to emit this event, since we are checking the state of our modifiers before emitting most other events. Signed-off-by: Kristofer Rye * mouse_motion: Add a call to update_potentially_stale_modifiers Now, cover all events (that I can think of, at least) where stale modifiers might affect how user programs behave. Effectively, every human-interface event (keypress, mouse click, keydown, etc.) will cause a ModifiersChanged event to be fired if something has changed. Signed-off-by: Kristofer Rye * key_up: Add a call to update_potentially_stale_modifiers We also want to make sure modifiers state is synchronized here, too. Signed-off-by: Kristofer Rye * mouse_motion: Remove update_potentially_stale_modifiers invocation Signed-off-by: Kristofer Rye * Retry CI * ViewState: Promote visibility of modifiers to the macos impl This is so that we can interact with the ViewState directly from the WindowDelegate. Signed-off-by: Kristofer Rye * window_delegate: Synthetically set modifiers state to empty on resignKey This logic is implemented similarly on other platforms, so we wish to regain parity here. Originally this behavior was implemented to always fire an event with ModifiersState::empty(), but that was not the best as it was not necessarily correct and could be a duplicate event. This solution is perhaps the most elegant possible to implement the desired behavior of sending a synthetic empty modifiers event when a window loses focus, trading some safety for interoperation between the NSWindowDelegate and the NSView (as the objc runtime must now be consulted in order to acquire access to the ViewState which is "owned" by the NSView). Signed-off-by: Kristofer Rye * Check for modifiers change in window events * Fix modifier changed on macOS Since the `mouse_entered` function was generating a mouse motion, which updates the modifier state, a modifiers changed event was incorrectly generated. The updating of the modifier state has also been changed to make sure it consistently happens before events that have a modifier state attached to it, without happening on any other event. This of course means that no `CursorMoved` event is generated anymore when the user enters the window without it being focused, however I'd say that is consistent with how winit should behave. * Fix unused variable warning * Move changelog entry into `Unreleased` section Co-authored-by: Freya Gentz Co-authored-by: Kristofer Rye Co-authored-by: Christian Duerr --- CHANGELOG.md | 1 + examples/cursor_grab.rs | 2 +- src/event.rs | 26 ++-- src/platform_impl/linux/wayland/keyboard.rs | 23 +++- .../linux/x11/event_processor.rs | 116 +++++++++++------- src/platform_impl/linux/x11/mod.rs | 1 + src/platform_impl/macos/view.rs | 42 +++++-- src/platform_impl/macos/window_delegate.rs | 26 +++- src/platform_impl/windows/event_loop.rs | 111 +++++++++-------- src/platform_impl/windows/window_state.rs | 3 + 10 files changed, 229 insertions(+), 122 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa1d6bfb..6bd417c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - Fix `Event::to_static` returning `None` for user events. - On Wayland, Hide CSD for fullscreen windows. - On Windows, ignore spurious mouse move messages. +- **Breaking:** Move `ModifiersChanged` variant from `DeviceEvent` to `WindowEvent`. # 0.21.0 (2020-02-04) diff --git a/examples/cursor_grab.rs b/examples/cursor_grab.rs index 5ed193ec..317577e3 100644 --- a/examples/cursor_grab.rs +++ b/examples/cursor_grab.rs @@ -38,6 +38,7 @@ fn main() { _ => (), } } + WindowEvent::ModifiersChanged(m) => modifiers = m, _ => (), }, Event::DeviceEvent { event, .. } => match event { @@ -46,7 +47,6 @@ 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 494a4a1d..e7e7be9e 100644 --- a/src/event.rs +++ b/src/event.rs @@ -235,6 +235,13 @@ pub enum WindowEvent<'a> { is_synthetic: bool, }, + /// The keyboard modifiers have changed. + /// + /// 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), + /// The cursor has moved on the window. CursorMoved { device_id: DeviceId, @@ -243,7 +250,7 @@ pub enum WindowEvent<'a> { /// limited by the display area and it may have been transformed by the OS to implement effects such as cursor /// acceleration, it should not be used to implement non-cursor-like interactions such as 3D camera control. position: PhysicalPosition, - #[deprecated = "Deprecated in favor of DeviceEvent::ModifiersChanged"] + #[deprecated = "Deprecated in favor of WindowEvent::ModifiersChanged"] modifiers: ModifiersState, }, @@ -258,7 +265,7 @@ pub enum WindowEvent<'a> { device_id: DeviceId, delta: MouseScrollDelta, phase: TouchPhase, - #[deprecated = "Deprecated in favor of DeviceEvent::ModifiersChanged"] + #[deprecated = "Deprecated in favor of WindowEvent::ModifiersChanged"] modifiers: ModifiersState, }, @@ -267,7 +274,7 @@ pub enum WindowEvent<'a> { device_id: DeviceId, state: ElementState, button: MouseButton, - #[deprecated = "Deprecated in favor of DeviceEvent::ModifiersChanged"] + #[deprecated = "Deprecated in favor of WindowEvent::ModifiersChanged"] modifiers: ModifiersState, }, @@ -341,6 +348,7 @@ impl<'a> WindowEvent<'a> { input, is_synthetic, }), + ModifiersChanged(modifiers) => Some(ModifiersChanged(modifiers)), #[allow(deprecated)] CursorMoved { device_id, @@ -464,16 +472,6 @@ pub enum DeviceEvent { Key(KeyboardInput), - /// 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, }, @@ -502,7 +500,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"] + #[deprecated = "Deprecated in favor of WindowEvent::ModifiersChanged"] pub modifiers: ModifiersState, } diff --git a/src/platform_impl/linux/wayland/keyboard.rs b/src/platform_impl/linux/wayland/keyboard.rs index ed7aa20a..8f911888 100644 --- a/src/platform_impl/linux/wayland/keyboard.rs +++ b/src/platform_impl/linux/wayland/keyboard.rs @@ -8,9 +8,7 @@ use smithay_client_toolkit::{ reexports::client::protocol::{wl_keyboard, wl_seat}, }; -use crate::event::{ - DeviceEvent, ElementState, KeyboardInput, ModifiersState, VirtualKeyCode, WindowEvent, -}; +use crate::event::{ElementState, KeyboardInput, ModifiersState, VirtualKeyCode, WindowEvent}; pub fn init_keyboard( seat: &wl_seat::WlSeat, @@ -33,9 +31,24 @@ pub fn init_keyboard( let wid = make_wid(&surface); my_sink.send_window_event(WindowEvent::Focused(true), wid); *target.lock().unwrap() = Some(wid); + + let modifiers = *modifiers_tracker.lock().unwrap(); + + if !modifiers.is_empty() { + my_sink.send_window_event(WindowEvent::ModifiersChanged(modifiers), wid); + } } KbEvent::Leave { surface, .. } => { let wid = make_wid(&surface); + let modifiers = *modifiers_tracker.lock().unwrap(); + + if !modifiers.is_empty() { + my_sink.send_window_event( + WindowEvent::ModifiersChanged(ModifiersState::empty()), + wid, + ); + } + my_sink.send_window_event(WindowEvent::Focused(false), wid); *target.lock().unwrap() = None; } @@ -88,7 +101,9 @@ pub fn init_keyboard( *modifiers_tracker.lock().unwrap() = modifiers; - my_sink.send_device_event(DeviceEvent::ModifiersChanged(modifiers), DeviceId); + if let Some(wid) = *target.lock().unwrap() { + my_sink.send_window_event(WindowEvent::ModifiersChanged(modifiers), wid); + } } } }, diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index 424bb89c..976c17b6 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -32,6 +32,8 @@ pub(super) struct EventProcessor { // Number of touch events currently in progress pub(super) num_touch: u32, pub(super) first_touch: Option, + // Currently focused window belonging to this process + pub(super) active_window: Option, } impl EventProcessor { @@ -136,11 +138,12 @@ impl EventProcessor { if let Some(modifiers) = self.device_mod_state.update_state(&state, modifier) { - let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD); - callback(Event::DeviceEvent { - device_id, - event: DeviceEvent::ModifiersChanged(modifiers), - }); + if let Some(window_id) = self.active_window { + callback(Event::WindowEvent { + window_id: mkwid(window_id), + event: WindowEvent::ModifiersChanged(modifiers), + }); + } } } } @@ -872,44 +875,58 @@ impl EventProcessor { ffi::XI_FocusIn => { let xev: &ffi::XIFocusInEvent = unsafe { &*(xev.data as *const _) }; - let window_id = mkwid(xev.event); - wt.ime .borrow_mut() .focus(xev.event) .expect("Failed to focus input context"); - callback(Event::WindowEvent { - window_id, - event: Focused(true), - }); - let modifiers = ModifiersState::from_x11(&xev.mods); - update_modifiers!(modifiers, None); + self.device_mod_state.update_state(&modifiers, None); - // The deviceid for this event is for a keyboard instead of a pointer, - // so we have to do a little extra work. - let pointer_id = self - .devices - .borrow() - .get(&DeviceId(xev.deviceid)) - .map(|device| device.attachment) - .unwrap_or(2); + if self.active_window != Some(xev.event) { + self.active_window = Some(xev.event); - let position = PhysicalPosition::new(xev.event_x, xev.event_y); + let window_id = mkwid(xev.event); + let position = PhysicalPosition::new(xev.event_x, xev.event_y); - callback(Event::WindowEvent { - window_id, - event: CursorMoved { - device_id: mkdid(pointer_id), - position, - modifiers, - }, - }); + callback(Event::WindowEvent { + window_id, + event: Focused(true), + }); - // Issue key press events for all pressed keys - self.handle_pressed_keys(window_id, ElementState::Pressed, &mut callback); + if !modifiers.is_empty() { + callback(Event::WindowEvent { + window_id, + event: WindowEvent::ModifiersChanged(modifiers), + }); + } + + // The deviceid for this event is for a keyboard instead of a pointer, + // so we have to do a little extra work. + let pointer_id = self + .devices + .borrow() + .get(&DeviceId(xev.deviceid)) + .map(|device| device.attachment) + .unwrap_or(2); + + callback(Event::WindowEvent { + window_id, + event: CursorMoved { + device_id: mkdid(pointer_id), + position, + modifiers, + }, + }); + + // Issue key press events for all pressed keys + self.handle_pressed_keys( + window_id, + ElementState::Pressed, + &mut callback, + ); + } } ffi::XI_FocusOut => { let xev: &ffi::XIFocusOutEvent = unsafe { &*(xev.data as *const _) }; @@ -921,15 +938,26 @@ impl EventProcessor { .unfocus(xev.event) .expect("Failed to unfocus input context"); - let window_id = mkwid(xev.event); + if self.active_window.take() == Some(xev.event) { + let window_id = mkwid(xev.event); - // Issue key release events for all pressed keys - self.handle_pressed_keys(window_id, ElementState::Released, &mut callback); + // Issue key release events for all pressed keys + self.handle_pressed_keys( + window_id, + ElementState::Released, + &mut callback, + ); - callback(Event::WindowEvent { - window_id, - event: Focused(false), - }) + callback(Event::WindowEvent { + window_id, + event: WindowEvent::ModifiersChanged(ModifiersState::empty()), + }); + + callback(Event::WindowEvent { + window_id, + event: Focused(false), + }) + } } ffi::XI_TouchBegin | ffi::XI_TouchUpdate | ffi::XI_TouchEnd => { @@ -1084,10 +1112,12 @@ impl EventProcessor { let new_modifiers = self.device_mod_state.modifiers(); if modifiers != new_modifiers { - callback(Event::DeviceEvent { - device_id, - event: DeviceEvent::ModifiersChanged(new_modifiers), - }); + if let Some(window_id) = self.active_window { + callback(Event::WindowEvent { + window_id: mkwid(window_id), + event: WindowEvent::ModifiersChanged(new_modifiers), + }); + } } } } diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index f58b4207..f9e0ea56 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -225,6 +225,7 @@ impl EventLoop { device_mod_state: Default::default(), num_touch: 0, first_touch: None, + active_window: None, }; // Register for device hotplug events diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index 741ecb48..2307d3e8 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -50,13 +50,13 @@ impl Default for CursorState { } } -struct ViewState { +pub(super) struct ViewState { ns_window: id, pub cursor_state: Arc>, ime_spot: Option<(f64, f64)>, raw_characters: Option, is_key_down: bool, - modifiers: ModifiersState, + pub(super) modifiers: ModifiersState, tracking_rect: Option, } @@ -618,6 +618,19 @@ fn retrieve_keycode(event: id) -> Option { }) } +// Update `state.modifiers` if `event` has something different +fn update_potentially_stale_modifiers(state: &mut ViewState, event: id) { + let event_modifiers = event_mods(event); + if state.modifiers != event_modifiers { + state.modifiers = event_modifiers; + + AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { + window_id: WindowId(get_window_id(state.ns_window)), + event: WindowEvent::ModifiersChanged(state.modifiers), + })); + } +} + extern "C" fn key_down(this: &Object, _sel: Sel, event: id) { trace!("Triggered `keyDown`"); unsafe { @@ -633,6 +646,8 @@ extern "C" fn key_down(this: &Object, _sel: Sel, event: id) { let is_repeat = msg_send![event, isARepeat]; + update_potentially_stale_modifiers(state, event); + #[allow(deprecated)] let window_event = Event::WindowEvent { window_id, @@ -686,6 +701,8 @@ extern "C" fn key_up(this: &Object, _sel: Sel, event: id) { let scancode = get_scancode(event) as u32; let virtual_keycode = retrieve_keycode(event); + update_potentially_stale_modifiers(state, event); + #[allow(deprecated)] let window_event = Event::WindowEvent { window_id: WindowId(get_window_id(state.ns_window)), @@ -750,16 +767,18 @@ extern "C" fn flags_changed(this: &Object, _sel: Sel, event: id) { events.push_back(window_event); } + let window_id = WindowId(get_window_id(state.ns_window)); + for event in events { AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { - window_id: WindowId(get_window_id(state.ns_window)), + window_id, event, })); } - AppState::queue_event(EventWrapper::StaticEvent(Event::DeviceEvent { - device_id: DEVICE_ID, - event: DeviceEvent::ModifiersChanged(state.modifiers), + AppState::queue_event(EventWrapper::StaticEvent(Event::WindowEvent { + window_id, + event: WindowEvent::ModifiersChanged(state.modifiers), })); } trace!("Completed `flagsChanged`"); @@ -801,6 +820,8 @@ extern "C" fn cancel_operation(this: &Object, _sel: Sel, _sender: id) { let event: id = msg_send![NSApp(), currentEvent]; + update_potentially_stale_modifiers(state, event); + #[allow(deprecated)] let window_event = Event::WindowEvent { window_id: WindowId(get_window_id(state.ns_window)), @@ -826,6 +847,8 @@ fn mouse_click(this: &Object, event: id, button: MouseButton, button_state: Elem let state_ptr: *mut c_void = *this.get_ivar("winitState"); let state = &mut *(state_ptr as *mut ViewState); + update_potentially_stale_modifiers(state, event); + let window_event = Event::WindowEvent { window_id: WindowId(get_window_id(state.ns_window)), event: WindowEvent::MouseInput { @@ -889,6 +912,8 @@ fn mouse_motion(this: &Object, event: id) { let y = view_rect.size.height as f64 - view_point.y as f64; let logical_position = LogicalPosition::new(x, y); + update_potentially_stale_modifiers(state, event); + let window_event = Event::WindowEvent { window_id: WindowId(get_window_id(state.ns_window)), event: WindowEvent::CursorMoved { @@ -918,7 +943,7 @@ extern "C" fn other_mouse_dragged(this: &Object, _sel: Sel, event: id) { mouse_motion(this, event); } -extern "C" fn mouse_entered(this: &Object, _sel: Sel, event: id) { +extern "C" fn mouse_entered(this: &Object, _sel: Sel, _event: id) { trace!("Triggered `mouseEntered`"); unsafe { let state_ptr: *mut c_void = *this.get_ivar("winitState"); @@ -932,7 +957,6 @@ extern "C" fn mouse_entered(this: &Object, _sel: Sel, event: id) { }; AppState::queue_event(EventWrapper::StaticEvent(enter_event)); - mouse_motion(this, event); } trace!("Completed `mouseEntered`"); } @@ -982,6 +1006,8 @@ extern "C" fn scroll_wheel(this: &Object, _sel: Sel, event: id) { let state_ptr: *mut c_void = *this.get_ivar("winitState"); let state = &mut *(state_ptr as *mut ViewState); + update_potentially_stale_modifiers(state, event); + let window_event = Event::WindowEvent { window_id: WindowId(get_window_id(state.ns_window)), event: WindowEvent::MouseWheel { diff --git a/src/platform_impl/macos/window_delegate.rs b/src/platform_impl/macos/window_delegate.rs index f04d7c73..a89e99fc 100644 --- a/src/platform_impl/macos/window_delegate.rs +++ b/src/platform_impl/macos/window_delegate.rs @@ -16,11 +16,12 @@ use objc::{ use crate::{ dpi::LogicalSize, - event::{Event, WindowEvent}, + event::{Event, ModifiersState, WindowEvent}, platform_impl::platform::{ app_state::AppState, event::{EventProxy, EventWrapper}, util::{self, IdRef}, + view::ViewState, window::{get_window_id, UnownedWindow}, }, window::{Fullscreen, WindowId}, @@ -319,6 +320,29 @@ extern "C" fn window_did_become_key(this: &Object, _: Sel, _: id) { extern "C" fn window_did_resign_key(this: &Object, _: Sel, _: id) { trace!("Triggered `windowDidResignKey:`"); with_state(this, |state| { + // It happens rather often, e.g. when the user is Cmd+Tabbing, that the + // NSWindowDelegate will receive a didResignKey event despite no event + // being received when the modifiers are released. This is because + // flagsChanged events are received by the NSView instead of the + // NSWindowDelegate, and as a result a tracked modifiers state can quite + // easily fall out of synchrony with reality. This requires us to emit + // a synthetic ModifiersChanged event when we lose focus. + // + // Here we (very unsafely) acquire the winitState (a ViewState) from the + // Object referenced by state.ns_view (an IdRef, which is dereferenced + // to an id) + let view_state: &mut ViewState = unsafe { + let ns_view: &Object = (*state.ns_view).as_ref().expect("failed to deref"); + let state_ptr: *mut c_void = *ns_view.get_ivar("winitState"); + &mut *(state_ptr as *mut ViewState) + }; + + // Both update the state and emit a ModifiersChanged event. + if !view_state.modifiers.is_empty() { + view_state.modifiers = ModifiersState::empty(); + state.emit_event(WindowEvent::ModifiersChanged(view_state.modifiers)); + } + state.emit_event(WindowEvent::Focused(false)); }); trace!("Completed `windowDidResignKey:`"); diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 38181089..a443a6ce 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -50,9 +50,7 @@ use crate::{ dark_mode::try_dark_mode, dpi::{become_dpi_aware, dpi_to_scale_factor, enable_non_client_dpi_scaling}, drop_handler::FileDropHandler, - event::{ - self, handle_extended_keys, process_key_params, vkey_to_winit_vkey, ModifiersStateSide, - }, + event::{self, handle_extended_keys, process_key_params, vkey_to_winit_vkey}, monitor, raw_input, util, window_state::{CursorFlags, WindowFlags, WindowState}, wrap_device_id, WindowId, DEVICE_ID, @@ -108,7 +106,6 @@ impl SubclassInput { struct ThreadMsgTargetSubclassInput { event_loop_runner: EventLoopRunnerShared, user_event_receiver: Receiver, - modifiers_state: ModifiersStateSide, } impl ThreadMsgTargetSubclassInput { @@ -553,7 +550,6 @@ 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( @@ -606,6 +602,24 @@ fn normalize_pointer_pressure(pressure: u32) -> Option { } } +/// Emit a `ModifiersChanged` event whenever modifiers have changed. +fn update_modifiers(window: HWND, subclass_input: &SubclassInput) { + use crate::event::WindowEvent::ModifiersChanged; + + let modifiers = event::get_key_mods(); + let mut window_state = subclass_input.window_state.lock(); + if window_state.modifiers_state != modifiers { + window_state.modifiers_state = modifiers; + + unsafe { + subclass_input.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window)), + event: ModifiersChanged(modifiers), + }); + } + } +} + /// Any window whose callback is configured to this function will have its events propagated /// through the events loop of the thread the window was created in. // @@ -866,6 +880,8 @@ unsafe extern "system" fn public_window_callback( w.mouse.last_position = Some(position); } if cursor_moved { + update_modifiers(window, subclass_input); + subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: CursorMoved { @@ -905,6 +921,8 @@ unsafe extern "system" fn public_window_callback( let value = value as i32; let value = value as f32 / winuser::WHEEL_DELTA as f32; + update_modifiers(window, subclass_input); + subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: WindowEvent::MouseWheel { @@ -925,6 +943,8 @@ unsafe extern "system" fn public_window_callback( let value = value as i32; let value = value as f32 / winuser::WHEEL_DELTA as f32; + update_modifiers(window, subclass_input); + subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: WindowEvent::MouseWheel { @@ -944,6 +964,8 @@ unsafe extern "system" fn public_window_callback( commctrl::DefSubclassProc(window, msg, wparam, lparam) } else { if let Some((scancode, vkey)) = process_key_params(wparam, lparam) { + update_modifiers(window, subclass_input); + #[allow(deprecated)] subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), @@ -974,6 +996,8 @@ unsafe extern "system" fn public_window_callback( winuser::WM_KEYUP | winuser::WM_SYSKEYUP => { use crate::event::ElementState::Released; if let Some((scancode, vkey)) = process_key_params(wparam, lparam) { + update_modifiers(window, subclass_input); + #[allow(deprecated)] subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), @@ -997,6 +1021,8 @@ unsafe extern "system" fn public_window_callback( capture_mouse(window, &mut *subclass_input.window_state.lock()); + update_modifiers(window, subclass_input); + subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { @@ -1016,6 +1042,8 @@ unsafe extern "system" fn public_window_callback( release_mouse(&mut *subclass_input.window_state.lock()); + update_modifiers(window, subclass_input); + subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { @@ -1035,6 +1063,8 @@ unsafe extern "system" fn public_window_callback( capture_mouse(window, &mut *subclass_input.window_state.lock()); + update_modifiers(window, subclass_input); + subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { @@ -1054,6 +1084,8 @@ unsafe extern "system" fn public_window_callback( release_mouse(&mut *subclass_input.window_state.lock()); + update_modifiers(window, subclass_input); + subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { @@ -1073,6 +1105,8 @@ unsafe extern "system" fn public_window_callback( capture_mouse(window, &mut *subclass_input.window_state.lock()); + update_modifiers(window, subclass_input); + subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { @@ -1092,6 +1126,8 @@ unsafe extern "system" fn public_window_callback( release_mouse(&mut *subclass_input.window_state.lock()); + update_modifiers(window, subclass_input); + subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { @@ -1112,6 +1148,8 @@ unsafe extern "system" fn public_window_callback( capture_mouse(window, &mut *subclass_input.window_state.lock()); + update_modifiers(window, subclass_input); + subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { @@ -1132,6 +1170,8 @@ unsafe extern "system" fn public_window_callback( release_mouse(&mut *subclass_input.window_state.lock()); + update_modifiers(window, subclass_input); + subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: MouseInput { @@ -1340,6 +1380,8 @@ unsafe extern "system" fn public_window_callback( winuser::MapVirtualKeyA(windows_keycode as _, winuser::MAPVK_VK_TO_VSC); let virtual_keycode = event::vkey_to_winit_vkey(windows_keycode); + update_modifiers(window, subclass_input); + #[allow(deprecated)] subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), @@ -1365,7 +1407,11 @@ unsafe extern "system" fn public_window_callback( } winuser::WM_KILLFOCUS => { - use crate::event::{ElementState::Released, WindowEvent::Focused}; + 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); @@ -1387,6 +1433,12 @@ unsafe extern "system" fn public_window_callback( }) } + subclass_input.window_state.lock().modifiers_state = ModifiersState::empty(); + subclass_input.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window)), + event: ModifiersChanged(ModifiersState::empty()), + }); + subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: Focused(false), @@ -1795,10 +1847,9 @@ unsafe extern "system" fn thread_event_target_callback( winuser::WM_INPUT => { use crate::event::{ - DeviceEvent::{Button, Key, ModifiersChanged, Motion, MouseMotion, MouseWheel}, + DeviceEvent::{Button, Key, Motion, MouseMotion, MouseWheel}, ElementState::{Pressed, Released}, MouseScrollDelta::LineDelta, - VirtualKeyCode, }; if let Some(data) = raw_input::get_raw_input_data(lparam as _) { @@ -1877,48 +1928,6 @@ unsafe extern "system" fn thread_event_target_callback( { 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), - }); - } - #[allow(deprecated)] subclass_input.send_event(Event::DeviceEvent { device_id, @@ -1926,7 +1935,7 @@ unsafe extern "system" fn thread_event_target_callback( scancode, state, virtual_keycode, - modifiers: new_modifiers_state, + modifiers: event::get_key_mods(), }), }); } diff --git a/src/platform_impl/windows/window_state.rs b/src/platform_impl/windows/window_state.rs index 76485f46..4be0c796 100644 --- a/src/platform_impl/windows/window_state.rs +++ b/src/platform_impl/windows/window_state.rs @@ -1,5 +1,6 @@ use crate::{ dpi::{PhysicalPosition, Size}, + event::ModifiersState, platform_impl::platform::{event_loop, icon::WinIcon, util}, window::{CursorIcon, Fullscreen, WindowAttributes}, }; @@ -28,6 +29,7 @@ pub struct WindowState { pub saved_window: Option, pub scale_factor: f64, + pub modifiers_state: ModifiersState, pub fullscreen: Option, /// Used to supress duplicate redraw attempts when calling `request_redraw` multiple /// times in `MainEventsCleared`. @@ -119,6 +121,7 @@ impl WindowState { saved_window: None, scale_factor, + modifiers_state: ModifiersState::default(), fullscreen: None, queued_out_of_band_redraw: false, is_dark_mode,