Der neue Displayserver Wayland unterschreibt gerade Dauermietverträge bei vielen Linux-Distributionen. Das wirkt sich auf die dort laufenden grafischen Anwendungen aus. Kwin-Entwickler Martin Gräßlin erklärt, wie Qt-Entwickler ihre Programme auch mit Wayland und Xwayland betreiben.
Wayland und X11 sind in großen Bereichen kompatibel, was es Xwayland erleichtert, das X11-Protokoll über Wayland anzubieten. Dennoch gibt es Unterschiede, die für Entwickler wichtig sind. Einer davon wirkt sich beispielsweise positiv auf die Sicherheit aus. Das Wayland-Protokoll darf im Gegensatz zu X11 keine Tastatur- und Mauseingaben abfangen. Generell erhält eine Anwendung keine Informationen über die Fenster anderer Anwendungen. Eingaben gehen stets nur an jene Anwendung, die gerade ein Fenster fokussiert. Das beeinflusst auch X11-Anwendungen, nicht zuletzt, weil sie nun nicht mehr alle Ereignisse bemerken, wie Abbildung 1 demonstriert.

Abbildung 1: Steht eine X11-Anwendung gerade nicht im Fokus, erfährt sie auch nichts von Eingabe-Ereignissen. So kann der Klassiker Xeyes dem Mauszeiger nur folgen, wenn dieser ein X-Fenster kreuzt.
Den Portierungsaufwand nach Wayland bestimmt eine Anwendung zum großen Teil selbst. Verwendet sie ein Toolkit wie Qt oder GTK+ und benutzt sie keinen X11-spezifischen Code, braucht sie meist keine Änderung. Benutzt sie hingegen X11-APIs, etwa durch direkten Zugriff auf Xlib oder XCB, sollte der Entwickler diesen Code durch Wayland-spezifischen ersetzen, etwa durch ein abstrahierendes Toolkit. Natürlich besteht auch die Option, eine Anwendung auf X11 zu lassen und auf Xwayland zu vertrauen.

