VirtualBox

source: vbox/trunk/src/VBox/HostServices/SharedClipboard/VBoxClipboard-win.cpp@ 74950

Last change on this file since 74950 was 69500, checked in by vboxsync, 7 years ago

*: scm --update-copyright-year

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 37.8 KB
Line 
1/** @file
2 * Shared Clipboard: Win32 host.
3 */
4
5/*
6 * Copyright (C) 2006-2017 Oracle Corporation
7 *
8 * This file is part of VirtualBox Open Source Edition (OSE), as
9 * available from http://www.virtualbox.org. This file is free software;
10 * you can redistribute it and/or modify it under the terms of the GNU
11 * General Public License (GPL) as published by the Free Software
12 * Foundation, in version 2 as it comes in the "COPYING" file of the
13 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
14 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
15 */
16
17#include <iprt/win/windows.h>
18
19#include <VBox/HostServices/VBoxClipboardSvc.h>
20
21#include <iprt/alloc.h>
22#include <iprt/string.h>
23#include <iprt/asm.h>
24#include <iprt/assert.h>
25#include <iprt/thread.h>
26#include <iprt/ldr.h>
27#include <process.h>
28
29#include "VBoxClipboard.h"
30
31#define dprintf Log
32
33static char gachWindowClassName[] = "VBoxSharedClipboardClass";
34
35enum { CBCHAIN_TIMEOUT = 5000 /* ms */ };
36
37/* Dynamically load clipboard functions from User32.dll. */
38typedef BOOL WINAPI FNADDCLIPBOARDFORMATLISTENER(HWND);
39typedef FNADDCLIPBOARDFORMATLISTENER *PFNADDCLIPBOARDFORMATLISTENER;
40
41typedef BOOL WINAPI FNREMOVECLIPBOARDFORMATLISTENER(HWND);
42typedef FNREMOVECLIPBOARDFORMATLISTENER *PFNREMOVECLIPBOARDFORMATLISTENER;
43
44
45/*********************************************************************************************************************************
46* Internal Functions *
47*********************************************************************************************************************************/
48static int ConvertCFHtmlToMime(const char *pszSource, const uint32_t cch, char **ppszOutput, uint32_t *pch);
49static int ConvertMimeToCFHTML(const char *pszSource, size_t cb, char **ppszOutput, uint32_t *pcbOutput);
50static bool IsWindowsHTML(const char *source);
51
52
53#ifndef WM_CLIPBOARDUPDATE
54#define WM_CLIPBOARDUPDATE 0x031D
55#endif
56
57struct _VBOXCLIPBOARDCONTEXT
58{
59 HWND hwnd;
60 HWND hwndNextInChain;
61
62 UINT timerRefresh;
63
64 bool fCBChainPingInProcess;
65
66 RTTHREAD thread;
67
68 HANDLE hRenderEvent;
69
70 VBOXCLIPBOARDCLIENTDATA *pClient;
71
72 PFNADDCLIPBOARDFORMATLISTENER pfnAddClipboardFormatListener;
73 PFNREMOVECLIPBOARDFORMATLISTENER pfnRemoveClipboardFormatListener;
74
75};
76
77/* Only one client is supported. There seems to be no need for more clients. */
78static VBOXCLIPBOARDCONTEXT g_ctx;
79
80
81#ifdef LOG_ENABLED
82void vboxClipboardDump(const void *pv, size_t cb, uint32_t u32Format)
83{
84 if (u32Format & VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT)
85 {
86 Log(("DUMP: VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT:\n"));
87 if (pv && cb)
88 Log(("%ls\n", pv));
89 else
90 Log(("%p %d\n", pv, cb));
91 }
92 else if (u32Format & VBOX_SHARED_CLIPBOARD_FMT_BITMAP)
93 dprintf(("DUMP: VBOX_SHARED_CLIPBOARD_FMT_BITMAP\n"));
94 else if (u32Format & VBOX_SHARED_CLIPBOARD_FMT_HTML)
95 {
96 Log(("DUMP: VBOX_SHARED_CLIPBOARD_FMT_HTML:\n"));
97 if (pv && cb)
98 {
99 Log(("%s\n", pv));
100
101 //size_t cb = RTStrNLen(pv, );
102 char *pszBuf = (char *)RTMemAllocZ(cb + 1);
103 RTStrCopy(pszBuf, cb + 1, (const char *)pv);
104 for (size_t off = 0; off < cb; ++off)
105 {
106 if (pszBuf[off] == '\n' || pszBuf[off] == '\r')
107 pszBuf[off] = ' ';
108 }
109
110 Log(("%s\n", pszBuf));
111 RTMemFree(pszBuf);
112 }
113 else
114 Log(("%p %d\n", pv, cb));
115 }
116 else
117 dprintf(("DUMP: invalid format %02X\n", u32Format));
118}
119#else /* !LOG_ENABLED */
120# define vboxClipboardDump(__pv, __cb, __format) do { NOREF(__pv); NOREF(__cb); NOREF(__format); } while (0)
121#endif /* !LOG_ENABLED */
122
123
124static void vboxClipboardInitNewAPI(VBOXCLIPBOARDCONTEXT *pCtx)
125{
126 RTLDRMOD hUser32 = NIL_RTLDRMOD;
127 int rc = RTLdrLoadSystem("User32.dll", /* fNoUnload = */ true, &hUser32);
128 if (RT_SUCCESS(rc))
129 {
130 rc = RTLdrGetSymbol(hUser32, "AddClipboardFormatListener", (void**)&pCtx->pfnAddClipboardFormatListener);
131 if (RT_SUCCESS(rc))
132 {
133 rc = RTLdrGetSymbol(hUser32, "RemoveClipboardFormatListener", (void**)&pCtx->pfnRemoveClipboardFormatListener);
134 }
135
136 RTLdrClose(hUser32);
137 }
138
139 if (RT_SUCCESS(rc))
140 {
141 Log(("New Clipboard API is enabled\n"));
142 }
143 else
144 {
145 pCtx->pfnAddClipboardFormatListener = NULL;
146 pCtx->pfnRemoveClipboardFormatListener = NULL;
147 Log(("New Clipboard API is not available. rc = %Rrc\n", rc));
148 }
149}
150
151static bool vboxClipboardIsNewAPI(VBOXCLIPBOARDCONTEXT *pCtx)
152{
153 return pCtx->pfnAddClipboardFormatListener != NULL;
154}
155
156
157static int vboxOpenClipboard(HWND hwnd)
158{
159 /* "OpenClipboard fails if another window has the clipboard open."
160 * So try a few times and wait up to 1 second.
161 */
162 BOOL fOpened = FALSE;
163
164 int i = 0;
165 for (;;)
166 {
167 if (OpenClipboard(hwnd))
168 {
169 fOpened = TRUE;
170 break;
171 }
172
173 if (i >= 10) /* sleep interval = [1..512] ms */
174 break;
175
176 RTThreadSleep(1 << i);
177 ++i;
178 }
179
180#ifdef LOG_ENABLED
181 if (i > 0)
182 LogFlowFunc(("%d times tried to open clipboard.\n", i + 1));
183#endif
184
185 int rc;
186 if (fOpened)
187 rc = VINF_SUCCESS;
188 else
189 {
190 const DWORD err = GetLastError();
191 LogFlowFunc(("error %d\n", err));
192 rc = RTErrConvertFromWin32(err);
193 }
194
195 return rc;
196}
197
198
199/** @todo Someone please explain the protocol wrt overflows... */
200static void vboxClipboardGetData (uint32_t u32Format, const void *pvSrc, uint32_t cbSrc,
201 void *pvDst, uint32_t cbDst, uint32_t *pcbActualDst)
202{
203 dprintf (("vboxClipboardGetData.\n"));
204
205 LogFlow(("vboxClipboardGetData cbSrc = %d, cbDst = %d\n", cbSrc, cbDst));
206
207 if ( u32Format == VBOX_SHARED_CLIPBOARD_FMT_HTML
208 && IsWindowsHTML((const char *)pvSrc))
209 {
210 /** @todo r=bird: Why the double conversion? */
211 char *pszBuf = NULL;
212 uint32_t cbBuf = 0;
213 int rc = ConvertCFHtmlToMime((const char*)pvSrc, cbSrc, &pszBuf, &cbBuf);
214 if (RT_SUCCESS(rc))
215 {
216 *pcbActualDst = cbBuf;
217 if (cbBuf > cbDst)
218 {
219 /* Do not copy data. The dst buffer is not enough. */
220 RTMemFree(pszBuf);
221 return;
222 }
223 memcpy(pvDst, pszBuf, cbBuf);
224 RTMemFree(pszBuf);
225 }
226 else
227 *pcbActualDst = 0;
228 }
229 else
230 {
231 *pcbActualDst = cbSrc;
232
233 if (cbSrc > cbDst)
234 {
235 /* Do not copy data. The dst buffer is not enough. */
236 return;
237 }
238
239 memcpy(pvDst, pvSrc, cbSrc);
240 }
241
242 vboxClipboardDump(pvDst, cbSrc, u32Format);
243
244 return;
245}
246
247static int vboxClipboardReadDataFromClient (VBOXCLIPBOARDCONTEXT *pCtx, uint32_t u32Format)
248{
249 Assert(pCtx->pClient);
250 Assert(pCtx->hRenderEvent);
251 Assert(pCtx->pClient->data.pv == NULL && pCtx->pClient->data.cb == 0 && pCtx->pClient->data.u32Format == 0);
252
253 LogFlow(("vboxClipboardReadDataFromClient u32Format = %02X\n", u32Format));
254
255 ResetEvent (pCtx->hRenderEvent);
256
257 vboxSvcClipboardReportMsg (pCtx->pClient, VBOX_SHARED_CLIPBOARD_HOST_MSG_READ_DATA, u32Format);
258
259 DWORD ret = WaitForSingleObject(pCtx->hRenderEvent, INFINITE);
260 LogFlow(("vboxClipboardReadDataFromClient wait completed, ret 0x%08X, err %d\n",
261 ret, GetLastError())); NOREF(ret);
262
263 return VINF_SUCCESS;
264}
265
266static void vboxClipboardChanged (VBOXCLIPBOARDCONTEXT *pCtx)
267{
268 LogFlow(("vboxClipboardChanged\n"));
269
270 if (pCtx->pClient == NULL)
271 {
272 return;
273 }
274
275 /* Query list of available formats and report to host. */
276 int rc = vboxOpenClipboard(pCtx->hwnd);
277 if (RT_SUCCESS(rc))
278 {
279 uint32_t u32Formats = 0;
280
281 UINT format = 0;
282
283 while ((format = EnumClipboardFormats (format)) != 0)
284 {
285 LogFlow(("vboxClipboardChanged format %#x\n", format));
286 switch (format)
287 {
288 case CF_UNICODETEXT:
289 case CF_TEXT:
290 u32Formats |= VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT;
291 break;
292
293 case CF_DIB:
294 case CF_BITMAP:
295 u32Formats |= VBOX_SHARED_CLIPBOARD_FMT_BITMAP;
296 break;
297
298 default:
299 if (format >= 0xC000)
300 {
301 TCHAR szFormatName[256];
302
303 int cActual = GetClipboardFormatName(format, szFormatName, sizeof(szFormatName)/sizeof (TCHAR));
304
305 if (cActual)
306 {
307 if (strcmp (szFormatName, "HTML Format") == 0)
308 {
309 u32Formats |= VBOX_SHARED_CLIPBOARD_FMT_HTML;
310 }
311 }
312 }
313 break;
314 }
315 }
316
317 CloseClipboard ();
318
319 LogFlow(("vboxClipboardChanged u32Formats %02X\n", u32Formats));
320
321 vboxSvcClipboardReportMsg (pCtx->pClient, VBOX_SHARED_CLIPBOARD_HOST_MSG_FORMATS, u32Formats);
322 }
323 else
324 {
325 LogFlow(("vboxClipboardChanged: error in open clipboard. hwnd: %x. err: %Rrc\n", pCtx->hwnd, rc));
326 }
327}
328
329/* Add ourselves into the chain of cliboard listeners */
330static void addToCBChain (VBOXCLIPBOARDCONTEXT *pCtx)
331{
332 if (vboxClipboardIsNewAPI(pCtx))
333 pCtx->pfnAddClipboardFormatListener(pCtx->hwnd);
334 else
335 pCtx->hwndNextInChain = SetClipboardViewer(pCtx->hwnd);
336}
337
338/* Remove ourselves from the chain of cliboard listeners */
339static void removeFromCBChain (VBOXCLIPBOARDCONTEXT *pCtx)
340{
341 if (vboxClipboardIsNewAPI(pCtx))
342 {
343 pCtx->pfnRemoveClipboardFormatListener(pCtx->hwnd);
344 }
345 else
346 {
347 ChangeClipboardChain(pCtx->hwnd, pCtx->hwndNextInChain);
348 pCtx->hwndNextInChain = NULL;
349 }
350}
351
352/* Callback which is invoked when we have successfully pinged ourselves down the
353 * clipboard chain. We simply unset a boolean flag to say that we are responding.
354 * There is a race if a ping returns after the next one is initiated, but nothing
355 * very bad is likely to happen. */
356VOID CALLBACK CBChainPingProc(HWND hwnd, UINT uMsg, ULONG_PTR dwData, LRESULT lResult)
357{
358 (void) hwnd;
359 (void) uMsg;
360 (void) lResult;
361 VBOXCLIPBOARDCONTEXT *pCtx = (VBOXCLIPBOARDCONTEXT *)dwData;
362 pCtx->fCBChainPingInProcess = FALSE;
363}
364
365static LRESULT CALLBACK vboxClipboardWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
366{
367 LRESULT rc = 0;
368
369 VBOXCLIPBOARDCONTEXT *pCtx = &g_ctx;
370
371 switch (msg)
372 {
373 case WM_CLIPBOARDUPDATE:
374 {
375 Log(("WM_CLIPBOARDUPDATE\n"));
376
377 if (GetClipboardOwner() != hwnd)
378 {
379 /* Clipboard was updated by another application. */
380 vboxClipboardChanged(pCtx);
381 }
382 } break;
383
384 case WM_CHANGECBCHAIN:
385 {
386 Log(("WM_CHANGECBCHAIN\n"));
387
388 if (vboxClipboardIsNewAPI(pCtx))
389 {
390 rc = DefWindowProc(hwnd, msg, wParam, lParam);
391 break;
392 }
393
394 HWND hwndRemoved = (HWND)wParam;
395 HWND hwndNext = (HWND)lParam;
396
397 if (hwndRemoved == pCtx->hwndNextInChain)
398 {
399 /* The window that was next to our in the chain is being removed.
400 * Relink to the new next window.
401 */
402 pCtx->hwndNextInChain = hwndNext;
403 }
404 else
405 {
406 if (pCtx->hwndNextInChain)
407 {
408 /* Pass the message further. */
409 DWORD_PTR dwResult;
410 rc = SendMessageTimeout(pCtx->hwndNextInChain, WM_CHANGECBCHAIN, wParam, lParam, 0, CBCHAIN_TIMEOUT, &dwResult);
411 if (!rc)
412 rc = (LRESULT)dwResult;
413 }
414 }
415 } break;
416
417 case WM_DRAWCLIPBOARD:
418 {
419 Log(("WM_DRAWCLIPBOARD\n"));
420
421 if (GetClipboardOwner () != hwnd)
422 {
423 /* Clipboard was updated by another application. */
424 vboxClipboardChanged (pCtx);
425 }
426
427 if (pCtx->hwndNextInChain)
428 {
429 Log(("WM_DRAWCLIPBOARD next %p\n", pCtx->hwndNextInChain));
430 /* Pass the message to next windows in the clipboard chain. */
431 DWORD_PTR dwResult;
432 rc = SendMessageTimeout(pCtx->hwndNextInChain, msg, wParam, lParam, 0, CBCHAIN_TIMEOUT, &dwResult);
433 if (!rc)
434 rc = dwResult;
435 }
436 } break;
437
438 case WM_TIMER:
439 {
440 if (vboxClipboardIsNewAPI(pCtx))
441 break;
442
443 HWND hViewer = GetClipboardViewer();
444
445 /* Re-register ourselves in the clipboard chain if our last ping
446 * timed out or there seems to be no valid chain. */
447 if (!hViewer || pCtx->fCBChainPingInProcess)
448 {
449 removeFromCBChain(pCtx);
450 addToCBChain(pCtx);
451 }
452 /* Start a new ping by passing a dummy WM_CHANGECBCHAIN to be
453 * processed by ourselves to the chain. */
454 pCtx->fCBChainPingInProcess = TRUE;
455 hViewer = GetClipboardViewer();
456 if (hViewer)
457 SendMessageCallback(hViewer, WM_CHANGECBCHAIN, (WPARAM)pCtx->hwndNextInChain, (LPARAM)pCtx->hwndNextInChain, CBChainPingProc, (ULONG_PTR) pCtx);
458 } break;
459
460 case WM_RENDERFORMAT:
461 {
462 /* Insert the requested clipboard format data into the clipboard. */
463 uint32_t u32Format = 0;
464
465 UINT format = (UINT)wParam;
466
467 Log(("WM_RENDERFORMAT %d\n", format));
468
469 switch (format)
470 {
471 case CF_UNICODETEXT:
472 u32Format |= VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT;
473 break;
474
475 case CF_DIB:
476 u32Format |= VBOX_SHARED_CLIPBOARD_FMT_BITMAP;
477 break;
478
479 default:
480 if (format >= 0xC000)
481 {
482 TCHAR szFormatName[256];
483
484 int cActual = GetClipboardFormatName(format, szFormatName, sizeof(szFormatName)/sizeof (TCHAR));
485
486 if (cActual)
487 {
488 if (strcmp (szFormatName, "HTML Format") == 0)
489 {
490 u32Format |= VBOX_SHARED_CLIPBOARD_FMT_HTML;
491 }
492 }
493 }
494 break;
495 }
496
497 if (u32Format == 0 || pCtx->pClient == NULL)
498 {
499 /* Unsupported clipboard format is requested. */
500 Log(("WM_RENDERFORMAT unsupported format requested or client is not active.\n"));
501 EmptyClipboard ();
502 }
503 else
504 {
505 int vboxrc = vboxClipboardReadDataFromClient (pCtx, u32Format);
506
507 dprintf(("vboxClipboardReadDataFromClient vboxrc = %d, pv %p, cb %d, u32Format %d\n",
508 vboxrc, pCtx->pClient->data.pv, pCtx->pClient->data.cb, pCtx->pClient->data.u32Format));
509
510 if ( RT_SUCCESS (vboxrc)
511 && pCtx->pClient->data.pv != NULL
512 && pCtx->pClient->data.cb > 0
513 && pCtx->pClient->data.u32Format == u32Format)
514 {
515 HANDLE hMem = GlobalAlloc (GMEM_DDESHARE | GMEM_MOVEABLE, pCtx->pClient->data.cb);
516
517 dprintf(("hMem %p\n", hMem));
518
519 if (hMem)
520 {
521 void *pMem = GlobalLock (hMem);
522
523 dprintf(("pMem %p, GlobalSize %d\n", pMem, GlobalSize (hMem)));
524
525 if (pMem)
526 {
527 Log(("WM_RENDERFORMAT setting data\n"));
528
529 if (pCtx->pClient->data.pv)
530 {
531 memcpy (pMem, pCtx->pClient->data.pv, pCtx->pClient->data.cb);
532
533 RTMemFree (pCtx->pClient->data.pv);
534 pCtx->pClient->data.pv = NULL;
535 }
536
537 pCtx->pClient->data.cb = 0;
538 pCtx->pClient->data.u32Format = 0;
539
540 /* The memory must be unlocked before inserting to the Clipboard. */
541 GlobalUnlock (hMem);
542
543 /* 'hMem' contains the host clipboard data.
544 * size is 'cb' and format is 'format'.
545 */
546 HANDLE hClip = SetClipboardData (format, hMem);
547
548 dprintf(("vboxClipboardHostEvent hClip %p\n", hClip));
549
550 if (hClip)
551 {
552 /* The hMem ownership has gone to the system. Nothing to do. */
553 break;
554 }
555 }
556
557 GlobalFree (hMem);
558 }
559 }
560
561 RTMemFree (pCtx->pClient->data.pv);
562 pCtx->pClient->data.pv = NULL;
563 pCtx->pClient->data.cb = 0;
564 pCtx->pClient->data.u32Format = 0;
565
566 /* Something went wrong. */
567 EmptyClipboard ();
568 }
569 } break;
570
571 case WM_RENDERALLFORMATS:
572 {
573 Log(("WM_RENDERALLFORMATS\n"));
574
575 /* Do nothing. The clipboard formats will be unavailable now, because the
576 * windows is to be destroyed and therefore the guest side becomes inactive.
577 */
578 int vboxrc = vboxOpenClipboard(hwnd);
579 if (RT_SUCCESS(vboxrc))
580 {
581 EmptyClipboard();
582
583 CloseClipboard();
584 }
585 else
586 {
587 LogFlow(("vboxClipboardWndProc: WM_RENDERALLFORMATS: error in open clipboard. hwnd: %x, rc: %Rrc\n", hwnd, vboxrc));
588 }
589 } break;
590
591 case WM_USER:
592 {
593 if (pCtx->pClient == NULL || pCtx->pClient->fMsgFormats)
594 {
595 /* Host has pending formats message. Ignore the guest announcement,
596 * because host clipboard has more priority.
597 */
598 Log(("WM_USER ignored\n"));
599 break;
600 }
601
602 /* Announce available formats. Do not insert data, they will be inserted in WM_RENDER*. */
603 uint32_t u32Formats = (uint32_t)lParam;
604
605 Log(("WM_USER u32Formats = %02X\n", u32Formats));
606
607 int vboxrc = vboxOpenClipboard(hwnd);
608 if (RT_SUCCESS(vboxrc))
609 {
610 EmptyClipboard();
611
612 Log(("WM_USER emptied clipboard\n"));
613
614 HANDLE hClip = NULL;
615
616 if (u32Formats & VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT)
617 {
618 dprintf(("window proc WM_USER: VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT\n"));
619
620 hClip = SetClipboardData (CF_UNICODETEXT, NULL);
621 }
622
623 if (u32Formats & VBOX_SHARED_CLIPBOARD_FMT_BITMAP)
624 {
625 dprintf(("window proc WM_USER: VBOX_SHARED_CLIPBOARD_FMT_BITMAP\n"));
626
627 hClip = SetClipboardData (CF_DIB, NULL);
628 }
629
630 if (u32Formats & VBOX_SHARED_CLIPBOARD_FMT_HTML)
631 {
632 UINT format = RegisterClipboardFormat ("HTML Format");
633 dprintf(("window proc WM_USER: VBOX_SHARED_CLIPBOARD_FMT_HTML 0x%04X\n", format));
634 if (format != 0)
635 {
636 hClip = SetClipboardData (format, NULL);
637 }
638 }
639
640 CloseClipboard();
641
642 dprintf(("window proc WM_USER: hClip %p, err %d\n", hClip, GetLastError ()));
643 }
644 else
645 {
646 dprintf(("window proc WM_USER: failed to open clipboard. rc: %Rrc\n", vboxrc));
647 }
648 } break;
649
650 case WM_DESTROY:
651 {
652 /* MS recommends to remove from Clipboard chain in this callback */
653 Assert(pCtx->hwnd);
654 removeFromCBChain(pCtx);
655 if (pCtx->timerRefresh)
656 KillTimer(pCtx->hwnd, 0);
657 PostQuitMessage(0);
658 } break;
659
660 default:
661 {
662 Log(("WM_ %p\n", msg));
663 rc = DefWindowProc(hwnd, msg, wParam, lParam);
664 }
665 }
666
667 Log(("WM_ rc %d\n", rc));
668 return rc;
669}
670
671DECLCALLBACK(int) VBoxClipboardThread (RTTHREAD hThreadSelf, void *pvUser)
672{
673 RT_NOREF2(hThreadSelf, pvUser);
674 /* Create a window and make it a clipboard viewer. */
675 int rc = VINF_SUCCESS;
676
677 LogFlow(("VBoxClipboardThread\n"));
678
679 VBOXCLIPBOARDCONTEXT *pCtx = &g_ctx;
680
681 HINSTANCE hInstance = (HINSTANCE)GetModuleHandle(NULL);
682
683 /* Register the Window Class. */
684 WNDCLASS wc;
685 RT_ZERO(wc);
686
687 wc.style = CS_NOCLOSE;
688 wc.lpfnWndProc = vboxClipboardWndProc;
689 wc.hInstance = hInstance;
690 wc.hbrBackground = (HBRUSH)(COLOR_BACKGROUND + 1);
691 wc.lpszClassName = gachWindowClassName;
692
693 ATOM atomWindowClass = RegisterClass(&wc);
694
695 if (atomWindowClass == 0)
696 {
697 Log(("Failed to register window class\n"));
698 rc = VERR_NOT_SUPPORTED;
699 }
700 else
701 {
702 /* Create the window. */
703 pCtx->hwnd = CreateWindowEx (WS_EX_TOOLWINDOW | WS_EX_TRANSPARENT | WS_EX_TOPMOST,
704 gachWindowClassName, gachWindowClassName,
705 WS_POPUPWINDOW,
706 -200, -200, 100, 100, NULL, NULL, hInstance, NULL);
707
708 if (pCtx->hwnd == NULL)
709 {
710 Log(("Failed to create window\n"));
711 rc = VERR_NOT_SUPPORTED;
712 }
713 else
714 {
715 SetWindowPos(pCtx->hwnd, HWND_TOPMOST, -200, -200, 0, 0,
716 SWP_NOACTIVATE | SWP_HIDEWINDOW | SWP_NOCOPYBITS | SWP_NOREDRAW | SWP_NOSIZE);
717
718 addToCBChain(pCtx);
719 if (!vboxClipboardIsNewAPI(pCtx))
720 pCtx->timerRefresh = SetTimer(pCtx->hwnd, 0, 10 * 1000, NULL);
721
722 MSG msg;
723 BOOL msgret = 0;
724 while ((msgret = GetMessage(&msg, NULL, 0, 0)) > 0)
725 {
726 TranslateMessage(&msg);
727 DispatchMessage(&msg);
728 }
729 /*
730 * Window procedure can return error,
731 * but this is exceptional situation
732 * that should be identified in testing
733 */
734 Assert(msgret >= 0);
735 Log(("VBoxClipboardThread Message loop finished. GetMessage returned %d, message id: %d \n", msgret, msg.message));
736 }
737 }
738
739 pCtx->hwnd = NULL;
740
741 if (atomWindowClass != 0)
742 {
743 UnregisterClass (gachWindowClassName, hInstance);
744 atomWindowClass = 0;
745 }
746
747 return 0;
748}
749
750/*
751 * Public platform dependent functions.
752 */
753int vboxClipboardInit (void)
754{
755 int rc = VINF_SUCCESS;
756
757 RT_ZERO(g_ctx);
758
759 /* Check that new Clipboard API is available */
760 vboxClipboardInitNewAPI(&g_ctx);
761
762 g_ctx.hRenderEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
763
764 rc = RTThreadCreate (&g_ctx.thread, VBoxClipboardThread, NULL, 65536,
765 RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "SHCLIP");
766
767 if (RT_FAILURE (rc))
768 {
769 CloseHandle (g_ctx.hRenderEvent);
770 }
771
772 return rc;
773}
774
775void vboxClipboardDestroy (void)
776{
777 Log(("vboxClipboardDestroy\n"));
778
779 if (g_ctx.hwnd)
780 {
781 PostMessage (g_ctx.hwnd, WM_CLOSE, 0, 0);
782 }
783
784 CloseHandle (g_ctx.hRenderEvent);
785
786 /* Wait for the window thread to terminate. */
787 RTThreadWait (g_ctx.thread, RT_INDEFINITE_WAIT, NULL);
788
789 g_ctx.thread = NIL_RTTHREAD;
790}
791
792int vboxClipboardConnect (VBOXCLIPBOARDCLIENTDATA *pClient, bool fHeadless)
793{
794 NOREF(fHeadless);
795 Log(("vboxClipboardConnect\n"));
796
797 if (g_ctx.pClient != NULL)
798 {
799 /* One client only. */
800 return VERR_NOT_SUPPORTED;
801 }
802
803 pClient->pCtx = &g_ctx;
804
805 pClient->pCtx->pClient = pClient;
806
807 /* Sync the host clipboard content with the client. */
808 vboxClipboardSync (pClient);
809
810 return VINF_SUCCESS;
811}
812
813int vboxClipboardSync (VBOXCLIPBOARDCLIENTDATA *pClient)
814{
815 /* Sync the host clipboard content with the client. */
816 vboxClipboardChanged (pClient->pCtx);
817
818 return VINF_SUCCESS;
819}
820
821void vboxClipboardDisconnect (VBOXCLIPBOARDCLIENTDATA *pClient)
822{
823 RT_NOREF1(pClient);
824 Log(("vboxClipboardDisconnect\n"));
825
826 g_ctx.pClient = NULL;
827}
828
829void vboxClipboardFormatAnnounce (VBOXCLIPBOARDCLIENTDATA *pClient, uint32_t u32Formats)
830{
831 /*
832 * The guest announces formats. Forward to the window thread.
833 */
834 PostMessage (pClient->pCtx->hwnd, WM_USER, 0, u32Formats);
835}
836
837int DumpHtml(const char *pszSrc, size_t cb)
838{
839 size_t cchIgnored = 0;
840 int rc = RTStrNLenEx(pszSrc, cb, &cchIgnored);
841 if (RT_SUCCESS(rc))
842 {
843 char *pszBuf = (char *)RTMemAllocZ(cb + 1);
844 if (pszBuf != NULL)
845 {
846 rc = RTStrCopy(pszBuf, cb + 1, (const char *)pszSrc);
847 if (RT_SUCCESS(rc))
848 {
849 for (size_t i = 0; i < cb; ++i)
850 if (pszBuf[i] == '\n' || pszBuf[i] == '\r')
851 pszBuf[i] = ' ';
852 }
853 else
854 Log(("Error in copying string.\n"));
855 Log(("Removed \\r\\n: %s\n", pszBuf));
856 RTMemFree(pszBuf);
857 }
858 else
859 {
860 rc = VERR_NO_MEMORY;
861 Log(("Not enough memory to allocate buffer.\n"));
862 }
863 }
864 return rc;
865}
866
867int vboxClipboardReadData (VBOXCLIPBOARDCLIENTDATA *pClient, uint32_t u32Format, void *pv, uint32_t cb, uint32_t *pcbActual)
868{
869 LogFlow(("vboxClipboardReadData: u32Format = %02X\n", u32Format));
870
871 HANDLE hClip = NULL;
872
873 /*
874 * The guest wants to read data in the given format.
875 */
876 int rc = vboxOpenClipboard(pClient->pCtx->hwnd);
877 if (RT_SUCCESS(rc))
878 {
879 dprintf(("Clipboard opened.\n"));
880
881 if (u32Format & VBOX_SHARED_CLIPBOARD_FMT_BITMAP)
882 {
883 hClip = GetClipboardData (CF_DIB);
884
885 if (hClip != NULL)
886 {
887 LPVOID lp = GlobalLock (hClip);
888
889 if (lp != NULL)
890 {
891 dprintf(("CF_DIB\n"));
892
893 vboxClipboardGetData (VBOX_SHARED_CLIPBOARD_FMT_BITMAP, lp, GlobalSize (hClip),
894 pv, cb, pcbActual);
895
896 GlobalUnlock(hClip);
897 }
898 else
899 {
900 hClip = NULL;
901 }
902 }
903 }
904 else if (u32Format & VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT)
905 {
906 hClip = GetClipboardData(CF_UNICODETEXT);
907
908 if (hClip != NULL)
909 {
910 LPWSTR uniString = (LPWSTR)GlobalLock (hClip);
911
912 if (uniString != NULL)
913 {
914 dprintf(("CF_UNICODETEXT\n"));
915
916 vboxClipboardGetData (VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT, uniString, (lstrlenW (uniString) + 1) * 2,
917 pv, cb, pcbActual);
918
919 GlobalUnlock(hClip);
920 }
921 else
922 {
923 hClip = NULL;
924 }
925 }
926 }
927 else if (u32Format & VBOX_SHARED_CLIPBOARD_FMT_HTML)
928 {
929 UINT format = RegisterClipboardFormat ("HTML Format");
930
931 if (format != 0)
932 {
933 hClip = GetClipboardData (format);
934
935 if (hClip != NULL)
936 {
937 LPVOID lp = GlobalLock (hClip);
938
939 if (lp != NULL)
940 {
941 dprintf(("CF_HTML\n"));
942
943 vboxClipboardGetData (VBOX_SHARED_CLIPBOARD_FMT_HTML, lp, GlobalSize (hClip),
944 pv, cb, pcbActual);
945 LogRelFlowFunc(("Raw HTML clipboard data from host :"));
946 DumpHtml((char*)pv, cb);
947 GlobalUnlock(hClip);
948 }
949 else
950 {
951 hClip = NULL;
952 }
953 }
954 }
955 }
956
957 CloseClipboard ();
958 }
959 else
960 {
961 dprintf(("vboxClipboardReadData: failed to open clipboard, rc: %Rrc\n", rc));
962 }
963
964 if (hClip == NULL)
965 {
966 /* Reply with empty data. */
967 vboxClipboardGetData (0, NULL, 0,
968 pv, cb, pcbActual);
969 }
970
971 return VINF_SUCCESS;
972}
973
974void vboxClipboardWriteData (VBOXCLIPBOARDCLIENTDATA *pClient, void *pv, uint32_t cb, uint32_t u32Format)
975{
976 LogFlow(("vboxClipboardWriteData\n"));
977
978 /*
979 * The guest returns data that was requested in the WM_RENDERFORMAT handler.
980 */
981 Assert(pClient->data.pv == NULL && pClient->data.cb == 0 && pClient->data.u32Format == 0);
982
983 vboxClipboardDump(pv, cb, u32Format);
984
985 if (cb > 0)
986 {
987 char *pszResult = NULL;
988
989 if ( u32Format == VBOX_SHARED_CLIPBOARD_FMT_HTML
990 && !IsWindowsHTML((const char*)pv))
991 {
992 /* check that this is not already CF_HTML */
993 uint32_t cbResult;
994 int rc = ConvertMimeToCFHTML((const char *)pv, cb, &pszResult, &cbResult);
995 if (RT_SUCCESS(rc))
996 {
997 if (pszResult != NULL && cbResult != 0)
998 {
999 pClient->data.pv = pszResult;
1000 pClient->data.cb = cbResult;
1001 pClient->data.u32Format = u32Format;
1002 }
1003 }
1004 }
1005 else
1006 {
1007 pClient->data.pv = RTMemDup(pv, cb);
1008 if (pClient->data.pv)
1009 {
1010 pClient->data.cb = cb;
1011 pClient->data.u32Format = u32Format;
1012 }
1013 }
1014 }
1015
1016 SetEvent(pClient->pCtx->hRenderEvent);
1017}
1018
1019
1020/**
1021 * Extracts field value from CF_HTML struct
1022 *
1023 * @returns VBox status code
1024 * @param pszSrc source in CF_HTML format
1025 * @param pszOption Name of CF_HTML field
1026 * @param puValue Where to return extracted value of CF_HTML field
1027 */
1028static int GetHeaderValue(const char *pszSrc, const char *pszOption, uint32_t *puValue)
1029{
1030 int rc = VERR_INVALID_PARAMETER;
1031
1032 Assert(pszSrc);
1033 Assert(pszOption);
1034
1035 const char *pszOptionValue = RTStrStr(pszSrc, pszOption);
1036 if (pszOptionValue)
1037 {
1038 size_t cchOption = strlen(pszOption);
1039 Assert(cchOption);
1040
1041 rc = RTStrToUInt32Ex(pszOptionValue + cchOption, NULL, 10, puValue);
1042 }
1043 return rc;
1044}
1045
1046
1047/**
1048 * Check that the source string contains CF_HTML struct
1049 *
1050 * @param pszSource source string.
1051 *
1052 * @returns @c true if the @a pszSource string is in CF_HTML format
1053 */
1054static bool IsWindowsHTML(const char *pszSource)
1055{
1056 return RTStrStr(pszSource, "Version:") != NULL
1057 && RTStrStr(pszSource, "StartHTML:") != NULL;
1058}
1059
1060
1061/*
1062 * Converts clipboard data from CF_HTML format to mimie clipboard format
1063 *
1064 * Returns allocated buffer that contains html converted to text/html mime type
1065 *
1066 * @returns VBox status code.
1067 * @param pszSource The input.
1068 * @param cch The length of the input.
1069 * @param ppszOutput Where to return the result. Free using RTMemFree.
1070 * @param pcbOutput Where to the return length of the result (bytes/chars).
1071 */
1072static int ConvertCFHtmlToMime(const char *pszSource, const uint32_t cch, char **ppszOutput, uint32_t *pcbOutput)
1073{
1074 Assert(pszSource);
1075 Assert(cch);
1076 Assert(ppszOutput);
1077 Assert(pcbOutput);
1078
1079 uint32_t offStart;
1080 int rc = GetHeaderValue(pszSource, "StartFragment:", &offStart);
1081 if (RT_SUCCESS(rc))
1082 {
1083 uint32_t offEnd;
1084 rc = GetHeaderValue(pszSource, "EndFragment:", &offEnd);
1085 if (RT_SUCCESS(rc))
1086 {
1087 if ( offStart > 0
1088 && offEnd > 0
1089 && offEnd > offStart
1090 && offEnd <= cch)
1091 {
1092 uint32_t cchSubStr = offEnd - offStart;
1093 char *pszResult = (char *)RTMemAlloc(cchSubStr + 1);
1094 if (pszResult)
1095 {
1096 rc = RTStrCopyEx(pszResult, cchSubStr + 1, pszSource + offStart, cchSubStr);
1097 if (RT_SUCCESS(rc))
1098 {
1099 *ppszOutput = pszResult;
1100 *pcbOutput = (uint32_t)(cchSubStr + 1);
1101 rc = VINF_SUCCESS;
1102 }
1103 else
1104 {
1105 LogRelFlowFunc(("Error: Unknown CF_HTML format. Expected EndFragment. rc = %Rrc\n", rc));
1106 RTMemFree(pszResult);
1107 }
1108 }
1109 else
1110 {
1111 LogRelFlowFunc(("Error: Unknown CF_HTML format. Expected EndFragment.\n"));
1112 rc = VERR_NO_MEMORY;
1113 }
1114 }
1115 else
1116 {
1117 LogRelFlowFunc(("Error: CF_HTML out of bounds - offStart=%#x offEnd=%#x cch=%#x\n", offStart, offEnd, cch));
1118 rc = VERR_INVALID_PARAMETER;
1119 }
1120 }
1121 else
1122 {
1123 LogRelFlowFunc(("Error: Unknown CF_HTML format. Expected EndFragment. rc = %Rrc.\n", rc));
1124 rc = VERR_INVALID_PARAMETER;
1125 }
1126 }
1127 else
1128 {
1129 LogRelFlowFunc(("Error: Unknown CF_HTML format. Expected StartFragment. rc = %Rrc.\n", rc));
1130 rc = VERR_INVALID_PARAMETER;
1131 }
1132
1133 return rc;
1134}
1135
1136
1137
1138/**
1139 * Converts source UTF-8 MIME HTML clipboard data to UTF-8 CF_HTML format.
1140 *
1141 * This is just encapsulation work, slapping a header on the data.
1142 *
1143 * It allocates
1144 *
1145 * Calculations:
1146 * Header length = format Length + (2*(10 - 5('%010d'))('digits')) - 2('%s') = format length + 8
1147 * EndHtml = Header length + fragment length
1148 * StartHtml = 105(constant)
1149 * StartFragment = 141(constant) may vary if the header html content will be extended
1150 * EndFragment = Header length + fragment length - 38(ending length)
1151 *
1152 * @param pszSource Source buffer that contains utf-16 string in mime html format
1153 * @param cb Size of source buffer in bytes
1154 * @param ppszOutput Where to return the allocated output buffer to put converted UTF-8
1155 * CF_HTML clipboard data. This function allocates memory for this.
1156 * @param pcbOutput Where to return the size of allocated result buffer in bytes/chars, including zero terminator
1157 *
1158 * @note output buffer should be free using RTMemFree()
1159 * @note Everything inside of fragment can be UTF8. Windows allows it. Everything in header should be Latin1.
1160 */
1161static int ConvertMimeToCFHTML(const char *pszSource, size_t cb, char **ppszOutput, uint32_t *pcbOutput)
1162{
1163 Assert(ppszOutput);
1164 Assert(pcbOutput);
1165 Assert(pszSource);
1166 Assert(cb);
1167
1168 /* construct CF_HTML formatted string */
1169 char *pszResult = NULL;
1170 size_t cchFragment;
1171 int rc = RTStrNLenEx(pszSource, cb, &cchFragment);
1172 if (!RT_SUCCESS(rc))
1173 {
1174 LogRelFlowFunc(("Error: invalid source fragment. rc = %Rrc.\n"));
1175 return VERR_INVALID_PARAMETER;
1176 }
1177
1178 /*
1179 @StartHtml - pos before <html>
1180 @EndHtml - whole size of text excluding ending zero char
1181 @StartFragment - pos after <!--StartFragment-->
1182 @EndFragment - pos before <!--EndFragment-->
1183 @note: all values includes CR\LF inserted into text
1184 Calculations:
1185 Header length = format Length + (3*6('digits')) - 2('%s') = format length + 16 (control value - 183)
1186 EndHtml = Header length + fragment length
1187 StartHtml = 105(constant)
1188 StartFragment = 143(constant)
1189 EndFragment = Header length + fragment length - 40(ending length)
1190 */
1191 static const char s_szFormatSample[] =
1192 /* 0: */ "Version:1.0\r\n"
1193 /* 13: */ "StartHTML:000000101\r\n"
1194 /* 34: */ "EndHTML:%0000009u\r\n" // END HTML = Header length + fragment length
1195 /* 53: */ "StartFragment:000000137\r\n"
1196 /* 78: */ "EndFragment:%0000009u\r\n"
1197 /* 101: */ "<html>\r\n"
1198 /* 109: */ "<body>\r\n"
1199 /* 117: */ "<!--StartFragment-->"
1200 /* 137: */ "%s"
1201 /* 137+2: */ "<!--EndFragment-->\r\n"
1202 /* 157+2: */ "</body>\r\n"
1203 /* 166+2: */ "</html>\r\n";
1204 /* 175+2: */
1205 AssertCompile(sizeof(s_szFormatSample) == 175+2+1);
1206
1207 /* calculate parameters of CF_HTML header */
1208 size_t cchHeader = sizeof(s_szFormatSample) - 1;
1209 size_t offEndHtml = cchHeader + cchFragment;
1210 size_t offEndFragment = cchHeader + cchFragment - 38; /* 175-137 = 38 */
1211 pszResult = (char *)RTMemAlloc(offEndHtml + 1);
1212 if (pszResult == NULL)
1213 {
1214 LogRelFlowFunc(("Error: Cannot allocate memory for result buffer. rc = %Rrc.\n"));
1215 return VERR_NO_MEMORY;
1216 }
1217
1218 /* format result CF_HTML string */
1219 size_t cchFormatted = RTStrPrintf(pszResult, offEndHtml + 1,
1220 s_szFormatSample, offEndHtml, offEndFragment, pszSource);
1221 Assert(offEndHtml == cchFormatted); NOREF(cchFormatted);
1222
1223#ifdef VBOX_STRICT
1224 /* Control calculations. check consistency.*/
1225 static const char s_szStartFragment[] = "<!--StartFragment-->";
1226 static const char s_szEndFragment[] = "<!--EndFragment-->";
1227
1228 /* check 'StartFragment:' value */
1229 const char *pszRealStartFragment = RTStrStr(pszResult, s_szStartFragment);
1230 Assert(&pszRealStartFragment[sizeof(s_szStartFragment) - 1] - pszResult == 137);
1231
1232 /* check 'EndFragment:' value */
1233 const char *pszRealEndFragment = RTStrStr(pszResult, s_szEndFragment);
1234 Assert((size_t)(pszRealEndFragment - pszResult) == offEndFragment);
1235#endif
1236
1237 *ppszOutput = pszResult;
1238 *pcbOutput = (uint32_t)cchFormatted + 1;
1239 Assert(*pcbOutput == cchFormatted + 1);
1240
1241 return VINF_SUCCESS;
1242}
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