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

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