Index: /trunk/src/VBox/GuestHost/SharedClipboard/Makefile.kmk
===================================================================
--- /trunk/src/VBox/GuestHost/SharedClipboard/Makefile.kmk	(revision 85295)
+++ /trunk/src/VBox/GuestHost/SharedClipboard/Makefile.kmk	(revision 85296)
@@ -20,5 +20,5 @@
 
 ifdef VBOX_WITH_TESTCASES
- include $(PATH_SUB_CURRENT)/testcases/Makefile.kmk
+ include $(PATH_SUB_CURRENT)/testcase/Makefile.kmk
 endif
 
Index: /trunk/src/VBox/GuestHost/SharedClipboard/testcase/Makefile.kmk
===================================================================
--- /trunk/src/VBox/GuestHost/SharedClipboard/testcase/Makefile.kmk	(revision 85296)
+++ /trunk/src/VBox/GuestHost/SharedClipboard/testcase/Makefile.kmk	(revision 85296)
@@ -0,0 +1,60 @@
+# $Id$
+## @file
+# Sub-Makefile for the Shared Clipboard Guest/Host testcases.
+#
+
+#
+# Copyright (C) 2011-2020 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.
+#
+
+SUB_DEPTH = ../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+if defined(VBOX_WITH_TESTCASES) && !defined(VBOX_ONLY_ADDITIONS) && !defined(VBOX_ONLY_SDK)
+ if1of ($(KBUILD_TARGET), freebsd linux netbsd openbsd solaris)
+
+  PROGRAMS += tstClipboardGH-X11 tstClipboardGH-X11Smoke
+  TESTING  += \
+  	$(tstClipboardGH-X11_0_OUTDIR)/tstClipboardGH-X11.run \
+  	$(tstClipboardGH-X11_0_OUTDIR)/tstClipboardGH-X11Smoke.run
+
+  tstClipboardGH-X11_TEMPLATE  = VBOXR3TSTEXE
+  tstClipboardGH-X11_DEFS      = VBOX_WITH_HGCM UNIT_TEST TESTCASE
+  tstClipboardGH-X11_SOURCES   = \
+	tstClipboardGH-X11.cpp \
+	../clipboard-x11.cpp \
+	../clipboard-common.cpp
+  tstClipboardGH-X11_CXXFLAGS += -Wno-array-bounds
+  tstClipboardGH-X11_CLEAN     = $(tstClipboardGH-X11_0_OUTDIR)/tstClipboardGH-X11.run
+
+  tstClipboardGH-X11Smoke_TEMPLATE = VBOXR3TSTEXE
+  tstClipboardGH-X11Smoke_DEFS     = VBOX_WITH_HGCM SMOKETEST
+  tstClipboardGH-X11Smoke_SOURCES  = \
+	tstClipboardGH-X11Smoke.cpp \
+	../clipboard-x11.cpp \
+	../clipboard-common.cpp
+  tstClipboardGH-X11Smoke_LIBPATH  = $(VBOX_LIBPATH_X11)
+  tstClipboardGH-X11Smoke_LIBS     = X11 Xt
+  tstClipboardGH-X11Smoke_CLEAN    = $(tstClipboardGH-X11Smoke_0_OUTDIR)/tstClipboardGH-X11Smoke.run
+
+  $$(tstClipboardGH-X11_0_OUTDIR)/tstClipboardGH-X11.run: $$(tstClipboardGH-X11_1_STAGE_TARGET)
+	export VBOX_LOG_DEST=nofile; $(tstClipboardGH-X11_1_STAGE_TARGET) quiet
+	$(QUIET)$(APPEND) -t "$@" "done"
+
+  $$(tstClipboardGH-X11Smoke_0_OUTDIR)/tstClipboardGH-X11Smoke.run:	$$(tstClipboardGH-X11Smoke_1_STAGE_TARGET)
+	export VBOX_LOG_DEST=nofile; $(tstClipboardGH-X11Smoke_1_STAGE_TARGET) quiet
+	$(QUIET)$(APPEND) -t "$@" "done"
+
+ endif
+endif
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
Index: /trunk/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11.cpp
===================================================================
--- /trunk/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11.cpp	(revision 85296)
+++ /trunk/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11.cpp	(revision 85296)
@@ -0,0 +1,927 @@
+/* $Id$ */
+/** @file
+ * Shared Clipboard guest/host X11 code test cases.
+ */
+
+/*
+ * Copyright (C) 2011-2020 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.
+ */
+
+#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 clipReportEmptyX11CB(PSHCLX11CTX pCtx);
+extern void clipConvertDataFromX11CallbackWorker(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);
+
+
+/*********************************************************************************************************************************
+*   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 tstClipQueueToEventThread(void (*proc)(void *, void *), void *client_data);
+
+
+/*********************************************************************************************************************************
+*   Own callback implementations                                                                                                 *
+*********************************************************************************************************************************/
+extern DECLCALLBACK(void) clipQueryX11FormatsCallback(PSHCLX11CTX pCtx);
+extern DECLCALLBACK(void) clipConvertX11TargetsCallback(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 tstClipQueueToEventThread(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;
+}
+
+/* Return the data in the simulated VBox clipboard. */
+DECLCALLBACK(int) ShClX11RequestDataForX11Callback(PSHCLCONTEXT pCtx, uint32_t Format, void **ppv, uint32_t *pcb)
+{
+    RT_NOREF(pCtx, Format);
+    *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;
+}
+
+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;
+    unsigned i = 0;
+    while (g_aFormats[i].pcszAtom)
+    {
+        if (!strcmp(pcsz, g_aFormats[i].pcszAtom))
+            atom = (Atom) (i + 0x1000);
+        ++i;
+    }
+    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])
+    {
+        clipConvertDataFromX11CallbackWorker(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;
+    }
+    clipConvertDataFromX11CallbackWorker(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;
+
+DECLCALLBACK(void) ShClX11ReportFormatsCallback(PSHCLCONTEXT pCtx, SHCLFORMATS Formats)
+{
+    RT_NOREF(pCtx);
+    g_tst_uX11Formats = Formats;
+}
+
+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)
+{
+    clipQueryX11FormatsCallback(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;
+        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];
+
+void ShClX11RequestFromX11CompleteCallback(PSHCLCONTEXT pCtx, int rc, CLIPREADCBREQ *pReq, void *pv, uint32_t cb)
+{
+    RT_NOREF(pCtx);
+    if (cb <= TESTCASE_MAX_BUF_SIZE)
+    {
+        g_tst_rcCompleted = rc;
+        if (cb != 0)
+            memcpy(g_tst_abCompletedBuf, pv, cb);
+    }
+    else
+        g_tst_rcCompleted = VERR_BUFFER_OVERFLOW;
+    g_tst_cbCompleted = cb;
+    g_tst_pCompletedReq = pReq;
+}
+
+/**
+ * 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)
+{
+    unsigned i = 0;
+    while (g_aFormats[i].pcszAtom)
+    {
+        if (!strcmp(g_aFormats[i].pcszAtom, pcszAtom))
+            return i;
+        ++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, (%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 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 (rc)
+        return rc;
+    RTTestBanner(hTest);
+
+    /*
+     * Run the tests.
+     */
+    SHCLX11CTX X11Ctx;
+    rc = ShClX11Init(&X11Ctx, NULL, false);
+    AssertRCReturn(rc, 1);
+    char *pc;
+    uint32_t cbActual;
+    CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)&pReq, *pReqRet = NULL;
+    rc = ShClX11ThreadStart(&X11Ctx, false /* fGrab */);
+    AssertRCReturn(rc, 1);
+
+    /* 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();
+    clipReportEmptyX11CB(&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;
+    clipConvertX11TargetsCallback(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);
+
+    rc = ShClX11ThreadStop(&X11Ctx);
+    AssertRCReturn(rc, 1);
+    ShClX11Destroy(&X11Ctx);
+
+    /*
+     * Headless clipboard tests
+     */
+    rc = ShClX11Init(&X11Ctx, NULL, true);
+    AssertRCReturn(rc, 1);
+    rc = ShClX11ThreadStart(&X11Ctx, false /* fGrab */);
+    AssertRCReturn(rc, 1);
+
+    /* 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");
+
+    rc = ShClX11ThreadStop(&X11Ctx);
+    AssertRCReturn(rc, 1);
+
+    ShClX11Destroy(&X11Ctx);
+
+    return RTTestSummaryAndDestroy(hTest);
+}
+
Index: /trunk/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11Smoke.cpp
===================================================================
--- /trunk/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11Smoke.cpp	(revision 85296)
+++ /trunk/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11Smoke.cpp	(revision 85296)
@@ -0,0 +1,83 @@
+/* $Id$ */
+/** @file
+ * Shared Clipboard guest/host X11 code smoke tests.
+ */
+
+/*
+ * Copyright (C) 2011-2020 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.
+ */
+
+/* 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/assert.h>
+#include <iprt/env.h>
+#include <iprt/err.h>
+#include <iprt/test.h>
+
+#include <VBox/GuestHost/SharedClipboard.h>
+#include <VBox/GuestHost/SharedClipboard-x11.h>
+#include <VBox/GuestHost/clipboard-helper.h>
+
+DECLCALLBACK(int) ShClX11RequestDataForX11Callback(PSHCLCONTEXT pCtx, SHCLFORMAT Format, void **ppv, uint32_t *pcb)
+{
+    RT_NOREF(pCtx, Format, ppv, pcb);
+    return VERR_NO_DATA;
+}
+
+DECLCALLBACK(void) ShClX11ReportFormatsCallback(PSHCLCONTEXT pCtx, SHCLFORMATS Formats)
+{
+    RT_NOREF(pCtx, Formats);
+}
+
+DECLCALLBACK(void) ShClX11RequestFromX11CompleteCallback(PSHCLCONTEXT pCtx, int rc, CLIPREADCBREQ *pReq, void *pv, uint32_t cb)
+{
+    RT_NOREF(pCtx, rc, pReq, pv, cb);
+}
+
+int main()
+{
+    /*
+     * Init the runtime, test and say hello.
+     */
+    RTTEST hTest;
+    int rc = RTTestInitAndCreate("tstClipboardGH-X11Smoke", &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);
+    }
+    SHCLX11CTX X11Ctx;
+    rc = ShClX11Init(&X11Ctx, NULL, false);
+    AssertRCReturn(rc, 1);
+    rc = ShClX11ThreadStart(&X11Ctx, false /* fGrab */);
+    AssertRCReturn(rc, 1);
+    /* Give the clipboard time to synchronise. */
+    RTThreadSleep(500);
+    rc = ShClX11ThreadStop(&X11Ctx);
+    AssertRCReturn(rc, 1);
+    ShClX11Destroy(&X11Ctx);
+    return RTTestSummaryAndDestroy(hTest);
+}
+
