diff options
Diffstat (limited to 'src/VBox/HostServices/DragAndDrop')
-rw-r--r-- | src/VBox/HostServices/DragAndDrop/Makefile.kmk | 57 | ||||
-rw-r--r-- | src/VBox/HostServices/DragAndDrop/VBoxDragAndDropSvc.cpp | 1219 | ||||
-rw-r--r-- | src/VBox/HostServices/DragAndDrop/VBoxDragAndDropSvc.rc | 51 | ||||
-rw-r--r-- | src/VBox/HostServices/DragAndDrop/dndmanager.cpp | 206 | ||||
-rw-r--r-- | src/VBox/HostServices/DragAndDrop/dndmanager.h | 113 |
5 files changed, 1646 insertions, 0 deletions
diff --git a/src/VBox/HostServices/DragAndDrop/Makefile.kmk b/src/VBox/HostServices/DragAndDrop/Makefile.kmk new file mode 100644 index 00000000..2889e61f --- /dev/null +++ b/src/VBox/HostServices/DragAndDrop/Makefile.kmk @@ -0,0 +1,57 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the Guest Control Host Service. +# + +# +# Copyright (C) 2011-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. +# + +SUB_DEPTH = ../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# Include sub-makefile(s). +# include $(PATH_SUB_CURRENT)/testcase/Makefile.kmk + +# +# The drag and drop service DLL. +# +DLLS += VBoxDragAndDropSvc +VBoxDragAndDropSvc_TEMPLATE = VBOXR3 +VBoxDragAndDropSvc_NAME.os2 = VBoxDnD +VBoxDragAndDropSvc_DEFS = \ + VBOX_WITH_HGCM \ + $(if $(VBOX_WITH_DRAG_AND_DROP_GH),VBOX_WITH_DRAG_AND_DROP_GH,) +VBoxDragAndDropSvc_INCS = $(PATH_ROOT)/src/VBox/Main/include ./ +VBoxDragAndDropSvc_INCS.win = \ + $(VBOX_PATH_SDK) + +VBoxDragAndDropSvc_SOURCES = \ + VBoxDragAndDropSvc.cpp \ + dndmanager.cpp + +VBoxDragAndDropSvc_SOURCES += \ + ../common/client.cpp \ + ../common/message.cpp + +VBoxDragAndDropSvc_SOURCES.win = \ + VBoxDragAndDropSvc.rc + +VBoxDragAndDropSvc_LIBS = \ + $(LIB_VMM) \ + $(LIB_RUNTIME) \ + $(LIB_REM) \ + $(PATH_STAGE_LIB)/VBoxDnDHostR3Lib$(VBOX_SUFF_LIB) + +VBoxDragAndDropSvc_LDFLAGS.darwin = \ + -install_name $(VBOX_DYLD_EXECUTABLE_PATH)/VBoxDragAndDropSvc.dylib + +include $(FILE_KBUILD_SUB_FOOTER) diff --git a/src/VBox/HostServices/DragAndDrop/VBoxDragAndDropSvc.cpp b/src/VBox/HostServices/DragAndDrop/VBoxDragAndDropSvc.cpp new file mode 100644 index 00000000..6a46b0f6 --- /dev/null +++ b/src/VBox/HostServices/DragAndDrop/VBoxDragAndDropSvc.cpp @@ -0,0 +1,1219 @@ +/* $Id: VBoxDragAndDropSvc.cpp $ */ +/** @file + * Drag and Drop Service. + */ + +/* + * Copyright (C) 2011-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_svc_guest_control Drag and drop HGCM Service + * + * TODO + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_GUEST_DND +#include <VBox/GuestHost/DragAndDrop.h> +#include <VBox/GuestHost/DragAndDropDefs.h> +#include <VBox/HostServices/Service.h> +#include <VBox/HostServices/DragAndDropSvc.h> + +#include <VBox/err.h> + +#include <algorithm> +#include <list> +#include <map> + +#include "dndmanager.h" + +using namespace DragAndDropSvc; + + +/********************************************************************************************************************************* +* Service class declaration * +*********************************************************************************************************************************/ + +class DragAndDropClient : public HGCM::Client +{ +public: + + DragAndDropClient(uint32_t uClientID) + : HGCM::Client(uClientID) + { + RT_ZERO(m_SvcCtx); + } + + virtual ~DragAndDropClient(void) + { + disconnect(); + } + +public: + + void disconnect(void); +}; + +/** Map holding pointers to drag and drop clients. Key is the (unique) HGCM client ID. */ +typedef std::map<uint32_t, DragAndDropClient*> DnDClientMap; + +/** Simple queue (list) which holds deferred (waiting) clients. */ +typedef std::list<uint32_t> DnDClientQueue; + +/** + * Specialized drag & drop service class. + */ +class DragAndDropService : public HGCM::AbstractService<DragAndDropService> +{ +public: + + explicit DragAndDropService(PVBOXHGCMSVCHELPERS pHelpers) + : HGCM::AbstractService<DragAndDropService>(pHelpers) + , m_pManager(NULL) {} + +protected: + + int init(VBOXHGCMSVCFNTABLE *pTable); + int uninit(void); + int clientConnect(uint32_t u32ClientID, void *pvClient); + int clientDisconnect(uint32_t u32ClientID, void *pvClient); + void guestCall(VBOXHGCMCALLHANDLE callHandle, uint32_t u32ClientID, void *pvClient, uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM paParms[]); + int hostCall(uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM paParms[]); + + int modeSet(uint32_t u32Mode); + inline uint32_t modeGet(void) const { return m_u32Mode; }; + +protected: + + static DECLCALLBACK(int) progressCallback(uint32_t uStatus, uint32_t uPercentage, int rc, void *pvUser); + +protected: + + /** Pointer to our DnD manager instance. */ + DnDManager *m_pManager; + /** Map of all connected clients. + * The primary key is the (unique) client ID, the secondary value + * an allocated pointer to the DragAndDropClient class, managed + * by this service class. */ + DnDClientMap m_clientMap; + /** List of all clients which are queued up (deferred return) and ready + * to process new commands. The key is the (unique) client ID. */ + DnDClientQueue m_clientQueue; + /** Current drag and drop mode. */ + uint32_t m_u32Mode; +}; + + +/********************************************************************************************************************************* +* Client implementation * +*********************************************************************************************************************************/ + +/** + * Called when the HGCM client disconnected on the guest side. + * This function takes care of the client's data cleanup and also lets the host + * know that the client has been disconnected. + * + */ +void DragAndDropClient::disconnect(void) +{ + LogFlowThisFunc(("uClient=%RU32\n", m_uClientID)); + + if (IsDeferred()) + CompleteDeferred(VERR_INTERRUPTED); + + /* + * Let the host know. + */ + VBOXDNDCBDISCONNECTMSGDATA data; + RT_ZERO(data); + /** @todo Magic needed? */ + /** @todo Add context ID. */ + + if (m_SvcCtx.pfnHostCallback) + { + int rc2 = m_SvcCtx.pfnHostCallback(m_SvcCtx.pvHostData, GUEST_DND_DISCONNECT, &data, sizeof(data)); + if (RT_FAILURE(rc2)) + LogFlowFunc(("Warning: Unable to notify host about client %RU32 disconnect, rc=%Rrc\n", m_uClientID, rc2)); + /* Not fatal. */ + } +} + + +/********************************************************************************************************************************* +* Service class implementation * +*********************************************************************************************************************************/ + +int DragAndDropService::init(VBOXHGCMSVCFNTABLE *pTable) +{ + /* Register functions. */ + pTable->pfnHostCall = svcHostCall; + pTable->pfnSaveState = NULL; /* The service is stateless, so the normal */ + pTable->pfnLoadState = NULL; /* construction done before restoring suffices */ + pTable->pfnRegisterExtension = svcRegisterExtension; + pTable->pfnNotify = NULL; + + /* Drag'n drop mode is disabled by default. */ + modeSet(VBOX_DRAG_AND_DROP_MODE_OFF); + + int rc = VINF_SUCCESS; + + try + { + m_pManager = new DnDManager(&DragAndDropService::progressCallback, this); + } + catch(std::bad_alloc &) + { + rc = VERR_NO_MEMORY; + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +int DragAndDropService::uninit(void) +{ + LogFlowFuncEnter(); + + if (m_pManager) + { + delete m_pManager; + m_pManager = NULL; + } + + DnDClientMap::iterator itClient = m_clientMap.begin(); + while (itClient != m_clientMap.end()) + { + delete itClient->second; + m_clientMap.erase(itClient); + itClient = m_clientMap.begin(); + } + + LogFlowFuncLeave(); + return VINF_SUCCESS; +} + +int DragAndDropService::clientConnect(uint32_t u32ClientID, void *pvClient) +{ + RT_NOREF1(pvClient); + if (m_clientMap.size() >= UINT8_MAX) /* Don't allow too much clients at the same time. */ + { + AssertMsgFailed(("Maximum number of clients reached\n")); + return VERR_MAX_PROCS_REACHED; + } + + int rc = VINF_SUCCESS; + + /* + * Add client to our client map. + */ + if (m_clientMap.find(u32ClientID) != m_clientMap.end()) + rc = VERR_ALREADY_EXISTS; + + if (RT_SUCCESS(rc)) + { + try + { + DragAndDropClient *pClient = new DragAndDropClient(u32ClientID); + pClient->SetSvcContext(m_SvcCtx); + m_clientMap[u32ClientID] = pClient; + } + catch(std::bad_alloc &) + { + rc = VERR_NO_MEMORY; + } + + if (RT_SUCCESS(rc)) + { + /* + * Reset the message queue as soon as a new clients connect + * to ensure that every client has the same state. + */ + if (m_pManager) + m_pManager->Reset(); + } + } + + LogFlowFunc(("Client %RU32 connected, rc=%Rrc\n", u32ClientID, rc)); + return rc; +} + +int DragAndDropService::clientDisconnect(uint32_t u32ClientID, void *pvClient) +{ + RT_NOREF1(pvClient); + + /* Client not found? Bail out early. */ + DnDClientMap::iterator itClient = m_clientMap.find(u32ClientID); + if (itClient == m_clientMap.end()) + return VERR_NOT_FOUND; + + /* + * Remove from waiters queue. + */ + m_clientQueue.remove(u32ClientID); + + /* + * Remove from client map and deallocate. + */ + AssertPtr(itClient->second); + delete itClient->second; + + m_clientMap.erase(itClient); + + LogFlowFunc(("Client %RU32 disconnected\n", u32ClientID)); + return VINF_SUCCESS; +} + +int DragAndDropService::modeSet(uint32_t u32Mode) +{ +#ifndef VBOX_WITH_DRAG_AND_DROP_GH + if ( u32Mode == VBOX_DRAG_AND_DROP_MODE_GUEST_TO_HOST + || u32Mode == VBOX_DRAG_AND_DROP_MODE_BIDIRECTIONAL) + { + m_u32Mode = VBOX_DRAG_AND_DROP_MODE_OFF; + return VERR_NOT_SUPPORTED; + } +#endif + + switch (u32Mode) + { + case VBOX_DRAG_AND_DROP_MODE_OFF: + case VBOX_DRAG_AND_DROP_MODE_HOST_TO_GUEST: + case VBOX_DRAG_AND_DROP_MODE_GUEST_TO_HOST: + case VBOX_DRAG_AND_DROP_MODE_BIDIRECTIONAL: + m_u32Mode = u32Mode; + break; + + default: + m_u32Mode = VBOX_DRAG_AND_DROP_MODE_OFF; + break; + } + + return VINF_SUCCESS; +} + +void DragAndDropService::guestCall(VBOXHGCMCALLHANDLE callHandle, uint32_t u32ClientID, + void *pvClient, uint32_t u32Function, + uint32_t cParms, VBOXHGCMSVCPARM paParms[]) +{ + RT_NOREF1(pvClient); + LogFlowFunc(("u32ClientID=%RU32, u32Function=%RU32, cParms=%RU32\n", + u32ClientID, u32Function, cParms)); + + /* Check if we've the right mode set. */ + int rc = VERR_ACCESS_DENIED; /* Play safe. */ + switch (u32Function) + { + case GUEST_DND_GET_NEXT_HOST_MSG: + { + if (modeGet() != VBOX_DRAG_AND_DROP_MODE_OFF) + { + rc = VINF_SUCCESS; + } + else + { + LogFlowFunc(("DnD disabled, deferring request\n")); + rc = VINF_HGCM_ASYNC_EXECUTE; + } + break; + } + + /* New since protocol v2. */ + case GUEST_DND_CONNECT: + { + /* + * Never block the initial connect call, as the clients do this when + * initializing and might get stuck if drag and drop is set to "disabled" at + * that time. + */ + rc = VINF_SUCCESS; + break; + } + case GUEST_DND_HG_ACK_OP: + case GUEST_DND_HG_REQ_DATA: + case GUEST_DND_HG_EVT_PROGRESS: + { + if ( modeGet() == VBOX_DRAG_AND_DROP_MODE_BIDIRECTIONAL + || modeGet() == VBOX_DRAG_AND_DROP_MODE_HOST_TO_GUEST) + { + rc = VINF_SUCCESS; + } + else + LogFlowFunc(("Host -> Guest DnD mode disabled, ignoring request\n")); + break; + } + + case GUEST_DND_GH_ACK_PENDING: + case GUEST_DND_GH_SND_DATA_HDR: + case GUEST_DND_GH_SND_DATA: + case GUEST_DND_GH_SND_DIR: + case GUEST_DND_GH_SND_FILE_HDR: + case GUEST_DND_GH_SND_FILE_DATA: + case GUEST_DND_GH_EVT_ERROR: + { +#ifdef VBOX_WITH_DRAG_AND_DROP_GH + if ( modeGet() == VBOX_DRAG_AND_DROP_MODE_BIDIRECTIONAL + || modeGet() == VBOX_DRAG_AND_DROP_MODE_GUEST_TO_HOST) + { + rc = VINF_SUCCESS; + } + else +#endif + LogFlowFunc(("Guest -> Host DnD mode disabled, ignoring request\n")); + break; + } + + default: + /* Reach through to DnD manager. */ + rc = VINF_SUCCESS; + break; + } + +#ifdef DEBUG_andy + LogFlowFunc(("Mode (%RU32) check rc=%Rrc\n", modeGet(), rc)); +#endif + +#define DO_HOST_CALLBACK(); \ + if ( RT_SUCCESS(rc) \ + && m_SvcCtx.pfnHostCallback) \ + { \ + rc = m_SvcCtx.pfnHostCallback(m_SvcCtx.pvHostData, u32Function, &data, sizeof(data)); \ + } + + /* + * Lookup client. + */ + DragAndDropClient *pClient = NULL; + + DnDClientMap::iterator itClient = m_clientMap.find(u32ClientID); + if (itClient != m_clientMap.end()) + { + pClient = itClient->second; + AssertPtr(pClient); + } + else + { + LogFunc(("Client %RU32 was not found\n", u32ClientID)); + rc = VERR_NOT_FOUND; + } + + if (rc == VINF_SUCCESS) /* Note: rc might be VINF_HGCM_ASYNC_EXECUTE! */ + { + LogFlowFunc(("Client %RU32: Protocol v%RU32\n", pClient->GetClientID(), pClient->GetProtocolVer())); + + rc = VERR_INVALID_PARAMETER; /* Play safe. */ + + switch (u32Function) + { + /* + * Note: Older VBox versions with enabled DnD guest->host support (< 5.0) + * used the same message ID (300) for GUEST_DND_GET_NEXT_HOST_MSG and + * HOST_DND_GH_REQ_PENDING, which led this service returning + * VERR_INVALID_PARAMETER when the guest wanted to actually + * handle HOST_DND_GH_REQ_PENDING. + */ + case GUEST_DND_GET_NEXT_HOST_MSG: + { + LogFlowFunc(("GUEST_DND_GET_NEXT_HOST_MSG\n")); + if (cParms == 3) + { + rc = m_pManager->GetNextMsgInfo(&paParms[0].u.uint32 /* uMsg */, &paParms[1].u.uint32 /* cParms */); + if (RT_FAILURE(rc)) /* No queued messages available? */ + { + if (m_SvcCtx.pfnHostCallback) /* Try asking the host. */ + { + VBOXDNDCBHGGETNEXTHOSTMSG data; + RT_ZERO(data); + data.hdr.uMagic = CB_MAGIC_DND_HG_GET_NEXT_HOST_MSG; + rc = m_SvcCtx.pfnHostCallback(m_SvcCtx.pvHostData, u32Function, &data, sizeof(data)); + if (RT_SUCCESS(rc)) + { + paParms[0].u.uint32 = data.uMsg; /* uMsg */ + paParms[1].u.uint32 = data.cParms; /* cParms */ + /* Note: paParms[2] was set by the guest as blocking flag. */ + } + } + else /* No host callback in place, so drag and drop is not supported by the host. */ + rc = VERR_NOT_SUPPORTED; + + if (RT_FAILURE(rc)) + rc = m_pManager->GetNextMsg(u32Function, cParms, paParms); + + /* Some error occurred or no (new) messages available? */ + if (RT_FAILURE(rc)) + { + uint32_t fFlags = 0; + int rc2 = HGCMSvcGetU32(&paParms[2], &fFlags); + if ( RT_SUCCESS(rc2) + && fFlags) /* Blocking flag set? */ + { + /* Defer client returning. */ + rc = VINF_HGCM_ASYNC_EXECUTE; + } + else + rc = VERR_INVALID_PARAMETER; + + LogFlowFunc(("Message queue is empty, returning %Rrc to guest\n", rc)); + } + } + } + break; + } + case GUEST_DND_CONNECT: + { + LogFlowFunc(("GUEST_DND_CONNECT\n")); + if (cParms >= 2) + { + const uint8_t idxProto = cParms >= 3 ? 1 : 0; + + VBOXDNDCBCONNECTMSGDATA data; + RT_ZERO(data); + data.hdr.uMagic = CB_MAGIC_DND_CONNECT; + if (cParms >= 3) + rc = HGCMSvcGetU32(&paParms[0], &data.hdr.uContextID); + else /* Older protocols don't have a context ID. */ + rc = VINF_SUCCESS; + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU32(&paParms[idxProto], &data.uProtocol); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU32(&paParms[idxProto + 1], &data.uFlags); + if (RT_SUCCESS(rc)) + pClient->SetProtocolVer(data.uProtocol); + if (RT_SUCCESS(rc)) + { + LogFlowFunc(("Client %RU32 is now using protocol v%RU32\n", pClient->GetClientID(), pClient->GetProtocolVer())); + DO_HOST_CALLBACK(); + } + } + break; + } + case GUEST_DND_HG_ACK_OP: + { + LogFlowFunc(("GUEST_DND_HG_ACK_OP\n")); + + VBOXDNDCBHGACKOPDATA data; + RT_ZERO(data); + data.hdr.uMagic = CB_MAGIC_DND_HG_ACK_OP; + + switch (pClient->GetProtocolVer()) + { + case 3: + { + if (cParms == 2) + { + rc = HGCMSvcGetU32(&paParms[0], &data.hdr.uContextID); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU32(&paParms[1], &data.uAction); /* Get drop action. */ + } + break; + } + + case 2: + default: + { + if (cParms == 1) + rc = HGCMSvcGetU32(&paParms[0], &data.uAction); /* Get drop action. */ + break; + } + } + + DO_HOST_CALLBACK(); + break; + } + case GUEST_DND_HG_REQ_DATA: + { + LogFlowFunc(("GUEST_DND_HG_REQ_DATA\n")); + + VBOXDNDCBHGREQDATADATA data; + RT_ZERO(data); + data.hdr.uMagic = CB_MAGIC_DND_HG_REQ_DATA; + + switch (pClient->GetProtocolVer()) + { + case 3: + { + if (cParms == 3) + { + rc = HGCMSvcGetU32(&paParms[0], &data.hdr.uContextID); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetPv(&paParms[1], (void **)&data.pszFormat, &data.cbFormat); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU32(&paParms[2], &data.cbFormat); + } + break; + } + + case 2: + default: + { + if (cParms == 1) + rc = HGCMSvcGetPv(&paParms[0], (void**)&data.pszFormat, &data.cbFormat); + break; + } + } + + DO_HOST_CALLBACK(); + break; + } + case GUEST_DND_HG_EVT_PROGRESS: + { + LogFlowFunc(("GUEST_DND_HG_EVT_PROGRESS\n")); + + VBOXDNDCBHGEVTPROGRESSDATA data; + RT_ZERO(data); + data.hdr.uMagic = CB_MAGIC_DND_HG_EVT_PROGRESS; + + switch (pClient->GetProtocolVer()) + { + case 3: + { + if (cParms == 4) + { + rc = HGCMSvcGetU32(&paParms[0], &data.uStatus); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU32(&paParms[1], &data.uStatus); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU32(&paParms[2], &data.uPercentage); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU32(&paParms[3], &data.rc); + } + break; + } + + case 2: + default: + { + if (cParms == 3) + { + rc = HGCMSvcGetU32(&paParms[0], &data.uStatus); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU32(&paParms[1], &data.uPercentage); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU32(&paParms[2], &data.rc); + } + break; + } + } + + DO_HOST_CALLBACK(); + break; + } +#ifdef VBOX_WITH_DRAG_AND_DROP_GH + case GUEST_DND_GH_ACK_PENDING: + { + LogFlowFunc(("GUEST_DND_GH_ACK_PENDING\n")); + + VBOXDNDCBGHACKPENDINGDATA data; + RT_ZERO(data); + data.hdr.uMagic = CB_MAGIC_DND_GH_ACK_PENDING; + + switch (pClient->GetProtocolVer()) + { + case 3: + { + if (cParms == 5) + { + rc = HGCMSvcGetU32(&paParms[0], &data.hdr.uContextID); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU32(&paParms[1], &data.uDefAction); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU32(&paParms[2], &data.uAllActions); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetPv(&paParms[3], (void**)&data.pszFormat, &data.cbFormat); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU32(&paParms[4], &data.cbFormat); + } + break; + } + + case 2: + default: + { + if (cParms == 3) + { + rc = HGCMSvcGetU32(&paParms[0], &data.uDefAction); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU32(&paParms[1], &data.uAllActions); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetPv(&paParms[2], (void**)&data.pszFormat, &data.cbFormat); + } + break; + } + } + + DO_HOST_CALLBACK(); + break; + } + /* New since protocol v3. */ + case GUEST_DND_GH_SND_DATA_HDR: + { + LogFlowFunc(("GUEST_DND_GH_SND_DATA_HDR\n")); + if (cParms == 12) + { + VBOXDNDCBSNDDATAHDRDATA data; + RT_ZERO(data); + data.hdr.uMagic = CB_MAGIC_DND_GH_SND_DATA_HDR; + rc = HGCMSvcGetU32(&paParms[0], &data.hdr.uContextID); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU32(&paParms[1], &data.data.uFlags); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU32(&paParms[2], &data.data.uScreenId); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU64(&paParms[3], &data.data.cbTotal); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU32(&paParms[4], &data.data.cbMeta); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetPv(&paParms[5], &data.data.pvMetaFmt, &data.data.cbMetaFmt); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU32(&paParms[6], &data.data.cbMetaFmt); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU64(&paParms[7], &data.data.cObjects); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU32(&paParms[8], &data.data.enmCompression); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU32(&paParms[9], (uint32_t *)&data.data.enmChecksumType); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetPv(&paParms[10], &data.data.pvChecksum, &data.data.cbChecksum); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU32(&paParms[11], &data.data.cbChecksum); + + LogFlowFunc(("fFlags=0x%x, cbTotalSize=%RU64, cObj=%RU64\n", + data.data.uFlags, data.data.cbTotal, data.data.cObjects)); + DO_HOST_CALLBACK(); + } + break; + } + case GUEST_DND_GH_SND_DATA: + { + LogFlowFunc(("GUEST_DND_GH_SND_DATA\n")); + switch (pClient->GetProtocolVer()) + { + case 3: + { + if (cParms == 5) + { + VBOXDNDCBSNDDATADATA data; + RT_ZERO(data); + data.hdr.uMagic = CB_MAGIC_DND_GH_SND_DATA; + rc = HGCMSvcGetU32(&paParms[0], &data.hdr.uContextID); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetPv(&paParms[1], (void**)&data.data.u.v3.pvData, &data.data.u.v3.cbData); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU32(&paParms[2], &data.data.u.v3.cbData); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetPv(&paParms[3], (void**)&data.data.u.v3.pvChecksum, &data.data.u.v3.cbChecksum); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU32(&paParms[4], &data.data.u.v3.cbChecksum); + DO_HOST_CALLBACK(); + } + break; + } + + case 2: + default: + { + if (cParms == 2) + { + VBOXDNDCBSNDDATADATA data; + RT_ZERO(data); + data.hdr.uMagic = CB_MAGIC_DND_GH_SND_DATA; + rc = HGCMSvcGetPv(&paParms[0], (void**)&data.data.u.v1.pvData, &data.data.u.v1.cbData); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU32(&paParms[1], &data.data.u.v1.cbTotalSize); + DO_HOST_CALLBACK(); + } + break; + } + } + break; + } + case GUEST_DND_GH_SND_DIR: + { + LogFlowFunc(("GUEST_DND_GH_SND_DIR\n")); + + VBOXDNDCBSNDDIRDATA data; + RT_ZERO(data); + data.hdr.uMagic = CB_MAGIC_DND_GH_SND_DIR; + + switch (pClient->GetProtocolVer()) + { + case 3: + { + if (cParms == 4) + { + rc = HGCMSvcGetU32(&paParms[0], &data.hdr.uContextID); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetPv(&paParms[1], (void**)&data.pszPath, &data.cbPath); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU32(&paParms[2], &data.cbPath); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU32(&paParms[3], &data.fMode); + } + break; + } + + case 2: + default: + { + if (cParms == 3) + { + rc = HGCMSvcGetPv(&paParms[0], (void**)&data.pszPath, &data.cbPath); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU32(&paParms[1], &data.cbPath); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU32(&paParms[2], &data.fMode); + } + break; + } + } + + DO_HOST_CALLBACK(); + break; + } + /* New since protocol v2 (>= VBox 5.0). */ + case GUEST_DND_GH_SND_FILE_HDR: + { + LogFlowFunc(("GUEST_DND_GH_SND_FILE_HDR\n")); + if (cParms == 6) + { + VBOXDNDCBSNDFILEHDRDATA data; + RT_ZERO(data); + data.hdr.uMagic = CB_MAGIC_DND_GH_SND_FILE_HDR; + + rc = HGCMSvcGetU32(&paParms[0], &data.hdr.uContextID); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetPv(&paParms[1], (void**)&data.pszFilePath, &data.cbFilePath); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU32(&paParms[2], &data.cbFilePath); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU32(&paParms[3], &data.fFlags); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU32(&paParms[4], &data.fMode); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU64(&paParms[5], &data.cbSize); + + LogFlowFunc(("pszPath=%s, cbPath=%RU32, fMode=0x%x, cbSize=%RU64\n", + data.pszFilePath, data.cbFilePath, data.fMode, data.cbSize)); + DO_HOST_CALLBACK(); + } + break; + } + case GUEST_DND_GH_SND_FILE_DATA: + { + LogFlowFunc(("GUEST_DND_GH_SND_FILE_DATA\n")); + + switch (pClient->GetProtocolVer()) + { + /* Protocol v3 adds (optional) checksums. */ + case 3: + { + if (cParms == 5) + { + VBOXDNDCBSNDFILEDATADATA data; + RT_ZERO(data); + data.hdr.uMagic = CB_MAGIC_DND_GH_SND_FILE_DATA; + + rc = HGCMSvcGetU32(&paParms[0], &data.hdr.uContextID); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetPv(&paParms[1], (void**)&data.pvData, &data.cbData); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU32(&paParms[2], &data.cbData); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetPv(&paParms[3], (void**)&data.u.v3.pvChecksum, &data.u.v3.cbChecksum); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU32(&paParms[4], &data.u.v3.cbChecksum); + + LogFlowFunc(("pvData=0x%p, cbData=%RU32\n", data.pvData, data.cbData)); + DO_HOST_CALLBACK(); + } + break; + } + /* Protocol v2 only sends the next data chunks to reduce traffic. */ + case 2: + { + if (cParms == 3) + { + VBOXDNDCBSNDFILEDATADATA data; + RT_ZERO(data); + data.hdr.uMagic = CB_MAGIC_DND_GH_SND_FILE_DATA; + rc = HGCMSvcGetU32(&paParms[0], &data.hdr.uContextID); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetPv(&paParms[1], (void**)&data.pvData, &data.cbData); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU32(&paParms[2], &data.cbData); + + LogFlowFunc(("cbData=%RU32, pvData=0x%p\n", data.cbData, data.pvData)); + DO_HOST_CALLBACK(); + } + break; + } + /* Protocol v1 sends the file path and attributes for every file chunk (!). */ + default: + { + if (cParms == 5) + { + VBOXDNDCBSNDFILEDATADATA data; + RT_ZERO(data); + data.hdr.uMagic = CB_MAGIC_DND_GH_SND_FILE_DATA; + uint32_t cTmp; + rc = HGCMSvcGetPv(&paParms[0], (void**)&data.u.v1.pszFilePath, &cTmp); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU32(&paParms[1], &data.u.v1.cbFilePath); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetPv(&paParms[2], (void**)&data.pvData, &cTmp); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU32(&paParms[3], &data.cbData); + if (RT_SUCCESS(rc)) + rc = HGCMSvcGetU32(&paParms[4], &data.u.v1.fMode); + + LogFlowFunc(("pszFilePath=%s, cbData=%RU32, pvData=0x%p, fMode=0x%x\n", + data.u.v1.pszFilePath, data.cbData, data.pvData, data.u.v1.fMode)); + DO_HOST_CALLBACK(); + } + break; + } + } + break; + } + case GUEST_DND_GH_EVT_ERROR: + { + LogFlowFunc(("GUEST_DND_GH_EVT_ERROR\n")); + + VBOXDNDCBEVTERRORDATA data; + RT_ZERO(data); + data.hdr.uMagic = CB_MAGIC_DND_GH_EVT_ERROR; + + switch (pClient->GetProtocolVer()) + { + case 3: + { + if (cParms == 2) + { + rc = HGCMSvcGetU32(&paParms[0], &data.hdr.uContextID); + if (RT_SUCCESS(rc)) + { + uint32_t rcOp; + rc = HGCMSvcGetU32(&paParms[1], &rcOp); + if (RT_SUCCESS(rc)) + data.rc = rcOp; + } + } + break; + } + + case 2: + default: + { + if (cParms == 1) + { + uint32_t rcOp; + rc = HGCMSvcGetU32(&paParms[0], &rcOp); + if (RT_SUCCESS(rc)) + data.rc = (int32_t)rcOp; + } + break; + } + } + + DO_HOST_CALLBACK(); + break; + } +#endif /* VBOX_WITH_DRAG_AND_DROP_GH */ + + default: + { + /* All other messages are handled by the DnD manager. */ + rc = m_pManager->GetNextMsg(u32Function, cParms, paParms); + if (rc == VERR_NO_DATA) /* Manager has no new messsages? Try asking the host. */ + { + if (m_SvcCtx.pfnHostCallback) + { + VBOXDNDCBHGGETNEXTHOSTMSGDATA data; + RT_ZERO(data); + + data.hdr.uMagic = VBOX_DND_CB_MAGIC_MAKE(0 /* uFn */, 0 /* uVer */); + + data.uMsg = u32Function; + data.cParms = cParms; + data.paParms = paParms; + + rc = m_SvcCtx.pfnHostCallback(m_SvcCtx.pvHostData, u32Function, + &data, sizeof(data)); + if (RT_SUCCESS(rc)) + { + cParms = data.cParms; + paParms = data.paParms; + } + else + { + /* + * In case the guest is too fast asking for the next message + * and the host did not supply it yet, just defer the client's + * return until a response from the host available. + */ + LogFlowFunc(("No new messages from the host (yet), deferring request: %Rrc\n", rc)); + rc = VINF_HGCM_ASYNC_EXECUTE; + } + } + else /* No host callback in place, so drag and drop is not supported by the host. */ + rc = VERR_NOT_SUPPORTED; + } + break; + } + } + } + + /* + * If async execution is requested, we didn't notify the guest yet about + * completion. The client is queued into the waiters list and will be + * notified as soon as a new event is available. + */ + if (rc == VINF_HGCM_ASYNC_EXECUTE) + { + try + { + AssertPtr(pClient); + pClient->SetDeferred(callHandle, u32Function, cParms, paParms); + m_clientQueue.push_back(u32ClientID); + } + catch (std::bad_alloc &) + { + rc = VERR_NO_MEMORY; + /* Don't report to guest. */ + } + } + else if (pClient) + pClient->Complete(callHandle, rc); + else + { + AssertMsgFailed(("Guest call failed with %Rrc\n", rc)); + rc = VERR_NOT_IMPLEMENTED; + } + + LogFlowFunc(("Returning rc=%Rrc\n", rc)); +} + +int DragAndDropService::hostCall(uint32_t u32Function, + uint32_t cParms, VBOXHGCMSVCPARM paParms[]) +{ + LogFlowFunc(("u32Function=%RU32, cParms=%RU32, cClients=%zu, cQueue=%zu\n", + u32Function, cParms, m_clientMap.size(), m_clientQueue.size())); + + int rc; + + do + { + bool fSendToGuest = false; /* Whether to send the message down to the guest side or not. */ + + switch (u32Function) + { + case HOST_DND_SET_MODE: + { + if (cParms != 1) + rc = VERR_INVALID_PARAMETER; + else if (paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT) + rc = VERR_INVALID_PARAMETER; + else + rc = modeSet(paParms[0].u.uint32); + break; + } + + case HOST_DND_CANCEL: + { + LogFlowFunc(("Cancelling all waiting clients ...\n")); + + /* Reset the message queue as the host cancelled the whole operation. */ + m_pManager->Reset(); + + rc = m_pManager->AddMsg(u32Function, cParms, paParms, true /* fAppend */); + if (RT_FAILURE(rc)) + { + AssertMsgFailed(("Adding new message of type=%RU32 failed with rc=%Rrc\n", u32Function, rc)); + break; + } + + /* + * Wake up all deferred clients and tell them to process + * the cancelling message next. + */ + DnDClientQueue::iterator itQueue = m_clientQueue.begin(); + while (itQueue != m_clientQueue.end()) + { + DnDClientMap::iterator itClient = m_clientMap.find(*itQueue); + Assert(itClient != m_clientMap.end()); + + DragAndDropClient *pClient = itClient->second; + AssertPtr(pClient); + + int rc2 = pClient->SetDeferredMsgInfo(HOST_DND_CANCEL, + /* Protocol v3+ also contains the context ID. */ + pClient->GetProtocolVer() >= 3 ? 1 : 0); + pClient->CompleteDeferred(rc2); + + m_clientQueue.erase(itQueue); + itQueue = m_clientQueue.begin(); + } + + Assert(m_clientQueue.empty()); + + /* Tell the host that everything went well. */ + rc = VINF_SUCCESS; + break; + } + + case HOST_DND_HG_EVT_ENTER: + { + /* Reset the message queue as a new DnD operation just began. */ + m_pManager->Reset(); + + fSendToGuest = true; + rc = VINF_SUCCESS; + break; + } + + default: + { + fSendToGuest = true; + rc = VINF_SUCCESS; + break; + } + } + + if (fSendToGuest) + { + if (modeGet() == VBOX_DRAG_AND_DROP_MODE_OFF) + { + /* Tell the host that a wrong drag'n drop mode is set. */ + rc = VERR_ACCESS_DENIED; + break; + } + + if (m_clientMap.empty()) /* At least one client on the guest connected? */ + { + /* + * Tell the host that the guest does not support drag'n drop. + * This might happen due to not installed Guest Additions or + * not running VBoxTray/VBoxClient. + */ + rc = VERR_NOT_SUPPORTED; + break; + } + + rc = m_pManager->AddMsg(u32Function, cParms, paParms, true /* fAppend */); + if (RT_FAILURE(rc)) + { + AssertMsgFailed(("Adding new message of type=%RU32 failed with rc=%Rrc\n", u32Function, rc)); + break; + } + + /* Any clients in our queue ready for processing the next command? */ + if (m_clientQueue.empty()) + { + LogFlowFunc(("All clients (%zu) busy -- delaying execution\n", m_clientMap.size())); + break; + } + + uint32_t uClientNext = m_clientQueue.front(); + DnDClientMap::iterator itClientNext = m_clientMap.find(uClientNext); + Assert(itClientNext != m_clientMap.end()); + + DragAndDropClient *pClient = itClientNext->second; + AssertPtr(pClient); + + /* + * Check if this was a request for getting the next host + * message. If so, return the message ID and the parameter + * count. The message itself has to be queued. + */ + uint32_t uMsgClient = pClient->GetMsgType(); + + uint32_t uMsgNext = 0; + uint32_t cParmsNext = 0; + int rcNext = m_pManager->GetNextMsgInfo(&uMsgNext, &cParmsNext); + + LogFlowFunc(("uMsgClient=%RU32, uMsgNext=%RU32, cParmsNext=%RU32, rcNext=%Rrc\n", + uMsgClient, uMsgNext, cParmsNext, rcNext)); + + if (RT_SUCCESS(rcNext)) + { + if (uMsgClient == GUEST_DND_GET_NEXT_HOST_MSG) + { + rc = pClient->SetDeferredMsgInfo(uMsgNext, cParmsNext); + + /* Note: Report the current rc back to the guest. */ + pClient->CompleteDeferred(rc); + } + /* + * Does the message the client is waiting for match the message + * next in the queue? Process it right away then. + */ + else if (uMsgClient == uMsgNext) + { + rc = m_pManager->GetNextMsg(u32Function, cParms, paParms); + + /* Note: Report the current rc back to the guest. */ + pClient->CompleteDeferred(rc); + } + else /* Should not happen; cancel the operation on the guest. */ + { + LogFunc(("Client ID=%RU32 in wrong state with uMsg=%RU32 (next message in queue: %RU32), cancelling\n", + pClient->GetClientID(), uMsgClient, uMsgNext)); + + pClient->CompleteDeferred(VERR_CANCELLED); + } + + m_clientQueue.pop_front(); + } + + } /* fSendToGuest */ + + } while (0); /* To use breaks. */ + + LogFlowFuncLeaveRC(rc); + return rc; +} + +DECLCALLBACK(int) DragAndDropService::progressCallback(uint32_t uStatus, uint32_t uPercentage, int rc, void *pvUser) +{ + AssertPtrReturn(pvUser, VERR_INVALID_POINTER); + + DragAndDropService *pSelf = static_cast<DragAndDropService *>(pvUser); + AssertPtr(pSelf); + + if (pSelf->m_SvcCtx.pfnHostCallback) + { + LogFlowFunc(("GUEST_DND_HG_EVT_PROGRESS: uStatus=%RU32, uPercentage=%RU32, rc=%Rrc\n", + uStatus, uPercentage, rc)); + + VBOXDNDCBHGEVTPROGRESSDATA data; + data.hdr.uMagic = CB_MAGIC_DND_HG_EVT_PROGRESS; + data.uPercentage = RT_MIN(uPercentage, 100); + data.uStatus = uStatus; + data.rc = rc; /** @todo uin32_t vs. int. */ + + return pSelf->m_SvcCtx.pfnHostCallback(pSelf->m_SvcCtx.pvHostData, + GUEST_DND_HG_EVT_PROGRESS, + &data, sizeof(data)); + } + + return VINF_SUCCESS; +} + +/** + * @copydoc VBOXHGCMSVCLOAD + */ +extern "C" DECLCALLBACK(DECLEXPORT(int)) VBoxHGCMSvcLoad(VBOXHGCMSVCFNTABLE *pTable) +{ + return DragAndDropService::svcLoad(pTable); +} + diff --git a/src/VBox/HostServices/DragAndDrop/VBoxDragAndDropSvc.rc b/src/VBox/HostServices/DragAndDrop/VBoxDragAndDropSvc.rc new file mode 100644 index 00000000..f4059f39 --- /dev/null +++ b/src/VBox/HostServices/DragAndDrop/VBoxDragAndDropSvc.rc @@ -0,0 +1,51 @@ +/* $Id: VBoxDragAndDropSvc.rc $ */ +/** @file + * VBoxDragAndDropSvc - Resource file containing version info and icon. + */ + +/* + * Copyright (C) 2015-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. + */ + +#include <windows.h> +#include <VBox/version.h> + +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VBOX_RC_FILE_VERSION + PRODUCTVERSION VBOX_RC_FILE_VERSION + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK + FILEFLAGS VBOX_RC_FILE_FLAGS + FILEOS VBOX_RC_FILE_OS + FILETYPE VBOX_RC_TYPE_DLL + FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" // Lang=US English, CharSet=Unicode + BEGIN + VALUE "FileDescription", "VirtualBox Drag and Drop Host Service\0" + VALUE "InternalName", "VBoxDragAndDropSvc\0" + VALUE "OriginalFilename", "VBoxDragAndDropSvc.dll\0" + VALUE "CompanyName", VBOX_RC_COMPANY_NAME + VALUE "FileVersion", VBOX_RC_FILE_VERSION_STR + VALUE "LegalCopyright", VBOX_RC_LEGAL_COPYRIGHT + VALUE "ProductName", VBOX_RC_PRODUCT_NAME_STR + VALUE "ProductVersion", VBOX_RC_PRODUCT_VERSION_STR + VBOX_RC_MORE_STRINGS + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END diff --git a/src/VBox/HostServices/DragAndDrop/dndmanager.cpp b/src/VBox/HostServices/DragAndDrop/dndmanager.cpp new file mode 100644 index 00000000..efc79280 --- /dev/null +++ b/src/VBox/HostServices/DragAndDrop/dndmanager.cpp @@ -0,0 +1,206 @@ +/* $Id: dndmanager.cpp $ */ +/** @file + * Drag and Drop manager: Handling of DnD messages on the host side. + */ + +/* + * Copyright (C) 2011-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. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ + +#ifdef LOG_GROUP + #undef LOG_GROUP +#endif +#define LOG_GROUP LOG_GROUP_GUEST_DND + +#include "dndmanager.h" + +#include <VBox/log.h> +#include <iprt/file.h> +#include <iprt/dir.h> +#include <iprt/path.h> +#include <iprt/uri.h> + + +/********************************************************************************************************************************* +* DnDManager * +*********************************************************************************************************************************/ + +/** + * Adds a DnD message to the manager's queue. + * + * @returns IPRT status code. + * @param pMsg Pointer to DnD message to add. The queue then owns the pointer. + * @param fAppend Whether to append or prepend the message to the queue. + */ +int DnDManager::AddMsg(DnDMessage *pMsg, bool fAppend /* = true */) +{ + AssertPtrReturn(pMsg, VERR_INVALID_POINTER); + + LogFlowFunc(("uMsg=%RU32, cParms=%RU32, fAppend=%RTbool\n", pMsg->GetType(), pMsg->GetParamCount(), fAppend)); + + if (fAppend) + m_queueMsg.append(pMsg); + else + m_queueMsg.prepend(pMsg); + + /** @todo Catch / handle OOM? */ + + return VINF_SUCCESS; +} + +/** + * Adds a DnD message to the manager's queue. + * + * @returns IPRT status code. + * @param uMsg Type (function number) of message to add. + * @param cParms Number of parameters of message to add. + * @param paParms Array of parameters of message to add. + * @param fAppend Whether to append or prepend the message to the queue. + */ +int DnDManager::AddMsg(uint32_t uMsg, uint32_t cParms, VBOXHGCMSVCPARM paParms[], bool fAppend /* = true */) +{ + int rc; + + try + { + DnDMessage *pMsg = new DnDGenericMessage(uMsg, cParms, paParms); + rc = AddMsg(pMsg, fAppend); + } + catch(std::bad_alloc &) + { + rc = VERR_NO_MEMORY; + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Retrieves information about the next message in the queue. + * + * @returns IPRT status code. VERR_NO_DATA if no next message is available. + * @param puType Where to store the message type. + * @param pcParms Where to store the message parameter count. + */ +int DnDManager::GetNextMsgInfo(uint32_t *puType, uint32_t *pcParms) +{ + AssertPtrReturn(puType, VERR_INVALID_POINTER); + AssertPtrReturn(pcParms, VERR_INVALID_POINTER); + + int rc; + + if (m_queueMsg.isEmpty()) + { + rc = VERR_NO_DATA; + } + else + { + DnDMessage *pMsg = m_queueMsg.first(); + AssertPtr(pMsg); + + *puType = pMsg->GetType(); + *pcParms = pMsg->GetParamCount(); + + rc = VINF_SUCCESS; + } + + LogFlowFunc(("Returning puMsg=%RU32, pcParms=%RU32, rc=%Rrc\n", *puType, *pcParms, rc)); + return rc; +} + +/** + * Retrieves the next queued up message and removes it from the queue on success. + * Will return VERR_NO_DATA if no next message is available. + * + * @returns IPRT status code. + * @param uMsg Message type to retrieve. + * @param cParms Number of parameters the \@a paParms array can store. + * @param paParms Where to store the message parameters. + */ +int DnDManager::GetNextMsg(uint32_t uMsg, uint32_t cParms, VBOXHGCMSVCPARM paParms[]) +{ + LogFlowFunc(("uMsg=%RU32, cParms=%RU32\n", uMsg, cParms)); + + /* Check for pending messages in our queue. */ + if (m_queueMsg.isEmpty()) + return VERR_NO_DATA; + + /* Get the current message. */ + DnDMessage *pMsg = m_queueMsg.first(); + AssertPtr(pMsg); + + m_queueMsg.removeFirst(); /* Remove the current message from the queue. */ + + /* Fetch the current message info. */ + int rc = pMsg->GetData(uMsg, cParms, paParms); + + /* + * If there was an error handling the current message or the user has canceled + * the operation, we need to cleanup all pending events and inform the progress + * callback about our exit. + */ + if (RT_FAILURE(rc)) + { + /* Clear any pending messages. */ + Reset(); + + /* Create a new cancel message to inform the guest + call + * the host whether the current transfer was canceled or aborted + * due to an error. */ + try + { + if (rc == VERR_CANCELLED) + LogFlowFunc(("Operation was cancelled\n")); + + DnDHGCancelMessage *pMsgCancel = new DnDHGCancelMessage(); + + int rc2 = AddMsg(pMsgCancel, false /* Prepend */); + AssertRC(rc2); + + if (m_pfnProgressCallback) + { + LogFlowFunc(("Notifying host about aborting operation (%Rrc) ...\n", rc)); + m_pfnProgressCallback( rc == VERR_CANCELLED + ? DragAndDropSvc::DND_PROGRESS_CANCELLED + : DragAndDropSvc::DND_PROGRESS_ERROR, + 100 /* Percent */, rc, + m_pvProgressUser); + } + } + catch(std::bad_alloc &) + { + rc = VERR_NO_MEMORY; + } + } + + LogFlowFunc(("Message processed with rc=%Rrc\n", rc)); + return rc; +} + +/** + * Resets the manager by clearing the message queue and internal state. + */ +void DnDManager::Reset(void) +{ + LogFlowFuncEnter(); + + while (!m_queueMsg.isEmpty()) + { + delete m_queueMsg.last(); + m_queueMsg.removeLast(); + } +} + diff --git a/src/VBox/HostServices/DragAndDrop/dndmanager.h b/src/VBox/HostServices/DragAndDrop/dndmanager.h new file mode 100644 index 00000000..be5986e6 --- /dev/null +++ b/src/VBox/HostServices/DragAndDrop/dndmanager.h @@ -0,0 +1,113 @@ +/** @file + * Drag and Drop manager. + */ + +/* + * Copyright (C) 2011-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. + */ + +#ifndef VBOX_INCLUDED_SRC_DragAndDrop_dndmanager_h +#define VBOX_INCLUDED_SRC_DragAndDrop_dndmanager_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <VBox/GuestHost/DragAndDrop.h> +#include <VBox/HostServices/Service.h> +#include <VBox/HostServices/DragAndDropSvc.h> + +#include <iprt/cpp/ministring.h> +#include <iprt/cpp/list.h> + +typedef DECLCALLBACK(int) FNDNDPROGRESS(uint32_t uState, uint32_t uPercentage, int rc, void *pvUser); +typedef FNDNDPROGRESS *PFNDNDPROGRESS; + +/** + * DnD message class. This class forms the base of all other more specialized + * message classes. + */ +class DnDMessage : public HGCM::Message +{ +public: + + DnDMessage(void) + { + } + + DnDMessage(uint32_t uMsg, uint32_t cParms, VBOXHGCMSVCPARM aParms[]) + : Message(uMsg, cParms, aParms) { } + + virtual ~DnDMessage(void) { } +}; + +/** + * DnD message class for generic messages which didn't need any special + * handling. + */ +class DnDGenericMessage: public DnDMessage +{ +public: + DnDGenericMessage(uint32_t uMsg, uint32_t cParms, VBOXHGCMSVCPARM paParms[]) + : DnDMessage(uMsg, cParms, paParms) { } +}; + +/** + * DnD message class for informing the guest to cancel any current (and pending) activities. + */ +class DnDHGCancelMessage: public DnDMessage +{ +public: + + DnDHGCancelMessage(void) + { + int rc2 = initData(DragAndDropSvc::HOST_DND_CANCEL, + 0 /* cParms */, 0 /* aParms */); + AssertRC(rc2); + } +}; + +/** + * DnD manager. Manage creation and queuing of messages for the various DnD + * messages types. + */ +class DnDManager +{ +public: + + DnDManager(PFNDNDPROGRESS pfnProgressCallback, void *pvProgressUser) + : m_pfnProgressCallback(pfnProgressCallback) + , m_pvProgressUser(pvProgressUser) + {} + + virtual ~DnDManager(void) + { + Reset(); + } + + int AddMsg(DnDMessage *pMessage, bool fAppend = true); + int AddMsg(uint32_t uMsg, uint32_t cParms, VBOXHGCMSVCPARM paParms[], bool fAppend = true); + + int GetNextMsgInfo(uint32_t *puType, uint32_t *pcParms); + int GetNextMsg(uint32_t uMsg, uint32_t cParms, VBOXHGCMSVCPARM paParms[]); + + void Reset(void); + +protected: + + /** DnD message queue (FIFO). */ + RTCList<DnDMessage *> m_queueMsg; + /** Pointer to host progress callback. Optional, can be NULL. */ + PFNDNDPROGRESS m_pfnProgressCallback; + /** Pointer to progress callback user context. Can be NULL if not used. */ + void *m_pvProgressUser; +}; +#endif /* !VBOX_INCLUDED_SRC_DragAndDrop_dndmanager_h */ + |