File indexing completed on 2024-05-05 17:36:02

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 "touch_input.h"
0015 #include "wayland_server.h"
0016 #include "window.h"
0017 #include "workspace.h"
0018 
0019 #include <KWayland/Client/compositor.h>
0020 #include <KWayland/Client/connection_thread.h>
0021 #include <KWayland/Client/seat.h>
0022 #include <KWayland/Client/surface.h>
0023 #include <KWayland/Client/touch.h>
0024 
0025 #include <QAction>
0026 
0027 namespace KWin
0028 {
0029 
0030 static const QString s_socketName = QStringLiteral("wayland_test_kwin_touch_input-0");
0031 
0032 class TouchInputTest : public QObject
0033 {
0034     Q_OBJECT
0035 private Q_SLOTS:
0036     void initTestCase();
0037     void init();
0038     void cleanup();
0039     void testTouchHidesCursor();
0040     void testMultipleTouchPoints_data();
0041     void testMultipleTouchPoints();
0042     void testCancel();
0043     void testTouchMouseAction();
0044     void testTouchPointCount();
0045     void testUpdateFocusOnDecorationDestroy();
0046     void testGestureDetection();
0047 
0048 private:
0049     std::pair<Window *, std::unique_ptr<KWayland::Client::Surface>> showWindow(bool decorated = false);
0050     KWayland::Client::Touch *m_touch = nullptr;
0051 };
0052 
0053 void TouchInputTest::initTestCase()
0054 {
0055     qRegisterMetaType<KWin::Window *>();
0056     QSignalSpy applicationStartedSpy(kwinApp(), &Application::started);
0057     QVERIFY(waylandServer()->init(s_socketName));
0058     QMetaObject::invokeMethod(kwinApp()->outputBackend(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(QVector<QRect>, QVector<QRect>() << QRect(0, 0, 1280, 1024) << QRect(1280, 0, 1280, 1024)));
0059 
0060     kwinApp()->start();
0061     QVERIFY(applicationStartedSpy.wait());
0062     const auto outputs = workspace()->outputs();
0063     QCOMPARE(outputs.count(), 2);
0064     QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024));
0065     QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024));
0066 }
0067 
0068 void TouchInputTest::init()
0069 {
0070     QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Seat | Test::AdditionalWaylandInterface::XdgDecorationV1));
0071     QVERIFY(Test::waitForWaylandTouch());
0072     m_touch = Test::waylandSeat()->createTouch(Test::waylandSeat());
0073     QVERIFY(m_touch);
0074     QVERIFY(m_touch->isValid());
0075 
0076     workspace()->setActiveOutput(QPoint(640, 512));
0077     Cursors::self()->mouse()->setPos(QPoint(640, 512));
0078 }
0079 
0080 void TouchInputTest::cleanup()
0081 {
0082     delete m_touch;
0083     m_touch = nullptr;
0084     Test::destroyWaylandConnection();
0085 }
0086 
0087 std::pair<Window *, std::unique_ptr<KWayland::Client::Surface>> TouchInputTest::showWindow(bool decorated)
0088 {
0089 #define VERIFY(statement)                                                 \
0090     if (!QTest::qVerify((statement), #statement, "", __FILE__, __LINE__)) \
0091         return {nullptr, nullptr};
0092 #define COMPARE(actual, expected)                                                   \
0093     if (!QTest::qCompare(actual, expected, #actual, #expected, __FILE__, __LINE__)) \
0094         return {nullptr, nullptr};
0095 
0096     std::unique_ptr<KWayland::Client::Surface> surface = Test::createSurface();
0097     VERIFY(surface.get());
0098     Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly, surface.get());
0099     VERIFY(shellSurface);
0100     if (decorated) {
0101         auto decoration = Test::createXdgToplevelDecorationV1(shellSurface, shellSurface);
0102         decoration->set_mode(Test::XdgToplevelDecorationV1::mode_server_side);
0103     }
0104     QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested);
0105     surface->commit(KWayland::Client::Surface::CommitFlag::None);
0106     VERIFY(surfaceConfigureRequestedSpy.wait());
0107     // let's render
0108     shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
0109     auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
0110 
0111     VERIFY(window);
0112     COMPARE(workspace()->activeWindow(), window);
0113 
0114 #undef VERIFY
0115 #undef COMPARE
0116 
0117     return {window, std::move(surface)};
0118 }
0119 
0120 void TouchInputTest::testTouchHidesCursor()
0121 {
0122     QCOMPARE(Cursors::self()->isCursorHidden(), false);
0123     quint32 timestamp = 1;
0124     Test::touchDown(1, QPointF(125, 125), timestamp++);
0125     QCOMPARE(Cursors::self()->isCursorHidden(), true);
0126     Test::touchDown(2, QPointF(130, 125), timestamp++);
0127     Test::touchUp(2, timestamp++);
0128     Test::touchUp(1, timestamp++);
0129 
0130     // now a mouse event should show the cursor again
0131     Test::pointerMotion(QPointF(0, 0), timestamp++);
0132     QCOMPARE(Cursors::self()->isCursorHidden(), false);
0133 
0134     // touch should hide again
0135     Test::touchDown(1, QPointF(125, 125), timestamp++);
0136     Test::touchUp(1, timestamp++);
0137     QCOMPARE(Cursors::self()->isCursorHidden(), true);
0138 
0139     // wheel should also show
0140     Test::pointerAxisVertical(1.0, timestamp++);
0141     QCOMPARE(Cursors::self()->isCursorHidden(), false);
0142 }
0143 
0144 void TouchInputTest::testMultipleTouchPoints_data()
0145 {
0146     QTest::addColumn<bool>("decorated");
0147 
0148     QTest::newRow("undecorated") << false;
0149     QTest::newRow("decorated") << true;
0150 }
0151 
0152 void TouchInputTest::testMultipleTouchPoints()
0153 {
0154     QFETCH(bool, decorated);
0155     auto [window, surface] = showWindow(decorated);
0156     QCOMPARE(window->isDecorated(), decorated);
0157     window->move(QPoint(100, 100));
0158     QVERIFY(window);
0159     QSignalSpy sequenceStartedSpy(m_touch, &KWayland::Client::Touch::sequenceStarted);
0160     QSignalSpy pointAddedSpy(m_touch, &KWayland::Client::Touch::pointAdded);
0161     QSignalSpy pointMovedSpy(m_touch, &KWayland::Client::Touch::pointMoved);
0162     QSignalSpy pointRemovedSpy(m_touch, &KWayland::Client::Touch::pointRemoved);
0163     QSignalSpy endedSpy(m_touch, &KWayland::Client::Touch::sequenceEnded);
0164 
0165     quint32 timestamp = 1;
0166     Test::touchDown(1, window->mapFromLocal(QPointF(25, 25)), timestamp++);
0167     QVERIFY(sequenceStartedSpy.wait());
0168     QCOMPARE(sequenceStartedSpy.count(), 1);
0169     QCOMPARE(m_touch->sequence().count(), 1);
0170     QCOMPARE(m_touch->sequence().first()->isDown(), true);
0171     QCOMPARE(m_touch->sequence().first()->position(), QPointF(25, 25));
0172     QCOMPARE(pointAddedSpy.count(), 0);
0173     QCOMPARE(pointMovedSpy.count(), 0);
0174 
0175     // a point outside the window
0176     Test::touchDown(2, window->mapFromLocal(QPointF(-100, -100)), timestamp++);
0177     QVERIFY(pointAddedSpy.wait());
0178     QCOMPARE(pointAddedSpy.count(), 1);
0179     QCOMPARE(m_touch->sequence().count(), 2);
0180     QCOMPARE(m_touch->sequence().at(1)->isDown(), true);
0181     QCOMPARE(m_touch->sequence().at(1)->position(), QPointF(-100, -100));
0182     QCOMPARE(pointMovedSpy.count(), 0);
0183 
0184     // let's move that one
0185     Test::touchMotion(2, window->mapFromLocal(QPointF(0, 0)), timestamp++);
0186     QVERIFY(pointMovedSpy.wait());
0187     QCOMPARE(pointMovedSpy.count(), 1);
0188     QCOMPARE(m_touch->sequence().count(), 2);
0189     QCOMPARE(m_touch->sequence().at(1)->isDown(), true);
0190     QCOMPARE(m_touch->sequence().at(1)->position(), QPointF(0, 0));
0191 
0192     Test::touchUp(1, timestamp++);
0193     QVERIFY(pointRemovedSpy.wait());
0194     QCOMPARE(pointRemovedSpy.count(), 1);
0195     QCOMPARE(m_touch->sequence().count(), 2);
0196     QCOMPARE(m_touch->sequence().first()->isDown(), false);
0197     QCOMPARE(endedSpy.count(), 0);
0198 
0199     Test::touchUp(2, timestamp++);
0200     QVERIFY(pointRemovedSpy.wait());
0201     QCOMPARE(pointRemovedSpy.count(), 2);
0202     QCOMPARE(m_touch->sequence().count(), 2);
0203     QCOMPARE(m_touch->sequence().first()->isDown(), false);
0204     QCOMPARE(m_touch->sequence().at(1)->isDown(), false);
0205     QCOMPARE(endedSpy.count(), 1);
0206 }
0207 
0208 void TouchInputTest::testCancel()
0209 {
0210     auto [window, surface] = showWindow();
0211     window->move(QPoint(100, 100));
0212     QVERIFY(window);
0213     QSignalSpy sequenceStartedSpy(m_touch, &KWayland::Client::Touch::sequenceStarted);
0214     QSignalSpy cancelSpy(m_touch, &KWayland::Client::Touch::sequenceCanceled);
0215     QSignalSpy pointRemovedSpy(m_touch, &KWayland::Client::Touch::pointRemoved);
0216 
0217     quint32 timestamp = 1;
0218     Test::touchDown(1, QPointF(125, 125), timestamp++);
0219     QVERIFY(sequenceStartedSpy.wait());
0220     QCOMPARE(sequenceStartedSpy.count(), 1);
0221 
0222     // cancel
0223     Test::touchCancel();
0224     QVERIFY(cancelSpy.wait());
0225     QCOMPARE(cancelSpy.count(), 1);
0226 }
0227 
0228 void TouchInputTest::testTouchMouseAction()
0229 {
0230     // this test verifies that a touch down on an inactive window will activate it
0231 
0232     // create two windows
0233     auto [c1, surface] = showWindow();
0234     QVERIFY(c1);
0235     auto [c2, surface2] = showWindow();
0236     QVERIFY(c2);
0237 
0238     QVERIFY(!c1->isActive());
0239     QVERIFY(c2->isActive());
0240 
0241     // also create a sequence started spy as the touch event should be passed through
0242     QSignalSpy sequenceStartedSpy(m_touch, &KWayland::Client::Touch::sequenceStarted);
0243 
0244     quint32 timestamp = 1;
0245     Test::touchDown(1, c1->frameGeometry().center(), timestamp++);
0246     QVERIFY(c1->isActive());
0247 
0248     QVERIFY(sequenceStartedSpy.wait());
0249     QCOMPARE(sequenceStartedSpy.count(), 1);
0250 
0251     // cleanup
0252     input()->touch()->cancel();
0253 }
0254 
0255 void TouchInputTest::testTouchPointCount()
0256 {
0257     QCOMPARE(input()->touch()->touchPointCount(), 0);
0258     quint32 timestamp = 1;
0259     Test::touchDown(0, QPointF(125, 125), timestamp++);
0260     Test::touchDown(1, QPointF(125, 125), timestamp++);
0261     Test::touchDown(2, QPointF(125, 125), timestamp++);
0262     QCOMPARE(input()->touch()->touchPointCount(), 3);
0263 
0264     Test::touchUp(1, timestamp++);
0265     QCOMPARE(input()->touch()->touchPointCount(), 2);
0266 
0267     input()->touch()->cancel();
0268     QCOMPARE(input()->touch()->touchPointCount(), 0);
0269 }
0270 
0271 void TouchInputTest::testUpdateFocusOnDecorationDestroy()
0272 {
0273     // This test verifies that a maximized window gets it's touch focus
0274     // if decoration was focused and then destroyed on maximize with BorderlessMaximizedWindows option.
0275 
0276     QSignalSpy sequenceEndedSpy(m_touch, &KWayland::Client::Touch::sequenceEnded);
0277 
0278     // Enable the borderless maximized windows option.
0279     auto group = kwinApp()->config()->group("Windows");
0280     group.writeEntry("BorderlessMaximizedWindows", true);
0281     group.sync();
0282     Workspace::self()->slotReconfigure();
0283     QCOMPARE(options->borderlessMaximizedWindows(), true);
0284 
0285     // Create the test window.
0286     std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
0287     std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly));
0288     std::unique_ptr<Test::XdgToplevelDecorationV1> decoration(Test::createXdgToplevelDecorationV1(shellSurface.get()));
0289 
0290     QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested);
0291     QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested);
0292     QSignalSpy decorationConfigureRequestedSpy(decoration.get(), &Test::XdgToplevelDecorationV1::configureRequested);
0293     decoration->set_mode(Test::XdgToplevelDecorationV1::mode_server_side);
0294     surface->commit(KWayland::Client::Surface::CommitFlag::None);
0295 
0296     // Wait for the initial configure event.
0297     Test::XdgToplevel::States states;
0298     QVERIFY(surfaceConfigureRequestedSpy.wait());
0299     QCOMPARE(surfaceConfigureRequestedSpy.count(), 1);
0300     QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(0, 0));
0301     states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>();
0302     QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated));
0303     QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized));
0304 
0305     // Map the window.
0306     shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
0307     Window *window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
0308     QVERIFY(window);
0309     QVERIFY(window->isActive());
0310     QCOMPARE(window->maximizeMode(), MaximizeMode::MaximizeRestore);
0311     QCOMPARE(window->requestedMaximizeMode(), MaximizeMode::MaximizeRestore);
0312     QCOMPARE(window->isDecorated(), true);
0313 
0314     // We should receive a configure event when the window becomes active.
0315     QVERIFY(surfaceConfigureRequestedSpy.wait());
0316     QCOMPARE(surfaceConfigureRequestedSpy.count(), 2);
0317     states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>();
0318     QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated));
0319     QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized));
0320 
0321     // Simulate decoration hover
0322     quint32 timestamp = 0;
0323     Test::touchDown(1, window->frameGeometry().topLeft(), timestamp++);
0324     QVERIFY(input()->touch()->decoration());
0325 
0326     // Maximize when on decoration
0327     workspace()->slotWindowMaximize();
0328     QVERIFY(surfaceConfigureRequestedSpy.wait());
0329     QCOMPARE(surfaceConfigureRequestedSpy.count(), 3);
0330     QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(1280, 1024));
0331     states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>();
0332     QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated));
0333     QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized));
0334 
0335     QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged);
0336     shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
0337     Test::render(surface.get(), QSize(1280, 1024), Qt::blue);
0338     QVERIFY(frameGeometryChangedSpy.wait());
0339     QCOMPARE(window->frameGeometry(), QRect(0, 0, 1280, 1024));
0340     QCOMPARE(window->maximizeMode(), MaximizeFull);
0341     QCOMPARE(window->requestedMaximizeMode(), MaximizeFull);
0342     QCOMPARE(window->isDecorated(), false);
0343 
0344     // Window should have focus
0345     QVERIFY(!input()->touch()->decoration());
0346     Test::touchUp(1, timestamp++);
0347     QVERIFY(!sequenceEndedSpy.wait(100));
0348     Test::touchDown(2, window->frameGeometry().center(), timestamp++);
0349     Test::touchUp(2, timestamp++);
0350     QVERIFY(sequenceEndedSpy.wait());
0351 
0352     // Destroy the window.
0353     shellSurface.reset();
0354     QVERIFY(Test::waitForWindowDestroyed(window));
0355 }
0356 
0357 void TouchInputTest::testGestureDetection()
0358 {
0359     bool callbackTriggered = false;
0360     const auto callback = [&callbackTriggered](float progress) {
0361         callbackTriggered = true;
0362         qWarning() << "progress callback!" << progress;
0363     };
0364     QAction action;
0365     input()->forceRegisterTouchscreenSwipeShortcut(SwipeDirection::Right, 3, &action, callback);
0366 
0367     // verify that gestures are detected
0368 
0369     quint32 timestamp = 1;
0370     Test::touchDown(0, QPointF(500, 125), timestamp++);
0371     Test::touchDown(1, QPointF(500, 125), timestamp++);
0372     Test::touchDown(2, QPointF(500, 125), timestamp++);
0373 
0374     Test::touchMotion(0, QPointF(100, 125), timestamp++);
0375     QVERIFY(callbackTriggered);
0376 
0377     // verify that gestures are canceled properly
0378     QSignalSpy gestureCancelled(&action, &QAction::triggered);
0379     Test::touchUp(0, timestamp++);
0380     QVERIFY(gestureCancelled.wait());
0381 
0382     Test::touchUp(1, timestamp++);
0383     Test::touchUp(2, timestamp++);
0384 
0385     callbackTriggered = false;
0386 
0387     // verify that touch points too far apart don't trigger a gesture
0388     Test::touchDown(0, QPointF(125, 125), timestamp++);
0389     Test::touchDown(1, QPointF(10000, 125), timestamp++);
0390     Test::touchDown(2, QPointF(125, 125), timestamp++);
0391     QVERIFY(!callbackTriggered);
0392 
0393     Test::touchUp(0, timestamp++);
0394     Test::touchUp(1, timestamp++);
0395     Test::touchUp(2, timestamp++);
0396 
0397     // verify that touch points triggered too slow don't trigger a gesture
0398     Test::touchDown(0, QPointF(125, 125), timestamp++);
0399     timestamp += 1000;
0400     Test::touchDown(1, QPointF(125, 125), timestamp++);
0401     Test::touchDown(2, QPointF(125, 125), timestamp++);
0402     QVERIFY(!callbackTriggered);
0403 
0404     Test::touchUp(0, timestamp++);
0405     Test::touchUp(1, timestamp++);
0406     Test::touchUp(2, timestamp++);
0407 
0408     // verify that after a gesture has been canceled but never initiated, gestures still work
0409     Test::touchDown(0, QPointF(500, 125), timestamp++);
0410     Test::touchDown(1, QPointF(500, 125), timestamp++);
0411     Test::touchDown(2, QPointF(500, 125), timestamp++);
0412 
0413     Test::touchMotion(0, QPointF(100, 125), timestamp++);
0414     Test::touchMotion(1, QPointF(100, 125), timestamp++);
0415     Test::touchMotion(2, QPointF(100, 125), timestamp++);
0416     QVERIFY(callbackTriggered);
0417 
0418     Test::touchUp(0, timestamp++);
0419     Test::touchUp(1, timestamp++);
0420     Test::touchUp(2, timestamp++);
0421 }
0422 }
0423 
0424 WAYLANDTEST_MAIN(KWin::TouchInputTest)
0425 #include "touch_input_test.moc"