VirtualBox

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

Last change on this file since 83967 was 82968, checked in by vboxsync, 5 years ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 32.9 KB
Line 
1/* $Id: GuestDnDPrivate.cpp 82968 2020-02-04 10:35:17Z vboxsync $ */
2/** @file
3 * Private guest drag and drop code, used by GuestDnDTarget + GuestDnDSource.
4 */
5
6/*
7 * Copyright (C) 2011-2020 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18#define LOG_GROUP LOG_GROUP_GUEST_DND
19#include "LoggingNew.h"
20
21#include "GuestImpl.h"
22#include "AutoCaller.h"
23
24#ifdef VBOX_WITH_DRAG_AND_DROP
25# include "ConsoleImpl.h"
26# include "ProgressImpl.h"
27# include "GuestDnDPrivate.h"
28
29# include <algorithm>
30
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
37# include <VMMDev.h>
38
39# include <VBox/GuestHost/DragAndDrop.h>
40# include <VBox/HostServices/DragAndDropSvc.h>
41# include <VBox/version.h>
42
43/** @page pg_main_dnd Dungeons & Dragons - Overview
44 * Overview:
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
59 * interfaces for blocking the caller by showing a progress dialog (see
60 * this file).
61 * 3. HGCM service: Handle all messages from the host to the guest at once and
62 * encapsulate the internal communication details (see dndmanager.cpp and
63 * friends).
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
67 * operations. Currently only X11 is supported (see draganddrop.cpp within
68 * VBoxClient).
69 *
70 * Host -> Guest:
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.
75 * 2. On drop, first a drop event is sent. If this is accepted a drop data
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:
88 * m_strSupportedFormats here in this file defines the allowed mime-types.
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 *
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
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 *
143 ** @todo
144 * - ESC doesn't really work (on Windows guests it's already implemented)
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.
148 * - Add support for more mime-types (especially images, csv)
149 * - Test unusual behavior:
150 * - DnD service crash in the guest during a DnD op (e.g. crash of VBoxClient or X11)
151 * - Not expected order of the events between HGCM and the guest
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
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
175int GuestDnDCallbackEvent::Notify(int rc /* = VINF_SUCCESS */)
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
188GuestDnDResponse::GuestDnDResponse(const ComObjPtr<Guest>& pGuest)
189 : m_EventSem(NIL_RTSEMEVENT)
190 , m_dndActionDefault(0)
191 , m_dndLstActionsAllowed(0)
192 , m_pParent(pGuest)
193{
194 int rc = RTSemEventCreate(&m_EventSem);
195 if (RT_FAILURE(rc))
196 throw rc;
197}
198
199GuestDnDResponse::~GuestDnDResponse(void)
200{
201 reset();
202
203 int rc = RTSemEventDestroy(m_EventSem);
204 AssertRC(rc);
205}
206
207int GuestDnDResponse::notifyAboutGuestResponse(void) const
208{
209 return RTSemEventSignal(m_EventSem);
210}
211
212void GuestDnDResponse::reset(void)
213{
214 LogFlowThisFuncEnter();
215
216 m_dndActionDefault = 0;
217 m_dndLstActionsAllowed = 0;
218
219 m_lstFormats.clear();
220}
221
222HRESULT GuestDnDResponse::resetProgress(const ComObjPtr<Guest>& pParent)
223{
224 m_pProgress.setNull();
225
226 HRESULT hr = m_pProgress.createObject();
227 if (SUCCEEDED(hr))
228 {
229 hr = m_pProgress->init(static_cast<IGuest *>(pParent),
230 Bstr(pParent->tr("Dropping data")).raw(),
231 TRUE /* aCancelable */);
232 }
233
234 return hr;
235}
236
237bool GuestDnDResponse::isProgressCanceled(void) const
238{
239 BOOL fCanceled;
240 if (!m_pProgress.isNull())
241 {
242 HRESULT hr = m_pProgress->COMGETTER(Canceled)(&fCanceled);
243 AssertComRC(hr);
244 }
245 else
246 fCanceled = TRUE;
247
248 return RT_BOOL(fCanceled);
249}
250
251int GuestDnDResponse::setCallback(uint32_t uMsg, PFNGUESTDNDCALLBACK pfnCallback, void *pvUser /* = NULL */)
252{
253 GuestDnDCallbackMap::iterator it = m_mapCallbacks.find(uMsg);
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
275int GuestDnDResponse::setProgress(unsigned uPercentage,
276 uint32_t uStatus,
277 int rcOp /* = VINF_SUCCESS */, const Utf8Str &strMsg /* = "" */)
278{
279 RT_NOREF(rcOp);
280 LogFlowFunc(("uStatus=%RU32, uPercentage=%RU32, rcOp=%Rrc, strMsg=%s\n",
281 uStatus, uPercentage, rcOp, strMsg.c_str()));
282
283 int rc = VINF_SUCCESS;
284 if (!m_pProgress.isNull())
285 {
286 BOOL fCompleted;
287 HRESULT hr = m_pProgress->COMGETTER(Completed)(&fCompleted);
288 AssertComRC(hr);
289
290 BOOL fCanceled;
291 hr = m_pProgress->COMGETTER(Canceled)(&fCanceled);
292 AssertComRC(hr);
293
294 LogFlowFunc(("Current: fCompleted=%RTbool, fCanceled=%RTbool\n", fCompleted, fCanceled));
295
296 if (!fCompleted)
297 {
298 switch (uStatus)
299 {
300 case DragAndDropSvc::DND_PROGRESS_ERROR:
301 {
302 hr = m_pProgress->i_notifyComplete(VBOX_E_IPRT_ERROR,
303 COM_IIDOF(IGuest),
304 m_pParent->getComponentName(), strMsg.c_str());
305 reset();
306 break;
307 }
308
309 case DragAndDropSvc::DND_PROGRESS_CANCELLED:
310 {
311 hr = m_pProgress->Cancel();
312 AssertComRC(hr);
313 hr = m_pProgress->i_notifyComplete(S_OK);
314 AssertComRC(hr);
315
316 reset();
317 break;
318 }
319
320 case DragAndDropSvc::DND_PROGRESS_RUNNING:
321 case DragAndDropSvc::DND_PROGRESS_COMPLETE:
322 {
323 if (!fCanceled)
324 {
325 hr = m_pProgress->SetCurrentOperationProgress(uPercentage);
326 AssertComRC(hr);
327 if ( uStatus == DragAndDropSvc::DND_PROGRESS_COMPLETE
328 || uPercentage >= 100)
329 {
330 hr = m_pProgress->i_notifyComplete(S_OK);
331 AssertComRC(hr);
332 }
333 }
334 break;
335 }
336
337 default:
338 break;
339 }
340 }
341
342 hr = m_pProgress->COMGETTER(Completed)(&fCompleted);
343 AssertComRC(hr);
344 hr = m_pProgress->COMGETTER(Canceled)(&fCanceled);
345 AssertComRC(hr);
346
347 LogFlowFunc(("New: fCompleted=%RTbool, fCanceled=%RTbool\n", fCompleted, fCanceled));
348 }
349
350 LogFlowFuncLeaveRC(rc);
351 return rc;
352}
353
354int GuestDnDResponse::onDispatch(uint32_t u32Function, void *pvParms, uint32_t cbParms)
355{
356 LogFlowFunc(("u32Function=%RU32, pvParms=%p, cbParms=%RU32\n", u32Function, pvParms, cbParms));
357
358 int rc = VERR_WRONG_ORDER; /* Play safe. */
359 bool fTryCallbacks = false;
360
361 switch (u32Function)
362 {
363 case DragAndDropSvc::GUEST_DND_CONNECT:
364 {
365 LogThisFunc(("Client connected\n"));
366
367 /* Nothing to do here (yet). */
368 rc = VINF_SUCCESS;
369 break;
370 }
371
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
379 case DragAndDropSvc::GUEST_DND_HG_ACK_OP:
380 {
381 DragAndDropSvc::PVBOXDNDCBHGACKOPDATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBHGACKOPDATA>(pvParms);
382 AssertPtr(pCBData);
383 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBHGACKOPDATA) == cbParms, VERR_INVALID_PARAMETER);
384 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_HG_ACK_OP == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
385
386 setActionDefault(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);
396 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_HG_REQ_DATA == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
397
398 if ( pCBData->cbFormat == 0
399 || pCBData->cbFormat > _64K /** @todo Make this configurable? */
400 || pCBData->pszFormat == NULL)
401 {
402 rc = VERR_INVALID_PARAMETER;
403 }
404 else if (!RTStrIsValidEncoding(pCBData->pszFormat))
405 {
406 rc = VERR_INVALID_PARAMETER;
407 }
408 else
409 {
410 setFormats(GuestDnD::toFormatList(pCBData->pszFormat));
411 rc = VINF_SUCCESS;
412 }
413
414 int rc2 = notifyAboutGuestResponse();
415 if (RT_SUCCESS(rc))
416 rc = rc2;
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);
426 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_HG_EVT_PROGRESS == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
427
428 rc = setProgress(pCBData->uPercentage, pCBData->uStatus, pCBData->rc);
429 if (RT_SUCCESS(rc))
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);
440 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_GH_ACK_PENDING == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
441
442 if ( pCBData->cbFormat == 0
443 || pCBData->cbFormat > _64K /** @todo Make the maximum size configurable? */
444 || pCBData->pszFormat == NULL)
445 {
446 rc = VERR_INVALID_PARAMETER;
447 }
448 else if (!RTStrIsValidEncoding(pCBData->pszFormat))
449 {
450 rc = VERR_INVALID_PARAMETER;
451 }
452 else
453 {
454 setFormats (GuestDnD::toFormatList(pCBData->pszFormat));
455 setActionDefault (pCBData->uDefAction);
456 setActionsAllowed(pCBData->uAllActions);
457
458 rc = VINF_SUCCESS;
459 }
460
461 int rc2 = notifyAboutGuestResponse();
462 if (RT_SUCCESS(rc))
463 rc = rc2;
464 break;
465 }
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;
471 }
472
473 /*
474 * Try the host's installed callbacks (if any).
475 */
476 if (fTryCallbacks)
477 {
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
485 {
486 LogFlowFunc(("No callback for function %RU32 defined (%zu callbacks total)\n", u32Function, m_mapCallbacks.size()));
487 rc = VERR_NOT_SUPPORTED; /* Tell the guest. */
488 }
489 }
490
491 LogFlowFunc(("Returning rc=%Rrc\n", rc));
492 return rc;
493}
494
495HRESULT GuestDnDResponse::queryProgressTo(IProgress **ppProgress)
496{
497 return m_pProgress.queryInterfaceTo(ppProgress);
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
509///////////////////////////////////////////////////////////////////////////////
510
511GuestDnD* GuestDnD::s_pInstance = NULL;
512
513GuestDnD::GuestDnD(const ComObjPtr<Guest> &pGuest)
514 : m_pGuest(pGuest)
515{
516 LogFlowFuncEnter();
517
518 m_pResponse = new GuestDnDResponse(pGuest);
519
520 /* List of supported default MIME types. */
521 LogRel2(("DnD: Supported default host formats:\n"));
522 const com::Utf8Str arrEntries[] = { VBOX_DND_FORMATS_DEFAULT };
523 for (size_t i = 0; i < RT_ELEMENTS(arrEntries); i++)
524 {
525 m_strDefaultFormats.push_back(arrEntries[i]);
526 LogRel2(("DnD: \t%s\n", arrEntries[i].c_str()));
527 }
528}
529
530GuestDnD::~GuestDnD(void)
531{
532 LogFlowFuncEnter();
533
534 if (m_pResponse)
535 delete m_pResponse;
536}
537
538HRESULT GuestDnD::adjustScreenCoordinates(ULONG uScreenId, ULONG *puX, ULONG *puY) const
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
543 /* For multi-monitor support we need to add shift values to the coordinates
544 * (depending on the screen number). */
545 ComObjPtr<Console> pConsole = m_pGuest->i_getConsole();
546 ComPtr<IDisplay> pDisplay;
547 HRESULT hr = pConsole->COMGETTER(Display)(pDisplay.asOutParam());
548 if (FAILED(hr))
549 return hr;
550
551 ULONG dummy;
552 LONG xShift, yShift;
553 GuestMonitorStatus_T monitorStatus;
554 hr = pDisplay->GetScreenResolution(uScreenId, &dummy, &dummy, &dummy,
555 &xShift, &yShift, &monitorStatus);
556 if (FAILED(hr))
557 return hr;
558
559 if (puX)
560 *puX += xShift;
561 if (puY)
562 *puY += yShift;
563
564 LogFlowFunc(("uScreenId=%RU32, x=%RU32, y=%RU32\n", uScreenId, puX ? *puX : 0, puY ? *puY : 0));
565 return S_OK;
566}
567
568int GuestDnD::hostCall(uint32_t u32Function, uint32_t cParms, PVBOXHGCMSVCPARM paParms) const
569{
570 Assert(!m_pGuest.isNull());
571 ComObjPtr<Console> pConsole = m_pGuest->i_getConsole();
572
573 /* Forward the information to the VMM device. */
574 Assert(!pConsole.isNull());
575 VMMDev *pVMMDev = pConsole->i_getVMMDev();
576 if (!pVMMDev)
577 return VERR_COM_OBJECT_NOT_FOUND;
578
579 return pVMMDev->hgcmHostCall("VBoxDragAndDropSvc", u32Function, cParms, paParms);
580}
581
582/* static */
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
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}
607
608/* static */
609GuestDnDMIMEList GuestDnD::toFormatList(const com::Utf8Str &strFormats)
610{
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{
623 com::Utf8Str strFormat;
624 for (size_t i = 0; i < lstFormats.size(); i++)
625 {
626 const com::Utf8Str &f = lstFormats.at(i);
627 strFormat += f + "\r\n";
628 }
629
630 return strFormat;
631}
632
633/* static */
634GuestDnDMIMEList GuestDnD::toFilteredFormatList(const GuestDnDMIMEList &lstFormatsSupported, const GuestDnDMIMEList &lstFormatsWanted)
635{
636 GuestDnDMIMEList lstFormats;
637
638 for (size_t i = 0; i < lstFormatsWanted.size(); i++)
639 {
640 /* Only keep supported format types. */
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");
657 size_t i = 0;
658 while (i < lstFormats.size())
659 {
660 /* Only keep allowed format types. */
661 if (std::find(lstFormatsSupported.begin(),
662 lstFormatsSupported.end(), lstFormats.at(i)) != lstFormatsSupported.end())
663 {
664 lstFmt.push_back(lstFormats[i]);
665 }
666 i++;
667 }
668
669 return lstFmt;
670}
671
672/* static */
673VBOXDNDACTION GuestDnD::toHGCMAction(DnDAction_T enmAction)
674{
675 VBOXDNDACTION dndAction = VBOX_DND_ACTION_IGNORE;
676 switch (enmAction)
677 {
678 case DnDAction_Copy:
679 dndAction = VBOX_DND_ACTION_COPY;
680 break;
681 case DnDAction_Move:
682 dndAction = VBOX_DND_ACTION_MOVE;
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;
693 }
694
695 return dndAction;
696}
697
698/* static */
699void GuestDnD::toHGCMActions(DnDAction_T enmDnDActionDefault,
700 VBOXDNDACTION *pDnDActionDefault,
701 const std::vector<DnDAction_T> vecDnDActionsAllowed,
702 VBOXDNDACTIONLIST *pDnDLstActionsAllowed)
703{
704 VBOXDNDACTIONLIST dndLstActionsAllowed = VBOX_DND_ACTION_IGNORE;
705 VBOXDNDACTION dndActionDefault = toHGCMAction(enmDnDActionDefault);
706
707 if (!vecDnDActionsAllowed.empty())
708 {
709 /* First convert the allowed actions to a bit array. */
710 for (size_t i = 0; i < vecDnDActionsAllowed.size(); i++)
711 dndLstActionsAllowed |= toHGCMAction(vecDnDActionsAllowed[i]);
712
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(dndActionDefault))
718 {
719 if (hasDnDCopyAction(dndLstActionsAllowed))
720 dndActionDefault = VBOX_DND_ACTION_COPY;
721 else if (hasDnDMoveAction(dndLstActionsAllowed))
722 dndActionDefault = VBOX_DND_ACTION_MOVE;
723 }
724 }
725
726 if (pDnDActionDefault)
727 *pDnDActionDefault = dndActionDefault;
728 if (pDnDLstActionsAllowed)
729 *pDnDLstActionsAllowed = dndLstActionsAllowed;
730}
731
732/* static */
733DnDAction_T GuestDnD::toMainAction(VBOXDNDACTION dndAction)
734{
735 /* For now it doesn't seems useful to allow a
736 * link action between host & guest. Maybe later! */
737 return isDnDCopyAction(dndAction) ? DnDAction_Copy
738 : isDnDMoveAction(dndAction) ? DnDAction_Move
739 : DnDAction_Ignore;
740}
741
742/* static */
743std::vector<DnDAction_T> GuestDnD::toMainActions(VBOXDNDACTIONLIST dndActionList)
744{
745 std::vector<DnDAction_T> vecActions;
746
747 /* For now it doesn't seems useful to allow a
748 * link action between host & guest. Maybe later! */
749 RTCList<DnDAction_T> lstActions;
750 if (hasDnDCopyAction(dndActionList))
751 lstActions.append(DnDAction_Copy);
752 if (hasDnDMoveAction(dndActionList))
753 lstActions.append(DnDAction_Move);
754
755 for (size_t i = 0; i < lstActions.size(); ++i)
756 vecActions.push_back(lstActions.at(i));
757
758 return vecActions;
759}
760
761///////////////////////////////////////////////////////////////////////////////
762
763GuestDnDBase::GuestDnDBase(void)
764{
765 /* Initialize public attributes. */
766 m_lstFmtSupported = GUESTDNDINST()->defaultFormats();
767
768 /* Initialzie private stuff. */
769 mDataBase.m_cTransfersPending = 0;
770 mDataBase.m_uProtocolVersion = 0;
771}
772
773HRESULT GuestDnDBase::i_isFormatSupported(const com::Utf8Str &aFormat, BOOL *aSupported)
774{
775 *aSupported = std::find(m_lstFmtSupported.begin(),
776 m_lstFmtSupported.end(), aFormat) != m_lstFmtSupported.end()
777 ? TRUE : FALSE;
778 return S_OK;
779}
780
781HRESULT GuestDnDBase::i_getFormats(GuestDnDMIMEList &aFormats)
782{
783 aFormats = m_lstFmtSupported;
784
785 return S_OK;
786}
787
788HRESULT GuestDnDBase::i_addFormats(const GuestDnDMIMEList &aFormats)
789{
790 for (size_t i = 0; i < aFormats.size(); ++i)
791 {
792 Utf8Str strFormat = aFormats.at(i);
793 if (std::find(m_lstFmtSupported.begin(),
794 m_lstFmtSupported.end(), strFormat) == m_lstFmtSupported.end())
795 {
796 m_lstFmtSupported.push_back(strFormat);
797 }
798 }
799
800 return S_OK;
801}
802
803HRESULT GuestDnDBase::i_removeFormats(const GuestDnDMIMEList &aFormats)
804{
805 for (size_t i = 0; i < aFormats.size(); ++i)
806 {
807 Utf8Str strFormat = aFormats.at(i);
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);
812 }
813
814 return S_OK;
815}
816
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
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 */
833int GuestDnDBase::getProtocolVersion(uint32_t *puProto)
834{
835 AssertPtrReturn(puProto, VERR_INVALID_POINTER);
836
837 int rc;
838
839 uint32_t uProto = 0;
840 uint32_t uVerAdditions;
841 uint32_t uRevAdditions;
842 if ( m_pGuest
843 && (uVerAdditions = m_pGuest->i_getAdditionsVersion()) > 0
844 && (uRevAdditions = m_pGuest->i_getAdditionsRevision()) > 0)
845 {
846#if 0 && defined(DEBUG)
847 /* Hardcode the to-used protocol version; nice for testing side effects. */
848 if (true)
849 uProto = 3;
850 else
851#endif
852 if (uVerAdditions >= VBOX_FULL_VERSION_MAKE(5, 0, 0))
853 {
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 */
863
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;
868 }
869 else
870 {
871 uProto = 1; /* Fallback. */
872 rc = VERR_NOT_FOUND;
873 }
874
875 LogRel2(("DnD: Guest is using protocol v%RU32, rc=%Rrc\n", uProto, rc));
876
877 *puProto = uProto;
878 return rc;
879}
880
881int GuestDnDBase::msgQueueAdd(GuestDnDMsg *pMsg)
882{
883 mDataBase.m_lstMsgOut.push_back(pMsg);
884 return VINF_SUCCESS;
885}
886
887GuestDnDMsg *GuestDnDBase::msgQueueGetNext(void)
888{
889 if (mDataBase.m_lstMsgOut.empty())
890 return NULL;
891 return mDataBase.m_lstMsgOut.front();
892}
893
894void GuestDnDBase::msgQueueRemoveNext(void)
895{
896 if (!mDataBase.m_lstMsgOut.empty())
897 {
898 GuestDnDMsg *pMsg = mDataBase.m_lstMsgOut.front();
899 if (pMsg)
900 delete pMsg;
901 mDataBase.m_lstMsgOut.pop_front();
902 }
903}
904
905void GuestDnDBase::msgQueueClear(void)
906{
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())
911 {
912 GuestDnDMsg *pMsg = *itMsg;
913 if (pMsg)
914 delete pMsg;
915
916 itMsg++;
917 }
918
919 mDataBase.m_lstMsgOut.clear();
920}
921
922int GuestDnDBase::sendCancel(void)
923{
924 GuestDnDMsg Msg;
925 Msg.setType(HOST_DND_CANCEL);
926 if (mDataBase.m_uProtocolVersion >= 3)
927 Msg.setNextUInt32(0); /** @todo ContextID not used yet. */
928
929 LogRel2(("DnD: Cancelling operation on guest ..."));
930
931 return GUESTDNDINST()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
932}
933
934int GuestDnDBase::updateProgress(GuestDnDData *pData, GuestDnDResponse *pResp,
935 uint32_t cbDataAdd /* = 0 */)
936{
937 AssertPtrReturn(pData, VERR_INVALID_POINTER);
938 AssertPtrReturn(pResp, VERR_INVALID_POINTER);
939 /* cbDataAdd is optional. */
940
941 LogFlowFunc(("cbTotal=%RU64, cbProcessed=%RU64, cbRemaining=%RU64, cbDataAdd=%RU32\n",
942 pData->getTotal(), pData->getProcessed(), pData->getRemaining(), cbDataAdd));
943
944 if (!pResp)
945 return VINF_SUCCESS;
946
947 if (cbDataAdd)
948 pData->addProcessed(cbDataAdd);
949
950 int rc = pResp->setProgress(pData->getPercentComplete(),
951 pData->isComplete()
952 ? DND_PROGRESS_COMPLETE
953 : DND_PROGRESS_RUNNING);
954 LogFlowFuncLeaveRC(rc);
955 return rc;
956}
957
958/** @todo GuestDnDResponse *pResp needs to go. */
959int GuestDnDBase::waitForEvent(GuestDnDCallbackEvent *pEvent, GuestDnDResponse *pResp, RTMSINTERVAL msTimeout)
960{
961 AssertPtrReturn(pEvent, VERR_INVALID_POINTER);
962 AssertPtrReturn(pResp, VERR_INVALID_POINTER);
963
964 int rc;
965
966 LogFlowFunc(("msTimeout=%RU32\n", msTimeout));
967
968 uint64_t tsStart = RTTimeMilliTS();
969 do
970 {
971 /*
972 * Wait until our desired callback triggered the
973 * wait event. As we don't want to block if the guest does not
974 * respond, do busy waiting here.
975 */
976 rc = pEvent->Wait(500 /* ms */);
977 if (RT_SUCCESS(rc))
978 {
979 rc = pEvent->Result();
980 LogFlowFunc(("Callback done, result is %Rrc\n", rc));
981 break;
982 }
983 else if (rc == VERR_TIMEOUT) /* Continue waiting. */
984 rc = VINF_SUCCESS;
985
986 if ( msTimeout != RT_INDEFINITE_WAIT
987 && RTTimeMilliTS() - tsStart > msTimeout)
988 {
989 rc = VERR_TIMEOUT;
990 LogRel2(("DnD: Error: Guest did not respond within time\n"));
991 }
992 else if (pResp->isProgressCanceled()) /** @todo GuestDnDResponse *pResp needs to go. */
993 {
994 LogRel2(("DnD: Operation was canceled by user\n"));
995 rc = VERR_CANCELLED;
996 }
997
998 } while (RT_SUCCESS(rc));
999
1000 LogFlowFuncLeaveRC(rc);
1001 return rc;
1002}
1003#endif /* VBOX_WITH_DRAG_AND_DROP */
1004
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