diff options
Diffstat (limited to 'src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-x11.cpp')
-rw-r--r-- | src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-x11.cpp | 566 |
1 files changed, 566 insertions, 0 deletions
diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-x11.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-x11.cpp new file mode 100644 index 00000000..e4eacb07 --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-x11.cpp @@ -0,0 +1,566 @@ +/* $Id: VBoxSharedClipboardSvc-x11.cpp $ */ +/** @file + * Shared Clipboard Service - Linux host. + */ + +/* + * Copyright (C) 2006-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 * +*********************************************************************************************************************************/ +#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 <iprt/asm.h> + +#include <VBox/GuestHost/SharedClipboard.h> +#include <VBox/GuestHost/SharedClipboard-x11.h> +#include <VBox/HostServices/VBoxClipboardSvc.h> +#include <iprt/errcore.h> + +#include "VBoxSharedClipboardSvc-internal.h" + +/* Number of currently extablished connections. */ +static volatile uint32_t g_cShClConnections; + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Global context information used by the host glue for the X11 clipboard backend. + */ +struct SHCLCONTEXT +{ + /** This mutex is grabbed during any critical operations on the clipboard + * which might clash with others. */ + RTCRITSECT CritSect; + /** X11 context data. */ + SHCLX11CTX X11; + /** Pointer to the VBox host client data structure. */ + PSHCLCLIENT pClient; + /** We set this when we start shutting down as a hint not to post any new + * requests. */ + bool fShuttingDown; +}; + + +/********************************************************************************************************************************* +* Prototypes * +*********************************************************************************************************************************/ +static DECLCALLBACK(int) shClReportFormatsCallback(PSHCLCONTEXT pCtx, uint32_t fFormats, void *pvUser); +static DECLCALLBACK(int) shClSendDataToDestCallback(PSHCLCONTEXT pCtx, void *pv, uint32_t cb, void *pvUser); +static DECLCALLBACK(int) shClRequestDataFromSourceCallback(PSHCLCONTEXT pCtx, SHCLFORMAT uFmt, void **ppv, uint32_t *pcb, void *pvUser); + + +int ShClBackendInit(PSHCLBACKEND pBackend, VBOXHGCMSVCFNTABLE *pTable) +{ + RT_NOREF(pBackend); + + LogFlowFuncEnter(); + + /* Override the connection limit. */ + for (uintptr_t i = 0; i < RT_ELEMENTS(pTable->acMaxClients); i++) + pTable->acMaxClients[i] = RT_MIN(VBOX_SHARED_CLIPBOARD_X11_CONNECTIONS_MAX, pTable->acMaxClients[i]); + + RT_ZERO(pBackend->Callbacks); + /* Use internal callbacks by default. */ + pBackend->Callbacks.pfnReportFormats = shClReportFormatsCallback; + pBackend->Callbacks.pfnOnRequestDataFromSource = shClRequestDataFromSourceCallback; + pBackend->Callbacks.pfnOnSendDataToDest = shClSendDataToDestCallback; + + return VINF_SUCCESS; +} + +void ShClBackendDestroy(PSHCLBACKEND pBackend) +{ + RT_NOREF(pBackend); + + LogFlowFuncEnter(); +} + +void ShClBackendSetCallbacks(PSHCLBACKEND pBackend, PSHCLCALLBACKS pCallbacks) +{ +#define SET_FN_IF_NOT_NULL(a_Fn) \ + if (pCallbacks->pfn##a_Fn) \ + pBackend->Callbacks.pfn##a_Fn = pCallbacks->pfn##a_Fn; + + SET_FN_IF_NOT_NULL(ReportFormats); + SET_FN_IF_NOT_NULL(OnClipboardRead); + SET_FN_IF_NOT_NULL(OnClipboardWrite); + SET_FN_IF_NOT_NULL(OnRequestDataFromSource); + SET_FN_IF_NOT_NULL(OnSendDataToDest); + +#undef SET_FN_IF_NOT_NULL +} + +/** + * @note On the host, we assume that some other application already owns + * the clipboard and leave ownership to X11. + */ +int ShClBackendConnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, bool fHeadless) +{ + int rc; + + /* Check if maximum allowed connections count has reached. */ + if (ASMAtomicIncU32(&g_cShClConnections) > VBOX_SHARED_CLIPBOARD_X11_CONNECTIONS_MAX) + { + ASMAtomicDecU32(&g_cShClConnections); + LogRel(("Shared Clipboard: maximum amount for client connections reached\n")); + return VERR_OUT_OF_RESOURCES; + } + + PSHCLCONTEXT pCtx = (PSHCLCONTEXT)RTMemAllocZ(sizeof(SHCLCONTEXT)); + if (pCtx) + { + rc = RTCritSectInit(&pCtx->CritSect); + if (RT_SUCCESS(rc)) + { + rc = ShClX11Init(&pCtx->X11, &pBackend->Callbacks, pCtx, fHeadless); + if (RT_SUCCESS(rc)) + { + pClient->State.pCtx = pCtx; + pCtx->pClient = pClient; + + rc = ShClX11ThreadStart(&pCtx->X11, true /* grab shared clipboard */); + if (RT_FAILURE(rc)) + ShClX11Destroy(&pCtx->X11); + } + + if (RT_FAILURE(rc)) + RTCritSectDelete(&pCtx->CritSect); + } + + if (RT_FAILURE(rc)) + { + pClient->State.pCtx = NULL; + RTMemFree(pCtx); + } + } + else + rc = VERR_NO_MEMORY; + + if (RT_FAILURE(rc)) + { + /* Restore active connections count. */ + ASMAtomicDecU32(&g_cShClConnections); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +int ShClBackendSync(PSHCLBACKEND pBackend, PSHCLCLIENT pClient) +{ + RT_NOREF(pBackend); + + LogFlowFuncEnter(); + + /* 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. */ + if (ShClSvcIsBackendActive()) + return ShClSvcHostReportFormats(pClient, VBOX_SHCL_FMT_NONE); + return VINF_SUCCESS; +} + +/* + * Shut down the shared clipboard service and "disconnect" the guest. + * Note! Host glue code + */ +int ShClBackendDisconnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient) +{ + RT_NOREF(pBackend); + + LogFlowFuncEnter(); + + PSHCLCONTEXT pCtx = pClient->State.pCtx; + AssertPtr(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; + + int rc = ShClX11ThreadStop(&pCtx->X11); + /** @todo handle this slightly more reasonably, or be really sure + * it won't go wrong. */ + AssertRC(rc); + + ShClX11Destroy(&pCtx->X11); + RTCritSectDelete(&pCtx->CritSect); + + RTMemFree(pCtx); + + /* Decrease active connections count. */ + ASMAtomicDecU32(&g_cShClConnections); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +int ShClBackendReportFormats(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, SHCLFORMATS fFormats) +{ + RT_NOREF(pBackend); + + int rc = ShClX11ReportFormatsToX11(&pClient->State.pCtx->X11, fFormats); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** Structure describing a request for clipoard data from the guest. */ +struct CLIPREADCBREQ +{ + /** User-supplied data pointer, based on the request type. */ + void *pv; + /** The size (in bytes) of the the user-supplied pointer in pv. */ + uint32_t cb; + /** The actual size of the data written. */ + uint32_t *pcbActual; + /** The request's event ID. */ + SHCLEVENTID idEvent; +}; + +/** + * @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 ShClBackendReadData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT uFormat, + void *pvData, uint32_t cbData, uint32_t *pcbActual) +{ + RT_NOREF(pBackend); + + AssertPtrReturn(pClient, VERR_INVALID_POINTER); + AssertPtrReturn(pCmdCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pvData, VERR_INVALID_POINTER); + AssertPtrReturn(pcbActual, VERR_INVALID_POINTER); + + RT_NOREF(pCmdCtx); + + LogFlowFunc(("pClient=%p, uFormat=%#x, pv=%p, cb=%RU32, pcbActual=%p\n", + pClient, uFormat, pvData, cbData, pcbActual)); + + int rc; + + CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)RTMemAllocZ(sizeof(CLIPREADCBREQ)); + if (pReq) + { + PSHCLEVENT pEvent; + rc = ShClEventSourceGenerateAndRegisterEvent(&pClient->EventSrc, &pEvent); + if (RT_SUCCESS(rc)) + { + pReq->pv = pvData; + pReq->cb = cbData; + pReq->pcbActual = pcbActual; + pReq->idEvent = pEvent->idEvent; + + /* Note: ShClX11ReadDataFromX11() will consume pReq on success. */ + rc = ShClX11ReadDataFromX11(&pClient->State.pCtx->X11, uFormat, pReq); + if (RT_SUCCESS(rc)) + { + PSHCLEVENTPAYLOAD pPayload; + rc = ShClEventWait(pEvent, 30 * 1000, &pPayload); + if (RT_SUCCESS(rc)) + { + if (pPayload) + { + memcpy(pvData, pPayload->pvData, RT_MIN(cbData, pPayload->cbData)); + + *pcbActual = (uint32_t)pPayload->cbData; + + ShClPayloadFree(pPayload); + } + else /* No payload given; could happen on invalid / not-expected formats. */ + *pcbActual = 0; + } + } + + ShClEventRelease(pEvent); + } + + if (RT_FAILURE(rc)) + RTMemFree(pReq); + } + else + rc = VERR_NO_MEMORY; + + if (RT_FAILURE(rc)) + LogRel(("Shared Clipboard: Error reading host clipboard data from X11, rc=%Rrc\n", rc)); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +int ShClBackendWriteData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT uFormat, void *pvData, uint32_t cbData) +{ + RT_NOREF(pBackend, pClient, pCmdCtx, uFormat, pvData, cbData); + + LogFlowFuncEnter(); + + /* Nothing to do here yet. */ + + LogFlowFuncLeave(); + return VINF_SUCCESS; +} + +/** @copydoc SHCLCALLBACKS::pfnReportFormats */ +static DECLCALLBACK(int) shClReportFormatsCallback(PSHCLCONTEXT pCtx, uint32_t fFormats, void *pvUser) +{ + RT_NOREF(pvUser); + + LogFlowFunc(("pCtx=%p, fFormats=%#x\n", pCtx, fFormats)); + + int rc = VINF_SUCCESS; + PSHCLCLIENT pClient = pCtx->pClient; + AssertPtr(pClient); + + rc = RTCritSectEnter(&pClient->CritSect); + if (RT_SUCCESS(rc)) + { + if (ShClSvcIsBackendActive()) + { + /** @todo r=bird: BUGBUG: Revisit this */ + if (fFormats != VBOX_SHCL_FMT_NONE) /* No formats to report? */ + { + rc = ShClSvcHostReportFormats(pCtx->pClient, fFormats); + } + } + + RTCritSectLeave(&pClient->CritSect); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** @copydoc SHCLCALLBACKS::pfnOnSendDataToDest */ +static DECLCALLBACK(int) shClSendDataToDestCallback(PSHCLCONTEXT pCtx, void *pv, uint32_t cb, void *pvUser) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pvUser, VERR_INVALID_POINTER); + + PSHCLX11READDATAREQ pData = (PSHCLX11READDATAREQ)pvUser; + CLIPREADCBREQ *pReq = pData->pReq; + + LogFlowFunc(("rcCompletion=%Rrc, pReq=%p, pv=%p, cb=%RU32, idEvent=%RU32\n", + pData->rcCompletion, pReq, pv, cb, pReq->idEvent)); + + if (pReq->idEvent != NIL_SHCLEVENTID) + { + int rc2; + + PSHCLEVENTPAYLOAD pPayload = NULL; + if ( RT_SUCCESS(pData->rcCompletion) + && pv + && cb) + { + rc2 = ShClPayloadAlloc(pReq->idEvent, pv, cb, &pPayload); + AssertRC(rc2); + } + + rc2 = RTCritSectEnter(&pCtx->pClient->CritSect); + if (RT_SUCCESS(rc2)) + { + const PSHCLEVENT pEvent = ShClEventSourceGetFromId(&pCtx->pClient->EventSrc, pReq->idEvent); + if (pEvent) + rc2 = ShClEventSignal(pEvent, pPayload); + + RTCritSectLeave(&pCtx->pClient->CritSect); + + if (RT_SUCCESS(rc2)) + pPayload = NULL; + } + + if (pPayload) + ShClPayloadFree(pPayload); + } + + if (pReq) + RTMemFree(pReq); + + LogRel2(("Shared Clipboard: Reading X11 clipboard data from host completed with %Rrc\n", pData->rcCompletion)); + + return VINF_SUCCESS; +} + +/** @copydoc SHCLCALLBACKS::pfnOnRequestDataFromSource */ +static DECLCALLBACK(int) shClRequestDataFromSourceCallback(PSHCLCONTEXT pCtx, SHCLFORMAT uFmt, void **ppv, uint32_t *pcb, void *pvUser) +{ + RT_NOREF(pvUser); + + LogFlowFunc(("pCtx=%p, uFmt=0x%x\n", pCtx, uFmt)); + + if (pCtx->fShuttingDown) + { + /* The shared clipboard is disconnecting. */ + LogRel(("Shared Clipboard: Host requested guest clipboard data after guest had disconnected\n")); + return VERR_WRONG_ORDER; + } + + PSHCLCLIENT pClient = pCtx->pClient; + AssertPtr(pClient); + + RTCritSectEnter(&pClient->CritSect); + + int rc = VINF_SUCCESS; + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + /* + * Note: We always return a generic URI list here. + * As we don't know which Atom target format was requested by the caller, the X11 clipboard codes needs + * to decide & transform the list into the actual clipboard Atom target format the caller wanted. + */ + if (uFmt == VBOX_SHCL_FMT_URI_LIST) + { + PSHCLTRANSFER pTransfer; + rc = shClSvcTransferStart(pCtx->pClient, + SHCLTRANSFERDIR_FROM_REMOTE, SHCLSOURCE_REMOTE, + &pTransfer); + if (RT_SUCCESS(rc)) + { + + } + else + LogRel(("Shared Clipboard: Initializing read transfer from guest failed with %Rrc\n", rc)); + + *ppv = NULL; + *pcb = 0; + + rc = VERR_NO_DATA; + } +#endif + + if (RT_SUCCESS(rc)) + { + /* Request data from the guest. */ + PSHCLEVENT pEvent; + rc = ShClSvcGuestDataRequest(pCtx->pClient, uFmt, &pEvent); + if (RT_SUCCESS(rc)) + { + RTCritSectLeave(&pClient->CritSect); + + PSHCLEVENTPAYLOAD pPayload; + rc = ShClEventWait(pEvent, 30 * 1000, &pPayload); + if (RT_SUCCESS(rc)) + { + if ( !pPayload + || !pPayload->cbData) + { + rc = VERR_NO_DATA; + } + else + { + *ppv = pPayload->pvData; + *pcb = pPayload->cbData; + } + } + + RTCritSectEnter(&pClient->CritSect); + + ShClEventRelease(pEvent); + } + } + + RTCritSectLeave(&pClient->CritSect); + + if (RT_FAILURE(rc)) + LogRel(("Shared Clipboard: Requesting data in format %#x for X11 host failed with %Rrc\n", uFmt, rc)); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + +int ShClBackendTransferCreate(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer) +{ + RT_NOREF(pBackend); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP + return ShClHttpTransferRegister(&pClient->State.pCtx->X11.HttpCtx, pTransfer); +#else + RT_NOREF(pClient, pTransfer); +#endif + return VERR_NOT_IMPLEMENTED; +} + +int ShClBackendTransferDestroy(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer) +{ + RT_NOREF(pBackend); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP + return ShClHttpTransferUnregister(&pClient->State.pCtx->X11.HttpCtx, pTransfer); +#else + RT_NOREF(pClient, pTransfer); +#endif + + return VINF_SUCCESS; +} + +int ShClBackendTransferGetRoots(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer) +{ + RT_NOREF(pBackend); + + LogFlowFuncEnter(); + + PSHCLEVENT pEvent; + int rc = ShClEventSourceGenerateAndRegisterEvent(&pClient->EventSrc, &pEvent); + if (RT_SUCCESS(rc)) + { + CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)RTMemAllocZ(sizeof(CLIPREADCBREQ)); + if (pReq) + { + pReq->idEvent = pEvent->idEvent; + + rc = ShClX11ReadDataFromX11(&pClient->State.pCtx->X11, VBOX_SHCL_FMT_URI_LIST, pReq); + if (RT_SUCCESS(rc)) + { + /* X supplies the data asynchronously, so we need to wait for data to arrive first. */ + PSHCLEVENTPAYLOAD pPayload; + rc = ShClEventWait(pEvent, 30 * 1000, &pPayload); + if (RT_SUCCESS(rc)) + { + rc = ShClTransferRootsSet(pTransfer, + (char *)pPayload->pvData, pPayload->cbData + 1 /* Include termination */); + } + } + } + else + rc = VERR_NO_MEMORY; + + ShClEventRelease(pEvent); + } + else + rc = VERR_SHCLPB_MAX_EVENTS_REACHED; + + LogFlowFuncLeaveRC(rc); + return rc; +} +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + |