diff options
Diffstat (limited to 'src/VBox/Additions/x11/VBoxClient/clipboard.cpp')
-rw-r--r-- | src/VBox/Additions/x11/VBoxClient/clipboard.cpp | 536 |
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; +} |