dplayx: Merge the IDirectPlay4_Close helper.
[wine] / dlls / winemac.drv / dragdrop.c
1 /*
2  * MACDRV Drag and drop code
3  *
4  * Copyright 2003 Ulrich Czekalla
5  * Copyright 2007 Damjan Jovanovic
6  * Copyright 2013 Ken Thomases for CodeWeavers Inc.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21  */
22
23 #include "config.h"
24
25 #define NONAMELESSUNION
26
27 #include "macdrv.h"
28
29 #define COBJMACROS
30 #include "objidl.h"
31 #include "shellapi.h"
32 #include "shlobj.h"
33
34 WINE_DEFAULT_DEBUG_CHANNEL(dragdrop);
35
36
37 static IDataObject *active_data_object;
38 static HWND last_droptarget_hwnd;
39
40
41 typedef struct
42 {
43     IDataObject IDataObject_iface;
44     LONG        ref;
45     CFTypeRef   pasteboard;
46 } DragDropDataObject;
47
48
49 static inline DragDropDataObject *impl_from_IDataObject(IDataObject *iface)
50 {
51     return CONTAINING_RECORD(iface, DragDropDataObject, IDataObject_iface);
52 }
53
54
55 static HRESULT WINAPI dddo_QueryInterface(IDataObject* iface, REFIID riid, LPVOID *ppvObj)
56 {
57     DragDropDataObject *This = impl_from_IDataObject(iface);
58
59     TRACE("(%p)->(%s,%p)\n", This, debugstr_guid(riid), ppvObj);
60
61     if (IsEqualIID(riid, &IID_IUnknown) || (IsEqualIID(riid, &IID_IDataObject)))
62     {
63         *ppvObj = This;
64         IUnknown_AddRef((IUnknown*)This);
65         return S_OK;
66     }
67
68     *ppvObj = NULL;
69     return E_NOINTERFACE;
70 }
71
72
73 static ULONG WINAPI dddo_AddRef(IDataObject* iface)
74 {
75     DragDropDataObject *This = impl_from_IDataObject(iface);
76     ULONG refCount = InterlockedIncrement(&This->ref);
77
78     TRACE("(%p)->(count=%u)\n", This, refCount - 1);
79
80     return refCount;
81 }
82
83
84 static ULONG WINAPI dddo_Release(IDataObject* iface)
85 {
86     DragDropDataObject *This = impl_from_IDataObject(iface);
87     ULONG refCount = InterlockedDecrement(&This->ref);
88
89     TRACE("(%p)->(count=%u)\n", This, refCount + 1);
90     if (refCount)
91         return refCount;
92
93     TRACE("-- destroying DragDropDataObject (%p)\n", This);
94     CFRelease(This->pasteboard);
95     HeapFree(GetProcessHeap(), 0, This);
96     return 0;
97 }
98
99
100 static HRESULT WINAPI dddo_GetData(IDataObject* iface, FORMATETC* formatEtc, STGMEDIUM* medium)
101 {
102     DragDropDataObject *This = impl_from_IDataObject(iface);
103     HRESULT hr;
104
105     TRACE("This %p formatEtc %s\n", This, debugstr_format(formatEtc->cfFormat));
106
107     hr = IDataObject_QueryGetData(iface, formatEtc);
108     if (SUCCEEDED(hr))
109     {
110         medium->tymed = TYMED_HGLOBAL;
111         medium->u.hGlobal = macdrv_get_pasteboard_data(This->pasteboard, formatEtc->cfFormat);
112         medium->pUnkForRelease = NULL;
113         hr = medium->u.hGlobal ? S_OK : E_OUTOFMEMORY;
114     }
115
116     return hr;
117 }
118
119
120 static HRESULT WINAPI dddo_GetDataHere(IDataObject* iface, FORMATETC* formatEtc,
121                                        STGMEDIUM* medium)
122 {
123     FIXME("iface %p formatEtc %p medium %p; stub\n", iface, formatEtc, medium);
124     return DATA_E_FORMATETC;
125 }
126
127
128 static HRESULT WINAPI dddo_QueryGetData(IDataObject* iface, FORMATETC* formatEtc)
129 {
130     DragDropDataObject *This = impl_from_IDataObject(iface);
131     HRESULT hr = DV_E_FORMATETC;
132
133     TRACE("This %p formatEtc %p={.tymed=0x%x, .dwAspect=%d, .cfFormat=%s}\n",
134           This, formatEtc, formatEtc->tymed, formatEtc->dwAspect,
135           debugstr_format(formatEtc->cfFormat));
136
137     if (formatEtc->tymed && !(formatEtc->tymed & TYMED_HGLOBAL))
138     {
139         FIXME("only HGLOBAL medium types supported right now\n");
140         return DV_E_TYMED;
141     }
142     if (formatEtc->dwAspect != DVASPECT_CONTENT)
143     {
144         FIXME("only the content aspect is supported right now\n");
145         return E_NOTIMPL;
146     }
147
148     if (macdrv_pasteboard_has_format(This->pasteboard, formatEtc->cfFormat))
149         hr = S_OK;
150
151     TRACE(" -> 0x%x\n", hr);
152     return hr;
153 }
154
155
156 static HRESULT WINAPI dddo_GetConicalFormatEtc(IDataObject* iface, FORMATETC* formatEtc,
157                                                FORMATETC* formatEtcOut)
158 {
159     DragDropDataObject *This = impl_from_IDataObject(iface);
160
161     TRACE("This %p formatEtc %p={.tymed=0x%x, .dwAspect=%d, .cfFormat=%s}\n",
162           This, formatEtc, formatEtc->tymed, formatEtc->dwAspect,
163           debugstr_format(formatEtc->cfFormat));
164
165     *formatEtcOut = *formatEtc;
166     formatEtcOut->ptd = NULL;
167     return DATA_S_SAMEFORMATETC;
168 }
169
170
171 static HRESULT WINAPI dddo_SetData(IDataObject* iface, FORMATETC* formatEtc,
172                                    STGMEDIUM* medium, BOOL fRelease)
173 {
174     DragDropDataObject *This = impl_from_IDataObject(iface);
175
176     TRACE("This %p formatEtc %p={.tymed=0x%x, .dwAspect=%d, .cfFormat=%s} medium %p fRelease %d\n",
177           This, formatEtc, formatEtc->tymed, formatEtc->dwAspect,
178           debugstr_format(formatEtc->cfFormat), medium, fRelease);
179
180     return E_NOTIMPL;
181 }
182
183
184 static HRESULT WINAPI dddo_EnumFormatEtc(IDataObject* iface, DWORD direction,
185                                          IEnumFORMATETC** enumFormatEtc)
186 {
187     DragDropDataObject *This = impl_from_IDataObject(iface);
188     CFArrayRef formats;
189     HRESULT hr;
190
191     TRACE("This %p direction %u enumFormatEtc %p\n", This, direction, enumFormatEtc);
192
193     if (direction != DATADIR_GET)
194     {
195         WARN("only the get direction is implemented\n");
196         return E_NOTIMPL;
197     }
198
199     formats = macdrv_copy_pasteboard_formats(This->pasteboard);
200     if (formats)
201     {
202         INT count = CFArrayGetCount(formats);
203         FORMATETC *formatEtcs = HeapAlloc(GetProcessHeap(), 0, count * sizeof(FORMATETC));
204         if (formatEtcs)
205         {
206             INT i;
207
208             for (i = 0; i < count; i++)
209             {
210                 formatEtcs[i].cfFormat = (UINT)CFArrayGetValueAtIndex(formats, i);
211                 formatEtcs[i].ptd = NULL;
212                 formatEtcs[i].dwAspect = DVASPECT_CONTENT;
213                 formatEtcs[i].lindex = -1;
214                 formatEtcs[i].tymed = TYMED_HGLOBAL;
215             }
216
217             hr = SHCreateStdEnumFmtEtc(count, formatEtcs, enumFormatEtc);
218             HeapFree(GetProcessHeap(), 0, formatEtcs);
219         }
220         else
221             hr = E_OUTOFMEMORY;
222
223         CFRelease(formats);
224     }
225     else
226         hr = SHCreateStdEnumFmtEtc(0, NULL, enumFormatEtc);
227
228     TRACE(" -> 0x%x\n", hr);
229     return hr;
230 }
231
232
233 static HRESULT WINAPI dddo_DAdvise(IDataObject* iface, FORMATETC* formatEtc, DWORD advf,
234                                    IAdviseSink* pAdvSink, DWORD* pdwConnection)
235 {
236     FIXME("(%p, %p, %u, %p, %p): stub\n", iface, formatEtc, advf,
237           pAdvSink, pdwConnection);
238     return OLE_E_ADVISENOTSUPPORTED;
239 }
240
241
242 static HRESULT WINAPI dddo_DUnadvise(IDataObject* iface, DWORD dwConnection)
243 {
244     FIXME("(%p, %u): stub\n", iface, dwConnection);
245     return OLE_E_ADVISENOTSUPPORTED;
246 }
247
248
249 static HRESULT WINAPI dddo_EnumDAdvise(IDataObject* iface, IEnumSTATDATA** pEnumAdvise)
250 {
251     FIXME("(%p, %p): stub\n", iface, pEnumAdvise);
252     return OLE_E_ADVISENOTSUPPORTED;
253 }
254
255
256 static const IDataObjectVtbl dovt =
257 {
258     dddo_QueryInterface,
259     dddo_AddRef,
260     dddo_Release,
261     dddo_GetData,
262     dddo_GetDataHere,
263     dddo_QueryGetData,
264     dddo_GetConicalFormatEtc,
265     dddo_SetData,
266     dddo_EnumFormatEtc,
267     dddo_DAdvise,
268     dddo_DUnadvise,
269     dddo_EnumDAdvise
270 };
271
272
273 static IDataObject *create_data_object_for_pasteboard(CFTypeRef pasteboard)
274 {
275     DragDropDataObject *dddo;
276
277     dddo = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*dddo));
278     if (!dddo)
279         return NULL;
280
281     dddo->ref = 1;
282     dddo->IDataObject_iface.lpVtbl = &dovt;
283     dddo->pasteboard = CFRetain(pasteboard);
284
285     return &dddo->IDataObject_iface;
286 }
287
288
289 /**************************************************************************
290  *              drag_operations_to_dropeffects
291  */
292 static DWORD drag_operations_to_dropeffects(uint32_t ops)
293 {
294     DWORD effects = DROPEFFECT_NONE;
295     if (ops & (DRAG_OP_COPY | DRAG_OP_GENERIC))
296         effects |= DROPEFFECT_COPY;
297     if (ops & DRAG_OP_MOVE)
298         effects |= DROPEFFECT_MOVE;
299     if (ops & (DRAG_OP_LINK | DRAG_OP_GENERIC))
300         effects |= DROPEFFECT_LINK;
301     return effects;
302 }
303
304
305 /**************************************************************************
306  *              dropeffect_to_drag_operation
307  */
308 static uint32_t dropeffect_to_drag_operation(DWORD effect, uint32_t ops)
309 {
310     switch (effect)
311     {
312         case DROPEFFECT_COPY: return (ops & DRAG_OP_COPY) ? DRAG_OP_COPY : DRAG_OP_GENERIC;
313         case DROPEFFECT_MOVE: return DRAG_OP_MOVE;
314         case DROPEFFECT_LINK: return (ops & DRAG_OP_LINK) ? DRAG_OP_LINK : DRAG_OP_GENERIC;
315     }
316
317     return DRAG_OP_NONE;
318 }
319
320
321 /* Based on functions in dlls/ole32/ole2.c */
322 static HANDLE get_droptarget_local_handle(HWND hwnd)
323 {
324     static const WCHAR prop_marshalleddroptarget[] =
325         {'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};
326     HANDLE handle;
327     HANDLE local_handle = 0;
328
329     handle = GetPropW(hwnd, prop_marshalleddroptarget);
330     if (handle)
331     {
332         DWORD pid;
333         HANDLE process;
334
335         GetWindowThreadProcessId(hwnd, &pid);
336         process = OpenProcess(PROCESS_DUP_HANDLE, FALSE, pid);
337         if (process)
338         {
339             DuplicateHandle(process, handle, GetCurrentProcess(), &local_handle, 0, FALSE, DUPLICATE_SAME_ACCESS);
340             CloseHandle(process);
341         }
342     }
343     return local_handle;
344 }
345
346
347 static HRESULT create_stream_from_map(HANDLE map, IStream **stream)
348 {
349     HRESULT hr = E_OUTOFMEMORY;
350     HGLOBAL hmem;
351     void *data;
352     MEMORY_BASIC_INFORMATION info;
353
354     data = MapViewOfFile(map, FILE_MAP_READ, 0, 0, 0);
355     if(!data) return hr;
356
357     VirtualQuery(data, &info, sizeof(info));
358     TRACE("size %d\n", (int)info.RegionSize);
359
360     hmem = GlobalAlloc(GMEM_MOVEABLE, info.RegionSize);
361     if(hmem)
362     {
363         memcpy(GlobalLock(hmem), data, info.RegionSize);
364         GlobalUnlock(hmem);
365         hr = CreateStreamOnHGlobal(hmem, TRUE, stream);
366     }
367     UnmapViewOfFile(data);
368     return hr;
369 }
370
371
372 static IDropTarget* get_droptarget_pointer(HWND hwnd)
373 {
374     IDropTarget *droptarget = NULL;
375     HANDLE map;
376     IStream *stream;
377
378     map = get_droptarget_local_handle(hwnd);
379     if(!map) return NULL;
380
381     if(SUCCEEDED(create_stream_from_map(map, &stream)))
382     {
383         CoUnmarshalInterface(stream, &IID_IDropTarget, (void**)&droptarget);
384         IStream_Release(stream);
385     }
386     CloseHandle(map);
387     return droptarget;
388 }
389
390
391 /**************************************************************************
392  *              query_drag_drop
393  */
394 BOOL query_drag_drop(macdrv_query* query)
395 {
396     BOOL ret = FALSE;
397     HWND hwnd = macdrv_get_window_hwnd(query->window);
398     struct macdrv_win_data *data = get_win_data(hwnd);
399     POINT pt;
400     IDropTarget *droptarget;
401
402     TRACE("win %p/%p x,y %d,%d op 0x%08x pasteboard %p\n", hwnd, query->window,
403           query->drag_drop.x, query->drag_drop.y, query->drag_drop.op, query->drag_drop.pasteboard);
404
405     if (!data)
406     {
407         WARN("no win_data for win %p/%p\n", hwnd, query->window);
408         return FALSE;
409     }
410
411     pt.x = query->drag_drop.x + data->whole_rect.left;
412     pt.y = query->drag_drop.y + data->whole_rect.top;
413     release_win_data(data);
414
415     droptarget = get_droptarget_pointer(last_droptarget_hwnd);
416     if (droptarget)
417     {
418         HRESULT hr;
419         POINTL pointl;
420         DWORD effect = drag_operations_to_dropeffects(query->drag_drop.op);
421
422         if (!active_data_object)
423         {
424             WARN("shouldn't happen: no active IDataObject\n");
425             active_data_object = create_data_object_for_pasteboard(query->drag_drop.pasteboard);
426         }
427
428         pointl.x = pt.x;
429         pointl.y = pt.y;
430         TRACE("Drop hwnd %p droptarget %p pointl (%d,%d) effect 0x%08x\n", last_droptarget_hwnd,
431               droptarget, pointl.x, pointl.y, effect);
432         hr = IDropTarget_Drop(droptarget, active_data_object, MK_LBUTTON, pointl, &effect);
433         if (SUCCEEDED(hr))
434         {
435             if (effect != DROPEFFECT_NONE)
436             {
437                 TRACE("drop succeeded\n");
438                 ret = TRUE;
439             }
440             else
441                 TRACE("the application refused the drop\n");
442         }
443         else
444             WARN("drop failed, error 0x%08X\n", hr);
445         IDropTarget_Release(droptarget);
446     }
447     else
448     {
449         hwnd = WindowFromPoint(pt);
450         while (hwnd && !(GetWindowLongW(hwnd, GWL_EXSTYLE) & WS_EX_ACCEPTFILES))
451             hwnd = GetParent(hwnd);
452         if (hwnd)
453         {
454             HDROP hdrop = macdrv_get_pasteboard_data(query->drag_drop.pasteboard, CF_HDROP);
455             DROPFILES *dropfiles = GlobalLock(hdrop);
456             if (dropfiles)
457             {
458                 dropfiles->pt.x = pt.x;
459                 dropfiles->pt.y = pt.y;
460                 dropfiles->fNC  = TRUE;
461
462                 TRACE("sending WM_DROPFILES: hwnd %p pt %s %s\n", hwnd, wine_dbgstr_point(&pt),
463                       debugstr_w((WCHAR*)((char*)dropfiles + dropfiles->pFiles)));
464
465                 GlobalUnlock(hdrop);
466
467                 ret = PostMessageW(hwnd, WM_DROPFILES, (WPARAM)hdrop, 0L);
468                 /* hdrop is owned by the message and freed when the recipient calls DragFinish(). */
469             }
470             else
471                 GlobalFree(hdrop);
472         }
473     }
474
475     if (active_data_object) IDataObject_Release(active_data_object);
476     active_data_object = NULL;
477     last_droptarget_hwnd = NULL;
478     return ret;
479 }
480
481
482 /**************************************************************************
483  *              query_drag_exited
484  */
485 BOOL query_drag_exited(macdrv_query* query)
486 {
487     HWND hwnd = macdrv_get_window_hwnd(query->window);
488     IDropTarget *droptarget;
489
490     TRACE("win %p/%p\n", hwnd, query->window);
491
492     droptarget = get_droptarget_pointer(last_droptarget_hwnd);
493     if (droptarget)
494     {
495         HRESULT hr;
496
497         TRACE("DragLeave hwnd %p droptarget %p\n", last_droptarget_hwnd, droptarget);
498         hr = IDropTarget_DragLeave(droptarget);
499         if (FAILED(hr))
500             WARN("IDropTarget_DragLeave failed, error 0x%08X\n", hr);
501         IDropTarget_Release(droptarget);
502     }
503
504     if (active_data_object) IDataObject_Release(active_data_object);
505     active_data_object = NULL;
506     last_droptarget_hwnd = NULL;
507
508     return TRUE;
509 }
510
511
512 /**************************************************************************
513  *              query_drag_operation
514  */
515 BOOL query_drag_operation(macdrv_query* query)
516 {
517     BOOL ret = FALSE;
518     HWND hwnd = macdrv_get_window_hwnd(query->window);
519     struct macdrv_win_data *data = get_win_data(hwnd);
520     POINT pt;
521     DWORD effect;
522     IDropTarget *droptarget;
523     HRESULT hr;
524
525     TRACE("win %p/%p x,y %d,%d offered_ops 0x%x pasteboard %p\n", hwnd, query->window,
526           query->drag_operation.x, query->drag_operation.y, query->drag_operation.offered_ops,
527           query->drag_operation.pasteboard);
528
529     if (!data)
530     {
531         WARN("no win_data for win %p/%p\n", hwnd, query->window);
532         return FALSE;
533     }
534
535     pt.x = query->drag_operation.x + data->whole_rect.left;
536     pt.y = query->drag_operation.y + data->whole_rect.top;
537     release_win_data(data);
538
539     effect = drag_operations_to_dropeffects(query->drag_operation.offered_ops);
540
541     /* Instead of the top-level window we got in the query, start with the deepest
542        child under the cursor.  Travel up the hierarchy looking for a window that
543        has an associated IDropTarget. */
544     hwnd = WindowFromPoint(pt);
545     do
546     {
547         droptarget = get_droptarget_pointer(hwnd);
548     } while (!droptarget && (hwnd = GetParent(hwnd)));
549
550     if (last_droptarget_hwnd != hwnd)
551     {
552         if (last_droptarget_hwnd)
553         {
554             IDropTarget *old_droptarget = get_droptarget_pointer(last_droptarget_hwnd);
555             if (old_droptarget)
556             {
557                 TRACE("DragLeave hwnd %p droptarget %p\n", last_droptarget_hwnd, old_droptarget);
558                 hr = IDropTarget_DragLeave(old_droptarget);
559                 if (FAILED(hr))
560                     WARN("IDropTarget_DragLeave failed, error 0x%08X\n", hr);
561                 IDropTarget_Release(old_droptarget);
562             }
563         }
564
565         last_droptarget_hwnd = hwnd;
566
567         if (droptarget)
568         {
569             POINTL pointl = { pt.x, pt.y };
570
571             if (!active_data_object)
572                 active_data_object = create_data_object_for_pasteboard(query->drag_operation.pasteboard);
573
574             TRACE("DragEnter hwnd %p droptarget %p\n", hwnd, droptarget);
575             hr = IDropTarget_DragEnter(droptarget, active_data_object, MK_LBUTTON,
576                                        pointl, &effect);
577             if (SUCCEEDED(hr))
578             {
579                 query->drag_operation.accepted_op = dropeffect_to_drag_operation(effect,
580                                                         query->drag_operation.offered_ops);
581                 TRACE("    effect %d accepted op %d\n", effect, query->drag_operation.accepted_op);
582                 ret = TRUE;
583             }
584             else
585                 WARN("IDropTarget_DragEnter failed, error 0x%08X\n", hr);
586             IDropTarget_Release(droptarget);
587         }
588     }
589     else if (droptarget)
590     {
591         POINTL pointl = { pt.x, pt.y };
592
593         TRACE("DragOver hwnd %p droptarget %p\n", hwnd, droptarget);
594         hr = IDropTarget_DragOver(droptarget, MK_LBUTTON, pointl, &effect);
595         if (SUCCEEDED(hr))
596         {
597             query->drag_operation.accepted_op = dropeffect_to_drag_operation(effect,
598                                                     query->drag_operation.offered_ops);
599             TRACE("    effect %d accepted op %d\n", effect, query->drag_operation.accepted_op);
600             ret = TRUE;
601         }
602         else
603             WARN("IDropTarget_DragOver failed, error 0x%08X\n", hr);
604         IDropTarget_Release(droptarget);
605     }
606
607     if (!ret)
608     {
609         hwnd = WindowFromPoint(pt);
610         while (hwnd && !(GetWindowLongW(hwnd, GWL_EXSTYLE) & WS_EX_ACCEPTFILES))
611             hwnd = GetParent(hwnd);
612         if (hwnd)
613         {
614             FORMATETC formatEtc;
615
616             if (!active_data_object)
617                 active_data_object = create_data_object_for_pasteboard(query->drag_operation.pasteboard);
618
619             formatEtc.cfFormat = CF_HDROP;
620             formatEtc.ptd = NULL;
621             formatEtc.dwAspect = DVASPECT_CONTENT;
622             formatEtc.lindex = -1;
623             formatEtc.tymed = TYMED_HGLOBAL;
624             if (SUCCEEDED(IDataObject_QueryGetData(active_data_object, &formatEtc)))
625             {
626                 TRACE("WS_EX_ACCEPTFILES hwnd %p\n", hwnd);
627                 query->drag_operation.accepted_op = DRAG_OP_GENERIC;
628                 ret = TRUE;
629             }
630         }
631     }
632
633     TRACE(" -> %s\n", ret ? "TRUE" : "FALSE");
634     return ret;
635 }