VirtualBox

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

Last change on this file since 25856 was 24142, checked in by vboxsync, 15 years ago

GuestHost and HostServices/SharedClipboard/x11: fix a crash when clipboard initialisation fails

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