VirtualBox

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

Last change on this file since 74411 was 74411, checked in by vboxsync, 6 years ago

DnD: Renamed action names, added typedefs for DnD actions.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 57.0 KB
Line 
1/* $Id: VBoxDnD.cpp 74411 2018-09-21 15:36:16Z vboxsync $ */
2/** @file
3 * VBoxDnD.cpp - Windows-specific bits of the drag and drop service.
4 */
5
6/*
7 * Copyright (C) 2013-2018 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/err.h>
36#include <iprt/ldr.h>
37#include <iprt/list.h>
38#include <iprt/mem.h>
39
40#include <iprt/cpp/mtlist.h>
41#include <iprt/cpp/ministring.h>
42
43#include <iprt/cpp/mtlist.h>
44
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 uAllActions(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_GENERAL_FAILURE; /** @todo Find a better rc. */
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: uAllActions=0x%x, dwOKEffects=0x%x ...\n",
480 uAllActions, 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.uAllActions);
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.uDefAction);
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.uAction);
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 VbglR3DnDEventFree(pEvent->pVbglR3Event);
653 pEvent->pVbglR3Event = NULL;
654
655 return 0;
656 }
657
658 default:
659 break;
660 }
661
662 return DefWindowProc(hWnd, uMsg, wParam, lParam);
663}
664
665#ifdef VBOX_WITH_DRAG_AND_DROP_GH
666/**
667 * Registers this proxy window as a local drop target.
668 *
669 * @return IPRT status code.
670 */
671int VBoxDnDWnd::RegisterAsDropTarget(void)
672{
673 if (pDropTarget) /* Already registered as drop target? */
674 return VINF_SUCCESS;
675
676 int rc;
677 try
678 {
679 pDropTarget = new VBoxDnDDropTarget(this /* pParent */);
680 HRESULT hr = CoLockObjectExternal(pDropTarget, TRUE /* fLock */,
681 FALSE /* fLastUnlockReleases */);
682 if (SUCCEEDED(hr))
683 hr = RegisterDragDrop(hWnd, pDropTarget);
684
685 if (FAILED(hr))
686 {
687 LogRel(("DnD: Creating drop target failed with hr=%Rhrc\n", hr));
688 rc = VERR_GENERAL_FAILURE; /** @todo Find a better rc. */
689 }
690 else
691 {
692 rc = VINF_SUCCESS;
693 }
694 }
695 catch (std::bad_alloc)
696 {
697 rc = VERR_NO_MEMORY;
698 }
699
700 LogFlowFuncLeaveRC(rc);
701 return rc;
702}
703
704/**
705 * Unregisters this proxy as a drop target.
706 *
707 * @return IPRT status code.
708 */
709int VBoxDnDWnd::UnregisterAsDropTarget(void)
710{
711 LogFlowFuncEnter();
712
713 if (!pDropTarget) /* No drop target? Bail out. */
714 return VINF_SUCCESS;
715
716 HRESULT hr = RevokeDragDrop(hWnd);
717 if (SUCCEEDED(hr))
718 hr = CoLockObjectExternal(pDropTarget, FALSE /* fLock */,
719 TRUE /* fLastUnlockReleases */);
720 if (SUCCEEDED(hr))
721 {
722 ULONG cRefs = pDropTarget->Release();
723 Assert(cRefs == 0); NOREF(cRefs);
724 pDropTarget = NULL;
725 }
726
727 int rc = SUCCEEDED(hr)
728 ? VINF_SUCCESS : VERR_GENERAL_FAILURE; /** @todo Fix this. */
729
730 LogFlowFuncLeaveRC(rc);
731 return rc;
732}
733#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
734
735/**
736 * Handles the creation of a proxy window.
737 *
738 * @return IPRT status code.
739 */
740int VBoxDnDWnd::OnCreate(void)
741{
742 LogFlowFuncEnter();
743 int rc = VbglR3DnDConnect(&mDnDCtx);
744 if (RT_FAILURE(rc))
745 {
746 LogRel(("DnD: Connection to host service failed, rc=%Rrc\n", rc));
747 return rc;
748 }
749
750 LogFlowThisFunc(("Client ID=%RU32, rc=%Rrc\n", mDnDCtx.uClientID, rc));
751 return rc;
752}
753
754/**
755 * Handles the destruction of a proxy window.
756 */
757void VBoxDnDWnd::OnDestroy(void)
758{
759 DestroyWindow(hWnd);
760
761 VbglR3DnDDisconnect(&mDnDCtx);
762 LogFlowThisFuncLeave();
763}
764
765/**
766 * Handles actions required when the host cursor enters
767 * the guest's screen to initiate a host -> guest DnD operation.
768 *
769 * @return IPRT status code.
770 * @param lstFormats Supported formats offered by the host.
771 * @param uAllActions Supported actions offered by the host.
772 */
773int VBoxDnDWnd::OnHgEnter(const RTCList<RTCString> &lstFormats, uint32_t uAllActions)
774{
775 if (mMode == GH) /* Wrong mode? Bail out. */
776 return VERR_WRONG_ORDER;
777
778#ifdef DEBUG
779 LogFlowThisFunc(("uActions=0x%x, lstFormats=%zu: ", uAllActions, lstFormats.size()));
780 for (size_t i = 0; i < lstFormats.size(); i++)
781 LogFlow(("'%s' ", lstFormats.at(i).c_str()));
782 LogFlow(("\n"));
783#endif
784
785 reset();
786 setMode(HG);
787
788 int rc = VINF_SUCCESS;
789
790 try
791 {
792 /* Save all allowed actions. */
793 this->uAllActions = uAllActions;
794
795 /*
796 * Check if reported formats from host are compatible with this client.
797 */
798 size_t cFormatsSup = this->lstFmtSup.size();
799 ULONG cFormatsActive = 0;
800
801 LPFORMATETC pFormatEtc = new FORMATETC[cFormatsSup];
802 RT_BZERO(pFormatEtc, sizeof(FORMATETC) * cFormatsSup);
803
804 LPSTGMEDIUM pStgMeds = new STGMEDIUM[cFormatsSup];
805 RT_BZERO(pStgMeds, sizeof(STGMEDIUM) * cFormatsSup);
806
807 LogRel2(("DnD: Reported formats:\n"));
808 for (size_t i = 0; i < lstFormats.size(); i++)
809 {
810 bool fSupported = false;
811 for (size_t a = 0; a < this->lstFmtSup.size(); a++)
812 {
813 const char *pszFormat = lstFormats.at(i).c_str();
814 LogFlowThisFunc(("\t\"%s\" <=> \"%s\"\n", this->lstFmtSup.at(a).c_str(), pszFormat));
815
816 fSupported = RTStrICmp(this->lstFmtSup.at(a).c_str(), pszFormat) == 0;
817 if (fSupported)
818 {
819 this->lstFmtActive.append(lstFormats.at(i));
820
821 /** @todo Put this into a \#define / struct. */
822 if (!RTStrICmp(pszFormat, "text/uri-list"))
823 {
824 pFormatEtc[cFormatsActive].cfFormat = CF_HDROP;
825 pFormatEtc[cFormatsActive].dwAspect = DVASPECT_CONTENT;
826 pFormatEtc[cFormatsActive].lindex = -1;
827 pFormatEtc[cFormatsActive].tymed = TYMED_HGLOBAL;
828
829 pStgMeds [cFormatsActive].tymed = TYMED_HGLOBAL;
830 cFormatsActive++;
831 }
832 else if ( !RTStrICmp(pszFormat, "text/plain")
833 || !RTStrICmp(pszFormat, "text/html")
834 || !RTStrICmp(pszFormat, "text/plain;charset=utf-8")
835 || !RTStrICmp(pszFormat, "text/plain;charset=utf-16")
836 || !RTStrICmp(pszFormat, "text/plain")
837 || !RTStrICmp(pszFormat, "text/richtext")
838 || !RTStrICmp(pszFormat, "UTF8_STRING")
839 || !RTStrICmp(pszFormat, "TEXT")
840 || !RTStrICmp(pszFormat, "STRING"))
841 {
842 pFormatEtc[cFormatsActive].cfFormat = CF_TEXT;
843 pFormatEtc[cFormatsActive].dwAspect = DVASPECT_CONTENT;
844 pFormatEtc[cFormatsActive].lindex = -1;
845 pFormatEtc[cFormatsActive].tymed = TYMED_HGLOBAL;
846
847 pStgMeds [cFormatsActive].tymed = TYMED_HGLOBAL;
848 cFormatsActive++;
849 }
850 else /* Should never happen. */
851 AssertReleaseMsgFailedBreak(("Format specification for '%s' not implemented\n", pszFormat));
852 break;
853 }
854 }
855
856 LogRel2(("DnD: \t%s: %RTbool\n", lstFormats.at(i).c_str(), fSupported));
857 }
858
859 /*
860 * Warn in the log if this guest does not accept anything.
861 */
862 Assert(cFormatsActive <= cFormatsSup);
863 if (cFormatsActive)
864 {
865 LogRel2(("DnD: %RU32 supported formats found:\n", cFormatsActive));
866 for (size_t i = 0; i < cFormatsActive; i++)
867 LogRel2(("DnD: \t%s\n", this->lstFmtActive.at(i).c_str()));
868 }
869 else
870 LogRel(("DnD: Warning: No supported drag and drop formats on the guest found!\n"));
871
872 /*
873 * Prepare the startup info for DoDragDrop().
874 */
875
876 /* Translate our drop actions into allowed Windows drop effects. */
877 startupInfo.dwOKEffects = DROPEFFECT_NONE;
878 if (uAllActions)
879 {
880 if (uAllActions & VBOX_DND_ACTION_COPY)
881 startupInfo.dwOKEffects |= DROPEFFECT_COPY;
882 if (uAllActions & VBOX_DND_ACTION_MOVE)
883 startupInfo.dwOKEffects |= DROPEFFECT_MOVE;
884 if (uAllActions & VBOX_DND_ACTION_LINK)
885 startupInfo.dwOKEffects |= DROPEFFECT_LINK;
886 }
887
888 LogRel2(("DnD: Supported drop actions: 0x%x\n", startupInfo.dwOKEffects));
889
890 startupInfo.pDropSource = new VBoxDnDDropSource(this);
891 startupInfo.pDataObject = new VBoxDnDDataObject(pFormatEtc, pStgMeds, cFormatsActive);
892
893 if (pFormatEtc)
894 delete pFormatEtc;
895 if (pStgMeds)
896 delete pStgMeds;
897 }
898 catch (std::bad_alloc)
899 {
900 rc = VERR_NO_MEMORY;
901 }
902
903 if (RT_SUCCESS(rc))
904 rc = makeFullscreen();
905
906 LogFlowFuncLeaveRC(rc);
907 return rc;
908}
909
910/**
911 * Handles actions required when the host cursor moves inside
912 * the guest's screen.
913 *
914 * @return IPRT status code.
915 * @param u32xPos Absolute X position (in pixels) of the host cursor
916 * inside the guest.
917 * @param u32yPos Absolute Y position (in pixels) of the host cursor
918 * inside the guest.
919 * @param uAction Action the host wants to perform while moving.
920 * Currently ignored.
921 */
922int VBoxDnDWnd::OnHgMove(uint32_t u32xPos, uint32_t u32yPos, uint32_t uAction)
923{
924 RT_NOREF(uAction);
925 int rc;
926
927 uint32_t uActionNotify = VBOX_DND_ACTION_IGNORE;
928 if (mMode == HG)
929 {
930 LogFlowThisFunc(("u32xPos=%RU32, u32yPos=%RU32, uAction=0x%x\n",
931 u32xPos, u32yPos, uAction));
932
933 rc = mouseMove(u32xPos, u32yPos, MOUSEEVENTF_LEFTDOWN);
934
935 if (RT_SUCCESS(rc))
936 rc = RTCritSectEnter(&mCritSect);
937 if (RT_SUCCESS(rc))
938 {
939 if ( (Dragging == mState)
940 && startupInfo.pDropSource)
941 uActionNotify = startupInfo.pDropSource->GetCurrentAction();
942
943 RTCritSectLeave(&mCritSect);
944 }
945 }
946 else /* Just acknowledge the operation with an ignore action. */
947 rc = VINF_SUCCESS;
948
949 if (RT_SUCCESS(rc))
950 {
951 rc = VbglR3DnDHGSendAckOp(&mDnDCtx, uActionNotify);
952 if (RT_FAILURE(rc))
953 LogFlowThisFunc(("Acknowledging operation failed with rc=%Rrc\n", rc));
954 }
955
956 LogFlowThisFunc(("Returning uActionNotify=0x%x, rc=%Rrc\n", uActionNotify, rc));
957 return rc;
958}
959
960/**
961 * Handles actions required when the host cursor leaves
962 * the guest's screen again.
963 *
964 * @return IPRT status code.
965 */
966int VBoxDnDWnd::OnHgLeave(void)
967{
968 if (mMode == GH) /* Wrong mode? Bail out. */
969 return VERR_WRONG_ORDER;
970
971 LogFlowThisFunc(("mMode=%ld, mState=%RU32\n", mMode, mState));
972 LogRel(("DnD: Drag and drop operation aborted\n"));
973
974 reset();
975
976 int rc = VINF_SUCCESS;
977
978 /* Post ESC to our window to officially abort the
979 * drag and drop operation. */
980 this->PostMessage(WM_KEYDOWN, VK_ESCAPE /* wParam */, 0 /* lParam */);
981
982 LogFlowFuncLeaveRC(rc);
983 return rc;
984}
985
986/**
987 * Handles actions required when the host cursor wants to drop
988 * and therefore start a "drop" action in the guest.
989 *
990 * @return IPRT status code.
991 */
992int VBoxDnDWnd::OnHgDrop(void)
993{
994 if (mMode == GH)
995 return VERR_WRONG_ORDER;
996
997 LogFlowThisFunc(("mMode=%ld, mState=%RU32\n", mMode, mState));
998
999 int rc = VINF_SUCCESS;
1000 if (mState == Dragging)
1001 {
1002 if (lstFmtActive.size() >= 1)
1003 {
1004 /** @todo What to do when multiple formats are available? */
1005 mFormatRequested = lstFmtActive.at(0);
1006
1007 rc = RTCritSectEnter(&mCritSect);
1008 if (RT_SUCCESS(rc))
1009 {
1010 if (startupInfo.pDataObject)
1011 startupInfo.pDataObject->SetStatus(VBoxDnDDataObject::Dropping);
1012 else
1013 rc = VERR_NOT_FOUND;
1014
1015 RTCritSectLeave(&mCritSect);
1016 }
1017
1018 if (RT_SUCCESS(rc))
1019 {
1020 LogRel(("DnD: Requesting data as '%s' ...\n", mFormatRequested.c_str()));
1021 rc = VbglR3DnDHGSendReqData(&mDnDCtx, mFormatRequested.c_str());
1022 if (RT_FAILURE(rc))
1023 LogFlowThisFunc(("Requesting data failed with rc=%Rrc\n", rc));
1024 }
1025
1026 }
1027 else /* Should never happen. */
1028 LogRel(("DnD: Error: Host did not specify a data format for drop data\n"));
1029 }
1030
1031 LogFlowFuncLeaveRC(rc);
1032 return rc;
1033}
1034
1035/**
1036 * Handles actions required when the host has sent over DnD data
1037 * to the guest after a "drop" event.
1038 *
1039 * @return IPRT status code.
1040 * @param pMeta Pointer to meta data received.
1041 */
1042int VBoxDnDWnd::OnHgDataReceive(PVBGLR3GUESTDNDMETADATA pMeta)
1043{
1044 LogFlowThisFunc(("mState=%ld, enmMetaType=%RU32, cbMeta=%RU32\n", mState, pMeta->enmType, pMeta->cbMeta));
1045
1046 mState = Dropped;
1047
1048 int rc = VINF_SUCCESS;
1049 if (pMeta->pvMeta)
1050 {
1051 Assert(pMeta->cbMeta);
1052 rc = RTCritSectEnter(&mCritSect);
1053 if (RT_SUCCESS(rc))
1054 {
1055 if (startupInfo.pDataObject)
1056 rc = startupInfo.pDataObject->Signal(mFormatRequested, pMeta->pvMeta, pMeta->cbMeta);
1057 else
1058 rc = VERR_NOT_FOUND;
1059
1060 RTCritSectLeave(&mCritSect);
1061 }
1062 }
1063
1064 int rc2 = mouseRelease();
1065 if (RT_SUCCESS(rc))
1066 rc = rc2;
1067
1068 LogFlowFuncLeaveRC(rc);
1069 return rc;
1070}
1071
1072/**
1073 * Handles actions required when the host wants to cancel the current
1074 * host -> guest operation.
1075 *
1076 * @return IPRT status code.
1077 */
1078int VBoxDnDWnd::OnHgCancel(void)
1079{
1080 int rc = RTCritSectEnter(&mCritSect);
1081 if (RT_SUCCESS(rc))
1082 {
1083 if (startupInfo.pDataObject)
1084 startupInfo.pDataObject->Abort();
1085
1086 RTCritSectLeave(&mCritSect);
1087 }
1088
1089 int rc2 = mouseRelease();
1090 if (RT_SUCCESS(rc))
1091 rc = rc2;
1092
1093 reset();
1094
1095 return rc;
1096}
1097
1098#ifdef VBOX_WITH_DRAG_AND_DROP_GH
1099/**
1100 * Handles actions required to start a guest -> host DnD operation.
1101 * This works by letting the host ask whether a DnD operation is pending
1102 * on the guest. The guest must not know anything about the host's DnD state
1103 * and/or operations due to security reasons.
1104 *
1105 * To capture a pending DnD operation on the guest which then can be communicated
1106 * to the host the proxy window needs to be registered as a drop target. This drop
1107 * target then will act as a proxy target between the guest OS and the host. In other
1108 * words, the guest OS will use this proxy target as a regular (invisible) window
1109 * which can be used by the regular guest OS' DnD mechanisms, independently of the
1110 * host OS. To make sure this proxy target is able receive an in-progress DnD operation
1111 * on the guest, it will be shown invisibly across all active guest OS screens. Just
1112 * think of an opened umbrella across all screens here.
1113 *
1114 * As soon as the proxy target and its underlying data object receive appropriate
1115 * DnD messages they'll be hidden again, and the control will be transferred back
1116 * this class again.
1117 *
1118 * @return IPRT status code.
1119 */
1120int VBoxDnDWnd::OnGhIsDnDPending(void)
1121{
1122 LogFlowThisFunc(("mMode=%ld, mState=%ld, uScreenID=%RU32\n", mMode, mState));
1123
1124 if (mMode == Unknown)
1125 setMode(GH);
1126
1127 if (mMode != GH)
1128 return VERR_WRONG_ORDER;
1129
1130 if (mState == Uninitialized)
1131 {
1132 /* Nothing to do here yet. */
1133 mState = Initialized;
1134 }
1135
1136 int rc;
1137 if (mState == Initialized)
1138 {
1139 rc = makeFullscreen();
1140 if (RT_SUCCESS(rc))
1141 {
1142 /*
1143 * We have to release the left mouse button to
1144 * get into our (invisible) proxy window.
1145 */
1146 mouseRelease();
1147
1148 /*
1149 * Even if we just released the left mouse button
1150 * we're still in the dragging state to handle our
1151 * own drop target (for the host).
1152 */
1153 mState = Dragging;
1154 }
1155 }
1156 else
1157 rc = VINF_SUCCESS;
1158
1159 /**
1160 * Some notes regarding guest cursor movement:
1161 * - The host only sends an HOST_DND_GH_REQ_PENDING message to the guest
1162 * if the mouse cursor is outside the VM's window.
1163 * - The guest does not know anything about the host's cursor
1164 * position / state due to security reasons.
1165 * - The guest *only* knows that the host currently is asking whether a
1166 * guest DnD operation is in progress.
1167 */
1168
1169 if ( RT_SUCCESS(rc)
1170 && mState == Dragging)
1171 {
1172 /** @todo Put this block into a function! */
1173 POINT p;
1174 GetCursorPos(&p);
1175 ClientToScreen(hWnd, &p);
1176#ifdef DEBUG_andy
1177 LogFlowThisFunc(("Client to screen curX=%ld, curY=%ld\n", p.x, p.y));
1178#endif
1179
1180 /** @todo Multi-monitor setups? */
1181#if 0 /* unused */
1182 int iScreenX = GetSystemMetrics(SM_CXSCREEN) - 1;
1183 int iScreenY = GetSystemMetrics(SM_CYSCREEN) - 1;
1184#endif
1185
1186 LONG px = p.x;
1187 if (px <= 0)
1188 px = 1;
1189 LONG py = p.y;
1190 if (py <= 0)
1191 py = 1;
1192
1193 rc = mouseMove(px, py, 0 /* dwMouseInputFlags */);
1194 }
1195
1196 if (RT_SUCCESS(rc))
1197 {
1198 uint32_t uDefAction = VBOX_DND_ACTION_IGNORE;
1199
1200 AssertPtr(pDropTarget);
1201 RTCString strFormats = pDropTarget->Formats();
1202 if (!strFormats.isEmpty())
1203 {
1204 uDefAction = VBOX_DND_ACTION_COPY;
1205
1206 LogFlowFunc(("Acknowledging pDropTarget=0x%p, uDefAction=0x%x, uAllActions=0x%x, strFormats=%s\n",
1207 pDropTarget, uDefAction, uAllActions, strFormats.c_str()));
1208 }
1209 else
1210 {
1211 strFormats = "unknown"; /* Prevent VERR_IO_GEN_FAILURE for IOCTL. */
1212 LogFlowFunc(("No format data available yet\n"));
1213 }
1214
1215 /** @todo Support more than one action at a time. */
1216 uAllActions = uDefAction;
1217
1218 int rc2 = VbglR3DnDGHSendAckPending(&mDnDCtx,
1219 uDefAction, uAllActions,
1220 strFormats.c_str(), (uint32_t)strFormats.length() + 1 /* Include termination */);
1221 if (RT_FAILURE(rc2))
1222 {
1223 char szMsg[256]; /* Sizes according to MSDN. */
1224 char szTitle[64];
1225
1226 /** @todo Add some i18l tr() macros here. */
1227 RTStrPrintf(szTitle, sizeof(szTitle), "VirtualBox Guest Additions Drag and Drop");
1228 RTStrPrintf(szMsg, sizeof(szMsg), "Drag and drop to the host either is not supported or disabled. "
1229 "Please enable Guest to Host or Bidirectional drag and drop mode "
1230 "or re-install the VirtualBox Guest Additions.");
1231 switch (rc2)
1232 {
1233 case VERR_ACCESS_DENIED:
1234 {
1235 rc = hlpShowBalloonTip(g_hInstance, g_hwndToolWindow, ID_TRAYICON,
1236 szMsg, szTitle,
1237 15 * 1000 /* Time to display in msec */, NIIF_INFO);
1238 AssertRC(rc);
1239 break;
1240 }
1241
1242 default:
1243 break;
1244 }
1245
1246 LogRel2(("DnD: Host refuses drag and drop operation from guest: %Rrc\n", rc2));
1247 reset();
1248 }
1249 }
1250
1251 if (RT_FAILURE(rc))
1252 reset(); /* Reset state on failure. */
1253
1254 LogFlowFuncLeaveRC(rc);
1255 return rc;
1256}
1257
1258/**
1259 * Handles actions required to let the guest know that the host
1260 * started a "drop" action on the host. This will tell the guest
1261 * to send data in a specific format the host requested.
1262 *
1263 * @return IPRT status code.
1264 * @param pszFormat Format the host requests the data in.
1265 * @param cbFormat Size (in bytes) of format string.
1266 * @param uDefAction Default action on the host.
1267 */
1268int VBoxDnDWnd::OnGhDrop(const RTCString &strFormat, uint32_t uDefAction)
1269{
1270 RT_NOREF(uDefAction);
1271
1272 LogFlowThisFunc(("mMode=%ld, mState=%ld, pDropTarget=0x%p, strFormat=%s, uDefAction=0x%x\n",
1273 mMode, mState, pDropTarget, strFormat.c_str(), uDefAction));
1274 int rc;
1275 if (mMode == GH)
1276 {
1277 if (mState == Dragging)
1278 {
1279 AssertPtr(pDropTarget);
1280 rc = pDropTarget->WaitForDrop(5 * 1000 /* 5s timeout */);
1281
1282 reset();
1283 }
1284 else if (mState == Dropped)
1285 {
1286 rc = VINF_SUCCESS;
1287 }
1288 else
1289 rc = VERR_WRONG_ORDER;
1290
1291 if (RT_SUCCESS(rc))
1292 {
1293 /** @todo Respect uDefAction. */
1294 void *pvData = pDropTarget->DataMutableRaw();
1295 uint32_t cbData = (uint32_t)pDropTarget->DataSize();
1296 Assert(cbData == pDropTarget->DataSize());
1297
1298 if ( pvData
1299 && cbData)
1300 {
1301 rc = VbglR3DnDGHSendData(&mDnDCtx, strFormat.c_str(), pvData, cbData);
1302 LogFlowFunc(("Sent pvData=0x%p, cbData=%RU32, rc=%Rrc\n", pvData, cbData, rc));
1303 }
1304 else
1305 rc = VERR_NO_DATA;
1306 }
1307 }
1308 else
1309 rc = VERR_WRONG_ORDER;
1310
1311 if (RT_FAILURE(rc))
1312 {
1313 /*
1314 * If an error occurred or the guest is in a wrong DnD mode,
1315 * send an error to the host in any case so that the host does
1316 * not wait for the data it expects from the guest.
1317 */
1318 int rc2 = VbglR3DnDGHSendError(&mDnDCtx, rc);
1319 AssertRC(rc2);
1320 }
1321
1322 LogFlowFuncLeaveRC(rc);
1323 return rc;
1324}
1325#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
1326
1327void VBoxDnDWnd::PostMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
1328{
1329 LogFlowFunc(("Posting message %u\n"));
1330 BOOL fRc = ::PostMessage(hWnd, uMsg, wParam, lParam);
1331 Assert(fRc); NOREF(fRc);
1332}
1333
1334/**
1335 * Injects a DnD event in this proxy window's Windows
1336 * event queue. The (allocated) event will be deleted by
1337 * this class after processing.
1338 *
1339 * @return IPRT status code.
1340 * @param pEvent Event to inject.
1341 */
1342int VBoxDnDWnd::ProcessEvent(PVBOXDNDEVENT pEvent)
1343{
1344 AssertPtrReturn(pEvent, VERR_INVALID_POINTER);
1345
1346 BOOL fRc = ::PostMessage(hWnd, WM_VBOXTRAY_DND_MESSAGE,
1347 0 /* wParm */, (LPARAM)pEvent /* lParm */);
1348 if (!fRc)
1349 {
1350 DWORD dwErr = GetLastError();
1351
1352 static int s_iBitchedAboutFailedDnDMessages = 0;
1353 if (s_iBitchedAboutFailedDnDMessages++ < 32)
1354 {
1355 LogRel(("DnD: Processing event %p failed with %ld (%Rrc), skipping\n",
1356 pEvent, dwErr, RTErrConvertFromWin32(dwErr)));
1357 }
1358
1359 RTMemFree(pEvent);
1360 pEvent = NULL;
1361
1362 return RTErrConvertFromWin32(dwErr);
1363 }
1364
1365 return VINF_SUCCESS;
1366}
1367
1368/**
1369 * Hides the proxy window again.
1370 *
1371 * @return IPRT status code.
1372 */
1373int VBoxDnDWnd::hide(void)
1374{
1375#ifdef DEBUG_andy
1376 LogFlowFunc(("\n"));
1377#endif
1378 ShowWindow(hWnd, SW_HIDE);
1379
1380 return VINF_SUCCESS;
1381}
1382
1383/**
1384 * Shows the (invisible) proxy window in fullscreen,
1385 * spawned across all active guest monitors.
1386 *
1387 * @return IPRT status code.
1388 */
1389int VBoxDnDWnd::makeFullscreen(void)
1390{
1391 int rc = VINF_SUCCESS;
1392
1393 RECT r;
1394 RT_ZERO(r);
1395
1396 BOOL fRc;
1397 HDC hDC = GetDC(NULL /* Entire screen */);
1398 if (hDC)
1399 {
1400 fRc = g_pfnEnumDisplayMonitors
1401 /* EnumDisplayMonitors is not available on NT4. */
1402 ? g_pfnEnumDisplayMonitors(hDC, NULL, VBoxDnDWnd::MonitorEnumProc, (LPARAM)&r):
1403 FALSE;
1404
1405 if (!fRc)
1406 rc = VERR_NOT_FOUND;
1407 ReleaseDC(NULL, hDC);
1408 }
1409 else
1410 rc = VERR_ACCESS_DENIED;
1411
1412 if (RT_FAILURE(rc))
1413 {
1414 /* If multi-monitor enumeration failed above, try getting at least the
1415 * primary monitor as a fallback. */
1416 r.left = 0;
1417 r.top = 0;
1418 r.right = GetSystemMetrics(SM_CXSCREEN);
1419 r.bottom = GetSystemMetrics(SM_CYSCREEN);
1420 rc = VINF_SUCCESS;
1421 }
1422
1423 if (RT_SUCCESS(rc))
1424 {
1425 LONG lStyle = GetWindowLong(hWnd, GWL_STYLE);
1426 SetWindowLong(hWnd, GWL_STYLE,
1427 lStyle & ~(WS_CAPTION | WS_THICKFRAME));
1428 LONG lExStyle = GetWindowLong(hWnd, GWL_EXSTYLE);
1429 SetWindowLong(hWnd, GWL_EXSTYLE,
1430 lExStyle & ~( WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE
1431 | WS_EX_CLIENTEDGE | WS_EX_STATICEDGE));
1432
1433 fRc = SetWindowPos(hWnd, HWND_TOPMOST,
1434 r.left,
1435 r.top,
1436 r.right - r.left,
1437 r.bottom - r.top,
1438#ifdef VBOX_DND_DEBUG_WND
1439 SWP_SHOWWINDOW | SWP_FRAMECHANGED);
1440#else
1441 SWP_SHOWWINDOW | SWP_NOOWNERZORDER | SWP_NOREDRAW | SWP_NOACTIVATE);
1442#endif
1443 if (fRc)
1444 {
1445 LogFlowFunc(("Virtual screen is %ld,%ld,%ld,%ld (%ld x %ld)\n",
1446 r.left, r.top, r.right, r.bottom,
1447 r.right - r.left, r.bottom - r.top));
1448 }
1449 else
1450 {
1451 DWORD dwErr = GetLastError();
1452 LogRel(("DnD: Failed to set proxy window position, rc=%Rrc\n",
1453 RTErrConvertFromWin32(dwErr)));
1454 }
1455 }
1456 else
1457 LogRel(("DnD: Failed to determine virtual screen size, rc=%Rrc\n", rc));
1458
1459 LogFlowFuncLeaveRC(rc);
1460 return rc;
1461}
1462
1463/**
1464 * Moves the guest mouse cursor to a specific position.
1465 *
1466 * @return IPRT status code.
1467 * @param x X position (in pixels) to move cursor to.
1468 * @param y Y position (in pixels) to move cursor to.
1469 * @param dwMouseInputFlags Additional movement flags. @sa MOUSEEVENTF_ flags.
1470 */
1471int VBoxDnDWnd::mouseMove(int x, int y, DWORD dwMouseInputFlags)
1472{
1473 int iScreenX = GetSystemMetrics(SM_CXSCREEN) - 1;
1474 int iScreenY = GetSystemMetrics(SM_CYSCREEN) - 1;
1475
1476 INPUT Input[1] = { 0 };
1477 Input[0].type = INPUT_MOUSE;
1478 Input[0].mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE
1479 | dwMouseInputFlags;
1480 Input[0].mi.dx = x * (65535 / iScreenX);
1481 Input[0].mi.dy = y * (65535 / iScreenY);
1482
1483 int rc;
1484 if (g_pfnSendInput(1 /* Number of inputs */,
1485 Input, sizeof(INPUT)))
1486 {
1487#ifdef DEBUG_andy
1488 CURSORINFO ci;
1489 RT_ZERO(ci);
1490 ci.cbSize = sizeof(ci);
1491 BOOL fRc = GetCursorInfo(&ci);
1492 if (fRc)
1493 LogFlowThisFunc(("Cursor shown=%RTbool, cursor=0x%p, x=%d, y=%d\n",
1494 (ci.flags & CURSOR_SHOWING) ? true : false,
1495 ci.hCursor, ci.ptScreenPos.x, ci.ptScreenPos.y));
1496#endif
1497 rc = VINF_SUCCESS;
1498 }
1499 else
1500 {
1501 DWORD dwErr = GetLastError();
1502 rc = RTErrConvertFromWin32(dwErr);
1503 LogFlowFunc(("SendInput failed with rc=%Rrc\n", rc));
1504 }
1505
1506 return rc;
1507}
1508
1509/**
1510 * Releases a previously pressed left guest mouse button.
1511 *
1512 * @return IPRT status code.
1513 */
1514int VBoxDnDWnd::mouseRelease(void)
1515{
1516 LogFlowFuncEnter();
1517
1518 int rc;
1519
1520 /* Release mouse button in the guest to start the "drop"
1521 * action at the current mouse cursor position. */
1522 INPUT Input[1] = { 0 };
1523 Input[0].type = INPUT_MOUSE;
1524 Input[0].mi.dwFlags = MOUSEEVENTF_LEFTUP;
1525 if (!g_pfnSendInput(1, Input, sizeof(INPUT)))
1526 {
1527 DWORD dwErr = GetLastError();
1528 rc = RTErrConvertFromWin32(dwErr);
1529 LogFlowFunc(("SendInput failed with rc=%Rrc\n", rc));
1530 }
1531 else
1532 rc = VINF_SUCCESS;
1533
1534 return rc;
1535}
1536
1537/**
1538 * Resets the proxy window.
1539 */
1540void VBoxDnDWnd::reset(void)
1541{
1542 LogFlowThisFunc(("Resetting, old mMode=%ld, mState=%ld\n",
1543 mMode, mState));
1544
1545 /*
1546 * Note: Don't clear this->lstAllowedFormats at the moment, as this value is initialized
1547 * on class creation. We might later want to modify the allowed formats at runtime,
1548 * so keep this in mind when implementing this.
1549 */
1550
1551 this->lstFmtActive.clear();
1552 this->uAllActions = VBOX_DND_ACTION_IGNORE;
1553
1554 int rc2 = setMode(Unknown);
1555 AssertRC(rc2);
1556
1557 hide();
1558}
1559
1560/**
1561 * Sets the current operation mode of this proxy window.
1562 *
1563 * @return IPRT status code.
1564 * @param enmMode New mode to set.
1565 */
1566int VBoxDnDWnd::setMode(Mode enmMode)
1567{
1568 LogFlowThisFunc(("Old mode=%ld, new mode=%ld\n",
1569 mMode, enmMode));
1570
1571 mMode = enmMode;
1572 mState = Initialized;
1573
1574 return VINF_SUCCESS;
1575}
1576
1577/**
1578 * Static helper function for having an own WndProc for proxy
1579 * window instances.
1580 */
1581static LRESULT CALLBACK vboxDnDWndProcInstance(HWND hWnd, UINT uMsg,
1582 WPARAM wParam, LPARAM lParam)
1583{
1584 LONG_PTR pUserData = GetWindowLongPtr(hWnd, GWLP_USERDATA);
1585 AssertPtrReturn(pUserData, 0);
1586
1587 VBoxDnDWnd *pWnd = reinterpret_cast<VBoxDnDWnd *>(pUserData);
1588 if (pWnd)
1589 return pWnd->WndProc(hWnd, uMsg, wParam, lParam);
1590
1591 return 0;
1592}
1593
1594/**
1595 * Static helper function for routing Windows messages to a specific
1596 * proxy window instance.
1597 */
1598static LRESULT CALLBACK vboxDnDWndProc(HWND hWnd, UINT uMsg,
1599 WPARAM wParam, LPARAM lParam)
1600{
1601 /* Note: WM_NCCREATE is not the first ever message which arrives, but
1602 * early enough for us. */
1603 if (uMsg == WM_NCCREATE)
1604 {
1605 LPCREATESTRUCT pCS = (LPCREATESTRUCT)lParam;
1606 AssertPtr(pCS);
1607 SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)pCS->lpCreateParams);
1608 SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)vboxDnDWndProcInstance);
1609
1610 return vboxDnDWndProcInstance(hWnd, uMsg, wParam, lParam);
1611 }
1612
1613 /* No window associated yet. */
1614 return DefWindowProc(hWnd, uMsg, wParam, lParam);
1615}
1616
1617/**
1618 * Initializes drag and drop.
1619 *
1620 * @return IPRT status code.
1621 * @param pEnv The DnD service's environment.
1622 * @param ppInstance The instance pointer which refer to this object.
1623 */
1624DECLCALLBACK(int) VBoxDnDInit(const PVBOXSERVICEENV pEnv, void **ppInstance)
1625{
1626 AssertPtrReturn(pEnv, VERR_INVALID_POINTER);
1627 AssertPtrReturn(ppInstance, VERR_INVALID_POINTER);
1628
1629 LogFlowFuncEnter();
1630
1631 PVBOXDNDCONTEXT pCtx = &g_Ctx; /* Only one instance at the moment. */
1632 AssertPtr(pCtx);
1633
1634 int rc;
1635 bool fSupportedOS = true;
1636
1637 if (VbglR3AutoLogonIsRemoteSession())
1638 {
1639 /* Do not do drag and drop for remote sessions. */
1640 LogRel(("DnD: Drag and drop has been disabled for a remote session\n"));
1641 rc = VERR_NOT_SUPPORTED;
1642 }
1643 else
1644 rc = VINF_SUCCESS;
1645
1646 if (RT_SUCCESS(rc))
1647 {
1648 g_pfnSendInput = (PFNSENDINPUT)
1649 RTLdrGetSystemSymbol("User32.dll", "SendInput");
1650 fSupportedOS = !RT_BOOL(g_pfnSendInput == NULL);
1651 g_pfnEnumDisplayMonitors = (PFNENUMDISPLAYMONITORS)
1652 RTLdrGetSystemSymbol("User32.dll", "EnumDisplayMonitors");
1653 /* g_pfnEnumDisplayMonitors is optional. */
1654
1655 if (!fSupportedOS)
1656 {
1657 LogRel(("DnD: Not supported Windows version, disabling drag and drop support\n"));
1658 rc = VERR_NOT_SUPPORTED;
1659 }
1660 }
1661
1662 if (RT_SUCCESS(rc))
1663 {
1664 /* Assign service environment to our context. */
1665 pCtx->pEnv = pEnv;
1666
1667 /* Create the proxy window. At the moment we
1668 * only support one window at a time. */
1669 VBoxDnDWnd *pWnd = NULL;
1670 try
1671 {
1672 pWnd = new VBoxDnDWnd();
1673 rc = pWnd->Initialize(pCtx);
1674
1675 /* Add proxy window to our proxy windows list. */
1676 if (RT_SUCCESS(rc))
1677 pCtx->lstWnd.append(pWnd);
1678 }
1679 catch (std::bad_alloc)
1680 {
1681 rc = VERR_NO_MEMORY;
1682 }
1683 }
1684
1685 if (RT_SUCCESS(rc))
1686 rc = RTSemEventCreate(&pCtx->hEvtQueueSem);
1687 if (RT_SUCCESS(rc))
1688 {
1689 *ppInstance = pCtx;
1690
1691 LogRel(("DnD: Drag and drop service successfully started\n"));
1692 }
1693 else
1694 LogRel(("DnD: Initializing drag and drop service failed with rc=%Rrc\n", rc));
1695
1696 LogFlowFuncLeaveRC(rc);
1697 return rc;
1698}
1699
1700DECLCALLBACK(int) VBoxDnDStop(void *pInstance)
1701{
1702 AssertPtrReturn(pInstance, VERR_INVALID_POINTER);
1703
1704 LogFunc(("Stopping pInstance=%p\n", pInstance));
1705
1706 PVBOXDNDCONTEXT pCtx = (PVBOXDNDCONTEXT)pInstance;
1707 AssertPtr(pCtx);
1708
1709 /* Set shutdown indicator. */
1710 ASMAtomicWriteBool(&pCtx->fShutdown, true);
1711
1712 /* Disconnect. */
1713 VbglR3DnDDisconnect(&pCtx->cmdCtx);
1714
1715 LogFlowFuncLeaveRC(VINF_SUCCESS);
1716 return VINF_SUCCESS;
1717}
1718
1719DECLCALLBACK(void) VBoxDnDDestroy(void *pInstance)
1720{
1721 AssertPtrReturnVoid(pInstance);
1722
1723 LogFunc(("Destroying pInstance=%p\n", pInstance));
1724
1725 PVBOXDNDCONTEXT pCtx = (PVBOXDNDCONTEXT)pInstance;
1726 AssertPtr(pCtx);
1727
1728 /** @todo At the moment we only have one DnD proxy window. */
1729 Assert(pCtx->lstWnd.size() == 1);
1730 VBoxDnDWnd *pWnd = pCtx->lstWnd.first();
1731 if (pWnd)
1732 {
1733 delete pWnd;
1734 pWnd = NULL;
1735 }
1736
1737 if (pCtx->hEvtQueueSem != NIL_RTSEMEVENT)
1738 {
1739 RTSemEventDestroy(pCtx->hEvtQueueSem);
1740 pCtx->hEvtQueueSem = NIL_RTSEMEVENT;
1741 }
1742
1743 LogFunc(("Destroyed pInstance=%p\n", pInstance));
1744}
1745
1746DECLCALLBACK(int) VBoxDnDWorker(void *pInstance, bool volatile *pfShutdown)
1747{
1748 AssertPtr(pInstance);
1749 AssertPtr(pfShutdown);
1750
1751 LogFlowFunc(("pInstance=%p\n", pInstance));
1752
1753 /*
1754 * Tell the control thread that it can continue
1755 * spawning services.
1756 */
1757 RTThreadUserSignal(RTThreadSelf());
1758
1759 PVBOXDNDCONTEXT pCtx = (PVBOXDNDCONTEXT)pInstance;
1760 AssertPtr(pCtx);
1761
1762 int rc = VbglR3DnDConnect(&pCtx->cmdCtx);
1763 if (RT_FAILURE(rc))
1764 return rc;
1765
1766 /** @todo At the moment we only have one DnD proxy window. */
1767 Assert(pCtx->lstWnd.size() == 1);
1768 VBoxDnDWnd *pWnd = pCtx->lstWnd.first();
1769 AssertPtr(pWnd);
1770
1771 /* Number of invalid messages skipped in a row. */
1772 int cMsgSkippedInvalid = 0;
1773 PVBOXDNDEVENT pEvent = NULL;
1774
1775 for (;;)
1776 {
1777 pEvent = (PVBOXDNDEVENT)RTMemAllocZ(sizeof(VBOXDNDEVENT));
1778 if (!pEvent)
1779 {
1780 rc = VERR_NO_MEMORY;
1781 break;
1782 }
1783 /* Note: pEvent will be free'd by the consumer later. */
1784
1785 PVBGLR3DNDEVENT pVbglR3Event = NULL;
1786 rc = VbglR3DnDEventGetNext(&pCtx->cmdCtx, &pVbglR3Event);
1787 LogFlowFunc(("enmType=%RU32, rc=%Rrc\n", pVbglR3Event->enmType, rc));
1788
1789 if (RT_SUCCESS(rc))
1790 {
1791 cMsgSkippedInvalid = 0; /* Reset skipped messages count. */
1792
1793 LogRel2(("DnD: Received new event, type=%RU32, rc=%Rrc\n", pVbglR3Event->enmType, rc));
1794
1795 /* pEvent now owns pVbglR3Event. */
1796 pEvent->pVbglR3Event = pVbglR3Event;
1797 pVbglR3Event = NULL;
1798
1799 rc = pWnd->ProcessEvent(pEvent);
1800 if (RT_SUCCESS(rc))
1801 {
1802 /* Event was consumed and the proxy window till take care of the memory -- NULL it. */
1803 pEvent = NULL;
1804 }
1805 else
1806 LogRel(("DnD: Processing proxy window event %RU32 failed with %Rrc\n", pVbglR3Event->enmType, rc));
1807 }
1808 else if (rc == VERR_INTERRUPTED) /* Disconnected from service. */
1809 {
1810 LogRel(("DnD: Received quit message, shutting down ...\n"));
1811 pWnd->PostMessage(WM_QUIT, 0 /* wParm */, 0 /* lParm */);
1812 rc = VINF_SUCCESS;
1813 }
1814
1815 if (RT_FAILURE(rc))
1816 {
1817 VbglR3DnDEventFree(pVbglR3Event);
1818 pVbglR3Event = NULL;
1819
1820 LogFlowFunc(("Processing next message failed with rc=%Rrc\n", rc));
1821
1822 /* Old(er) hosts either are broken regarding DnD support or otherwise
1823 * don't support the stuff we do on the guest side, so make sure we
1824 * don't process invalid messages forever. */
1825 if (rc == VERR_INVALID_PARAMETER)
1826 cMsgSkippedInvalid++;
1827 if (cMsgSkippedInvalid > 32)
1828 {
1829 LogRel(("DnD: Too many invalid/skipped messages from host, exiting ...\n"));
1830 break;
1831 }
1832
1833 /* Make sure our proxy window is hidden when an error occured to
1834 * not block the guest's UI. */
1835 pWnd->hide();
1836
1837 int rc2 = VbglR3DnDGHSendError(&pCtx->cmdCtx, rc);
1838 if (RT_FAILURE(rc2))
1839 {
1840 /* Ignore the following errors reported back from the host. */
1841 if ( rc2 != VERR_NOT_SUPPORTED
1842 && rc2 != VERR_NOT_IMPLEMENTED)
1843 {
1844 LogRel(("DnD: Could not report error %Rrc back to host: %Rrc\n", rc, rc2));
1845 }
1846 }
1847 }
1848
1849 if (*pfShutdown)
1850 break;
1851
1852 if (ASMAtomicReadBool(&pCtx->fShutdown))
1853 break;
1854
1855 if (RT_FAILURE(rc)) /* Don't hog the CPU on errors. */
1856 RTThreadSleep(1000 /* ms */);
1857 }
1858
1859 if (pEvent)
1860 {
1861 RTMemFree(pEvent);
1862 pEvent = NULL;
1863 }
1864
1865 VbglR3DnDDisconnect(&pCtx->cmdCtx);
1866
1867 LogRel(("DnD: Ended\n"));
1868
1869 LogFlowFuncLeaveRC(rc);
1870 return rc;
1871}
1872
1873/**
1874 * The service description.
1875 */
1876VBOXSERVICEDESC g_SvcDescDnD =
1877{
1878 /* pszName. */
1879 "draganddrop",
1880 /* pszDescription. */
1881 "Drag and Drop",
1882 /* methods */
1883 VBoxDnDInit,
1884 VBoxDnDWorker,
1885 VBoxDnDStop,
1886 VBoxDnDDestroy
1887};
1888
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