Commit cd2c13bd authored by David Edmundson's avatar David Edmundson

Port DataDevice to the new inheritance approach

This was done mostly because I wanted to get rid of the Resource
dependency in AbstractDataSource so I can make our xwl bridge direct,
but this also fixes up some issues with object lifespan present in the
previous version and keeps all our clipboard code in-line.
parent b439bc5f
......@@ -27,6 +27,8 @@
// Wayland
#include <wayland-client.h>
#include <unistd.h>
class TestDataDevice : public QObject
{
Q_OBJECT
......@@ -101,8 +103,6 @@ void TestDataDevice::init()
registry.setup();
m_dataDeviceManagerInterface = m_display->createDataDeviceManager(m_display);
m_dataDeviceManagerInterface->create();
QVERIFY(m_dataDeviceManagerInterface->isValid());
QVERIFY(dataDeviceManagerSpy.wait());
m_dataDeviceManager = registry.createDataDeviceManager(dataDeviceManagerSpy.first().first().value<quint32>(),
......@@ -179,8 +179,6 @@ void TestDataDevice::testCreate()
QVERIFY(!deviceInterface->origin());
QVERIFY(!deviceInterface->icon());
QVERIFY(!deviceInterface->selection());
QVERIFY(deviceInterface->parentResource());
// this will probably fail, we need to make a selection client side
QVERIFY(!m_seatInterface->selection());
......@@ -433,12 +431,10 @@ void TestDataDevice::testSetSelection()
dataDevice->setSelection(2, dataSource.data());
QVERIFY(selectionChangedSpy.wait());
// now unbind the dataDevice
QSignalSpy unboundSpy(deviceInterface, &DataDeviceInterface::unbound);
QSignalSpy unboundSpy(deviceInterface, &QObject::destroyed);
QVERIFY(unboundSpy.isValid());
dataDevice.reset();
QVERIFY(unboundSpy.wait());
// send a selection to the unbound data device
deviceInterface->sendSelection(deviceInterface->selection());
}
void TestDataDevice::testSendSelectionOnSeat()
......@@ -491,7 +487,7 @@ void TestDataDevice::testSendSelectionOnSeat()
// now let's try to destroy the data device and set a focused keyboard just while the data device is being destroyedd
m_seatInterface->setFocusedKeyboardSurface(nullptr);
QSignalSpy unboundSpy(serverDataDevice, &Resource::unbound);
QSignalSpy unboundSpy(serverDataDevice, &QObject::destroyed);
QVERIFY(unboundSpy.isValid());
dataDevice.reset();
QVERIFY(unboundSpy.wait());
......@@ -575,6 +571,21 @@ void TestDataDevice::testReplaceSource()
dataSource3.reset();
dataDevice2->setSelection(1, dataSource4.data());
QVERIFY(selectionOfferedSpy.wait());
auto dataOffer = selectionOfferedSpy.last()[0].value<DataOffer*>();
// try to crash by destroying the data source, then requesting data
dataSource4.reset();
int pipeFds[2];
Q_ASSERT(pipe(pipeFds) == 0);
dataOffer->receive(QStringLiteral("text/plain"), pipeFds[1]);
close(pipeFds[1]);
//spin the event loop, nothing should explode
QTest::qWait(10);
close(pipeFds[0]);
}
void TestDataDevice::testDestroy()
......
......@@ -29,7 +29,6 @@ private Q_SLOTS:
void testTargetAccepts_data();
void testTargetAccepts();
void testRequestSend();
void testRequestSendOnUnbound();
void testCancel();
void testServerGet();
void testDestroy();
......@@ -82,8 +81,6 @@ void TestDataSource::init()
registry.setup();
m_dataDeviceManagerInterface = m_display->createDataDeviceManager(m_display);
m_dataDeviceManagerInterface->create();
QVERIFY(m_dataDeviceManagerInterface->isValid());
QVERIFY(dataDeviceManagerSpy.wait());
m_dataDeviceManager = registry.createDataDeviceManager(dataDeviceManagerSpy.first().first().value<quint32>(),
......@@ -131,7 +128,6 @@ void TestDataSource::testOffer()
QPointer<DataSourceInterface> serverDataSource = dataSourceCreatedSpy.first().first().value<DataSourceInterface*>();
QVERIFY(!serverDataSource.isNull());
QCOMPARE(serverDataSource->mimeTypes().count(), 0);
QVERIFY(serverDataSource->parentResource());
QSignalSpy offeredSpy(serverDataSource.data(), SIGNAL(mimeTypeOffered(QString)));
QVERIFY(offeredSpy.isValid());
......@@ -161,10 +157,6 @@ void TestDataSource::testOffer()
dataSource.reset();
QVERIFY(!serverDataSource.isNull());
wl_display_flush(m_connection->display());
// after running the event loop the Wayland event should be delivered, but it uses delete later
QCoreApplication::processEvents();
QVERIFY(!serverDataSource.isNull());
// so once more event loop
QCoreApplication::processEvents();
QVERIFY(serverDataSource.isNull());
}
......@@ -212,7 +204,6 @@ void TestDataSource::testRequestSend()
QScopedPointer<DataSource> dataSource(m_dataDeviceManager->createDataSource());
QVERIFY(dataSource->isValid());
QSignalSpy sendRequestedSpy(dataSource.data(), SIGNAL(sendDataRequested(QString,qint32)));
QVERIFY(sendRequestedSpy.isValid());
......@@ -234,28 +225,6 @@ void TestDataSource::testRequestSend()
writeFile.close();
}
void TestDataSource::testRequestSendOnUnbound()
{
// this test verifies that the server doesn't crash when requesting a send on an unbound DataSource
using namespace KWayland::Client;
using namespace KWaylandServer;
QSignalSpy dataSourceCreatedSpy(m_dataDeviceManagerInterface, &DataDeviceManagerInterface::dataSourceCreated);
QVERIFY(dataSourceCreatedSpy.isValid());
QScopedPointer<DataSource> dataSource(m_dataDeviceManager->createDataSource());
QVERIFY(dataSource->isValid());
QVERIFY(dataSourceCreatedSpy.wait());
QCOMPARE(dataSourceCreatedSpy.count(), 1);
auto sds = dataSourceCreatedSpy.first().first().value<DataSourceInterface*>();
QVERIFY(sds);
QSignalSpy unboundSpy(sds, &Resource::unbound);
QVERIFY(unboundSpy.isValid());
dataSource.reset();
QVERIFY(unboundSpy.wait());
sds->requestData(QStringLiteral("text/plain"), -1);
}
void TestDataSource::testCancel()
{
using namespace KWayland::Client;
......
......@@ -84,8 +84,6 @@ void TestDragAndDrop::init()
m_seatInterface->create();
QVERIFY(m_seatInterface->isValid());
m_dataDeviceManagerInterface = m_display->createDataDeviceManager(m_display);
m_dataDeviceManagerInterface->create();
QVERIFY(m_dataDeviceManagerInterface->isValid());
m_display->createShm();
m_thread = new QThread(this);
......
......@@ -72,7 +72,6 @@ void SelectionTest::init()
m_seatInterface->setHasKeyboard(true);
m_seatInterface->create();
m_ddmInterface = m_display->createDataDeviceManager(m_display);
m_ddmInterface->create();
// setup connection
setupConnection(&m_client1);
......
......@@ -1814,7 +1814,6 @@ void TestWaylandSeat::testSelection()
using namespace KWayland::Client;
using namespace KWaylandServer;
QScopedPointer<DataDeviceManagerInterface> ddmi(m_display->createDataDeviceManager());
ddmi->create();
Registry registry;
QSignalSpy dataDeviceManagerSpy(&registry, SIGNAL(dataDeviceManagerAnnounced(quint32,quint32)));
QVERIFY(dataDeviceManagerSpy.isValid());
......@@ -1927,7 +1926,6 @@ void TestWaylandSeat::testDataDeviceForKeyboardSurface()
using namespace KWaylandServer;
// create the DataDeviceManager
QScopedPointer<DataDeviceManagerInterface> ddmi(m_display->createDataDeviceManager());
ddmi->create();
QSignalSpy ddiCreatedSpy(ddmi.data(), &DataDeviceManagerInterface::dataDeviceCreated);
QVERIFY(ddiCreatedSpy.isValid());
......
......@@ -110,7 +110,7 @@ public:
AbstractDataSource(nullptr)
{}
~TestDataSource() {
emit unbound();
emit aboutToBeDestroyed();
}
void requestData(const QString &mimeType, qint32 fd) override {
Q_UNUSED(mimeType);
......
......@@ -8,6 +8,6 @@
using namespace KWaylandServer;
AbstractDataSource::AbstractDataSource(Resource::Private *d, QObject *parent)
: Resource(d, parent)
AbstractDataSource::AbstractDataSource(QObject *parent)
: QObject(parent)
{}
......@@ -27,10 +27,7 @@ namespace KWaylandServer {
// Anything related to selections are pure virtual, content relating
// to drag and drop has a default implementation
// TODO ideally this shouldn't inherit from resource as it provides some misleading public methods
// This can be resolved once DataSource is ported to the new system
class KWAYLANDSERVER_EXPORT AbstractDataSource : public Resource
class KWAYLANDSERVER_EXPORT AbstractDataSource : public QObject
{
Q_OBJECT
public:
......@@ -64,16 +61,18 @@ public:
Q_UNUSED(action);
};
virtual wl_client* client() {
return Resource::client()->client();
}
virtual wl_client* client() const {
return nullptr;
};
Q_SIGNALS:
void aboutToBeDestroyed();
void mimeTypeOffered(const QString&);
void supportedDragAndDropActionsChanged();
protected:
explicit AbstractDataSource(Resource::Private *d, QObject *parent = nullptr);
explicit AbstractDataSource(QObject *parent = nullptr);
};
}
......@@ -40,7 +40,7 @@ DataControlSourceV1InterfacePrivate::DataControlSourceV1InterfacePrivate(DataCon
void DataControlSourceV1InterfacePrivate::zwlr_data_control_source_v1_destroy_resource(QtWaylandServer::zwlr_data_control_source_v1::Resource *resource)
{
Q_UNUSED(resource)
emit q->unbound();
emit q->aboutToBeDestroyed();
delete q;
}
......@@ -56,7 +56,7 @@ void DataControlSourceV1InterfacePrivate::zwlr_data_control_source_v1_destroy(Qt
}
DataControlSourceV1Interface::DataControlSourceV1Interface(DataControlDeviceManagerV1Interface *parent, ::wl_resource *resource)
: AbstractDataSource(nullptr, parent)
: AbstractDataSource(parent)
, d(new DataControlSourceV1InterfacePrivate(this, resource))
{
}
......@@ -79,7 +79,7 @@ QStringList DataControlSourceV1Interface::mimeTypes() const
return d->mimeTypes;
}
wl_client *DataControlSourceV1Interface::client()
wl_client *DataControlSourceV1Interface::client() const
{
return d->resource()->client();
}
......
......@@ -33,7 +33,7 @@ public:
void cancel() override;
QStringList mimeTypes() const override;
wl_client *client() override;
wl_client *client() const override;
static DataControlSourceV1Interface *get(wl_resource *native);
......
/*
SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2020 David Edmundson <davidedmundson@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "datadevice_interface.h"
#include "datadevicemanager_interface.h"
#include "dataoffer_interface_p.h"
#include "datasource_interface.h"
#include "dataoffer_interface.h"
#include "display.h"
#include "resource_p.h"
#include "pointer_interface.h"
#include "seat_interface.h"
#include "seat_interface_p.h"
#include "surface_interface.h"
// Wayland
#include <wayland-server.h>
#include <qwayland-server-wayland.h>
namespace KWaylandServer
{
class DataDeviceInterface::Private : public Resource::Private
class DataDeviceInterfacePrivate : public QtWaylandServer::wl_data_device
{
public:
Private(SeatInterface *seat, DataDeviceInterface *q, DataDeviceManagerInterface *manager, wl_resource *parentResource);
~Private();
DataDeviceInterfacePrivate(SeatInterface *seat, DataDeviceInterface *_q, wl_resource *resource);
DataOfferInterface *createDataOffer(AbstractDataSource *source);
......@@ -31,9 +31,7 @@ public:
SurfaceInterface *surface = nullptr;
SurfaceInterface *icon = nullptr;
DataSourceInterface *selection = nullptr;
QMetaObject::Connection selectionUnboundConnection;
QMetaObject::Connection selectionDestroyedConnection;
QPointer<DataSourceInterface> selection;
struct Drag {
SurfaceInterface *surface = nullptr;
......@@ -45,47 +43,34 @@ public:
};
Drag drag;
QPointer<SurfaceInterface> proxyRemoteSurface;
private:
DataDeviceInterface *q_func() {
return reinterpret_cast<DataDeviceInterface*>(q);
}
void startDrag(DataSourceInterface *dataSource, SurfaceInterface *origin, SurfaceInterface *icon, quint32 serial);
void setSelection(DataSourceInterface *dataSource);
static void startDragCallback(wl_client *client, wl_resource *resource, wl_resource *source, wl_resource *origin, wl_resource *icon, uint32_t serial);
static void setSelectionCallback(wl_client *client, wl_resource *resource, wl_resource *source, uint32_t serial);
DataDeviceInterface *q;
static const struct wl_data_device_interface s_interface;
};
QPointer<SurfaceInterface> proxyRemoteSurface;
#ifndef K_DOXYGEN
const struct wl_data_device_interface DataDeviceInterface::Private::s_interface = {
startDragCallback,
setSelectionCallback,
resourceDestroyedCallback
protected:
void data_device_destroy_resource(Resource *resource) override;
void data_device_start_drag(Resource *resource, wl_resource *source, wl_resource *origin, wl_resource *icon, uint32_t serial) override;
void data_device_set_selection(Resource *resource, wl_resource *source, uint32_t serial) override;
void data_device_release(Resource *resource) override;
};
#endif
DataDeviceInterface::Private::Private(SeatInterface *seat, DataDeviceInterface *q, DataDeviceManagerInterface *manager, wl_resource *parentResource)
: Resource::Private(q, manager, parentResource, &wl_data_device_interface, &s_interface)
DataDeviceInterfacePrivate::DataDeviceInterfacePrivate(SeatInterface *seat, DataDeviceInterface *_q, wl_resource *resource)
: QtWaylandServer::wl_data_device(resource)
, seat(seat)
, q(_q)
{
}
DataDeviceInterface::Private::~Private() = default;
void DataDeviceInterface::Private::startDragCallback(wl_client *client, wl_resource *resource, wl_resource *source, wl_resource *origin, wl_resource *icon, uint32_t serial)
void DataDeviceInterfacePrivate::data_device_start_drag(Resource *resource, wl_resource *sourceResource, wl_resource *originResource, wl_resource *iconResource, uint32_t serial)
{
Q_UNUSED(client)
Q_UNUSED(serial)
// TODO: verify serial
cast<Private>(resource)->startDrag(DataSourceInterface::get(source), SurfaceInterface::get(origin), SurfaceInterface::get(icon), serial);
}
Q_UNUSED(resource)
SurfaceInterface *focusSurface = SurfaceInterface::get(originResource);
SurfaceInterface *i = SurfaceInterface::get(iconResource);
DataSourceInterface *dataSource = nullptr;
if (sourceResource) {
dataSource = DataSourceInterface::get(sourceResource);
}
void DataDeviceInterface::Private::startDrag(DataSourceInterface *dataSource, SurfaceInterface *origin, SurfaceInterface *i, quint32 serial)
{
SurfaceInterface *focusSurface = origin;
if (proxyRemoteSurface) {
// origin is a proxy surface
focusSurface = proxyRemoteSurface.data();
......@@ -100,144 +85,123 @@ void DataDeviceInterface::Private::startDrag(DataSourceInterface *dataSource, Su
}
}
// TODO: source is allowed to be null, handled client internally!
Q_Q(DataDeviceInterface);
source = dataSource;
if (dataSource) {
QObject::connect(dataSource, &Resource::aboutToBeUnbound, q, [this] { source = nullptr; });
QObject::connect(dataSource, &AbstractDataSource::aboutToBeDestroyed, q, [this] { source = nullptr; });
}
surface = origin;
surface = focusSurface;
icon = i;
drag.serial = serial;
emit q->dragStarted();
}
void DataDeviceInterface::Private::setSelectionCallback(wl_client *client, wl_resource *resource, wl_resource *source, uint32_t serial)
void DataDeviceInterfacePrivate::data_device_set_selection(Resource *resource, wl_resource *source, uint32_t serial)
{
Q_UNUSED(client)
Q_UNUSED(resource)
Q_UNUSED(serial)
// TODO: verify serial
cast<Private>(resource)->setSelection(DataSourceInterface::get(source));
}
DataSourceInterface *dataSource = DataSourceInterface::get(source);
void DataDeviceInterface::Private::setSelection(DataSourceInterface *dataSource)
{
if (dataSource && dataSource->supportedDragAndDropActions() && wl_resource_get_version(dataSource->resource()) >= WL_DATA_SOURCE_ACTION_SINCE_VERSION) {
wl_resource_post_error(dataSource->resource(), WL_DATA_SOURCE_ERROR_INVALID_SOURCE, "Data source is for drag and drop");
wl_resource_post_error(dataSource->resource(), QtWaylandServer::wl_data_source::error_invalid_source, "Data source is for drag and drop");
return;
}
if (selection == dataSource) {
return;
}
Q_Q(DataDeviceInterface);
QObject::disconnect(selectionUnboundConnection);
QObject::disconnect(selectionDestroyedConnection);
if (selection) {
selection->cancel();
}
selection = dataSource;
if (selection) {
auto clearSelection = [this] {
setSelection(nullptr);
};
selectionUnboundConnection = QObject::connect(selection, &Resource::unbound, q, clearSelection);
selectionDestroyedConnection = QObject::connect(selection, &QObject::destroyed, q, clearSelection);
emit q->selectionChanged(selection);
} else {
selectionUnboundConnection = QMetaObject::Connection();
selectionDestroyedConnection = QMetaObject::Connection();
emit q->selectionCleared();
}
}
DataOfferInterface *DataDeviceInterface::Private::createDataOffer(AbstractDataSource *source)
void DataDeviceInterfacePrivate::data_device_release(QtWaylandServer::wl_data_device::Resource *resource)
{
wl_resource_destroy(resource->handle);
}
DataOfferInterface *DataDeviceInterfacePrivate::createDataOffer(AbstractDataSource *source)
{
if (!resource) {
return nullptr;
}
if (!source) {
// a data offer can only exist together with a source
return nullptr;
}
Q_Q(DataDeviceInterface);
DataOfferInterface *offer = new DataOfferInterface(source, q, resource);
auto c = q->global()->display()->getConnection(wl_resource_get_client(resource));
offer->create(c, wl_resource_get_version(resource), 0);
if (!offer->resource()) {
// TODO: send error?
delete offer;
wl_resource *data_offer_resource = wl_resource_create(resource()->client(), &wl_data_offer_interface, resource()->version(), 0);
if (!data_offer_resource) {
wl_resource_post_no_memory(resource()->handle);
return nullptr;
}
wl_data_device_send_data_offer(resource, offer->resource());
DataOfferInterface *offer = new DataOfferInterface(source, data_offer_resource);
send_data_offer(offer->resource());
offer->sendAllOffers();
return offer;
}
DataDeviceInterface::DataDeviceInterface(SeatInterface *seat, DataDeviceManagerInterface *parent, wl_resource *parentResource)
: Resource(new Private(seat, this, parent, parentResource))
void DataDeviceInterfacePrivate::data_device_destroy_resource(QtWaylandServer::wl_data_device::Resource *resource)
{
Q_UNUSED(resource)
delete q;
}
DataDeviceInterface::DataDeviceInterface(SeatInterface *seat, wl_resource *resource)
: QObject(nullptr)
, d(new DataDeviceInterfacePrivate(seat, this, resource))
{
seat->d_func()->registerDataDevice(this);
}
DataDeviceInterface::~DataDeviceInterface() = default;
SeatInterface *DataDeviceInterface::seat() const
{
Q_D();
return d->seat;
}
DataSourceInterface *DataDeviceInterface::dragSource() const
{
Q_D();
return d->source;
}
SurfaceInterface *DataDeviceInterface::icon() const
{
Q_D();
return d->icon;
}
SurfaceInterface *DataDeviceInterface::origin() const
{
Q_D();
return d->proxyRemoteSurface ? d->proxyRemoteSurface.data() : d->surface;
}
DataSourceInterface *DataDeviceInterface::selection() const
{
Q_D();
return d->selection;
}
void DataDeviceInterface::sendSelection(AbstractDataSource *other)
{
Q_D();
auto r = d->createDataOffer(other);
if (!r) {
return;
}
if (!d->resource) {
return;
}
wl_data_device_send_selection(d->resource, r->resource());
d->send_selection(r->resource());
}
void DataDeviceInterface::sendClearSelection()
{
Q_D();
if (!d->resource) {
return;
}
wl_data_device_send_selection(d->resource, nullptr);
d->send_selection(nullptr);
}
void DataDeviceInterface::drop()
{
Q_D();
if (!d->resource) {
return;
}
wl_data_device_send_drop(d->resource);
d->send_drop();
if (d->drag.posConnection) {
disconnect(d->drag.posConnection);
d->drag.posConnection = QMetaObject::Connection();
......@@ -245,15 +209,14 @@ void DataDeviceInterface::drop()
disconnect(d->drag.destroyConnection);
d->drag.destroyConnection = QMetaObject::Connection();
d->drag.surface = nullptr;
client()->flush();
wl_client_flush(d->resource()->client());
}
void DataDeviceInterface::updateDragTarget(SurfaceInterface *surface, quint32 serial)
{
Q_D();
if (d->drag.surface) {
if (d->resource && d->drag.surface->resource()) {
wl_data_device_send_leave(d->resource);
if (d->drag.surface->resource()) {
d->send_leave();
}
if (d->drag.posConnection) {
disconnect(d->drag.posConnection);
......@@ -289,48 +252,43 @@ void DataDeviceInterface::updateDragTarget(SurfaceInterface *surface, quint32 se
if (d->seat->isDragPointer()) {
d->drag.posConnection = connect(d->seat, &SeatInterface::pointerPosChanged, this,
[this] {
Q_D();
const QPointF pos = d->seat->dragSurfaceTransformation().map(d->seat->pointerPos());
wl_data_device_send_motion(d->resource, d->seat->timestamp(),
d->send_motion(d->seat->timestamp(),
wl_fixed_from_double(pos.x()), wl_fixed_from_double(pos.y()));
client()->flush();
wl_client_flush(d->resource()->client());
}
);
} else if (d->seat->isDragTouch()) {
d->drag.posConnection = connect(d->seat, &SeatInterface::touchMoved, this,
[this](qint32 id, quint32 serial, const QPointF &globalPosition) {
Q_D();
Q_UNUSED(id);
if (serial != d->drag.serial) {
// different touch down has been moved
return;
}