1// Copyright (C) 2016 Klarälvdalens Datakonsult AB (KDAB).
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4
5#include "qwaylanddatadevice_p.h"
6
7#include "qwaylanddatadevicemanager_p.h"
8#include "qwaylanddataoffer_p.h"
9#include "qwaylanddatasource_p.h"
10#include "qwaylanddnd_p.h"
11#include "qwaylandinputdevice_p.h"
12#include "qwaylanddisplay_p.h"
13#include "qwaylandabstractdecoration_p.h"
14#include "qwaylandsurface_p.h"
15
16#include <QtWaylandClient/private/qwayland-xdg-toplevel-drag-v1.h>
17
18#include <QtCore/QMimeData>
19#include <QtGui/QGuiApplication>
20#include <QtGui/private/qguiapplication_p.h>
21
22#if QT_CONFIG(clipboard)
23#include <qpa/qplatformclipboard.h>
24#endif
25#include <qpa/qplatformdrag.h>
26#include <qpa/qwindowsysteminterface.h>
27
28QT_BEGIN_NAMESPACE
29
30namespace QtWaylandClient {
31
32using namespace Qt::StringLiterals;
33
34QWaylandDataDevice::QWaylandDataDevice(QWaylandDataDeviceManager *manager, QWaylandInputDevice *inputDevice)
35 : QObject(inputDevice)
36 , QtWayland::wl_data_device(manager->get_data_device(inputDevice->wl_seat()))
37 , m_display(manager->display())
38 , m_inputDevice(inputDevice)
39{
40}
41
42QWaylandDataDevice::~QWaylandDataDevice()
43{
44 if (version() >= WL_DATA_DEVICE_RELEASE_SINCE_VERSION)
45 release();
46 else
47 wl_data_device_destroy(object());
48}
49
50QWaylandDataOffer *QWaylandDataDevice::selectionOffer() const
51{
52 return m_selectionOffer.data();
53}
54
55void QWaylandDataDevice::invalidateSelectionOffer()
56{
57 if (m_selectionOffer.isNull())
58 return;
59
60 m_selectionOffer.reset();
61
62#if QT_CONFIG(clipboard)
63 QGuiApplicationPrivate::platformIntegration()->clipboard()->emitChanged(mode: QClipboard::Clipboard);
64#endif
65}
66
67QWaylandDataSource *QWaylandDataDevice::selectionSource() const
68{
69 return m_selectionSource.data();
70}
71
72void QWaylandDataDevice::setSelectionSource(QWaylandDataSource *source)
73{
74 if (source)
75 connect(sender: source, signal: &QWaylandDataSource::cancelled, context: this, slot: &QWaylandDataDevice::selectionSourceCancelled);
76 set_selection(source ? source->object() : nullptr, m_inputDevice->serial());
77 m_selectionSource.reset(other: source);
78}
79
80#if QT_CONFIG(draganddrop)
81QWaylandDataOffer *QWaylandDataDevice::dragOffer() const
82{
83 return m_dragOffer.data();
84}
85
86bool QWaylandDataDevice::startDrag(QMimeData *mimeData, Qt::DropActions supportedActions, QWaylandWindow *icon)
87{
88 auto *origin = m_display->lastInputWindow();
89
90 if (!origin) {
91 qCDebug(lcQpaWayland) << "Couldn't start a drag because the origin window could not be found.";
92 return false;
93 }
94
95 // dragging data without mimetypes is a legal operation in Qt terms
96 // but Wayland uses a mimetype to determine if a drag is accepted or not
97 // In this rare case, insert a placeholder
98 if (mimeData->formats().isEmpty())
99 mimeData->setData(mimetype: "application/x-qt-avoid-empty-placeholder"_L1, data: QByteArray("1"));
100
101 static const QString plain = QStringLiteral("text/plain");
102 static const QString utf8 = QStringLiteral("text/plain;charset=utf-8");
103
104 if (mimeData->hasFormat(mimetype: plain) && !mimeData->hasFormat(mimetype: utf8))
105 mimeData->setData(mimetype: utf8, data: mimeData->data(mimetype: plain));
106
107 m_dragSource.reset(other: new QWaylandDataSource(m_display->dndSelectionHandler(), mimeData));
108
109 if (version() >= 3)
110 m_dragSource->set_actions(dropActionsToWl(dropActions: supportedActions));
111
112 connect(sender: m_dragSource.data(), signal: &QWaylandDataSource::cancelled, context: this, slot: &QWaylandDataDevice::dragSourceCancelled);
113 connect(sender: m_dragSource.data(), signal: &QWaylandDataSource::dndResponseUpdated, context: this, slot: [this](bool accepted, Qt::DropAction action) {
114 auto drag = static_cast<QWaylandDrag *>(QGuiApplicationPrivate::platformIntegration()->drag());
115 if (!drag->currentDrag()) {
116 return;
117 }
118 // in old versions drop action is not set, so we guess
119 if (m_dragSource->version() < 3) {
120 drag->setResponse(accepted);
121 } else {
122 QPlatformDropQtResponse response(accepted, action);
123 drag->setResponse(response);
124 }
125 });
126 connect(sender: m_dragSource.data(), signal: &QWaylandDataSource::dndDropped, context: this,
127 slot: [this](bool accepted, Qt::DropAction action) {
128 QPlatformDropQtResponse response(accepted, action);
129 if (m_toplevelDrag) {
130 // If the widget was dropped but the drag not accepted it
131 // should be its own window in the future. To distinguish
132 // from canceling mid-drag the drag is accepted here as the
133 // we know if the widget is over a zone where it can be
134 // incorporated or not
135 response = { true, Qt::MoveAction };
136 }
137 static_cast<QWaylandDrag *>(QGuiApplicationPrivate::platformIntegration()->drag())
138 ->setDropResponse(response);
139 });
140 connect(sender: m_dragSource.data(), signal: &QWaylandDataSource::finished, context: this, slot: [this]() {
141 static_cast<QWaylandDrag *>(QGuiApplicationPrivate::platformIntegration()->drag())->finishDrag();
142 if (m_toplevelDrag) {
143 m_toplevelDrag->destroy();
144 m_toplevelDrag = nullptr;
145 }
146 });
147
148 if (mimeData->hasFormat(mimetype: "application/x-qt-mainwindowdrag-window"_L1)
149 && m_display->xdgToplevelDragManager()) {
150 qintptr dockWindowPtr;
151 QPoint offset;
152 QDataStream windowStream(mimeData->data(mimetype: "application/x-qt-mainwindowdrag-window"_L1));
153 windowStream >> dockWindowPtr;
154 QWindow *dockWindow = reinterpret_cast<QWindow *>(dockWindowPtr);
155 QDataStream offsetStream(mimeData->data(mimetype: "application/x-qt-mainwindowdrag-position"_L1));
156 offsetStream >> offset;
157 if (auto waylandWindow = static_cast<QWaylandWindow *>(dockWindow->handle())) {
158 if (auto toplevel = waylandWindow->surfaceRole<xdg_toplevel>()) {
159 m_toplevelDrag = new QtWayland::xdg_toplevel_drag_v1(
160 m_display->xdgToplevelDragManager()->get_xdg_toplevel_drag(
161 m_dragSource->object()));
162 m_toplevelDrag->attach(toplevel, offset.x(), offset.y());
163 }
164 }
165 }
166
167 start_drag(m_dragSource->object(), origin->wlSurface(), icon->wlSurface(), m_display->lastInputSerial());
168 return true;
169}
170
171void QWaylandDataDevice::cancelDrag()
172{
173 m_dragSource.reset();
174}
175#endif
176
177void QWaylandDataDevice::data_device_data_offer(struct ::wl_data_offer *id)
178{
179 new QWaylandDataOffer(m_display, id);
180}
181
182#if QT_CONFIG(draganddrop)
183void QWaylandDataDevice::data_device_drop()
184{
185 QDrag *drag = static_cast<QWaylandDrag *>(QGuiApplicationPrivate::platformIntegration()->drag())->currentDrag();
186
187 QMimeData *dragData = nullptr;
188 Qt::DropActions supportedActions;
189 if (drag) {
190 dragData = drag->mimeData();
191 supportedActions = drag->supportedActions();
192 } else if (m_dragOffer) {
193 dragData = m_dragOffer->mimeData();
194 supportedActions = m_dragOffer->supportedActions();
195 } else {
196 return;
197 }
198
199 QPlatformDropQtResponse response = QWindowSystemInterface::handleDrop(window: m_dragWindow, dropData: dragData, p: m_dragPoint, supportedActions,
200 buttons: QGuiApplication::mouseButtons(),
201 modifiers: m_inputDevice->modifiers());
202
203 // re-evaluate as it could have changed during user code above
204 drag = static_cast<QWaylandDrag *>(QGuiApplicationPrivate::platformIntegration()->drag())->currentDrag();
205 if (drag) {
206 auto platformDrag = static_cast<QWaylandDrag *>(QGuiApplicationPrivate::platformIntegration()->drag());
207 platformDrag->setDropResponse(response);
208 platformDrag->finishDrag();
209 } else if (m_dragOffer) {
210 m_dragOffer->finish();
211 }
212}
213
214void QWaylandDataDevice::data_device_enter(uint32_t serial, wl_surface *surface, wl_fixed_t x, wl_fixed_t y, wl_data_offer *id)
215{
216 auto *dragWaylandWindow = surface ? QWaylandWindow::fromWlSurface(surface) : nullptr;
217 if (!dragWaylandWindow)
218 return; // Ignore foreign surfaces
219
220 m_dragWindow = dragWaylandWindow->window();
221 m_dragPoint = calculateDragPosition(x: x, y: y, wnd: m_dragWindow);
222 m_enterSerial = serial;
223
224 QMimeData *dragData = nullptr;
225 Qt::DropActions supportedActions;
226
227 m_dragOffer.reset(other: static_cast<QWaylandDataOffer *>(wl_data_offer_get_user_data(id)));
228 QDrag *drag = static_cast<QWaylandDrag *>(QGuiApplicationPrivate::platformIntegration()->drag())->currentDrag();
229 if (drag) {
230 dragData = drag->mimeData();
231 supportedActions = drag->supportedActions();
232 } else if (m_dragOffer) {
233 dragData = m_dragOffer->mimeData();
234 supportedActions = m_dragOffer->supportedActions();
235 }
236
237 const QPlatformDragQtResponse &response = QWindowSystemInterface::handleDrag(
238 window: m_dragWindow, dropData: dragData, p: m_dragPoint, supportedActions, buttons: QGuiApplication::mouseButtons(),
239 modifiers: m_inputDevice->modifiers());
240 if (drag) {
241 static_cast<QWaylandDrag *>(QGuiApplicationPrivate::platformIntegration()->drag())->setResponse(response);
242 }
243
244 sendResponse(supportedActions, response);
245}
246
247void QWaylandDataDevice::data_device_leave()
248{
249 if (m_dragWindow)
250 QWindowSystemInterface::handleDrag(window: m_dragWindow, dropData: nullptr, p: QPoint(), supportedActions: Qt::IgnoreAction,
251 buttons: QGuiApplication::mouseButtons(),
252 modifiers: m_inputDevice->modifiers());
253
254 QDrag *drag = static_cast<QWaylandDrag *>(QGuiApplicationPrivate::platformIntegration()->drag())->currentDrag();
255 if (!drag) {
256 m_dragOffer.reset();
257 }
258}
259
260void QWaylandDataDevice::data_device_motion(uint32_t time, wl_fixed_t x, wl_fixed_t y)
261{
262 Q_UNUSED(time);
263
264 QDrag *drag = static_cast<QWaylandDrag *>(QGuiApplicationPrivate::platformIntegration()->drag())->currentDrag();
265
266 if (!drag && !m_dragOffer)
267 return;
268
269 if (!m_dragWindow)
270 return;
271
272 m_dragPoint = calculateDragPosition(x: x, y: y, wnd: m_dragWindow);
273
274 QMimeData *dragData = nullptr;
275 Qt::DropActions supportedActions;
276 if (drag) {
277 dragData = drag->mimeData();
278 supportedActions = drag->supportedActions();
279 } else {
280 dragData = m_dragOffer->mimeData();
281 supportedActions = m_dragOffer->supportedActions();
282 }
283
284 const QPlatformDragQtResponse response = QWindowSystemInterface::handleDrag(window: m_dragWindow, dropData: dragData, p: m_dragPoint, supportedActions,
285 buttons: QGuiApplication::mouseButtons(),
286 modifiers: m_inputDevice->modifiers());
287
288 if (drag) {
289 static_cast<QWaylandDrag *>(QGuiApplicationPrivate::platformIntegration()->drag())->setResponse(response);
290 }
291
292 sendResponse(supportedActions, response);
293}
294#endif // QT_CONFIG(draganddrop)
295
296void QWaylandDataDevice::data_device_selection(wl_data_offer *id)
297{
298 if (id)
299 m_selectionOffer.reset(other: static_cast<QWaylandDataOffer *>(wl_data_offer_get_user_data(id)));
300 else
301 m_selectionOffer.reset();
302
303#if QT_CONFIG(clipboard)
304 QGuiApplicationPrivate::platformIntegration()->clipboard()->emitChanged(mode: QClipboard::Clipboard);
305#endif
306}
307
308void QWaylandDataDevice::selectionSourceCancelled()
309{
310 m_selectionSource.reset();
311#if QT_CONFIG(clipboard)
312 QGuiApplicationPrivate::platformIntegration()->clipboard()->emitChanged(mode: QClipboard::Clipboard);
313#endif
314}
315
316#if QT_CONFIG(draganddrop)
317void QWaylandDataDevice::dragSourceCancelled()
318{
319 static_cast<QWaylandDrag *>(QGuiApplicationPrivate::platformIntegration()->drag())->finishDrag();
320 m_dragSource.reset();
321 if (m_toplevelDrag) {
322 m_toplevelDrag->destroy();
323 m_toplevelDrag = nullptr;
324 }
325}
326
327QPoint QWaylandDataDevice::calculateDragPosition(int x, int y, QWindow *wnd) const
328{
329 QPoint pnt(wl_fixed_to_int(x), wl_fixed_to_int(y));
330 if (wnd) {
331 QWaylandWindow *wwnd = static_cast<QWaylandWindow*>(m_dragWindow->handle());
332 if (wwnd && wwnd->decoration()) {
333 pnt -= QPoint(wwnd->decoration()->margins().left(),
334 wwnd->decoration()->margins().top());
335 }
336 }
337 return pnt;
338}
339
340void QWaylandDataDevice::sendResponse(Qt::DropActions supportedActions, const QPlatformDragQtResponse &response)
341{
342 if (response.isAccepted()) {
343 if (version() >= 3)
344 m_dragOffer->set_actions(dropActionsToWl(dropActions: supportedActions), dropActionsToWl(dropActions: response.acceptedAction()));
345
346 m_dragOffer->accept(m_enterSerial, m_dragOffer->firstFormat());
347 } else {
348 m_dragOffer->accept(m_enterSerial, QString());
349 }
350}
351
352int QWaylandDataDevice::dropActionsToWl(Qt::DropActions actions)
353{
354
355 int wlActions = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE;
356 if (actions & Qt::CopyAction)
357 wlActions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY;
358 if (actions & (Qt::MoveAction | Qt::TargetMoveAction))
359 wlActions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE;
360
361 // wayland does not support LinkAction at the time of writing
362 return wlActions;
363}
364
365
366#endif // QT_CONFIG(draganddrop)
367
368}
369
370QT_END_NAMESPACE
371
372#include "moc_qwaylanddatadevice_p.cpp"
373

source code of qtbase/src/plugins/platforms/wayland/qwaylanddatadevice.cpp