VirtualBox

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

Last change on this file since 107044 was 106061, checked in by vboxsync, 2 months ago

Copyright year updates by scm.

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