VirtualBox

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

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

HostServices/SharedClipboard: fixed handling of unknown formats on the other side in the X11 code

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