VirtualBox

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

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

GuestHost/SharedClipboard/X11: attempt to fix public bug #11792 (copy and paste between virtual machines).

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