From f2873dcd36b863d4654aac0605aeab28417a3e28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Gr=C3=A4=C3=9Flin?= Date: Thu, 3 Mar 2016 13:32:50 +0100 Subject: [PATCH] [autotest] Add test for transient placement positioning Test creates windows and transients for it and verifies the position. For decorated windows the position is currently broken as it does not consider the adjusted client position. --- autotests/wayland/CMakeLists.txt | 9 + autotests/wayland/transient_placement.cpp | 307 ++++++++++++++++++++++ 2 files changed, 316 insertions(+) create mode 100644 autotests/wayland/transient_placement.cpp 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"