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