Implement pointer hold gestures interface

This commit is contained in:
David Redondo 2021-08-05 10:21:29 +02:00
parent e63783df9f
commit 2aeaf9331b
8 changed files with 322 additions and 3 deletions

View file

@ -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)

View file

@ -36,6 +36,7 @@
#include "KWayland/Client/surface.h"
#include "KWayland/Client/touch.h"
// Wayland
#include "qwayland-pointer-gestures-unstable-v1.h"
#include <wayland-client-protocol.h>
#include <linux/input.h>
@ -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<bool>("cancel");
QTest::addColumn<int>("expectedEndCount");
QTest::addColumn<int>("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> pointer(m_seat->createPointer());
Registry registry;
QSignalSpy gesturesAnnoucedSpy(&registry, &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<int>(), gesturesAnnoucedSpy.first().at(1).value<int>());
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> surface(m_compositor->createSurface());
QVERIFY(surfaceCreatedSpy.wait());
auto serverSurface = surfaceCreatedSpy.first().first().value<SurfaceInterface*>();
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<quint32>(), m_display->serial());
QCOMPARE(startSpy.first().at(1).value<quint32>(), 1u);
QCOMPARE(startSpy.first().at(2).value<void*>(), *surface.get());
QCOMPARE(startSpy.first().at(3).value<quint32>(), 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<quint32>(), m_display->serial());
QCOMPARE(spy->first().at(1).value<quint32>(), 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;

View file

@ -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))
{
}

View file

@ -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<RelativePointerV1Interface> relativePointersV1;
QScopedPointer<PointerSwipeGestureV1Interface> swipeGesturesV1;
QScopedPointer<PointerPinchGestureV1Interface> pinchGesturesV1;
QScopedPointer<PointerHoldGestureV1Interface> holdGesturesV1;
QPointF lastPosition;
void sendLeave(quint32 serial);

View file

@ -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<Resource *> 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<Resource *> 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<Resource *> 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

View file

@ -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<ClientConnection> 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<ClientConnection> focusedClient;
};
} // namespace KWaylandServer

View file

@ -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;

View file

@ -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();
///@}
/**