Wayland: seal xdg-activation tokens with the last pointer-press serial#4612
Wayland: seal xdg-activation tokens with the last pointer-press serial#4612Le-Syl21 wants to merge 2 commits into
Conversation
Tokens issued by Window::request_activation_token and the user-attention path were built with set_surface + commit but never set_serial. Compositors enforcing focus-stealing prevention (mutter, kwin, niri) validate the token's serial against the pointer's grab serial and refuse the ensuing activation when it is absent — the launched application's window opens unfocused, defeating the purpose of handing it an activation token. Record the (seat, serial) of the last pointer button *press* on the window's WindowState, and seal both token issuers with it before commit. Press only: compositors update the pointer grab serial exclusively on button press (mutter meta-wayland-pointer.c), and UI toolkits fire click actions on release, so recording the release serial too would always overwrite the one the compositor recognizes. Verified on GNOME 46 / mutter Wayland: a launcher app requesting a token on click and passing it via XDG_ACTIVATION_TOKEN to a spawned GTK4 app now transfers keyboard focus; before the patch the same flow produced a token that mutter discarded.
|
Note on CI: the |
kchibisov
left a comment
There was a problem hiding this comment.
there's already latest button serial stored on wl pointer, can just use it.
Per review — reuse the pointer-side storage rather than introducing window-level state. The existing `latest_button_serial` can't be used directly: it is updated on both press and release (both winit's copy and sctk's), while compositors validate activation serials against the pointer's grab serial, which only changes on button press (mutter `meta-wayland-pointer.c::handle_button_event` updates `grab_serial` solely on CLUTTER_BUTTON_PRESS). Toolkits fire click actions on release, so at request time the combined serial always holds the release value — one the compositor refuses. Add `latest_press_serial` next to it on `WinitPointerDataInner`, and read it from the token issuers through the same `apply_on_pointer` pattern `drag_window` / `show_window_menu` use.
|
Thanks for the review! Reworked in 5660929 — the serial now lives on One nuance on reusing the existing implicit_grab = (clutter_event_type (event) == CLUTTER_BUTTON_PRESS) && (pointer->button_count == 1);
...
if (implicit_grab)
pointer->grab_serial = wl_display_get_serial (seat->wl_display);and |
kchibisov
left a comment
There was a problem hiding this comment.
Not in the mood to talk with robot.
Problem
Window::request_activation_token()(and the xdg-activation path ofrequest_user_attention) builds itsxdg_activation_token_v1request withset_surface+commit, but never callsset_serial.The protocol marks
set_serialas optional, but adds:In practice, compositors enforcing focus-stealing prevention (mutter, KWin, niri in strict mode) validate the token's serial against the pointer's grab serial and silently refuse the ensuing
activate()when it's absent. On mutter the minted token string ends in_TIME0and the activation degrades to demands-attention at best. Net effect: an app that requests a token on user click and hands it to a child process viaXDG_ACTIVATION_TOKENsees the child's window open without focus — defeating the exact purpose of the API.This mirrors what GTK4/GDK and Qt do internally (both seal launch tokens with the serial of the triggering input event), and is the missing piece behind several "launched app opens behind the launcher on GNOME Wayland" reports across the ecosystem (e.g. wezterm/wezterm#3619, ghostty-org/ghostty#5812 discuss the same compositor behavior).
Fix
(WlSeat, serial)of the last pointer button press on the window'sWindowState(recorded in the pointer handler, which already locks that state).request_activation_token,request_user_attention) with it viaset_serial(serial, &seat)beforecommit.Why press only: compositors update the pointer grab serial exclusively on button press (mutter
meta-wayland-pointer.c). UI toolkits fire click actions on release, so recording the release serial too would always overwrite the press serial with one the compositor doesn't recognize — we hit exactly this during testing (token still refused when sealed with the release serial; accepted with the press serial).Storing per-
WindowState(rather than globally) also keeps the serial and theset_surfacetarget referring to the same surface, which matches how compositors validate the pair, and avoids growing the platformWindowenum.If the window has never seen a button press, behavior is unchanged (token issued without serial, best-effort as before).
Testing
Tested on a GNOME 46 / mutter Wayland cabinet setup (winit-based launcher app, fullscreen):
ActivationTokenDone, and passes it inXDG_ACTIVATION_TOKENto a spawned process.meta-wayland-activation.c:token_can_activate→meta_wayland_seat_get_grab_info).gnome-text-editor(GTK4, reference consumer ofXDG_ACTIVATION_TOKEN) with the sealed token transfers keyboard focus reliably (3/3 runs, including >30 s after launcher startup, i.e. long past any startup token's validity).set_serialtime.cargo +nightly fmt,cargo clippy -p winit-waylandandcargo test -p winit-waylandare clean; the change is confined towinit-wayland.Future work
Keyboard (
wl_keyboard.key) and touch (wl_touch.down) serials can be recorded the same way for apps driven without a pointer; this PR keeps the scope to the pointer path.