VirtualBox

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

Last change on this file since 46307 was 46307, checked in by vboxsync, 12 years ago

GuestHost/SharedClipboard: remove COMPOUND_TEXT support from the shared clipboard: too much code for a format which no one uses.

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

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