VBoxDnD.cpp revision fdca3f8441b2cfdf10d1c78011986554948a7753
/* $Id$ */
/** @file
* VBoxDnD.cpp - Windows-specific bits of the drag'n drop service.
*/
/*
* Copyright (C) 2013-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 <windows.h>
#include "VBoxTray.h"
#include "VBoxHelpers.h"
#include "VBoxDnD.h"
#include <VBox/VBoxGuestLib.h>
#include "VBox/HostServices/DragAndDropSvc.h"
#include <VBoxGuestInternal.h>
#ifdef LOG_GROUP
#endif
#define LOG_GROUP LOG_GROUP_GUEST_DND
/* 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
/** @todo Merge this with messages from VBoxTray.h. */
/** Function pointer for SendInput(). This only is available starting
* at NT4 SP3+. */
/** Static pointer to SendInput() function. */
VBoxDnDWnd::VBoxDnDWnd(void)
: hThread(NIL_RTTHREAD),
mfMouseButtonDown(false),
#ifdef VBOX_WITH_DRAG_AND_DROP_GH
#endif
{
}
VBoxDnDWnd::~VBoxDnDWnd(void)
{
Destroy();
}
/**
* Initializes the proxy window with a given DnD context.
*
* @return IPRT status code.
* @param pContext Pointer to context to use.
*/
{
/* Save the context. */
if (RT_SUCCESS(rc))
if (RT_SUCCESS(rc))
{
/* Message pump thread for our proxy window. */
"VBoxTrayDnDWnd");
if (RT_SUCCESS(rc))
}
if (RT_FAILURE(rc))
return rc;
}
/**
* Destroys the proxy window and releases all remaining
* resources again.
*
*/
void VBoxDnDWnd::Destroy(void)
{
if (hThread != NIL_RTTHREAD)
{
int rcThread = VERR_WRONG_ORDER;
LogFlowFunc(("Waiting for thread resulted in %Rrc (thread exited with %Rrc)\n",
}
reset();
if (mEventSem != NIL_RTSEMEVENT)
}
/**
* Thread for handling the window's message pump.
*
* @return IPRT status code.
* @param hThread Handle to this thread.
* @param pvUser Pointer to VBoxDnDWnd instance which
* is using the thread.
*/
/* static */
{
/* Create our proxy window. */
#ifdef VBOX_DND_DEBUG_WND
#else
#endif
bool fSignalled = false; /* Thread signalled? */
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
/*
* Install some mouse tracking.
*/
#endif
}
}
{
#ifdef VBOX_WITH_DRAG_AND_DROP_GH
#endif
}
else
{
}
if (RT_SUCCESS(rc))
{
bool fShutdown = false;
while (RT_SUCCESS(rc))
{
while (GetMessage(&uMsg, 0, 0, 0))
{
}
fShutdown = true;
if (fShutdown)
{
LogFlowFunc(("Cancelling ...\n"));
break;
}
/** @todo Immediately drop on failure? */
}
#ifdef VBOX_WITH_DRAG_AND_DROP_GH
if (RT_SUCCESS(rc))
#endif
}
if (!fSignalled)
{
}
return rc;
}
/**
* Monitor enumeration callback for building up a simple bounding
* box, capable of holding all enumerated monitors.
*
* @return BOOL TRUE if enumeration should continue,
* FALSE if not.
* @param hMonitor Handle to current monitor being enumerated.
* @param hdcMonitor The current monitor's DC (device context).
* @param lprcMonitor The current monitor's RECT.
* @param lParam Pointer to a RECT structure holding the
* bounding box to build.
*/
/* static */
{
LogFlowFunc(("Monitor is %ld,%ld,%ld,%ld\n",
/* Build up a simple bounding box to hold the entire (virtual) screen. */
return TRUE;
}
/**
* The proxy window's WndProc.
*/
{
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;
/* As the mouse button was released, Hide the proxy window again.
* This can happen if
* - the user bumped a guest window to the screen's edges
* - there was no drop data from the guest available and the user
* enters the guest screen again after this unsuccessful operation */
reset();
return 0;
case WM_MOUSELEAVE:
LogFlowThisFunc(("WM_MOUSELEAVE\n"));
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, mMode=%ld, mState=%ld\n",
#ifdef DEBUG_andy
POINT p;
GetCursorPos(&p);
LogFlowThisFunc(("WM_MOUSEMOVE: curX=%ld, curY=%ld\n", p.x, p.y));
#endif
int rc = VINF_SUCCESS;
{
/* Dragging not started yet? Kick it off ... */
if ( mfMouseButtonDown
{
#if 0
/* Delay hiding the proxy window a bit when debugging, to see
* whether the desired range is covered correctly. */
RTThreadSleep(5000);
#endif
hide();
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))
{
if (RT_SUCCESS(rc))
}
}
}
{
/* Starting here VBoxDnDDropTarget should
* take over; was instantiated when registering
* this proxy window as a (valid) drop target. */
}
else
LogFlowThisFunc(("WM_MOUSEMOVE: mMode=%ld, mState=%ld, rc=%Rrc\n",
return 0;
}
case WM_NCMOUSEHOVER:
LogFlowThisFunc(("WM_NCMOUSEHOVER\n"));
return 0;
case WM_NCMOUSELEAVE:
LogFlowThisFunc(("WM_NCMOUSELEAVE\n"));
return 0;
case WM_VBOXTRAY_DND_MESSAGE:
{
if (!pEvent)
break; /* No event received, bail out. */
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;
}
{
LogFlowThisFunc(("HOST_DND_GH_REQ_PENDING\n"));
#ifdef VBOX_WITH_DRAG_AND_DROP_GH
#else
#endif
break;
}
{
LogFlowThisFunc(("HOST_DND_GH_EVT_DROPPED\n"));
#ifdef VBOX_WITH_DRAG_AND_DROP_GH
#else
#endif
break;
}
default:
break;
}
/* Some messages require cleanup. */
{
#ifdef VBOX_WITH_DRAG_AND_DROP_GH
#endif
{
break;
}
{
break;
}
default:
/* Ignore. */
break;
}
if (pEvent)
{
LogFlowThisFunc(("Processing event %RU32 resulted in rc=%Rrc\n",
}
return 0;
}
default:
break;
}
}
#ifdef VBOX_WITH_DRAG_AND_DROP_GH
/**
* Registers this proxy window as a local drop target.
*
* @return IPRT status code.
*/
int VBoxDnDWnd::RegisterAsDropTarget(void)
{
if (pDropTarget) /* Already registered as drop target? */
return VINF_SUCCESS;
int rc;
try
{
FALSE /* fLastUnlockReleases */);
{
}
else
{
rc = VINF_SUCCESS;
}
}
{
rc = VERR_NO_MEMORY;
}
return rc;
}
/**
* Unregisters this proxy as a drop target.
*
* @return IPRT status code.
*/
int VBoxDnDWnd::UnregisterAsDropTarget(void)
{
if (!pDropTarget) /* No drop target? Bail out. */
return VINF_SUCCESS;
TRUE /* fLastUnlockReleases */);
{
pDropTarget = NULL;
}
return rc;
}
#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
/**
* Handles the creation of a proxy window.
*
* @return IPRT status code.
*/
int VBoxDnDWnd::OnCreate(void)
{
if (RT_FAILURE(rc))
{
return rc;
}
return rc;
}
/**
* Handles the destruction of a proxy window.
*/
void VBoxDnDWnd::OnDestroy(void)
{
}
/**
* Handles actions required when the host cursor enters
* the guest's screen to initiate a host -> guest DnD operation.
*
* @return IPRT status code.
* @param lstFormats Supported formats offered by the host.
* @param uAllActions Supported actions offered by the host.
*/
{
return VERR_WRONG_ORDER;
#ifdef DEBUG
LogFlow(("\n"));
#endif
reset();
/* Save all allowed actions. */
this->uAllActions = uAllActions;
/*
* Check if requested formats are compatible with this client.
*/
LogFlowThisFunc(("Supported formats:\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;
}
if (RT_SUCCESS(rc))
rc = makeFullscreen();
return rc;
}
/**
* Handles actions required when the host cursor moves inside
* the guest's screen.
*
* @return IPRT status code.
* @param u32xPos Absolute X position (in pixels) of the host cursor
* inside the guest.
* @param u32yPos Absolute Y position (in pixels) of the host cursor
* inside the guest.
* @param uAction Action the host wants to perform while moving.
* Currently ignored.
*/
{
int rc;
{
LogFlowThisFunc(("u32xPos=%RU32, u32yPos=%RU32, uAction=0x%x\n",
if (RT_SUCCESS(rc))
if (RT_SUCCESS(rc))
{
}
}
else /* Just acknowledge the operation with an ignore action. */
rc = VINF_SUCCESS;
if (RT_SUCCESS(rc))
{
if (RT_FAILURE(rc))
}
return rc;
}
/**
* Handles actions required when the host cursor leaves
* the guest's screen again.
*
* @return IPRT status code.
*/
int VBoxDnDWnd::OnHgLeave(void)
{
return VERR_WRONG_ORDER;
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;
}
/**
* Handles actions required when the host cursor wants to drop
* and therefore start a "drop" action in the guest.
*
* @return IPRT status code.
*/
int VBoxDnDWnd::OnHgDrop(void)
{
return VERR_WRONG_ORDER;
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;
}
/**
* Handles actions required when the host has sent over DnD data
* to the guest after a "drop" event.
*
* @return IPRT status code.
* @param pvData Pointer to raw data received.
* @param cbData Size of data (in bytes) received.
*/
{
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 = mouseRelease();
if (RT_SUCCESS(rc))
return rc;
}
/**
* Handles actions required when the host wants to cancel an action
* host -> guest operation.
*
* @return IPRT status code.
*/
int VBoxDnDWnd::OnHgCancel(void)
{
if (RT_SUCCESS(rc))
{
if (startupInfo.pDataObject)
}
int rc2 = mouseRelease();
if (RT_SUCCESS(rc))
reset();
return rc;
}
#ifdef VBOX_WITH_DRAG_AND_DROP_GH
/**
* Handles actions required to start a guest -> host DnD operation.
* This works by letting the host ask whether a DnD operation is pending
* on the guest. The guest must not know anything about the host's DnD state
*
* To capture a pending DnD operation on the guest which then can be communicated
* to the host the proxy window needs to be registered as a drop target. This drop
* target then will act as a proxy target between the guest OS and the host. In other
* words, the guest OS will use this proxy target as a regular (invisible) window
* which can be used by the regular guest OS' DnD mechanisms, independently of the
* host OS. To make sure this proxy target is able receive an in-progress DnD operation
* on the guest, it will be shown invisibly across all active guest OS screens. Just
* think of an opened umbrella across all screens here.
*
* As soon as the proxy target and its underlying data object receive appropriate
* DnD messages they'll be hidden again, and the control will be transferred back
* this class again.
*
* @return IPRT status code.
* @param uScreenID Screen ID the host wants to query a pending operation
*/
{
LogFlowThisFunc(("mMode=%ld, mState=%ld, uScreenID=%RU32\n",
return VERR_WRONG_ORDER;
if (mState == Uninitialized)
{
/* Nothing to do here yet. */
}
int rc;
if (mState == Initialized)
{
rc = makeFullscreen();
if (RT_SUCCESS(rc))
{
/*
* We have to release the left mouse button to
* get into our (invisible) proxy window.
*/
mouseRelease();
/*
* Even if we just released the left mouse button
* we're still in the dragging state to handle our
* own drop target (for the host).
*/
}
}
else
rc = VINF_SUCCESS;
/**
* Some notes regarding guest cursor movement:
* - The host only sends an HOST_DND_GH_REQ_PENDING message to the guest
* if the mouse cursor is outside the VM's window.
* - The guest does not know anything about the host's cursor
* position / state due to security reasons.
* - The guest *only* knows that the host currently is asking whether a
* guest DnD operation is in progress.
*/
if ( RT_SUCCESS(rc)
{
/** @todo Put this block into a function! */
POINT p;
GetCursorPos(&p);
ClientToScreen(hWnd, &p);
#ifdef DEBUG_andy
LogFlowThisFunc(("Client to screen curX=%ld, curY=%ld\n", p.x, p.y));
#endif
/** @todo Multi-monitor setups? */
if (px <= 0)
px = 1;
if (py <= 0)
py = 1;
}
if (RT_SUCCESS(rc))
{
if (!strFormats.isEmpty())
{
LogFlowFunc(("Acknowledging pDropTarget=0x%p, uDefAction=0x%x, uAllActions=0x%x, strFormats=%s\n",
}
else
{
LogFlowFunc(("No format data available yet\n"));
}
/** @todo Support more than one action at a time. */
if (RT_FAILURE(rc))
{
char szTitle[64];
/** @todo Add some i18l tr() macros here. */
"Please enable Guest to Host or Bidirectional drag'n drop mode "
"or re-install the VirtualBox Guest Additions.");
switch (rc)
{
case VERR_ACCESS_DENIED:
break;
default:
break;
}
}
}
if (RT_FAILURE(rc))
reset(); /* Reset state on failure. */
return rc;
}
/**
* Handles actions required to let the guest know that the host
* started a "drop" action on the host. This will tell the guest
* to send data in a specific format the host requested.
*
* @return IPRT status code.
* @param pszFormat Format the host requests the data in.
* @param cbFormat Size (in bytes) of format string.
* @param uDefAction Default action on the host.
*/
{
LogFlowThisFunc(("mMode=%ld, mState=%ld, pDropTarget=0x%p, pszFormat=%s, uDefAction=0x%x\n",
int rc;
{
{
reset();
}
{
rc = VINF_SUCCESS;
}
else
if (RT_SUCCESS(rc))
{
/** @todo Respect uDefAction. */
LogFlowFunc(("Sent pvData=0x%p, cbData=%RU32, rc=%Rrc\n",
}
}
else
return rc;
}
#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
/**
* Injects a DnD event in this proxy window's Windows
* event queue. The (allocated) event will be deleted by
* this class after processing.
*
* @return IPRT status code.
* @param pEvent Event to inject.
*/
{
if (!fRc)
{
static int s_iBitchedAboutFailedDnDMessages = 0;
if (s_iBitchedAboutFailedDnDMessages++ < 10)
{
LogRel(("DnD: Processing event %p failed with %ld (%Rrc), skpping\n",
}
return VERR_NO_MEMORY;
}
return VINF_SUCCESS;
}
/**
* Hides the proxy window again.
*
* @return IPRT status code.
*/
int VBoxDnDWnd::hide(void)
{
#ifdef DEBUG_andy
LogFlowFunc(("\n"));
#endif
return VINF_SUCCESS;
}
/**
* Shows the (invisible) proxy window in fullscreen,
* spawned across all active guest monitors.
*
* @return IPRT status code.
*/
int VBoxDnDWnd::makeFullscreen(void)
{
int rc = VINF_SUCCESS;
RECT r;
RT_ZERO(r);
if (hDC)
{
/* EnumDisplayMonitors is not available on NT4. */
if (!fRc)
rc = VERR_NOT_FOUND;
}
else
if (RT_FAILURE(rc))
{
/* If multi-monitor enumeration failed above, try getting at least the
* primary monitor as a fallback. */
r.left = 0;
r.top = 0;
rc = VINF_SUCCESS;
}
if (RT_SUCCESS(rc))
{
| WS_EX_CLIENTEDGE | WS_EX_STATICEDGE));
r.left,
r.top,
#ifdef VBOX_DND_DEBUG_WND
#else
#endif
if (fRc)
{
LogFlowFunc(("Virtual screen is %ld,%ld,%ld,%ld (%ld x %ld)\n",
}
else
{
LogRel(("DnD: Failed to set proxy window position, rc=%Rrc\n",
}
}
else
return rc;
}
/**
* Moves the guest mouse cursor to a specific position.
*
* @return IPRT status code.
* @param x X position (in pixels) to move cursor to.
* @param y Y position (in pixels) to move cursor to.
* @param dwMouseInputFlags Additional movement flags. @sa MOUSEEVENTF_ flags.
*/
{
int rc;
{
#ifdef DEBUG_andy
if (fRc)
LogFlowThisFunc(("Cursor shown=%RTbool, cursor=0x%p, x=%d, y=%d\n",
#endif
rc = VINF_SUCCESS;
}
else
{
}
return rc;
}
/**
* Releases a previously pressed left guest mouse button.
*
* @return IPRT status code.
*/
int VBoxDnDWnd::mouseRelease(void)
{
#ifdef DEBUG_andy
LogFlowFunc(("\n"));
#endif
int rc;
/* Release mouse button in the guest to start the "drop"
* action at the current mouse cursor position. */
{
}
else
rc = VINF_SUCCESS;
return rc;
}
/**
* Resets the proxy window.
*/
void VBoxDnDWnd::reset(void)
{
LogFlowThisFunc(("Resetting, old mMode=%ld, mState=%ld\n",
lstFormats.clear();
hide();
}
/**
* Sets the current operation mode of this proxy window.
*
* @return IPRT status code.
* @param enmMode New mode to set.
*/
{
LogFlowThisFunc(("Old mode=%ld, new mode=%ld\n",
return VINF_SUCCESS;
}
/**
* Static helper function for having an own WndProc for proxy
* window instances.
*/
{
AssertPtrReturn(pUserData, 0);
if (pWnd)
return 0;
}
/**
* Static helper function for routing Windows messages to a specific
* proxy window instance.
*/
{
/* 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;
bool fSupportedOS = true;
/* g_pfnEnumDisplayMonitors is optional. */
if (!fSupportedOS)
{
LogRel(("DnD: Not supported Windows version, disabling drag'n drop support\n"));
}
else
rc = VINF_SUCCESS;
if (RT_SUCCESS(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. */
}
{
int rc = VINF_SUCCESS;
/** @todo At the moment we only have one DnD proxy window. */
if (pWnd)
delete pWnd;
LogFunc(("Destroyed pInstance=%p, rc=%Rrc\n",
}
{
if (RT_FAILURE(rc))
return rc;
/** @todo At the moment we only have one DnD proxy window. */
/* Number of invalid messages skipped in a row. */
int cMsgSkippedInvalid = 0;
do
{
if (!pEvent)
{
rc = VERR_NO_MEMORY;
break;
}
/* Note: pEvent will be free'd by the consumer later. */
LogFlowFunc(("VbglR3DnDProcessNextMessage returned uType=%RU32, rc=%Rrc\n",
break;
if (RT_SUCCESS(rc))
{
cMsgSkippedInvalid = 0; /* Reset skipped messages count. */
if (RT_FAILURE(rc2))
}
else if (rc == VERR_CANCELLED)
{
if (RT_FAILURE(rc2))
break;
}
else
{
/* Old(er) hosts either are broken regarding DnD support or otherwise
* don't support the stuff we do on the guest side, so make sure we
* don't process invalid messages forever. */
if (cMsgSkippedInvalid++ > 3)
{
break;
}
}
break;
} while (true);
LogFlowFunc(("Shutting down ...\n"));
return rc;
}