| /**************************************************************************** |
| ** |
| ** 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 <xcb/xcb.h> |
| #include "qxcbconnection.h" |
| #include "qxcbclipboard.h" |
| #include "qxcbmime.h" |
| #include "qxcbwindow.h" |
| #include "qxcbscreen.h" |
| #include "qwindow.h" |
| #include "qxcbcursor.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 <private/qguiapplication_p.h> |
| #include <private/qshapedpixmapdndwindow_p.h> |
| #include <private/qsimpledrag_p.h> |
| #include <private/qhighdpiscaling_p.h> |
| |
| QT_BEGIN_NAMESPACE |
| |
| const int xdnd_version = 5; |
| |
| static inline xcb_window_t xcb_window(QPlatformWindow *w) |
| { |
| return static_cast<QXcbWindow *>(w)->xcb_window(); |
| } |
| |
| 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; |
| |
| 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<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)); |
| } |
| } |
| #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(); |
| } |
| |
| Qt::DropAction QXcbDrag::defaultAction(Qt::DropActions possibleActions, Qt::KeyboardModifiers modifiers) const |
| { |
| if (currentDrag() || drop_actions.isEmpty()) |
| return QBasicDrag::defaultAction(possibleActions, modifiers); |
| |
| return toDropAction(drop_actions.first()); |
| } |
| |
| void QXcbDrag::handlePropertyNotifyEvent(const xcb_property_notify_event_t *event) |
| { |
| if (event->window != xdnd_dragsource || event->atom != atom(QXcbAtom::XdndActionList)) |
| return; |
| |
| readActionList(); |
| } |
| |
| 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<QPlatformScreen*>(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; |
| } else { |
| 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(); |
| const auto supportedActions = currentDrag()->supportedActions(); |
| const auto requestedAction = defaultAction(supportedActions, mods); |
| move.data.data32[4] = toXdndAction(requestedAction); |
| |
| qCDebug(lcQpaXDnd) << "sending XdndPosition to target:" << target; |
| |
| source_time = connection()->time(); |
| |
| if (w) { |
| handle_xdnd_position(w, &move, b, mods); |
| } else { |
| setActionList(requestedAction, supportedActions); |
| 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; |
| } |
| |
| Qt::DropActions QXcbDrag::toDropActions(const QVector<xcb_atom_t> &atoms) const |
| { |
| Qt::DropActions actions; |
| for (const auto actionAtom : atoms) { |
| if (actionAtom != atom(QXcbAtom::XdndActionAsk)) |
| actions |= toDropAction(actionAtom); |
| } |
| return actions; |
| } |
| |
| 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); |
| } |
| } |
| |
| void QXcbDrag::readActionList() |
| { |
| drop_actions.clear(); |
| auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(), false, xdnd_dragsource, |
| atom(QXcbAtom::XdndActionList), XCB_ATOM_ATOM, |
| 0, 1024); |
| if (reply && reply->type != XCB_NONE && reply->format == 32) { |
| int length = xcb_get_property_value_length(reply.get()) / 4; |
| |
| xcb_atom_t *atoms = (xcb_atom_t *)xcb_get_property_value(reply.get()); |
| for (int i = 0; i < length; ++i) |
| drop_actions.append(atoms[i]); |
| } |
| } |
| |
| void QXcbDrag::setActionList(Qt::DropAction requestedAction, Qt::DropActions supportedActions) |
| { |
| #ifndef QT_NO_CLIPBOARD |
| QVector<xcb_atom_t> actions; |
| if (requestedAction != Qt::IgnoreAction) |
| actions.append(toXdndAction(requestedAction)); |
| |
| auto checkAppend = [this, requestedAction, supportedActions, &actions](Qt::DropAction action) { |
| if (requestedAction != action && supportedActions & action) |
| actions.append(toXdndAction(action)); |
| }; |
| |
| checkAppend(Qt::CopyAction); |
| checkAppend(Qt::MoveAction); |
| checkAppend(Qt::LinkAction); |
| |
| if (current_actions != actions) { |
| xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, connection()->clipboard()->owner(), |
| atom(QXcbAtom::XdndActionList), |
| XCB_ATOM_ATOM, 32, actions.size(), actions.constData()); |
| current_actions = actions; |
| } |
| #endif |
| } |
| |
| void QXcbDrag::startListeningForActionListChanges() |
| { |
| connection()->addWindowEventListener(xdnd_dragsource, this); |
| const uint32_t event_mask[] = { XCB_EVENT_MASK_PROPERTY_CHANGE }; |
| xcb_change_window_attributes(xcb_connection(), xdnd_dragsource, XCB_CW_EVENT_MASK, event_mask); |
| } |
| |
| void QXcbDrag::stopListeningForActionListChanges() |
| { |
| const uint32_t event_mask[] = { XCB_EVENT_MASK_NO_EVENT }; |
| xcb_change_window_attributes(xcb_connection(), xdnd_dragsource, XCB_CW_EVENT_MASK, event_mask); |
| connection()->removeWindowEventListener(xdnd_dragsource); |
| } |
| |
| 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]; |
| startListeningForActionListChanges(); |
| readActionList(); |
| |
| 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 = toDropActions(drop_actions); |
| if (e->data.data32[4] != atom(QXcbAtom::XdndActionAsk)) |
| 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<xcb_client_message_event_t *>(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<xcb_client_message_event_t *>(event); |
| ClientMessageScanner scanner(atom(QXcbAtom::XdndPosition)); |
| while (auto nextEvent = connection()->eventQueue()->peek(scanner)) { |
| if (lastEvent != event) |
| free(lastEvent); |
| lastEvent = reinterpret_cast<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) |
| { |
| // 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<xcb_client_message_event_t *>(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()) { |
| stopListeningForActionListChanges(); |
| 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); |
| } |
| |
| stopListeningForActionListChanges(); |
| |
| 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) { |
| stopListeningForActionListChanges(); |
| 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 | toDropActions(drop_actions); |
| } |
| |
| 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); |
| |
| stopListeningForActionListChanges(); |
| |
| 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<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) { |
| 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<QXcbWindow *>(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<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; |
| |
| #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 |