From: Damjan Jovanovic Subject: [PATCH 2/7] winex11.drv: implement DoDragDrop on top of XDND (try 3) Message-Id: Date: Sat, 18 Jul 2015 10:42:45 +0200 Implements DoDragDrop on top of XDND. While some of DoDragDrop() and supporting functions are copied from ole32, they were modified to be non-blocking and event-driven. The clipboard is patched to allow searching its clipboard formats for X11 atoms, export data in X11 format, and forward selection requests for the XdndSelection selection to XDND. We support XDND version 3 to 5 (although we still need XdndProxy support for version 4). XdndPosition messages are rate-limited to at most 1 unreplied message and at least every 50 milliseconds apart, so that we don't flood slow X11 clients like the spec warns. Try 2 uses a FIXME instead of TRACE for an error, and fixes various typos. Try 3 adds the .spec file entry point for X11DRV_DoDragDrop. Damjan Jovanovic --- dlls/winex11.drv/clipboard.c | 50 +++ dlls/winex11.drv/event.c | 2 + dlls/winex11.drv/winex11.drv.spec | 1 + dlls/winex11.drv/x11drv.h | 7 + dlls/winex11.drv/xdnd.c | 837 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 897 insertions(+) diff --git a/dlls/winex11.drv/clipboard.c b/dlls/winex11.drv/clipboard.c index 6c6b3b9..eb5c567 100644 --- a/dlls/winex11.drv/clipboard.c +++ b/dlls/winex11.drv/clipboard.c @@ -1694,6 +1694,53 @@ HANDLE X11DRV_CLIPBOARD_ImportSelection(Display *d, Atom target, Window w, Atom /************************************************************************** + * X11DRV_CLIPBOARD_WindowsFormatToX11Atom + * + * Returns the X11 atom equivalent to the given windows format. + */ +Atom X11DRV_CLIPBOARD_WindowsFormatToX11Atom(UINT windowsFormat) +{ + LPWINE_CLIPFORMAT format; + LIST_FOR_EACH_ENTRY( format, &format_list, WINE_CLIPFORMAT, entry ) + { + if ((format->wFormatID == windowsFormat) && + format->lpDrvExportFunc && format->drvData) + { + return format->drvData; + } + } + return None; +} + +/************************************************************************** + * 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 * * Generic export clipboard data routine. @@ -3428,6 +3475,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/event.c b/dlls/winex11.drv/event.c index ee30259..9226a5b 100644 --- a/dlls/winex11.drv/event.c +++ b/dlls/winex11.drv/event.c @@ -1708,7 +1708,9 @@ static const struct client_message_handler client_messages[] = { XATOM_DndProtocol, handle_dnd_protocol }, { XATOM_XdndEnter, X11DRV_XDND_EnterEvent }, { XATOM_XdndPosition, X11DRV_XDND_PositionEvent }, + { XATOM_XdndStatus, X11DRV_XDND_StatusEvent }, { XATOM_XdndDrop, X11DRV_XDND_DropEvent }, + { XATOM_XdndFinished, X11DRV_XDND_FinishedEvent }, { XATOM_XdndLeave, X11DRV_XDND_LeaveEvent } }; diff --git a/dlls/winex11.drv/winex11.drv.spec b/dlls/winex11.drv/winex11.drv.spec index d9bbebc..bd40ef7 100644 --- a/dlls/winex11.drv/winex11.drv.spec +++ b/dlls/winex11.drv/winex11.drv.spec @@ -27,6 +27,7 @@ @ cdecl CreateDesktopWindow(long) X11DRV_CreateDesktopWindow @ cdecl CreateWindow(long) X11DRV_CreateWindow @ cdecl DestroyWindow(long) X11DRV_DestroyWindow +@ cdecl DoDragDrop(ptr ptr long ptr) X11DRV_DoDragDrop @ cdecl EmptyClipboard() X11DRV_EmptyClipboard @ cdecl EndClipboardUpdate() X11DRV_EndClipboardUpdate @ cdecl EnumClipboardFormats(long) X11DRV_EnumClipboardFormats diff --git a/dlls/winex11.drv/x11drv.h b/dlls/winex11.drv/x11drv.h index cb4b0bb..ad5b766 100644 --- a/dlls/winex11.drv/x11drv.h +++ b/dlls/winex11.drv/x11drv.h @@ -61,6 +61,8 @@ typedef int Status; #define MAX_DASHLEN 16 +/* Support for versions below 3 is optional: */ +#define WINE_MIN_XDND_VERSION 3 #define WINE_XDND_VERSION 5 /* X physical pen */ @@ -234,9 +236,14 @@ extern void IME_SetResultString(LPWSTR lpResult, DWORD dwResultlen) DECLSPEC_HID extern void X11DRV_XDND_EnterEvent( HWND hWnd, XClientMessageEvent *event ) DECLSPEC_HIDDEN; extern void X11DRV_XDND_PositionEvent( HWND hWnd, XClientMessageEvent *event ) DECLSPEC_HIDDEN; +extern void X11DRV_XDND_StatusEvent( HWND hWnd, XClientMessageEvent *event ) DECLSPEC_HIDDEN; extern void X11DRV_XDND_DropEvent( HWND hWnd, XClientMessageEvent *event ) DECLSPEC_HIDDEN; +extern void X11DRV_XDND_FinishedEvent( 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; +extern Atom X11DRV_CLIPBOARD_WindowsFormatToX11Atom(UINT windowsFormat) DECLSPEC_HIDDEN; /************************************************************************** * X11 GDI driver diff --git a/dlls/winex11.drv/xdnd.c b/dlls/winex11.drv/xdnd.c index f6ce5e9..87fc641 100644 --- a/dlls/winex11.drv/xdnd.c +++ b/dlls/winex11.drv/xdnd.c @@ -867,3 +867,840 @@ static IDataObjectVtbl xdndDataObjectVtbl = }; static IDataObject XDNDDataObject = { &xdndDataObjectVtbl }; + +/* ============================ + * The dragging implementation: + * ============================ + */ + +/* + * Name of our registered window class. + */ +static const WCHAR X11DRV_DRAGTRACKERCLASS[] = + {'W','i','n','e','X','1','1','D','R','V','D','r','a','g','D','r','o','p','T','r','a','c','k','e','r','3','2',0}; + +#define XDND_TIMEOUT_MILLIS 10000 + +typedef struct tagDragState +{ + IDataObject* dataObject; + IDropSource* dropSource; + DWORD dwOKEffect; + DWORD* pdwEffect; + BOOL trackingDone; + HRESULT returnValue; + BOOL escPressed; + Atom xdndVersion; + Atom* xdndTypeList; + int xdndTypeListSize; + Time timeXdndSelectionAcquired; + Window source; /* source window */ + Window target; /* drop target window */ + POINT curMousePos; /* current position of the mouse in X11 screen coordinates */ + DWORD dwKeyState; /* current state of the shift and ctrl keys and the mouse buttons */ + BOOLEAN awaitingStatus; /* because we sent out an XdndPosition */ + BOOLEAN queuedNextPosition; /* while awaitingStatus, to be sent after XdndStatus arrives */ + ULONGLONG timeXdndPositionSent; + BOOLEAN isDropping; /* or are we still dragging? */ + BOOLEAN hasDropped; /* so ignore further XdndStatus messages which buggy apps send */ + ULONGLONG timeXdndDropSent; +} DragState; + +static DragState dragState; + +static HRESULT set_XdndTypeList(IDataObject *dataObject, Window window, Atom **pTypeList, int *pCount) +{ + IEnumFORMATETC *enumFormatEtc = NULL; + FORMATETC formatEtc; + HRESULT hr; + Atom *typeList = NULL; + int count = 0; + int next = 0; + + hr = IDataObject_EnumFormatEtc(dataObject, DATADIR_GET, &enumFormatEtc); + if (FAILED(hr)) + goto end; + for (hr = IEnumFORMATETC_Next(enumFormatEtc, 1, &formatEtc, NULL); + SUCCEEDED(hr) && hr != S_FALSE; + hr = IEnumFORMATETC_Next(enumFormatEtc, 1, &formatEtc, NULL)) + { + BOOL ok = TRUE; + if (formatEtc.dwAspect != DVASPECT_CONTENT) + { + FIXME("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 0x%08x doesn't exclusively contain TYMED_HGLOBAL which is unimplemented\n", formatEtc.tymed); + ok = FALSE; + } + if (ok) + { + Atom atom = X11DRV_CLIPBOARD_WindowsFormatToX11Atom(formatEtc.cfFormat); + if (atom != None) + count++; + } + } + + typeList = HeapAlloc(GetProcessHeap(), 0, count * sizeof(Atom)); + if (typeList == NULL) + { + hr = E_OUTOFMEMORY; + goto end; + } + + hr = IEnumFORMATETC_Reset(enumFormatEtc); + if (FAILED(hr)) + goto end; + for (hr = IEnumFORMATETC_Next(enumFormatEtc, 1, &formatEtc, NULL); + SUCCEEDED(hr) && hr != S_FALSE; + hr = IEnumFORMATETC_Next(enumFormatEtc, 1, &formatEtc, NULL)) + { + Atom atom = X11DRV_CLIPBOARD_WindowsFormatToX11Atom(formatEtc.cfFormat); + if (formatEtc.dwAspect == DVASPECT_CONTENT && + formatEtc.lindex == -1 && + formatEtc.ptd == NULL && + formatEtc.tymed == TYMED_HGLOBAL && + atom != None) + { + typeList[next++] = atom; + } + } + + XChangeProperty(thread_display(), window, x11drv_atom(XdndTypeList), XA_ATOM, 32, + PropModeReplace, (const unsigned char*)typeList, count); + +end: + if (enumFormatEtc) + IEnumFORMATETC_Release(enumFormatEtc); + *pTypeList = typeList; + *pCount = count; + return hr; +} + +/************************************************************************** + * X11DRV_XDND_SelectionRequest + */ +void X11DRV_XDND_SelectionRequest(XSelectionRequestEvent *event) +{ + XSelectionEvent result; + IEnumFORMATETC *enumFormatEtc = NULL; + FORMATETC formatEtc; + HRESULT hr; + BOOL found = FALSE; + BOOL ok = TRUE; + + TRACE("Selection request from window 0x%lx for selection %ld, property %ld, target %ld, time %lu\n", + event->requestor, event->selection, event->property, event->target, event->time); + + if (event->time != dragState.timeXdndSelectionAcquired) + { + ERR("selection time isn't the time we acquired XdndSelection\n"); + goto end; + } + + hr = IDataObject_EnumFormatEtc(dragState.dataObject, DATADIR_GET, &enumFormatEtc); + if (FAILED(hr)) + goto end; + for (hr = IEnumFORMATETC_Next(enumFormatEtc, 1, &formatEtc, NULL); + SUCCEEDED(hr) && hr != S_FALSE; + hr = IEnumFORMATETC_Next(enumFormatEtc, 1, &formatEtc, NULL)) + { + if (X11DRV_CLIPBOARD_WindowsFormatToX11Atom(formatEtc.cfFormat) == event->target) + { + found = TRUE; + break; + } + } + if (!found) + goto end; + 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 0x%08x doesn't exclusively contain TYMED_HGLOBAL which is unimplemented\n", formatEtc.tymed); + ok = FALSE; + } + if (ok) + { + STGMEDIUM stgMedium; + char formatDesc[1024]; + + X11DRV_XDND_DescribeClipboardFormat(formatEtc.cfFormat, formatDesc, sizeof(formatDesc)); + hr = IDataObject_GetData(dragState.dataObject, &formatEtc, &stgMedium); + if (SUCCEEDED(hr)) + { + Atom x11Atom = None; + DWORD cBytes = 0; + HANDLE dataHandle; + dataHandle = X11DRV_CLIPBOARD_ExportSelection(formatEtc.cfFormat, stgMedium.u.hGlobal, &x11Atom, &cBytes); + if (dataHandle) + { + int mode = PropModeReplace; + char *data; + TRACE("Exported clipboard format %d (%s)\n", formatEtc.cfFormat, formatDesc); + data = GlobalLock(dataHandle); + 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(dataHandle); + GlobalFree(dataHandle); + } + else + WARN("Exporting clipboard format %d (%s) failed\n", formatEtc.cfFormat, formatDesc); + + if (stgMedium.pUnkForRelease) + IUnknown_Release(stgMedium.pUnkForRelease); + else + GlobalFree(stgMedium.u.hGlobal); + } + else + WARN("IDataObject_GetData() for clipboard format %d (%s) failed, hr=%08x\n", formatEtc.cfFormat, formatDesc, hr); + } + +end: + 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 = found ? event->target : None; + result.time = event->time; + TRACE("Sending SelectionNotify event...\n"); + XSendEvent(thread_display(), event->requestor, False, NoEventMask, (XEvent*)&result); + + if (enumFormatEtc) + IEnumFORMATETC_Release(enumFormatEtc); +} + +static Window droppable_window_at(int x, int y, Atom *xdndVersion) +{ + int destX, destY; + Window destWindow = 0; + Window childWindow = 0; + + for (destWindow = root_window; + XTranslateCoordinates(thread_display(), root_window, destWindow, x, y, &destX, &destY, &childWindow) && childWindow; + destWindow = childWindow) + { + Atom acttype; + int actfmt; + unsigned long count, bytesret; + Atom *xdndAware = NULL; + + XGetWindowProperty(thread_display(), childWindow, x11drv_atom(XdndAware), + 0, 1, FALSE, XA_ATOM, &acttype, &actfmt, &count, + &bytesret, (unsigned char**)&xdndAware); + if (xdndAware) + { + *xdndVersion = *xdndAware; + XFree(xdndAware); + if (*xdndVersion >= WINE_MIN_XDND_VERSION) + return childWindow; + } + } + return None; +} + +static void send_XdndEnter(Window source, int targetXdndVersion, Atom *typeList, int typeListSize, Window target) +{ + XClientMessageEvent e; + int actualVersion = (targetXdndVersion < WINE_XDND_VERSION) ? targetXdndVersion : WINE_XDND_VERSION; + e.type = ClientMessage; + e.display = thread_display(); + e.window = target; + e.message_type = x11drv_atom(XdndEnter); + e.format = 32; + e.data.l[0] = source; + e.data.l[1] = (actualVersion << 24) | (typeListSize > 3 ? 1 : 0); + e.data.l[2] = typeListSize >= 1 ? typeList[0] : None; + e.data.l[3] = typeListSize >= 2 ? typeList[1] : None; + e.data.l[4] = typeListSize >= 3 ? typeList[2] : None; + TRACE("0x%lx -> XdndEnter(version=%d, types=%ld/%ld/%ld) -> 0x%lx\n", source, actualVersion, + e.data.l[2], e.data.l[3], e.data.l[4], target); + XSendEvent(thread_display(), target, False, NoEventMask, (XEvent*)&e); +} + +static void send_XdndPosition(Window source, long action, POINT *point, Time timestamp, Window target) +{ + XClientMessageEvent e; + e.type = ClientMessage; + e.display = thread_display(); + e.window = target; + e.message_type = x11drv_atom(XdndPosition); + e.format = 32; + e.data.l[0] = source; + e.data.l[1] = 0; + e.data.l[2] = (point->x << 16) | (point->y & 0xffff); + e.data.l[3] = timestamp; + e.data.l[4] = action; + TRACE("0x%lx -> XdndPosition(point=(%d,%d), timestamp=%lu, action=%lu) -> 0x%lx\n", + source, point->x, point->y, timestamp, action, target); + XSendEvent(thread_display(), target, False, NoEventMask, (XEvent*)&e); +} + +static void send_XdndDrop(Window source, Time timestamp, Window target) +{ + XClientMessageEvent e; + e.type = ClientMessage; + e.display = thread_display(); + e.window = target; + e.message_type = x11drv_atom(XdndDrop); + e.format = 32; + e.data.l[0] = source; + e.data.l[1] = 0; + e.data.l[2] = timestamp; + e.data.l[3] = 0; + e.data.l[4] = 0; + TRACE("0x%lx -> XdndDrop(timestamp=%lu) -> 0x%lx\n", source, timestamp, target); + XSendEvent(thread_display(), target, False, NoEventMask, (XEvent*)&e); +} + +static void send_XdndLeave(Window source, Window target) +{ + XClientMessageEvent e; + e.type = ClientMessage; + e.display = thread_display(); + e.window = target; + e.message_type = x11drv_atom(XdndLeave); + e.format = 32; + e.data.l[0] = source; + e.data.l[1] = 0; + e.data.l[2] = 0; + e.data.l[3] = 0; + e.data.l[4] = 0; + TRACE("0x%lx -> XdndLeave() -> 0x%lx\n", source, target); + XSendEvent(thread_display(), target, False, NoEventMask, (XEvent*)&e); +} + +/* Queries whether the drag should be continued and performs the next action, + * either updating the drop target, or starting the drop */ +static void next_action(BOOL sendPositionEvenIfNotQueued) +{ + dragState.returnValue = IDropSource_QueryContinueDrag(dragState.dropSource, + dragState.escPressed, + dragState.dwKeyState); + + /* + * All the return values will stop the operation except the S_OK + * return value. + */ + if (dragState.returnValue == S_OK) + { + if (sendPositionEvenIfNotQueued || dragState.queuedNextPosition) + { + send_XdndPosition(dragState.source, X11DRV_XDND_DROPEFFECTToXdndAction(dragState.dwOKEffect), + &dragState.curMousePos, dragState.timeXdndSelectionAcquired, dragState.target); + dragState.timeXdndPositionSent = GetTickCount64(); + dragState.awaitingStatus = TRUE; + dragState.queuedNextPosition = FALSE; + } + } + else + { + /* + * If we end-up over a target, drop the object in the target or + * inform the target that the operation was cancelled. + */ + if (dragState.target) + { + switch (dragState.returnValue) + { + /* + * If the source wants us to complete the operation, we tell + * the drop target that we just dropped the object in it. + */ + case DRAGDROP_S_DROP: + if (*dragState.pdwEffect != DROPEFFECT_NONE) + { + /* Send a final XdndPosition to set the mouse position and action before the drop */ + send_XdndPosition(dragState.source, X11DRV_XDND_DROPEFFECTToXdndAction(dragState.dwOKEffect), + &dragState.curMousePos, dragState.timeXdndSelectionAcquired, dragState.target); + dragState.timeXdndPositionSent = GetTickCount64(); + dragState.awaitingStatus = TRUE; + dragState.isDropping = TRUE; + } + else + { + dragState.trackingDone = TRUE; + ReleaseCapture(); + send_XdndLeave(dragState.source, dragState.target); + } + break; + + /* + * If the source told us that we should cancel, fool the drop + * target by telling it that the mouse left its window. + * Also set the drop effect to "NONE" in case the application + * ignores the result of DoDragDrop. + */ + case DRAGDROP_S_CANCEL: + dragState.trackingDone = TRUE; + ReleaseCapture(); + send_XdndLeave(dragState.source, dragState.target); + *dragState.pdwEffect = DROPEFFECT_NONE; + break; + } + } + else + { + dragState.trackingDone = TRUE; + ReleaseCapture(); + } + } +} + +static void give_feedback(void) +{ + HRESULT hr; + + /* + * If we don't have a target, simulate no effect. + */ + if (dragState.target == None) + { + *dragState.pdwEffect = DROPEFFECT_NONE; + } + + hr = IDropSource_GiveFeedback(dragState.dropSource, + *dragState.pdwEffect); + + /* + * When we ask for feedback from the drop source, sometimes it will + * do all the necessary work and sometimes, when it doesn't handle it, + * we must display the standard drag and drop cursors. + */ + if (hr == DRAGDROP_S_USEDEFAULTCURSORS) + { + HCURSOR hCur; + + if (*dragState.pdwEffect & DROPEFFECT_MOVE) + { + hCur = LoadCursorW(GetModuleHandleA("ole32"), MAKEINTRESOURCEW(2)); + } + else if (*dragState.pdwEffect & DROPEFFECT_COPY) + { + hCur = LoadCursorW(GetModuleHandleA("ole32"), MAKEINTRESOURCEW(3)); + } + else if (*dragState.pdwEffect & DROPEFFECT_LINK) + { + hCur = LoadCursorW(GetModuleHandleA("ole32"), MAKEINTRESOURCEW(4)); + } + else + { + hCur = LoadCursorW(GetModuleHandleA("ole32"), MAKEINTRESOURCEW(1)); + } + + SetCursor(hCur); + } +} + +/************************************************************************** + * X11DRV_XDND_StatusEvent + * + * Handle an XdndStatus event. + */ +void X11DRV_XDND_StatusEvent( HWND hWnd, XClientMessageEvent *event ) +{ + if (event->data.l[0] != dragState.target) + { + TRACE("ignoring XdndStatus from wrong target window 0x%lx\n", event->data.l[0]); + return; + } + + TRACE("0x%lx <- XdndStatusEvent(accepted=%ld, action=%ld) <- 0x%lx\n", event->window, + event->data.l[1] & 1, event->data.l[4], event->data.l[0]); + + if (event->data.l[1] & 1) + { + *dragState.pdwEffect = + event->data.l[4] == None ? DROPEFFECT_NONE : X11DRV_XDND_XdndActionToDROPEFFECT(event->data.l[4]); + } + else + *dragState.pdwEffect = DROPEFFECT_NONE; + *dragState.pdwEffect &= dragState.dwOKEffect; + give_feedback(); + + if (!dragState.isDropping) + { + dragState.awaitingStatus = FALSE; + next_action(FALSE); + } + else + { + if (!dragState.hasDropped) + { + dragState.hasDropped = TRUE; + ReleaseCapture(); + send_XdndDrop(dragState.source, dragState.timeXdndSelectionAcquired, dragState.target); + dragState.timeXdndDropSent = GetTickCount64(); + dragState.awaitingStatus = FALSE; + } + } +} + +/************************************************************************** + * X11DRV_XDND_FinishedEvent + * + * Handle an XdndFinished event. + */ +void X11DRV_XDND_FinishedEvent( HWND hWnd, XClientMessageEvent *event ) +{ + if (event->data.l[0] != dragState.target) + { + TRACE("ignoring XdndFinished from wrong target window 0x%lx\n", event->data.l[0]); + return; + } + + if (dragState.xdndVersion >= 5) + { + TRACE("0x%lx <- XdndFinishedEvent(accepted=%ld, action=%ld) <- 0x%lx\n", event->window, + event->data.l[1] & 1, event->data.l[2], event->data.l[0]); + if (event->data.l[1] & 1) + { + *dragState.pdwEffect = event->data.l[2] == None ? DROPEFFECT_NONE : + X11DRV_XDND_XdndActionToDROPEFFECT(event->data.l[2]); + } + else + { + *dragState.pdwEffect = DROPEFFECT_NONE; + dragState.returnValue = E_UNEXPECTED; + } + } + else + { + TRACE("0x%lx <- XdndFinishedEvent() <- 0x%lx\n", event->window, event->data.l[0]); + /* for XDND version < 5, just keep the previous dragState.pdwEffect */ + } + + dragState.trackingDone = TRUE; +} + +/*** + * X11DRV_XDND_TrackStateChange() + * + * This method is invoked while a drag and drop operation is in effect. + */ +static void X11DRV_XDND_TrackStateChange(void) +{ + if (!dragState.isDropping) + { + Window newTarget = droppable_window_at(dragState.curMousePos.x, dragState.curMousePos.y, &dragState.xdndVersion); + + if (dragState.target != None && dragState.target == newTarget) + { + if (!dragState.awaitingStatus) + next_action(TRUE); + else + { + if ((GetTickCount64() - dragState.timeXdndPositionSent) <= XDND_TIMEOUT_MILLIS) + dragState.queuedNextPosition = TRUE; + else + { + ERR("timeout waiting for XdndStatus from X11 window 0x%lx\n", dragState.target); + send_XdndLeave(dragState.source, dragState.target); + dragState.target = None; + dragState.awaitingStatus = FALSE; + dragState.queuedNextPosition = FALSE; + } + } + } + else + { + if (dragState.target) + { + send_XdndLeave(dragState.source, dragState.target); + dragState.awaitingStatus = FALSE; + dragState.queuedNextPosition = FALSE; + } + dragState.target = newTarget; + if (dragState.target) + { + send_XdndEnter(dragState.source, dragState.xdndVersion, dragState.xdndTypeList, + dragState.xdndTypeListSize, dragState.target); + next_action(TRUE); + } + else + { + *dragState.pdwEffect = DROPEFFECT_NONE; + next_action(FALSE); + give_feedback(); + } + } + } + else + { + if (dragState.awaitingStatus) + { + if ((GetTickCount64() - dragState.timeXdndPositionSent) > XDND_TIMEOUT_MILLIS) + { + ERR("drag failed: timeout waiting for XdndStatus just before the drop\n"); + *dragState.pdwEffect = DROPEFFECT_NONE; + dragState.returnValue = E_UNEXPECTED; + dragState.trackingDone = TRUE; + ReleaseCapture(); + } + } + else + { + if ((GetTickCount64() - dragState.timeXdndDropSent) > XDND_TIMEOUT_MILLIS) + { + ERR("drag failed: timeout waiting for XdndFinished\n"); + *dragState.pdwEffect = DROPEFFECT_NONE; + dragState.returnValue = E_UNEXPECTED; + dragState.trackingDone = TRUE; + ReleaseCapture(); + } + } + } +} + +/*** + * OLEDD_GetButtonState() + * + * This method will use the current state of the keyboard to build + * a button state mask equivalent to the one passed in the + * WM_MOUSEMOVE wParam. + */ +static DWORD OLEDD_GetButtonState(void) +{ + BYTE keyboardState[256]; + DWORD keyMask = 0; + + GetKeyboardState(keyboardState); + + if ( (keyboardState[VK_SHIFT] & 0x80) !=0) + keyMask |= MK_SHIFT; + + if ( (keyboardState[VK_CONTROL] & 0x80) !=0) + keyMask |= MK_CONTROL; + + if ( (keyboardState[VK_MENU] & 0x80) !=0) + keyMask |= MK_ALT; + + if ( (keyboardState[VK_LBUTTON] & 0x80) !=0) + keyMask |= MK_LBUTTON; + + if ( (keyboardState[VK_RBUTTON] & 0x80) !=0) + keyMask |= MK_RBUTTON; + + if ( (keyboardState[VK_MBUTTON] & 0x80) !=0) + keyMask |= MK_MBUTTON; + + return keyMask; +} + +#define DRAG_TIMER_ID 1 + +/*** + * X11DRV_DragTrackerWindowProc() + * + * This method is the WindowProcedure of the drag n drop tracking + * window. During a drag n Drop operation, an invisible window is created + * to receive the user input and act upon it. This procedure is in charge + * of this behavior. + */ +static LRESULT WINAPI X11DRV_DragTrackerWindowProc( + HWND hwnd, + UINT uMsg, + WPARAM wParam, + LPARAM lParam) +{ + switch (uMsg) + { + case WM_CREATE: + { + SetTimer(hwnd, DRAG_TIMER_ID, 50, NULL); + break; + } + case WM_TIMER: + case WM_MOUSEMOVE: + case WM_LBUTTONUP: + case WM_MBUTTONUP: + case WM_RBUTTONUP: + case WM_LBUTTONDOWN: + case WM_MBUTTONDOWN: + case WM_RBUTTONDOWN: + { + if (dragState.trackingDone) break; + X11DRV_XDND_TrackStateChange(); + break; + } + case WM_DESTROY: + { + KillTimer(hwnd, DRAG_TIMER_ID); + break; + } + } + + /* + * This is a window proc after all. Let's call the default. + */ + return DefWindowProcW (hwnd, uMsg, wParam, lParam); +} + +static void drag_cleanup(void) +{ + HeapFree(GetProcessHeap(), 0, dragState.xdndTypeList); + if (XGetSelectionOwner(thread_display(), x11drv_atom(XdndSelection)) == dragState.source) + XSetSelectionOwner(thread_display(), x11drv_atom(XdndSelection), None, CurrentTime); +} + +/************************************************************************** + * X11DRV_DoDragDrop [winex11.drv.@] + */ +HRESULT CDECL X11DRV_DoDragDrop ( + IDataObject *pDataObject, /* [in] ptr to the data obj */ + IDropSource* pDropSource, /* [in] ptr to the source obj */ + DWORD dwOKEffect, /* [in] effects allowed by the source */ + DWORD *pdwEffect) /* [out] ptr to effects of the source */ +{ + static const WCHAR trackerW[] = {'T','r','a','c','k','e','r','W','i','n','d','o','w',0}; + HWND hwndTrackWindow; + MSG msg; + WNDCLASSW wndClass; + HRESULT hr; + + TRACE("(%p, %p, %08x, %p)\n", pDataObject, pDropSource, dwOKEffect, pdwEffect); + + if (!pDataObject || !pDropSource || !pdwEffect) + return E_INVALIDARG; + + /* + * Setup the drag n drop tracking window. + */ + ZeroMemory (&wndClass, sizeof(WNDCLASSW)); + wndClass.style = CS_GLOBALCLASS; + wndClass.lpfnWndProc = X11DRV_DragTrackerWindowProc; + wndClass.cbClsExtra = 0; + wndClass.cbWndExtra = sizeof(DragState*); + wndClass.hCursor = 0; + wndClass.hbrBackground = 0; + wndClass.lpszClassName = X11DRV_DRAGTRACKERCLASS; + RegisterClassW (&wndClass); + + hwndTrackWindow = CreateWindowW(X11DRV_DRAGTRACKERCLASS, trackerW, + WS_POPUP, CW_USEDEFAULT, CW_USEDEFAULT, + CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, 0, + NULL); + if (!hwndTrackWindow) + return E_FAIL; + + dragState.source = X11DRV_get_whole_window(hwndTrackWindow); + hr = set_XdndTypeList(pDataObject, dragState.source, &dragState.xdndTypeList, &dragState.xdndTypeListSize); + if (FAILED(hr)) + { + DestroyWindow(hwndTrackWindow); + return hr; + } + dragState.dataObject = pDataObject; + dragState.dropSource = pDropSource; + dragState.dwOKEffect = dwOKEffect; + dragState.pdwEffect = pdwEffect; + dragState.trackingDone = FALSE; + dragState.escPressed = FALSE; + dragState.target = None; + dragState.dwKeyState = 0; + dragState.awaitingStatus = FALSE; + dragState.queuedNextPosition = FALSE; + dragState.isDropping = FALSE; + dragState.hasDropped = FALSE; + dragState.timeXdndSelectionAcquired = GetMessageTime() - EVENT_x11_time_to_win32_time(0); + XSetSelectionOwner(thread_display(), x11drv_atom(XdndSelection), dragState.source, dragState.timeXdndSelectionAcquired); + if (XGetSelectionOwner(thread_display(), x11drv_atom(XdndSelection)) != dragState.source) + { + ERR("could not acquire XdndSelection, aborting drag\n"); + DestroyWindow(hwndTrackWindow); + drag_cleanup(); + return E_FAIL; + } + + /* + * Capture the mouse input + */ + SetCapture(hwndTrackWindow); + + msg.message = 0; + + /* + * Pump messages. All mouse input should go to the capture window. + */ + while (!dragState.trackingDone && GetMessageW(&msg, 0, 0, 0) ) + { + dragState.curMousePos = virtual_screen_to_root(msg.pt.x, msg.pt.y); + dragState.dwKeyState = OLEDD_GetButtonState(); + + if ( (msg.message >= WM_KEYFIRST) && + (msg.message <= WM_KEYLAST) ) + { + /* + * When keyboard messages are sent to windows on this thread, + * we swallow key events except to notify the drop source about + * the Escape key. + */ + if ( (msg.message==WM_KEYDOWN) && + (msg.wParam==VK_ESCAPE) ) + { + dragState.escPressed = TRUE; + } + + /* + * Notify the drop source. + */ + X11DRV_XDND_TrackStateChange(); + } + else + { + /* + * Dispatch the messages only when it's not a keyboard message. + */ + DispatchMessageW(&msg); + } + } + + /* re-post the quit message to outer message loop */ + if (msg.message == WM_QUIT) + PostQuitMessage(msg.wParam); + /* + * Destroy the temporary window. + */ + DestroyWindow(hwndTrackWindow); + + drag_cleanup(); + return dragState.returnValue; +}