VirtualBox

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

Last change on this file since 85563 was 85561, checked in by vboxsync, 5 years ago

DnD/Main: Extended @todo.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 47.1 KB
Line 
1/* $Id: GuestDnDPrivate.cpp 85561 2020-07-30 13:49:03Z 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
158
159/*********************************************************************************************************************************
160 * Internal macros. *
161 ********************************************************************************************************************************/
162
163/** Tries locking the GuestDnD object and returns on failure. */
164#define GUESTDND_LOCK() \
165 { \
166 int rcLock = RTCritSectEnter(&m_CritSect); \
167 if (RT_FAILURE(rcLock)) \
168 return rcLock; \
169 }
170
171/** Tries locking the GuestDnD object and returns a_Ret failure. */
172#define GUESTDND_LOCK_RET(a_Ret) \
173 { \
174 int rcLock = RTCritSectEnter(&m_CritSect); \
175 if (RT_FAILURE(rcLock)) \
176 return a_Ret; \
177 }
178
179/** Unlocks a formerly locked GuestDnD object. */
180#define GUESTDND_UNLOCK() \
181 { \
182 int rcUnlock = RTCritSectLeave(&m_CritSect); RT_NOREF(rcUnlock); \
183 AssertRC(rcUnlock); \
184 }
185
186/*********************************************************************************************************************************
187 * GuestDnDSendCtx implementation. *
188 ********************************************************************************************************************************/
189
190GuestDnDSendCtx::GuestDnDSendCtx(void)
191 : pTarget(NULL)
192 , pResp(NULL)
193{
194 reset();
195}
196
197/**
198 * Resets a GuestDnDSendCtx object.
199 */
200void GuestDnDSendCtx::reset(void)
201{
202 if (pResp)
203 pResp->reset();
204
205 uScreenID = 0;
206
207 Transfer.reset();
208
209 int rc2 = EventCallback.Reset();
210 AssertRC(rc2);
211
212 GuestDnDData::reset();
213}
214
215/*********************************************************************************************************************************
216 * GuestDnDRecvCtx implementation. *
217 ********************************************************************************************************************************/
218
219GuestDnDRecvCtx::GuestDnDRecvCtx(void)
220 : pSource(NULL)
221 , pResp(NULL)
222{
223 reset();
224}
225
226/**
227 * Resets a GuestDnDRecvCtx object.
228 */
229void GuestDnDRecvCtx::reset(void)
230{
231 if (pResp)
232 pResp->reset();
233
234 lstFmtOffered.clear();
235 strFmtReq = "";
236 strFmtRecv = "";
237 enmAction = 0;
238
239 Transfer.reset();
240
241 int rc2 = EventCallback.Reset();
242 AssertRC(rc2);
243
244 GuestDnDData::reset();
245}
246
247/*********************************************************************************************************************************
248 * GuestDnDCallbackEvent implementation. *
249 ********************************************************************************************************************************/
250
251GuestDnDCallbackEvent::~GuestDnDCallbackEvent(void)
252{
253 if (NIL_RTSEMEVENT != m_SemEvent)
254 RTSemEventDestroy(m_SemEvent);
255}
256
257/**
258 * Resets a GuestDnDCallbackEvent object.
259 */
260int GuestDnDCallbackEvent::Reset(void)
261{
262 int rc = VINF_SUCCESS;
263
264 if (NIL_RTSEMEVENT == m_SemEvent)
265 rc = RTSemEventCreate(&m_SemEvent);
266
267 m_Rc = VINF_SUCCESS;
268 return rc;
269}
270
271/**
272 * Completes a callback event by notifying the waiting side.
273 *
274 * @returns VBox status code.
275 * @param rc Result code to use for the event completion.
276 */
277int GuestDnDCallbackEvent::Notify(int rc /* = VINF_SUCCESS */)
278{
279 m_Rc = rc;
280 return RTSemEventSignal(m_SemEvent);
281}
282
283/**
284 * Waits on a callback event for being notified.
285 *
286 * @returns VBox status code.
287 * @param msTimeout Timeout (in ms) to wait for callback event.
288 */
289int GuestDnDCallbackEvent::Wait(RTMSINTERVAL msTimeout)
290{
291 return RTSemEventWait(m_SemEvent, msTimeout);
292}
293
294/********************************************************************************************************************************
295 *
296 ********************************************************************************************************************************/
297
298GuestDnDResponse::GuestDnDResponse(const ComObjPtr<Guest>& pGuest)
299 : m_EventSem(NIL_RTSEMEVENT)
300 , m_dndActionDefault(0)
301 , m_dndLstActionsAllowed(0)
302 , m_pParent(pGuest)
303{
304 int rc = RTSemEventCreate(&m_EventSem);
305 if (RT_FAILURE(rc))
306 throw rc;
307}
308
309GuestDnDResponse::~GuestDnDResponse(void)
310{
311 reset();
312
313 int rc = RTSemEventDestroy(m_EventSem);
314 AssertRC(rc);
315}
316
317/**
318 * Notifies the waiting side about a guest notification response.
319 */
320int GuestDnDResponse::notifyAboutGuestResponse(void) const
321{
322 return RTSemEventSignal(m_EventSem);
323}
324
325/**
326 * Resets a GuestDnDResponse object.
327 */
328void GuestDnDResponse::reset(void)
329{
330 LogFlowThisFuncEnter();
331
332 m_dndActionDefault = 0;
333 m_dndLstActionsAllowed = 0;
334
335 m_lstFormats.clear();
336}
337
338/**
339 * Resets the progress object.
340 *
341 * @returns HRESULT
342 * @param pParent Parent to set for the progress object.
343 */
344HRESULT GuestDnDResponse::resetProgress(const ComObjPtr<Guest>& pParent)
345{
346 m_pProgress.setNull();
347
348 HRESULT hr = m_pProgress.createObject();
349 if (SUCCEEDED(hr))
350 {
351 hr = m_pProgress->init(static_cast<IGuest *>(pParent),
352 Bstr(pParent->tr("Dropping data")).raw(),
353 TRUE /* aCancelable */);
354 }
355
356 return hr;
357}
358
359/**
360 * Returns whether the progress object has been canceled or not.
361 *
362 * @returns \c true if canceled, \c false if not.
363 */
364bool GuestDnDResponse::isProgressCanceled(void) const
365{
366 BOOL fCanceled;
367 if (!m_pProgress.isNull())
368 {
369 HRESULT hr = m_pProgress->COMGETTER(Canceled)(&fCanceled);
370 AssertComRC(hr);
371 }
372 else
373 fCanceled = TRUE;
374
375 return RT_BOOL(fCanceled);
376}
377
378/**
379 * Sets a callback for a specific HGCM message.
380 *
381 * @returns VBox status code.
382 * @param uMsg HGCM message ID to set callback for.
383 * @param pfnCallback Callback function pointer to use.
384 * @param pvUser User-provided arguments for the callback function. Optional and can be NULL.
385 */
386int GuestDnDResponse::setCallback(uint32_t uMsg, PFNGUESTDNDCALLBACK pfnCallback, void *pvUser /* = NULL */)
387{
388 GuestDnDCallbackMap::iterator it = m_mapCallbacks.find(uMsg);
389
390 /* Add. */
391 if (pfnCallback)
392 {
393 if (it == m_mapCallbacks.end())
394 {
395 m_mapCallbacks[uMsg] = GuestDnDCallback(pfnCallback, uMsg, pvUser);
396 return VINF_SUCCESS;
397 }
398
399 AssertMsgFailed(("Callback for message %RU32 already registered\n", uMsg));
400 return VERR_ALREADY_EXISTS;
401 }
402
403 /* Remove. */
404 if (it != m_mapCallbacks.end())
405 m_mapCallbacks.erase(it);
406
407 return VINF_SUCCESS;
408}
409
410/**
411 * Sets the progress object to a new state.
412 *
413 * @returns VBox status code.
414 * @param uPercentage Percentage (0-100) to set.
415 * @param uStatus Status (of type DND_PROGRESS_XXX) to set.
416 * @param rcOp IPRT-style result code to set. Optional.
417 * @param strMsg Message to set. Optional.
418 */
419int GuestDnDResponse::setProgress(unsigned uPercentage, uint32_t uStatus,
420 int rcOp /* = VINF_SUCCESS */, const Utf8Str &strMsg /* = "" */)
421{
422 LogFlowFunc(("uPercentage=%u, uStatus=%RU32, , rcOp=%Rrc, strMsg=%s\n",
423 uPercentage, uStatus, rcOp, strMsg.c_str()));
424
425 HRESULT hr = S_OK;
426
427 BOOL fCompleted = FALSE;
428 BOOL fCanceled = FALSE;
429
430 int rc = VINF_SUCCESS;
431
432 if (!m_pProgress.isNull())
433 {
434 hr = m_pProgress->COMGETTER(Completed)(&fCompleted);
435 AssertComRC(hr);
436
437 hr = m_pProgress->COMGETTER(Canceled)(&fCanceled);
438 AssertComRC(hr);
439
440 LogFlowFunc(("Progress fCompleted=%RTbool, fCanceled=%RTbool\n", fCompleted, fCanceled));
441 }
442
443 switch (uStatus)
444 {
445 case DragAndDropSvc::DND_PROGRESS_ERROR:
446 {
447 LogRel(("DnD: Guest reported error %Rrc\n", rcOp));
448
449 if ( !m_pProgress.isNull()
450 && !fCompleted)
451 hr = m_pProgress->i_notifyComplete(VBOX_E_IPRT_ERROR,
452 COM_IIDOF(IGuest),
453 m_pParent->getComponentName(), strMsg.c_str());
454 reset();
455 break;
456 }
457
458 case DragAndDropSvc::DND_PROGRESS_CANCELLED:
459 {
460 LogRel2(("DnD: Guest cancelled operation\n"));
461
462 if ( !m_pProgress.isNull()
463 && !fCompleted)
464 {
465 hr = m_pProgress->Cancel();
466 AssertComRC(hr);
467 hr = m_pProgress->i_notifyComplete(S_OK);
468 AssertComRC(hr);
469 }
470
471 reset();
472 break;
473 }
474
475 case DragAndDropSvc::DND_PROGRESS_RUNNING:
476 RT_FALL_THROUGH();
477 case DragAndDropSvc::DND_PROGRESS_COMPLETE:
478 {
479 LogRel2(("DnD: Guest reporting running/completion status with %u%%\n", uPercentage));
480
481 if ( !m_pProgress.isNull()
482 && !fCompleted)
483 {
484 hr = m_pProgress->SetCurrentOperationProgress(uPercentage);
485 AssertComRC(hr);
486 if ( uStatus == DragAndDropSvc::DND_PROGRESS_COMPLETE
487 || uPercentage >= 100)
488 {
489 hr = m_pProgress->i_notifyComplete(S_OK);
490 AssertComRC(hr);
491 }
492 }
493 break;
494 }
495
496 default:
497 break;
498 }
499
500 if (!m_pProgress.isNull())
501 {
502 hr = m_pProgress->COMGETTER(Completed)(&fCompleted);
503 AssertComRC(hr);
504 hr = m_pProgress->COMGETTER(Canceled)(&fCanceled);
505 AssertComRC(hr);
506
507 LogFlowFunc(("Progress fCompleted=%RTbool, fCanceled=%RTbool\n", fCompleted, fCanceled));
508 }
509
510 LogFlowFuncLeaveRC(rc);
511 return rc;
512}
513
514/**
515 * Dispatching function for handling the host service service callback.
516 *
517 * @returns VBox status code.
518 * @param u32Function HGCM message ID to handle.
519 * @param pvParms Pointer to optional data provided for a particular message. Optional.
520 * @param cbParms Size (in bytes) of \a pvParms.
521 */
522int GuestDnDResponse::onDispatch(uint32_t u32Function, void *pvParms, uint32_t cbParms)
523{
524 LogFlowFunc(("u32Function=%RU32, pvParms=%p, cbParms=%RU32\n", u32Function, pvParms, cbParms));
525
526 int rc = VERR_WRONG_ORDER; /* Play safe. */
527 bool fTryCallbacks = false;
528
529 switch (u32Function)
530 {
531 case DragAndDropSvc::GUEST_DND_CONNECT:
532 {
533 LogThisFunc(("Client connected\n"));
534
535 /* Nothing to do here (yet). */
536 rc = VINF_SUCCESS;
537 break;
538 }
539
540 case DragAndDropSvc::GUEST_DND_DISCONNECT:
541 {
542 LogThisFunc(("Client disconnected\n"));
543 rc = setProgress(100, DND_PROGRESS_CANCELLED, VINF_SUCCESS);
544 break;
545 }
546
547 case DragAndDropSvc::GUEST_DND_HG_ACK_OP:
548 {
549 DragAndDropSvc::PVBOXDNDCBHGACKOPDATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBHGACKOPDATA>(pvParms);
550 AssertPtr(pCBData);
551 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBHGACKOPDATA) == cbParms, VERR_INVALID_PARAMETER);
552 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_HG_ACK_OP == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
553
554 setActionDefault(pCBData->uAction);
555 rc = notifyAboutGuestResponse();
556 break;
557 }
558
559 case DragAndDropSvc::GUEST_DND_HG_REQ_DATA:
560 {
561 DragAndDropSvc::PVBOXDNDCBHGREQDATADATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBHGREQDATADATA>(pvParms);
562 AssertPtr(pCBData);
563 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBHGREQDATADATA) == cbParms, VERR_INVALID_PARAMETER);
564 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_HG_REQ_DATA == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
565
566 if ( pCBData->cbFormat == 0
567 || pCBData->cbFormat > _64K /** @todo Make this configurable? */
568 || pCBData->pszFormat == NULL)
569 {
570 rc = VERR_INVALID_PARAMETER;
571 }
572 else if (!RTStrIsValidEncoding(pCBData->pszFormat))
573 {
574 rc = VERR_INVALID_PARAMETER;
575 }
576 else
577 {
578 setFormats(GuestDnD::toFormatList(pCBData->pszFormat));
579 rc = VINF_SUCCESS;
580 }
581
582 int rc2 = notifyAboutGuestResponse();
583 if (RT_SUCCESS(rc))
584 rc = rc2;
585 break;
586 }
587
588 case DragAndDropSvc::GUEST_DND_HG_EVT_PROGRESS:
589 {
590 DragAndDropSvc::PVBOXDNDCBHGEVTPROGRESSDATA pCBData =
591 reinterpret_cast<DragAndDropSvc::PVBOXDNDCBHGEVTPROGRESSDATA>(pvParms);
592 AssertPtr(pCBData);
593 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBHGEVTPROGRESSDATA) == cbParms, VERR_INVALID_PARAMETER);
594 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_HG_EVT_PROGRESS == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
595
596 rc = setProgress(pCBData->uPercentage, pCBData->uStatus, pCBData->rc);
597 if (RT_SUCCESS(rc))
598 rc = notifyAboutGuestResponse();
599 break;
600 }
601#ifdef VBOX_WITH_DRAG_AND_DROP_GH
602 case DragAndDropSvc::GUEST_DND_GH_ACK_PENDING:
603 {
604 DragAndDropSvc::PVBOXDNDCBGHACKPENDINGDATA pCBData =
605 reinterpret_cast<DragAndDropSvc::PVBOXDNDCBGHACKPENDINGDATA>(pvParms);
606 AssertPtr(pCBData);
607 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBGHACKPENDINGDATA) == cbParms, VERR_INVALID_PARAMETER);
608 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_GH_ACK_PENDING == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
609
610 if ( pCBData->cbFormat == 0
611 || pCBData->cbFormat > _64K /** @todo Make the maximum size configurable? */
612 || pCBData->pszFormat == NULL)
613 {
614 rc = VERR_INVALID_PARAMETER;
615 }
616 else if (!RTStrIsValidEncoding(pCBData->pszFormat))
617 {
618 rc = VERR_INVALID_PARAMETER;
619 }
620 else
621 {
622 setFormats (GuestDnD::toFormatList(pCBData->pszFormat));
623 setActionDefault (pCBData->uDefAction);
624 setActionsAllowed(pCBData->uAllActions);
625
626 rc = VINF_SUCCESS;
627 }
628
629 int rc2 = notifyAboutGuestResponse();
630 if (RT_SUCCESS(rc))
631 rc = rc2;
632 break;
633 }
634#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
635 default:
636 /* * Try if the event is covered by a registered callback. */
637 fTryCallbacks = true;
638 break;
639 }
640
641 /*
642 * Try the host's installed callbacks (if any).
643 */
644 if (fTryCallbacks)
645 {
646 GuestDnDCallbackMap::const_iterator it = m_mapCallbacks.find(u32Function);
647 if (it != m_mapCallbacks.end())
648 {
649 AssertPtr(it->second.pfnCallback);
650 rc = it->second.pfnCallback(u32Function, pvParms, cbParms, it->second.pvUser);
651 }
652 else
653 {
654 LogFlowFunc(("No callback for function %RU32 defined (%zu callbacks total)\n", u32Function, m_mapCallbacks.size()));
655 rc = VERR_NOT_SUPPORTED; /* Tell the guest. */
656 }
657 }
658
659 LogFlowFunc(("Returning rc=%Rrc\n", rc));
660 return rc;
661}
662
663/**
664 * Helper function to query the internal progress object to an IProgress interface.
665 *
666 * @returns HRESULT
667 * @param ppProgress Where to query the progress object to.
668 */
669HRESULT GuestDnDResponse::queryProgressTo(IProgress **ppProgress)
670{
671 return m_pProgress.queryInterfaceTo(ppProgress);
672}
673
674/**
675 * Waits for a guest response to happen.
676 *
677 * @returns VBox status code.
678 * @param msTimeout Timeout (in ms) for waiting. Optional, waits 500 ms if not specified.
679 */
680int GuestDnDResponse::waitForGuestResponse(RTMSINTERVAL msTimeout /*= 500 */) const
681{
682 int rc = RTSemEventWait(m_EventSem, msTimeout);
683#ifdef DEBUG_andy
684 LogFlowFunc(("msTimeout=%RU32, rc=%Rrc\n", msTimeout, rc));
685#endif
686 return rc;
687}
688
689/*********************************************************************************************************************************
690 * GuestDnD implementation. *
691 ********************************************************************************************************************************/
692
693/** Static (Singleton) instance of the GuestDnD object. */
694GuestDnD* GuestDnD::s_pInstance = NULL;
695
696GuestDnD::GuestDnD(const ComObjPtr<Guest> &pGuest)
697 : m_pGuest(pGuest)
698 , m_cTransfersPending(0)
699{
700 LogFlowFuncEnter();
701
702 try
703 {
704 m_pResponse = new GuestDnDResponse(pGuest);
705 }
706 catch (std::bad_alloc &)
707 {
708 throw VERR_NO_MEMORY;
709 }
710
711 int rc = RTCritSectInit(&m_CritSect);
712 if (RT_FAILURE(rc))
713 throw rc;
714
715 /* List of supported default MIME types. */
716 LogRel2(("DnD: Supported default host formats:\n"));
717 const com::Utf8Str arrEntries[] = { VBOX_DND_FORMATS_DEFAULT };
718 for (size_t i = 0; i < RT_ELEMENTS(arrEntries); i++)
719 {
720 m_strDefaultFormats.push_back(arrEntries[i]);
721 LogRel2(("DnD: \t%s\n", arrEntries[i].c_str()));
722 }
723}
724
725GuestDnD::~GuestDnD(void)
726{
727 LogFlowFuncEnter();
728
729 Assert(m_cTransfersPending == 0); /* Sanity. */
730
731 RTCritSectDelete(&m_CritSect);
732
733 if (m_pResponse)
734 delete m_pResponse;
735}
736
737/**
738 * Adjusts coordinations to a given screen.
739 *
740 * @returns HRESULT
741 * @param uScreenId ID of screen to adjust coordinates to.
742 * @param puX Pointer to X coordinate to adjust. Will return the adjusted value on success.
743 * @param puY Pointer to Y coordinate to adjust. Will return the adjusted value on success.
744 */
745HRESULT GuestDnD::adjustScreenCoordinates(ULONG uScreenId, ULONG *puX, ULONG *puY) const
746{
747 /** @todo r=andy Save the current screen's shifting coordinates to speed things up.
748 * Only query for new offsets when the screen ID or the screen's resolution has changed. */
749
750 /* For multi-monitor support we need to add shift values to the coordinates
751 * (depending on the screen number). */
752 ComObjPtr<Console> pConsole = m_pGuest->i_getConsole();
753 ComPtr<IDisplay> pDisplay;
754 HRESULT hr = pConsole->COMGETTER(Display)(pDisplay.asOutParam());
755 if (FAILED(hr))
756 return hr;
757
758 ULONG dummy;
759 LONG xShift, yShift;
760 GuestMonitorStatus_T monitorStatus;
761 hr = pDisplay->GetScreenResolution(uScreenId, &dummy, &dummy, &dummy,
762 &xShift, &yShift, &monitorStatus);
763 if (FAILED(hr))
764 return hr;
765
766 if (puX)
767 *puX += xShift;
768 if (puY)
769 *puY += yShift;
770
771 LogFlowFunc(("uScreenId=%RU32, x=%RU32, y=%RU32\n", uScreenId, puX ? *puX : 0, puY ? *puY : 0));
772 return S_OK;
773}
774
775/**
776 * Sends a (blocking) message to the host side of the host service.
777 *
778 * @returns VBox status code.
779 * @param u32Function HGCM message ID to send.
780 * @param cParms Number of parameters to send.
781 * @param paParms Array of parameters to send. Must match \c cParms.
782 */
783int GuestDnD::hostCall(uint32_t u32Function, uint32_t cParms, PVBOXHGCMSVCPARM paParms) const
784{
785 Assert(!m_pGuest.isNull());
786 ComObjPtr<Console> pConsole = m_pGuest->i_getConsole();
787
788 /* Forward the information to the VMM device. */
789 Assert(!pConsole.isNull());
790 VMMDev *pVMMDev = pConsole->i_getVMMDev();
791 if (!pVMMDev)
792 return VERR_COM_OBJECT_NOT_FOUND;
793
794 return pVMMDev->hgcmHostCall("VBoxDragAndDropSvc", u32Function, cParms, paParms);
795}
796
797/**
798 * Registers a GuestDnDSource object with the GuestDnD manager.
799 *
800 * Currently only one source is supported at a time.
801 *
802 * @returns VBox status code.
803 * @param Source Source to register.
804 */
805int GuestDnD::registerSource(const ComObjPtr<GuestDnDSource> &Source)
806{
807 GUESTDND_LOCK();
808
809 Assert(m_lstSrc.size() == 0); /* We only support one source at a time at the moment. */
810 m_lstSrc.push_back(Source);
811
812 GUESTDND_UNLOCK();
813 return VINF_SUCCESS;
814}
815
816/**
817 * Unregisters a GuestDnDSource object from the GuestDnD manager.
818 *
819 * @returns VBox status code.
820 * @param Source Source to unregister.
821 */
822int GuestDnD::unregisterSource(const ComObjPtr<GuestDnDSource> &Source)
823{
824 GUESTDND_LOCK();
825
826 GuestDnDSrcList::iterator itSrc = std::find(m_lstSrc.begin(), m_lstSrc.end(), Source);
827 if (itSrc != m_lstSrc.end())
828 m_lstSrc.erase(itSrc);
829
830 GUESTDND_UNLOCK();
831 return VINF_SUCCESS;
832}
833
834/**
835 * Returns the current number of registered sources.
836 *
837 * @returns Current number of registered sources.
838 */
839size_t GuestDnD::getSourceCount(void)
840{
841 GUESTDND_LOCK_RET(0);
842
843 size_t cSources = m_lstSrc.size();
844
845 GUESTDND_UNLOCK();
846 return cSources;
847}
848
849/**
850 * Registers a GuestDnDTarget object with the GuestDnD manager.
851 *
852 * Currently only one target is supported at a time.
853 *
854 * @returns VBox status code.
855 * @param Target Target to register.
856 */
857int GuestDnD::registerTarget(const ComObjPtr<GuestDnDTarget> &Target)
858{
859 GUESTDND_LOCK();
860
861 Assert(m_lstTgt.size() == 0); /* We only support one target at a time at the moment. */
862 m_lstTgt.push_back(Target);
863
864 GUESTDND_UNLOCK();
865 return VINF_SUCCESS;
866}
867
868/**
869 * Unregisters a GuestDnDTarget object from the GuestDnD manager.
870 *
871 * @returns VBox status code.
872 * @param Target Target to unregister.
873 */
874int GuestDnD::unregisterTarget(const ComObjPtr<GuestDnDTarget> &Target)
875{
876 GUESTDND_LOCK();
877
878 GuestDnDTgtList::iterator itTgt = std::find(m_lstTgt.begin(), m_lstTgt.end(), Target);
879 if (itTgt != m_lstTgt.end())
880 m_lstTgt.erase(itTgt);
881
882 GUESTDND_UNLOCK();
883 return VINF_SUCCESS;
884}
885
886/**
887 * Returns the current number of registered targets.
888 *
889 * @returns Current number of registered targets.
890 */
891size_t GuestDnD::getTargetCount(void)
892{
893 GUESTDND_LOCK_RET(0);
894
895 size_t cTargets = m_lstTgt.size();
896
897 GUESTDND_UNLOCK();
898 return cTargets;
899}
900
901/**
902 * Static main dispatcher function to handle callbacks from the DnD host service.
903 *
904 * @returns VBox status code.
905 * @param pvExtension Pointer to service extension.
906 * @param u32Function Callback HGCM message ID.
907 * @param pvParms Pointer to optional data provided for a particular message. Optional.
908 * @param cbParms Size (in bytes) of \a pvParms.
909 */
910/* static */
911DECLCALLBACK(int) GuestDnD::notifyDnDDispatcher(void *pvExtension, uint32_t u32Function,
912 void *pvParms, uint32_t cbParms)
913{
914 LogFlowFunc(("pvExtension=%p, u32Function=%RU32, pvParms=%p, cbParms=%RU32\n",
915 pvExtension, u32Function, pvParms, cbParms));
916
917 GuestDnD *pGuestDnD = reinterpret_cast<GuestDnD*>(pvExtension);
918 AssertPtrReturn(pGuestDnD, VERR_INVALID_POINTER);
919
920 /** @todo In case we need to handle multiple guest DnD responses at a time this
921 * would be the place to lookup and dispatch to those. For the moment we
922 * only have one response -- simple. */
923 GuestDnDResponse *pResp = pGuestDnD->m_pResponse;
924 if (pResp)
925 return pResp->onDispatch(u32Function, pvParms, cbParms);
926
927 return VERR_NOT_SUPPORTED;
928}
929
930/**
931 * Static helper function to determine whether a format is part of a given MIME list.
932 *
933 * @returns \c true if found, \c false if not.
934 * @param strFormat Format to search for.
935 * @param lstFormats MIME list to search in.
936 */
937/* static */
938bool GuestDnD::isFormatInFormatList(const com::Utf8Str &strFormat, const GuestDnDMIMEList &lstFormats)
939{
940 return std::find(lstFormats.begin(), lstFormats.end(), strFormat) != lstFormats.end();
941}
942
943/**
944 * Static helper function to create a GuestDnDMIMEList out of a format list string.
945 *
946 * @returns MIME list object.
947 * @param strFormats List of formats to convert.
948 * @param strSep Separator to use. If not specified, DND_FORMATS_SEPARATOR will be used.
949 */
950/* static */
951GuestDnDMIMEList GuestDnD::toFormatList(const com::Utf8Str &strFormats, const com::Utf8Str &strSep /* = DND_FORMATS_SEPARATOR */)
952{
953 GuestDnDMIMEList lstFormats;
954 RTCList<RTCString> lstFormatsTmp = strFormats.split(strSep);
955
956 for (size_t i = 0; i < lstFormatsTmp.size(); i++)
957 lstFormats.push_back(com::Utf8Str(lstFormatsTmp.at(i)));
958
959 return lstFormats;
960}
961
962/**
963 * Static helper function to create a format list string from a given GuestDnDMIMEList object.
964 *
965 * @returns Format list string.
966 * @param lstFormats GuestDnDMIMEList to convert.
967 */
968/* static */
969com::Utf8Str GuestDnD::toFormatString(const GuestDnDMIMEList &lstFormats)
970{
971 com::Utf8Str strFormat;
972 for (size_t i = 0; i < lstFormats.size(); i++)
973 {
974 const com::Utf8Str &f = lstFormats.at(i);
975 strFormat += f + DND_FORMATS_SEPARATOR;
976 }
977
978 return strFormat;
979}
980
981/**
982 * Static helper function to create a filtered GuestDnDMIMEList object from supported and wanted formats.
983 *
984 * @returns Filtered MIME list object.
985 * @param lstFormatsSupported MIME list of supported formats.
986 * @param lstFormatsWanted MIME list of wanted formats in returned object.
987 */
988/* static */
989GuestDnDMIMEList GuestDnD::toFilteredFormatList(const GuestDnDMIMEList &lstFormatsSupported, const GuestDnDMIMEList &lstFormatsWanted)
990{
991 GuestDnDMIMEList lstFormats;
992
993 for (size_t i = 0; i < lstFormatsWanted.size(); i++)
994 {
995 /* Only keep supported format types. */
996 if (std::find(lstFormatsSupported.begin(),
997 lstFormatsSupported.end(), lstFormatsWanted.at(i)) != lstFormatsSupported.end())
998 {
999 lstFormats.push_back(lstFormatsWanted[i]);
1000 }
1001 }
1002
1003 return lstFormats;
1004}
1005
1006/**
1007 * Static helper function to create a filtered GuestDnDMIMEList object from supported and wanted formats.
1008 *
1009 * @returns Filtered MIME list object.
1010 * @param lstFormatsSupported MIME list of supported formats.
1011 * @param strFormatsWanted Format list string of wanted formats in returned object.
1012 */
1013/* static */
1014GuestDnDMIMEList GuestDnD::toFilteredFormatList(const GuestDnDMIMEList &lstFormatsSupported, const com::Utf8Str &strFormatsWanted)
1015{
1016 GuestDnDMIMEList lstFmt;
1017
1018 RTCList<RTCString> lstFormats = strFormatsWanted.split(DND_FORMATS_SEPARATOR);
1019 size_t i = 0;
1020 while (i < lstFormats.size())
1021 {
1022 /* Only keep allowed format types. */
1023 if (std::find(lstFormatsSupported.begin(),
1024 lstFormatsSupported.end(), lstFormats.at(i)) != lstFormatsSupported.end())
1025 {
1026 lstFmt.push_back(lstFormats[i]);
1027 }
1028 i++;
1029 }
1030
1031 return lstFmt;
1032}
1033
1034/**
1035 * Static helper function to convert a Main DnD action an internal DnD action.
1036 *
1037 * @returns Internal DnD action, or VBOX_DND_ACTION_IGNORE if not found / supported.
1038 * @param enmAction Main DnD action to convert.
1039 */
1040/* static */
1041VBOXDNDACTION GuestDnD::toHGCMAction(DnDAction_T enmAction)
1042{
1043 VBOXDNDACTION dndAction = VBOX_DND_ACTION_IGNORE;
1044 switch (enmAction)
1045 {
1046 case DnDAction_Copy:
1047 dndAction = VBOX_DND_ACTION_COPY;
1048 break;
1049 case DnDAction_Move:
1050 dndAction = VBOX_DND_ACTION_MOVE;
1051 break;
1052 case DnDAction_Link:
1053 /* For now it doesn't seems useful to allow a link
1054 action between host & guest. Later? */
1055 case DnDAction_Ignore:
1056 /* Ignored. */
1057 break;
1058 default:
1059 AssertMsgFailed(("Action %RU32 not recognized!\n", enmAction));
1060 break;
1061 }
1062
1063 return dndAction;
1064}
1065
1066/**
1067 * Static helper function to convert a Main DnD default action and allowed Main actions to their
1068 * corresponding internal representations.
1069 *
1070 * @param enmDnDActionDefault Default Main action to convert.
1071 * @param pDnDActionDefault Where to store the converted default action.
1072 * @param vecDnDActionsAllowed Allowed Main actions to convert.
1073 * @param pDnDLstActionsAllowed Where to store the converted allowed actions.
1074 */
1075/* static */
1076void GuestDnD::toHGCMActions(DnDAction_T enmDnDActionDefault,
1077 VBOXDNDACTION *pDnDActionDefault,
1078 const std::vector<DnDAction_T> vecDnDActionsAllowed,
1079 VBOXDNDACTIONLIST *pDnDLstActionsAllowed)
1080{
1081 VBOXDNDACTIONLIST dndLstActionsAllowed = VBOX_DND_ACTION_IGNORE;
1082 VBOXDNDACTION dndActionDefault = toHGCMAction(enmDnDActionDefault);
1083
1084 if (!vecDnDActionsAllowed.empty())
1085 {
1086 /* First convert the allowed actions to a bit array. */
1087 for (size_t i = 0; i < vecDnDActionsAllowed.size(); i++)
1088 dndLstActionsAllowed |= toHGCMAction(vecDnDActionsAllowed[i]);
1089
1090 /*
1091 * If no default action is set (ignoring), try one of the
1092 * set allowed actions, preferring copy, move (in that order).
1093 */
1094 if (isDnDIgnoreAction(dndActionDefault))
1095 {
1096 if (hasDnDCopyAction(dndLstActionsAllowed))
1097 dndActionDefault = VBOX_DND_ACTION_COPY;
1098 else if (hasDnDMoveAction(dndLstActionsAllowed))
1099 dndActionDefault = VBOX_DND_ACTION_MOVE;
1100 }
1101 }
1102
1103 if (pDnDActionDefault)
1104 *pDnDActionDefault = dndActionDefault;
1105 if (pDnDLstActionsAllowed)
1106 *pDnDLstActionsAllowed = dndLstActionsAllowed;
1107}
1108
1109/**
1110 * Static helper function to convert an internal DnD action to its Main representation.
1111 *
1112 * @returns Converted Main DnD action.
1113 * @param dndAction DnD action to convert.
1114 */
1115/* static */
1116DnDAction_T GuestDnD::toMainAction(VBOXDNDACTION dndAction)
1117{
1118 /* For now it doesn't seems useful to allow a
1119 * link action between host & guest. Maybe later! */
1120 return isDnDCopyAction(dndAction) ? DnDAction_Copy
1121 : isDnDMoveAction(dndAction) ? DnDAction_Move
1122 : DnDAction_Ignore;
1123}
1124
1125/**
1126 * Static helper function to convert an internal DnD action list to its Main representation.
1127 *
1128 * @returns Converted Main DnD action list.
1129 * @param dndActionList DnD action list to convert.
1130 */
1131/* static */
1132std::vector<DnDAction_T> GuestDnD::toMainActions(VBOXDNDACTIONLIST dndActionList)
1133{
1134 std::vector<DnDAction_T> vecActions;
1135
1136 /* For now it doesn't seems useful to allow a
1137 * link action between host & guest. Maybe later! */
1138 RTCList<DnDAction_T> lstActions;
1139 if (hasDnDCopyAction(dndActionList))
1140 lstActions.append(DnDAction_Copy);
1141 if (hasDnDMoveAction(dndActionList))
1142 lstActions.append(DnDAction_Move);
1143
1144 for (size_t i = 0; i < lstActions.size(); ++i)
1145 vecActions.push_back(lstActions.at(i));
1146
1147 return vecActions;
1148}
1149
1150/*********************************************************************************************************************************
1151 * GuestDnDBase implementation. *
1152 ********************************************************************************************************************************/
1153
1154GuestDnDBase::GuestDnDBase(void)
1155 : m_fIsPending(false)
1156{
1157 /* Initialize public attributes. */
1158 m_lstFmtSupported = GuestDnDInst()->defaultFormats();
1159
1160 /* Initialzie private stuff. */
1161 m_DataBase.uProtocolVersion = 0;
1162}
1163
1164/**
1165 * Checks whether a given DnD format is supported or not.
1166 *
1167 * @returns \c true if supported, \c false if not.
1168 * @param aFormat DnD format to check.
1169 */
1170bool GuestDnDBase::i_isFormatSupported(const com::Utf8Str &aFormat) const
1171{
1172 return std::find(m_lstFmtSupported.begin(), m_lstFmtSupported.end(), aFormat) != m_lstFmtSupported.end();
1173}
1174
1175/**
1176 * Returns the currently supported DnD formats.
1177 *
1178 * @returns List of the supported DnD formats.
1179 */
1180const GuestDnDMIMEList &GuestDnDBase::i_getFormats(void) const
1181{
1182 return m_lstFmtSupported;
1183}
1184
1185/**
1186 * Adds DnD formats to the supported formats list.
1187 *
1188 * @returns HRESULT
1189 * @param aFormats List of DnD formats to add.
1190 */
1191HRESULT GuestDnDBase::i_addFormats(const GuestDnDMIMEList &aFormats)
1192{
1193 for (size_t i = 0; i < aFormats.size(); ++i)
1194 {
1195 Utf8Str strFormat = aFormats.at(i);
1196 if (std::find(m_lstFmtSupported.begin(),
1197 m_lstFmtSupported.end(), strFormat) == m_lstFmtSupported.end())
1198 {
1199 m_lstFmtSupported.push_back(strFormat);
1200 }
1201 }
1202
1203 return S_OK;
1204}
1205
1206/**
1207 * Removes DnD formats from tehh supported formats list.
1208 *
1209 * @returns HRESULT
1210 * @param aFormats List of DnD formats to remove.
1211 */
1212HRESULT GuestDnDBase::i_removeFormats(const GuestDnDMIMEList &aFormats)
1213{
1214 for (size_t i = 0; i < aFormats.size(); ++i)
1215 {
1216 Utf8Str strFormat = aFormats.at(i);
1217 GuestDnDMIMEList::iterator itFormat = std::find(m_lstFmtSupported.begin(),
1218 m_lstFmtSupported.end(), strFormat);
1219 if (itFormat != m_lstFmtSupported.end())
1220 m_lstFmtSupported.erase(itFormat);
1221 }
1222
1223 return S_OK;
1224}
1225
1226/* Deprecated. */
1227HRESULT GuestDnDBase::i_getProtocolVersion(ULONG *puVersion)
1228{
1229 int rc = getProtocolVersion((uint32_t *)puVersion);
1230 return RT_SUCCESS(rc) ? S_OK : E_FAIL;
1231}
1232
1233/**
1234 * Tries to guess the DnD protocol version to use on the guest, based on the
1235 * installed Guest Additions version + revision.
1236 *
1237 * Deprecated.
1238 *
1239 * If unable to retrieve the protocol version, VERR_NOT_FOUND is returned along
1240 * with protocol version 1.
1241 *
1242 * @return IPRT status code.
1243 * @param puProto Where to store the protocol version.
1244 */
1245int GuestDnDBase::getProtocolVersion(uint32_t *puProto)
1246{
1247 AssertPtrReturn(puProto, VERR_INVALID_POINTER);
1248
1249 int rc;
1250
1251 uint32_t uProto = 0;
1252 uint32_t uVerAdditions;
1253 uint32_t uRevAdditions;
1254 if ( m_pGuest
1255 && (uVerAdditions = m_pGuest->i_getAdditionsVersion()) > 0
1256 && (uRevAdditions = m_pGuest->i_getAdditionsRevision()) > 0)
1257 {
1258#if 0 && defined(DEBUG)
1259 /* Hardcode the to-used protocol version; nice for testing side effects. */
1260 if (true)
1261 uProto = 3;
1262 else
1263#endif
1264 if (uVerAdditions >= VBOX_FULL_VERSION_MAKE(5, 0, 0))
1265 {
1266/** @todo
1267 * r=bird: This is just too bad for anyone using an OSE additions build...
1268 */
1269 if (uRevAdditions >= 103344) /* Since r103344: Protocol v3. */
1270 uProto = 3;
1271 else
1272 uProto = 2; /* VBox 5.0.0 - 5.0.8: Protocol v2. */
1273 }
1274 /* else: uProto: 0 */
1275
1276 LogFlowFunc(("uVerAdditions=%RU32 (%RU32.%RU32.%RU32), r%RU32\n",
1277 uVerAdditions, VBOX_FULL_VERSION_GET_MAJOR(uVerAdditions), VBOX_FULL_VERSION_GET_MINOR(uVerAdditions),
1278 VBOX_FULL_VERSION_GET_BUILD(uVerAdditions), uRevAdditions));
1279 rc = VINF_SUCCESS;
1280 }
1281 else
1282 {
1283 uProto = 1; /* Fallback. */
1284 rc = VERR_NOT_FOUND;
1285 }
1286
1287 LogRel2(("DnD: Guest is using protocol v%RU32, rc=%Rrc\n", uProto, rc));
1288
1289 *puProto = uProto;
1290 return rc;
1291}
1292
1293/**
1294 * Adds a new guest DnD message to the internal message queue.
1295 *
1296 * @returns VBox status code.
1297 * @param pMsg Pointer to message to add.
1298 */
1299int GuestDnDBase::msgQueueAdd(GuestDnDMsg *pMsg)
1300{
1301 m_DataBase.lstMsgOut.push_back(pMsg);
1302 return VINF_SUCCESS;
1303}
1304
1305/**
1306 * Returns the next guest DnD message in the internal message queue (FIFO).
1307 *
1308 * @returns Pointer to guest DnD message, or NULL if none found.
1309 */
1310GuestDnDMsg *GuestDnDBase::msgQueueGetNext(void)
1311{
1312 if (m_DataBase.lstMsgOut.empty())
1313 return NULL;
1314 return m_DataBase.lstMsgOut.front();
1315}
1316
1317/**
1318 * Removes the next guest DnD message from the internal message queue.
1319 */
1320void GuestDnDBase::msgQueueRemoveNext(void)
1321{
1322 if (!m_DataBase.lstMsgOut.empty())
1323 {
1324 GuestDnDMsg *pMsg = m_DataBase.lstMsgOut.front();
1325 if (pMsg)
1326 delete pMsg;
1327 m_DataBase.lstMsgOut.pop_front();
1328 }
1329}
1330
1331/**
1332 * Clears the internal message queue.
1333 */
1334void GuestDnDBase::msgQueueClear(void)
1335{
1336 LogFlowFunc(("cMsg=%zu\n", m_DataBase.lstMsgOut.size()));
1337
1338 GuestDnDMsgList::iterator itMsg = m_DataBase.lstMsgOut.begin();
1339 while (itMsg != m_DataBase.lstMsgOut.end())
1340 {
1341 GuestDnDMsg *pMsg = *itMsg;
1342 if (pMsg)
1343 delete pMsg;
1344
1345 itMsg++;
1346 }
1347
1348 m_DataBase.lstMsgOut.clear();
1349}
1350
1351/**
1352 * Sends a request to the guest side to cancel the current DnD operation.
1353 *
1354 * @returns VBox status code.
1355 */
1356int GuestDnDBase::sendCancel(void)
1357{
1358 GuestDnDMsg Msg;
1359 Msg.setType(HOST_DND_CANCEL);
1360 if (m_DataBase.uProtocolVersion >= 3)
1361 Msg.appendUInt32(0); /** @todo ContextID not used yet. */
1362
1363 LogRel2(("DnD: Cancelling operation on guest ...\n"));
1364
1365 int rc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
1366 if (RT_FAILURE(rc))
1367 LogRel(("DnD: Cancelling operation on guest failed with %Rrc\n", rc));
1368
1369 return rc;
1370}
1371
1372/**
1373 * Helper function to update the progress based on given a GuestDnDData object.
1374 *
1375 * @returns VBox status code.
1376 * @param pData GuestDnDData object to use for accounting.
1377 * @param pResp GuestDnDResponse to update its progress object for.
1378 * @param cbDataAdd By how much data (in bytes) to update the progress.
1379 */
1380int GuestDnDBase::updateProgress(GuestDnDData *pData, GuestDnDResponse *pResp,
1381 size_t cbDataAdd /* = 0 */)
1382{
1383 AssertPtrReturn(pData, VERR_INVALID_POINTER);
1384 AssertPtrReturn(pResp, VERR_INVALID_POINTER);
1385 /* cbDataAdd is optional. */
1386
1387 LogFlowFunc(("cbExtra=%zu, cbProcessed=%zu, cbRemaining=%zu, cbDataAdd=%zu\n",
1388 pData->cbExtra, pData->cbProcessed, pData->getRemaining(), cbDataAdd));
1389
1390 if ( !pResp
1391 || !cbDataAdd) /* Only update if something really changes. */
1392 return VINF_SUCCESS;
1393
1394 if (cbDataAdd)
1395 pData->addProcessed(cbDataAdd);
1396
1397 const uint8_t uPercent = pData->getPercentComplete();
1398
1399 LogRel2(("DnD: Transfer %RU8%% complete\n", uPercent));
1400
1401 int rc = pResp->setProgress(uPercent,
1402 pData->isComplete()
1403 ? DND_PROGRESS_COMPLETE
1404 : DND_PROGRESS_RUNNING);
1405 LogFlowFuncLeaveRC(rc);
1406 return rc;
1407}
1408
1409/**
1410 * Waits for a specific guest callback event to get signalled.
1411 *
1412 ** @todo GuestDnDResponse *pResp needs to go.
1413 *
1414 * @returns VBox status code. Will return VERR_CANCELLED if the user has cancelled the progress object.
1415 * @param pEvent Callback event to wait for.
1416 * @param pResp Response to update.
1417 * @param msTimeout Timeout (in ms) to wait.
1418 */
1419int GuestDnDBase::waitForEvent(GuestDnDCallbackEvent *pEvent, GuestDnDResponse *pResp, RTMSINTERVAL msTimeout)
1420{
1421 AssertPtrReturn(pEvent, VERR_INVALID_POINTER);
1422 AssertPtrReturn(pResp, VERR_INVALID_POINTER);
1423
1424 int rc;
1425
1426 LogFlowFunc(("msTimeout=%RU32\n", msTimeout));
1427
1428 uint64_t tsStart = RTTimeMilliTS();
1429 do
1430 {
1431 /*
1432 * Wait until our desired callback triggered the
1433 * wait event. As we don't want to block if the guest does not
1434 * respond, do busy waiting here.
1435 */
1436 rc = pEvent->Wait(500 /* ms */);
1437 if (RT_SUCCESS(rc))
1438 {
1439 rc = pEvent->Result();
1440 LogFlowFunc(("Callback done, result is %Rrc\n", rc));
1441 break;
1442 }
1443 else if (rc == VERR_TIMEOUT) /* Continue waiting. */
1444 rc = VINF_SUCCESS;
1445
1446 if ( msTimeout != RT_INDEFINITE_WAIT
1447 && RTTimeMilliTS() - tsStart > msTimeout)
1448 {
1449 rc = VERR_TIMEOUT;
1450 LogRel2(("DnD: Error: Guest did not respond within time\n"));
1451 }
1452 else if (pResp->isProgressCanceled()) /** @todo GuestDnDResponse *pResp needs to go. */
1453 {
1454 LogRel2(("DnD: Operation was canceled by user\n"));
1455 rc = VERR_CANCELLED;
1456 }
1457
1458 } while (RT_SUCCESS(rc));
1459
1460 LogFlowFuncLeaveRC(rc);
1461 return rc;
1462}
1463#endif /* VBOX_WITH_DRAG_AND_DROP */
1464
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