VirtualBox

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

Last change on this file since 50752 was 50734, checked in by vboxsync, 11 years ago

DnD: Bugfixes.

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