Add PLUGIN_CLASS_NAME to qtbase plugins
[profile/ivi/qtbase.git] / src / plugins / platforms / xcb / qxcbdrag.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/legal
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 Digia.  For licensing terms and
14 ** conditions see http://qt.digia.com/licensing.  For further information
15 ** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file.  Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 **
25 ** In addition, as a special exception, Digia gives you certain additional
26 ** rights.  These rights are described in the Digia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 **
29 ** GNU General Public License Usage
30 ** Alternatively, this file may be used under the terms of the GNU
31 ** General Public License version 3.0 as published by the Free Software
32 ** Foundation and appearing in the file LICENSE.GPL included in the
33 ** packaging of this file.  Please review the following information to
34 ** ensure the GNU General Public License version 3.0 requirements will be
35 ** met: http://www.gnu.org/copyleft/gpl.html.
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "qxcbdrag.h"
43 #include <xcb/xcb.h>
44 #include "qxcbconnection.h"
45 #include "qxcbclipboard.h"
46 #include "qxcbmime.h"
47 #include "qxcbwindow.h"
48 #include "qxcbscreen.h"
49 #include "qwindow.h"
50 #include <private/qdnd_p.h>
51 #include <qdebug.h>
52 #include <qevent.h>
53 #include <qguiapplication.h>
54 #include <qrect.h>
55 #include <qpainter.h>
56 #include <qtimer.h>
57
58 #include <qpa/qwindowsysteminterface.h>
59
60 #include <QtPlatformSupport/private/qshapedpixmapdndwindow_p.h>
61 #include <QtPlatformSupport/private/qsimpledrag_p.h>
62
63 QT_BEGIN_NAMESPACE
64
65 #ifndef QT_NO_DRAGANDDROP
66
67 //#define DND_DEBUG
68 #ifdef DND_DEBUG
69 #define DEBUG qDebug
70 #else
71 #define DEBUG if(0) qDebug
72 #endif
73
74 #ifdef DND_DEBUG
75 #define DNDDEBUG qDebug()
76 #else
77 #define DNDDEBUG if(0) qDebug()
78 #endif
79
80 const int xdnd_version = 5;
81
82 static inline xcb_window_t xcb_window(QWindow *w)
83 {
84     return static_cast<QXcbWindow *>(w->handle())->xcb_window();
85 }
86
87
88 static xcb_window_t xdndProxy(QXcbConnection *c, xcb_window_t w)
89 {
90     xcb_window_t proxy = XCB_NONE;
91
92     xcb_get_property_cookie_t cookie = Q_XCB_CALL2(xcb_get_property(c->xcb_connection(), false, w, c->atom(QXcbAtom::XdndProxy),
93                                                         XCB_ATOM_WINDOW, 0, 1), c);
94     xcb_get_property_reply_t *reply = xcb_get_property_reply(c->xcb_connection(), cookie, 0);
95
96     if (reply && reply->type == XCB_ATOM_WINDOW)
97         proxy = *((xcb_window_t *)xcb_get_property_value(reply));
98     free(reply);
99
100     if (proxy == XCB_NONE)
101         return proxy;
102
103     // exists and is real?
104     cookie = Q_XCB_CALL2(xcb_get_property(c->xcb_connection(), false, proxy, c->atom(QXcbAtom::XdndProxy),
105                                                         XCB_ATOM_WINDOW, 0, 1), c);
106     reply = xcb_get_property_reply(c->xcb_connection(), cookie, 0);
107
108     if (reply && reply->type == XCB_ATOM_WINDOW) {
109         xcb_window_t p = *((xcb_window_t *)xcb_get_property_value(reply));
110         if (proxy != p)
111             proxy = 0;
112     } else {
113         proxy = 0;
114     }
115
116     free(reply);
117
118     return proxy;
119 }
120
121 class QXcbDropData : public QXcbMime
122 {
123 public:
124     QXcbDropData(QXcbDrag *d);
125     ~QXcbDropData();
126
127 protected:
128     bool hasFormat_sys(const QString &mimeType) const;
129     QStringList formats_sys() const;
130     QVariant retrieveData_sys(const QString &mimeType, QVariant::Type type) const;
131
132     QVariant xdndObtainData(const QByteArray &format, QVariant::Type requestedType) const;
133
134     QXcbDrag *drag;
135 };
136
137
138 QXcbDrag::QXcbDrag(QXcbConnection *c) : QXcbObject(c)
139 {
140     dropData = new QXcbDropData(this);
141
142     init();
143     heartbeat = -1;
144     cleanup_timer = -1;
145 }
146
147 QXcbDrag::~QXcbDrag()
148 {
149     delete dropData;
150 }
151
152 void QXcbDrag::init()
153 {
154     currentWindow.clear();
155
156     accepted_drop_action = Qt::IgnoreAction;
157
158     xdnd_dragsource = XCB_NONE;
159
160     waiting_for_status = false;
161     current_target = XCB_NONE;
162     current_proxy_target = XCB_NONE;
163
164     source_time = XCB_CURRENT_TIME;
165     target_time = XCB_CURRENT_TIME;
166
167     current_screen = 0;
168     drag_types.clear();
169 }
170
171 QMimeData *QXcbDrag::platformDropData()
172 {
173     return dropData;
174 }
175
176 void QXcbDrag::startDrag()
177 {
178     // #fixme enableEventFilter();
179
180     init();
181
182     heartbeat = startTimer(200);
183
184
185     xcb_set_selection_owner(xcb_connection(), connection()->clipboard()->owner(),
186                             atom(QXcbAtom::XdndSelection), connection()->time());
187
188     QStringList fmts = QXcbMime::formatsHelper(drag()->mimeData());
189     for (int i = 0; i < fmts.size(); ++i) {
190         QList<xcb_atom_t> atoms = QXcbMime::mimeAtomsForFormat(connection(), fmts.at(i));
191         for (int j = 0; j < atoms.size(); ++j) {
192             if (!drag_types.contains(atoms.at(j)))
193                 drag_types.append(atoms.at(j));
194         }
195     }
196     if (drag_types.size() > 3)
197         xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, connection()->clipboard()->owner(),
198                             atom(QXcbAtom::XdndTypelist),
199                             XCB_ATOM_ATOM, 32, drag_types.size(), (const void *)drag_types.constData());
200     QBasicDrag::startDrag();
201 }
202
203 void QXcbDrag::endDrag()
204 {
205     if (heartbeat != -1) {
206         killTimer(heartbeat);
207         heartbeat = -1;
208     }
209     QBasicDrag::endDrag();
210 }
211
212 static xcb_translate_coordinates_reply_t *
213 translateCoordinates(QXcbConnection *c, xcb_window_t from, xcb_window_t to, int x, int y)
214 {
215     xcb_translate_coordinates_cookie_t cookie =
216             xcb_translate_coordinates(c->xcb_connection(), from, to, x, y);
217     return xcb_translate_coordinates_reply(c->xcb_connection(), cookie, 0);
218 }
219
220 static
221 bool windowInteractsWithPosition(xcb_connection_t *connection, const QPoint & pos, xcb_window_t w, xcb_shape_sk_t shapeType)
222 {
223     bool interacts = false;
224     xcb_shape_get_rectangles_reply_t *reply = xcb_shape_get_rectangles_reply(connection, xcb_shape_get_rectangles(connection, w, shapeType), NULL);
225     if (reply) {
226         xcb_rectangle_t *rectangles = xcb_shape_get_rectangles_rectangles(reply);
227         if (rectangles) {
228             const int nRectangles = xcb_shape_get_rectangles_rectangles_length(reply);
229             for (int i = 0; !interacts && i < nRectangles; ++i) {
230                 interacts = QRect(rectangles[i].x, rectangles[i].y, rectangles[i].width, rectangles[i].height).contains(pos);
231             }
232         }
233         free(reply);
234     }
235
236     return interacts;
237 }
238
239 xcb_window_t QXcbDrag::findRealWindow(const QPoint & pos, xcb_window_t w, int md, bool ignoreNonXdndAwareWindows)
240 {
241     if (w == shapedPixmapWindow()->handle()->winId())
242         return 0;
243
244     if (md) {
245         xcb_get_window_attributes_cookie_t cookie = xcb_get_window_attributes(xcb_connection(), w);
246         xcb_get_window_attributes_reply_t *reply = xcb_get_window_attributes_reply(xcb_connection(), cookie, 0);
247         if (!reply)
248             return 0;
249
250         if (reply->map_state != XCB_MAP_STATE_VIEWABLE)
251             return 0;
252
253         xcb_get_geometry_cookie_t gcookie = xcb_get_geometry(xcb_connection(), w);
254         xcb_get_geometry_reply_t *greply = xcb_get_geometry_reply(xcb_connection(), gcookie, 0);
255         if (reply && QRect(greply->x, greply->y, greply->width, greply->height).contains(pos)) {
256             bool windowContainsMouse = !ignoreNonXdndAwareWindows;
257             {
258                 xcb_get_property_cookie_t cookie =
259                         Q_XCB_CALL(xcb_get_property(xcb_connection(), false, w, connection()->atom(QXcbAtom::XdndAware),
260                                                     XCB_GET_PROPERTY_TYPE_ANY, 0, 0));
261                 xcb_get_property_reply_t *reply = xcb_get_property_reply(xcb_connection(), cookie, 0);
262
263                 bool isAware = reply && reply->type != XCB_NONE;
264                 free(reply);
265                 if (isAware) {
266                     const QPoint relPos = pos - QPoint(greply->x, greply->y);
267                     // When ShapeInput and ShapeBounding are not set they return a single rectangle with the geometry of the window, this is why we
268                     // need to check both here so that in the case one is set and the other is not we still get the correct result.
269                     if (connection()->hasInputShape())
270                         windowContainsMouse = windowInteractsWithPosition(xcb_connection(), relPos, w, XCB_SHAPE_SK_INPUT);
271                     if (windowContainsMouse && connection()->hasXShape())
272                         windowContainsMouse = windowInteractsWithPosition(xcb_connection(), relPos, w, XCB_SHAPE_SK_BOUNDING);
273                     if (!connection()->hasInputShape() && !connection()->hasXShape())
274                         windowContainsMouse = true;
275                     if (windowContainsMouse)
276                         return w;
277                 }
278             }
279
280             xcb_query_tree_cookie_t cookie = xcb_query_tree (xcb_connection(), w);
281             xcb_query_tree_reply_t *reply = xcb_query_tree_reply(xcb_connection(), cookie, 0);
282
283             if (!reply)
284                 return 0;
285             int nc = xcb_query_tree_children_length(reply);
286             xcb_window_t *c = xcb_query_tree_children(reply);
287
288             xcb_window_t r = 0;
289             for (uint i = nc; !r && i--;)
290                 r = findRealWindow(pos - QPoint(greply->x, greply->y), c[i], md-1, ignoreNonXdndAwareWindows);
291
292             free(reply);
293             if (r)
294                 return r;
295
296             // We didn't find a client window!  Just use the
297             // innermost window.
298
299             // No children!
300             if (!windowContainsMouse)
301                 return 0;
302             else
303                 return w;
304         }
305     }
306     return 0;
307 }
308
309 void QXcbDrag::move(const QMouseEvent *me)
310 {
311     QBasicDrag::move(me);
312     QPoint globalPos = me->globalPos();
313
314     if (source_sameanswer.contains(globalPos) && source_sameanswer.isValid())
315         return;
316
317     const QList<QXcbScreen *> &screens = connection()->screens();
318     QXcbScreen *screen = screens.at(connection()->primaryScreen());
319     for (int i = 0; i < screens.size(); ++i) {
320         if (screens.at(i)->geometry().contains(globalPos)) {
321             screen = screens.at(i);
322             break;
323         }
324     }
325     if (screen != current_screen) {
326         // ### need to recreate the shaped pixmap window?
327 //    int screen = QCursor::x11Screen();
328 //    if ((qt_xdnd_current_screen == -1 && screen != X11->defaultScreen) || (screen != qt_xdnd_current_screen)) {
329 //        // recreate the pixmap on the new screen...
330 //        delete xdnd_data.deco;
331 //        QWidget* parent = object->source()->window()->x11Info().screen() == screen
332 //            ? object->source()->window() : QApplication::desktop()->screen(screen);
333 //        xdnd_data.deco = new QShapedPixmapWidget(parent);
334 //        if (!QWidget::mouseGrabber()) {
335 //            updatePixmap();
336 //            xdnd_data.deco->grabMouse();
337 //        }
338 //    }
339 //    xdnd_data.deco->move(QCursor::pos() - xdnd_data.deco->pm_hot);
340         current_screen = screen;
341     }
342
343
344 //    qt_xdnd_current_screen = screen;
345     xcb_window_t rootwin = current_screen->root();
346     xcb_translate_coordinates_reply_t *translate =
347             ::translateCoordinates(connection(), rootwin, rootwin, globalPos.x(), globalPos.y());
348     if (!translate)
349         return;
350
351     xcb_window_t target = translate->child;
352     int lx = translate->dst_x;
353     int ly = translate->dst_y;
354     free (translate);
355
356     if (target && target != rootwin) {
357         xcb_window_t src = rootwin;
358         while (target != 0) {
359             DNDDEBUG << "checking target for XdndAware" << target << lx << ly;
360
361             // translate coordinates
362             translate = ::translateCoordinates(connection(), src, target, lx, ly);
363             if (!translate) {
364                 target = 0;
365                 break;
366             }
367             lx = translate->dst_x;
368             ly = translate->dst_y;
369             src = target;
370             xcb_window_t child = translate->child;
371             free(translate);
372
373             // check if it has XdndAware
374             xcb_get_property_cookie_t cookie = Q_XCB_CALL(xcb_get_property(xcb_connection(), false, target,
375                                                           atom(QXcbAtom::XdndAware), XCB_GET_PROPERTY_TYPE_ANY, 0, 0));
376             xcb_get_property_reply_t *reply = xcb_get_property_reply(xcb_connection(), cookie, 0);
377             bool aware = reply && reply->type != XCB_NONE;
378             free(reply);
379             if (aware) {
380                 DNDDEBUG << "Found XdndAware on " << target;
381                 break;
382             }
383
384             target = child;
385         }
386
387         if (!target || target == shapedPixmapWindow()->handle()->winId()) {
388             DNDDEBUG << "need to find real window";
389             target = findRealWindow(globalPos, rootwin, 6, true);
390             if (target == 0)
391                 target = findRealWindow(globalPos, rootwin, 6, false);
392             DNDDEBUG << "real window found" << target;
393         }
394     }
395
396     QXcbWindow *w = 0;
397     if (target) {
398         w = connection()->platformWindowFromId(target);
399         if (w && (w->window()->type() == Qt::Desktop) /*&& !w->acceptDrops()*/)
400             w = 0;
401     } else {
402         w = 0;
403         target = rootwin;
404     }
405
406     xcb_window_t proxy_target = xdndProxy(connection(), target);
407     if (!proxy_target)
408         proxy_target = target;
409     int target_version = 1;
410
411     if (proxy_target) {
412         xcb_get_property_cookie_t cookie = xcb_get_property(xcb_connection(), false, target,
413                                                             atom(QXcbAtom::XdndAware), XCB_GET_PROPERTY_TYPE_ANY, 0, 1);
414         xcb_get_property_reply_t *reply = xcb_get_property_reply(xcb_connection(), cookie, 0);
415         if (!reply || reply->type == XCB_NONE)
416             target = 0;
417
418         target_version = *(uint32_t *)xcb_get_property_value(reply);
419         target_version = qMin(xdnd_version, target_version ? target_version : 1);
420
421         free(reply);
422     }
423
424     if (target != current_target) {
425         if (current_target)
426             send_leave();
427
428         current_target = target;
429         current_proxy_target = proxy_target;
430         if (target) {
431             int flags = target_version << 24;
432             if (drag_types.size() > 3)
433                 flags |= 0x0001;
434
435             xcb_client_message_event_t enter;
436             enter.response_type = XCB_CLIENT_MESSAGE;
437             enter.window = target;
438             enter.format = 32;
439             enter.type = atom(QXcbAtom::XdndEnter);
440             enter.data.data32[0] = connection()->clipboard()->owner();
441             enter.data.data32[1] = flags;
442             enter.data.data32[2] = drag_types.size()>0 ? drag_types.at(0) : 0;
443             enter.data.data32[3] = drag_types.size()>1 ? drag_types.at(1) : 0;
444             enter.data.data32[4] = drag_types.size()>2 ? drag_types.at(2) : 0;
445             // provisionally set the rectangle to 5x5 pixels...
446             source_sameanswer = QRect(globalPos.x() - 2, globalPos.y() -2 , 5, 5);
447
448             DEBUG() << "sending Xdnd enter source=" << enter.data.data32[0];
449             if (w)
450                 handleEnter(w->window(), &enter);
451             else if (target)
452                 xcb_send_event(xcb_connection(), false, proxy_target, XCB_EVENT_MASK_NO_EVENT, (const char *)&enter);
453             waiting_for_status = false;
454         }
455     }
456
457     if (waiting_for_status)
458         return;
459
460     if (target) {
461         waiting_for_status = true;
462
463         xcb_client_message_event_t move;
464         move.response_type = XCB_CLIENT_MESSAGE;
465         move.window = target;
466         move.format = 32;
467         move.type = atom(QXcbAtom::XdndPosition);
468         move.data.data32[0] = connection()->clipboard()->owner();
469         move.data.data32[1] = 0; // flags
470         move.data.data32[2] = (globalPos.x() << 16) + globalPos.y();
471         move.data.data32[3] = connection()->time();
472         move.data.data32[4] = toXdndAction(defaultAction(currentDrag()->supportedActions(), QGuiApplication::keyboardModifiers()));
473         DEBUG() << "sending Xdnd position source=" << move.data.data32[0] << "target=" << move.window;
474
475         source_time = connection()->time();
476
477         if (w)
478             handle_xdnd_position(w->window(), &move);
479         else
480             xcb_send_event(xcb_connection(), false, proxy_target, XCB_EVENT_MASK_NO_EVENT, (const char *)&move);
481     }
482 }
483
484 void QXcbDrag::drop(const QMouseEvent *event)
485 {
486     QBasicDrag::drop(event);
487
488     if (heartbeat != -1) {
489         killTimer(heartbeat);
490         heartbeat = -1;
491     }
492
493     if (!current_target)
494         return;
495
496     xcb_client_message_event_t drop;
497     drop.response_type = XCB_CLIENT_MESSAGE;
498     drop.window = current_target;
499     drop.format = 32;
500     drop.type = atom(QXcbAtom::XdndDrop);
501     drop.data.data32[0] = connection()->clipboard()->owner();
502     drop.data.data32[1] = 0; // flags
503     drop.data.data32[2] = connection()->time();
504
505     drop.data.data32[3] = 0;
506     drop.data.data32[4] = currentDrag()->supportedActions();
507
508     QXcbWindow *w = connection()->platformWindowFromId(current_proxy_target);
509
510     if (w && (w->window()->type() == Qt::Desktop) /*&& !w->acceptDrops()*/)
511         w = 0;
512
513     Transaction t = {
514         connection()->time(),
515         current_target,
516         current_proxy_target,
517         (w ? w->window() : 0),
518 //        current_embeddig_widget,
519         currentDrag(),
520         QTime::currentTime()
521     };
522     transactions.append(t);
523
524     // timer is needed only for drops that came from other processes.
525     if (!t.targetWindow && cleanup_timer == -1) {
526         cleanup_timer = startTimer(XdndDropTransactionTimeout);
527     }
528
529     if (w) {
530         handleDrop(w->window(), &drop);
531     } else {
532         xcb_send_event(xcb_connection(), false, current_proxy_target, XCB_EVENT_MASK_NO_EVENT, (const char *)&drop);
533     }
534
535     current_target = 0;
536     current_proxy_target = 0;
537     source_time = 0;
538 //    current_embedding_widget = 0;
539     // #fixme resetDndState(false);
540 }
541
542 Qt::DropAction QXcbDrag::toDropAction(xcb_atom_t a) const
543 {
544     if (a == atom(QXcbAtom::XdndActionCopy) || a == 0)
545         return Qt::CopyAction;
546     if (a == atom(QXcbAtom::XdndActionLink))
547         return Qt::LinkAction;
548     if (a == atom(QXcbAtom::XdndActionMove))
549         return Qt::MoveAction;
550     return Qt::CopyAction;
551 }
552
553 xcb_atom_t QXcbDrag::toXdndAction(Qt::DropAction a) const
554 {
555     switch (a) {
556     case Qt::CopyAction:
557         return atom(QXcbAtom::XdndActionCopy);
558     case Qt::LinkAction:
559         return atom(QXcbAtom::XdndActionLink);
560     case Qt::MoveAction:
561     case Qt::TargetMoveAction:
562         return atom(QXcbAtom::XdndActionMove);
563     case Qt::IgnoreAction:
564         return XCB_NONE;
565     default:
566         return atom(QXcbAtom::XdndActionCopy);
567     }
568 }
569
570 int QXcbDrag::findTransactionByWindow(xcb_window_t window)
571 {
572     int at = -1;
573     for (int i = 0; i < transactions.count(); ++i) {
574         const Transaction &t = transactions.at(i);
575         if (t.target == window || t.proxy_target == window) {
576             at = i;
577             break;
578         }
579     }
580     return at;
581 }
582
583 int QXcbDrag::findTransactionByTime(xcb_timestamp_t timestamp)
584 {
585     int at = -1;
586     for (int i = 0; i < transactions.count(); ++i) {
587         const Transaction &t = transactions.at(i);
588         if (t.timestamp == timestamp) {
589             at = i;
590             break;
591         }
592     }
593     return at;
594 }
595
596 #if 0
597
598 // find an ancestor with XdndAware on it
599 static Window findXdndAwareParent(Window window)
600 {
601     Window target = 0;
602     forever {
603         // check if window has XdndAware
604         Atom type = 0;
605         int f;
606         unsigned long n, a;
607         unsigned char *data = 0;
608         if (XGetWindowProperty(X11->display, window, ATOM(XdndAware), 0, 0, False,
609                                AnyPropertyType, &type, &f,&n,&a,&data) == Success) {
610             if (data)
611                 XFree(data);
612             if (type) {
613                 target = window;
614                 break;
615             }
616         }
617
618         // try window's parent
619         Window root;
620         Window parent;
621         Window *children;
622         uint unused;
623         if (!XQueryTree(X11->display, window, &root, &parent, &children, &unused))
624             break;
625         if (children)
626             XFree(children);
627         if (window == root)
628             break;
629         window = parent;
630     }
631     return target;
632 }
633
634
635 // for embedding only
636 static QWidget* current_embedding_widget  = 0;
637 static xcb_client_message_event_t last_enter_event;
638
639
640 static bool checkEmbedded(QWidget* w, const XEvent* xe)
641 {
642     if (!w)
643         return false;
644
645     if (current_embedding_widget != 0 && current_embedding_widget != w) {
646         current_target = ((QExtraWidget*)current_embedding_widget)->extraData()->xDndProxy;
647         current_proxy_target = current_target;
648         qt_xdnd_send_leave();
649         current_target = 0;
650         current_proxy_target = 0;
651         current_embedding_widget = 0;
652     }
653
654     QWExtra* extra = ((QExtraWidget*)w)->extraData();
655     if (extra && extra->xDndProxy != 0) {
656
657         if (current_embedding_widget != w) {
658
659             last_enter_event.xany.window = extra->xDndProxy;
660             XSendEvent(X11->display, extra->xDndProxy, False, NoEventMask, &last_enter_event);
661             current_embedding_widget = w;
662         }
663
664         ((XEvent*)xe)->xany.window = extra->xDndProxy;
665         XSendEvent(X11->display, extra->xDndProxy, False, NoEventMask, (XEvent*)xe);
666         if (currentWindow != w) {
667             currentWindow = w;
668         }
669         return true;
670     }
671     current_embedding_widget = 0;
672     return false;
673 }
674 #endif
675
676
677 void QXcbDrag::handleEnter(QWindow *window, const xcb_client_message_event_t *event)
678 {
679     Q_UNUSED(window);
680     DEBUG() << "handleEnter" << window;
681
682     xdnd_types.clear();
683
684     int version = (int)(event->data.data32[1] >> 24);
685     if (version > xdnd_version)
686         return;
687
688     xdnd_dragsource = event->data.data32[0];
689
690     if (event->data.data32[1] & 1) {
691         // get the types from XdndTypeList
692         xcb_get_property_cookie_t cookie = xcb_get_property(xcb_connection(), false, xdnd_dragsource,
693                                                             atom(QXcbAtom::XdndTypelist), XCB_ATOM_ATOM,
694                                                             0, xdnd_max_type);
695         xcb_get_property_reply_t *reply = xcb_get_property_reply(xcb_connection(), cookie, 0);
696         if (reply && reply->type != XCB_NONE && reply->format == 32) {
697             int length = xcb_get_property_value_length(reply) / 4;
698             if (length > xdnd_max_type)
699                 length = xdnd_max_type;
700
701             xcb_atom_t *atoms = (xcb_atom_t *)xcb_get_property_value(reply);
702             for (int i = 0; i < length; ++i)
703                 xdnd_types.append(atoms[i]);
704         }
705         free(reply);
706     } else {
707         // get the types from the message
708         for(int i = 2; i < 5; i++) {
709             if (event->data.data32[i])
710                 xdnd_types.append(event->data.data32[i]);
711         }
712     }
713     for(int i = 0; i < xdnd_types.length(); ++i)
714         DEBUG() << "    " << connection()->atomName(xdnd_types.at(i));
715 }
716
717 void QXcbDrag::handle_xdnd_position(QWindow *w, const xcb_client_message_event_t *e)
718 {
719     QPoint p((e->data.data32[2] & 0xffff0000) >> 16, e->data.data32[2] & 0x0000ffff);
720     Q_ASSERT(w);
721     QRect geometry = w->geometry();
722
723     p -= geometry.topLeft();
724
725     if (!w || (w->type() == Qt::Desktop))
726         return;
727
728     if (e->data.data32[0] != xdnd_dragsource) {
729         DEBUG("xdnd drag position from unexpected source (%x not %x)", e->data.data32[0], xdnd_dragsource);
730         return;
731     }
732
733     currentPosition = p;
734     currentWindow = w;
735
736     // timestamp from the source
737     if (e->data.data32[3] != XCB_NONE) {
738         target_time = e->data.data32[3];
739     }
740
741     QMimeData *dropData = 0;
742     Qt::DropActions supported_actions = Qt::IgnoreAction;
743     if (currentDrag()) {
744         dropData = currentDrag()->mimeData();
745         supported_actions = currentDrag()->supportedActions();
746     } else {
747         dropData = platformDropData();
748         supported_actions = Qt::DropActions(toDropAction(e->data.data32[4]));
749     }
750
751     QPlatformDragQtResponse qt_response = QWindowSystemInterface::handleDrag(w,dropData,p,supported_actions);
752     QRect answerRect(p + geometry.topLeft(), QSize(1,1));
753     answerRect = qt_response.answerRect().translated(geometry.topLeft()).intersected(geometry);
754
755     xcb_client_message_event_t response;
756     response.response_type = XCB_CLIENT_MESSAGE;
757     response.window = xdnd_dragsource;
758     response.format = 32;
759     response.type = atom(QXcbAtom::XdndStatus);
760     response.data.data32[0] = xcb_window(w);
761     response.data.data32[1] = qt_response.isAccepted(); // flags
762     response.data.data32[2] = 0; // x, y
763     response.data.data32[3] = 0; // w, h
764     response.data.data32[4] = toXdndAction(qt_response.acceptedAction()); // action
765
766     accepted_drop_action = qt_response.acceptedAction();
767
768     if (answerRect.left() < 0)
769         answerRect.setLeft(0);
770     if (answerRect.right() > 4096)
771         answerRect.setRight(4096);
772     if (answerRect.top() < 0)
773         answerRect.setTop(0);
774     if (answerRect.bottom() > 4096)
775         answerRect.setBottom(4096);
776     if (answerRect.width() < 0)
777         answerRect.setWidth(0);
778     if (answerRect.height() < 0)
779         answerRect.setHeight(0);
780
781     // reset
782     target_time = XCB_CURRENT_TIME;
783
784     if (xdnd_dragsource == connection()->clipboard()->owner())
785         handle_xdnd_status(&response);
786     else
787         Q_XCB_CALL(xcb_send_event(xcb_connection(), false, xdnd_dragsource,
788                                   XCB_EVENT_MASK_NO_EVENT, (const char *)&response));
789 }
790
791 namespace
792 {
793     class ClientMessageScanner {
794     public:
795         ClientMessageScanner(xcb_atom_t a) : atom(a) {}
796         xcb_atom_t atom;
797         bool checkEvent(xcb_generic_event_t *event) const {
798             if (!event)
799                 return false;
800             if ((event->response_type & 0x7f) != XCB_CLIENT_MESSAGE)
801                 return false;
802             return ((xcb_client_message_event_t *)event)->type == atom;
803         }
804     };
805 }
806
807 void QXcbDrag::handlePosition(QWindow * w, const xcb_client_message_event_t *event)
808 {
809     xcb_client_message_event_t *lastEvent = const_cast<xcb_client_message_event_t *>(event);
810     xcb_generic_event_t *nextEvent;
811     ClientMessageScanner scanner(atom(QXcbAtom::XdndPosition));
812     while ((nextEvent = connection()->checkEvent(scanner))) {
813         if (lastEvent != event)
814             free(lastEvent);
815         lastEvent = (xcb_client_message_event_t *)nextEvent;
816     }
817
818     handle_xdnd_position(w, lastEvent);
819     if (lastEvent != event)
820         free(lastEvent);
821 }
822
823 void QXcbDrag::handle_xdnd_status(const xcb_client_message_event_t *event)
824 {
825     DEBUG("xdndHandleStatus");
826     waiting_for_status = false;
827     // ignore late status messages
828     if (event->data.data32[0] && event->data.data32[0] != current_proxy_target)
829         return;
830
831     const bool dropPossible = event->data.data32[1];
832     setCanDrop(dropPossible);
833
834     if (dropPossible) {
835         accepted_drop_action = toDropAction(event->data.data32[4]);
836         updateCursor(accepted_drop_action);
837     } else {
838         updateCursor(Qt::IgnoreAction);
839     }
840
841     if ((event->data.data32[1] & 2) == 0) {
842         QPoint p((event->data.data32[2] & 0xffff0000) >> 16, event->data.data32[2] & 0x0000ffff);
843         QSize s((event->data.data32[3] & 0xffff0000) >> 16, event->data.data32[3] & 0x0000ffff);
844         source_sameanswer = QRect(p, s);
845     } else {
846         source_sameanswer = QRect();
847     }
848 }
849
850 void QXcbDrag::handleStatus(const xcb_client_message_event_t *event)
851 {
852     if (event->window != connection()->clipboard()->owner())
853         return;
854
855     xcb_client_message_event_t *lastEvent = const_cast<xcb_client_message_event_t *>(event);
856     xcb_generic_event_t *nextEvent;
857     ClientMessageScanner scanner(atom(QXcbAtom::XdndStatus));
858     while ((nextEvent = connection()->checkEvent(scanner))) {
859         if (lastEvent != event)
860             free(lastEvent);
861         lastEvent = (xcb_client_message_event_t *)nextEvent;
862     }
863
864     handle_xdnd_status(lastEvent);
865     if (lastEvent != event)
866         free(lastEvent);
867     DEBUG("xdndHandleStatus end");
868 }
869
870 void QXcbDrag::handleLeave(QWindow *w, const xcb_client_message_event_t *event)
871 {
872     DEBUG("xdnd leave");
873     if (!currentWindow || w != currentWindow.data())
874         return; // sanity
875
876     // ###
877 //    if (checkEmbedded(current_embedding_widget, event)) {
878 //        current_embedding_widget = 0;
879 //        currentWindow.clear();
880 //        return;
881 //    }
882
883     if (event->data.data32[0] != xdnd_dragsource) {
884         // This often happens - leave other-process window quickly
885         DEBUG("xdnd drag leave from unexpected source (%x not %x", event->data.data32[0], xdnd_dragsource);
886     }
887
888     QWindowSystemInterface::handleDrag(w,0,QPoint(),Qt::IgnoreAction);
889     updateAction(Qt::IgnoreAction);
890
891     xdnd_dragsource = 0;
892     xdnd_types.clear();
893     currentWindow.clear();
894 }
895
896 void QXcbDrag::send_leave()
897 {
898     if (!current_target)
899         return;
900
901
902     xcb_client_message_event_t leave;
903     leave.response_type = XCB_CLIENT_MESSAGE;
904     leave.window = current_target;
905     leave.format = 32;
906     leave.type = atom(QXcbAtom::XdndLeave);
907     leave.data.data32[0] = connection()->clipboard()->owner();
908     leave.data.data32[1] = 0; // flags
909     leave.data.data32[2] = 0; // x, y
910     leave.data.data32[3] = 0; // w, h
911     leave.data.data32[4] = 0; // just null
912
913     QXcbWindow *w = connection()->platformWindowFromId(current_proxy_target);
914
915     if (w && (w->window()->type() == Qt::Desktop) /*&& !w->acceptDrops()*/)
916         w = 0;
917
918     if (w)
919         handleLeave(w->window(), (const xcb_client_message_event_t *)&leave);
920     else
921         xcb_send_event(xcb_connection(), false,current_proxy_target,
922                        XCB_EVENT_MASK_NO_EVENT, (const char *)&leave);
923
924     current_target = 0;
925     current_proxy_target = 0;
926     source_time = XCB_CURRENT_TIME;
927     waiting_for_status = false;
928 }
929
930 void QXcbDrag::handleDrop(QWindow *, const xcb_client_message_event_t *event)
931 {
932     DEBUG("xdndHandleDrop");
933     if (!currentWindow) {
934         xdnd_dragsource = 0;
935         return; // sanity
936     }
937
938     const uint32_t *l = event->data.data32;
939
940     DEBUG("xdnd drop");
941
942     if (l[0] != xdnd_dragsource) {
943         DEBUG("xdnd drop from unexpected source (%x not %x", l[0], xdnd_dragsource);
944         return;
945     }
946
947     // update the "user time" from the timestamp in the event.
948     if (l[2] != 0)
949         target_time = /*X11->userTime =*/ l[2];
950
951     Qt::DropActions supported_drop_actions;
952     QMimeData *dropData = 0;
953     if (currentDrag()) {
954         dropData = currentDrag()->mimeData();
955         supported_drop_actions = Qt::DropActions(l[4]);
956     } else {
957         dropData = platformDropData();
958         supported_drop_actions = accepted_drop_action;
959     }
960
961     if (!dropData)
962         return;
963     // ###
964     //        int at = findXdndDropTransactionByTime(target_time);
965     //        if (at != -1)
966     //            dropData = QDragManager::dragPrivate(X11->dndDropTransactions.at(at).object)->data;
967     // if we can't find it, then use the data in the drag manager
968
969     QPlatformDropQtResponse response = QWindowSystemInterface::handleDrop(currentWindow.data(),dropData,currentPosition,supported_drop_actions);
970     setExecutedDropAction(response.acceptedAction());
971
972     xcb_client_message_event_t finished;
973     finished.response_type = XCB_CLIENT_MESSAGE;
974     finished.window = xdnd_dragsource;
975     finished.format = 32;
976     finished.type = atom(QXcbAtom::XdndFinished);
977     finished.data.data32[0] = currentWindow ? xcb_window(currentWindow.data()) : XCB_NONE;
978     finished.data.data32[1] = response.isAccepted(); // flags
979     finished.data.data32[2] = toXdndAction(response.acceptedAction());
980     Q_XCB_CALL(xcb_send_event(xcb_connection(), false, xdnd_dragsource,
981                               XCB_EVENT_MASK_NO_EVENT, (char *)&finished));
982
983     xdnd_dragsource = 0;
984     currentWindow.clear();
985     waiting_for_status = false;
986
987     // reset
988     target_time = XCB_CURRENT_TIME;
989 }
990
991
992 void QXcbDrag::handleFinished(const xcb_client_message_event_t *event)
993 {
994     DEBUG("xdndHandleFinished");
995     if (event->window != connection()->clipboard()->owner())
996         return;
997
998     const unsigned long *l = (const unsigned long *)event->data.data32;
999
1000     DNDDEBUG << "xdndHandleFinished, l[0]" << l[0]
1001              << "current_target" << current_target
1002              << "qt_xdnd_current_proxy_targe" << current_proxy_target;
1003
1004     if (l[0]) {
1005         int at = findTransactionByWindow(l[0]);
1006         if (at != -1) {
1007
1008             Transaction t = transactions.takeAt(at);
1009 //            QDragManager *manager = QDragManager::self();
1010
1011 //            Window target = current_target;
1012 //            Window proxy_target = current_proxy_target;
1013 //            QWidget *embedding_widget = current_embedding_widget;
1014 //            QDrag *currentObject = manager->object;
1015
1016 //            current_target = t.target;
1017 //            current_proxy_target = t.proxy_target;
1018 //            current_embedding_widget = t.embedding_widget;
1019 //            manager->object = t.object;
1020
1021 //            if (!passive)
1022 //                (void) checkEmbedded(currentWindow, xe);
1023
1024 //            current_embedding_widget = 0;
1025 //            current_target = 0;
1026 //            current_proxy_target = 0;
1027
1028             if (t.drag)
1029                 t.drag->deleteLater();
1030
1031 //            current_target = target;
1032 //            current_proxy_target = proxy_target;
1033 //            current_embedding_widget = embedding_widget;
1034 //            manager->object = currentObject;
1035         } else {
1036             qWarning("QXcbDrag::handleFinished - drop data has expired");
1037         }
1038     }
1039     waiting_for_status = false;
1040 }
1041
1042 void QXcbDrag::timerEvent(QTimerEvent* e)
1043 {
1044     if (e->timerId() == heartbeat && source_sameanswer.isNull()) {
1045         QPointF pos = QCursor::pos();
1046         QMouseEvent me(QEvent::MouseMove, pos, pos, pos, Qt::LeftButton,
1047                        QGuiApplication::mouseButtons(), QGuiApplication::keyboardModifiers());
1048         move(&me);
1049     } else if (e->timerId() == cleanup_timer) {
1050         bool stopTimer = true;
1051         for (int i = 0; i < transactions.count(); ++i) {
1052             const Transaction &t = transactions.at(i);
1053             if (t.targetWindow) {
1054                 // dnd within the same process, don't delete, these are taken care of
1055                 // in handleFinished()
1056                 continue;
1057             }
1058             QTime currentTime = QTime::currentTime();
1059             int delta = t.time.msecsTo(currentTime);
1060             if (delta > XdndDropTransactionTimeout) {
1061                 /* delete transactions which are older than XdndDropTransactionTimeout. It could mean
1062                  one of these:
1063                  - client has crashed and as a result we have never received XdndFinished
1064                  - showing dialog box on drop event where user's response takes more time than XdndDropTransactionTimeout (QTBUG-14493)
1065                  - dnd takes unusually long time to process data
1066                  */
1067                 t.drag->deleteLater();
1068                 transactions.removeAt(i--);
1069             } else {
1070                 stopTimer = false;
1071             }
1072
1073         }
1074         if (stopTimer && cleanup_timer != -1) {
1075             killTimer(cleanup_timer);
1076             cleanup_timer = -1;
1077         }
1078     }
1079 }
1080
1081 void QXcbDrag::cancel()
1082 {
1083     DEBUG("QXcbDrag::cancel");
1084     QBasicDrag::cancel();
1085     if (current_target)
1086         send_leave();
1087 }
1088
1089
1090 void QXcbDrag::handleSelectionRequest(const xcb_selection_request_event_t *event)
1091 {
1092     xcb_selection_notify_event_t notify;
1093     notify.response_type = XCB_SELECTION_NOTIFY;
1094     notify.requestor = event->requestor;
1095     notify.selection = event->selection;
1096     notify.target = XCB_NONE;
1097     notify.property = XCB_NONE;
1098     notify.time = event->time;
1099
1100     // which transaction do we use? (note: -2 means use current currentDrag())
1101     int at = -1;
1102
1103     // figure out which data the requestor is really interested in
1104     if (currentDrag() && event->time == source_time) {
1105         // requestor wants the current drag data
1106         at = -2;
1107     } else {
1108         // if someone has requested data in response to XdndDrop, find the corresponding transaction. the
1109         // spec says to call xcb_convert_selection() using the timestamp from the XdndDrop
1110         at = findTransactionByTime(event->time);
1111         if (at == -1) {
1112             // no dice, perhaps the client was nice enough to use the same window id in
1113             // xcb_convert_selection() that we sent the XdndDrop event to.
1114             at = findTransactionByWindow(event->requestor);
1115         }
1116 //        if (at == -1 && event->time == XCB_CURRENT_TIME) {
1117 //            // previous Qt versions always requested the data on a child of the target window
1118 //            // using CurrentTime... but it could be asking for either drop data or the current drag's data
1119 //            Window target = findXdndAwareParent(event->requestor);
1120 //            if (target) {
1121 //                if (current_target && current_target == target)
1122 //                    at = -2;
1123 //                else
1124 //                    at = findXdndDropTransactionByWindow(target);
1125 //            }
1126 //        }
1127     }
1128
1129     QDrag *transactionDrag = 0;
1130     if (at >= 0) {
1131         transactionDrag = transactions.at(at).drag;
1132     } else if (at == -2) {
1133         transactionDrag = currentDrag();
1134     }
1135
1136     if (transactionDrag) {
1137         xcb_atom_t atomFormat = event->target;
1138         int dataFormat = 0;
1139         QByteArray data;
1140         if (QXcbMime::mimeDataForAtom(connection(), event->target, transactionDrag->mimeData(),
1141                                      &data, &atomFormat, &dataFormat)) {
1142             int dataSize = data.size() / (dataFormat / 8);
1143             xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, event->requestor, event->property,
1144                                 atomFormat, dataFormat, dataSize, (const void *)data.constData());
1145             notify.property = event->property;
1146             notify.target = atomFormat;
1147         }
1148     }
1149
1150     xcb_send_event(xcb_connection(), false, event->requestor, XCB_EVENT_MASK_NO_EVENT, (const char *)&notify);
1151 }
1152
1153
1154 bool QXcbDrag::dndEnable(QXcbWindow *w, bool on)
1155 {
1156     DNDDEBUG << "xdndEnable" << w << on;
1157     if (on) {
1158         QXcbWindow *xdnd_widget = 0;
1159         if ((w->window()->type() == Qt::Desktop)) {
1160             if (desktop_proxy) // *WE* already have one.
1161                 return false;
1162
1163             xcb_grab_server(xcb_connection());
1164
1165             // As per Xdnd4, use XdndProxy
1166             xcb_window_t proxy_id = xdndProxy(connection(), w->xcb_window());
1167
1168             if (!proxy_id) {
1169                 desktop_proxy = new QWindow;
1170                 xdnd_widget = static_cast<QXcbWindow *>(desktop_proxy->handle());
1171                 proxy_id = xdnd_widget->xcb_window();
1172                 xcb_atom_t xdnd_proxy = atom(QXcbAtom::XdndProxy);
1173                 xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, w->xcb_window(), xdnd_proxy,
1174                                     XCB_ATOM_WINDOW, 32, 1, &proxy_id);
1175                 xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, proxy_id, xdnd_proxy,
1176                                     XCB_ATOM_WINDOW, 32, 1, &proxy_id);
1177             }
1178
1179             xcb_ungrab_server(xcb_connection());
1180         } else {
1181             xdnd_widget = w;
1182         }
1183         if (xdnd_widget) {
1184             DNDDEBUG << "setting XdndAware for" << xdnd_widget << xdnd_widget->xcb_window();
1185             xcb_atom_t atm = xdnd_version;
1186             xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, xdnd_widget->xcb_window(),
1187                                 atom(QXcbAtom::XdndAware), XCB_ATOM_ATOM, 32, 1, &atm);
1188             return true;
1189         } else {
1190             return false;
1191         }
1192     } else {
1193         if ((w->window()->type() == Qt::Desktop)) {
1194             xcb_delete_property(xcb_connection(), w->xcb_window(), atom(QXcbAtom::XdndProxy));
1195             delete desktop_proxy;
1196             desktop_proxy = 0;
1197         } else {
1198             DNDDEBUG << "not deleting XDndAware";
1199         }
1200         return true;
1201     }
1202 }
1203
1204 QXcbDropData::QXcbDropData(QXcbDrag *d)
1205     : QXcbMime(),
1206       drag(d)
1207 {
1208 }
1209
1210 QXcbDropData::~QXcbDropData()
1211 {
1212 }
1213
1214 QVariant QXcbDropData::retrieveData_sys(const QString &mimetype, QVariant::Type requestedType) const
1215 {
1216     QByteArray mime = mimetype.toLatin1();
1217     QVariant data = xdndObtainData(mime, requestedType);
1218     return data;
1219 }
1220
1221 QVariant QXcbDropData::xdndObtainData(const QByteArray &format, QVariant::Type requestedType) const
1222 {
1223     QByteArray result;
1224
1225     QXcbConnection *c = drag->connection();
1226     QXcbWindow *xcb_window = c->platformWindowFromId(drag->xdnd_dragsource);
1227     if (xcb_window && drag->currentDrag() && xcb_window->window()->type() != Qt::Desktop) {
1228         QMimeData *data = drag->currentDrag()->mimeData();
1229         if (data->hasFormat(QLatin1String(format)))
1230             result = data->data(QLatin1String(format));
1231         return result;
1232     }
1233
1234     QList<xcb_atom_t> atoms = drag->xdnd_types;
1235     QByteArray encoding;
1236     xcb_atom_t a = mimeAtomForFormat(c, QLatin1String(format), requestedType, atoms, &encoding);
1237     if (a == XCB_NONE)
1238         return result;
1239
1240     if (c->clipboard()->getSelectionOwner(drag->atom(QXcbAtom::XdndSelection)) == XCB_NONE)
1241         return result; // should never happen?
1242
1243     xcb_atom_t xdnd_selection = c->atom(QXcbAtom::XdndSelection);
1244     result = c->clipboard()->getSelection(xdnd_selection, a, xdnd_selection, drag->targetTime());
1245
1246     return mimeConvertToFormat(c, a, result, QLatin1String(format), requestedType, encoding);
1247 }
1248
1249
1250 bool QXcbDropData::hasFormat_sys(const QString &format) const
1251 {
1252     return formats().contains(format);
1253 }
1254
1255 QStringList QXcbDropData::formats_sys() const
1256 {
1257     QStringList formats;
1258     for (int i = 0; i < drag->xdnd_types.size(); ++i) {
1259         QString f = mimeAtomToString(drag->connection(), drag->xdnd_types.at(i));
1260         if (!formats.contains(f))
1261             formats.append(f);
1262     }
1263     return formats;
1264 }
1265
1266 #endif // QT_NO_DRAGANDDROP
1267
1268 QT_END_NAMESPACE