VirtualBox

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

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

header (C) fixes

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 98.3 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 }
1280}
1281
1282/**
1283 * Worker function for ClipAnnounceFormatToX11 which runs on the
1284 * event thread.
1285 * @param pUserData Pointer to a CLIPNEWVBOXFORMATS structure containing
1286 * information about the VBox formats available and the
1287 * clipboard context data. Must be freed by the worker.
1288 */
1289static void clipNewVBoxFormatsWorker(XtPointer pUserData,
1290 XtIntervalId * /* interval */)
1291{
1292 CLIPNEWVBOXFORMATS *pFormats = (CLIPNEWVBOXFORMATS *)pUserData;
1293 CLIPBACKEND *pCtx = pFormats->pCtx;
1294 uint32_t u32Formats = pFormats->formats;
1295 RTMemFree(pFormats);
1296 LogRelFlowFunc (("u32Formats=%d\n", u32Formats));
1297 clipInvalidateVBoxCBCache(pCtx);
1298 clipGrabX11CB(pCtx, u32Formats);
1299 clipResetX11Formats(pCtx);
1300 LogRelFlowFunc(("returning\n"));
1301}
1302
1303/**
1304 * VBox is taking possession of the shared clipboard.
1305 *
1306 * @param u32Formats Clipboard formats that VBox is offering
1307 * @note X11 backend code
1308 */
1309void ClipAnnounceFormatToX11(CLIPBACKEND *pCtx,
1310 uint32_t u32Formats)
1311{
1312 /*
1313 * Immediately return if we are not connected to the X server.
1314 */
1315 if (!pCtx->fHaveX11)
1316 return;
1317 /* This must be freed by the worker callback */
1318 CLIPNEWVBOXFORMATS *pFormats =
1319 (CLIPNEWVBOXFORMATS *) RTMemAlloc(sizeof(CLIPNEWVBOXFORMATS));
1320 if (pFormats != NULL) /* if it is we will soon have other problems */
1321 {
1322 pFormats->pCtx = pCtx;
1323 pFormats->formats = u32Formats;
1324 clipQueueToEventThread(pCtx, clipNewVBoxFormatsWorker,
1325 (XtPointer) pFormats);
1326 }
1327}
1328
1329/**
1330 * Massage generic Utf16 with CR end-of-lines into the format Windows expects
1331 * and return the result in a RTMemAlloc allocated buffer.
1332 * @returns IPRT status code
1333 * @param pwcSrc The source Utf16
1334 * @param cwcSrc The number of 16bit elements in @a pwcSrc, not counting
1335 * the terminating zero
1336 * @param ppwszDest Where to store the buffer address
1337 * @param pcbDest On success, where to store the number of bytes written.
1338 * Undefined otherwise. Optional
1339 */
1340static int clipUtf16ToWinTxt(RTUTF16 *pwcSrc, size_t cwcSrc,
1341 PRTUTF16 *ppwszDest, uint32_t *pcbDest)
1342{
1343 LogRelFlowFunc(("pwcSrc=%p, cwcSrc=%u, ppwszDest=%p\n", pwcSrc, cwcSrc,
1344 ppwszDest));
1345 AssertPtrReturn(pwcSrc, VERR_INVALID_POINTER);
1346 AssertPtrReturn(ppwszDest, VERR_INVALID_POINTER);
1347 if (pcbDest)
1348 *pcbDest = 0;
1349 PRTUTF16 pwszDest = NULL;
1350 size_t cwcDest;
1351 int rc = vboxClipboardUtf16GetWinSize(pwcSrc, cwcSrc + 1, &cwcDest);
1352 if (RT_SUCCESS(rc))
1353 {
1354 pwszDest = (PRTUTF16) RTMemAlloc(cwcDest * 2);
1355 if (!pwszDest)
1356 rc = VERR_NO_MEMORY;
1357 }
1358 if (RT_SUCCESS(rc))
1359 rc = vboxClipboardUtf16LinToWin(pwcSrc, cwcSrc + 1, pwszDest,
1360 cwcDest);
1361 if (RT_SUCCESS(rc))
1362 {
1363 LogRelFlowFunc (("converted string is %.*ls\n", cwcDest, pwszDest));
1364 *ppwszDest = pwszDest;
1365 if (pcbDest)
1366 *pcbDest = cwcDest * 2;
1367 }
1368 else
1369 RTMemFree(pwszDest);
1370 LogRelFlowFunc(("returning %Rrc\n", rc));
1371 if (pcbDest)
1372 LogRelFlowFunc(("*pcbDest=%u\n", *pcbDest));
1373 return rc;
1374}
1375
1376/**
1377 * Convert Utf-8 text with CR end-of-lines into Utf-16 as Windows expects it
1378 * and return the result in a RTMemAlloc allocated buffer.
1379 * @returns IPRT status code
1380 * @param pcSrc The source Utf-8
1381 * @param cbSrc The size of the source in bytes, not counting the
1382 * terminating zero
1383 * @param ppwszDest Where to store the buffer address
1384 * @param pcbDest On success, where to store the number of bytes written.
1385 * Undefined otherwise. Optional
1386 */
1387static int clipUtf8ToWinTxt(const char *pcSrc, unsigned cbSrc,
1388 PRTUTF16 *ppwszDest, uint32_t *pcbDest)
1389{
1390 LogRelFlowFunc(("pcSrc=%p, cbSrc=%u, ppwszDest=%p\n", pcSrc, cbSrc,
1391 ppwszDest));
1392 AssertPtrReturn(pcSrc, VERR_INVALID_POINTER);
1393 AssertPtrReturn(ppwszDest, VERR_INVALID_POINTER);
1394 if (pcbDest)
1395 *pcbDest = 0;
1396 /* Intermediate conversion to UTF16 */
1397 size_t cwcTmp;
1398 PRTUTF16 pwcTmp = NULL;
1399 int rc = RTStrToUtf16Ex(pcSrc, cbSrc, &pwcTmp, 0, &cwcTmp);
1400 if (RT_SUCCESS(rc))
1401 rc = clipUtf16ToWinTxt(pwcTmp, cwcTmp, ppwszDest, pcbDest);
1402 RTUtf16Free(pwcTmp);
1403 LogRelFlowFunc(("Returning %Rrc\n", rc));
1404 if (pcbDest)
1405 LogRelFlowFunc(("*pcbDest=%u\n", *pcbDest));
1406 return rc;
1407}
1408
1409/**
1410 * Convert COMPOUND TEXT with CR end-of-lines into Utf-16 as Windows expects
1411 * it and return the result in a RTMemAlloc allocated buffer.
1412 * @returns IPRT status code
1413 * @param widget An Xt widget, necessary because we use Xt/Xlib for the
1414 * conversion
1415 * @param pcSrc The source text
1416 * @param cbSrc The size of the source in bytes, not counting the
1417 * terminating zero
1418 * @param ppwszDest Where to store the buffer address
1419 * @param pcbDest On success, where to store the number of bytes written.
1420 * Undefined otherwise. Optional
1421 */
1422static int clipCTextToWinTxt(Widget widget, unsigned char *pcSrc,
1423 unsigned cbSrc, PRTUTF16 *ppwszDest,
1424 uint32_t *pcbDest)
1425{
1426 LogRelFlowFunc(("widget=%p, pcSrc=%p, cbSrc=%u, ppwszDest=%p\n", widget,
1427 pcSrc, cbSrc, ppwszDest));
1428 AssertReturn(widget, VERR_INVALID_PARAMETER);
1429 AssertPtrReturn(pcSrc, VERR_INVALID_POINTER);
1430 AssertPtrReturn(ppwszDest, VERR_INVALID_POINTER);
1431 if (pcbDest)
1432 *pcbDest = 0;
1433
1434 /* Special case as X*TextProperty* can't seem to handle empty strings. */
1435 if (cbSrc == 0)
1436 {
1437 *ppwszDest = (PRTUTF16) RTMemAlloc(2);
1438 if (!*ppwszDest)
1439 return VERR_NO_MEMORY;
1440 **ppwszDest = 0;
1441 if (pcbDest)
1442 *pcbDest = 2;
1443 return VINF_SUCCESS;
1444 }
1445
1446 if (pcbDest)
1447 *pcbDest = 0;
1448 /* Intermediate conversion to Utf8 */
1449 int rc = VINF_SUCCESS;
1450 XTextProperty property;
1451 char **ppcTmp = NULL, *pszTmp = NULL;
1452 int cProps;
1453
1454 property.value = pcSrc;
1455 property.encoding = clipGetAtom(widget, "COMPOUND_TEXT");
1456 property.format = 8;
1457 property.nitems = cbSrc;
1458 int xrc = XmbTextPropertyToTextList(XtDisplay(widget), &property,
1459 &ppcTmp, &cProps);
1460 if (xrc < 0)
1461 rc = ( xrc == XNoMemory ? VERR_NO_MEMORY
1462 : xrc == XLocaleNotSupported ? VERR_NOT_SUPPORTED
1463 : xrc == XConverterNotFound ? VERR_NOT_SUPPORTED
1464 : VERR_UNRESOLVED_ERROR);
1465 /* Convert the text returned to UTF8 */
1466 if (RT_SUCCESS(rc))
1467 rc = RTStrCurrentCPToUtf8(&pszTmp, *ppcTmp);
1468 /* Now convert the UTF8 to UTF16 */
1469 if (RT_SUCCESS(rc))
1470 rc = clipUtf8ToWinTxt(pszTmp, strlen(pszTmp), ppwszDest, pcbDest);
1471 if (ppcTmp != NULL)
1472 XFreeStringList(ppcTmp);
1473 RTStrFree(pszTmp);
1474 LogRelFlowFunc(("Returning %Rrc\n", rc));
1475 if (pcbDest)
1476 LogRelFlowFunc(("*pcbDest=%u\n", *pcbDest));
1477 return rc;
1478}
1479
1480/**
1481 * Convert Latin-1 text with CR end-of-lines into Utf-16 as Windows expects
1482 * it and return the result in a RTMemAlloc allocated buffer.
1483 * @returns IPRT status code
1484 * @param pcSrc The source text
1485 * @param cbSrc The size of the source in bytes, not counting the
1486 * terminating zero
1487 * @param ppwszDest Where to store the buffer address
1488 * @param pcbDest On success, where to store the number of bytes written.
1489 * Undefined otherwise. Optional
1490 */
1491static int clipLatin1ToWinTxt(char *pcSrc, unsigned cbSrc,
1492 PRTUTF16 *ppwszDest, uint32_t *pcbDest)
1493{
1494 LogRelFlowFunc (("pcSrc=%.*s, cbSrc=%u, ppwszDest=%p\n", cbSrc,
1495 (char *) pcSrc, cbSrc, ppwszDest));
1496 AssertPtrReturn(pcSrc, VERR_INVALID_POINTER);
1497 AssertPtrReturn(ppwszDest, VERR_INVALID_POINTER);
1498 PRTUTF16 pwszDest = NULL;
1499 int rc = VINF_SUCCESS;
1500
1501 /* Calculate the space needed */
1502 unsigned cwcDest = 0;
1503 for (unsigned i = 0; i < cbSrc && pcSrc[i] != '\0'; ++i)
1504 if (pcSrc[i] == LINEFEED)
1505 cwcDest += 2;
1506 else
1507 ++cwcDest;
1508 ++cwcDest; /* Leave space for the terminator */
1509 if (pcbDest)
1510 *pcbDest = cwcDest * 2;
1511 pwszDest = (PRTUTF16) RTMemAlloc(cwcDest * 2);
1512 if (!pwszDest)
1513 rc = VERR_NO_MEMORY;
1514
1515 /* And do the conversion, bearing in mind that Latin-1 expands "naturally"
1516 * to Utf-16. */
1517 if (RT_SUCCESS(rc))
1518 {
1519 for (unsigned i = 0, j = 0; i < cbSrc; ++i, ++j)
1520 if (pcSrc[i] != LINEFEED)
1521 pwszDest[j] = pcSrc[i];
1522 else
1523 {
1524 pwszDest[j] = CARRIAGERETURN;
1525 pwszDest[j + 1] = LINEFEED;
1526 ++j;
1527 }
1528 pwszDest[cwcDest - 1] = '\0'; /* Make sure we are zero-terminated. */
1529 LogRelFlowFunc (("converted text is %.*ls\n", cwcDest, pwszDest));
1530 }
1531 if (RT_SUCCESS(rc))
1532 *ppwszDest = pwszDest;
1533 else
1534 RTMemFree(pwszDest);
1535 LogRelFlowFunc(("Returning %Rrc\n", rc));
1536 if (pcbDest)
1537 LogRelFlowFunc(("*pcbDest=%u\n", *pcbDest));
1538 return rc;
1539}
1540
1541/** A structure containing information about where to store a request
1542 * for the X11 clipboard contents. */
1543struct _CLIPREADX11CBREQ
1544{
1545 /** The format VBox would like the data in */
1546 uint32_t mFormat;
1547 /** The text format we requested from X11 if we requested text */
1548 CLIPX11FORMAT mTextFormat;
1549 /** The bitmap format we requested from X11 if we requested bitmap */
1550 CLIPX11FORMAT mBitmapFormat;
1551 /** The clipboard context this request is associated with */
1552 CLIPBACKEND *mCtx;
1553 /** The request structure passed in from the backend. */
1554 CLIPREADCBREQ *mReq;
1555};
1556
1557typedef struct _CLIPREADX11CBREQ CLIPREADX11CBREQ;
1558
1559/**
1560 * Convert the data obtained from the X11 clipboard to the required format,
1561 * place it in the buffer supplied and signal that data has arrived.
1562 * Convert the text obtained UTF-16LE with Windows EOLs.
1563 * Convert full BMP data to DIB format.
1564 * @note X11 backend code, callback for XtGetSelectionValue, for use when
1565 * the X11 clipboard contains a format we understand.
1566 */
1567static void clipConvertX11CB(Widget widget, XtPointer pClientData,
1568 Atom * /* selection */, Atom *atomType,
1569 XtPointer pvSrc, long unsigned int *pcLen,
1570 int *piFormat)
1571{
1572 CLIPREADX11CBREQ *pReq = (CLIPREADX11CBREQ *) pClientData;
1573 LogRelFlowFunc(("pReq->mFormat=%02X, pReq->mTextFormat=%u, "
1574 "pReq->mBitmapFormat=%u, pReq->mCtx=%p\n",
1575 pReq->mFormat, pReq->mTextFormat, pReq->mBitmapFormat,
1576 pReq->mCtx));
1577 AssertPtr(pReq->mCtx);
1578 Assert(pReq->mFormat != 0); /* sanity */
1579 int rc = VINF_SUCCESS;
1580 CLIPBACKEND *pCtx = pReq->mCtx;
1581 unsigned cbSrc = (*pcLen) * (*piFormat) / 8;
1582 void *pvDest = NULL;
1583 uint32_t cbDest = 0;
1584
1585 if (pvSrc == NULL)
1586 /* The clipboard selection may have changed before we could get it. */
1587 rc = VERR_NO_DATA;
1588 else if (*atomType == XT_CONVERT_FAIL) /* Xt timeout */
1589 rc = VERR_TIMEOUT;
1590 else if (pReq->mFormat == VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT)
1591 {
1592 /* In which format is the clipboard data? */
1593 switch (clipRealFormatForX11Format(pReq->mTextFormat))
1594 {
1595 case CTEXT:
1596 rc = clipCTextToWinTxt(widget, (unsigned char *)pvSrc, cbSrc,
1597 (PRTUTF16 *) &pvDest, &cbDest);
1598 break;
1599 case UTF8:
1600 case TEXT:
1601 {
1602 /* If we are given broken Utf-8, we treat it as Latin1. Is
1603 * this acceptable? */
1604 if (RT_SUCCESS(RTStrValidateEncodingEx((char *)pvSrc, cbSrc,
1605 0)))
1606 rc = clipUtf8ToWinTxt((const char *)pvSrc, cbSrc,
1607 (PRTUTF16 *) &pvDest, &cbDest);
1608 else
1609 rc = clipLatin1ToWinTxt((char *) pvSrc, cbSrc,
1610 (PRTUTF16 *) &pvDest, &cbDest);
1611 break;
1612 }
1613 default:
1614 rc = VERR_INVALID_PARAMETER;
1615 }
1616 }
1617 else if (pReq->mFormat == VBOX_SHARED_CLIPBOARD_FMT_BITMAP)
1618 {
1619 /* In which format is the clipboard data? */
1620 switch (clipRealFormatForX11Format(pReq->mBitmapFormat))
1621 {
1622 case BMP:
1623 {
1624 const void *pDib;
1625 size_t cbDibSize;
1626 rc = vboxClipboardBmpGetDib((const void *)pvSrc, cbSrc,
1627 &pDib, &cbDibSize);
1628 if (RT_SUCCESS(rc))
1629 {
1630 pvDest = RTMemAlloc(cbDibSize);
1631 if (!pvDest)
1632 rc = VERR_NO_MEMORY;
1633 else
1634 {
1635 memcpy(pvDest, pDib, cbDibSize);
1636 cbDest = cbDibSize;
1637 }
1638 }
1639 break;
1640 }
1641 default:
1642 rc = VERR_INVALID_PARAMETER;
1643 }
1644 }
1645 else
1646 rc = VERR_NOT_IMPLEMENTED;
1647 XtFree((char *)pvSrc);
1648 ClipCompleteDataRequestFromX11(pReq->mCtx->pFrontend, rc, pReq->mReq,
1649 pvDest, cbDest);
1650 RTMemFree(pvDest);
1651 RTMemFree(pReq);
1652 LogRelFlowFunc(("rc=%Rrc\n", rc));
1653}
1654
1655/** Worker function for ClipRequestDataFromX11 which runs on the event
1656 * thread. */
1657static void vboxClipboardReadX11Worker(XtPointer pUserData,
1658 XtIntervalId * /* interval */)
1659{
1660 CLIPREADX11CBREQ *pReq = (CLIPREADX11CBREQ *)pUserData;
1661 CLIPBACKEND *pCtx = pReq->mCtx;
1662 LogRelFlowFunc (("pReq->mFormat = %02X\n", pReq->mFormat));
1663
1664 int rc = VINF_SUCCESS;
1665 /*
1666 * VBox wants to read data in the given format.
1667 */
1668 if (pReq->mFormat == VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT)
1669 {
1670 pReq->mTextFormat = pCtx->X11TextFormat;
1671 if (pReq->mTextFormat == INVALID)
1672 /* VBox thinks we have data and we don't */
1673 rc = VERR_NO_DATA;
1674 else
1675 /* Send out a request for the data to the current clipboard
1676 * owner */
1677 XtGetSelectionValue(pCtx->widget, clipGetAtom(pCtx->widget, "CLIPBOARD"),
1678 clipAtomForX11Format(pCtx->widget,
1679 pCtx->X11TextFormat),
1680 clipConvertX11CB,
1681 reinterpret_cast<XtPointer>(pReq),
1682 CurrentTime);
1683 }
1684 else if (pReq->mFormat == VBOX_SHARED_CLIPBOARD_FMT_BITMAP)
1685 {
1686 pReq->mBitmapFormat = pCtx->X11BitmapFormat;
1687 if (pReq->mBitmapFormat == INVALID)
1688 /* VBox thinks we have data and we don't */
1689 rc = VERR_NO_DATA;
1690 else
1691 /* Send out a request for the data to the current clipboard
1692 * owner */
1693 XtGetSelectionValue(pCtx->widget, clipGetAtom(pCtx->widget, "CLIPBOARD"),
1694 clipAtomForX11Format(pCtx->widget,
1695 pCtx->X11BitmapFormat),
1696 clipConvertX11CB,
1697 reinterpret_cast<XtPointer>(pReq),
1698 CurrentTime);
1699 }
1700 else
1701 rc = VERR_NOT_IMPLEMENTED;
1702 if (RT_FAILURE(rc))
1703 {
1704 /* The clipboard callback was never scheduled, so we must signal
1705 * that the request processing is finished and clean up ourselves. */
1706 ClipCompleteDataRequestFromX11(pReq->mCtx->pFrontend, rc, pReq->mReq,
1707 NULL, 0);
1708 RTMemFree(pReq);
1709 }
1710 LogRelFlowFunc(("status %Rrc\n", rc));
1711}
1712
1713/**
1714 * Called when VBox wants to read the X11 clipboard.
1715 *
1716 * @returns iprt status code
1717 * @param pCtx Context data for the clipboard backend
1718 * @param u32Format The format that the VBox would like to receive the data
1719 * in
1720 * @param pv Where to write the data to
1721 * @param cb The size of the buffer to write the data to
1722 * @param pcbActual Where to write the actual size of the written data
1723 * @note We allocate a request structure which must be freed by the worker
1724 */
1725int ClipRequestDataFromX11(CLIPBACKEND *pCtx, uint32_t u32Format,
1726 CLIPREADCBREQ *pReq)
1727{
1728 /*
1729 * Immediately return if we are not connected to the X server.
1730 */
1731 if (!pCtx->fHaveX11)
1732 return VERR_NO_DATA;
1733 int rc = VINF_SUCCESS;
1734 CLIPREADX11CBREQ *pX11Req;
1735 pX11Req = (CLIPREADX11CBREQ *)RTMemAllocZ(sizeof(*pX11Req));
1736 if (!pX11Req)
1737 rc = VERR_NO_MEMORY;
1738 else
1739 {
1740 pX11Req->mFormat = u32Format;
1741 pX11Req->mCtx = pCtx;
1742 pX11Req->mReq = pReq;
1743 /* We use this to schedule a worker function on the event thread. */
1744 clipQueueToEventThread(pCtx, vboxClipboardReadX11Worker,
1745 (XtPointer) pX11Req);
1746 }
1747 return rc;
1748}
1749
1750#ifdef TESTCASE
1751
1752/** @todo This unit test currently works by emulating the X11 and X toolkit
1753 * APIs to exercise the code, since I didn't want to rewrite the code too much
1754 * when I wrote the tests. However, this makes it rather ugly and hard to
1755 * understand. Anyone doing any work on the code should feel free to
1756 * rewrite the tests and the code to make them cleaner and more readable. */
1757
1758#include <iprt/test.h>
1759#include <poll.h>
1760
1761#define TEST_WIDGET (Widget)0xffff
1762
1763/* For the purpose of the test case, we just execute the procedure to be
1764 * scheduled, as we are running single threaded. */
1765void clipQueueToEventThread(CLIPBACKEND *pCtx,
1766 XtTimerCallbackProc proc,
1767 XtPointer client_data)
1768{
1769 proc(client_data, NULL);
1770}
1771
1772void XtFree(char *ptr)
1773{ RTMemFree((void *) ptr); }
1774
1775/* The data in the simulated VBox clipboard */
1776static int g_vboxDataRC = VINF_SUCCESS;
1777static void *g_vboxDatapv = NULL;
1778static uint32_t g_vboxDatacb = 0;
1779
1780/* Set empty data in the simulated VBox clipboard. */
1781static void clipEmptyVBox(CLIPBACKEND *pCtx, int retval)
1782{
1783 g_vboxDataRC = retval;
1784 RTMemFree(g_vboxDatapv);
1785 g_vboxDatapv = NULL;
1786 g_vboxDatacb = 0;
1787 ClipAnnounceFormatToX11(pCtx, 0);
1788}
1789
1790/* Set the data in the simulated VBox clipboard. */
1791static int clipSetVBoxUtf16(CLIPBACKEND *pCtx, int retval,
1792 const char *pcszData, size_t cb)
1793{
1794 PRTUTF16 pwszData = NULL;
1795 size_t cwData = 0;
1796 int rc = RTStrToUtf16Ex(pcszData, RTSTR_MAX, &pwszData, 0, &cwData);
1797 if (RT_FAILURE(rc))
1798 return rc;
1799 AssertReturn(cb <= cwData * 2 + 2, VERR_BUFFER_OVERFLOW);
1800 void *pv = RTMemDup(pwszData, cb);
1801 RTUtf16Free(pwszData);
1802 if (pv == NULL)
1803 return VERR_NO_MEMORY;
1804 if (g_vboxDatapv)
1805 RTMemFree(g_vboxDatapv);
1806 g_vboxDataRC = retval;
1807 g_vboxDatapv = pv;
1808 g_vboxDatacb = cb;
1809 ClipAnnounceFormatToX11(pCtx,
1810 VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT);
1811 return VINF_SUCCESS;
1812}
1813
1814/* Return the data in the simulated VBox clipboard. */
1815int ClipRequestDataForX11(VBOXCLIPBOARDCONTEXT *pCtx,
1816 uint32_t u32Format, void **ppv,
1817 uint32_t *pcb)
1818{
1819 *pcb = g_vboxDatacb;
1820 if (g_vboxDatapv != NULL)
1821 {
1822 void *pv = RTMemDup(g_vboxDatapv, g_vboxDatacb);
1823 *ppv = pv;
1824 return pv != NULL ? g_vboxDataRC : VERR_NO_MEMORY;
1825 }
1826 *ppv = NULL;
1827 return g_vboxDataRC;
1828}
1829
1830Display *XtDisplay(Widget w)
1831{ return (Display *) 0xffff; }
1832
1833int XmbTextListToTextProperty(Display *display, char **list, int count,
1834 XICCEncodingStyle style,
1835 XTextProperty *text_prop_return)
1836{
1837 /* We don't fully reimplement this API for obvious reasons. */
1838 AssertReturn(count == 1, XLocaleNotSupported);
1839 AssertReturn(style == XCompoundTextStyle, XLocaleNotSupported);
1840 /* We simplify the conversion by only accepting ASCII. */
1841 for (unsigned i = 0; (*list)[i] != 0; ++i)
1842 AssertReturn(((*list)[i] & 0x80) == 0, XLocaleNotSupported);
1843 text_prop_return->value =
1844 (unsigned char*)RTMemDup(*list, strlen(*list) + 1);
1845 text_prop_return->encoding = clipGetAtom(NULL, "COMPOUND_TEXT");
1846 text_prop_return->format = 8;
1847 text_prop_return->nitems = strlen(*list);
1848 return 0;
1849}
1850
1851int Xutf8TextListToTextProperty(Display *display, char **list, int count,
1852 XICCEncodingStyle style,
1853 XTextProperty *text_prop_return)
1854{
1855 return XmbTextListToTextProperty(display, list, count, style,
1856 text_prop_return);
1857}
1858
1859int XmbTextPropertyToTextList(Display *display,
1860 const XTextProperty *text_prop,
1861 char ***list_return, int *count_return)
1862{
1863 int rc = 0;
1864 if (text_prop->nitems == 0)
1865 {
1866 *list_return = NULL;
1867 *count_return = 0;
1868 return 0;
1869 }
1870 /* Only accept simple ASCII properties */
1871 for (unsigned i = 0; i < text_prop->nitems; ++i)
1872 AssertReturn(!(text_prop->value[i] & 0x80), XConverterNotFound);
1873 char **ppList = (char **)RTMemAlloc(sizeof(char *));
1874 char *pValue = (char *)RTMemAlloc(text_prop->nitems + 1);
1875 if (pValue)
1876 {
1877 memcpy(pValue, text_prop->value, text_prop->nitems);
1878 pValue[text_prop->nitems] = 0;
1879 }
1880 if (ppList)
1881 *ppList = pValue;
1882 if (!ppList || !pValue)
1883 {
1884 RTMemFree(ppList);
1885 RTMemFree(pValue);
1886 rc = XNoMemory;
1887 }
1888 else
1889 {
1890 /* NULL-terminate the string */
1891 pValue[text_prop->nitems] = '\0';
1892 *count_return = 1;
1893 *list_return = ppList;
1894 }
1895 return rc;
1896}
1897
1898int Xutf8TextPropertyToTextList(Display *display,
1899 const XTextProperty *text_prop,
1900 char ***list_return, int *count_return)
1901{
1902 return XmbTextPropertyToTextList(display, text_prop, list_return,
1903 count_return);
1904}
1905
1906void XtAppSetExitFlag(XtAppContext app_context) {}
1907
1908void XtDestroyWidget(Widget w) {}
1909
1910XtAppContext XtCreateApplicationContext(void) { return (XtAppContext)0xffff; }
1911
1912void XtDestroyApplicationContext(XtAppContext app_context) {}
1913
1914void XtToolkitInitialize(void) {}
1915
1916Boolean XtToolkitThreadInitialize(void) { return True; }
1917
1918Display *XtOpenDisplay(XtAppContext app_context,
1919 _Xconst _XtString display_string,
1920 _Xconst _XtString application_name,
1921 _Xconst _XtString application_class,
1922 XrmOptionDescRec *options, Cardinal num_options,
1923 int *argc, char **argv)
1924{ return (Display *)0xffff; }
1925
1926Widget XtVaAppCreateShell(_Xconst _XtString application_name,
1927 _Xconst _XtString application_class,
1928 WidgetClass widget_class, Display *display, ...)
1929{ return TEST_WIDGET; }
1930
1931void XtSetMappedWhenManaged(Widget widget, _XtBoolean mapped_when_managed) {}
1932
1933void XtRealizeWidget(Widget widget) {}
1934
1935XtInputId XtAppAddInput(XtAppContext app_context, int source,
1936 XtPointer condition, XtInputCallbackProc proc,
1937 XtPointer closure)
1938{ return 0xffff; }
1939
1940/* Atoms we need other than the formats we support. */
1941static const char *g_apszSupAtoms[] =
1942{
1943 "PRIMARY", "CLIPBOARD", "TARGETS", "MULTIPLE", "TIMESTAMP"
1944};
1945
1946/* This just looks for the atom names in a couple of tables and returns an
1947 * index with an offset added. */
1948Boolean XtConvertAndStore(Widget widget, _Xconst _XtString from_type,
1949 XrmValue* from, _Xconst _XtString to_type,
1950 XrmValue* to_in_out)
1951{
1952 Boolean rc = False;
1953 /* What we support is: */
1954 AssertReturn(from_type == XtRString, False);
1955 AssertReturn(to_type == XtRAtom, False);
1956 for (unsigned i = 0; i < RT_ELEMENTS(g_aFormats); ++i)
1957 if (!strcmp(from->addr, g_aFormats[i].pcszAtom))
1958 {
1959 *(Atom *)(to_in_out->addr) = (Atom) (i + 0x1000);
1960 rc = True;
1961 }
1962 for (unsigned i = 0; i < RT_ELEMENTS(g_apszSupAtoms); ++i)
1963 if (!strcmp(from->addr, g_apszSupAtoms[i]))
1964 {
1965 *(Atom *)(to_in_out->addr) = (Atom) (i + 0x2000);
1966 rc = True;
1967 }
1968 Assert(rc == True); /* Have we missed any atoms? */
1969 return rc;
1970}
1971
1972/* The current values of the X selection, which will be returned to the
1973 * XtGetSelectionValue callback. */
1974static Atom g_selTarget[1] = { 0 };
1975static Atom g_selType = 0;
1976static const void *g_pSelData = NULL;
1977static unsigned long g_cSelData = 0;
1978static int g_selFormat = 0;
1979static bool g_fTargetsTimeout = false;
1980static bool g_fTargetsFailure = false;
1981
1982void XtGetSelectionValue(Widget widget, Atom selection, Atom target,
1983 XtSelectionCallbackProc callback,
1984 XtPointer closure, Time time)
1985{
1986 unsigned long count = 0;
1987 int format = 0;
1988 Atom type = XA_STRING;
1989 if ( ( selection != clipGetAtom(NULL, "PRIMARY")
1990 && selection != clipGetAtom(NULL, "CLIPBOARD")
1991 && selection != clipGetAtom(NULL, "TARGETS"))
1992 || ( target != g_selTarget[0]
1993 && target != clipGetAtom(NULL, "TARGETS")))
1994 {
1995 /* Otherwise this is probably a caller error. */
1996 Assert(target != g_selTarget[0]);
1997 callback(widget, closure, &selection, &type, NULL, &count, &format);
1998 /* Could not convert to target. */
1999 return;
2000 }
2001 XtPointer pValue = NULL;
2002 if (target == clipGetAtom(NULL, "TARGETS"))
2003 {
2004 if (g_fTargetsFailure)
2005 pValue = NULL;
2006 else
2007 pValue = (XtPointer) RTMemDup(&g_selTarget, sizeof(g_selTarget));
2008 type = g_fTargetsTimeout ? XT_CONVERT_FAIL : XA_ATOM;
2009 count = g_fTargetsFailure ? 0 : RT_ELEMENTS(g_selTarget);
2010 format = 32;
2011 }
2012 else
2013 {
2014 pValue = (XtPointer) g_pSelData ? RTMemDup(g_pSelData, g_cSelData)
2015 : NULL;
2016 type = g_selType;
2017 count = g_pSelData ? g_cSelData : 0;
2018 format = g_selFormat;
2019 }
2020 if (!pValue)
2021 {
2022 count = 0;
2023 format = 0;
2024 }
2025 callback(widget, closure, &selection, &type, pValue,
2026 &count, &format);
2027}
2028
2029/* The formats currently on offer from X11 via the shared clipboard */
2030static uint32_t g_fX11Formats = 0;
2031
2032void ClipReportX11Formats(VBOXCLIPBOARDCONTEXT* pCtx,
2033 uint32_t u32Formats)
2034{
2035 g_fX11Formats = u32Formats;
2036}
2037
2038static uint32_t clipQueryFormats()
2039{
2040 return g_fX11Formats;
2041}
2042
2043static void clipInvalidateFormats()
2044{
2045 g_fX11Formats = ~0;
2046}
2047
2048/* Does our clipboard code currently own the selection? */
2049static bool g_ownsSel = false;
2050/* The procedure that is called when we should convert the selection to a
2051 * given format. */
2052static XtConvertSelectionProc g_pfnSelConvert = NULL;
2053/* The procedure which is called when we lose the selection. */
2054static XtLoseSelectionProc g_pfnSelLose = NULL;
2055/* The procedure which is called when the selection transfer has completed. */
2056static XtSelectionDoneProc g_pfnSelDone = NULL;
2057
2058Boolean XtOwnSelection(Widget widget, Atom selection, Time time,
2059 XtConvertSelectionProc convert,
2060 XtLoseSelectionProc lose,
2061 XtSelectionDoneProc done)
2062{
2063 if (selection != clipGetAtom(NULL, "CLIPBOARD"))
2064 return True; /* We don't really care about this. */
2065 g_ownsSel = true; /* Always succeed. */
2066 g_pfnSelConvert = convert;
2067 g_pfnSelLose = lose;
2068 g_pfnSelDone = done;
2069 return True;
2070}
2071
2072void XtDisownSelection(Widget widget, Atom selection, Time time)
2073{
2074 g_ownsSel = false;
2075 g_pfnSelConvert = NULL;
2076 g_pfnSelLose = NULL;
2077 g_pfnSelDone = NULL;
2078}
2079
2080/* Request the shared clipboard to convert its data to a given format. */
2081static bool clipConvertSelection(const char *pcszTarget, Atom *type,
2082 XtPointer *value, unsigned long *length,
2083 int *format)
2084{
2085 Atom target = clipGetAtom(NULL, pcszTarget);
2086 if (target == 0)
2087 return false;
2088 /* Initialise all return values in case we make a quick exit. */
2089 *type = XA_STRING;
2090 *value = NULL;
2091 *length = 0;
2092 *format = 0;
2093 if (!g_ownsSel)
2094 return false;
2095 if (!g_pfnSelConvert)
2096 return false;
2097 Atom clipAtom = clipGetAtom(NULL, "CLIPBOARD");
2098 if (!g_pfnSelConvert(TEST_WIDGET, &clipAtom, &target, type,
2099 value, length, format))
2100 return false;
2101 if (g_pfnSelDone)
2102 g_pfnSelDone(TEST_WIDGET, &clipAtom, &target);
2103 return true;
2104}
2105
2106/* Set the current X selection data */
2107static void clipSetSelectionValues(const char *pcszTarget, Atom type,
2108 const void *data,
2109 unsigned long count, int format)
2110{
2111 Atom clipAtom = clipGetAtom(NULL, "CLIPBOARD");
2112 g_selTarget[0] = clipGetAtom(NULL, pcszTarget);
2113 g_selType = type;
2114 g_pSelData = data;
2115 g_cSelData = count;
2116 g_selFormat = format;
2117 if (g_pfnSelLose)
2118 g_pfnSelLose(TEST_WIDGET, &clipAtom);
2119 g_ownsSel = false;
2120 g_fTargetsTimeout = false;
2121 g_fTargetsFailure = false;
2122}
2123
2124static void clipSendTargetUpdate(CLIPBACKEND *pCtx)
2125{
2126 clipUpdateX11Targets(pCtx, g_selTarget, RT_ELEMENTS(g_selTarget));
2127}
2128
2129/* Configure if and how the X11 TARGETS clipboard target will fail */
2130static void clipSetTargetsFailure(bool fTimeout, bool fFailure)
2131{
2132 g_fTargetsTimeout = fTimeout;
2133 g_fTargetsFailure = fFailure;
2134}
2135
2136char *XtMalloc(Cardinal size) { return (char *) RTMemAlloc(size); }
2137
2138char *XGetAtomName(Display *display, Atom atom)
2139{
2140 AssertReturn((unsigned)atom < RT_ELEMENTS(g_aFormats) + 1, NULL);
2141 const char *pcszName = NULL;
2142 if (atom < 0x1000)
2143 return NULL;
2144 else if (0x1000 <= atom && atom < 0x2000)
2145 {
2146 unsigned index = atom - 0x1000;
2147 AssertReturn(index < RT_ELEMENTS(g_aFormats), NULL);
2148 pcszName = g_aFormats[index].pcszAtom;
2149 }
2150 else
2151 {
2152 unsigned index = atom - 0x2000;
2153 AssertReturn(index < RT_ELEMENTS(g_apszSupAtoms), NULL);
2154 pcszName = g_apszSupAtoms[index];
2155 }
2156 return (char *)RTMemDup(pcszName, sizeof(pcszName) + 1);
2157}
2158
2159int XFree(void *data)
2160{
2161 RTMemFree(data);
2162 return 0;
2163}
2164
2165void XFreeStringList(char **list)
2166{
2167 if (list)
2168 RTMemFree(*list);
2169 RTMemFree(list);
2170}
2171
2172#define MAX_BUF_SIZE 256
2173
2174static int g_completedRC = VINF_SUCCESS;
2175static int g_completedCB = 0;
2176static CLIPREADCBREQ *g_completedReq = NULL;
2177static char g_completedBuf[MAX_BUF_SIZE];
2178
2179void ClipCompleteDataRequestFromX11(VBOXCLIPBOARDCONTEXT *pCtx, int rc,
2180 CLIPREADCBREQ *pReq, void *pv,
2181 uint32_t cb)
2182{
2183 if (cb <= MAX_BUF_SIZE)
2184 {
2185 g_completedRC = rc;
2186 memcpy(g_completedBuf, pv, cb);
2187 }
2188 else
2189 g_completedRC = VERR_BUFFER_OVERFLOW;
2190 g_completedCB = cb;
2191 g_completedReq = pReq;
2192}
2193
2194static void clipGetCompletedRequest(int *prc, char ** ppc, uint32_t *pcb,
2195 CLIPREADCBREQ **ppReq)
2196{
2197 *prc = g_completedRC;
2198 *ppc = g_completedBuf;
2199 *pcb = g_completedCB;
2200 *ppReq = g_completedReq;
2201}
2202#ifdef RT_OS_SOLARIS_10
2203char XtStrings [] = "";
2204_WidgetClassRec* applicationShellWidgetClass;
2205char XtShellStrings [] = "";
2206int XmbTextPropertyToTextList(
2207 Display* /* display */,
2208 XTextProperty* /* text_prop */,
2209 char*** /* list_return */,
2210 int* /* count_return */
2211)
2212{
2213 return 0;
2214}
2215#else
2216const char XtStrings [] = "";
2217_WidgetClassRec* applicationShellWidgetClass;
2218const char XtShellStrings [] = "";
2219#endif
2220
2221static void testStringFromX11(RTTEST hTest, CLIPBACKEND *pCtx,
2222 const char *pcszExp, int rcExp)
2223{
2224 bool retval = true;
2225 clipSendTargetUpdate(pCtx);
2226 if (clipQueryFormats() != VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT)
2227 RTTestFailed(hTest, "Wrong targets reported: %02X\n",
2228 clipQueryFormats());
2229 else
2230 {
2231 char *pc;
2232 CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)&pReq, *pReqRet = NULL;
2233 ClipRequestDataFromX11(pCtx, VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT,
2234 pReq);
2235 int rc = VINF_SUCCESS;
2236 uint32_t cbActual = 0;
2237 clipGetCompletedRequest(&rc, &pc, &cbActual, &pReqRet);
2238 if (rc != rcExp)
2239 RTTestFailed(hTest, "Wrong return code, expected %Rrc, got %Rrc\n",
2240 rcExp, rc);
2241 else if (pReqRet != pReq)
2242 RTTestFailed(hTest, "Wrong returned request data, expected %p, got %p\n",
2243 pReq, pReqRet);
2244 else if (RT_FAILURE(rcExp))
2245 retval = true;
2246 else
2247 {
2248 RTUTF16 wcExp[MAX_BUF_SIZE / 2];
2249 RTUTF16 *pwcExp = wcExp;
2250 size_t cwc = 0;
2251 rc = RTStrToUtf16Ex(pcszExp, RTSTR_MAX, &pwcExp,
2252 RT_ELEMENTS(wcExp), &cwc);
2253 size_t cbExp = cwc * 2 + 2;
2254 AssertRC(rc);
2255 if (RT_SUCCESS(rc))
2256 {
2257 if (cbActual != cbExp)
2258 {
2259 RTTestFailed(hTest, "Returned string is the wrong size, string \"%.*ls\", size %u, expected \"%s\", size %u\n",
2260 RT_MIN(MAX_BUF_SIZE, cbActual), pc, cbActual,
2261 pcszExp, cbExp);
2262 }
2263 else
2264 {
2265 if (memcmp(pc, wcExp, cbExp) == 0)
2266 retval = true;
2267 else
2268 RTTestFailed(hTest, "Returned string \"%.*ls\" does not match expected string \"%s\"\n",
2269 MAX_BUF_SIZE, pc, pcszExp);
2270 }
2271 }
2272 }
2273 }
2274 if (!retval)
2275 RTTestFailureDetails(hTest, "Expected: string \"%s\", rc %Rrc\n",
2276 pcszExp, rcExp);
2277}
2278
2279static void testLatin1FromX11(RTTEST hTest, CLIPBACKEND *pCtx,
2280 const char *pcszExp, int rcExp)
2281{
2282 bool retval = false;
2283 clipSendTargetUpdate(pCtx);
2284 if (clipQueryFormats() != VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT)
2285 RTTestFailed(hTest, "Wrong targets reported: %02X\n",
2286 clipQueryFormats());
2287 else
2288 {
2289 char *pc;
2290 CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)&pReq, *pReqRet = NULL;
2291 ClipRequestDataFromX11(pCtx, VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT,
2292 pReq);
2293 int rc = VINF_SUCCESS;
2294 uint32_t cbActual = 0;
2295 clipGetCompletedRequest(&rc, &pc, &cbActual, &pReqRet);
2296 if (rc != rcExp)
2297 RTTestFailed(hTest, "Wrong return code, expected %Rrc, got %Rrc\n",
2298 rcExp, rc);
2299 else if (pReqRet != pReq)
2300 RTTestFailed(hTest, "Wrong returned request data, expected %p, got %p\n",
2301 pReq, pReqRet);
2302 else if (RT_FAILURE(rcExp))
2303 retval = true;
2304 else
2305 {
2306 RTUTF16 wcExp[MAX_BUF_SIZE / 2];
2307 RTUTF16 *pwcExp = wcExp;
2308 size_t cwc;
2309 for (cwc = 0; cwc == 0 || pcszExp[cwc - 1] != '\0'; ++cwc)
2310 wcExp[cwc] = pcszExp[cwc];
2311 size_t cbExp = cwc * 2;
2312 if (cbActual != cbExp)
2313 {
2314 RTTestFailed(hTest, "Returned string is the wrong size, string \"%.*ls\", size %u, expected \"%s\", size %u\n",
2315 RT_MIN(MAX_BUF_SIZE, cbActual), pc, cbActual,
2316 pcszExp, cbExp);
2317 }
2318 else
2319 {
2320 if (memcmp(pc, wcExp, cbExp) == 0)
2321 retval = true;
2322 else
2323 RTTestFailed(hTest, "Returned string \"%.*ls\" does not match expected string \"%s\"\n",
2324 MAX_BUF_SIZE, pc, pcszExp);
2325 }
2326 }
2327 }
2328 if (!retval)
2329 RTTestFailureDetails(hTest, "Expected: string \"%s\", rc %Rrc\n",
2330 pcszExp, rcExp);
2331}
2332
2333static void testStringFromVBox(RTTEST hTest, CLIPBACKEND *pCtx,
2334 const char *pcszTarget, Atom typeExp,
2335 const char *valueExp)
2336{
2337 bool retval = false;
2338 Atom type;
2339 XtPointer value = NULL;
2340 unsigned long length;
2341 int format;
2342 size_t lenExp = strlen(valueExp);
2343 if (clipConvertSelection(pcszTarget, &type, &value, &length, &format))
2344 {
2345 if ( type != typeExp
2346 || length != lenExp
2347 || format != 8
2348 || memcmp((const void *) value, (const void *)valueExp,
2349 lenExp))
2350 {
2351 RTTestFailed(hTest, "Bad data: type %d, (expected %d), length %u, (%u), format %d (%d), value \"%.*s\" (\"%.*s\")\n",
2352 type, typeExp, length, lenExp, format, 8,
2353 RT_MIN(length, 20), value, RT_MIN(lenExp, 20), valueExp);
2354 }
2355 else
2356 retval = true;
2357 }
2358 else
2359 RTTestFailed(hTest, "Conversion failed\n");
2360 XtFree((char *)value);
2361 if (!retval)
2362 RTTestFailureDetails(hTest, "Conversion to %s, expected \"%s\"\n",
2363 pcszTarget, valueExp);
2364}
2365
2366static void testNoX11(CLIPBACKEND *pCtx, const char *pcszTestCtx)
2367{
2368 CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)&pReq, *pReqRet = NULL;
2369 int rc = ClipRequestDataFromX11(pCtx,
2370 VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT,
2371 pReq);
2372 RTTESTI_CHECK_MSG(rc == VERR_NO_DATA, ("context: %s\n", pcszTestCtx));
2373}
2374
2375static void testStringFromVBoxFailed(RTTEST hTest, CLIPBACKEND *pCtx,
2376 const char *pcszTarget)
2377{
2378 bool retval = false;
2379 Atom type;
2380 XtPointer value = NULL;
2381 unsigned long length;
2382 int format;
2383 RTTEST_CHECK_MSG(hTest, !clipConvertSelection(pcszTarget, &type, &value,
2384 &length, &format),
2385 (hTest, "Conversion to target %s, should have failed but didn't, returned type %d, length %u, format %d, value \"%.*s\"\n",
2386 pcszTarget, type, length, format, RT_MIN(length, 20),
2387 value));
2388 XtFree((char *)value);
2389}
2390
2391static void testNoSelectionOwnership(CLIPBACKEND *pCtx,
2392 const char *pcszTestCtx)
2393{
2394 RTTESTI_CHECK_MSG(!g_ownsSel, ("context: %s\n", pcszTestCtx));
2395}
2396
2397int main()
2398{
2399 /*
2400 * Init the runtime, test and say hello.
2401 */
2402 RTTEST hTest;
2403 int rc = RTTestInitAndCreate("tstClipboardX11", &hTest);
2404 if (rc)
2405 return rc;
2406 RTTestBanner(hTest);
2407
2408 /*
2409 * Run the test.
2410 */
2411 CLIPBACKEND *pCtx = ClipConstructX11(NULL, false);
2412 char *pc;
2413 uint32_t cbActual;
2414 CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)&pReq, *pReqRet = NULL;
2415 rc = ClipStartX11(pCtx);
2416 AssertRCReturn(rc, 1);
2417
2418 /*** Utf-8 from X11 ***/
2419 RTTestSub(hTest, "reading Utf-8 from X11");
2420 /* Simple test */
2421 clipSetSelectionValues("UTF8_STRING", XA_STRING, "hello world",
2422 sizeof("hello world"), 8);
2423 testStringFromX11(hTest, pCtx, "hello world", VINF_SUCCESS);
2424 /* With an embedded carriage return */
2425 clipSetSelectionValues("text/plain;charset=UTF-8", XA_STRING,
2426 "hello\nworld", sizeof("hello\nworld"), 8);
2427 testStringFromX11(hTest, pCtx, "hello\r\nworld", VINF_SUCCESS);
2428 /* With an embedded CRLF */
2429 clipSetSelectionValues("text/plain;charset=UTF-8", XA_STRING,
2430 "hello\r\nworld", sizeof("hello\r\nworld"), 8);
2431 testStringFromX11(hTest, pCtx, "hello\r\r\nworld", VINF_SUCCESS);
2432 /* With an embedded LFCR */
2433 clipSetSelectionValues("text/plain;charset=UTF-8", XA_STRING,
2434 "hello\n\rworld", sizeof("hello\n\rworld"), 8);
2435 testStringFromX11(hTest, pCtx, "hello\r\n\rworld", VINF_SUCCESS);
2436 /* An empty string */
2437 clipSetSelectionValues("text/plain;charset=utf-8", XA_STRING, "",
2438 sizeof(""), 8);
2439 testStringFromX11(hTest, pCtx, "", VINF_SUCCESS);
2440 /* With an embedded Utf-8 character. */
2441 clipSetSelectionValues("STRING", XA_STRING,
2442 "100\xE2\x82\xAC" /* 100 Euro */,
2443 sizeof("100\xE2\x82\xAC"), 8);
2444 testStringFromX11(hTest, pCtx, "100\xE2\x82\xAC", VINF_SUCCESS);
2445 /* A non-zero-terminated string */
2446 clipSetSelectionValues("TEXT", XA_STRING,
2447 "hello world", sizeof("hello world") - 1, 8);
2448 testStringFromX11(hTest, pCtx, "hello world", VINF_SUCCESS);
2449
2450 /*** COMPOUND TEXT from X11 ***/
2451 RTTestSub(hTest, "reading compound text from X11");
2452 /* Simple test */
2453 clipSetSelectionValues("COMPOUND_TEXT", XA_STRING, "hello world",
2454 sizeof("hello world"), 8);
2455 testStringFromX11(hTest, pCtx, "hello world", VINF_SUCCESS);
2456 /* With an embedded carriage return */
2457 clipSetSelectionValues("COMPOUND_TEXT", XA_STRING, "hello\nworld",
2458 sizeof("hello\nworld"), 8);
2459 testStringFromX11(hTest, pCtx, "hello\r\nworld", VINF_SUCCESS);
2460 /* With an embedded CRLF */
2461 clipSetSelectionValues("COMPOUND_TEXT", XA_STRING, "hello\r\nworld",
2462 sizeof("hello\r\nworld"), 8);
2463 testStringFromX11(hTest, pCtx, "hello\r\r\nworld", VINF_SUCCESS);
2464 /* With an embedded LFCR */
2465 clipSetSelectionValues("COMPOUND_TEXT", XA_STRING, "hello\n\rworld",
2466 sizeof("hello\n\rworld"), 8);
2467 testStringFromX11(hTest, pCtx, "hello\r\n\rworld", VINF_SUCCESS);
2468 /* An empty string */
2469 clipSetSelectionValues("COMPOUND_TEXT", XA_STRING, "",
2470 sizeof(""), 8);
2471 testStringFromX11(hTest, pCtx, "", VINF_SUCCESS);
2472 /* A non-zero-terminated string */
2473 clipSetSelectionValues("COMPOUND_TEXT", XA_STRING,
2474 "hello world", sizeof("hello world") - 1, 8);
2475 testStringFromX11(hTest, pCtx, "hello world", VINF_SUCCESS);
2476
2477 /*** Latin1 from X11 ***/
2478 RTTestSub(hTest, "reading Latin1 from X11");
2479 /* Simple test */
2480 clipSetSelectionValues("STRING", XA_STRING, "Georges Dupr\xEA",
2481 sizeof("Georges Dupr\xEA"), 8);
2482 testLatin1FromX11(hTest, pCtx, "Georges Dupr\xEA", VINF_SUCCESS);
2483 /* With an embedded carriage return */
2484 clipSetSelectionValues("TEXT", XA_STRING, "Georges\nDupr\xEA",
2485 sizeof("Georges\nDupr\xEA"), 8);
2486 testLatin1FromX11(hTest, pCtx, "Georges\r\nDupr\xEA", VINF_SUCCESS);
2487 /* With an embedded CRLF */
2488 clipSetSelectionValues("TEXT", XA_STRING, "Georges\r\nDupr\xEA",
2489 sizeof("Georges\r\nDupr\xEA"), 8);
2490 testLatin1FromX11(hTest, pCtx, "Georges\r\r\nDupr\xEA", VINF_SUCCESS);
2491 /* With an embedded LFCR */
2492 clipSetSelectionValues("TEXT", XA_STRING, "Georges\n\rDupr\xEA",
2493 sizeof("Georges\n\rDupr\xEA"), 8);
2494 testLatin1FromX11(hTest, pCtx, "Georges\r\n\rDupr\xEA", VINF_SUCCESS);
2495 /* A non-zero-terminated string */
2496 clipSetSelectionValues("text/plain", XA_STRING,
2497 "Georges Dupr\xEA!",
2498 sizeof("Georges Dupr\xEA!") - 1, 8);
2499 testLatin1FromX11(hTest, pCtx, "Georges Dupr\xEA!", VINF_SUCCESS);
2500
2501 /*** Unknown X11 format ***/
2502 RTTestSub(hTest, "handling of an unknown X11 format");
2503 clipInvalidateFormats();
2504 clipSetSelectionValues("CLIPBOARD", XA_STRING, "Test",
2505 sizeof("Test"), 8);
2506 clipSendTargetUpdate(pCtx);
2507 RTTEST_CHECK_MSG(hTest, clipQueryFormats() == 0,
2508 (hTest, "Failed to send a format update notification\n"));
2509
2510 /*** Timeout from X11 ***/
2511 RTTestSub(hTest, "X11 timeout");
2512 clipSetSelectionValues("UTF8_STRING", XT_CONVERT_FAIL, "hello world",
2513 sizeof("hello world"), 8);
2514 testStringFromX11(hTest, pCtx, "hello world", VERR_TIMEOUT);
2515
2516 /*** No data in X11 clipboard ***/
2517 RTTestSub(hTest, "a data request from an empty X11 clipboard");
2518 clipSetSelectionValues("UTF8_STRING", XA_STRING, NULL,
2519 0, 8);
2520 ClipRequestDataFromX11(pCtx, VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT,
2521 pReq);
2522 clipGetCompletedRequest(&rc, &pc, &cbActual, &pReqRet);
2523 RTTEST_CHECK_MSG(hTest, rc == VERR_NO_DATA,
2524 (hTest, "Returned %Rrc instead of VERR_NO_DATA\n",
2525 rc));
2526 RTTEST_CHECK_MSG(hTest, pReqRet == pReq,
2527 (hTest, "Wrong returned request data, expected %p, got %p\n",
2528 pReq, pReqRet));
2529
2530 /*** Ensure that VBox is notified when we return the CB to X11 ***/
2531 RTTestSub(hTest, "notification of switch to X11 clipboard");
2532 clipInvalidateFormats();
2533 clipReportEmptyX11CB(pCtx);
2534 RTTEST_CHECK_MSG(hTest, clipQueryFormats() == 0,
2535 (hTest, "Failed to send a format update (release) notification\n"));
2536
2537 /*** request for an invalid VBox format from X11 ***/
2538 RTTestSub(hTest, "a request for an invalid VBox format from X11");
2539 ClipRequestDataFromX11(pCtx, 0xffff, pReq);
2540 clipGetCompletedRequest(&rc, &pc, &cbActual, &pReqRet);
2541 RTTEST_CHECK_MSG(hTest, rc == VERR_NOT_IMPLEMENTED,
2542 (hTest, "Returned %Rrc instead of VERR_NOT_IMPLEMENTED\n",
2543 rc));
2544 RTTEST_CHECK_MSG(hTest, pReqRet == pReq,
2545 (hTest, "Wrong returned request data, expected %p, got %p\n",
2546 pReq, pReqRet));
2547
2548 /*** Targets failure from X11 ***/
2549 RTTestSub(hTest, "X11 targets conversion failure");
2550 clipSetSelectionValues("UTF8_STRING", XA_STRING, "hello world",
2551 sizeof("hello world"), 8);
2552 clipSetTargetsFailure(false, true);
2553 Atom atom = XA_STRING;
2554 long unsigned int cLen = 0;
2555 int format = 8;
2556 clipConvertX11Targets(NULL, (XtPointer) pCtx, NULL, &atom, NULL, &cLen,
2557 &format);
2558 RTTEST_CHECK_MSG(hTest, clipQueryFormats() == 0,
2559 (hTest, "Wrong targets reported: %02X\n",
2560 clipQueryFormats()));
2561
2562 /*** X11 text format conversion ***/
2563 RTTestSub(hTest, "handling of X11 selection targets");
2564 RTTEST_CHECK_MSG(hTest, clipTestTextFormatConversion(pCtx),
2565 (hTest, "failed to select the right X11 text formats\n"));
2566
2567 /*** Utf-8 from VBox ***/
2568 RTTestSub(hTest, "reading Utf-8 from VBox");
2569 /* Simple test */
2570 clipSetVBoxUtf16(pCtx, VINF_SUCCESS, "hello world",
2571 sizeof("hello world") * 2);
2572 testStringFromVBox(hTest, pCtx, "UTF8_STRING",
2573 clipGetAtom(NULL, "UTF8_STRING"), "hello world");
2574 /* With an embedded carriage return */
2575 clipSetVBoxUtf16(pCtx, VINF_SUCCESS, "hello\r\nworld",
2576 sizeof("hello\r\nworld") * 2);
2577 testStringFromVBox(hTest, pCtx, "text/plain;charset=UTF-8",
2578 clipGetAtom(NULL, "text/plain;charset=UTF-8"),
2579 "hello\nworld");
2580 /* With an embedded CRCRLF */
2581 clipSetVBoxUtf16(pCtx, VINF_SUCCESS, "hello\r\r\nworld",
2582 sizeof("hello\r\r\nworld") * 2);
2583 testStringFromVBox(hTest, pCtx, "text/plain;charset=UTF-8",
2584 clipGetAtom(NULL, "text/plain;charset=UTF-8"),
2585 "hello\r\nworld");
2586 /* With an embedded CRLFCR */
2587 clipSetVBoxUtf16(pCtx, VINF_SUCCESS, "hello\r\n\rworld",
2588 sizeof("hello\r\n\rworld") * 2);
2589 testStringFromVBox(hTest, pCtx, "text/plain;charset=UTF-8",
2590 clipGetAtom(NULL, "text/plain;charset=UTF-8"),
2591 "hello\n\rworld");
2592 /* An empty string */
2593 clipSetVBoxUtf16(pCtx, VINF_SUCCESS, "", 2);
2594 testStringFromVBox(hTest, pCtx, "text/plain;charset=utf-8",
2595 clipGetAtom(NULL, "text/plain;charset=utf-8"), "");
2596 /* With an embedded Utf-8 character. */
2597 clipSetVBoxUtf16(pCtx, VINF_SUCCESS, "100\xE2\x82\xAC" /* 100 Euro */,
2598 10);
2599 testStringFromVBox(hTest, pCtx, "STRING",
2600 clipGetAtom(NULL, "STRING"), "100\xE2\x82\xAC");
2601 /* A non-zero-terminated string */
2602 clipSetVBoxUtf16(pCtx, VINF_SUCCESS, "hello world",
2603 sizeof("hello world") * 2 - 2);
2604 testStringFromVBox(hTest, pCtx, "TEXT", clipGetAtom(NULL, "TEXT"),
2605 "hello world");
2606
2607 /*** COMPOUND TEXT from VBox ***/
2608 RTTestSub(hTest, "reading COMPOUND TEXT from VBox");
2609 /* Simple test */
2610 clipSetVBoxUtf16(pCtx, VINF_SUCCESS, "hello world",
2611 sizeof("hello world") * 2);
2612 testStringFromVBox(hTest, pCtx, "COMPOUND_TEXT",
2613 clipGetAtom(NULL, "COMPOUND_TEXT"), "hello world");
2614 /* With an embedded carriage return */
2615 clipSetVBoxUtf16(pCtx, VINF_SUCCESS, "hello\r\nworld",
2616 sizeof("hello\r\nworld") * 2);
2617 testStringFromVBox(hTest, pCtx, "COMPOUND_TEXT",
2618 clipGetAtom(NULL, "COMPOUND_TEXT"), "hello\nworld");
2619 /* With an embedded CRCRLF */
2620 clipSetVBoxUtf16(pCtx, VINF_SUCCESS, "hello\r\r\nworld",
2621 sizeof("hello\r\r\nworld") * 2);
2622 testStringFromVBox(hTest, pCtx, "COMPOUND_TEXT",
2623 clipGetAtom(NULL, "COMPOUND_TEXT"), "hello\r\nworld");
2624 /* With an embedded CRLFCR */
2625 clipSetVBoxUtf16(pCtx, VINF_SUCCESS, "hello\r\n\rworld",
2626 sizeof("hello\r\n\rworld") * 2);
2627 testStringFromVBox(hTest, pCtx, "COMPOUND_TEXT",
2628 clipGetAtom(NULL, "COMPOUND_TEXT"), "hello\n\rworld");
2629 /* An empty string */
2630 clipSetVBoxUtf16(pCtx, VINF_SUCCESS, "", 2);
2631 testStringFromVBox(hTest, pCtx, "COMPOUND_TEXT",
2632 clipGetAtom(NULL, "COMPOUND_TEXT"), "");
2633 /* A non-zero-terminated string */
2634 clipSetVBoxUtf16(pCtx, VINF_SUCCESS, "hello world",
2635 sizeof("hello world") * 2 - 2);
2636 testStringFromVBox(hTest, pCtx, "COMPOUND_TEXT",
2637 clipGetAtom(NULL, "COMPOUND_TEXT"), "hello world");
2638
2639 /*** Timeout from VBox ***/
2640 RTTestSub(hTest, "reading from VBox with timeout");
2641 clipEmptyVBox(pCtx, VERR_TIMEOUT);
2642 testStringFromVBoxFailed(hTest, pCtx, "UTF8_STRING");
2643
2644 /*** No data in VBox clipboard ***/
2645 RTTestSub(hTest, "an empty VBox clipboard");
2646 clipSetSelectionValues("TEXT", XA_STRING, "", sizeof(""), 8);
2647 clipEmptyVBox(pCtx, VINF_SUCCESS);
2648 RTTEST_CHECK_MSG(hTest, g_ownsSel,
2649 (hTest, "VBox grabbed the clipboard with no data and we ignored it\n"));
2650 testStringFromVBoxFailed(hTest, pCtx, "UTF8_STRING");
2651
2652 /*** An unknown VBox format ***/
2653 RTTestSub(hTest, "reading an unknown VBox format");
2654 clipSetSelectionValues("TEXT", XA_STRING, "", sizeof(""), 8);
2655 clipSetVBoxUtf16(pCtx, VINF_SUCCESS, "", 2);
2656 ClipAnnounceFormatToX11(pCtx, 0xa0000);
2657 RTTEST_CHECK_MSG(hTest, g_ownsSel,
2658 (hTest, "VBox grabbed the clipboard with unknown data and we ignored it\n"));
2659 testStringFromVBoxFailed(hTest, pCtx, "UTF8_STRING");
2660 rc = ClipStopX11(pCtx);
2661 AssertRCReturn(rc, 1);
2662 ClipDestructX11(pCtx);
2663
2664 /*** Headless clipboard tests ***/
2665
2666 pCtx = ClipConstructX11(NULL, true);
2667 rc = ClipStartX11(pCtx);
2668 AssertRCReturn(rc, 1);
2669
2670 /*** Read from X11 ***/
2671 RTTestSub(hTest, "reading from X11, headless clipboard");
2672 /* Simple test */
2673 clipSetVBoxUtf16(pCtx, VINF_SUCCESS, "",
2674 sizeof("") * 2);
2675 clipSetSelectionValues("UTF8_STRING", XA_STRING, "hello world",
2676 sizeof("hello world"), 8);
2677 testNoX11(pCtx, "reading from X11, headless clipboard");
2678
2679 /*** Read from VBox ***/
2680 RTTestSub(hTest, "reading from VBox, headless clipboard");
2681 /* Simple test */
2682 clipEmptyVBox(pCtx, VERR_WRONG_ORDER);
2683 clipSetSelectionValues("TEXT", XA_STRING, "", sizeof(""), 8);
2684 clipSetVBoxUtf16(pCtx, VINF_SUCCESS, "hello world",
2685 sizeof("hello world") * 2);
2686 testNoSelectionOwnership(pCtx, "reading from VBox, headless clipboard");
2687
2688 rc = ClipStopX11(pCtx);
2689 AssertRCReturn(rc, 1);
2690 ClipDestructX11(pCtx);
2691
2692 return RTTestSummaryAndDestroy(hTest);
2693}
2694
2695#endif
2696
2697#ifdef SMOKETEST
2698
2699/* This is a simple test case that just starts a copy of the X11 clipboard
2700 * backend, checks the X11 clipboard and exits. If ever needed I will add an
2701 * interactive mode in which the user can read and copy to the clipboard from
2702 * the command line. */
2703
2704#include <iprt/env.h>
2705#include <iprt/test.h>
2706
2707int ClipRequestDataForX11(VBOXCLIPBOARDCONTEXT *pCtx,
2708 uint32_t u32Format, void **ppv,
2709 uint32_t *pcb)
2710{
2711 return VERR_NO_DATA;
2712}
2713
2714void ClipReportX11Formats(VBOXCLIPBOARDCONTEXT *pCtx,
2715 uint32_t u32Formats)
2716{}
2717
2718void ClipCompleteDataRequestFromX11(VBOXCLIPBOARDCONTEXT *pCtx, int rc,
2719 CLIPREADCBREQ *pReq, void *pv,
2720 uint32_t cb)
2721{}
2722
2723int main()
2724{
2725 /*
2726 * Init the runtime, test and say hello.
2727 */
2728 RTTEST hTest;
2729 int rc = RTTestInitAndCreate("tstClipboardX11Smoke", &hTest);
2730 if (rc)
2731 return rc;
2732 RTTestBanner(hTest);
2733
2734 /*
2735 * Run the test.
2736 */
2737 rc = VINF_SUCCESS;
2738 /* We can't test anything without an X session, so just return success
2739 * in that case. */
2740 if (!RTEnvExist("DISPLAY"))
2741 {
2742 RTTestPrintf(hTest, RTTESTLVL_INFO,
2743 "X11 not available, not running test\n");
2744 return RTTestSummaryAndDestroy(hTest);
2745 }
2746 CLIPBACKEND *pCtx = ClipConstructX11(NULL, false);
2747 AssertReturn(pCtx, 1);
2748 rc = ClipStartX11(pCtx);
2749 AssertRCReturn(rc, 1);
2750 /* Give the clipboard time to synchronise. */
2751 RTThreadSleep(500);
2752 rc = ClipStopX11(pCtx);
2753 AssertRCReturn(rc, 1);
2754 ClipDestructX11(pCtx);
2755 return RTTestSummaryAndDestroy(hTest);
2756}
2757
2758#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