diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -651,7 +651,13 @@ 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}) diff --git a/main_wayland.h b/main_wayland.h --- a/main_wayland.h +++ b/main_wayland.h @@ -22,10 +22,12 @@ #include "main.h" #include -class QProcess; - namespace KWin { +namespace Xwl +{ +class Xwayland; +} class ApplicationWayland : public Application { @@ -58,22 +60,21 @@ 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; }; } diff --git a/main_wayland.cpp b/main_wayland.cpp --- a/main_wayland.cpp +++ b/main_wayland.cpp @@ -27,7 +27,7 @@ #include "effects.h" #include "tabletmodemanager.h" #include "wayland_server.h" -#include "xcbutils.h" +#include "xwl/xwayland.h" // KWayland #include @@ -41,29 +41,18 @@ // Qt #include -#include #include -#include -#include #include -#include #include -#include #include -#include #include #include // system -#ifdef HAVE_UNISTD_H -#include -#endif // HAVE_UNISTD_H - #if HAVE_SYS_PRCTL_H #include #endif #if HAVE_SYS_PROCCTL_H -#include #include #endif @@ -92,8 +81,6 @@ // that would enable drkonqi Q_CONSTRUCTOR_FUNCTION(disableDrKonqi) -static void readDisplay(int pipe); - enum class RealTimeFlags { DontReset, @@ -143,24 +130,13 @@ } 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 @@ 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 @@ 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); - - // 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 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(); + disconnect(Compositor::self(), &Compositor::sceneCreated, this, &ApplicationWayland::continueStartupWithXwayland); + + 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::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(&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 *watcher = new QFutureWatcher(this); - QObject::connect(watcher, &QFutureWatcher::finished, this, &ApplicationWayland::continueStartupWithX, Qt::QueuedConnection); - QObject::connect(watcher, &QFutureWatcher::finished, watcher, &QFutureWatcher::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"); diff --git a/xwl/xwayland.h b/xwl/xwayland.h new file mode 100644 --- /dev/null +++ b/xwl/xwayland.h @@ -0,0 +1,62 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright 2019 Roman Gilg + +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 . +*********************************************************************/ +#ifndef KWIN_XWL_XWAYLAND +#define KWIN_XWL_XWAYLAND + +#include + +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 diff --git a/xwl/xwayland.cpp b/xwl/xwayland.cpp new file mode 100644 --- /dev/null +++ b/xwl/xwayland.cpp @@ -0,0 +1,245 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright 2014 Martin Gräßlin +Copyright 2019 Roman Gilg + +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 . +*********************************************************************/ +#include "xwayland.h" +#include "wayland_server.h" +#include "main_wayland.h" +#include "utils.h" + +#include + +#include "xcbutils.h" + +#include +#include +#include +#include +#include +#include +#include + +// system +#ifdef HAVE_UNISTD_H +#include +#endif +#if HAVE_SYS_PROCCTL_H +#include +#endif + +#include +#include + +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(&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 *watcher = new QFutureWatcher(this); + QObject::connect(watcher, &QFutureWatcher::finished, this, &Xwayland::continueStartupWithX, Qt::QueuedConnection); + QObject::connect(watcher, &QFutureWatcher::finished, watcher, &QFutureWatcher::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 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(); +} + +} +}