VirtualBox

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

Last change on this file since 20783 was 20551, checked in by vboxsync, 16 years ago

GuestHost/SharedClipboard: refactored the shared clipboard code to move knowledge of some host-specific buffering mechanisms into the host-specific code

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