1 /****************************************************************************
3 ** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/legal
6 ** This file is part of the QtGui module of the Qt Toolkit.
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.
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.
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.
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.
40 ****************************************************************************/
44 #include "qxcbconnection.h"
45 #include "qxcbclipboard.h"
47 #include "qxcbwindow.h"
48 #include "qxcbscreen.h"
50 #include <private/qdnd_p.h>
53 #include <qguiapplication.h>
58 #include <qpa/qwindowsysteminterface.h>
60 #include <QtPlatformSupport/private/qshapedpixmapdndwindow_p.h>
61 #include <QtPlatformSupport/private/qsimpledrag_p.h>
65 #ifndef QT_NO_DRAGANDDROP
71 #define DEBUG if(0) qDebug
75 #define DNDDEBUG qDebug()
77 #define DNDDEBUG if(0) qDebug()
80 const int xdnd_version = 5;
82 static inline xcb_window_t xcb_window(QWindow *w)
84 return static_cast<QXcbWindow *>(w->handle())->xcb_window();
88 static xcb_window_t xdndProxy(QXcbConnection *c, xcb_window_t w)
90 xcb_window_t proxy = XCB_NONE;
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);
96 if (reply && reply->type == XCB_ATOM_WINDOW)
97 proxy = *((xcb_window_t *)xcb_get_property_value(reply));
100 if (proxy == XCB_NONE)
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);
108 if (reply && reply->type == XCB_ATOM_WINDOW) {
109 xcb_window_t p = *((xcb_window_t *)xcb_get_property_value(reply));
121 class QXcbDropData : public QXcbMime
124 QXcbDropData(QXcbDrag *d);
128 bool hasFormat_sys(const QString &mimeType) const;
129 QStringList formats_sys() const;
130 QVariant retrieveData_sys(const QString &mimeType, QVariant::Type type) const;
132 QVariant xdndObtainData(const QByteArray &format, QVariant::Type requestedType) const;
138 QXcbDrag::QXcbDrag(QXcbConnection *c) : QXcbObject(c)
140 dropData = new QXcbDropData(this);
147 QXcbDrag::~QXcbDrag()
152 void QXcbDrag::init()
154 currentWindow.clear();
156 accepted_drop_action = Qt::IgnoreAction;
158 xdnd_dragsource = XCB_NONE;
160 waiting_for_status = false;
161 current_target = XCB_NONE;
162 current_proxy_target = XCB_NONE;
164 source_time = XCB_CURRENT_TIME;
165 target_time = XCB_CURRENT_TIME;
171 QMimeData *QXcbDrag::platformDropData()
176 void QXcbDrag::startDrag()
178 // #fixme enableEventFilter();
182 heartbeat = startTimer(200);
185 xcb_set_selection_owner(xcb_connection(), connection()->clipboard()->owner(),
186 atom(QXcbAtom::XdndSelection), connection()->time());
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));
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();
203 void QXcbDrag::endDrag()
205 if (heartbeat != -1) {
206 killTimer(heartbeat);
209 QBasicDrag::endDrag();
212 static xcb_translate_coordinates_reply_t *
213 translateCoordinates(QXcbConnection *c, xcb_window_t from, xcb_window_t to, int x, int y)
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);
221 bool windowInteractsWithPosition(xcb_connection_t *connection, const QPoint & pos, xcb_window_t w, xcb_shape_sk_t shapeType)
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);
226 xcb_rectangle_t *rectangles = xcb_shape_get_rectangles_rectangles(reply);
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);
239 xcb_window_t QXcbDrag::findRealWindow(const QPoint & pos, xcb_window_t w, int md, bool ignoreNonXdndAwareWindows)
241 if (w == shapedPixmapWindow()->handle()->winId())
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);
250 if (reply->map_state != XCB_MAP_STATE_VIEWABLE)
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;
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);
263 bool isAware = reply && reply->type != XCB_NONE;
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)
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);
285 int nc = xcb_query_tree_children_length(reply);
286 xcb_window_t *c = xcb_query_tree_children(reply);
289 for (uint i = nc; !r && i--;)
290 r = findRealWindow(pos - QPoint(greply->x, greply->y), c[i], md-1, ignoreNonXdndAwareWindows);
296 // We didn't find a client window! Just use the
300 if (!windowContainsMouse)
309 void QXcbDrag::move(const QMouseEvent *me)
311 QBasicDrag::move(me);
312 QPoint globalPos = me->globalPos();
314 if (source_sameanswer.contains(globalPos) && source_sameanswer.isValid())
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);
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()) {
336 // xdnd_data.deco->grabMouse();
339 // xdnd_data.deco->move(QCursor::pos() - xdnd_data.deco->pm_hot);
340 current_screen = screen;
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());
351 xcb_window_t target = translate->child;
352 int lx = translate->dst_x;
353 int ly = translate->dst_y;
356 if (target && target != rootwin) {
357 xcb_window_t src = rootwin;
358 while (target != 0) {
359 DNDDEBUG << "checking target for XdndAware" << target << lx << ly;
361 // translate coordinates
362 translate = ::translateCoordinates(connection(), src, target, lx, ly);
367 lx = translate->dst_x;
368 ly = translate->dst_y;
370 xcb_window_t child = translate->child;
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;
380 DNDDEBUG << "Found XdndAware on " << target;
387 if (!target || target == shapedPixmapWindow()->handle()->winId()) {
388 DNDDEBUG << "need to find real window";
389 target = findRealWindow(globalPos, rootwin, 6, true);
391 target = findRealWindow(globalPos, rootwin, 6, false);
392 DNDDEBUG << "real window found" << target;
398 w = connection()->platformWindowFromId(target);
399 if (w && (w->window()->type() == Qt::Desktop) /*&& !w->acceptDrops()*/)
406 xcb_window_t proxy_target = xdndProxy(connection(), target);
408 proxy_target = target;
409 int target_version = 1;
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)
418 target_version = *(uint32_t *)xcb_get_property_value(reply);
419 target_version = qMin(xdnd_version, target_version ? target_version : 1);
424 if (target != current_target) {
428 current_target = target;
429 current_proxy_target = proxy_target;
431 int flags = target_version << 24;
432 if (drag_types.size() > 3)
435 xcb_client_message_event_t enter;
436 enter.response_type = XCB_CLIENT_MESSAGE;
437 enter.window = target;
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);
448 DEBUG() << "sending Xdnd enter source=" << enter.data.data32[0];
450 handleEnter(w->window(), &enter);
452 xcb_send_event(xcb_connection(), false, proxy_target, XCB_EVENT_MASK_NO_EVENT, (const char *)&enter);
453 waiting_for_status = false;
457 if (waiting_for_status)
461 waiting_for_status = true;
463 xcb_client_message_event_t move;
464 move.response_type = XCB_CLIENT_MESSAGE;
465 move.window = target;
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;
475 source_time = connection()->time();
478 handle_xdnd_position(w->window(), &move);
480 xcb_send_event(xcb_connection(), false, proxy_target, XCB_EVENT_MASK_NO_EVENT, (const char *)&move);
484 void QXcbDrag::drop(const QMouseEvent *event)
486 QBasicDrag::drop(event);
488 if (heartbeat != -1) {
489 killTimer(heartbeat);
496 xcb_client_message_event_t drop;
497 drop.response_type = XCB_CLIENT_MESSAGE;
498 drop.window = current_target;
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();
505 drop.data.data32[3] = 0;
506 drop.data.data32[4] = currentDrag()->supportedActions();
508 QXcbWindow *w = connection()->platformWindowFromId(current_proxy_target);
510 if (w && (w->window()->type() == Qt::Desktop) /*&& !w->acceptDrops()*/)
514 connection()->time(),
516 current_proxy_target,
517 (w ? w->window() : 0),
518 // current_embeddig_widget,
522 transactions.append(t);
524 // timer is needed only for drops that came from other processes.
525 if (!t.targetWindow && cleanup_timer == -1) {
526 cleanup_timer = startTimer(XdndDropTransactionTimeout);
530 handleDrop(w->window(), &drop);
532 xcb_send_event(xcb_connection(), false, current_proxy_target, XCB_EVENT_MASK_NO_EVENT, (const char *)&drop);
536 current_proxy_target = 0;
538 // current_embedding_widget = 0;
539 // #fixme resetDndState(false);
542 Qt::DropAction QXcbDrag::toDropAction(xcb_atom_t a) const
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;
553 xcb_atom_t QXcbDrag::toXdndAction(Qt::DropAction a) const
557 return atom(QXcbAtom::XdndActionCopy);
559 return atom(QXcbAtom::XdndActionLink);
561 case Qt::TargetMoveAction:
562 return atom(QXcbAtom::XdndActionMove);
563 case Qt::IgnoreAction:
566 return atom(QXcbAtom::XdndActionCopy);
570 int QXcbDrag::findTransactionByWindow(xcb_window_t window)
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) {
583 int QXcbDrag::findTransactionByTime(xcb_timestamp_t timestamp)
586 for (int i = 0; i < transactions.count(); ++i) {
587 const Transaction &t = transactions.at(i);
588 if (t.timestamp == timestamp) {
598 // find an ancestor with XdndAware on it
599 static Window findXdndAwareParent(Window window)
603 // check if window has XdndAware
607 unsigned char *data = 0;
608 if (XGetWindowProperty(X11->display, window, ATOM(XdndAware), 0, 0, False,
609 AnyPropertyType, &type, &f,&n,&a,&data) == Success) {
618 // try window's parent
623 if (!XQueryTree(X11->display, window, &root, &parent, &children, &unused))
635 // for embedding only
636 static QWidget* current_embedding_widget = 0;
637 static xcb_client_message_event_t last_enter_event;
640 static bool checkEmbedded(QWidget* w, const XEvent* xe)
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();
650 current_proxy_target = 0;
651 current_embedding_widget = 0;
654 QWExtra* extra = ((QExtraWidget*)w)->extraData();
655 if (extra && extra->xDndProxy != 0) {
657 if (current_embedding_widget != w) {
659 last_enter_event.xany.window = extra->xDndProxy;
660 XSendEvent(X11->display, extra->xDndProxy, False, NoEventMask, &last_enter_event);
661 current_embedding_widget = w;
664 ((XEvent*)xe)->xany.window = extra->xDndProxy;
665 XSendEvent(X11->display, extra->xDndProxy, False, NoEventMask, (XEvent*)xe);
666 if (currentWindow != w) {
671 current_embedding_widget = 0;
677 void QXcbDrag::handleEnter(QWindow *window, const xcb_client_message_event_t *event)
680 DEBUG() << "handleEnter" << window;
684 int version = (int)(event->data.data32[1] >> 24);
685 if (version > xdnd_version)
688 xdnd_dragsource = event->data.data32[0];
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,
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;
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]);
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]);
713 for(int i = 0; i < xdnd_types.length(); ++i)
714 DEBUG() << " " << connection()->atomName(xdnd_types.at(i));
717 void QXcbDrag::handle_xdnd_position(QWindow *w, const xcb_client_message_event_t *e)
719 QPoint p((e->data.data32[2] & 0xffff0000) >> 16, e->data.data32[2] & 0x0000ffff);
721 QRect geometry = w->geometry();
723 p -= geometry.topLeft();
725 if (!w || (w->type() == Qt::Desktop))
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);
736 // timestamp from the source
737 if (e->data.data32[3] != XCB_NONE) {
738 target_time = e->data.data32[3];
741 QMimeData *dropData = 0;
742 Qt::DropActions supported_actions = Qt::IgnoreAction;
744 dropData = currentDrag()->mimeData();
745 supported_actions = currentDrag()->supportedActions();
747 dropData = platformDropData();
748 supported_actions = Qt::DropActions(toDropAction(e->data.data32[4]));
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);
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
766 accepted_drop_action = qt_response.acceptedAction();
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);
782 target_time = XCB_CURRENT_TIME;
784 if (xdnd_dragsource == connection()->clipboard()->owner())
785 handle_xdnd_status(&response);
787 Q_XCB_CALL(xcb_send_event(xcb_connection(), false, xdnd_dragsource,
788 XCB_EVENT_MASK_NO_EVENT, (const char *)&response));
793 class ClientMessageScanner {
795 ClientMessageScanner(xcb_atom_t a) : atom(a) {}
797 bool checkEvent(xcb_generic_event_t *event) const {
800 if ((event->response_type & 0x7f) != XCB_CLIENT_MESSAGE)
802 return ((xcb_client_message_event_t *)event)->type == atom;
807 void QXcbDrag::handlePosition(QWindow * w, const xcb_client_message_event_t *event)
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)
815 lastEvent = (xcb_client_message_event_t *)nextEvent;
818 handle_xdnd_position(w, lastEvent);
819 if (lastEvent != event)
823 void QXcbDrag::handle_xdnd_status(const xcb_client_message_event_t *event)
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)
831 const bool dropPossible = event->data.data32[1];
832 setCanDrop(dropPossible);
835 accepted_drop_action = toDropAction(event->data.data32[4]);
836 updateCursor(accepted_drop_action);
838 updateCursor(Qt::IgnoreAction);
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);
846 source_sameanswer = QRect();
850 void QXcbDrag::handleStatus(const xcb_client_message_event_t *event)
852 if (event->window != connection()->clipboard()->owner())
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)
861 lastEvent = (xcb_client_message_event_t *)nextEvent;
864 handle_xdnd_status(lastEvent);
865 if (lastEvent != event)
867 DEBUG("xdndHandleStatus end");
870 void QXcbDrag::handleLeave(QWindow *w, const xcb_client_message_event_t *event)
873 if (!currentWindow || w != currentWindow.data())
877 // if (checkEmbedded(current_embedding_widget, event)) {
878 // current_embedding_widget = 0;
879 // currentWindow.clear();
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);
888 QWindowSystemInterface::handleDrag(w,0,QPoint(),Qt::IgnoreAction);
889 updateAction(Qt::IgnoreAction);
893 currentWindow.clear();
896 void QXcbDrag::send_leave()
902 xcb_client_message_event_t leave;
903 leave.response_type = XCB_CLIENT_MESSAGE;
904 leave.window = current_target;
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
913 QXcbWindow *w = connection()->platformWindowFromId(current_proxy_target);
915 if (w && (w->window()->type() == Qt::Desktop) /*&& !w->acceptDrops()*/)
919 handleLeave(w->window(), (const xcb_client_message_event_t *)&leave);
921 xcb_send_event(xcb_connection(), false,current_proxy_target,
922 XCB_EVENT_MASK_NO_EVENT, (const char *)&leave);
925 current_proxy_target = 0;
926 source_time = XCB_CURRENT_TIME;
927 waiting_for_status = false;
930 void QXcbDrag::handleDrop(QWindow *, const xcb_client_message_event_t *event)
932 DEBUG("xdndHandleDrop");
933 if (!currentWindow) {
938 const uint32_t *l = event->data.data32;
942 if (l[0] != xdnd_dragsource) {
943 DEBUG("xdnd drop from unexpected source (%x not %x", l[0], xdnd_dragsource);
947 // update the "user time" from the timestamp in the event.
949 target_time = /*X11->userTime =*/ l[2];
951 Qt::DropActions supported_drop_actions;
952 QMimeData *dropData = 0;
954 dropData = currentDrag()->mimeData();
955 supported_drop_actions = Qt::DropActions(l[4]);
957 dropData = platformDropData();
958 supported_drop_actions = accepted_drop_action;
964 // int at = findXdndDropTransactionByTime(target_time);
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
969 QPlatformDropQtResponse response = QWindowSystemInterface::handleDrop(currentWindow.data(),dropData,currentPosition,supported_drop_actions);
970 setExecutedDropAction(response.acceptedAction());
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));
984 currentWindow.clear();
985 waiting_for_status = false;
988 target_time = XCB_CURRENT_TIME;
992 void QXcbDrag::handleFinished(const xcb_client_message_event_t *event)
994 DEBUG("xdndHandleFinished");
995 if (event->window != connection()->clipboard()->owner())
998 const unsigned long *l = (const unsigned long *)event->data.data32;
1000 DNDDEBUG << "xdndHandleFinished, l[0]" << l[0]
1001 << "current_target" << current_target
1002 << "qt_xdnd_current_proxy_targe" << current_proxy_target;
1005 int at = findTransactionByWindow(l[0]);
1008 Transaction t = transactions.takeAt(at);
1009 // QDragManager *manager = QDragManager::self();
1011 // Window target = current_target;
1012 // Window proxy_target = current_proxy_target;
1013 // QWidget *embedding_widget = current_embedding_widget;
1014 // QDrag *currentObject = manager->object;
1016 // current_target = t.target;
1017 // current_proxy_target = t.proxy_target;
1018 // current_embedding_widget = t.embedding_widget;
1019 // manager->object = t.object;
1022 // (void) checkEmbedded(currentWindow, xe);
1024 // current_embedding_widget = 0;
1025 // current_target = 0;
1026 // current_proxy_target = 0;
1029 t.drag->deleteLater();
1031 // current_target = target;
1032 // current_proxy_target = proxy_target;
1033 // current_embedding_widget = embedding_widget;
1034 // manager->object = currentObject;
1036 qWarning("QXcbDrag::handleFinished - drop data has expired");
1039 waiting_for_status = false;
1042 void QXcbDrag::timerEvent(QTimerEvent* e)
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());
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()
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
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
1067 t.drag->deleteLater();
1068 transactions.removeAt(i--);
1074 if (stopTimer && cleanup_timer != -1) {
1075 killTimer(cleanup_timer);
1081 void QXcbDrag::cancel()
1083 DEBUG("QXcbDrag::cancel");
1084 QBasicDrag::cancel();
1090 void QXcbDrag::handleSelectionRequest(const xcb_selection_request_event_t *event)
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;
1100 // which transaction do we use? (note: -2 means use current currentDrag())
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
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);
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);
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);
1121 // if (current_target && current_target == target)
1124 // at = findXdndDropTransactionByWindow(target);
1129 QDrag *transactionDrag = 0;
1131 transactionDrag = transactions.at(at).drag;
1132 } else if (at == -2) {
1133 transactionDrag = currentDrag();
1136 if (transactionDrag) {
1137 xcb_atom_t atomFormat = event->target;
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;
1150 xcb_send_event(xcb_connection(), false, event->requestor, XCB_EVENT_MASK_NO_EVENT, (const char *)¬ify);
1154 bool QXcbDrag::dndEnable(QXcbWindow *w, bool on)
1156 DNDDEBUG << "xdndEnable" << w << on;
1158 QXcbWindow *xdnd_widget = 0;
1159 if ((w->window()->type() == Qt::Desktop)) {
1160 if (desktop_proxy) // *WE* already have one.
1163 xcb_grab_server(xcb_connection());
1165 // As per Xdnd4, use XdndProxy
1166 xcb_window_t proxy_id = xdndProxy(connection(), w->xcb_window());
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);
1179 xcb_ungrab_server(xcb_connection());
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);
1193 if ((w->window()->type() == Qt::Desktop)) {
1194 xcb_delete_property(xcb_connection(), w->xcb_window(), atom(QXcbAtom::XdndProxy));
1195 delete desktop_proxy;
1198 DNDDEBUG << "not deleting XDndAware";
1204 QXcbDropData::QXcbDropData(QXcbDrag *d)
1210 QXcbDropData::~QXcbDropData()
1214 QVariant QXcbDropData::retrieveData_sys(const QString &mimetype, QVariant::Type requestedType) const
1216 QByteArray mime = mimetype.toLatin1();
1217 QVariant data = xdndObtainData(mime, requestedType);
1221 QVariant QXcbDropData::xdndObtainData(const QByteArray &format, QVariant::Type requestedType) const
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));
1234 QList<xcb_atom_t> atoms = drag->xdnd_types;
1235 QByteArray encoding;
1236 xcb_atom_t a = mimeAtomForFormat(c, QLatin1String(format), requestedType, atoms, &encoding);
1240 if (c->clipboard()->getSelectionOwner(drag->atom(QXcbAtom::XdndSelection)) == XCB_NONE)
1241 return result; // should never happen?
1243 xcb_atom_t xdnd_selection = c->atom(QXcbAtom::XdndSelection);
1244 result = c->clipboard()->getSelection(xdnd_selection, a, xdnd_selection, drag->targetTime());
1246 return mimeConvertToFormat(c, a, result, QLatin1String(format), requestedType, encoding);
1250 bool QXcbDropData::hasFormat_sys(const QString &format) const
1252 return formats().contains(format);
1255 QStringList QXcbDropData::formats_sys() const
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))
1266 #endif // QT_NO_DRAGANDDROP