diff --git a/autotests/wayland/CMakeLists.txt b/autotests/wayland/CMakeLists.txt index bc2a0fe230..7244f9ef7b 100644 --- a/autotests/wayland/CMakeLists.txt +++ b/autotests/wayland/CMakeLists.txt @@ -178,3 +178,14 @@ if (XCB_ICCCM_FOUND) add_test(kwin-testDontCrashAuroraeDestroyDeco testDontCrashAuroraeDestroyDeco) ecm_mark_as_test(testDontCrashAuroraeDestroyDeco) endif() + +######################################################## +# PlasmaWindow test +######################################################## +if (XCB_ICCCM_FOUND) + set( testPlasmaWindow_SRCS plasmawindow_test.cpp kwin_wayland_test.cpp ) + add_executable(testPlasmaWindow ${testPlasmaWindow_SRCS}) + target_link_libraries( testPlasmaWindow kwin Qt5::Test XCB::ICCCM) + add_test(kwin-testPlasmaWindow testPlasmaWindow) + ecm_mark_as_test(testPlasmaWindow) +endif() diff --git a/autotests/wayland/plasmawindow_test.cpp b/autotests/wayland/plasmawindow_test.cpp new file mode 100644 index 0000000000..b52d0100d5 --- /dev/null +++ b/autotests/wayland/plasmawindow_test.cpp @@ -0,0 +1,333 @@ +/******************************************************************** +KWin - the KDE window manager +This file is part of the KDE project. + +Copyright (C) 2016 Martin Gräßlin + +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 "platform.h" +#include "client.h" +#include "cursor.h" +#include "screenedge.h" +#include "screens.h" +#include "wayland_server.h" +#include "workspace.h" +#include "shell_client.h" +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +using namespace KWayland::Client; + +namespace KWin +{ + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_plasma-window-0"); + +class PlasmaWindowTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + void testCreateDestroyX11PlasmaWindow(); + void testInternalWindowNoPlasmaWindow(); + void testPopupWindowNoPlasmaWindow(); + +private: + ConnectionThread *m_connection = nullptr; + PlasmaWindowManagement *m_windowManagement = nullptr; + KWayland::Client::Compositor *m_compositor = nullptr; + Shell *m_shell = nullptr; + ShmPool *m_shm = nullptr; + EventQueue *m_queue = nullptr; + QThread *m_thread = nullptr; +}; + +void PlasmaWindowTest::initTestCase() +{ + qRegisterMetaType(); + qRegisterMetaType(); + QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); + QVERIFY(workspaceCreatedSpy.isValid()); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setOutputCount", Qt::DirectConnection, Q_ARG(int, 2)); + waylandServer()->init(s_socketName.toLocal8Bit()); + + kwinApp()->start(); + QVERIFY(workspaceCreatedSpy.wait()); + QCOMPARE(screens()->count(), 2); + QCOMPARE(screens()->geometry(0), QRect(0, 0, 1280, 1024)); + QCOMPARE(screens()->geometry(1), QRect(1280, 0, 1280, 1024)); + setenv("QT_QPA_PLATFORM", "wayland", true); + waylandServer()->initWorkspace(); +} + +void PlasmaWindowTest::init() +{ + // setup connection + m_connection = new ConnectionThread; + QSignalSpy connectedSpy(m_connection, &ConnectionThread::connected); + QVERIFY(connectedSpy.isValid()); + m_connection->setSocketName(s_socketName); + + m_thread = new QThread(this); + m_connection->moveToThread(m_thread); + m_thread->start(); + + m_connection->initConnection(); + QVERIFY(connectedSpy.wait()); + + m_queue = new EventQueue(this); + QVERIFY(!m_queue->isValid()); + m_queue->setup(m_connection); + QVERIFY(m_queue->isValid()); + + Registry registry; + registry.setEventQueue(m_queue); + QSignalSpy allAnnounced(®istry, &Registry::interfacesAnnounced); + QVERIFY(allAnnounced.isValid()); + registry.create(m_connection->display()); + QVERIFY(registry.isValid()); + registry.setup(); + QVERIFY(allAnnounced.wait()); + + m_windowManagement = registry.createPlasmaWindowManagement(registry.interface(Registry::Interface::PlasmaWindowManagement).name, + registry.interface(Registry::Interface::PlasmaWindowManagement).version, + this); + QVERIFY(m_windowManagement); + m_compositor = registry.createCompositor(registry.interface(Registry::Interface::Compositor).name, + registry.interface(Registry::Interface::Compositor).version, + this); + QVERIFY(m_compositor); + m_shm = registry.createShmPool(registry.interface(Registry::Interface::Shm).name, + registry.interface(Registry::Interface::Shm).version, + this); + QVERIFY(m_shm); + m_shell = registry.createShell(registry.interface(Registry::Interface::Shell).name, + registry.interface(Registry::Interface::Shell).version, + this); + QVERIFY(m_shell); + + screens()->setCurrent(0); + Cursor::setPos(QPoint(640, 512)); +} + +void PlasmaWindowTest::cleanup() +{ +#define CLEANUP(name) \ + if (name) { \ + delete name; \ + name = nullptr; \ + } + CLEANUP(m_windowManagement) + CLEANUP(m_shm) + CLEANUP(m_shell) + CLEANUP(m_compositor) + CLEANUP(m_queue) +#undef CLEANUP + if (m_connection) { + m_connection->deleteLater(); + m_connection = nullptr; + } + if (m_thread) { + m_thread->quit(); + m_thread->wait(); + delete m_thread; + m_thread = nullptr; + } +} + +void PlasmaWindowTest::testCreateDestroyX11PlasmaWindow() +{ + // this test verifies that a PlasmaWindow gets unmapped on Client side when an X11 client is destroyed + QSignalSpy plasmaWindowCreatedSpy(m_windowManagement, &PlasmaWindowManagement::windowCreated); + QVERIFY(plasmaWindowCreatedSpy.isValid()); + + // create an xcb window + struct XcbConnectionDeleter + { + static inline void cleanup(xcb_connection_t *pointer) + { + xcb_disconnect(pointer); + } + }; + QScopedPointer c(xcb_connect(nullptr, nullptr)); + QVERIFY(!xcb_connection_has_error(c.data())); + const QRect windowGeometry(0, 0, 100, 200); + xcb_window_t w = xcb_generate_id(c.data()); + xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, 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_set_wm_normal_hints(c.data(), w, &hints); + xcb_map_window(c.data(), w); + xcb_flush(c.data()); + + // we should get a client for it + QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded); + QVERIFY(windowCreatedSpy.isValid()); + QVERIFY(windowCreatedSpy.wait()); + Client *client = windowCreatedSpy.first().first().value(); + QVERIFY(client); + QCOMPARE(client->window(), w); + QVERIFY(client->isDecorated()); + QVERIFY(client->isActive()); + + // now that should also give it to us on client side + QVERIFY(plasmaWindowCreatedSpy.wait()); + QCOMPARE(plasmaWindowCreatedSpy.count(), 1); + QCOMPARE(m_windowManagement->windows().count(), 1); + + QSignalSpy unmappedSpy(m_windowManagement->windows().first(), &PlasmaWindow::unmapped); + QVERIFY(unmappedSpy.isValid()); + QSignalSpy destroyedSpy(m_windowManagement->windows().first(), &QObject::destroyed); + QVERIFY(destroyedSpy.isValid()); + + // now shade the window + const QRect geoBeforeShade = client->geometry(); + QVERIFY(geoBeforeShade.isValid()); + QVERIFY(!geoBeforeShade.isEmpty()); + workspace()->slotWindowShade(); + QVERIFY(client->isShade()); + QVERIFY(client->geometry() != geoBeforeShade); + // and unshade again + workspace()->slotWindowShade(); + QVERIFY(!client->isShade()); + QCOMPARE(client->geometry(), geoBeforeShade); + + // and destroy the window again + xcb_unmap_window(c.data(), w); + xcb_flush(c.data()); + + QSignalSpy windowClosedSpy(client, &Client::windowClosed); + QVERIFY(windowClosedSpy.isValid()); + QVERIFY(windowClosedSpy.wait()); + xcb_destroy_window(c.data(), w); + c.reset(); + + QVERIFY(unmappedSpy.wait()); + QCOMPARE(unmappedSpy.count(), 1); + + QVERIFY(destroyedSpy.wait()); +} + +class HelperWindow : public QRasterWindow +{ + Q_OBJECT +public: + HelperWindow(); + ~HelperWindow(); + +protected: + void paintEvent(QPaintEvent *event) override; +}; + +HelperWindow::HelperWindow() + : QRasterWindow(nullptr) +{ +} + +HelperWindow::~HelperWindow() = default; + +void HelperWindow::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event) + QPainter p(this); + p.fillRect(0, 0, width(), height(), Qt::red); +} + +void PlasmaWindowTest::testInternalWindowNoPlasmaWindow() +{ + // this test verifies that an internal window is not added as a PlasmaWindow to the client + QSignalSpy plasmaWindowCreatedSpy(m_windowManagement, &PlasmaWindowManagement::windowCreated); + QVERIFY(plasmaWindowCreatedSpy.isValid()); + HelperWindow win; + win.setGeometry(0, 0, 100, 100); + win.show(); + + QEXPECT_FAIL("", "Internal windows still exposed", Continue); + QVERIFY(!plasmaWindowCreatedSpy.wait()); +} + +void PlasmaWindowTest::testPopupWindowNoPlasmaWindow() +{ + // this test verifies that for a popup window no PlasmaWindow is sent to the client + QSignalSpy plasmaWindowCreatedSpy(m_windowManagement, &PlasmaWindowManagement::windowCreated); + QVERIFY(plasmaWindowCreatedSpy.isValid()); + + // first create the parent window + QScopedPointer parentSurface(m_compositor->createSurface()); + QScopedPointer parentShellSurface(m_shell->createSurface(parentSurface.data())); + // map that window + QImage img(QSize(100, 50), QImage::Format_ARGB32); + img.fill(Qt::blue); + parentSurface->attachBuffer(m_shm->createBuffer(img)); + parentSurface->damage(QRect(0, 0, 100, 50)); + parentSurface->commit(); + // this should create a plasma window + QVERIFY(plasmaWindowCreatedSpy.wait()); + + // now let's create a popup window for it + QScopedPointer popupSurface(m_compositor->createSurface()); + QScopedPointer popupShellSurface(m_shell->createSurface(popupSurface.data())); + popupShellSurface->setTransient(parentSurface.data(), QPoint(0, 0), ShellSurface::TransientFlag::NoFocus); + // let's map it + popupSurface->attachBuffer(m_shm->createBuffer(img)); + popupSurface->damage(QRect(0, 0, 100, 50)); + popupSurface->commit(); + + // this should not create a plasma window + QEXPECT_FAIL("", "Popup windows are still exposed", Continue); + QVERIFY(!plasmaWindowCreatedSpy.wait()); + + // now the same with an already mapped surface when we create the shell surface + QScopedPointer popup2Surface(m_compositor->createSurface()); + popup2Surface->attachBuffer(m_shm->createBuffer(img)); + popup2Surface->damage(QRect(0, 0, 100, 50)); + popup2Surface->commit(); + QScopedPointer popup2ShellSurface(m_shell->createSurface(popup2Surface.data())); + popup2ShellSurface->setTransient(popupSurface.data(), QPoint(0, 0), ShellSurface::TransientFlag::NoFocus); + + // this should not create a plasma window + QEXPECT_FAIL("", "Popup windows are still exposed", Continue); + QVERIFY(!plasmaWindowCreatedSpy.wait()); +} + +} + +WAYLANDTEST_MAIN(KWin::PlasmaWindowTest) +#include "plasmawindow_test.moc"