diff --git a/autotests/integration/CMakeLists.txt b/autotests/integration/CMakeLists.txt --- a/autotests/integration/CMakeLists.txt +++ b/autotests/integration/CMakeLists.txt @@ -1,6 +1,6 @@ add_subdirectory(helper) -add_library(KWinIntegrationTestFramework STATIC kwin_wayland_test.cpp test_helpers.cpp) +add_library(KWinIntegrationTestFramework STATIC ../../xwl/xwayland.cpp kwin_wayland_test.cpp test_helpers.cpp) target_link_libraries(KWinIntegrationTestFramework kwin Qt5::Test) function(integrationTest) diff --git a/autotests/integration/kwin_wayland_test.h b/autotests/integration/kwin_wayland_test.h --- a/autotests/integration/kwin_wayland_test.h +++ b/autotests/integration/kwin_wayland_test.h @@ -52,11 +52,15 @@ namespace KWin { +namespace Xwl +{ +class Xwayland; +} class AbstractClient; class ShellClient; -class WaylandTestApplication : public Application +class WaylandTestApplication : public ApplicationWaylandAbstract { Q_OBJECT public: @@ -68,15 +72,11 @@ private: void createBackend(); - void createX11Connection(); void continueStartupWithScreens(); void continueStartupWithSceen(); - void continueStartupWithX(); - void startXwaylandServer(); + void continueStartupWithXwayland(); - int m_xcbConnectionFd = -1; - QProcess *m_xwaylandProcess = nullptr; - QMetaObject::Connection m_xwaylandFailConnection; + Xwl::Xwayland *m_xwayland = nullptr; }; namespace Test diff --git a/autotests/integration/kwin_wayland_test.cpp b/autotests/integration/kwin_wayland_test.cpp --- a/autotests/integration/kwin_wayland_test.cpp +++ b/autotests/integration/kwin_wayland_test.cpp @@ -24,6 +24,7 @@ #include "../../wayland_server.h" #include "../../workspace.h" #include "../../xcbutils.h" +#include "../../xwl/xwayland.h" #include @@ -42,10 +43,8 @@ namespace KWin { -static void readDisplay(int pipe); - WaylandTestApplication::WaylandTestApplication(OperationMode mode, int &argc, char **argv) - : Application(mode, argc, argv) + : ApplicationWaylandAbstract(mode, argc, argv) { QStandardPaths::setTestModeEnabled(true); // TODO: add a test move to kglobalaccel instead? @@ -72,6 +71,7 @@ } initPlatform(plugins.first()); WaylandServer::create(this); + setProcessStartupEnvironment(QProcessEnvironment::systemEnvironment()); } WaylandTestApplication::~WaylandTestApplication() @@ -84,24 +84,11 @@ } 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; waylandServer()->terminateClientConnections(); destroyCompositor(); } @@ -141,174 +128,27 @@ return; } createCompositor(); - connect(Compositor::self(), &Compositor::sceneCreated, this, &WaylandTestApplication::startXwaylandServer); + connect(Compositor::self(), &Compositor::sceneCreated, this, &WaylandTestApplication::continueStartupWithXwayland); } void WaylandTestApplication::continueStartupWithSceen() { disconnect(Compositor::self(), &Compositor::sceneCreated, this, &WaylandTestApplication::continueStartupWithSceen); createWorkspace(); } -void WaylandTestApplication::continueStartupWithX() -{ - 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)) { - updateX11Time(event); - long result = 0; - if (QThread::currentThread()->eventDispatcher()->filterNativeEvent(QByteArrayLiteral("xcb_generic_event_t"), event, &result)) { - free(event); - continue; - } - if (Workspace::self()) { - Workspace::self()->workspaceEvent(event); - } - 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()) { - ::exit(1); - } - - createWorkspace(); - - Xcb::sync(); // Trigger possible errors, there's still a chance to abort -} - -void WaylandTestApplication::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 WaylandTestApplication::startXwaylandServer() +void WaylandTestApplication::continueStartupWithXwayland() { - disconnect(Compositor::self(), &Compositor::sceneCreated, this, &WaylandTestApplication::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 QProcess(kwinApp()); - m_xwaylandProcess->setProgram(QStringLiteral("Xwayland")); - QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); - env.insert("WAYLAND_SOCKET", QByteArray::number(wlfd)); - 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, &WaylandTestApplication::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); + disconnect(Compositor::self(), &Compositor::sceneCreated, this, &WaylandTestApplication::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(); } } diff --git a/main.h b/main.h --- a/main.h +++ b/main.h @@ -259,6 +259,27 @@ return static_cast(QCoreApplication::instance()); } +namespace Xwl +{ +class Xwayland; +} + +class KWIN_EXPORT ApplicationWaylandAbstract : public Application +{ + Q_OBJECT +public: + virtual ~ApplicationWaylandAbstract() = 0; +protected: + friend class Xwl::Xwayland; + + ApplicationWaylandAbstract(OperationMode mode, int &argc, char **argv); + virtual void setProcessStartupEnvironment(const QProcessEnvironment &environment) { + Q_UNUSED(environment); + } + virtual void startSession() {} +}; + + } // namespace #endif diff --git a/main.cpp b/main.cpp --- a/main.cpp +++ b/main.cpp @@ -456,5 +456,14 @@ } } +ApplicationWaylandAbstract::ApplicationWaylandAbstract(OperationMode mode, int &argc, char **argv) + : Application(mode, argc, argv) +{ +} + +ApplicationWaylandAbstract::~ApplicationWaylandAbstract() +{ +} + } // namespace diff --git a/main_wayland.h b/main_wayland.h --- a/main_wayland.h +++ b/main_wayland.h @@ -29,7 +29,7 @@ class Xwayland; } -class ApplicationWayland : public Application +class ApplicationWayland : public ApplicationWaylandAbstract { Q_OBJECT public: @@ -45,7 +45,7 @@ void setInputMethodServerToStart(const QString &inputMethodServer) { m_inputMethodServerToStart = inputMethodServer; } - void setProcessStartupEnvironment(const QProcessEnvironment &environment) { + void setProcessStartupEnvironment(const QProcessEnvironment &environment) override { m_environment = environment; } void setSessionArgument(const QString &session) { @@ -60,13 +60,11 @@ void performStartup() override; private: - friend class Xwl::Xwayland; - void createBackend(); void continueStartupWithScreens(); void continueStartupWithSceen(); void continueStartupWithXwayland(); - void startSession(); + void startSession() override; bool m_startXWayland = false; QStringList m_applicationsToStart; diff --git a/main_wayland.cpp b/main_wayland.cpp --- a/main_wayland.cpp +++ b/main_wayland.cpp @@ -110,7 +110,7 @@ //************************************ ApplicationWayland::ApplicationWayland(int &argc, char **argv) - : Application(OperationModeWaylandOnly, argc, argv) + : ApplicationWaylandAbstract(OperationModeWaylandOnly, argc, argv) { } @@ -212,7 +212,7 @@ if (!m_inputMethodServerToStart.isEmpty()) { int socket = dup(waylandServer()->createInputMethodConnection()); if (socket >= 0) { - QProcessEnvironment environment = m_environment; + QProcessEnvironment environment = processStartupEnvironment(); environment.insert(QStringLiteral("WAYLAND_SOCKET"), QByteArray::number(socket)); environment.insert(QStringLiteral("QT_QPA_PLATFORM"), QStringLiteral("wayland")); environment.remove("DISPLAY"); @@ -238,7 +238,7 @@ if (!m_sessionArgument.isEmpty()) { QProcess *p = new Process(this); p->setProcessChannelMode(QProcess::ForwardedErrorChannel); - p->setProcessEnvironment(m_environment); + p->setProcessEnvironment(processStartupEnvironment()); auto finishedSignal = static_cast(&QProcess::finished); connect(p, finishedSignal, this, &ApplicationWayland::quit); p->start(m_sessionArgument); @@ -250,7 +250,7 @@ // this is going to happen anyway as we are the wayland and X server the app connects to QProcess *p = new Process(this); p->setProcessChannelMode(QProcess::ForwardedErrorChannel); - p->setProcessEnvironment(m_environment); + p->setProcessEnvironment(processStartupEnvironment()); p->start(application); } } diff --git a/xwl/xwayland.h b/xwl/xwayland.h --- a/xwl/xwayland.h +++ b/xwl/xwayland.h @@ -28,16 +28,16 @@ namespace KWin { -class ApplicationWayland; +class ApplicationWaylandAbstract; namespace Xwl { class Xwayland : public QObject { Q_OBJECT public: - Xwayland(ApplicationWayland *app, QObject *parent = nullptr); + Xwayland(ApplicationWaylandAbstract *app, QObject *parent = nullptr); virtual ~Xwayland(); void init(); @@ -53,7 +53,7 @@ QProcess *m_xwaylandProcess = nullptr; QMetaObject::Connection m_xwaylandFailConnection; - ApplicationWayland *m_app; + ApplicationWaylandAbstract *m_app; }; } diff --git a/xwl/xwayland.cpp b/xwl/xwayland.cpp --- a/xwl/xwayland.cpp +++ b/xwl/xwayland.cpp @@ -69,7 +69,7 @@ namespace Xwl { -Xwayland::Xwayland(ApplicationWayland *app, QObject *parent) +Xwayland::Xwayland(ApplicationWaylandAbstract *app, QObject *parent) : QObject(parent), m_app(app) {