dndmanager.cpp revision 25920a546f23287c1d2abc4745ca2eaee190d064
/* $Id$ */
/** @file
* Drag and Drop manager.
*/
/*
* Copyright (C) 2011-2012 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.
*/
/******************************************************************************
* Header Files *
******************************************************************************/
#define LOG_GROUP LOG_GROUP_HGCM
#include "dndmanager.h"
#define VERBOSE 1
#if defined(VERBOSE) && defined(DEBUG_poetzsch)
#else
# define DO(s) do {} while(0)
//# define DO(s) Log s
#endif
/******************************************************************************
* Private declarations *
******************************************************************************/
typedef FNDNDPRIVATEPROGRESS *PFNDNDPRIVATEPROGRESS;
/**
* Internal DnD message class for informing the guest about a new directory.
*
* @see DnDHGSendDataMessage
*/
class DnDHGSendDirPrivate: public DnDMessage
{
public:
DnDHGSendDirPrivate(const RTCString &strPath, uint32_t fMode, uint64_t cbSize, PFNDNDPRIVATEPROGRESS pfnProgressCallback, void *pvProgressUser)
{
}
{
/* Advance progress info */
if ( RT_SUCCESS(rc)
return rc;
}
protected:
/* Progress stuff */
void *m_pvProgressUser;
};
/**
* Internal DnD message class for informing the guest about a new file.
*
* @see DnDHGSendDataMessage
*/
class DnDHGSendFilePrivate: public DnDMessage
{
public:
DnDHGSendFilePrivate(const RTCString &strHostPath, const RTCString &strGuestPath, uint32_t fMode, uint64_t cbSize, PFNDNDPRIVATEPROGRESS pfnProgressCallback, void *pvProgressUser);
protected:
/* Progress stuff */
void *m_pvProgressUser;
};
/**
* Internal DnD message class for informing the guest about new drag & drop
* data.
*
* @see DnDHGSendDataMessage
*/
class DnDHGSendDataMessagePrivate: public DnDMessage
{
public:
DnDHGSendDataMessagePrivate(uint32_t uMsg, uint32_t cParms, VBOXHGCMSVCPARM paParms[], PFNDNDPRIVATEPROGRESS pfnProgressCallback, void *pvProgressUser);
protected:
/* Progress stuff */
void *m_pvProgressUser;
};
/******************************************************************************
* Implementation *
******************************************************************************/
/******************************************************************************
* DnDHGSendFilePrivate *
******************************************************************************/
DnDHGSendFilePrivate::DnDHGSendFilePrivate(const RTCString &strHostPath, const RTCString &strGuestPath, uint32_t fMode, uint64_t cbSize, PFNDNDPRIVATEPROGRESS pfnProgressCallback, void *pvProgressUser)
, m_cbDone(0)
, m_hCurFile(0)
{
}
{
if (m_hCurFile)
}
{
if (!m_pNextMsg)
return VERR_NO_DATA;
clearNextMsg();
if (RT_FAILURE(rc))
return rc;
if (!m_hCurFile)
{
rc = RTFileOpen(&m_hCurFile, m_strHostPath.c_str(), RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_ALL);
if (RT_FAILURE(rc))
return rc;
}
/* How big is the pointer provided by the guest? */
if (RT_FAILURE(rc))
{
/* On error, immediately close the file. */
m_hCurFile = 0;
return rc;
}
/* Tell the guest the actual size. */
/* Check if we are done. */
{
m_hCurFile = 0;
}
else
{
/* More data! Prepare the next message. */
}
/* Advance progress info */
if ( RT_SUCCESS(rc)
return rc;
}
/******************************************************************************
* DnDHGSendDataMessagePrivate *
******************************************************************************/
DnDHGSendDataMessagePrivate::DnDHGSendDataMessagePrivate(uint32_t uMsg, uint32_t cParms, VBOXHGCMSVCPARM paParms[], PFNDNDPRIVATEPROGRESS pfnProgressCallback, void *pvProgressUser)
, m_cbDone(0)
{
/* Create the initial data message. */
}
int DnDHGSendDataMessagePrivate::currentMessage(uint32_t uMsg, uint32_t cParms, VBOXHGCMSVCPARM paParms[])
{
/* Todo: don't copy the data parts ... just move the data pointer in
* the original data ptr. */
if (!m_pNextMsg)
return VERR_NO_DATA;
int rc = VINF_SUCCESS;
m_pNextMsg = 0;
/* Depending on the current message, the data pointer is on a
* different position (HOST_DND_HG_SND_DATA=3;
* HOST_DND_HG_SND_MORE_DATA=0). */
/* Info & data send already? */
if (rc == VERR_BUFFER_OVERFLOW)
{
void *pvOldData;
paTmpParms[0].setPointer(static_cast<uint8_t*>(pvOldData) + paParms[iPos].u.pointer.size, cOldData - paParms[iPos].u.pointer.size);
}
delete pCurMsg;
/* Advance progress info */
if ( RT_SUCCESS(rc)
return rc;
}
/******************************************************************************
* DnDHGSendDataMessage *
******************************************************************************/
/*
* This class is a meta message class. It doesn't consist of any own message
* data, but handle the meta info, the data itself as well any files or
* directories which have to be transfered to the guest.
*/
DnDHGSendDataMessage::DnDHGSendDataMessage(uint32_t uMsg, uint32_t cParms, VBOXHGCMSVCPARM paParms[], PFNDNDPROGRESS pfnProgressCallback, void *pvProgressUser)
: m_cbAll(0)
, m_cbTransfered(0)
{
/* Check the format for any uri type. */
{
/* The list is separated by newline (Even if only one file is
* listed). */
RTCList<RTCString> oldUriList = RTCString(static_cast<const char*>(paParms[3].u.pointer.addr), paParms[3].u.pointer.size).split("\r\n");
if (!oldUriList.isEmpty())
{
{
/* Query the path component of a file URI. If this hasn't a
* file scheme null is returned. */
{
/* Add the path to our internal file list (recursive in
* the case of a directory). */
{
if (pszNewUri)
{
}
}
}
else
}
/* We have to change the actual DnD data. Remove any host paths and
* just decode the filename into the new data. The guest tools will
* add the correct path again, before sending the DnD drop event to
* some window. */
/* Remark: We don't delete the old pointer here, cause this is done
* by the caller. We just use the RTString data, which has the
* scope of this ctor. This is enough cause the data is copied in
* the DnDHGSendDataMessagePrivate anyway. */
}
}
/* Add the size of the data to the todo list. */
/* The first message is the meta info for the data and the data itself. */
m_pNextPathMsg = new DnDHGSendDataMessagePrivate(uMsg, cParms, paParms, &DnDHGSendDataMessage::progressCallback, this);
DO(("file: %s : %s - %o - %ld\n", m_uriList.at(i).m_strHostPath.c_str(), m_uriList.at(i).m_strGuestPath.c_str(), m_uriList.at(i).m_fMode, m_uriList.at(i).m_cbSize));
}
{
if (m_pNextPathMsg)
delete m_pNextPathMsg;
}
{
if (!m_pNextPathMsg)
return NULL;
return m_pNextPathMsg->nextHGCMMessage();
}
{
if (!m_pNextPathMsg)
return VERR_NO_DATA;
}
{
if (!m_pNextPathMsg)
return VERR_NO_DATA;
/* Fill the data out of our current queued message. */
/* Has this message more data to deliver? */
if (!m_pNextPathMsg->isMessageWaiting())
{
delete m_pNextPathMsg;
}
/* File data to send? */
if (!m_pNextPathMsg)
{
return rc;
/* Create new messages based on our internal path list. Currently
* this could be directories or regular files. */
m_pNextPathMsg = new DnDHGSendDirPrivate(nextPath.m_strGuestPath, nextPath.m_fMode, nextPath.m_cbSize, &DnDHGSendDataMessage::progressCallback, this);
m_pNextPathMsg = new DnDHGSendFilePrivate(nextPath.m_strHostPath, nextPath.m_strGuestPath, nextPath.m_fMode, nextPath.m_cbSize, &DnDHGSendDataMessage::progressCallback, this);
else
AssertMsgFailedReturn(("type '%d' is not supported for path '%s'", nextPath.m_fMode, nextPath.m_strHostPath.c_str()), VERR_NO_DATA);
}
return rc;
}
{
}
{
if (RT_FAILURE(rc))
return rc;
/* These are the types we currently support. Symlinks are not directly
* supported. First the guest could be an OS which doesn't support it and
* second the symlink could point to a file which is out of the base tree.
* Both things are hard to support. For now we just copy the target file in
* this case. */
return VINF_SUCCESS;
if (rc == VERR_IS_A_DIRECTORY)
rc = VINF_SUCCESS;
if (RT_FAILURE(rc))
return rc;
/* We have to try to open even symlinks, cause they could be symlinks
* to directories. */
/* The following error happens when this was a symlink to an file or a
* regular file. */
if (rc == VERR_PATH_NOT_FOUND)
return VINF_SUCCESS;
if (RT_FAILURE(rc))
return rc;
while (RT_SUCCESS(rc))
{
if (RT_FAILURE(rc))
{
if (rc == VERR_NO_MORE_FILES)
rc = VINF_SUCCESS;
break;
}
{
case RTDIRENTRYTYPE_DIRECTORY:
{
/* Skip "." and ".." entries. */
break;
{
}
else
rc = VERR_NO_MEMORY;
break;
}
case RTDIRENTRYTYPE_SYMLINK:
case RTDIRENTRYTYPE_FILE:
{
{
/* We need the size and the mode of the file. */
if (RT_FAILURE(rc))
return rc;
if (RT_FAILURE(rc))
break;
}
else
rc = VERR_NO_MEMORY;
break;
}
default: break;
}
}
return rc;
}
{
/* How many bytes are transfered already. */
/* Advance progress info */
int rc = VINF_SUCCESS;
if ( pSelf->m_pfnProgressCallback
return rc;
}
/******************************************************************************
* DnDManager *
******************************************************************************/
{
int rc = VINF_SUCCESS;
switch (uMsg)
{
{
clear();
LogFlowFunc(("HOST_DND_HG_EVT_ENTER\n"));
DO(("HOST_DND_HG_EVT_ENTER\n"));
/* Verify parameter count and types. */
if ( cParms != 7
else
{
m_fOpInProcess = true;
}
break;
}
{
LogFlowFunc(("HOST_DND_HG_EVT_MOVE\n"));
DO(("HOST_DND_HG_EVT_MOVE\n"));
/* Verify parameter count and types. */
if ( cParms != 7
else
{
m_fOpInProcess = true;
}
break;
}
{
LogFlowFunc(("HOST_DND_HG_EVT_LEAVE\n"));
DO(("HOST_DND_HG_EVT_LEAVE\n"));
/* Verify parameter count and types. */
if (cParms != 0)
else
{
}
m_fOpInProcess = false;
break;
}
{
LogFlowFunc(("HOST_DND_HG_EVT_DROPPED\n"));
DO(("HOST_DND_HG_EVT_DROPPED\n"));
/* Verify parameter count and types. */
if ( cParms != 7
else
{
}
break;
}
{
LogFlowFunc(("HOST_DND_HG_SND_DATA\n"));
DO(("HOST_DND_HG_SND_DATA\n"));
/* Verify parameter count and types. */
if ( cParms != 5
else
{
DnDHGSendDataMessage *pMessage = new DnDHGSendDataMessage(uMsg, cParms, paParms, m_pfnProgressCallback, m_pvProgressUser);
}
break;
}
#ifdef VBOX_WITH_DRAG_AND_DROP_GH
{
LogFlowFunc(("HOST_DND_GH_REQ_PENDING\n"));
DO(("HOST_DND_GH_REQ_PENDING\n"));
/* Verify parameter count and types. */
if ( cParms != 1
else
{
}
break;
}
{
LogFlowFunc(("HOST_DND_GH_EVT_DROPPED\n"));
DO(("HOST_DND_GH_EVT_DROPPED\n"));
/* Verify parameter count and types. */
if ( cParms != 3
else
{
}
break;
}
#endif
default: rc = VERR_NOT_IMPLEMENTED; break;
}
return rc;
}
{
if (m_pCurMsg)
return m_pCurMsg->nextHGCMMessage();
else
{
if (m_dndMessageQueue.isEmpty())
return 0;
}
}
{
int rc = VINF_SUCCESS;
if (m_pCurMsg)
else
{
if (m_dndMessageQueue.isEmpty())
{
rc = VERR_NO_DATA;
// if (m_pfnProgressCallback)
// m_pfnProgressCallback(100.0, DragAndDropSvc::DND_OP_CANCELLED, m_pvProgressUser);
}
else
}
return rc;
}
{
if (!m_pCurMsg)
{
/* Check for pending messages in our queue. */
if (m_dndMessageQueue.isEmpty())
return VERR_NO_DATA;
}
/* Fetch the current message info */
/* If this message not provide any additional sub messages, clear it. */
if (!m_pCurMsg->isMessageWaiting())
{
delete m_pCurMsg;
m_pCurMsg = 0;
}
/* If the user has canceled the operation, we need to cleanup all pending
* events and inform the progress callback about our successful cleanup. */
if ( rc == VERR_CANCELLED
{
/* Clear any pending messages */
clear();
/* Create a new cancel message to inform the guest. */
m_pCurMsg = new DnDHGCancelMessage();
}
return rc;
}
void DnDManager::clear()
{
if (m_pCurMsg)
{
delete m_pCurMsg;
m_pCurMsg = 0;
}
while (!m_dndMessageQueue.isEmpty())
{
delete m_dndMessageQueue.last();
}
}