diff --git a/autotests/wayland/CMakeLists.txt b/autotests/wayland/CMakeLists.txt --- a/autotests/wayland/CMakeLists.txt +++ b/autotests/wayland/CMakeLists.txt @@ -207,3 +207,12 @@ target_link_libraries( testMaximized kwin Qt5::Test) add_test(kwin-testMaximized testMaximized) ecm_mark_as_test(testMaximized) + +######################################################## +# Shell Client Test +######################################################## +set( testShellClient_SRCS shell_client_test.cpp kwin_wayland_test.cpp ) +add_executable(testShellClient ${testShellClient_SRCS}) +target_link_libraries( testShellClient kwin Qt5::Test) +add_test(kwin-testShellClient testShellClient) +ecm_mark_as_test(testShellClient) diff --git a/autotests/wayland/shell_client_test.cpp b/autotests/wayland/shell_client_test.cpp new file mode 100644 --- /dev/null +++ b/autotests/wayland/shell_client_test.cpp @@ -0,0 +1,208 @@ +/******************************************************************** +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 "cursor.h" +#include "platform.h" +#include "shell_client.h" +#include "screens.h" +#include "wayland_server.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +using namespace KWin; +using namespace KWayland::Client; + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_shell_client-0"); + +class TestShellClient : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + + void testMapUnmapMap(); + +private: + KWayland::Client::Compositor *m_compositor = nullptr; + Shell *m_shell = nullptr; + ShmPool *m_shm = nullptr; + EventQueue *m_queue = nullptr; + ConnectionThread *m_connection = nullptr; + QThread *m_thread = nullptr; +}; + +void TestShellClient::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)); + waylandServer()->initWorkspace(); +} + +void TestShellClient::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_compositor = registry.createCompositor(registry.interface(Registry::Interface::Compositor).name, + registry.interface(Registry::Interface::Compositor).version, + this); + QVERIFY(m_compositor->isValid()); + m_shm = registry.createShmPool(registry.interface(Registry::Interface::Shm).name, + registry.interface(Registry::Interface::Shm).version, + this); + QVERIFY(m_shm->isValid()); + m_shell = registry.createShell(registry.interface(Registry::Interface::Shell).name, + registry.interface(Registry::Interface::Shell).version, + this); + QVERIFY(m_shell->isValid()); + + screens()->setCurrent(0); + KWin::Cursor::setPos(QPoint(1280, 512)); +} + +void TestShellClient::cleanup() +{ +#define CLEANUP(name) \ + if (name) { \ + delete name; \ + name = nullptr; \ + } + CLEANUP(m_compositor) + CLEANUP(m_shm) + CLEANUP(m_shell) + CLEANUP(m_queue) + + if (m_connection) { + m_connection->deleteLater(); + m_connection = nullptr; + } + if (m_thread) { + m_thread->quit(); + m_thread->wait(); + delete m_thread; + m_thread = nullptr; + } +#undef CLEANUP +} + +void TestShellClient::testMapUnmapMap() +{ + // this test verifies that mapping a previously mapped window works correctly + QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); + QVERIFY(clientAddedSpy.isValid()); + + QScopedPointer surface(m_compositor->createSurface()); + QScopedPointer shellSurface(m_shell->createSurface(surface.data())); + + // now let's render + QImage img(QSize(100, 50), QImage::Format_ARGB32); + img.fill(Qt::blue); + surface->attachBuffer(m_shm->createBuffer(img)); + surface->damage(QRect(0, 0, 100, 50)); + surface->commit(Surface::CommitFlag::None); + + QVERIFY(clientAddedSpy.isEmpty()); + QVERIFY(clientAddedSpy.wait()); + auto client = clientAddedSpy.first().first().value(); + QVERIFY(client); + QVERIFY(client->isShown(true)); + + // now unmap + QSignalSpy hiddenSpy(client, &ShellClient::windowHidden); + QVERIFY(hiddenSpy.isValid()); + QSignalSpy windowClosedSpy(client, &ShellClient::windowClosed); + QVERIFY(windowClosedSpy.isValid()); + surface->attachBuffer(Buffer::Ptr()); + surface->commit(Surface::CommitFlag::None); + QVERIFY(hiddenSpy.wait()); + QVERIFY(windowClosedSpy.isEmpty()); + + QSignalSpy windowShownSpy(client, &ShellClient::windowShown); + QVERIFY(windowShownSpy.isValid()); + surface->attachBuffer(m_shm->createBuffer(img)); + surface->damage(QRect(0, 0, 100, 50)); + surface->commit(Surface::CommitFlag::None); + QCOMPARE(clientAddedSpy.count(), 1); + QVERIFY(windowShownSpy.wait()); + QCOMPARE(windowShownSpy.count(), 1); + QCOMPARE(clientAddedSpy.count(), 1); + + // let's unmap again + surface->attachBuffer(Buffer::Ptr()); + surface->commit(Surface::CommitFlag::None); + QVERIFY(hiddenSpy.wait()); + QCOMPARE(hiddenSpy.count(), 2); + QVERIFY(windowClosedSpy.isEmpty()); + + shellSurface.reset(); + surface.reset(); + QVERIFY(windowClosedSpy.wait()); + QCOMPARE(windowClosedSpy.count(), 1); +} + +WAYLANDTEST_MAIN(TestShellClient) +#include "shell_client_test.moc" diff --git a/wayland_server.h b/wayland_server.h --- a/wayland_server.h +++ b/wayland_server.h @@ -59,6 +59,7 @@ class ShellClient; class AbstractClient; +class Toplevel; class KWIN_EXPORT WaylandServer : public QObject { @@ -159,6 +160,7 @@ void terminatingInternalClientConnection(); private: + void shellClientShown(Toplevel *t); void initOutputs(); quint16 createClientId(KWayland::Server::ClientConnection *c); void destroyInternalConnection(); diff --git a/wayland_server.cpp b/wayland_server.cpp --- a/wayland_server.cpp +++ b/wayland_server.cpp @@ -167,11 +167,7 @@ if (client->readyForPainting()) { emit shellClientAdded(client); } else { - connect(client, &ShellClient::windowShown, this, - [this, client] { - emit shellClientAdded(client); - } - ); + connect(client, &ShellClient::windowShown, this, &WaylandServer::shellClientShown); } } ); @@ -249,6 +245,17 @@ m_display->createSubCompositor(m_display)->create(); } +void WaylandServer::shellClientShown(Toplevel *t) +{ + ShellClient *c = dynamic_cast(t); + if (!c) { + qCWarning(KWIN_CORE) << "Failed to cast a Toplevel which is supposed to be a ShellClient to ShellClient"; + return; + } + disconnect(c, &ShellClient::windowShown, this, &WaylandServer::shellClientShown); + emit shellClientAdded(c); +} + void WaylandServer::initWorkspace() { if (m_windowManagement) {