VirtualBox

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

Last change on this file since 82303 was 82279, checked in by vboxsync, 5 years ago

Shared Clipboard: Resolved some more @todos.

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

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette