VirtualBox

source: vbox/trunk/src/VBox/GuestHost/SharedClipboard/x11-clipboard.cpp@ 19144

Last change on this file since 19144 was 19027, checked in by vboxsync, 16 years ago

GuestHost/SharedClipboard: add testcases for the X11 clipboard code and fix some issues they revealed

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 60.9 KB
Line 
1/** @file
2 *
3 * Shared Clipboard:
4 * X11 backend code.
5 */
6
7/*
8 * Copyright (C) 2006-2007 Sun Microsystems, Inc.
9 *
10 * This file is part of VirtualBox Open Source Edition (OSE), as
11 * available from http://www.virtualbox.org. This file is free software;
12 * you can redistribute it and/or modify it under the terms of the GNU
13 * General Public License (GPL) as published by the Free Software
14 * Foundation, in version 2 as it comes in the "COPYING" file of the
15 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
16 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
17 *
18 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
19 * Clara, CA 95054 USA or visit http://www.sun.com if you need
20 * additional information or have any questions.
21 */
22
23#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD
24
25#include <vector>
26
27#ifdef RT_OS_SOLARIS
28#include <tsol/label.h>
29#endif
30
31#include <X11/Xlib.h>
32#include <X11/Xatom.h>
33#include <X11/Intrinsic.h>
34#include <X11/Shell.h>
35#include <X11/Xproto.h>
36#include <X11/StringDefs.h>
37
38#include <iprt/env.h>
39#include <iprt/mem.h>
40#include <iprt/semaphore.h>
41#include <iprt/thread.h>
42
43#include <VBox/log.h>
44
45#include <VBox/GuestHost/SharedClipboard.h>
46#include <VBox/GuestHost/clipboard-helper.h>
47#include <VBox/HostServices/VBoxClipboardSvc.h>
48
49/** Do we want to test Utf8 by disabling other text formats? */
50static bool g_testUtf8 = false;
51/** Do we want to test compount text by disabling other text formats? */
52static bool g_testCText = false;
53/** Are we currently debugging the clipboard code? */
54static bool g_debugClipboard = false;
55
56/** The different clipboard formats which we support. */
57enum g_eClipboardFormats
58{
59 INVALID = 0,
60 TARGETS,
61 CTEXT,
62 UTF8
63};
64
65/** The X11 clipboard uses several names for the same format. This
66 * structure maps an X11 name to a format. */
67typedef struct {
68 Atom atom;
69 g_eClipboardFormats format;
70 unsigned guestFormat;
71} VBOXCLIPBOARDFORMAT;
72
73/** Global context information used by the X11 clipboard backend */
74struct _VBOXCLIPBOARDCONTEXTX11
75{
76 /** Opaque data structure describing the front-end. */
77 VBOXCLIPBOARDCONTEXT *pFrontend;
78 /** The X Toolkit application context structure */
79 XtAppContext appContext;
80
81 /** We have a separate thread to wait for Window and Clipboard events */
82 RTTHREAD thread;
83 /** The X Toolkit widget which we use as our clipboard client. It is never made visible. */
84 Widget widget;
85
86 /** X11 atom refering to the clipboard: CLIPBOARD */
87 Atom atomClipboard;
88 /** X11 atom refering to the selection: PRIMARY */
89 Atom atomPrimary;
90 /** X11 atom refering to the clipboard targets: TARGETS */
91 Atom atomTargets;
92 /** X11 atom refering to the clipboard multiple target: MULTIPLE */
93 Atom atomMultiple;
94 /** X11 atom refering to the clipboard timestamp target: TIMESTAMP */
95 Atom atomTimestamp;
96 /** X11 atom refering to the clipboard utf8 text format: UTF8_STRING */
97 Atom atomUtf8;
98 /** X11 atom refering to the clipboard compound text format: COMPOUND_TEXT */
99 Atom atomCText;
100
101 /** A list of the X11 formats which we support, mapped to our identifier for them, in the
102 order we prefer to have them in. */
103 std::vector<VBOXCLIPBOARDFORMAT> formatList;
104
105 /** Does VBox or X11 currently own the clipboard? */
106 volatile enum g_eOwner eOwner;
107
108 /** What is the best text format X11 has to offer? INVALID for none. */
109 g_eClipboardFormats X11TextFormat;
110 /** Atom corresponding to the X11 text format */
111 Atom atomX11TextFormat;
112 /** What is the best bitmap format X11 has to offer? INVALID for none. */
113 g_eClipboardFormats X11BitmapFormat;
114 /** Atom corresponding to the X11 Bitmap format */
115 Atom atomX11BitmapFormat;
116 /** What formats does VBox have on offer? */
117 uint32_t vboxFormats;
118 /** Windows hosts and guests cache the clipboard data they receive.
119 * Since we have no way of knowing whether their cache is still valid,
120 * we always send a "data changed" message after a successful transfer
121 * to invalidate it. */
122 bool notifyVBox;
123
124 /** Since the clipboard data moves asynchronously, we use an event
125 * semaphore to wait for it. When a function issues a request for
126 * clipboard data it must wait for this semaphore, which is triggered
127 * when the data arrives. */
128 RTSEMEVENT waitForData;
129};
130
131/** The number of simultaneous instances we support. For all normal purposes
132 * we should never need more than one. For the testcase it is convenient to
133 * have a second instance that the first can interact with in order to have
134 * a more controlled environment. */
135enum { CLIPBOARD_NUM_CONTEXTS = 20 };
136
137/** Array of structures for mapping Xt widgets to context pointers. We
138 * need this because the widget clipboard callbacks do not pass user data. */
139static struct {
140 /** The widget we want to associate the context with */
141 Widget widget;
142 /** The context associated with the widget */
143 VBOXCLIPBOARDCONTEXTX11 *pCtx;
144} g_contexts[CLIPBOARD_NUM_CONTEXTS];
145
146/** Register a new X11 clipboard context. */
147static int vboxClipboardAddContext(VBOXCLIPBOARDCONTEXTX11 *pCtx)
148{
149 bool found = false;
150 AssertReturn(pCtx != NULL, VERR_INVALID_PARAMETER);
151 Widget widget = pCtx->widget;
152 AssertReturn(widget != NULL, VERR_INVALID_PARAMETER);
153 for (unsigned i = 0; i < RT_ELEMENTS(g_contexts); ++i)
154 {
155 AssertReturn( (g_contexts[i].widget != widget)
156 && (g_contexts[i].pCtx != pCtx), VERR_WRONG_ORDER);
157 if (g_contexts[i].widget == NULL && !found)
158 {
159 AssertReturn(g_contexts[i].pCtx == NULL, VERR_INTERNAL_ERROR);
160 g_contexts[i].widget = widget;
161 g_contexts[i].pCtx = pCtx;
162 found = true;
163 }
164 }
165 return found ? VINF_SUCCESS : VERR_OUT_OF_RESOURCES;
166}
167
168/** Unregister an X11 clipboard context. */
169static void vboxClipboardRemoveContext(VBOXCLIPBOARDCONTEXTX11 *pCtx)
170{
171 bool found = false;
172 AssertReturnVoid(pCtx != NULL);
173 Widget widget = pCtx->widget;
174 AssertReturnVoid(widget != NULL);
175 for (unsigned i = 0; i < RT_ELEMENTS(g_contexts); ++i)
176 {
177 Assert(!found || g_contexts[i].widget != widget);
178 if (g_contexts[i].widget == widget)
179 {
180 Assert(g_contexts[i].pCtx != NULL);
181 g_contexts[i].widget = NULL;
182 g_contexts[i].pCtx = NULL;
183 found = true;
184 }
185 }
186}
187
188/** Find an X11 clipboard context. */
189static VBOXCLIPBOARDCONTEXTX11 *vboxClipboardFindContext(Widget widget)
190{
191 AssertReturn(widget != NULL, NULL);
192 for (unsigned i = 0; i < RT_ELEMENTS(g_contexts); ++i)
193 {
194 if (g_contexts[i].widget == widget)
195 {
196 Assert(g_contexts[i].pCtx != NULL);
197 return g_contexts[i].pCtx;
198 }
199 }
200 return NULL;
201}
202
203/* Are we actually connected to the X server? */
204static bool g_fHaveX11;
205
206static int vboxClipboardWriteUtf16LE(VBOXCLIPBOARDCONTEXTX11 *pCtx,
207 PRTUTF16 pu16SrcText,
208 size_t cwSrcLen,
209 void *pv, unsigned cb,
210 uint32_t *pcbActual)
211{
212 size_t cwDestLen;
213 PRTUTF16 pu16DestText = reinterpret_cast<PRTUTF16>(pv);
214 int rc = VINF_SUCCESS;
215 /* Check how much longer will the converted text will be. */
216 rc = vboxClipboardUtf16GetWinSize(pu16SrcText, cwSrcLen, &cwDestLen);
217 if (RT_SUCCESS(rc) && (cb < cwDestLen * 2))
218 {
219 /* Not enough buffer space provided - report the amount needed. */
220 LogFlowFunc (("guest buffer too small: size %d bytes, needed %d. Returning.\n",
221 cb, cwDestLen * 2));
222 *pcbActual = cwDestLen * 2;
223 rc = VERR_BUFFER_OVERFLOW;
224 }
225 /* Convert the text. */
226 if (RT_SUCCESS(rc))
227 rc = vboxClipboardUtf16LinToWin(pu16SrcText, cwSrcLen, pu16DestText, cb / 2);
228 if (RT_SUCCESS(rc))
229 {
230 LogFlowFunc (("converted string is %.*ls\n", cwDestLen, pu16DestText));
231 *pcbActual = cwDestLen * 2;
232 }
233 return rc;
234}
235
236/**
237 * Convert the UTF-8 text obtained from the X11 clipboard to UTF-16LE with
238 * Windows EOLs, place it in the buffer supplied and signal that data has
239 * arrived.
240 *
241 * @param pValue Source UTF-8 text
242 * @param cbSourceLen Length in 8-bit bytes of the source text
243 * @param pv Where to store the converted data
244 * @param cb Length in bytes of the buffer pointed to by pv
245 * @param pcbActual Where to store the size of the converted data
246 * @param pClient Pointer to the client context structure
247 * @note X11 backend code, called from the Xt callback when we wish to read
248 * the X11 clipboard.
249 */
250static void vboxClipboardGetUtf8FromX11(VBOXCLIPBOARDCONTEXTX11 *pCtx,
251 XtPointer pValue, unsigned cbSrcLen,
252 void *pv, unsigned cb,
253 uint32_t *pcbActual)
254{
255 size_t cwSrcLen;
256 char *pu8SrcText = reinterpret_cast<char *>(pValue);
257 PRTUTF16 pu16SrcText = NULL;
258
259 LogFlowFunc (("converting Utf-8 to Utf-16LE. cbSrcLen=%d, cb=%d, pu8SrcText=%.*s\n",
260 cbSrcLen, cb, cbSrcLen, pu8SrcText));
261 *pcbActual = 0; /* Only set this to the right value on success. */
262 /* First convert the UTF8 to UTF16 */
263 int rc = RTStrToUtf16Ex(pu8SrcText, cbSrcLen, &pu16SrcText, 0, &cwSrcLen);
264 if (RT_SUCCESS(rc))
265 rc = vboxClipboardWriteUtf16LE(pCtx, pu16SrcText, cwSrcLen,
266 pv, cb, pcbActual);
267 XtFree(reinterpret_cast<char *>(pValue));
268 RTUtf16Free(pu16SrcText);
269 RTSemEventSignal(pCtx->waitForData);
270 LogFlowFunc(("Returning. Status is %Rrc\n", rc));
271}
272
273/**
274 * Convert the COMPOUND_TEXT obtained from the X11 clipboard to UTF-16LE with
275 * Windows EOLs, place it in the buffer supplied and signal that data has
276 * arrived.
277 *
278 * @param pValue Source COMPOUND_TEXT
279 * @param cbSourceLen Length in 8-bit bytes of the source text
280 * @param pv Where to store the converted data
281 * @param cb Length in bytes of the buffer pointed to by pv
282 * @param pcbActual Where to store the size of the converted data
283 * @param pClient Pointer to the client context structure
284 * @note X11 backend code, called from the Xt callback when we wish to read
285 * the X11 clipboard.
286 */
287static void vboxClipboardGetCTextFromX11(VBOXCLIPBOARDCONTEXTX11 *pCtx,
288 XtPointer pValue, unsigned cbSrcLen,
289 void *pv, unsigned cb,
290 uint32_t *pcbActual)
291{
292 size_t cwSrcLen;
293 char **ppu8SrcText = NULL;
294 PRTUTF16 pu16SrcText = NULL;
295 XTextProperty property;
296 int rc = VINF_SUCCESS;
297 int cProps;
298
299 LogFlowFunc (("converting COMPOUND TEXT to Utf-16LE. cbSrcLen=%d, cb=%d, pu8SrcText=%.*s\n",
300 cbSrcLen, cb, cbSrcLen, reinterpret_cast<char *>(pValue)));
301 *pcbActual = 0; /* Only set this to the right value on success. */
302 /* First convert the compound text to Utf8 */
303 property.value = reinterpret_cast<unsigned char *>(pValue);
304 property.encoding = pCtx->atomCText;
305 property.format = 8;
306 property.nitems = cbSrcLen;
307#ifdef RT_OS_SOLARIS
308 int xrc = XmbTextPropertyToTextList(XtDisplay(pCtx->widget), &property,
309 &ppu8SrcText, &cProps);
310#else
311 int xrc = Xutf8TextPropertyToTextList(XtDisplay(pCtx->widget),
312 &property, &ppu8SrcText, &cProps);
313#endif
314 XtFree(reinterpret_cast<char *>(pValue));
315 if (xrc < 0)
316 switch(xrc)
317 {
318 case XNoMemory:
319 rc = VERR_NO_MEMORY;
320 break;
321 case XLocaleNotSupported:
322 case XConverterNotFound:
323 rc = VERR_NOT_SUPPORTED;
324 break;
325 default:
326 rc = VERR_UNRESOLVED_ERROR;
327 }
328 /* Now convert the UTF8 to UTF16 */
329 if (RT_SUCCESS(rc))
330 rc = RTStrToUtf16Ex(*ppu8SrcText, cbSrcLen, &pu16SrcText, 0, &cwSrcLen);
331 if (RT_SUCCESS(rc))
332 rc = vboxClipboardWriteUtf16LE(pCtx, pu16SrcText, cwSrcLen,
333 pv, cb, pcbActual);
334 if (ppu8SrcText != NULL)
335 XFreeStringList(ppu8SrcText);
336 RTUtf16Free(pu16SrcText);
337 LogFlowFunc(("Returning. Status is %Rrc\n", rc));
338 RTSemEventSignal(pCtx->waitForData);
339}
340
341/**
342 * Convert the Latin1 text obtained from the X11 clipboard to UTF-16LE with
343 * Windows EOLs, place it in the buffer supplied and signal that data has
344 * arrived.
345 *
346 * @param pValue Source Latin1 text
347 * @param cbSourceLen Length in 8-bit bytes of the source text
348 * @param pv Where to store the converted data
349 * @param cb Length in bytes of the buffer pointed to by cb
350 * @param pcbActual Where to store the size of the converted data
351 * @param pClient Pointer to the client context structure
352 * @note X11 backend code, called from the Xt callback when we wish to read
353 * the X11 clipboard.
354 */
355static void vboxClipboardGetLatin1FromX11(VBOXCLIPBOARDCONTEXTX11 *pCtx,
356 XtPointer pValue,
357 unsigned cbSourceLen, void *pv,
358 unsigned cb, uint32_t *pcbActual)
359{
360 unsigned cwDestLen = cbSourceLen + 1;
361 char *pu8SourceText = reinterpret_cast<char *>(pValue);
362 PRTUTF16 pu16DestText = reinterpret_cast<PRTUTF16>(pv);
363 int rc = VINF_SUCCESS;
364
365 LogFlowFunc (("converting Latin1 to Utf-16LE. Original is %.*s\n",
366 cbSourceLen, pu8SourceText));
367 *pcbActual = 0; /* Only set this to the right value on success. */
368 for (unsigned i = 0; i < cbSourceLen; i++)
369 if (pu8SourceText[i] == LINEFEED)
370 ++cwDestLen;
371 if (cb < cwDestLen * 2)
372 {
373 /* Not enough buffer space provided - report the amount needed. */
374 LogFlowFunc (("guest buffer too small: size %d bytes\n", cb));
375 *pcbActual = cwDestLen * 2;
376 rc = VERR_BUFFER_OVERFLOW;
377 }
378 if (RT_SUCCESS(rc))
379 {
380 for (unsigned i = 0, j = 0; i < cbSourceLen; ++i, ++j)
381 if (pu8SourceText[i] != LINEFEED)
382 pu16DestText[j] = pu8SourceText[i]; /* latin1 < utf-16LE */
383 else
384 {
385 pu16DestText[j] = CARRIAGERETURN;
386 ++j;
387 pu16DestText[j] = LINEFEED;
388 }
389 pu16DestText[cwDestLen - 1] = 0;
390 *pcbActual = cwDestLen * 2;
391 LogFlowFunc (("converted text is %.*ls\n", cwDestLen, pu16DestText));
392 }
393 XtFree(reinterpret_cast<char *>(pValue));
394 RTSemEventSignal(pCtx->waitForData);
395 LogFlowFunc(("Returning. Status is %Rrc\n", rc));
396}
397
398/**
399 * Convert the text obtained from the X11 clipboard to UTF-16LE with Windows
400 * EOLs, place it in the buffer supplied and signal that data has arrived.
401 * @note X11 backend code, callback for XtGetSelectionValue, for use when
402 * the X11 clipboard contains a text format we understand.
403 */
404static void vboxClipboardGetDataFromX11(Widget, XtPointer pClientData,
405 Atom * /* selection */,
406 Atom *atomType,
407 XtPointer pValue,
408 long unsigned int *pcLen,
409 int *piFormat)
410{
411 VBOXCLIPBOARDREQUEST *pRequest
412 = reinterpret_cast<VBOXCLIPBOARDREQUEST *>(pClientData);
413 VBOXCLIPBOARDCONTEXTX11 *pCtx = pRequest->pCtx;
414 pRequest->rc = VINF_SUCCESS;
415 LogFlowFunc(("pClientData=%p, *pcLen=%lu, *piFormat=%d\n", pClientData,
416 *pcLen, *piFormat));
417 LogFlowFunc(("pCtx->X11TextFormat=%d, pRequest->cb=%d\n",
418 pCtx->X11TextFormat, pRequest->cb));
419 unsigned cTextLen = (*pcLen) * (*piFormat) / 8;
420 /* The X Toolkit may have failed to get the clipboard selection for us. */
421 if (*atomType == XT_CONVERT_FAIL) /* timeout */
422 {
423 pRequest->rc = VERR_TIMEOUT;
424 RTSemEventSignal(pCtx->waitForData);
425 return;
426 }
427 /* The clipboard selection may have changed before we could get it. */
428 if (NULL == pValue)
429 {
430 pRequest->rc = VERR_NO_DATA;
431 RTSemEventSignal(pCtx->waitForData);
432 return;
433 }
434 /* In which format is the clipboard data? */
435 switch (pCtx->X11TextFormat)
436 {
437 case CTEXT:
438 vboxClipboardGetCTextFromX11(pCtx, pValue, cTextLen, pRequest->pv,
439 pRequest->cb, pRequest->pcbActual);
440 break;
441 case UTF8:
442 {
443 /* If we are given broken Utf-8, we treat it as Latin1. Is this acceptable? */
444 size_t cStringLen;
445 char *pu8SourceText = reinterpret_cast<char *>(pValue);
446
447 if ((pCtx->X11TextFormat == UTF8)
448 && (RTStrUniLenEx(pu8SourceText, *pcLen, &cStringLen) == VINF_SUCCESS))
449 {
450 vboxClipboardGetUtf8FromX11(pCtx, pValue, cTextLen, pRequest->pv,
451 pRequest->cb, pRequest->pcbActual);
452 break;
453 }
454 else
455 {
456 vboxClipboardGetLatin1FromX11(pCtx, pValue, cTextLen,
457 pRequest->pv, pRequest->cb,
458 pRequest->pcbActual);
459 break;
460 }
461 }
462 default:
463 LogFunc (("bad target format\n"));
464 XtFree(reinterpret_cast<char *>(pValue));
465 pRequest->rc = VERR_INVALID_PARAMETER;
466 RTSemEventSignal(pCtx->waitForData);
467 return;
468 }
469 pCtx->notifyVBox = true;
470}
471
472/**
473 * Notify the host clipboard about the data formats we support, based on the
474 * "targets" (available data formats) information obtained from the X11
475 * clipboard.
476 * @note X11 backend code, callback for XtGetSelectionValue, called when we
477 * poll for available targets.
478 */
479static void vboxClipboardGetTargetsFromX11(Widget,
480 XtPointer pClientData,
481 Atom * /* selection */,
482 Atom *atomType,
483 XtPointer pValue,
484 long unsigned int *pcLen,
485 int *piFormat)
486{
487 VBOXCLIPBOARDCONTEXTX11 *pCtx =
488 reinterpret_cast<VBOXCLIPBOARDCONTEXTX11 *>(pClientData);
489 Atom *atomTargets = reinterpret_cast<Atom *>(pValue);
490 unsigned cAtoms = *pcLen;
491 g_eClipboardFormats eBestTarget = INVALID;
492 Atom atomBestTarget = None;
493
494 Log3 (("%s: called\n", __PRETTY_FUNCTION__));
495 if (*atomType == XT_CONVERT_FAIL) /* timeout */
496 {
497 LogFunc (("reading clipboard from host, X toolkit failed to convert the selection\n"));
498 pCtx->atomX11TextFormat = None;
499 pCtx->X11TextFormat = INVALID;
500 return;
501 }
502
503 for (unsigned i = 0; i < cAtoms; ++i)
504 {
505 for (unsigned j = 0; j != pCtx->formatList.size(); ++j)
506 if (pCtx->formatList[j].atom == atomTargets[i])
507 {
508 if (eBestTarget < pCtx->formatList[j].format)
509 {
510 eBestTarget = pCtx->formatList[j].format;
511 atomBestTarget = pCtx->formatList[j].atom;
512 }
513 break;
514 }
515 if (g_debugClipboard)
516 {
517 char *szAtomName = XGetAtomName(XtDisplay(pCtx->widget),
518 atomTargets[i]);
519 if (szAtomName != 0)
520 {
521 Log2 (("%s: the host offers target %s\n", __PRETTY_FUNCTION__,
522 szAtomName));
523 XFree(szAtomName);
524 }
525 }
526 }
527 pCtx->atomX11TextFormat = atomBestTarget;
528 if ((eBestTarget != pCtx->X11TextFormat) || (pCtx->notifyVBox == true))
529 {
530 uint32_t u32Formats = 0;
531 if (g_debugClipboard)
532 {
533 if (atomBestTarget != None)
534 {
535 char *szAtomName = XGetAtomName(XtDisplay(pCtx->widget),
536 atomBestTarget);
537 Log2 (("%s: switching to host text target %s. Available targets are:\n",
538 __PRETTY_FUNCTION__, szAtomName));
539 XFree(szAtomName);
540 }
541 else
542 Log2(("%s: no supported host text target found. Available targets are:\n",
543 __PRETTY_FUNCTION__));
544 for (unsigned i = 0; i < cAtoms; ++i)
545 {
546 char *szAtomName = XGetAtomName(XtDisplay(pCtx->widget),
547 atomTargets[i]);
548 if (szAtomName != 0)
549 {
550 Log2 (("%s: %s\n", __PRETTY_FUNCTION__, szAtomName));
551 XFree(szAtomName);
552 }
553 }
554 }
555 pCtx->X11TextFormat = eBestTarget;
556 if (eBestTarget != INVALID)
557 u32Formats |= VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT;
558 VBoxX11ClipboardReportX11Formats(pCtx->pFrontend, u32Formats);
559 pCtx->notifyVBox = false;
560 }
561 XtFree(reinterpret_cast<char *>(pValue));
562}
563
564/**
565 * This timer callback is called every 200ms to check the contents of the X11
566 * clipboard.
567 * @note X11 backend code, callback for XtAppAddTimeOut, recursively
568 * re-armed.
569 * @todo Use the XFIXES extension to check for new clipboard data when
570 * available.
571 */
572static void vboxClipboardPollX11ForTargets(XtPointer pUserData,
573 XtIntervalId * /* hTimerId */)
574{
575 VBOXCLIPBOARDCONTEXTX11 *pCtx =
576 reinterpret_cast<VBOXCLIPBOARDCONTEXTX11 *>(pUserData);
577 Log3 (("%s: called\n", __PRETTY_FUNCTION__));
578 /* Get the current clipboard contents */
579 if ((pCtx->eOwner != VB))
580 {
581 Log3 (("%s: requesting the targets that the host clipboard offers\n",
582 __PRETTY_FUNCTION__));
583 XtGetSelectionValue(pCtx->widget, pCtx->atomClipboard,
584 pCtx->atomTargets,
585 vboxClipboardGetTargetsFromX11, pCtx,
586 CurrentTime);
587 }
588 /* Re-arm our timer */
589 XtAppAddTimeOut(pCtx->appContext, 200 /* ms */,
590 vboxClipboardPollX11ForTargets, pCtx);
591}
592
593/** We store information about the target formats we can handle in a global
594 * vector for internal use.
595 * @note X11 backend code.
596 */
597static void vboxClipboardAddFormat(VBOXCLIPBOARDCONTEXTX11 *pCtx,
598 const char *pszName,
599 g_eClipboardFormats eFormat,
600 unsigned guestFormat)
601{
602 VBOXCLIPBOARDFORMAT sFormat;
603 /* Get an atom from the X server for that target format */
604 Atom atomFormat = XInternAtom(XtDisplay(pCtx->widget), pszName, false);
605 sFormat.atom = atomFormat;
606 sFormat.format = eFormat;
607 sFormat.guestFormat = guestFormat;
608 pCtx->formatList.push_back(sFormat);
609 LogFlow (("vboxClipboardAddFormat: added format %s (%d)\n", pszName, eFormat));
610}
611
612/**
613 * The main loop of our clipboard reader.
614 * @note X11 backend code.
615 */
616static int vboxClipboardThread(RTTHREAD self, void *pvUser)
617{
618 LogRel(("Shared clipboard: starting host clipboard thread\n"));
619
620 VBOXCLIPBOARDCONTEXTX11 *pCtx =
621 reinterpret_cast<VBOXCLIPBOARDCONTEXTX11 *>(pvUser);
622 /* Set up a timer to poll the host clipboard */
623 XtAppAddTimeOut(pCtx->appContext, 200 /* ms */,
624 vboxClipboardPollX11ForTargets, pCtx);
625
626 XtAppMainLoop(pCtx->appContext);
627 pCtx->formatList.clear();
628 LogRel(("Shared clipboard: host clipboard thread terminated successfully\n"));
629 return VINF_SUCCESS;
630}
631
632/** X11 specific uninitialisation for the shared clipboard.
633 * @note X11 backend code.
634 */
635static void vboxClipboardUninitX11(VBOXCLIPBOARDCONTEXTX11 *pCtx)
636{
637 AssertPtrReturnVoid(pCtx);
638 if (pCtx->widget)
639 {
640 /* Valid widget + invalid appcontext = bug. But don't return yet. */
641 AssertPtr(pCtx->appContext);
642 vboxClipboardRemoveContext(pCtx);
643 XtDestroyWidget(pCtx->widget);
644 pCtx->widget = NULL;
645 }
646 if (pCtx->appContext)
647 {
648 XtDestroyApplicationContext(pCtx->appContext);
649 pCtx->appContext = NULL;
650 }
651}
652
653/** X11 specific initialisation for the shared clipboard.
654 * @note X11 backend code.
655 */
656static int vboxClipboardInitX11 (VBOXCLIPBOARDCONTEXTX11 *pCtx)
657{
658 /* Create a window and make it a clipboard viewer. */
659 int cArgc = 0;
660 char *pcArgv = 0;
661 int rc = VINF_SUCCESS;
662 Display *pDisplay;
663
664 /* Make sure we are thread safe */
665 XtToolkitThreadInitialize();
666 /* Set up the Clipbard application context and main window. We call all these functions
667 directly instead of calling XtOpenApplication() so that we can fail gracefully if we
668 can't get an X11 display. */
669 XtToolkitInitialize();
670 pCtx->appContext = XtCreateApplicationContext();
671 pDisplay = XtOpenDisplay(pCtx->appContext, 0, 0, "VBoxClipboard", 0, 0, &cArgc, &pcArgv);
672 if (NULL == pDisplay)
673 {
674 LogRel(("Shared clipboard: failed to connect to the host clipboard - the window system may not be running.\n"));
675 rc = VERR_NOT_SUPPORTED;
676 }
677 if (RT_SUCCESS(rc))
678 {
679 pCtx->widget = XtVaAppCreateShell(0, "VBoxClipboard", applicationShellWidgetClass, pDisplay,
680 XtNwidth, 1, XtNheight, 1, NULL);
681 if (NULL == pCtx->widget)
682 {
683 LogRel(("Shared clipboard: failed to construct the X11 window for the host clipboard manager.\n"));
684 rc = VERR_NO_MEMORY;
685 }
686 else
687 rc = vboxClipboardAddContext(pCtx);
688 }
689 if (RT_SUCCESS(rc))
690 {
691 XtSetMappedWhenManaged(pCtx->widget, false);
692 XtRealizeWidget(pCtx->widget);
693
694 /* Get hold of the atoms which we need */
695 pCtx->atomClipboard = XInternAtom(XtDisplay(pCtx->widget), "CLIPBOARD", false /* only_if_exists */);
696 pCtx->atomPrimary = XInternAtom(XtDisplay(pCtx->widget), "PRIMARY", false);
697 pCtx->atomTargets = XInternAtom(XtDisplay(pCtx->widget), "TARGETS", false);
698 pCtx->atomMultiple = XInternAtom(XtDisplay(pCtx->widget), "MULTIPLE", false);
699 pCtx->atomTimestamp = XInternAtom(XtDisplay(pCtx->widget), "TIMESTAMP", false);
700 pCtx->atomUtf8 = XInternAtom(XtDisplay(pCtx->widget), "UTF_STRING", false);
701 /* And build up the vector of supported formats */
702 pCtx->atomCText = XInternAtom(XtDisplay(pCtx->widget), "COMPOUND_TEXT", false);
703 /* And build up the vector of supported formats */
704 if (!g_testCText)
705 {
706 vboxClipboardAddFormat(pCtx, "UTF8_STRING", UTF8,
707 VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT);
708 vboxClipboardAddFormat(pCtx, "text/plain;charset=UTF-8", UTF8,
709 VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT);
710 vboxClipboardAddFormat(pCtx, "text/plain;charset=utf-8", UTF8,
711 VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT);
712 vboxClipboardAddFormat(pCtx, "STRING", UTF8,
713 VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT);
714 vboxClipboardAddFormat(pCtx, "TEXT", UTF8,
715 VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT);
716 vboxClipboardAddFormat(pCtx, "text/plain", UTF8,
717 VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT);
718}
719 if (!g_testUtf8)
720 vboxClipboardAddFormat(pCtx, "COMPOUND_TEXT", CTEXT,
721 VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT);
722 }
723 if (RT_FAILURE(rc))
724 vboxClipboardUninitX11(pCtx);
725 return rc;
726}
727
728/**
729 * Construct the X11 backend of the shared clipboard.
730 * @note X11 backend code
731 */
732VBOXCLIPBOARDCONTEXTX11 *VBoxX11ClipboardConstructX11
733 (VBOXCLIPBOARDCONTEXT *pFrontend)
734{
735 int rc;
736
737 VBOXCLIPBOARDCONTEXTX11 *pCtx = (VBOXCLIPBOARDCONTEXTX11 *)
738 RTMemAllocZ(sizeof(VBOXCLIPBOARDCONTEXTX11));
739 if (pCtx && !RTEnvGet("DISPLAY"))
740 {
741 /*
742 * If we don't find the DISPLAY environment variable we assume that
743 * we are not connected to an X11 server. Don't actually try to do
744 * this then, just fail silently and report success on every call.
745 * This is important for VBoxHeadless.
746 */
747 LogRelFunc(("X11 DISPLAY variable not set -- disabling shared clipboard\n"));
748 g_fHaveX11 = false;
749 return pCtx;
750 }
751
752 if (RTEnvGet("VBOX_CBTEST_UTF8"))
753 {
754 g_testUtf8 = true;
755 LogRel(("Host clipboard: testing Utf8\n"));
756 }
757 else if (RTEnvGet("VBOX_CBTEST_CTEXT"))
758 {
759 g_testCText = true;
760 LogRel(("Host clipboard: testing compound text\n"));
761 }
762 else if (RTEnvGet("VBOX_CBDEBUG"))
763 {
764 g_debugClipboard = true;
765 LogRel(("Host clipboard: enabling additional debugging output\n"));
766 }
767
768 g_fHaveX11 = true;
769
770 LogRel(("Initializing X11 clipboard backend\n"));
771 if (pCtx)
772 {
773 pCtx->pFrontend = pFrontend;
774 RTSemEventCreate(&pCtx->waitForData);
775 }
776 return pCtx;
777}
778
779/**
780 * Destruct the shared clipboard X11 backend.
781 * @note X11 backend code
782 */
783void VBoxX11ClipboardDestructX11(VBOXCLIPBOARDCONTEXTX11 *pCtx)
784{
785 /*
786 * Immediately return if we are not connected to the host X server.
787 */
788 if (!g_fHaveX11)
789 return;
790
791 /* We set this to NULL when the event thread exits. It really should
792 * have exited at this point, when we are about to unload the code from
793 * memory. */
794 Assert(pCtx->widget == NULL);
795 RTSemEventDestroy(pCtx->waitForData);
796}
797
798/**
799 * Announce to the X11 backend that we are ready to start.
800 * @param owner who is the initial clipboard owner
801 */
802int VBoxX11ClipboardStartX11(VBOXCLIPBOARDCONTEXTX11 *pCtx,
803 bool fOwnsClipboard)
804{
805 int rc = VINF_SUCCESS;
806 LogFlowFunc(("\n"));
807 /*
808 * Immediately return if we are not connected to the host X server.
809 */
810 if (!g_fHaveX11)
811 return VINF_SUCCESS;
812
813 rc = vboxClipboardInitX11(pCtx);
814 if (RT_SUCCESS(rc))
815 {
816 rc = RTThreadCreate(&pCtx->thread, vboxClipboardThread, pCtx, 0,
817 RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "SHCLIP");
818 if (RT_FAILURE(rc))
819 LogRel(("Failed to initialise the shared clipboard X11 backend.\n"));
820 }
821 if (RT_SUCCESS(rc))
822 {
823 if (fOwnsClipboard)
824 {
825 pCtx->eOwner = X11;
826 pCtx->notifyVBox = true;
827 }
828 else
829 {
830 /** @todo Check whether the guest gets a format announcement at
831 * startup. */
832 pCtx->eOwner = NONE;
833 VBoxX11ClipboardAnnounceVBoxFormat(pCtx, 0);
834 }
835 }
836 return rc;
837}
838
839/**
840 * Called when the VBox may have fallen out of sync with the backend.
841 * @note X11 backend code
842 */
843void VBoxX11ClipboardRequestSyncX11(VBOXCLIPBOARDCONTEXTX11 *pCtx)
844{
845 /*
846 * Immediately return if we are not connected to the host X server.
847 */
848 if (!g_fHaveX11)
849 return;
850 pCtx->notifyVBox = true;
851}
852
853/**
854 * Shut down the shared clipboard X11 backend.
855 * @note X11 backend code
856 * @note Any requests from this object to get clipboard data from VBox
857 * *must* have completed or aborted before we are called, as
858 * otherwise the X11 event loop will still be waiting for the request
859 * to return and will not be able to terminate.
860 */
861int VBoxX11ClipboardStopX11(VBOXCLIPBOARDCONTEXTX11 *pCtx)
862{
863 int rc, rcThread;
864 unsigned count = 0;
865 XEvent ev;
866 /*
867 * Immediately return if we are not connected to the host X server.
868 */
869 if (!g_fHaveX11)
870 return VINF_SUCCESS;
871
872 /* This might mean that we are getting stopped twice. */
873 AssertReturn(pCtx->widget != NULL, VERR_WRONG_ORDER);
874 LogRelFunc(("stopping the shared clipboard X11 backend\n"));
875
876 /* Set the termination flag to tell the Xt event loop to exit. We
877 * reiterate that any outstanding requests from the X11 event loop to
878 * the VBox part *must* have returned before we do this. */
879 XtAppSetExitFlag(pCtx->appContext);
880 /* Wake up the event loop */
881 memset(&ev, 0, sizeof(ev));
882 ev.xclient.type = ClientMessage;
883 ev.xclient.format = 8;
884 XSendEvent(XtDisplay(pCtx->widget), XtWindow(pCtx->widget), false, 0, &ev);
885 XFlush(XtDisplay(pCtx->widget));
886 pCtx->eOwner = NONE;
887 pCtx->X11TextFormat = INVALID;
888 pCtx->X11BitmapFormat = INVALID;
889 do
890 {
891 rc = RTThreadWait(pCtx->thread, 1000, &rcThread);
892 ++count;
893 Assert(RT_SUCCESS(rc) || ((VERR_TIMEOUT == rc) && (count != 5)));
894 } while ((VERR_TIMEOUT == rc) && (count < 300));
895 if (RT_SUCCESS(rc))
896 AssertRC(rcThread);
897 else
898 LogRelFunc(("rc=%Rrc\n", rc));
899 vboxClipboardUninitX11(pCtx);
900 LogFlowFunc(("returning %Rrc.\n", rc));
901 return rc;
902}
903
904/**
905 * Satisfy a request from X11 for clipboard targets supported by VBox.
906 *
907 * @returns true if we successfully convert the data to the format
908 * requested, false otherwise.
909 *
910 * @param atomTypeReturn The type of the data we are returning
911 * @param pValReturn A pointer to the data we are returning. This
912 * should be set to memory allocated by XtMalloc,
913 * which will be freed later by the Xt toolkit.
914 * @param pcLenReturn The length of the data we are returning
915 * @param piFormatReturn The format (8bit, 16bit, 32bit) of the data we are
916 * returning
917 * @note X11 backend code, called by the XtOwnSelection callback.
918 */
919static Boolean vboxClipboardConvertTargetsForX11(VBOXCLIPBOARDCONTEXTX11
920 *pCtx,
921 Atom *atomTypeReturn,
922 XtPointer *pValReturn,
923 unsigned long *pcLenReturn,
924 int *piFormatReturn)
925{
926 unsigned uListSize = pCtx->formatList.size();
927 Atom *atomTargets = reinterpret_cast<Atom *>(XtMalloc((uListSize + 3) * sizeof(Atom)));
928 unsigned cTargets = 0;
929
930 LogFlowFunc (("called\n"));
931 for (unsigned i = 0; i < uListSize; ++i)
932 {
933 if ( ((pCtx->vboxFormats & VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT) != 0)
934 && ( pCtx->formatList[i].guestFormat
935 == VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT))
936 {
937 atomTargets[cTargets] = pCtx->formatList[i].atom;
938 ++cTargets;
939 }
940 }
941 atomTargets[cTargets] = pCtx->atomTargets;
942 atomTargets[cTargets + 1] = pCtx->atomMultiple;
943 atomTargets[cTargets + 2] = pCtx->atomTimestamp;
944 if (g_debugClipboard)
945 {
946 for (unsigned i = 0; i < cTargets + 3; i++)
947 {
948 char *szAtomName = XGetAtomName(XtDisplay(pCtx->widget), atomTargets[i]);
949 if (szAtomName != 0)
950 {
951 Log2 (("%s: returning target %s\n", __PRETTY_FUNCTION__,
952 szAtomName));
953 XFree(szAtomName);
954 }
955 else
956 {
957 Log(("%s: invalid atom %d in the list!\n", __PRETTY_FUNCTION__,
958 atomTargets[i]));
959 }
960 }
961 }
962 *atomTypeReturn = XA_ATOM;
963 *pValReturn = reinterpret_cast<XtPointer>(atomTargets);
964 *pcLenReturn = cTargets + 3;
965 *piFormatReturn = 32;
966 return true;
967}
968
969/**
970 * Satisfy a request from X11 to convert the clipboard text to Utf8. We
971 * return non-zero terminated text.
972 * @todo that works, but it is bad. Change it to return zero-terminated
973 * text.
974 *
975 * @returns true if we successfully convert the data to the format
976 * requested, false otherwise.
977 *
978 * @param atomTypeReturn Where to store the atom for the type of the data
979 * we are returning
980 * @param pValReturn Where to store the pointer to the data we are
981 * returning. This should be to memory allocated by
982 * XtMalloc, which will be freed by the Xt toolkit
983 * later.
984 * @param pcLenReturn Where to store the length of the data we are
985 * returning
986 * @param piFormatReturn Where to store the bit width (8, 16, 32) of the
987 * data we are returning
988 * @note X11 backend code, called by the callback for XtOwnSelection.
989 */
990static Boolean vboxClipboardConvertToUtf8ForX11(VBOXCLIPBOARDCONTEXTX11
991 *pCtx,
992 Atom *atomTypeReturn,
993 XtPointer *pValReturn,
994 unsigned long *pcLenReturn,
995 int *piFormatReturn)
996{
997 PRTUTF16 pu16SrcText, pu16DestText;
998 char *pu8DestText;
999 void *pvVBox = NULL;
1000 uint32_t cbVBox = 0;
1001 size_t cwSrcLen, cwDestLen, cbDestLen;
1002 int rc;
1003
1004 LogFlowFunc (("called\n"));
1005 /* Read the clipboard data from the guest. */
1006 rc = VBoxX11ClipboardReadVBoxData(pCtx->pFrontend, VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT, &pvVBox, &cbVBox);
1007 if ((rc != VINF_SUCCESS) || (cbVBox == 0))
1008 {
1009 /* If VBoxX11ClipboardReadVBoxData fails then pClient may be invalid */
1010 LogRelFunc (("VBoxX11ClipboardReadVBoxData returned %Rrc%s\n", rc,
1011 RT_SUCCESS(rc) ? ", cbVBox == 0" : ""));
1012 RTMemFree(pvVBox);
1013 return false;
1014 }
1015 pu16SrcText = reinterpret_cast<PRTUTF16>(pvVBox);
1016 cwSrcLen = cbVBox / 2;
1017 /* How long will the converted text be? */
1018 rc = vboxClipboardUtf16GetLinSize(pu16SrcText, cwSrcLen, &cwDestLen);
1019 if (RT_FAILURE(rc))
1020 {
1021 LogRelFunc (("clipboard conversion failed. vboxClipboardUtf16GetLinSize returned %Rrc. Abandoning.\n", rc));
1022 RTMemFree(pvVBox);
1023 AssertRCReturn(rc, false);
1024 }
1025 if (cwDestLen == 0)
1026 {
1027 LogFlowFunc(("received empty clipboard data from the guest, returning false.\n"));
1028 RTMemFree(pvVBox);
1029 return false;
1030 }
1031 pu16DestText = reinterpret_cast<PRTUTF16>(RTMemAlloc(cwDestLen * 2));
1032 if (pu16DestText == 0)
1033 {
1034 LogRelFunc (("failed to allocate %d bytes\n", cwDestLen * 2));
1035 RTMemFree(pvVBox);
1036 return false;
1037 }
1038 /* Convert the text. */
1039 rc = vboxClipboardUtf16WinToLin(pu16SrcText, cwSrcLen, pu16DestText, cwDestLen);
1040 if (RT_FAILURE(rc))
1041 {
1042 LogRelFunc (("clipboard conversion failed. vboxClipboardUtf16WinToLin() returned %Rrc. Abandoning.\n", rc));
1043 RTMemFree(reinterpret_cast<void *>(pu16DestText));
1044 RTMemFree(pvVBox);
1045 return false;
1046 }
1047 /* Allocate enough space, as RTUtf16ToUtf8Ex may fail if the
1048 space is too tightly calculated. */
1049 pu8DestText = XtMalloc(cwDestLen * 4);
1050 if (pu8DestText == 0)
1051 {
1052 LogRelFunc (("failed to allocate %d bytes\n", cwDestLen * 4));
1053 RTMemFree(reinterpret_cast<void *>(pu16DestText));
1054 RTMemFree(pvVBox);
1055 return false;
1056 }
1057 /* Convert the Utf16 string to Utf8. */
1058 rc = RTUtf16ToUtf8Ex(pu16DestText + 1, cwDestLen - 1, &pu8DestText, cwDestLen * 4,
1059 &cbDestLen);
1060 RTMemFree(reinterpret_cast<void *>(pu16DestText));
1061 if (RT_FAILURE(rc))
1062 {
1063 LogRelFunc (("clipboard conversion failed. RTUtf16ToUtf8Ex() returned %Rrc. Abandoning.\n", rc));
1064 XtFree(pu8DestText);
1065 RTMemFree(pvVBox);
1066 return false;
1067 }
1068 LogFlowFunc (("converted string is %.*s. Returning.\n", cbDestLen, pu8DestText));
1069 RTMemFree(pvVBox);
1070 *atomTypeReturn = pCtx->atomUtf8;
1071 *pValReturn = reinterpret_cast<XtPointer>(pu8DestText);
1072 *pcLenReturn = cbDestLen;
1073 *piFormatReturn = 8;
1074 return true;
1075}
1076
1077/**
1078 * Satisfy a request from X11 to convert the clipboard text to
1079 * COMPOUND_TEXT. We return non-zero terminated text.
1080 * @todo that works, but it is bad. Change it to return zero-terminated
1081 * text.
1082 *
1083 * @returns true if we successfully convert the data to the format
1084 * requested, false otherwise.
1085 *
1086 * @param atomTypeReturn Where to store the atom for the type of the data
1087 * we are returning
1088 * @param pValReturn Where to store the pointer to the data we are
1089 * returning. This should be to memory allocated by
1090 * XtMalloc, which will be freed by the Xt toolkit
1091 * later.
1092 * @param pcLenReturn Where to store the length of the data we are
1093 * returning
1094 * @param piFormatReturn Where to store the bit width (8, 16, 32) of the
1095 * data we are returning
1096 * @note X11 backend code, called by the callback for XtOwnSelection.
1097 */
1098static Boolean vboxClipboardConvertToCTextForX11(VBOXCLIPBOARDCONTEXTX11
1099 *pCtx,
1100 Atom *atomTypeReturn,
1101 XtPointer *pValReturn,
1102 unsigned long *pcLenReturn,
1103 int *piFormatReturn)
1104{
1105 PRTUTF16 pu16SrcText, pu16DestText;
1106 void *pvVBox = NULL;
1107 uint32_t cbVBox = 0;
1108 char *pu8DestText = 0;
1109 size_t cwSrcLen, cwDestLen, cbDestLen;
1110 XTextProperty property;
1111 int rc;
1112
1113 LogFlowFunc (("called\n"));
1114 /* Read the clipboard data from the guest. */
1115 rc = VBoxX11ClipboardReadVBoxData(pCtx->pFrontend, VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT, &pvVBox, &cbVBox);
1116 if ((rc != VINF_SUCCESS) || (cbVBox == 0))
1117 {
1118 /* If VBoxX11ClipboardReadVBoxData fails then pClient may be invalid */
1119 LogRelFunc (("VBoxX11ClipboardReadVBoxData returned %Rrc%s\n", rc,
1120 RT_SUCCESS(rc) ? ", cbVBox == 0" : ""));
1121 RTMemFree(pvVBox);
1122 return false;
1123 }
1124 pu16SrcText = reinterpret_cast<PRTUTF16>(pvVBox);
1125 cwSrcLen = cbVBox / 2;
1126 /* How long will the converted text be? */
1127 rc = vboxClipboardUtf16GetLinSize(pu16SrcText, cwSrcLen, &cwDestLen);
1128 if (RT_FAILURE(rc))
1129 {
1130 LogRelFunc (("clipboard conversion failed. vboxClipboardUtf16GetLinSize returned %Rrc. Abandoning.\n", rc));
1131 RTMemFree(pvVBox);
1132 AssertRCReturn(rc, false);
1133 }
1134 if (cwDestLen == 0)
1135 {
1136 LogFlowFunc(("received empty clipboard data from the guest, returning false.\n"));
1137 RTMemFree(pvVBox);
1138 return false;
1139 }
1140 pu16DestText = reinterpret_cast<PRTUTF16>(RTMemAlloc(cwDestLen * 2));
1141 if (pu16DestText == 0)
1142 {
1143 LogRelFunc (("failed to allocate %d bytes\n", cwDestLen * 2));
1144 RTMemFree(pvVBox);
1145 return false;
1146 }
1147 /* Convert the text. */
1148 rc = vboxClipboardUtf16WinToLin(pu16SrcText, cwSrcLen, pu16DestText, cwDestLen);
1149 if (RT_FAILURE(rc))
1150 {
1151 LogRelFunc (("clipboard conversion failed. vboxClipboardUtf16WinToLin() returned %Rrc. Abandoning.\n", rc));
1152 RTMemFree(reinterpret_cast<void *>(pu16DestText));
1153 RTMemFree(pvVBox);
1154 return false;
1155 }
1156 /* Convert the Utf16 string to Utf8. */
1157 rc = RTUtf16ToUtf8Ex(pu16DestText + 1, cwDestLen - 1, &pu8DestText, 0, &cbDestLen);
1158 RTMemFree(reinterpret_cast<void *>(pu16DestText));
1159 if (RT_FAILURE(rc))
1160 {
1161 LogRelFunc (("clipboard conversion failed. RTUtf16ToUtf8Ex() returned %Rrc. Abandoning.\n", rc));
1162 RTMemFree(pvVBox);
1163 return false;
1164 }
1165 /* And finally (!) convert the Utf8 text to compound text. */
1166#ifdef RT_OS_SOLARIS
1167 rc = XmbTextListToTextProperty(XtDisplay(pCtx->widget), &pu8DestText, 1,
1168 XCompoundTextStyle, &property);
1169#else
1170 rc = Xutf8TextListToTextProperty(XtDisplay(pCtx->widget), &pu8DestText, 1,
1171 XCompoundTextStyle, &property);
1172#endif
1173 RTMemFree(pu8DestText);
1174 if (rc < 0)
1175 {
1176 const char *pcReason;
1177 switch(rc)
1178 {
1179 case XNoMemory:
1180 pcReason = "out of memory";
1181 break;
1182 case XLocaleNotSupported:
1183 pcReason = "locale (Utf8) not supported";
1184 break;
1185 case XConverterNotFound:
1186 pcReason = "converter not found";
1187 break;
1188 default:
1189 pcReason = "unknown error";
1190 }
1191 LogRelFunc (("Xutf8TextListToTextProperty failed. Reason: %s\n",
1192 pcReason));
1193 RTMemFree(pvVBox);
1194 return false;
1195 }
1196 LogFlowFunc (("converted string is %s. Returning.\n", property.value));
1197 RTMemFree(pvVBox);
1198 *atomTypeReturn = property.encoding;
1199 *pValReturn = reinterpret_cast<XtPointer>(property.value);
1200 *pcLenReturn = property.nitems;
1201 *piFormatReturn = property.format;
1202 return true;
1203}
1204
1205/**
1206 * Return VBox's clipboard data for an X11 client.
1207 * @note X11 backend code, callback for XtOwnSelection
1208 */
1209static Boolean vboxClipboardConvertForX11(Widget widget, Atom *atomSelection,
1210 Atom *atomTarget,
1211 Atom *atomTypeReturn,
1212 XtPointer *pValReturn,
1213 unsigned long *pcLenReturn,
1214 int *piFormatReturn)
1215{
1216 g_eClipboardFormats eFormat = INVALID;
1217 VBOXCLIPBOARDCONTEXTX11 *pCtx = vboxClipboardFindContext(widget);
1218
1219 LogFlowFunc(("\n"));
1220 /* Drop requests that we receive too late. */
1221 if (pCtx->eOwner != VB)
1222 return false;
1223 if ( (*atomSelection != pCtx->atomClipboard)
1224 && (*atomSelection != pCtx->atomPrimary)
1225 )
1226 {
1227 LogFlowFunc(("rc = false\n"));
1228 return false;
1229 }
1230 if (g_debugClipboard)
1231 {
1232 char *szAtomName = XGetAtomName(XtDisplay(pCtx->widget), *atomTarget);
1233 if (szAtomName != 0)
1234 {
1235 Log2 (("%s: request for format %s\n", __PRETTY_FUNCTION__, szAtomName));
1236 XFree(szAtomName);
1237 }
1238 else
1239 {
1240 LogFunc (("request for invalid target atom %d!\n", *atomTarget));
1241 }
1242 }
1243 if (*atomTarget == pCtx->atomTargets)
1244 {
1245 eFormat = TARGETS;
1246 }
1247 else
1248 {
1249 for (unsigned i = 0; i != pCtx->formatList.size(); ++i)
1250 {
1251 if (pCtx->formatList[i].atom == *atomTarget)
1252 {
1253 eFormat = pCtx->formatList[i].format;
1254 break;
1255 }
1256 }
1257 }
1258 switch (eFormat)
1259 {
1260 case TARGETS:
1261 return vboxClipboardConvertTargetsForX11(pCtx, atomTypeReturn,
1262 pValReturn, pcLenReturn,
1263 piFormatReturn);
1264 case UTF8:
1265 return vboxClipboardConvertToUtf8ForX11(pCtx, atomTypeReturn,
1266 pValReturn, pcLenReturn,
1267 piFormatReturn);
1268 case CTEXT:
1269 return vboxClipboardConvertToCTextForX11(pCtx, atomTypeReturn,
1270 pValReturn, pcLenReturn,
1271 piFormatReturn);
1272 default:
1273 LogFunc (("bad format\n"));
1274 return false;
1275 }
1276}
1277
1278/**
1279 * This is called by the X toolkit intrinsics to let us know that another
1280 * X11 client has taken the clipboard. In this case we notify VBox that
1281 * we want ownership of the clipboard.
1282 * @note X11 backend code, callback for XtOwnSelection
1283 */
1284static void vboxClipboardReturnToX11(Widget widget, Atom *)
1285{
1286 VBOXCLIPBOARDCONTEXTX11 *pCtx = vboxClipboardFindContext(widget);
1287 LogFlowFunc (("called, giving VBox clipboard ownership\n"));
1288 pCtx->eOwner = X11;
1289 pCtx->notifyVBox = true;
1290}
1291
1292/**
1293 * VBox is taking possession of the shared clipboard.
1294 *
1295 * @param u32Formats Clipboard formats the guest is offering
1296 * @note X11 backend code
1297 */
1298void VBoxX11ClipboardAnnounceVBoxFormat(VBOXCLIPBOARDCONTEXTX11 *pCtx,
1299 uint32_t u32Formats)
1300{
1301 /*
1302 * Immediately return if we are not connected to the host X server.
1303 */
1304 if (!g_fHaveX11)
1305 return;
1306
1307 LogFlowFunc (("u32Formats=%d\n", u32Formats));
1308 pCtx->vboxFormats = u32Formats;
1309 if (u32Formats == 0)
1310 {
1311 /* This is just an automatism, not a genuine anouncement */
1312 XtDisownSelection(pCtx->widget, pCtx->atomClipboard, CurrentTime);
1313 pCtx->eOwner = NONE;
1314 LogFlowFunc(("returning\n"));
1315 return;
1316 }
1317 Log2 (("%s: giving the guest clipboard ownership\n", __PRETTY_FUNCTION__));
1318 pCtx->eOwner = VB;
1319 pCtx->X11TextFormat = INVALID;
1320 pCtx->X11BitmapFormat = INVALID;
1321 if (XtOwnSelection(pCtx->widget, pCtx->atomClipboard, CurrentTime,
1322 vboxClipboardConvertForX11, vboxClipboardReturnToX11,
1323 0) != True)
1324 {
1325 Log2 (("%s: returning clipboard ownership to the host\n", __PRETTY_FUNCTION__));
1326 /* We set this so that the guest gets notified when we take the clipboard, even if no
1327 guest formats are found which we understand. */
1328 pCtx->notifyVBox = true;
1329 pCtx->eOwner = X11;
1330 }
1331 XtOwnSelection(pCtx->widget, pCtx->atomPrimary, CurrentTime, vboxClipboardConvertForX11,
1332 NULL, 0);
1333 LogFlowFunc(("returning\n"));
1334
1335}
1336
1337/**
1338 * Called when VBox wants to read the X11 clipboard.
1339 *
1340 * @param pClient Context information about the guest VM
1341 * @param u32Format The format that the guest would like to receive the data in
1342 * @param pv Where to write the data to
1343 * @param cb The size of the buffer to write the data to
1344 * @param pcbActual Where to write the actual size of the written data
1345 * @note X11 backend code
1346 */
1347int VBoxX11ClipboardReadX11Data(VBOXCLIPBOARDCONTEXTX11 *pCtx,
1348 uint32_t u32Format,
1349 VBOXCLIPBOARDREQUEST *pRequest)
1350{
1351 /*
1352 * Immediately return if we are not connected to the host X server.
1353 */
1354 if (!g_fHaveX11)
1355 {
1356 /* no data available */
1357 *pRequest->pcbActual = 0;
1358 pRequest->rc = VINF_SUCCESS;
1359 return VINF_SUCCESS;
1360 }
1361
1362 LogFlowFunc (("u32Format = %d, cb = %d\n", u32Format, pRequest->cb));
1363 pRequest->rc = VERR_WRONG_ORDER;
1364
1365 /*
1366 * VBox wants to read data in the given format.
1367 */
1368 if (u32Format == VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT)
1369 {
1370 if (pCtx->X11TextFormat == INVALID)
1371 {
1372 /* No data available. */
1373 *pRequest->pcbActual = 0;
1374 return VERR_NO_DATA; /* VBox thinks we have data and we don't */
1375 }
1376 /* Initially set the size of the data read to zero in case we fail
1377 * somewhere. */
1378 *pRequest->pcbActual = 0;
1379 /* Send out a request for the data to the current clipboard owner */
1380 XtGetSelectionValue(pCtx->widget, pCtx->atomClipboard,
1381 pCtx->atomX11TextFormat,
1382 vboxClipboardGetDataFromX11,
1383 reinterpret_cast<XtPointer>(pRequest),
1384 CurrentTime);
1385 /* When the data arrives, the vboxClipboardGetDataFromX11 callback
1386 * will be called. The callback will signal the event semaphore when
1387 * it has processed the data for us. */
1388
1389 int rc = RTSemEventWait(pCtx->waitForData, RT_INDEFINITE_WAIT);
1390 if (RT_FAILURE(rc))
1391 return rc;
1392 }
1393 else
1394 {
1395 return VERR_NOT_IMPLEMENTED;
1396 }
1397 LogFlowFunc(("returning %Rrc\n", pRequest->rc));
1398 return pRequest->rc;
1399}
1400
1401#ifdef TESTCASE
1402
1403#include <iprt/initterm.h>
1404#include <iprt/stream.h>
1405
1406enum { MAX_ATTEMPTS = 10 };
1407
1408static VBOXCLIPBOARDCONTEXTX11 *g_pCtxTestX11;
1409static VBOXCLIPBOARDCONTEXTX11 *g_pCtxTestVBox;
1410
1411/** Quick Utf16 string class, initialised from a Utf8 string */
1412class testUtf16String
1413{
1414 /** Stores the Utf16 data. NULL if something went wrong. */
1415 PRTUTF16 mData;
1416 /** Stores the size in bytes of the data. 0 if something went wrong. */
1417 size_t mSize;
1418public:
1419 /** Constructor
1420 * @param aString the Utf8 representation of the string data
1421 */
1422 testUtf16String(const char *aString)
1423 {
1424 int rc = RTStrToUtf16(aString, &mData);
1425 if (RT_FAILURE(rc))
1426 {
1427 mData = NULL;
1428 mSize = 0;
1429 return;
1430 }
1431 mSize = RTUtf16Len(mData) * 2 + 2;
1432 }
1433 /** Destructor */
1434 ~testUtf16String()
1435 {
1436 RTUtf16Free(mData);
1437 }
1438 /** Getter for the data */
1439 PCRTUTF16 getData() { return mData; }
1440 /** Getter for the data size */
1441 size_t getSize() { return mSize; }
1442};
1443
1444/** Parameters for a test run. */
1445static struct testData
1446{
1447 /** The string data we are offering. */
1448 testUtf16String data;
1449 /** The size of the buffer to receive the request data */
1450 uint32_t cchBuffer;
1451 /** The format we request the data in */
1452 PCRTUTF16 getData() { return data.getData(); }
1453 /** Getter for the Utf16 string data size in bytes */
1454 size_t getSize() { return data.getSize(); }
1455 /** Is the test expected to produce a buffer overflow? */
1456 bool overflow() { return cchBuffer < getSize(); }
1457} g_sTestData[] =
1458{
1459 { "Hello world\r\n", 256 },
1460 { "Goodbye world", 28 },
1461 { "", 2 },
1462 /* This should produce a buffer overflow */
1463 { "Goodbye world", 27 },
1464};
1465
1466/** Which line of the table above are we currently testing? */
1467size_t g_testNum = 0;
1468enum { MAX_TESTS = RT_ELEMENTS(g_sTestData) };
1469/** Are we doing a timeout test? Ugly, but whatever. */
1470static bool g_testTimeout = false;
1471
1472/** @todo the current code can't test the following conditions:
1473 * * the X11 clipboard offers non-Utf8 text
1474 * * the X11 clipboard offers invalid Utf8 text
1475 */
1476/** @todo stress test our context store code? Probably not important... */
1477
1478int VBoxX11ClipboardReadVBoxData(VBOXCLIPBOARDCONTEXT *pCtx,
1479 uint32_t u32Format, void **ppv,
1480 uint32_t *pcb)
1481{
1482 LogFlowFunc(("pCtx = %p\n", pCtx));
1483 /* This should only ever be called for the second "test" clipboard. */
1484 AssertReturn(pCtx == (VBOXCLIPBOARDCONTEXT *)g_pCtxTestX11
1485 /* Magic cookie */, VERR_WRONG_ORDER);
1486 /* Timeout test hack */
1487 if (g_testTimeout)
1488 {
1489 RTThreadSleep(200);
1490 /* Try to return data after we have been timed out. */
1491 *ppv = RTMemDup((const void *) "Scribblings", sizeof("Scribblings"));
1492 *pcb = sizeof("Scribblings");
1493 LogFlowFunc(("sleep finished, returning\n"));
1494 return VINF_SUCCESS;
1495 }
1496 /* Sanity. */
1497 AssertReturn(g_testNum < MAX_TESTS, VERR_WRONG_ORDER);
1498 testData *pData = &g_sTestData[g_testNum];
1499 AssertPtrReturn(pData->getData(), VERR_NO_MEMORY);
1500 AssertReturn(pData->getSize(), VERR_WRONG_ORDER);
1501 void *retval = RTMemDup((const void *) pData->getData(),
1502 pData->getSize());
1503 AssertPtrReturn(retval, VERR_NO_MEMORY);
1504 *ppv = (void *) retval;
1505 *pcb = pData->getSize();
1506 LogFlowFunc(("returning\n"));
1507 return VINF_SUCCESS;
1508}
1509
1510/** As long as our test is running, we grab back the clipboard if X11 ever
1511 * tries to take it away. I hope we don't end up with a tug of war... */
1512void VBoxX11ClipboardReportX11Formats(VBOXCLIPBOARDCONTEXT *pCtx,
1513 uint32_t u32Formats)
1514{
1515 LogFlowFunc(("pCtx = %p\n", pCtx));
1516 if (pCtx == (VBOXCLIPBOARDCONTEXT *)g_pCtxTestX11) /* Magic cookie */
1517 VBoxX11ClipboardAnnounceVBoxFormat(g_pCtxTestVBox,
1518 VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT);
1519}
1520
1521/** Initial tests that can be done while the clipboard contents are still
1522 * invalid.
1523 * @returns boolean success value
1524 * @note prints status information to stdout
1525 */
1526bool testInvalid(void)
1527{
1528 bool fSuccess = true;
1529 char pc[256];
1530 uint32_t cbActual;
1531 VBOXCLIPBOARDREQUEST request = {(void *) pc, sizeof(pc),
1532 &cbActual, VINF_SUCCESS, g_pCtxTestX11};
1533 RTPrintf("tstClipboardX11: TESTING a request for an invalid data format\n");
1534 int rc = VBoxX11ClipboardReadX11Data(g_pCtxTestX11,
1535 0xffff, &request);
1536 RTPrintf("Returned %Rrc - %s\n", rc,
1537 rc == VERR_NOT_IMPLEMENTED ? "SUCCESS" : "FAILURE");
1538 if (rc != VERR_NOT_IMPLEMENTED)
1539 fSuccess = false;
1540 RTPrintf("tstClipboardX11: TESTING a request for data from an empty clipboard\n");
1541 rc = VBoxX11ClipboardReadX11Data(g_pCtxTestX11,
1542 VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT, &request);
1543 RTPrintf("Returned %Rrc - %s\n", rc,
1544 rc == VERR_NO_DATA ? "SUCCESS" : "FAILURE");
1545 if (rc != VERR_NO_DATA)
1546 fSuccess = false;
1547 return fSuccess;
1548}
1549
1550/** Tests an entry in the table above.
1551 * @returns boolean success value
1552 * @note prints status information to stdout
1553 */
1554bool testEntry(testData *pData)
1555{
1556 bool fSuccess = false;
1557 char pc[256];
1558 uint32_t cbActual;
1559 VBOXCLIPBOARDREQUEST request = {(void *) pc, pData->cchBuffer,
1560 &cbActual, VINF_SUCCESS, g_pCtxTestX11};
1561 for (int i = 0; (i < MAX_ATTEMPTS) && !fSuccess; ++i)
1562 {
1563 int rc = VBoxX11ClipboardReadX11Data(g_pCtxTestX11,
1564 VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT, &request);
1565 AssertReturn(RT_SUCCESS(rc), 1);
1566 /* Did we expect and get an overflow? */
1567 if ( RT_SUCCESS(rc)
1568 && (cbActual == pData->getSize())
1569 && pData->overflow())
1570 fSuccess = true;
1571 /* Did we expect a string and get it? */
1572 else if ( RT_SUCCESS(rc)
1573 && (memcmp(pc, pData->getData(),
1574 pData->getSize()) == 0))
1575 fSuccess = true;
1576 else
1577 RTThreadSleep(50);
1578 if (fSuccess)
1579 RTPrintf("text %ls, %sretval %Rrc - SUCCESS\n",
1580 pData->getData(),
1581 pData->overflow() ? "buffer overflow as expected, "
1582 : "", rc);
1583 else
1584 RTPrintf("text %ls, retval %Rrc, attempt %d of %d\n",
1585 pData->getData(), rc, i + 1,
1586 MAX_ATTEMPTS);
1587 }
1588 if (!fSuccess)
1589 RTPrintf("FAILURE. Last string obtained was %.*lS\n",
1590 RT_MIN(cbActual / 2, 20), pc);
1591 return fSuccess;
1592}
1593
1594int main()
1595{
1596 int rc = VINF_SUCCESS;
1597 int status = 0;
1598 /* We can't reasonably test anything without an X session, so just
1599 * continue. */
1600 if (!RTEnvGet("DISPLAY"))
1601 return 0;
1602 g_debugClipboard = true;
1603 RTR3Init();
1604 g_pCtxTestX11 = VBoxX11ClipboardConstructX11(NULL);
1605 g_pCtxTestVBox =
1606 VBoxX11ClipboardConstructX11((VBOXCLIPBOARDCONTEXT *)g_pCtxTestX11);
1607 rc = VBoxX11ClipboardStartX11(g_pCtxTestVBox, false);
1608 AssertRCReturn(rc, 1);
1609 bool fSuccess = false;
1610 /* Test a request for an invalid data format and data from an empty
1611 * clipboard */
1612 for (unsigned i = 0; (i < MAX_ATTEMPTS) && !fSuccess; ++i)
1613 {
1614 rc = VBoxX11ClipboardStartX11(g_pCtxTestX11, false);
1615 AssertRCReturn(rc, 1);
1616 fSuccess = testInvalid();
1617 VBoxX11ClipboardStopX11(g_pCtxTestX11);
1618 if (!fSuccess)
1619 {
1620 RTPrintf("attempt %d of %d\n", i + 1, MAX_ATTEMPTS + 1);
1621 RTThreadSleep(50);
1622 }
1623 }
1624 if (!fSuccess)
1625 status = 1;
1626 rc = VBoxX11ClipboardStartX11(g_pCtxTestX11, true);
1627 AssertRCReturn(rc, 1);
1628 /* Claim the clipboard and make sure we get it */
1629 VBoxX11ClipboardAnnounceVBoxFormat(g_pCtxTestVBox,
1630 VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT);
1631 for (unsigned i = 0;
1632 i < 255
1633 && g_pCtxTestX11->X11TextFormat == INVALID;
1634 ++i)
1635 RTThreadSleep(50);
1636 AssertReturn(g_pCtxTestX11->X11TextFormat != INVALID, 1);
1637 /* Do general purpose clipboard tests */
1638 for (int i = 0; i < 2; ++i)
1639 {
1640 g_testUtf8 = (i == 0);
1641 g_testCText = (i == 1);
1642 RTPrintf("tstClipboardX11: TESTING sending and receiving of %s\n",
1643 i == 0 ? "Utf-8" : "COMPOUND TEXT");
1644 for (g_testNum = 0; g_testNum < MAX_TESTS; ++g_testNum)
1645 {
1646 if (!testEntry(&g_sTestData[g_testNum]))
1647 status = 1;
1648 }
1649 }
1650 /* Finally test timeouts. */
1651 XtAppSetSelectionTimeout(g_pCtxTestX11->appContext, 10);
1652 RTPrintf("tstClipboardX11: TESTING the clipboard timeout\n");
1653 rc = VINF_SUCCESS;
1654 g_testTimeout = true;
1655 for (unsigned i = 0; (i < MAX_ATTEMPTS) && (rc != VERR_TIMEOUT); ++i)
1656 {
1657 char pc[256];
1658 uint32_t cbActual;
1659 VBOXCLIPBOARDREQUEST request = {(void *) pc, sizeof(pc),
1660 &cbActual, VINF_SUCCESS,
1661 g_pCtxTestX11};
1662 rc = VBoxX11ClipboardReadX11Data(g_pCtxTestX11,
1663 VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT, &request);
1664 AssertMsg( RT_SUCCESS(rc)
1665 || (rc == VERR_TIMEOUT)
1666 || (rc == VERR_NO_DATA),
1667 ("rc = %Rrc\n", rc));
1668 if (rc == VERR_TIMEOUT)
1669 RTPrintf("SUCCESS\n");
1670 else
1671 {
1672 RTPrintf("Attempt %d of %d\n", i + 1, MAX_ATTEMPTS);
1673 RTThreadSleep(50);
1674 }
1675 }
1676 if (rc != VERR_TIMEOUT)
1677 status = 1;
1678 rc = VBoxX11ClipboardStopX11(g_pCtxTestX11);
1679 AssertRCReturn(rc, 1);
1680 rc = VBoxX11ClipboardStopX11(g_pCtxTestVBox);
1681 AssertRCReturn(rc, 1);
1682 VBoxX11ClipboardDestructX11(g_pCtxTestX11);
1683 VBoxX11ClipboardDestructX11(g_pCtxTestVBox);
1684 return status;
1685}
1686
1687#endif /* TESTCASE defined */
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