diff --git a/autotests/client/test_drag_drop.cpp b/autotests/client/test_drag_drop.cpp --- a/autotests/client/test_drag_drop.cpp +++ b/autotests/client/test_drag_drop.cpp @@ -218,6 +218,9 @@ auto serverSurface = getServerSurface(); QVERIFY(serverSurface); + QSignalSpy dataSourceSelectedActionChangedSpy(m_dataSource, &DataSource::selectedDragAndDropActionChanged); + QVERIFY(dataSourceSelectedActionChangedSpy.isValid()); + // now we need to pass pointer focus to the Surface and simulate a button press QSignalSpy buttonPressSpy(m_pointer, &Pointer::buttonStateChanged); QVERIFY(buttonPressSpy.isValid()); @@ -234,10 +237,13 @@ QVERIFY(dragMotionSpy.isValid()); QSignalSpy pointerMotionSpy(m_pointer, &Pointer::motion); QVERIFY(pointerMotionSpy.isValid()); + QSignalSpy sourceDropSpy(m_dataSource, &DataSource::dragAndDropPerformed); + QVERIFY(sourceDropSpy.isValid()); // now we can start the drag and drop QSignalSpy dragStartedSpy(m_seatInterface, &SeatInterface::dragStarted); QVERIFY(dragStartedSpy.isValid()); + m_dataSource->setDragAndDropActions(DataDeviceManager::DnDAction::Copy | DataDeviceManager::DnDAction::Move); m_dataDevice->startDrag(buttonPressSpy.first().first().value(), m_dataSource, s.data()); QVERIFY(dragStartedSpy.wait()); QCOMPARE(m_seatInterface->dragSurface(), serverSurface); @@ -249,8 +255,20 @@ QCOMPARE(dragEnteredSpy.first().first().value(), m_display->serial()); QCOMPARE(dragEnteredSpy.first().last().toPointF(), QPointF(0, 0)); QCOMPARE(m_dataDevice->dragSurface().data(), s.data()); + auto offer = m_dataDevice->dragOffer(); + QVERIFY(offer); + QCOMPARE(offer->selectedDragAndDropAction(), DataDeviceManager::DnDAction::None); + QSignalSpy offerActionChangedSpy(offer, &DataOffer::selectedDragAndDropActionChanged); + QVERIFY(offerActionChangedSpy.isValid()); QCOMPARE(m_dataDevice->dragOffer()->offeredMimeTypes().count(), 1); QCOMPARE(m_dataDevice->dragOffer()->offeredMimeTypes().first().name(), QStringLiteral("text/plain")); + QTRY_COMPARE(offer->sourceDragAndDropActions(), DataDeviceManager::DnDAction::Copy | DataDeviceManager::DnDAction::Move); + offer->setDragAndDropActions(DataDeviceManager::DnDAction::Copy | DataDeviceManager::DnDAction::Move, DataDeviceManager::DnDAction::Move); + QVERIFY(offerActionChangedSpy.wait()); + QCOMPARE(offerActionChangedSpy.count(), 1); + QCOMPARE(offer->selectedDragAndDropAction(), DataDeviceManager::DnDAction::Move); + QCOMPARE(dataSourceSelectedActionChangedSpy.count(), 1); + QCOMPARE(m_dataSource->selectedDragAndDropAction(), DataDeviceManager::DnDAction::Move); // simulate motion m_seatInterface->setTimestamp(3); @@ -261,18 +279,23 @@ QCOMPARE(dragMotionSpy.first().last().toUInt(), 3u); // simulate drop - QSignalSpy leftSpy(m_dataDevice, &DataDevice::dragLeft); - QVERIFY(leftSpy.isValid()); QSignalSpy serverDragEndedSpy(m_seatInterface, &SeatInterface::dragEnded); QVERIFY(serverDragEndedSpy.isValid()); QSignalSpy droppedSpy(m_dataDevice, &DataDevice::dropped); QVERIFY(droppedSpy.isValid()); m_seatInterface->setTimestamp(4); m_seatInterface->pointerButtonReleased(1); + QVERIFY(sourceDropSpy.isEmpty()); QVERIFY(droppedSpy.wait()); - QCOMPARE(leftSpy.count(), 1); + QCOMPARE(sourceDropSpy.count(), 1); QCOMPARE(serverDragEndedSpy.count(), 1); + QSignalSpy finishedSpy(m_dataSource, &DataSource::dragAndDropFinished); + QVERIFY(finishedSpy.isValid()); + offer->dragAndDropFinished(); + QVERIFY(finishedSpy.wait()); + delete offer; + // verify that we did not get any further input events QVERIFY(pointerMotionSpy.isEmpty()); QCOMPARE(buttonPressSpy.count(), 1); diff --git a/src/client/datadevice.cpp b/src/client/datadevice.cpp --- a/src/client/datadevice.cpp +++ b/src/client/datadevice.cpp @@ -41,7 +41,7 @@ WaylandPointer device; QScopedPointer selectionOffer; struct Drag { - DataOffer *offer = nullptr; + QPointer offer; QPointer surface; }; Drag drag; @@ -174,6 +174,9 @@ DataDevice::~DataDevice() { + if (d->drag.offer) { + delete d->drag.offer; + } release(); } diff --git a/src/client/datadevicemanager.h b/src/client/datadevicemanager.h --- a/src/client/datadevicemanager.h +++ b/src/client/datadevicemanager.h @@ -63,6 +63,18 @@ { Q_OBJECT public: + /** + * Drag and Drop actions supported by DataSource and DataOffer. + * @since 5.42 + **/ + enum class DnDAction { + None = 0, + Copy = 1 << 0, + Move = 1 << 1, + Ask = 1 << 2 + }; + Q_DECLARE_FLAGS(DnDActions, DnDAction) + /** * Creates a new Compositor. * Note: after constructing the Compositor it is not yet valid and one needs @@ -139,4 +151,6 @@ } } +Q_DECLARE_OPERATORS_FOR_FLAGS(KWayland::Client::DataDeviceManager::DnDActions) + #endif diff --git a/src/client/dataoffer.h b/src/client/dataoffer.h --- a/src/client/dataoffer.h +++ b/src/client/dataoffer.h @@ -24,6 +24,8 @@ #include +#include "datadevicemanager.h" + struct wl_data_offer; class QMimeType; @@ -79,11 +81,54 @@ void receive(const QMimeType &mimeType, qint32 fd); void receive(const QString &mimeType, qint32 fd); + /** + * Notifies the compositor that the drag destination successfully + * finished the drag-and-drop operation. + * + * After this operation it is only allowed to release the DataOffer. + * + * @since 5.42 + **/ + void dragAndDropFinished(); + + /** + * The actions offered by the DataSource. + * @since 5.42 + * @see sourceDragAndDropActionsChanged + **/ + DataDeviceManager::DnDActions sourceDragAndDropActions() const; + + /** + * Sets the @p supported and @p preferred Drag and Drop actions. + * @since 5.42 + **/ + void setDragAndDropActions(DataDeviceManager::DnDActions supported, DataDeviceManager::DnDAction preferred); + + /** + * The currently selected drag and drop action by the compositor. + * @see selectedDragAndDropActionChanged + * @since 5.42 + **/ + DataDeviceManager::DnDAction selectedDragAndDropAction() const; + operator wl_data_offer*(); operator wl_data_offer*() const; Q_SIGNALS: void mimeTypeOffered(const QString&); + /** + * Emitted whenever the @link{sourceDragAndDropActions} changed, e.g. on enter or when + * the DataSource changes the supported actions. + * @see sourceDragAndDropActions + * @since 5.42 + **/ + void sourceDragAndDropActionsChanged(); + /** + * Emitted whenever the selected drag and drop action changes. + * @see selectedDragAndDropAction + * @since 5.42 + **/ + void selectedDragAndDropActionChanged(); private: friend class DataDevice; diff --git a/src/client/dataoffer.cpp b/src/client/dataoffer.cpp --- a/src/client/dataoffer.cpp +++ b/src/client/dataoffer.cpp @@ -38,18 +38,25 @@ Private(wl_data_offer *offer, DataOffer *q); WaylandPointer dataOffer; QList mimeTypes; + DataDeviceManager::DnDActions sourceActions = DataDeviceManager::DnDAction::None; + DataDeviceManager::DnDAction selectedAction = DataDeviceManager::DnDAction::None; private: void offer(const QString &mimeType); + void setAction(DataDeviceManager::DnDAction action); static void offerCallback(void *data, wl_data_offer *dataOffer, const char *mimeType); + static void sourceActionsCallback(void *data, wl_data_offer *wl_data_offer, uint32_t source_actions); + static void actionCallback(void *data, wl_data_offer *wl_data_offer, uint32_t dnd_action); DataOffer *q; static const struct wl_data_offer_listener s_listener; }; #ifndef DOXYGEN_SHOULD_SKIP_THIS const struct wl_data_offer_listener DataOffer::Private::s_listener = { - offerCallback + offerCallback, + sourceActionsCallback, + actionCallback }; #endif @@ -77,6 +84,57 @@ } } +void DataOffer::Private::sourceActionsCallback(void *data, wl_data_offer *wl_data_offer, uint32_t source_actions) +{ + Q_UNUSED(wl_data_offer) + DataDeviceManager::DnDActions actions; + if (source_actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY) { + actions |= DataDeviceManager::DnDAction::Copy; + } + if (source_actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE) { + actions |= DataDeviceManager::DnDAction::Move; + } + if (source_actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK) { + actions |= DataDeviceManager::DnDAction::Ask; + } + auto d = reinterpret_cast(data); + if (d->sourceActions != actions) { + d->sourceActions = actions; + emit d->q->sourceDragAndDropActionsChanged(); + } +} + +void DataOffer::Private::actionCallback(void *data, wl_data_offer *wl_data_offer, uint32_t dnd_action) +{ + Q_UNUSED(wl_data_offer) + auto d = reinterpret_cast(data); + switch(dnd_action) { + case WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY: + d->setAction(DataDeviceManager::DnDAction::Copy); + break; + case WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE: + d->setAction(DataDeviceManager::DnDAction::Move); + break; + case WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK: + d->setAction(DataDeviceManager::DnDAction::Ask); + break; + case WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE: + d->setAction(DataDeviceManager::DnDAction::None); + break; + default: + Q_UNREACHABLE(); + } +} + +void DataOffer::Private::setAction(DataDeviceManager::DnDAction action) +{ + if (action == selectedAction) { + return; + } + selectedAction = action; + emit q->selectedDragAndDropActionChanged(); +} + DataOffer::DataOffer(DataDevice *parent, wl_data_offer *dataOffer) : QObject(parent) , d(new Private(dataOffer, this)) @@ -129,5 +187,56 @@ return d->dataOffer; } +void DataOffer::dragAndDropFinished() +{ + Q_ASSERT(isValid()); + if (wl_proxy_get_version(d->dataOffer) < WL_DATA_OFFER_FINISH_SINCE_VERSION) { + return; + } + wl_data_offer_finish(d->dataOffer); +} + +DataDeviceManager::DnDActions DataOffer::sourceDragAndDropActions() const +{ + return d->sourceActions; +} + +void DataOffer::setDragAndDropActions(DataDeviceManager::DnDActions supported, DataDeviceManager::DnDAction preferred) +{ + if (wl_proxy_get_version(d->dataOffer) < WL_DATA_OFFER_SET_ACTIONS_SINCE_VERSION) { + return; + } + auto toWayland = [] (DataDeviceManager::DnDAction action) { + switch (action) { + case DataDeviceManager::DnDAction::Copy: + return WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY; + case DataDeviceManager::DnDAction::Move: + return WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE; + case DataDeviceManager::DnDAction::Ask: + return WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK; + case DataDeviceManager::DnDAction::None: + return WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; + default: + Q_UNREACHABLE(); + } + }; + uint32_t wlSupported = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; + if (supported.testFlag(DataDeviceManager::DnDAction::Copy)) { + wlSupported |= toWayland(DataDeviceManager::DnDAction::Copy); + } + if (supported.testFlag(DataDeviceManager::DnDAction::Move)) { + wlSupported |= toWayland(DataDeviceManager::DnDAction::Move); + } + if (supported.testFlag(DataDeviceManager::DnDAction::Ask)) { + wlSupported |= toWayland(DataDeviceManager::DnDAction::Ask); + } + wl_data_offer_set_actions(d->dataOffer, wlSupported, toWayland(preferred)); +} + +DataDeviceManager::DnDAction DataOffer::selectedDragAndDropAction() const +{ + return d->selectedAction; +} + } } diff --git a/src/client/datasource.h b/src/client/datasource.h --- a/src/client/datasource.h +++ b/src/client/datasource.h @@ -21,6 +21,7 @@ #define WAYLAND_DATASOURCE_H #include "buffer.h" +#include "datadevicemanager.h" #include @@ -85,6 +86,25 @@ void offer(const QString &mimeType); void offer(const QMimeType &mimeType); + /** + * Sets the actions that the source side client supports for this + * operation. + * + * This request must be made once only, and can only be made on sources + * used in drag-and-drop, so it must be performed before + * @link{DataDevice::startDrag}. Attempting to use the source other than + * for drag-and-drop will raise a protocol error. + * @since 5.42 + **/ + void setDragAndDropActions(DataDeviceManager::DnDActions actions); + + /** + * The currently selected drag and drop action by the compositor. + * @see selectedDragAndDropActionChanged + * @since 5.42 + **/ + DataDeviceManager::DnDAction selectedDragAndDropAction() const; + operator wl_data_source*(); operator wl_data_source*() const; @@ -106,6 +126,43 @@ **/ void cancelled(); + /** + * The drag-and-drop operation physically finished. + * + * The user performed the drop action. This signal does not + * indicate acceptance, @link{cancelled} may still be + * emitted afterwards if the drop destination does not accept any + * mime type. + * + * However, this signal might not be received if the + * compositor cancelled the drag-and-drop operation before this + * signal could happen. + * + * Note that the DataSource may still be used in the future and + * should not be destroyed here. + * @since 5.42 + **/ + void dragAndDropPerformed(); + + /** + * The drag-and-drop operation concluded. + * + * The drop destination finished interoperating with this DataSource, + * so the client is now free to destroy this DataSource. + * + * If the action used to perform the operation was "move", the + * source can now delete the transferred data. + * @since 5.42 + */ + void dragAndDropFinished(); + + /** + * Emitted whenever the selected drag and drop action changes. + * @see selectedDragAndDropAction + * @since 5.42 + **/ + void selectedDragAndDropActionChanged(); + private: class Private; QScopedPointer d; diff --git a/src/client/datasource.cpp b/src/client/datasource.cpp --- a/src/client/datasource.cpp +++ b/src/client/datasource.cpp @@ -36,11 +36,16 @@ void setup(wl_data_source *s); WaylandPointer source; + DataDeviceManager::DnDAction selectedAction = DataDeviceManager::DnDAction::None; private: + void setAction(DataDeviceManager::DnDAction action); static void targetCallback(void *data, wl_data_source *dataSource, const char *mimeType); static void sendCallback(void *data, wl_data_source *dataSource, const char *mimeType, int32_t fd); static void cancelledCallback(void *data, wl_data_source *dataSource); + static void dndDropPerformedCallback(void *data, wl_data_source *wl_data_source); + static void dndFinishedCallback(void *data, wl_data_source *wl_data_source); + static void actionCallback(void *data, wl_data_source *wl_data_source, uint32_t dnd_action); static const struct wl_data_source_listener s_listener; @@ -50,7 +55,10 @@ const wl_data_source_listener DataSource::Private::s_listener = { targetCallback, sendCallback, - cancelledCallback + cancelledCallback, + dndDropPerformedCallback, + dndFinishedCallback, + actionCallback }; DataSource::Private::Private(DataSource *q) @@ -79,6 +87,51 @@ emit d->q->cancelled(); } +void DataSource::Private::dndDropPerformedCallback(void *data, wl_data_source *wl_data_source) +{ + Q_UNUSED(wl_data_source) + auto d = reinterpret_cast(data); + emit d->q->dragAndDropPerformed(); +} + +void DataSource::Private::dndFinishedCallback(void *data, wl_data_source *wl_data_source) +{ + Q_UNUSED(wl_data_source) + auto d = reinterpret_cast(data); + emit d->q->dragAndDropFinished(); +} + +void DataSource::Private::actionCallback(void *data, wl_data_source *wl_data_source, uint32_t dnd_action) +{ + Q_UNUSED(wl_data_source) + auto d = reinterpret_cast(data); + switch(dnd_action) { + case WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY: + d->setAction(DataDeviceManager::DnDAction::Copy); + break; + case WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE: + d->setAction(DataDeviceManager::DnDAction::Move); + break; + case WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK: + d->setAction(DataDeviceManager::DnDAction::Ask); + break; + case WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE: + d->setAction(DataDeviceManager::DnDAction::None); + break; + default: + Q_UNREACHABLE(); + } +} + +void DataSource::Private::setAction(DataDeviceManager::DnDAction action) +{ + if (action == selectedAction) { + return; + } + selectedAction = action; + emit q->selectedDragAndDropActionChanged(); +} + void DataSource::Private::setup(wl_data_source *s) { Q_ASSERT(!source.isValid()); @@ -141,5 +194,25 @@ return d->source; } +void DataSource::setDragAndDropActions(DataDeviceManager::DnDActions actions) +{ + uint32_t wlActions = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; + if (actions.testFlag(DataDeviceManager::DnDAction::Copy)) { + wlActions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY; + } + if (actions.testFlag(DataDeviceManager::DnDAction::Move)) { + wlActions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE; + } + if (actions.testFlag(DataDeviceManager::DnDAction::Ask)) { + wlActions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK; + } + wl_data_source_set_actions(d->source, wlActions); +} + +DataDeviceManager::DnDAction DataSource::selectedDragAndDropAction() const +{ + return d->selectedAction; +} + } } diff --git a/src/client/registry.cpp b/src/client/registry.cpp --- a/src/client/registry.cpp +++ b/src/client/registry.cpp @@ -115,7 +115,7 @@ &Registry::compositorRemoved }}, {Registry::Interface::DataDeviceManager, { - 2, + 3, QByteArrayLiteral("wl_data_device_manager"), &wl_data_device_manager_interface, &Registry::dataDeviceManagerAnnounced, diff --git a/src/server/datadevice_interface.cpp b/src/server/datadevice_interface.cpp --- a/src/server/datadevice_interface.cpp +++ b/src/server/datadevice_interface.cpp @@ -19,7 +19,7 @@ *********************************************************************/ #include "datadevice_interface.h" #include "datadevicemanager_interface.h" -#include "dataoffer_interface.h" +#include "dataoffer_interface_p.h" #include "datasource_interface.h" #include "display.h" #include "resource_p.h" @@ -55,6 +55,8 @@ SurfaceInterface *surface = nullptr; QMetaObject::Connection destroyConnection; QMetaObject::Connection pointerPosConnection; + QMetaObject::Connection sourceActionConnection; + QMetaObject::Connection targetActionConnection; quint32 serial = 0; }; Drag drag; @@ -121,6 +123,10 @@ void DataDeviceInterface::Private::setSelection(DataSourceInterface *dataSource) { + if (dataSource->supportedDragAndDropActions()) { + wl_resource_post_error(dataSource->resource(), WL_DATA_SOURCE_ERROR_INVALID_SOURCE, "Data source is for drag and drop"); + return; + } Q_Q(DataDeviceInterface); QObject::disconnect(selectionUnboundConnection); QObject::disconnect(selectionDestroyedConnection); @@ -232,6 +238,13 @@ return; } wl_data_device_send_drop(d->resource); + if (d->drag.pointerPosConnection) { + disconnect(d->drag.pointerPosConnection); + d->drag.pointerPosConnection = QMetaObject::Connection(); + } + disconnect(d->drag.destroyConnection); + d->drag.destroyConnection = QMetaObject::Connection(); + d->drag.surface = nullptr; client()->flush(); } @@ -249,12 +262,22 @@ disconnect(d->drag.destroyConnection); d->drag.destroyConnection = QMetaObject::Connection(); d->drag.surface = nullptr; + if (d->drag.sourceActionConnection) { + disconnect(d->drag.sourceActionConnection); + d->drag.sourceActionConnection = QMetaObject::Connection(); + } + if (d->drag.targetActionConnection) { + disconnect(d->drag.targetActionConnection); + d->drag.targetActionConnection = QMetaObject::Connection(); + } // don't update serial, we need it } if (!surface) { + d->seat->dragSource()->dragSource()->dndAction(DataDeviceManagerInterface::DnDAction::None); return; } - DataOfferInterface *offer = d->createDataOffer(d->seat->dragSource()->dragSource()); + auto *source = d->seat->dragSource()->dragSource(); + DataOfferInterface *offer = d->createDataOffer(source); d->drag.surface = surface; if (d->seat->isDragPointer()) { d->drag.pointerPosConnection = connect(d->seat, &SeatInterface::pointerPosChanged, this, @@ -285,6 +308,30 @@ const QPointF pos = d->seat->dragSurfaceTransformation().map(d->seat->pointerPos()); wl_data_device_send_enter(d->resource, serial, surface->resource(), wl_fixed_from_double(pos.x()), wl_fixed_from_double(pos.y()), offer ? offer->resource() : nullptr); + if (offer) { + offer->d_func()->sendSourceActions(); + auto matchOffers = [source, offer] { + DataDeviceManagerInterface::DnDAction action{DataDeviceManagerInterface::DnDAction::None}; + if (source->supportedDragAndDropActions().testFlag(offer->preferredDragAndDropAction())) { + action = offer->preferredDragAndDropAction(); + } else { + if (source->supportedDragAndDropActions().testFlag(DataDeviceManagerInterface::DnDAction::Copy) && + offer->supportedDragAndDropActions().testFlag(DataDeviceManagerInterface::DnDAction::Copy)) { + action = DataDeviceManagerInterface::DnDAction::Copy; + } else if (source->supportedDragAndDropActions().testFlag(DataDeviceManagerInterface::DnDAction::Move) && + offer->supportedDragAndDropActions().testFlag(DataDeviceManagerInterface::DnDAction::Move)) { + action = DataDeviceManagerInterface::DnDAction::Move; + } else if (source->supportedDragAndDropActions().testFlag(DataDeviceManagerInterface::DnDAction::Ask) && + offer->supportedDragAndDropActions().testFlag(DataDeviceManagerInterface::DnDAction::Ask)) { + action = DataDeviceManagerInterface::DnDAction::Ask; + } + } + offer->dndAction(action); + source->dndAction(action); + }; + d->drag.targetActionConnection = connect(offer, &DataOfferInterface::dragAndDropActionsChanged, offer, matchOffers); + d->drag.sourceActionConnection = connect(source, &DataSourceInterface::supportedDragAndDropActionsChanged, source, matchOffers); + } d->client->flush(); } diff --git a/src/server/datadevicemanager_interface.h b/src/server/datadevicemanager_interface.h --- a/src/server/datadevicemanager_interface.h +++ b/src/server/datadevicemanager_interface.h @@ -25,14 +25,14 @@ #include #include "global.h" #include "datadevice_interface.h" -#include "datasource_interface.h" namespace KWayland { namespace Server { class Display; +class DataSourceInterface; /** * @brief Represents the Global for wl_data_device_manager interface. @@ -44,6 +44,18 @@ public: virtual ~DataDeviceManagerInterface(); + /** + * Drag and Drop actions supported by the DataSourceInterface. + * @since 5.XX + **/ + enum class DnDAction { + None = 0, + Copy = 1 << 0, + Move = 1 << 1, + Ask = 1 << 2 + }; + Q_DECLARE_FLAGS(DnDActions, DnDAction) + Q_SIGNALS: void dataSourceCreated(KWayland::Server::DataSourceInterface*); void dataDeviceCreated(KWayland::Server::DataDeviceInterface*); @@ -57,4 +69,6 @@ } } +Q_DECLARE_OPERATORS_FOR_FLAGS(KWayland::Server::DataDeviceManagerInterface::DnDActions) + #endif diff --git a/src/server/datadevicemanager_interface.cpp b/src/server/datadevicemanager_interface.cpp --- a/src/server/datadevicemanager_interface.cpp +++ b/src/server/datadevicemanager_interface.cpp @@ -18,6 +18,7 @@ License along with this library. If not, see . *********************************************************************/ #include "datadevicemanager_interface.h" +#include "datasource_interface.h" #include "global_p.h" #include "display.h" #include "seat_interface_p.h" @@ -53,9 +54,9 @@ static const qint32 s_dataSourceVersion; }; -const quint32 DataDeviceManagerInterface::Private::s_version = 2; -const qint32 DataDeviceManagerInterface::Private::s_dataDeviceVersion = 2; -const qint32 DataDeviceManagerInterface::Private::s_dataSourceVersion = 1; +const quint32 DataDeviceManagerInterface::Private::s_version = 3; +const qint32 DataDeviceManagerInterface::Private::s_dataDeviceVersion = 3; +const qint32 DataDeviceManagerInterface::Private::s_dataSourceVersion = 3; #ifndef DOXYGEN_SHOULD_SKIP_THIS const struct wl_data_device_manager_interface DataDeviceManagerInterface::Private::s_interface = { diff --git a/src/server/dataoffer_interface.h b/src/server/dataoffer_interface.h --- a/src/server/dataoffer_interface.h +++ b/src/server/dataoffer_interface.h @@ -25,6 +25,7 @@ #include #include "resource.h" +#include "datadevicemanager_interface.h" namespace KWayland { @@ -46,6 +47,32 @@ void sendAllOffers(); + /** + * @returns The Drag and Drop actions supported by this DataOfferInterface. + * @since 5.42 + **/ + DataDeviceManagerInterface::DnDActions supportedDragAndDropActions() const; + + /** + * @returns The preferred Drag and Drop action of this DataOfferInterface. + * @since 5.42 + **/ + DataDeviceManagerInterface::DnDAction preferredDragAndDropAction() const; + + /** + * This event indicates the @p action selected by the compositor after matching the + * source/destination side actions. Only one action (or none) will be offered here. + * @since 5.42 + **/ + void dndAction(DataDeviceManagerInterface::DnDAction action); + +Q_SIGNALS: + /** + * Emitted whenever the supported or preferred Drag and Drop actions changed. + * @since 5.42 + **/ + void dragAndDropActionsChanged(); + private: friend class DataDeviceInterface; explicit DataOfferInterface(DataSourceInterface *source, DataDeviceInterface *parentInterface, wl_resource *parentResource); diff --git a/src/server/dataoffer_interface.cpp b/src/server/dataoffer_interface.cpp --- a/src/server/dataoffer_interface.cpp +++ b/src/server/dataoffer_interface.cpp @@ -17,10 +17,9 @@ You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . *********************************************************************/ -#include "dataoffer_interface.h" +#include "dataoffer_interface_p.h" #include "datadevice_interface.h" #include "datasource_interface.h" -#include "resource_p.h" // Qt #include // Wayland @@ -31,30 +30,13 @@ namespace Server { -class DataOfferInterface::Private : public Resource::Private -{ -public: - Private(DataSourceInterface *source, DataDeviceInterface *parentInterface, DataOfferInterface *q, wl_resource *parentResource); - ~Private(); - DataSourceInterface *source; - DataDeviceInterface *dataDevice; - -private: - DataOfferInterface *q_func() { - return reinterpret_cast(q); - } - void receive(const QString &mimeType, qint32 fd); - static void acceptCallback(wl_client *client, wl_resource *resource, uint32_t serial, const char *mimeType); - static void receiveCallback(wl_client *client, wl_resource *resource, const char *mimeType, int32_t fd); - - static const struct wl_data_offer_interface s_interface; -}; - #ifndef DOXYGEN_SHOULD_SKIP_THIS const struct wl_data_offer_interface DataOfferInterface::Private::s_interface = { acceptCallback, receiveCallback, - resourceDestroyedCallback + resourceDestroyedCallback, + finishCallback, + setActionsCallback }; #endif @@ -90,6 +72,81 @@ source->requestData(mimeType, fd); } +void DataOfferInterface::Private::finishCallback(wl_client *client, wl_resource *resource) +{ + Q_UNUSED(client) + auto p = cast(resource); + if (!p->source) { + return; + } + p->source->dndFinished(); + // TODO: It is a client error to perform other requests than wl_data_offer.destroy after this one +} + +void DataOfferInterface::Private::setActionsCallback(wl_client *client, wl_resource *resource, uint32_t dnd_actions, uint32_t preferred_action) +{ + // TODO: check it's drag and drop, otherwise send error + Q_UNUSED(client) + DataDeviceManagerInterface::DnDActions supportedActions; + if (dnd_actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY) { + supportedActions |= DataDeviceManagerInterface::DnDAction::Copy; + } + if (dnd_actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE) { + supportedActions |= DataDeviceManagerInterface::DnDAction::Move; + } + if (dnd_actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK) { + supportedActions |= DataDeviceManagerInterface::DnDAction::Ask; + } + // verify that the no other actions are sent + if (dnd_actions & ~(WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY | WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE | WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK)) { + wl_resource_post_error(resource, WL_DATA_OFFER_ERROR_INVALID_ACTION_MASK, "Invalid action mask"); + return; + } + if (preferred_action != WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY && + preferred_action != WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE && + preferred_action != WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK && + preferred_action != WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE) { + wl_resource_post_error(resource, WL_DATA_OFFER_ERROR_INVALID_ACTION, "Invalid preferred action"); + return; + } + + DataDeviceManagerInterface::DnDAction preferredAction = DataDeviceManagerInterface::DnDAction::None; + if (preferred_action == WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY) { + preferredAction = DataDeviceManagerInterface::DnDAction::Copy; + } else if (preferred_action == WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE) { + preferredAction = DataDeviceManagerInterface::DnDAction::Move; + } else if (preferred_action == WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK) { + preferredAction = DataDeviceManagerInterface::DnDAction::Ask; + } + + auto p = cast(resource); + p->supportedDnDActions = supportedActions; + p->preferredDnDAction = preferredAction; + emit p->q_func()->dragAndDropActionsChanged(); +} + +void DataOfferInterface::Private::sendSourceActions() +{ + if (!source) { + return; + } + if (wl_resource_get_version(resource) < WL_DATA_OFFER_SOURCE_ACTIONS_SINCE_VERSION) { + return; + } + uint32_t wlActions = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; + const auto actions = source->supportedDragAndDropActions(); + if (actions.testFlag(DataDeviceManagerInterface::DnDAction::Copy)) { + wlActions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY; + } + if (actions.testFlag(DataDeviceManagerInterface::DnDAction::Move)) { + wlActions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE; + } + if (actions.testFlag(DataDeviceManagerInterface::DnDAction::Ask)) { + wlActions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK; + } + wl_data_offer_send_source_actions(resource, wlActions); +} + DataOfferInterface::DataOfferInterface(DataSourceInterface *source, DataDeviceInterface *parentInterface, wl_resource *parentResource) : Resource(new Private(source, parentInterface, this, parentResource)) { @@ -126,5 +183,34 @@ return reinterpret_cast(d.data()); } +DataDeviceManagerInterface::DnDActions DataOfferInterface::supportedDragAndDropActions() const +{ + Q_D(); + return d->supportedDnDActions; +} + +DataDeviceManagerInterface::DnDAction DataOfferInterface::preferredDragAndDropAction() const +{ + Q_D(); + return d->preferredDnDAction; +} + +void DataOfferInterface::dndAction(DataDeviceManagerInterface::DnDAction action) +{ + Q_D(); + if (wl_resource_get_version(d->resource) < WL_DATA_OFFER_ACTION_SINCE_VERSION) { + return; + } + uint32_t wlAction = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; + if (action == DataDeviceManagerInterface::DnDAction::Copy) { + wlAction = WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY; + } else if (action == DataDeviceManagerInterface::DnDAction::Move ) { + wlAction = WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE; + } else if (action == DataDeviceManagerInterface::DnDAction::Ask) { + wlAction = WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK; + } + wl_data_offer_send_action(d->resource, wlAction); +} + } } diff --git a/src/server/dataoffer_interface_p.h b/src/server/dataoffer_interface_p.h new file mode 100644 --- /dev/null +++ b/src/server/dataoffer_interface_p.h @@ -0,0 +1,61 @@ +/******************************************************************** +Copyright 2017 Martin Flöser + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) version 3, or any +later version accepted by the membership of KDE e.V. (or its +successor approved by the membership of KDE e.V.), which shall +act as a proxy defined in Section 6 of version 3 of the license. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library. If not, see . +*********************************************************************/ +#ifndef KWAYLAND_SERVER_DATAOFFERINTERFACE_P_H +#define KWAYLAND_SERVER_DATAOFFERINTERFACE_P_H +#include "dataoffer_interface.h" +#include "datasource_interface.h" +#include "resource_p.h" +#include + +namespace KWayland +{ +namespace Server +{ + +class Q_DECL_HIDDEN DataOfferInterface::Private : public Resource::Private +{ +public: + Private(DataSourceInterface *source, DataDeviceInterface *parentInterface, DataOfferInterface *q, wl_resource *parentResource); + ~Private(); + DataSourceInterface *source; + DataDeviceInterface *dataDevice; + // defaults are set to sensible values for < version 3 interfaces + DataDeviceManagerInterface::DnDActions supportedDnDActions = DataDeviceManagerInterface::DnDAction::Copy | DataDeviceManagerInterface::DnDAction::Move; + DataDeviceManagerInterface::DnDAction preferredDnDAction = DataDeviceManagerInterface::DnDAction::Copy; + + void sendSourceActions(); + +private: + DataOfferInterface *q_func() { + return reinterpret_cast(q); + } + void receive(const QString &mimeType, qint32 fd); + static void acceptCallback(wl_client *client, wl_resource *resource, uint32_t serial, const char *mimeType); + static void receiveCallback(wl_client *client, wl_resource *resource, const char *mimeType, int32_t fd); + static void finishCallback(wl_client *client, wl_resource *resource); + static void setActionsCallback(wl_client *client, wl_resource *resource, uint32_t dnd_actions, uint32_t preferred_action); + + static const struct wl_data_offer_interface s_interface; +}; + +} +} + +#endif diff --git a/src/server/datasource_interface.h b/src/server/datasource_interface.h --- a/src/server/datasource_interface.h +++ b/src/server/datasource_interface.h @@ -25,12 +25,12 @@ #include #include "resource.h" +#include "datadevicemanager_interface.h" namespace KWayland { namespace Server { -class DataDeviceManagerInterface; /** * @brief Represents the Resource for the wl_data_source interface. @@ -49,8 +49,36 @@ static DataSourceInterface *get(wl_resource *native); + /** + * @returns The Drag and Drop actions supported by this DataSourceInterface. + * @since 5.42 + **/ + DataDeviceManagerInterface::DnDActions supportedDragAndDropActions() const; + + /** + * The user performed the drop action during a drag and drop operation. + * @since 5.42 + **/ + void dropPerformed(); + /** + * The drop destination finished interoperating with this data source. + * @since 5.42 + **/ + void dndFinished(); + /** + * This event indicates the @p action selected by the compositor after matching the + * source/destination side actions. Only one action (or none) will be offered here. + * @since 5.42 + **/ + void dndAction(DataDeviceManagerInterface::DnDAction action); + Q_SIGNALS: void mimeTypeOffered(const QString&); + /** + * Emitted whenever this DataSourceInterface changes the supported drag and drop actions + * @since 5.42 + **/ + void supportedDragAndDropActionsChanged(); private: friend class DataDeviceManagerInterface; diff --git a/src/server/datasource_interface.cpp b/src/server/datasource_interface.cpp --- a/src/server/datasource_interface.cpp +++ b/src/server/datasource_interface.cpp @@ -40,22 +40,26 @@ ~Private(); QStringList mimeTypes; + // sensible default for < version 3 + DataDeviceManagerInterface::DnDActions supportedDnDActions = DataDeviceManagerInterface::DnDAction::Copy; private: DataSourceInterface *q_func() { return reinterpret_cast(q); } void offer(const QString &mimeType); static void offerCallback(wl_client *client, wl_resource *resource, const char *mimeType); + static void setActionsCallback(wl_client *client, wl_resource *resource, uint32_t dnd_actions); const static struct wl_data_source_interface s_interface; }; #ifndef DOXYGEN_SHOULD_SKIP_THIS const struct wl_data_source_interface DataSourceInterface::Private::s_interface = { offerCallback, - resourceDestroyedCallback + resourceDestroyedCallback, + setActionsCallback }; #endif @@ -79,6 +83,31 @@ emit q->mimeTypeOffered(mimeType); } +void DataSourceInterface::Private::setActionsCallback(wl_client *client, wl_resource *resource, uint32_t dnd_actions) +{ + Q_UNUSED(client) + DataDeviceManagerInterface::DnDActions supportedActions; + if (dnd_actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY) { + supportedActions |= DataDeviceManagerInterface::DnDAction::Copy; + } + if (dnd_actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE) { + supportedActions |= DataDeviceManagerInterface::DnDAction::Move; + } + if (dnd_actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK) { + supportedActions |= DataDeviceManagerInterface::DnDAction::Ask; + } + // verify that the no other actions are sent + if (dnd_actions & ~(WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY | WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE | WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK)) { + wl_resource_post_error(resource, WL_DATA_SOURCE_ERROR_INVALID_ACTION_MASK, "Invalid action mask"); + return; + } + auto p = cast(resource); + if (p->supportedDnDActions!= supportedActions) { + p->supportedDnDActions = supportedActions; + emit p->q_func()->supportedDragAndDropActionsChanged(); + } +} + DataSourceInterface::DataSourceInterface(DataDeviceManagerInterface *parent, wl_resource *parentResource) : Resource(new Private(this, parent, parentResource)) { @@ -129,5 +158,46 @@ return reinterpret_cast(d.data()); } +DataDeviceManagerInterface::DnDActions DataSourceInterface::supportedDragAndDropActions() const +{ + Q_D(); + return d->supportedDnDActions; +} + +void DataSourceInterface::dropPerformed() +{ + Q_D(); + if (wl_resource_get_version(d->resource) < WL_DATA_SOURCE_DND_DROP_PERFORMED_SINCE_VERSION) { + return; + } + wl_data_source_send_dnd_drop_performed(d->resource); +} + +void DataSourceInterface::dndFinished() +{ + Q_D(); + if (wl_resource_get_version(d->resource) < WL_DATA_SOURCE_DND_FINISHED_SINCE_VERSION) { + return; + } + wl_data_source_send_dnd_finished(d->resource); +} + +void DataSourceInterface::dndAction(DataDeviceManagerInterface::DnDAction action) +{ + Q_D(); + if (wl_resource_get_version(d->resource) < WL_DATA_SOURCE_ACTION_SINCE_VERSION) { + return; + } + uint32_t wlAction = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; + if (action == DataDeviceManagerInterface::DnDAction::Copy) { + wlAction = WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY; + } else if (action == DataDeviceManagerInterface::DnDAction::Move ) { + wlAction = WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE; + } else if (action == DataDeviceManagerInterface::DnDAction::Ask) { + wlAction = WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK; + } + wl_data_source_send_action(d->resource, wlAction); +} + } } diff --git a/src/server/seat_interface.cpp b/src/server/seat_interface.cpp --- a/src/server/seat_interface.cpp +++ b/src/server/seat_interface.cpp @@ -350,6 +350,9 @@ { auto target = drag.target; QObject::disconnect(drag.destroyConnection); + if (drag.source) { + drag.source->dragSource()->dropPerformed(); + } if (target) { target->drop(); target->updateDragTarget(nullptr, serial);