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