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 | |
68 | static inline xcb_window_t xcb_window(QPlatformWindow *w) |
69 | { |
70 | return static_cast<QXcbWindow *>(w)->xcb_window(); |
71 | } |
72 | |
73 | static inline xcb_window_t xcb_window(QWindow *w) |
74 | { |
75 | return static_cast<QXcbWindow *>(w->handle())->xcb_window(); |
76 | } |
77 | |
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(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 | |
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 | |
123 | QXcbDrag::QXcbDrag(QXcbConnection *c) : QXcbObject(c) |
124 | { |
125 | m_dropData = new QXcbDropData(this); |
126 | |
127 | init(); |
128 | cleanup_timer = -1; |
129 | } |
130 | |
131 | QXcbDrag::~QXcbDrag() |
132 | { |
133 | delete m_dropData; |
134 | } |
135 | |
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(c: connection(), virtualDesktop: ¤t_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 | |
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 | |
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(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 | |
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 | |
219 | 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(atom: drop_actions.first()); |
225 | } |
226 | |
227 | void 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 | |
235 | static |
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(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 | |
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(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 | |
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(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 | |
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(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 | |
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(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 | |
573 | Qt::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 | |
584 | 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(atom: QXcbAtom::XdndActionAsk)) |
589 | actions |= toDropAction(a: actionAtom); |
590 | } |
591 | return actions; |
592 | } |
593 | |
594 | xcb_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 | |
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(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 | |
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(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 | |
651 | void 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 | |
658 | void 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 | |
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 | |
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 | |
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(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 | |
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(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 | |
873 | namespace |
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 | |
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(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 | |
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(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 | |
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(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 | |
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(window: w->window(), dropData: nullptr, p: QPoint(), supportedActions: Qt::IgnoreAction, buttons: { }, modifiers: { }); |
981 | } |
982 | |
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(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 | |
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(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 | |
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(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 | |
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: 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 | |
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 | |
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 | |
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(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 *)¬ify); |
1278 | } |
1279 | |
1280 | |
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(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 | |
1330 | bool QXcbDrag::ownsDragObject() const |
1331 | { |
1332 | return true; |
1333 | } |
1334 | |
1335 | QXcbDropData::QXcbDropData(QXcbDrag *d) |
1336 | : QXcbMime(), |
1337 | drag(d) |
1338 | { |
1339 | } |
1340 | |
1341 | QXcbDropData::~QXcbDropData() |
1342 | { |
1343 | } |
1344 | |
1345 | QVariant 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 | |
1352 | 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(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 | |
1382 | bool QXcbDropData::hasFormat_sys(const QString &format) const |
1383 | { |
1384 | return formats().contains(str: format); |
1385 | } |
1386 | |
1387 | QStringList 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 | |
1398 | QT_END_NAMESPACE |
1399 | |