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