File indexing completed on 2024-05-05 17:35:56

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2016 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 "core/outputbackend.h"
0013 #include "cursor.h"
0014 #include "deleted.h"
0015 #include "effects.h"
0016 #include "options.h"
0017 #include "pointer_input.h"
0018 #include "screenedge.h"
0019 #include "utils/xcursortheme.h"
0020 #include "virtualdesktops.h"
0021 #include "wayland/clientconnection.h"
0022 #include "wayland/seat_interface.h"
0023 #include "wayland_server.h"
0024 #include "window.h"
0025 #include "workspace.h"
0026 #include <kwineffects.h>
0027 
0028 #include <KWayland/Client/buffer.h>
0029 #include <KWayland/Client/compositor.h>
0030 #include <KWayland/Client/connection_thread.h>
0031 #include <KWayland/Client/pointer.h>
0032 #include <KWayland/Client/region.h>
0033 #include <KWayland/Client/seat.h>
0034 #include <KWayland/Client/server_decoration.h>
0035 #include <KWayland/Client/shm_pool.h>
0036 #include <KWayland/Client/surface.h>
0037 
0038 #include <linux/input.h>
0039 
0040 namespace KWin
0041 {
0042 
0043 static PlatformCursorImage loadReferenceThemeCursor_helper(const KXcursorTheme &theme,
0044                                                            const QByteArray &name)
0045 {
0046     const QVector<KXcursorSprite> sprites = theme.shape(name);
0047     if (sprites.isEmpty()) {
0048         return PlatformCursorImage();
0049     }
0050 
0051     return PlatformCursorImage(sprites.constFirst().data(), sprites.constFirst().hotspot());
0052 }
0053 
0054 static PlatformCursorImage loadReferenceThemeCursor(const QByteArray &name)
0055 {
0056     const Cursor *pointerCursor = Cursors::self()->mouse();
0057 
0058     const KXcursorTheme theme(pointerCursor->themeName(), pointerCursor->themeSize(), kwinApp()->devicePixelRatio());
0059     if (theme.isEmpty()) {
0060         return PlatformCursorImage();
0061     }
0062 
0063     PlatformCursorImage platformCursorImage = loadReferenceThemeCursor_helper(theme, name);
0064     if (!platformCursorImage.isNull()) {
0065         return platformCursorImage;
0066     }
0067 
0068     const QVector<QByteArray> alternativeNames = Cursor::cursorAlternativeNames(name);
0069     for (const QByteArray &alternativeName : alternativeNames) {
0070         platformCursorImage = loadReferenceThemeCursor_helper(theme, alternativeName);
0071         if (!platformCursorImage.isNull()) {
0072             break;
0073         }
0074     }
0075 
0076     return platformCursorImage;
0077 }
0078 
0079 static PlatformCursorImage loadReferenceThemeCursor(const CursorShape &shape)
0080 {
0081     return loadReferenceThemeCursor(shape.name());
0082 }
0083 
0084 static const QString s_socketName = QStringLiteral("wayland_test_kwin_pointer_input-0");
0085 
0086 class PointerInputTest : public QObject
0087 {
0088     Q_OBJECT
0089 private Q_SLOTS:
0090     void initTestCase();
0091     void init();
0092     void cleanup();
0093     void testWarpingUpdatesFocus();
0094     void testWarpingGeneratesPointerMotion();
0095     void testWarpingDuringFilter();
0096     void testWarpingBetweenWindows();
0097     void testUpdateFocusAfterScreenChange();
0098     void testUpdateFocusOnDecorationDestroy();
0099     void testModifierClickUnrestrictedMove_data();
0100     void testModifierClickUnrestrictedMove();
0101     void testModifierClickUnrestrictedFullscreenMove();
0102     void testModifierClickUnrestrictedMoveGlobalShortcutsDisabled();
0103     void testModifierScrollOpacity_data();
0104     void testModifierScrollOpacity();
0105     void testModifierScrollOpacityGlobalShortcutsDisabled();
0106     void testScrollAction();
0107     void testFocusFollowsMouse();
0108     void testMouseActionInactiveWindow_data();
0109     void testMouseActionInactiveWindow();
0110     void testMouseActionActiveWindow_data();
0111     void testMouseActionActiveWindow();
0112     void testCursorImage();
0113     void testEffectOverrideCursorImage();
0114     void testPopup();
0115     void testDecoCancelsPopup();
0116     void testWindowUnderCursorWhileButtonPressed();
0117     void testConfineToScreenGeometry_data();
0118     void testConfineToScreenGeometry();
0119     void testResizeCursor_data();
0120     void testResizeCursor();
0121     void testMoveCursor();
0122     void testHideShowCursor();
0123     void testDefaultInputRegion();
0124     void testEmptyInputRegion();
0125 
0126 private:
0127     void render(KWayland::Client::Surface *surface, const QSize &size = QSize(100, 50));
0128     KWayland::Client::Compositor *m_compositor = nullptr;
0129     KWayland::Client::Seat *m_seat = nullptr;
0130 };
0131 
0132 void PointerInputTest::initTestCase()
0133 {
0134     qRegisterMetaType<KWin::Window *>();
0135     qRegisterMetaType<KWin::Deleted *>();
0136     QSignalSpy applicationStartedSpy(kwinApp(), &Application::started);
0137     QVERIFY(waylandServer()->init(s_socketName));
0138     QMetaObject::invokeMethod(kwinApp()->outputBackend(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(QVector<QRect>, QVector<QRect>() << QRect(0, 0, 1280, 1024) << QRect(1280, 0, 1280, 1024)));
0139 
0140     kwinApp()->setConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig));
0141 
0142     if (!QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("icons/DMZ-White/index.theme")).isEmpty()) {
0143         qputenv("XCURSOR_THEME", QByteArrayLiteral("DMZ-White"));
0144     } else {
0145         // might be vanilla-dmz (e.g. Arch, FreeBSD)
0146         qputenv("XCURSOR_THEME", QByteArrayLiteral("Vanilla-DMZ"));
0147     }
0148     qputenv("XCURSOR_SIZE", QByteArrayLiteral("24"));
0149     qputenv("XKB_DEFAULT_RULES", "evdev");
0150     qputenv("KWIN_COMPOSE", QByteArrayLiteral("O2"));
0151 
0152     kwinApp()->start();
0153     QVERIFY(applicationStartedSpy.wait());
0154     const auto outputs = workspace()->outputs();
0155     QCOMPARE(outputs.count(), 2);
0156     QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024));
0157     QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024));
0158     setenv("QT_QPA_PLATFORM", "wayland", true);
0159 }
0160 
0161 void PointerInputTest::init()
0162 {
0163     QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Seat | Test::AdditionalWaylandInterface::Decoration | Test::AdditionalWaylandInterface::XdgDecorationV1));
0164     QVERIFY(Test::waitForWaylandPointer());
0165     m_compositor = Test::waylandCompositor();
0166     m_seat = Test::waylandSeat();
0167 
0168     workspace()->setActiveOutput(QPoint(640, 512));
0169     Cursors::self()->mouse()->setPos(QPoint(640, 512));
0170 }
0171 
0172 void PointerInputTest::cleanup()
0173 {
0174     Test::destroyWaylandConnection();
0175 }
0176 
0177 void PointerInputTest::render(KWayland::Client::Surface *surface, const QSize &size)
0178 {
0179     Test::render(surface, size, Qt::blue);
0180     Test::flushWaylandConnection();
0181 }
0182 
0183 void PointerInputTest::testWarpingUpdatesFocus()
0184 {
0185     // this test verifies that warping the pointer creates pointer enter and leave events
0186 
0187     // create pointer and signal spy for enter and leave signals
0188     auto pointer = m_seat->createPointer(m_seat);
0189     QVERIFY(pointer);
0190     QVERIFY(pointer->isValid());
0191     QSignalSpy enteredSpy(pointer, &KWayland::Client::Pointer::entered);
0192     QSignalSpy leftSpy(pointer, &KWayland::Client::Pointer::left);
0193 
0194     // create a window
0195     QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
0196     std::unique_ptr<KWayland::Client::Surface> surface = Test::createSurface();
0197     QVERIFY(surface);
0198     Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get());
0199     QVERIFY(shellSurface);
0200     render(surface.get());
0201     QVERIFY(windowAddedSpy.wait());
0202     Window *window = workspace()->activeWindow();
0203     QVERIFY(window);
0204 
0205     // currently there should not be a focused pointer surface
0206     QVERIFY(!waylandServer()->seat()->focusedPointerSurface());
0207     QVERIFY(!pointer->enteredSurface());
0208 
0209     // enter
0210     Cursors::self()->mouse()->setPos(QPoint(25, 25));
0211     QVERIFY(enteredSpy.wait());
0212     QCOMPARE(enteredSpy.count(), 1);
0213     QCOMPARE(enteredSpy.first().at(1).toPointF(), QPointF(25, 25));
0214     // window should have focus
0215     QCOMPARE(pointer->enteredSurface(), surface.get());
0216     // also on the server
0217     QCOMPARE(waylandServer()->seat()->focusedPointerSurface(), window->surface());
0218 
0219     // and out again
0220     Cursors::self()->mouse()->setPos(QPoint(250, 250));
0221     QVERIFY(leftSpy.wait());
0222     QCOMPARE(leftSpy.count(), 1);
0223     // there should not be a focused pointer surface anymore
0224     QVERIFY(!waylandServer()->seat()->focusedPointerSurface());
0225     QVERIFY(!pointer->enteredSurface());
0226 }
0227 
0228 void PointerInputTest::testWarpingGeneratesPointerMotion()
0229 {
0230     // this test verifies that warping the pointer creates pointer motion events
0231 
0232     // create pointer and signal spy for enter and motion
0233     auto pointer = m_seat->createPointer(m_seat);
0234     QVERIFY(pointer);
0235     QVERIFY(pointer->isValid());
0236     QSignalSpy enteredSpy(pointer, &KWayland::Client::Pointer::entered);
0237     QSignalSpy movedSpy(pointer, &KWayland::Client::Pointer::motion);
0238 
0239     // create a window
0240     QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
0241     std::unique_ptr<KWayland::Client::Surface> surface = Test::createSurface();
0242     QVERIFY(surface);
0243     Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get());
0244     QVERIFY(shellSurface);
0245     render(surface.get());
0246     QVERIFY(windowAddedSpy.wait());
0247     Window *window = workspace()->activeWindow();
0248     QVERIFY(window);
0249 
0250     // enter
0251     Test::pointerMotion(QPointF(25, 25), 1);
0252     QVERIFY(enteredSpy.wait());
0253     QCOMPARE(enteredSpy.first().at(1).toPointF(), QPointF(25, 25));
0254 
0255     // now warp
0256     Cursors::self()->mouse()->setPos(QPoint(26, 26));
0257     QVERIFY(movedSpy.wait());
0258     QCOMPARE(movedSpy.count(), 1);
0259     QCOMPARE(movedSpy.last().first().toPointF(), QPointF(26, 26));
0260 }
0261 
0262 void PointerInputTest::testWarpingDuringFilter()
0263 {
0264     // this test verifies that pointer motion is handled correctly if
0265     // the pointer gets warped during processing of input events
0266 
0267     // create pointer
0268     auto pointer = m_seat->createPointer(m_seat);
0269     QVERIFY(pointer);
0270     QVERIFY(pointer->isValid());
0271     QSignalSpy movedSpy(pointer, &KWayland::Client::Pointer::motion);
0272 
0273     // warp cursor into expected geometry
0274     Cursors::self()->mouse()->setPos(10, 10);
0275 
0276     // create a window
0277     QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
0278     std::unique_ptr<KWayland::Client::Surface> surface = Test::createSurface();
0279     QVERIFY(surface);
0280     Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get());
0281     QVERIFY(shellSurface);
0282     render(surface.get());
0283     QVERIFY(windowAddedSpy.wait());
0284     Window *window = workspace()->activeWindow();
0285     QVERIFY(window);
0286 
0287     QCOMPARE(window->pos(), QPoint(0, 0));
0288     QVERIFY(window->frameGeometry().contains(Cursors::self()->mouse()->pos()));
0289 
0290     // is WindowView effect for top left screen edge loaded
0291     QVERIFY(static_cast<EffectsHandlerImpl *>(effects)->isEffectLoaded("windowview"));
0292     QVERIFY(movedSpy.isEmpty());
0293     quint32 timestamp = 0;
0294     Test::pointerMotion(QPoint(0, 0), timestamp++);
0295     // screen edges push back
0296     QCOMPARE(Cursors::self()->mouse()->pos(), QPoint(1, 1));
0297     QVERIFY(movedSpy.wait());
0298     QCOMPARE(movedSpy.count(), 2);
0299     QCOMPARE(movedSpy.at(0).first().toPoint(), QPoint(0, 0));
0300     QCOMPARE(movedSpy.at(1).first().toPoint(), QPoint(1, 1));
0301 }
0302 
0303 void PointerInputTest::testWarpingBetweenWindows()
0304 {
0305     // This test verifies that the compositor will send correct events when the pointer
0306     // leaves one window and enters another window.
0307 
0308     std::unique_ptr<KWayland::Client::Pointer> pointer(m_seat->createPointer(m_seat));
0309     QSignalSpy enteredSpy(pointer.get(), &KWayland::Client::Pointer::entered);
0310     QSignalSpy leftSpy(pointer.get(), &KWayland::Client::Pointer::left);
0311     QSignalSpy motionSpy(pointer.get(), &KWayland::Client::Pointer::motion);
0312 
0313     // create windows
0314     std::unique_ptr<KWayland::Client::Surface> surface1(Test::createSurface());
0315     std::unique_ptr<Test::XdgToplevel> shellSurface1(Test::createXdgToplevelSurface(surface1.get()));
0316     auto window1 = Test::renderAndWaitForShown(surface1.get(), QSize(100, 50), Qt::cyan);
0317     std::unique_ptr<KWayland::Client::Surface> surface2(Test::createSurface());
0318     std::unique_ptr<Test::XdgToplevel> shellSurface2(Test::createXdgToplevelSurface(surface2.get()));
0319     auto window2 = Test::renderAndWaitForShown(surface2.get(), QSize(200, 100), Qt::red);
0320 
0321     // place windows side by side
0322     window1->move(QPoint(0, 0));
0323     window2->move(QPoint(100, 0));
0324 
0325     quint32 timestamp = 0;
0326 
0327     // put the pointer at the center of the first window
0328     Test::pointerMotion(window1->frameGeometry().center(), timestamp++);
0329     QVERIFY(enteredSpy.wait());
0330     QCOMPARE(enteredSpy.count(), 1);
0331     QCOMPARE(enteredSpy.last().at(1).toPointF(), QPointF(50, 25));
0332     QCOMPARE(leftSpy.count(), 0);
0333     QCOMPARE(motionSpy.count(), 0);
0334     QCOMPARE(pointer->enteredSurface(), surface1.get());
0335 
0336     // put the pointer at the center of the second window
0337     Test::pointerMotion(window2->frameGeometry().center(), timestamp++);
0338     QVERIFY(enteredSpy.wait());
0339     QCOMPARE(enteredSpy.count(), 2);
0340     QCOMPARE(enteredSpy.last().at(1).toPointF(), QPointF(100, 50));
0341     QCOMPARE(leftSpy.count(), 1);
0342     QCOMPARE(motionSpy.count(), 0);
0343     QCOMPARE(pointer->enteredSurface(), surface2.get());
0344 }
0345 
0346 void PointerInputTest::testUpdateFocusAfterScreenChange()
0347 {
0348     // this test verifies that a pointer enter event is generated when the cursor changes to another
0349     // screen due to removal of screen
0350 
0351     // create pointer and signal spy for enter and motion
0352     auto pointer = m_seat->createPointer(m_seat);
0353     QVERIFY(pointer);
0354     QVERIFY(pointer->isValid());
0355     QSignalSpy enteredSpy(pointer, &KWayland::Client::Pointer::entered);
0356     QSignalSpy leftSpy(pointer, &KWayland::Client::Pointer::left);
0357 
0358     // create a window
0359     QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
0360     std::unique_ptr<KWayland::Client::Surface> surface = Test::createSurface();
0361     QVERIFY(surface);
0362     Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get());
0363     QVERIFY(shellSurface);
0364     render(surface.get(), QSize(1280, 1024));
0365     QVERIFY(windowAddedSpy.wait());
0366     Window *window = workspace()->activeWindow();
0367     QVERIFY(window);
0368     QVERIFY(window->frameGeometry().contains(Cursors::self()->mouse()->pos()));
0369     QVERIFY(enteredSpy.wait());
0370     QCOMPARE(enteredSpy.count(), 1);
0371 
0372     // move the cursor to the second screen
0373     Cursors::self()->mouse()->setPos(1500, 300);
0374     QVERIFY(!window->frameGeometry().contains(Cursors::self()->mouse()->pos()));
0375     QVERIFY(leftSpy.wait());
0376 
0377     // now let's remove the screen containing the cursor
0378     QMetaObject::invokeMethod(kwinApp()->outputBackend(), "setVirtualOutputs",
0379                               Qt::DirectConnection,
0380                               Q_ARG(QVector<QRect>, QVector<QRect>{QRect(0, 0, 1280, 1024)}));
0381     QCOMPARE(workspace()->outputs().count(), 1);
0382 
0383     // this should have warped the cursor
0384     QCOMPARE(Cursors::self()->mouse()->pos(), QPoint(639, 511));
0385     QVERIFY(window->frameGeometry().contains(Cursors::self()->mouse()->pos()));
0386 
0387     // and we should get an enter event
0388     QVERIFY(enteredSpy.wait());
0389     QCOMPARE(enteredSpy.count(), 2);
0390 }
0391 
0392 void PointerInputTest::testUpdateFocusOnDecorationDestroy()
0393 {
0394     // This test verifies that a maximized window gets it's pointer focus
0395     // if decoration was focused and then destroyed on maximize with BorderlessMaximizedWindows option.
0396 
0397     // create pointer for focus tracking
0398     auto pointer = m_seat->createPointer(m_seat);
0399     QVERIFY(pointer);
0400     QVERIFY(pointer->isValid());
0401     QSignalSpy buttonStateChangedSpy(pointer, &KWayland::Client::Pointer::buttonStateChanged);
0402 
0403     // Enable the borderless maximized windows option.
0404     auto group = kwinApp()->config()->group("Windows");
0405     group.writeEntry("BorderlessMaximizedWindows", true);
0406     group.sync();
0407     Workspace::self()->slotReconfigure();
0408     QCOMPARE(options->borderlessMaximizedWindows(), true);
0409 
0410     // Create the test window.
0411     std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
0412     std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly));
0413     std::unique_ptr<Test::XdgToplevelDecorationV1> decoration(Test::createXdgToplevelDecorationV1(shellSurface.get()));
0414 
0415     QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested);
0416     QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested);
0417     QSignalSpy decorationConfigureRequestedSpy(decoration.get(), &Test::XdgToplevelDecorationV1::configureRequested);
0418     decoration->set_mode(Test::XdgToplevelDecorationV1::mode_server_side);
0419     surface->commit(KWayland::Client::Surface::CommitFlag::None);
0420 
0421     // Wait for the initial configure event.
0422     Test::XdgToplevel::States states;
0423     QVERIFY(surfaceConfigureRequestedSpy.wait());
0424     QCOMPARE(surfaceConfigureRequestedSpy.count(), 1);
0425     QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(0, 0));
0426     states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>();
0427     QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated));
0428     QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized));
0429 
0430     // Map the window.
0431     shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
0432     Window *window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
0433     QVERIFY(window);
0434     QVERIFY(window->isActive());
0435     QCOMPARE(window->maximizeMode(), MaximizeMode::MaximizeRestore);
0436     QCOMPARE(window->requestedMaximizeMode(), MaximizeMode::MaximizeRestore);
0437     QCOMPARE(window->isDecorated(), true);
0438 
0439     // We should receive a configure event when the window becomes active.
0440     QVERIFY(surfaceConfigureRequestedSpy.wait());
0441     QCOMPARE(surfaceConfigureRequestedSpy.count(), 2);
0442     states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>();
0443     QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated));
0444     QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized));
0445 
0446     // Simulate decoration hover
0447     quint32 timestamp = 0;
0448     Test::pointerMotion(window->frameGeometry().topLeft(), timestamp++);
0449     QVERIFY(input()->pointer()->decoration());
0450 
0451     // Maximize when on decoration
0452     workspace()->slotWindowMaximize();
0453     QVERIFY(surfaceConfigureRequestedSpy.wait());
0454     QCOMPARE(surfaceConfigureRequestedSpy.count(), 3);
0455     QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(1280, 1024));
0456     states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>();
0457     QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated));
0458     QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized));
0459 
0460     QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged);
0461     shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
0462     Test::render(surface.get(), QSize(1280, 1024), Qt::blue);
0463     QVERIFY(frameGeometryChangedSpy.wait());
0464     QCOMPARE(window->frameGeometry(), QRect(0, 0, 1280, 1024));
0465     QCOMPARE(window->maximizeMode(), MaximizeFull);
0466     QCOMPARE(window->requestedMaximizeMode(), MaximizeFull);
0467     QCOMPARE(window->isDecorated(), false);
0468 
0469     // Window should have focus, BUG 411884
0470     QVERIFY(!input()->pointer()->decoration());
0471     Test::pointerButtonPressed(BTN_LEFT, timestamp++);
0472     Test::pointerButtonReleased(BTN_LEFT, timestamp++);
0473     QVERIFY(buttonStateChangedSpy.wait());
0474     QCOMPARE(pointer->enteredSurface(), surface.get());
0475 
0476     // Destroy the window.
0477     shellSurface.reset();
0478     QVERIFY(Test::waitForWindowDestroyed(window));
0479 }
0480 
0481 void PointerInputTest::testModifierClickUnrestrictedMove_data()
0482 {
0483     QTest::addColumn<int>("modifierKey");
0484     QTest::addColumn<int>("mouseButton");
0485     QTest::addColumn<QString>("modKey");
0486     QTest::addColumn<bool>("capsLock");
0487 
0488     const QString alt = QStringLiteral("Alt");
0489     const QString meta = QStringLiteral("Meta");
0490 
0491     QTest::newRow("Left Alt + Left Click") << KEY_LEFTALT << BTN_LEFT << alt << false;
0492     QTest::newRow("Left Alt + Right Click") << KEY_LEFTALT << BTN_RIGHT << alt << false;
0493     QTest::newRow("Left Alt + Middle Click") << KEY_LEFTALT << BTN_MIDDLE << alt << false;
0494     QTest::newRow("Right Alt + Left Click") << KEY_RIGHTALT << BTN_LEFT << alt << false;
0495     QTest::newRow("Right Alt + Right Click") << KEY_RIGHTALT << BTN_RIGHT << alt << false;
0496     QTest::newRow("Right Alt + Middle Click") << KEY_RIGHTALT << BTN_MIDDLE << alt << false;
0497     // now everything with meta
0498     QTest::newRow("Left Meta + Left Click") << KEY_LEFTMETA << BTN_LEFT << meta << false;
0499     QTest::newRow("Left Meta + Right Click") << KEY_LEFTMETA << BTN_RIGHT << meta << false;
0500     QTest::newRow("Left Meta + Middle Click") << KEY_LEFTMETA << BTN_MIDDLE << meta << false;
0501     QTest::newRow("Right Meta + Left Click") << KEY_RIGHTMETA << BTN_LEFT << meta << false;
0502     QTest::newRow("Right Meta + Right Click") << KEY_RIGHTMETA << BTN_RIGHT << meta << false;
0503     QTest::newRow("Right Meta + Middle Click") << KEY_RIGHTMETA << BTN_MIDDLE << meta << false;
0504 
0505     // and with capslock
0506     QTest::newRow("Left Alt + Left Click/CapsLock") << KEY_LEFTALT << BTN_LEFT << alt << true;
0507     QTest::newRow("Left Alt + Right Click/CapsLock") << KEY_LEFTALT << BTN_RIGHT << alt << true;
0508     QTest::newRow("Left Alt + Middle Click/CapsLock") << KEY_LEFTALT << BTN_MIDDLE << alt << true;
0509     QTest::newRow("Right Alt + Left Click/CapsLock") << KEY_RIGHTALT << BTN_LEFT << alt << true;
0510     QTest::newRow("Right Alt + Right Click/CapsLock") << KEY_RIGHTALT << BTN_RIGHT << alt << true;
0511     QTest::newRow("Right Alt + Middle Click/CapsLock") << KEY_RIGHTALT << BTN_MIDDLE << alt << true;
0512     // now everything with meta
0513     QTest::newRow("Left Meta + Left Click/CapsLock") << KEY_LEFTMETA << BTN_LEFT << meta << true;
0514     QTest::newRow("Left Meta + Right Click/CapsLock") << KEY_LEFTMETA << BTN_RIGHT << meta << true;
0515     QTest::newRow("Left Meta + Middle Click/CapsLock") << KEY_LEFTMETA << BTN_MIDDLE << meta << true;
0516     QTest::newRow("Right Meta + Left Click/CapsLock") << KEY_RIGHTMETA << BTN_LEFT << meta << true;
0517     QTest::newRow("Right Meta + Right Click/CapsLock") << KEY_RIGHTMETA << BTN_RIGHT << meta << true;
0518     QTest::newRow("Right Meta + Middle Click/CapsLock") << KEY_RIGHTMETA << BTN_MIDDLE << meta << true;
0519 }
0520 
0521 void PointerInputTest::testModifierClickUnrestrictedMove()
0522 {
0523     // this test ensures that Alt+mouse button press triggers unrestricted move
0524 
0525     // create pointer and signal spy for button events
0526     auto pointer = m_seat->createPointer(m_seat);
0527     QVERIFY(pointer);
0528     QVERIFY(pointer->isValid());
0529     QSignalSpy buttonSpy(pointer, &KWayland::Client::Pointer::buttonStateChanged);
0530 
0531     // first modify the config for this run
0532     QFETCH(QString, modKey);
0533     KConfigGroup group = kwinApp()->config()->group("MouseBindings");
0534     group.writeEntry("CommandAllKey", modKey);
0535     group.writeEntry("CommandAll1", "Move");
0536     group.writeEntry("CommandAll2", "Move");
0537     group.writeEntry("CommandAll3", "Move");
0538     group.sync();
0539     workspace()->slotReconfigure();
0540     QCOMPARE(options->commandAllModifier(), modKey == QStringLiteral("Alt") ? Qt::AltModifier : Qt::MetaModifier);
0541     QCOMPARE(options->commandAll1(), Options::MouseUnrestrictedMove);
0542     QCOMPARE(options->commandAll2(), Options::MouseUnrestrictedMove);
0543     QCOMPARE(options->commandAll3(), Options::MouseUnrestrictedMove);
0544 
0545     // create a window
0546     QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
0547     std::unique_ptr<KWayland::Client::Surface> surface = Test::createSurface();
0548     QVERIFY(surface);
0549     Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get());
0550     QVERIFY(shellSurface);
0551     render(surface.get());
0552     QVERIFY(windowAddedSpy.wait());
0553     Window *window = workspace()->activeWindow();
0554     QVERIFY(window);
0555 
0556     // move cursor on window
0557     Cursors::self()->mouse()->setPos(window->frameGeometry().center());
0558 
0559     // simulate modifier+click
0560     quint32 timestamp = 1;
0561     QFETCH(bool, capsLock);
0562     if (capsLock) {
0563         Test::keyboardKeyPressed(KEY_CAPSLOCK, timestamp++);
0564     }
0565     QFETCH(int, modifierKey);
0566     QFETCH(int, mouseButton);
0567     Test::keyboardKeyPressed(modifierKey, timestamp++);
0568     QVERIFY(!window->isInteractiveMove());
0569     Test::pointerButtonPressed(mouseButton, timestamp++);
0570     QVERIFY(window->isInteractiveMove());
0571     // release modifier should not change it
0572     Test::keyboardKeyReleased(modifierKey, timestamp++);
0573     QVERIFY(window->isInteractiveMove());
0574     // but releasing the key should end move/resize
0575     Test::pointerButtonReleased(mouseButton, timestamp++);
0576     QVERIFY(!window->isInteractiveMove());
0577     if (capsLock) {
0578         Test::keyboardKeyReleased(KEY_CAPSLOCK, timestamp++);
0579     }
0580 
0581     // all of that should not have triggered button events on the surface
0582     QCOMPARE(buttonSpy.count(), 0);
0583     // also waiting shouldn't give us the event
0584     QVERIFY(!buttonSpy.wait(100));
0585 }
0586 
0587 void PointerInputTest::testModifierClickUnrestrictedFullscreenMove()
0588 {
0589     // this test ensures that Meta+mouse button press triggers unrestricted move for fullscreen windows
0590     if (workspace()->outputs().size() < 2) {
0591         QMetaObject::invokeMethod(kwinApp()->outputBackend(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(QVector<QRect>, QVector<QRect>() << QRect(0, 0, 1280, 1024) << QRect(1280, 0, 1280, 1024)));
0592     }
0593 
0594     // first modify the config for this run
0595     KConfigGroup group = kwinApp()->config()->group("MouseBindings");
0596     group.writeEntry("CommandAllKey", "Meta");
0597     group.writeEntry("CommandAll1", "Move");
0598     group.writeEntry("CommandAll2", "Move");
0599     group.writeEntry("CommandAll3", "Move");
0600     group.sync();
0601     workspace()->slotReconfigure();
0602     QCOMPARE(options->commandAllModifier(), Qt::MetaModifier);
0603     QCOMPARE(options->commandAll1(), Options::MouseUnrestrictedMove);
0604     QCOMPARE(options->commandAll2(), Options::MouseUnrestrictedMove);
0605     QCOMPARE(options->commandAll3(), Options::MouseUnrestrictedMove);
0606 
0607     // create a window
0608     std::unique_ptr<KWayland::Client::Surface> surface = Test::createSurface();
0609     QVERIFY(surface);
0610     Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get());
0611     QVERIFY(shellSurface);
0612     shellSurface->set_fullscreen(nullptr);
0613     QSignalSpy toplevelConfigureRequestedSpy(shellSurface, &Test::XdgToplevel::configureRequested);
0614     QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested);
0615     QVERIFY(surfaceConfigureRequestedSpy.wait());
0616     shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
0617     Window *window = Test::renderAndWaitForShown(surface.get(), toplevelConfigureRequestedSpy.last().at(0).value<QSize>(), Qt::blue);
0618     QVERIFY(window);
0619     QVERIFY(window->isFullScreen());
0620 
0621     // move cursor on window
0622     Cursors::self()->mouse()->setPos(window->frameGeometry().center());
0623 
0624     // simulate modifier+click
0625     quint32 timestamp = 1;
0626     Test::keyboardKeyPressed(KEY_LEFTMETA, timestamp++);
0627     QVERIFY(!window->isInteractiveMove());
0628     Test::pointerButtonPressed(BTN_LEFT, timestamp++);
0629     QVERIFY(window->isInteractiveMove());
0630     // release modifier should not change it
0631     Test::keyboardKeyReleased(KEY_LEFTMETA, timestamp++);
0632     QVERIFY(window->isInteractiveMove());
0633     // but releasing the key should end move/resize
0634     Test::pointerButtonReleased(BTN_LEFT, timestamp++);
0635     QVERIFY(!window->isInteractiveMove());
0636 }
0637 
0638 void PointerInputTest::testModifierClickUnrestrictedMoveGlobalShortcutsDisabled()
0639 {
0640     // this test ensures that Alt+mouse button press triggers unrestricted move
0641 
0642     // create pointer and signal spy for button events
0643     auto pointer = m_seat->createPointer(m_seat);
0644     QVERIFY(pointer);
0645     QVERIFY(pointer->isValid());
0646     QSignalSpy buttonSpy(pointer, &KWayland::Client::Pointer::buttonStateChanged);
0647 
0648     // first modify the config for this run
0649     KConfigGroup group = kwinApp()->config()->group("MouseBindings");
0650     group.writeEntry("CommandAllKey", "Meta");
0651     group.writeEntry("CommandAll1", "Move");
0652     group.writeEntry("CommandAll2", "Move");
0653     group.writeEntry("CommandAll3", "Move");
0654     group.sync();
0655     workspace()->slotReconfigure();
0656     QCOMPARE(options->commandAllModifier(), Qt::MetaModifier);
0657     QCOMPARE(options->commandAll1(), Options::MouseUnrestrictedMove);
0658     QCOMPARE(options->commandAll2(), Options::MouseUnrestrictedMove);
0659     QCOMPARE(options->commandAll3(), Options::MouseUnrestrictedMove);
0660 
0661     // create a window
0662     QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
0663     std::unique_ptr<KWayland::Client::Surface> surface = Test::createSurface();
0664     QVERIFY(surface);
0665     Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get());
0666     QVERIFY(shellSurface);
0667     render(surface.get());
0668     QVERIFY(windowAddedSpy.wait());
0669     Window *window = workspace()->activeWindow();
0670     QVERIFY(window);
0671 
0672     // disable global shortcuts
0673     QVERIFY(!workspace()->globalShortcutsDisabled());
0674     workspace()->disableGlobalShortcutsForClient(true);
0675     QVERIFY(workspace()->globalShortcutsDisabled());
0676 
0677     // move cursor on window
0678     Cursors::self()->mouse()->setPos(window->frameGeometry().center());
0679 
0680     // simulate modifier+click
0681     quint32 timestamp = 1;
0682     Test::keyboardKeyPressed(KEY_LEFTMETA, timestamp++);
0683     QVERIFY(!window->isInteractiveMove());
0684     Test::pointerButtonPressed(BTN_LEFT, timestamp++);
0685     QVERIFY(!window->isInteractiveMove());
0686     // release modifier should not change it
0687     Test::keyboardKeyReleased(KEY_LEFTMETA, timestamp++);
0688     QVERIFY(!window->isInteractiveMove());
0689     Test::pointerButtonReleased(BTN_LEFT, timestamp++);
0690 
0691     workspace()->disableGlobalShortcutsForClient(false);
0692 }
0693 
0694 void PointerInputTest::testModifierScrollOpacity_data()
0695 {
0696     QTest::addColumn<int>("modifierKey");
0697     QTest::addColumn<QString>("modKey");
0698     QTest::addColumn<bool>("capsLock");
0699 
0700     const QString alt = QStringLiteral("Alt");
0701     const QString meta = QStringLiteral("Meta");
0702 
0703     QTest::newRow("Left Alt") << KEY_LEFTALT << alt << false;
0704     QTest::newRow("Right Alt") << KEY_RIGHTALT << alt << false;
0705     QTest::newRow("Left Meta") << KEY_LEFTMETA << meta << false;
0706     QTest::newRow("Right Meta") << KEY_RIGHTMETA << meta << false;
0707     QTest::newRow("Left Alt/CapsLock") << KEY_LEFTALT << alt << true;
0708     QTest::newRow("Right Alt/CapsLock") << KEY_RIGHTALT << alt << true;
0709     QTest::newRow("Left Meta/CapsLock") << KEY_LEFTMETA << meta << true;
0710     QTest::newRow("Right Meta/CapsLock") << KEY_RIGHTMETA << meta << true;
0711 }
0712 
0713 void PointerInputTest::testModifierScrollOpacity()
0714 {
0715     // this test verifies that mod+wheel performs a window operation and does not
0716     // pass the wheel to the window
0717 
0718     // create pointer and signal spy for button events
0719     auto pointer = m_seat->createPointer(m_seat);
0720     QVERIFY(pointer);
0721     QVERIFY(pointer->isValid());
0722     QSignalSpy axisSpy(pointer, &KWayland::Client::Pointer::axisChanged);
0723 
0724     // first modify the config for this run
0725     QFETCH(QString, modKey);
0726     KConfigGroup group = kwinApp()->config()->group("MouseBindings");
0727     group.writeEntry("CommandAllKey", modKey);
0728     group.writeEntry("CommandAllWheel", "change opacity");
0729     group.sync();
0730     workspace()->slotReconfigure();
0731 
0732     // create a window
0733     QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
0734     std::unique_ptr<KWayland::Client::Surface> surface = Test::createSurface();
0735     QVERIFY(surface);
0736     Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get());
0737     QVERIFY(shellSurface);
0738     render(surface.get());
0739     QVERIFY(windowAddedSpy.wait());
0740     Window *window = workspace()->activeWindow();
0741     QVERIFY(window);
0742     // set the opacity to 0.5
0743     window->setOpacity(0.5);
0744     QCOMPARE(window->opacity(), 0.5);
0745 
0746     // move cursor on window
0747     Cursors::self()->mouse()->setPos(window->frameGeometry().center());
0748 
0749     // simulate modifier+wheel
0750     quint32 timestamp = 1;
0751     QFETCH(bool, capsLock);
0752     if (capsLock) {
0753         Test::keyboardKeyPressed(KEY_CAPSLOCK, timestamp++);
0754     }
0755     QFETCH(int, modifierKey);
0756     Test::keyboardKeyPressed(modifierKey, timestamp++);
0757     Test::pointerAxisVertical(-5, timestamp++);
0758     QCOMPARE(window->opacity(), 0.6);
0759     Test::pointerAxisVertical(5, timestamp++);
0760     QCOMPARE(window->opacity(), 0.5);
0761     Test::keyboardKeyReleased(modifierKey, timestamp++);
0762     if (capsLock) {
0763         Test::keyboardKeyReleased(KEY_CAPSLOCK, timestamp++);
0764     }
0765 
0766     // axis should have been filtered out
0767     QCOMPARE(axisSpy.count(), 0);
0768     QVERIFY(!axisSpy.wait(100));
0769 }
0770 
0771 void PointerInputTest::testModifierScrollOpacityGlobalShortcutsDisabled()
0772 {
0773     // this test verifies that mod+wheel performs a window operation and does not
0774     // pass the wheel to the window
0775 
0776     // create pointer and signal spy for button events
0777     auto pointer = m_seat->createPointer(m_seat);
0778     QVERIFY(pointer);
0779     QVERIFY(pointer->isValid());
0780     QSignalSpy axisSpy(pointer, &KWayland::Client::Pointer::axisChanged);
0781 
0782     // first modify the config for this run
0783     KConfigGroup group = kwinApp()->config()->group("MouseBindings");
0784     group.writeEntry("CommandAllKey", "Meta");
0785     group.writeEntry("CommandAllWheel", "change opacity");
0786     group.sync();
0787     workspace()->slotReconfigure();
0788 
0789     // create a window
0790     QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
0791     std::unique_ptr<KWayland::Client::Surface> surface = Test::createSurface();
0792     QVERIFY(surface);
0793     Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get());
0794     QVERIFY(shellSurface);
0795     render(surface.get());
0796     QVERIFY(windowAddedSpy.wait());
0797     Window *window = workspace()->activeWindow();
0798     QVERIFY(window);
0799     // set the opacity to 0.5
0800     window->setOpacity(0.5);
0801     QCOMPARE(window->opacity(), 0.5);
0802 
0803     // move cursor on window
0804     Cursors::self()->mouse()->setPos(window->frameGeometry().center());
0805 
0806     // disable global shortcuts
0807     QVERIFY(!workspace()->globalShortcutsDisabled());
0808     workspace()->disableGlobalShortcutsForClient(true);
0809     QVERIFY(workspace()->globalShortcutsDisabled());
0810 
0811     // simulate modifier+wheel
0812     quint32 timestamp = 1;
0813     Test::keyboardKeyPressed(KEY_LEFTMETA, timestamp++);
0814     Test::pointerAxisVertical(-5, timestamp++);
0815     QCOMPARE(window->opacity(), 0.5);
0816     Test::pointerAxisVertical(5, timestamp++);
0817     QCOMPARE(window->opacity(), 0.5);
0818     Test::keyboardKeyReleased(KEY_LEFTMETA, timestamp++);
0819 
0820     workspace()->disableGlobalShortcutsForClient(false);
0821 }
0822 
0823 void PointerInputTest::testScrollAction()
0824 {
0825     // this test verifies that scroll on inactive window performs a mouse action
0826     auto pointer = m_seat->createPointer(m_seat);
0827     QVERIFY(pointer);
0828     QVERIFY(pointer->isValid());
0829     QSignalSpy axisSpy(pointer, &KWayland::Client::Pointer::axisChanged);
0830 
0831     // first modify the config for this run
0832     KConfigGroup group = kwinApp()->config()->group("MouseBindings");
0833     group.writeEntry("CommandWindowWheel", "activate and scroll");
0834     group.sync();
0835     workspace()->slotReconfigure();
0836     // create two windows
0837     QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
0838     std::unique_ptr<KWayland::Client::Surface> surface1 = Test::createSurface();
0839     QVERIFY(surface1);
0840     Test::XdgToplevel *shellSurface1 = Test::createXdgToplevelSurface(surface1.get(), surface1.get());
0841     QVERIFY(shellSurface1);
0842     render(surface1.get());
0843     QVERIFY(windowAddedSpy.wait());
0844     Window *window1 = workspace()->activeWindow();
0845     QVERIFY(window1);
0846     std::unique_ptr<KWayland::Client::Surface> surface2 = Test::createSurface();
0847     QVERIFY(surface2);
0848     Test::XdgToplevel *shellSurface2 = Test::createXdgToplevelSurface(surface2.get(), surface2.get());
0849     QVERIFY(shellSurface2);
0850     render(surface2.get());
0851     QVERIFY(windowAddedSpy.wait());
0852     Window *window2 = workspace()->activeWindow();
0853     QVERIFY(window2);
0854     QVERIFY(window1 != window2);
0855 
0856     // move cursor to the inactive window
0857     Cursors::self()->mouse()->setPos(window1->frameGeometry().center());
0858 
0859     quint32 timestamp = 1;
0860     QVERIFY(!window1->isActive());
0861     Test::pointerAxisVertical(5, timestamp++);
0862     QVERIFY(window1->isActive());
0863 
0864     // but also the wheel event should be passed to the window
0865     QVERIFY(axisSpy.wait());
0866 }
0867 
0868 void PointerInputTest::testFocusFollowsMouse()
0869 {
0870     // need to create a pointer, otherwise it doesn't accept focus
0871     auto pointer = m_seat->createPointer(m_seat);
0872     QVERIFY(pointer);
0873     QVERIFY(pointer->isValid());
0874     // move cursor out of the way of first window to be created
0875     Cursors::self()->mouse()->setPos(900, 900);
0876 
0877     // first modify the config for this run
0878     KConfigGroup group = kwinApp()->config()->group("Windows");
0879     group.writeEntry("AutoRaise", true);
0880     group.writeEntry("AutoRaiseInterval", 20);
0881     group.writeEntry("DelayFocusInterval", 200);
0882     group.writeEntry("FocusPolicy", "FocusFollowsMouse");
0883     group.sync();
0884     workspace()->slotReconfigure();
0885     // verify the settings
0886     QCOMPARE(options->focusPolicy(), Options::FocusFollowsMouse);
0887     QVERIFY(options->isAutoRaise());
0888     QCOMPARE(options->autoRaiseInterval(), 20);
0889     QCOMPARE(options->delayFocusInterval(), 200);
0890 
0891     // create two windows
0892     QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
0893     std::unique_ptr<KWayland::Client::Surface> surface1 = Test::createSurface();
0894     QVERIFY(surface1);
0895     Test::XdgToplevel *shellSurface1 = Test::createXdgToplevelSurface(surface1.get(), surface1.get());
0896     QVERIFY(shellSurface1);
0897     render(surface1.get(), QSize(800, 800));
0898     QVERIFY(windowAddedSpy.wait());
0899     Window *window1 = workspace()->activeWindow();
0900     QVERIFY(window1);
0901     std::unique_ptr<KWayland::Client::Surface> surface2 = Test::createSurface();
0902     QVERIFY(surface2);
0903     Test::XdgToplevel *shellSurface2 = Test::createXdgToplevelSurface(surface2.get(), surface2.get());
0904     QVERIFY(shellSurface2);
0905     render(surface2.get(), QSize(800, 800));
0906     QVERIFY(windowAddedSpy.wait());
0907     Window *window2 = workspace()->activeWindow();
0908     QVERIFY(window2);
0909     QVERIFY(window1 != window2);
0910     QCOMPARE(workspace()->topWindowOnDesktop(VirtualDesktopManager::self()->currentDesktop()), window2);
0911     // geometry of the two windows should be overlapping
0912     QVERIFY(window1->frameGeometry().intersects(window2->frameGeometry()));
0913 
0914     // signal spies for active window changed and stacking order changed
0915     QSignalSpy activeWindowChangedSpy(workspace(), &Workspace::windowActivated);
0916     QSignalSpy stackingOrderChangedSpy(workspace(), &Workspace::stackingOrderChanged);
0917 
0918     QVERIFY(!window1->isActive());
0919     QVERIFY(window2->isActive());
0920 
0921     // move on top of first window
0922     QVERIFY(window1->frameGeometry().contains(10, 10));
0923     QVERIFY(!window2->frameGeometry().contains(10, 10));
0924     Cursors::self()->mouse()->setPos(10, 10);
0925     QVERIFY(stackingOrderChangedSpy.wait());
0926     QCOMPARE(stackingOrderChangedSpy.count(), 1);
0927     QCOMPARE(workspace()->topWindowOnDesktop(VirtualDesktopManager::self()->currentDesktop()), window1);
0928     QTRY_VERIFY(window1->isActive());
0929 
0930     // move on second window, but move away before active window change delay hits
0931     Cursors::self()->mouse()->setPos(810, 810);
0932     QVERIFY(stackingOrderChangedSpy.wait());
0933     QCOMPARE(stackingOrderChangedSpy.count(), 2);
0934     QCOMPARE(workspace()->topWindowOnDesktop(VirtualDesktopManager::self()->currentDesktop()), window2);
0935     Cursors::self()->mouse()->setPos(10, 10);
0936     QVERIFY(!activeWindowChangedSpy.wait(250));
0937     QVERIFY(window1->isActive());
0938     QCOMPARE(workspace()->topWindowOnDesktop(VirtualDesktopManager::self()->currentDesktop()), window1);
0939     // as we moved back on window 1 that should been raised in the mean time
0940     QCOMPARE(stackingOrderChangedSpy.count(), 3);
0941 
0942     // quickly move on window 2 and back on window 1 should not raise window 2
0943     Cursors::self()->mouse()->setPos(810, 810);
0944     Cursors::self()->mouse()->setPos(10, 10);
0945     QVERIFY(!stackingOrderChangedSpy.wait(250));
0946 }
0947 
0948 void PointerInputTest::testMouseActionInactiveWindow_data()
0949 {
0950     QTest::addColumn<quint32>("button");
0951 
0952     QTest::newRow("Left") << quint32(BTN_LEFT);
0953     QTest::newRow("Middle") << quint32(BTN_MIDDLE);
0954     QTest::newRow("Right") << quint32(BTN_RIGHT);
0955 }
0956 
0957 void PointerInputTest::testMouseActionInactiveWindow()
0958 {
0959     // this test performs the mouse button window action on an inactive window
0960     // it should activate the window and raise it
0961 
0962     // first modify the config for this run - disable FocusFollowsMouse
0963     KConfigGroup group = kwinApp()->config()->group("Windows");
0964     group.writeEntry("FocusPolicy", "ClickToFocus");
0965     group.sync();
0966     group = kwinApp()->config()->group("MouseBindings");
0967     group.writeEntry("CommandWindow1", "Activate, raise and pass click");
0968     group.writeEntry("CommandWindow2", "Activate, raise and pass click");
0969     group.writeEntry("CommandWindow3", "Activate, raise and pass click");
0970     group.sync();
0971     workspace()->slotReconfigure();
0972 
0973     // create two windows
0974     QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
0975     std::unique_ptr<KWayland::Client::Surface> surface1 = Test::createSurface();
0976     QVERIFY(surface1);
0977     Test::XdgToplevel *shellSurface1 = Test::createXdgToplevelSurface(surface1.get(), surface1.get());
0978     QVERIFY(shellSurface1);
0979     render(surface1.get(), QSize(800, 800));
0980     QVERIFY(windowAddedSpy.wait());
0981     Window *window1 = workspace()->activeWindow();
0982     QVERIFY(window1);
0983     std::unique_ptr<KWayland::Client::Surface> surface2 = Test::createSurface();
0984     QVERIFY(surface2);
0985     Test::XdgToplevel *shellSurface2 = Test::createXdgToplevelSurface(surface2.get(), surface2.get());
0986     QVERIFY(shellSurface2);
0987     render(surface2.get(), QSize(800, 800));
0988     QVERIFY(windowAddedSpy.wait());
0989     Window *window2 = workspace()->activeWindow();
0990     QVERIFY(window2);
0991     QVERIFY(window1 != window2);
0992     QCOMPARE(workspace()->topWindowOnDesktop(VirtualDesktopManager::self()->currentDesktop()), window2);
0993     // geometry of the two windows should be overlapping
0994     QVERIFY(window1->frameGeometry().intersects(window2->frameGeometry()));
0995 
0996     // signal spies for active window changed and stacking order changed
0997     QSignalSpy activeWindowChangedSpy(workspace(), &Workspace::windowActivated);
0998     QSignalSpy stackingOrderChangedSpy(workspace(), &Workspace::stackingOrderChanged);
0999 
1000     QVERIFY(!window1->isActive());
1001     QVERIFY(window2->isActive());
1002 
1003     // move on top of first window
1004     QVERIFY(window1->frameGeometry().contains(10, 10));
1005     QVERIFY(!window2->frameGeometry().contains(10, 10));
1006     Cursors::self()->mouse()->setPos(10, 10);
1007     // no focus follows mouse
1008     QVERIFY(!stackingOrderChangedSpy.wait(200));
1009     QVERIFY(stackingOrderChangedSpy.isEmpty());
1010     QVERIFY(activeWindowChangedSpy.isEmpty());
1011     QVERIFY(window2->isActive());
1012     // and click
1013     quint32 timestamp = 1;
1014     QFETCH(quint32, button);
1015     Test::pointerButtonPressed(button, timestamp++);
1016     // should raise window1 and activate it
1017     QCOMPARE(stackingOrderChangedSpy.count(), 1);
1018     QVERIFY(!activeWindowChangedSpy.isEmpty());
1019     QCOMPARE(workspace()->topWindowOnDesktop(VirtualDesktopManager::self()->currentDesktop()), window1);
1020     QVERIFY(window1->isActive());
1021     QVERIFY(!window2->isActive());
1022 
1023     // release again
1024     Test::pointerButtonReleased(button, timestamp++);
1025 }
1026 
1027 void PointerInputTest::testMouseActionActiveWindow_data()
1028 {
1029     QTest::addColumn<bool>("clickRaise");
1030     QTest::addColumn<quint32>("button");
1031 
1032     for (quint32 i = BTN_LEFT; i < BTN_JOYSTICK; i++) {
1033         QByteArray number = QByteArray::number(i, 16);
1034         QTest::newRow(QByteArrayLiteral("click raise/").append(number).constData()) << true << i;
1035         QTest::newRow(QByteArrayLiteral("no click raise/").append(number).constData()) << false << i;
1036     }
1037 }
1038 
1039 void PointerInputTest::testMouseActionActiveWindow()
1040 {
1041     // this test verifies the mouse action performed on an active window
1042     // for all buttons it should trigger a window raise depending on the
1043     // click raise option
1044 
1045     // create a button spy - all clicks should be passed through
1046     auto pointer = m_seat->createPointer(m_seat);
1047     QVERIFY(pointer);
1048     QVERIFY(pointer->isValid());
1049     QSignalSpy buttonSpy(pointer, &KWayland::Client::Pointer::buttonStateChanged);
1050 
1051     // adjust config for this run
1052     QFETCH(bool, clickRaise);
1053     KConfigGroup group = kwinApp()->config()->group("Windows");
1054     group.writeEntry("ClickRaise", clickRaise);
1055     group.sync();
1056     workspace()->slotReconfigure();
1057     QCOMPARE(options->isClickRaise(), clickRaise);
1058 
1059     // create two windows
1060     QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
1061     std::unique_ptr<KWayland::Client::Surface> surface1 = Test::createSurface();
1062     QVERIFY(surface1);
1063     Test::XdgToplevel *shellSurface1 = Test::createXdgToplevelSurface(surface1.get(), surface1.get());
1064     QVERIFY(shellSurface1);
1065     render(surface1.get(), QSize(800, 800));
1066     QVERIFY(windowAddedSpy.wait());
1067     Window *window1 = workspace()->activeWindow();
1068     QVERIFY(window1);
1069     QSignalSpy window1DestroyedSpy(window1, &QObject::destroyed);
1070     std::unique_ptr<KWayland::Client::Surface> surface2 = Test::createSurface();
1071     QVERIFY(surface2);
1072     Test::XdgToplevel *shellSurface2 = Test::createXdgToplevelSurface(surface2.get(), surface2.get());
1073     QVERIFY(shellSurface2);
1074     render(surface2.get(), QSize(800, 800));
1075     QVERIFY(windowAddedSpy.wait());
1076     Window *window2 = workspace()->activeWindow();
1077     QVERIFY(window2);
1078     QVERIFY(window1 != window2);
1079     QSignalSpy window2DestroyedSpy(window2, &QObject::destroyed);
1080     QCOMPARE(workspace()->topWindowOnDesktop(VirtualDesktopManager::self()->currentDesktop()), window2);
1081     // geometry of the two windows should be overlapping
1082     QVERIFY(window1->frameGeometry().intersects(window2->frameGeometry()));
1083     // lower the currently active window
1084     workspace()->lowerWindow(window2);
1085     QCOMPARE(workspace()->topWindowOnDesktop(VirtualDesktopManager::self()->currentDesktop()), window1);
1086 
1087     // signal spy for stacking order spy
1088     QSignalSpy stackingOrderChangedSpy(workspace(), &Workspace::stackingOrderChanged);
1089 
1090     // move on top of second window
1091     QVERIFY(!window1->frameGeometry().contains(900, 900));
1092     QVERIFY(window2->frameGeometry().contains(900, 900));
1093     Cursors::self()->mouse()->setPos(900, 900);
1094 
1095     // and click
1096     quint32 timestamp = 1;
1097     QFETCH(quint32, button);
1098     Test::pointerButtonPressed(button, timestamp++);
1099     QVERIFY(buttonSpy.wait());
1100     if (clickRaise) {
1101         QCOMPARE(stackingOrderChangedSpy.count(), 1);
1102         QTRY_COMPARE_WITH_TIMEOUT(workspace()->topWindowOnDesktop(VirtualDesktopManager::self()->currentDesktop()), window2, 200);
1103     } else {
1104         QCOMPARE(stackingOrderChangedSpy.count(), 0);
1105         QVERIFY(!stackingOrderChangedSpy.wait(100));
1106         QCOMPARE(workspace()->topWindowOnDesktop(VirtualDesktopManager::self()->currentDesktop()), window1);
1107     }
1108 
1109     // release again
1110     Test::pointerButtonReleased(button, timestamp++);
1111 
1112     surface1.reset();
1113     QVERIFY(window1DestroyedSpy.wait());
1114     surface2.reset();
1115     QVERIFY(window2DestroyedSpy.wait());
1116 }
1117 
1118 void PointerInputTest::testCursorImage()
1119 {
1120     // this test verifies that the pointer image gets updated correctly from the client provided data
1121 
1122     // we need a pointer to get the enter event
1123     auto pointer = m_seat->createPointer(m_seat);
1124     QVERIFY(pointer);
1125     QVERIFY(pointer->isValid());
1126     QSignalSpy enteredSpy(pointer, &KWayland::Client::Pointer::entered);
1127 
1128     // move cursor somewhere the new window won't open
1129     auto cursor = Cursors::self()->mouse();
1130     cursor->setPos(800, 800);
1131     auto p = input()->pointer();
1132     // at the moment it should be the fallback cursor
1133     const QImage fallbackCursor = cursor->image();
1134     QVERIFY(!fallbackCursor.isNull());
1135 
1136     // create a window
1137     QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
1138     std::unique_ptr<KWayland::Client::Surface> surface = Test::createSurface();
1139     QVERIFY(surface);
1140     Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get());
1141     QVERIFY(shellSurface);
1142     render(surface.get());
1143     QVERIFY(windowAddedSpy.wait());
1144     Window *window = workspace()->activeWindow();
1145     QVERIFY(window);
1146 
1147     // move cursor to center of window, this should first set a null pointer, so we still show old cursor
1148     cursor->setPos(window->frameGeometry().center());
1149     QCOMPARE(p->focus(), window);
1150     QCOMPARE(cursor->image(), fallbackCursor);
1151     QVERIFY(enteredSpy.wait());
1152 
1153     // create a cursor on the pointer
1154     auto cursorSurface = Test::createSurface();
1155     QVERIFY(cursorSurface);
1156     QSignalSpy cursorRenderedSpy(cursorSurface.get(), &KWayland::Client::Surface::frameRendered);
1157     QImage red = QImage(QSize(10, 10), QImage::Format_ARGB32_Premultiplied);
1158     red.fill(Qt::red);
1159     cursorSurface->attachBuffer(Test::waylandShmPool()->createBuffer(red));
1160     cursorSurface->damage(QRect(0, 0, 10, 10));
1161     cursorSurface->commit();
1162     pointer->setCursor(cursorSurface.get(), QPoint(5, 5));
1163     QVERIFY(cursorRenderedSpy.wait());
1164     QCOMPARE(cursor->image(), red);
1165     QCOMPARE(cursor->hotspot(), QPoint(5, 5));
1166     // change hotspot
1167     pointer->setCursor(cursorSurface.get(), QPoint(6, 6));
1168     Test::flushWaylandConnection();
1169     QTRY_COMPARE(cursor->hotspot(), QPoint(6, 6));
1170     QCOMPARE(cursor->image(), red);
1171 
1172     // change the buffer
1173     QImage blue = QImage(QSize(10, 10), QImage::Format_ARGB32_Premultiplied);
1174     blue.fill(Qt::blue);
1175     auto b = Test::waylandShmPool()->createBuffer(blue);
1176     cursorSurface->attachBuffer(b);
1177     cursorSurface->damage(QRect(0, 0, 10, 10));
1178     cursorSurface->commit();
1179     QVERIFY(cursorRenderedSpy.wait());
1180     QTRY_COMPARE(cursor->image(), blue);
1181     QCOMPARE(cursor->hotspot(), QPoint(6, 6));
1182 
1183     // scaled cursor
1184     QImage blueScaled = QImage(QSize(20, 20), QImage::Format_ARGB32_Premultiplied);
1185     blueScaled.setDevicePixelRatio(2);
1186     blueScaled.fill(Qt::blue);
1187     auto bs = Test::waylandShmPool()->createBuffer(blueScaled);
1188     cursorSurface->attachBuffer(bs);
1189     cursorSurface->setScale(2);
1190     cursorSurface->damage(QRect(0, 0, 20, 20));
1191     cursorSurface->commit();
1192     QVERIFY(cursorRenderedSpy.wait());
1193     QTRY_COMPARE(cursor->image(), blueScaled);
1194     QCOMPARE(cursor->hotspot(), QPoint(6, 6)); // surface-local (so not changed)
1195 
1196     // hide the cursor
1197     pointer->setCursor(nullptr);
1198     Test::flushWaylandConnection();
1199     QTRY_VERIFY(cursor->image().isNull());
1200 
1201     // move cursor somewhere else, should reset to fallback cursor
1202     Cursors::self()->mouse()->setPos(window->frameGeometry().bottomLeft() + QPoint(20, 20));
1203     QVERIFY(!p->focus());
1204     QVERIFY(!cursor->image().isNull());
1205     QCOMPARE(cursor->image(), fallbackCursor);
1206 }
1207 
1208 class HelperEffect : public Effect
1209 {
1210     Q_OBJECT
1211 public:
1212     HelperEffect()
1213     {
1214     }
1215     ~HelperEffect() override
1216     {
1217     }
1218 };
1219 
1220 void PointerInputTest::testEffectOverrideCursorImage()
1221 {
1222     // this test verifies the effect cursor override handling
1223 
1224     // we need a pointer to get the enter event and set a cursor
1225     auto pointer = m_seat->createPointer(m_seat);
1226     auto cursor = Cursors::self()->mouse();
1227     QVERIFY(pointer);
1228     QVERIFY(pointer->isValid());
1229     QSignalSpy enteredSpy(pointer, &KWayland::Client::Pointer::entered);
1230     QSignalSpy leftSpy(pointer, &KWayland::Client::Pointer::left);
1231     // move cursor somewhere the new window won't open
1232     cursor->setPos(800, 800);
1233     // here we should have the fallback cursor
1234     const QImage fallback = cursor->image();
1235     QVERIFY(!fallback.isNull());
1236 
1237     // now let's create a window
1238     QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
1239     std::unique_ptr<KWayland::Client::Surface> surface = Test::createSurface();
1240     QVERIFY(surface);
1241     Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get());
1242     QVERIFY(shellSurface);
1243     render(surface.get());
1244     QVERIFY(windowAddedSpy.wait());
1245     Window *window = workspace()->activeWindow();
1246     QVERIFY(window);
1247 
1248     // and move cursor to the window
1249     QVERIFY(!window->frameGeometry().contains(QPoint(800, 800)));
1250     cursor->setPos(window->frameGeometry().center());
1251     QVERIFY(enteredSpy.wait());
1252     // cursor image should still be fallback
1253     QCOMPARE(cursor->image(), fallback);
1254 
1255     // now create an effect and set an override cursor
1256     std::unique_ptr<HelperEffect> effect(new HelperEffect);
1257     effects->startMouseInterception(effect.get(), Qt::SizeAllCursor);
1258     const QImage sizeAll = cursor->image();
1259     QVERIFY(!sizeAll.isNull());
1260     QVERIFY(sizeAll != fallback);
1261     QVERIFY(leftSpy.wait());
1262 
1263     // let's change to arrow cursor, this should be our fallback
1264     effects->defineCursor(Qt::ArrowCursor);
1265     QCOMPARE(cursor->image(), fallback);
1266 
1267     // back to size all
1268     effects->defineCursor(Qt::SizeAllCursor);
1269     QCOMPARE(cursor->image(), sizeAll);
1270 
1271     // move cursor outside the window area
1272     Cursors::self()->mouse()->setPos(800, 800);
1273     // and end the override, which should switch to fallback
1274     effects->stopMouseInterception(effect.get());
1275     QCOMPARE(cursor->image(), fallback);
1276 
1277     // start mouse interception again
1278     effects->startMouseInterception(effect.get(), Qt::SizeAllCursor);
1279     QCOMPARE(cursor->image(), sizeAll);
1280 
1281     // move cursor to area of window
1282     Cursors::self()->mouse()->setPos(window->frameGeometry().center());
1283     // this should not result in an enter event
1284     QVERIFY(!enteredSpy.wait(100));
1285 
1286     // after ending the interception we should get an enter event
1287     effects->stopMouseInterception(effect.get());
1288     QVERIFY(enteredSpy.wait());
1289     QVERIFY(cursor->image().isNull());
1290 }
1291 
1292 void PointerInputTest::testPopup()
1293 {
1294     // this test validates the basic popup behavior
1295     // a button press outside the window should dismiss the popup
1296 
1297     // first create a parent surface
1298     auto pointer = m_seat->createPointer(m_seat);
1299     QVERIFY(pointer);
1300     QVERIFY(pointer->isValid());
1301     QSignalSpy enteredSpy(pointer, &KWayland::Client::Pointer::entered);
1302     QSignalSpy leftSpy(pointer, &KWayland::Client::Pointer::left);
1303     QSignalSpy buttonStateChangedSpy(pointer, &KWayland::Client::Pointer::buttonStateChanged);
1304     QSignalSpy motionSpy(pointer, &KWayland::Client::Pointer::motion);
1305 
1306     Cursors::self()->mouse()->setPos(800, 800);
1307 
1308     QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
1309     std::unique_ptr<KWayland::Client::Surface> surface = Test::createSurface();
1310     QVERIFY(surface);
1311     Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get());
1312     QVERIFY(shellSurface);
1313     render(surface.get());
1314     QVERIFY(windowAddedSpy.wait());
1315     Window *window = workspace()->activeWindow();
1316     QVERIFY(window);
1317     QCOMPARE(window->hasPopupGrab(), false);
1318     // move pointer into window
1319     QVERIFY(!window->frameGeometry().contains(QPoint(800, 800)));
1320     Cursors::self()->mouse()->setPos(window->frameGeometry().center());
1321     QVERIFY(enteredSpy.wait());
1322     // click inside window to create serial
1323     quint32 timestamp = 0;
1324     Test::pointerButtonPressed(BTN_LEFT, timestamp++);
1325     Test::pointerButtonReleased(BTN_LEFT, timestamp++);
1326     QVERIFY(buttonStateChangedSpy.wait());
1327 
1328     // now create the popup surface
1329     std::unique_ptr<Test::XdgPositioner> positioner(Test::createXdgPositioner());
1330     positioner->set_size(100, 50);
1331     positioner->set_anchor_rect(0, 0, 80, 20);
1332     positioner->set_anchor(Test::XdgPositioner::anchor_bottom_right);
1333     positioner->set_gravity(Test::XdgPositioner::gravity_bottom_right);
1334     std::unique_ptr<KWayland::Client::Surface> popupSurface = Test::createSurface();
1335     QVERIFY(popupSurface);
1336     Test::XdgPopup *popupShellSurface = Test::createXdgPopupSurface(popupSurface.get(), shellSurface->xdgSurface(), positioner.get());
1337     QVERIFY(popupShellSurface);
1338     QSignalSpy doneReceivedSpy(popupShellSurface, &Test::XdgPopup::doneReceived);
1339     popupShellSurface->grab(*Test::waylandSeat(), 0); // FIXME: Serial.
1340     render(popupSurface.get(), QSize(100, 50));
1341     QVERIFY(windowAddedSpy.wait());
1342     auto popupWindow = windowAddedSpy.last().first().value<Window *>();
1343     QVERIFY(popupWindow);
1344     QVERIFY(popupWindow != window);
1345     QCOMPARE(window, workspace()->activeWindow());
1346     QCOMPARE(popupWindow->transientFor(), window);
1347     QCOMPARE(popupWindow->pos(), window->pos() + QPoint(80, 20));
1348     QCOMPARE(popupWindow->hasPopupGrab(), true);
1349 
1350     // let's move the pointer into the center of the window
1351     Cursors::self()->mouse()->setPos(popupWindow->frameGeometry().center());
1352     QVERIFY(enteredSpy.wait());
1353     QCOMPARE(enteredSpy.count(), 2);
1354     QCOMPARE(leftSpy.count(), 1);
1355     QCOMPARE(pointer->enteredSurface(), popupSurface.get());
1356 
1357     // let's move the pointer outside of the popup window
1358     // this should not really change anything, it gets a leave event
1359     Cursors::self()->mouse()->setPos(popupWindow->frameGeometry().bottomRight() + QPoint(2, 2));
1360     QVERIFY(leftSpy.wait());
1361     QCOMPARE(leftSpy.count(), 2);
1362     QVERIFY(doneReceivedSpy.isEmpty());
1363     // now click, should trigger popupDone
1364     Test::pointerButtonPressed(BTN_LEFT, timestamp++);
1365     QVERIFY(doneReceivedSpy.wait());
1366     Test::pointerButtonReleased(BTN_LEFT, timestamp++);
1367 }
1368 
1369 void PointerInputTest::testDecoCancelsPopup()
1370 {
1371     // this test verifies that clicking the window decoration of parent window
1372     // cancels the popup
1373 
1374     // first create a parent surface
1375     auto pointer = m_seat->createPointer(m_seat);
1376     QVERIFY(pointer);
1377     QVERIFY(pointer->isValid());
1378     QSignalSpy enteredSpy(pointer, &KWayland::Client::Pointer::entered);
1379     QSignalSpy leftSpy(pointer, &KWayland::Client::Pointer::left);
1380     QSignalSpy buttonStateChangedSpy(pointer, &KWayland::Client::Pointer::buttonStateChanged);
1381     QSignalSpy motionSpy(pointer, &KWayland::Client::Pointer::motion);
1382 
1383     Cursors::self()->mouse()->setPos(800, 800);
1384 
1385     // create a decorated window
1386     std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
1387     std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly));
1388     std::unique_ptr<Test::XdgToplevelDecorationV1> decoration(Test::createXdgToplevelDecorationV1(shellSurface.get()));
1389     QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested);
1390     decoration->set_mode(Test::XdgToplevelDecorationV1::mode_server_side);
1391     surface->commit(KWayland::Client::Surface::CommitFlag::None);
1392     QVERIFY(surfaceConfigureRequestedSpy.wait());
1393     shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
1394     auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
1395     QVERIFY(window);
1396     QCOMPARE(window->hasPopupGrab(), false);
1397     QVERIFY(window->isDecorated());
1398 
1399     // move pointer into window
1400     QVERIFY(!window->frameGeometry().contains(QPoint(800, 800)));
1401     Cursors::self()->mouse()->setPos(window->frameGeometry().center());
1402     QVERIFY(enteredSpy.wait());
1403     // click inside window to create serial
1404     quint32 timestamp = 0;
1405     Test::pointerButtonPressed(BTN_LEFT, timestamp++);
1406     Test::pointerButtonReleased(BTN_LEFT, timestamp++);
1407     QVERIFY(buttonStateChangedSpy.wait());
1408 
1409     // now create the popup surface
1410     std::unique_ptr<Test::XdgPositioner> positioner(Test::createXdgPositioner());
1411     positioner->set_size(100, 50);
1412     positioner->set_anchor_rect(0, 0, 80, 20);
1413     positioner->set_anchor(Test::XdgPositioner::anchor_bottom_right);
1414     positioner->set_gravity(Test::XdgPositioner::gravity_bottom_right);
1415     std::unique_ptr<KWayland::Client::Surface> popupSurface = Test::createSurface();
1416     QVERIFY(popupSurface);
1417     Test::XdgPopup *popupShellSurface = Test::createXdgPopupSurface(popupSurface.get(), shellSurface->xdgSurface(), positioner.get());
1418     QVERIFY(popupShellSurface);
1419     QSignalSpy doneReceivedSpy(popupShellSurface, &Test::XdgPopup::doneReceived);
1420     popupShellSurface->grab(*Test::waylandSeat(), 0); // FIXME: Serial.
1421     auto popupWindow = Test::renderAndWaitForShown(popupSurface.get(), QSize(100, 50), Qt::red);
1422     QVERIFY(popupWindow);
1423     QVERIFY(popupWindow != window);
1424     QCOMPARE(window, workspace()->activeWindow());
1425     QCOMPARE(popupWindow->transientFor(), window);
1426     QCOMPARE(popupWindow->pos(), window->mapFromLocal(QPoint(80, 20)));
1427     QCOMPARE(popupWindow->hasPopupGrab(), true);
1428 
1429     // let's move the pointer into the center of the deco
1430     Cursors::self()->mouse()->setPos(window->frameGeometry().center().x(), window->y() + (window->height() - window->clientSize().height()) / 2);
1431 
1432     Test::pointerButtonPressed(BTN_RIGHT, timestamp++);
1433     QVERIFY(doneReceivedSpy.wait());
1434     Test::pointerButtonReleased(BTN_RIGHT, timestamp++);
1435 }
1436 
1437 void PointerInputTest::testWindowUnderCursorWhileButtonPressed()
1438 {
1439     // this test verifies that opening a window underneath the mouse cursor does not
1440     // trigger a leave event if a button is pressed
1441     // see BUG: 372876
1442 
1443     // first create a parent surface
1444     auto pointer = m_seat->createPointer(m_seat);
1445     QVERIFY(pointer);
1446     QVERIFY(pointer->isValid());
1447     QSignalSpy enteredSpy(pointer, &KWayland::Client::Pointer::entered);
1448     QSignalSpy leftSpy(pointer, &KWayland::Client::Pointer::left);
1449 
1450     Cursors::self()->mouse()->setPos(800, 800);
1451     QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
1452     std::unique_ptr<KWayland::Client::Surface> surface = Test::createSurface();
1453     QVERIFY(surface);
1454     Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get());
1455     QVERIFY(shellSurface);
1456     render(surface.get());
1457     QVERIFY(windowAddedSpy.wait());
1458     Window *window = workspace()->activeWindow();
1459     QVERIFY(window);
1460 
1461     // move cursor over window
1462     QVERIFY(!window->frameGeometry().contains(QPoint(800, 800)));
1463     Cursors::self()->mouse()->setPos(window->frameGeometry().center());
1464     QVERIFY(enteredSpy.wait());
1465     // click inside window
1466     quint32 timestamp = 0;
1467     Test::pointerButtonPressed(BTN_LEFT, timestamp++);
1468 
1469     // now create a second window as transient
1470     std::unique_ptr<Test::XdgPositioner> positioner(Test::createXdgPositioner());
1471     positioner->set_size(99, 49);
1472     positioner->set_anchor_rect(0, 0, 1, 1);
1473     positioner->set_anchor(Test::XdgPositioner::anchor_bottom_right);
1474     positioner->set_gravity(Test::XdgPositioner::gravity_bottom_right);
1475     std::unique_ptr<KWayland::Client::Surface> popupSurface = Test::createSurface();
1476     QVERIFY(popupSurface);
1477     Test::XdgPopup *popupShellSurface = Test::createXdgPopupSurface(popupSurface.get(), shellSurface->xdgSurface(), positioner.get());
1478     QVERIFY(popupShellSurface);
1479     render(popupSurface.get(), QSize(99, 49));
1480     QVERIFY(windowAddedSpy.wait());
1481     auto popupWindow = windowAddedSpy.last().first().value<Window *>();
1482     QVERIFY(popupWindow);
1483     QVERIFY(popupWindow != window);
1484     QVERIFY(window->frameGeometry().contains(Cursors::self()->mouse()->pos()));
1485     QVERIFY(popupWindow->frameGeometry().contains(Cursors::self()->mouse()->pos()));
1486     QVERIFY(!leftSpy.wait());
1487 
1488     Test::pointerButtonReleased(BTN_LEFT, timestamp++);
1489     // now that the button is no longer pressed we should get the leave event
1490     QVERIFY(leftSpy.wait());
1491     QCOMPARE(leftSpy.count(), 1);
1492     QCOMPARE(enteredSpy.count(), 2);
1493 }
1494 
1495 void PointerInputTest::testConfineToScreenGeometry_data()
1496 {
1497     QTest::addColumn<QPoint>("startPos");
1498     QTest::addColumn<QPoint>("targetPos");
1499     QTest::addColumn<QPoint>("expectedPos");
1500 
1501     // screen layout:
1502     //
1503     // +----------+----------+---------+
1504     // |   left   |    top   |  right  |
1505     // +----------+----------+---------+
1506     //            |  bottom  |
1507     //            +----------+
1508     //
1509 
1510     QTest::newRow("move top-left - left screen") << QPoint(640, 512) << QPoint(-100, -100) << QPoint(0, 0);
1511     QTest::newRow("move top - left screen") << QPoint(640, 512) << QPoint(640, -100) << QPoint(640, 0);
1512     QTest::newRow("move top-right - left screen") << QPoint(640, 512) << QPoint(1380, -100) << QPoint(1380, 0);
1513     QTest::newRow("move right - left screen") << QPoint(640, 512) << QPoint(1380, 512) << QPoint(1380, 512);
1514     QTest::newRow("move bottom-right - left screen") << QPoint(640, 512) << QPoint(1380, 1124) << QPoint(1380, 1124);
1515     QTest::newRow("move bottom - left screen") << QPoint(640, 512) << QPoint(640, 1124) << QPoint(640, 1023);
1516     QTest::newRow("move bottom-left - left screen") << QPoint(640, 512) << QPoint(-100, 1124) << QPoint(0, 1023);
1517     QTest::newRow("move left - left screen") << QPoint(640, 512) << QPoint(-100, 512) << QPoint(0, 512);
1518 
1519     QTest::newRow("move top-left - top screen") << QPoint(1920, 512) << QPoint(1180, -100) << QPoint(1180, 0);
1520     QTest::newRow("move top - top screen") << QPoint(1920, 512) << QPoint(1920, -100) << QPoint(1920, 0);
1521     QTest::newRow("move top-right - top screen") << QPoint(1920, 512) << QPoint(2660, -100) << QPoint(2660, 0);
1522     QTest::newRow("move right - top screen") << QPoint(1920, 512) << QPoint(2660, 512) << QPoint(2660, 512);
1523     QTest::newRow("move bottom-right - top screen") << QPoint(1920, 512) << QPoint(2660, 1124) << QPoint(2660, 1023);
1524     QTest::newRow("move bottom - top screen") << QPoint(1920, 512) << QPoint(1920, 1124) << QPoint(1920, 1124);
1525     QTest::newRow("move bottom-left - top screen") << QPoint(1920, 512) << QPoint(1180, 1124) << QPoint(1280, 1124);
1526     QTest::newRow("move left - top screen") << QPoint(1920, 512) << QPoint(1180, 512) << QPoint(1180, 512);
1527 
1528     QTest::newRow("move top-left - right screen") << QPoint(3200, 512) << QPoint(2460, -100) << QPoint(2460, 0);
1529     QTest::newRow("move top - right screen") << QPoint(3200, 512) << QPoint(3200, -100) << QPoint(3200, 0);
1530     QTest::newRow("move top-right - right screen") << QPoint(3200, 512) << QPoint(3940, -100) << QPoint(3839, 0);
1531     QTest::newRow("move right - right screen") << QPoint(3200, 512) << QPoint(3940, 512) << QPoint(3839, 512);
1532     QTest::newRow("move bottom-right - right screen") << QPoint(3200, 512) << QPoint(3940, 1124) << QPoint(3839, 1023);
1533     QTest::newRow("move bottom - right screen") << QPoint(3200, 512) << QPoint(3200, 1124) << QPoint(3200, 1023);
1534     QTest::newRow("move bottom-left - right screen") << QPoint(3200, 512) << QPoint(2460, 1124) << QPoint(2460, 1124);
1535     QTest::newRow("move left - right screen") << QPoint(3200, 512) << QPoint(2460, 512) << QPoint(2460, 512);
1536 
1537     QTest::newRow("move top-left - bottom screen") << QPoint(1920, 1536) << QPoint(1180, 924) << QPoint(1180, 924);
1538     QTest::newRow("move top - bottom screen") << QPoint(1920, 1536) << QPoint(1920, 924) << QPoint(1920, 924);
1539     QTest::newRow("move top-right - bottom screen") << QPoint(1920, 1536) << QPoint(2660, 924) << QPoint(2660, 924);
1540     QTest::newRow("move right - bottom screen") << QPoint(1920, 1536) << QPoint(2660, 1536) << QPoint(2559, 1536);
1541     QTest::newRow("move bottom-right - bottom screen") << QPoint(1920, 1536) << QPoint(2660, 2148) << QPoint(2559, 2047);
1542     QTest::newRow("move bottom - bottom screen") << QPoint(1920, 1536) << QPoint(1920, 2148) << QPoint(1920, 2047);
1543     QTest::newRow("move bottom-left - bottom screen") << QPoint(1920, 1536) << QPoint(1180, 2148) << QPoint(1280, 2047);
1544     QTest::newRow("move left - bottom screen") << QPoint(1920, 1536) << QPoint(1180, 1536) << QPoint(1280, 1536);
1545 }
1546 
1547 void PointerInputTest::testConfineToScreenGeometry()
1548 {
1549     // this test verifies that pointer belongs to at least one screen
1550     // after moving it to off-screen area
1551 
1552     // unload the Window View effect because it pushes back
1553     // pointer if it's at (0, 0)
1554     static_cast<EffectsHandlerImpl *>(effects)->unloadEffect(QStringLiteral("windowview"));
1555 
1556     // setup screen layout
1557     const QVector<QRect> geometries{
1558         QRect(0, 0, 1280, 1024),
1559         QRect(1280, 0, 1280, 1024),
1560         QRect(2560, 0, 1280, 1024),
1561         QRect(1280, 1024, 1280, 1024)};
1562     QMetaObject::invokeMethod(kwinApp()->outputBackend(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(QVector<QRect>, geometries));
1563 
1564     const auto outputs = workspace()->outputs();
1565     QCOMPARE(outputs.count(), geometries.count());
1566     QCOMPARE(outputs[0]->geometry(), geometries.at(0));
1567     QCOMPARE(outputs[1]->geometry(), geometries.at(1));
1568     QCOMPARE(outputs[2]->geometry(), geometries.at(2));
1569     QCOMPARE(outputs[3]->geometry(), geometries.at(3));
1570 
1571     // move pointer to initial position
1572     QFETCH(QPoint, startPos);
1573     Cursors::self()->mouse()->setPos(startPos);
1574     QCOMPARE(Cursors::self()->mouse()->pos(), startPos);
1575 
1576     // perform movement
1577     QFETCH(QPoint, targetPos);
1578     Test::pointerMotion(targetPos, 1);
1579 
1580     QFETCH(QPoint, expectedPos);
1581     QCOMPARE(Cursors::self()->mouse()->pos(), expectedPos);
1582 }
1583 
1584 void PointerInputTest::testResizeCursor_data()
1585 {
1586     QTest::addColumn<Qt::Edges>("edges");
1587     QTest::addColumn<KWin::CursorShape>("cursorShape");
1588 
1589     QTest::newRow("top-left") << Qt::Edges(Qt::TopEdge | Qt::LeftEdge) << CursorShape(ExtendedCursor::SizeNorthWest);
1590     QTest::newRow("top") << Qt::Edges(Qt::TopEdge) << CursorShape(ExtendedCursor::SizeNorth);
1591     QTest::newRow("top-right") << Qt::Edges(Qt::TopEdge | Qt::RightEdge) << CursorShape(ExtendedCursor::SizeNorthEast);
1592     QTest::newRow("right") << Qt::Edges(Qt::RightEdge) << CursorShape(ExtendedCursor::SizeEast);
1593     QTest::newRow("bottom-right") << Qt::Edges(Qt::BottomEdge | Qt::RightEdge) << CursorShape(ExtendedCursor::SizeSouthEast);
1594     QTest::newRow("bottom") << Qt::Edges(Qt::BottomEdge) << CursorShape(ExtendedCursor::SizeSouth);
1595     QTest::newRow("bottom-left") << Qt::Edges(Qt::BottomEdge | Qt::LeftEdge) << CursorShape(ExtendedCursor::SizeSouthWest);
1596     QTest::newRow("left") << Qt::Edges(Qt::LeftEdge) << CursorShape(ExtendedCursor::SizeWest);
1597 }
1598 
1599 void PointerInputTest::testResizeCursor()
1600 {
1601     // this test verifies that the cursor has correct shape during resize operation
1602 
1603     // first modify the config for this run
1604     KConfigGroup group = kwinApp()->config()->group("MouseBindings");
1605     group.writeEntry("CommandAllKey", "Meta");
1606     group.writeEntry("CommandAll3", "Resize");
1607     group.sync();
1608     workspace()->slotReconfigure();
1609     QCOMPARE(options->commandAllModifier(), Qt::MetaModifier);
1610     QCOMPARE(options->commandAll3(), Options::MouseUnrestrictedResize);
1611 
1612     // load the fallback cursor (arrow cursor)
1613     const PlatformCursorImage arrowCursor = loadReferenceThemeCursor(Qt::ArrowCursor);
1614     QVERIFY(!arrowCursor.isNull());
1615     QCOMPARE(kwinApp()->cursorImage().image(), arrowCursor.image());
1616     QCOMPARE(kwinApp()->cursorImage().hotSpot(), arrowCursor.hotSpot());
1617 
1618     // we need a pointer to get the enter event
1619     auto pointer = m_seat->createPointer(m_seat);
1620     QVERIFY(pointer);
1621     QVERIFY(pointer->isValid());
1622     QSignalSpy enteredSpy(pointer, &KWayland::Client::Pointer::entered);
1623 
1624     // create a test window
1625     std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
1626     QVERIFY(surface != nullptr);
1627     std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
1628     QVERIFY(shellSurface != nullptr);
1629     Window *window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
1630     QVERIFY(window);
1631 
1632     // move the cursor to the test position
1633     QPoint cursorPos;
1634     QFETCH(Qt::Edges, edges);
1635 
1636     if (edges & Qt::LeftEdge) {
1637         cursorPos.setX(window->frameGeometry().left());
1638     } else if (edges & Qt::RightEdge) {
1639         cursorPos.setX(window->frameGeometry().right() - 1);
1640     } else {
1641         cursorPos.setX(window->frameGeometry().center().x());
1642     }
1643 
1644     if (edges & Qt::TopEdge) {
1645         cursorPos.setY(window->frameGeometry().top());
1646     } else if (edges & Qt::BottomEdge) {
1647         cursorPos.setY(window->frameGeometry().bottom() - 1);
1648     } else {
1649         cursorPos.setY(window->frameGeometry().center().y());
1650     }
1651 
1652     Cursors::self()->mouse()->setPos(cursorPos);
1653 
1654     // wait for the enter event and set the cursor
1655     QVERIFY(enteredSpy.wait());
1656     std::unique_ptr<KWayland::Client::Surface> cursorSurface(Test::createSurface());
1657     QVERIFY(cursorSurface);
1658     QSignalSpy cursorRenderedSpy(cursorSurface.get(), &KWayland::Client::Surface::frameRendered);
1659     cursorSurface->attachBuffer(Test::waylandShmPool()->createBuffer(arrowCursor.image()));
1660     cursorSurface->damage(arrowCursor.image().rect());
1661     cursorSurface->commit();
1662     pointer->setCursor(cursorSurface.get(), arrowCursor.hotSpot());
1663     QVERIFY(cursorRenderedSpy.wait());
1664 
1665     // start resizing the window
1666     int timestamp = 1;
1667     Test::keyboardKeyPressed(KEY_LEFTMETA, timestamp++);
1668     Test::pointerButtonPressed(BTN_RIGHT, timestamp++);
1669     QVERIFY(window->isInteractiveResize());
1670 
1671     QFETCH(KWin::CursorShape, cursorShape);
1672     const PlatformCursorImage resizeCursor = loadReferenceThemeCursor(cursorShape);
1673     QVERIFY(!resizeCursor.isNull());
1674     QCOMPARE(kwinApp()->cursorImage().image(), resizeCursor.image());
1675     QCOMPARE(kwinApp()->cursorImage().hotSpot(), resizeCursor.hotSpot());
1676 
1677     // finish resizing the window
1678     Test::keyboardKeyReleased(KEY_LEFTMETA, timestamp++);
1679     Test::pointerButtonReleased(BTN_RIGHT, timestamp++);
1680     QVERIFY(!window->isInteractiveResize());
1681 
1682     QCOMPARE(kwinApp()->cursorImage().image(), arrowCursor.image());
1683     QCOMPARE(kwinApp()->cursorImage().hotSpot(), arrowCursor.hotSpot());
1684 }
1685 
1686 void PointerInputTest::testMoveCursor()
1687 {
1688     // this test verifies that the cursor has correct shape during move operation
1689 
1690     // first modify the config for this run
1691     KConfigGroup group = kwinApp()->config()->group("MouseBindings");
1692     group.writeEntry("CommandAllKey", "Meta");
1693     group.writeEntry("CommandAll1", "Move");
1694     group.sync();
1695     workspace()->slotReconfigure();
1696     QCOMPARE(options->commandAllModifier(), Qt::MetaModifier);
1697     QCOMPARE(options->commandAll1(), Options::MouseUnrestrictedMove);
1698 
1699     // load the fallback cursor (arrow cursor)
1700     const PlatformCursorImage arrowCursor = loadReferenceThemeCursor(Qt::ArrowCursor);
1701     QVERIFY(!arrowCursor.isNull());
1702     QCOMPARE(kwinApp()->cursorImage().image(), arrowCursor.image());
1703     QCOMPARE(kwinApp()->cursorImage().hotSpot(), arrowCursor.hotSpot());
1704 
1705     // we need a pointer to get the enter event
1706     auto pointer = m_seat->createPointer(m_seat);
1707     QVERIFY(pointer);
1708     QVERIFY(pointer->isValid());
1709     QSignalSpy enteredSpy(pointer, &KWayland::Client::Pointer::entered);
1710 
1711     // create a test window
1712     std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
1713     QVERIFY(surface != nullptr);
1714     std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
1715     QVERIFY(shellSurface != nullptr);
1716     Window *window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
1717     QVERIFY(window);
1718 
1719     // move cursor to the test position
1720     Cursors::self()->mouse()->setPos(window->frameGeometry().center());
1721 
1722     // wait for the enter event and set the cursor
1723     QVERIFY(enteredSpy.wait());
1724     std::unique_ptr<KWayland::Client::Surface> cursorSurface = Test::createSurface();
1725     QVERIFY(cursorSurface);
1726     QSignalSpy cursorRenderedSpy(cursorSurface.get(), &KWayland::Client::Surface::frameRendered);
1727     cursorSurface->attachBuffer(Test::waylandShmPool()->createBuffer(arrowCursor.image()));
1728     cursorSurface->damage(arrowCursor.image().rect());
1729     cursorSurface->commit();
1730     pointer->setCursor(cursorSurface.get(), arrowCursor.hotSpot());
1731     QVERIFY(cursorRenderedSpy.wait());
1732 
1733     // start moving the window
1734     int timestamp = 1;
1735     Test::keyboardKeyPressed(KEY_LEFTMETA, timestamp++);
1736     Test::pointerButtonPressed(BTN_LEFT, timestamp++);
1737     QVERIFY(window->isInteractiveMove());
1738 
1739     const PlatformCursorImage sizeAllCursor = loadReferenceThemeCursor(Qt::SizeAllCursor);
1740     QVERIFY(!sizeAllCursor.isNull());
1741     QCOMPARE(kwinApp()->cursorImage().image(), sizeAllCursor.image());
1742     QCOMPARE(kwinApp()->cursorImage().hotSpot(), sizeAllCursor.hotSpot());
1743 
1744     // finish moving the window
1745     Test::keyboardKeyReleased(KEY_LEFTMETA, timestamp++);
1746     Test::pointerButtonReleased(BTN_LEFT, timestamp++);
1747     QVERIFY(!window->isInteractiveMove());
1748 
1749     QCOMPARE(kwinApp()->cursorImage().image(), arrowCursor.image());
1750     QCOMPARE(kwinApp()->cursorImage().hotSpot(), arrowCursor.hotSpot());
1751 }
1752 
1753 void PointerInputTest::testHideShowCursor()
1754 {
1755     QCOMPARE(Cursors::self()->isCursorHidden(), false);
1756     Cursors::self()->hideCursor();
1757     QCOMPARE(Cursors::self()->isCursorHidden(), true);
1758     Cursors::self()->showCursor();
1759     QCOMPARE(Cursors::self()->isCursorHidden(), false);
1760 
1761     Cursors::self()->hideCursor();
1762     QCOMPARE(Cursors::self()->isCursorHidden(), true);
1763     Cursors::self()->hideCursor();
1764     Cursors::self()->hideCursor();
1765     Cursors::self()->hideCursor();
1766     QCOMPARE(Cursors::self()->isCursorHidden(), true);
1767 
1768     Cursors::self()->showCursor();
1769     QCOMPARE(Cursors::self()->isCursorHidden(), true);
1770     Cursors::self()->showCursor();
1771     QCOMPARE(Cursors::self()->isCursorHidden(), true);
1772     Cursors::self()->showCursor();
1773     QCOMPARE(Cursors::self()->isCursorHidden(), true);
1774     Cursors::self()->showCursor();
1775     QCOMPARE(Cursors::self()->isCursorHidden(), false);
1776 }
1777 
1778 void PointerInputTest::testDefaultInputRegion()
1779 {
1780     // This test verifies that a surface that hasn't specified the input region can be focused.
1781 
1782     // Create a test window.
1783     std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
1784     QVERIFY(surface != nullptr);
1785     std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
1786     QVERIFY(shellSurface != nullptr);
1787     Window *window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
1788     QVERIFY(window);
1789 
1790     // Move the point to the center of the surface.
1791     Cursors::self()->mouse()->setPos(window->frameGeometry().center());
1792     QCOMPARE(waylandServer()->seat()->focusedPointerSurface(), window->surface());
1793 
1794     // Destroy the test window.
1795     shellSurface.reset();
1796     QVERIFY(Test::waitForWindowDestroyed(window));
1797 }
1798 
1799 void PointerInputTest::testEmptyInputRegion()
1800 {
1801     // This test verifies that a surface that has specified an empty input region can't be focused.
1802 
1803     // Create a test window.
1804     std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
1805     QVERIFY(surface != nullptr);
1806     std::unique_ptr<KWayland::Client::Region> inputRegion(m_compositor->createRegion(QRegion()));
1807     surface->setInputRegion(inputRegion.get());
1808     std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
1809     QVERIFY(shellSurface != nullptr);
1810     Window *window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
1811     QVERIFY(window);
1812 
1813     // Move the point to the center of the surface.
1814     Cursors::self()->mouse()->setPos(window->frameGeometry().center());
1815     QVERIFY(!waylandServer()->seat()->focusedPointerSurface());
1816 
1817     // Destroy the test window.
1818     shellSurface.reset();
1819     QVERIFY(Test::waitForWindowDestroyed(window));
1820 }
1821 
1822 }
1823 
1824 WAYLANDTEST_MAIN(KWin::PointerInputTest)
1825 #include "pointer_input.moc"