[xwl] Move Xwayland parts into separate class

Summary:
The Xwayland code path is moved from ApplicationWayland to a dedicated class
Xwayland in a new top-level directory xwl.

This is a direct preparation step for generic support of Xwayland Selections.

On a longer timescale this should also allow us to further separate Wayland
native functionality from Xwayland to allow us at one point to build KWin's
Wayland binary optionally without X dependencies. Another long term goal, that
becomes possible through this separation is to recover from Xwayland crashes.

Test Plan: Manually and auto tests

Reviewers: #kwin, davidedmundson

Subscribers: davidedmundson, zzag, kwin

Tags: #kwin

Differential Revision: https://phabricator.kde.org/D15020
This commit is contained in:
Roman Gilg 2019-02-19 09:50:20 +01:00
parent 58de7d6862
commit 050cf0451e
5 changed files with 339 additions and 197 deletions

View file

@ -651,7 +651,13 @@ install(TARGETS kwin ${INSTALL_TARGETS_DEFAULT_ARGS} LIBRARY NAMELIN
install(TARGETS kdeinit_kwin_x11 ${INSTALL_TARGETS_DEFAULT_ARGS} )
install(TARGETS kwin_x11 ${INSTALL_TARGETS_DEFAULT_ARGS} )
add_executable(kwin_wayland tabletmodemanager.cpp main_wayland.cpp)
set(kwin_WAYLAND_SRCS
tabletmodemanager.cpp
main_wayland.cpp
xwl/xwayland.cpp
)
add_executable(kwin_wayland ${kwin_WAYLAND_SRCS})
target_link_libraries(kwin_wayland kwin KF5::Crash)
if (HAVE_LIBCAP)
target_link_libraries(kwin_wayland ${Libcap_LIBRARIES})

View file

@ -27,7 +27,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "effects.h"
#include "tabletmodemanager.h"
#include "wayland_server.h"
#include "xcbutils.h"
#include "xwl/xwayland.h"
// KWayland
#include <KWayland/Server/display.h>
@ -41,29 +41,18 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
// Qt
#include <qplatformdefs.h>
#include <QAbstractEventDispatcher>
#include <QCommandLineParser>
#include <QtConcurrentRun>
#include <QFile>
#include <QFileInfo>
#include <QFutureWatcher>
#include <QProcess>
#include <QSocketNotifier>
#include <QStyle>
#include <QThread>
#include <QDebug>
#include <QWindow>
// system
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif // HAVE_UNISTD_H
#if HAVE_SYS_PRCTL_H
#include <sys/prctl.h>
#endif
#if HAVE_SYS_PROCCTL_H
#include <unistd.h>
#include <sys/procctl.h>
#endif
@ -92,8 +81,6 @@ void disableDrKonqi()
// that would enable drkonqi
Q_CONSTRUCTOR_FUNCTION(disableDrKonqi)
static void readDisplay(int pipe);
enum class RealTimeFlags
{
DontReset,
@ -143,24 +130,13 @@ ApplicationWayland::~ApplicationWayland()
}
destroyWorkspace();
waylandServer()->dispatch();
disconnect(m_xwaylandFailConnection);
if (x11Connection()) {
Xcb::setInputFocus(XCB_INPUT_FOCUS_POINTER_ROOT);
destroyAtoms();
emit x11ConnectionAboutToBeDestroyed();
xcb_disconnect(x11Connection());
setX11Connection(nullptr);
}
if (m_xwaylandProcess) {
m_xwaylandProcess->terminate();
while (m_xwaylandProcess->state() != QProcess::NotRunning) {
processEvents(QEventLoop::WaitForMoreEvents);
}
waylandServer()->destroyXWaylandConnection();
}
if (QStyle *s = style()) {
s->unpolish(this);
}
// kill Xwayland before terminating its connection
delete m_xwayland;
m_xwayland = nullptr;
waylandServer()->terminateClientConnections();
destroyCompositor();
}
@ -207,7 +183,7 @@ void ApplicationWayland::continueStartupWithScreens()
return;
}
createCompositor();
connect(Compositor::self(), &Compositor::sceneCreated, this, &ApplicationWayland::startXwaylandServer);
connect(Compositor::self(), &Compositor::sceneCreated, this, &ApplicationWayland::continueStartupWithXwayland);
}
void ApplicationWayland::continueStartupWithSceen()
@ -218,55 +194,18 @@ void ApplicationWayland::continueStartupWithSceen()
notifyKSplash();
}
void ApplicationWayland::continueStartupWithX()
void ApplicationWayland::continueStartupWithXwayland()
{
createX11Connection();
xcb_connection_t *c = x11Connection();
if (!c) {
// about to quit
return;
}
QSocketNotifier *notifier = new QSocketNotifier(xcb_get_file_descriptor(c), QSocketNotifier::Read, this);
auto processXcbEvents = [this, c] {
while (auto event = xcb_poll_for_event(c)) {
long result = 0;
QThread::currentThread()->eventDispatcher()->filterNativeEvent(QByteArrayLiteral("xcb_generic_event_t"), event, &result);
free(event);
}
xcb_flush(c);
};
connect(notifier, &QSocketNotifier::activated, this, processXcbEvents);
connect(QThread::currentThread()->eventDispatcher(), &QAbstractEventDispatcher::aboutToBlock, this, processXcbEvents);
connect(QThread::currentThread()->eventDispatcher(), &QAbstractEventDispatcher::awake, this, processXcbEvents);
disconnect(Compositor::self(), &Compositor::sceneCreated, this, &ApplicationWayland::continueStartupWithXwayland);
// create selection owner for WM_S0 - magic X display number expected by XWayland
KSelectionOwner owner("WM_S0", c, x11RootWindow());
owner.claim(true);
createAtoms();
setupEventFilters();
// Check whether another windowmanager is running
const uint32_t maskValues[] = {XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT};
ScopedCPointer<xcb_generic_error_t> redirectCheck(xcb_request_check(connection(),
xcb_change_window_attributes_checked(connection(),
rootWindow(),
XCB_CW_EVENT_MASK,
maskValues)));
if (!redirectCheck.isNull()) {
fputs(i18n("kwin_wayland: an X11 window manager is running on the X11 Display.\n").toLocal8Bit().constData(), stderr);
::exit(1);
}
m_environment.insert(QStringLiteral("DISPLAY"), QString::fromUtf8(qgetenv("DISPLAY")));
startSession();
createWorkspace();
Xcb::sync(); // Trigger possible errors, there's still a chance to abort
notifyKSplash();
m_xwayland = new Xwl::Xwayland(this);
connect(m_xwayland, &Xwl::Xwayland::criticalError, this, [](int code) {
// we currently exit on Xwayland errors always directly
// TODO: restart Xwayland
std::cerr << "Xwayland had a critical error. Going to exit now." << std::endl;
exit(code);
});
m_xwayland->init();
}
void ApplicationWayland::startSession()
@ -318,117 +257,6 @@ void ApplicationWayland::startSession()
}
}
void ApplicationWayland::createX11Connection()
{
int screenNumber = 0;
xcb_connection_t *c = nullptr;
if (m_xcbConnectionFd == -1) {
c = xcb_connect(nullptr, &screenNumber);
} else {
c = xcb_connect_to_fd(m_xcbConnectionFd, nullptr);
}
if (int error = xcb_connection_has_error(c)) {
std::cerr << "FATAL ERROR: Creating connection to XServer failed: " << error << std::endl;
exit(1);
return;
}
setX11Connection(c);
// we don't support X11 multi-head in Wayland
setX11ScreenNumber(screenNumber);
setX11RootWindow(defaultScreen()->root);
}
void ApplicationWayland::startXwaylandServer()
{
disconnect(Compositor::self(), &Compositor::sceneCreated, this, &ApplicationWayland::startXwaylandServer);
int pipeFds[2];
if (pipe(pipeFds) != 0) {
std::cerr << "FATAL ERROR failed to create pipe to start Xwayland " << std::endl;
exit(1);
return;
}
int sx[2];
if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sx) < 0) {
std::cerr << "FATAL ERROR: failed to open socket to open XCB connection" << std::endl;
exit(1);
return;
}
int fd = dup(sx[1]);
if (fd < 0) {
std::cerr << "FATAL ERROR: failed to open socket to open XCB connection" << std::endl;
exit(20);
return;
}
const int waylandSocket = waylandServer()->createXWaylandConnection();
if (waylandSocket == -1) {
std::cerr << "FATAL ERROR: failed to open socket for Xwayland" << std::endl;
exit(1);
return;
}
const int wlfd = dup(waylandSocket);
if (wlfd < 0) {
std::cerr << "FATAL ERROR: failed to open socket for Xwayland" << std::endl;
exit(20);
return;
}
m_xcbConnectionFd = sx[0];
m_xwaylandProcess = new Process(kwinApp());
m_xwaylandProcess->setProcessChannelMode(QProcess::ForwardedErrorChannel);
m_xwaylandProcess->setProgram(QStringLiteral("Xwayland"));
QProcessEnvironment env = m_environment;
env.insert("WAYLAND_SOCKET", QByteArray::number(wlfd));
env.insert("EGL_PLATFORM", QByteArrayLiteral("DRM"));
m_xwaylandProcess->setProcessEnvironment(env);
m_xwaylandProcess->setArguments({QStringLiteral("-displayfd"),
QString::number(pipeFds[1]),
QStringLiteral("-rootless"),
QStringLiteral("-wm"),
QString::number(fd)});
m_xwaylandFailConnection = connect(m_xwaylandProcess, static_cast<void (QProcess::*)(QProcess::ProcessError)>(&QProcess::error), this,
[] (QProcess::ProcessError error) {
if (error == QProcess::FailedToStart) {
std::cerr << "FATAL ERROR: failed to start Xwayland" << std::endl;
} else {
std::cerr << "FATAL ERROR: Xwayland failed, going to exit now" << std::endl;
}
exit(1);
}
);
const int xDisplayPipe = pipeFds[0];
connect(m_xwaylandProcess, &QProcess::started, this,
[this, xDisplayPipe] {
QFutureWatcher<void> *watcher = new QFutureWatcher<void>(this);
QObject::connect(watcher, &QFutureWatcher<void>::finished, this, &ApplicationWayland::continueStartupWithX, Qt::QueuedConnection);
QObject::connect(watcher, &QFutureWatcher<void>::finished, watcher, &QFutureWatcher<void>::deleteLater, Qt::QueuedConnection);
watcher->setFuture(QtConcurrent::run(readDisplay, xDisplayPipe));
}
);
m_xwaylandProcess->start();
close(pipeFds[1]);
}
static void readDisplay(int pipe)
{
QFile readPipe;
if (!readPipe.open(pipe, QIODevice::ReadOnly)) {
std::cerr << "FATAL ERROR failed to open pipe to start X Server" << std::endl;
exit(1);
}
QByteArray displayNumber = readPipe.readLine();
displayNumber.prepend(QByteArray(":"));
displayNumber.remove(displayNumber.size() -1, 1);
std::cout << "X-Server started on display " << displayNumber.constData() << std::endl;
setenv("DISPLAY", displayNumber.constData(), true);
// close our pipe
close(pipe);
}
static const QString s_waylandPlugin = QStringLiteral("KWinWaylandWaylandBackend");
static const QString s_x11Plugin = QStringLiteral("KWinWaylandX11Backend");
static const QString s_fbdevPlugin = QStringLiteral("KWinWaylandFbdevBackend");

