VirtualBox

source: vbox/trunk/src/VBox/Main/src-client/GuestDnDPrivate.cpp@ 73768

Last change on this file since 73768 was 72980, checked in by vboxsync, 6 years ago

Main: Enjoy VBOX_WITH_XPCOM_CPP_ENUM_HACK and get rid of silly casts.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 32.5 KB
RevLine 
[42261]1/* $Id: GuestDnDPrivate.cpp 72980 2018-07-08 14:32:09Z vboxsync $ */
2/** @file
[67914]3 * Private guest drag and drop code, used by GuestDnDTarget + GuestDnDSource.
[42261]4 */
5
6/*
[69500]7 * Copyright (C) 2011-2017 Oracle Corporation
[42261]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
[67914]18#define LOG_GROUP LOG_GROUP_GUEST_DND
19#include "LoggingNew.h"
20
[42261]21#include "GuestImpl.h"
22#include "AutoCaller.h"
23
24#ifdef VBOX_WITH_DRAG_AND_DROP
25# include "ConsoleImpl.h"
26# include "ProgressImpl.h"
[51476]27# include "GuestDnDPrivate.h"
[42261]28
[51476]29# include <algorithm>
30
[50460]31# include <iprt/dir.h>
32# include <iprt/path.h>
33# include <iprt/stream.h>
34# include <iprt/semaphore.h>
35# include <iprt/cpp/utils.h>
36
[42261]37# include <VMMDev.h>
38
[50460]39# include <VBox/GuestHost/DragAndDrop.h>
[42261]40# include <VBox/HostServices/DragAndDropSvc.h>
[55422]41# include <VBox/version.h>
[42261]42
[67914]43/** @page pg_main_dnd Dungeons & Dragons - Overview
[51476]44 * Overview:
[42261]45 *
46 * Drag and Drop is handled over the internal HGCM service for the host <->
47 * guest communication. Beside that we need to map the Drag and Drop protocols
48 * of the various OS's we support to our internal channels, this is also highly
49 * communicative in both directions. Unfortunately HGCM isn't really designed
50 * for that. Next we have to foul some of the components. This includes to
51 * trick X11 on the guest side, but also Qt needs to be tricked on the host
52 * side a little bit.
53 *
54 * The following components are involved:
55 *
56 * 1. GUI: Uses the Qt classes for Drag and Drop and mainly forward the content
57 * of it to the Main IGuest interface (see UIDnDHandler.cpp).
58 * 2. Main: Public interface for doing Drag and Drop. Also manage the IProgress
[49891]59 * interfaces for blocking the caller by showing a progress dialog (see
60 * this file).
[42261]61 * 3. HGCM service: Handle all messages from the host to the guest at once and
[49891]62 * encapsulate the internal communication details (see dndmanager.cpp and
63 * friends).
[42261]64 * 4. Guest additions: Split into the platform neutral part (see
65 * VBoxGuestR3LibDragAndDrop.cpp) and the guest OS specific parts.
66 * Receive/send message from/to the HGCM service and does all guest specific
[49891]67 * operations. Currently only X11 is supported (see draganddrop.cpp within
68 * VBoxClient).
[42261]69 *
[49891]70 * Host -> Guest:
[42261]71 * 1. There are DnD Enter, Move, Leave events which are send exactly like this
72 * to the guest. The info includes the pos, mimetypes and allowed actions.
73 * The guest has to respond with an action it would accept, so the GUI could
74 * change the cursor.
[51476]75 * 2. On drop, first a drop event is sent. If this is accepted a drop data
[42261]76 * event follows. This blocks the GUI and shows some progress indicator.
77 *
78 * Guest -> Host:
79 * 1. The GUI is asking the guest if a DnD event is pending when the user moves
80 * the cursor out of the view window. If so, this returns the mimetypes and
81 * allowed actions.
82 * (2. On every mouse move this is asked again, to make sure the DnD event is
83 * still valid.)
84 * 3. On drop the host request the data from the guest. This blocks the GUI and
85 * shows some progress indicator.
86 *
87 * Some hints:
[51476]88 * m_strSupportedFormats here in this file defines the allowed mime-types.
[42261]89 * This is necessary because we need special handling for some of the
90 * mime-types. E.g. for URI lists we need to transfer the actual dirs and
91 * files. Text EOL may to be changed. Also unknown mime-types may need special
92 * handling as well, which may lead to undefined behavior in the host/guest, if
93 * not done.
94 *
95 * Dropping of a directory, means recursively transferring _all_ the content.
96 *
[49891]97 * Directories and files are placed into the user's temporary directory on the
98 * guest (e.g. /tmp/VirtualBox Dropped Files). We can't delete them after the
[42261]99 * DnD operation, because we didn't know what the DnD target does with it. E.g.
100 * it could just be opened in place. This could lead ofc to filling up the disk
101 * within the guest. To inform the user about this, a small app could be
102 * developed which scans this directory regularly and inform the user with a
103 * tray icon hint (and maybe the possibility to clean this up instantly). The
104 * same has to be done in the G->H direction when it is implemented.
105 *
106 * Of course only regularly files are supported. Symlinks are resolved and
107 * transfered as regularly files. First we don't know if the other side support
108 * symlinks at all and second they could point to somewhere in a directory tree
109 * which not exists on the other side.
110 *
111 * The code tries to preserve the file modes of the transfered dirs/files. This
112 * is useful (and maybe necessary) for two things:
113 * 1. If a file is executable, it should be also after the transfer, so the
114 * user can just execute it, without manually tweaking the modes first.
115 * 2. If a dir/file is not accessible by group/others in the host, it shouldn't
116 * be in the guest.
117 * In any case, the user mode is always set to rwx (so that we can access it
118 * ourself, in e.g. for a cleanup case after cancel).
119 *
120 * Cancel is supported in both directions and cleans up all previous steps
121 * (thats is: deleting already transfered dirs/files).
122 *
123 * In general I propose the following changes in the VBox HGCM infrastructure
124 * for the future:
125 * - Currently it isn't really possible to send messages to the guest from the
126 * host. The host informs the guest just that there is something, the guest
127 * than has to ask which message and depending on that send the appropriate
128 * message to the host, which is filled with the right data.
129 * - There is no generic interface for sending bigger memory blocks to/from the
130 * guest. This is now done here, but I guess was also necessary for e.g.
131 * guest execution. So something generic which brake this up into smaller
132 * blocks and send it would be nice (with all the error handling and such
133 * ofc).
134 * - I developed a "protocol" for the DnD communication here. So the host and
135 * the guest have always to match in the revision. This is ofc bad, because
136 * the additions could be outdated easily. So some generic protocol number
137 * support in HGCM for asking the host and the guest of the support version,
138 * would be nice. Ofc at least the host should be able to talk to the guest,
139 * even when the version is below the host one.
140 * All this stuff would be useful for the current services, but also for future
141 * onces.
142 *
[51476]143 ** @todo
144 * - ESC doesn't really work (on Windows guests it's already implemented)
[42261]145 * ... in any case it seems a little bit difficult to handle from the Qt
146 * side. Maybe also a host specific implementation becomes necessary ...
147 * this would be really worst ofc.
[51476]148 * - Add support for more mime-types (especially images, csv)
149 * - Test unusual behavior:
[42261]150 * - DnD service crash in the guest during a DnD op (e.g. crash of VBoxClient or X11)
[51476]151 * - Not expected order of the events between HGCM and the guest
[42261]152 * - Security considerations: We transfer a lot of memory between the guest and
153 * the host and even allow the creation of dirs/files. Maybe there should be
154 * limits introduced to preventing DOS attacks or filling up all the memory
155 * (both in the host and the guest).
156 */
157
[55512]158GuestDnDCallbackEvent::~GuestDnDCallbackEvent(void)
159{
160 if (NIL_RTSEMEVENT != mSemEvent)
161 RTSemEventDestroy(mSemEvent);
162}
163
164int GuestDnDCallbackEvent::Reset(void)
165{
166 int rc = VINF_SUCCESS;
167
168 if (NIL_RTSEMEVENT == mSemEvent)
169 rc = RTSemEventCreate(&mSemEvent);
170
171 mRc = VINF_SUCCESS;
172 return rc;
173}
174
[55963]175int GuestDnDCallbackEvent::Notify(int rc /* = VINF_SUCCESS */)
[55512]176{
177 mRc = rc;
178 return RTSemEventSignal(mSemEvent);
179}
180
181int GuestDnDCallbackEvent::Wait(RTMSINTERVAL msTimeout)
182{
183 return RTSemEventWait(mSemEvent, msTimeout);
184}
185
186///////////////////////////////////////////////////////////////////////////////
187
[51476]188GuestDnDResponse::GuestDnDResponse(const ComObjPtr<Guest>& pGuest)
189 : m_EventSem(NIL_RTSEMEVENT)
190 , m_defAction(0)
191 , m_allActions(0)
[58212]192 , m_pParent(pGuest)
[42261]193{
194 int rc = RTSemEventCreate(&m_EventSem);
[55422]195 if (RT_FAILURE(rc))
196 throw rc;
[42261]197}
198
[51476]199GuestDnDResponse::~GuestDnDResponse(void)
[42261]200{
[50265]201 reset();
[50734]202
[42261]203 int rc = RTSemEventDestroy(m_EventSem);
204 AssertRC(rc);
205}
206
[55422]207int GuestDnDResponse::notifyAboutGuestResponse(void) const
[50609]208{
209 return RTSemEventSignal(m_EventSem);
210}
211
[51476]212void GuestDnDResponse::reset(void)
[42261]213{
[50734]214 LogFlowThisFuncEnter();
215
[55512]216 m_defAction = 0;
[50734]217 m_allActions = 0;
218
[57221]219 m_lstFormats.clear();
[42261]220}
221
[51476]222HRESULT GuestDnDResponse::resetProgress(const ComObjPtr<Guest>& pParent)
[42261]223{
[58212]224 m_pProgress.setNull();
[55549]225
[58212]226 HRESULT hr = m_pProgress.createObject();
[55549]227 if (SUCCEEDED(hr))
[42261]228 {
[58212]229 hr = m_pProgress->init(static_cast<IGuest *>(pParent),
230 Bstr(pParent->tr("Dropping data")).raw(),
231 TRUE /* aCancelable */);
[42261]232 }
[55549]233
234 return hr;
[42261]235}
236
[55422]237bool GuestDnDResponse::isProgressCanceled(void) const
238{
239 BOOL fCanceled;
[58212]240 if (!m_pProgress.isNull())
[55422]241 {
[58212]242 HRESULT hr = m_pProgress->COMGETTER(Canceled)(&fCanceled);
[55422]243 AssertComRC(hr);
244 }
[55512]245 else
246 fCanceled = TRUE;
[55422]247
248 return RT_BOOL(fCanceled);
249}
250
251int GuestDnDResponse::setCallback(uint32_t uMsg, PFNGUESTDNDCALLBACK pfnCallback, void *pvUser /* = NULL */)
252{
[55424]253 GuestDnDCallbackMap::iterator it = m_mapCallbacks.find(uMsg);
[55422]254
255 /* Add. */
256 if (pfnCallback)
257 {
258 if (it == m_mapCallbacks.end())
259 {
260 m_mapCallbacks[uMsg] = GuestDnDCallback(pfnCallback, uMsg, pvUser);
261 return VINF_SUCCESS;
262 }
263
264 AssertMsgFailed(("Callback for message %RU32 already registered\n", uMsg));
265 return VERR_ALREADY_EXISTS;
266 }
267
268 /* Remove. */
269 if (it != m_mapCallbacks.end())
270 m_mapCallbacks.erase(it);
271
272 return VINF_SUCCESS;
273}
274
[51476]275int GuestDnDResponse::setProgress(unsigned uPercentage,
[55963]276 uint32_t uStatus,
277 int rcOp /* = VINF_SUCCESS */, const Utf8Str &strMsg /* = "" */)
[42261]278{
[63259]279 RT_NOREF(rcOp);
[55963]280 LogFlowFunc(("uStatus=%RU32, uPercentage=%RU32, rcOp=%Rrc, strMsg=%s\n",
281 uStatus, uPercentage, rcOp, strMsg.c_str()));
[49891]282
[55556]283 int rc = VINF_SUCCESS;
[58212]284 if (!m_pProgress.isNull())
[42261]285 {
286 BOOL fCompleted;
[58212]287 HRESULT hr = m_pProgress->COMGETTER(Completed)(&fCompleted);
[55422]288 AssertComRC(hr);
289
290 BOOL fCanceled;
[58212]291 hr = m_pProgress->COMGETTER(Canceled)(&fCanceled);
[55422]292 AssertComRC(hr);
293
[55512]294 LogFlowFunc(("Current: fCompleted=%RTbool, fCanceled=%RTbool\n", fCompleted, fCanceled));
[55422]295
[42261]296 if (!fCompleted)
297 {
[55422]298 switch (uStatus)
[49891]299 {
[55422]300 case DragAndDropSvc::DND_PROGRESS_ERROR:
301 {
[58212]302 hr = m_pProgress->i_notifyComplete(VBOX_E_IPRT_ERROR,
303 COM_IIDOF(IGuest),
304 m_pParent->getComponentName(), strMsg.c_str());
[55422]305 reset();
306 break;
307 }
[50561]308
[55422]309 case DragAndDropSvc::DND_PROGRESS_CANCELLED:
310 {
[58329]311 hr = m_pProgress->Cancel();
[55422]312 AssertComRC(hr);
[58330]313 hr = m_pProgress->i_notifyComplete(S_OK);
[58329]314 AssertComRC(hr);
[55422]315
316 reset();
317 break;
318 }
319
320 case DragAndDropSvc::DND_PROGRESS_RUNNING:
321 case DragAndDropSvc::DND_PROGRESS_COMPLETE:
322 {
323 if (!fCanceled)
324 {
[58212]325 hr = m_pProgress->SetCurrentOperationProgress(uPercentage);
[55422]326 AssertComRC(hr);
327 if ( uStatus == DragAndDropSvc::DND_PROGRESS_COMPLETE
328 || uPercentage >= 100)
329 {
[58212]330 hr = m_pProgress->i_notifyComplete(S_OK);
[55422]331 AssertComRC(hr);
332 }
333 }
334 break;
335 }
336
337 default:
338 break;
[42261]339 }
340 }
[55512]341
[58212]342 hr = m_pProgress->COMGETTER(Completed)(&fCompleted);
[55512]343 AssertComRC(hr);
[58212]344 hr = m_pProgress->COMGETTER(Canceled)(&fCanceled);
[55512]345 AssertComRC(hr);
346
347 LogFlowFunc(("New: fCompleted=%RTbool, fCanceled=%RTbool\n", fCompleted, fCanceled));
[42261]348 }
[49891]349
[55556]350 LogFlowFuncLeaveRC(rc);
351 return rc;
[42261]352}
353
[55422]354int GuestDnDResponse::onDispatch(uint32_t u32Function, void *pvParms, uint32_t cbParms)
[42261]355{
[55422]356 LogFlowFunc(("u32Function=%RU32, pvParms=%p, cbParms=%RU32\n", u32Function, pvParms, cbParms));
[42261]357
[55422]358 int rc = VERR_WRONG_ORDER; /* Play safe. */
359 bool fTryCallbacks = false;
[50609]360
[55422]361 switch (u32Function)
[50734]362 {
[58230]363 case DragAndDropSvc::GUEST_DND_CONNECT:
364 {
[58329]365 LogThisFunc(("Client connected\n"));
366
367 /* Nothing to do here (yet). */
[58230]368 rc = VINF_SUCCESS;
369 break;
370 }
371
[58329]372 case DragAndDropSvc::GUEST_DND_DISCONNECT:
373 {
374 LogThisFunc(("Client disconnected\n"));
375 rc = setProgress(100, DND_PROGRESS_CANCELLED, VINF_SUCCESS);
376 break;
377 }
378
[55422]379 case DragAndDropSvc::GUEST_DND_HG_ACK_OP:
[50734]380 {
[55422]381 DragAndDropSvc::PVBOXDNDCBHGACKOPDATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBHGACKOPDATA>(pvParms);
382 AssertPtr(pCBData);
383 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBHGACKOPDATA) == cbParms, VERR_INVALID_PARAMETER);
[58257]384 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_HG_ACK_OP == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
[55422]385
386 setDefAction(pCBData->uAction);
387 rc = notifyAboutGuestResponse();
388 break;
389 }
390
391 case DragAndDropSvc::GUEST_DND_HG_REQ_DATA:
392 {
393 DragAndDropSvc::PVBOXDNDCBHGREQDATADATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBHGREQDATADATA>(pvParms);
394 AssertPtr(pCBData);
395 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBHGREQDATADATA) == cbParms, VERR_INVALID_PARAMETER);
[58257]396 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_HG_REQ_DATA == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
[55422]397
[58232]398 if ( pCBData->cbFormat == 0
399 || pCBData->cbFormat > _64K /** @todo Make this configurable? */
400 || pCBData->pszFormat == NULL)
[55539]401 {
402 rc = VERR_INVALID_PARAMETER;
403 }
[58232]404 else if (!RTStrIsValidEncoding(pCBData->pszFormat))
405 {
406 rc = VERR_INVALID_PARAMETER;
407 }
[55539]408 else
409 {
[57221]410 setFormats(GuestDnD::toFormatList(pCBData->pszFormat));
[55539]411 rc = VINF_SUCCESS;
412 }
413
414 int rc2 = notifyAboutGuestResponse();
415 if (RT_SUCCESS(rc))
416 rc = rc2;
[55422]417 break;
418 }
419
420 case DragAndDropSvc::GUEST_DND_HG_EVT_PROGRESS:
421 {
422 DragAndDropSvc::PVBOXDNDCBHGEVTPROGRESSDATA pCBData =
423 reinterpret_cast<DragAndDropSvc::PVBOXDNDCBHGEVTPROGRESSDATA>(pvParms);
424 AssertPtr(pCBData);
425 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBHGEVTPROGRESSDATA) == cbParms, VERR_INVALID_PARAMETER);
[58257]426 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_HG_EVT_PROGRESS == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
[55422]427
428 rc = setProgress(pCBData->uPercentage, pCBData->uStatus, pCBData->rc);
[50734]429 if (RT_SUCCESS(rc))
[55422]430 rc = notifyAboutGuestResponse();
431 break;
432 }
433#ifdef VBOX_WITH_DRAG_AND_DROP_GH
434 case DragAndDropSvc::GUEST_DND_GH_ACK_PENDING:
435 {
436 DragAndDropSvc::PVBOXDNDCBGHACKPENDINGDATA pCBData =
437 reinterpret_cast<DragAndDropSvc::PVBOXDNDCBGHACKPENDINGDATA>(pvParms);
438 AssertPtr(pCBData);
439 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBGHACKPENDINGDATA) == cbParms, VERR_INVALID_PARAMETER);
[58257]440 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_GH_ACK_PENDING == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
[50734]441
[58232]442 if ( pCBData->cbFormat == 0
443 || pCBData->cbFormat > _64K /** @todo Make the maximum size configurable? */
444 || pCBData->pszFormat == NULL)
[55539]445 {
446 rc = VERR_INVALID_PARAMETER;
447 }
[58232]448 else if (!RTStrIsValidEncoding(pCBData->pszFormat))
449 {
450 rc = VERR_INVALID_PARAMETER;
451 }
[55539]452 else
453 {
[57221]454 setFormats (GuestDnD::toFormatList(pCBData->pszFormat));
[55539]455 setDefAction (pCBData->uDefAction);
456 setAllActions(pCBData->uAllActions);
[50734]457
[55539]458 rc = VINF_SUCCESS;
459 }
460
461 int rc2 = notifyAboutGuestResponse();
462 if (RT_SUCCESS(rc))
463 rc = rc2;
[55422]464 break;
[50734]465 }
[55422]466#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
467 default:
468 /* * Try if the event is covered by a registered callback. */
469 fTryCallbacks = true;
470 break;
[50734]471 }
472
[55422]473 /*
474 * Try the host's installed callbacks (if any).
475 */
476 if (fTryCallbacks)
[50734]477 {
[55422]478 GuestDnDCallbackMap::const_iterator it = m_mapCallbacks.find(u32Function);
479 if (it != m_mapCallbacks.end())
480 {
481 AssertPtr(it->second.pfnCallback);
482 rc = it->second.pfnCallback(u32Function, pvParms, cbParms, it->second.pvUser);
483 }
484 else
[57291]485 {
[57295]486 LogFlowFunc(("No callback for function %RU32 defined (%zu callbacks total)\n", u32Function, m_mapCallbacks.size()));
[57287]487 rc = VERR_NOT_SUPPORTED; /* Tell the guest. */
[57291]488 }
[50734]489 }
490
[55422]491 LogFlowFunc(("Returning rc=%Rrc\n", rc));
[50734]492 return rc;
493}
494
[55422]495HRESULT GuestDnDResponse::queryProgressTo(IProgress **ppProgress)
496{
[58212]497 return m_pProgress.queryInterfaceTo(ppProgress);
[55422]498}
499
500int GuestDnDResponse::waitForGuestResponse(RTMSINTERVAL msTimeout /*= 500 */) const
501{
502 int rc = RTSemEventWait(m_EventSem, msTimeout);
503#ifdef DEBUG_andy
504 LogFlowFunc(("msTimeout=%RU32, rc=%Rrc\n", msTimeout, rc));
505#endif
506 return rc;
507}
508
[50609]509///////////////////////////////////////////////////////////////////////////////
510
[51476]511GuestDnD* GuestDnD::s_pInstance = NULL;
512
513GuestDnD::GuestDnD(const ComObjPtr<Guest> &pGuest)
514 : m_pGuest(pGuest)
[42261]515{
[51476]516 LogFlowFuncEnter();
517
518 m_pResponse = new GuestDnDResponse(pGuest);
519
[51556]520 /* List of supported default MIME types. */
[56653]521 LogRel2(("DnD: Supported default host formats:\n"));
[51556]522 const com::Utf8Str arrEntries[] = { VBOX_DND_FORMATS_DEFAULT };
[51476]523 for (size_t i = 0; i < RT_ELEMENTS(arrEntries); i++)
[56653]524 {
[51556]525 m_strDefaultFormats.push_back(arrEntries[i]);
[56653]526 LogRel2(("DnD: \t%s\n", arrEntries[i].c_str()));
527 }
[51476]528}
529
530GuestDnD::~GuestDnD(void)
531{
532 LogFlowFuncEnter();
533
534 if (m_pResponse)
535 delete m_pResponse;
536}
537
[57221]538HRESULT GuestDnD::adjustScreenCoordinates(ULONG uScreenId, ULONG *puX, ULONG *puY) const
[51476]539{
540 /** @todo r=andy Save the current screen's shifting coordinates to speed things up.
541 * Only query for new offsets when the screen ID has changed. */
542
[42261]543 /* For multi-monitor support we need to add shift values to the coordinates
544 * (depending on the screen number). */
[52082]545 ComObjPtr<Console> pConsole = m_pGuest->i_getConsole();
[42261]546 ComPtr<IDisplay> pDisplay;
[51476]547 HRESULT hr = pConsole->COMGETTER(Display)(pDisplay.asOutParam());
[50561]548 if (FAILED(hr))
549 return hr;
[50460]550
[52064]551 ULONG dummy;
[42261]552 LONG xShift, yShift;
[52978]553 GuestMonitorStatus_T monitorStatus;
[52064]554 hr = pDisplay->GetScreenResolution(uScreenId, &dummy, &dummy, &dummy,
[52978]555 &xShift, &yShift, &monitorStatus);
[50561]556 if (FAILED(hr))
557 return hr;
[50460]558
[51476]559 if (puX)
560 *puX += xShift;
561 if (puY)
562 *puY += yShift;
[50561]563
[57221]564 LogFlowFunc(("uScreenId=%RU32, x=%RU32, y=%RU32\n", uScreenId, puX ? *puX : 0, puY ? *puY : 0));
565 return S_OK;
[42261]566}
567
[51476]568int GuestDnD::hostCall(uint32_t u32Function, uint32_t cParms, PVBOXHGCMSVCPARM paParms) const
[42261]569{
[51476]570 Assert(!m_pGuest.isNull());
[52082]571 ComObjPtr<Console> pConsole = m_pGuest->i_getConsole();
[42261]572
[51476]573 /* Forward the information to the VMM device. */
574 Assert(!pConsole.isNull());
[51612]575 VMMDev *pVMMDev = pConsole->i_getVMMDev();
[50561]576 if (!pVMMDev)
[51476]577 return VERR_COM_OBJECT_NOT_FOUND;
[42261]578
[57221]579 return pVMMDev->hgcmHostCall("VBoxDragAndDropSvc", u32Function, cParms, paParms);
[42261]580}
581
582/* static */
[55422]583DECLCALLBACK(int) GuestDnD::notifyDnDDispatcher(void *pvExtension, uint32_t u32Function,
584 void *pvParms, uint32_t cbParms)
585{
586 LogFlowFunc(("pvExtension=%p, u32Function=%RU32, pvParms=%p, cbParms=%RU32\n",
587 pvExtension, u32Function, pvParms, cbParms));
588
589 GuestDnD *pGuestDnD = reinterpret_cast<GuestDnD*>(pvExtension);
590 AssertPtrReturn(pGuestDnD, VERR_INVALID_POINTER);
591
592 /** @todo In case we need to handle multiple guest DnD responses at a time this
593 * would be the place to lookup and dispatch to those. For the moment we
594 * only have one response -- simple. */
595 GuestDnDResponse *pResp = pGuestDnD->m_pResponse;
596 if (pResp)
597 return pResp->onDispatch(u32Function, pvParms, cbParms);
598
599 return VERR_NOT_SUPPORTED;
600}
601
[57221]602/* static */
603bool GuestDnD::isFormatInFormatList(const com::Utf8Str &strFormat, const GuestDnDMIMEList &lstFormats)
604{
605 return std::find(lstFormats.begin(), lstFormats.end(), strFormat) != lstFormats.end();
606}
[55422]607
608/* static */
[57221]609GuestDnDMIMEList GuestDnD::toFormatList(const com::Utf8Str &strFormats)
[42261]610{
[57221]611 GuestDnDMIMEList lstFormats;
612 RTCList<RTCString> lstFormatsTmp = strFormats.split("\r\n");
613
614 for (size_t i = 0; i < lstFormatsTmp.size(); i++)
615 lstFormats.push_back(com::Utf8Str(lstFormatsTmp.at(i)));
616
617 return lstFormats;
618}
619
620/* static */
621com::Utf8Str GuestDnD::toFormatString(const GuestDnDMIMEList &lstFormats)
622{
[51476]623 com::Utf8Str strFormat;
[57221]624 for (size_t i = 0; i < lstFormats.size(); i++)
[42261]625 {
[57221]626 const com::Utf8Str &f = lstFormats.at(i);
627 strFormat += f + "\r\n";
[42261]628 }
[51476]629
[42261]630 return strFormat;
631}
632
633/* static */
[57221]634GuestDnDMIMEList GuestDnD::toFilteredFormatList(const GuestDnDMIMEList &lstFormatsSupported, const GuestDnDMIMEList &lstFormatsWanted)
[42261]635{
[57221]636 GuestDnDMIMEList lstFormats;
637
638 for (size_t i = 0; i < lstFormatsWanted.size(); i++)
639 {
[59842]640 /* Only keep supported format types. */
[57221]641 if (std::find(lstFormatsSupported.begin(),
642 lstFormatsSupported.end(), lstFormatsWanted.at(i)) != lstFormatsSupported.end())
643 {
644 lstFormats.push_back(lstFormatsWanted[i]);
645 }
646 }
647
648 return lstFormats;
649}
650
651/* static */
652GuestDnDMIMEList GuestDnD::toFilteredFormatList(const GuestDnDMIMEList &lstFormatsSupported, const com::Utf8Str &strFormatsWanted)
653{
654 GuestDnDMIMEList lstFmt;
655
656 RTCList<RTCString> lstFormats = strFormatsWanted.split("\r\n");
[42261]657 size_t i = 0;
[51476]658 while (i < lstFormats.size())
[42261]659 {
660 /* Only keep allowed format types. */
[57221]661 if (std::find(lstFormatsSupported.begin(),
662 lstFormatsSupported.end(), lstFormats.at(i)) != lstFormatsSupported.end())
663 {
664 lstFmt.push_back(lstFormats[i]);
665 }
666 i++;
[42261]667 }
[51476]668
[57221]669 return lstFmt;
[42261]670}
671
672/* static */
[51476]673uint32_t GuestDnD::toHGCMAction(DnDAction_T enmAction)
[42261]674{
[51476]675 uint32_t uAction = DND_IGNORE_ACTION;
676 switch (enmAction)
[42261]677 {
[51476]678 case DnDAction_Copy:
679 uAction = DND_COPY_ACTION;
680 break;
681 case DnDAction_Move:
682 uAction = DND_MOVE_ACTION;
683 break;
684 case DnDAction_Link:
685 /* For now it doesn't seems useful to allow a link
686 action between host & guest. Later? */
687 case DnDAction_Ignore:
688 /* Ignored. */
689 break;
690 default:
691 AssertMsgFailed(("Action %RU32 not recognized!\n", enmAction));
692 break;
[42261]693 }
[51476]694
695 return uAction;
[42261]696}
697
698/* static */
[56734]699void GuestDnD::toHGCMActions(DnDAction_T enmDefAction,
700 uint32_t *puDefAction,
[51476]701 const std::vector<DnDAction_T> vecAllowedActions,
[56734]702 uint32_t *puAllowedActions)
[42261]703{
[56734]704 uint32_t uAllowedActions = DND_IGNORE_ACTION;
705 uint32_t uDefAction = toHGCMAction(enmDefAction);
706
707 if (!vecAllowedActions.empty())
[51476]708 {
709 /* First convert the allowed actions to a bit array. */
[56734]710 for (size_t i = 0; i < vecAllowedActions.size(); i++)
711 uAllowedActions |= toHGCMAction(vecAllowedActions[i]);
[42261]712
[56734]713 /*
714 * If no default action is set (ignoring), try one of the
715 * set allowed actions, preferring copy, move (in that order).
716 */
717 if (isDnDIgnoreAction(uDefAction))
[51476]718 {
[56734]719 if (hasDnDCopyAction(uAllowedActions))
720 uDefAction = DND_COPY_ACTION;
721 else if (hasDnDMoveAction(uAllowedActions))
722 uDefAction = DND_MOVE_ACTION;
[51476]723 }
[42261]724 }
[56734]725
726 if (puDefAction)
727 *puDefAction = uDefAction;
728 if (puAllowedActions)
729 *puAllowedActions = uAllowedActions;
[42261]730}
731
732/* static */
[51476]733DnDAction_T GuestDnD::toMainAction(uint32_t uAction)
[42261]734{
[50508]735 /* For now it doesn't seems useful to allow a
736 * link action between host & guest. Maybe later! */
[72980]737 return isDnDCopyAction(uAction) ? DnDAction_Copy
738 : isDnDMoveAction(uAction) ? DnDAction_Move
739 : DnDAction_Ignore;
[42261]740}
741
742/* static */
[57221]743std::vector<DnDAction_T> GuestDnD::toMainActions(uint32_t uActions)
[42261]744{
[57221]745 std::vector<DnDAction_T> vecActions;
746
[50508]747 /* For now it doesn't seems useful to allow a
748 * link action between host & guest. Maybe later! */
[51476]749 RTCList<DnDAction_T> lstActions;
[42261]750 if (hasDnDCopyAction(uActions))
[51476]751 lstActions.append(DnDAction_Copy);
[42261]752 if (hasDnDMoveAction(uActions))
[51476]753 lstActions.append(DnDAction_Move);
[42261]754
[51476]755 for (size_t i = 0; i < lstActions.size(); ++i)
756 vecActions.push_back(lstActions.at(i));
[57221]757
758 return vecActions;
[42261]759}
760
[51556]761///////////////////////////////////////////////////////////////////////////////
762
763GuestDnDBase::GuestDnDBase(void)
764{
[58212]765 /* Initialize public attributes. */
766 m_lstFmtSupported = GuestDnDInst()->defaultFormats();
[55549]767
[58212]768 /* Initialzie private stuff. */
769 mDataBase.m_cTransfersPending = 0;
[58230]770 mDataBase.m_uProtocolVersion = 0;
[51556]771}
772
[55422]773HRESULT GuestDnDBase::i_isFormatSupported(const com::Utf8Str &aFormat, BOOL *aSupported)
[51556]774{
[57221]775 *aSupported = std::find(m_lstFmtSupported.begin(),
776 m_lstFmtSupported.end(), aFormat) != m_lstFmtSupported.end()
[51556]777 ? TRUE : FALSE;
778 return S_OK;
779}
780
[57221]781HRESULT GuestDnDBase::i_getFormats(GuestDnDMIMEList &aFormats)
[51556]782{
[57221]783 aFormats = m_lstFmtSupported;
[51556]784
785 return S_OK;
786}
787
[57221]788HRESULT GuestDnDBase::i_addFormats(const GuestDnDMIMEList &aFormats)
[51556]789{
790 for (size_t i = 0; i < aFormats.size(); ++i)
791 {
792 Utf8Str strFormat = aFormats.at(i);
[57221]793 if (std::find(m_lstFmtSupported.begin(),
794 m_lstFmtSupported.end(), strFormat) == m_lstFmtSupported.end())
[51556]795 {
[57221]796 m_lstFmtSupported.push_back(strFormat);
[51556]797 }
798 }
799
800 return S_OK;
801}
802
[57221]803HRESULT GuestDnDBase::i_removeFormats(const GuestDnDMIMEList &aFormats)
[51556]804{
805 for (size_t i = 0; i < aFormats.size(); ++i)
806 {
807 Utf8Str strFormat = aFormats.at(i);
[57221]808 GuestDnDMIMEList::iterator itFormat = std::find(m_lstFmtSupported.begin(),
809 m_lstFmtSupported.end(), strFormat);
810 if (itFormat != m_lstFmtSupported.end())
811 m_lstFmtSupported.erase(itFormat);
[51556]812 }
813
814 return S_OK;
815}
816
[55422]817HRESULT GuestDnDBase::i_getProtocolVersion(ULONG *puVersion)
818{
819 int rc = getProtocolVersion((uint32_t *)puVersion);
820 return RT_SUCCESS(rc) ? S_OK : E_FAIL;
821}
822
[58230]823/**
824 * Tries to guess the DnD protocol version to use on the guest, based on the
825 * installed Guest Additions version + revision.
826 *
827 * If unable to retrieve the protocol version, VERR_NOT_FOUND is returned along
828 * with protocol version 1.
829 *
830 * @return IPRT status code.
831 * @param puProto Where to store the protocol version.
832 */
[58212]833int GuestDnDBase::getProtocolVersion(uint32_t *puProto)
[55422]834{
[58212]835 AssertPtrReturn(puProto, VERR_INVALID_POINTER);
[55422]836
837 int rc;
838
[63158]839 uint32_t uProto = 0;
840 uint32_t uVerAdditions;
841 uint32_t uRevAdditions;
[55422]842 if ( m_pGuest
[58230]843 && (uVerAdditions = m_pGuest->i_getAdditionsVersion()) > 0
844 && (uRevAdditions = m_pGuest->i_getAdditionsRevision()) > 0)
[55422]845 {
[63158]846#if 0 && defined(DEBUG)
[55422]847 /* Hardcode the to-used protocol version; nice for testing side effects. */
[63158]848 if (true)
849 uProto = 3;
850 else
[58230]851#endif
[63158]852 if (uVerAdditions >= VBOX_FULL_VERSION_MAKE(5, 0, 0))
[58212]853 {
[63158]854/** @todo
855 * r=bird: This is just too bad for anyone using an OSE additions build...
856 */
857 if (uRevAdditions >= 103344) /* Since r103344: Protocol v3. */
858 uProto = 3;
859 else
860 uProto = 2; /* VBox 5.0.0 - 5.0.8: Protocol v2. */
861 }
862 /* else: uProto: 0 */
[58230]863
[63158]864 LogFlowFunc(("uVerAdditions=%RU32 (%RU32.%RU32.%RU32), r%RU32\n",
865 uVerAdditions, VBOX_FULL_VERSION_GET_MAJOR(uVerAdditions), VBOX_FULL_VERSION_GET_MINOR(uVerAdditions),
866 VBOX_FULL_VERSION_GET_BUILD(uVerAdditions), uRevAdditions));
867 rc = VINF_SUCCESS;
[55422]868 }
869 else
870 {
[58212]871 uProto = 1; /* Fallback. */
[55422]872 rc = VERR_NOT_FOUND;
873 }
874
[58230]875 LogRel2(("DnD: Guest is using protocol v%RU32, rc=%Rrc\n", uProto, rc));
[55422]876
[58212]877 *puProto = uProto;
[55422]878 return rc;
879}
[55571]880
881int GuestDnDBase::msgQueueAdd(GuestDnDMsg *pMsg)
882{
[58212]883 mDataBase.m_lstMsgOut.push_back(pMsg);
[55571]884 return VINF_SUCCESS;
885}
886
887GuestDnDMsg *GuestDnDBase::msgQueueGetNext(void)
888{
[58212]889 if (mDataBase.m_lstMsgOut.empty())
[55571]890 return NULL;
[58212]891 return mDataBase.m_lstMsgOut.front();
[55571]892}
893
894void GuestDnDBase::msgQueueRemoveNext(void)
895{
[58212]896 if (!mDataBase.m_lstMsgOut.empty())
[55571]897 {
[58212]898 GuestDnDMsg *pMsg = mDataBase.m_lstMsgOut.front();
[55571]899 if (pMsg)
900 delete pMsg;
[58212]901 mDataBase.m_lstMsgOut.pop_front();
[55571]902 }
903}
904
905void GuestDnDBase::msgQueueClear(void)
906{
[58212]907 LogFlowFunc(("cMsg=%zu\n", mDataBase.m_lstMsgOut.size()));
908
909 GuestDnDMsgList::iterator itMsg = mDataBase.m_lstMsgOut.begin();
910 while (itMsg != mDataBase.m_lstMsgOut.end())
[55571]911 {
[58212]912 GuestDnDMsg *pMsg = *itMsg;
913 if (pMsg)
914 delete pMsg;
915
916 itMsg++;
[55571]917 }
918
[58212]919 mDataBase.m_lstMsgOut.clear();
[55571]920}
921
922int GuestDnDBase::sendCancel(void)
923{
[59842]924 int rc = GuestDnDInst()->hostCall(HOST_DND_HG_EVT_CANCEL,
925 0 /* cParms */, NULL /* paParms */);
[55571]926
[56494]927 LogFlowFunc(("Generated cancelling request, rc=%Rrc\n", rc));
[55640]928 return rc;
[55571]929}
930
[58212]931int GuestDnDBase::updateProgress(GuestDnDData *pData, GuestDnDResponse *pResp,
932 uint32_t cbDataAdd /* = 0 */)
933{
934 AssertPtrReturn(pData, VERR_INVALID_POINTER);
935 AssertPtrReturn(pResp, VERR_INVALID_POINTER);
936 /* cbDataAdd is optional. */
937
938 LogFlowFunc(("cbTotal=%RU64, cbProcessed=%RU64, cbRemaining=%RU64, cbDataAdd=%RU32\n",
[58329]939 pData->getTotal(), pData->getProcessed(), pData->getRemaining(), cbDataAdd));
[58212]940
941 if (!pResp)
942 return VINF_SUCCESS;
943
944 if (cbDataAdd)
945 pData->addProcessed(cbDataAdd);
946
947 int rc = pResp->setProgress(pData->getPercentComplete(),
948 pData->isComplete()
949 ? DND_PROGRESS_COMPLETE
950 : DND_PROGRESS_RUNNING);
951 LogFlowFuncLeaveRC(rc);
952 return rc;
953}
954
[55571]955/** @todo GuestDnDResponse *pResp needs to go. */
[58212]956int GuestDnDBase::waitForEvent(GuestDnDCallbackEvent *pEvent, GuestDnDResponse *pResp, RTMSINTERVAL msTimeout)
[55571]957{
[58212]958 AssertPtrReturn(pEvent, VERR_INVALID_POINTER);
959 AssertPtrReturn(pResp, VERR_INVALID_POINTER);
960
[55571]961 int rc;
962
[59842]963 LogFlowFunc(("msTimeout=%RU32\n", msTimeout));
964
[55571]965 uint64_t tsStart = RTTimeMilliTS();
966 do
967 {
968 /*
969 * Wait until our desired callback triggered the
970 * wait event. As we don't want to block if the guest does not
[55963]971 * respond, do busy waiting here.
[55571]972 */
[58212]973 rc = pEvent->Wait(500 /* ms */);
[55571]974 if (RT_SUCCESS(rc))
975 {
[58212]976 rc = pEvent->Result();
[55571]977 LogFlowFunc(("Callback done, result is %Rrc\n", rc));
978 break;
979 }
980 else if (rc == VERR_TIMEOUT) /* Continue waiting. */
[56494]981 rc = VINF_SUCCESS;
[55571]982
983 if ( msTimeout != RT_INDEFINITE_WAIT
984 && RTTimeMilliTS() - tsStart > msTimeout)
985 {
986 rc = VERR_TIMEOUT;
[56494]987 LogRel2(("DnD: Error: Guest did not respond within time\n"));
[55571]988 }
[55963]989 else if (pResp->isProgressCanceled()) /** @todo GuestDnDResponse *pResp needs to go. */
[55571]990 {
[56494]991 LogRel2(("DnD: Operation was canceled by user\n"));
[55571]992 rc = VERR_CANCELLED;
993 }
994
995 } while (RT_SUCCESS(rc));
996
997 LogFlowFuncLeaveRC(rc);
998 return rc;
999}
[42261]1000#endif /* VBOX_WITH_DRAG_AND_DROP */
1001
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use