VirtualBox

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

Last change on this file since 63101 was 63101, checked in by vboxsync, 9 years ago

GA/NT/VBoxTray: warnings

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

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