summaryrefslogtreecommitdiffstats
path: root/src/VBox/HostServices/SharedClipboard/x11-clipboard.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/VBox/HostServices/SharedClipboard/x11-clipboard.cpp615
1 files changed, 615 insertions, 0 deletions
diff --git a/src/VBox/HostServices/SharedClipboard/x11-clipboard.cpp b/src/VBox/HostServices/SharedClipboard/x11-clipboard.cpp
new file mode 100644
index 00000000..88339e02
--- /dev/null
+++ b/src/VBox/HostServices/SharedClipboard/x11-clipboard.cpp
@@ -0,0 +1,615 @@
+/* $Id: x11-clipboard.cpp $ */
+/** @file
+ * Shared Clipboard Service - Linux host.
+ */
+
+/*
+ * 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.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD
+#include <iprt/assert.h>
+#include <iprt/critsect.h>
+#include <iprt/env.h>
+#include <iprt/mem.h>
+#include <iprt/semaphore.h>
+#include <iprt/string.h>
+
+#include <VBox/GuestHost/SharedClipboard.h>
+#include <VBox/HostServices/VBoxClipboardSvc.h>
+#include <VBox/err.h>
+
+#include "VBoxClipboard.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+struct _VBOXCLIPBOARDREQFROMVBOX;
+typedef struct _VBOXCLIPBOARDREQFROMVBOX VBOXCLIPBOARDREQFROMVBOX;
+
+/** Global context information used by the host glue for the X11 clipboard
+ * backend */
+struct _VBOXCLIPBOARDCONTEXT
+{
+ /** This mutex is grabbed during any critical operations on the clipboard
+ * which might clash with others. */
+ RTCRITSECT clipboardMutex;
+ /** The currently pending request for data from VBox. NULL if there is
+ * no request pending. The protocol for completing a request is to grab
+ * the critical section, check that @a pReq is not NULL, fill in the data
+ * fields and set @a pReq to NULL. The protocol for cancelling a pending
+ * request is to grab the critical section and set pReq to NULL.
+ * It is an error if a request arrives while another one is pending, and
+ * the backend is responsible for ensuring that this does not happen. */
+ VBOXCLIPBOARDREQFROMVBOX *pReq;
+
+ /** Pointer to the opaque X11 backend structure */
+ CLIPBACKEND *pBackend;
+ /** Pointer to the VBox host client data structure. */
+ VBOXCLIPBOARDCLIENTDATA *pClient;
+ /** We set this when we start shutting down as a hint not to post any new
+ * requests. */
+ bool fShuttingDown;
+};
+
+
+
+/**
+ * Report formats available in the X11 clipboard to VBox.
+ * @param pCtx Opaque context pointer for the glue code
+ * @param u32Formats The formats available
+ * @note Host glue code
+ */
+void ClipReportX11Formats(VBOXCLIPBOARDCONTEXT *pCtx,
+ uint32_t u32Formats)
+{
+ LogRelFlowFunc(("called. pCtx=%p, u32Formats=%02X\n", pCtx, u32Formats));
+ vboxSvcClipboardReportMsg(pCtx->pClient,
+ VBOX_SHARED_CLIPBOARD_HOST_MSG_FORMATS,
+ u32Formats);
+}
+
+/**
+ * Initialise the host side of the shared clipboard.
+ * @note Host glue code
+ */
+int vboxClipboardInit (void)
+{
+ return VINF_SUCCESS;
+}
+
+/**
+ * Terminate the host side of the shared clipboard.
+ * @note host glue code
+ */
+void vboxClipboardDestroy (void)
+{
+
+}
+
+/**
+ * Connect a guest to the shared clipboard.
+ * @note host glue code
+ * @note on the host, we assume that some other application already owns
+ * the clipboard and leave ownership to X11.
+ */
+int vboxClipboardConnect (VBOXCLIPBOARDCLIENTDATA *pClient, bool fHeadless)
+{
+ int rc = VINF_SUCCESS;
+ CLIPBACKEND *pBackend = NULL;
+
+ LogRel(("Starting host clipboard service\n"));
+ VBOXCLIPBOARDCONTEXT *pCtx =
+ (VBOXCLIPBOARDCONTEXT *) RTMemAllocZ(sizeof(VBOXCLIPBOARDCONTEXT));
+ if (!pCtx)
+ rc = VERR_NO_MEMORY;
+ else
+ {
+ RTCritSectInit(&pCtx->clipboardMutex);
+ pBackend = ClipConstructX11(pCtx, fHeadless);
+ if (pBackend == NULL)
+ rc = VERR_NO_MEMORY;
+ else
+ {
+ pCtx->pBackend = pBackend;
+ pClient->pCtx = pCtx;
+ pCtx->pClient = pClient;
+ rc = ClipStartX11(pBackend, true /* grab shared clipboard */);
+ }
+ if (RT_FAILURE(rc))
+ RTCritSectDelete(&pCtx->clipboardMutex);
+ }
+ if (RT_FAILURE(rc))
+ {
+ RTMemFree(pCtx);
+ LogRel(("Failed to initialise the shared clipboard\n"));
+ }
+ LogRelFlowFunc(("returning %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Synchronise the contents of the host clipboard with the guest, called
+ * after a save and restore of the guest.
+ * @note Host glue code
+ */
+int vboxClipboardSync (VBOXCLIPBOARDCLIENTDATA *pClient)
+{
+ /* Tell the guest we have no data in case X11 is not available. If
+ * there is data in the host clipboard it will automatically be sent to
+ * the guest when the clipboard starts up. */
+ vboxSvcClipboardReportMsg (pClient,
+ VBOX_SHARED_CLIPBOARD_HOST_MSG_FORMATS, 0);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Shut down the shared clipboard service and "disconnect" the guest.
+ * @note Host glue code
+ */
+void vboxClipboardDisconnect (VBOXCLIPBOARDCLIENTDATA *pClient)
+{
+ LogRelFlow(("vboxClipboardDisconnect\n"));
+
+ LogRel(("Stopping the host clipboard service\n"));
+ VBOXCLIPBOARDCONTEXT *pCtx = pClient->pCtx;
+ /* Drop the reference to the client, in case it is still there. This
+ * will cause any outstanding clipboard data requests from X11 to fail
+ * immediately. */
+ pCtx->fShuttingDown = true;
+ /* If there is a currently pending request, release it immediately. */
+ vboxClipboardWriteData(pClient, NULL, 0, 0);
+ int rc = ClipStopX11(pCtx->pBackend);
+ /** @todo handle this slightly more reasonably, or be really sure
+ * it won't go wrong. */
+ AssertRC(rc);
+ if (RT_SUCCESS(rc)) /* And if not? */
+ {
+ ClipDestructX11(pCtx->pBackend);
+ RTCritSectDelete(&pCtx->clipboardMutex);
+ RTMemFree(pCtx);
+ }
+}
+
+/**
+ * VBox is taking possession of the shared clipboard.
+ *
+ * @param pClient Context data for the guest system
+ * @param u32Formats Clipboard formats the guest is offering
+ * @note Host glue code
+ */
+void vboxClipboardFormatAnnounce (VBOXCLIPBOARDCLIENTDATA *pClient,
+ uint32_t u32Formats)
+{
+ LogRelFlowFunc(("called. pClient=%p, u32Formats=%02X\n", pClient,
+ u32Formats));
+ ClipAnnounceFormatToX11 (pClient->pCtx->pBackend, u32Formats);
+}
+
+/** Structure describing a request for clipoard data from the guest. */
+struct _CLIPREADCBREQ
+{
+ /** Where to write the returned data to. */
+ void *pv;
+ /** The size of the buffer in pv */
+ uint32_t cb;
+ /** The actual size of the data written */
+ uint32_t *pcbActual;
+};
+
+/**
+ * Called when VBox wants to read the X11 clipboard.
+ *
+ * @returns VINF_SUCCESS on successful completion
+ * @returns VINF_HGCM_ASYNC_EXECUTE if the operation will complete
+ * asynchronously
+ * @returns iprt status code on failure
+ * @param pClient Context information about the guest VM
+ * @param u32Format The format that the guest 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 always fail or complete asynchronously
+ * @note On success allocates a CLIPREADCBREQ structure which must be
+ * freed in ClipCompleteDataRequestFromX11 when it is called back from
+ * the backend code.
+ *
+ */
+int vboxClipboardReadData (VBOXCLIPBOARDCLIENTDATA *pClient,
+ uint32_t u32Format, void *pv, uint32_t cb,
+ uint32_t *pcbActual)
+{
+ LogRelFlowFunc(("pClient=%p, u32Format=%02X, pv=%p, cb=%u, pcbActual=%p",
+ pClient, u32Format, pv, cb, pcbActual));
+
+ int rc = VINF_SUCCESS;
+ CLIPREADCBREQ *pReq = (CLIPREADCBREQ *) RTMemAlloc(sizeof(CLIPREADCBREQ));
+ if (!pReq)
+ rc = VERR_NO_MEMORY;
+ else
+ {
+ pReq->pv = pv;
+ pReq->cb = cb;
+ pReq->pcbActual = pcbActual;
+ rc = ClipRequestDataFromX11(pClient->pCtx->pBackend, u32Format, pReq);
+ if (RT_SUCCESS(rc))
+ rc = VINF_HGCM_ASYNC_EXECUTE;
+ }
+ LogRelFlowFunc(("returning %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Complete a request from VBox for the X11 clipboard data. The data should
+ * be written to the buffer provided in the initial request.
+ * @param pCtx request context information
+ * @param rc the completion status of the request
+ * @param pReq request
+ * @param pv address
+ * @param cb size
+ *
+ * @todo change this to deal with the buffer issues rather than offloading
+ * them onto the caller
+ */
+void ClipCompleteDataRequestFromX11(VBOXCLIPBOARDCONTEXT *pCtx, int rc,
+ CLIPREADCBREQ *pReq, void *pv, uint32_t cb)
+{
+ if (cb <= pReq->cb && cb != 0)
+ memcpy(pReq->pv, pv, cb);
+ RTMemFree(pReq);
+ vboxSvcClipboardCompleteReadData(pCtx->pClient, rc, cb);
+}
+
+/** A request for clipboard data from VBox */
+struct _VBOXCLIPBOARDREQFROMVBOX
+{
+ /** Data received */
+ void *pv;
+ /** The size of the data */
+ uint32_t cb;
+ /** Format of the data */
+ uint32_t format;
+ /** A semaphore for waiting for the data */
+ RTSEMEVENT finished;
+};
+
+/** Wait for clipboard data requested from VBox to arrive. */
+static int clipWaitForDataFromVBox(VBOXCLIPBOARDCONTEXT *pCtx,
+ VBOXCLIPBOARDREQFROMVBOX *pReq,
+ uint32_t u32Format)
+{
+ int rc = VINF_SUCCESS;
+ LogRelFlowFunc(("pCtx=%p, pReq=%p, u32Format=%02X\n", pCtx, pReq, u32Format));
+ /* Request data from VBox */
+ vboxSvcClipboardReportMsg(pCtx->pClient,
+ VBOX_SHARED_CLIPBOARD_HOST_MSG_READ_DATA,
+ u32Format);
+ /* Which will signal us when it is ready. We use a timeout here
+ * because we can't be sure that the guest will behave correctly.
+ */
+ rc = RTSemEventWait(pReq->finished, CLIPBOARD_TIMEOUT);
+ /* If the request hasn't yet completed then we cancel it. We use
+ * the critical section to prevent these operations colliding. */
+ RTCritSectEnter(&pCtx->clipboardMutex);
+ /* The data may have arrived between the semaphore timing out and
+ * our grabbing the mutex. */
+ if (rc == VERR_TIMEOUT && pReq->pv != NULL)
+ rc = VINF_SUCCESS;
+ if (pCtx->pReq == pReq)
+ pCtx->pReq = NULL;
+ Assert(pCtx->pReq == NULL);
+ RTCritSectLeave(&pCtx->clipboardMutex);
+ if (RT_SUCCESS(rc) && (pReq->pv == NULL))
+ rc = VERR_NO_DATA;
+ LogRelFlowFunc(("returning %Rrc\n", rc));
+ return rc;
+}
+
+/** Post a request for clipboard data to VBox/the guest and wait for it to be
+ * completed. */
+static int clipRequestDataFromVBox(VBOXCLIPBOARDCONTEXT *pCtx,
+ VBOXCLIPBOARDREQFROMVBOX *pReq,
+ uint32_t u32Format)
+{
+ int rc = VINF_SUCCESS;
+ LogRelFlowFunc(("pCtx=%p, pReq=%p, u32Format=%02X\n", pCtx, pReq,
+ u32Format));
+ /* Start by "posting" the request for the next invocation of
+ * vboxClipboardWriteData. */
+ RTCritSectEnter(&pCtx->clipboardMutex);
+ if (pCtx->pReq != NULL)
+ {
+ /* This would be a violation of the protocol, see the comments in the
+ * context structure definition. */
+ Assert(false);
+ rc = VERR_WRONG_ORDER;
+ }
+ else
+ pCtx->pReq = pReq;
+ RTCritSectLeave(&pCtx->clipboardMutex);
+ if (RT_SUCCESS(rc))
+ rc = clipWaitForDataFromVBox(pCtx, pReq, u32Format);
+ LogRelFlowFunc(("returning %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Send a request to VBox to transfer the contents of its clipboard to X11.
+ *
+ * @param pCtx Pointer to the host clipboard structure
+ * @param u32Format The format in which the data should be transferred
+ * @param ppv On success and if pcb > 0, this will point to a buffer
+ * to be freed with RTMemFree containing the data read.
+ * @param pcb On success, this contains the number of bytes of data
+ * returned
+ * @note Host glue code.
+ */
+int ClipRequestDataForX11 (VBOXCLIPBOARDCONTEXT *pCtx,
+ uint32_t u32Format, void **ppv,
+ uint32_t *pcb)
+{
+ VBOXCLIPBOARDREQFROMVBOX request = { NULL, 0, 0, NIL_RTSEMEVENT };
+
+ LogRelFlowFunc(("pCtx=%p, u32Format=%02X, ppv=%p, pcb=%p\n", pCtx,
+ u32Format, ppv, pcb));
+ if (pCtx->fShuttingDown)
+ {
+ /* The shared clipboard is disconnecting. */
+ LogRelFunc(("host requested guest clipboard data after guest had disconnected.\n"));
+ return VERR_WRONG_ORDER;
+ }
+ int rc = RTSemEventCreate(&request.finished);
+ if (RT_SUCCESS(rc))
+ {
+ rc = clipRequestDataFromVBox(pCtx, &request, u32Format);
+ RTSemEventDestroy(request.finished);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ *ppv = request.pv;
+ *pcb = request.cb;
+ }
+ LogRelFlowFunc(("returning %Rrc\n", rc));
+ if (RT_SUCCESS(rc))
+ LogRelFlowFunc(("*ppv=%.*ls, *pcb=%u\n", *pcb / 2, *ppv, *pcb));
+ return rc;
+}
+
+/**
+ * Called when we have requested data from VBox and that data has arrived.
+ *
+ * @param pClient Context information about the guest VM
+ * @param pv Buffer to which the data was written
+ * @param cb The size of the data written
+ * @param u32Format The format of the data written
+ * @note Host glue code
+ */
+void vboxClipboardWriteData (VBOXCLIPBOARDCLIENTDATA *pClient,
+ void *pv, uint32_t cb, uint32_t u32Format)
+{
+ LogRelFlowFunc (("called. pClient=%p, pv=%p (%.*ls), cb=%u, u32Format=%02X\n",
+ pClient, pv, cb / 2, pv, cb, u32Format));
+
+ VBOXCLIPBOARDCONTEXT *pCtx = pClient->pCtx;
+ /* Grab the mutex and check whether there is a pending request for data.
+ */
+ RTCritSectEnter(&pCtx->clipboardMutex);
+ VBOXCLIPBOARDREQFROMVBOX *pReq = pCtx->pReq;
+ if (pReq != NULL)
+ {
+ if (cb > 0)
+ {
+ pReq->pv = RTMemDup(pv, cb);
+ if (pReq->pv != NULL) /* NULL may also mean no memory... */
+ {
+ pReq->cb = cb;
+ pReq->format = u32Format;
+ }
+ }
+ /* Signal that the request has been completed. */
+ RTSemEventSignal(pReq->finished);
+ pCtx->pReq = NULL;
+ }
+ RTCritSectLeave(&pCtx->clipboardMutex);
+}
+
+#ifdef TESTCASE
+#include <iprt/initterm.h>
+#include <iprt/stream.h>
+
+#define TEST_NAME "tstClipboardX11-2"
+
+struct _CLIPBACKEND
+{
+ uint32_t formats;
+ struct _READDATA
+ {
+ uint32_t format;
+ int rc;
+ CLIPREADCBREQ *pReq;
+ } readData;
+ struct _COMPLETEREAD
+ {
+ int rc;
+ uint32_t cbActual;
+ } completeRead;
+ struct _WRITEDATA
+ {
+ void *pv;
+ uint32_t cb;
+ uint32_t format;
+ bool timeout;
+ } writeData;
+ struct _REPORTDATA
+ {
+ uint32_t format;
+ } reportData;
+};
+
+void vboxSvcClipboardReportMsg (VBOXCLIPBOARDCLIENTDATA *pClient, uint32_t u32Msg, uint32_t u32Formats)
+{
+ RT_NOREF1(u32Formats);
+ CLIPBACKEND *pBackend = pClient->pCtx->pBackend;
+ if ( (u32Msg == VBOX_SHARED_CLIPBOARD_HOST_MSG_READ_DATA)
+ && !pBackend->writeData.timeout)
+ vboxClipboardWriteData(pClient, pBackend->writeData.pv,
+ pBackend->writeData.cb,
+ pBackend->writeData.format);
+ else
+ return;
+}
+
+void vboxSvcClipboardCompleteReadData(VBOXCLIPBOARDCLIENTDATA *pClient, int rc, uint32_t cbActual)
+{
+ CLIPBACKEND *pBackend = pClient->pCtx->pBackend;
+ pBackend->completeRead.rc = rc;
+ pBackend->completeRead.cbActual = cbActual;
+}
+
+CLIPBACKEND *ClipConstructX11(VBOXCLIPBOARDCONTEXT *pFrontend, bool)
+{
+ RT_NOREF1(pFrontend);
+ return (CLIPBACKEND *)RTMemAllocZ(sizeof(CLIPBACKEND));
+}
+
+void ClipDestructX11(CLIPBACKEND *pBackend)
+{
+ RTMemFree(pBackend);
+}
+
+int ClipStartX11(CLIPBACKEND *pBackend, bool)
+{
+ RT_NOREF1(pBackend);
+ return VINF_SUCCESS;
+}
+
+int ClipStopX11(CLIPBACKEND *pBackend)
+{
+ RT_NOREF1(pBackend);
+ return VINF_SUCCESS;
+}
+
+void ClipAnnounceFormatToX11(CLIPBACKEND *pBackend,
+ uint32_t u32Formats)
+{
+ pBackend->formats = u32Formats;
+}
+
+extern int ClipRequestDataFromX11(CLIPBACKEND *pBackend, uint32_t u32Format,
+ CLIPREADCBREQ *pReq)
+{
+ pBackend->readData.format = u32Format;
+ pBackend->readData.pReq = pReq;
+ return pBackend->readData.rc;
+}
+
+int main()
+{
+ VBOXCLIPBOARDCLIENTDATA client;
+ unsigned cErrors = 0;
+ int rc = RTR3InitExeNoArguments(0);
+ RTPrintf(TEST_NAME ": TESTING\n");
+ AssertRCReturn(rc, 1);
+ rc = vboxClipboardConnect(&client, false);
+ CLIPBACKEND *pBackend = client.pCtx->pBackend;
+ AssertRCReturn(rc, 1);
+ vboxClipboardFormatAnnounce(&client,
+ VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT);
+ if (pBackend->formats != VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT)
+ {
+ RTPrintf(TEST_NAME ": vboxClipboardFormatAnnounce failed with VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT\n");
+ ++cErrors;
+ }
+ pBackend->readData.rc = VINF_SUCCESS;
+ client.asyncRead.callHandle = (VBOXHGCMCALLHANDLE)pBackend;
+ client.asyncRead.paParms = (VBOXHGCMSVCPARM *)&client;
+ uint32_t u32Dummy;
+ rc = vboxClipboardReadData(&client, VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT,
+ &u32Dummy, 42, &u32Dummy);
+ if (rc != VINF_HGCM_ASYNC_EXECUTE)
+ {
+ RTPrintf(TEST_NAME ": vboxClipboardReadData returned %Rrc\n", rc);
+ ++cErrors;
+ }
+ else
+ {
+ if ( pBackend->readData.format !=
+ VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT
+ || pBackend->readData.pReq->pv != &u32Dummy
+ || pBackend->readData.pReq->cb != 42
+ || pBackend->readData.pReq->pcbActual != &u32Dummy)
+ {
+ RTPrintf(TEST_NAME ": format=%u, pReq->pv=%p, pReq->cb=%u, pReq->pcbActual=%p\n",
+ pBackend->readData.format, pBackend->readData.pReq->pv,
+ pBackend->readData.pReq->cb,
+ pBackend->readData.pReq->pcbActual);
+ ++cErrors;
+ }
+ else
+ {
+ ClipCompleteDataRequestFromX11(client.pCtx, VERR_NO_DATA,
+ pBackend->readData.pReq, NULL, 43);
+ if ( pBackend->completeRead.rc != VERR_NO_DATA
+ || pBackend->completeRead.cbActual != 43)
+ {
+ RTPrintf(TEST_NAME ": rc=%Rrc, cbActual=%u\n",
+ pBackend->completeRead.rc,
+ pBackend->completeRead.cbActual);
+ ++cErrors;
+ }
+ }
+ }
+ void *pv;
+ uint32_t cb;
+ pBackend->writeData.pv = (void *)"testing";
+ pBackend->writeData.cb = sizeof("testing");
+ pBackend->writeData.format = 1234;
+ pBackend->reportData.format = 4321; /* XX this should be handled! */
+ rc = ClipRequestDataForX11(client.pCtx, 23, &pv, &cb);
+ if ( rc != VINF_SUCCESS
+ || strcmp((const char *)pv, "testing") != 0
+ || cb != sizeof("testing"))
+ {
+ RTPrintf("rc=%Rrc, pv=%p, cb=%u\n", rc, pv, cb);
+ ++cErrors;
+ }
+ else
+ RTMemFree(pv);
+ pBackend->writeData.timeout = true;
+ rc = ClipRequestDataForX11(client.pCtx, 23, &pv, &cb);
+ if (rc != VERR_TIMEOUT)
+ {
+ RTPrintf("rc=%Rrc, expected VERR_TIMEOUT\n", rc);
+ ++cErrors;
+ }
+ pBackend->writeData.pv = NULL;
+ pBackend->writeData.cb = 0;
+ pBackend->writeData.timeout = false;
+ rc = ClipRequestDataForX11(client.pCtx, 23, &pv, &cb);
+ if (rc != VERR_NO_DATA)
+ {
+ RTPrintf("rc=%Rrc, expected VERR_NO_DATA\n", rc);
+ ++cErrors;
+ }
+ /* Data arriving after a timeout should *not* cause any segfaults or
+ * memory leaks. Check with Valgrind! */
+ vboxClipboardWriteData(&client, (void *)"tested", sizeof("tested"), 999);
+ vboxClipboardDisconnect(&client);
+ if (cErrors > 0)
+ RTPrintf(TEST_NAME ": errors: %u\n", cErrors);
+ return cErrors > 0 ? 1 : 0;
+}
+#endif /* TESTCASE */