File indexing completed on 2024-05-12 05:30:44

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