VirtualBox

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

Last change on this file since 63567 was 63567, checked in by vboxsync, 8 years ago

scm: cleaning up todos

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

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette