VirtualBox

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

Last change on this file since 96229 was 95822, checked in by vboxsync, 3 years ago

DnD: Added GuestHost/DnDUtils module for various utility functions, (release2) log the acknowledged / pending actions by the guest, to make it easier in the future to diagnose DnD errors [better logging text]. bugref:10267

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 46.5 KB
Line 
1/* $Id: GuestDnDPrivate.cpp 95822 2022-07-25 16:02:40Z vboxsync $ */
2/** @file
3 * Private guest drag and drop code, used by GuestDnDTarget + GuestDnDSource.
4 */
5
6/*
7 * Copyright (C) 2011-2022 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(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 LogRel2(("DnD: Guest responded with action '%s' for host->guest drag event\n", DnDActionToStr(pCBData->uAction)));
575
576 setActionDefault(pCBData->uAction);
577 rc = notifyAboutGuestResponse();
578 break;
579 }
580
581 case DragAndDropSvc::GUEST_DND_FN_HG_REQ_DATA:
582 {
583 DragAndDropSvc::PVBOXDNDCBHGREQDATADATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBHGREQDATADATA>(pvParms);
584 AssertPtr(pCBData);
585 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBHGREQDATADATA) == cbParms, VERR_INVALID_PARAMETER);
586 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_HG_REQ_DATA == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
587
588 if ( pCBData->cbFormat == 0
589 || pCBData->cbFormat > _64K /** @todo Make this configurable? */
590 || pCBData->pszFormat == NULL)
591 {
592 rc = VERR_INVALID_PARAMETER;
593 }
594 else if (!RTStrIsValidEncoding(pCBData->pszFormat))
595 {
596 rc = VERR_INVALID_PARAMETER;
597 }
598 else
599 {
600 setFormats(GuestDnD::toFormatList(pCBData->pszFormat));
601 rc = VINF_SUCCESS;
602 }
603
604 int rc2 = notifyAboutGuestResponse();
605 if (RT_SUCCESS(rc))
606 rc = rc2;
607 break;
608 }
609
610 case DragAndDropSvc::GUEST_DND_FN_HG_EVT_PROGRESS:
611 {
612 DragAndDropSvc::PVBOXDNDCBHGEVTPROGRESSDATA pCBData =
613 reinterpret_cast<DragAndDropSvc::PVBOXDNDCBHGEVTPROGRESSDATA>(pvParms);
614 AssertPtr(pCBData);
615 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBHGEVTPROGRESSDATA) == cbParms, VERR_INVALID_PARAMETER);
616 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_HG_EVT_PROGRESS == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
617
618 rc = setProgress(pCBData->uPercentage, pCBData->uStatus, pCBData->rc);
619 if (RT_SUCCESS(rc))
620 rc = notifyAboutGuestResponse();
621 break;
622 }
623#ifdef VBOX_WITH_DRAG_AND_DROP_GH
624 case DragAndDropSvc::GUEST_DND_FN_GH_ACK_PENDING:
625 {
626 DragAndDropSvc::PVBOXDNDCBGHACKPENDINGDATA pCBData =
627 reinterpret_cast<DragAndDropSvc::PVBOXDNDCBGHACKPENDINGDATA>(pvParms);
628 AssertPtr(pCBData);
629 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBGHACKPENDINGDATA) == cbParms, VERR_INVALID_PARAMETER);
630 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_GH_ACK_PENDING == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
631
632 LogRel2(("DnD: Guest responded with pending action '%s' (%RU32 bytes format data) to guest->host drag event\n",
633 DnDActionToStr(pCBData->uDefAction), pCBData->cbFormat));
634
635 if ( pCBData->cbFormat == 0
636 || pCBData->cbFormat > _64K /** @todo Make the maximum size configurable? */
637 || pCBData->pszFormat == NULL)
638 {
639 rc = VERR_INVALID_PARAMETER;
640 }
641 else if (!RTStrIsValidEncoding(pCBData->pszFormat))
642 {
643 rc = VERR_INVALID_PARAMETER;
644 }
645 else
646 {
647 setFormats (GuestDnD::toFormatList(pCBData->pszFormat));
648 setActionDefault (pCBData->uDefAction);
649 setActionsAllowed(pCBData->uAllActions);
650
651 rc = VINF_SUCCESS;
652 }
653
654 int rc2 = notifyAboutGuestResponse();
655 if (RT_SUCCESS(rc))
656 rc = rc2;
657 break;
658 }
659#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
660 default:
661 /* * Try if the event is covered by a registered callback. */
662 fTryCallbacks = true;
663 break;
664 }
665
666 /*
667 * Try the host's installed callbacks (if any).
668 */
669 if (fTryCallbacks)
670 {
671 GuestDnDCallbackMap::const_iterator it = m_mapCallbacks.find(u32Function);
672 if (it != m_mapCallbacks.end())
673 {
674 AssertPtr(it->second.pfnCallback);
675 rc = it->second.pfnCallback(u32Function, pvParms, cbParms, it->second.pvUser);
676 }
677 else
678 {
679 LogFlowFunc(("No callback for function %RU32 defined (%zu callbacks total)\n", u32Function, m_mapCallbacks.size()));
680 rc = VERR_NOT_SUPPORTED; /* Tell the guest. */
681 }
682 }
683
684 LogFlowFunc(("Returning rc=%Rrc\n", rc));
685 return rc;
686}
687
688/**
689 * Helper function to query the internal progress object to an IProgress interface.
690 *
691 * @returns HRESULT
692 * @param ppProgress Where to query the progress object to.
693 */
694HRESULT GuestDnDState::queryProgressTo(IProgress **ppProgress)
695{
696 return m_pProgress.queryInterfaceTo(ppProgress);
697}
698
699/**
700 * Waits for a guest response to happen.
701 *
702 * @returns VBox status code.
703 * @param msTimeout Timeout (in ms) for waiting. Optional, waits 500 ms if not specified.
704 */
705int GuestDnDState::waitForGuestResponse(RTMSINTERVAL msTimeout /*= 500 */) const
706{
707 int rc = RTSemEventWait(m_EventSem, msTimeout);
708#ifdef DEBUG_andy
709 LogFlowFunc(("msTimeout=%RU32, rc=%Rrc\n", msTimeout, rc));
710#endif
711 return rc;
712}
713
714/*********************************************************************************************************************************
715 * GuestDnD implementation. *
716 ********************************************************************************************************************************/
717
718/** Static (Singleton) instance of the GuestDnD object. */
719GuestDnD* GuestDnD::s_pInstance = NULL;
720
721GuestDnD::GuestDnD(const ComObjPtr<Guest> &pGuest)
722 : m_pGuest(pGuest)
723 , m_cTransfersPending(0)
724{
725 LogFlowFuncEnter();
726
727 try
728 {
729 m_pState = new GuestDnDState(pGuest);
730 }
731 catch (std::bad_alloc &)
732 {
733 throw VERR_NO_MEMORY;
734 }
735
736 int rc = RTCritSectInit(&m_CritSect);
737 if (RT_FAILURE(rc))
738 throw rc;
739
740 /* List of supported default MIME types. */
741 LogRel2(("DnD: Supported default host formats:\n"));
742 const com::Utf8Str arrEntries[] = { VBOX_DND_FORMATS_DEFAULT };
743 for (size_t i = 0; i < RT_ELEMENTS(arrEntries); i++)
744 {
745 m_strDefaultFormats.push_back(arrEntries[i]);
746 LogRel2(("DnD: \t%s\n", arrEntries[i].c_str()));
747 }
748}
749
750GuestDnD::~GuestDnD(void)
751{
752 LogFlowFuncEnter();
753
754 Assert(m_cTransfersPending == 0); /* Sanity. */
755
756 RTCritSectDelete(&m_CritSect);
757
758 if (m_pState)
759 delete m_pState;
760}
761
762/**
763 * Adjusts coordinations to a given screen.
764 *
765 * @returns HRESULT
766 * @param uScreenId ID of screen to adjust coordinates to.
767 * @param puX Pointer to X coordinate to adjust. Will return the adjusted value on success.
768 * @param puY Pointer to Y coordinate to adjust. Will return the adjusted value on success.
769 */
770HRESULT GuestDnD::adjustScreenCoordinates(ULONG uScreenId, ULONG *puX, ULONG *puY) const
771{
772 /** @todo r=andy Save the current screen's shifting coordinates to speed things up.
773 * Only query for new offsets when the screen ID or the screen's resolution has changed. */
774
775 /* For multi-monitor support we need to add shift values to the coordinates
776 * (depending on the screen number). */
777 ComObjPtr<Console> pConsole = m_pGuest->i_getConsole();
778 ComPtr<IDisplay> pDisplay;
779 HRESULT hr = pConsole->COMGETTER(Display)(pDisplay.asOutParam());
780 if (FAILED(hr))
781 return hr;
782
783 ULONG dummy;
784 LONG xShift, yShift;
785 GuestMonitorStatus_T monitorStatus;
786 hr = pDisplay->GetScreenResolution(uScreenId, &dummy, &dummy, &dummy,
787 &xShift, &yShift, &monitorStatus);
788 if (FAILED(hr))
789 return hr;
790
791 if (puX)
792 *puX += xShift;
793 if (puY)
794 *puY += yShift;
795
796 LogFlowFunc(("uScreenId=%RU32, x=%RU32, y=%RU32\n", uScreenId, puX ? *puX : 0, puY ? *puY : 0));
797 return S_OK;
798}
799
800/**
801 * Returns a DnD guest state.
802 *
803 * @returns Pointer to DnD guest state, or NULL if not found / invalid.
804 * @param uID ID of DnD guest state to return.
805 */
806GuestDnDState *GuestDnD::getState(uint32_t uID /* = 0 */) const
807{
808 AssertMsgReturn(uID == 0, ("Only one state (0) is supported at the moment\n"), NULL);
809
810 return m_pState;
811}
812
813/**
814 * Sends a (blocking) message to the host side of the host service.
815 *
816 * @returns VBox status code.
817 * @param u32Function HGCM message ID to send.
818 * @param cParms Number of parameters to send.
819 * @param paParms Array of parameters to send. Must match \c cParms.
820 */
821int GuestDnD::hostCall(uint32_t u32Function, uint32_t cParms, PVBOXHGCMSVCPARM paParms) const
822{
823 Assert(!m_pGuest.isNull());
824 ComObjPtr<Console> pConsole = m_pGuest->i_getConsole();
825
826 /* Forward the information to the VMM device. */
827 Assert(!pConsole.isNull());
828 VMMDev *pVMMDev = pConsole->i_getVMMDev();
829 if (!pVMMDev)
830 return VERR_COM_OBJECT_NOT_FOUND;
831
832 return pVMMDev->hgcmHostCall("VBoxDragAndDropSvc", u32Function, cParms, paParms);
833}
834
835/**
836 * Registers a GuestDnDSource object with the GuestDnD manager.
837 *
838 * Currently only one source is supported at a time.
839 *
840 * @returns VBox status code.
841 * @param Source Source to register.
842 */
843int GuestDnD::registerSource(const ComObjPtr<GuestDnDSource> &Source)
844{
845 GUESTDND_LOCK();
846
847 Assert(m_lstSrc.size() == 0); /* We only support one source at a time at the moment. */
848 m_lstSrc.push_back(Source);
849
850 GUESTDND_UNLOCK();
851 return VINF_SUCCESS;
852}
853
854/**
855 * Unregisters a GuestDnDSource object from the GuestDnD manager.
856 *
857 * @returns VBox status code.
858 * @param Source Source to unregister.
859 */
860int GuestDnD::unregisterSource(const ComObjPtr<GuestDnDSource> &Source)
861{
862 GUESTDND_LOCK();
863
864 GuestDnDSrcList::iterator itSrc = std::find(m_lstSrc.begin(), m_lstSrc.end(), Source);
865 if (itSrc != m_lstSrc.end())
866 m_lstSrc.erase(itSrc);
867
868 GUESTDND_UNLOCK();
869 return VINF_SUCCESS;
870}
871
872/**
873 * Returns the current number of registered sources.
874 *
875 * @returns Current number of registered sources.
876 */
877size_t GuestDnD::getSourceCount(void)
878{
879 GUESTDND_LOCK_RET(0);
880
881 size_t cSources = m_lstSrc.size();
882
883 GUESTDND_UNLOCK();
884 return cSources;
885}
886
887/**
888 * Registers a GuestDnDTarget object with the GuestDnD manager.
889 *
890 * Currently only one target is supported at a time.
891 *
892 * @returns VBox status code.
893 * @param Target Target to register.
894 */
895int GuestDnD::registerTarget(const ComObjPtr<GuestDnDTarget> &Target)
896{
897 GUESTDND_LOCK();
898
899 Assert(m_lstTgt.size() == 0); /* We only support one target at a time at the moment. */
900 m_lstTgt.push_back(Target);
901
902 GUESTDND_UNLOCK();
903 return VINF_SUCCESS;
904}
905
906/**
907 * Unregisters a GuestDnDTarget object from the GuestDnD manager.
908 *
909 * @returns VBox status code.
910 * @param Target Target to unregister.
911 */
912int GuestDnD::unregisterTarget(const ComObjPtr<GuestDnDTarget> &Target)
913{
914 GUESTDND_LOCK();
915
916 GuestDnDTgtList::iterator itTgt = std::find(m_lstTgt.begin(), m_lstTgt.end(), Target);
917 if (itTgt != m_lstTgt.end())
918 m_lstTgt.erase(itTgt);
919
920 GUESTDND_UNLOCK();
921 return VINF_SUCCESS;
922}
923
924/**
925 * Returns the current number of registered targets.
926 *
927 * @returns Current number of registered targets.
928 */
929size_t GuestDnD::getTargetCount(void)
930{
931 GUESTDND_LOCK_RET(0);
932
933 size_t cTargets = m_lstTgt.size();
934
935 GUESTDND_UNLOCK();
936 return cTargets;
937}
938
939/**
940 * Static main dispatcher function to handle callbacks from the DnD host service.
941 *
942 * @returns VBox status code.
943 * @param pvExtension Pointer to service extension.
944 * @param u32Function Callback HGCM message ID.
945 * @param pvParms Pointer to optional data provided for a particular message. Optional.
946 * @param cbParms Size (in bytes) of \a pvParms.
947 */
948/* static */
949DECLCALLBACK(int) GuestDnD::notifyDnDDispatcher(void *pvExtension, uint32_t u32Function,
950 void *pvParms, uint32_t cbParms)
951{
952 LogFlowFunc(("pvExtension=%p, u32Function=%RU32, pvParms=%p, cbParms=%RU32\n",
953 pvExtension, u32Function, pvParms, cbParms));
954
955 GuestDnD *pGuestDnD = reinterpret_cast<GuestDnD*>(pvExtension);
956 AssertPtrReturn(pGuestDnD, VERR_INVALID_POINTER);
957
958 /** @todo In case we need to handle multiple guest DnD responses at a time this
959 * would be the place to lookup and dispatch to those. For the moment we
960 * only have one response -- simple. */
961 if (pGuestDnD->m_pState)
962 return pGuestDnD->m_pState->onDispatch(u32Function, pvParms, cbParms);
963
964 return VERR_NOT_SUPPORTED;
965}
966
967/**
968 * Static helper function to determine whether a format is part of a given MIME list.
969 *
970 * @returns \c true if found, \c false if not.
971 * @param strFormat Format to search for.
972 * @param lstFormats MIME list to search in.
973 */
974/* static */
975bool GuestDnD::isFormatInFormatList(const com::Utf8Str &strFormat, const GuestDnDMIMEList &lstFormats)
976{
977 return std::find(lstFormats.begin(), lstFormats.end(), strFormat) != lstFormats.end();
978}
979
980/**
981 * Static helper function to create a GuestDnDMIMEList out of a format list string.
982 *
983 * @returns MIME list object.
984 * @param strFormats List of formats to convert.
985 * @param strSep Separator to use. If not specified, DND_FORMATS_SEPARATOR_STR will be used.
986 */
987/* static */
988GuestDnDMIMEList GuestDnD::toFormatList(const com::Utf8Str &strFormats, const com::Utf8Str &strSep /* = DND_FORMATS_SEPARATOR_STR */)
989{
990 GuestDnDMIMEList lstFormats;
991 RTCList<RTCString> lstFormatsTmp = strFormats.split(strSep);
992
993 for (size_t i = 0; i < lstFormatsTmp.size(); i++)
994 lstFormats.push_back(com::Utf8Str(lstFormatsTmp.at(i)));
995
996 return lstFormats;
997}
998
999/**
1000 * Static helper function to create a format list string from a given GuestDnDMIMEList object.
1001 *
1002 * @returns Format list string.
1003 * @param lstFormats GuestDnDMIMEList to convert.
1004 */
1005/* static */
1006com::Utf8Str GuestDnD::toFormatString(const GuestDnDMIMEList &lstFormats)
1007{
1008 com::Utf8Str strFormat;
1009 for (size_t i = 0; i < lstFormats.size(); i++)
1010 {
1011 const com::Utf8Str &f = lstFormats.at(i);
1012 strFormat += f + DND_FORMATS_SEPARATOR_STR;
1013 }
1014
1015 return strFormat;
1016}
1017
1018/**
1019 * Static helper function to create a filtered GuestDnDMIMEList object from supported and wanted formats.
1020 *
1021 * @returns Filtered MIME list object.
1022 * @param lstFormatsSupported MIME list of supported formats.
1023 * @param lstFormatsWanted MIME list of wanted formats in returned object.
1024 */
1025/* static */
1026GuestDnDMIMEList GuestDnD::toFilteredFormatList(const GuestDnDMIMEList &lstFormatsSupported, const GuestDnDMIMEList &lstFormatsWanted)
1027{
1028 GuestDnDMIMEList lstFormats;
1029
1030 for (size_t i = 0; i < lstFormatsWanted.size(); i++)
1031 {
1032 /* Only keep supported format types. */
1033 if (std::find(lstFormatsSupported.begin(),
1034 lstFormatsSupported.end(), lstFormatsWanted.at(i)) != lstFormatsSupported.end())
1035 {
1036 lstFormats.push_back(lstFormatsWanted[i]);
1037 }
1038 }
1039
1040 return lstFormats;
1041}
1042
1043/**
1044 * Static helper function to create a filtered GuestDnDMIMEList object from supported and wanted formats.
1045 *
1046 * @returns Filtered MIME list object.
1047 * @param lstFormatsSupported MIME list of supported formats.
1048 * @param strFormatsWanted Format list string of wanted formats in returned object.
1049 */
1050/* static */
1051GuestDnDMIMEList GuestDnD::toFilteredFormatList(const GuestDnDMIMEList &lstFormatsSupported, const com::Utf8Str &strFormatsWanted)
1052{
1053 GuestDnDMIMEList lstFmt;
1054
1055 RTCList<RTCString> lstFormats = strFormatsWanted.split(DND_FORMATS_SEPARATOR_STR);
1056 size_t i = 0;
1057 while (i < lstFormats.size())
1058 {
1059 /* Only keep allowed format types. */
1060 if (std::find(lstFormatsSupported.begin(),
1061 lstFormatsSupported.end(), lstFormats.at(i)) != lstFormatsSupported.end())
1062 {
1063 lstFmt.push_back(lstFormats[i]);
1064 }
1065 i++;
1066 }
1067
1068 return lstFmt;
1069}
1070
1071/**
1072 * Static helper function to convert a Main DnD action an internal DnD action.
1073 *
1074 * @returns Internal DnD action, or VBOX_DND_ACTION_IGNORE if not found / supported.
1075 * @param enmAction Main DnD action to convert.
1076 */
1077/* static */
1078VBOXDNDACTION GuestDnD::toHGCMAction(DnDAction_T enmAction)
1079{
1080 VBOXDNDACTION dndAction = VBOX_DND_ACTION_IGNORE;
1081 switch (enmAction)
1082 {
1083 case DnDAction_Copy:
1084 dndAction = VBOX_DND_ACTION_COPY;
1085 break;
1086 case DnDAction_Move:
1087 dndAction = VBOX_DND_ACTION_MOVE;
1088 break;
1089 case DnDAction_Link:
1090 /* For now it doesn't seems useful to allow a link
1091 action between host & guest. Later? */
1092 case DnDAction_Ignore:
1093 /* Ignored. */
1094 break;
1095 default:
1096 AssertMsgFailed(("Action %RU32 not recognized!\n", enmAction));
1097 break;
1098 }
1099
1100 return dndAction;
1101}
1102
1103/**
1104 * Static helper function to convert a Main DnD default action and allowed Main actions to their
1105 * corresponding internal representations.
1106 *
1107 * @param enmDnDActionDefault Default Main action to convert.
1108 * @param pDnDActionDefault Where to store the converted default action.
1109 * @param vecDnDActionsAllowed Allowed Main actions to convert.
1110 * @param pDnDLstActionsAllowed Where to store the converted allowed actions.
1111 */
1112/* static */
1113void GuestDnD::toHGCMActions(DnDAction_T enmDnDActionDefault,
1114 VBOXDNDACTION *pDnDActionDefault,
1115 const std::vector<DnDAction_T> vecDnDActionsAllowed,
1116 VBOXDNDACTIONLIST *pDnDLstActionsAllowed)
1117{
1118 VBOXDNDACTIONLIST dndLstActionsAllowed = VBOX_DND_ACTION_IGNORE;
1119 VBOXDNDACTION dndActionDefault = toHGCMAction(enmDnDActionDefault);
1120
1121 if (!vecDnDActionsAllowed.empty())
1122 {
1123 /* First convert the allowed actions to a bit array. */
1124 for (size_t i = 0; i < vecDnDActionsAllowed.size(); i++)
1125 dndLstActionsAllowed |= toHGCMAction(vecDnDActionsAllowed[i]);
1126
1127 /*
1128 * If no default action is set (ignoring), try one of the
1129 * set allowed actions, preferring copy, move (in that order).
1130 */
1131 if (isDnDIgnoreAction(dndActionDefault))
1132 {
1133 if (hasDnDCopyAction(dndLstActionsAllowed))
1134 dndActionDefault = VBOX_DND_ACTION_COPY;
1135 else if (hasDnDMoveAction(dndLstActionsAllowed))
1136 dndActionDefault = VBOX_DND_ACTION_MOVE;
1137 }
1138 }
1139
1140 if (pDnDActionDefault)
1141 *pDnDActionDefault = dndActionDefault;
1142 if (pDnDLstActionsAllowed)
1143 *pDnDLstActionsAllowed = dndLstActionsAllowed;
1144}
1145
1146/**
1147 * Static helper function to convert an internal DnD action to its Main representation.
1148 *
1149 * @returns Converted Main DnD action.
1150 * @param dndAction DnD action to convert.
1151 */
1152/* static */
1153DnDAction_T GuestDnD::toMainAction(VBOXDNDACTION dndAction)
1154{
1155 /* For now it doesn't seems useful to allow a
1156 * link action between host & guest. Maybe later! */
1157 return isDnDCopyAction(dndAction) ? DnDAction_Copy
1158 : isDnDMoveAction(dndAction) ? DnDAction_Move
1159 : DnDAction_Ignore;
1160}
1161
1162/**
1163 * Static helper function to convert an internal DnD action list to its Main representation.
1164 *
1165 * @returns Converted Main DnD action list.
1166 * @param dndActionList DnD action list to convert.
1167 */
1168/* static */
1169std::vector<DnDAction_T> GuestDnD::toMainActions(VBOXDNDACTIONLIST dndActionList)
1170{
1171 std::vector<DnDAction_T> vecActions;
1172
1173 /* For now it doesn't seems useful to allow a
1174 * link action between host & guest. Maybe later! */
1175 RTCList<DnDAction_T> lstActions;
1176 if (hasDnDCopyAction(dndActionList))
1177 lstActions.append(DnDAction_Copy);
1178 if (hasDnDMoveAction(dndActionList))
1179 lstActions.append(DnDAction_Move);
1180
1181 for (size_t i = 0; i < lstActions.size(); ++i)
1182 vecActions.push_back(lstActions.at(i));
1183
1184 return vecActions;
1185}
1186
1187/*********************************************************************************************************************************
1188 * GuestDnDBase implementation. *
1189 ********************************************************************************************************************************/
1190
1191GuestDnDBase::GuestDnDBase(void)
1192 : m_fIsPending(false)
1193{
1194 /* Initialize public attributes. */
1195 m_lstFmtSupported = GuestDnDInst()->defaultFormats();
1196}
1197
1198/**
1199 * Checks whether a given DnD format is supported or not.
1200 *
1201 * @returns \c true if supported, \c false if not.
1202 * @param aFormat DnD format to check.
1203 */
1204bool GuestDnDBase::i_isFormatSupported(const com::Utf8Str &aFormat) const
1205{
1206 return std::find(m_lstFmtSupported.begin(), m_lstFmtSupported.end(), aFormat) != m_lstFmtSupported.end();
1207}
1208
1209/**
1210 * Returns the currently supported DnD formats.
1211 *
1212 * @returns List of the supported DnD formats.
1213 */
1214const GuestDnDMIMEList &GuestDnDBase::i_getFormats(void) const
1215{
1216 return m_lstFmtSupported;
1217}
1218
1219/**
1220 * Adds DnD formats to the supported formats list.
1221 *
1222 * @returns HRESULT
1223 * @param aFormats List of DnD formats to add.
1224 */
1225HRESULT GuestDnDBase::i_addFormats(const GuestDnDMIMEList &aFormats)
1226{
1227 for (size_t i = 0; i < aFormats.size(); ++i)
1228 {
1229 Utf8Str strFormat = aFormats.at(i);
1230 if (std::find(m_lstFmtSupported.begin(),
1231 m_lstFmtSupported.end(), strFormat) == m_lstFmtSupported.end())
1232 {
1233 m_lstFmtSupported.push_back(strFormat);
1234 }
1235 }
1236
1237 return S_OK;
1238}
1239
1240/**
1241 * Removes DnD formats from tehh supported formats list.
1242 *
1243 * @returns HRESULT
1244 * @param aFormats List of DnD formats to remove.
1245 */
1246HRESULT GuestDnDBase::i_removeFormats(const GuestDnDMIMEList &aFormats)
1247{
1248 for (size_t i = 0; i < aFormats.size(); ++i)
1249 {
1250 Utf8Str strFormat = aFormats.at(i);
1251 GuestDnDMIMEList::iterator itFormat = std::find(m_lstFmtSupported.begin(),
1252 m_lstFmtSupported.end(), strFormat);
1253 if (itFormat != m_lstFmtSupported.end())
1254 m_lstFmtSupported.erase(itFormat);
1255 }
1256
1257 return S_OK;
1258}
1259
1260/**
1261 * Adds a new guest DnD message to the internal message queue.
1262 *
1263 * @returns VBox status code.
1264 * @param pMsg Pointer to message to add.
1265 */
1266int GuestDnDBase::msgQueueAdd(GuestDnDMsg *pMsg)
1267{
1268 m_DataBase.lstMsgOut.push_back(pMsg);
1269 return VINF_SUCCESS;
1270}
1271
1272/**
1273 * Returns the next guest DnD message in the internal message queue (FIFO).
1274 *
1275 * @returns Pointer to guest DnD message, or NULL if none found.
1276 */
1277GuestDnDMsg *GuestDnDBase::msgQueueGetNext(void)
1278{
1279 if (m_DataBase.lstMsgOut.empty())
1280 return NULL;
1281 return m_DataBase.lstMsgOut.front();
1282}
1283
1284/**
1285 * Removes the next guest DnD message from the internal message queue.
1286 */
1287void GuestDnDBase::msgQueueRemoveNext(void)
1288{
1289 if (!m_DataBase.lstMsgOut.empty())
1290 {
1291 GuestDnDMsg *pMsg = m_DataBase.lstMsgOut.front();
1292 if (pMsg)
1293 delete pMsg;
1294 m_DataBase.lstMsgOut.pop_front();
1295 }
1296}
1297
1298/**
1299 * Clears the internal message queue.
1300 */
1301void GuestDnDBase::msgQueueClear(void)
1302{
1303 LogFlowFunc(("cMsg=%zu\n", m_DataBase.lstMsgOut.size()));
1304
1305 GuestDnDMsgList::iterator itMsg = m_DataBase.lstMsgOut.begin();
1306 while (itMsg != m_DataBase.lstMsgOut.end())
1307 {
1308 GuestDnDMsg *pMsg = *itMsg;
1309 if (pMsg)
1310 delete pMsg;
1311
1312 itMsg++;
1313 }
1314
1315 m_DataBase.lstMsgOut.clear();
1316}
1317
1318/**
1319 * Sends a request to the guest side to cancel the current DnD operation.
1320 *
1321 * @returns VBox status code.
1322 */
1323int GuestDnDBase::sendCancel(void)
1324{
1325 GuestDnDMsg Msg;
1326 Msg.setType(HOST_DND_FN_CANCEL);
1327 if (m_pState->m_uProtocolVersion >= 3)
1328 Msg.appendUInt32(0); /** @todo ContextID not used yet. */
1329
1330 LogRel2(("DnD: Cancelling operation on guest ...\n"));
1331
1332 int rc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
1333 if (RT_FAILURE(rc))
1334 LogRel(("DnD: Cancelling operation on guest failed with %Rrc\n", rc));
1335
1336 return rc;
1337}
1338
1339/**
1340 * Helper function to update the progress based on given a GuestDnDData object.
1341 *
1342 * @returns VBox status code.
1343 * @param pData GuestDnDData object to use for accounting.
1344 * @param pState Guest state to update its progress object for.
1345 * @param cbDataAdd By how much data (in bytes) to update the progress.
1346 */
1347int GuestDnDBase::updateProgress(GuestDnDData *pData, GuestDnDState *pState,
1348 size_t cbDataAdd /* = 0 */)
1349{
1350 AssertPtrReturn(pData, VERR_INVALID_POINTER);
1351 AssertPtrReturn(pState, VERR_INVALID_POINTER);
1352 /* cbDataAdd is optional. */
1353
1354 LogFlowFunc(("cbExtra=%zu, cbProcessed=%zu, cbRemaining=%zu, cbDataAdd=%zu\n",
1355 pData->cbExtra, pData->cbProcessed, pData->getRemaining(), cbDataAdd));
1356
1357 if ( !pState
1358 || !cbDataAdd) /* Only update if something really changes. */
1359 return VINF_SUCCESS;
1360
1361 if (cbDataAdd)
1362 pData->addProcessed(cbDataAdd);
1363
1364 const uint8_t uPercent = pData->getPercentComplete();
1365
1366 LogRel2(("DnD: Transfer %RU8%% complete\n", uPercent));
1367
1368 int rc = pState->setProgress(uPercent,
1369 pData->isComplete()
1370 ? DND_PROGRESS_COMPLETE
1371 : DND_PROGRESS_RUNNING);
1372 LogFlowFuncLeaveRC(rc);
1373 return rc;
1374}
1375
1376/**
1377 * Waits for a specific guest callback event to get signalled.
1378 *
1379 * @returns VBox status code. Will return VERR_CANCELLED if the user has cancelled the progress object.
1380 * @param pEvent Callback event to wait for.
1381 * @param pState Guest state to update.
1382 * @param msTimeout Timeout (in ms) to wait.
1383 */
1384int GuestDnDBase::waitForEvent(GuestDnDCallbackEvent *pEvent, GuestDnDState *pState, RTMSINTERVAL msTimeout)
1385{
1386 AssertPtrReturn(pEvent, VERR_INVALID_POINTER);
1387 AssertPtrReturn(pState, VERR_INVALID_POINTER);
1388
1389 int rc;
1390
1391 LogFlowFunc(("msTimeout=%RU32\n", msTimeout));
1392
1393 uint64_t tsStart = RTTimeMilliTS();
1394 do
1395 {
1396 /*
1397 * Wait until our desired callback triggered the
1398 * wait event. As we don't want to block if the guest does not
1399 * respond, do busy waiting here.
1400 */
1401 rc = pEvent->Wait(500 /* ms */);
1402 if (RT_SUCCESS(rc))
1403 {
1404 rc = pEvent->Result();
1405 LogFlowFunc(("Callback done, result is %Rrc\n", rc));
1406 break;
1407 }
1408 else if (rc == VERR_TIMEOUT) /* Continue waiting. */
1409 rc = VINF_SUCCESS;
1410
1411 if ( msTimeout != RT_INDEFINITE_WAIT
1412 && RTTimeMilliTS() - tsStart > msTimeout)
1413 {
1414 rc = VERR_TIMEOUT;
1415 LogRel2(("DnD: Error: Guest did not respond within time\n"));
1416 }
1417 else if (pState->isProgressCanceled())
1418 {
1419 LogRel2(("DnD: Operation was canceled by user\n"));
1420 rc = VERR_CANCELLED;
1421 }
1422
1423 } while (RT_SUCCESS(rc));
1424
1425 LogFlowFuncLeaveRC(rc);
1426 return rc;
1427}
1428#endif /* VBOX_WITH_DRAG_AND_DROP */
1429
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