VirtualBox

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

Last change on this file since 85451 was 85423, checked in by vboxsync, 5 years ago

DnD/Main: More fixes.

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

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette