VirtualBox

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

Last change on this file since 100396 was 100394, checked in by vboxsync, 19 months ago

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