File indexing completed on 2024-05-12 05:30:40

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 #include "kwin_wayland_test.h"
0010 
0011 #include "core/output.h"
0012 #include "cursor.h"
0013 #include "decorations/decorationbridge.h"
0014 #include "decorations/settings.h"
0015 #include "pointer_input.h"
0016 #include "scripting/scripting.h"
0017 #include "utils/common.h"
0018 #include "wayland_server.h"
0019 #include "window.h"
0020 #include "workspace.h"
0021 #include "x11window.h"
0022 
0023 #include <KDecoration2/DecoratedClient>
0024 #include <KDecoration2/Decoration>
0025 #include <KDecoration2/DecorationSettings>
0026 
0027 #include <KWayland/Client/compositor.h>
0028 #include <KWayland/Client/connection_thread.h>
0029 #include <KWayland/Client/surface.h>
0030 
0031 #include <QDBusConnection>
0032 #include <QDBusMessage>
0033 #include <QDBusPendingCall>
0034 #include <QTemporaryFile>
0035 #include <QTextStream>
0036 
0037 #include <netwm.h>
0038 #include <xcb/xcb_icccm.h>
0039 
0040 #include <linux/input.h>
0041 
0042 Q_DECLARE_METATYPE(KWin::QuickTileMode)
0043 Q_DECLARE_METATYPE(KWin::MaximizeMode)
0044 
0045 namespace KWin
0046 {
0047 
0048 static const QString s_socketName = QStringLiteral("wayland_test_kwin_quick_tiling-0");
0049 
0050 class QuickTilingTest : public QObject
0051 {
0052     Q_OBJECT
0053 private Q_SLOTS:
0054     void initTestCase();
0055     void init();
0056     void cleanup();
0057     void testQuickTiling_data();
0058     void testQuickTiling();
0059     void testQuickMaximizing_data();
0060     void testQuickMaximizing();
0061     void testQuickTilingKeyboardMove_data();
0062     void testQuickTilingKeyboardMove();
0063     void testQuickTilingPointerMove_data();
0064     void testQuickTilingPointerMove();
0065     void testQuickTilingTouchMove_data();
0066     void testQuickTilingTouchMove();
0067     void testX11QuickTiling_data();
0068     void testX11QuickTiling();
0069     void testX11QuickTilingAfterVertMaximize_data();
0070     void testX11QuickTilingAfterVertMaximize();
0071     void testShortcut_data();
0072     void testShortcut();
0073     void testScript_data();
0074     void testScript();
0075 
0076 private:
0077     KWayland::Client::ConnectionThread *m_connection = nullptr;
0078     KWayland::Client::Compositor *m_compositor = nullptr;
0079 };
0080 
0081 void QuickTilingTest::initTestCase()
0082 {
0083     qRegisterMetaType<KWin::Window *>();
0084     qRegisterMetaType<KWin::MaximizeMode>("MaximizeMode");
0085     QSignalSpy applicationStartedSpy(kwinApp(), &Application::started);
0086     QVERIFY(waylandServer()->init(s_socketName));
0087     Test::setOutputConfig({
0088         QRect(0, 0, 1280, 1024),
0089         QRect(1280, 0, 1280, 1024),
0090     });
0091 
0092     // set custom config which disables the Outline
0093     KSharedConfig::Ptr config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig);
0094     KConfigGroup group = config->group(QStringLiteral("Outline"));
0095     group.writeEntry(QStringLiteral("QmlPath"), QString("/does/not/exist.qml"));
0096     group.sync();
0097 
0098     kwinApp()->setConfig(config);
0099 
0100     qputenv("XKB_DEFAULT_RULES", "evdev");
0101 
0102     kwinApp()->start();
0103     QVERIFY(applicationStartedSpy.wait());
0104 
0105     const auto outputs = workspace()->outputs();
0106     QCOMPARE(outputs.count(), 2);
0107     QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024));
0108     QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024));
0109 }
0110 
0111 void QuickTilingTest::init()
0112 {
0113     QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::XdgDecorationV1));
0114     m_connection = Test::waylandConnection();
0115     m_compositor = Test::waylandCompositor();
0116 
0117     workspace()->setActiveOutput(QPoint(640, 512));
0118     input()->pointer()->warp(QPoint(640, 512));
0119 }
0120 
0121 void QuickTilingTest::cleanup()
0122 {
0123     Test::destroyWaylandConnection();
0124 }
0125 
0126 void QuickTilingTest::testQuickTiling_data()
0127 {
0128     QTest::addColumn<QuickTileMode>("mode");
0129     QTest::addColumn<QRectF>("expectedGeometry");
0130     QTest::addColumn<QRectF>("secondScreen");
0131     QTest::addColumn<QuickTileMode>("expectedModeAfterToggle");
0132 
0133 #define FLAG(name) QuickTileMode(QuickTileFlag::name)
0134 
0135     QTest::newRow("left") << FLAG(Left) << QRectF(0, 0, 640, 1024) << QRectF(1280, 0, 640, 1024) << FLAG(Right);
0136     QTest::newRow("top") << FLAG(Top) << QRectF(0, 0, 1280, 512) << QRectF(1280, 0, 1280, 512) << QuickTileMode();
0137     QTest::newRow("right") << FLAG(Right) << QRectF(640, 0, 640, 1024) << QRectF(1920, 0, 640, 1024) << QuickTileMode();
0138     QTest::newRow("bottom") << FLAG(Bottom) << QRectF(0, 512, 1280, 512) << QRectF(1280, 512, 1280, 512) << QuickTileMode();
0139 
0140     QTest::newRow("top left") << (FLAG(Left) | FLAG(Top)) << QRectF(0, 0, 640, 512) << QRectF(1280, 0, 640, 512) << (FLAG(Right) | FLAG(Top));
0141     QTest::newRow("top right") << (FLAG(Right) | FLAG(Top)) << QRectF(640, 0, 640, 512) << QRectF(1920, 0, 640, 512) << QuickTileMode();
0142     QTest::newRow("bottom left") << (FLAG(Left) | FLAG(Bottom)) << QRectF(0, 512, 640, 512) << QRectF(1280, 512, 640, 512) << (FLAG(Right) | FLAG(Bottom));
0143     QTest::newRow("bottom right") << (FLAG(Right) | FLAG(Bottom)) << QRectF(640, 512, 640, 512) << QRectF(1920, 512, 640, 512) << QuickTileMode();
0144 
0145     QTest::newRow("maximize") << FLAG(Maximize) << QRectF(0, 0, 1280, 1024) << QRectF(1280, 0, 1280, 1024) << QuickTileMode();
0146 
0147 #undef FLAG
0148 }
0149 
0150 void QuickTilingTest::testQuickTiling()
0151 {
0152     std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
0153     QVERIFY(surface != nullptr);
0154     std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
0155     QVERIFY(shellSurface != nullptr);
0156 
0157     // Map the window.
0158     auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
0159     QVERIFY(window);
0160     QCOMPARE(workspace()->activeWindow(), window);
0161     QCOMPARE(window->frameGeometry(), QRect(0, 0, 100, 50));
0162     QCOMPARE(window->quickTileMode(), QuickTileMode(QuickTileFlag::None));
0163 
0164     // We have to receive a configure event when the window becomes active.
0165     QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested);
0166     QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested);
0167     QVERIFY(surfaceConfigureRequestedSpy.wait());
0168     QCOMPARE(surfaceConfigureRequestedSpy.count(), 1);
0169 
0170     QSignalSpy quickTileChangedSpy(window, &Window::quickTileModeChanged);
0171     QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged);
0172 
0173     QFETCH(QuickTileMode, mode);
0174     QFETCH(QRectF, expectedGeometry);
0175     window->setQuickTileMode(mode, true);
0176     QCOMPARE(quickTileChangedSpy.count(), 1);
0177     // at this point the geometry did not yet change
0178     QCOMPARE(window->frameGeometry(), QRect(0, 0, 100, 50));
0179     // but quick tile mode already changed
0180     QCOMPARE(window->quickTileMode(), mode);
0181 
0182     // but we got requested a new geometry
0183     QVERIFY(surfaceConfigureRequestedSpy.wait());
0184     QCOMPARE(surfaceConfigureRequestedSpy.count(), 2);
0185     QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), expectedGeometry.size());
0186 
0187     // attach a new image
0188     shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
0189     Test::render(surface.get(), expectedGeometry.size().toSize(), Qt::red);
0190 
0191     QVERIFY(frameGeometryChangedSpy.wait());
0192     QCOMPARE(frameGeometryChangedSpy.count(), 1);
0193     QCOMPARE(window->frameGeometry(), expectedGeometry);
0194 
0195     // send window to other screen
0196     QList<Output *> outputs = workspace()->outputs();
0197     QCOMPARE(window->output(), outputs[0]);
0198     window->sendToOutput(outputs[1]);
0199     QCOMPARE(window->output(), outputs[1]);
0200     // quick tile should not be changed
0201     QCOMPARE(window->quickTileMode(), mode);
0202     QTEST(window->frameGeometry(), "secondScreen");
0203 
0204     // now try to toggle again
0205     window->setQuickTileMode(mode, true);
0206     QTEST(window->quickTileMode(), "expectedModeAfterToggle");
0207 }
0208 
0209 void QuickTilingTest::testQuickMaximizing_data()
0210 {
0211     QTest::addColumn<QuickTileMode>("mode");
0212 
0213 #define FLAG(name) QuickTileMode(QuickTileFlag::name)
0214 
0215     QTest::newRow("maximize") << FLAG(Maximize);
0216     QTest::newRow("none") << FLAG(None);
0217 
0218 #undef FLAG
0219 }
0220 
0221 void QuickTilingTest::testQuickMaximizing()
0222 {
0223     std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
0224     QVERIFY(surface != nullptr);
0225     std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
0226     QVERIFY(shellSurface != nullptr);
0227 
0228     // Map the window.
0229     auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
0230     QVERIFY(window);
0231     QCOMPARE(workspace()->activeWindow(), window);
0232     QCOMPARE(window->frameGeometry(), QRect(0, 0, 100, 50));
0233     QCOMPARE(window->quickTileMode(), QuickTileMode(QuickTileFlag::None));
0234     QCOMPARE(window->maximizeMode(), MaximizeRestore);
0235 
0236     // We have to receive a configure event upon becoming active.
0237     QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested);
0238     QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested);
0239     QVERIFY(surfaceConfigureRequestedSpy.wait());
0240     QCOMPARE(surfaceConfigureRequestedSpy.count(), 1);
0241 
0242     QSignalSpy quickTileChangedSpy(window, &Window::quickTileModeChanged);
0243     QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged);
0244     QSignalSpy maximizeChangedSpy(window, &Window::maximizedChanged);
0245 
0246     window->setQuickTileMode(QuickTileFlag::Maximize, true);
0247     QCOMPARE(quickTileChangedSpy.count(), 1);
0248 
0249     // at this point the geometry did not yet change
0250     QCOMPARE(window->frameGeometry(), QRect(0, 0, 100, 50));
0251     // but quick tile mode already changed
0252     QCOMPARE(window->quickTileMode(), QuickTileFlag::Maximize);
0253     QCOMPARE(window->geometryRestore(), QRect(0, 0, 100, 50));
0254 
0255     // but we got requested a new geometry
0256     QVERIFY(surfaceConfigureRequestedSpy.wait());
0257     QCOMPARE(surfaceConfigureRequestedSpy.count(), 2);
0258     QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(1280, 1024));
0259 
0260     // attach a new image
0261     shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
0262     Test::render(surface.get(), QSize(1280, 1024), Qt::red);
0263 
0264     QVERIFY(frameGeometryChangedSpy.wait());
0265     QCOMPARE(frameGeometryChangedSpy.count(), 1);
0266     QCOMPARE(window->frameGeometry(), QRect(0, 0, 1280, 1024));
0267     QCOMPARE(window->geometryRestore(), QRect(0, 0, 100, 50));
0268 
0269     // window is now set to maximised
0270     QCOMPARE(maximizeChangedSpy.count(), 1);
0271     QCOMPARE(window->maximizeMode(), MaximizeFull);
0272     QCOMPARE(window->tile(), nullptr);
0273 
0274     // go back to quick tile none
0275     QFETCH(QuickTileMode, mode);
0276     window->setQuickTileMode(mode, true);
0277     QCOMPARE(window->quickTileMode(), QuickTileMode(QuickTileFlag::None));
0278     QCOMPARE(quickTileChangedSpy.count(), 2);
0279     // geometry not yet changed
0280     QCOMPARE(window->frameGeometry(), QRect(0, 0, 1280, 1024));
0281     QCOMPARE(window->geometryRestore(), QRect(0, 0, 100, 50));
0282     // we got requested a new geometry
0283     QVERIFY(surfaceConfigureRequestedSpy.wait());
0284     QCOMPARE(surfaceConfigureRequestedSpy.count(), 3);
0285     QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(100, 50));
0286 
0287     // render again
0288     shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
0289     Test::render(surface.get(), QSize(100, 50), Qt::yellow);
0290 
0291     QVERIFY(frameGeometryChangedSpy.wait());
0292     QCOMPARE(frameGeometryChangedSpy.count(), 2);
0293     QCOMPARE(window->frameGeometry(), QRect(0, 0, 100, 50));
0294     QCOMPARE(window->geometryRestore(), QRect(0, 0, 100, 50));
0295     QCOMPARE(maximizeChangedSpy.count(), 2);
0296 }
0297 
0298 void QuickTilingTest::testQuickTilingKeyboardMove_data()
0299 {
0300     QTest::addColumn<QPoint>("targetPos");
0301     QTest::addColumn<QuickTileMode>("expectedMode");
0302 
0303     QTest::newRow("topRight") << QPoint(2559, 24) << QuickTileMode(QuickTileFlag::Top | QuickTileFlag::Right);
0304     QTest::newRow("right") << QPoint(2559, 512) << QuickTileMode(QuickTileFlag::Right);
0305     QTest::newRow("bottomRight") << QPoint(2559, 1023) << QuickTileMode(QuickTileFlag::Bottom | QuickTileFlag::Right);
0306     QTest::newRow("bottomLeft") << QPoint(0, 1023) << QuickTileMode(QuickTileFlag::Bottom | QuickTileFlag::Left);
0307     QTest::newRow("Left") << QPoint(0, 512) << QuickTileMode(QuickTileFlag::Left);
0308     QTest::newRow("topLeft") << QPoint(0, 24) << QuickTileMode(QuickTileFlag::Top | QuickTileFlag::Left);
0309 }
0310 
0311 void QuickTilingTest::testQuickTilingKeyboardMove()
0312 {
0313     std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
0314     QVERIFY(surface != nullptr);
0315 
0316     std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
0317     QVERIFY(shellSurface != nullptr);
0318     // let's render
0319     auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
0320 
0321     QVERIFY(window);
0322     QCOMPARE(workspace()->activeWindow(), window);
0323     QCOMPARE(window->frameGeometry(), QRect(0, 0, 100, 50));
0324     QCOMPARE(window->quickTileMode(), QuickTileMode(QuickTileFlag::None));
0325     QCOMPARE(window->maximizeMode(), MaximizeRestore);
0326 
0327     QSignalSpy quickTileChangedSpy(window, &Window::quickTileModeChanged);
0328 
0329     workspace()->performWindowOperation(window, Options::UnrestrictedMoveOp);
0330     QCOMPARE(window, workspace()->moveResizeWindow());
0331     QCOMPARE(Cursors::self()->mouse()->pos(), QPoint(50, 25));
0332 
0333     QFETCH(QPoint, targetPos);
0334     quint32 timestamp = 1;
0335     Test::keyboardKeyPressed(KEY_LEFTCTRL, timestamp++);
0336     while (Cursors::self()->mouse()->pos().x() > targetPos.x()) {
0337         Test::keyboardKeyPressed(KEY_LEFT, timestamp++);
0338         Test::keyboardKeyReleased(KEY_LEFT, timestamp++);
0339     }
0340     while (Cursors::self()->mouse()->pos().x() < targetPos.x()) {
0341         Test::keyboardKeyPressed(KEY_RIGHT, timestamp++);
0342         Test::keyboardKeyReleased(KEY_RIGHT, timestamp++);
0343     }
0344     while (Cursors::self()->mouse()->pos().y() < targetPos.y()) {
0345         Test::keyboardKeyPressed(KEY_DOWN, timestamp++);
0346         Test::keyboardKeyReleased(KEY_DOWN, timestamp++);
0347     }
0348     while (Cursors::self()->mouse()->pos().y() > targetPos.y()) {
0349         Test::keyboardKeyPressed(KEY_UP, timestamp++);
0350         Test::keyboardKeyReleased(KEY_UP, timestamp++);
0351     }
0352     Test::keyboardKeyReleased(KEY_LEFTCTRL, timestamp++);
0353     Test::keyboardKeyPressed(KEY_ENTER, timestamp++);
0354     Test::keyboardKeyReleased(KEY_ENTER, timestamp++);
0355     QCOMPARE(Cursors::self()->mouse()->pos(), targetPos);
0356     QVERIFY(!workspace()->moveResizeWindow());
0357 
0358     QCOMPARE(quickTileChangedSpy.count(), 1);
0359     QTEST(window->quickTileMode(), "expectedMode");
0360 }
0361 
0362 void QuickTilingTest::testQuickTilingPointerMove_data()
0363 {
0364     QTest::addColumn<QPoint>("pointerPos");
0365     QTest::addColumn<QSize>("tileSize");
0366     QTest::addColumn<QuickTileMode>("expectedMode");
0367 
0368     QTest::newRow("topRight") << QPoint(2559, 24) << QSize(640, 512) << QuickTileMode(QuickTileFlag::Top | QuickTileFlag::Right);
0369     QTest::newRow("right") << QPoint(2559, 512) << QSize(640, 1024) << QuickTileMode(QuickTileFlag::Right);
0370     QTest::newRow("bottomRight") << QPoint(2559, 1023) << QSize(640, 512) << QuickTileMode(QuickTileFlag::Bottom | QuickTileFlag::Right);
0371     QTest::newRow("bottomLeft") << QPoint(0, 1023) << QSize(640, 512) << QuickTileMode(QuickTileFlag::Bottom | QuickTileFlag::Left);
0372     QTest::newRow("Left") << QPoint(0, 512) << QSize(640, 1024) << QuickTileMode(QuickTileFlag::Left);
0373     QTest::newRow("topLeft") << QPoint(0, 24) << QSize(640, 512) << QuickTileMode(QuickTileFlag::Top | QuickTileFlag::Left);
0374 }
0375 
0376 void QuickTilingTest::testQuickTilingPointerMove()
0377 {
0378     std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
0379     std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
0380 
0381     // let's render
0382     auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
0383     QVERIFY(window);
0384     QCOMPARE(workspace()->activeWindow(), window);
0385     QCOMPARE(window->frameGeometry(), QRect(0, 0, 100, 50));
0386     QCOMPARE(window->quickTileMode(), QuickTileMode(QuickTileFlag::None));
0387     QCOMPARE(window->maximizeMode(), MaximizeRestore);
0388 
0389     // we have to receive a configure event when the window becomes active
0390     QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested);
0391     QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested);
0392     QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged);
0393     QVERIFY(surfaceConfigureRequestedSpy.wait());
0394     QCOMPARE(surfaceConfigureRequestedSpy.count(), 1);
0395 
0396     // verify that basic quick tile mode works as expected, i.e. the window is going to be
0397     // tiled if the user drags it to a screen edge or a corner
0398     QSignalSpy quickTileChangedSpy(window, &Window::quickTileModeChanged);
0399     workspace()->performWindowOperation(window, Options::UnrestrictedMoveOp);
0400     QCOMPARE(window, workspace()->moveResizeWindow());
0401     QCOMPARE(Cursors::self()->mouse()->pos(), QPoint(50, 25));
0402 
0403     QFETCH(QPoint, pointerPos);
0404     QFETCH(QSize, tileSize);
0405     quint32 timestamp = 1;
0406     Test::pointerButtonPressed(BTN_LEFT, timestamp++);
0407     Test::pointerMotion(pointerPos, timestamp++);
0408     Test::pointerButtonReleased(BTN_LEFT, timestamp++);
0409     QCOMPARE(quickTileChangedSpy.count(), 1);
0410     QTEST(window->quickTileMode(), "expectedMode");
0411     QCOMPARE(window->geometryRestore(), QRect(0, 0, 100, 50));
0412     QVERIFY(surfaceConfigureRequestedSpy.wait());
0413     QCOMPARE(surfaceConfigureRequestedSpy.count(), 2);
0414     QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), tileSize);
0415 
0416     // attach a new image
0417     shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
0418     Test::render(surface.get(), tileSize, Qt::red);
0419     QVERIFY(frameGeometryChangedSpy.wait());
0420     QCOMPARE(window->frameGeometry().size(), tileSize);
0421 
0422     // verify that geometry restore is correct after user untiles the window, but changes
0423     // their mind and tiles the window again while still holding left button
0424     workspace()->performWindowOperation(window, Options::UnrestrictedMoveOp);
0425     QCOMPARE(window, workspace()->moveResizeWindow());
0426 
0427     Test::pointerButtonPressed(BTN_LEFT, timestamp++); // untile the window
0428     Test::pointerMotion(QPoint(1280, 1024) / 2, timestamp++);
0429     QCOMPARE(quickTileChangedSpy.count(), 2);
0430     QCOMPARE(window->quickTileMode(), QuickTileMode(QuickTileFlag::None));
0431     QVERIFY(surfaceConfigureRequestedSpy.wait());
0432     QCOMPARE(surfaceConfigureRequestedSpy.count(), 3);
0433     QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(100, 50));
0434 
0435     // attach a new image
0436     shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
0437     Test::render(surface.get(), QSize(100, 50), Qt::red);
0438     QVERIFY(frameGeometryChangedSpy.wait());
0439     QCOMPARE(window->frameGeometry().size(), QSize(100, 50));
0440 
0441     Test::pointerMotion(pointerPos, timestamp++); // tile the window again
0442     Test::pointerButtonReleased(BTN_LEFT, timestamp++);
0443     QCOMPARE(quickTileChangedSpy.count(), 3);
0444     QTEST(window->quickTileMode(), "expectedMode");
0445     QCOMPARE(window->geometryRestore(), QRect(0, 0, 100, 50));
0446     QVERIFY(surfaceConfigureRequestedSpy.wait());
0447     QCOMPARE(surfaceConfigureRequestedSpy.count(), 4);
0448     QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), tileSize);
0449 }
0450 
0451 void QuickTilingTest::testQuickTilingTouchMove_data()
0452 {
0453     QTest::addColumn<QPoint>("targetPos");
0454     QTest::addColumn<QuickTileMode>("expectedMode");
0455 
0456     QTest::newRow("topRight") << QPoint(2559, 24) << QuickTileMode(QuickTileFlag::Top | QuickTileFlag::Right);
0457     QTest::newRow("right") << QPoint(2559, 512) << QuickTileMode(QuickTileFlag::Right);
0458     QTest::newRow("bottomRight") << QPoint(2559, 1023) << QuickTileMode(QuickTileFlag::Bottom | QuickTileFlag::Right);
0459     QTest::newRow("bottomLeft") << QPoint(0, 1023) << QuickTileMode(QuickTileFlag::Bottom | QuickTileFlag::Left);
0460     QTest::newRow("Left") << QPoint(0, 512) << QuickTileMode(QuickTileFlag::Left);
0461     QTest::newRow("topLeft") << QPoint(0, 24) << QuickTileMode(QuickTileFlag::Top | QuickTileFlag::Left);
0462 }
0463 
0464 void QuickTilingTest::testQuickTilingTouchMove()
0465 {
0466     // test verifies that touch on decoration also allows quick tiling
0467     // see BUG: 390113
0468     std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
0469     std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly));
0470     std::unique_ptr<Test::XdgToplevelDecorationV1> deco(Test::createXdgToplevelDecorationV1(shellSurface.get()));
0471 
0472     QSignalSpy decorationConfigureRequestedSpy(deco.get(), &Test::XdgToplevelDecorationV1::configureRequested);
0473     QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested);
0474     QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested);
0475 
0476     // wait for the initial configure event
0477     surface->commit(KWayland::Client::Surface::CommitFlag::None);
0478     QVERIFY(surfaceConfigureRequestedSpy.wait());
0479     QCOMPARE(surfaceConfigureRequestedSpy.count(), 1);
0480 
0481     // let's render
0482     shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
0483     auto window = Test::renderAndWaitForShown(surface.get(), QSize(1000, 50), Qt::blue);
0484 
0485     QVERIFY(window);
0486     QVERIFY(window->isDecorated());
0487     const auto decoration = window->decoration();
0488     QCOMPARE(workspace()->activeWindow(), window);
0489     QCOMPARE(window->frameGeometry(), QRect(-decoration->borderLeft(), 0, 1000 + decoration->borderLeft() + decoration->borderRight(), 50 + decoration->borderTop() + decoration->borderBottom()));
0490     QCOMPARE(window->quickTileMode(), QuickTileMode(QuickTileFlag::None));
0491     QCOMPARE(window->maximizeMode(), MaximizeRestore);
0492 
0493     // we have to receive a configure event when the window becomes active
0494     QVERIFY(surfaceConfigureRequestedSpy.wait());
0495     QTRY_COMPARE(surfaceConfigureRequestedSpy.count(), 2);
0496 
0497     QSignalSpy quickTileChangedSpy(window, &Window::quickTileModeChanged);
0498 
0499     // Note that interactive move will be started with a delay.
0500     quint32 timestamp = 1;
0501     QSignalSpy interactiveMoveResizeStartedSpy(window, &Window::interactiveMoveResizeStarted);
0502     Test::touchDown(0, QPointF(window->frameGeometry().center().x(), window->frameGeometry().y() + decoration->borderTop() / 2), timestamp++);
0503     QVERIFY(interactiveMoveResizeStartedSpy.wait());
0504     QCOMPARE(window, workspace()->moveResizeWindow());
0505 
0506     QFETCH(QPoint, targetPos);
0507     Test::touchMotion(0, targetPos, timestamp++);
0508     Test::touchUp(0, timestamp++);
0509     QVERIFY(!workspace()->moveResizeWindow());
0510 
0511     // When there are no borders, there is no change to them when quick-tiling.
0512     // TODO: we should test both cases with fixed fake decoration for autotests.
0513     const bool hasBorders = Workspace::self()->decorationBridge()->settings()->borderSize() != KDecoration2::BorderSize::None;
0514 
0515     QCOMPARE(quickTileChangedSpy.count(), 1);
0516     QTEST(window->quickTileMode(), "expectedMode");
0517     QVERIFY(surfaceConfigureRequestedSpy.wait());
0518     QTRY_COMPARE(surfaceConfigureRequestedSpy.count(), hasBorders ? 4 : 3);
0519     QCOMPARE(false, toplevelConfigureRequestedSpy.last().first().toSize().isEmpty());
0520 }
0521 
0522 void QuickTilingTest::testX11QuickTiling_data()
0523 {
0524     QTest::addColumn<QuickTileMode>("mode");
0525     QTest::addColumn<QRectF>("expectedGeometry");
0526     QTest::addColumn<int>("screenId");
0527     QTest::addColumn<QuickTileMode>("modeAfterToggle");
0528 
0529 #define FLAG(name) QuickTileMode(QuickTileFlag::name)
0530 
0531     QTest::newRow("left") << FLAG(Left) << QRectF(0, 0, 640, 1024) << 0 << QuickTileMode();
0532     QTest::newRow("top") << FLAG(Top) << QRectF(0, 0, 1280, 512) << 0 << QuickTileMode();
0533     QTest::newRow("right") << FLAG(Right) << QRectF(640, 0, 640, 1024) << 1 << FLAG(Left);
0534     QTest::newRow("bottom") << FLAG(Bottom) << QRectF(0, 512, 1280, 512) << 0 << QuickTileMode();
0535 
0536     QTest::newRow("top left") << (FLAG(Left) | FLAG(Top)) << QRectF(0, 0, 640, 512) << 0 << QuickTileMode();
0537     QTest::newRow("top right") << (FLAG(Right) | FLAG(Top)) << QRectF(640, 0, 640, 512) << 1 << (FLAG(Left) | FLAG(Top));
0538     QTest::newRow("bottom left") << (FLAG(Left) | FLAG(Bottom)) << QRectF(0, 512, 640, 512) << 0 << QuickTileMode();
0539     QTest::newRow("bottom right") << (FLAG(Right) | FLAG(Bottom)) << QRectF(640, 512, 640, 512) << 1 << (FLAG(Left) | FLAG(Bottom));
0540 
0541     QTest::newRow("maximize") << FLAG(Maximize) << QRectF(0, 0, 1280, 1024) << 0 << QuickTileMode();
0542 
0543 #undef FLAG
0544 }
0545 void QuickTilingTest::testX11QuickTiling()
0546 {
0547     Test::XcbConnectionPtr c = Test::createX11Connection();
0548     QVERIFY(!xcb_connection_has_error(c.get()));
0549     const QRect windowGeometry(0, 0, 100, 200);
0550     xcb_window_t windowId = xcb_generate_id(c.get());
0551     xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(),
0552                       windowGeometry.x(),
0553                       windowGeometry.y(),
0554                       windowGeometry.width(),
0555                       windowGeometry.height(),
0556                       0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
0557     xcb_size_hints_t hints;
0558     memset(&hints, 0, sizeof(hints));
0559     xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y());
0560     xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());
0561     xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints);
0562     xcb_map_window(c.get(), windowId);
0563     xcb_flush(c.get());
0564 
0565     // we should get a window for it
0566     QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded);
0567     QVERIFY(windowCreatedSpy.wait());
0568     X11Window *window = windowCreatedSpy.first().first().value<X11Window *>();
0569     QVERIFY(window);
0570     QCOMPARE(window->window(), windowId);
0571 
0572     // now quick tile
0573     QSignalSpy quickTileChangedSpy(window, &Window::quickTileModeChanged);
0574     const QRectF origGeo = window->frameGeometry();
0575     QFETCH(QuickTileMode, mode);
0576     window->setQuickTileMode(mode, true);
0577     QCOMPARE(window->quickTileMode(), mode);
0578     QTEST(window->frameGeometry(), "expectedGeometry");
0579     QCOMPARE(window->geometryRestore(), origGeo);
0580     QEXPECT_FAIL("maximize", "For maximize we get two changed signals", Continue);
0581     QCOMPARE(quickTileChangedSpy.count(), 1);
0582 
0583     // quick tile to same edge again should also act like send to screen
0584     // if screen is on the same edge
0585     const auto outputs = workspace()->outputs();
0586     QCOMPARE(window->output(), outputs[0]);
0587     window->setQuickTileMode(mode, true);
0588     QFETCH(int, screenId);
0589     QCOMPARE(window->output(), outputs[screenId]);
0590     QTEST(window->quickTileMode(), "modeAfterToggle");
0591     QCOMPARE(window->geometryRestore(), origGeo);
0592 
0593     // and destroy the window again
0594     xcb_unmap_window(c.get(), windowId);
0595     xcb_destroy_window(c.get(), windowId);
0596     xcb_flush(c.get());
0597     c.reset();
0598 
0599     QSignalSpy windowClosedSpy(window, &X11Window::closed);
0600     QVERIFY(windowClosedSpy.wait());
0601 }
0602 
0603 void QuickTilingTest::testX11QuickTilingAfterVertMaximize_data()
0604 {
0605     QTest::addColumn<QuickTileMode>("mode");
0606     QTest::addColumn<QRectF>("expectedGeometry");
0607 
0608 #define FLAG(name) QuickTileMode(QuickTileFlag::name)
0609 
0610     QTest::newRow("left") << FLAG(Left) << QRectF(0, 0, 640, 1024);
0611     QTest::newRow("top") << FLAG(Top) << QRectF(0, 0, 1280, 512);
0612     QTest::newRow("right") << FLAG(Right) << QRectF(640, 0, 640, 1024);
0613     QTest::newRow("bottom") << FLAG(Bottom) << QRectF(0, 512, 1280, 512);
0614 
0615     QTest::newRow("top left") << (FLAG(Left) | FLAG(Top)) << QRectF(0, 0, 640, 512);
0616     QTest::newRow("top right") << (FLAG(Right) | FLAG(Top)) << QRectF(640, 0, 640, 512);
0617     QTest::newRow("bottom left") << (FLAG(Left) | FLAG(Bottom)) << QRectF(0, 512, 640, 512);
0618     QTest::newRow("bottom right") << (FLAG(Right) | FLAG(Bottom)) << QRectF(640, 512, 640, 512);
0619 
0620     QTest::newRow("maximize") << FLAG(Maximize) << QRectF(0, 0, 1280, 1024);
0621 
0622 #undef FLAG
0623 }
0624 
0625 void QuickTilingTest::testX11QuickTilingAfterVertMaximize()
0626 {
0627     Test::XcbConnectionPtr c = Test::createX11Connection();
0628     QVERIFY(!xcb_connection_has_error(c.get()));
0629     const QRect windowGeometry(0, 0, 100, 200);
0630     xcb_window_t windowId = xcb_generate_id(c.get());
0631     xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(),
0632                       windowGeometry.x(),
0633                       windowGeometry.y(),
0634                       windowGeometry.width(),
0635                       windowGeometry.height(),
0636                       0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
0637     xcb_size_hints_t hints;
0638     memset(&hints, 0, sizeof(hints));
0639     xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y());
0640     xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());
0641     xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints);
0642     xcb_map_window(c.get(), windowId);
0643     xcb_flush(c.get());
0644 
0645     // we should get a window for it
0646     QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded);
0647     QVERIFY(windowCreatedSpy.wait());
0648     X11Window *window = windowCreatedSpy.first().first().value<X11Window *>();
0649     QVERIFY(window);
0650     QCOMPARE(window->window(), windowId);
0651 
0652     const QRectF origGeo = window->frameGeometry();
0653     QCOMPARE(window->maximizeMode(), MaximizeRestore);
0654     // vertically maximize the window
0655     window->maximize(window->maximizeMode() ^ MaximizeVertical);
0656     QCOMPARE(window->frameGeometry().width(), origGeo.width());
0657     QCOMPARE(window->height(), window->output()->geometry().height());
0658     QCOMPARE(window->geometryRestore(), origGeo);
0659 
0660     // now quick tile
0661     QSignalSpy quickTileChangedSpy(window, &Window::quickTileModeChanged);
0662     QFETCH(QuickTileMode, mode);
0663     window->setQuickTileMode(mode, true);
0664     QCOMPARE(window->quickTileMode(), mode);
0665     QTEST(window->frameGeometry(), "expectedGeometry");
0666     QEXPECT_FAIL("", "We get two changed events", Continue);
0667     QCOMPARE(quickTileChangedSpy.count(), 1);
0668 
0669     // and destroy the window again
0670     xcb_unmap_window(c.get(), windowId);
0671     xcb_destroy_window(c.get(), windowId);
0672     xcb_flush(c.get());
0673     c.reset();
0674 
0675     QSignalSpy windowClosedSpy(window, &X11Window::closed);
0676     QVERIFY(windowClosedSpy.wait());
0677 }
0678 
0679 void QuickTilingTest::testShortcut_data()
0680 {
0681     QTest::addColumn<QStringList>("shortcutList");
0682     QTest::addColumn<QuickTileMode>("expectedMode");
0683     QTest::addColumn<QRect>("expectedGeometry");
0684 
0685 #define FLAG(name) QuickTileMode(QuickTileFlag::name)
0686     QTest::newRow("top") << QStringList{QStringLiteral("Window Quick Tile Top")} << FLAG(Top) << QRect(0, 0, 1280, 512);
0687     QTest::newRow("bottom") << QStringList{QStringLiteral("Window Quick Tile Bottom")} << FLAG(Bottom) << QRect(0, 512, 1280, 512);
0688     QTest::newRow("top right") << QStringList{QStringLiteral("Window Quick Tile Top Right")} << (FLAG(Top) | FLAG(Right)) << QRect(640, 0, 640, 512);
0689     QTest::newRow("top left") << QStringList{QStringLiteral("Window Quick Tile Top Left")} << (FLAG(Top) | FLAG(Left)) << QRect(0, 0, 640, 512);
0690     QTest::newRow("bottom right") << QStringList{QStringLiteral("Window Quick Tile Bottom Right")} << (FLAG(Bottom) | FLAG(Right)) << QRect(640, 512, 640, 512);
0691     QTest::newRow("bottom left") << QStringList{QStringLiteral("Window Quick Tile Bottom Left")} << (FLAG(Bottom) | FLAG(Left)) << QRect(0, 512, 640, 512);
0692     QTest::newRow("left") << QStringList{QStringLiteral("Window Quick Tile Left")} << FLAG(Left) << QRect(0, 0, 640, 1024);
0693     QTest::newRow("right") << QStringList{QStringLiteral("Window Quick Tile Right")} << FLAG(Right) << QRect(640, 0, 640, 1024);
0694 
0695     // Test combined actions for corner tiling
0696     QTest::newRow("top left combined") << QStringList{QStringLiteral("Window Quick Tile Left"), QStringLiteral("Window Quick Tile Top")} << (FLAG(Top) | FLAG(Left)) << QRect(0, 0, 640, 512);
0697     QTest::newRow("top right combined") << QStringList{QStringLiteral("Window Quick Tile Right"), QStringLiteral("Window Quick Tile Top")} << (FLAG(Top) | FLAG(Right)) << QRect(640, 0, 640, 512);
0698     QTest::newRow("bottom left combined") << QStringList{QStringLiteral("Window Quick Tile Left"), QStringLiteral("Window Quick Tile Bottom")} << (FLAG(Bottom) | FLAG(Left)) << QRect(0, 512, 640, 512);
0699     QTest::newRow("bottom right combined") << QStringList{QStringLiteral("Window Quick Tile Right"), QStringLiteral("Window Quick Tile Bottom")} << (FLAG(Bottom) | FLAG(Right)) << QRect(640, 512, 640, 512);
0700 #undef FLAG
0701 }
0702 
0703 void QuickTilingTest::testShortcut()
0704 {
0705 #if !KWIN_BUILD_GLOBALSHORTCUTS
0706     QSKIP("Can't test shortcuts without shortcuts");
0707     return;
0708 #endif
0709 
0710     std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
0711     QVERIFY(surface != nullptr);
0712     std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
0713     QVERIFY(shellSurface != nullptr);
0714 
0715     // Map the window.
0716     auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
0717     QVERIFY(window);
0718     QCOMPARE(workspace()->activeWindow(), window);
0719     QCOMPARE(window->frameGeometry(), QRect(0, 0, 100, 50));
0720     QCOMPARE(window->quickTileMode(), QuickTileMode(QuickTileFlag::None));
0721 
0722     // We have to receive a configure event when the window becomes active.
0723     QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested);
0724     QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested);
0725     QVERIFY(surfaceConfigureRequestedSpy.wait());
0726     QCOMPARE(surfaceConfigureRequestedSpy.count(), 1);
0727 
0728     QFETCH(QStringList, shortcutList);
0729     QFETCH(QRect, expectedGeometry);
0730 
0731     const int numberOfQuickTileActions = shortcutList.count();
0732 
0733     for (QString shortcut : shortcutList) {
0734         // invoke global shortcut through dbus
0735         auto msg = QDBusMessage::createMethodCall(
0736             QStringLiteral("org.kde.kglobalaccel"),
0737             QStringLiteral("/component/kwin"),
0738             QStringLiteral("org.kde.kglobalaccel.Component"),
0739             QStringLiteral("invokeShortcut"));
0740         msg.setArguments(QList<QVariant>{shortcut});
0741         QDBusConnection::sessionBus().asyncCall(msg);
0742     }
0743 
0744     QSignalSpy quickTileChangedSpy(window, &Window::quickTileModeChanged);
0745     QTRY_COMPARE(quickTileChangedSpy.count(), numberOfQuickTileActions);
0746     // at this point the geometry did not yet change
0747     QCOMPARE(window->frameGeometry(), QRect(0, 0, 100, 50));
0748     // but quick tile mode already changed
0749     QTEST(window->quickTileMode(), "expectedMode");
0750 
0751     // but we got requested a new geometry
0752     QVERIFY(surfaceConfigureRequestedSpy.wait());
0753     QCOMPARE(surfaceConfigureRequestedSpy.count(), 2);
0754     QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), expectedGeometry.size());
0755 
0756     // attach a new image
0757     QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged);
0758     shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
0759     Test::render(surface.get(), expectedGeometry.size(), Qt::red);
0760 
0761     QVERIFY(frameGeometryChangedSpy.wait());
0762     QEXPECT_FAIL("maximize", "Geometry changed called twice for maximize", Continue);
0763     QCOMPARE(frameGeometryChangedSpy.count(), 1);
0764     QCOMPARE(window->frameGeometry(), expectedGeometry);
0765 }
0766 
0767 void QuickTilingTest::testScript_data()
0768 {
0769     QTest::addColumn<QString>("action");
0770     QTest::addColumn<QuickTileMode>("expectedMode");
0771     QTest::addColumn<QRect>("expectedGeometry");
0772 
0773 #define FLAG(name) QuickTileMode(QuickTileFlag::name)
0774     QTest::newRow("top") << QStringLiteral("Top") << FLAG(Top) << QRect(0, 0, 1280, 512);
0775     QTest::newRow("bottom") << QStringLiteral("Bottom") << FLAG(Bottom) << QRect(0, 512, 1280, 512);
0776     QTest::newRow("top right") << QStringLiteral("TopRight") << (FLAG(Top) | FLAG(Right)) << QRect(640, 0, 640, 512);
0777     QTest::newRow("top left") << QStringLiteral("TopLeft") << (FLAG(Top) | FLAG(Left)) << QRect(0, 0, 640, 512);
0778     QTest::newRow("bottom right") << QStringLiteral("BottomRight") << (FLAG(Bottom) | FLAG(Right)) << QRect(640, 512, 640, 512);
0779     QTest::newRow("bottom left") << QStringLiteral("BottomLeft") << (FLAG(Bottom) | FLAG(Left)) << QRect(0, 512, 640, 512);
0780     QTest::newRow("left") << QStringLiteral("Left") << FLAG(Left) << QRect(0, 0, 640, 1024);
0781     QTest::newRow("right") << QStringLiteral("Right") << FLAG(Right) << QRect(640, 0, 640, 1024);
0782 #undef FLAG
0783 }
0784 
0785 void QuickTilingTest::testScript()
0786 {
0787     std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
0788     QVERIFY(surface != nullptr);
0789     std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
0790     QVERIFY(shellSurface != nullptr);
0791 
0792     // Map the window.
0793     auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
0794     QVERIFY(window);
0795     QCOMPARE(workspace()->activeWindow(), window);
0796     QCOMPARE(window->frameGeometry(), QRect(0, 0, 100, 50));
0797     QCOMPARE(window->quickTileMode(), QuickTileMode(QuickTileFlag::None));
0798 
0799     // We have to receive a configure event upon the window becoming active.
0800     QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested);
0801     QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested);
0802     QVERIFY(surfaceConfigureRequestedSpy.wait());
0803     QCOMPARE(surfaceConfigureRequestedSpy.count(), 1);
0804 
0805     QSignalSpy quickTileChangedSpy(window, &Window::quickTileModeChanged);
0806     QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged);
0807 
0808     QVERIFY(Scripting::self());
0809     QTemporaryFile tmpFile;
0810     QVERIFY(tmpFile.open());
0811     QTextStream out(&tmpFile);
0812 
0813     QFETCH(QString, action);
0814     out << "workspace.slotWindowQuickTile" << action << "()";
0815     out.flush();
0816 
0817     QFETCH(QuickTileMode, expectedMode);
0818     QFETCH(QRect, expectedGeometry);
0819 
0820     const int id = Scripting::self()->loadScript(tmpFile.fileName());
0821     QVERIFY(id != -1);
0822     QVERIFY(Scripting::self()->isScriptLoaded(tmpFile.fileName()));
0823     auto s = Scripting::self()->findScript(tmpFile.fileName());
0824     QVERIFY(s);
0825     QSignalSpy runningChangedSpy(s, &AbstractScript::runningChanged);
0826     s->run();
0827 
0828     QVERIFY(quickTileChangedSpy.wait());
0829     QCOMPARE(quickTileChangedSpy.count(), 1);
0830 
0831     QCOMPARE(runningChangedSpy.count(), 1);
0832     QCOMPARE(runningChangedSpy.first().first().toBool(), true);
0833 
0834     // at this point the geometry did not yet change
0835     QCOMPARE(window->frameGeometry(), QRect(0, 0, 100, 50));
0836     // but quick tile mode already changed
0837     QCOMPARE(window->quickTileMode(), expectedMode);
0838 
0839     // but we got requested a new geometry
0840     QVERIFY(surfaceConfigureRequestedSpy.wait());
0841     QCOMPARE(surfaceConfigureRequestedSpy.count(), 2);
0842     QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), expectedGeometry.size());
0843 
0844     // attach a new image
0845     shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
0846     Test::render(surface.get(), expectedGeometry.size(), Qt::red);
0847 
0848     QVERIFY(frameGeometryChangedSpy.wait());
0849     QEXPECT_FAIL("maximize", "Geometry changed called twice for maximize", Continue);
0850     QCOMPARE(frameGeometryChangedSpy.count(), 1);
0851     QCOMPARE(window->frameGeometry(), expectedGeometry);
0852 }
0853 
0854 }
0855 
0856 WAYLANDTEST_MAIN(KWin::QuickTilingTest)
0857 #include "quick_tiling_test.moc"