VirtualBox

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

Last change on this file since 50177 was 50177, checked in by vboxsync, 11 years ago

DnD/VBoxTray: Update.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 38.0 KB
Line 
1/* $Id: VBoxDnD.cpp 50177 2014-01-23 11:51:09Z vboxsync $ */
2/** @file
3 * VBoxDnD.cpp - Windows-specific bits of the drag'n drop service.
4 */
5
6/*
7 * Copyright (C) 2013-2014 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17#include <windows.h>
18#include "VBoxTray.h"
19#include "VBoxHelpers.h"
20#include "VBoxDnD.h"
21
22#include <VBox/VBoxGuestLib.h>
23#include "VBox/HostServices/DragAndDropSvc.h"
24
25#include <iprt/asm.h>
26#include <iprt/assert.h>
27#include <iprt/err.h>
28#include <iprt/ldr.h>
29#include <iprt/list.h>
30#include <iprt/mem.h>
31
32#include <iprt/cpp/mtlist.h>
33#include <iprt/cpp/ministring.h>
34
35#include <iprt/cpp/mtlist.h>
36
37#include <VBoxGuestInternal.h>
38
39/* Enable this define to see the proxy window(s) when debugging
40 * their behavior. Don't have this enabled in release builds! */
41#ifdef DEBUG
42//# define VBOX_DND_DEBUG_WND
43#endif
44
45/** @todo Merge this with messages from VBoxTray.h. */
46#define WM_VBOXTRAY_DND_MESSAGE WM_APP + 401
47
48static LRESULT CALLBACK vboxDnDWndProcInstance(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
49static LRESULT CALLBACK vboxDnDWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
50
51VBoxDnDWnd::VBoxDnDWnd(void)
52 : hWnd(NULL),
53 mfMouseButtonDown(false),
54#ifdef VBOX_WITH_DRAG_AND_DROP_GH
55 pDropTarget(NULL),
56#endif
57 mClientID(UINT32_MAX),
58 mMode(Unknown),
59 mState(Uninitialized)
60{
61 RT_ZERO(startupInfo);
62
63 reset();
64}
65
66VBoxDnDWnd::~VBoxDnDWnd(void)
67{
68 /** @todo Shutdown crit sect / event etc! */
69
70 reset();
71}
72
73int VBoxDnDWnd::Initialize(PVBOXDNDCONTEXT pContext)
74{
75 AssertPtrReturn(pContext, VERR_INVALID_POINTER);
76
77 /* Save the context. */
78 this->pContext = pContext;
79
80 int rc = RTSemEventCreate(&mEventSem);
81 if (RT_SUCCESS(rc))
82 rc = RTCritSectInit(&mCritSect);
83
84 if (RT_SUCCESS(rc))
85 {
86 /* Message pump thread for our proxy window. */
87 rc = RTThreadCreate(&gCtx.hEvtQueue, VBoxDnDWnd::Thread, this,
88 0, RTTHREADTYPE_MSG_PUMP, RTTHREADFLAGS_WAITABLE,
89 "VBoxTrayDnDWnd");
90 if (RT_FAILURE(rc))
91 LogRel(("DnD: Failed to start proxy window thread, rc=%Rrc\n", rc));
92 /** @todo Wait for thread to be started! */
93 }
94
95 LogFlowThisFunc(("Returning rc=%Rrc\n", rc));
96 return rc;
97}
98
99/**
100 * Thread for handling the window's message pump.
101 *
102 * @return IPRT status code.
103 * @param hThread
104 * @param pvUser
105 */
106/* static */
107int VBoxDnDWnd::Thread(RTTHREAD hThread, void *pvUser)
108{
109 AssertPtrReturn(pvUser, VERR_INVALID_POINTER);
110
111 VBoxDnDWnd *pThis = (VBoxDnDWnd*)pvUser;
112 AssertPtr(pThis);
113
114 PVBOXDNDCONTEXT pContext = pThis->pContext;
115 AssertPtr(pContext);
116 AssertPtr(pContext->pEnv);
117
118 HINSTANCE hInstance = pContext->pEnv->hInstance;
119 Assert(hInstance != 0);
120
121 /* Create our proxy window. */
122 WNDCLASSEX wndClass;
123 RT_ZERO(wndClass);
124
125 wndClass.cbSize = sizeof(WNDCLASSEX);
126 wndClass.lpfnWndProc = vboxDnDWndProc;
127 wndClass.lpszClassName = "VBoxTrayDnDWnd";
128 wndClass.hInstance = hInstance;
129 wndClass.style = CS_NOCLOSE;
130#ifdef VBOX_DND_DEBUG_WND
131 wndClass.style |= CS_HREDRAW | CS_VREDRAW;
132 wndClass.hbrBackground = (HBRUSH)(CreateSolidBrush(RGB(255, 0, 0)));
133#else
134 wndClass.hbrBackground = (HBRUSH)(COLOR_BACKGROUND + 1);
135#endif
136
137 int rc = VINF_SUCCESS;
138 if (!RegisterClassEx(&wndClass))
139 {
140 DWORD dwErr = GetLastError();
141 LogFlowFunc(("Unable to register proxy window class, error=%ld\n", dwErr));
142 rc = RTErrConvertFromWin32(dwErr);
143 }
144
145 if (RT_SUCCESS(rc))
146 {
147 DWORD dwExStyle = WS_EX_TOOLWINDOW | WS_EX_TRANSPARENT | WS_EX_NOACTIVATE;
148 DWORD dwStyle = WS_POPUP;
149#ifdef VBOX_DND_DEBUG_WND
150 dwExStyle &= ~WS_EX_TRANSPARENT; /* Remove transparency bit. */
151 dwStyle |= WS_VISIBLE; /* Make the window visible. */
152#endif
153 pThis->hWnd =
154 CreateWindowEx(dwExStyle,
155 "VBoxTrayDnDWnd", "VBoxTrayDnDWnd",
156 dwStyle,
157#ifdef VBOX_DND_DEBUG_WND
158 CW_USEDEFAULT, CW_USEDEFAULT, 200, 200, NULL, NULL,
159#else
160 -200, -200, 100, 100, NULL, NULL,
161#endif
162 hInstance, pThis /* lParm */);
163 if (!pThis->hWnd)
164 {
165 DWORD dwErr = GetLastError();
166 LogFlowFunc(("Unable to create proxy window, error=%ld\n", dwErr));
167 rc = RTErrConvertFromWin32(dwErr);
168 }
169 else
170 {
171#ifndef VBOX_DND_DEBUG_WND
172 SetWindowPos(pThis->hWnd, HWND_TOPMOST, -200, -200, 0, 0,
173 SWP_NOACTIVATE | SWP_HIDEWINDOW
174 | SWP_NOCOPYBITS | SWP_NOREDRAW | SWP_NOSIZE);
175 LogFlowFunc(("Proxy window created, hWnd=0x%x\n", pThis->hWnd));
176#else
177 LogFlowFunc(("Debug proxy window created, hWnd=0x%x\n", pThis->hWnd));
178
179 /*
180 * Install some mouse tracking.
181 */
182 TRACKMOUSEEVENT me;
183 RT_ZERO(me);
184 me.cbSize = sizeof(TRACKMOUSEEVENT);
185 me.dwFlags = TME_HOVER | TME_LEAVE | TME_NONCLIENT;
186 me.hwndTrack = pThis->hWnd;
187 BOOL fRc = TrackMouseEvent(&me);
188 Assert(fRc);
189#endif
190 }
191 }
192
193 if (RT_SUCCESS(rc))
194 {
195 OleInitialize(NULL);
196
197 pThis->RegisterAsDropTarget();
198
199 bool fShutdown = false;
200
201 do
202 {
203 MSG uMsg;
204 while (GetMessage(&uMsg, 0, 0, 0))
205 {
206 TranslateMessage(&uMsg);
207 DispatchMessage(&uMsg);
208 }
209
210 if (ASMAtomicReadBool(&pContext->fShutdown))
211 fShutdown = true;
212
213 if (fShutdown)
214 {
215 LogFlowFunc(("Cancelling ...\n"));
216 break;
217 }
218
219 /** @todo Immediately drop on failure? */
220
221 } while (RT_SUCCESS(rc));
222
223 OleUninitialize();
224 }
225
226 LogFlowFuncLeaveRC(rc);
227 return rc;
228}
229
230/* static */
231BOOL CALLBACK VBoxDnDWnd::MonitorEnumProc(HMONITOR hMonitor, HDC hdcMonitor,
232 LPRECT lprcMonitor, LPARAM lParam)
233{
234 LPRECT pRect = (LPRECT)lParam;
235 AssertPtrReturn(pRect, FALSE);
236
237 AssertPtr(lprcMonitor);
238 LogFlowFunc(("Monitor is %ld,%ld,%ld,%ld\n",
239 lprcMonitor->left, lprcMonitor->top,
240 lprcMonitor->right, lprcMonitor->bottom));
241
242 /* Build up a simple bounding box to hold the entire (virtual) screen. */
243 if (pRect->left > lprcMonitor->left)
244 pRect->left = lprcMonitor->left;
245 if (pRect->right < lprcMonitor->right)
246 pRect->right = lprcMonitor->right;
247 if (pRect->top > lprcMonitor->top)
248 pRect->top = lprcMonitor->top;
249 if (pRect->bottom < lprcMonitor->bottom)
250 pRect->bottom = lprcMonitor->bottom;
251
252 return TRUE;
253}
254
255LRESULT CALLBACK VBoxDnDWnd::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
256{
257 switch (uMsg)
258 {
259 case WM_CREATE:
260 {
261 int rc = OnCreate();
262 if (RT_FAILURE(rc))
263 return FALSE;
264 return TRUE;
265 }
266
267 case WM_CLOSE:
268 {
269 OnDestroy();
270
271 DestroyWindow(hWnd);
272 PostQuitMessage(0);
273 return 0;
274 }
275
276 case WM_LBUTTONDOWN:
277 LogFlowThisFunc(("WM_LBUTTONDOWN\n"));
278 mfMouseButtonDown = true;
279 return 0;
280
281 case WM_LBUTTONUP:
282 LogFlowThisFunc(("WM_LBUTTONUP\n"));
283 mfMouseButtonDown = false;
284 return 0;
285
286 case WM_MOUSELEAVE:
287 LogFlowThisFunc(("WM_MOUSELEAVE\n"));
288 return 0;
289
290 /* Will only be called once; after the first mouse move, this
291 * window will be hidden! */
292 case WM_MOUSEMOVE:
293 {
294 LogFlowThisFunc(("WM_MOUSEMOVE: mfMouseButtonDown=%RTbool, mMode=%ld, mState=%ld\n",
295 mfMouseButtonDown, mMode, mState));
296#ifdef DEBUG_andy
297 POINT p;
298 GetCursorPos(&p);
299 LogFlowThisFunc(("WM_MOUSEMOVE: curX=%ld, curY=%ld\n", p.x, p.y));
300#endif
301 int rc = VINF_SUCCESS;
302 if (mMode == HG) /* Host to guest. */
303 {
304 /* Dragging not started yet? Kick it off ... */
305 if ( mfMouseButtonDown
306 && (mState != Dragging))
307 {
308 mState = Dragging;
309#if 0
310 /* Delay hiding the proxy window a bit when debugging, to see
311 * whether the desired range is covered correctly. */
312 RTThreadSleep(5000);
313#endif
314 hide();
315
316 LogFlowThisFunc(("Starting drag'n drop: uAllActions=0x%x, dwOKEffects=0x%x ...\n",
317 uAllActions, startupInfo.dwOKEffects));
318
319 AssertPtr(startupInfo.pDataObject);
320 AssertPtr(startupInfo.pDropSource);
321 DWORD dwEffect;
322 HRESULT hr = DoDragDrop(startupInfo.pDataObject, startupInfo.pDropSource,
323 startupInfo.dwOKEffects, &dwEffect);
324 LogFlowThisFunc(("rc=%Rhrc, dwEffect=%RI32\n", hr, dwEffect));
325 switch (hr)
326 {
327 case DRAGDROP_S_DROP:
328 mState = Dropped;
329 break;
330
331 case DRAGDROP_S_CANCEL:
332 mState = Canceled;
333 break;
334
335 default:
336 LogFlowThisFunc(("Drag'n drop failed with %Rhrc\n", hr));
337 mState = Canceled;
338 rc = VERR_GENERAL_FAILURE; /** @todo Find a better status code. */
339 break;
340 }
341
342 int rc2 = RTCritSectEnter(&mCritSect);
343 if (RT_SUCCESS(rc2))
344 {
345 startupInfo.pDropSource->Release();
346 startupInfo.pDataObject->Release();
347
348 RT_ZERO(startupInfo);
349
350 rc2 = RTCritSectLeave(&mCritSect);
351 if (RT_SUCCESS(rc))
352 rc = rc2;
353 }
354
355 mMode = Unknown;
356 }
357 }
358 else if (mMode == GH) /* Guest to host. */
359 {
360 //hide();
361
362 /* Starting here VBoxDnDDropTarget should
363 * take over; was instantiated when registering
364 * this proxy window as a (valid) drop target. */
365 }
366 else
367 rc = VERR_NOT_SUPPORTED;
368
369 LogFlowThisFunc(("WM_MOUSEMOVE: mMode=%ld, mState=%ld, rc=%Rrc\n",
370 mMode, mState, rc));
371 return 0;
372 }
373
374 case WM_NCMOUSEHOVER:
375 LogFlowThisFunc(("WM_NCMOUSEHOVER\n"));
376 return 0;
377
378 case WM_NCMOUSELEAVE:
379 LogFlowThisFunc(("WM_NCMOUSELEAVE\n"));
380 return 0;
381
382 case WM_VBOXTRAY_DND_MESSAGE:
383 {
384 VBOXDNDEVENT *pEvent = (PVBOXDNDEVENT)lParam;
385 AssertPtr(pEvent);
386
387 LogFlowThisFunc(("Received uType=%RU32, uScreenID=%RU32\n",
388 pEvent->Event.uType, pEvent->Event.uScreenId));
389
390 int rc;
391 switch (pEvent->Event.uType)
392 {
393 case DragAndDropSvc::HOST_DND_HG_EVT_ENTER:
394 {
395 LogFlowThisFunc(("HOST_DND_HG_EVT_ENTER\n"));
396
397 reset();
398
399 Assert(mMode == Unknown);
400 mMode = HG;
401
402 if (pEvent->Event.cbFormats)
403 {
404 RTCList<RTCString> lstFormats =
405 RTCString(pEvent->Event.pszFormats, pEvent->Event.cbFormats - 1).split("\r\n");
406 rc = OnHgEnter(lstFormats, pEvent->Event.u.a.uAllActions);
407 }
408 else
409 {
410 AssertMsgFailed(("cbFormats is 0\n"));
411 rc = VERR_INVALID_PARAMETER;
412 }
413
414 /* Note: After HOST_DND_HG_EVT_ENTER there immediately is a move
415 * event, so fall through is intentional here. */
416 }
417
418 case DragAndDropSvc::HOST_DND_HG_EVT_MOVE:
419 {
420 LogFlowThisFunc(("HOST_DND_HG_EVT_MOVE: %d,%d\n",
421 pEvent->Event.u.a.uXpos, pEvent->Event.u.a.uYpos));
422
423 rc = OnHgMove(pEvent->Event.u.a.uXpos, pEvent->Event.u.a.uYpos,
424 pEvent->Event.u.a.uDefAction);
425 break;
426 }
427
428 case DragAndDropSvc::HOST_DND_HG_EVT_LEAVE:
429 {
430 LogFlowThisFunc(("HOST_DND_HG_EVT_LEAVE\n"));
431
432 rc = OnHgLeave();
433 break;
434 }
435
436 case DragAndDropSvc::HOST_DND_HG_EVT_DROPPED:
437 {
438 LogFlowThisFunc(("HOST_DND_HG_EVT_DROPPED\n"));
439
440 rc = OnHgDrop();
441 break;
442 }
443
444 case DragAndDropSvc::HOST_DND_HG_SND_DATA:
445 {
446 LogFlowThisFunc(("HOST_DND_HG_SND_DATA\n"));
447
448 rc = OnHgDataReceived(pEvent->Event.u.b.pvData,
449 pEvent->Event.u.b.cbData);
450 break;
451 }
452
453 case DragAndDropSvc::HOST_DND_HG_EVT_CANCEL:
454 {
455 LogFlowThisFunc(("HOST_DND_HG_EVT_CANCEL\n"));
456
457 rc = OnHgCancel();
458
459 reset();
460 break;
461 }
462
463 case DragAndDropSvc::HOST_DND_GH_REQ_PENDING:
464 {
465 LogFlowThisFunc(("HOST_DND_GH_REQ_PENDING\n"));
466#ifdef VBOX_WITH_DRAG_AND_DROP_GH
467 Assert( mMode == Unknown
468 || mMode == GH);
469 mMode = GH;
470 rc = OnGhIsDnDPending(pEvent->Event.uScreenId);
471#else
472 rc = VERR_NOT_SUPPORTED;
473#endif
474 break;
475 }
476
477 case DragAndDropSvc::HOST_DND_GH_EVT_DROPPED:
478 {
479 LogFlowThisFunc(("HOST_DND_GH_EVT_DROPPED\n"));
480#ifdef VBOX_WITH_DRAG_AND_DROP_GH
481 Assert(mMode == GH);
482 rc = OnGhDropped(pEvent->Event.pszFormats,
483 pEvent->Event.cbFormats,
484 pEvent->Event.u.a.uDefAction);
485 mMode = Unknown;
486#else
487 rc = VERR_NOT_SUPPORTED;
488#endif
489 break;
490 }
491
492 case DragAndDropSvc::GUEST_DND_GH_EVT_ERROR:
493 {
494 LogFlowThisFunc(("GUEST_DND_GH_EVT_ERROR\n"));
495#ifdef VBOX_WITH_DRAG_AND_DROP_GH
496 reset();
497 rc = VINF_SUCCESS; /** @todo GUEST_DND_GH_EVT_ERROR */
498#else
499 rc = VERR_NOT_SUPPORTED;
500#endif
501 break;
502 }
503
504 default:
505 rc = VERR_NOT_SUPPORTED;
506 break;
507 }
508
509 /* Some messages require cleanup. */
510 switch (pEvent->Event.uType)
511 {
512 case DragAndDropSvc::HOST_DND_HG_EVT_ENTER:
513 case DragAndDropSvc::HOST_DND_HG_EVT_MOVE:
514 case DragAndDropSvc::HOST_DND_HG_EVT_DROPPED:
515#ifdef VBOX_WITH_DRAG_AND_DROP_GH
516 case DragAndDropSvc::HOST_DND_GH_EVT_DROPPED:
517#endif
518 {
519 if (pEvent->Event.pszFormats)
520 RTMemFree(pEvent->Event.pszFormats);
521 break;
522 }
523
524 case DragAndDropSvc::HOST_DND_HG_SND_DATA:
525 {
526 if (pEvent->Event.pszFormats)
527 RTMemFree(pEvent->Event.pszFormats);
528 if (pEvent->Event.u.b.pvData)
529 RTMemFree(pEvent->Event.u.b.pvData);
530 break;
531 }
532
533 default:
534 /* Ignore. */
535 break;
536 }
537
538 LogFlowThisFunc(("Processing event %RU32 resulted in rc=%Rrc\n",
539 pEvent->Event.uType, rc));
540 if (pEvent)
541 RTMemFree(pEvent);
542 return 0;
543 }
544
545 default:
546 break;
547 }
548
549 return DefWindowProc(hWnd, uMsg, wParam, lParam);
550}
551
552#ifdef VBOX_WITH_DRAG_AND_DROP_GH
553/**
554 * Registers this proxy window as a local drop target.
555 *
556 * @return IPRT status code.
557 */
558int VBoxDnDWnd::RegisterAsDropTarget(void)
559{
560 if (pDropTarget) /* Already registered as drop target? */
561 return VINF_SUCCESS;
562
563 int rc;
564 try
565 {
566 pDropTarget = new VBoxDnDDropTarget(this /* pParent */);
567 AssertPtr(pDropTarget);
568 HRESULT hr = CoLockObjectExternal(pDropTarget, TRUE /* fLock */,
569 FALSE /* fLastUnlockReleases */);
570 if (SUCCEEDED(hr))
571 hr = RegisterDragDrop(hWnd, pDropTarget);
572
573 if (FAILED(hr))
574 {
575 LogRel(("DnD: Creating drop target failed with hr=%Rhrc\n", hr));
576 rc = VERR_GENERAL_FAILURE; /** @todo Find a better rc. */
577 }
578 else
579 rc = VINF_SUCCESS;
580 }
581 catch (std::bad_alloc)
582 {
583 rc = VERR_NO_MEMORY;
584 }
585
586 LogFlowFuncLeaveRC(rc);
587 return rc;
588}
589
590int VBoxDnDWnd::UnregisterAsDropTarget(void)
591{
592 if (!pDropTarget) /* No drop target? Bail out. */
593 return VINF_SUCCESS;
594
595 HRESULT hr = RevokeDragDrop(hWnd);
596 if (SUCCEEDED(hr))
597 hr = CoLockObjectExternal(pDropTarget, FALSE /* fLock */,
598 TRUE /* fLastUnlockReleases */);
599 if (SUCCEEDED(hr))
600 pDropTarget->Release();
601
602 int rc = SUCCEEDED(hr)
603 ? VINF_SUCCESS : VERR_GENERAL_FAILURE; /** @todo Fix this. */
604
605 LogFlowFuncLeaveRC(rc);
606 return rc;
607}
608#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
609
610int VBoxDnDWnd::OnCreate(void)
611{
612 int rc = VbglR3DnDConnect(&mClientID);
613 if (RT_FAILURE(rc))
614 {
615 LogFlowThisFunc(("Connection to host service failed, rc=%Rrc\n", rc));
616 return rc;
617 }
618
619 LogFlowThisFunc(("Client ID=%RU32, rc=%Rrc\n", mClientID, rc));
620 return rc;
621}
622
623void VBoxDnDWnd::OnDestroy(void)
624{
625 VbglR3DnDDisconnect(mClientID);
626 LogFlowThisFuncLeave();
627}
628
629int VBoxDnDWnd::OnHgEnter(const RTCList<RTCString> &lstFormats, uint32_t uAllActions)
630{
631#ifdef DEBUG
632 LogFlowThisFunc(("uActions=0x%x, lstFormats=%zu: ", uAllActions, lstFormats.size()));
633 for (size_t i = 0; i < lstFormats.size(); i++)
634 LogFlow(("'%s' ", lstFormats.at(i).c_str()));
635 LogFlow(("\n"));
636#endif
637
638 /* Save all allowed actions. */
639 this->uAllActions = uAllActions;
640
641 /*
642 * Install our allowed MIME types.
643 ** @todo See todo for m_sstrAllowedMimeTypes in GuestDnDImpl.cpp.
644 */
645 const RTCList<RTCString> lstAllowedMimeTypes = RTCList<RTCString>()
646 /* Uri's */
647 << "text/uri-list"
648 /* Text */
649 << "text/plain;charset=utf-8"
650 << "UTF8_STRING"
651 << "text/plain"
652 << "COMPOUND_TEXT"
653 << "TEXT"
654 << "STRING"
655 /* OpenOffice formates */
656 << "application/x-openoffice-embed-source-xml;windows_formatname=\"Star Embed Source (XML)\""
657 << "application/x-openoffice-drawing;windows_formatname=\"Drawing Format\"";
658 this->lstAllowedFormats = lstAllowedMimeTypes;
659
660 /*
661 * Check MIME compatibility with this client.
662 */
663 LogFlowThisFunc(("Supported MIME types:\n"));
664 for (size_t i = 0; i < lstFormats.size(); i++)
665 {
666 bool fSupported = lstAllowedFormats.contains(lstFormats.at(i));
667 if (fSupported)
668 this->lstFormats.append(lstFormats.at(i));
669 LogFlowThisFunc(("\t%s: %RTbool\n", lstFormats.at(i).c_str(), fSupported));
670 }
671
672 /*
673 * Prepare the startup info for DoDragDrop().
674 */
675 int rc = VINF_SUCCESS;
676 try
677 {
678 /* Translate our drop actions into
679 * allowed Windows drop effects. */
680 startupInfo.dwOKEffects = DROPEFFECT_NONE;
681 if (uAllActions)
682 {
683 if (uAllActions & DND_COPY_ACTION)
684 startupInfo.dwOKEffects |= DROPEFFECT_COPY;
685 if (uAllActions & DND_MOVE_ACTION)
686 startupInfo.dwOKEffects |= DROPEFFECT_MOVE;
687 if (uAllActions & DND_LINK_ACTION)
688 startupInfo.dwOKEffects |= DROPEFFECT_LINK;
689 }
690
691 startupInfo.pDropSource = new VBoxDnDDropSource(this);
692 startupInfo.pDataObject = new VBoxDnDDataObject();
693 }
694 catch (std::bad_alloc)
695 {
696 rc = VERR_NO_MEMORY;
697 }
698
699 if (RT_SUCCESS(rc))
700 rc = makeFullscreen();
701
702 LogFlowFuncLeaveRC(rc);
703 return rc;
704}
705
706int VBoxDnDWnd::OnHgMove(uint32_t u32xPos, uint32_t u32yPos, uint32_t uAction)
707{
708 LogFlowThisFunc(("u32xPos=%RU32, u32yPos=%RU32, uAction=0x%x\n",
709 u32xPos, u32yPos, uAction));
710
711 /** @todo Put this block into a function! */
712 /** @todo Multi-monitor setups? */
713 int iScreenX = GetSystemMetrics(SM_CXSCREEN) - 1;
714 int iScreenY = GetSystemMetrics(SM_CYSCREEN) - 1;
715
716 INPUT Input[1] = { 0 };
717 Input[0].type = INPUT_MOUSE;
718 Input[0].mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_ABSOLUTE;
719 Input[0].mi.dx = u32xPos * (65535 / iScreenX);
720 Input[0].mi.dy = u32yPos * (65535 / iScreenY);
721 SendInput(1, Input, sizeof(INPUT));
722
723#ifdef DEBUG_andy
724 POINT p;
725 GetCursorPos(&p);
726 LogFlowThisFunc(("curX=%d, curY=%d\n", p.x, p.y));
727#endif
728
729 uint32_t uActionNotify = DND_IGNORE_ACTION;
730 int rc = RTCritSectEnter(&mCritSect);
731 if (RT_SUCCESS(rc))
732 {
733 if ( (Dragging == mState)
734 && startupInfo.pDropSource)
735 uActionNotify = startupInfo.pDropSource->GetCurrentAction();
736
737 RTCritSectLeave(&mCritSect);
738 }
739
740 if (RT_SUCCESS(rc))
741 {
742 rc = VbglR3DnDHGAcknowledgeOperation(mClientID, uActionNotify);
743 if (RT_FAILURE(rc))
744 LogFlowThisFunc(("Acknowledging operation failed with rc=%Rrc\n", rc));
745 }
746
747 LogFlowThisFunc(("Returning uActionNotify=0x%x, rc=%Rrc\n", uActionNotify, rc));
748 return rc;
749}
750
751int VBoxDnDWnd::OnHgLeave(void)
752{
753 LogFlowThisFunc(("mMode=%ld, mState=%RU32\n", mMode, mState));
754 LogRel(("DnD: Drag'n drop operation aborted\n"));
755
756 reset();
757
758 int rc = VINF_SUCCESS;
759
760 /* Post ESC to our window to officially abort the
761 * drag'n drop operation. */
762 PostMessage(hWnd, WM_KEYDOWN, VK_ESCAPE, 0 /* lParam */);
763
764 LogFlowFuncLeaveRC(rc);
765 return rc;
766}
767
768int VBoxDnDWnd::OnHgDrop(void)
769{
770 LogFlowThisFunc(("mMode=%ld, mState=%RU32\n", mMode, mState));
771
772 int rc = VINF_SUCCESS;
773 if (mState == Dragging)
774 {
775 Assert(lstFormats.size() >= 1);
776
777 /** @todo What to do when multiple formats are available? */
778 mFormatRequested = lstFormats.at(0);
779
780 rc = RTCritSectEnter(&mCritSect);
781 if (RT_SUCCESS(rc))
782 {
783 if (startupInfo.pDataObject)
784 startupInfo.pDataObject->SetStatus(VBoxDnDDataObject::Dropping);
785 else
786 rc = VERR_NOT_FOUND;
787
788 RTCritSectLeave(&mCritSect);
789 }
790
791 if (RT_SUCCESS(rc))
792 {
793 LogRel(("DnD: Requesting data as '%s' ...\n", mFormatRequested.c_str()));
794 rc = VbglR3DnDHGRequestData(mClientID, mFormatRequested.c_str());
795 if (RT_FAILURE(rc))
796 LogFlowThisFunc(("Requesting data failed with rc=%Rrc\n", rc));
797 }
798 }
799
800 LogFlowFuncLeaveRC(rc);
801 return rc;
802}
803
804int VBoxDnDWnd::OnHgDataReceived(const void *pvData, uint32_t cbData)
805{
806 LogFlowThisFunc(("mState=%ld, pvData=%p, cbData=%RU32\n",
807 mState, pvData, cbData));
808
809 mState = Dropped;
810
811 int rc = VINF_SUCCESS;
812 if (pvData)
813 {
814 Assert(cbData);
815 rc = RTCritSectEnter(&mCritSect);
816 if (RT_SUCCESS(rc))
817 {
818 if (startupInfo.pDataObject)
819 rc = startupInfo.pDataObject->Signal(mFormatRequested, pvData, cbData);
820 else
821 rc = VERR_NOT_FOUND;
822
823 RTCritSectLeave(&mCritSect);
824 }
825 }
826
827 int rc2 = dragRelease();
828 if (RT_SUCCESS(rc))
829 rc = rc2;
830
831 LogFlowFuncLeaveRC(rc);
832 return rc;
833}
834
835int VBoxDnDWnd::OnHgCancel(void)
836{
837 int rc = RTCritSectEnter(&mCritSect);
838 if (RT_SUCCESS(rc))
839 {
840 if (startupInfo.pDataObject)
841 startupInfo.pDataObject->Abort();
842
843 RTCritSectLeave(&mCritSect);
844 }
845
846 int rc2 = dragRelease();
847 if (RT_SUCCESS(rc))
848 rc = rc2;
849
850 return rc;
851}
852
853#ifdef VBOX_WITH_DRAG_AND_DROP_GH
854int VBoxDnDWnd::OnGhIsDnDPending(uint32_t uScreenID)
855{
856 LogFlowThisFunc(("mMode=%ld, mState=%ld, uScreenID=%RU32\n",
857 mMode, mState, uScreenID));
858
859 if (mState == Uninitialized)
860 reset();
861
862 int rc;
863 if (mState == Initialized)
864 {
865 rc = makeFullscreen();
866 /*if (RT_SUCCESS(rc))
867 rc = RegisterAsDropTarget();*/
868
869 if (RT_SUCCESS(rc))
870 {
871 /*
872 * We have to release the left mouse button to
873 * get into our (invisible) proxy window.
874 */
875 dragRelease();
876
877 /*
878 * Even if we just released the left mouse button
879 * we're still in the dragging state to handle our
880 * own drop target (for the host).
881 */
882 mState = Dragging;
883 }
884 }
885 else
886 rc = VINF_SUCCESS;
887
888 /**
889 * Some notes regarding guest cursor movement:
890 * - The host only sends an HOST_DND_GH_REQ_PENDING message to the guest
891 * if the mouse cursor is outside the VM's window.
892 * - The guest does not know anything about the host's cursor
893 * position / state due to security reasons.
894 * - The guest *only* knows that the host currently is asking whether a
895 * guest DnD operation is in progress.
896 */
897
898 if ( RT_SUCCESS(rc)
899 && (mState == Dragging))
900 {
901 /** @todo Put this block into a function! */
902 POINT p;
903 GetCursorPos(&p);
904 ClientToScreen(hWnd, &p);
905#ifdef DEBUG_andy
906 LogFlowThisFunc(("Client to screen curX=%ld, curY=%ld\n", p.x, p.y));
907#endif
908
909#if 1
910 /** @todo Multi-monitor setups? */
911 int iScreenX = GetSystemMetrics(SM_CXSCREEN) - 1;
912 int iScreenY = GetSystemMetrics(SM_CYSCREEN) - 1;
913
914 static LONG px = p.x;
915 if (px <= 0)
916 px = 1;
917 static LONG py = p.y;
918 if (py <= 0)
919 py = 1;
920 //px++; py++;
921 LogFlowThisFunc(("px=%ld, py=%ld\n", px, py));
922
923 INPUT Input[1] = { 0 };
924 Input[0].type = INPUT_MOUSE;
925 Input[0].mi.dwFlags = MOUSEEVENTF_MOVE | /*MOUSEEVENTF_LEFTDOWN |*/ MOUSEEVENTF_ABSOLUTE;
926 Input[0].mi.dx = px * (65535 / iScreenX);
927 Input[0].mi.dy = py * (65535 / iScreenY);
928 UINT uiProcessed = SendInput(1, Input, sizeof(INPUT));
929 if (uiProcessed)
930 {
931#ifdef DEBUG_andy
932 LogFlowFunc(("Sent %RU16 mouse input(s), x=%ld, y=%ld, flags=0x%x\n",
933 uiProcessed, Input[0].mi.dx, Input[0].mi.dy, Input[0].mi.dwFlags));
934#endif
935 }
936 else
937 LogFlowFunc(("Unable to send input, error=0x%x\n", GetLastError()));
938#else
939 SetCursorPos(p.x, p.y);
940#endif
941
942#ifdef DEBUG_andy
943 CURSORINFO ci;
944 RT_ZERO(ci);
945 ci.cbSize = sizeof(ci);
946 BOOL fRc = GetCursorInfo(&ci);
947 if (fRc)
948 LogFlowThisFunc(("Cursor shown=%RTbool, cursor=0x%p, x=%d, y=%d\n",
949 (ci.flags & CURSOR_SHOWING) ? true : false,
950 ci.hCursor, ci.ptScreenPos.x, ci.ptScreenPos.y));
951#endif
952 }
953
954 if (RT_SUCCESS(rc))
955 {
956 uint32_t uDefAction = DND_IGNORE_ACTION;
957 RTCString strFormat = "unknown";
958 if ( pDropTarget
959 && pDropTarget->HasData())
960 {
961 uDefAction = DND_COPY_ACTION;
962 uAllActions = uDefAction;
963 strFormat = "text/plain;charset=utf-8";
964 }
965
966 LogFlowFunc(("Acknowledging pDropTarget=0x%p, uDefAction=0x%x, uAllActions=0x%x, strFormat=%s\n",
967 pDropTarget, uDefAction, uAllActions, strFormat.c_str()));
968 rc = VbglR3DnDGHAcknowledgePending(mClientID,
969 uDefAction, uAllActions, strFormat.c_str());
970 }
971
972 LogFlowFuncLeaveRC(rc);
973 return rc;
974}
975
976int VBoxDnDWnd::OnGhDropped(const char *pszFormats, uint32_t cbFormats,
977 uint32_t uDefAction)
978{
979 LogFlowThisFunc(("mMode=%ld, mState=%ld, cbFormats=%RU32, uDefAction=0x%x\n",
980 mMode, mState, cbFormats, uDefAction));
981 int rc;
982 if (mState == Dragging)
983 {
984 AssertPtr(pDropTarget);
985 }
986 else
987 rc = VERR_WRONG_ORDER;
988
989 LogFlowFuncLeaveRC(rc);
990 return rc;
991}
992#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
993
994int VBoxDnDWnd::ProcessEvent(PVBOXDNDEVENT pEvent)
995{
996 AssertPtrReturn(pEvent, VERR_INVALID_POINTER);
997
998 PostMessage(hWnd, WM_VBOXTRAY_DND_MESSAGE,
999 0 /* wParm */, (LPARAM)pEvent /* lParm */);
1000
1001 return VINF_SUCCESS;
1002}
1003
1004int VBoxDnDWnd::dragRelease(void)
1005{
1006#ifdef DEBUG_andy
1007 LogFlowFunc(("\n"));
1008#endif
1009 /* Release mouse button in the guest to start the "drop"
1010 * action at the current mouse cursor position. */
1011 INPUT Input[1] = { 0 };
1012 Input[0].type = INPUT_MOUSE;
1013 Input[0].mi.dwFlags = MOUSEEVENTF_LEFTUP;
1014 SendInput(1, Input, sizeof(INPUT));
1015
1016 return VINF_SUCCESS;
1017}
1018
1019int VBoxDnDWnd::hide(void)
1020{
1021#ifdef DEBUG_andy
1022 LogFlowFunc(("\n"));
1023#endif
1024 ShowWindow(hWnd, SW_HIDE);
1025
1026 return VINF_SUCCESS;
1027}
1028
1029int VBoxDnDWnd::makeFullscreen(void)
1030{
1031 int rc = VINF_SUCCESS;
1032
1033 RECT r;
1034 RT_ZERO(r);
1035
1036 BOOL fRc;
1037 HDC hDC = GetDC(NULL /* Entire screen */);
1038 if (hDC)
1039 {
1040 fRc = EnumDisplayMonitors(hDC, NULL, VBoxDnDWnd::MonitorEnumProc,
1041 (LPARAM)&r);
1042 if (!fRc)
1043 rc = VERR_NOT_FOUND;
1044 ReleaseDC(NULL, hDC);
1045 }
1046 else
1047 rc = VERR_ACCESS_DENIED;
1048
1049 if (RT_FAILURE(rc))
1050 {
1051 /* If multi-monitor enumeration failed above, try getting at least the
1052 * primary monitor as a fallback. */
1053 MONITORINFO monitor_info;
1054 monitor_info.cbSize = sizeof(monitor_info);
1055 if (GetMonitorInfo(MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST),
1056 &monitor_info))
1057 {
1058
1059 r = monitor_info.rcMonitor;
1060 rc = VINF_SUCCESS;
1061 }
1062 }
1063
1064 if (RT_SUCCESS(rc))
1065 {
1066 LONG lStyle = GetWindowLong(hWnd, GWL_STYLE);
1067 SetWindowLong(hWnd, GWL_STYLE,
1068 lStyle & ~(WS_CAPTION | WS_THICKFRAME));
1069 LONG lExStyle = GetWindowLong(hWnd, GWL_EXSTYLE);
1070 SetWindowLong(hWnd, GWL_EXSTYLE,
1071 lExStyle & ~( WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE
1072 | WS_EX_CLIENTEDGE | WS_EX_STATICEDGE));
1073
1074 fRc = SetWindowPos(hWnd, HWND_TOPMOST,
1075 r.left,
1076 r.top,
1077 r.right - r.left,
1078 r.bottom - r.top,
1079#ifdef VBOX_DND_DEBUG_WND
1080 SWP_SHOWWINDOW | SWP_FRAMECHANGED);
1081#else
1082 SWP_SHOWWINDOW | SWP_NOOWNERZORDER | SWP_NOREDRAW | SWP_NOACTIVATE);
1083#endif
1084 if (fRc)
1085 {
1086 LogFlowFunc(("Virtual screen is %ld,%ld,%ld,%ld (%ld x %ld)\n",
1087 r.left, r.top, r.right, r.bottom,
1088 r.right - r.left, r.bottom - r.top));
1089 }
1090 else
1091 {
1092 DWORD dwErr = GetLastError();
1093 LogRel(("DnD: Failed to set proxy window position, rc=%Rrc\n",
1094 RTErrConvertFromWin32(dwErr)));
1095 }
1096 }
1097 else
1098 LogRel(("DnD: Failed to determine virtual screen size, rc=%Rrc\n", rc));
1099
1100 LogFlowFuncLeaveRC(rc);
1101 return rc;
1102}
1103
1104void VBoxDnDWnd::reset(void)
1105{
1106 LogFlowThisFunc(("Old mState=%ld\n", mState));
1107
1108 lstAllowedFormats.clear();
1109 lstFormats.clear();
1110 uAllActions = DND_IGNORE_ACTION;
1111
1112 mMode = Unknown;
1113 mState = Initialized;
1114}
1115
1116static LRESULT CALLBACK vboxDnDWndProcInstance(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
1117{
1118 LONG_PTR pUserData = GetWindowLongPtr(hWnd, GWLP_USERDATA);
1119 AssertPtrReturn(pUserData, 0);
1120
1121 VBoxDnDWnd *pWnd = reinterpret_cast<VBoxDnDWnd *>(pUserData);
1122 if (pWnd)
1123 return pWnd->WndProc(hWnd, uMsg, wParam, lParam);
1124
1125 return 0;
1126}
1127
1128static LRESULT CALLBACK vboxDnDWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
1129{
1130 /* Note: WM_NCCREATE is not the first ever message which arrives, but
1131 * early enough for us. */
1132 if (uMsg == WM_NCCREATE)
1133 {
1134 LPCREATESTRUCT pCS = (LPCREATESTRUCT)lParam;
1135 AssertPtr(pCS);
1136 SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)pCS->lpCreateParams);
1137 SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)vboxDnDWndProcInstance);
1138
1139 return vboxDnDWndProcInstance(hWnd, uMsg, wParam, lParam);
1140 }
1141
1142 /* No window associated yet. */
1143 return DefWindowProc(hWnd, uMsg, wParam, lParam);
1144}
1145
1146/**
1147 * Initializes drag'n drop.
1148 *
1149 * @return IPRT status code.
1150 * @param pEnv The DnD service's environment.
1151 * @param ppInstance The instance pointer which refer to this object.
1152 * @param pfStartThread Pointer to flag whether the DnD service can be started or not.
1153 */
1154int VBoxDnDInit(const VBOXSERVICEENV *pEnv, void **ppInstance, bool *pfStartThread)
1155{
1156 AssertPtrReturn(pEnv, VERR_INVALID_POINTER);
1157 /** ppInstance not used here. */
1158 AssertPtrReturn(pfStartThread, VERR_INVALID_POINTER);
1159
1160 LogFlowFuncEnter();
1161
1162 *pfStartThread = false;
1163
1164 PVBOXDNDCONTEXT pCtx = &gCtx;
1165
1166 int rc;
1167
1168 /* Create the proxy window. At the moment we
1169 * only support one window at a time. */
1170 VBoxDnDWnd *pWnd = NULL;
1171 try
1172 {
1173 pWnd = new VBoxDnDWnd();
1174 rc = pWnd->Initialize(pCtx);
1175
1176 /* Add proxy window to our proxy windows list. */
1177 if (RT_SUCCESS(rc))
1178 pCtx->lstWnd.append(pWnd);
1179 }
1180 catch (std::bad_alloc)
1181 {
1182 rc = VERR_NO_MEMORY;
1183 }
1184
1185 if (RT_SUCCESS(rc))
1186 rc = RTSemEventCreate(&pCtx->hEvtQueueSem);
1187 if (RT_SUCCESS(rc))
1188 {
1189 /* Assign service environment to our context. */
1190 gCtx.pEnv = pEnv;
1191 }
1192
1193 if (RT_SUCCESS(rc))
1194 {
1195 *ppInstance = pCtx;
1196 *pfStartThread = true;
1197
1198 LogRel(("DnD: Drag'n drop service successfully started\n"));
1199 return VINF_SUCCESS;
1200 }
1201
1202 LogRel(("DnD: Initializing drag'n drop service failed with rc=%Rrc\n", rc));
1203 return rc;
1204}
1205
1206void VBoxDnDStop(const VBOXSERVICEENV *pEnv, void *pInstance)
1207{
1208 AssertPtrReturnVoid(pEnv);
1209 AssertPtrReturnVoid(pInstance);
1210
1211 LogFunc(("Stopping pInstance=%p\n", pInstance));
1212
1213 PVBOXDNDCONTEXT pCtx = (PVBOXDNDCONTEXT)pInstance;
1214 AssertPtr(pCtx);
1215
1216 /* Set shutdown indicator. */
1217 ASMAtomicWriteBool(&pCtx->fShutdown, true);
1218
1219 /** @todo Notify / wait for HGCM thread! */
1220}
1221
1222void VBoxDnDDestroy(const VBOXSERVICEENV *pEnv, void *pInstance)
1223{
1224 AssertPtr(pEnv);
1225 AssertPtr(pInstance);
1226
1227 LogFunc(("Destroying pInstance=%p\n", pInstance));
1228
1229 PVBOXDNDCONTEXT pCtx = (PVBOXDNDCONTEXT)pInstance;
1230 AssertPtr(pCtx);
1231
1232 int rc = VINF_SUCCESS;
1233
1234 LogFunc(("Destroyed pInstance=%p, rc=%Rrc\n",
1235 pInstance, rc));
1236}
1237
1238unsigned __stdcall VBoxDnDThread(void *pInstance)
1239{
1240 LogFlowFunc(("pInstance=%p\n", pInstance));
1241
1242 PVBOXDNDCONTEXT pCtx = (PVBOXDNDCONTEXT)pInstance;
1243 AssertPtr(pCtx);
1244
1245 uint32_t uClientID;
1246 int rc = VbglR3DnDConnect(&uClientID);
1247 if (RT_FAILURE(rc))
1248 return rc;
1249
1250 /** @todo At the moment we only have one DnD proxy window. */
1251 Assert(pCtx->lstWnd.size() == 1);
1252 VBoxDnDWnd *pWnd = pCtx->lstWnd.first();
1253 AssertPtr(pWnd);
1254
1255 /* Number of invalid messages skipped in a row. */
1256 int cMsgSkippedInvalid = 0;
1257
1258 do
1259 {
1260 PVBOXDNDEVENT pEvent = (PVBOXDNDEVENT)RTMemAlloc(sizeof(VBOXDNDEVENT));
1261 if (!pEvent)
1262 {
1263 rc = VERR_NO_MEMORY;
1264 break;
1265 }
1266 /* Note: pEvent will be free'd by the consumer later. */
1267
1268 rc = VbglR3DnDProcessNextMessage(uClientID, &pEvent->Event);
1269 LogFlowFunc(("VbglR3DnDProcessNextMessage returned rc=%Rrc\n", rc));
1270
1271 if (ASMAtomicReadBool(&pCtx->fShutdown))
1272 break;
1273
1274 if (RT_SUCCESS(rc))
1275 {
1276 cMsgSkippedInvalid = 0; /* Reset skipped messages count. */
1277
1278 LogFlowFunc(("Received new event, type=%RU32\n", pEvent->Event.uType));
1279
1280 int rc2 = pWnd->ProcessEvent(pEvent);
1281 if (RT_FAILURE(rc2))
1282 LogFlowFunc(("Processing event failed with rc=%Rrc\n", rc2));
1283 }
1284 else if (rc == VERR_CANCELLED)
1285 {
1286 int rc2 = pWnd->OnHgCancel();
1287 if (RT_FAILURE(rc2))
1288 LogFlowFunc(("Cancelling failed with rc=%Rrc\n", rc2));
1289 }
1290 else
1291 {
1292 LogFlowFunc(("Processing next message failed with rc=%Rrc\n", rc));
1293
1294 /* Old(er) hosts either are broken regarding DnD support or otherwise
1295 * don't support the stuff we do on the guest side, so make sure we
1296 * don't process invalid messages forever. */
1297 if (rc == VERR_INVALID_PARAMETER)
1298 cMsgSkippedInvalid++;
1299 if (cMsgSkippedInvalid > 3)
1300 {
1301 LogFlowFunc(("Too many invalid/skipped messages from host, exiting ...\n"));
1302 break;
1303 }
1304 }
1305
1306 if (ASMAtomicReadBool(&pCtx->fShutdown))
1307 break;
1308
1309 } while (true);
1310
1311 LogFlowFunc(("Shutting down ...\n"));
1312
1313 VbglR3DnDDisconnect(uClientID);
1314
1315 LogFlowFuncLeaveRC(rc);
1316 return rc;
1317}
1318
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