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

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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