VirtualBox

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

Last change on this file since 62865 was 62679, checked in by vboxsync, 8 years ago

Use the iprt/win/windows.h wrapper for Windows.h

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