VirtualBox

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

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

GuestHost/SharedClipboard/X11: XInternAtom() does caching too, and is much simpler.

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