VirtualBox

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

Last change on this file since 55091 was 55091, checked in by vboxsync, 10 years ago

VBoxTray/VBoxDnD: Fixed a crash + format detection.

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

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