From: Damjan Jovanovic Subject: [PATCH 3/3] ole32/winex11.drv: implement dragging from Wine and dropping to X11 Message-Id: Date: Mon, 2 Mar 2015 06:32:43 +0200 The window used as the drag source on the X11 side is the clipboard's thread_selection_wnd(), which is fine as XDnD uses XdndSelection instead of the PRIMARY/CLIPBOARD selections, so both can happily coexist. Data is converted to X11 formats and cached during IDropTarget_DragEnter(), then provided to the drop target during selection requests. The file clipboard.c had to be patched a bit to export data in a form useful to XDnD, as well as to forward selection requests received between calls to the IDropTarget methods to xdnd.c. The complication is that ole32 implements DoDragDrop() using the win32 event loop, and IDropTarget methods block, while the exchange of XDnD messages over X11 is asynchronous, so winex11.drv's IDropTarget implementation is forced to selectively pump the event loop itself while waiting for the reply - for up to 500 milliseconds. However the clipboard does this too. Even so, X11 apps are slow to send XdndFinished, sometimes leading Windows apps to believe the drop failed when it didn't. Ideally the drag loop in ole32 would be broken up and made event-driven and asynchronous, so XDnD messages would be interpreted and re-posted to ole32's drag window as they come in while the drag message loop continues running in between them, that way much longer waits would be viable. At the moment, at least the 50 millisecond DRAG_TIMER_ID timer in ole32 was changed so it doesn't create a large backlog of WM_TIMER events - it is now stopped every time it fires and started again after OLEDD_TrackStateChange() returns. There's several other ways this could have been done: * copy or move the entire drag loop from ole32 to winex11.drv / winemac.drv and reimplement it using the native windowing functions. The problem then is that there is duplication of code in 2 display drivers if not in ole32 as well. * adopt every native window with create_foreign_window() when the mouse is first dragged over it, give it an IDropTarget that does conversion to XDnD, and thus treat all windows as HWND - transparently to ole32. Has the benefit of fewer changes to the user32 driver API, keeps the drag loop in ole32 so it's shared between X11 and Mac, but in my attempts to do this, the z-order of windows got messed up while dragging (it seems create_foreign_window() is only designed for XEMBED?). With this patch all 4 drag-and-drop scenarios on http://wiki.winehq.org/DragAndDrop become implemented, but the following still remains: * better FORMATETC supports, eg. tymeds other than HGLOBAL * XEMBED support * XdndProxy support * Supporting more clipboard formats and conversions between them. Apps that deal with files often want CFSTR_FILECONTENTS/CFSTR_FILEDESCRIPTOR which can't be implemented completely/correctly, as the X Direct Save protocol supports only a single application/octet-stream unlike the many streams Windows can have, and can supply a directory name to save files to but then Wine has to copy the streams from the application into files in that directory in a background thread, and even a directory name isn't always possible as the drop target might not be a directory that actually exists on disk either (eg. directory on remote server, a window selecting which files to compress, etc.). Otherwise dragging out of Wine works quite well and behaves just like native dragging does. Damjan Jovanovic --- dlls/ole32/ole2.c | 2 + dlls/winex11.drv/clipboard.c | 32 +++- dlls/winex11.drv/x11drv.h | 5 + dlls/winex11.drv/xdnd.c | 439 ++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 476 insertions(+), 2 deletions(-) diff --git a/dlls/ole32/ole2.c b/dlls/ole32/ole2.c index 1942135..f542d60 100644 --- a/dlls/ole32/ole2.c +++ b/dlls/ole32/ole2.c @@ -2213,7 +2213,9 @@ static LRESULT WINAPI OLEDD_DragTrackerWindowProc( { TrackerWindowInfo *trackerInfo = (TrackerWindowInfo*)GetWindowLongPtrA(hwnd, 0); if (trackerInfo->trackingDone) break; + KillTimer(hwnd, DRAG_TIMER_ID); OLEDD_TrackStateChange(trackerInfo); + SetTimer(hwnd, DRAG_TIMER_ID, 50, NULL); break; } case WM_DESTROY: diff --git a/dlls/winex11.drv/clipboard.c b/dlls/winex11.drv/clipboard.c index dad4a7a..cee1094 100644 --- a/dlls/winex11.drv/clipboard.c +++ b/dlls/winex11.drv/clipboard.c @@ -260,7 +260,7 @@ static UINT wSeqNo = 0; * Internal Clipboard implementation methods **************************************************************************/ -static Window thread_selection_wnd(void) +Window thread_selection_wnd(void) { struct x11drv_thread_data *thread_data = x11drv_init_thread_data(); Window w = thread_data->selection_wnd; @@ -1718,6 +1718,33 @@ HANDLE X11DRV_CLIPBOARD_ImportSelection(Display *d, Atom target, Window w, Atom return NULL; } +/************************************************************************** + * X11DRV_CLIPBOARD_ExportSelection + * + * Export the Windows data into X. + */ +HANDLE X11DRV_CLIPBOARD_ExportSelection(UINT windowsFormat, HGLOBAL hGlobal, Atom *atom, DWORD *cBytes) +{ + LPWINE_CLIPFORMAT format; + LIST_FOR_EACH_ENTRY( format, &format_list, WINE_CLIPFORMAT, entry ) + { + if ((format->wFormatID == windowsFormat) && + format->lpDrvExportFunc && format->drvData) + { + WINE_CLIPDATA wineClipdata; + wineClipdata.wFormatID = windowsFormat; + wineClipdata.hData = hGlobal; + wineClipdata.wFlags = 0; + wineClipdata.drvData = format->drvData; + wineClipdata.lpFormat = format; + HANDLE hClipData = format->lpDrvExportFunc(NULL, None, None, None, + &wineClipdata, cBytes); + *atom = format->drvData; + return hClipData; + } + } + return NULL; +} /************************************************************************** X11DRV_CLIPBOARD_ExportClipboardData @@ -3474,6 +3501,9 @@ static void X11DRV_HandleSelectionRequest( HWND hWnd, XSelectionRequestEvent *ev TRACE("\n"); + if (event->selection == x11drv_atom(XdndSelection)) + return X11DRV_XDND_SelectionRequest(event); + /* * We can only handle the selection request if : * The selection is PRIMARY or CLIPBOARD, AND we can successfully open the clipboard. diff --git a/dlls/winex11.drv/x11drv.h b/dlls/winex11.drv/x11drv.h index 7fe9845..88a55ff 100644 --- a/dlls/winex11.drv/x11drv.h +++ b/dlls/winex11.drv/x11drv.h @@ -238,7 +238,9 @@ extern void X11DRV_XDND_EnterEvent( HWND hWnd, XClientMessageEvent *event ) DECL extern void X11DRV_XDND_PositionEvent( HWND hWnd, XClientMessageEvent *event ) DECLSPEC_HIDDEN; extern void X11DRV_XDND_DropEvent( HWND hWnd, XClientMessageEvent *event ) DECLSPEC_HIDDEN; extern void X11DRV_XDND_LeaveEvent( HWND hWnd, XClientMessageEvent *event ) DECLSPEC_HIDDEN; +extern void X11DRV_XDND_SelectionRequest(XSelectionRequestEvent *event) DECLSPEC_HIDDEN; extern HANDLE X11DRV_CLIPBOARD_ImportSelection(Display *d, Atom target, Window w, Atom prop, UINT *windowsFormat) DECLSPEC_HIDDEN; +extern HANDLE X11DRV_CLIPBOARD_ExportSelection(UINT windowsFormat, HGLOBAL hGlobal, Atom *atom, DWORD *cBytes) DECLSPEC_HIDDEN; /************************************************************************** * X11 GDI driver @@ -349,6 +351,9 @@ static inline Display *thread_init_display(void) return x11drv_init_thread_data()->display; } +/* retrieve the thread selection window, creating it if needed */ +extern Window thread_selection_wnd(void) DECLSPEC_HIDDEN; + static inline size_t get_property_size( int format, unsigned long count ) { /* format==32 means long, which can be 64 bits... */ diff --git a/dlls/winex11.drv/xdnd.c b/dlls/winex11.drv/xdnd.c index fcd2841..20d698a 100644 --- a/dlls/winex11.drv/xdnd.c +++ b/dlls/winex11.drv/xdnd.c @@ -868,6 +868,411 @@ static IDataObjectVtbl xdndDataObjectVtbl = static IDataObject XDNDDataObject = { &xdndDataObjectVtbl }; +/* ====================================== + * Support for dragging from Wine to X11: + * ====================================== + */ + +static Bool is_selection_request_or_xdndstatus(Display *display, XEvent *event, XPointer arg) +{ + return (event->type == SelectionRequest && event->xselectionrequest.owner == thread_selection_wnd()) || + (event->type == ClientMessage && event->xclient.message_type == x11drv_atom(XdndStatus) && event->xclient.data.l[0] == (Window)arg); +} + +static Bool is_selection_request_or_xdndfinished(Display *display, XEvent *event, XPointer arg) +{ + return (event->type == SelectionRequest && event->xselectionrequest.owner == thread_selection_wnd()) || + (event->type == ClientMessage && event->xclient.message_type == x11drv_atom(XdndFinished) && event->xclient.data.l[0] == (Window)arg); +} + +static HRESULT win_to_xdnd_data(IDataObject *dataObject) +{ + HRESULT hr; + IEnumFORMATETC *enumFormatEtc = NULL; + FORMATETC formatEtc; + + X11DRV_XDND_FreeDragDropOp(); + + hr = IDataObject_EnumFormatEtc(dataObject, DATADIR_GET, &enumFormatEtc); + if (FAILED(hr)) + goto end; + hr = IEnumFORMATETC_Next(enumFormatEtc, 1, &formatEtc, NULL); + while (SUCCEEDED(hr) && hr != S_FALSE) + { + BOOL ok = TRUE; + if (formatEtc.dwAspect != DVASPECT_CONTENT) + { + TRACE("dwAspect isn't DVASPECT_CONTENT but %d which is unimplemented\n", formatEtc.dwAspect); + ok = FALSE; + } + if (formatEtc.lindex != -1) + { + FIXME("lindex isn't -1 but %d which is unimplemented\n", formatEtc.lindex); + ok = FALSE; + } + if (formatEtc.ptd != NULL) + { + FIXME("ptd isn't NULL which is unimplemented\n"); + ok = FALSE; + } + if (formatEtc.tymed != TYMED_HGLOBAL) + { + FIXME("tymed isn't TYMED_HGLOBAL but %d which is unimplemented\n", formatEtc.tymed); + ok = FALSE; + } + if (ok) + { + STGMEDIUM stgMedium; + char formatDesc[1024]; + + stgMedium.tymed = TYMED_HGLOBAL; + stgMedium.u.hGlobal = NULL; + stgMedium.pUnkForRelease = NULL; + X11DRV_XDND_DescribeClipboardFormat(formatEtc.cfFormat, formatDesc, sizeof(formatDesc)); + hr = IDataObject_GetData(dataObject, &formatEtc, &stgMedium); + if (SUCCEEDED(hr)) + { + Atom x11Atom = None; + DWORD cBytes = 0; + HANDLE data; + data = X11DRV_CLIPBOARD_ExportSelection(formatEtc.cfFormat, stgMedium.u.hGlobal, &x11Atom, &cBytes); + if (data) + { + TRACE("Exported clipboard format %d (%s)\n", formatEtc.cfFormat, formatDesc); + X11DRV_XDND_InsertXDNDData(x11Atom, formatEtc.cfFormat, data); + } + else + WARN("Exporting clipboard format %d (%s) failed\n", formatEtc.cfFormat, formatDesc); + } + else + WARN("IDataObject_GetData() for clipboard fromat %d (%s) failed, hr=%08x\n", formatEtc.cfFormat, formatDesc, hr); + } + hr = IEnumFORMATETC_Next(enumFormatEtc, 1, &formatEtc, NULL); + } + +end: + if (enumFormatEtc) + IEnumFORMATETC_Release(enumFormatEtc); + return hr; +} + +typedef struct { + IDropTarget IDropTarget_iface; + LONG ref; + int xdndVersion; + Window dropWindow; + DWORD mostRecentDropEffect; +} X11DropTarget; + +static inline X11DropTarget *impl_from_IDropTarget(IDropTarget *iface) +{ + return CONTAINING_RECORD(iface, X11DropTarget, IDropTarget_iface); +} + +static HRESULT WINAPI X11DropTarget_QueryInterface(IDropTarget *iface, + REFIID riid, void **ppvObject) +{ + TRACE("(%p, %s, %p)\n", iface, debugstr_guid(riid), ppvObject); + if (IsEqualIID(riid, &IID_IUnknown) || IsEqualIID(riid, &IID_IDropTarget)) + { + *ppvObject = iface; + IDropTarget_AddRef(iface); + return S_OK; + } + *ppvObject = NULL; + return E_NOINTERFACE; +} + +static ULONG WINAPI X11DropTarget_AddRef(IDropTarget *iface) +{ + X11DropTarget *This = impl_from_IDropTarget(iface); + TRACE("(%p)\n", iface); + return InterlockedIncrement(&This->ref); +} + +static ULONG WINAPI X11DropTarget_Release(IDropTarget *iface) +{ + LONG ref; + X11DropTarget *This = impl_from_IDropTarget(iface); + TRACE("(%p)\n", iface); + ref = InterlockedDecrement(&This->ref); + if (!ref) + { + X11DRV_XDND_FreeDragDropOp(); + XSetSelectionOwner(thread_display(), x11drv_atom(XdndSelection), None, CurrentTime); + HeapFree(GetProcessHeap(), 0, This); + } + return ref; +} + +static HRESULT WINAPI X11DropTarget_DragEnter(IDropTarget *iface, IDataObject *pDataObj, + DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) +{ + XClientMessageEvent e; + X11DropTarget *This = impl_from_IDropTarget(iface); + unsigned int xdndDataSize; + Atom *xdndTypeList; + XDNDDATA *current; + int index; + HRESULT hr; + + TRACE("(%p, %p, %u, POINTL(x=%d y=%d), %p)\n", iface, pDataObj, grfKeyState, + pt.x, pt.y, pdwEffect); + + hr = win_to_xdnd_data(pDataObj); + if (FAILED(hr)) + return hr; + xdndDataSize = list_count(&xdndData); + xdndTypeList = HeapAlloc(GetProcessHeap(), 0, xdndDataSize * sizeof(Atom)); + if (xdndTypeList == NULL) + return E_OUTOFMEMORY; + index = 0; + LIST_FOR_EACH_ENTRY(current, &xdndData, XDNDDATA, entry) + xdndTypeList[index++] = current->cf_xdnd; + XChangeProperty(thread_display(), thread_selection_wnd(), x11drv_atom(XdndTypeList), XA_ATOM, 32, + PropModeReplace, (const unsigned char*)xdndTypeList, xdndDataSize); + HeapFree(GetProcessHeap(), 0, xdndTypeList); + + e.type = ClientMessage; + e.display = thread_display(); + e.window = This->dropWindow; + e.message_type = x11drv_atom(XdndEnter); + e.format = 32; + e.data.l[0] = thread_selection_wnd(); + e.data.l[1] = (((This->xdndVersion < WINE_XDND_VERSION) ? This->xdndVersion : WINE_XDND_VERSION) << 24) | + ((xdndDataSize > 3) ? 1 : 0); + e.data.l[2] = None; + e.data.l[3] = None; + e.data.l[4] = None; + index = 2; + LIST_FOR_EACH_ENTRY(current, &xdndData, XDNDDATA, entry) + { + if (index <= 4) + e.data.l[index++] = current->cf_xdnd; + else + break; + } + TRACE("XdndEnter -> source X11 window = 0x%lx, XDnD version = %ld, more than 3 data types = %ld, first 3 data types = (%ld, %ld, %ld)\n", + e.data.l[0], (e.data.l[1] >> 24) & 0xff, e.data.l[1] & 0x1, e.data.l[2], e.data.l[3], e.data.l[4]); + XSendEvent(thread_display(), This->dropWindow, False, NoEventMask, (XEvent*)&e); + return S_OK; +} + +static HRESULT WINAPI X11DropTarget_DragOver(IDropTarget *iface, DWORD grfKeyState, + POINTL pt, DWORD *pdwEffect) +{ + XClientMessageEvent e; + POINT x11point; + X11DropTarget *This = impl_from_IDropTarget(iface); + int i; + + TRACE("(%p, %u, POINTL(x=%d y=%d), %p)\n", iface, grfKeyState, + pt.x, pt.y, pdwEffect); + + x11point = virtual_screen_to_root(pt.x, pt.y); + + e.type = ClientMessage; + e.display = thread_display(); + e.window = This->dropWindow; + e.message_type = x11drv_atom(XdndPosition); + e.format = 32; + e.data.l[0] = thread_selection_wnd(); + e.data.l[1] = None; + e.data.l[2] = (x11point.x << 16) | x11point.y; + e.data.l[3] = GetMessageTime() - EVENT_x11_time_to_win32_time(0); + e.data.l[4] = X11DRV_XDND_DROPEFFECTToXdndAction(*pdwEffect); + TRACE("XdndPosition -> source X11 window = 0x%lx, mouse x = %ld, mouse y = %ld, timestamp = %ld, action requested = %ld\n", + e.data.l[0], (e.data.l[2] >> 16) & 0xffff, e.data.l[2] & 0xffff, e.data.l[3], e.data.l[4]); + XSendEvent(thread_display(), This->dropWindow, False, NoEventMask, (XEvent*)&e); + XFlush(thread_display()); + + for (i = 0; i < SELECTION_RETRIES; i++) + { + XEvent xe; + /* In XDnD the drop target can request the data at any time. Be prepared. */ + Bool res = XCheckIfEvent(thread_display(), &xe, is_selection_request_or_xdndstatus, (char*)This->dropWindow); + if (res) + { + if (xe.type == ClientMessage) + { + TRACE("XdndStatus <- target X11 window = 0x%lx, accepting drop = %ld, wants XdndPosition while in (%ld, %ld, %ld, %ld) = %ld, " + "action accepted = %ld\n", + xe.xclient.data.l[0], xe.xclient.data.l[1] & 1, (xe.xclient.data.l[2] >> 16) & 0xffff, xe.xclient.data.l[2] & 0xffff, + (xe.xclient.data.l[3] >> 16) & 0xffff, xe.xclient.data.l[3] & 0xffff, (xe.xclient.data.l[1] >> 1) & 0x1, xe.xclient.data.l[4]); + if (xe.xclient.data.l[1] & 1) + { + if (xe.xclient.data.l[4] == None) + *pdwEffect = DROPEFFECT_NONE; + else + *pdwEffect = X11DRV_XDND_XdndActionToDROPEFFECT(xe.xclient.data.l[4]); + } + else + *pdwEffect = DROPEFFECT_NONE; + This->mostRecentDropEffect = *pdwEffect; + return S_OK; + } + else + X11DRV_SelectionRequest(NULL, &xe); + } + usleep(SELECTION_WAIT); + } + + ERR("XdndStatus <- NO RESPONSE from X11 window 0x%lx\n", This->dropWindow); + This->mostRecentDropEffect = *pdwEffect = DROPEFFECT_NONE; + return S_OK; +} + +static HRESULT WINAPI X11DropTarget_DragLeave(IDropTarget *iface) +{ + XClientMessageEvent e; + X11DropTarget *This = impl_from_IDropTarget(iface); + + TRACE("(%p)\n", iface); + + e.type = ClientMessage; + e.display = thread_display(); + e.window = This->dropWindow; + e.message_type = x11drv_atom(XdndLeave); + e.format = 32; + e.data.l[0] = thread_selection_wnd(); + e.data.l[1] = None; + e.data.l[2] = None; + e.data.l[3] = None; + e.data.l[4] = None; + TRACE("XdndLeave -> source X11 window = 0x%lx\n", e.data.l[0]); + XSendEvent(thread_display(), This->dropWindow, False, NoEventMask, (XEvent*)&e); + return S_OK; +} + +static HRESULT WINAPI X11DropTarget_Drop(IDropTarget *iface, IDataObject *pDataObj, + DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) +{ + XClientMessageEvent e; + int i; + X11DropTarget *This = impl_from_IDropTarget(iface); + + TRACE("(%p, %p, %u, POINTL(x=%d y=%d), %p)\n", iface, pDataObj, grfKeyState, + pt.x, pt.y, pdwEffect); + + /* XDnD does not give us a chance to give the drop target the new point and drop effect in XdndDrop, + * so send one last XdndPosition to give it those just before the XdndDrop */ + X11DropTarget_DragOver(iface, grfKeyState, pt, pdwEffect); + + e.type = ClientMessage; + e.display = thread_display(); + e.window = This->dropWindow; + e.message_type = x11drv_atom(XdndDrop); + e.format = 32; + e.data.l[0] = thread_selection_wnd(); + e.data.l[1] = None; + e.data.l[2] = GetMessageTime() - EVENT_x11_time_to_win32_time(0); + e.data.l[3] = None; + e.data.l[4] = None; + TRACE("XdndDrop -> source X11 window = 0x%lx, timestamp = %ld\n", e.data.l[0], e.data.l[2]); + XSendEvent(thread_display(), This->dropWindow, False, NoEventMask, (XEvent*)&e); + XFlush(thread_display()); + + for (i = 0; i < SELECTION_RETRIES; i++) + { + XEvent xe; + /* In XDnD the drop target can request the data at any time. Be prepared. */ + Bool res = XCheckIfEvent(thread_display(), &xe, is_selection_request_or_xdndfinished, (char*)This->dropWindow); + if (res) + { + if (xe.type == ClientMessage) + { + TRACE("XdndFinished <- target X11 window = 0x%lx, accepted drop = %ld, action performed (v5) = %ld\n", + xe.xclient.data.l[0], This->xdndVersion >= 5 ? xe.xclient.data.l[1] & 1 : 1, xe.xclient.data.l[2]); + if (This->xdndVersion >= 5) + { + if (xe.xclient.data.l[1] & 1) + { + if (xe.xclient.data.l[2] == None) + *pdwEffect = DROPEFFECT_NONE; + else + *pdwEffect = X11DRV_XDND_XdndActionToDROPEFFECT(xe.xclient.data.l[2]); + } + else + *pdwEffect = DROPEFFECT_NONE; + } + else + *pdwEffect = This->mostRecentDropEffect; + return S_OK; + } + else + X11DRV_SelectionRequest(NULL, &xe); + } + + usleep(SELECTION_WAIT); + } + ERR("XdndFinished <- NO RESPONSE from X11 window 0x%lx\n", This->dropWindow); + *pdwEffect = DROPEFFECT_NONE; + return S_OK; +} + +static IDropTargetVtbl dropTargetVtbl = +{ + X11DropTarget_QueryInterface, + X11DropTarget_AddRef, + X11DropTarget_Release, + X11DropTarget_DragEnter, + X11DropTarget_DragOver, + X11DropTarget_DragLeave, + X11DropTarget_Drop +}; + +/************************************************************************** + * X11DRV_XDND_SelectionRequest + */ +void X11DRV_XDND_SelectionRequest(XSelectionRequestEvent *event) +{ + XDNDDATA *current; + XSelectionEvent result; + BOOL found = FALSE; + + TRACE("Selection request from window 0x%lx for selection %ld, property %ld, target %ld\n", + event->requestor, event->selection, event->property, event->target); + + LIST_FOR_EACH_ENTRY(current, &xdndData, XDNDDATA, entry) + { + if (current->cf_xdnd == event->target) + { + int mode = PropModeReplace; + int cBytes; + char *data; + + cBytes = GlobalSize(current->contents); + data = GlobalLock(current->contents); + do + { + int nelements = min(cBytes, 65536); + XChangeProperty(event->display, event->requestor, event->property, event->target, + 8, mode, (const unsigned char*)data, nelements); + mode = PropModeAppend; + cBytes -= nelements; + data += nelements; + } while (cBytes > 0); + GlobalUnlock(current->contents); + found = TRUE; + break; + } + } + if (found) + TRACE("found the target\n"); + else + TRACE("didn't find the target\n"); + + result.type = SelectionNotify; + result.display = event->display; + result.requestor = event->requestor; + result.selection = event->selection; + result.property = event->property; + result.target = event->target; + result.time = event->time; + TRACE("Sending SelectionNotify event...\n"); + XSendEvent(thread_display(), event->requestor, False, NoEventMask, (XEvent*)&result); +} + /************************************************************************** * X11DRV_WineGetNativeWindowAt */ @@ -933,5 +1338,37 @@ ULONG_PTR CDECL X11DRV_WineGetNativeWindowAt(LONG x, LONG y) */ IDropTarget* CDECL X11DRV_WineGetNativeDroptarget(ULONG_PTR nativeWindow) { - return NULL; + Atom acttype; + int actfmt; + unsigned long count, bytesret; + Atom *xdndAwarePtr = NULL; + Atom xdndAware; + X11DropTarget *x11DropTarget = NULL; + + XGetWindowProperty(thread_display(), (Window) nativeWindow, x11drv_atom(XdndAware), + 0, 1, FALSE, XA_ATOM, &acttype, &actfmt, &count, + &bytesret, (unsigned char**)&xdndAwarePtr); + if (xdndAwarePtr) + { + xdndAware = *xdndAwarePtr; + XFree(xdndAwarePtr); + } + else + return NULL; + + + /* Only XDnD versions 3 and up are compatible with each other. GTK and Java don't support < 3 either. */ + if (xdndAware < 3) return NULL; + + x11DropTarget = HeapAlloc(GetProcessHeap(), 0, sizeof(X11DropTarget)); + if (x11DropTarget == NULL) return NULL; + + XSetSelectionOwner(thread_display(), x11drv_atom(XdndSelection), thread_selection_wnd(), CurrentTime); + + x11DropTarget->IDropTarget_iface.lpVtbl = &dropTargetVtbl; + x11DropTarget->ref = 1; + x11DropTarget->dropWindow = (Window) nativeWindow; + x11DropTarget->xdndVersion = xdndAware; + x11DropTarget->mostRecentDropEffect = DROPEFFECT_NONE; + return &x11DropTarget->IDropTarget_iface; }