VirtualBox

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

Last change on this file since 63103 was 62679, checked in by vboxsync, 8 years ago

Use the iprt/win/windows.h wrapper for Windows.h

  • 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 62679 2016-07-29 12:52:10Z 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 <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));
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,
132 POINTL pt, DWORD *pdwEffect)
133{
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 AssertPtrReturn(pdwEffect, E_INVALIDARG);
232
233#ifdef DEBUG_andy
234 LogFlowFunc(("cfFormat=%RI16, grfKeyState=0x%x, x=%ld, y=%ld\n",
235 mFormatEtc.cfFormat, grfKeyState, pt.x, pt.y));
236#endif
237
238 if (mFormatEtc.cfFormat)
239 {
240 /* Note: pt is not used since we don't need to differentiate within our
241 * proxy window. */
242 *pdwEffect = VBoxDnDDropTarget::GetDropEffect(grfKeyState, *pdwEffect);
243 }
244 else
245 {
246 *pdwEffect = DROPEFFECT_NONE;
247 }
248
249#ifdef DEBUG_andy
250 LogFlowFunc(("Returning *pdwEffect=%ld\n", *pdwEffect));
251#endif
252 return S_OK;
253}
254
255STDMETHODIMP VBoxDnDDropTarget::DragLeave(void)
256{
257#ifdef DEBUG_andy
258 LogFlowFunc(("cfFormat=%RI16\n", mFormatEtc.cfFormat));
259#endif
260
261 if (mpWndParent)
262 mpWndParent->hide();
263
264 return S_OK;
265}
266
267STDMETHODIMP VBoxDnDDropTarget::Drop(IDataObject *pDataObject,
268 DWORD grfKeyState, POINTL pt, DWORD *pdwEffect)
269{
270 AssertPtrReturn(pDataObject, E_INVALIDARG);
271 AssertPtrReturn(pdwEffect, E_INVALIDARG);
272
273 LogFlowFunc(("mFormatEtc.cfFormat=%RI16 (%s), pDataObject=0x%p, grfKeyState=0x%x, x=%ld, y=%ld\n",
274 mFormatEtc.cfFormat, VBoxDnDDataObject::ClipboardFormatToString(mFormatEtc.cfFormat),
275 pDataObject, grfKeyState, pt.x, pt.y));
276
277 HRESULT hr = S_OK;
278
279 if (mFormatEtc.cfFormat) /* Did we get a supported format yet? */
280 {
281 /* Make sure the data object's data format is still valid. */
282 hr = pDataObject->QueryGetData(&mFormatEtc);
283 AssertMsg(SUCCEEDED(hr),
284 ("Data format changed to invalid between DragEnter() and Drop(), cfFormat=%RI16 (%s), hr=%Rhrc\n",
285 mFormatEtc.cfFormat, VBoxDnDDataObject::ClipboardFormatToString(mFormatEtc.cfFormat), hr));
286 }
287
288 int rc = VINF_SUCCESS;
289
290 if (SUCCEEDED(hr))
291 {
292 STGMEDIUM stgMed;
293 hr = pDataObject->GetData(&mFormatEtc, &stgMed);
294 if (SUCCEEDED(hr))
295 {
296 /*
297 * First stage: Prepare the access to the storage medium.
298 * For now we only support HGLOBAL stuff.
299 */
300 PVOID pvData = NULL; /** @todo Put this in an own union? */
301
302 switch (mFormatEtc.tymed)
303 {
304 case TYMED_HGLOBAL:
305 pvData = GlobalLock(stgMed.hGlobal);
306 if (!pvData)
307 {
308 LogFlowFunc(("Locking HGLOBAL storage failed with %Rrc\n",
309 RTErrConvertFromWin32(GetLastError())));
310 rc = VERR_INVALID_HANDLE;
311 hr = E_INVALIDARG; /* Set special hr for OLE. */
312 }
313 break;
314
315 default:
316 AssertMsgFailed(("Storage medium type %RI32 supported\n",
317 mFormatEtc.tymed));
318 rc = VERR_NOT_SUPPORTED;
319 hr = DV_E_TYMED; /* Set special hr for OLE. */
320 break;
321 }
322
323 if (RT_SUCCESS(rc))
324 {
325 /*
326 * Second stage: Do the actual copying of the data object's data,
327 * based on the storage medium type.
328 */
329 switch (mFormatEtc.cfFormat)
330 {
331 case CF_TEXT:
332 /* Fall through is intentional. */
333 case CF_UNICODETEXT:
334 {
335 AssertPtr(pvData);
336 size_t cbSize = GlobalSize(pvData);
337 LogFlowFunc(("CF_TEXT/CF_UNICODETEXT 0x%p got %zu bytes\n", pvData, cbSize));
338 if (cbSize)
339 {
340 char *pszText = NULL;
341
342 rc = mFormatEtc.cfFormat == CF_TEXT
343 /* ANSI codepage -> UTF-8 */
344 ? RTStrCurrentCPToUtf8(&pszText, (char *)pvData)
345 /* Unicode -> UTF-8 */
346 : RTUtf16ToUtf8((PCRTUTF16)pvData, &pszText);
347
348 if (RT_SUCCESS(rc))
349 {
350 AssertPtr(pszText);
351
352 size_t cbText = strlen(pszText) + 1; /* Include termination. */
353
354 mpvData = RTMemDup((void *)pszText, cbText);
355 mcbData = cbText;
356
357 RTStrFree(pszText);
358 pszText = NULL;
359 }
360 }
361
362 break;
363 }
364
365 case CF_HDROP:
366 {
367 AssertPtr(pvData);
368
369 /* Convert to a string list, separated by \r\n. */
370 DROPFILES *pDropFiles = (DROPFILES *)pvData;
371 AssertPtr(pDropFiles);
372 bool fUnicode = RT_BOOL(pDropFiles->fWide);
373
374 /* Get the offset of the file list. */
375 Assert(pDropFiles->pFiles >= sizeof(DROPFILES));
376 /* Note: This is *not* pDropFiles->pFiles! DragQueryFile only
377 * will work with the plain storage medium pointer! */
378 HDROP hDrop = (HDROP)(pvData);
379
380 /* First, get the file count. */
381 /** @todo Does this work on Windows 2000 / NT4? */
382 char *pszFiles = NULL;
383 uint32_t cchFiles = 0;
384 UINT cFiles = DragQueryFile(hDrop, UINT32_MAX /* iFile */,
385 NULL /* lpszFile */, 0 /* cchFile */);
386 LogFlowFunc(("CF_HDROP got %RU16 file(s)\n", cFiles));
387
388 for (UINT i = 0; i < cFiles; i++)
389 {
390 UINT cch = DragQueryFile(hDrop, i /* File index */,
391 NULL /* Query size first */,
392 0 /* cchFile */);
393 Assert(cch);
394
395 if (RT_FAILURE(rc))
396 break;
397
398 char *pszFile = NULL; /* UTF-8 version. */
399 UINT cchFile = 0;
400 if (fUnicode)
401 {
402 /* Allocate enough space (including terminator). */
403 WCHAR *pwszFile = (WCHAR *)RTMemAlloc((cch + 1) * sizeof(WCHAR));
404 if (pwszFile)
405 {
406 cchFile = DragQueryFileW(hDrop, i /* File index */,
407 pwszFile, cch + 1 /* Include terminator */);
408 AssertMsg(cchFile == cch, ("cchCopied (%RU16) does not match cchFile (%RU16)\n",
409 cchFile, cch));
410 rc = RTUtf16ToUtf8(pwszFile, &pszFile);
411 AssertRC(rc);
412
413 RTMemFree(pwszFile);
414 }
415 else
416 rc = VERR_NO_MEMORY;
417 }
418 else /* ANSI */
419 {
420 /* Allocate enough space (including terminator). */
421 pszFile = (char *)RTMemAlloc((cch + 1) * sizeof(char));
422 if (pszFile)
423 {
424 cchFile = DragQueryFileA(hDrop, i /* File index */,
425 pszFile, cchFile + 1 /* Include terminator */);
426 AssertMsg(cchFile == cch, ("cchCopied (%RU16) does not match cchFile (%RU16)\n",
427 cchFile, cch));
428 }
429 else
430 rc = VERR_NO_MEMORY;
431 }
432
433 if (RT_SUCCESS(rc))
434 {
435 LogFlowFunc(("\tFile: %s (cchFile=%RU32)\n", pszFile, cchFile));
436 rc = RTStrAAppendExN(&pszFiles, 1 /* cPairs */,
437 pszFile, cchFile);
438 if (RT_SUCCESS(rc))
439 cchFiles += cchFile;
440 }
441
442 if (pszFile)
443 RTStrFree(pszFile);
444
445 if (RT_FAILURE(rc))
446 break;
447
448 /* Add separation between filenames.
449 * Note: Also do this for the last element of the list. */
450 rc = RTStrAAppendExN(&pszFiles, 1 /* cPairs */,
451 "\r\n", 2 /* Bytes */);
452 if (RT_SUCCESS(rc))
453 cchFiles += 2; /* Include \r\n */
454 }
455
456 if (RT_SUCCESS(rc))
457 {
458 cchFiles += 1; /* Add string termination. */
459 uint32_t cbFiles = cchFiles * sizeof(char);
460
461 LogFlowFunc(("cFiles=%u, cchFiles=%RU32, cbFiles=%RU32, pszFiles=0x%p\n",
462 cFiles, cchFiles, cbFiles, pszFiles));
463
464 /* Translate the list into URI elements. */
465 DnDURIList lstURI;
466 rc = lstURI.AppendNativePathsFromList(pszFiles, cbFiles,
467 DNDURILIST_FLAGS_ABSOLUTE_PATHS);
468 if (RT_SUCCESS(rc))
469 {
470 RTCString strRoot = lstURI.RootToString();
471 size_t cbRoot = strRoot.length() + 1; /* Include termination */
472
473 mpvData = RTMemAlloc(cbRoot);
474 if (mpvData)
475 {
476 memcpy(mpvData, strRoot.c_str(), cbRoot);
477 mcbData = cbRoot;
478 }
479 else
480 rc = VERR_NO_MEMORY;
481 }
482 }
483
484 LogFlowFunc(("Building CF_HDROP list rc=%Rrc, pszFiles=0x%p, cFiles=%RU16, cchFiles=%RU32\n",
485 rc, pszFiles, cFiles, cchFiles));
486
487 if (pszFiles)
488 RTStrFree(pszFiles);
489 break;
490 }
491
492 default:
493 /* Note: Should not happen due to the checks done in DragEnter(). */
494 AssertMsgFailed(("Format of type %RI16 (%s) not supported\n",
495 mFormatEtc.cfFormat, VBoxDnDDataObject::ClipboardFormatToString(mFormatEtc.cfFormat)));
496 hr = DV_E_CLIPFORMAT; /* Set special hr for OLE. */
497 break;
498 }
499
500 /*
501 * Third stage: Unlock + release access to the storage medium again.
502 */
503 switch (mFormatEtc.tymed)
504 {
505 case TYMED_HGLOBAL:
506 GlobalUnlock(stgMed.hGlobal);
507 break;
508
509 default:
510 AssertMsgFailed(("Really should not happen -- see init stage!\n"));
511 break;
512 }
513 }
514
515 /* Release storage medium again. */
516 ReleaseStgMedium(&stgMed);
517
518 /* Signal waiters. */
519 mDroppedRc = rc;
520 RTSemEventSignal(hEventDrop);
521 }
522 }
523
524 if (RT_SUCCESS(rc))
525 {
526 /* Note: pt is not used since we don't need to differentiate within our
527 * proxy window. */
528 *pdwEffect = VBoxDnDDropTarget::GetDropEffect(grfKeyState, *pdwEffect);
529 }
530 else
531 *pdwEffect = DROPEFFECT_NONE;
532
533 if (mpWndParent)
534 mpWndParent->hide();
535
536 LogFlowFunc(("Returning with hr=%Rhrc (%Rrc), mFormatEtc.cfFormat=%RI16 (%s), *pdwEffect=%RI32\n",
537 hr, rc, mFormatEtc.cfFormat, VBoxDnDDataObject::ClipboardFormatToString(mFormatEtc.cfFormat),
538 *pdwEffect));
539
540 return hr;
541}
542
543/* static */
544DWORD VBoxDnDDropTarget::GetDropEffect(DWORD grfKeyState, DWORD dwAllowedEffects)
545{
546 DWORD dwEffect = DROPEFFECT_NONE;
547
548 if(grfKeyState & MK_CONTROL)
549 dwEffect = dwAllowedEffects & DROPEFFECT_COPY;
550 else if(grfKeyState & MK_SHIFT)
551 dwEffect = dwAllowedEffects & DROPEFFECT_MOVE;
552
553 /* If there still was no drop effect assigned, check for the handed-in
554 * allowed effects and assign one of them.
555 *
556 * Note: A move action has precendence over a copy action! */
557 if (dwEffect == DROPEFFECT_NONE)
558 {
559 if (dwAllowedEffects & DROPEFFECT_COPY)
560 dwEffect = DROPEFFECT_COPY;
561 if (dwAllowedEffects & DROPEFFECT_MOVE)
562 dwEffect = DROPEFFECT_MOVE;
563 }
564
565#ifdef DEBUG_andy
566 LogFlowFunc(("grfKeyState=0x%x, dwAllowedEffects=0x%x, dwEffect=0x%x\n",
567 grfKeyState, dwAllowedEffects, dwEffect));
568#endif
569 return dwEffect;
570}
571
572void VBoxDnDDropTarget::reset(void)
573{
574 LogFlowFuncEnter();
575
576 if (mpvData)
577 {
578 RTMemFree(mpvData);
579 mpvData = NULL;
580 }
581
582 mcbData = 0;
583
584 RT_ZERO(mFormatEtc);
585 mstrFormats = "";
586}
587
588RTCString VBoxDnDDropTarget::Formats(void) const
589{
590 return mstrFormats;
591}
592
593int VBoxDnDDropTarget::WaitForDrop(RTMSINTERVAL msTimeout)
594{
595 LogFlowFunc(("msTimeout=%RU32\n", msTimeout));
596
597 int rc = RTSemEventWait(hEventDrop, msTimeout);
598 if (RT_SUCCESS(rc))
599 rc = mDroppedRc;
600
601 LogFlowFuncLeaveRC(rc);
602 return rc;
603}
604
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