diff --git a/autotests/integration/CMakeLists.txt b/autotests/integration/CMakeLists.txt index 59bc3e132a..b552630dd3 100644 --- a/autotests/integration/CMakeLists.txt +++ b/autotests/integration/CMakeLists.txt @@ -78,6 +78,7 @@ if (XCB_ICCCM_FOUND) integrationTest(NAME testSceneQPainterShadow SRCS scene_qpainter_shadow_test.cpp LIBS XCB::ICCCM) integrationTest(NAME testStackingOrder SRCS stacking_order_test.cpp LIBS XCB::ICCCM) integrationTest(NAME testDbusInterface SRCS dbus_interface_test.cpp LIBS XCB::ICCCM) + integrationTest(NAME testXwaylandServer SRCS xwaylandserver_test.cpp LIBS XCB::ICCCM) if (KWIN_BUILD_ACTIVITIES) integrationTest(NAME testActivities SRCS activities_test.cpp LIBS XCB::ICCCM) diff --git a/autotests/integration/xwaylandserver_test.cpp b/autotests/integration/xwaylandserver_test.cpp new file mode 100644 index 0000000000..22bf31e99a --- /dev/null +++ b/autotests/integration/xwaylandserver_test.cpp @@ -0,0 +1,139 @@ +/******************************************************************** +KWin - the KDE window manager +This file is part of the KDE project. + +Copyright (C) 2020 Vlad Zahorodnii + +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 "kwin_wayland_test.h" +#include "main.h" +#include "platform.h" +#include "screens.h" +#include "unmanaged.h" +#include "wayland_server.h" +#include "workspace.h" +#include "x11client.h" +#include "xwl/xwayland_interface.h" + +#include + +namespace KWin +{ + +struct XcbConnectionDeleter +{ + static inline void cleanup(xcb_connection_t *pointer) + { + xcb_disconnect(pointer); + } +}; + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_xwayland_server-0"); + +class XwaylandServerTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void testCrash(); +}; + +void XwaylandServerTest::initTestCase() +{ + qRegisterMetaType(); + qRegisterMetaType(); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + QVERIFY(applicationStartedSpy.isValid()); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + QCOMPARE(screens()->count(), 2); + QCOMPARE(screens()->geometry(0), QRect(0, 0, 1280, 1024)); + QCOMPARE(screens()->geometry(1), QRect(1280, 0, 1280, 1024)); + waylandServer()->initWorkspace(); +} + +void XwaylandServerTest::testCrash() +{ + // This test verifies that all connected X11 clients get destroyed when Xwayland crashes. + + // Create a normal window. + QScopedPointer c(xcb_connect(nullptr, nullptr)); + QVERIFY(!xcb_connection_has_error(c.data())); + const QRect windowGeometry(0, 0, 100, 200); + xcb_window_t window1 = xcb_generate_id(c.data()); + xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, window1, rootWindow(), + windowGeometry.x(), + windowGeometry.y(), + windowGeometry.width(), + windowGeometry.height(), + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); + xcb_size_hints_t hints; + memset(&hints, 0, sizeof(hints)); + xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); + xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); + xcb_icccm_size_hints_set_min_size(&hints, windowGeometry.width(), windowGeometry.height()); + xcb_icccm_set_wm_normal_hints(c.data(), window1, &hints); + xcb_map_window(c.data(), window1); + xcb_flush(c.data()); + + QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded); + QVERIFY(windowCreatedSpy.isValid()); + QVERIFY(windowCreatedSpy.wait()); + QPointer client = windowCreatedSpy.last().first().value(); + QVERIFY(client); + QVERIFY(client->isDecorated()); + + // Create an override-redirect window. + xcb_window_t window2 = xcb_generate_id(c.data()); + const uint32_t values[] = { true }; + xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, window2, rootWindow(), + windowGeometry.x(), windowGeometry.y(), + windowGeometry.width(), windowGeometry.height(), 0, + XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, + XCB_CW_OVERRIDE_REDIRECT, values); + xcb_map_window(c.data(), window2); + xcb_flush(c.data()); + + QSignalSpy unmanagedAddedSpy(workspace(), &Workspace::unmanagedAdded); + QVERIFY(unmanagedAddedSpy.isValid()); + QVERIFY(unmanagedAddedSpy.wait()); + QPointer unmanaged = unmanagedAddedSpy.last().first().value(); + QVERIFY(unmanaged); + + // Let's pretend that the Xwayland process has crashed. + QSignalSpy x11ConnectionChangedSpy(kwinApp(), &Application::x11ConnectionChanged); + QVERIFY(x11ConnectionChangedSpy.isValid()); + xwayland()->process()->terminate(); + QVERIFY(x11ConnectionChangedSpy.wait()); + + // When Xwayland crashes, the compositor should tear down the XCB connection and destroy + // all connected X11 clients. + QTRY_VERIFY(!client); + QTRY_VERIFY(!unmanaged); + QCOMPARE(kwinApp()->x11Connection(), nullptr); + QCOMPARE(kwinApp()->x11RootWindow(), XCB_WINDOW_NONE); + QCOMPARE(kwinApp()->x11ScreenNumber(), -1); +} + +} // namespace KWin + +WAYLANDTEST_MAIN(KWin::XwaylandServerTest) +#include "xwaylandserver_test.moc" diff --git a/unmanaged.h b/unmanaged.h index 1890c753fb..3194528316 100644 --- a/unmanaged.h +++ b/unmanaged.h @@ -28,8 +28,7 @@ along with this program. If not, see . namespace KWin { -class Unmanaged - : public Toplevel +class KWIN_EXPORT Unmanaged : public Toplevel { Q_OBJECT public: diff --git a/xwl/xwayland.cpp b/xwl/xwayland.cpp index d05f325096..7be8384814 100644 --- a/xwl/xwayland.cpp +++ b/xwl/xwayland.cpp @@ -101,6 +101,11 @@ Xwayland::~Xwayland() s_self = nullptr; } +QProcess *Xwayland::process() const +{ + return m_xwaylandProcess; +} + void Xwayland::init() { int pipeFds[2]; diff --git a/xwl/xwayland.h b/xwl/xwayland.h index 7609e35822..df359d6c55 100644 --- a/xwl/xwayland.h +++ b/xwl/xwayland.h @@ -24,8 +24,6 @@ along with this program. If not, see . #include -class QProcess; - class xcb_screen_t; namespace KWin @@ -56,6 +54,8 @@ public: return m_xfixes; } + QProcess *process() const override; + Q_SIGNALS: void initialized(); void criticalError(int code); diff --git a/xwl/xwayland_interface.h b/xwl/xwayland_interface.h index a7ef7cb987..baa645fc63 100644 --- a/xwl/xwayland_interface.h +++ b/xwl/xwayland_interface.h @@ -25,6 +25,8 @@ along with this program. If not, see . #include #include +class QProcess; + namespace KWin { class Toplevel; @@ -49,6 +51,7 @@ public: static XwaylandInterface *self(); virtual Xwl::DragEventReply dragMoveFilter(Toplevel *target, const QPoint &pos) = 0; + virtual QProcess *process() const = 0; protected: explicit XwaylandInterface(QObject *parent = nullptr);