VirtualBox

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

Last change on this file since 21854 was 21651, checked in by vboxsync, 16 years ago

SharedClipboard/x11: switch logging from debug to user-enabled release logging so that users can trace clipboard issues

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