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