Commit 5492ad5d authored by Roman Gilg's avatar Roman Gilg
Browse files

[server] Touch drag support

Summary: Adds functionality to do drag and drop with touch screens.

Test Plan: Manually. Autotest planned.

Reviewers: #kwin, #frameworks, davidedmundson

Reviewed By: #kwin, davidedmundson

Subscribers: davidedmundson, kde-frameworks-devel

Tags: #frameworks

Differential Revision: https://phabricator.kde.org/D15464
parent 28a98504
......@@ -27,6 +27,7 @@ License along with this library. If not, see <http://www.gnu.org/licenses/>.
#include "../../src/client/datasource.h"
#include "../../src/client/event_queue.h"
#include "../../src/client/pointer.h"
#include "../../src/client/touch.h"
#include "../../src/client/registry.h"
#include "../../src/client/seat.h"
#include "../../src/client/shell.h"
......@@ -47,7 +48,8 @@ private Q_SLOTS:
void init();
void cleanup();
void testDragAndDrop();
void testPointerDragAndDrop();
void testTouchDragAndDrop();
void testDragAndDropWithCancelByDestroyDataSource();
void testPointerEventsIgnored();
......@@ -69,6 +71,7 @@ private:
KWayland::Client::Registry *m_registry = nullptr;
KWayland::Client::Seat *m_seat = nullptr;
KWayland::Client::Pointer *m_pointer = nullptr;
KWayland::Client::Touch *m_touch = nullptr;
KWayland::Client::DataDeviceManager *m_ddm = nullptr;
KWayland::Client::ShmPool *m_shm = nullptr;
KWayland::Client::Shell *m_shell = nullptr;
......@@ -97,6 +100,7 @@ void TestDragAndDrop::init()
QVERIFY(m_compositorInterface->isValid());
m_seatInterface = m_display->createSeat(m_display);
m_seatInterface->setHasPointer(true);
m_seatInterface->setHasTouch(true);
m_seatInterface->create();
QVERIFY(m_seatInterface->isValid());
m_dataDeviceManagerInterface = m_display->createDataDeviceManager(m_display);
......@@ -147,6 +151,8 @@ void TestDragAndDrop::init()
QVERIFY(pointerSpy.wait());
m_pointer = m_seat->createPointer(m_seat);
QVERIFY(m_pointer->isValid());
m_touch = m_seat->createTouch(m_seat);
QVERIFY(m_touch->isValid());
m_dataDevice = m_ddm->getDataDevice(m_seat, this);
QVERIFY(m_dataDevice->isValid());
m_dataSource = m_ddm->createDataSource(this);
......@@ -210,7 +216,7 @@ KWayland::Server::SurfaceInterface *TestDragAndDrop::getServerSurface()
return surfaceCreatedSpy.first().first().value<SurfaceInterface*>();
}
void TestDragAndDrop::testDragAndDrop()
void TestDragAndDrop::testPointerDragAndDrop()
{
// this test verifies the very basic drag and drop on one surface, an enter, a move and the drop
using namespace KWayland::Server;
......@@ -303,6 +309,105 @@ void TestDragAndDrop::testDragAndDrop()
QCOMPARE(buttonPressSpy.count(), 1);
}
void TestDragAndDrop::testTouchDragAndDrop()
{
// this test verifies the very basic drag and drop on one surface, an enter, a move and the drop
using namespace KWayland::Server;
using namespace KWayland::Client;
// first create a window
QScopedPointer<Surface> s(createSurface());
s->setSize(QSize(100,100));
auto serverSurface = getServerSurface();
QVERIFY(serverSurface);
QSignalSpy dataSourceSelectedActionChangedSpy(m_dataSource, &DataSource::selectedDragAndDropActionChanged);
QVERIFY(dataSourceSelectedActionChangedSpy.isValid());
// now we need to pass touch focus to the Surface and simulate a touch down
QSignalSpy sequenceStartedSpy(m_touch, &Touch::sequenceStarted);
QVERIFY(sequenceStartedSpy.isValid());
QSignalSpy pointAddedSpy(m_touch, &Touch::pointAdded);
QVERIFY(pointAddedSpy.isValid());
m_seatInterface->setFocusedTouchSurface(serverSurface);
m_seatInterface->setTimestamp(2);
const qint32 touchId = m_seatInterface->touchDown(QPointF(50,50));
QVERIFY(sequenceStartedSpy.wait());
QScopedPointer<TouchPoint> tp(sequenceStartedSpy.first().at(0).value<TouchPoint*>());
QVERIFY(!tp.isNull());
QCOMPARE(tp->time(), quint32(2));
// add some signal spies for client side
QSignalSpy dragEnteredSpy(m_dataDevice, &DataDevice::dragEntered);
QVERIFY(dragEnteredSpy.isValid());
QSignalSpy dragMotionSpy(m_dataDevice, &DataDevice::dragMotion);
QVERIFY(dragMotionSpy.isValid());
QSignalSpy touchMotionSpy(m_touch, &Touch::pointMoved);
QVERIFY(touchMotionSpy.isValid());
QSignalSpy sourceDropSpy(m_dataSource, &DataSource::dragAndDropPerformed);
QVERIFY(sourceDropSpy.isValid());
// now we can start the drag and drop
QSignalSpy dragStartedSpy(m_seatInterface, &SeatInterface::dragStarted);
QVERIFY(dragStartedSpy.isValid());
m_dataSource->setDragAndDropActions(DataDeviceManager::DnDAction::Copy | DataDeviceManager::DnDAction::Move);
m_dataDevice->startDrag(tp->downSerial(), m_dataSource, s.data());
QVERIFY(dragStartedSpy.wait());
QCOMPARE(m_seatInterface->dragSurface(), serverSurface);
QCOMPARE(m_seatInterface->dragSurfaceTransformation(), QMatrix4x4());
QVERIFY(!m_seatInterface->dragSource()->icon());
QCOMPARE(m_seatInterface->dragSource()->dragImplicitGrabSerial(), tp->downSerial());
QVERIFY(dragEnteredSpy.wait());
QCOMPARE(dragEnteredSpy.count(), 1);
QCOMPARE(dragEnteredSpy.first().first().value<quint32>(), m_display->serial());
QCOMPARE(dragEnteredSpy.first().last().toPointF(), QPointF(0, 0));
QCOMPARE(m_dataDevice->dragSurface().data(), s.data());
auto offer = m_dataDevice->dragOffer();
QVERIFY(offer);
QCOMPARE(offer->selectedDragAndDropAction(), DataDeviceManager::DnDAction::None);
QSignalSpy offerActionChangedSpy(offer, &DataOffer::selectedDragAndDropActionChanged);
QVERIFY(offerActionChangedSpy.isValid());
QCOMPARE(m_dataDevice->dragOffer()->offeredMimeTypes().count(), 1);
QCOMPARE(m_dataDevice->dragOffer()->offeredMimeTypes().first().name(), QStringLiteral("text/plain"));
QTRY_COMPARE(offer->sourceDragAndDropActions(), DataDeviceManager::DnDAction::Copy | DataDeviceManager::DnDAction::Move);
offer->setDragAndDropActions(DataDeviceManager::DnDAction::Copy | DataDeviceManager::DnDAction::Move, DataDeviceManager::DnDAction::Move);
QVERIFY(offerActionChangedSpy.wait());
QCOMPARE(offerActionChangedSpy.count(), 1);
QCOMPARE(offer->selectedDragAndDropAction(), DataDeviceManager::DnDAction::Move);
QCOMPARE(dataSourceSelectedActionChangedSpy.count(), 1);
QCOMPARE(m_dataSource->selectedDragAndDropAction(), DataDeviceManager::DnDAction::Move);
// simulate motion
m_seatInterface->setTimestamp(3);
m_seatInterface->touchMove(touchId, QPointF(75, 75));
QVERIFY(dragMotionSpy.wait());
QCOMPARE(dragMotionSpy.count(), 1);
QCOMPARE(dragMotionSpy.first().first().toPointF(), QPointF(75, 75));
QCOMPARE(dragMotionSpy.first().last().toUInt(), 3u);
// simulate drop
QSignalSpy serverDragEndedSpy(m_seatInterface, &SeatInterface::dragEnded);
QVERIFY(serverDragEndedSpy.isValid());
QSignalSpy droppedSpy(m_dataDevice, &DataDevice::dropped);
QVERIFY(droppedSpy.isValid());
m_seatInterface->setTimestamp(4);
m_seatInterface->touchUp(touchId);
QVERIFY(sourceDropSpy.isEmpty());
QVERIFY(droppedSpy.wait());
QCOMPARE(sourceDropSpy.count(), 1);
QCOMPARE(serverDragEndedSpy.count(), 1);
QSignalSpy finishedSpy(m_dataSource, &DataSource::dragAndDropFinished);
QVERIFY(finishedSpy.isValid());
offer->dragAndDropFinished();
QVERIFY(finishedSpy.wait());
delete offer;
// verify that we did not get any further input events
QVERIFY(touchMotionSpy.isEmpty());
QCOMPARE(pointAddedSpy.count(), 0);
}
void TestDragAndDrop::testDragAndDropWithCancelByDestroyDataSource()
{
......
......@@ -54,7 +54,7 @@ public:
struct Drag {
SurfaceInterface *surface = nullptr;
QMetaObject::Connection destroyConnection;
QMetaObject::Connection pointerPosConnection;
QMetaObject::Connection posConnection;
QMetaObject::Connection sourceActionConnection;
QMetaObject::Connection targetActionConnection;
quint32 serial = 0;
......@@ -99,10 +99,14 @@ void DataDeviceInterface::Private::startDragCallback(wl_client *client, wl_resou
void DataDeviceInterface::Private::startDrag(DataSourceInterface *dataSource, SurfaceInterface *origin, SurfaceInterface *i, quint32 serial)
{
// TODO: allow touch
if (!seat->hasImplicitPointerGrab(serial) || seat->focusedPointerSurface() != origin) {
// Surface doesn't have pointer grab.
return;
const bool pointerGrab = seat->hasImplicitPointerGrab(serial) && seat->focusedPointerSurface() == origin;
if (!pointerGrab) {
// Client doesn't have pointer grab.
const bool touchGrab = seat->hasImplicitTouchGrab(serial) && seat->focusedTouchSurface() == origin;
if (!touchGrab) {
// Client neither has pointer nor touch grab. No drag start allowed.
return;
}
}
// TODO: source is allowed to be null, handled client internally!
Q_Q(DataDeviceInterface);
......@@ -248,9 +252,9 @@ void DataDeviceInterface::drop()
return;
}
wl_data_device_send_drop(d->resource);
if (d->drag.pointerPosConnection) {
disconnect(d->drag.pointerPosConnection);
d->drag.pointerPosConnection = QMetaObject::Connection();
if (d->drag.posConnection) {
disconnect(d->drag.posConnection);
d->drag.posConnection = QMetaObject::Connection();
}
disconnect(d->drag.destroyConnection);
d->drag.destroyConnection = QMetaObject::Connection();
......@@ -265,9 +269,9 @@ void DataDeviceInterface::updateDragTarget(SurfaceInterface *surface, quint32 se
if (d->resource && d->drag.surface->resource()) {
wl_data_device_send_leave(d->resource);
}
if (d->drag.pointerPosConnection) {
disconnect(d->drag.pointerPosConnection);
d->drag.pointerPosConnection = QMetaObject::Connection();
if (d->drag.posConnection) {
disconnect(d->drag.posConnection);
d->drag.posConnection = QMetaObject::Connection();
}
disconnect(d->drag.destroyConnection);
d->drag.destroyConnection = QMetaObject::Connection();
......@@ -292,7 +296,7 @@ void DataDeviceInterface::updateDragTarget(SurfaceInterface *surface, quint32 se
DataOfferInterface *offer = d->createDataOffer(source);
d->drag.surface = surface;
if (d->seat->isDragPointer()) {
d->drag.pointerPosConnection = connect(d->seat, &SeatInterface::pointerPosChanged, this,
d->drag.posConnection = connect(d->seat, &SeatInterface::pointerPosChanged, this,
[this] {
Q_D();
const QPointF pos = d->seat->dragSurfaceTransformation().map(d->seat->pointerPos());
......@@ -301,16 +305,30 @@ void DataDeviceInterface::updateDragTarget(SurfaceInterface *surface, quint32 se
client()->flush();
}
);
} else if (d->seat->isDragTouch()) {
d->drag.posConnection = connect(d->seat, &SeatInterface::touchMoved, this,
[this](qint32 id, quint32 serial, const QPointF &globalPosition) {
Q_D();
Q_UNUSED(id);
if (serial != d->drag.serial) {
// different touch down has been moved
return;
}
const QPointF pos = d->seat->dragSurfaceTransformation().map(globalPosition);
wl_data_device_send_motion(d->resource, d->seat->timestamp(),
wl_fixed_from_double(pos.x()), wl_fixed_from_double(pos.y()));
client()->flush();
}
);
}
// TODO: same for touch
d->drag.destroyConnection = connect(d->drag.surface, &QObject::destroyed, this,
[this] {
Q_D();
if (d->resource) {
wl_data_device_send_leave(d->resource);
}
if (d->drag.pointerPosConnection) {
disconnect(d->drag.pointerPosConnection);
if (d->drag.posConnection) {
disconnect(d->drag.posConnection);
}
d->drag = Private::Drag();
}
......
......@@ -292,18 +292,23 @@ void SeatInterface::Private::registerDataDevice(DataDeviceInterface *dataDevice)
);
QObject::connect(dataDevice, &DataDeviceInterface::dragStarted, q,
[this, dataDevice] {
if (q->hasImplicitPointerGrab(dataDevice->dragImplicitGrabSerial())) {
const auto dragSerial = dataDevice->dragImplicitGrabSerial();
auto *dragSurface = dataDevice->origin();
if (q->hasImplicitPointerGrab(dragSerial)) {
drag.mode = Drag::Mode::Pointer;
drag.sourcePointer = interfaceForSurface(dragSurface, pointers);
drag.transformation = globalPointer.focus.transformation;
} else if (q->hasImplicitTouchGrab(dragSerial)) {
drag.mode = Drag::Mode::Touch;
drag.sourceTouch = interfaceForSurface(dragSurface, touchs);
// TODO: touch transformation
} else {
// TODO: touch
// no implicit grab, abort drag
return;
}
drag.source = dataDevice;
drag.target = dataDevice;
drag.surface = dataDevice->origin();
drag.sourcePointer = interfaceForSurface(drag.surface, pointers);
// TODO: transformation needs to be either pointer or touch
drag.transformation = globalPointer.focus.transformation;
drag.surface = dragSurface;
drag.destroyConnection = QObject::connect(dataDevice, &QObject::destroyed, q,
[this] {
endDrag(display->nextSerial());
......@@ -645,9 +650,11 @@ void SeatInterface::setDragTarget(SurfaceInterface *surface, const QPointF &glob
d->drag.target->updateDragTarget(nullptr, serial);
}
d->drag.target = d->dataDeviceForSurface(surface);
// TODO: update touch
if (d->drag.mode == Private::Drag::Mode::Pointer) {
setPointerPos(globalPosition);
} else if (d->drag.mode == Private::Drag::Mode::Touch &&
d->globalTouch.focus.firstTouchPos != globalPosition) {
touchMove(d->globalTouch.ids.first(), globalPosition);
}
if (d->drag.target) {
d->drag.surface = surface;
......@@ -662,8 +669,14 @@ void SeatInterface::setDragTarget(SurfaceInterface *surface, const QPointF &glob
void SeatInterface::setDragTarget(SurfaceInterface *surface, const QMatrix4x4 &inputTransformation)
{
// TODO: handle touch
setDragTarget(surface, pointerPos(), inputTransformation);
Q_D();
if (d->drag.mode == Private::Drag::Mode::Pointer) {
setDragTarget(surface, pointerPos(), inputTransformation);
} else {
Q_ASSERT(d->drag.mode == Private::Drag::Mode::Touch);
setDragTarget(surface, d->globalTouch.focus.firstTouchPos, inputTransformation);
}
}
SurfaceInterface *SeatInterface::focusedPointerSurface() const
......@@ -1253,6 +1266,16 @@ void SeatInterface::cancelTouchSequence()
for (auto it = d->globalTouch.focus.touchs.constBegin(), end = d->globalTouch.focus.touchs.constEnd(); it != end; ++it) {
(*it)->cancel();
}
if (d->drag.mode == Private::Drag::Mode::Touch) {
// cancel the drag, don't drop.
if (d->drag.target) {
// remove the current target
d->drag.target->updateDragTarget(nullptr, 0);
d->drag.target = nullptr;
}
// and end the drag for the source, serial does not matter
d->endDrag(0);
}
d->globalTouch.ids.clear();
}
......@@ -1289,6 +1312,7 @@ void SeatInterface::setFocusedTouchSurface(SurfaceInterface *surface, const QPoi
// changing surface not allowed during a touch sequence
return;
}
Q_ASSERT(!isDragTouch());
Q_D();
if (d->globalTouch.focus.surface) {
disconnect(d->globalTouch.focus.destroyConnection);
......@@ -1329,6 +1353,10 @@ qint32 SeatInterface::touchDown(const QPointF &globalPosition)
(*it)->down(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
......@@ -1348,7 +1376,7 @@ qint32 SeatInterface::touchDown(const QPointF &globalPosition)
}
#endif
d->globalTouch.ids << id;
d->globalTouch.ids[id] = serial;
return id;
}
......@@ -1361,6 +1389,10 @@ void SeatInterface::touchMove(qint32 id, const QPointF &globalPosition)
(*it)->move(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.
forEachInterface<PointerInterface>(focusedTouchSurface(), d->pointers,
......@@ -1370,6 +1402,7 @@ void SeatInterface::touchMove(qint32 id, const QPointF &globalPosition)
}
);
}
emit touchMoved(id, d->globalTouch.ids[id], globalPosition);
}
void SeatInterface::touchUp(qint32 id)
......@@ -1377,6 +1410,11 @@ void SeatInterface::touchUp(qint32 id)
Q_D();
Q_ASSERT(d->globalTouch.ids.contains(id));
const qint32 serial = display()->nextSerial();
if (d->drag.mode == Private::Drag::Mode::Touch &&
d->drag.source->dragImplicitGrabSerial() == d->globalTouch.ids.value(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);
}
......@@ -1404,6 +1442,16 @@ void SeatInterface::touchFrame()
}
}
bool SeatInterface::hasImplicitTouchGrab(quint32 serial) const
{
Q_D();
if (!d->globalTouch.focus.surface) {
// origin surface has been destroyed
return false;
}
return d->globalTouch.ids.key(serial, -1) != -1;
}
bool SeatInterface::isDrag() const
{
Q_D();
......
......@@ -589,6 +589,12 @@ public:
void touchFrame();
void cancelTouchSequence();
bool isTouchSequence() const;
/**
* @returns true if there is a touch sequence going on associated with a touch
* down of the given @p serial.
* @since 5.XX
**/
bool hasImplicitTouchGrab(quint32 serial) const;
///@}
/**
......@@ -663,6 +669,7 @@ Q_SIGNALS:
void hasKeyboardChanged(bool);
void hasTouchChanged(bool);
void pointerPosChanged(const QPointF &pos);
void touchMoved(qint32 id, quint32 serial, const QPointF &globalPosition);
void timestampChanged(quint32);
void pointerCreated(KWayland::Server::PointerInterface*);
......
......@@ -24,6 +24,7 @@ License along with this library. If not, see <http://www.gnu.org/licenses/>.
#include "global_p.h"
// Qt
#include <QHash>
#include <QMap>
#include <QPointer>
#include <QVector>
// Wayland
......@@ -147,9 +148,10 @@ public:
QVector<TouchInterface*> touchs;
QMetaObject::Connection destroyConnection;
QPointF offset = QPointF();
QPointF firstTouchPos;
};
Focus focus;
QVector<qint32> ids;
QMap<qint32, quint32> ids;
};
Touch globalTouch;
......@@ -164,6 +166,7 @@ public:
DataDeviceInterface *target = nullptr;
SurfaceInterface *surface = nullptr;
PointerInterface *sourcePointer = nullptr;
TouchInterface *sourceTouch = nullptr;
QMatrix4x4 transformation;
QMetaObject::Connection destroyConnection;
QMetaObject::Connection dragSourceDestroyConnection;
......
......@@ -92,6 +92,10 @@ void TouchInterface::move(qint32 id, const QPointF &localPos)
if (!d->resource) {
return;
}
if (d->seat->isDragTouch()) {
// handled by DataDevice
return;
}
wl_touch_send_motion(d->resource, d->seat->timestamp(), id, wl_fixed_from_double(localPos.x()), wl_fixed_from_double(localPos.y()));
d->client->flush();
}
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment