VirtualBox

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

Last change on this file since 51556 was 51469, checked in by vboxsync, 11 years ago

VBoxTray: Logging; ripped out all custom logging.

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