summaryrefslogtreecommitdiffstats
path: root/src/VBox/Additions/x11/VBoxClient/clipboard.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Additions/x11/VBoxClient/clipboard.cpp')
-rw-r--r--src/VBox/Additions/x11/VBoxClient/clipboard.cpp536
1 files changed, 536 insertions, 0 deletions
diff --git a/src/VBox/Additions/x11/VBoxClient/clipboard.cpp b/src/VBox/Additions/x11/VBoxClient/clipboard.cpp
new file mode 100644
index 00000000..578c0b0b
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/clipboard.cpp
@@ -0,0 +1,536 @@
+/** $Id: clipboard.cpp $ */
+/** @file
+ * Guest Additions - X11 Shared Clipboard.
+ */
+
+/*
+ * Copyright (C) 2007-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.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/alloc.h>
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_FUSE
+# include <iprt/dir.h>
+#endif
+#include <iprt/initterm.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/path.h>
+#include <iprt/process.h>
+#include <iprt/semaphore.h>
+
+#include <VBox/log.h>
+#include <VBox/VBoxGuestLib.h>
+#include <VBox/HostServices/VBoxClipboardSvc.h>
+#include <VBox/GuestHost/SharedClipboard.h>
+#include <VBox/GuestHost/SharedClipboard-x11.h>
+
+#include "VBoxClient.h"
+
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_FUSE
+# include "clipboard-fuse.h"
+#endif
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_FUSE
+typedef struct _SHCLCTXFUSE
+{
+ RTTHREAD Thread;
+} SHCLCTXFUSE;
+#endif /* VBOX_WITH_SHARED_CLIPBOARD_FUSE */
+
+/**
+ * Global clipboard context information.
+ */
+struct SHCLCONTEXT
+{
+ /** Client command context */
+ VBGLR3SHCLCMDCTX CmdCtx;
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+ /** Associated transfer data. */
+ SHCLTRANSFERCTX TransferCtx;
+# ifdef VBOX_WITH_SHARED_CLIPBOARD_FUSE
+ SHCLCTXFUSE FUSE;
+# endif
+#endif
+ /** X11 clipboard context. */
+ SHCLX11CTX X11;
+};
+
+/** Only one client is supported. There seems to be no need for more clients. */
+static SHCLCONTEXT g_Ctx;
+
+
+/**
+ * Callback implementation for getting clipboard data from the host.
+ *
+ * @returns VBox status code. VERR_NO_DATA if no data available.
+ * @param pCtx Our context information.
+ * @param Format The format of the data being requested.
+ * @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.
+ */
+DECLCALLBACK(int) ShClX11RequestDataForX11Callback(PSHCLCONTEXT pCtx, SHCLFORMAT Format, void **ppv, uint32_t *pcb)
+{
+ RT_NOREF(pCtx);
+
+ LogFlowFunc(("Format=0x%x\n", Format));
+
+ int rc = VINF_SUCCESS;
+
+ uint32_t cbRead = 0;
+
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+ if (Format == VBOX_SHCL_FMT_URI_LIST)
+ {
+ //rc = VbglR3ClipboardRootListRead()
+ }
+ else
+#endif
+ {
+ uint32_t cbData = _4K; /** @todo Make this dynamic. */
+ void *pvData = RTMemAlloc(cbData);
+ if (pvData)
+ {
+ rc = VbglR3ClipboardReadDataEx(&pCtx->CmdCtx, Format, pvData, cbData, &cbRead);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ /*
+ * A return value of VINF_BUFFER_OVERFLOW tells us to try again with a
+ * larger buffer. The size of the buffer needed is placed in *pcb.
+ * So we start all over again.
+ */
+ if (rc == VINF_BUFFER_OVERFLOW)
+ {
+ /* cbRead contains the size required. */
+
+ cbData = cbRead;
+ pvData = RTMemRealloc(pvData, cbRead);
+ if (pvData)
+ {
+ rc = VbglR3ClipboardReadDataEx(&pCtx->CmdCtx, Format, pvData, cbData, &cbRead);
+ if (rc == VINF_BUFFER_OVERFLOW)
+ rc = VERR_BUFFER_OVERFLOW;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ if (!cbRead)
+ rc = VERR_NO_DATA;
+
+ if (RT_SUCCESS(rc))
+ {
+ *pcb = cbRead; /* Actual bytes read. */
+ *ppv = pvData;
+ }
+ else
+ {
+ /*
+ * Catch other errors. This also catches the case in which the buffer was
+ * too small a second time, possibly because the clipboard contents
+ * changed half-way through the operation. Since we can't say whether or
+ * not this is actually an error, we just return size 0.
+ */
+ RTMemFree(pvData);
+ }
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Opaque data structure describing a request from the host for clipboard
+ * data, passed in when the request is forwarded to the X11 backend so that
+ * it can be completed correctly.
+ */
+struct CLIPREADCBREQ
+{
+ /** The data format that was requested. */
+ SHCLFORMAT Format;
+};
+
+/**
+ * Tell the host that new clipboard formats are available.
+ *
+ * @param pCtx Our context information.
+ * @param fFormats The formats to report.
+ */
+DECLCALLBACK(void) ShClX11ReportFormatsCallback(PSHCLCONTEXT pCtx, SHCLFORMATS fFormats)
+{
+ RT_NOREF(pCtx);
+
+ LogFlowFunc(("Formats=0x%x\n", fFormats));
+
+ int rc2 = VbglR3ClipboardReportFormats(pCtx->CmdCtx.idClient, fFormats);
+ RT_NOREF(rc2);
+ LogFlowFuncLeaveRC(rc2);
+}
+
+/**
+ * This is called by the backend to tell us that a request for data from
+ * X11 has completed.
+ *
+ * @param pCtx Our context information.
+ * @param rcCompletion The completion status of the request.
+ * @param pReq The request structure that we passed in when we started
+ * the request. We RTMemFree() this in this function.
+ * @param pv The clipboard data returned from X11 if the request succeeded (see @a rc).
+ * @param cb The size of the data in @a pv.
+ */
+DECLCALLBACK(void) ShClX11RequestFromX11CompleteCallback(PSHCLCONTEXT pCtx,
+ int rcCompletion, CLIPREADCBREQ *pReq, void *pv, uint32_t cb)
+{
+ LogFlowFunc(("rcCompletion=%Rrc, Format=0x%x, pv=%p, cb=%RU32\n", rcCompletion, pReq->Format, pv, cb));
+
+ if (RT_SUCCESS(rcCompletion)) /* Only write data if the request succeeded. */
+ {
+ AssertPtrReturnVoid(pv);
+ AssertReturnVoid(pv);
+
+ rcCompletion = VbglR3ClipboardWriteDataEx(&pCtx->CmdCtx, pReq->Format, pv, cb);
+ }
+
+ RTMemFree(pReq);
+
+ LogFlowFuncLeaveRC(rcCompletion);
+}
+
+/**
+ * Connect the guest clipboard to the host.
+ *
+ * @returns VBox status code.
+ */
+static int vboxClipboardConnect(void)
+{
+ LogFlowFuncEnter();
+
+ int rc = ShClX11Init(&g_Ctx.X11, &g_Ctx, false /* fHeadless */);
+ if (RT_SUCCESS(rc))
+ {
+ rc = ShClX11ThreadStart(&g_Ctx.X11, false /* grab */);
+ if (RT_SUCCESS(rc))
+ {
+ rc = VbglR3ClipboardConnectEx(&g_Ctx.CmdCtx, VBOX_SHCL_GF_0_CONTEXT_ID);
+ if (RT_FAILURE(rc))
+ ShClX11ThreadStop(&g_Ctx.X11);
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ if (RT_FAILURE(rc))
+ {
+ VBClLogError("Error connecting to host service, rc=%Rrc\n", rc);
+
+ VbglR3ClipboardDisconnectEx(&g_Ctx.CmdCtx);
+ ShClX11Destroy(&g_Ctx.X11);
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * The main loop of our clipboard reader.
+ */
+int vboxClipboardMain(void)
+{
+ LogRel(("Worker loop running\n"));
+
+ int rc;
+
+ PSHCLCONTEXT pCtx = &g_Ctx;
+
+ bool fShutdown = false;
+
+ /* The thread waits for incoming messages from the host. */
+ for (;;)
+ {
+ PVBGLR3CLIPBOARDEVENT pEvent = (PVBGLR3CLIPBOARDEVENT)RTMemAllocZ(sizeof(VBGLR3CLIPBOARDEVENT));
+ AssertPtrBreakStmt(pEvent, rc = VERR_NO_MEMORY);
+
+ LogFlowFunc(("Waiting for host message (fUseLegacyProtocol=%RTbool, fHostFeatures=%#RX64) ...\n",
+ pCtx->CmdCtx.fUseLegacyProtocol, pCtx->CmdCtx.fHostFeatures));
+
+ uint32_t idMsg = 0;
+ uint32_t cParms = 0;
+ rc = VbglR3ClipboardMsgPeekWait(&pCtx->CmdCtx, &idMsg, &cParms, NULL /* pidRestoreCheck */);
+ if (RT_SUCCESS(rc))
+ {
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+ rc = VbglR3ClipboardEventGetNextEx(idMsg, cParms, &pCtx->CmdCtx, &pCtx->TransferCtx, pEvent);
+#else
+ rc = VbglR3ClipboardEventGetNext(idMsg, cParms, &pCtx->CmdCtx, pEvent);
+#endif
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ LogFlowFunc(("Getting next event failed with %Rrc\n", rc));
+
+ VbglR3ClipboardEventFree(pEvent);
+ pEvent = NULL;
+
+ if (fShutdown)
+ break;
+
+ /* Wait a bit before retrying. */
+ RTThreadSleep(1000);
+ continue;
+ }
+ else
+ {
+ AssertPtr(pEvent);
+ LogFlowFunc(("Event uType=%RU32\n", pEvent->enmType));
+
+ switch (pEvent->enmType)
+ {
+ case VBGLR3CLIPBOARDEVENTTYPE_REPORT_FORMATS:
+ {
+ ShClX11ReportFormatsToX11(&g_Ctx.X11, pEvent->u.fReportedFormats);
+ break;
+ }
+
+ case VBGLR3CLIPBOARDEVENTTYPE_READ_DATA:
+ {
+ /* The host needs data in the specified format. */
+ CLIPREADCBREQ *pReq;
+ pReq = (CLIPREADCBREQ *)RTMemAllocZ(sizeof(CLIPREADCBREQ));
+ if (pReq)
+ {
+ pReq->Format = pEvent->u.fReadData;
+ ShClX11ReadDataFromX11(&g_Ctx.X11, pReq->Format, pReq);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ case VBGLR3CLIPBOARDEVENTTYPE_QUIT:
+ {
+ LogRel2(("Host requested termination\n"));
+ fShutdown = true;
+ break;
+ }
+
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+ case VBGLR3CLIPBOARDEVENTTYPE_TRANSFER_STATUS:
+ {
+ /* Nothing to do here. */
+ rc = VINF_SUCCESS;
+ break;
+ }
+#endif
+ case VBGLR3CLIPBOARDEVENTTYPE_NONE:
+ {
+ /* Nothing to do here. */
+ rc = VINF_SUCCESS;
+ break;
+ }
+
+ default:
+ {
+ AssertMsgFailedBreakStmt(("Event type %RU32 not implemented\n", pEvent->enmType), rc = VERR_NOT_SUPPORTED);
+ }
+ }
+
+ if (pEvent)
+ {
+ VbglR3ClipboardEventFree(pEvent);
+ pEvent = NULL;
+ }
+ }
+
+ if (fShutdown)
+ break;
+ }
+
+ LogRel(("Worker loop ended\n"));
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_FUSE
+static DECLCALLBACK(int) vboxClipoardFUSEThread(RTTHREAD hThreadSelf, void *pvUser)
+{
+ RT_NOREF(hThreadSelf, pvUser);
+
+ VbglR3Init();
+
+ LogFlowFuncEnter();
+
+ RTThreadUserSignal(hThreadSelf);
+
+ SHCL_FUSE_OPTS Opts;
+ RT_ZERO(Opts);
+
+ Opts.fForeground = true;
+ Opts.fSingleThreaded = false; /** @todo Do we want multithread here? */
+
+ int rc = RTPathTemp(Opts.szMountPoint, sizeof(Opts.szMountPoint));
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTPathAppend(Opts.szMountPoint, sizeof(Opts.szMountPoint), "VBoxSharedClipboard");
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTDirCreate(Opts.szMountPoint, 0700,
+ RTDIRCREATE_FLAGS_NO_SYMLINKS);
+ if (rc == VERR_ALREADY_EXISTS)
+ rc = VINF_SUCCESS;
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = ShClFuseMain(&Opts);
+ }
+ else
+ LogRel(("Error creating FUSE mount directory, rc=%Rrc\n", rc));
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+static int vboxClipboardFUSEStart()
+{
+ LogFlowFuncEnter();
+
+ PSHCLCONTEXT pCtx = &g_Ctx;
+
+ int rc = RTThreadCreate(&pCtx->FUSE.Thread, vboxClipoardFUSEThread, &pCtx->FUSE, 0,
+ RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "SHCLFUSE");
+ if (RT_SUCCESS(rc))
+ rc = RTThreadUserWait(pCtx->FUSE.Thread, 30 * 1000);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+static int vboxClipboardFUSEStop()
+{
+ LogFlowFuncEnter();
+
+ PSHCLCONTEXT pCtx = &g_Ctx;
+
+ int rcThread;
+ int rc = RTThreadWait(pCtx->FUSE.Thread, 1000, &rcThread);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+#endif /* VBOX_WITH_SHARED_CLIPBOARD_FUSE */
+
+static const char *getName()
+{
+ return "Shared Clipboard";
+}
+
+static const char *getPidFilePath()
+{
+ return ".vboxclient-clipboard.pid";
+}
+
+static int init(struct VBCLSERVICE **pSelf)
+{
+ RT_NOREF(pSelf);
+
+ int rc;
+
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+ rc = ShClTransferCtxInit(&g_Ctx.TransferCtx);
+#else
+ rc = VINF_SUCCESS;
+#endif
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+static int run(struct VBCLSERVICE **ppInterface, bool fDaemonised)
+{
+ RT_NOREF(ppInterface, fDaemonised);
+
+ /* Initialise the guest library. */
+ int rc = vboxClipboardConnect();
+ if (RT_SUCCESS(rc))
+ {
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_FUSE
+ rc = vboxClipboardFUSEStart();
+ if (RT_SUCCESS(rc))
+ {
+#endif
+ rc = vboxClipboardMain();
+
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_FUSE
+ int rc2 = vboxClipboardFUSEStop();
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+#endif
+ }
+
+ if (RT_FAILURE(rc))
+ VBClLogError("Service terminated abnormally with %Rrc\n", rc);
+
+ if (rc == VERR_HGCM_SERVICE_NOT_FOUND)
+ rc = VINF_SUCCESS; /* Prevent automatic restart by daemon script if host service not available. */
+
+ return rc;
+}
+
+static void cleanup(struct VBCLSERVICE **ppInterface)
+{
+ RT_NOREF(ppInterface);
+
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+ ShClTransferCtxDestroy(&g_Ctx.TransferCtx);
+#endif
+}
+
+struct VBCLSERVICE vbclClipboardInterface =
+{
+ getName,
+ getPidFilePath,
+ init,
+ run,
+ cleanup
+};
+
+struct CLIPBOARDSERVICE
+{
+ struct VBCLSERVICE *pInterface;
+};
+
+struct VBCLSERVICE **VBClGetClipboardService(void)
+{
+ struct CLIPBOARDSERVICE *pService =
+ (struct CLIPBOARDSERVICE *)RTMemAlloc(sizeof(*pService));
+
+ if (!pService)
+ VBClLogFatalError("Out of memory\n");
+ pService->pInterface = &vbclClipboardInterface;
+ return &pService->pInterface;
+}