View file

@ -22,10 +22,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "main.h"
#include <QProcessEnvironment>
class QProcess;
namespace KWin
{
namespace Xwl
{
class Xwayland;
}
class ApplicationWayland : public Application
{
@ -58,22 +60,21 @@ protected:
void performStartup() override;
private:
friend class Xwl::Xwayland;
void createBackend();
void createX11Connection();
void continueStartupWithScreens();
void continueStartupWithSceen();
void continueStartupWithX();
void startXwaylandServer();
void continueStartupWithXwayland();
void startSession();
bool m_startXWayland = false;
int m_xcbConnectionFd = -1;
QStringList m_applicationsToStart;
QString m_inputMethodServerToStart;
QProcess *m_xwaylandProcess = nullptr;
QMetaObject::Connection m_xwaylandFailConnection;
QProcessEnvironment m_environment;
QString m_sessionArgument;
Xwl::Xwayland *m_xwayland = nullptr;
};
}

245
xwl/xwayland.cpp Normal file
View file

@ -0,0 +1,245 @@
/********************************************************************
KWin - the KDE window manager
This file is part of the KDE project.
Copyright 2014 Martin Gräßlin <mgraesslin@kde.org>
Copyright 2019 Roman Gilg <subdiff@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/
#include "xwayland.h"
#include "wayland_server.h"
#include "main_wayland.h"
#include "utils.h"
#include <KLocalizedString>
#include "xcbutils.h"
#include <QAbstractEventDispatcher>
#include <QtConcurrentRun>
#include <QFile>
#include <QFutureWatcher>
#include <QProcess>
#include <QSocketNotifier>
#include <QThread>
// system
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#if HAVE_SYS_PROCCTL_H
#include <unistd.h>
#endif
#include <sys/socket.h>
#include <iostream>
static void readDisplay(int pipe)
{
QFile readPipe;
if (!readPipe.open(pipe, QIODevice::ReadOnly)) {
std::cerr << "FATAL ERROR failed to open pipe to start X Server" << std::endl;
exit(1);
}
QByteArray displayNumber = readPipe.readLine();
displayNumber.prepend(QByteArray(":"));
displayNumber.remove(displayNumber.size() -1, 1);
std::cout << "X-Server started on display " << displayNumber.constData() << std::endl;
setenv("DISPLAY", displayNumber.constData(), true);
// close our pipe
close(pipe);
}
namespace KWin {
namespace Xwl
{
Xwayland::Xwayland(ApplicationWayland *app, QObject *parent)
: QObject(parent),
m_app(app)
{
}
Xwayland::~Xwayland()
{
disconnect(m_xwaylandFailConnection);
if (m_app->x11Connection()) {
Xcb::setInputFocus(XCB_INPUT_FOCUS_POINTER_ROOT);
m_app->destroyAtoms();
Q_EMIT m_app->x11ConnectionAboutToBeDestroyed();
xcb_disconnect(m_app->x11Connection());
m_app->setX11Connection(nullptr);
}
if (m_xwaylandProcess) {
m_xwaylandProcess->terminate();
while (m_xwaylandProcess->state() != QProcess::NotRunning) {
m_app->processEvents(QEventLoop::WaitForMoreEvents);
}
waylandServer()->destroyXWaylandConnection();
}
}
void Xwayland::init()
{
int pipeFds[2];
if (pipe(pipeFds) != 0) {
std::cerr << "FATAL ERROR failed to create pipe to start Xwayland " << std::endl;
Q_EMIT criticalError(1);
return;
}
int sx[2];
if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sx) < 0) {
std::cerr << "FATAL ERROR: failed to open socket to open XCB connection" << std::endl;
Q_EMIT criticalError(1);
return;
}
int fd = dup(sx[1]);
if (fd < 0) {
std::cerr << "FATAL ERROR: failed to open socket to open XCB connection" << std::endl;
Q_EMIT criticalError(20);
return;
}
const int waylandSocket = waylandServer()->createXWaylandConnection();
if (waylandSocket == -1) {
std::cerr << "FATAL ERROR: failed to open socket for Xwayland" << std::endl;
Q_EMIT criticalError(1);
return;
}
const int wlfd = dup(waylandSocket);
if (wlfd < 0) {
std::cerr << "FATAL ERROR: failed to open socket for Xwayland" << std::endl;
Q_EMIT criticalError(20);
return;
}
m_xcbConnectionFd = sx[0];
m_xwaylandProcess = new Process(this);
m_xwaylandProcess->setProcessChannelMode(QProcess::ForwardedErrorChannel);
m_xwaylandProcess->setProgram(QStringLiteral("Xwayland"));
QProcessEnvironment env = m_app->processStartupEnvironment();
env.insert("WAYLAND_SOCKET", QByteArray::number(wlfd));
env.insert("EGL_PLATFORM", QByteArrayLiteral("DRM"));
m_xwaylandProcess->setProcessEnvironment(env);
m_xwaylandProcess->setArguments({QStringLiteral("-displayfd"),
QString::number(pipeFds[1]),
QStringLiteral("-rootless"),
QStringLiteral("-wm"),
QString::number(fd)});
m_xwaylandFailConnection = connect(m_xwaylandProcess, static_cast<void (QProcess::*)(QProcess::ProcessError)>(&QProcess::error), this,
[this] (QProcess::ProcessError error) {
if (error == QProcess::FailedToStart) {
std::cerr << "FATAL ERROR: failed to start Xwayland" << std::endl;
} else {
std::cerr << "FATAL ERROR: Xwayland failed, going to exit now" << std::endl;
}
Q_EMIT criticalError(1);
}
);
const int xDisplayPipe = pipeFds[0];
connect(m_xwaylandProcess, &QProcess::started, this,
[this, xDisplayPipe] {
QFutureWatcher<void> *watcher = new QFutureWatcher<void>(this);
QObject::connect(watcher, &QFutureWatcher<void>::finished, this, &Xwayland::continueStartupWithX, Qt::QueuedConnection);
QObject::connect(watcher, &QFutureWatcher<void>::finished, watcher, &QFutureWatcher<void>::deleteLater, Qt::QueuedConnection);
watcher->setFuture(QtConcurrent::run(readDisplay, xDisplayPipe));
}
);
m_xwaylandProcess->start();
close(pipeFds[1]);
}
void Xwayland::createX11Connection()
{
int screenNumber = 0;
xcb_connection_t *c = nullptr;
if (m_xcbConnectionFd == -1) {
c = xcb_connect(nullptr, &screenNumber);
} else {
c = xcb_connect_to_fd(m_xcbConnectionFd, nullptr);
}
if (int error = xcb_connection_has_error(c)) {
std::cerr << "FATAL ERROR: Creating connection to XServer failed: " << error << std::endl;
Q_EMIT criticalError(1);
return;
}
m_app->setX11Connection(c);
// we don't support X11 multi-head in Wayland
m_app->setX11ScreenNumber(screenNumber);
m_app->setX11RootWindow(defaultScreen()->root);
}
void Xwayland::continueStartupWithX()
{
createX11Connection();
auto *xcbConn = m_app->x11Connection();
if (!xcbConn) {
// about to quit
Q_EMIT criticalError(1);
return;
}
QSocketNotifier *notifier = new QSocketNotifier(xcb_get_file_descriptor(xcbConn), QSocketNotifier::Read, this);
auto processXcbEvents = [this, xcbConn] {
while (auto event = xcb_poll_for_event(xcbConn)) {
long result = 0;
QThread::currentThread()->eventDispatcher()->filterNativeEvent(QByteArrayLiteral("xcb_generic_event_t"), event, &result);
free(event);
}
xcb_flush(xcbConn);
};
connect(notifier, &QSocketNotifier::activated, this, processXcbEvents);
connect(QThread::currentThread()->eventDispatcher(), &QAbstractEventDispatcher::aboutToBlock, this, processXcbEvents);
connect(QThread::currentThread()->eventDispatcher(), &QAbstractEventDispatcher::awake, this, processXcbEvents);
// create selection owner for WM_S0 - magic X display number expected by XWayland
KSelectionOwner owner("WM_S0", xcbConn, m_app->x11RootWindow());
owner.claim(true);
m_app->createAtoms();
m_app->setupEventFilters();
// Check whether another windowmanager is running
const uint32_t maskValues[] = {XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT};
ScopedCPointer<xcb_generic_error_t> redirectCheck(xcb_request_check(connection(),
xcb_change_window_attributes_checked(connection(),
rootWindow(),
XCB_CW_EVENT_MASK,
maskValues)));
if (!redirectCheck.isNull()) {
fputs(i18n("kwin_wayland: an X11 window manager is running on the X11 Display.\n").toLocal8Bit().constData(), stderr);
Q_EMIT criticalError(1);
return;
}
auto env = m_app->processStartupEnvironment();
env.insert(QStringLiteral("DISPLAY"), QString::fromUtf8(qgetenv("DISPLAY")));
m_app->setProcessStartupEnvironment(env);
m_app->startSession();
m_app->createWorkspace();
Xcb::sync(); // Trigger possible errors, there's still a chance to abort
m_app->notifyKSplash();
}
}
}

62
xwl/xwayland.h Normal file
View file

@ -0,0 +1,62 @@
/********************************************************************
KWin - the KDE window manager
This file is part of the KDE project.
Copyright 2019 Roman Gilg <subdiff@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/
#ifndef KWIN_XWL_XWAYLAND
#define KWIN_XWL_XWAYLAND
#include <QObject>
class QProcess;
class xcb_screen_t;
namespace KWin
{
class ApplicationWayland;
namespace Xwl
{
class Xwayland : public QObject
{
Q_OBJECT
public:
Xwayland(ApplicationWayland *app, QObject *parent = nullptr);
virtual ~Xwayland();
void init();
Q_SIGNALS:
void criticalError(int code);
private:
void createX11Connection();
void continueStartupWithX();
int m_xcbConnectionFd = -1;
QProcess *m_xwaylandProcess = nullptr;
QMetaObject::Connection m_xwaylandFailConnection;
ApplicationWayland *m_app;
};
}
}
#endif