| /* |
| * 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); |
| UINT *formats, count; |
| 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_get_pasteboard_formats(This->pasteboard, &count); |
| if (formats) |
| { |
| FORMATETC *formatEtcs = HeapAlloc(GetProcessHeap(), 0, count * sizeof(FORMATETC)); |
| if (formatEtcs) |
| { |
| INT i; |
| |
| for (i = 0; i < count; i++) |
| { |
| formatEtcs[i].cfFormat = 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; |
| |
| HeapFree(GetProcessHeap(), 0, 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(). */ |
| } |
| |
| if (!ret) 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; |
| } |