VirtualBox

source: vbox/trunk/src/VBox/Additions/WINNT/VBoxTray/VBoxDnDDropTarget.cpp@ 67116

Last change on this file since 67116 was 63311, checked in by vboxsync, 8 years ago

Use shlobj.h wrapper.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 20.8 KB
Line 
1/* $Id: VBoxDnDDropTarget.cpp 63311 2016-08-11 00:02:38Z vboxsync $ */
2/** @file
3 * VBoxDnDTarget.cpp - IDropTarget implementation.
4 */
5
6/*
7 * Copyright (C) 2014-2016 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17#include <iprt/win/windows.h>
18#include <new> /* For bad_alloc. */
19#include <iprt/win/shlobj.h> /* For DROPFILES and friends. */
20
21#include "VBoxTray.h"
22#include "VBoxHelpers.h"
23#include "VBoxDnD.h"
24
25#include "VBox/GuestHost/DragAndDrop.h"
26#include "VBox/HostServices/DragAndDropSvc.h"
27
28#ifdef LOG_GROUP
29# undef LOG_GROUP
30#endif
31#define LOG_GROUP LOG_GROUP_GUEST_DND
32#include <VBox/log.h>
33
34
35
36VBoxDnDDropTarget::VBoxDnDDropTarget(VBoxDnDWnd *pParent)
37 : mRefCount(1),
38 mpWndParent(pParent),
39 mdwCurEffect(0),
40 mpvData(NULL),
41 mcbData(0),
42 hEventDrop(NIL_RTSEMEVENT)
43{
44 int rc = RTSemEventCreate(&hEventDrop);
45 LogFlowFunc(("rc=%Rrc\n", rc)); NOREF(rc);
46}
47
48VBoxDnDDropTarget::~VBoxDnDDropTarget(void)
49{
50 reset();
51
52 int rc2 = RTSemEventDestroy(hEventDrop);
53 AssertRC(rc2);
54
55 LogFlowFunc(("rc=%Rrc, mRefCount=%RI32\n", rc2, mRefCount));
56}
57
58/*
59 * IUnknown methods.
60 */
61
62STDMETHODIMP_(ULONG) VBoxDnDDropTarget::AddRef(void)
63{
64 return InterlockedIncrement(&mRefCount);
65}
66
67STDMETHODIMP_(ULONG) VBoxDnDDropTarget::Release(void)
68{
69 LONG lCount = InterlockedDecrement(&mRefCount);
70 if (lCount == 0)
71 {
72 delete this;
73 return 0;
74 }
75
76 return lCount;
77}
78
79STDMETHODIMP VBoxDnDDropTarget::QueryInterface(REFIID iid, void **ppvObject)
80{
81 AssertPtrReturn(ppvObject, E_INVALIDARG);
82
83 if ( iid == IID_IDropSource
84 || iid == IID_IUnknown)
85 {
86 AddRef();
87 *ppvObject = this;
88 return S_OK;
89 }
90
91 *ppvObject = 0;
92 return E_NOINTERFACE;
93}
94
95/* static */
96void VBoxDnDDropTarget::DumpFormats(IDataObject *pDataObject)
97{
98 AssertPtrReturnVoid(pDataObject);
99
100 /* Enumerate supported source formats. This shouldn't happen too often
101 * on day to day use, but still keep it in here. */
102 IEnumFORMATETC *pEnumFormats;
103 HRESULT hr2 = pDataObject->EnumFormatEtc(DATADIR_GET, &pEnumFormats);
104 if (SUCCEEDED(hr2))
105 {
106 LogRel(("DnD: The following formats were offered to us:\n"));
107
108 FORMATETC curFormatEtc;
109 while (pEnumFormats->Next(1, &curFormatEtc,
110 NULL /* pceltFetched */) == S_OK)
111 {
112 WCHAR wszCfName[128]; /* 128 chars should be enough, rest will be truncated. */
113 hr2 = GetClipboardFormatNameW(curFormatEtc.cfFormat, wszCfName,
114 sizeof(wszCfName) / sizeof(WCHAR));
115 LogRel(("\tcfFormat=%RI16 (%s), tyMed=%RI32, dwAspect=%RI32, strCustomName=%ls, hr=%Rhrc\n",
116 curFormatEtc.cfFormat,
117 VBoxDnDDataObject::ClipboardFormatToString(curFormatEtc.cfFormat),
118 curFormatEtc.tymed,
119 curFormatEtc.dwAspect,
120 wszCfName, hr2));
121 }
122
123 pEnumFormats->Release();
124 }
125}
126
127/*
128 * IDropTarget methods.
129 */
130
131STDMETHODIMP VBoxDnDDropTarget::DragEnter(IDataObject *pDataObject, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect)
132{
133 RT_NOREF(pt);
134 AssertPtrReturn(pDataObject, E_INVALIDARG);
135 AssertPtrReturn(pdwEffect, E_INVALIDARG);
136
137 LogFlowFunc(("pDataObject=0x%p, grfKeyState=0x%x, x=%ld, y=%ld, dwEffect=%RU32\n",
138 pDataObject, grfKeyState, pt.x, pt.y, *pdwEffect));
139
140 reset();
141
142 /** @todo At the moment we only support one DnD format at a time. */
143
144#ifdef DEBUG
145 VBoxDnDDropTarget::DumpFormats(pDataObject);
146#endif
147
148 /* Try different formats.
149 * CF_HDROP is the most common one, so start with this. */
150 FORMATETC fmtEtc = { CF_HDROP, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
151 HRESULT hr = pDataObject->QueryGetData(&fmtEtc);
152 if (hr == S_OK)
153 {
154 mstrFormats = "text/uri-list";
155 }
156 else
157 {
158 LogFlowFunc(("CF_HDROP not wanted, hr=%Rhrc\n", hr));
159
160 /* So we couldn't retrieve the data in CF_HDROP format; try with
161 * CF_UNICODETEXT + CF_TEXT formats now. Rest stays the same. */
162 fmtEtc.cfFormat = CF_UNICODETEXT;
163 hr = pDataObject->QueryGetData(&fmtEtc);
164 if (hr == S_OK)
165 {
166 mstrFormats = "text/plain;charset=utf-8";
167 }
168 else
169 {
170 LogFlowFunc(("CF_UNICODETEXT not wanted, hr=%Rhrc\n", hr));
171
172 fmtEtc.cfFormat = CF_TEXT;
173 hr = pDataObject->QueryGetData(&fmtEtc);
174 if (hr == S_OK)
175 {
176 mstrFormats = "text/plain;charset=utf-8";
177 }
178 else
179 {
180 LogFlowFunc(("CF_TEXT not wanted, hr=%Rhrc\n", hr));
181 fmtEtc.cfFormat = 0; /* Set it to non-supported. */
182
183 /* Clean up. */
184 reset();
185 }
186 }
187 }
188
189 /* Did we find a format that we support? */
190 if (fmtEtc.cfFormat)
191 {
192 LogFlowFunc(("Found supported format %RI16 (%s)\n",
193 fmtEtc.cfFormat, VBoxDnDDataObject::ClipboardFormatToString(fmtEtc.cfFormat)));
194
195 /* Make a copy of the FORMATETC structure so that we later can
196 * use this for comparrison and stuff. */
197 /** @todo The DVTARGETDEVICE member only is a shallow copy for now! */
198 memcpy(&mFormatEtc, &fmtEtc, sizeof(FORMATETC));
199
200 /* Which drop effect we're going to use? */
201 /* Note: pt is not used since we don't need to differentiate within our
202 * proxy window. */
203 *pdwEffect = VBoxDnDDropTarget::GetDropEffect(grfKeyState, *pdwEffect);
204 }
205 else
206 {
207 /* No or incompatible data -- so no drop effect required. */
208 *pdwEffect = DROPEFFECT_NONE;
209
210 switch (hr)
211 {
212 case ERROR_INVALID_FUNCTION:
213 {
214 LogRel(("DnD: Drag and drop format is not supported by VBoxTray\n"));
215 VBoxDnDDropTarget::DumpFormats(pDataObject);
216 break;
217 }
218
219 default:
220 break;
221 }
222 }
223
224 LogFlowFunc(("Returning mstrFormats=%s, cfFormat=%RI16, pdwEffect=%ld, hr=%Rhrc\n",
225 mstrFormats.c_str(), fmtEtc.cfFormat, *pdwEffect, hr));
226 return hr;
227}
228
229STDMETHODIMP VBoxDnDDropTarget::DragOver(DWORD grfKeyState, POINTL pt, DWORD *pdwEffect)
230{
231 RT_NOREF(pt);
232 AssertPtrReturn(pdwEffect, E_INVALIDARG);
233
234#ifdef DEBUG_andy
235 LogFlowFunc(("cfFormat=%RI16, grfKeyState=0x%x, x=%ld, y=%ld\n",
236 mFormatEtc.cfFormat, grfKeyState, pt.x, pt.y));
237#endif
238
239 if (mFormatEtc.cfFormat)
240 {
241 /* Note: pt is not used since we don't need to differentiate within our
242 * proxy window. */
243 *pdwEffect = VBoxDnDDropTarget::GetDropEffect(grfKeyState, *pdwEffect);
244 }
245 else
246 {
247 *pdwEffect = DROPEFFECT_NONE;
248 }
249
250#ifdef DEBUG_andy
251 LogFlowFunc(("Returning *pdwEffect=%ld\n", *pdwEffect));
252#endif
253 return S_OK;
254}
255
256STDMETHODIMP VBoxDnDDropTarget::DragLeave(void)
257{
258#ifdef DEBUG_andy
259 LogFlowFunc(("cfFormat=%RI16\n", mFormatEtc.cfFormat));
260#endif
261
262 if (mpWndParent)
263 mpWndParent->hide();
264
265 return S_OK;
266}
267
268STDMETHODIMP VBoxDnDDropTarget::Drop(IDataObject *pDataObject, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect)
269{
270 RT_NOREF(pt);
271 AssertPtrReturn(pDataObject, E_INVALIDARG);
272 AssertPtrReturn(pdwEffect, E_INVALIDARG);
273
274 LogFlowFunc(("mFormatEtc.cfFormat=%RI16 (%s), pDataObject=0x%p, grfKeyState=0x%x, x=%ld, y=%ld\n",
275 mFormatEtc.cfFormat, VBoxDnDDataObject::ClipboardFormatToString(mFormatEtc.cfFormat),
276 pDataObject, grfKeyState, pt.x, pt.y));
277
278 HRESULT hr = S_OK;
279
280 if (mFormatEtc.cfFormat) /* Did we get a supported format yet? */
281 {
282 /* Make sure the data object's data format is still valid. */
283 hr = pDataObject->QueryGetData(&mFormatEtc);
284 AssertMsg(SUCCEEDED(hr),
285 ("Data format changed to invalid between DragEnter() and Drop(), cfFormat=%RI16 (%s), hr=%Rhrc\n",
286 mFormatEtc.cfFormat, VBoxDnDDataObject::ClipboardFormatToString(mFormatEtc.cfFormat), hr));
287 }
288
289 int rc = VINF_SUCCESS;
290
291 if (SUCCEEDED(hr))
292 {
293 STGMEDIUM stgMed;
294 hr = pDataObject->GetData(&mFormatEtc, &stgMed);
295 if (SUCCEEDED(hr))
296 {
297 /*
298 * First stage: Prepare the access to the storage medium.
299 * For now we only support HGLOBAL stuff.
300 */
301 PVOID pvData = NULL; /** @todo Put this in an own union? */
302
303 switch (mFormatEtc.tymed)
304 {
305 case TYMED_HGLOBAL:
306 pvData = GlobalLock(stgMed.hGlobal);
307 if (!pvData)
308 {
309 LogFlowFunc(("Locking HGLOBAL storage failed with %Rrc\n",
310 RTErrConvertFromWin32(GetLastError())));
311 rc = VERR_INVALID_HANDLE;
312 hr = E_INVALIDARG; /* Set special hr for OLE. */
313 }
314 break;
315
316 default:
317 AssertMsgFailed(("Storage medium type %RI32 supported\n",
318 mFormatEtc.tymed));
319 rc = VERR_NOT_SUPPORTED;
320 hr = DV_E_TYMED; /* Set special hr for OLE. */
321 break;
322 }
323
324 if (RT_SUCCESS(rc))
325 {
326 /*
327 * Second stage: Do the actual copying of the data object's data,
328 * based on the storage medium type.
329 */
330 switch (mFormatEtc.cfFormat)
331 {
332 case CF_TEXT:
333 /* Fall through is intentional. */
334 case CF_UNICODETEXT:
335 {
336 AssertPtr(pvData);
337 size_t cbSize = GlobalSize(pvData);
338 LogFlowFunc(("CF_TEXT/CF_UNICODETEXT 0x%p got %zu bytes\n", pvData, cbSize));
339 if (cbSize)
340 {
341 char *pszText = NULL;
342
343 rc = mFormatEtc.cfFormat == CF_TEXT
344 /* ANSI codepage -> UTF-8 */
345 ? RTStrCurrentCPToUtf8(&pszText, (char *)pvData)
346 /* Unicode -> UTF-8 */
347 : RTUtf16ToUtf8((PCRTUTF16)pvData, &pszText);
348
349 if (RT_SUCCESS(rc))
350 {
351 AssertPtr(pszText);
352
353 size_t cbText = strlen(pszText) + 1; /* Include termination. */
354
355 mpvData = RTMemDup((void *)pszText, cbText);
356 mcbData = cbText;
357
358 RTStrFree(pszText);
359 pszText = NULL;
360 }
361 }
362
363 break;
364 }
365
366 case CF_HDROP:
367 {
368 AssertPtr(pvData);
369
370 /* Convert to a string list, separated by \r\n. */
371 DROPFILES *pDropFiles = (DROPFILES *)pvData;
372 AssertPtr(pDropFiles);
373 bool fUnicode = RT_BOOL(pDropFiles->fWide);
374
375 /* Get the offset of the file list. */
376 Assert(pDropFiles->pFiles >= sizeof(DROPFILES));
377 /* Note: This is *not* pDropFiles->pFiles! DragQueryFile only
378 * will work with the plain storage medium pointer! */
379 HDROP hDrop = (HDROP)(pvData);
380
381 /* First, get the file count. */
382 /** @todo Does this work on Windows 2000 / NT4? */
383 char *pszFiles = NULL;
384 uint32_t cchFiles = 0;
385 UINT cFiles = DragQueryFile(hDrop, UINT32_MAX /* iFile */,
386 NULL /* lpszFile */, 0 /* cchFile */);
387 LogFlowFunc(("CF_HDROP got %RU16 file(s)\n", cFiles));
388
389 for (UINT i = 0; i < cFiles; i++)
390 {
391 UINT cch = DragQueryFile(hDrop, i /* File index */,
392 NULL /* Query size first */,
393 0 /* cchFile */);
394 Assert(cch);
395
396 if (RT_FAILURE(rc))
397 break;
398
399 char *pszFile = NULL; /* UTF-8 version. */
400 UINT cchFile = 0;
401 if (fUnicode)
402 {
403 /* Allocate enough space (including terminator). */
404 WCHAR *pwszFile = (WCHAR *)RTMemAlloc((cch + 1) * sizeof(WCHAR));
405 if (pwszFile)
406 {
407 cchFile = DragQueryFileW(hDrop, i /* File index */,
408 pwszFile, cch + 1 /* Include terminator */);
409 AssertMsg(cchFile == cch, ("cchCopied (%RU16) does not match cchFile (%RU16)\n",
410 cchFile, cch));
411 rc = RTUtf16ToUtf8(pwszFile, &pszFile);
412 AssertRC(rc);
413
414 RTMemFree(pwszFile);
415 }
416 else
417 rc = VERR_NO_MEMORY;
418 }
419 else /* ANSI */
420 {
421 /* Allocate enough space (including terminator). */
422 pszFile = (char *)RTMemAlloc((cch + 1) * sizeof(char));
423 if (pszFile)
424 {
425 cchFile = DragQueryFileA(hDrop, i /* File index */,
426 pszFile, cchFile + 1 /* Include terminator */);
427 AssertMsg(cchFile == cch, ("cchCopied (%RU16) does not match cchFile (%RU16)\n",
428 cchFile, cch));
429 }
430 else
431 rc = VERR_NO_MEMORY;
432 }
433
434 if (RT_SUCCESS(rc))
435 {
436 LogFlowFunc(("\tFile: %s (cchFile=%RU32)\n", pszFile, cchFile));
437 rc = RTStrAAppendExN(&pszFiles, 1 /* cPairs */,
438 pszFile, cchFile);
439 if (RT_SUCCESS(rc))
440 cchFiles += cchFile;
441 }
442
443 if (pszFile)
444 RTStrFree(pszFile);
445
446 if (RT_FAILURE(rc))
447 break;
448
449 /* Add separation between filenames.
450 * Note: Also do this for the last element of the list. */
451 rc = RTStrAAppendExN(&pszFiles, 1 /* cPairs */,
452 "\r\n", 2 /* Bytes */);
453 if (RT_SUCCESS(rc))
454 cchFiles += 2; /* Include \r\n */
455 }
456
457 if (RT_SUCCESS(rc))
458 {
459 cchFiles += 1; /* Add string termination. */
460 uint32_t cbFiles = cchFiles * sizeof(char);
461
462 LogFlowFunc(("cFiles=%u, cchFiles=%RU32, cbFiles=%RU32, pszFiles=0x%p\n",
463 cFiles, cchFiles, cbFiles, pszFiles));
464
465 /* Translate the list into URI elements. */
466 DnDURIList lstURI;
467 rc = lstURI.AppendNativePathsFromList(pszFiles, cbFiles,
468 DNDURILIST_FLAGS_ABSOLUTE_PATHS);
469 if (RT_SUCCESS(rc))
470 {
471 RTCString strRoot = lstURI.RootToString();
472 size_t cbRoot = strRoot.length() + 1; /* Include termination */
473
474 mpvData = RTMemAlloc(cbRoot);
475 if (mpvData)
476 {
477 memcpy(mpvData, strRoot.c_str(), cbRoot);
478 mcbData = cbRoot;
479 }
480 else
481 rc = VERR_NO_MEMORY;
482 }
483 }
484
485 LogFlowFunc(("Building CF_HDROP list rc=%Rrc, pszFiles=0x%p, cFiles=%RU16, cchFiles=%RU32\n",
486 rc, pszFiles, cFiles, cchFiles));
487
488 if (pszFiles)
489 RTStrFree(pszFiles);
490 break;
491 }
492
493 default:
494 /* Note: Should not happen due to the checks done in DragEnter(). */
495 AssertMsgFailed(("Format of type %RI16 (%s) not supported\n",
496 mFormatEtc.cfFormat, VBoxDnDDataObject::ClipboardFormatToString(mFormatEtc.cfFormat)));
497 hr = DV_E_CLIPFORMAT; /* Set special hr for OLE. */
498 break;
499 }
500
501 /*
502 * Third stage: Unlock + release access to the storage medium again.
503 */
504 switch (mFormatEtc.tymed)
505 {
506 case TYMED_HGLOBAL:
507 GlobalUnlock(stgMed.hGlobal);
508 break;
509
510 default:
511 AssertMsgFailed(("Really should not happen -- see init stage!\n"));
512 break;
513 }
514 }
515
516 /* Release storage medium again. */
517 ReleaseStgMedium(&stgMed);
518
519 /* Signal waiters. */
520 mDroppedRc = rc;
521 RTSemEventSignal(hEventDrop);
522 }
523 }
524
525 if (RT_SUCCESS(rc))
526 {
527 /* Note: pt is not used since we don't need to differentiate within our
528 * proxy window. */
529 *pdwEffect = VBoxDnDDropTarget::GetDropEffect(grfKeyState, *pdwEffect);
530 }
531 else
532 *pdwEffect = DROPEFFECT_NONE;
533
534 if (mpWndParent)
535 mpWndParent->hide();
536
537 LogFlowFunc(("Returning with hr=%Rhrc (%Rrc), mFormatEtc.cfFormat=%RI16 (%s), *pdwEffect=%RI32\n",
538 hr, rc, mFormatEtc.cfFormat, VBoxDnDDataObject::ClipboardFormatToString(mFormatEtc.cfFormat),
539 *pdwEffect));
540
541 return hr;
542}
543
544/* static */
545DWORD VBoxDnDDropTarget::GetDropEffect(DWORD grfKeyState, DWORD dwAllowedEffects)
546{
547 DWORD dwEffect = DROPEFFECT_NONE;
548
549 if(grfKeyState & MK_CONTROL)
550 dwEffect = dwAllowedEffects & DROPEFFECT_COPY;
551 else if(grfKeyState & MK_SHIFT)
552 dwEffect = dwAllowedEffects & DROPEFFECT_MOVE;
553
554 /* If there still was no drop effect assigned, check for the handed-in
555 * allowed effects and assign one of them.
556 *
557 * Note: A move action has precendence over a copy action! */
558 if (dwEffect == DROPEFFECT_NONE)
559 {
560 if (dwAllowedEffects & DROPEFFECT_COPY)
561 dwEffect = DROPEFFECT_COPY;
562 if (dwAllowedEffects & DROPEFFECT_MOVE)
563 dwEffect = DROPEFFECT_MOVE;
564 }
565
566#ifdef DEBUG_andy
567 LogFlowFunc(("grfKeyState=0x%x, dwAllowedEffects=0x%x, dwEffect=0x%x\n",
568 grfKeyState, dwAllowedEffects, dwEffect));
569#endif
570 return dwEffect;
571}
572
573void VBoxDnDDropTarget::reset(void)
574{
575 LogFlowFuncEnter();
576
577 if (mpvData)
578 {
579 RTMemFree(mpvData);
580 mpvData = NULL;
581 }
582
583 mcbData = 0;
584
585 RT_ZERO(mFormatEtc);
586 mstrFormats = "";
587}
588
589RTCString VBoxDnDDropTarget::Formats(void) const
590{
591 return mstrFormats;
592}
593
594int VBoxDnDDropTarget::WaitForDrop(RTMSINTERVAL msTimeout)
595{
596 LogFlowFunc(("msTimeout=%RU32\n", msTimeout));
597
598 int rc = RTSemEventWait(hEventDrop, msTimeout);
599 if (RT_SUCCESS(rc))
600 rc = mDroppedRc;
601
602 LogFlowFuncLeaveRC(rc);
603 return rc;
604}
605
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette