GuestDnDImpl.cpp revision 99f33ab590a3a65e0cd082dd8d67779efb9cc6c9
/* $Id$ */
/** @file
* VirtualBox COM class implementation: Guest Drag and Drop parts
*/
/*
* Copyright (C) 2011-2013 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* 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 "GuestImpl.h"
#include "AutoCaller.h"
#ifdef VBOX_WITH_DRAG_AND_DROP
# include "ConsoleImpl.h"
# include "ProgressImpl.h"
# include "GuestDnDImpl.h"
# include <VMMDev.h>
# ifdef LOG_GROUP
# endif
# define LOG_GROUP LOG_GROUP_GUEST_DND
# include <iprt/semaphore.h>
/* How does this work:
*
* Drag and Drop is handled over the internal HGCM service for the host <->
* guest communication. Beside that we need to map the Drag and Drop protocols
* of the various OS's we support to our internal channels, this is also highly
* communicative in both directions. Unfortunately HGCM isn't really designed
* for that. Next we have to foul some of the components. This includes to
* trick X11 on the guest side, but also Qt needs to be tricked on the host
* side a little bit.
*
* The following components are involved:
*
* 1. GUI: Uses the Qt classes for Drag and Drop and mainly forward the content
* of it to the Main IGuest interface (see UIDnDHandler.cpp).
* 2. Main: Public interface for doing Drag and Drop. Also manage the IProgress
* interfaces for blocking the caller by showing a progress dialog (see
* this file).
* 3. HGCM service: Handle all messages from the host to the guest at once and
* encapsulate the internal communication details (see dndmanager.cpp and
* friends).
* 4. Guest additions: Split into the platform neutral part (see
* VBoxGuestR3LibDragAndDrop.cpp) and the guest OS specific parts.
* operations. Currently only X11 is supported (see draganddrop.cpp within
* VBoxClient).
*
* Host -> Guest:
* 1. There are DnD Enter, Move, Leave events which are send exactly like this
* to the guest. The info includes the pos, mimetypes and allowed actions.
* The guest has to respond with an action it would accept, so the GUI could
* change the cursor.
* 2. On drop, first a drop event is send. If this is accepted a drop data
* event follows. This blocks the GUI and shows some progress indicator.
*
* Guest -> Host:
* 1. The GUI is asking the guest if a DnD event is pending when the user moves
* the cursor out of the view window. If so, this returns the mimetypes and
* allowed actions.
* (2. On every mouse move this is asked again, to make sure the DnD event is
* still valid.)
* 3. On drop the host request the data from the guest. This blocks the GUI and
* shows some progress indicator.
*
* Some hints:
* m_sstrAllowedMimeTypes here in this file defines the allowed mime-types.
* This is necessary because we need special handling for some of the
* mime-types. E.g. for URI lists we need to transfer the actual dirs and
* files. Text EOL may to be changed. Also unknown mime-types may need special
* not done.
*
* Dropping of a directory, means recursively transferring _all_ the content.
*
* Directories and files are placed into the user's temporary directory on the
* guest (e.g. /tmp/VirtualBox Dropped Files). We can't delete them after the
* DnD operation, because we didn't know what the DnD target does with it. E.g.
* it could just be opened in place. This could lead ofc to filling up the disk
* within the guest. To inform the user about this, a small app could be
* developed which scans this directory regularly and inform the user with a
* tray icon hint (and maybe the possibility to clean this up instantly). The
* same has to be done in the G->H direction when it is implemented.
*
* Of course only regularly files are supported. Symlinks are resolved and
* transfered as regularly files. First we don't know if the other side support
* symlinks at all and second they could point to somewhere in a directory tree
* which not exists on the other side.
*
* is useful (and maybe necessary) for two things:
* 1. If a file is executable, it should be also after the transfer, so the
* user can just execute it, without manually tweaking the modes first.
* be in the guest.
* In any case, the user mode is always set to rwx (so that we can access it
* ourself, in e.g. for a cleanup case after cancel).
*
* Cancel is supported in both directions and cleans up all previous steps
*
* In general I propose the following changes in the VBox HGCM infrastructure
* for the future:
* - Currently it isn't really possible to send messages to the guest from the
* host. The host informs the guest just that there is something, the guest
* than has to ask which message and depending on that send the appropriate
* message to the host, which is filled with the right data.
* guest. This is now done here, but I guess was also necessary for e.g.
* guest execution. So something generic which brake this up into smaller
* blocks and send it would be nice (with all the error handling and such
* ofc).
* - I developed a "protocol" for the DnD communication here. So the host and
* the guest have always to match in the revision. This is ofc bad, because
* the additions could be outdated easily. So some generic protocol number
* support in HGCM for asking the host and the guest of the support version,
* would be nice. Ofc at least the host should be able to talk to the guest,
* even when the version is below the host one.
* All this stuff would be useful for the current services, but also for future
* onces.
*
* Todo:
* - Dragging out of the guest (partly done)
* - ESC doesn't really work (on Windows guests it's already implemented)
* - transfer of URIs (that is the files and patching of the data)
* - testing in a multi monitor setup
* ... in any case it seems a little bit difficult to handle from the Qt
* side. Maybe also a host specific implementation becomes necessary ...
* this would be really worst ofc.
* - Win guest support (maybe there have to be done a mapping between the
* official mime-types and Win Clipboard formats (see QWindowsMime, for an
* idea), for VBox internally only mime-types should be used)
* - EOL handling on text (should be shared with the clipboard code)
* - add configuration (GH, HG, Bidirectional, None), like for the clipboard
* - HG->GH and GH->HG-switch: Handle the case the user drags something out of
* the guest and than return to the source view (or another window in the
* multiple guest screen scenario).
* - add support for more mime-types (especially images, csv)
* - test unusual behavior:
* - DnD service crash in the guest during a DnD op (e.g. crash of VBoxClient or X11)
* - not expected order of the events between HGCM and the guest
* - Security considerations: We transfer a lot of memory between the guest and
* limits introduced to preventing DOS attacks or filling up all the memory
* (both in the host and the guest).
* - test, test, test ...
*/
class DnDGuestResponse
{
public:
~DnDGuestResponse();
int notifyAboutGuestResponse();
int waitForGuestResponse();
void resetData();
private:
void *m_pvData;
};
class GuestDnDPrivate
{
private:
/* todo: currently we only support one response. Maybe this needs to be extended at some time. */
: q_ptr(q)
, p(pGuest)
{}
~GuestDnDPrivate() { delete m_pDnDResponse; }
/* Static helper */
static void toHGCMActions(DragAndDropAction_T inDefAction, uint32_t *pOutDefAction, ComSafeArrayIn(DragAndDropAction_T, inAllowedActions), uint32_t *pOutAllowedActions);
/* Private q and parent pointer */
/* Private helper members */
friend class GuestDnD;
};
/* What mime-types are supported by VirtualBox.
* Note: If you add something here, make sure you test it with all guest OS's!
** @todo Make this MIME list configurable / extendable (by extra data?). Currently
*/
/* static */
/* Uri's */
/* Text */
<< "UTF8_STRING"
<< "COMPOUND_TEXT"
<< "TEXT"
<< "STRING"
/* OpenOffice formates */
<< "application/x-openoffice-embed-source-xml;windows_formatname=\"Star Embed Source (XML)\""
<< "application/x-openoffice-drawing;windows_formatname=\"Drawing Format\"";
, m_defAction(0)
, m_allActions(0)
, m_pvData(0)
, m_cbData(0)
{
}
{
resetData();
}
{
return RTSemEventSignal(m_EventSem);
}
{
#ifdef DEBUG_andy
#endif
return vrc;
}
{
int rc = VINF_SUCCESS;
if (m_pvData)
{
*pcbCurSize = m_cbData;
}
else
rc = VERR_NO_MEMORY;
return rc;
}
void DnDGuestResponse::resetData()
{
if (m_pvData)
{
}
m_cbData = 0;
}
{
{
TRUE);
}
return rc;
}
int DnDGuestResponse::setProgress(unsigned uPercentage, uint32_t uState, int rcOp /* = VINF_SUCCESS */)
{
int vrc = VINF_SUCCESS;
if (!m_progress.isNull())
{
if (!fCompleted)
{
{
}
{
}
else /* uState == DragAndDropSvc::DND_PROGRESS_RUNNING */
{
#ifndef DEBUG_andy
#endif
|| uPercentage >= 100)
}
#ifndef DEBUG_andy
#endif
}
}
return vrc;
}
{
}
{
/* For multi-monitor support we need to add shift values to the coordinates
* (depending on the screen number). */
}
void GuestDnDPrivate::hostCall(uint32_t u32Function, uint32_t cParms, PVBOXHGCMSVCPARM paParms) const
{
{
/* Make sure mParent is valid, so set the read lock while using.
* Do not keep this lock while doing the actual call, because in the meanwhile
* another thread could request a write lock which would be a bad idea ... */
/* Forward the information to the VMM device. */
}
if (!vmmDev)
throw p->setError(VBOX_E_VM_ERROR,
p->tr("VMM device is not available (is the VM running?)"));
if (RT_FAILURE(vrc))
{
throw p->setError(VBOX_E_VM_ERROR,
}
}
/* static */
{
{
/* Only keep allowed format types. */
if (m_sstrAllowedMimeTypes.contains(f))
strFormat += f + "\r\n";
}
return strFormat;
}
/* static */
void GuestDnDPrivate::toFormatSafeArray(const RTCString &strFormats, ComSafeArrayOut(BSTR, formats))
{
size_t i = 0;
{
/* Only keep allowed format types. */
else
++i;
}
/* Create a safe array out of the cleaned list. */
{
if (m_sstrAllowedMimeTypes.contains(f))
{
}
}
}
/* static */
{
switch (action)
{
case DragAndDropAction_Copy: a = DND_COPY_ACTION; break;
case DragAndDropAction_Move: a = DND_MOVE_ACTION; break;
case DragAndDropAction_Link: /* For now it doesn't seems useful to allow a link action between host & guest. Maybe later! */
case DragAndDropAction_Ignore: /* Ignored */ break;
}
return a;
}
/* static */
void GuestDnDPrivate::toHGCMActions(DragAndDropAction_T inDefAction, uint32_t *pOutDefAction, ComSafeArrayIn(DragAndDropAction_T, inAllowedActions), uint32_t *pOutAllowedActions)
{
/* Defaults */
/* First convert the allowed actions to a bit array. */
/* Second check if the default action is a valid action and if not so try
* to find an allowed action. */
if (isDnDIgnoreAction(*pOutDefAction))
{
else if (hasDnDMoveAction(*pOutAllowedActions))
}
}
/* static */
{
/* For now it doesn't seems useful to allow a link action between host & guest. Maybe later! */
}
/* static */
void GuestDnDPrivate::toMainActions(uint32_t uActions, ComSafeArrayOut(DragAndDropAction_T, actions))
{
/* For now it doesn't seems useful to allow a link action between host & guest. Maybe later! */
if (hasDnDCopyAction(uActions))
if (hasDnDMoveAction(uActions))
}
{
}
{
delete d_ptr;
}
HRESULT GuestDnD::dragHGEnter(ULONG uScreenId, ULONG uX, ULONG uY, DragAndDropAction_T defaultAction, ComSafeArrayIn(DragAndDropAction_T, allowedActions), ComSafeArrayIn(IN_BSTR, formats), DragAndDropAction_T *pResultAction)
{
/* Default is ignoring */
/* Check & convert the drag & drop actions */
uint32_t uDefAction = 0;
uint32_t uAllowedActions = 0;
/* If there is no usable action, ignore this request. */
if (isDnDIgnoreAction(uDefAction))
return S_OK;
/* Make a flat data string out of the mime-type list. */
/* If there is no valid mime-type, ignore this request. */
if (strFormats.isEmpty())
return S_OK;
try
{
/* Adjust the coordinates in a multi-monitor setup. */
int i = 0;
i,
paParms);
/* This blocks until the request is answered (or timeout). */
return S_OK;
/* Copy the response info */
}
{
}
return rc;
}
HRESULT GuestDnD::dragHGMove(ULONG uScreenId, ULONG uX, ULONG uY, DragAndDropAction_T defaultAction, ComSafeArrayIn(DragAndDropAction_T, allowedActions), ComSafeArrayIn(IN_BSTR, formats), DragAndDropAction_T *pResultAction)
{
/* Default is ignoring */
/* Check & convert the drag & drop actions */
uint32_t uDefAction = 0;
uint32_t uAllowedActions = 0;
/* If there is no usable action, ignore this request. */
if (isDnDIgnoreAction(uDefAction))
return S_OK;
/* Make a flat data string out of the mime-type list. */
/* If there is no valid mime-type, ignore this request. */
if (strFormats.isEmpty())
return S_OK;
try
{
/* Adjust the coordinates in a multi-monitor setup. */
int i = 0;
i,
paParms);
/* This blocks until the request is answered (or timeout). */
return S_OK;
/* Copy the response info */
}
{
}
return rc;
}
{
try
{
0,
NULL);
/* This blocks until the request is answered (or timeout). */
}
{
}
return rc;
}
HRESULT GuestDnD::dragHGDrop(ULONG uScreenId, ULONG uX, ULONG uY, DragAndDropAction_T defaultAction, ComSafeArrayIn(DragAndDropAction_T, allowedActions), ComSafeArrayIn(IN_BSTR, formats), BSTR *pstrFormat, DragAndDropAction_T *pResultAction)
{
/* Default is ignoring */
/* Check & convert the drag & drop actions */
uint32_t uDefAction = 0;
uint32_t uAllowedActions = 0;
/* If there is no usable action, ignore this request. */
if (isDnDIgnoreAction(uDefAction))
return S_OK;
/* Make a flat data string out of the mime-type list. */
/* If there is no valid mime-type, ignore this request. */
if (strFormats.isEmpty())
return S_OK;
try
{
/* Adjust the coordinates in a multi-monitor setup. */
int i = 0;
i,
paParms);
/* This blocks until the request is answered (or timeout). */
return S_OK;
/* Copy the response info */
}
{
}
return rc;
}
HRESULT GuestDnD::dragHGPutData(ULONG uScreenId, IN_BSTR bstrFormat, ComSafeArrayIn(BYTE, data), IProgress **ppProgress)
{
try
{
int i = 0;
/* Reset any old progress status. */
pDnD->resetProgress(p);
i,
paParms);
/* Query the progress object to the caller. */
}
{
}
return rc;
}
# ifdef VBOX_WITH_DRAG_AND_DROP_GH
HRESULT GuestDnD::dragGHPending(ULONG uScreenId, ComSafeArrayOut(BSTR, formats), ComSafeArrayOut(DragAndDropAction_T, allowedActions), DragAndDropAction_T *pDefaultAction)
{
/* Default is ignoring */
try
{
int i = 0;
i,
paParms);
/* This blocks until the request is answered (or timeout). */
return S_OK;
return S_OK;
/* Fetch the default action to use. */
/* Convert the formats strings to a vector of strings. */
/* Convert the action bit field to a vector of actions. */
}
{
}
return rc;
}
HRESULT GuestDnD::dragGHDropped(IN_BSTR bstrFormat, DragAndDropAction_T action, IProgress **ppProgress)
{
/* If there is no usable action, ignore this request. */
if (isDnDIgnoreAction(uAction))
return S_OK;
try
{
int i = 0;
/* Reset any old data and the progress status. */
pDnD->resetProgress(p);
i,
paParms);
/* Query the progress object to the caller. */
}
{
}
return rc;
}
{
/* Is there data at all? */
{
/* Copy the data into an safe array of bytes. */
void *pvData = 0;
/* Delete the data. */
}
else
return rc;
}
# endif /* VBOX_WITH_DRAG_AND_DROP_GH */
DECLCALLBACK(int) GuestDnD::notifyGuestDragAndDropEvent(void *pvExtension, uint32_t u32Function, void *pvParms, uint32_t cbParms)
{
LogFlowFunc(("pvExtension=%p, u32Function=%RU32, pvParms=%p, cbParms=%RU32\n",
if (!pGuest->m_pGuestDnD)
return VINF_SUCCESS;
return VERR_INVALID_PARAMETER;
int rc = VINF_SUCCESS;
switch (u32Function)
{
{
DragAndDropSvc::PVBOXDNDCBHGACKOPDATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBHGACKOPDATA>(pvParms);
AssertReturn(DragAndDropSvc::CB_MAGIC_DND_HG_ACK_OP == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
break;
}
{
DragAndDropSvc::PVBOXDNDCBHGREQDATADATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBHGREQDATADATA>(pvParms);
AssertReturn(DragAndDropSvc::CB_MAGIC_DND_HG_REQ_DATA == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
break;
}
{
DragAndDropSvc::PVBOXDNDCBHGEVTPROGRESSDATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBHGEVTPROGRESSDATA>(pvParms);
AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBHGEVTPROGRESSDATA) == cbParms, VERR_INVALID_PARAMETER);
AssertReturn(DragAndDropSvc::CB_MAGIC_DND_HG_EVT_PROGRESS == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
break;
}
#ifdef VBOX_WITH_DRAG_AND_DROP_GH
{
DragAndDropSvc::PVBOXDNDCBGHACKPENDINGDATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBGHACKPENDINGDATA>(pvParms);
AssertReturn(DragAndDropSvc::CB_MAGIC_DND_GH_ACK_PENDING == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
break;
}
{
DragAndDropSvc::PVBOXDNDCBSNDDATADATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBSNDDATADATA>(pvParms);
AssertReturn(DragAndDropSvc::CB_MAGIC_DND_GH_SND_DATA == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
rc = pResp->setProgress(100.0 / pCBData->cbAllSize * cbCurSize, (pCBData->cbAllSize == cbCurSize ? DragAndDropSvc::DND_PROGRESS_COMPLETE : DragAndDropSvc::DND_PROGRESS_RUNNING));
/* Todo: for now we instantly confirm the cancel. Check if the
* guest should first clean up stuff itself and than really confirm
* the cancel request by an extra message. */
if (rc == VERR_CANCELLED)
break;
}
{
DragAndDropSvc::PVBOXDNDCBEVTERRORDATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBEVTERRORDATA>(pvParms);
AssertReturn(DragAndDropSvc::CB_MAGIC_DND_GH_EVT_ERROR == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
/* Cleanup */
break;
}
#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
default:
break;
}
return rc;
}
#endif /* VBOX_WITH_DRAG_AND_DROP */