diff --git a/autotests/client/test_drag_drop.cpp b/autotests/client/test_drag_drop.cpp --- a/autotests/client/test_drag_drop.cpp +++ b/autotests/client/test_drag_drop.cpp @@ -27,6 +27,7 @@ #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 @@ void init(); void cleanup(); - void testDragAndDrop(); + void testPointerDragAndDrop(); + void testTouchDragAndDrop(); void testDragAndDropWithCancelByDestroyDataSource(); void testPointerEventsIgnored(); @@ -69,6 +71,7 @@ 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 @@ 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 @@ 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 @@ return surfaceCreatedSpy.first().first().value(); } -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 @@ 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 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 tp(sequenceStartedSpy.first().at(0).value()); + 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(), 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() { diff --git a/src/server/datadevice_interface.cpp b/src/server/datadevice_interface.cpp --- a/src/server/datadevice_interface.cpp +++ b/src/server/datadevice_interface.cpp @@ -54,7 +54,7 @@ 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::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 @@ 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 @@ 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,25 +296,39 @@ 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()); wl_data_device_send_motion(d->resource, d->seat->timestamp(), wl_fixed_from_double(pos.x()), wl_fixed_from_double(pos.y())); 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(); } diff --git a/src/server/seat_interface.h b/src/server/seat_interface.h --- a/src/server/seat_interface.h +++ b/src/server/seat_interface.h @@ -589,6 +589,12 @@ 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 @@ 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*); diff --git a/src/server/seat_interface.cpp b/src/server/seat_interface.cpp --- a/src/server/seat_interface.cpp +++ b/src/server/seat_interface.cpp @@ -292,18 +292,23 @@ ); 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 @@ 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 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 @@ 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 @@ // 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 @@ (*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 @@ } #endif - d->globalTouch.ids << id; + d->globalTouch.ids[id] = serial; return id; } @@ -1361,6 +1389,10 @@ (*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(focusedTouchSurface(), d->pointers, @@ -1370,13 +1402,19 @@ } ); } + emit touchMoved(id, d->globalTouch.ids[id], globalPosition); } 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 @@ } } +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(); diff --git a/src/server/seat_interface_p.h b/src/server/seat_interface_p.h --- a/src/server/seat_interface_p.h +++ b/src/server/seat_interface_p.h @@ -24,6 +24,7 @@ #include "global_p.h" // Qt #include +#include #include #include // Wayland @@ -147,9 +148,10 @@ QVector touchs; QMetaObject::Connection destroyConnection; QPointF offset = QPointF(); + QPointF firstTouchPos; }; Focus focus; - QVector ids; + QMap ids; }; Touch globalTouch; @@ -164,6 +166,7 @@ DataDeviceInterface *target = nullptr; SurfaceInterface *surface = nullptr; PointerInterface *sourcePointer = nullptr; + TouchInterface *sourceTouch = nullptr; QMatrix4x4 transformation; QMetaObject::Connection destroyConnection; QMetaObject::Connection dragSourceDestroyConnection; diff --git a/src/server/touch_interface.cpp b/src/server/touch_interface.cpp --- a/src/server/touch_interface.cpp +++ b/src/server/touch_interface.cpp @@ -92,6 +92,10 @@ 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(); }