One of the the less apparent omissions in Plasma’s Wayland session compared to X was the lack of a prompt for terminating an unresponsive app. Of course, you should never see one because any decent app will just crash and quit rather than get stuck. Nevertheless, over the course of three evenings I spent way too much time making the “KWin Killer Helper” work on Wayland and while at it revamped its user interface entirely.
It’s always fun to add your copyright from 2023 to a file with “© 2003” at the top. Times were surely different back then and what was considered good user interface design, too. The prompt for terminating an app is actually a separate executable since under X KWin isn’t really designed to open windows by itself and even under Wayland it’s tough. The helper might also use KAuth to end a privileged process. Being an external application, however, poses some challenges under Wayland because unlike X, it actually has some notion of security, and thus no random client can just mess with another one.
In order for the helper to attach itself to the frozen application window, it needs to use the XDG Foreign protocol. It’s designed for this particular use case of parenting oneself to a window in another process and used by for example the XDG Desktop Portal where a file dialog should appear as if it were coming from the application that requested it. The way it works is relatively simple: A client can ask the compositor to “export” a surface and receives a unique token for it. This token can then be passed to another application by any means (DBus, environment variable, doesn’t matter) which uses it to “import” that surface (to put it simply) and parent a window to it. But it’s a one-way street – you cannot pull a surface from another client, it has to send it to you.
KWin of course supports this protocol but its internals weren’t designed for it to launch an application by itself and export a surface to it, it would only export surfaces on behalf of an actual Wayland client. It took me like three different attempts to find a good solution everyone was happy about: just create an “exported surface” base class that is referenced internally and create a subclass including the Wayland resources when actually needed. I’m still having a bit of a hard time wrapping my head around the memory ownership model employed by Qt Wayland Scanner. It’s a build tool which takes a Wayland XML Protocol description and generates C++ classes out of it for use in your Qt application.
Next, I introduced a KillPrompt class that managed the life cycle of the process so its logic could easily be shared between X11 and Wayland windows. Originally, the X11Window class even used lowlevel ::kill(0) calls scattered throughout it to check whether the helper was still alive. Not something you would do today thanks to QProcess. The helper takes a bunch of command-line arguments, such as the window title, application name, process ID, etc. Instead of sending an X Window ID I used the UUID of the aforementioned exported surface and instead of using the “Window Class” I used the more contemporary “desktop file name” (application ID) – that’s also why the original version above reads “org.kde.kwrite” instead of just “kwrite”.
Additionally, instead of setting the “X User Time” for soothing KWin’s focus stealing prevention I generated an activation token to enable the dialog to properly focus itself. That’s using XDG Activation, a Wayland protocol for passing focus between programs, for instance clicking a link in a chat app should bring the browser to the front. The same principle applies: you ask the compositor for a token and then pass it to another application. However, the compositor may invalidate that token if you’re typing elsewhere in the meantime or the window wasn’t actually active at the time it requested the token and so on.
KWin also has a killPingTimeout option (despite what some people say about KDE software being jammed with settings, this one isn’t in a spin box in System Settings) which defaults to 5 seconds. When clicking the close button, at half that time the window gets de-saturated as “not responding” and once the full timeout has passed the prompt asks whether to terminate the application. Now you might be asking yourself: Wait a minute, I have seen my windows getting all washed out even without trying to close them?!
I congratulate you on using Wayland then! On X we only ping a window when asking it to close because we cannot really be certain that it will respond to our ping or even implement the interfaces that we need for that. On Wayland however, your typical floating desktop application will implement the XDG Shell protocol which handles things such as the window title, title bar buttons, and popup menus, and specifies a “ping” operation. In addition to pinging a window when closing it, KWin can also safely ping it whenever it gets focus, and thus realize sooner that it has become frozen.
Coming back to the timeout, I found that it was actually hard-coded to 1 second in the Wayland case. Sometimes even my IDE loading a large project or the email client handling lots of incoming messages was enough to cause a brief flash of white. How hard can it be to pass an int around? Well, KWin’s Wayland interfaces are to be kept separate from its main application logic (most importantly to ease unit testing but also historically from trying to make them available to others as a KDE Framework) so I couldn’t just access KWin’s options dict from there and read the setting out of it. Instead, I had to inject the value from the outside and refactor KWin’s startup a bit so that the XDG Shell integration is only initialized once the options have actually been read.
Ultimately, I figured, if I’m working on this piece of code anyway, I might as well overhaul its design which hasn’t changed much in two decades. You might recall I like doing that sort of thing. First of all, I used the desktop file name to look up the actual application name. Next, I significantly reduced the amount of text – by the time you finished reading the original dialog, the app might already be responding again ;) Technical information like the process ID and host name (in case of remote X applications) was then tucked away in the expandable “Details” section. I also tried hard to remove the redundant application name from the displayed title but some apps use an ndash, some an mdash, some just a regular hyphen, and some even put it before the window title.
I furthermore felt the need to more prominently correlate the dialog to the application that is about to be terminated and used its icon instead of the generic orange exclamation mark. The overlay you can see in the screenshots was achieved using KIconUtils in our KGuiAddons framework which – instead of drawing a pixmap once – creates a “live” QIconEngine that can re-render when the window gets moved between screens of different DPI. Saved me quite some QPainter headaches right there! Finally, the helper received a proper .desktop file which is needed to give it an appropriate window icon. It also let me suppress the bouncing cursor it would otherwise get by setting StartupFeedback to false. Originally, I wanted to restore the “bomb” icon it used to have but given the circumstances I opted for that sad face we also use in DrKonqi when an application crashes.
Next time you’re wondering why it took so long to implement that tiny feature X and Y, think of me and all the plumbing that had to be done for a stupid freaking message box that you should never even see in the first place and the love and attention to detail that me and my fellow KDE developers put in our software. A good way to show your love for KDE is to donate to our Plasma 6 fundraiser! ♥
Discuss this post on KDE Discuss.