Commit cd26b6f1 authored by Vlad Zahorodnii's avatar Vlad Zahorodnii
Browse files

Port wl_touch wrapper to the new approach

With the new design, a single TouchInterface manages multiple wl_touch
objects. This allows implementing things such as touch grabs, etc.
parent 22018ee0
Pipeline #53674 passed with stage
in 7 minutes and 8 seconds
......@@ -75,7 +75,6 @@ private Q_SLOTS:
void testSelection();
void testDataDeviceForKeyboardSurface();
void testTouch();
void testDisconnect();
void testKeymap();
private:
......@@ -1963,16 +1962,13 @@ void TestWaylandSeat::testTouch()
m_seatInterface->setFocusedTouchSurface(serverSurface);
// no keyboard yet
QCOMPARE(m_seatInterface->focusedTouchSurface(), serverSurface);
QVERIFY(!m_seatInterface->focusedTouch());
QSignalSpy touchCreatedSpy(m_seatInterface, &KWaylandServer::SeatInterface::touchCreated);
QVERIFY(touchCreatedSpy.isValid());
Touch *touch = m_seat->createTouch(m_seat);
QVERIFY(touch->isValid());
QVERIFY(touchCreatedSpy.wait());
auto serverTouch = m_seatInterface->focusedTouch();
QVERIFY(serverTouch);
QCOMPARE(touchCreatedSpy.first().first().value<KWaylandServer::TouchInterface*>(), m_seatInterface->focusedTouch());
// Process wl_touch bind request.
wl_display_flush(m_connection->display());
QCoreApplication::processEvents();
QSignalSpy sequenceStartedSpy(touch, &KWayland::Client::Touch::sequenceStarted);
QVERIFY(sequenceStartedSpy.isValid());
......@@ -2130,82 +2126,6 @@ void TestWaylandSeat::testTouch()
QCOMPARE(pointMovedSpy.count(), 1);
QCOMPARE(pointRemovedSpy.count(), 3);
QCOMPARE(touch->sequence().first()->position(), QPointF(0, 0));
// destroy touch on client side
QSignalSpy unboundSpy(serverTouch, &TouchInterface::unbound);
QVERIFY(unboundSpy.isValid());
QSignalSpy destroyedSpy(serverTouch, &TouchInterface::destroyed);
QVERIFY(destroyedSpy.isValid());
delete touch;
QVERIFY(unboundSpy.wait());
QCOMPARE(unboundSpy.count(), 1);
QCOMPARE(destroyedSpy.count(), 0);
QVERIFY(!serverTouch->resource());
// try to call into all the methods of the touch interface, should not crash
QCOMPARE(m_seatInterface->focusedTouch(), serverTouch);
m_seatInterface->setTimestamp(8);
m_seatInterface->touchDown(0, QPointF(15, 26));
m_seatInterface->touchFrame();
m_seatInterface->touchMove(0, QPointF(0, 0));
m_seatInterface->touchDown(1, QPointF(15, 26));
m_seatInterface->cancelTouchSequence();
QVERIFY(destroyedSpy.wait());
QCOMPARE(destroyedSpy.count(), 1);
// should have unset the focused touch
QVERIFY(!m_seatInterface->focusedTouch());
// but not the focused touch surface
QCOMPARE(m_seatInterface->focusedTouchSurface(), serverSurface);
}
void TestWaylandSeat::testDisconnect()
{
// this test verifies that disconnecting the client cleans up correctly
using namespace KWayland::Client;
using namespace KWaylandServer;
QSignalSpy keyboardCreatedSpy(m_seatInterface, &SeatInterface::keyboardCreated);
QVERIFY(keyboardCreatedSpy.isValid());
QSignalSpy touchCreatedSpy(m_seatInterface, &SeatInterface::touchCreated);
QVERIFY(touchCreatedSpy.isValid());
// create the things we need
m_seatInterface->setHasKeyboard(true);
m_seatInterface->setHasTouch(true);
QSignalSpy touchSpy(m_seat, &Seat::hasTouchChanged);
QVERIFY(touchSpy.isValid());
QVERIFY(touchSpy.wait());
QScopedPointer<Keyboard> keyboard(m_seat->createKeyboard());
QVERIFY(!keyboard.isNull());
QVERIFY(keyboardCreatedSpy.wait());
auto serverKeyboard = keyboardCreatedSpy.first().first().value<KeyboardInterface*>();
QVERIFY(serverKeyboard);
QScopedPointer<Touch> touch(m_seat->createTouch());
QVERIFY(!touch.isNull());
QVERIFY(touchCreatedSpy.wait());
auto serverTouch = touchCreatedSpy.first().first().value<TouchInterface*>();
QVERIFY(serverTouch);
// setup destroys
QSignalSpy keyboardDestroyedSpy(serverKeyboard, &QObject::destroyed);
QVERIFY(keyboardDestroyedSpy.isValid());
QSignalSpy touchDestroyedSpy(serverTouch, &QObject::destroyed);
QVERIFY(touchDestroyedSpy.isValid());
if (m_connection) {
m_connection->deleteLater();
m_connection = nullptr;
}
keyboard->destroy();
touch->destroy();
m_relativePointerManager->destroy();
m_pointerGestures->destroy();
m_compositor->destroy();
m_seat->destroy();
m_shm->destroy();
m_subCompositor->destroy();
m_queue->destroy();
}
void TestWaylandSeat::testKeymap()
......
......@@ -27,6 +27,7 @@
#include "surface_interface.h"
#include "textinput_v2_interface_p.h"
#include "textinput_v3_interface_p.h"
#include "touch_interface_p.h"
// Qt
#include <QFile>
// Wayland
......@@ -183,64 +184,6 @@ SeatInterface::Private *SeatInterface::Private::cast(wl_resource *r)
return r ? reinterpret_cast<SeatInterface::Private*>(wl_resource_get_user_data(r)) : nullptr;
}
namespace {
template <typename T>
static
T *interfaceForSurface(SurfaceInterface *surface, const QVector<T*> &interfaces)
{
if (!surface) {
return nullptr;
}
for (auto it = interfaces.constBegin(); it != interfaces.constEnd(); ++it) {
if ((*it)->client() == surface->client()) {
return (*it);
}
}
return nullptr;
}
template <typename T>
static
QVector<T *> interfacesForSurface(SurfaceInterface *surface, const QVector<T*> &interfaces)
{
QVector<T *> ret;
if (!surface) {
return ret;
}
for (auto it = interfaces.constBegin(); it != interfaces.constEnd(); ++it) {
if ((*it)->client() == surface->client() && (*it)->resource()) {
ret << *it;
}
}
return ret;
}
template <typename T>
static
bool forEachInterface(SurfaceInterface *surface, const QVector<T*> &interfaces, std::function<void (T*)> method)
{
if (!surface) {
return false;
}
bool calledAtLeastOne = false;
for (auto it = interfaces.constBegin(); it != interfaces.constEnd(); ++it) {
if ((*it)->client() == surface->client() && (*it)->resource()) {
method(*it);
calledAtLeastOne = true;
}
}
return calledAtLeastOne;
}
}
QVector<TouchInterface *> SeatInterface::Private::touchsForSurface(SurfaceInterface *surface) const
{
return interfacesForSurface(surface, touchs);
}
QVector<DataDeviceInterface *> SeatInterface::Private::dataDevicesForSurface(SurfaceInterface *surface) const
{
if (!surface) {
......@@ -283,7 +226,6 @@ void SeatInterface::Private::registerDataDevice(DataDeviceInterface *dataDevice)
drag.transformation = globalPointer.focus.transformation;
} else if (q->hasImplicitTouchGrab(dragSerial)) {
drag.mode = Drag::Mode::Touch;
drag.sourceTouch = interfaceForSurface(dragSurface, touchs);
// TODO: touch transformation
} else {
// no implicit grab, abort drag
......@@ -490,10 +432,15 @@ void SeatInterface::setHasPointer(bool has)
void SeatInterface::setHasTouch(bool has)
{
Q_D();
if (d->touch == has) {
if (d->touch.isNull() != has) {
return;
}
d->touch = has;
if (has) {
d->touch.reset(new TouchInterface(this));
} else {
d->touch.reset();
}
emit hasTouchChanged(d->touch);
}
......@@ -542,30 +489,10 @@ void SeatInterface::Private::getTouchCallback(wl_client *client, wl_resource *re
void SeatInterface::Private::getTouch(wl_client *client, wl_resource *resource, uint32_t id)
{
// TODO: only create if seat has touch?
TouchInterface *touch = new TouchInterface(q, resource);
auto clientConnection = display->getConnection(client);
touch->create(clientConnection, qMin(wl_resource_get_version(resource), s_touchVersion), id);
if (!touch->resource()) {
wl_resource_post_no_memory(resource);
delete touch;
return;
}
touchs << touch;
if (globalTouch.focus.surface && globalTouch.focus.surface->client() == clientConnection) {
// this is a touch for the currently focused touch surface
globalTouch.focus.touchs << touch;
if (!globalTouch.ids.isEmpty()) {
// TODO: send out all the points
}
if (touch) {
TouchInterfacePrivate *touchPrivate = TouchInterfacePrivate::get(touch.data());
touchPrivate->add(client, id, wl_resource_get_version(resource));
}
QObject::connect(touch, &QObject::destroyed, q,
[touch,this] {
touchs.removeAt(touchs.indexOf(touch));
globalTouch.focus.touchs.removeOne(touch);
}
);
emit q->touchCreated(touch);
}
QString SeatInterface::name() const
......@@ -1117,9 +1044,9 @@ KeyboardInterface *SeatInterface::keyboard() const
void SeatInterface::cancelTouchSequence()
{
Q_D();
for (auto it = d->globalTouch.focus.touchs.constBegin(), end = d->globalTouch.focus.touchs.constEnd(); it != end; ++it) {
(*it)->cancel();
}
Q_ASSERT(d->touch);
d->touch->sendCancel();
if (d->drag.mode == Private::Drag::Mode::Touch) {
// cancel the drag, don't drop. serial does not matter
d->cancelDrag(0);
......@@ -1127,15 +1054,6 @@ void SeatInterface::cancelTouchSequence()
d->globalTouch.ids.clear();
}
TouchInterface *SeatInterface::focusedTouch() const
{
Q_D();
if (d->globalTouch.focus.touchs.isEmpty()) {
return nullptr;
}
return d->globalTouch.focus.touchs.first();
}
SurfaceInterface *SeatInterface::focusedTouchSurface() const
{
Q_D();
......@@ -1154,13 +1072,21 @@ bool SeatInterface::isTouchSequence() const
return !d->globalTouch.ids.isEmpty();
}
TouchInterface *SeatInterface::touch() const
{
Q_D();
return d->touch.data();
}
void SeatInterface::setFocusedTouchSurface(SurfaceInterface *surface, const QPointF &surfacePosition)
{
if (isTouchSequence()) {
// changing surface not allowed during a touch sequence
return;
}
Q_ASSERT(!isDragTouch());
if (isDragTouch()) {
return;
}
Q_D();
if (d->globalTouch.focus.surface) {
disconnect(d->globalTouch.focus.destroyConnection);
......@@ -1168,21 +1094,17 @@ void SeatInterface::setFocusedTouchSurface(SurfaceInterface *surface, const QPoi
d->globalTouch.focus = Private::Touch::Focus();
d->globalTouch.focus.surface = surface;
d->globalTouch.focus.offset = surfacePosition;
d->globalTouch.focus.touchs = d->touchsForSurface(surface);
if (d->globalTouch.focus.surface) {
d->globalTouch.focus.destroyConnection = connect(surface, &QObject::destroyed, this,
[this] {
Q_D();
if (isTouchSequence()) {
// Surface destroyed during touch sequence - send a cancel
for (auto it = d->globalTouch.focus.touchs.constBegin(), end = d->globalTouch.focus.touchs.constEnd(); it != end; ++it) {
(*it)->cancel();
}
}
d->globalTouch.focus = Private::Touch::Focus();
d->globalTouch.focus.destroyConnection = connect(surface, &QObject::destroyed, this, [this]() {
Q_D();
if (isTouchSequence()) {
// Surface destroyed during touch sequence - send a cancel
d->touch->sendCancel();
}
);
d->globalTouch.focus = Private::Touch::Focus();
});
}
d->touch->setFocusedSurface(surface);
}
void SeatInterface::setFocusedTouchSurfacePosition(const QPointF &surfacePosition)
......@@ -1194,23 +1116,25 @@ void SeatInterface::setFocusedTouchSurfacePosition(const QPointF &surfacePositio
void SeatInterface::touchDown(qint32 id, const QPointF &globalPosition)
{
Q_D();
Q_ASSERT(d->touch);
const qint32 serial = display()->nextSerial();
const auto pos = globalPosition - d->globalTouch.focus.offset;
for (auto it = d->globalTouch.focus.touchs.constBegin(), end = d->globalTouch.focus.touchs.constEnd(); it != end; ++it) {
(*it)->down(id, serial, pos);
}
d->touch->sendDown(id, serial, pos);
if (id == 0) {
d->globalTouch.focus.firstTouchPos = globalPosition;
}
#if HAVE_LINUX_INPUT_H
if (id == 0 && d->globalTouch.focus.touchs.isEmpty()) {
// If the client did not bind the touch interface fall back
// to at least emulating touch through pointer events.
d->pointer->setFocusedSurface(focusedTouchSurface(), pos, serial);
d->pointer->sendMotion(pos);
d->pointer->sendFrame();
if (id == 0 && hasPointer() && focusedTouchSurface()) {
TouchInterfacePrivate *touchPrivate = TouchInterfacePrivate::get(d->touch.data());
if (touchPrivate->touchesForClient(focusedTouchSurface()->client()).isEmpty()) {
// If the client did not bind the touch interface fall back
// to at least emulating touch through pointer events.
d->pointer->setFocusedSurface(focusedTouchSurface(), pos, serial);
d->pointer->sendMotion(pos);
d->pointer->sendFrame();
}
}
#endif
......@@ -1220,20 +1144,27 @@ void SeatInterface::touchDown(qint32 id, const QPointF &globalPosition)
void SeatInterface::touchMove(qint32 id, const QPointF &globalPosition)
{
Q_D();
Q_ASSERT(d->touch);
Q_ASSERT(d->globalTouch.ids.contains(id));
const auto pos = globalPosition - d->globalTouch.focus.offset;
for (auto it = d->globalTouch.focus.touchs.constBegin(), end = d->globalTouch.focus.touchs.constEnd(); it != end; ++it) {
(*it)->move(id, pos);
if (isDragTouch()) {
// handled by DataDevice
} else {
d->touch->sendMotion(id, pos);
}
if (id == 0) {
d->globalTouch.focus.firstTouchPos = globalPosition;
}
if (id == 0 && d->globalTouch.focus.touchs.isEmpty()) {
// Client did not bind touch, fall back to emulating with pointer events.
d->pointer->sendMotion(pos);
d->pointer->sendFrame();
if (hasPointer() && focusedTouchSurface()) {
TouchInterfacePrivate *touchPrivate = TouchInterfacePrivate::get(d->touch.data());
if (touchPrivate->touchesForClient(focusedTouchSurface()->client()).isEmpty()) {
// Client did not bind touch, fall back to emulating with pointer events.
d->pointer->sendMotion(pos);
d->pointer->sendFrame();
}
}
}
emit touchMoved(id, d->globalTouch.ids[id], globalPosition);
}
......@@ -1241,6 +1172,7 @@ void SeatInterface::touchMove(qint32 id, const QPointF &globalPosition)
void SeatInterface::touchUp(qint32 id)
{
Q_D();
Q_ASSERT(d->touch);
Q_ASSERT(d->globalTouch.ids.contains(id));
const qint32 serial = display()->nextSerial();
if (d->drag.mode == Private::Drag::Mode::Touch &&
......@@ -1248,16 +1180,17 @@ void SeatInterface::touchUp(qint32 id)
// the implicitly grabbing touch point has been upped
d->endDrag(serial);
}
for (auto it = d->globalTouch.focus.touchs.constBegin(), end = d->globalTouch.focus.touchs.constEnd(); it != end; ++it) {
(*it)->up(id, serial);
}
d->touch->sendUp(id, serial);
#if HAVE_LINUX_INPUT_H
if (id == 0 && d->globalTouch.focus.touchs.isEmpty()) {
// Client did not bind touch, fall back to emulating with pointer events.
const quint32 serial = display()->nextSerial();
d->pointer->sendRelease(BTN_LEFT, serial);
d->pointer->sendFrame();
if (id == 0 && hasPointer() && focusedTouchSurface()) {
TouchInterfacePrivate *touchPrivate = TouchInterfacePrivate::get(d->touch.data());
if (touchPrivate->touchesForClient(focusedTouchSurface()->client()).isEmpty()) {
// Client did not bind touch, fall back to emulating with pointer events.
const quint32 serial = display()->nextSerial();
d->pointer->sendRelease(BTN_LEFT, serial);
d->pointer->sendFrame();
}
}
#endif
......@@ -1267,9 +1200,8 @@ void SeatInterface::touchUp(qint32 id)
void SeatInterface::touchFrame()
{
Q_D();
for (auto it = d->globalTouch.focus.touchs.constBegin(), end = d->globalTouch.focus.touchs.constEnd(); it != end; ++it) {
(*it)->frame();
}
Q_ASSERT(d->touch);
d->touch->sendFrame();
}
bool SeatInterface::hasImplicitTouchGrab(quint32 serial) const
......
......@@ -553,7 +553,7 @@ public:
///@{
void setFocusedTouchSurface(SurfaceInterface *surface, const QPointF &surfacePosition = QPointF());
SurfaceInterface *focusedTouchSurface() const;
TouchInterface *focusedTouch() const;
TouchInterface *touch() const;
void setFocusedTouchSurfacePosition(const QPointF &surfacePosition);
QPointF focusedTouchSurfacePosition() const;
void touchDown(qint32 id, const QPointF &globalPosition);
......@@ -654,7 +654,6 @@ Q_SIGNALS:
void timestampChanged(quint32);
void keyboardCreated(KWaylandServer::KeyboardInterface*);
void touchCreated(KWaylandServer::TouchInterface*);
/**
* Emitted whenever the selection changes
......
......@@ -34,7 +34,6 @@ public:
void bind(wl_client *client, uint32_t version, uint32_t id) override;
void sendCapabilities(wl_resource *r);
void sendName(wl_resource *r);
QVector<TouchInterface *> touchsForSurface(SurfaceInterface *surface) const;
QVector<DataDeviceInterface *> dataDevicesForSurface(SurfaceInterface *surface) const;
void registerPrimarySelectionDevice(PrimarySelectionDeviceV1Interface *primarySelectionDevice);
void registerDataDevice(DataDeviceInterface *dataDevice);
......@@ -44,12 +43,11 @@ public:
quint32 nextSerial() const;
QString name;
bool touch = false;
QList<wl_resource*> resources;
quint32 timestamp = 0;
QScopedPointer<KeyboardInterface> keyboard;
QScopedPointer<PointerInterface> pointer;
QVector<TouchInterface*> touchs;
QScopedPointer<TouchInterface> touch;
QVector<DataDeviceInterface*> dataDevices;
QVector<PrimarySelectionDeviceV1Interface*> primarySelectionDevices;
QVector<DataControlDeviceV1Interface*> dataControlDevices;
......@@ -104,7 +102,6 @@ public:
struct Touch {
struct Focus {
SurfaceInterface *surface = nullptr;
QVector<TouchInterface*> touchs;
QMetaObject::Connection destroyConnection;
QPointF offset = QPointF();
QPointF firstTouchPos;
......@@ -124,7 +121,6 @@ public:
DataDeviceInterface *source = nullptr;
QPointer<DataDeviceInterface> target;
SurfaceInterface *surface = nullptr;
TouchInterface *sourceTouch = nullptr;
QMatrix4x4 transformation;
QMetaObject::Connection destroyConnection;
QMetaObject::Connection dragSourceDestroyConnection;
......
/*
SPDX-FileCopyrightText: 2015 Martin Gräßlin <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2020 Adrien Faveraux <ad1rie3@hotmail.fr>
SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "touch_interface.h"
#include "resource_p.h"
#include "seat_interface.h"
#include "clientconnection.h"
#include "display.h"
#include "seat_interface.h"
#include "surface_interface.h"
// Wayland
#include <wayland-server.h>
#include "touch_interface_p.h"
namespace KWaylandServer
{
class TouchInterface::Private : public Resource::Private
TouchInterfacePrivate *TouchInterfacePrivate::get(TouchInterface *touch)
{
public:
Private(SeatInterface *parent, wl_resource *parentResource, TouchInterface *q);
return touch->d.data();
}
SeatInterface *seat;
QMetaObject::Connection destroyConnection;
TouchInterfacePrivate::TouchInterfacePrivate(TouchInterface *q, SeatInterface *seat)
: q(q)
, seat(seat)
{
}
private:
TouchInterface *q_func() {
return reinterpret_cast<TouchInterface *>(q);
}
void TouchInterfacePrivate::touch_release(Resource *resource)
{
wl_resource_destroy(resource->handle);
}
static const struct wl_touch_interface s_interface;
};
QList<TouchInterfacePrivate::Resource *> TouchInterfacePrivate::touchesForClient(ClientConnection *client) const
{
return resourceMap().values(client->client());
}
#ifndef K_DOXYGEN
const struct wl_touch_interface TouchInterface::Private::s_interface = {
resourceDestroyedCallback
};
#endif
TouchInterface::TouchInterface(SeatInterface *seat)
: d(new TouchInterfacePrivate(this, seat))
{
}
TouchInterface::Private::Private(SeatInterface *parent, wl_resource *parentResource, TouchInterface *q)
: Resource::Private(q, parent, parentResource, &wl_touch_interface, &s_interface)
, seat(parent)
TouchInterface::~TouchInterface()
{
}
TouchInterface::TouchInterface(SeatInterface *parent, wl_resource *parentResource)
: Resource(new Private(parent, parentResource, this))
SurfaceInterface *TouchInterface::focusedSurface() const
{
return d->focusedSurface;
}
TouchInterface::~TouchInterface() = default;
void TouchInterface::setFocusedSurface(SurfaceInterface *surface)
{
d->focusedSurface = surface;
}
void TouchInterface::cancel()
void TouchInterface::sendCancel()
{
Q_D();
if (!d->resource) {
if (!d->focusedSurface) {
return;
}
wl_touch_send_cancel(d->resource);
d->client->flush();
const auto touchResources = d->touchesForClient(d->focusedSurface->client());
for (TouchInterfacePrivate::Resource *resource : touchResources) {
d->send_cancel(resource->handle);
}
}
void TouchInterface::frame()
void TouchInterface::sendFrame()
{
Q_D();
if (!d->resource) {
if (!d->focusedSurface) {