VirtualBox

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

Last change on this file since 50433 was 50399, checked in by vboxsync, 11 years ago

DnD/VBoxTray: Only works on NT4 SP3+, bugfixes.

  • 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 50399 2014-02-10 16:21:09Z 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 /* Add separation between filenames. */
360 if (i > 0)
361 {
362 rc = RTStrAAppendExN(&pszFiles, 1 /* cPairs */,
363 "\r\n", 2 /* Bytes */);
364 if (RT_SUCCESS(rc))
365 cchFiles += 2; /* Include \r\n */
366 }
367
368 if (RT_FAILURE(rc))
369 break;
370
371 char *pszFile = NULL; /* UTF-8 version. */
372 UINT cchFile = 0;
373 if (fUnicode)
374 {
375 /* Allocate enough space (including terminator). */
376 WCHAR *pwszFile = (WCHAR *)RTMemAlloc((cch + 1) * sizeof(WCHAR));
377 if (pwszFile)
378 {
379 cchFile = DragQueryFileW(hDrop, i /* File index */,
380 pwszFile, cch + 1 /* Include terminator */);
381 AssertMsg(cchFile == cch, ("cchCopied (%RU16) does not match cchFile (%RU16)\n",
382 cchFile, cch));
383 int rc2 = RTUtf16ToUtf8(pwszFile, &pszFile);
384 AssertRC(rc2);
385 }
386 else
387 rc = VERR_NO_MEMORY;
388 }
389 else /* ANSI */
390 {
391 /* Allocate enough space (including terminator). */
392 pszFile = (char *)RTMemAlloc((cch + 1) * sizeof(char));
393 if (pszFile)
394 {
395 cchFile = DragQueryFileA(hDrop, i /* File index */,
396 pszFile, cchFile + 1 /* Include terminator */);
397 AssertMsg(cchFile == cch, ("cchCopied (%RU16) does not match cchFile (%RU16)\n",
398 cchFile, cch));
399 }
400 else
401 rc = VERR_NO_MEMORY;
402 }
403
404 if (RT_SUCCESS(rc))
405 {
406 LogFlowFunc(("\tFile: %s (cchFile=%RU32)\n",
407 pszFile, cchFile));
408
409 rc = RTStrAAppendExN(&pszFiles, 1 /* cPairs */,
410 pszFile, cchFile);
411 if (RT_SUCCESS(rc))
412 cchFiles += cchFile;
413 }
414
415 if (pszFile)
416 RTMemFree(pszFile);
417
418 if (RT_FAILURE(rc))
419 break;
420 }
421
422 if (RT_SUCCESS(rc))
423 {
424 uint32_t cbSize = cchFiles * sizeof(char);
425 Assert(cbSize);
426
427 mpvData = RTMemDup(pszFiles, cbSize);
428 if (mpvData)
429 {
430 mcbData = cbSize;
431 }
432 else
433 rc = VERR_NO_MEMORY;
434 }
435
436 LogFlowFunc(("Building CF_HDROP list rc=%Rrc, pszFiles=0x%p, cFiles=%RU16, cchFiles=%RU32\n",
437 rc, pszFiles, cFiles, cchFiles));
438
439 if (pszFiles)
440 RTStrFree(pszFiles);
441 break;
442 }
443
444 default:
445 /* Note: Should not happen due to the checks done in DragEnter(). */
446 AssertMsgFailed(("Format of type %RI16 (%s) not supported\n",
447 mFormatEtc.cfFormat,
448 VBoxDnDDataObject::ClipboardFormatToString(mFormatEtc.cfFormat)));
449 hr = DV_E_CLIPFORMAT; /* Set special hr for OLE. */
450 break;
451 }
452
453 /*
454 * Third stage: Release access to the storage medium again.
455 */
456 switch (mFormatEtc.tymed)
457 {
458 case TYMED_HGLOBAL:
459 GlobalUnlock(stgMed.hGlobal);
460 break;
461
462 default:
463 AssertMsgFailed(("Really should not happen -- see init stage!\n"));
464 break;
465 }
466 }
467
468 /* Signal waiters. */
469 mDroppedRc = rc;
470 RTSemEventSignal(hEventDrop);
471
472 /* Release storage medium again. */
473 ReleaseStgMedium(&stgMed);
474 }
475 }
476
477 if (RT_SUCCESS(rc))
478 {
479 /* Note: pt is not used since we don't need to differentiate within our
480 * proxy window. */
481 *pdwEffect = VBoxDnDDropTarget::GetDropEffect(grfKeyState, *pdwEffect);
482 }
483 else
484 *pdwEffect = DROPEFFECT_NONE;
485
486 if (mpWndParent)
487 mpWndParent->hide();
488
489 LogFlowFunc(("Returning with hr=%Rhrc (%Rrc), mFormatEtc.cfFormat=%RI16 (%s), *pdwEffect=%RI32\n",
490 hr, rc, mFormatEtc.cfFormat, VBoxDnDDataObject::ClipboardFormatToString(mFormatEtc.cfFormat),
491 *pdwEffect));
492
493 return hr;
494}
495
496/* static */
497DWORD VBoxDnDDropTarget::GetDropEffect(DWORD grfKeyState, DWORD dwAllowedEffects)
498{
499 DWORD dwEffect = DROPEFFECT_NONE;
500
501 if(grfKeyState & MK_CONTROL)
502 dwEffect = dwAllowedEffects & DROPEFFECT_COPY;
503 else if(grfKeyState & MK_SHIFT)
504 dwEffect = dwAllowedEffects & DROPEFFECT_MOVE;
505
506 /* If there still was no drop effect assigned, check for the handed-in
507 * allowed effects and assign one of them.
508 *
509 * Note: A move action has precendence over a copy action! */
510 if (dwEffect == DROPEFFECT_NONE)
511 {
512 if (dwAllowedEffects & DROPEFFECT_COPY)
513 dwEffect = DROPEFFECT_COPY;
514 if (dwAllowedEffects & DROPEFFECT_MOVE)
515 dwEffect = DROPEFFECT_MOVE;
516 }
517
518#ifdef DEBUG_andy
519 LogFlowFunc(("grfKeyState=0x%x, dwAllowedEffects=0x%x, dwEffect=0x%x\n",
520 grfKeyState, dwAllowedEffects, dwEffect));
521#endif
522 return dwEffect;
523}
524
525void VBoxDnDDropTarget::reset(void)
526{
527 LogFlowFuncEnter();
528
529 if (mpvData)
530 {
531 RTMemFree(mpvData);
532 mpvData = NULL;
533 }
534
535 mcbData = 0;
536 RT_ZERO(mFormatEtc);
537 mFormats = "";
538}
539
540RTCString VBoxDnDDropTarget::Formats(void)
541{
542 return mFormats;
543}
544
545int VBoxDnDDropTarget::WaitForDrop(RTMSINTERVAL msTimeout)
546{
547 LogFlowFunc(("msTimeout=%RU32\n", msTimeout));
548
549 int rc = RTSemEventWait(hEventDrop, msTimeout);
550 if (RT_SUCCESS(rc))
551 rc = mDroppedRc;
552
553 LogFlowFuncLeaveRC(rc);
554 return rc;
555}
556
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