VirtualBox

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

Last change on this file since 64664 was 63259, checked in by vboxsync, 8 years ago

Main: warnings

  • 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 63259 2016-08-10 12:37:42Z 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 RT_NOREF(rcOp);
284 LogFlowFunc(("uStatus=%RU32, uPercentage=%RU32, rcOp=%Rrc, strMsg=%s\n",
285 uStatus, uPercentage, rcOp, strMsg.c_str()));
286
287 int rc = VINF_SUCCESS;
288 if (!m_pProgress.isNull())
289 {
290 BOOL fCompleted;
291 HRESULT hr = m_pProgress->COMGETTER(Completed)(&fCompleted);
292 AssertComRC(hr);
293
294 BOOL fCanceled;
295 hr = m_pProgress->COMGETTER(Canceled)(&fCanceled);
296 AssertComRC(hr);
297
298 LogFlowFunc(("Current: fCompleted=%RTbool, fCanceled=%RTbool\n", fCompleted, fCanceled));
299
300 if (!fCompleted)
301 {
302 switch (uStatus)
303 {
304 case DragAndDropSvc::DND_PROGRESS_ERROR:
305 {
306 hr = m_pProgress->i_notifyComplete(VBOX_E_IPRT_ERROR,
307 COM_IIDOF(IGuest),
308 m_pParent->getComponentName(), strMsg.c_str());
309 reset();
310 break;
311 }
312
313 case DragAndDropSvc::DND_PROGRESS_CANCELLED:
314 {
315 hr = m_pProgress->Cancel();
316 AssertComRC(hr);
317 hr = m_pProgress->i_notifyComplete(S_OK);
318 AssertComRC(hr);
319
320 reset();
321 break;
322 }
323
324 case DragAndDropSvc::DND_PROGRESS_RUNNING:
325 case DragAndDropSvc::DND_PROGRESS_COMPLETE:
326 {
327 if (!fCanceled)
328 {
329 hr = m_pProgress->SetCurrentOperationProgress(uPercentage);
330 AssertComRC(hr);
331 if ( uStatus == DragAndDropSvc::DND_PROGRESS_COMPLETE
332 || uPercentage >= 100)
333 {
334 hr = m_pProgress->i_notifyComplete(S_OK);
335 AssertComRC(hr);
336 }
337 }
338 break;
339 }
340
341 default:
342 break;
343 }
344 }
345
346 hr = m_pProgress->COMGETTER(Completed)(&fCompleted);
347 AssertComRC(hr);
348 hr = m_pProgress->COMGETTER(Canceled)(&fCanceled);
349 AssertComRC(hr);
350
351 LogFlowFunc(("New: fCompleted=%RTbool, fCanceled=%RTbool\n", fCompleted, fCanceled));
352 }
353
354 LogFlowFuncLeaveRC(rc);
355 return rc;
356}
357
358int GuestDnDResponse::onDispatch(uint32_t u32Function, void *pvParms, uint32_t cbParms)
359{
360 LogFlowFunc(("u32Function=%RU32, pvParms=%p, cbParms=%RU32\n", u32Function, pvParms, cbParms));
361
362 int rc = VERR_WRONG_ORDER; /* Play safe. */
363 bool fTryCallbacks = false;
364
365 switch (u32Function)
366 {
367 case DragAndDropSvc::GUEST_DND_CONNECT:
368 {
369 LogThisFunc(("Client connected\n"));
370
371 /* Nothing to do here (yet). */
372 rc = VINF_SUCCESS;
373 break;
374 }
375
376 case DragAndDropSvc::GUEST_DND_DISCONNECT:
377 {
378 LogThisFunc(("Client disconnected\n"));
379 rc = setProgress(100, DND_PROGRESS_CANCELLED, VINF_SUCCESS);
380 break;
381 }
382
383 case DragAndDropSvc::GUEST_DND_HG_ACK_OP:
384 {
385 DragAndDropSvc::PVBOXDNDCBHGACKOPDATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBHGACKOPDATA>(pvParms);
386 AssertPtr(pCBData);
387 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBHGACKOPDATA) == cbParms, VERR_INVALID_PARAMETER);
388 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_HG_ACK_OP == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
389
390 setDefAction(pCBData->uAction);
391 rc = notifyAboutGuestResponse();
392 break;
393 }
394
395 case DragAndDropSvc::GUEST_DND_HG_REQ_DATA:
396 {
397 DragAndDropSvc::PVBOXDNDCBHGREQDATADATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBHGREQDATADATA>(pvParms);
398 AssertPtr(pCBData);
399 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBHGREQDATADATA) == cbParms, VERR_INVALID_PARAMETER);
400 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_HG_REQ_DATA == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
401
402 if ( pCBData->cbFormat == 0
403 || pCBData->cbFormat > _64K /** @todo Make this configurable? */
404 || pCBData->pszFormat == NULL)
405 {
406 rc = VERR_INVALID_PARAMETER;
407 }
408 else if (!RTStrIsValidEncoding(pCBData->pszFormat))
409 {
410 rc = VERR_INVALID_PARAMETER;
411 }
412 else
413 {
414 setFormats(GuestDnD::toFormatList(pCBData->pszFormat));
415 rc = VINF_SUCCESS;
416 }
417
418 int rc2 = notifyAboutGuestResponse();
419 if (RT_SUCCESS(rc))
420 rc = rc2;
421 break;
422 }
423
424 case DragAndDropSvc::GUEST_DND_HG_EVT_PROGRESS:
425 {
426 DragAndDropSvc::PVBOXDNDCBHGEVTPROGRESSDATA pCBData =
427 reinterpret_cast<DragAndDropSvc::PVBOXDNDCBHGEVTPROGRESSDATA>(pvParms);
428 AssertPtr(pCBData);
429 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBHGEVTPROGRESSDATA) == cbParms, VERR_INVALID_PARAMETER);
430 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_HG_EVT_PROGRESS == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
431
432 rc = setProgress(pCBData->uPercentage, pCBData->uStatus, pCBData->rc);
433 if (RT_SUCCESS(rc))
434 rc = notifyAboutGuestResponse();
435 break;
436 }
437#ifdef VBOX_WITH_DRAG_AND_DROP_GH
438 case DragAndDropSvc::GUEST_DND_GH_ACK_PENDING:
439 {
440 DragAndDropSvc::PVBOXDNDCBGHACKPENDINGDATA pCBData =
441 reinterpret_cast<DragAndDropSvc::PVBOXDNDCBGHACKPENDINGDATA>(pvParms);
442 AssertPtr(pCBData);
443 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBGHACKPENDINGDATA) == cbParms, VERR_INVALID_PARAMETER);
444 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_GH_ACK_PENDING == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
445
446 if ( pCBData->cbFormat == 0
447 || pCBData->cbFormat > _64K /** @todo Make the maximum size configurable? */
448 || pCBData->pszFormat == NULL)
449 {
450 rc = VERR_INVALID_PARAMETER;
451 }
452 else if (!RTStrIsValidEncoding(pCBData->pszFormat))
453 {
454 rc = VERR_INVALID_PARAMETER;
455 }
456 else
457 {
458 setFormats (GuestDnD::toFormatList(pCBData->pszFormat));
459 setDefAction (pCBData->uDefAction);
460 setAllActions(pCBData->uAllActions);
461
462 rc = VINF_SUCCESS;
463 }
464
465 int rc2 = notifyAboutGuestResponse();
466 if (RT_SUCCESS(rc))
467 rc = rc2;
468 break;
469 }
470#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
471 default:
472 /* * Try if the event is covered by a registered callback. */
473 fTryCallbacks = true;
474 break;
475 }
476
477 /*
478 * Try the host's installed callbacks (if any).
479 */
480 if (fTryCallbacks)
481 {
482 GuestDnDCallbackMap::const_iterator it = m_mapCallbacks.find(u32Function);
483 if (it != m_mapCallbacks.end())
484 {
485 AssertPtr(it->second.pfnCallback);
486 rc = it->second.pfnCallback(u32Function, pvParms, cbParms, it->second.pvUser);
487 }
488 else
489 {
490 LogFlowFunc(("No callback for function %RU32 defined (%zu callbacks total)\n", u32Function, m_mapCallbacks.size()));
491 rc = VERR_NOT_SUPPORTED; /* Tell the guest. */
492 }
493 }
494
495 LogFlowFunc(("Returning rc=%Rrc\n", rc));
496 return rc;
497}
498
499HRESULT GuestDnDResponse::queryProgressTo(IProgress **ppProgress)
500{
501 return m_pProgress.queryInterfaceTo(ppProgress);
502}
503
504int GuestDnDResponse::waitForGuestResponse(RTMSINTERVAL msTimeout /*= 500 */) const
505{
506 int rc = RTSemEventWait(m_EventSem, msTimeout);
507#ifdef DEBUG_andy
508 LogFlowFunc(("msTimeout=%RU32, rc=%Rrc\n", msTimeout, rc));
509#endif
510 return rc;
511}
512
513///////////////////////////////////////////////////////////////////////////////
514
515GuestDnD* GuestDnD::s_pInstance = NULL;
516
517GuestDnD::GuestDnD(const ComObjPtr<Guest> &pGuest)
518 : m_pGuest(pGuest)
519{
520 LogFlowFuncEnter();
521
522 m_pResponse = new GuestDnDResponse(pGuest);
523
524 /* List of supported default MIME types. */
525 LogRel2(("DnD: Supported default host formats:\n"));
526 const com::Utf8Str arrEntries[] = { VBOX_DND_FORMATS_DEFAULT };
527 for (size_t i = 0; i < RT_ELEMENTS(arrEntries); i++)
528 {
529 m_strDefaultFormats.push_back(arrEntries[i]);
530 LogRel2(("DnD: \t%s\n", arrEntries[i].c_str()));
531 }
532}
533
534GuestDnD::~GuestDnD(void)
535{
536 LogFlowFuncEnter();
537
538 if (m_pResponse)
539 delete m_pResponse;
540}
541
542HRESULT GuestDnD::adjustScreenCoordinates(ULONG uScreenId, ULONG *puX, ULONG *puY) const
543{
544 /** @todo r=andy Save the current screen's shifting coordinates to speed things up.
545 * Only query for new offsets when the screen ID has changed. */
546
547 /* For multi-monitor support we need to add shift values to the coordinates
548 * (depending on the screen number). */
549 ComObjPtr<Console> pConsole = m_pGuest->i_getConsole();
550 ComPtr<IDisplay> pDisplay;
551 HRESULT hr = pConsole->COMGETTER(Display)(pDisplay.asOutParam());
552 if (FAILED(hr))
553 return hr;
554
555 ULONG dummy;
556 LONG xShift, yShift;
557 GuestMonitorStatus_T monitorStatus;
558 hr = pDisplay->GetScreenResolution(uScreenId, &dummy, &dummy, &dummy,
559 &xShift, &yShift, &monitorStatus);
560 if (FAILED(hr))
561 return hr;
562
563 if (puX)
564 *puX += xShift;
565 if (puY)
566 *puY += yShift;
567
568 LogFlowFunc(("uScreenId=%RU32, x=%RU32, y=%RU32\n", uScreenId, puX ? *puX : 0, puY ? *puY : 0));
569 return S_OK;
570}
571
572int GuestDnD::hostCall(uint32_t u32Function, uint32_t cParms, PVBOXHGCMSVCPARM paParms) const
573{
574 Assert(!m_pGuest.isNull());
575 ComObjPtr<Console> pConsole = m_pGuest->i_getConsole();
576
577 /* Forward the information to the VMM device. */
578 Assert(!pConsole.isNull());
579 VMMDev *pVMMDev = pConsole->i_getVMMDev();
580 if (!pVMMDev)
581 return VERR_COM_OBJECT_NOT_FOUND;
582
583 return pVMMDev->hgcmHostCall("VBoxDragAndDropSvc", u32Function, cParms, paParms);
584}
585
586/* static */
587DECLCALLBACK(int) GuestDnD::notifyDnDDispatcher(void *pvExtension, uint32_t u32Function,
588 void *pvParms, uint32_t cbParms)
589{
590 LogFlowFunc(("pvExtension=%p, u32Function=%RU32, pvParms=%p, cbParms=%RU32\n",
591 pvExtension, u32Function, pvParms, cbParms));
592
593 GuestDnD *pGuestDnD = reinterpret_cast<GuestDnD*>(pvExtension);
594 AssertPtrReturn(pGuestDnD, VERR_INVALID_POINTER);
595
596 /** @todo In case we need to handle multiple guest DnD responses at a time this
597 * would be the place to lookup and dispatch to those. For the moment we
598 * only have one response -- simple. */
599 GuestDnDResponse *pResp = pGuestDnD->m_pResponse;
600 if (pResp)
601 return pResp->onDispatch(u32Function, pvParms, cbParms);
602
603 return VERR_NOT_SUPPORTED;
604}
605
606/* static */
607bool GuestDnD::isFormatInFormatList(const com::Utf8Str &strFormat, const GuestDnDMIMEList &lstFormats)
608{
609 return std::find(lstFormats.begin(), lstFormats.end(), strFormat) != lstFormats.end();
610}
611
612/* static */
613GuestDnDMIMEList GuestDnD::toFormatList(const com::Utf8Str &strFormats)
614{
615 GuestDnDMIMEList lstFormats;
616 RTCList<RTCString> lstFormatsTmp = strFormats.split("\r\n");
617
618 for (size_t i = 0; i < lstFormatsTmp.size(); i++)
619 lstFormats.push_back(com::Utf8Str(lstFormatsTmp.at(i)));
620
621 return lstFormats;
622}
623
624/* static */
625com::Utf8Str GuestDnD::toFormatString(const GuestDnDMIMEList &lstFormats)
626{
627 com::Utf8Str strFormat;
628 for (size_t i = 0; i < lstFormats.size(); i++)
629 {
630 const com::Utf8Str &f = lstFormats.at(i);
631 strFormat += f + "\r\n";
632 }
633
634 return strFormat;
635}
636
637/* static */
638GuestDnDMIMEList GuestDnD::toFilteredFormatList(const GuestDnDMIMEList &lstFormatsSupported, const GuestDnDMIMEList &lstFormatsWanted)
639{
640 GuestDnDMIMEList lstFormats;
641
642 for (size_t i = 0; i < lstFormatsWanted.size(); i++)
643 {
644 /* Only keep supported format types. */
645 if (std::find(lstFormatsSupported.begin(),
646 lstFormatsSupported.end(), lstFormatsWanted.at(i)) != lstFormatsSupported.end())
647 {
648 lstFormats.push_back(lstFormatsWanted[i]);
649 }
650 }
651
652 return lstFormats;
653}
654
655/* static */
656GuestDnDMIMEList GuestDnD::toFilteredFormatList(const GuestDnDMIMEList &lstFormatsSupported, const com::Utf8Str &strFormatsWanted)
657{
658 GuestDnDMIMEList lstFmt;
659
660 RTCList<RTCString> lstFormats = strFormatsWanted.split("\r\n");
661 size_t i = 0;
662 while (i < lstFormats.size())
663 {
664 /* Only keep allowed format types. */
665 if (std::find(lstFormatsSupported.begin(),
666 lstFormatsSupported.end(), lstFormats.at(i)) != lstFormatsSupported.end())
667 {
668 lstFmt.push_back(lstFormats[i]);
669 }
670 i++;
671 }
672
673 return lstFmt;
674}
675
676/* static */
677uint32_t GuestDnD::toHGCMAction(DnDAction_T enmAction)
678{
679 uint32_t uAction = DND_IGNORE_ACTION;
680 switch (enmAction)
681 {
682 case DnDAction_Copy:
683 uAction = DND_COPY_ACTION;
684 break;
685 case DnDAction_Move:
686 uAction = DND_MOVE_ACTION;
687 break;
688 case DnDAction_Link:
689 /* For now it doesn't seems useful to allow a link
690 action between host & guest. Later? */
691 case DnDAction_Ignore:
692 /* Ignored. */
693 break;
694 default:
695 AssertMsgFailed(("Action %RU32 not recognized!\n", enmAction));
696 break;
697 }
698
699 return uAction;
700}
701
702/* static */
703void GuestDnD::toHGCMActions(DnDAction_T enmDefAction,
704 uint32_t *puDefAction,
705 const std::vector<DnDAction_T> vecAllowedActions,
706 uint32_t *puAllowedActions)
707{
708 uint32_t uAllowedActions = DND_IGNORE_ACTION;
709 uint32_t uDefAction = toHGCMAction(enmDefAction);
710
711 if (!vecAllowedActions.empty())
712 {
713 /* First convert the allowed actions to a bit array. */
714 for (size_t i = 0; i < vecAllowedActions.size(); i++)
715 uAllowedActions |= toHGCMAction(vecAllowedActions[i]);
716
717 /*
718 * If no default action is set (ignoring), try one of the
719 * set allowed actions, preferring copy, move (in that order).
720 */
721 if (isDnDIgnoreAction(uDefAction))
722 {
723 if (hasDnDCopyAction(uAllowedActions))
724 uDefAction = DND_COPY_ACTION;
725 else if (hasDnDMoveAction(uAllowedActions))
726 uDefAction = DND_MOVE_ACTION;
727 }
728 }
729
730 if (puDefAction)
731 *puDefAction = uDefAction;
732 if (puAllowedActions)
733 *puAllowedActions = uAllowedActions;
734}
735
736/* static */
737DnDAction_T GuestDnD::toMainAction(uint32_t uAction)
738{
739 /* For now it doesn't seems useful to allow a
740 * link action between host & guest. Maybe later! */
741 return (isDnDCopyAction(uAction) ? (DnDAction_T)DnDAction_Copy :
742 isDnDMoveAction(uAction) ? (DnDAction_T)DnDAction_Move :
743 (DnDAction_T)DnDAction_Ignore);
744}
745
746/* static */
747std::vector<DnDAction_T> GuestDnD::toMainActions(uint32_t uActions)
748{
749 std::vector<DnDAction_T> vecActions;
750
751 /* For now it doesn't seems useful to allow a
752 * link action between host & guest. Maybe later! */
753 RTCList<DnDAction_T> lstActions;
754 if (hasDnDCopyAction(uActions))
755 lstActions.append(DnDAction_Copy);
756 if (hasDnDMoveAction(uActions))
757 lstActions.append(DnDAction_Move);
758
759 for (size_t i = 0; i < lstActions.size(); ++i)
760 vecActions.push_back(lstActions.at(i));
761
762 return vecActions;
763}
764
765///////////////////////////////////////////////////////////////////////////////
766
767GuestDnDBase::GuestDnDBase(void)
768{
769 /* Initialize public attributes. */
770 m_lstFmtSupported = GuestDnDInst()->defaultFormats();
771
772 /* Initialzie private stuff. */
773 mDataBase.m_cTransfersPending = 0;
774 mDataBase.m_uProtocolVersion = 0;
775}
776
777HRESULT GuestDnDBase::i_isFormatSupported(const com::Utf8Str &aFormat, BOOL *aSupported)
778{
779 *aSupported = std::find(m_lstFmtSupported.begin(),
780 m_lstFmtSupported.end(), aFormat) != m_lstFmtSupported.end()
781 ? TRUE : FALSE;
782 return S_OK;
783}
784
785HRESULT GuestDnDBase::i_getFormats(GuestDnDMIMEList &aFormats)
786{
787 aFormats = m_lstFmtSupported;
788
789 return S_OK;
790}
791
792HRESULT GuestDnDBase::i_addFormats(const GuestDnDMIMEList &aFormats)
793{
794 for (size_t i = 0; i < aFormats.size(); ++i)
795 {
796 Utf8Str strFormat = aFormats.at(i);
797 if (std::find(m_lstFmtSupported.begin(),
798 m_lstFmtSupported.end(), strFormat) == m_lstFmtSupported.end())
799 {
800 m_lstFmtSupported.push_back(strFormat);
801 }
802 }
803
804 return S_OK;
805}
806
807HRESULT GuestDnDBase::i_removeFormats(const GuestDnDMIMEList &aFormats)
808{
809 for (size_t i = 0; i < aFormats.size(); ++i)
810 {
811 Utf8Str strFormat = aFormats.at(i);
812 GuestDnDMIMEList::iterator itFormat = std::find(m_lstFmtSupported.begin(),
813 m_lstFmtSupported.end(), strFormat);
814 if (itFormat != m_lstFmtSupported.end())
815 m_lstFmtSupported.erase(itFormat);
816 }
817
818 return S_OK;
819}
820
821HRESULT GuestDnDBase::i_getProtocolVersion(ULONG *puVersion)
822{
823 int rc = getProtocolVersion((uint32_t *)puVersion);
824 return RT_SUCCESS(rc) ? S_OK : E_FAIL;
825}
826
827/**
828 * Tries to guess the DnD protocol version to use on the guest, based on the
829 * installed Guest Additions version + revision.
830 *
831 * If unable to retrieve the protocol version, VERR_NOT_FOUND is returned along
832 * with protocol version 1.
833 *
834 * @return IPRT status code.
835 * @param puProto Where to store the protocol version.
836 */
837int GuestDnDBase::getProtocolVersion(uint32_t *puProto)
838{
839 AssertPtrReturn(puProto, VERR_INVALID_POINTER);
840
841 int rc;
842
843 uint32_t uProto = 0;
844 uint32_t uVerAdditions;
845 uint32_t uRevAdditions;
846 if ( m_pGuest
847 && (uVerAdditions = m_pGuest->i_getAdditionsVersion()) > 0
848 && (uRevAdditions = m_pGuest->i_getAdditionsRevision()) > 0)
849 {
850#if 0 && defined(DEBUG)
851 /* Hardcode the to-used protocol version; nice for testing side effects. */
852 if (true)
853 uProto = 3;
854 else
855#endif
856 if (uVerAdditions >= VBOX_FULL_VERSION_MAKE(5, 0, 0))
857 {
858/** @todo
859 * r=bird: This is just too bad for anyone using an OSE additions build...
860 */
861 if (uRevAdditions >= 103344) /* Since r103344: Protocol v3. */
862 uProto = 3;
863 else
864 uProto = 2; /* VBox 5.0.0 - 5.0.8: Protocol v2. */
865 }
866 /* else: uProto: 0 */
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 else
874 {
875 uProto = 1; /* Fallback. */
876 rc = VERR_NOT_FOUND;
877 }
878
879 LogRel2(("DnD: Guest is using protocol v%RU32, rc=%Rrc\n", uProto, rc));
880
881 *puProto = uProto;
882 return rc;
883}
884
885int GuestDnDBase::msgQueueAdd(GuestDnDMsg *pMsg)
886{
887 mDataBase.m_lstMsgOut.push_back(pMsg);
888 return VINF_SUCCESS;
889}
890
891GuestDnDMsg *GuestDnDBase::msgQueueGetNext(void)
892{
893 if (mDataBase.m_lstMsgOut.empty())
894 return NULL;
895 return mDataBase.m_lstMsgOut.front();
896}
897
898void GuestDnDBase::msgQueueRemoveNext(void)
899{
900 if (!mDataBase.m_lstMsgOut.empty())
901 {
902 GuestDnDMsg *pMsg = mDataBase.m_lstMsgOut.front();
903 if (pMsg)
904 delete pMsg;
905 mDataBase.m_lstMsgOut.pop_front();
906 }
907}
908
909void GuestDnDBase::msgQueueClear(void)
910{
911 LogFlowFunc(("cMsg=%zu\n", mDataBase.m_lstMsgOut.size()));
912
913 GuestDnDMsgList::iterator itMsg = mDataBase.m_lstMsgOut.begin();
914 while (itMsg != mDataBase.m_lstMsgOut.end())
915 {
916 GuestDnDMsg *pMsg = *itMsg;
917 if (pMsg)
918 delete pMsg;
919
920 itMsg++;
921 }
922
923 mDataBase.m_lstMsgOut.clear();
924}
925
926int GuestDnDBase::sendCancel(void)
927{
928 int rc = GuestDnDInst()->hostCall(HOST_DND_HG_EVT_CANCEL,
929 0 /* cParms */, NULL /* paParms */);
930
931 LogFlowFunc(("Generated cancelling request, rc=%Rrc\n", rc));
932 return rc;
933}
934
935int GuestDnDBase::updateProgress(GuestDnDData *pData, GuestDnDResponse *pResp,
936 uint32_t cbDataAdd /* = 0 */)
937{
938 AssertPtrReturn(pData, VERR_INVALID_POINTER);
939 AssertPtrReturn(pResp, VERR_INVALID_POINTER);
940 /* cbDataAdd is optional. */
941
942 LogFlowFunc(("cbTotal=%RU64, cbProcessed=%RU64, cbRemaining=%RU64, cbDataAdd=%RU32\n",
943 pData->getTotal(), pData->getProcessed(), pData->getRemaining(), cbDataAdd));
944
945 if (!pResp)
946 return VINF_SUCCESS;
947
948 if (cbDataAdd)
949 pData->addProcessed(cbDataAdd);
950
951 int rc = pResp->setProgress(pData->getPercentComplete(),
952 pData->isComplete()
953 ? DND_PROGRESS_COMPLETE
954 : DND_PROGRESS_RUNNING);
955 LogFlowFuncLeaveRC(rc);
956 return rc;
957}
958
959/** @todo GuestDnDResponse *pResp needs to go. */
960int GuestDnDBase::waitForEvent(GuestDnDCallbackEvent *pEvent, GuestDnDResponse *pResp, RTMSINTERVAL msTimeout)
961{
962 AssertPtrReturn(pEvent, VERR_INVALID_POINTER);
963 AssertPtrReturn(pResp, VERR_INVALID_POINTER);
964
965 int rc;
966
967 LogFlowFunc(("msTimeout=%RU32\n", msTimeout));
968
969 uint64_t tsStart = RTTimeMilliTS();
970 do
971 {
972 /*
973 * Wait until our desired callback triggered the
974 * wait event. As we don't want to block if the guest does not
975 * respond, do busy waiting here.
976 */
977 rc = pEvent->Wait(500 /* ms */);
978 if (RT_SUCCESS(rc))
979 {
980 rc = pEvent->Result();
981 LogFlowFunc(("Callback done, result is %Rrc\n", rc));
982 break;
983 }
984 else if (rc == VERR_TIMEOUT) /* Continue waiting. */
985 rc = VINF_SUCCESS;
986
987 if ( msTimeout != RT_INDEFINITE_WAIT
988 && RTTimeMilliTS() - tsStart > msTimeout)
989 {
990 rc = VERR_TIMEOUT;
991 LogRel2(("DnD: Error: Guest did not respond within time\n"));
992 }
993 else if (pResp->isProgressCanceled()) /** @todo GuestDnDResponse *pResp needs to go. */
994 {
995 LogRel2(("DnD: Operation was canceled by user\n"));
996 rc = VERR_CANCELLED;
997 }
998
999 } while (RT_SUCCESS(rc));
1000
1001 LogFlowFuncLeaveRC(rc);
1002 return rc;
1003}
1004#endif /* VBOX_WITH_DRAG_AND_DROP */
1005
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