VirtualBox

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

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

DnD/VBoxTray: Added a "started" indicator for the proxy window creation and check for it on startup.

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