summaryrefslogtreecommitdiffstats
path: root/src/VBox/GuestHost/SharedClipboard/x11-clipboard.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/GuestHost/SharedClipboard/x11-clipboard.cpp')
-rw-r--r--src/VBox/GuestHost/SharedClipboard/x11-clipboard.cpp2935
1 files changed, 2935 insertions, 0 deletions
diff --git a/src/VBox/GuestHost/SharedClipboard/x11-clipboard.cpp b/src/VBox/GuestHost/SharedClipboard/x11-clipboard.cpp
new file mode 100644
index 00000000..793ee7f3
--- /dev/null
+++ b/src/VBox/GuestHost/SharedClipboard/x11-clipboard.cpp
@@ -0,0 +1,2935 @@
+/** @file
+ *
+ * Shared Clipboard:
+ * X11 backend code.
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+/* Note: to automatically run regression tests on the shared clipboard,
+ * execute the tstClipboardX11 testcase. If you often make changes to the
+ * clipboard code, adding the line
+ * OTHERS += $(PATH_tstClipboardX11)/tstClipboardX11.run
+ * to LocalConfig.kmk will cause the tests to be run every time the code is
+ * changed. */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD
+
+#include <errno.h>
+
+#include <dlfcn.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#ifdef RT_OS_SOLARIS
+#include <tsol/label.h>
+#endif
+
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+#include <X11/Intrinsic.h>
+#include <X11/Shell.h>
+#include <X11/Xproto.h>
+#include <X11/StringDefs.h>
+
+#include <iprt/types.h>
+#include <iprt/mem.h>
+#include <iprt/semaphore.h>
+#include <iprt/thread.h>
+#include <iprt/utf16.h>
+
+#include <VBox/log.h>
+#include <VBox/version.h>
+
+#include <VBox/GuestHost/SharedClipboard.h>
+#include <VBox/GuestHost/clipboard-helper.h>
+#include <VBox/HostServices/VBoxClipboardSvc.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/* The serialisation mechanism looks like it is not needed (everything using it
+ * runs on one thread, and the flag is always cleared at the end of calls which
+ * use it). So we will remove it after the 5.2 series. */
+#if (VBOX_VERSION_MAJOR * 100000 + VBOX_VERSION_MINOR * 1000 + VBOX_VERION_BUILD >= 502051)
+# define VBOX_AFTER_5_2
+#endif
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** The different clipboard formats which we support. */
+enum CLIPFORMAT
+{
+ INVALID = 0,
+ TARGETS,
+ TEXT, /* Treat this as Utf8, but it may really be ascii */
+ UTF8,
+ BMP,
+ HTML
+};
+
+typedef unsigned CLIPX11FORMAT;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+class formats;
+static Atom clipGetAtom(CLIPBACKEND *pCtx, const char *pszName);
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** The table mapping X11 names to data formats and to the corresponding
+ * VBox clipboard formats (currently only Unicode) */
+static struct _CLIPFORMATTABLE
+{
+ /** The X11 atom name of the format (several names can match one format)
+ */
+ const char *pcszAtom;
+ /** The format corresponding to the name */
+ CLIPFORMAT enmFormat;
+ /** The corresponding VBox clipboard format */
+ uint32_t u32VBoxFormat;
+} g_aFormats[] =
+{
+ { "INVALID", INVALID, 0 },
+ { "UTF8_STRING", UTF8, VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT },
+ { "text/plain;charset=UTF-8", UTF8,
+ VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT },
+ { "text/plain;charset=utf-8", UTF8,
+ VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT },
+ { "STRING", TEXT, VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT },
+ { "TEXT", TEXT, VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT },
+ { "text/plain", TEXT, VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT },
+ { "text/html", HTML, VBOX_SHARED_CLIPBOARD_FMT_HTML },
+ { "text/html;charset=utf-8", HTML,
+ VBOX_SHARED_CLIPBOARD_FMT_HTML },
+ { "image/bmp", BMP, VBOX_SHARED_CLIPBOARD_FMT_BITMAP },
+ { "image/x-bmp", BMP, VBOX_SHARED_CLIPBOARD_FMT_BITMAP },
+ { "image/x-MS-bmp", BMP, VBOX_SHARED_CLIPBOARD_FMT_BITMAP }
+
+
+ /** @todo Inkscape exports image/png but not bmp... */
+};
+
+enum
+{
+ NIL_CLIPX11FORMAT = 0,
+ MAX_CLIP_X11_FORMATS = RT_ELEMENTS(g_aFormats)
+};
+
+
+/** Return the atom corresponding to a supported X11 format.
+ * @param widget a valid Xt widget
+ */
+static Atom clipAtomForX11Format(CLIPBACKEND *pCtx, CLIPX11FORMAT format)
+{
+ return clipGetAtom(pCtx, g_aFormats[format].pcszAtom);
+}
+
+/** Return the CLIPFORMAT corresponding to a supported X11 format. */
+static CLIPFORMAT clipRealFormatForX11Format(CLIPX11FORMAT format)
+{
+ return g_aFormats[format].enmFormat;
+}
+
+/** Return the atom corresponding to a supported X11 format. */
+static uint32_t clipVBoxFormatForX11Format(CLIPX11FORMAT format)
+{
+ return g_aFormats[format].u32VBoxFormat;
+}
+
+/** Lookup the X11 format matching a given X11 atom.
+ * @returns the format on success, NIL_CLIPX11FORMAT on failure
+ * @param widget a valid Xt widget
+ */
+static CLIPX11FORMAT clipFindX11FormatByAtom(CLIPBACKEND *pCtx, Atom atomFormat)
+{
+ for (unsigned i = 0; i < RT_ELEMENTS(g_aFormats); ++i)
+ if (clipAtomForX11Format(pCtx, i) == atomFormat)
+ return i;
+ return NIL_CLIPX11FORMAT;
+}
+
+#ifdef TESTCASE
+/** Lookup the X11 format matching a given X11 atom text.
+ * @returns the format on success, NIL_CLIPX11FORMAT on failure
+ * @param widget a valid Xt widget
+ */
+static CLIPX11FORMAT clipFindX11FormatByAtomText(const char *pcsz)
+{
+ for (unsigned i = 0; i < RT_ELEMENTS(g_aFormats); ++i)
+ if (!strcmp(g_aFormats[i].pcszAtom, pcsz))
+ return i;
+ return NIL_CLIPX11FORMAT;
+}
+#endif
+
+/**
+ * Enumerates supported X11 clipboard formats corresponding to a given VBox
+ * format.
+ * @returns the next matching X11 format in the list, or NIL_CLIPX11FORMAT if
+ * there are no more
+ * @param lastFormat The value returned from the last call of this function.
+ * Use NIL_CLIPX11FORMAT to start the enumeration.
+ */
+static CLIPX11FORMAT clipEnumX11Formats(uint32_t u32VBoxFormats,
+ CLIPX11FORMAT lastFormat)
+{
+ for (unsigned i = lastFormat + 1; i < RT_ELEMENTS(g_aFormats); ++i)
+ if (u32VBoxFormats & clipVBoxFormatForX11Format(i))
+ return i;
+ return NIL_CLIPX11FORMAT;
+}
+
+/** Global context information used by the X11 clipboard backend */
+struct _CLIPBACKEND
+{
+ /** Opaque data structure describing the front-end. */
+ VBOXCLIPBOARDCONTEXT *pFrontend;
+ /** Is an X server actually available? */
+ bool fHaveX11;
+ /** The X Toolkit application context structure */
+ XtAppContext appContext;
+
+ /** We have a separate thread to wait for Window and Clipboard events */
+ RTTHREAD thread;
+ /** The X Toolkit widget which we use as our clipboard client. It is never made visible. */
+ Widget widget;
+
+ /** Should we try to grab the clipboard on startup? */
+ bool fGrabClipboardOnStart;
+
+ /** The best text format X11 has to offer, as an index into the formats
+ * table */
+ CLIPX11FORMAT X11TextFormat;
+ /** The best bitmap format X11 has to offer, as an index into the formats
+ * table */
+ CLIPX11FORMAT X11BitmapFormat;
+ /** The best HTML format X11 has to offer, as an index into the formats
+ * table */
+ CLIPX11FORMAT X11HTMLFormat;
+ /** What formats does VBox have on offer? */
+ uint32_t vboxFormats;
+ /** Cache of the last unicode data that we received */
+ void *pvUnicodeCache;
+ /** Size of the unicode data in the cache */
+ uint32_t cbUnicodeCache;
+ /** When we wish the clipboard to exit, we have to wake up the event
+ * loop. We do this by writing into a pipe. This end of the pipe is
+ * the end that another thread can write to. */
+ int wakeupPipeWrite;
+ /** The reader end of the pipe */
+ int wakeupPipeRead;
+ /** A pointer to the XFixesSelectSelectionInput function */
+ void (*fixesSelectInput)(Display *, Window, Atom, unsigned long);
+ /** The first XFixes event number */
+ int fixesEventBase;
+#ifndef VBOX_AFTER_5_2
+ /** The Xt Intrinsics can only handle one outstanding clipboard operation
+ * at a time, so we keep track of whether one is in process. */
+ bool fBusy;
+ /** We can't handle a clipboard update event while we are busy, so remember
+ * it for later. */
+ bool fUpdateNeeded;
+#endif
+};
+
+/** The number of simultaneous instances we support. For all normal purposes
+ * we should never need more than one. For the testcase it is convenient to
+ * have a second instance that the first can interact with in order to have
+ * a more controlled environment. */
+enum { CLIP_MAX_CONTEXTS = 20 };
+
+/** Array of structures for mapping Xt widgets to context pointers. We
+ * need this because the widget clipboard callbacks do not pass user data. */
+static struct {
+ /** The widget we want to associate the context with */
+ Widget widget;
+ /** The context associated with the widget */
+ CLIPBACKEND *pCtx;
+} g_contexts[CLIP_MAX_CONTEXTS];
+
+/** Register a new X11 clipboard context. */
+static int clipRegisterContext(CLIPBACKEND *pCtx)
+{
+ bool found = false;
+ AssertReturn(pCtx != NULL, VERR_INVALID_PARAMETER);
+ Widget widget = pCtx->widget;
+ AssertReturn(widget != NULL, VERR_INVALID_PARAMETER);
+ for (unsigned i = 0; i < RT_ELEMENTS(g_contexts); ++i)
+ {
+ AssertReturn( (g_contexts[i].widget != widget)
+ && (g_contexts[i].pCtx != pCtx), VERR_WRONG_ORDER);
+ if (g_contexts[i].widget == NULL && !found)
+ {
+ AssertReturn(g_contexts[i].pCtx == NULL, VERR_INTERNAL_ERROR);
+ g_contexts[i].widget = widget;
+ g_contexts[i].pCtx = pCtx;
+ found = true;
+ }
+ }
+ return found ? VINF_SUCCESS : VERR_OUT_OF_RESOURCES;
+}
+
+/** Unregister an X11 clipboard context. */
+static void clipUnregisterContext(CLIPBACKEND *pCtx)
+{
+ bool found = false;
+ AssertReturnVoid(pCtx != NULL);
+ Widget widget = pCtx->widget;
+ AssertReturnVoid(widget != NULL);
+ for (unsigned i = 0; i < RT_ELEMENTS(g_contexts); ++i)
+ {
+ Assert(!found || g_contexts[i].widget != widget);
+ if (g_contexts[i].widget == widget)
+ {
+ Assert(g_contexts[i].pCtx != NULL);
+ g_contexts[i].widget = NULL;
+ g_contexts[i].pCtx = NULL;
+ found = true;
+ }
+ }
+}
+
+/** Find an X11 clipboard context. */
+static CLIPBACKEND *clipLookupContext(Widget widget)
+{
+ AssertReturn(widget != NULL, NULL);
+ for (unsigned i = 0; i < RT_ELEMENTS(g_contexts); ++i)
+ {
+ if (g_contexts[i].widget == widget)
+ {
+ Assert(g_contexts[i].pCtx != NULL);
+ return g_contexts[i].pCtx;
+ }
+ }
+ return NULL;
+}
+
+/** Convert an atom name string to an X11 atom, looking it up in a cache
+ * before asking the server */
+static Atom clipGetAtom(CLIPBACKEND *pCtx, const char *pszName)
+{
+ AssertPtrReturn(pszName, None);
+ return XInternAtom(XtDisplay(pCtx->widget), pszName, False);
+}
+
+#ifdef TESTCASE
+static void testQueueToEventThread(void (*proc)(void *, void *),
+ void *client_data);
+#endif
+
+/** String written to the wakeup pipe. */
+#define WAKE_UP_STRING "WakeUp!"
+/** Length of the string written. */
+#define WAKE_UP_STRING_LEN ( sizeof(WAKE_UP_STRING) - 1 )
+
+/** Schedule a function call to run on the Xt event thread by passing it to
+ * the application context as a 0ms timeout and waking up the event loop by
+ * writing to the wakeup pipe which it monitors. */
+void clipQueueToEventThread(CLIPBACKEND *pCtx,
+ void (*proc)(void *, void *),
+ void *client_data)
+{
+ LogRel2(("clipQueueToEventThread: proc=%p, client_data=%p\n",
+ proc, client_data));
+#ifndef TESTCASE
+ XtAppAddTimeOut(pCtx->appContext, 0, (XtTimerCallbackProc)proc,
+ (XtPointer)client_data);
+ ssize_t cbWritten = write(pCtx->wakeupPipeWrite, WAKE_UP_STRING, WAKE_UP_STRING_LEN);
+ NOREF(cbWritten);
+#else
+ RT_NOREF1(pCtx);
+ testQueueToEventThread(proc, client_data);
+#endif
+}
+
+/**
+ * Report the formats currently supported by the X11 clipboard to VBox.
+ */
+static void clipReportFormatsToVBox(CLIPBACKEND *pCtx)
+{
+ uint32_t u32VBoxFormats = clipVBoxFormatForX11Format(pCtx->X11TextFormat);
+ u32VBoxFormats |= clipVBoxFormatForX11Format(pCtx->X11BitmapFormat);
+ u32VBoxFormats |= clipVBoxFormatForX11Format(pCtx->X11HTMLFormat);
+ LogRelFlowFunc(("clipReportFormatsToVBox format: %d\n", u32VBoxFormats));
+ LogRelFlowFunc(("clipReportFormatsToVBox txt: %d, bitm: %d, html:%d, u32VBoxFormats: %d\n",
+ pCtx->X11TextFormat, pCtx->X11BitmapFormat, pCtx->X11HTMLFormat,
+ u32VBoxFormats ));
+ ClipReportX11Formats(pCtx->pFrontend, u32VBoxFormats);
+}
+
+/**
+ * Forget which formats were previously in the X11 clipboard. Called when we
+ * grab the clipboard. */
+static void clipResetX11Formats(CLIPBACKEND *pCtx)
+{
+ pCtx->X11TextFormat = INVALID;
+ pCtx->X11BitmapFormat = INVALID;
+ pCtx->X11HTMLFormat = INVALID;
+}
+
+/** Tell VBox that X11 currently has nothing in its clipboard. */
+static void clipReportEmptyX11CB(CLIPBACKEND *pCtx)
+{
+ clipResetX11Formats(pCtx);
+ clipReportFormatsToVBox(pCtx);
+}
+
+/**
+ * Go through an array of X11 clipboard targets to see if they contain a text
+ * format we can support, and if so choose the ones we prefer (e.g. we like
+ * Utf8 better than plain text).
+ * @param pCtx the clipboard backend context structure
+ * @param pTargets the list of targets
+ * @param cTargets the size of the list in @a pTargets
+ */
+static CLIPX11FORMAT clipGetTextFormatFromTargets(CLIPBACKEND *pCtx,
+ CLIPX11FORMAT *pTargets,
+ size_t cTargets)
+{
+ CLIPX11FORMAT bestTextFormat = NIL_CLIPX11FORMAT;
+ CLIPFORMAT enmBestTextTarget = INVALID;
+ AssertPtrReturn(pCtx, NIL_CLIPX11FORMAT);
+ AssertReturn(VALID_PTR(pTargets) || cTargets == 0, NIL_CLIPX11FORMAT);
+ for (unsigned i = 0; i < cTargets; ++i)
+ {
+ CLIPX11FORMAT format = pTargets[i];
+ if (format != NIL_CLIPX11FORMAT)
+ {
+ if ( (clipVBoxFormatForX11Format(format)
+ == VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT)
+ && enmBestTextTarget < clipRealFormatForX11Format(format))
+ {
+ enmBestTextTarget = clipRealFormatForX11Format(format);
+ bestTextFormat = format;
+ }
+ }
+ }
+ return bestTextFormat;
+}
+
+#ifdef TESTCASE
+static bool clipTestTextFormatConversion(CLIPBACKEND *pCtx)
+{
+ bool success = true;
+ CLIPX11FORMAT targets[2];
+ CLIPX11FORMAT x11Format;
+ targets[0] = clipFindX11FormatByAtomText("text/plain");
+ targets[1] = clipFindX11FormatByAtomText("image/bmp");
+ x11Format = clipGetTextFormatFromTargets(pCtx, targets, 2);
+ if (clipRealFormatForX11Format(x11Format) != TEXT)
+ success = false;
+ targets[0] = clipFindX11FormatByAtomText("UTF8_STRING");
+ targets[1] = clipFindX11FormatByAtomText("text/plain");
+ x11Format = clipGetTextFormatFromTargets(pCtx, targets, 2);
+ if (clipRealFormatForX11Format(x11Format) != UTF8)
+ success = false;
+ return success;
+}
+#endif
+
+/**
+ * Go through an array of X11 clipboard targets to see if they contain a bitmap
+ * format we can support, and if so choose the ones we prefer (e.g. we like
+ * BMP better than PNG because we don't have to convert).
+ * @param pCtx the clipboard backend context structure
+ * @param pTargets the list of targets
+ * @param cTargets the size of the list in @a pTargets
+ */
+static CLIPX11FORMAT clipGetBitmapFormatFromTargets(CLIPBACKEND *pCtx,
+ CLIPX11FORMAT *pTargets,
+ size_t cTargets)
+{
+ CLIPX11FORMAT bestBitmapFormat = NIL_CLIPX11FORMAT;
+ CLIPFORMAT enmBestBitmapTarget = INVALID;
+ AssertPtrReturn(pCtx, NIL_CLIPX11FORMAT);
+ AssertReturn(VALID_PTR(pTargets) || cTargets == 0, NIL_CLIPX11FORMAT);
+ for (unsigned i = 0; i < cTargets; ++i)
+ {
+ CLIPX11FORMAT format = pTargets[i];
+ if (format != NIL_CLIPX11FORMAT)
+ {
+ if ( (clipVBoxFormatForX11Format(format)
+ == VBOX_SHARED_CLIPBOARD_FMT_BITMAP)
+ && enmBestBitmapTarget < clipRealFormatForX11Format(format))
+ {
+ enmBestBitmapTarget = clipRealFormatForX11Format(format);
+ bestBitmapFormat = format;
+ }
+ }
+ }
+ return bestBitmapFormat;
+}
+
+/**
+ * Go through an array of X11 clipboard targets to see if they contain a HTML
+ * format we can support, and if so choose the ones we prefer
+ * @param pCtx the clipboard backend context structure
+ * @param pTargets the list of targets
+ * @param cTargets the size of the list in @a pTargets
+ */
+static CLIPX11FORMAT clipGetHtmlFormatFromTargets(CLIPBACKEND *pCtx,
+ CLIPX11FORMAT *pTargets,
+ size_t cTargets)
+{
+ CLIPX11FORMAT bestHTMLFormat = NIL_CLIPX11FORMAT;
+ CLIPFORMAT enmBestHtmlTarget = INVALID;
+ AssertPtrReturn(pCtx, NIL_CLIPX11FORMAT);
+ AssertReturn(VALID_PTR(pTargets) || cTargets == 0, NIL_CLIPX11FORMAT);
+ for (unsigned i = 0; i < cTargets; ++i)
+ {
+ CLIPX11FORMAT format = pTargets[i];
+ if (format != NIL_CLIPX11FORMAT)
+ {
+ if ( (clipVBoxFormatForX11Format(format) == VBOX_SHARED_CLIPBOARD_FMT_HTML)
+ && enmBestHtmlTarget < clipRealFormatForX11Format(format))
+ {
+ enmBestHtmlTarget = clipRealFormatForX11Format(format);
+ bestHTMLFormat = format;
+ }
+ }
+ }
+ return bestHTMLFormat;
+}
+
+
+/**
+ * Go through an array of X11 clipboard targets to see if we can support any
+ * of them and if relevant to choose the ones we prefer (e.g. we like Utf8
+ * better than plain text).
+ * @param pCtx the clipboard backend context structure
+ * @param pTargets the list of targets
+ * @param cTargets the size of the list in @a pTargets
+ */
+static void clipGetFormatsFromTargets(CLIPBACKEND *pCtx,
+ CLIPX11FORMAT *pTargets, size_t cTargets)
+{
+ AssertPtrReturnVoid(pCtx);
+ AssertPtrReturnVoid(pTargets);
+ CLIPX11FORMAT bestTextFormat;
+ CLIPX11FORMAT bestBitmapFormat;
+ CLIPX11FORMAT bestHtmlFormat;
+ bestTextFormat = clipGetTextFormatFromTargets(pCtx, pTargets, cTargets);
+ if (pCtx->X11TextFormat != bestTextFormat)
+ {
+ pCtx->X11TextFormat = bestTextFormat;
+ }
+ pCtx->X11BitmapFormat = INVALID; /* not yet supported */
+ bestBitmapFormat = clipGetBitmapFormatFromTargets(pCtx, pTargets, cTargets);
+ if (pCtx->X11BitmapFormat != bestBitmapFormat)
+ {
+ pCtx->X11BitmapFormat = bestBitmapFormat;
+ }
+ bestHtmlFormat = clipGetHtmlFormatFromTargets(pCtx, pTargets, cTargets);
+ if(pCtx->X11HTMLFormat != bestHtmlFormat)
+ {
+ pCtx->X11HTMLFormat = bestHtmlFormat;
+ }
+}
+
+static void clipQueryX11CBFormats(CLIPBACKEND *pCtx);
+
+/**
+ * Update the context's information about targets currently supported by X11,
+ * based on an array of X11 atoms.
+ * @param pCtx the context to be updated
+ * @param pTargets the array of atoms describing the targets supported
+ * @param cTargets the size of the array @a pTargets
+ */
+static void clipUpdateX11Targets(CLIPBACKEND *pCtx, CLIPX11FORMAT *pTargets,
+ size_t cTargets)
+{
+ LogRel2 (("%s: called\n", __FUNCTION__));
+#ifndef VBOX_AFTER_5_2
+ pCtx->fBusy = false;
+ if (pCtx->fUpdateNeeded)
+ {
+ /* We may already be out of date. */
+ pCtx->fUpdateNeeded = false;
+ clipQueryX11CBFormats(pCtx);
+ return;
+ }
+#endif
+ if (pTargets == NULL) {
+ /* No data available */
+ clipReportEmptyX11CB(pCtx);
+ return;
+ }
+ clipGetFormatsFromTargets(pCtx, pTargets, cTargets);
+ clipReportFormatsToVBox(pCtx);
+}
+
+/**
+ * Notify the VBox clipboard about available data formats, based on the
+ * "targets" information obtained from the X11 clipboard.
+ * @note Callback for XtGetSelectionValue
+ * @note This function is treated as API glue, and as such is not part of any
+ * unit test. So keep it simple, be paranoid and log everything.
+ */
+static void clipConvertX11Targets(Widget widget, XtPointer pClientData,
+ Atom * /* selection */, Atom *atomType,
+ XtPointer pValue, long unsigned int *pcLen,
+ int *piFormat)
+{
+ RT_NOREF1(piFormat);
+ CLIPBACKEND *pCtx = reinterpret_cast<CLIPBACKEND *>(pClientData);
+ Atom *pAtoms = (Atom *)pValue;
+ unsigned i, j;
+ LogRel2(("%s: pValue=%p, *pcLen=%u, *atomType=%d%s\n", __FUNCTION__,
+ pValue, *pcLen, *atomType,
+ *atomType == XT_CONVERT_FAIL ? " (XT_CONVERT_FAIL)" : ""));
+ CLIPX11FORMAT *pFormats = NULL;
+ if (*pcLen && pValue && (*atomType != XT_CONVERT_FAIL /* time out */))
+ pFormats = (CLIPX11FORMAT *)RTMemAllocZ(*pcLen * sizeof(CLIPX11FORMAT));
+#if defined(DEBUG) && !defined(TESTCASE)
+ if (pValue)
+ {
+ for (i = 0; i < *pcLen; ++i)
+ if (pAtoms[i])
+ {
+ char *pszName = XGetAtomName(XtDisplay(widget), pAtoms[i]);
+ LogRel2(("%s: found target %s\n", __FUNCTION__,
+ pszName));
+ XFree(pszName);
+ }
+ else
+ LogRel2(("%s: found empty target.\n", __FUNCTION__));
+ }
+#endif
+ if (pFormats)
+ {
+ for (i = 0; i < *pcLen; ++i)
+ {
+ for (j = 0; j < RT_ELEMENTS(g_aFormats); ++j)
+ {
+ Atom target = XInternAtom(XtDisplay(widget),
+ g_aFormats[j].pcszAtom, False);
+ if (*(pAtoms + i) == target)
+ pFormats[i] = j;
+ }
+#if defined(DEBUG) && !defined(TESTCASE)
+ LogRel2(("%s: reporting format %d (%s)\n", __FUNCTION__,
+ pFormats[i], g_aFormats[pFormats[i]].pcszAtom));
+#endif
+ }
+ }
+ else
+ LogRel2(("%s: reporting empty targets (none reported or allocation failure).\n",
+ __FUNCTION__));
+ clipUpdateX11Targets(pCtx, pFormats, *pcLen);
+ RTMemFree(pFormats);
+ XtFree(reinterpret_cast<char *>(pValue));
+}
+
+#ifdef TESTCASE
+ void testRequestTargets(CLIPBACKEND *pCtx);
+#endif
+
+/**
+ * Callback to notify us when the contents of the X11 clipboard change.
+ */
+static void clipQueryX11CBFormats(CLIPBACKEND *pCtx)
+{
+ LogRel2 (("%s: requesting the targets that the X11 clipboard offers\n",
+ __PRETTY_FUNCTION__));
+#ifndef VBOX_AFTER_5_2
+ if (pCtx->fBusy)
+ {
+ pCtx->fUpdateNeeded = true;
+ return;
+ }
+ pCtx->fBusy = true;
+#endif
+#ifndef TESTCASE
+ XtGetSelectionValue(pCtx->widget,
+ clipGetAtom(pCtx, "CLIPBOARD"),
+ clipGetAtom(pCtx, "TARGETS"),
+ clipConvertX11Targets, pCtx,
+ CurrentTime);
+#else
+ testRequestTargets(pCtx);
+#endif
+}
+
+#ifndef TESTCASE
+
+typedef struct {
+ int type; /* event base */
+ unsigned long serial;
+ Bool send_event;
+ Display *display;
+ Window window;
+ int subtype;
+ Window owner;
+ Atom selection;
+ Time timestamp;
+ Time selection_timestamp;
+} XFixesSelectionNotifyEvent;
+
+/**
+ * Wait until an event arrives and handle it if it is an XFIXES selection
+ * event, which Xt doesn't know about.
+ */
+void clipPeekEventAndDoXFixesHandling(CLIPBACKEND *pCtx)
+{
+ union
+ {
+ XEvent event;
+ XFixesSelectionNotifyEvent fixes;
+ } event = { { 0 } };
+
+ if (XtAppPeekEvent(pCtx->appContext, &event.event))
+ if ( (event.event.type == pCtx->fixesEventBase)
+ && (event.fixes.owner != XtWindow(pCtx->widget)))
+ {
+ if ( (event.fixes.subtype == 0 /* XFixesSetSelectionOwnerNotify */)
+ && (event.fixes.owner != 0))
+ clipQueryX11CBFormats(pCtx);
+ else
+ clipReportEmptyX11CB(pCtx);
+ }
+}
+
+/**
+ * The main loop of our clipboard reader.
+ * @note X11 backend code.
+ */
+static DECLCALLBACK(int) clipEventThread(RTTHREAD hThreadSelf, void *pvUser)
+{
+ RT_NOREF1(hThreadSelf);
+ LogRel(("Shared clipboard: Starting shared clipboard thread\n"));
+
+ CLIPBACKEND *pCtx = (CLIPBACKEND *)pvUser;
+
+ if (pCtx->fGrabClipboardOnStart)
+ clipQueryX11CBFormats(pCtx);
+ while (XtAppGetExitFlag(pCtx->appContext) == FALSE)
+ {
+ clipPeekEventAndDoXFixesHandling(pCtx);
+ XtAppProcessEvent(pCtx->appContext, XtIMAll);
+ }
+ LogRel(("Shared clipboard: Shared clipboard thread terminated successfully\n"));
+ return VINF_SUCCESS;
+}
+#endif
+
+/** X11 specific uninitialisation for the shared clipboard.
+ * @note X11 backend code.
+ */
+static void clipUninit(CLIPBACKEND *pCtx)
+{
+ AssertPtrReturnVoid(pCtx);
+ if (pCtx->widget)
+ {
+ /* Valid widget + invalid appcontext = bug. But don't return yet. */
+ AssertPtr(pCtx->appContext);
+ clipUnregisterContext(pCtx);
+ XtDestroyWidget(pCtx->widget);
+ }
+ pCtx->widget = NULL;
+ if (pCtx->appContext)
+ XtDestroyApplicationContext(pCtx->appContext);
+ pCtx->appContext = NULL;
+ if (pCtx->wakeupPipeRead != 0)
+ close(pCtx->wakeupPipeRead);
+ if (pCtx->wakeupPipeWrite != 0)
+ close(pCtx->wakeupPipeWrite);
+ pCtx->wakeupPipeRead = 0;
+ pCtx->wakeupPipeWrite = 0;
+}
+
+/** Worker function for stopping the clipboard which runs on the event
+ * thread. */
+static void clipStopEventThreadWorker(void *pUserData, void *)
+{
+
+ CLIPBACKEND *pCtx = (CLIPBACKEND *)pUserData;
+
+ /* This might mean that we are getting stopped twice. */
+ Assert(pCtx->widget != NULL);
+
+ /* Set the termination flag to tell the Xt event loop to exit. We
+ * reiterate that any outstanding requests from the X11 event loop to
+ * the VBox part *must* have returned before we do this. */
+ XtAppSetExitFlag(pCtx->appContext);
+}
+
+#ifndef TESTCASE
+/** Setup the XFixes library and load the XFixesSelectSelectionInput symbol */
+static int clipLoadXFixes(Display *pDisplay, CLIPBACKEND *pCtx)
+{
+ int rc;
+
+ void *hFixesLib = dlopen("libXfixes.so.1", RTLD_LAZY);
+ if (!hFixesLib)
+ hFixesLib = dlopen("libXfixes.so.2", RTLD_LAZY);
+ if (!hFixesLib)
+ hFixesLib = dlopen("libXfixes.so.3", RTLD_LAZY);
+ if (!hFixesLib)
+ hFixesLib = dlopen("libXfixes.so.4", RTLD_LAZY);
+ if (hFixesLib)
+ {
+ /* For us, a NULL function pointer is a failure */
+ pCtx->fixesSelectInput = (void (*)(Display *, Window, Atom, long unsigned int))
+ (uintptr_t)dlsym(hFixesLib, "XFixesSelectSelectionInput");
+ if (pCtx->fixesSelectInput)
+ {
+ int dummy1 = 0;
+ int dummy2 = 0;
+ if (XQueryExtension(pDisplay, "XFIXES", &dummy1, &pCtx->fixesEventBase, &dummy2) != 0)
+ {
+ if (pCtx->fixesEventBase >= 0)
+ rc = VINF_SUCCESS;
+ else
+ {
+ LogRel(("clipLoadXFixes: fixesEventBase is less than zero: %d\n", pCtx->fixesEventBase));
+ rc = VERR_NOT_SUPPORTED;
+ }
+ }
+ else
+ {
+ LogRel(("clipLoadXFixes: XQueryExtension failed\n"));
+ rc = VERR_NOT_SUPPORTED;
+ }
+ }
+ else
+ {
+ LogRel(("clipLoadXFixes: Symbol XFixesSelectSelectionInput not found!\n"));
+ rc = VERR_NOT_SUPPORTED;
+ }
+ }
+ else
+ {
+ LogRel(("clipLoadXFixes: libxFixes.so.* not found!\n"));
+ rc = VERR_NOT_SUPPORTED;
+ }
+ return rc;
+}
+#endif
+
+/** This is the callback which is scheduled when data is available on the
+ * wakeup pipe. It simply reads all data from the pipe. */
+static void clipDrainWakeupPipe(XtPointer pUserData, int *, XtInputId *)
+{
+ CLIPBACKEND *pCtx = (CLIPBACKEND *)pUserData;
+ char acBuf[WAKE_UP_STRING_LEN];
+
+ LogRel2(("clipDrainWakeupPipe: called\n"));
+ while (read(pCtx->wakeupPipeRead, acBuf, sizeof(acBuf)) > 0) {}
+}
+
+/** X11 specific initialisation for the shared clipboard.
+ * @note X11 backend code.
+ */
+static int clipInit(CLIPBACKEND *pCtx)
+{
+ /* Create a window and make it a clipboard viewer. */
+ int cArgc = 0;
+ char *pcArgv = 0;
+ int rc = VINF_SUCCESS;
+ Display *pDisplay;
+
+ /* Make sure we are thread safe */
+ XtToolkitThreadInitialize();
+ /* Set up the Clipboard application context and main window. We call all
+ * these functions directly instead of calling XtOpenApplication() so
+ * that we can fail gracefully if we can't get an X11 display. */
+ XtToolkitInitialize();
+ pCtx->appContext = XtCreateApplicationContext();
+ pDisplay = XtOpenDisplay(pCtx->appContext, 0, 0, "VBoxClipboard", 0, 0, &cArgc, &pcArgv);
+ if (NULL == pDisplay)
+ {
+ LogRel(("Shared clipboard: Failed to connect to the X11 clipboard - the window system may not be running.\n"));
+ rc = VERR_NOT_SUPPORTED;
+ }
+#ifndef TESTCASE
+ if (RT_SUCCESS(rc))
+ {
+ rc = clipLoadXFixes(pDisplay, pCtx);
+ if (RT_FAILURE(rc))
+ LogRel(("Shared clipboard: Failed to load the XFIXES extension.\n"));
+ }
+#endif
+ if (RT_SUCCESS(rc))
+ {
+ pCtx->widget = XtVaAppCreateShell(0, "VBoxClipboard",
+ applicationShellWidgetClass,
+ pDisplay, XtNwidth, 1, XtNheight,
+ 1, NULL);
+ if (NULL == pCtx->widget)
+ {
+ LogRel(("Shared clipboard: Failed to construct the X11 window for the shared clipboard manager.\n"));
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ rc = clipRegisterContext(pCtx);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ XtSetMappedWhenManaged(pCtx->widget, false);
+ XtRealizeWidget(pCtx->widget);
+#ifndef TESTCASE
+ /* Enable clipboard update notification */
+ pCtx->fixesSelectInput(pDisplay, XtWindow(pCtx->widget),
+ clipGetAtom(pCtx, "CLIPBOARD"),
+ 7 /* All XFixes*Selection*NotifyMask flags */);
+#endif
+ }
+ /* Create the pipes */
+ int pipes[2];
+ if (!pipe(pipes))
+ {
+ pCtx->wakeupPipeRead = pipes[0];
+ pCtx->wakeupPipeWrite = pipes[1];
+ if (!XtAppAddInput(pCtx->appContext, pCtx->wakeupPipeRead,
+ (XtPointer) XtInputReadMask,
+ clipDrainWakeupPipe, (XtPointer) pCtx))
+ rc = VERR_NO_MEMORY; /* What failure means is not doc'ed. */
+ if ( RT_SUCCESS(rc)
+ && (fcntl(pCtx->wakeupPipeRead, F_SETFL, O_NONBLOCK) != 0))
+ rc = RTErrConvertFromErrno(errno);
+ if (RT_FAILURE(rc))
+ LogRel(("Shared clipboard: Failed to setup the termination mechanism.\n"));
+ }
+ else
+ rc = RTErrConvertFromErrno(errno);
+ if (RT_FAILURE(rc))
+ clipUninit(pCtx);
+ if (RT_FAILURE(rc))
+ LogRel(("Shared clipboard: Initialisation failed: %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Construct the X11 backend of the shared clipboard.
+ * @note X11 backend code
+ */
+CLIPBACKEND *ClipConstructX11(VBOXCLIPBOARDCONTEXT *pFrontend, bool fHeadless)
+{
+ CLIPBACKEND *pCtx = (CLIPBACKEND *)RTMemAllocZ(sizeof(CLIPBACKEND));
+ if (pCtx && fHeadless)
+ {
+ /*
+ * If we don't find the DISPLAY environment variable we assume that
+ * we are not connected to an X11 server. Don't actually try to do
+ * this then, just fail silently and report success on every call.
+ * This is important for VBoxHeadless.
+ */
+ LogRelFunc(("X11 DISPLAY variable not set -- disabling shared clipboard\n"));
+ pCtx->fHaveX11 = false;
+ return pCtx;
+ }
+
+ pCtx->fHaveX11 = true;
+
+ LogRel(("Shared clipboard: Initializing X11 clipboard backend\n"));
+ if (pCtx)
+ pCtx->pFrontend = pFrontend;
+ return pCtx;
+}
+
+/**
+ * Destruct the shared clipboard X11 backend.
+ * @note X11 backend code
+ */
+void ClipDestructX11(CLIPBACKEND *pCtx)
+{
+ if (pCtx->fHaveX11)
+ /* We set this to NULL when the event thread exits. It really should
+ * have exited at this point, when we are about to unload the code from
+ * memory. */
+ Assert(pCtx->widget == NULL);
+ RTMemFree(pCtx);
+}
+
+/**
+ * Announce to the X11 backend that we are ready to start.
+ * @param grab whether we should try to grab the shared clipboard at once
+ */
+int ClipStartX11(CLIPBACKEND *pCtx, bool grab)
+{
+ int rc = VINF_SUCCESS;
+ LogRelFlowFunc(("\n"));
+ /*
+ * Immediately return if we are not connected to the X server.
+ */
+ if (!pCtx->fHaveX11)
+ return VINF_SUCCESS;
+
+ rc = clipInit(pCtx);
+ if (RT_SUCCESS(rc))
+ {
+ clipResetX11Formats(pCtx);
+ pCtx->fGrabClipboardOnStart = grab;
+ }
+#ifndef TESTCASE
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTThreadCreate(&pCtx->thread, clipEventThread, pCtx, 0,
+ RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "SHCLIP");
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("Shared clipboard: Failed to start the shared clipboard thread.\n"));
+ clipUninit(pCtx);
+ }
+ }
+#endif
+ return rc;
+}
+
+/**
+ * Shut down the shared clipboard X11 backend.
+ * @note X11 backend code
+ * @note Any requests from this object to get clipboard data from VBox
+ * *must* have completed or aborted before we are called, as
+ * otherwise the X11 event loop will still be waiting for the request
+ * to return and will not be able to terminate.
+ */
+int ClipStopX11(CLIPBACKEND *pCtx)
+{
+ int rc, rcThread;
+ unsigned count = 0;
+ /*
+ * Immediately return if we are not connected to the X server.
+ */
+ if (!pCtx->fHaveX11)
+ return VINF_SUCCESS;
+
+ LogRelFunc(("stopping the shared clipboard X11 backend\n"));
+ /* Write to the "stop" pipe */
+ clipQueueToEventThread(pCtx, clipStopEventThreadWorker, (XtPointer) pCtx);
+#ifndef TESTCASE
+ do
+ {
+ rc = RTThreadWait(pCtx->thread, 1000, &rcThread);
+ ++count;
+ Assert(RT_SUCCESS(rc) || ((VERR_TIMEOUT == rc) && (count != 5)));
+ } while ((VERR_TIMEOUT == rc) && (count < 300));
+#else
+ rc = VINF_SUCCESS;
+ rcThread = VINF_SUCCESS;
+ RT_NOREF_PV(count);
+#endif
+ if (RT_SUCCESS(rc))
+ AssertRC(rcThread);
+ else
+ LogRelFunc(("rc=%Rrc\n", rc));
+ clipUninit(pCtx);
+ LogRelFlowFunc(("returning %Rrc.\n", rc));
+ RT_NOREF_PV(rcThread);
+ return rc;
+}
+
+/**
+ * Satisfy a request from X11 for clipboard targets supported by VBox.
+ *
+ * @returns iprt status code
+ * @param atomTypeReturn The type of the data we are returning
+ * @param pValReturn A pointer to the data we are returning. This
+ * should be set to memory allocated by XtMalloc,
+ * which will be freed later by the Xt toolkit.
+ * @param pcLenReturn The length of the data we are returning
+ * @param piFormatReturn The format (8bit, 16bit, 32bit) of the data we are
+ * returning
+ * @note X11 backend code, called by the XtOwnSelection callback.
+ */
+static int clipCreateX11Targets(CLIPBACKEND *pCtx, Atom *atomTypeReturn,
+ XtPointer *pValReturn,
+ unsigned long *pcLenReturn,
+ int *piFormatReturn)
+{
+ Atom *atomTargets = (Atom *)XtMalloc( (MAX_CLIP_X11_FORMATS + 3)
+ * sizeof(Atom));
+ unsigned cTargets = 0;
+ LogRelFlowFunc (("called\n"));
+ CLIPX11FORMAT format = NIL_CLIPX11FORMAT;
+ do
+ {
+ format = clipEnumX11Formats(pCtx->vboxFormats, format);
+ if (format != NIL_CLIPX11FORMAT)
+ {
+ atomTargets[cTargets] = clipAtomForX11Format(pCtx, format);
+ ++cTargets;
+ }
+ } while (format != NIL_CLIPX11FORMAT);
+ /* We always offer these */
+ atomTargets[cTargets] = clipGetAtom(pCtx, "TARGETS");
+ atomTargets[cTargets + 1] = clipGetAtom(pCtx, "MULTIPLE");
+ atomTargets[cTargets + 2] = clipGetAtom(pCtx, "TIMESTAMP");
+ *atomTypeReturn = XA_ATOM;
+ *pValReturn = (XtPointer)atomTargets;
+ *pcLenReturn = cTargets + 3;
+ *piFormatReturn = 32;
+ return VINF_SUCCESS;
+}
+
+/** This is a wrapper around ClipRequestDataForX11 that will cache the
+ * data returned.
+ */
+static int clipReadVBoxClipboard(CLIPBACKEND *pCtx, uint32_t u32Format,
+ void **ppv, uint32_t *pcb)
+{
+ int rc = VINF_SUCCESS;
+ LogRelFlowFunc(("pCtx=%p, u32Format=%02X, ppv=%p, pcb=%p\n", pCtx,
+ u32Format, ppv, pcb));
+ if (u32Format == VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT)
+ {
+ if (pCtx->pvUnicodeCache == NULL)
+ rc = ClipRequestDataForX11(pCtx->pFrontend, u32Format,
+ &pCtx->pvUnicodeCache,
+ &pCtx->cbUnicodeCache);
+ if (RT_SUCCESS(rc))
+ {
+ *ppv = RTMemDup(pCtx->pvUnicodeCache, pCtx->cbUnicodeCache);
+ *pcb = pCtx->cbUnicodeCache;
+ if (*ppv == NULL)
+ rc = VERR_NO_MEMORY;
+ }
+ }
+ else
+ rc = ClipRequestDataForX11(pCtx->pFrontend, u32Format,
+ ppv, pcb);
+ LogRelFlowFunc(("returning %Rrc\n", rc));
+ if (RT_SUCCESS(rc))
+ LogRelFlowFunc(("*ppv=%.*ls, *pcb=%u\n", *pcb, *ppv, *pcb));
+ return rc;
+}
+
+/**
+ * Calculate a buffer size large enough to hold the source Windows format
+ * text converted into Unix Utf8, including the null terminator
+ * @returns iprt status code
+ * @param pwsz the source text in UCS-2 with Windows EOLs
+ * @param cwc the size in USC-2 elements of the source text, with or
+ * without the terminator
+ * @param pcbActual where to store the buffer size needed
+ */
+static int clipWinTxtBufSizeForUtf8(PRTUTF16 pwsz, size_t cwc,
+ size_t *pcbActual)
+{
+ size_t cbRet = 0;
+ int rc = RTUtf16CalcUtf8LenEx(pwsz, cwc, &cbRet);
+ if (RT_SUCCESS(rc))
+ *pcbActual = cbRet + 1; /* null terminator */
+ return rc;
+}
+
+/**
+ * Convert text from Windows format (UCS-2 with CRLF line endings) to standard
+ * Utf-8.
+ *
+ * @returns iprt status code
+ *
+ * @param pwszSrc the text to be converted
+ * @param cbSrc the length of @a pwszSrc in bytes
+ * @param pszBuf where to write the converted string
+ * @param cbBuf the size of the buffer pointed to by @a pszBuf
+ * @param pcbActual where to store the size of the converted string.
+ * optional.
+ */
+static int clipWinTxtToUtf8(PRTUTF16 pwszSrc, size_t cbSrc, char *pszBuf,
+ size_t cbBuf, size_t *pcbActual)
+{
+ PRTUTF16 pwszTmp = NULL;
+ size_t cwSrc = cbSrc / 2, cwTmp = 0, cbDest = 0;
+ int rc = VINF_SUCCESS;
+
+ LogRelFlowFunc (("pwszSrc=%.*ls, cbSrc=%u\n", cbSrc, pwszSrc, cbSrc));
+ /* How long will the converted text be? */
+ AssertPtr(pwszSrc);
+ AssertPtr(pszBuf);
+ rc = vboxClipboardUtf16GetLinSize(pwszSrc, cwSrc, &cwTmp);
+ if (RT_SUCCESS(rc) && cwTmp == 0)
+ rc = VERR_NO_DATA;
+ if (RT_SUCCESS(rc))
+ pwszTmp = (PRTUTF16)RTMemAlloc(cwTmp * 2);
+ if (!pwszTmp)
+ rc = VERR_NO_MEMORY;
+ /* Convert the text. */
+ if (RT_SUCCESS(rc))
+ rc = vboxClipboardUtf16WinToLin(pwszSrc, cwSrc, pwszTmp, cwTmp);
+ if (RT_SUCCESS(rc))
+ /* Convert the Utf16 string to Utf8. */
+ rc = RTUtf16ToUtf8Ex(pwszTmp + 1, cwTmp - 1, &pszBuf, cbBuf,
+ &cbDest);
+ RTMemFree(reinterpret_cast<void *>(pwszTmp));
+ if (pcbActual)
+ *pcbActual = cbDest + 1;
+ LogRelFlowFunc(("returning %Rrc\n", rc));
+ if (RT_SUCCESS(rc))
+ LogRelFlowFunc (("converted string is %.*s. Returning.\n", cbDest,
+ pszBuf));
+ return rc;
+}
+
+/**
+ * Satisfy a request from X11 to convert the clipboard text to Utf-8. We
+ * return null-terminated text, but can cope with non-null-terminated input.
+ *
+ * @returns iprt status code
+ * @param pDisplay an X11 display structure, needed for conversions
+ * performed by Xlib
+ * @param pv the text to be converted (UCS-2 with Windows EOLs)
+ * @param cb the length of the text in @cb in bytes
+ * @param atomTypeReturn where to store the atom for the type of the data
+ * we are returning
+ * @param pValReturn where to store the pointer to the data we are
+ * returning. This should be to memory allocated by
+ * XtMalloc, which will be freed by the Xt toolkit
+ * later.
+ * @param pcLenReturn where to store the length of the data we are
+ * returning
+ * @param piFormatReturn where to store the bit width (8, 16, 32) of the
+ * data we are returning
+ */
+static int clipWinTxtToUtf8ForX11CB(Display *pDisplay, PRTUTF16 pwszSrc,
+ size_t cbSrc, Atom *atomTarget,
+ Atom *atomTypeReturn,
+ XtPointer *pValReturn,
+ unsigned long *pcLenReturn,
+ int *piFormatReturn)
+{
+ RT_NOREF2(pDisplay, pcLenReturn);
+
+ /* This may slightly overestimate the space needed. */
+ size_t cbDest = 0;
+ int rc = clipWinTxtBufSizeForUtf8(pwszSrc, cbSrc / 2, &cbDest);
+ if (RT_SUCCESS(rc))
+ {
+ char *pszDest = (char *)XtMalloc(cbDest);
+ size_t cbActual = 0;
+ if (pszDest)
+ rc = clipWinTxtToUtf8(pwszSrc, cbSrc, pszDest, cbDest,
+ &cbActual);
+ if (RT_SUCCESS(rc))
+ {
+ *atomTypeReturn = *atomTarget;
+ *pValReturn = (XtPointer)pszDest;
+ *pcLenReturn = cbActual;
+ *piFormatReturn = 8;
+ }
+ }
+ return rc;
+}
+
+/**
+ * Satisfy a request from X11 to convert the clipboard HTML fragment to Utf-8. We
+ * return null-terminated text, but can cope with non-null-terminated input.
+ *
+ * @returns iprt status code
+ * @param pDisplay an X11 display structure, needed for conversions
+ * performed by Xlib
+ * @param pv the text to be converted (UTF8 with Windows EOLs)
+ * @param cb the length of the text in @cb in bytes
+ * @param atomTypeReturn where to store the atom for the type of the data
+ * we are returning
+ * @param pValReturn where to store the pointer to the data we are
+ * returning. This should be to memory allocated by
+ * XtMalloc, which will be freed by the Xt toolkit
+ * later.
+ * @param pcLenReturn where to store the length of the data we are
+ * returning
+ * @param piFormatReturn where to store the bit width (8, 16, 32) of the
+ * data we are returning
+ */
+static int clipWinHTMLToUtf8ForX11CB(Display *pDisplay, const char *pszSrc,
+ size_t cbSrc, Atom *atomTarget,
+ Atom *atomTypeReturn,
+ XtPointer *pValReturn,
+ unsigned long *pcLenReturn,
+ int *piFormatReturn)
+{
+ RT_NOREF2(pDisplay, pValReturn);
+
+ /* This may slightly overestimate the space needed. */
+ LogRelFlowFunc(("source: %s", pszSrc));
+
+ char *pszDest = (char *)XtMalloc(cbSrc);
+ if(pszDest == NULL)
+ return VERR_NO_MEMORY;
+
+ memcpy(pszDest, pszSrc, cbSrc);
+
+ *atomTypeReturn = *atomTarget;
+ *pValReturn = (XtPointer)pszDest;
+ *pcLenReturn = cbSrc;
+ *piFormatReturn = 8;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Does this atom correspond to one of the two selection types we support?
+ * @param widget a valid Xt widget
+ * @param selType the atom in question
+ */
+static bool clipIsSupportedSelectionType(CLIPBACKEND *pCtx, Atom selType)
+{
+ return( (selType == clipGetAtom(pCtx, "CLIPBOARD"))
+ || (selType == clipGetAtom(pCtx, "PRIMARY")));
+}
+
+/**
+ * Remove a trailing nul character from a string by adjusting the string
+ * length. Some X11 applications don't like zero-terminated text...
+ * @param pText the text in question
+ * @param pcText the length of the text, adjusted on return
+ * @param format the format of the text
+ */
+static void clipTrimTrailingNul(XtPointer pText, unsigned long *pcText,
+ CLIPFORMAT format)
+{
+ AssertPtrReturnVoid(pText);
+ AssertPtrReturnVoid(pcText);
+ AssertReturnVoid((format == UTF8) || (format == TEXT) || (format == HTML));
+ if (((char *)pText)[*pcText - 1] == '\0')
+ --(*pcText);
+}
+
+static int clipConvertVBoxCBForX11(CLIPBACKEND *pCtx, Atom *atomTarget,
+ Atom *atomTypeReturn,
+ XtPointer *pValReturn,
+ unsigned long *pcLenReturn,
+ int *piFormatReturn)
+{
+ int rc = VINF_SUCCESS;
+ CLIPX11FORMAT x11Format = clipFindX11FormatByAtom(pCtx, *atomTarget);
+ CLIPFORMAT format = clipRealFormatForX11Format(x11Format);
+ if ( ((format == UTF8) || (format == TEXT))
+ && (pCtx->vboxFormats & VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT))
+ {
+ void *pv = NULL;
+ uint32_t cb = 0;
+ rc = clipReadVBoxClipboard(pCtx,
+ VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT,
+ &pv, &cb);
+ if (RT_SUCCESS(rc) && (cb == 0))
+ rc = VERR_NO_DATA;
+ if (RT_SUCCESS(rc) && ((format == UTF8) || (format == TEXT)))
+ rc = clipWinTxtToUtf8ForX11CB(XtDisplay(pCtx->widget),
+ (PRTUTF16)pv, cb, atomTarget,
+ atomTypeReturn, pValReturn,
+ pcLenReturn, piFormatReturn);
+ if (RT_SUCCESS(rc))
+ clipTrimTrailingNul(*(XtPointer *)pValReturn, pcLenReturn, format);
+ RTMemFree(pv);
+ }
+ else if ( (format == BMP)
+ && (pCtx->vboxFormats & VBOX_SHARED_CLIPBOARD_FMT_BITMAP))
+ {
+ void *pv = NULL;
+ uint32_t cb = 0;
+ rc = clipReadVBoxClipboard(pCtx,
+ VBOX_SHARED_CLIPBOARD_FMT_BITMAP,
+ &pv, &cb);
+ if (RT_SUCCESS(rc) && (cb == 0))
+ rc = VERR_NO_DATA;
+ if (RT_SUCCESS(rc) && (format == BMP))
+ {
+ /* Create a full BMP from it */
+ rc = vboxClipboardDibToBmp(pv, cb, (void **)pValReturn,
+ (size_t *)pcLenReturn);
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+
+ if (RT_SUCCESS(rc))
+ {
+ *atomTypeReturn = *atomTarget;
+ *piFormatReturn = 8;
+ }
+ RTMemFree(pv);
+ }
+ else if ( (format == HTML)
+ && (pCtx->vboxFormats & VBOX_SHARED_CLIPBOARD_FMT_HTML))
+ {
+ void *pv = NULL;
+ uint32_t cb = 0;
+ rc = clipReadVBoxClipboard(pCtx,
+ VBOX_SHARED_CLIPBOARD_FMT_HTML,
+ &pv, &cb);
+ if (RT_SUCCESS(rc) && (cb == 0))
+ rc = VERR_NO_DATA;
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * The common VBox HTML encoding will be - Utf8
+ * becuase it more general for HTML formats then UTF16
+ * X11 clipboard returns UTF16, so before sending it we should
+ * convert it to UTF8
+ * It's very strange but here we get utf16 from x11 clipboard
+ * in same time we send utf8 to x11 clipboard and it's work
+ */
+ rc = clipWinHTMLToUtf8ForX11CB(XtDisplay(pCtx->widget),
+ (const char*)pv, cb, atomTarget,
+ atomTypeReturn, pValReturn,
+ pcLenReturn, piFormatReturn);
+
+
+ if (RT_SUCCESS(rc))
+ clipTrimTrailingNul(*(XtPointer *)pValReturn, pcLenReturn, format);
+ RTMemFree(pv);
+ }
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+ return rc;
+}
+
+/**
+ * Return VBox's clipboard data for an X11 client.
+ * @note X11 backend code, callback for XtOwnSelection
+ */
+static Boolean clipXtConvertSelectionProc(Widget widget, Atom *atomSelection,
+ Atom *atomTarget,
+ Atom *atomTypeReturn,
+ XtPointer *pValReturn,
+ unsigned long *pcLenReturn,
+ int *piFormatReturn)
+{
+ CLIPBACKEND *pCtx = clipLookupContext(widget);
+ int rc = VINF_SUCCESS;
+
+ LogRelFlowFunc(("\n"));
+ if (!pCtx)
+ return false;
+ if (!clipIsSupportedSelectionType(pCtx, *atomSelection))
+ return false;
+ if (*atomTarget == clipGetAtom(pCtx, "TARGETS"))
+ rc = clipCreateX11Targets(pCtx, atomTypeReturn, pValReturn,
+ pcLenReturn, piFormatReturn);
+ else
+ rc = clipConvertVBoxCBForX11(pCtx, atomTarget, atomTypeReturn,
+ pValReturn, pcLenReturn, piFormatReturn);
+ LogRelFlowFunc(("returning, internal status code %Rrc\n", rc));
+ return RT_SUCCESS(rc);
+}
+
+/** Structure used to pass information about formats that VBox supports */
+typedef struct _CLIPNEWVBOXFORMATS
+{
+ /** Context information for the X11 clipboard */
+ CLIPBACKEND *pCtx;
+ /** Formats supported by VBox */
+ uint32_t formats;
+} CLIPNEWVBOXFORMATS;
+
+/** Invalidates the local cache of the data in the VBox clipboard. */
+static void clipInvalidateVBoxCBCache(CLIPBACKEND *pCtx)
+{
+ if (pCtx->pvUnicodeCache != NULL)
+ {
+ RTMemFree(pCtx->pvUnicodeCache);
+ pCtx->pvUnicodeCache = NULL;
+ }
+}
+
+/**
+ * Take possession of the X11 clipboard (and middle-button selection).
+ */
+static void clipGrabX11CB(CLIPBACKEND *pCtx, uint32_t u32Formats)
+{
+ if (XtOwnSelection(pCtx->widget, clipGetAtom(pCtx, "CLIPBOARD"),
+ CurrentTime, clipXtConvertSelectionProc, NULL, 0))
+ {
+ pCtx->vboxFormats = u32Formats;
+ /* Grab the middle-button paste selection too. */
+ XtOwnSelection(pCtx->widget, clipGetAtom(pCtx, "PRIMARY"),
+ CurrentTime, clipXtConvertSelectionProc, NULL, 0);
+#ifndef TESTCASE
+ /* Xt suppresses these if we already own the clipboard, so send them
+ * ourselves. */
+ XSetSelectionOwner(XtDisplay(pCtx->widget),
+ clipGetAtom(pCtx, "CLIPBOARD"),
+ XtWindow(pCtx->widget), CurrentTime);
+ XSetSelectionOwner(XtDisplay(pCtx->widget),
+ clipGetAtom(pCtx, "PRIMARY"),
+ XtWindow(pCtx->widget), CurrentTime);
+#endif
+ }
+}
+
+/**
+ * Worker function for ClipAnnounceFormatToX11 which runs on the
+ * event thread.
+ * @param pUserData Pointer to a CLIPNEWVBOXFORMATS structure containing
+ * information about the VBox formats available and the
+ * clipboard context data. Must be freed by the worker.
+ */
+static void clipNewVBoxFormatsWorker(void *pUserData,
+ void * /* interval */)
+{
+ CLIPNEWVBOXFORMATS *pFormats = (CLIPNEWVBOXFORMATS *)pUserData;
+ CLIPBACKEND *pCtx = pFormats->pCtx;
+ uint32_t u32Formats = pFormats->formats;
+ RTMemFree(pFormats);
+ LogRelFlowFunc (("u32Formats=%d\n", u32Formats));
+ clipInvalidateVBoxCBCache(pCtx);
+ clipGrabX11CB(pCtx, u32Formats);
+ clipResetX11Formats(pCtx);
+ LogRelFlowFunc(("returning\n"));
+}
+
+/**
+ * VBox is taking possession of the shared clipboard.
+ *
+ * @param u32Formats Clipboard formats that VBox is offering
+ * @note X11 backend code
+ */
+void ClipAnnounceFormatToX11(CLIPBACKEND *pCtx,
+ uint32_t u32Formats)
+{
+ /*
+ * Immediately return if we are not connected to the X server.
+ */
+ if (!pCtx->fHaveX11)
+ return;
+ /* This must be freed by the worker callback */
+ CLIPNEWVBOXFORMATS *pFormats =
+ (CLIPNEWVBOXFORMATS *) RTMemAlloc(sizeof(CLIPNEWVBOXFORMATS));
+ if (pFormats != NULL) /* if it is we will soon have other problems */
+ {
+ pFormats->pCtx = pCtx;
+ pFormats->formats = u32Formats;
+ clipQueueToEventThread(pCtx, clipNewVBoxFormatsWorker,
+ (XtPointer) pFormats);
+ }
+}
+
+/**
+ * Massage generic Utf16 with CR end-of-lines into the format Windows expects
+ * and return the result in a RTMemAlloc allocated buffer.
+ * @returns IPRT status code
+ * @param pwcSrc The source Utf16
+ * @param cwcSrc The number of 16bit elements in @a pwcSrc, not counting
+ * the terminating zero
+ * @param ppwszDest Where to store the buffer address
+ * @param pcbDest On success, where to store the number of bytes written.
+ * Undefined otherwise. Optional
+ */
+static int clipUtf16ToWinTxt(RTUTF16 *pwcSrc, size_t cwcSrc,
+ PRTUTF16 *ppwszDest, uint32_t *pcbDest)
+{
+ LogRelFlowFunc(("pwcSrc=%p, cwcSrc=%u, ppwszDest=%p\n", pwcSrc, cwcSrc,
+ ppwszDest));
+ AssertPtrReturn(pwcSrc, VERR_INVALID_POINTER);
+ AssertPtrReturn(ppwszDest, VERR_INVALID_POINTER);
+ if (pcbDest)
+ *pcbDest = 0;
+ PRTUTF16 pwszDest = NULL;
+ size_t cwcDest;
+ int rc = vboxClipboardUtf16GetWinSize(pwcSrc, cwcSrc + 1, &cwcDest);
+ if (RT_SUCCESS(rc))
+ {
+ pwszDest = (PRTUTF16) RTMemAlloc(cwcDest * 2);
+ if (!pwszDest)
+ rc = VERR_NO_MEMORY;
+ }
+ if (RT_SUCCESS(rc))
+ rc = vboxClipboardUtf16LinToWin(pwcSrc, cwcSrc + 1, pwszDest,
+ cwcDest);
+ if (RT_SUCCESS(rc))
+ {
+ LogRelFlowFunc (("converted string is %.*ls\n", cwcDest, pwszDest));
+ *ppwszDest = pwszDest;
+ if (pcbDest)
+ *pcbDest = cwcDest * 2;
+ }
+ else
+ RTMemFree(pwszDest);
+ LogRelFlowFunc(("returning %Rrc\n", rc));
+ if (pcbDest)
+ LogRelFlowFunc(("*pcbDest=%u\n", *pcbDest));
+ return rc;
+}
+
+/**
+ * Convert Utf-8 text with CR end-of-lines into Utf-16 as Windows expects it
+ * and return the result in a RTMemAlloc allocated buffer.
+ * @returns IPRT status code
+ * @param pcSrc The source Utf-8
+ * @param cbSrc The size of the source in bytes, not counting the
+ * terminating zero
+ * @param ppwszDest Where to store the buffer address
+ * @param pcbDest On success, where to store the number of bytes written.
+ * Undefined otherwise. Optional
+ */
+static int clipUtf8ToWinTxt(const char *pcSrc, unsigned cbSrc,
+ PRTUTF16 *ppwszDest, uint32_t *pcbDest)
+{
+ LogRelFlowFunc(("pcSrc=%p, cbSrc=%u, ppwszDest=%p\n", pcSrc, cbSrc,
+ ppwszDest));
+ AssertPtrReturn(pcSrc, VERR_INVALID_POINTER);
+ AssertPtrReturn(ppwszDest, VERR_INVALID_POINTER);
+ if (pcbDest)
+ *pcbDest = 0;
+ /* Intermediate conversion to UTF16 */
+ size_t cwcTmp;
+ PRTUTF16 pwcTmp = NULL;
+ int rc = RTStrToUtf16Ex(pcSrc, cbSrc, &pwcTmp, 0, &cwcTmp);
+ if (RT_SUCCESS(rc))
+ rc = clipUtf16ToWinTxt(pwcTmp, cwcTmp, ppwszDest, pcbDest);
+ RTUtf16Free(pwcTmp);
+ LogRelFlowFunc(("Returning %Rrc\n", rc));
+ if (pcbDest)
+ LogRelFlowFunc(("*pcbDest=%u\n", *pcbDest));
+ return rc;
+}
+
+/**
+ * Convert Latin-1 text with CR end-of-lines into Utf-16 as Windows expects
+ * it and return the result in a RTMemAlloc allocated buffer.
+ * @returns IPRT status code
+ * @param pcSrc The source text
+ * @param cbSrc The size of the source in bytes, not counting the
+ * terminating zero
+ * @param ppwszDest Where to store the buffer address
+ * @param pcbDest On success, where to store the number of bytes written.
+ * Undefined otherwise. Optional
+ */
+static int clipLatin1ToWinTxt(char *pcSrc, unsigned cbSrc,
+ PRTUTF16 *ppwszDest, uint32_t *pcbDest)
+{
+ LogRelFlowFunc (("pcSrc=%.*s, cbSrc=%u, ppwszDest=%p\n", cbSrc,
+ (char *) pcSrc, cbSrc, ppwszDest));
+ AssertPtrReturn(pcSrc, VERR_INVALID_POINTER);
+ AssertPtrReturn(ppwszDest, VERR_INVALID_POINTER);
+ PRTUTF16 pwszDest = NULL;
+ int rc = VINF_SUCCESS;
+
+ /* Calculate the space needed */
+ unsigned cwcDest = 0;
+ for (unsigned i = 0; i < cbSrc && pcSrc[i] != '\0'; ++i)
+ if (pcSrc[i] == LINEFEED)
+ cwcDest += 2;
+ else
+ ++cwcDest;
+ ++cwcDest; /* Leave space for the terminator */
+ if (pcbDest)
+ *pcbDest = cwcDest * 2;
+ pwszDest = (PRTUTF16) RTMemAlloc(cwcDest * 2);
+ if (!pwszDest)
+ rc = VERR_NO_MEMORY;
+
+ /* And do the conversion, bearing in mind that Latin-1 expands "naturally"
+ * to Utf-16. */
+ if (RT_SUCCESS(rc))
+ {
+ for (unsigned i = 0, j = 0; i < cbSrc; ++i, ++j)
+ if (pcSrc[i] != LINEFEED)
+ pwszDest[j] = pcSrc[i];
+ else
+ {
+ pwszDest[j] = CARRIAGERETURN;
+ pwszDest[j + 1] = LINEFEED;
+ ++j;
+ }
+ pwszDest[cwcDest - 1] = '\0'; /* Make sure we are zero-terminated. */
+ LogRelFlowFunc (("converted text is %.*ls\n", cwcDest, pwszDest));
+ }
+ if (RT_SUCCESS(rc))
+ *ppwszDest = pwszDest;
+ else
+ RTMemFree(pwszDest);
+ LogRelFlowFunc(("Returning %Rrc\n", rc));
+ if (pcbDest)
+ LogRelFlowFunc(("*pcbDest=%u\n", *pcbDest));
+ return rc;
+}
+
+
+/**
+* Convert Utf16 text into UTF8 as Windows expects
+* it and return the result in a RTMemAlloc allocated buffer.
+* @returns IPRT status code
+* @param pcSrc The source text
+* @param cbSrc The size of the source in bytes, not counting the
+* terminating zero
+* @param ppwszDest Where to store the buffer address
+* @param pcbDest On success, where to store the number of bytes written.
+* Undefined otherwise. Optional
+*/
+int clipUTF16ToWinHTML(RTUTF16 *pwcBuf, size_t cb, char **ppszOut, uint32_t *pcOut)
+{
+ Assert(pwcBuf);
+ Assert(cb);
+ Assert(ppszOut);
+ Assert(pcOut);
+
+ if (cb % 2)
+ return VERR_INVALID_PARAMETER;
+ size_t cwc = cb / 2;
+ size_t i = 0;
+ RTUTF16 *pwc = pwcBuf;
+ char *pchRes = NULL;
+ size_t cRes = 0;
+ LogRelFlowFunc(("clipUTF16ToWinHTML src= %ls cb=%d i=%i, %x %x\n", pwcBuf, cb, i, ppszOut, pcOut));
+ while (i < cwc)
+ {
+ /* find zero symbol (end of string) */
+ for (; i < cwc && pwcBuf[i] != 0; i++)
+ ;
+ LogRelFlowFunc(("skipped nulls i=%d cwc=%d\n", i, cwc));
+
+ /* convert found string */
+ char *psz = NULL;
+ size_t cch = 0;
+ int rc = RTUtf16ToUtf8Ex(pwc, cwc, &psz, pwc - pwcBuf, &cch);
+ LogRelFlowFunc(("utf16toutf8 src= %ls res=%s i=%i\n", pwc, psz, i));
+ if (RT_FAILURE(rc))
+ {
+ RTMemFree(pchRes);
+ return rc;
+ }
+
+ /* append new substring */
+ char *pchNew = (char*)RTMemRealloc(pchRes, cRes + cch + 1);
+ if (!pchNew)
+ {
+ RTMemFree(pchRes);
+ RTStrFree(psz);
+ return VERR_NO_MEMORY;
+ }
+ pchRes = pchNew;
+ memcpy(pchRes + cRes, psz, cch + 1);
+ LogRelFlowFunc(("Temp result res=%s\n", pchRes + cRes));
+
+ /* remove temporary buffer */
+ RTStrFree(psz);
+ cRes += cch + 1;
+ /* skip zero symbols */
+ for (; i < cwc && pwcBuf[i] == 0; i++)
+ ;
+ /* remember start of string */
+ pwc += i;
+ }
+ *ppszOut = pchRes;
+ *pcOut = cRes;
+
+ return VINF_SUCCESS;
+}
+
+
+
+/** A structure containing information about where to store a request
+ * for the X11 clipboard contents. */
+struct _CLIPREADX11CBREQ
+{
+ /** The format VBox would like the data in */
+ uint32_t mFormat;
+ /** The text format we requested from X11 if we requested text */
+ CLIPX11FORMAT mTextFormat;
+ /** The bitmap format we requested from X11 if we requested bitmap */
+ CLIPX11FORMAT mBitmapFormat;
+ /** The HTML format we requested from X11 if we requested HTML */
+ CLIPX11FORMAT mHtmlFormat;
+ /** The clipboard context this request is associated with */
+ CLIPBACKEND *mCtx;
+ /** The request structure passed in from the backend. */
+ CLIPREADCBREQ *mReq;
+};
+
+typedef struct _CLIPREADX11CBREQ CLIPREADX11CBREQ;
+
+/**
+ * Convert the data obtained from the X11 clipboard to the required format,
+ * place it in the buffer supplied and signal that data has arrived.
+ * Convert the text obtained UTF-16LE with Windows EOLs.
+ * Convert full BMP data to DIB format.
+ * @note X11 backend code, callback for XtGetSelectionValue, for use when
+ * the X11 clipboard contains a format we understand.
+ */
+static void clipConvertX11CB(void *pClientData, void *pvSrc, unsigned cbSrc)
+{
+ CLIPREADX11CBREQ *pReq = (CLIPREADX11CBREQ *) pClientData;
+ LogRelFlowFunc(("pReq->mFormat=%02X, pReq->mTextFormat=%u, "
+ "pReq->mBitmapFormat=%u, pReq->mHtmlFormat=%u, pReq->mCtx=%p\n",
+ pReq->mFormat, pReq->mTextFormat, pReq->mBitmapFormat,
+ pReq->mHtmlFormat, pReq->mCtx));
+ AssertPtr(pReq->mCtx);
+ Assert(pReq->mFormat != 0); /* sanity */
+ int rc = VINF_SUCCESS;
+#ifndef VBOX_AFTER_5_2
+ CLIPBACKEND *pCtx = pReq->mCtx;
+#endif
+ void *pvDest = NULL;
+ uint32_t cbDest = 0;
+
+#ifndef VBOX_AFTER_5_2
+ pCtx->fBusy = false;
+ if (pCtx->fUpdateNeeded)
+ clipQueryX11CBFormats(pCtx);
+#endif
+ if (pvSrc == NULL)
+ /* The clipboard selection may have changed before we could get it. */
+ rc = VERR_NO_DATA;
+ else if (pReq->mFormat == VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT)
+ {
+ /* In which format is the clipboard data? */
+ switch (clipRealFormatForX11Format(pReq->mTextFormat))
+ {
+ case UTF8:
+ case TEXT:
+ {
+ /* If we are given broken Utf-8, we treat it as Latin1. Is
+ * this acceptable? */
+ if (RT_SUCCESS(RTStrValidateEncodingEx((char *)pvSrc, cbSrc,
+ 0)))
+ rc = clipUtf8ToWinTxt((const char *)pvSrc, cbSrc,
+ (PRTUTF16 *) &pvDest, &cbDest);
+ else
+ rc = clipLatin1ToWinTxt((char *) pvSrc, cbSrc,
+ (PRTUTF16 *) &pvDest, &cbDest);
+ break;
+ }
+ default:
+ rc = VERR_INVALID_PARAMETER;
+ }
+ }
+ else if (pReq->mFormat == VBOX_SHARED_CLIPBOARD_FMT_BITMAP)
+ {
+ /* In which format is the clipboard data? */
+ switch (clipRealFormatForX11Format(pReq->mBitmapFormat))
+ {
+ case BMP:
+ {
+ const void *pDib;
+ size_t cbDibSize;
+ rc = vboxClipboardBmpGetDib((const void *)pvSrc, cbSrc,
+ &pDib, &cbDibSize);
+ if (RT_SUCCESS(rc))
+ {
+ pvDest = RTMemAlloc(cbDibSize);
+ if (!pvDest)
+ rc = VERR_NO_MEMORY;
+ else
+ {
+ memcpy(pvDest, pDib, cbDibSize);
+ cbDest = cbDibSize;
+ }
+ }
+ break;
+ }
+ default:
+ rc = VERR_INVALID_PARAMETER;
+ }
+ }
+ else if(pReq->mFormat == VBOX_SHARED_CLIPBOARD_FMT_HTML)
+ {
+ /* In which format is the clipboard data? */
+ switch (clipRealFormatForX11Format(pReq->mHtmlFormat))
+ {
+ case HTML:
+ {
+ /* The common VBox HTML encoding will be - Utf8
+ * becuase it more general for HTML formats then UTF16
+ * X11 clipboard returns UTF16, so before sending it we should
+ * convert it to UTF8
+ */
+ pvDest = NULL;
+ cbDest = 0;
+ /* Some applications sends data in utf16, some in itf8,
+ * without indication it in MIME.
+ * But in case of utf16, at least an OpenOffice adds Byte Order Mark - 0xfeff
+ * at start of clipboard data
+ */
+ if( cbSrc >= sizeof(RTUTF16) && *(PRTUTF16)pvSrc == 0xfeff )
+ {
+ LogRelFlowFunc((" \n"));
+ rc = clipUTF16ToWinHTML((RTUTF16*)pvSrc, cbSrc,
+ (char**)&pvDest, &cbDest);
+ }
+ else
+ {
+ pvDest = RTMemAlloc(cbSrc);
+ if(pvDest)
+ {
+ memcpy(pvDest, pvSrc, cbSrc);
+ cbDest = cbSrc;
+ }
+ else
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ }
+
+ LogRelFlowFunc(("Source unicode %ls, cbSrc = %d\n, Byte Order Mark = %hx",
+ pvSrc, cbSrc, ((PRTUTF16)pvSrc)[0]));
+ LogRelFlowFunc(("converted to win unicode %s, cbDest = %d, rc = %Rrc\n", pvDest, cbDest, rc));
+ rc = VINF_SUCCESS;
+ break;
+ }
+ default:
+ {
+ rc = VERR_INVALID_PARAMETER;
+ }
+ }
+ }
+ else
+ rc = VERR_NOT_IMPLEMENTED;
+ ClipCompleteDataRequestFromX11(pReq->mCtx->pFrontend, rc, pReq->mReq,
+ pvDest, cbDest);
+ RTMemFree(pvDest);
+ RTMemFree(pReq);
+ LogRelFlowFunc(("rc=%Rrc\n", rc));
+}
+
+#ifndef TESTCASE
+/**
+ * Convert the data obtained from the X11 clipboard to the required format,
+ * place it in the buffer supplied and signal that data has arrived.
+ * Convert the text obtained UTF-16LE with Windows EOLs.
+ * Convert full BMP data to DIB format.
+ * @note X11 backend code, callback for XtGetSelectionValue, for use when
+ * the X11 clipboard contains a format we understand.
+ */
+static void cbConvertX11CB(Widget widget, XtPointer pClientData,
+ Atom * /* selection */, Atom *atomType,
+ XtPointer pvSrc, long unsigned int *pcLen,
+ int *piFormat)
+{
+ RT_NOREF1(widget);
+ if (*atomType == XT_CONVERT_FAIL) /* Xt timeout */
+ clipConvertX11CB(pClientData, NULL, 0);
+ else
+ clipConvertX11CB(pClientData, pvSrc, (*pcLen) * (*piFormat) / 8);
+
+ XtFree((char *)pvSrc);
+}
+#endif
+
+#ifdef TESTCASE
+static void testRequestData(CLIPBACKEND* pCtx, CLIPX11FORMAT target,
+ void *closure);
+#endif
+
+static void getSelectionValue(CLIPBACKEND *pCtx, CLIPX11FORMAT format,
+ CLIPREADX11CBREQ *pReq)
+{
+#ifndef TESTCASE
+ XtGetSelectionValue(pCtx->widget, clipGetAtom(pCtx, "CLIPBOARD"),
+ clipAtomForX11Format(pCtx, format),
+ cbConvertX11CB,
+ reinterpret_cast<XtPointer>(pReq),
+ CurrentTime);
+#else
+ testRequestData(pCtx, format, (void *)pReq);
+#endif
+}
+
+/** Worker function for ClipRequestDataFromX11 which runs on the event
+ * thread. */
+static void vboxClipboardReadX11Worker(void *pUserData,
+ void * /* interval */)
+{
+ CLIPREADX11CBREQ *pReq = (CLIPREADX11CBREQ *)pUserData;
+ CLIPBACKEND *pCtx = pReq->mCtx;
+ LogRelFlowFunc (("pReq->mFormat = %02X\n", pReq->mFormat));
+
+ int rc = VINF_SUCCESS;
+#ifndef VBOX_AFTER_5_2
+ bool fBusy = pCtx->fBusy;
+ pCtx->fBusy = true;
+ if (fBusy)
+ /* If the clipboard is busy just fend off the request. */
+ rc = VERR_TRY_AGAIN;
+ else
+#endif
+ if (pReq->mFormat == VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT)
+ {
+ /*
+ * VBox wants to read data in the given format.
+ */
+ pReq->mTextFormat = pCtx->X11TextFormat;
+ if (pReq->mTextFormat == INVALID)
+ /* VBox thinks we have data and we don't */
+ rc = VERR_NO_DATA;
+ else
+ /* Send out a request for the data to the current clipboard
+ * owner */
+ getSelectionValue(pCtx, pCtx->X11TextFormat, pReq);
+ }
+ else if (pReq->mFormat == VBOX_SHARED_CLIPBOARD_FMT_BITMAP)
+ {
+ pReq->mBitmapFormat = pCtx->X11BitmapFormat;
+ if (pReq->mBitmapFormat == INVALID)
+ /* VBox thinks we have data and we don't */
+ rc = VERR_NO_DATA;
+ else
+ /* Send out a request for the data to the current clipboard
+ * owner */
+ getSelectionValue(pCtx, pCtx->X11BitmapFormat, pReq);
+ }
+ else if(pReq->mFormat == VBOX_SHARED_CLIPBOARD_FMT_HTML)
+ {
+ /* Send out a request for the data to the current clipboard
+ * owner */
+ pReq->mHtmlFormat = pCtx->X11HTMLFormat;
+ if(pReq->mHtmlFormat == INVALID)
+ /* VBox thinks we have data and we don't */
+ rc = VERR_NO_DATA;
+ else
+ /* Send out a request for the data to the current clipboard
+ * owner */
+ getSelectionValue(pCtx, pCtx->X11HTMLFormat, pReq);
+ }
+ else
+ {
+ rc = VERR_NOT_IMPLEMENTED;
+#ifndef VBOX_AFTER_5_2
+ pCtx->fBusy = false;
+#endif
+ }
+ if (RT_FAILURE(rc))
+ {
+ /* The clipboard callback was never scheduled, so we must signal
+ * that the request processing is finished and clean up ourselves. */
+ ClipCompleteDataRequestFromX11(pReq->mCtx->pFrontend, rc, pReq->mReq,
+ NULL, 0);
+ RTMemFree(pReq);
+ }
+ LogRelFlowFunc(("status %Rrc\n", rc));
+}
+
+/**
+ * Called when VBox wants to read the X11 clipboard.
+ *
+ * @returns iprt status code
+ * @param pCtx Context data for the clipboard backend
+ * @param u32Format The format that the VBox would like to receive the data
+ * in
+ * @param pv Where to write the data to
+ * @param cb The size of the buffer to write the data to
+ * @param pcbActual Where to write the actual size of the written data
+ * @note We allocate a request structure which must be freed by the worker
+ */
+int ClipRequestDataFromX11(CLIPBACKEND *pCtx, uint32_t u32Format,
+ CLIPREADCBREQ *pReq)
+{
+ /*
+ * Immediately return if we are not connected to the X server.
+ */
+ if (!pCtx->fHaveX11)
+ return VERR_NO_DATA;
+ int rc = VINF_SUCCESS;
+ CLIPREADX11CBREQ *pX11Req;
+ pX11Req = (CLIPREADX11CBREQ *)RTMemAllocZ(sizeof(*pX11Req));
+ if (!pX11Req)
+ rc = VERR_NO_MEMORY;
+ else
+ {
+ pX11Req->mFormat = u32Format;
+ pX11Req->mCtx = pCtx;
+ pX11Req->mReq = pReq;
+ /* We use this to schedule a worker function on the event thread. */
+ clipQueueToEventThread(pCtx, vboxClipboardReadX11Worker,
+ (XtPointer) pX11Req);
+ }
+ return rc;
+}
+
+#ifdef TESTCASE
+
+/** @todo This unit test currently works by emulating the X11 and X toolkit
+ * APIs to exercise the code, since I didn't want to rewrite the code too much
+ * when I wrote the tests. However, this makes it rather ugly and hard to
+ * understand. Anyone doing any work on the code should feel free to
+ * rewrite the tests and the code to make them cleaner and more readable. */
+
+#include <iprt/test.h>
+#include <poll.h>
+
+#define TEST_WIDGET (Widget)0xffff
+
+/* For the purpose of the test case, we just execute the procedure to be
+ * scheduled, as we are running single threaded. */
+void testQueueToEventThread(void (*proc)(void *, void *),
+ void *client_data)
+{
+ proc(client_data, NULL);
+}
+
+void XtFree(char *ptr)
+{ RTMemFree((void *) ptr); }
+
+/* The data in the simulated VBox clipboard */
+static int g_vboxDataRC = VINF_SUCCESS;
+static void *g_vboxDatapv = NULL;
+static uint32_t g_vboxDatacb = 0;
+
+/* Set empty data in the simulated VBox clipboard. */
+static void clipEmptyVBox(CLIPBACKEND *pCtx, int retval)
+{
+ g_vboxDataRC = retval;
+ RTMemFree(g_vboxDatapv);
+ g_vboxDatapv = NULL;
+ g_vboxDatacb = 0;
+ ClipAnnounceFormatToX11(pCtx, 0);
+}
+
+/* Set the data in the simulated VBox clipboard. */
+static int clipSetVBoxUtf16(CLIPBACKEND *pCtx, int retval,
+ const char *pcszData, size_t cb)
+{
+ PRTUTF16 pwszData = NULL;
+ size_t cwData = 0;
+ int rc = RTStrToUtf16Ex(pcszData, RTSTR_MAX, &pwszData, 0, &cwData);
+ if (RT_FAILURE(rc))
+ return rc;
+ AssertReturn(cb <= cwData * 2 + 2, VERR_BUFFER_OVERFLOW);
+ void *pv = RTMemDup(pwszData, cb);
+ RTUtf16Free(pwszData);
+ if (pv == NULL)
+ return VERR_NO_MEMORY;
+ if (g_vboxDatapv)
+ RTMemFree(g_vboxDatapv);
+ g_vboxDataRC = retval;
+ g_vboxDatapv = pv;
+ g_vboxDatacb = cb;
+ ClipAnnounceFormatToX11(pCtx,
+ VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT);
+ return VINF_SUCCESS;
+}
+
+/* Return the data in the simulated VBox clipboard. */
+int ClipRequestDataForX11(VBOXCLIPBOARDCONTEXT *pCtx, uint32_t u32Format, void **ppv, uint32_t *pcb)
+{
+ RT_NOREF2(pCtx, u32Format);
+ *pcb = g_vboxDatacb;
+ if (g_vboxDatapv != NULL)
+ {
+ void *pv = RTMemDup(g_vboxDatapv, g_vboxDatacb);
+ *ppv = pv;
+ return pv != NULL ? g_vboxDataRC : VERR_NO_MEMORY;
+ }
+ *ppv = NULL;
+ return g_vboxDataRC;
+}
+
+Display *XtDisplay(Widget w)
+{ NOREF(w); return (Display *) 0xffff; }
+
+void XtAppSetExitFlag(XtAppContext app_context) { NOREF(app_context); }
+
+void XtDestroyWidget(Widget w) { NOREF(w); }
+
+XtAppContext XtCreateApplicationContext(void) { return (XtAppContext)0xffff; }
+
+void XtDestroyApplicationContext(XtAppContext app_context) { NOREF(app_context); }
+
+void XtToolkitInitialize(void) {}
+
+Boolean XtToolkitThreadInitialize(void) { return True; }
+
+Display *XtOpenDisplay(XtAppContext app_context,
+ _Xconst _XtString display_string,
+ _Xconst _XtString application_name,
+ _Xconst _XtString application_class,
+ XrmOptionDescRec *options, Cardinal num_options,
+ int *argc, char **argv)
+{
+ RT_NOREF8(app_context, display_string, application_name, application_class, options, num_options, argc, argv);
+ return (Display *)0xffff;
+}
+
+Widget XtVaAppCreateShell(_Xconst _XtString application_name, _Xconst _XtString application_class,
+ WidgetClass widget_class, Display *display, ...)
+{
+ RT_NOREF4(application_name, application_class, widget_class, display);
+ return TEST_WIDGET;
+}
+
+void XtSetMappedWhenManaged(Widget widget, _XtBoolean mapped_when_managed) { RT_NOREF2(widget, mapped_when_managed); }
+
+void XtRealizeWidget(Widget widget) { NOREF(widget); }
+
+XtInputId XtAppAddInput(XtAppContext app_context, int source, XtPointer condition, XtInputCallbackProc proc, XtPointer closure)
+{
+ RT_NOREF5(app_context, source, condition, proc, closure);
+ return 0xffff;
+}
+
+/* Atoms we need other than the formats we support. */
+static const char *g_apszSupAtoms[] =
+{
+ "PRIMARY", "CLIPBOARD", "TARGETS", "MULTIPLE", "TIMESTAMP"
+};
+
+/* This just looks for the atom names in a couple of tables and returns an
+ * index with an offset added. */
+Atom XInternAtom(Display *, const char *pcsz, int)
+{
+ Atom atom = 0;
+ for (unsigned i = 0; i < RT_ELEMENTS(g_aFormats); ++i)
+ if (!strcmp(pcsz, g_aFormats[i].pcszAtom))
+ atom = (Atom) (i + 0x1000);
+ for (unsigned i = 0; i < RT_ELEMENTS(g_apszSupAtoms); ++i)
+ if (!strcmp(pcsz, g_apszSupAtoms[i]))
+ atom = (Atom) (i + 0x2000);
+ Assert(atom); /* Have we missed any atoms? */
+ return atom;
+}
+
+/* Take a request for the targets we are currently offering. */
+static CLIPX11FORMAT g_selTargets[10] = { 0 };
+static size_t g_cTargets = 0;
+
+void testRequestTargets(CLIPBACKEND* pCtx)
+{
+ clipUpdateX11Targets(pCtx, g_selTargets, g_cTargets);
+}
+
+/* The current values of the X selection, which will be returned to the
+ * XtGetSelectionValue callback. */
+static Atom g_selType = 0;
+static const void *g_pSelData = NULL;
+static unsigned long g_cSelData = 0;
+static int g_selFormat = 0;
+
+void testRequestData(CLIPBACKEND *pCtx, CLIPX11FORMAT target, void *closure)
+{
+ RT_NOREF1(pCtx);
+ unsigned long count = 0;
+ int format = 0;
+ if (target != g_selTargets[0])
+ {
+ clipConvertX11CB(closure, NULL, 0); /* Could not convert to target. */
+ return;
+ }
+ void *pValue = NULL;
+ pValue = g_pSelData ? RTMemDup(g_pSelData, g_cSelData) : NULL;
+ count = g_pSelData ? g_cSelData : 0;
+ format = g_selFormat;
+ if (!pValue)
+ {
+ count = 0;
+ format = 0;
+ }
+ clipConvertX11CB(closure, pValue, count * format / 8);
+ if (pValue)
+ RTMemFree(pValue);
+}
+
+/* The formats currently on offer from X11 via the shared clipboard */
+static uint32_t g_fX11Formats = 0;
+
+void ClipReportX11Formats(VBOXCLIPBOARDCONTEXT *pCtx, uint32_t u32Formats)
+{
+ RT_NOREF1(pCtx);
+ g_fX11Formats = u32Formats;
+}
+
+static uint32_t clipQueryFormats()
+{
+ return g_fX11Formats;
+}
+
+static void clipInvalidateFormats()
+{
+ g_fX11Formats = ~0;
+}
+
+/* Does our clipboard code currently own the selection? */
+static bool g_ownsSel = false;
+/* The procedure that is called when we should convert the selection to a
+ * given format. */
+static XtConvertSelectionProc g_pfnSelConvert = NULL;
+/* The procedure which is called when we lose the selection. */
+static XtLoseSelectionProc g_pfnSelLose = NULL;
+/* The procedure which is called when the selection transfer has completed. */
+static XtSelectionDoneProc g_pfnSelDone = NULL;
+
+Boolean XtOwnSelection(Widget widget, Atom selection, Time time,
+ XtConvertSelectionProc convert,
+ XtLoseSelectionProc lose,
+ XtSelectionDoneProc done)
+{
+ RT_NOREF2(widget, time);
+ if (selection != XInternAtom(NULL, "CLIPBOARD", 0))
+ return True; /* We don't really care about this. */
+ g_ownsSel = true; /* Always succeed. */
+ g_pfnSelConvert = convert;
+ g_pfnSelLose = lose;
+ g_pfnSelDone = done;
+ return True;
+}
+
+void XtDisownSelection(Widget widget, Atom selection, Time time)
+{
+ RT_NOREF3(widget, time, selection);
+ g_ownsSel = false;
+ g_pfnSelConvert = NULL;
+ g_pfnSelLose = NULL;
+ g_pfnSelDone = NULL;
+}
+
+/* Request the shared clipboard to convert its data to a given format. */
+static bool clipConvertSelection(const char *pcszTarget, Atom *type,
+ XtPointer *value, unsigned long *length,
+ int *format)
+{
+ Atom target = XInternAtom(NULL, pcszTarget, 0);
+ if (target == 0)
+ return false;
+ /* Initialise all return values in case we make a quick exit. */
+ *type = XA_STRING;
+ *value = NULL;
+ *length = 0;
+ *format = 0;
+ if (!g_ownsSel)
+ return false;
+ if (!g_pfnSelConvert)
+ return false;
+ Atom clipAtom = XInternAtom(NULL, "CLIPBOARD", 0);
+ if (!g_pfnSelConvert(TEST_WIDGET, &clipAtom, &target, type,
+ value, length, format))
+ return false;
+ if (g_pfnSelDone)
+ g_pfnSelDone(TEST_WIDGET, &clipAtom, &target);
+ return true;
+}
+
+/* Set the current X selection data */
+static void clipSetSelectionValues(const char *pcszTarget, Atom type,
+ const void *data,
+ unsigned long count, int format)
+{
+ Atom clipAtom = XInternAtom(NULL, "CLIPBOARD", 0);
+ g_selTargets[0] = clipFindX11FormatByAtomText(pcszTarget);
+ g_cTargets = 1;
+ g_selType = type;
+ g_pSelData = data;
+ g_cSelData = count;
+ g_selFormat = format;
+ if (g_pfnSelLose)
+ g_pfnSelLose(TEST_WIDGET, &clipAtom);
+ g_ownsSel = false;
+}
+
+static void clipSendTargetUpdate(CLIPBACKEND *pCtx)
+{
+ clipQueryX11CBFormats(pCtx);
+}
+
+/* Configure if and how the X11 TARGETS clipboard target will fail */
+static void clipSetTargetsFailure(void)
+{
+ g_cTargets = 0;
+}
+
+char *XtMalloc(Cardinal size) { return (char *) RTMemAlloc(size); }
+
+char *XGetAtomName(Display *display, Atom atom)
+{
+ RT_NOREF1(display);
+ AssertReturn((unsigned)atom < RT_ELEMENTS(g_aFormats) + 1, NULL);
+ const char *pcszName = NULL;
+ if (atom < 0x1000)
+ return NULL;
+ if (0x1000 <= atom && atom < 0x2000)
+ {
+ unsigned index = atom - 0x1000;
+ AssertReturn(index < RT_ELEMENTS(g_aFormats), NULL);
+ pcszName = g_aFormats[index].pcszAtom;
+ }
+ else
+ {
+ unsigned index = atom - 0x2000;
+ AssertReturn(index < RT_ELEMENTS(g_apszSupAtoms), NULL);
+ pcszName = g_apszSupAtoms[index];
+ }
+ return (char *)RTMemDup(pcszName, sizeof(pcszName) + 1);
+}
+
+int XFree(void *data)
+{
+ RTMemFree(data);
+ return 0;
+}
+
+void XFreeStringList(char **list)
+{
+ if (list)
+ RTMemFree(*list);
+ RTMemFree(list);
+}
+
+#define MAX_BUF_SIZE 256
+
+static int g_completedRC = VINF_SUCCESS;
+static int g_completedCB = 0;
+static CLIPREADCBREQ *g_completedReq = NULL;
+static char g_completedBuf[MAX_BUF_SIZE];
+
+void ClipCompleteDataRequestFromX11(VBOXCLIPBOARDCONTEXT *pCtx, int rc, CLIPREADCBREQ *pReq, void *pv, uint32_t cb)
+{
+ RT_NOREF1(pCtx);
+ if (cb <= MAX_BUF_SIZE)
+ {
+ g_completedRC = rc;
+ if (cb != 0)
+ memcpy(g_completedBuf, pv, cb);
+ }
+ else
+ g_completedRC = VERR_BUFFER_OVERFLOW;
+ g_completedCB = cb;
+ g_completedReq = pReq;
+}
+
+static void clipGetCompletedRequest(int *prc, char ** ppc, uint32_t *pcb, CLIPREADCBREQ **ppReq)
+{
+ *prc = g_completedRC;
+ *ppc = g_completedBuf;
+ *pcb = g_completedCB;
+ *ppReq = g_completedReq;
+}
+#ifdef RT_OS_SOLARIS_10
+char XtStrings [] = "";
+_WidgetClassRec* applicationShellWidgetClass;
+char XtShellStrings [] = "";
+int XmbTextPropertyToTextList(
+ Display* /* display */,
+ XTextProperty* /* text_prop */,
+ char*** /* list_return */,
+ int* /* count_return */
+)
+{
+ return 0;
+}
+#else
+const char XtStrings [] = "";
+_WidgetClassRec* applicationShellWidgetClass;
+const char XtShellStrings [] = "";
+#endif
+
+static void testStringFromX11(RTTEST hTest, CLIPBACKEND *pCtx,
+ const char *pcszExp, int rcExp)
+{
+ bool retval = true;
+ clipSendTargetUpdate(pCtx);
+ if (clipQueryFormats() != VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT)
+ RTTestFailed(hTest, "Wrong targets reported: %02X\n",
+ clipQueryFormats());
+ else
+ {
+ char *pc;
+ CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)&pReq, *pReqRet = NULL;
+ ClipRequestDataFromX11(pCtx, VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT,
+ pReq);
+ int rc = VINF_SUCCESS;
+ uint32_t cbActual = 0;
+ clipGetCompletedRequest(&rc, &pc, &cbActual, &pReqRet);
+ if (rc != rcExp)
+ RTTestFailed(hTest, "Wrong return code, expected %Rrc, got %Rrc\n",
+ rcExp, rc);
+ else if (pReqRet != pReq)
+ RTTestFailed(hTest, "Wrong returned request data, expected %p, got %p\n",
+ pReq, pReqRet);
+ else if (RT_FAILURE(rcExp))
+ retval = true;
+ else
+ {
+ RTUTF16 wcExp[MAX_BUF_SIZE / 2];
+ RTUTF16 *pwcExp = wcExp;
+ size_t cwc = 0;
+ rc = RTStrToUtf16Ex(pcszExp, RTSTR_MAX, &pwcExp,
+ RT_ELEMENTS(wcExp), &cwc);
+ size_t cbExp = cwc * 2 + 2;
+ AssertRC(rc);
+ if (RT_SUCCESS(rc))
+ {
+ if (cbActual != cbExp)
+ {
+ RTTestFailed(hTest, "Returned string is the wrong size, string \"%.*ls\", size %u, expected \"%s\", size %u\n",
+ RT_MIN(MAX_BUF_SIZE, cbActual), pc, cbActual,
+ pcszExp, cbExp);
+ }
+ else
+ {
+ if (memcmp(pc, wcExp, cbExp) == 0)
+ retval = true;
+ else
+ RTTestFailed(hTest, "Returned string \"%.*ls\" does not match expected string \"%s\"\n",
+ MAX_BUF_SIZE, pc, pcszExp);
+ }
+ }
+ }
+ }
+ if (!retval)
+ RTTestFailureDetails(hTest, "Expected: string \"%s\", rc %Rrc\n",
+ pcszExp, rcExp);
+}
+
+static void testLatin1FromX11(RTTEST hTest, CLIPBACKEND *pCtx,
+ const char *pcszExp, int rcExp)
+{
+ bool retval = false;
+ clipSendTargetUpdate(pCtx);
+ if (clipQueryFormats() != VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT)
+ RTTestFailed(hTest, "Wrong targets reported: %02X\n",
+ clipQueryFormats());
+ else
+ {
+ char *pc;
+ CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)&pReq, *pReqRet = NULL;
+ ClipRequestDataFromX11(pCtx, VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT,
+ pReq);
+ int rc = VINF_SUCCESS;
+ uint32_t cbActual = 0;
+ clipGetCompletedRequest(&rc, &pc, &cbActual, &pReqRet);
+ if (rc != rcExp)
+ RTTestFailed(hTest, "Wrong return code, expected %Rrc, got %Rrc\n",
+ rcExp, rc);
+ else if (pReqRet != pReq)
+ RTTestFailed(hTest, "Wrong returned request data, expected %p, got %p\n",
+ pReq, pReqRet);
+ else if (RT_FAILURE(rcExp))
+ retval = true;
+ else
+ {
+ RTUTF16 wcExp[MAX_BUF_SIZE / 2];
+ //RTUTF16 *pwcExp = wcExp; - unused
+ size_t cwc;
+ for (cwc = 0; cwc == 0 || pcszExp[cwc - 1] != '\0'; ++cwc)
+ wcExp[cwc] = pcszExp[cwc];
+ size_t cbExp = cwc * 2;
+ if (cbActual != cbExp)
+ {
+ RTTestFailed(hTest, "Returned string is the wrong size, string \"%.*ls\", size %u, expected \"%s\", size %u\n",
+ RT_MIN(MAX_BUF_SIZE, cbActual), pc, cbActual,
+ pcszExp, cbExp);
+ }
+ else
+ {
+ if (memcmp(pc, wcExp, cbExp) == 0)
+ retval = true;
+ else
+ RTTestFailed(hTest, "Returned string \"%.*ls\" does not match expected string \"%s\"\n",
+ MAX_BUF_SIZE, pc, pcszExp);
+ }
+ }
+ }
+ if (!retval)
+ RTTestFailureDetails(hTest, "Expected: string \"%s\", rc %Rrc\n",
+ pcszExp, rcExp);
+}
+
+static void testStringFromVBox(RTTEST hTest, CLIPBACKEND *pCtx, const char *pcszTarget, Atom typeExp, const char *valueExp)
+{
+ RT_NOREF1(pCtx);
+ bool retval = false;
+ Atom type;
+ XtPointer value = NULL;
+ unsigned long length;
+ int format;
+ size_t lenExp = strlen(valueExp);
+ if (clipConvertSelection(pcszTarget, &type, &value, &length, &format))
+ {
+ if ( type != typeExp
+ || length != lenExp
+ || format != 8
+ || memcmp((const void *) value, (const void *)valueExp,
+ lenExp))
+ {
+ RTTestFailed(hTest, "Bad data: type %d, (expected %d), length %u, (%u), format %d (%d), value \"%.*s\" (\"%.*s\")\n",
+ type, typeExp, length, lenExp, format, 8,
+ RT_MIN(length, 20), value, RT_MIN(lenExp, 20), valueExp);
+ }
+ else
+ retval = true;
+ }
+ else
+ RTTestFailed(hTest, "Conversion failed\n");
+ XtFree((char *)value);
+ if (!retval)
+ RTTestFailureDetails(hTest, "Conversion to %s, expected \"%s\"\n",
+ pcszTarget, valueExp);
+}
+
+static void testNoX11(CLIPBACKEND *pCtx, const char *pcszTestCtx)
+{
+ CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)&pReq;
+ int rc = ClipRequestDataFromX11(pCtx,
+ VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT,
+ pReq);
+ RTTESTI_CHECK_MSG(rc == VERR_NO_DATA, ("context: %s\n", pcszTestCtx));
+}
+
+static void testStringFromVBoxFailed(RTTEST hTest, CLIPBACKEND *pCtx, const char *pcszTarget)
+{
+ RT_NOREF1(pCtx);
+ Atom type;
+ XtPointer value = NULL;
+ unsigned long length;
+ int format;
+ RTTEST_CHECK_MSG(hTest, !clipConvertSelection(pcszTarget, &type, &value,
+ &length, &format),
+ (hTest, "Conversion to target %s, should have failed but didn't, returned type %d, length %u, format %d, value \"%.*s\"\n",
+ pcszTarget, type, length, format, RT_MIN(length, 20),
+ value));
+ XtFree((char *)value);
+}
+
+static void testNoSelectionOwnership(CLIPBACKEND *pCtx, const char *pcszTestCtx)
+{
+ RT_NOREF1(pCtx);
+ RTTESTI_CHECK_MSG(!g_ownsSel, ("context: %s\n", pcszTestCtx));
+}
+
+static void testBadFormatRequestFromHost(RTTEST hTest, CLIPBACKEND *pCtx)
+{
+ clipSetSelectionValues("UTF8_STRING", XA_STRING, "hello world",
+ sizeof("hello world"), 8);
+ clipSendTargetUpdate(pCtx);
+ if (clipQueryFormats() != VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT)
+ RTTestFailed(hTest, "Wrong targets reported: %02X\n",
+ clipQueryFormats());
+ else
+ {
+ char *pc;
+ CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)&pReq, *pReqRet = NULL;
+ ClipRequestDataFromX11(pCtx, 100, pReq); /* Bad format. */
+ int rc = VINF_SUCCESS;
+ uint32_t cbActual = 0;
+ clipGetCompletedRequest(&rc, &pc, &cbActual, &pReqRet);
+ if (rc != VERR_NOT_IMPLEMENTED)
+ RTTestFailed(hTest, "Wrong return code, expected VERR_NOT_IMPLEMENTED, got %Rrc\n",
+ rc);
+ clipSetSelectionValues("", XA_STRING, "", sizeof(""), 8);
+ clipSendTargetUpdate(pCtx);
+ if (clipQueryFormats() == VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT)
+ RTTestFailed(hTest, "Failed to report targets after bad host request.\n");
+ }
+}
+
+int main()
+{
+ /*
+ * Init the runtime, test and say hello.
+ */
+ RTTEST hTest;
+ int rc = RTTestInitAndCreate("tstClipboardX11", &hTest);
+ if (rc)
+ return rc;
+ RTTestBanner(hTest);
+
+ /*
+ * Run the test.
+ */
+ CLIPBACKEND *pCtx = ClipConstructX11(NULL, false);
+ char *pc;
+ uint32_t cbActual;
+ CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)&pReq, *pReqRet = NULL;
+ rc = ClipStartX11(pCtx);
+ AssertRCReturn(rc, 1);
+
+ /*** Utf-8 from X11 ***/
+ RTTestSub(hTest, "reading Utf-8 from X11");
+ /* Simple test */
+ clipSetSelectionValues("UTF8_STRING", XA_STRING, "hello world",
+ sizeof("hello world"), 8);
+ testStringFromX11(hTest, pCtx, "hello world", VINF_SUCCESS);
+ /* With an embedded carriage return */
+ clipSetSelectionValues("text/plain;charset=UTF-8", XA_STRING,
+ "hello\nworld", sizeof("hello\nworld"), 8);
+ testStringFromX11(hTest, pCtx, "hello\r\nworld", VINF_SUCCESS);
+ /* With an embedded CRLF */
+ clipSetSelectionValues("text/plain;charset=UTF-8", XA_STRING,
+ "hello\r\nworld", sizeof("hello\r\nworld"), 8);
+ testStringFromX11(hTest, pCtx, "hello\r\r\nworld", VINF_SUCCESS);
+ /* With an embedded LFCR */
+ clipSetSelectionValues("text/plain;charset=UTF-8", XA_STRING,
+ "hello\n\rworld", sizeof("hello\n\rworld"), 8);
+ testStringFromX11(hTest, pCtx, "hello\r\n\rworld", VINF_SUCCESS);
+ /* An empty string */
+ clipSetSelectionValues("text/plain;charset=utf-8", XA_STRING, "",
+ sizeof(""), 8);
+ testStringFromX11(hTest, pCtx, "", VINF_SUCCESS);
+ /* With an embedded Utf-8 character. */
+ clipSetSelectionValues("STRING", XA_STRING,
+ "100\xE2\x82\xAC" /* 100 Euro */,
+ sizeof("100\xE2\x82\xAC"), 8);
+ testStringFromX11(hTest, pCtx, "100\xE2\x82\xAC", VINF_SUCCESS);
+ /* A non-zero-terminated string */
+ clipSetSelectionValues("TEXT", XA_STRING,
+ "hello world", sizeof("hello world") - 1, 8);
+ testStringFromX11(hTest, pCtx, "hello world", VINF_SUCCESS);
+
+ /*** Latin1 from X11 ***/
+ RTTestSub(hTest, "reading Latin1 from X11");
+ /* Simple test */
+ clipSetSelectionValues("STRING", XA_STRING, "Georges Dupr\xEA",
+ sizeof("Georges Dupr\xEA"), 8);
+ testLatin1FromX11(hTest, pCtx, "Georges Dupr\xEA", VINF_SUCCESS);
+ /* With an embedded carriage return */
+ clipSetSelectionValues("TEXT", XA_STRING, "Georges\nDupr\xEA",
+ sizeof("Georges\nDupr\xEA"), 8);
+ testLatin1FromX11(hTest, pCtx, "Georges\r\nDupr\xEA", VINF_SUCCESS);
+ /* With an embedded CRLF */
+ clipSetSelectionValues("TEXT", XA_STRING, "Georges\r\nDupr\xEA",
+ sizeof("Georges\r\nDupr\xEA"), 8);
+ testLatin1FromX11(hTest, pCtx, "Georges\r\r\nDupr\xEA", VINF_SUCCESS);
+ /* With an embedded LFCR */
+ clipSetSelectionValues("TEXT", XA_STRING, "Georges\n\rDupr\xEA",
+ sizeof("Georges\n\rDupr\xEA"), 8);
+ testLatin1FromX11(hTest, pCtx, "Georges\r\n\rDupr\xEA", VINF_SUCCESS);
+ /* A non-zero-terminated string */
+ clipSetSelectionValues("text/plain", XA_STRING,
+ "Georges Dupr\xEA!",
+ sizeof("Georges Dupr\xEA!") - 1, 8);
+ testLatin1FromX11(hTest, pCtx, "Georges Dupr\xEA!", VINF_SUCCESS);
+
+ /*** Unknown X11 format ***/
+ RTTestSub(hTest, "handling of an unknown X11 format");
+ clipInvalidateFormats();
+ clipSetSelectionValues("CLIPBOARD", XA_STRING, "Test",
+ sizeof("Test"), 8);
+ clipSendTargetUpdate(pCtx);
+ RTTEST_CHECK_MSG(hTest, clipQueryFormats() == 0,
+ (hTest, "Failed to send a format update notification\n"));
+
+ /*** Timeout from X11 ***/
+ RTTestSub(hTest, "X11 timeout");
+ clipSetSelectionValues("UTF8_STRING", XT_CONVERT_FAIL, NULL,0, 8);
+ testStringFromX11(hTest, pCtx, "", VERR_NO_DATA);
+
+ /*** No data in X11 clipboard ***/
+ RTTestSub(hTest, "a data request from an empty X11 clipboard");
+ clipSetSelectionValues("UTF8_STRING", XA_STRING, NULL,
+ 0, 8);
+ ClipRequestDataFromX11(pCtx, VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT,
+ pReq);
+ clipGetCompletedRequest(&rc, &pc, &cbActual, &pReqRet);
+ RTTEST_CHECK_MSG(hTest, rc == VERR_NO_DATA,
+ (hTest, "Returned %Rrc instead of VERR_NO_DATA\n",
+ rc));
+ RTTEST_CHECK_MSG(hTest, pReqRet == pReq,
+ (hTest, "Wrong returned request data, expected %p, got %p\n",
+ pReq, pReqRet));
+
+ /*** Ensure that VBox is notified when we return the CB to X11 ***/
+ RTTestSub(hTest, "notification of switch to X11 clipboard");
+ clipInvalidateFormats();
+ clipReportEmptyX11CB(pCtx);
+ RTTEST_CHECK_MSG(hTest, clipQueryFormats() == 0,
+ (hTest, "Failed to send a format update (release) notification\n"));
+
+ /*** request for an invalid VBox format from X11 ***/
+ RTTestSub(hTest, "a request for an invalid VBox format from X11");
+ ClipRequestDataFromX11(pCtx, 0xffff, pReq);
+ clipGetCompletedRequest(&rc, &pc, &cbActual, &pReqRet);
+ RTTEST_CHECK_MSG(hTest, rc == VERR_NOT_IMPLEMENTED,
+ (hTest, "Returned %Rrc instead of VERR_NOT_IMPLEMENTED\n",
+ rc));
+ RTTEST_CHECK_MSG(hTest, pReqRet == pReq,
+ (hTest, "Wrong returned request data, expected %p, got %p\n",
+ pReq, pReqRet));
+
+ /*** Targets failure from X11 ***/
+ RTTestSub(hTest, "X11 targets conversion failure");
+ clipSetSelectionValues("UTF8_STRING", XA_STRING, "hello world",
+ sizeof("hello world"), 8);
+ clipSetTargetsFailure();
+ Atom atom = XA_STRING;
+ long unsigned int cLen = 0;
+ int format = 8;
+ clipConvertX11Targets(NULL, (XtPointer) pCtx, NULL, &atom, NULL, &cLen,
+ &format);
+ RTTEST_CHECK_MSG(hTest, clipQueryFormats() == 0,
+ (hTest, "Wrong targets reported: %02X\n",
+ clipQueryFormats()));
+
+ /*** X11 text format conversion ***/
+ RTTestSub(hTest, "handling of X11 selection targets");
+ RTTEST_CHECK_MSG(hTest, clipTestTextFormatConversion(pCtx),
+ (hTest, "failed to select the right X11 text formats\n"));
+
+ /*** Utf-8 from VBox ***/
+ RTTestSub(hTest, "reading Utf-8 from VBox");
+ /* Simple test */
+ clipSetVBoxUtf16(pCtx, VINF_SUCCESS, "hello world",
+ sizeof("hello world") * 2);
+ testStringFromVBox(hTest, pCtx, "UTF8_STRING",
+ clipGetAtom(pCtx, "UTF8_STRING"), "hello world");
+ /* With an embedded carriage return */
+ clipSetVBoxUtf16(pCtx, VINF_SUCCESS, "hello\r\nworld",
+ sizeof("hello\r\nworld") * 2);
+ testStringFromVBox(hTest, pCtx, "text/plain;charset=UTF-8",
+ clipGetAtom(pCtx, "text/plain;charset=UTF-8"),
+ "hello\nworld");
+ /* With an embedded CRCRLF */
+ clipSetVBoxUtf16(pCtx, VINF_SUCCESS, "hello\r\r\nworld",
+ sizeof("hello\r\r\nworld") * 2);
+ testStringFromVBox(hTest, pCtx, "text/plain;charset=UTF-8",
+ clipGetAtom(pCtx, "text/plain;charset=UTF-8"),
+ "hello\r\nworld");
+ /* With an embedded CRLFCR */
+ clipSetVBoxUtf16(pCtx, VINF_SUCCESS, "hello\r\n\rworld",
+ sizeof("hello\r\n\rworld") * 2);
+ testStringFromVBox(hTest, pCtx, "text/plain;charset=UTF-8",
+ clipGetAtom(pCtx, "text/plain;charset=UTF-8"),
+ "hello\n\rworld");
+ /* An empty string */
+ clipSetVBoxUtf16(pCtx, VINF_SUCCESS, "", 2);
+ testStringFromVBox(hTest, pCtx, "text/plain;charset=utf-8",
+ clipGetAtom(pCtx, "text/plain;charset=utf-8"), "");
+ /* With an embedded Utf-8 character. */
+ clipSetVBoxUtf16(pCtx, VINF_SUCCESS, "100\xE2\x82\xAC" /* 100 Euro */,
+ 10);
+ testStringFromVBox(hTest, pCtx, "STRING",
+ clipGetAtom(pCtx, "STRING"), "100\xE2\x82\xAC");
+ /* A non-zero-terminated string */
+ clipSetVBoxUtf16(pCtx, VINF_SUCCESS, "hello world",
+ sizeof("hello world") * 2 - 2);
+ testStringFromVBox(hTest, pCtx, "TEXT", clipGetAtom(pCtx, "TEXT"),
+ "hello world");
+
+ /*** Timeout from VBox ***/
+ RTTestSub(hTest, "reading from VBox with timeout");
+ clipEmptyVBox(pCtx, VERR_TIMEOUT);
+ testStringFromVBoxFailed(hTest, pCtx, "UTF8_STRING");
+
+ /*** No data in VBox clipboard ***/
+ RTTestSub(hTest, "an empty VBox clipboard");
+ clipSetSelectionValues("TEXT", XA_STRING, "", sizeof(""), 8);
+ clipEmptyVBox(pCtx, VINF_SUCCESS);
+ RTTEST_CHECK_MSG(hTest, g_ownsSel,
+ (hTest, "VBox grabbed the clipboard with no data and we ignored it\n"));
+ testStringFromVBoxFailed(hTest, pCtx, "UTF8_STRING");
+
+ /*** An unknown VBox format ***/
+ RTTestSub(hTest, "reading an unknown VBox format");
+ clipSetSelectionValues("TEXT", XA_STRING, "", sizeof(""), 8);
+ clipSetVBoxUtf16(pCtx, VINF_SUCCESS, "", 2);
+ ClipAnnounceFormatToX11(pCtx, 0xa0000);
+ RTTEST_CHECK_MSG(hTest, g_ownsSel,
+ (hTest, "VBox grabbed the clipboard with unknown data and we ignored it\n"));
+ testStringFromVBoxFailed(hTest, pCtx, "UTF8_STRING");
+
+ /*** VBox requests a bad format ***/
+ RTTestSub(hTest, "recovery from a bad format request");
+ testBadFormatRequestFromHost(hTest, pCtx);
+
+ rc = ClipStopX11(pCtx);
+ AssertRCReturn(rc, 1);
+ ClipDestructX11(pCtx);
+
+ /*** Headless clipboard tests ***/
+
+ pCtx = ClipConstructX11(NULL, true);
+ rc = ClipStartX11(pCtx);
+ AssertRCReturn(rc, 1);
+
+ /*** Read from X11 ***/
+ RTTestSub(hTest, "reading from X11, headless clipboard");
+ /* Simple test */
+ clipSetVBoxUtf16(pCtx, VINF_SUCCESS, "",
+ sizeof("") * 2);
+ clipSetSelectionValues("UTF8_STRING", XA_STRING, "hello world",
+ sizeof("hello world"), 8);
+ testNoX11(pCtx, "reading from X11, headless clipboard");
+
+ /*** Read from VBox ***/
+ RTTestSub(hTest, "reading from VBox, headless clipboard");
+ /* Simple test */
+ clipEmptyVBox(pCtx, VERR_WRONG_ORDER);
+ clipSetSelectionValues("TEXT", XA_STRING, "", sizeof(""), 8);
+ clipSetVBoxUtf16(pCtx, VINF_SUCCESS, "hello world",
+ sizeof("hello world") * 2);
+ testNoSelectionOwnership(pCtx, "reading from VBox, headless clipboard");
+
+ rc = ClipStopX11(pCtx);
+ AssertRCReturn(rc, 1);
+ ClipDestructX11(pCtx);
+
+ return RTTestSummaryAndDestroy(hTest);
+}
+
+#endif
+
+#ifdef SMOKETEST
+
+/* This is a simple test case that just starts a copy of the X11 clipboard
+ * backend, checks the X11 clipboard and exits. If ever needed I will add an
+ * interactive mode in which the user can read and copy to the clipboard from
+ * the command line. */
+
+# include <iprt/env.h>
+# include <iprt/test.h>
+
+int ClipRequestDataForX11(VBOXCLIPBOARDCONTEXT *pCtx, uint32_t u32Format, void **ppv, uint32_t *pcb)
+{
+ RT_NOREF4(pCtx, u32Format, ppv, pcb);
+ return VERR_NO_DATA;
+}
+
+void ClipReportX11Formats(VBOXCLIPBOARDCONTEXT *pCtx, uint32_t u32Formats)
+{
+ RT_NOREF2(pCtx, u32Formats);
+}
+
+void ClipCompleteDataRequestFromX11(VBOXCLIPBOARDCONTEXT *pCtx, int rc, CLIPREADCBREQ *pReq, void *pv, uint32_t cb)
+{
+ RT_NOREF5(pCtx, rc, pReq, pv, cb);
+}
+
+int main()
+{
+ /*
+ * Init the runtime, test and say hello.
+ */
+ RTTEST hTest;
+ int rc = RTTestInitAndCreate("tstClipboardX11Smoke", &hTest);
+ if (rc)
+ return rc;
+ RTTestBanner(hTest);
+
+ /*
+ * Run the test.
+ */
+ rc = VINF_SUCCESS;
+ /* We can't test anything without an X session, so just return success
+ * in that case. */
+ if (!RTEnvExist("DISPLAY"))
+ {
+ RTTestPrintf(hTest, RTTESTLVL_INFO,
+ "X11 not available, not running test\n");
+ return RTTestSummaryAndDestroy(hTest);
+ }
+ CLIPBACKEND *pCtx = ClipConstructX11(NULL, false);
+ AssertReturn(pCtx, 1);
+ rc = ClipStartX11(pCtx);
+ AssertRCReturn(rc, 1);
+ /* Give the clipboard time to synchronise. */
+ RTThreadSleep(500);
+ rc = ClipStopX11(pCtx);
+ AssertRCReturn(rc, 1);
+ ClipDestructX11(pCtx);
+ return RTTestSummaryAndDestroy(hTest);
+}
+
+#endif /* SMOKETEST defined */
+