During Akademy I hold a session about porting applications to Wayland. I collected some of the general problems I saw in various KDE projects and want to highlight them in this blog post, too.
Starting Wayland
First the basics: how to start a Wayland compositor? Assuming one is using Plasma 5.4 (aka master) the way to start a nested KWin is:
kwin_wayland --xwayland
Important: ensure that you have Xwayland installed!
If you run nested this will open a window, if it turns black, everything works. If it doesn’t turn black: contact me (please check whether you use free drivers first).
As an alternative you can also start Weston:
weston
If you run into issues with your application running under Wayland, please always test whether the problem is also visible on Weston before blaming me 😛
Starting applications on Wayland
Now we have our black KWin window and we want to get a window to it. There are several ways to start it. As an example I’ll use kate.
Environment variable
Maybe the easiest way is to just use an environment variable to switch the QPA platform to Wayland:
export QT_QPA_PLATFORM=wayland
kate
And a Kate window will open on the nested KWin.
The environment variable is set to wayland in our startplasmacompositor script to start a full Plasma on Wayland session on tty.
Command line switch
Every Qt application also has a command line switch to select the windowing system:
kate --platform wayland
Starting applications together with KWin
KWin supports starting applications once the Wayland and Xwayland servers are started. You can add the programs to start to the command which starts KWin:
kwin_wayland --xwayland "kate --platform wayland"
Or to simplify with environment variables:
export QT_QPA_PLATFORM=wayland
kwin_wayland --xwayland kate
Of course it’s also possible to just start a Konsole and then launch applications from inside the Konsole. Please note that any key combination with ctrl-key is unfortunately broken when running Konsole as a Wayland application.
X11 Specific code
Many applications have X11 specific code. Being it implicitly or explicitly. So we need to move away from it and in most cases it means also improving the situation on X11 because the solution is wrong.
Opening X connection
A common mistake I saw in Qt applications is to open a connection to the X server. E.g. like that:
Display *dpy = XOpenDisplay(NULL);
Now this code is already wrong on X11 as it opens another connection which is not the Qt display connection. It might be on the wrong head, it might be on the wrong X server. From an integration point of view it’s broken.
So the solution is to always use QX11Info from QtX11Extras to integrate with the Display:
Display *dpy = QX11Info::display();
Runtime checks in addition to compile time checks
Before Qt5 the way to support multiple windowing systems was to use compile time switches, like:
#if HAVE_X11
#include <QX11Info>
void doSomethingWithX11()
{
XFlush(QX11Info::display());
}
#endif
Now this will crash on Wayland. It will be compiled in, but crashes when running on Wayland. So you need to improve. Let’s try:
#if HAVE_X11
#include <QX11Info>
void doSomethingWithX11()
{
Display *dpy = QX11Info::display();
if (!dpy) {
return;
}
XFlush(dpy);
}
#endif
Now this code will also crash! The problem is that QX11Info::display() goes through Qt’s native interface and uses the key “display”. QtWayland also provides the key “display” and returns a wl_display for it, which gets casted into a void in the native interface and then to Display* in QX11Info and boom at interesting places.
The proper way is to check the platform name:
#if HAVE_X11
#include <QX11Info>
void doSomethingWithX11()
{
if (!QX11Info::isPlatformX11()) {
return;
}
XFlush(QX11Info::display());
}
#endif
My recommendation is to keep the check in a local variable if the check is needed multiple times as in the end it’s a string comparison each time. Ideally you should restructure your code to have platform specific subclasses or similar refactored solutions.
General Changes with Wayland
Now here it becomes interesting: runtime changes. The most important information for application developers is probably that there are no more global window ids.
No global window ids
This has quite some implications for your application. E.g. QWindow::winId() returns an internal incremented number. Neither the compositor nor any other application knows anything about your internal window id counter. Similarly QWindow::fromWinId is not able to map a window id to a QWindow any more. This of course affects use cases like sending a window id through DBus to another process to use it for setting the transient hint. Also it means that KWindowSystem API has a hard time to support the calls on Wayland and in most cases it will just print a warning (this might improve, though).
Setting window icon
On X11 the applications exported the window icon as pixmap data. This is no longer possible on Wayland. Instead the compositor maps the window icon from the desktop file and the application announces the desktop file for a window. In Qt this is done by using the reverse domain name combined with the binary name. The domain name is set with KAboutData and defaults to “kde.org”. So for example in the case of kate the desktop file should be called “org.kde.kate.desktop”. So please fix the naming of your desktop files. If you want to test please be aware that this requires Qt 5.5 to work properly.
No longer working API
Quite some of the API calls of QWindow cannot be supported by Wayland on default (some of them might work on KWin, but please don’t implement against KWin!).
A short list of those API calls I know that they cannot work:
- QWindow::setPosition
- QWindow::setMouseGrabEnabled
- QWindow::setKeyboardGrabEnabled
- QWindow::alert
- QWindow::lower
- QWindow::raise
- QWindow::requestActivate
In addition it’s no longer possible to grab the image of the screen using e.g. QScreen::grabWindow (most obvious for the case of “root” window). Also warping the pointer using QCursor::setPos is no longer supported. Please note that warping the pointer is also a bad idea on X11 and you shouldn’t do that.
In case your application set the Qt::BypassWindowManagerHint on your QWindow/QWidget you need to do some porting as QtWayland doesn’t show such windows. Unfortunately that needs a case by case evaluation of the use case and the solution I presented during my Akademy talk should not be applied for applications.
Porting to Wayland
Don’t port
The solution to port to Wayland is: DON’T PORT! Mostly your X11 specific code or behavior is wrong and could be solved in better ways. Instead of porting you might want to rethink your problem or change to existing abstracted API in e.g. Qt or KWindowSystem. I have seen many cases where X11 API was used as a workaround by e.g. raising windows, force activating them and so on. If one things about it one realizes that there seems to be an obvious problem. So don’t try to port that but rethink your problem and improve not just on Wayland, but also on X11.
But I really need platform specific code
Ok, ok, I get it! Now let’s start with the obvious: your code needs to be compile time and runtime checked. Let’s start with how to not do it:
#if HAVE_X11
if (!QX11Info::isPlatformX11()) {
return;
}
// here goes the X11 specific code
#else
// here goes the Wayland specific code
#endif
The problem with this code snippet is that X11 support is probably also available when you use Wayland. Also the else part is not just Wayland, but also any other platform.
As I said the proper solution is to make it compile and runtime checked:
#if HAVE_X11
if (QX11Info::isPlatformX11()) {
callX11SpecificImplementation();
}
#endif
#if HAVE_WAYLAND
if (QGuiApplication::platformName().startsWith(
QLatin1String("wayland"), Qt::CaseInsensitive)) {
callWaylandSpecificImplementation();
}
#endif
Be aware that your code might also run on other platforms on Wayland, e.g. eglfs or other custom windowing systems. So don’t crash if you don’t hit any of the code paths 😉