Abbildung 2: Die Anwendung aus Listing 1 läuft auf X11 (mit Xwayland) und Wayland gleichermaßen. Die Methode ist wichtig bei Portierungen.
Qt 5 erlaubt es, das Fenstersystem mit Hilfe der Qt Platform Abstraction (QPA) zur Laufzeit zu wählen. Über die Umgebungsvariable »QT_QPA_PLATFORM« oder das Kommandozeilenargument »–platform« gibt der Entwickler das zu verwendende Fenstersystem an. Zur Laufzeit fragt die Anwendung mittels »QGuiApplication::platformName()«[1] die verwendete Plattform ab, wie es auch Listing 1 zeigt. Abbildung 2 illustriert das Beispiel für die Plattformen Wayland und XCB. Diese Methode spielt beim Portieren eine sehr wichtige Rolle. Im Gegensatz zu einer Portierung nach Windows oder OS X reichen Checks zur Kompilierzeit über »ifdef« nicht aus.
Listing 1
Plattform-Erkennung in Qt
01 #include <QApplication>
02 #include <QLabel>
03
04 int main (int argc, char *argv[])
05 {
06 QApplication app(argc, argv);
07 QLabel label(app.platformName());
08 label.show();
09 return app.exec();
10 }
Wayland bringt ebenfalls Code für X11 mit:
#ifdef HAVE_X11 XFlush(QX11Info::display()); #endif
Führt ihn ein Entwickler allerdings so unter Wayland aus, bringt er die Anwendung zum Absturz. Er muss stets auch die Plattform überprüfen, wobei der folgende Code hilft:
if (qApp->platformName() == QLatin1String("xcb"))
XFlush(QX11Info::display());
Verlinkt er auf »QtX11Extras«, ändert sich das Programm ein wenig:
if (QX11Info::isPlatformX11())
XFlush(QX11Info::display());
Intern führt »QX11Info::isPlatformX11«[2] den gleichen Stringvergleich aus. Verwendet eine Anwendung an vielen Stellen plattformspezifischen Code, bietet es sich an, den Check in einer boolschen Variablen zu speichern. So wird ein String-Vergleich nicht immer nötig.
Wichtig ist, immer die Plattform zu prüfen und nicht etwa zu checken, ob beispielsweise »QX11Info::display« einen »nullptr« zurückliefert. Ruft eine Anwendung »QX11Info::display« unter Wayland auf, erfolgt ein Cast von »wl_display*« nach »XDisplay*«, der zum Absturz der Anwendung führt.
Will ein Entwickler wissen, ob seine Anwendung unter Wayland läuft, ist das etwas schwieriger. Dank Mir darf er nicht automatisch davon ausgehen, dass er anstelle von X11 stets auf Wayland trifft. Er muss die Plattform erneut überprüfen. Leider verwendet Qt unter Wayland nicht den simplen Namen, sondern ergänzt ihn um zusätzliche Informationen. Kommt Open GL zum Einsatz, lautet der Name »wayland-egl« statt »wayland«. Jedoch beginnt der Name stets mit »wayland«:
if (qApp->platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive)) {
// Wayland-Code
}
Das KDE-Framework »KWindowSystem« bietet seit Version 5.25 eine Abstraktion hierfür an [3], die jedoch aktuell nur X11 und Wayland unterstützt.
Portierung
Bereiche, die Wayland nicht erfasst, deckt auch Qt in seinem abstrahierenden API nicht ab. Wayland nutzt zum Beispiel keine globalen IDs für Fenster, daher liefert »QWindow::winId«[4] keine globale ID zurück, sondern verwendet eine in der Anwendung inkrementierte ID. Diese kennt weder der Compositor, noch eine andere Anwendung. Interaktionen mit anderen Anwendung über Fenster-IDs erlaubt Qt damit nicht mehr, »QWindow::fromWinId«[5] liefert ein Pointer-Literal zurück.
Startet der Entwickler API-Calls, die Wayland nicht ermöglicht, wirft Qt teilweise Warnungen zur Laufzeit aus. So eignet sich »QWindow::setMouseGrabEnabled«[6] nur für Popup-Fenster. Bei anderen Fensterarten warnt Qt: »This plugin supports grabbing the mouse only for popup windows«. Welche Methoden in Wayland fehlen, zeigt der Kasten “Von Wayland nicht erlaubte Methoden”.
Von Wayland nicht erlaubte Methoden
- QWindow::setPosition()
- QWindow::setMouseGrabEnabled()
- QWindow::setKeyboardGrabEnabled()
- QWindow::alert()
- QWindow::lower()
- QWindow::raise()
- QWindow::requestActivate()
- QScreen::grabWindow()
- QCursor::setPos()
Eine Alternative gibt es nicht. Entwickler sollten daher überlegen, ob sie die Funktion wirklich brauchen. Sie gilt unter X11 oder Windows als schlechte Idee, denn idealerweise positioniert hier der Fenstermanager eine Anwendung.
Fenstericons
Fast jede Anwendung muss das Setzen des Fenstericons überarbeiten. Unter Wayland platziert der Entwickler es nicht direkt, sondern der Compositor holt es aus der ».desktop«-Datei [7]. Wayland-Fenster übermitteln ihm dafür den Namen der ».desktop«-Datei.
Qt errechnet den Namen laut Standard aus »QCoreApplication::organizationDomain«[8] und dem Namen der ausgeführten Datei, was etwa »org.kate-editor.kate« ergibt. Lautet er nur »org.kde.kate«, kann der Compositor das Fenster keiner Anwendung zuordnen und kein Icon setzen. Anwendungen brauchen also einen passenden ».desktop«-Dateinamen.
Seit Qt 5.7 darf der Entwickler den Namen mit »QGuiApplication::setDesktopFileName«[9] auch direkt angeben. Hat er die Anwendungen gegen die LTS-Version Qt 5.6 gebaut, übermittelt er das Property wahlweise auch über:
qApp->setProperty("desktopFileName",
QStringLiteral("org.kde.kate"));
Die Verbindung von Fenstern und ».desktop«-Dateien spielt in Desktopumgebungen eine zentrale Rolle, zumindest KDEs Plasma-Desktop nutzt dieses Feature auch unter X11.
Transients
Als häufige Fehlerquelle für Anwendungen unter Wayland erweist sich eine fehlende Eltern-Kind-Beziehung bei temporären Fenstern. Vor allem Anwendungen, die etwa Tooltipps oder Benachrichtigungen selbst implementieren, zeigen sich anfällig für dieses Problem.
War es unter X11 noch möglich, einen Tooltipp auch ohne korrekte Eltern-Kind-Beziehung von Hand zu platzieren, fehlt diese Möglichkeit unter Wayland. Vergisst der Entwickler also, die Eltern-Kind-Beziehung für temporäre Fenster zu spezifizieren, platziert der Compositor den Tooltipp einfach irgendwo. Das Tooltipp-Fenster erscheint als gewöhnliches Toplevel-Fenster, der Compositor behandelt es auch so. Erhält es zum Beispiel den Fokus, schließt die Anwendung es sofort wieder. Das Tooltipp-Fenster ist dann niemals zu sehen.
Solche Probleme vermeidet, wer für die Anwendung »QWindow::setTransientParent«[10] wählt. Dies versorgt Qt mit genügend Informationen, um den Tooltipp als vorübergehendes Fenster zum Hauptfenster zu setzen. Auch X11 profitiert davon, denn generell sind Fenstermanager für Hinweise auf temporäre Fenster dankbar.
Benachrichtigungen
Benachrichtigungen kämpfen mit ähnlichen Problemen. Erscheinen sie nicht in der Anwendung selbst, sondern erzeugt diese sie global mit einem »Qt::BypassWindowManagerHint«, muss der Entwickler nachbessern. Auch dieses Fenster platziert der Compositor irgendwo – und eine Benachrichtigung erreicht den Anwender nur per Zufall.
In der Regel lohnt es sich, an dieser Stelle den vom System bereitgestellten Benachrichtigungsdienst zu nutzen, unter Linux basiert er auf der Notification Specification [11]. Hierfür gibt es einige Bibliotheken wie etwa KDEs KNotifications Framework [12], Libnotify [13] oder auch eine Plattform-unabhängige Bibliothek wie KDEs Snorenotify [14]. Nicht gerade überraschend eignen sich die zwei erwähnten KDE-Technologien ideal für den Einsatz in einer Qt-Anwendung.
Auch beim Einsatz eines »QSystemTrayIcon«[15] gibt es Grund für erhöhte Aufmerksamkeit. Generell darf eine Anwendung nicht mehr annehmen, dass eine Klasse unter allen Linux-Desktops funktioniert. Entwickler prüfen besser die Methode »QSystemTrayIcon::isSystemTrayAvailable()«. »QSystemTrayIcon« verwendet unter X11 das alte Xembed-Protokoll [16] oder – wenn vorhanden – das neuere Status-Notifier-Item-Protokoll (bekannt als App-Indicator, [17]).
Die X11-Implementierung läuft unter Wayland aber nicht mehr, die Gnome-Shell bietet das neuere Protokoll gar nicht an. Somit funktioniert »QSystemTrayIcon« unter Gnome Wayland nicht, dafür aber unter KDE Plasma, Enlightenment – und auch unter Wayland.
Going native
Natürlich gibt es auch Fälle, in denen Qts Abstraktion nicht ausreicht und die Anwendung direkt mit Wayland sprechen muss. Liegt solch ein Fall vor, empfiehlt es sich, in einer Qt-Anwendung das von KDE im Rahmen der Wayland-Portierung entwickelte KWayland-Framework [18] einzusetzen. Dieses Tier-1-Framework (keine Abhängigkeiten außer zu Qt und Wayland) formt einen Qt-artigen Wrapper für die Wayland-Bibliothek. Er ersetzt die C-typischen Callbacks durch Signals und passt Argumente an Qt an. Die Bibliothek interagiert auch sehr gut mit der “QtWayland Qt Platform Abstraction”: Entwickler verwenden die Wayland-Verbindung von Qt und erstellen für ein QWindow ein KWayland-gewrapptes Objekt.
Als Beispiel für den Einsatz von KWayland in einer Qt-Anwendung dient das Relative-Pointer-Protokoll [19]. Normalerweise bemerkt Wayland Zeigerbewegungen nur dann, wenn sich der Mauszeiger bewegt. Ist der aber am Bildschirmrand gefangen, sendet Wayland keine Zeigerereignisse an die Anwendung.
Das Protokoll für relative Zeigerbewegungen bringt Abhilfe für dieses Problem und sendet immer Ereignisse, auch in höherer Auflösung. Es deckt Spezialfälle ab, etwa First Person Shooter, fehlt aber in Qt. Ob die Entwickler es integrieren, ist fraglich, da es keinen Standardanwendungsfall des Qt-Toolkits bedient.
Das Beispiel zeigt, wie Entwickler mittels KWayland Zusatzprotokolle in Qt-Anwendungen einsetzen. Zunächst erstellen sie dazu eine Registry, um die verfügbaren Protokolle zu verwalten:
auto c = ConnectionThread::fromApplication(); m_registry = new Registry(this); m_registry->create(c);
Die Registry meldet jedes für sie bekannte Protokoll über ein dediziertes Signal, zum Beispiel »Registry::seatAnnounced«, und informiert zusätzlich über »Registry::interfacesAnnounced«, dass alle Protokolle übermittelt sind:
connect(m_registry, &Registry::seatAnnounced, this, &RP::seatAnnounced);
connect(m_registry, &Registry::relativePointerManagerUnstableV1Announced,
this, &RP::rpmAnnounced);
Die Signale enthalten die Argumente »name« und »version«, um ein globales Protokoll zu verwenden:
void RP::rpmAnnounced(quint32 name, quint32 version) {
m_rpm = m_registry->createRelative PointerManager(name, version, this);
Die Registry hilft dabei, einen Seat und einen »RelativePointerManager« zu fabrizieren. Der Seat informiert über Tastatur-, Maus- (Pointer-) und Touch-Eingaben. Existiert eine Mausunterstützung, erzeugt der Entwickler einen Pointer
m_pointer = m_seat->createPointer(this);
und mit »RelativePointerManager« einen »RelativePointer« für den Pointer:
m_rp = m_rpm->createRelativePointer(m_pointer, this);
Erhält ein Fenster den Mausfokus, übermittelt ein Signal die relativen Zeigeränderungen an den »RelativePointer«:
connect(m_rp, &RelativePointer::relativeMotion, this,
[] (const QSizeF &delta, const QSizeF &deltaNonAccelerated, quint64 timestamp) {
qDebug() << delta << deltaNonAccelerated << timestamp; });
Das komplette Listing zum Beispiel wartet unter [20]. Das geschilderte Vorgehen betrifft alle Protokolle: Über die Registry erzeugt der Entwickler einen Manager, der die eigentlichen Objekte generiert. Die liefern dann Ereignisse in Form von Signals und bieten Anfragen an den Server über öffentliche Methoden.
Fazit
Für Wayland sind Qt-Anwendungen gut gerüstet. Meist brauchen sie keine Anpassungen und falls doch, profitieren sie auch unter X11 von diesen. Trotzdem sollten Entwickler ihre Anwendung sowohl unter Wayland als auch unter Xwayland testen, um keine Überraschungen zu erleben. Gerade im Eingabebereich ändert Wayland einiges, und das wirkt sich auch auf X11-Programme aus.
Auf Fälle, in denen die Anwendung nativen Code benötigt, ist Qt gut vorbereitet. Mit KWayland existiert eine ausgereifte Bibliothek, die seit Jahren im produktiven Einsatz ist. Zudem ist die KDE-Community ein guter Ansprechpartner für Entwickler, die Qt-Anwendungen nach Wayland portieren wollen.
Infos
- »QGuiApplication::platformName«: https://doc.qt.io/qt-5/qguiapplication.html#platformName-prop
- »QX11Info::isPlatformX11«: https://doc.qt.io/qt-5/qx11info.html#isPlatformX11
- »KWindowSystem::platform«: https://api.kde.org/frameworks/kwindowsystem/html/classKWindowSystem.html#ae9f7971618790b50cf64af09cc0d7d7d
- »QWindow::winId«: https://doc.qt.io/qt-5/qwindow.html#winId
- »QWindow::fromWinId«: https://doc.qt.io/qt-5/qwindow.html#fromWinId
- »QWindow::setMouseGrabEnabled«: https://doc.qt.io/qt-5/qwindow.html#setMouseGrabEnabled
- Die Desktop-Entry-Spezifikation von Freedesktop.org:https://standards.freedesktop.org/desktop-entry-spec/latest/
- »QCoreApplication::organizationDomain«: https://doc.qt.io/qt-5/qcoreapplication.html#organizationDomain-prop
- »QGuiApplication::setDesktopFileName«: https://doc.qt.io/qt-5/qguiapplication.html#desktopFileName-prop
- »QWindow::setTransientParent«: https://doc.qt.io/qt-5/qwindow.html#setTransientParent
- Gnomes Desktop-Notifications-Spezifikation: https://developer.gnome.org/notification-spec/
- KDEs KNotifications-Framework: https://api.kde.org/frameworks/knotifications/html/index.html
- Gnomes Libnotify: https://developer.gnome.org/libnotify/
- Snorenotify: https://github.com/KDE/snorenotify
- »QSystemTrayIcon«: https://doc.qt.io/qt-5/qsystemtrayicon.html
- Die System-Tray-Protocol-Spezifikation von Freedesktop.org: https://standards.freedesktop.org/systemtray-spec/systemtray-spec-0.2.html
- »StatusNotifierItem«: https://www.freedesktop.org/wiki/Specifications/StatusNotifierItem/
- KWayland: https://api.kde.org/frameworks/kwayland/html/index.html
- Mehr zum Relative Pointer Protocol: https://cgit.freedesktop.org/wayland/wayland-protocols/tree/unstable/relative-pointer/relative-pointer-unstable-v1.xml?id=1.7
- Alle Listings zum Artikel in voller Länge: https://www.linux-magazin.de/static/listings/magazin/2017/01/wayland_und_qt






