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