VirtualBox

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

Last change on this file since 85569 was 85520, checked in by vboxsync, 4 years ago

DnD/VBoxTray: More fixes needed for new DnDTransferList and DnDTransferObject implementations.

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