File indexing completed on 2024-05-05 17:35:45

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"