VBoxDnD.cpp revision 00331fbaff118e6a5077fe96327aca51a70459db
/* $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>
/* 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. */
VBoxDnDWnd::VBoxDnDWnd(void)
mfMouseButtonDown(false),
#ifdef VBOX_WITH_DRAG_AND_DROP_GH
#endif
{
reset();
}
VBoxDnDWnd::~VBoxDnDWnd(void)
{
/** @todo Shutdown crit sect / event etc! */
reset();
}
{
/* 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
/*
* Install some mouse tracking.
*/
#endif
}
}
{
#ifdef VBOX_WITH_DRAG_AND_DROP_GH
#endif
}
else
{
}
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));
#ifdef VBOX_WITH_DRAG_AND_DROP_GH
if (RT_SUCCESS(rc))
#endif
}
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;
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))
}
}
}
{
//hide();
/* 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:
{
LogFlowThisFunc(("Received uType=%RU32, uScreenID=%RU32\n",
int rc;
{
{
LogFlowThisFunc(("HOST_DND_HG_EVT_ENTER\n"));
reset();
{
}
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
/* There can be more than one HOST_DND_GH_REQ_PENDING
* messages coming in. */
{
}
else
#else
#endif
break;
}
{
LogFlowThisFunc(("HOST_DND_GH_EVT_DROPPED\n"));
#ifdef VBOX_WITH_DRAG_AND_DROP_GH
{
}
else
#else
#endif
break;
}
{
LogFlowThisFunc(("GUEST_DND_GH_EVT_ERROR\n"));
#ifdef VBOX_WITH_DRAG_AND_DROP_GH
reset();
#else
#endif
break;
}
{
LogFlowThisFunc(("HOST_DND_GH_RECV_DIR\n"));
#ifdef VBOX_WITH_DRAG_AND_DROP_GH
{
}
else
#else
#endif
break;
}
{
LogFlowThisFunc(("HOST_DND_GH_RECV_FILE\n"));
#ifdef VBOX_WITH_DRAG_AND_DROP_GH
{
}
else
#else
#endif
break;
}
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;
}
}
#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;
}
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 */
int VBoxDnDWnd::OnCreate(void)
{
if (RT_FAILURE(rc))
{
return rc;
}
return rc;
}
void VBoxDnDWnd::OnDestroy(void)
{
}
{
#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 formats */
<< "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;
}
if (RT_SUCCESS(rc))
rc = makeFullscreen();
return rc;
}
{
LogFlowThisFunc(("u32xPos=%RU32, u32yPos=%RU32, uAction=0x%x\n",
/** @todo Put this block into a function! */
/** @todo Multi-monitor setups? */
#ifdef DEBUG_andy
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))
reset();
return rc;
}
#ifdef VBOX_WITH_DRAG_AND_DROP_GH
{
LogFlowThisFunc(("mMode=%ld, mState=%ld, uScreenID=%RU32\n",
if (mState == Uninitialized)
reset();
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.
*/
dragRelease();
/*
* 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;
//px++; py++;
if (uiProcessed)
{
#ifdef DEBUG_andy
LogFlowFunc(("Sent %RU16 mouse input(s), x=%ld, y=%ld, flags=0x%x\n",
#endif
}
else
#ifdef DEBUG_andy
if (fRc)
LogFlowThisFunc(("Cursor shown=%RTbool, cursor=0x%p, x=%d, y=%d\n",
#endif
}
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"));
}
return rc;
}
{
LogFlowThisFunc(("mMode=%ld, mState=%ld, pDropTarget=0x%p, uDefAction=0x%x\n",
#ifdef DEBUG
LogFlow(("\n"));
#endif
int rc;
{
if (RT_SUCCESS(rc))
{
/** @todo Respect uDefAction. */
/** @todo Do data checking / conversion based on pszFormats. */
LogFlowFunc(("Sent pvData=0x%p, cbData=%RU32, rc=%Rrc\n",
}
reset();
}
else
return rc;
}
{
int rc = 0;
return rc;
}
{
int rc = 0;
return rc;
}
#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
{
return VINF_SUCCESS;
}
int VBoxDnDWnd::dragRelease(void)
{
#ifdef DEBUG_andy
LogFlowFunc(("\n"));
#endif
/* Release mouse button in the guest to start the "drop"
* action at the current mouse cursor position. */
return VINF_SUCCESS;
}
int VBoxDnDWnd::hide(void)
{
#ifdef DEBUG_andy
LogFlowFunc(("\n"));
#endif
return VINF_SUCCESS;
}
int VBoxDnDWnd::makeFullscreen(void)
{
int rc = VINF_SUCCESS;
RECT r;
RT_ZERO(r);
if (hDC)
{
(LPARAM)&r);
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. */
&monitor_info))
{
r = monitor_info.rcMonitor;
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;
}
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. */
/* 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. */
break;
if (RT_SUCCESS(rc))
{
cMsgSkippedInvalid = 0; /* Reset skipped messages count. */
if (RT_FAILURE(rc2))
}
else if (rc == VERR_CANCELLED)
{
if (RT_FAILURE(rc2))
}
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 (rc == VERR_INVALID_PARAMETER)
if (cMsgSkippedInvalid > 3)
{
LogFlowFunc(("Too many invalid/skipped messages from host, exiting ...\n"));
break;
}
}
break;
} while (true);
LogFlowFunc(("Shutting down ...\n"));
return rc;
}