summaryrefslogtreecommitdiffstats
path: root/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.cpp')
-rw-r--r--src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.cpp1047
1 files changed, 1047 insertions, 0 deletions
diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.cpp
new file mode 100644
index 00000000..1b9b9cbc
--- /dev/null
+++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.cpp
@@ -0,0 +1,1047 @@
+/* $Id: VBoxSharedClipboardSvc.cpp $ */
+/** @file
+ * Shared Clipboard Service - Host service entry points.
+ */
+
+/*
+ * 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.
+ */
+
+
+/** @page pg_hostclip The Shared Clipboard Host Service
+ *
+ * The shared clipboard host service provides a proxy between the host's
+ * clipboard and a similar proxy running on a guest. The service is split
+ * into a platform-independent core and platform-specific backends. The
+ * service defines two communication protocols - one to communicate with the
+ * clipboard service running on the guest, and one to communicate with the
+ * backend. These will be described in a very skeletal fashion here.
+ *
+ * @section sec_hostclip_guest_proto The guest communication protocol
+ *
+ * The guest clipboard service communicates with the host service via HGCM
+ * (the host service runs as an HGCM service). The guest clipboard must
+ * connect to the host service before all else (Windows hosts currently only
+ * support one simultaneous connection). Once it has connected, it can send
+ * HGCM messages to the host services, some of which will receive replies from
+ * the host. The host can only reply to a guest message, it cannot initiate
+ * any communication. The guest can in theory send any number of messages in
+ * parallel (see the descriptions of the messages for the practice), and the
+ * host will receive these in sequence, and may reply to them at once
+ * (releasing the caller in the guest) or defer the reply until later.
+ *
+ * There are currently four messages defined. The first is
+ * VBOX_SHARED_CLIPBOARD_FN_GET_HOST_MSG, which waits for a message from the
+ * host. Host messages currently defined are
+ * VBOX_SHARED_CLIPBOARD_HOST_MSG_QUIT (unused),
+ * VBOX_SHARED_CLIPBOARD_HOST_MSG_READ_DATA (request that the guest send the
+ * contents of its clipboard to the host) and
+ * VBOX_SHARED_CLIPBOARD_HOST_MSG_FORMATS (to notify the guest that new
+ * clipboard data is available). If a host message is sent while the guest is
+ * not waiting, it will be queued until the guest requests it. At most one
+ * host message of each type will be kept in the queue. The host code only
+ * supports a single simultaneous VBOX_SHARED_CLIPBOARD_FN_GET_HOST_MSG call
+ * from the guest.
+ *
+ * The second guest message is VBOX_SHARED_CLIPBOARD_FN_FORMATS, which tells
+ * the host that the guest has new clipboard data available. The third is
+ * VBOX_SHARED_CLIPBOARD_FN_READ_DATA, which asks the host to send its
+ * clipboard data and waits until it arrives. The host supports at most one
+ * simultaneous VBOX_SHARED_CLIPBOARD_FN_READ_DATA call from the guest - if a
+ * second call is made before the first has returned, the first will be
+ * aborted.
+ *
+ * The last guest message is VBOX_SHARED_CLIPBOARD_FN_WRITE_DATA, which is
+ * used to send the contents of the guest clipboard to the host. This call
+ * should be used after the host has requested data from the guest.
+ *
+ * @section sec_hostclip_backend_proto The communication protocol with the
+ * platform-specific backend
+ *
+ * This section may be written in the future :)
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD
+#include <VBox/HostServices/VBoxClipboardSvc.h>
+#include <VBox/HostServices/VBoxClipboardExt.h>
+
+#include <iprt/alloc.h>
+#include <iprt/string.h>
+#include <iprt/assert.h>
+#include <iprt/critsect.h>
+#include <VBox/err.h>
+#include <VBox/vmm/ssm.h>
+
+#include "VBoxClipboard.h"
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static PVBOXHGCMSVCHELPERS g_pHelpers;
+
+static RTCRITSECT critsect;
+static uint32_t g_u32Mode;
+
+static PFNHGCMSVCEXT g_pfnExtension;
+static void *g_pvExtension;
+
+static VBOXCLIPBOARDCLIENTDATA *g_pClient;
+
+/* Serialization of data reading and format announcements from the RDP client. */
+static bool g_fReadingData = false;
+static bool g_fDelayedAnnouncement = false;
+static uint32_t g_u32DelayedFormats = 0;
+
+/** Is the clipboard running in headless mode? */
+static bool g_fHeadless = false;
+
+
+static void VBoxHGCMParmUInt32Set (VBOXHGCMSVCPARM *pParm, uint32_t u32)
+{
+ pParm->type = VBOX_HGCM_SVC_PARM_32BIT;
+ pParm->u.uint32 = u32;
+}
+
+static int VBoxHGCMParmUInt32Get (VBOXHGCMSVCPARM *pParm, uint32_t *pu32)
+{
+ if (pParm->type == VBOX_HGCM_SVC_PARM_32BIT)
+ {
+ *pu32 = pParm->u.uint32;
+ return VINF_SUCCESS;
+ }
+
+ return VERR_INVALID_PARAMETER;
+}
+
+#if 0
+static void VBoxHGCMParmPtrSet (VBOXHGCMSVCPARM *pParm, void *pv, uint32_t cb)
+{
+ pParm->type = VBOX_HGCM_SVC_PARM_PTR;
+ pParm->u.pointer.size = cb;
+ pParm->u.pointer.addr = pv;
+}
+#endif
+
+static int VBoxHGCMParmPtrGet (VBOXHGCMSVCPARM *pParm, void **ppv, uint32_t *pcb)
+{
+ if (pParm->type == VBOX_HGCM_SVC_PARM_PTR)
+ {
+ *ppv = pParm->u.pointer.addr;
+ *pcb = pParm->u.pointer.size;
+ return VINF_SUCCESS;
+ }
+
+ return VERR_INVALID_PARAMETER;
+}
+
+
+static uint32_t vboxSvcClipboardMode (void)
+{
+ return g_u32Mode;
+}
+
+#ifdef UNIT_TEST
+/** Testing interface, getter for clipboard mode */
+uint32_t TestClipSvcGetMode(void)
+{
+ return vboxSvcClipboardMode();
+}
+#endif
+
+/** Getter for headless setting */
+bool vboxSvcClipboardGetHeadless(void)
+{
+ return g_fHeadless;
+}
+
+static void vboxSvcClipboardModeSet (uint32_t u32Mode)
+{
+ switch (u32Mode)
+ {
+ case VBOX_SHARED_CLIPBOARD_MODE_OFF:
+ case VBOX_SHARED_CLIPBOARD_MODE_HOST_TO_GUEST:
+ case VBOX_SHARED_CLIPBOARD_MODE_GUEST_TO_HOST:
+ case VBOX_SHARED_CLIPBOARD_MODE_BIDIRECTIONAL:
+ g_u32Mode = u32Mode;
+ break;
+
+ default:
+ g_u32Mode = VBOX_SHARED_CLIPBOARD_MODE_OFF;
+ }
+}
+
+bool vboxSvcClipboardLock (void)
+{
+ return RT_SUCCESS(RTCritSectEnter (&critsect));
+}
+
+void vboxSvcClipboardUnlock (void)
+{
+ RTCritSectLeave (&critsect);
+}
+
+/* Set the HGCM parameters according to pending messages.
+ * Executed under the clipboard lock.
+ */
+static bool vboxSvcClipboardReturnMsg (VBOXCLIPBOARDCLIENTDATA *pClient, VBOXHGCMSVCPARM paParms[])
+{
+ /* Message priority is taken into account. */
+ if (pClient->fMsgQuit)
+ {
+ LogRelFlow(("vboxSvcClipboardReturnMsg: Quit\n"));
+ VBoxHGCMParmUInt32Set (&paParms[0], VBOX_SHARED_CLIPBOARD_HOST_MSG_QUIT);
+ VBoxHGCMParmUInt32Set (&paParms[1], 0);
+ pClient->fMsgQuit = false;
+ }
+ else if (pClient->fMsgReadData)
+ {
+ uint32_t fFormat = 0;
+
+ LogRelFlow(("vboxSvcClipboardReturnMsg: ReadData %02X\n", pClient->u32RequestedFormat));
+ if (pClient->u32RequestedFormat & VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT)
+ fFormat = VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT;
+ else if (pClient->u32RequestedFormat & VBOX_SHARED_CLIPBOARD_FMT_BITMAP)
+ fFormat = VBOX_SHARED_CLIPBOARD_FMT_BITMAP;
+ else if (pClient->u32RequestedFormat & VBOX_SHARED_CLIPBOARD_FMT_HTML)
+ fFormat = VBOX_SHARED_CLIPBOARD_FMT_HTML;
+ else
+ AssertStmt(pClient->u32RequestedFormat == 0, pClient->u32RequestedFormat = 0);
+ pClient->u32RequestedFormat &= ~fFormat;
+ VBoxHGCMParmUInt32Set (&paParms[0], VBOX_SHARED_CLIPBOARD_HOST_MSG_READ_DATA);
+ VBoxHGCMParmUInt32Set (&paParms[1], fFormat);
+ if (pClient->u32RequestedFormat == 0)
+ pClient->fMsgReadData = false;
+ }
+ else if (pClient->fMsgFormats)
+ {
+ LogRelFlow(("vboxSvcClipboardReturnMsg: Formats %02X\n", pClient->u32AvailableFormats));
+ VBoxHGCMParmUInt32Set (&paParms[0], VBOX_SHARED_CLIPBOARD_HOST_MSG_FORMATS);
+ VBoxHGCMParmUInt32Set (&paParms[1], pClient->u32AvailableFormats);
+ pClient->fMsgFormats = false;
+ }
+ else
+ {
+ /* No pending messages. */
+ LogRelFlow(("vboxSvcClipboardReturnMsg: no message\n"));
+ return false;
+ }
+
+ /* Message information assigned. */
+ return true;
+}
+
+void vboxSvcClipboardReportMsg (VBOXCLIPBOARDCLIENTDATA *pClient, uint32_t u32Msg, uint32_t u32Formats)
+{
+ if (vboxSvcClipboardLock ())
+ {
+ switch (u32Msg)
+ {
+ case VBOX_SHARED_CLIPBOARD_HOST_MSG_QUIT:
+ {
+ LogRelFlow(("vboxSvcClipboardReportMsg: Quit\n"));
+ pClient->fMsgQuit = true;
+ } break;
+ case VBOX_SHARED_CLIPBOARD_HOST_MSG_READ_DATA:
+ {
+ if ( vboxSvcClipboardMode () != VBOX_SHARED_CLIPBOARD_MODE_GUEST_TO_HOST
+ && vboxSvcClipboardMode () != VBOX_SHARED_CLIPBOARD_MODE_BIDIRECTIONAL)
+ {
+ /* Skip the message. */
+ break;
+ }
+
+ LogRelFlow(("vboxSvcClipboardReportMsg: ReadData %02X\n", u32Formats));
+ pClient->u32RequestedFormat = u32Formats;
+ pClient->fMsgReadData = true;
+ } break;
+ case VBOX_SHARED_CLIPBOARD_HOST_MSG_FORMATS:
+ {
+ if ( vboxSvcClipboardMode () != VBOX_SHARED_CLIPBOARD_MODE_HOST_TO_GUEST
+ && vboxSvcClipboardMode () != VBOX_SHARED_CLIPBOARD_MODE_BIDIRECTIONAL)
+ {
+ /* Skip the message. */
+ break;
+ }
+
+ LogRelFlow(("vboxSvcClipboardReportMsg: Formats %02X\n", u32Formats));
+ pClient->u32AvailableFormats = u32Formats;
+ pClient->fMsgFormats = true;
+ } break;
+ default:
+ {
+ /* Invalid message. */
+ LogRelFlow(("vboxSvcClipboardReportMsg: invalid message %d\n", u32Msg));
+ } break;
+ }
+
+ if (pClient->fAsync)
+ {
+ /* The client waits for a response. */
+ bool fMessageReturned = vboxSvcClipboardReturnMsg (pClient, pClient->async.paParms);
+
+ /* Make a copy of the handle. */
+ VBOXHGCMCALLHANDLE callHandle = pClient->async.callHandle;
+
+ if (fMessageReturned)
+ {
+ /* There is a response. */
+ pClient->fAsync = false;
+ }
+
+ vboxSvcClipboardUnlock ();
+
+ if (fMessageReturned)
+ {
+ LogRelFlow(("vboxSvcClipboardReportMsg: CallComplete\n"));
+ g_pHelpers->pfnCallComplete (callHandle, VINF_SUCCESS);
+ }
+ }
+ else
+ {
+ vboxSvcClipboardUnlock ();
+ }
+ }
+}
+
+static int svcInit (void)
+{
+ int rc = RTCritSectInit (&critsect);
+
+ if (RT_SUCCESS (rc))
+ {
+ vboxSvcClipboardModeSet (VBOX_SHARED_CLIPBOARD_MODE_OFF);
+
+ rc = vboxClipboardInit ();
+
+ /* Clean up on failure, because 'svnUnload' will not be called
+ * if the 'svcInit' returns an error.
+ */
+ if (RT_FAILURE (rc))
+ {
+ RTCritSectDelete (&critsect);
+ }
+ }
+
+ return rc;
+}
+
+static DECLCALLBACK(int) svcUnload (void *)
+{
+ vboxClipboardDestroy ();
+ RTCritSectDelete (&critsect);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Disconnect the host side of the shared clipboard and send a "host disconnected" message
+ * to the guest side.
+ */
+static DECLCALLBACK(int) svcDisconnect (void *, uint32_t u32ClientID, void *pvClient)
+{
+ VBOXCLIPBOARDCLIENTDATA *pClient = (VBOXCLIPBOARDCLIENTDATA *)pvClient;
+
+ LogRel2(("svcDisconnect: u32ClientID = %d\n", u32ClientID));
+
+ vboxSvcClipboardReportMsg (pClient, VBOX_SHARED_CLIPBOARD_HOST_MSG_QUIT, 0);
+
+ vboxSvcClipboardCompleteReadData(pClient, VERR_NO_DATA, 0);
+
+ vboxClipboardDisconnect (pClient);
+
+ memset (pClient, 0, sizeof (*pClient));
+
+ g_pClient = NULL;
+
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) svcConnect (void *, uint32_t u32ClientID, void *pvClient, uint32_t fRequestor, bool fRestoring)
+{
+ RT_NOREF(fRequestor, fRestoring);
+ VBOXCLIPBOARDCLIENTDATA *pClient = (VBOXCLIPBOARDCLIENTDATA *)pvClient;
+
+ int rc = VINF_SUCCESS;
+
+ /* If there is already a client connected then we want to release it first. */
+ if (g_pClient != NULL)
+ {
+ uint32_t u32OldClientID = g_pClient->u32ClientID;
+
+ svcDisconnect(NULL, u32OldClientID, g_pClient);
+ /* And free the resources in the hgcm subsystem. */
+ g_pHelpers->pfnDisconnectClient(g_pHelpers->pvInstance, u32OldClientID);
+ }
+
+ /* Register the client. */
+ memset (pClient, 0, sizeof (*pClient));
+
+ pClient->u32ClientID = u32ClientID;
+
+ rc = vboxClipboardConnect (pClient, vboxSvcClipboardGetHeadless());
+
+ if (RT_SUCCESS (rc))
+ {
+ g_pClient = pClient;
+ }
+
+ LogRel2(("vboxClipboardConnect: rc = %Rrc\n", rc));
+
+ return rc;
+}
+
+static DECLCALLBACK(void) svcCall (void *,
+ VBOXHGCMCALLHANDLE callHandle,
+ uint32_t u32ClientID,
+ void *pvClient,
+ uint32_t u32Function,
+ uint32_t cParms,
+ VBOXHGCMSVCPARM paParms[],
+ uint64_t tsArrival)
+{
+ RT_NOREF_PV(tsArrival);
+ int rc = VINF_SUCCESS;
+
+ LogRel2(("svcCall: u32ClientID = %d, fn = %d, cParms = %d, pparms = %d\n",
+ u32ClientID, u32Function, cParms, paParms));
+
+ VBOXCLIPBOARDCLIENTDATA *pClient = (VBOXCLIPBOARDCLIENTDATA *)pvClient;
+
+ bool fAsynchronousProcessing = false;
+
+#ifdef DEBUG
+ uint32_t i;
+
+ for (i = 0; i < cParms; i++)
+ {
+ /** @todo parameters other than 32 bit */
+ LogRel2((" pparms[%d]: type %d value %d\n", i, paParms[i].type, paParms[i].u.uint32));
+ }
+#endif
+
+ switch (u32Function)
+ {
+ case VBOX_SHARED_CLIPBOARD_FN_GET_HOST_MSG:
+ {
+ /* The quest requests a host message. */
+ LogRel2(("svcCall: VBOX_SHARED_CLIPBOARD_FN_GET_HOST_MSG\n"));
+
+ if (cParms != VBOX_SHARED_CLIPBOARD_CPARMS_GET_HOST_MSG)
+ {
+ rc = VERR_INVALID_PARAMETER;
+ }
+ else if ( paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT /* msg */
+ || paParms[1].type != VBOX_HGCM_SVC_PARM_32BIT /* formats */
+ )
+ {
+ rc = VERR_INVALID_PARAMETER;
+ }
+ else
+ {
+ /* Atomically verify the client's state. */
+ if (vboxSvcClipboardLock ())
+ {
+ bool fMessageReturned = vboxSvcClipboardReturnMsg (pClient, paParms);
+
+ if (fMessageReturned)
+ {
+ /* Just return to the caller. */
+ pClient->fAsync = false;
+ }
+ else
+ {
+ /* No event available at the time. Process asynchronously. */
+ fAsynchronousProcessing = true;
+
+ pClient->fAsync = true;
+ pClient->async.callHandle = callHandle;
+ pClient->async.paParms = paParms;
+
+ LogRel2(("svcCall: async.\n"));
+ }
+
+ vboxSvcClipboardUnlock ();
+ }
+ else
+ {
+ rc = VERR_NOT_SUPPORTED;
+ }
+ }
+ } break;
+
+ case VBOX_SHARED_CLIPBOARD_FN_FORMATS:
+ {
+ /* The guest reports that some formats are available. */
+ LogRel2(("svcCall: VBOX_SHARED_CLIPBOARD_FN_FORMATS\n"));
+
+ if (cParms != VBOX_SHARED_CLIPBOARD_CPARMS_FORMATS)
+ {
+ rc = VERR_INVALID_PARAMETER;
+ }
+ else if ( paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT /* formats */
+ )
+ {
+ rc = VERR_INVALID_PARAMETER;
+ }
+ else
+ {
+ uint32_t u32Formats;
+
+ rc = VBoxHGCMParmUInt32Get (&paParms[0], &u32Formats);
+
+ if (RT_SUCCESS (rc))
+ {
+ if ( vboxSvcClipboardMode () != VBOX_SHARED_CLIPBOARD_MODE_GUEST_TO_HOST
+ && vboxSvcClipboardMode () != VBOX_SHARED_CLIPBOARD_MODE_BIDIRECTIONAL)
+ {
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ if (g_pfnExtension)
+ {
+ VBOXCLIPBOARDEXTPARMS parms;
+
+ parms.u32Format = u32Formats;
+
+ g_pfnExtension (g_pvExtension, VBOX_CLIPBOARD_EXT_FN_FORMAT_ANNOUNCE, &parms, sizeof (parms));
+ }
+ else
+ {
+ vboxClipboardFormatAnnounce (pClient, u32Formats);
+ }
+ }
+ }
+ } break;
+
+ case VBOX_SHARED_CLIPBOARD_FN_READ_DATA:
+ {
+ /* The guest wants to read data in the given format. */
+ LogRel2(("svcCall: VBOX_SHARED_CLIPBOARD_FN_READ_DATA\n"));
+
+ if (cParms != VBOX_SHARED_CLIPBOARD_CPARMS_READ_DATA)
+ {
+ rc = VERR_INVALID_PARAMETER;
+ }
+ else if ( paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT /* format */
+ || paParms[1].type != VBOX_HGCM_SVC_PARM_PTR /* ptr */
+ || paParms[2].type != VBOX_HGCM_SVC_PARM_32BIT /* size */
+ )
+ {
+ rc = VERR_INVALID_PARAMETER;
+ }
+ else
+ {
+ uint32_t u32Format;
+ void *pv;
+ uint32_t cb;
+
+ rc = VBoxHGCMParmUInt32Get (&paParms[0], &u32Format);
+
+ if (RT_SUCCESS (rc))
+ {
+ rc = VBoxHGCMParmPtrGet (&paParms[1], &pv, &cb);
+
+ if (RT_SUCCESS (rc))
+ {
+ if ( vboxSvcClipboardMode () != VBOX_SHARED_CLIPBOARD_MODE_HOST_TO_GUEST
+ && vboxSvcClipboardMode () != VBOX_SHARED_CLIPBOARD_MODE_BIDIRECTIONAL)
+ {
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ uint32_t cbActual = 0;
+
+ if (g_pfnExtension)
+ {
+ VBOXCLIPBOARDEXTPARMS parms;
+
+ parms.u32Format = u32Format;
+ parms.u.pvData = pv;
+ parms.cbData = cb;
+
+ g_fReadingData = true;
+ rc = g_pfnExtension (g_pvExtension, VBOX_CLIPBOARD_EXT_FN_DATA_READ, &parms, sizeof (parms));
+ LogRelFlow(("DATA: g_fDelayedAnnouncement = %d, g_u32DelayedFormats = 0x%x\n", g_fDelayedAnnouncement, g_u32DelayedFormats));
+ if (g_fDelayedAnnouncement)
+ {
+ vboxSvcClipboardReportMsg (g_pClient, VBOX_SHARED_CLIPBOARD_HOST_MSG_FORMATS, g_u32DelayedFormats);
+ g_fDelayedAnnouncement = false;
+ g_u32DelayedFormats = 0;
+ }
+ g_fReadingData = false;
+
+ if (RT_SUCCESS (rc))
+ {
+ cbActual = parms.cbData;
+ }
+ }
+ else
+ {
+ /* Release any other pending read, as we only
+ * support one pending read at one time. */
+ vboxSvcClipboardCompleteReadData(pClient, VERR_NO_DATA, 0);
+ rc = vboxClipboardReadData (pClient, u32Format, pv, cb, &cbActual);
+ }
+
+ /* Remember our read request until it is completed.
+ * See the protocol description above for more
+ * information. */
+ if (rc == VINF_HGCM_ASYNC_EXECUTE)
+ {
+ if (vboxSvcClipboardLock())
+ {
+ pClient->asyncRead.callHandle = callHandle;
+ pClient->asyncRead.paParms = paParms;
+ pClient->fReadPending = true;
+ fAsynchronousProcessing = true;
+ vboxSvcClipboardUnlock();
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+ }
+ else if (RT_SUCCESS (rc))
+ {
+ VBoxHGCMParmUInt32Set (&paParms[2], cbActual);
+ }
+ }
+ }
+ }
+ } break;
+
+ case VBOX_SHARED_CLIPBOARD_FN_WRITE_DATA:
+ {
+ /* The guest writes the requested data. */
+ LogRel2(("svcCall: VBOX_SHARED_CLIPBOARD_FN_WRITE_DATA\n"));
+
+ if (cParms != VBOX_SHARED_CLIPBOARD_CPARMS_WRITE_DATA)
+ {
+ rc = VERR_INVALID_PARAMETER;
+ }
+ else if ( paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT /* format */
+ || paParms[1].type != VBOX_HGCM_SVC_PARM_PTR /* ptr */
+ )
+ {
+ rc = VERR_INVALID_PARAMETER;
+ }
+ else
+ {
+ void *pv;
+ uint32_t cb;
+ uint32_t u32Format;
+
+ rc = VBoxHGCMParmUInt32Get (&paParms[0], &u32Format);
+
+ if (RT_SUCCESS (rc))
+ {
+ rc = VBoxHGCMParmPtrGet (&paParms[1], &pv, &cb);
+
+ if (RT_SUCCESS (rc))
+ {
+ if ( vboxSvcClipboardMode () != VBOX_SHARED_CLIPBOARD_MODE_GUEST_TO_HOST
+ && vboxSvcClipboardMode () != VBOX_SHARED_CLIPBOARD_MODE_BIDIRECTIONAL)
+ {
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ if (g_pfnExtension)
+ {
+ VBOXCLIPBOARDEXTPARMS parms;
+
+ parms.u32Format = u32Format;
+ parms.u.pvData = pv;
+ parms.cbData = cb;
+
+ g_pfnExtension (g_pvExtension, VBOX_CLIPBOARD_EXT_FN_DATA_WRITE, &parms, sizeof (parms));
+ }
+ else
+ {
+ vboxClipboardWriteData (pClient, pv, cb, u32Format);
+ }
+ }
+ }
+ }
+ } break;
+
+ default:
+ {
+ rc = VERR_NOT_IMPLEMENTED;
+ }
+ }
+
+ LogRelFlow(("svcCall: rc = %Rrc\n", rc));
+
+ if (!fAsynchronousProcessing)
+ {
+ g_pHelpers->pfnCallComplete (callHandle, rc);
+ }
+}
+
+/** If the client in the guest is waiting for a read operation to complete
+ * then complete it, otherwise return. See the protocol description in the
+ * shared clipboard module description. */
+void vboxSvcClipboardCompleteReadData(VBOXCLIPBOARDCLIENTDATA *pClient, int rc, uint32_t cbActual)
+{
+ VBOXHGCMCALLHANDLE callHandle = NULL;
+ VBOXHGCMSVCPARM *paParms = NULL;
+ bool fReadPending = false;
+ if (vboxSvcClipboardLock()) /* if not can we do anything useful? */
+ {
+ callHandle = pClient->asyncRead.callHandle;
+ paParms = pClient->asyncRead.paParms;
+ fReadPending = pClient->fReadPending;
+ pClient->fReadPending = false;
+ vboxSvcClipboardUnlock();
+ }
+ if (fReadPending)
+ {
+ VBoxHGCMParmUInt32Set (&paParms[2], cbActual);
+ g_pHelpers->pfnCallComplete (callHandle, rc);
+ }
+}
+
+/*
+ * We differentiate between a function handler for the guest and one for the host.
+ */
+static DECLCALLBACK(int) svcHostCall (void *,
+ uint32_t u32Function,
+ uint32_t cParms,
+ VBOXHGCMSVCPARM paParms[])
+{
+ int rc = VINF_SUCCESS;
+
+ LogRel2(("svcHostCall: fn = %d, cParms = %d, pparms = %d\n",
+ u32Function, cParms, paParms));
+
+ switch (u32Function)
+ {
+ case VBOX_SHARED_CLIPBOARD_HOST_FN_SET_MODE:
+ {
+ LogRel2(("svcCall: VBOX_SHARED_CLIPBOARD_HOST_FN_SET_MODE\n"));
+
+ if (cParms != 1)
+ {
+ rc = VERR_INVALID_PARAMETER;
+ }
+ else if ( paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT /* mode */
+ )
+ {
+ rc = VERR_INVALID_PARAMETER;
+ }
+ else
+ {
+ uint32_t u32Mode = VBOX_SHARED_CLIPBOARD_MODE_OFF;
+
+ rc = VBoxHGCMParmUInt32Get (&paParms[0], &u32Mode);
+
+ /* The setter takes care of invalid values. */
+ vboxSvcClipboardModeSet (u32Mode);
+ }
+ } break;
+
+ case VBOX_SHARED_CLIPBOARD_HOST_FN_SET_HEADLESS:
+ {
+ uint32_t u32Headless = g_fHeadless;
+
+ rc = VERR_INVALID_PARAMETER;
+ if (cParms != 1)
+ break;
+ rc = VBoxHGCMParmUInt32Get (&paParms[0], &u32Headless);
+ if (RT_SUCCESS(rc))
+ LogRelFlow(("svcCall: VBOX_SHARED_CLIPBOARD_HOST_FN_SET_HEADLESS, u32Headless=%u\n",
+ (unsigned) u32Headless));
+ g_fHeadless = RT_BOOL(u32Headless);
+ } break;
+
+ default:
+ break;
+ }
+
+ LogRelFlow(("svcHostCall: rc = %Rrc\n", rc));
+ return rc;
+}
+
+#ifndef UNIT_TEST
+/**
+ * SSM descriptor table for the VBOXCLIPBOARDCLIENTDATA structure.
+ */
+static SSMFIELD const g_aClipboardClientDataFields[] =
+{
+ SSMFIELD_ENTRY(VBOXCLIPBOARDCLIENTDATA, u32ClientID), /* for validation purposes */
+ SSMFIELD_ENTRY(VBOXCLIPBOARDCLIENTDATA, fMsgQuit),
+ SSMFIELD_ENTRY(VBOXCLIPBOARDCLIENTDATA, fMsgReadData),
+ SSMFIELD_ENTRY(VBOXCLIPBOARDCLIENTDATA, fMsgFormats),
+ SSMFIELD_ENTRY(VBOXCLIPBOARDCLIENTDATA, u32RequestedFormat),
+ SSMFIELD_ENTRY_TERM()
+};
+#endif
+
+static DECLCALLBACK(int) svcSaveState(void *, uint32_t u32ClientID, void *pvClient, PSSMHANDLE pSSM)
+{
+#ifndef UNIT_TEST
+ /*
+ * When the state will be restored, pending requests will be reissued
+ * by VMMDev. The service therefore must save state as if there were no
+ * pending request.
+ * Pending requests, if any, will be completed in svcDisconnect.
+ */
+ LogRel2 (("svcSaveState: u32ClientID = %d\n", u32ClientID));
+
+ VBOXCLIPBOARDCLIENTDATA *pClient = (VBOXCLIPBOARDCLIENTDATA *)pvClient;
+
+ /* This field used to be the length. We're using it as a version field
+ with the high bit set. */
+ SSMR3PutU32 (pSSM, UINT32_C (0x80000002));
+ int rc = SSMR3PutStructEx (pSSM, pClient, sizeof(*pClient), 0 /*fFlags*/, &g_aClipboardClientDataFields[0], NULL);
+ AssertRCReturn (rc, rc);
+
+#else /* UNIT_TEST */
+ RT_NOREF3(u32ClientID, pvClient, pSSM);
+#endif /* UNIT_TEST */
+ return VINF_SUCCESS;
+}
+
+/**
+ * This structure corresponds to the original layout of the
+ * VBOXCLIPBOARDCLIENTDATA structure. As the structure was saved as a whole
+ * when saving state, we need to remember it forever in order to preserve
+ * compatibility.
+ *
+ * (Starting with 3.1 this is no longer used.)
+ *
+ * @remarks Putting this outside svcLoadState to avoid visibility warning caused
+ * by -Wattributes.
+ */
+typedef struct CLIPSAVEDSTATEDATA
+{
+ struct CLIPSAVEDSTATEDATA *pNext;
+ struct CLIPSAVEDSTATEDATA *pPrev;
+
+ VBOXCLIPBOARDCONTEXT *pCtx;
+
+ uint32_t u32ClientID;
+
+ bool fAsync: 1; /* Guest is waiting for a message. */
+
+ bool fMsgQuit: 1;
+ bool fMsgReadData: 1;
+ bool fMsgFormats: 1;
+
+ struct {
+ VBOXHGCMCALLHANDLE callHandle;
+ VBOXHGCMSVCPARM *paParms;
+ } async;
+
+ struct {
+ void *pv;
+ uint32_t cb;
+ uint32_t u32Format;
+ } data;
+
+ uint32_t u32AvailableFormats;
+ uint32_t u32RequestedFormat;
+
+} CLIPSAVEDSTATEDATA;
+
+static DECLCALLBACK(int) svcLoadState(void *, uint32_t u32ClientID, void *pvClient, PSSMHANDLE pSSM, uint32_t uVersion)
+{
+#ifndef UNIT_TEST
+ RT_NOREF(uVersion);
+ LogRel2 (("svcLoadState: u32ClientID = %d\n", u32ClientID));
+
+ VBOXCLIPBOARDCLIENTDATA *pClient = (VBOXCLIPBOARDCLIENTDATA *)pvClient;
+
+ /* Existing client can not be in async state yet. */
+ Assert (!pClient->fAsync);
+
+ /* Save the client ID for data validation. */
+ /** @todo isn't this the same as u32ClientID? Playing safe for now... */
+ uint32_t const u32ClientIDOld = pClient->u32ClientID;
+
+ /* Restore the client data. */
+ uint32_t lenOrVer;
+ int rc = SSMR3GetU32 (pSSM, &lenOrVer);
+ AssertRCReturn (rc, rc);
+ if (lenOrVer == UINT32_C (0x80000002))
+ {
+ rc = SSMR3GetStructEx (pSSM, pClient, sizeof(*pClient), 0 /*fFlags*/, &g_aClipboardClientDataFields[0], NULL);
+ AssertRCReturn (rc, rc);
+ }
+ else if (lenOrVer == (SSMR3HandleHostBits (pSSM) == 64 ? 72U : 48U))
+ {
+ /**
+ * SSM descriptor table for the CLIPSAVEDSTATEDATA structure.
+ */
+ static SSMFIELD const s_aClipSavedStateDataFields30[] =
+ {
+ SSMFIELD_ENTRY_IGN_HCPTR( CLIPSAVEDSTATEDATA, pNext),
+ SSMFIELD_ENTRY_IGN_HCPTR( CLIPSAVEDSTATEDATA, pPrev),
+ SSMFIELD_ENTRY_IGN_HCPTR( CLIPSAVEDSTATEDATA, pCtx),
+ SSMFIELD_ENTRY( CLIPSAVEDSTATEDATA, u32ClientID),
+ SSMFIELD_ENTRY_CUSTOM(fMsgQuit + fMsgReadData + fMsgFormats, RT_UOFFSETOF(CLIPSAVEDSTATEDATA, u32ClientID) + 4, 4),
+ SSMFIELD_ENTRY_IGN_HCPTR( CLIPSAVEDSTATEDATA, async.callHandle),
+ SSMFIELD_ENTRY_IGN_HCPTR( CLIPSAVEDSTATEDATA, async.paParms),
+ SSMFIELD_ENTRY_IGNORE( CLIPSAVEDSTATEDATA, data.pv),
+ SSMFIELD_ENTRY_IGNORE( CLIPSAVEDSTATEDATA, data.cb),
+ SSMFIELD_ENTRY_IGNORE( CLIPSAVEDSTATEDATA, data.u32Format),
+ SSMFIELD_ENTRY_IGNORE( CLIPSAVEDSTATEDATA, u32AvailableFormats),
+ SSMFIELD_ENTRY( CLIPSAVEDSTATEDATA, u32RequestedFormat),
+ SSMFIELD_ENTRY_TERM()
+ };
+
+ CLIPSAVEDSTATEDATA savedState;
+ RT_ZERO (savedState);
+ rc = SSMR3GetStructEx (pSSM, &savedState, sizeof(savedState), SSMSTRUCT_FLAGS_MEM_BAND_AID,
+ &s_aClipSavedStateDataFields30[0], NULL);
+ AssertRCReturn (rc, rc);
+
+ pClient->fMsgQuit = savedState.fMsgQuit;
+ pClient->fMsgReadData = savedState.fMsgReadData;
+ pClient->fMsgFormats = savedState.fMsgFormats;
+ pClient->u32RequestedFormat = savedState.u32RequestedFormat;
+ }
+ else
+ {
+ LogRel (("Client data size mismatch: got %#x\n", lenOrVer));
+ return VERR_SSM_DATA_UNIT_FORMAT_CHANGED;
+ }
+
+ /* Verify the client ID. */
+ if (pClient->u32ClientID != u32ClientIDOld)
+ {
+ LogRel (("Client ID mismatch: expected %d, got %d\n", u32ClientIDOld, pClient->u32ClientID));
+ pClient->u32ClientID = u32ClientIDOld;
+ return VERR_SSM_DATA_UNIT_FORMAT_CHANGED;
+ }
+
+ /* Actual host data are to be reported to guest (SYNC). */
+ vboxClipboardSync (pClient);
+
+#else /* UNIT_TEST*/
+ RT_NOREF(u32ClientID, pvClient, pSSM, uVersion);
+#endif /* UNIT_TEST */
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) extCallback (uint32_t u32Function, uint32_t u32Format, void *pvData, uint32_t cbData)
+{
+ RT_NOREF2(pvData, cbData);
+ if (g_pClient != NULL)
+ {
+ switch (u32Function)
+ {
+ case VBOX_CLIPBOARD_EXT_FN_FORMAT_ANNOUNCE:
+ {
+ LogRelFlow(("ANNOUNCE: g_fReadingData = %d\n", g_fReadingData));
+ if (g_fReadingData)
+ {
+ g_fDelayedAnnouncement = true;
+ g_u32DelayedFormats = u32Format;
+ }
+ else
+ {
+ vboxSvcClipboardReportMsg (g_pClient, VBOX_SHARED_CLIPBOARD_HOST_MSG_FORMATS, u32Format);
+ }
+ } break;
+
+ case VBOX_CLIPBOARD_EXT_FN_DATA_READ:
+ {
+ vboxSvcClipboardReportMsg (g_pClient, VBOX_SHARED_CLIPBOARD_HOST_MSG_READ_DATA, u32Format);
+ } break;
+
+ default:
+ return VERR_NOT_SUPPORTED;
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) svcRegisterExtension(void *, PFNHGCMSVCEXT pfnExtension, void *pvExtension)
+{
+ LogRelFlowFunc(("pfnExtension = %p\n", pfnExtension));
+
+ VBOXCLIPBOARDEXTPARMS parms;
+
+ if (pfnExtension)
+ {
+ /* Install extension. */
+ g_pfnExtension = pfnExtension;
+ g_pvExtension = pvExtension;
+
+ parms.u.pfnCallback = extCallback;
+ g_pfnExtension (g_pvExtension, VBOX_CLIPBOARD_EXT_FN_SET_CALLBACK, &parms, sizeof (parms));
+ }
+ else
+ {
+ if (g_pfnExtension)
+ {
+ parms.u.pfnCallback = NULL;
+ g_pfnExtension (g_pvExtension, VBOX_CLIPBOARD_EXT_FN_SET_CALLBACK, &parms, sizeof (parms));
+ }
+
+ /* Uninstall extension. */
+ g_pfnExtension = NULL;
+ g_pvExtension = NULL;
+ }
+
+ return VINF_SUCCESS;
+}
+
+extern "C" DECLCALLBACK(DECLEXPORT(int)) VBoxHGCMSvcLoad (VBOXHGCMSVCFNTABLE *ptable)
+{
+ int rc = VINF_SUCCESS;
+
+ LogRelFlowFunc(("ptable = %p\n", ptable));
+
+ if (!ptable)
+ {
+ rc = VERR_INVALID_PARAMETER;
+ }
+ else
+ {
+ LogRel2(("VBoxHGCMSvcLoad: ptable->cbSize = %d, ptable->u32Version = 0x%08X\n", ptable->cbSize, ptable->u32Version));
+
+ if ( ptable->cbSize != sizeof (VBOXHGCMSVCFNTABLE)
+ || ptable->u32Version != VBOX_HGCM_SVC_VERSION)
+ {
+ rc = VERR_INVALID_PARAMETER;
+ }
+ else
+ {
+ g_pHelpers = ptable->pHelpers;
+
+ ptable->cbClient = sizeof (VBOXCLIPBOARDCLIENTDATA);
+
+ ptable->pfnUnload = svcUnload;
+ ptable->pfnConnect = svcConnect;
+ ptable->pfnDisconnect = svcDisconnect;
+ ptable->pfnCall = svcCall;
+ ptable->pfnHostCall = svcHostCall;
+ ptable->pfnSaveState = svcSaveState;
+ ptable->pfnLoadState = svcLoadState;
+ ptable->pfnRegisterExtension = svcRegisterExtension;
+ ptable->pfnNotify = NULL;
+ ptable->pvService = NULL;
+
+ /* Service specific initialization. */
+ rc = svcInit ();
+ }
+ }
+
+ return rc;
+}