diff options
Diffstat (limited to 'src/VBox/Additions/x11/VBoxClient/clipboard.cpp')
-rw-r--r-- | src/VBox/Additions/x11/VBoxClient/clipboard.cpp | 440 |
1 files changed, 440 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..13b1fbc2 --- /dev/null +++ b/src/VBox/Additions/x11/VBoxClient/clipboard.cpp @@ -0,0 +1,440 @@ +/** $Id: clipboard.cpp $ */ +/** @file + * Guest Additions - X11 Shared Clipboard. + */ + +/* + * Copyright (C) 2007-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 + */ + + +/********************************************************************************************************************************* +* 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" + +#include "clipboard.h" +#ifdef VBOX_WITH_SHARED_CLIPBOARD_FUSE +# include "clipboard-fuse.h" +#endif + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ + +/** Only one context is supported at a time for now. */ +SHCLCONTEXT g_Ctx; +#ifdef VBOX_WITH_SHARED_CLIPBOARD_FUSE +SHCLFUSECTX g_FuseCtx; +#endif + + +static DECLCALLBACK(int) vbclOnRequestDataFromSourceCallback(PSHCLCONTEXT pCtx, + SHCLFORMAT uFmt, void **ppv, uint32_t *pcb, void *pvUser) +{ + RT_NOREF(pvUser); + + LogFlowFunc(("pCtx=%p, uFmt=%#x\n", pCtx, uFmt)); + + int rc = VINF_SUCCESS; + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + if (uFmt == VBOX_SHCL_FMT_URI_LIST) + { + //rc = VbglR3ClipboardRootListRead() + rc = VERR_NO_DATA; + } + else +#endif + { + uint32_t cbRead = 0; + + uint32_t cbData = _4K; /** @todo Make this dynamic. */ + void *pvData = RTMemAlloc(cbData); + if (pvData) + { + rc = VbglR3ClipboardReadDataEx(&pCtx->CmdCtx, uFmt, 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, uFmt, 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); + } + } + + if (RT_FAILURE(rc)) + LogRel(("Requesting data in format %#x from host failed with %Rrc\n", uFmt, rc)); + + 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 uFmt; +}; + +static DECLCALLBACK(int) vbclReportFormatsCallback(PSHCLCONTEXT pCtx, uint32_t fFormats, void *pvUser) +{ + RT_NOREF(pvUser); + + LogFlowFunc(("fFormats=%#x\n", fFormats)); + + int rc = VbglR3ClipboardReportFormats(pCtx->CmdCtx.idClient, fFormats); + LogFlowFuncLeaveRC(rc); + + return rc; +} + +static DECLCALLBACK(int) vbclOnSendDataToDestCallback(PSHCLCONTEXT pCtx, void *pv, uint32_t cb, void *pvUser) +{ + PSHCLX11READDATAREQ pData = (PSHCLX11READDATAREQ)pvUser; + AssertPtrReturn(pData, VERR_INVALID_POINTER); + + LogFlowFunc(("rcCompletion=%Rrc, Format=0x%x, pv=%p, cb=%RU32\n", pData->rcCompletion, pData->pReq->uFmt, pv, cb)); + + Assert((cb == 0 && pv == NULL) || (cb != 0 && pv != NULL)); + pData->rcCompletion = VbglR3ClipboardWriteDataEx(&pCtx->CmdCtx, pData->pReq->uFmt, pv, cb); + + RTMemFree(pData->pReq); + + LogFlowFuncLeaveRC(pData->rcCompletion); + + return VINF_SUCCESS; +} + +/** + * Connect the guest clipboard to the host. + * + * @returns VBox status code. + */ +static int vboxClipboardConnect(void) +{ + LogFlowFuncEnter(); + + SHCLCALLBACKS Callbacks; + RT_ZERO(Callbacks); + Callbacks.pfnReportFormats = vbclReportFormatsCallback; + Callbacks.pfnOnRequestDataFromSource = vbclOnRequestDataFromSourceCallback; + Callbacks.pfnOnSendDataToDest = vbclOnSendDataToDestCallback; + + int rc = ShClX11Init(&g_Ctx.X11, &Callbacks, &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) +{ + 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->uFmt = pEvent->u.fReadData; + ShClX11ReadDataFromX11(&g_Ctx.X11, pReq->uFmt, pReq); + } + else + rc = VERR_NO_MEMORY; + break; + } + + case VBGLR3CLIPBOARDEVENTTYPE_QUIT: + { + VBClLogVerbose(2, "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; + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * @interface_method_impl{VBCLSERVICE,pfnInit} + */ +static DECLCALLBACK(int) vbclShClInit(void) +{ + int rc; + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + rc = ShClTransferCtxInit(&g_Ctx.TransferCtx); +#else + rc = VINF_SUCCESS; +#endif + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * @interface_method_impl{VBCLSERVICE,pfnWorker} + */ +static DECLCALLBACK(int) vbclShClWorker(bool volatile *pfShutdown) +{ + RT_NOREF(pfShutdown); + + /* Initialise the guest library. */ + int rc = vboxClipboardConnect(); + if (RT_SUCCESS(rc)) + { +#ifdef VBOX_WITH_SHARED_CLIPBOARD_FUSE + rc = VbClShClFUSEInit(&g_FuseCtx, &g_Ctx); + if (RT_SUCCESS(rc)) + { + rc = VbClShClFUSEStart(&g_FuseCtx); + if (RT_SUCCESS(rc)) + { +#endif + /* Let the main thread know that it can continue spawning services. */ + RTThreadUserSignal(RTThreadSelf()); + + rc = vboxClipboardMain(); + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_FUSE + int rc2 = VbClShClFUSEStop(&g_FuseCtx); + 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; +} + +/** + * @interface_method_impl{VBCLSERVICE,pfnStop} + */ +static DECLCALLBACK(void) vbclShClStop(void) +{ + /* Disconnect from the host service. + * This will also send a VBOX_SHCL_HOST_MSG_QUIT from the host so that we can break out from our message worker. */ + VbglR3ClipboardDisconnect(g_Ctx.CmdCtx.idClient); + g_Ctx.CmdCtx.idClient = 0; +} + +/** + * @interface_method_impl{VBCLSERVICE,pfnTerm} + */ +static DECLCALLBACK(int) vbclShClTerm(void) +{ +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + ShClTransferCtxDestroy(&g_Ctx.TransferCtx); +#endif + + return VINF_SUCCESS; +} + +VBCLSERVICE g_SvcClipboard = +{ + "shcl", /* szName */ + "Shared Clipboard", /* pszDescription */ + ".vboxclient-clipboard", /* pszPidFilePathTemplate */ + NULL, /* pszUsage */ + NULL, /* pszOptions */ + NULL, /* pfnOption */ + vbclShClInit, /* pfnInit */ + vbclShClWorker, /* pfnWorker */ + vbclShClStop, /* pfnStop*/ + vbclShClTerm /* pfnTerm */ +}; + |