/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** 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 The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/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 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qxcbdrag.h" #include #include "qxcbconnection.h" #include "qxcbclipboard.h" #include "qxcbmime.h" #include "qxcbwindow.h" #include "qxcbscreen.h" #include "qwindow.h" #include "qxcbcursor.h" #include #include #include #include #include #include #include #include #include #include #include #include QT_BEGIN_NAMESPACE const int xdnd_version = 5; static inline xcb_window_t xcb_window(QPlatformWindow *w) { return static_cast(w)->xcb_window(); } static inline xcb_window_t xcb_window(QWindow *w) { return static_cast(w->handle())->xcb_window(); } static xcb_window_t xdndProxy(QXcbConnection *c, xcb_window_t w) { xcb_window_t proxy = XCB_NONE; auto reply = Q_XCB_REPLY(xcb_get_property, c->xcb_connection(), false, w, c->atom(QXcbAtom::XdndProxy), XCB_ATOM_WINDOW, 0, 1); if (reply && reply->type == XCB_ATOM_WINDOW) proxy = *((xcb_window_t *)xcb_get_property_value(reply.get())); if (proxy == XCB_NONE) return proxy; // exists and is real? reply = Q_XCB_REPLY(xcb_get_property, c->xcb_connection(), false, proxy, c->atom(QXcbAtom::XdndProxy), XCB_ATOM_WINDOW, 0, 1); if (reply && reply->type == XCB_ATOM_WINDOW) { xcb_window_t p = *((xcb_window_t *)xcb_get_property_value(reply.get())); if (proxy != p) proxy = 0; } else { proxy = 0; } return proxy; } class QXcbDropData : public QXcbMime { public: QXcbDropData(QXcbDrag *d); ~QXcbDropData(); protected: bool hasFormat_sys(const QString &mimeType) const override; QStringList formats_sys() const override; QVariant retrieveData_sys(const QString &mimeType, QVariant::Type type) const override; QVariant xdndObtainData(const QByteArray &format, QMetaType::Type requestedType) const; QXcbDrag *drag; }; QXcbDrag::QXcbDrag(QXcbConnection *c) : QXcbObject(c) { m_dropData = new QXcbDropData(this); init(); cleanup_timer = -1; } QXcbDrag::~QXcbDrag() { delete m_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; QXcbCursor::queryPointer(connection(), ¤t_virtual_desktop, nullptr); drag_types.clear(); //current_embedding_widget = 0; dropped = false; canceled = false; source_sameanswer = QRect(); } bool QXcbDrag::eventFilter(QObject *o, QEvent *e) { /* We are setting a mouse grab on the QShapedPixmapWindow in order not to * lose the grab when the virtual desktop changes, but * QBasicDrag::eventFilter() expects the events to be coming from the * window where the drag was started. */ if (initiatorWindow && o == shapedPixmapWindow()) o = initiatorWindow.data(); return QBasicDrag::eventFilter(o, e); } void QXcbDrag::startDrag() { init(); #ifndef QT_NO_CLIPBOARD qCDebug(lcQpaXDnd) << "starting drag where source:" << connection()->clipboard()->owner(); xcb_set_selection_owner(xcb_connection(), connection()->clipboard()->owner(), atom(QXcbAtom::XdndSelection), connection()->time()); #endif QStringList fmts = QXcbMime::formatsHelper(drag()->mimeData()); for (int i = 0; i < fmts.size(); ++i) { QVector 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)); } } #ifndef QT_NO_CLIPBOARD 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()); #endif setUseCompositing(current_virtual_desktop->compositingActive()); setScreen(current_virtual_desktop->screens().constFirst()->screen()); initiatorWindow = QGuiApplicationPrivate::currentMouseWindow; QBasicDrag::startDrag(); if (connection()->mouseGrabber() == nullptr) shapedPixmapWindow()->setMouseGrabEnabled(true); auto nativePixelPos = QHighDpi::toNativePixels(QCursor::pos(), initiatorWindow.data()); move(nativePixelPos, QGuiApplication::mouseButtons(), QGuiApplication::keyboardModifiers()); } void QXcbDrag::endDrag() { QBasicDrag::endDrag(); if (!dropped && !canceled && canDrop()) { // Set executed drop action when dropping outside application. setExecutedDropAction(accepted_drop_action); } initiatorWindow.clear(); } static bool windowInteractsWithPosition(xcb_connection_t *connection, const QPoint & pos, xcb_window_t w, xcb_shape_sk_t shapeType) { bool interacts = false; auto reply = Q_XCB_REPLY(xcb_shape_get_rectangles, connection, w, shapeType); if (reply) { xcb_rectangle_t *rectangles = xcb_shape_get_rectangles_rectangles(reply.get()); if (rectangles) { const int nRectangles = xcb_shape_get_rectangles_rectangles_length(reply.get()); for (int i = 0; !interacts && i < nRectangles; ++i) { interacts = QRect(rectangles[i].x, rectangles[i].y, rectangles[i].width, rectangles[i].height).contains(pos); } } } 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) { auto reply = Q_XCB_REPLY(xcb_get_window_attributes, xcb_connection(), w); if (!reply) return 0; if (reply->map_state != XCB_MAP_STATE_VIEWABLE) return 0; auto greply = Q_XCB_REPLY(xcb_get_geometry, xcb_connection(), w); if (!greply) return 0; QRect windowRect(greply->x, greply->y, greply->width, greply->height); if (windowRect.contains(pos)) { bool windowContainsMouse = !ignoreNonXdndAwareWindows; { auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(), false, w, connection()->atom(QXcbAtom::XdndAware), XCB_GET_PROPERTY_TYPE_ANY, 0, 0); bool isAware = reply && reply->type != XCB_NONE; if (isAware) { const QPoint relPos = pos - windowRect.topLeft(); // 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; } } auto reply = Q_XCB_REPLY(xcb_query_tree, xcb_connection(), w); if (!reply) return 0; int nc = xcb_query_tree_children_length(reply.get()); xcb_window_t *c = xcb_query_tree_children(reply.get()); xcb_window_t r = 0; for (uint i = nc; !r && i--;) r = findRealWindow(pos - windowRect.topLeft(), c[i], md-1, ignoreNonXdndAwareWindows); 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; } bool QXcbDrag::findXdndAwareTarget(const QPoint &globalPos, xcb_window_t *target_out) { xcb_window_t rootwin = current_virtual_desktop->root(); auto translate = Q_XCB_REPLY(xcb_translate_coordinates, xcb_connection(), rootwin, rootwin, globalPos.x(), globalPos.y()); if (!translate) return false; xcb_window_t target = translate->child; int lx = translate->dst_x; int ly = translate->dst_y; if (target && target != rootwin) { xcb_window_t src = rootwin; while (target != 0) { qCDebug(lcQpaXDnd) << "checking target for XdndAware" << target; auto translate = Q_XCB_REPLY(xcb_translate_coordinates, xcb_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; auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(), false, target, atom(QXcbAtom::XdndAware), XCB_GET_PROPERTY_TYPE_ANY, 0, 0); bool aware = reply && reply->type != XCB_NONE; if (aware) { qCDebug(lcQpaXDnd) << "found XdndAware on" << target; break; } target = child; } if (!target || target == shapedPixmapWindow()->handle()->winId()) { qCDebug(lcQpaXDnd) << "need to find real window"; target = findRealWindow(globalPos, rootwin, 6, true); if (target == 0) target = findRealWindow(globalPos, rootwin, 6, false); qCDebug(lcQpaXDnd) << "real window found" << target; } } *target_out = target; return true; } void QXcbDrag::move(const QPoint &globalPos, Qt::MouseButtons b, Qt::KeyboardModifiers mods) { // currentDrag() might be deleted while 'drag' is progressing if (!currentDrag()) { cancel(); return; } // The source sends XdndEnter and XdndPosition to the target. if (source_sameanswer.contains(globalPos) && source_sameanswer.isValid()) return; QXcbVirtualDesktop *virtualDesktop = nullptr; QPoint cursorPos; QXcbCursor::queryPointer(connection(), &virtualDesktop, &cursorPos); QXcbScreen *screen = virtualDesktop->screenAt(cursorPos); QPoint deviceIndependentPos = QHighDpiScaling::mapPositionFromNative(globalPos, screen); if (virtualDesktop != current_virtual_desktop) { setUseCompositing(virtualDesktop->compositingActive()); recreateShapedPixmapWindow(static_cast(screen)->screen(), deviceIndependentPos); if (connection()->mouseGrabber() == nullptr) shapedPixmapWindow()->setMouseGrabEnabled(true); current_virtual_desktop = virtualDesktop; } else { QBasicDrag::moveShapedPixmapWindow(deviceIndependentPos); } xcb_window_t target; if (!findXdndAwareTarget(globalPos, &target)) return; QXcbWindow *w = nullptr; if (target) { w = connection()->platformWindowFromId(target); if (w && (w->window()->type() == Qt::Desktop) /*&& !w->acceptDrops()*/) w = nullptr; } else { w = nullptr; target = current_virtual_desktop->root(); } xcb_window_t proxy_target = xdndProxy(connection(), target); if (!proxy_target) proxy_target = target; int target_version = 1; if (proxy_target) { auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(), false, proxy_target, atom(QXcbAtom::XdndAware), XCB_GET_PROPERTY_TYPE_ANY, 0, 1); if (!reply || reply->type == XCB_NONE) target = 0; target_version = *(uint32_t *)xcb_get_property_value(reply.get()); target_version = qMin(xdnd_version, target_version ? target_version : 1); } if (target != current_target) { if (current_target) send_leave(); current_target = target; 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.sequence = 0; enter.window = target; enter.format = 32; enter.type = atom(QXcbAtom::XdndEnter); #ifndef QT_NO_CLIPBOARD enter.data.data32[0] = connection()->clipboard()->owner(); #else enter.data.data32[0] = 0; #endif 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); qCDebug(lcQpaXDnd) << "sending XdndEnter to target:" << target; if (w) handleEnter(w, &enter, current_proxy_target); 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; // The source sends a ClientMessage of type XdndPosition. This tells the target the // position of the mouse and the action that the user requested. xcb_client_message_event_t move; move.response_type = XCB_CLIENT_MESSAGE; move.sequence = 0; move.window = target; move.format = 32; move.type = atom(QXcbAtom::XdndPosition); #ifndef QT_NO_CLIPBOARD move.data.data32[0] = connection()->clipboard()->owner(); #else move.data.data32[0] = 0; #endif 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(), mods)); qCDebug(lcQpaXDnd) << "sending XdndPosition to target:" << target; source_time = connection()->time(); if (w) handle_xdnd_position(w, &move, b, mods); else xcb_send_event(xcb_connection(), false, proxy_target, XCB_EVENT_MASK_NO_EVENT, (const char *)&move); } static const bool isUnity = qgetenv("XDG_CURRENT_DESKTOP").toLower() == "unity"; if (isUnity && xdndCollectionWindow == XCB_NONE) { QString name = QXcbWindow::windowTitle(connection(), target); if (name == QStringLiteral("XdndCollectionWindowImp")) xdndCollectionWindow = target; } if (target == xdndCollectionWindow) { setCanDrop(false); updateCursor(Qt::IgnoreAction); } } void QXcbDrag::drop(const QPoint &globalPos, Qt::MouseButtons b, Qt::KeyboardModifiers mods) { // XdndDrop is sent from source to target to complete the drop. QBasicDrag::drop(globalPos, b, mods); if (!current_target) return; xcb_client_message_event_t drop; drop.response_type = XCB_CLIENT_MESSAGE; drop.sequence = 0; drop.window = current_target; drop.format = 32; drop.type = atom(QXcbAtom::XdndDrop); #ifndef QT_NO_CLIPBOARD drop.data.data32[0] = connection()->clipboard()->owner(); #else drop.data.data32[0] = 0; #endif drop.data.data32[1] = 0; // flags drop.data.data32[2] = connection()->time(); 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 = nullptr; Transaction t = { connection()->time(), current_target, current_proxy_target, w, // 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); } qCDebug(lcQpaXDnd) << "sending drop to target:" << current_target; if (w) { handleDrop(w, &drop, b, mods); } else { xcb_send_event(xcb_connection(), false, current_proxy_target, XCB_EVENT_MASK_NO_EVENT, (const char *)&drop); } } 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) { 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 // 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; 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(QPlatformWindow *, const xcb_client_message_event_t *event, xcb_window_t proxy) { // The target receives XdndEnter. qCDebug(lcQpaXDnd) << "target:" << event->window << "received XdndEnter"; xdnd_types.clear(); int version = (int)(event->data.data32[1] >> 24); if (version > xdnd_version) return; xdnd_dragsource = event->data.data32[0]; if (!proxy) proxy = xdndProxy(connection(), xdnd_dragsource); current_proxy_target = proxy ? proxy : xdnd_dragsource; if (event->data.data32[1] & 1) { // get the types from XdndTypeList auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(), false, xdnd_dragsource, atom(QXcbAtom::XdndTypelist), XCB_ATOM_ATOM, 0, xdnd_max_type); if (reply && reply->type != XCB_NONE && reply->format == 32) { int length = xcb_get_property_value_length(reply.get()) / 4; if (length > xdnd_max_type) length = xdnd_max_type; xcb_atom_t *atoms = (xcb_atom_t *)xcb_get_property_value(reply.get()); xdnd_types.reserve(length); for (int i = 0; i < length; ++i) xdnd_types.append(atoms[i]); } } 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) qCDebug(lcQpaXDnd) << " " << connection()->atomName(xdnd_types.at(i)); } void QXcbDrag::handle_xdnd_position(QPlatformWindow *w, const xcb_client_message_event_t *e, Qt::MouseButtons b, Qt::KeyboardModifiers mods) { // The target receives XdndPosition. The target window must determine which widget the mouse // is in and ask it whether or not it will accept the drop. qCDebug(lcQpaXDnd) << "target:" << e->window << "received XdndPosition"; 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->window() || (w->window()->type() == Qt::Desktop)) return; if (Q_UNLIKELY(e->data.data32[0] != xdnd_dragsource)) { qCDebug(lcQpaXDnd, "xdnd drag position from unexpected source (%x not %x)", e->data.data32[0], xdnd_dragsource); return; } currentPosition = p; currentWindow = w->window(); // timestamp from the source if (e->data.data32[3] != XCB_NONE) { target_time = e->data.data32[3]; } QMimeData *dropData = nullptr; Qt::DropActions supported_actions = Qt::IgnoreAction; if (currentDrag()) { dropData = currentDrag()->mimeData(); supported_actions = currentDrag()->supportedActions(); } else { dropData = m_dropData; supported_actions = Qt::DropActions(toDropAction(e->data.data32[4])); } auto buttons = currentDrag() ? b : connection()->queryMouseButtons(); auto modifiers = currentDrag() ? mods : connection()->queryKeyboardModifiers(); QPlatformDragQtResponse qt_response = QWindowSystemInterface::handleDrag( w->window(), dropData, p, supported_actions, buttons, modifiers); // ### FIXME ? - answerRect appears to be unused. QRect answerRect(p + geometry.topLeft(), QSize(1,1)); answerRect = qt_response.answerRect().translated(geometry.topLeft()).intersected(geometry); // The target sends a ClientMessage of type XdndStatus. This tells the source whether or not // it will accept the drop, and, if so, what action will be taken. It also includes a rectangle // that means "don't send another XdndPosition message until the mouse moves out of here". xcb_client_message_event_t response; response.response_type = XCB_CLIENT_MESSAGE; response.sequence = 0; 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; qCDebug(lcQpaXDnd) << "sending XdndStatus to source:" << xdnd_dragsource; #ifndef QT_NO_CLIPBOARD if (xdnd_dragsource == connection()->clipboard()->owner()) handle_xdnd_status(&response); else #endif xcb_send_event(xcb_connection(), false, current_proxy_target, XCB_EVENT_MASK_NO_EVENT, (const char *)&response); } namespace { class ClientMessageScanner { public: ClientMessageScanner(xcb_atom_t a) : atom(a) {} xcb_atom_t atom; bool operator() (xcb_generic_event_t *event, int type) const { if (type != XCB_CLIENT_MESSAGE) return false; auto clientMessage = reinterpret_cast(event); return clientMessage->type == atom; } }; } void QXcbDrag::handlePosition(QPlatformWindow * w, const xcb_client_message_event_t *event) { xcb_client_message_event_t *lastEvent = const_cast(event); ClientMessageScanner scanner(atom(QXcbAtom::XdndPosition)); while (auto nextEvent = connection()->eventQueue()->peek(scanner)) { if (lastEvent != event) free(lastEvent); lastEvent = reinterpret_cast(nextEvent); } handle_xdnd_position(w, lastEvent); if (lastEvent != event) free(lastEvent); } void QXcbDrag::handle_xdnd_status(const xcb_client_message_event_t *event) { // The source receives XdndStatus. It can use the action to change the cursor to indicate // whether or not the user's requested action will be performed. qCDebug(lcQpaXDnd) << "source:" << event->window << "received XdndStatus"; waiting_for_status = false; // ignore late status messages if (event->data.data32[0] && event->data.data32[0] != current_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 ( #ifndef QT_NO_CLIPBOARD event->window != connection()->clipboard()->owner() || #endif !drag()) return; xcb_client_message_event_t *lastEvent = const_cast(event); xcb_generic_event_t *nextEvent; ClientMessageScanner scanner(atom(QXcbAtom::XdndStatus)); while ((nextEvent = connection()->eventQueue()->peek(scanner))) { if (lastEvent != event) free(lastEvent); lastEvent = (xcb_client_message_event_t *)nextEvent; } handle_xdnd_status(lastEvent); if (lastEvent != event) free(lastEvent); } void QXcbDrag::handleLeave(QPlatformWindow *w, const xcb_client_message_event_t *event) { // If the target receives XdndLeave, it frees any cached data and forgets the whole incident. qCDebug(lcQpaXDnd) << "target:" << event->window << "received XdndLeave"; if (!currentWindow || w != currentWindow.data()->handle()) 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 qCDebug(lcQpaXDnd, "xdnd drag leave from unexpected source (%x not %x", event->data.data32[0], xdnd_dragsource); } QWindowSystemInterface::handleDrag(w->window(), nullptr, QPoint(), Qt::IgnoreAction, { }, { }); } void QXcbDrag::send_leave() { // XdndLeave is sent from the source to the target to cancel the drop. if (!current_target) return; xcb_client_message_event_t leave; leave.response_type = XCB_CLIENT_MESSAGE; leave.sequence = 0; leave.window = current_target; leave.format = 32; leave.type = atom(QXcbAtom::XdndLeave); #ifndef QT_NO_CLIPBOARD leave.data.data32[0] = connection()->clipboard()->owner(); #else leave.data.data32[0] = 0; #endif 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 = nullptr; qCDebug(lcQpaXDnd) << "sending XdndLeave to target:" << current_target; if (w) handleLeave(w, (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); } void QXcbDrag::handleDrop(QPlatformWindow *, const xcb_client_message_event_t *event, Qt::MouseButtons b, Qt::KeyboardModifiers mods) { // Target receives XdndDrop. Once it is finished processing the drop, it sends XdndFinished. qCDebug(lcQpaXDnd) << "target:" << event->window << "received XdndDrop"; if (!currentWindow) { xdnd_dragsource = 0; return; // sanity } const uint32_t *l = event->data.data32; if (l[0] != xdnd_dragsource) { qCDebug(lcQpaXDnd, "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 = l[2]; Qt::DropActions supported_drop_actions; QMimeData *dropData = nullptr; if (currentDrag()) { dropData = currentDrag()->mimeData(); supported_drop_actions = Qt::DropActions(l[4]); } else { dropData = m_dropData; 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 auto buttons = currentDrag() ? b : connection()->queryMouseButtons(); auto modifiers = currentDrag() ? mods : connection()->queryKeyboardModifiers(); QPlatformDropQtResponse response = QWindowSystemInterface::handleDrop( currentWindow.data(), dropData, currentPosition, supported_drop_actions, buttons, modifiers); setExecutedDropAction(response.acceptedAction()); xcb_client_message_event_t finished; finished.response_type = XCB_CLIENT_MESSAGE; finished.sequence = 0; 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()); qCDebug(lcQpaXDnd) << "sending XdndFinished to source:" << xdnd_dragsource; xcb_send_event(xcb_connection(), false, current_proxy_target, XCB_EVENT_MASK_NO_EVENT, (char *)&finished); dropped = true; } void QXcbDrag::handleFinished(const xcb_client_message_event_t *event) { // Source receives XdndFinished when target is done processing the drop data. qCDebug(lcQpaXDnd) << "source:" << event->window << "received XdndFinished"; #ifndef QT_NO_CLIPBOARD if (event->window != connection()->clipboard()->owner()) return; #endif const unsigned long *l = (const unsigned long *)event->data.data32; if (l[0]) { int at = findTransactionByWindow(l[0]); if (at != -1) { Transaction t = transactions.takeAt(at); if (t.drag) t.drag->deleteLater(); // 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; // 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 */ if (t.drag) t.drag->deleteLater(); transactions.removeAt(i--); } else { stopTimer = false; } } if (stopTimer && cleanup_timer != -1) { killTimer(cleanup_timer); cleanup_timer = -1; } } } void QXcbDrag::cancel() { qCDebug(lcQpaXDnd) << "dnd was canceled"; QBasicDrag::cancel(); if (current_target) send_leave(); // remove canceled object if (currentDrag()) currentDrag()->deleteLater(); canceled = true; } static xcb_window_t findXdndAwareParent(QXcbConnection *c, xcb_window_t window) { xcb_window_t target = 0; forever { // check if window has XdndAware auto gpReply = Q_XCB_REPLY(xcb_get_property, c->xcb_connection(), false, window, c->atom(QXcbAtom::XdndAware), XCB_GET_PROPERTY_TYPE_ANY, 0, 0); bool aware = gpReply && gpReply->type != XCB_NONE; if (aware) { target = window; break; } // try window's parent auto qtReply = Q_XCB_REPLY_UNCHECKED(xcb_query_tree, c->xcb_connection(), window); if (!qtReply) break; xcb_window_t root = qtReply->root; xcb_window_t parent = qtReply->parent; if (window == root) break; window = parent; } return target; } void QXcbDrag::handleSelectionRequest(const xcb_selection_request_event_t *event) { qCDebug(lcQpaXDnd) << "handle selection request from target:" << event->requestor; q_padded_xcb_event 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) { xcb_window_t target = findXdndAwareParent(connection(), event->requestor); if (target) { if (event->time == XCB_CURRENT_TIME && current_target == target) at = -2; else at = findTransactionByWindow(target); } } } QDrag *transactionDrag = nullptr; 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; 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_window_t proxy_target = xdndProxy(connection(), event->requestor); if (!proxy_target) proxy_target = event->requestor; xcb_send_event(xcb_connection(), false, proxy_target, XCB_EVENT_MASK_NO_EVENT, (const char *)¬ify); } bool QXcbDrag::dndEnable(QXcbWindow *w, bool on) { // Windows announce that they support the XDND protocol by creating a window property XdndAware. if (on) { QXcbWindow *window = nullptr; if (w->window()->type() == Qt::Desktop) { if (desktop_proxy) // *WE* already have one. return false; QXcbConnectionGrabber grabber(connection()); // As per Xdnd4, use XdndProxy xcb_window_t proxy_id = xdndProxy(connection(), w->xcb_window()); if (!proxy_id) { desktop_proxy = new QWindow; window = static_cast(desktop_proxy->handle()); proxy_id = window->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); } } else { window = w; } if (window) { qCDebug(lcQpaXDnd) << "setting XdndAware for" << window->xcb_window(); xcb_atom_t atm = xdnd_version; xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, window->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 = nullptr; } else { qCDebug(lcQpaXDnd) << "not deleting XDndAware"; } return true; } } bool QXcbDrag::ownsDragObject() const { return true; } QXcbDropData::QXcbDropData(QXcbDrag *d) : QXcbMime(), drag(d) { } QXcbDropData::~QXcbDropData() { } QVariant QXcbDropData::retrieveData_sys(const QString &mimetype, QVariant::Type requestedType) const { QByteArray mime = mimetype.toLatin1(); QVariant data = xdndObtainData(mime, QMetaType::Type(requestedType)); return data; } QVariant QXcbDropData::xdndObtainData(const QByteArray &format, QMetaType::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; } QVector atoms = drag->xdnd_types; QByteArray encoding; xcb_atom_t a = mimeAtomForFormat(c, QLatin1String(format), requestedType, atoms, &encoding); if (a == XCB_NONE) return result; #ifndef QT_NO_CLIPBOARD 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()); #endif 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; } QT_END_NAMESPACE