Add ViewportCommand::RequestActivationToken to support cross-window focus on Wayland
- Dominant language
- Rust
- Stars
- 30.6k
- Forks
- 2.1k
- Avg merge
- 1d 9h
- Merged PRs (30d)
- 72
Description
notice: This is a real request made by me for a real issue, the content is AI assisted but proof-read and fact-checked manually.
## Summary
On Wayland, programmatically focusing or raising a window requires an xdg_activation_v1 token obtained from a recent user input event. Without a token, the
compositor silently ignores activation requests. This is a deliberate focus-stealing prevention measure baked into the protocol.
egui-winit currently exposes ViewportCommand::Focus, which calls winit::window::Window::focus_window(). On Wayland, that call is a no-op unless the window
already holds an activation token, and egui-winit provides no way to request or supply one. As a result, multi-window egui apps cannot bring a sibling
window to the foreground on Wayland, even in response to a direct user gesture in another window of the same app.
## Reproduction
A multi-process or multi-window egui app on Wayland (tested on KDE):
1. Window A (focused) sends an IPC message to window B asking it to surface.
2. Window B calls ctx.send_viewport_cmd(ViewportCommand::Focus).
3. Window B does not come to the foreground. Toggling WindowLevel::AlwaysOnTop then Normal, and Visible(false) then Visible(true), are also no-ops.
RequestUserAttention(Critical) works (urgency hint / taskbar flash) but does not actually raise the window.
## Relevant code
window.focus_window() on Wayland is implemented as xdg_activation_v1::activate(token, surface) and requires the caller to have already obtained a token.
winit exposes the token-request side via:
- winit::window::Window::request_activation_token, which returns a Result.
- The token is delivered asynchronously through WindowEvent::ActivationTokenDone { serial, token }.
egui-winit already observes this event but discards it ([egui-winit/src/lib.rs:497](https://github.com/emilk/egui/blob/fe8b1edfc603ac511c7ea8b0a19e5e3218f5928c/crates/egui-winit/src/lib.rs#L497)):
There is no public surface for either requesting a token or supplying an externally-obtained one back to Focus.
## Proposal
Add two pieces to the ViewportCommand API:
1. ViewportCommand::RequestActivationToken: instruct egui-winit to call Window::request_activation_token() on the underlying winit window. The resulting
token is delivered to the app via a new egui::ViewportEvent::ActivationToken(String) (or similar), forwarded through the existing event mechanism that
surfaces window events to the app.
2. ViewportCommand::ActivateWith(String) (or extend Focus to take an Option): accept a previously-obtained activation token and pass it to a function like
Window::set_activation_token then call Window::focus_window so the compositor honors the activation request?
Together these allow a focused window to request a token, ship it to a sibling (over IPC, command-line arg, env var, or whatever transport the app
chooses), and have the sibling self-activate cleanly.
## Why this is needed
The Wayland model assumes activation tokens flow alongside user intent (clicks, XDG_ACTIVATION_TOKEN env var on spawn, .desktop Activate calls, etc.). Apps
that span multiple windows or processes, such as download managers, IDEs, chat apps with notification windows, and file managers with detached previews,
all need to pass tokens between their own windows. Today, egui-winit users have no way to participate in this protocol and are forced to either fall back
to RequestUserAttention (urgency hint only) or shell out to compositor-specific tools (kdotool, swaymsg, hyprctl, wmctrl) which are brittle and
non-portable.
Currently Winit does not expose a way to set activation token on an already open window. this needs to be implement on winit first.
## Possible related issues
- https://github.com/rust-windowing/winit/issues/3633
- https://github.com/rust-windowing/winit/issues/3964
## References
- xdg-activation protocol: https://wayland.app/protocols/xdg-activation-v1
- winit request_activation_token: https://docs.rs/winit/latest/winit/window/struct.Window.html#method.request_activation_token
- winit WindowEvent::ActivationTokenDone: https://docs.rs/winit/latest/winit/event/enum.WindowEvent.html#variant.ActivationTokenDone
- winit PR introducing the API: https://github.com/rust-windowing/winit/pull/2978
## Workaround
Multi-window egui apps on Wayland today must rely on RequestUserAttention(Critical) for cross-window surfacing, accepting that the window will not actually rise; only the taskbar entry will pulse. Visible, Minimized(false), WindowLevel, and Focus viewport commands all silently no-op for this use case.
The only real workaround for this situation for now is to recreate the target window, which depending on context and app may be cheap or not.
Contributor guide
Research direction
Start with egui-winit/src/lib.rs:497 and trace how WindowEvent::ActivationTokenDone and viewport commands are handled. Read winit's request_activation_token API and related issues 3633 and 3964 first, since setting a token on an existing window is not currently exposed. Done requires an agreed API and working token request and activation flow for Wayland multi-window apps.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- api, desktop
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100