VirtualBox

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

Last change on this file since 85698 was 85681, checked in by vboxsync, 5 years ago

DnD: Lots of documentation.

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