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
64 QT_BEGIN_NAMESPACE
65
66 const int xdnd_version = 5;
67
xcb_window(QPlatformWindow * w)68 static inline xcb_window_t xcb_window(QPlatformWindow *w)
69 {
70 return static_cast<QXcbWindow *>(w)->xcb_window();
71 }
72
xcb_window(QWindow * w)73 static inline xcb_window_t xcb_window(QWindow *w)
74 {
75 return static_cast<QXcbWindow *>(w->handle())->xcb_window();
76 }
77
xdndProxy(QXcbConnection * c,xcb_window_t w)78 static 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(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(reply.get()));
97 if (proxy != p)
98 proxy = 0;
99 } else {
100 proxy = 0;
101 }
102
103 return proxy;
104 }
105
106 class QXcbDropData : public QXcbMime
107 {
108 public:
109 QXcbDropData(QXcbDrag *d);
110 ~QXcbDropData();
111
112 protected:
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
QXcbDrag(QXcbConnection * c)123 QXcbDrag::QXcbDrag(QXcbConnection *c) : QXcbObject(c)
124 {
125 m_dropData = new QXcbDropData(this);
126
127 init();
128 cleanup_timer = -1;
129 }
130
~QXcbDrag()131 QXcbDrag::~QXcbDrag()
132 {
133 delete m_dropData;
134 }
135
init()136 void 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(connection(), ¤t_virtual_desktop, 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
eventFilter(QObject * o,QEvent * e)162 bool 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
startDrag()173 void 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(xcb_connection(), connection()->clipboard()->owner(),
180 atom(QXcbAtom::XdndSelection), connection()->time());
181 #endif
182
183 QStringList fmts = QXcbMime::formatsHelper(drag()->mimeData());
184 for (int i = 0; i < fmts.size(); ++i) {
185 QVector<xcb_atom_t> atoms = QXcbMime::mimeAtomsForFormat(connection(), fmts.at(i));
186 for (int j = 0; j < atoms.size(); ++j) {
187 if (!drag_types.contains(atoms.at(j)))
188 drag_types.append(atoms.at(j));
189 }
190 }
191 #ifndef QT_NO_CLIPBOARD
192 if (drag_types.size() > 3)
193 xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, connection()->clipboard()->owner(),
194 atom(QXcbAtom::XdndTypelist),
195 XCB_ATOM_ATOM, 32, drag_types.size(), (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(QCursor::pos(), initiatorWindow.data());
206 move(nativePixelPos, QGuiApplication::mouseButtons(), QGuiApplication::keyboardModifiers());
207 }
208
endDrag()209 void 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
defaultAction(Qt::DropActions possibleActions,Qt::KeyboardModifiers modifiers) const219 Qt::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(drop_actions.first());
225 }
226
handlePropertyNotifyEvent(const xcb_property_notify_event_t * event)227 void QXcbDrag::handlePropertyNotifyEvent(const xcb_property_notify_event_t *event)
228 {
229 if (event->window != xdnd_dragsource || event->atom != atom(QXcbAtom::XdndActionList))
230 return;
231
232 readActionList();
233 }
234
235 static
windowInteractsWithPosition(xcb_connection_t * connection,const QPoint & pos,xcb_window_t w,xcb_shape_sk_t shapeType)236 bool 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(reply.get());
242 if (rectangles) {
243 const int nRectangles = xcb_shape_get_rectangles_rectangles_length(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(pos);
246 }
247 }
248 }
249
250 return interacts;
251 }
252
findRealWindow(const QPoint & pos,xcb_window_t w,int md,bool ignoreNonXdndAwareWindows)253 xcb_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(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(xcb_connection(), relPos, w, XCB_SHAPE_SK_INPUT);
284 if (windowContainsMouse && connection()->hasXShape())
285 windowContainsMouse = windowInteractsWithPosition(xcb_connection(), relPos, w, 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(reply.get());
297 xcb_window_t *c = xcb_query_tree_children(reply.get());
298
299 xcb_window_t r = 0;
300 for (uint i = nc; !r && i--;)
301 r = findRealWindow(pos - windowRect.topLeft(), c[i], 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
findXdndAwareTarget(const QPoint & globalPos,xcb_window_t * target_out)319 bool 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(globalPos, rootwin, 6, true);
361 if (target == 0)
362 target = findRealWindow(globalPos, rootwin, 6, false);
363 qCDebug(lcQpaXDnd) << "real window found" << target;
364 }
365 }
366
367 *target_out = target;
368 return true;
369 }
370
move(const QPoint & globalPos,Qt::MouseButtons b,Qt::KeyboardModifiers mods)371 void 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(globalPos) && source_sameanswer.isValid())
380 return;
381
382 QXcbVirtualDesktop *virtualDesktop = nullptr;
383 QPoint cursorPos;
384 QXcbCursor::queryPointer(connection(), &virtualDesktop, &cursorPos);
385 QXcbScreen *screen = virtualDesktop->screenAt(cursorPos);
386 QPoint deviceIndependentPos = QHighDpiScaling::mapPositionFromNative(globalPos, screen);
387
388 if (virtualDesktop != current_virtual_desktop) {
389 setUseCompositing(virtualDesktop->compositingActive());
390 recreateShapedPixmapWindow(static_cast<QPlatformScreen*>(screen)->screen(), deviceIndependentPos);
391 if (connection()->mouseGrabber() == nullptr)
392 shapedPixmapWindow()->setMouseGrabEnabled(true);
393
394 current_virtual_desktop = virtualDesktop;
395 } else {
396 QBasicDrag::moveShapedPixmapWindow(deviceIndependentPos);
397 }
398
399 xcb_window_t target;
400 if (!findXdndAwareTarget(globalPos, &target))
401 return;
402
403 QXcbWindow *w = nullptr;
404 if (target) {
405 w = connection()->platformWindowFromId(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(connection(), 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(reply.get());
426 target_version = qMin(xdnd_version, 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(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(0) : 0;
454 enter.data.data32[3] = drag_types.size() > 1 ? drag_types.at(1) : 0;
455 enter.data.data32[4] = drag_types.size() > 2 ? drag_types.at(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(w, &enter, current_proxy_target);
463 else if (target)
464 xcb_send_event(xcb_connection(), false, proxy_target, XCB_EVENT_MASK_NO_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(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(supportedActions, mods);
492 move.data.data32[4] = toXdndAction(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, &move, b, mods);
500 } else {
501 setActionList(requestedAction, supportedActions);
502 xcb_send_event(xcb_connection(), false, proxy_target, XCB_EVENT_MASK_NO_EVENT, (const char *)&move);
503 }
504 }
505
506 static const bool isUnity = qgetenv("XDG_CURRENT_DESKTOP").toLower() == "unity";
507 if (isUnity && xdndCollectionWindow == XCB_NONE) {
508 QString name = QXcbWindow::windowTitle(connection(), target);
509 if (name == QStringLiteral("XdndCollectionWindowImp"))
510 xdndCollectionWindow = target;
511 }
512 if (target == xdndCollectionWindow) {
513 setCanDrop(false);
514 updateCursor(Qt::IgnoreAction);
515 }
516 }
517
drop(const QPoint & globalPos,Qt::MouseButtons b,Qt::KeyboardModifiers mods)518 void 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(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(current_proxy_target);
544
545 if (w && w->window()->type() == Qt::Desktop) // && !w->acceptDrops()
546 w = nullptr;
547
548 Transaction t = {
549 connection()->time(),
550 current_target,
551 current_proxy_target,
552 w,
553 // current_embeddig_widget,
554 currentDrag(),
555 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(XdndDropTransactionTimeout);
562 }
563
564 qCDebug(lcQpaXDnd) << "sending drop to target:" << current_target;
565
566 if (w) {
567 handleDrop(w, &drop, b, mods);
568 } else {
569 xcb_send_event(xcb_connection(), false, current_proxy_target, XCB_EVENT_MASK_NO_EVENT, (const char *)&drop);
570 }
571 }
572
toDropAction(xcb_atom_t a) const573 Qt::DropAction QXcbDrag::toDropAction(xcb_atom_t a) const
574 {
575 if (a == atom(QXcbAtom::XdndActionCopy) || a == 0)
576 return Qt::CopyAction;
577 if (a == atom(QXcbAtom::XdndActionLink))
578 return Qt::LinkAction;
579 if (a == atom(QXcbAtom::XdndActionMove))
580 return Qt::MoveAction;
581 return Qt::CopyAction;
582 }
583
toDropActions(const QVector<xcb_atom_t> & atoms) const584 Qt::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(QXcbAtom::XdndActionAsk))
589 actions |= toDropAction(actionAtom);
590 }
591 return actions;
592 }
593
toXdndAction(Qt::DropAction a) const594 xcb_atom_t QXcbDrag::toXdndAction(Qt::DropAction a) const
595 {
596 switch (a) {
597 case Qt::CopyAction:
598 return atom(QXcbAtom::XdndActionCopy);
599 case Qt::LinkAction:
600 return atom(QXcbAtom::XdndActionLink);
601 case Qt::MoveAction:
602 case Qt::TargetMoveAction:
603 return atom(QXcbAtom::XdndActionMove);
604 case Qt::IgnoreAction:
605 return XCB_NONE;
606 default:
607 return atom(QXcbAtom::XdndActionCopy);
608 }
609 }
610
readActionList()611 void 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(reply.get()) / 4;
619
620 xcb_atom_t *atoms = (xcb_atom_t *)xcb_get_property_value(reply.get());
621 for (int i = 0; i < length; ++i)
622 drop_actions.append(atoms[i]);
623 }
624 }
625
setActionList(Qt::DropAction requestedAction,Qt::DropActions supportedActions)626 void 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(toXdndAction(requestedAction));
632
633 auto checkAppend = [this, requestedAction, supportedActions, &actions](Qt::DropAction action) {
634 if (requestedAction != action && supportedActions & action)
635 actions.append(toXdndAction(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(xcb_connection(), XCB_PROP_MODE_REPLACE, connection()->clipboard()->owner(),
644 atom(QXcbAtom::XdndActionList),
645 XCB_ATOM_ATOM, 32, actions.size(), actions.constData());
646 current_actions = actions;
647 }
648 #endif
649 }
650
startListeningForActionListChanges()651 void QXcbDrag::startListeningForActionListChanges()
652 {
653 connection()->addWindowEventListener(xdnd_dragsource, this);
654 const uint32_t event_mask[] = { XCB_EVENT_MASK_PROPERTY_CHANGE };
655 xcb_change_window_attributes(xcb_connection(), xdnd_dragsource, XCB_CW_EVENT_MASK, event_mask);
656 }
657
stopListeningForActionListChanges()658 void QXcbDrag::stopListeningForActionListChanges()
659 {
660 const uint32_t event_mask[] = { XCB_EVENT_MASK_NO_EVENT };
661 xcb_change_window_attributes(xcb_connection(), xdnd_dragsource, XCB_CW_EVENT_MASK, event_mask);
662 connection()->removeWindowEventListener(xdnd_dragsource);
663 }
664
findTransactionByWindow(xcb_window_t window)665 int 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
findTransactionByTime(xcb_timestamp_t timestamp)678 int 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
693 static QWidget* current_embedding_widget = 0;
694 static xcb_client_message_event_t last_enter_event;
695
696
697 static 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
handleEnter(QPlatformWindow *,const xcb_client_message_event_t * event,xcb_window_t proxy)733 void 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(connection(), 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(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(reply.get());
763 xdnd_types.reserve(length);
764 for (int i = 0; i < length; ++i)
765 xdnd_types.append(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(event->data.data32[i]);
772 }
773 }
774 for(int i = 0; i < xdnd_types.length(); ++i)
775 qCDebug(lcQpaXDnd) << " " << connection()->atomName(xdnd_types.at(i));
776 }
777
handle_xdnd_position(QPlatformWindow * w,const xcb_client_message_event_t * e,Qt::MouseButtons b,Qt::KeyboardModifiers mods)778 void 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(drop_actions);
815 if (e->data.data32[4] != atom(QXcbAtom::XdndActionAsk))
816 supported_actions |= Qt::DropActions(toDropAction(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 w->window(), dropData, p, 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(geometry.topLeft()).intersected(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(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(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(&response);
867 else
868 #endif
869 xcb_send_event(xcb_connection(), false, current_proxy_target,
870 XCB_EVENT_MASK_NO_EVENT, (const char *)&response);
871 }
872
873 namespace
874 {
875 class ClientMessageScanner {
876 public:
ClientMessageScanner(xcb_atom_t a)877 ClientMessageScanner(xcb_atom_t a) : atom(a) {}
878 xcb_atom_t atom;
operator ()(xcb_generic_event_t * event,int type) const879 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
handlePosition(QPlatformWindow * w,const xcb_client_message_event_t * event)888 void 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(QXcbAtom::XdndPosition));
892 while (auto nextEvent = connection()->eventQueue()->peek(scanner)) {
893 if (lastEvent != event)
894 free(lastEvent);
895 lastEvent = reinterpret_cast<xcb_client_message_event_t *>(nextEvent);
896 }
897
898 handle_xdnd_position(w, lastEvent);
899 if (lastEvent != event)
900 free(lastEvent);
901 }
902
handle_xdnd_status(const xcb_client_message_event_t * event)903 void 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(event->data.data32[4]);
918 updateCursor(accepted_drop_action);
919 } else {
920 updateCursor(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
handleStatus(const xcb_client_message_event_t * event)932 void 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(QXcbAtom::XdndStatus));
944 while ((nextEvent = connection()->eventQueue()->peek(scanner))) {
945 if (lastEvent != event)
946 free(lastEvent);
947 lastEvent = (xcb_client_message_event_t *)nextEvent;
948 }
949
950 handle_xdnd_status(lastEvent);
951 if (lastEvent != event)
952 free(lastEvent);
953 }
954
handleLeave(QPlatformWindow * w,const xcb_client_message_event_t * event)955 void 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(w->window(), nullptr, QPoint(), Qt::IgnoreAction, { }, { });
981 }
982
send_leave()983 void 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(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(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, (const xcb_client_message_event_t *)&leave);
1014 else
1015 xcb_send_event(xcb_connection(), false,current_proxy_target,
1016 XCB_EVENT_MASK_NO_EVENT, (const char *)&leave);
1017 }
1018
handleDrop(QPlatformWindow *,const xcb_client_message_event_t * event,Qt::MouseButtons b,Qt::KeyboardModifiers mods)1019 void 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(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 currentWindow.data(), dropData, currentPosition, 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(QXcbAtom::XdndFinished);
1075 finished.data.data32[0] = currentWindow ? xcb_window(currentWindow.data()) : XCB_NONE;
1076 finished.data.data32[1] = response.isAccepted(); // flags
1077 finished.data.data32[2] = toXdndAction(response.acceptedAction());
1078
1079 qCDebug(lcQpaXDnd) << "sending XdndFinished to source:" << xdnd_dragsource;
1080
1081 xcb_send_event(xcb_connection(), false, current_proxy_target,
1082 XCB_EVENT_MASK_NO_EVENT, (char *)&finished);
1083
1084 stopListeningForActionListChanges();
1085
1086 dropped = true;
1087 }
1088
handleFinished(const xcb_client_message_event_t * event)1089 void 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(l[0]);
1102 if (at != -1) {
1103
1104 Transaction t = transactions.takeAt(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("QXcbDrag::handleFinished - drop data has expired");
1132 }
1133 }
1134 waiting_for_status = false;
1135 }
1136
timerEvent(QTimerEvent * e)1137 void 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--);
1160 } else {
1161 stopTimer = false;
1162 }
1163
1164 }
1165 if (stopTimer && cleanup_timer != -1) {
1166 killTimer(cleanup_timer);
1167 cleanup_timer = -1;
1168 }
1169 }
1170 }
1171
cancel()1172 void 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
findXdndAwareParent(QXcbConnection * c,xcb_window_t window)1187 static 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
handleSelectionRequest(const xcb_selection_request_event_t * event)1213 void 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(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(event->requestor);
1239 }
1240
1241 if (at == -1) {
1242 xcb_window_t target = findXdndAwareParent(connection(), event->requestor);
1243 if (target) {
1244 if (event->time == XCB_CURRENT_TIME && current_target == target)
1245 at = -2;
1246 else
1247 at = findTransactionByWindow(target);
1248 }
1249 }
1250 }
1251
1252 QDrag *transactionDrag = nullptr;
1253 if (at >= 0) {
1254 transactionDrag = transactions.at(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(), event->target, transactionDrag->mimeData(),
1264 &data, &atomFormat, &dataFormat)) {
1265 int dataSize = data.size() / (dataFormat / 8);
1266 xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, event->requestor, event->property,
1267 atomFormat, dataFormat, dataSize, (const void *)data.constData());
1268 notify.property = event->property;
1269 notify.target = atomFormat;
1270 }
1271 }
1272
1273 xcb_window_t proxy_target = xdndProxy(connection(), event->requestor);
1274 if (!proxy_target)
1275 proxy_target = event->requestor;
1276
1277 xcb_send_event(xcb_connection(), false, proxy_target, XCB_EVENT_MASK_NO_EVENT, (const char *)¬ify);
1278 }
1279
1280
dndEnable(QXcbWindow * w,bool on)1281 bool 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(connection(), 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(QXcbAtom::XdndProxy);
1300 xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, w->xcb_window(), xdnd_proxy,
1301 XCB_ATOM_WINDOW, 32, 1, &proxy_id);
1302 xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, proxy_id, xdnd_proxy,
1303 XCB_ATOM_WINDOW, 32, 1, &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(xcb_connection(), XCB_PROP_MODE_REPLACE, window->xcb_window(),
1313 atom(QXcbAtom::XdndAware), XCB_ATOM_ATOM, 32, 1, &atm);
1314 return true;
1315 } else {
1316 return false;
1317 }
1318 } else {
1319 if (w->window()->type() == Qt::Desktop) {
1320 xcb_delete_property(xcb_connection(), w->xcb_window(), 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
ownsDragObject() const1330 bool QXcbDrag::ownsDragObject() const
1331 {
1332 return true;
1333 }
1334
QXcbDropData(QXcbDrag * d)1335 QXcbDropData::QXcbDropData(QXcbDrag *d)
1336 : QXcbMime(),
1337 drag(d)
1338 {
1339 }
1340
~QXcbDropData()1341 QXcbDropData::~QXcbDropData()
1342 {
1343 }
1344
retrieveData_sys(const QString & mimetype,QVariant::Type requestedType) const1345 QVariant QXcbDropData::retrieveData_sys(const QString &mimetype, QVariant::Type requestedType) const
1346 {
1347 QByteArray mime = mimetype.toLatin1();
1348 QVariant data = xdndObtainData(mime, QMetaType::Type(requestedType));
1349 return data;
1350 }
1351
xdndObtainData(const QByteArray & format,QMetaType::Type requestedType) const1352 QVariant 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(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(QLatin1String(format)))
1361 result = data->data(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(c, QLatin1String(format), requestedType, atoms, &encoding);
1368 if (a == XCB_NONE)
1369 return result;
1370
1371 #ifndef QT_NO_CLIPBOARD
1372 if (c->clipboard()->getSelectionOwner(drag->atom(QXcbAtom::XdndSelection)) == XCB_NONE)
1373 return result; // should never happen?
1374
1375 xcb_atom_t xdnd_selection = c->atom(QXcbAtom::XdndSelection);
1376 result = c->clipboard()->getSelection(xdnd_selection, a, xdnd_selection, drag->targetTime());
1377 #endif
1378
1379 return mimeConvertToFormat(c, a, result, QLatin1String(format), requestedType, encoding);
1380 }
1381
hasFormat_sys(const QString & format) const1382 bool QXcbDropData::hasFormat_sys(const QString &format) const
1383 {
1384 return formats().contains(format);
1385 }
1386
formats_sys() const1387 QStringList QXcbDropData::formats_sys() const
1388 {
1389 QStringList formats;
1390 for (int i = 0; i < drag->xdnd_types.size(); ++i) {
1391 QString f = mimeAtomToString(drag->connection(), drag->xdnd_types.at(i));
1392 if (!formats.contains(f))
1393 formats.append(f);
1394 }
1395 return formats;
1396 }
1397
1398 QT_END_NAMESPACE
1399