VirtualBox

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

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