diff --git a/dlls/winex11.drv/Makefile.in b/dlls/winex11.drv/Makefile.in index e381b95..cb5bba0 100644 --- a/dlls/winex11.drv/Makefile.in +++ b/dlls/winex11.drv/Makefile.in @@ -4,7 +4,7 @@ SRCDIR = @srcdir@ VPATH = @srcdir@ MODULE = winex11.drv IMPORTS = user32 gdi32 advapi32 imm32 -DELAYIMPORTS = comctl32 +DELAYIMPORTS = comctl32 ole32 EXTRAINCL = @X_CFLAGS@ EXTRALIBS = @X_LIBS@ @X_PRE_LIBS@ @XLIB@ @X_EXTRA_LIBS@ diff --git a/dlls/winex11.drv/xdnd.c b/dlls/winex11.drv/xdnd.c index 9d209fc..e825f59 100644 --- a/dlls/winex11.drv/xdnd.c +++ b/dlls/winex11.drv/xdnd.c @@ -22,6 +22,9 @@ #include "config.h" #include "wine/port.h" +#define COBJMACROS +#include + #include #ifdef HAVE_UNISTD_H # include @@ -36,6 +39,8 @@ #include "x11drv.h" #include "shlobj.h" /* DROPFILES */ +#include "oleidl.h" +#include "objidl.h" #include "wine/unicode.h" #include "wine/debug.h" @@ -57,6 +62,13 @@ typedef struct tagXDNDDATA static LPXDNDDATA XDNDData = NULL; static POINT XDNDxy = { 0, 0 }; +static IDataObject xdndDataObject; +static BOOL XDNDAccepted = FALSE; +static DWORD XDNDDropEffect = DROPEFFECT_NONE; +/* the last window the mouse was over */ +static HWND XDND_last_target_wnd; +/* might be a ancestor of XDND_last_target_wnd */ +static HWND XDND_last_drop_target_wnd; static void X11DRV_XDND_InsertXDNDData(int property, int format, void* data, unsigned int len); static int X11DRV_XDND_DeconstructTextURIList(int property, void* data, int len); @@ -69,6 +81,9 @@ static void X11DRV_XDND_SendDropFiles(HWND hwnd); static void X11DRV_XDND_FreeDragDropOp(void); static unsigned int X11DRV_XDND_UnixToDos(char** lpdest, char* lpsrc, int len); static WCHAR* X11DRV_XDND_URIToDOS(char *encodedURI); +static void X11DRV_XDND_DescribeClipboardFormat(int cfFormat, char *buffer, int size); +static DWORD X11DRV_XDND_ActionToDROPEFFECT(long action); +static long X11DRV_XDND_DROPEFFECTToAction(DWORD effect); static CRITICAL_SECTION xdnd_cs; static CRITICAL_SECTION_DEBUG critsect_debug = @@ -80,6 +95,73 @@ static CRITICAL_SECTION_DEBUG critsect_debug = static CRITICAL_SECTION xdnd_cs = { &critsect_debug, -1, 0, 0, 0, 0 }; +/* Based on functions in dlls/ole32/ole2.c */ +static HANDLE get_droptarget_local_handle(HWND hwnd) +{ + static const WCHAR prop_marshalleddroptarget[] = + {'W','i','n','e','M','a','r','s','h','a','l','l','e','d','D','r','o','p','T','a','r','g','e','t',0}; + HANDLE handle; + HANDLE local_handle = 0; + + handle = GetPropW(hwnd, prop_marshalleddroptarget); + if (handle) + { + DWORD pid; + HANDLE process; + + GetWindowThreadProcessId(hwnd, &pid); + process = OpenProcess(PROCESS_DUP_HANDLE, FALSE, pid); + if (process) + { + DuplicateHandle(process, handle, GetCurrentProcess(), &local_handle, 0, FALSE, DUPLICATE_SAME_ACCESS); + CloseHandle(process); + } + } + return local_handle; +} + +static HRESULT create_stream_from_map(HANDLE map, IStream **stream) +{ + HRESULT hr = E_OUTOFMEMORY; + HGLOBAL hmem; + void *data; + MEMORY_BASIC_INFORMATION info; + + data = MapViewOfFile(map, FILE_MAP_READ, 0, 0, 0); + if(!data) return hr; + + VirtualQuery(data, &info, sizeof(info)); + TRACE("size %d\n", (int)info.RegionSize); + + hmem = GlobalAlloc(GMEM_MOVEABLE, info.RegionSize); + if(hmem) + { + memcpy(GlobalLock(hmem), data, info.RegionSize); + GlobalUnlock(hmem); + hr = CreateStreamOnHGlobal(hmem, TRUE, stream); + } + UnmapViewOfFile(data); + return hr; +} + +static IDropTarget* get_droptarget_pointer(HWND hwnd) +{ + IDropTarget *droptarget = NULL; + HANDLE map; + IStream *stream; + + map = get_droptarget_local_handle(hwnd); + if(!map) return NULL; + + if(SUCCEEDED(create_stream_from_map(map, &stream))) + { + CoUnmarshalInterface(stream, &IID_IDropTarget, (void**)&droptarget); + IStream_Release(stream); + } + CloseHandle(map); + return droptarget; +} + /************************************************************************** * X11DRV_XDND_EnterEvent * @@ -103,6 +185,9 @@ void X11DRV_XDND_EnterEvent( HWND hWnd, XClientMessageEvent *event ) return; } + XDND_last_target_wnd = NULL; + XDND_last_drop_target_wnd = NULL; + /* If the source supports more than 3 data types we retrieve * the entire list. */ if (event->data.l[1] & 1) @@ -158,11 +243,75 @@ void X11DRV_XDND_PositionEvent( HWND hWnd, XClientMessageEvent *event ) { XClientMessageEvent e; int accept = 0; /* Assume we're not accepting */ + IDropTarget *dropTarget = NULL; + DWORD effect; + POINTL pointl; + HWND hWndTarget = NULL; + HRESULT hr; XDNDxy.x = event->data.l[2] >> 16; XDNDxy.y = event->data.l[2] & 0xFFFF; + hWnd = WindowFromPoint(XDNDxy); - /* FIXME: Notify OLE of DragEnter. Result determines if we accept */ + pointl.x = XDNDxy.x; + pointl.y = XDNDxy.y; + + effect = X11DRV_XDND_ActionToDROPEFFECT(event->data.l[4]); + + /* Notify OLE of DragEnter. Result determines if we accept */ + if (XDND_last_target_wnd && + XDND_last_target_wnd == hWnd) + { + dropTarget = get_droptarget_pointer(XDND_last_drop_target_wnd); + if (dropTarget) + { + hr = IDropTarget_DragOver(dropTarget, MK_LBUTTON, pointl, &effect); + if (SUCCEEDED(hr)) + XDNDDropEffect = effect; + else + WARN("IDropTarget_DragOver failed, error 0x%X\n", hr); + } + } + else + { + if (XDND_last_drop_target_wnd && + (dropTarget = get_droptarget_pointer(XDND_last_drop_target_wnd)) != NULL) + { + IDropTarget_DragLeave(dropTarget); + } + + hWndTarget = hWnd; + do + { + dropTarget = get_droptarget_pointer(hWndTarget); + } while (!dropTarget && (hWndTarget = GetParent(hWndTarget)) != NULL); + + XDND_last_target_wnd = hWnd; + XDND_last_drop_target_wnd = hWndTarget; + + if (dropTarget) + { + hr = IDropTarget_DragEnter(dropTarget, &xdndDataObject, + MK_LBUTTON, pointl, &effect); + if (SUCCEEDED(hr)) + { + if (effect != DROPEFFECT_NONE) + { + XDNDAccepted = TRUE; + TRACE("the application accepted the drop\n"); + } + else + TRACE("the application refused the drop\n"); + } + else + WARN("IDropTarget_DragEnter failed, error 0x%X\n", hr); + } + else + TRACE("no drop target\n"); + } + + if (XDNDAccepted) + accept = 1; if (GetWindowLongW( hWnd, GWL_EXSTYLE ) & WS_EX_ACCEPTFILES) accept = 1; @@ -184,14 +333,12 @@ void X11DRV_XDND_PositionEvent( HWND hWnd, XClientMessageEvent *event ) e.data.l[2] = 0; /* Empty Rect */ e.data.l[3] = 0; /* Empty Rect */ if (accept) - e.data.l[4] = event->data.l[4]; + e.data.l[4] = X11DRV_XDND_DROPEFFECTToAction(effect); else e.data.l[4] = None; wine_tsx11_lock(); XSendEvent(event->display, event->data.l[0], False, NoEventMask, (XEvent*)&e); wine_tsx11_unlock(); - - /* FIXME: if drag accepted notify OLE of DragOver */ } /************************************************************************** @@ -202,14 +349,42 @@ void X11DRV_XDND_PositionEvent( HWND hWnd, XClientMessageEvent *event ) void X11DRV_XDND_DropEvent( HWND hWnd, XClientMessageEvent *event ) { XClientMessageEvent e; + IDropTarget *dropTarget; TRACE("\n"); + hWnd = WindowFromPoint(XDNDxy); + if (!hWnd) return; + /* If we have a HDROP type we send a WM_ACCEPTFILES.*/ if (GetWindowLongW( hWnd, GWL_EXSTYLE ) & WS_EX_ACCEPTFILES) X11DRV_XDND_SendDropFiles( hWnd ); - /* FIXME: Notify OLE of Drop */ + /* Notify OLE of Drop */ + dropTarget = get_droptarget_pointer(XDND_last_drop_target_wnd); + if (dropTarget) + { + HRESULT hr; + POINTL pointl; + DWORD effect = XDNDDropEffect; + + pointl.x = XDNDxy.x; + pointl.y = XDNDxy.y; + hr = IDropTarget_Drop(dropTarget, &xdndDataObject, MK_LBUTTON, + pointl, &effect); + if (SUCCEEDED(hr)) + { + if (effect != DROPEFFECT_NONE) + TRACE("drop succeeded\n"); + else + TRACE("the application refused the drop\n"); + } + else + WARN("drop failed, error 0x%X\n", hr); + } + else + TRACE("no drop target\n"); + X11DRV_XDND_FreeDragDropOp(); /* Tell the target we are finished. */ @@ -223,6 +398,10 @@ void X11DRV_XDND_DropEvent( HWND hWnd, XClientMessageEvent *event ) wine_tsx11_lock(); XSendEvent(event->display, event->data.l[0], False, NoEventMask, (XEvent*)&e); wine_tsx11_unlock(); + + XDND_last_target_wnd = NULL; + XDND_last_drop_target_wnd = NULL; + XDNDAccepted = FALSE; } /************************************************************************** @@ -232,11 +411,28 @@ void X11DRV_XDND_DropEvent( HWND hWnd, XClientMessageEvent *event ) */ void X11DRV_XDND_LeaveEvent( HWND hWnd, XClientMessageEvent *event ) { + IDropTarget *dropTarget; + TRACE("DND Operation canceled\n"); + if (!XDND_last_drop_target_wnd) return; + X11DRV_XDND_FreeDragDropOp(); - /* FIXME: Notify OLE of DragLeave */ + /* Notify OLE of DragLeave */ + dropTarget = get_droptarget_pointer(XDND_last_drop_target_wnd); + if (dropTarget) + { + HRESULT hr = IDropTarget_DragLeave(dropTarget); + if (FAILED(hr)) + WARN("IDropTarget_DragLeave failed, error 0x%X\n", hr); + } + else + TRACE("drop target not found\n"); + + XDND_last_target_wnd = NULL; + XDND_last_drop_target_wnd = NULL; + XDNDAccepted = FALSE; } @@ -256,6 +452,9 @@ static void X11DRV_XDND_ResolveProperty(Display *display, Window xwin, Time tm, unsigned long bytesret, icount; int entries = 0; unsigned char* data = NULL; + XDNDDATA *previous = NULL; + XDNDDATA *current, *next; + BOOL haveHDROP = FALSE; TRACE("count(%ld)\n", *count); @@ -300,6 +499,66 @@ static void X11DRV_XDND_ResolveProperty(Display *display, Window xwin, Time tm, wine_tsx11_unlock(); } + /* We get the list of formats in descending order of preference, yet the linked + * list is a stack so the order is inverted. Reverse the linked list here so + * that we get the right order. + */ + current = XDNDData; + while (current != NULL) + { + next = current->next; + current->next = previous; + previous = current; + current = next; + } + if (previous) + XDNDData = previous; + + /* On Windows when there is a CF_HDROP, there is no other CF_ formats. + * foobar2000 relies on this (spaces -> %20's without it). + */ + current = XDNDData; + while (current != NULL) + { + if (current->cf_win == CF_HDROP) + { + haveHDROP = TRUE; + break; + } + current = current->next; + } + if (haveHDROP) + { + current = XDNDData; + while (current != NULL + && current->cf_win != CF_HDROP + && current->cf_win < CF_MAX) + { + next = current->next; + HeapFree(GetProcessHeap(), 0, current->data); + HeapFree(GetProcessHeap(), 0, current); + current = next; + --entries; + } + XDNDData = previous = current; + while (current != NULL) + { + current = current->next; + while (current != NULL + && current->cf_win != CF_HDROP + && current->cf_win < CF_MAX) + { + next = current->next; + HeapFree(GetProcessHeap(), 0, current->data); + HeapFree(GetProcessHeap(), 0, current); + current = next; + --entries; + } + previous->next = current; + previous = current; + } + } + *count = entries; } @@ -661,3 +920,319 @@ static WCHAR* X11DRV_XDND_URIToDOS(char *encodedURI) HeapFree(GetProcessHeap(), 0, uri); return ret; } + + +/************************************************************************** + * X11DRV_XDND_DescribeClipboardFormat + */ +static void X11DRV_XDND_DescribeClipboardFormat(int cfFormat, char *buffer, int size) +{ +#define D(x) case x: lstrcpynA(buffer, #x, size); return; + switch (cfFormat) + { + D(CF_TEXT) + D(CF_BITMAP) + D(CF_METAFILEPICT) + D(CF_SYLK) + D(CF_DIF) + D(CF_TIFF) + D(CF_OEMTEXT) + D(CF_DIB) + D(CF_PALETTE) + D(CF_PENDATA) + D(CF_RIFF) + D(CF_WAVE) + D(CF_UNICODETEXT) + D(CF_ENHMETAFILE) + D(CF_HDROP) + D(CF_LOCALE) + D(CF_DIBV5) + } +#undef D + + if (CF_PRIVATEFIRST <= cfFormat && cfFormat <= CF_PRIVATELAST) + { + lstrcpynA(buffer, "some private object", size); + return; + } + if (CF_GDIOBJFIRST <= cfFormat && cfFormat <= CF_GDIOBJLAST) + { + lstrcpynA(buffer, "some GDI object", size); + return; + } + + GetClipboardFormatNameA(cfFormat, buffer, size); +} + + +/************************************************************************** + * X11DRV_XDND_ActionToDROPEFFECT + */ +static DWORD X11DRV_XDND_ActionToDROPEFFECT(long action) +{ + /* In Windows, nothing but the given effects is allowed. + * In X the given action is just a hint, and you can always + * XdndActionCopy and XdndActionPrivate, so be more permissive. */ + if (action == x11drv_atom(XdndActionCopy)) + return DROPEFFECT_COPY; + else if (action == x11drv_atom(XdndActionMove)) + return DROPEFFECT_MOVE | DROPEFFECT_COPY; + else if (action == x11drv_atom(XdndActionLink)) + return DROPEFFECT_LINK | DROPEFFECT_COPY; + else if (action == x11drv_atom(XdndActionAsk)) + /* FIXME: should we somehow ask the user what to do here? */ + return DROPEFFECT_COPY | DROPEFFECT_MOVE | DROPEFFECT_LINK; + FIXME("unknown action %ld, assuming DROPEFFECT_COPY\n", action); + return DROPEFFECT_COPY; +} + + +/************************************************************************** + * X11DRV_XDND_DROPEFFECTToAction + */ +static long X11DRV_XDND_DROPEFFECTToAction(DWORD effect) +{ + if (effect == DROPEFFECT_COPY) + return x11drv_atom(XdndActionCopy); + else if (effect == DROPEFFECT_MOVE) + return x11drv_atom(XdndActionMove); + else if (effect == DROPEFFECT_LINK) + return x11drv_atom(XdndActionLink); + FIXME("unknown drop effect %u, assuming XdndActionCopy\n", effect); + return x11drv_atom(XdndActionCopy); +} + + +/* The IDataObject singleton we feed to OLE follows */ + +static HRESULT WINAPI DATAOBJECT_QueryInterface(IDataObject *dataObject, + REFIID riid, void **ppvObject) +{ + TRACE("(%p, %s, %p)\n", dataObject, debugstr_guid(riid), ppvObject); + if (IsEqualIID(riid, &IID_IUnknown) || IsEqualIID(riid, &IID_IDataObject)) + { + *ppvObject = dataObject; + IDataObject_AddRef(dataObject); + return S_OK; + } + *ppvObject = NULL; + return E_NOINTERFACE; +} + +static ULONG WINAPI DATAOBJECT_AddRef(IDataObject *dataObject) +{ + TRACE("(%p)\n", dataObject); + return 2; +} + +static ULONG WINAPI DATAOBJECT_Release(IDataObject *dataObject) +{ + TRACE("(%p)\n", dataObject); + return 1; +} + +static HRESULT WINAPI DATAOBJECT_GetData(IDataObject *dataObject, + FORMATETC *formatEtc, + STGMEDIUM *pMedium) +{ + XDNDDATA *data = XDNDData; + HRESULT hr; + char buffer[1024]; + + TRACE("(%p, %p, %p)\n", dataObject, formatEtc, pMedium); + X11DRV_XDND_DescribeClipboardFormat(formatEtc->cfFormat, + buffer, sizeof(buffer)); + TRACE("application is looking for %s\n", buffer); + + hr = IDataObject_QueryGetData(dataObject, formatEtc); + if (SUCCEEDED(hr)) + { + while (data != NULL) + { + if (data->cf_win == formatEtc->cfFormat) + { + pMedium->tymed = TYMED_HGLOBAL; + pMedium->hGlobal = HeapAlloc(GetProcessHeap(), 0, data->size); + if (pMedium->hGlobal == NULL) + return E_OUTOFMEMORY; + memcpy(pMedium->hGlobal, data->data, data->size); + pMedium->pUnkForRelease = 0; + return S_OK; + } + data = data->next; + } + } + return hr; +} + +static HRESULT WINAPI DATAOBJECT_GetDataHere(IDataObject *dataObject, + FORMATETC *formatEtc, + STGMEDIUM *pMedium) +{ + FIXME("(%p, %p, %p): stub\n", dataObject, formatEtc, pMedium); + return DATA_E_FORMATETC; +} + +static HRESULT WINAPI DATAOBJECT_QueryGetData(IDataObject *dataObject, + FORMATETC *formatEtc) +{ + XDNDDATA *data = XDNDData; + char buffer[1024]; + + TRACE("(%p, %p)\n", dataObject, formatEtc); + X11DRV_XDND_DescribeClipboardFormat(formatEtc->cfFormat, buffer, + sizeof(buffer)); + + TRACE("formatetc = {.tymed = 0x%x, .dwAspect = %d, .cfFormat = %d}\n", formatEtc->tymed, formatEtc->dwAspect, formatEtc->cfFormat); + + if (formatEtc->tymed && !(formatEtc->tymed & TYMED_HGLOBAL)) + { + FIXME("only HGLOBAL medium types supported right now\n"); + return DV_E_TYMED; + } + if (formatEtc->dwAspect != DVASPECT_CONTENT) + { + FIXME("only the content aspect is supported right now\n"); + return E_NOTIMPL; + } + + while (data != NULL) + { + if (data->cf_win == formatEtc->cfFormat) + { + TRACE("application found %s\n", buffer); + return S_OK; + } + data = data->next; + } + TRACE("application didn't find %s\n", buffer); + return DV_E_FORMATETC; +} + +static HRESULT WINAPI DATAOBJECT_GetCanonicalFormatEtc(IDataObject *dataObject, + FORMATETC *formatEtc, + FORMATETC *formatEtcOut) +{ + FIXME("(%p, %p, %p): stub\n", dataObject, formatEtc, formatEtcOut); + formatEtcOut->ptd = NULL; + return E_NOTIMPL; +} + +static HRESULT WINAPI DATAOBJECT_SetData(IDataObject *dataObject, + FORMATETC *formatEtc, + STGMEDIUM *pMedium, BOOL fRelease) +{ + FIXME("(%p, %p, %p, %s): stub\n", dataObject, formatEtc, + pMedium, fRelease?"TRUE":"FALSE"); + return E_NOTIMPL; +} + +static HRESULT WINAPI DATAOBJECT_EnumFormatEtc(IDataObject *dataObject, + DWORD dwDirection, + IEnumFORMATETC **ppEnumFormatEtc) +{ + HMODULE shell32; + XDNDDATA *data = XDNDData; + int i; + DWORD count = 0; + FORMATETC *formats; + HRESULT hr; + + TRACE("(%u, %p)\n", dwDirection, ppEnumFormatEtc); + + if (dwDirection != DATADIR_GET) + { + FIXME("only the get direction is implemented\n"); + return E_NOTIMPL; + } + + while (data != NULL) + { + data = data->next; + ++count; + } + + formats = HeapAlloc(GetProcessHeap(), 0, count * sizeof(FORMATETC)); + if (formats) + { + data = XDNDData; + for (i = 0; i < count; ++i) + { + formats[i].cfFormat = data->cf_win; + formats[i].ptd = NULL; + formats[i].dwAspect = DVASPECT_CONTENT; + formats[i].lindex = -1; + formats[i].tymed = TYMED_HGLOBAL; + data = data->next; + } + /* For some strange reason, load-time linking SHELL32.DLL to WINEX11.DRV + * causes wine to segfault on startup. So rather use run-time linking. + */ + shell32 = GetModuleHandleA("SHELL32.DLL"); + if (shell32 == NULL) + shell32 = LoadLibraryA("SHELL32.DLL"); + if (shell32) + { + HRESULT (WINAPI *SHCreateStdEnumFmtEtc)(DWORD, FORMATETC*, IEnumFORMATETC**) = (void*) + GetProcAddress(shell32, (char*) MAKELONG(74, 0)); + if (SHCreateStdEnumFmtEtc) + hr = SHCreateStdEnumFmtEtc(count, formats, ppEnumFormatEtc); + else + { + ERR("symbol lookup of SHCreateStdEnumFmtEtc failed\n"); + hr = E_FAIL; + } + } + else + { + WARN("loading SHELL32.DLL failed\n"); + hr = E_FAIL; + } + HeapFree(GetProcessHeap(), 0, formats); + return hr; + } + else + return E_OUTOFMEMORY; +} + +static HRESULT WINAPI DATAOBJECT_DAdvise(IDataObject *dataObject, + FORMATETC *formatEtc, DWORD advf, + IAdviseSink *adviseSink, + DWORD *pdwConnection) +{ + FIXME("(%p, %p, %u, %p, %p): stub\n", dataObject, formatEtc, advf, + adviseSink, pdwConnection); + return OLE_E_ADVISENOTSUPPORTED; +} + +static HRESULT WINAPI DATAOBJECT_DUnadvise(IDataObject *dataObject, + DWORD dwConnection) +{ + FIXME("(%p, %u): stub\n", dataObject, dwConnection); + return OLE_E_ADVISENOTSUPPORTED; +} + +static HRESULT WINAPI DATAOBJECT_EnumDAdvise(IDataObject *dataObject, + IEnumSTATDATA **pEnumAdvise) +{ + FIXME("(%p, %p): stub\n", dataObject, pEnumAdvise); + return OLE_E_ADVISENOTSUPPORTED; +} + +static IDataObjectVtbl xdndDataObjectVtbl = +{ + DATAOBJECT_QueryInterface, + DATAOBJECT_AddRef, + DATAOBJECT_Release, + DATAOBJECT_GetData, + DATAOBJECT_GetDataHere, + DATAOBJECT_QueryGetData, + DATAOBJECT_GetCanonicalFormatEtc, + DATAOBJECT_SetData, + DATAOBJECT_EnumFormatEtc, + DATAOBJECT_DAdvise, + DATAOBJECT_DUnadvise, + DATAOBJECT_EnumDAdvise +}; + +static IDataObject xdndDataObject = { &xdndDataObjectVtbl };