summaryrefslogtreecommitdiffstats
path: root/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11.cpp941
1 files changed, 941 insertions, 0 deletions
diff --git a/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11.cpp b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11.cpp
new file mode 100644
index 00000000..f9bc19c1
--- /dev/null
+++ b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11.cpp
@@ -0,0 +1,941 @@
+/* $Id: tstClipboardGH-X11.cpp $ */
+/** @file
+ * Shared Clipboard guest/host X11 code test cases.
+ */
+
+/*
+ * Copyright (C) 2011-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <VBox/GuestHost/SharedClipboard.h>
+#include <VBox/GuestHost/SharedClipboard-x11.h>
+#include <VBox/GuestHost/clipboard-helper.h>
+#include <VBox/HostServices/VBoxClipboardSvc.h>
+
+#include <iprt/assert.h>
+#include <iprt/string.h>
+#include <iprt/test.h>
+#include <iprt/utf16.h>
+
+#include <poll.h>
+#include <X11/Xatom.h>
+
+
+/*********************************************************************************************************************************
+* Externals *
+*********************************************************************************************************************************/
+extern SHCLX11FMTTABLE g_aFormats[];
+
+extern void clipUpdateX11Targets(PSHCLX11CTX pCtx, SHCLX11FMTIDX *pTargets, size_t cTargets);
+extern void clipReportEmpty(PSHCLX11CTX pCtx);
+extern void clipConvertDataFromX11Worker(void *pClient, void *pvSrc, unsigned cbSrc);
+extern SHCLX11FMTIDX clipGetTextFormatFromTargets(PSHCLX11CTX pCtx, SHCLX11FMTIDX *pTargets, size_t cTargets);
+extern SHCLX11FMT clipRealFormatForX11Format(SHCLX11FMTIDX uFmtIdx);
+extern Atom clipGetAtom(PSHCLX11CTX pCtx, const char *pcszName);
+extern void clipQueryX11Targets(PSHCLX11CTX pCtx);
+extern size_t clipReportMaxX11Formats(void);
+
+
+/*********************************************************************************************************************************
+* Internal prototypes *
+*********************************************************************************************************************************/
+static SHCLX11FMTIDX tstClipFindX11FormatByAtomText(const char *pcszAtom);
+
+
+/*********************************************************************************************************************************
+* Prototypes, used for testcases by clipboard-x11.cpp *
+*********************************************************************************************************************************/
+void tstRequestTargets(SHCLX11CTX* pCtx);
+void tstClipRequestData(PSHCLX11CTX pCtx, SHCLX11FMTIDX target, void *closure);
+void tstThreadScheduleCall(void (*proc)(void *, void *), void *client_data);
+
+
+/*********************************************************************************************************************************
+* Own callback implementations *
+*********************************************************************************************************************************/
+extern DECLCALLBACK(void) clipQueryX11TargetsCallback(Widget widget, XtPointer pClient,
+ Atom * /* selection */, Atom *atomType,
+ XtPointer pValue, long unsigned int *pcLen,
+ int *piFormat);
+
+
+/*********************************************************************************************************************************
+* Defines *
+*********************************************************************************************************************************/
+#define TESTCASE_WIDGET_ID (Widget)0xffff
+
+
+/* For the purpose of the test case, we just execute the procedure to be
+ * scheduled, as we are running single threaded. */
+void tstThreadScheduleCall(void (*proc)(void *, void *), void *client_data)
+{
+ proc(client_data, NULL);
+}
+
+/* The data in the simulated VBox clipboard. */
+static int g_tst_rcDataVBox = VINF_SUCCESS;
+static void *g_tst_pvDataVBox = NULL;
+static uint32_t g_tst_cbDataVBox = 0;
+
+/* Set empty data in the simulated VBox clipboard. */
+static void tstClipEmptyVBox(PSHCLX11CTX pCtx, int retval)
+{
+ g_tst_rcDataVBox = retval;
+ RTMemFree(g_tst_pvDataVBox);
+ g_tst_pvDataVBox = NULL;
+ g_tst_cbDataVBox = 0;
+ ShClX11ReportFormatsToX11(pCtx, 0);
+}
+
+/* Set the data in the simulated VBox clipboard. */
+static int tstClipSetVBoxUtf16(PSHCLX11CTX 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_tst_pvDataVBox)
+ RTMemFree(g_tst_pvDataVBox);
+ g_tst_rcDataVBox = retval;
+ g_tst_pvDataVBox = pv;
+ g_tst_cbDataVBox = cb;
+ ShClX11ReportFormatsToX11(pCtx, VBOX_SHCL_FMT_UNICODETEXT);
+ return VINF_SUCCESS;
+}
+
+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_NOREF(application_name, application_class, widget_class, display);
+ return TESTCASE_WIDGET_ID;
+}
+
+void XtSetMappedWhenManaged(Widget widget, _XtBoolean mapped_when_managed) { RT_NOREF(widget, mapped_when_managed); }
+
+void XtRealizeWidget(Widget widget) { NOREF(widget); }
+
+XtInputId XtAppAddInput(XtAppContext app_context, int source, XtPointer condition, XtInputCallbackProc proc, XtPointer closure)
+{
+ RT_NOREF(app_context, source, condition, proc, closure);
+ return 0xffff;
+}
+
+/* Atoms we need other than the formats we support. */
+static const char *g_tst_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;
+ size_t const cFormats = clipReportMaxX11Formats();
+ size_t i;
+ for (i = 0; i < cFormats; ++i)
+ {
+ if (!strcmp(pcsz, g_aFormats[i].pcszAtom))
+ atom = (Atom) (i + 0x1000);
+ }
+ for (i = 0; i < RT_ELEMENTS(g_tst_apszSupAtoms); ++i)
+ if (!strcmp(pcsz, g_tst_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 SHCLX11FMTIDX g_tst_aSelTargetsIdx[10] = { 0 };
+static size_t g_tst_cTargets = 0;
+
+void tstRequestTargets(SHCLX11CTX* pCtx)
+{
+ clipUpdateX11Targets(pCtx, g_tst_aSelTargetsIdx, g_tst_cTargets);
+}
+
+/* The current values of the X selection, which will be returned to the
+ * XtGetSelectionValue callback. */
+static Atom g_tst_atmSelType = 0;
+static const void *g_tst_pSelData = NULL;
+static unsigned long g_tst_cSelData = 0;
+static int g_tst_selFormat = 0;
+
+void tstClipRequestData(PSHCLX11CTX pCtx, SHCLX11FMTIDX target, void *closure)
+{
+ RT_NOREF(pCtx);
+ unsigned long count = 0;
+ int format = 0;
+ if (target != g_tst_aSelTargetsIdx[0])
+ {
+ clipConvertDataFromX11Worker(closure, NULL, 0); /* Could not convert to target. */
+ return;
+ }
+ void *pValue = NULL;
+ pValue = g_tst_pSelData ? RTMemDup(g_tst_pSelData, g_tst_cSelData) : NULL;
+ count = g_tst_pSelData ? g_tst_cSelData : 0;
+ format = g_tst_selFormat;
+ if (!pValue)
+ {
+ count = 0;
+ format = 0;
+ }
+ clipConvertDataFromX11Worker(closure, pValue, count * format / 8);
+ if (pValue)
+ RTMemFree(pValue);
+}
+
+/* The formats currently on offer from X11 via the shared clipboard. */
+static uint32_t g_tst_uX11Formats = 0;
+
+static uint32_t tstClipQueryFormats(void)
+{
+ return g_tst_uX11Formats;
+}
+
+static void tstClipInvalidateFormats(void)
+{
+ g_tst_uX11Formats = ~0;
+}
+
+/* Does our clipboard code currently own the selection? */
+static bool g_tst_fOwnsSel = false;
+/* The procedure that is called when we should convert the selection to a
+ * given format. */
+static XtConvertSelectionProc g_tst_pfnSelConvert = NULL;
+/* The procedure which is called when we lose the selection. */
+static XtLoseSelectionProc g_tst_pfnSelLose = NULL;
+/* The procedure which is called when the selection transfer has completed. */
+static XtSelectionDoneProc g_tst_pfnSelDone = NULL;
+
+Boolean XtOwnSelection(Widget widget, Atom selection, Time time,
+ XtConvertSelectionProc convert,
+ XtLoseSelectionProc lose,
+ XtSelectionDoneProc done)
+{
+ RT_NOREF(widget, time);
+ if (selection != XInternAtom(NULL, "CLIPBOARD", 0))
+ return True; /* We don't really care about this. */
+ g_tst_fOwnsSel = true; /* Always succeed. */
+ g_tst_pfnSelConvert = convert;
+ g_tst_pfnSelLose = lose;
+ g_tst_pfnSelDone = done;
+ return True;
+}
+
+void XtDisownSelection(Widget widget, Atom selection, Time time)
+{
+ RT_NOREF(widget, time, selection);
+ g_tst_fOwnsSel = false;
+ g_tst_pfnSelConvert = NULL;
+ g_tst_pfnSelLose = NULL;
+ g_tst_pfnSelDone = NULL;
+}
+
+/* Request the shared clipboard to convert its data to a given format. */
+static bool tstClipConvertSelection(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_tst_fOwnsSel)
+ return false;
+ if (!g_tst_pfnSelConvert)
+ return false;
+ Atom clipAtom = XInternAtom(NULL, "CLIPBOARD", 0);
+ if (!g_tst_pfnSelConvert(TESTCASE_WIDGET_ID, &clipAtom, &target, type,
+ value, length, format))
+ return false;
+ if (g_tst_pfnSelDone)
+ g_tst_pfnSelDone(TESTCASE_WIDGET_ID, &clipAtom, &target);
+ return true;
+}
+
+/* Set the current X selection data */
+static void tstClipSetSelectionValues(const char *pcszTarget, Atom type,
+ const void *data,
+ unsigned long count, int format)
+{
+ Atom clipAtom = XInternAtom(NULL, "CLIPBOARD", 0);
+ g_tst_aSelTargetsIdx[0] = tstClipFindX11FormatByAtomText(pcszTarget);
+ g_tst_cTargets = 1;
+ g_tst_atmSelType = type;
+ g_tst_pSelData = data;
+ g_tst_cSelData = count;
+ g_tst_selFormat = format;
+ if (g_tst_pfnSelLose)
+ g_tst_pfnSelLose(TESTCASE_WIDGET_ID, &clipAtom);
+ g_tst_fOwnsSel = false;
+}
+
+static void tstClipSendTargetUpdate(PSHCLX11CTX pCtx)
+{
+ clipQueryX11Targets(pCtx);
+}
+
+/* Configure if and how the X11 TARGETS clipboard target will fail. */
+static void tstClipSetTargetsFailure(void)
+{
+ g_tst_cTargets = 0;
+}
+
+char *XtMalloc(Cardinal size)
+{
+ return (char *) RTMemAlloc(size);
+}
+
+void XtFree(char *ptr)
+{
+ RTMemFree((void *)ptr);
+}
+
+char *XGetAtomName(Display *display, Atom atom)
+{
+ RT_NOREF(display);
+ const char *pcszName = NULL;
+ if (atom < 0x1000)
+ return NULL;
+ if (0x1000 <= atom && atom < 0x2000)
+ {
+ unsigned index = atom - 0x1000;
+ AssertReturn(index < clipReportMaxX11Formats(), NULL);
+ pcszName = g_aFormats[index].pcszAtom;
+ }
+ else
+ {
+ unsigned index = atom - 0x2000;
+ AssertReturn(index < RT_ELEMENTS(g_tst_apszSupAtoms), NULL);
+ pcszName = g_tst_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 TESTCASE_MAX_BUF_SIZE 256
+
+static int g_tst_rcCompleted = VINF_SUCCESS;
+static int g_tst_cbCompleted = 0;
+static CLIPREADCBREQ *g_tst_pCompletedReq = NULL;
+static char g_tst_abCompletedBuf[TESTCASE_MAX_BUF_SIZE];
+
+static DECLCALLBACK(int) tstShClReportFormatsCallback(PSHCLCONTEXT pCtx, uint32_t fFormats, void *pvUser)
+{
+ RT_NOREF(pCtx, pvUser);
+ g_tst_uX11Formats = fFormats;
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) tstShClOnRequestDataFromSourceCallback(PSHCLCONTEXT pCtx, SHCLFORMAT uFmt, void **ppv, uint32_t *pcb, void *pvUser)
+{
+ RT_NOREF(pCtx, uFmt, pvUser);
+ *pcb = g_tst_cbDataVBox;
+ if (g_tst_pvDataVBox != NULL)
+ {
+ void *pv = RTMemDup(g_tst_pvDataVBox, g_tst_cbDataVBox);
+ *ppv = pv;
+ return pv != NULL ? g_tst_rcDataVBox : VERR_NO_MEMORY;
+ }
+ *ppv = NULL;
+ return g_tst_rcDataVBox;
+}
+
+static DECLCALLBACK(int) tstShClOnSendDataToDestCallback(PSHCLCONTEXT pCtx, void *pv, uint32_t cb, void *pvUser)
+{
+ RT_NOREF(pCtx);
+
+ PSHCLX11READDATAREQ pData = (PSHCLX11READDATAREQ)pvUser;
+
+ if (cb <= TESTCASE_MAX_BUF_SIZE)
+ {
+ g_tst_rcCompleted = pData->rcCompletion;
+ if (cb != 0)
+ memcpy(g_tst_abCompletedBuf, pv, cb);
+ }
+ else
+ g_tst_rcCompleted = VERR_BUFFER_OVERFLOW;
+ g_tst_cbCompleted = cb;
+ g_tst_pCompletedReq = pData->pReq;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Looks up the X11 format matching a given X11 atom text.
+ *
+ * @returns the format on success, NIL_CLIPX11FORMAT on failure
+ * @param pcszAtom Atom text to look up format for.
+ */
+static SHCLX11FMTIDX tstClipFindX11FormatByAtomText(const char *pcszAtom)
+{
+ const size_t j = clipReportMaxX11Formats();
+
+ for (unsigned i = 0; i < j; ++i)
+ {
+ if (!strcmp(g_aFormats[i].pcszAtom, pcszAtom))
+ return i;
+ }
+ return NIL_CLIPX11FORMAT;
+}
+
+static bool tstClipTextFormatConversion(PSHCLX11CTX pCtx)
+{
+ bool fSuccess = true;
+ SHCLX11FMTIDX targets[2];
+ SHCLX11FMTIDX x11Format;
+ targets[0] = tstClipFindX11FormatByAtomText("text/plain");
+ targets[1] = tstClipFindX11FormatByAtomText("image/bmp");
+ x11Format = clipGetTextFormatFromTargets(pCtx, targets, 2);
+ if (clipRealFormatForX11Format(x11Format) != SHCLX11FMT_TEXT)
+ fSuccess = false;
+ targets[0] = tstClipFindX11FormatByAtomText("UTF8_STRING");
+ targets[1] = tstClipFindX11FormatByAtomText("text/plain");
+ x11Format = clipGetTextFormatFromTargets(pCtx, targets, 2);
+ if (clipRealFormatForX11Format(x11Format) != SHCLX11FMT_UTF8)
+ fSuccess = false;
+ return fSuccess;
+}
+
+static void tstClipGetCompletedRequest(int *prc, char ** ppc, uint32_t *pcb, CLIPREADCBREQ **ppReq)
+{
+ *prc = g_tst_rcCompleted;
+ *ppc = g_tst_abCompletedBuf;
+ *pcb = g_tst_cbCompleted;
+ *ppReq = g_tst_pCompletedReq;
+}
+
+static void tstStringFromX11(RTTEST hTest, PSHCLX11CTX pCtx,
+ const char *pcszExp, int rcExp)
+{
+ bool retval = true;
+ tstClipSendTargetUpdate(pCtx);
+ if (tstClipQueryFormats() != VBOX_SHCL_FMT_UNICODETEXT)
+ {
+ RTTestFailed(hTest, "Wrong targets reported: %02X\n", tstClipQueryFormats());
+ }
+ else
+ {
+ char *pc;
+ CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)&pReq, *pReqRet = NULL;
+ ShClX11ReadDataFromX11(pCtx, VBOX_SHCL_FMT_UNICODETEXT, pReq);
+ int rc = VINF_SUCCESS;
+ uint32_t cbActual = 0;
+ tstClipGetCompletedRequest(&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[TESTCASE_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(TESTCASE_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",
+ TESTCASE_MAX_BUF_SIZE, pc, pcszExp);
+ }
+ }
+ }
+ }
+ if (!retval)
+ RTTestFailureDetails(hTest, "Expected: string \"%s\", rc %Rrc\n",
+ pcszExp, rcExp);
+}
+
+static void tstLatin1FromX11(RTTEST hTest, PSHCLX11CTX pCtx,
+ const char *pcszExp, int rcExp)
+{
+ bool retval = false;
+ tstClipSendTargetUpdate(pCtx);
+ if (tstClipQueryFormats() != VBOX_SHCL_FMT_UNICODETEXT)
+ RTTestFailed(hTest, "Wrong targets reported: %02X\n",
+ tstClipQueryFormats());
+ else
+ {
+ char *pc;
+ CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)&pReq, *pReqRet = NULL;
+ ShClX11ReadDataFromX11(pCtx, VBOX_SHCL_FMT_UNICODETEXT, pReq);
+ int rc = VINF_SUCCESS;
+ uint32_t cbActual = 0;
+ tstClipGetCompletedRequest(&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[TESTCASE_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(TESTCASE_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",
+ TESTCASE_MAX_BUF_SIZE, pc, pcszExp);
+ }
+ }
+ }
+ if (!retval)
+ RTTestFailureDetails(hTest, "Expected: string \"%s\", rc %Rrc\n",
+ pcszExp, rcExp);
+}
+
+static void tstStringFromVBox(RTTEST hTest, PSHCLX11CTX pCtx, const char *pcszTarget, Atom typeExp, const char *valueExp)
+{
+ RT_NOREF(pCtx);
+ bool retval = false;
+ Atom type;
+ XtPointer value = NULL;
+ unsigned long length;
+ int format;
+ size_t lenExp = strlen(valueExp);
+ if (tstClipConvertSelection(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, (expected %u), format %d (expected %d), value \"%.*s\" (expected \"%.*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 tstNoX11(PSHCLX11CTX pCtx, const char *pcszTestCtx)
+{
+ CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)&pReq;
+ int rc = ShClX11ReadDataFromX11(pCtx, VBOX_SHCL_FMT_UNICODETEXT, pReq);
+ RTTESTI_CHECK_MSG(rc == VERR_NO_DATA, ("context: %s\n", pcszTestCtx));
+}
+
+static void tstStringFromVBoxFailed(RTTEST hTest, PSHCLX11CTX pCtx, const char *pcszTarget)
+{
+ RT_NOREF(pCtx);
+ Atom type;
+ XtPointer value = NULL;
+ unsigned long length;
+ int format;
+ RTTEST_CHECK_MSG(hTest, !tstClipConvertSelection(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 tstNoSelectionOwnership(PSHCLX11CTX pCtx, const char *pcszTestCtx)
+{
+ RT_NOREF(pCtx);
+ RTTESTI_CHECK_MSG(!g_tst_fOwnsSel, ("context: %s\n", pcszTestCtx));
+}
+
+static void tstBadFormatRequestFromHost(RTTEST hTest, PSHCLX11CTX pCtx)
+{
+ tstClipSetSelectionValues("UTF8_STRING", XA_STRING, "hello world",
+ sizeof("hello world"), 8);
+ tstClipSendTargetUpdate(pCtx);
+ if (tstClipQueryFormats() != VBOX_SHCL_FMT_UNICODETEXT)
+ RTTestFailed(hTest, "Wrong targets reported: %02X\n",
+ tstClipQueryFormats());
+ else
+ {
+ char *pc;
+ CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)&pReq, *pReqRet = NULL;
+ ShClX11ReadDataFromX11(pCtx, 0xF000 /* vboxFormat */, pReq); /* Bad format. */
+ int rc = VINF_SUCCESS;
+ uint32_t cbActual = 0;
+ tstClipGetCompletedRequest(&rc, &pc, &cbActual, &pReqRet);
+ if (rc != VERR_NOT_IMPLEMENTED)
+ RTTestFailed(hTest, "Wrong return code, expected VERR_NOT_IMPLEMENTED, got %Rrc\n",
+ rc);
+ tstClipSetSelectionValues("", XA_STRING, "", sizeof(""), 8);
+ tstClipSendTargetUpdate(pCtx);
+ if (tstClipQueryFormats() == VBOX_SHCL_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("tstClipboardGH-X11", &hTest);
+ if (RT_FAILURE(rc))
+ return RTEXITCODE_FAILURE;
+ RTTestBanner(hTest);
+
+ /*
+ * Run the tests.
+ */
+ SHCLCALLBACKS Callbacks;
+ RT_ZERO(Callbacks);
+ Callbacks.pfnReportFormats = tstShClReportFormatsCallback;
+ Callbacks.pfnOnRequestDataFromSource = tstShClOnRequestDataFromSourceCallback;
+ Callbacks.pfnOnSendDataToDest = tstShClOnSendDataToDestCallback;
+
+ SHCLX11CTX X11Ctx;
+ rc = ShClX11Init(&X11Ctx, &Callbacks, NULL /* pParent */, false /* fHeadless */);
+ AssertRCReturn(rc, RTEXITCODE_FAILURE);
+
+ char *pc;
+ uint32_t cbActual;
+ CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)&pReq, *pReqRet = NULL;
+
+ /* UTF-8 from X11 */
+ RTTestSub(hTest, "reading UTF-8 from X11");
+ /* Simple test */
+ tstClipSetSelectionValues("UTF8_STRING", XA_STRING, "hello world",
+ sizeof("hello world"), 8);
+ tstStringFromX11(hTest, &X11Ctx, "hello world", VINF_SUCCESS);
+ /* With an embedded carriage return */
+ tstClipSetSelectionValues("text/plain;charset=UTF-8", XA_STRING,
+ "hello\nworld", sizeof("hello\nworld"), 8);
+ tstStringFromX11(hTest, &X11Ctx, "hello\r\nworld", VINF_SUCCESS);
+ /* With an embedded CRLF */
+ tstClipSetSelectionValues("text/plain;charset=UTF-8", XA_STRING,
+ "hello\r\nworld", sizeof("hello\r\nworld"), 8);
+ tstStringFromX11(hTest, &X11Ctx, "hello\r\r\nworld", VINF_SUCCESS);
+ /* With an embedded LFCR */
+ tstClipSetSelectionValues("text/plain;charset=UTF-8", XA_STRING,
+ "hello\n\rworld", sizeof("hello\n\rworld"), 8);
+ tstStringFromX11(hTest, &X11Ctx, "hello\r\n\rworld", VINF_SUCCESS);
+ /* An empty string */
+ tstClipSetSelectionValues("text/plain;charset=utf-8", XA_STRING, "",
+ sizeof(""), 8);
+ tstStringFromX11(hTest, &X11Ctx, "", VINF_SUCCESS);
+ /* With an embedded UTF-8 character. */
+ tstClipSetSelectionValues("STRING", XA_STRING,
+ "100\xE2\x82\xAC" /* 100 Euro */,
+ sizeof("100\xE2\x82\xAC"), 8);
+ tstStringFromX11(hTest, &X11Ctx, "100\xE2\x82\xAC", VINF_SUCCESS);
+ /* A non-zero-terminated string */
+ tstClipSetSelectionValues("TEXT", XA_STRING,
+ "hello world", sizeof("hello world") - 1, 8);
+ tstStringFromX11(hTest, &X11Ctx, "hello world", VINF_SUCCESS);
+
+ /* Latin1 from X11 */
+ RTTestSub(hTest, "reading Latin1 from X11");
+ /* Simple test */
+ tstClipSetSelectionValues("STRING", XA_STRING, "Georges Dupr\xEA",
+ sizeof("Georges Dupr\xEA"), 8);
+ tstLatin1FromX11(hTest, &X11Ctx, "Georges Dupr\xEA", VINF_SUCCESS);
+ /* With an embedded carriage return */
+ tstClipSetSelectionValues("TEXT", XA_STRING, "Georges\nDupr\xEA",
+ sizeof("Georges\nDupr\xEA"), 8);
+ tstLatin1FromX11(hTest, &X11Ctx, "Georges\r\nDupr\xEA", VINF_SUCCESS);
+ /* With an embedded CRLF */
+ tstClipSetSelectionValues("TEXT", XA_STRING, "Georges\r\nDupr\xEA",
+ sizeof("Georges\r\nDupr\xEA"), 8);
+ tstLatin1FromX11(hTest, &X11Ctx, "Georges\r\r\nDupr\xEA", VINF_SUCCESS);
+ /* With an embedded LFCR */
+ tstClipSetSelectionValues("TEXT", XA_STRING, "Georges\n\rDupr\xEA",
+ sizeof("Georges\n\rDupr\xEA"), 8);
+ tstLatin1FromX11(hTest, &X11Ctx, "Georges\r\n\rDupr\xEA", VINF_SUCCESS);
+ /* A non-zero-terminated string */
+ tstClipSetSelectionValues("text/plain", XA_STRING,
+ "Georges Dupr\xEA!",
+ sizeof("Georges Dupr\xEA!") - 1, 8);
+ tstLatin1FromX11(hTest, &X11Ctx, "Georges Dupr\xEA!", VINF_SUCCESS);
+
+ /*
+ * Unknown X11 format
+ */
+ RTTestSub(hTest, "handling of an unknown X11 format");
+ tstClipInvalidateFormats();
+ tstClipSetSelectionValues("CLIPBOARD", XA_STRING, "Test",
+ sizeof("Test"), 8);
+ tstClipSendTargetUpdate(&X11Ctx);
+ RTTEST_CHECK_MSG(hTest, tstClipQueryFormats() == 0,
+ (hTest, "Failed to send a format update notification\n"));
+
+ /*
+ * Timeout from X11
+ */
+ RTTestSub(hTest, "X11 timeout");
+ tstClipSetSelectionValues("UTF8_STRING", XT_CONVERT_FAIL, NULL,0, 8);
+ tstStringFromX11(hTest, &X11Ctx, "", VERR_NO_DATA);
+
+ /*
+ * No data in X11 clipboard
+ */
+ RTTestSub(hTest, "a data request from an empty X11 clipboard");
+ tstClipSetSelectionValues("UTF8_STRING", XA_STRING, NULL,
+ 0, 8);
+ ShClX11ReadDataFromX11(&X11Ctx, VBOX_SHCL_FMT_UNICODETEXT, pReq);
+ tstClipGetCompletedRequest(&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");
+ tstClipInvalidateFormats();
+ clipReportEmpty(&X11Ctx);
+ RTTEST_CHECK_MSG(hTest, tstClipQueryFormats() == 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");
+ /* Testing for 0xffff will go into handling VBOX_SHCL_FMT_UNICODETEXT, where we don't have
+ * have any data at the moment so far, so this will return VERR_NO_DATA. */
+ ShClX11ReadDataFromX11(&X11Ctx, 0xffff /* vboxFormat */, pReq);
+ tstClipGetCompletedRequest(&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));
+
+ /*
+ * Targets failure from X11
+ */
+ RTTestSub(hTest, "X11 targets conversion failure");
+ tstClipSetSelectionValues("UTF8_STRING", XA_STRING, "hello world",
+ sizeof("hello world"), 8);
+ tstClipSetTargetsFailure();
+ Atom atom = XA_STRING;
+ long unsigned int cLen = 0;
+ int format = 8;
+ clipQueryX11TargetsCallback(NULL, (XtPointer) &X11Ctx, NULL, &atom, NULL, &cLen,
+ &format);
+ RTTEST_CHECK_MSG(hTest, tstClipQueryFormats() == 0,
+ (hTest, "Wrong targets reported: %02X\n",
+ tstClipQueryFormats()));
+
+ /*
+ * X11 text format conversion
+ */
+ RTTestSub(hTest, "handling of X11 selection targets");
+ RTTEST_CHECK_MSG(hTest, tstClipTextFormatConversion(&X11Ctx),
+ (hTest, "failed to select the right X11 text formats\n"));
+ /*
+ * UTF-8 from VBox
+ */
+ RTTestSub(hTest, "reading UTF-8 from VBox");
+ /* Simple test */
+ tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "hello world",
+ sizeof("hello world") * 2);
+ tstStringFromVBox(hTest, &X11Ctx, "UTF8_STRING",
+ clipGetAtom(&X11Ctx, "UTF8_STRING"), "hello world");
+ /* With an embedded carriage return */
+ tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "hello\r\nworld",
+ sizeof("hello\r\nworld") * 2);
+ tstStringFromVBox(hTest, &X11Ctx, "text/plain;charset=UTF-8",
+ clipGetAtom(&X11Ctx, "text/plain;charset=UTF-8"),
+ "hello\nworld");
+ /* With an embedded CRCRLF */
+ tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "hello\r\r\nworld",
+ sizeof("hello\r\r\nworld") * 2);
+ tstStringFromVBox(hTest, &X11Ctx, "text/plain;charset=UTF-8",
+ clipGetAtom(&X11Ctx, "text/plain;charset=UTF-8"),
+ "hello\r\nworld");
+ /* With an embedded CRLFCR */
+ tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "hello\r\n\rworld",
+ sizeof("hello\r\n\rworld") * 2);
+ tstStringFromVBox(hTest, &X11Ctx, "text/plain;charset=UTF-8",
+ clipGetAtom(&X11Ctx, "text/plain;charset=UTF-8"),
+ "hello\n\rworld");
+ /* An empty string */
+ tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "", 2);
+ tstStringFromVBox(hTest, &X11Ctx, "text/plain;charset=utf-8",
+ clipGetAtom(&X11Ctx, "text/plain;charset=utf-8"), "");
+ /* With an embedded UTF-8 character. */
+ tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "100\xE2\x82\xAC" /* 100 Euro */,
+ 10);
+ tstStringFromVBox(hTest, &X11Ctx, "STRING",
+ clipGetAtom(&X11Ctx, "STRING"), "100\xE2\x82\xAC");
+ /* A non-zero-terminated string */
+ tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "hello world",
+ sizeof("hello world") * 2 - 2);
+ tstStringFromVBox(hTest, &X11Ctx, "TEXT", clipGetAtom(&X11Ctx, "TEXT"),
+ "hello world");
+
+ /*
+ * Timeout from VBox
+ */
+ RTTestSub(hTest, "reading from VBox with timeout");
+ tstClipEmptyVBox(&X11Ctx, VERR_TIMEOUT);
+ tstStringFromVBoxFailed(hTest, &X11Ctx, "UTF8_STRING");
+
+ /*
+ * No data in VBox clipboard
+ */
+ RTTestSub(hTest, "an empty VBox clipboard");
+ tstClipSetSelectionValues("TEXT", XA_STRING, "", sizeof(""), 8);
+ tstClipEmptyVBox(&X11Ctx, VINF_SUCCESS);
+ RTTEST_CHECK_MSG(hTest, g_tst_fOwnsSel,
+ (hTest, "VBox grabbed the clipboard with no data and we ignored it\n"));
+ tstStringFromVBoxFailed(hTest, &X11Ctx, "UTF8_STRING");
+
+ /*
+ * An unknown VBox format
+ */
+ RTTestSub(hTest, "reading an unknown VBox format");
+ tstClipSetSelectionValues("TEXT", XA_STRING, "", sizeof(""), 8);
+ tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "", 2);
+ ShClX11ReportFormatsToX11(&X11Ctx, 0xa0000);
+ RTTEST_CHECK_MSG(hTest, g_tst_fOwnsSel,
+ (hTest, "VBox grabbed the clipboard with unknown data and we ignored it\n"));
+ tstStringFromVBoxFailed(hTest, &X11Ctx, "UTF8_STRING");
+
+ /*
+ * VBox requests a bad format
+ */
+ RTTestSub(hTest, "recovery from a bad format request");
+ tstBadFormatRequestFromHost(hTest, &X11Ctx);
+
+ ShClX11Destroy(&X11Ctx);
+
+ /*
+ * Headless clipboard tests
+ */
+ rc = ShClX11Init(&X11Ctx, &Callbacks, NULL /* pParent */, true /* fHeadless */);
+ AssertRCReturn(rc, RTEXITCODE_FAILURE);
+
+ /* Read from X11 */
+ RTTestSub(hTest, "reading from X11, headless clipboard");
+ /* Simple test */
+ tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "",
+ sizeof("") * 2);
+ tstClipSetSelectionValues("UTF8_STRING", XA_STRING, "hello world",
+ sizeof("hello world"), 8);
+ tstNoX11(&X11Ctx, "reading from X11, headless clipboard");
+
+ /* Read from VBox */
+ RTTestSub(hTest, "reading from VBox, headless clipboard");
+ /* Simple test */
+ tstClipEmptyVBox(&X11Ctx, VERR_WRONG_ORDER);
+ tstClipSetSelectionValues("TEXT", XA_STRING, "", sizeof(""), 8);
+ tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "hello world",
+ sizeof("hello world") * 2);
+ tstNoSelectionOwnership(&X11Ctx, "reading from VBox, headless clipboard");
+
+ ShClX11Destroy(&X11Ctx);
+
+ return RTTestSummaryAndDestroy(hTest);
+}
+