From 2aeaf9331b2f74e1619651cea6e33f6b0a33a487 Mon Sep 17 00:00:00 2001 From: David Redondo Date: Thu, 5 Aug 2021 10:21:29 +0200 Subject: [PATCH] Implement pointer hold gestures interface --- src/wayland/autotests/client/CMakeLists.txt | 4 + .../autotests/client/test_wayland_seat.cpp | 129 ++++++++++++++++++ src/wayland/pointer_interface.cpp | 1 + src/wayland/pointer_interface_p.h | 2 + src/wayland/pointergestures_v1_interface.cpp | 93 ++++++++++++- src/wayland/pointergestures_v1_interface_p.h | 21 +++ src/wayland/seat_interface.cpp | 36 +++++ src/wayland/seat_interface.h | 39 +++++- 8 files changed, 322 insertions(+), 3 deletions(-) diff --git a/src/wayland/autotests/client/CMakeLists.txt b/src/wayland/autotests/client/CMakeLists.txt index 3a1dfbe13a..8d8ab20913 100644 --- a/src/wayland/autotests/client/CMakeLists.txt +++ b/src/wayland/autotests/client/CMakeLists.txt @@ -27,6 +27,10 @@ if (HAVE_LINUX_INPUT_H) set( testWaylandSeat_SRCS test_wayland_seat.cpp ) + ecm_add_qtwayland_client_protocol(testWaylandSeat_SRCS + PROTOCOL ${WaylandProtocols_DATADIR}/unstable/pointer-gestures/pointer-gestures-unstable-v1.xml + BASENAME pointer-gestures-unstable-v1 + ) add_executable(testWaylandSeat ${testWaylandSeat_SRCS}) target_link_libraries( testWaylandSeat Qt::Test Qt::Gui KF5::WaylandClient Plasma::KWaylandServer Wayland::Client Wayland::Server) add_test(NAME kwayland-testWaylandSeat COMMAND testWaylandSeat) diff --git a/src/wayland/autotests/client/test_wayland_seat.cpp b/src/wayland/autotests/client/test_wayland_seat.cpp index 31540cf52c..bd21d3587e 100644 --- a/src/wayland/autotests/client/test_wayland_seat.cpp +++ b/src/wayland/autotests/client/test_wayland_seat.cpp @@ -36,6 +36,7 @@ #include "KWayland/Client/surface.h" #include "KWayland/Client/touch.h" // Wayland +#include "qwayland-pointer-gestures-unstable-v1.h" #include #include @@ -65,6 +66,8 @@ private Q_SLOTS: void testPointerSwipeGesture(); void testPointerPinchGesture_data(); void testPointerPinchGesture(); + void testPointerHoldGesture_data(); + void testPointerHoldGesture(); void testPointerAxis(); void testCursor(); void testCursorDamage(); @@ -1081,6 +1084,132 @@ void TestWaylandSeat::testPointerPinchGesture() QVERIFY(spy->wait()); } +void TestWaylandSeat::testPointerHoldGesture_data() +{ + QTest::addColumn("cancel"); + QTest::addColumn("expectedEndCount"); + QTest::addColumn("expectedCancelCount"); + + QTest::newRow("end") << false << 1 << 0; + QTest::newRow("cancel") << true << 0 << 1; +} + +class PointerHoldGesture : public QObject, public QtWayland::zwp_pointer_gesture_hold_v1 +{ + using zwp_pointer_gesture_hold_v1::zwp_pointer_gesture_hold_v1; + Q_OBJECT + void zwp_pointer_gesture_hold_v1_begin(uint32_t serial, uint32_t time, wl_surface *surface, uint32_t fingers) override + { + Q_EMIT started(serial, time, surface, fingers); + } + + void zwp_pointer_gesture_hold_v1_end(uint32_t serial, uint32_t time, int32_t cancelled) override + { + cancelled ? Q_EMIT this->cancelled(serial, time) : Q_EMIT ended(serial, time); + } +Q_SIGNALS: + void started(quint32 serial , quint32 time, void *surface, quint32 fingers); + void ended(quint32 serial, quint32 time); + void cancelled(quint32 serial, quint32 time); +}; + +void TestWaylandSeat::testPointerHoldGesture() +{ + using namespace KWayland::Client; + using namespace KWaylandServer; + + // first create the pointer and pointer swipe gesture + QSignalSpy hasPointerChangedSpy(m_seat, &Seat::hasPointerChanged); + QVERIFY(hasPointerChangedSpy.isValid()); + m_seatInterface->setHasPointer(true); + QVERIFY(hasPointerChangedSpy.wait()); + QScopedPointer pointer(m_seat->createPointer()); + Registry registry; + QSignalSpy gesturesAnnoucedSpy(®istry, &Registry::pointerGesturesUnstableV1Announced); + QVERIFY(gesturesAnnoucedSpy.isValid()); + registry.create(m_connection); + registry.setup(); + QVERIFY(gesturesAnnoucedSpy.wait()); + QtWayland::zwp_pointer_gestures_v1 gestures(registry, gesturesAnnoucedSpy.first().at(0).value(), gesturesAnnoucedSpy.first().at(1).value()); + PointerHoldGesture gesture(gestures.get_hold_gesture(*pointer)); + QVERIFY(gesture.isInitialized()); + + + QSignalSpy startSpy(&gesture, &PointerHoldGesture::started); + QVERIFY(startSpy.isValid()); + QSignalSpy endSpy(&gesture, &PointerHoldGesture::ended); + QVERIFY(endSpy.isValid()); + QSignalSpy cancelledSpy(&gesture, &PointerHoldGesture::cancelled); + QVERIFY(cancelledSpy.isValid()); + + // now create a surface + QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated); + QVERIFY(surfaceCreatedSpy.isValid()); + QScopedPointer surface(m_compositor->createSurface()); + QVERIFY(surfaceCreatedSpy.wait()); + auto serverSurface = surfaceCreatedSpy.first().first().value(); + QVERIFY(serverSurface); + + QImage image(QSize(100, 100), QImage::Format_ARGB32_Premultiplied); + image.fill(Qt::black); + surface->attachBuffer(m_shm->createBuffer(image)); + surface->damage(image.rect()); + surface->commit(Surface::CommitFlag::None); + QSignalSpy committedSpy(serverSurface, &KWaylandServer::SurfaceInterface::committed); + QVERIFY(committedSpy.wait()); + + m_seatInterface->setFocusedPointerSurface(serverSurface); + QCOMPARE(m_seatInterface->focusedPointerSurface(), serverSurface); + QVERIFY(m_seatInterface->pointer()); + + // send in the start + quint32 timestamp = 1; + m_seatInterface->setTimestamp(timestamp++); + m_seatInterface->startPointerHoldGesture(3); + QVERIFY(startSpy.wait()); + QCOMPARE(startSpy.count(), 1); + QCOMPARE(startSpy.first().at(0).value(), m_display->serial()); + QCOMPARE(startSpy.first().at(1).value(), 1u); + QCOMPARE(startSpy.first().at(2).value(), *surface.get()); + QCOMPARE(startSpy.first().at(3).value(), 3); + + // another start should not be possible + m_seatInterface->startPointerPinchGesture(3); + QVERIFY(!startSpy.wait(500)); + + // now end or cancel + QFETCH(bool, cancel); + QSignalSpy *spy; + m_seatInterface->setTimestamp(timestamp++); + if (cancel) { + m_seatInterface->cancelPointerHoldGesture(); + spy = &cancelledSpy; + } else { + m_seatInterface->endPointerHoldGesture(); + spy = &endSpy; + } + QVERIFY(spy->wait()); + QTEST(endSpy.count(), "expectedEndCount"); + QTEST(cancelledSpy.count(), "expectedCancelCount"); + QCOMPARE(spy->count(), 1); + QCOMPARE(spy->first().at(0).value(), m_display->serial()); + QCOMPARE(spy->first().at(1).value(), 2); + + // now a start should be possible again + m_seatInterface->setTimestamp(timestamp++); + m_seatInterface->startPointerHoldGesture(3); + QVERIFY(startSpy.wait()); + + // and end + m_seatInterface->setTimestamp(timestamp++); + if (cancel) { + m_seatInterface->cancelPointerHoldGesture(); + } else { + m_seatInterface->endPointerHoldGesture(); + } + QVERIFY(spy->wait()); +} + void TestWaylandSeat::testPointerAxis() { using namespace KWayland::Client; diff --git a/src/wayland/pointer_interface.cpp b/src/wayland/pointer_interface.cpp index 7a35383b25..b552ac4406 100644 --- a/src/wayland/pointer_interface.cpp +++ b/src/wayland/pointer_interface.cpp @@ -45,6 +45,7 @@ PointerInterfacePrivate::PointerInterfacePrivate(PointerInterface *q, SeatInterf , relativePointersV1(new RelativePointerV1Interface(q)) , swipeGesturesV1(new PointerSwipeGestureV1Interface(q)) , pinchGesturesV1(new PointerPinchGestureV1Interface(q)) + , holdGesturesV1(new PointerHoldGestureV1Interface(q)) { } diff --git a/src/wayland/pointer_interface_p.h b/src/wayland/pointer_interface_p.h index 0e8d4c2f0d..af3d96834c 100644 --- a/src/wayland/pointer_interface_p.h +++ b/src/wayland/pointer_interface_p.h @@ -20,6 +20,7 @@ namespace KWaylandServer class ClientConnection; class PointerPinchGestureV1Interface; class PointerSwipeGestureV1Interface; +class PointerHoldGestureV1Interface; class RelativePointerV1Interface; class PointerInterfacePrivate : public QtWaylandServer::wl_pointer @@ -40,6 +41,7 @@ public: QScopedPointer relativePointersV1; QScopedPointer swipeGesturesV1; QScopedPointer pinchGesturesV1; + QScopedPointer holdGesturesV1; QPointF lastPosition; void sendLeave(quint32 serial); diff --git a/src/wayland/pointergestures_v1_interface.cpp b/src/wayland/pointergestures_v1_interface.cpp index 65b36cd418..68afc9405c 100644 --- a/src/wayland/pointergestures_v1_interface.cpp +++ b/src/wayland/pointergestures_v1_interface.cpp @@ -15,7 +15,7 @@ namespace KWaylandServer { -static const int s_version = 2; +static const int s_version = 3; PointerGesturesV1InterfacePrivate::PointerGesturesV1InterfacePrivate(Display *display) : QtWaylandServer::zwp_pointer_gestures_v1(*display, s_version) @@ -46,6 +46,19 @@ void PointerGesturesV1InterfacePrivate::zwp_pointer_gestures_v1_get_pinch_gestur pinchGesture->add(resource->client(), id, resource->version()); } +void PointerGesturesV1InterfacePrivate::zwp_pointer_gestures_v1_get_hold_gesture(Resource *resource, uint32_t id, struct ::wl_resource *pointer_resource) +{ + PointerInterface *pointer = PointerInterface::get(pointer_resource); + if (!pointer) { + wl_resource_post_error(resource->handle, WL_DISPLAY_ERROR_INVALID_OBJECT, + "invalid pointer"); + return; + } + + PointerHoldGestureV1Interface *holdGesture = PointerHoldGestureV1Interface::get(pointer); + holdGesture->add(resource->client(), id, resource->version()); +} + void PointerGesturesV1InterfacePrivate::zwp_pointer_gestures_v1_release(Resource *resource) { wl_resource_destroy(resource->handle); @@ -254,4 +267,82 @@ void PointerPinchGestureV1Interface::sendCancel(quint32 serial) focusedClient = nullptr; } +PointerHoldGestureV1Interface::PointerHoldGestureV1Interface(PointerInterface *pointer) + : pointer(pointer) +{ +} + +PointerHoldGestureV1Interface *PointerHoldGestureV1Interface::get(PointerInterface *pointer) +{ + if (pointer) { + PointerInterfacePrivate *pointerPrivate = PointerInterfacePrivate::get(pointer); + return pointerPrivate->holdGesturesV1.data(); + } + return nullptr; +} + +void PointerHoldGestureV1Interface::zwp_pointer_gesture_hold_v1_destroy(Resource *resource) +{ + wl_resource_destroy(resource->handle); +} + +void PointerHoldGestureV1Interface::sendBegin(quint32 serial, quint32 fingerCount) +{ + if (focusedClient) { + return; // gesture is already active + } + if (!pointer->focusedSurface()) { + return; + } + + const SurfaceInterface *focusedSurface = pointer->focusedSurface(); + focusedClient = focusedSurface->client(); + SeatInterface *seat = pointer->seat(); + + const QList holdResources = resourceMap().values(*focusedClient); + for (Resource *holdResource : holdResources) { + if (holdResource->client() == focusedClient->client()) { + send_begin(holdResource->handle, serial, seat->timestamp(), focusedSurface->resource(), fingerCount); + } + } +} + +void PointerHoldGestureV1Interface::sendEnd(quint32 serial) +{ + if (!focusedClient) { + return; + } + + SeatInterface *seat = pointer->seat(); + + const QList holdResources = resourceMap().values(*focusedClient); + for (Resource *holdResource : holdResources) { + if (holdResource->client() == focusedClient->client()) { + send_end(holdResource->handle, serial, seat->timestamp(), false); + } + } + + // The gesture session has been just finished, reset the cached focused client. + focusedClient = nullptr; +} + +void PointerHoldGestureV1Interface::sendCancel(quint32 serial) +{ + if (!focusedClient) { + return; + } + + SeatInterface *seat = pointer->seat(); + + const QList holdResources = resourceMap().values(*focusedClient); + for (Resource *holdResource : holdResources) { + if (holdResource->client() == holdResource->client()) { + send_end(holdResource->handle, serial, seat->timestamp(), true); + } + } + + // The gesture session has been just finished, reset the cached focused client. + focusedClient = nullptr; +} + } // namespace KWaylandServer diff --git a/src/wayland/pointergestures_v1_interface_p.h b/src/wayland/pointergestures_v1_interface_p.h index 37a1997a38..a17464e861 100644 --- a/src/wayland/pointergestures_v1_interface_p.h +++ b/src/wayland/pointergestures_v1_interface_p.h @@ -26,6 +26,7 @@ public: protected: void zwp_pointer_gestures_v1_get_swipe_gesture(Resource *resource, uint32_t id, struct ::wl_resource *pointer_resource) override; void zwp_pointer_gestures_v1_get_pinch_gesture(Resource *resource, uint32_t id, struct ::wl_resource *pointer_resource) override; + void zwp_pointer_gestures_v1_get_hold_gesture(Resource *resource, uint32_t id, struct ::wl_resource *pointer_resource) override; void zwp_pointer_gestures_v1_release(Resource *resource) override; }; @@ -69,4 +70,24 @@ private: QPointer focusedClient; }; +class PointerHoldGestureV1Interface : public QtWaylandServer::zwp_pointer_gesture_hold_v1 +{ +public: + explicit PointerHoldGestureV1Interface(PointerInterface *pointer); + + static PointerHoldGestureV1Interface *get(PointerInterface *pointer); + + void sendBegin(quint32 serial, quint32 fingerCount); + void sendUpdate(const QSizeF &delta, qreal scale, qreal rotation); + void sendEnd(quint32 serial); + void sendCancel(quint32 serial); + +protected: + void zwp_pointer_gesture_hold_v1_destroy(Resource *resource) override; + +private: + PointerInterface *pointer; + QPointer focusedClient; +}; + } // namespace KWaylandServer diff --git a/src/wayland/seat_interface.cpp b/src/wayland/seat_interface.cpp index 0f7579e7d0..ae4f173214 100644 --- a/src/wayland/seat_interface.cpp +++ b/src/wayland/seat_interface.cpp @@ -854,6 +854,42 @@ void SeatInterface::cancelPointerPinchGesture() } } +void SeatInterface::startPointerHoldGesture(quint32 fingerCount) +{ + if (!d->pointer) { + return; + } + + auto holdGesture = PointerHoldGestureV1Interface::get(pointer()); + if (holdGesture) { + holdGesture->sendBegin(d->display->nextSerial(), fingerCount); + } +} + +void SeatInterface::endPointerHoldGesture() +{ + if (!d->pointer) { + return; + } + + auto holdGesture = PointerHoldGestureV1Interface::get(pointer()); + if (holdGesture) { + holdGesture->sendEnd(d->display->nextSerial()); + } +} + +void SeatInterface::cancelPointerHoldGesture() +{ + if (!d->pointer) { + return; + } + + auto holdGesture = PointerHoldGestureV1Interface::get(pointer()); + if (holdGesture) { + holdGesture->sendCancel(d->display->nextSerial()); + } +} + SurfaceInterface *SeatInterface::focusedKeyboardSurface() const { return d->globalKeyboard.focus.surface; diff --git a/src/wayland/seat_interface.h b/src/wayland/seat_interface.h index 13eaeeaf20..6bd2d0c933 100644 --- a/src/wayland/seat_interface.h +++ b/src/wayland/seat_interface.h @@ -422,7 +422,7 @@ public: * The precise conditions of when such a gesture is detected are * implementation-dependent. * - * Only one gesture (either swipe or pinch) can be active at a given time. + * Only one gesture (either swipe or pinch or hold) can be active at a given time. * * @param fingerCount The number of fingers involved in this multi-finger touchpad gesture * @@ -472,7 +472,7 @@ public: * around a logical center of gravity. The precise conditions of when * such a gesture is detected are implementation-dependent. * - * Only one gesture (either swipe or pinch) can be active at a given time. + * Only one gesture (either swipe or pinch or hold) can be active at a given time. * * @param fingerCount The number of fingers involved in this multi-touch touchpad gesture * @@ -513,6 +513,41 @@ public: * @see endPointerPinchGesture */ void cancelPointerPinchGesture(); + + /** + * Starts a multi-finger hold gesture for the currently focused pointer surface. + * + * Such gestures are normally reported through dedicated input devices such as touchpads. + * + * The gesture is usually initiated by multiple fingers being held down on the touchpad. + * The precise conditions of when such a gesture is detected are + * implementation-dependent. + * + * Only one gesture (either swipe or pinch or hold) can be active at a given time. + * + * @param fingerCount The number of fingers involved in this multi-finger touchpad gesture + * + * @see PointerGesturesInterface + * @see focusedPointerSurface + * @see endPointerHoldeGesture + * @see cancelPointerHoldGesture + */ + void startPointerHoldGesture(quint32 fingerCount); + + + /** + * The multi-finger hold gesture ended. This may happen when one or more fingers are lifted. + * @see startPointerHoldGesture + * @see cancelPointerHoldGesture + */ + void endPointerHoldGesture(); + + /** + * The multi-finger swipe gestures ended and got cancelled by the Wayland compositor. + * @see startPointerHoldGesture + * @see endPointerHoldGesture + */ + void cancelPointerHoldGesture(); ///@} /**