Skip to content

On Window Activation

Monday, 4 August 2025  |  Kai Uwe Broulik

You click a link in your chat app, your browser with a hundred tabs comes to the front and opens that page. How hard can it be? Well, you probably know by now that Wayland, unlike X, doesn’t let one application force its idiot wishes on everyone else. In order for an application to bring its window to the front, it needs to make use of the XDG Activation protocol.

KWrite (text editor) window, window has no focus (colors are softened). Task bar with a couple of apps, KWrite icon has an orange background behind it, indicating KWrite is demanding attention
A KWrite window that failed to activate and instead is weeping bitterly for attention in the task bar

In essence, an application cannot take focus, it can only receive focus. In the example above, your chat app would request an XDG Activation token from the compositor. It then asks the system to open the given URL (typically launching the web browser) and sends along the token. The browser can then use this token to activate its window.

This token is just a magic string, it doesn’t matter how it gets from one application to another. Typically, a new application is launched with the XDG_ACTIVATION_TOKEN variable in its environment. When activating an existing one, an activation-token property is added to the platform_data dict sent via DBus. There’s also older protocols that weren’t designed with this in mind, such as Notifications, StatusNotifierItem (tray icons), or PolKit requests where we cannot change the existing method signatures. Here we instead added some way to set a token just before the actual call.

However, just because you have a token doesn’t mean you can raise your window! The compositor can invalidate your token at any time and reject your activation request. The idea is that the compositor gets enough information to decide whether the request is genuine or some application popping up a dialog in the middle of you typing something. A token request can include the surface that requests the activation, the input serial from the focus or mouse event that resulted in this request, and/or the application ID of the application that should be activated. While all of this is optional (and there can be valid reasons why you don’t have a particular piece of information at this time), the compositor is more likely to decline activation if the information is incomplete or doesn’t match what the requesting application provided.

A lot of places in Qt, KDE Frameworks, and other toolkits and applications have already been adjusted to this workflow and work seamlessly. For example, calling requestActivate on a QWindow will check if there is an XDG_ACTIVATION_TOKEN in the environment and use it, otherwise request one. Qt also does this automatically when the window opens to match the behavior of other platforms. Likewise, things like ApplicationLauncherJob and OpenUrlJob will automatically request a token before proceeding. On the other hand, KDBusService (for implementing single instance applications) automatically sets the corresponding environment variable when it received a token via DBus. Together this makes sure that most KDE applications just work out of the box.

You might be wondering: didn’t KWin-X11 have “focus stealing prevention”? It sure does. There’s a complicated set of heuristics based on _NET_WM_USER_TIME to judge whether the new window appeared as a result of explicit user interaction or is unsolicited. Remember how back in ye olde days, KWin’s focus stealing prevention would keep the Adobe Flash Player fullscreen window from showing ontop of the YouTube video you’re watching? Yeah, it’s not perfect. KWin can also only react on things that have already happened. For instance, when an application uses XSetInputFocus on a window from a different application, KWin will detect that and consider it a malicious request and restore previous focus but for a split second focus did change. If you want to know more, there’s a 200+ lines comment in activation.cpp in KWin’s git repo that explains it all. But then again the application could just do whatever it wants and bypass all of this.

“Window Behavior” configuration dialog, various window-related tabs and options, mouse cursor pointing at a combo box “Focus stealing prevention” whose current item is “Extreme”
Xtreme Focus Stealing Prevention™

Unfortunately, there’s still a few places that don’t do XDG Activation correctly. It didn’t matter much under X – in doubt we could just forceActiveWindow – but now we have to fix those scenarios properly! In order to test whether your application is well-behaved, use the latest git master branch of KWin and set “Focus Stealing Prevention” in Window Management settings to “Extreme”. This will make KWin activate a window if and only if it requests activation with a valid token.

Using this, over the past couple of days Xaver Hugl of KWin fame and I fixed a bunch of issues, including but not limited to:

  • Dolphin threw away its token before activating its main window when launching a new instance (activating an existing one worked fine)
  • KRunner, Kickoff, and other Plasmoid popups did not request activation at all
  • LayerShell-Qt now requests activation on show (to match Qt behavior)
  • LayerShell-Qt didn’t read the XDG_ACTIVATION_TOKEN from the environment when provided
  • Privileged clients, like Plasma and KGlobalAccel, were unable to request tokens in some situations
  • Modifier key presses no longer count towards focus stealing prevention: they’re often used as part of a global keyboard shortcut and don’t necessarily mean the user is interacting with the active window

Furthermore, the DBusRunner specification gained a SetActivationToken method which is called just before Run. Baloo (desktop search) runner now uses this to ensure opening files in an existing application window works. Likewise for the KClock runner bringing KClock to the front properly. I further improved the recent documents runner and places runner to send the file type to the OpenUrlJob so it doesn’t have to determine it again. This makes the job much quicker and avoids KRunner closing before the activation token is requested by the job. However, we have yet to find a proper solution for this in KRunner.

With all of this in place, we’ll likely switch on KWin’s focus stealing on Wayland at a low level and make it gradually stricter as applications are being fixed.