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