VirtualBox

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

Last change on this file since 50509 was 50508, checked in by vboxsync, 11 years ago

DnD: Update.

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

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette