VirtualBox

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

Last change on this file since 76409 was 75498, checked in by vboxsync, 6 years ago

HGCM,Main,SharedFolder,SharedClipboard,GuestProperties: Added HGCM service helpers for statistics and dbg info registration/deregistration. A PUVM is passed to HGCMService (where the helpers are implemented) when the service is loaded. Since this drags in both dbg.h and stam.h, LOG_GROUP defines now have to be at the top of the include list as everywhere else (i.e. hgcmsvc.h will define LOG_GROUP default by dragging in log.h). Added generic statistics of HGCM message processing and function level statistics to the shared folder service. [missing files, ++]

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