VirtualBox

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

Last change on this file since 81908 was 81843, checked in by vboxsync, 5 years ago

Shared Clipboard/Transfers: Update.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 108.5 KB
Line 
1/** @file
2 *
3 * Shared Clipboard:
4 * X11 backend code.
5 */
6
7/*
8 * Copyright (C) 2006-2019 Oracle Corporation
9 *
10 * This file is part of VirtualBox Open Source Edition (OSE), as
11 * available from http://www.virtualbox.org. This file is free software;
12 * you can redistribute it and/or modify it under the terms of the GNU
13 * General Public License (GPL) as published by the Free Software
14 * Foundation, in version 2 as it comes in the "COPYING" file of the
15 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
16 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
17 */
18
19/* Note: to automatically run regression tests on the shared clipboard,
20 * execute the tstClipboardX11 testcase. If you often make changes to the
21 * clipboard code, adding the line
22 * OTHERS += $(PATH_tstClipboardX11)/tstClipboardX11.run
23 * to LocalConfig.kmk will cause the tests to be run every time the code is
24 * changed. */
25
26
27/*********************************************************************************************************************************
28* Header Files *
29*********************************************************************************************************************************/
30#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD
31
32#include <errno.h>
33
34#include <dlfcn.h>
35#include <fcntl.h>
36#include <unistd.h>
37
38#ifdef RT_OS_SOLARIS
39#include <tsol/label.h>
40#endif
41
42#include <X11/Xlib.h>
43#include <X11/Xatom.h>
44#include <X11/Intrinsic.h>
45#include <X11/Shell.h>
46#include <X11/Xproto.h>
47#include <X11/StringDefs.h>
48
49#include <iprt/types.h>
50#include <iprt/mem.h>
51#include <iprt/semaphore.h>
52#include <iprt/thread.h>
53#include <iprt/utf16.h>
54#include <iprt/uri.h>
55
56#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
57# include <iprt/cpp/list.h>
58# include <iprt/cpp/ministring.h>
59#endif
60
61#include <VBox/log.h>
62#include <VBox/version.h>
63
64#include <VBox/GuestHost/SharedClipboard.h>
65#include <VBox/GuestHost/clipboard-helper.h>
66#include <VBox/HostServices/VBoxClipboardSvc.h>
67
68
69/*********************************************************************************************************************************
70* Defined Constants And Macros *
71*********************************************************************************************************************************/
72
73
74/*********************************************************************************************************************************
75* Structures and Typedefs *
76*********************************************************************************************************************************/
77/** The different clipboard formats which we support. */
78enum CLIPFORMAT
79{
80 INVALID = 0,
81 TARGETS,
82 TEXT, /* Treat this as UTF-8, but it may really be ascii */
83 UTF8,
84 BMP,
85 HTML
86#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
87 , URI_LIST
88#endif
89};
90
91typedef unsigned CLIPX11FORMAT;
92
93
94/*********************************************************************************************************************************
95* Internal Functions *
96*********************************************************************************************************************************/
97class formats;
98static Atom clipGetAtom(CLIPBACKEND *pCtx, const char *pszName);
99
100
101/*********************************************************************************************************************************
102* Global Variables *
103*********************************************************************************************************************************/
104
105/**
106 * The table maps X11 names to data formats
107 * and to the corresponding VBox clipboard formats.
108 */
109static struct _CLIPFORMATTABLE
110{
111 /** The X11 atom name of the format (several names can match one format)
112 */
113 const char *pcszAtom;
114 /** The format corresponding to the name */
115 CLIPFORMAT enmFormat;
116 /** The corresponding VBox clipboard format */
117 uint32_t u32VBoxFormat;
118} g_aFormats[] =
119{
120 { "INVALID", INVALID, 0 },
121 { "UTF8_STRING", UTF8, VBOX_SHCL_FMT_UNICODETEXT },
122 { "text/plain;charset=UTF-8", UTF8, VBOX_SHCL_FMT_UNICODETEXT },
123 { "text/plain;charset=utf-8", UTF8, VBOX_SHCL_FMT_UNICODETEXT },
124 { "STRING", TEXT, VBOX_SHCL_FMT_UNICODETEXT },
125 { "TEXT", TEXT, VBOX_SHCL_FMT_UNICODETEXT },
126 { "text/plain", TEXT, VBOX_SHCL_FMT_UNICODETEXT },
127 { "text/html", HTML, VBOX_SHCL_FMT_HTML },
128 { "text/html;charset=utf-8", HTML, VBOX_SHCL_FMT_HTML },
129 { "image/bmp", BMP, VBOX_SHCL_FMT_BITMAP },
130 { "image/x-bmp", BMP, VBOX_SHCL_FMT_BITMAP },
131 { "image/x-MS-bmp", BMP, VBOX_SHCL_FMT_BITMAP },
132 /** @todo Inkscape exports image/png but not bmp... */
133#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
134 { "text/uri-list", URI_LIST, VBOX_SHCL_FMT_URI_LIST },
135 { "x-special/gnome-copied-files", URI_LIST, VBOX_SHCL_FMT_URI_LIST },
136 { "x-special/nautilus-clipboard", URI_LIST, VBOX_SHCL_FMT_URI_LIST },
137 { "application/x-kde-cutselection", URI_LIST, VBOX_SHCL_FMT_URI_LIST },
138 /** @todo Anything else we need to add here? */
139 /** @todo Add Wayland / Weston support. */
140#endif
141};
142
143enum
144{
145 NIL_CLIPX11FORMAT = 0,
146 MAX_CLIP_X11_FORMATS = RT_ELEMENTS(g_aFormats)
147};
148
149
150/**
151 * Returns the atom corresponding to a supported X11 format.
152 * @param widget a valid Xt widget
153 */
154static Atom clipAtomForX11Format(CLIPBACKEND *pCtx, CLIPX11FORMAT format)
155{
156 return clipGetAtom(pCtx, g_aFormats[format].pcszAtom);
157}
158
159/**
160 * Returns the CLIPFORMAT corresponding to a supported X11 format.
161 */
162static CLIPFORMAT clipRealFormatForX11Format(CLIPX11FORMAT format)
163{
164 return g_aFormats[format].enmFormat;
165}
166
167/** Returns the atom corresponding to a supported X11 format. */
168static uint32_t clipVBoxFormatForX11Format(CLIPX11FORMAT format)
169{
170 return g_aFormats[format].u32VBoxFormat;
171}
172
173/**
174 * Looks up the X11 format matching a given X11 atom.
175 *
176 * @returns The format on success, NIL_CLIPX11FORMAT on failure.
177 * @param Widget a valid Xt widget.
178 */
179static CLIPX11FORMAT clipFindX11FormatByAtom(CLIPBACKEND *pCtx, Atom atomFormat)
180{
181 for (unsigned i = 0; i < RT_ELEMENTS(g_aFormats); ++i)
182 if (clipAtomForX11Format(pCtx, i) == atomFormat)
183 return i;
184 return NIL_CLIPX11FORMAT;
185}
186
187#ifdef TESTCASE
188/**
189 * Looks up the X11 format matching a given X11 atom text.
190 *
191 * @returns the format on success, NIL_CLIPX11FORMAT on failure
192 * @param Widget a valid Xt widget.
193 */
194static CLIPX11FORMAT clipFindX11FormatByAtomText(const char *pcsz)
195{
196 for (unsigned i = 0; i < RT_ELEMENTS(g_aFormats); ++i)
197 if (!strcmp(g_aFormats[i].pcszAtom, pcsz))
198 return i;
199 return NIL_CLIPX11FORMAT;
200}
201#endif
202
203/**
204 * Enumerates supported X11 clipboard formats corresponding to a given VBox
205 * format.
206 *
207 * @returns The next matching X11 format in the list, or NIL_CLIPX11FORMAT if
208 * there are no more.
209 * @param lastFormat The value returned from the last call of this function.
210 * Use NIL_CLIPX11FORMAT to start the enumeration.
211 */
212static CLIPX11FORMAT clipEnumX11Formats(uint32_t u32VBoxFormats,
213 CLIPX11FORMAT lastFormat)
214{
215 for (unsigned i = lastFormat + 1; i < RT_ELEMENTS(g_aFormats); ++i)
216 if (u32VBoxFormats & clipVBoxFormatForX11Format(i))
217 return i;
218 return NIL_CLIPX11FORMAT;
219}
220
221/** Global context information used by the X11 clipboard backend */
222struct _CLIPBACKEND
223{
224 /** Opaque data structure describing the front-end. */
225 SHCLCONTEXT *pFrontend;
226 /** Is an X server actually available? */
227 bool fHaveX11;
228 /** The X Toolkit application context structure */
229 XtAppContext appContext;
230
231 /** We have a separate thread to wait for Window and Clipboard events */
232 RTTHREAD thread;
233 /** The X Toolkit widget which we use as our clipboard client. It is never made visible. */
234 Widget widget;
235
236 /** Should we try to grab the clipboard on startup? */
237 bool fGrabClipboardOnStart;
238
239 /** The best text format X11 has to offer, as an index into the formats
240 * table */
241 CLIPX11FORMAT X11TextFormat;
242 /** The best bitmap format X11 has to offer, as an index into the formats
243 * table */
244 CLIPX11FORMAT X11BitmapFormat;
245 /** The best HTML format X11 has to offer, as an index into the formats
246 * table */
247 CLIPX11FORMAT X11HTMLFormat;
248#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
249 /** The best HTML format X11 has to offer, as an index into the formats
250 * table */
251 CLIPX11FORMAT X11URIListFormat;
252#endif
253 /** What formats does VBox have on offer? */
254 SHCLFORMATS vboxFormats;
255 /** Cache of the last unicode data that we received */
256 void *pvUnicodeCache;
257 /** Size of the unicode data in the cache */
258 uint32_t cbUnicodeCache;
259 /** When we wish the clipboard to exit, we have to wake up the event
260 * loop. We do this by writing into a pipe. This end of the pipe is
261 * the end that another thread can write to. */
262 int wakeupPipeWrite;
263 /** The reader end of the pipe */
264 int wakeupPipeRead;
265 /** A pointer to the XFixesSelectSelectionInput function */
266 void (*fixesSelectInput)(Display *, Window, Atom, unsigned long);
267 /** The first XFixes event number */
268 int fixesEventBase;
269};
270
271/**
272 * The number of simultaneous instances we support. For all normal purposes
273 * we should never need more than one. For the testcase it is convenient to
274 * have a second instance that the first can interact with in order to have
275 * a more controlled environment.
276 */
277enum { CLIP_MAX_CONTEXTS = 20 };
278
279/**
280 * Array of structures for mapping Xt widgets to context pointers. We
281 * need this because the widget clipboard callbacks do not pass user data.
282 */
283static struct
284{
285 /** The widget we want to associate the context with */
286 Widget widget;
287 /** The context associated with the widget */
288 CLIPBACKEND *pCtx;
289} g_contexts[CLIP_MAX_CONTEXTS];
290
291/**
292 * Registers a new X11 clipboard context.
293 *
294 * @param pCtx The clipboard backend context to use.
295 */
296static int clipRegisterContext(CLIPBACKEND *pCtx)
297{
298 AssertPtrReturn(pCtx, VERR_INVALID_PARAMETER);
299
300 bool fFound = false;
301 Widget widget = pCtx->widget;
302 AssertReturn(widget != NULL, VERR_INVALID_PARAMETER);
303 for (unsigned i = 0; i < RT_ELEMENTS(g_contexts); ++i)
304 {
305 AssertReturn( (g_contexts[i].widget != widget)
306 && (g_contexts[i].pCtx != pCtx), VERR_WRONG_ORDER);
307 if (g_contexts[i].widget == NULL && !fFound)
308 {
309 AssertReturn(g_contexts[i].pCtx == NULL, VERR_INTERNAL_ERROR);
310 g_contexts[i].widget = widget;
311 g_contexts[i].pCtx = pCtx;
312 fFound = true;
313 }
314 }
315
316 return fFound ? VINF_SUCCESS : VERR_OUT_OF_RESOURCES;
317}
318
319/**
320 * Unregister an X11 clipboard context.
321 *
322 * @param pCtx The clipboard backend context to use.
323 */
324static void clipUnregisterContext(CLIPBACKEND *pCtx)
325{
326 AssertPtrReturnVoid(pCtx);
327
328 Widget widget = pCtx->widget;
329 AssertPtrReturnVoid(widget);
330
331 bool fFound = false;
332 for (unsigned i = 0; i < RT_ELEMENTS(g_contexts); ++i)
333 {
334 Assert(!fFound || g_contexts[i].widget != widget);
335 if (g_contexts[i].widget == widget)
336 {
337 Assert(g_contexts[i].pCtx != NULL);
338 g_contexts[i].widget = NULL;
339 g_contexts[i].pCtx = NULL;
340 fFound = true;
341 }
342 }
343}
344
345/** Finds an X11 clipboard context. */
346static CLIPBACKEND *clipLookupContext(Widget widget)
347{
348 AssertPtrReturn(widget, NULL);
349
350 for (unsigned i = 0; i < RT_ELEMENTS(g_contexts); ++i)
351 {
352 if (g_contexts[i].widget == widget)
353 {
354 Assert(g_contexts[i].pCtx != NULL);
355 return g_contexts[i].pCtx;
356 }
357 }
358 return NULL;
359}
360
361/**
362 * Converts an atom name string to an X11 atom, looking it up in a cache
363 * before asking the server.
364 */
365static Atom clipGetAtom(CLIPBACKEND *pCtx, const char *pszName)
366{
367 AssertPtrReturn(pszName, None);
368 return XInternAtom(XtDisplay(pCtx->widget), pszName, False);
369}
370
371#ifdef TESTCASE
372static void tstClipQueueToEventThread(void (*proc)(void *, void *),
373 void *client_data);
374#endif
375
376/** String written to the wakeup pipe. */
377#define WAKE_UP_STRING "WakeUp!"
378/** Length of the string written. */
379#define WAKE_UP_STRING_LEN ( sizeof(WAKE_UP_STRING) - 1 )
380
381/**
382 * Schedules a function call to run on the Xt event thread by passing it to
383 * the application context as a 0ms timeout and waking up the event loop by
384 * writing to the wakeup pipe which it monitors.
385 */
386static int clipQueueToEventThread(CLIPBACKEND *pCtx,
387 void (*proc)(void *, void *),
388 void *client_data)
389{
390 LogFlowFunc(("proc=%p, client_data=%p\n", proc, client_data));
391
392 int rc = VINF_SUCCESS;
393
394#ifndef TESTCASE
395 XtAppAddTimeOut(pCtx->appContext, 0, (XtTimerCallbackProc)proc,
396 (XtPointer)client_data);
397 ssize_t cbWritten = write(pCtx->wakeupPipeWrite, WAKE_UP_STRING, WAKE_UP_STRING_LEN);
398 RT_NOREF(cbWritten);
399#else
400 RT_NOREF(pCtx);
401 tstClipQueueToEventThread(proc, client_data);
402#endif
403
404 LogFlowFuncLeaveRC(rc);
405 return rc;
406}
407
408/**
409 * Reports the formats currently supported by the X11 clipboard to VBox.
410 *
411 * @note Runs in Xt event thread.
412 *
413 * @param pCtx The clipboard backend context to use.
414 */
415static void clipReportFormatsToVBox(CLIPBACKEND *pCtx)
416{
417 uint32_t fFormats = clipVBoxFormatForX11Format(pCtx->X11TextFormat);
418 fFormats |= clipVBoxFormatForX11Format(pCtx->X11BitmapFormat);
419 fFormats |= clipVBoxFormatForX11Format(pCtx->X11HTMLFormat);
420#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
421 fFormats |= clipVBoxFormatForX11Format(pCtx->X11URIListFormat);
422#endif
423
424 LogFlowFunc(("fFormats=0x%x\n", fFormats));
425 LogFlowFunc(("txt: %u, bmp: %u, html: %u\n",
426 pCtx->X11TextFormat, pCtx->X11BitmapFormat, pCtx->X11HTMLFormat));
427#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
428 LogFlowFunc(("uri list: %u\n", pCtx->X11URIListFormat));
429#endif
430
431 LogRel2(("Shared Clipboard: Reported formats = 0x%x\n", fFormats));
432
433 ClipReportX11FormatsCallback(pCtx->pFrontend, fFormats);
434}
435
436/**
437 * Forgets which formats were previously in the X11 clipboard. Called when we
438 * grab the clipboard.
439 *
440 * @param pCtx The clipboard backend context to use.
441 */
442static void clipResetX11Formats(CLIPBACKEND *pCtx)
443{
444 LogFlowFuncEnter();
445
446 pCtx->X11TextFormat = INVALID;
447 pCtx->X11BitmapFormat = INVALID;
448 pCtx->X11HTMLFormat = INVALID;
449#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
450 pCtx->X11URIListFormat = INVALID;
451#endif
452}
453
454/**
455 * Tells VBox that X11 currently has nothing in its clipboard.
456 *
457 * @param pCtx The clipboard backend context to use.
458 */
459static void clipReportEmptyX11CB(CLIPBACKEND *pCtx)
460{
461 clipResetX11Formats(pCtx);
462 clipReportFormatsToVBox(pCtx);
463}
464
465/**
466 * Go through an array of X11 clipboard targets to see if they contain a text
467 * format we can support, and if so choose the ones we prefer (e.g. we like
468 * UTF-8 better than plain text).
469 *
470 * @return Supported X clipboard format.
471 * @param pCtx The clipboard backend context to use.
472 * @param pTargets The list of targets.
473 * @param cTargets The size of the list in @a pTargets.
474 */
475static CLIPX11FORMAT clipGetTextFormatFromTargets(CLIPBACKEND *pCtx,
476 CLIPX11FORMAT *pTargets,
477 size_t cTargets)
478{
479 AssertPtrReturn(pCtx, NIL_CLIPX11FORMAT);
480 AssertReturn(VALID_PTR(pTargets) || cTargets == 0, NIL_CLIPX11FORMAT);
481
482 CLIPX11FORMAT bestTextFormat = NIL_CLIPX11FORMAT;
483 CLIPFORMAT enmBestTextTarget = INVALID;
484 for (unsigned i = 0; i < cTargets; ++i)
485 {
486 CLIPX11FORMAT format = pTargets[i];
487 if (format != NIL_CLIPX11FORMAT)
488 {
489 if ( (clipVBoxFormatForX11Format(format) == VBOX_SHCL_FMT_UNICODETEXT)
490 && enmBestTextTarget < clipRealFormatForX11Format(format))
491 {
492 enmBestTextTarget = clipRealFormatForX11Format(format);
493 bestTextFormat = format;
494 }
495 }
496 }
497 return bestTextFormat;
498}
499
500#ifdef TESTCASE
501static bool tstClipTextFormatConversion(CLIPBACKEND *pCtx)
502{
503 bool fSuccess = true;
504 CLIPX11FORMAT targets[2];
505 CLIPX11FORMAT x11Format;
506 targets[0] = clipFindX11FormatByAtomText("text/plain");
507 targets[1] = clipFindX11FormatByAtomText("image/bmp");
508 x11Format = clipGetTextFormatFromTargets(pCtx, targets, 2);
509 if (clipRealFormatForX11Format(x11Format) != TEXT)
510 fSuccess = false;
511 targets[0] = clipFindX11FormatByAtomText("UTF8_STRING");
512 targets[1] = clipFindX11FormatByAtomText("text/plain");
513 x11Format = clipGetTextFormatFromTargets(pCtx, targets, 2);
514 if (clipRealFormatForX11Format(x11Format) != UTF8)
515 fSuccess = false;
516 return fSuccess;
517}
518#endif
519
520/**
521 * Goes through an array of X11 clipboard targets to see if they contain a bitmap
522 * format we can support, and if so choose the ones we prefer (e.g. we like
523 * BMP better than PNG because we don't have to convert).
524 *
525 * @return Supported X clipboard format.
526 * @param pCtx The clipboard backend context to use.
527 * @param pTargets The list of targets.
528 * @param cTargets The size of the list in @a pTargets.
529 */
530static CLIPX11FORMAT clipGetBitmapFormatFromTargets(CLIPBACKEND *pCtx,
531 CLIPX11FORMAT *pTargets,
532 size_t cTargets)
533{
534 AssertPtrReturn(pCtx, NIL_CLIPX11FORMAT);
535 AssertReturn(VALID_PTR(pTargets) || cTargets == 0, NIL_CLIPX11FORMAT);
536
537 CLIPX11FORMAT bestBitmapFormat = NIL_CLIPX11FORMAT;
538 CLIPFORMAT enmBestBitmapTarget = INVALID;
539 for (unsigned i = 0; i < cTargets; ++i)
540 {
541 CLIPX11FORMAT format = pTargets[i];
542 if (format != NIL_CLIPX11FORMAT)
543 {
544 if ( (clipVBoxFormatForX11Format(format) == VBOX_SHCL_FMT_BITMAP)
545 && enmBestBitmapTarget < clipRealFormatForX11Format(format))
546 {
547 enmBestBitmapTarget = clipRealFormatForX11Format(format);
548 bestBitmapFormat = format;
549 }
550 }
551 }
552 return bestBitmapFormat;
553}
554
555/**
556 * Goes through an array of X11 clipboard targets to see if they contain a HTML
557 * format we can support, and if so choose the ones we prefer.
558 *
559 * @return Supported X clipboard format.
560 * @param pCtx The clipboard backend context to use.
561 * @param pTargets The list of targets.
562 * @param cTargets The size of the list in @a pTargets.
563 */
564static CLIPX11FORMAT clipGetHtmlFormatFromTargets(CLIPBACKEND *pCtx,
565 CLIPX11FORMAT *pTargets,
566 size_t cTargets)
567{
568 AssertPtrReturn(pCtx, NIL_CLIPX11FORMAT);
569 AssertReturn(VALID_PTR(pTargets) || cTargets == 0, NIL_CLIPX11FORMAT);
570
571 CLIPX11FORMAT bestHTMLFormat = NIL_CLIPX11FORMAT;
572 CLIPFORMAT enmBestHtmlTarget = INVALID;
573 for (unsigned i = 0; i < cTargets; ++i)
574 {
575 CLIPX11FORMAT format = pTargets[i];
576 if (format != NIL_CLIPX11FORMAT)
577 {
578 if ( (clipVBoxFormatForX11Format(format) == VBOX_SHCL_FMT_HTML)
579 && enmBestHtmlTarget < clipRealFormatForX11Format(format))
580 {
581 enmBestHtmlTarget = clipRealFormatForX11Format(format);
582 bestHTMLFormat = format;
583 }
584 }
585 }
586 return bestHTMLFormat;
587}
588
589#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
590/**
591 * Goes through an array of X11 clipboard targets to see if they contain an URI list
592 * format we can support, and if so choose the ones we prefer.
593 *
594 * @return Supported X clipboard format.
595 * @param pCtx The clipboard backend context to use.
596 * @param pTargets The list of targets.
597 * @param cTargets The size of the list in @a pTargets.
598 */
599static CLIPX11FORMAT clipGetURIListFormatFromTargets(CLIPBACKEND *pCtx,
600 CLIPX11FORMAT *pTargets,
601 size_t cTargets)
602{
603 AssertPtrReturn(pCtx, NIL_CLIPX11FORMAT);
604 AssertReturn(VALID_PTR(pTargets) || cTargets == 0, NIL_CLIPX11FORMAT);
605
606 CLIPX11FORMAT bestURIListFormat = NIL_CLIPX11FORMAT;
607 CLIPFORMAT enmBestURIListTarget = INVALID;
608 for (unsigned i = 0; i < cTargets; ++i)
609 {
610 CLIPX11FORMAT format = pTargets[i];
611 if (format != NIL_CLIPX11FORMAT)
612 {
613 if ( (clipVBoxFormatForX11Format(format) == VBOX_SHCL_FMT_URI_LIST)
614 && enmBestURIListTarget < clipRealFormatForX11Format(format))
615 {
616 enmBestURIListTarget = clipRealFormatForX11Format(format);
617 bestURIListFormat = format;
618 }
619 }
620 }
621 return bestURIListFormat;
622}
623#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */
624
625/**
626 * Goes through an array of X11 clipboard targets to see if we can support any
627 * of them and if relevant to choose the ones we prefer (e.g. we like Utf8
628 * better than plain text).
629 *
630 * @param pCtx The clipboard backend context to use.
631 * @param pTargets The list of targets.
632 * @param cTargets The size of the list in @a pTargets.
633 */
634static void clipGetFormatsFromTargets(CLIPBACKEND *pCtx,
635 CLIPX11FORMAT *pTargets, size_t cTargets)
636{
637 AssertPtrReturnVoid(pCtx);
638 AssertPtrReturnVoid(pTargets);
639
640 CLIPX11FORMAT bestTextFormat = clipGetTextFormatFromTargets(pCtx, pTargets, cTargets);
641 if (pCtx->X11TextFormat != bestTextFormat)
642 pCtx->X11TextFormat = bestTextFormat;
643
644 pCtx->X11BitmapFormat = INVALID; /* not yet supported */
645 CLIPX11FORMAT bestBitmapFormat = clipGetBitmapFormatFromTargets(pCtx, pTargets, cTargets);
646 if (pCtx->X11BitmapFormat != bestBitmapFormat)
647 pCtx->X11BitmapFormat = bestBitmapFormat;
648
649 CLIPX11FORMAT bestHtmlFormat = clipGetHtmlFormatFromTargets(pCtx, pTargets, cTargets);
650 if (pCtx->X11HTMLFormat != bestHtmlFormat)
651 pCtx->X11HTMLFormat = bestHtmlFormat;
652
653#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
654 CLIPX11FORMAT bestURIListFormat = clipGetURIListFormatFromTargets(pCtx, pTargets, cTargets);
655 if (pCtx->X11URIListFormat != bestURIListFormat)
656 pCtx->X11URIListFormat = bestURIListFormat;
657#endif
658}
659
660static DECLCALLBACK(void) clipQueryX11FormatsCallback(CLIPBACKEND *pCtx);
661
662/**
663 * Updates the context's information about targets currently supported by X11,
664 * based on an array of X11 atoms.
665 *
666 * @param pCtx The clipboard backend context to use.
667 * @param pTargets The array of atoms describing the targets supported.
668 * @param cTargets The size of the array @a pTargets.
669 */
670static void clipUpdateX11Targets(CLIPBACKEND *pCtx, CLIPX11FORMAT *pTargets, size_t cTargets)
671{
672 LogFlowFuncEnter();
673
674 if (pTargets == NULL)
675 {
676 /* No data available */
677 clipReportEmptyX11CB(pCtx);
678 return;
679 }
680
681 clipGetFormatsFromTargets(pCtx, pTargets, cTargets);
682 clipReportFormatsToVBox(pCtx);
683}
684
685/**
686 * Notifies the VBox clipboard about available data formats, based on the
687 * "targets" information obtained from the X11 clipboard.
688 *
689 * @note Callback for XtGetSelectionValue().
690 * @note This function is treated as API glue, and as such is not part of any
691 * unit test. So keep it simple, be paranoid and log everything.
692 */
693static void clipConvertX11TargetsCallback(Widget widget, XtPointer pClient,
694 Atom * /* selection */, Atom *atomType,
695 XtPointer pValue, long unsigned int *pcLen,
696 int *piFormat)
697{
698 RT_NOREF(piFormat);
699
700 CLIPBACKEND *pCtx = reinterpret_cast<CLIPBACKEND *>(pClient);
701
702 Atom *pAtoms = (Atom *)pValue;
703 unsigned i, j;
704
705 LogFlowFunc(("pValue=%p, *pcLen=%u, *atomType=%d%s\n",
706 pValue, *pcLen, *atomType, *atomType == XT_CONVERT_FAIL ? " (XT_CONVERT_FAIL)" : ""));
707
708 CLIPX11FORMAT *pFormats = NULL;
709 if ( *pcLen
710 && pValue
711 && (*atomType != XT_CONVERT_FAIL /* time out */))
712 {
713 pFormats = (CLIPX11FORMAT *)RTMemAllocZ(*pcLen * sizeof(CLIPX11FORMAT));
714 }
715
716#if !defined(TESTCASE)
717 if (pValue)
718 {
719 for (i = 0; i < *pcLen; ++i)
720 {
721 if (pAtoms[i])
722 {
723 char *pszName = XGetAtomName(XtDisplay(widget), pAtoms[i]);
724 LogRel2(("Shared Clipboard: Found target '%s'\n", pszName));
725 XFree(pszName);
726 }
727 else
728 LogFunc(("Found empty target\n"));
729 }
730 }
731#endif
732
733 if (pFormats)
734 {
735 for (i = 0; i < *pcLen; ++i)
736 {
737 for (j = 0; j < RT_ELEMENTS(g_aFormats); ++j)
738 {
739 Atom target = XInternAtom(XtDisplay(widget),
740 g_aFormats[j].pcszAtom, False);
741 if (*(pAtoms + i) == target)
742 pFormats[i] = j;
743 }
744#if defined(DEBUG) && !defined(TESTCASE)
745 LogRel2(("%s: reporting format %d (%s)\n", __FUNCTION__,
746 pFormats[i], g_aFormats[pFormats[i]].pcszAtom));
747#endif
748 }
749 }
750 else
751 LogFunc(("Reporting empty targets (none reported or allocation failure)\n"));
752
753 clipUpdateX11Targets(pCtx, pFormats, *pcLen);
754 RTMemFree(pFormats);
755
756 XtFree(reinterpret_cast<char *>(pValue));
757}
758
759#ifdef TESTCASE
760 void tstRequestTargets(CLIPBACKEND *pCtx);
761#endif
762
763/**
764 * Callback to notify us when the contents of the X11 clipboard change.
765 *
766 * @param pCtx The clipboard backend context to use.
767 */
768static DECLCALLBACK(void) clipQueryX11FormatsCallback(CLIPBACKEND *pCtx)
769{
770 LogFlowFuncEnter();
771
772#ifndef TESTCASE
773 XtGetSelectionValue(pCtx->widget,
774 clipGetAtom(pCtx, "CLIPBOARD"),
775 clipGetAtom(pCtx, "TARGETS"),
776 clipConvertX11TargetsCallback, pCtx,
777 CurrentTime);
778#else
779 tstRequestTargets(pCtx);
780#endif
781}
782
783#ifndef TESTCASE
784
785typedef struct
786{
787 int type; /* event base */
788 unsigned long serial;
789 Bool send_event;
790 Display *display;
791 Window window;
792 int subtype;
793 Window owner;
794 Atom selection;
795 Time timestamp;
796 Time selection_timestamp;
797} XFixesSelectionNotifyEvent;
798
799/**
800 * Waits until an event arrives and handle it if it is an XFIXES selection
801 * event, which Xt doesn't know about.
802 *
803 * @param pCtx The clipboard backend context to use.
804 */
805void clipPeekEventAndDoXFixesHandling(CLIPBACKEND *pCtx)
806{
807 union
808 {
809 XEvent event;
810 XFixesSelectionNotifyEvent fixes;
811 } event = { { 0 } };
812
813 if (XtAppPeekEvent(pCtx->appContext, &event.event))
814 {
815 if ( (event.event.type == pCtx->fixesEventBase)
816 && (event.fixes.owner != XtWindow(pCtx->widget)))
817 {
818 if ( (event.fixes.subtype == 0 /* XFixesSetSelectionOwnerNotify */)
819 && (event.fixes.owner != 0))
820 clipQueryX11FormatsCallback(pCtx);
821 else
822 clipReportEmptyX11CB(pCtx);
823 }
824 }
825}
826
827/**
828 * The main loop of our X11 event thread.
829 *
830 * @note X11 backend code.
831 */
832static DECLCALLBACK(int) clipEventThread(RTTHREAD hThreadSelf, void *pvUser)
833{
834 RT_NOREF(hThreadSelf);
835
836 LogRel(("Shared Clipboard: Starting X11 event thread\n"));
837
838 CLIPBACKEND *pCtx = (CLIPBACKEND *)pvUser;
839
840 if (pCtx->fGrabClipboardOnStart)
841 clipQueryX11FormatsCallback(pCtx);
842
843 while (XtAppGetExitFlag(pCtx->appContext) == FALSE)
844 {
845 clipPeekEventAndDoXFixesHandling(pCtx);
846 XtAppProcessEvent(pCtx->appContext, XtIMAll);
847 }
848
849 LogRel(("Shared Clipboard: X11 event thread terminated successfully\n"));
850 return VINF_SUCCESS;
851}
852#endif /* !TESTCASE */
853
854/**
855 * X11 specific uninitialisation for the shared clipboard.
856 *
857 * @note X11 backend code.
858 *
859 * @param pCtx The clipboard backend context to use.
860 */
861static void clipUninit(CLIPBACKEND *pCtx)
862{
863 AssertPtrReturnVoid(pCtx);
864 if (pCtx->widget)
865 {
866 /* Valid widget + invalid appcontext = bug. But don't return yet. */
867 AssertPtr(pCtx->appContext);
868 clipUnregisterContext(pCtx);
869 XtDestroyWidget(pCtx->widget);
870 }
871 pCtx->widget = NULL;
872 if (pCtx->appContext)
873 XtDestroyApplicationContext(pCtx->appContext);
874 pCtx->appContext = NULL;
875 if (pCtx->wakeupPipeRead != 0)
876 close(pCtx->wakeupPipeRead);
877 if (pCtx->wakeupPipeWrite != 0)
878 close(pCtx->wakeupPipeWrite);
879 pCtx->wakeupPipeRead = 0;
880 pCtx->wakeupPipeWrite = 0;
881}
882
883/** Worker function for stopping the clipboard which runs on the event
884 * thread. */
885static void clipStopEventThreadWorker(void *pUserData, void *)
886{
887
888 CLIPBACKEND *pCtx = (CLIPBACKEND *)pUserData;
889
890 /* This might mean that we are getting stopped twice. */
891 Assert(pCtx->widget != NULL);
892
893 /* Set the termination flag to tell the Xt event loop to exit. We
894 * reiterate that any outstanding requests from the X11 event loop to
895 * the VBox part *must* have returned before we do this. */
896 XtAppSetExitFlag(pCtx->appContext);
897}
898
899#ifndef TESTCASE
900/**
901 * Sets up the XFixes library and load the XFixesSelectSelectionInput symbol.
902 */
903static int clipLoadXFixes(Display *pDisplay, CLIPBACKEND *pCtx)
904{
905 int rc;
906
907 void *hFixesLib = dlopen("libXfixes.so.1", RTLD_LAZY);
908 if (!hFixesLib)
909 hFixesLib = dlopen("libXfixes.so.2", RTLD_LAZY);
910 if (!hFixesLib)
911 hFixesLib = dlopen("libXfixes.so.3", RTLD_LAZY);
912 if (!hFixesLib)
913 hFixesLib = dlopen("libXfixes.so.4", RTLD_LAZY);
914 if (hFixesLib)
915 {
916 /* For us, a NULL function pointer is a failure */
917 pCtx->fixesSelectInput = (void (*)(Display *, Window, Atom, long unsigned int))
918 (uintptr_t)dlsym(hFixesLib, "XFixesSelectSelectionInput");
919 if (pCtx->fixesSelectInput)
920 {
921 int dummy1 = 0;
922 int dummy2 = 0;
923 if (XQueryExtension(pDisplay, "XFIXES", &dummy1, &pCtx->fixesEventBase, &dummy2) != 0)
924 {
925 if (pCtx->fixesEventBase >= 0)
926 {
927 rc = VINF_SUCCESS;
928 }
929 else
930 {
931 LogRel(("Shared Clipboard: fixesEventBase is less than zero: %d\n", pCtx->fixesEventBase));
932 rc = VERR_NOT_SUPPORTED;
933 }
934 }
935 else
936 {
937 LogRel(("Shared Clipboard: XQueryExtension failed\n"));
938 rc = VERR_NOT_SUPPORTED;
939 }
940 }
941 else
942 {
943 LogRel(("Shared Clipboard: Symbol XFixesSelectSelectionInput not found!\n"));
944 rc = VERR_NOT_SUPPORTED;
945 }
946 }
947 else
948 {
949 LogRel(("Shared Clipboard: libxFixes.so.* not found!\n"));
950 rc = VERR_NOT_SUPPORTED;
951 }
952 return rc;
953}
954#endif /* !TESTCASE */
955
956/**
957 * This is the callback which is scheduled when data is available on the
958 * wakeup pipe. It simply reads all data from the pipe.
959 */
960static void clipDrainWakeupPipe(XtPointer pUserData, int *, XtInputId *)
961{
962 LogFlowFuncEnter();
963
964 CLIPBACKEND *pCtx = (CLIPBACKEND *)pUserData;
965 char acBuf[WAKE_UP_STRING_LEN];
966
967 while (read(pCtx->wakeupPipeRead, acBuf, sizeof(acBuf)) > 0) {}
968}
969
970/**
971 * X11 specific initialisation for the shared clipboard.
972 *
973 * @note X11 backend code.
974 *
975 * @returns VBox status code.
976 * @param pCtx The clipboard backend context to use.
977 */
978static int clipInit(CLIPBACKEND *pCtx)
979{
980 /* Create a window and make it a clipboard viewer. */
981 int cArgc = 0;
982 char *pcArgv = 0;
983 int rc = VINF_SUCCESS;
984 Display *pDisplay;
985
986 /* Make sure we are thread safe. */
987 XtToolkitThreadInitialize();
988
989 /*
990 * Set up the Clipboard application context and main window. We call all
991 * these functions directly instead of calling XtOpenApplication() so
992 * that we can fail gracefully if we can't get an X11 display.
993 */
994 XtToolkitInitialize();
995 pCtx->appContext = XtCreateApplicationContext();
996 pDisplay = XtOpenDisplay(pCtx->appContext, 0, 0, "VBoxShCl", 0, 0, &cArgc, &pcArgv);
997 if (NULL == pDisplay)
998 {
999 LogRel(("Shared Clipboard: Failed to connect to the X11 clipboard - the window system may not be running\n"));
1000 rc = VERR_NOT_SUPPORTED;
1001 }
1002#ifndef TESTCASE
1003 if (RT_SUCCESS(rc))
1004 {
1005 rc = clipLoadXFixes(pDisplay, pCtx);
1006 if (RT_FAILURE(rc))
1007 LogRel(("Shared Clipboard: Failed to load the XFIXES extension\n"));
1008 }
1009#endif
1010 if (RT_SUCCESS(rc))
1011 {
1012 pCtx->widget = XtVaAppCreateShell(0, "VBoxShCl",
1013 applicationShellWidgetClass,
1014 pDisplay, XtNwidth, 1, XtNheight,
1015 1, NULL);
1016 if (NULL == pCtx->widget)
1017 {
1018 LogRel(("Shared Clipboard: Failed to construct the X11 window for the shared clipboard manager\n"));
1019 rc = VERR_NO_MEMORY;
1020 }
1021 else
1022 rc = clipRegisterContext(pCtx);
1023 }
1024 if (RT_SUCCESS(rc))
1025 {
1026 XtSetMappedWhenManaged(pCtx->widget, false);
1027 XtRealizeWidget(pCtx->widget);
1028#ifndef TESTCASE
1029 /* Enable clipboard update notification. */
1030 pCtx->fixesSelectInput(pDisplay, XtWindow(pCtx->widget),
1031 clipGetAtom(pCtx, "CLIPBOARD"),
1032 7 /* All XFixes*Selection*NotifyMask flags */);
1033#endif
1034 }
1035
1036 /*
1037 * Create the pipes.
1038 */
1039 int pipes[2];
1040 if (!pipe(pipes))
1041 {
1042 pCtx->wakeupPipeRead = pipes[0];
1043 pCtx->wakeupPipeWrite = pipes[1];
1044 if (!XtAppAddInput(pCtx->appContext, pCtx->wakeupPipeRead,
1045 (XtPointer) XtInputReadMask,
1046 clipDrainWakeupPipe, (XtPointer) pCtx))
1047 rc = VERR_NO_MEMORY; /* What failure means is not doc'ed. */
1048 if ( RT_SUCCESS(rc)
1049 && (fcntl(pCtx->wakeupPipeRead, F_SETFL, O_NONBLOCK) != 0))
1050 rc = RTErrConvertFromErrno(errno);
1051 if (RT_FAILURE(rc))
1052 LogRel(("Shared Clipboard: Failed to setup the termination mechanism\n"));
1053 }
1054 else
1055 rc = RTErrConvertFromErrno(errno);
1056 if (RT_FAILURE(rc))
1057 clipUninit(pCtx);
1058 if (RT_FAILURE(rc))
1059 LogRel(("Shared Clipboard: Initialisation failed: %Rrc\n", rc));
1060 return rc;
1061}
1062
1063/**
1064 * Constructs the X11 backend of the shared clipboard.
1065 *
1066 * @note X11 backend code
1067 */
1068CLIPBACKEND *ClipConstructX11(SHCLCONTEXT *pFrontend, bool fHeadless)
1069{
1070 CLIPBACKEND *pCtx = (CLIPBACKEND *)RTMemAllocZ(sizeof(CLIPBACKEND));
1071 if (pCtx && fHeadless)
1072 {
1073 /*
1074 * If we don't find the DISPLAY environment variable we assume that
1075 * we are not connected to an X11 server. Don't actually try to do
1076 * this then, just fail silently and report success on every call.
1077 * This is important for VBoxHeadless.
1078 */
1079 LogRel(("Shared Clipboard: X11 DISPLAY variable not set -- disabling clipboard sharing\n"));
1080 pCtx->fHaveX11 = false;
1081 return pCtx;
1082 }
1083
1084 pCtx->fHaveX11 = true;
1085
1086 LogRel(("Shared Clipboard: Initializing X11 clipboard backend\n"));
1087
1088 if (pCtx)
1089 pCtx->pFrontend = pFrontend;
1090 return pCtx;
1091}
1092
1093/**
1094 * Destructs the shared clipboard X11 backend.
1095 *
1096 * @note X11 backend code.
1097 *
1098 * @param pCtx The clipboard backend context to use.
1099 */
1100void ClipDestructX11(CLIPBACKEND *pCtx)
1101{
1102 if (!pCtx)
1103 return;
1104
1105 if (pCtx->fHaveX11)
1106 {
1107 /* We set this to NULL when the event thread exits. It really should
1108 * have exited at this point, when we are about to unload the code from
1109 * memory. */
1110 Assert(pCtx->widget == NULL);
1111 }
1112
1113 RTMemFree(pCtx);
1114}
1115
1116/**
1117 * Announces to the X11 backend that we are ready to start.
1118 *
1119 * @returns VBox status code.
1120 * @param pCtx The clipboard backend context to use.
1121 * @param fGrab Whether we should try to grab the shared clipboard at once.
1122 */
1123int ClipStartX11(CLIPBACKEND *pCtx, bool fGrab)
1124{
1125 int rc = VINF_SUCCESS;
1126
1127 /*
1128 * Immediately return if we are not connected to the X server.
1129 */
1130 if (!pCtx->fHaveX11)
1131 return VINF_SUCCESS;
1132
1133 rc = clipInit(pCtx);
1134 if (RT_SUCCESS(rc))
1135 {
1136 clipResetX11Formats(pCtx);
1137 pCtx->fGrabClipboardOnStart = fGrab;
1138 }
1139#ifndef TESTCASE
1140 if (RT_SUCCESS(rc))
1141 {
1142 rc = RTThreadCreate(&pCtx->thread, clipEventThread, pCtx, 0,
1143 RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "SHCLX11");
1144 if (RT_FAILURE(rc))
1145 {
1146 LogRel(("Shared Clipboard: Failed to start the X11 event thread with %Rrc\n", rc));
1147 clipUninit(pCtx);
1148 }
1149 }
1150#endif
1151 return rc;
1152}
1153
1154/**
1155 * Shuts down the shared clipboard X11 backend.
1156 *
1157 * @note X11 backend code.
1158 * @note Any requests from this object to get clipboard data from VBox
1159 * *must* have completed or aborted before we are called, as
1160 * otherwise the X11 event loop will still be waiting for the request
1161 * to return and will not be able to terminate.
1162 *
1163 * @returns VBox status code.
1164 * @param pCtx The clipboard backend context to use.
1165 */
1166int ClipStopX11(CLIPBACKEND *pCtx)
1167{
1168 int rc, rcThread;
1169 unsigned count = 0;
1170 /*
1171 * Immediately return if we are not connected to the X server.
1172 */
1173 if (!pCtx->fHaveX11)
1174 return VINF_SUCCESS;
1175
1176 LogRel(("Shared Clipboard: Stopping X11 backend\n"));
1177
1178 /* Write to the "stop" pipe */
1179 clipQueueToEventThread(pCtx, clipStopEventThreadWorker, (XtPointer) pCtx);
1180
1181#ifndef TESTCASE
1182 do
1183 {
1184 rc = RTThreadWait(pCtx->thread, 1000, &rcThread);
1185 ++count;
1186 Assert(RT_SUCCESS(rc) || ((VERR_TIMEOUT == rc) && (count != 5)));
1187 } while ((VERR_TIMEOUT == rc) && (count < 300));
1188#else
1189 rc = VINF_SUCCESS;
1190 rcThread = VINF_SUCCESS;
1191 RT_NOREF_PV(count);
1192#endif
1193 if (RT_SUCCESS(rc))
1194 {
1195 AssertRC(rcThread);
1196 }
1197 else
1198 LogRel(("Shared Clipboard: Stopping X11 backend failed with %Rrc\n", rc));
1199
1200 clipUninit(pCtx);
1201
1202 RT_NOREF_PV(rcThread);
1203 return rc;
1204}
1205
1206/**
1207 * Satisfies a request from X11 for clipboard targets supported by VBox.
1208 *
1209 * @returns VBox status code.
1210 * @param pCtx The clipboard backend context to use.
1211 * @param atomTypeReturn The type of the data we are returning.
1212 * @param pValReturn A pointer to the data we are returning. This
1213 * should be set to memory allocated by XtMalloc,
1214 * which will be freed later by the Xt toolkit.
1215 * @param pcLenReturn The length of the data we are returning.
1216 * @param piFormatReturn The format (8bit, 16bit, 32bit) of the data we are
1217 * returning.
1218 * @note X11 backend code, called by the XtOwnSelection callback.
1219 */
1220static int clipCreateX11Targets(CLIPBACKEND *pCtx, Atom *atomTypeReturn,
1221 XtPointer *pValReturn,
1222 unsigned long *pcLenReturn,
1223 int *piFormatReturn)
1224{
1225 LogFlowFuncEnter();
1226
1227 const unsigned cFixedTargets = 3;
1228
1229 Atom *atomTargets = (Atom *)XtMalloc((MAX_CLIP_X11_FORMATS + cFixedTargets) * sizeof(Atom));
1230 unsigned cTargets = 0;
1231 CLIPX11FORMAT format = NIL_CLIPX11FORMAT;
1232 do
1233 {
1234 format = clipEnumX11Formats(pCtx->vboxFormats, format);
1235 if (format != NIL_CLIPX11FORMAT)
1236 {
1237 atomTargets[cTargets] = clipAtomForX11Format(pCtx, format);
1238 ++cTargets;
1239 }
1240 } while (format != NIL_CLIPX11FORMAT);
1241
1242 /* We always offer these fixed targets. */
1243 atomTargets[cTargets] = clipGetAtom(pCtx, "TARGETS");
1244 atomTargets[cTargets + 1] = clipGetAtom(pCtx, "MULTIPLE");
1245 atomTargets[cTargets + 2] = clipGetAtom(pCtx, "TIMESTAMP");
1246
1247 *atomTypeReturn = XA_ATOM;
1248 *pValReturn = (XtPointer)atomTargets;
1249 *pcLenReturn = cTargets + cFixedTargets;
1250 *piFormatReturn = 32;
1251
1252 return VINF_SUCCESS;
1253}
1254
1255/**
1256 * This is a wrapper around ClipRequestDataForX11Callback that will cache the
1257 * data returned.
1258 */
1259static int clipReadVBoxShCl(CLIPBACKEND *pCtx, SHCLFORMAT Format,
1260 void **ppv, uint32_t *pcb)
1261{
1262 LogFlowFunc(("pCtx=%p, Format=%02X, ppv=%p, pcb=%p\n", pCtx, Format, ppv, pcb));
1263
1264 int rc = VINF_SUCCESS;
1265
1266 if (Format == VBOX_SHCL_FMT_UNICODETEXT)
1267 {
1268 if (pCtx->pvUnicodeCache == NULL)
1269 rc = ClipRequestDataForX11Callback(pCtx->pFrontend, Format,
1270 &pCtx->pvUnicodeCache,
1271 &pCtx->cbUnicodeCache);
1272 if (RT_SUCCESS(rc))
1273 {
1274 *ppv = RTMemDup(pCtx->pvUnicodeCache, pCtx->cbUnicodeCache);
1275 *pcb = pCtx->cbUnicodeCache;
1276 if (*ppv == NULL)
1277 rc = VERR_NO_MEMORY;
1278 }
1279 }
1280 else
1281 rc = ClipRequestDataForX11Callback(pCtx->pFrontend, Format,
1282 ppv, pcb);
1283 if (RT_SUCCESS(rc))
1284 LogFlowFunc(("*ppv=%.*ls, *pcb=%u\n", *pcb, *ppv, *pcb));
1285
1286 LogFlowFuncLeaveRC(rc);
1287 return rc;
1288}
1289
1290/**
1291 * Calculates a buffer size large enough to hold the source Windows format
1292 * text converted into Unix Utf8, including the null terminator.
1293 *
1294 * @returns VBox status code.
1295 * @param pwsz The source text in UCS-2 with Windows EOLs.
1296 * @param cwc The size in USC-2 elements of the source text, with or
1297 * without the terminator.
1298 * @param pcbActual Where to store the buffer size needed.
1299 */
1300static int clipWinTxtBufSizeForUtf8(PRTUTF16 pwsz, size_t cwc,
1301 size_t *pcbActual)
1302{
1303 size_t cbRet = 0;
1304 int rc = RTUtf16CalcUtf8LenEx(pwsz, cwc, &cbRet);
1305 if (RT_SUCCESS(rc))
1306 *pcbActual = cbRet + 1; /* null terminator */
1307 return rc;
1308}
1309
1310/**
1311 * Converts text from Windows format (UCS-2 with CRLF line endings) to standard UTF-8.
1312 *
1313 * @returns VBox status code.
1314 * @param pwszSrc The text to be converted.
1315 * @param cbSrc The length of @a pwszSrc in bytes.
1316 * @param pszBuf Where to write the converted string.
1317 * @param cbBuf The size of the buffer pointed to by @a pszBuf.
1318 * @param pcbActual Where to store the size of the converted string.
1319 * optional.
1320 */
1321static int clipWinTxtToUtf8(PRTUTF16 pwszSrc, size_t cbSrc, char *pszBuf,
1322 size_t cbBuf, size_t *pcbActual)
1323{
1324 PRTUTF16 pwszTmp = NULL;
1325 size_t cwSrc = cbSrc / 2, cwTmp = 0, cbDest = 0;
1326 int rc = VINF_SUCCESS;
1327
1328 LogFlowFunc (("pwszSrc=%.*ls, cbSrc=%u\n", cbSrc, pwszSrc, cbSrc));
1329 /* How long will the converted text be? */
1330 AssertPtr(pwszSrc);
1331 AssertPtr(pszBuf);
1332 rc = ShClUtf16GetLinSize(pwszSrc, cwSrc, &cwTmp);
1333 if (RT_SUCCESS(rc) && cwTmp == 0)
1334 rc = VERR_NO_DATA;
1335 if (RT_SUCCESS(rc))
1336 pwszTmp = (PRTUTF16)RTMemAlloc(cwTmp * 2);
1337 if (!pwszTmp)
1338 rc = VERR_NO_MEMORY;
1339 /* Convert the text. */
1340 if (RT_SUCCESS(rc))
1341 rc = ShClUtf16WinToLin(pwszSrc, cwSrc, pwszTmp, cwTmp);
1342 if (RT_SUCCESS(rc))
1343 {
1344 /* Convert the UTF-16 string to Utf8. */
1345 rc = RTUtf16ToUtf8Ex(pwszTmp + 1, cwTmp - 1, &pszBuf, cbBuf,
1346 &cbDest);
1347 }
1348 RTMemFree(reinterpret_cast<void *>(pwszTmp));
1349 if (pcbActual)
1350 *pcbActual = cbDest + 1;
1351
1352 if (RT_SUCCESS(rc))
1353 LogFlowFunc (("converted string is %.*s. Returning.\n", cbDest, pszBuf));
1354
1355 LogFlowFuncLeaveRC(rc);
1356 return rc;
1357}
1358
1359/**
1360 * Satisfies a request from X11 to convert the clipboard text to UTF-8. We
1361 * return null-terminated text, but can cope with non-null-terminated input.
1362 *
1363 * @returns VBox status code.
1364 * @param pDisplay An X11 display structure, needed for conversions
1365 * performed by Xlib.
1366 * @param pv The text to be converted (UCS-2 with Windows EOLs).
1367 * @param cb The length of the text in @cb in bytes.
1368 * @param atomTypeReturn Where to store the atom for the type of the data
1369 * we are returning.
1370 * @param pValReturn Where to store the pointer to the data we are
1371 * returning. This should be to memory allocated by
1372 * XtMalloc, which will be freed by the Xt toolkit
1373 * later.
1374 * @param pcLenReturn Where to store the length of the data we are
1375 * returning.
1376 * @param piFormatReturn Where to store the bit width (8, 16, 32) of the
1377 * data we are returning.
1378 */
1379static int clipWinTxtToUtf8ForX11CB(Display *pDisplay, PRTUTF16 pwszSrc,
1380 size_t cbSrc, Atom *atomTarget,
1381 Atom *atomTypeReturn,
1382 XtPointer *pValReturn,
1383 unsigned long *pcLenReturn,
1384 int *piFormatReturn)
1385{
1386 RT_NOREF(pDisplay, pcLenReturn);
1387
1388 /* This may slightly overestimate the space needed. */
1389 size_t cbDest = 0;
1390 int rc = clipWinTxtBufSizeForUtf8(pwszSrc, cbSrc / 2, &cbDest);
1391 if (RT_SUCCESS(rc))
1392 {
1393 char *pszDest = (char *)XtMalloc(cbDest);
1394 size_t cbActual = 0;
1395 if (pszDest)
1396 {
1397 rc = clipWinTxtToUtf8(pwszSrc, cbSrc, pszDest, cbDest, &cbActual);
1398 }
1399 else
1400 rc = VERR_NO_MEMORY;
1401
1402 if (RT_SUCCESS(rc))
1403 {
1404 *atomTypeReturn = *atomTarget;
1405 *pValReturn = (XtPointer)pszDest;
1406 *pcLenReturn = cbActual;
1407 *piFormatReturn = 8;
1408 }
1409 }
1410 return rc;
1411}
1412
1413/**
1414 * Satisfies a request from X11 to convert the clipboard HTML fragment to UTF-8. We
1415 * return null-terminated text, but can cope with non-null-terminated input.
1416 *
1417 * @returns VBox status code.
1418 * @param pDisplay An X11 display structure, needed for conversions
1419 * performed by Xlib.
1420 * @param pv The text to be converted (UTF8 with Windows EOLs).
1421 * @param cb The length of the text in @cb in bytes.
1422 * @param atomTypeReturn Where to store the atom for the type of the data
1423 * we are returning.
1424 * @param pValReturn Where to store the pointer to the data we are
1425 * returning. This should be to memory allocated by
1426 * XtMalloc, which will be freed by the Xt toolkit later.
1427 * @param pcLenReturn Where to store the length of the data we are returning.
1428 * @param piFormatReturn Where to store the bit width (8, 16, 32) of the
1429 * data we are returning.
1430 */
1431static int clipWinHTMLToUtf8ForX11CB(Display *pDisplay, const char *pszSrc,
1432 size_t cbSrc, Atom *atomTarget,
1433 Atom *atomTypeReturn,
1434 XtPointer *pValReturn,
1435 unsigned long *pcLenReturn,
1436 int *piFormatReturn)
1437{
1438 RT_NOREF(pDisplay, pValReturn);
1439
1440 /* This may slightly overestimate the space needed. */
1441 LogFlowFunc(("Source: %s", pszSrc));
1442
1443 char *pszDest = (char *)XtMalloc(cbSrc);
1444 if (pszDest == NULL)
1445 return VERR_NO_MEMORY;
1446
1447 memcpy(pszDest, pszSrc, cbSrc);
1448
1449 *atomTypeReturn = *atomTarget;
1450 *pValReturn = (XtPointer)pszDest;
1451 *pcLenReturn = cbSrc;
1452 *piFormatReturn = 8;
1453
1454 return VINF_SUCCESS;
1455}
1456
1457
1458/**
1459 * Does this atom correspond to one of the two selection types we support?
1460 *
1461 * @param pCtx The clipboard backend context to use.
1462 * @param selType The atom in question.
1463 */
1464static bool clipIsSupportedSelectionType(CLIPBACKEND *pCtx, Atom selType)
1465{
1466 return( (selType == clipGetAtom(pCtx, "CLIPBOARD"))
1467 || (selType == clipGetAtom(pCtx, "PRIMARY")));
1468}
1469
1470/**
1471 * Removes a trailing nul character from a string by adjusting the string
1472 * length. Some X11 applications don't like zero-terminated text...
1473 *
1474 * @param pText The text in question.
1475 * @param pcText The length of the text, adjusted on return.
1476 * @param format The format of the text.
1477 */
1478static void clipTrimTrailingNul(XtPointer pText, unsigned long *pcText,
1479 CLIPFORMAT format)
1480{
1481 AssertPtrReturnVoid(pText);
1482 AssertPtrReturnVoid(pcText);
1483 AssertReturnVoid((format == UTF8) || (format == TEXT) || (format == HTML));
1484
1485 if (((char *)pText)[*pcText - 1] == '\0')
1486 --(*pcText);
1487}
1488
1489static int clipConvertVBoxCBForX11(CLIPBACKEND *pCtx, Atom *atomTarget,
1490 Atom *atomTypeReturn,
1491 XtPointer *pValReturn,
1492 unsigned long *pcLenReturn,
1493 int *piFormatReturn)
1494{
1495 int rc = VINF_SUCCESS;
1496
1497 CLIPX11FORMAT x11Format = clipFindX11FormatByAtom(pCtx, *atomTarget);
1498 CLIPFORMAT clipFormat = clipRealFormatForX11Format(x11Format);
1499
1500 LogFlowFunc(("fFormats=0x%x, x11Format=%u, clipFormat=%u\n", pCtx->vboxFormats, x11Format, clipFormat));
1501
1502 if ( ((clipFormat == UTF8) || (clipFormat == TEXT))
1503 && (pCtx->vboxFormats & VBOX_SHCL_FMT_UNICODETEXT))
1504 {
1505 void *pv = NULL;
1506 uint32_t cb = 0;
1507 rc = clipReadVBoxShCl(pCtx, VBOX_SHCL_FMT_UNICODETEXT, &pv, &cb);
1508 if (RT_SUCCESS(rc) && (cb == 0))
1509 rc = VERR_NO_DATA;
1510 if (RT_SUCCESS(rc) && ((clipFormat == UTF8) || (clipFormat == TEXT)))
1511 rc = clipWinTxtToUtf8ForX11CB(XtDisplay(pCtx->widget),
1512 (PRTUTF16)pv, cb, atomTarget,
1513 atomTypeReturn, pValReturn,
1514 pcLenReturn, piFormatReturn);
1515 if (RT_SUCCESS(rc))
1516 clipTrimTrailingNul(*(XtPointer *)pValReturn, pcLenReturn, clipFormat);
1517
1518 RTMemFree(pv);
1519 }
1520 else if ( (clipFormat == BMP)
1521 && (pCtx->vboxFormats & VBOX_SHCL_FMT_BITMAP))
1522 {
1523 void *pv = NULL;
1524 uint32_t cb = 0;
1525 rc = clipReadVBoxShCl(pCtx, VBOX_SHCL_FMT_BITMAP, &pv, &cb);
1526 if (RT_SUCCESS(rc) && (cb == 0))
1527 rc = VERR_NO_DATA;
1528 if (RT_SUCCESS(rc) && (clipFormat == BMP))
1529 {
1530 /* Create a full BMP from it */
1531 rc = ShClDibToBmp(pv, cb, (void **)pValReturn,
1532 (size_t *)pcLenReturn);
1533 }
1534 else
1535 rc = VERR_NOT_SUPPORTED;
1536
1537 if (RT_SUCCESS(rc))
1538 {
1539 *atomTypeReturn = *atomTarget;
1540 *piFormatReturn = 8;
1541 }
1542
1543 RTMemFree(pv);
1544 }
1545 else if ( (clipFormat == HTML)
1546 && (pCtx->vboxFormats & VBOX_SHCL_FMT_HTML))
1547 {
1548 void *pv = NULL;
1549 uint32_t cb = 0;
1550 rc = clipReadVBoxShCl(pCtx, VBOX_SHCL_FMT_HTML, &pv, &cb);
1551 if (RT_SUCCESS(rc) && (cb == 0))
1552 rc = VERR_NO_DATA;
1553 if (RT_SUCCESS(rc))
1554 {
1555 /*
1556 * The common VBox HTML encoding will be - Utf8
1557 * because it more general for HTML formats then UTF16
1558 * X11 clipboard returns UTF-16, so before sending it we should
1559 * convert it to UTF8.
1560 * It's very strange but here we get UTF-16 from x11 clipboard
1561 * in same time we send UTF-8 to x11 clipboard and it's work.
1562 */
1563 rc = clipWinHTMLToUtf8ForX11CB(XtDisplay(pCtx->widget),
1564 (const char*)pv, cb, atomTarget,
1565 atomTypeReturn, pValReturn,
1566 pcLenReturn, piFormatReturn);
1567 if (RT_SUCCESS(rc))
1568 clipTrimTrailingNul(*(XtPointer *)pValReturn, pcLenReturn, clipFormat);
1569
1570 RTMemFree(pv);
1571 }
1572 }
1573#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
1574 else if (pCtx->vboxFormats & VBOX_SHCL_FMT_URI_LIST)
1575 {
1576 switch (clipFormat)
1577 {
1578 case TEXT:
1579 RT_FALL_THROUGH();
1580 case UTF8:
1581 {
1582 void *pv = NULL;
1583 uint32_t cb = 0;
1584 rc = clipReadVBoxShCl(pCtx, VBOX_SHCL_FMT_URI_LIST, &pv, &cb);
1585
1586 RTMemFree(pv);
1587 break;
1588 }
1589
1590 case URI_LIST:
1591 {
1592 break;
1593 }
1594
1595 default:
1596 rc = VERR_NOT_SUPPORTED;
1597 break;
1598 }
1599 }
1600#endif
1601 else
1602 {
1603 *atomTypeReturn = XT_CONVERT_FAIL;
1604 *pValReturn = (XtPointer)NULL;
1605 *pcLenReturn = 0;
1606 *piFormatReturn = 0;
1607
1608 rc = VERR_NOT_SUPPORTED;
1609 }
1610
1611 if (RT_FAILURE(rc))
1612 LogRel2(("Shared Clipboard: Converting format 0x%x for X11 (x11Format=%u, clipFormat=%u) failed, rc=%Rrc\n",
1613 pCtx->vboxFormats, x11Format, clipFormat, rc));
1614
1615 LogFlowFuncLeaveRC(rc);
1616 return rc;
1617}
1618
1619/**
1620 * Returns VBox's clipboard data for an X11 client.
1621 *
1622 * @note X11 backend code, callback for XtOwnSelection
1623 */
1624static Boolean clipXtConvertSelectionProc(Widget widget, Atom *atomSelection,
1625 Atom *atomTarget,
1626 Atom *atomTypeReturn,
1627 XtPointer *pValReturn,
1628 unsigned long *pcLenReturn,
1629 int *piFormatReturn)
1630{
1631 LogFlowFuncEnter();
1632
1633 CLIPBACKEND *pCtx = clipLookupContext(widget);
1634 int rc = VINF_SUCCESS;
1635
1636 if (!pCtx)
1637 return False;
1638
1639 if (!clipIsSupportedSelectionType(pCtx, *atomSelection))
1640 return False;
1641
1642 if (*atomTarget == clipGetAtom(pCtx, "TARGETS"))
1643 rc = clipCreateX11Targets(pCtx, atomTypeReturn, pValReturn,
1644 pcLenReturn, piFormatReturn);
1645 else
1646 rc = clipConvertVBoxCBForX11(pCtx, atomTarget, atomTypeReturn,
1647 pValReturn, pcLenReturn, piFormatReturn);
1648
1649 LogFlowFunc(("returning %RTbool, internal status code %Rrc\n", RT_SUCCESS(rc), rc));
1650 return RT_SUCCESS(rc) ? True : False;
1651}
1652
1653/**
1654 * Structure used to pass information about formats that VBox supports.
1655 */
1656typedef struct _CLIPNEWVBOXFORMATS
1657{
1658 /** Context information for the X11 clipboard. */
1659 CLIPBACKEND *pCtx;
1660 /** Formats supported by VBox. */
1661 SHCLFORMATS Formats;
1662} CLIPNEWVBOXFORMATS, *PCLIPNEWVBOXFORMATS;
1663
1664/** Invalidates the local cache of the data in the VBox clipboard. */
1665static void clipInvalidateVBoxCBCache(CLIPBACKEND *pCtx)
1666{
1667 if (pCtx->pvUnicodeCache != NULL)
1668 {
1669 RTMemFree(pCtx->pvUnicodeCache);
1670 pCtx->pvUnicodeCache = NULL;
1671 }
1672}
1673
1674/**
1675 * Takes possession of the X11 clipboard (and middle-button selection).
1676 */
1677static void clipGrabX11CB(CLIPBACKEND *pCtx, SHCLFORMATS Formats)
1678{
1679 LogFlowFuncEnter();
1680
1681 if (XtOwnSelection(pCtx->widget, clipGetAtom(pCtx, "CLIPBOARD"),
1682 CurrentTime, clipXtConvertSelectionProc, NULL, 0))
1683 {
1684 pCtx->vboxFormats = Formats;
1685 /* Grab the middle-button paste selection too. */
1686 XtOwnSelection(pCtx->widget, clipGetAtom(pCtx, "PRIMARY"),
1687 CurrentTime, clipXtConvertSelectionProc, NULL, 0);
1688#ifndef TESTCASE
1689 /* Xt suppresses these if we already own the clipboard, so send them
1690 * ourselves. */
1691 XSetSelectionOwner(XtDisplay(pCtx->widget),
1692 clipGetAtom(pCtx, "CLIPBOARD"),
1693 XtWindow(pCtx->widget), CurrentTime);
1694 XSetSelectionOwner(XtDisplay(pCtx->widget),
1695 clipGetAtom(pCtx, "PRIMARY"),
1696 XtWindow(pCtx->widget), CurrentTime);
1697#endif
1698 }
1699}
1700
1701/**
1702 * Worker function for ClipAnnounceFormatToX11 which runs on the
1703 * event thread.
1704 *
1705 * @param pUserData Pointer to a CLIPNEWVBOXFORMATS structure containing
1706 * information about the VBox formats available and the
1707 * clipboard context data. Must be freed by the worker.
1708 */
1709static void clipAnnounceFormatToX11Worker(void *pUserData,
1710 void * /* interval */)
1711{
1712 CLIPNEWVBOXFORMATS *pFormats = (CLIPNEWVBOXFORMATS *)pUserData;
1713 CLIPBACKEND *pCtx = pFormats->pCtx;
1714
1715 uint32_t fFormats = pFormats->Formats;
1716
1717 RTMemFree(pFormats);
1718
1719 LogFlowFunc (("fFormats=0x%x\n", fFormats));
1720
1721 clipInvalidateVBoxCBCache(pCtx);
1722 clipGrabX11CB(pCtx, fFormats);
1723 clipResetX11Formats(pCtx);
1724
1725 LogFlowFuncLeave();
1726}
1727
1728/**
1729 * Announces new clipboard formats to the host.
1730 *
1731 * @returns VBox status code.
1732 * @param Formats Clipboard formats offered.
1733 */
1734int ClipAnnounceFormatToX11(CLIPBACKEND *pCtx, uint32_t Formats)
1735{
1736 /*
1737 * Immediately return if we are not connected to the X server.
1738 */
1739 if (!pCtx->fHaveX11)
1740 return VINF_SUCCESS;
1741
1742 int rc;
1743
1744 /* This must be free'd by the worker callback. */
1745 PCLIPNEWVBOXFORMATS pFormats = (PCLIPNEWVBOXFORMATS)RTMemAlloc(sizeof(CLIPNEWVBOXFORMATS));
1746 if (pFormats)
1747 {
1748 pFormats->pCtx = pCtx;
1749 pFormats->Formats = Formats;
1750
1751 rc = clipQueueToEventThread(pCtx, clipAnnounceFormatToX11Worker,
1752 (XtPointer) pFormats);
1753 }
1754 else
1755 rc = VERR_NO_MEMORY;
1756
1757 LogFlowFuncLeaveRC(rc);
1758 return rc;
1759}
1760
1761/**
1762 * Massages generic UTF-16 with CR end-of-lines into the format Windows expects
1763 * and return the result in a RTMemAlloc allocated buffer.
1764 *
1765 * @returns VBox status code.
1766 * @param pwcSrc The source as UTF-16.
1767 * @param cwcSrc The number of 16bit elements in @a pwcSrc, not counting
1768 * the terminating zero.
1769 * @param ppwszDest Where to store the buffer address.
1770 * @param pcbDest On success, where to store the number of bytes written.
1771 * Undefined otherwise. Optional.
1772 */
1773static int clipUtf16ToWinTxt(RTUTF16 *pwcSrc, size_t cwcSrc,
1774 PRTUTF16 *ppwszDest, uint32_t *pcbDest)
1775{
1776 AssertPtrReturn(pwcSrc, VERR_INVALID_POINTER);
1777 AssertPtrReturn(ppwszDest, VERR_INVALID_POINTER);
1778
1779 LogFlowFunc(("pwcSrc=%p, cwcSrc=%u, ppwszDest=%p\n", pwcSrc, cwcSrc, ppwszDest));
1780
1781 if (pcbDest)
1782 *pcbDest = 0;
1783
1784 PRTUTF16 pwszDest = NULL;
1785 size_t cwcDest;
1786 int rc = ShClUtf16GetWinSize(pwcSrc, cwcSrc + 1, &cwcDest);
1787 if (RT_SUCCESS(rc))
1788 {
1789 pwszDest = (PRTUTF16)RTMemAlloc(cwcDest * sizeof(RTUTF16));
1790 if (!pwszDest)
1791 rc = VERR_NO_MEMORY;
1792 }
1793
1794 if (RT_SUCCESS(rc))
1795 rc = ShClUtf16LinToWin(pwcSrc, cwcSrc + 1, pwszDest, cwcDest);
1796
1797 if (RT_SUCCESS(rc))
1798 {
1799 LogFlowFunc(("Converted string is %.*ls\n", cwcDest, pwszDest));
1800
1801 *ppwszDest = pwszDest;
1802
1803 if (pcbDest)
1804 *pcbDest = cwcDest * sizeof(RTUTF16);
1805 }
1806 else
1807 RTMemFree(pwszDest);
1808
1809 LogFlowFuncLeaveRC(rc);
1810 return rc;
1811}
1812
1813/**
1814 * Converts UTF-8 text with CR end-of-lines into UTF-16 as Windows expects it
1815 * and return the result in a RTMemAlloc allocated buffer.
1816 *
1817 * @returns VBox status code.
1818 * @param pcSrc The source UTF-8.
1819 * @param cbSrc The size of the source in bytes, not counting the
1820 * terminating zero.
1821 * @param ppwszDest Where to store the buffer address.
1822 * @param pcbDest On success, where to store the number of bytes written.
1823 * Undefined otherwise. Optional.
1824 */
1825static int clipUtf8ToWinTxt(const char *pcSrc, unsigned cbSrc,
1826 PRTUTF16 *ppwszDest, uint32_t *pcbDest)
1827{
1828 AssertPtrReturn(pcSrc, VERR_INVALID_POINTER);
1829 AssertPtrReturn(ppwszDest, VERR_INVALID_POINTER);
1830
1831 LogFlowFunc(("pcSrc=%p, cbSrc=%u, ppwszDest=%p\n", pcSrc, cbSrc, ppwszDest));
1832
1833 if (pcbDest)
1834 *pcbDest = 0;
1835
1836 /* Intermediate conversion to UTF-16. */
1837 size_t cwcTmp;
1838 PRTUTF16 pwcTmp = NULL;
1839 int rc = RTStrToUtf16Ex(pcSrc, cbSrc, &pwcTmp, 0, &cwcTmp);
1840 if (RT_SUCCESS(rc))
1841 rc = clipUtf16ToWinTxt(pwcTmp, cwcTmp, ppwszDest, pcbDest);
1842
1843 RTUtf16Free(pwcTmp);
1844
1845 LogFlowFuncLeaveRC(rc);
1846 return rc;
1847}
1848
1849/**
1850 * Converts Latin-1 text with CR end-of-lines into UTF-16 as Windows expects
1851 * it and return the result in a RTMemAlloc allocated buffer.
1852 *
1853 * @returns VBox status code.
1854 * @param pcSrc The source text.
1855 * @param cbSrc The size of the source in bytes, not counting the
1856 * terminating zero.
1857 * @param ppwszDest Where to store the buffer address.
1858 * @param pcbDest On success, where to store the number of bytes written.
1859 * Undefined otherwise. Optional.
1860 */
1861static int clipLatin1ToWinTxt(char *pcSrc, unsigned cbSrc,
1862 PRTUTF16 *ppwszDest, uint32_t *pcbDest)
1863{
1864 AssertPtrReturn(pcSrc, VERR_INVALID_POINTER);
1865 AssertPtrReturn(ppwszDest, VERR_INVALID_POINTER);
1866
1867 LogFlowFunc(("pcSrc=%.*s, cbSrc=%u, ppwszDest=%p\n", cbSrc, (char *) pcSrc, cbSrc, ppwszDest));
1868
1869 PRTUTF16 pwszDest = NULL;
1870 int rc = VINF_SUCCESS;
1871
1872 /* Calculate the space needed. */
1873 unsigned cwcDest = 0;
1874 for (unsigned i = 0; i < cbSrc && pcSrc[i] != '\0'; ++i)
1875 {
1876 if (pcSrc[i] == LINEFEED)
1877 cwcDest += 2;
1878 else
1879 ++cwcDest;
1880 }
1881
1882 ++cwcDest; /* Leave space for the terminator. */
1883
1884 if (pcbDest)
1885 *pcbDest = cwcDest * sizeof(RTUTF16);
1886
1887 pwszDest = (PRTUTF16) RTMemAlloc(cwcDest * sizeof(RTUTF16));
1888 if (!pwszDest)
1889 rc = VERR_NO_MEMORY;
1890
1891 /* And do the conversion, bearing in mind that Latin-1 expands "naturally"
1892 * to UTF-16. */
1893 if (RT_SUCCESS(rc))
1894 {
1895 for (unsigned i = 0, j = 0; i < cbSrc; ++i, ++j)
1896 {
1897 if (pcSrc[i] != LINEFEED)
1898 pwszDest[j] = pcSrc[i];
1899 else
1900 {
1901 pwszDest[j] = CARRIAGERETURN;
1902 pwszDest[j + 1] = LINEFEED;
1903 ++j;
1904 }
1905 }
1906
1907 pwszDest[cwcDest - 1] = '\0'; /* Make sure we are zero-terminated. */
1908
1909 LogFlowFunc(("Converted text is %.*ls\n", cwcDest, pwszDest));
1910 }
1911
1912 if (RT_SUCCESS(rc))
1913 {
1914 *ppwszDest = pwszDest;
1915 }
1916 else
1917 RTMemFree(pwszDest);
1918
1919 LogFlowFuncLeaveRC(rc);
1920 return rc;
1921}
1922
1923/**
1924* Converts UTF-16 text into UTF-8 as Windows expects
1925* it and return the result in a RTMemAlloc allocated buffer.
1926*
1927* @returns VBox status code.
1928* @param pcSrc The source text.
1929* @param cbSrc The size of the source in bytes, not counting the
1930* terminating zero.
1931* @param ppwszDest Where to store the buffer address.
1932* @param pcbDest On success, where to store the number of bytes written.
1933* Undefined otherwise. Optional.
1934*/
1935static int clipUTF16ToWinHTML(RTUTF16 *pwcBuf, size_t cb, char **ppszOut, uint32_t *pcOut)
1936{
1937 AssertPtrReturn(pwcBuf, VERR_INVALID_POINTER);
1938 AssertReturn (cb, VERR_INVALID_PARAMETER);
1939 AssertPtrReturn(ppszOut, VERR_INVALID_POINTER);
1940 AssertPtrReturn(pcOut, VERR_INVALID_POINTER);
1941
1942 if (cb % 2)
1943 return VERR_INVALID_PARAMETER;
1944
1945 size_t cwc = cb / 2;
1946 size_t i = 0;
1947 RTUTF16 *pwc = pwcBuf;
1948 char *pchRes = NULL;
1949 size_t cRes = 0;
1950 LogFlowFunc(("src= %ls cb=%d i=%i, %x %x\n", pwcBuf, cb, i, ppszOut, pcOut));
1951 while (i < cwc)
1952 {
1953 /* find zero symbol (end of string) */
1954 for (; i < cwc && pwcBuf[i] != 0; i++)
1955 ;
1956 LogFlowFunc(("skipped nulls i=%d cwc=%d\n", i, cwc));
1957
1958 /* convert found string */
1959 char *psz = NULL;
1960 size_t cch = 0;
1961 int rc = RTUtf16ToUtf8Ex(pwc, cwc, &psz, pwc - pwcBuf, &cch);
1962 LogFlowFunc(("utf16toutf8 src= %ls res=%s i=%i\n", pwc, psz, i));
1963 if (RT_FAILURE(rc))
1964 {
1965 RTMemFree(pchRes);
1966 return rc;
1967 }
1968
1969 /* append new substring */
1970 char *pchNew = (char *)RTMemRealloc(pchRes, cRes + cch + 1);
1971 if (!pchNew)
1972 {
1973 RTMemFree(pchRes);
1974 RTStrFree(psz);
1975 return VERR_NO_MEMORY;
1976 }
1977 pchRes = pchNew;
1978 memcpy(pchRes + cRes, psz, cch + 1);
1979 LogFlowFunc(("Temp result res=%s\n", pchRes + cRes));
1980
1981 /* remove temporary buffer */
1982 RTStrFree(psz);
1983 cRes += cch + 1;
1984 /* skip zero symbols */
1985 for (; i < cwc && pwcBuf[i] == 0; i++)
1986 ;
1987 /* remember start of string */
1988 pwc += i;
1989 }
1990 *ppszOut = pchRes;
1991 *pcOut = cRes;
1992
1993 return VINF_SUCCESS;
1994}
1995
1996/**
1997 * A structure containing information about where to store a request
1998 * for the X11 clipboard contents.
1999 */
2000typedef struct _CLIPREADX11CBREQ
2001{
2002 /** The format VBox would like the data in. */
2003 SHCLFORMAT mFormat;
2004 /** The format we requested from X11. */
2005 CLIPX11FORMAT mX11Format;
2006 /** The clipboard context this request is associated with. */
2007 CLIPBACKEND *mpCtx;
2008 /** The request structure passed in from the backend. */
2009 CLIPREADCBREQ *mpReq;
2010} CLIPREADX11CBREQ;
2011
2012/**
2013 * Converts the data obtained from the X11 clipboard to the required format,
2014 * place it in the buffer supplied and signal that data has arrived.
2015 *
2016 * Converts the text obtained UTF-16LE with Windows EOLs.
2017 * Converts full BMP data to DIB format.
2018 *
2019 * @note X11 backend code, callback for XtGetSelectionValue, for use when
2020 * the X11 clipboard contains a format we understand.
2021 */
2022static void clipConvertDataFromX11CallbackWorker(void *pClient, void *pvSrc, unsigned cbSrc)
2023{
2024 CLIPREADX11CBREQ *pReq = (CLIPREADX11CBREQ *)pClient;
2025
2026 LogFlowFunc(("pReq->mFormat=%02X, pReq->mX11Format=%u, pReq->mCtx=%p\n", pReq->mFormat, pReq->mX11Format, pReq->mpCtx));
2027
2028 AssertPtr(pReq->mpCtx);
2029 Assert(pReq->mFormat != VBOX_SHCL_FMT_NONE); /* Sanity. */
2030
2031 int rc = VINF_SUCCESS;
2032
2033 void *pvDst = NULL;
2034 uint32_t cbDst = 0;
2035
2036 if (pvSrc == NULL)
2037 {
2038 /* The clipboard selection may have changed before we could get it. */
2039 rc = VERR_NO_DATA;
2040 }
2041 else if (pReq->mFormat == VBOX_SHCL_FMT_UNICODETEXT)
2042 {
2043 /* In which format is the clipboard data? */
2044 switch (clipRealFormatForX11Format(pReq->mX11Format))
2045 {
2046 case UTF8:
2047 RT_FALL_THROUGH();
2048 case TEXT:
2049 {
2050 /* If we are given broken UTF-8, we treat it as Latin1. */ /** @todo Is this acceptable? */
2051 if (RT_SUCCESS(RTStrValidateEncodingEx((char *)pvSrc, cbSrc, 0)))
2052 {
2053 rc = clipUtf8ToWinTxt((const char *)pvSrc, cbSrc,
2054 (PRTUTF16 *)&pvDst, &cbDst);
2055 }
2056 else
2057 {
2058 rc = clipLatin1ToWinTxt((char *)pvSrc, cbSrc,
2059 (PRTUTF16 *)&pvDst, &cbDst);
2060 }
2061 break;
2062 }
2063
2064 default:
2065 {
2066 rc = VERR_INVALID_PARAMETER;
2067 break;
2068 }
2069 }
2070 }
2071 else if (pReq->mFormat == VBOX_SHCL_FMT_BITMAP)
2072 {
2073 /* In which format is the clipboard data? */
2074 switch (clipRealFormatForX11Format(pReq->mX11Format))
2075 {
2076 case BMP:
2077 {
2078 const void *pDib;
2079 size_t cbDibSize;
2080 rc = ShClBmpGetDib((const void *)pvSrc, cbSrc,
2081 &pDib, &cbDibSize);
2082 if (RT_SUCCESS(rc))
2083 {
2084 pvDst = RTMemAlloc(cbDibSize);
2085 if (!pvDst)
2086 rc = VERR_NO_MEMORY;
2087 else
2088 {
2089 memcpy(pvDst, pDib, cbDibSize);
2090 cbDst = cbDibSize;
2091 }
2092 }
2093 break;
2094 }
2095
2096 default:
2097 {
2098 rc = VERR_INVALID_PARAMETER;
2099 break;
2100 }
2101 }
2102 }
2103 else if (pReq->mFormat == VBOX_SHCL_FMT_HTML)
2104 {
2105 /* In which format is the clipboard data? */
2106 switch (clipRealFormatForX11Format(pReq->mX11Format))
2107 {
2108 case HTML:
2109 {
2110 /*
2111 * The common VBox HTML encoding will be - UTF-8
2112 * because it more general for HTML formats then UTF-16
2113 * X11 clipboard returns UTF-16, so before sending it we should
2114 * convert it to UTF-8.
2115 */
2116 pvDst = NULL;
2117 cbDst = 0;
2118
2119 /*
2120 * Some applications sends data in UTF-16, some in UTF-8,
2121 * without indication it in MIME.
2122 * But in case of UTF-16, at least an OpenOffice adds Byte Order Mark - 0xfeff
2123 * at start of clipboard data.
2124 */
2125 if ( cbSrc >= sizeof(RTUTF16)
2126 && *(PRTUTF16)pvSrc == 0xfeff)
2127 {
2128 LogFlowFunc((" \n"));
2129 rc = clipUTF16ToWinHTML((RTUTF16 *)pvSrc, cbSrc,
2130 (char**)&pvDst, &cbDst);
2131 }
2132 else
2133 {
2134 pvDst = RTMemAlloc(cbSrc);
2135 if(pvDst)
2136 {
2137 memcpy(pvDst, pvSrc, cbSrc);
2138 cbDst = cbSrc;
2139 }
2140 else
2141 {
2142 rc = VERR_NO_MEMORY;
2143 break;
2144 }
2145 }
2146
2147 LogFlowFunc(("Source unicode %ls, cbSrc = %d\n, Byte Order Mark = %hx", pvSrc, cbSrc, ((PRTUTF16)pvSrc)[0]));
2148 LogFlowFunc(("converted to win unicode %s, cbDest = %d, rc = %Rrc\n", pvDst, cbDst, rc));
2149 rc = VINF_SUCCESS;
2150 break;
2151 }
2152
2153 default:
2154 {
2155 rc = VERR_INVALID_PARAMETER;
2156 break;
2157 }
2158 }
2159 }
2160#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
2161 else if (pReq->mFormat == VBOX_SHCL_FMT_URI_LIST)
2162 {
2163 /* In which format is the clipboard data? */
2164 switch (clipRealFormatForX11Format(pReq->mX11Format))
2165 {
2166 case URI_LIST:
2167 {
2168 /* For URI lists we only accept valid UTF-8 encodings. */
2169 if (RT_SUCCESS(RTStrValidateEncodingEx((char *)pvSrc, cbSrc, 0)))
2170 {
2171 /* URI lists on X are string separated with "\r\n". */
2172 RTCList<RTCString> lstRootEntries = RTCString((char *)pvSrc, cbSrc).split("\r\n");
2173 for (size_t i = 0; i < lstRootEntries.size(); ++i)
2174 {
2175 char *pszEntry = RTUriFilePath(lstRootEntries.at(i).c_str());
2176 AssertPtrBreakStmt(pszEntry, VERR_INVALID_PARAMETER);
2177
2178 LogFlowFunc(("URI list entry '%s'\n", pszEntry));
2179
2180 rc = RTStrAAppend((char **)&pvDst, pszEntry);
2181 AssertRCBreakStmt(rc, VERR_NO_MEMORY);
2182 cbDst += (uint32_t)strlen(pszEntry);
2183
2184 rc = RTStrAAppend((char **)&pvDst, "\r\n");
2185 AssertRCBreakStmt(rc, VERR_NO_MEMORY);
2186 cbDst += (uint32_t)strlen("\r\n");
2187
2188 RTStrFree(pszEntry);
2189 }
2190
2191 if (cbDst)
2192 cbDst++; /* Include final (zero) termination. */
2193
2194 LogFlowFunc(("URI list: cbDst=%RU32\n", cbDst));
2195 }
2196 else
2197 rc = VERR_INVALID_PARAMETER;
2198 break;
2199 }
2200
2201 default:
2202 {
2203 rc = VERR_INVALID_PARAMETER;
2204 break;
2205 }
2206 }
2207 }
2208#endif
2209 else
2210 rc = VERR_NOT_SUPPORTED;
2211
2212 ClipRequestFromX11CompleteCallback(pReq->mpCtx->pFrontend, rc, pReq->mpReq,
2213 pvDst, cbDst);
2214 RTMemFree(pvDst);
2215 RTMemFree(pReq);
2216
2217 LogFlowFuncLeaveRC(rc);
2218}
2219
2220#ifndef TESTCASE
2221/**
2222 * Converts the data obtained from the X11 clipboard to the required format,
2223 * place it in the buffer supplied and signal that data has arrived.
2224 *
2225 * Converts the text obtained UTF-16LE with Windows EOLs.
2226 * Converts full BMP data to DIB format.
2227 *
2228 * @note X11 backend code, callback for XtGetSelectionValue(), for use when
2229 * the X11 clipboard contains a format we understand.
2230 */
2231static void clipConvertDataFromX11Callback(Widget widget, XtPointer pClient,
2232 Atom * /* selection */, Atom *atomType,
2233 XtPointer pvSrc, long unsigned int *pcLen,
2234 int *piFormat)
2235{
2236 RT_NOREF(widget);
2237 if (*atomType == XT_CONVERT_FAIL) /* Xt timeout */
2238 clipConvertDataFromX11CallbackWorker(pClient, NULL, 0);
2239 else
2240 clipConvertDataFromX11CallbackWorker(pClient, pvSrc, (*pcLen) * (*piFormat) / 8);
2241
2242 XtFree((char *)pvSrc);
2243}
2244#endif
2245
2246#ifdef TESTCASE
2247static void tstClipRequestData(CLIPBACKEND* pCtx, CLIPX11FORMAT target,
2248 void *closure);
2249#endif
2250
2251static int clipGetSelectionValue(CLIPBACKEND *pCtx, CLIPX11FORMAT format,
2252 CLIPREADX11CBREQ *pReq)
2253{
2254#ifndef TESTCASE
2255 XtGetSelectionValue(pCtx->widget, clipGetAtom(pCtx, "CLIPBOARD"),
2256 clipAtomForX11Format(pCtx, format),
2257 clipConvertDataFromX11Callback,
2258 reinterpret_cast<XtPointer>(pReq),
2259 CurrentTime);
2260#else
2261 tstClipRequestData(pCtx, format, (void *)pReq);
2262#endif
2263
2264 return VINF_SUCCESS; /** @todo Return real rc. */
2265}
2266
2267/**
2268 * Worker function for ClipReadDataFromX11 which runs on the event thread.
2269 */
2270static void clipReadDataFromX11Worker(void *pvUserData, void * /* interval */)
2271{
2272 AssertPtrReturnVoid(pvUserData);
2273
2274 CLIPREADX11CBREQ *pReq = (CLIPREADX11CBREQ *)pvUserData;
2275 CLIPBACKEND *pCtx = pReq->mpCtx;
2276
2277 LogFlowFunc(("pReq->mFormat = %02X\n", pReq->mFormat));
2278
2279 int rc = VERR_NO_DATA; /* VBox thinks we have data and we don't. */
2280
2281 if (pReq->mFormat == VBOX_SHCL_FMT_UNICODETEXT)
2282 {
2283 pReq->mX11Format = pCtx->X11TextFormat;
2284 if (pReq->mX11Format != INVALID)
2285 {
2286 /* Send out a request for the data to the current clipboard owner. */
2287 rc = clipGetSelectionValue(pCtx, pCtx->X11TextFormat, pReq);
2288 }
2289 }
2290 else if (pReq->mFormat == VBOX_SHCL_FMT_BITMAP)
2291 {
2292 pReq->mX11Format = pCtx->X11BitmapFormat;
2293 if (pReq->mX11Format != INVALID)
2294 {
2295 /* Send out a request for the data to the current clipboard owner. */
2296 rc = clipGetSelectionValue(pCtx, pCtx->X11BitmapFormat, pReq);
2297 }
2298 }
2299 else if (pReq->mFormat == VBOX_SHCL_FMT_HTML)
2300 {
2301 pReq->mX11Format = pCtx->X11HTMLFormat;
2302 if (pReq->mX11Format != INVALID)
2303 {
2304 /* Send out a request for the data to the current clipboard owner. */
2305 rc = clipGetSelectionValue(pCtx, pCtx->X11HTMLFormat, pReq);
2306 }
2307 }
2308#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
2309 else if (pReq->mFormat == VBOX_SHCL_FMT_URI_LIST)
2310 {
2311 pReq->mX11Format = pCtx->X11URIListFormat;
2312 if (pReq->mX11Format != INVALID)
2313 {
2314 /* Send out a request for the data to the current clipboard owner. */
2315 rc = clipGetSelectionValue(pCtx, pCtx->X11URIListFormat, pReq);
2316 }
2317 }
2318#endif
2319 else
2320 {
2321 rc = VERR_NOT_IMPLEMENTED;
2322 }
2323
2324 if (RT_FAILURE(rc))
2325 {
2326 /* The clipboard callback was never scheduled, so we must signal
2327 * that the request processing is finished and clean up ourselves. */
2328 ClipRequestFromX11CompleteCallback(pReq->mpCtx->pFrontend, rc, pReq->mpReq,
2329 NULL /* pv */ ,0 /* cb */);
2330 RTMemFree(pReq);
2331 }
2332
2333 LogFlowFuncLeaveRC(rc);
2334}
2335
2336/**
2337 * Called when VBox wants to read the X11 clipboard.
2338 *
2339 * @returns VBox status code.
2340 * @param pCtx Context data for the clipboard backend.
2341 * @param Format The format that the VBox would like to receive the data in.
2342 * @param pReq Read callback request to use. Must be free'd in the callback.
2343 *
2344 * @note We allocate a request structure which must be freed by the worker.
2345 */
2346int ClipReadDataFromX11(CLIPBACKEND *pCtx, SHCLFORMAT Format, CLIPREADCBREQ *pReq)
2347{
2348 /*
2349 * Immediately return if we are not connected to the X server.
2350 */
2351 if (!pCtx->fHaveX11)
2352 return VERR_NO_DATA;
2353
2354 int rc = VINF_SUCCESS;
2355
2356 CLIPREADX11CBREQ *pX11Req = (CLIPREADX11CBREQ *)RTMemAllocZ(sizeof(CLIPREADX11CBREQ));
2357 if (pX11Req)
2358 {
2359 pX11Req->mpCtx = pCtx;
2360 pX11Req->mFormat = Format;
2361 pX11Req->mpReq = pReq;
2362
2363 /* We use this to schedule a worker function on the event thread. */
2364 rc = clipQueueToEventThread(pCtx, clipReadDataFromX11Worker, (XtPointer)pX11Req);
2365 }
2366 else
2367 rc = VERR_NO_MEMORY;
2368
2369 LogFlowFuncLeaveRC(rc);
2370 return rc;
2371}
2372
2373#ifdef TESTCASE
2374
2375/** @todo This unit test currently works by emulating the X11 and X toolkit
2376 * APIs to exercise the code, since I didn't want to rewrite the code too much
2377 * when I wrote the tests. However, this makes it rather ugly and hard to
2378 * understand. Anyone doing any work on the code should feel free to
2379 * rewrite the tests and the code to make them cleaner and more readable. */
2380
2381#include <iprt/test.h>
2382#include <poll.h>
2383
2384#define TEST_WIDGET (Widget)0xffff
2385
2386/* For the purpose of the test case, we just execute the procedure to be
2387 * scheduled, as we are running single threaded. */
2388void tstClipQueueToEventThread(void (*proc)(void *, void *),
2389 void *client_data)
2390{
2391 proc(client_data, NULL);
2392}
2393
2394void XtFree(char *ptr)
2395{
2396 RTMemFree((void *)ptr);
2397}
2398
2399/* The data in the simulated VBox clipboard. */
2400static int g_tstVBoxDataRC = VINF_SUCCESS;
2401static void *g_tstVBoxDataPv = NULL;
2402static uint32_t g_tstVBoxDataCb = 0;
2403
2404/* Set empty data in the simulated VBox clipboard. */
2405static void tstClipEmptyVBox(CLIPBACKEND *pCtx, int retval)
2406{
2407 g_tstVBoxDataRC = retval;
2408 RTMemFree(g_tstVBoxDataPv);
2409 g_tstVBoxDataPv = NULL;
2410 g_tstVBoxDataCb = 0;
2411 ClipAnnounceFormatToX11(pCtx, 0);
2412}
2413
2414/* Set the data in the simulated VBox clipboard. */
2415static int tstClipSetVBoxUtf16(CLIPBACKEND *pCtx, int retval,
2416 const char *pcszData, size_t cb)
2417{
2418 PRTUTF16 pwszData = NULL;
2419 size_t cwData = 0;
2420 int rc = RTStrToUtf16Ex(pcszData, RTSTR_MAX, &pwszData, 0, &cwData);
2421 if (RT_FAILURE(rc))
2422 return rc;
2423 AssertReturn(cb <= cwData * 2 + 2, VERR_BUFFER_OVERFLOW);
2424 void *pv = RTMemDup(pwszData, cb);
2425 RTUtf16Free(pwszData);
2426 if (pv == NULL)
2427 return VERR_NO_MEMORY;
2428 if (g_tstVBoxDataPv)
2429 RTMemFree(g_tstVBoxDataPv);
2430 g_tstVBoxDataRC = retval;
2431 g_tstVBoxDataPv = pv;
2432 g_tstVBoxDataCb = cb;
2433 ClipAnnounceFormatToX11(pCtx, VBOX_SHCL_FMT_UNICODETEXT);
2434 return VINF_SUCCESS;
2435}
2436
2437/* Return the data in the simulated VBox clipboard. */
2438DECLCALLBACK(int) ClipRequestDataForX11Callback(SHCLCONTEXT *pCtx, uint32_t Format, void **ppv, uint32_t *pcb)
2439{
2440 RT_NOREF(pCtx, Format);
2441 *pcb = g_tstVBoxDataCb;
2442 if (g_tstVBoxDataPv != NULL)
2443 {
2444 void *pv = RTMemDup(g_tstVBoxDataPv, g_tstVBoxDataCb);
2445 *ppv = pv;
2446 return pv != NULL ? g_tstVBoxDataRC : VERR_NO_MEMORY;
2447 }
2448 *ppv = NULL;
2449 return g_tstVBoxDataRC;
2450}
2451
2452Display *XtDisplay(Widget w) { NOREF(w); return (Display *) 0xffff; }
2453
2454void XtAppSetExitFlag(XtAppContext app_context) { NOREF(app_context); }
2455
2456void XtDestroyWidget(Widget w) { NOREF(w); }
2457
2458XtAppContext XtCreateApplicationContext(void) { return (XtAppContext)0xffff; }
2459
2460void XtDestroyApplicationContext(XtAppContext app_context) { NOREF(app_context); }
2461
2462void XtToolkitInitialize(void) {}
2463
2464Boolean XtToolkitThreadInitialize(void) { return True; }
2465
2466Display *XtOpenDisplay(XtAppContext app_context,
2467 _Xconst _XtString display_string,
2468 _Xconst _XtString application_name,
2469 _Xconst _XtString application_class,
2470 XrmOptionDescRec *options, Cardinal num_options,
2471 int *argc, char **argv)
2472{
2473 RT_NOREF8(app_context, display_string, application_name, application_class, options, num_options, argc, argv);
2474 return (Display *)0xffff;
2475}
2476
2477Widget XtVaAppCreateShell(_Xconst _XtString application_name, _Xconst _XtString application_class,
2478 WidgetClass widget_class, Display *display, ...)
2479{
2480 RT_NOREF(application_name, application_class, widget_class, display);
2481 return TEST_WIDGET;
2482}
2483
2484void XtSetMappedWhenManaged(Widget widget, _XtBoolean mapped_when_managed) { RT_NOREF(widget, mapped_when_managed); }
2485
2486void XtRealizeWidget(Widget widget) { NOREF(widget); }
2487
2488XtInputId XtAppAddInput(XtAppContext app_context, int source, XtPointer condition, XtInputCallbackProc proc, XtPointer closure)
2489{
2490 RT_NOREF(app_context, source, condition, proc, closure);
2491 return 0xffff;
2492}
2493
2494/* Atoms we need other than the formats we support. */
2495static const char *g_tstapszSupAtoms[] =
2496{
2497 "PRIMARY", "CLIPBOARD", "TARGETS", "MULTIPLE", "TIMESTAMP"
2498};
2499
2500/* This just looks for the atom names in a couple of tables and returns an
2501 * index with an offset added. */
2502Atom XInternAtom(Display *, const char *pcsz, int)
2503{
2504 Atom atom = 0;
2505 for (unsigned i = 0; i < RT_ELEMENTS(g_aFormats); ++i)
2506 if (!strcmp(pcsz, g_aFormats[i].pcszAtom))
2507 atom = (Atom) (i + 0x1000);
2508 for (unsigned i = 0; i < RT_ELEMENTS(g_tstapszSupAtoms); ++i)
2509 if (!strcmp(pcsz, g_tstapszSupAtoms[i]))
2510 atom = (Atom) (i + 0x2000);
2511 Assert(atom); /* Have we missed any atoms? */
2512 return atom;
2513}
2514
2515/* Take a request for the targets we are currently offering. */
2516static CLIPX11FORMAT g_selTargets[10] = { 0 };
2517static size_t g_cTargets = 0;
2518
2519void tstRequestTargets(CLIPBACKEND* pCtx)
2520{
2521 clipUpdateX11Targets(pCtx, g_selTargets, g_cTargets);
2522}
2523
2524/* The current values of the X selection, which will be returned to the
2525 * XtGetSelectionValue callback. */
2526static Atom g_selType = 0;
2527static const void *g_pSelData = NULL;
2528static unsigned long g_cSelData = 0;
2529static int g_selFormat = 0;
2530
2531void tstClipRequestData(CLIPBACKEND *pCtx, CLIPX11FORMAT target, void *closure)
2532{
2533 RT_NOREF(pCtx);
2534 unsigned long count = 0;
2535 int format = 0;
2536 if (target != g_selTargets[0])
2537 {
2538 clipConvertDataFromX11CallbackWorker(closure, NULL, 0); /* Could not convert to target. */
2539 return;
2540 }
2541 void *pValue = NULL;
2542 pValue = g_pSelData ? RTMemDup(g_pSelData, g_cSelData) : NULL;
2543 count = g_pSelData ? g_cSelData : 0;
2544 format = g_selFormat;
2545 if (!pValue)
2546 {
2547 count = 0;
2548 format = 0;
2549 }
2550 clipConvertDataFromX11CallbackWorker(closure, pValue, count * format / 8);
2551 if (pValue)
2552 RTMemFree(pValue);
2553}
2554
2555/* The formats currently on offer from X11 via the shared clipboard. */
2556static uint32_t g_fX11Formats = 0;
2557
2558DECLCALLBACK(void) ClipReportX11FormatsCallback(SHCLCONTEXT *pCtx, SHCLFORMATS Formats)
2559{
2560 RT_NOREF(pCtx);
2561 g_fX11Formats = Formats;
2562}
2563
2564static uint32_t tstClipQueryFormats(void)
2565{
2566 return g_fX11Formats;
2567}
2568
2569static void tstClipInvalidateFormats(void)
2570{
2571 g_fX11Formats = ~0;
2572}
2573
2574/* Does our clipboard code currently own the selection? */
2575static bool g_ownsSel = false;
2576/* The procedure that is called when we should convert the selection to a
2577 * given format. */
2578static XtConvertSelectionProc g_pfnSelConvert = NULL;
2579/* The procedure which is called when we lose the selection. */
2580static XtLoseSelectionProc g_pfnSelLose = NULL;
2581/* The procedure which is called when the selection transfer has completed. */
2582static XtSelectionDoneProc g_pfnSelDone = NULL;
2583
2584Boolean XtOwnSelection(Widget widget, Atom selection, Time time,
2585 XtConvertSelectionProc convert,
2586 XtLoseSelectionProc lose,
2587 XtSelectionDoneProc done)
2588{
2589 RT_NOREF(widget, time);
2590 if (selection != XInternAtom(NULL, "CLIPBOARD", 0))
2591 return True; /* We don't really care about this. */
2592 g_ownsSel = true; /* Always succeed. */
2593 g_pfnSelConvert = convert;
2594 g_pfnSelLose = lose;
2595 g_pfnSelDone = done;
2596 return True;
2597}
2598
2599void XtDisownSelection(Widget widget, Atom selection, Time time)
2600{
2601 RT_NOREF(widget, time, selection);
2602 g_ownsSel = false;
2603 g_pfnSelConvert = NULL;
2604 g_pfnSelLose = NULL;
2605 g_pfnSelDone = NULL;
2606}
2607
2608/* Request the shared clipboard to convert its data to a given format. */
2609static bool tstClipConvertSelection(const char *pcszTarget, Atom *type,
2610 XtPointer *value, unsigned long *length,
2611 int *format)
2612{
2613 Atom target = XInternAtom(NULL, pcszTarget, 0);
2614 if (target == 0)
2615 return false;
2616 /* Initialise all return values in case we make a quick exit. */
2617 *type = XA_STRING;
2618 *value = NULL;
2619 *length = 0;
2620 *format = 0;
2621 if (!g_ownsSel)
2622 return false;
2623 if (!g_pfnSelConvert)
2624 return false;
2625 Atom clipAtom = XInternAtom(NULL, "CLIPBOARD", 0);
2626 if (!g_pfnSelConvert(TEST_WIDGET, &clipAtom, &target, type,
2627 value, length, format))
2628 return false;
2629 if (g_pfnSelDone)
2630 g_pfnSelDone(TEST_WIDGET, &clipAtom, &target);
2631 return true;
2632}
2633
2634/* Set the current X selection data */
2635static void tstClipSetSelectionValues(const char *pcszTarget, Atom type,
2636 const void *data,
2637 unsigned long count, int format)
2638{
2639 Atom clipAtom = XInternAtom(NULL, "CLIPBOARD", 0);
2640 g_selTargets[0] = clipFindX11FormatByAtomText(pcszTarget);
2641 g_cTargets = 1;
2642 g_selType = type;
2643 g_pSelData = data;
2644 g_cSelData = count;
2645 g_selFormat = format;
2646 if (g_pfnSelLose)
2647 g_pfnSelLose(TEST_WIDGET, &clipAtom);
2648 g_ownsSel = false;
2649}
2650
2651static void tstClipSendTargetUpdate(CLIPBACKEND *pCtx)
2652{
2653 clipQueryX11FormatsCallback(pCtx);
2654}
2655
2656/* Configure if and how the X11 TARGETS clipboard target will fail. */
2657static void tstClipSetTargetsFailure(void)
2658{
2659 g_cTargets = 0;
2660}
2661
2662char *XtMalloc(Cardinal size) { return (char *) RTMemAlloc(size); }
2663
2664char *XGetAtomName(Display *display, Atom atom)
2665{
2666 RT_NOREF(display);
2667 AssertReturn((unsigned)atom < RT_ELEMENTS(g_aFormats) + 1, NULL);
2668 const char *pcszName = NULL;
2669 if (atom < 0x1000)
2670 return NULL;
2671 if (0x1000 <= atom && atom < 0x2000)
2672 {
2673 unsigned index = atom - 0x1000;
2674 AssertReturn(index < RT_ELEMENTS(g_aFormats), NULL);
2675 pcszName = g_aFormats[index].pcszAtom;
2676 }
2677 else
2678 {
2679 unsigned index = atom - 0x2000;
2680 AssertReturn(index < RT_ELEMENTS(g_tstapszSupAtoms), NULL);
2681 pcszName = g_tstapszSupAtoms[index];
2682 }
2683 return (char *)RTMemDup(pcszName, sizeof(pcszName) + 1);
2684}
2685
2686int XFree(void *data)
2687{
2688 RTMemFree(data);
2689 return 0;
2690}
2691
2692void XFreeStringList(char **list)
2693{
2694 if (list)
2695 RTMemFree(*list);
2696 RTMemFree(list);
2697}
2698
2699#define MAX_BUF_SIZE 256
2700
2701static int g_completedRC = VINF_SUCCESS;
2702static int g_completedCB = 0;
2703static CLIPREADCBREQ *g_completedReq = NULL;
2704static char g_completedBuf[MAX_BUF_SIZE];
2705
2706void ClipRequestFromX11CompleteCallback(SHCLCONTEXT *pCtx, int rc, CLIPREADCBREQ *pReq, void *pv, uint32_t cb)
2707{
2708 RT_NOREF(pCtx);
2709 if (cb <= MAX_BUF_SIZE)
2710 {
2711 g_completedRC = rc;
2712 if (cb != 0)
2713 memcpy(g_completedBuf, pv, cb);
2714 }
2715 else
2716 g_completedRC = VERR_BUFFER_OVERFLOW;
2717 g_completedCB = cb;
2718 g_completedReq = pReq;
2719}
2720
2721static void tstClipGetCompletedRequest(int *prc, char ** ppc, uint32_t *pcb, CLIPREADCBREQ **ppReq)
2722{
2723 *prc = g_completedRC;
2724 *ppc = g_completedBuf;
2725 *pcb = g_completedCB;
2726 *ppReq = g_completedReq;
2727}
2728#ifdef RT_OS_SOLARIS_10
2729char XtStrings [] = "";
2730_WidgetClassRec* applicationShellWidgetClass;
2731char XtShellStrings [] = "";
2732int XmbTextPropertyToTextList(
2733 Display* /* display */,
2734 XTextProperty* /* text_prop */,
2735 char*** /* list_return */,
2736 int* /* count_return */
2737)
2738{
2739 return 0;
2740}
2741#else
2742const char XtStrings [] = "";
2743_WidgetClassRec* applicationShellWidgetClass;
2744const char XtShellStrings [] = "";
2745#endif
2746
2747static void tstStringFromX11(RTTEST hTest, CLIPBACKEND *pCtx,
2748 const char *pcszExp, int rcExp)
2749{
2750 bool retval = true;
2751 tstClipSendTargetUpdate(pCtx);
2752 if (tstClipQueryFormats() != VBOX_SHCL_FMT_UNICODETEXT)
2753 RTTestFailed(hTest, "Wrong targets reported: %02X\n",
2754 tstClipQueryFormats());
2755 else
2756 {
2757 char *pc;
2758 CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)&pReq, *pReqRet = NULL;
2759 ClipReadDataFromX11(pCtx, VBOX_SHCL_FMT_UNICODETEXT, pReq);
2760 int rc = VINF_SUCCESS;
2761 uint32_t cbActual = 0;
2762 tstClipGetCompletedRequest(&rc, &pc, &cbActual, &pReqRet);
2763 if (rc != rcExp)
2764 RTTestFailed(hTest, "Wrong return code, expected %Rrc, got %Rrc\n",
2765 rcExp, rc);
2766 else if (pReqRet != pReq)
2767 RTTestFailed(hTest, "Wrong returned request data, expected %p, got %p\n",
2768 pReq, pReqRet);
2769 else if (RT_FAILURE(rcExp))
2770 retval = true;
2771 else
2772 {
2773 RTUTF16 wcExp[MAX_BUF_SIZE / 2];
2774 RTUTF16 *pwcExp = wcExp;
2775 size_t cwc = 0;
2776 rc = RTStrToUtf16Ex(pcszExp, RTSTR_MAX, &pwcExp,
2777 RT_ELEMENTS(wcExp), &cwc);
2778 size_t cbExp = cwc * 2 + 2;
2779 AssertRC(rc);
2780 if (RT_SUCCESS(rc))
2781 {
2782 if (cbActual != cbExp)
2783 {
2784 RTTestFailed(hTest, "Returned string is the wrong size, string \"%.*ls\", size %u, expected \"%s\", size %u\n",
2785 RT_MIN(MAX_BUF_SIZE, cbActual), pc, cbActual,
2786 pcszExp, cbExp);
2787 }
2788 else
2789 {
2790 if (memcmp(pc, wcExp, cbExp) == 0)
2791 retval = true;
2792 else
2793 RTTestFailed(hTest, "Returned string \"%.*ls\" does not match expected string \"%s\"\n",
2794 MAX_BUF_SIZE, pc, pcszExp);
2795 }
2796 }
2797 }
2798 }
2799 if (!retval)
2800 RTTestFailureDetails(hTest, "Expected: string \"%s\", rc %Rrc\n",
2801 pcszExp, rcExp);
2802}
2803
2804static void tstLatin1FromX11(RTTEST hTest, CLIPBACKEND *pCtx,
2805 const char *pcszExp, int rcExp)
2806{
2807 bool retval = false;
2808 tstClipSendTargetUpdate(pCtx);
2809 if (tstClipQueryFormats() != VBOX_SHCL_FMT_UNICODETEXT)
2810 RTTestFailed(hTest, "Wrong targets reported: %02X\n",
2811 tstClipQueryFormats());
2812 else
2813 {
2814 char *pc;
2815 CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)&pReq, *pReqRet = NULL;
2816 ClipReadDataFromX11(pCtx, VBOX_SHCL_FMT_UNICODETEXT, pReq);
2817 int rc = VINF_SUCCESS;
2818 uint32_t cbActual = 0;
2819 tstClipGetCompletedRequest(&rc, &pc, &cbActual, &pReqRet);
2820 if (rc != rcExp)
2821 RTTestFailed(hTest, "Wrong return code, expected %Rrc, got %Rrc\n",
2822 rcExp, rc);
2823 else if (pReqRet != pReq)
2824 RTTestFailed(hTest, "Wrong returned request data, expected %p, got %p\n",
2825 pReq, pReqRet);
2826 else if (RT_FAILURE(rcExp))
2827 retval = true;
2828 else
2829 {
2830 RTUTF16 wcExp[MAX_BUF_SIZE / 2];
2831 //RTUTF16 *pwcExp = wcExp; - unused
2832 size_t cwc;
2833 for (cwc = 0; cwc == 0 || pcszExp[cwc - 1] != '\0'; ++cwc)
2834 wcExp[cwc] = pcszExp[cwc];
2835 size_t cbExp = cwc * 2;
2836 if (cbActual != cbExp)
2837 {
2838 RTTestFailed(hTest, "Returned string is the wrong size, string \"%.*ls\", size %u, expected \"%s\", size %u\n",
2839 RT_MIN(MAX_BUF_SIZE, cbActual), pc, cbActual,
2840 pcszExp, cbExp);
2841 }
2842 else
2843 {
2844 if (memcmp(pc, wcExp, cbExp) == 0)
2845 retval = true;
2846 else
2847 RTTestFailed(hTest, "Returned string \"%.*ls\" does not match expected string \"%s\"\n",
2848 MAX_BUF_SIZE, pc, pcszExp);
2849 }
2850 }
2851 }
2852 if (!retval)
2853 RTTestFailureDetails(hTest, "Expected: string \"%s\", rc %Rrc\n",
2854 pcszExp, rcExp);
2855}
2856
2857static void tstStringFromVBox(RTTEST hTest, CLIPBACKEND *pCtx, const char *pcszTarget, Atom typeExp, const char *valueExp)
2858{
2859 RT_NOREF(pCtx);
2860 bool retval = false;
2861 Atom type;
2862 XtPointer value = NULL;
2863 unsigned long length;
2864 int format;
2865 size_t lenExp = strlen(valueExp);
2866 if (tstClipConvertSelection(pcszTarget, &type, &value, &length, &format))
2867 {
2868 if ( type != typeExp
2869 || length != lenExp
2870 || format != 8
2871 || memcmp((const void *) value, (const void *)valueExp,
2872 lenExp))
2873 {
2874 RTTestFailed(hTest, "Bad data: type %d, (expected %d), length %u, (%u), format %d (%d), value \"%.*s\" (\"%.*s\")\n",
2875 type, typeExp, length, lenExp, format, 8,
2876 RT_MIN(length, 20), value, RT_MIN(lenExp, 20), valueExp);
2877 }
2878 else
2879 retval = true;
2880 }
2881 else
2882 RTTestFailed(hTest, "Conversion failed\n");
2883 XtFree((char *)value);
2884 if (!retval)
2885 RTTestFailureDetails(hTest, "Conversion to %s, expected \"%s\"\n",
2886 pcszTarget, valueExp);
2887}
2888
2889static void tstNoX11(CLIPBACKEND *pCtx, const char *pcszTestCtx)
2890{
2891 CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)&pReq;
2892 int rc = ClipReadDataFromX11(pCtx, VBOX_SHCL_FMT_UNICODETEXT, pReq);
2893 RTTESTI_CHECK_MSG(rc == VERR_NO_DATA, ("context: %s\n", pcszTestCtx));
2894}
2895
2896static void tstStringFromVBoxFailed(RTTEST hTest, CLIPBACKEND *pCtx, const char *pcszTarget)
2897{
2898 RT_NOREF(pCtx);
2899 Atom type;
2900 XtPointer value = NULL;
2901 unsigned long length;
2902 int format;
2903 RTTEST_CHECK_MSG(hTest, !tstClipConvertSelection(pcszTarget, &type, &value,
2904 &length, &format),
2905 (hTest, "Conversion to target %s, should have failed but didn't, returned type %d, length %u, format %d, value \"%.*s\"\n",
2906 pcszTarget, type, length, format, RT_MIN(length, 20),
2907 value));
2908 XtFree((char *)value);
2909}
2910
2911static void tstNoSelectionOwnership(CLIPBACKEND *pCtx, const char *pcszTestCtx)
2912{
2913 RT_NOREF(pCtx);
2914 RTTESTI_CHECK_MSG(!g_ownsSel, ("context: %s\n", pcszTestCtx));
2915}
2916
2917static void tstBadFormatRequestFromHost(RTTEST hTest, CLIPBACKEND *pCtx)
2918{
2919 tstClipSetSelectionValues("UTF8_STRING", XA_STRING, "hello world",
2920 sizeof("hello world"), 8);
2921 tstClipSendTargetUpdate(pCtx);
2922 if (tstClipQueryFormats() != VBOX_SHCL_FMT_UNICODETEXT)
2923 RTTestFailed(hTest, "Wrong targets reported: %02X\n",
2924 tstClipQueryFormats());
2925 else
2926 {
2927 char *pc;
2928 CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)&pReq, *pReqRet = NULL;
2929 ClipReadDataFromX11(pCtx, 100, pReq); /* Bad format. */
2930 int rc = VINF_SUCCESS;
2931 uint32_t cbActual = 0;
2932 tstClipGetCompletedRequest(&rc, &pc, &cbActual, &pReqRet);
2933 if (rc != VERR_NOT_IMPLEMENTED)
2934 RTTestFailed(hTest, "Wrong return code, expected VERR_NOT_IMPLEMENTED, got %Rrc\n",
2935 rc);
2936 tstClipSetSelectionValues("", XA_STRING, "", sizeof(""), 8);
2937 tstClipSendTargetUpdate(pCtx);
2938 if (tstClipQueryFormats() == VBOX_SHCL_FMT_UNICODETEXT)
2939 RTTestFailed(hTest, "Failed to report targets after bad host request.\n");
2940 }
2941}
2942
2943int main()
2944{
2945 /*
2946 * Init the runtime, test and say hello.
2947 */
2948 RTTEST hTest;
2949 int rc = RTTestInitAndCreate("tstClipboardX11", &hTest);
2950 if (rc)
2951 return rc;
2952 RTTestBanner(hTest);
2953
2954 /*
2955 * Run the test.
2956 */
2957 CLIPBACKEND *pCtx = ClipConstructX11(NULL, false);
2958 char *pc;
2959 uint32_t cbActual;
2960 CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)&pReq, *pReqRet = NULL;
2961 rc = ClipStartX11(pCtx, false /* fGrab */);
2962 AssertRCReturn(rc, 1);
2963
2964 /*** UTF-8 from X11 ***/
2965 RTTestSub(hTest, "reading UTF-8 from X11");
2966 /* Simple test */
2967 tstClipSetSelectionValues("UTF8_STRING", XA_STRING, "hello world",
2968 sizeof("hello world"), 8);
2969 tstStringFromX11(hTest, pCtx, "hello world", VINF_SUCCESS);
2970 /* With an embedded carriage return */
2971 tstClipSetSelectionValues("text/plain;charset=UTF-8", XA_STRING,
2972 "hello\nworld", sizeof("hello\nworld"), 8);
2973 tstStringFromX11(hTest, pCtx, "hello\r\nworld", VINF_SUCCESS);
2974 /* With an embedded CRLF */
2975 tstClipSetSelectionValues("text/plain;charset=UTF-8", XA_STRING,
2976 "hello\r\nworld", sizeof("hello\r\nworld"), 8);
2977 tstStringFromX11(hTest, pCtx, "hello\r\r\nworld", VINF_SUCCESS);
2978 /* With an embedded LFCR */
2979 tstClipSetSelectionValues("text/plain;charset=UTF-8", XA_STRING,
2980 "hello\n\rworld", sizeof("hello\n\rworld"), 8);
2981 tstStringFromX11(hTest, pCtx, "hello\r\n\rworld", VINF_SUCCESS);
2982 /* An empty string */
2983 tstClipSetSelectionValues("text/plain;charset=utf-8", XA_STRING, "",
2984 sizeof(""), 8);
2985 tstStringFromX11(hTest, pCtx, "", VINF_SUCCESS);
2986 /* With an embedded UTF-8 character. */
2987 tstClipSetSelectionValues("STRING", XA_STRING,
2988 "100\xE2\x82\xAC" /* 100 Euro */,
2989 sizeof("100\xE2\x82\xAC"), 8);
2990 tstStringFromX11(hTest, pCtx, "100\xE2\x82\xAC", VINF_SUCCESS);
2991 /* A non-zero-terminated string */
2992 tstClipSetSelectionValues("TEXT", XA_STRING,
2993 "hello world", sizeof("hello world") - 1, 8);
2994 tstStringFromX11(hTest, pCtx, "hello world", VINF_SUCCESS);
2995
2996 /*** Latin1 from X11 ***/
2997 RTTestSub(hTest, "reading Latin1 from X11");
2998 /* Simple test */
2999 tstClipSetSelectionValues("STRING", XA_STRING, "Georges Dupr\xEA",
3000 sizeof("Georges Dupr\xEA"), 8);
3001 tstLatin1FromX11(hTest, pCtx, "Georges Dupr\xEA", VINF_SUCCESS);
3002 /* With an embedded carriage return */
3003 tstClipSetSelectionValues("TEXT", XA_STRING, "Georges\nDupr\xEA",
3004 sizeof("Georges\nDupr\xEA"), 8);
3005 tstLatin1FromX11(hTest, pCtx, "Georges\r\nDupr\xEA", VINF_SUCCESS);
3006 /* With an embedded CRLF */
3007 tstClipSetSelectionValues("TEXT", XA_STRING, "Georges\r\nDupr\xEA",
3008 sizeof("Georges\r\nDupr\xEA"), 8);
3009 tstLatin1FromX11(hTest, pCtx, "Georges\r\r\nDupr\xEA", VINF_SUCCESS);
3010 /* With an embedded LFCR */
3011 tstClipSetSelectionValues("TEXT", XA_STRING, "Georges\n\rDupr\xEA",
3012 sizeof("Georges\n\rDupr\xEA"), 8);
3013 tstLatin1FromX11(hTest, pCtx, "Georges\r\n\rDupr\xEA", VINF_SUCCESS);
3014 /* A non-zero-terminated string */
3015 tstClipSetSelectionValues("text/plain", XA_STRING,
3016 "Georges Dupr\xEA!",
3017 sizeof("Georges Dupr\xEA!") - 1, 8);
3018 tstLatin1FromX11(hTest, pCtx, "Georges Dupr\xEA!", VINF_SUCCESS);
3019
3020 /*** Unknown X11 format ***/
3021 RTTestSub(hTest, "handling of an unknown X11 format");
3022 tstClipInvalidateFormats();
3023 tstClipSetSelectionValues("CLIPBOARD", XA_STRING, "Test",
3024 sizeof("Test"), 8);
3025 tstClipSendTargetUpdate(pCtx);
3026 RTTEST_CHECK_MSG(hTest, tstClipQueryFormats() == 0,
3027 (hTest, "Failed to send a format update notification\n"));
3028
3029 /*** Timeout from X11 ***/
3030 RTTestSub(hTest, "X11 timeout");
3031 tstClipSetSelectionValues("UTF8_STRING", XT_CONVERT_FAIL, NULL,0, 8);
3032 tstStringFromX11(hTest, pCtx, "", VERR_NO_DATA);
3033
3034 /*** No data in X11 clipboard ***/
3035 RTTestSub(hTest, "a data request from an empty X11 clipboard");
3036 tstClipSetSelectionValues("UTF8_STRING", XA_STRING, NULL,
3037 0, 8);
3038 ClipReadDataFromX11(pCtx, VBOX_SHCL_FMT_UNICODETEXT, pReq);
3039 tstClipGetCompletedRequest(&rc, &pc, &cbActual, &pReqRet);
3040 RTTEST_CHECK_MSG(hTest, rc == VERR_NO_DATA,
3041 (hTest, "Returned %Rrc instead of VERR_NO_DATA\n",
3042 rc));
3043 RTTEST_CHECK_MSG(hTest, pReqRet == pReq,
3044 (hTest, "Wrong returned request data, expected %p, got %p\n",
3045 pReq, pReqRet));
3046
3047 /*** Ensure that VBox is notified when we return the CB to X11 ***/
3048 RTTestSub(hTest, "notification of switch to X11 clipboard");
3049 tstClipInvalidateFormats();
3050 clipReportEmptyX11CB(pCtx);
3051 RTTEST_CHECK_MSG(hTest, tstClipQueryFormats() == 0,
3052 (hTest, "Failed to send a format update (release) notification\n"));
3053
3054 /*** request for an invalid VBox format from X11 ***/
3055 RTTestSub(hTest, "a request for an invalid VBox format from X11");
3056 ClipReadDataFromX11(pCtx, 0xffff, pReq);
3057 tstClipGetCompletedRequest(&rc, &pc, &cbActual, &pReqRet);
3058 RTTEST_CHECK_MSG(hTest, rc == VERR_NOT_IMPLEMENTED,
3059 (hTest, "Returned %Rrc instead of VERR_NOT_IMPLEMENTED\n",
3060 rc));
3061 RTTEST_CHECK_MSG(hTest, pReqRet == pReq,
3062 (hTest, "Wrong returned request data, expected %p, got %p\n",
3063 pReq, pReqRet));
3064
3065 /*** Targets failure from X11 ***/
3066 RTTestSub(hTest, "X11 targets conversion failure");
3067 tstClipSetSelectionValues("UTF8_STRING", XA_STRING, "hello world",
3068 sizeof("hello world"), 8);
3069 tstClipSetTargetsFailure();
3070 Atom atom = XA_STRING;
3071 long unsigned int cLen = 0;
3072 int format = 8;
3073 clipConvertX11TargetsCallback(NULL, (XtPointer) pCtx, NULL, &atom, NULL, &cLen,
3074 &format);
3075 RTTEST_CHECK_MSG(hTest, tstClipQueryFormats() == 0,
3076 (hTest, "Wrong targets reported: %02X\n",
3077 tstClipQueryFormats()));
3078
3079 /*** X11 text format conversion ***/
3080 RTTestSub(hTest, "handling of X11 selection targets");
3081 RTTEST_CHECK_MSG(hTest, tstClipTextFormatConversion(pCtx),
3082 (hTest, "failed to select the right X11 text formats\n"));
3083
3084 /*** UTF-8 from VBox ***/
3085 RTTestSub(hTest, "reading UTF-8 from VBox");
3086 /* Simple test */
3087 tstClipSetVBoxUtf16(pCtx, VINF_SUCCESS, "hello world",
3088 sizeof("hello world") * 2);
3089 tstStringFromVBox(hTest, pCtx, "UTF8_STRING",
3090 clipGetAtom(pCtx, "UTF8_STRING"), "hello world");
3091 /* With an embedded carriage return */
3092 tstClipSetVBoxUtf16(pCtx, VINF_SUCCESS, "hello\r\nworld",
3093 sizeof("hello\r\nworld") * 2);
3094 tstStringFromVBox(hTest, pCtx, "text/plain;charset=UTF-8",
3095 clipGetAtom(pCtx, "text/plain;charset=UTF-8"),
3096 "hello\nworld");
3097 /* With an embedded CRCRLF */
3098 tstClipSetVBoxUtf16(pCtx, VINF_SUCCESS, "hello\r\r\nworld",
3099 sizeof("hello\r\r\nworld") * 2);
3100 tstStringFromVBox(hTest, pCtx, "text/plain;charset=UTF-8",
3101 clipGetAtom(pCtx, "text/plain;charset=UTF-8"),
3102 "hello\r\nworld");
3103 /* With an embedded CRLFCR */
3104 tstClipSetVBoxUtf16(pCtx, VINF_SUCCESS, "hello\r\n\rworld",
3105 sizeof("hello\r\n\rworld") * 2);
3106 tstStringFromVBox(hTest, pCtx, "text/plain;charset=UTF-8",
3107 clipGetAtom(pCtx, "text/plain;charset=UTF-8"),
3108 "hello\n\rworld");
3109 /* An empty string */
3110 tstClipSetVBoxUtf16(pCtx, VINF_SUCCESS, "", 2);
3111 tstStringFromVBox(hTest, pCtx, "text/plain;charset=utf-8",
3112 clipGetAtom(pCtx, "text/plain;charset=utf-8"), "");
3113 /* With an embedded UTF-8 character. */
3114 tstClipSetVBoxUtf16(pCtx, VINF_SUCCESS, "100\xE2\x82\xAC" /* 100 Euro */,
3115 10);
3116 tstStringFromVBox(hTest, pCtx, "STRING",
3117 clipGetAtom(pCtx, "STRING"), "100\xE2\x82\xAC");
3118 /* A non-zero-terminated string */
3119 tstClipSetVBoxUtf16(pCtx, VINF_SUCCESS, "hello world",
3120 sizeof("hello world") * 2 - 2);
3121 tstStringFromVBox(hTest, pCtx, "TEXT", clipGetAtom(pCtx, "TEXT"),
3122 "hello world");
3123
3124 /*** Timeout from VBox ***/
3125 RTTestSub(hTest, "reading from VBox with timeout");
3126 tstClipEmptyVBox(pCtx, VERR_TIMEOUT);
3127 tstStringFromVBoxFailed(hTest, pCtx, "UTF8_STRING");
3128
3129 /*** No data in VBox clipboard ***/
3130 RTTestSub(hTest, "an empty VBox clipboard");
3131 tstClipSetSelectionValues("TEXT", XA_STRING, "", sizeof(""), 8);
3132 tstClipEmptyVBox(pCtx, VINF_SUCCESS);
3133 RTTEST_CHECK_MSG(hTest, g_ownsSel,
3134 (hTest, "VBox grabbed the clipboard with no data and we ignored it\n"));
3135 tstStringFromVBoxFailed(hTest, pCtx, "UTF8_STRING");
3136
3137 /*** An unknown VBox format ***/
3138 RTTestSub(hTest, "reading an unknown VBox format");
3139 tstClipSetSelectionValues("TEXT", XA_STRING, "", sizeof(""), 8);
3140 tstClipSetVBoxUtf16(pCtx, VINF_SUCCESS, "", 2);
3141 ClipAnnounceFormatToX11(pCtx, 0xa0000);
3142 RTTEST_CHECK_MSG(hTest, g_ownsSel,
3143 (hTest, "VBox grabbed the clipboard with unknown data and we ignored it\n"));
3144 tstStringFromVBoxFailed(hTest, pCtx, "UTF8_STRING");
3145
3146 /*** VBox requests a bad format ***/
3147 RTTestSub(hTest, "recovery from a bad format request");
3148 tstBadFormatRequestFromHost(hTest, pCtx);
3149
3150 rc = ClipStopX11(pCtx);
3151 AssertRCReturn(rc, 1);
3152 ClipDestructX11(pCtx);
3153
3154 /*** Headless clipboard tests ***/
3155
3156 pCtx = ClipConstructX11(NULL, true);
3157 rc = ClipStartX11(pCtx, false /* fGrab */);
3158 AssertRCReturn(rc, 1);
3159
3160 /*** Read from X11 ***/
3161 RTTestSub(hTest, "reading from X11, headless clipboard");
3162 /* Simple test */
3163 tstClipSetVBoxUtf16(pCtx, VINF_SUCCESS, "",
3164 sizeof("") * 2);
3165 tstClipSetSelectionValues("UTF8_STRING", XA_STRING, "hello world",
3166 sizeof("hello world"), 8);
3167 tstNoX11(pCtx, "reading from X11, headless clipboard");
3168
3169 /*** Read from VBox ***/
3170 RTTestSub(hTest, "reading from VBox, headless clipboard");
3171 /* Simple test */
3172 tstClipEmptyVBox(pCtx, VERR_WRONG_ORDER);
3173 tstClipSetSelectionValues("TEXT", XA_STRING, "", sizeof(""), 8);
3174 tstClipSetVBoxUtf16(pCtx, VINF_SUCCESS, "hello world",
3175 sizeof("hello world") * 2);
3176 tstNoSelectionOwnership(pCtx, "reading from VBox, headless clipboard");
3177
3178 rc = ClipStopX11(pCtx);
3179 AssertRCReturn(rc, 1);
3180 ClipDestructX11(pCtx);
3181
3182 return RTTestSummaryAndDestroy(hTest);
3183}
3184
3185#endif
3186
3187#ifdef SMOKETEST
3188
3189/* This is a simple test case that just starts a copy of the X11 clipboard
3190 * backend, checks the X11 clipboard and exits. If ever needed I will add an
3191 * interactive mode in which the user can read and copy to the clipboard from
3192 * the command line. */
3193
3194# include <iprt/env.h>
3195# include <iprt/test.h>
3196
3197DECLCALLBACK(int) ClipRequestDataForX11Callback(SHCLCONTEXT *pCtx, SHCLFORMAT Format, void **ppv, uint32_t *pcb)
3198{
3199 RT_NOREF(pCtx, Format, ppv, pcb);
3200 return VERR_NO_DATA;
3201}
3202
3203DECLCALLBACK(void) ClipReportX11FormatsCallback(SHCLCONTEXT *pCtx, SHCLFORMATS Formats)
3204{
3205 RT_NOREF(pCtx, Formats);
3206}
3207
3208DECLCALLBACK(void) ClipRequestFromX11CompleteCallback(SHCLCONTEXT *pCtx, int rc, CLIPREADCBREQ *pReq, void *pv, uint32_t cb)
3209{
3210 RT_NOREF(pCtx, rc, pReq, pv, cb);
3211}
3212
3213int main()
3214{
3215 /*
3216 * Init the runtime, test and say hello.
3217 */
3218 RTTEST hTest;
3219 int rc = RTTestInitAndCreate("tstClipboardX11Smoke", &hTest);
3220 if (rc)
3221 return rc;
3222 RTTestBanner(hTest);
3223
3224 /*
3225 * Run the test.
3226 */
3227 rc = VINF_SUCCESS;
3228 /* We can't test anything without an X session, so just return success
3229 * in that case. */
3230 if (!RTEnvExist("DISPLAY"))
3231 {
3232 RTTestPrintf(hTest, RTTESTLVL_INFO,
3233 "X11 not available, not running test\n");
3234 return RTTestSummaryAndDestroy(hTest);
3235 }
3236 CLIPBACKEND *pCtx = ClipConstructX11(NULL, false);
3237 AssertReturn(pCtx, 1);
3238 rc = ClipStartX11(pCtx, false /* fGrab */);
3239 AssertRCReturn(rc, 1);
3240 /* Give the clipboard time to synchronise. */
3241 RTThreadSleep(500);
3242 rc = ClipStopX11(pCtx);
3243 AssertRCReturn(rc, 1);
3244 ClipDestructX11(pCtx);
3245 return RTTestSummaryAndDestroy(hTest);
3246}
3247
3248#endif /* SMOKETEST defined */
3249
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