On Window Activation

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.

9 thoughts on “On Window Activation”

  1. Do you think it would be possible for a terminal, e.g. Konsole, to somehow inject such a token into its shell process so that tools like kde-open could then pick it up?

      1. Ah, nice!

        The other way around but much more straight forward, likely shareable with other applications.

        Changing the shell’s environment would probably require to “be the shell” or some LD_PRELOAD trickery.

  2. How does requiring activation tokens interact with requirements like “launch a program from a terminal and it should be given focus if the controlling terminal is still activated and untouched”? (I recall issues with some Qt apps not gaining focus on startup on macOS, but forgot the exact circumstances.)

    I’ve also observed weirdness where Firefox doesn’t get activated when launching it from XWayland applications, xdg-open, or even right-clicking in Dolphin. There’s a writeup on Mozilla at https://bugzilla.mozilla.org/show_bug.cgi?id=1873981#c28, and in my testing I found activate() is called twice with “not-granted-666” when it fails, and once with “not-granted-666” and once with “kwin-…” when it succeeds. I don’t know why Firefox doesn’t get a token properly in some cases.

  3. I am reminded of this https://discuss.kde.org/t/ideas-for-better-focus-stealing-prevention/32154/ thread where we discussed ways to prevent focus stealing. OP actually wanted to keep focus stealing, sometimes, so it’s a bit off-topic, but I discussed how to prevent it entirely.

    I guess in the context of this change, that could be adapted to: when the user provides input (to any window, or to plasma/the desktop itself) all the tokens need to be invalidated so that the new window doesn’t steal focus (and subsequent input) from wherever they were typing/clicking while they waited for it to appear.

    An example you can try that causes focus stealing on 6.4:
    Open konsole.
    Open your app launcher.
    Click the kate icon and start typing stuff.
    Now half of it is in konsole and half is in kate. Kate stole focus mid-sentence, because konsole had focus and we were actively using it.

    Probably, konsole shouldn’t have even been given focus after we clicked the kate icon (because that should focus kate), but if it is, focus definitely shouldn’t be later granted to kate if we started typing a command into konsole in the meantime. That’s the worst and most dangerous type of focus stealing; mid-sentence.

    A real world example where this bites me every day is starting apps at boot time. I click the app launcher, click firefox, firefox starts opening windows and they get focus, I click the app launcher to open another app, another firefox window opens, and my focus is stolen and the app launcher closes. I click the app launcher again to try again, nope, stolen by the next firefox window, have to wait until the windows have all finished opening before I can do anything else.

    It’s a paper cut, but it’s still great to see someone taking care of it. Thanks Kai and Xavier!

  4. After years of Wayland I just moved back to X11 because of this. It was starting to annoy the hell out of me, it’s good to see that it is being addressed.

    What I would like to know is since when this focus stealing is being prevented at all because I never noticed this before.

  5. The big item I’m missing for controlling activation of existing windows is: global shortcut activation. When an application has a registered global shortcut, and has a window, you’d expect the window to be able to activate as needed when the global shortcut is triggered.

    There are probably more applications with this use case, but for me the issue is Yakuake – when you hit the shortcut the first time, it opens a window and therefor gets activated, but if want to use “keep window open when it loses focus” if you alt-tab to a different window and then want to focus back on Yakuake – pressing the global shortcut again does nothing. Internally Yakuke is trying to activate itself, but it has no token and therefor can’t do that.

    This issue is tracked in bug 402634 and there was some implementation effort around this by Nicolas Fella in Yakuake MR!91, but this hasn’t been moving in over a year now.

  6. Will this work fix the following focus stealing event?
    1. Have a slow disk (aka HDD)
    2. Load a disk heavy app, like Firefox
    3. While Firefox is still loading without painting a window, focus a terminal (Konsole) and start writing in it
    4. Finally, Firefox presents its main window which is given focus by the compositor even though you had explicitly set focus to the terminal. This breaks your typing, possibly leaks secrets, and reduces UX.

Leave a Reply

Your email address will not be published. Required fields are marked *