Skip to content

Commit

Permalink
Make winit focus take activity into account on Windows (#2159)
Browse files Browse the repository at this point in the history
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)
  • Loading branch information
swooster committed Jul 15, 2022
1 parent 9116b6c commit 1091a8b
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 62 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
150 changes: 88 additions & 62 deletions src/platform_impl/windows/event_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -795,6 +795,74 @@ fn update_modifiers<T>(window: HWND, userdata: &WindowData<T>) {
}
}

unsafe fn gain_active_focus<T>(window: HWND, userdata: &WindowData<T>) {
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<T>(window: HWND, userdata: &WindowData<T>) {
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.
//
Expand Down Expand Up @@ -1770,74 +1838,32 @@ unsafe fn public_window_callback_inner<T: 'static>(
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
}

Expand Down
25 changes: 25 additions & 0 deletions src/platform_impl/windows/window_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -145,6 +149,9 @@ impl WindowState {

ime_state: ImeState::Disabled,
ime_allowed: false,

is_active: false,
is_focused: false,
}
}

Expand All @@ -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 {
Expand Down

0 comments on commit 1091a8b

Please sign in to comment.