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"