VirtualBox

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

Last change on this file since 78440 was 76553, checked in by vboxsync, 6 years ago

scm --update-copyright-year

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