Skip to content

AppKit: Synthesize per-key modifier events on focus changes#4552

Open
OlivierJG wants to merge 1 commit intorust-windowing:masterfrom
OlivierJG:master
Open

AppKit: Synthesize per-key modifier events on focus changes#4552
OlivierJG wants to merge 1 commit intorust-windowing:masterfrom
OlivierJG:master

Conversation

@OlivierJG
Copy link
Copy Markdown

On focus loss, emit synthetic KeyboardInput releases for every tracked modifier key before clearing state — previously only ModifiersChanged was emitted, leaving downstream consumers with stale per-key state.

On focus gain, query CGEventSourceFlagsState for hardware truth and emit differential press/release events to synchronize. This brings macOS in line with the Windows (WM_SETFOCUS/WM_KILLFOCUS) and X11 (XI_FocusIn/XI_FocusOut) backends.

See #1272

  • [x ] Tested on all platforms changed
  • [ x] Added an entry to the changelog module if knowledge of this change could be valuable to users
  • [ x] Updated documentation to reflect any user-facing changes, including notes of platform-specific behavior
  • [ x] Created or updated an example program if it would help users understand this functionality

On focus loss, emit synthetic KeyboardInput releases for every
tracked modifier key before clearing state — previously only
ModifiersChanged was emitted, leaving downstream consumers with
stale per-key state.

On focus gain, query CGEventSourceFlagsState for hardware truth
and emit differential press/release events to synchronize.  This
brings macOS in line with the Windows (WM_SETFOCUS/WM_KILLFOCUS)
and X11 (XI_FocusIn/XI_FocusOut) backends.

See rust-windowing#1272
@OlivierJG OlivierJG requested a review from madsmtm as a code owner March 30, 2026 18:14
@kchibisov
Copy link
Copy Markdown
Member

To be honest with you, I want this API to be removed from the keyboard events in a form that we use it now. This is just too error prone, you should never act on the keys that were pressed before focusing your application, and most folks don't distinguish synthetic and not synthetic + they are not cross platform in a sense we use.

If you really need such API, I'd advocate to move this to a separate Event all together, and not spread into each backend.

Generally, on focus lost, clients must assume that all the keys are lifted, on re-focus, some may have been latched, but they shouldn't start any repeat, etc or action, it's just bad UX all along, unless you specifically need tracking for some reason, and for such case dedicated event is better.

@OlivierJG
Copy link
Copy Markdown
Author

Ok, it looks like bevy (the intended target of the fix) moved away from synthetic events anyhow:
bevyengine/bevy@b054d11

I do have a case where bevy mods get stuck on OSX, but it (currently) looks solvable without winit changes

@OlivierJG
Copy link
Copy Markdown
Author

Update on findings: I think this isn't solvable within Bevy on OSX with the current winit state.

The key constraint that Bevy has is that it wants to provide the user with a consistent ButtonInput that allows any window to query whether any key (modifier or not) is pressed. Bevy maintains this state by updating on events from winit (currently, ignoring synthetic).

There are two types of event dropping that occur on OSX when closing a window:

  1. Bevy has a bug in which winit will send events to a window that no longer exists, and it will discard those events instead of updating the global state -> stale key state table.
  2. On OSX, performClose (Cmd+W or titlebar button) runs a nested eventloop synchronously within the current event dispatch. This nested runloop processes the window destruction (delegate callbacks, dealloc) before returning control to the outer event loop. Any events that were queued after the close are discarded by AppKit.

The first issue can be fixed in Bevy, but the only way to resolve the second issue is to expose API to query key state or to query key state inside winit and forge some type of event that would allow Bevy to update the key state table.

Note that, for Bevy, a key constraint is that switching focus between bevy windows should not affect the key table validity, so clearing the key state as it might do when the whole app loses/gains focus isn't an option.

Do you have any opinion on whether winit wants to expose the necessary API/Event for this? Preferences for how it should be exposed if so?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants