From 1091a8ba1adb6533cb3747d49cf4c08303cdb92a Mon Sep 17 00:00:00 2001 From: Steve Wooster Date: Fri, 15 Jul 2022 01:27:27 -0700 Subject: [PATCH] Make winit focus take activity into account on Windows (#2159) winit's notion of "focus" is very simple; you're either focused or not. However, Windows has both notions of focused window and active window and paying attention only to WM_SETFOCUS/WM_KILLFOCUS can cause a window to believe the user is interacting with it when they're not. (this manifests when a user switches to another application between when a winit application starts and it creates its first window) --- CHANGELOG.md | 1 + src/platform_impl/windows/event_loop.rs | 150 +++++++++++++--------- src/platform_impl/windows/window_state.rs | 25 ++++ 3 files changed, 114 insertions(+), 62 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dbc289efee..5e412377c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -71,6 +71,7 @@ And please only add new entries to the top of this list, right below the `# Unre - Added `MonitorHandle::refresh_rate_millihertz` to get monitor's refresh rate. - **Breaking**, Replaced `VideoMode::refresh_rate` with `VideoMode::refresh_rate_millihertz` providing better precision. - On Web, add `with_prevent_default` and `with_focusable` to `WindowBuilderExtWebSys` to control whether events should be propagated. +- On Windows, fix focus events being sent to inactive windows. # 0.26.1 (2022-01-05) diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index d0a857436b..870f323d96 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -61,7 +61,7 @@ use windows_sys::Win32::{ WM_GETMINMAXINFO, WM_IME_COMPOSITION, WM_IME_ENDCOMPOSITION, WM_IME_SETCONTEXT, WM_IME_STARTCOMPOSITION, WM_INPUT, WM_INPUT_DEVICE_CHANGE, WM_KEYDOWN, WM_KEYUP, WM_KILLFOCUS, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN, WM_MBUTTONUP, - WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCCREATE, WM_NCDESTROY, + WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCACTIVATE, WM_NCCREATE, WM_NCDESTROY, WM_NCLBUTTONDOWN, WM_PAINT, WM_POINTERDOWN, WM_POINTERUP, WM_POINTERUPDATE, WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SETCURSOR, WM_SETFOCUS, WM_SETTINGCHANGE, WM_SIZE, WM_SYSCHAR, WM_SYSCOMMAND, WM_SYSKEYDOWN, WM_SYSKEYUP, WM_TOUCH, WM_WINDOWPOSCHANGED, @@ -795,6 +795,74 @@ fn update_modifiers(window: HWND, userdata: &WindowData) { } } +unsafe fn gain_active_focus(window: HWND, userdata: &WindowData) { + use crate::event::{ElementState::Released, WindowEvent::Focused}; + for windows_keycode in event::get_pressed_keys() { + let scancode = MapVirtualKeyA(windows_keycode as u32, MAPVK_VK_TO_VSC); + let virtual_keycode = event::vkey_to_winit_vkey(windows_keycode); + + update_modifiers(window, userdata); + + #[allow(deprecated)] + userdata.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window)), + event: WindowEvent::KeyboardInput { + device_id: DEVICE_ID, + input: KeyboardInput { + scancode, + virtual_keycode, + state: Released, + modifiers: event::get_key_mods(), + }, + is_synthetic: true, + }, + }) + } + + userdata.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window)), + event: Focused(true), + }); +} + +unsafe fn lose_active_focus(window: HWND, userdata: &WindowData) { + use crate::event::{ + ElementState::Released, + ModifiersState, + WindowEvent::{Focused, ModifiersChanged}, + }; + for windows_keycode in event::get_pressed_keys() { + let scancode = MapVirtualKeyA(windows_keycode as u32, MAPVK_VK_TO_VSC); + let virtual_keycode = event::vkey_to_winit_vkey(windows_keycode); + + #[allow(deprecated)] + userdata.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window)), + event: WindowEvent::KeyboardInput { + device_id: DEVICE_ID, + input: KeyboardInput { + scancode, + virtual_keycode, + state: Released, + modifiers: event::get_key_mods(), + }, + is_synthetic: true, + }, + }) + } + + userdata.window_state.lock().modifiers_state = ModifiersState::empty(); + userdata.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window)), + event: ModifiersChanged(ModifiersState::empty()), + }); + + userdata.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window)), + event: Focused(false), + }); +} + /// 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. // @@ -1770,74 +1838,32 @@ unsafe fn public_window_callback_inner( 0 } - WM_SETFOCUS => { - use crate::event::{ElementState::Released, WindowEvent::Focused}; - for windows_keycode in event::get_pressed_keys() { - let scancode = MapVirtualKeyA(windows_keycode as u32, MAPVK_VK_TO_VSC); - let virtual_keycode = event::vkey_to_winit_vkey(windows_keycode); - - update_modifiers(window, userdata); - - #[allow(deprecated)] - userdata.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window)), - event: WindowEvent::KeyboardInput { - device_id: DEVICE_ID, - input: KeyboardInput { - scancode, - virtual_keycode, - state: Released, - modifiers: event::get_key_mods(), - }, - is_synthetic: true, - }, - }) + WM_NCACTIVATE => { + let is_active = wparam == 1; + let active_focus_changed = userdata.window_state.lock().set_active(is_active); + if active_focus_changed { + if is_active { + gain_active_focus(window, userdata); + } else { + lose_active_focus(window, userdata); + } } + DefWindowProcW(window, msg, wparam, lparam) + } - userdata.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window)), - event: Focused(true), - }); - + WM_SETFOCUS => { + let active_focus_changed = userdata.window_state.lock().set_focused(true); + if active_focus_changed { + gain_active_focus(window, userdata); + } 0 } WM_KILLFOCUS => { - use crate::event::{ - ElementState::Released, - ModifiersState, - WindowEvent::{Focused, ModifiersChanged}, - }; - for windows_keycode in event::get_pressed_keys() { - let scancode = MapVirtualKeyA(windows_keycode as u32, MAPVK_VK_TO_VSC); - let virtual_keycode = event::vkey_to_winit_vkey(windows_keycode); - - #[allow(deprecated)] - userdata.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window)), - event: WindowEvent::KeyboardInput { - device_id: DEVICE_ID, - input: KeyboardInput { - scancode, - virtual_keycode, - state: Released, - modifiers: event::get_key_mods(), - }, - is_synthetic: true, - }, - }) + let active_focus_changed = userdata.window_state.lock().set_focused(false); + if active_focus_changed { + lose_active_focus(window, userdata); } - - userdata.window_state.lock().modifiers_state = ModifiersState::empty(); - userdata.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window)), - event: ModifiersChanged(ModifiersState::empty()), - }); - - userdata.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window)), - event: Focused(false), - }); 0 } diff --git a/src/platform_impl/windows/window_state.rs b/src/platform_impl/windows/window_state.rs index 744dca0a18..35a8f0d51e 100644 --- a/src/platform_impl/windows/window_state.rs +++ b/src/platform_impl/windows/window_state.rs @@ -45,6 +45,10 @@ pub struct WindowState { pub ime_state: ImeState, pub ime_allowed: bool, + + // Used by WM_NCACTIVATE, WM_SETFOCUS and WM_KILLFOCUS + pub is_active: bool, + pub is_focused: bool, } #[derive(Clone)] @@ -145,6 +149,9 @@ impl WindowState { ime_state: ImeState::Disabled, ime_allowed: false, + + is_active: false, + is_focused: false, } } @@ -170,6 +177,24 @@ impl WindowState { { f(&mut self.window_flags); } + + pub fn has_active_focus(&self) -> bool { + self.is_active && self.is_focused + } + + // Updates is_active and returns whether active-focus state has changed + pub fn set_active(&mut self, is_active: bool) -> bool { + let old = self.has_active_focus(); + self.is_active = is_active; + old != self.has_active_focus() + } + + // Updates is_focused and returns whether active-focus state has changed + pub fn set_focused(&mut self, is_focused: bool) -> bool { + let old = self.has_active_focus(); + self.is_focused = is_focused; + old != self.has_active_focus() + } } impl MouseProperties {