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