VirtualBox

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

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

DnD/VBoxTray: VBoxDnD.cpp: Fixes for cancellation and error handling.

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