VirtualBox

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

Last change on this file since 36663 was 33995, checked in by vboxsync, 14 years ago

GuestHost/clipboard: typo

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