diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 03:01:46 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 03:01:46 +0000 |
commit | f8fe689a81f906d1b91bb3220acde2a4ecb14c5b (patch) | |
tree | 26484e9d7e2c67806c2d1760196ff01aaa858e8c /src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibDragAndDrop.cpp | |
parent | Initial commit. (diff) | |
download | virtualbox-f8fe689a81f906d1b91bb3220acde2a4ecb14c5b.tar.xz virtualbox-f8fe689a81f906d1b91bb3220acde2a4ecb14c5b.zip |
Adding upstream version 6.0.4-dfsg.upstream/6.0.4-dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibDragAndDrop.cpp')
-rw-r--r-- | src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibDragAndDrop.cpp | 1898 |
1 files changed, 1898 insertions, 0 deletions
diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibDragAndDrop.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibDragAndDrop.cpp new file mode 100644 index 00000000..1385fa52 --- /dev/null +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibDragAndDrop.cpp @@ -0,0 +1,1898 @@ +/* $Id: VBoxGuestR3LibDragAndDrop.cpp $ */ +/** @file + * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Drag & Drop. + */ + +/* + * 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. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/path.h> +#include <iprt/dir.h> +#include <iprt/file.h> +#include <iprt/uri.h> +#include <iprt/thread.h> + +#include <iprt/cpp/list.h> +#include <iprt/cpp/ministring.h> + +#ifdef LOG_GROUP + #undef LOG_GROUP +#endif +#define LOG_GROUP LOG_GROUP_GUEST_DND +#include <VBox/log.h> + +#include <VBox/VBoxGuestLib.h> +#include <VBox/GuestHost/DragAndDrop.h> +#include <VBox/HostServices/DragAndDropSvc.h> + +using namespace DragAndDropSvc; + +#include "VBoxGuestR3LibInternal.h" + + +/********************************************************************************************************************************* +* Private internal functions * +*********************************************************************************************************************************/ + +/** + * Receives the next upcoming message for a given DnD context. + * + * @returns IPRT status code. + * @param pCtx DnD context to use. + * @param puMsg Where to store the message type. + * @param pcParms Where to store the number of parameters required for receiving the message. + * @param fWait Whether to wait (block) for a new message to arrive or not. + */ +static int vbglR3DnDGetNextMsgType(PVBGLR3GUESTDNDCMDCTX pCtx, uint32_t *puMsg, uint32_t *pcParms, bool fWait) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(puMsg, VERR_INVALID_POINTER); + AssertPtrReturn(pcParms, VERR_INVALID_POINTER); + + VBOXDNDNEXTMSGMSG Msg; + RT_ZERO(Msg); + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_DND_GET_NEXT_HOST_MSG, 3); + Msg.uMsg.SetUInt32(0); + Msg.cParms.SetUInt32(0); + Msg.fBlock.SetUInt32(fWait ? 1 : 0); + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + rc = Msg.uMsg.GetUInt32(puMsg); AssertRC(rc); + rc = Msg.cParms.GetUInt32(pcParms); AssertRC(rc); + } + + return rc; +} + +/** + * Host -> Guest + * Utility function to receive a so-called "action message" from the host. + * Certain DnD messages use the same amount / sort of parameters and grouped as "action messages". + * + * @returns IPRT status code. + * @param pCtx DnD context to use. + * @param uMsg Which kind of message to receive. + * @param puScreenID Where to store the host screen ID the message is bound to. Optional. + * @param puX Where to store the absolute X coordinates. Optional. + * @param puY Where to store the absolute Y coordinates. Optional. + * @param puDefAction Where to store the default action to perform. Optional. + * @param puAllActions Where to store the available actions. Optional. + * @param ppszFormats Where to store List of formats. Optional. + * @param pcbFormats Size (in bytes) of where to store the list of formats. Optional. + * + * @todo r=andy Get rid of this function as soon as we resolved the protocol TODO #1. + * This was part of the initial protocol and needs to go. + */ +static int vbglR3DnDHGRecvAction(PVBGLR3GUESTDNDCMDCTX pCtx, + uint32_t uMsg, + uint32_t *puScreenID, + uint32_t *puX, + uint32_t *puY, + uint32_t *puDefAction, + uint32_t *puAllActions, + char **ppszFormats, + uint32_t *pcbFormats) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + /* The rest is optional. */ + + const uint32_t cbFormatsTmp = pCtx->cbMaxChunkSize; + + char *pszFormatsTmp = static_cast<char *>(RTMemAlloc(cbFormatsTmp)); + if (!pszFormatsTmp) + return VERR_NO_MEMORY; + + VBOXDNDHGACTIONMSG Msg; + RT_ZERO(Msg); + + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, uMsg, 8); + Msg.u.v3.uContext.SetUInt32(0); + Msg.u.v3.uScreenId.SetUInt32(0); + Msg.u.v3.uX.SetUInt32(0); + Msg.u.v3.uY.SetUInt32(0); + Msg.u.v3.uDefAction.SetUInt32(0); + Msg.u.v3.uAllActions.SetUInt32(0); + Msg.u.v3.pvFormats.SetPtr(pszFormatsTmp, cbFormatsTmp); + Msg.u.v3.cbFormats.SetUInt32(0); + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + /** @todo Context ID not used yet. */ + if (RT_SUCCESS(rc) && puScreenID) + rc = Msg.u.v3.uScreenId.GetUInt32(puScreenID); + if (RT_SUCCESS(rc) && puX) + rc = Msg.u.v3.uX.GetUInt32(puX); + if (RT_SUCCESS(rc) && puY) + rc = Msg.u.v3.uY.GetUInt32(puY); + if (RT_SUCCESS(rc) && puDefAction) + rc = Msg.u.v3.uDefAction.GetUInt32(puDefAction); + if (RT_SUCCESS(rc) && puAllActions) + rc = Msg.u.v3.uAllActions.GetUInt32(puAllActions); + if (RT_SUCCESS(rc) && pcbFormats) + rc = Msg.u.v3.cbFormats.GetUInt32(pcbFormats); + + if (RT_SUCCESS(rc)) + { + if (ppszFormats) + { + *ppszFormats = RTStrDup(pszFormatsTmp); + if (!*ppszFormats) + rc = VERR_NO_MEMORY; + } + } + } + + RTStrFree(pszFormatsTmp); + + return rc; +} + +/** + * Host -> Guest + * Utility function to receive a HOST_DND_HG_EVT_LEAVE message from the host. + * + * @returns IPRT status code. + * @param pCtx DnD context to use. + */ +static int vbglR3DnDHGRecvLeave(PVBGLR3GUESTDNDCMDCTX pCtx) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + VBOXDNDHGLEAVEMSG Msg; + RT_ZERO(Msg); + + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, HOST_DND_HG_EVT_LEAVE, 1); + /** @todo Context ID not used yet. */ + Msg.u.v3.uContext.SetUInt32(0); + + return VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); +} + +/** + * Host -> Guest + * Utility function to receive a HOST_DND_HG_EVT_CANCEL message from the host. + * + * @returns IPRT status code. + * @param pCtx DnD context to use. + */ +static int vbglR3DnDHGRecvCancel(PVBGLR3GUESTDNDCMDCTX pCtx) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + VBOXDNDHGCANCELMSG Msg; + RT_ZERO(Msg); + + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, HOST_DND_CANCEL, 1); + /** @todo Context ID not used yet. */ + Msg.u.v3.uContext.SetUInt32(0); + + return VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); +} + +/** + * Host -> Guest + * Utility function to receive a HOST_DND_HG_SND_DIR message from the host. + * + * @returns IPRT status code. + * @param pCtx DnD context to use. + * @param pszDirname Where to store the directory name of the directory being created. + * @param cbDirname Size (in bytes) of where to store the directory name of the directory being created. + * @param pcbDirnameRecv Size (in bytes) of the actual directory name received. + * @param pfMode Where to store the directory creation mode. + */ +static int vbglR3DnDHGRecvDir(PVBGLR3GUESTDNDCMDCTX pCtx, + char *pszDirname, + uint32_t cbDirname, + uint32_t *pcbDirnameRecv, + uint32_t *pfMode) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pszDirname, VERR_INVALID_POINTER); + AssertReturn(cbDirname, VERR_INVALID_PARAMETER); + AssertPtrReturn(pcbDirnameRecv, VERR_INVALID_POINTER); + AssertPtrReturn(pfMode, VERR_INVALID_POINTER); + + VBOXDNDHGSENDDIRMSG Msg; + RT_ZERO(Msg); + + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, HOST_DND_HG_SND_DIR, 4); + /** @todo Context ID not used yet. */ + Msg.u.v3.uContext.SetUInt32(0); + Msg.u.v3.pvName.SetPtr(pszDirname, cbDirname); + Msg.u.v3.cbName.SetUInt32(cbDirname); + Msg.u.v3.fMode.SetUInt32(0); + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + /** @todo Context ID not used yet. */ + rc = Msg.u.v3.cbName.GetUInt32(pcbDirnameRecv); AssertRC(rc); + rc = Msg.u.v3.fMode.GetUInt32(pfMode); AssertRC(rc); + + AssertReturn(cbDirname >= *pcbDirnameRecv, VERR_TOO_MUCH_DATA); + } + + return rc; +} + +/** + * Host -> Guest + * Utility function to receive a HOST_DND_HG_SND_FILE_DATA message from the host. + * + * @returns IPRT status code. + * @param pCtx DnD context to use. + * @param pvData Where to store the file data chunk. + * @param cbData Size (in bytes) of where to store the data chunk. + * @param pcbDataRecv Size (in bytes) of the actual data chunk size received. + */ +static int vbglR3DnDHGRecvFileData(PVBGLR3GUESTDNDCMDCTX pCtx, + void *pvData, + uint32_t cbData, + uint32_t *pcbDataRecv) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pvData, VERR_INVALID_POINTER); + AssertReturn(cbData, VERR_INVALID_PARAMETER); + AssertPtrReturn(pcbDataRecv, VERR_INVALID_POINTER); + + VBOXDNDHGSENDFILEDATAMSG Msg; + RT_ZERO(Msg); + + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, HOST_DND_HG_SND_FILE_DATA, 5); + Msg.u.v3.uContext.SetUInt32(0); + Msg.u.v3.pvData.SetPtr(pvData, cbData); + Msg.u.v3.cbData.SetUInt32(0); + Msg.u.v3.pvChecksum.SetPtr(NULL, 0); + Msg.u.v3.cbChecksum.SetUInt32(0); + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + /** @todo Context ID not used yet. */ + rc = Msg.u.v3.cbData.GetUInt32(pcbDataRecv); AssertRC(rc); + AssertReturn(cbData >= *pcbDataRecv, VERR_TOO_MUCH_DATA); + /** @todo Add checksum support. */ + } + + return rc; +} + +/** + * Host -> Guest + * Utility function to receive the HOST_DND_HG_SND_FILE_HDR message from the host. + * + * @returns IPRT status code. + * @param pCtx DnD context to use. + * @param pszFilename Where to store the file name of the file being transferred. + * @param cbFilename Size (in bytes) of where to store the file name of the file being transferred. + * @param puFlags File transfer flags. Currently not being used. + * @param pfMode Where to store the file creation mode. + * @param pcbTotal Where to store the file size (in bytes). + */ +static int vbglR3DnDHGRecvFileHdr(PVBGLR3GUESTDNDCMDCTX pCtx, + char *pszFilename, + uint32_t cbFilename, + uint32_t *puFlags, + uint32_t *pfMode, + uint64_t *pcbTotal) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pszFilename, VERR_INVALID_POINTER); + AssertReturn(cbFilename, VERR_INVALID_PARAMETER); + AssertPtrReturn(puFlags, VERR_INVALID_POINTER); + AssertPtrReturn(pfMode, VERR_INVALID_POINTER); + AssertReturn(pcbTotal, VERR_INVALID_POINTER); + + VBOXDNDHGSENDFILEHDRMSG Msg; + RT_ZERO(Msg); + + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, HOST_DND_HG_SND_FILE_HDR, 6); + Msg.uContext.SetUInt32(0); /** @todo Not used yet. */ + Msg.pvName.SetPtr(pszFilename, cbFilename); + Msg.cbName.SetUInt32(cbFilename); + Msg.uFlags.SetUInt32(0); + Msg.fMode.SetUInt32(0); + Msg.cbTotal.SetUInt64(0); + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + /** @todo Get context ID. */ + rc = Msg.uFlags.GetUInt32(puFlags); AssertRC(rc); + rc = Msg.fMode.GetUInt32(pfMode); AssertRC(rc); + rc = Msg.cbTotal.GetUInt64(pcbTotal); AssertRC(rc); + } + + return rc; +} + +/** + * Host -> Guest + * Helper function for receiving URI data from the host. Do not call directly. + * This function also will take care of the file creation / locking on the guest. + * + * @returns IPRT status code. + * @param pCtx DnD context to use. + * @param pDataHdr DnD data header to use. Needed for accounting. + * @param pDroppedFiles Dropped files object to use for maintaining the file creation / locking. + */ +static int vbglR3DnDHGRecvURIData(PVBGLR3GUESTDNDCMDCTX pCtx, PVBOXDNDSNDDATAHDR pDataHdr, DnDDroppedFiles *pDroppedFiles) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pDataHdr, VERR_INVALID_POINTER); + AssertPtrReturn(pDroppedFiles, VERR_INVALID_POINTER); + + /* Only count the raw data minus the already received meta data. */ + Assert(pDataHdr->cbTotal >= pDataHdr->cbMeta); + uint64_t cbToRecvBytes = pDataHdr->cbTotal - pDataHdr->cbMeta; + uint64_t cToRecvObjs = pDataHdr->cObjects; + + LogFlowFunc(("cbToRecvBytes=%RU64, cToRecvObjs=%RU64, (cbTotal=%RU64, cbMeta=%RU32)\n", + cbToRecvBytes, cToRecvObjs, pDataHdr->cbTotal, pDataHdr->cbMeta)); + + /* Anything to do at all? */ + /* Note: Do not check for cbToRecvBytes == 0 here, as this might be just + * a bunch of 0-byte files to be transferred. */ + if (!cToRecvObjs) + return VINF_SUCCESS; + + /* + * Allocate temporary chunk buffer. + */ + uint32_t cbChunkMax = pCtx->cbMaxChunkSize; + void *pvChunk = RTMemAlloc(cbChunkMax); + if (!pvChunk) + return VERR_NO_MEMORY; + uint32_t cbChunkRead = 0; + + uint64_t cbFileSize = 0; /* Total file size (in bytes). */ + uint64_t cbFileWritten = 0; /* Written bytes. */ + + /* + * Create and query the (unique) drop target directory in the user's temporary directory. + */ + int rc = pDroppedFiles->OpenTemp(0 /* fFlags */); + if (RT_FAILURE(rc)) + { + RTMemFree(pvChunk); + return VERR_NO_MEMORY; + } + + const char *pszDropDir = pDroppedFiles->GetDirAbs(); + AssertPtr(pszDropDir); + + /* + * Enter the main loop of retieving files + directories. + */ + DnDURIObject objFile(DnDURIObject::Type_File); + + char szPathName[RTPATH_MAX] = { 0 }; + uint32_t cbPathName = 0; + uint32_t fFlags = 0; + uint32_t fMode = 0; + + do + { + LogFlowFunc(("Wating for new message ...\n")); + + uint32_t uNextMsg; + uint32_t cNextParms; + rc = vbglR3DnDGetNextMsgType(pCtx, &uNextMsg, &cNextParms, true /* fWait */); + if (RT_SUCCESS(rc)) + { + LogFlowFunc(("uNextMsg=%RU32, cNextParms=%RU32\n", uNextMsg, cNextParms)); + + switch (uNextMsg) + { + case HOST_DND_HG_SND_DIR: + { + rc = vbglR3DnDHGRecvDir(pCtx, + szPathName, + sizeof(szPathName), + &cbPathName, + &fMode); + LogFlowFunc(("HOST_DND_HG_SND_DIR pszPathName=%s, cbPathName=%RU32, fMode=0x%x, rc=%Rrc\n", + szPathName, cbPathName, fMode, rc)); + + char *pszPathAbs = RTPathJoinA(pszDropDir, szPathName); + if (pszPathAbs) + { +#ifdef RT_OS_WINDOWS + uint32_t fCreationMode = (fMode & RTFS_DOS_MASK) | RTFS_DOS_NT_NORMAL; +#else + uint32_t fCreationMode = (fMode & RTFS_UNIX_MASK) | RTFS_UNIX_IRWXU; +#endif + rc = RTDirCreate(pszPathAbs, fCreationMode, 0); + if (RT_SUCCESS(rc)) + rc = pDroppedFiles->AddDir(pszPathAbs); + + if (RT_SUCCESS(rc)) + { + Assert(cToRecvObjs); + cToRecvObjs--; + } + + RTStrFree(pszPathAbs); + } + else + rc = VERR_NO_MEMORY; + break; + } + case HOST_DND_HG_SND_FILE_HDR: + case HOST_DND_HG_SND_FILE_DATA: + { + if (uNextMsg == HOST_DND_HG_SND_FILE_HDR) + { + rc = vbglR3DnDHGRecvFileHdr(pCtx, + szPathName, + sizeof(szPathName), + &fFlags, + &fMode, + &cbFileSize); + LogFlowFunc(("HOST_DND_HG_SND_FILE_HDR: " + "szPathName=%s, fFlags=0x%x, fMode=0x%x, cbFileSize=%RU64, rc=%Rrc\n", + szPathName, fFlags, fMode, cbFileSize, rc)); + } + else + { + rc = vbglR3DnDHGRecvFileData(pCtx, + pvChunk, + cbChunkMax, + &cbChunkRead); + LogFlowFunc(("HOST_DND_HG_SND_FILE_DATA: " + "cbChunkRead=%RU32, rc=%Rrc\n", cbChunkRead, rc)); + } + + if ( RT_SUCCESS(rc) + && uNextMsg == HOST_DND_HG_SND_FILE_HDR) + { + char *pszPathAbs = RTPathJoinA(pszDropDir, szPathName); + if (pszPathAbs) + { + LogFlowFunc(("Opening pszPathName=%s, cbPathName=%RU32, fMode=0x%x, cbFileSize=%zu\n", + szPathName, cbPathName, fMode, cbFileSize)); + + uint64_t fOpen = RTFILE_O_WRITE | RTFILE_O_DENY_WRITE + | RTFILE_O_CREATE_REPLACE; + + /* Is there already a file open, e.g. in transfer? */ + if (!objFile.IsOpen()) + { + RTCString strPathAbs(pszPathAbs); +#ifdef RT_OS_WINDOWS + uint32_t fCreationMode = (fMode & RTFS_DOS_MASK) | RTFS_DOS_NT_NORMAL; +#else + uint32_t fCreationMode = (fMode & RTFS_UNIX_MASK) | RTFS_UNIX_IRUSR | RTFS_UNIX_IWUSR; +#endif + rc = objFile.OpenEx(strPathAbs, DnDURIObject::View_Target, fOpen, fCreationMode); + if (RT_SUCCESS(rc)) + { + rc = pDroppedFiles->AddFile(strPathAbs.c_str()); + if (RT_SUCCESS(rc)) + { + cbFileWritten = 0; + objFile.SetSize(cbFileSize); + } + } + } + else + { + AssertMsgFailed(("ObjType=%RU32\n", objFile.GetType())); + rc = VERR_WRONG_ORDER; + } + + RTStrFree(pszPathAbs); + } + else + rc = VERR_NO_MEMORY; + } + + if ( RT_SUCCESS(rc) + && uNextMsg == HOST_DND_HG_SND_FILE_DATA + && cbChunkRead) + { + uint32_t cbChunkWritten; + rc = objFile.Write(pvChunk, cbChunkRead, &cbChunkWritten); + if (RT_SUCCESS(rc)) + { + LogFlowFunc(("HOST_DND_HG_SND_FILE_DATA " + "cbChunkRead=%RU32, cbChunkWritten=%RU32, cbFileWritten=%RU64 cbFileSize=%RU64\n", + cbChunkRead, cbChunkWritten, cbFileWritten + cbChunkWritten, cbFileSize)); + + cbFileWritten += cbChunkWritten; + + Assert(cbChunkRead <= cbToRecvBytes); + cbToRecvBytes -= cbChunkRead; + } + } + + /* Data transfer complete? Close the file. */ + bool fClose = objFile.IsComplete(); + if (fClose) + { + Assert(cToRecvObjs); + cToRecvObjs--; + } + + /* Only since protocol v2 we know the file size upfront. */ + Assert(cbFileWritten <= cbFileSize); + + if (fClose) + { + LogFlowFunc(("Closing file\n")); + objFile.Close(); + } + + break; + } + case HOST_DND_CANCEL: + { + rc = vbglR3DnDHGRecvCancel(pCtx); + if (RT_SUCCESS(rc)) + rc = VERR_CANCELLED; + break; + } + default: + { + LogFlowFunc(("Message %RU32 not supported\n", uNextMsg)); + rc = VERR_NOT_SUPPORTED; + break; + } + } + } + + if (RT_FAILURE(rc)) + break; + + LogFlowFunc(("cbToRecvBytes=%RU64, cToRecvObjs=%RU64\n", cbToRecvBytes, cToRecvObjs)); + if ( !cbToRecvBytes + && !cToRecvObjs) + { + break; + } + + } while (RT_SUCCESS(rc)); + + LogFlowFunc(("Loop ended with %Rrc\n", rc)); + + /* All URI data processed? */ + if (rc == VERR_NO_DATA) + rc = VINF_SUCCESS; + + /* Delete temp buffer again. */ + if (pvChunk) + RTMemFree(pvChunk); + + /* Cleanup on failure or if the user has canceled the operation or + * something else went wrong. */ + if (RT_FAILURE(rc)) + { + objFile.Close(); + pDroppedFiles->Rollback(); + } + else + { + /** @todo Compare the URI list with the dirs/files we really transferred. */ + /** @todo Implement checksum verification, if any. */ + } + + /* + * Close the dropped files directory. + * Don't try to remove it here, however, as the files are being needed + * by the client's drag'n drop operation lateron. + */ + int rc2 = pDroppedFiles->Reset(false /* fRemoveDropDir */); + if (RT_FAILURE(rc2)) /* Not fatal, don't report back to host. */ + LogFlowFunc(("Closing dropped files directory failed with %Rrc\n", rc2)); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Host -> Guest + * Utility function to receive the HOST_DND_HG_SND_DATA message from the host. + * + * @returns IPRT status code. + * @param pCtx DnD context to use. + * @param pDataHdr DnD data header to use. Need for accounting and stuff. + * @param pvData Where to store the received data from the host. + * @param cbData Size (in bytes) of where to store the received data. + * @param pcbDataRecv Where to store the received amount of data (in bytes). + */ +static int vbglR3DnDHGRecvDataRaw(PVBGLR3GUESTDNDCMDCTX pCtx, PVBOXDNDSNDDATAHDR pDataHdr, + void *pvData, uint32_t cbData, uint32_t *pcbDataRecv) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pDataHdr, VERR_INVALID_POINTER); + AssertPtrReturn(pvData, VERR_INVALID_POINTER); + AssertReturn(cbData, VERR_INVALID_PARAMETER); + AssertPtrNullReturn(pcbDataRecv, VERR_INVALID_POINTER); + + LogFlowFunc(("pvDate=%p, cbData=%RU32\n", pvData, cbData)); + + VBOXDNDHGSENDDATAMSG Msg; + RT_ZERO(Msg); + + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, HOST_DND_HG_SND_DATA, 5); + Msg.u.v3.uContext.SetUInt32(0); + Msg.u.v3.pvData.SetPtr(pvData, cbData); + Msg.u.v3.cbData.SetUInt32(0); + Msg.u.v3.pvChecksum.SetPtr(NULL, 0); + Msg.u.v3.cbChecksum.SetUInt32(0); + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + uint32_t cbDataRecv; + rc = Msg.u.v3.cbData.GetUInt32(&cbDataRecv); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + /** @todo Use checksum for validating the received data. */ + if (pcbDataRecv) + *pcbDataRecv = cbDataRecv; + LogFlowFuncLeaveRC(rc); + return rc; + } + } + + /* failure */ + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Host -> Guest + * Utility function to receive the HOST_DND_HG_SND_DATA_HDR message from the host. + * + * @returns IPRT status code. + * @param pCtx DnD context to use. + * @param pDataHdr Where to store the receivd DnD data header. + */ +static int vbglR3DnDHGRecvDataHdr(PVBGLR3GUESTDNDCMDCTX pCtx, PVBOXDNDSNDDATAHDR pDataHdr) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pDataHdr, VERR_INVALID_POINTER); + + Assert(pCtx->uProtocol >= 3); /* Only for protocol v3 and up. */ + + VBOXDNDHGSENDDATAHDRMSG Msg; + RT_ZERO(Msg); + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, HOST_DND_HG_SND_DATA_HDR, 12); + Msg.uContext.SetUInt32(0); + Msg.uFlags.SetUInt32(0); + Msg.uScreenId.SetUInt32(0); + Msg.cbTotal.SetUInt64(0); + Msg.cbMeta.SetUInt32(0); + Msg.pvMetaFmt.SetPtr(pDataHdr->pvMetaFmt, pDataHdr->cbMetaFmt); + Msg.cbMetaFmt.SetUInt32(0); + Msg.cObjects.SetUInt64(0); + Msg.enmCompression.SetUInt32(0); + Msg.enmChecksumType.SetUInt32(0); + Msg.pvChecksum.SetPtr(pDataHdr->pvChecksum, pDataHdr->cbChecksum); + Msg.cbChecksum.SetUInt32(0); + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + /* Msg.uContext not needed here. */ + Msg.uFlags.GetUInt32(&pDataHdr->uFlags); + Msg.uScreenId.GetUInt32(&pDataHdr->uScreenId); + Msg.cbTotal.GetUInt64(&pDataHdr->cbTotal); + Msg.cbMeta.GetUInt32(&pDataHdr->cbMeta); + Msg.cbMetaFmt.GetUInt32(&pDataHdr->cbMetaFmt); + Msg.cObjects.GetUInt64(&pDataHdr->cObjects); + Msg.enmCompression.GetUInt32(&pDataHdr->enmCompression); + Msg.enmChecksumType.GetUInt32((uint32_t *)&pDataHdr->enmChecksumType); + Msg.cbChecksum.GetUInt32(&pDataHdr->cbChecksum); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Host -> Guest + * Helper function for receiving the actual DnD data from the host. Do not call directly. + * + * @returns IPRT status code. + * @param pCtx DnD context to use. + * @param pDataHdr Where to store the data header data. + * @param ppvData Returns the received meta data. Needs to be free'd by the caller. + * @param pcbData Where to store the size (in bytes) of the received meta data. + */ +static int vbglR3DnDHGRecvDataLoop(PVBGLR3GUESTDNDCMDCTX pCtx, PVBOXDNDSNDDATAHDR pDataHdr, + void **ppvData, uint64_t *pcbData) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pDataHdr, VERR_INVALID_POINTER); + AssertPtrReturn(ppvData, VERR_INVALID_POINTER); + AssertPtrReturn(pcbData, VERR_INVALID_POINTER); + + int rc; + uint32_t cbDataRecv; + + LogFlowFuncEnter(); + + rc = vbglR3DnDHGRecvDataHdr(pCtx, pDataHdr); + if (RT_FAILURE(rc)) + return rc; + + LogFlowFunc(("cbTotal=%RU64, cbMeta=%RU32, cObjects=%RU32\n", pDataHdr->cbTotal, pDataHdr->cbMeta, pDataHdr->cObjects)); + if (pDataHdr->cbMeta) + { + uint64_t cbDataTmp = 0; + void *pvDataTmp = RTMemAlloc(pDataHdr->cbMeta); + if (!pvDataTmp) + rc = VERR_NO_MEMORY; + + if (RT_SUCCESS(rc)) + { + uint8_t *pvDataOff = (uint8_t *)pvDataTmp; + while (cbDataTmp < pDataHdr->cbMeta) + { + rc = vbglR3DnDHGRecvDataRaw(pCtx, pDataHdr, + pvDataOff, RT_MIN(pDataHdr->cbMeta - cbDataTmp, pCtx->cbMaxChunkSize), + &cbDataRecv); + if (RT_SUCCESS(rc)) + { + LogFlowFunc(("cbDataRecv=%RU32, cbDataTmp=%RU64\n", cbDataRecv, cbDataTmp)); + Assert(cbDataTmp + cbDataRecv <= pDataHdr->cbMeta); + cbDataTmp += cbDataRecv; + pvDataOff += cbDataRecv; + } + else + break; + } + + if (RT_SUCCESS(rc)) + { + Assert(cbDataTmp == pDataHdr->cbMeta); + + LogFlowFunc(("Received %RU64 bytes of data\n", cbDataTmp)); + + *ppvData = pvDataTmp; + *pcbData = cbDataTmp; + } + else + RTMemFree(pvDataTmp); + } + } + else + { + *ppvData = NULL; + *pcbData = 0; + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Host -> Guest + * Main function for receiving the actual DnD data from the host, extended version. + * + * @returns IPRT status code. + * @param pCtx DnD context to use. + * @param pEnmType Where to store the meta data type. Optional. + * @param ppvData Returns the received meta data. Needs to be free'd by the caller. Optional. + * @param pcbData Where to store the size (in bytes) of the received meta data. Optional. + */ +static int vbglR3DnDHGRecvDataMainEx(PVBGLR3GUESTDNDCMDCTX pCtx, + VBGLR3GUESTDNDMETADATATYPE *pEnmType, + void **ppvData, + uint32_t *pcbData) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + /* The rest is optional. */ + + VBOXDNDDATAHDR dataHdr; + RT_ZERO(dataHdr); + + AssertMsg(pCtx->cbMaxChunkSize, ("Maximum chunk size must not be 0\n")); + + dataHdr.cbMetaFmt = pCtx->cbMaxChunkSize; + dataHdr.pvMetaFmt = RTMemAlloc(dataHdr.cbMetaFmt); + if (!dataHdr.pvMetaFmt) + return VERR_NO_MEMORY; + + DnDURIList lstURI; + DnDDroppedFiles droppedFiles; + + void *pvData = NULL; + uint64_t cbData = 0; + + int rc = vbglR3DnDHGRecvDataLoop(pCtx, &dataHdr, &pvData, &cbData); + if (RT_SUCCESS(rc)) + { + /** + * Check if this is an URI event. If so, let VbglR3 do all the actual + * data transfer + file/directory creation internally without letting + * the caller know. + * + * This keeps the actual (guest OS-)dependent client (like VBoxClient / + * VBoxTray) small by not having too much redundant code. + */ + Assert(dataHdr.cbMetaFmt); + AssertPtr(dataHdr.pvMetaFmt); + if (DnDMIMEHasFileURLs((char *)dataHdr.pvMetaFmt, dataHdr.cbMetaFmt)) /* URI data. */ + { + AssertPtr(pvData); + Assert(cbData); + + rc = lstURI.SetFromURIData(pvData, cbData, 0 /* fFlags */); + if (RT_SUCCESS(rc)) + rc = vbglR3DnDHGRecvURIData(pCtx, &dataHdr, &droppedFiles); + + if (RT_SUCCESS(rc)) /** @todo Remove this block as soon as we hand in DnDURIList. */ + { + if (pvData) + { + /* Reuse data buffer to fill in the transformed URI file list. */ + RTMemFree(pvData); + pvData = NULL; + } + + RTCString strData = lstURI.GetRootEntries(droppedFiles.GetDirAbs()); + Assert(!strData.isEmpty()); + + cbData = strData.length() + 1; + LogFlowFunc(("URI list has %zu bytes\n", cbData)); + + pvData = RTMemAlloc(cbData); + if (pvData) + { + memcpy(pvData, strData.c_str(), cbData); + + if (pEnmType) + *pEnmType = VBGLR3GUESTDNDMETADATATYPE_URI_LIST; + } + else + rc = VERR_NO_MEMORY; + } + } + else /* Raw data. */ + { + if (pEnmType) + *pEnmType = VBGLR3GUESTDNDMETADATATYPE_RAW; + } + } + + if (dataHdr.pvMetaFmt) + RTMemFree(dataHdr.pvMetaFmt); + + if (RT_SUCCESS(rc)) + { + if ( pvData + && cbData) + { + if (pcbData) + *pcbData = cbData; + if (ppvData) + *ppvData = pvData; + else + RTMemFree(pvData); + } + } + else if ( RT_FAILURE(rc) + && rc != VERR_CANCELLED) + { + if (pvData) + RTMemFree(pvData); + + int rc2 = VbglR3DnDHGSendProgress(pCtx, DND_PROGRESS_ERROR, 100 /* Percent */, rc); + if (RT_FAILURE(rc2)) + LogFlowFunc(("Unable to send progress error %Rrc to host: %Rrc\n", rc, rc2)); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Host -> Guest + * Main function for receiving the actual DnD data from the host. + * + * @returns IPRT status code. + * @param pCtx DnD context to use. + * @param pMeta Where to store the actual meta data received from the host. + */ +static int vbglR3DnDHGRecvDataMain(PVBGLR3GUESTDNDCMDCTX pCtx, + PVBGLR3GUESTDNDMETADATA pMeta) +{ + AssertPtrReturn(pMeta, VERR_INVALID_POINTER); + + int rc = vbglR3DnDHGRecvDataMainEx(pCtx, + &pMeta->enmType, + &pMeta->pvMeta, + &pMeta->cbMeta); + return rc; +} + +#ifdef VBOX_WITH_DRAG_AND_DROP_GH +/** + * Guest -> Host + * Utility function to receive the HOST_DND_GH_REQ_PENDING message from the host. + * + * @returns IPRT status code. + * @param pCtx DnD context to use. + * @param puScreenID For which screen on the host the request is for. Optional. + */ +static int vbglR3DnDGHRecvPending(PVBGLR3GUESTDNDCMDCTX pCtx, uint32_t *puScreenID) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + /* pScreenID is optional. */ + + VBOXDNDGHREQPENDINGMSG Msg; + RT_ZERO(Msg); + + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, HOST_DND_GH_REQ_PENDING, 2); + /** @todo Context ID not used yet. */ + Msg.u.v3.uContext.SetUInt32(0); + Msg.u.v3.uScreenId.SetUInt32(0); + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + /** @todo Context ID not used yet. */ + if (puScreenID) + rc = Msg.u.v3.uContext.GetUInt32(puScreenID); + } + + return rc; +} + +/** + * Guest -> Host + * Utility function to receive the HOST_DND_GH_EVT_DROPPED message from the host. + * + * @returns IPRT status code. + * @param pCtx DnD context to use. + * @param ppszFormat Requested data format from the host. Optional. + * @param pcbFormat Size of requested data format (in bytes). Optional. + * @param puAction Requested action from the host. Optional. + */ +static int vbglR3DnDGHRecvDropped(PVBGLR3GUESTDNDCMDCTX pCtx, + char **ppszFormat, + uint32_t *pcbFormat, + uint32_t *puAction) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + /* The rest is optional. */ + + const uint32_t cbFormatTmp = pCtx->cbMaxChunkSize; + + char *pszFormatTmp = static_cast<char *>(RTMemAlloc(cbFormatTmp)); + if (!pszFormatTmp) + return VERR_NO_MEMORY; + + VBOXDNDGHDROPPEDMSG Msg; + RT_ZERO(Msg); + + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, HOST_DND_GH_EVT_DROPPED, 4); + Msg.u.v3.uContext.SetUInt32(0); + Msg.u.v3.pvFormat.SetPtr(pszFormatTmp, cbFormatTmp); + Msg.u.v3.cbFormat.SetUInt32(0); + Msg.u.v3.uAction.SetUInt32(0); + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_SUCCESS(rc)) + { + /** @todo Context ID not used yet. */ + if (pcbFormat) + rc = Msg.u.v3.cbFormat.GetUInt32(pcbFormat); + if (RT_SUCCESS(rc) && puAction) + rc = Msg.u.v3.uAction.GetUInt32(puAction); + + if (RT_SUCCESS(rc)) + { + *ppszFormat = RTStrDup(pszFormatTmp); + if (!*ppszFormat) + rc = VERR_NO_MEMORY; + } + } + + RTMemFree(pszFormatTmp); + + return rc; +} +#endif /* VBOX_WITH_DRAG_AND_DROP_GH */ + + +/********************************************************************************************************************************* +* Public functions * +*********************************************************************************************************************************/ + +/** + * Connects a DnD context to the DnD host service. + * + * @returns IPRT status code. + * @param pCtx DnD context to connect. + */ +VBGLR3DECL(int) VbglR3DnDConnect(PVBGLR3GUESTDNDCMDCTX pCtx) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + /* Initialize header */ + int rc = VbglR3HGCMConnect("VBoxDragAndDropSvc", &pCtx->uClientID); + if (RT_FAILURE(rc)) + return rc; + + /* Set the default protocol version to use. */ + pCtx->uProtocol = 3; + Assert(pCtx->uClientID); + + /* + * Get the VM's session ID. + * This is not fatal in case we're running with an ancient VBox version. + */ + pCtx->uSessionID = 0; + int rc2 = VbglR3GetSessionId(&pCtx->uSessionID); + LogFlowFunc(("uSessionID=%RU64, rc=%Rrc\n", pCtx->uSessionID, rc2)); + + /* + * Check if the host is >= VBox 5.0 which in case supports GUEST_DND_CONNECT. + */ + bool fSupportsConnectReq = false; + if (RT_SUCCESS(rc)) + { + /* The guest property service might not be available. Not fatal. */ + uint32_t uGuestPropSvcClientID; + rc2 = VbglR3GuestPropConnect(&uGuestPropSvcClientID); + if (RT_SUCCESS(rc2)) + { + char *pszHostVersion; + rc2 = VbglR3GuestPropReadValueAlloc(uGuestPropSvcClientID, "/VirtualBox/HostInfo/VBoxVer", &pszHostVersion); + if (RT_SUCCESS(rc2)) + { + fSupportsConnectReq = RTStrVersionCompare(pszHostVersion, "5.0") >= 0; + LogFlowFunc(("pszHostVersion=%s, fSupportsConnectReq=%RTbool\n", pszHostVersion, fSupportsConnectReq)); + VbglR3GuestPropReadValueFree(pszHostVersion); + } + + VbglR3GuestPropDisconnect(uGuestPropSvcClientID); + } + + if (RT_FAILURE(rc2)) + LogFlowFunc(("Retrieving host version failed with rc=%Rrc\n", rc2)); + } + + if (fSupportsConnectReq) + { + /* + * Try sending the connect message to tell the protocol version to use. + * Note: This might fail when the Guest Additions run on an older VBox host (< VBox 5.0) which + * does not implement this command. + */ + VBOXDNDCONNECTMSG Msg; + RT_ZERO(Msg); + + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_DND_CONNECT, 3); + /** @todo Context ID not used yet. */ + Msg.u.v3.uContext.SetUInt32(0); + Msg.u.v3.uProtocol.SetUInt32(pCtx->uProtocol); + Msg.u.v3.uFlags.SetUInt32(0); /* Unused at the moment. */ + + rc2 = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + if (RT_FAILURE(rc2)) + fSupportsConnectReq = false; + + LogFlowFunc(("Connection request ended with rc=%Rrc\n", rc2)); + } + + if (fSupportsConnectReq) + { + pCtx->cbMaxChunkSize = _64K; /** @todo Use a scratch buffer on the heap? */ + } + else /* GUEST_DND_CONNECT not supported; the user needs to upgrade the host. */ + rc = VERR_NOT_SUPPORTED; + + LogFlowFunc(("uClient=%RU32, uProtocol=%RU32, rc=%Rrc\n", pCtx->uClientID, pCtx->uProtocol, rc)); + return rc; +} + +/** + * Disconnects a given DnD context from the DnD host service. + * + * @returns IPRT status code. + * @param pCtx DnD context to disconnect. + * The context is invalid afterwards on successful disconnection. + */ +VBGLR3DECL(int) VbglR3DnDDisconnect(PVBGLR3GUESTDNDCMDCTX pCtx) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + int rc = VbglR3HGCMDisconnect(pCtx->uClientID); + if (RT_SUCCESS(rc)) + pCtx->uClientID = 0; + return rc; +} + +/** + * Receives the next upcoming DnD event. + * + * This is the main function DnD clients call in order to implement any DnD functionality. + * The purpose of it is to abstract the actual DnD protocol handling as much as possible from + * the clients -- those only need to react to certain events, regardless of how the underlying + * protocol actually is working. + * + * @returns IPRT status code. + * @param pCtx DnD context to work with. + * @param ppEvent Next DnD event received on success; needs to be free'd by the client calling + * VbglR3DnDEventFree() when done. + */ +VBGLR3DECL(int) VbglR3DnDEventGetNext(PVBGLR3GUESTDNDCMDCTX pCtx, PVBGLR3DNDEVENT *ppEvent) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(ppEvent, VERR_INVALID_POINTER); + + PVBGLR3DNDEVENT pEvent = (PVBGLR3DNDEVENT)RTMemAllocZ(sizeof(VBGLR3DNDEVENT)); + if (!pEvent) + return VERR_NO_MEMORY; + + uint32_t uMsg = 0; + uint32_t cParms = 0; + int rc = vbglR3DnDGetNextMsgType(pCtx, &uMsg, &cParms, true /* fWait */); + if (RT_SUCCESS(rc)) + { + /* Check for VM session change. */ + uint64_t uSessionID; + int rc2 = VbglR3GetSessionId(&uSessionID); + if ( RT_SUCCESS(rc2) + && (uSessionID != pCtx->uSessionID)) + { + LogFlowFunc(("VM session ID changed to %RU64\n", uSessionID)); + } + } + + if (RT_SUCCESS(rc)) + { + LogFunc(("Handling uMsg=%RU32\n", uMsg)); + + switch(uMsg) + { + case HOST_DND_HG_EVT_ENTER: + { + rc = vbglR3DnDHGRecvAction(pCtx, + uMsg, + &pEvent->u.HG_Enter.uScreenID, + NULL /* puXPos */, + NULL /* puYPos */, + NULL /* uDefAction */, + &pEvent->u.HG_Enter.dndLstActionsAllowed, + &pEvent->u.HG_Enter.pszFormats, + &pEvent->u.HG_Enter.cbFormats); + if (RT_SUCCESS(rc)) + pEvent->enmType = VBGLR3DNDEVENTTYPE_HG_ENTER; + break; + } + case HOST_DND_HG_EVT_MOVE: + { + rc = vbglR3DnDHGRecvAction(pCtx, + uMsg, + NULL /* puScreenId */, + &pEvent->u.HG_Move.uXpos, + &pEvent->u.HG_Move.uYpos, + &pEvent->u.HG_Move.dndActionDefault, + NULL /* puAllActions */, + NULL /* pszFormats */, + NULL /* pcbFormats */); + if (RT_SUCCESS(rc)) + pEvent->enmType = VBGLR3DNDEVENTTYPE_HG_MOVE; + break; + } + case HOST_DND_HG_EVT_DROPPED: + { + rc = vbglR3DnDHGRecvAction(pCtx, + uMsg, + NULL /* puScreenId */, + &pEvent->u.HG_Drop.uXpos, + &pEvent->u.HG_Drop.uYpos, + &pEvent->u.HG_Drop.dndActionDefault, + NULL /* puAllActions */, + NULL /* pszFormats */, + NULL /* pcbFormats */); + if (RT_SUCCESS(rc)) + pEvent->enmType = VBGLR3DNDEVENTTYPE_HG_DROP; + break; + } + case HOST_DND_HG_EVT_LEAVE: + { + rc = vbglR3DnDHGRecvLeave(pCtx); + if (RT_SUCCESS(rc)) + pEvent->enmType = VBGLR3DNDEVENTTYPE_HG_LEAVE; + break; + } + case HOST_DND_HG_SND_DATA_HDR: + { + rc = vbglR3DnDHGRecvDataMain(pCtx, &pEvent->u.HG_Received.Meta); + if (RT_SUCCESS(rc)) + pEvent->enmType = VBGLR3DNDEVENTTYPE_HG_RECEIVE; + break; + } + case HOST_DND_HG_SND_DIR: + RT_FALL_THROUGH(); + case HOST_DND_HG_SND_FILE_DATA: + { + /* + * All messages in this case are handled internally + * by vbglR3DnDHGRecvDataMain() and must be specified + * by preceeding HOST_DND_HG_SND_DATA or HOST_DND_HG_SND_DATA_HDR calls. + */ + rc = VERR_WRONG_ORDER; + break; + } + case HOST_DND_CANCEL: + { + rc = vbglR3DnDHGRecvCancel(pCtx); + if (RT_SUCCESS(rc)) + pEvent->enmType = VBGLR3DNDEVENTTYPE_HG_CANCEL; + break; + } +#ifdef VBOX_WITH_DRAG_AND_DROP_GH + case HOST_DND_GH_REQ_PENDING: + { + rc = vbglR3DnDGHRecvPending(pCtx, &pEvent->u.GH_IsPending.uScreenID); + if (RT_SUCCESS(rc)) + pEvent->enmType = VBGLR3DNDEVENTTYPE_GH_REQ_PENDING; + break; + } + case HOST_DND_GH_EVT_DROPPED: + { + rc = vbglR3DnDGHRecvDropped(pCtx, + &pEvent->u.GH_Drop.pszFormat, + &pEvent->u.GH_Drop.cbFormat, + &pEvent->u.GH_Drop.dndActionRequested); + if (RT_SUCCESS(rc)) + pEvent->enmType = VBGLR3DNDEVENTTYPE_GH_DROP; + break; + } +#endif + default: + { + rc = VERR_NOT_SUPPORTED; + break; + } + } + } + + if (RT_FAILURE(rc)) + { + VbglR3DnDEventFree(pEvent); + LogFlowFunc(("Failed with %Rrc\n", rc)); + } + else + *ppEvent = pEvent; + + return rc; +} + +/** + * Frees (destroys) a formerly allocated DnD event. + * + * @returns IPRT status code. + * @param pEvent Event to free (destroy). + */ +VBGLR3DECL(void) VbglR3DnDEventFree(PVBGLR3DNDEVENT pEvent) +{ + if (!pEvent) + return; + + /* Some messages require additional cleanup. */ + switch (pEvent->enmType) + { + case VBGLR3DNDEVENTTYPE_HG_ENTER: + { + if (pEvent->u.HG_Enter.pszFormats) + RTStrFree(pEvent->u.HG_Enter.pszFormats); + break; + } + +#ifdef VBOX_WITH_DRAG_AND_DROP_GH + case VBGLR3DNDEVENTTYPE_GH_DROP: + { + if (pEvent->u.GH_Drop.pszFormat) + RTStrFree(pEvent->u.GH_Drop.pszFormat); + break; + } +#endif + case VBGLR3DNDEVENTTYPE_HG_RECEIVE: + { + PVBGLR3GUESTDNDMETADATA pMeta = &pEvent->u.HG_Received.Meta; + if (pMeta->pvMeta) + { + Assert(pMeta->cbMeta); + RTMemFree(pMeta->pvMeta); + pMeta->cbMeta = 0; + } + break; + } + + default: + break; + } + + RTMemFree(pEvent); + pEvent = NULL; +} + +VBGLR3DECL(int) VbglR3DnDHGSendAckOp(PVBGLR3GUESTDNDCMDCTX pCtx, VBOXDNDACTION dndAction) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + VBOXDNDHGACKOPMSG Msg; + RT_ZERO(Msg); + + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_DND_HG_ACK_OP, 2); + /** @todo Context ID not used yet. */ + Msg.u.v3.uContext.SetUInt32(0); + Msg.u.v3.uAction.SetUInt32(dndAction); + + return VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); +} + +/** + * Host -> Guest + * Requests the actual DnD data to be sent from the host. + * + * @returns IPRT status code. + * @param pCtx DnD context to use. + * @param pcszFormat Format to request the data from the host in. + */ +VBGLR3DECL(int) VbglR3DnDHGSendReqData(PVBGLR3GUESTDNDCMDCTX pCtx, const char* pcszFormat) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pcszFormat, VERR_INVALID_POINTER); + if (!RTStrIsValidEncoding(pcszFormat)) + return VERR_INVALID_PARAMETER; + + const uint32_t cbFormat = (uint32_t)strlen(pcszFormat) + 1; /* Include termination */ + + VBOXDNDHGREQDATAMSG Msg; + RT_ZERO(Msg); + + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_DND_HG_REQ_DATA, 3); + /** @todo Context ID not used yet. */ + Msg.u.v3.uContext.SetUInt32(0); + Msg.u.v3.pvFormat.SetPtr((void*)pcszFormat, cbFormat); + Msg.u.v3.cbFormat.SetUInt32(cbFormat); + + return VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); +} + +/** + * Host -> Guest + * Reports back its progress back to the host. + * + * @returns IPRT status code. + * @param pCtx DnD context to use. + * @param uStatus DnD status to report. + * @param uPercent Overall progress (in percent) to report. + * @param rcErr Error code (IPRT-style) to report. + */ +VBGLR3DECL(int) VbglR3DnDHGSendProgress(PVBGLR3GUESTDNDCMDCTX pCtx, uint32_t uStatus, uint8_t uPercent, int rcErr) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertReturn(uStatus > DND_PROGRESS_UNKNOWN, VERR_INVALID_PARAMETER); + + VBOXDNDHGEVTPROGRESSMSG Msg; + RT_ZERO(Msg); + + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_DND_HG_EVT_PROGRESS, 4); + /** @todo Context ID not used yet. */ + Msg.u.v3.uContext.SetUInt32(0); + Msg.u.v3.uStatus.SetUInt32(uStatus); + Msg.u.v3.uPercent.SetUInt32(uPercent); + Msg.u.v3.rc.SetUInt32((uint32_t)rcErr); /* uint32_t vs. int. */ + + return VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); +} + +#ifdef VBOX_WITH_DRAG_AND_DROP_GH +/** + * Guest -> Host + * Acknowledges that there currently is a drag'n drop operation in progress on the guest, + * which eventually could be dragged over to the host. + * + * @returns IPRT status code. + * @param pCtx DnD context to use. + * @param dndActionDefault Default action for the operation to report. + * @param dndLstActionsAllowed All available actions for the operation to report. + * @param pcszFormats Available formats for the operation to report. + * @param cbFormats Size (in bytes) of formats to report. + */ +VBGLR3DECL(int) VbglR3DnDGHSendAckPending(PVBGLR3GUESTDNDCMDCTX pCtx, + VBOXDNDACTION dndActionDefault, VBOXDNDACTIONLIST dndLstActionsAllowed, + const char* pcszFormats, uint32_t cbFormats) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pcszFormats, VERR_INVALID_POINTER); + AssertReturn(cbFormats, VERR_INVALID_PARAMETER); + + if (!RTStrIsValidEncoding(pcszFormats)) + return VERR_INVALID_UTF8_ENCODING; + + VBOXDNDGHACKPENDINGMSG Msg; + RT_ZERO(Msg); + + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_DND_GH_ACK_PENDING, 5); + /** @todo Context ID not used yet. */ + Msg.u.v3.uContext.SetUInt32(0); + Msg.u.v3.uDefAction.SetUInt32(dndActionDefault); + Msg.u.v3.uAllActions.SetUInt32(dndLstActionsAllowed); + Msg.u.v3.pvFormats.SetPtr((void*)pcszFormats, cbFormats); + Msg.u.v3.cbFormats.SetUInt32(cbFormats); + + return VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); +} + +/** + * Guest -> Host + * Utility function to send DnD data from guest to the host. + * + * @returns IPRT status code. + * @param pCtx DnD context to use. + * @param pvData Data block to send. + * @param cbData Size (in bytes) of data block to send. + * @param pDataHdr Data header to use -- needed for accounting. + */ +static int vbglR3DnDGHSendDataInternal(PVBGLR3GUESTDNDCMDCTX pCtx, + void *pvData, uint64_t cbData, PVBOXDNDSNDDATAHDR pDataHdr) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pvData, VERR_INVALID_POINTER); + AssertReturn(cbData, VERR_INVALID_PARAMETER); + AssertPtrReturn(pDataHdr, VERR_INVALID_POINTER); + + VBOXDNDGHSENDDATAHDRMSG MsgHdr; + RT_ZERO(MsgHdr); + + VBGL_HGCM_HDR_INIT(&MsgHdr.hdr, pCtx->uClientID, GUEST_DND_GH_SND_DATA_HDR, 12); + MsgHdr.uContext.SetUInt32(0); /** @todo Not used yet. */ + MsgHdr.uFlags.SetUInt32(0); /** @todo Not used yet. */ + MsgHdr.uScreenId.SetUInt32(0); /** @todo Not used for guest->host (yet). */ + MsgHdr.cbTotal.SetUInt64(pDataHdr->cbTotal); + MsgHdr.cbMeta.SetUInt32(pDataHdr->cbMeta); + MsgHdr.pvMetaFmt.SetPtr(pDataHdr->pvMetaFmt, pDataHdr->cbMetaFmt); + MsgHdr.cbMetaFmt.SetUInt32(pDataHdr->cbMetaFmt); + MsgHdr.cObjects.SetUInt64(pDataHdr->cObjects); + MsgHdr.enmCompression.SetUInt32(0); /** @todo Not used yet. */ + MsgHdr.enmChecksumType.SetUInt32(RTDIGESTTYPE_INVALID); /** @todo Not used yet. */ + MsgHdr.pvChecksum.SetPtr(NULL, 0); /** @todo Not used yet. */ + MsgHdr.cbChecksum.SetUInt32(0); /** @todo Not used yet. */ + + int rc = VbglR3HGCMCall(&MsgHdr.hdr, sizeof(MsgHdr)); + + LogFlowFunc(("cbTotal=%RU64, cbMeta=%RU32, cObjects=%RU64, rc=%Rrc\n", + pDataHdr->cbTotal, pDataHdr->cbMeta, pDataHdr->cObjects, rc)); + + if (RT_SUCCESS(rc)) + { + VBOXDNDGHSENDDATAMSG MsgData; + RT_ZERO(MsgData); + + VBGL_HGCM_HDR_INIT(&MsgData.hdr, pCtx->uClientID, GUEST_DND_GH_SND_DATA, 5); + MsgData.u.v3.uContext.SetUInt32(0); /** @todo Not used yet. */ + MsgData.u.v3.pvChecksum.SetPtr(NULL, 0); /** @todo Not used yet. */ + MsgData.u.v3.cbChecksum.SetUInt32(0); /** @todo Not used yet. */ + + uint32_t cbCurChunk; + const uint32_t cbMaxChunk = pCtx->cbMaxChunkSize; + uint32_t cbSent = 0; + + HGCMFunctionParameter *pParm = &MsgData.u.v3.pvData; + + while (cbSent < cbData) + { + cbCurChunk = RT_MIN(cbData - cbSent, cbMaxChunk); + pParm->SetPtr(static_cast<uint8_t *>(pvData) + cbSent, cbCurChunk); + + MsgData.u.v3.cbData.SetUInt32(cbCurChunk); + + rc = VbglR3HGCMCall(&MsgData.hdr, sizeof(MsgData)); + if (RT_FAILURE(rc)) + break; + + cbSent += cbCurChunk; + } + + LogFlowFunc(("cbMaxChunk=%RU32, cbData=%RU64, cbSent=%RU32, rc=%Rrc\n", + cbMaxChunk, cbData, cbSent, rc)); + + if (RT_SUCCESS(rc)) + Assert(cbSent == cbData); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Guest -> Host + * Utility function to send a guest directory to the host. + * + * @returns IPRT status code. + * @param pCtx DnD context to use. + * @param pObj URI object containing the directory to send. + */ +static int vbglR3DnDGHSendDir(PVBGLR3GUESTDNDCMDCTX pCtx, DnDURIObject *pObj) +{ + AssertPtrReturn(pObj, VERR_INVALID_POINTER); + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertReturn(pObj->GetType() == DnDURIObject::Type_Directory, VERR_INVALID_PARAMETER); + + RTCString strPath = pObj->GetDestPathAbs(); + LogFlowFunc(("strDir=%s (%zu), fMode=0x%x\n", + strPath.c_str(), strPath.length(), pObj->GetMode())); + + if (strPath.length() > RTPATH_MAX) + return VERR_INVALID_PARAMETER; + + const uint32_t cbPath = (uint32_t)strPath.length() + 1; /* Include termination. */ + + VBOXDNDGHSENDDIRMSG Msg; + RT_ZERO(Msg); + + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_DND_GH_SND_DIR, 4); + /** @todo Context ID not used yet. */ + Msg.u.v3.uContext.SetUInt32(0); + Msg.u.v3.pvName.SetPtr((void *)strPath.c_str(), (uint32_t)cbPath); + Msg.u.v3.cbName.SetUInt32((uint32_t)cbPath); + Msg.u.v3.fMode.SetUInt32(pObj->GetMode()); + + return VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); +} + +/** + * Guest -> Host + * Utility function to send a file from the guest to the host. + * + * @returns IPRT status code. + * @param pCtx DnD context to use. + * @param pObj URI object containing the file to send. + */ +static int vbglR3DnDGHSendFile(PVBGLR3GUESTDNDCMDCTX pCtx, DnDURIObject *pObj) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pObj, VERR_INVALID_POINTER); + AssertReturn(pObj->GetType() == DnDURIObject::Type_File, VERR_INVALID_PARAMETER); + AssertReturn(pObj->IsOpen(), VERR_INVALID_STATE); + + uint32_t cbBuf = _64K; /** @todo Make this configurable? */ + void *pvBuf = RTMemAlloc(cbBuf); /** @todo Make this buffer part of PVBGLR3GUESTDNDCMDCTX? */ + if (!pvBuf) + return VERR_NO_MEMORY; + + RTCString strPath = pObj->GetDestPathAbs(); + + LogFlowFunc(("strFile=%s (%zu), cbSize=%RU64, fMode=0x%x\n", strPath.c_str(), strPath.length(), + pObj->GetSize(), pObj->GetMode())); + + VBOXDNDGHSENDFILEHDRMSG MsgHdr; + RT_ZERO(MsgHdr); + + VBGL_HGCM_HDR_INIT(&MsgHdr.hdr, pCtx->uClientID, GUEST_DND_GH_SND_FILE_HDR, 6); + MsgHdr.uContext.SetUInt32(0); /* Context ID; unused at the moment. */ + MsgHdr.pvName.SetPtr((void *)strPath.c_str(), (uint32_t)(strPath.length() + 1)); + MsgHdr.cbName.SetUInt32((uint32_t)(strPath.length() + 1)); + MsgHdr.uFlags.SetUInt32(0); /* Flags; unused at the moment. */ + MsgHdr.fMode.SetUInt32(pObj->GetMode()); /* File mode */ + MsgHdr.cbTotal.SetUInt64(pObj->GetSize()); /* File size (in bytes). */ + + int rc = VbglR3HGCMCall(&MsgHdr.hdr, sizeof(MsgHdr)); + + LogFlowFunc(("Sending file header resulted in %Rrc\n", rc)); + + if (RT_SUCCESS(rc)) + { + /* + * Send the actual file data, chunk by chunk. + */ + VBOXDNDGHSENDFILEDATAMSG Msg; + RT_ZERO(Msg); + + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_DND_GH_SND_FILE_DATA, 5); + Msg.u.v3.uContext.SetUInt32(0); + Msg.u.v3.pvChecksum.SetPtr(NULL, 0); + Msg.u.v3.cbChecksum.SetUInt32(0); + + uint64_t cbToReadTotal = pObj->GetSize(); + uint64_t cbWrittenTotal = 0; + while (cbToReadTotal) + { + uint32_t cbToRead = RT_MIN(cbToReadTotal, cbBuf); + uint32_t cbRead = 0; + if (cbToRead) + rc = pObj->Read(pvBuf, cbToRead, &cbRead); + + LogFlowFunc(("cbToReadTotal=%RU64, cbToRead=%RU32, cbRead=%RU32, rc=%Rrc\n", + cbToReadTotal, cbToRead, cbRead, rc)); + + if ( RT_SUCCESS(rc) + && cbRead) + { + Msg.u.v3.pvData.SetPtr(pvBuf, cbRead); + Msg.u.v3.cbData.SetUInt32(cbRead); + /** @todo Calculate + set checksums. */ + + rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + } + + if (RT_FAILURE(rc)) + { + LogFlowFunc(("Reading failed with rc=%Rrc\n", rc)); + break; + } + + Assert(cbRead <= cbToReadTotal); + cbToReadTotal -= cbRead; + cbWrittenTotal += cbRead; + + LogFlowFunc(("%RU64/%RU64 -- %RU8%%\n", cbWrittenTotal, pObj->GetSize(), cbWrittenTotal * 100 / pObj->GetSize())); + }; + } + + RTMemFree(pvBuf); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Guest -> Host + * Utility function to send an URI object from guest to the host. + * + * @returns IPRT status code. + * @param pCtx DnD context to use. + * @param pObj URI object to send from guest to the host. + */ +static int vbglR3DnDGHSendURIObject(PVBGLR3GUESTDNDCMDCTX pCtx, DnDURIObject *pObj) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pObj, VERR_INVALID_POINTER); + + int rc; + + switch (pObj->GetType()) + { + case DnDURIObject::Type_Directory: + rc = vbglR3DnDGHSendDir(pCtx, pObj); + break; + + case DnDURIObject::Type_File: + rc = vbglR3DnDGHSendFile(pCtx, pObj); + break; + + default: + AssertMsgFailed(("Object type %ld not implemented\n", pObj->GetType())); + rc = VERR_NOT_IMPLEMENTED; + break; + } + + return rc; +} + +/** + * Guest -> Host + * Utility function to send raw data from guest to the host. + * + * @returns IPRT status code. + * @param pCtx DnD context to use. + * @param pvData Block to raw data to send. + * @param cbData Size (in bytes) of raw data to send. + */ +static int vbglR3DnDGHSendRawData(PVBGLR3GUESTDNDCMDCTX pCtx, void *pvData, size_t cbData) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pvData, VERR_INVALID_POINTER); + /* cbData can be 0. */ + + VBOXDNDDATAHDR dataHdr; + RT_ZERO(dataHdr); + + /* For raw data only the total size is required to be specified. */ + dataHdr.cbTotal = cbData; + + return vbglR3DnDGHSendDataInternal(pCtx, pvData, cbData, &dataHdr); +} + +/** + * Guest -> Host + * Utility function to send URI data from guest to the host. + * + * @returns IPRT status code. + * @param pCtx DnD context to use. + * @param pvData Block to URI data to send. + * @param cbData Size (in bytes) of URI data to send. + */ +static int vbglR3DnDGHSendURIData(PVBGLR3GUESTDNDCMDCTX pCtx, const void *pvData, size_t cbData) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pvData, VERR_INVALID_POINTER); + AssertReturn(cbData, VERR_INVALID_PARAMETER); + + RTCList<RTCString> lstPaths = + RTCString((const char *)pvData, cbData).split("\r\n"); + + /** @todo Add symlink support (DNDURILIST_FLAGS_RESOLVE_SYMLINKS) here. */ + /** @todo Add lazy loading (DNDURILIST_FLAGS_LAZY) here. */ + uint32_t fFlags = DNDURILIST_FLAGS_KEEP_OPEN; + + DnDURIList lstURI; + int rc = lstURI.AppendURIPathsFromList(lstPaths, fFlags); + if (RT_SUCCESS(rc)) + { + /* + * Send the (meta) data; in case of URIs it's the (non-recursive) file/directory + * URI list the host needs to know upfront to set up the drag'n drop operation. + */ + RTCString strRootDest = lstURI.GetRootEntries(); + if (strRootDest.isNotEmpty()) + { + void *pvURIList = (void *)strRootDest.c_str(); /* URI root list. */ + uint32_t cbURLIist = (uint32_t)strRootDest.length() + 1; /* Include string termination. */ + + /* The total size also contains the size of the meta data. */ + uint64_t cbTotal = cbURLIist; + cbTotal += lstURI.GetTotalBytes(); + + /* We're going to send an URI list in text format. */ + const char szMetaFmt[] = "text/uri-list"; + const uint32_t cbMetaFmt = (uint32_t)strlen(szMetaFmt) + 1; /* Include termination. */ + + VBOXDNDDATAHDR dataHdr; + dataHdr.uFlags = 0; /* Flags not used yet. */ + dataHdr.cbTotal = cbTotal; + dataHdr.cbMeta = cbURLIist; + dataHdr.pvMetaFmt = (void *)szMetaFmt; + dataHdr.cbMetaFmt = cbMetaFmt; + dataHdr.cObjects = lstURI.GetTotalCount(); + + rc = vbglR3DnDGHSendDataInternal(pCtx, + pvURIList, cbURLIist, &dataHdr); + } + else + rc = VERR_INVALID_PARAMETER; + } + + if (RT_SUCCESS(rc)) + { + while (!lstURI.IsEmpty()) + { + DnDURIObject *pNextObj = lstURI.First(); + + rc = vbglR3DnDGHSendURIObject(pCtx, pNextObj); + if (RT_FAILURE(rc)) + break; + + lstURI.RemoveFirst(); + } + } + + return rc; +} + +/** + * Guest -> Host + * Sends data, which either can be raw or URI data, from guest to the host. This function + * initiates the actual data transfer from guest to the host. + * + * @returns IPRT status code. + * @param pCtx DnD context to use. + * @param pszFormat In which format the data will be sent. + * @param pvData Data block to send. + * @param cbData Size (in bytes) of data block to send. + */ +VBGLR3DECL(int) VbglR3DnDGHSendData(PVBGLR3GUESTDNDCMDCTX pCtx, const char *pszFormat, void *pvData, uint32_t cbData) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pszFormat, VERR_INVALID_POINTER); + AssertPtrReturn(pvData, VERR_INVALID_POINTER); + AssertReturn(cbData, VERR_INVALID_PARAMETER); + + LogFlowFunc(("pszFormat=%s, pvData=%p, cbData=%RU32\n", pszFormat, pvData, cbData)); + + int rc; + if (DnDMIMEHasFileURLs(pszFormat, strlen(pszFormat))) + { + /* Send file data. */ + rc = vbglR3DnDGHSendURIData(pCtx, pvData, cbData); + } + else + rc = vbglR3DnDGHSendRawData(pCtx, pvData, cbData); + + if (RT_FAILURE(rc)) + { + int rc2 = VbglR3DnDGHSendError(pCtx, rc); + if (RT_FAILURE(rc2)) + LogFlowFunc(("Unable to send error (%Rrc) to host, rc=%Rrc\n", rc, rc2)); + } + + return rc; +} + +/** + * Guest -> Host + * Send an error back to the host. + * + * @returns IPRT status code. + * @param pCtx DnD context to use. + * @param rcErr Error (IPRT-style) to send. + */ +VBGLR3DECL(int) VbglR3DnDGHSendError(PVBGLR3GUESTDNDCMDCTX pCtx, int rcErr) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + VBOXDNDGHEVTERRORMSG Msg; + RT_ZERO(Msg); + + VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_DND_GH_EVT_ERROR, 2); + /** @todo Context ID not used yet. */ + Msg.u.v3.uContext.SetUInt32(0); + Msg.u.v3.rc.SetUInt32((uint32_t)rcErr); /* uint32_t vs. int. */ + + int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg)); + + /* + * Never return an error if the host did not accept the error at the current + * time. This can be due to the host not having any appropriate callbacks + * set which would handle that error. + * + * bird: Looks like VERR_NOT_SUPPORTED is what the host will return if it + * doesn't an appropriate callback. The code used to ignore ALL errors + * the host would return, also relevant ones. + */ + if (RT_FAILURE(rc)) + LogFlowFunc(("Sending error %Rrc failed with rc=%Rrc\n", rcErr, rc)); + if (rc == VERR_NOT_SUPPORTED) + rc = VINF_SUCCESS; + + return rc; +} +#endif /* VBOX_WITH_DRAG_AND_DROP_GH */ + |