VirtualBox

source: vbox/trunk/src/VBox/Additions/WINNT/VBoxTray/VBoxDnD.cpp@ 82968

Last change on this file since 82968 was 82968, checked in by vboxsync, 5 years ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 58.2 KB
Line 
1/* $Id: VBoxDnD.cpp 82968 2020-02-04 10:35:17Z vboxsync $ */
2/** @file
3 * VBoxDnD.cpp - Windows-specific bits of the drag and drop service.
4 */
5
6/*
7 * Copyright (C) 2013-2020 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18
19/*********************************************************************************************************************************
20* Header Files *
21*********************************************************************************************************************************/
22#define LOG_GROUP LOG_GROUP_GUEST_DND
23#include <iprt/win/windows.h>
24#include "VBoxTray.h"
25#include "VBoxHelpers.h"
26#include "VBoxDnD.h"
27
28#include <VBox/VBoxGuestLib.h>
29#include "VBox/HostServices/DragAndDropSvc.h"
30
31using namespace DragAndDropSvc;
32
33#include <iprt/asm.h>
34#include <iprt/assert.h>
35#include <iprt/ldr.h>
36#include <iprt/list.h>
37#include <iprt/mem.h>
38
39#include <iprt/cpp/mtlist.h>
40#include <iprt/cpp/ministring.h>
41
42#include <iprt/cpp/mtlist.h>
43
44#include <VBox/err.h>
45#include <VBox/log.h>
46
47
48/*********************************************************************************************************************************
49* Defined Constants And Macros *
50*********************************************************************************************************************************/
51/* Enable this define to see the proxy window(s) when debugging
52 * their behavior. Don't have this enabled in release builds! */
53#ifdef DEBUG
54//# define VBOX_DND_DEBUG_WND
55#endif
56
57/** The drag and drop window's window class. */
58#define VBOX_DND_WND_CLASS "VBoxTrayDnDWnd"
59
60/** @todo Merge this with messages from VBoxTray.h. */
61#define WM_VBOXTRAY_DND_MESSAGE WM_APP + 401
62
63
64/*********************************************************************************************************************************
65* Structures and Typedefs *
66*********************************************************************************************************************************/
67/** Function pointer for SendInput(). This only is available starting
68 * at NT4 SP3+. */
69typedef BOOL (WINAPI *PFNSENDINPUT)(UINT, LPINPUT, int);
70typedef BOOL (WINAPI* PFNENUMDISPLAYMONITORS)(HDC, LPCRECT, MONITORENUMPROC, LPARAM);
71
72
73/*********************************************************************************************************************************
74* Global Variables *
75*********************************************************************************************************************************/
76/** Static pointer to SendInput() function. */
77static PFNSENDINPUT g_pfnSendInput = NULL;
78static PFNENUMDISPLAYMONITORS g_pfnEnumDisplayMonitors = NULL;
79
80static VBOXDNDCONTEXT g_Ctx = { 0 };
81
82
83/*********************************************************************************************************************************
84* Internal Functions *
85*********************************************************************************************************************************/
86static LRESULT CALLBACK vboxDnDWndProcInstance(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
87static LRESULT CALLBACK vboxDnDWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
88
89
90
91
92VBoxDnDWnd::VBoxDnDWnd(void)
93 : hThread(NIL_RTTHREAD),
94 mEventSem(NIL_RTSEMEVENT),
95 hWnd(NULL),
96 dndLstActionsAllowed(VBOX_DND_ACTION_IGNORE),
97 mfMouseButtonDown(false),
98#ifdef VBOX_WITH_DRAG_AND_DROP_GH
99 pDropTarget(NULL),
100#endif
101 mMode(Unknown),
102 mState(Uninitialized)
103{
104 RT_ZERO(startupInfo);
105
106 LogFlowFunc(("Supported formats:\n"));
107 const RTCString arrEntries[] = { VBOX_DND_FORMATS_DEFAULT };
108 for (size_t i = 0; i < RT_ELEMENTS(arrEntries); i++)
109 {
110 LogFlowFunc(("\t%s\n", arrEntries[i].c_str()));
111 this->lstFmtSup.append(arrEntries[i]);
112 }
113}
114
115VBoxDnDWnd::~VBoxDnDWnd(void)
116{
117 Destroy();
118}
119
120/**
121 * Initializes the proxy window with a given DnD context.
122 *
123 * @return IPRT status code.
124 * @param pCtx Pointer to context to use.
125 */
126int VBoxDnDWnd::Initialize(PVBOXDNDCONTEXT pCtx)
127{
128 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
129
130 /* Save the context. */
131 this->pCtx = pCtx;
132
133 int rc = RTSemEventCreate(&mEventSem);
134 if (RT_SUCCESS(rc))
135 rc = RTCritSectInit(&mCritSect);
136
137 if (RT_SUCCESS(rc))
138 {
139 /* Message pump thread for our proxy window. */
140 rc = RTThreadCreate(&hThread, VBoxDnDWnd::Thread, this,
141 0, RTTHREADTYPE_MSG_PUMP, RTTHREADFLAGS_WAITABLE,
142 "dndwnd"); /** @todo Include ID if there's more than one proxy window. */
143 if (RT_SUCCESS(rc))
144 {
145 int rc2 = RTThreadUserWait(hThread, 30 * 1000 /* Timeout in ms */);
146 AssertRC(rc2);
147
148 if (!pCtx->fStarted) /* Did the thread fail to start? */
149 rc = VERR_NOT_SUPPORTED; /* Report back DnD as not being supported. */
150 }
151 }
152
153 if (RT_FAILURE(rc))
154 LogRel(("DnD: Failed to initialize proxy window, rc=%Rrc\n", rc));
155
156 LogFlowThisFunc(("Returning rc=%Rrc\n", rc));
157 return rc;
158}
159
160/**
161 * Destroys the proxy window and releases all remaining
162 * resources again.
163 */
164void VBoxDnDWnd::Destroy(void)
165{
166 if (hThread != NIL_RTTHREAD)
167 {
168 int rcThread = VERR_WRONG_ORDER;
169 int rc = RTThreadWait(hThread, 60 * 1000 /* Timeout in ms */, &rcThread);
170 LogFlowFunc(("Waiting for thread resulted in %Rrc (thread exited with %Rrc)\n",
171 rc, rcThread));
172 NOREF(rc);
173 }
174
175 Reset();
176
177 RTCritSectDelete(&mCritSect);
178 if (mEventSem != NIL_RTSEMEVENT)
179 {
180 RTSemEventDestroy(mEventSem);
181 mEventSem = NIL_RTSEMEVENT;
182 }
183
184 if (pCtx->wndClass != 0)
185 {
186 UnregisterClass(VBOX_DND_WND_CLASS, pCtx->pEnv->hInstance);
187 pCtx->wndClass = 0;
188 }
189
190 LogFlowFuncLeave();
191}
192
193/**
194 * Thread for handling the window's message pump.
195 *
196 * @return IPRT status code.
197 * @param hThread Handle to this thread.
198 * @param pvUser Pointer to VBoxDnDWnd instance which
199 * is using the thread.
200 */
201/* static */
202int VBoxDnDWnd::Thread(RTTHREAD hThread, void *pvUser)
203{
204 AssertPtrReturn(pvUser, VERR_INVALID_POINTER);
205
206 LogFlowFuncEnter();
207
208 VBoxDnDWnd *pThis = (VBoxDnDWnd*)pvUser;
209 AssertPtr(pThis);
210
211 PVBOXDNDCONTEXT pCtx = pThis->pCtx;
212 AssertPtr(pCtx);
213 AssertPtr(pCtx->pEnv);
214
215 int rc = VINF_SUCCESS;
216
217 AssertPtr(pCtx->pEnv);
218 HINSTANCE hInstance = pCtx->pEnv->hInstance;
219 Assert(hInstance != 0);
220
221 /* Create our proxy window. */
222 WNDCLASSEX wc = { 0 };
223 wc.cbSize = sizeof(WNDCLASSEX);
224
225 if (!GetClassInfoEx(hInstance, VBOX_DND_WND_CLASS, &wc))
226 {
227 wc.lpfnWndProc = vboxDnDWndProc;
228 wc.lpszClassName = VBOX_DND_WND_CLASS;
229 wc.hInstance = hInstance;
230 wc.style = CS_NOCLOSE;
231#ifdef VBOX_DND_DEBUG_WND
232 wc.style |= CS_HREDRAW | CS_VREDRAW;
233 wc.hbrBackground = (HBRUSH)(CreateSolidBrush(RGB(255, 0, 0)));
234#else
235 wc.hbrBackground = (HBRUSH)(COLOR_BACKGROUND + 1);
236#endif
237 if (!RegisterClassEx(&wc))
238 {
239 DWORD dwErr = GetLastError();
240 LogFlowFunc(("Unable to register proxy window class, error=%ld\n", dwErr));
241 rc = RTErrConvertFromWin32(dwErr);
242 }
243 }
244
245 if (RT_SUCCESS(rc))
246 {
247 DWORD dwExStyle = WS_EX_TOOLWINDOW | WS_EX_TRANSPARENT | WS_EX_NOACTIVATE;
248 DWORD dwStyle = WS_POPUP;
249#ifdef VBOX_DND_DEBUG_WND
250 dwExStyle &= ~WS_EX_TRANSPARENT; /* Remove transparency bit. */
251 dwStyle |= WS_VISIBLE; /* Make the window visible. */
252#endif
253 pThis->hWnd =
254 CreateWindowEx(dwExStyle,
255 VBOX_DND_WND_CLASS, VBOX_DND_WND_CLASS,
256 dwStyle,
257#ifdef VBOX_DND_DEBUG_WND
258 CW_USEDEFAULT, CW_USEDEFAULT, 200, 200, NULL, NULL,
259#else
260 -200, -200, 100, 100, NULL, NULL,
261#endif
262 hInstance, pThis /* lParm */);
263 if (!pThis->hWnd)
264 {
265 DWORD dwErr = GetLastError();
266 LogFlowFunc(("Unable to create proxy window, error=%ld\n", dwErr));
267 rc = RTErrConvertFromWin32(dwErr);
268 }
269 else
270 {
271#ifndef VBOX_DND_DEBUG_WND
272 SetWindowPos(pThis->hWnd, HWND_TOPMOST, -200, -200, 0, 0,
273 SWP_NOACTIVATE | SWP_HIDEWINDOW
274 | SWP_NOCOPYBITS | SWP_NOREDRAW | SWP_NOSIZE);
275 LogFlowFunc(("Proxy window created, hWnd=0x%x\n", pThis->hWnd));
276#else
277 LogFlowFunc(("Debug proxy window created, hWnd=0x%x\n", pThis->hWnd));
278
279 /*
280 * Install some mouse tracking.
281 */
282 TRACKMOUSEEVENT me;
283 RT_ZERO(me);
284 me.cbSize = sizeof(TRACKMOUSEEVENT);
285 me.dwFlags = TME_HOVER | TME_LEAVE | TME_NONCLIENT;
286 me.hwndTrack = pThis->hWnd;
287 BOOL fRc = TrackMouseEvent(&me);
288 Assert(fRc);
289#endif
290 }
291 }
292
293 HRESULT hr = OleInitialize(NULL);
294 if (SUCCEEDED(hr))
295 {
296#ifdef VBOX_WITH_DRAG_AND_DROP_GH
297 rc = pThis->RegisterAsDropTarget();
298#endif
299 }
300 else
301 {
302 LogRel(("DnD: Unable to initialize OLE, hr=%Rhrc\n", hr));
303 rc = VERR_COM_UNEXPECTED;
304 }
305
306 if (RT_SUCCESS(rc))
307 pCtx->fStarted = true; /* Set started indicator on success. */
308
309 int rc2 = RTThreadUserSignal(hThread);
310 bool fSignalled = RT_SUCCESS(rc2);
311
312 if (RT_SUCCESS(rc))
313 {
314 bool fShutdown = false;
315 for (;;)
316 {
317 MSG uMsg;
318 BOOL fRet;
319 while ((fRet = GetMessage(&uMsg, 0, 0, 0)) > 0)
320 {
321 TranslateMessage(&uMsg);
322 DispatchMessage(&uMsg);
323 }
324 Assert(fRet >= 0);
325
326 if (ASMAtomicReadBool(&pCtx->fShutdown))
327 fShutdown = true;
328
329 if (fShutdown)
330 {
331 LogFlowFunc(("Closing proxy window ...\n"));
332 break;
333 }
334
335 /** @todo Immediately drop on failure? */
336 }
337
338#ifdef VBOX_WITH_DRAG_AND_DROP_GH
339 rc2 = pThis->UnregisterAsDropTarget();
340 if (RT_SUCCESS(rc))
341 rc = rc2;
342#endif
343 OleUninitialize();
344 }
345
346 if (!fSignalled)
347 {
348 rc2 = RTThreadUserSignal(hThread);
349 AssertRC(rc2);
350 }
351
352 LogFlowFuncLeaveRC(rc);
353 return rc;
354}
355
356/**
357 * Monitor enumeration callback for building up a simple bounding
358 * box, capable of holding all enumerated monitors.
359 *
360 * @return BOOL TRUE if enumeration should continue,
361 * FALSE if not.
362 * @param hMonitor Handle to current monitor being enumerated.
363 * @param hdcMonitor The current monitor's DC (device context).
364 * @param lprcMonitor The current monitor's RECT.
365 * @param lParam Pointer to a RECT structure holding the
366 * bounding box to build.
367 */
368/* static */
369BOOL CALLBACK VBoxDnDWnd::MonitorEnumProc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM lParam)
370{
371 RT_NOREF(hMonitor, hdcMonitor);
372 LPRECT pRect = (LPRECT)lParam;
373 AssertPtrReturn(pRect, FALSE);
374
375 AssertPtr(lprcMonitor);
376 LogFlowFunc(("Monitor is %ld,%ld,%ld,%ld\n",
377 lprcMonitor->left, lprcMonitor->top,
378 lprcMonitor->right, lprcMonitor->bottom));
379
380 /* Build up a simple bounding box to hold the entire (virtual) screen. */
381 if (pRect->left > lprcMonitor->left)
382 pRect->left = lprcMonitor->left;
383 if (pRect->right < lprcMonitor->right)
384 pRect->right = lprcMonitor->right;
385 if (pRect->top > lprcMonitor->top)
386 pRect->top = lprcMonitor->top;
387 if (pRect->bottom < lprcMonitor->bottom)
388 pRect->bottom = lprcMonitor->bottom;
389
390 return TRUE;
391}
392
393/**
394 * The proxy window's WndProc.
395 */
396LRESULT CALLBACK VBoxDnDWnd::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
397{
398 switch (uMsg)
399 {
400 case WM_CREATE:
401 {
402 int rc = OnCreate();
403 if (RT_FAILURE(rc))
404 {
405 LogRel(("DnD: Failed to create proxy window, rc=%Rrc\n", rc));
406 return -1;
407 }
408 return 0;
409 }
410
411 case WM_QUIT:
412 {
413 LogFlowThisFunc(("WM_QUIT\n"));
414 PostQuitMessage(0);
415 return 0;
416 }
417
418 case WM_DESTROY:
419 {
420 LogFlowThisFunc(("WM_DESTROY\n"));
421
422 OnDestroy();
423 return 0;
424 }
425
426 case WM_LBUTTONDOWN:
427 {
428 LogFlowThisFunc(("WM_LBUTTONDOWN\n"));
429 mfMouseButtonDown = true;
430 return 0;
431 }
432
433 case WM_LBUTTONUP:
434 {
435 LogFlowThisFunc(("WM_LBUTTONUP\n"));
436 mfMouseButtonDown = false;
437
438 /* As the mouse button was released, Hide the proxy window again.
439 * This can happen if
440 * - the user bumped a guest window to the screen's edges
441 * - there was no drop data from the guest available and the user
442 * enters the guest screen again after this unsuccessful operation */
443 Reset();
444 return 0;
445 }
446
447 case WM_MOUSELEAVE:
448 {
449 LogFlowThisFunc(("WM_MOUSELEAVE\n"));
450 return 0;
451 }
452
453 /* Will only be called once; after the first mouse move, this
454 * window will be hidden! */
455 case WM_MOUSEMOVE:
456 {
457 LogFlowThisFunc(("WM_MOUSEMOVE: mfMouseButtonDown=%RTbool, mMode=%ld, mState=%ld\n",
458 mfMouseButtonDown, mMode, mState));
459#ifdef DEBUG_andy
460 POINT p;
461 GetCursorPos(&p);
462 LogFlowThisFunc(("WM_MOUSEMOVE: curX=%ld, curY=%ld\n", p.x, p.y));
463#endif
464 int rc = VINF_SUCCESS;
465 if (mMode == HG) /* Host to guest. */
466 {
467 /* Dragging not started yet? Kick it off ... */
468 if ( mfMouseButtonDown
469 && (mState != Dragging))
470 {
471 mState = Dragging;
472#if 0
473 /* Delay hiding the proxy window a bit when debugging, to see
474 * whether the desired range is covered correctly. */
475 RTThreadSleep(5000);
476#endif
477 Hide();
478
479 LogFlowThisFunc(("Starting drag and drop: dndLstActionsAllowed=0x%x, dwOKEffects=0x%x ...\n",
480 dndLstActionsAllowed, startupInfo.dwOKEffects));
481
482 AssertPtr(startupInfo.pDataObject);
483 AssertPtr(startupInfo.pDropSource);
484 DWORD dwEffect;
485 HRESULT hr = DoDragDrop(startupInfo.pDataObject, startupInfo.pDropSource,
486 startupInfo.dwOKEffects, &dwEffect);
487 LogFlowThisFunc(("hr=%Rhrc, dwEffect=%RI32\n", hr, dwEffect));
488 switch (hr)
489 {
490 case DRAGDROP_S_DROP:
491 mState = Dropped;
492 break;
493
494 case DRAGDROP_S_CANCEL:
495 mState = Canceled;
496 break;
497
498 default:
499 LogFlowThisFunc(("Drag and drop failed with %Rhrc\n", hr));
500 mState = Canceled;
501 rc = VERR_GENERAL_FAILURE; /** @todo Find a better status code. */
502 break;
503 }
504
505 int rc2 = RTCritSectEnter(&mCritSect);
506 if (RT_SUCCESS(rc2))
507 {
508 startupInfo.pDropSource->Release();
509 startupInfo.pDataObject->Release();
510
511 RT_ZERO(startupInfo);
512
513 rc2 = RTCritSectLeave(&mCritSect);
514 if (RT_SUCCESS(rc))
515 rc = rc2;
516 }
517
518 mMode = Unknown;
519 }
520 }
521 else if (mMode == GH) /* Guest to host. */
522 {
523 /* Starting here VBoxDnDDropTarget should
524 * take over; was instantiated when registering
525 * this proxy window as a (valid) drop target. */
526 }
527 else
528 rc = VERR_NOT_SUPPORTED;
529
530 LogFlowThisFunc(("WM_MOUSEMOVE: mMode=%ld, mState=%ld, rc=%Rrc\n",
531 mMode, mState, rc));
532 return 0;
533 }
534
535 case WM_NCMOUSEHOVER:
536 LogFlowThisFunc(("WM_NCMOUSEHOVER\n"));
537 return 0;
538
539 case WM_NCMOUSELEAVE:
540 LogFlowThisFunc(("WM_NCMOUSELEAVE\n"));
541 return 0;
542
543 case WM_VBOXTRAY_DND_MESSAGE:
544 {
545 PVBOXDNDEVENT pEvent = (PVBOXDNDEVENT)lParam;
546 if (!pEvent)
547 break; /* No event received, bail out. */
548
549 PVBGLR3DNDEVENT pVbglR3Event = pEvent->pVbglR3Event;
550 AssertPtrBreak(pVbglR3Event);
551
552 LogFlowThisFunc(("Received enmType=%RU32\n", pVbglR3Event->enmType));
553
554 int rc;
555 switch (pVbglR3Event->enmType)
556 {
557 case VBGLR3DNDEVENTTYPE_HG_ENTER:
558 {
559 if (pVbglR3Event->u.HG_Enter.cbFormats)
560 {
561 RTCList<RTCString> lstFormats =
562 RTCString(pVbglR3Event->u.HG_Enter.pszFormats, pVbglR3Event->u.HG_Enter.cbFormats - 1).split("\r\n");
563 rc = OnHgEnter(lstFormats, pVbglR3Event->u.HG_Enter.dndLstActionsAllowed);
564 if (RT_FAILURE(rc))
565 break;
566 }
567 else
568 {
569 AssertMsgFailed(("cbFormats is 0\n"));
570 rc = VERR_INVALID_PARAMETER;
571 break;
572 }
573
574 /* Note: After HOST_DND_HG_EVT_ENTER there immediately is a move
575 * event, so fall through is intentional here. */
576 RT_FALL_THROUGH();
577 }
578
579 case VBGLR3DNDEVENTTYPE_HG_MOVE:
580 {
581 rc = OnHgMove(pVbglR3Event->u.HG_Move.uXpos, pVbglR3Event->u.HG_Move.uYpos,
582 pVbglR3Event->u.HG_Move.dndActionDefault);
583 break;
584 }
585
586 case VBGLR3DNDEVENTTYPE_HG_LEAVE:
587 {
588 rc = OnHgLeave();
589 break;
590 }
591
592 case VBGLR3DNDEVENTTYPE_HG_DROP:
593 {
594 rc = OnHgDrop();
595 break;
596 }
597
598 /**
599 * The data header now will contain all the (meta) data the guest needs in
600 * order to complete the DnD operation.
601 */
602 case VBGLR3DNDEVENTTYPE_HG_RECEIVE:
603 {
604 rc = OnHgDataReceive(&pVbglR3Event->u.HG_Received.Meta);
605 break;
606 }
607
608 case VBGLR3DNDEVENTTYPE_HG_CANCEL:
609 {
610 rc = OnHgCancel();
611 break;
612 }
613
614#ifdef VBOX_WITH_DRAG_AND_DROP_GH
615 case VBGLR3DNDEVENTTYPE_GH_ERROR:
616 {
617 Reset();
618 rc = VINF_SUCCESS;
619 break;
620 }
621
622 case VBGLR3DNDEVENTTYPE_GH_REQ_PENDING:
623 {
624 rc = OnGhIsDnDPending();
625 break;
626 }
627
628 case VBGLR3DNDEVENTTYPE_GH_DROP:
629 {
630 rc = OnGhDrop(pVbglR3Event->u.GH_Drop.pszFormat, pVbglR3Event->u.GH_Drop.dndActionRequested);
631 break;
632 }
633#endif
634 default:
635 {
636 LogRel(("DnD: Received unsupported message '%RU32'\n", pVbglR3Event->enmType));
637 rc = VERR_NOT_SUPPORTED;
638 break;
639 }
640 }
641
642 LogFlowFunc(("Message %RU32 processed with %Rrc\n", pVbglR3Event->enmType, rc));
643 if (RT_FAILURE(rc))
644 {
645 /* Tell the user. */
646 LogRel(("DnD: Processing message %RU32 failed with %Rrc\n", pVbglR3Event->enmType, rc));
647
648 /* If anything went wrong, do a reset and start over. */
649 Reset();
650 }
651
652 if (pEvent)
653 {
654 VbglR3DnDEventFree(pEvent->pVbglR3Event);
655 pEvent->pVbglR3Event = NULL;
656
657 RTMemFree(pEvent);
658 }
659
660 return 0;
661 }
662
663 default:
664 break;
665 }
666
667 return DefWindowProc(hWnd, uMsg, wParam, lParam);
668}
669
670#ifdef VBOX_WITH_DRAG_AND_DROP_GH
671/**
672 * Registers this proxy window as a local drop target.
673 *
674 * @return IPRT status code.
675 */
676int VBoxDnDWnd::RegisterAsDropTarget(void)
677{
678 if (pDropTarget) /* Already registered as drop target? */
679 return VINF_SUCCESS;
680
681 int rc;
682 try
683 {
684 pDropTarget = new VBoxDnDDropTarget(this /* pParent */);
685 HRESULT hr = CoLockObjectExternal(pDropTarget, TRUE /* fLock */,
686 FALSE /* fLastUnlockReleases */);
687 if (SUCCEEDED(hr))
688 hr = RegisterDragDrop(hWnd, pDropTarget);
689
690 if (FAILED(hr))
691 {
692 if (hr != DRAGDROP_E_INVALIDHWND) /* Could be because the DnD host service is not available. */
693 LogRel(("DnD: Creating drop target failed with hr=%Rhrc\n", hr));
694 rc = VERR_NOT_SUPPORTED; /* Report back DnD as not being supported. */
695 }
696 else
697 {
698 rc = VINF_SUCCESS;
699 }
700 }
701 catch (std::bad_alloc)
702 {
703 rc = VERR_NO_MEMORY;
704 }
705
706 LogFlowFuncLeaveRC(rc);
707 return rc;
708}
709
710/**
711 * Unregisters this proxy as a drop target.
712 *
713 * @return IPRT status code.
714 */
715int VBoxDnDWnd::UnregisterAsDropTarget(void)
716{
717 LogFlowFuncEnter();
718
719 if (!pDropTarget) /* No drop target? Bail out. */
720 return VINF_SUCCESS;
721
722 HRESULT hr = RevokeDragDrop(hWnd);
723 if (SUCCEEDED(hr))
724 hr = CoLockObjectExternal(pDropTarget, FALSE /* fLock */,
725 TRUE /* fLastUnlockReleases */);
726 if (SUCCEEDED(hr))
727 {
728 ULONG cRefs = pDropTarget->Release();
729 Assert(cRefs == 0); NOREF(cRefs);
730 pDropTarget = NULL;
731 }
732
733 int rc = SUCCEEDED(hr)
734 ? VINF_SUCCESS : VERR_GENERAL_FAILURE; /** @todo Fix this. */
735
736 LogFlowFuncLeaveRC(rc);
737 return rc;
738}
739#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
740
741/**
742 * Handles the creation of a proxy window.
743 *
744 * @return IPRT status code.
745 */
746int VBoxDnDWnd::OnCreate(void)
747{
748 LogFlowFuncEnter();
749 int rc = VbglR3DnDConnect(&mDnDCtx);
750 if (RT_FAILURE(rc))
751 {
752 LogRel(("DnD: Connection to host service failed, rc=%Rrc\n", rc));
753 return rc;
754 }
755
756 LogFlowThisFunc(("Client ID=%RU32, rc=%Rrc\n", mDnDCtx.uClientID, rc));
757 return rc;
758}
759
760/**
761 * Handles the destruction of a proxy window.
762 */
763void VBoxDnDWnd::OnDestroy(void)
764{
765 DestroyWindow(hWnd);
766
767 VbglR3DnDDisconnect(&mDnDCtx);
768 LogFlowThisFuncLeave();
769}
770
771/**
772 * Aborts an in-flight DnD operation on the guest.
773 *
774 * @return VBox status code.
775 */
776int VBoxDnDWnd::Abort(void)
777{
778 LogFlowThisFunc(("mMode=%ld, mState=%RU32\n", mMode, mState));
779 LogRel(("DnD: Drag and drop operation aborted\n"));
780
781 int rc = RTCritSectEnter(&mCritSect);
782 if (RT_SUCCESS(rc))
783 {
784 if (startupInfo.pDataObject)
785 startupInfo.pDataObject->Abort();
786
787 RTCritSectLeave(&mCritSect);
788 }
789
790 /* Post ESC to our window to officially abort the
791 * drag and drop operation. */
792 this->PostMessage(WM_KEYDOWN, VK_ESCAPE /* wParam */, 0 /* lParam */);
793
794 Reset();
795
796 return rc;
797}
798
799/**
800 * Handles actions required when the host cursor enters
801 * the guest's screen to initiate a host -> guest DnD operation.
802 *
803 * @return IPRT status code.
804 * @param lstFormats Supported formats offered by the host.
805 * @param dndLstActionsAllowed Supported actions offered by the host.
806 */
807int VBoxDnDWnd::OnHgEnter(const RTCList<RTCString> &lstFormats, VBOXDNDACTIONLIST dndLstActionsAllowed)
808{
809 if (mMode == GH) /* Wrong mode? Bail out. */
810 return VERR_WRONG_ORDER;
811
812#ifdef DEBUG
813 LogFlowThisFunc(("dndActionList=0x%x, lstFormats=%zu: ", dndLstActionsAllowed, lstFormats.size()));
814 for (size_t i = 0; i < lstFormats.size(); i++)
815 LogFlow(("'%s' ", lstFormats.at(i).c_str()));
816 LogFlow(("\n"));
817#endif
818
819 Reset();
820 setMode(HG);
821
822 /* Check if the VM session has changed and reconnect to the HGCM service if necessary. */
823 int rc = checkForSessionChange();
824 if (RT_FAILURE(rc))
825 return rc;
826
827 try
828 {
829 /* Save all allowed actions. */
830 this->dndLstActionsAllowed = dndLstActionsAllowed;
831
832 /*
833 * Check if reported formats from host are compatible with this client.
834 */
835 size_t cFormatsSup = this->lstFmtSup.size();
836 ULONG cFormatsActive = 0;
837
838 LPFORMATETC pFormatEtc = new FORMATETC[cFormatsSup];
839 RT_BZERO(pFormatEtc, sizeof(FORMATETC) * cFormatsSup);
840
841 LPSTGMEDIUM pStgMeds = new STGMEDIUM[cFormatsSup];
842 RT_BZERO(pStgMeds, sizeof(STGMEDIUM) * cFormatsSup);
843
844 LogRel2(("DnD: Reported formats:\n"));
845 for (size_t i = 0; i < lstFormats.size(); i++)
846 {
847 bool fSupported = false;
848 for (size_t a = 0; a < this->lstFmtSup.size(); a++)
849 {
850 const char *pszFormat = lstFormats.at(i).c_str();
851 LogFlowThisFunc(("\t\"%s\" <=> \"%s\"\n", this->lstFmtSup.at(a).c_str(), pszFormat));
852
853 fSupported = RTStrICmp(this->lstFmtSup.at(a).c_str(), pszFormat) == 0;
854 if (fSupported)
855 {
856 this->lstFmtActive.append(lstFormats.at(i));
857
858 /** @todo Put this into a \#define / struct. */
859 if (!RTStrICmp(pszFormat, "text/uri-list"))
860 {
861 pFormatEtc[cFormatsActive].cfFormat = CF_HDROP;
862 pFormatEtc[cFormatsActive].dwAspect = DVASPECT_CONTENT;
863 pFormatEtc[cFormatsActive].lindex = -1;
864 pFormatEtc[cFormatsActive].tymed = TYMED_HGLOBAL;
865
866 pStgMeds [cFormatsActive].tymed = TYMED_HGLOBAL;
867 cFormatsActive++;
868 }
869 else if ( !RTStrICmp(pszFormat, "text/plain")
870 || !RTStrICmp(pszFormat, "text/html")
871 || !RTStrICmp(pszFormat, "text/plain;charset=utf-8")
872 || !RTStrICmp(pszFormat, "text/plain;charset=utf-16")
873 || !RTStrICmp(pszFormat, "text/plain")
874 || !RTStrICmp(pszFormat, "text/richtext")
875 || !RTStrICmp(pszFormat, "UTF8_STRING")
876 || !RTStrICmp(pszFormat, "TEXT")
877 || !RTStrICmp(pszFormat, "STRING"))
878 {
879 pFormatEtc[cFormatsActive].cfFormat = CF_TEXT;
880 pFormatEtc[cFormatsActive].dwAspect = DVASPECT_CONTENT;
881 pFormatEtc[cFormatsActive].lindex = -1;
882 pFormatEtc[cFormatsActive].tymed = TYMED_HGLOBAL;
883
884 pStgMeds [cFormatsActive].tymed = TYMED_HGLOBAL;
885 cFormatsActive++;
886 }
887 else /* Should never happen. */
888 AssertReleaseMsgFailedBreak(("Format specification for '%s' not implemented\n", pszFormat));
889 break;
890 }
891 }
892
893 LogRel2(("DnD: \t%s: %RTbool\n", lstFormats.at(i).c_str(), fSupported));
894 }
895
896 /*
897 * Warn in the log if this guest does not accept anything.
898 */
899 Assert(cFormatsActive <= cFormatsSup);
900 if (cFormatsActive)
901 {
902 LogRel2(("DnD: %RU32 supported formats found:\n", cFormatsActive));
903 for (size_t i = 0; i < cFormatsActive; i++)
904 LogRel2(("DnD: \t%s\n", this->lstFmtActive.at(i).c_str()));
905 }
906 else
907 LogRel(("DnD: Warning: No supported drag and drop formats on the guest found!\n"));
908
909 /*
910 * Prepare the startup info for DoDragDrop().
911 */
912
913 /* Translate our drop actions into allowed Windows drop effects. */
914 startupInfo.dwOKEffects = DROPEFFECT_NONE;
915 if (dndLstActionsAllowed)
916 {
917 if (dndLstActionsAllowed & VBOX_DND_ACTION_COPY)
918 startupInfo.dwOKEffects |= DROPEFFECT_COPY;
919 if (dndLstActionsAllowed & VBOX_DND_ACTION_MOVE)
920 startupInfo.dwOKEffects |= DROPEFFECT_MOVE;
921 if (dndLstActionsAllowed & VBOX_DND_ACTION_LINK)
922 startupInfo.dwOKEffects |= DROPEFFECT_LINK;
923 }
924
925 LogRel2(("DnD: Supported drop actions: 0x%x\n", startupInfo.dwOKEffects));
926
927 startupInfo.pDropSource = new VBoxDnDDropSource(this);
928 startupInfo.pDataObject = new VBoxDnDDataObject(pFormatEtc, pStgMeds, cFormatsActive);
929
930 if (pFormatEtc)
931 delete pFormatEtc;
932 if (pStgMeds)
933 delete pStgMeds;
934 }
935 catch (std::bad_alloc)
936 {
937 rc = VERR_NO_MEMORY;
938 }
939
940 if (RT_SUCCESS(rc))
941 rc = makeFullscreen();
942
943 LogFlowFuncLeaveRC(rc);
944 return rc;
945}
946
947/**
948 * Handles actions required when the host cursor moves inside
949 * the guest's screen.
950 *
951 * @return IPRT status code.
952 * @param u32xPos Absolute X position (in pixels) of the host cursor
953 * inside the guest.
954 * @param u32yPos Absolute Y position (in pixels) of the host cursor
955 * inside the guest.
956 * @param dndAction Action the host wants to perform while moving.
957 * Currently ignored.
958 */
959int VBoxDnDWnd::OnHgMove(uint32_t u32xPos, uint32_t u32yPos, VBOXDNDACTION dndAction)
960{
961 RT_NOREF(dndAction);
962 int rc;
963
964 uint32_t uActionNotify = VBOX_DND_ACTION_IGNORE;
965 if (mMode == HG)
966 {
967 LogFlowThisFunc(("u32xPos=%RU32, u32yPos=%RU32, dndAction=0x%x\n",
968 u32xPos, u32yPos, dndAction));
969
970 rc = mouseMove(u32xPos, u32yPos, MOUSEEVENTF_LEFTDOWN);
971
972 if (RT_SUCCESS(rc))
973 rc = RTCritSectEnter(&mCritSect);
974 if (RT_SUCCESS(rc))
975 {
976 if ( (Dragging == mState)
977 && startupInfo.pDropSource)
978 uActionNotify = startupInfo.pDropSource->GetCurrentAction();
979
980 RTCritSectLeave(&mCritSect);
981 }
982 }
983 else /* Just acknowledge the operation with an ignore action. */
984 rc = VINF_SUCCESS;
985
986 if (RT_SUCCESS(rc))
987 {
988 rc = VbglR3DnDHGSendAckOp(&mDnDCtx, uActionNotify);
989 if (RT_FAILURE(rc))
990 LogFlowThisFunc(("Acknowledging operation failed with rc=%Rrc\n", rc));
991 }
992
993 LogFlowThisFunc(("Returning uActionNotify=0x%x, rc=%Rrc\n", uActionNotify, rc));
994 return rc;
995}
996
997/**
998 * Handles actions required when the host cursor leaves
999 * the guest's screen again.
1000 *
1001 * @return IPRT status code.
1002 */
1003int VBoxDnDWnd::OnHgLeave(void)
1004{
1005 if (mMode == GH) /* Wrong mode? Bail out. */
1006 return VERR_WRONG_ORDER;
1007
1008 int rc = Abort();
1009
1010 LogFlowFuncLeaveRC(rc);
1011 return rc;
1012}
1013
1014/**
1015 * Handles actions required when the host cursor wants to drop
1016 * and therefore start a "drop" action in the guest.
1017 *
1018 * @return IPRT status code.
1019 */
1020int VBoxDnDWnd::OnHgDrop(void)
1021{
1022 if (mMode == GH)
1023 return VERR_WRONG_ORDER;
1024
1025 LogFlowThisFunc(("mMode=%ld, mState=%RU32\n", mMode, mState));
1026
1027 int rc = VINF_SUCCESS;
1028 if (mState == Dragging)
1029 {
1030 if (lstFmtActive.size() >= 1)
1031 {
1032 /** @todo What to do when multiple formats are available? */
1033 mFormatRequested = lstFmtActive.at(0);
1034
1035 rc = RTCritSectEnter(&mCritSect);
1036 if (RT_SUCCESS(rc))
1037 {
1038 if (startupInfo.pDataObject)
1039 startupInfo.pDataObject->SetStatus(VBoxDnDDataObject::Dropping);
1040 else
1041 rc = VERR_NOT_FOUND;
1042
1043 RTCritSectLeave(&mCritSect);
1044 }
1045
1046 if (RT_SUCCESS(rc))
1047 {
1048 LogRel(("DnD: Requesting data as '%s' ...\n", mFormatRequested.c_str()));
1049 rc = VbglR3DnDHGSendReqData(&mDnDCtx, mFormatRequested.c_str());
1050 if (RT_FAILURE(rc))
1051 LogFlowThisFunc(("Requesting data failed with rc=%Rrc\n", rc));
1052 }
1053
1054 }
1055 else /* Should never happen. */
1056 LogRel(("DnD: Error: Host did not specify a data format for drop data\n"));
1057 }
1058
1059 LogFlowFuncLeaveRC(rc);
1060 return rc;
1061}
1062
1063/**
1064 * Handles actions required when the host has sent over DnD data
1065 * to the guest after a "drop" event.
1066 *
1067 * @return IPRT status code.
1068 * @param pMeta Pointer to meta data received.
1069 */
1070int VBoxDnDWnd::OnHgDataReceive(PVBGLR3GUESTDNDMETADATA pMeta)
1071{
1072 LogFlowThisFunc(("mState=%ld, enmMetaType=%RU32, cbMeta=%RU32\n", mState, pMeta->enmType, pMeta->cbMeta));
1073
1074 mState = Dropped;
1075
1076 int rc = VINF_SUCCESS;
1077 if (pMeta->pvMeta)
1078 {
1079 Assert(pMeta->cbMeta);
1080 rc = RTCritSectEnter(&mCritSect);
1081 if (RT_SUCCESS(rc))
1082 {
1083 if (startupInfo.pDataObject)
1084 rc = startupInfo.pDataObject->Signal(mFormatRequested, pMeta->pvMeta, pMeta->cbMeta);
1085 else
1086 rc = VERR_NOT_FOUND;
1087
1088 RTCritSectLeave(&mCritSect);
1089 }
1090 }
1091
1092 int rc2 = mouseRelease();
1093 if (RT_SUCCESS(rc))
1094 rc = rc2;
1095
1096 LogFlowFuncLeaveRC(rc);
1097 return rc;
1098}
1099
1100/**
1101 * Handles actions required when the host wants to cancel the current
1102 * host -> guest operation.
1103 *
1104 * @return IPRT status code.
1105 */
1106int VBoxDnDWnd::OnHgCancel(void)
1107{
1108 return Abort();
1109}
1110
1111#ifdef VBOX_WITH_DRAG_AND_DROP_GH
1112/**
1113 * Handles actions required to start a guest -> host DnD operation.
1114 * This works by letting the host ask whether a DnD operation is pending
1115 * on the guest. The guest must not know anything about the host's DnD state
1116 * and/or operations due to security reasons.
1117 *
1118 * To capture a pending DnD operation on the guest which then can be communicated
1119 * to the host the proxy window needs to be registered as a drop target. This drop
1120 * target then will act as a proxy target between the guest OS and the host. In other
1121 * words, the guest OS will use this proxy target as a regular (invisible) window
1122 * which can be used by the regular guest OS' DnD mechanisms, independently of the
1123 * host OS. To make sure this proxy target is able receive an in-progress DnD operation
1124 * on the guest, it will be shown invisibly across all active guest OS screens. Just
1125 * think of an opened umbrella across all screens here.
1126 *
1127 * As soon as the proxy target and its underlying data object receive appropriate
1128 * DnD messages they'll be hidden again, and the control will be transferred back
1129 * this class again.
1130 *
1131 * @return IPRT status code.
1132 */
1133int VBoxDnDWnd::OnGhIsDnDPending(void)
1134{
1135 LogFlowThisFunc(("mMode=%ld, mState=%ld\n", mMode, mState));
1136
1137 if (mMode == Unknown)
1138 setMode(GH);
1139
1140 if (mMode != GH)
1141 return VERR_WRONG_ORDER;
1142
1143 if (mState == Uninitialized)
1144 {
1145 /* Nothing to do here yet. */
1146 mState = Initialized;
1147 }
1148
1149 int rc;
1150 if (mState == Initialized)
1151 {
1152 /* Check if the VM session has changed and reconnect to the HGCM service if necessary. */
1153 rc = checkForSessionChange();
1154 if (RT_SUCCESS(rc))
1155 {
1156 rc = makeFullscreen();
1157 if (RT_SUCCESS(rc))
1158 {
1159 /*
1160 * We have to release the left mouse button to
1161 * get into our (invisible) proxy window.
1162 */
1163 mouseRelease();
1164
1165 /*
1166 * Even if we just released the left mouse button
1167 * we're still in the dragging state to handle our
1168 * own drop target (for the host).
1169 */
1170 mState = Dragging;
1171 }
1172 }
1173 }
1174 else
1175 rc = VINF_SUCCESS;
1176
1177 /**
1178 * Some notes regarding guest cursor movement:
1179 * - The host only sends an HOST_DND_GH_REQ_PENDING message to the guest
1180 * if the mouse cursor is outside the VM's window.
1181 * - The guest does not know anything about the host's cursor
1182 * position / state due to security reasons.
1183 * - The guest *only* knows that the host currently is asking whether a
1184 * guest DnD operation is in progress.
1185 */
1186
1187 if ( RT_SUCCESS(rc)
1188 && mState == Dragging)
1189 {
1190 /** @todo Put this block into a function! */
1191 POINT p;
1192 GetCursorPos(&p);
1193 ClientToScreen(hWnd, &p);
1194#ifdef DEBUG_andy
1195 LogFlowThisFunc(("Client to screen curX=%ld, curY=%ld\n", p.x, p.y));
1196#endif
1197
1198 /** @todo Multi-monitor setups? */
1199#if 0 /* unused */
1200 int iScreenX = GetSystemMetrics(SM_CXSCREEN) - 1;
1201 int iScreenY = GetSystemMetrics(SM_CYSCREEN) - 1;
1202#endif
1203
1204 LONG px = p.x;
1205 if (px <= 0)
1206 px = 1;
1207 LONG py = p.y;
1208 if (py <= 0)
1209 py = 1;
1210
1211 rc = mouseMove(px, py, 0 /* dwMouseInputFlags */);
1212 }
1213
1214 if (RT_SUCCESS(rc))
1215 {
1216 VBOXDNDACTION dndActionDefault = VBOX_DND_ACTION_IGNORE;
1217
1218 AssertPtr(pDropTarget);
1219 RTCString strFormats = pDropTarget->Formats();
1220 if (!strFormats.isEmpty())
1221 {
1222 dndActionDefault = VBOX_DND_ACTION_COPY;
1223
1224 LogFlowFunc(("Acknowledging pDropTarget=0x%p, dndActionDefault=0x%x, dndLstActionsAllowed=0x%x, strFormats=%s\n",
1225 pDropTarget, dndActionDefault, dndLstActionsAllowed, strFormats.c_str()));
1226 }
1227 else
1228 {
1229 strFormats = "unknown"; /* Prevent VERR_IO_GEN_FAILURE for IOCTL. */
1230 LogFlowFunc(("No format data from proxy window available yet\n"));
1231 }
1232
1233 /** @todo Support more than one action at a time. */
1234 dndLstActionsAllowed = dndActionDefault;
1235
1236 int rc2 = VbglR3DnDGHSendAckPending(&mDnDCtx,
1237 dndActionDefault, dndLstActionsAllowed,
1238 strFormats.c_str(), (uint32_t)strFormats.length() + 1 /* Include termination */);
1239 if (RT_FAILURE(rc2))
1240 {
1241 char szMsg[256]; /* Sizes according to MSDN. */
1242 char szTitle[64];
1243
1244 /** @todo Add some i18l tr() macros here. */
1245 RTStrPrintf(szTitle, sizeof(szTitle), "VirtualBox Guest Additions Drag and Drop");
1246 RTStrPrintf(szMsg, sizeof(szMsg), "Drag and drop to the host either is not supported or disabled. "
1247 "Please enable Guest to Host or Bidirectional drag and drop mode "
1248 "or re-install the VirtualBox Guest Additions.");
1249 switch (rc2)
1250 {
1251 case VERR_ACCESS_DENIED:
1252 {
1253 rc = hlpShowBalloonTip(g_hInstance, g_hwndToolWindow, ID_TRAYICON,
1254 szMsg, szTitle,
1255 15 * 1000 /* Time to display in msec */, NIIF_INFO);
1256 AssertRC(rc);
1257 break;
1258 }
1259
1260 default:
1261 break;
1262 }
1263
1264 LogRel2(("DnD: Host refuses drag and drop operation from guest: %Rrc\n", rc2));
1265 Reset();
1266 }
1267 }
1268
1269 if (RT_FAILURE(rc))
1270 Reset(); /* Reset state on failure. */
1271
1272 LogFlowFuncLeaveRC(rc);
1273 return rc;
1274}
1275
1276/**
1277 * Handles actions required to let the guest know that the host
1278 * started a "drop" action on the host. This will tell the guest
1279 * to send data in a specific format the host requested.
1280 *
1281 * @return IPRT status code.
1282 * @param pszFormat Format the host requests the data in.
1283 * @param cbFormat Size (in bytes) of format string.
1284 * @param dndActionDefault Default action on the host.
1285 */
1286int VBoxDnDWnd::OnGhDrop(const RTCString &strFormat, uint32_t dndActionDefault)
1287{
1288 RT_NOREF(dndActionDefault);
1289
1290 LogFlowThisFunc(("mMode=%ld, mState=%ld, pDropTarget=0x%p, strFormat=%s, dndActionDefault=0x%x\n",
1291 mMode, mState, pDropTarget, strFormat.c_str(), dndActionDefault));
1292 int rc;
1293 if (mMode == GH)
1294 {
1295 if (mState == Dragging)
1296 {
1297 AssertPtr(pDropTarget);
1298 rc = pDropTarget->WaitForDrop(5 * 1000 /* 5s timeout */);
1299
1300 Reset();
1301 }
1302 else if (mState == Dropped)
1303 {
1304 rc = VINF_SUCCESS;
1305 }
1306 else
1307 rc = VERR_WRONG_ORDER;
1308
1309 if (RT_SUCCESS(rc))
1310 {
1311 /** @todo Respect uDefAction. */
1312 void *pvData = pDropTarget->DataMutableRaw();
1313 uint32_t cbData = (uint32_t)pDropTarget->DataSize();
1314 Assert(cbData == pDropTarget->DataSize());
1315
1316 if ( pvData
1317 && cbData)
1318 {
1319 rc = VbglR3DnDGHSendData(&mDnDCtx, strFormat.c_str(), pvData, cbData);
1320 LogFlowFunc(("Sent pvData=0x%p, cbData=%RU32, rc=%Rrc\n", pvData, cbData, rc));
1321 }
1322 else
1323 rc = VERR_NO_DATA;
1324 }
1325 }
1326 else
1327 rc = VERR_WRONG_ORDER;
1328
1329 if (RT_FAILURE(rc))
1330 {
1331 /*
1332 * If an error occurred or the guest is in a wrong DnD mode,
1333 * send an error to the host in any case so that the host does
1334 * not wait for the data it expects from the guest.
1335 */
1336 int rc2 = VbglR3DnDGHSendError(&mDnDCtx, rc);
1337 AssertRC(rc2);
1338 }
1339
1340 LogFlowFuncLeaveRC(rc);
1341 return rc;
1342}
1343#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
1344
1345void VBoxDnDWnd::PostMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
1346{
1347 LogFlowFunc(("Posting message %u\n"));
1348 BOOL fRc = ::PostMessage(hWnd, uMsg, wParam, lParam);
1349 Assert(fRc); NOREF(fRc);
1350}
1351
1352/**
1353 * Injects a DnD event in this proxy window's Windows
1354 * event queue. The (allocated) event will be deleted by
1355 * this class after processing.
1356 *
1357 * @return IPRT status code.
1358 * @param pEvent Event to inject.
1359 */
1360int VBoxDnDWnd::ProcessEvent(PVBOXDNDEVENT pEvent)
1361{
1362 AssertPtrReturn(pEvent, VERR_INVALID_POINTER);
1363
1364 BOOL fRc = ::PostMessage(hWnd, WM_VBOXTRAY_DND_MESSAGE,
1365 0 /* wParm */, (LPARAM)pEvent /* lParm */);
1366 if (!fRc)
1367 {
1368 DWORD dwErr = GetLastError();
1369
1370 static int s_iBitchedAboutFailedDnDMessages = 0;
1371 if (s_iBitchedAboutFailedDnDMessages++ < 32)
1372 {
1373 LogRel(("DnD: Processing event %p failed with %ld (%Rrc), skipping\n",
1374 pEvent, dwErr, RTErrConvertFromWin32(dwErr)));
1375 }
1376
1377 VbglR3DnDEventFree(pEvent->pVbglR3Event);
1378
1379 RTMemFree(pEvent);
1380 pEvent = NULL;
1381
1382 return RTErrConvertFromWin32(dwErr);
1383 }
1384
1385 return VINF_SUCCESS;
1386}
1387
1388/**
1389 * Checks if the VM session has changed (can happen when restoring the VM from a saved state)
1390 * and do a reconnect to the DnD HGCM service.
1391 *
1392 * @returns IPRT status code.
1393 */
1394int VBoxDnDWnd::checkForSessionChange(void)
1395{
1396 uint64_t uSessionID;
1397 int rc = VbglR3GetSessionId(&uSessionID);
1398 if ( RT_SUCCESS(rc)
1399 && uSessionID != mDnDCtx.uSessionID)
1400 {
1401 LogFlowThisFunc(("VM session has changed to %RU64\n", uSessionID));
1402
1403 rc = VbglR3DnDDisconnect(&mDnDCtx);
1404 AssertRC(rc);
1405
1406 rc = VbglR3DnDConnect(&mDnDCtx);
1407 AssertRC(rc);
1408 }
1409
1410 LogFlowFuncLeaveRC(rc);
1411 return rc;
1412}
1413
1414/**
1415 * Hides the proxy window again.
1416 *
1417 * @return IPRT status code.
1418 */
1419int VBoxDnDWnd::Hide(void)
1420{
1421#ifdef DEBUG_andy
1422 LogFlowFunc(("\n"));
1423#endif
1424 ShowWindow(hWnd, SW_HIDE);
1425
1426 return VINF_SUCCESS;
1427}
1428
1429/**
1430 * Shows the (invisible) proxy window in fullscreen,
1431 * spawned across all active guest monitors.
1432 *
1433 * @return IPRT status code.
1434 */
1435int VBoxDnDWnd::makeFullscreen(void)
1436{
1437 int rc = VINF_SUCCESS;
1438
1439 RECT r;
1440 RT_ZERO(r);
1441
1442 BOOL fRc;
1443 HDC hDC = GetDC(NULL /* Entire screen */);
1444 if (hDC)
1445 {
1446 fRc = g_pfnEnumDisplayMonitors
1447 /* EnumDisplayMonitors is not available on NT4. */
1448 ? g_pfnEnumDisplayMonitors(hDC, NULL, VBoxDnDWnd::MonitorEnumProc, (LPARAM)&r):
1449 FALSE;
1450
1451 if (!fRc)
1452 rc = VERR_NOT_FOUND;
1453 ReleaseDC(NULL, hDC);
1454 }
1455 else
1456 rc = VERR_ACCESS_DENIED;
1457
1458 if (RT_FAILURE(rc))
1459 {
1460 /* If multi-monitor enumeration failed above, try getting at least the
1461 * primary monitor as a fallback. */
1462 r.left = 0;
1463 r.top = 0;
1464 r.right = GetSystemMetrics(SM_CXSCREEN);
1465 r.bottom = GetSystemMetrics(SM_CYSCREEN);
1466 rc = VINF_SUCCESS;
1467 }
1468
1469 if (RT_SUCCESS(rc))
1470 {
1471 LONG lStyle = GetWindowLong(hWnd, GWL_STYLE);
1472 SetWindowLong(hWnd, GWL_STYLE,
1473 lStyle & ~(WS_CAPTION | WS_THICKFRAME));
1474 LONG lExStyle = GetWindowLong(hWnd, GWL_EXSTYLE);
1475 SetWindowLong(hWnd, GWL_EXSTYLE,
1476 lExStyle & ~( WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE
1477 | WS_EX_CLIENTEDGE | WS_EX_STATICEDGE));
1478
1479 fRc = SetWindowPos(hWnd, HWND_TOPMOST,
1480 r.left,
1481 r.top,
1482 r.right - r.left,
1483 r.bottom - r.top,
1484#ifdef VBOX_DND_DEBUG_WND
1485 SWP_SHOWWINDOW | SWP_FRAMECHANGED);
1486#else
1487 SWP_SHOWWINDOW | SWP_NOOWNERZORDER | SWP_NOREDRAW | SWP_NOACTIVATE);
1488#endif
1489 if (fRc)
1490 {
1491 LogFlowFunc(("Virtual screen is %ld,%ld,%ld,%ld (%ld x %ld)\n",
1492 r.left, r.top, r.right, r.bottom,
1493 r.right - r.left, r.bottom - r.top));
1494 }
1495 else
1496 {
1497 DWORD dwErr = GetLastError();
1498 LogRel(("DnD: Failed to set proxy window position, rc=%Rrc\n",
1499 RTErrConvertFromWin32(dwErr)));
1500 }
1501 }
1502 else
1503 LogRel(("DnD: Failed to determine virtual screen size, rc=%Rrc\n", rc));
1504
1505 LogFlowFuncLeaveRC(rc);
1506 return rc;
1507}
1508
1509/**
1510 * Moves the guest mouse cursor to a specific position.
1511 *
1512 * @return IPRT status code.
1513 * @param x X position (in pixels) to move cursor to.
1514 * @param y Y position (in pixels) to move cursor to.
1515 * @param dwMouseInputFlags Additional movement flags. @sa MOUSEEVENTF_ flags.
1516 */
1517int VBoxDnDWnd::mouseMove(int x, int y, DWORD dwMouseInputFlags)
1518{
1519 int iScreenX = GetSystemMetrics(SM_CXSCREEN) - 1;
1520 int iScreenY = GetSystemMetrics(SM_CYSCREEN) - 1;
1521
1522 INPUT Input[1] = { 0 };
1523 Input[0].type = INPUT_MOUSE;
1524 Input[0].mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE
1525 | dwMouseInputFlags;
1526 Input[0].mi.dx = x * (65535 / iScreenX);
1527 Input[0].mi.dy = y * (65535 / iScreenY);
1528
1529 int rc;
1530 if (g_pfnSendInput(1 /* Number of inputs */,
1531 Input, sizeof(INPUT)))
1532 {
1533#ifdef DEBUG_andy
1534 CURSORINFO ci;
1535 RT_ZERO(ci);
1536 ci.cbSize = sizeof(ci);
1537 BOOL fRc = GetCursorInfo(&ci);
1538 if (fRc)
1539 LogFlowThisFunc(("Cursor shown=%RTbool, cursor=0x%p, x=%d, y=%d\n",
1540 (ci.flags & CURSOR_SHOWING) ? true : false,
1541 ci.hCursor, ci.ptScreenPos.x, ci.ptScreenPos.y));
1542#endif
1543 rc = VINF_SUCCESS;
1544 }
1545 else
1546 {
1547 DWORD dwErr = GetLastError();
1548 rc = RTErrConvertFromWin32(dwErr);
1549 LogFlowFunc(("SendInput failed with rc=%Rrc\n", rc));
1550 }
1551
1552 return rc;
1553}
1554
1555/**
1556 * Releases a previously pressed left guest mouse button.
1557 *
1558 * @return IPRT status code.
1559 */
1560int VBoxDnDWnd::mouseRelease(void)
1561{
1562 LogFlowFuncEnter();
1563
1564 int rc;
1565
1566 /* Release mouse button in the guest to start the "drop"
1567 * action at the current mouse cursor position. */
1568 INPUT Input[1] = { 0 };
1569 Input[0].type = INPUT_MOUSE;
1570 Input[0].mi.dwFlags = MOUSEEVENTF_LEFTUP;
1571 if (!g_pfnSendInput(1, Input, sizeof(INPUT)))
1572 {
1573 DWORD dwErr = GetLastError();
1574 rc = RTErrConvertFromWin32(dwErr);
1575 LogFlowFunc(("SendInput failed with rc=%Rrc\n", rc));
1576 }
1577 else
1578 rc = VINF_SUCCESS;
1579
1580 return rc;
1581}
1582
1583/**
1584 * Resets the proxy window.
1585 */
1586void VBoxDnDWnd::Reset(void)
1587{
1588 LogFlowThisFunc(("Resetting, old mMode=%ld, mState=%ld\n",
1589 mMode, mState));
1590
1591 /*
1592 * Note: Don't clear this->lstAllowedFormats at the moment, as this value is initialized
1593 * on class creation. We might later want to modify the allowed formats at runtime,
1594 * so keep this in mind when implementing this.
1595 */
1596
1597 this->lstFmtActive.clear();
1598 this->dndLstActionsAllowed = VBOX_DND_ACTION_IGNORE;
1599
1600 int rc2 = setMode(Unknown);
1601 AssertRC(rc2);
1602
1603 Hide();
1604}
1605
1606/**
1607 * Sets the current operation mode of this proxy window.
1608 *
1609 * @return IPRT status code.
1610 * @param enmMode New mode to set.
1611 */
1612int VBoxDnDWnd::setMode(Mode enmMode)
1613{
1614 LogFlowThisFunc(("Old mode=%ld, new mode=%ld\n",
1615 mMode, enmMode));
1616
1617 mMode = enmMode;
1618 mState = Initialized;
1619
1620 return VINF_SUCCESS;
1621}
1622
1623/**
1624 * Static helper function for having an own WndProc for proxy
1625 * window instances.
1626 */
1627static LRESULT CALLBACK vboxDnDWndProcInstance(HWND hWnd, UINT uMsg,
1628 WPARAM wParam, LPARAM lParam)
1629{
1630 LONG_PTR pUserData = GetWindowLongPtr(hWnd, GWLP_USERDATA);
1631 AssertPtrReturn(pUserData, 0);
1632
1633 VBoxDnDWnd *pWnd = reinterpret_cast<VBoxDnDWnd *>(pUserData);
1634 if (pWnd)
1635 return pWnd->WndProc(hWnd, uMsg, wParam, lParam);
1636
1637 return 0;
1638}
1639
1640/**
1641 * Static helper function for routing Windows messages to a specific
1642 * proxy window instance.
1643 */
1644static LRESULT CALLBACK vboxDnDWndProc(HWND hWnd, UINT uMsg,
1645 WPARAM wParam, LPARAM lParam)
1646{
1647 /* Note: WM_NCCREATE is not the first ever message which arrives, but
1648 * early enough for us. */
1649 if (uMsg == WM_NCCREATE)
1650 {
1651 LPCREATESTRUCT pCS = (LPCREATESTRUCT)lParam;
1652 AssertPtr(pCS);
1653 SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)pCS->lpCreateParams);
1654 SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)vboxDnDWndProcInstance);
1655
1656 return vboxDnDWndProcInstance(hWnd, uMsg, wParam, lParam);
1657 }
1658
1659 /* No window associated yet. */
1660 return DefWindowProc(hWnd, uMsg, wParam, lParam);
1661}
1662
1663/**
1664 * Initializes drag and drop.
1665 *
1666 * @return IPRT status code.
1667 * @param pEnv The DnD service's environment.
1668 * @param ppInstance The instance pointer which refer to this object.
1669 */
1670DECLCALLBACK(int) VBoxDnDInit(const PVBOXSERVICEENV pEnv, void **ppInstance)
1671{
1672 AssertPtrReturn(pEnv, VERR_INVALID_POINTER);
1673 AssertPtrReturn(ppInstance, VERR_INVALID_POINTER);
1674
1675 LogFlowFuncEnter();
1676
1677 PVBOXDNDCONTEXT pCtx = &g_Ctx; /* Only one instance at the moment. */
1678 AssertPtr(pCtx);
1679
1680 int rc;
1681 bool fSupportedOS = true;
1682
1683 if (VbglR3AutoLogonIsRemoteSession())
1684 {
1685 /* Do not do drag and drop for remote sessions. */
1686 LogRel(("DnD: Drag and drop has been disabled for a remote session\n"));
1687 rc = VERR_NOT_SUPPORTED;
1688 }
1689 else
1690 rc = VINF_SUCCESS;
1691
1692 if (RT_SUCCESS(rc))
1693 {
1694 g_pfnSendInput = (PFNSENDINPUT)
1695 RTLdrGetSystemSymbol("User32.dll", "SendInput");
1696 fSupportedOS = !RT_BOOL(g_pfnSendInput == NULL);
1697 g_pfnEnumDisplayMonitors = (PFNENUMDISPLAYMONITORS)
1698 RTLdrGetSystemSymbol("User32.dll", "EnumDisplayMonitors");
1699 /* g_pfnEnumDisplayMonitors is optional. */
1700
1701 if (!fSupportedOS)
1702 {
1703 LogRel(("DnD: Not supported Windows version, disabling drag and drop support\n"));
1704 rc = VERR_NOT_SUPPORTED;
1705 }
1706 }
1707
1708 if (RT_SUCCESS(rc))
1709 {
1710 /* Assign service environment to our context. */
1711 pCtx->pEnv = pEnv;
1712
1713 /* Create the proxy window. At the moment we
1714 * only support one window at a time. */
1715 VBoxDnDWnd *pWnd = NULL;
1716 try
1717 {
1718 pWnd = new VBoxDnDWnd();
1719 rc = pWnd->Initialize(pCtx);
1720
1721 /* Add proxy window to our proxy windows list. */
1722 if (RT_SUCCESS(rc))
1723 pCtx->lstWnd.append(pWnd);
1724 }
1725 catch (std::bad_alloc)
1726 {
1727 rc = VERR_NO_MEMORY;
1728 }
1729 }
1730
1731 if (RT_SUCCESS(rc))
1732 rc = RTSemEventCreate(&pCtx->hEvtQueueSem);
1733 if (RT_SUCCESS(rc))
1734 {
1735 *ppInstance = pCtx;
1736
1737 LogRel(("DnD: Drag and drop service successfully started\n"));
1738 }
1739 else
1740 LogRel(("DnD: Initializing drag and drop service failed with rc=%Rrc\n", rc));
1741
1742 LogFlowFuncLeaveRC(rc);
1743 return rc;
1744}
1745
1746DECLCALLBACK(int) VBoxDnDStop(void *pInstance)
1747{
1748 AssertPtrReturn(pInstance, VERR_INVALID_POINTER);
1749
1750 LogFunc(("Stopping pInstance=%p\n", pInstance));
1751
1752 PVBOXDNDCONTEXT pCtx = (PVBOXDNDCONTEXT)pInstance;
1753 AssertPtr(pCtx);
1754
1755 /* Set shutdown indicator. */
1756 ASMAtomicWriteBool(&pCtx->fShutdown, true);
1757
1758 /* Disconnect. */
1759 VbglR3DnDDisconnect(&pCtx->cmdCtx);
1760
1761 LogFlowFuncLeaveRC(VINF_SUCCESS);
1762 return VINF_SUCCESS;
1763}
1764
1765DECLCALLBACK(void) VBoxDnDDestroy(void *pInstance)
1766{
1767 AssertPtrReturnVoid(pInstance);
1768
1769 LogFunc(("Destroying pInstance=%p\n", pInstance));
1770
1771 PVBOXDNDCONTEXT pCtx = (PVBOXDNDCONTEXT)pInstance;
1772 AssertPtr(pCtx);
1773
1774 /** @todo At the moment we only have one DnD proxy window. */
1775 Assert(pCtx->lstWnd.size() == 1);
1776 VBoxDnDWnd *pWnd = pCtx->lstWnd.first();
1777 if (pWnd)
1778 {
1779 delete pWnd;
1780 pWnd = NULL;
1781 }
1782
1783 if (pCtx->hEvtQueueSem != NIL_RTSEMEVENT)
1784 {
1785 RTSemEventDestroy(pCtx->hEvtQueueSem);
1786 pCtx->hEvtQueueSem = NIL_RTSEMEVENT;
1787 }
1788
1789 LogFunc(("Destroyed pInstance=%p\n", pInstance));
1790}
1791
1792DECLCALLBACK(int) VBoxDnDWorker(void *pInstance, bool volatile *pfShutdown)
1793{
1794 AssertPtr(pInstance);
1795 AssertPtr(pfShutdown);
1796
1797 LogFlowFunc(("pInstance=%p\n", pInstance));
1798
1799 /*
1800 * Tell the control thread that it can continue
1801 * spawning services.
1802 */
1803 RTThreadUserSignal(RTThreadSelf());
1804
1805 PVBOXDNDCONTEXT pCtx = (PVBOXDNDCONTEXT)pInstance;
1806 AssertPtr(pCtx);
1807
1808 int rc = VbglR3DnDConnect(&pCtx->cmdCtx);
1809 if (RT_FAILURE(rc))
1810 return rc;
1811
1812 /** @todo At the moment we only have one DnD proxy window. */
1813 Assert(pCtx->lstWnd.size() == 1);
1814 VBoxDnDWnd *pWnd = pCtx->lstWnd.first();
1815 AssertPtr(pWnd);
1816
1817 /* Number of invalid messages skipped in a row. */
1818 int cMsgSkippedInvalid = 0;
1819 PVBOXDNDEVENT pEvent = NULL;
1820
1821 for (;;)
1822 {
1823 pEvent = (PVBOXDNDEVENT)RTMemAllocZ(sizeof(VBOXDNDEVENT));
1824 if (!pEvent)
1825 {
1826 rc = VERR_NO_MEMORY;
1827 break;
1828 }
1829 /* Note: pEvent will be free'd by the consumer later. */
1830
1831 PVBGLR3DNDEVENT pVbglR3Event = NULL;
1832 rc = VbglR3DnDEventGetNext(&pCtx->cmdCtx, &pVbglR3Event);
1833 if (RT_SUCCESS(rc))
1834 {
1835 LogFunc(("enmType=%RU32, rc=%Rrc\n", pVbglR3Event->enmType, rc));
1836
1837 cMsgSkippedInvalid = 0; /* Reset skipped messages count. */
1838
1839 LogRel2(("DnD: Received new event, type=%RU32, rc=%Rrc\n", pVbglR3Event->enmType, rc));
1840
1841 /* pEvent now owns pVbglR3Event. */
1842 pEvent->pVbglR3Event = pVbglR3Event;
1843 pVbglR3Event = NULL;
1844
1845 rc = pWnd->ProcessEvent(pEvent);
1846 if (RT_SUCCESS(rc))
1847 {
1848 /* Event was consumed and the proxy window till take care of the memory -- NULL it. */
1849 pEvent = NULL;
1850 }
1851 else
1852 LogRel(("DnD: Processing proxy window event %RU32 failed with %Rrc\n", pVbglR3Event->enmType, rc));
1853 }
1854 else if (rc == VERR_INTERRUPTED) /* Disconnected from service. */
1855 {
1856 LogRel(("DnD: Received quit message, shutting down ...\n"));
1857 pWnd->PostMessage(WM_QUIT, 0 /* wParm */, 0 /* lParm */);
1858 rc = VINF_SUCCESS;
1859 }
1860
1861 if (RT_FAILURE(rc))
1862 {
1863 if (pEvent)
1864 {
1865 RTMemFree(pEvent);
1866 pEvent = NULL;
1867 }
1868
1869 LogFlowFunc(("Processing next message failed with rc=%Rrc\n", rc));
1870
1871 /* Old(er) hosts either are broken regarding DnD support or otherwise
1872 * don't support the stuff we do on the guest side, so make sure we
1873 * don't process invalid messages forever. */
1874 if (cMsgSkippedInvalid++ > 32)
1875 {
1876 LogRel(("DnD: Too many invalid/skipped messages from host, exiting ...\n"));
1877 break;
1878 }
1879
1880 /* Make sure our proxy window is hidden when an error occured to
1881 * not block the guest's UI. */
1882 int rc2 = pWnd->Abort();
1883 AssertRC(rc2);
1884 }
1885
1886 if (*pfShutdown)
1887 break;
1888
1889 if (ASMAtomicReadBool(&pCtx->fShutdown))
1890 break;
1891
1892 if (RT_FAILURE(rc)) /* Don't hog the CPU on errors. */
1893 RTThreadSleep(1000 /* ms */);
1894 }
1895
1896 if (pEvent)
1897 {
1898 VbglR3DnDEventFree(pEvent->pVbglR3Event);
1899
1900 RTMemFree(pEvent);
1901 pEvent = NULL;
1902 }
1903
1904 VbglR3DnDDisconnect(&pCtx->cmdCtx);
1905
1906 LogRel(("DnD: Ended\n"));
1907
1908 LogFlowFuncLeaveRC(rc);
1909 return rc;
1910}
1911
1912/**
1913 * The service description.
1914 */
1915VBOXSERVICEDESC g_SvcDescDnD =
1916{
1917 /* pszName. */
1918 "draganddrop",
1919 /* pszDescription. */
1920 "Drag and Drop",
1921 /* methods */
1922 VBoxDnDInit,
1923 VBoxDnDWorker,
1924 VBoxDnDStop,
1925 VBoxDnDDestroy
1926};
1927
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette