VirtualBox

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

Last change on this file since 93668 was 93115, checked in by vboxsync, 3 years ago

scm --update-copyright-year

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