1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348 | // Copyright (C) 2016 The Qt Company Ltd.// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only#include"qxcbdrag.h"#include <xcb/xcb.h>#include"qxcbconnection.h"#include"qxcbclipboard.h"#include"qxcbkeyboard.h"#include"qxcbmime.h"#include"qxcbwindow.h"#include"qxcbscreen.h"#include"qwindow.h"#include"qxcbcursor.h"#include <private/qdnd_p.h>#include <qdebug.h>#include <qevent.h>#include <qguiapplication.h>#include <qrect.h>#include <qpainter.h>#include <qpa/qwindowsysteminterface.h>#include <private/qguiapplication_p.h>#include <private/qshapedpixmapdndwindow_p.h>#include <private/qsimpledrag_p.h>#include <private/qhighdpiscaling_p.h> QT_BEGIN_NAMESPACE using namespaceQt::Literals::StringLiterals;const int xdnd_version =5;staticinline xcb_window_t xcb_window(QPlatformWindow *w){return static_cast<QXcbWindow *>(w)->xcb_window();}staticinline xcb_window_t xcb_window(QWindow *w){return static_cast<QXcbWindow *>(w->handle())->xcb_window();}static xcb_window_t xdndProxy(QXcbConnection *c, xcb_window_t w){ xcb_window_t proxy = XCB_NONE;auto reply =Q_XCB_REPLY(xcb_get_property, c->xcb_connection(),false, w, c->atom(QXcbAtom::AtomXdndProxy), XCB_ATOM_WINDOW,0,1);if(reply && reply->type == XCB_ATOM_WINDOW) proxy = *((xcb_window_t *)xcb_get_property_value(reply.get()));if(proxy == XCB_NONE)return proxy;// exists and is real? reply =Q_XCB_REPLY(xcb_get_property, c->xcb_connection(),false, proxy, c->atom(QXcbAtom::AtomXdndProxy), XCB_ATOM_WINDOW,0,1);if(reply && reply->type == XCB_ATOM_WINDOW) { xcb_window_t p = *((xcb_window_t *)xcb_get_property_value(reply.get()));if(proxy != p) proxy = XCB_NONE;}else{ proxy = XCB_NONE;}return proxy;}class QXcbDropData :public QXcbMime {public:QXcbDropData(QXcbDrag *d);~QXcbDropData();protected:boolhasFormat_sys(const QString &mimeType)const override; QStringList formats_sys()const override; QVariant retrieveData_sys(const QString &mimeType, QMetaType type)const override; QVariant xdndObtainData(const QByteArray &format, QMetaType requestedType)const; QXcbDrag *drag;};QXcbDrag::QXcbDrag(QXcbConnection *c) :QXcbObject(c){ m_dropData =newQXcbDropData(this);init();}QXcbDrag::~QXcbDrag(){delete m_dropData;}voidQXcbDrag::init(){ currentWindow.clear(); accepted_drop_action =Qt::IgnoreAction; xdnd_dragsource = XCB_NONE; waiting_for_status =false; current_target = XCB_NONE; current_proxy_target = XCB_NONE; source_time = XCB_CURRENT_TIME; target_time = XCB_CURRENT_TIME;QXcbCursor::queryPointer(connection(), ¤t_virtual_desktop,nullptr); drag_types.clear();//current_embedding_widget = 0; dropped =false; canceled =false; source_sameanswer =QRect();}boolQXcbDrag::eventFilter(QObject *o, QEvent *e){/* We are setting a mouse grab on the QShapedPixmapWindow in order not to * lose the grab when the virtual desktop changes, but * QBasicDrag::eventFilter() expects the events to be coming from the * window where the drag was started. */if(initiatorWindow && o ==shapedPixmapWindow()) o = initiatorWindow.data();returnQBasicDrag::eventFilter(o, e);}voidQXcbDrag::startDrag(){init();qCDebug(lcQpaXDnd) <<"starting drag where source:"<<connection()->qtSelectionOwner();xcb_set_selection_owner(xcb_connection(),connection()->qtSelectionOwner(),atom(QXcbAtom::AtomXdndSelection),connection()->time()); QStringList fmts =QXcbMime::formatsHelper(drag()->mimeData());for(int i =0; i < fmts.size(); ++i) { QList<xcb_atom_t> atoms =QXcbMime::mimeAtomsForFormat(connection(), fmts.at(i));for(int j =0; j < atoms.size(); ++j) {if(!drag_types.contains(atoms.at(j))) drag_types.append(atoms.at(j));}}if(drag_types.size() >3)xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE,connection()->qtSelectionOwner(),atom(QXcbAtom::AtomXdndTypelist), XCB_ATOM_ATOM,32, drag_types.size(), (const void*)drag_types.constData());setUseCompositing(current_virtual_desktop->compositingActive());setScreen(current_virtual_desktop->screens().constFirst()->screen()); initiatorWindow =QGuiApplicationPrivate::currentMouseWindow;QBasicDrag::startDrag();if(connection()->mouseGrabber() ==nullptr)shapedPixmapWindow()->setMouseGrabEnabled(true);auto nativePixelPos =QHighDpi::toNativePixels(QCursor::pos(), initiatorWindow.data());move(nativePixelPos,QGuiApplication::mouseButtons(),QGuiApplication::keyboardModifiers());}voidQXcbDrag::endDrag(){QBasicDrag::endDrag();if(!dropped && !canceled &&canDrop()) {// Set executed drop action when dropping outside application.setExecutedDropAction(accepted_drop_action);} initiatorWindow.clear();}Qt::DropAction QXcbDrag::defaultAction(Qt::DropActions possibleActions,Qt::KeyboardModifiers modifiers)const{if(currentDrag() || drop_actions.isEmpty())returnQBasicDrag::defaultAction(possibleActions, modifiers);returntoDropAction(drop_actions.first());}voidQXcbDrag::handlePropertyNotifyEvent(const xcb_property_notify_event_t *event){if(event->window != xdnd_dragsource || event->atom !=atom(QXcbAtom::AtomXdndActionList))return;readActionList();}staticboolwindowInteractsWithPosition(xcb_connection_t *connection,const QPoint & pos, xcb_window_t w, xcb_shape_sk_t shapeType){bool interacts =false;auto reply =Q_XCB_REPLY(xcb_shape_get_rectangles, connection, w, shapeType);if(reply) { xcb_rectangle_t *rectangles =xcb_shape_get_rectangles_rectangles(reply.get());if(rectangles) {const int nRectangles =xcb_shape_get_rectangles_rectangles_length(reply.get());for(int i =0; !interacts && i < nRectangles; ++i) { interacts =QRect(rectangles[i].x, rectangles[i].y, rectangles[i].width, rectangles[i].height).contains(pos);}}}return interacts;} xcb_window_t QXcbDrag::findRealWindow(const QPoint & pos, xcb_window_t w,int md,bool ignoreNonXdndAwareWindows){if(w ==shapedPixmapWindow()->handle()->winId())return0;if(md) {auto reply =Q_XCB_REPLY(xcb_get_window_attributes,xcb_connection(), w);if(!reply)return0;if(reply->map_state != XCB_MAP_STATE_VIEWABLE)return0;auto greply =Q_XCB_REPLY(xcb_get_geometry,xcb_connection(), w);if(!greply)return0; QRect windowRect(greply->x, greply->y, greply->width, greply->height);if(windowRect.contains(pos)) {bool windowContainsMouse = !ignoreNonXdndAwareWindows;{auto reply =Q_XCB_REPLY(xcb_get_property,xcb_connection(),false, w,connection()->atom(QXcbAtom::AtomXdndAware), XCB_GET_PROPERTY_TYPE_ANY,0,0);bool isAware = reply && reply->type != XCB_NONE;if(isAware) {const QPoint relPos = pos - windowRect.topLeft();// When ShapeInput and ShapeBounding are not set they return a single rectangle with the geometry of the window, this is why we// need to check both here so that in the case one is set and the other is not we still get the correct result.if(connection()->hasInputShape()) windowContainsMouse =windowInteractsWithPosition(xcb_connection(), relPos, w, XCB_SHAPE_SK_INPUT);if(windowContainsMouse &&connection()->hasXShape()) windowContainsMouse =windowInteractsWithPosition(xcb_connection(), relPos, w, XCB_SHAPE_SK_BOUNDING);if(!connection()->hasInputShape() && !connection()->hasXShape()) windowContainsMouse =true;if(windowContainsMouse)return w;}}auto reply =Q_XCB_REPLY(xcb_query_tree,xcb_connection(), w);if(!reply)return0;int nc =xcb_query_tree_children_length(reply.get()); xcb_window_t *c =xcb_query_tree_children(reply.get()); xcb_window_t r =0;for(uint i = nc; !r && i--;) r =findRealWindow(pos - windowRect.topLeft(), c[i], md-1, ignoreNonXdndAwareWindows);if(r)return r;// We didn't find a client window! Just use the// innermost window.// No children!if(!windowContainsMouse)return0;elsereturn w;}}return0;}boolQXcbDrag::findXdndAwareTarget(const QPoint &globalPos, xcb_window_t *target_out){ xcb_window_t rootwin = current_virtual_desktop->root();auto translate =Q_XCB_REPLY(xcb_translate_coordinates,xcb_connection(), rootwin, rootwin, globalPos.x(), globalPos.y());if(!translate)return false; xcb_window_t target = translate->child;int lx = translate->dst_x;int ly = translate->dst_y;if(target && target != rootwin) { xcb_window_t src = rootwin;while(target !=0) {qCDebug(lcQpaXDnd) <<"checking target for XdndAware"<< target;auto translate =Q_XCB_REPLY(xcb_translate_coordinates,xcb_connection(), src, target, lx, ly);if(!translate) { target =0;break;} lx = translate->dst_x; ly = translate->dst_y; src = target; xcb_window_t child = translate->child;auto reply =Q_XCB_REPLY(xcb_get_property,xcb_connection(),false, target,atom(QXcbAtom::AtomXdndAware), XCB_GET_PROPERTY_TYPE_ANY,0,0);bool aware = reply && reply->type != XCB_NONE;if(aware) {qCDebug(lcQpaXDnd) <<"found XdndAware on"<< target;break;} target = child;}if(!target || target ==shapedPixmapWindow()->handle()->winId()) {qCDebug(lcQpaXDnd) <<"need to find real window"; target =findRealWindow(globalPos, rootwin,6,true);if(target ==0) target =findRealWindow(globalPos, rootwin,6,false);qCDebug(lcQpaXDnd) <<"real window found"<< target;}}*target_out = target;return true;}voidQXcbDrag::move(const QPoint &globalPos,Qt::MouseButtons b,Qt::KeyboardModifiers mods){// currentDrag() might be deleted while 'drag' is progressingif(!currentDrag()) {cancel();return;}// The source sends XdndEnter and XdndPosition to the target.if(source_sameanswer.contains(globalPos) && source_sameanswer.isValid())return; QXcbVirtualDesktop *virtualDesktop =nullptr; QPoint cursorPos;QXcbCursor::queryPointer(connection(), &virtualDesktop, &cursorPos); QXcbScreen *screen = virtualDesktop->screenAt(cursorPos); QPoint deviceIndependentPos =QHighDpiScaling::mapPositionFromNative(globalPos, screen);if(virtualDesktop != current_virtual_desktop) {setUseCompositing(virtualDesktop->compositingActive());recreateShapedPixmapWindow(static_cast<QPlatformScreen*>(screen)->screen(), deviceIndependentPos);if(connection()->mouseGrabber() ==nullptr)shapedPixmapWindow()->setMouseGrabEnabled(true); current_virtual_desktop = virtualDesktop;}else{QBasicDrag::moveShapedPixmapWindow(deviceIndependentPos);} xcb_window_t target;if(!findXdndAwareTarget(globalPos, &target))return; QXcbWindow *w =nullptr;if(target) { w =connection()->platformWindowFromId(target);if(w && (w->window()->type() ==Qt::Desktop)/*&& !w->acceptDrops()*/) w =nullptr;}else{ w =nullptr; target = current_virtual_desktop->root();} xcb_window_t proxy_target =xdndProxy(connection(), target);if(!proxy_target) proxy_target = target;int target_version =1;if(proxy_target) {auto reply =Q_XCB_REPLY(xcb_get_property,xcb_connection(),false, proxy_target,atom(QXcbAtom::AtomXdndAware), XCB_GET_PROPERTY_TYPE_ANY,0,1);if(!reply || reply->type == XCB_NONE) { target =0;}else{ target_version = *(uint32_t*)xcb_get_property_value(reply.get()); target_version =qMin(xdnd_version, target_version ? target_version :1);}}if(target != current_target) {if(current_target)send_leave(); current_target = target; current_proxy_target = proxy_target;if(target) {int flags = target_version <<24;if(drag_types.size() >3) flags |=0x0001; xcb_client_message_event_t enter; enter.response_type = XCB_CLIENT_MESSAGE; enter.sequence =0; enter.window = target; enter.format =32; enter.type =atom(QXcbAtom::AtomXdndEnter); enter.data.data32[0] =connection()->qtSelectionOwner(); enter.data.data32[1] = flags; enter.data.data32[2] = drag_types.size() >0? drag_types.at(0) :0; enter.data.data32[3] = drag_types.size() >1? drag_types.at(1) :0; enter.data.data32[4] = drag_types.size() >2? drag_types.at(2) :0;// provisionally set the rectangle to 5x5 pixels... source_sameanswer =QRect(globalPos.x() -2, globalPos.y() -2,5,5);qCDebug(lcQpaXDnd) <<"sending XdndEnter to target:"<< target;if(w)handleEnter(w, &enter, current_proxy_target);else if(target)xcb_send_event(xcb_connection(),false, proxy_target, XCB_EVENT_MASK_NO_EVENT, (const char*)&enter); waiting_for_status =false;}}if(waiting_for_status)return;if(target) { waiting_for_status =true;// The source sends a ClientMessage of type XdndPosition. This tells the target the// position of the mouse and the action that the user requested. xcb_client_message_event_t move; move.response_type = XCB_CLIENT_MESSAGE; move.sequence =0; move.window = target; move.format =32; move.type =atom(QXcbAtom::AtomXdndPosition); move.data.data32[0] =connection()->qtSelectionOwner(); move.data.data32[1] =0;// flags move.data.data32[2] = (globalPos.x() <<16) + globalPos.y(); move.data.data32[3] =connection()->time();constauto supportedActions =currentDrag()->supportedActions();constauto requestedAction =defaultAction(supportedActions, mods); move.data.data32[4] =toXdndAction(requestedAction);qCDebug(lcQpaXDnd) <<"sending XdndPosition to target:"<< target; source_time =connection()->time();if(w) {handle_xdnd_position(w, &move, b, mods);}else{setActionList(requestedAction, supportedActions);xcb_send_event(xcb_connection(),false, proxy_target, XCB_EVENT_MASK_NO_EVENT, (const char*)&move);}}static const bool isUnity =qgetenv("XDG_CURRENT_DESKTOP").toLower() =="unity";if(isUnity && xdndCollectionWindow == XCB_NONE) { QString name =QXcbWindow::windowTitle(connection(), target);if(name =="XdndCollectionWindowImp"_L1) xdndCollectionWindow = target;}if(target == xdndCollectionWindow) {setCanDrop(false);updateCursor(Qt::IgnoreAction);}}voidQXcbDrag::drop(const QPoint &globalPos,Qt::MouseButtons b,Qt::KeyboardModifiers mods){// XdndDrop is sent from source to target to complete the drop.QBasicDrag::drop(globalPos, b, mods);if(!current_target)return; xcb_client_message_event_t drop; drop.response_type = XCB_CLIENT_MESSAGE; drop.sequence =0; drop.window = current_target; drop.format =32; drop.type =atom(QXcbAtom::AtomXdndDrop); drop.data.data32[0] =connection()->qtSelectionOwner(); drop.data.data32[1] =0;// flags drop.data.data32[2] =connection()->time(); drop.data.data32[3] =0; drop.data.data32[4] =currentDrag()->supportedActions(); QXcbWindow *w =connection()->platformWindowFromId(current_proxy_target);if(w && w->window()->type() ==Qt::Desktop)// && !w->acceptDrops() w =nullptr; Transaction t = {connection()->time(), current_target, current_proxy_target, w,// current_embeddig_widget,currentDrag(),QTime::currentTime()}; transactions.append(t);// timer is needed only for drops that came from other processes.if(!t.targetWindow && !cleanup_timer.isActive()) cleanup_timer.start(XdndDropTransactionTimeout,this);qCDebug(lcQpaXDnd) <<"sending drop to target:"<< current_target;if(w) {handleDrop(w, &drop, b, mods);}else{xcb_send_event(xcb_connection(),false, current_proxy_target, XCB_EVENT_MASK_NO_EVENT, (const char*)&drop);}}Qt::DropAction QXcbDrag::toDropAction(xcb_atom_t a)const{if(a ==atom(QXcbAtom::AtomXdndActionCopy) || a ==0)returnQt::CopyAction;if(a ==atom(QXcbAtom::AtomXdndActionLink))returnQt::LinkAction;if(a ==atom(QXcbAtom::AtomXdndActionMove))returnQt::MoveAction;returnQt::CopyAction;}Qt::DropActions QXcbDrag::toDropActions(const QList<xcb_atom_t> &atoms)const{Qt::DropActions actions;for(constauto actionAtom : atoms) {if(actionAtom !=atom(QXcbAtom::AtomXdndActionAsk)) actions |=toDropAction(actionAtom);}return actions;} xcb_atom_t QXcbDrag::toXdndAction(Qt::DropAction a)const{switch(a) {caseQt::CopyAction:returnatom(QXcbAtom::AtomXdndActionCopy);caseQt::LinkAction:returnatom(QXcbAtom::AtomXdndActionLink);caseQt::MoveAction:caseQt::TargetMoveAction:returnatom(QXcbAtom::AtomXdndActionMove);caseQt::IgnoreAction:return XCB_NONE;default:returnatom(QXcbAtom::AtomXdndActionCopy);}}voidQXcbDrag::readActionList(){ drop_actions.clear();auto reply =Q_XCB_REPLY(xcb_get_property,xcb_connection(),false, xdnd_dragsource,atom(QXcbAtom::AtomXdndActionList), XCB_ATOM_ATOM,0,1024);if(reply && reply->type != XCB_NONE && reply->format ==32) {int length =xcb_get_property_value_length(reply.get()) /4; xcb_atom_t *atoms = (xcb_atom_t *)xcb_get_property_value(reply.get());for(int i =0; i < length; ++i) drop_actions.append(atoms[i]);}}voidQXcbDrag::setActionList(Qt::DropAction requestedAction,Qt::DropActions supportedActions){#ifndef QT_NO_CLIPBOARD QList<xcb_atom_t> actions;if(requestedAction !=Qt::IgnoreAction) actions.append(toXdndAction(requestedAction));auto checkAppend = [this, requestedAction, supportedActions, &actions](Qt::DropAction action) {if(requestedAction != action && supportedActions & action) actions.append(toXdndAction(action));};checkAppend(Qt::CopyAction);checkAppend(Qt::MoveAction);checkAppend(Qt::LinkAction);if(current_actions != actions) {xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE,connection()->qtSelectionOwner(),atom(QXcbAtom::AtomXdndActionList), XCB_ATOM_ATOM,32, actions.size(), actions.constData()); current_actions = actions;}#endif}voidQXcbDrag::startListeningForActionListChanges(){connection()->addWindowEventListener(xdnd_dragsource,this);const uint32_t event_mask[] = { XCB_EVENT_MASK_PROPERTY_CHANGE };xcb_change_window_attributes(xcb_connection(), xdnd_dragsource, XCB_CW_EVENT_MASK, event_mask);}voidQXcbDrag::stopListeningForActionListChanges(){const uint32_t event_mask[] = { XCB_EVENT_MASK_NO_EVENT };xcb_change_window_attributes(xcb_connection(), xdnd_dragsource, XCB_CW_EVENT_MASK, event_mask);connection()->removeWindowEventListener(xdnd_dragsource);}intQXcbDrag::findTransactionByWindow(xcb_window_t window){int at = -1;for(int i =0; i < transactions.size(); ++i) {const Transaction &t = transactions.at(i);if(t.target == window || t.proxy_target == window) { at = i;break;}}return at;}intQXcbDrag::findTransactionByTime(xcb_timestamp_t timestamp){int at = -1;for(int i =0; i < transactions.size(); ++i) {const Transaction &t = transactions.at(i);if(t.timestamp == timestamp) { at = i;break;}}return at;}#if 0// for embedding onlystatic QWidget* current_embedding_widget =nullptr;static xcb_client_message_event_t last_enter_event;static boolcheckEmbedded(QWidget* w,const XEvent* xe){if(!w)return false;if(current_embedding_widget !=0&& current_embedding_widget != w) { current_target = ((QExtraWidget*)current_embedding_widget)->extraData()->xDndProxy; current_proxy_target = current_target;qt_xdnd_send_leave(); current_target =0; current_proxy_target =0; current_embedding_widget =0;} QWExtra* extra = ((QExtraWidget*)w)->extraData();if(extra && extra->xDndProxy !=0) {if(current_embedding_widget != w) { last_enter_event.xany.window = extra->xDndProxy;XSendEvent(X11->display, extra->xDndProxy, False, NoEventMask, &last_enter_event); current_embedding_widget = w;}((XEvent*)xe)->xany.window = extra->xDndProxy;XSendEvent(X11->display, extra->xDndProxy, False, NoEventMask, (XEvent*)xe);if(currentWindow != w) { currentWindow = w;}return true;} current_embedding_widget =0;return false;}#endifvoidQXcbDrag::handleEnter(QPlatformWindow *,const xcb_client_message_event_t *event, xcb_window_t proxy){// The target receives XdndEnter.qCDebug(lcQpaXDnd) <<"target:"<< event->window <<"received XdndEnter"; xdnd_types.clear();int version = (int)(event->data.data32[1] >>24);if(version > xdnd_version)return; xdnd_dragsource = event->data.data32[0];startListeningForActionListChanges();readActionList();if(!proxy) proxy =xdndProxy(connection(), xdnd_dragsource); current_proxy_target = proxy ? proxy : xdnd_dragsource;if(event->data.data32[1] &1) {// get the types from XdndTypeListauto reply =Q_XCB_REPLY(xcb_get_property,xcb_connection(),false, xdnd_dragsource,atom(QXcbAtom::AtomXdndTypelist), XCB_ATOM_ATOM,0, xdnd_max_type);if(reply && reply->type != XCB_NONE && reply->format ==32) {int length =xcb_get_property_value_length(reply.get()) /4;if(length > xdnd_max_type) length = xdnd_max_type; xcb_atom_t *atoms = (xcb_atom_t *)xcb_get_property_value(reply.get()); xdnd_types.reserve(length);for(int i =0; i < length; ++i) xdnd_types.append(atoms[i]);}}else{// get the types from the messagefor(int i =2; i <5; i++) {if(event->data.data32[i]) xdnd_types.append(event->data.data32[i]);}}for(int i =0; i < xdnd_types.size(); ++i)qCDebug(lcQpaXDnd) <<" "<<connection()->atomName(xdnd_types.at(i));}voidQXcbDrag::handle_xdnd_position(QPlatformWindow *w,const xcb_client_message_event_t *e,Qt::MouseButtons b,Qt::KeyboardModifiers mods){// The target receives XdndPosition. The target window must determine which widget the mouse// is in and ask it whether or not it will accept the drop.qCDebug(lcQpaXDnd) <<"target:"<< e->window <<"received XdndPosition"; QPoint p((e->data.data32[2] &0xffff0000) >>16, e->data.data32[2] &0x0000ffff);Q_ASSERT(w); QRect geometry = w->geometry(); p -= w->isEmbedded() ? w->mapToGlobal(geometry.topLeft()) : geometry.topLeft();if(!w || !w->window() || (w->window()->type() ==Qt::Desktop))return;if(Q_UNLIKELY(e->data.data32[0] != xdnd_dragsource)) {qCDebug(lcQpaXDnd,"xdnd drag position from unexpected source (%x not %x)", e->data.data32[0], xdnd_dragsource);return;} currentPosition = p; currentWindow = w->window();// timestamp from the sourceif(e->data.data32[3] != XCB_NONE) { target_time = e->data.data32[3];} QMimeData *dropData =nullptr;Qt::DropActions supported_actions =Qt::IgnoreAction;if(currentDrag()) { dropData =currentDrag()->mimeData(); supported_actions =currentDrag()->supportedActions();}else{ dropData = m_dropData; supported_actions =toDropActions(drop_actions);if(e->data.data32[4] !=atom(QXcbAtom::AtomXdndActionAsk)) supported_actions |=Qt::DropActions(toDropAction(e->data.data32[4]));}auto buttons =currentDrag() ? b :connection()->queryMouseButtons();auto modifiers =currentDrag() ? mods :connection()->keyboard()->queryKeyboardModifiers(); QPlatformDragQtResponse qt_response =QWindowSystemInterface::handleDrag( w->window(), dropData, p, supported_actions, buttons, modifiers);// ### FIXME ? - answerRect appears to be unused. QRect answerRect(p + geometry.topLeft(),QSize(1,1)); answerRect = qt_response.answerRect().translated(geometry.topLeft()).intersected(geometry);// The target sends a ClientMessage of type XdndStatus. This tells the source whether or not// it will accept the drop, and, if so, what action will be taken. It also includes a rectangle// that means "don't send another XdndPosition message until the mouse moves out of here". xcb_client_message_event_t response; response.response_type = XCB_CLIENT_MESSAGE; response.sequence =0; response.window = xdnd_dragsource; response.format =32; response.type =atom(QXcbAtom::AtomXdndStatus); response.data.data32[0] =xcb_window(w); response.data.data32[1] = qt_response.isAccepted();// flags response.data.data32[2] =0;// x, y response.data.data32[3] =0;// w, h response.data.data32[4] =toXdndAction(qt_response.acceptedAction());// action accepted_drop_action = qt_response.acceptedAction();if(answerRect.left() <0) answerRect.setLeft(0);if(answerRect.right() >4096) answerRect.setRight(4096);if(answerRect.top() <0) answerRect.setTop(0);if(answerRect.bottom() >4096) answerRect.setBottom(4096);if(answerRect.width() <0) answerRect.setWidth(0);if(answerRect.height() <0) answerRect.setHeight(0);// reset target_time = XCB_CURRENT_TIME;qCDebug(lcQpaXDnd) <<"sending XdndStatus to source:"<< xdnd_dragsource;if(xdnd_dragsource ==connection()->qtSelectionOwner())handle_xdnd_status(&response);elsexcb_send_event(xcb_connection(),false, current_proxy_target, XCB_EVENT_MASK_NO_EVENT, (const char*)&response);}namespace{class ClientMessageScanner {public:ClientMessageScanner(xcb_atom_t a) :atom(a) {} xcb_atom_t atom;booloperator() (xcb_generic_event_t *event,int type)const{if(type != XCB_CLIENT_MESSAGE)return false;auto clientMessage =reinterpret_cast<xcb_client_message_event_t *>(event);return clientMessage->type == atom;}};}voidQXcbDrag::handlePosition(QPlatformWindow * w,const xcb_client_message_event_t *event){ xcb_client_message_event_t *lastEvent =const_cast<xcb_client_message_event_t *>(event); ClientMessageScanner scanner(atom(QXcbAtom::AtomXdndPosition));while(auto nextEvent =connection()->eventQueue()->peek(scanner)) {if(lastEvent != event)free(lastEvent); lastEvent =reinterpret_cast<xcb_client_message_event_t *>(nextEvent);}handle_xdnd_position(w, lastEvent);if(lastEvent != event)free(lastEvent);}voidQXcbDrag::handle_xdnd_status(const xcb_client_message_event_t *event){// The source receives XdndStatus. It can use the action to change the cursor to indicate// whether or not the user's requested action will be performed.qCDebug(lcQpaXDnd) <<"source:"<< event->window <<"received XdndStatus"; waiting_for_status =false;// ignore late status messagesif(event->data.data32[0] && event->data.data32[0] != current_target)return;const bool dropPossible = event->data.data32[1];setCanDrop(dropPossible);if(dropPossible) { accepted_drop_action =toDropAction(event->data.data32[4]);updateCursor(accepted_drop_action);}else{updateCursor(Qt::IgnoreAction);}if((event->data.data32[1] &2) ==0) { QPoint p((event->data.data32[2] &0xffff0000) >>16, event->data.data32[2] &0x0000ffff); QSize s((event->data.data32[3] &0xffff0000) >>16, event->data.data32[3] &0x0000ffff); source_sameanswer =QRect(p, s);}else{ source_sameanswer =QRect();}}voidQXcbDrag::handleStatus(const xcb_client_message_event_t *event){if(event->window !=connection()->qtSelectionOwner() || !drag())return; xcb_client_message_event_t *lastEvent =const_cast<xcb_client_message_event_t *>(event); xcb_generic_event_t *nextEvent; ClientMessageScanner scanner(atom(QXcbAtom::AtomXdndStatus));while((nextEvent =connection()->eventQueue()->peek(scanner))) {if(lastEvent != event)free(lastEvent); lastEvent = (xcb_client_message_event_t *)nextEvent;}handle_xdnd_status(lastEvent);if(lastEvent != event)free(lastEvent);}voidQXcbDrag::handleLeave(QPlatformWindow *w,const xcb_client_message_event_t *event){// If the target receives XdndLeave, it frees any cached data and forgets the whole incident.qCDebug(lcQpaXDnd) <<"target:"<< event->window <<"received XdndLeave";if(!currentWindow || w != currentWindow.data()->handle()) {stopListeningForActionListChanges();return;// sanity}// ###// if (checkEmbedded(current_embedding_widget, event)) {// current_embedding_widget = 0;// currentWindow.clear();// return;// }if(event->data.data32[0] != xdnd_dragsource) {// This often happens - leave other-process window quicklyqCDebug(lcQpaXDnd,"xdnd drag leave from unexpected source (%x not %x", event->data.data32[0], xdnd_dragsource);}stopListeningForActionListChanges();QWindowSystemInterface::handleDrag(w->window(),nullptr,QPoint(),Qt::IgnoreAction, { }, { });}voidQXcbDrag::send_leave(){// XdndLeave is sent from the source to the target to cancel the drop.if(!current_target)return; xcb_client_message_event_t leave; leave.response_type = XCB_CLIENT_MESSAGE; leave.sequence =0; leave.window = current_target; leave.format =32; leave.type =atom(QXcbAtom::AtomXdndLeave); leave.data.data32[0] =connection()->qtSelectionOwner(); leave.data.data32[1] =0;// flags leave.data.data32[2] =0;// x, y leave.data.data32[3] =0;// w, h leave.data.data32[4] =0;// just null QXcbWindow *w =connection()->platformWindowFromId(current_proxy_target);if(w && (w->window()->type() ==Qt::Desktop)/*&& !w->acceptDrops()*/) w =nullptr;qCDebug(lcQpaXDnd) <<"sending XdndLeave to target:"<< current_target;if(w)handleLeave(w, (const xcb_client_message_event_t *)&leave);elsexcb_send_event(xcb_connection(),false,current_proxy_target, XCB_EVENT_MASK_NO_EVENT, (const char*)&leave);}voidQXcbDrag::handleDrop(QPlatformWindow *,const xcb_client_message_event_t *event,Qt::MouseButtons b,Qt::KeyboardModifiers mods){// Target receives XdndDrop. Once it is finished processing the drop, it sends XdndFinished.qCDebug(lcQpaXDnd) <<"target:"<< event->window <<"received XdndDrop";if(!currentWindow) {stopListeningForActionListChanges(); xdnd_dragsource =0;return;// sanity}const uint32_t*l = event->data.data32;if(l[0] != xdnd_dragsource) {qCDebug(lcQpaXDnd,"xdnd drop from unexpected source (%x not %x", l[0], xdnd_dragsource);return;}// update the "user time" from the timestamp in the event.if(l[2] !=0) target_time = l[2];Qt::DropActions supported_drop_actions; QMimeData *dropData =nullptr;// this could be a same-application drop, just proxied due to// some XEMBEDding, so try to find the real QMimeData used// based on the timestamp for this drop.int at =findTransactionByTime(target_time);if(at != -1) {qCDebug(lcQpaXDnd) <<"found one transaction via findTransactionByTime()"; dropData = transactions.at(at).drag->mimeData();// Can't use the source QMimeData if we need the image conversion code from xdndObtainDataif(dropData && dropData->hasImage()) dropData =0;}// if we can't find it, then use the data in the drag managerif(currentDrag()) {if(!dropData) dropData =currentDrag()->mimeData(); supported_drop_actions =Qt::DropActions(l[4]);}else{if(!dropData) dropData = m_dropData; supported_drop_actions = accepted_drop_action |toDropActions(drop_actions);}if(!dropData)return;auto buttons =currentDrag() ? b :connection()->queryMouseButtons();auto modifiers =currentDrag() ? mods :connection()->keyboard()->queryKeyboardModifiers(); QPlatformDropQtResponse response =QWindowSystemInterface::handleDrop( currentWindow.data(), dropData, currentPosition, supported_drop_actions, buttons, modifiers);Qt::DropAction acceptedAaction = response.acceptedAction();if(!response.isAccepted()) {// Ignore a failed drag acceptedAaction =Qt::IgnoreAction;}setExecutedDropAction(acceptedAaction); xcb_client_message_event_t finished = {}; finished.response_type = XCB_CLIENT_MESSAGE; finished.sequence =0; finished.window = xdnd_dragsource; finished.format =32; finished.type =atom(QXcbAtom::AtomXdndFinished); finished.data.data32[0] = currentWindow ?xcb_window(currentWindow.data()) : XCB_NONE; finished.data.data32[1] = response.isAccepted();// flags finished.data.data32[2] =toXdndAction(acceptedAaction);qCDebug(lcQpaXDnd) <<"sending XdndFinished to source:"<< xdnd_dragsource;xcb_send_event(xcb_connection(),false, current_proxy_target, XCB_EVENT_MASK_NO_EVENT, (char*)&finished);stopListeningForActionListChanges(); dropped =true;}voidQXcbDrag::handleFinished(const xcb_client_message_event_t *event){// Source receives XdndFinished when target is done processing the drop data.qCDebug(lcQpaXDnd) <<"source:"<< event->window <<"received XdndFinished";if(event->window !=connection()->qtSelectionOwner())return;if(xcb_window_t w = event->data.data32[0]) {int at =findTransactionByWindow(w);if(at != -1) { Transaction t = transactions.takeAt(at);if(t.drag) t.drag->deleteLater();// QDragManager *manager = QDragManager::self();// Window target = current_target;// Window proxy_target = current_proxy_target;// QWidget *embedding_widget = current_embedding_widget;// QDrag *currentObject = manager->object;// current_target = t.target;// current_proxy_target = t.proxy_target;// current_embedding_widget = t.embedding_widget;// manager->object = t.object;// if (!passive)// (void) checkEmbedded(currentWindow, xe);// current_embedding_widget = 0;// current_target = 0;// current_proxy_target = 0;// current_target = target;// current_proxy_target = proxy_target;// current_embedding_widget = embedding_widget;// manager->object = currentObject;}else{qWarning("QXcbDrag::handleFinished - drop data has expired");}} waiting_for_status =false;}voidQXcbDrag::timerEvent(QTimerEvent* e){if(e->id() == cleanup_timer.id()) {bool stopTimer =true;for(int i =0; i < transactions.size(); ++i) {const Transaction &t = transactions.at(i);if(t.targetWindow) {// dnd within the same process, don't delete, these are taken care of// in handleFinished()continue;} QTime currentTime =QTime::currentTime();std::chrono::milliseconds delta{t.time.msecsTo(currentTime)};if(delta > XdndDropTransactionTimeout) {/* delete transactions which are older than XdndDropTransactionTimeout. It could mean one of these: - client has crashed and as a result we have never received XdndFinished - showing dialog box on drop event where user's response takes more time than XdndDropTransactionTimeout (QTBUG-14493) - dnd takes unusually long time to process data */if(t.drag) t.drag->deleteLater(); transactions.removeAt(i--);}else{ stopTimer =false;}}if(stopTimer) cleanup_timer.stop();}}voidQXcbDrag::cancel(){qCDebug(lcQpaXDnd) <<"dnd was canceled";QBasicDrag::cancel();if(current_target)send_leave();// remove canceled objectif(currentDrag())currentDrag()->deleteLater(); canceled =true;}static xcb_window_t findXdndAwareParent(QXcbConnection *c, xcb_window_t window){ xcb_window_t target =0; forever {// check if window has XdndAwareauto gpReply =Q_XCB_REPLY(xcb_get_property, c->xcb_connection(),false, window, c->atom(QXcbAtom::AtomXdndAware), XCB_GET_PROPERTY_TYPE_ANY,0,0);bool aware = gpReply && gpReply->type != XCB_NONE;if(aware) { target = window;break;}// try window's parentauto qtReply =Q_XCB_REPLY_UNCHECKED(xcb_query_tree, c->xcb_connection(), window);if(!qtReply)break; xcb_window_t root = qtReply->root; xcb_window_t parent = qtReply->parent;if(window == root)break; window = parent;}return target;}voidQXcbDrag::handleSelectionRequest(const xcb_selection_request_event_t *event){qCDebug(lcQpaXDnd) <<"handle selection request from target:"<< event->requestor; q_padded_xcb_event<xcb_selection_notify_event_t> notify = {}; notify.response_type = XCB_SELECTION_NOTIFY; notify.requestor = event->requestor; notify.selection = event->selection; notify.target = XCB_NONE; notify.property = XCB_NONE; notify.time = event->time;// which transaction do we use? (note: -2 means use current currentDrag())int at = -1;// figure out which data the requestor is really interested inif(currentDrag() && event->time == source_time) {// requestor wants the current drag data at = -2;}else{// if someone has requested data in response to XdndDrop, find the corresponding transaction. the// spec says to call xcb_convert_selection() using the timestamp from the XdndDrop at =findTransactionByTime(event->time);if(at == -1) {// no dice, perhaps the client was nice enough to use the same window id in// xcb_convert_selection() that we sent the XdndDrop event to. at =findTransactionByWindow(event->requestor);}if(at == -1) { xcb_window_t target =findXdndAwareParent(connection(), event->requestor);if(target) {if(event->time == XCB_CURRENT_TIME && current_target == target) at = -2;else at =findTransactionByWindow(target);}}} QDrag *transactionDrag =nullptr;if(at >=0) { transactionDrag = transactions.at(at).drag;}else if(at == -2) { transactionDrag =currentDrag();}if(transactionDrag) { xcb_atom_t atomFormat = event->target;int dataFormat =0; QByteArray data;if(QXcbMime::mimeDataForAtom(connection(), event->target, transactionDrag->mimeData(),&data, &atomFormat, &dataFormat)) {int dataSize = data.size() / (dataFormat /8);xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, event->requestor, event->property, atomFormat, dataFormat, dataSize, (const void*)data.constData()); notify.property = event->property; notify.target = atomFormat;}} xcb_window_t proxy_target =xdndProxy(connection(), event->requestor);if(!proxy_target) proxy_target = event->requestor;xcb_send_event(xcb_connection(),false, proxy_target, XCB_EVENT_MASK_NO_EVENT, (const char*)¬ify);}boolQXcbDrag::dndEnable(QXcbWindow *w,bool on){qCDebug(lcQpaXDnd) <<"dndEnable"<<static_cast<QPlatformWindow *>(w) << on;// Windows announce that they support the XDND protocol by creating a window property XdndAware.if(on) { QXcbWindow *window =nullptr;if(w->window()->type() ==Qt::Desktop) {if(desktop_proxy)// *WE* already have one.return false; QXcbConnectionGrabber grabber(connection());// As per Xdnd4, use XdndProxy xcb_window_t proxy_id =xdndProxy(connection(), w->xcb_window());if(!proxy_id) { desktop_proxy =new QWindow; window =static_cast<QXcbWindow *>(desktop_proxy->handle()); proxy_id = window->xcb_window(); xcb_atom_t xdnd_proxy =atom(QXcbAtom::AtomXdndProxy);xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, w->xcb_window(), xdnd_proxy, XCB_ATOM_WINDOW,32,1, &proxy_id);xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, proxy_id, xdnd_proxy, XCB_ATOM_WINDOW,32,1, &proxy_id);}}else{ window = w;}if(window) {qCDebug(lcQpaXDnd) <<"setting XdndAware for"<< window->xcb_window(); xcb_atom_t atm = xdnd_version;xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, window->xcb_window(),atom(QXcbAtom::AtomXdndAware), XCB_ATOM_ATOM,32,1, &atm);return true;}else{return false;}}else{if(w->window()->type() ==Qt::Desktop) {xcb_delete_property(xcb_connection(), w->xcb_window(),atom(QXcbAtom::AtomXdndProxy));delete desktop_proxy; desktop_proxy =nullptr;}else{qCDebug(lcQpaXDnd) <<"not deleting XDndAware";}return true;}}boolQXcbDrag::ownsDragObject()const{return true;}QXcbDropData::QXcbDropData(QXcbDrag *d):QXcbMime(),drag(d){}QXcbDropData::~QXcbDropData(){} QVariant QXcbDropData::retrieveData_sys(const QString &mimetype, QMetaType requestedType)const{ QByteArray mime = mimetype.toLatin1(); QVariant data =xdndObtainData(mime, requestedType);return data;} QVariant QXcbDropData::xdndObtainData(const QByteArray &format, QMetaType requestedType)const{ QXcbConnection *c = drag->connection(); QXcbWindow *xcb_window = c->platformWindowFromId(drag->xdnd_dragsource);if(xcb_window && drag->currentDrag() && xcb_window->window()->type() !=Qt::Desktop) { QMimeData *data = drag->currentDrag()->mimeData();if(data->hasFormat(QLatin1StringView(format)))return data->data(QLatin1StringView(format));returnQVariant();} QList<xcb_atom_t> atoms = drag->xdnd_types;bool hasUtf8 =false; xcb_atom_t a =mimeAtomForFormat(c,QLatin1StringView(format), requestedType, atoms, &hasUtf8);if(a == XCB_NONE)returnQVariant();#ifndef QT_NO_CLIPBOARDif(c->selectionOwner(c->atom(QXcbAtom::AtomXdndSelection)) == XCB_NONE)returnQVariant();// should never happen? xcb_atom_t xdnd_selection = c->atom(QXcbAtom::AtomXdndSelection);conststd::optional<QByteArray> result = c->clipboard()->getSelection(xdnd_selection, a, xdnd_selection, drag->targetTime());if(!result.has_value())returnQVariant();returnmimeConvertToFormat(c, a, result.value(),QLatin1StringView(format), requestedType, hasUtf8);#elsereturnQVariant();#endif}boolQXcbDropData::hasFormat_sys(const QString &format)const{returnformats().contains(format);} QStringList QXcbDropData::formats_sys()const{ QStringList formats;for(int i =0; i < drag->xdnd_types.size(); ++i) { QString f =mimeAtomToString(drag->connection(), drag->xdnd_types.at(i));if(!formats.contains(f)) formats.append(f);}return formats;} QT_END_NAMESPACE
|