VirtualBox

source: vbox/trunk/src/VBox/GuestHost/SharedClipboard/clipboard-win.cpp@ 100204

Last change on this file since 100204 was 100204, checked in by vboxsync, 21 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.

bugref:9437

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 48.1 KB
Line 
1/* $Id: clipboard-win.cpp 100204 2023-06-19 09:11:37Z vboxsync $ */
2/** @file
3 * Shared Clipboard: Windows-specific functions for clipboard handling.
4 */
5
6/*
7 * Copyright (C) 2006-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#include <VBox/GuestHost/SharedClipboard.h>
29
30#include <iprt/assert.h>
31#include <iprt/errcore.h>
32#include <iprt/ldr.h>
33#include <iprt/mem.h>
34#include <iprt/thread.h>
35#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
36# include <iprt/win/windows.h>
37# include <iprt/win/shlobj.h> /* For CFSTR_FILEDESCRIPTORXXX + CFSTR_FILECONTENTS. */
38# include <iprt/utf16.h>
39#endif
40
41#ifdef LOG_GROUP
42# undef LOG_GROUP
43#endif
44#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD
45#include <VBox/log.h>
46
47#include <VBox/HostServices/VBoxClipboardSvc.h>
48#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
49# include <VBox/GuestHost/SharedClipboard-transfers.h>
50#endif
51#include <VBox/GuestHost/SharedClipboard-win.h>
52#include <VBox/GuestHost/clipboard-helper.h>
53
54
55#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
56int SharedClipboardWinTransferDropFilesToStringList(DROPFILES *pDropFiles, char **ppszList, uint32_t *pcbList);
57#endif
58
59
60/**
61 * Opens the clipboard of a specific window.
62 *
63 * @returns VBox status code.
64 * @param hWnd Handle of window to open clipboard for.
65 */
66int SharedClipboardWinOpen(HWND hWnd)
67{
68 /* "OpenClipboard fails if another window has the clipboard open."
69 * So try a few times and wait up to 1 second.
70 */
71 BOOL fOpened = FALSE;
72
73 LogFlowFunc(("hWnd=%p\n", hWnd));
74
75 int i = 0;
76 for (;;)
77 {
78 if (OpenClipboard(hWnd))
79 {
80 fOpened = TRUE;
81 break;
82 }
83
84 if (i >= 10) /* sleep interval = [1..512] ms */
85 break;
86
87 RTThreadSleep(1 << i);
88 ++i;
89 }
90
91#ifdef LOG_ENABLED
92 if (i > 0)
93 LogFlowFunc(("%d times tried to open clipboard\n", i + 1));
94#endif
95
96 int rc;
97 if (fOpened)
98 rc = VINF_SUCCESS;
99 else
100 {
101 const DWORD dwLastErr = GetLastError();
102 rc = RTErrConvertFromWin32(dwLastErr);
103 LogRel(("Failed to open clipboard, rc=%Rrc (0x%x)\n", rc, dwLastErr));
104 }
105
106 return rc;
107}
108
109/**
110 * Closes the clipboard for the current thread.
111 *
112 * @returns VBox status code.
113 */
114int SharedClipboardWinClose(void)
115{
116 int rc;
117
118 const BOOL fRc = CloseClipboard();
119 if (RT_UNLIKELY(!fRc))
120 {
121 const DWORD dwLastErr = GetLastError();
122 if (dwLastErr == ERROR_CLIPBOARD_NOT_OPEN)
123 {
124 rc = VINF_SUCCESS; /* Not important, so just report success instead. */
125 }
126 else
127 {
128 rc = RTErrConvertFromWin32(dwLastErr);
129 LogFunc(("Failed with %Rrc (0x%x)\n", rc, dwLastErr));
130 }
131 }
132 else
133 rc = VINF_SUCCESS;
134
135 LogFlowFuncLeaveRC(rc);
136 return rc;
137}
138
139/**
140 * Clears the clipboard for the current thread.
141 *
142 * @returns VBox status code.
143 */
144int SharedClipboardWinClear(void)
145{
146 LogFlowFuncEnter();
147 if (EmptyClipboard())
148 return VINF_SUCCESS;
149
150 const DWORD dwLastErr = GetLastError();
151 AssertReturn(dwLastErr != ERROR_CLIPBOARD_NOT_OPEN, VERR_INVALID_STATE);
152
153 int rc = RTErrConvertFromWin32(dwLastErr);
154 LogFunc(("Failed with %Rrc (0x%x)\n", rc, dwLastErr));
155 return rc;
156}
157
158/**
159 * Initializes a Shared Clipboard Windows context.
160 *
161 * @returns VBox status code.
162 * @param pWinCtx Shared Clipboard Windows context to initialize.
163 */
164int SharedClipboardWinCtxInit(PSHCLWINCTX pWinCtx)
165{
166 int rc = RTCritSectInit(&pWinCtx->CritSect);
167 if (RT_SUCCESS(rc))
168 {
169 /* Check that new Clipboard API is available. */
170 SharedClipboardWinCheckAndInitNewAPI(&pWinCtx->newAPI);
171 /* Do *not* check the rc, as the call might return VERR_SYMBOL_NOT_FOUND is the new API isn't available. */
172
173 pWinCtx->hWnd = NULL;
174 pWinCtx->hWndClipboardOwnerUs = NULL;
175 pWinCtx->hWndNextInChain = NULL;
176 }
177
178 LogFlowFuncLeaveRC(rc);
179 return rc;
180}
181
182/**
183 * Destroys a Shared Clipboard Windows context.
184 *
185 * @param pWinCtx Shared Clipboard Windows context to destroy.
186 */
187void SharedClipboardWinCtxDestroy(PSHCLWINCTX pWinCtx)
188{
189 if (!pWinCtx)
190 return;
191
192 LogFlowFuncEnter();
193
194 if (RTCritSectIsInitialized(&pWinCtx->CritSect))
195 {
196 int rc2 = RTCritSectDelete(&pWinCtx->CritSect);
197 AssertRC(rc2);
198 }
199}
200
201/**
202 * Checks and initializes function pointer which are required for using
203 * the new clipboard API.
204 *
205 * @returns VBox status code, or VERR_SYMBOL_NOT_FOUND if the new API is not available.
206 * @param pAPI Where to store the retrieved function pointers.
207 * Will be set to NULL if the new API is not available.
208 */
209int SharedClipboardWinCheckAndInitNewAPI(PSHCLWINAPINEW pAPI)
210{
211 RTLDRMOD hUser32 = NIL_RTLDRMOD;
212 int rc = RTLdrLoadSystem("User32.dll", /* fNoUnload = */ true, &hUser32);
213 if (RT_SUCCESS(rc))
214 {
215 rc = RTLdrGetSymbol(hUser32, "AddClipboardFormatListener", (void **)&pAPI->pfnAddClipboardFormatListener);
216 if (RT_SUCCESS(rc))
217 {
218 rc = RTLdrGetSymbol(hUser32, "RemoveClipboardFormatListener", (void **)&pAPI->pfnRemoveClipboardFormatListener);
219 }
220
221 RTLdrClose(hUser32);
222 }
223
224 if (RT_SUCCESS(rc))
225 {
226 LogRel(("Shared Clipboard: New Clipboard API enabled\n"));
227 }
228 else
229 {
230 RT_BZERO(pAPI, sizeof(SHCLWINAPINEW));
231 LogRel(("Shared Clipboard: New Clipboard API not available (%Rrc)\n", rc));
232 }
233
234 LogFlowFuncLeaveRC(rc);
235 return rc;
236}
237
238/**
239 * Returns if the new clipboard API is available or not.
240 *
241 * @returns @c true if the new API is available, or @c false if not.
242 * @param pAPI Structure used for checking if the new clipboard API is available or not.
243 */
244bool SharedClipboardWinIsNewAPI(PSHCLWINAPINEW pAPI)
245{
246 if (!pAPI)
247 return false;
248 return pAPI->pfnAddClipboardFormatListener != NULL;
249}
250
251/**
252 * Adds ourselves into the chain of cliboard listeners.
253 *
254 * @returns VBox status code.
255 * @param pCtx Windows clipboard context to use to add ourselves.
256 */
257int SharedClipboardWinChainAdd(PSHCLWINCTX pCtx)
258{
259 const PSHCLWINAPINEW pAPI = &pCtx->newAPI;
260
261 BOOL fRc;
262 if (SharedClipboardWinIsNewAPI(pAPI))
263 fRc = pAPI->pfnAddClipboardFormatListener(pCtx->hWnd);
264 else
265 {
266 SetLastError(NO_ERROR);
267 pCtx->hWndNextInChain = SetClipboardViewer(pCtx->hWnd);
268 fRc = pCtx->hWndNextInChain != NULL || GetLastError() == NO_ERROR;
269 }
270
271 int rc = VINF_SUCCESS;
272
273 if (!fRc)
274 {
275 const DWORD dwLastErr = GetLastError();
276 rc = RTErrConvertFromWin32(dwLastErr);
277 LogFunc(("Failed with %Rrc (0x%x)\n", rc, dwLastErr));
278 }
279
280 return rc;
281}
282
283/**
284 * Remove ourselves from the chain of cliboard listeners
285 *
286 * @returns VBox status code.
287 * @param pCtx Windows clipboard context to use to remove ourselves.
288 */
289int SharedClipboardWinChainRemove(PSHCLWINCTX pCtx)
290{
291 if (!pCtx->hWnd)
292 return VINF_SUCCESS;
293
294 const PSHCLWINAPINEW pAPI = &pCtx->newAPI;
295
296 BOOL fRc;
297 if (SharedClipboardWinIsNewAPI(pAPI))
298 {
299 fRc = pAPI->pfnRemoveClipboardFormatListener(pCtx->hWnd);
300 }
301 else
302 {
303 fRc = ChangeClipboardChain(pCtx->hWnd, pCtx->hWndNextInChain);
304 if (fRc)
305 pCtx->hWndNextInChain = NULL;
306 }
307
308 int rc = VINF_SUCCESS;
309
310 if (!fRc)
311 {
312 const DWORD dwLastErr = GetLastError();
313 rc = RTErrConvertFromWin32(dwLastErr);
314 LogFunc(("Failed with %Rrc (0x%x)\n", rc, dwLastErr));
315 }
316
317 return rc;
318}
319
320/**
321 * Callback which is invoked when we have successfully pinged ourselves down the
322 * clipboard chain. We simply unset a boolean flag to say that we are responding.
323 * There is a race if a ping returns after the next one is initiated, but nothing
324 * very bad is likely to happen.
325 *
326 * @param hWnd Window handle to use for this callback. Not used currently.
327 * @param uMsg Message to handle. Not used currently.
328 * @param dwData Pointer to user-provided data. Contains our Windows clipboard context.
329 * @param lResult Additional data to pass. Not used currently.
330 */
331VOID CALLBACK SharedClipboardWinChainPingProc(HWND hWnd, UINT uMsg, ULONG_PTR dwData, LRESULT lResult) RT_NOTHROW_DEF
332{
333 RT_NOREF(hWnd);
334 RT_NOREF(uMsg);
335 RT_NOREF(lResult);
336
337 /** @todo r=andy Why not using SetWindowLongPtr for keeping the context? */
338 PSHCLWINCTX pCtx = (PSHCLWINCTX)dwData;
339 AssertPtrReturnVoid(pCtx);
340
341 pCtx->oldAPI.fCBChainPingInProcess = FALSE;
342}
343
344/**
345 * Passes a window message to the next window in the clipboard chain.
346 *
347 * @returns LRESULT
348 * @param pWinCtx Window context to use.
349 * @param msg Window message to pass.
350 * @param wParam WPARAM to pass.
351 * @param lParam LPARAM to pass.
352 */
353LRESULT SharedClipboardWinChainPassToNext(PSHCLWINCTX pWinCtx,
354 UINT msg, WPARAM wParam, LPARAM lParam)
355{
356 LogFlowFuncEnter();
357
358 LRESULT lresultRc = 0;
359
360 if (pWinCtx->hWndNextInChain)
361 {
362 LogFunc(("hWndNextInChain=%p\n", pWinCtx->hWndNextInChain));
363
364 /* Pass the message to next window in the clipboard chain. */
365 DWORD_PTR dwResult;
366 lresultRc = SendMessageTimeout(pWinCtx->hWndNextInChain, msg, wParam, lParam, 0,
367 SHCL_WIN_CBCHAIN_TIMEOUT_MS, &dwResult);
368 if (!lresultRc)
369 lresultRc = dwResult;
370 }
371
372 LogFlowFunc(("lresultRc=%ld\n", lresultRc));
373 return lresultRc;
374}
375
376/**
377 * Converts a (registered or standard) Windows clipboard format to a VBox clipboard format.
378 *
379 * @returns Converted VBox clipboard format, or VBOX_SHCL_FMT_NONE if not found.
380 * @param uFormat Windows clipboard format to convert.
381 */
382SHCLFORMAT SharedClipboardWinClipboardFormatToVBox(UINT uFormat)
383{
384 /* Insert the requested clipboard format data into the clipboard. */
385 SHCLFORMAT vboxFormat = VBOX_SHCL_FMT_NONE;
386
387 switch (uFormat)
388 {
389 case CF_UNICODETEXT:
390 vboxFormat = VBOX_SHCL_FMT_UNICODETEXT;
391 break;
392
393 case CF_DIB:
394 vboxFormat = VBOX_SHCL_FMT_BITMAP;
395 break;
396
397#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
398 /* CF_HDROP handles file system entries which are locally present
399 * on source for transferring to the target.
400 *
401 * This does *not* invoke any IDataObject / IStream implementations! */
402 case CF_HDROP:
403 vboxFormat = VBOX_SHCL_FMT_URI_LIST;
404 break;
405#endif
406
407 default:
408 if (uFormat >= 0xC000) /** Formats registered with RegisterClipboardFormat() start at this index. */
409 {
410 TCHAR szFormatName[256]; /** @todo r=andy Do we need Unicode support here as well? */
411 int cActual = GetClipboardFormatName(uFormat, szFormatName, sizeof(szFormatName) / sizeof(TCHAR));
412 if (cActual)
413 {
414 LogFlowFunc(("uFormat=%u -> szFormatName=%s\n", uFormat, szFormatName));
415
416 if (RTStrCmp(szFormatName, SHCL_WIN_REGFMT_HTML) == 0)
417 vboxFormat = VBOX_SHCL_FMT_HTML;
418#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
419 /* These types invoke our IDataObject / IStream implementations. */
420 else if ( (RTStrCmp(szFormatName, CFSTR_FILEDESCRIPTORA) == 0)
421 || (RTStrCmp(szFormatName, CFSTR_FILECONTENTS) == 0))
422 vboxFormat = VBOX_SHCL_FMT_URI_LIST;
423 /** @todo Do we need to handle CFSTR_FILEDESCRIPTORW here as well? */
424#endif
425 }
426 }
427 break;
428 }
429
430 LogFlowFunc(("uFormat=%u -> vboxFormat=0x%x\n", uFormat, vboxFormat));
431 return vboxFormat;
432}
433
434/**
435 * Retrieves all supported clipboard formats of a specific clipboard.
436 *
437 * @returns VBox status code.
438 * @param pCtx Windows clipboard context to retrieve formats for.
439 * @param pfFormats Where to store the retrieved formats.
440 */
441int SharedClipboardWinGetFormats(PSHCLWINCTX pCtx, PSHCLFORMATS pfFormats)
442{
443 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
444 AssertPtrReturn(pfFormats, VERR_INVALID_POINTER);
445
446 SHCLFORMATS fFormats = VBOX_SHCL_FMT_NONE;
447
448 /* Query list of available formats and report to host. */
449 int rc = SharedClipboardWinOpen(pCtx->hWnd);
450 if (RT_SUCCESS(rc))
451 {
452 UINT uCurFormat = 0; /* Must be set to zero for EnumClipboardFormats(). */
453 while ((uCurFormat = EnumClipboardFormats(uCurFormat)) != 0)
454 fFormats |= SharedClipboardWinClipboardFormatToVBox(uCurFormat);
455
456 int rc2 = SharedClipboardWinClose();
457 AssertRC(rc2);
458 LogFlowFunc(("fFormats=%#x\n", fFormats));
459 }
460 else
461 LogFunc(("Failed with rc=%Rrc (fFormats=%#x)\n", rc, fFormats));
462
463 *pfFormats = fFormats;
464 return rc;
465}
466
467/**
468 * Extracts a field value from CF_HTML data.
469 *
470 * @returns VBox status code.
471 * @param pszSrc source in CF_HTML format.
472 * @param pszOption Name of CF_HTML field.
473 * @param puValue Where to return extracted value of CF_HTML field.
474 */
475int SharedClipboardWinGetCFHTMLHeaderValue(const char *pszSrc, const char *pszOption, uint32_t *puValue)
476{
477 AssertPtrReturn(pszSrc, VERR_INVALID_POINTER);
478 AssertPtrReturn(pszOption, VERR_INVALID_POINTER);
479
480 int rc = VERR_INVALID_PARAMETER;
481
482 const char *pszOptionValue = RTStrStr(pszSrc, pszOption);
483 if (pszOptionValue)
484 {
485 size_t cchOption = strlen(pszOption);
486 Assert(cchOption);
487
488 rc = RTStrToUInt32Ex(pszOptionValue + cchOption, NULL, 10, puValue);
489 }
490 return rc;
491}
492
493/**
494 * Check that the source string contains CF_HTML struct.
495 *
496 * @returns @c true if the @a pszSource string is in CF_HTML format.
497 * @param pszSource Source string to check.
498 */
499bool SharedClipboardWinIsCFHTML(const char *pszSource)
500{
501 return RTStrStr(pszSource, "Version:") != NULL
502 && RTStrStr(pszSource, "StartHTML:") != NULL;
503}
504
505/**
506 * Converts clipboard data from CF_HTML format to MIME clipboard format.
507 *
508 * Returns allocated buffer that contains html converted to text/html mime type
509 *
510 * @returns VBox status code.
511 * @param pszSource The input.
512 * @param cch The length of the input.
513 * @param ppszOutput Where to return the result. Free using RTMemFree.
514 * @param pcbOutput Where to the return length of the result (bytes/chars).
515 */
516int SharedClipboardWinConvertCFHTMLToMIME(const char *pszSource, const uint32_t cch, char **ppszOutput, uint32_t *pcbOutput)
517{
518 Assert(pszSource);
519 Assert(cch);
520 Assert(ppszOutput);
521 Assert(pcbOutput);
522
523 uint32_t offStart;
524 int rc = SharedClipboardWinGetCFHTMLHeaderValue(pszSource, "StartFragment:", &offStart);
525 if (RT_SUCCESS(rc))
526 {
527 uint32_t offEnd;
528 rc = SharedClipboardWinGetCFHTMLHeaderValue(pszSource, "EndFragment:", &offEnd);
529 if (RT_SUCCESS(rc))
530 {
531 if ( offStart > 0
532 && offEnd > 0
533 && offEnd >= offStart
534 && offEnd <= cch)
535 {
536 uint32_t cchSubStr = offEnd - offStart;
537 char *pszResult = (char *)RTMemAlloc(cchSubStr + 1);
538 if (pszResult)
539 {
540 rc = RTStrCopyEx(pszResult, cchSubStr + 1, pszSource + offStart, cchSubStr);
541 if (RT_SUCCESS(rc))
542 {
543 *ppszOutput = pszResult;
544 *pcbOutput = (uint32_t)(cchSubStr + 1);
545 rc = VINF_SUCCESS;
546 }
547 else
548 {
549 LogRelFlowFunc(("Error: Unknown CF_HTML format. Expected EndFragment. rc = %Rrc\n", rc));
550 RTMemFree(pszResult);
551 }
552 }
553 else
554 {
555 LogRelFlowFunc(("Error: Unknown CF_HTML format. Expected EndFragment\n"));
556 rc = VERR_NO_MEMORY;
557 }
558 }
559 else
560 {
561 LogRelFlowFunc(("Error: CF_HTML out of bounds - offStart=%#x offEnd=%#x cch=%#x\n", offStart, offEnd, cch));
562 rc = VERR_INVALID_PARAMETER;
563 }
564 }
565 else
566 {
567 LogRelFlowFunc(("Error: Unknown CF_HTML format. Expected EndFragment. rc = %Rrc\n", rc));
568 rc = VERR_INVALID_PARAMETER;
569 }
570 }
571 else
572 {
573 LogRelFlowFunc(("Error: Unknown CF_HTML format. Expected StartFragment. rc = %Rrc\n", rc));
574 rc = VERR_INVALID_PARAMETER;
575 }
576
577 return rc;
578}
579
580/**
581 * Converts source UTF-8 MIME HTML clipboard data to UTF-8 CF_HTML format.
582 *
583 * This is just encapsulation work, slapping a header on the data.
584 *
585 * It allocates [..]
586 *
587 * Calculations:
588 * Header length = format Length + (2*(10 - 5('%010d'))('digits')) - 2('%s') = format length + 8
589 * EndHtml = Header length + fragment length
590 * StartHtml = 105(constant)
591 * StartFragment = 141(constant) may vary if the header html content will be extended
592 * EndFragment = Header length + fragment length - 38(ending length)
593 *
594 * For more format details, check out:
595 * https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/aa767917(v=vs.85)
596 *
597 * @returns VBox status code.
598 * @param pszSource Source buffer that contains utf-16 string in mime html format
599 * @param cb Size of source buffer in bytes
600 * @param ppszOutput Where to return the allocated output buffer to put converted UTF-8
601 * CF_HTML clipboard data. This function allocates memory for this.
602 * @param pcbOutput Where to return the size of allocated result buffer in bytes/chars, including zero terminator
603 *
604 * @note output buffer should be free using RTMemFree()
605 * @note Everything inside of fragment can be UTF8. Windows allows it. Everything in header should be Latin1.
606 */
607int SharedClipboardWinConvertMIMEToCFHTML(const char *pszSource, size_t cb, char **ppszOutput, uint32_t *pcbOutput)
608{
609 Assert(ppszOutput);
610 Assert(pcbOutput);
611 Assert(pszSource);
612 Assert(cb);
613
614 /*
615 * Check that input UTF-8 and properly zero terminated.
616 * Note! The zero termination may come earlier than 'cb' - 1, that's fine.
617 */
618 int rc = RTStrValidateEncodingEx(pszSource, cb, RTSTR_VALIDATE_ENCODING_ZERO_TERMINATED);
619 if (RT_SUCCESS(rc))
620 { /* likely */ }
621 else
622 {
623 LogRelFlowFunc(("Error: invalid source fragment. rc = %Rrc\n", rc));
624 return rc;
625 }
626 size_t const cchFragment = strlen(pszSource); /* Unfortunately the validator doesn't return the length. */
627
628 /*
629 * @StartHtml - Absolute offset of <html>
630 * @EndHtml - Size of the whole resulting text (excluding ending zero char)
631 * @StartFragment - Absolute position after <!--StartFragment-->
632 * @EndFragment - Absolute position of <!--EndFragment-->
633 *
634 * Note! The offset are zero padded to max width so we don't have any variations due to those.
635 * Note! All values includes CRLFs inserted into text.
636 *
637 * Calculations:
638 * Header length = Format sample length - 2 ('%s')
639 * EndHtml = Header length + fragment length
640 * StartHtml = 101(constant)
641 * StartFragment = 137(constant)
642 * EndFragment = Header length + fragment length - 38 (ending length)
643 */
644 static const char s_szFormatSample[] =
645 /* 0: */ "Version:1.0\r\n"
646 /* 13: */ "StartHTML:000000101\r\n"
647 /* 34: */ "EndHTML:%0000009u\r\n" // END HTML = Header length + fragment length
648 /* 53: */ "StartFragment:000000137\r\n"
649 /* 78: */ "EndFragment:%0000009u\r\n"
650 /* 101: */ "<html>\r\n"
651 /* 109: */ "<body>\r\n"
652 /* 117: */ "<!--StartFragment-->"
653 /* 137: */ "%s"
654 /* 137+2: */ "<!--EndFragment-->\r\n"
655 /* 157+2: */ "</body>\r\n"
656 /* 166+2: */ "</html>\r\n"
657 /* 175+2: */ ;
658 AssertCompile(sizeof(s_szFormatSample) == 175 + 2 + 1);
659
660 /* Calculate parameters of the CF_HTML header */
661 size_t const cchHeader = sizeof(s_szFormatSample) - 2 /*%s*/ - 1 /*'\0'*/;
662 size_t const offEndHtml = cchHeader + cchFragment;
663 size_t const offEndFragment = cchHeader + cchFragment - 38; /* 175-137 = 38 */
664 char *pszResult = (char *)RTMemAlloc(offEndHtml + 1);
665 AssertLogRelReturn(pszResult, VERR_NO_MEMORY);
666
667 /* Format resulting CF_HTML string: */
668 size_t cchFormatted = RTStrPrintf(pszResult, offEndHtml + 1, s_szFormatSample, offEndHtml, offEndFragment, pszSource);
669 Assert(offEndHtml == cchFormatted);
670
671#ifdef VBOX_STRICT
672 /*
673 * Check the calculations.
674 */
675
676 /* check 'StartFragment:' value */
677 static const char s_szStartFragment[] = "<!--StartFragment-->";
678 const char *pszRealStartFragment = RTStrStr(pszResult, s_szStartFragment);
679 Assert(&pszRealStartFragment[sizeof(s_szStartFragment) - 1] - pszResult == 137);
680
681 /* check 'EndFragment:' value */
682 static const char s_szEndFragment[] = "<!--EndFragment-->";
683 const char *pszRealEndFragment = RTStrStr(pszResult, s_szEndFragment);
684 Assert((size_t)(pszRealEndFragment - pszResult) == offEndFragment);
685#endif
686
687 *ppszOutput = pszResult;
688 *pcbOutput = (uint32_t)cchFormatted + 1;
689 Assert(*pcbOutput == cchFormatted + 1);
690
691 return VINF_SUCCESS;
692}
693
694/**
695 * Handles the WM_CHANGECBCHAIN code.
696 *
697 * @returns LRESULT
698 * @param pWinCtx Windows context to use.
699 * @param hWnd Window handle to use.
700 * @param msg Message ID to pass on.
701 * @param wParam wParam to pass on
702 * @param lParam lParam to pass on.
703 */
704LRESULT SharedClipboardWinHandleWMChangeCBChain(PSHCLWINCTX pWinCtx,
705 HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
706{
707 LRESULT lresultRc = 0;
708
709 LogFlowFuncEnter();
710
711 if (SharedClipboardWinIsNewAPI(&pWinCtx->newAPI))
712 {
713 lresultRc = DefWindowProc(hWnd, msg, wParam, lParam);
714 }
715 else /* Old API */
716 {
717 HWND hwndRemoved = (HWND)wParam;
718 HWND hwndNext = (HWND)lParam;
719
720 if (hwndRemoved == pWinCtx->hWndNextInChain)
721 {
722 /* The window that was next to our in the chain is being removed.
723 * Relink to the new next window.
724 */
725 pWinCtx->hWndNextInChain = hwndNext;
726 }
727 else
728 {
729 if (pWinCtx->hWndNextInChain)
730 {
731 /* Pass the message further. */
732 DWORD_PTR dwResult;
733 lresultRc = SendMessageTimeout(pWinCtx->hWndNextInChain, WM_CHANGECBCHAIN, wParam, lParam, 0,
734 SHCL_WIN_CBCHAIN_TIMEOUT_MS,
735 &dwResult);
736 if (!lresultRc)
737 lresultRc = (LRESULT)dwResult;
738 }
739 }
740 }
741
742 LogFlowFunc(("lresultRc=%ld\n", lresultRc));
743 return lresultRc;
744}
745
746/**
747 * Handles the WM_DESTROY code.
748 *
749 * @returns VBox status code.
750 * @param pWinCtx Windows context to use.
751 */
752int SharedClipboardWinHandleWMDestroy(PSHCLWINCTX pWinCtx)
753{
754 LogFlowFuncEnter();
755
756 int rc = VINF_SUCCESS;
757
758 /* MS recommends to remove from Clipboard chain in this callback. */
759 SharedClipboardWinChainRemove(pWinCtx);
760
761 if (pWinCtx->oldAPI.timerRefresh)
762 {
763 Assert(pWinCtx->hWnd);
764 KillTimer(pWinCtx->hWnd, 0);
765 }
766
767 LogFlowFuncLeaveRC(rc);
768 return rc;
769}
770
771/**
772 * Handles the WM_RENDERALLFORMATS message.
773 *
774 * @returns VBox status code.
775 * @param pWinCtx Windows context to use.
776 * @param hWnd Window handle to use.
777 */
778int SharedClipboardWinHandleWMRenderAllFormats(PSHCLWINCTX pWinCtx, HWND hWnd)
779{
780 RT_NOREF(pWinCtx);
781
782 LogFlowFuncEnter();
783
784 /* Do nothing. The clipboard formats will be unavailable now, because the
785 * windows is to be destroyed and therefore the guest side becomes inactive.
786 */
787 int rc = SharedClipboardWinOpen(hWnd);
788 if (RT_SUCCESS(rc))
789 {
790 SharedClipboardWinClear();
791 SharedClipboardWinClose();
792 }
793
794 LogFlowFuncLeaveRC(rc);
795 return rc;
796}
797
798/**
799 * Handles the WM_TIMER code, which is needed if we're running with the so-called "old" Windows clipboard API.
800 * Does nothing if we're running with the "new" Windows API.
801 *
802 * @returns VBox status code.
803 * @param pWinCtx Windows context to use.
804 */
805int SharedClipboardWinHandleWMTimer(PSHCLWINCTX pWinCtx)
806{
807 int rc = VINF_SUCCESS;
808
809 if (!SharedClipboardWinIsNewAPI(&pWinCtx->newAPI)) /* Only run when using the "old" Windows API. */
810 {
811 LogFlowFuncEnter();
812
813 HWND hViewer = GetClipboardViewer();
814
815 /* Re-register ourselves in the clipboard chain if our last ping
816 * timed out or there seems to be no valid chain. */
817 if (!hViewer || pWinCtx->oldAPI.fCBChainPingInProcess)
818 {
819 SharedClipboardWinChainRemove(pWinCtx);
820 SharedClipboardWinChainAdd(pWinCtx);
821 }
822
823 /* Start a new ping by passing a dummy WM_CHANGECBCHAIN to be
824 * processed by ourselves to the chain. */
825 pWinCtx->oldAPI.fCBChainPingInProcess = TRUE;
826
827 hViewer = GetClipboardViewer();
828 if (hViewer)
829 SendMessageCallback(hViewer, WM_CHANGECBCHAIN, (WPARAM)pWinCtx->hWndNextInChain, (LPARAM)pWinCtx->hWndNextInChain,
830 SharedClipboardWinChainPingProc, (ULONG_PTR)pWinCtx);
831 }
832
833 LogFlowFuncLeaveRC(rc);
834 return rc;
835}
836
837/**
838 * Announces a clipboard format to the Windows clipboard.
839 *
840 * The actual rendering (setting) of the clipboard data will be done later with
841 * a separate WM_RENDERFORMAT message.
842 *
843 * @returns VBox status code.
844 * @retval VERR_NOT_SUPPORTED if *all* format(s) is/are not supported / handled.
845 * @param pWinCtx Windows context to use.
846 * @param fFormats Clipboard format(s) to announce.
847 */
848static int sharedClipboardWinAnnounceFormats(PSHCLWINCTX pWinCtx, SHCLFORMATS fFormats)
849{
850 LogFunc(("fFormats=0x%x\n", fFormats));
851
852 /* Make sure that if VBOX_SHCL_FMT_URI_LIST is announced, we don't announced anything else.
853 * This otherwise this will trigger a WM_DRAWCLIPBOARD or friends, which will result in fun bugs coming up. */
854 AssertReturn(( !(fFormats & VBOX_SHCL_FMT_URI_LIST)
855 || (fFormats & VBOX_SHCL_FMT_URI_LIST) == VBOX_SHCL_FMT_URI_LIST), VERR_INVALID_PARAMETER);
856 /*
857 * Set the clipboard formats.
858 */
859 static struct
860 {
861 /** VBox format to handle. */
862 uint32_t fVBoxFormat;
863 /** Native Windows format to use.
864 * Set to 0 if unused / needs special handling. */
865 UINT uWinFormat;
866 /** Own registered format. Set to NULL if not used / applicable. */
867 const char *pszRegFormat;
868 const char *pszLog;
869 } s_aFormats[] =
870 {
871 { VBOX_SHCL_FMT_UNICODETEXT, CF_UNICODETEXT, NULL, "CF_UNICODETEXT" },
872#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
873 /* We don't announce anything here for an URI list to the Windows clipboard, as we later have to
874 * initialize our custom IDataObject and set it via OleSetClipboard(). */
875 { VBOX_SHCL_FMT_URI_LIST, 0, NULL, "SHCL_URI_LIST" },
876#endif
877 { VBOX_SHCL_FMT_BITMAP, CF_DIB, NULL, "CF_DIB" },
878 { VBOX_SHCL_FMT_HTML, 0, SHCL_WIN_REGFMT_HTML, "SHCL_WIN_REGFMT_HTML" }
879 };
880
881 unsigned cSuccessfullySet = 0;
882 SHCLFORMATS fFormatsLeft = fFormats;
883 int rc = VINF_SUCCESS;
884 for (uintptr_t i = 0; i < RT_ELEMENTS(s_aFormats) && fFormatsLeft != 0; i++)
885 {
886 if (fFormatsLeft & s_aFormats[i].fVBoxFormat)
887 {
888 LogRel2(("Shared Clipboard: Announcing format '%s' to clipboard\n", s_aFormats[i].pszLog));
889 fFormatsLeft &= ~s_aFormats[i].fVBoxFormat;
890
891 UINT uWinFormat = 0;
892 if (s_aFormats[i].pszRegFormat) /* See if we have (special) registered format. */
893 {
894 uWinFormat = RegisterClipboardFormat(s_aFormats[i].pszRegFormat);
895 AssertContinue(uWinFormat != 0);
896 }
897 else /* Native format. */
898 uWinFormat = s_aFormats[i].uWinFormat;
899
900 /* Any native format set? If not, just skip it (as successful). */
901 if (!uWinFormat)
902 {
903 cSuccessfullySet++;
904 continue;
905 }
906
907 /* Tell the clipboard we've got data upon a request. We check the
908 last error here as hClip will be NULL even on success (despite
909 what MSDN says). */
910 SetLastError(NO_ERROR);
911 HANDLE hClip = SetClipboardData(uWinFormat, NULL);
912 DWORD dwErr = GetLastError();
913 if (dwErr == NO_ERROR || hClip != NULL)
914 cSuccessfullySet++;
915 else
916 {
917 AssertMsgFailed(("%s/%u: %u\n", s_aFormats[i].pszLog, uWinFormat, dwErr));
918 rc = RTErrConvertFromWin32(dwErr);
919 }
920 }
921 }
922
923 /*
924 * Consider setting anything a success, converting any error into
925 * informational status. Unsupport error only happens if all formats
926 * were unsupported.
927 */
928 if (cSuccessfullySet > 0)
929 {
930 pWinCtx->hWndClipboardOwnerUs = GetClipboardOwner();
931 if (RT_FAILURE(rc))
932 rc = -rc;
933 }
934 else if (RT_SUCCESS(rc) && fFormatsLeft != 0)
935 {
936 LogRel(("Shared Clipboard: Unable to announce unsupported/invalid formats: %#x (%#x)\n", fFormatsLeft, fFormats));
937 rc = VERR_NOT_SUPPORTED;
938 }
939
940 LogFlowFuncLeaveRC(rc);
941 return rc;
942}
943
944/**
945 * Opens the clipboard, clears it, announces @a fFormats and closes it.
946 *
947 * The actual rendering (setting) of the clipboard data will be done later with
948 * a separate WM_RENDERFORMAT message.
949 *
950 * @returns VBox status code.
951 * @param pWinCtx Windows context to use.
952 * @param fFormats Clipboard format(s) to announce.
953 * @param hWnd The window handle to use as owner.
954 */
955int SharedClipboardWinClearAndAnnounceFormats(PSHCLWINCTX pWinCtx, SHCLFORMATS fFormats, HWND hWnd)
956{
957 int rc = SharedClipboardWinOpen(hWnd);
958 if (RT_SUCCESS(rc))
959 {
960 SharedClipboardWinClear();
961
962 rc = sharedClipboardWinAnnounceFormats(pWinCtx, fFormats);
963 Assert(pWinCtx->hWndClipboardOwnerUs == hWnd || pWinCtx->hWndClipboardOwnerUs == NULL);
964
965 SharedClipboardWinClose();
966 }
967 return rc;
968}
969
970/**
971 * Writes (places) clipboard data into the Windows clipboard.
972 *
973 * @returns VBox status code.
974 * @param cfFormat Windows clipboard format to write data for.
975 * @param pvData Pointer to actual clipboard data to write.
976 * @param cbData Size (in bytes) of actual clipboard data to write.
977 *
978 * @note ASSUMES that the clipboard has already been opened.
979 */
980int SharedClipboardWinDataWrite(UINT cfFormat, void *pvData, uint32_t cbData)
981{
982 AssertPtrReturn(pvData, VERR_INVALID_POINTER);
983 AssertReturn (cbData, VERR_INVALID_PARAMETER);
984
985 int rc = VINF_SUCCESS;
986
987 HANDLE hMem = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, cbData);
988
989 LogFlowFunc(("hMem=%p\n", hMem));
990
991 if (hMem)
992 {
993 void *pMem = GlobalLock(hMem);
994
995 LogFlowFunc(("pMem=%p, GlobalSize=%zu\n", pMem, GlobalSize(hMem)));
996
997 if (pMem)
998 {
999 LogFlowFunc(("Setting data\n"));
1000
1001 memcpy(pMem, pvData, cbData);
1002
1003 /* The memory must be unlocked before inserting to the Clipboard. */
1004 GlobalUnlock(hMem);
1005
1006 /* 'hMem' contains the host clipboard data.
1007 * size is 'cb' and format is 'format'.
1008 */
1009 HANDLE hClip = SetClipboardData(cfFormat, hMem);
1010
1011 LogFlowFunc(("hClip=%p\n", hClip));
1012
1013 if (hClip)
1014 {
1015 /* The hMem ownership has gone to the system. Nothing to do. */
1016 }
1017 else
1018 rc = RTErrConvertFromWin32(GetLastError());
1019 }
1020 else
1021 rc = VERR_ACCESS_DENIED;
1022
1023 GlobalFree(hMem);
1024 }
1025 else
1026 rc = RTErrConvertFromWin32(GetLastError());
1027
1028 if (RT_FAILURE(rc))
1029 LogFunc(("Setting clipboard data failed with %Rrc\n", rc));
1030
1031 LogFlowFuncLeaveRC(rc);
1032 return rc;
1033}
1034
1035#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
1036/**
1037 * Creates an Shared Clipboard transfer by announcing transfer data (via IDataObject) to Windows.
1038 *
1039 * This creates the necessary IDataObject + IStream implementations and initiates the actual transfers required for getting
1040 * the meta data. Whether or not the actual (file++) transfer(s) are happening is up to the user (at some point) later then.
1041 *
1042 * @returns VBox status code.
1043 * @param pWinCtx Windows context to use.
1044 * @param pCtx Shared Clipboard context to use.
1045 * Needed for the data object to communicate with the main window thread.
1046 * @param pCallbacks Callbacks table to use.
1047 */
1048int SharedClipboardWinTransferCreateAndSetDataObject(PSHCLWINCTX pWinCtx, PSHCLCONTEXT pCtx, PSHCLCALLBACKS pCallbacks)
1049{
1050 AssertPtrReturn(pWinCtx, VERR_INVALID_POINTER);
1051 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1052 AssertPtrReturn(pCallbacks, VERR_INVALID_POINTER);
1053
1054 /* Make sure to enter the critical section before setting the clipboard data, as otherwise WM_CLIPBOARDUPDATE
1055 * might get called *before* we had the opportunity to set pWinCtx->hWndClipboardOwnerUs below. */
1056 int rc = RTCritSectEnter(&pWinCtx->CritSect);
1057 if (RT_SUCCESS(rc))
1058 {
1059 if (pWinCtx->pDataObjInFlight == NULL)
1060 {
1061 SharedClipboardWinDataObject *pObj = new SharedClipboardWinDataObject();
1062 if (pObj)
1063 {
1064 rc = pObj->Init(pCtx);
1065 if (RT_SUCCESS(rc))
1066 {
1067 if (RT_SUCCESS(rc))
1068 pObj->SetCallbacks(pCallbacks);
1069
1070 if (RT_SUCCESS(rc))
1071 pWinCtx->pDataObjInFlight = pObj;
1072 }
1073 }
1074 else
1075 rc = VERR_NO_MEMORY;
1076 }
1077
1078 if (RT_SUCCESS(rc))
1079 {
1080 SharedClipboardWinClose();
1081 /* Note: Clipboard must be closed first before calling OleSetClipboard(). */
1082
1083 /** @todo There is a potential race between SharedClipboardWinClose() and OleSetClipboard(),
1084 * where another application could own the clipboard (open), and thus the call to
1085 * OleSetClipboard() will fail. Needs (better) fixing. */
1086 HRESULT hr = S_OK;
1087
1088 for (unsigned uTries = 0; uTries < 3; uTries++)
1089 {
1090 hr = OleSetClipboard(pWinCtx->pDataObjInFlight);
1091 if (SUCCEEDED(hr))
1092 {
1093 Assert(OleIsCurrentClipboard(pWinCtx->pDataObjInFlight) == S_OK); /* Sanity. */
1094
1095 /*
1096 * Calling OleSetClipboard() changed the clipboard owner, which in turn will let us receive
1097 * a WM_CLIPBOARDUPDATE message. To not confuse ourselves with our own clipboard owner changes,
1098 * save a new window handle and deal with it in WM_CLIPBOARDUPDATE.
1099 */
1100 pWinCtx->hWndClipboardOwnerUs = GetClipboardOwner();
1101
1102 LogFlowFunc(("hWndClipboardOwnerUs=%p\n", pWinCtx->hWndClipboardOwnerUs));
1103 break;
1104 }
1105
1106 LogFlowFunc(("Failed with %Rhrc (try %u/3)\n", hr, uTries + 1));
1107 RTThreadSleep(500); /* Wait a bit. */
1108 }
1109
1110 if (FAILED(hr))
1111 {
1112 rc = VERR_ACCESS_DENIED; /** @todo Fudge; fix this. */
1113 LogRel(("Shared Clipboard: Failed with %Rhrc when setting data object to clipboard\n", hr));
1114 }
1115 }
1116
1117 int rc2 = RTCritSectLeave(&pWinCtx->CritSect);
1118 AssertRC(rc2);
1119 }
1120
1121 LogFlowFuncLeaveRC(rc);
1122 return rc;
1123}
1124
1125#if 0
1126/**
1127 * Creates an Shared Clipboard transfer by announcing transfer data (via IDataObject) to Windows.
1128 *
1129 * This creates the necessary IDataObject + IStream implementations and initiates the actual transfers required for getting
1130 * the meta data. Whether or not the actual (file++) transfer(s) are happening is up to the user (at some point) later then.
1131 *
1132 * @returns VBox status code.
1133 * @param pWinCtx Windows context to use.
1134 * @param pTransferCtxCtx Transfer contextto use.
1135 * @param pTransfer Shared Clipboard transfer to use.
1136 */
1137int SharedClipboardWinTransferCreate(PSHCLWINCTX pWinCtx, PSHCLTRANSFER pTransfer)
1138{
1139 AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
1140
1141 LogFlowFunc(("pWinCtx=%p\n", pWinCtx));
1142
1143 AssertReturn(pTransfer->pvUser == NULL, VERR_WRONG_ORDER);
1144
1145 /* Make sure to enter the critical section before setting the clipboard data, as otherwise WM_CLIPBOARDUPDATE
1146 * might get called *before* we had the opportunity to set pWinCtx->hWndClipboardOwnerUs below. */
1147 int rc = RTCritSectEnter(&pWinCtx->CritSect);
1148 if (RT_SUCCESS(rc))
1149 {
1150 SharedClipboardWinTransferCtx *pWinURITransferCtx = new SharedClipboardWinTransferCtx();
1151 if (pWinURITransferCtx)
1152 {
1153 pTransfer->pvUser = pWinURITransferCtx;
1154 pTransfer->cbUser = sizeof(SharedClipboardWinTransferCtx);
1155
1156 pWinURITransferCtx->pDataObj = new SharedClipboardWinDataObject();
1157 if (pWinURITransferCtx->pDataObj)
1158 {
1159 rc = pWinURITransferCtx->pDataObj->Init(pCtx);
1160 if (RT_SUCCESS(rc))
1161 {
1162 SharedClipboardWinClose();
1163 /* Note: Clipboard must be closed first before calling OleSetClipboard(). */
1164
1165 /** @todo There is a potential race between SharedClipboardWinClose() and OleSetClipboard(),
1166 * where another application could own the clipboard (open), and thus the call to
1167 * OleSetClipboard() will fail. Needs (better) fixing. */
1168 HRESULT hr = S_OK;
1169
1170 for (unsigned uTries = 0; uTries < 3; uTries++)
1171 {
1172 hr = OleSetClipboard(pWinURITransferCtx->pDataObj);
1173 if (SUCCEEDED(hr))
1174 {
1175 Assert(OleIsCurrentClipboard(pWinURITransferCtx->pDataObj) == S_OK); /* Sanity. */
1176
1177 /*
1178 * Calling OleSetClipboard() changed the clipboard owner, which in turn will let us receive
1179 * a WM_CLIPBOARDUPDATE message. To not confuse ourselves with our own clipboard owner changes,
1180 * save a new window handle and deal with it in WM_CLIPBOARDUPDATE.
1181 */
1182 pWinCtx->hWndClipboardOwnerUs = GetClipboardOwner();
1183
1184 LogFlowFunc(("hWndClipboardOwnerUs=%p\n", pWinCtx->hWndClipboardOwnerUs));
1185 break;
1186 }
1187
1188 LogFlowFunc(("Failed with %Rhrc (try %u/3)\n", hr, uTries + 1));
1189 RTThreadSleep(500); /* Wait a bit. */
1190 }
1191
1192 if (FAILED(hr))
1193 {
1194 rc = VERR_ACCESS_DENIED; /** @todo Fudge; fix this. */
1195 LogRel(("Shared Clipboard: Failed with %Rhrc when setting data object to clipboard\n", hr));
1196 }
1197 }
1198 }
1199 else
1200 rc = VERR_NO_MEMORY;
1201 }
1202 else
1203 rc = VERR_NO_MEMORY;
1204
1205 int rc2 = RTCritSectLeave(&pWinCtx->CritSect);
1206 AssertRC(rc2);
1207 }
1208
1209 LogFlowFuncLeaveRC(rc);
1210 return rc;
1211}
1212#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */
1213
1214/**
1215 * Destroys implementation-specific data for an Shared Clipboard transfer.
1216 *
1217 * @param pWinCtx Windows context to use.
1218 * @param pTransfer Shared Clipboard transfer to create implementation-specific data for.
1219 */
1220void SharedClipboardWinTransferDestroy(PSHCLWINCTX pWinCtx, PSHCLTRANSFER pTransfer)
1221{
1222 RT_NOREF(pWinCtx);
1223
1224 if (!pTransfer)
1225 return;
1226
1227 LogFlowFuncEnter();
1228
1229 if (pTransfer->pvUser)
1230 {
1231 Assert(pTransfer->cbUser == sizeof(SharedClipboardWinTransferCtx));
1232 SharedClipboardWinTransferCtx *pWinURITransferCtx = (SharedClipboardWinTransferCtx *)pTransfer->pvUser;
1233 Assert(pWinURITransferCtx);
1234
1235 if (pWinURITransferCtx->pDataObj)
1236 {
1237 delete pWinURITransferCtx->pDataObj;
1238 pWinURITransferCtx->pDataObj = NULL;
1239 }
1240
1241 delete pWinURITransferCtx;
1242
1243 pTransfer->pvUser = NULL;
1244 pTransfer->cbUser = 0;
1245 }
1246}
1247
1248/**
1249 * Retrieves the roots for a transfer by opening the clipboard and getting the clipboard data
1250 * as string list (CF_HDROP), assigning it to the transfer as roots then.
1251 *
1252 * @returns VBox status code.
1253 * @param pWinCtx Windows context to use.
1254 * @param pTransfer Transfer to get roots for.
1255 */
1256int SharedClipboardWinTransferGetRootsFromClipboard(PSHCLWINCTX pWinCtx, PSHCLTRANSFER pTransfer)
1257{
1258 AssertPtrReturn(pWinCtx, VERR_INVALID_POINTER);
1259 AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
1260
1261 Assert(ShClTransferGetSource(pTransfer) == SHCLSOURCE_LOCAL); /* Sanity. */
1262
1263 int rc = SharedClipboardWinOpen(pWinCtx->hWnd);
1264 if (RT_SUCCESS(rc))
1265 {
1266 /* The data data in CF_HDROP format, as the files are locally present and don't need to be
1267 * presented as a IDataObject or IStream. */
1268 HANDLE hClip = hClip = GetClipboardData(CF_HDROP);
1269 if (hClip)
1270 {
1271 HDROP hDrop = (HDROP)GlobalLock(hClip);
1272 if (hDrop)
1273 {
1274 char *pszList = NULL;
1275 uint32_t cbList;
1276 rc = SharedClipboardWinTransferDropFilesToStringList((DROPFILES *)hDrop, &pszList, &cbList);
1277
1278 GlobalUnlock(hClip);
1279
1280 if (RT_SUCCESS(rc))
1281 {
1282 rc = ShClTransferRootsInitFromStringList(pTransfer, pszList, cbList);
1283 RTStrFree(pszList);
1284 }
1285 }
1286 else
1287 LogRel(("Shared Clipboard: Unable to lock clipboard data, last error: %ld\n", GetLastError()));
1288 }
1289 else
1290 LogRel(("Shared Clipboard: Unable to retrieve clipboard data from clipboard (CF_HDROP), last error: %ld\n",
1291 GetLastError()));
1292
1293 SharedClipboardWinClose();
1294 }
1295
1296 LogFlowFuncLeaveRC(rc);
1297 return rc;
1298}
1299
1300/**
1301 * Converts a DROPFILES (HDROP) structure to a string list, separated by SHCL_TRANSFER_URI_LIST_SEP_STR.
1302 * Does not do any locking on the input data.
1303 *
1304 * @returns VBox status code.
1305 * @param pDropFiles Pointer to DROPFILES structure to convert.
1306 * @param ppszList Where to store the allocated string list on success.
1307 * Needs to be free'd with RTStrFree().
1308 * @param pcbList Where to store the size (in bytes) of the allocated string list.
1309 * Includes zero terminator.
1310 */
1311int SharedClipboardWinTransferDropFilesToStringList(DROPFILES *pDropFiles, char **ppszList, uint32_t *pcbList)
1312{
1313 AssertPtrReturn(pDropFiles, VERR_INVALID_POINTER);
1314 AssertPtrReturn(ppszList, VERR_INVALID_POINTER);
1315 AssertPtrReturn(pcbList, VERR_INVALID_POINTER);
1316
1317 /* Do we need to do Unicode stuff? */
1318 const bool fUnicode = RT_BOOL(pDropFiles->fWide);
1319
1320 /* Get the offset of the file list. */
1321 Assert(pDropFiles->pFiles >= sizeof(DROPFILES));
1322
1323 /* Note: This is *not* pDropFiles->pFiles! DragQueryFile only
1324 * will work with the plain storage medium pointer! */
1325 HDROP hDrop = (HDROP)(pDropFiles);
1326
1327 int rc = VINF_SUCCESS;
1328
1329 /* First, get the file count. */
1330 /** @todo Does this work on Windows 2000 / NT4? */
1331 char *pszFiles = NULL;
1332 uint32_t cchFiles = 0;
1333 UINT cFiles = DragQueryFile(hDrop, UINT32_MAX /* iFile */, NULL /* lpszFile */, 0 /* cchFile */);
1334
1335 LogFlowFunc(("Got %RU16 file(s), fUnicode=%RTbool\n", cFiles, fUnicode));
1336
1337 for (UINT i = 0; i < cFiles; i++)
1338 {
1339 UINT cchFile = DragQueryFile(hDrop, i /* File index */, NULL /* Query size first */, 0 /* cchFile */);
1340 Assert(cchFile);
1341
1342 if (RT_FAILURE(rc))
1343 break;
1344
1345 char *pszFileUtf8 = NULL; /* UTF-8 version. */
1346 UINT cchFileUtf8 = 0;
1347 if (fUnicode)
1348 {
1349 /* Allocate enough space (including terminator). */
1350 WCHAR *pwszFile = (WCHAR *)RTMemAlloc((cchFile + 1) * sizeof(WCHAR));
1351 if (pwszFile)
1352 {
1353 const UINT cwcFileUtf16 = DragQueryFileW(hDrop, i /* File index */,
1354 pwszFile, cchFile + 1 /* Include terminator */);
1355
1356 AssertMsg(cwcFileUtf16 == cchFile, ("cchFileUtf16 (%RU16) does not match cchFile (%RU16)\n",
1357 cwcFileUtf16, cchFile));
1358 RT_NOREF(cwcFileUtf16);
1359
1360 rc = RTUtf16ToUtf8(pwszFile, &pszFileUtf8);
1361 if (RT_SUCCESS(rc))
1362 {
1363 cchFileUtf8 = (UINT)strlen(pszFileUtf8);
1364 Assert(cchFileUtf8);
1365 }
1366
1367 RTMemFree(pwszFile);
1368 }
1369 else
1370 rc = VERR_NO_MEMORY;
1371 }
1372 else /* ANSI */
1373 {
1374 /* Allocate enough space (including terminator). */
1375 char *pszFileANSI = (char *)RTMemAlloc((cchFile + 1) * sizeof(char));
1376 UINT cchFileANSI = 0;
1377 if (pszFileANSI)
1378 {
1379 cchFileANSI = DragQueryFileA(hDrop, i /* File index */,
1380 pszFileANSI, cchFile + 1 /* Include terminator */);
1381
1382 AssertMsg(cchFileANSI == cchFile, ("cchFileANSI (%RU16) does not match cchFile (%RU16)\n",
1383 cchFileANSI, cchFile));
1384
1385 /* Convert the ANSI codepage to UTF-8. */
1386 rc = RTStrCurrentCPToUtf8(&pszFileUtf8, pszFileANSI);
1387 if (RT_SUCCESS(rc))
1388 {
1389 cchFileUtf8 = (UINT)strlen(pszFileUtf8);
1390 }
1391 }
1392 else
1393 rc = VERR_NO_MEMORY;
1394 }
1395
1396 if (RT_SUCCESS(rc))
1397 {
1398 LogFlowFunc(("\tFile: %s (cchFile=%RU16)\n", pszFileUtf8, cchFileUtf8));
1399
1400 LogRel2(("Shared Clipboard: Adding file '%s' to transfer\n", pszFileUtf8));
1401
1402 rc = RTStrAAppendExN(&pszFiles, 1 /* cPairs */, pszFileUtf8, strlen(pszFileUtf8));
1403 cchFiles += (uint32_t)strlen(pszFileUtf8);
1404 }
1405
1406 if (pszFileUtf8)
1407 RTStrFree(pszFileUtf8);
1408
1409 if (RT_FAILURE(rc))
1410 {
1411 LogFunc(("Error handling file entry #%u, rc=%Rrc\n", i, rc));
1412 break;
1413 }
1414
1415 /* Add separation between filenames.
1416 * Note: Also do this for the last element of the list. */
1417 rc = RTStrAAppendExN(&pszFiles, 1 /* cPairs */, SHCL_TRANSFER_URI_LIST_SEP_STR, 2 /* Bytes */);
1418 if (RT_SUCCESS(rc))
1419 cchFiles += 2; /* Include SHCL_TRANSFER_URI_LIST_SEP_STR */
1420 }
1421
1422 if (RT_SUCCESS(rc))
1423 {
1424 cchFiles += 1; /* Add string termination. */
1425 uint32_t cbFiles = cchFiles * sizeof(char); /* UTF-8. */
1426
1427 LogFlowFunc(("cFiles=%u, cchFiles=%RU32, cbFiles=%RU32, pszFiles=0x%p\n",
1428 cFiles, cchFiles, cbFiles, pszFiles));
1429
1430 *ppszList = pszFiles;
1431 *pcbList = cbFiles;
1432 }
1433 else
1434 {
1435 if (pszFiles)
1436 RTStrFree(pszFiles);
1437 }
1438
1439 LogFlowFuncLeaveRC(rc);
1440 return rc;
1441}
1442#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */
1443
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