diff --git a/autotests/wayland/CMakeLists.txt b/autotests/wayland/CMakeLists.txt index f1a2333ad6..d46f6dff88 100644 --- a/autotests/wayland/CMakeLists.txt +++ b/autotests/wayland/CMakeLists.txt @@ -116,3 +116,12 @@ add_executable(testDontCrashCancelAnimation ${testDontCrashCancelAnimation_SRCS} target_link_libraries( testDontCrashCancelAnimation kwin Qt5::Test) add_test(kwin-testDontCrashCancelAnimation testDontCrashCancelAnimation) ecm_mark_as_test(testDontCrashCancelAnimation) + +######################################################## +# Transient Placement Test +######################################################## +set( testTransientPlacmenet_SRCS transient_placement.cpp kwin_wayland_test.cpp ) +add_executable(testTransientPlacmenet ${testTransientPlacmenet_SRCS}) +target_link_libraries( testTransientPlacmenet kwin Qt5::Test) +add_test(kwin-testTransientPlacmenet testTransientPlacmenet) +ecm_mark_as_test(testTransientPlacmenet) diff --git a/autotests/wayland/transient_placement.cpp b/autotests/wayland/transient_placement.cpp new file mode 100644 index 0000000000..a8cc736b4f --- /dev/null +++ b/autotests/wayland/transient_placement.cpp @@ -0,0 +1,307 @@ +/******************************************************************** +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 "abstract_backend.h" +#include "abstract_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 +#include +#include + +namespace KWin +{ + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_transient_placement-0"); + +class TransientPlacementTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + void testSimplePosition_data(); + void testSimplePosition(); + void testDecorationPosition_data(); + void testDecorationPosition(); + +private: + AbstractClient *showWindow(const QSize &size, bool decorated = false, KWayland::Client::Surface *parent = nullptr, const QPoint &offset = QPoint()); + KWayland::Client::Surface *surfaceForClient(AbstractClient *c) const; + KWayland::Client::ConnectionThread *m_connection = nullptr; + KWayland::Client::Compositor *m_compositor = nullptr; + KWayland::Client::ServerSideDecorationManager *m_deco = nullptr; + KWayland::Client::Seat *m_seat = nullptr; + KWayland::Client::ShmPool *m_shm = nullptr; + KWayland::Client::Shell *m_shell = nullptr; + KWayland::Client::EventQueue *m_queue = nullptr; + QThread *m_thread = nullptr; +}; + +void TransientPlacementTest::initTestCase() +{ + qRegisterMetaType(); + qRegisterMetaType(); + QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); + QVERIFY(workspaceCreatedSpy.isValid()); + waylandServer()->backend()->setInitialWindowSize(QSize(1280, 1024)); + QMetaObject::invokeMethod(waylandServer()->backend(), "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 TransientPlacementTest::init() +{ + using namespace KWayland::Client; + // 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 compositorSpy(®istry, &Registry::compositorAnnounced); + QSignalSpy shmSpy(®istry, &Registry::shmAnnounced); + QSignalSpy shellSpy(®istry, &Registry::shellAnnounced); + QSignalSpy seatSpy(®istry, &Registry::seatAnnounced); + QSignalSpy allAnnounced(®istry, &Registry::interfacesAnnounced); + QVERIFY(allAnnounced.isValid()); + QVERIFY(shmSpy.isValid()); + QVERIFY(shellSpy.isValid()); + QVERIFY(compositorSpy.isValid()); + QVERIFY(seatSpy.isValid()); + registry.create(m_connection->display()); + QVERIFY(registry.isValid()); + registry.setup(); + QVERIFY(allAnnounced.wait()); + QVERIFY(!compositorSpy.isEmpty()); + QVERIFY(!shmSpy.isEmpty()); + QVERIFY(!shellSpy.isEmpty()); + QVERIFY(!seatSpy.isEmpty()); + + m_compositor = registry.createCompositor(compositorSpy.first().first().value(), compositorSpy.first().last().value(), this); + QVERIFY(m_compositor->isValid()); + m_shm = registry.createShmPool(shmSpy.first().first().value(), shmSpy.first().last().value(), this); + QVERIFY(m_shm->isValid()); + m_shell = registry.createShell(shellSpy.first().first().value(), shellSpy.first().last().value(), this); + QVERIFY(m_shell->isValid()); + m_seat = registry.createSeat(seatSpy.first().first().value(), seatSpy.first().last().value(), this); + QVERIFY(m_seat->isValid()); + QSignalSpy hasPointerSpy(m_seat, &Seat::hasPointerChanged); + QVERIFY(hasPointerSpy.isValid()); + QVERIFY(hasPointerSpy.wait()); + + m_deco = registry.createServerSideDecorationManager(registry.interface(Registry::Interface::ServerSideDecorationManager).name, registry.interface(Registry::Interface::ServerSideDecorationManager).version, this); + QVERIFY(m_deco->isValid()); + + screens()->setCurrent(0); + Cursor::setPos(QPoint(640, 512)); +} + +void TransientPlacementTest::cleanup() +{ + delete m_deco; + m_deco = nullptr; + delete m_compositor; + m_compositor = nullptr; + delete m_seat; + m_seat = nullptr; + delete m_shm; + m_shm = nullptr; + delete m_shell; + m_shell = nullptr; + delete m_queue; + m_queue = nullptr; + if (m_thread) { + m_connection->deleteLater(); + m_thread->quit(); + m_thread->wait(); + delete m_thread; + m_thread = nullptr; + m_connection = nullptr; + } +} + +AbstractClient *TransientPlacementTest::showWindow(const QSize &size, bool decorated, KWayland::Client::Surface *parent, const QPoint &offset) +{ + using namespace KWayland::Client; +#define VERIFY(statement) \ + if (!QTest::qVerify((statement), #statement, "", __FILE__, __LINE__))\ + return nullptr; +#define COMPARE(actual, expected) \ + if (!QTest::qCompare(actual, expected, #actual, #expected, __FILE__, __LINE__))\ + return nullptr; + QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); + VERIFY(clientAddedSpy.isValid()); + + Surface *surface = m_compositor->createSurface(m_compositor); + VERIFY(surface); + ShellSurface *shellSurface = m_shell->createSurface(surface, surface); + VERIFY(shellSurface); + if (parent) { + shellSurface->setTransient(parent, offset); + } + if (decorated) { + auto deco = m_deco->create(surface, surface); + QSignalSpy decoSpy(deco, &ServerSideDecoration::modeChanged); + VERIFY(decoSpy.isValid()); + VERIFY(decoSpy.wait()); + deco->requestMode(ServerSideDecoration::Mode::Server); + VERIFY(decoSpy.wait()); + COMPARE(deco->mode(), ServerSideDecoration::Mode::Server); + } + // let's render + QImage img(size, QImage::Format_ARGB32); + img.fill(Qt::blue); + surface->attachBuffer(m_shm->createBuffer(img)); + surface->damage(QRect(QPoint(0, 0), size)); + surface->commit(Surface::CommitFlag::None); + + m_connection->flush(); + VERIFY(clientAddedSpy.wait()); + AbstractClient *c = workspace()->activeClient(); + VERIFY(c); + COMPARE(clientAddedSpy.first().first().value(), c); + +#undef VERIFY +#undef COMPARE + + return c; +} + +KWayland::Client::Surface *TransientPlacementTest::surfaceForClient(AbstractClient *c) const +{ + const auto &surfaces = KWayland::Client::Surface::all(); + auto it = std::find_if(surfaces.begin(), surfaces.end(), [c] (KWayland::Client::Surface *s) { return s->id() == c->surface()->id(); }); + if (it != surfaces.end()) { + return *it; + } + return nullptr; +} + +void TransientPlacementTest::testSimplePosition_data() +{ + QTest::addColumn("parentSize"); + QTest::addColumn("parentPosition"); + QTest::addColumn("transientSize"); + QTest::addColumn("transientOffset"); + QTest::addColumn("expectedGeometry"); + + QTest::newRow("0/0") << QSize(640, 512) << QPoint(0, 0) << QSize(10, 100) << QPoint(0, 0) << QRect(0, 0, 10, 100); + QTest::newRow("bottomRight") << QSize(640, 512) << QPoint(0, 0) << QSize(10, 100) << QPoint(639, 511) << QRect(639, 511, 10, 100); + QTest::newRow("offset") << QSize(640, 512) << QPoint(200, 300) << QSize(100, 10) << QPoint(320, 256) << QRect(520, 556, 100, 10); +} + +void TransientPlacementTest::testSimplePosition() +{ + // this test verifies that the position of a transient window is taken from the passed position + // there are no further constraints like window too large to fit screen, cascading transients, etc + QFETCH(QSize, parentSize); + AbstractClient *parent = showWindow(parentSize); + QVERIFY(parent->clientPos().isNull()); + QVERIFY(!parent->isDecorated()); + QFETCH(QPoint, parentPosition); + parent->move(parentPosition); + QFETCH(QSize, transientSize); + QFETCH(QPoint, transientOffset); + AbstractClient *transient = showWindow(transientSize, false, surfaceForClient(parent), transientOffset); + QVERIFY(transient); + QVERIFY(!transient->isDecorated()); + QVERIFY(transient->hasTransientPlacementHint()); + QTEST(transient->geometry(), "expectedGeometry"); +} + +void TransientPlacementTest::testDecorationPosition_data() +{ + QTest::addColumn("parentSize"); + QTest::addColumn("parentPosition"); + QTest::addColumn("transientSize"); + QTest::addColumn("transientOffset"); + QTest::addColumn("expectedGeometry"); + + QTest::newRow("0/0") << QSize(640, 512) << QPoint(0, 0) << QSize(10, 100) << QPoint(0, 0) << QRect(0, 0, 10, 100); + QTest::newRow("bottomRight") << QSize(640, 512) << QPoint(0, 0) << QSize(10, 100) << QPoint(639, 511) << QRect(639, 511, 10, 100); + QTest::newRow("offset") << QSize(640, 512) << QPoint(200, 300) << QSize(100, 10) << QPoint(320, 256) << QRect(520, 556, 100, 10); +} + +void TransientPlacementTest::testDecorationPosition() +{ + // this test verifies that a transient window is correctly placed if the parent window has a + // server side decoration + QFETCH(QSize, parentSize); + AbstractClient *parent = showWindow(parentSize, true); + QVERIFY(!parent->clientPos().isNull()); + QVERIFY(parent->isDecorated()); + QFETCH(QPoint, parentPosition); + parent->move(parentPosition); + QFETCH(QSize, transientSize); + QFETCH(QPoint, transientOffset); + AbstractClient *transient = showWindow(transientSize, false, surfaceForClient(parent), transientOffset); + QVERIFY(transient); + QVERIFY(!transient->isDecorated()); + QVERIFY(transient->hasTransientPlacementHint()); + QFETCH(QRect, expectedGeometry); + expectedGeometry.translate(parent->clientPos()); + QEXPECT_FAIL("", "Fix me", Continue); + QCOMPARE(transient->geometry(), expectedGeometry); +} + +} + +WAYLANDTEST_MAIN(KWin::TransientPlacementTest) +#include "transient_placement.moc"