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