1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtGui module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qxcbdrag.h"
41#include <xcb/xcb.h>
42#include "qxcbconnection.h"
43#include "qxcbclipboard.h"
44#include "qxcbmime.h"
45#include "qxcbwindow.h"
46#include "qxcbscreen.h"
47#include "qwindow.h"
48#include "qxcbcursor.h"
49#include <private/qdnd_p.h>
50#include <qdebug.h>
51#include <qevent.h>
52#include <qguiapplication.h>
53#include <qrect.h>
54#include <qpainter.h>
55#include <qtimer.h>
56
57#include <qpa/qwindowsysteminterface.h>
58
59#include <private/qguiapplication_p.h>
60#include <private/qshapedpixmapdndwindow_p.h>
61#include <private/qsimpledrag_p.h>
62#include <private/qhighdpiscaling_p.h>
63
64QT_BEGIN_NAMESPACE
65
66const int xdnd_version = 5;
67
68static inline xcb_window_t xcb_window(QPlatformWindow *w)
69{
70 return static_cast<QXcbWindow *>(w)->xcb_window();
71}
72
73static inline xcb_window_t xcb_window(QWindow *w)
74{
75 return static_cast<QXcbWindow *>(w->handle())->xcb_window();
76}
77
78static xcb_window_t xdndProxy(QXcbConnection *c, xcb_window_t w)
79{
80 xcb_window_t proxy = XCB_NONE;
81
82 auto reply = Q_XCB_REPLY(xcb_get_property, c->xcb_connection(),
83 false, w, c->atom(QXcbAtom::XdndProxy), XCB_ATOM_WINDOW, 0, 1);
84
85 if (reply && reply->type == XCB_ATOM_WINDOW)
86 proxy = *((xcb_window_t *)xcb_get_property_value(R: reply.get()));
87
88 if (proxy == XCB_NONE)
89 return proxy;
90
91 // exists and is real?
92 reply = Q_XCB_REPLY(xcb_get_property, c->xcb_connection(),
93 false, proxy, c->atom(QXcbAtom::XdndProxy), XCB_ATOM_WINDOW, 0, 1);
94
95 if (reply && reply->type == XCB_ATOM_WINDOW) {
96 xcb_window_t p = *((xcb_window_t *)xcb_get_property_value(R: reply.get()));
97 if (proxy != p)
98 proxy = 0;
99 } else {
100 proxy = 0;
101 }
102
103 return proxy;
104}
105
106class QXcbDropData : public QXcbMime
107{
108public:
109 QXcbDropData(QXcbDrag *d);
110 ~QXcbDropData();
111
112protected:
113 bool hasFormat_sys(const QString &mimeType) const override;
114 QStringList formats_sys() const override;
115 QVariant retrieveData_sys(const QString &mimeType, QVariant::Type type) const override;
116
117 QVariant xdndObtainData(const QByteArray &format, QMetaType::Type requestedType) const;
118
119 QXcbDrag *drag;
120};
121
122
123QXcbDrag::QXcbDrag(QXcbConnection *c) : QXcbObject(c)
124{
125 m_dropData = new QXcbDropData(this);
126
127 init();
128 cleanup_timer = -1;
129}
130
131QXcbDrag::~QXcbDrag()
132{
133 delete m_dropData;
134}
135
136void QXcbDrag::init()
137{
138 currentWindow.clear();
139
140 accepted_drop_action = Qt::IgnoreAction;
141
142 xdnd_dragsource = XCB_NONE;
143
144 waiting_for_status = false;
145 current_target = XCB_NONE;
146 current_proxy_target = XCB_NONE;
147
148 source_time = XCB_CURRENT_TIME;
149 target_time = XCB_CURRENT_TIME;
150
151 QXcbCursor::queryPointer(c: connection(), virtualDesktop: &current_virtual_desktop, pos: nullptr);
152 drag_types.clear();
153
154 //current_embedding_widget = 0;
155
156 dropped = false;
157 canceled = false;
158
159 source_sameanswer = QRect();
160}
161
162bool QXcbDrag::eventFilter(QObject *o, QEvent *e)
163{
164 /* We are setting a mouse grab on the QShapedPixmapWindow in order not to
165 * lose the grab when the virtual desktop changes, but
166 * QBasicDrag::eventFilter() expects the events to be coming from the
167 * window where the drag was started. */
168 if (initiatorWindow && o == shapedPixmapWindow())
169 o = initiatorWindow.data();
170 return QBasicDrag::eventFilter(o, e);
171}
172
173void QXcbDrag::startDrag()
174{
175 init();
176
177#ifndef QT_NO_CLIPBOARD
178 qCDebug(lcQpaXDnd) << "starting drag where source:" << connection()->clipboard()->owner();
179 xcb_set_selection_owner(c: xcb_connection(), owner: connection()->clipboard()->owner(),
180 selection: atom(atom: QXcbAtom::XdndSelection), time: connection()->time());
181#endif
182
183 QStringList fmts = QXcbMime::formatsHelper(data: drag()->mimeData());
184 for (int i = 0; i < fmts.size(); ++i) {
185 QVector<xcb_atom_t> atoms = QXcbMime::mimeAtomsForFormat(connection: connection(), format: fmts.at(i));
186 for (int j = 0; j < atoms.size(); ++j) {
187 if (!drag_types.contains(t: atoms.at(i: j)))
188 drag_types.append(t: atoms.at(i: j));
189 }
190 }
191#ifndef QT_NO_CLIPBOARD
192 if (drag_types.size() > 3)
193 xcb_change_property(c: xcb_connection(), mode: XCB_PROP_MODE_REPLACE, window: connection()->clipboard()->owner(),
194 property: atom(atom: QXcbAtom::XdndTypelist),
195 type: XCB_ATOM_ATOM, format: 32, data_len: drag_types.size(), data: (const void *)drag_types.constData());
196#endif
197
198 setUseCompositing(current_virtual_desktop->compositingActive());
199 setScreen(current_virtual_desktop->screens().constFirst()->screen());
200 initiatorWindow = QGuiApplicationPrivate::currentMouseWindow;
201 QBasicDrag::startDrag();
202 if (connection()->mouseGrabber() == nullptr)
203 shapedPixmapWindow()->setMouseGrabEnabled(true);
204
205 auto nativePixelPos = QHighDpi::toNativePixels(value: QCursor::pos(), context: initiatorWindow.data());
206 move(globalPos: nativePixelPos, b: QGuiApplication::mouseButtons(), mods: QGuiApplication::keyboardModifiers());
207}
208
209void QXcbDrag::endDrag()
210{
211 QBasicDrag::endDrag();
212 if (!dropped && !canceled && canDrop()) {
213 // Set executed drop action when dropping outside application.
214 setExecutedDropAction(accepted_drop_action);
215 }
216 initiatorWindow.clear();
217}
218
219Qt::DropAction QXcbDrag::defaultAction(Qt::DropActions possibleActions, Qt::KeyboardModifiers modifiers) const
220{
221 if (currentDrag() || drop_actions.isEmpty())
222 return QBasicDrag::defaultAction(possibleActions, modifiers);
223
224 return toDropAction(atom: drop_actions.first());
225}
226
227void QXcbDrag::handlePropertyNotifyEvent(const xcb_property_notify_event_t *event)
228{
229 if (event->window != xdnd_dragsource || event->atom != atom(atom: QXcbAtom::XdndActionList))
230 return;
231
232 readActionList();
233}
234
235static
236bool windowInteractsWithPosition(xcb_connection_t *connection, const QPoint & pos, xcb_window_t w, xcb_shape_sk_t shapeType)
237{
238 bool interacts = false;
239 auto reply = Q_XCB_REPLY(xcb_shape_get_rectangles, connection, w, shapeType);
240 if (reply) {
241 xcb_rectangle_t *rectangles = xcb_shape_get_rectangles_rectangles(R: reply.get());
242 if (rectangles) {
243 const int nRectangles = xcb_shape_get_rectangles_rectangles_length(R: reply.get());
244 for (int i = 0; !interacts && i < nRectangles; ++i) {
245 interacts = QRect(rectangles[i].x, rectangles[i].y, rectangles[i].width, rectangles[i].height).contains(p: pos);
246 }
247 }
248 }
249
250 return interacts;
251}
252
253xcb_window_t QXcbDrag::findRealWindow(const QPoint & pos, xcb_window_t w, int md, bool ignoreNonXdndAwareWindows)
254{
255 if (w == shapedPixmapWindow()->handle()->winId())
256 return 0;
257
258 if (md) {
259 auto reply = Q_XCB_REPLY(xcb_get_window_attributes, xcb_connection(), w);
260 if (!reply)
261 return 0;
262
263 if (reply->map_state != XCB_MAP_STATE_VIEWABLE)
264 return 0;
265
266 auto greply = Q_XCB_REPLY(xcb_get_geometry, xcb_connection(), w);
267 if (!greply)
268 return 0;
269
270 QRect windowRect(greply->x, greply->y, greply->width, greply->height);
271 if (windowRect.contains(p: pos)) {
272 bool windowContainsMouse = !ignoreNonXdndAwareWindows;
273 {
274 auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(),
275 false, w, connection()->atom(QXcbAtom::XdndAware),
276 XCB_GET_PROPERTY_TYPE_ANY, 0, 0);
277 bool isAware = reply && reply->type != XCB_NONE;
278 if (isAware) {
279 const QPoint relPos = pos - windowRect.topLeft();
280 // When ShapeInput and ShapeBounding are not set they return a single rectangle with the geometry of the window, this is why we
281 // need to check both here so that in the case one is set and the other is not we still get the correct result.
282 if (connection()->hasInputShape())
283 windowContainsMouse = windowInteractsWithPosition(connection: xcb_connection(), pos: relPos, w, shapeType: XCB_SHAPE_SK_INPUT);
284 if (windowContainsMouse && connection()->hasXShape())
285 windowContainsMouse = windowInteractsWithPosition(connection: xcb_connection(), pos: relPos, w, shapeType: XCB_SHAPE_SK_BOUNDING);
286 if (!connection()->hasInputShape() && !connection()->hasXShape())
287 windowContainsMouse = true;
288 if (windowContainsMouse)
289 return w;
290 }
291 }
292
293 auto reply = Q_XCB_REPLY(xcb_query_tree, xcb_connection(), w);
294 if (!reply)
295 return 0;
296 int nc = xcb_query_tree_children_length(R: reply.get());
297 xcb_window_t *c = xcb_query_tree_children(R: reply.get());
298
299 xcb_window_t r = 0;
300 for (uint i = nc; !r && i--;)
301 r = findRealWindow(pos: pos - windowRect.topLeft(), w: c[i], md: md-1, ignoreNonXdndAwareWindows);
302
303 if (r)
304 return r;
305
306 // We didn't find a client window! Just use the
307 // innermost window.
308
309 // No children!
310 if (!windowContainsMouse)
311 return 0;
312 else
313 return w;
314 }
315 }
316 return 0;
317}
318
319bool QXcbDrag::findXdndAwareTarget(const QPoint &globalPos, xcb_window_t *target_out)
320{
321 xcb_window_t rootwin = current_virtual_desktop->root();
322 auto translate = Q_XCB_REPLY(xcb_translate_coordinates, xcb_connection(),
323 rootwin, rootwin, globalPos.x(), globalPos.y());
324 if (!translate)
325 return false;
326
327 xcb_window_t target = translate->child;
328 int lx = translate->dst_x;
329 int ly = translate->dst_y;
330
331 if (target && target != rootwin) {
332 xcb_window_t src = rootwin;
333 while (target != 0) {
334 qCDebug(lcQpaXDnd) << "checking target for XdndAware" << target;
335
336 auto translate = Q_XCB_REPLY(xcb_translate_coordinates, xcb_connection(),
337 src, target, lx, ly);
338 if (!translate) {
339 target = 0;
340 break;
341 }
342 lx = translate->dst_x;
343 ly = translate->dst_y;
344 src = target;
345 xcb_window_t child = translate->child;
346
347 auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(), false, target,
348 atom(QXcbAtom::XdndAware), XCB_GET_PROPERTY_TYPE_ANY, 0, 0);
349 bool aware = reply && reply->type != XCB_NONE;
350 if (aware) {
351 qCDebug(lcQpaXDnd) << "found XdndAware on" << target;
352 break;
353 }
354
355 target = child;
356 }
357
358 if (!target || target == shapedPixmapWindow()->handle()->winId()) {
359 qCDebug(lcQpaXDnd) << "need to find real window";
360 target = findRealWindow(pos: globalPos, w: rootwin, md: 6, ignoreNonXdndAwareWindows: true);
361 if (target == 0)
362 target = findRealWindow(pos: globalPos, w: rootwin, md: 6, ignoreNonXdndAwareWindows: false);
363 qCDebug(lcQpaXDnd) << "real window found" << target;
364 }
365 }
366
367 *target_out = target;
368 return true;
369}
370
371void QXcbDrag::move(const QPoint &globalPos, Qt::MouseButtons b, Qt::KeyboardModifiers mods)
372{
373 // currentDrag() might be deleted while 'drag' is progressing
374 if (!currentDrag()) {
375 cancel();
376 return;
377 }
378 // The source sends XdndEnter and XdndPosition to the target.
379 if (source_sameanswer.contains(p: globalPos) && source_sameanswer.isValid())
380 return;
381
382 QXcbVirtualDesktop *virtualDesktop = nullptr;
383 QPoint cursorPos;
384 QXcbCursor::queryPointer(c: connection(), virtualDesktop: &virtualDesktop, pos: &cursorPos);
385 QXcbScreen *screen = virtualDesktop->screenAt(pos: cursorPos);
386 QPoint deviceIndependentPos = QHighDpiScaling::mapPositionFromNative(pos: globalPos, platformScreen: screen);
387
388 if (virtualDesktop != current_virtual_desktop) {
389 setUseCompositing(virtualDesktop->compositingActive());
390 recreateShapedPixmapWindow(screen: static_cast<QPlatformScreen*>(screen)->screen(), pos: deviceIndependentPos);
391 if (connection()->mouseGrabber() == nullptr)
392 shapedPixmapWindow()->setMouseGrabEnabled(true);
393
394 current_virtual_desktop = virtualDesktop;
395 } else {
396 QBasicDrag::moveShapedPixmapWindow(deviceIndependentPosition: deviceIndependentPos);
397 }
398
399 xcb_window_t target;
400 if (!findXdndAwareTarget(globalPos, target_out: &target))
401 return;
402
403 QXcbWindow *w = nullptr;
404 if (target) {
405 w = connection()->platformWindowFromId(id: target);
406 if (w && (w->window()->type() == Qt::Desktop) /*&& !w->acceptDrops()*/)
407 w = nullptr;
408 } else {
409 w = nullptr;
410 target = current_virtual_desktop->root();
411 }
412
413 xcb_window_t proxy_target = xdndProxy(c: connection(), w: target);
414 if (!proxy_target)
415 proxy_target = target;
416 int target_version = 1;
417
418 if (proxy_target) {
419 auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(),
420 false, proxy_target,
421 atom(QXcbAtom::XdndAware), XCB_GET_PROPERTY_TYPE_ANY, 0, 1);
422 if (!reply || reply->type == XCB_NONE) {
423 target = 0;
424 } else {
425 target_version = *(uint32_t *)xcb_get_property_value(R: reply.get());
426 target_version = qMin(a: xdnd_version, b: target_version ? target_version : 1);
427 }
428 }
429
430 if (target != current_target) {
431 if (current_target)
432 send_leave();
433
434 current_target = target;
435 current_proxy_target = proxy_target;
436 if (target) {
437 int flags = target_version << 24;
438 if (drag_types.size() > 3)
439 flags |= 0x0001;
440
441 xcb_client_message_event_t enter;
442 enter.response_type = XCB_CLIENT_MESSAGE;
443 enter.sequence = 0;
444 enter.window = target;
445 enter.format = 32;
446 enter.type = atom(atom: QXcbAtom::XdndEnter);
447#ifndef QT_NO_CLIPBOARD
448 enter.data.data32[0] = connection()->clipboard()->owner();
449#else
450 enter.data.data32[0] = 0;
451#endif
452 enter.data.data32[1] = flags;
453 enter.data.data32[2] = drag_types.size() > 0 ? drag_types.at(i: 0) : 0;
454 enter.data.data32[3] = drag_types.size() > 1 ? drag_types.at(i: 1) : 0;
455 enter.data.data32[4] = drag_types.size() > 2 ? drag_types.at(i: 2) : 0;
456 // provisionally set the rectangle to 5x5 pixels...
457 source_sameanswer = QRect(globalPos.x() - 2, globalPos.y() - 2 , 5, 5);
458
459 qCDebug(lcQpaXDnd) << "sending XdndEnter to target:" << target;
460
461 if (w)
462 handleEnter(window: w, event: &enter, proxy: current_proxy_target);
463 else if (target)
464 xcb_send_event(c: xcb_connection(), propagate: false, destination: proxy_target, event_mask: XCB_EVENT_MASK_NO_EVENT, event: (const char *)&enter);
465 waiting_for_status = false;
466 }
467 }
468
469 if (waiting_for_status)
470 return;
471
472 if (target) {
473 waiting_for_status = true;
474 // The source sends a ClientMessage of type XdndPosition. This tells the target the
475 // position of the mouse and the action that the user requested.
476 xcb_client_message_event_t move;
477 move.response_type = XCB_CLIENT_MESSAGE;
478 move.sequence = 0;
479 move.window = target;
480 move.format = 32;
481 move.type = atom(atom: QXcbAtom::XdndPosition);
482#ifndef QT_NO_CLIPBOARD
483 move.data.data32[0] = connection()->clipboard()->owner();
484#else
485 move.data.data32[0] = 0;
486#endif
487 move.data.data32[1] = 0; // flags
488 move.data.data32[2] = (globalPos.x() << 16) + globalPos.y();
489 move.data.data32[3] = connection()->time();
490 const auto supportedActions = currentDrag()->supportedActions();
491 const auto requestedAction = defaultAction(possibleActions: supportedActions, modifiers: mods);
492 move.data.data32[4] = toXdndAction(a: requestedAction);
493
494 qCDebug(lcQpaXDnd) << "sending XdndPosition to target:" << target;
495
496 source_time = connection()->time();
497
498 if (w) {
499 handle_xdnd_position(w, event: &move, b, mods);
500 } else {
501 setActionList(requestedAction, supportedActions);
502 xcb_send_event(c: xcb_connection(), propagate: false, destination: proxy_target, event_mask: XCB_EVENT_MASK_NO_EVENT, event: (const char *)&move);
503 }
504 }
505
506 static const bool isUnity = qgetenv(varName: "XDG_CURRENT_DESKTOP").toLower() == "unity";
507 if (isUnity && xdndCollectionWindow == XCB_NONE) {
508 QString name = QXcbWindow::windowTitle(conn: connection(), window: target);
509 if (name == QStringLiteral("XdndCollectionWindowImp"))
510 xdndCollectionWindow = target;
511 }
512 if (target == xdndCollectionWindow) {
513 setCanDrop(false);
514 updateCursor(action: Qt::IgnoreAction);
515 }
516}
517
518void QXcbDrag::drop(const QPoint &globalPos, Qt::MouseButtons b, Qt::KeyboardModifiers mods)
519{
520 // XdndDrop is sent from source to target to complete the drop.
521 QBasicDrag::drop(globalPos, b, mods);
522
523 if (!current_target)
524 return;
525
526 xcb_client_message_event_t drop;
527 drop.response_type = XCB_CLIENT_MESSAGE;
528 drop.sequence = 0;
529 drop.window = current_target;
530 drop.format = 32;
531 drop.type = atom(atom: QXcbAtom::XdndDrop);
532#ifndef QT_NO_CLIPBOARD
533 drop.data.data32[0] = connection()->clipboard()->owner();
534#else
535 drop.data.data32[0] = 0;
536#endif
537 drop.data.data32[1] = 0; // flags
538 drop.data.data32[2] = connection()->time();
539
540 drop.data.data32[3] = 0;
541 drop.data.data32[4] = currentDrag()->supportedActions();
542
543 QXcbWindow *w = connection()->platformWindowFromId(id: current_proxy_target);
544
545 if (w && w->window()->type() == Qt::Desktop) // && !w->acceptDrops()
546 w = nullptr;
547
548 Transaction t = {
549 .timestamp: connection()->time(),
550 .target: current_target,
551 .proxy_target: current_proxy_target,
552 .targetWindow: w,
553// current_embeddig_widget,
554 .drag: currentDrag(),
555 .time: QTime::currentTime()
556 };
557 transactions.append(t);
558
559 // timer is needed only for drops that came from other processes.
560 if (!t.targetWindow && cleanup_timer == -1) {
561 cleanup_timer = startTimer(interval: XdndDropTransactionTimeout);
562 }
563
564 qCDebug(lcQpaXDnd) << "sending drop to target:" << current_target;
565
566 if (w) {
567 handleDrop(w, event: &drop, b, mods);
568 } else {
569 xcb_send_event(c: xcb_connection(), propagate: false, destination: current_proxy_target, event_mask: XCB_EVENT_MASK_NO_EVENT, event: (const char *)&drop);
570 }
571}
572
573Qt::DropAction QXcbDrag::toDropAction(xcb_atom_t a) const
574{
575 if (a == atom(atom: QXcbAtom::XdndActionCopy) || a == 0)
576 return Qt::CopyAction;
577 if (a == atom(atom: QXcbAtom::XdndActionLink))
578 return Qt::LinkAction;
579 if (a == atom(atom: QXcbAtom::XdndActionMove))
580 return Qt::MoveAction;
581 return Qt::CopyAction;
582}
583
584Qt::DropActions QXcbDrag::toDropActions(const QVector<xcb_atom_t> &atoms) const
585{
586 Qt::DropActions actions;
587 for (const auto actionAtom : atoms) {
588 if (actionAtom != atom(atom: QXcbAtom::XdndActionAsk))
589 actions |= toDropAction(a: actionAtom);
590 }
591 return actions;
592}
593
594xcb_atom_t QXcbDrag::toXdndAction(Qt::DropAction a) const
595{
596 switch (a) {
597 case Qt::CopyAction:
598 return atom(atom: QXcbAtom::XdndActionCopy);
599 case Qt::LinkAction:
600 return atom(atom: QXcbAtom::XdndActionLink);
601 case Qt::MoveAction:
602 case Qt::TargetMoveAction:
603 return atom(atom: QXcbAtom::XdndActionMove);
604 case Qt::IgnoreAction:
605 return XCB_NONE;
606 default:
607 return atom(atom: QXcbAtom::XdndActionCopy);
608 }
609}
610
611void QXcbDrag::readActionList()
612{
613 drop_actions.clear();
614 auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(), false, xdnd_dragsource,
615 atom(QXcbAtom::XdndActionList), XCB_ATOM_ATOM,
616 0, 1024);
617 if (reply && reply->type != XCB_NONE && reply->format == 32) {
618 int length = xcb_get_property_value_length(R: reply.get()) / 4;
619
620 xcb_atom_t *atoms = (xcb_atom_t *)xcb_get_property_value(R: reply.get());
621 for (int i = 0; i < length; ++i)
622 drop_actions.append(t: atoms[i]);
623 }
624}
625
626void QXcbDrag::setActionList(Qt::DropAction requestedAction, Qt::DropActions supportedActions)
627{
628#ifndef QT_NO_CLIPBOARD
629 QVector<xcb_atom_t> actions;
630 if (requestedAction != Qt::IgnoreAction)
631 actions.append(t: toXdndAction(a: requestedAction));
632
633 auto checkAppend = [this, requestedAction, supportedActions, &actions](Qt::DropAction action) {
634 if (requestedAction != action && supportedActions & action)
635 actions.append(t: toXdndAction(a: action));
636 };
637
638 checkAppend(Qt::CopyAction);
639 checkAppend(Qt::MoveAction);
640 checkAppend(Qt::LinkAction);
641
642 if (current_actions != actions) {
643 xcb_change_property(c: xcb_connection(), mode: XCB_PROP_MODE_REPLACE, window: connection()->clipboard()->owner(),
644 property: atom(atom: QXcbAtom::XdndActionList),
645 type: XCB_ATOM_ATOM, format: 32, data_len: actions.size(), data: actions.constData());
646 current_actions = actions;
647 }
648#endif
649}
650
651void QXcbDrag::startListeningForActionListChanges()
652{
653 connection()->addWindowEventListener(id: xdnd_dragsource, eventListener: this);
654 const uint32_t event_mask[] = { XCB_EVENT_MASK_PROPERTY_CHANGE };
655 xcb_change_window_attributes(c: xcb_connection(), window: xdnd_dragsource, value_mask: XCB_CW_EVENT_MASK, value_list: event_mask);
656}
657
658void QXcbDrag::stopListeningForActionListChanges()
659{
660 const uint32_t event_mask[] = { XCB_EVENT_MASK_NO_EVENT };
661 xcb_change_window_attributes(c: xcb_connection(), window: xdnd_dragsource, value_mask: XCB_CW_EVENT_MASK, value_list: event_mask);
662 connection()->removeWindowEventListener(id: xdnd_dragsource);
663}
664
665int QXcbDrag::findTransactionByWindow(xcb_window_t window)
666{
667 int at = -1;
668 for (int i = 0; i < transactions.count(); ++i) {
669 const Transaction &t = transactions.at(i);
670 if (t.target == window || t.proxy_target == window) {
671 at = i;
672 break;
673 }
674 }
675 return at;
676}
677
678int QXcbDrag::findTransactionByTime(xcb_timestamp_t timestamp)
679{
680 int at = -1;
681 for (int i = 0; i < transactions.count(); ++i) {
682 const Transaction &t = transactions.at(i);
683 if (t.timestamp == timestamp) {
684 at = i;
685 break;
686 }
687 }
688 return at;
689}
690
691#if 0
692// for embedding only
693static QWidget* current_embedding_widget = 0;
694static xcb_client_message_event_t last_enter_event;
695
696
697static bool checkEmbedded(QWidget* w, const XEvent* xe)
698{
699 if (!w)
700 return false;
701
702 if (current_embedding_widget != 0 && current_embedding_widget != w) {
703 current_target = ((QExtraWidget*)current_embedding_widget)->extraData()->xDndProxy;
704 current_proxy_target = current_target;
705 qt_xdnd_send_leave();
706 current_target = 0;
707 current_proxy_target = 0;
708 current_embedding_widget = 0;
709 }
710
711 QWExtra* extra = ((QExtraWidget*)w)->extraData();
712 if (extra && extra->xDndProxy != 0) {
713
714 if (current_embedding_widget != w) {
715
716 last_enter_event.xany.window = extra->xDndProxy;
717 XSendEvent(X11->display, extra->xDndProxy, False, NoEventMask, &last_enter_event);
718 current_embedding_widget = w;
719 }
720
721 ((XEvent*)xe)->xany.window = extra->xDndProxy;
722 XSendEvent(X11->display, extra->xDndProxy, False, NoEventMask, (XEvent*)xe);
723 if (currentWindow != w) {
724 currentWindow = w;
725 }
726 return true;
727 }
728 current_embedding_widget = 0;
729 return false;
730}
731#endif
732
733void QXcbDrag::handleEnter(QPlatformWindow *, const xcb_client_message_event_t *event, xcb_window_t proxy)
734{
735 // The target receives XdndEnter.
736 qCDebug(lcQpaXDnd) << "target:" << event->window << "received XdndEnter";
737
738 xdnd_types.clear();
739
740 int version = (int)(event->data.data32[1] >> 24);
741 if (version > xdnd_version)
742 return;
743
744 xdnd_dragsource = event->data.data32[0];
745 startListeningForActionListChanges();
746 readActionList();
747
748 if (!proxy)
749 proxy = xdndProxy(c: connection(), w: xdnd_dragsource);
750 current_proxy_target = proxy ? proxy : xdnd_dragsource;
751
752 if (event->data.data32[1] & 1) {
753 // get the types from XdndTypeList
754 auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(), false, xdnd_dragsource,
755 atom(QXcbAtom::XdndTypelist), XCB_ATOM_ATOM,
756 0, xdnd_max_type);
757 if (reply && reply->type != XCB_NONE && reply->format == 32) {
758 int length = xcb_get_property_value_length(R: reply.get()) / 4;
759 if (length > xdnd_max_type)
760 length = xdnd_max_type;
761
762 xcb_atom_t *atoms = (xcb_atom_t *)xcb_get_property_value(R: reply.get());
763 xdnd_types.reserve(asize: length);
764 for (int i = 0; i < length; ++i)
765 xdnd_types.append(t: atoms[i]);
766 }
767 } else {
768 // get the types from the message
769 for(int i = 2; i < 5; i++) {
770 if (event->data.data32[i])
771 xdnd_types.append(t: event->data.data32[i]);
772 }
773 }
774 for(int i = 0; i < xdnd_types.length(); ++i)
775 qCDebug(lcQpaXDnd) << " " << connection()->atomName(atom: xdnd_types.at(i));
776}
777
778void QXcbDrag::handle_xdnd_position(QPlatformWindow *w, const xcb_client_message_event_t *e,
779 Qt::MouseButtons b, Qt::KeyboardModifiers mods)
780{
781 // The target receives XdndPosition. The target window must determine which widget the mouse
782 // is in and ask it whether or not it will accept the drop.
783 qCDebug(lcQpaXDnd) << "target:" << e->window << "received XdndPosition";
784
785 QPoint p((e->data.data32[2] & 0xffff0000) >> 16, e->data.data32[2] & 0x0000ffff);
786 Q_ASSERT(w);
787 QRect geometry = w->geometry();
788 p -= geometry.topLeft();
789
790 if (!w || !w->window() || (w->window()->type() == Qt::Desktop))
791 return;
792
793 if (Q_UNLIKELY(e->data.data32[0] != xdnd_dragsource)) {
794 qCDebug(lcQpaXDnd, "xdnd drag position from unexpected source (%x not %x)",
795 e->data.data32[0], xdnd_dragsource);
796 return;
797 }
798
799 currentPosition = p;
800 currentWindow = w->window();
801
802 // timestamp from the source
803 if (e->data.data32[3] != XCB_NONE) {
804 target_time = e->data.data32[3];
805 }
806
807 QMimeData *dropData = nullptr;
808 Qt::DropActions supported_actions = Qt::IgnoreAction;
809 if (currentDrag()) {
810 dropData = currentDrag()->mimeData();
811 supported_actions = currentDrag()->supportedActions();
812 } else {
813 dropData = m_dropData;
814 supported_actions = toDropActions(atoms: drop_actions);
815 if (e->data.data32[4] != atom(atom: QXcbAtom::XdndActionAsk))
816 supported_actions |= Qt::DropActions(toDropAction(a: e->data.data32[4]));
817 }
818
819 auto buttons = currentDrag() ? b : connection()->queryMouseButtons();
820 auto modifiers = currentDrag() ? mods : connection()->queryKeyboardModifiers();
821
822 QPlatformDragQtResponse qt_response = QWindowSystemInterface::handleDrag(
823 window: w->window(), dropData, p, supportedActions: supported_actions, buttons, modifiers);
824
825 // ### FIXME ? - answerRect appears to be unused.
826 QRect answerRect(p + geometry.topLeft(), QSize(1,1));
827 answerRect = qt_response.answerRect().translated(p: geometry.topLeft()).intersected(other: geometry);
828
829 // The target sends a ClientMessage of type XdndStatus. This tells the source whether or not
830 // it will accept the drop, and, if so, what action will be taken. It also includes a rectangle
831 // that means "don't send another XdndPosition message until the mouse moves out of here".
832 xcb_client_message_event_t response;
833 response.response_type = XCB_CLIENT_MESSAGE;
834 response.sequence = 0;
835 response.window = xdnd_dragsource;
836 response.format = 32;
837 response.type = atom(atom: QXcbAtom::XdndStatus);
838 response.data.data32[0] = xcb_window(w);
839 response.data.data32[1] = qt_response.isAccepted(); // flags
840 response.data.data32[2] = 0; // x, y
841 response.data.data32[3] = 0; // w, h
842 response.data.data32[4] = toXdndAction(a: qt_response.acceptedAction()); // action
843
844 accepted_drop_action = qt_response.acceptedAction();
845
846 if (answerRect.left() < 0)
847 answerRect.setLeft(0);
848 if (answerRect.right() > 4096)
849 answerRect.setRight(4096);
850 if (answerRect.top() < 0)
851 answerRect.setTop(0);
852 if (answerRect.bottom() > 4096)
853 answerRect.setBottom(4096);
854 if (answerRect.width() < 0)
855 answerRect.setWidth(0);
856 if (answerRect.height() < 0)
857 answerRect.setHeight(0);
858
859 // reset
860 target_time = XCB_CURRENT_TIME;
861
862 qCDebug(lcQpaXDnd) << "sending XdndStatus to source:" << xdnd_dragsource;
863
864#ifndef QT_NO_CLIPBOARD
865 if (xdnd_dragsource == connection()->clipboard()->owner())
866 handle_xdnd_status(event: &response);
867 else
868#endif
869 xcb_send_event(c: xcb_connection(), propagate: false, destination: current_proxy_target,
870 event_mask: XCB_EVENT_MASK_NO_EVENT, event: (const char *)&response);
871}
872
873namespace
874{
875 class ClientMessageScanner {
876 public:
877 ClientMessageScanner(xcb_atom_t a) : atom(a) {}
878 xcb_atom_t atom;
879 bool operator() (xcb_generic_event_t *event, int type) const {
880 if (type != XCB_CLIENT_MESSAGE)
881 return false;
882 auto clientMessage = reinterpret_cast<xcb_client_message_event_t *>(event);
883 return clientMessage->type == atom;
884 }
885 };
886}
887
888void QXcbDrag::handlePosition(QPlatformWindow * w, const xcb_client_message_event_t *event)
889{
890 xcb_client_message_event_t *lastEvent = const_cast<xcb_client_message_event_t *>(event);
891 ClientMessageScanner scanner(atom(atom: QXcbAtom::XdndPosition));
892 while (auto nextEvent = connection()->eventQueue()->peek(peeker&: scanner)) {
893 if (lastEvent != event)
894 free(ptr: lastEvent);
895 lastEvent = reinterpret_cast<xcb_client_message_event_t *>(nextEvent);
896 }
897
898 handle_xdnd_position(w, e: lastEvent);
899 if (lastEvent != event)
900 free(ptr: lastEvent);
901}
902
903void QXcbDrag::handle_xdnd_status(const xcb_client_message_event_t *event)
904{
905 // The source receives XdndStatus. It can use the action to change the cursor to indicate
906 // whether or not the user's requested action will be performed.
907 qCDebug(lcQpaXDnd) << "source:" << event->window << "received XdndStatus";
908 waiting_for_status = false;
909 // ignore late status messages
910 if (event->data.data32[0] && event->data.data32[0] != current_target)
911 return;
912
913 const bool dropPossible = event->data.data32[1];
914 setCanDrop(dropPossible);
915
916 if (dropPossible) {
917 accepted_drop_action = toDropAction(a: event->data.data32[4]);
918 updateCursor(action: accepted_drop_action);
919 } else {
920 updateCursor(action: Qt::IgnoreAction);
921 }
922
923 if ((event->data.data32[1] & 2) == 0) {
924 QPoint p((event->data.data32[2] & 0xffff0000) >> 16, event->data.data32[2] & 0x0000ffff);
925 QSize s((event->data.data32[3] & 0xffff0000) >> 16, event->data.data32[3] & 0x0000ffff);
926 source_sameanswer = QRect(p, s);
927 } else {
928 source_sameanswer = QRect();
929 }
930}
931
932void QXcbDrag::handleStatus(const xcb_client_message_event_t *event)
933{
934 if (
935#ifndef QT_NO_CLIPBOARD
936 event->window != connection()->clipboard()->owner() ||
937#endif
938 !drag())
939 return;
940
941 xcb_client_message_event_t *lastEvent = const_cast<xcb_client_message_event_t *>(event);
942 xcb_generic_event_t *nextEvent;
943 ClientMessageScanner scanner(atom(atom: QXcbAtom::XdndStatus));
944 while ((nextEvent = connection()->eventQueue()->peek(peeker&: scanner))) {
945 if (lastEvent != event)
946 free(ptr: lastEvent);
947 lastEvent = (xcb_client_message_event_t *)nextEvent;
948 }
949
950 handle_xdnd_status(event: lastEvent);
951 if (lastEvent != event)
952 free(ptr: lastEvent);
953}
954
955void QXcbDrag::handleLeave(QPlatformWindow *w, const xcb_client_message_event_t *event)
956{
957 // If the target receives XdndLeave, it frees any cached data and forgets the whole incident.
958 qCDebug(lcQpaXDnd) << "target:" << event->window << "received XdndLeave";
959
960 if (!currentWindow || w != currentWindow.data()->handle()) {
961 stopListeningForActionListChanges();
962 return; // sanity
963 }
964
965 // ###
966// if (checkEmbedded(current_embedding_widget, event)) {
967// current_embedding_widget = 0;
968// currentWindow.clear();
969// return;
970// }
971
972 if (event->data.data32[0] != xdnd_dragsource) {
973 // This often happens - leave other-process window quickly
974 qCDebug(lcQpaXDnd, "xdnd drag leave from unexpected source (%x not %x",
975 event->data.data32[0], xdnd_dragsource);
976 }
977
978 stopListeningForActionListChanges();
979
980 QWindowSystemInterface::handleDrag(window: w->window(), dropData: nullptr, p: QPoint(), supportedActions: Qt::IgnoreAction, buttons: { }, modifiers: { });
981}
982
983void QXcbDrag::send_leave()
984{
985 // XdndLeave is sent from the source to the target to cancel the drop.
986 if (!current_target)
987 return;
988
989 xcb_client_message_event_t leave;
990 leave.response_type = XCB_CLIENT_MESSAGE;
991 leave.sequence = 0;
992 leave.window = current_target;
993 leave.format = 32;
994 leave.type = atom(atom: QXcbAtom::XdndLeave);
995#ifndef QT_NO_CLIPBOARD
996 leave.data.data32[0] = connection()->clipboard()->owner();
997#else
998 leave.data.data32[0] = 0;
999#endif
1000 leave.data.data32[1] = 0; // flags
1001 leave.data.data32[2] = 0; // x, y
1002 leave.data.data32[3] = 0; // w, h
1003 leave.data.data32[4] = 0; // just null
1004
1005 QXcbWindow *w = connection()->platformWindowFromId(id: current_proxy_target);
1006
1007 if (w && (w->window()->type() == Qt::Desktop) /*&& !w->acceptDrops()*/)
1008 w = nullptr;
1009
1010 qCDebug(lcQpaXDnd) << "sending XdndLeave to target:" << current_target;
1011
1012 if (w)
1013 handleLeave(w, event: (const xcb_client_message_event_t *)&leave);
1014 else
1015 xcb_send_event(c: xcb_connection(), propagate: false,destination: current_proxy_target,
1016 event_mask: XCB_EVENT_MASK_NO_EVENT, event: (const char *)&leave);
1017}
1018
1019void QXcbDrag::handleDrop(QPlatformWindow *, const xcb_client_message_event_t *event,
1020 Qt::MouseButtons b, Qt::KeyboardModifiers mods)
1021{
1022 // Target receives XdndDrop. Once it is finished processing the drop, it sends XdndFinished.
1023 qCDebug(lcQpaXDnd) << "target:" << event->window << "received XdndDrop";
1024
1025 if (!currentWindow) {
1026 stopListeningForActionListChanges();
1027 xdnd_dragsource = 0;
1028 return; // sanity
1029 }
1030
1031 const uint32_t *l = event->data.data32;
1032
1033 if (l[0] != xdnd_dragsource) {
1034 qCDebug(lcQpaXDnd, "xdnd drop from unexpected source (%x not %x", l[0], xdnd_dragsource);
1035 return;
1036 }
1037
1038 // update the "user time" from the timestamp in the event.
1039 if (l[2] != 0)
1040 target_time = l[2];
1041
1042 Qt::DropActions supported_drop_actions;
1043 QMimeData *dropData = nullptr;
1044 if (currentDrag()) {
1045 dropData = currentDrag()->mimeData();
1046 supported_drop_actions = Qt::DropActions(l[4]);
1047 } else {
1048 dropData = m_dropData;
1049 supported_drop_actions = accepted_drop_action | toDropActions(atoms: drop_actions);
1050 }
1051
1052 if (!dropData)
1053 return;
1054 // ###
1055 // int at = findXdndDropTransactionByTime(target_time);
1056 // if (at != -1)
1057 // dropData = QDragManager::dragPrivate(X11->dndDropTransactions.at(at).object)->data;
1058 // if we can't find it, then use the data in the drag manager
1059
1060 auto buttons = currentDrag() ? b : connection()->queryMouseButtons();
1061 auto modifiers = currentDrag() ? mods : connection()->queryKeyboardModifiers();
1062
1063 QPlatformDropQtResponse response = QWindowSystemInterface::handleDrop(
1064 window: currentWindow.data(), dropData, p: currentPosition, supportedActions: supported_drop_actions,
1065 buttons, modifiers);
1066
1067 setExecutedDropAction(response.acceptedAction());
1068
1069 xcb_client_message_event_t finished = {};
1070 finished.response_type = XCB_CLIENT_MESSAGE;
1071 finished.sequence = 0;
1072 finished.window = xdnd_dragsource;
1073 finished.format = 32;
1074 finished.type = atom(atom: QXcbAtom::XdndFinished);
1075 finished.data.data32[0] = currentWindow ? xcb_window(w: currentWindow.data()) : XCB_NONE;
1076 finished.data.data32[1] = response.isAccepted(); // flags
1077 finished.data.data32[2] = toXdndAction(a: response.acceptedAction());
1078
1079 qCDebug(lcQpaXDnd) << "sending XdndFinished to source:" << xdnd_dragsource;
1080
1081 xcb_send_event(c: xcb_connection(), propagate: false, destination: current_proxy_target,
1082 event_mask: XCB_EVENT_MASK_NO_EVENT, event: (char *)&finished);
1083
1084 stopListeningForActionListChanges();
1085
1086 dropped = true;
1087}
1088
1089void QXcbDrag::handleFinished(const xcb_client_message_event_t *event)
1090{
1091 // Source receives XdndFinished when target is done processing the drop data.
1092 qCDebug(lcQpaXDnd) << "source:" << event->window << "received XdndFinished";
1093
1094#ifndef QT_NO_CLIPBOARD
1095 if (event->window != connection()->clipboard()->owner())
1096 return;
1097#endif
1098
1099 const unsigned long *l = (const unsigned long *)event->data.data32;
1100 if (l[0]) {
1101 int at = findTransactionByWindow(window: l[0]);
1102 if (at != -1) {
1103
1104 Transaction t = transactions.takeAt(i: at);
1105 if (t.drag)
1106 t.drag->deleteLater();
1107// QDragManager *manager = QDragManager::self();
1108
1109// Window target = current_target;
1110// Window proxy_target = current_proxy_target;
1111// QWidget *embedding_widget = current_embedding_widget;
1112// QDrag *currentObject = manager->object;
1113
1114// current_target = t.target;
1115// current_proxy_target = t.proxy_target;
1116// current_embedding_widget = t.embedding_widget;
1117// manager->object = t.object;
1118
1119// if (!passive)
1120// (void) checkEmbedded(currentWindow, xe);
1121
1122// current_embedding_widget = 0;
1123// current_target = 0;
1124// current_proxy_target = 0;
1125
1126// current_target = target;
1127// current_proxy_target = proxy_target;
1128// current_embedding_widget = embedding_widget;
1129// manager->object = currentObject;
1130 } else {
1131 qWarning(msg: "QXcbDrag::handleFinished - drop data has expired");
1132 }
1133 }
1134 waiting_for_status = false;
1135}
1136
1137void QXcbDrag::timerEvent(QTimerEvent* e)
1138{
1139 if (e->timerId() == cleanup_timer) {
1140 bool stopTimer = true;
1141 for (int i = 0; i < transactions.count(); ++i) {
1142 const Transaction &t = transactions.at(i);
1143 if (t.targetWindow) {
1144 // dnd within the same process, don't delete, these are taken care of
1145 // in handleFinished()
1146 continue;
1147 }
1148 QTime currentTime = QTime::currentTime();
1149 int delta = t.time.msecsTo(currentTime);
1150 if (delta > XdndDropTransactionTimeout) {
1151 /* delete transactions which are older than XdndDropTransactionTimeout. It could mean
1152 one of these:
1153 - client has crashed and as a result we have never received XdndFinished
1154 - showing dialog box on drop event where user's response takes more time than XdndDropTransactionTimeout (QTBUG-14493)
1155 - dnd takes unusually long time to process data
1156 */
1157 if (t.drag)
1158 t.drag->deleteLater();
1159 transactions.removeAt(i: i--);
1160 } else {
1161 stopTimer = false;
1162 }
1163
1164 }
1165 if (stopTimer && cleanup_timer != -1) {
1166 killTimer(id: cleanup_timer);
1167 cleanup_timer = -1;
1168 }
1169 }
1170}
1171
1172void QXcbDrag::cancel()
1173{
1174 qCDebug(lcQpaXDnd) << "dnd was canceled";
1175
1176 QBasicDrag::cancel();
1177 if (current_target)
1178 send_leave();
1179
1180 // remove canceled object
1181 if (currentDrag())
1182 currentDrag()->deleteLater();
1183
1184 canceled = true;
1185}
1186
1187static xcb_window_t findXdndAwareParent(QXcbConnection *c, xcb_window_t window)
1188{
1189 xcb_window_t target = 0;
1190 forever {
1191 // check if window has XdndAware
1192 auto gpReply = Q_XCB_REPLY(xcb_get_property, c->xcb_connection(), false, window,
1193 c->atom(QXcbAtom::XdndAware), XCB_GET_PROPERTY_TYPE_ANY, 0, 0);
1194 bool aware = gpReply && gpReply->type != XCB_NONE;
1195 if (aware) {
1196 target = window;
1197 break;
1198 }
1199
1200 // try window's parent
1201 auto qtReply = Q_XCB_REPLY_UNCHECKED(xcb_query_tree, c->xcb_connection(), window);
1202 if (!qtReply)
1203 break;
1204 xcb_window_t root = qtReply->root;
1205 xcb_window_t parent = qtReply->parent;
1206 if (window == root)
1207 break;
1208 window = parent;
1209 }
1210 return target;
1211}
1212
1213void QXcbDrag::handleSelectionRequest(const xcb_selection_request_event_t *event)
1214{
1215 qCDebug(lcQpaXDnd) << "handle selection request from target:" << event->requestor;
1216 q_padded_xcb_event<xcb_selection_notify_event_t> notify = {};
1217 notify.response_type = XCB_SELECTION_NOTIFY;
1218 notify.requestor = event->requestor;
1219 notify.selection = event->selection;
1220 notify.target = XCB_NONE;
1221 notify.property = XCB_NONE;
1222 notify.time = event->time;
1223
1224 // which transaction do we use? (note: -2 means use current currentDrag())
1225 int at = -1;
1226
1227 // figure out which data the requestor is really interested in
1228 if (currentDrag() && event->time == source_time) {
1229 // requestor wants the current drag data
1230 at = -2;
1231 } else {
1232 // if someone has requested data in response to XdndDrop, find the corresponding transaction. the
1233 // spec says to call xcb_convert_selection() using the timestamp from the XdndDrop
1234 at = findTransactionByTime(timestamp: event->time);
1235 if (at == -1) {
1236 // no dice, perhaps the client was nice enough to use the same window id in
1237 // xcb_convert_selection() that we sent the XdndDrop event to.
1238 at = findTransactionByWindow(window: event->requestor);
1239 }
1240
1241 if (at == -1) {
1242 xcb_window_t target = findXdndAwareParent(c: connection(), window: event->requestor);
1243 if (target) {
1244 if (event->time == XCB_CURRENT_TIME && current_target == target)
1245 at = -2;
1246 else
1247 at = findTransactionByWindow(window: target);
1248 }
1249 }
1250 }
1251
1252 QDrag *transactionDrag = nullptr;
1253 if (at >= 0) {
1254 transactionDrag = transactions.at(i: at).drag;
1255 } else if (at == -2) {
1256 transactionDrag = currentDrag();
1257 }
1258
1259 if (transactionDrag) {
1260 xcb_atom_t atomFormat = event->target;
1261 int dataFormat = 0;
1262 QByteArray data;
1263 if (QXcbMime::mimeDataForAtom(connection: connection(), a: event->target, mimeData: transactionDrag->mimeData(),
1264 data: &data, atomFormat: &atomFormat, dataFormat: &dataFormat)) {
1265 int dataSize = data.size() / (dataFormat / 8);
1266 xcb_change_property(c: xcb_connection(), mode: XCB_PROP_MODE_REPLACE, window: event->requestor, property: event->property,
1267 type: atomFormat, format: dataFormat, data_len: dataSize, data: (const void *)data.constData());
1268 notify.property = event->property;
1269 notify.target = atomFormat;
1270 }
1271 }
1272
1273 xcb_window_t proxy_target = xdndProxy(c: connection(), w: event->requestor);
1274 if (!proxy_target)
1275 proxy_target = event->requestor;
1276
1277 xcb_send_event(c: xcb_connection(), propagate: false, destination: proxy_target, event_mask: XCB_EVENT_MASK_NO_EVENT, event: (const char *)&notify);
1278}
1279
1280
1281bool QXcbDrag::dndEnable(QXcbWindow *w, bool on)
1282{
1283 // Windows announce that they support the XDND protocol by creating a window property XdndAware.
1284 if (on) {
1285 QXcbWindow *window = nullptr;
1286 if (w->window()->type() == Qt::Desktop) {
1287 if (desktop_proxy) // *WE* already have one.
1288 return false;
1289
1290 QXcbConnectionGrabber grabber(connection());
1291
1292 // As per Xdnd4, use XdndProxy
1293 xcb_window_t proxy_id = xdndProxy(c: connection(), w: w->xcb_window());
1294
1295 if (!proxy_id) {
1296 desktop_proxy = new QWindow;
1297 window = static_cast<QXcbWindow *>(desktop_proxy->handle());
1298 proxy_id = window->xcb_window();
1299 xcb_atom_t xdnd_proxy = atom(atom: QXcbAtom::XdndProxy);
1300 xcb_change_property(c: xcb_connection(), mode: XCB_PROP_MODE_REPLACE, window: w->xcb_window(), property: xdnd_proxy,
1301 type: XCB_ATOM_WINDOW, format: 32, data_len: 1, data: &proxy_id);
1302 xcb_change_property(c: xcb_connection(), mode: XCB_PROP_MODE_REPLACE, window: proxy_id, property: xdnd_proxy,
1303 type: XCB_ATOM_WINDOW, format: 32, data_len: 1, data: &proxy_id);
1304 }
1305
1306 } else {
1307 window = w;
1308 }
1309 if (window) {
1310 qCDebug(lcQpaXDnd) << "setting XdndAware for" << window->xcb_window();
1311 xcb_atom_t atm = xdnd_version;
1312 xcb_change_property(c: xcb_connection(), mode: XCB_PROP_MODE_REPLACE, window: window->xcb_window(),
1313 property: atom(atom: QXcbAtom::XdndAware), type: XCB_ATOM_ATOM, format: 32, data_len: 1, data: &atm);
1314 return true;
1315 } else {
1316 return false;
1317 }
1318 } else {
1319 if (w->window()->type() == Qt::Desktop) {
1320 xcb_delete_property(c: xcb_connection(), window: w->xcb_window(), property: atom(atom: QXcbAtom::XdndProxy));
1321 delete desktop_proxy;
1322 desktop_proxy = nullptr;
1323 } else {
1324 qCDebug(lcQpaXDnd) << "not deleting XDndAware";
1325 }
1326 return true;
1327 }
1328}
1329
1330bool QXcbDrag::ownsDragObject() const
1331{
1332 return true;
1333}
1334
1335QXcbDropData::QXcbDropData(QXcbDrag *d)
1336 : QXcbMime(),
1337 drag(d)
1338{
1339}
1340
1341QXcbDropData::~QXcbDropData()
1342{
1343}
1344
1345QVariant QXcbDropData::retrieveData_sys(const QString &mimetype, QVariant::Type requestedType) const
1346{
1347 QByteArray mime = mimetype.toLatin1();
1348 QVariant data = xdndObtainData(format: mime, requestedType: QMetaType::Type(requestedType));
1349 return data;
1350}
1351
1352QVariant QXcbDropData::xdndObtainData(const QByteArray &format, QMetaType::Type requestedType) const
1353{
1354 QByteArray result;
1355
1356 QXcbConnection *c = drag->connection();
1357 QXcbWindow *xcb_window = c->platformWindowFromId(id: drag->xdnd_dragsource);
1358 if (xcb_window && drag->currentDrag() && xcb_window->window()->type() != Qt::Desktop) {
1359 QMimeData *data = drag->currentDrag()->mimeData();
1360 if (data->hasFormat(mimetype: QLatin1String(format)))
1361 result = data->data(mimetype: QLatin1String(format));
1362 return result;
1363 }
1364
1365 QVector<xcb_atom_t> atoms = drag->xdnd_types;
1366 QByteArray encoding;
1367 xcb_atom_t a = mimeAtomForFormat(connection: c, format: QLatin1String(format), requestedType, atoms, requestedEncoding: &encoding);
1368 if (a == XCB_NONE)
1369 return result;
1370
1371#ifndef QT_NO_CLIPBOARD
1372 if (c->clipboard()->getSelectionOwner(atom: drag->atom(atom: QXcbAtom::XdndSelection)) == XCB_NONE)
1373 return result; // should never happen?
1374
1375 xcb_atom_t xdnd_selection = c->atom(qatom: QXcbAtom::XdndSelection);
1376 result = c->clipboard()->getSelection(selection: xdnd_selection, target: a, property: xdnd_selection, t: drag->targetTime());
1377#endif
1378
1379 return mimeConvertToFormat(connection: c, a, data: result, format: QLatin1String(format), requestedType, encoding);
1380}
1381
1382bool QXcbDropData::hasFormat_sys(const QString &format) const
1383{
1384 return formats().contains(str: format);
1385}
1386
1387QStringList QXcbDropData::formats_sys() const
1388{
1389 QStringList formats;
1390 for (int i = 0; i < drag->xdnd_types.size(); ++i) {
1391 QString f = mimeAtomToString(connection: drag->connection(), a: drag->xdnd_types.at(i));
1392 if (!formats.contains(str: f))
1393 formats.append(t: f);
1394 }
1395 return formats;
1396}
1397
1398QT_END_NAMESPACE
1399

source code of qtbase/src/plugins/platforms/xcb/qxcbdrag.cpp