VirtualBox

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

Last change on this file since 38904 was 38904, checked in by vboxsync, 13 years ago

some small memory leaks in error cases and additional NULL checks

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