fix(app): carry keyboard modifiers on mouse and wheel events - #205
Merged
Conversation
Every MouseEvent and WheelEvent the bridge builds is given event.ModNone, so
MouseEvent.Modifiers() is always empty. ⌥click, ⇧click, ⌃click and
shift-to-extend-a-selection cannot be expressed by an application at all.
The platform's mouse callbacks report a button and a position and nothing else,
so the modifier state has to come from the key events, which do carry it. The
bridge now tracks it there and stamps it onto the mouse and wheel events it
builds.
Two details that are the whole difficulty:
- A key event reports the modifiers held BEFORE it, so the event that presses
Alt reports no Alt. Holding a modifier and clicking, with no other key in
between, is exactly the gesture — so the key's own bit is folded in on press
and taken out on release.
- Focus loss clears the state. A modifier released while another window had
focus was never seen here, and believing it is still held turns the next
ordinary click into a modified one.
Found in a terminal emulator on this toolkit, where ⌥click reveals a masked
value: the click arrived with no modifier, and the application had to
reimplement this tracking on top of the key events to tell the two clicks apart.
Doing it in the bridge means every application gets it instead of each one
rediscovering it.
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
kolkov
approved these changes
Aug 3, 2026
kolkov
left a comment
Contributor
There was a problem hiding this comment.
Validated: matches winit/SDL3 Model B (tracked modifier state stamped onto pointer events). Focus-loss clearing matches winit exactly. modifierForKey OR-folding is safe across all 4 platforms (X11, Windows, Wayland, macOS) due to idempotency. Thread-safe by design (single-threaded event loop). Three tests cover held/released/focus-loss cases.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Every
MouseEventandWheelEventthe bridge builds is givenevent.ModNone, soMouseEvent.Modifiers()is always empty. ⌥click, ⇧click, ⌃click and shift-to-extend-a-selection cannot be expressed by an application at all.The platform's mouse callbacks report a button and a position and nothing else, so the modifier state has to come from the key events, which do carry it. The bridge now tracks it there and stamps it onto the mouse and wheel events it builds.
The two details that are the whole difficulty
Found by
A terminal emulator on this toolkit, where ⌥click reveals a masked secret. The click arrived with no modifier, and the app had to reimplement this tracking on top of the key events to tell an ⌥click from a plain one. Doing it in the bridge means every application gets it rather than each one rediscovering it.
Tests
Three, covering held-over-click, cleared-on-release and cleared-on-focus-loss. The first fails on
main(click carried None, want Alt). Full suite green (61 packages),golangci-lintclean.A deeper fix would put the modifiers on
gpucontext.EventSource's mouse callbacks — X11 already carries them in the button event — but that changes an interface across repos. This is the non-breaking version and is correct for every current platform backend.