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