2098 lines
109 KiB
C++
2098 lines
109 KiB
C++
/*
|
|
SPDX-FileCopyrightText: 2025 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
|
|
|
|
SPDX-License-Identifier: GPL-2.0-or-later
|
|
*/
|
|
|
|
#include "kwin_wayland_test.h"
|
|
|
|
#include "core/output.h"
|
|
#include "pointer_input.h"
|
|
#include "utils/pipe.h"
|
|
#include "wayland_server.h"
|
|
#include "window.h"
|
|
#include "workspace.h"
|
|
|
|
#include <KWayland/Client/datadevice.h>
|
|
#include <KWayland/Client/dataoffer.h>
|
|
#include <KWayland/Client/datasource.h>
|
|
#include <KWayland/Client/pointer.h>
|
|
#include <KWayland/Client/seat.h>
|
|
#include <KWayland/Client/subsurface.h>
|
|
#include <KWayland/Client/surface.h>
|
|
#include <KWayland/Client/touch.h>
|
|
|
|
#include <QFile>
|
|
#include <QFuture>
|
|
#include <QFutureWatcher>
|
|
#include <QMimeDatabase>
|
|
#include <QMimeType>
|
|
#include <QtConcurrent>
|
|
|
|
#include <fcntl.h>
|
|
#include <linux/input-event-codes.h>
|
|
|
|
namespace KWin
|
|
{
|
|
|
|
static const QString s_socketName = QStringLiteral("wayland_test_dnd-0");
|
|
|
|
static QFuture<QByteArray> readMimeTypeData(KWayland::Client::DataOffer *offer, const QString &mimeType)
|
|
{
|
|
auto pipe = Pipe::create(O_CLOEXEC);
|
|
if (!pipe) {
|
|
return QFuture<QByteArray>();
|
|
}
|
|
|
|
offer->receive(mimeType, pipe->writeEndpoint.get());
|
|
|
|
return QtConcurrent::run([fd = std::move(pipe->readEndpoint)] {
|
|
QFile file;
|
|
if (!file.open(fd.get(), QFile::ReadOnly | QFile::Text)) {
|
|
return QByteArray();
|
|
}
|
|
|
|
return file.readAll();
|
|
});
|
|
}
|
|
|
|
template<typename T>
|
|
static bool waitFuture(const QFuture<T> &future)
|
|
{
|
|
QFutureWatcher<T> watcher;
|
|
QSignalSpy finishedSpy(&watcher, &QFutureWatcher<T>::finished);
|
|
watcher.setFuture(future);
|
|
return finishedSpy.wait();
|
|
}
|
|
|
|
class DndTest : public QObject
|
|
{
|
|
Q_OBJECT
|
|
|
|
private Q_SLOTS:
|
|
void initTestCase();
|
|
void init();
|
|
void cleanup();
|
|
|
|
void pointerDrag();
|
|
void pointerSubSurfaceDrag();
|
|
void pointerDragAction_data();
|
|
void pointerDragAction();
|
|
void inertPointerDragOffer();
|
|
void invalidSerialForPointerDrag();
|
|
void cancelPointerDrag();
|
|
void destroyPointerDragSource();
|
|
void noAcceptedMimeTypePointerDrag();
|
|
void secondPointerDrag();
|
|
void touchDrag();
|
|
void touchSubSurfaceDrag();
|
|
void touchDragAction_data();
|
|
void touchDragAction();
|
|
void inertTouchDragOffer();
|
|
void invalidSerialForTouchDrag();
|
|
void noAcceptedMimeTypeTouchDrag();
|
|
void secondTouchDrag();
|
|
void cancelTouchSequence();
|
|
void tabletDrag();
|
|
void tabletSubSurfaceDrag();
|
|
void tabletDragAction_data();
|
|
void tabletDragAction();
|
|
void inertTabletDragOffer();
|
|
void invalidSerialForTabletDrag();
|
|
void noAcceptedMimeTypeTabletDrag();
|
|
void secondTabletDrag();
|
|
|
|
private:
|
|
QMimeDatabase m_mimeDb;
|
|
};
|
|
|
|
void DndTest::initTestCase()
|
|
{
|
|
qRegisterMetaType<Window *>();
|
|
|
|
QVERIFY(waylandServer()->init(s_socketName));
|
|
kwinApp()->start();
|
|
Test::setOutputConfig({
|
|
Rect(0, 0, 1280, 1024),
|
|
Rect(1280, 0, 1280, 1024),
|
|
});
|
|
const auto outputs = workspace()->outputs();
|
|
QCOMPARE(outputs.count(), 2);
|
|
QCOMPARE(outputs[0]->geometry(), Rect(0, 0, 1280, 1024));
|
|
QCOMPARE(outputs[1]->geometry(), Rect(1280, 0, 1280, 1024));
|
|
|
|
// Generate tablet tool events so the corresponding tablet tool device is registered.
|
|
Test::tabletToolProximityEvent(QPointF(640, 480), 0, 0, 0, 0, true, 0, 0);
|
|
Test::tabletToolProximityEvent(QPointF(640, 480), 0, 0, 0, 0, false, 0, 0);
|
|
}
|
|
|
|
void DndTest::init()
|
|
{
|
|
QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Seat | Test::AdditionalWaylandInterface::DataDeviceManager | Test::AdditionalWaylandInterface::WpTabletV2));
|
|
|
|
workspace()->setActiveOutput(QPoint(640, 512));
|
|
input()->pointer()->warp(QPoint(640, 512));
|
|
}
|
|
|
|
void DndTest::cleanup()
|
|
{
|
|
Test::destroyWaylandConnection();
|
|
}
|
|
|
|
void DndTest::pointerDrag()
|
|
{
|
|
// This test verifies that a simple pointer drag-and-drop operation between two Wayland clients works as expected.
|
|
|
|
QVERIFY(Test::waitForWaylandPointer());
|
|
|
|
// Setup the data source.
|
|
auto pointer = Test::waylandSeat()->createPointer(Test::waylandSeat());
|
|
auto dataDevice = Test::waylandDataDeviceManager()->getDataDevice(Test::waylandSeat(), Test::waylandSeat());
|
|
auto dataSource = Test::waylandDataDeviceManager()->createDataSource(Test::waylandSeat());
|
|
dataSource->offer(QStringLiteral("text/plain"));
|
|
dataSource->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Copy | KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
connect(dataSource, &KWayland::Client::DataSource::sendDataRequested, this, [](const QString &mimeType, int fd) {
|
|
if (mimeType == QLatin1String("text/plain")) {
|
|
const auto payload = QByteArrayLiteral("foo");
|
|
write(fd, payload.data(), payload.size());
|
|
}
|
|
close(fd);
|
|
});
|
|
QSignalSpy pointerButtonSpy(pointer, &KWayland::Client::Pointer::buttonStateChanged);
|
|
QSignalSpy pointerEnteredSpy(pointer, &KWayland::Client::Pointer::entered);
|
|
QSignalSpy pointerLeftSpy(pointer, &KWayland::Client::Pointer::left);
|
|
QSignalSpy dataDeviceDragEnteredSpy(dataDevice, &KWayland::Client::DataDevice::dragEntered);
|
|
QSignalSpy dataDeviceDragLeftSpy(dataDevice, &KWayland::Client::DataDevice::dragLeft);
|
|
QSignalSpy dataDeviceDragMotionSpy(dataDevice, &KWayland::Client::DataDevice::dragMotion);
|
|
QSignalSpy dataDeviceDroppedSpy(dataDevice, &KWayland::Client::DataDevice::dropped);
|
|
|
|
// Setup the source client.
|
|
auto sourceSurface = Test::createSurface();
|
|
auto sourceShellSurface = Test::createXdgToplevelSurface(sourceSurface.get());
|
|
auto sourceWindow = Test::renderAndWaitForShown(sourceSurface.get(), QSize(100, 100), Qt::red);
|
|
QVERIFY(sourceWindow);
|
|
sourceWindow->move(QPointF(0, 0));
|
|
|
|
// Setup the target client.
|
|
auto targetSurface = Test::createSurface();
|
|
auto targetShellSurface = Test::createXdgToplevelSurface(targetSurface.get());
|
|
auto targetWindow = Test::renderAndWaitForShown(targetSurface.get(), QSize(100, 100), Qt::red);
|
|
QVERIFY(targetWindow);
|
|
targetWindow->move(QPointF(100, 0));
|
|
|
|
// Move the pointer to the center of the source window and press the left mouse button.
|
|
quint32 timestamp = 0;
|
|
Test::pointerMotion(QPointF(50, 50), timestamp++);
|
|
Test::pointerButtonPressed(BTN_LEFT, timestamp++);
|
|
QVERIFY(pointerButtonSpy.wait());
|
|
|
|
// Start a drag-and-drop operation.
|
|
const quint32 buttonPressSerial = pointerButtonSpy.last().at(0).value<quint32>();
|
|
dataDevice->startDrag(buttonPressSerial, dataSource, sourceSurface.get());
|
|
QVERIFY(dataDeviceDragEnteredSpy.wait());
|
|
QCOMPARE(pointerLeftSpy.count(), 1);
|
|
QVERIFY(!pointer->enteredSurface());
|
|
QCOMPARE(dataDeviceDragEnteredSpy.last().at(1).value<QPointF>(), QPointF(50, 50));
|
|
QCOMPARE(dataDevice->dragSurface(), sourceSurface.get());
|
|
|
|
QCOMPARE(dataDeviceDragEnteredSpy.count(), 1);
|
|
QCOMPARE(dataDeviceDragMotionSpy.count(), 0);
|
|
QCOMPARE(dataDeviceDragLeftSpy.count(), 0);
|
|
QCOMPARE(dataDeviceDroppedSpy.count(), 0);
|
|
|
|
// Accept our own offer.
|
|
auto sourceOffer = dataDevice->takeDragOffer();
|
|
QCOMPARE(sourceOffer->offeredMimeTypes(), (QList<QMimeType>{m_mimeDb.mimeTypeForName(QStringLiteral("text/plain"))}));
|
|
QCOMPARE(sourceOffer->sourceDragAndDropActions(), KWayland::Client::DataDeviceManager::DnDAction::Copy | KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
QCOMPARE(sourceOffer->selectedDragAndDropAction(), KWayland::Client::DataDeviceManager::DnDAction::None);
|
|
|
|
QSignalSpy sourceTargetsAcceptsSpy(dataSource, &KWayland::Client::DataSource::targetAccepts);
|
|
sourceOffer->accept(QString(), dataDeviceDragEnteredSpy.last().at(0).value<quint32>());
|
|
QVERIFY(sourceTargetsAcceptsSpy.wait());
|
|
QCOMPARE(sourceTargetsAcceptsSpy.last().at(0).value<QString>(), QString());
|
|
|
|
sourceOffer->accept(QStringLiteral("text/plain"), dataDeviceDragEnteredSpy.last().at(0).value<quint32>());
|
|
QVERIFY(sourceTargetsAcceptsSpy.wait());
|
|
QCOMPARE(sourceTargetsAcceptsSpy.last().at(0).value<QString>(), QStringLiteral("text/plain"));
|
|
|
|
QSignalSpy sourceOfferSelectedDragAndDropActionSpy(sourceOffer.get(), &KWayland::Client::DataOffer::selectedDragAndDropActionChanged);
|
|
sourceOffer->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Copy, KWayland::Client::DataDeviceManager::DnDAction::Copy);
|
|
QVERIFY(sourceOfferSelectedDragAndDropActionSpy.wait());
|
|
QCOMPARE(sourceOffer->selectedDragAndDropAction(), KWayland::Client::DataDeviceManager::DnDAction::Copy);
|
|
QCOMPARE(dataSource->selectedDragAndDropAction(), KWayland::Client::DataDeviceManager::DnDAction::Copy);
|
|
|
|
// Move the pointer a little bit.
|
|
Test::pointerMotion(QPointF(60, 50), timestamp++);
|
|
QVERIFY(dataDeviceDragMotionSpy.wait());
|
|
QCOMPARE(dataDeviceDragMotionSpy.last().at(0).value<QPointF>(), QPointF(60, 50));
|
|
|
|
QCOMPARE(dataDeviceDragEnteredSpy.count(), 1);
|
|
QCOMPARE(dataDeviceDragMotionSpy.count(), 1);
|
|
QCOMPARE(dataDeviceDragLeftSpy.count(), 0);
|
|
QCOMPARE(dataDeviceDroppedSpy.count(), 0);
|
|
|
|
// Move the pointer to the second client.
|
|
Test::pointerMotion(QPointF(140, 50), timestamp++);
|
|
QVERIFY(dataDeviceDragEnteredSpy.wait());
|
|
QCOMPARE(dataDeviceDragEnteredSpy.last().at(1).value<QPointF>(), QPointF(40, 50));
|
|
QCOMPARE(dataDeviceDragLeftSpy.count(), 1);
|
|
QCOMPARE(dataDevice->dragSurface(), targetSurface.get());
|
|
|
|
QCOMPARE(dataDeviceDragEnteredSpy.count(), 2);
|
|
QCOMPARE(dataDeviceDragMotionSpy.count(), 1);
|
|
QCOMPARE(dataDeviceDragLeftSpy.count(), 1);
|
|
QCOMPARE(dataDeviceDroppedSpy.count(), 0);
|
|
|
|
// Accept the offer.
|
|
auto targetOffer = dataDevice->takeDragOffer();
|
|
QCOMPARE(targetOffer->offeredMimeTypes(), (QList<QMimeType>{m_mimeDb.mimeTypeForName(QStringLiteral("text/plain"))}));
|
|
QCOMPARE(targetOffer->sourceDragAndDropActions(), KWayland::Client::DataDeviceManager::DnDAction::Copy | KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
QCOMPARE(targetOffer->selectedDragAndDropAction(), KWayland::Client::DataDeviceManager::DnDAction::None);
|
|
|
|
targetOffer->accept(QString(), dataDeviceDragEnteredSpy.last().at(0).value<quint32>());
|
|
QVERIFY(sourceTargetsAcceptsSpy.wait());
|
|
QCOMPARE(sourceTargetsAcceptsSpy.last().at(0).value<QString>(), QString());
|
|
|
|
targetOffer->accept(QStringLiteral("text/plain"), dataDeviceDragEnteredSpy.last().at(0).value<quint32>());
|
|
QVERIFY(sourceTargetsAcceptsSpy.wait());
|
|
QCOMPARE(sourceTargetsAcceptsSpy.last().at(0).value<QString>(), QStringLiteral("text/plain"));
|
|
|
|
QSignalSpy targetOfferSelectedDragAndDropActionSpy(targetOffer.get(), &KWayland::Client::DataOffer::selectedDragAndDropActionChanged);
|
|
targetOffer->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Move, KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
QVERIFY(targetOfferSelectedDragAndDropActionSpy.wait());
|
|
QCOMPARE(targetOffer->selectedDragAndDropAction(), KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
QCOMPARE(dataSource->selectedDragAndDropAction(), KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
|
|
// Move the pointer a little bit.
|
|
Test::pointerMotion(QPointF(160, 50), timestamp++);
|
|
QVERIFY(dataDeviceDragMotionSpy.wait());
|
|
QCOMPARE(dataDeviceDragMotionSpy.last().at(0).value<QPointF>(), QPointF(60, 50));
|
|
|
|
QCOMPARE(dataDeviceDragEnteredSpy.count(), 2);
|
|
QCOMPARE(dataDeviceDragMotionSpy.count(), 2);
|
|
QCOMPARE(dataDeviceDragLeftSpy.count(), 1);
|
|
QCOMPARE(dataDeviceDroppedSpy.count(), 0);
|
|
|
|
// Drop.
|
|
Test::pointerButtonReleased(BTN_LEFT, timestamp++);
|
|
QVERIFY(pointerEnteredSpy.wait());
|
|
QCOMPARE(pointer->enteredSurface(), targetSurface.get());
|
|
QCOMPARE(pointerEnteredSpy.last().at(1).value<QPointF>(), QPointF(60, 50));
|
|
|
|
QCOMPARE(dataDeviceDragEnteredSpy.count(), 2);
|
|
QCOMPARE(dataDeviceDragMotionSpy.count(), 2);
|
|
QCOMPARE(dataDeviceDragLeftSpy.count(), 2);
|
|
QCOMPARE(dataDeviceDroppedSpy.count(), 1);
|
|
|
|
// Ask for data.
|
|
const QFuture<QByteArray> plainData = readMimeTypeData(targetOffer.get(), QStringLiteral("text/plain"));
|
|
QVERIFY(waitFuture(plainData));
|
|
QCOMPARE(plainData.result(), QByteArrayLiteral("foo"));
|
|
|
|
// text/html hasn't been offered, so this should fail.
|
|
const QFuture<QByteArray> htmlData = readMimeTypeData(targetOffer.get(), QStringLiteral("text/html"));
|
|
QVERIFY(waitFuture(htmlData));
|
|
QCOMPARE(htmlData.result(), QByteArray());
|
|
|
|
// Finish drag-and-drop.
|
|
QSignalSpy dragAndDropFinishedSpy(dataSource, &KWayland::Client::DataSource::dragAndDropFinished);
|
|
targetOffer->dragAndDropFinished();
|
|
QVERIFY(dragAndDropFinishedSpy.wait());
|
|
}
|
|
|
|
void DndTest::pointerSubSurfaceDrag()
|
|
{
|
|
// This test verifies that a drag-and-drop originating from a sub-surface works as expected.
|
|
|
|
QVERIFY(Test::waitForWaylandPointer());
|
|
|
|
// Setup the data source.
|
|
auto pointer = Test::waylandSeat()->createPointer(Test::waylandSeat());
|
|
auto dataDevice = Test::waylandDataDeviceManager()->getDataDevice(Test::waylandSeat(), Test::waylandSeat());
|
|
auto dataSource = Test::waylandDataDeviceManager()->createDataSource(Test::waylandSeat());
|
|
dataSource->offer(QStringLiteral("text/plain"));
|
|
dataSource->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Copy | KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
QSignalSpy pointerButtonSpy(pointer, &KWayland::Client::Pointer::buttonStateChanged);
|
|
QSignalSpy pointerEnteredSpy(pointer, &KWayland::Client::Pointer::entered);
|
|
QSignalSpy pointerLeftSpy(pointer, &KWayland::Client::Pointer::left);
|
|
QSignalSpy dataDeviceDragEnteredSpy(dataDevice, &KWayland::Client::DataDevice::dragEntered);
|
|
QSignalSpy dataDeviceDragLeftSpy(dataDevice, &KWayland::Client::DataDevice::dragLeft);
|
|
QSignalSpy dataDeviceDragMotionSpy(dataDevice, &KWayland::Client::DataDevice::dragMotion);
|
|
QSignalSpy dataDeviceDroppedSpy(dataDevice, &KWayland::Client::DataDevice::dropped);
|
|
|
|
// Setup a window with a subsurface that covers the right half.
|
|
auto parentSurface = Test::createSurface();
|
|
auto parentShellSurface = Test::createXdgToplevelSurface(parentSurface.get());
|
|
auto window = Test::renderAndWaitForShown(parentSurface.get(), QSize(100, 100), Qt::red);
|
|
QVERIFY(window);
|
|
window->move(QPointF(0, 0));
|
|
|
|
auto childSurface = Test::createSurface();
|
|
auto subSurface = Test::createSubSurface(childSurface.get(), parentSurface.get());
|
|
subSurface->setPosition(QPoint(50, 0));
|
|
Test::render(childSurface.get(), QSize(50, 100), Qt::blue);
|
|
parentSurface->commit(KWayland::Client::Surface::CommitFlag::None);
|
|
|
|
// Move the pointer to the center of the subsurface and press the left mouse button.
|
|
quint32 timestamp = 0;
|
|
Test::pointerMotion(QPointF(75, 50), timestamp++);
|
|
Test::pointerButtonPressed(BTN_LEFT, timestamp++);
|
|
QVERIFY(pointerButtonSpy.wait());
|
|
|
|
// Start a drag-and-drop operation.
|
|
const quint32 buttonPressSerial = pointerButtonSpy.last().at(0).value<quint32>();
|
|
dataDevice->startDrag(buttonPressSerial, dataSource, childSurface.get());
|
|
QVERIFY(dataDeviceDragEnteredSpy.wait());
|
|
QCOMPARE(pointerLeftSpy.count(), 1);
|
|
QVERIFY(!pointer->enteredSurface());
|
|
QCOMPARE(dataDeviceDragEnteredSpy.last().at(1).value<QPointF>(), QPointF(25, 50));
|
|
QCOMPARE(dataDevice->dragSurface(), childSurface.get());
|
|
|
|
// Move the pointer to the parent surface.
|
|
Test::pointerMotion(QPointF(10, 50), timestamp++);
|
|
QVERIFY(dataDeviceDragEnteredSpy.wait());
|
|
QCOMPARE(dataDeviceDragEnteredSpy.count(), 2);
|
|
QCOMPARE(dataDeviceDragLeftSpy.count(), 1);
|
|
QCOMPARE(dataDeviceDragEnteredSpy.last().at(1).value<QPointF>(), QPointF(10, 50));
|
|
QCOMPARE(dataDevice->dragSurface(), parentSurface.get());
|
|
|
|
Test::pointerMotion(QPointF(15, 50), timestamp++);
|
|
QVERIFY(dataDeviceDragMotionSpy.wait());
|
|
QCOMPARE(dataDeviceDragMotionSpy.last().at(0).value<QPointF>(), QPointF(15, 50));
|
|
QCOMPARE(dataDeviceDragEnteredSpy.count(), 2);
|
|
QCOMPARE(dataDeviceDragLeftSpy.count(), 1);
|
|
QCOMPARE(dataDevice->dragSurface(), parentSurface.get());
|
|
|
|
// Move the pointer to the child surface.
|
|
Test::pointerMotion(QPointF(80, 50), timestamp++);
|
|
QVERIFY(dataDeviceDragEnteredSpy.wait());
|
|
QCOMPARE(dataDeviceDragEnteredSpy.count(), 3);
|
|
QCOMPARE(dataDeviceDragLeftSpy.count(), 2);
|
|
QCOMPARE(dataDeviceDragEnteredSpy.last().at(1).value<QPointF>(), QPointF(30, 50));
|
|
QCOMPARE(dataDevice->dragSurface(), childSurface.get());
|
|
|
|
Test::pointerMotion(QPointF(75, 50), timestamp++);
|
|
QVERIFY(dataDeviceDragMotionSpy.wait());
|
|
QCOMPARE(dataDeviceDragMotionSpy.last().at(0).value<QPointF>(), QPointF(25, 50));
|
|
QCOMPARE(dataDeviceDragEnteredSpy.count(), 3);
|
|
QCOMPARE(dataDeviceDragLeftSpy.count(), 2);
|
|
QCOMPARE(dataDevice->dragSurface(), childSurface.get());
|
|
|
|
// Accept the data offer.
|
|
auto offer = dataDevice->takeDragOffer();
|
|
QCOMPARE(offer->offeredMimeTypes(), (QList<QMimeType>{m_mimeDb.mimeTypeForName(QStringLiteral("text/plain"))}));
|
|
QCOMPARE(offer->sourceDragAndDropActions(), KWayland::Client::DataDeviceManager::DnDAction::Copy | KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
QCOMPARE(offer->selectedDragAndDropAction(), KWayland::Client::DataDeviceManager::DnDAction::None);
|
|
|
|
QSignalSpy sourceTargetsAcceptsSpy(dataSource, &KWayland::Client::DataSource::targetAccepts);
|
|
offer->accept(QString(), dataDeviceDragEnteredSpy.last().at(0).value<quint32>());
|
|
QVERIFY(sourceTargetsAcceptsSpy.wait());
|
|
QCOMPARE(sourceTargetsAcceptsSpy.last().at(0).value<QString>(), QString());
|
|
|
|
offer->accept(QStringLiteral("text/plain"), dataDeviceDragEnteredSpy.last().at(0).value<quint32>());
|
|
QVERIFY(sourceTargetsAcceptsSpy.wait());
|
|
QCOMPARE(sourceTargetsAcceptsSpy.last().at(0).value<QString>(), QStringLiteral("text/plain"));
|
|
|
|
QSignalSpy targetOfferSelectedDragAndDropActionSpy(offer.get(), &KWayland::Client::DataOffer::selectedDragAndDropActionChanged);
|
|
offer->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Move, KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
QVERIFY(targetOfferSelectedDragAndDropActionSpy.wait());
|
|
QCOMPARE(offer->selectedDragAndDropAction(), KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
QCOMPARE(dataSource->selectedDragAndDropAction(), KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
|
|
// Drop.
|
|
Test::pointerButtonReleased(BTN_LEFT, timestamp++);
|
|
QVERIFY(pointerEnteredSpy.wait());
|
|
QCOMPARE(pointer->enteredSurface(), childSurface.get());
|
|
QCOMPARE(pointerEnteredSpy.last().at(1).value<QPointF>(), QPointF(25, 50));
|
|
QCOMPARE(dataDeviceDroppedSpy.count(), 1);
|
|
|
|
// Finish drag-and-drop.
|
|
QSignalSpy dragAndDropFinishedSpy(dataSource, &KWayland::Client::DataSource::dragAndDropFinished);
|
|
offer->dragAndDropFinished();
|
|
QVERIFY(dragAndDropFinishedSpy.wait());
|
|
}
|
|
|
|
void DndTest::pointerDragAction_data()
|
|
{
|
|
QTest::addColumn<KWayland::Client::DataDeviceManager::DnDAction>("action");
|
|
|
|
QTest::addRow("ask") << KWayland::Client::DataDeviceManager::DnDAction::Ask;
|
|
QTest::addRow("copy") << KWayland::Client::DataDeviceManager::DnDAction::Copy;
|
|
QTest::addRow("move") << KWayland::Client::DataDeviceManager::DnDAction::Move;
|
|
}
|
|
|
|
void DndTest::pointerDragAction()
|
|
{
|
|
// This test verifies that the dnd actions are handled as expected. "Ask" action is a bit
|
|
// special; if it has been selected, then the final action can be set after the drop.
|
|
|
|
QVERIFY(Test::waitForWaylandPointer());
|
|
QFETCH(KWayland::Client::DataDeviceManager::DnDAction, action);
|
|
|
|
// Setup the data source.
|
|
auto pointer = Test::waylandSeat()->createPointer(Test::waylandSeat());
|
|
auto dataDevice = Test::waylandDataDeviceManager()->getDataDevice(Test::waylandSeat(), Test::waylandSeat());
|
|
auto dataSource = Test::waylandDataDeviceManager()->createDataSource(Test::waylandSeat());
|
|
dataSource->offer(QStringLiteral("text/plain"));
|
|
dataSource->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Copy | KWayland::Client::DataDeviceManager::DnDAction::Move | KWayland::Client::DataDeviceManager::DnDAction::Ask);
|
|
QSignalSpy pointerButtonSpy(pointer, &KWayland::Client::Pointer::buttonStateChanged);
|
|
QSignalSpy dataDeviceDragEnteredSpy(dataDevice, &KWayland::Client::DataDevice::dragEntered);
|
|
QSignalSpy dataDeviceDroppedSpy(dataDevice, &KWayland::Client::DataDevice::dropped);
|
|
QSignalSpy dataSourceSelectedDragAndDropActionChangedSpy(dataSource, &KWayland::Client::DataSource::selectedDragAndDropActionChanged);
|
|
|
|
// Setup the source window.
|
|
auto surface = Test::createSurface();
|
|
auto shellSurface = Test::createXdgToplevelSurface(surface.get());
|
|
auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 100), Qt::red);
|
|
QVERIFY(window);
|
|
window->move(QPointF(0, 0));
|
|
|
|
// Move the pointer to the center of the surface and press the left mouse button.
|
|
quint32 timestamp = 0;
|
|
Test::pointerMotion(QPointF(50, 50), timestamp++);
|
|
Test::pointerButtonPressed(BTN_LEFT, timestamp++);
|
|
QVERIFY(pointerButtonSpy.wait());
|
|
|
|
// Start a drag-and-drop operation.
|
|
dataDevice->startDrag(pointerButtonSpy.last().at(0).value<quint32>(), dataSource, surface.get());
|
|
QVERIFY(dataDeviceDragEnteredSpy.wait());
|
|
|
|
const auto offer = dataDevice->takeDragOffer();
|
|
offer->accept(QStringLiteral("text/plain"), dataDeviceDragEnteredSpy.last().at(0).value<quint32>());
|
|
offer->setDragAndDropActions(action, action);
|
|
QVERIFY(dataSourceSelectedDragAndDropActionChangedSpy.wait());
|
|
QCOMPARE(dataSourceSelectedDragAndDropActionChangedSpy.count(), 1);
|
|
QCOMPARE(dataSource->selectedDragAndDropAction(), action);
|
|
|
|
// Drop.
|
|
Test::pointerButtonReleased(BTN_LEFT, timestamp++);
|
|
QVERIFY(dataDeviceDroppedSpy.wait());
|
|
QCOMPARE(dataSourceSelectedDragAndDropActionChangedSpy.count(), 1);
|
|
|
|
// Change action after drop.
|
|
switch (action) {
|
|
case KWayland::Client::DataDeviceManager::DnDAction::Ask:
|
|
offer->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Copy, KWayland::Client::DataDeviceManager::DnDAction::Copy);
|
|
QVERIFY(dataSourceSelectedDragAndDropActionChangedSpy.wait());
|
|
QCOMPARE(dataSourceSelectedDragAndDropActionChangedSpy.count(), 2);
|
|
QCOMPARE(dataSource->selectedDragAndDropAction(), KWayland::Client::DataDeviceManager::DnDAction::Copy);
|
|
break;
|
|
case KWayland::Client::DataDeviceManager::DnDAction::Copy:
|
|
case KWayland::Client::DataDeviceManager::DnDAction::Move: {
|
|
const auto oppositeAction = action == KWayland::Client::DataDeviceManager::DnDAction::Copy
|
|
? KWayland::Client::DataDeviceManager::DnDAction::Move
|
|
: KWayland::Client::DataDeviceManager::DnDAction::Copy;
|
|
offer->setDragAndDropActions(oppositeAction, oppositeAction);
|
|
QVERIFY(!dataSourceSelectedDragAndDropActionChangedSpy.wait(10));
|
|
break;
|
|
}
|
|
case KWayland::Client::DataDeviceManager::DnDAction::None:
|
|
Q_UNREACHABLE();
|
|
}
|
|
|
|
// Finish drag-and-drop.
|
|
QSignalSpy dragAndDropFinishedSpy(dataSource, &KWayland::Client::DataSource::dragAndDropFinished);
|
|
offer->dragAndDropFinished();
|
|
QVERIFY(dragAndDropFinishedSpy.wait());
|
|
}
|
|
|
|
void DndTest::inertPointerDragOffer()
|
|
{
|
|
// This test verifies that requests from a previous data offer don't affect the current drag-and-drop operation status.
|
|
|
|
QVERIFY(Test::waitForWaylandPointer());
|
|
|
|
// Setup the data source.
|
|
auto pointer = Test::waylandSeat()->createPointer(Test::waylandSeat());
|
|
auto dataDevice = Test::waylandDataDeviceManager()->getDataDevice(Test::waylandSeat(), Test::waylandSeat());
|
|
auto dataSource = Test::waylandDataDeviceManager()->createDataSource(Test::waylandSeat());
|
|
dataSource->offer(QStringLiteral("text/plain"));
|
|
dataSource->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Copy | KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
QSignalSpy pointerButtonSpy(pointer, &KWayland::Client::Pointer::buttonStateChanged);
|
|
QSignalSpy dataDeviceDragEnteredSpy(dataDevice, &KWayland::Client::DataDevice::dragEntered);
|
|
QSignalSpy dataDeviceDragLeftSpy(dataDevice, &KWayland::Client::DataDevice::dragLeft);
|
|
QSignalSpy dataDeviceDroppedSpy(dataDevice, &KWayland::Client::DataDevice::dropped);
|
|
QSignalSpy sourceTargetsAcceptsSpy(dataSource, &KWayland::Client::DataSource::targetAccepts);
|
|
QSignalSpy dataSourceSelectedDragAndDropActionChangedSpy(dataSource, &KWayland::Client::DataSource::selectedDragAndDropActionChanged);
|
|
|
|
// Setup the source window.
|
|
auto surface = Test::createSurface();
|
|
auto shellSurface = Test::createXdgToplevelSurface(surface.get());
|
|
auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 100), Qt::red);
|
|
QVERIFY(window);
|
|
window->move(QPointF(0, 0));
|
|
|
|
// Move the pointer to the center of the surface and press the left mouse button.
|
|
quint32 timestamp = 0;
|
|
Test::pointerMotion(QPointF(50, 50), timestamp++);
|
|
Test::pointerButtonPressed(BTN_LEFT, timestamp++);
|
|
QVERIFY(pointerButtonSpy.wait());
|
|
|
|
// Start a drag-and-drop operation.
|
|
dataDevice->startDrag(pointerButtonSpy.last().at(0).value<quint32>(), dataSource, surface.get());
|
|
QVERIFY(dataDeviceDragEnteredSpy.wait());
|
|
|
|
const auto firstOffer = dataDevice->takeDragOffer();
|
|
firstOffer->accept(QString(), dataDeviceDragEnteredSpy.last().at(0).value<quint32>());
|
|
QVERIFY(sourceTargetsAcceptsSpy.wait());
|
|
QCOMPARE(sourceTargetsAcceptsSpy.last().at(0).value<QString>(), QString());
|
|
|
|
firstOffer->accept(QStringLiteral("text/plain"), dataDeviceDragEnteredSpy.last().at(0).value<quint32>());
|
|
QVERIFY(sourceTargetsAcceptsSpy.wait());
|
|
QCOMPARE(sourceTargetsAcceptsSpy.last().at(0).value<QString>(), QStringLiteral("text/plain"));
|
|
|
|
firstOffer->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Move, KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
QVERIFY(dataSourceSelectedDragAndDropActionChangedSpy.wait());
|
|
QCOMPARE(dataSource->selectedDragAndDropAction(), KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
|
|
// Move the pointer outside the surface.
|
|
Test::pointerMotion(QPointF(150, 50), timestamp++);
|
|
QVERIFY(dataDeviceDragLeftSpy.wait());
|
|
|
|
firstOffer->accept(QString(), dataDeviceDragEnteredSpy.last().at(0).value<quint32>());
|
|
QVERIFY(!sourceTargetsAcceptsSpy.wait(10));
|
|
|
|
firstOffer->accept(QStringLiteral("text/plain"), dataDeviceDragEnteredSpy.last().at(0).value<quint32>());
|
|
QVERIFY(!sourceTargetsAcceptsSpy.wait(10));
|
|
|
|
firstOffer->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Copy, KWayland::Client::DataDeviceManager::DnDAction::Copy);
|
|
QVERIFY(!dataSourceSelectedDragAndDropActionChangedSpy.wait(10));
|
|
|
|
// Move the pointer back inside the surface, it will result in a new data offer.
|
|
Test::pointerMotion(QPointF(75, 50), timestamp++);
|
|
QVERIFY(dataDeviceDragEnteredSpy.wait());
|
|
|
|
const auto secondOffer = dataDevice->takeDragOffer();
|
|
secondOffer->accept(QString(), dataDeviceDragEnteredSpy.last().at(0).value<quint32>());
|
|
QVERIFY(sourceTargetsAcceptsSpy.wait());
|
|
QCOMPARE(sourceTargetsAcceptsSpy.last().at(0).value<QString>(), QString());
|
|
|
|
secondOffer->accept(QStringLiteral("text/plain"), dataDeviceDragEnteredSpy.last().at(0).value<quint32>());
|
|
QVERIFY(sourceTargetsAcceptsSpy.wait());
|
|
QCOMPARE(sourceTargetsAcceptsSpy.last().at(0).value<QString>(), QStringLiteral("text/plain"));
|
|
|
|
secondOffer->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Move, KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
QVERIFY(dataSourceSelectedDragAndDropActionChangedSpy.wait());
|
|
QCOMPARE(dataSource->selectedDragAndDropAction(), KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
|
|
// The first offer shouldn't change the accepted mime type or selected action.
|
|
firstOffer->accept(QString(), dataDeviceDragEnteredSpy.last().at(0).value<quint32>());
|
|
QVERIFY(!sourceTargetsAcceptsSpy.wait(10));
|
|
|
|
firstOffer->accept(QStringLiteral("text/plain"), dataDeviceDragEnteredSpy.last().at(0).value<quint32>());
|
|
QVERIFY(!sourceTargetsAcceptsSpy.wait(10));
|
|
|
|
firstOffer->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Copy, KWayland::Client::DataDeviceManager::DnDAction::Copy);
|
|
QVERIFY(!dataSourceSelectedDragAndDropActionChangedSpy.wait(10));
|
|
|
|
// Drop.
|
|
Test::pointerButtonReleased(BTN_LEFT, timestamp++);
|
|
QVERIFY(dataDeviceDroppedSpy.wait());
|
|
|
|
// Finish drag-and-drop.
|
|
QSignalSpy dragAndDropFinishedSpy(dataSource, &KWayland::Client::DataSource::dragAndDropFinished);
|
|
|
|
firstOffer->dragAndDropFinished();
|
|
QVERIFY(!dragAndDropFinishedSpy.wait(10));
|
|
|
|
secondOffer->dragAndDropFinished();
|
|
QVERIFY(dragAndDropFinishedSpy.wait());
|
|
}
|
|
|
|
void DndTest::invalidSerialForPointerDrag()
|
|
{
|
|
// This test verifies that the data source will be canceled if a dnd operation is forbidden.
|
|
|
|
QVERIFY(Test::waitForWaylandPointer());
|
|
|
|
// Setup the data source.
|
|
auto pointer = Test::waylandSeat()->createPointer(Test::waylandSeat());
|
|
auto dataDevice = Test::waylandDataDeviceManager()->getDataDevice(Test::waylandSeat(), Test::waylandSeat());
|
|
auto dataSource = Test::waylandDataDeviceManager()->createDataSource(Test::waylandSeat());
|
|
dataSource->offer(QStringLiteral("text/plain"));
|
|
dataSource->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Copy | KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
|
|
// Setup a window with a subsurface that covers the right half.
|
|
auto surface = Test::createSurface();
|
|
auto shellSurface = Test::createXdgToplevelSurface(surface.get());
|
|
auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 100), Qt::red);
|
|
QVERIFY(window);
|
|
window->move(QPointF(0, 0));
|
|
|
|
// Move the pointer to the center of the subsurface and click the left mouse button.
|
|
QSignalSpy pointerButtonSpy(pointer, &KWayland::Client::Pointer::buttonStateChanged);
|
|
quint32 timestamp = 0;
|
|
Test::pointerMotion(QPointF(75, 50), timestamp++);
|
|
Test::pointerButtonPressed(BTN_LEFT, timestamp++);
|
|
Test::pointerButtonReleased(BTN_LEFT, timestamp++);
|
|
QVERIFY(pointerButtonSpy.wait());
|
|
|
|
// The dnd operation will be rejected because LMB is already released.
|
|
QSignalSpy dataSourceCanceledSpy(dataSource, &KWayland::Client::DataSource::cancelled);
|
|
dataDevice->startDrag(pointerButtonSpy.last().at(0).value<quint32>(), dataSource, surface.get());
|
|
QVERIFY(dataSourceCanceledSpy.wait());
|
|
}
|
|
|
|
void DndTest::cancelPointerDrag()
|
|
{
|
|
// This test verifies that a drag-and-operation will be canceled after pressing the Esc key.
|
|
|
|
QVERIFY(Test::waitForWaylandPointer());
|
|
|
|
// Setup the data source.
|
|
auto pointer = Test::waylandSeat()->createPointer(Test::waylandSeat());
|
|
auto dataDevice = Test::waylandDataDeviceManager()->getDataDevice(Test::waylandSeat(), Test::waylandSeat());
|
|
auto dataSource = Test::waylandDataDeviceManager()->createDataSource(Test::waylandSeat());
|
|
dataSource->offer(QStringLiteral("text/plain"));
|
|
dataSource->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Copy | KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
QSignalSpy pointerButtonSpy(pointer, &KWayland::Client::Pointer::buttonStateChanged);
|
|
QSignalSpy dataDeviceDragEnteredSpy(dataDevice, &KWayland::Client::DataDevice::dragEntered);
|
|
QSignalSpy dataDeviceDragLeftSpy(dataDevice, &KWayland::Client::DataDevice::dragLeft);
|
|
QSignalSpy dataDeviceDroppedSpy(dataDevice, &KWayland::Client::DataDevice::dropped);
|
|
QSignalSpy dataSourceCancelledSpy(dataSource, &KWayland::Client::DataSource::cancelled);
|
|
|
|
// Setup the source window.
|
|
auto surface = Test::createSurface();
|
|
auto shellSurface = Test::createXdgToplevelSurface(surface.get());
|
|
auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 100), Qt::red);
|
|
QVERIFY(window);
|
|
window->move(QPointF(0, 0));
|
|
|
|
// Move the pointer to the center of the surface and press the left mouse button.
|
|
quint32 timestamp = 0;
|
|
Test::pointerMotion(QPointF(50, 50), timestamp++);
|
|
Test::pointerButtonPressed(BTN_LEFT, timestamp++);
|
|
QVERIFY(pointerButtonSpy.wait());
|
|
|
|
// Start a drag-and-drop operation.
|
|
dataDevice->startDrag(pointerButtonSpy.last().at(0).value<quint32>(), dataSource, surface.get());
|
|
QVERIFY(dataDeviceDragEnteredSpy.wait());
|
|
QCOMPARE(dataDeviceDragEnteredSpy.last().at(1).value<QPointF>(), QPointF(50, 50));
|
|
QCOMPARE(dataDevice->dragSurface(), surface.get());
|
|
|
|
// Press the Esc key.
|
|
Test::keyboardKeyPressed(KEY_ESC, timestamp++);
|
|
Test::keyboardKeyReleased(KEY_ESC, timestamp++);
|
|
QVERIFY(dataDeviceDragLeftSpy.wait());
|
|
QCOMPARE(dataSourceCancelledSpy.count(), 1);
|
|
QCOMPARE(dataDeviceDroppedSpy.count(), 0);
|
|
|
|
Test::pointerButtonReleased(BTN_LEFT, timestamp++);
|
|
}
|
|
|
|
void DndTest::destroyPointerDragSource()
|
|
{
|
|
// This test verifies that a drag-and-drop operation will be cancelled when the source is destroyed.
|
|
|
|
QVERIFY(Test::waitForWaylandPointer());
|
|
|
|
// Setup the data source.
|
|
auto pointer = Test::waylandSeat()->createPointer(Test::waylandSeat());
|
|
auto dataDevice = Test::waylandDataDeviceManager()->getDataDevice(Test::waylandSeat(), Test::waylandSeat());
|
|
auto dataSource = Test::waylandDataDeviceManager()->createDataSource(Test::waylandSeat());
|
|
dataSource->offer(QStringLiteral("text/plain"));
|
|
dataSource->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Copy | KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
QSignalSpy pointerButtonSpy(pointer, &KWayland::Client::Pointer::buttonStateChanged);
|
|
QSignalSpy dataDeviceDragEnteredSpy(dataDevice, &KWayland::Client::DataDevice::dragEntered);
|
|
QSignalSpy dataDeviceDragLeftSpy(dataDevice, &KWayland::Client::DataDevice::dragLeft);
|
|
QSignalSpy dataDeviceDroppedSpy(dataDevice, &KWayland::Client::DataDevice::dropped);
|
|
|
|
// Setup the source window.
|
|
auto surface = Test::createSurface();
|
|
auto shellSurface = Test::createXdgToplevelSurface(surface.get());
|
|
auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 100), Qt::red);
|
|
QVERIFY(window);
|
|
window->move(QPointF(0, 0));
|
|
|
|
// Move the pointer to the center of the surface and press the left mouse button.
|
|
quint32 timestamp = 0;
|
|
Test::pointerMotion(QPointF(50, 50), timestamp++);
|
|
Test::pointerButtonPressed(BTN_LEFT, timestamp++);
|
|
QVERIFY(pointerButtonSpy.wait());
|
|
|
|
// Start a drag-and-drop operation.
|
|
dataDevice->startDrag(pointerButtonSpy.last().at(0).value<quint32>(), dataSource, surface.get());
|
|
QVERIFY(dataDeviceDragEnteredSpy.wait());
|
|
QCOMPARE(dataDeviceDragEnteredSpy.last().at(1).value<QPointF>(), QPointF(50, 50));
|
|
QCOMPARE(dataDevice->dragSurface(), surface.get());
|
|
|
|
// Nuke the data source.
|
|
delete dataSource;
|
|
dataSource = nullptr;
|
|
QVERIFY(dataDeviceDragLeftSpy.wait());
|
|
QCOMPARE(dataDeviceDroppedSpy.count(), 0);
|
|
|
|
Test::pointerButtonReleased(BTN_LEFT, timestamp++);
|
|
}
|
|
|
|
void DndTest::noAcceptedMimeTypePointerDrag()
|
|
{
|
|
// This test verifies that a drag-and-drop operation will be canceled if no mime type has been accepted.
|
|
|
|
QVERIFY(Test::waitForWaylandPointer());
|
|
|
|
// Setup the data source.
|
|
auto pointer = Test::waylandSeat()->createPointer(Test::waylandSeat());
|
|
auto dataDevice = Test::waylandDataDeviceManager()->getDataDevice(Test::waylandSeat(), Test::waylandSeat());
|
|
auto dataSource = Test::waylandDataDeviceManager()->createDataSource(Test::waylandSeat());
|
|
dataSource->offer(QStringLiteral("text/plain"));
|
|
dataSource->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Copy | KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
QSignalSpy pointerButtonSpy(pointer, &KWayland::Client::Pointer::buttonStateChanged);
|
|
QSignalSpy dataDeviceDragEnteredSpy(dataDevice, &KWayland::Client::DataDevice::dragEntered);
|
|
QSignalSpy dataDeviceDragLeftSpy(dataDevice, &KWayland::Client::DataDevice::dragLeft);
|
|
QSignalSpy dataDeviceDroppedSpy(dataDevice, &KWayland::Client::DataDevice::dropped);
|
|
QSignalSpy dataSourceCancelledSpy(dataSource, &KWayland::Client::DataSource::cancelled);
|
|
QSignalSpy dataSourceDropPerformedSpy(dataSource, &KWayland::Client::DataSource::dragAndDropPerformed);
|
|
|
|
// Setup the source window.
|
|
auto surface = Test::createSurface();
|
|
auto shellSurface = Test::createXdgToplevelSurface(surface.get());
|
|
auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 100), Qt::red);
|
|
QVERIFY(window);
|
|
window->move(QPointF(0, 0));
|
|
|
|
// Move the pointer to the center of the surface and press the left mouse button.
|
|
quint32 timestamp = 0;
|
|
Test::pointerMotion(QPointF(50, 50), timestamp++);
|
|
Test::pointerButtonPressed(BTN_LEFT, timestamp++);
|
|
QVERIFY(pointerButtonSpy.wait());
|
|
|
|
// Start a drag-and-drop operation.
|
|
dataDevice->startDrag(pointerButtonSpy.last().at(0).value<quint32>(), dataSource, surface.get());
|
|
QVERIFY(dataDeviceDragEnteredSpy.wait());
|
|
QCOMPARE(dataDeviceDragEnteredSpy.last().at(1).value<QPointF>(), QPointF(50, 50));
|
|
QCOMPARE(dataDevice->dragSurface(), surface.get());
|
|
|
|
// Without accepted mime type, the drag should be canceled when the LMB is released.
|
|
QSignalSpy dataSourceSelectedDragAndDropActionChangedSpy(dataSource, &KWayland::Client::DataSource::selectedDragAndDropActionChanged);
|
|
auto offer = dataDevice->takeDragOffer();
|
|
offer->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Copy, KWayland::Client::DataDeviceManager::DnDAction::Copy);
|
|
QVERIFY(dataSourceSelectedDragAndDropActionChangedSpy.wait());
|
|
|
|
// Finish the drag-and-drop operation.
|
|
Test::pointerButtonReleased(BTN_LEFT, timestamp++);
|
|
QVERIFY(dataDeviceDragLeftSpy.wait());
|
|
QCOMPARE(dataDeviceDroppedSpy.count(), 0);
|
|
QCOMPARE(dataSourceCancelledSpy.count(), 1);
|
|
QCOMPARE(dataSourceDropPerformedSpy.count(), 1);
|
|
}
|
|
|
|
void DndTest::secondPointerDrag()
|
|
{
|
|
// This test verifies that no second drag operation will be started while there's another active drag.
|
|
|
|
QVERIFY(Test::waitForWaylandPointer());
|
|
|
|
// Setup the data source.
|
|
auto pointer = Test::waylandSeat()->createPointer(Test::waylandSeat());
|
|
auto dataDevice = Test::waylandDataDeviceManager()->getDataDevice(Test::waylandSeat(), Test::waylandSeat());
|
|
auto dataSource = Test::waylandDataDeviceManager()->createDataSource(Test::waylandSeat());
|
|
dataSource->offer(QStringLiteral("text/plain"));
|
|
dataSource->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Copy | KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
QSignalSpy pointerButtonSpy(pointer, &KWayland::Client::Pointer::buttonStateChanged);
|
|
QSignalSpy dataDeviceDragEnteredSpy(dataDevice, &KWayland::Client::DataDevice::dragEntered);
|
|
QSignalSpy dataDeviceDragLeftSpy(dataDevice, &KWayland::Client::DataDevice::dragLeft);
|
|
QSignalSpy dataDeviceDroppedSpy(dataDevice, &KWayland::Client::DataDevice::dropped);
|
|
QSignalSpy dataSourceCancelledSpy(dataSource, &KWayland::Client::DataSource::cancelled);
|
|
QSignalSpy dataSourceDropPerformedSpy(dataSource, &KWayland::Client::DataSource::dragAndDropPerformed);
|
|
|
|
auto secondDataSource = Test::waylandDataDeviceManager()->createDataSource(Test::waylandSeat());
|
|
secondDataSource->offer(QStringLiteral("text/plain"));
|
|
secondDataSource->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Copy | KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
|
|
// Setup the source window.
|
|
auto surface = Test::createSurface();
|
|
auto shellSurface = Test::createXdgToplevelSurface(surface.get());
|
|
auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 100), Qt::red);
|
|
QVERIFY(window);
|
|
window->move(QPointF(0, 0));
|
|
|
|
// Move the pointer to the center of the surface and press the left mouse button.
|
|
quint32 timestamp = 0;
|
|
Test::pointerMotion(QPointF(50, 50), timestamp++);
|
|
Test::pointerButtonPressed(BTN_LEFT, timestamp++);
|
|
QVERIFY(pointerButtonSpy.wait());
|
|
|
|
// Start a drag-and-drop operation.
|
|
dataDevice->startDrag(pointerButtonSpy.last().at(0).value<quint32>(), dataSource, surface.get());
|
|
QVERIFY(dataDeviceDragEnteredSpy.wait());
|
|
QCOMPARE(dataDeviceDragEnteredSpy.last().at(1).value<QPointF>(), QPointF(50, 50));
|
|
QCOMPARE(dataDevice->dragSurface(), surface.get());
|
|
|
|
// Attempt to start another drag-and-drop operation.
|
|
QSignalSpy secondDataSourceCancelledSpy(secondDataSource, &KWayland::Client::DataSource::cancelled);
|
|
dataDevice->startDrag(pointerButtonSpy.last().at(0).value<quint32>(), secondDataSource, surface.get());
|
|
QVERIFY(secondDataSourceCancelledSpy.wait());
|
|
QCOMPARE(dataSourceCancelledSpy.count(), 0);
|
|
|
|
// Finish the drag-and-drop operation.
|
|
Test::pointerButtonReleased(BTN_LEFT, timestamp++);
|
|
QVERIFY(dataDeviceDragLeftSpy.wait());
|
|
QCOMPARE(dataDeviceDroppedSpy.count(), 0);
|
|
QCOMPARE(dataSourceCancelledSpy.count(), 1);
|
|
QCOMPARE(dataSourceDropPerformedSpy.count(), 1);
|
|
}
|
|
|
|
void DndTest::touchDrag()
|
|
{
|
|
// This test verifies that a simple touch drag-and-drop operation between two Wayland surfaces works as expected.
|
|
|
|
QVERIFY(Test::waitForWaylandTouch());
|
|
|
|
// Setup the data source.
|
|
auto touch = Test::waylandSeat()->createTouch(Test::waylandSeat());
|
|
auto dataDevice = Test::waylandDataDeviceManager()->getDataDevice(Test::waylandSeat(), Test::waylandSeat());
|
|
auto dataSource = Test::waylandDataDeviceManager()->createDataSource(Test::waylandSeat());
|
|
dataSource->offer(QStringLiteral("text/plain"));
|
|
dataSource->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Copy | KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
connect(dataSource, &KWayland::Client::DataSource::sendDataRequested, this, [](const QString &mimeType, int fd) {
|
|
if (mimeType == QLatin1String("text/plain")) {
|
|
const auto payload = QByteArrayLiteral("foo");
|
|
write(fd, payload.data(), payload.size());
|
|
}
|
|
close(fd);
|
|
});
|
|
QSignalSpy touchSequenceStartedSpy(touch, &KWayland::Client::Touch::sequenceStarted);
|
|
QSignalSpy touchSequenceEndedSpy(touch, &KWayland::Client::Touch::sequenceEnded);
|
|
QSignalSpy dataDeviceDragEnteredSpy(dataDevice, &KWayland::Client::DataDevice::dragEntered);
|
|
QSignalSpy dataDeviceDragLeftSpy(dataDevice, &KWayland::Client::DataDevice::dragLeft);
|
|
QSignalSpy dataDeviceDragMotionSpy(dataDevice, &KWayland::Client::DataDevice::dragMotion);
|
|
QSignalSpy dataDeviceDroppedSpy(dataDevice, &KWayland::Client::DataDevice::dropped);
|
|
|
|
// Setup the source client.
|
|
auto sourceSurface = Test::createSurface();
|
|
auto sourceShellSurface = Test::createXdgToplevelSurface(sourceSurface.get());
|
|
auto sourceWindow = Test::renderAndWaitForShown(sourceSurface.get(), QSize(100, 100), Qt::red);
|
|
QVERIFY(sourceWindow);
|
|
sourceWindow->move(QPointF(0, 0));
|
|
|
|
// Setup the target client.
|
|
auto targetSurface = Test::createSurface();
|
|
auto targetShellSurface = Test::createXdgToplevelSurface(targetSurface.get());
|
|
auto targetWindow = Test::renderAndWaitForShown(targetSurface.get(), QSize(100, 100), Qt::red);
|
|
QVERIFY(targetWindow);
|
|
targetWindow->move(QPointF(100, 0));
|
|
|
|
// Tap the source surface.
|
|
quint32 timestamp = 0;
|
|
Test::touchDown(0, QPointF(50, 50), timestamp++);
|
|
QVERIFY(touchSequenceStartedSpy.wait());
|
|
|
|
// Start a drag-and-drop operation.
|
|
dataDevice->startDrag(touchSequenceStartedSpy.last().at(0).value<KWayland::Client::TouchPoint *>()->downSerial(), dataSource, sourceSurface.get());
|
|
QVERIFY(dataDeviceDragEnteredSpy.wait());
|
|
QCOMPARE(dataDeviceDragEnteredSpy.last().at(1).value<QPointF>(), QPointF(50, 50));
|
|
QCOMPARE(dataDevice->dragSurface(), sourceSurface.get());
|
|
|
|
QCOMPARE(dataDeviceDragEnteredSpy.count(), 1);
|
|
QCOMPARE(dataDeviceDragMotionSpy.count(), 0);
|
|
QCOMPARE(dataDeviceDragLeftSpy.count(), 0);
|
|
QCOMPARE(dataDeviceDroppedSpy.count(), 0);
|
|
|
|
// Accept our own offer.
|
|
auto sourceOffer = dataDevice->takeDragOffer();
|
|
QCOMPARE(sourceOffer->offeredMimeTypes(), (QList<QMimeType>{m_mimeDb.mimeTypeForName(QStringLiteral("text/plain"))}));
|
|
QCOMPARE(sourceOffer->sourceDragAndDropActions(), KWayland::Client::DataDeviceManager::DnDAction::Copy | KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
QCOMPARE(sourceOffer->selectedDragAndDropAction(), KWayland::Client::DataDeviceManager::DnDAction::None);
|
|
|
|
QSignalSpy sourceTargetsAcceptsSpy(dataSource, &KWayland::Client::DataSource::targetAccepts);
|
|
sourceOffer->accept(QString(), dataDeviceDragEnteredSpy.last().at(0).value<quint32>());
|
|
QVERIFY(sourceTargetsAcceptsSpy.wait());
|
|
QCOMPARE(sourceTargetsAcceptsSpy.last().at(0).value<QString>(), QString());
|
|
|
|
sourceOffer->accept(QStringLiteral("text/plain"), dataDeviceDragEnteredSpy.last().at(0).value<quint32>());
|
|
QVERIFY(sourceTargetsAcceptsSpy.wait());
|
|
QCOMPARE(sourceTargetsAcceptsSpy.last().at(0).value<QString>(), QStringLiteral("text/plain"));
|
|
|
|
QSignalSpy sourceOfferSelectedDragAndDropActionSpy(sourceOffer.get(), &KWayland::Client::DataOffer::selectedDragAndDropActionChanged);
|
|
sourceOffer->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Copy, KWayland::Client::DataDeviceManager::DnDAction::Copy);
|
|
QVERIFY(sourceOfferSelectedDragAndDropActionSpy.wait());
|
|
QCOMPARE(sourceOffer->selectedDragAndDropAction(), KWayland::Client::DataDeviceManager::DnDAction::Copy);
|
|
QCOMPARE(dataSource->selectedDragAndDropAction(), KWayland::Client::DataDeviceManager::DnDAction::Copy);
|
|
|
|
// Move the finger a little bit.
|
|
Test::touchMotion(0, QPointF(60, 50), timestamp++);
|
|
QVERIFY(dataDeviceDragMotionSpy.wait());
|
|
QCOMPARE(dataDeviceDragMotionSpy.last().at(0).value<QPointF>(), QPointF(60, 50));
|
|
|
|
QCOMPARE(dataDeviceDragEnteredSpy.count(), 1);
|
|
QCOMPARE(dataDeviceDragMotionSpy.count(), 1);
|
|
QCOMPARE(dataDeviceDragLeftSpy.count(), 0);
|
|
QCOMPARE(dataDeviceDroppedSpy.count(), 0);
|
|
|
|
// No drag motion event will be sent if an unrelated finger moves.
|
|
Test::touchDown(1, QPointF(40, 40), timestamp++);
|
|
Test::touchMotion(1, QPointF(45, 45), timestamp++);
|
|
Test::touchUp(1, timestamp++);
|
|
QVERIFY(!dataDeviceDragMotionSpy.wait(10));
|
|
|
|
// Move the finger to the target surface.
|
|
Test::touchMotion(0, QPointF(140, 50), timestamp++);
|
|
QVERIFY(dataDeviceDragEnteredSpy.wait());
|
|
QCOMPARE(dataDeviceDragEnteredSpy.last().at(1).value<QPointF>(), QPointF(40, 50));
|
|
QCOMPARE(dataDeviceDragLeftSpy.count(), 1);
|
|
QCOMPARE(dataDevice->dragSurface(), targetSurface.get());
|
|
|
|
QCOMPARE(dataDeviceDragEnteredSpy.count(), 2);
|
|
QCOMPARE(dataDeviceDragMotionSpy.count(), 1);
|
|
QCOMPARE(dataDeviceDragLeftSpy.count(), 1);
|
|
QCOMPARE(dataDeviceDroppedSpy.count(), 0);
|
|
|
|
// Accept the offer.
|
|
auto targetOffer = dataDevice->takeDragOffer();
|
|
QCOMPARE(targetOffer->offeredMimeTypes(), (QList<QMimeType>{m_mimeDb.mimeTypeForName(QStringLiteral("text/plain"))}));
|
|
QCOMPARE(targetOffer->sourceDragAndDropActions(), KWayland::Client::DataDeviceManager::DnDAction::Copy | KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
QCOMPARE(targetOffer->selectedDragAndDropAction(), KWayland::Client::DataDeviceManager::DnDAction::None);
|
|
|
|
targetOffer->accept(QString(), dataDeviceDragEnteredSpy.last().at(0).value<quint32>());
|
|
QVERIFY(sourceTargetsAcceptsSpy.wait());
|
|
QCOMPARE(sourceTargetsAcceptsSpy.last().at(0).value<QString>(), QString());
|
|
|
|
targetOffer->accept(QStringLiteral("text/plain"), dataDeviceDragEnteredSpy.last().at(0).value<quint32>());
|
|
QVERIFY(sourceTargetsAcceptsSpy.wait());
|
|
QCOMPARE(sourceTargetsAcceptsSpy.last().at(0).value<QString>(), QStringLiteral("text/plain"));
|
|
|
|
QSignalSpy targetOfferSelectedDragAndDropActionSpy(targetOffer.get(), &KWayland::Client::DataOffer::selectedDragAndDropActionChanged);
|
|
targetOffer->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Move, KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
QVERIFY(targetOfferSelectedDragAndDropActionSpy.wait());
|
|
QCOMPARE(targetOffer->selectedDragAndDropAction(), KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
QCOMPARE(dataSource->selectedDragAndDropAction(), KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
|
|
// Move the finger a little bit.
|
|
Test::touchMotion(0, QPointF(160, 50), timestamp++);
|
|
QVERIFY(dataDeviceDragMotionSpy.wait());
|
|
QCOMPARE(dataDeviceDragMotionSpy.last().at(0).value<QPointF>(), QPointF(60, 50));
|
|
|
|
QCOMPARE(dataDeviceDragEnteredSpy.count(), 2);
|
|
QCOMPARE(dataDeviceDragMotionSpy.count(), 2);
|
|
QCOMPARE(dataDeviceDragLeftSpy.count(), 1);
|
|
QCOMPARE(dataDeviceDroppedSpy.count(), 0);
|
|
|
|
// Drop.
|
|
Test::touchUp(0, timestamp++);
|
|
QVERIFY(touchSequenceEndedSpy.wait());
|
|
|
|
QCOMPARE(dataDeviceDragEnteredSpy.count(), 2);
|
|
QCOMPARE(dataDeviceDragMotionSpy.count(), 2);
|
|
QCOMPARE(dataDeviceDragLeftSpy.count(), 2);
|
|
QCOMPARE(dataDeviceDroppedSpy.count(), 1);
|
|
|
|
// Ask for data.
|
|
const QFuture<QByteArray> plainData = readMimeTypeData(targetOffer.get(), QStringLiteral("text/plain"));
|
|
QVERIFY(waitFuture(plainData));
|
|
QCOMPARE(plainData.result(), QByteArrayLiteral("foo"));
|
|
|
|
// text/html hasn't been offered, so this should fail.
|
|
const QFuture<QByteArray> htmlData = readMimeTypeData(targetOffer.get(), QStringLiteral("text/html"));
|
|
QVERIFY(waitFuture(htmlData));
|
|
QCOMPARE(htmlData.result(), QByteArray());
|
|
|
|
// Finish drag-and-drop.
|
|
QSignalSpy dragAndDropFinishedSpy(dataSource, &KWayland::Client::DataSource::dragAndDropFinished);
|
|
targetOffer->dragAndDropFinished();
|
|
QVERIFY(dragAndDropFinishedSpy.wait());
|
|
}
|
|
|
|
void DndTest::touchSubSurfaceDrag()
|
|
{
|
|
// This test verifies that a drag-and-drop originating from a sub-surface works as expected.
|
|
|
|
QVERIFY(Test::waitForWaylandTouch());
|
|
|
|
// Setup the data source.
|
|
auto touch = Test::waylandSeat()->createTouch(Test::waylandSeat());
|
|
auto dataDevice = Test::waylandDataDeviceManager()->getDataDevice(Test::waylandSeat(), Test::waylandSeat());
|
|
auto dataSource = Test::waylandDataDeviceManager()->createDataSource(Test::waylandSeat());
|
|
dataSource->offer(QStringLiteral("text/plain"));
|
|
dataSource->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Copy | KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
QSignalSpy touchSequenceStartedSpy(touch, &KWayland::Client::Touch::sequenceStarted);
|
|
QSignalSpy touchSequenceEndedSpy(touch, &KWayland::Client::Touch::sequenceEnded);
|
|
QSignalSpy dataDeviceDragEnteredSpy(dataDevice, &KWayland::Client::DataDevice::dragEntered);
|
|
QSignalSpy dataDeviceDragLeftSpy(dataDevice, &KWayland::Client::DataDevice::dragLeft);
|
|
QSignalSpy dataDeviceDragMotionSpy(dataDevice, &KWayland::Client::DataDevice::dragMotion);
|
|
QSignalSpy dataDeviceDroppedSpy(dataDevice, &KWayland::Client::DataDevice::dropped);
|
|
|
|
// Setup a window with a subsurface that covers the right half.
|
|
auto parentSurface = Test::createSurface();
|
|
auto parentShellSurface = Test::createXdgToplevelSurface(parentSurface.get());
|
|
auto window = Test::renderAndWaitForShown(parentSurface.get(), QSize(100, 100), Qt::red);
|
|
QVERIFY(window);
|
|
window->move(QPointF(0, 0));
|
|
|
|
auto childSurface = Test::createSurface();
|
|
auto subSurface = Test::createSubSurface(childSurface.get(), parentSurface.get());
|
|
subSurface->setPosition(QPoint(50, 0));
|
|
Test::render(childSurface.get(), QSize(50, 100), Qt::blue);
|
|
parentSurface->commit(KWayland::Client::Surface::CommitFlag::None);
|
|
|
|
// Tap the child surface.
|
|
quint32 timestamp = 0;
|
|
Test::touchDown(0, QPointF(75, 50), timestamp++);
|
|
QVERIFY(touchSequenceStartedSpy.wait());
|
|
|
|
// Start a drag-and-drop operation.
|
|
dataDevice->startDrag(touchSequenceStartedSpy.last().at(0).value<KWayland::Client::TouchPoint *>()->downSerial(), dataSource, childSurface.get());
|
|
QVERIFY(dataDeviceDragEnteredSpy.wait());
|
|
QCOMPARE(dataDeviceDragEnteredSpy.last().at(1).value<QPointF>(), QPointF(25, 50));
|
|
QCOMPARE(dataDevice->dragSurface(), childSurface.get());
|
|
|
|
// Move the finger to the parent surface.
|
|
Test::touchMotion(0, QPointF(10, 50), timestamp++);
|
|
QVERIFY(dataDeviceDragEnteredSpy.wait());
|
|
QCOMPARE(dataDeviceDragEnteredSpy.count(), 2);
|
|
QCOMPARE(dataDeviceDragLeftSpy.count(), 1);
|
|
QCOMPARE(dataDeviceDragEnteredSpy.last().at(1).value<QPointF>(), QPointF(10, 50));
|
|
QCOMPARE(dataDevice->dragSurface(), parentSurface.get());
|
|
|
|
Test::touchMotion(0, QPointF(15, 50), timestamp++);
|
|
QVERIFY(dataDeviceDragMotionSpy.wait());
|
|
QCOMPARE(dataDeviceDragMotionSpy.last().at(0).value<QPointF>(), QPointF(15, 50));
|
|
QCOMPARE(dataDeviceDragEnteredSpy.count(), 2);
|
|
QCOMPARE(dataDeviceDragLeftSpy.count(), 1);
|
|
QCOMPARE(dataDevice->dragSurface(), parentSurface.get());
|
|
|
|
// Move the finger to the child surface.
|
|
Test::touchMotion(0, QPointF(80, 50), timestamp++);
|
|
QVERIFY(dataDeviceDragEnteredSpy.wait());
|
|
QCOMPARE(dataDeviceDragEnteredSpy.count(), 3);
|
|
QCOMPARE(dataDeviceDragLeftSpy.count(), 2);
|
|
QCOMPARE(dataDeviceDragEnteredSpy.last().at(1).value<QPointF>(), QPointF(30, 50));
|
|
QCOMPARE(dataDevice->dragSurface(), childSurface.get());
|
|
|
|
Test::touchMotion(0, QPointF(75, 50), timestamp++);
|
|
QVERIFY(dataDeviceDragMotionSpy.wait());
|
|
QCOMPARE(dataDeviceDragMotionSpy.last().at(0).value<QPointF>(), QPointF(25, 50));
|
|
QCOMPARE(dataDeviceDragEnteredSpy.count(), 3);
|
|
QCOMPARE(dataDeviceDragLeftSpy.count(), 2);
|
|
QCOMPARE(dataDevice->dragSurface(), childSurface.get());
|
|
|
|
// Accept the data offer.
|
|
auto offer = dataDevice->takeDragOffer();
|
|
QCOMPARE(offer->offeredMimeTypes(), (QList<QMimeType>{m_mimeDb.mimeTypeForName(QStringLiteral("text/plain"))}));
|
|
QCOMPARE(offer->sourceDragAndDropActions(), KWayland::Client::DataDeviceManager::DnDAction::Copy | KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
QCOMPARE(offer->selectedDragAndDropAction(), KWayland::Client::DataDeviceManager::DnDAction::None);
|
|
|
|
QSignalSpy sourceTargetsAcceptsSpy(dataSource, &KWayland::Client::DataSource::targetAccepts);
|
|
offer->accept(QString(), dataDeviceDragEnteredSpy.last().at(0).value<quint32>());
|
|
QVERIFY(sourceTargetsAcceptsSpy.wait());
|
|
QCOMPARE(sourceTargetsAcceptsSpy.last().at(0).value<QString>(), QString());
|
|
|
|
offer->accept(QStringLiteral("text/plain"), dataDeviceDragEnteredSpy.last().at(0).value<quint32>());
|
|
QVERIFY(sourceTargetsAcceptsSpy.wait());
|
|
QCOMPARE(sourceTargetsAcceptsSpy.last().at(0).value<QString>(), QStringLiteral("text/plain"));
|
|
|
|
QSignalSpy targetOfferSelectedDragAndDropActionSpy(offer.get(), &KWayland::Client::DataOffer::selectedDragAndDropActionChanged);
|
|
offer->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Move, KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
QVERIFY(targetOfferSelectedDragAndDropActionSpy.wait());
|
|
QCOMPARE(offer->selectedDragAndDropAction(), KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
QCOMPARE(dataSource->selectedDragAndDropAction(), KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
|
|
// Drop.
|
|
Test::touchUp(0, timestamp++);
|
|
QVERIFY(touchSequenceEndedSpy.wait());
|
|
QCOMPARE(dataDeviceDroppedSpy.count(), 1);
|
|
|
|
// Finish drag-and-drop.
|
|
QSignalSpy dragAndDropFinishedSpy(dataSource, &KWayland::Client::DataSource::dragAndDropFinished);
|
|
offer->dragAndDropFinished();
|
|
QVERIFY(dragAndDropFinishedSpy.wait());
|
|
}
|
|
|
|
void DndTest::touchDragAction_data()
|
|
{
|
|
QTest::addColumn<KWayland::Client::DataDeviceManager::DnDAction>("action");
|
|
|
|
QTest::addRow("ask") << KWayland::Client::DataDeviceManager::DnDAction::Ask;
|
|
QTest::addRow("copy") << KWayland::Client::DataDeviceManager::DnDAction::Copy;
|
|
QTest::addRow("move") << KWayland::Client::DataDeviceManager::DnDAction::Move;
|
|
}
|
|
|
|
void DndTest::touchDragAction()
|
|
{
|
|
// This test verifies that the dnd actions are handled as expected. "Ask" action is a bit
|
|
// special; if it has been selected, then the final action can be set after the drop.
|
|
|
|
QVERIFY(Test::waitForWaylandTouch());
|
|
QFETCH(KWayland::Client::DataDeviceManager::DnDAction, action);
|
|
|
|
// Setup the data source.
|
|
auto touch = Test::waylandSeat()->createTouch(Test::waylandSeat());
|
|
auto dataDevice = Test::waylandDataDeviceManager()->getDataDevice(Test::waylandSeat(), Test::waylandSeat());
|
|
auto dataSource = Test::waylandDataDeviceManager()->createDataSource(Test::waylandSeat());
|
|
dataSource->offer(QStringLiteral("text/plain"));
|
|
dataSource->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Copy | KWayland::Client::DataDeviceManager::DnDAction::Move | KWayland::Client::DataDeviceManager::DnDAction::Ask);
|
|
QSignalSpy touchSequenceStartedSpy(touch, &KWayland::Client::Touch::sequenceStarted);
|
|
QSignalSpy dataDeviceDragEnteredSpy(dataDevice, &KWayland::Client::DataDevice::dragEntered);
|
|
QSignalSpy dataDeviceDroppedSpy(dataDevice, &KWayland::Client::DataDevice::dropped);
|
|
QSignalSpy dataSourceSelectedDragAndDropActionChangedSpy(dataSource, &KWayland::Client::DataSource::selectedDragAndDropActionChanged);
|
|
|
|
// Setup the source window.
|
|
auto surface = Test::createSurface();
|
|
auto shellSurface = Test::createXdgToplevelSurface(surface.get());
|
|
auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 100), Qt::red);
|
|
QVERIFY(window);
|
|
window->move(QPointF(0, 0));
|
|
|
|
// Touch the surface in the center.
|
|
quint32 timestamp = 0;
|
|
Test::touchDown(0, QPointF(50, 50), timestamp++);
|
|
QVERIFY(touchSequenceStartedSpy.wait());
|
|
|
|
// Start a drag-and-drop operation.
|
|
dataDevice->startDrag(touchSequenceStartedSpy.last().at(0).value<KWayland::Client::TouchPoint *>()->downSerial(), dataSource, surface.get());
|
|
QVERIFY(dataDeviceDragEnteredSpy.wait());
|
|
|
|
const auto offer = dataDevice->takeDragOffer();
|
|
offer->accept(QStringLiteral("text/plain"), dataDeviceDragEnteredSpy.last().at(0).value<quint32>());
|
|
offer->setDragAndDropActions(action, action);
|
|
QVERIFY(dataSourceSelectedDragAndDropActionChangedSpy.wait());
|
|
QCOMPARE(dataSourceSelectedDragAndDropActionChangedSpy.count(), 1);
|
|
QCOMPARE(dataSource->selectedDragAndDropAction(), action);
|
|
|
|
// Drop.
|
|
Test::touchUp(0, timestamp++);
|
|
QVERIFY(dataDeviceDroppedSpy.wait());
|
|
QCOMPARE(dataSourceSelectedDragAndDropActionChangedSpy.count(), 1);
|
|
|
|
// Change action after drop.
|
|
switch (action) {
|
|
case KWayland::Client::DataDeviceManager::DnDAction::Ask:
|
|
offer->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Copy, KWayland::Client::DataDeviceManager::DnDAction::Copy);
|
|
QVERIFY(dataSourceSelectedDragAndDropActionChangedSpy.wait());
|
|
QCOMPARE(dataSourceSelectedDragAndDropActionChangedSpy.count(), 2);
|
|
QCOMPARE(dataSource->selectedDragAndDropAction(), KWayland::Client::DataDeviceManager::DnDAction::Copy);
|
|
break;
|
|
case KWayland::Client::DataDeviceManager::DnDAction::Copy:
|
|
case KWayland::Client::DataDeviceManager::DnDAction::Move: {
|
|
const auto oppositeAction = action == KWayland::Client::DataDeviceManager::DnDAction::Copy
|
|
? KWayland::Client::DataDeviceManager::DnDAction::Move
|
|
: KWayland::Client::DataDeviceManager::DnDAction::Copy;
|
|
offer->setDragAndDropActions(oppositeAction, oppositeAction);
|
|
QVERIFY(!dataSourceSelectedDragAndDropActionChangedSpy.wait(10));
|
|
break;
|
|
}
|
|
case KWayland::Client::DataDeviceManager::DnDAction::None:
|
|
Q_UNREACHABLE();
|
|
}
|
|
|
|
// Finish drag-and-drop.
|
|
QSignalSpy dragAndDropFinishedSpy(dataSource, &KWayland::Client::DataSource::dragAndDropFinished);
|
|
offer->dragAndDropFinished();
|
|
QVERIFY(dragAndDropFinishedSpy.wait());
|
|
}
|
|
|
|
void DndTest::inertTouchDragOffer()
|
|
{
|
|
// This test verifies that requests from a previous data offer don't affect the current drag-and-drop operation status.
|
|
|
|
QVERIFY(Test::waitForWaylandTouch());
|
|
|
|
// Setup the data source.
|
|
auto touch = Test::waylandSeat()->createTouch(Test::waylandSeat());
|
|
auto dataDevice = Test::waylandDataDeviceManager()->getDataDevice(Test::waylandSeat(), Test::waylandSeat());
|
|
auto dataSource = Test::waylandDataDeviceManager()->createDataSource(Test::waylandSeat());
|
|
dataSource->offer(QStringLiteral("text/plain"));
|
|
dataSource->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Copy | KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
QSignalSpy touchSequenceStartedSpy(touch, &KWayland::Client::Touch::sequenceStarted);
|
|
QSignalSpy dataDeviceDragEnteredSpy(dataDevice, &KWayland::Client::DataDevice::dragEntered);
|
|
QSignalSpy dataDeviceDragLeftSpy(dataDevice, &KWayland::Client::DataDevice::dragLeft);
|
|
QSignalSpy dataDeviceDroppedSpy(dataDevice, &KWayland::Client::DataDevice::dropped);
|
|
QSignalSpy sourceTargetsAcceptsSpy(dataSource, &KWayland::Client::DataSource::targetAccepts);
|
|
QSignalSpy dataSourceSelectedDragAndDropActionChangedSpy(dataSource, &KWayland::Client::DataSource::selectedDragAndDropActionChanged);
|
|
|
|
// Setup the source window.
|
|
auto surface = Test::createSurface();
|
|
auto shellSurface = Test::createXdgToplevelSurface(surface.get());
|
|
auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 100), Qt::red);
|
|
QVERIFY(window);
|
|
window->move(QPointF(0, 0));
|
|
|
|
// Tap the surface in the center.
|
|
quint32 timestamp = 0;
|
|
Test::touchDown(0, QPointF(50, 50), timestamp++);
|
|
QVERIFY(touchSequenceStartedSpy.wait());
|
|
|
|
// Start a drag-and-drop operation.
|
|
dataDevice->startDrag(touchSequenceStartedSpy.last().at(0).value<KWayland::Client::TouchPoint *>()->downSerial(), dataSource, surface.get());
|
|
QVERIFY(dataDeviceDragEnteredSpy.wait());
|
|
|
|
const auto firstOffer = dataDevice->takeDragOffer();
|
|
firstOffer->accept(QString(), dataDeviceDragEnteredSpy.last().at(0).value<quint32>());
|
|
QVERIFY(sourceTargetsAcceptsSpy.wait());
|
|
QCOMPARE(sourceTargetsAcceptsSpy.last().at(0).value<QString>(), QString());
|
|
|
|
firstOffer->accept(QStringLiteral("text/plain"), dataDeviceDragEnteredSpy.last().at(0).value<quint32>());
|
|
QVERIFY(sourceTargetsAcceptsSpy.wait());
|
|
QCOMPARE(sourceTargetsAcceptsSpy.last().at(0).value<QString>(), QStringLiteral("text/plain"));
|
|
|
|
firstOffer->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Move, KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
QVERIFY(dataSourceSelectedDragAndDropActionChangedSpy.wait());
|
|
QCOMPARE(dataSource->selectedDragAndDropAction(), KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
|
|
// Move the finger outside the surface.
|
|
Test::touchMotion(0, QPointF(150, 50), timestamp++);
|
|
QVERIFY(dataDeviceDragLeftSpy.wait());
|
|
|
|
firstOffer->accept(QString(), dataDeviceDragEnteredSpy.last().at(0).value<quint32>());
|
|
QVERIFY(!sourceTargetsAcceptsSpy.wait(10));
|
|
|
|
firstOffer->accept(QStringLiteral("text/plain"), dataDeviceDragEnteredSpy.last().at(0).value<quint32>());
|
|
QVERIFY(!sourceTargetsAcceptsSpy.wait(10));
|
|
|
|
firstOffer->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Copy, KWayland::Client::DataDeviceManager::DnDAction::Copy);
|
|
QVERIFY(!dataSourceSelectedDragAndDropActionChangedSpy.wait(10));
|
|
|
|
// Move the finger back inside the surface, it will result in a new data offer.
|
|
Test::touchMotion(0, QPointF(75, 50), timestamp++);
|
|
QVERIFY(dataDeviceDragEnteredSpy.wait());
|
|
|
|
const auto secondOffer = dataDevice->takeDragOffer();
|
|
secondOffer->accept(QString(), dataDeviceDragEnteredSpy.last().at(0).value<quint32>());
|
|
QVERIFY(sourceTargetsAcceptsSpy.wait());
|
|
QCOMPARE(sourceTargetsAcceptsSpy.last().at(0).value<QString>(), QString());
|
|
|
|
secondOffer->accept(QStringLiteral("text/plain"), dataDeviceDragEnteredSpy.last().at(0).value<quint32>());
|
|
QVERIFY(sourceTargetsAcceptsSpy.wait());
|
|
QCOMPARE(sourceTargetsAcceptsSpy.last().at(0).value<QString>(), QStringLiteral("text/plain"));
|
|
|
|
secondOffer->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Move, KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
QVERIFY(dataSourceSelectedDragAndDropActionChangedSpy.wait());
|
|
QCOMPARE(dataSource->selectedDragAndDropAction(), KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
|
|
// The first offer shouldn't change the accepted mime type or selected action.
|
|
|
|
firstOffer->accept(QString(), dataDeviceDragEnteredSpy.last().at(0).value<quint32>());
|
|
QVERIFY(!sourceTargetsAcceptsSpy.wait(10));
|
|
|
|
firstOffer->accept(QStringLiteral("text/plain"), dataDeviceDragEnteredSpy.last().at(0).value<quint32>());
|
|
QVERIFY(!sourceTargetsAcceptsSpy.wait(10));
|
|
|
|
firstOffer->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Copy, KWayland::Client::DataDeviceManager::DnDAction::Copy);
|
|
QVERIFY(!dataSourceSelectedDragAndDropActionChangedSpy.wait(10));
|
|
|
|
// Drop.
|
|
Test::touchUp(0, timestamp++);
|
|
QVERIFY(dataDeviceDroppedSpy.wait());
|
|
|
|
// Finish drag-and-drop.
|
|
QSignalSpy dragAndDropFinishedSpy(dataSource, &KWayland::Client::DataSource::dragAndDropFinished);
|
|
|
|
firstOffer->dragAndDropFinished();
|
|
QVERIFY(!dragAndDropFinishedSpy.wait(10));
|
|
|
|
secondOffer->dragAndDropFinished();
|
|
QVERIFY(dragAndDropFinishedSpy.wait());
|
|
}
|
|
|
|
void DndTest::invalidSerialForTouchDrag()
|
|
{
|
|
// This test verifies that the data source will be canceled if a dnd operation is forbidden.
|
|
|
|
QVERIFY(Test::waitForWaylandTouch());
|
|
|
|
// Setup the data source.
|
|
auto touch = Test::waylandSeat()->createTouch(Test::waylandSeat());
|
|
auto dataDevice = Test::waylandDataDeviceManager()->getDataDevice(Test::waylandSeat(), Test::waylandSeat());
|
|
auto dataSource = Test::waylandDataDeviceManager()->createDataSource(Test::waylandSeat());
|
|
dataSource->offer(QStringLiteral("text/plain"));
|
|
dataSource->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Copy | KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
|
|
// Setup a window with a subsurface that covers the right half.
|
|
auto surface = Test::createSurface();
|
|
auto shellSurface = Test::createXdgToplevelSurface(surface.get());
|
|
auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 100), Qt::red);
|
|
QVERIFY(window);
|
|
window->move(QPointF(0, 0));
|
|
|
|
// Tap the surface.
|
|
QSignalSpy touchSequenceStartedSpy(touch, &KWayland::Client::Touch::sequenceStarted);
|
|
quint32 timestamp = 0;
|
|
Test::touchDown(0, QPointF(75, 50), timestamp++);
|
|
Test::touchUp(0, timestamp++);
|
|
QVERIFY(touchSequenceStartedSpy.wait());
|
|
|
|
// The dnd operation will be rejected because the touch point is already released.
|
|
QSignalSpy dataSourceCanceledSpy(dataSource, &KWayland::Client::DataSource::cancelled);
|
|
dataDevice->startDrag(touchSequenceStartedSpy.last().at(0).value<KWayland::Client::TouchPoint *>()->downSerial(), dataSource, surface.get());
|
|
QVERIFY(dataSourceCanceledSpy.wait());
|
|
}
|
|
|
|
void DndTest::noAcceptedMimeTypeTouchDrag()
|
|
{
|
|
// This test verifies that a drag-and-drop operation will be cancelled if no mime type is accepted.
|
|
|
|
QVERIFY(Test::waitForWaylandTouch());
|
|
|
|
// Setup the data source.
|
|
auto touch = Test::waylandSeat()->createTouch(Test::waylandSeat());
|
|
auto dataDevice = Test::waylandDataDeviceManager()->getDataDevice(Test::waylandSeat(), Test::waylandSeat());
|
|
auto dataSource = Test::waylandDataDeviceManager()->createDataSource(Test::waylandSeat());
|
|
dataSource->offer(QStringLiteral("text/plain"));
|
|
dataSource->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Copy | KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
QSignalSpy touchSequenceStartedSpy(touch, &KWayland::Client::Touch::sequenceStarted);
|
|
QSignalSpy dataDeviceDragEnteredSpy(dataDevice, &KWayland::Client::DataDevice::dragEntered);
|
|
QSignalSpy dataDeviceDragLeftSpy(dataDevice, &KWayland::Client::DataDevice::dragLeft);
|
|
QSignalSpy dataDeviceDragMotionSpy(dataDevice, &KWayland::Client::DataDevice::dragMotion);
|
|
QSignalSpy dataDeviceDroppedSpy(dataDevice, &KWayland::Client::DataDevice::dropped);
|
|
QSignalSpy dataSourceCancelledSpy(dataSource, &KWayland::Client::DataSource::cancelled);
|
|
QSignalSpy dataSourceDropPerformedSpy(dataSource, &KWayland::Client::DataSource::dragAndDropPerformed);
|
|
|
|
// Create a surface.
|
|
auto surface = Test::createSurface();
|
|
auto shellSurface = Test::createXdgToplevelSurface(surface.get());
|
|
auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 100), Qt::red);
|
|
QVERIFY(window);
|
|
window->move(QPointF(0, 0));
|
|
|
|
// Tap the surface.
|
|
quint32 timestamp = 0;
|
|
Test::touchDown(0, QPointF(50, 50), timestamp++);
|
|
QVERIFY(touchSequenceStartedSpy.wait());
|
|
|
|
// Start a drag-and-drop operation.
|
|
dataDevice->startDrag(touchSequenceStartedSpy.last().at(0).value<KWayland::Client::TouchPoint *>()->downSerial(), dataSource, surface.get());
|
|
QVERIFY(dataDeviceDragEnteredSpy.wait());
|
|
QCOMPARE(dataDeviceDragEnteredSpy.last().at(1).value<QPointF>(), QPointF(50, 50));
|
|
QCOMPARE(dataDevice->dragSurface(), surface.get());
|
|
|
|
// Without accepted mime type, the drag should be canceled when the finger is lifted.
|
|
QSignalSpy dataSourceSelectedDragAndDropActionChangedSpy(dataSource, &KWayland::Client::DataSource::selectedDragAndDropActionChanged);
|
|
auto offer = dataDevice->takeDragOffer();
|
|
offer->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Copy, KWayland::Client::DataDeviceManager::DnDAction::Copy);
|
|
QVERIFY(dataSourceSelectedDragAndDropActionChangedSpy.wait());
|
|
|
|
// Drop.
|
|
Test::touchUp(0, timestamp++);
|
|
QVERIFY(dataDeviceDragLeftSpy.wait());
|
|
QCOMPARE(dataDeviceDroppedSpy.count(), 0);
|
|
QCOMPARE(dataSourceCancelledSpy.count(), 1);
|
|
QCOMPARE(dataSourceDropPerformedSpy.count(), 1);
|
|
}
|
|
|
|
void DndTest::secondTouchDrag()
|
|
{
|
|
// This test verifies that no second drag operation will be started while there's another active drag.
|
|
|
|
QVERIFY(Test::waitForWaylandTouch());
|
|
|
|
// Setup the data source.
|
|
auto touch = Test::waylandSeat()->createTouch(Test::waylandSeat());
|
|
auto dataDevice = Test::waylandDataDeviceManager()->getDataDevice(Test::waylandSeat(), Test::waylandSeat());
|
|
auto dataSource = Test::waylandDataDeviceManager()->createDataSource(Test::waylandSeat());
|
|
dataSource->offer(QStringLiteral("text/plain"));
|
|
dataSource->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Copy | KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
QSignalSpy touchSequenceStartedSpy(touch, &KWayland::Client::Touch::sequenceStarted);
|
|
QSignalSpy dataDeviceDragEnteredSpy(dataDevice, &KWayland::Client::DataDevice::dragEntered);
|
|
QSignalSpy dataDeviceDragLeftSpy(dataDevice, &KWayland::Client::DataDevice::dragLeft);
|
|
QSignalSpy dataDeviceDragMotionSpy(dataDevice, &KWayland::Client::DataDevice::dragMotion);
|
|
QSignalSpy dataDeviceDroppedSpy(dataDevice, &KWayland::Client::DataDevice::dropped);
|
|
QSignalSpy dataSourceCancelledSpy(dataSource, &KWayland::Client::DataSource::cancelled);
|
|
QSignalSpy dataSourceDropPerformedSpy(dataSource, &KWayland::Client::DataSource::dragAndDropPerformed);
|
|
|
|
auto secondDataSource = Test::waylandDataDeviceManager()->createDataSource(Test::waylandSeat());
|
|
secondDataSource->offer(QStringLiteral("text/plain"));
|
|
secondDataSource->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Copy | KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
|
|
// Create a surface.
|
|
auto surface = Test::createSurface();
|
|
auto shellSurface = Test::createXdgToplevelSurface(surface.get());
|
|
auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 100), Qt::red);
|
|
QVERIFY(window);
|
|
window->move(QPointF(0, 0));
|
|
|
|
// Tap the surface.
|
|
quint32 timestamp = 0;
|
|
Test::touchDown(0, QPointF(50, 50), timestamp++);
|
|
QVERIFY(touchSequenceStartedSpy.wait());
|
|
|
|
// Start a drag-and-drop operation.
|
|
const quint32 downSerial = touchSequenceStartedSpy.last().at(0).value<KWayland::Client::TouchPoint *>()->downSerial();
|
|
dataDevice->startDrag(downSerial, dataSource, surface.get());
|
|
QVERIFY(dataDeviceDragEnteredSpy.wait());
|
|
QCOMPARE(dataDeviceDragEnteredSpy.last().at(1).value<QPointF>(), QPointF(50, 50));
|
|
QCOMPARE(dataDevice->dragSurface(), surface.get());
|
|
|
|
// Attempt to start another drag-and-drop operation.
|
|
QSignalSpy secondDataSourceCancelledSpy(secondDataSource, &KWayland::Client::DataSource::cancelled);
|
|
dataDevice->startDrag(downSerial, secondDataSource, surface.get());
|
|
QVERIFY(secondDataSourceCancelledSpy.wait());
|
|
QCOMPARE(dataSourceCancelledSpy.count(), 0);
|
|
|
|
// Drop.
|
|
Test::touchUp(0, timestamp++);
|
|
QVERIFY(dataDeviceDragLeftSpy.wait());
|
|
QCOMPARE(dataDeviceDroppedSpy.count(), 0);
|
|
QCOMPARE(dataSourceCancelledSpy.count(), 1);
|
|
QCOMPARE(dataSourceDropPerformedSpy.count(), 1);
|
|
}
|
|
|
|
void DndTest::cancelTouchSequence()
|
|
{
|
|
// This test verifies that a drag-and-drop session will be cancelled when the input backend generates a touch cancel event.
|
|
|
|
QVERIFY(Test::waitForWaylandTouch());
|
|
|
|
// Setup the data source.
|
|
auto touch = Test::waylandSeat()->createTouch(Test::waylandSeat());
|
|
auto dataDevice = Test::waylandDataDeviceManager()->getDataDevice(Test::waylandSeat(), Test::waylandSeat());
|
|
auto dataSource = Test::waylandDataDeviceManager()->createDataSource(Test::waylandSeat());
|
|
dataSource->offer(QStringLiteral("text/plain"));
|
|
dataSource->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Copy | KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
QSignalSpy touchSequenceStartedSpy(touch, &KWayland::Client::Touch::sequenceStarted);
|
|
QSignalSpy dataDeviceDragEnteredSpy(dataDevice, &KWayland::Client::DataDevice::dragEntered);
|
|
QSignalSpy dataDeviceDragLeftSpy(dataDevice, &KWayland::Client::DataDevice::dragLeft);
|
|
QSignalSpy dataDeviceDroppedSpy(dataDevice, &KWayland::Client::DataDevice::dropped);
|
|
QSignalSpy dataSourceCancelledSpy(dataSource, &KWayland::Client::DataSource::cancelled);
|
|
QSignalSpy dataSourceDropPerformedSpy(dataSource, &KWayland::Client::DataSource::dragAndDropPerformed);
|
|
|
|
// Create a surface.
|
|
auto surface = Test::createSurface();
|
|
auto shellSurface = Test::createXdgToplevelSurface(surface.get());
|
|
auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 100), Qt::red);
|
|
QVERIFY(window);
|
|
window->move(QPointF(0, 0));
|
|
|
|
// Tap the surface.
|
|
quint32 timestamp = 0;
|
|
Test::touchDown(0, QPointF(50, 50), timestamp++);
|
|
QVERIFY(touchSequenceStartedSpy.wait());
|
|
|
|
// Start a drag-and-drop operation.
|
|
dataDevice->startDrag(touchSequenceStartedSpy.last().at(0).value<KWayland::Client::TouchPoint *>()->downSerial(), dataSource, surface.get());
|
|
QVERIFY(dataDeviceDragEnteredSpy.wait());
|
|
QCOMPARE(dataDeviceDragEnteredSpy.last().at(1).value<QPointF>(), QPointF(50, 50));
|
|
QCOMPARE(dataDevice->dragSurface(), surface.get());
|
|
|
|
// Cancel the touch sequence.
|
|
Test::touchCancel();
|
|
QVERIFY(dataDeviceDragLeftSpy.wait());
|
|
QCOMPARE(dataDeviceDroppedSpy.count(), 0);
|
|
QCOMPARE(dataSourceCancelledSpy.count(), 1);
|
|
QCOMPARE(dataSourceDropPerformedSpy.count(), 0);
|
|
}
|
|
|
|
void DndTest::tabletDrag()
|
|
{
|
|
// This test verifies that a simple tablet drag-and-drop operation between two Wayland surfaces works as expected.
|
|
|
|
std::unique_ptr<Test::WpTabletSeatV2> tabletSeat = Test::tabletManager()->createSeat(Test::waylandSeat());
|
|
QSignalSpy toolAddedSpy(tabletSeat.get(), &Test::WpTabletSeatV2::toolAdded);
|
|
QVERIFY(toolAddedSpy.wait());
|
|
Test::WpTabletToolV2 *tabletTool = toolAddedSpy.last().at(0).value<Test::WpTabletToolV2 *>();
|
|
QVERIFY(Test::waitForWaylandTabletTool(tabletTool));
|
|
|
|
// Setup the data source.
|
|
auto dataDevice = Test::waylandDataDeviceManager()->getDataDevice(Test::waylandSeat(), Test::waylandSeat());
|
|
auto dataSource = Test::waylandDataDeviceManager()->createDataSource(Test::waylandSeat());
|
|
dataSource->offer(QStringLiteral("text/plain"));
|
|
dataSource->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Copy | KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
connect(dataSource, &KWayland::Client::DataSource::sendDataRequested, this, [](const QString &mimeType, int fd) {
|
|
if (mimeType == QLatin1String("text/plain")) {
|
|
const auto payload = QByteArrayLiteral("foo");
|
|
write(fd, payload.data(), payload.size());
|
|
}
|
|
close(fd);
|
|
});
|
|
QSignalSpy dataDeviceDragEnteredSpy(dataDevice, &KWayland::Client::DataDevice::dragEntered);
|
|
QSignalSpy dataDeviceDragLeftSpy(dataDevice, &KWayland::Client::DataDevice::dragLeft);
|
|
QSignalSpy dataDeviceDragMotionSpy(dataDevice, &KWayland::Client::DataDevice::dragMotion);
|
|
QSignalSpy dataDeviceDroppedSpy(dataDevice, &KWayland::Client::DataDevice::dropped);
|
|
|
|
// Setup the source client.
|
|
auto sourceSurface = Test::createSurface();
|
|
auto sourceShellSurface = Test::createXdgToplevelSurface(sourceSurface.get());
|
|
auto sourceWindow = Test::renderAndWaitForShown(sourceSurface.get(), QSize(100, 100), Qt::red);
|
|
QVERIFY(sourceWindow);
|
|
sourceWindow->move(QPointF(0, 0));
|
|
|
|
// Setup the target client.
|
|
auto targetSurface = Test::createSurface();
|
|
auto targetShellSurface = Test::createXdgToplevelSurface(targetSurface.get());
|
|
auto targetWindow = Test::renderAndWaitForShown(targetSurface.get(), QSize(100, 100), Qt::red);
|
|
QVERIFY(targetWindow);
|
|
targetWindow->move(QPointF(100, 0));
|
|
|
|
// Press the tip of the tablet tool in the center of the window.
|
|
QSignalSpy tabletToolDownSpy(tabletTool, &Test::WpTabletToolV2::down);
|
|
quint32 timestamp = 0;
|
|
Test::tabletToolProximityEvent(QPointF(50, 50), 0, 0, 0, 0, true, 0, timestamp++);
|
|
Test::tabletToolTipEvent(QPointF(50, 50), 1, 0, 0, 0, 0, true, 0, timestamp++);
|
|
QVERIFY(tabletToolDownSpy.wait());
|
|
|
|
// Start a drag-and-drop operation.
|
|
dataDevice->startDrag(tabletToolDownSpy.last().at(0).value<uint32_t>(), dataSource, sourceSurface.get());
|
|
QVERIFY(dataDeviceDragEnteredSpy.wait());
|
|
QCOMPARE(dataDeviceDragEnteredSpy.last().at(1).value<QPointF>(), QPointF(50, 50));
|
|
QCOMPARE(dataDevice->dragSurface(), sourceSurface.get());
|
|
|
|
QCOMPARE(dataDeviceDragEnteredSpy.count(), 1);
|
|
QCOMPARE(dataDeviceDragMotionSpy.count(), 0);
|
|
QCOMPARE(dataDeviceDragLeftSpy.count(), 0);
|
|
QCOMPARE(dataDeviceDroppedSpy.count(), 0);
|
|
|
|
// Accept our own offer.
|
|
auto sourceOffer = dataDevice->takeDragOffer();
|
|
QCOMPARE(sourceOffer->offeredMimeTypes(), (QList<QMimeType>{m_mimeDb.mimeTypeForName(QStringLiteral("text/plain"))}));
|
|
QCOMPARE(sourceOffer->sourceDragAndDropActions(), KWayland::Client::DataDeviceManager::DnDAction::Copy | KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
QCOMPARE(sourceOffer->selectedDragAndDropAction(), KWayland::Client::DataDeviceManager::DnDAction::None);
|
|
|
|
QSignalSpy sourceTargetsAcceptsSpy(dataSource, &KWayland::Client::DataSource::targetAccepts);
|
|
sourceOffer->accept(QString(), dataDeviceDragEnteredSpy.last().at(0).value<quint32>());
|
|
QVERIFY(sourceTargetsAcceptsSpy.wait());
|
|
QCOMPARE(sourceTargetsAcceptsSpy.last().at(0).value<QString>(), QString());
|
|
|
|
sourceOffer->accept(QStringLiteral("text/plain"), dataDeviceDragEnteredSpy.last().at(0).value<quint32>());
|
|
QVERIFY(sourceTargetsAcceptsSpy.wait());
|
|
QCOMPARE(sourceTargetsAcceptsSpy.last().at(0).value<QString>(), QStringLiteral("text/plain"));
|
|
|
|
QSignalSpy sourceOfferSelectedDragAndDropActionSpy(sourceOffer.get(), &KWayland::Client::DataOffer::selectedDragAndDropActionChanged);
|
|
sourceOffer->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Copy, KWayland::Client::DataDeviceManager::DnDAction::Copy);
|
|
QVERIFY(sourceOfferSelectedDragAndDropActionSpy.wait());
|
|
QCOMPARE(sourceOffer->selectedDragAndDropAction(), KWayland::Client::DataDeviceManager::DnDAction::Copy);
|
|
QCOMPARE(dataSource->selectedDragAndDropAction(), KWayland::Client::DataDeviceManager::DnDAction::Copy);
|
|
|
|
// Move the tablet tool a little bit.
|
|
Test::tabletToolAxisEvent(QPointF(60, 50), 1, 0, 0, 0, 0, true, 0, timestamp++);
|
|
QVERIFY(dataDeviceDragMotionSpy.wait());
|
|
QCOMPARE(dataDeviceDragMotionSpy.last().at(0).value<QPointF>(), QPointF(60, 50));
|
|
|
|
QCOMPARE(dataDeviceDragEnteredSpy.count(), 1);
|
|
QCOMPARE(dataDeviceDragMotionSpy.count(), 1);
|
|
QCOMPARE(dataDeviceDragLeftSpy.count(), 0);
|
|
QCOMPARE(dataDeviceDroppedSpy.count(), 0);
|
|
|
|
// Move the tablet tool to the target surface.
|
|
Test::tabletToolAxisEvent(QPointF(140, 50), 1, 0, 0, 0, 0, true, 0, timestamp++);
|
|
QVERIFY(dataDeviceDragEnteredSpy.wait());
|
|
QCOMPARE(dataDeviceDragEnteredSpy.last().at(1).value<QPointF>(), QPointF(40, 50));
|
|
QCOMPARE(dataDeviceDragLeftSpy.count(), 1);
|
|
QCOMPARE(dataDevice->dragSurface(), targetSurface.get());
|
|
|
|
QCOMPARE(dataDeviceDragEnteredSpy.count(), 2);
|
|
QCOMPARE(dataDeviceDragMotionSpy.count(), 1);
|
|
QCOMPARE(dataDeviceDragLeftSpy.count(), 1);
|
|
QCOMPARE(dataDeviceDroppedSpy.count(), 0);
|
|
|
|
// Accept the offer.
|
|
auto targetOffer = dataDevice->takeDragOffer();
|
|
QCOMPARE(targetOffer->offeredMimeTypes(), (QList<QMimeType>{m_mimeDb.mimeTypeForName(QStringLiteral("text/plain"))}));
|
|
QCOMPARE(targetOffer->sourceDragAndDropActions(), KWayland::Client::DataDeviceManager::DnDAction::Copy | KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
QCOMPARE(targetOffer->selectedDragAndDropAction(), KWayland::Client::DataDeviceManager::DnDAction::None);
|
|
|
|
targetOffer->accept(QString(), dataDeviceDragEnteredSpy.last().at(0).value<quint32>());
|
|
QVERIFY(sourceTargetsAcceptsSpy.wait());
|
|
QCOMPARE(sourceTargetsAcceptsSpy.last().at(0).value<QString>(), QString());
|
|
|
|
targetOffer->accept(QStringLiteral("text/plain"), dataDeviceDragEnteredSpy.last().at(0).value<quint32>());
|
|
QVERIFY(sourceTargetsAcceptsSpy.wait());
|
|
QCOMPARE(sourceTargetsAcceptsSpy.last().at(0).value<QString>(), QStringLiteral("text/plain"));
|
|
|
|
QSignalSpy targetOfferSelectedDragAndDropActionSpy(targetOffer.get(), &KWayland::Client::DataOffer::selectedDragAndDropActionChanged);
|
|
targetOffer->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Move, KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
QVERIFY(targetOfferSelectedDragAndDropActionSpy.wait());
|
|
QCOMPARE(targetOffer->selectedDragAndDropAction(), KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
QCOMPARE(dataSource->selectedDragAndDropAction(), KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
|
|
// Move the tablet tool a little bit.
|
|
Test::tabletToolAxisEvent(QPointF(160, 50), 1, 0, 0, 0, 0, true, 0, timestamp++);
|
|
QVERIFY(dataDeviceDragMotionSpy.wait());
|
|
QCOMPARE(dataDeviceDragMotionSpy.last().at(0).value<QPointF>(), QPointF(60, 50));
|
|
|
|
QCOMPARE(dataDeviceDragEnteredSpy.count(), 2);
|
|
QCOMPARE(dataDeviceDragMotionSpy.count(), 2);
|
|
QCOMPARE(dataDeviceDragLeftSpy.count(), 1);
|
|
QCOMPARE(dataDeviceDroppedSpy.count(), 0);
|
|
|
|
// Drop.
|
|
Test::tabletToolTipEvent(QPointF(160, 50), 0, 0, 0, 0, 0, false, 0, timestamp++);
|
|
Test::tabletToolProximityEvent(QPointF(160, 50), 0, 0, 0, 0, false, 0, timestamp++);
|
|
QVERIFY(dataDeviceDroppedSpy.wait());
|
|
|
|
QCOMPARE(dataDeviceDragEnteredSpy.count(), 2);
|
|
QCOMPARE(dataDeviceDragMotionSpy.count(), 2);
|
|
QCOMPARE(dataDeviceDragLeftSpy.count(), 2);
|
|
QCOMPARE(dataDeviceDroppedSpy.count(), 1);
|
|
|
|
// Ask for data.
|
|
const QFuture<QByteArray> plainData = readMimeTypeData(targetOffer.get(), QStringLiteral("text/plain"));
|
|
QVERIFY(waitFuture(plainData));
|
|
QCOMPARE(plainData.result(), QByteArrayLiteral("foo"));
|
|
|
|
// text/html hasn't been offered, so this should fail.
|
|
const QFuture<QByteArray> htmlData = readMimeTypeData(targetOffer.get(), QStringLiteral("text/html"));
|
|
QVERIFY(waitFuture(htmlData));
|
|
QCOMPARE(htmlData.result(), QByteArray());
|
|
|
|
// Finish drag-and-drop.
|
|
QSignalSpy dragAndDropFinishedSpy(dataSource, &KWayland::Client::DataSource::dragAndDropFinished);
|
|
targetOffer->dragAndDropFinished();
|
|
QVERIFY(dragAndDropFinishedSpy.wait());
|
|
}
|
|
|
|
void DndTest::tabletSubSurfaceDrag()
|
|
{
|
|
// This test verifies that a drag-and-drop originating from a sub-surface works as expected.
|
|
|
|
std::unique_ptr<Test::WpTabletSeatV2> tabletSeat = Test::tabletManager()->createSeat(Test::waylandSeat());
|
|
QSignalSpy toolAddedSpy(tabletSeat.get(), &Test::WpTabletSeatV2::toolAdded);
|
|
QVERIFY(toolAddedSpy.wait());
|
|
Test::WpTabletToolV2 *tabletTool = toolAddedSpy.last().at(0).value<Test::WpTabletToolV2 *>();
|
|
QVERIFY(Test::waitForWaylandTabletTool(tabletTool));
|
|
|
|
// Setup the data source.
|
|
auto dataDevice = Test::waylandDataDeviceManager()->getDataDevice(Test::waylandSeat(), Test::waylandSeat());
|
|
auto dataSource = Test::waylandDataDeviceManager()->createDataSource(Test::waylandSeat());
|
|
dataSource->offer(QStringLiteral("text/plain"));
|
|
dataSource->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Copy | KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
QSignalSpy dataDeviceDragEnteredSpy(dataDevice, &KWayland::Client::DataDevice::dragEntered);
|
|
QSignalSpy dataDeviceDragLeftSpy(dataDevice, &KWayland::Client::DataDevice::dragLeft);
|
|
QSignalSpy dataDeviceDragMotionSpy(dataDevice, &KWayland::Client::DataDevice::dragMotion);
|
|
QSignalSpy dataDeviceDroppedSpy(dataDevice, &KWayland::Client::DataDevice::dropped);
|
|
|
|
// Setup a window with a subsurface that covers the right half.
|
|
auto parentSurface = Test::createSurface();
|
|
auto parentShellSurface = Test::createXdgToplevelSurface(parentSurface.get());
|
|
auto window = Test::renderAndWaitForShown(parentSurface.get(), QSize(100, 100), Qt::red);
|
|
QVERIFY(window);
|
|
window->move(QPointF(0, 0));
|
|
|
|
auto childSurface = Test::createSurface();
|
|
auto subSurface = Test::createSubSurface(childSurface.get(), parentSurface.get());
|
|
subSurface->setPosition(QPoint(50, 0));
|
|
Test::render(childSurface.get(), QSize(50, 100), Qt::blue);
|
|
parentSurface->commit(KWayland::Client::Surface::CommitFlag::None);
|
|
|
|
// Press the tip of the tablet tool in the center of the child surface.
|
|
QSignalSpy tabletToolDownSpy(tabletTool, &Test::WpTabletToolV2::down);
|
|
quint32 timestamp = 0;
|
|
Test::tabletToolProximityEvent(QPointF(75, 50), 0, 0, 0, 0, true, 0, timestamp++);
|
|
Test::tabletToolTipEvent(QPointF(75, 50), 1, 0, 0, 0, 0, true, 0, timestamp++);
|
|
QVERIFY(tabletToolDownSpy.wait());
|
|
|
|
// Start a drag-and-drop operation.
|
|
dataDevice->startDrag(tabletToolDownSpy.last().at(0).value<uint32_t>(), dataSource, childSurface.get());
|
|
QVERIFY(dataDeviceDragEnteredSpy.wait());
|
|
QCOMPARE(dataDeviceDragEnteredSpy.last().at(1).value<QPointF>(), QPointF(25, 50));
|
|
QCOMPARE(dataDevice->dragSurface(), childSurface.get());
|
|
|
|
// Move the tablet tool to the parent surface.
|
|
Test::tabletToolAxisEvent(QPointF(10, 50), 1, 0, 0, 0, 0, true, 0, timestamp++);
|
|
QVERIFY(dataDeviceDragEnteredSpy.wait());
|
|
QCOMPARE(dataDeviceDragEnteredSpy.count(), 2);
|
|
QCOMPARE(dataDeviceDragLeftSpy.count(), 1);
|
|
QCOMPARE(dataDeviceDragEnteredSpy.last().at(1).value<QPointF>(), QPointF(10, 50));
|
|
QCOMPARE(dataDevice->dragSurface(), parentSurface.get());
|
|
|
|
Test::tabletToolAxisEvent(QPointF(15, 50), 1, 0, 0, 0, 0, true, 0, timestamp++);
|
|
QVERIFY(dataDeviceDragMotionSpy.wait());
|
|
QCOMPARE(dataDeviceDragMotionSpy.last().at(0).value<QPointF>(), QPointF(15, 50));
|
|
QCOMPARE(dataDeviceDragEnteredSpy.count(), 2);
|
|
QCOMPARE(dataDeviceDragLeftSpy.count(), 1);
|
|
QCOMPARE(dataDevice->dragSurface(), parentSurface.get());
|
|
|
|
// Move the tablet tool to the child surface.
|
|
Test::tabletToolAxisEvent(QPointF(80, 50), 1, 0, 0, 0, 0, true, 0, timestamp++);
|
|
QVERIFY(dataDeviceDragEnteredSpy.wait());
|
|
QCOMPARE(dataDeviceDragEnteredSpy.count(), 3);
|
|
QCOMPARE(dataDeviceDragLeftSpy.count(), 2);
|
|
QCOMPARE(dataDeviceDragEnteredSpy.last().at(1).value<QPointF>(), QPointF(30, 50));
|
|
QCOMPARE(dataDevice->dragSurface(), childSurface.get());
|
|
|
|
Test::tabletToolAxisEvent(QPointF(75, 50), 1, 0, 0, 0, 0, true, 0, timestamp++);
|
|
QVERIFY(dataDeviceDragMotionSpy.wait());
|
|
QCOMPARE(dataDeviceDragMotionSpy.last().at(0).value<QPointF>(), QPointF(25, 50));
|
|
QCOMPARE(dataDeviceDragEnteredSpy.count(), 3);
|
|
QCOMPARE(dataDeviceDragLeftSpy.count(), 2);
|
|
QCOMPARE(dataDevice->dragSurface(), childSurface.get());
|
|
|
|
// Accept the data offer.
|
|
auto offer = dataDevice->takeDragOffer();
|
|
QCOMPARE(offer->offeredMimeTypes(), (QList<QMimeType>{m_mimeDb.mimeTypeForName(QStringLiteral("text/plain"))}));
|
|
QCOMPARE(offer->sourceDragAndDropActions(), KWayland::Client::DataDeviceManager::DnDAction::Copy | KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
QCOMPARE(offer->selectedDragAndDropAction(), KWayland::Client::DataDeviceManager::DnDAction::None);
|
|
|
|
QSignalSpy sourceTargetsAcceptsSpy(dataSource, &KWayland::Client::DataSource::targetAccepts);
|
|
offer->accept(QString(), dataDeviceDragEnteredSpy.last().at(0).value<quint32>());
|
|
QVERIFY(sourceTargetsAcceptsSpy.wait());
|
|
QCOMPARE(sourceTargetsAcceptsSpy.last().at(0).value<QString>(), QString());
|
|
|
|
offer->accept(QStringLiteral("text/plain"), dataDeviceDragEnteredSpy.last().at(0).value<quint32>());
|
|
QVERIFY(sourceTargetsAcceptsSpy.wait());
|
|
QCOMPARE(sourceTargetsAcceptsSpy.last().at(0).value<QString>(), QStringLiteral("text/plain"));
|
|
|
|
QSignalSpy targetOfferSelectedDragAndDropActionSpy(offer.get(), &KWayland::Client::DataOffer::selectedDragAndDropActionChanged);
|
|
offer->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Move, KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
QVERIFY(targetOfferSelectedDragAndDropActionSpy.wait());
|
|
QCOMPARE(offer->selectedDragAndDropAction(), KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
QCOMPARE(dataSource->selectedDragAndDropAction(), KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
|
|
// Drop.
|
|
Test::tabletToolTipEvent(QPointF(75, 50), 0, 0, 0, 0, 0, false, 0, timestamp++);
|
|
Test::tabletToolProximityEvent(QPointF(75, 50), 0, 0, 0, 0, false, 0, timestamp++);
|
|
QVERIFY(dataDeviceDroppedSpy.wait());
|
|
QCOMPARE(dataDeviceDroppedSpy.count(), 1);
|
|
|
|
// Finish drag-and-drop.
|
|
QSignalSpy dragAndDropFinishedSpy(dataSource, &KWayland::Client::DataSource::dragAndDropFinished);
|
|
offer->dragAndDropFinished();
|
|
QVERIFY(dragAndDropFinishedSpy.wait());
|
|
}
|
|
|
|
void DndTest::tabletDragAction_data()
|
|
{
|
|
QTest::addColumn<KWayland::Client::DataDeviceManager::DnDAction>("action");
|
|
|
|
QTest::addRow("ask") << KWayland::Client::DataDeviceManager::DnDAction::Ask;
|
|
QTest::addRow("copy") << KWayland::Client::DataDeviceManager::DnDAction::Copy;
|
|
QTest::addRow("move") << KWayland::Client::DataDeviceManager::DnDAction::Move;
|
|
}
|
|
|
|
void DndTest::tabletDragAction()
|
|
{
|
|
// This test verifies that the dnd actions are handled as expected. "Ask" action is a bit
|
|
// special; if it has been selected, then the final action can be set after the drop.
|
|
|
|
QFETCH(KWayland::Client::DataDeviceManager::DnDAction, action);
|
|
|
|
std::unique_ptr<Test::WpTabletSeatV2> tabletSeat = Test::tabletManager()->createSeat(Test::waylandSeat());
|
|
QSignalSpy toolAddedSpy(tabletSeat.get(), &Test::WpTabletSeatV2::toolAdded);
|
|
QVERIFY(toolAddedSpy.wait());
|
|
Test::WpTabletToolV2 *tabletTool = toolAddedSpy.last().at(0).value<Test::WpTabletToolV2 *>();
|
|
QVERIFY(Test::waitForWaylandTabletTool(tabletTool));
|
|
|
|
// Setup the data source.
|
|
auto dataDevice = Test::waylandDataDeviceManager()->getDataDevice(Test::waylandSeat(), Test::waylandSeat());
|
|
auto dataSource = Test::waylandDataDeviceManager()->createDataSource(Test::waylandSeat());
|
|
dataSource->offer(QStringLiteral("text/plain"));
|
|
dataSource->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Copy | KWayland::Client::DataDeviceManager::DnDAction::Move | KWayland::Client::DataDeviceManager::DnDAction::Ask);
|
|
QSignalSpy dataDeviceDragEnteredSpy(dataDevice, &KWayland::Client::DataDevice::dragEntered);
|
|
QSignalSpy dataDeviceDroppedSpy(dataDevice, &KWayland::Client::DataDevice::dropped);
|
|
QSignalSpy dataSourceSelectedDragAndDropActionChangedSpy(dataSource, &KWayland::Client::DataSource::selectedDragAndDropActionChanged);
|
|
|
|
// Setup the source window.
|
|
auto surface = Test::createSurface();
|
|
auto shellSurface = Test::createXdgToplevelSurface(surface.get());
|
|
auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 100), Qt::red);
|
|
QVERIFY(window);
|
|
window->move(QPointF(0, 0));
|
|
|
|
// Press the tip of the tablet tool in the center of the window.
|
|
QSignalSpy tabletToolDownSpy(tabletTool, &Test::WpTabletToolV2::down);
|
|
quint32 timestamp = 0;
|
|
Test::tabletToolProximityEvent(QPointF(50, 50), 0, 0, 0, 0, true, 0, timestamp++);
|
|
Test::tabletToolTipEvent(QPointF(50, 50), 1, 0, 0, 0, 0, true, 0, timestamp++);
|
|
QVERIFY(tabletToolDownSpy.wait());
|
|
|
|
// Start a drag-and-drop operation.
|
|
dataDevice->startDrag(tabletToolDownSpy.last().at(0).value<uint32_t>(), dataSource, surface.get());
|
|
QVERIFY(dataDeviceDragEnteredSpy.wait());
|
|
|
|
const auto offer = dataDevice->takeDragOffer();
|
|
offer->accept(QStringLiteral("text/plain"), dataDeviceDragEnteredSpy.last().at(0).value<quint32>());
|
|
offer->setDragAndDropActions(action, action);
|
|
QVERIFY(dataSourceSelectedDragAndDropActionChangedSpy.wait());
|
|
QCOMPARE(dataSourceSelectedDragAndDropActionChangedSpy.count(), 1);
|
|
QCOMPARE(dataSource->selectedDragAndDropAction(), action);
|
|
|
|
// Drop.
|
|
Test::tabletToolTipEvent(QPointF(50, 50), 0, 0, 0, 0, 0, false, 0, timestamp++);
|
|
Test::tabletToolProximityEvent(QPointF(50, 50), 0, 0, 0, 0, false, 0, timestamp++);
|
|
QVERIFY(dataDeviceDroppedSpy.wait());
|
|
QCOMPARE(dataSourceSelectedDragAndDropActionChangedSpy.count(), 1);
|
|
|
|
// Change action after drop.
|
|
switch (action) {
|
|
case KWayland::Client::DataDeviceManager::DnDAction::Ask:
|
|
offer->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Copy, KWayland::Client::DataDeviceManager::DnDAction::Copy);
|
|
QVERIFY(dataSourceSelectedDragAndDropActionChangedSpy.wait());
|
|
QCOMPARE(dataSourceSelectedDragAndDropActionChangedSpy.count(), 2);
|
|
QCOMPARE(dataSource->selectedDragAndDropAction(), KWayland::Client::DataDeviceManager::DnDAction::Copy);
|
|
break;
|
|
case KWayland::Client::DataDeviceManager::DnDAction::Copy:
|
|
case KWayland::Client::DataDeviceManager::DnDAction::Move: {
|
|
const auto oppositeAction = action == KWayland::Client::DataDeviceManager::DnDAction::Copy
|
|
? KWayland::Client::DataDeviceManager::DnDAction::Move
|
|
: KWayland::Client::DataDeviceManager::DnDAction::Copy;
|
|
offer->setDragAndDropActions(oppositeAction, oppositeAction);
|
|
QVERIFY(!dataSourceSelectedDragAndDropActionChangedSpy.wait(10));
|
|
break;
|
|
}
|
|
case KWayland::Client::DataDeviceManager::DnDAction::None:
|
|
Q_UNREACHABLE();
|
|
}
|
|
|
|
// Finish drag-and-drop.
|
|
QSignalSpy dragAndDropFinishedSpy(dataSource, &KWayland::Client::DataSource::dragAndDropFinished);
|
|
offer->dragAndDropFinished();
|
|
QVERIFY(dragAndDropFinishedSpy.wait());
|
|
}
|
|
|
|
void DndTest::inertTabletDragOffer()
|
|
{
|
|
// This test verifies that requests from a previous data offer don't affect the current drag-and-drop operation status.
|
|
|
|
std::unique_ptr<Test::WpTabletSeatV2> tabletSeat = Test::tabletManager()->createSeat(Test::waylandSeat());
|
|
QSignalSpy toolAddedSpy(tabletSeat.get(), &Test::WpTabletSeatV2::toolAdded);
|
|
QVERIFY(toolAddedSpy.wait());
|
|
Test::WpTabletToolV2 *tabletTool = toolAddedSpy.last().at(0).value<Test::WpTabletToolV2 *>();
|
|
QVERIFY(Test::waitForWaylandTabletTool(tabletTool));
|
|
|
|
// Setup the data source.
|
|
auto dataDevice = Test::waylandDataDeviceManager()->getDataDevice(Test::waylandSeat(), Test::waylandSeat());
|
|
auto dataSource = Test::waylandDataDeviceManager()->createDataSource(Test::waylandSeat());
|
|
dataSource->offer(QStringLiteral("text/plain"));
|
|
dataSource->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Copy | KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
QSignalSpy dataDeviceDragEnteredSpy(dataDevice, &KWayland::Client::DataDevice::dragEntered);
|
|
QSignalSpy dataDeviceDragLeftSpy(dataDevice, &KWayland::Client::DataDevice::dragLeft);
|
|
QSignalSpy dataDeviceDroppedSpy(dataDevice, &KWayland::Client::DataDevice::dropped);
|
|
QSignalSpy sourceTargetsAcceptsSpy(dataSource, &KWayland::Client::DataSource::targetAccepts);
|
|
QSignalSpy dataSourceSelectedDragAndDropActionChangedSpy(dataSource, &KWayland::Client::DataSource::selectedDragAndDropActionChanged);
|
|
|
|
// Setup the source window.
|
|
auto surface = Test::createSurface();
|
|
auto shellSurface = Test::createXdgToplevelSurface(surface.get());
|
|
auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 100), Qt::red);
|
|
QVERIFY(window);
|
|
window->move(QPointF(0, 0));
|
|
|
|
// Press the tip of the tablet tool in the center of the window.
|
|
QSignalSpy tabletToolDownSpy(tabletTool, &Test::WpTabletToolV2::down);
|
|
quint32 timestamp = 0;
|
|
Test::tabletToolProximityEvent(QPointF(50, 50), 0, 0, 0, 0, true, 0, timestamp++);
|
|
Test::tabletToolTipEvent(QPointF(50, 50), 1, 0, 0, 0, 0, true, 0, timestamp++);
|
|
QVERIFY(tabletToolDownSpy.wait());
|
|
|
|
// Start a drag-and-drop operation.
|
|
dataDevice->startDrag(tabletToolDownSpy.last().at(0).value<uint32_t>(), dataSource, surface.get());
|
|
QVERIFY(dataDeviceDragEnteredSpy.wait());
|
|
|
|
const auto firstOffer = dataDevice->takeDragOffer();
|
|
firstOffer->accept(QString(), dataDeviceDragEnteredSpy.last().at(0).value<quint32>());
|
|
QVERIFY(sourceTargetsAcceptsSpy.wait());
|
|
QCOMPARE(sourceTargetsAcceptsSpy.last().at(0).value<QString>(), QString());
|
|
|
|
firstOffer->accept(QStringLiteral("text/plain"), dataDeviceDragEnteredSpy.last().at(0).value<quint32>());
|
|
QVERIFY(sourceTargetsAcceptsSpy.wait());
|
|
QCOMPARE(sourceTargetsAcceptsSpy.last().at(0).value<QString>(), QStringLiteral("text/plain"));
|
|
|
|
firstOffer->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Move, KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
QVERIFY(dataSourceSelectedDragAndDropActionChangedSpy.wait());
|
|
QCOMPARE(dataSource->selectedDragAndDropAction(), KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
|
|
// Move the tablet tool tip outside the surface.
|
|
Test::tabletToolAxisEvent(QPointF(150, 50), 1, 0, 0, 0, 0, true, 0, timestamp++);
|
|
QVERIFY(dataDeviceDragLeftSpy.wait());
|
|
|
|
firstOffer->accept(QString(), dataDeviceDragEnteredSpy.last().at(0).value<quint32>());
|
|
QVERIFY(!sourceTargetsAcceptsSpy.wait(10));
|
|
|
|
firstOffer->accept(QStringLiteral("text/plain"), dataDeviceDragEnteredSpy.last().at(0).value<quint32>());
|
|
QVERIFY(!sourceTargetsAcceptsSpy.wait(10));
|
|
|
|
firstOffer->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Copy, KWayland::Client::DataDeviceManager::DnDAction::Copy);
|
|
QVERIFY(!dataSourceSelectedDragAndDropActionChangedSpy.wait(10));
|
|
|
|
// Move the finger back inside the surface, it will result in a new data offer.
|
|
Test::tabletToolAxisEvent(QPointF(75, 50), 1, 0, 0, 0, 0, true, 0, timestamp++);
|
|
QVERIFY(dataDeviceDragEnteredSpy.wait());
|
|
|
|
const auto secondOffer = dataDevice->takeDragOffer();
|
|
secondOffer->accept(QString(), dataDeviceDragEnteredSpy.last().at(0).value<quint32>());
|
|
QVERIFY(sourceTargetsAcceptsSpy.wait());
|
|
QCOMPARE(sourceTargetsAcceptsSpy.last().at(0).value<QString>(), QString());
|
|
|
|
secondOffer->accept(QStringLiteral("text/plain"), dataDeviceDragEnteredSpy.last().at(0).value<quint32>());
|
|
QVERIFY(sourceTargetsAcceptsSpy.wait());
|
|
QCOMPARE(sourceTargetsAcceptsSpy.last().at(0).value<QString>(), QStringLiteral("text/plain"));
|
|
|
|
secondOffer->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Move, KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
QVERIFY(dataSourceSelectedDragAndDropActionChangedSpy.wait());
|
|
QCOMPARE(dataSource->selectedDragAndDropAction(), KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
|
|
// The first offer shouldn't change the accepted mime type or selected action.
|
|
firstOffer->accept(QString(), dataDeviceDragEnteredSpy.last().at(0).value<quint32>());
|
|
QVERIFY(!sourceTargetsAcceptsSpy.wait(10));
|
|
|
|
firstOffer->accept(QStringLiteral("text/plain"), dataDeviceDragEnteredSpy.last().at(0).value<quint32>());
|
|
QVERIFY(!sourceTargetsAcceptsSpy.wait(10));
|
|
|
|
firstOffer->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Copy, KWayland::Client::DataDeviceManager::DnDAction::Copy);
|
|
QVERIFY(!dataSourceSelectedDragAndDropActionChangedSpy.wait(10));
|
|
|
|
// Drop.
|
|
Test::tabletToolTipEvent(QPointF(75, 50), 0, 0, 0, 0, 0, false, 0, timestamp++);
|
|
Test::tabletToolProximityEvent(QPointF(75, 50), 0, 0, 0, 0, false, 0, timestamp++);
|
|
QVERIFY(dataDeviceDroppedSpy.wait());
|
|
|
|
// Finish drag-and-drop.
|
|
QSignalSpy dragAndDropFinishedSpy(dataSource, &KWayland::Client::DataSource::dragAndDropFinished);
|
|
|
|
firstOffer->dragAndDropFinished();
|
|
QVERIFY(!dragAndDropFinishedSpy.wait(10));
|
|
|
|
secondOffer->dragAndDropFinished();
|
|
QVERIFY(dragAndDropFinishedSpy.wait());
|
|
}
|
|
|
|
void DndTest::invalidSerialForTabletDrag()
|
|
{
|
|
// This test verifies that the data source will be canceled if a dnd operation is forbidden.
|
|
|
|
std::unique_ptr<Test::WpTabletSeatV2> tabletSeat = Test::tabletManager()->createSeat(Test::waylandSeat());
|
|
QSignalSpy toolAddedSpy(tabletSeat.get(), &Test::WpTabletSeatV2::toolAdded);
|
|
QVERIFY(toolAddedSpy.wait());
|
|
Test::WpTabletToolV2 *tabletTool = toolAddedSpy.last().at(0).value<Test::WpTabletToolV2 *>();
|
|
QVERIFY(Test::waitForWaylandTabletTool(tabletTool));
|
|
|
|
// Setup the data source.
|
|
auto dataDevice = Test::waylandDataDeviceManager()->getDataDevice(Test::waylandSeat(), Test::waylandSeat());
|
|
auto dataSource = Test::waylandDataDeviceManager()->createDataSource(Test::waylandSeat());
|
|
dataSource->offer(QStringLiteral("text/plain"));
|
|
dataSource->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Copy | KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
|
|
// Setup a window with a subsurface that covers the right half.
|
|
auto surface = Test::createSurface();
|
|
auto shellSurface = Test::createXdgToplevelSurface(surface.get());
|
|
auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 100), Qt::red);
|
|
QVERIFY(window);
|
|
window->move(QPointF(0, 0));
|
|
|
|
// Press the tip of the tablet tool in the center of the window.
|
|
QSignalSpy tabletToolDownSpy(tabletTool, &Test::WpTabletToolV2::down);
|
|
quint32 timestamp = 0;
|
|
Test::tabletToolProximityEvent(QPointF(50, 50), 0, 0, 0, 0, true, 0, timestamp++);
|
|
Test::tabletToolTipEvent(QPointF(50, 50), 1, 0, 0, 0, 0, true, 0, timestamp++);
|
|
Test::tabletToolTipEvent(QPointF(50, 50), 1, 0, 0, 0, 0, false, 0, timestamp++);
|
|
Test::tabletToolProximityEvent(QPointF(50, 50), 0, 0, 0, 0, false, 0, timestamp++);
|
|
QVERIFY(tabletToolDownSpy.wait());
|
|
|
|
// The dnd operation will be rejected because the touch point is already released.
|
|
QSignalSpy dataSourceCanceledSpy(dataSource, &KWayland::Client::DataSource::cancelled);
|
|
dataDevice->startDrag(tabletToolDownSpy.last().at(0).value<uint32_t>(), dataSource, surface.get());
|
|
QVERIFY(dataSourceCanceledSpy.wait());
|
|
}
|
|
|
|
void DndTest::noAcceptedMimeTypeTabletDrag()
|
|
{
|
|
// This test verifies that a drag-and-drop operation will be cancelled if no mime type is accepted.
|
|
|
|
std::unique_ptr<Test::WpTabletSeatV2> tabletSeat = Test::tabletManager()->createSeat(Test::waylandSeat());
|
|
QSignalSpy toolAddedSpy(tabletSeat.get(), &Test::WpTabletSeatV2::toolAdded);
|
|
QVERIFY(toolAddedSpy.wait());
|
|
Test::WpTabletToolV2 *tabletTool = toolAddedSpy.last().at(0).value<Test::WpTabletToolV2 *>();
|
|
QVERIFY(Test::waitForWaylandTabletTool(tabletTool));
|
|
|
|
// Setup the data source.
|
|
auto dataDevice = Test::waylandDataDeviceManager()->getDataDevice(Test::waylandSeat(), Test::waylandSeat());
|
|
auto dataSource = Test::waylandDataDeviceManager()->createDataSource(Test::waylandSeat());
|
|
dataSource->offer(QStringLiteral("text/plain"));
|
|
dataSource->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Copy | KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
QSignalSpy dataDeviceDragEnteredSpy(dataDevice, &KWayland::Client::DataDevice::dragEntered);
|
|
QSignalSpy dataDeviceDragLeftSpy(dataDevice, &KWayland::Client::DataDevice::dragLeft);
|
|
QSignalSpy dataDeviceDragMotionSpy(dataDevice, &KWayland::Client::DataDevice::dragMotion);
|
|
QSignalSpy dataDeviceDroppedSpy(dataDevice, &KWayland::Client::DataDevice::dropped);
|
|
QSignalSpy dataSourceCancelledSpy(dataSource, &KWayland::Client::DataSource::cancelled);
|
|
QSignalSpy dataSourceDropPerformedSpy(dataSource, &KWayland::Client::DataSource::dragAndDropPerformed);
|
|
|
|
// Create a surface.
|
|
auto surface = Test::createSurface();
|
|
auto shellSurface = Test::createXdgToplevelSurface(surface.get());
|
|
auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 100), Qt::red);
|
|
QVERIFY(window);
|
|
window->move(QPointF(0, 0));
|
|
|
|
// Press the tip of the tablet tool in the center of the window.
|
|
QSignalSpy tabletToolDownSpy(tabletTool, &Test::WpTabletToolV2::down);
|
|
quint32 timestamp = 0;
|
|
Test::tabletToolProximityEvent(QPointF(50, 50), 0, 0, 0, 0, true, 0, timestamp++);
|
|
Test::tabletToolTipEvent(QPointF(50, 50), 1, 0, 0, 0, 0, true, 0, timestamp++);
|
|
QVERIFY(tabletToolDownSpy.wait());
|
|
|
|
// Start a drag-and-drop operation.
|
|
dataDevice->startDrag(tabletToolDownSpy.last().at(0).value<uint32_t>(), dataSource, surface.get());
|
|
QVERIFY(dataDeviceDragEnteredSpy.wait());
|
|
QCOMPARE(dataDeviceDragEnteredSpy.last().at(1).value<QPointF>(), QPointF(50, 50));
|
|
QCOMPARE(dataDevice->dragSurface(), surface.get());
|
|
|
|
// Without accepted mime type, the drag should be canceled when the finger is lifted.
|
|
QSignalSpy dataSourceSelectedDragAndDropActionChangedSpy(dataSource, &KWayland::Client::DataSource::selectedDragAndDropActionChanged);
|
|
auto offer = dataDevice->takeDragOffer();
|
|
offer->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Copy, KWayland::Client::DataDeviceManager::DnDAction::Copy);
|
|
QVERIFY(dataSourceSelectedDragAndDropActionChangedSpy.wait());
|
|
|
|
// Drop.
|
|
Test::tabletToolTipEvent(QPointF(50, 50), 0, 0, 0, 0, 0, false, 0, timestamp++);
|
|
Test::tabletToolProximityEvent(QPointF(50, 50), 0, 0, 0, 0, false, 0, timestamp++);
|
|
QVERIFY(dataDeviceDragLeftSpy.wait());
|
|
QCOMPARE(dataDeviceDroppedSpy.count(), 0);
|
|
QCOMPARE(dataSourceCancelledSpy.count(), 1);
|
|
QCOMPARE(dataSourceDropPerformedSpy.count(), 1);
|
|
}
|
|
|
|
void DndTest::secondTabletDrag()
|
|
{
|
|
// This test verifies that no second drag operation will be started while there's another active drag.
|
|
|
|
std::unique_ptr<Test::WpTabletSeatV2> tabletSeat = Test::tabletManager()->createSeat(Test::waylandSeat());
|
|
QSignalSpy toolAddedSpy(tabletSeat.get(), &Test::WpTabletSeatV2::toolAdded);
|
|
QVERIFY(toolAddedSpy.wait());
|
|
Test::WpTabletToolV2 *tabletTool = toolAddedSpy.last().at(0).value<Test::WpTabletToolV2 *>();
|
|
QVERIFY(Test::waitForWaylandTabletTool(tabletTool));
|
|
|
|
// Setup the data source.
|
|
auto dataDevice = Test::waylandDataDeviceManager()->getDataDevice(Test::waylandSeat(), Test::waylandSeat());
|
|
auto dataSource = Test::waylandDataDeviceManager()->createDataSource(Test::waylandSeat());
|
|
dataSource->offer(QStringLiteral("text/plain"));
|
|
dataSource->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Copy | KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
QSignalSpy dataDeviceDragEnteredSpy(dataDevice, &KWayland::Client::DataDevice::dragEntered);
|
|
QSignalSpy dataDeviceDragLeftSpy(dataDevice, &KWayland::Client::DataDevice::dragLeft);
|
|
QSignalSpy dataDeviceDragMotionSpy(dataDevice, &KWayland::Client::DataDevice::dragMotion);
|
|
QSignalSpy dataDeviceDroppedSpy(dataDevice, &KWayland::Client::DataDevice::dropped);
|
|
QSignalSpy dataSourceCancelledSpy(dataSource, &KWayland::Client::DataSource::cancelled);
|
|
QSignalSpy dataSourceDropPerformedSpy(dataSource, &KWayland::Client::DataSource::dragAndDropPerformed);
|
|
|
|
auto secondDataSource = Test::waylandDataDeviceManager()->createDataSource(Test::waylandSeat());
|
|
secondDataSource->offer(QStringLiteral("text/plain"));
|
|
secondDataSource->setDragAndDropActions(KWayland::Client::DataDeviceManager::DnDAction::Copy | KWayland::Client::DataDeviceManager::DnDAction::Move);
|
|
|
|
// Create a surface.
|
|
auto surface = Test::createSurface();
|
|
auto shellSurface = Test::createXdgToplevelSurface(surface.get());
|
|
auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 100), Qt::red);
|
|
QVERIFY(window);
|
|
window->move(QPointF(0, 0));
|
|
|
|
// Press the tip of the tablet tool in the center of the window.
|
|
QSignalSpy tabletToolDownSpy(tabletTool, &Test::WpTabletToolV2::down);
|
|
quint32 timestamp = 0;
|
|
Test::tabletToolProximityEvent(QPointF(50, 50), 0, 0, 0, 0, true, 0, timestamp++);
|
|
Test::tabletToolTipEvent(QPointF(50, 50), 1, 0, 0, 0, 0, true, 0, timestamp++);
|
|
QVERIFY(tabletToolDownSpy.wait());
|
|
|
|
// Start a drag-and-drop operation.
|
|
const quint32 downSerial = tabletToolDownSpy.last().at(0).value<uint32_t>();
|
|
dataDevice->startDrag(downSerial, dataSource, surface.get());
|
|
QVERIFY(dataDeviceDragEnteredSpy.wait());
|
|
QCOMPARE(dataDeviceDragEnteredSpy.last().at(1).value<QPointF>(), QPointF(50, 50));
|
|
QCOMPARE(dataDevice->dragSurface(), surface.get());
|
|
|
|
// Attempt to start another drag-and-drop operation.
|
|
QSignalSpy secondDataSourceCancelledSpy(secondDataSource, &KWayland::Client::DataSource::cancelled);
|
|
dataDevice->startDrag(downSerial, secondDataSource, surface.get());
|
|
QVERIFY(secondDataSourceCancelledSpy.wait());
|
|
QCOMPARE(dataSourceCancelledSpy.count(), 0);
|
|
|
|
// Drop.
|
|
Test::tabletToolTipEvent(QPointF(50, 50), 0, 0, 0, 0, 0, false, 0, timestamp++);
|
|
Test::tabletToolProximityEvent(QPointF(50, 50), 0, 0, 0, 0, false, 0, timestamp++);
|
|
QVERIFY(dataDeviceDragLeftSpy.wait());
|
|
QCOMPARE(dataDeviceDroppedSpy.count(), 0);
|
|
QCOMPARE(dataSourceCancelledSpy.count(), 1);
|
|
QCOMPARE(dataSourceDropPerformedSpy.count(), 1);
|
|
}
|
|
|
|
} // namespace KWin
|
|
|
|
WAYLANDTEST_MAIN(KWin::DndTest)
|
|
#include "dnd_test.moc"
|