Client: Implement DataDeviceV3

DataDeviceV2 fixes a leak of DataDevice resources.

DataDeviceV3 brings multiple improvements:

Action negotiation. The source announces which actions are supported,
the target then announces which subset of those action the target
supports and a preferred action. After negotiation both the source and
target are notified of which action is to be performed.

Drag sources are now notified when contents are dropped and when a
client has finished with the drag and drop operation.

A good test is the draggableicons example in QtBase.

Change-Id: I55e9759ca5a2e4218d02d863144a64ade53ef764
Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>
This commit is contained in:
David Edmundson 2021-02-16 09:51:47 +00:00
parent d89d826bb2
commit 30fecf220d
12 changed files with 153 additions and 46 deletions

View File

@ -73,6 +73,8 @@ QWaylandDataDevice::QWaylandDataDevice(QWaylandDataDeviceManager *manager, QWayl
QWaylandDataDevice::~QWaylandDataDevice() QWaylandDataDevice::~QWaylandDataDevice()
{ {
if (version() >= WL_DATA_DEVICE_RELEASE_SINCE_VERSION)
release();
} }
QWaylandDataOffer *QWaylandDataDevice::selectionOffer() const QWaylandDataOffer *QWaylandDataDevice::selectionOffer() const
@ -111,7 +113,7 @@ QWaylandDataOffer *QWaylandDataDevice::dragOffer() const
return m_dragOffer.data(); return m_dragOffer.data();
} }
bool QWaylandDataDevice::startDrag(QMimeData *mimeData, QWaylandWindow *icon) bool QWaylandDataDevice::startDrag(QMimeData *mimeData, Qt::DropActions supportedActions, QWaylandWindow *icon)
{ {
auto *seat = m_display->currentInputDevice(); auto *seat = m_display->currentInputDevice();
auto *origin = seat->pointerFocus(); auto *origin = seat->pointerFocus();
@ -124,8 +126,28 @@ bool QWaylandDataDevice::startDrag(QMimeData *mimeData, QWaylandWindow *icon)
} }
m_dragSource.reset(new QWaylandDataSource(m_display->dndSelectionHandler(), mimeData)); m_dragSource.reset(new QWaylandDataSource(m_display->dndSelectionHandler(), mimeData));
if (version() >= 3)
m_dragSource->set_actions(dropActionsToWl(supportedActions));
connect(m_dragSource.data(), &QWaylandDataSource::cancelled, this, &QWaylandDataDevice::dragSourceCancelled); connect(m_dragSource.data(), &QWaylandDataSource::cancelled, this, &QWaylandDataDevice::dragSourceCancelled);
connect(m_dragSource.data(), &QWaylandDataSource::targetChanged, this, &QWaylandDataDevice::dragSourceTargetChanged); connect(m_dragSource.data(), &QWaylandDataSource::dndResponseUpdated, this, [this](bool accepted, Qt::DropAction action) {
auto drag = static_cast<QWaylandDrag *>(QGuiApplicationPrivate::platformIntegration()->drag());
// in old versions drop action is not set, so we guess
if (m_dragSource->version() < 3) {
drag->setResponse(accepted);
} else {
QPlatformDropQtResponse response(accepted, action);
drag->setResponse(response);
}
});
connect(m_dragSource.data(), &QWaylandDataSource::dndDropped, this, [](bool accepted, Qt::DropAction action) {
QPlatformDropQtResponse response(accepted, action);
static_cast<QWaylandDrag *>(QGuiApplicationPrivate::platformIntegration()->drag())->setDropResponse(response);
});
connect(m_dragSource.data(), &QWaylandDataSource::finished, this, []() {
static_cast<QWaylandDrag *>(QGuiApplicationPrivate::platformIntegration()->drag())->finishDrag();
});
start_drag(m_dragSource->object(), origin->wlSurface(), icon->wlSurface(), m_display->currentInputDevice()->serial()); start_drag(m_dragSource->object(), origin->wlSurface(), icon->wlSurface(), m_display->currentInputDevice()->serial());
return true; return true;
@ -154,7 +176,7 @@ void QWaylandDataDevice::data_device_drop()
supportedActions = drag->supportedActions(); supportedActions = drag->supportedActions();
} else if (m_dragOffer) { } else if (m_dragOffer) {
dragData = m_dragOffer->mimeData(); dragData = m_dragOffer->mimeData();
supportedActions = Qt::CopyAction | Qt::MoveAction | Qt::LinkAction; supportedActions = m_dragOffer->supportedActions();
} else { } else {
return; return;
} }
@ -164,7 +186,11 @@ void QWaylandDataDevice::data_device_drop()
QGuiApplication::keyboardModifiers()); QGuiApplication::keyboardModifiers());
if (drag) { if (drag) {
static_cast<QWaylandDrag *>(QGuiApplicationPrivate::platformIntegration()->drag())->finishDrag(response); auto drag = static_cast<QWaylandDrag *>(QGuiApplicationPrivate::platformIntegration()->drag());
drag->setDropResponse(response);
drag->finishDrag();
} else if (m_dragOffer) {
m_dragOffer->finish();
} }
} }
@ -188,7 +214,7 @@ void QWaylandDataDevice::data_device_enter(uint32_t serial, wl_surface *surface,
supportedActions = drag->supportedActions(); supportedActions = drag->supportedActions();
} else if (m_dragOffer) { } else if (m_dragOffer) {
dragData = m_dragOffer->mimeData(); dragData = m_dragOffer->mimeData();
supportedActions = Qt::CopyAction | Qt::MoveAction | Qt::LinkAction; supportedActions = m_dragOffer->supportedActions();
} }
const QPlatformDragQtResponse &response = QWindowSystemInterface::handleDrag(m_dragWindow, dragData, m_dragPoint, supportedActions, const QPlatformDragQtResponse &response = QWindowSystemInterface::handleDrag(m_dragWindow, dragData, m_dragPoint, supportedActions,
@ -199,11 +225,7 @@ void QWaylandDataDevice::data_device_enter(uint32_t serial, wl_surface *surface,
static_cast<QWaylandDrag *>(QGuiApplicationPrivate::platformIntegration()->drag())->setResponse(response); static_cast<QWaylandDrag *>(QGuiApplicationPrivate::platformIntegration()->drag())->setResponse(response);
} }
if (response.isAccepted()) { sendResponse(supportedActions, response);
wl_data_offer_accept(m_dragOffer->object(), m_enterSerial, m_dragOffer->firstFormat().toUtf8().constData());
} else {
wl_data_offer_accept(m_dragOffer->object(), m_enterSerial, nullptr);
}
} }
void QWaylandDataDevice::data_device_leave() void QWaylandDataDevice::data_device_leave()
@ -237,10 +259,10 @@ void QWaylandDataDevice::data_device_motion(uint32_t time, wl_fixed_t x, wl_fixe
supportedActions = drag->supportedActions(); supportedActions = drag->supportedActions();
} else { } else {
dragData = m_dragOffer->mimeData(); dragData = m_dragOffer->mimeData();
supportedActions = Qt::CopyAction | Qt::MoveAction | Qt::LinkAction; supportedActions = m_dragOffer->supportedActions();
} }
QPlatformDragQtResponse response = QWindowSystemInterface::handleDrag(m_dragWindow, dragData, m_dragPoint, supportedActions, const QPlatformDragQtResponse response = QWindowSystemInterface::handleDrag(m_dragWindow, dragData, m_dragPoint, supportedActions,
QGuiApplication::mouseButtons(), QGuiApplication::mouseButtons(),
QGuiApplication::keyboardModifiers()); QGuiApplication::keyboardModifiers());
@ -248,11 +270,7 @@ void QWaylandDataDevice::data_device_motion(uint32_t time, wl_fixed_t x, wl_fixe
static_cast<QWaylandDrag *>(QGuiApplicationPrivate::platformIntegration()->drag())->setResponse(response); static_cast<QWaylandDrag *>(QGuiApplicationPrivate::platformIntegration()->drag())->setResponse(response);
} }
if (response.isAccepted()) { sendResponse(supportedActions, response);
wl_data_offer_accept(m_dragOffer->object(), m_enterSerial, m_dragOffer->firstFormat().toUtf8().constData());
} else {
wl_data_offer_accept(m_dragOffer->object(), m_enterSerial, nullptr);
}
} }
#endif // QT_CONFIG(draganddrop) #endif // QT_CONFIG(draganddrop)
@ -282,11 +300,6 @@ void QWaylandDataDevice::dragSourceCancelled()
m_dragSource.reset(); m_dragSource.reset();
} }
void QWaylandDataDevice::dragSourceTargetChanged(const QString &mimeType)
{
static_cast<QWaylandDrag *>(QGuiApplicationPrivate::platformIntegration()->drag())->updateTarget(mimeType);
}
QPoint QWaylandDataDevice::calculateDragPosition(int x, int y, QWindow *wnd) const QPoint QWaylandDataDevice::calculateDragPosition(int x, int y, QWindow *wnd) const
{ {
QPoint pnt(wl_fixed_to_int(x), wl_fixed_to_int(y)); QPoint pnt(wl_fixed_to_int(x), wl_fixed_to_int(y));
@ -299,6 +312,33 @@ QPoint QWaylandDataDevice::calculateDragPosition(int x, int y, QWindow *wnd) con
} }
return pnt; return pnt;
} }
void QWaylandDataDevice::sendResponse(Qt::DropActions supportedActions, const QPlatformDragQtResponse &response)
{
if (response.isAccepted()) {
if (version() >= 3)
m_dragOffer->set_actions(dropActionsToWl(supportedActions), dropActionsToWl(response.acceptedAction()));
m_dragOffer->accept(m_enterSerial, m_dragOffer->firstFormat());
} else {
m_dragOffer->accept(m_enterSerial, QString());
}
}
int QWaylandDataDevice::dropActionsToWl(Qt::DropActions actions)
{
int wlActions = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE;
if (actions & Qt::CopyAction)
wlActions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY;
if (actions & (Qt::MoveAction | Qt::TargetMoveAction))
wlActions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE;
// wayland does not support LinkAction at the time of writing
return wlActions;
}
#endif // QT_CONFIG(draganddrop) #endif // QT_CONFIG(draganddrop)
} }

