1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qxcbdrag.h"
5#include <xcb/xcb.h>
6#include "qxcbconnection.h"
7#include "qxcbclipboard.h"
8#include "qxcbkeyboard.h"
9#include "qxcbmime.h"
10#include "qxcbwindow.h"
11#include "qxcbscreen.h"
12#include "qwindow.h"
13#include "qxcbcursor.h"
14#include <private/qdnd_p.h>
15#include <qdebug.h>
16#include <qevent.h>
17#include <qguiapplication.h>
18#include <qrect.h>
19#include <qpainter.h>
20
21#include <qpa/qwindowsysteminterface.h>
22
23#include <private/qguiapplication_p.h>
24#include <private/qshapedpixmapdndwindow_p.h>
25#include <private/qsimpledrag_p.h>
26#include <private/qhighdpiscaling_p.h>
27
28QT_BEGIN_NAMESPACE
29
30using namespace Qt::Literals::StringLiterals;
31
32const int xdnd_version = 5;
33
34static inline xcb_window_t xcb_window(QPlatformWindow *w)
35{
36 return static_cast<QXcbWindow *>(w)->xcb_window();
37}
38
39static inline xcb_window_t xcb_window(QWindow *w)
40{
41 return static_cast<QXcbWindow *>(w->handle())->xcb_window();
42}
43
44static xcb_window_t xdndProxy(QXcbConnection *c, xcb_window_t w)
45{
46 xcb_window_t proxy = XCB_NONE;
47
48 auto reply = Q_XCB_REPLY(xcb_get_property, c->xcb_connection(),
49 false, w, c->atom(QXcbAtom::AtomXdndProxy), XCB_ATOM_WINDOW, 0, 1);
50
51 if (reply && reply->type == XCB_ATOM_WINDOW)
52 proxy = *((xcb_window_t *)xcb_get_property_value(R: reply.get()));
53
54 if (proxy == XCB_NONE)
55 return proxy;
56
57 // exists and is real?
58 reply = Q_XCB_REPLY(xcb_get_property, c->xcb_connection(),
59 false, proxy, c->atom(QXcbAtom::AtomXdndProxy), XCB_ATOM_WINDOW, 0, 1);
60
61 if (reply && reply->type == XCB_ATOM_WINDOW) {
62 xcb_window_t p = *((xcb_window_t *)xcb_get_property_value(R: reply.get()));
63 if (proxy != p)
64 proxy = XCB_NONE;
65 } else {
66 proxy = XCB_NONE;
67 }
68
69 return proxy;
70}
71
72class QXcbDropData : public QXcbMime
73{
74public:
75 QXcbDropData(QXcbDrag *d);
76 ~QXcbDropData();
77
78protected:
79 bool hasFormat_sys(const QString &mimeType) const override;
80 QStringList formats_sys() const override;
81 QVariant retrieveData_sys(const QString &mimeType, QMetaType type) const override;
82
83 QVariant xdndObtainData(const QByteArray &format, QMetaType requestedType) const;
84
85 QXcbDrag *drag;
86};
87
88
89QXcbDrag::QXcbDrag(QXcbConnection *c) : QXcbObject(c)
90{
91 m_dropData = new QXcbDropData(this);
92
93 init();
94}
95
96QXcbDrag::~QXcbDrag()
97{
98 delete m_dropData;
99}
100
101void QXcbDrag::init()
102{
103 currentWindow.clear();
104
105 accepted_drop_action = Qt::IgnoreAction;
106
107 xdnd_dragsource = XCB_NONE;
108
109 waiting_for_status = false;
110 current_target = XCB_NONE;
111 current_proxy_target = XCB_NONE;
112
113 source_time = XCB_CURRENT_TIME;
114 target_time = XCB_CURRENT_TIME;
115
116 QXcbCursor::queryPointer(c: connection(), virtualDesktop: &current_virtual_desktop, pos: nullptr);
117 drag_types.clear();
118
119 //current_embedding_widget = 0;
120
121 dropped = false;
122 canceled = false;
123
124 source_sameanswer = QRect();
125}
126
127bool QXcbDrag::eventFilter(QObject *o, QEvent *e)
128{
129 /* We are setting a mouse grab on the QShapedPixmapWindow in order not to
130 * lose the grab when the virtual desktop changes, but
131 * QBasicDrag::eventFilter() expects the events to be coming from the
132 * window where the drag was started. */
133 if (initiatorWindow && o == shapedPixmapWindow())
134 o = initiatorWindow.data();
135 return QBasicDrag::eventFilter(o, e);
136}
137
138void QXcbDrag::startDrag()
139{
140 init();
141
142 qCDebug(lcQpaXDnd) << "starting drag where source:" << connection()->qtSelectionOwner();
143 xcb_set_selection_owner(c: xcb_connection(), owner: connection()->qtSelectionOwner(),
144 selection: atom(atom: QXcbAtom::AtomXdndSelection), time: connection()->time());
145
146 QStringList fmts = QXcbMime::formatsHelper(data: drag()->mimeData());
147 for (int i = 0; i < fmts.size(); ++i) {
148 QList<xcb_atom_t> atoms = QXcbMime::mimeAtomsForFormat(connection: connection(), format: fmts.at(i));
149 for (int j = 0; j < atoms.size(); ++j) {
150 if (!drag_types.contains(t: atoms.at(i: j)))
151 drag_types.append(t: atoms.at(i: j));
152 }
153 }
154
155 if (drag_types.size() > 3)
156 xcb_change_property(c: xcb_connection(), mode: XCB_PROP_MODE_REPLACE, window: connection()->qtSelectionOwner(),
157 property: atom(atom: QXcbAtom::AtomXdndTypelist),
158 type: XCB_ATOM_ATOM, format: 32, data_len: drag_types.size(), data: (const void *)drag_types.constData());
159
160 setUseCompositing(current_virtual_desktop->compositingActive());
161 setScreen(current_virtual_desktop->screens().constFirst()->screen());
162 initiatorWindow = QGuiApplicationPrivate::currentMouseWindow;
163 QBasicDrag::startDrag();
164 if (connection()->mouseGrabber() == nullptr)
165 shapedPixmapWindow()->setMouseGrabEnabled(true);
166
167 auto nativePixelPos = QHighDpi::toNativePixels(value: QCursor::pos(), context: initiatorWindow.data());
168 move(globalPos: nativePixelPos, b: QGuiApplication::mouseButtons(), mods: QGuiApplication::keyboardModifiers());
169}
170
171void QXcbDrag::endDrag()
172{
173 QBasicDrag::endDrag();
174 if (!dropped && !canceled && canDrop()) {
175 // Set executed drop action when dropping outside application.
176 setExecutedDropAction(accepted_drop_action);
177 }
178 initiatorWindow.clear();
179}
180
181Qt::DropAction QXcbDrag::defaultAction(Qt::DropActions possibleActions, Qt::KeyboardModifiers modifiers) const
182{
183 if (currentDrag() || drop_actions.isEmpty())
184 return QBasicDrag::defaultAction(possibleActions, modifiers);
185
186 return toDropAction(atom: drop_actions.first());
187}
188
189void QXcbDrag::handlePropertyNotifyEvent(const xcb_property_notify_event_t *event)
190{
191 if (event->window != xdnd_dragsource || event->atom != atom(atom: QXcbAtom::AtomXdndActionList))
192 return;
193
194 readActionList();
195}
196
197static
198bool windowInteractsWithPosition(xcb_connection_t *connection, const QPoint & pos, xcb_window_t w, xcb_shape_sk_t shapeType)
199{
200 bool interacts = false;
201 auto reply = Q_XCB_REPLY(xcb_shape_get_rectangles, connection, w, shapeType);
202 if (reply) {
203 xcb_rectangle_t *rectangles = xcb_shape_get_rectangles_rectangles(R: reply.get());
204 if (rectangles) {
205 const int nRectangles = xcb_shape_get_rectangles_rectangles_length(R: reply.get());
206 for (int i = 0; !interacts && i < nRectangles; ++i) {
207 interacts = QRect(rectangles[i].x, rectangles[i].y, rectangles[i].width, rectangles[i].height).contains(p: pos);
208 }
209 }
210 }
211
212 return interacts;
213}
214
215xcb_window_t QXcbDrag::findRealWindow(const QPoint & pos, xcb_window_t w, int md, bool ignoreNonXdndAwareWindows)
216{
217 if (w == shapedPixmapWindow()->handle()->winId())
218 return 0;
219
220 if (md) {
221 auto reply = Q_XCB_REPLY(xcb_get_window_attributes, xcb_connection(), w);
222 if (!reply)
223 return 0;
224
225 if (reply->map_state != XCB_MAP_STATE_VIEWABLE)
226 return 0;
227
228 auto greply = Q_XCB_REPLY(xcb_get_geometry, xcb_connection(), w);
229 if (!greply)
230 return 0;
231
232 QRect windowRect(greply->x, greply->y, greply->width, greply->height);
233 if (windowRect.contains(p: pos)) {
234 bool windowContainsMouse = !ignoreNonXdndAwareWindows;
235 {
236 auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(),
237 false, w, connection()->atom(QXcbAtom::AtomXdndAware),
238 XCB_GET_PROPERTY_TYPE_ANY, 0, 0);
239 bool isAware = reply && reply->type != XCB_NONE;
240 if (isAware) {
241 const QPoint relPos = pos - windowRect.topLeft();
242 // When ShapeInput and ShapeBounding are not set they return a single rectangle with the geometry of the window, this is why we
243 // need to check both here so that in the case one is set and the other is not we still get the correct result.
244 if (connection()->hasInputShape())
245 windowContainsMouse = windowInteractsWithPosition(connection: xcb_connection(), pos: relPos, w, shapeType: XCB_SHAPE_SK_INPUT);
246 if (windowContainsMouse && connection()->hasXShape())
247 windowContainsMouse = windowInteractsWithPosition(connection: xcb_connection(), pos: relPos, w, shapeType: XCB_SHAPE_SK_BOUNDING);
248 if (!connection()->hasInputShape() && !connection()->hasXShape())
249 windowContainsMouse = true;
250 if (windowContainsMouse)
251 return w;
252 }
253 }
254
255 auto reply = Q_XCB_REPLY(xcb_query_tree, xcb_connection(), w);
256 if (!reply)
257 return 0;
258 int nc = xcb_query_tree_children_length(R: reply.get());
259 xcb_window_t *c = xcb_query_tree_children(R: reply.get());
260
261 xcb_window_t r = 0;
262 for (uint i = nc; !r && i--;)
263 r = findRealWindow(pos: pos - windowRect.topLeft(), w: c[i], md: md-1, ignoreNonXdndAwareWindows);
264
265 if (r)
266 return r;
267
268 // We didn't find a client window! Just use the
269 // innermost window.
270
271 // No children!
272 if (!windowContainsMouse)
273 return 0;
274 else
275 return w;
276 }
277 }
278 return 0;
279}
280
281bool QXcbDrag::findXdndAwareTarget(const QPoint &globalPos, xcb_window_t *target_out)
282{
283 xcb_window_t rootwin = current_virtual_desktop->root();
284 auto translate = Q_XCB_REPLY(xcb_translate_coordinates, xcb_connection(),
285 rootwin, rootwin, globalPos.x(), globalPos.y());
286 if (!translate)
287 return false;
288
289 xcb_window_t target = translate->child;
290 int lx = translate->dst_x;
291 int ly = translate->dst_y;
292
293 if (target && target != rootwin) {
294 xcb_window_t src = rootwin;
295 while (target != 0) {
296 qCDebug(lcQpaXDnd) << "checking target for XdndAware" << target;
297
298 auto translate = Q_XCB_REPLY(xcb_translate_coordinates, xcb_connection(),
299 src, target, lx, ly);
300 if (!translate) {
301 target = 0;
302 break;
303 }
304 lx = translate->dst_x;
305 ly = translate->dst_y;
306 src = target;
307 xcb_window_t child = translate->child;
308
309 auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(), false, target,
310 atom(QXcbAtom::AtomXdndAware), XCB_GET_PROPERTY_TYPE_ANY, 0, 0);
311 bool aware = reply && reply->type != XCB_NONE;
312 if (aware) {
313 qCDebug(lcQpaXDnd) << "found XdndAware on" << target;
314 break;
315 }
316
317 target = child;
318 }
319
320 if (!target || target == shapedPixmapWindow()->handle()->winId()) {
321 qCDebug(lcQpaXDnd) << "need to find real window";
322 target = findRealWindow(pos: globalPos, w: rootwin, md: 6, ignoreNonXdndAwareWindows: true);
323 if (target == 0)
324 target = findRealWindow(pos: globalPos, w: rootwin, md: 6, ignoreNonXdndAwareWindows: false);
325 qCDebug(lcQpaXDnd) << "real window found" << target;
326 }
327 }
328
329 *target_out = target;
330 return true;
331}
332
333void QXcbDrag::move(const QPoint &globalPos, Qt::MouseButtons b, Qt::KeyboardModifiers mods)
334{
335 // currentDrag() might be deleted while 'drag' is progressing
336 if (!currentDrag()) {
337 cancel();
338 return;
339 }
340 // The source sends XdndEnter and XdndPosition to the target.
341 if (source_sameanswer.contains(p: globalPos) && source_sameanswer.isValid())
342 return;
343
344 QXcbVirtualDesktop *virtualDesktop = nullptr;
345 QPoint cursorPos;
346 QXcbCursor::queryPointer(c: connection(), virtualDesktop: &virtualDesktop, pos: &cursorPos);
347 QXcbScreen *screen = virtualDesktop->screenAt(pos: cursorPos);
348 QPoint deviceIndependentPos = QHighDpiScaling::mapPositionFromNative(pos: globalPos, platformScreen: screen);
349
350 if (virtualDesktop != current_virtual_desktop) {
351 setUseCompositing(virtualDesktop->compositingActive());
352 recreateShapedPixmapWindow(screen: static_cast<QPlatformScreen*>(screen)->screen(), pos: deviceIndependentPos);
353 if (connection()->mouseGrabber() == nullptr)
354 shapedPixmapWindow()->setMouseGrabEnabled(true);
355
356 current_virtual_desktop = virtualDesktop;
357 } else {
358 QBasicDrag::moveShapedPixmapWindow(deviceIndependentPosition: deviceIndependentPos);
359 }
360
361 xcb_window_t target;
362 if (!findXdndAwareTarget(globalPos, target_out: &target))
363 return;
364
365 QXcbWindow *w = nullptr;
366 if (target) {
367 w = connection()->platformWindowFromId(id: target);
368 if (w && (w->window()->type() == Qt::Desktop) /*&& !w->acceptDrops()*/)
369 w = nullptr;
370 } else {
371 w = nullptr;
372 target = current_virtual_desktop->root();
373 }
374
375 xcb_window_t proxy_target = xdndProxy(c: connection(), w: target);
376 if (!proxy_target)
377 proxy_target = target;
378 int target_version = 1;
379
380 if (proxy_target) {
381 auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(),
382 false, proxy_target,
383 atom(QXcbAtom::AtomXdndAware), XCB_GET_PROPERTY_TYPE_ANY, 0, 1);
384 if (!reply || reply->type == XCB_NONE) {
385 target = 0;
386 } else {
387 target_version = *(uint32_t *)xcb_get_property_value(R: reply.get());
388 target_version = qMin(a: xdnd_version, b: target_version ? target_version : 1);
389 }
390 }
391
392 if (target != current_target) {
393 if (current_target)
394 send_leave();
395
396 current_target = target;
397 current_proxy_target = proxy_target;
398 if (target) {
399 int flags = target_version << 24;
400 if (drag_types.size() > 3)
401 flags |= 0x0001;
402
403 xcb_client_message_event_t enter;
404 enter.response_type = XCB_CLIENT_MESSAGE;
405 enter.sequence = 0;
406 enter.window = target;
407 enter.format = 32;
408 enter.type = atom(atom: QXcbAtom::AtomXdndEnter);
409 enter.data.data32[0] = connection()->qtSelectionOwner();
410 enter.data.data32[1] = flags;
411 enter.data.data32[2] = drag_types.size() > 0 ? drag_types.at(i: 0) : 0;
412 enter.data.data32[3] = drag_types.size() > 1 ? drag_types.at(i: 1) : 0;
413 enter.data.data32[4] = drag_types.size() > 2 ? drag_types.at(i: 2) : 0;
414 // provisionally set the rectangle to 5x5 pixels...
415 source_sameanswer = QRect(globalPos.x() - 2, globalPos.y() - 2 , 5, 5);
416
417 qCDebug(lcQpaXDnd) << "sending XdndEnter to target:" << target;
418
419 if (w)
420 handleEnter(window: w, event: &enter, proxy: current_proxy_target);
421 else if (target)
422 xcb_send_event(c: xcb_connection(), propagate: false, destination: proxy_target, event_mask: XCB_EVENT_MASK_NO_EVENT, event: (const char *)&enter);
423 waiting_for_status = false;
424 }
425 }
426
427 if (waiting_for_status)
428 return;
429
430 if (target) {
431 waiting_for_status = true;
432 // The source sends a ClientMessage of type XdndPosition. This tells the target the
433 // position of the mouse and the action that the user requested.
434 xcb_client_message_event_t move;
435 move.response_type = XCB_CLIENT_MESSAGE;
436 move.sequence = 0;
437 move.window = target;
438 move.format = 32;
439 move.type = atom(atom: QXcbAtom::AtomXdndPosition);
440 move.data.data32[0] = connection()->qtSelectionOwner();
441 move.data.data32[1] = 0; // flags
442 move.data.data32[2] = (globalPos.x() << 16) + globalPos.y();
443 move.data.data32[3] = connection()->time();
444 const auto supportedActions = currentDrag()->supportedActions();
445 const auto requestedAction = defaultAction(possibleActions: supportedActions, modifiers: mods);
446 move.data.data32[4] = toXdndAction(a: requestedAction);
447
448 qCDebug(lcQpaXDnd) << "sending XdndPosition to target:" << target;
449
450 source_time = connection()->time();
451
452 if (w) {
453 handle_xdnd_position(w, event: &move, b, mods);
454 } else {
455 setActionList(requestedAction, supportedActions);
456 xcb_send_event(c: xcb_connection(), propagate: false, destination: proxy_target, event_mask: XCB_EVENT_MASK_NO_EVENT, event: (const char *)&move);
457 }
458 }
459
460 static const bool isUnity = qgetenv(varName: "XDG_CURRENT_DESKTOP").toLower() == "unity";
461 if (isUnity && xdndCollectionWindow == XCB_NONE) {
462 QString name = QXcbWindow::windowTitle(conn: connection(), window: target);
463 if (name == "XdndCollectionWindowImp"_L1)
464 xdndCollectionWindow = target;
465 }
466 if (target == xdndCollectionWindow) {
467 setCanDrop(false);
468 updateCursor(action: Qt::IgnoreAction);
469 }
470}
471
472void QXcbDrag::drop(const QPoint &globalPos, Qt::MouseButtons b, Qt::KeyboardModifiers mods)
473{
474 // XdndDrop is sent from source to target to complete the drop.
475 QBasicDrag::drop(globalPos, b, mods);
476
477 if (!current_target)
478 return;
479
480 xcb_client_message_event_t drop;
481 drop.response_type = XCB_CLIENT_MESSAGE;
482 drop.sequence = 0;
483 drop.window = current_target;
484 drop.format = 32;
485 drop.type = atom(atom: QXcbAtom::AtomXdndDrop);
486 drop.data.data32[0] = connection()->qtSelectionOwner();
487 drop.data.data32[1] = 0; // flags
488 drop.data.data32[2] = connection()->time();
489
490 drop.data.data32[3] = 0;
491 drop.data.data32[4] = currentDrag()->supportedActions();
492
493 QXcbWindow *w = connection()->platformWindowFromId(id: current_proxy_target);
494
495 if (w && w->window()->type() == Qt::Desktop) // && !w->acceptDrops()
496 w = nullptr;
497
498 Transaction t = {
499 .timestamp: connection()->time(),
500 .target: current_target,
501 .proxy_target: current_proxy_target,
502 .targetWindow: w,
503// current_embeddig_widget,
504 .drag: currentDrag(),
505 .time: QTime::currentTime()
506 };
507 transactions.append(t);
508
509 // timer is needed only for drops that came from other processes.
510 if (!t.targetWindow && !cleanup_timer.isActive())
511 cleanup_timer.start(duration: XdndDropTransactionTimeout, obj: this);
512
513 qCDebug(lcQpaXDnd) << "sending drop to target:" << current_target;
514
515 if (w) {
516 handleDrop(w, event: &drop, b, mods);
517 } else {
518 xcb_send_event(c: xcb_connection(), propagate: false, destination: current_proxy_target, event_mask: XCB_EVENT_MASK_NO_EVENT, event: (const char *)&drop);
519 }
520}
521
522Qt::DropAction QXcbDrag::toDropAction(xcb_atom_t a) const
523{
524 if (a == atom(atom: QXcbAtom::AtomXdndActionCopy) || a == 0)
525 return Qt::CopyAction;
526 if (a == atom(atom: QXcbAtom::AtomXdndActionLink))
527 return Qt::LinkAction;
528 if (a == atom(atom: QXcbAtom::AtomXdndActionMove))
529 return Qt::MoveAction;
530 return Qt::CopyAction;
531}
532
533Qt::DropActions QXcbDrag::toDropActions(const QList<xcb_atom_t> &atoms) const
534{
535 Qt::DropActions actions;
536 for (const auto actionAtom : atoms) {
537 if (actionAtom != atom(atom: QXcbAtom::AtomXdndActionAsk))
538 actions |= toDropAction(a: actionAtom);
539 }
540 return actions;
541}
542
543xcb_atom_t QXcbDrag::toXdndAction(Qt::DropAction a) const
544{
545 switch (a) {
546 case Qt::CopyAction:
547 return atom(atom: QXcbAtom::AtomXdndActionCopy);
548 case Qt::LinkAction:
549 return atom(atom: QXcbAtom::AtomXdndActionLink);
550 case Qt::MoveAction:
551 case Qt::TargetMoveAction:
552 return atom(atom: QXcbAtom::AtomXdndActionMove);
553 case Qt::IgnoreAction:
554 return XCB_NONE;
555 default:
556 return atom(atom: QXcbAtom::AtomXdndActionCopy);
557 }
558}
559
560void QXcbDrag::readActionList()
561{
562 drop_actions.clear();
563 auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(), false, xdnd_dragsource,
564 atom(QXcbAtom::AtomXdndActionList), XCB_ATOM_ATOM,
565 0, 1024);
566 if (reply && reply->type != XCB_NONE && reply->format == 32) {
567 int length = xcb_get_property_value_length(R: reply.get()) / 4;
568
569 xcb_atom_t *atoms = (xcb_atom_t *)xcb_get_property_value(R: reply.get());
570 for (int i = 0; i < length; ++i)
571 drop_actions.append(t: atoms[i]);
572 }
573}
574
575void QXcbDrag::setActionList(Qt::DropAction requestedAction, Qt::DropActions supportedActions)
576{
577#ifndef QT_NO_CLIPBOARD
578 QList<xcb_atom_t> actions;
579 if (requestedAction != Qt::IgnoreAction)
580 actions.append(t: toXdndAction(a: requestedAction));
581
582 auto checkAppend = [this, requestedAction, supportedActions, &actions](Qt::DropAction action) {
583 if (requestedAction != action && supportedActions & action)
584 actions.append(t: toXdndAction(a: action));
585 };
586
587 checkAppend(Qt::CopyAction);
588 checkAppend(Qt::MoveAction);
589 checkAppend(Qt::LinkAction);
590
591 if (current_actions != actions) {
592 xcb_change_property(c: xcb_connection(), mode: XCB_PROP_MODE_REPLACE, window: connection()->qtSelectionOwner(),
593 property: atom(atom: QXcbAtom::AtomXdndActionList),
594 type: XCB_ATOM_ATOM, format: 32, data_len: actions.size(), data: actions.constData());
595 current_actions = actions;
596 }
597#else
598 Q_UNUSED(requestedAction)
599 Q_UNUSED(supportedActions)
600#endif
601}
602
603void QXcbDrag::startListeningForActionListChanges()
604{
605 connection()->addWindowEventListener(id: xdnd_dragsource, eventListener: this);
606 const uint32_t event_mask[] = { XCB_EVENT_MASK_PROPERTY_CHANGE };
607 xcb_change_window_attributes(c: xcb_connection(), window: xdnd_dragsource, value_mask: XCB_CW_EVENT_MASK, value_list: event_mask);
608}
609
610void QXcbDrag::stopListeningForActionListChanges()
611{
612 const uint32_t event_mask[] = { XCB_EVENT_MASK_NO_EVENT };
613 xcb_change_window_attributes(c: xcb_connection(), window: xdnd_dragsource, value_mask: XCB_CW_EVENT_MASK, value_list: event_mask);
614 connection()->removeWindowEventListener(id: xdnd_dragsource);
615}
616
617int QXcbDrag::findTransactionByWindow(xcb_window_t window)
618{
619 int at = -1;
620 for (int i = 0; i < transactions.size(); ++i) {
621 const Transaction &t = transactions.at(i);
622 if (t.target == window || t.proxy_target == window) {
623 at = i;
624 break;
625 }
626 }
627 return at;
628}
629
630int QXcbDrag::findTransactionByTime(xcb_timestamp_t timestamp)
631{
632 int at = -1;
633 for (int i = 0; i < transactions.size(); ++i) {
634 const Transaction &t = transactions.at(i);
635 if (t.timestamp == timestamp) {
636 at = i;
637 break;
638 }
639 }
640 return at;
641}
642
643#if 0
644// for embedding only
645static QWidget* current_embedding_widget = nullptr;
646static xcb_client_message_event_t last_enter_event;
647
648
649static bool checkEmbedded(QWidget* w, const XEvent* xe)
650{
651 if (!w)
652 return false;
653
654 if (current_embedding_widget != 0 && current_embedding_widget != w) {
655 current_target = ((QExtraWidget*)current_embedding_widget)->extraData()->xDndProxy;
656 current_proxy_target = current_target;
657 qt_xdnd_send_leave();
658 current_target = 0;
659 current_proxy_target = 0;
660 current_embedding_widget = 0;
661 }
662
663 QWExtra* extra = ((QExtraWidget*)w)->extraData();
664 if (extra && extra->xDndProxy != 0) {
665
666 if (current_embedding_widget != w) {
667
668 last_enter_event.xany.window = extra->xDndProxy;
669 XSendEvent(X11->display, extra->xDndProxy, False, NoEventMask, &last_enter_event);
670 current_embedding_widget = w;
671 }
672
673 ((XEvent*)xe)->xany.window = extra->xDndProxy;
674 XSendEvent(X11->display, extra->xDndProxy, False, NoEventMask, (XEvent*)xe);
675 if (currentWindow != w) {
676 currentWindow = w;
677 }
678 return true;
679 }
680 current_embedding_widget = 0;
681 return false;
682}
683#endif
684
685void QXcbDrag::handleEnter(QPlatformWindow *, const xcb_client_message_event_t *event, xcb_window_t proxy)
686{
687 // The target receives XdndEnter.
688 qCDebug(lcQpaXDnd) << "target:" << event->window << "received XdndEnter";
689
690 xdnd_types.clear();
691
692 int version = (int)(event->data.data32[1] >> 24);
693 if (version > xdnd_version)
694 return;
695
696 xdnd_dragsource = event->data.data32[0];
697 startListeningForActionListChanges();
698 readActionList();
699
700 if (!proxy)
701 proxy = xdndProxy(c: connection(), w: xdnd_dragsource);
702 current_proxy_target = proxy ? proxy : xdnd_dragsource;
703
704 if (event->data.data32[1] & 1) {
705 // get the types from XdndTypeList
706 auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(), false, xdnd_dragsource,
707 atom(QXcbAtom::AtomXdndTypelist), XCB_ATOM_ATOM,
708 0, xdnd_max_type);
709 if (reply && reply->type != XCB_NONE && reply->format == 32) {
710 int length = xcb_get_property_value_length(R: reply.get()) / 4;
711 if (length > xdnd_max_type)
712 length = xdnd_max_type;
713
714 xcb_atom_t *atoms = (xcb_atom_t *)xcb_get_property_value(R: reply.get());
715 xdnd_types.reserve(asize: length);
716 for (int i = 0; i < length; ++i)
717 xdnd_types.append(t: atoms[i]);
718 }
719 } else {
720 // get the types from the message
721 for(int i = 2; i < 5; i++) {
722 if (event->data.data32[i])
723 xdnd_types.append(t: event->data.data32[i]);
724 }
725 }
726 for(int i = 0; i < xdnd_types.size(); ++i)
727 qCDebug(lcQpaXDnd) << " " << connection()->atomName(atom: xdnd_types.at(i));
728}
729
730void QXcbDrag::handle_xdnd_position(QPlatformWindow *w, const xcb_client_message_event_t *e,
731 Qt::MouseButtons b, Qt::KeyboardModifiers mods)
732{
733 // The target receives XdndPosition. The target window must determine which widget the mouse
734 // is in and ask it whether or not it will accept the drop.
735 qCDebug(lcQpaXDnd) << "target:" << e->window << "received XdndPosition";
736
737 QPoint p((e->data.data32[2] & 0xffff0000) >> 16, e->data.data32[2] & 0x0000ffff);
738 Q_ASSERT(w);
739 QRect geometry = w->geometry();
740 p -= w->isEmbedded() ? w->mapToGlobal(pos: geometry.topLeft()) : geometry.topLeft();
741
742 if (!w || !w->window() || (w->window()->type() == Qt::Desktop))
743 return;
744
745 if (Q_UNLIKELY(e->data.data32[0] != xdnd_dragsource)) {
746 qCDebug(lcQpaXDnd, "xdnd drag position from unexpected source (%x not %x)",
747 e->data.data32[0], xdnd_dragsource);
748 return;
749 }
750
751 currentPosition = p;
752 currentWindow = w->window();
753
754 // timestamp from the source
755 if (e->data.data32[3] != XCB_NONE) {
756 target_time = e->data.data32[3];
757 }
758
759 QMimeData *dropData = nullptr;
760 Qt::DropActions supported_actions = Qt::IgnoreAction;
761 if (currentDrag()) {
762 dropData = currentDrag()->mimeData();
763 supported_actions = currentDrag()->supportedActions();
764 } else {
765 dropData = m_dropData;
766 supported_actions = toDropActions(atoms: drop_actions);
767 if (e->data.data32[4] != atom(atom: QXcbAtom::AtomXdndActionAsk))
768 supported_actions |= Qt::DropActions(toDropAction(a: e->data.data32[4]));
769 }
770
771 auto buttons = currentDrag() ? b : connection()->queryMouseButtons();
772 auto modifiers = currentDrag() ? mods : connection()->keyboard()->queryKeyboardModifiers();
773
774 QPlatformDragQtResponse qt_response = QWindowSystemInterface::handleDrag(
775 window: w->window(), dropData, p, supportedActions: supported_actions, buttons, modifiers);
776
777 // ### FIXME ? - answerRect appears to be unused.
778 QRect answerRect(p + geometry.topLeft(), QSize(1,1));
779 answerRect = qt_response.answerRect().translated(p: geometry.topLeft()).intersected(other: geometry);
780
781 // The target sends a ClientMessage of type XdndStatus. This tells the source whether or not
782 // it will accept the drop, and, if so, what action will be taken. It also includes a rectangle
783 // that means "don't send another XdndPosition message until the mouse moves out of here".
784 xcb_client_message_event_t response;
785 response.response_type = XCB_CLIENT_MESSAGE;
786 response.sequence = 0;
787 response.window = xdnd_dragsource;
788 response.format = 32;
789 response.type = atom(atom: QXcbAtom::AtomXdndStatus);
790 response.data.data32[0] = xcb_window(w);
791 response.data.data32[1] = qt_response.isAccepted(); // flags
792 response.data.data32[2] = 0; // x, y
793 response.data.data32[3] = 0; // w, h
794 response.data.data32[4] = toXdndAction(a: qt_response.acceptedAction()); // action
795
796 accepted_drop_action = qt_response.acceptedAction();
797
798 if (answerRect.left() < 0)
799 answerRect.setLeft(0);
800 if (answerRect.right() > 4096)
801 answerRect.setRight(4096);
802 if (answerRect.top() < 0)
803 answerRect.setTop(0);
804 if (answerRect.bottom() > 4096)
805 answerRect.setBottom(4096);
806 if (answerRect.width() < 0)
807 answerRect.setWidth(0);
808 if (answerRect.height() < 0)
809 answerRect.setHeight(0);
810
811 // reset
812 target_time = XCB_CURRENT_TIME;
813
814 qCDebug(lcQpaXDnd) << "sending XdndStatus to source:" << xdnd_dragsource;
815
816 if (xdnd_dragsource == connection()->qtSelectionOwner())
817 handle_xdnd_status(event: &response);
818 else
819 xcb_send_event(c: xcb_connection(), propagate: false, destination: current_proxy_target,
820 event_mask: XCB_EVENT_MASK_NO_EVENT, event: (const char *)&response);
821}
822
823namespace
824{
825 class ClientMessageScanner {
826 public:
827 ClientMessageScanner(xcb_atom_t a) : atom(a) {}
828 xcb_atom_t atom;
829 bool operator() (xcb_generic_event_t *event, int type) const {
830 if (type != XCB_CLIENT_MESSAGE)
831 return false;
832 auto clientMessage = reinterpret_cast<xcb_client_message_event_t *>(event);
833 return clientMessage->type == atom;
834 }
835 };
836}
837
838void QXcbDrag::handlePosition(QPlatformWindow * w, const xcb_client_message_event_t *event)
839{
840 xcb_client_message_event_t *lastEvent = const_cast<xcb_client_message_event_t *>(event);
841 ClientMessageScanner scanner(atom(atom: QXcbAtom::AtomXdndPosition));
842 while (auto nextEvent = connection()->eventQueue()->peek(peeker&: scanner)) {
843 if (lastEvent != event)
844 free(ptr: lastEvent);
845 lastEvent = reinterpret_cast<xcb_client_message_event_t *>(nextEvent);
846 }
847
848 handle_xdnd_position(w, e: lastEvent);
849 if (lastEvent != event)
850 free(ptr: lastEvent);
851}
852
853void QXcbDrag::handle_xdnd_status(const xcb_client_message_event_t *event)
854{
855 // The source receives XdndStatus. It can use the action to change the cursor to indicate
856 // whether or not the user's requested action will be performed.
857 qCDebug(lcQpaXDnd) << "source:" << event->window << "received XdndStatus";
858 waiting_for_status = false;
859 // ignore late status messages
860 if (event->data.data32[0] && event->data.data32[0] != current_target)
861 return;
862
863 const bool dropPossible = event->data.data32[1];
864 setCanDrop(dropPossible);
865
866 if (dropPossible) {
867 accepted_drop_action = toDropAction(a: event->data.data32[4]);
868 updateCursor(action: accepted_drop_action);
869 } else {
870 updateCursor(action: Qt::IgnoreAction);
871 }
872
873 if ((event->data.data32[1] & 2) == 0) {
874 QPoint p((event->data.data32[2] & 0xffff0000) >> 16, event->data.data32[2] & 0x0000ffff);
875 QSize s((event->data.data32[3] & 0xffff0000) >> 16, event->data.data32[3] & 0x0000ffff);
876 source_sameanswer = QRect(p, s);
877 } else {
878 source_sameanswer = QRect();
879 }
880}
881
882void QXcbDrag::handleStatus(const xcb_client_message_event_t *event)
883{
884 if (event->window != connection()->qtSelectionOwner() || !drag())
885 return;
886
887 xcb_client_message_event_t *lastEvent = const_cast<xcb_client_message_event_t *>(event);
888 xcb_generic_event_t *nextEvent;
889 ClientMessageScanner scanner(atom(atom: QXcbAtom::AtomXdndStatus));
890 while ((nextEvent = connection()->eventQueue()->peek(peeker&: scanner))) {
891 if (lastEvent != event)
892 free(ptr: lastEvent);
893 lastEvent = (xcb_client_message_event_t *)nextEvent;
894 }
895
896 handle_xdnd_status(event: lastEvent);
897 if (lastEvent != event)
898 free(ptr: lastEvent);
899}
900
901void QXcbDrag::handleLeave(QPlatformWindow *w, const xcb_client_message_event_t *event)
902{
903 // If the target receives XdndLeave, it frees any cached data and forgets the whole incident.
904 qCDebug(lcQpaXDnd) << "target:" << event->window << "received XdndLeave";
905
906 if (!currentWindow || w != currentWindow.data()->handle()) {
907 stopListeningForActionListChanges();
908 return; // sanity
909 }
910
911 // ###
912// if (checkEmbedded(current_embedding_widget, event)) {
913// current_embedding_widget = 0;
914// currentWindow.clear();
915// return;
916// }
917
918 if (event->data.data32[0] != xdnd_dragsource) {
919 // This often happens - leave other-process window quickly
920 qCDebug(lcQpaXDnd, "xdnd drag leave from unexpected source (%x not %x",
921 event->data.data32[0], xdnd_dragsource);
922 }
923
924 stopListeningForActionListChanges();
925
926 QWindowSystemInterface::handleDrag(window: w->window(), dropData: nullptr, p: QPoint(), supportedActions: Qt::IgnoreAction, buttons: { }, modifiers: { });
927}
928
929void QXcbDrag::send_leave()
930{
931 // XdndLeave is sent from the source to the target to cancel the drop.
932 if (!current_target)
933 return;
934
935 xcb_client_message_event_t leave;
936 leave.response_type = XCB_CLIENT_MESSAGE;
937 leave.sequence = 0;
938 leave.window = current_target;
939 leave.format = 32;
940 leave.type = atom(atom: QXcbAtom::AtomXdndLeave);
941 leave.data.data32[0] = connection()->qtSelectionOwner();
942 leave.data.data32[1] = 0; // flags
943 leave.data.data32[2] = 0; // x, y
944 leave.data.data32[3] = 0; // w, h
945 leave.data.data32[4] = 0; // just null
946
947 QXcbWindow *w = connection()->platformWindowFromId(id: current_proxy_target);
948
949 if (w && (w->window()->type() == Qt::Desktop) /*&& !w->acceptDrops()*/)
950 w = nullptr;
951
952 qCDebug(lcQpaXDnd) << "sending XdndLeave to target:" << current_target;
953
954 if (w)
955 handleLeave(w, event: (const xcb_client_message_event_t *)&leave);
956 else
957 xcb_send_event(c: xcb_connection(), propagate: false,destination: current_proxy_target,
958 event_mask: XCB_EVENT_MASK_NO_EVENT, event: (const char *)&leave);
959}
960
961void QXcbDrag::handleDrop(QPlatformWindow *, const xcb_client_message_event_t *event,
962 Qt::MouseButtons b, Qt::KeyboardModifiers mods)
963{
964 // Target receives XdndDrop. Once it is finished processing the drop, it sends XdndFinished.
965 qCDebug(lcQpaXDnd) << "target:" << event->window << "received XdndDrop";
966
967 if (!currentWindow) {
968 stopListeningForActionListChanges();
969 xdnd_dragsource = 0;
970 return; // sanity
971 }
972
973 const uint32_t *l = event->data.data32;
974
975 if (l[0] != xdnd_dragsource) {
976 qCDebug(lcQpaXDnd, "xdnd drop from unexpected source (%x not %x", l[0], xdnd_dragsource);
977 return;
978 }
979
980 // update the "user time" from the timestamp in the event.
981 if (l[2] != 0)
982 target_time = l[2];
983
984 Qt::DropActions supported_drop_actions;
985 QMimeData *dropData = nullptr;
986 // this could be a same-application drop, just proxied due to
987 // some XEMBEDding, so try to find the real QMimeData used
988 // based on the timestamp for this drop.
989 int at = findTransactionByTime(timestamp: target_time);
990 if (at != -1) {
991 qCDebug(lcQpaXDnd) << "found one transaction via findTransactionByTime()";
992 dropData = transactions.at(i: at).drag->mimeData();
993 // Can't use the source QMimeData if we need the image conversion code from xdndObtainData
994 if (dropData && dropData->hasImage())
995 dropData = 0;
996 }
997 // if we can't find it, then use the data in the drag manager
998 if (currentDrag()) {
999 if (!dropData)
1000 dropData = currentDrag()->mimeData();
1001 supported_drop_actions = Qt::DropActions(l[4]);
1002 } else {
1003 if (!dropData)
1004 dropData = m_dropData;
1005 supported_drop_actions = accepted_drop_action | toDropActions(atoms: drop_actions);
1006 }
1007
1008 if (!dropData)
1009 return;
1010
1011 auto buttons = currentDrag() ? b : connection()->queryMouseButtons();
1012 auto modifiers = currentDrag() ? mods : connection()->keyboard()->queryKeyboardModifiers();
1013
1014 QPlatformDropQtResponse response = QWindowSystemInterface::handleDrop(
1015 window: currentWindow.data(), dropData, p: currentPosition, supportedActions: supported_drop_actions,
1016 buttons, modifiers);
1017
1018 Qt::DropAction acceptedAaction = response.acceptedAction();
1019 if (!response.isAccepted()) {
1020 // Ignore a failed drag
1021 acceptedAaction = Qt::IgnoreAction;
1022 }
1023 setExecutedDropAction(acceptedAaction);
1024
1025 xcb_client_message_event_t finished = {};
1026 finished.response_type = XCB_CLIENT_MESSAGE;
1027 finished.sequence = 0;
1028 finished.window = xdnd_dragsource;
1029 finished.format = 32;
1030 finished.type = atom(atom: QXcbAtom::AtomXdndFinished);
1031 finished.data.data32[0] = currentWindow ? xcb_window(w: currentWindow.data()) : XCB_NONE;
1032 finished.data.data32[1] = response.isAccepted(); // flags
1033 finished.data.data32[2] = toXdndAction(a: acceptedAaction);
1034
1035 qCDebug(lcQpaXDnd) << "sending XdndFinished to source:" << xdnd_dragsource;
1036
1037 xcb_send_event(c: xcb_connection(), propagate: false, destination: current_proxy_target,
1038 event_mask: XCB_EVENT_MASK_NO_EVENT, event: (char *)&finished);
1039
1040 stopListeningForActionListChanges();
1041
1042 dropped = true;
1043}
1044
1045void QXcbDrag::handleFinished(const xcb_client_message_event_t *event)
1046{
1047 // Source receives XdndFinished when target is done processing the drop data.
1048 qCDebug(lcQpaXDnd) << "source:" << event->window << "received XdndFinished";
1049
1050 if (event->window != connection()->qtSelectionOwner())
1051 return;
1052
1053 if (xcb_window_t w = event->data.data32[0]) {
1054 int at = findTransactionByWindow(window: w);
1055 if (at != -1) {
1056
1057 Transaction t = transactions.takeAt(i: at);
1058 if (t.drag)
1059 t.drag->deleteLater();
1060// QDragManager *manager = QDragManager::self();
1061
1062// Window target = current_target;
1063// Window proxy_target = current_proxy_target;
1064// QWidget *embedding_widget = current_embedding_widget;
1065// QDrag *currentObject = manager->object;
1066
1067// current_target = t.target;
1068// current_proxy_target = t.proxy_target;
1069// current_embedding_widget = t.embedding_widget;
1070// manager->object = t.object;
1071
1072// if (!passive)
1073// (void) checkEmbedded(currentWindow, xe);
1074
1075// current_embedding_widget = 0;
1076// current_target = 0;
1077// current_proxy_target = 0;
1078
1079// current_target = target;
1080// current_proxy_target = proxy_target;
1081// current_embedding_widget = embedding_widget;
1082// manager->object = currentObject;
1083 } else {
1084 qWarning(msg: "QXcbDrag::handleFinished - drop data has expired");
1085 }
1086 }
1087 waiting_for_status = false;
1088}
1089
1090void QXcbDrag::timerEvent(QTimerEvent* e)
1091{
1092 if (e->id() == cleanup_timer.id()) {
1093 bool stopTimer = true;
1094 for (int i = 0; i < transactions.size(); ++i) {
1095 const Transaction &t = transactions.at(i);
1096 if (t.targetWindow) {
1097 // dnd within the same process, don't delete, these are taken care of
1098 // in handleFinished()
1099 continue;
1100 }
1101 QTime currentTime = QTime::currentTime();
1102 std::chrono::milliseconds delta{t.time.msecsTo(t: currentTime)};
1103 if (delta > XdndDropTransactionTimeout) {
1104 /* delete transactions which are older than XdndDropTransactionTimeout. It could mean
1105 one of these:
1106 - client has crashed and as a result we have never received XdndFinished
1107 - showing dialog box on drop event where user's response takes more time than XdndDropTransactionTimeout (QTBUG-14493)
1108 - dnd takes unusually long time to process data
1109 */
1110 if (t.drag)
1111 t.drag->deleteLater();
1112 transactions.removeAt(i: i--);
1113 } else {
1114 stopTimer = false;
1115 }
1116
1117 }
1118 if (stopTimer)
1119 cleanup_timer.stop();
1120 }
1121}
1122
1123void QXcbDrag::cancel()
1124{
1125 qCDebug(lcQpaXDnd) << "dnd was canceled";
1126
1127 QBasicDrag::cancel();
1128 if (current_target)
1129 send_leave();
1130
1131 // remove canceled object
1132 if (currentDrag())
1133 currentDrag()->deleteLater();
1134
1135 canceled = true;
1136}
1137
1138static xcb_window_t findXdndAwareParent(QXcbConnection *c, xcb_window_t window)
1139{
1140 xcb_window_t target = 0;
1141 forever {
1142 // check if window has XdndAware
1143 auto gpReply = Q_XCB_REPLY(xcb_get_property, c->xcb_connection(), false, window,
1144 c->atom(QXcbAtom::AtomXdndAware), XCB_GET_PROPERTY_TYPE_ANY, 0, 0);
1145 bool aware = gpReply && gpReply->type != XCB_NONE;
1146 if (aware) {
1147 target = window;
1148 break;
1149 }
1150
1151 // try window's parent
1152 auto qtReply = Q_XCB_REPLY_UNCHECKED(xcb_query_tree, c->xcb_connection(), window);
1153 if (!qtReply)
1154 break;
1155 xcb_window_t root = qtReply->root;
1156 xcb_window_t parent = qtReply->parent;
1157 if (window == root)
1158 break;
1159 window = parent;
1160 }
1161 return target;
1162}
1163
1164void QXcbDrag::handleSelectionRequest(const xcb_selection_request_event_t *event)
1165{
1166 qCDebug(lcQpaXDnd) << "handle selection request from target:" << event->requestor;
1167 q_padded_xcb_event<xcb_selection_notify_event_t> notify = {};
1168 notify.response_type = XCB_SELECTION_NOTIFY;
1169 notify.requestor = event->requestor;
1170 notify.selection = event->selection;
1171 notify.target = XCB_NONE;
1172 notify.property = XCB_NONE;
1173 notify.time = event->time;
1174
1175 // which transaction do we use? (note: -2 means use current currentDrag())
1176 int at = -1;
1177
1178 // figure out which data the requestor is really interested in
1179 if (currentDrag() && event->time == source_time) {
1180 // requestor wants the current drag data
1181 at = -2;
1182 } else {
1183 // if someone has requested data in response to XdndDrop, find the corresponding transaction. the
1184 // spec says to call xcb_convert_selection() using the timestamp from the XdndDrop
1185 at = findTransactionByTime(timestamp: event->time);
1186 if (at == -1) {
1187 // no dice, perhaps the client was nice enough to use the same window id in
1188 // xcb_convert_selection() that we sent the XdndDrop event to.
1189 at = findTransactionByWindow(window: event->requestor);
1190 }
1191
1192 if (at == -1) {
1193 xcb_window_t target = findXdndAwareParent(c: connection(), window: event->requestor);
1194 if (target) {
1195 if (event->time == XCB_CURRENT_TIME && current_target == target)
1196 at = -2;
1197 else
1198 at = findTransactionByWindow(window: target);
1199 }
1200 }
1201 }
1202
1203 QDrag *transactionDrag = nullptr;
1204 if (at >= 0) {
1205 transactionDrag = transactions.at(i: at).drag;
1206 } else if (at == -2) {
1207 transactionDrag = currentDrag();
1208 }
1209
1210 if (transactionDrag) {
1211 xcb_atom_t atomFormat = event->target;
1212 int dataFormat = 0;
1213 QByteArray data;
1214 if (QXcbMime::mimeDataForAtom(connection: connection(), a: event->target, mimeData: transactionDrag->mimeData(),
1215 data: &data, atomFormat: &atomFormat, dataFormat: &dataFormat)) {
1216 int dataSize = data.size() / (dataFormat / 8);
1217 xcb_change_property(c: xcb_connection(), mode: XCB_PROP_MODE_REPLACE, window: event->requestor, property: event->property,
1218 type: atomFormat, format: dataFormat, data_len: dataSize, data: (const void *)data.constData());
1219 notify.property = event->property;
1220 notify.target = atomFormat;
1221 }
1222 }
1223
1224 xcb_window_t proxy_target = xdndProxy(c: connection(), w: event->requestor);
1225 if (!proxy_target)
1226 proxy_target = event->requestor;
1227
1228 xcb_send_event(c: xcb_connection(), propagate: false, destination: proxy_target, event_mask: XCB_EVENT_MASK_NO_EVENT, event: (const char *)&notify);
1229}
1230
1231
1232bool QXcbDrag::dndEnable(QXcbWindow *w, bool on)
1233{
1234 qCDebug(lcQpaXDnd) << "dndEnable" << static_cast<QPlatformWindow *>(w) << on;
1235 // Windows announce that they support the XDND protocol by creating a window property XdndAware.
1236 if (on) {
1237 QXcbWindow *window = nullptr;
1238 if (w->window()->type() == Qt::Desktop) {
1239 if (desktop_proxy) // *WE* already have one.
1240 return false;
1241
1242 QXcbConnectionGrabber grabber(connection());
1243
1244 // As per Xdnd4, use XdndProxy
1245 xcb_window_t proxy_id = xdndProxy(c: connection(), w: w->xcb_window());
1246
1247 if (!proxy_id) {
1248 desktop_proxy = new QWindow;
1249 window = static_cast<QXcbWindow *>(desktop_proxy->handle());
1250 proxy_id = window->xcb_window();
1251 xcb_atom_t xdnd_proxy = atom(atom: QXcbAtom::AtomXdndProxy);
1252 xcb_change_property(c: xcb_connection(), mode: XCB_PROP_MODE_REPLACE, window: w->xcb_window(), property: xdnd_proxy,
1253 type: XCB_ATOM_WINDOW, format: 32, data_len: 1, data: &proxy_id);
1254 xcb_change_property(c: xcb_connection(), mode: XCB_PROP_MODE_REPLACE, window: proxy_id, property: xdnd_proxy,
1255 type: XCB_ATOM_WINDOW, format: 32, data_len: 1, data: &proxy_id);
1256 }
1257
1258 } else {
1259 window = w;
1260 }
1261 if (window) {
1262 qCDebug(lcQpaXDnd) << "setting XdndAware for" << window->xcb_window();
1263 xcb_atom_t atm = xdnd_version;
1264 xcb_change_property(c: xcb_connection(), mode: XCB_PROP_MODE_REPLACE, window: window->xcb_window(),
1265 property: atom(atom: QXcbAtom::AtomXdndAware), type: XCB_ATOM_ATOM, format: 32, data_len: 1, data: &atm);
1266 return true;
1267 } else {
1268 return false;
1269 }
1270 } else {
1271 if (w->window()->type() == Qt::Desktop) {
1272 xcb_delete_property(c: xcb_connection(), window: w->xcb_window(), property: atom(atom: QXcbAtom::AtomXdndProxy));
1273 delete desktop_proxy;
1274 desktop_proxy = nullptr;
1275 } else {
1276 qCDebug(lcQpaXDnd) << "not deleting XDndAware";
1277 }
1278 return true;
1279 }
1280}
1281
1282bool QXcbDrag::ownsDragObject() const
1283{
1284 return true;
1285}
1286
1287QXcbDropData::QXcbDropData(QXcbDrag *d)
1288 : QXcbMime(),
1289 drag(d)
1290{
1291}
1292
1293QXcbDropData::~QXcbDropData()
1294{
1295}
1296
1297QVariant QXcbDropData::retrieveData_sys(const QString &mimetype, QMetaType requestedType) const
1298{
1299 QByteArray mime = mimetype.toLatin1();
1300 QVariant data = xdndObtainData(format: mime, requestedType);
1301 return data;
1302}
1303
1304QVariant QXcbDropData::xdndObtainData(const QByteArray &format, QMetaType requestedType) const
1305{
1306 QXcbConnection *c = drag->connection();
1307 QXcbWindow *xcb_window = c->platformWindowFromId(id: drag->xdnd_dragsource);
1308 if (xcb_window && drag->currentDrag() && xcb_window->window()->type() != Qt::Desktop) {
1309 QMimeData *data = drag->currentDrag()->mimeData();
1310 if (data->hasFormat(mimetype: QLatin1StringView(format)))
1311 return data->data(mimetype: QLatin1StringView(format));
1312 return QVariant();
1313 }
1314
1315 QList<xcb_atom_t> atoms = drag->xdnd_types;
1316 bool hasUtf8 = false;
1317 xcb_atom_t a = mimeAtomForFormat(connection: c, format: QLatin1StringView(format), requestedType, atoms, hasUtf8: &hasUtf8);
1318 if (a == XCB_NONE)
1319 return QVariant();
1320
1321#ifndef QT_NO_CLIPBOARD
1322 if (c->selectionOwner(atom: c->atom(qatom: QXcbAtom::AtomXdndSelection)) == XCB_NONE)
1323 return QVariant(); // should never happen?
1324
1325 xcb_atom_t xdnd_selection = c->atom(qatom: QXcbAtom::AtomXdndSelection);
1326 const std::optional<QByteArray> result = c->clipboard()->getSelection(selection: xdnd_selection, target: a, property: xdnd_selection, t: drag->targetTime());
1327 if (!result.has_value())
1328 return QVariant();
1329 return mimeConvertToFormat(connection: c, a, data: result.value(), format: QLatin1StringView(format), requestedType, hasUtf8);
1330#else
1331 return QVariant();
1332#endif
1333}
1334
1335bool QXcbDropData::hasFormat_sys(const QString &format) const
1336{
1337 return formats().contains(str: format);
1338}
1339
1340QStringList QXcbDropData::formats_sys() const
1341{
1342 QStringList formats;
1343 for (int i = 0; i < drag->xdnd_types.size(); ++i) {
1344 QString f = mimeAtomToString(connection: drag->connection(), a: drag->xdnd_types.at(i));
1345 if (!formats.contains(str: f))
1346 formats.append(t: f);
1347 }
1348 return formats;
1349}
1350
1351QT_END_NAMESPACE
1352

source code of qtbase/src/plugins/platforms/xcb/qxcbdrag.cpp