mirror of
https://github.com/games-on-whales/gst-wayland-display.git
synced 2026-10-15 12:51:57 +00:00
[PR #44] [MERGED] fix(compositor): pointer focus for newly mapped windows — bbox refresh, map-time enter, and a get_pointer-race guard #45
Labels
No labels
enhancement
enhancement
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
games-on-whales/gst-wayland-display#45
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
📋 Pull Request Information
Original PR: https://github.com/games-on-whales/gst-wayland-display/pull/44
Author: @salty2011
Created: 7/26/2026
Status: ✅ Merged
Merged: 9/7/2026
Merged by: @ABeltramo
Base:
stable← Head:fix/initial-pointer-focus📝 Commits (3)
6dd6719fix(compositor): refresh a newly mapped window's bbox so pointer focus resolvesc327a99fix(compositor): resolve pointer focus for a newly mapped toplevel6db7979fix(compositor): re-emit wl_pointer.enter for clients that lose the map race📊 Changes
5 files changed (+323 additions, -2 deletions)
View changed files
📝
wayland-display-core/src/comp/input.rs(+56 -2)📝
wayland-display-core/src/comp/mod.rs(+5 -0)📝
wayland-display-core/src/tests/client.rs(+16 -0)📝
wayland-display-core/src/tests/test_pointer.rs(+209 -0)📝
wayland-display-core/src/wayland/handlers/compositor.rs(+37 -0)📄 Description
Window::bbox()stays(0,0)untilon_commit()recomputes it from the surface tree, and the per-commiton_commit()at the top ofcommit()only runs for surfaces already in the space. A client whose root toplevel is mapped by this commit and never re-committed afterwards therefore keeps an empty bbox forever, soSpace::element_under()never resolves it andwl_pointerfocus is never assigned. Keyboard focus is assigned directly at map, which is what makes this awkward to diagnose: typing works, the pointer is dead.This PR originally carried only the bbox refresh, and its description said of the map-time-enter follow-up: "That is a protocol-visible behaviour change rather than a bug fix, so it seemed wrong to bundle it in." We have since shipped that follow-up in production, hit the failure mode it introduces, and root-caused it — so the PR now carries the complete, hardened change as two further commits, each independently green including the test updates it necessitates. Field evidence below.
The three commits
on_commit()aftermap_element()(unchanged from the original PR): recomputes the bbox from the mapping commit's buffer soelement_under()can resolve the window. The in-treefixture.rsworkaround ("I've spent hours trying to debug why the window size was kept at (0,0)") is this same call done by hand.Synthetic zero-delta
pointer_motion()at map: deliverswl_pointer.enterto the newly mapped toplevel immediately and runs constraint activation, so a pointer lock/confine requested before the surface became mappable (nested gamescope with--force-grab-cursor) is applied the moment the surface is focusable. Pointer focus thereby follows the newest toplevel exactly like keyboard focus.move_mouseis updated in the same commit for the earlier enter — the behaviour change the original description predicted — by draining the map-timeEnter; its motion-delivery assertions are unchanged.An edge-triggered refocus guarding the race that (2) introduces. smithay emits
enteronly on a focus transition, broadcasts it to thewl_pointerresources existing at that instant, and records the new focus unconditionally (GetPointersends no retroactive enter). So when (2) resolves focus at map time, a client that callswl_seat.get_pointerafter that instant loses permanently: the enter reached zero resources, and every subsequent motion takes smithay's same-target arm —wl_pointer.motiononly, no enter, forever. The client never learns it has pointer focus and discards all pointer input.This is not theoretical. Rootful Xwayland (
Xwayland :0, one surface for the whole X screen, mapped exactly once) loses the race deterministically: the cursor moves (compositor-drawn) but every XButtonPresslands at one frozen stale coordinate — "mouse moves, nothing responds to clicks" on an XFCE-in-Xwayland desktop. gamescope loses it intermittently and self-heals on its next surface re-map, which presents as "input dead at launch, starts working later".The fix: the map handler arms
pending_pointer_refocusafter its synthetic motion (order matters — that motion is itself apointer_motion()call and would consume the flag inside the race window). The next real motion, guarded on surface-present (else retained), pointer-not-grabbed (else retained — a forced leave mid-click-drag would break the drag), and no-active-constraint (else cleared — a constraint can only activate on an entered surface, so the client demonstrably has its enter), forces the focus toNoneso the following motion takes the(focus, None)arm and re-emits a genuineenter. The re-emitted enter's serial is allocated after the forced leave's so it is the newer one, since clients echo enter serials (set_cursor, constraint activation).Two tests assert a real fixture client receives the re-emitted
Enter, and that the flag survives a motion with nothing under the pointer. (The fixture client binds its pointer at seat-bind time so it cannot lose the race naturally; the tests arm the flag directly.)Field verification (Quasar, production fork)
Debugging chain on a live deployment, each hop instrumented: evdev capture proved motion+buttons reached the compositor's input devices;
WAYLAND_DEBUG=1inside the client container showedwl_pointer.motionandwl_pointer.buttondelivered with zerowl_pointer.enterin the whole trace (whilewl_keyboard.enterarrived fine);xinput test-xi2 --rootinside Xwayland showedMotion=0, RawMotion=0, ButtonPress frozen at one coordinate. With (3) applied: XMotionevents flow and ButtonPress tracks the cursor; Steam/gamescope validated over repeated cold starts with no dead-input window; both confirmed by a human tester.The deeper root cause — no retroactive
enterfor awl_pointercreated while its surface already holds focus — arguably belongs in smithay'sGetPointerhandling; we intend to raise it there. This PR's (3) is the compositor-side guard that makes map-time focus safe regardless.Verification
cargo test -p wayland-display-core --libin a container with the build deps, at every commit (the series is bisect-safe — each commit carries the test updates it necessitates):stable+ the bbox commit)utils::allocator::tests::test_dmabuf— needs a real render node, fails in a deviceless container regardless)move_mousegreen under the new behaviourpending_refocus_re_emits_enterasserts a real fixture client receives the re-emittedEnter;..._retained_without_surfaceand..._retained_while_grabbedcover the retain guards)cargo fmt --all --checkclean and clippy zero-new at both commits.🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.