• Gatis Paeglis's avatar
    Don't send QMouseEvent events from the QXcbDrag::timerEvent() · 29b9b92f
    Gatis Paeglis authored
    
    This is not needed. The DnD initiated scrolling is done by
    installing an eventFilter which forwards all MouseMove events
    to the QGuiApplicationPrivate::processDrag which creates and forwards
    all the necessary QDrag* events. QAbstractScrollArea has its own logic
    in the timerEvent() to make the view scroll when the pointer is standing
    still on the edge and the drag process is still ongoing.
    
    With the current implementation, widgets (during the DnD) were receiving
    DragMove events even when the pointer was standing still outside auto scroll
    areas.
    
    Task-number: QTBUG-28171
    Change-Id: I355d88f3eab0ad39f916f84d66f5d0af7c0ff93e
    Reviewed-by: default avatarLars Knoll <lars.knoll@digia.com>
    Reviewed-by: default avatarStephen Kelly <stephen.kelly@kdab.com>
    29b9b92f
qxcbdrag.cpp 41.36 KiB
/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the QtGui module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia.  For licensing terms and
** conditions see http://qt.digia.com/licensing.  For further information
** use the contact form at http://qt.digia.com/contact-us.
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
** $QT_END_LICENSE$
****************************************************************************/
#include "qxcbdrag.h"
#include <xcb/xcb.h>
#include "qxcbconnection.h"
#include "qxcbclipboard.h"
#include "qxcbmime.h"
#include "qxcbwindow.h"
#include "qxcbscreen.h"
#include "qwindow.h"
#include <private/qdnd_p.h>
#include <qdebug.h>
#include <qevent.h>
#include <qguiapplication.h>
#include <qrect.h>
#include <qpainter.h>
#include <qtimer.h>
#include <qpa/qwindowsysteminterface.h>
#include <QtPlatformSupport/private/qshapedpixmapdndwindow_p.h>
#include <QtPlatformSupport/private/qsimpledrag_p.h>
QT_BEGIN_NAMESPACE
#ifndef QT_NO_DRAGANDDROP
//#define DND_DEBUG
#ifdef DND_DEBUG
#define DEBUG qDebug
#else
7172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
#define DEBUG if(0) qDebug #endif #ifdef DND_DEBUG #define DNDDEBUG qDebug() #else #define DNDDEBUG if(0) qDebug() #endif const int xdnd_version = 5; static inline xcb_window_t xcb_window(QWindow *w) { return static_cast<QXcbWindow *>(w->handle())->xcb_window(); } static xcb_window_t xdndProxy(QXcbConnection *c, xcb_window_t w) { xcb_window_t proxy = XCB_NONE; xcb_get_property_cookie_t cookie = Q_XCB_CALL2(xcb_get_property(c->xcb_connection(), false, w, c->atom(QXcbAtom::XdndProxy), XCB_ATOM_WINDOW, 0, 1), c); xcb_get_property_reply_t *reply = xcb_get_property_reply(c->xcb_connection(), cookie, 0); if (reply && reply->type == XCB_ATOM_WINDOW) proxy = *((xcb_window_t *)xcb_get_property_value(reply)); free(reply); if (proxy == XCB_NONE) return proxy; // exists and is real? cookie = Q_XCB_CALL2(xcb_get_property(c->xcb_connection(), false, proxy, c->atom(QXcbAtom::XdndProxy), XCB_ATOM_WINDOW, 0, 1), c); reply = xcb_get_property_reply(c->xcb_connection(), cookie, 0); if (reply && reply->type == XCB_ATOM_WINDOW) { xcb_window_t p = *((xcb_window_t *)xcb_get_property_value(reply)); if (proxy != p) proxy = 0; } else { proxy = 0; } free(reply); return proxy; } class QXcbDropData : public QXcbMime { public: QXcbDropData(QXcbDrag *d); ~QXcbDropData(); protected: bool hasFormat_sys(const QString &mimeType) const; QStringList formats_sys() const; QVariant retrieveData_sys(const QString &mimeType, QVariant::Type type) const; QVariant xdndObtainData(const QByteArray &format, QVariant::Type requestedType) const; QXcbDrag *drag; }; QXcbDrag::QXcbDrag(QXcbConnection *c) : QXcbObject(c) { dropData = new QXcbDropData(this);
141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
init(); cleanup_timer = -1; } QXcbDrag::~QXcbDrag() { delete dropData; } void QXcbDrag::init() { currentWindow.clear(); accepted_drop_action = Qt::IgnoreAction; xdnd_dragsource = XCB_NONE; waiting_for_status = false; current_target = XCB_NONE; current_proxy_target = XCB_NONE; source_time = XCB_CURRENT_TIME; target_time = XCB_CURRENT_TIME; current_screen = 0; drag_types.clear(); } QMimeData *QXcbDrag::platformDropData() { return dropData; } void QXcbDrag::startDrag() { // #fixme enableEventFilter(); init(); xcb_set_selection_owner(xcb_connection(), connection()->clipboard()->owner(), atom(QXcbAtom::XdndSelection), connection()->time()); QStringList fmts = QXcbMime::formatsHelper(drag()->mimeData()); for (int i = 0; i < fmts.size(); ++i) { QList<xcb_atom_t> atoms = QXcbMime::mimeAtomsForFormat(connection(), fmts.at(i)); for (int j = 0; j < atoms.size(); ++j) { if (!drag_types.contains(atoms.at(j))) drag_types.append(atoms.at(j)); } } if (drag_types.size() > 3) xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, connection()->clipboard()->owner(), atom(QXcbAtom::XdndTypelist), XCB_ATOM_ATOM, 32, drag_types.size(), (const void *)drag_types.constData()); QBasicDrag::startDrag(); } void QXcbDrag::endDrag() { QBasicDrag::endDrag(); } static xcb_translate_coordinates_reply_t * translateCoordinates(QXcbConnection *c, xcb_window_t from, xcb_window_t to, int x, int y) { xcb_translate_coordinates_cookie_t cookie = xcb_translate_coordinates(c->xcb_connection(), from, to, x, y); return xcb_translate_coordinates_reply(c->xcb_connection(), cookie, 0); }
211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
static bool windowInteractsWithPosition(xcb_connection_t *connection, const QPoint & pos, xcb_window_t w, xcb_shape_sk_t shapeType) { bool interacts = false; xcb_shape_get_rectangles_reply_t *reply = xcb_shape_get_rectangles_reply(connection, xcb_shape_get_rectangles(connection, w, shapeType), NULL); if (reply) { xcb_rectangle_t *rectangles = xcb_shape_get_rectangles_rectangles(reply); if (rectangles) { const int nRectangles = xcb_shape_get_rectangles_rectangles_length(reply); for (int i = 0; !interacts && i < nRectangles; ++i) { interacts = QRect(rectangles[i].x, rectangles[i].y, rectangles[i].width, rectangles[i].height).contains(pos); } } free(reply); } return interacts; } xcb_window_t QXcbDrag::findRealWindow(const QPoint & pos, xcb_window_t w, int md, bool ignoreNonXdndAwareWindows) { if (w == shapedPixmapWindow()->handle()->winId()) return 0; if (md) { xcb_get_window_attributes_cookie_t cookie = xcb_get_window_attributes(xcb_connection(), w); xcb_get_window_attributes_reply_t *reply = xcb_get_window_attributes_reply(xcb_connection(), cookie, 0); if (!reply) return 0; if (reply->map_state != XCB_MAP_STATE_VIEWABLE) return 0; xcb_get_geometry_cookie_t gcookie = xcb_get_geometry(xcb_connection(), w); xcb_get_geometry_reply_t *greply = xcb_get_geometry_reply(xcb_connection(), gcookie, 0); if (reply && QRect(greply->x, greply->y, greply->width, greply->height).contains(pos)) { bool windowContainsMouse = !ignoreNonXdndAwareWindows; { xcb_get_property_cookie_t cookie = Q_XCB_CALL(xcb_get_property(xcb_connection(), false, w, connection()->atom(QXcbAtom::XdndAware), XCB_GET_PROPERTY_TYPE_ANY, 0, 0)); xcb_get_property_reply_t *reply = xcb_get_property_reply(xcb_connection(), cookie, 0); bool isAware = reply && reply->type != XCB_NONE; free(reply); if (isAware) { const QPoint relPos = pos - QPoint(greply->x, greply->y); // When ShapeInput and ShapeBounding are not set they return a single rectangle with the geometry of the window, this is why we // need to check both here so that in the case one is set and the other is not we still get the correct result. if (connection()->hasInputShape()) windowContainsMouse = windowInteractsWithPosition(xcb_connection(), relPos, w, XCB_SHAPE_SK_INPUT); if (windowContainsMouse && connection()->hasXShape()) windowContainsMouse = windowInteractsWithPosition(xcb_connection(), relPos, w, XCB_SHAPE_SK_BOUNDING); if (!connection()->hasInputShape() && !connection()->hasXShape()) windowContainsMouse = true; if (windowContainsMouse) return w; } } xcb_query_tree_cookie_t cookie = xcb_query_tree (xcb_connection(), w); xcb_query_tree_reply_t *reply = xcb_query_tree_reply(xcb_connection(), cookie, 0); if (!reply) return 0; int nc = xcb_query_tree_children_length(reply); xcb_window_t *c = xcb_query_tree_children(reply); xcb_window_t r = 0;
281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
for (uint i = nc; !r && i--;) r = findRealWindow(pos - QPoint(greply->x, greply->y), c[i], md-1, ignoreNonXdndAwareWindows); free(reply); if (r) return r; // We didn't find a client window! Just use the // innermost window. // No children! if (!windowContainsMouse) return 0; else return w; } } return 0; } void QXcbDrag::move(const QMouseEvent *me) { QBasicDrag::move(me); QPoint globalPos = me->globalPos(); if (source_sameanswer.contains(globalPos) && source_sameanswer.isValid()) return; const QList<QXcbScreen *> &screens = connection()->screens(); QXcbScreen *screen = screens.at(connection()->primaryScreen()); for (int i = 0; i < screens.size(); ++i) { if (screens.at(i)->geometry().contains(globalPos)) { screen = screens.at(i); break; } } if (screen != current_screen) { // ### need to recreate the shaped pixmap window? // int screen = QCursor::x11Screen(); // if ((qt_xdnd_current_screen == -1 && screen != X11->defaultScreen) || (screen != qt_xdnd_current_screen)) { // // recreate the pixmap on the new screen... // delete xdnd_data.deco; // QWidget* parent = object->source()->window()->x11Info().screen() == screen // ? object->source()->window() : QApplication::desktop()->screen(screen); // xdnd_data.deco = new QShapedPixmapWidget(parent); // if (!QWidget::mouseGrabber()) { // updatePixmap(); // xdnd_data.deco->grabMouse(); // } // } // xdnd_data.deco->move(QCursor::pos() - xdnd_data.deco->pm_hot); current_screen = screen; } // qt_xdnd_current_screen = screen; xcb_window_t rootwin = current_screen->root(); xcb_translate_coordinates_reply_t *translate = ::translateCoordinates(connection(), rootwin, rootwin, globalPos.x(), globalPos.y()); if (!translate) return; xcb_window_t target = translate->child; int lx = translate->dst_x; int ly = translate->dst_y; free (translate); if (target && target != rootwin) { xcb_window_t src = rootwin; while (target != 0) {
351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
DNDDEBUG << "checking target for XdndAware" << target << lx << ly; // translate coordinates translate = ::translateCoordinates(connection(), src, target, lx, ly); if (!translate) { target = 0; break; } lx = translate->dst_x; ly = translate->dst_y; src = target; xcb_window_t child = translate->child; free(translate); // check if it has XdndAware xcb_get_property_cookie_t cookie = Q_XCB_CALL(xcb_get_property(xcb_connection(), false, target, atom(QXcbAtom::XdndAware), XCB_GET_PROPERTY_TYPE_ANY, 0, 0)); xcb_get_property_reply_t *reply = xcb_get_property_reply(xcb_connection(), cookie, 0); bool aware = reply && reply->type != XCB_NONE; free(reply); if (aware) { DNDDEBUG << "Found XdndAware on " << target; break; } target = child; } if (!target || target == shapedPixmapWindow()->handle()->winId()) { DNDDEBUG << "need to find real window"; target = findRealWindow(globalPos, rootwin, 6, true); if (target == 0) target = findRealWindow(globalPos, rootwin, 6, false); DNDDEBUG << "real window found" << target; } } QXcbWindow *w = 0; if (target) { w = connection()->platformWindowFromId(target); if (w && (w->window()->type() == Qt::Desktop) /*&& !w->acceptDrops()*/) w = 0; } else { w = 0; target = rootwin; } xcb_window_t proxy_target = xdndProxy(connection(), target); if (!proxy_target) proxy_target = target; int target_version = 1; if (proxy_target) { xcb_get_property_cookie_t cookie = xcb_get_property(xcb_connection(), false, target, atom(QXcbAtom::XdndAware), XCB_GET_PROPERTY_TYPE_ANY, 0, 1); xcb_get_property_reply_t *reply = xcb_get_property_reply(xcb_connection(), cookie, 0); if (!reply || reply->type == XCB_NONE) target = 0; target_version = *(uint32_t *)xcb_get_property_value(reply); target_version = qMin(xdnd_version, target_version ? target_version : 1); free(reply); } if (target != current_target) { if (current_target) send_leave(); current_target = target;
421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
current_proxy_target = proxy_target; if (target) { int flags = target_version << 24; if (drag_types.size() > 3) flags |= 0x0001; xcb_client_message_event_t enter; enter.response_type = XCB_CLIENT_MESSAGE; enter.window = target; enter.format = 32; enter.type = atom(QXcbAtom::XdndEnter); enter.data.data32[0] = connection()->clipboard()->owner(); enter.data.data32[1] = flags; enter.data.data32[2] = drag_types.size()>0 ? drag_types.at(0) : 0; enter.data.data32[3] = drag_types.size()>1 ? drag_types.at(1) : 0; enter.data.data32[4] = drag_types.size()>2 ? drag_types.at(2) : 0; // provisionally set the rectangle to 5x5 pixels... source_sameanswer = QRect(globalPos.x() - 2, globalPos.y() -2 , 5, 5); DEBUG() << "sending Xdnd enter source=" << enter.data.data32[0]; if (w) handleEnter(w->window(), &enter); else if (target) xcb_send_event(xcb_connection(), false, proxy_target, XCB_EVENT_MASK_NO_EVENT, (const char *)&enter); waiting_for_status = false; } } if (waiting_for_status) return; if (target) { waiting_for_status = true; xcb_client_message_event_t move; move.response_type = XCB_CLIENT_MESSAGE; move.window = target; move.format = 32; move.type = atom(QXcbAtom::XdndPosition); move.data.data32[0] = connection()->clipboard()->owner(); move.data.data32[1] = 0; // flags move.data.data32[2] = (globalPos.x() << 16) + globalPos.y(); move.data.data32[3] = connection()->time(); move.data.data32[4] = toXdndAction(defaultAction(currentDrag()->supportedActions(), QGuiApplication::keyboardModifiers())); DEBUG() << "sending Xdnd position source=" << move.data.data32[0] << "target=" << move.window; source_time = connection()->time(); if (w) handle_xdnd_position(w->window(), &move); else xcb_send_event(xcb_connection(), false, proxy_target, XCB_EVENT_MASK_NO_EVENT, (const char *)&move); } } void QXcbDrag::drop(const QMouseEvent *event) { QBasicDrag::drop(event); if (!current_target) return; xcb_client_message_event_t drop; drop.response_type = XCB_CLIENT_MESSAGE; drop.window = current_target; drop.format = 32; drop.type = atom(QXcbAtom::XdndDrop); drop.data.data32[0] = connection()->clipboard()->owner(); drop.data.data32[1] = 0; // flags drop.data.data32[2] = connection()->time();
491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
drop.data.data32[3] = 0; drop.data.data32[4] = currentDrag()->supportedActions(); QXcbWindow *w = connection()->platformWindowFromId(current_proxy_target); if (w && (w->window()->type() == Qt::Desktop) /*&& !w->acceptDrops()*/) w = 0; Transaction t = { connection()->time(), current_target, current_proxy_target, (w ? w->window() : 0), // current_embeddig_widget, currentDrag(), QTime::currentTime() }; transactions.append(t); // timer is needed only for drops that came from other processes. if (!t.targetWindow && cleanup_timer == -1) { cleanup_timer = startTimer(XdndDropTransactionTimeout); } if (w) { handleDrop(w->window(), &drop); } else { xcb_send_event(xcb_connection(), false, current_proxy_target, XCB_EVENT_MASK_NO_EVENT, (const char *)&drop); } current_target = 0; current_proxy_target = 0; source_time = 0; // current_embedding_widget = 0; // #fixme resetDndState(false); } Qt::DropAction QXcbDrag::toDropAction(xcb_atom_t a) const { if (a == atom(QXcbAtom::XdndActionCopy) || a == 0) return Qt::CopyAction; if (a == atom(QXcbAtom::XdndActionLink)) return Qt::LinkAction; if (a == atom(QXcbAtom::XdndActionMove)) return Qt::MoveAction; return Qt::CopyAction; } xcb_atom_t QXcbDrag::toXdndAction(Qt::DropAction a) const { switch (a) { case Qt::CopyAction: return atom(QXcbAtom::XdndActionCopy); case Qt::LinkAction: return atom(QXcbAtom::XdndActionLink); case Qt::MoveAction: case Qt::TargetMoveAction: return atom(QXcbAtom::XdndActionMove); case Qt::IgnoreAction: return XCB_NONE; default: return atom(QXcbAtom::XdndActionCopy); } } int QXcbDrag::findTransactionByWindow(xcb_window_t window) { int at = -1; for (int i = 0; i < transactions.count(); ++i) {
561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630
const Transaction &t = transactions.at(i); if (t.target == window || t.proxy_target == window) { at = i; break; } } return at; } int QXcbDrag::findTransactionByTime(xcb_timestamp_t timestamp) { int at = -1; for (int i = 0; i < transactions.count(); ++i) { const Transaction &t = transactions.at(i); if (t.timestamp == timestamp) { at = i; break; } } return at; } #if 0 // find an ancestor with XdndAware on it static Window findXdndAwareParent(Window window) { Window target = 0; forever { // check if window has XdndAware Atom type = 0; int f; unsigned long n, a; unsigned char *data = 0; if (XGetWindowProperty(X11->display, window, ATOM(XdndAware), 0, 0, False, AnyPropertyType, &type, &f,&n,&a,&data) == Success) { if (data) XFree(data); if (type) { target = window; break; } } // try window's parent Window root; Window parent; Window *children; uint unused; if (!XQueryTree(X11->display, window, &root, &parent, &children, &unused)) break; if (children) XFree(children); if (window == root) break; window = parent; } return target; } // for embedding only static QWidget* current_embedding_widget = 0; static xcb_client_message_event_t last_enter_event; static bool checkEmbedded(QWidget* w, const XEvent* xe) { if (!w) return false;
631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700
if (current_embedding_widget != 0 && current_embedding_widget != w) { current_target = ((QExtraWidget*)current_embedding_widget)->extraData()->xDndProxy; current_proxy_target = current_target; qt_xdnd_send_leave(); current_target = 0; current_proxy_target = 0; current_embedding_widget = 0; } QWExtra* extra = ((QExtraWidget*)w)->extraData(); if (extra && extra->xDndProxy != 0) { if (current_embedding_widget != w) { last_enter_event.xany.window = extra->xDndProxy; XSendEvent(X11->display, extra->xDndProxy, False, NoEventMask, &last_enter_event); current_embedding_widget = w; } ((XEvent*)xe)->xany.window = extra->xDndProxy; XSendEvent(X11->display, extra->xDndProxy, False, NoEventMask, (XEvent*)xe); if (currentWindow != w) { currentWindow = w; } return true; } current_embedding_widget = 0; return false; } #endif void QXcbDrag::handleEnter(QWindow *window, const xcb_client_message_event_t *event) { Q_UNUSED(window); DEBUG() << "handleEnter" << window; xdnd_types.clear(); int version = (int)(event->data.data32[1] >> 24); if (version > xdnd_version) return; xdnd_dragsource = event->data.data32[0]; if (event->data.data32[1] & 1) { // get the types from XdndTypeList xcb_get_property_cookie_t cookie = xcb_get_property(xcb_connection(), false, xdnd_dragsource, atom(QXcbAtom::XdndTypelist), XCB_ATOM_ATOM, 0, xdnd_max_type); xcb_get_property_reply_t *reply = xcb_get_property_reply(xcb_connection(), cookie, 0); if (reply && reply->type != XCB_NONE && reply->format == 32) { int length = xcb_get_property_value_length(reply) / 4; if (length > xdnd_max_type) length = xdnd_max_type; xcb_atom_t *atoms = (xcb_atom_t *)xcb_get_property_value(reply); for (int i = 0; i < length; ++i) xdnd_types.append(atoms[i]); } free(reply); } else { // get the types from the message for(int i = 2; i < 5; i++) { if (event->data.data32[i]) xdnd_types.append(event->data.data32[i]); } } for(int i = 0; i < xdnd_types.length(); ++i)
701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770
DEBUG() << " " << connection()->atomName(xdnd_types.at(i)); } void QXcbDrag::handle_xdnd_position(QWindow *w, const xcb_client_message_event_t *e) { QPoint p((e->data.data32[2] & 0xffff0000) >> 16, e->data.data32[2] & 0x0000ffff); Q_ASSERT(w); QRect geometry = w->geometry(); p -= geometry.topLeft(); if (!w || (w->type() == Qt::Desktop)) return; if (e->data.data32[0] != xdnd_dragsource) { DEBUG("xdnd drag position from unexpected source (%x not %x)", e->data.data32[0], xdnd_dragsource); return; } currentPosition = p; currentWindow = w; // timestamp from the source if (e->data.data32[3] != XCB_NONE) { target_time = e->data.data32[3]; } QMimeData *dropData = 0; Qt::DropActions supported_actions = Qt::IgnoreAction; if (currentDrag()) { dropData = currentDrag()->mimeData(); supported_actions = currentDrag()->supportedActions(); } else { dropData = platformDropData(); supported_actions = Qt::DropActions(toDropAction(e->data.data32[4])); } QPlatformDragQtResponse qt_response = QWindowSystemInterface::handleDrag(w,dropData,p,supported_actions); QRect answerRect(p + geometry.topLeft(), QSize(1,1)); answerRect = qt_response.answerRect().translated(geometry.topLeft()).intersected(geometry); xcb_client_message_event_t response; response.response_type = XCB_CLIENT_MESSAGE; response.window = xdnd_dragsource; response.format = 32; response.type = atom(QXcbAtom::XdndStatus); response.data.data32[0] = xcb_window(w); response.data.data32[1] = qt_response.isAccepted(); // flags response.data.data32[2] = 0; // x, y response.data.data32[3] = 0; // w, h response.data.data32[4] = toXdndAction(qt_response.acceptedAction()); // action accepted_drop_action = qt_response.acceptedAction(); if (answerRect.left() < 0) answerRect.setLeft(0); if (answerRect.right() > 4096) answerRect.setRight(4096); if (answerRect.top() < 0) answerRect.setTop(0); if (answerRect.bottom() > 4096) answerRect.setBottom(4096); if (answerRect.width() < 0) answerRect.setWidth(0); if (answerRect.height() < 0) answerRect.setHeight(0); // reset target_time = XCB_CURRENT_TIME;
771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840
if (xdnd_dragsource == connection()->clipboard()->owner()) handle_xdnd_status(&response); else Q_XCB_CALL(xcb_send_event(xcb_connection(), false, xdnd_dragsource, XCB_EVENT_MASK_NO_EVENT, (const char *)&response)); } namespace { class ClientMessageScanner { public: ClientMessageScanner(xcb_atom_t a) : atom(a) {} xcb_atom_t atom; bool checkEvent(xcb_generic_event_t *event) const { if (!event) return false; if ((event->response_type & 0x7f) != XCB_CLIENT_MESSAGE) return false; return ((xcb_client_message_event_t *)event)->type == atom; } }; } void QXcbDrag::handlePosition(QWindow * w, const xcb_client_message_event_t *event) { xcb_client_message_event_t *lastEvent = const_cast<xcb_client_message_event_t *>(event); xcb_generic_event_t *nextEvent; ClientMessageScanner scanner(atom(QXcbAtom::XdndPosition)); while ((nextEvent = connection()->checkEvent(scanner))) { if (lastEvent != event) free(lastEvent); lastEvent = (xcb_client_message_event_t *)nextEvent; } handle_xdnd_position(w, lastEvent); if (lastEvent != event) free(lastEvent); } void QXcbDrag::handle_xdnd_status(const xcb_client_message_event_t *event) { DEBUG("xdndHandleStatus"); waiting_for_status = false; // ignore late status messages if (event->data.data32[0] && event->data.data32[0] != current_proxy_target) return; const bool dropPossible = event->data.data32[1]; setCanDrop(dropPossible); if (dropPossible) { accepted_drop_action = toDropAction(event->data.data32[4]); updateCursor(accepted_drop_action); } else { updateCursor(Qt::IgnoreAction); } if ((event->data.data32[1] & 2) == 0) { QPoint p((event->data.data32[2] & 0xffff0000) >> 16, event->data.data32[2] & 0x0000ffff); QSize s((event->data.data32[3] & 0xffff0000) >> 16, event->data.data32[3] & 0x0000ffff); source_sameanswer = QRect(p, s); } else { source_sameanswer = QRect(); } } void QXcbDrag::handleStatus(const xcb_client_message_event_t *event) { if (event->window != connection()->clipboard()->owner()) return;
841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910
xcb_client_message_event_t *lastEvent = const_cast<xcb_client_message_event_t *>(event); xcb_generic_event_t *nextEvent; ClientMessageScanner scanner(atom(QXcbAtom::XdndStatus)); while ((nextEvent = connection()->checkEvent(scanner))) { if (lastEvent != event) free(lastEvent); lastEvent = (xcb_client_message_event_t *)nextEvent; } handle_xdnd_status(lastEvent); if (lastEvent != event) free(lastEvent); DEBUG("xdndHandleStatus end"); } void QXcbDrag::handleLeave(QWindow *w, const xcb_client_message_event_t *event) { DEBUG("xdnd leave"); if (!currentWindow || w != currentWindow.data()) return; // sanity // ### // if (checkEmbedded(current_embedding_widget, event)) { // current_embedding_widget = 0; // currentWindow.clear(); // return; // } if (event->data.data32[0] != xdnd_dragsource) { // This often happens - leave other-process window quickly DEBUG("xdnd drag leave from unexpected source (%x not %x", event->data.data32[0], xdnd_dragsource); } QWindowSystemInterface::handleDrag(w,0,QPoint(),Qt::IgnoreAction); updateAction(Qt::IgnoreAction); xdnd_dragsource = 0; xdnd_types.clear(); currentWindow.clear(); } void QXcbDrag::send_leave() { if (!current_target) return; xcb_client_message_event_t leave; leave.response_type = XCB_CLIENT_MESSAGE; leave.window = current_target; leave.format = 32; leave.type = atom(QXcbAtom::XdndLeave); leave.data.data32[0] = connection()->clipboard()->owner(); leave.data.data32[1] = 0; // flags leave.data.data32[2] = 0; // x, y leave.data.data32[3] = 0; // w, h leave.data.data32[4] = 0; // just null QXcbWindow *w = connection()->platformWindowFromId(current_proxy_target); if (w && (w->window()->type() == Qt::Desktop) /*&& !w->acceptDrops()*/) w = 0; if (w) handleLeave(w->window(), (const xcb_client_message_event_t *)&leave); else xcb_send_event(xcb_connection(), false,current_proxy_target, XCB_EVENT_MASK_NO_EVENT, (const char *)&leave);
911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980
current_target = 0; current_proxy_target = 0; source_time = XCB_CURRENT_TIME; waiting_for_status = false; } void QXcbDrag::handleDrop(QWindow *, const xcb_client_message_event_t *event) { DEBUG("xdndHandleDrop"); if (!currentWindow) { xdnd_dragsource = 0; return; // sanity } const uint32_t *l = event->data.data32; DEBUG("xdnd drop"); if (l[0] != xdnd_dragsource) { DEBUG("xdnd drop from unexpected source (%x not %x", l[0], xdnd_dragsource); return; } // update the "user time" from the timestamp in the event. if (l[2] != 0) target_time = /*X11->userTime =*/ l[2]; Qt::DropActions supported_drop_actions; QMimeData *dropData = 0; if (currentDrag()) { dropData = currentDrag()->mimeData(); supported_drop_actions = Qt::DropActions(l[4]); } else { dropData = platformDropData(); supported_drop_actions = accepted_drop_action; } if (!dropData) return; // ### // int at = findXdndDropTransactionByTime(target_time); // if (at != -1) // dropData = QDragManager::dragPrivate(X11->dndDropTransactions.at(at).object)->data; // if we can't find it, then use the data in the drag manager QPlatformDropQtResponse response = QWindowSystemInterface::handleDrop(currentWindow.data(),dropData,currentPosition,supported_drop_actions); setExecutedDropAction(response.acceptedAction()); xcb_client_message_event_t finished; finished.response_type = XCB_CLIENT_MESSAGE; finished.window = xdnd_dragsource; finished.format = 32; finished.type = atom(QXcbAtom::XdndFinished); finished.data.data32[0] = currentWindow ? xcb_window(currentWindow.data()) : XCB_NONE; finished.data.data32[1] = response.isAccepted(); // flags finished.data.data32[2] = toXdndAction(response.acceptedAction()); Q_XCB_CALL(xcb_send_event(xcb_connection(), false, xdnd_dragsource, XCB_EVENT_MASK_NO_EVENT, (char *)&finished)); xdnd_dragsource = 0; currentWindow.clear(); waiting_for_status = false; // reset target_time = XCB_CURRENT_TIME; } void QXcbDrag::handleFinished(const xcb_client_message_event_t *event) {
981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050
DEBUG("xdndHandleFinished"); if (event->window != connection()->clipboard()->owner()) return; const unsigned long *l = (const unsigned long *)event->data.data32; DNDDEBUG << "xdndHandleFinished, l[0]" << l[0] << "current_target" << current_target << "qt_xdnd_current_proxy_targe" << current_proxy_target; if (l[0]) { int at = findTransactionByWindow(l[0]); if (at != -1) { Transaction t = transactions.takeAt(at); // QDragManager *manager = QDragManager::self(); // Window target = current_target; // Window proxy_target = current_proxy_target; // QWidget *embedding_widget = current_embedding_widget; // QDrag *currentObject = manager->object; // current_target = t.target; // current_proxy_target = t.proxy_target; // current_embedding_widget = t.embedding_widget; // manager->object = t.object; // if (!passive) // (void) checkEmbedded(currentWindow, xe); // current_embedding_widget = 0; // current_target = 0; // current_proxy_target = 0; if (t.drag) t.drag->deleteLater(); // current_target = target; // current_proxy_target = proxy_target; // current_embedding_widget = embedding_widget; // manager->object = currentObject; } else { qWarning("QXcbDrag::handleFinished - drop data has expired"); } } waiting_for_status = false; } void QXcbDrag::timerEvent(QTimerEvent* e) { if (e->timerId() == cleanup_timer) { bool stopTimer = true; for (int i = 0; i < transactions.count(); ++i) { const Transaction &t = transactions.at(i); if (t.targetWindow) { // dnd within the same process, don't delete, these are taken care of // in handleFinished() continue; } QTime currentTime = QTime::currentTime(); int delta = t.time.msecsTo(currentTime); if (delta > XdndDropTransactionTimeout) { /* delete transactions which are older than XdndDropTransactionTimeout. It could mean one of these: - client has crashed and as a result we have never received XdndFinished - showing dialog box on drop event where user's response takes more time than XdndDropTransactionTimeout (QTBUG-14493) - dnd takes unusually long time to process data */ t.drag->deleteLater(); transactions.removeAt(i--);
1051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120
} else { stopTimer = false; } } if (stopTimer && cleanup_timer != -1) { killTimer(cleanup_timer); cleanup_timer = -1; } } } void QXcbDrag::cancel() { DEBUG("QXcbDrag::cancel"); QBasicDrag::cancel(); if (current_target) send_leave(); } void QXcbDrag::handleSelectionRequest(const xcb_selection_request_event_t *event) { xcb_selection_notify_event_t notify; notify.response_type = XCB_SELECTION_NOTIFY; notify.requestor = event->requestor; notify.selection = event->selection; notify.target = XCB_NONE; notify.property = XCB_NONE; notify.time = event->time; // which transaction do we use? (note: -2 means use current currentDrag()) int at = -1; // figure out which data the requestor is really interested in if (currentDrag() && event->time == source_time) { // requestor wants the current drag data at = -2; } else { // if someone has requested data in response to XdndDrop, find the corresponding transaction. the // spec says to call xcb_convert_selection() using the timestamp from the XdndDrop at = findTransactionByTime(event->time); if (at == -1) { // no dice, perhaps the client was nice enough to use the same window id in // xcb_convert_selection() that we sent the XdndDrop event to. at = findTransactionByWindow(event->requestor); } // if (at == -1 && event->time == XCB_CURRENT_TIME) { // // previous Qt versions always requested the data on a child of the target window // // using CurrentTime... but it could be asking for either drop data or the current drag's data // Window target = findXdndAwareParent(event->requestor); // if (target) { // if (current_target && current_target == target) // at = -2; // else // at = findXdndDropTransactionByWindow(target); // } // } } QDrag *transactionDrag = 0; if (at >= 0) { transactionDrag = transactions.at(at).drag; } else if (at == -2) { transactionDrag = currentDrag(); } if (transactionDrag) { xcb_atom_t atomFormat = event->target; int dataFormat = 0;
1121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190
QByteArray data; if (QXcbMime::mimeDataForAtom(connection(), event->target, transactionDrag->mimeData(), &data, &atomFormat, &dataFormat)) { int dataSize = data.size() / (dataFormat / 8); xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, event->requestor, event->property, atomFormat, dataFormat, dataSize, (const void *)data.constData()); notify.property = event->property; notify.target = atomFormat; } } xcb_send_event(xcb_connection(), false, event->requestor, XCB_EVENT_MASK_NO_EVENT, (const char *)&notify); } bool QXcbDrag::dndEnable(QXcbWindow *w, bool on) { DNDDEBUG << "xdndEnable" << w << on; if (on) { QXcbWindow *xdnd_widget = 0; if ((w->window()->type() == Qt::Desktop)) { if (desktop_proxy) // *WE* already have one. return false; connection()->grabServer(); // As per Xdnd4, use XdndProxy xcb_window_t proxy_id = xdndProxy(connection(), w->xcb_window()); if (!proxy_id) { desktop_proxy = new QWindow; xdnd_widget = static_cast<QXcbWindow *>(desktop_proxy->handle()); proxy_id = xdnd_widget->xcb_window(); xcb_atom_t xdnd_proxy = atom(QXcbAtom::XdndProxy); xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, w->xcb_window(), xdnd_proxy, XCB_ATOM_WINDOW, 32, 1, &proxy_id); xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, proxy_id, xdnd_proxy, XCB_ATOM_WINDOW, 32, 1, &proxy_id); } connection()->ungrabServer(); } else { xdnd_widget = w; } if (xdnd_widget) { DNDDEBUG << "setting XdndAware for" << xdnd_widget << xdnd_widget->xcb_window(); xcb_atom_t atm = xdnd_version; xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, xdnd_widget->xcb_window(), atom(QXcbAtom::XdndAware), XCB_ATOM_ATOM, 32, 1, &atm); return true; } else { return false; } } else { if ((w->window()->type() == Qt::Desktop)) { xcb_delete_property(xcb_connection(), w->xcb_window(), atom(QXcbAtom::XdndProxy)); delete desktop_proxy; desktop_proxy = 0; } else { DNDDEBUG << "not deleting XDndAware"; } return true; } } QXcbDropData::QXcbDropData(QXcbDrag *d) : QXcbMime(), drag(d) { }
1191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251
QXcbDropData::~QXcbDropData() { } QVariant QXcbDropData::retrieveData_sys(const QString &mimetype, QVariant::Type requestedType) const { QByteArray mime = mimetype.toLatin1(); QVariant data = xdndObtainData(mime, requestedType); return data; } QVariant QXcbDropData::xdndObtainData(const QByteArray &format, QVariant::Type requestedType) const { QByteArray result; QXcbConnection *c = drag->connection(); QXcbWindow *xcb_window = c->platformWindowFromId(drag->xdnd_dragsource); if (xcb_window && drag->currentDrag() && xcb_window->window()->type() != Qt::Desktop) { QMimeData *data = drag->currentDrag()->mimeData(); if (data->hasFormat(QLatin1String(format))) result = data->data(QLatin1String(format)); return result; } QList<xcb_atom_t> atoms = drag->xdnd_types; QByteArray encoding; xcb_atom_t a = mimeAtomForFormat(c, QLatin1String(format), requestedType, atoms, &encoding); if (a == XCB_NONE) return result; if (c->clipboard()->getSelectionOwner(drag->atom(QXcbAtom::XdndSelection)) == XCB_NONE) return result; // should never happen? xcb_atom_t xdnd_selection = c->atom(QXcbAtom::XdndSelection); result = c->clipboard()->getSelection(xdnd_selection, a, xdnd_selection, drag->targetTime()); return mimeConvertToFormat(c, a, result, QLatin1String(format), requestedType, encoding); } bool QXcbDropData::hasFormat_sys(const QString &format) const { return formats().contains(format); } QStringList QXcbDropData::formats_sys() const { QStringList formats; for (int i = 0; i < drag->xdnd_types.size(); ++i) { QString f = mimeAtomToString(drag->connection(), drag->xdnd_types.at(i)); if (!formats.contains(f)) formats.append(f); } return formats; } #endif // QT_NO_DRAGANDDROP QT_END_NAMESPACE