GuestDnDPrivate.cpp revision 1a3b02207c6b6aca1b7bdd50fc2e79defe4e405e
/* $Id$ */
/** @file
* Private guest drag and drop code, used by GuestDnDTarget +
* GuestDnDSource.
*/
/*
* Copyright (C) 2011-2014 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 "GuestDnDPrivate.h"
# include <algorithm>
# include <iprt/semaphore.h>
# include <VMMDev.h>
# ifdef LOG_GROUP
# endif
# define LOG_GROUP LOG_GROUP_GUEST_DND
/**
* Overview:
*
* 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 sent. 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_strSupportedFormats 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
* - ESC doesn't really work (on Windows guests it's already implemented)
* ... 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.
* - 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).
*/
, m_defAction(0)
, m_allActions(0)
, m_pvData(0)
, m_cbData(0)
, m_cbDataCurrent(0)
, m_cbDataTotal(0)
{
}
GuestDnDResponse::~GuestDnDResponse(void)
{
reset();
}
{
/* pcbCurSize is optional. */
int rc = VINF_SUCCESS;
/** @todo Make reallocation scheme a bit smarter here. */
if (m_pvData)
{
if (pcbCurSize)
*pcbCurSize = m_cbData;
}
else
rc = VERR_NO_MEMORY;
return rc;
}
/* static */
{
switch (guestRc)
{
case VERR_ACCESS_DENIED:
strError += Utf8StrFmt(pGuest->tr("For one or more guest files or directories selected for transferring to the host your guest "
"user does not have the appropriate access rights for. Please make sure that all selected "
"elements can be accessed and that your guest user has the appropriate rights."));
break;
case VERR_NOT_FOUND:
/* Should not happen due to file locking on the guest, but anyway ... */
strError += Utf8StrFmt(pGuest->tr("One or more guest files or directories selected for transferring to the host were not"
"altered while the drag and drop operation was in progress."));
break;
case VERR_SHARING_VIOLATION:
strError += Utf8StrFmt(pGuest->tr("One or more guest files or directories selected for transferring to the host were locked. "
"Please make sure that all selected elements can be accessed and that your guest user has "
"the appropriate rights."));
break;
default:
break;
}
return strError;
}
int GuestDnDResponse::notifyAboutGuestResponse(void)
{
return RTSemEventSignal(m_EventSem);
}
void GuestDnDResponse::reset(void)
{
m_defAction = 0;
m_allActions = 0;
m_strDropDir = "";
m_strFormat = "";
if (m_pvData)
{
}
m_cbData = 0;
m_cbDataCurrent = 0;
m_cbDataTotal = 0;
if (m_hFile != NIL_RTFILE)
{
}
m_strFile = "";
}
{
{
TRUE);
}
return rc;
}
{
LogFlowFunc(("uPercentage=%RU32, uState=%RU32, rcOp=%Rrc\n",
int vrc = VINF_SUCCESS;
if (!m_progress.isNull())
{
if (!fCompleted)
{
{
reset();
}
{
reset();
}
else /* uState == DragAndDropSvc::DND_PROGRESS_RUNNING */
{
|| uPercentage >= 100)
}
}
}
return vrc;
}
{
if (cbDataTotal)
{
#ifndef DEBUG_andy
AssertMsg(m_cbDataTotal <= cbDataTotal, ("New data size must not be smaller (%zu) than old value (%zu)\n",
#endif
}
/** @todo Don't use anonymous enums (uint32_t). */
if (m_cbDataCurrent >= m_cbDataTotal)
#ifdef DEBUG_andy
LogFlowFunc(("Updating transfer status (%zu/%zu), status=%ld\n",
#else
("More data transferred (%zu) than initially announced (%zu), cbDataAdd=%zu\n",
#endif
/** @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)
return rc;
}
{
}
{
#ifdef DEBUG_andy
#endif
return rc;
}
{
/** @todo Support locking more than one file at a time! We
* might want to have a table in DnDGuestImpl which
* keeps those file pointers around, or extend the
* actual protocol for explicit open calls.
*
* For now we only keep one file open at a time, so if
* a client does alternating writes to different files
* this function will close the old and re-open the new
* file on every call. */
int rc;
if ( m_hFile == NIL_RTFILE
{
if (pszFile)
{
/** @todo Respect fMode! */
if (RT_SUCCESS(rc))
{
LogFlowFunc(("Opening \"%s\" (fMode=0x%x) for writing ...\n",
}
}
else
rc = VERR_NO_MEMORY;
}
else
rc = VINF_SUCCESS;
if (RT_SUCCESS(rc))
{
NULL /* No partial writes */);
if (RT_SUCCESS(rc))
}
return rc;
}
///////////////////////////////////////////////////////////////////////////////
{
/* List of supported default MIME types. */
}
{
if (m_pResponse)
delete m_pResponse;
}
{
/** @todo r=andy Save the current screen's shifting coordinates to speed things up.
* Only query for new offsets when the screen ID has changed. */
/* For multi-monitor support we need to add shift values to the coordinates
* (depending on the screen number). */
return hr;
return hr;
if (puX)
if (puY)
#ifdef DEBUG_andy
LogFlowFunc(("uScreenId=%RU32, x=%RU32, y=%RU32\n",
#endif
return VINF_SUCCESS;
}
{
/* Forward the information to the VMM device. */
if (!pVMMDev)
return VERR_COM_OBJECT_NOT_FOUND;
LogFlowFunc(("uMsg=%RU32, cParms=%RU32, rc=%Rrc\n",
return rc;
}
/* static */
{
{
/* Only keep allowed format types. */
strFormat += f + "\r\n";
}
return strFormat;
}
/* static */
{
size_t i = 0;
while (i < lstFormats.size())
{
/* Only keep allowed format types. */
lstFormats.removeAt(i);
else
++i;
}
for (i = 0; i < lstFormats.size(); i++)
{
}
}
/* static */
{
switch (enmAction)
{
case DnDAction_Copy:
break;
case DnDAction_Move:
break;
case DnDAction_Link:
/* For now it doesn't seems useful to allow a link
action between host & guest. Later? */
case DnDAction_Ignore:
/* Ignored. */
break;
default:
break;
}
return uAction;
}
/* static */
{
if (puDefAction)
if (puAllowedActions)
{
/* 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 (hasDnDCopyAction(*puAllowedActions))
else if (hasDnDMoveAction(*puAllowedActions))
}
}
}
/* static */
{
/* For now it doesn't seems useful to allow a
* link action between host & guest. Maybe later! */
}
/* static */
{
/* For now it doesn't seems useful to allow a
* link action between host & guest. Maybe later! */
if (hasDnDCopyAction(uActions))
if (hasDnDMoveAction(uActions))
}
/* static */
{
LogFlowFunc(("pvExtension=%p, u32Function=%RU32, pvParms=%p, cbParms=%RU32\n",
int rc;
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;
}
{
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
{
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);
break;
}
{
DragAndDropSvc::PVBOXDNDCBSNDDIRDATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBSNDDIRDATA>(pvParms);
AssertReturn(DragAndDropSvc::CB_MAGIC_DND_GH_SND_DIR == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
break;
}
{
DragAndDropSvc::PVBOXDNDCBSNDFILEDATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBSNDFILEDATA>(pvParms);
AssertReturn(DragAndDropSvc::CB_MAGIC_DND_GH_SND_FILE == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
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;
}
# ifdef VBOX_WITH_DRAG_AND_DROP_GH
{
if (RT_SUCCESS(rc))
return rc;
}
{
LogFlowFunc(("pszPath=%s, cbPath=%zu, fMode=0x%x\n",
int rc;
if (pszDir)
{
}
else
rc = VERR_NO_MEMORY;
if (RT_SUCCESS(rc))
return rc;
}
{
LogFlowFunc(("pszPath=%s, cbPath=%zu, fMode=0x%x\n",
return rc;
}
# endif /* VBOX_WITH_DRAG_AND_DROP_GH */
///////////////////////////////////////////////////////////////////////////////
GuestDnDBase::GuestDnDBase(void)
{
}
{
return S_OK;
}
{
return S_OK;
}
{
{
{
}
}
return S_OK;
}
{
{
}
return S_OK;
}
#endif /* VBOX_WITH_DRAG_AND_DROP */