diff --git a/autotests/integration/window_selection_test.cpp b/autotests/integration/window_selection_test.cpp --- a/autotests/integration/window_selection_test.cpp +++ b/autotests/integration/window_selection_test.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include @@ -53,10 +54,12 @@ void testSelectOnWindowPointer(); void testSelectOnWindowKeyboard_data(); void testSelectOnWindowKeyboard(); + void testSelectOnWindowTouch(); void testCancelOnWindowPointer(); void testCancelOnWindowKeyboard(); void testSelectPointPointer(); + void testSelectPointTouch(); }; void TestWindowSelection::initTestCase() @@ -249,6 +252,69 @@ kwinApp()->platform()->keyboardKeyReleased(key, timestamp++); } +void TestWindowSelection::testSelectOnWindowTouch() +{ + // this test verifies window selection through touch + QScopedPointer touch(Test::waylandSeat()->createTouch()); + QSignalSpy touchStartedSpy(touch.data(), &Touch::sequenceStarted); + QVERIFY(touchStartedSpy.isValid()); + QSignalSpy touchCanceledSpy(touch.data(), &Touch::sequenceCanceled); + QVERIFY(touchCanceledSpy.isValid()); + QScopedPointer surface(Test::createSurface()); + QScopedPointer shellSurface(Test::createShellSurface(surface.data())); + auto client = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); + QVERIFY(client); + + Toplevel *selectedWindow = nullptr; + auto callback = [&selectedWindow] (Toplevel *t) { + selectedWindow = t; + }; + + // start the interaction + QCOMPARE(input()->isSelectingWindow(), false); + kwinApp()->platform()->startInteractiveWindowSelection(callback); + QCOMPARE(input()->isSelectingWindow(), true); + QVERIFY(!selectedWindow); + + // simulate touch down + quint32 timestamp = 0; + kwinApp()->platform()->touchDown(0, client->geometry().center(), timestamp++); + QVERIFY(!selectedWindow); + kwinApp()->platform()->touchUp(0, timestamp++); + QCOMPARE(input()->isSelectingWindow(), false); + QCOMPARE(selectedWindow, client); + + // with movement + selectedWindow = nullptr; + kwinApp()->platform()->startInteractiveWindowSelection(callback); + kwinApp()->platform()->touchDown(0, client->geometry().bottomRight() + QPoint(20, 20), timestamp++); + QVERIFY(!selectedWindow); + kwinApp()->platform()->touchMotion(0, client->geometry().bottomRight() - QPoint(1, 1), timestamp++); + QVERIFY(!selectedWindow); + kwinApp()->platform()->touchUp(0, timestamp++); + QCOMPARE(selectedWindow, client); + QCOMPARE(input()->isSelectingWindow(), false); + + // it cancels active touch sequence on the window + kwinApp()->platform()->touchDown(0, client->geometry().center(), timestamp++); + QVERIFY(touchStartedSpy.wait()); + selectedWindow = nullptr; + kwinApp()->platform()->startInteractiveWindowSelection(callback); + QCOMPARE(input()->isSelectingWindow(), true); + QVERIFY(touchCanceledSpy.wait()); + QVERIFY(!selectedWindow); + // this touch up does not yet select the window, it was started prior to the selection + kwinApp()->platform()->touchUp(0, timestamp++); + QVERIFY(!selectedWindow); + kwinApp()->platform()->touchDown(0, client->geometry().center(), timestamp++); + kwinApp()->platform()->touchUp(0, timestamp++); + QCOMPARE(selectedWindow, client); + QCOMPARE(input()->isSelectingWindow(), false); + + QCOMPARE(touchStartedSpy.count(), 1); + QCOMPARE(touchCanceledSpy.count(), 1); +} + void TestWindowSelection::testCancelOnWindowPointer() { // this test verifies that window selection cancels through right button click @@ -451,5 +517,42 @@ QCOMPARE(keyboardEnteredSpy.count(), 2); } +void TestWindowSelection::testSelectPointTouch() +{ + // this test verifies point selection through touch works + QPoint point; + auto callback = [&point] (const QPoint &p) { + point = p; + }; + + // start the interaction + QCOMPARE(input()->isSelectingWindow(), false); + kwinApp()->platform()->startInteractivePositionSelection(callback); + QCOMPARE(input()->isSelectingWindow(), true); + QCOMPARE(point, QPoint()); + + // let's create multiple touch points + quint32 timestamp = 0; + kwinApp()->platform()->touchDown(0, QPointF(0, 1), timestamp++); + QCOMPARE(input()->isSelectingWindow(), true); + kwinApp()->platform()->touchDown(1, QPointF(10, 20), timestamp++); + QCOMPARE(input()->isSelectingWindow(), true); + kwinApp()->platform()->touchDown(2, QPointF(30, 40), timestamp++); + QCOMPARE(input()->isSelectingWindow(), true); + + // let's move our points + kwinApp()->platform()->touchMotion(0, QPointF(5, 10), timestamp++); + kwinApp()->platform()->touchMotion(2, QPointF(20, 25), timestamp++); + kwinApp()->platform()->touchMotion(1, QPointF(25, 35), timestamp++); + QCOMPARE(input()->isSelectingWindow(), true); + kwinApp()->platform()->touchUp(0, timestamp++); + QCOMPARE(input()->isSelectingWindow(), true); + kwinApp()->platform()->touchUp(2, timestamp++); + QCOMPARE(input()->isSelectingWindow(), true); + kwinApp()->platform()->touchUp(1, timestamp++); + QCOMPARE(input()->isSelectingWindow(), false); + QCOMPARE(point, QPoint(25, 35)); +} + WAYLANDTEST_MAIN(TestWindowSelection) #include "window_selection_test.moc" diff --git a/input.cpp b/input.cpp --- a/input.cpp +++ b/input.cpp @@ -555,7 +555,7 @@ if (event->button() == Qt::RightButton) { cancel(); } else { - accept(); + accept(event->globalPos()); } } break; @@ -584,7 +584,7 @@ } else if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return || event->key() == Qt::Key_Space) { - accept(); + accept(input()->globalPointer()); } if (input()->supportsPointerWarping()) { int mx = 0; @@ -612,28 +612,68 @@ return true; } + bool touchDown(quint32 id, const QPointF &pos, quint32 time) override { + Q_UNUSED(time) + if (!isActive()) { + return false; + } + m_touchPoints.insert(id, pos); + return true; + } + + bool touchMotion(quint32 id, const QPointF &pos, quint32 time) override { + Q_UNUSED(time) + if (!isActive()) { + return false; + } + auto it = m_touchPoints.find(id); + if (it != m_touchPoints.end()) { + *it = pos; + } + return true; + } + + bool touchUp(quint32 id, quint32 time) override { + Q_UNUSED(time) + if (!isActive()) { + return false; + } + auto it = m_touchPoints.find(id); + if (it != m_touchPoints.end()) { + const auto pos = it.value(); + m_touchPoints.erase(it); + if (m_touchPoints.isEmpty()) { + accept(pos); + } + } + return true; + } + bool isActive() const { return m_active; } void start(std::function callback) { Q_ASSERT(!m_active); m_active = true; m_callback = callback; input()->keyboard()->update(); + input()->cancelTouch(); } void start(std::function callback) { Q_ASSERT(!m_active); m_active = true; m_pointSelectionFallback = callback; input()->keyboard()->update(); + input()->cancelTouch(); } private: void deactivate() { m_active = false; m_callback = std::function(); m_pointSelectionFallback = std::function(); input()->pointer()->removeWindowSelectionCursor(); input()->keyboard()->update(); + m_touchPoints.clear(); } void cancel() { if (m_callback) { @@ -644,19 +684,23 @@ } deactivate(); } - void accept() { + void accept(const QPoint &pos) { if (m_callback) { // TODO: this ignores shaped windows - m_callback(input()->findToplevel(input()->globalPointer().toPoint())); + m_callback(input()->findToplevel(pos)); } if (m_pointSelectionFallback) { - m_pointSelectionFallback(input()->globalPointer().toPoint()); + m_pointSelectionFallback(pos); } deactivate(); } + void accept(const QPointF &pos) { + accept(pos.toPoint()); + } bool m_active = false; std::function m_callback; std::function m_pointSelectionFallback; + QMap m_touchPoints; }; class GlobalShortcutFilter : public InputEventFilter {