From 1c412b0f160e9aa2d3dc376049d2105d12acb3d8 Mon Sep 17 00:00:00 2001 From: Vlad Zahorodnii Date: Thu, 29 Oct 2020 10:29:30 +0200 Subject: [PATCH] Introduce DragAndDropIcon Every time the icon surface is committed, we need to accumulate the surface offset. However, this is not easy to do on the compositor side with the current design. This change introduces a new class DragAndDropIcon that defines a surface role as well as accumulates surface offsets on every commit. This fixes a bug where the dnd icon jumps all of sudden while dragging an image from Firefox to other applications. --- .../autotests/client/test_datadevice.cpp | 7 +- src/wayland/datadevice_interface.cpp | 98 ++++++++++++------- src/wayland/datadevice_interface.h | 38 ++++++- src/wayland/datadevice_interface_p.h | 60 ++++++++++++ src/wayland/seat_interface.cpp | 18 ++-- 5 files changed, 178 insertions(+), 43 deletions(-) create mode 100644 src/wayland/datadevice_interface_p.h diff --git a/src/wayland/autotests/client/test_datadevice.cpp b/src/wayland/autotests/client/test_datadevice.cpp index a0db9b5b3d..f3f09922ad 100644 --- a/src/wayland/autotests/client/test_datadevice.cpp +++ b/src/wayland/autotests/client/test_datadevice.cpp @@ -352,7 +352,12 @@ void TestDataDevice::testDragInternally() QCOMPARE(!dragStartedSpy.isEmpty(), success); QVERIFY(!deviceInterface->dragSource()); QCOMPARE(deviceInterface->origin(), success ? surfaceInterface : nullptr); - QCOMPARE(deviceInterface->icon(), success ? iconSurfaceInterface : nullptr); + + if (success) { + QCOMPARE(deviceInterface->icon()->surface(), iconSurfaceInterface); + } else { + QCOMPARE(deviceInterface->icon(), nullptr); + } } void TestDataDevice::testSetSelection() diff --git a/src/wayland/datadevice_interface.cpp b/src/wayland/datadevice_interface.cpp index 8b28d25f15..29b6d8f49b 100644 --- a/src/wayland/datadevice_interface.cpp +++ b/src/wayland/datadevice_interface.cpp @@ -1,58 +1,70 @@ /* SPDX-FileCopyrightText: 2014 Martin Gräßlin SPDX-FileCopyrightText: 2020 David Edmundson + SPDX-FileCopyrightText: 2020 Vlad Zahorodnii SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ #include "datadevice_interface.h" +#include "datadevice_interface_p.h" #include "datadevicemanager_interface.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" -#include +#include "surfacerole_p.h" namespace KWaylandServer { -class DataDeviceInterfacePrivate : public QtWaylandServer::wl_data_device +class DragAndDropIconPrivate : public SurfaceRole { public: - DataDeviceInterfacePrivate(SeatInterface *seat, DataDeviceInterface *_q, wl_resource *resource); + explicit DragAndDropIconPrivate(SurfaceInterface *surface); - DataOfferInterface *createDataOffer(AbstractDataSource *source); + void commit() override; - SeatInterface *seat; - DataSourceInterface *source = nullptr; - SurfaceInterface *surface = nullptr; - QPointer icon; - QPointer selection; - - struct Drag { - SurfaceInterface *surface = nullptr; - QMetaObject::Connection destroyConnection; - QMetaObject::Connection posConnection; - QMetaObject::Connection sourceActionConnection; - QMetaObject::Connection targetActionConnection; - quint32 serial = 0; - }; - Drag drag; - - DataDeviceInterface *q; - - QPointer proxyRemoteSurface; - -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; + QPoint position; }; +DragAndDropIconPrivate::DragAndDropIconPrivate(SurfaceInterface *surface) + : SurfaceRole(surface, QByteArrayLiteral("dnd_icon")) +{ +} + +void DragAndDropIconPrivate::commit() +{ + position += surface()->offset(); +} + +DragAndDropIcon::DragAndDropIcon(SurfaceInterface *surface, QObject *parent) + : QObject(parent) + , d(new DragAndDropIconPrivate(surface)) +{ +} + +DragAndDropIcon::~DragAndDropIcon() +{ +} + +QPoint DragAndDropIcon::position() const +{ + return d->position; +} + +SurfaceInterface *DragAndDropIcon::surface() const +{ + return d->surface(); +} + +DataDeviceInterfacePrivate *DataDeviceInterfacePrivate::get(DataDeviceInterface *device) +{ + return device->d.data(); +} + DataDeviceInterfacePrivate::DataDeviceInterfacePrivate(SeatInterface *seat, DataDeviceInterface *_q, wl_resource *resource) : QtWaylandServer::wl_data_device(resource) , seat(seat) @@ -60,11 +72,24 @@ DataDeviceInterfacePrivate::DataDeviceInterfacePrivate(SeatInterface *seat, Data { } +void DataDeviceInterfacePrivate::endDrag() +{ + icon.reset(); +} + void DataDeviceInterfacePrivate::data_device_start_drag(Resource *resource, wl_resource *sourceResource, wl_resource *originResource, wl_resource *iconResource, uint32_t serial) { - Q_UNUSED(resource) + SurfaceInterface *iconSurface = SurfaceInterface::get(iconResource); + + const SurfaceRole *surfaceRole = SurfaceRole::get(iconSurface); + if (surfaceRole) { + wl_resource_post_error(resource->handle, error_role, + "the icon surface already has a role assigned %s", + surfaceRole->name().constData()); + return; + } + SurfaceInterface *focusSurface = SurfaceInterface::get(originResource); - SurfaceInterface *i = SurfaceInterface::get(iconResource); DataSourceInterface *dataSource = nullptr; if (sourceResource) { dataSource = DataSourceInterface::get(sourceResource); @@ -88,8 +113,11 @@ void DataDeviceInterfacePrivate::data_device_start_drag(Resource *resource, wl_r if (dataSource) { QObject::connect(dataSource, &AbstractDataSource::aboutToBeDestroyed, q, [this] { source = nullptr; }); } + if (iconSurface) { + icon.reset(new DragAndDropIcon(iconSurface)); + QObject::connect(iconSurface, &SurfaceInterface::aboutToBeDestroyed, icon.data(), [this] { icon.reset(); }); + } surface = focusSurface; - icon = i; drag.serial = serial; emit q->dragStarted(); } @@ -169,9 +197,9 @@ DataSourceInterface *DataDeviceInterface::dragSource() const return d->source; } -SurfaceInterface *DataDeviceInterface::icon() const +DragAndDropIcon *DataDeviceInterface::icon() const { - return d->icon; + return d->icon.data(); } SurfaceInterface *DataDeviceInterface::origin() const diff --git a/src/wayland/datadevice_interface.h b/src/wayland/datadevice_interface.h index 750cba319b..991220a23b 100644 --- a/src/wayland/datadevice_interface.h +++ b/src/wayland/datadevice_interface.h @@ -1,6 +1,7 @@ /* SPDX-FileCopyrightText: 2014 Martin Gräßlin SPDX-FileCopyrightText: 2020 David Edmundson + SPDX-FileCopyrightText: 2020 Vlad Zahorodnii SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ @@ -23,6 +24,36 @@ class AbstractDataSource; class SeatInterface; class SurfaceInterface; class DataDeviceInterfacePrivate; +class DragAndDropIconPrivate; + +/** + * The DragAndDropIcon class represents a drag-and-drop icon. + * + * Note that the lifetime of the drag-and-drop icon is bound to the lifetime of the underlying + * icon surface. + */ +class KWAYLANDSERVER_EXPORT DragAndDropIcon : public QObject +{ + Q_OBJECT + +public: + ~DragAndDropIcon() override; + + /** + * Returns the position of the icon relative to the cursor's hotspot. + */ + QPoint position() const; + + /** + * Returns the underlying icon surface. This function always returns a valid surface. + */ + SurfaceInterface *surface() const; + +private: + explicit DragAndDropIcon(SurfaceInterface *surface, QObject *parent = nullptr); + friend class DataDeviceInterfacePrivate; + QScopedPointer d; +}; /** * @brief DataDeviceInterface allows clients to share data by copy-and-paste and drag-and-drop. @@ -44,7 +75,11 @@ public: SeatInterface *seat() const; DataSourceInterface *dragSource() const; SurfaceInterface *origin() const; - SurfaceInterface *icon() const; + /** + * Returns the additional icon attached to the cursor during a drag-and-drop operation. + * This function returns @c null if no drag-and-drop icon has been attached. + */ + DragAndDropIcon *icon() const; /** * @returns the serial of the implicit grab which started the drag @@ -91,6 +126,7 @@ private: friend class DataDeviceManagerInterfacePrivate; explicit DataDeviceInterface(SeatInterface *seat, wl_resource *resource); QScopedPointer d; + friend class DataDeviceInterfacePrivate; }; } diff --git a/src/wayland/datadevice_interface_p.h b/src/wayland/datadevice_interface_p.h new file mode 100644 index 0000000000..9d34db2c33 --- /dev/null +++ b/src/wayland/datadevice_interface_p.h @@ -0,0 +1,60 @@ +/* + SPDX-FileCopyrightText: 2014 Martin Gräßlin + SPDX-FileCopyrightText: 2020 David Edmundson + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#pragma once + +#include + +#include "qwayland-server-wayland.h" + +namespace KWaylandServer +{ + +class AbstractDataSource; +class DataDeviceInterface; +class DataOfferInterface; +class DataSourceInterface; +class DragAndDropIcon; +class SeatInterface; +class SurfaceInterface; + +class DataDeviceInterfacePrivate : public QtWaylandServer::wl_data_device +{ +public: + static DataDeviceInterfacePrivate *get(DataDeviceInterface *device); + + DataDeviceInterfacePrivate(SeatInterface *seat, DataDeviceInterface *_q, wl_resource *resource); + + DataOfferInterface *createDataOffer(AbstractDataSource *source); + void endDrag(); + + SeatInterface *seat; + DataDeviceInterface *q; + DataSourceInterface *source = nullptr; + SurfaceInterface *surface = nullptr; + QScopedPointer icon; + QPointer selection; + QPointer proxyRemoteSurface; + + struct Drag { + SurfaceInterface *surface = nullptr; + QMetaObject::Connection destroyConnection; + QMetaObject::Connection posConnection; + QMetaObject::Connection sourceActionConnection; + QMetaObject::Connection targetActionConnection; + quint32 serial = 0; + }; + Drag drag; + +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; +}; + +} // namespace KWaylandServer diff --git a/src/wayland/seat_interface.cpp b/src/wayland/seat_interface.cpp index 0943dcc25e..a1e1d191a3 100644 --- a/src/wayland/seat_interface.cpp +++ b/src/wayland/seat_interface.cpp @@ -9,6 +9,7 @@ #include "seat_interface_p.h" #include "display.h" #include "datadevice_interface.h" +#include "datadevice_interface_p.h" #include "datasource_interface.h" #include "datacontroldevice_v1_interface.h" #include "datacontrolsource_v1_interface.h" @@ -399,12 +400,13 @@ void SeatInterface::Private::endDrag(quint32 serial) QObject::disconnect(drag.destroyConnection); QObject::disconnect(drag.dragSourceDestroyConnection); - DataDeviceInterface *dragTarget = drag.target; - DataSourceInterface *dragSource = drag.source ? drag.source->dragSource() : nullptr; + DataDeviceInterface *dragTargetDevice = drag.target; + DataDeviceInterface *dragSourceDevice = drag.source; + DataSourceInterface *dragSource = dragSourceDevice ? dragSourceDevice->dragSource() : nullptr; if (dragSource) { // TODO: Also check the current drag-and-drop action. - if (dragTarget && dragSource->isAccepted()) { - dragTarget->drop(); + if (dragTargetDevice && dragSource->isAccepted()) { + dragTargetDevice->drop(); dragSource->dropPerformed(); } else { if (wl_resource_get_version(dragSource->resource()) >= @@ -414,8 +416,12 @@ void SeatInterface::Private::endDrag(quint32 serial) } } - if (dragTarget) { - dragTarget->updateDragTarget(nullptr, serial); + if (dragTargetDevice) { + dragTargetDevice->updateDragTarget(nullptr, serial); + } + if (dragSourceDevice) { + auto devicePrivate = DataDeviceInterfacePrivate::get(dragSourceDevice); + devicePrivate->endDrag(); } drag = Drag();