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-qt-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}
47
48QWaylandDataOffer *QWaylandDataDevice::selectionOffer() const
49{
50 return m_selectionOffer.data();
51}
52
53void QWaylandDataDevice::invalidateSelectionOffer()
54{
55 if (m_selectionOffer.isNull())
56 return;
57
58 m_selectionOffer.reset();
59
60#if QT_CONFIG(clipboard)
61 QGuiApplicationPrivate::platformIntegration()->clipboard()->emitChanged(mode: QClipboard::Clipboard);
62#endif
63}
64
65QWaylandDataSource *QWaylandDataDevice::selectionSource() const
66{
67 return m_selectionSource.data();
68}
69
70void QWaylandDataDevice::setSelectionSource(QWaylandDataSource *source)
71{
72 if (source)
73 connect(sender: source, signal: &QWaylandDataSource::cancelled, context: this, slot: &QWaylandDataDevice::selectionSourceCancelled);
74 set_selection(source ? source->object() : nullptr, m_inputDevice->serial());
75 m_selectionSource.reset(other: source);
76}
77
78#if QT_CONFIG(draganddrop)
79QWaylandDataOffer *QWaylandDataDevice::dragOffer() const
80{
81 return m_dragOffer.data();
82}
83
84bool QWaylandDataDevice::startDrag(QMimeData *mimeData, Qt::DropActions supportedActions, QWaylandWindow *icon)
85{
86 auto *seat = m_display->currentInputDevice();
87 auto *origin = seat->pointerFocus();
88 if (!origin)
89 origin = seat->touchFocus();
90
91 if (!origin) {
92 qCDebug(lcQpaWayland) << "Couldn't start a drag because the origin window could not be found.";
93 return false;
94 }
95
96 m_dragSource.reset(other: new QWaylandDataSource(m_display->dndSelectionHandler(), mimeData));
97
98 if (version() >= 3)
99 m_dragSource->set_actions(dropActionsToWl(dropActions: supportedActions));
100
101 connect(sender: m_dragSource.data(), signal: &QWaylandDataSource::cancelled, context: this, slot: &QWaylandDataDevice::dragSourceCancelled);
102 connect(sender: m_dragSource.data(), signal: &QWaylandDataSource::dndResponseUpdated, context: this, slot: [this](bool accepted, Qt::DropAction action) {
103 auto drag = static_cast<QWaylandDrag *>(QGuiApplicationPrivate::platformIntegration()->drag());
104 if (!drag->currentDrag()) {
105 return;
106 }
107 // in old versions drop action is not set, so we guess
108 if (m_dragSource->version() < 3) {
109 drag->setResponse(accepted);
110 } else {
111 QPlatformDropQtResponse response(accepted, action);
112 drag->setResponse(response);
113 }
114 });
115 connect(sender: m_dragSource.data(), signal: &QWaylandDataSource::dndDropped, context: this,
116 slot: [this](bool accepted, Qt::DropAction action) {
117 QPlatformDropQtResponse response(accepted, action);
118 if (m_toplevelDrag) {
119 // If the widget was dropped but the drag not accepted it
120 // should be its own window in the future. To distinguish
121 // from canceling mid-drag the drag is accepted here as the
122 // we know if the widget is over a zone where it can be
123 // incorporated or not
124 response = { accepted, Qt::MoveAction };
125 }
126 static_cast<QWaylandDrag *>(QGuiApplicationPrivate::platformIntegration()->drag())
127 ->setDropResponse(response);
128 });
129 connect(sender: m_dragSource.data(), signal: &QWaylandDataSource::finished, context: this, slot: [this]() {
130 static_cast<QWaylandDrag *>(QGuiApplicationPrivate::platformIntegration()->drag())->finishDrag();
131 if (m_toplevelDrag) {
132 m_toplevelDrag->destroy();
133 m_toplevelDrag = nullptr;
134 }
135 });
136
137 if (mimeData->hasFormat(mimetype: "application/x-qt-mainwindowdrag-window"_L1)
138 && m_display->xdgToplevelDragManager()) {
139 qintptr dockWindowPtr;
140 QPoint offset;
141 QDataStream windowStream(mimeData->data(mimetype: "application/x-qt-mainwindowdrag-window"_L1));
142 windowStream >> dockWindowPtr;
143 QWindow *dockWindow = reinterpret_cast<QWindow *>(dockWindowPtr);
144 QDataStream offsetStream(mimeData->data(mimetype: "application/x-qt-mainwindowdrag-position"_L1));
145 offsetStream >> offset;
146 if (auto waylandWindow = static_cast<QWaylandWindow *>(dockWindow->handle())) {
147 if (auto toplevel = waylandWindow->surfaceRole<xdg_toplevel>()) {
148 m_toplevelDrag = new QtWayland::qt_toplevel_drag_v1(
149 m_display->xdgToplevelDragManager()->get_qt_toplevel_drag(
150 m_dragSource->object()));
151 m_toplevelDrag->attach(toplevel, offset.x(), offset.y());
152 }
153 }
154 }
155
156 start_drag(m_dragSource->object(), origin->wlSurface(), icon->wlSurface(), m_display->currentInputDevice()->serial());
157 return true;
158}
159
160void QWaylandDataDevice::cancelDrag()
161{
162 m_dragSource.reset();
163}
164#endif
165
166void QWaylandDataDevice::data_device_data_offer(struct ::wl_data_offer *id)
167{
168 new QWaylandDataOffer(m_display, id);
169}
170
171#if QT_CONFIG(draganddrop)
172void QWaylandDataDevice::data_device_drop()
173{
174 QDrag *drag = static_cast<QWaylandDrag *>(QGuiApplicationPrivate::platformIntegration()->drag())->currentDrag();
175
176 QMimeData *dragData = nullptr;
177 Qt::DropActions supportedActions;
178 if (drag) {
179 dragData = drag->mimeData();
180 supportedActions = drag->supportedActions();
181 } else if (m_dragOffer) {
182 dragData = m_dragOffer->mimeData();
183 supportedActions = m_dragOffer->supportedActions();
184 } else {
185 return;
186 }
187
188 QPlatformDropQtResponse response = QWindowSystemInterface::handleDrop(window: m_dragWindow, dropData: dragData, p: m_dragPoint, supportedActions,
189 buttons: QGuiApplication::mouseButtons(),
190 modifiers: QGuiApplication::keyboardModifiers());
191 if (drag) {
192 auto drag = static_cast<QWaylandDrag *>(QGuiApplicationPrivate::platformIntegration()->drag());
193 drag->setDropResponse(response);
194 drag->finishDrag();
195 } else if (m_dragOffer) {
196 m_dragOffer->finish();
197 }
198}
199
200void QWaylandDataDevice::data_device_enter(uint32_t serial, wl_surface *surface, wl_fixed_t x, wl_fixed_t y, wl_data_offer *id)
201{
202 auto *dragWaylandWindow = surface ? QWaylandWindow::fromWlSurface(surface) : nullptr;
203 if (!dragWaylandWindow)
204 return; // Ignore foreign surfaces
205
206 m_dragWindow = dragWaylandWindow->window();
207 m_dragPoint = calculateDragPosition(x: x, y: y, wnd: m_dragWindow);
208 m_enterSerial = serial;
209
210 QMimeData *dragData = nullptr;
211 Qt::DropActions supportedActions;
212
213 m_dragOffer.reset(other: static_cast<QWaylandDataOffer *>(wl_data_offer_get_user_data(id)));
214 QDrag *drag = static_cast<QWaylandDrag *>(QGuiApplicationPrivate::platformIntegration()->drag())->currentDrag();
215 if (drag) {
216 dragData = drag->mimeData();
217 supportedActions = drag->supportedActions();
218 } else if (m_dragOffer) {
219 dragData = m_dragOffer->mimeData();
220 supportedActions = m_dragOffer->supportedActions();
221 }
222
223 const QPlatformDragQtResponse &response = QWindowSystemInterface::handleDrag(
224 window: m_dragWindow, dropData: dragData, p: m_dragPoint, supportedActions, buttons: QGuiApplication::mouseButtons(),
225 modifiers: QGuiApplication::keyboardModifiers());
226 if (drag) {
227 static_cast<QWaylandDrag *>(QGuiApplicationPrivate::platformIntegration()->drag())->setResponse(response);
228 }
229
230 sendResponse(supportedActions, response);
231}
232
233void QWaylandDataDevice::data_device_leave()
234{
235 if (m_dragWindow)
236 QWindowSystemInterface::handleDrag(window: m_dragWindow, dropData: nullptr, p: QPoint(), supportedActions: Qt::IgnoreAction,
237 buttons: QGuiApplication::mouseButtons(),
238 modifiers: QGuiApplication::keyboardModifiers());
239
240 QDrag *drag = static_cast<QWaylandDrag *>(QGuiApplicationPrivate::platformIntegration()->drag())->currentDrag();
241 if (!drag) {
242 m_dragOffer.reset();
243 }
244}
245
246void QWaylandDataDevice::data_device_motion(uint32_t time, wl_fixed_t x, wl_fixed_t y)
247{
248 Q_UNUSED(time);
249
250 QDrag *drag = static_cast<QWaylandDrag *>(QGuiApplicationPrivate::platformIntegration()->drag())->currentDrag();
251
252 if (!drag && !m_dragOffer)
253 return;
254
255 m_dragPoint = calculateDragPosition(x: x, y: y, wnd: m_dragWindow);
256
257 QMimeData *dragData = nullptr;
258 Qt::DropActions supportedActions;
259 if (drag) {
260 dragData = drag->mimeData();
261 supportedActions = drag->supportedActions();
262 } else {
263 dragData = m_dragOffer->mimeData();
264 supportedActions = m_dragOffer->supportedActions();
265 }
266
267 const QPlatformDragQtResponse response = QWindowSystemInterface::handleDrag(window: m_dragWindow, dropData: dragData, p: m_dragPoint, supportedActions,
268 buttons: QGuiApplication::mouseButtons(),
269 modifiers: QGuiApplication::keyboardModifiers());
270
271 if (drag) {
272 static_cast<QWaylandDrag *>(QGuiApplicationPrivate::platformIntegration()->drag())->setResponse(response);
273 }
274
275 sendResponse(supportedActions, response);
276}
277#endif // QT_CONFIG(draganddrop)
278
279void QWaylandDataDevice::data_device_selection(wl_data_offer *id)
280{
281 if (id)
282 m_selectionOffer.reset(other: static_cast<QWaylandDataOffer *>(wl_data_offer_get_user_data(id)));
283 else
284 m_selectionOffer.reset();
285
286#if QT_CONFIG(clipboard)
287 QGuiApplicationPrivate::platformIntegration()->clipboard()->emitChanged(mode: QClipboard::Clipboard);
288#endif
289}
290
291void QWaylandDataDevice::selectionSourceCancelled()
292{
293 m_selectionSource.reset();
294#if QT_CONFIG(clipboard)
295 QGuiApplicationPrivate::platformIntegration()->clipboard()->emitChanged(mode: QClipboard::Clipboard);
296#endif
297}
298
299#if QT_CONFIG(draganddrop)
300void QWaylandDataDevice::dragSourceCancelled()
301{
302 static_cast<QWaylandDrag *>(QGuiApplicationPrivate::platformIntegration()->drag())->finishDrag();
303 m_dragSource.reset();
304 if (m_toplevelDrag) {
305 m_toplevelDrag->destroy();
306 m_toplevelDrag = nullptr;
307 }
308}
309
310QPoint QWaylandDataDevice::calculateDragPosition(int x, int y, QWindow *wnd) const
311{
312 QPoint pnt(wl_fixed_to_int(x), wl_fixed_to_int(y));
313 if (wnd) {
314 QWaylandWindow *wwnd = static_cast<QWaylandWindow*>(m_dragWindow->handle());
315 if (wwnd && wwnd->decoration()) {
316 pnt -= QPoint(wwnd->decoration()->margins().left(),
317 wwnd->decoration()->margins().top());
318 }
319 }
320 return pnt;
321}
322
323void QWaylandDataDevice::sendResponse(Qt::DropActions supportedActions, const QPlatformDragQtResponse &response)
324{
325 if (response.isAccepted()) {
326 if (version() >= 3)
327 m_dragOffer->set_actions(dropActionsToWl(dropActions: supportedActions), dropActionsToWl(dropActions: response.acceptedAction()));
328
329 m_dragOffer->accept(m_enterSerial, m_dragOffer->firstFormat());
330 } else {
331 m_dragOffer->accept(m_enterSerial, QString());
332 }
333}
334
335int QWaylandDataDevice::dropActionsToWl(Qt::DropActions actions)
336{
337
338 int wlActions = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE;
339 if (actions & Qt::CopyAction)
340 wlActions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY;
341 if (actions & (Qt::MoveAction | Qt::TargetMoveAction))
342 wlActions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE;
343
344 // wayland does not support LinkAction at the time of writing
345 return wlActions;
346}
347
348
349#endif // QT_CONFIG(draganddrop)
350
351}
352
353QT_END_NAMESPACE
354
355#include "moc_qwaylanddatadevice_p.cpp"
356

source code of qtwayland/src/client/qwaylanddatadevice.cpp