VirtualBox

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

Last change on this file since 85800 was 85746, checked in by vboxsync, 4 years ago

DnD: Renaming -- DND_FORMATS_SEPARATOR -> DND_FORMATS_SEPARATOR_STR and DND_PATH_SEPARATOR -> DND_PATH_SEPARATOR_STR.

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