VirtualBox

source: vbox/trunk/src/VBox/GuestHost/SharedClipboard/ClipboardDataObjectImpl-win.cpp@ 100220

Last change on this file since 100220 was 100205, checked in by vboxsync, 20 months ago

Shared Clipboard: Unified root list entry code to also use the generic list entry code, a lot of updates for the cross OS transfer handling code, more updates for HTTP server transfer handling. This also changed the handling of how that transfers are being initiated, as we needed to have this for X11: Before, transfers were initiated as soon as on side announced the URI list format -- now we postpone initiating the transfer until the receiving side requests the data as URI list [build fix]. ​bugref:9437

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 38.5 KB
Line 
1/* $Id: ClipboardDataObjectImpl-win.cpp 100205 2023-06-19 10:25:09Z vboxsync $ */
2/** @file
3 * ClipboardDataObjectImpl-win.cpp - Shared Clipboard IDataObject implementation.
4 */
5
6/*
7 * Copyright (C) 2019-2023 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28
29/*********************************************************************************************************************************
30* Header Files *
31*********************************************************************************************************************************/
32#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD
33#include <VBox/GuestHost/SharedClipboard-win.h>
34#include <VBox/GuestHost/SharedClipboard-transfers.h>
35
36#include <iprt/win/windows.h>
37#include <iprt/win/shlobj.h>
38#include <iprt/win/shlwapi.h>
39
40#include <iprt/thread.h> // REMOVE
41
42#include <iprt/asm.h>
43#include <iprt/errcore.h>
44#include <iprt/path.h>
45#include <iprt/semaphore.h>
46#include <iprt/uri.h>
47#include <iprt/utf16.h>
48
49#include <iprt/errcore.h>
50#include <VBox/log.h>
51
52/** @todo Also handle Unicode entries.
53 * !!! WARNING: Buggy, doesn't work yet (some memory corruption / garbage in the file name descriptions) !!! */
54//#define VBOX_CLIPBOARD_WITH_UNICODE_SUPPORT 1
55
56SharedClipboardWinDataObject::SharedClipboardWinDataObject(void)
57 : m_pCtx(NULL)
58 , m_enmStatus(Uninitialized)
59 , m_lRefCount(0)
60 , m_cFormats(0)
61 , m_pTransfer(NULL)
62 , m_pStream(NULL)
63 , m_uObjIdx(0)
64 , m_fThreadRunning(false)
65 , m_EventListComplete(NIL_RTSEMEVENT)
66 , m_EventStatusChanged(NIL_RTSEMEVENT)
67{
68}
69
70SharedClipboardWinDataObject::~SharedClipboardWinDataObject(void)
71{
72 Destroy();
73
74 LogFlowFunc(("mRefCount=%RI32\n", m_lRefCount));
75}
76
77/**
78 * Initializes a data object instance.
79 *
80 * @returns VBox status code.
81 * @param pFormatEtc FormatETC to use.
82 * @param pStgMed Storage medium to use.
83 * @param cFormats Number of formats in \a pFormatEtc and \a pStgMed.
84 */
85int SharedClipboardWinDataObject::Init(PSHCLCONTEXT pCtx,
86 LPFORMATETC pFormatEtc /* = NULL */, LPSTGMEDIUM pStgMed /* = NULL */,
87 ULONG cFormats /* = 0 */)
88{
89 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
90 AssertReturn(cFormats == 0 || (RT_VALID_PTR(pFormatEtc) && RT_VALID_PTR(pStgMed)), VERR_INVALID_POINTER);
91
92 int rc = VINF_SUCCESS;
93
94 m_pCtx = pCtx; /* Save opaque context. */
95
96 ULONG cFixedFormats = 3; /* CFSTR_FILEDESCRIPTORA + CFSTR_FILECONTENTS + CFSTR_PERFORMEDDROPEFFECT */
97#ifdef VBOX_CLIPBOARD_WITH_UNICODE_SUPPORT
98 cFixedFormats++; /* CFSTR_FILEDESCRIPTORW */
99#endif
100 const ULONG cAllFormats = cFormats + cFixedFormats;
101
102 m_pFormatEtc = new FORMATETC[cAllFormats];
103 AssertPtrReturn(m_pFormatEtc, VERR_NO_MEMORY);
104 RT_BZERO(m_pFormatEtc, sizeof(FORMATETC) * cAllFormats);
105 m_pStgMedium = new STGMEDIUM[cAllFormats];
106 AssertPtrReturn(m_pStgMedium, VERR_NO_MEMORY);
107 RT_BZERO(m_pStgMedium, sizeof(STGMEDIUM) * cAllFormats);
108
109 /** @todo Do we need CFSTR_FILENAME / CFSTR_SHELLIDLIST here? */
110
111 /*
112 * Register fixed formats.
113 */
114 unsigned uIdx = 0;
115
116 LogFlowFunc(("Registering CFSTR_FILEDESCRIPTORA ...\n"));
117 m_cfFileDescriptorA = RegisterClipboardFormat(CFSTR_FILEDESCRIPTORA);
118 registerFormat(&m_pFormatEtc[uIdx++], m_cfFileDescriptorA);
119#ifdef VBOX_CLIPBOARD_WITH_UNICODE_SUPPORT
120 LogFlowFunc(("Registering CFSTR_FILEDESCRIPTORW ...\n"));
121 m_cfFileDescriptorW = RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW);
122 registerFormat(&m_pFormatEtc[uIdx++], m_cfFileDescriptorW);
123#endif
124
125 /* IStream interface, implemented in ClipboardStreamImpl-win.cpp. */
126 LogFlowFunc(("Registering CFSTR_FILECONTENTS ...\n"));
127 m_cfFileContents = RegisterClipboardFormat(CFSTR_FILECONTENTS);
128 registerFormat(&m_pFormatEtc[uIdx++], m_cfFileContents, TYMED_ISTREAM, 0 /* lIndex */);
129
130 /* We want to know from the target what the outcome of the operation was to react accordingly (e.g. abort a transfer). */
131 LogFlowFunc(("Registering CFSTR_PERFORMEDDROPEFFECT ...\n"));
132 m_cfPerformedDropEffect = RegisterClipboardFormat(CFSTR_PERFORMEDDROPEFFECT);
133 registerFormat(&m_pFormatEtc[uIdx++], m_cfPerformedDropEffect, TYMED_HGLOBAL, -1 /* lIndex */, DVASPECT_CONTENT);
134
135 /*
136 * Registration of dynamic formats needed?
137 */
138 LogFlowFunc(("%RU32 dynamic formats\n", cFormats));
139 if (cFormats)
140 {
141 for (ULONG i = 0; i < cFormats; i++)
142 {
143 LogFlowFunc(("Format %RU32: cfFormat=%RI16, tyMed=%RU32, dwAspect=%RU32\n",
144 i, pFormatEtc[i].cfFormat, pFormatEtc[i].tymed, pFormatEtc[i].dwAspect));
145 m_pFormatEtc[cFixedFormats + i] = pFormatEtc[i];
146 m_pStgMedium[cFixedFormats + i] = pStgMed[i];
147 }
148 }
149
150 if (RT_SUCCESS(rc))
151 {
152 m_cFormats = cAllFormats;
153 m_enmStatus = Initialized;
154
155 rc = RTCritSectInit(&m_CritSect);
156 if (RT_SUCCESS(rc))
157 {
158 rc = RTSemEventCreate(&m_EventListComplete);
159 if (RT_SUCCESS(rc))
160 rc = RTSemEventCreate(&m_EventStatusChanged);
161 }
162 }
163
164 LogFlowFunc(("cAllFormats=%RU32, rc=%Rrc\n", cAllFormats, rc));
165 return rc;
166}
167
168/**
169 * Destroys a data object instance.
170 */
171void SharedClipboardWinDataObject::Destroy(void)
172{
173 LogFlowFuncEnter();
174
175 AssertReturnVoid(m_lRefCount == 0);
176
177 int rc = RTCritSectDelete(&m_CritSect);
178 AssertRC(rc);
179
180 if (m_EventListComplete != NIL_RTSEMEVENT)
181 {
182 rc = RTSemEventDestroy(m_EventListComplete);
183 AssertRC(rc);
184 m_EventListComplete = NIL_RTSEMEVENT;
185 }
186
187 if (m_EventStatusChanged != NIL_RTSEMEVENT)
188 {
189 rc = RTSemEventDestroy(m_EventStatusChanged);
190 AssertRC(rc);
191 m_EventStatusChanged = NIL_RTSEMEVENT;
192 }
193
194 if (m_pStream)
195 {
196 m_pStream->Release();
197 m_pStream = NULL;
198 }
199
200 if (m_pFormatEtc)
201 {
202 delete[] m_pFormatEtc;
203 m_pFormatEtc = NULL;
204 }
205
206 if (m_pStgMedium)
207 {
208 delete[] m_pStgMedium;
209 m_pStgMedium = NULL;
210 }
211
212 if (m_pTransfer)
213 ShClTransferRelease(m_pTransfer);
214
215 FsObjEntryList::const_iterator itRoot = m_lstEntries.cbegin();
216 while (itRoot != m_lstEntries.end())
217 {
218 RTStrFree(itRoot->pszPath);
219 ++itRoot;
220 }
221 m_lstEntries.clear();
222
223 m_enmStatus = Uninitialized;
224 m_fThreadRunning = false;
225 m_pTransfer = NULL;
226}
227
228/**
229 * Sets the callbacks for this object.
230 *
231 * @param pCallbacks Pointer to callbacks table to use.
232 */
233void SharedClipboardWinDataObject::SetCallbacks(PSHCLCALLBACKS pCallbacks)
234{
235 AssertPtrReturnVoid(pCallbacks);
236
237 RT_ZERO(m_Callbacks);
238
239 m_Callbacks.pfnOnRequestDataFromSource = pCallbacks->pfnOnRequestDataFromSource;
240}
241
242
243/*********************************************************************************************************************************
244 * IUnknown methods.
245 ********************************************************************************************************************************/
246
247STDMETHODIMP_(ULONG) SharedClipboardWinDataObject::AddRef(void)
248{
249 LONG lCount = InterlockedIncrement(&m_lRefCount);
250 LogFlowFunc(("lCount=%RI32\n", lCount));
251 return lCount;
252}
253
254STDMETHODIMP_(ULONG) SharedClipboardWinDataObject::Release(void)
255{
256 LONG lCount = InterlockedDecrement(&m_lRefCount);
257 LogFlowFunc(("lCount=%RI32\n", m_lRefCount));
258 if (lCount == 0)
259 {
260 delete this;
261 return 0;
262 }
263
264 return lCount;
265}
266
267STDMETHODIMP SharedClipboardWinDataObject::QueryInterface(REFIID iid, void **ppvObject)
268{
269 AssertPtrReturn(ppvObject, E_INVALIDARG);
270
271 if ( iid == IID_IDataObject
272 || iid == IID_IUnknown)
273 {
274 AddRef();
275 *ppvObject = this;
276 return S_OK;
277 }
278
279 *ppvObject = 0;
280 return E_NOINTERFACE;
281}
282
283/**
284 * Copies a chunk of data into a HGLOBAL object.
285 *
286 * @returns VBox status code.
287 * @param pvData Data to copy.
288 * @param cbData Size (in bytes) to copy.
289 * @param fFlags GlobalAlloc flags, used for allocating the HGLOBAL block.
290 * @param phGlobal Where to store the allocated HGLOBAL object.
291 */
292int SharedClipboardWinDataObject::copyToHGlobal(const void *pvData, size_t cbData, UINT fFlags, HGLOBAL *phGlobal)
293{
294 AssertPtrReturn(phGlobal, VERR_INVALID_POINTER);
295
296 HGLOBAL hGlobal = GlobalAlloc(fFlags, cbData);
297 if (!hGlobal)
298 return VERR_NO_MEMORY;
299
300 void *pvAlloc = GlobalLock(hGlobal);
301 if (pvAlloc)
302 {
303 CopyMemory(pvAlloc, pvData, cbData);
304 GlobalUnlock(hGlobal);
305
306 *phGlobal = hGlobal;
307
308 return VINF_SUCCESS;
309 }
310
311 GlobalFree(hGlobal);
312 return VERR_ACCESS_DENIED;
313}
314
315/**
316 * Reads (handles) a specific directory reursively and inserts its entry into the
317 * objects's entry list.
318 *
319 * @returns VBox status code.
320 * @param pTransfer Shared Clipboard transfer object to handle.
321 * @param strDir Directory path to handle.
322 */
323int SharedClipboardWinDataObject::readDir(PSHCLTRANSFER pTransfer, const Utf8Str &strDir)
324{
325 LogFlowFunc(("strDir=%s\n", strDir.c_str()));
326
327 SHCLLISTOPENPARMS openParmsList;
328 int rc = ShClTransferListOpenParmsInit(&openParmsList);
329 if (RT_SUCCESS(rc))
330 {
331 rc = RTStrCopy(openParmsList.pszPath, openParmsList.cbPath, strDir.c_str());
332 if (RT_SUCCESS(rc))
333 {
334 SHCLLISTHANDLE hList;
335 rc = ShClTransferListOpen(pTransfer, &openParmsList, &hList);
336 if (RT_SUCCESS(rc))
337 {
338 LogFlowFunc(("strDir=%s -> hList=%RU64\n", strDir.c_str(), hList));
339
340 SHCLLISTHDR hdrList;
341 rc = ShClTransferListGetHeader(pTransfer, hList, &hdrList);
342 if (RT_SUCCESS(rc))
343 {
344 LogFlowFunc(("cTotalObjects=%RU64, cbTotalSize=%RU64\n\n",
345 hdrList.cEntries, hdrList.cbTotalSize));
346
347 for (uint64_t o = 0; o < hdrList.cEntries; o++)
348 {
349 SHCLLISTENTRY entryList;
350 rc = ShClTransferListEntryInit(&entryList);
351 if (RT_SUCCESS(rc))
352 {
353 rc = ShClTransferListRead(pTransfer, hList, &entryList);
354 if (RT_SUCCESS(rc))
355 {
356 if (ShClTransferListEntryIsValid(&entryList))
357 {
358 PSHCLFSOBJINFO pFsObjInfo = (PSHCLFSOBJINFO)entryList.pvInfo;
359 Assert(entryList.cbInfo == sizeof(SHCLFSOBJINFO));
360
361 Utf8Str strPath = strDir + Utf8Str("\\") + Utf8Str(entryList.pszName);
362
363 LogFlowFunc(("\t%s (%RU64 bytes) -> %s\n",
364 entryList.pszName, pFsObjInfo->cbObject, strPath.c_str()));
365
366 if ( RTFS_IS_DIRECTORY(pFsObjInfo->Attr.fMode)
367 || RTFS_IS_FILE (pFsObjInfo->Attr.fMode))
368 {
369 FSOBJENTRY objEntry;
370 objEntry.pszPath = RTStrDup(strPath.c_str());
371 AssertPtrBreakStmt(objEntry.pszPath, rc = VERR_NO_MEMORY);
372 objEntry.objInfo = *pFsObjInfo;
373
374 m_lstEntries.push_back(objEntry); /** @todo Can this throw? */
375 }
376 else /* Not fatal, just skip. */
377 LogRel(("Shared Clipboard: Warning: File system object '%s' of type %#x not supported, skipping\n",
378 strPath.c_str(), pFsObjInfo->Attr.fMode & RTFS_TYPE_MASK));
379
380 /** @todo Handle symlinks. */
381 }
382 else
383 rc = VERR_INVALID_PARAMETER;
384 }
385
386 ShClTransferListEntryDestroy(&entryList);
387 }
388
389 if ( RT_FAILURE(rc)
390 && pTransfer->Thread.fStop)
391 break;
392 }
393 }
394
395 ShClTransferListClose(pTransfer, hList);
396 }
397 }
398
399 ShClTransferListOpenParmsDestroy(&openParmsList);
400 }
401
402 if (RT_FAILURE(rc))
403 LogRel(("Shared Clipboard: Reading directory '%s' failed with %Rrc\n", strDir.c_str(), rc));
404
405 LogFlowFuncLeaveRC(rc);
406 return rc;
407}
408
409/**
410 * Thread for reading transfer data.
411 * The data object needs the (high level, root) transfer listing at the time of ::GetData(), so we need
412 * to block and wait until we have this data (via this thread) and continue.
413 *
414 * @returns VBox status code.
415 * @param ThreadSelf Thread handle. Unused at the moment.
416 * @param pvUser Pointer to user-provided data. Of type SharedClipboardWinDataObject.
417 */
418/* static */
419DECLCALLBACK(int) SharedClipboardWinDataObject::readThread(RTTHREAD ThreadSelf, void *pvUser)
420{
421 RT_NOREF(ThreadSelf);
422
423 LogFlowFuncEnter();
424
425 SharedClipboardWinDataObject *pThis = (SharedClipboardWinDataObject *)pvUser;
426
427 PSHCLTRANSFER pTransfer = pThis->m_pTransfer;
428 AssertPtr(pTransfer);
429
430 pTransfer->Thread.fStarted = true;
431 pTransfer->Thread.fStop = false;
432
433 RTThreadUserSignal(RTThreadSelf());
434
435 LogRel2(("Shared Clipboard: Calculating transfer ...\n"));
436
437 int rc = ShClTransferRootListRead(pTransfer);
438 if (RT_SUCCESS(rc))
439 {
440 uint64_t const cRoots = ShClTransferRootsCount(pTransfer);
441
442 LogFlowFunc(("cRoots=%RU64\n\n", cRoots));
443
444 for (uint32_t i = 0; i < cRoots; i++)
445 {
446 PCSHCLLISTENTRY pRootEntry = ShClTransferRootsEntryGet(pTransfer, i);
447
448 AssertBreakStmt(pRootEntry->cbInfo == sizeof(SHCLFSOBJINFO), rc = VERR_INVALID_PARAMETER);
449 PSHCLFSOBJINFO const pFsObjInfo = (PSHCLFSOBJINFO)pRootEntry->pvInfo;
450
451 LogFlowFunc(("pszRoot=%s, fMode=0x%x (type %#x)\n",
452 pRootEntry->pszName, pFsObjInfo->Attr.fMode, (pFsObjInfo->Attr.fMode & RTFS_TYPE_MASK)));
453
454 if (RTFS_IS_DIRECTORY(pFsObjInfo->Attr.fMode))
455 {
456 FSOBJENTRY objEntry;
457 objEntry.pszPath = RTStrDup(pRootEntry->pszName);
458 AssertPtrBreakStmt(objEntry.pszPath, rc = VERR_NO_MEMORY);
459 objEntry.objInfo = *pFsObjInfo;
460
461 pThis->m_lstEntries.push_back(objEntry); /** @todo Can this throw? */
462
463 rc = pThis->readDir(pTransfer, pRootEntry->pszName);
464 }
465 else if (RTFS_IS_FILE(pFsObjInfo->Attr.fMode))
466 {
467 FSOBJENTRY objEntry;
468 objEntry.pszPath = RTStrDup(pRootEntry->pszName);
469 AssertPtrBreakStmt(objEntry.pszPath, rc = VERR_NO_MEMORY);
470 objEntry.objInfo = *pFsObjInfo;
471
472 pThis->m_lstEntries.push_back(objEntry); /** @todo Can this throw? */
473 }
474 else
475 {
476 LogRel(("Shared Clipboard: Root entry '%s': File type %#x not supported\n",
477 pRootEntry->pszName, (pFsObjInfo->Attr.fMode & RTFS_TYPE_MASK)));
478 rc = VERR_NOT_SUPPORTED;
479 }
480
481 if (ASMAtomicReadBool(&pTransfer->Thread.fStop))
482 {
483 LogRel2(("Shared Clipboard: Stopping transfer calculation ...\n"));
484 break;
485 }
486
487 if (RT_FAILURE(rc))
488 break;
489 }
490
491 if ( RT_SUCCESS(rc)
492 && !ASMAtomicReadBool(&pTransfer->Thread.fStop))
493 {
494 LogRel2(("Shared Clipboard: Transfer calculation complete (%zu root entries)\n", pThis->m_lstEntries.size()));
495
496 /*
497 * Signal the "list complete" event so that this data object can return (valid) data via ::GetData().
498 * This in turn then will create IStream instances (by the OS) for each file system object to handle.
499 */
500 int rc2 = RTSemEventSignal(pThis->m_EventListComplete);
501 AssertRC(rc2);
502
503 if (pThis->m_lstEntries.size())
504 {
505 LogRel2(("Shared Clipboard: Waiting for transfer to complete ...\n"));
506
507 /* Transferring stuff can take a while, so don't use any timeout here. */
508 rc2 = RTSemEventWait(pThis->m_EventStatusChanged, RT_INDEFINITE_WAIT);
509 AssertRC(rc2);
510
511 switch (pThis->m_enmStatus)
512 {
513 case Completed:
514 LogRel2(("Shared Clipboard: Transfer complete\n"));
515 break;
516
517 case Canceled:
518 LogRel2(("Shared Clipboard: Transfer canceled\n"));
519 break;
520
521 case Error:
522 LogRel2(("Shared Clipboard: Transfer error occurred\n"));
523 break;
524
525 default:
526 break;
527 }
528 }
529 else
530 LogRel(("Shared Clipboard: No transfer root entries found -- should not happen, please file a bug report\n"));
531 }
532 else if (RT_FAILURE(rc))
533 LogRel(("Shared Clipboard: Transfer failed with %Rrc\n", rc));
534 }
535
536 LogFlowFuncLeaveRC(rc);
537 return rc;
538}
539
540/**
541 * Creates a FILEGROUPDESCRIPTOR object from a given Shared Clipboard transfer and stores the result into an HGLOBAL object.
542 *
543 * @returns VBox status code.
544 * @param pTransfer Shared Clipboard transfer to create file grou desciprtor for.
545 * @param fUnicode Whether the FILEGROUPDESCRIPTOR object shall contain Unicode data or not.
546 * @param phGlobal Where to store the allocated HGLOBAL object on success.
547 */
548int SharedClipboardWinDataObject::createFileGroupDescriptorFromTransfer(PSHCLTRANSFER pTransfer,
549 bool fUnicode, HGLOBAL *phGlobal)
550{
551 AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
552 AssertPtrReturn(phGlobal, VERR_INVALID_POINTER);
553
554 LogFlowFuncEnter();
555
556 const size_t cbFileGroupDescriptor = fUnicode ? sizeof(FILEGROUPDESCRIPTORW) : sizeof(FILEGROUPDESCRIPTORA);
557 const size_t cbFileDescriptor = fUnicode ? sizeof(FILEDESCRIPTORW) : sizeof(FILEDESCRIPTORA);
558
559 const UINT cItems = (UINT)m_lstEntries.size(); /** UINT vs. size_t. */
560 if (!cItems)
561 return VERR_NOT_FOUND;
562
563 UINT curIdx = 0; /* Current index of the handled file group descriptor (FGD). */
564
565 const size_t cbFGD = cbFileGroupDescriptor + (cbFileDescriptor * (cItems - 1));
566
567 LogFunc(("fUnicode=%RTbool, cItems=%u, cbFileDescriptor=%zu\n", fUnicode, cItems, cbFileDescriptor));
568
569 /* FILEGROUPDESCRIPTORA / FILEGROUPDESCRIPTOR matches except the cFileName member (TCHAR vs. WCHAR). */
570 FILEGROUPDESCRIPTOR *pFGD = (FILEGROUPDESCRIPTOR *)RTMemAllocZ(cbFGD);
571 if (!pFGD)
572 return VERR_NO_MEMORY;
573
574 int rc = VINF_SUCCESS;
575
576 pFGD->cItems = cItems;
577
578 char *pszFileSpec = NULL;
579
580 FsObjEntryList::const_iterator itRoot = m_lstEntries.cbegin();
581 while (itRoot != m_lstEntries.end())
582 {
583 FILEDESCRIPTOR *pFD = &pFGD->fgd[curIdx];
584 RT_BZERO(pFD, cbFileDescriptor);
585
586 const char *pszFile = itRoot->pszPath;
587 AssertPtr(pszFile);
588
589 pszFileSpec = RTStrDup(pszFile);
590 AssertBreakStmt(pszFileSpec != NULL, rc = VERR_NO_MEMORY);
591
592 if (fUnicode)
593 {
594 PRTUTF16 pwszFileSpec;
595 rc = RTStrToUtf16(pszFileSpec, &pwszFileSpec);
596 if (RT_SUCCESS(rc))
597 {
598 rc = RTUtf16CopyEx((PRTUTF16 )pFD->cFileName, sizeof(pFD->cFileName) / sizeof(WCHAR),
599 pwszFileSpec, RTUtf16Len(pwszFileSpec));
600 RTUtf16Free(pwszFileSpec);
601
602 LogFlowFunc(("pFD->cFileNameW=%ls\n", pFD->cFileName));
603 }
604 }
605 else
606 {
607 rc = RTStrCopy(pFD->cFileName, sizeof(pFD->cFileName), pszFileSpec);
608 LogFlowFunc(("pFD->cFileNameA=%s\n", pFD->cFileName));
609 }
610
611 RTStrFree(pszFileSpec);
612 pszFileSpec = NULL;
613
614 if (RT_FAILURE(rc))
615 break;
616
617 pFD->dwFlags = FD_PROGRESSUI | FD_ATTRIBUTES;
618 if (fUnicode) /** @todo Only >= Vista. */
619 pFD->dwFlags |= FD_UNICODE;
620 pFD->dwFileAttributes = FILE_ATTRIBUTE_NORMAL;
621
622 const SHCLFSOBJINFO *pObjInfo = &itRoot->objInfo;
623
624 if (RTFS_IS_DIRECTORY(pObjInfo->Attr.fMode))
625 {
626 pFD->dwFileAttributes |= FILE_ATTRIBUTE_DIRECTORY;
627 }
628 else if (RTFS_IS_FILE(pObjInfo->Attr.fMode))
629 {
630 pFD->dwFlags |= FD_FILESIZE;
631
632 const uint64_t cbObjSize = pObjInfo->cbObject;
633
634 pFD->nFileSizeHigh = RT_HI_U32(cbObjSize);
635 pFD->nFileSizeLow = RT_LO_U32(cbObjSize);
636 }
637 else if (RTFS_IS_SYMLINK(pObjInfo->Attr.fMode))
638 {
639 /** @todo Implement. */
640 }
641#if 0 /** @todo Implement this. */
642 pFD->dwFlags = FD_ATTRIBUTES | FD_CREATETIME | FD_ACCESSTIME | FD_WRITESTIME | FD_FILESIZE;
643 pFD->dwFileAttributes =
644 pFD->ftCreationTime =
645 pFD->ftLastAccessTime =
646 pFD->ftLastWriteTime =
647#endif
648 ++curIdx;
649 ++itRoot;
650 }
651
652 if (pszFileSpec)
653 RTStrFree(pszFileSpec);
654
655 if (RT_SUCCESS(rc))
656 rc = copyToHGlobal(pFGD, cbFGD, GMEM_MOVEABLE, phGlobal);
657
658 RTMemFree(pFGD);
659
660 LogFlowFuncLeaveRC(rc);
661 return rc;
662}
663
664/**
665 * Retrieves the data stored in this object and store the result in pMedium.
666 *
667 * @return HRESULT
668 * @param pFormatEtc Format to retrieve.
669 * @param pMedium Where to store the data on success.
670 *
671 * @thread Windows event thread.
672 */
673STDMETHODIMP SharedClipboardWinDataObject::GetData(LPFORMATETC pFormatEtc, LPSTGMEDIUM pMedium)
674{
675 AssertPtrReturn(pFormatEtc, DV_E_FORMATETC);
676 AssertPtrReturn(pMedium, DV_E_FORMATETC);
677
678 int rc2 = RTCritSectEnter(&m_CritSect);
679 AssertRCReturn(rc2, E_UNEXPECTED);
680
681 LogFlowFunc(("lIndex=%RI32, enmStatus=%#x\n", pFormatEtc->lindex, m_enmStatus));
682
683 /*
684 * Initialize default values.
685 */
686 RT_BZERO(pMedium, sizeof(STGMEDIUM));
687
688 HRESULT hr = DV_E_FORMATETC; /* Play safe. */
689
690 int rc = VINF_SUCCESS;
691
692 if ( pFormatEtc->cfFormat == m_cfFileDescriptorA
693#ifdef VBOX_CLIPBOARD_WITH_UNICODE_SUPPORT
694 || pFormatEtc->cfFormat == m_cfFileDescriptorW
695#endif
696 )
697 {
698 switch (m_enmStatus)
699 {
700 case Initialized:
701 {
702 LogRel2(("Shared Clipboard: Requesting data for IDataObject ...\n"));
703
704 /* Leave lock while requesting + waiting. */
705 rc2 = RTCritSectLeave(&m_CritSect);
706 AssertRCReturn(rc2, E_UNEXPECTED);
707
708 /* Request the transfer from the source.
709 * This will generate a transfer status message which we're waiting for here. */
710 AssertPtr(m_Callbacks.pfnOnRequestDataFromSource);
711 rc = m_Callbacks.pfnOnRequestDataFromSource(m_pCtx,
712 VBOX_SHCL_FMT_URI_LIST, NULL /* ppv */, NULL /* pv */,
713 this /* pvUser */);
714 AssertRCBreak(rc);
715
716 LogRel2(("Shared Clipboard: Waiting for IDataObject started status ...\n"));
717
718 rc = RTSemEventWait(m_EventStatusChanged, SHCL_TIMEOUT_DEFAULT_MS);
719
720 /* Re-acquire lock. */
721 rc2 = RTCritSectEnter(&m_CritSect);
722 AssertRCReturn(rc2, E_UNEXPECTED);
723
724 if (RT_SUCCESS(rc))
725 {
726 if (m_enmStatus != Running)
727 {
728 LogRel(("Shared Clipboard: Received wrong IDataObject status (%#x)\n", m_enmStatus));
729 rc = VERR_WRONG_ORDER;
730 break;
731 }
732
733 /* There now must be a transfer assigned. */
734 AssertPtrBreakStmt(m_pTransfer, rc = VERR_WRONG_ORDER);
735 }
736 else /* Bail out on failure. */
737 break;
738
739 RT_FALL_THROUGH();
740 }
741
742 case Running:
743 {
744 const bool fUnicode = pFormatEtc->cfFormat == m_cfFileDescriptorW;
745
746 const uint32_t enmTransferStatus = ShClTransferGetStatus(m_pTransfer);
747 RT_NOREF(enmTransferStatus);
748
749 LogFlowFunc(("FormatIndex_FileDescriptor%s, enmTransferStatus=%s, m_fRunning=%RTbool\n",
750 fUnicode ? "W" : "A", ShClTransferStatusToStr(enmTransferStatus), m_fThreadRunning));
751
752 /* The caller can call GetData() several times, so make sure we don't do the same transfer multiple times. */
753 if (!m_fThreadRunning)
754 {
755 /* Start the transfer asynchronously in a separate thread. */
756 rc = ShClTransferRun(m_pTransfer, &SharedClipboardWinDataObject::readThread, this /* pvUser */);
757 if (RT_SUCCESS(rc))
758 {
759 m_fThreadRunning = true;
760
761 /* Leave lock while waiting. */
762 rc2 = RTCritSectLeave(&m_CritSect);
763 AssertRCReturn(rc2, E_UNEXPECTED);
764
765 /* Don't block for too long here, as this also will screw other apps running on the OS. */
766 LogRel2(("Shared Clipboard: Waiting for IDataObject listing to arrive ...\n"));
767 rc = RTSemEventWait(m_EventListComplete, SHCL_TIMEOUT_DEFAULT_MS);
768
769 /* Re-acquire lock. */
770 rc2 = RTCritSectEnter(&m_CritSect);
771 AssertRCReturn(rc2, E_UNEXPECTED);
772 }
773 }
774
775 if (RT_SUCCESS(rc))
776 {
777 HGLOBAL hGlobal;
778 rc = createFileGroupDescriptorFromTransfer(m_pTransfer, fUnicode, &hGlobal);
779 if (RT_SUCCESS(rc))
780 {
781 pMedium->tymed = TYMED_HGLOBAL;
782 pMedium->hGlobal = hGlobal;
783 /* Note: hGlobal now is being owned by pMedium / the caller. */
784
785 hr = S_OK;
786 }
787 }
788
789 break;
790 }
791
792 default:
793 break;
794 }
795
796 if (RT_FAILURE(rc))
797 {
798 LogRel(("Shared Clipboard: Error getting data for IDataObject, rc=%Rrc\n", rc));
799 hr = E_UNEXPECTED; /* We can't tell any better to the caller, unfortunately. */
800 }
801 }
802
803 if (RT_SUCCESS(rc))
804 {
805 if (pFormatEtc->cfFormat == m_cfFileContents)
806 {
807 if ( pFormatEtc->lindex >= 0
808 && (ULONG)pFormatEtc->lindex < m_lstEntries.size())
809 {
810 m_uObjIdx = pFormatEtc->lindex; /* lIndex of FormatEtc contains the actual index to the object being handled. */
811
812 FSOBJENTRY &fsObjEntry = m_lstEntries.at(m_uObjIdx);
813
814 LogFlowFunc(("FormatIndex_FileContents: m_uObjIdx=%u (entry '%s')\n", m_uObjIdx, fsObjEntry.pszPath));
815
816 LogRel2(("Shared Clipboard: Receiving object '%s' ...\n", fsObjEntry.pszPath));
817
818 /* Hand-in the provider so that our IStream implementation can continue working with it. */
819 hr = SharedClipboardWinStreamImpl::Create(this /* pParent */, m_pTransfer,
820 fsObjEntry.pszPath /* File name */, &fsObjEntry.objInfo /* PSHCLFSOBJINFO */,
821 &m_pStream);
822 if (SUCCEEDED(hr))
823 {
824 /* Hand over the stream to the caller. */
825 pMedium->tymed = TYMED_ISTREAM;
826 pMedium->pstm = m_pStream;
827 }
828 }
829 }
830 else if (pFormatEtc->cfFormat == m_cfPerformedDropEffect)
831 {
832 HGLOBAL hGlobal = GlobalAlloc(GHND, sizeof(DWORD));
833
834 DWORD* pdwDropEffect = (DWORD*)GlobalLock(hGlobal);
835 *pdwDropEffect = DROPEFFECT_COPY;
836
837 GlobalUnlock(hGlobal);
838
839 pMedium->tymed = TYMED_HGLOBAL;
840 pMedium->hGlobal = hGlobal;
841 pMedium->pUnkForRelease = NULL;
842 }
843
844 if ( FAILED(hr)
845 && hr != DV_E_FORMATETC) /* Can happen if the caller queries unknown / unhandled formats. */
846 {
847 LogRel(("Shared Clipboard: Error returning data from data object (%Rhrc)\n", hr));
848 }
849 }
850
851 rc2 = RTCritSectLeave(&m_CritSect);
852 AssertRCReturn(rc2, E_UNEXPECTED);
853
854 LogFlowFunc(("hr=%Rhrc\n", hr));
855 return hr;
856}
857
858/**
859 * Only required for IStream / IStorage interfaces.
860 *
861 * @return IPRT status code.
862 * @return HRESULT
863 * @param pFormatEtc
864 * @param pMedium
865 */
866STDMETHODIMP SharedClipboardWinDataObject::GetDataHere(LPFORMATETC pFormatEtc, LPSTGMEDIUM pMedium)
867{
868 RT_NOREF(pFormatEtc, pMedium);
869 LogFlowFunc(("\n"));
870 return E_NOTIMPL;
871}
872
873/**
874 * Query if this objects supports a specific format.
875 *
876 * @return IPRT status code.
877 * @return HRESULT
878 * @param pFormatEtc
879 */
880STDMETHODIMP SharedClipboardWinDataObject::QueryGetData(LPFORMATETC pFormatEtc)
881{
882 LogFlowFunc(("\n"));
883 return lookupFormatEtc(pFormatEtc, NULL /* puIndex */) ? S_OK : DV_E_FORMATETC;
884}
885
886STDMETHODIMP SharedClipboardWinDataObject::GetCanonicalFormatEtc(LPFORMATETC pFormatEtc, LPFORMATETC pFormatEtcOut)
887{
888 RT_NOREF(pFormatEtc);
889 LogFlowFunc(("\n"));
890
891 /* Set this to NULL in any case. */
892 pFormatEtcOut->ptd = NULL;
893 return E_NOTIMPL;
894}
895
896STDMETHODIMP SharedClipboardWinDataObject::SetData(LPFORMATETC pFormatEtc, LPSTGMEDIUM pMedium, BOOL fRelease)
897{
898 if ( pFormatEtc == NULL
899 || pMedium == NULL)
900 return E_INVALIDARG;
901
902 if (pFormatEtc->lindex != -1)
903 return DV_E_LINDEX;
904
905 if (pFormatEtc->tymed != TYMED_HGLOBAL)
906 return DV_E_TYMED;
907
908 if (pFormatEtc->dwAspect != DVASPECT_CONTENT)
909 return DV_E_DVASPECT;
910
911 LogFlowFunc(("cfFormat=%RU16, lookupFormatEtc=%RTbool\n",
912 pFormatEtc->cfFormat, lookupFormatEtc(pFormatEtc, NULL /* puIndex */)));
913
914 /* CFSTR_PERFORMEDDROPEFFECT is used by the drop target (caller of this IDataObject) to communicate
915 * the outcome of the overall operation. */
916 if ( pFormatEtc->cfFormat == m_cfPerformedDropEffect
917 && pMedium->tymed == TYMED_HGLOBAL)
918 {
919 DWORD dwEffect = *(DWORD *)GlobalLock(pMedium->hGlobal);
920 GlobalUnlock(pMedium->hGlobal);
921
922 LogFlowFunc(("dwEffect=%RI32\n", dwEffect));
923
924 /* Did the user cancel the operation via UI (shell)? This also might happen when overwriting an existing file
925 * and the user doesn't want to allow this. */
926 if (dwEffect == DROPEFFECT_NONE)
927 {
928 LogRel2(("Shared Clipboard: Transfer canceled by user interaction\n"));
929
930 SetStatus(Canceled);
931 }
932 /** @todo Detect move / overwrite actions here. */
933
934 if (fRelease)
935 ReleaseStgMedium(pMedium);
936
937 return S_OK;
938 }
939
940 return E_NOTIMPL;
941}
942
943STDMETHODIMP SharedClipboardWinDataObject::EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC **ppEnumFormatEtc)
944{
945 LogFlowFunc(("dwDirection=%RI32, mcFormats=%RI32, mpFormatEtc=%p\n", dwDirection, m_cFormats, m_pFormatEtc));
946
947 HRESULT hr;
948 if (dwDirection == DATADIR_GET)
949 hr = SharedClipboardWinEnumFormatEtc::CreateEnumFormatEtc(m_cFormats, m_pFormatEtc, ppEnumFormatEtc);
950 else
951 hr = E_NOTIMPL;
952
953 LogFlowFunc(("hr=%Rhrc\n", hr));
954 return hr;
955}
956
957STDMETHODIMP SharedClipboardWinDataObject::DAdvise(LPFORMATETC pFormatEtc, DWORD fAdvise, IAdviseSink *pAdvSink, DWORD *pdwConnection)
958{
959 RT_NOREF(pFormatEtc, fAdvise, pAdvSink, pdwConnection);
960 return OLE_E_ADVISENOTSUPPORTED;
961}
962
963STDMETHODIMP SharedClipboardWinDataObject::DUnadvise(DWORD dwConnection)
964{
965 RT_NOREF(dwConnection);
966 return OLE_E_ADVISENOTSUPPORTED;
967}
968
969STDMETHODIMP SharedClipboardWinDataObject::EnumDAdvise(IEnumSTATDATA **ppEnumAdvise)
970{
971 RT_NOREF(ppEnumAdvise);
972 return OLE_E_ADVISENOTSUPPORTED;
973}
974
975#ifdef VBOX_WITH_SHARED_CLIPBOARD_WIN_ASYNC
976/*
977 * IDataObjectAsyncCapability methods.
978 */
979
980STDMETHODIMP SharedClipboardWinDataObject::EndOperation(HRESULT hResult, IBindCtx *pbcReserved, DWORD dwEffects)
981{
982 RT_NOREF(hResult, pbcReserved, dwEffects);
983 return E_NOTIMPL;
984}
985
986STDMETHODIMP SharedClipboardWinDataObject::GetAsyncMode(BOOL *pfIsOpAsync)
987{
988 RT_NOREF(pfIsOpAsync);
989 return E_NOTIMPL;
990}
991
992STDMETHODIMP SharedClipboardWinDataObject::InOperation(BOOL *pfInAsyncOp)
993{
994 RT_NOREF(pfInAsyncOp);
995 return E_NOTIMPL;
996}
997
998STDMETHODIMP SharedClipboardWinDataObject::SetAsyncMode(BOOL fDoOpAsync)
999{
1000 RT_NOREF(fDoOpAsync);
1001 return E_NOTIMPL;
1002}
1003
1004STDMETHODIMP SharedClipboardWinDataObject::StartOperation(IBindCtx *pbcReserved)
1005{
1006 RT_NOREF(pbcReserved);
1007 return E_NOTIMPL;
1008}
1009#endif /* VBOX_WITH_SHARED_CLIPBOARD_WIN_ASYNC */
1010
1011/*
1012 * Own stuff.
1013 */
1014
1015/**
1016 * Assigns a transfer object and starts the transfer for the data object.
1017 *
1018 * @returns VBox status code.
1019 * @param pTransfer Transfer to assign.
1020 */
1021int SharedClipboardWinDataObject::SetAndStartTransfer(PSHCLTRANSFER pTransfer)
1022{
1023 AssertReturn(m_pTransfer == NULL, VERR_WRONG_ORDER); /* Transfer already set? */
1024
1025 int rc = RTCritSectEnter(&m_CritSect);
1026 if (RT_SUCCESS(rc))
1027 {
1028 if (m_enmStatus == Initialized)
1029 {
1030 m_pTransfer = pTransfer;
1031
1032 ShClTransferAcquire(pTransfer);
1033
1034 rc = setStatusLocked(Running);
1035 }
1036 else
1037 AssertFailedStmt(rc = VERR_WRONG_ORDER);
1038
1039 RTCritSectLeave(&m_CritSect);
1040 }
1041
1042 return rc;
1043}
1044
1045/**
1046 * Sets a new status to the data object and signals its waiter.
1047 *
1048 * @returns VBox status code.
1049 * @param enmStatus New status to signal.
1050 * @param rc Result code. Optional.
1051 *
1052 * @note Called by the main clipboard thread + SharedClipboardWinStreamImpl.
1053 */
1054int SharedClipboardWinDataObject::SetStatus(Status enmStatus, int rc /* = VINF_SUCCESS */)
1055{
1056 int rc2 = RTCritSectEnter(&m_CritSect);
1057 if (RT_SUCCESS(rc2))
1058 {
1059 setStatusLocked(enmStatus, rc);
1060
1061 RTCritSectLeave(&m_CritSect);
1062 }
1063
1064 return rc2;
1065}
1066
1067/* static */
1068void SharedClipboardWinDataObject::logFormat(CLIPFORMAT fmt)
1069{
1070 char szFormat[128];
1071 if (GetClipboardFormatName(fmt, szFormat, sizeof(szFormat)))
1072 {
1073 LogFlowFunc(("clipFormat=%RI16 -> %s\n", fmt, szFormat));
1074 }
1075 else
1076 LogFlowFunc(("clipFormat=%RI16 is unknown\n", fmt));
1077}
1078
1079bool SharedClipboardWinDataObject::lookupFormatEtc(LPFORMATETC pFormatEtc, ULONG *puIndex)
1080{
1081 AssertReturn(pFormatEtc, false);
1082 /* puIndex is optional. */
1083
1084 for (ULONG i = 0; i < m_cFormats; i++)
1085 {
1086 if( (pFormatEtc->tymed & m_pFormatEtc[i].tymed)
1087 && pFormatEtc->cfFormat == m_pFormatEtc[i].cfFormat)
1088 /* Note: Do *not* compare dwAspect here, as this can be dynamic, depending on how the object should be represented. */
1089 //&& pFormatEtc->dwAspect == m_pFormatEtc[i].dwAspect)
1090 {
1091 LogRel2(("Shared Clipboard: Format found: tyMed=%RI32, cfFormat=%RI16, dwAspect=%RI32, ulIndex=%RU32\n",
1092 pFormatEtc->tymed, pFormatEtc->cfFormat, pFormatEtc->dwAspect, i));
1093 if (puIndex)
1094 *puIndex = i;
1095 return true;
1096 }
1097 }
1098
1099 LogRel2(("Shared Clipboard: Format NOT found: tyMed=%RI32, cfFormat=%RI16, dwAspect=%RI32\n",
1100 pFormatEtc->tymed, pFormatEtc->cfFormat, pFormatEtc->dwAspect));
1101
1102 logFormat(pFormatEtc->cfFormat);
1103
1104 return false;
1105}
1106
1107void SharedClipboardWinDataObject::registerFormat(LPFORMATETC pFormatEtc, CLIPFORMAT clipFormat,
1108 TYMED tyMed, LONG lIndex, DWORD dwAspect,
1109 DVTARGETDEVICE *pTargetDevice)
1110{
1111 AssertPtr(pFormatEtc);
1112
1113 pFormatEtc->cfFormat = clipFormat;
1114 pFormatEtc->tymed = tyMed;
1115 pFormatEtc->lindex = lIndex;
1116 pFormatEtc->dwAspect = dwAspect;
1117 pFormatEtc->ptd = pTargetDevice;
1118
1119 LogFlowFunc(("Registered format=%ld\n", pFormatEtc->cfFormat));
1120
1121 logFormat(pFormatEtc->cfFormat);
1122}
1123
1124/**
1125 * Sets a new status to the data object and signals its waiter.
1126 *
1127 * @returns VBox status code.
1128 * @param enmStatus New status to signal.
1129 * @param rc Result code. Optional.
1130 *
1131 * @note Caller must have taken the critical section.
1132 */
1133int SharedClipboardWinDataObject::setStatusLocked(Status enmStatus, int rc /* = VINF_SUCCESS */)
1134{
1135 AssertReturn(enmStatus == Error || RT_SUCCESS(rc), VERR_INVALID_PARAMETER);
1136 AssertReturn(RTCritSectIsOwned(&m_CritSect), VERR_WRONG_ORDER);
1137
1138 RT_NOREF(rc);
1139
1140 int rc2 = RTCritSectEnter(&m_CritSect);
1141 if (RT_SUCCESS(rc2))
1142 {
1143 LogFlowFunc(("enmStatus=%#x (current is: %#x)\n", enmStatus, m_enmStatus));
1144
1145 switch (enmStatus)
1146 {
1147 case Completed:
1148 {
1149 LogFlowFunc(("m_uObjIdx=%RU32 (total: %zu)\n", m_uObjIdx, m_lstEntries.size()));
1150
1151 const bool fComplete = m_uObjIdx == m_lstEntries.size() - 1 /* Object index is zero-based */;
1152 if (fComplete)
1153 m_enmStatus = Completed;
1154 break;
1155 }
1156
1157 default:
1158 {
1159 m_enmStatus = enmStatus;
1160 break;
1161 }
1162 }
1163
1164 if (RT_FAILURE(rc))
1165 LogRel(("Shared Clipboard: Data object received error %Rrc (status %#x)\n", rc, enmStatus));
1166
1167 if (m_EventStatusChanged != NIL_RTSEMEVENT)
1168 rc2 = RTSemEventSignal(m_EventStatusChanged);
1169
1170 RTCritSectLeave(&m_CritSect);
1171 }
1172
1173 return rc2;
1174}
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