VirtualBox

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

Last change on this file since 86223 was 85746, checked in by vboxsync, 4 years ago

DnD: Renaming -- DND_FORMATS_SEPARATOR -> DND_FORMATS_SEPARATOR_STR and DND_PATH_SEPARATOR -> DND_PATH_SEPARATOR_STR.

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

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