VirtualBox

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

Last change on this file since 63107 was 63105, checked in by vboxsync, 8 years ago

GA/NT: warnings

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