File indexing completed on 2025-03-23 13:47:55
0001 /* 0002 KWin - the KDE window manager 0003 This file is part of the KDE project. 0004 0005 SPDX-FileCopyrightText: 2016 Martin Gräßlin <mgraesslin@kde.org> 0006 0007 SPDX-License-Identifier: GPL-2.0-or-later 0008 */ 0009 #include "kwin_wayland_test.h" 0010 0011 #include "core/output.h" 0012 #include "core/outputbackend.h" 0013 #include "cursor.h" 0014 #include "internalwindow.h" 0015 #include "pointer_input.h" 0016 #include "touch_input.h" 0017 #include "wayland_server.h" 0018 #include "window.h" 0019 #include "workspace.h" 0020 #include <kwineffects.h> 0021 0022 #include "decorations/decoratedclient.h" 0023 #include "decorations/decorationbridge.h" 0024 #include "decorations/settings.h" 0025 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/seat.h> 0031 #include <KWayland/Client/shm_pool.h> 0032 #include <KWayland/Client/surface.h> 0033 0034 #include <KDecoration2/Decoration> 0035 #include <KDecoration2/DecorationSettings> 0036 0037 #include <linux/input.h> 0038 0039 Q_DECLARE_METATYPE(Qt::WindowFrameSection) 0040 0041 namespace KWin 0042 { 0043 0044 static const QString s_socketName = QStringLiteral("wayland_test_kwin_decoration_input-0"); 0045 0046 class DecorationInputTest : public QObject 0047 { 0048 Q_OBJECT 0049 private Q_SLOTS: 0050 void initTestCase(); 0051 void init(); 0052 void cleanup(); 0053 void testAxis_data(); 0054 void testAxis(); 0055 void testDoubleClick_data(); 0056 void testDoubleClick(); 0057 void testDoubleTap_data(); 0058 void testDoubleTap(); 0059 void testHover(); 0060 void testPressToMove_data(); 0061 void testPressToMove(); 0062 void testTapToMove_data(); 0063 void testTapToMove(); 0064 void testResizeOutsideWindow_data(); 0065 void testResizeOutsideWindow(); 0066 void testModifierClickUnrestrictedMove_data(); 0067 void testModifierClickUnrestrictedMove(); 0068 void testModifierScrollOpacity_data(); 0069 void testModifierScrollOpacity(); 0070 void testTouchEvents(); 0071 void testTooltipDoesntEatKeyEvents(); 0072 0073 private: 0074 std::pair<Window *, std::unique_ptr<KWayland::Client::Surface>> showWindow(); 0075 }; 0076 0077 #define MOTION(target) Test::pointerMotion(target, timestamp++) 0078 0079 #define PRESS Test::pointerButtonPressed(BTN_LEFT, timestamp++) 0080 0081 #define RELEASE Test::pointerButtonReleased(BTN_LEFT, timestamp++) 0082 0083 std::pair<Window *, std::unique_ptr<KWayland::Client::Surface>> DecorationInputTest::showWindow() 0084 { 0085 #define VERIFY(statement) \ 0086 if (!QTest::qVerify((statement), #statement, "", __FILE__, __LINE__)) \ 0087 return {nullptr, nullptr}; 0088 #define COMPARE(actual, expected) \ 0089 if (!QTest::qCompare(actual, expected, #actual, #expected, __FILE__, __LINE__)) \ 0090 return {nullptr, nullptr}; 0091 0092 std::unique_ptr<KWayland::Client::Surface> surface{Test::createSurface()}; 0093 VERIFY(surface.get()); 0094 Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly, surface.get()); 0095 VERIFY(shellSurface); 0096 Test::XdgToplevelDecorationV1 *decoration = Test::createXdgToplevelDecorationV1(shellSurface, shellSurface); 0097 VERIFY(decoration); 0098 0099 QSignalSpy decorationConfigureRequestedSpy(decoration, &Test::XdgToplevelDecorationV1::configureRequested); 0100 QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); 0101 0102 decoration->set_mode(Test::XdgToplevelDecorationV1::mode_server_side); 0103 surface->commit(KWayland::Client::Surface::CommitFlag::None); 0104 VERIFY(surfaceConfigureRequestedSpy.wait()); 0105 COMPARE(decorationConfigureRequestedSpy.last().at(0).value<Test::XdgToplevelDecorationV1::mode>(), Test::XdgToplevelDecorationV1::mode_server_side); 0106 0107 // let's render 0108 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>()); 0109 auto window = Test::renderAndWaitForShown(surface.get(), QSize(500, 50), Qt::blue); 0110 VERIFY(window); 0111 COMPARE(workspace()->activeWindow(), window); 0112 0113 #undef VERIFY 0114 #undef COMPARE 0115 0116 return {window, std::move(surface)}; 0117 } 0118 0119 void DecorationInputTest::initTestCase() 0120 { 0121 qRegisterMetaType<KWin::Window *>(); 0122 qRegisterMetaType<KWin::InternalWindow *>(); 0123 QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); 0124 QVERIFY(waylandServer()->init(s_socketName)); 0125 QMetaObject::invokeMethod(kwinApp()->outputBackend(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(QVector<QRect>, QVector<QRect>() << QRect(0, 0, 1280, 1024) << QRect(1280, 0, 1280, 1024))); 0126 0127 // change some options 0128 KSharedConfig::Ptr config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); 0129 config->group(QStringLiteral("MouseBindings")).writeEntry("CommandTitlebarWheel", QStringLiteral("above/below")); 0130 config->group(QStringLiteral("Windows")).writeEntry("TitlebarDoubleClickCommand", QStringLiteral("OnAllDesktops")); 0131 config->group(QStringLiteral("Desktops")).writeEntry("Number", 2); 0132 config->sync(); 0133 0134 kwinApp()->setConfig(config); 0135 0136 kwinApp()->start(); 0137 QVERIFY(applicationStartedSpy.wait()); 0138 const auto outputs = workspace()->outputs(); 0139 QCOMPARE(outputs.count(), 2); 0140 QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024)); 0141 QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024)); 0142 setenv("QT_QPA_PLATFORM", "wayland", true); 0143 } 0144 0145 void DecorationInputTest::init() 0146 { 0147 QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Seat | Test::AdditionalWaylandInterface::XdgDecorationV1)); 0148 QVERIFY(Test::waitForWaylandPointer()); 0149 0150 workspace()->setActiveOutput(QPoint(640, 512)); 0151 Cursors::self()->mouse()->setPos(QPoint(640, 512)); 0152 } 0153 0154 void DecorationInputTest::cleanup() 0155 { 0156 Test::destroyWaylandConnection(); 0157 } 0158 0159 void DecorationInputTest::testAxis_data() 0160 { 0161 QTest::addColumn<QPoint>("decoPoint"); 0162 QTest::addColumn<Qt::WindowFrameSection>("expectedSection"); 0163 0164 QTest::newRow("topLeft") << QPoint(0, 0) << Qt::TopLeftSection; 0165 QTest::newRow("top") << QPoint(250, 0) << Qt::TopSection; 0166 QTest::newRow("topRight") << QPoint(499, 0) << Qt::TopRightSection; 0167 } 0168 0169 void DecorationInputTest::testAxis() 0170 { 0171 static constexpr double oneTick = 15; 0172 0173 const auto [window, surface] = showWindow(); 0174 QVERIFY(window); 0175 QVERIFY(window->isDecorated()); 0176 QVERIFY(!window->noBorder()); 0177 QCOMPARE(window->titlebarPosition(), Qt::TopEdge); 0178 QVERIFY(!window->keepAbove()); 0179 QVERIFY(!window->keepBelow()); 0180 0181 quint32 timestamp = 1; 0182 MOTION(QPoint(window->frameGeometry().center().x(), window->frameMargins().top() / 2.0)); 0183 QVERIFY(input()->pointer()->decoration()); 0184 QCOMPARE(input()->pointer()->decoration()->decoration()->sectionUnderMouse(), Qt::TitleBarArea); 0185 0186 // TODO: mouse wheel direction looks wrong to me 0187 // simulate wheel 0188 Test::pointerAxisVertical(oneTick, timestamp++); 0189 QVERIFY(window->keepBelow()); 0190 QVERIFY(!window->keepAbove()); 0191 Test::pointerAxisVertical(-oneTick, timestamp++); 0192 QVERIFY(!window->keepBelow()); 0193 QVERIFY(!window->keepAbove()); 0194 Test::pointerAxisVertical(-oneTick, timestamp++); 0195 QVERIFY(!window->keepBelow()); 0196 QVERIFY(window->keepAbove()); 0197 0198 // test top most deco pixel, BUG: 362860 0199 window->move(QPoint(0, 0)); 0200 QFETCH(QPoint, decoPoint); 0201 MOTION(decoPoint); 0202 QVERIFY(input()->pointer()->decoration()); 0203 QCOMPARE(input()->pointer()->decoration()->window(), window); 0204 QTEST(input()->pointer()->decoration()->decoration()->sectionUnderMouse(), "expectedSection"); 0205 Test::pointerAxisVertical(oneTick, timestamp++); 0206 QVERIFY(!window->keepBelow()); 0207 QVERIFY(!window->keepAbove()); 0208 } 0209 0210 void DecorationInputTest::testDoubleClick_data() 0211 { 0212 QTest::addColumn<QPoint>("decoPoint"); 0213 QTest::addColumn<Qt::WindowFrameSection>("expectedSection"); 0214 0215 QTest::newRow("topLeft") << QPoint(0, 0) << Qt::TopLeftSection; 0216 QTest::newRow("top") << QPoint(250, 0) << Qt::TopSection; 0217 QTest::newRow("topRight") << QPoint(499, 0) << Qt::TopRightSection; 0218 } 0219 0220 void KWin::DecorationInputTest::testDoubleClick() 0221 { 0222 const auto [window, surface] = showWindow(); 0223 QVERIFY(window); 0224 QVERIFY(window->isDecorated()); 0225 QVERIFY(!window->noBorder()); 0226 QVERIFY(!window->isOnAllDesktops()); 0227 quint32 timestamp = 1; 0228 MOTION(QPoint(window->frameGeometry().center().x(), window->frameMargins().top() / 2.0)); 0229 0230 // double click 0231 PRESS; 0232 RELEASE; 0233 PRESS; 0234 RELEASE; 0235 QVERIFY(window->isOnAllDesktops()); 0236 // double click again 0237 PRESS; 0238 RELEASE; 0239 QVERIFY(window->isOnAllDesktops()); 0240 PRESS; 0241 RELEASE; 0242 QVERIFY(!window->isOnAllDesktops()); 0243 0244 // test top most deco pixel, BUG: 362860 0245 window->move(QPoint(0, 0)); 0246 QFETCH(QPoint, decoPoint); 0247 MOTION(decoPoint); 0248 QVERIFY(input()->pointer()->decoration()); 0249 QCOMPARE(input()->pointer()->decoration()->window(), window); 0250 QTEST(input()->pointer()->decoration()->decoration()->sectionUnderMouse(), "expectedSection"); 0251 // double click 0252 PRESS; 0253 RELEASE; 0254 QVERIFY(!window->isOnAllDesktops()); 0255 PRESS; 0256 RELEASE; 0257 QVERIFY(window->isOnAllDesktops()); 0258 } 0259 0260 void DecorationInputTest::testDoubleTap_data() 0261 { 0262 QTest::addColumn<QPoint>("decoPoint"); 0263 QTest::addColumn<Qt::WindowFrameSection>("expectedSection"); 0264 0265 QTest::newRow("topLeft") << QPoint(10, 10) << Qt::TopLeftSection; 0266 QTest::newRow("top") << QPoint(260, 10) << Qt::TopSection; 0267 QTest::newRow("topRight") << QPoint(509, 10) << Qt::TopRightSection; 0268 } 0269 0270 void KWin::DecorationInputTest::testDoubleTap() 0271 { 0272 const auto [window, surface] = showWindow(); 0273 QVERIFY(window); 0274 QVERIFY(window->isDecorated()); 0275 QVERIFY(!window->noBorder()); 0276 QVERIFY(!window->isOnAllDesktops()); 0277 quint32 timestamp = 1; 0278 const QPoint tapPoint(window->frameGeometry().center().x(), window->frameMargins().top() / 2.0); 0279 0280 // double tap 0281 Test::touchDown(0, tapPoint, timestamp++); 0282 Test::touchUp(0, timestamp++); 0283 Test::touchDown(0, tapPoint, timestamp++); 0284 Test::touchUp(0, timestamp++); 0285 QVERIFY(window->isOnAllDesktops()); 0286 // double tap again 0287 Test::touchDown(0, tapPoint, timestamp++); 0288 Test::touchUp(0, timestamp++); 0289 QVERIFY(window->isOnAllDesktops()); 0290 Test::touchDown(0, tapPoint, timestamp++); 0291 Test::touchUp(0, timestamp++); 0292 QVERIFY(!window->isOnAllDesktops()); 0293 0294 // test top most deco pixel, BUG: 362860 0295 // 0296 // Not directly at (0, 0), otherwise ScreenEdgeInputFilter catches 0297 // event before DecorationEventFilter. 0298 window->move(QPoint(10, 10)); 0299 QFETCH(QPoint, decoPoint); 0300 // double click 0301 Test::touchDown(0, decoPoint, timestamp++); 0302 QVERIFY(input()->touch()->decoration()); 0303 QCOMPARE(input()->touch()->decoration()->window(), window); 0304 QTEST(input()->touch()->decoration()->decoration()->sectionUnderMouse(), "expectedSection"); 0305 Test::touchUp(0, timestamp++); 0306 QVERIFY(!window->isOnAllDesktops()); 0307 Test::touchDown(0, decoPoint, timestamp++); 0308 Test::touchUp(0, timestamp++); 0309 QVERIFY(window->isOnAllDesktops()); 0310 } 0311 0312 void DecorationInputTest::testHover() 0313 { 0314 const auto [window, surface] = showWindow(); 0315 QVERIFY(window); 0316 QVERIFY(window->isDecorated()); 0317 QVERIFY(!window->noBorder()); 0318 0319 // our left border is moved out of the visible area, so move the window to a better place 0320 window->move(QPoint(20, 0)); 0321 0322 quint32 timestamp = 1; 0323 MOTION(QPoint(window->frameGeometry().center().x(), window->frameMargins().top() / 2.0)); 0324 QCOMPARE(window->cursor(), CursorShape(Qt::ArrowCursor)); 0325 0326 // There is a mismatch of the cursor key positions between windows 0327 // with and without borders (with borders one can move inside a bit and still 0328 // be on an edge, without not). We should make this consistent in KWin's core. 0329 // 0330 // TODO: Test input position with different border sizes. 0331 // TODO: We should test with the fake decoration to have a fixed test environment. 0332 const bool hasBorders = Workspace::self()->decorationBridge()->settings()->borderSize() != KDecoration2::BorderSize::None; 0333 auto deviation = [hasBorders] { 0334 return hasBorders ? -1 : 0; 0335 }; 0336 0337 MOTION(QPoint(window->frameGeometry().x(), 0)); 0338 QCOMPARE(window->cursor(), CursorShape(KWin::ExtendedCursor::SizeNorthWest)); 0339 MOTION(QPoint(window->frameGeometry().x() + window->frameGeometry().width() / 2, 0)); 0340 QCOMPARE(window->cursor(), CursorShape(KWin::ExtendedCursor::SizeNorth)); 0341 MOTION(QPoint(window->frameGeometry().x() + window->frameGeometry().width() - 1, 0)); 0342 QCOMPARE(window->cursor(), CursorShape(KWin::ExtendedCursor::SizeNorthEast)); 0343 MOTION(QPoint(window->frameGeometry().x() + window->frameGeometry().width() + deviation(), window->height() / 2)); 0344 QCOMPARE(window->cursor(), CursorShape(KWin::ExtendedCursor::SizeEast)); 0345 MOTION(QPoint(window->frameGeometry().x() + window->frameGeometry().width() + deviation(), window->height() - 1)); 0346 QCOMPARE(window->cursor(), CursorShape(KWin::ExtendedCursor::SizeSouthEast)); 0347 MOTION(QPoint(window->frameGeometry().x() + window->frameGeometry().width() / 2, window->height() + deviation())); 0348 QCOMPARE(window->cursor(), CursorShape(KWin::ExtendedCursor::SizeSouth)); 0349 MOTION(QPoint(window->frameGeometry().x(), window->height() + deviation())); 0350 QCOMPARE(window->cursor(), CursorShape(KWin::ExtendedCursor::SizeSouthWest)); 0351 MOTION(QPoint(window->frameGeometry().x() - 1, window->height() / 2)); 0352 QCOMPARE(window->cursor(), CursorShape(KWin::ExtendedCursor::SizeWest)); 0353 0354 MOTION(window->frameGeometry().center()); 0355 QEXPECT_FAIL("", "Cursor not set back on leave", Continue); 0356 QCOMPARE(window->cursor(), CursorShape(Qt::ArrowCursor)); 0357 } 0358 0359 void DecorationInputTest::testPressToMove_data() 0360 { 0361 QTest::addColumn<QPoint>("offset"); 0362 QTest::addColumn<QPoint>("offset2"); 0363 QTest::addColumn<QPoint>("offset3"); 0364 0365 QTest::newRow("To right") << QPoint(10, 0) << QPoint(20, 0) << QPoint(30, 0); 0366 QTest::newRow("To left") << QPoint(-10, 0) << QPoint(-20, 0) << QPoint(-30, 0); 0367 QTest::newRow("To bottom") << QPoint(0, 10) << QPoint(0, 20) << QPoint(0, 30); 0368 QTest::newRow("To top") << QPoint(0, -10) << QPoint(0, -20) << QPoint(0, -30); 0369 } 0370 0371 void DecorationInputTest::testPressToMove() 0372 { 0373 const auto [window, surface] = showWindow(); 0374 QVERIFY(window); 0375 QVERIFY(window->isDecorated()); 0376 QVERIFY(!window->noBorder()); 0377 window->move(workspace()->activeOutput()->geometry().center() - QPoint(window->width() / 2, window->height() / 2)); 0378 QSignalSpy startMoveResizedSpy(window, &Window::clientStartUserMovedResized); 0379 QSignalSpy clientFinishUserMovedResizedSpy(window, &Window::clientFinishUserMovedResized); 0380 0381 quint32 timestamp = 1; 0382 MOTION(QPoint(window->frameGeometry().center().x(), window->y() + window->frameMargins().top() / 2.0)); 0383 QCOMPARE(window->cursor(), CursorShape(Qt::ArrowCursor)); 0384 0385 PRESS; 0386 QVERIFY(!window->isInteractiveMove()); 0387 QFETCH(QPoint, offset); 0388 MOTION(QPoint(window->frameGeometry().center().x(), window->y() + window->frameMargins().top() / 2.0) + offset); 0389 const QPointF oldPos = window->pos(); 0390 QVERIFY(window->isInteractiveMove()); 0391 QCOMPARE(startMoveResizedSpy.count(), 1); 0392 0393 RELEASE; 0394 QTRY_VERIFY(!window->isInteractiveMove()); 0395 QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1); 0396 QEXPECT_FAIL("", "Just trigger move doesn't move the window", Continue); 0397 QCOMPARE(window->pos(), oldPos + offset); 0398 0399 // again 0400 PRESS; 0401 QVERIFY(!window->isInteractiveMove()); 0402 QFETCH(QPoint, offset2); 0403 MOTION(QPoint(window->frameGeometry().center().x(), window->y() + window->frameMargins().top() / 2.0) + offset2); 0404 QVERIFY(window->isInteractiveMove()); 0405 QCOMPARE(startMoveResizedSpy.count(), 2); 0406 QFETCH(QPoint, offset3); 0407 MOTION(QPoint(window->frameGeometry().center().x(), window->y() + window->frameMargins().top() / 2.0) + offset3); 0408 0409 RELEASE; 0410 QTRY_VERIFY(!window->isInteractiveMove()); 0411 QCOMPARE(clientFinishUserMovedResizedSpy.count(), 2); 0412 // TODO: the offset should also be included 0413 QCOMPARE(window->pos(), oldPos + offset2 + offset3); 0414 } 0415 0416 void DecorationInputTest::testTapToMove_data() 0417 { 0418 QTest::addColumn<QPoint>("offset"); 0419 QTest::addColumn<QPoint>("offset2"); 0420 QTest::addColumn<QPoint>("offset3"); 0421 0422 QTest::newRow("To right") << QPoint(10, 0) << QPoint(20, 0) << QPoint(30, 0); 0423 QTest::newRow("To left") << QPoint(-10, 0) << QPoint(-20, 0) << QPoint(-30, 0); 0424 QTest::newRow("To bottom") << QPoint(0, 10) << QPoint(0, 20) << QPoint(0, 30); 0425 QTest::newRow("To top") << QPoint(0, -10) << QPoint(0, -20) << QPoint(0, -30); 0426 } 0427 0428 void DecorationInputTest::testTapToMove() 0429 { 0430 const auto [window, surface] = showWindow(); 0431 QVERIFY(window); 0432 QVERIFY(window->isDecorated()); 0433 QVERIFY(!window->noBorder()); 0434 window->move(workspace()->activeOutput()->geometry().center() - QPoint(window->width() / 2, window->height() / 2)); 0435 QSignalSpy startMoveResizedSpy(window, &Window::clientStartUserMovedResized); 0436 QSignalSpy clientFinishUserMovedResizedSpy(window, &Window::clientFinishUserMovedResized); 0437 0438 quint32 timestamp = 1; 0439 QPoint p = QPoint(window->frameGeometry().center().x(), window->y() + window->frameMargins().top() / 2.0); 0440 0441 Test::touchDown(0, p, timestamp++); 0442 QVERIFY(!window->isInteractiveMove()); 0443 QFETCH(QPoint, offset); 0444 QCOMPARE(input()->touch()->decorationPressId(), 0); 0445 Test::touchMotion(0, p + offset, timestamp++); 0446 const QPointF oldPos = window->pos(); 0447 QVERIFY(window->isInteractiveMove()); 0448 QCOMPARE(startMoveResizedSpy.count(), 1); 0449 0450 Test::touchUp(0, timestamp++); 0451 QTRY_VERIFY(!window->isInteractiveMove()); 0452 QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1); 0453 QEXPECT_FAIL("", "Just trigger move doesn't move the window", Continue); 0454 QCOMPARE(window->pos(), oldPos + offset); 0455 0456 // again 0457 Test::touchDown(1, p + offset, timestamp++); 0458 QCOMPARE(input()->touch()->decorationPressId(), 1); 0459 QVERIFY(!window->isInteractiveMove()); 0460 QFETCH(QPoint, offset2); 0461 Test::touchMotion(1, QPoint(window->frameGeometry().center().x(), window->y() + window->frameMargins().top() / 2.0) + offset2, timestamp++); 0462 QVERIFY(window->isInteractiveMove()); 0463 QCOMPARE(startMoveResizedSpy.count(), 2); 0464 QFETCH(QPoint, offset3); 0465 Test::touchMotion(1, QPoint(window->frameGeometry().center().x(), window->y() + window->frameMargins().top() / 2.0) + offset3, timestamp++); 0466 0467 Test::touchUp(1, timestamp++); 0468 QTRY_VERIFY(!window->isInteractiveMove()); 0469 QCOMPARE(clientFinishUserMovedResizedSpy.count(), 2); 0470 // TODO: the offset should also be included 0471 QCOMPARE(window->pos(), oldPos + offset2 + offset3); 0472 } 0473 0474 void DecorationInputTest::testResizeOutsideWindow_data() 0475 { 0476 QTest::addColumn<Qt::Edge>("edge"); 0477 QTest::addColumn<Qt::CursorShape>("expectedCursor"); 0478 0479 QTest::newRow("left") << Qt::LeftEdge << Qt::SizeHorCursor; 0480 QTest::newRow("right") << Qt::RightEdge << Qt::SizeHorCursor; 0481 QTest::newRow("bottom") << Qt::BottomEdge << Qt::SizeVerCursor; 0482 } 0483 0484 void DecorationInputTest::testResizeOutsideWindow() 0485 { 0486 // this test verifies that one can resize the window outside the decoration with NoSideBorder 0487 0488 // first adjust config 0489 kwinApp()->config()->group("org.kde.kdecoration2").writeEntry("BorderSize", QStringLiteral("None")); 0490 kwinApp()->config()->sync(); 0491 workspace()->slotReconfigure(); 0492 0493 // now create window 0494 const auto [window, surface] = showWindow(); 0495 QVERIFY(window); 0496 QVERIFY(window->isDecorated()); 0497 QVERIFY(!window->noBorder()); 0498 window->move(workspace()->activeOutput()->geometry().center() - QPoint(window->width() / 2, window->height() / 2)); 0499 QVERIFY(window->frameGeometry() != window->inputGeometry()); 0500 QVERIFY(window->inputGeometry().contains(window->frameGeometry())); 0501 QSignalSpy startMoveResizedSpy(window, &Window::clientStartUserMovedResized); 0502 0503 // go to border 0504 quint32 timestamp = 1; 0505 QFETCH(Qt::Edge, edge); 0506 switch (edge) { 0507 case Qt::LeftEdge: 0508 MOTION(QPoint(window->frameGeometry().x() - 1, window->frameGeometry().center().y())); 0509 break; 0510 case Qt::RightEdge: 0511 MOTION(QPoint(window->frameGeometry().x() + window->frameGeometry().width() + 1, window->frameGeometry().center().y())); 0512 break; 0513 case Qt::BottomEdge: 0514 MOTION(QPoint(window->frameGeometry().center().x(), window->frameGeometry().y() + window->frameGeometry().height() + 1)); 0515 break; 0516 default: 0517 break; 0518 } 0519 QVERIFY(!window->frameGeometry().contains(KWin::Cursors::self()->mouse()->pos())); 0520 0521 // pressing should trigger resize 0522 PRESS; 0523 QVERIFY(!window->isInteractiveResize()); 0524 QVERIFY(startMoveResizedSpy.wait()); 0525 QVERIFY(window->isInteractiveResize()); 0526 0527 RELEASE; 0528 QVERIFY(!window->isInteractiveResize()); 0529 } 0530 0531 void DecorationInputTest::testModifierClickUnrestrictedMove_data() 0532 { 0533 QTest::addColumn<int>("modifierKey"); 0534 QTest::addColumn<int>("mouseButton"); 0535 QTest::addColumn<QString>("modKey"); 0536 QTest::addColumn<bool>("capsLock"); 0537 0538 const QString alt = QStringLiteral("Alt"); 0539 const QString meta = QStringLiteral("Meta"); 0540 0541 QTest::newRow("Left Alt + Left Click") << KEY_LEFTALT << BTN_LEFT << alt << false; 0542 QTest::newRow("Left Alt + Right Click") << KEY_LEFTALT << BTN_RIGHT << alt << false; 0543 QTest::newRow("Left Alt + Middle Click") << KEY_LEFTALT << BTN_MIDDLE << alt << false; 0544 QTest::newRow("Right Alt + Left Click") << KEY_RIGHTALT << BTN_LEFT << alt << false; 0545 QTest::newRow("Right Alt + Right Click") << KEY_RIGHTALT << BTN_RIGHT << alt << false; 0546 QTest::newRow("Right Alt + Middle Click") << KEY_RIGHTALT << BTN_MIDDLE << alt << false; 0547 // now everything with meta 0548 QTest::newRow("Left Meta + Left Click") << KEY_LEFTMETA << BTN_LEFT << meta << false; 0549 QTest::newRow("Left Meta + Right Click") << KEY_LEFTMETA << BTN_RIGHT << meta << false; 0550 QTest::newRow("Left Meta + Middle Click") << KEY_LEFTMETA << BTN_MIDDLE << meta << false; 0551 QTest::newRow("Right Meta + Left Click") << KEY_RIGHTMETA << BTN_LEFT << meta << false; 0552 QTest::newRow("Right Meta + Right Click") << KEY_RIGHTMETA << BTN_RIGHT << meta << false; 0553 QTest::newRow("Right Meta + Middle Click") << KEY_RIGHTMETA << BTN_MIDDLE << meta << false; 0554 0555 // and with capslock 0556 QTest::newRow("Left Alt + Left Click/CapsLock") << KEY_LEFTALT << BTN_LEFT << alt << true; 0557 QTest::newRow("Left Alt + Right Click/CapsLock") << KEY_LEFTALT << BTN_RIGHT << alt << true; 0558 QTest::newRow("Left Alt + Middle Click/CapsLock") << KEY_LEFTALT << BTN_MIDDLE << alt << true; 0559 QTest::newRow("Right Alt + Left Click/CapsLock") << KEY_RIGHTALT << BTN_LEFT << alt << true; 0560 QTest::newRow("Right Alt + Right Click/CapsLock") << KEY_RIGHTALT << BTN_RIGHT << alt << true; 0561 QTest::newRow("Right Alt + Middle Click/CapsLock") << KEY_RIGHTALT << BTN_MIDDLE << alt << true; 0562 // now everything with meta 0563 QTest::newRow("Left Meta + Left Click/CapsLock") << KEY_LEFTMETA << BTN_LEFT << meta << true; 0564 QTest::newRow("Left Meta + Right Click/CapsLock") << KEY_LEFTMETA << BTN_RIGHT << meta << true; 0565 QTest::newRow("Left Meta + Middle Click/CapsLock") << KEY_LEFTMETA << BTN_MIDDLE << meta << true; 0566 QTest::newRow("Right Meta + Left Click/CapsLock") << KEY_RIGHTMETA << BTN_LEFT << meta << true; 0567 QTest::newRow("Right Meta + Right Click/CapsLock") << KEY_RIGHTMETA << BTN_RIGHT << meta << true; 0568 QTest::newRow("Right Meta + Middle Click/CapsLock") << KEY_RIGHTMETA << BTN_MIDDLE << meta << true; 0569 } 0570 0571 void DecorationInputTest::testModifierClickUnrestrictedMove() 0572 { 0573 // this test ensures that Alt+mouse button press triggers unrestricted move 0574 0575 // first modify the config for this run 0576 QFETCH(QString, modKey); 0577 KConfigGroup group = kwinApp()->config()->group("MouseBindings"); 0578 group.writeEntry("CommandAllKey", modKey); 0579 group.writeEntry("CommandAll1", "Move"); 0580 group.writeEntry("CommandAll2", "Move"); 0581 group.writeEntry("CommandAll3", "Move"); 0582 group.sync(); 0583 workspace()->slotReconfigure(); 0584 QCOMPARE(options->commandAllModifier(), modKey == QStringLiteral("Alt") ? Qt::AltModifier : Qt::MetaModifier); 0585 QCOMPARE(options->commandAll1(), Options::MouseUnrestrictedMove); 0586 QCOMPARE(options->commandAll2(), Options::MouseUnrestrictedMove); 0587 QCOMPARE(options->commandAll3(), Options::MouseUnrestrictedMove); 0588 0589 // create a window 0590 const auto [window, surface] = showWindow(); 0591 QVERIFY(window); 0592 QVERIFY(window->isDecorated()); 0593 QVERIFY(!window->noBorder()); 0594 window->move(workspace()->activeOutput()->geometry().center() - QPoint(window->width() / 2, window->height() / 2)); 0595 // move cursor on window 0596 Cursors::self()->mouse()->setPos(QPoint(window->frameGeometry().center().x(), window->y() + window->frameMargins().top() / 2.0)); 0597 0598 // simulate modifier+click 0599 quint32 timestamp = 1; 0600 QFETCH(bool, capsLock); 0601 if (capsLock) { 0602 Test::keyboardKeyPressed(KEY_CAPSLOCK, timestamp++); 0603 } 0604 QFETCH(int, modifierKey); 0605 QFETCH(int, mouseButton); 0606 Test::keyboardKeyPressed(modifierKey, timestamp++); 0607 QVERIFY(!window->isInteractiveMove()); 0608 Test::pointerButtonPressed(mouseButton, timestamp++); 0609 QVERIFY(window->isInteractiveMove()); 0610 // release modifier should not change it 0611 Test::keyboardKeyReleased(modifierKey, timestamp++); 0612 QVERIFY(window->isInteractiveMove()); 0613 // but releasing the key should end move/resize 0614 Test::pointerButtonReleased(mouseButton, timestamp++); 0615 QVERIFY(!window->isInteractiveMove()); 0616 if (capsLock) { 0617 Test::keyboardKeyReleased(KEY_CAPSLOCK, timestamp++); 0618 } 0619 } 0620 0621 void DecorationInputTest::testModifierScrollOpacity_data() 0622 { 0623 QTest::addColumn<int>("modifierKey"); 0624 QTest::addColumn<QString>("modKey"); 0625 QTest::addColumn<bool>("capsLock"); 0626 0627 const QString alt = QStringLiteral("Alt"); 0628 const QString meta = QStringLiteral("Meta"); 0629 0630 QTest::newRow("Left Alt") << KEY_LEFTALT << alt << false; 0631 QTest::newRow("Right Alt") << KEY_RIGHTALT << alt << false; 0632 QTest::newRow("Left Meta") << KEY_LEFTMETA << meta << false; 0633 QTest::newRow("Right Meta") << KEY_RIGHTMETA << meta << false; 0634 QTest::newRow("Left Alt/CapsLock") << KEY_LEFTALT << alt << true; 0635 QTest::newRow("Right Alt/CapsLock") << KEY_RIGHTALT << alt << true; 0636 QTest::newRow("Left Meta/CapsLock") << KEY_LEFTMETA << meta << true; 0637 QTest::newRow("Right Meta/CapsLock") << KEY_RIGHTMETA << meta << true; 0638 } 0639 0640 void DecorationInputTest::testModifierScrollOpacity() 0641 { 0642 // this test verifies that mod+wheel performs a window operation 0643 0644 // first modify the config for this run 0645 QFETCH(QString, modKey); 0646 KConfigGroup group = kwinApp()->config()->group("MouseBindings"); 0647 group.writeEntry("CommandAllKey", modKey); 0648 group.writeEntry("CommandAllWheel", "change opacity"); 0649 group.sync(); 0650 workspace()->slotReconfigure(); 0651 0652 const auto [window, surface] = showWindow(); 0653 QVERIFY(window); 0654 QVERIFY(window->isDecorated()); 0655 QVERIFY(!window->noBorder()); 0656 window->move(workspace()->activeOutput()->geometry().center() - QPoint(window->width() / 2, window->height() / 2)); 0657 // move cursor on window 0658 Cursors::self()->mouse()->setPos(QPoint(window->frameGeometry().center().x(), window->y() + window->frameMargins().top() / 2.0)); 0659 // set the opacity to 0.5 0660 window->setOpacity(0.5); 0661 QCOMPARE(window->opacity(), 0.5); 0662 0663 // simulate modifier+wheel 0664 quint32 timestamp = 1; 0665 QFETCH(bool, capsLock); 0666 if (capsLock) { 0667 Test::keyboardKeyPressed(KEY_CAPSLOCK, timestamp++); 0668 } 0669 QFETCH(int, modifierKey); 0670 Test::keyboardKeyPressed(modifierKey, timestamp++); 0671 Test::pointerAxisVertical(-5, timestamp++); 0672 QCOMPARE(window->opacity(), 0.6); 0673 Test::pointerAxisVertical(5, timestamp++); 0674 QCOMPARE(window->opacity(), 0.5); 0675 Test::keyboardKeyReleased(modifierKey, timestamp++); 0676 if (capsLock) { 0677 Test::keyboardKeyReleased(KEY_CAPSLOCK, timestamp++); 0678 } 0679 } 0680 0681 class EventHelper : public QObject 0682 { 0683 Q_OBJECT 0684 public: 0685 EventHelper() 0686 : QObject() 0687 { 0688 } 0689 ~EventHelper() override = default; 0690 0691 bool eventFilter(QObject *watched, QEvent *event) override 0692 { 0693 if (event->type() == QEvent::HoverMove) { 0694 Q_EMIT hoverMove(); 0695 } else if (event->type() == QEvent::HoverLeave) { 0696 Q_EMIT hoverLeave(); 0697 } 0698 return false; 0699 } 0700 0701 Q_SIGNALS: 0702 void hoverMove(); 0703 void hoverLeave(); 0704 }; 0705 0706 void DecorationInputTest::testTouchEvents() 0707 { 0708 // this test verifies that the decoration gets a hover leave event on touch release 0709 // see BUG 386231 0710 const auto [window, surface] = showWindow(); 0711 QVERIFY(window); 0712 QVERIFY(window->isDecorated()); 0713 QVERIFY(!window->noBorder()); 0714 0715 EventHelper helper; 0716 window->decoration()->installEventFilter(&helper); 0717 QSignalSpy hoverMoveSpy(&helper, &EventHelper::hoverMove); 0718 QSignalSpy hoverLeaveSpy(&helper, &EventHelper::hoverLeave); 0719 0720 quint32 timestamp = 1; 0721 const QPoint tapPoint(window->frameGeometry().center().x(), window->frameMargins().top() / 2.0); 0722 0723 QVERIFY(!input()->touch()->decoration()); 0724 Test::touchDown(0, tapPoint, timestamp++); 0725 QVERIFY(input()->touch()->decoration()); 0726 QCOMPARE(input()->touch()->decoration()->decoration(), window->decoration()); 0727 QCOMPARE(hoverMoveSpy.count(), 1); 0728 QCOMPARE(hoverLeaveSpy.count(), 0); 0729 Test::touchUp(0, timestamp++); 0730 QCOMPARE(hoverMoveSpy.count(), 1); 0731 QCOMPARE(hoverLeaveSpy.count(), 1); 0732 0733 QCOMPARE(window->isInteractiveMove(), false); 0734 0735 // let's check that a hover motion is sent if the pointer is on deco, when touch release 0736 Cursors::self()->mouse()->setPos(tapPoint); 0737 QCOMPARE(hoverMoveSpy.count(), 2); 0738 Test::touchDown(0, tapPoint, timestamp++); 0739 QCOMPARE(hoverMoveSpy.count(), 3); 0740 QCOMPARE(hoverLeaveSpy.count(), 1); 0741 Test::touchUp(0, timestamp++); 0742 QCOMPARE(hoverMoveSpy.count(), 3); 0743 QCOMPARE(hoverLeaveSpy.count(), 2); 0744 } 0745 0746 void DecorationInputTest::testTooltipDoesntEatKeyEvents() 0747 { 0748 // this test verifies that a tooltip on the decoration does not steal key events 0749 // BUG: 393253 0750 0751 // first create a keyboard 0752 auto keyboard = Test::waylandSeat()->createKeyboard(Test::waylandSeat()); 0753 QVERIFY(keyboard); 0754 QSignalSpy enteredSpy(keyboard, &KWayland::Client::Keyboard::entered); 0755 0756 const auto [window, surface] = showWindow(); 0757 QVERIFY(window); 0758 QVERIFY(window->isDecorated()); 0759 QVERIFY(!window->noBorder()); 0760 QVERIFY(enteredSpy.wait()); 0761 0762 QSignalSpy keyEvent(keyboard, &KWayland::Client::Keyboard::keyChanged); 0763 QVERIFY(keyEvent.isValid()); 0764 0765 QSignalSpy windowAddedSpy(workspace(), &Workspace::internalWindowAdded); 0766 window->decoratedClient()->requestShowToolTip(QStringLiteral("test")); 0767 // now we should get an internal window 0768 QVERIFY(windowAddedSpy.wait()); 0769 InternalWindow *internal = windowAddedSpy.first().first().value<InternalWindow *>(); 0770 QVERIFY(internal->isInternal()); 0771 QVERIFY(internal->handle()->flags().testFlag(Qt::ToolTip)); 0772 0773 // now send a key 0774 quint32 timestamp = 0; 0775 Test::keyboardKeyPressed(KEY_A, timestamp++); 0776 QVERIFY(keyEvent.wait()); 0777 Test::keyboardKeyReleased(KEY_A, timestamp++); 0778 QVERIFY(keyEvent.wait()); 0779 0780 window->decoratedClient()->requestHideToolTip(); 0781 Test::waitForWindowDestroyed(internal); 0782 } 0783 0784 } 0785 0786 WAYLANDTEST_MAIN(KWin::DecorationInputTest) 0787 #include "decoration_input_test.moc"