/* * MACDRV Drag and drop code * * Copyright 2003 Ulrich Czekalla * Copyright 2007 Damjan Jovanovic * Copyright 2013 Ken Thomases for CodeWeavers Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ #include "config.h" #define NONAMELESSUNION #include "macdrv.h" #define COBJMACROS #include "objidl.h" #include "shellapi.h" #include "shlobj.h" WINE_DEFAULT_DEBUG_CHANNEL(dragdrop); static IDataObject *active_data_object; static HWND last_droptarget_hwnd; typedef struct { IDataObject IDataObject_iface; LONG ref; CFTypeRef pasteboard; } DragDropDataObject; static inline DragDropDataObject *impl_from_IDataObject(IDataObject *iface) { return CONTAINING_RECORD(iface, DragDropDataObject, IDataObject_iface); } static HRESULT WINAPI dddo_QueryInterface(IDataObject* iface, REFIID riid, LPVOID *ppvObj) { DragDropDataObject *This = impl_from_IDataObject(iface); TRACE("(%p)->(%s,%p)\n", This, debugstr_guid(riid), ppvObj); if (IsEqualIID(riid, &IID_IUnknown) || (IsEqualIID(riid, &IID_IDataObject))) { *ppvObj = This; IUnknown_AddRef((IUnknown*)This); return S_OK; } *ppvObj = NULL; return E_NOINTERFACE; } static ULONG WINAPI dddo_AddRef(IDataObject* iface) { DragDropDataObject *This = impl_from_IDataObject(iface); ULONG refCount = InterlockedIncrement(&This->ref); TRACE("(%p)->(count=%u)\n", This, refCount - 1); return refCount; } static ULONG WINAPI dddo_Release(IDataObject* iface) { DragDropDataObject *This = impl_from_IDataObject(iface); ULONG refCount = InterlockedDecrement(&This->ref); TRACE("(%p)->(count=%u)\n", This, refCount + 1); if (refCount) return refCount; TRACE("-- destroying DragDropDataObject (%p)\n", This); CFRelease(This->pasteboard); HeapFree(GetProcessHeap(), 0, This); return 0; } static HRESULT WINAPI dddo_GetData(IDataObject* iface, FORMATETC* formatEtc, STGMEDIUM* medium) { DragDropDataObject *This = impl_from_IDataObject(iface); HRESULT hr; TRACE("This %p formatEtc %s\n", This, debugstr_format(formatEtc->cfFormat)); hr = IDataObject_QueryGetData(iface, formatEtc); if (SUCCEEDED(hr)) { medium->tymed = TYMED_HGLOBAL; medium->u.hGlobal = macdrv_get_pasteboard_data(This->pasteboard, formatEtc->cfFormat); medium->pUnkForRelease = NULL; hr = medium->u.hGlobal ? S_OK : E_OUTOFMEMORY; } return hr; } static HRESULT WINAPI dddo_GetDataHere(IDataObject* iface, FORMATETC* formatEtc, STGMEDIUM* medium) { FIXME("iface %p formatEtc %p medium %p; stub\n", iface, formatEtc, medium); return DATA_E_FORMATETC; } static HRESULT WINAPI dddo_QueryGetData(IDataObject* iface, FORMATETC* formatEtc) { DragDropDataObject *This = impl_from_IDataObject(iface); HRESULT hr = DV_E_FORMATETC; TRACE("This %p formatEtc %p={.tymed=0x%x, .dwAspect=%d, .cfFormat=%s}\n", This, formatEtc, formatEtc->tymed, formatEtc->dwAspect, debugstr_format(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; } if (macdrv_pasteboard_has_format(This->pasteboard, formatEtc->cfFormat)) hr = S_OK; TRACE(" -> 0x%x\n", hr); return hr; } static HRESULT WINAPI dddo_GetConicalFormatEtc(IDataObject* iface, FORMATETC* formatEtc, FORMATETC* formatEtcOut) { DragDropDataObject *This = impl_from_IDataObject(iface); TRACE("This %p formatEtc %p={.tymed=0x%x, .dwAspect=%d, .cfFormat=%s}\n", This, formatEtc, formatEtc->tymed, formatEtc->dwAspect, debugstr_format(formatEtc->cfFormat)); *formatEtcOut = *formatEtc; formatEtcOut->ptd = NULL; return DATA_S_SAMEFORMATETC; } static HRESULT WINAPI dddo_SetData(IDataObject* iface, FORMATETC* formatEtc, STGMEDIUM* medium, BOOL fRelease) { DragDropDataObject *This = impl_from_IDataObject(iface); TRACE("This %p formatEtc %p={.tymed=0x%x, .dwAspect=%d, .cfFormat=%s} medium %p fRelease %d\n", This, formatEtc, formatEtc->tymed, formatEtc->dwAspect, debugstr_format(formatEtc->cfFormat), medium, fRelease); return E_NOTIMPL; } static HRESULT WINAPI dddo_EnumFormatEtc(IDataObject* iface, DWORD direction, IEnumFORMATETC** enumFormatEtc) { DragDropDataObject *This = impl_from_IDataObject(iface); CFArrayRef formats; HRESULT hr; TRACE("This %p direction %u enumFormatEtc %p\n", This, direction, enumFormatEtc); if (direction != DATADIR_GET) { WARN("only the get direction is implemented\n"); return E_NOTIMPL; } formats = macdrv_copy_pasteboard_formats(This->pasteboard); if (formats) { INT count = CFArrayGetCount(formats); FORMATETC *formatEtcs = HeapAlloc(GetProcessHeap(), 0, count * sizeof(FORMATETC)); if (formatEtcs) { INT i; for (i = 0; i < count; i++) { formatEtcs[i].cfFormat = (UINT)CFArrayGetValueAtIndex(formats, i); formatEtcs[i].ptd = NULL; formatEtcs[i].dwAspect = DVASPECT_CONTENT; formatEtcs[i].lindex = -1; formatEtcs[i].tymed = TYMED_HGLOBAL; } hr = SHCreateStdEnumFmtEtc(count, formatEtcs, enumFormatEtc); HeapFree(GetProcessHeap(), 0, formatEtcs); } else hr = E_OUTOFMEMORY; CFRelease(formats); } else hr = SHCreateStdEnumFmtEtc(0, NULL, enumFormatEtc); TRACE(" -> 0x%x\n", hr); return hr; } static HRESULT WINAPI dddo_DAdvise(IDataObject* iface, FORMATETC* formatEtc, DWORD advf, IAdviseSink* pAdvSink, DWORD* pdwConnection) { FIXME("(%p, %p, %u, %p, %p): stub\n", iface, formatEtc, advf, pAdvSink, pdwConnection); return OLE_E_ADVISENOTSUPPORTED; } static HRESULT WINAPI dddo_DUnadvise(IDataObject* iface, DWORD dwConnection) { FIXME("(%p, %u): stub\n", iface, dwConnection); return OLE_E_ADVISENOTSUPPORTED; } static HRESULT WINAPI dddo_EnumDAdvise(IDataObject* iface, IEnumSTATDATA** pEnumAdvise) { FIXME("(%p, %p): stub\n", iface, pEnumAdvise); return OLE_E_ADVISENOTSUPPORTED; } static const IDataObjectVtbl dovt = { dddo_QueryInterface, dddo_AddRef, dddo_Release, dddo_GetData, dddo_GetDataHere, dddo_QueryGetData, dddo_GetConicalFormatEtc, dddo_SetData, dddo_EnumFormatEtc, dddo_DAdvise, dddo_DUnadvise, dddo_EnumDAdvise }; static IDataObject *create_data_object_for_pasteboard(CFTypeRef pasteboard) { DragDropDataObject *dddo; dddo = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*dddo)); if (!dddo) return NULL; dddo->ref = 1; dddo->IDataObject_iface.lpVtbl = &dovt; dddo->pasteboard = CFRetain(pasteboard); return &dddo->IDataObject_iface; } /************************************************************************** * drag_operations_to_dropeffects */ static DWORD drag_operations_to_dropeffects(uint32_t ops) { DWORD effects = DROPEFFECT_NONE; if (ops & (DRAG_OP_COPY | DRAG_OP_GENERIC)) effects |= DROPEFFECT_COPY; if (ops & DRAG_OP_MOVE) effects |= DROPEFFECT_MOVE; if (ops & (DRAG_OP_LINK | DRAG_OP_GENERIC)) effects |= DROPEFFECT_LINK; return effects; } /************************************************************************** * dropeffect_to_drag_operation */ static uint32_t dropeffect_to_drag_operation(DWORD effect, uint32_t ops) { if (effect & DROPEFFECT_LINK && ops & DRAG_OP_LINK) return DRAG_OP_LINK; if (effect & DROPEFFECT_COPY && ops & DRAG_OP_COPY) return DRAG_OP_COPY; if (effect & DROPEFFECT_MOVE && ops & DRAG_OP_MOVE) return DRAG_OP_MOVE; if (effect & DROPEFFECT_LINK && ops & DRAG_OP_GENERIC) return DRAG_OP_GENERIC; if (effect & DROPEFFECT_COPY && ops & DRAG_OP_GENERIC) return DRAG_OP_GENERIC; return DRAG_OP_NONE; } /* 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; } /************************************************************************** * query_drag_drop */ BOOL query_drag_drop(macdrv_query* query) { BOOL ret = FALSE; HWND hwnd = macdrv_get_window_hwnd(query->window); struct macdrv_win_data *data = get_win_data(hwnd); POINT pt; IDropTarget *droptarget; TRACE("win %p/%p x,y %d,%d op 0x%08x pasteboard %p\n", hwnd, query->window, query->drag_drop.x, query->drag_drop.y, query->drag_drop.op, query->drag_drop.pasteboard); if (!data) { WARN("no win_data for win %p/%p\n", hwnd, query->window); return FALSE; } pt.x = query->drag_drop.x + data->whole_rect.left; pt.y = query->drag_drop.y + data->whole_rect.top; release_win_data(data); droptarget = get_droptarget_pointer(last_droptarget_hwnd); if (droptarget) { HRESULT hr; POINTL pointl; DWORD effect = drag_operations_to_dropeffects(query->drag_drop.op); if (!active_data_object) { WARN("shouldn't happen: no active IDataObject\n"); active_data_object = create_data_object_for_pasteboard(query->drag_drop.pasteboard); } pointl.x = pt.x; pointl.y = pt.y; TRACE("Drop hwnd %p droptarget %p pointl (%d,%d) effect 0x%08x\n", last_droptarget_hwnd, droptarget, pointl.x, pointl.y, effect); hr = IDropTarget_Drop(droptarget, active_data_object, MK_LBUTTON, pointl, &effect); if (SUCCEEDED(hr)) { if (effect != DROPEFFECT_NONE) { TRACE("drop succeeded\n"); ret = TRUE; } else TRACE("the application refused the drop\n"); } else WARN("drop failed, error 0x%08X\n", hr); IDropTarget_Release(droptarget); } else { hwnd = WindowFromPoint(pt); while (hwnd && !(GetWindowLongW(hwnd, GWL_EXSTYLE) & WS_EX_ACCEPTFILES)) hwnd = GetParent(hwnd); if (hwnd) { HDROP hdrop = macdrv_get_pasteboard_data(query->drag_drop.pasteboard, CF_HDROP); DROPFILES *dropfiles = GlobalLock(hdrop); if (dropfiles) { dropfiles->pt.x = pt.x; dropfiles->pt.y = pt.y; dropfiles->fNC = TRUE; TRACE("sending WM_DROPFILES: hwnd %p pt %s %s\n", hwnd, wine_dbgstr_point(&pt), debugstr_w((WCHAR*)((char*)dropfiles + dropfiles->pFiles))); GlobalUnlock(hdrop); ret = PostMessageW(hwnd, WM_DROPFILES, (WPARAM)hdrop, 0L); /* hdrop is owned by the message and freed when the recipient calls DragFinish(). */ } else GlobalFree(hdrop); } } if (active_data_object) IDataObject_Release(active_data_object); active_data_object = NULL; last_droptarget_hwnd = NULL; return ret; } /************************************************************************** * query_drag_exited */ BOOL query_drag_exited(macdrv_query* query) { HWND hwnd = macdrv_get_window_hwnd(query->window); IDropTarget *droptarget; TRACE("win %p/%p\n", hwnd, query->window); droptarget = get_droptarget_pointer(last_droptarget_hwnd); if (droptarget) { HRESULT hr; TRACE("DragLeave hwnd %p droptarget %p\n", last_droptarget_hwnd, droptarget); hr = IDropTarget_DragLeave(droptarget); if (FAILED(hr)) WARN("IDropTarget_DragLeave failed, error 0x%08X\n", hr); IDropTarget_Release(droptarget); } if (active_data_object) IDataObject_Release(active_data_object); active_data_object = NULL; last_droptarget_hwnd = NULL; return TRUE; } /************************************************************************** * query_drag_operation */ BOOL query_drag_operation(macdrv_query* query) { BOOL ret = FALSE; HWND hwnd = macdrv_get_window_hwnd(query->window); struct macdrv_win_data *data = get_win_data(hwnd); POINT pt; DWORD effect; IDropTarget *droptarget; HRESULT hr; TRACE("win %p/%p x,y %d,%d offered_ops 0x%x pasteboard %p\n", hwnd, query->window, query->drag_operation.x, query->drag_operation.y, query->drag_operation.offered_ops, query->drag_operation.pasteboard); if (!data) { WARN("no win_data for win %p/%p\n", hwnd, query->window); return FALSE; } pt.x = query->drag_operation.x + data->whole_rect.left; pt.y = query->drag_operation.y + data->whole_rect.top; release_win_data(data); effect = drag_operations_to_dropeffects(query->drag_operation.offered_ops); /* Instead of the top-level window we got in the query, start with the deepest child under the cursor. Travel up the hierarchy looking for a window that has an associated IDropTarget. */ hwnd = WindowFromPoint(pt); do { droptarget = get_droptarget_pointer(hwnd); } while (!droptarget && (hwnd = GetParent(hwnd))); if (last_droptarget_hwnd != hwnd) { if (last_droptarget_hwnd) { IDropTarget *old_droptarget = get_droptarget_pointer(last_droptarget_hwnd); if (old_droptarget) { TRACE("DragLeave hwnd %p droptarget %p\n", last_droptarget_hwnd, old_droptarget); hr = IDropTarget_DragLeave(old_droptarget); if (FAILED(hr)) WARN("IDropTarget_DragLeave failed, error 0x%08X\n", hr); IDropTarget_Release(old_droptarget); } } last_droptarget_hwnd = hwnd; if (droptarget) { POINTL pointl = { pt.x, pt.y }; if (!active_data_object) active_data_object = create_data_object_for_pasteboard(query->drag_operation.pasteboard); TRACE("DragEnter hwnd %p droptarget %p\n", hwnd, droptarget); hr = IDropTarget_DragEnter(droptarget, active_data_object, MK_LBUTTON, pointl, &effect); if (SUCCEEDED(hr)) { query->drag_operation.accepted_op = dropeffect_to_drag_operation(effect, query->drag_operation.offered_ops); TRACE(" effect %d accepted op %d\n", effect, query->drag_operation.accepted_op); ret = TRUE; } else WARN("IDropTarget_DragEnter failed, error 0x%08X\n", hr); IDropTarget_Release(droptarget); } } else if (droptarget) { POINTL pointl = { pt.x, pt.y }; TRACE("DragOver hwnd %p droptarget %p\n", hwnd, droptarget); hr = IDropTarget_DragOver(droptarget, MK_LBUTTON, pointl, &effect); if (SUCCEEDED(hr)) { query->drag_operation.accepted_op = dropeffect_to_drag_operation(effect, query->drag_operation.offered_ops); TRACE(" effect %d accepted op %d\n", effect, query->drag_operation.accepted_op); ret = TRUE; } else WARN("IDropTarget_DragOver failed, error 0x%08X\n", hr); IDropTarget_Release(droptarget); } if (!ret) { hwnd = WindowFromPoint(pt); while (hwnd && !(GetWindowLongW(hwnd, GWL_EXSTYLE) & WS_EX_ACCEPTFILES)) hwnd = GetParent(hwnd); if (hwnd) { FORMATETC formatEtc; if (!active_data_object) active_data_object = create_data_object_for_pasteboard(query->drag_operation.pasteboard); formatEtc.cfFormat = CF_HDROP; formatEtc.ptd = NULL; formatEtc.dwAspect = DVASPECT_CONTENT; formatEtc.lindex = -1; formatEtc.tymed = TYMED_HGLOBAL; if (SUCCEEDED(IDataObject_QueryGetData(active_data_object, &formatEtc))) { TRACE("WS_EX_ACCEPTFILES hwnd %p\n", hwnd); query->drag_operation.accepted_op = DRAG_OP_GENERIC; ret = TRUE; } } } TRACE(" -> %s\n", ret ? "TRUE" : "FALSE"); return ret; }