VirtualBox

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

Last change on this file since 82894 was 82507, checked in by vboxsync, 5 years ago

VBoxTray,VBoxClient: Use '&' rather than '==' when testing format requested in VBOX_SHCL_HOST_MSG_READ_DATA, as darwin hosts may (incorrectly) request more than one format. bugref:9437

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 109.8 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#if !defined(SMOKETEST) && !defined(TESTCASE)
1060 /* Smoktests / Testcases don't have a (valid) parent. */
1061 AssertPtrReturn(pParent, VERR_INVALID_POINTER);
1062#endif
1063
1064 RT_BZERO(pCtx, sizeof(SHCLX11CTX));
1065
1066 if (fHeadless)
1067 {
1068 /*
1069 * If we don't find the DISPLAY environment variable we assume that
1070 * we are not connected to an X11 server. Don't actually try to do
1071 * this then, just fail silently and report success on every call.
1072 * This is important for VBoxHeadless.
1073 */
1074 LogRel(("Shared Clipboard: X11 DISPLAY variable not set -- disabling clipboard sharing\n"));
1075 }
1076
1077 pCtx->fHaveX11 = !fHeadless;
1078 pCtx->pFrontend = pParent;
1079
1080 pCtx->fXtBusy = false;
1081 pCtx->fXtNeedsUpdate = false;
1082
1083 LogFlowFuncLeaveRC(VINF_SUCCESS);
1084 return VINF_SUCCESS;
1085}
1086
1087/**
1088 * Destructs the Shared Clipboard X11 context.
1089 *
1090 * @param pCtx The X11 clipboard context to destroy.
1091 */
1092void ShClX11Destroy(PSHCLX11CTX pCtx)
1093{
1094 if (!pCtx)
1095 return;
1096
1097 if (pCtx->fHaveX11)
1098 {
1099 /* We set this to NULL when the event thread exits. It really should
1100 * have exited at this point, when we are about to unload the code from
1101 * memory. */
1102 Assert(pCtx->pWidget == NULL);
1103 }
1104}
1105
1106/**
1107 * Announces to the X11 backend that we are ready to start.
1108 *
1109 * @returns VBox status code.
1110 * @param pCtx The X11 clipboard context to use.
1111 * @param fGrab Whether we should try to grab the shared clipboard at once.
1112 */
1113int ShClX11ThreadStart(PSHCLX11CTX pCtx, bool fGrab)
1114{
1115 int rc = VINF_SUCCESS;
1116
1117 /*
1118 * Immediately return if we are not connected to the X server.
1119 */
1120 if (!pCtx->fHaveX11)
1121 return VINF_SUCCESS;
1122
1123 rc = clipInit(pCtx);
1124 if (RT_SUCCESS(rc))
1125 {
1126 clipResetX11Formats(pCtx);
1127 pCtx->fGrabClipboardOnStart = fGrab;
1128 }
1129#ifndef TESTCASE
1130 if (RT_SUCCESS(rc))
1131 {
1132 LogRel2(("Shared Clipboard: Starting X11 event thread ...\n"));
1133
1134 rc = RTThreadCreate(&pCtx->Thread, clipEventThread, pCtx, 0,
1135 RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "SHCLX11");
1136 if (RT_FAILURE(rc))
1137 {
1138 LogRel(("Shared Clipboard: Failed to start the X11 event thread with %Rrc\n", rc));
1139 clipUninit(pCtx);
1140 }
1141 }
1142#endif
1143 return rc;
1144}
1145
1146/**
1147 * Shuts down the shared clipboard X11 backend.
1148 *
1149 * @note Any requests from this object to get clipboard data from VBox
1150 * *must* have completed or aborted before we are called, as
1151 * otherwise the X11 event loop will still be waiting for the request
1152 * to return and will not be able to terminate.
1153 *
1154 * @returns VBox status code.
1155 * @param pCtx The X11 clipboard context to use.
1156 */
1157int ShClX11ThreadStop(PSHCLX11CTX pCtx)
1158{
1159 int rc, rcThread;
1160 unsigned count = 0;
1161 /*
1162 * Immediately return if we are not connected to the X server.
1163 */
1164 if (!pCtx->fHaveX11)
1165 return VINF_SUCCESS;
1166
1167 LogRel(("Shared Clipboard: Stopping X11 event thread ...\n"));
1168
1169 /* Write to the "stop" pipe. */
1170 clipQueueToEventThread(pCtx, clipStopEventThreadWorker, (XtPointer)pCtx);
1171
1172#ifndef TESTCASE
1173 do
1174 {
1175 rc = RTThreadWait(pCtx->Thread, 1000, &rcThread);
1176 ++count;
1177 Assert(RT_SUCCESS(rc) || ((VERR_TIMEOUT == rc) && (count != 5)));
1178 } while ((VERR_TIMEOUT == rc) && (count < 300));
1179#else
1180 rc = VINF_SUCCESS;
1181 rcThread = VINF_SUCCESS;
1182 RT_NOREF_PV(count);
1183#endif
1184 if (RT_SUCCESS(rc))
1185 {
1186 AssertRC(rcThread);
1187 }
1188 else
1189 LogRel(("Shared Clipboard: Stopping X11 event thread failed with %Rrc\n", rc));
1190
1191 clipUninit(pCtx);
1192
1193 RT_NOREF_PV(rcThread);
1194 return rc;
1195}
1196
1197/**
1198 * Satisfies a request from X11 for clipboard targets supported by VBox.
1199 *
1200 * @returns VBox status code.
1201 * @param pCtx The X11 clipboard context to use.
1202 * @param atomTypeReturn The type of the data we are returning.
1203 * @param pValReturn A pointer to the data we are returning. This
1204 * should be set to memory allocated by XtMalloc,
1205 * which will be freed later by the Xt toolkit.
1206 * @param pcLenReturn The length of the data we are returning.
1207 * @param piFormatReturn The format (8bit, 16bit, 32bit) of the data we are
1208 * returning.
1209 * @note X11 backend code, called by the XtOwnSelection callback.
1210 */
1211static int clipCreateX11Targets(PSHCLX11CTX pCtx, Atom *atomTypeReturn,
1212 XtPointer *pValReturn,
1213 unsigned long *pcLenReturn,
1214 int *piFormatReturn)
1215{
1216 LogFlowFuncEnter();
1217
1218 const unsigned cFixedTargets = 3;
1219
1220 Atom *atomTargets = (Atom *)XtMalloc((MAX_CLIP_X11_FORMATS + cFixedTargets) * sizeof(Atom));
1221 unsigned cTargets = 0;
1222 SHCLX11FMTIDX format = NIL_CLIPX11FORMAT;
1223 do
1224 {
1225 format = clipEnumX11Formats(pCtx->vboxFormats, format);
1226 if (format != NIL_CLIPX11FORMAT)
1227 {
1228 atomTargets[cTargets] = clipAtomForX11Format(pCtx, format);
1229 ++cTargets;
1230 }
1231 } while (format != NIL_CLIPX11FORMAT);
1232
1233 /* We always offer these fixed targets. */
1234 atomTargets[cTargets] = clipGetAtom(pCtx, "TARGETS");
1235 atomTargets[cTargets + 1] = clipGetAtom(pCtx, "MULTIPLE");
1236 atomTargets[cTargets + 2] = clipGetAtom(pCtx, "TIMESTAMP");
1237
1238 *atomTypeReturn = XA_ATOM;
1239 *pValReturn = (XtPointer)atomTargets;
1240 *pcLenReturn = cTargets + cFixedTargets;
1241 *piFormatReturn = 32;
1242
1243 return VINF_SUCCESS;
1244}
1245
1246/**
1247 * This is a wrapper around ShClX11RequestDataForX11Callback that will cache the
1248 * data returned.
1249 */
1250static int clipReadVBoxShCl(PSHCLX11CTX pCtx, SHCLFORMAT Format,
1251 void **ppv, uint32_t *pcb)
1252{
1253 LogFlowFunc(("pCtx=%p, Format=%02X, ppv=%p, pcb=%p\n", pCtx, Format, ppv, pcb));
1254
1255 int rc = VINF_SUCCESS;
1256
1257 if (Format == VBOX_SHCL_FMT_UNICODETEXT)
1258 {
1259 if (pCtx->pvUnicodeCache == NULL)
1260 rc = ShClX11RequestDataForX11Callback(pCtx->pFrontend, Format,
1261 &pCtx->pvUnicodeCache,
1262 &pCtx->cbUnicodeCache);
1263 if (RT_SUCCESS(rc))
1264 {
1265 AssertPtrReturn(pCtx->pvUnicodeCache, VERR_INVALID_POINTER);
1266 AssertReturn (pCtx->cbUnicodeCache, VERR_INVALID_PARAMETER);
1267
1268 *ppv = RTMemDup(pCtx->pvUnicodeCache, pCtx->cbUnicodeCache);
1269 *pcb = pCtx->cbUnicodeCache;
1270 if (*ppv == NULL)
1271 rc = VERR_NO_MEMORY;
1272 }
1273 }
1274 else
1275 rc = ShClX11RequestDataForX11Callback(pCtx->pFrontend, Format,
1276 ppv, pcb);
1277 if (RT_SUCCESS(rc))
1278 LogFlowFunc(("*ppv=%.*ls, *pcb=%u\n", *pcb, *ppv, *pcb));
1279
1280 LogFlowFuncLeaveRC(rc);
1281 return rc;
1282}
1283
1284/**
1285 * Calculates a buffer size large enough to hold the source Windows format
1286 * text converted into Unix Utf8, including the null terminator.
1287 *
1288 * @returns VBox status code.
1289 * @param pwsz The source text in UCS-2 with Windows EOLs.
1290 * @param cwc The size in USC-2 elements of the source text, with or
1291 * without the terminator.
1292 * @param pcbActual Where to store the buffer size needed.
1293 */
1294static int clipWinTxtBufSizeForUtf8(PRTUTF16 pwsz, size_t cwc,
1295 size_t *pcbActual)
1296{
1297 size_t cbRet = 0;
1298 int rc = RTUtf16CalcUtf8LenEx(pwsz, cwc, &cbRet);
1299 if (RT_SUCCESS(rc))
1300 *pcbActual = cbRet + 1; /* null terminator */
1301 return rc;
1302}
1303
1304/**
1305 * Converts text from Windows format (UCS-2 with CRLF line endings) to standard UTF-8.
1306 *
1307 * @returns VBox status code.
1308 * @param pwszSrc The text to be converted.
1309 * @param cbSrc The length of @a pwszSrc in bytes.
1310 * @param pszBuf Where to write the converted string.
1311 * @param cbBuf The size of the buffer pointed to by @a pszBuf.
1312 * @param pcbActual Where to store the size of the converted string.
1313 * optional.
1314 */
1315static int clipWinTxtToUtf8(PRTUTF16 pwszSrc, size_t cbSrc, char *pszBuf,
1316 size_t cbBuf, size_t *pcbActual)
1317{
1318 PRTUTF16 pwszTmp = NULL;
1319 size_t cwSrc = cbSrc / 2, cwTmp = 0, cbDest = 0;
1320 int rc = VINF_SUCCESS;
1321
1322 LogFlowFunc (("pwszSrc=%.*ls, cbSrc=%u\n", cbSrc, pwszSrc, cbSrc));
1323 /* How long will the converted text be? */
1324 AssertPtr(pwszSrc);
1325 AssertPtr(pszBuf);
1326 rc = ShClUtf16GetLinSize(pwszSrc, cwSrc, &cwTmp);
1327 if (RT_SUCCESS(rc) && cwTmp == 0)
1328 rc = VERR_NO_DATA;
1329 if (RT_SUCCESS(rc))
1330 pwszTmp = (PRTUTF16)RTMemAlloc(cwTmp * 2);
1331 if (!pwszTmp)
1332 rc = VERR_NO_MEMORY;
1333 /* Convert the text. */
1334 if (RT_SUCCESS(rc))
1335 rc = ShClUtf16WinToLin(pwszSrc, cwSrc, pwszTmp, cwTmp);
1336 if (RT_SUCCESS(rc))
1337 {
1338 /* Convert the UTF-16 string to Utf8. */
1339 rc = RTUtf16ToUtf8Ex(pwszTmp + 1, cwTmp - 1, &pszBuf, cbBuf,
1340 &cbDest);
1341 }
1342 RTMemFree(reinterpret_cast<void *>(pwszTmp));
1343 if (pcbActual)
1344 *pcbActual = cbDest + 1;
1345
1346 if (RT_SUCCESS(rc))
1347 LogFlowFunc (("converted string is %.*s. Returning.\n", cbDest, pszBuf));
1348
1349 LogFlowFuncLeaveRC(rc);
1350 return rc;
1351}
1352
1353/**
1354 * Satisfies a request from X11 to convert the clipboard text to UTF-8. We
1355 * return null-terminated text, but can cope with non-null-terminated input.
1356 *
1357 * @returns VBox status code.
1358 * @param pDisplay An X11 display structure, needed for conversions
1359 * performed by Xlib.
1360 * @param pv The text to be converted (UCS-2 with Windows EOLs).
1361 * @param cb The length of the text in @cb in bytes.
1362 * @param atomTypeReturn Where to store the atom for the type of the data
1363 * we are returning.
1364 * @param pValReturn Where to store the pointer to the data we are
1365 * returning. This should be to memory allocated by
1366 * XtMalloc, which will be freed by the Xt toolkit
1367 * later.
1368 * @param pcLenReturn Where to store the length of the data we are
1369 * returning.
1370 * @param piFormatReturn Where to store the bit width (8, 16, 32) of the
1371 * data we are returning.
1372 */
1373static int clipWinTxtToUtf8ForX11CB(Display *pDisplay, PRTUTF16 pwszSrc,
1374 size_t cbSrc, Atom *atomTarget,
1375 Atom *atomTypeReturn,
1376 XtPointer *pValReturn,
1377 unsigned long *pcLenReturn,
1378 int *piFormatReturn)
1379{
1380 RT_NOREF(pDisplay, pcLenReturn);
1381
1382 /* This may slightly overestimate the space needed. */
1383 size_t cbDest = 0;
1384 int rc = clipWinTxtBufSizeForUtf8(pwszSrc, cbSrc / 2, &cbDest);
1385 if (RT_SUCCESS(rc))
1386 {
1387 char *pszDest = (char *)XtMalloc(cbDest);
1388 size_t cbActual = 0;
1389 if (pszDest)
1390 {
1391 rc = clipWinTxtToUtf8(pwszSrc, cbSrc, pszDest, cbDest, &cbActual);
1392 }
1393 else
1394 rc = VERR_NO_MEMORY;
1395
1396 if (RT_SUCCESS(rc))
1397 {
1398 *atomTypeReturn = *atomTarget;
1399 *pValReturn = (XtPointer)pszDest;
1400 *pcLenReturn = cbActual;
1401 *piFormatReturn = 8;
1402 }
1403 }
1404 return rc;
1405}
1406
1407/**
1408 * Satisfies a request from X11 to convert the clipboard HTML fragment to UTF-8. We
1409 * return null-terminated text, but can cope with non-null-terminated input.
1410 *
1411 * @returns VBox status code.
1412 * @param pDisplay An X11 display structure, needed for conversions
1413 * performed by Xlib.
1414 * @param pv The text to be converted (UTF8 with Windows EOLs).
1415 * @param cb The length of the text in @cb in bytes.
1416 * @param atomTypeReturn Where to store the atom for the type of the data
1417 * we are returning.
1418 * @param pValReturn Where to store the pointer to the data we are
1419 * returning. This should be to memory allocated by
1420 * XtMalloc, which will be freed by the Xt toolkit later.
1421 * @param pcLenReturn Where to store the length of the data we are returning.
1422 * @param piFormatReturn Where to store the bit width (8, 16, 32) of the
1423 * data we are returning.
1424 */
1425static int clipWinHTMLToUtf8ForX11CB(Display *pDisplay, const char *pszSrc,
1426 size_t cbSrc, Atom *atomTarget,
1427 Atom *atomTypeReturn,
1428 XtPointer *pValReturn,
1429 unsigned long *pcLenReturn,
1430 int *piFormatReturn)
1431{
1432 RT_NOREF(pDisplay, pValReturn);
1433
1434 /* This may slightly overestimate the space needed. */
1435 LogFlowFunc(("Source: %s", pszSrc));
1436
1437 char *pszDest = (char *)XtMalloc(cbSrc);
1438 if (pszDest == NULL)
1439 return VERR_NO_MEMORY;
1440
1441 memcpy(pszDest, pszSrc, cbSrc);
1442
1443 *atomTypeReturn = *atomTarget;
1444 *pValReturn = (XtPointer)pszDest;
1445 *pcLenReturn = cbSrc;
1446 *piFormatReturn = 8;
1447
1448 return VINF_SUCCESS;
1449}
1450
1451
1452/**
1453 * Does this atom correspond to one of the two selection types we support?
1454 *
1455 * @param pCtx The X11 clipboard context to use.
1456 * @param selType The atom in question.
1457 */
1458static bool clipIsSupportedSelectionType(PSHCLX11CTX pCtx, Atom selType)
1459{
1460 return( (selType == clipGetAtom(pCtx, "CLIPBOARD"))
1461 || (selType == clipGetAtom(pCtx, "PRIMARY")));
1462}
1463
1464/**
1465 * Removes a trailing nul character from a string by adjusting the string
1466 * length. Some X11 applications don't like zero-terminated text...
1467 *
1468 * @param pText The text in question.
1469 * @param pcText The length of the text, adjusted on return.
1470 * @param format The format of the text.
1471 */
1472static void clipTrimTrailingNul(XtPointer pText, unsigned long *pcText,
1473 SHCLX11FMT format)
1474{
1475 AssertPtrReturnVoid(pText);
1476 AssertPtrReturnVoid(pcText);
1477 AssertReturnVoid((format == SHCLX11FMT_UTF8) || (format == SHCLX11FMT_TEXT) || (format == SHCLX11FMT_HTML));
1478
1479 if (((char *)pText)[*pcText - 1] == '\0')
1480 --(*pcText);
1481}
1482
1483static int clipConvertVBoxCBForX11(PSHCLX11CTX pCtx, Atom *atomTarget,
1484 Atom *atomTypeReturn,
1485 XtPointer *pValReturn,
1486 unsigned long *pcLenReturn,
1487 int *piFormatReturn)
1488{
1489 int rc = VINF_SUCCESS;
1490
1491 SHCLX11FMTIDX x11Format = clipFindX11FormatByAtom(pCtx, *atomTarget);
1492 SHCLX11FMT clipFormat = clipRealFormatForX11Format(x11Format);
1493
1494 LogFlowFunc(("fFormats=0x%x, x11Format=%u, clipFormat=%u\n", pCtx->vboxFormats, x11Format, clipFormat));
1495
1496 if ( ((clipFormat == SHCLX11FMT_UTF8) || (clipFormat == SHCLX11FMT_TEXT))
1497 && (pCtx->vboxFormats & VBOX_SHCL_FMT_UNICODETEXT))
1498 {
1499 void *pv = NULL;
1500 uint32_t cb = 0;
1501 rc = clipReadVBoxShCl(pCtx, VBOX_SHCL_FMT_UNICODETEXT, &pv, &cb);
1502 if (RT_SUCCESS(rc) && (cb == 0))
1503 rc = VERR_NO_DATA;
1504 if (RT_SUCCESS(rc) && ((clipFormat == SHCLX11FMT_UTF8) || (clipFormat == SHCLX11FMT_TEXT)))
1505 rc = clipWinTxtToUtf8ForX11CB(XtDisplay(pCtx->pWidget),
1506 (PRTUTF16)pv, cb, atomTarget,
1507 atomTypeReturn, pValReturn,
1508 pcLenReturn, piFormatReturn);
1509 if (RT_SUCCESS(rc))
1510 clipTrimTrailingNul(*(XtPointer *)pValReturn, pcLenReturn, clipFormat);
1511
1512 RTMemFree(pv);
1513 }
1514 else if ( (clipFormat == SHCLX11FMT_BMP)
1515 && (pCtx->vboxFormats & VBOX_SHCL_FMT_BITMAP))
1516 {
1517 void *pv = NULL;
1518 uint32_t cb = 0;
1519 rc = clipReadVBoxShCl(pCtx, VBOX_SHCL_FMT_BITMAP, &pv, &cb);
1520 if (RT_SUCCESS(rc) && (cb == 0))
1521 rc = VERR_NO_DATA;
1522 if (RT_SUCCESS(rc) && (clipFormat == SHCLX11FMT_BMP))
1523 {
1524 /* Create a full BMP from it */
1525 rc = ShClDibToBmp(pv, cb, (void **)pValReturn,
1526 (size_t *)pcLenReturn);
1527 }
1528 else
1529 rc = VERR_NOT_SUPPORTED;
1530
1531 if (RT_SUCCESS(rc))
1532 {
1533 *atomTypeReturn = *atomTarget;
1534 *piFormatReturn = 8;
1535 }
1536
1537 RTMemFree(pv);
1538 }
1539 else if ( (clipFormat == SHCLX11FMT_HTML)
1540 && (pCtx->vboxFormats & VBOX_SHCL_FMT_HTML))
1541 {
1542 void *pv = NULL;
1543 uint32_t cb = 0;
1544 rc = clipReadVBoxShCl(pCtx, VBOX_SHCL_FMT_HTML, &pv, &cb);
1545 if (RT_SUCCESS(rc) && (cb == 0))
1546 rc = VERR_NO_DATA;
1547 if (RT_SUCCESS(rc))
1548 {
1549 /*
1550 * The common VBox HTML encoding will be - Utf8
1551 * because it more general for HTML formats then UTF16
1552 * X11 clipboard returns UTF-16, so before sending it we should
1553 * convert it to UTF8.
1554 * It's very strange but here we get UTF-16 from x11 clipboard
1555 * in same time we send UTF-8 to x11 clipboard and it's work.
1556 */
1557 rc = clipWinHTMLToUtf8ForX11CB(XtDisplay(pCtx->pWidget),
1558 (const char*)pv, cb, atomTarget,
1559 atomTypeReturn, pValReturn,
1560 pcLenReturn, piFormatReturn);
1561 if (RT_SUCCESS(rc))
1562 clipTrimTrailingNul(*(XtPointer *)pValReturn, pcLenReturn, clipFormat);
1563
1564 RTMemFree(pv);
1565 }
1566 }
1567#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
1568 else if (pCtx->vboxFormats & VBOX_SHCL_FMT_URI_LIST)
1569 {
1570 switch (clipFormat)
1571 {
1572 case SHCLX11FMT_TEXT:
1573 RT_FALL_THROUGH();
1574 case SHCLX11FMT_UTF8:
1575 RT_FALL_THROUGH();
1576 case SHCLX11FMT_URI_LIST:
1577 {
1578 break;
1579 }
1580
1581 default:
1582 rc = VERR_NOT_SUPPORTED;
1583 break;
1584 }
1585 }
1586#endif
1587 else
1588 {
1589 *atomTypeReturn = XT_CONVERT_FAIL;
1590 *pValReturn = (XtPointer)NULL;
1591 *pcLenReturn = 0;
1592 *piFormatReturn = 0;
1593
1594 rc = VERR_NOT_SUPPORTED;
1595 }
1596
1597 if (RT_FAILURE(rc))
1598 LogRel2(("Shared Clipboard: Converting format 0x%x for X11 (x11Format=%u, clipFormat=%u) failed, rc=%Rrc\n",
1599 pCtx->vboxFormats, x11Format, clipFormat, rc));
1600
1601 LogFlowFuncLeaveRC(rc);
1602 return rc;
1603}
1604
1605/**
1606 * Returns VBox's clipboard data for an X11 client.
1607 *
1608 * @note Callback for XtOwnSelection.
1609 */
1610static Boolean clipXtConvertSelectionProc(Widget widget, Atom *atomSelection,
1611 Atom *atomTarget,
1612 Atom *atomTypeReturn,
1613 XtPointer *pValReturn,
1614 unsigned long *pcLenReturn,
1615 int *piFormatReturn)
1616{
1617 LogFlowFuncEnter();
1618
1619 PSHCLX11CTX pCtx = clipLookupContext(widget);
1620 int rc = VINF_SUCCESS;
1621
1622 if (!pCtx)
1623 return False;
1624
1625 if (!clipIsSupportedSelectionType(pCtx, *atomSelection))
1626 return False;
1627
1628 if (*atomTarget == clipGetAtom(pCtx, "TARGETS"))
1629 rc = clipCreateX11Targets(pCtx, atomTypeReturn, pValReturn,
1630 pcLenReturn, piFormatReturn);
1631 else
1632 rc = clipConvertVBoxCBForX11(pCtx, atomTarget, atomTypeReturn,
1633 pValReturn, pcLenReturn, piFormatReturn);
1634
1635 LogFlowFunc(("returning %RTbool, internal status code %Rrc\n", RT_SUCCESS(rc), rc));
1636 return RT_SUCCESS(rc) ? True : False;
1637}
1638
1639/**
1640 * Structure used to pass information about formats that VBox supports.
1641 */
1642typedef struct _CLIPNEWVBOXFORMATS
1643{
1644 /** Context information for the X11 clipboard. */
1645 PSHCLX11CTX pCtx;
1646 /** Formats supported by VBox. */
1647 SHCLFORMATS Formats;
1648} CLIPNEWVBOXFORMATS, *PCLIPNEWVBOXFORMATS;
1649
1650/** Invalidates the local cache of the data in the VBox clipboard. */
1651static void clipInvalidateVBoxCBCache(PSHCLX11CTX pCtx)
1652{
1653 if (pCtx->pvUnicodeCache != NULL)
1654 {
1655 RTMemFree(pCtx->pvUnicodeCache);
1656 pCtx->pvUnicodeCache = NULL;
1657 }
1658}
1659
1660/**
1661 * Takes possession of the X11 clipboard (and middle-button selection).
1662 */
1663static void clipGrabX11CB(PSHCLX11CTX pCtx, SHCLFORMATS Formats)
1664{
1665 LogFlowFuncEnter();
1666
1667 if (XtOwnSelection(pCtx->pWidget, clipGetAtom(pCtx, "CLIPBOARD"),
1668 CurrentTime, clipXtConvertSelectionProc, NULL, 0))
1669 {
1670 pCtx->vboxFormats = Formats;
1671 /* Grab the middle-button paste selection too. */
1672 XtOwnSelection(pCtx->pWidget, clipGetAtom(pCtx, "PRIMARY"),
1673 CurrentTime, clipXtConvertSelectionProc, NULL, 0);
1674#ifndef TESTCASE
1675 /* Xt suppresses these if we already own the clipboard, so send them
1676 * ourselves. */
1677 XSetSelectionOwner(XtDisplay(pCtx->pWidget),
1678 clipGetAtom(pCtx, "CLIPBOARD"),
1679 XtWindow(pCtx->pWidget), CurrentTime);
1680 XSetSelectionOwner(XtDisplay(pCtx->pWidget),
1681 clipGetAtom(pCtx, "PRIMARY"),
1682 XtWindow(pCtx->pWidget), CurrentTime);
1683#endif
1684 }
1685}
1686
1687/**
1688 * Worker function for ShClX11ReportFormatsToX11 which runs on the
1689 * event thread.
1690 *
1691 * @param pUserData Pointer to a CLIPNEWVBOXFORMATS structure containing
1692 * information about the VBox formats available and the
1693 * clipboard context data. Must be freed by the worker.
1694 */
1695static void ShClX11ReportFormatsToX11Worker(void *pUserData, void * /* interval */)
1696{
1697 CLIPNEWVBOXFORMATS *pFormats = (CLIPNEWVBOXFORMATS *)pUserData;
1698 PSHCLX11CTX pCtx = pFormats->pCtx;
1699
1700 uint32_t fFormats = pFormats->Formats;
1701
1702 RTMemFree(pFormats);
1703
1704 LogFlowFunc (("fFormats=0x%x\n", fFormats));
1705
1706 clipInvalidateVBoxCBCache(pCtx);
1707 clipGrabX11CB(pCtx, fFormats);
1708 clipResetX11Formats(pCtx);
1709
1710 LogFlowFuncLeave();
1711}
1712
1713/**
1714 * Announces new clipboard formats to the host.
1715 *
1716 * @returns VBox status code.
1717 * @param Formats Clipboard formats offered.
1718 */
1719int ShClX11ReportFormatsToX11(PSHCLX11CTX pCtx, uint32_t Formats)
1720{
1721 /*
1722 * Immediately return if we are not connected to the X server.
1723 */
1724 if (!pCtx->fHaveX11)
1725 return VINF_SUCCESS;
1726
1727 int rc;
1728
1729 /* This must be free'd by the worker callback. */
1730 PCLIPNEWVBOXFORMATS pFormats = (PCLIPNEWVBOXFORMATS)RTMemAlloc(sizeof(CLIPNEWVBOXFORMATS));
1731 if (pFormats)
1732 {
1733 pFormats->pCtx = pCtx;
1734 pFormats->Formats = Formats;
1735
1736 rc = clipQueueToEventThread(pCtx, ShClX11ReportFormatsToX11Worker,
1737 (XtPointer) pFormats);
1738 }
1739 else
1740 rc = VERR_NO_MEMORY;
1741
1742 LogFlowFuncLeaveRC(rc);
1743 return rc;
1744}
1745
1746/**
1747 * Massages generic UTF-16 with CR end-of-lines into the format Windows expects
1748 * and return the result in a RTMemAlloc allocated buffer.
1749 *
1750 * @returns VBox status code.
1751 * @param pwcSrc The source as UTF-16.
1752 * @param cwcSrc The number of 16bit elements in @a pwcSrc, not counting
1753 * the terminating zero.
1754 * @param ppwszDest Where to store the buffer address.
1755 * @param pcbDest On success, where to store the number of bytes written.
1756 * Undefined otherwise. Optional.
1757 */
1758static int clipUtf16ToWinTxt(RTUTF16 *pwcSrc, size_t cwcSrc,
1759 PRTUTF16 *ppwszDest, uint32_t *pcbDest)
1760{
1761 AssertPtrReturn(pwcSrc, VERR_INVALID_POINTER);
1762 AssertPtrReturn(ppwszDest, VERR_INVALID_POINTER);
1763
1764 LogFlowFunc(("pwcSrc=%p, cwcSrc=%u, ppwszDest=%p\n", pwcSrc, cwcSrc, ppwszDest));
1765
1766 if (pcbDest)
1767 *pcbDest = 0;
1768
1769 PRTUTF16 pwszDest = NULL;
1770 size_t cwcDest;
1771 int rc = ShClUtf16GetWinSize(pwcSrc, cwcSrc + 1, &cwcDest);
1772 if (RT_SUCCESS(rc))
1773 {
1774 pwszDest = (PRTUTF16)RTMemAlloc(cwcDest * sizeof(RTUTF16));
1775 if (!pwszDest)
1776 rc = VERR_NO_MEMORY;
1777 }
1778
1779 if (RT_SUCCESS(rc))
1780 rc = ShClUtf16LinToWin(pwcSrc, cwcSrc + 1, pwszDest, cwcDest);
1781
1782 if (RT_SUCCESS(rc))
1783 {
1784 LogFlowFunc(("Converted string is %.*ls\n", cwcDest, pwszDest));
1785
1786 *ppwszDest = pwszDest;
1787
1788 if (pcbDest)
1789 *pcbDest = cwcDest * sizeof(RTUTF16);
1790 }
1791 else
1792 RTMemFree(pwszDest);
1793
1794 LogFlowFuncLeaveRC(rc);
1795 return rc;
1796}
1797
1798/**
1799 * Converts UTF-8 text with CR end-of-lines into UTF-16 as Windows expects it
1800 * and return the result in a RTMemAlloc allocated buffer.
1801 *
1802 * @returns VBox status code.
1803 * @param pcSrc The source UTF-8.
1804 * @param cbSrc The size of the source in bytes, not counting the
1805 * terminating zero.
1806 * @param ppwszDest Where to store the buffer address.
1807 * @param pcbDest On success, where to store the number of bytes written.
1808 * Undefined otherwise. Optional.
1809 */
1810static int clipUtf8ToWinTxt(const char *pcSrc, unsigned cbSrc,
1811 PRTUTF16 *ppwszDest, uint32_t *pcbDest)
1812{
1813 AssertPtrReturn(pcSrc, VERR_INVALID_POINTER);
1814 AssertPtrReturn(ppwszDest, VERR_INVALID_POINTER);
1815
1816 LogFlowFunc(("pcSrc=%p, cbSrc=%u, ppwszDest=%p\n", pcSrc, cbSrc, ppwszDest));
1817
1818 if (pcbDest)
1819 *pcbDest = 0;
1820
1821 /* Intermediate conversion to UTF-16. */
1822 size_t cwcTmp;
1823 PRTUTF16 pwcTmp = NULL;
1824 int rc = RTStrToUtf16Ex(pcSrc, cbSrc, &pwcTmp, 0, &cwcTmp);
1825 if (RT_SUCCESS(rc))
1826 rc = clipUtf16ToWinTxt(pwcTmp, cwcTmp, ppwszDest, pcbDest);
1827
1828 RTUtf16Free(pwcTmp);
1829
1830 LogFlowFuncLeaveRC(rc);
1831 return rc;
1832}
1833
1834/**
1835 * Converts Latin-1 text with CR end-of-lines into UTF-16 as Windows expects
1836 * it and return the result in a RTMemAlloc allocated buffer.
1837 *
1838 * @returns VBox status code.
1839 * @param pcSrc The source text.
1840 * @param cbSrc The size of the source in bytes, not counting the
1841 * terminating zero.
1842 * @param ppwszDest Where to store the buffer address.
1843 * @param pcbDest On success, where to store the number of bytes written.
1844 * Undefined otherwise. Optional.
1845 */
1846static int clipLatin1ToWinTxt(char *pcSrc, unsigned cbSrc,
1847 PRTUTF16 *ppwszDest, uint32_t *pcbDest)
1848{
1849 AssertPtrReturn(pcSrc, VERR_INVALID_POINTER);
1850 AssertPtrReturn(ppwszDest, VERR_INVALID_POINTER);
1851
1852 LogFlowFunc(("pcSrc=%.*s, cbSrc=%u, ppwszDest=%p\n", cbSrc, (char *) pcSrc, cbSrc, ppwszDest));
1853
1854 PRTUTF16 pwszDest = NULL;
1855 int rc = VINF_SUCCESS;
1856
1857 /* Calculate the space needed. */
1858 unsigned cwcDest = 0;
1859 for (unsigned i = 0; i < cbSrc && pcSrc[i] != '\0'; ++i)
1860 {
1861 if (pcSrc[i] == VBOX_SHCL_LINEFEED)
1862 cwcDest += 2;
1863 else
1864 ++cwcDest;
1865 }
1866
1867 ++cwcDest; /* Leave space for the terminator. */
1868
1869 if (pcbDest)
1870 *pcbDest = cwcDest * sizeof(RTUTF16);
1871
1872 pwszDest = (PRTUTF16) RTMemAlloc(cwcDest * sizeof(RTUTF16));
1873 if (!pwszDest)
1874 rc = VERR_NO_MEMORY;
1875
1876 /* And do the conversion, bearing in mind that Latin-1 expands "naturally"
1877 * to UTF-16. */
1878 if (RT_SUCCESS(rc))
1879 {
1880 for (unsigned i = 0, j = 0; i < cbSrc; ++i, ++j)
1881 {
1882 if (pcSrc[i] != VBOX_SHCL_LINEFEED)
1883 pwszDest[j] = pcSrc[i];
1884 else
1885 {
1886 pwszDest[j] = VBOX_SHCL_CARRIAGERETURN;
1887 pwszDest[j + 1] = VBOX_SHCL_LINEFEED;
1888 ++j;
1889 }
1890 }
1891
1892 pwszDest[cwcDest - 1] = '\0'; /* Make sure we are zero-terminated. */
1893
1894 LogFlowFunc(("Converted text is %.*ls\n", cwcDest, pwszDest));
1895 }
1896
1897 if (RT_SUCCESS(rc))
1898 {
1899 *ppwszDest = pwszDest;
1900 }
1901 else
1902 RTMemFree(pwszDest);
1903
1904 LogFlowFuncLeaveRC(rc);
1905 return rc;
1906}
1907
1908/**
1909* Converts UTF-16 text into UTF-8 as Windows expects
1910* it and return the result in a RTMemAlloc allocated buffer.
1911*
1912* @returns VBox status code.
1913* @param pcSrc The source text.
1914* @param cbSrc The size of the source in bytes, not counting the
1915* terminating zero.
1916* @param ppwszDest Where to store the buffer address.
1917* @param pcbDest On success, where to store the number of bytes written.
1918* Undefined otherwise. Optional.
1919*/
1920static int clipUTF16ToWinHTML(RTUTF16 *pwcBuf, size_t cb, char **ppszOut, uint32_t *pcOut)
1921{
1922 AssertPtrReturn(pwcBuf, VERR_INVALID_POINTER);
1923 AssertReturn (cb, VERR_INVALID_PARAMETER);
1924 AssertPtrReturn(ppszOut, VERR_INVALID_POINTER);
1925 AssertPtrReturn(pcOut, VERR_INVALID_POINTER);
1926
1927 if (cb % 2)
1928 return VERR_INVALID_PARAMETER;
1929
1930 size_t cwc = cb / 2;
1931 size_t i = 0;
1932 RTUTF16 *pwc = pwcBuf;
1933 char *pchRes = NULL;
1934 size_t cRes = 0;
1935 LogFlowFunc(("src= %ls cb=%d i=%i, %x %x\n", pwcBuf, cb, i, ppszOut, pcOut));
1936 while (i < cwc)
1937 {
1938 /* find zero symbol (end of string) */
1939 for (; i < cwc && pwcBuf[i] != 0; i++)
1940 ;
1941 LogFlowFunc(("skipped nulls i=%d cwc=%d\n", i, cwc));
1942
1943 /* convert found string */
1944 char *psz = NULL;
1945 size_t cch = 0;
1946 int rc = RTUtf16ToUtf8Ex(pwc, cwc, &psz, pwc - pwcBuf, &cch);
1947 LogFlowFunc(("utf16toutf8 src= %ls res=%s i=%i\n", pwc, psz, i));
1948 if (RT_FAILURE(rc))
1949 {
1950 RTMemFree(pchRes);
1951 return rc;
1952 }
1953
1954 /* append new substring */
1955 char *pchNew = (char *)RTMemRealloc(pchRes, cRes + cch + 1);
1956 if (!pchNew)
1957 {
1958 RTMemFree(pchRes);
1959 RTStrFree(psz);
1960 return VERR_NO_MEMORY;
1961 }
1962 pchRes = pchNew;
1963 memcpy(pchRes + cRes, psz, cch + 1);
1964 LogFlowFunc(("Temp result res=%s\n", pchRes + cRes));
1965
1966 /* remove temporary buffer */
1967 RTStrFree(psz);
1968 cRes += cch + 1;
1969 /* skip zero symbols */
1970 for (; i < cwc && pwcBuf[i] == 0; i++)
1971 ;
1972 /* remember start of string */
1973 pwc += i;
1974 }
1975 *ppszOut = pchRes;
1976 *pcOut = cRes;
1977
1978 return VINF_SUCCESS;
1979}
1980
1981/**
1982 * A structure containing information about where to store a request
1983 * for the X11 clipboard contents.
1984 */
1985typedef struct _CLIPREADX11CBREQ
1986{
1987 /** The format VBox would like the data in. */
1988 SHCLFORMAT mFormat;
1989 /** The format we requested from X11. */
1990 SHCLX11FMTIDX mX11Format;
1991 /** The clipboard context this request is associated with. */
1992 SHCLX11CTX *mpCtx;
1993 /** The request structure passed in from the backend. */
1994 CLIPREADCBREQ *mpReq;
1995} CLIPREADX11CBREQ;
1996
1997/**
1998 * Converts the data obtained from the X11 clipboard to the required format,
1999 * place it in the buffer supplied and signal that data has arrived.
2000 *
2001 * Converts the text obtained UTF-16LE with Windows EOLs.
2002 * Converts full BMP data to DIB format.
2003 *
2004 * @note Callback for XtGetSelectionValue, for use when
2005 * the X11 clipboard contains a format we understand.
2006 */
2007static void clipConvertDataFromX11CallbackWorker(void *pClient, void *pvSrc, unsigned cbSrc)
2008{
2009 CLIPREADX11CBREQ *pReq = (CLIPREADX11CBREQ *)pClient;
2010
2011 LogFlowFunc(("pReq->mFormat=%02X, pReq->mX11Format=%u, pReq->mCtx=%p\n", pReq->mFormat, pReq->mX11Format, pReq->mpCtx));
2012
2013 AssertPtr(pReq->mpCtx);
2014 Assert(pReq->mFormat != VBOX_SHCL_FMT_NONE); /* Sanity. */
2015
2016 int rc = VINF_SUCCESS;
2017
2018 void *pvDst = NULL;
2019 uint32_t cbDst = 0;
2020
2021 if (pvSrc == NULL)
2022 {
2023 /* The clipboard selection may have changed before we could get it. */
2024 rc = VERR_NO_DATA;
2025 }
2026 else if (pReq->mFormat == VBOX_SHCL_FMT_UNICODETEXT)
2027 {
2028 /* In which format is the clipboard data? */
2029 switch (clipRealFormatForX11Format(pReq->mX11Format))
2030 {
2031 case SHCLX11FMT_UTF8:
2032 RT_FALL_THROUGH();
2033 case SHCLX11FMT_TEXT:
2034 {
2035 /* If we are given broken UTF-8, we treat it as Latin1. */ /** @todo Is this acceptable? */
2036 if (RT_SUCCESS(RTStrValidateEncodingEx((char *)pvSrc, cbSrc, 0)))
2037 {
2038 rc = clipUtf8ToWinTxt((const char *)pvSrc, cbSrc,
2039 (PRTUTF16 *)&pvDst, &cbDst);
2040 }
2041 else
2042 {
2043 rc = clipLatin1ToWinTxt((char *)pvSrc, cbSrc,
2044 (PRTUTF16 *)&pvDst, &cbDst);
2045 }
2046 break;
2047 }
2048
2049 default:
2050 {
2051 rc = VERR_INVALID_PARAMETER;
2052 break;
2053 }
2054 }
2055 }
2056 else if (pReq->mFormat == VBOX_SHCL_FMT_BITMAP)
2057 {
2058 /* In which format is the clipboard data? */
2059 switch (clipRealFormatForX11Format(pReq->mX11Format))
2060 {
2061 case SHCLX11FMT_BMP:
2062 {
2063 const void *pDib;
2064 size_t cbDibSize;
2065 rc = ShClBmpGetDib((const void *)pvSrc, cbSrc,
2066 &pDib, &cbDibSize);
2067 if (RT_SUCCESS(rc))
2068 {
2069 pvDst = RTMemAlloc(cbDibSize);
2070 if (!pvDst)
2071 rc = VERR_NO_MEMORY;
2072 else
2073 {
2074 memcpy(pvDst, pDib, cbDibSize);
2075 cbDst = cbDibSize;
2076 }
2077 }
2078 break;
2079 }
2080
2081 default:
2082 {
2083 rc = VERR_INVALID_PARAMETER;
2084 break;
2085 }
2086 }
2087 }
2088 else if (pReq->mFormat == VBOX_SHCL_FMT_HTML)
2089 {
2090 /* In which format is the clipboard data? */
2091 switch (clipRealFormatForX11Format(pReq->mX11Format))
2092 {
2093 case SHCLX11FMT_HTML:
2094 {
2095 /*
2096 * The common VBox HTML encoding will be - UTF-8
2097 * because it more general for HTML formats then UTF-16
2098 * X11 clipboard returns UTF-16, so before sending it we should
2099 * convert it to UTF-8.
2100 */
2101 pvDst = NULL;
2102 cbDst = 0;
2103
2104 /*
2105 * Some applications sends data in UTF-16, some in UTF-8,
2106 * without indication it in MIME.
2107 * But in case of UTF-16, at least an OpenOffice adds Byte Order Mark - 0xfeff
2108 * at start of clipboard data.
2109 */
2110 if ( cbSrc >= sizeof(RTUTF16)
2111 && *(PRTUTF16)pvSrc == 0xfeff)
2112 {
2113 LogFlowFunc((" \n"));
2114 rc = clipUTF16ToWinHTML((RTUTF16 *)pvSrc, cbSrc,
2115 (char**)&pvDst, &cbDst);
2116 }
2117 else
2118 {
2119 pvDst = RTMemAlloc(cbSrc);
2120 if(pvDst)
2121 {
2122 memcpy(pvDst, pvSrc, cbSrc);
2123 cbDst = cbSrc;
2124 }
2125 else
2126 {
2127 rc = VERR_NO_MEMORY;
2128 break;
2129 }
2130 }
2131
2132 LogFlowFunc(("Source unicode %ls, cbSrc = %d\n, Byte Order Mark = %hx", pvSrc, cbSrc, ((PRTUTF16)pvSrc)[0]));
2133 LogFlowFunc(("converted to win unicode %s, cbDest = %d, rc = %Rrc\n", pvDst, cbDst, rc));
2134 rc = VINF_SUCCESS;
2135 break;
2136 }
2137
2138 default:
2139 {
2140 rc = VERR_INVALID_PARAMETER;
2141 break;
2142 }
2143 }
2144 }
2145#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
2146 else if (pReq->mFormat == VBOX_SHCL_FMT_URI_LIST)
2147 {
2148 /* In which format is the clipboard data? */
2149 switch (clipRealFormatForX11Format(pReq->mX11Format))
2150 {
2151 case SHCLX11FMT_URI_LIST:
2152 {
2153 /* For URI lists we only accept valid UTF-8 encodings. */
2154 if (RT_SUCCESS(RTStrValidateEncodingEx((char *)pvSrc, cbSrc, 0)))
2155 {
2156 /* URI lists on X are string separated with "\r\n". */
2157 RTCList<RTCString> lstRootEntries = RTCString((char *)pvSrc, cbSrc).split("\r\n");
2158 for (size_t i = 0; i < lstRootEntries.size(); ++i)
2159 {
2160 char *pszEntry = RTUriFilePath(lstRootEntries.at(i).c_str());
2161 AssertPtrBreakStmt(pszEntry, VERR_INVALID_PARAMETER);
2162
2163 LogFlowFunc(("URI list entry '%s'\n", pszEntry));
2164
2165 rc = RTStrAAppend((char **)&pvDst, pszEntry);
2166 AssertRCBreakStmt(rc, VERR_NO_MEMORY);
2167 cbDst += (uint32_t)strlen(pszEntry);
2168
2169 rc = RTStrAAppend((char **)&pvDst, "\r\n");
2170 AssertRCBreakStmt(rc, VERR_NO_MEMORY);
2171 cbDst += (uint32_t)strlen("\r\n");
2172
2173 RTStrFree(pszEntry);
2174 }
2175
2176 if (cbDst)
2177 cbDst++; /* Include final (zero) termination. */
2178
2179 LogFlowFunc(("URI list: cbDst=%RU32\n", cbDst));
2180 }
2181 else
2182 rc = VERR_INVALID_PARAMETER;
2183 break;
2184 }
2185
2186 default:
2187 {
2188 rc = VERR_INVALID_PARAMETER;
2189 break;
2190 }
2191 }
2192 }
2193#endif
2194 else
2195 rc = VERR_NOT_SUPPORTED;
2196
2197 ShClX11RequestFromX11CompleteCallback(pReq->mpCtx->pFrontend, rc, pReq->mpReq,
2198 pvDst, cbDst);
2199 RTMemFree(pvDst);
2200 RTMemFree(pReq);
2201
2202 LogFlowFuncLeaveRC(rc);
2203}
2204
2205#ifndef TESTCASE
2206/**
2207 * Converts the data obtained from the X11 clipboard to the required format,
2208 * place it in the buffer supplied and signal that data has arrived.
2209 *
2210 * Converts the text obtained UTF-16LE with Windows EOLs.
2211 * Converts full BMP data to DIB format.
2212 *
2213 * @note Callback for XtGetSelectionValue(), for use when
2214 * the X11 clipboard contains a format we understand.
2215 */
2216static void clipConvertDataFromX11Callback(Widget widget, XtPointer pClient,
2217 Atom * /* selection */, Atom *atomType,
2218 XtPointer pvSrc, long unsigned int *pcLen,
2219 int *piFormat)
2220{
2221 RT_NOREF(widget);
2222 if (*atomType == XT_CONVERT_FAIL) /* Xt timeout */
2223 clipConvertDataFromX11CallbackWorker(pClient, NULL, 0);
2224 else
2225 clipConvertDataFromX11CallbackWorker(pClient, pvSrc, (*pcLen) * (*piFormat) / 8);
2226
2227 XtFree((char *)pvSrc);
2228}
2229#endif
2230
2231#ifdef TESTCASE
2232static void tstClipRequestData(SHCLX11CTX* pCtx, SHCLX11FMTIDX target,
2233 void *closure);
2234#endif
2235
2236static int clipGetSelectionValue(PSHCLX11CTX pCtx, SHCLX11FMTIDX format,
2237 CLIPREADX11CBREQ *pReq)
2238{
2239#ifndef TESTCASE
2240 XtGetSelectionValue(pCtx->pWidget, clipGetAtom(pCtx, "CLIPBOARD"),
2241 clipAtomForX11Format(pCtx, format),
2242 clipConvertDataFromX11Callback,
2243 reinterpret_cast<XtPointer>(pReq),
2244 CurrentTime);
2245#else
2246 tstClipRequestData(pCtx, format, (void *)pReq);
2247#endif
2248
2249 return VINF_SUCCESS; /** @todo Return real rc. */
2250}
2251
2252/**
2253 * Worker function for ShClX11ReadDataFromX11 which runs on the event thread.
2254 */
2255static void ShClX11ReadDataFromX11Worker(void *pvUserData, void * /* interval */)
2256{
2257 AssertPtrReturnVoid(pvUserData);
2258
2259 CLIPREADX11CBREQ *pReq = (CLIPREADX11CBREQ *)pvUserData;
2260 SHCLX11CTX *pCtx = pReq->mpCtx;
2261
2262 LogFlowFunc(("pReq->mFormat = %02x\n", pReq->mFormat));
2263
2264 int rc = VERR_NO_DATA; /* VBox thinks we have data and we don't. */
2265
2266 if (pReq->mFormat & VBOX_SHCL_FMT_UNICODETEXT)
2267 {
2268 pReq->mX11Format = pCtx->X11TextFormat;
2269 if (pReq->mX11Format != SHCLX11FMT_INVALID)
2270 {
2271 /* Send out a request for the data to the current clipboard owner. */
2272 rc = clipGetSelectionValue(pCtx, pCtx->X11TextFormat, pReq);
2273 }
2274 }
2275 else if (pReq->mFormat & VBOX_SHCL_FMT_BITMAP)
2276 {
2277 pReq->mX11Format = pCtx->X11BitmapFormat;
2278 if (pReq->mX11Format != SHCLX11FMT_INVALID)
2279 {
2280 /* Send out a request for the data to the current clipboard owner. */
2281 rc = clipGetSelectionValue(pCtx, pCtx->X11BitmapFormat, pReq);
2282 }
2283 }
2284 else if (pReq->mFormat & VBOX_SHCL_FMT_HTML)
2285 {
2286 pReq->mX11Format = pCtx->X11HTMLFormat;
2287 if (pReq->mX11Format != SHCLX11FMT_INVALID)
2288 {
2289 /* Send out a request for the data to the current clipboard owner. */
2290 rc = clipGetSelectionValue(pCtx, pCtx->X11HTMLFormat, pReq);
2291 }
2292 }
2293#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
2294 else if (pReq->mFormat & VBOX_SHCL_FMT_URI_LIST)
2295 {
2296 pReq->mX11Format = pCtx->X11URIListFormat;
2297 if (pReq->mX11Format != SHCLX11FMT_INVALID)
2298 {
2299 /* Send out a request for the data to the current clipboard owner. */
2300 rc = clipGetSelectionValue(pCtx, pCtx->X11URIListFormat, pReq);
2301 }
2302 }
2303#endif
2304 else
2305 {
2306 rc = VERR_NOT_IMPLEMENTED;
2307 }
2308
2309 if (RT_FAILURE(rc))
2310 {
2311 /* The clipboard callback was never scheduled, so we must signal
2312 * that the request processing is finished and clean up ourselves. */
2313 ShClX11RequestFromX11CompleteCallback(pReq->mpCtx->pFrontend, rc, pReq->mpReq,
2314 NULL /* pv */ ,0 /* cb */);
2315 RTMemFree(pReq);
2316 }
2317
2318 LogFlowFuncLeaveRC(rc);
2319}
2320
2321/**
2322 * Called when VBox wants to read the X11 clipboard.
2323 *
2324 * @returns VBox status code.
2325 * @param pCtx Context data for the clipboard backend.
2326 * @param Format The format that the VBox would like to receive the data in.
2327 * @param pReq Read callback request to use. Must be free'd in the callback.
2328 *
2329 * @note We allocate a request structure which must be freed by the worker.
2330 */
2331int ShClX11ReadDataFromX11(PSHCLX11CTX pCtx, SHCLFORMAT Format, CLIPREADCBREQ *pReq)
2332{
2333 /*
2334 * Immediately return if we are not connected to the X server.
2335 */
2336 if (!pCtx->fHaveX11)
2337 return VERR_NO_DATA;
2338
2339 int rc = VINF_SUCCESS;
2340
2341 CLIPREADX11CBREQ *pX11Req = (CLIPREADX11CBREQ *)RTMemAllocZ(sizeof(CLIPREADX11CBREQ));
2342 if (pX11Req)
2343 {
2344 pX11Req->mpCtx = pCtx;
2345 pX11Req->mFormat = Format;
2346 pX11Req->mpReq = pReq;
2347
2348 /* We use this to schedule a worker function on the event thread. */
2349 rc = clipQueueToEventThread(pCtx, ShClX11ReadDataFromX11Worker, (XtPointer)pX11Req);
2350 }
2351 else
2352 rc = VERR_NO_MEMORY;
2353
2354 LogFlowFuncLeaveRC(rc);
2355 return rc;
2356}
2357
2358#ifdef TESTCASE
2359
2360/** @todo This unit test currently works by emulating the X11 and X toolkit
2361 * APIs to exercise the code, since I didn't want to rewrite the code too much
2362 * when I wrote the tests. However, this makes it rather ugly and hard to
2363 * understand. Anyone doing any work on the code should feel free to
2364 * rewrite the tests and the code to make them cleaner and more readable. */
2365
2366#include <iprt/test.h>
2367#include <poll.h>
2368
2369#define TESTCASE_WIDGET_ID (Widget)0xffff
2370
2371/* For the purpose of the test case, we just execute the procedure to be
2372 * scheduled, as we are running single threaded. */
2373void tstClipQueueToEventThread(void (*proc)(void *, void *),
2374 void *client_data)
2375{
2376 proc(client_data, NULL);
2377}
2378
2379void XtFree(char *ptr)
2380{
2381 RTMemFree((void *)ptr);
2382}
2383
2384/* The data in the simulated VBox clipboard. */
2385static int g_tst_rcDataVBox = VINF_SUCCESS;
2386static void *g_tst_pvDataVBox = NULL;
2387static uint32_t g_tst_cbDataVBox = 0;
2388
2389/* Set empty data in the simulated VBox clipboard. */
2390static void tstClipEmptyVBox(PSHCLX11CTX pCtx, int retval)
2391{
2392 g_tst_rcDataVBox = retval;
2393 RTMemFree(g_tst_pvDataVBox);
2394 g_tst_pvDataVBox = NULL;
2395 g_tst_cbDataVBox = 0;
2396 ShClX11ReportFormatsToX11(pCtx, 0);
2397}
2398
2399/* Set the data in the simulated VBox clipboard. */
2400static int tstClipSetVBoxUtf16(PSHCLX11CTX pCtx, int retval,
2401 const char *pcszData, size_t cb)
2402{
2403 PRTUTF16 pwszData = NULL;
2404 size_t cwData = 0;
2405 int rc = RTStrToUtf16Ex(pcszData, RTSTR_MAX, &pwszData, 0, &cwData);
2406 if (RT_FAILURE(rc))
2407 return rc;
2408 AssertReturn(cb <= cwData * 2 + 2, VERR_BUFFER_OVERFLOW);
2409 void *pv = RTMemDup(pwszData, cb);
2410 RTUtf16Free(pwszData);
2411 if (pv == NULL)
2412 return VERR_NO_MEMORY;
2413 if (g_tst_pvDataVBox)
2414 RTMemFree(g_tst_pvDataVBox);
2415 g_tst_rcDataVBox = retval;
2416 g_tst_pvDataVBox = pv;
2417 g_tst_cbDataVBox = cb;
2418 ShClX11ReportFormatsToX11(pCtx, VBOX_SHCL_FMT_UNICODETEXT);
2419 return VINF_SUCCESS;
2420}
2421
2422/* Return the data in the simulated VBox clipboard. */
2423DECLCALLBACK(int) ShClX11RequestDataForX11Callback(PSHCLCONTEXT pCtx, uint32_t Format, void **ppv, uint32_t *pcb)
2424{
2425 RT_NOREF(pCtx, Format);
2426 *pcb = g_tst_cbDataVBox;
2427 if (g_tst_pvDataVBox != NULL)
2428 {
2429 void *pv = RTMemDup(g_tst_pvDataVBox, g_tst_cbDataVBox);
2430 *ppv = pv;
2431 return pv != NULL ? g_tst_rcDataVBox : VERR_NO_MEMORY;
2432 }
2433 *ppv = NULL;
2434 return g_tst_rcDataVBox;
2435}
2436
2437Display *XtDisplay(Widget w) { NOREF(w); return (Display *) 0xffff; }
2438
2439void XtAppSetExitFlag(XtAppContext app_context) { NOREF(app_context); }
2440
2441void XtDestroyWidget(Widget w) { NOREF(w); }
2442
2443XtAppContext XtCreateApplicationContext(void) { return (XtAppContext)0xffff; }
2444
2445void XtDestroyApplicationContext(XtAppContext app_context) { NOREF(app_context); }
2446
2447void XtToolkitInitialize(void) {}
2448
2449Boolean XtToolkitThreadInitialize(void) { return True; }
2450
2451Display *XtOpenDisplay(XtAppContext app_context,
2452 _Xconst _XtString display_string,
2453 _Xconst _XtString application_name,
2454 _Xconst _XtString application_class,
2455 XrmOptionDescRec *options, Cardinal num_options,
2456 int *argc, char **argv)
2457{
2458 RT_NOREF8(app_context, display_string, application_name, application_class, options, num_options, argc, argv);
2459 return (Display *)0xffff;
2460}
2461
2462Widget XtVaAppCreateShell(_Xconst _XtString application_name, _Xconst _XtString application_class,
2463 WidgetClass widget_class, Display *display, ...)
2464{
2465 RT_NOREF(application_name, application_class, widget_class, display);
2466 return TESTCASE_WIDGET_ID;
2467}
2468
2469void XtSetMappedWhenManaged(Widget widget, _XtBoolean mapped_when_managed) { RT_NOREF(widget, mapped_when_managed); }
2470
2471void XtRealizeWidget(Widget widget) { NOREF(widget); }
2472
2473XtInputId XtAppAddInput(XtAppContext app_context, int source, XtPointer condition, XtInputCallbackProc proc, XtPointer closure)
2474{
2475 RT_NOREF(app_context, source, condition, proc, closure);
2476 return 0xffff;
2477}
2478
2479/* Atoms we need other than the formats we support. */
2480static const char *g_tst_apszSupAtoms[] =
2481{
2482 "PRIMARY", "CLIPBOARD", "TARGETS", "MULTIPLE", "TIMESTAMP"
2483};
2484
2485/* This just looks for the atom names in a couple of tables and returns an
2486 * index with an offset added. */
2487Atom XInternAtom(Display *, const char *pcsz, int)
2488{
2489 Atom atom = 0;
2490 for (unsigned i = 0; i < RT_ELEMENTS(g_aFormats); ++i)
2491 if (!strcmp(pcsz, g_aFormats[i].pcszAtom))
2492 atom = (Atom) (i + 0x1000);
2493 for (unsigned i = 0; i < RT_ELEMENTS(g_tst_apszSupAtoms); ++i)
2494 if (!strcmp(pcsz, g_tst_apszSupAtoms[i]))
2495 atom = (Atom) (i + 0x2000);
2496 Assert(atom); /* Have we missed any atoms? */
2497 return atom;
2498}
2499
2500/* Take a request for the targets we are currently offering. */
2501static SHCLX11FMTIDX g_tst_aSelTargetsIdx[10] = { 0 };
2502static size_t g_tst_cTargets = 0;
2503
2504void tstRequestTargets(SHCLX11CTX* pCtx)
2505{
2506 clipUpdateX11Targets(pCtx, g_tst_aSelTargetsIdx, g_tst_cTargets);
2507}
2508
2509/* The current values of the X selection, which will be returned to the
2510 * XtGetSelectionValue callback. */
2511static Atom g_tst_atmSelType = 0;
2512static const void *g_tst_pSelData = NULL;
2513static unsigned long g_tst_cSelData = 0;
2514static int g_tst_selFormat = 0;
2515
2516void tstClipRequestData(PSHCLX11CTX pCtx, SHCLX11FMTIDX target, void *closure)
2517{
2518 RT_NOREF(pCtx);
2519 unsigned long count = 0;
2520 int format = 0;
2521 if (target != g_tst_aSelTargetsIdx[0])
2522 {
2523 clipConvertDataFromX11CallbackWorker(closure, NULL, 0); /* Could not convert to target. */
2524 return;
2525 }
2526 void *pValue = NULL;
2527 pValue = g_tst_pSelData ? RTMemDup(g_tst_pSelData, g_tst_cSelData) : NULL;
2528 count = g_tst_pSelData ? g_tst_cSelData : 0;
2529 format = g_tst_selFormat;
2530 if (!pValue)
2531 {
2532 count = 0;
2533 format = 0;
2534 }
2535 clipConvertDataFromX11CallbackWorker(closure, pValue, count * format / 8);
2536 if (pValue)
2537 RTMemFree(pValue);
2538}
2539
2540/* The formats currently on offer from X11 via the shared clipboard. */
2541static uint32_t g_tst_uX11Formats = 0;
2542
2543DECLCALLBACK(void) ShClX11ReportFormatsCallback(PSHCLCONTEXT pCtx, SHCLFORMATS Formats)
2544{
2545 RT_NOREF(pCtx);
2546 g_tst_uX11Formats = Formats;
2547}
2548
2549static uint32_t tstClipQueryFormats(void)
2550{
2551 return g_tst_uX11Formats;
2552}
2553
2554static void tstClipInvalidateFormats(void)
2555{
2556 g_tst_uX11Formats = ~0;
2557}
2558
2559/* Does our clipboard code currently own the selection? */
2560static bool g_tst_fOwnsSel = false;
2561/* The procedure that is called when we should convert the selection to a
2562 * given format. */
2563static XtConvertSelectionProc g_tst_pfnSelConvert = NULL;
2564/* The procedure which is called when we lose the selection. */
2565static XtLoseSelectionProc g_tst_pfnSelLose = NULL;
2566/* The procedure which is called when the selection transfer has completed. */
2567static XtSelectionDoneProc g_tst_pfnSelDone = NULL;
2568
2569Boolean XtOwnSelection(Widget widget, Atom selection, Time time,
2570 XtConvertSelectionProc convert,
2571 XtLoseSelectionProc lose,
2572 XtSelectionDoneProc done)
2573{
2574 RT_NOREF(widget, time);
2575 if (selection != XInternAtom(NULL, "CLIPBOARD", 0))
2576 return True; /* We don't really care about this. */
2577 g_tst_fOwnsSel = true; /* Always succeed. */
2578 g_tst_pfnSelConvert = convert;
2579 g_tst_pfnSelLose = lose;
2580 g_tst_pfnSelDone = done;
2581 return True;
2582}
2583
2584void XtDisownSelection(Widget widget, Atom selection, Time time)
2585{
2586 RT_NOREF(widget, time, selection);
2587 g_tst_fOwnsSel = false;
2588 g_tst_pfnSelConvert = NULL;
2589 g_tst_pfnSelLose = NULL;
2590 g_tst_pfnSelDone = NULL;
2591}
2592
2593/* Request the shared clipboard to convert its data to a given format. */
2594static bool tstClipConvertSelection(const char *pcszTarget, Atom *type,
2595 XtPointer *value, unsigned long *length,
2596 int *format)
2597{
2598 Atom target = XInternAtom(NULL, pcszTarget, 0);
2599 if (target == 0)
2600 return false;
2601 /* Initialise all return values in case we make a quick exit. */
2602 *type = XA_STRING;
2603 *value = NULL;
2604 *length = 0;
2605 *format = 0;
2606 if (!g_tst_fOwnsSel)
2607 return false;
2608 if (!g_tst_pfnSelConvert)
2609 return false;
2610 Atom clipAtom = XInternAtom(NULL, "CLIPBOARD", 0);
2611 if (!g_tst_pfnSelConvert(TESTCASE_WIDGET_ID, &clipAtom, &target, type,
2612 value, length, format))
2613 return false;
2614 if (g_tst_pfnSelDone)
2615 g_tst_pfnSelDone(TESTCASE_WIDGET_ID, &clipAtom, &target);
2616 return true;
2617}
2618
2619/* Set the current X selection data */
2620static void tstClipSetSelectionValues(const char *pcszTarget, Atom type,
2621 const void *data,
2622 unsigned long count, int format)
2623{
2624 Atom clipAtom = XInternAtom(NULL, "CLIPBOARD", 0);
2625 g_tst_aSelTargetsIdx[0] = clipFindX11FormatByAtomText(pcszTarget);
2626 g_tst_cTargets = 1;
2627 g_tst_atmSelType = type;
2628 g_tst_pSelData = data;
2629 g_tst_cSelData = count;
2630 g_tst_selFormat = format;
2631 if (g_tst_pfnSelLose)
2632 g_tst_pfnSelLose(TESTCASE_WIDGET_ID, &clipAtom);
2633 g_tst_fOwnsSel = false;
2634}
2635
2636static void tstClipSendTargetUpdate(PSHCLX11CTX pCtx)
2637{
2638 clipQueryX11FormatsCallback(pCtx);
2639}
2640
2641/* Configure if and how the X11 TARGETS clipboard target will fail. */
2642static void tstClipSetTargetsFailure(void)
2643{
2644 g_tst_cTargets = 0;
2645}
2646
2647char *XtMalloc(Cardinal size) { return (char *) RTMemAlloc(size); }
2648
2649char *XGetAtomName(Display *display, Atom atom)
2650{
2651 RT_NOREF(display);
2652 AssertReturn((unsigned)atom < RT_ELEMENTS(g_aFormats) + 1, NULL);
2653 const char *pcszName = NULL;
2654 if (atom < 0x1000)
2655 return NULL;
2656 if (0x1000 <= atom && atom < 0x2000)
2657 {
2658 unsigned index = atom - 0x1000;
2659 AssertReturn(index < RT_ELEMENTS(g_aFormats), NULL);
2660 pcszName = g_aFormats[index].pcszAtom;
2661 }
2662 else
2663 {
2664 unsigned index = atom - 0x2000;
2665 AssertReturn(index < RT_ELEMENTS(g_tst_apszSupAtoms), NULL);
2666 pcszName = g_tst_apszSupAtoms[index];
2667 }
2668 return (char *)RTMemDup(pcszName, sizeof(pcszName) + 1);
2669}
2670
2671int XFree(void *data)
2672{
2673 RTMemFree(data);
2674 return 0;
2675}
2676
2677void XFreeStringList(char **list)
2678{
2679 if (list)
2680 RTMemFree(*list);
2681 RTMemFree(list);
2682}
2683
2684#define TESTCASE_MAX_BUF_SIZE 256
2685
2686static int g_tst_rcCompleted = VINF_SUCCESS;
2687static int g_tst_cbCompleted = 0;
2688static CLIPREADCBREQ *g_tst_pCompletedReq = NULL;
2689static char g_tst_abCompletedBuf[TESTCASE_MAX_BUF_SIZE];
2690
2691void ShClX11RequestFromX11CompleteCallback(PSHCLCONTEXT pCtx, int rc, CLIPREADCBREQ *pReq, void *pv, uint32_t cb)
2692{
2693 RT_NOREF(pCtx);
2694 if (cb <= TESTCASE_MAX_BUF_SIZE)
2695 {
2696 g_tst_rcCompleted = rc;
2697 if (cb != 0)
2698 memcpy(g_tst_abCompletedBuf, pv, cb);
2699 }
2700 else
2701 g_tst_rcCompleted = VERR_BUFFER_OVERFLOW;
2702 g_tst_cbCompleted = cb;
2703 g_tst_pCompletedReq = pReq;
2704}
2705
2706static void tstClipGetCompletedRequest(int *prc, char ** ppc, uint32_t *pcb, CLIPREADCBREQ **ppReq)
2707{
2708 *prc = g_tst_rcCompleted;
2709 *ppc = g_tst_abCompletedBuf;
2710 *pcb = g_tst_cbCompleted;
2711 *ppReq = g_tst_pCompletedReq;
2712}
2713#ifdef RT_OS_SOLARIS_10
2714char XtStrings [] = "";
2715_WidgetClassRec* applicationShellWidgetClass;
2716char XtShellStrings [] = "";
2717int XmbTextPropertyToTextList(
2718 Display* /* display */,
2719 XTextProperty* /* text_prop */,
2720 char*** /* list_return */,
2721 int* /* count_return */
2722)
2723{
2724 return 0;
2725}
2726#else
2727const char XtStrings [] = "";
2728_WidgetClassRec* applicationShellWidgetClass;
2729const char XtShellStrings [] = "";
2730#endif
2731
2732static void tstStringFromX11(RTTEST hTest, PSHCLX11CTX pCtx,
2733 const char *pcszExp, int rcExp)
2734{
2735 bool retval = true;
2736 tstClipSendTargetUpdate(pCtx);
2737 if (tstClipQueryFormats() != VBOX_SHCL_FMT_UNICODETEXT)
2738 RTTestFailed(hTest, "Wrong targets reported: %02X\n",
2739 tstClipQueryFormats());
2740 else
2741 {
2742 char *pc;
2743 CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)&pReq, *pReqRet = NULL;
2744 ShClX11ReadDataFromX11(pCtx, VBOX_SHCL_FMT_UNICODETEXT, pReq);
2745 int rc = VINF_SUCCESS;
2746 uint32_t cbActual = 0;
2747 tstClipGetCompletedRequest(&rc, &pc, &cbActual, &pReqRet);
2748 if (rc != rcExp)
2749 RTTestFailed(hTest, "Wrong return code, expected %Rrc, got %Rrc\n",
2750 rcExp, rc);
2751 else if (pReqRet != pReq)
2752 RTTestFailed(hTest, "Wrong returned request data, expected %p, got %p\n",
2753 pReq, pReqRet);
2754 else if (RT_FAILURE(rcExp))
2755 retval = true;
2756 else
2757 {
2758 RTUTF16 wcExp[TESTCASE_MAX_BUF_SIZE / 2];
2759 RTUTF16 *pwcExp = wcExp;
2760 size_t cwc = 0;
2761 rc = RTStrToUtf16Ex(pcszExp, RTSTR_MAX, &pwcExp,
2762 RT_ELEMENTS(wcExp), &cwc);
2763 size_t cbExp = cwc * 2 + 2;
2764 AssertRC(rc);
2765 if (RT_SUCCESS(rc))
2766 {
2767 if (cbActual != cbExp)
2768 {
2769 RTTestFailed(hTest, "Returned string is the wrong size, string \"%.*ls\", size %u, expected \"%s\", size %u\n",
2770 RT_MIN(TESTCASE_MAX_BUF_SIZE, cbActual), pc, cbActual,
2771 pcszExp, cbExp);
2772 }
2773 else
2774 {
2775 if (memcmp(pc, wcExp, cbExp) == 0)
2776 retval = true;
2777 else
2778 RTTestFailed(hTest, "Returned string \"%.*ls\" does not match expected string \"%s\"\n",
2779 TESTCASE_MAX_BUF_SIZE, pc, pcszExp);
2780 }
2781 }
2782 }
2783 }
2784 if (!retval)
2785 RTTestFailureDetails(hTest, "Expected: string \"%s\", rc %Rrc\n",
2786 pcszExp, rcExp);
2787}
2788
2789static void tstLatin1FromX11(RTTEST hTest, PSHCLX11CTX pCtx,
2790 const char *pcszExp, int rcExp)
2791{
2792 bool retval = false;
2793 tstClipSendTargetUpdate(pCtx);
2794 if (tstClipQueryFormats() != VBOX_SHCL_FMT_UNICODETEXT)
2795 RTTestFailed(hTest, "Wrong targets reported: %02X\n",
2796 tstClipQueryFormats());
2797 else
2798 {
2799 char *pc;
2800 CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)&pReq, *pReqRet = NULL;
2801 ShClX11ReadDataFromX11(pCtx, VBOX_SHCL_FMT_UNICODETEXT, pReq);
2802 int rc = VINF_SUCCESS;
2803 uint32_t cbActual = 0;
2804 tstClipGetCompletedRequest(&rc, &pc, &cbActual, &pReqRet);
2805 if (rc != rcExp)
2806 RTTestFailed(hTest, "Wrong return code, expected %Rrc, got %Rrc\n",
2807 rcExp, rc);
2808 else if (pReqRet != pReq)
2809 RTTestFailed(hTest, "Wrong returned request data, expected %p, got %p\n",
2810 pReq, pReqRet);
2811 else if (RT_FAILURE(rcExp))
2812 retval = true;
2813 else
2814 {
2815 RTUTF16 wcExp[TESTCASE_MAX_BUF_SIZE / 2];
2816 //RTUTF16 *pwcExp = wcExp; - unused
2817 size_t cwc;
2818 for (cwc = 0; cwc == 0 || pcszExp[cwc - 1] != '\0'; ++cwc)
2819 wcExp[cwc] = pcszExp[cwc];
2820 size_t cbExp = cwc * 2;
2821 if (cbActual != cbExp)
2822 {
2823 RTTestFailed(hTest, "Returned string is the wrong size, string \"%.*ls\", size %u, expected \"%s\", size %u\n",
2824 RT_MIN(TESTCASE_MAX_BUF_SIZE, cbActual), pc, cbActual,
2825 pcszExp, cbExp);
2826 }
2827 else
2828 {
2829 if (memcmp(pc, wcExp, cbExp) == 0)
2830 retval = true;
2831 else
2832 RTTestFailed(hTest, "Returned string \"%.*ls\" does not match expected string \"%s\"\n",
2833 TESTCASE_MAX_BUF_SIZE, pc, pcszExp);
2834 }
2835 }
2836 }
2837 if (!retval)
2838 RTTestFailureDetails(hTest, "Expected: string \"%s\", rc %Rrc\n",
2839 pcszExp, rcExp);
2840}
2841
2842static void tstStringFromVBox(RTTEST hTest, PSHCLX11CTX pCtx, const char *pcszTarget, Atom typeExp, const char *valueExp)
2843{
2844 RT_NOREF(pCtx);
2845 bool retval = false;
2846 Atom type;
2847 XtPointer value = NULL;
2848 unsigned long length;
2849 int format;
2850 size_t lenExp = strlen(valueExp);
2851 if (tstClipConvertSelection(pcszTarget, &type, &value, &length, &format))
2852 {
2853 if ( type != typeExp
2854 || length != lenExp
2855 || format != 8
2856 || memcmp((const void *) value, (const void *)valueExp,
2857 lenExp))
2858 {
2859 RTTestFailed(hTest, "Bad data: type %d, (expected %d), length %u, (%u), format %d (%d), value \"%.*s\" (\"%.*s\")\n",
2860 type, typeExp, length, lenExp, format, 8,
2861 RT_MIN(length, 20), value, RT_MIN(lenExp, 20), valueExp);
2862 }
2863 else
2864 retval = true;
2865 }
2866 else
2867 RTTestFailed(hTest, "Conversion failed\n");
2868 XtFree((char *)value);
2869 if (!retval)
2870 RTTestFailureDetails(hTest, "Conversion to %s, expected \"%s\"\n",
2871 pcszTarget, valueExp);
2872}
2873
2874static void tstNoX11(PSHCLX11CTX pCtx, const char *pcszTestCtx)
2875{
2876 CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)&pReq;
2877 int rc = ShClX11ReadDataFromX11(pCtx, VBOX_SHCL_FMT_UNICODETEXT, pReq);
2878 RTTESTI_CHECK_MSG(rc == VERR_NO_DATA, ("context: %s\n", pcszTestCtx));
2879}
2880
2881static void tstStringFromVBoxFailed(RTTEST hTest, PSHCLX11CTX pCtx, const char *pcszTarget)
2882{
2883 RT_NOREF(pCtx);
2884 Atom type;
2885 XtPointer value = NULL;
2886 unsigned long length;
2887 int format;
2888 RTTEST_CHECK_MSG(hTest, !tstClipConvertSelection(pcszTarget, &type, &value,
2889 &length, &format),
2890 (hTest, "Conversion to target %s, should have failed but didn't, returned type %d, length %u, format %d, value \"%.*s\"\n",
2891 pcszTarget, type, length, format, RT_MIN(length, 20),
2892 value));
2893 XtFree((char *)value);
2894}
2895
2896static void tstNoSelectionOwnership(PSHCLX11CTX pCtx, const char *pcszTestCtx)
2897{
2898 RT_NOREF(pCtx);
2899 RTTESTI_CHECK_MSG(!g_tst_fOwnsSel, ("context: %s\n", pcszTestCtx));
2900}
2901
2902static void tstBadFormatRequestFromHost(RTTEST hTest, PSHCLX11CTX pCtx)
2903{
2904 tstClipSetSelectionValues("UTF8_STRING", XA_STRING, "hello world",
2905 sizeof("hello world"), 8);
2906 tstClipSendTargetUpdate(pCtx);
2907 if (tstClipQueryFormats() != VBOX_SHCL_FMT_UNICODETEXT)
2908 RTTestFailed(hTest, "Wrong targets reported: %02X\n",
2909 tstClipQueryFormats());
2910 else
2911 {
2912 char *pc;
2913 CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)&pReq, *pReqRet = NULL;
2914 ShClX11ReadDataFromX11(pCtx, 100, pReq); /* Bad format. */
2915 int rc = VINF_SUCCESS;
2916 uint32_t cbActual = 0;
2917 tstClipGetCompletedRequest(&rc, &pc, &cbActual, &pReqRet);
2918 if (rc != VERR_NOT_IMPLEMENTED)
2919 RTTestFailed(hTest, "Wrong return code, expected VERR_NOT_IMPLEMENTED, got %Rrc\n",
2920 rc);
2921 tstClipSetSelectionValues("", XA_STRING, "", sizeof(""), 8);
2922 tstClipSendTargetUpdate(pCtx);
2923 if (tstClipQueryFormats() == VBOX_SHCL_FMT_UNICODETEXT)
2924 RTTestFailed(hTest, "Failed to report targets after bad host request.\n");
2925 }
2926}
2927
2928int main()
2929{
2930 /*
2931 * Init the runtime, test and say hello.
2932 */
2933 RTTEST hTest;
2934 int rc = RTTestInitAndCreate("tstClipboardX11", &hTest);
2935 if (rc)
2936 return rc;
2937 RTTestBanner(hTest);
2938
2939 /*
2940 * Run the test.
2941 */
2942 SHCLX11CTX X11Ctx;
2943 rc = ShClX11Init(&X11Ctx, NULL, false);
2944 AssertRCReturn(rc, 1);
2945 char *pc;
2946 uint32_t cbActual;
2947 CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)&pReq, *pReqRet = NULL;
2948 rc = ShClX11ThreadStart(&X11Ctx, false /* fGrab */);
2949 AssertRCReturn(rc, 1);
2950
2951 /*** UTF-8 from X11 ***/
2952 RTTestSub(hTest, "reading UTF-8 from X11");
2953 /* Simple test */
2954 tstClipSetSelectionValues("UTF8_STRING", XA_STRING, "hello world",
2955 sizeof("hello world"), 8);
2956 tstStringFromX11(hTest, &X11Ctx, "hello world", VINF_SUCCESS);
2957 /* With an embedded carriage return */
2958 tstClipSetSelectionValues("text/plain;charset=UTF-8", XA_STRING,
2959 "hello\nworld", sizeof("hello\nworld"), 8);
2960 tstStringFromX11(hTest, &X11Ctx, "hello\r\nworld", VINF_SUCCESS);
2961 /* With an embedded CRLF */
2962 tstClipSetSelectionValues("text/plain;charset=UTF-8", XA_STRING,
2963 "hello\r\nworld", sizeof("hello\r\nworld"), 8);
2964 tstStringFromX11(hTest, &X11Ctx, "hello\r\r\nworld", VINF_SUCCESS);
2965 /* With an embedded LFCR */
2966 tstClipSetSelectionValues("text/plain;charset=UTF-8", XA_STRING,
2967 "hello\n\rworld", sizeof("hello\n\rworld"), 8);
2968 tstStringFromX11(hTest, &X11Ctx, "hello\r\n\rworld", VINF_SUCCESS);
2969 /* An empty string */
2970 tstClipSetSelectionValues("text/plain;charset=utf-8", XA_STRING, "",
2971 sizeof(""), 8);
2972 tstStringFromX11(hTest, &X11Ctx, "", VINF_SUCCESS);
2973 /* With an embedded UTF-8 character. */
2974 tstClipSetSelectionValues("STRING", XA_STRING,
2975 "100\xE2\x82\xAC" /* 100 Euro */,
2976 sizeof("100\xE2\x82\xAC"), 8);
2977 tstStringFromX11(hTest, &X11Ctx, "100\xE2\x82\xAC", VINF_SUCCESS);
2978 /* A non-zero-terminated string */
2979 tstClipSetSelectionValues("TEXT", XA_STRING,
2980 "hello world", sizeof("hello world") - 1, 8);
2981 tstStringFromX11(hTest, &X11Ctx, "hello world", VINF_SUCCESS);
2982
2983 /*** Latin1 from X11 ***/
2984 RTTestSub(hTest, "reading Latin1 from X11");
2985 /* Simple test */
2986 tstClipSetSelectionValues("STRING", XA_STRING, "Georges Dupr\xEA",
2987 sizeof("Georges Dupr\xEA"), 8);
2988 tstLatin1FromX11(hTest, &X11Ctx, "Georges Dupr\xEA", VINF_SUCCESS);
2989 /* With an embedded carriage return */
2990 tstClipSetSelectionValues("TEXT", XA_STRING, "Georges\nDupr\xEA",
2991 sizeof("Georges\nDupr\xEA"), 8);
2992 tstLatin1FromX11(hTest, &X11Ctx, "Georges\r\nDupr\xEA", VINF_SUCCESS);
2993 /* With an embedded CRLF */
2994 tstClipSetSelectionValues("TEXT", XA_STRING, "Georges\r\nDupr\xEA",
2995 sizeof("Georges\r\nDupr\xEA"), 8);
2996 tstLatin1FromX11(hTest, &X11Ctx, "Georges\r\r\nDupr\xEA", VINF_SUCCESS);
2997 /* With an embedded LFCR */
2998 tstClipSetSelectionValues("TEXT", XA_STRING, "Georges\n\rDupr\xEA",
2999 sizeof("Georges\n\rDupr\xEA"), 8);
3000 tstLatin1FromX11(hTest, &X11Ctx, "Georges\r\n\rDupr\xEA", VINF_SUCCESS);
3001 /* A non-zero-terminated string */
3002 tstClipSetSelectionValues("text/plain", XA_STRING,
3003 "Georges Dupr\xEA!",
3004 sizeof("Georges Dupr\xEA!") - 1, 8);
3005 tstLatin1FromX11(hTest, &X11Ctx, "Georges Dupr\xEA!", VINF_SUCCESS);
3006
3007 /*** Unknown X11 format ***/
3008 RTTestSub(hTest, "handling of an unknown X11 format");
3009 tstClipInvalidateFormats();
3010 tstClipSetSelectionValues("CLIPBOARD", XA_STRING, "Test",
3011 sizeof("Test"), 8);
3012 tstClipSendTargetUpdate(&X11Ctx);
3013 RTTEST_CHECK_MSG(hTest, tstClipQueryFormats() == 0,
3014 (hTest, "Failed to send a format update notification\n"));
3015
3016 /*** Timeout from X11 ***/
3017 RTTestSub(hTest, "X11 timeout");
3018 tstClipSetSelectionValues("UTF8_STRING", XT_CONVERT_FAIL, NULL,0, 8);
3019 tstStringFromX11(hTest, &X11Ctx, "", VERR_NO_DATA);
3020
3021 /*** No data in X11 clipboard ***/
3022 RTTestSub(hTest, "a data request from an empty X11 clipboard");
3023 tstClipSetSelectionValues("UTF8_STRING", XA_STRING, NULL,
3024 0, 8);
3025 ShClX11ReadDataFromX11(&X11Ctx, VBOX_SHCL_FMT_UNICODETEXT, pReq);
3026 tstClipGetCompletedRequest(&rc, &pc, &cbActual, &pReqRet);
3027 RTTEST_CHECK_MSG(hTest, rc == VERR_NO_DATA,
3028 (hTest, "Returned %Rrc instead of VERR_NO_DATA\n",
3029 rc));
3030 RTTEST_CHECK_MSG(hTest, pReqRet == pReq,
3031 (hTest, "Wrong returned request data, expected %p, got %p\n",
3032 pReq, pReqRet));
3033
3034 /*** Ensure that VBox is notified when we return the CB to X11 ***/
3035 RTTestSub(hTest, "notification of switch to X11 clipboard");
3036 tstClipInvalidateFormats();
3037 clipReportEmptyX11CB(&X11Ctx);
3038 RTTEST_CHECK_MSG(hTest, tstClipQueryFormats() == 0,
3039 (hTest, "Failed to send a format update (release) notification\n"));
3040
3041 /*** request for an invalid VBox format from X11 ***/
3042 RTTestSub(hTest, "a request for an invalid VBox format from X11");
3043 ShClX11ReadDataFromX11(&X11Ctx, 0xffff, pReq);
3044 tstClipGetCompletedRequest(&rc, &pc, &cbActual, &pReqRet);
3045 RTTEST_CHECK_MSG(hTest, rc == VERR_NOT_IMPLEMENTED,
3046 (hTest, "Returned %Rrc instead of VERR_NOT_IMPLEMENTED\n",
3047 rc));
3048 RTTEST_CHECK_MSG(hTest, pReqRet == pReq,
3049 (hTest, "Wrong returned request data, expected %p, got %p\n",
3050 pReq, pReqRet));
3051
3052 /*** Targets failure from X11 ***/
3053 RTTestSub(hTest, "X11 targets conversion failure");
3054 tstClipSetSelectionValues("UTF8_STRING", XA_STRING, "hello world",
3055 sizeof("hello world"), 8);
3056 tstClipSetTargetsFailure();
3057 Atom atom = XA_STRING;
3058 long unsigned int cLen = 0;
3059 int format = 8;
3060 clipConvertX11TargetsCallback(NULL, (XtPointer) &X11Ctx, NULL, &atom, NULL, &cLen,
3061 &format);
3062 RTTEST_CHECK_MSG(hTest, tstClipQueryFormats() == 0,
3063 (hTest, "Wrong targets reported: %02X\n",
3064 tstClipQueryFormats()));
3065
3066 /*** X11 text format conversion ***/
3067 RTTestSub(hTest, "handling of X11 selection targets");
3068 RTTEST_CHECK_MSG(hTest, tstClipTextFormatConversion(&X11Ctx),
3069 (hTest, "failed to select the right X11 text formats\n"));
3070
3071 /*** UTF-8 from VBox ***/
3072 RTTestSub(hTest, "reading UTF-8 from VBox");
3073 /* Simple test */
3074 tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "hello world",
3075 sizeof("hello world") * 2);
3076 tstStringFromVBox(hTest, &X11Ctx, "UTF8_STRING",
3077 clipGetAtom(&X11Ctx, "UTF8_STRING"), "hello world");
3078 /* With an embedded carriage return */
3079 tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "hello\r\nworld",
3080 sizeof("hello\r\nworld") * 2);
3081 tstStringFromVBox(hTest, &X11Ctx, "text/plain;charset=UTF-8",
3082 clipGetAtom(&X11Ctx, "text/plain;charset=UTF-8"),
3083 "hello\nworld");
3084 /* With an embedded CRCRLF */
3085 tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "hello\r\r\nworld",
3086 sizeof("hello\r\r\nworld") * 2);
3087 tstStringFromVBox(hTest, &X11Ctx, "text/plain;charset=UTF-8",
3088 clipGetAtom(&X11Ctx, "text/plain;charset=UTF-8"),
3089 "hello\r\nworld");
3090 /* With an embedded CRLFCR */
3091 tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "hello\r\n\rworld",
3092 sizeof("hello\r\n\rworld") * 2);
3093 tstStringFromVBox(hTest, &X11Ctx, "text/plain;charset=UTF-8",
3094 clipGetAtom(&X11Ctx, "text/plain;charset=UTF-8"),
3095 "hello\n\rworld");
3096 /* An empty string */
3097 tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "", 2);
3098 tstStringFromVBox(hTest, &X11Ctx, "text/plain;charset=utf-8",
3099 clipGetAtom(&X11Ctx, "text/plain;charset=utf-8"), "");
3100 /* With an embedded UTF-8 character. */
3101 tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "100\xE2\x82\xAC" /* 100 Euro */,
3102 10);
3103 tstStringFromVBox(hTest, &X11Ctx, "STRING",
3104 clipGetAtom(&X11Ctx, "STRING"), "100\xE2\x82\xAC");
3105 /* A non-zero-terminated string */
3106 tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "hello world",
3107 sizeof("hello world") * 2 - 2);
3108 tstStringFromVBox(hTest, &X11Ctx, "TEXT", clipGetAtom(&X11Ctx, "TEXT"),
3109 "hello world");
3110
3111 /*** Timeout from VBox ***/
3112 RTTestSub(hTest, "reading from VBox with timeout");
3113 tstClipEmptyVBox(&X11Ctx, VERR_TIMEOUT);
3114 tstStringFromVBoxFailed(hTest, &X11Ctx, "UTF8_STRING");
3115
3116 /*** No data in VBox clipboard ***/
3117 RTTestSub(hTest, "an empty VBox clipboard");
3118 tstClipSetSelectionValues("TEXT", XA_STRING, "", sizeof(""), 8);
3119 tstClipEmptyVBox(&X11Ctx, VINF_SUCCESS);
3120 RTTEST_CHECK_MSG(hTest, g_tst_fOwnsSel,
3121 (hTest, "VBox grabbed the clipboard with no data and we ignored it\n"));
3122 tstStringFromVBoxFailed(hTest, &X11Ctx, "UTF8_STRING");
3123
3124 /*** An unknown VBox format ***/
3125 RTTestSub(hTest, "reading an unknown VBox format");
3126 tstClipSetSelectionValues("TEXT", XA_STRING, "", sizeof(""), 8);
3127 tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "", 2);
3128 ShClX11ReportFormatsToX11(&X11Ctx, 0xa0000);
3129 RTTEST_CHECK_MSG(hTest, g_tst_fOwnsSel,
3130 (hTest, "VBox grabbed the clipboard with unknown data and we ignored it\n"));
3131 tstStringFromVBoxFailed(hTest, &X11Ctx, "UTF8_STRING");
3132
3133 /*** VBox requests a bad format ***/
3134 RTTestSub(hTest, "recovery from a bad format request");
3135 tstBadFormatRequestFromHost(hTest, &X11Ctx);
3136
3137 rc = ShClX11ThreadStop(&X11Ctx);
3138 AssertRCReturn(rc, 1);
3139 ShClX11Destroy(&X11Ctx);
3140
3141 /*** Headless clipboard tests ***/
3142
3143 rc = ShClX11Init(&X11Ctx, NULL, true);
3144 AssertRCReturn(rc, 1);
3145
3146 rc = ShClX11ThreadStart(&X11Ctx, false /* fGrab */);
3147 AssertRCReturn(rc, 1);
3148
3149 /*** Read from X11 ***/
3150 RTTestSub(hTest, "reading from X11, headless clipboard");
3151 /* Simple test */
3152 tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "",
3153 sizeof("") * 2);
3154 tstClipSetSelectionValues("UTF8_STRING", XA_STRING, "hello world",
3155 sizeof("hello world"), 8);
3156 tstNoX11(&X11Ctx, "reading from X11, headless clipboard");
3157
3158 /*** Read from VBox ***/
3159 RTTestSub(hTest, "reading from VBox, headless clipboard");
3160 /* Simple test */
3161 tstClipEmptyVBox(&X11Ctx, VERR_WRONG_ORDER);
3162 tstClipSetSelectionValues("TEXT", XA_STRING, "", sizeof(""), 8);
3163 tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "hello world",
3164 sizeof("hello world") * 2);
3165 tstNoSelectionOwnership(&X11Ctx, "reading from VBox, headless clipboard");
3166
3167 rc = ShClX11ThreadStop(&X11Ctx);
3168 AssertRCReturn(rc, 1);
3169
3170 ShClX11Destroy(&X11Ctx);
3171
3172 return RTTestSummaryAndDestroy(hTest);
3173}
3174
3175#endif
3176
3177#ifdef SMOKETEST
3178
3179/* This is a simple test case that just starts a copy of the X11 clipboard
3180 * backend, checks the X11 clipboard and exits. If ever needed I will add an
3181 * interactive mode in which the user can read and copy to the clipboard from
3182 * the command line. */
3183
3184# include <iprt/env.h>
3185# include <iprt/test.h>
3186
3187DECLCALLBACK(int) ShClX11RequestDataForX11Callback(PSHCLCONTEXT pCtx, SHCLFORMAT Format, void **ppv, uint32_t *pcb)
3188{
3189 RT_NOREF(pCtx, Format, ppv, pcb);
3190 return VERR_NO_DATA;
3191}
3192
3193DECLCALLBACK(void) ShClX11ReportFormatsCallback(PSHCLCONTEXT pCtx, SHCLFORMATS Formats)
3194{
3195 RT_NOREF(pCtx, Formats);
3196}
3197
3198DECLCALLBACK(void) ShClX11RequestFromX11CompleteCallback(PSHCLCONTEXT pCtx, int rc, CLIPREADCBREQ *pReq, void *pv, uint32_t cb)
3199{
3200 RT_NOREF(pCtx, rc, pReq, pv, cb);
3201}
3202
3203int main()
3204{
3205 /*
3206 * Init the runtime, test and say hello.
3207 */
3208 RTTEST hTest;
3209 int rc = RTTestInitAndCreate("tstClipboardX11Smoke", &hTest);
3210 if (rc)
3211 return rc;
3212 RTTestBanner(hTest);
3213
3214 /*
3215 * Run the test.
3216 */
3217 rc = VINF_SUCCESS;
3218 /* We can't test anything without an X session, so just return success
3219 * in that case. */
3220 if (!RTEnvExist("DISPLAY"))
3221 {
3222 RTTestPrintf(hTest, RTTESTLVL_INFO,
3223 "X11 not available, not running test\n");
3224 return RTTestSummaryAndDestroy(hTest);
3225 }
3226 SHCLX11CTX X11Ctx;
3227 rc = ShClX11Init(&X11Ctx, NULL, false);
3228 AssertRCReturn(rc, 1);
3229 rc = ShClX11ThreadStart(&X11Ctx, false /* fGrab */);
3230 AssertRCReturn(rc, 1);
3231 /* Give the clipboard time to synchronise. */
3232 RTThreadSleep(500);
3233 rc = ShClX11ThreadStop(&X11Ctx);
3234 AssertRCReturn(rc, 1);
3235 ShClX11Destroy(&X11Ctx);
3236 return RTTestSummaryAndDestroy(hTest);
3237}
3238
3239#endif /* SMOKETEST defined */
3240
Note: See TracBrowser for help on using the repository browser.

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