VirtualBox

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

Last change on this file since 62823 was 59842, checked in by vboxsync, 9 years ago

DnD/Main: Fixes for raw data transfers and cancellation logic.

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