VirtualBox

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

Last change on this file since 78582 was 78582, checked in by vboxsync, 5 years ago

Shared Clipboard/URI: Build fix.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 103.5 KB
Line 
1/** @file
2 *
3 * Shared Clipboard:
4 * X11 backend code.
5 */
6
7/*
8 * Copyright (C) 2006-2019 Oracle Corporation
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
19/* Note: to automatically run regression tests on the shared clipboard,
20 * execute the tstClipboardX11 testcase. If you often make changes to the
21 * clipboard code, adding the line
22 * OTHERS += $(PATH_tstClipboardX11)/tstClipboardX11.run
23 * to LocalConfig.kmk will cause the tests to be run every time the code is
24 * changed. */
25
26
27/*********************************************************************************************************************************
28* Header Files *
29*********************************************************************************************************************************/
30#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD
31
32#include <errno.h>
33
34#include <dlfcn.h>
35#include <fcntl.h>
36#include <unistd.h>
37
38#ifdef RT_OS_SOLARIS
39#include <tsol/label.h>
40#endif
41
42#include <X11/Xlib.h>
43#include <X11/Xatom.h>
44#include <X11/Intrinsic.h>
45#include <X11/Shell.h>
46#include <X11/Xproto.h>
47#include <X11/StringDefs.h>
48
49#include <iprt/types.h>
50#include <iprt/mem.h>
51#include <iprt/semaphore.h>
52#include <iprt/thread.h>
53#include <iprt/utf16.h>
54
55#include <VBox/log.h>
56#include <VBox/version.h>
57
58#include <VBox/GuestHost/SharedClipboard.h>
59#include <VBox/GuestHost/clipboard-helper.h>
60#include <VBox/HostServices/VBoxClipboardSvc.h>
61
62
63/*********************************************************************************************************************************
64* Defined Constants And Macros *
65*********************************************************************************************************************************/
66/* The serialisation mechanism looks like it is not needed (everything using it
67 * runs on one thread, and the flag is always cleared at the end of calls which
68 * use it). So we will remove it after the 5.2 series. */
69#if (VBOX_VERSION_MAJOR * 100000 + VBOX_VERSION_MINOR * 1000 + VBOX_VERION_BUILD >= 502051)
70# define VBOX_AFTER_5_2
71#endif
72
73
74/*********************************************************************************************************************************
75* Structures and Typedefs *
76*********************************************************************************************************************************/
77/** The different X11 clipboard format targets. */
78typedef enum _CLIPX11FORMATTARGET
79{
80 CLIPX11FORMATTARGET_INVALID = 0,
81 CLIPX11FORMATTARGET_TARGETS,
82 CLIPX11FORMATTARGET_TEXT, /* Treat this as Utf8, but it may really be ascii */
83 CLIPX11FORMATTARGET_UTF8,
84 CLIPX11FORMATTARGET_BMP,
85 CLIPX11FORMATTARGET_HTML
86#ifdef VBOX_WITH_SHARED_CLIPBOARD_URI_LIST
87 , CLIPX11FORMATTARGET_URI_LIST
88#endif
89} CLIPX11FORMATTARGET;
90
91/** Index into the format array. */
92typedef unsigned CLIPX11FORMATIDX;
93
94
95/*********************************************************************************************************************************
96* Internal Functions *
97*********************************************************************************************************************************/
98class formats;
99static Atom clipGetAtom(CLIPBACKEND *pCtx, const char *pszName);
100
101
102/*********************************************************************************************************************************
103* Global Variables *
104*********************************************************************************************************************************/
105/** The table mapping X11 names to data formats and to the corresponding
106 * VBox clipboard formats (currently only Unicode) */
107static struct _CLIPX11FORMATTABLE
108{
109 /** The X11 atom name of the format (several names can match one format) */
110 const char *pcszAtom;
111 /** The format target corresponding to the atom name */
112 CLIPX11FORMATTARGET enmTarget;
113 /** The corresponding VBox clipboard format */
114 VBOXCLIPBOARDFORMAT vboxFormat;
115} g_aFormats[] =
116{
117 { "INVALID", CLIPX11FORMATTARGET_INVALID, 0 },
118 { "UTF8_STRING", CLIPX11FORMATTARGET_UTF8, VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT },
119 { "text/plain;charset=UTF-8", CLIPX11FORMATTARGET_UTF8, VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT },
120 { "text/plain;charset=utf-8", CLIPX11FORMATTARGET_UTF8, VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT },
121 { "STRING", CLIPX11FORMATTARGET_TEXT, VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT },
122 { "TEXT", CLIPX11FORMATTARGET_TEXT, VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT },
123 { "text/plain", CLIPX11FORMATTARGET_TEXT, VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT },
124 { "text/html", CLIPX11FORMATTARGET_HTML, VBOX_SHARED_CLIPBOARD_FMT_HTML },
125 { "text/html;charset=utf-8", CLIPX11FORMATTARGET_HTML, VBOX_SHARED_CLIPBOARD_FMT_HTML },
126 { "image/bmp", CLIPX11FORMATTARGET_BMP, VBOX_SHARED_CLIPBOARD_FMT_BITMAP },
127 { "image/x-bmp", CLIPX11FORMATTARGET_BMP, VBOX_SHARED_CLIPBOARD_FMT_BITMAP },
128 { "image/x-MS-bmp", CLIPX11FORMATTARGET_BMP, VBOX_SHARED_CLIPBOARD_FMT_BITMAP },
129#ifdef VBOX_WITH_SHARED_CLIPBOARD_URI_LIST
130 { "text/uri-list", CLIPX11FORMATTARGET_URI_LIST, VBOX_SHARED_CLIPBOARD_FMT_URI_LIST },
131 { "x-special/gnome-copied-files", CLIPX11FORMATTARGET_URI_LIST, VBOX_SHARED_CLIPBOARD_FMT_URI_LIST }
132#endif
133 /** @todo Inkscape exports image/png but not bmp... */
134};
135
136enum
137{
138 CLIPX11FORMATIDX_NIL = 0,
139 CLIPX11FORMATIDX_MAX = RT_ELEMENTS(g_aFormats)
140};
141
142
143/** Returnx the atom name corresponding to a supported X11 format index.
144 * @param widget a valid Xt widget
145 */
146static Atom clipGetAtomFromFmtIdx(CLIPBACKEND *pCtx, CLIPX11FORMATIDX formatIdx)
147{
148 return clipGetAtom(pCtx, g_aFormats[formatIdx].pcszAtom);
149}
150
151/** Returns the format target corresponding to a supported X11 format index. */
152static CLIPX11FORMATTARGET clipGetTargetFmtFromFmtIdx(CLIPX11FORMATIDX formatIdx)
153{
154 return g_aFormats[formatIdx].enmTarget;
155}
156
157/** Returns the VBox format corresponding to a supported X11 format index. */
158static VBOXCLIPBOARDFORMAT clipGetVBoxFmtFromFmtIdx(CLIPX11FORMATIDX formatIdx)
159{
160 return g_aFormats[formatIdx].vboxFormat;
161}
162
163/**
164 * Looks up the X11 format index matching a given X11 atom.
165 *
166 * @returns the format on success, CLIPX11FORMATIDX_NIL on failure.
167 * @param widget a valid Xt widget
168 */
169static CLIPX11FORMATIDX clipGetFmtIdxFromAtom(CLIPBACKEND *pCtx, Atom atomFormat)
170{
171 for (unsigned i = 0; i < RT_ELEMENTS(g_aFormats); ++i)
172 if (clipGetAtomFromFmtIdx(pCtx, i) == atomFormat)
173 return i;
174 return CLIPX11FORMATIDX_NIL;
175}
176
177#ifdef TESTCASE
178/** Lookup the X11 format matching a given X11 atom text.
179 * @returns the format on success, NIL_CLIPX11FORMATTARGET on failure
180 * @param widget a valid Xt widget
181 */
182static CLIPX11FORMATIDX clipGetFmtIdxFromAtomText(const char *pcsz)
183{
184 for (unsigned i = 0; i < RT_ELEMENTS(g_aFormats); ++i)
185 if (!strcmp(g_aFormats[i].pcszAtom, pcsz))
186 return i;
187 return CLIPX11FORMATIDX_NIL;
188}
189#endif
190
191/**
192 * Enumerates supported X11 clipboard formats corresponding to a given VBox format.
193 *
194 * @returns the next matching X11 format in the list, or NIL_CLIPX11FORMATTARGET if
195 * there are no more
196 * @param lastFmtIdx The format index returned from the last call of this function.
197 * Use CLIPX11FORMATIDX_NIL to start the enumeration.
198 */
199static CLIPX11FORMATIDX clipEnumFmtIdxForVBoxFmt(VBOXCLIPBOARDFORMAT vboxFormat,
200 CLIPX11FORMATIDX lastFmtIdx)
201{
202 for (unsigned i = lastFmtIdx + 1; i < RT_ELEMENTS(g_aFormats); ++i)
203 if (vboxFormat & clipGetVBoxFmtFromFmtIdx(i))
204 return i;
205 return CLIPX11FORMATIDX_NIL;
206}
207
208/** Global context information used by the X11 clipboard backend */
209struct _CLIPBACKEND
210{
211 /** Opaque data structure describing the front-end. */
212 VBOXCLIPBOARDCONTEXT *pFrontend;
213 /** Is an X server actually available? */
214 bool fHaveX11;
215 /** The X Toolkit application context structure */
216 XtAppContext appContext;
217
218 /** We have a separate thread to wait for Window and Clipboard events */
219 RTTHREAD thread;
220 /** The X Toolkit widget which we use as our clipboard client. It is never made visible. */
221 Widget widget;
222
223 /** Should we try to grab the clipboard on startup? */
224 bool fGrabClipboardOnStart;
225
226 /** The best text format X11 has to offer, as an index into the formats
227 * table */
228 CLIPX11FORMATIDX X11TextFmtIdx;
229 /** The best bitmap format X11 has to offer, as an index into the formats
230 * table */
231 CLIPX11FORMATIDX X11BitmapFmtIdx;
232 /** The best HTML format X11 has to offer, as an index into the formats
233 * table */
234 CLIPX11FORMATIDX X11HTMLFmtIdx;
235#ifdef VBOX_WITH_SHARED_CLIPBOARD_URI_LIST
236 /** The best URI list format X11 has to offer, as an index into the formats
237 * table */
238 CLIPX11FORMATIDX X11URListFmtIdx;
239#endif
240 /** What formats does VBox have on offer? */
241 VBOXCLIPBOARDFORMATS vboxFormats;
242 /** Cache of the last unicode data that we received */
243 void *pvUnicodeCache;
244 /** Size of the unicode data in the cache */
245 uint32_t cbUnicodeCache;
246 /** When we wish the clipboard to exit, we have to wake up the event
247 * loop. We do this by writing into a pipe. This end of the pipe is
248 * the end that another thread can write to. */
249 int wakeupPipeWrite;
250 /** The reader end of the pipe */
251 int wakeupPipeRead;
252 /** A pointer to the XFixesSelectSelectionInput function */
253 void (*fixesSelectInput)(Display *, Window, Atom, unsigned long);
254 /** The first XFixes event number */
255 int fixesEventBase;
256#ifndef VBOX_AFTER_5_2
257 /** The Xt Intrinsics can only handle one outstanding clipboard operation
258 * at a time, so we keep track of whether one is in process. */
259 bool fBusy;
260 /** We can't handle a clipboard update event while we are busy, so remember
261 * it for later. */
262 bool fUpdateNeeded;
263#endif
264};
265
266/** The number of simultaneous instances we support. For all normal purposes
267 * we should never need more than one. For the testcase it is convenient to
268 * have a second instance that the first can interact with in order to have
269 * a more controlled environment. */
270enum { CLIP_MAX_CONTEXTS = 20 };
271
272/** Array of structures for mapping Xt widgets to context pointers. We
273 * need this because the widget clipboard callbacks do not pass user data. */
274static struct {
275 /** The widget we want to associate the context with */
276 Widget widget;
277 /** The context associated with the widget */
278 CLIPBACKEND *pCtx;
279} g_contexts[CLIP_MAX_CONTEXTS];
280
281/** Register a new X11 clipboard context. */
282static int clipRegisterContext(CLIPBACKEND *pCtx)
283{
284 bool found = false;
285 AssertReturn(pCtx != NULL, VERR_INVALID_PARAMETER);
286 Widget widget = pCtx->widget;
287 AssertReturn(widget != NULL, VERR_INVALID_PARAMETER);
288 for (unsigned i = 0; i < RT_ELEMENTS(g_contexts); ++i)
289 {
290 AssertReturn( (g_contexts[i].widget != widget)
291 && (g_contexts[i].pCtx != pCtx), VERR_WRONG_ORDER);
292 if (g_contexts[i].widget == NULL && !found)
293 {
294 AssertReturn(g_contexts[i].pCtx == NULL, VERR_INTERNAL_ERROR);
295 g_contexts[i].widget = widget;
296 g_contexts[i].pCtx = pCtx;
297 found = true;
298 }
299 }
300 return found ? VINF_SUCCESS : VERR_OUT_OF_RESOURCES;
301}
302
303/** Unregister an X11 clipboard context. */
304static void clipUnregisterContext(CLIPBACKEND *pCtx)
305{
306 bool found = false;
307 AssertReturnVoid(pCtx != NULL);
308 Widget widget = pCtx->widget;
309 AssertReturnVoid(widget != NULL);
310 for (unsigned i = 0; i < RT_ELEMENTS(g_contexts); ++i)
311 {
312 Assert(!found || g_contexts[i].widget != widget);
313 if (g_contexts[i].widget == widget)
314 {
315 Assert(g_contexts[i].pCtx != NULL);
316 g_contexts[i].widget = NULL;
317 g_contexts[i].pCtx = NULL;
318 found = true;
319 }
320 }
321}
322
323/** Find an X11 clipboard context. */
324static CLIPBACKEND *clipLookupContext(Widget widget)
325{
326 AssertReturn(widget != NULL, NULL);
327 for (unsigned i = 0; i < RT_ELEMENTS(g_contexts); ++i)
328 {
329 if (g_contexts[i].widget == widget)
330 {
331 Assert(g_contexts[i].pCtx != NULL);
332 return g_contexts[i].pCtx;
333 }
334 }
335 return NULL;
336}
337
338/** Convert an atom name string to an X11 atom, looking it up in a cache
339 * before asking the server */
340static Atom clipGetAtom(CLIPBACKEND *pCtx, const char *pszName)
341{
342 AssertPtrReturn(pszName, None);
343 return XInternAtom(XtDisplay(pCtx->widget), pszName, False);
344}
345
346#ifdef TESTCASE
347static void testQueueToEventThread(void (*proc)(void *, void *),
348 void *client_data);
349#endif
350
351/** String written to the wakeup pipe. */
352#define WAKE_UP_STRING "WakeUp!"
353/** Length of the string written. */
354#define WAKE_UP_STRING_LEN ( sizeof(WAKE_UP_STRING) - 1 )
355
356/** Schedule a function call to run on the Xt event thread by passing it to
357 * the application context as a 0ms timeout and waking up the event loop by
358 * writing to the wakeup pipe which it monitors. */
359void clipQueueToEventThread(CLIPBACKEND *pCtx,
360 void (*proc)(void *, void *),
361 void *client_data)
362{
363 LogRel2(("clipQueueToEventThread: proc=%p, client_data=%p\n",
364 proc, client_data));
365#ifndef TESTCASE
366 XtAppAddTimeOut(pCtx->appContext, 0, (XtTimerCallbackProc)proc,
367 (XtPointer)client_data);
368 ssize_t cbWritten = write(pCtx->wakeupPipeWrite, WAKE_UP_STRING, WAKE_UP_STRING_LEN);
369 RT_NOREF(cbWritten);
370#else
371 RT_NOREF(pCtx);
372 testQueueToEventThread(proc, client_data);
373#endif
374}
375
376/**
377 * Report the formats currently supported by the X11 clipboard to VBox.
378 */
379static void clipReportFormatsToVBox(CLIPBACKEND *pCtx)
380{
381 VBOXCLIPBOARDFORMATS vboxFormats = clipGetVBoxFmtFromFmtIdx(pCtx->X11TextFmtIdx);
382 vboxFormats |= clipGetVBoxFmtFromFmtIdx(pCtx->X11BitmapFmtIdx);
383 vboxFormats |= clipGetVBoxFmtFromFmtIdx(pCtx->X11HTMLFmtIdx);
384
385 LogRelFlowFunc(("clipReportFormatsToVBox formats: 0x%x\n", vboxFormats));
386 LogRelFlowFunc(("clipReportFormatsToVBox txt: %d, bitm: %d, html:%d, vboxFormat: %d\n",
387 pCtx->X11TextFmtIdx, pCtx->X11BitmapFmtIdx, pCtx->X11HTMLFmtIdx,
388 vboxFormats));
389
390 ClipReportX11Formats(pCtx->pFrontend, vboxFormats);
391}
392
393/**
394 * Forget which formats were previously in the X11 clipboard. Called when we
395 * grab the clipboard. */
396static void clipResetX11Formats(CLIPBACKEND *pCtx)
397{
398 pCtx->X11TextFmtIdx = CLIPX11FORMATIDX_NIL;
399 pCtx->X11BitmapFmtIdx = CLIPX11FORMATIDX_NIL;
400 pCtx->X11HTMLFmtIdx = CLIPX11FORMATIDX_NIL;
401#ifdef VBOX_WITH_SHARED_CLIPBOARD_URI_LIST
402 pCtx->X11URListFmtIdx = CLIPX11FORMATIDX_NIL;
403#endif
404}
405
406/** Tell VBox that X11 currently has nothing in its clipboard. */
407static void clipReportEmptyX11CB(CLIPBACKEND *pCtx)
408{
409 clipResetX11Formats(pCtx);
410 clipReportFormatsToVBox(pCtx);
411}
412
413#ifdef TESTCASE
414static bool clipTestTextFormatConversion(CLIPBACKEND *pCtx)
415{
416 bool success = true;
417 CLIPX11FORMATTARGET targets[2];
418 CLIPX11FORMATTARGET x11Format;
419 targets[0] = clipGetFmtIdxFromAtomText("text/plain");
420 targets[1] = clipGetFmtIdxFromAtomText("image/bmp");
421 x11Format = clipGetTextFormatFromTargets(pCtx, targets, 2);
422 if (clipGetTargetFmtFromFmtIdx(x11Format) != TEXT)
423 success = false;
424 targets[0] = clipGetFmtIdxFromAtomText("UTF8_STRING");
425 targets[1] = clipGetFmtIdxFromAtomText("text/plain");
426 x11Format = clipGetTextFormatFromTargets(pCtx, targets, 2);
427 if (clipGetTargetFmtFromFmtIdx(x11Format) != UTF8)
428 success = false;
429 return success;
430}
431#endif
432
433/**
434 * Go through an array of X11 clipboard targets to see if they contain a bitmap
435 * format we can support, and if so choose the ones we prefer (e.g. we like
436 * BMP better than PNG because we don't have to convert).
437 *
438 * @param pCtx the clipboard backend context structure
439 * @param pTargets the list of targets
440 * @param cTargets the size of the list in @a pTargets
441 */
442static CLIPX11FORMATIDX clipGetBestFmtIdxFromTargets(CLIPBACKEND *pCtx,
443 VBOXCLIPBOARDFORMAT vboxFormat,
444 CLIPX11FORMATIDX *paTargets, size_t cTargets)
445{
446 AssertPtrReturn(pCtx, CLIPX11FORMATIDX_NIL);
447 AssertReturn(VALID_PTR(paTargets) || cTargets == 0, CLIPX11FORMATIDX_NIL);
448
449 CLIPX11FORMATIDX bestFmtIdx = CLIPX11FORMATIDX_NIL;
450 CLIPX11FORMATTARGET enmBestTarget = CLIPX11FORMATTARGET_INVALID;
451
452 for (unsigned i = 0; i < cTargets; ++i)
453 {
454 CLIPX11FORMATIDX format = paTargets[i];
455 if (format != CLIPX11FORMATIDX_NIL)
456 {
457 if ( (clipGetVBoxFmtFromFmtIdx(format) == vboxFormat)
458 && enmBestTarget < clipGetTargetFmtFromFmtIdx(format))
459 {
460 enmBestTarget = clipGetTargetFmtFromFmtIdx(format);
461 bestFmtIdx = format;
462 }
463 }
464 }
465 return bestFmtIdx;
466}
467
468/**
469 * Go through an array of X11 clipboard targets to see if we can support any
470 * of them and if relevant to choose the ones we prefer (e.g. we like Utf8
471 * better than plain text).
472 *
473 * @param pCtx the clipboard backend context structure
474 * @param pTargets the list of targets
475 * @param cTargets the size of the list in @a pTargets
476 */
477static void clipGetFormatsFromTargets(CLIPBACKEND *pCtx,
478 CLIPX11FORMATIDX *paTargets, size_t cTargets)
479{
480 AssertPtrReturnVoid(pCtx);
481 AssertPtrReturnVoid(paTargets);
482
483 CLIPX11FORMATIDX bestFmtIdx = clipGetBestFmtIdxFromTargets(pCtx, VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT,
484 paTargets, cTargets);
485 if (pCtx->X11TextFmtIdx != bestFmtIdx)
486 pCtx->X11TextFmtIdx = bestFmtIdx;
487
488 pCtx->X11BitmapFmtIdx = CLIPX11FORMATIDX_NIL; /* not yet supported */
489 bestFmtIdx = clipGetBestFmtIdxFromTargets(pCtx, VBOX_SHARED_CLIPBOARD_FMT_BITMAP, paTargets, cTargets);
490 if (pCtx->X11BitmapFmtIdx != bestFmtIdx)
491 pCtx->X11BitmapFmtIdx = bestFmtIdx;
492
493 bestFmtIdx = clipGetBestFmtIdxFromTargets(pCtx, VBOX_SHARED_CLIPBOARD_FMT_HTML, paTargets, cTargets);
494 if(pCtx->X11HTMLFmtIdx != bestFmtIdx)
495 pCtx->X11HTMLFmtIdx = bestFmtIdx;
496
497#ifdef VBOX_WITH_SHARED_CLIPBOARD_URI_LIST
498 bestFmtIdx = clipGetBestFmtIdxFromTargets(pCtx, VBOX_SHARED_CLIPBOARD_FMT_URI_LIST, paTargets, cTargets);
499 if(pCtx->X11URListFmtIdx != bestFmtIdx)
500 pCtx->X11URListFmtIdx = bestFmtIdx;
501#endif
502}
503
504static void clipQueryX11CBFormats(CLIPBACKEND *pCtx);
505
506/**
507 * Update the context's information about targets currently supported by X11,
508 * based on an array of X11 atoms.
509 *
510 * @param pCtx the context to be updated
511 * @param pTargets the array of atoms describing the targets supported
512 * @param cTargets the size of the array @a pTargets
513 */
514static void clipUpdateX11Targets(CLIPBACKEND *pCtx, CLIPX11FORMATIDX *paTargetFmtIdx, size_t cTargets)
515{
516 LogRel2(("Shared clipboard: clipUpdateX11Targets called\n"));
517
518#ifndef VBOX_AFTER_5_2
519 pCtx->fBusy = false;
520 if (pCtx->fUpdateNeeded)
521 {
522 /* We may already be out of date. */
523 pCtx->fUpdateNeeded = false;
524 clipQueryX11CBFormats(pCtx);
525 return;
526 }
527#endif
528
529 if (paTargetFmtIdx == NULL)
530 {
531 /* No data available */
532 clipReportEmptyX11CB(pCtx);
533 return;
534 }
535
536 clipGetFormatsFromTargets(pCtx, paTargetFmtIdx, cTargets);
537 clipReportFormatsToVBox(pCtx);
538}
539
540/**
541 * Notify the VBox clipboard about available data formats, based on the
542 * "targets" information obtained from the X11 clipboard.
543 * @note Callback for XtGetSelectionValue
544 * @note This function is treated as API glue, and as such is not part of any
545 * unit test. So keep it simple, be paranoid and log everything.
546 */
547static void clipConvertX11Targets(Widget widget, XtPointer pSvcCtxData,
548 Atom * /* selection */, Atom *atomType,
549 XtPointer pValue, long unsigned int *pcLen,
550 int *piFormat)
551{
552 RT_NOREF(piFormat);
553
554 CLIPBACKEND *pCtx = reinterpret_cast<CLIPBACKEND *>(pSvcCtxData);
555 Atom *pAtoms = (Atom *)pValue;
556 unsigned i, j;
557
558 LogRel2(("Shared clipboard: pValue=%p, *pcLen=%u, *atomType=%d%s\n",
559 pValue, *pcLen, *atomType,
560 *atomType == XT_CONVERT_FAIL ? " (XT_CONVERT_FAIL)" : ""));
561
562 CLIPX11FORMATIDX *paFmtIdx = NULL;
563
564 if (*pcLen && pValue && (*atomType != XT_CONVERT_FAIL /* time out */))
565 paFmtIdx = (CLIPX11FORMATIDX *)RTMemAllocZ(*pcLen * sizeof(CLIPX11FORMATIDX));
566
567#if defined(DEBUG) && !defined(TESTCASE)
568 if (pValue)
569 {
570 for (i = 0; i < *pcLen; ++i)
571 {
572 if (pAtoms[i])
573 {
574 char *pszName = XGetAtomName(XtDisplay(widget), pAtoms[i]);
575 LogRel(("Shared clipboard: Found target %s\n", pszName));
576 XFree(pszName);
577 }
578 else
579 LogRel(("Shared clipboard: Found empty target\n"));
580 }
581 }
582#endif
583
584 if (paFmtIdx)
585 {
586 for (i = 0; i < *pcLen; ++i)
587 {
588 for (j = 0; j < RT_ELEMENTS(g_aFormats); ++j)
589 {
590 Atom target = XInternAtom(XtDisplay(widget), g_aFormats[j].pcszAtom, False);
591 if (*(pAtoms + i) == target)
592 paFmtIdx[i] = j;
593 }
594#if defined(DEBUG) && !defined(TESTCASE)
595 LogRel(("Shared clipboard: Reporting format %d (%s)\n",
596 paFmtIdx[i], g_aFormats[paFmtIdx[i]].pcszAtom));
597#endif
598 }
599 }
600 else
601 LogRel2(("Shared clipboard: Reporting empty targets (none reported or allocation failure)\n"));
602
603 clipUpdateX11Targets(pCtx, paFmtIdx, *pcLen);
604
605 RTMemFree(paFmtIdx);
606 XtFree(reinterpret_cast<char *>(pValue));
607}
608
609#ifdef TESTCASE
610 void testRequestTargets(CLIPBACKEND *pCtx);
611#endif
612
613/**
614 * Callback to notify us when the contents of the X11 clipboard change.
615 */
616static void clipQueryX11CBFormats(CLIPBACKEND *pCtx)
617{
618 LogRel2 (("Shared clipboard: Requesting the targets that the X11 clipboard offers\n"));
619
620#ifndef VBOX_AFTER_5_2
621 if (pCtx->fBusy)
622 {
623 pCtx->fUpdateNeeded = true;
624 return;
625 }
626 pCtx->fBusy = true;
627#endif
628#ifndef TESTCASE
629 XtGetSelectionValue(pCtx->widget,
630 clipGetAtom(pCtx, "CLIPBOARD"),
631 clipGetAtom(pCtx, "TARGETS"),
632 clipConvertX11Targets, pCtx,
633 CurrentTime);
634#else
635 testRequestTargets(pCtx);
636#endif
637}
638
639#ifndef TESTCASE
640
641typedef struct {
642 int type; /* event base */
643 unsigned long serial;
644 Bool send_event;
645 Display *display;
646 Window window;
647 int subtype;
648 Window owner;
649 Atom selection;
650 Time timestamp;
651 Time selection_timestamp;
652} XFixesSelectionNotifyEvent;
653
654/**
655 * Wait until an event arrives and handle it if it is an XFIXES selection
656 * event, which Xt doesn't know about.
657 */
658void clipPeekEventAndDoXFixesHandling(CLIPBACKEND *pCtx)
659{
660 union
661 {
662 XEvent event;
663 XFixesSelectionNotifyEvent fixes;
664 } event = { { 0 } };
665
666 if (XtAppPeekEvent(pCtx->appContext, &event.event))
667 if ( (event.event.type == pCtx->fixesEventBase)
668 && (event.fixes.owner != XtWindow(pCtx->widget)))
669 {
670 if ( (event.fixes.subtype == 0 /* XFixesSetSelectionOwnerNotify */)
671 && (event.fixes.owner != 0))
672 clipQueryX11CBFormats(pCtx);
673 else
674 clipReportEmptyX11CB(pCtx);
675 }
676}
677
678/**
679 * The main loop of our clipboard reader.
680 * @note X11 backend code.
681 */
682static DECLCALLBACK(int) clipEventThread(RTTHREAD hThreadSelf, void *pvUser)
683{
684 RT_NOREF(hThreadSelf);
685
686 LogRel(("Shared clipboard: Starting shared clipboard thread\n"));
687
688 CLIPBACKEND *pCtx = (CLIPBACKEND *)pvUser;
689
690 if (pCtx->fGrabClipboardOnStart)
691 clipQueryX11CBFormats(pCtx);
692
693 while (XtAppGetExitFlag(pCtx->appContext) == FALSE)
694 {
695 clipPeekEventAndDoXFixesHandling(pCtx);
696 XtAppProcessEvent(pCtx->appContext, XtIMAll);
697 }
698
699 LogRel(("Shared clipboard: Shared clipboard thread terminated successfully\n"));
700 return VINF_SUCCESS;
701}
702#endif
703
704/** X11 specific uninitialisation for the shared clipboard.
705 * @note X11 backend code.
706 */
707static void clipUninit(CLIPBACKEND *pCtx)
708{
709 AssertPtrReturnVoid(pCtx);
710 if (pCtx->widget)
711 {
712 /* Valid widget + invalid appcontext = bug. But don't return yet. */
713 AssertPtr(pCtx->appContext);
714 clipUnregisterContext(pCtx);
715 XtDestroyWidget(pCtx->widget);
716 }
717 pCtx->widget = NULL;
718 if (pCtx->appContext)
719 XtDestroyApplicationContext(pCtx->appContext);
720 pCtx->appContext = NULL;
721 if (pCtx->wakeupPipeRead != 0)
722 close(pCtx->wakeupPipeRead);
723 if (pCtx->wakeupPipeWrite != 0)
724 close(pCtx->wakeupPipeWrite);
725 pCtx->wakeupPipeRead = 0;
726 pCtx->wakeupPipeWrite = 0;
727}
728
729/** Worker function for stopping the clipboard which runs on the event
730 * thread. */
731static void clipStopEventThreadWorker(void *pUserData, void *)
732{
733 CLIPBACKEND *pCtx = (CLIPBACKEND *)pUserData;
734
735 /* This might mean that we are getting stopped twice. */
736 Assert(pCtx->widget != NULL);
737
738 /* Set the termination flag to tell the Xt event loop to exit. We
739 * reiterate that any outstanding requests from the X11 event loop to
740 * the VBox part *must* have returned before we do this. */
741 XtAppSetExitFlag(pCtx->appContext);
742}
743
744#ifndef TESTCASE
745/** Setup the XFixes library and load the XFixesSelectSelectionInput symbol */
746static int clipLoadXFixes(Display *pDisplay, CLIPBACKEND *pCtx)
747{
748 int rc;
749
750 void *hFixesLib = dlopen("libXfixes.so.1", RTLD_LAZY);
751 if (!hFixesLib)
752 hFixesLib = dlopen("libXfixes.so.2", RTLD_LAZY);
753 if (!hFixesLib)
754 hFixesLib = dlopen("libXfixes.so.3", RTLD_LAZY);
755 if (!hFixesLib)
756 hFixesLib = dlopen("libXfixes.so.4", RTLD_LAZY);
757 if (hFixesLib)
758 {
759 /* For us, a NULL function pointer is a failure */
760 pCtx->fixesSelectInput = (void (*)(Display *, Window, Atom, long unsigned int))
761 (uintptr_t)dlsym(hFixesLib, "XFixesSelectSelectionInput");
762 if (pCtx->fixesSelectInput)
763 {
764 int dummy1 = 0;
765 int dummy2 = 0;
766 if (XQueryExtension(pDisplay, "XFIXES", &dummy1, &pCtx->fixesEventBase, &dummy2) != 0)
767 {
768 if (pCtx->fixesEventBase >= 0)
769 rc = VINF_SUCCESS;
770 else
771 {
772 LogRel(("clipLoadXFixes: fixesEventBase is less than zero: %d\n", pCtx->fixesEventBase));
773 rc = VERR_NOT_SUPPORTED;
774 }
775 }
776 else
777 {
778 LogRel(("clipLoadXFixes: XQueryExtension failed\n"));
779 rc = VERR_NOT_SUPPORTED;
780 }
781 }
782 else
783 {
784 LogRel(("clipLoadXFixes: Symbol XFixesSelectSelectionInput not found!\n"));
785 rc = VERR_NOT_SUPPORTED;
786 }
787 }
788 else
789 {
790 LogRel(("clipLoadXFixes: libxFixes.so.* not found!\n"));
791 rc = VERR_NOT_SUPPORTED;
792 }
793 return rc;
794}
795#endif
796
797/**
798 * This is the callback which is scheduled when data is available on the
799 * wakeup pipe. It simply reads all data from the pipe.
800 */
801static void clipDrainWakeupPipe(XtPointer pUserData, int *, XtInputId *)
802{
803 CLIPBACKEND *pCtx = (CLIPBACKEND *)pUserData;
804 char acBuf[WAKE_UP_STRING_LEN];
805
806 LogRel2(("Shared clipboard: clipDrainWakeupPipe called\n"));
807
808 while (read(pCtx->wakeupPipeRead, acBuf, sizeof(acBuf)) > 0) {}
809}
810
811/**
812 * X11 specific initialisation for the shared clipboard.
813 *
814 * @note X11 backend code.
815 */
816static int clipInit(CLIPBACKEND *pCtx)
817{
818 /* Create a window and make it a clipboard viewer. */
819 int cArgc = 0;
820 char *pcArgv = 0;
821 int rc = VINF_SUCCESS;
822 Display *pDisplay;
823
824 /* Make sure we are thread safe */
825 XtToolkitThreadInitialize();
826 /* Set up the Clipboard application context and main window. We call all
827 * these functions directly instead of calling XtOpenApplication() so
828 * that we can fail gracefully if we can't get an X11 display. */
829 XtToolkitInitialize();
830 pCtx->appContext = XtCreateApplicationContext();
831 pDisplay = XtOpenDisplay(pCtx->appContext, 0, 0, "VBoxClipboard", 0, 0, &cArgc, &pcArgv);
832 if (NULL == pDisplay)
833 {
834 LogRel(("Shared clipboard: Failed to connect to the X11 clipboard - the window system may not be running\n"));
835 rc = VERR_NOT_SUPPORTED;
836 }
837#ifndef TESTCASE
838 if (RT_SUCCESS(rc))
839 {
840 rc = clipLoadXFixes(pDisplay, pCtx);
841 if (RT_FAILURE(rc))
842 LogRel(("Shared clipboard: Failed to load the XFIXES extension\n"));
843 }
844#endif
845 if (RT_SUCCESS(rc))
846 {
847 pCtx->widget = XtVaAppCreateShell(0, "VBoxClipboard",
848 applicationShellWidgetClass,
849 pDisplay, XtNwidth, 1, XtNheight,
850 1, NULL);
851 if (NULL == pCtx->widget)
852 {
853 LogRel(("Shared clipboard: Failed to construct the X11 window for the shared clipboard manager\n"));
854 rc = VERR_NO_MEMORY;
855 }
856 else
857 rc = clipRegisterContext(pCtx);
858 }
859 if (RT_SUCCESS(rc))
860 {
861 XtSetMappedWhenManaged(pCtx->widget, false);
862 XtRealizeWidget(pCtx->widget);
863#ifndef TESTCASE
864 /* Enable clipboard update notification */
865 pCtx->fixesSelectInput(pDisplay, XtWindow(pCtx->widget),
866 clipGetAtom(pCtx, "CLIPBOARD"),
867 7 /* All XFixes*Selection*NotifyMask flags */);
868#endif
869 }
870 /* Create the pipes */
871 int pipes[2];
872 if (!pipe(pipes))
873 {
874 pCtx->wakeupPipeRead = pipes[0];
875 pCtx->wakeupPipeWrite = pipes[1];
876 if (!XtAppAddInput(pCtx->appContext, pCtx->wakeupPipeRead,
877 (XtPointer) XtInputReadMask,
878 clipDrainWakeupPipe, (XtPointer) pCtx))
879 rc = VERR_NO_MEMORY; /* What failure means is not doc'ed. */
880 if ( RT_SUCCESS(rc)
881 && (fcntl(pCtx->wakeupPipeRead, F_SETFL, O_NONBLOCK) != 0))
882 rc = RTErrConvertFromErrno(errno);
883 if (RT_FAILURE(rc))
884 LogRel(("Shared clipboard: Failed to setup the termination mechanism\n"));
885 }
886 else
887 rc = RTErrConvertFromErrno(errno);
888 if (RT_FAILURE(rc))
889 clipUninit(pCtx);
890 if (RT_FAILURE(rc))
891 LogRel(("Shared clipboard: Initialisation failed: %Rrc\n", rc));
892 return rc;
893}
894
895/**
896 * Constructs the X11 backend of the shared clipboard.
897 *
898 * @note X11 backend code
899 */
900CLIPBACKEND *ClipConstructX11(VBOXCLIPBOARDCONTEXT *pFrontend, bool fHeadless)
901{
902 CLIPBACKEND *pCtx = (CLIPBACKEND *)RTMemAllocZ(sizeof(CLIPBACKEND));
903 if (pCtx && fHeadless)
904 {
905 /*
906 * If we don't find the DISPLAY environment variable we assume that
907 * we are not connected to an X11 server. Don't actually try to do
908 * this then, just fail silently and report success on every call.
909 * This is important for VBoxHeadless.
910 */
911 LogRel(("Shared clipboard: X11 DISPLAY variable not set -- disabling Shared Clipboard\n"));
912 pCtx->fHaveX11 = false;
913 return pCtx;
914 }
915
916 pCtx->fHaveX11 = true;
917
918 LogRel(("Shared clipboard: Initializing X11 clipboard backend\n"));
919
920 if (pCtx)
921 pCtx->pFrontend = pFrontend;
922 return pCtx;
923}
924
925/**
926 * Destructs the shared clipboard X11 backend.
927 *
928 * @note X11 backend code
929 */
930void ClipDestructX11(CLIPBACKEND *pCtx)
931{
932 if (pCtx->fHaveX11)
933 /* We set this to NULL when the event thread exits. It really should
934 * have exited at this point, when we are about to unload the code from
935 * memory. */
936 Assert(pCtx->widget == NULL);
937 RTMemFree(pCtx);
938}
939
940/**
941 * Announces to the X11 backend that we are ready to start.
942 *
943 * @param grab whether we should try to grab the shared clipboard at once
944 */
945int ClipStartX11(CLIPBACKEND *pCtx, bool grab)
946{
947 int rc = VINF_SUCCESS;
948
949 LogRel2(("Shared clipboard: Starting\n"));
950
951 /*
952 * Immediately return if we are not connected to the X server.
953 */
954 if (!pCtx->fHaveX11)
955 return VINF_SUCCESS;
956
957 rc = clipInit(pCtx);
958 if (RT_SUCCESS(rc))
959 {
960 clipResetX11Formats(pCtx);
961 pCtx->fGrabClipboardOnStart = grab;
962 }
963#ifndef TESTCASE
964 if (RT_SUCCESS(rc))
965 {
966 rc = RTThreadCreate(&pCtx->thread, clipEventThread, pCtx, 0,
967 RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "SHCLIP");
968 if (RT_FAILURE(rc))
969 {
970 LogRel(("Shared clipboard: Failed to start the shared clipboard thread, rc=%Rrc\n", rc));
971 clipUninit(pCtx);
972 }
973 }
974#endif
975 return rc;
976}
977
978/**
979 * Shuts down the shared clipboard X11 backend.
980 *
981 * @note X11 backend code.
982 * @note Any requests from this object to get clipboard data from VBox
983 * *must* have completed or aborted before we are called, as
984 * otherwise the X11 event loop will still be waiting for the request
985 * to return and will not be able to terminate.
986 */
987int ClipStopX11(CLIPBACKEND *pCtx)
988{
989 int rc, rcThread;
990 unsigned count = 0;
991 /*
992 * Immediately return if we are not connected to the X server.
993 */
994 if (!pCtx->fHaveX11)
995 return VINF_SUCCESS;
996
997 LogRel(("Shared clipboard: Stopping X11 backend\n"));
998
999 /* Write to the "stop" pipe */
1000 clipQueueToEventThread(pCtx, clipStopEventThreadWorker, (XtPointer) pCtx);
1001#ifndef TESTCASE
1002 do
1003 {
1004 rc = RTThreadWait(pCtx->thread, 1000, &rcThread);
1005 ++count;
1006 Assert(RT_SUCCESS(rc) || ((VERR_TIMEOUT == rc) && (count != 5)));
1007 } while ((VERR_TIMEOUT == rc) && (count < 300));
1008#else
1009 rc = VINF_SUCCESS;
1010 rcThread = VINF_SUCCESS;
1011 RT_NOREF_PV(count);
1012#endif
1013 if (RT_SUCCESS(rc))
1014 AssertRC(rcThread);
1015 else
1016 LogRel(("Shared clipboard: Stopping X11 backend failed with rc=%Rrc\n", rc));
1017
1018 clipUninit(pCtx);
1019
1020 RT_NOREF_PV(rcThread);
1021 return rc;
1022}
1023
1024/**
1025 * Satisfy a request from X11 for clipboard targets supported by VBox.
1026 *
1027 * @returns IPRT status code.
1028 * @param atomTypeReturn The type of the data we are returning.
1029 * @param pValReturn A pointer to the data we are returning. This
1030 * should be set to memory allocated by XtMalloc,
1031 * which will be freed later by the Xt toolkit.
1032 * @param pcLenReturn The length of the data we are returning.
1033 * @param piFormatReturn The format (8bit, 16bit, 32bit) of the data we are
1034 * returning.
1035 *
1036 * @note X11 backend code, called by the XtOwnSelection callback.
1037 */
1038static int clipCreateX11Targets(CLIPBACKEND *pCtx, Atom *atomTypeReturn,
1039 XtPointer *pValReturn,
1040 unsigned long *pcLenReturn,
1041 int *piFormatReturn)
1042{
1043 Atom *atomTargets = (Atom *)XtMalloc((CLIPX11FORMATIDX_MAX + 3) * sizeof(Atom));
1044 unsigned cTargets = 0;
1045
1046 LogRelFlowFunc (("called\n"));
1047
1048 CLIPX11FORMATIDX fmtIdx = CLIPX11FORMATIDX_NIL;
1049
1050 do
1051 {
1052 fmtIdx = clipEnumFmtIdxForVBoxFmt(pCtx->vboxFormats, fmtIdx);
1053 if (fmtIdx != CLIPX11FORMATIDX_NIL)
1054 {
1055 atomTargets[cTargets] = clipGetAtomFromFmtIdx(pCtx, fmtIdx);
1056 ++cTargets;
1057 }
1058 } while (fmtIdx != CLIPX11FORMATIDX_NIL);
1059
1060 /* We always offer these */
1061 atomTargets[cTargets] = clipGetAtom(pCtx, "TARGETS");
1062 atomTargets[cTargets + 1] = clipGetAtom(pCtx, "MULTIPLE");
1063 atomTargets[cTargets + 2] = clipGetAtom(pCtx, "TIMESTAMP");
1064
1065 *atomTypeReturn = XA_ATOM;
1066 *pValReturn = (XtPointer)atomTargets;
1067 *pcLenReturn = cTargets + 3;
1068 *piFormatReturn = 32;
1069
1070 return VINF_SUCCESS;
1071}
1072
1073/**
1074 * This is a wrapper around ClipRequestDataForX11 that will cache the
1075 * data returned.
1076 */
1077static int clipReadVBoxClipboard(CLIPBACKEND *pCtx, VBOXCLIPBOARDFORMAT vboxFormat,
1078 void **ppv, uint32_t *pcb)
1079{
1080 int rc = VINF_SUCCESS;
1081 LogRelFlowFunc(("pCtx=%p, vboxFormat=%02X, ppv=%p, pcb=%p\n", pCtx,
1082 vboxFormat, ppv, pcb));
1083 if (vboxFormat == VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT)
1084 {
1085 if (pCtx->pvUnicodeCache == NULL)
1086 rc = ClipRequestDataForX11(pCtx->pFrontend, vboxFormat,
1087 &pCtx->pvUnicodeCache,
1088 &pCtx->cbUnicodeCache);
1089 if (RT_SUCCESS(rc))
1090 {
1091 *ppv = RTMemDup(pCtx->pvUnicodeCache, pCtx->cbUnicodeCache);
1092 *pcb = pCtx->cbUnicodeCache;
1093 if (*ppv == NULL)
1094 rc = VERR_NO_MEMORY;
1095 }
1096 }
1097 else
1098 rc = ClipRequestDataForX11(pCtx->pFrontend, vboxFormat,
1099 ppv, pcb);
1100 LogRelFlowFunc(("returning %Rrc\n", rc));
1101 if (RT_SUCCESS(rc))
1102 LogRelFlowFunc(("*ppv=%.*ls, *pcb=%u\n", *pcb, *ppv, *pcb));
1103 return rc;
1104}
1105
1106/**
1107 * Calculates a buffer size large enough to hold the source Windows format
1108 * text converted into Unix Utf8, including the null terminator.
1109 *
1110 * @returns IPRT status code.
1111 * @param pwsz the source text in UCS-2 with Windows EOLs.
1112 * @param cwc the size in USC-2 elements of the source text, with or
1113 * without the terminator.
1114 * @param pcbActual where to store the buffer size needed.
1115 */
1116static int clipWinTxtBufSizeForUtf8(PRTUTF16 pwsz, size_t cwc,
1117 size_t *pcbActual)
1118{
1119 size_t cbRet = 0;
1120 int rc = RTUtf16CalcUtf8LenEx(pwsz, cwc, &cbRet);
1121 if (RT_SUCCESS(rc))
1122 *pcbActual = cbRet + 1; /* null terminator */
1123 return rc;
1124}
1125
1126/**
1127 * Converts text from Windows format (UCS-2 with CRLF line endings) to standard
1128 * Utf-8.
1129 *
1130 * @returns IPRT status code.
1131 * @param pwszSrc the text to be converted.
1132 * @param cbSrc the length of @a pwszSrc in bytes.
1133 * @param pszBuf where to write the converted string.
1134 * @param cbBuf the size of the buffer pointed to by @a pszBuf.
1135 * @param pcbActual where to store the size of the converted string.
1136 * optional.
1137 */
1138static int clipWinTxtToUtf8(PRTUTF16 pwszSrc, size_t cbSrc, char *pszBuf,
1139 size_t cbBuf, size_t *pcbActual)
1140{
1141 PRTUTF16 pwszTmp = NULL;
1142 size_t cwSrc = cbSrc / 2, cwTmp = 0, cbDest = 0;
1143 int rc = VINF_SUCCESS;
1144
1145 LogRelFlowFunc (("pwszSrc=%.*ls, cbSrc=%u\n", cbSrc, pwszSrc, cbSrc));
1146 /* How long will the converted text be? */
1147 AssertPtr(pwszSrc);
1148 AssertPtr(pszBuf);
1149 rc = vboxClipboardUtf16GetLinSize(pwszSrc, cwSrc, &cwTmp);
1150 if (RT_SUCCESS(rc) && cwTmp == 0)
1151 rc = VERR_NO_DATA;
1152 if (RT_SUCCESS(rc))
1153 pwszTmp = (PRTUTF16)RTMemAlloc(cwTmp * 2);
1154 if (!pwszTmp)
1155 rc = VERR_NO_MEMORY;
1156 /* Convert the text. */
1157 if (RT_SUCCESS(rc))
1158 rc = vboxClipboardUtf16WinToLin(pwszSrc, cwSrc, pwszTmp, cwTmp);
1159 if (RT_SUCCESS(rc))
1160 /* Convert the Utf16 string to Utf8. */
1161 rc = RTUtf16ToUtf8Ex(pwszTmp + 1, cwTmp - 1, &pszBuf, cbBuf,
1162 &cbDest);
1163 RTMemFree(reinterpret_cast<void *>(pwszTmp));
1164 if (pcbActual)
1165 *pcbActual = cbDest + 1;
1166 LogRelFlowFunc(("returning %Rrc\n", rc));
1167 if (RT_SUCCESS(rc))
1168 LogRelFlowFunc (("converted string is %.*s. Returning\n", cbDest,
1169 pszBuf));
1170 return rc;
1171}
1172
1173/**
1174 * Satisfies a request from X11 to convert the clipboard text to Utf-8. We
1175 * return null-terminated text, but can cope with non-null-terminated input.
1176 *
1177 * @returns IPRT status code.
1178 * @param pDisplay an X11 display structure, needed for conversions
1179 * performed by Xlib.
1180 * @param pv the text to be converted (UCS-2 with Windows EOLs).
1181 * @param cb the length of the text in @cb in bytes.
1182 * @param atomTypeReturn where to store the atom for the type of the data
1183 * we are returning.
1184 * @param pValReturn where to store the pointer to the data we are
1185 * returning. This should be to memory allocated by
1186 * XtMalloc, which will be freed by the Xt toolkit
1187 * later.
1188 * @param pcLenReturn where to store the length of the data we are
1189 * returning.
1190 * @param piFormatReturn where to store the bit width (8, 16, 32) of the
1191 * data we are returning.
1192 */
1193static int clipWinTxtToUtf8ForX11CB(Display *pDisplay, PRTUTF16 pwszSrc,
1194 size_t cbSrc, Atom *atomTarget,
1195 Atom *atomTypeReturn,
1196 XtPointer *pValReturn,
1197 unsigned long *pcLenReturn,
1198 int *piFormatReturn)
1199{
1200 RT_NOREF(pDisplay, pcLenReturn);
1201
1202 /* This may slightly overestimate the space needed. */
1203 size_t cbDest = 0;
1204 int rc = clipWinTxtBufSizeForUtf8(pwszSrc, cbSrc / 2, &cbDest);
1205 if (RT_SUCCESS(rc))
1206 {
1207 char *pszDest = (char *)XtMalloc(cbDest);
1208 size_t cbActual = 0;
1209 if (pszDest)
1210 rc = clipWinTxtToUtf8(pwszSrc, cbSrc, pszDest, cbDest,
1211 &cbActual);
1212 if (RT_SUCCESS(rc))
1213 {
1214 *atomTypeReturn = *atomTarget;
1215 *pValReturn = (XtPointer)pszDest;
1216 *pcLenReturn = cbActual;
1217 *piFormatReturn = 8;
1218 }
1219 }
1220 return rc;
1221}
1222
1223/**
1224 * Satisfies a request from X11 to convert the clipboard HTML fragment to Utf-8. We
1225 * return null-terminated text, but can cope with non-null-terminated input.
1226 *
1227 * @returns IPRT status code.
1228 * @param pDisplay an X11 display structure, needed for conversions
1229 * performed by Xlib.
1230 * @param pv the text to be converted (UTF8 with Windows EOLs).
1231 * @param cb the length of the text in @cb in bytes.
1232 * @param atomTypeReturn where to store the atom for the type of the data
1233 * we are returning.
1234 * @param pValReturn where to store the pointer to the data we are
1235 * returning. This should be to memory allocated by
1236 * XtMalloc, which will be freed by the Xt toolkit
1237 * later.
1238 * @param pcLenReturn where to store the length of the data we are
1239 * returning.
1240 * @param piFormatReturn where to store the bit width (8, 16, 32) of the
1241 * data we are returning.
1242 */
1243static int clipWinHTMLToUtf8ForX11CB(Display *pDisplay, const char *pszSrc,
1244 size_t cbSrc, Atom *atomTarget,
1245 Atom *atomTypeReturn,
1246 XtPointer *pValReturn,
1247 unsigned long *pcLenReturn,
1248 int *piFormatReturn)
1249{
1250 RT_NOREF(pDisplay, pValReturn);
1251
1252 /* This may slightly overestimate the space needed. */
1253 LogRelFlowFunc(("source: %s", pszSrc));
1254
1255 char *pszDest = (char *)XtMalloc(cbSrc);
1256 if(pszDest == NULL)
1257 return VERR_NO_MEMORY;
1258
1259 memcpy(pszDest, pszSrc, cbSrc);
1260
1261 *atomTypeReturn = *atomTarget;
1262 *pValReturn = (XtPointer)pszDest;
1263 *pcLenReturn = cbSrc;
1264 *piFormatReturn = 8;
1265
1266 return VINF_SUCCESS;
1267}
1268
1269/**
1270 * Does this atom correspond to one of the two selection types we support?
1271 *
1272 * @param widget a valid Xt widget
1273 * @param selType the atom in question
1274 */
1275static bool clipIsSupportedSelectionType(CLIPBACKEND *pCtx, Atom selType)
1276{
1277 return( (selType == clipGetAtom(pCtx, "CLIPBOARD"))
1278 || (selType == clipGetAtom(pCtx, "PRIMARY")));
1279}
1280
1281/**
1282 * Removes a trailing nul character from a string by adjusting the string
1283 * length. Some X11 applications don't like zero-terminated text...
1284 *
1285 * @param pText the text in question
1286 * @param pcText the length of the text, adjusted on return
1287 * @param format the format of the text
1288 */
1289static void clipTrimTrailingNul(XtPointer pText, unsigned long *pcText,
1290 CLIPX11FORMATTARGET format)
1291{
1292 AssertPtrReturnVoid(pText);
1293 AssertPtrReturnVoid(pcText);
1294 AssertReturnVoid( (format == CLIPX11FORMATTARGET_UTF8)
1295 || (format == CLIPX11FORMATTARGET_TEXT)
1296 || (format == CLIPX11FORMATTARGET_HTML));
1297 if (((char *)pText)[*pcText - 1] == '\0')
1298 --(*pcText);
1299}
1300
1301static int clipConvertVBoxCBForX11(CLIPBACKEND *pCtx, Atom *atomTarget,
1302 Atom *atomTypeReturn,
1303 XtPointer *pValReturn,
1304 unsigned long *pcLenReturn,
1305 int *piFormatReturn)
1306{
1307 int rc = VINF_SUCCESS;
1308
1309 CLIPX11FORMATIDX fmtIdx = clipGetFmtIdxFromAtom(pCtx, *atomTarget);
1310 CLIPX11FORMATTARGET fmtTgt = clipGetTargetFmtFromFmtIdx(fmtIdx);
1311
1312 if ( ((fmtTgt == CLIPX11FORMATTARGET_UTF8) || (fmtTgt == CLIPX11FORMATTARGET_TEXT))
1313 && (pCtx->vboxFormats & VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT))
1314 {
1315 void *pv = NULL;
1316 uint32_t cb = 0;
1317 rc = clipReadVBoxClipboard(pCtx, VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT, &pv, &cb);
1318 if (RT_SUCCESS(rc) && (cb == 0))
1319 rc = VERR_NO_DATA;
1320
1321 if (RT_SUCCESS(rc) && ( (fmtTgt == CLIPX11FORMATTARGET_UTF8)
1322 || (fmtTgt == CLIPX11FORMATTARGET_TEXT)))
1323 {
1324 rc = clipWinTxtToUtf8ForX11CB(XtDisplay(pCtx->widget),
1325 (PRTUTF16)pv, cb, atomTarget,
1326 atomTypeReturn, pValReturn,
1327 pcLenReturn, piFormatReturn);
1328 }
1329
1330 if (RT_SUCCESS(rc))
1331 clipTrimTrailingNul(*(XtPointer *)pValReturn, pcLenReturn, fmtTgt);
1332
1333 RTMemFree(pv);
1334 }
1335 else if ( (fmtTgt == CLIPX11FORMATTARGET_BMP)
1336 && (pCtx->vboxFormats & VBOX_SHARED_CLIPBOARD_FMT_BITMAP))
1337 {
1338 void *pv = NULL;
1339 uint32_t cb = 0;
1340 rc = clipReadVBoxClipboard(pCtx, VBOX_SHARED_CLIPBOARD_FMT_BITMAP, &pv, &cb);
1341 if (RT_SUCCESS(rc) && (cb == 0))
1342 rc = VERR_NO_DATA;
1343
1344 if (RT_SUCCESS(rc) && (fmtTgt == CLIPX11FORMATTARGET_BMP))
1345 {
1346 /* Create a full BMP from it. */
1347 rc = vboxClipboardDibToBmp(pv, cb, (void **)pValReturn,
1348 (size_t *)pcLenReturn);
1349 }
1350 else
1351 rc = VERR_NOT_SUPPORTED;
1352
1353 if (RT_SUCCESS(rc))
1354 {
1355 *atomTypeReturn = *atomTarget;
1356 *piFormatReturn = 8;
1357 }
1358
1359 RTMemFree(pv);
1360 }
1361 else if ( (fmtTgt == CLIPX11FORMATTARGET_HTML)
1362 && (pCtx->vboxFormats & VBOX_SHARED_CLIPBOARD_FMT_HTML))
1363 {
1364 void *pv = NULL;
1365 uint32_t cb = 0;
1366 rc = clipReadVBoxClipboard(pCtx, VBOX_SHARED_CLIPBOARD_FMT_HTML, &pv, &cb);
1367 if (RT_SUCCESS(rc) && (cb == 0))
1368 rc = VERR_NO_DATA;
1369
1370 if (RT_SUCCESS(rc))
1371 {
1372 /*
1373 * The common VBox HTML encoding will be - Utf8
1374 * becuase it more general for HTML formats then UTF16
1375 * X11 clipboard returns UTF16, so before sending it we should
1376 * convert it to UTF8
1377 * It's very strange but here we get utf16 from x11 clipboard
1378 * in same time we send utf8 to x11 clipboard and it's work
1379 */
1380 rc = clipWinHTMLToUtf8ForX11CB(XtDisplay(pCtx->widget),
1381 (const char*)pv, cb, atomTarget,
1382 atomTypeReturn, pValReturn,
1383 pcLenReturn, piFormatReturn);
1384 if (RT_SUCCESS(rc))
1385 clipTrimTrailingNul(*(XtPointer *)pValReturn, pcLenReturn, fmtTgt);
1386
1387 RTMemFree(pv);
1388 }
1389 }
1390#ifdef VBOX_WITH_SHARED_CLIPBOARD_URI_LIST
1391 else if ( (fmtTgt == CLIPX11FORMATTARGET_URI_LIST)
1392 && (pCtx->vboxFormats & VBOX_SHARED_CLIPBOARD_FMT_URI_LIST))
1393 {
1394 /** Nothing to do here yet. */
1395 }
1396#endif
1397 else
1398 rc = VERR_NOT_SUPPORTED;
1399 return rc;
1400}
1401
1402/**
1403 * Returns VBox's clipboard data for an X11 client.
1404 *
1405 * @note X11 backend code, callback for XtOwnSelection.
1406 */
1407static Boolean clipXtConvertSelectionProc(Widget widget, Atom *atomSelection,
1408 Atom *atomTarget,
1409 Atom *atomTypeReturn,
1410 XtPointer *pValReturn,
1411 unsigned long *pcLenReturn,
1412 int *piFormatReturn)
1413{
1414 CLIPBACKEND *pCtx = clipLookupContext(widget);
1415 int rc = VINF_SUCCESS;
1416
1417 LogRelFlowFunc(("\n"));
1418 if (!pCtx)
1419 return false;
1420 if (!clipIsSupportedSelectionType(pCtx, *atomSelection))
1421 return false;
1422 if (*atomTarget == clipGetAtom(pCtx, "TARGETS"))
1423 rc = clipCreateX11Targets(pCtx, atomTypeReturn, pValReturn,
1424 pcLenReturn, piFormatReturn);
1425 else
1426 rc = clipConvertVBoxCBForX11(pCtx, atomTarget, atomTypeReturn,
1427 pValReturn, pcLenReturn, piFormatReturn);
1428 LogRelFlowFunc(("returning, internal status code %Rrc\n", rc));
1429 return RT_SUCCESS(rc);
1430}
1431
1432/** Structure used to pass information about formats that VBox supports */
1433typedef struct _CLIPNEWVBOXFORMATS
1434{
1435 /** Context information for the X11 clipboard */
1436 CLIPBACKEND *pCtx;
1437 /** Formats supported by VBox */
1438 VBOXCLIPBOARDFORMATS vboxFormats;
1439} CLIPNEWVBOXFORMATS;
1440
1441/** Invalidates the local cache of the data in the VBox clipboard. */
1442static void clipInvalidateVBoxCBCache(CLIPBACKEND *pCtx)
1443{
1444 if (pCtx->pvUnicodeCache != NULL)
1445 {
1446 RTMemFree(pCtx->pvUnicodeCache);
1447 pCtx->pvUnicodeCache = NULL;
1448 }
1449}
1450
1451/**
1452 * Takes possession of the X11 clipboard (and middle-button selection).
1453 */
1454static void clipGrabX11CB(CLIPBACKEND *pCtx, VBOXCLIPBOARDFORMATS vboxFormats)
1455{
1456 if (XtOwnSelection(pCtx->widget, clipGetAtom(pCtx, "CLIPBOARD"),
1457 CurrentTime, clipXtConvertSelectionProc, NULL, 0))
1458 {
1459 pCtx->vboxFormats = vboxFormats;
1460
1461 /* Grab the middle-button paste selection too. */
1462 XtOwnSelection(pCtx->widget, clipGetAtom(pCtx, "PRIMARY"),
1463 CurrentTime, clipXtConvertSelectionProc, NULL, 0);
1464#ifndef TESTCASE
1465 /* Xt suppresses these if we already own the clipboard, so send them
1466 * ourselves. */
1467 XSetSelectionOwner(XtDisplay(pCtx->widget),
1468 clipGetAtom(pCtx, "CLIPBOARD"),
1469 XtWindow(pCtx->widget), CurrentTime);
1470 XSetSelectionOwner(XtDisplay(pCtx->widget),
1471 clipGetAtom(pCtx, "PRIMARY"),
1472 XtWindow(pCtx->widget), CurrentTime);
1473#endif
1474 }
1475}
1476
1477/**
1478 * Worker function for ClipAnnounceFormatToX11 which runs on the
1479 * event thread.
1480 *
1481 * @param pUserData Pointer to a CLIPNEWVBOXFORMATS structure containing
1482 * information about the VBox formats available and the
1483 * clipboard context data. Must be freed by the worker.
1484 */
1485static void clipNewVBoxFormatsWorker(void *pUserData, void * /* interval */)
1486{
1487 CLIPNEWVBOXFORMATS *pFormats = (CLIPNEWVBOXFORMATS *)pUserData;
1488
1489 CLIPBACKEND *pCtx = pFormats->pCtx;
1490 VBOXCLIPBOARDFORMAT vboxFormat = pFormats->vboxFormats;
1491
1492 RTMemFree(pFormats);
1493
1494 LogRelFlowFunc (("vboxFormat=%RU32\n", vboxFormat));
1495
1496 clipInvalidateVBoxCBCache(pCtx);
1497 clipGrabX11CB(pCtx, vboxFormat);
1498 clipResetX11Formats(pCtx);
1499
1500 LogRelFlowFunc(("returning\n"));
1501}
1502
1503/**
1504 * VBox is taking possession of the shared clipboard.
1505 *
1506 * @param vboxFormats Clipboard formats that VBox is offering
1507 * @note X11 backend code
1508 */
1509int ClipAnnounceFormatToX11(CLIPBACKEND *pCtx, VBOXCLIPBOARDFORMATS vboxFormats)
1510{
1511 /*
1512 * Immediately return if we are not connected to the X server.
1513 */
1514 if (!pCtx->fHaveX11)
1515 return VINF_SUCCESS;
1516
1517 int rc;
1518
1519 /* This must be freed by the worker callback */
1520 CLIPNEWVBOXFORMATS *pFormats =
1521 (CLIPNEWVBOXFORMATS *) RTMemAlloc(sizeof(CLIPNEWVBOXFORMATS));
1522
1523 if (pFormats != NULL) /* if it is we will soon have other problems */
1524 {
1525 pFormats->pCtx = pCtx;
1526 pFormats->vboxFormats = vboxFormats;
1527 clipQueueToEventThread(pCtx, clipNewVBoxFormatsWorker,
1528 (XtPointer) pFormats);
1529 rc = VINF_SUCCESS;
1530 }
1531 else
1532 rc = VERR_NO_MEMORY;
1533
1534 return rc;
1535}
1536
1537/**
1538 * Massages generic Utf16 with CR end-of-lines into the format Windows expects
1539 * and return the result in a RTMemAlloc allocated buffer.
1540 *
1541 * @returns IPRT status code.
1542 * @param pwcSrc The source Utf16.
1543 * @param cwcSrc The number of 16bit elements in @a pwcSrc, not counting
1544 * the terminating zero.
1545 * @param ppwszDest Where to store the buffer address.
1546 * @param pcbDest On success, where to store the number of bytes written.
1547 * Undefined otherwise. Optional.
1548 */
1549static int clipUtf16ToWinTxt(RTUTF16 *pwcSrc, size_t cwcSrc,
1550 PRTUTF16 *ppwszDest, uint32_t *pcbDest)
1551{
1552 LogRelFlowFunc(("pwcSrc=%p, cwcSrc=%u, ppwszDest=%p\n", pwcSrc, cwcSrc,
1553 ppwszDest));
1554 AssertPtrReturn(pwcSrc, VERR_INVALID_POINTER);
1555 AssertPtrReturn(ppwszDest, VERR_INVALID_POINTER);
1556 if (pcbDest)
1557 *pcbDest = 0;
1558 PRTUTF16 pwszDest = NULL;
1559 size_t cwcDest;
1560 int rc = vboxClipboardUtf16GetWinSize(pwcSrc, cwcSrc + 1, &cwcDest);
1561 if (RT_SUCCESS(rc))
1562 {
1563 pwszDest = (PRTUTF16) RTMemAlloc(cwcDest * 2);
1564 if (!pwszDest)
1565 rc = VERR_NO_MEMORY;
1566 }
1567 if (RT_SUCCESS(rc))
1568 rc = vboxClipboardUtf16LinToWin(pwcSrc, cwcSrc + 1, pwszDest,
1569 cwcDest);
1570 if (RT_SUCCESS(rc))
1571 {
1572 LogRelFlowFunc (("converted string is %.*ls\n", cwcDest, pwszDest));
1573 *ppwszDest = pwszDest;
1574 if (pcbDest)
1575 *pcbDest = cwcDest * 2;
1576 }
1577 else
1578 RTMemFree(pwszDest);
1579 LogRelFlowFunc(("returning %Rrc\n", rc));
1580 if (pcbDest)
1581 LogRelFlowFunc(("*pcbDest=%u\n", *pcbDest));
1582 return rc;
1583}
1584
1585/**
1586 * Converts Utf-8 text with CR end-of-lines into Utf-16 as Windows expects it
1587 * and return the result in a RTMemAlloc allocated buffer.
1588 *
1589 * @returns IPRT status code.
1590 * @param pcSrc The source Utf-8.
1591 * @param cbSrc The size of the source in bytes, not counting the
1592 * terminating zero.
1593 * @param ppwszDest Where to store the buffer address.
1594 * @param pcbDest On success, where to store the number of bytes written.
1595 * Undefined otherwise. Optional.
1596 */
1597static int clipUtf8ToWinTxt(const char *pcSrc, unsigned cbSrc,
1598 PRTUTF16 *ppwszDest, uint32_t *pcbDest)
1599{
1600 LogRelFlowFunc(("pcSrc=%p, cbSrc=%u, ppwszDest=%p\n", pcSrc, cbSrc,
1601 ppwszDest));
1602 AssertPtrReturn(pcSrc, VERR_INVALID_POINTER);
1603 AssertPtrReturn(ppwszDest, VERR_INVALID_POINTER);
1604 if (pcbDest)
1605 *pcbDest = 0;
1606 /* Intermediate conversion to UTF16 */
1607 size_t cwcTmp;
1608 PRTUTF16 pwcTmp = NULL;
1609 int rc = RTStrToUtf16Ex(pcSrc, cbSrc, &pwcTmp, 0, &cwcTmp);
1610 if (RT_SUCCESS(rc))
1611 rc = clipUtf16ToWinTxt(pwcTmp, cwcTmp, ppwszDest, pcbDest);
1612 RTUtf16Free(pwcTmp);
1613 LogRelFlowFunc(("Returning %Rrc\n", rc));
1614 if (pcbDest)
1615 LogRelFlowFunc(("*pcbDest=%u\n", *pcbDest));
1616 return rc;
1617}
1618
1619/**
1620 * Converts Latin-1 text with CR end-of-lines into Utf-16 as Windows expects
1621 * it and return the result in a RTMemAlloc allocated buffer.
1622 *
1623 * @returns IPRT status code.
1624 * @param pcSrc The source text.
1625 * @param cbSrc The size of the source in bytes, not counting the
1626 * terminating zero.
1627 * @param ppwszDest Where to store the buffer address.
1628 * @param pcbDest On success, where to store the number of bytes written.
1629 * Undefined otherwise. Optional.
1630 */
1631static int clipLatin1ToWinTxt(char *pcSrc, unsigned cbSrc,
1632 PRTUTF16 *ppwszDest, uint32_t *pcbDest)
1633{
1634 LogRelFlowFunc (("pcSrc=%.*s, cbSrc=%u, ppwszDest=%p\n", cbSrc,
1635 (char *) pcSrc, cbSrc, ppwszDest));
1636 AssertPtrReturn(pcSrc, VERR_INVALID_POINTER);
1637 AssertPtrReturn(ppwszDest, VERR_INVALID_POINTER);
1638 PRTUTF16 pwszDest = NULL;
1639 int rc = VINF_SUCCESS;
1640
1641 /* Calculate the space needed */
1642 unsigned cwcDest = 0;
1643 for (unsigned i = 0; i < cbSrc && pcSrc[i] != '\0'; ++i)
1644 if (pcSrc[i] == LINEFEED)
1645 cwcDest += 2;
1646 else
1647 ++cwcDest;
1648 ++cwcDest; /* Leave space for the terminator */
1649 if (pcbDest)
1650 *pcbDest = cwcDest * 2;
1651 pwszDest = (PRTUTF16) RTMemAlloc(cwcDest * 2);
1652 if (!pwszDest)
1653 rc = VERR_NO_MEMORY;
1654
1655 /* And do the conversion, bearing in mind that Latin-1 expands "naturally"
1656 * to Utf-16. */
1657 if (RT_SUCCESS(rc))
1658 {
1659 for (unsigned i = 0, j = 0; i < cbSrc; ++i, ++j)
1660 if (pcSrc[i] != LINEFEED)
1661 pwszDest[j] = pcSrc[i];
1662 else
1663 {
1664 pwszDest[j] = CARRIAGERETURN;
1665 pwszDest[j + 1] = LINEFEED;
1666 ++j;
1667 }
1668 pwszDest[cwcDest - 1] = '\0'; /* Make sure we are zero-terminated. */
1669 LogRelFlowFunc (("converted text is %.*ls\n", cwcDest, pwszDest));
1670 }
1671 if (RT_SUCCESS(rc))
1672 *ppwszDest = pwszDest;
1673 else
1674 RTMemFree(pwszDest);
1675 LogRelFlowFunc(("Returning %Rrc\n", rc));
1676 if (pcbDest)
1677 LogRelFlowFunc(("*pcbDest=%u\n", *pcbDest));
1678 return rc;
1679}
1680
1681
1682/**
1683 * Converts Utf16 text into UTF8 as Windows expects
1684 * it and return the result in a RTMemAlloc allocated buffer.
1685 *
1686 * @returns IPRT status code.
1687 * @param pcSrc The source text.
1688 * @param cbSrc The size of the source in bytes, not counting the
1689 * terminating zero.
1690 * @param ppwszDest Where to store the buffer address.
1691 * @param pcbDest On success, where to store the number of bytes written.
1692 * Undefined otherwise. Optional.
1693 */
1694int clipUTF16ToWinHTML(RTUTF16 *pwcBuf, size_t cb, char **ppszOut, uint32_t *pcOut)
1695{
1696 Assert(pwcBuf);
1697 Assert(cb);
1698 Assert(ppszOut);
1699 Assert(pcOut);
1700
1701 if (cb % 2)
1702 return VERR_INVALID_PARAMETER;
1703 size_t cwc = cb / 2;
1704 size_t i = 0;
1705 RTUTF16 *pwc = pwcBuf;
1706 char *pchRes = NULL;
1707 size_t cRes = 0;
1708 LogRelFlowFunc(("clipUTF16ToWinHTML src= %ls cb=%d i=%i, %x %x\n", pwcBuf, cb, i, ppszOut, pcOut));
1709 while (i < cwc)
1710 {
1711 /* find zero symbol (end of string) */
1712 for (; i < cwc && pwcBuf[i] != 0; i++)
1713 ;
1714 LogRelFlowFunc(("skipped nulls i=%d cwc=%d\n", i, cwc));
1715
1716 /* convert found string */
1717 char *psz = NULL;
1718 size_t cch = 0;
1719 int rc = RTUtf16ToUtf8Ex(pwc, cwc, &psz, pwc - pwcBuf, &cch);
1720 LogRelFlowFunc(("utf16toutf8 src= %ls res=%s i=%i\n", pwc, psz, i));
1721 if (RT_FAILURE(rc))
1722 {
1723 RTMemFree(pchRes);
1724 return rc;
1725 }
1726
1727 /* append new substring */
1728 char *pchNew = (char*)RTMemRealloc(pchRes, cRes + cch + 1);
1729 if (!pchNew)
1730 {
1731 RTMemFree(pchRes);
1732 RTStrFree(psz);
1733 return VERR_NO_MEMORY;
1734 }
1735 pchRes = pchNew;
1736 memcpy(pchRes + cRes, psz, cch + 1);
1737 LogRelFlowFunc(("Temp result res=%s\n", pchRes + cRes));
1738
1739 /* remove temporary buffer */
1740 RTStrFree(psz);
1741 cRes += cch + 1;
1742 /* skip zero symbols */
1743 for (; i < cwc && pwcBuf[i] == 0; i++)
1744 ;
1745 /* remember start of string */
1746 pwc += i;
1747 }
1748 *ppszOut = pchRes;
1749 *pcOut = cRes;
1750
1751 return VINF_SUCCESS;
1752}
1753
1754/** A structure containing information about where to store a request
1755 * for the X11 clipboard contents. */
1756struct _CLIPREADX11CBREQ
1757{
1758 /** @todo r=andy Why all those different CLIPX11FORMATTARGET members? Shouldn't be one enough? */
1759
1760 /** The format VBox would like the data in */
1761 VBOXCLIPBOARDFORMAT mFormat;
1762 /** The text format we requested from X11 if we requested text */
1763 CLIPX11FORMATIDX mTextFmtIdx;
1764 /** The bitmap format we requested from X11 if we requested bitmap */
1765 CLIPX11FORMATIDX mBitmapFmtIdx;
1766 /** The HTML format we requested from X11 if we requested HTML */
1767 CLIPX11FORMATIDX mHtmlFmtIdx;
1768#ifdef VBOX_WITH_SHARED_CLIPBOARD_URI_LIST
1769 /** The URI list format we requested from X11 if we requested URI list */
1770 CLIPX11FORMATIDX mURIListFmtIdx;
1771#endif
1772 /** The clipboard context this request is associated with */
1773 CLIPBACKEND *mCtx;
1774 /** The request structure passed in from the backend. */
1775 CLIPREADCBREQ *mReq;
1776};
1777
1778typedef struct _CLIPREADX11CBREQ CLIPREADX11CBREQ;
1779
1780/**
1781 * Converts the data obtained from the X11 clipboard to the required format,
1782 * place it in the buffer supplied and signal that data has arrived.
1783 * Convert the text obtained UTF-16LE with Windows EOLs.
1784 * Convert full BMP data to DIB format.
1785 *
1786 * @note X11 backend code, callback for XtGetSelectionValue, for use when
1787 * the X11 clipboard contains a format we understand.
1788 */
1789static void clipConvertX11CB(void *pSvcCtxData, void *pvSrc, unsigned cbSrc)
1790{
1791 CLIPREADX11CBREQ *pReq = (CLIPREADX11CBREQ *) pSvcCtxData;
1792 LogRelFlowFunc(("pReq->mFormat=%02X, pReq->mTextFormat=%u, "
1793 "pReq->mBitmapFormat=%u, pReq->mHtmlFormat=%u, pReq->mCtx=%p\n",
1794 pReq->mFormat, pReq->mTextFmtIdx, pReq->mBitmapFmtIdx,
1795 pReq->mHtmlFmtIdx, pReq->mCtx));
1796 AssertPtr(pReq->mCtx);
1797 Assert(pReq->mFormat != 0); /* sanity */
1798 int rc = VINF_SUCCESS;
1799#ifndef VBOX_AFTER_5_2
1800 CLIPBACKEND *pCtx = pReq->mCtx;
1801#endif
1802 void *pvDest = NULL;
1803 uint32_t cbDest = 0;
1804
1805#ifndef VBOX_AFTER_5_2
1806 pCtx->fBusy = false;
1807 if (pCtx->fUpdateNeeded)
1808 clipQueryX11CBFormats(pCtx);
1809#endif
1810 if (pvSrc == NULL)
1811 /* The clipboard selection may have changed before we could get it. */
1812 rc = VERR_NO_DATA;
1813 else if (pReq->mFormat == VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT)
1814 {
1815 /* In which format is the clipboard data? */
1816 switch (clipGetTargetFmtFromFmtIdx(pReq->mTextFmtIdx))
1817 {
1818 case CLIPX11FORMATTARGET_UTF8:
1819 case CLIPX11FORMATTARGET_TEXT:
1820 {
1821 /* If we are given broken Utf-8, we treat it as Latin1. Is
1822 * this acceptable? */
1823 if (RT_SUCCESS(RTStrValidateEncodingEx((char *)pvSrc, cbSrc,
1824 0)))
1825 rc = clipUtf8ToWinTxt((const char *)pvSrc, cbSrc,
1826 (PRTUTF16 *) &pvDest, &cbDest);
1827 else
1828 rc = clipLatin1ToWinTxt((char *) pvSrc, cbSrc,
1829 (PRTUTF16 *) &pvDest, &cbDest);
1830 break;
1831 }
1832 default:
1833 rc = VERR_INVALID_PARAMETER;
1834 }
1835 }
1836 else if (pReq->mFormat == VBOX_SHARED_CLIPBOARD_FMT_BITMAP)
1837 {
1838 /* In which format is the clipboard data? */
1839 switch (clipGetTargetFmtFromFmtIdx(pReq->mBitmapFmtIdx))
1840 {
1841 case CLIPX11FORMATTARGET_BMP:
1842 {
1843 const void *pDib;
1844 size_t cbDibSize;
1845 rc = vboxClipboardBmpGetDib((const void *)pvSrc, cbSrc,
1846 &pDib, &cbDibSize);
1847 if (RT_SUCCESS(rc))
1848 {
1849 pvDest = RTMemAlloc(cbDibSize);
1850 if (!pvDest)
1851 rc = VERR_NO_MEMORY;
1852 else
1853 {
1854 memcpy(pvDest, pDib, cbDibSize);
1855 cbDest = cbDibSize;
1856 }
1857 }
1858 break;
1859 }
1860 default:
1861 rc = VERR_INVALID_PARAMETER;
1862 }
1863 }
1864 else if(pReq->mFormat == VBOX_SHARED_CLIPBOARD_FMT_HTML)
1865 {
1866 /* In which format is the clipboard data? */
1867 switch (clipGetTargetFmtFromFmtIdx(pReq->mHtmlFmtIdx))
1868 {
1869 case CLIPX11FORMATTARGET_HTML:
1870 {
1871 /* The common VBox HTML encoding will be - Utf8
1872 * becuase it more general for HTML formats then UTF16
1873 * X11 clipboard returns UTF16, so before sending it we should
1874 * convert it to UTF8
1875 */
1876 pvDest = NULL;
1877 cbDest = 0;
1878 /* Some applications sends data in utf16, some in itf8,
1879 * without indication it in MIME.
1880 * But in case of utf16, at least an OpenOffice adds Byte Order Mark - 0xfeff
1881 * at start of clipboard data
1882 */
1883 if( cbSrc >= sizeof(RTUTF16) && *(PRTUTF16)pvSrc == 0xfeff )
1884 {
1885 LogRelFlowFunc((" \n"));
1886 rc = clipUTF16ToWinHTML((RTUTF16*)pvSrc, cbSrc,
1887 (char**)&pvDest, &cbDest);
1888 }
1889 else
1890 {
1891 pvDest = RTMemAlloc(cbSrc);
1892 if(pvDest)
1893 {
1894 memcpy(pvDest, pvSrc, cbSrc);
1895 cbDest = cbSrc;
1896 }
1897 else
1898 {
1899 rc = VERR_NO_MEMORY;
1900 break;
1901 }
1902 }
1903
1904 LogRelFlowFunc(("Source unicode %ls, cbSrc = %d\n, Byte Order Mark = %hx",
1905 pvSrc, cbSrc, ((PRTUTF16)pvSrc)[0]));
1906 LogRelFlowFunc(("converted to win unicode %s, cbDest = %d, rc = %Rrc\n", pvDest, cbDest, rc));
1907 rc = VINF_SUCCESS;
1908 break;
1909 }
1910 default:
1911 {
1912 rc = VERR_INVALID_PARAMETER;
1913 }
1914 }
1915 }
1916#ifdef VBOX_WITH_SHARED_CLIPBOARD_URI_LIST
1917 else if (pReq->mFormat == VBOX_SHARED_CLIPBOARD_FMT_URI_LIST)
1918 {
1919 // @todo
1920 }
1921#endif
1922 else
1923 rc = VERR_NOT_IMPLEMENTED;
1924 ClipCompleteDataRequestFromX11(pReq->mCtx->pFrontend, rc, pReq->mReq,
1925 pvDest, cbDest);
1926 RTMemFree(pvDest);
1927 RTMemFree(pReq);
1928 LogRelFlowFunc(("rc=%Rrc\n", rc));
1929}
1930
1931#ifndef TESTCASE
1932/**
1933 * Converts the data obtained from the X11 clipboard to the required format,
1934 * place it in the buffer supplied and signal that data has arrived.
1935 * Convert the text obtained UTF-16LE with Windows EOLs.
1936 * Convert full BMP data to DIB format.
1937 *
1938 * @note X11 backend code, callback for XtGetSelectionValue, for use when
1939 * the X11 clipboard contains a format we understand.
1940 */
1941static void cbConvertX11CB(Widget widget, XtPointer pSvcCtxData,
1942 Atom * /* selection */, Atom *atomType,
1943 XtPointer pvSrc, long unsigned int *pcLen,
1944 int *piFormat)
1945{
1946 RT_NOREF(widget);
1947 if (*atomType == XT_CONVERT_FAIL) /* Xt timeout */
1948 clipConvertX11CB(pSvcCtxData, NULL, 0);
1949 else
1950 clipConvertX11CB(pSvcCtxData, pvSrc, (*pcLen) * (*piFormat) / 8);
1951
1952 XtFree((char *)pvSrc);
1953}
1954#endif
1955
1956#ifdef TESTCASE
1957static void testRequestData(CLIPBACKEND* pCtx, CLIPX11FORMATTARGET target,
1958 void *closure);
1959#endif
1960
1961static void getSelectionValue(CLIPBACKEND *pCtx, CLIPX11FORMATIDX format,
1962 CLIPREADX11CBREQ *pReq)
1963{
1964#ifndef TESTCASE
1965 XtGetSelectionValue(pCtx->widget, clipGetAtom(pCtx, "CLIPBOARD"),
1966 clipGetAtomFromFmtIdx(pCtx, format),
1967 cbConvertX11CB,
1968 reinterpret_cast<XtPointer>(pReq),
1969 CurrentTime);
1970#else
1971 testRequestData(pCtx, format, (void *)pReq);
1972#endif
1973}
1974
1975/** Worker function for ClipRequestDataFromX11 which runs on the event
1976 * thread. */
1977static void vboxClipboardReadX11Worker(void *pUserData, void * /* interval */)
1978{
1979 CLIPREADX11CBREQ *pReq = (CLIPREADX11CBREQ *)pUserData;
1980 CLIPBACKEND *pCtx = pReq->mCtx;
1981 LogRelFlowFunc (("pReq->mFormat = %02X\n", pReq->mFormat));
1982
1983 int rc = VINF_SUCCESS;
1984#ifndef VBOX_AFTER_5_2
1985 bool fBusy = pCtx->fBusy;
1986 pCtx->fBusy = true;
1987 if (fBusy)
1988 {
1989 /* If the clipboard is busy just fend off the request. */
1990 rc = VERR_TRY_AGAIN;
1991 }
1992 else
1993#endif
1994 if (pReq->mFormat == VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT)
1995 {
1996 /*
1997 * VBox wants to read data in the given format.
1998 */
1999 pReq->mTextFmtIdx = pCtx->X11TextFmtIdx;
2000 if (pReq->mTextFmtIdx == CLIPX11FORMATIDX_NIL)
2001 {
2002 /* VBox thinks we have data and we don't */
2003 rc = VERR_NO_DATA;
2004 }
2005 else
2006 {
2007 /* Send out a request for the data to the current clipboard owner */
2008 getSelectionValue(pCtx, pCtx->X11TextFmtIdx, pReq);
2009 }
2010 }
2011 else if (pReq->mFormat == VBOX_SHARED_CLIPBOARD_FMT_BITMAP)
2012 {
2013 pReq->mBitmapFmtIdx = pCtx->X11BitmapFmtIdx;
2014 if (pReq->mBitmapFmtIdx == CLIPX11FORMATIDX_NIL)
2015 {
2016 /* VBox thinks we have data and we don't */
2017 rc = VERR_NO_DATA;
2018 }
2019 else
2020 {
2021 /* Send out a request for the data to the current clipboard
2022 * owner */
2023 getSelectionValue(pCtx, pCtx->X11BitmapFmtIdx, pReq);
2024 }
2025 }
2026 else if(pReq->mFormat == VBOX_SHARED_CLIPBOARD_FMT_HTML)
2027 {
2028 /* Send out a request for the data to the current clipboard owner */
2029 pReq->mHtmlFmtIdx = pCtx->X11HTMLFmtIdx;
2030 if(pReq->mHtmlFmtIdx == CLIPX11FORMATIDX_NIL)
2031 {
2032 /* VBox thinks we have data and we don't */
2033 rc = VERR_NO_DATA;
2034 }
2035 else
2036 /* Send out a request for the data to the current clipboard
2037 * owner */
2038 getSelectionValue(pCtx, pCtx->X11HTMLFmtIdx, pReq);
2039 }
2040#ifdef VBOX_WITH_SHARED_CLIPBOARD_URI_LIST
2041 else if(pReq->mFormat == VBOX_SHARED_CLIPBOARD_FMT_URI_LIST)
2042 {
2043 /* Send out a request for the data to the current clipboard owner */
2044 pReq->mURIListFmtIdx = pCtx->X11URListFmtIdx;
2045 if(pReq->mURIListFmtIdx == CLIPX11FORMATIDX_NIL)
2046 {
2047 /* VBox thinks we have data and we don't */
2048 rc = VERR_NO_DATA;
2049 }
2050 else
2051 {
2052 /* Send out a request for the data to the current clipboard owner */
2053 getSelectionValue(pCtx, pCtx->X11HTMLFmtIdx, pReq);
2054 }
2055 }
2056#endif
2057 else
2058 {
2059 rc = VERR_NOT_IMPLEMENTED;
2060#ifndef VBOX_AFTER_5_2
2061 pCtx->fBusy = false;
2062#endif
2063 }
2064
2065 if (RT_FAILURE(rc))
2066 {
2067 /* The clipboard callback was never scheduled, so we must signal
2068 * that the request processing is finished and clean up ourselves. */
2069 ClipCompleteDataRequestFromX11(pReq->mCtx->pFrontend, rc, pReq->mReq,
2070 NULL, 0);
2071 RTMemFree(pReq);
2072 }
2073
2074 LogRelFlowFunc(("status %Rrc\n", rc));
2075}
2076
2077/**
2078 * Called when VBox wants to read the X11 clipboard.
2079 *
2080 * @returns IPRT status code.
2081 * @param pCtx Context data for the clipboard backend.
2082 * @param vboxFormat The format that the VBox would like to receive the data in.
2083 * @param pv Where to write the data to.
2084 * @param cb The size of the buffer to write the data to.
2085 * @param pcbActual Where to write the actual size of the written data.
2086 *
2087 * @note We allocate a request structure which must be freed by the worker.
2088 */
2089int ClipRequestDataFromX11(CLIPBACKEND *pCtx, VBOXCLIPBOARDFORMAT vboxFormat,
2090 CLIPREADCBREQ *pReq)
2091{
2092 /*
2093 * Immediately return if we are not connected to the X server.
2094 */
2095 if (!pCtx->fHaveX11)
2096 return VERR_NO_DATA;
2097 int rc = VINF_SUCCESS;
2098 CLIPREADX11CBREQ *pX11Req;
2099 pX11Req = (CLIPREADX11CBREQ *)RTMemAllocZ(sizeof(*pX11Req));
2100 if (!pX11Req)
2101 rc = VERR_NO_MEMORY;
2102 else
2103 {
2104 pX11Req->mFormat = vboxFormat;
2105 pX11Req->mCtx = pCtx;
2106 pX11Req->mReq = pReq;
2107 /* We use this to schedule a worker function on the event thread. */
2108 clipQueueToEventThread(pCtx, vboxClipboardReadX11Worker,
2109 (XtPointer) pX11Req);
2110 }
2111 return rc;
2112}
2113
2114#ifdef TESTCASE
2115
2116/** @todo This unit test currently works by emulating the X11 and X toolkit
2117 * APIs to exercise the code, since I didn't want to rewrite the code too much
2118 * when I wrote the tests. However, this makes it rather ugly and hard to
2119 * understand. Anyone doing any work on the code should feel free to
2120 * rewrite the tests and the code to make them cleaner and more readable. */
2121
2122#include <iprt/test.h>
2123#include <poll.h>
2124
2125#define TEST_WIDGET (Widget)0xffff
2126
2127/* For the purpose of the test case, we just execute the procedure to be
2128 * scheduled, as we are running single threaded. */
2129void testQueueToEventThread(void (*proc)(void *, void *),
2130 void *client_data)
2131{
2132 proc(client_data, NULL);
2133}
2134
2135void XtFree(char *ptr)
2136{ RTMemFree((void *) ptr); }
2137
2138/* The data in the simulated VBox clipboard */
2139static int g_vboxDataRC = VINF_SUCCESS;
2140static void *g_vboxDatapv = NULL;
2141static uint32_t g_vboxDatacb = 0;
2142
2143/* Set empty data in the simulated VBox clipboard. */
2144static void clipEmptyVBox(CLIPBACKEND *pCtx, int retval)
2145{
2146 g_vboxDataRC = retval;
2147 RTMemFree(g_vboxDatapv);
2148 g_vboxDatapv = NULL;
2149 g_vboxDatacb = 0;
2150 ClipAnnounceFormatToX11(pCtx, 0);
2151}
2152
2153/* Set the data in the simulated VBox clipboard. */
2154static int clipSetVBoxUtf16(CLIPBACKEND *pCtx, int retval,
2155 const char *pcszData, size_t cb)
2156{
2157 PRTUTF16 pwszData = NULL;
2158 size_t cwData = 0;
2159 int rc = RTStrToUtf16Ex(pcszData, RTSTR_MAX, &pwszData, 0, &cwData);
2160 if (RT_FAILURE(rc))
2161 return rc;
2162 AssertReturn(cb <= cwData * 2 + 2, VERR_BUFFER_OVERFLOW);
2163 void *pv = RTMemDup(pwszData, cb);
2164 RTUtf16Free(pwszData);
2165 if (pv == NULL)
2166 return VERR_NO_MEMORY;
2167 if (g_vboxDatapv)
2168 RTMemFree(g_vboxDatapv);
2169 g_vboxDataRC = retval;
2170 g_vboxDatapv = pv;
2171 g_vboxDatacb = cb;
2172 ClipAnnounceFormatToX11(pCtx,
2173 VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT);
2174 return VINF_SUCCESS;
2175}
2176
2177/* Return the data in the simulated VBox clipboard. */
2178int ClipRequestDataForX11(VBOXCLIPBOARDCONTEXT *pCtx, VBOXCLIPBOARDFORMAT vboxFormat, void **ppv, uint32_t *pcb)
2179{
2180 RT_NOREF(pCtx, vboxFormat);
2181 *pcb = g_vboxDatacb;
2182 if (g_vboxDatapv != NULL)
2183 {
2184 void *pv = RTMemDup(g_vboxDatapv, g_vboxDatacb);
2185 *ppv = pv;
2186 return pv != NULL ? g_vboxDataRC : VERR_NO_MEMORY;
2187 }
2188 *ppv = NULL;
2189 return g_vboxDataRC;
2190}
2191
2192Display *XtDisplay(Widget w)
2193{ RT_NOREF(w); return (Display *) 0xffff; }
2194
2195void XtAppSetExitFlag(XtAppContext app_context) { RT_NOREF(app_context); }
2196
2197void XtDestroyWidget(Widget w) { RT_NOREF(w); }
2198
2199XtAppContext XtCreateApplicationContext(void) { return (XtAppContext)0xffff; }
2200
2201void XtDestroyApplicationContext(XtAppContext app_context) { RT_NOREF(app_context); }
2202
2203void XtToolkitInitialize(void) {}
2204
2205Boolean XtToolkitThreadInitialize(void) { return True; }
2206
2207Display *XtOpenDisplay(XtAppContext app_context,
2208 _Xconst _XtString display_string,
2209 _Xconst _XtString application_name,
2210 _Xconst _XtString application_class,
2211 XrmOptionDescRec *options, Cardinal num_options,
2212 int *argc, char **argv)
2213{
2214 RT_NOREF8(app_context, display_string, application_name, application_class, options, num_options, argc, argv);
2215 return (Display *)0xffff;
2216}
2217
2218Widget XtVaAppCreateShell(_Xconst _XtString application_name, _Xconst _XtString application_class,
2219 WidgetClass widget_class, Display *display, ...)
2220{
2221 RT_NOREF4(application_name, application_class, widget_class, display);
2222 return TEST_WIDGET;
2223}
2224
2225void XtSetMappedWhenManaged(Widget widget, _XtBoolean mapped_when_managed) { RT_NOREF(widget, mapped_when_managed); }
2226
2227void XtRealizeWidget(Widget widget) { RT_NOREF(widget); }
2228
2229XtInputId XtAppAddInput(XtAppContext app_context, int source, XtPointer condition, XtInputCallbackProc proc, XtPointer closure)
2230{
2231 RT_NOREF5(app_context, source, condition, proc, closure);
2232 return 0xffff;
2233}
2234
2235/* Atoms we need other than the formats we support. */
2236static const char *g_apszSupAtoms[] =
2237{
2238 "PRIMARY", "CLIPBOARD", "TARGETS", "MULTIPLE", "TIMESTAMP"
2239};
2240
2241/* This just looks for the atom names in a couple of tables and returns an
2242 * index with an offset added. */
2243Atom XInternAtom(Display *, const char *pcsz, int)
2244{
2245 Atom atom = 0;
2246 for (unsigned i = 0; i < RT_ELEMENTS(g_aFormats); ++i)
2247 if (!strcmp(pcsz, g_aFormats[i].pcszAtom))
2248 atom = (Atom) (i + 0x1000);
2249 for (unsigned i = 0; i < RT_ELEMENTS(g_apszSupAtoms); ++i)
2250 if (!strcmp(pcsz, g_apszSupAtoms[i]))
2251 atom = (Atom) (i + 0x2000);
2252 Assert(atom); /* Have we missed any atoms? */
2253 return atom;
2254}
2255
2256/* Take a request for the targets we are currently offering. */
2257static CLIPX11FORMATTARGET g_selTargets[10] = { 0 };
2258static size_t g_cTargets = 0;
2259
2260void testRequestTargets(CLIPBACKEND* pCtx)
2261{
2262 clipUpdateX11Targets(pCtx, g_selTargets, g_cTargets);
2263}
2264
2265/* The current values of the X selection, which will be returned to the
2266 * XtGetSelectionValue callback. */
2267static Atom g_selType = 0;
2268static const void *g_pSelData = NULL;
2269static unsigned long g_cSelData = 0;
2270static int g_selFormat = 0;
2271
2272void testRequestData(CLIPBACKEND *pCtx, CLIPX11FORMATTARGET target, void *closure)
2273{
2274 RT_NOREF(pCtx);
2275 unsigned long count = 0;
2276 int format = 0;
2277 if (target != g_selTargets[0])
2278 {
2279 clipConvertX11CB(closure, NULL, 0); /* Could not convert to target. */
2280 return;
2281 }
2282 void *pValue = NULL;
2283 pValue = g_pSelData ? RTMemDup(g_pSelData, g_cSelData) : NULL;
2284 count = g_pSelData ? g_cSelData : 0;
2285 format = g_selFormat;
2286 if (!pValue)
2287 {
2288 count = 0;
2289 format = 0;
2290 }
2291 clipConvertX11CB(closure, pValue, count * format / 8);
2292 if (pValue)
2293 RTMemFree(pValue);
2294}
2295
2296/* The formats currently on offer from X11 via the shared clipboard */
2297static uint32_t g_fX11Formats = 0;
2298
2299void ClipReportX11Formats(VBOXCLIPBOARDCONTEXT *pCtx, uint32_t u32Formats)
2300{
2301 RT_NOREF(pCtx);
2302 g_fX11Formats = u32Formats;
2303}
2304
2305static uint32_t clipQueryFormats()
2306{
2307 return g_fX11Formats;
2308}
2309
2310static void clipInvalidateFormats()
2311{
2312 g_fX11Formats = ~0;
2313}
2314
2315/* Does our clipboard code currently own the selection? */
2316static bool g_ownsSel = false;
2317/* The procedure that is called when we should convert the selection to a
2318 * given format. */
2319static XtConvertSelectionProc g_pfnSelConvert = NULL;
2320/* The procedure which is called when we lose the selection. */
2321static XtLoseSelectionProc g_pfnSelLose = NULL;
2322/* The procedure which is called when the selection transfer has completed. */
2323static XtSelectionDoneProc g_pfnSelDone = NULL;
2324
2325Boolean XtOwnSelection(Widget widget, Atom selection, Time time,
2326 XtConvertSelectionProc convert,
2327 XtLoseSelectionProc lose,
2328 XtSelectionDoneProc done)
2329{
2330 RT_NOREF(widget, time);
2331 if (selection != XInternAtom(NULL, "CLIPBOARD", 0))
2332 return True; /* We don't really care about this. */
2333 g_ownsSel = true; /* Always succeed. */
2334 g_pfnSelConvert = convert;
2335 g_pfnSelLose = lose;
2336 g_pfnSelDone = done;
2337 return True;
2338}
2339
2340void XtDisownSelection(Widget widget, Atom selection, Time time)
2341{
2342 RT_NOREF3(widget, time, selection);
2343 g_ownsSel = false;
2344 g_pfnSelConvert = NULL;
2345 g_pfnSelLose = NULL;
2346 g_pfnSelDone = NULL;
2347}
2348
2349/* Request the shared clipboard to convert its data to a given format. */
2350static bool clipConvertSelection(const char *pcszTarget, Atom *type,
2351 XtPointer *value, unsigned long *length,
2352 int *format)
2353{
2354 Atom target = XInternAtom(NULL, pcszTarget, 0);
2355 if (target == 0)
2356 return false;
2357 /* Initialise all return values in case we make a quick exit. */
2358 *type = XA_STRING;
2359 *value = NULL;
2360 *length = 0;
2361 *format = 0;
2362 if (!g_ownsSel)
2363 return false;
2364 if (!g_pfnSelConvert)
2365 return false;
2366 Atom clipAtom = XInternAtom(NULL, "CLIPBOARD", 0);
2367 if (!g_pfnSelConvert(TEST_WIDGET, &clipAtom, &target, type,
2368 value, length, format))
2369 return false;
2370 if (g_pfnSelDone)
2371 g_pfnSelDone(TEST_WIDGET, &clipAtom, &target);
2372 return true;
2373}
2374
2375/* Set the current X selection data */
2376static void clipSetSelectionValues(const char *pcszTarget, Atom type,
2377 const void *data,
2378 unsigned long count, int format)
2379{
2380 Atom clipAtom = XInternAtom(NULL, "CLIPBOARD", 0);
2381 g_selTargets[0] = clipGetFmtIdxFromAtomText(pcszTarget);
2382 g_cTargets = 1;
2383 g_selType = type;
2384 g_pSelData = data;
2385 g_cSelData = count;
2386 g_selFormat = format;
2387 if (g_pfnSelLose)
2388 g_pfnSelLose(TEST_WIDGET, &clipAtom);
2389 g_ownsSel = false;
2390}
2391
2392static void clipSendTargetUpdate(CLIPBACKEND *pCtx)
2393{
2394 clipQueryX11CBFormats(pCtx);
2395}
2396
2397/* Configure if and how the X11 TARGETS clipboard target will fail */
2398static void clipSetTargetsFailure(void)
2399{
2400 g_cTargets = 0;
2401}
2402
2403char *XtMalloc(Cardinal size) { return (char *) RTMemAlloc(size); }
2404
2405char *XGetAtomName(Display *display, Atom atom)
2406{
2407 RT_NOREF(display);
2408 AssertReturn((unsigned)atom < RT_ELEMENTS(g_aFormats) + 1, NULL);
2409 const char *pcszName = NULL;
2410 if (atom < 0x1000)
2411 return NULL;
2412 if (0x1000 <= atom && atom < 0x2000)
2413 {
2414 unsigned index = atom - 0x1000;
2415 AssertReturn(index < RT_ELEMENTS(g_aFormats), NULL);
2416 pcszName = g_aFormats[index].pcszAtom;
2417 }
2418 else
2419 {
2420 unsigned index = atom - 0x2000;
2421 AssertReturn(index < RT_ELEMENTS(g_apszSupAtoms), NULL);
2422 pcszName = g_apszSupAtoms[index];
2423 }
2424 return (char *)RTMemDup(pcszName, sizeof(pcszName) + 1);
2425}
2426
2427int XFree(void *data)
2428{
2429 RTMemFree(data);
2430 return 0;
2431}
2432
2433void XFreeStringList(char **list)
2434{
2435 if (list)
2436 RTMemFree(*list);
2437 RTMemFree(list);
2438}
2439
2440#define MAX_BUF_SIZE 256
2441
2442static int g_completedRC = VINF_SUCCESS;
2443static int g_completedCB = 0;
2444static CLIPREADCBREQ *g_completedReq = NULL;
2445static char g_completedBuf[MAX_BUF_SIZE];
2446
2447void ClipCompleteDataRequestFromX11(VBOXCLIPBOARDCONTEXT *pCtx, int rc, CLIPREADCBREQ *pReq, void *pv, uint32_t cb)
2448{
2449 RT_NOREF(pCtx);
2450 if (cb <= MAX_BUF_SIZE)
2451 {
2452 g_completedRC = rc;
2453 if (cb != 0)
2454 memcpy(g_completedBuf, pv, cb);
2455 }
2456 else
2457 g_completedRC = VERR_BUFFER_OVERFLOW;
2458 g_completedCB = cb;
2459 g_completedReq = pReq;
2460}
2461
2462static void clipGetCompletedRequest(int *prc, char ** ppc, uint32_t *pcb, CLIPREADCBREQ **ppReq)
2463{
2464 *prc = g_completedRC;
2465 *ppc = g_completedBuf;
2466 *pcb = g_completedCB;
2467 *ppReq = g_completedReq;
2468}
2469#ifdef RT_OS_SOLARIS_10
2470char XtStrings [] = "";
2471_WidgetClassRec* applicationShellWidgetClass;
2472char XtShellStrings [] = "";
2473int XmbTextPropertyToTextList(
2474 Display* /* display */,
2475 XTextProperty* /* text_prop */,
2476 char*** /* list_return */,
2477 int* /* count_return */
2478)
2479{
2480 return 0;
2481}
2482#else
2483const char XtStrings [] = "";
2484_WidgetClassRec* applicationShellWidgetClass;
2485const char XtShellStrings [] = "";
2486#endif
2487
2488static void testStringFromX11(RTTEST hTest, CLIPBACKEND *pCtx,
2489 const char *pcszExp, int rcExp)
2490{
2491 bool retval = true;
2492 clipSendTargetUpdate(pCtx);
2493 if (clipQueryFormats() != VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT)
2494 RTTestFailed(hTest, "Wrong targets reported: %02X\n",
2495 clipQueryFormats());
2496 else
2497 {
2498 char *pc;
2499 CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)&pReq, *pReqRet = NULL;
2500 ClipRequestDataFromX11(pCtx, VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT,
2501 pReq);
2502 int rc = VINF_SUCCESS;
2503 uint32_t cbActual = 0;
2504 clipGetCompletedRequest(&rc, &pc, &cbActual, &pReqRet);
2505 if (rc != rcExp)
2506 RTTestFailed(hTest, "Wrong return code, expected %Rrc, got %Rrc\n",
2507 rcExp, rc);
2508 else if (pReqRet != pReq)
2509 RTTestFailed(hTest, "Wrong returned request data, expected %p, got %p\n",
2510 pReq, pReqRet);
2511 else if (RT_FAILURE(rcExp))
2512 retval = true;
2513 else
2514 {
2515 RTUTF16 wcExp[MAX_BUF_SIZE / 2];
2516 RTUTF16 *pwcExp = wcExp;
2517 size_t cwc = 0;
2518 rc = RTStrToUtf16Ex(pcszExp, RTSTR_MAX, &pwcExp,
2519 RT_ELEMENTS(wcExp), &cwc);
2520 size_t cbExp = cwc * 2 + 2;
2521 AssertRC(rc);
2522 if (RT_SUCCESS(rc))
2523 {
2524 if (cbActual != cbExp)
2525 {
2526 RTTestFailed(hTest, "Returned string is the wrong size, string \"%.*ls\", size %u, expected \"%s\", size %u\n",
2527 RT_MIN(MAX_BUF_SIZE, cbActual), pc, cbActual,
2528 pcszExp, cbExp);
2529 }
2530 else
2531 {
2532 if (memcmp(pc, wcExp, cbExp) == 0)
2533 retval = true;
2534 else
2535 RTTestFailed(hTest, "Returned string \"%.*ls\" does not match expected string \"%s\"\n",
2536 MAX_BUF_SIZE, pc, pcszExp);
2537 }
2538 }
2539 }
2540 }
2541 if (!retval)
2542 RTTestFailureDetails(hTest, "Expected: string \"%s\", rc %Rrc\n",
2543 pcszExp, rcExp);
2544}
2545
2546static void testLatin1FromX11(RTTEST hTest, CLIPBACKEND *pCtx,
2547 const char *pcszExp, int rcExp)
2548{
2549 bool retval = false;
2550 clipSendTargetUpdate(pCtx);
2551 if (clipQueryFormats() != VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT)
2552 RTTestFailed(hTest, "Wrong targets reported: %02X\n",
2553 clipQueryFormats());
2554 else
2555 {
2556 char *pc;
2557 CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)&pReq, *pReqRet = NULL;
2558 ClipRequestDataFromX11(pCtx, VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT,
2559 pReq);
2560 int rc = VINF_SUCCESS;
2561 uint32_t cbActual = 0;
2562 clipGetCompletedRequest(&rc, &pc, &cbActual, &pReqRet);
2563 if (rc != rcExp)
2564 RTTestFailed(hTest, "Wrong return code, expected %Rrc, got %Rrc\n",
2565 rcExp, rc);
2566 else if (pReqRet != pReq)
2567 RTTestFailed(hTest, "Wrong returned request data, expected %p, got %p\n",
2568 pReq, pReqRet);
2569 else if (RT_FAILURE(rcExp))
2570 retval = true;
2571 else
2572 {
2573 RTUTF16 wcExp[MAX_BUF_SIZE / 2];
2574 //RTUTF16 *pwcExp = wcExp; - unused
2575 size_t cwc;
2576 for (cwc = 0; cwc == 0 || pcszExp[cwc - 1] != '\0'; ++cwc)
2577 wcExp[cwc] = pcszExp[cwc];
2578 size_t cbExp = cwc * 2;
2579 if (cbActual != cbExp)
2580 {
2581 RTTestFailed(hTest, "Returned string is the wrong size, string \"%.*ls\", size %u, expected \"%s\", size %u\n",
2582 RT_MIN(MAX_BUF_SIZE, cbActual), pc, cbActual,
2583 pcszExp, cbExp);
2584 }
2585 else
2586 {
2587 if (memcmp(pc, wcExp, cbExp) == 0)
2588 retval = true;
2589 else
2590 RTTestFailed(hTest, "Returned string \"%.*ls\" does not match expected string \"%s\"\n",
2591 MAX_BUF_SIZE, pc, pcszExp);
2592 }
2593 }
2594 }
2595 if (!retval)
2596 RTTestFailureDetails(hTest, "Expected: string \"%s\", rc %Rrc\n",
2597 pcszExp, rcExp);
2598}
2599
2600static void testStringFromVBox(RTTEST hTest, CLIPBACKEND *pCtx, const char *pcszTarget, Atom typeExp, const char *valueExp)
2601{
2602 RT_NOREF(pCtx);
2603 bool retval = false;
2604 Atom type;
2605 XtPointer value = NULL;
2606 unsigned long length;
2607 int format;
2608 size_t lenExp = strlen(valueExp);
2609 if (clipConvertSelection(pcszTarget, &type, &value, &length, &format))
2610 {
2611 if ( type != typeExp
2612 || length != lenExp
2613 || format != 8
2614 || memcmp((const void *) value, (const void *)valueExp,
2615 lenExp))
2616 {
2617 RTTestFailed(hTest, "Bad data: type %d, (expected %d), length %u, (%u), format %d (%d), value \"%.*s\" (\"%.*s\")\n",
2618 type, typeExp, length, lenExp, format, 8,
2619 RT_MIN(length, 20), value, RT_MIN(lenExp, 20), valueExp);
2620 }
2621 else
2622 retval = true;
2623 }
2624 else
2625 RTTestFailed(hTest, "Conversion failed\n");
2626 XtFree((char *)value);
2627 if (!retval)
2628 RTTestFailureDetails(hTest, "Conversion to %s, expected \"%s\"\n",
2629 pcszTarget, valueExp);
2630}
2631
2632static void testNoX11(CLIPBACKEND *pCtx, const char *pcszTestCtx)
2633{
2634 CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)&pReq;
2635 int rc = ClipRequestDataFromX11(pCtx,
2636 VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT,
2637 pReq);
2638 RTTESTI_CHECK_MSG(rc == VERR_NO_DATA, ("context: %s\n", pcszTestCtx));
2639}
2640
2641static void testStringFromVBoxFailed(RTTEST hTest, CLIPBACKEND *pCtx, const char *pcszTarget)
2642{
2643 RT_NOREF(pCtx);
2644 Atom type;
2645 XtPointer value = NULL;
2646 unsigned long length;
2647 int format;
2648 RTTEST_CHECK_MSG(hTest, !clipConvertSelection(pcszTarget, &type, &value,
2649 &length, &format),
2650 (hTest, "Conversion to target %s, should have failed but didn't, returned type %d, length %u, format %d, value \"%.*s\"\n",
2651 pcszTarget, type, length, format, RT_MIN(length, 20),
2652 value));
2653 XtFree((char *)value);
2654}
2655
2656static void testNoSelectionOwnership(CLIPBACKEND *pCtx, const char *pcszTestCtx)
2657{
2658 RT_NOREF(pCtx);
2659 RTTESTI_CHECK_MSG(!g_ownsSel, ("context: %s\n", pcszTestCtx));
2660}
2661
2662static void testBadFormatRequestFromHost(RTTEST hTest, CLIPBACKEND *pCtx)
2663{
2664 clipSetSelectionValues("UTF8_STRING", XA_STRING, "hello world",
2665 sizeof("hello world"), 8);
2666 clipSendTargetUpdate(pCtx);
2667 if (clipQueryFormats() != VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT)
2668 RTTestFailed(hTest, "Wrong targets reported: %02X\n",
2669 clipQueryFormats());
2670 else
2671 {
2672 char *pc;
2673 CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)&pReq, *pReqRet = NULL;
2674 ClipRequestDataFromX11(pCtx, 100, pReq); /* Bad format. */
2675 int rc = VINF_SUCCESS;
2676 uint32_t cbActual = 0;
2677 clipGetCompletedRequest(&rc, &pc, &cbActual, &pReqRet);
2678 if (rc != VERR_NOT_IMPLEMENTED)
2679 RTTestFailed(hTest, "Wrong return code, expected VERR_NOT_IMPLEMENTED, got %Rrc\n",
2680 rc);
2681 clipSetSelectionValues("", XA_STRING, "", sizeof(""), 8);
2682 clipSendTargetUpdate(pCtx);
2683 if (clipQueryFormats() == VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT)
2684 RTTestFailed(hTest, "Failed to report targets after bad host request\n");
2685 }
2686}
2687
2688int main()
2689{
2690 /*
2691 * Init the runtime, test and say hello.
2692 */
2693 RTTEST hTest;
2694 int rc = RTTestInitAndCreate("tstClipboardX11", &hTest);
2695 if (rc)
2696 return rc;
2697 RTTestBanner(hTest);
2698
2699 /*
2700 * Run the test.
2701 */
2702 CLIPBACKEND *pCtx = ClipConstructX11(NULL, false);
2703 char *pc;
2704 uint32_t cbActual;
2705 CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)&pReq, *pReqRet = NULL;
2706 rc = ClipStartX11(pCtx);
2707 AssertRCReturn(rc, 1);
2708
2709 /*** Utf-8 from X11 ***/
2710 RTTestSub(hTest, "reading Utf-8 from X11");
2711 /* Simple test */
2712 clipSetSelectionValues("UTF8_STRING", XA_STRING, "hello world",
2713 sizeof("hello world"), 8);
2714 testStringFromX11(hTest, pCtx, "hello world", VINF_SUCCESS);
2715 /* With an embedded carriage return */
2716 clipSetSelectionValues("text/plain;charset=UTF-8", XA_STRING,
2717 "hello\nworld", sizeof("hello\nworld"), 8);
2718 testStringFromX11(hTest, pCtx, "hello\r\nworld", VINF_SUCCESS);
2719 /* With an embedded CRLF */
2720 clipSetSelectionValues("text/plain;charset=UTF-8", XA_STRING,
2721 "hello\r\nworld", sizeof("hello\r\nworld"), 8);
2722 testStringFromX11(hTest, pCtx, "hello\r\r\nworld", VINF_SUCCESS);
2723 /* With an embedded LFCR */
2724 clipSetSelectionValues("text/plain;charset=UTF-8", XA_STRING,
2725 "hello\n\rworld", sizeof("hello\n\rworld"), 8);
2726 testStringFromX11(hTest, pCtx, "hello\r\n\rworld", VINF_SUCCESS);
2727 /* An empty string */
2728 clipSetSelectionValues("text/plain;charset=utf-8", XA_STRING, "",
2729 sizeof(""), 8);
2730 testStringFromX11(hTest, pCtx, "", VINF_SUCCESS);
2731 /* With an embedded Utf-8 character. */
2732 clipSetSelectionValues("STRING", XA_STRING,
2733 "100\xE2\x82\xAC" /* 100 Euro */,
2734 sizeof("100\xE2\x82\xAC"), 8);
2735 testStringFromX11(hTest, pCtx, "100\xE2\x82\xAC", VINF_SUCCESS);
2736 /* A non-zero-terminated string */
2737 clipSetSelectionValues("TEXT", XA_STRING,
2738 "hello world", sizeof("hello world") - 1, 8);
2739 testStringFromX11(hTest, pCtx, "hello world", VINF_SUCCESS);
2740
2741 /*** Latin1 from X11 ***/
2742 RTTestSub(hTest, "reading Latin1 from X11");
2743 /* Simple test */
2744 clipSetSelectionValues("STRING", XA_STRING, "Georges Dupr\xEA",
2745 sizeof("Georges Dupr\xEA"), 8);
2746 testLatin1FromX11(hTest, pCtx, "Georges Dupr\xEA", VINF_SUCCESS);
2747 /* With an embedded carriage return */
2748 clipSetSelectionValues("TEXT", XA_STRING, "Georges\nDupr\xEA",
2749 sizeof("Georges\nDupr\xEA"), 8);
2750 testLatin1FromX11(hTest, pCtx, "Georges\r\nDupr\xEA", VINF_SUCCESS);
2751 /* With an embedded CRLF */
2752 clipSetSelectionValues("TEXT", XA_STRING, "Georges\r\nDupr\xEA",
2753 sizeof("Georges\r\nDupr\xEA"), 8);
2754 testLatin1FromX11(hTest, pCtx, "Georges\r\r\nDupr\xEA", VINF_SUCCESS);
2755 /* With an embedded LFCR */
2756 clipSetSelectionValues("TEXT", XA_STRING, "Georges\n\rDupr\xEA",
2757 sizeof("Georges\n\rDupr\xEA"), 8);
2758 testLatin1FromX11(hTest, pCtx, "Georges\r\n\rDupr\xEA", VINF_SUCCESS);
2759 /* A non-zero-terminated string */
2760 clipSetSelectionValues("text/plain", XA_STRING,
2761 "Georges Dupr\xEA!",
2762 sizeof("Georges Dupr\xEA!") - 1, 8);
2763 testLatin1FromX11(hTest, pCtx, "Georges Dupr\xEA!", VINF_SUCCESS);
2764
2765 /*** Unknown X11 format ***/
2766 RTTestSub(hTest, "handling of an unknown X11 format");
2767 clipInvalidateFormats();
2768 clipSetSelectionValues("CLIPBOARD", XA_STRING, "Test",
2769 sizeof("Test"), 8);
2770 clipSendTargetUpdate(pCtx);
2771 RTTEST_CHECK_MSG(hTest, clipQueryFormats() == 0,
2772 (hTest, "Failed to send a format update notification\n"));
2773
2774 /*** Timeout from X11 ***/
2775 RTTestSub(hTest, "X11 timeout");
2776 clipSetSelectionValues("UTF8_STRING", XT_CONVERT_FAIL, NULL,0, 8);
2777 testStringFromX11(hTest, pCtx, "", VERR_NO_DATA);
2778
2779 /*** No data in X11 clipboard ***/
2780 RTTestSub(hTest, "a data request from an empty X11 clipboard");
2781 clipSetSelectionValues("UTF8_STRING", XA_STRING, NULL,
2782 0, 8);
2783 ClipRequestDataFromX11(pCtx, VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT,
2784 pReq);
2785 clipGetCompletedRequest(&rc, &pc, &cbActual, &pReqRet);
2786 RTTEST_CHECK_MSG(hTest, rc == VERR_NO_DATA,
2787 (hTest, "Returned %Rrc instead of VERR_NO_DATA\n",
2788 rc));
2789 RTTEST_CHECK_MSG(hTest, pReqRet == pReq,
2790 (hTest, "Wrong returned request data, expected %p, got %p\n",
2791 pReq, pReqRet));
2792
2793 /*** Ensure that VBox is notified when we return the CB to X11 ***/
2794 RTTestSub(hTest, "notification of switch to X11 clipboard");
2795 clipInvalidateFormats();
2796 clipReportEmptyX11CB(pCtx);
2797 RTTEST_CHECK_MSG(hTest, clipQueryFormats() == 0,
2798 (hTest, "Failed to send a format update (release) notification\n"));
2799
2800 /*** request for an invalid VBox format from X11 ***/
2801 RTTestSub(hTest, "a request for an invalid VBox format from X11");
2802 ClipRequestDataFromX11(pCtx, 0xffff, pReq);
2803 clipGetCompletedRequest(&rc, &pc, &cbActual, &pReqRet);
2804 RTTEST_CHECK_MSG(hTest, rc == VERR_NOT_IMPLEMENTED,
2805 (hTest, "Returned %Rrc instead of VERR_NOT_IMPLEMENTED\n",
2806 rc));
2807 RTTEST_CHECK_MSG(hTest, pReqRet == pReq,
2808 (hTest, "Wrong returned request data, expected %p, got %p\n",
2809 pReq, pReqRet));
2810
2811 /*** Targets failure from X11 ***/
2812 RTTestSub(hTest, "X11 targets conversion failure");
2813 clipSetSelectionValues("UTF8_STRING", XA_STRING, "hello world",
2814 sizeof("hello world"), 8);
2815 clipSetTargetsFailure();
2816 Atom atom = XA_STRING;
2817 long unsigned int cLen = 0;
2818 int format = 8;
2819 clipConvertX11Targets(NULL, (XtPointer) pCtx, NULL, &atom, NULL, &cLen,
2820 &format);
2821 RTTEST_CHECK_MSG(hTest, clipQueryFormats() == 0,
2822 (hTest, "Wrong targets reported: %02X\n",
2823 clipQueryFormats()));
2824
2825 /*** X11 text format conversion ***/
2826 RTTestSub(hTest, "handling of X11 selection targets");
2827 RTTEST_CHECK_MSG(hTest, clipTestTextFormatConversion(pCtx),
2828 (hTest, "failed to select the right X11 text formats\n"));
2829
2830 /*** Utf-8 from VBox ***/
2831 RTTestSub(hTest, "reading Utf-8 from VBox");
2832 /* Simple test */
2833 clipSetVBoxUtf16(pCtx, VINF_SUCCESS, "hello world",
2834 sizeof("hello world") * 2);
2835 testStringFromVBox(hTest, pCtx, "UTF8_STRING",
2836 clipGetAtom(pCtx, "UTF8_STRING"), "hello world");
2837 /* With an embedded carriage return */
2838 clipSetVBoxUtf16(pCtx, VINF_SUCCESS, "hello\r\nworld",
2839 sizeof("hello\r\nworld") * 2);
2840 testStringFromVBox(hTest, pCtx, "text/plain;charset=UTF-8",
2841 clipGetAtom(pCtx, "text/plain;charset=UTF-8"),
2842 "hello\nworld");
2843 /* With an embedded CRCRLF */
2844 clipSetVBoxUtf16(pCtx, VINF_SUCCESS, "hello\r\r\nworld",
2845 sizeof("hello\r\r\nworld") * 2);
2846 testStringFromVBox(hTest, pCtx, "text/plain;charset=UTF-8",
2847 clipGetAtom(pCtx, "text/plain;charset=UTF-8"),
2848 "hello\r\nworld");
2849 /* With an embedded CRLFCR */
2850 clipSetVBoxUtf16(pCtx, VINF_SUCCESS, "hello\r\n\rworld",
2851 sizeof("hello\r\n\rworld") * 2);
2852 testStringFromVBox(hTest, pCtx, "text/plain;charset=UTF-8",
2853 clipGetAtom(pCtx, "text/plain;charset=UTF-8"),
2854 "hello\n\rworld");
2855 /* An empty string */
2856 clipSetVBoxUtf16(pCtx, VINF_SUCCESS, "", 2);
2857 testStringFromVBox(hTest, pCtx, "text/plain;charset=utf-8",
2858 clipGetAtom(pCtx, "text/plain;charset=utf-8"), "");
2859 /* With an embedded Utf-8 character. */
2860 clipSetVBoxUtf16(pCtx, VINF_SUCCESS, "100\xE2\x82\xAC" /* 100 Euro */,
2861 10);
2862 testStringFromVBox(hTest, pCtx, "STRING",
2863 clipGetAtom(pCtx, "STRING"), "100\xE2\x82\xAC");
2864 /* A non-zero-terminated string */
2865 clipSetVBoxUtf16(pCtx, VINF_SUCCESS, "hello world",
2866 sizeof("hello world") * 2 - 2);
2867 testStringFromVBox(hTest, pCtx, "TEXT", clipGetAtom(pCtx, "TEXT"),
2868 "hello world");
2869
2870 /*** Timeout from VBox ***/
2871 RTTestSub(hTest, "reading from VBox with timeout");
2872 clipEmptyVBox(pCtx, VERR_TIMEOUT);
2873 testStringFromVBoxFailed(hTest, pCtx, "UTF8_STRING");
2874
2875 /*** No data in VBox clipboard ***/
2876 RTTestSub(hTest, "an empty VBox clipboard");
2877 clipSetSelectionValues("TEXT", XA_STRING, "", sizeof(""), 8);
2878 clipEmptyVBox(pCtx, VINF_SUCCESS);
2879 RTTEST_CHECK_MSG(hTest, g_ownsSel,
2880 (hTest, "VBox grabbed the clipboard with no data and we ignored it\n"));
2881 testStringFromVBoxFailed(hTest, pCtx, "UTF8_STRING");
2882
2883 /*** An unknown VBox format ***/
2884 RTTestSub(hTest, "reading an unknown VBox format");
2885 clipSetSelectionValues("TEXT", XA_STRING, "", sizeof(""), 8);
2886 clipSetVBoxUtf16(pCtx, VINF_SUCCESS, "", 2);
2887 ClipAnnounceFormatToX11(pCtx, 0xa0000);
2888 RTTEST_CHECK_MSG(hTest, g_ownsSel,
2889 (hTest, "VBox grabbed the clipboard with unknown data and we ignored it\n"));
2890 testStringFromVBoxFailed(hTest, pCtx, "UTF8_STRING");
2891
2892 /*** VBox requests a bad format ***/
2893 RTTestSub(hTest, "recovery from a bad format request");
2894 testBadFormatRequestFromHost(hTest, pCtx);
2895
2896 rc = ClipStopX11(pCtx);
2897 AssertRCReturn(rc, 1);
2898 ClipDestructX11(pCtx);
2899
2900 /*** Headless clipboard tests ***/
2901
2902 pCtx = ClipConstructX11(NULL, true);
2903 rc = ClipStartX11(pCtx);
2904 AssertRCReturn(rc, 1);
2905
2906 /*** Read from X11 ***/
2907 RTTestSub(hTest, "reading from X11, headless clipboard");
2908 /* Simple test */
2909 clipSetVBoxUtf16(pCtx, VINF_SUCCESS, "",
2910 sizeof("") * 2);
2911 clipSetSelectionValues("UTF8_STRING", XA_STRING, "hello world",
2912 sizeof("hello world"), 8);
2913 testNoX11(pCtx, "reading from X11, headless clipboard");
2914
2915 /*** Read from VBox ***/
2916 RTTestSub(hTest, "reading from VBox, headless clipboard");
2917 /* Simple test */
2918 clipEmptyVBox(pCtx, VERR_WRONG_ORDER);
2919 clipSetSelectionValues("TEXT", XA_STRING, "", sizeof(""), 8);
2920 clipSetVBoxUtf16(pCtx, VINF_SUCCESS, "hello world",
2921 sizeof("hello world") * 2);
2922 testNoSelectionOwnership(pCtx, "reading from VBox, headless clipboard");
2923
2924 rc = ClipStopX11(pCtx);
2925 AssertRCReturn(rc, 1);
2926 ClipDestructX11(pCtx);
2927
2928 return RTTestSummaryAndDestroy(hTest);
2929}
2930
2931#endif
2932
2933#ifdef SMOKETEST
2934
2935/* This is a simple test case that just starts a copy of the X11 clipboard
2936 * backend, checks the X11 clipboard and exits. If ever needed I will add an
2937 * interactive mode in which the user can read and copy to the clipboard from
2938 * the command line. */
2939
2940# include <iprt/env.h>
2941# include <iprt/test.h>
2942
2943int ClipRequestDataForX11(VBOXCLIPBOARDCONTEXT *pCtx, uint32_t u32Format, void **ppv, uint32_t *pcb)
2944{
2945 RT_NOREF4(pCtx, u32Format, ppv, pcb);
2946 return VERR_NO_DATA;
2947}
2948
2949void ClipReportX11Formats(VBOXCLIPBOARDCONTEXT *pCtx, uint32_t u32Formats)
2950{
2951 RT_NOREF(pCtx, u32Formats);
2952}
2953
2954void ClipCompleteDataRequestFromX11(VBOXCLIPBOARDCONTEXT *pCtx, int rc, CLIPREADCBREQ *pReq, void *pv, uint32_t cb)
2955{
2956 RT_NOREF5(pCtx, rc, pReq, pv, cb);
2957}
2958
2959int main()
2960{
2961 /*
2962 * Init the runtime, test and say hello.
2963 */
2964 RTTEST hTest;
2965 int rc = RTTestInitAndCreate("tstClipboardX11Smoke", &hTest);
2966 if (rc)
2967 return rc;
2968 RTTestBanner(hTest);
2969
2970 /*
2971 * Run the test.
2972 */
2973 rc = VINF_SUCCESS;
2974 /* We can't test anything without an X session, so just return success
2975 * in that case. */
2976 if (!RTEnvExist("DISPLAY"))
2977 {
2978 RTTestPrintf(hTest, RTTESTLVL_INFO,
2979 "X11 not available, not running test\n");
2980 return RTTestSummaryAndDestroy(hTest);
2981 }
2982 CLIPBACKEND *pCtx = ClipConstructX11(NULL, false);
2983 AssertReturn(pCtx, 1);
2984 rc = ClipStartX11(pCtx);
2985 AssertRCReturn(rc, 1);
2986 /* Give the clipboard time to synchronise. */
2987 RTThreadSleep(500);
2988 rc = ClipStopX11(pCtx);
2989 AssertRCReturn(rc, 1);
2990 ClipDestructX11(pCtx);
2991 return RTTestSummaryAndDestroy(hTest);
2992}
2993
2994#endif /* SMOKETEST defined */
2995
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use