Skip to content

Commit

Permalink
Make winit focus take activity into account on Windows
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 Jan 22, 2022
1 parent 001fb7e commit ef9f951
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 64 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Unreleased

- **Breaking:** Rename the `Exit` variant of `ControlFlow` to `ExitWithCode`, which holds a value to control the exit code after running. Add an `Exit` constant which aliases to `ExitWithCode(0)` instead to avoid major breakage. This shouldn't affect most existing programs.
- Fix focus events being sent to inactive windows on Windows.

# 0.26.1 (2022-01-05)

Expand Down
152 changes: 88 additions & 64 deletions src/platform_impl/windows/event_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use std::{
use winapi::{
shared::{
basetsd::LONG_PTR,
minwindef::{BOOL, DWORD, HIWORD, INT, LOWORD, LPARAM, LRESULT, UINT, WORD, WPARAM},
minwindef::{BOOL, DWORD, HIWORD, INT, LOWORD, LPARAM, LRESULT, TRUE, UINT, WORD, WPARAM},
windef::{HWND, POINT, RECT},
windowsx, winerror,
},
Expand Down Expand Up @@ -792,6 +792,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 = 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,
},
})
}

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 = 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,
},
})
}

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),
});
}

#[cfg(target_arch = "x86_64")]
pub(crate) type WindowLongPtr = LONG_PTR;
#[cfg(target_arch = "x86")]
Expand Down Expand Up @@ -1647,76 +1715,32 @@ unsafe fn public_window_callback_inner<T: 'static>(
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,
},
})
winuser::WM_NCACTIVATE => {
let is_active = wparam == TRUE as _;
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);
}
}
winuser::DefWindowProcW(window, msg, wparam, lparam)
}

userdata.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: Focused(true),
});

winuser::WM_SETFOCUS => {
let active_focus_changed = userdata.window_state.lock().set_focused(true);
if active_focus_changed {
gain_active_focus(window, userdata);
}
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,
},
})
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 @@ -35,6 +35,10 @@ pub struct WindowState {
pub preferred_theme: Option<Theme>,
pub high_surrogate: Option<u16>,
pub window_flags: WindowFlags,

// Used by WM_NCACTIVATE, WM_SETFOCUS and WM_KILLFOCUS
pub is_active: bool,
pub is_focused: bool,
}

#[derive(Clone)]
Expand Down Expand Up @@ -123,6 +127,9 @@ impl WindowState {
preferred_theme,
high_surrogate: None,
window_flags: WindowFlags::empty(),

is_active: false,
is_focused: false,
}
}

Expand All @@ -148,6 +155,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 ef9f951

Please sign in to comment.