File indexing completed on 2024-05-19 09:23:13

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