VBoxDnD.cpp revision 81f46059436c6145937a4cc2c7424023a289fcd8
/* $Id$ */
/** @file
* VBoxDnD.cpp - Windows-specific bits of the drag'n drop service.
*/
/*
* Copyright (C) 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 <windows.h>
#include "VBoxTray.h"
#include "VBoxHelpers.h"
#include "VBoxDnD.h"
#include <VBox/VBoxGuestLib.h>
#include "VBox/HostServices/DragAndDropSvc.h"
#include <iprt/critsect.h>
#include <VBoxGuestInternal.h>
/* Enable this define to see the proxy window(s) when debugging
* their behavior. Don't have this enabled in release builds! */
#ifdef DEBUG
//# define VBOX_DND_DEBUG_WND
#endif
struct VBOXDNDCONTEXT;
class VBoxDnDWnd;
/*
* A drag'n drop event from the host.
*/
typedef struct VBOXDNDEVENT
{
/** The actual event data. */
/**
* DnD context data.
*/
typedef struct VBOXDNDCONTEXT
{
/** Pointer to the service environment. */
const VBOXSERVICEENV *pEnv;
/** Shutdown indicator. */
bool fShutdown;
/** Thread handle for main event queue
* processing. */
/** The DnD main event queue. */
/** Semaphore for waiting on main event queue
* events. */
/** List of drag'n drop windows. At
* the moment only one source is supported. */
static VBOXDNDCONTEXT gCtx = {0};
/**
* Everything which is required to successfully start
* a drag'n drop operation via DoDragDrop().
*/
typedef struct VBOXDNDSTARTUPINFO
{
/** Our DnD data object, holding
* the raw DnD data. */
/** The drop source for sending the
* DnD request to a IDropTarget. */
/** The DnD effects which are wanted / allowed. */
/**
* Class for handling a DnD proxy window.
** @todo Unify this and VBoxClient's DragInstance!
*/
class VBoxDnDWnd
{
enum State
{
};
enum Mode
{
HG,
};
public:
VBoxDnDWnd(void);
virtual ~VBoxDnDWnd(void);
public:
public:
/** The window's thread for the native message pump and
* OLE context. */
public:
static BOOL CALLBACK MonitorEnumProc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM lParam);
/** The per-instance wndproc routine. */
public:
int DragRelease(void);
int OnCreate(void);
void OnDestroy(void);
/* H->G */
int OnHgDrop(void);
int OnHgLeave(void);
int OnHgCancel(void);
#ifdef VBOX_WITH_DRAG_AND_DROP_GH
/* G->H */
int OnGhIsDnDPending(void);
#endif
protected:
void reset(void);
public: /** @todo Make protected! */
/** Pointer to DnD context. */
#ifdef RT_OS_WINDOWS
/** The window's handle. */
/** List of allowed MIME types this
* client can handle. Make this a per-instance
* certain types later on runtime. */
/** List of formats for the current
* drag'n drop operation. */
/** Flags of all current drag'n drop
* actions allowed. */
/** The startup information required
* for the actual DoDragDrop() call. */
bool mfMouseButtonDown;
#else
/** @todo */
#endif
/** The window's own HGCM client ID. */
};
VBoxDnDWnd::VBoxDnDWnd(void)
mfMouseButtonDown(false),
{
}
VBoxDnDWnd::~VBoxDnDWnd(void)
{
/** @todo Shutdown crit sect / event etc! */
reset();
}
int VBoxDnDWnd::DragRelease(void)
{
/* Release mouse button in the guest to start the "drop"
* action at the current mouse cursor position. */
return VINF_SUCCESS;
}
{
/* Save the context. */
if (RT_SUCCESS(rc))
if (RT_SUCCESS(rc))
{
/* Message pump thread for our proxy window. */
"VBoxTrayDnDWnd");
if (RT_FAILURE(rc))
/** @todo Wait for thread to be started! */
}
return rc;
}
/**
* Thread for handling the window's message pump.
*
* @return IPRT status code.
* @param hThread
* @param pvUser
*/
/* static */
{
/* Create our proxy window. */
#ifdef VBOX_DND_DEBUG_WND
#else
#endif
int rc = VINF_SUCCESS;
if (!RegisterClassEx(&wndClass))
{
}
if (RT_SUCCESS(rc))
{
#ifdef VBOX_DND_DEBUG_WND
#endif
"VBoxTrayDnDWnd", "VBoxTrayDnDWnd",
#ifdef VBOX_DND_DEBUG_WND
#else
#endif
{
}
else
{
#ifndef VBOX_DND_DEBUG_WND
#else
#endif
}
}
if (RT_SUCCESS(rc))
{
bool fShutdown = false;
do
{
while (GetMessage(&uMsg, 0, 0, 0))
{
}
fShutdown = true;
if (fShutdown)
{
LogFlowFunc(("Cancelling ...\n"));
break;
}
/** @todo Immediately drop on failure? */
} while (RT_SUCCESS(rc));
}
return rc;
}
/* static */
{
LogFlowFunc(("Monitor is %ld,%ld,%ld,%ld\n",
/* Build up a simple bounding box to hold the entire (virtual) screen. */
return TRUE;
}
{
switch (uMsg)
{
case WM_CREATE:
{
if (RT_FAILURE(rc))
return FALSE;
return TRUE;
}
case WM_CLOSE:
{
OnDestroy();
PostQuitMessage(0);
return 0;
}
case WM_LBUTTONDOWN:
LogFlowThisFunc(("WM_LBUTTONDOWN\n"));
mfMouseButtonDown = true;
return 0;
case WM_LBUTTONUP:
LogFlowThisFunc(("WM_LBUTTONUP\n"));
mfMouseButtonDown = false;
return 0;
/* Will only be called once; after the first mouse move, this
* window will be hidden! */
case WM_MOUSEMOVE:
{
LogFlowThisFunc(("WM_MOUSEMOVE: mfMouseButtonDown=%RTbool, mState=%ld\n",
/* Dragging not started yet? Kick it off ... */
if ( mfMouseButtonDown
{
#ifdef VBOX_DND_DEBUG_WND
/* Delay hinding the proxy window a bit when debugging, to see
* whether the desired range is covered correctly. */
RTThreadSleep(5000);
#endif
int rc = VINF_SUCCESS;
LogFlowThisFunc(("Starting drag'n drop: uAllActions=0x%x, dwOKEffects=0x%x ...\n",
switch (hr)
{
case DRAGDROP_S_DROP:
break;
case DRAGDROP_S_CANCEL:
break;
default:
break;
}
if (RT_SUCCESS(rc2))
{
}
LogFlowThisFunc(("Drag'n drop resulted in mState=%ld, rc=%Rrc\n",
}
return 0;
}
case WM_VBOXTRAY_DND_MESSAGE:
{
LogFlowThisFunc(("Received uType=%RU32, uScreenID=%RU32\n",
int rc;
{
{
LogFlowThisFunc(("HOST_DND_HG_EVT_ENTER\n"));
{
}
else
{
AssertMsgFailed(("cbFormats is 0\n"));
}
/* Note: After HOST_DND_HG_EVT_ENTER there immediately is a move
* event, so fall through is intentional here. */
}
{
LogFlowThisFunc(("HOST_DND_HG_EVT_MOVE: %d,%d\n",
break;
}
{
LogFlowThisFunc(("HOST_DND_HG_EVT_LEAVE\n"));
break;
}
{
LogFlowThisFunc(("HOST_DND_HG_EVT_DROPPED\n"));
break;
}
{
LogFlowThisFunc(("HOST_DND_HG_SND_DATA\n"));
break;
}
{
LogFlowThisFunc(("HOST_DND_HG_EVT_CANCEL\n"));
rc = OnHgCancel();
break;
}
#ifdef VBOX_WITH_DRAG_AND_DROP_GH
{
LogFlowThisFunc(("HOST_DND_GH_REQ_PENDING\n"));
break;
}
{
LogFlowThisFunc(("HOST_DND_GH_EVT_DROPPED\n"));
break;
}
{
LogFlowThisFunc(("GUEST_DND_GH_EVT_ERROR\n"));
break;
}
#endif
default:
break;
}
/* Some messages require cleanup. */
{
#ifdef VBOX_WITH_DRAG_AND_DROP_GH
#endif
{
break;
}
{
break;
}
default:
/* Ignore. */
break;
}
LogFlowThisFunc(("Processing event %RU32 resulted in rc=%Rrc\n",
if (pEvent)
return 0;
}
default:
break;
}
}
int VBoxDnDWnd::OnCreate(void)
{
if (RT_FAILURE(rc))
{
return rc;
}
return rc;
}
void VBoxDnDWnd::OnDestroy(void)
{
}
{
reset();
#ifdef DEBUG
LogFlow(("\n"));
#endif
/* Save all allowed actions. */
this->uAllActions = uAllActions;
/*
* Install our allowed MIME types.
** @todo See todo for m_sstrAllowedMimeTypes in GuestDnDImpl.cpp.
*/
/* 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\"";
this->lstAllowedFormats = lstAllowedMimeTypes;
/*
* Check MIME compatibility with this client.
*/
LogFlowThisFunc(("Supported MIME types:\n"));
{
if (fSupported)
}
/*
* Prepare the startup info for DoDragDrop().
*/
int rc = VINF_SUCCESS;
try
{
/* Translate our drop actions into
* allowed Windows drop effects. */
if (uAllActions)
{
if (uAllActions & DND_COPY_ACTION)
if (uAllActions & DND_MOVE_ACTION)
if (uAllActions & DND_LINK_ACTION)
}
}
{
rc = VERR_NO_MEMORY;
}
/*
* Prepare the proxy window.
*/
RECT r;
RT_ZERO(r);
if (RT_SUCCESS(rc))
{
if (!fRc)
rc = VERR_NOT_FOUND;
}
if (RT_FAILURE(rc))
{
/* If multi-monitor enumeration failed above, try getting at least the
* primary monitor as a fallback. */
&monitor_info))
{
r = monitor_info.rcMonitor;
rc = VINF_SUCCESS;
}
}
if (RT_SUCCESS(rc))
{
r.left,
r.top,
#ifdef VBOX_DND_DEBUG_WND
#else
#endif
LogFlowFunc(("Virtual screen is %ld,%ld,%ld,%ld (%ld x %ld)\n",
}
else
return rc;
}
{
LogFlowThisFunc(("u32xPos=%RU32, u32yPos=%RU32, uAction=0x%x\n",
/** @todo Multi-monitor setups? */
#ifdef DEBUG
POINT p;
GetCursorPos(&p);
LogFlowThisFunc(("curX=%d, cury=%d\n", p.x, p.y));
#endif
if (RT_SUCCESS(rc))
{
}
if (RT_SUCCESS(rc))
{
if (RT_FAILURE(rc))
}
return rc;
}
int VBoxDnDWnd::OnHgLeave(void)
{
LogRel(("DnD: Drag'n drop operation aborted\n"));
reset();
int rc = VINF_SUCCESS;
/* Post ESC to our window to officially abort the
* drag'n drop operation. */
return rc;
}
int VBoxDnDWnd::OnHgDrop(void)
{
int rc = VINF_SUCCESS;
{
/** @todo What to do when multiple formats are available? */
if (RT_SUCCESS(rc))
{
if (startupInfo.pDataObject)
else
rc = VERR_NOT_FOUND;
}
if (RT_SUCCESS(rc))
{
if (RT_FAILURE(rc))
}
}
return rc;
}
{
LogFlowThisFunc(("mState=%ld, pvData=%p, cbData=%RU32\n",
int rc = VINF_SUCCESS;
if (pvData)
{
if (RT_SUCCESS(rc))
{
if (startupInfo.pDataObject)
else
rc = VERR_NOT_FOUND;
}
}
int rc2 = DragRelease();
if (RT_SUCCESS(rc))
return rc;
}
int VBoxDnDWnd::OnHgCancel(void)
{
if (RT_SUCCESS(rc))
{
if (startupInfo.pDataObject)
}
int rc2 = DragRelease();
if (RT_SUCCESS(rc))
return rc;
}
#ifdef VBOX_WITH_DRAG_AND_DROP_GH
int VBoxDnDWnd::OnGhIsDnDPending(void)
{
return 0;
}
{
return 0;
}
#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
{
return VINF_SUCCESS;
}
void VBoxDnDWnd::reset(void)
{
lstFormats.clear();
}
{
AssertPtrReturn(pUserData, 0);
if (pWnd)
return 0;
}
{
/* Note: WM_NCCREATE is not the first ever message which arrives, but
* early enough for us. */
if (uMsg == WM_NCCREATE)
{
}
/* No window associated yet. */
}
/**
* Initializes drag'n drop.
*
* @return IPRT status code.
* @param pEnv The DnD service's environment.
* @param ppInstance The instance pointer which refer to this object.
* @param pfStartThread Pointer to flag whether the DnD service can be started or not.
*/
{
/** ppInstance not used here. */
*pfStartThread = false;
int rc;
/* Create the proxy window. At the moment we
* only support one window at a time. */
try
{
pWnd = new VBoxDnDWnd();
/* Add proxy window to our proxy windows list. */
if (RT_SUCCESS(rc))
}
{
rc = VERR_NO_MEMORY;
}
if (RT_SUCCESS(rc))
if (RT_SUCCESS(rc))
{
/* Assign service environment to our context. */
}
if (RT_SUCCESS(rc))
{
*ppInstance = pCtx;
*pfStartThread = true;
LogRel(("DnD: Drag'n drop service successfully started\n"));
return VINF_SUCCESS;
}
return rc;
}
{
/* Set shutdown indicator. */
/** @todo Notify / wait for HGCM thread! */
}
{
int rc = VINF_SUCCESS;
LogFunc(("Destroyed pInstance=%p, rc=%Rrc\n",
}
{
if (RT_FAILURE(rc))
return rc;
/** @todo At the moment we only have one DnD proxy window. */
do
{
if (!pEvent)
{
rc = VERR_NO_MEMORY;
break;
}
/* Note: pEvent will be free'd by the consumer later. */
break;
if (RT_SUCCESS(rc))
{
if (RT_FAILURE(rc2))
}
else if (rc == VERR_CANCELLED)
{
if (RT_FAILURE(rc2))
}
else
break;
} while (true);
LogFlowFunc(("Shutting down ...\n"));
return rc;
}