VirtualBox

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

Last change on this file since 56319 was 54807, checked in by vboxsync, 10 years ago

SharedClipboard/x11-clipboard: logrels.

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

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