View File

@ -64,6 +64,7 @@ QT_REQUIRE_CONFIG(wayland_datadevice);
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
class QMimeData; class QMimeData;
class QPlatformDragQtResponse;
class QWindow; class QWindow;
namespace QtWaylandClient { namespace QtWaylandClient {
@ -89,7 +90,7 @@ public:
#if QT_CONFIG(draganddrop) #if QT_CONFIG(draganddrop)
QWaylandDataOffer *dragOffer() const; QWaylandDataOffer *dragOffer() const;
bool startDrag(QMimeData *mimeData, QWaylandWindow *icon); bool startDrag(QMimeData *mimeData, Qt::DropActions supportedActions, QWaylandWindow *icon);
void cancelDrag(); void cancelDrag();
#endif #endif
@ -109,13 +110,16 @@ private Q_SLOTS:
#if QT_CONFIG(draganddrop) #if QT_CONFIG(draganddrop)
void dragSourceCancelled(); void dragSourceCancelled();
void dragSourceTargetChanged(const QString &mimeType);
#endif #endif
private: private:
#if QT_CONFIG(draganddrop) #if QT_CONFIG(draganddrop)
QPoint calculateDragPosition(int x, int y, QWindow *wnd) const; QPoint calculateDragPosition(int x, int y, QWindow *wnd) const;
#endif #endif
void sendResponse(Qt::DropActions supportedActions, const QPlatformDragQtResponse &response);
static int dropActionsToWl(Qt::DropActions dropActions);
QWaylandDisplay *m_display = nullptr; QWaylandDisplay *m_display = nullptr;
QWaylandInputDevice *m_inputDevice = nullptr; QWaylandInputDevice *m_inputDevice = nullptr;

View File

@ -50,8 +50,8 @@ QT_BEGIN_NAMESPACE
namespace QtWaylandClient { namespace QtWaylandClient {
QWaylandDataDeviceManager::QWaylandDataDeviceManager(QWaylandDisplay *display, uint32_t id) QWaylandDataDeviceManager::QWaylandDataDeviceManager(QWaylandDisplay *display, int version, uint32_t id)
: wl_data_device_manager(display->wl_registry(), id, 1) : wl_data_device_manager(display->wl_registry(), id, qMin(version, 3))
, m_display(display) , m_display(display)
{ {
// Create transfer devices for all input devices. // Create transfer devices for all input devices.

View File

@ -68,7 +68,7 @@ class QWaylandInputDevice;
class Q_WAYLAND_CLIENT_EXPORT QWaylandDataDeviceManager : public QtWayland::wl_data_device_manager class Q_WAYLAND_CLIENT_EXPORT QWaylandDataDeviceManager : public QtWayland::wl_data_device_manager
{ {
public: public:
QWaylandDataDeviceManager(QWaylandDisplay *display, uint32_t id); QWaylandDataDeviceManager(QWaylandDisplay *display, int version, uint32_t id);
~QWaylandDataDeviceManager() override; ~QWaylandDataDeviceManager() override;
QWaylandDataDevice *getDataDevice(QWaylandInputDevice *inputDevice); QWaylandDataDevice *getDataDevice(QWaylandInputDevice *inputDevice);

View File

@ -82,6 +82,15 @@ QMimeData *QWaylandDataOffer::mimeData()
return m_mimeData.data(); return m_mimeData.data();
} }
Qt::DropActions QWaylandDataOffer::supportedActions() const
{
if (version() < 3) {
return Qt::MoveAction | Qt::CopyAction;
}
return m_supportedActions;
}
void QWaylandDataOffer::startReceiving(const QString &mimeType, int fd) void QWaylandDataOffer::startReceiving(const QString &mimeType, int fd)
{ {
receive(mimeType, fd); receive(mimeType, fd);
@ -93,6 +102,22 @@ void QWaylandDataOffer::data_offer_offer(const QString &mime_type)
m_mimeData->appendFormat(mime_type); m_mimeData->appendFormat(mime_type);
} }
void QWaylandDataOffer::data_offer_action(uint32_t dnd_action)
{
Q_UNUSED(dnd_action);
// This is the compositor telling the drag target what action it should perform
// It does not map nicely into Qt final drop semantics, other than pretending there is only one supported action?
}
void QWaylandDataOffer::data_offer_source_actions(uint32_t source_actions)
{
m_supportedActions = Qt::DropActions();
if (source_actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE)
m_supportedActions |= Qt::MoveAction;
if (source_actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY)
m_supportedActions |= Qt::CopyAction;
}
QWaylandMimeData::QWaylandMimeData(QWaylandAbstractDataOffer *dataOffer) QWaylandMimeData::QWaylandMimeData(QWaylandAbstractDataOffer *dataOffer)
: m_dataOffer(dataOffer) : m_dataOffer(dataOffer)
{ {

View File

@ -82,6 +82,7 @@ public:
explicit QWaylandDataOffer(QWaylandDisplay *display, struct ::wl_data_offer *offer); explicit QWaylandDataOffer(QWaylandDisplay *display, struct ::wl_data_offer *offer);
~QWaylandDataOffer() override; ~QWaylandDataOffer() override;
QMimeData *mimeData() override; QMimeData *mimeData() override;
Qt::DropActions supportedActions() const;
QString firstFormat() const; QString firstFormat() const;
@ -89,10 +90,13 @@ public:
protected: protected:
void data_offer_offer(const QString &mime_type) override; void data_offer_offer(const QString &mime_type) override;
void data_offer_source_actions(uint32_t source_actions) override;
void data_offer_action(uint32_t dnd_action) override;
private: private:
QWaylandDisplay *m_display = nullptr; QWaylandDisplay *m_display = nullptr;
QScopedPointer<QWaylandMimeData> m_mimeData; QScopedPointer<QWaylandMimeData> m_mimeData;
Qt::DropActions m_supportedActions;
}; };

View File

@ -101,7 +101,32 @@ void QWaylandDataSource::data_source_send(const QString &mime_type, int32_t fd)
void QWaylandDataSource::data_source_target(const QString &mime_type) void QWaylandDataSource::data_source_target(const QString &mime_type)
{ {
Q_EMIT targetChanged(mime_type); m_accepted = !mime_type.isEmpty();
Q_EMIT dndResponseUpdated(m_accepted, m_dropAction);
}
void QWaylandDataSource::data_source_action(uint32_t action)
{
Qt::DropAction qtAction = Qt::IgnoreAction;
if (action == WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE)
qtAction = Qt::MoveAction;
else if (action == WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY)
qtAction = Qt::CopyAction;
m_dropAction = qtAction;
Q_EMIT dndResponseUpdated(m_accepted, m_dropAction);
}
void QWaylandDataSource::data_source_dnd_finished()
{
Q_EMIT finished();
}
void QWaylandDataSource::data_source_dnd_drop_performed()
{
Q_EMIT dndDropped(m_accepted, m_dropAction);
} }
} }

View File

@ -77,17 +77,25 @@ public:
QMimeData *mimeData() const; QMimeData *mimeData() const;
Q_SIGNALS: Q_SIGNALS:
void targetChanged(const QString &mime_type);
void cancelled(); void cancelled();
void finished();
void dndResponseUpdated(bool accepted, Qt::DropAction action);
void dndDropped(bool accepted, Qt::DropAction action);
protected: protected:
void data_source_cancelled() override; void data_source_cancelled() override;
void data_source_send(const QString &mime_type, int32_t fd) override; void data_source_send(const QString &mime_type, int32_t fd) override;
void data_source_target(const QString &mime_type) override; void data_source_target(const QString &mime_type) override;
void data_source_dnd_drop_performed() override;
void data_source_dnd_finished() override;
void data_source_action(uint32_t action) override;
private: private:
QWaylandDisplay *m_display = nullptr; QWaylandDisplay *m_display = nullptr;
QMimeData *m_mime_data = nullptr; QMimeData *m_mime_data = nullptr;
bool m_accepted = false;
Qt::DropAction m_dropAction = Qt::IgnoreAction;
}; };
} }

View File

@ -514,7 +514,7 @@ void QWaylandDisplay::registry_global(uint32_t id, const QString &interface, uin
mInputDevices.append(inputDevice); mInputDevices.append(inputDevice);
#if QT_CONFIG(wayland_datadevice) #if QT_CONFIG(wayland_datadevice)
} else if (interface == QLatin1String(QWaylandDataDeviceManager::interface()->name)) { } else if (interface == QLatin1String(QWaylandDataDeviceManager::interface()->name)) {
mDndSelectionHandler.reset(new QWaylandDataDeviceManager(this, id)); mDndSelectionHandler.reset(new QWaylandDataDeviceManager(this, version, id));
#endif #endif
} else if (interface == QLatin1String(QtWayland::qt_surface_extension::interface()->name)) { } else if (interface == QLatin1String(QtWayland::qt_surface_extension::interface()->name)) {
mWindowExtension.reset(new QtWayland::qt_surface_extension(registry, id, 1)); mWindowExtension.reset(new QtWayland::qt_surface_extension(registry, id, 1));

View File

@ -66,7 +66,7 @@ void QWaylandDrag::startDrag()
{ {
QBasicDrag::startDrag(); QBasicDrag::startDrag();
QWaylandWindow *icon = static_cast<QWaylandWindow *>(shapedPixmapWindow()->handle()); QWaylandWindow *icon = static_cast<QWaylandWindow *>(shapedPixmapWindow()->handle());
if (m_display->currentInputDevice()->dataDevice()->startDrag(drag()->mimeData(), icon)) { if (m_display->currentInputDevice()->dataDevice()->startDrag(drag()->mimeData(), drag()->supportedActions(), icon)) {
icon->addAttachOffset(-drag()->hotSpot()); icon->addAttachOffset(-drag()->hotSpot());
} else { } else {
// Cancelling immediately does not work, since the event loop for QDrag::exec is started // Cancelling immediately does not work, since the event loop for QDrag::exec is started
@ -103,31 +103,31 @@ void QWaylandDrag::endDrag()
m_display->currentInputDevice()->handleEndDrag(); m_display->currentInputDevice()->handleEndDrag();
} }
void QWaylandDrag::updateTarget(const QString &mimeType) void QWaylandDrag::setResponse(bool accepted)
{ {
setCanDrop(!mimeType.isEmpty()); // This method is used for old DataDevices where the drag action is not communicated
Qt::DropAction action = defaultAction(drag()->supportedActions(), m_display->currentInputDevice()->modifiers());
if (canDrop()) { setResponse(QPlatformDropQtResponse(accepted, action));
updateCursor(defaultAction(drag()->supportedActions(), m_display->currentInputDevice()->modifiers()));
} else {
updateCursor(Qt::IgnoreAction);
}
} }
void QWaylandDrag::setResponse(const QPlatformDragQtResponse &response) void QWaylandDrag::setResponse(const QPlatformDropQtResponse &response)
{ {
setCanDrop(response.isAccepted()); setCanDrop(response.isAccepted());
if (canDrop()) { if (canDrop()) {
updateCursor(defaultAction(drag()->supportedActions(), m_display->currentInputDevice()->modifiers())); updateCursor(response.acceptedAction());
} else { } else {
updateCursor(Qt::IgnoreAction); updateCursor(Qt::IgnoreAction);
} }
} }
void QWaylandDrag::finishDrag(const QPlatformDropQtResponse &response) void QWaylandDrag::setDropResponse(const QPlatformDropQtResponse &response)
{ {
setExecutedDropAction(response.acceptedAction()); setExecutedDropAction(response.acceptedAction());
}
void QWaylandDrag::finishDrag()
{
QKeyEvent event(QEvent::KeyPress, Qt::Key_Escape, Qt::NoModifier); QKeyEvent event(QEvent::KeyPress, Qt::Key_Escape, Qt::NoModifier);
eventFilter(shapedPixmapWindow(), &event); eventFilter(shapedPixmapWindow(), &event);
} }

View File

@ -71,9 +71,10 @@ public:
QWaylandDrag(QWaylandDisplay *display); QWaylandDrag(QWaylandDisplay *display);
~QWaylandDrag() override; ~QWaylandDrag() override;
void updateTarget(const QString &mimeType); void setResponse(bool accepted);
void setResponse(const QPlatformDragQtResponse &response); void setResponse(const QPlatformDropQtResponse &response);
void finishDrag(const QPlatformDropQtResponse &response); void setDropResponse(const QPlatformDropQtResponse &response);
void finishDrag();
protected: protected:
void startDrag() override; void startDrag() override;

View File

@ -35,7 +35,7 @@
using namespace MockCompositor; using namespace MockCompositor;
constexpr int dataDeviceVersion = 1; constexpr int dataDeviceVersion = 3;
class DataDeviceCompositor : public DefaultCompositor { class DataDeviceCompositor : public DefaultCompositor {
public: public: