VirtualBox

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

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

Shared Clipboard/URI: Backed out clipboard-x11.cpp changes from r130636 -- the logic wrt the format / target handling needs a major overhaul first and is far too convoluted (and misleading). Sigh.

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

© 2023 Oracle
ContactPrivacy policyTerms of Use