VirtualBox

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

Last change on this file since 94521 was 93115, checked in by vboxsync, 2 years ago

scm --update-copyright-year

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 46.1 KB
RevLine 
[42261]1/* $Id: GuestDnDPrivate.cpp 93115 2022-01-01 11:31:46Z vboxsync $ */
2/** @file
[67914]3 * Private guest drag and drop code, used by GuestDnDTarget + GuestDnDSource.
[42261]4 */
5
6/*
[93115]7 * Copyright (C) 2011-2022 Oracle Corporation
[42261]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
[67914]18#define LOG_GROUP LOG_GROUP_GUEST_DND
19#include "LoggingNew.h"
20
[42261]21#include "GuestImpl.h"
22#include "AutoCaller.h"
23
24#ifdef VBOX_WITH_DRAG_AND_DROP
25# include "ConsoleImpl.h"
26# include "ProgressImpl.h"
[51476]27# include "GuestDnDPrivate.h"
[42261]28
[51476]29# include <algorithm>
30
[50460]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
[42261]37# include <VMMDev.h>
38
[50460]39# include <VBox/GuestHost/DragAndDrop.h>
[42261]40# include <VBox/HostServices/DragAndDropSvc.h>
[55422]41# include <VBox/version.h>
[42261]42
[67914]43/** @page pg_main_dnd Dungeons & Dragons - Overview
[51476]44 * Overview:
[42261]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
[85681]57 * of it to the Main IGuest / IGuestDnDSource / IGuestDnDTarget interfaces.
[42261]58 * 2. Main: Public interface for doing Drag and Drop. Also manage the IProgress
[49891]59 * interfaces for blocking the caller by showing a progress dialog (see
60 * this file).
[42261]61 * 3. HGCM service: Handle all messages from the host to the guest at once and
[49891]62 * encapsulate the internal communication details (see dndmanager.cpp and
63 * friends).
[85681]64 * 4. Guest Additions: Split into the platform neutral part (see
[42261]65 * VBoxGuestR3LibDragAndDrop.cpp) and the guest OS specific parts.
66 * Receive/send message from/to the HGCM service and does all guest specific
[85681]67 * operations. For Windows guests VBoxTray is in charge, whereas on UNIX-y guests
68 * VBoxClient will be used.
[42261]69 *
[85681]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 *
[49891]79 * Host -> Guest:
[42261]80 * 1. There are DnD Enter, Move, Leave events which are send exactly like this
[85681]81 * to the guest. The info includes the position, MIME types and allowed actions.
[42261]82 * The guest has to respond with an action it would accept, so the GUI could
[85681]83 * change the cursor accordingly.
[51476]84 * 2. On drop, first a drop event is sent. If this is accepted a drop data
[42261]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 *
[85681]96 * Implementation hints:
[51476]97 * m_strSupportedFormats here in this file defines the allowed mime-types.
[42261]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 *
[49891]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
[42261]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 *
[85681]115 * Only regular files are supported; symlinks are not allowed.
[42261]116 *
[85681]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:
[42261]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 *
[85681]131 * ACEs / ACLs currently are not supported.
[42261]132 *
[85681]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.
[42261]136 *
[51476]137 ** @todo
138 * - ESC doesn't really work (on Windows guests it's already implemented)
[85681]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)
[51476]143 * - Test unusual behavior:
[42261]144 * - DnD service crash in the guest during a DnD op (e.g. crash of VBoxClient or X11)
[51476]145 * - Not expected order of the events between HGCM and the guest
[42261]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
[85681]148 * limits introduced to preventing DoS attacks or filling up all the memory
[42261]149 * (both in the host and the guest).
150 */
151
[85537]152
[85553]153/*********************************************************************************************************************************
154 * Internal macros. *
[85537]155 ********************************************************************************************************************************/
156
[85553]157/** Tries locking the GuestDnD object and returns on failure. */
[85537]158#define GUESTDND_LOCK() \
159 { \
160 int rcLock = RTCritSectEnter(&m_CritSect); \
161 if (RT_FAILURE(rcLock)) \
162 return rcLock; \
163 }
164
[85553]165/** Tries locking the GuestDnD object and returns a_Ret failure. */
[85537]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
[85553]173/** Unlocks a formerly locked GuestDnD object. */
[85537]174#define GUESTDND_UNLOCK() \
175 { \
176 int rcUnlock = RTCritSectLeave(&m_CritSect); RT_NOREF(rcUnlock); \
177 AssertRC(rcUnlock); \
178 }
179
[85553]180/*********************************************************************************************************************************
181 * GuestDnDSendCtx implementation. *
[85537]182 ********************************************************************************************************************************/
183
184GuestDnDSendCtx::GuestDnDSendCtx(void)
185 : pTarget(NULL)
[85744]186 , pState(NULL)
[85537]187{
188 reset();
189}
190
[85553]191/**
192 * Resets a GuestDnDSendCtx object.
193 */
[85537]194void GuestDnDSendCtx::reset(void)
195{
[85744]196 if (pState)
197 pState->reset();
[85537]198
199 uScreenID = 0;
200
201 Transfer.reset();
202
203 int rc2 = EventCallback.Reset();
204 AssertRC(rc2);
205
206 GuestDnDData::reset();
207}
208
209/*********************************************************************************************************************************
[85553]210 * GuestDnDRecvCtx implementation. *
[85537]211 ********************************************************************************************************************************/
212
213GuestDnDRecvCtx::GuestDnDRecvCtx(void)
214 : pSource(NULL)
[85744]215 , pState(NULL)
[85537]216{
217 reset();
218}
219
[85553]220/**
221 * Resets a GuestDnDRecvCtx object.
222 */
[85537]223void GuestDnDRecvCtx::reset(void)
224{
[85744]225 if (pState)
226 pState->reset();
[85537]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/*********************************************************************************************************************************
[85553]242 * GuestDnDCallbackEvent implementation. *
[85537]243 ********************************************************************************************************************************/
244
[55512]245GuestDnDCallbackEvent::~GuestDnDCallbackEvent(void)
246{
[85402]247 if (NIL_RTSEMEVENT != m_SemEvent)
248 RTSemEventDestroy(m_SemEvent);
[55512]249}
250
[85553]251/**
252 * Resets a GuestDnDCallbackEvent object.
253 */
[55512]254int GuestDnDCallbackEvent::Reset(void)
255{
256 int rc = VINF_SUCCESS;
257
[85402]258 if (NIL_RTSEMEVENT == m_SemEvent)
259 rc = RTSemEventCreate(&m_SemEvent);
[55512]260
[85402]261 m_Rc = VINF_SUCCESS;
[55512]262 return rc;
263}
264
[85553]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 */
[55963]271int GuestDnDCallbackEvent::Notify(int rc /* = VINF_SUCCESS */)
[55512]272{
[85402]273 m_Rc = rc;
274 return RTSemEventSignal(m_SemEvent);
[55512]275}
276
[85553]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 */
[55512]283int GuestDnDCallbackEvent::Wait(RTMSINTERVAL msTimeout)
284{
[85402]285 return RTSemEventWait(m_SemEvent, msTimeout);
[55512]286}
287
[85537]288/********************************************************************************************************************************
289 *
290 ********************************************************************************************************************************/
[55512]291
[85744]292GuestDnDState::GuestDnDState(const ComObjPtr<Guest>& pGuest)
[85739]293 : m_uProtocolVersion(0)
294 , m_fGuestFeatures0(VBOX_DND_GF_NONE)
295 , m_EventSem(NIL_RTSEMEVENT)
[74439]296 , m_dndActionDefault(0)
297 , m_dndLstActionsAllowed(0)
[58212]298 , m_pParent(pGuest)
[42261]299{
300 int rc = RTSemEventCreate(&m_EventSem);
[55422]301 if (RT_FAILURE(rc))
302 throw rc;
[42261]303}
304
[85744]305GuestDnDState::~GuestDnDState(void)
[42261]306{
[50265]307 reset();
[50734]308
[42261]309 int rc = RTSemEventDestroy(m_EventSem);
310 AssertRC(rc);
311}
312
[85553]313/**
314 * Notifies the waiting side about a guest notification response.
315 */
[85744]316int GuestDnDState::notifyAboutGuestResponse(void) const
[50609]317{
318 return RTSemEventSignal(m_EventSem);
319}
320
[85553]321/**
322 * Resets a GuestDnDResponse object.
323 */
[85744]324void GuestDnDState::reset(void)
[42261]325{
[50734]326 LogFlowThisFuncEnter();
327
[74439]328 m_dndActionDefault = 0;
329 m_dndLstActionsAllowed = 0;
[50734]330
[57221]331 m_lstFormats.clear();
[42261]332}
333
[85553]334/**
335 * Resets the progress object.
336 *
337 * @returns HRESULT
338 * @param pParent Parent to set for the progress object.
339 */
[85744]340HRESULT GuestDnDState::resetProgress(const ComObjPtr<Guest>& pParent)
[42261]341{
[58212]342 m_pProgress.setNull();
[55549]343
[58212]344 HRESULT hr = m_pProgress.createObject();
[55549]345 if (SUCCEEDED(hr))
[42261]346 {
[58212]347 hr = m_pProgress->init(static_cast<IGuest *>(pParent),
[90828]348 Bstr(tr("Dropping data")).raw(),
[58212]349 TRUE /* aCancelable */);
[42261]350 }
[55549]351
352 return hr;
[42261]353}
354
[85553]355/**
356 * Returns whether the progress object has been canceled or not.
357 *
358 * @returns \c true if canceled, \c false if not.
359 */
[85744]360bool GuestDnDState::isProgressCanceled(void) const
[55422]361{
362 BOOL fCanceled;
[58212]363 if (!m_pProgress.isNull())
[55422]364 {
[58212]365 HRESULT hr = m_pProgress->COMGETTER(Canceled)(&fCanceled);
[55422]366 AssertComRC(hr);
367 }
[55512]368 else
369 fCanceled = TRUE;
[55422]370
371 return RT_BOOL(fCanceled);
372}
373
[85553]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 */
[85744]382int GuestDnDState::setCallback(uint32_t uMsg, PFNGUESTDNDCALLBACK pfnCallback, void *pvUser /* = NULL */)
[55422]383{
[55424]384 GuestDnDCallbackMap::iterator it = m_mapCallbacks.find(uMsg);
[55422]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
[85553]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 */
[85744]415int GuestDnDState::setProgress(unsigned uPercentage, uint32_t uStatus,
416 int rcOp /* = VINF_SUCCESS */, const Utf8Str &strMsg /* = "" */)
[42261]417{
[85423]418 LogFlowFunc(("uPercentage=%u, uStatus=%RU32, , rcOp=%Rrc, strMsg=%s\n",
419 uPercentage, uStatus, rcOp, strMsg.c_str()));
[49891]420
[85423]421 HRESULT hr = S_OK;
422
423 BOOL fCompleted = FALSE;
424 BOOL fCanceled = FALSE;
425
[55556]426 int rc = VINF_SUCCESS;
[85423]427
[58212]428 if (!m_pProgress.isNull())
[42261]429 {
[85423]430 hr = m_pProgress->COMGETTER(Completed)(&fCompleted);
[55422]431 AssertComRC(hr);
432
[58212]433 hr = m_pProgress->COMGETTER(Canceled)(&fCanceled);
[55422]434 AssertComRC(hr);
435
[85423]436 LogFlowFunc(("Progress fCompleted=%RTbool, fCanceled=%RTbool\n", fCompleted, fCanceled));
437 }
[55422]438
[85423]439 switch (uStatus)
440 {
441 case DragAndDropSvc::DND_PROGRESS_ERROR:
[42261]442 {
[85423]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)
[49891]460 {
[85423]461 hr = m_pProgress->Cancel();
462 AssertComRC(hr);
463 hr = m_pProgress->i_notifyComplete(S_OK);
464 AssertComRC(hr);
465 }
[50561]466
[85423]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)
[55422]484 {
[58330]485 hr = m_pProgress->i_notifyComplete(S_OK);
[58329]486 AssertComRC(hr);
[55422]487 }
[42261]488 }
[85423]489 break;
[42261]490 }
[55512]491
[85423]492 default:
493 break;
494 }
495
496 if (!m_pProgress.isNull())
497 {
[58212]498 hr = m_pProgress->COMGETTER(Completed)(&fCompleted);
[55512]499 AssertComRC(hr);
[58212]500 hr = m_pProgress->COMGETTER(Canceled)(&fCanceled);
[55512]501 AssertComRC(hr);
502
[85423]503 LogFlowFunc(("Progress fCompleted=%RTbool, fCanceled=%RTbool\n", fCompleted, fCanceled));
[42261]504 }
[49891]505
[55556]506 LogFlowFuncLeaveRC(rc);
507 return rc;
[42261]508}
509
[85553]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 */
[85744]518int GuestDnDState::onDispatch(uint32_t u32Function, void *pvParms, uint32_t cbParms)
[42261]519{
[55422]520 LogFlowFunc(("u32Function=%RU32, pvParms=%p, cbParms=%RU32\n", u32Function, pvParms, cbParms));
[42261]521
[55422]522 int rc = VERR_WRONG_ORDER; /* Play safe. */
[85739]523
524 /* Whether or not to try calling host-installed callbacks after successfully processing the message. */
[55422]525 bool fTryCallbacks = false;
[50609]526
[55422]527 switch (u32Function)
[50734]528 {
[85745]529 case DragAndDropSvc::GUEST_DND_FN_CONNECT:
[58230]530 {
[85739]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);
[58329]535
[85739]536 m_uProtocolVersion = pCBData->uProtocolVersion;
537 /** @todo Handle flags. */
538
539 LogThisFunc(("Client connected, using protocol v%RU32\n", m_uProtocolVersion));
540
[58230]541 rc = VINF_SUCCESS;
542 break;
543 }
544
[85745]545 case DragAndDropSvc::GUEST_DND_FN_REPORT_FEATURES:
[85739]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
[85745]560 case DragAndDropSvc::GUEST_DND_FN_DISCONNECT:
[58329]561 {
562 LogThisFunc(("Client disconnected\n"));
563 rc = setProgress(100, DND_PROGRESS_CANCELLED, VINF_SUCCESS);
564 break;
565 }
566
[85745]567 case DragAndDropSvc::GUEST_DND_FN_HG_ACK_OP:
[50734]568 {
[55422]569 DragAndDropSvc::PVBOXDNDCBHGACKOPDATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBHGACKOPDATA>(pvParms);
570 AssertPtr(pCBData);
571 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBHGACKOPDATA) == cbParms, VERR_INVALID_PARAMETER);
[58257]572 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_HG_ACK_OP == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
[55422]573
[74439]574 setActionDefault(pCBData->uAction);
[55422]575 rc = notifyAboutGuestResponse();
576 break;
577 }
578
[85745]579 case DragAndDropSvc::GUEST_DND_FN_HG_REQ_DATA:
[55422]580 {
581 DragAndDropSvc::PVBOXDNDCBHGREQDATADATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBHGREQDATADATA>(pvParms);
582 AssertPtr(pCBData);
583 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBHGREQDATADATA) == cbParms, VERR_INVALID_PARAMETER);
[58257]584 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_HG_REQ_DATA == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
[55422]585
[58232]586 if ( pCBData->cbFormat == 0
587 || pCBData->cbFormat > _64K /** @todo Make this configurable? */
588 || pCBData->pszFormat == NULL)
[55539]589 {
590 rc = VERR_INVALID_PARAMETER;
591 }
[58232]592 else if (!RTStrIsValidEncoding(pCBData->pszFormat))
593 {
594 rc = VERR_INVALID_PARAMETER;
595 }
[55539]596 else
597 {
[57221]598 setFormats(GuestDnD::toFormatList(pCBData->pszFormat));
[55539]599 rc = VINF_SUCCESS;
600 }
601
602 int rc2 = notifyAboutGuestResponse();
603 if (RT_SUCCESS(rc))
604 rc = rc2;
[55422]605 break;
606 }
607
[85745]608 case DragAndDropSvc::GUEST_DND_FN_HG_EVT_PROGRESS:
[55422]609 {
610 DragAndDropSvc::PVBOXDNDCBHGEVTPROGRESSDATA pCBData =
611 reinterpret_cast<DragAndDropSvc::PVBOXDNDCBHGEVTPROGRESSDATA>(pvParms);
612 AssertPtr(pCBData);
613 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBHGEVTPROGRESSDATA) == cbParms, VERR_INVALID_PARAMETER);
[58257]614 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_HG_EVT_PROGRESS == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
[55422]615
616 rc = setProgress(pCBData->uPercentage, pCBData->uStatus, pCBData->rc);
[50734]617 if (RT_SUCCESS(rc))
[55422]618 rc = notifyAboutGuestResponse();
619 break;
620 }
621#ifdef VBOX_WITH_DRAG_AND_DROP_GH
[85745]622 case DragAndDropSvc::GUEST_DND_FN_GH_ACK_PENDING:
[55422]623 {
624 DragAndDropSvc::PVBOXDNDCBGHACKPENDINGDATA pCBData =
625 reinterpret_cast<DragAndDropSvc::PVBOXDNDCBGHACKPENDINGDATA>(pvParms);
626 AssertPtr(pCBData);
627 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBGHACKPENDINGDATA) == cbParms, VERR_INVALID_PARAMETER);
[58257]628 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_GH_ACK_PENDING == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
[50734]629
[58232]630 if ( pCBData->cbFormat == 0
631 || pCBData->cbFormat > _64K /** @todo Make the maximum size configurable? */
632 || pCBData->pszFormat == NULL)
[55539]633 {
634 rc = VERR_INVALID_PARAMETER;
635 }
[58232]636 else if (!RTStrIsValidEncoding(pCBData->pszFormat))
637 {
638 rc = VERR_INVALID_PARAMETER;
639 }
[55539]640 else
641 {
[57221]642 setFormats (GuestDnD::toFormatList(pCBData->pszFormat));
[74439]643 setActionDefault (pCBData->uDefAction);
644 setActionsAllowed(pCBData->uAllActions);
[50734]645
[55539]646 rc = VINF_SUCCESS;
647 }
648
649 int rc2 = notifyAboutGuestResponse();
650 if (RT_SUCCESS(rc))
651 rc = rc2;
[55422]652 break;
[50734]653 }
[55422]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;
[50734]659 }
660
[55422]661 /*
662 * Try the host's installed callbacks (if any).
663 */
664 if (fTryCallbacks)
[50734]665 {
[55422]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
[57291]673 {
[57295]674 LogFlowFunc(("No callback for function %RU32 defined (%zu callbacks total)\n", u32Function, m_mapCallbacks.size()));
[57287]675 rc = VERR_NOT_SUPPORTED; /* Tell the guest. */
[57291]676 }
[50734]677 }
678
[55422]679 LogFlowFunc(("Returning rc=%Rrc\n", rc));
[50734]680 return rc;
681}
682
[85553]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 */
[85744]689HRESULT GuestDnDState::queryProgressTo(IProgress **ppProgress)
[55422]690{
[58212]691 return m_pProgress.queryInterfaceTo(ppProgress);
[55422]692}
693
[85553]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 */
[85744]700int GuestDnDState::waitForGuestResponse(RTMSINTERVAL msTimeout /*= 500 */) const
[55422]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
[85537]709/*********************************************************************************************************************************
[85553]710 * GuestDnD implementation. *
[85537]711 ********************************************************************************************************************************/
[50609]712
[85553]713/** Static (Singleton) instance of the GuestDnD object. */
[51476]714GuestDnD* GuestDnD::s_pInstance = NULL;
715
716GuestDnD::GuestDnD(const ComObjPtr<Guest> &pGuest)
717 : m_pGuest(pGuest)
[85537]718 , m_cTransfersPending(0)
[42261]719{
[51476]720 LogFlowFuncEnter();
721
[85537]722 try
723 {
[85744]724 m_pState = new GuestDnDState(pGuest);
[85537]725 }
726 catch (std::bad_alloc &)
727 {
728 throw VERR_NO_MEMORY;
729 }
[51476]730
[85537]731 int rc = RTCritSectInit(&m_CritSect);
732 if (RT_FAILURE(rc))
733 throw rc;
734
[51556]735 /* List of supported default MIME types. */
[56653]736 LogRel2(("DnD: Supported default host formats:\n"));
[51556]737 const com::Utf8Str arrEntries[] = { VBOX_DND_FORMATS_DEFAULT };
[51476]738 for (size_t i = 0; i < RT_ELEMENTS(arrEntries); i++)
[56653]739 {
[51556]740 m_strDefaultFormats.push_back(arrEntries[i]);
[56653]741 LogRel2(("DnD: \t%s\n", arrEntries[i].c_str()));
742 }
[51476]743}
744
745GuestDnD::~GuestDnD(void)
746{
747 LogFlowFuncEnter();
748
[85537]749 Assert(m_cTransfersPending == 0); /* Sanity. */
750
751 RTCritSectDelete(&m_CritSect);
752
[85744]753 if (m_pState)
754 delete m_pState;
[51476]755}
756
[85553]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 */
[57221]765HRESULT GuestDnD::adjustScreenCoordinates(ULONG uScreenId, ULONG *puX, ULONG *puY) const
[51476]766{
767 /** @todo r=andy Save the current screen's shifting coordinates to speed things up.
[85561]768 * Only query for new offsets when the screen ID or the screen's resolution has changed. */
[51476]769
[42261]770 /* For multi-monitor support we need to add shift values to the coordinates
771 * (depending on the screen number). */
[52082]772 ComObjPtr<Console> pConsole = m_pGuest->i_getConsole();
[42261]773 ComPtr<IDisplay> pDisplay;
[51476]774 HRESULT hr = pConsole->COMGETTER(Display)(pDisplay.asOutParam());
[50561]775 if (FAILED(hr))
776 return hr;
[50460]777
[52064]778 ULONG dummy;
[42261]779 LONG xShift, yShift;
[52978]780 GuestMonitorStatus_T monitorStatus;
[52064]781 hr = pDisplay->GetScreenResolution(uScreenId, &dummy, &dummy, &dummy,
[52978]782 &xShift, &yShift, &monitorStatus);
[50561]783 if (FAILED(hr))
784 return hr;
[50460]785
[51476]786 if (puX)
787 *puX += xShift;
788 if (puY)
789 *puY += yShift;
[50561]790
[57221]791 LogFlowFunc(("uScreenId=%RU32, x=%RU32, y=%RU32\n", uScreenId, puX ? *puX : 0, puY ? *puY : 0));
792 return S_OK;
[42261]793}
794
[85553]795/**
[85744]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/**
[85553]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 */
[51476]816int GuestDnD::hostCall(uint32_t u32Function, uint32_t cParms, PVBOXHGCMSVCPARM paParms) const
[42261]817{
[51476]818 Assert(!m_pGuest.isNull());
[52082]819 ComObjPtr<Console> pConsole = m_pGuest->i_getConsole();
[42261]820
[51476]821 /* Forward the information to the VMM device. */
822 Assert(!pConsole.isNull());
[51612]823 VMMDev *pVMMDev = pConsole->i_getVMMDev();
[50561]824 if (!pVMMDev)
[51476]825 return VERR_COM_OBJECT_NOT_FOUND;
[42261]826
[57221]827 return pVMMDev->hgcmHostCall("VBoxDragAndDropSvc", u32Function, cParms, paParms);
[42261]828}
829
[85553]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 */
[85537]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
[85553]849/**
850 * Unregisters a GuestDnDSource object from the GuestDnD manager.
851 *
852 * @returns VBox status code.
853 * @param Source Source to unregister.
854 */
[85537]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
[85553]867/**
868 * Returns the current number of registered sources.
869 *
870 * @returns Current number of registered sources.
871 */
[85537]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
[85553]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 */
[85537]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
[85553]901/**
902 * Unregisters a GuestDnDTarget object from the GuestDnD manager.
903 *
904 * @returns VBox status code.
905 * @param Target Target to unregister.
906 */
[85537]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
[85553]919/**
920 * Returns the current number of registered targets.
921 *
922 * @returns Current number of registered targets.
923 */
[85537]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
[85553]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 */
[42261]943/* static */
[55422]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. */
[85744]956 if (pGuestDnD->m_pState)
957 return pGuestDnD->m_pState->onDispatch(u32Function, pvParms, cbParms);
[55422]958
959 return VERR_NOT_SUPPORTED;
960}
961
[85553]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 */
[57221]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}
[55422]974
[85553]975/**
976 * Static helper function to create a GuestDnDMIMEList out of a format list string.
977 *
978 * @returns MIME list object.
[85560]979 * @param strFormats List of formats to convert.
[85746]980 * @param strSep Separator to use. If not specified, DND_FORMATS_SEPARATOR_STR will be used.
[85553]981 */
[55422]982/* static */
[85746]983GuestDnDMIMEList GuestDnD::toFormatList(const com::Utf8Str &strFormats, const com::Utf8Str &strSep /* = DND_FORMATS_SEPARATOR_STR */)
[42261]984{
[57221]985 GuestDnDMIMEList lstFormats;
[85560]986 RTCList<RTCString> lstFormatsTmp = strFormats.split(strSep);
[57221]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
[85553]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 */
[57221]1000/* static */
1001com::Utf8Str GuestDnD::toFormatString(const GuestDnDMIMEList &lstFormats)
1002{
[51476]1003 com::Utf8Str strFormat;
[57221]1004 for (size_t i = 0; i < lstFormats.size(); i++)
[42261]1005 {
[57221]1006 const com::Utf8Str &f = lstFormats.at(i);
[85746]1007 strFormat += f + DND_FORMATS_SEPARATOR_STR;
[42261]1008 }
[51476]1009
[42261]1010 return strFormat;
1011}
1012
[85553]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 */
[42261]1020/* static */
[57221]1021GuestDnDMIMEList GuestDnD::toFilteredFormatList(const GuestDnDMIMEList &lstFormatsSupported, const GuestDnDMIMEList &lstFormatsWanted)
[42261]1022{
[57221]1023 GuestDnDMIMEList lstFormats;
1024
1025 for (size_t i = 0; i < lstFormatsWanted.size(); i++)
1026 {
[59842]1027 /* Only keep supported format types. */
[57221]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
[85553]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 */
[57221]1045/* static */
1046GuestDnDMIMEList GuestDnD::toFilteredFormatList(const GuestDnDMIMEList &lstFormatsSupported, const com::Utf8Str &strFormatsWanted)
1047{
1048 GuestDnDMIMEList lstFmt;
1049
[85746]1050 RTCList<RTCString> lstFormats = strFormatsWanted.split(DND_FORMATS_SEPARATOR_STR);
[42261]1051 size_t i = 0;
[51476]1052 while (i < lstFormats.size())
[42261]1053 {
1054 /* Only keep allowed format types. */
[57221]1055 if (std::find(lstFormatsSupported.begin(),
1056 lstFormatsSupported.end(), lstFormats.at(i)) != lstFormatsSupported.end())
1057 {
1058 lstFmt.push_back(lstFormats[i]);
1059 }
1060 i++;
[42261]1061 }
[51476]1062
[57221]1063 return lstFmt;
[42261]1064}
1065
[85553]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 */
[42261]1072/* static */
[74439]1073VBOXDNDACTION GuestDnD::toHGCMAction(DnDAction_T enmAction)
[42261]1074{
[74439]1075 VBOXDNDACTION dndAction = VBOX_DND_ACTION_IGNORE;
[51476]1076 switch (enmAction)
[42261]1077 {
[51476]1078 case DnDAction_Copy:
[74439]1079 dndAction = VBOX_DND_ACTION_COPY;
[51476]1080 break;
1081 case DnDAction_Move:
[74439]1082 dndAction = VBOX_DND_ACTION_MOVE;
[51476]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;
[42261]1093 }
[51476]1094
[74439]1095 return dndAction;
[42261]1096}
1097
[85553]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 */
[42261]1107/* static */
[74439]1108void GuestDnD::toHGCMActions(DnDAction_T enmDnDActionDefault,
1109 VBOXDNDACTION *pDnDActionDefault,
1110 const std::vector<DnDAction_T> vecDnDActionsAllowed,
1111 VBOXDNDACTIONLIST *pDnDLstActionsAllowed)
[42261]1112{
[74439]1113 VBOXDNDACTIONLIST dndLstActionsAllowed = VBOX_DND_ACTION_IGNORE;
1114 VBOXDNDACTION dndActionDefault = toHGCMAction(enmDnDActionDefault);
[56734]1115
[74439]1116 if (!vecDnDActionsAllowed.empty())
[51476]1117 {
1118 /* First convert the allowed actions to a bit array. */
[74439]1119 for (size_t i = 0; i < vecDnDActionsAllowed.size(); i++)
1120 dndLstActionsAllowed |= toHGCMAction(vecDnDActionsAllowed[i]);
[42261]1121
[56734]1122 /*
1123 * If no default action is set (ignoring), try one of the
1124 * set allowed actions, preferring copy, move (in that order).
1125 */
[74439]1126 if (isDnDIgnoreAction(dndActionDefault))
[51476]1127 {
[74439]1128 if (hasDnDCopyAction(dndLstActionsAllowed))
1129 dndActionDefault = VBOX_DND_ACTION_COPY;
1130 else if (hasDnDMoveAction(dndLstActionsAllowed))
1131 dndActionDefault = VBOX_DND_ACTION_MOVE;
[51476]1132 }
[42261]1133 }
[56734]1134
[74439]1135 if (pDnDActionDefault)
1136 *pDnDActionDefault = dndActionDefault;
1137 if (pDnDLstActionsAllowed)
1138 *pDnDLstActionsAllowed = dndLstActionsAllowed;
[42261]1139}
1140
[85553]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 */
[42261]1147/* static */
[74439]1148DnDAction_T GuestDnD::toMainAction(VBOXDNDACTION dndAction)
[42261]1149{
[50508]1150 /* For now it doesn't seems useful to allow a
1151 * link action between host & guest. Maybe later! */
[74439]1152 return isDnDCopyAction(dndAction) ? DnDAction_Copy
1153 : isDnDMoveAction(dndAction) ? DnDAction_Move
1154 : DnDAction_Ignore;
[42261]1155}
1156
[85553]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 */
[42261]1163/* static */
[74439]1164std::vector<DnDAction_T> GuestDnD::toMainActions(VBOXDNDACTIONLIST dndActionList)
[42261]1165{
[57221]1166 std::vector<DnDAction_T> vecActions;
1167
[50508]1168 /* For now it doesn't seems useful to allow a
1169 * link action between host & guest. Maybe later! */
[51476]1170 RTCList<DnDAction_T> lstActions;
[74439]1171 if (hasDnDCopyAction(dndActionList))
[51476]1172 lstActions.append(DnDAction_Copy);
[74439]1173 if (hasDnDMoveAction(dndActionList))
[51476]1174 lstActions.append(DnDAction_Move);
[42261]1175
[51476]1176 for (size_t i = 0; i < lstActions.size(); ++i)
1177 vecActions.push_back(lstActions.at(i));
[57221]1178
1179 return vecActions;
[42261]1180}
1181
[85537]1182/*********************************************************************************************************************************
[85553]1183 * GuestDnDBase implementation. *
[85537]1184 ********************************************************************************************************************************/
[51556]1185
1186GuestDnDBase::GuestDnDBase(void)
[85537]1187 : m_fIsPending(false)
[51556]1188{
[58212]1189 /* Initialize public attributes. */
[85537]1190 m_lstFmtSupported = GuestDnDInst()->defaultFormats();
[51556]1191}
1192
[85553]1193/**
1194 * Checks whether a given DnD format is supported or not.
1195 *
[85559]1196 * @returns \c true if supported, \c false if not.
[85553]1197 * @param aFormat DnD format to check.
1198 */
[85559]1199bool GuestDnDBase::i_isFormatSupported(const com::Utf8Str &aFormat) const
[51556]1200{
[85559]1201 return std::find(m_lstFmtSupported.begin(), m_lstFmtSupported.end(), aFormat) != m_lstFmtSupported.end();
[51556]1202}
1203
[85553]1204/**
1205 * Returns the currently supported DnD formats.
1206 *
[85558]1207 * @returns List of the supported DnD formats.
[85553]1208 */
[85558]1209const GuestDnDMIMEList &GuestDnDBase::i_getFormats(void) const
[51556]1210{
[85558]1211 return m_lstFmtSupported;
[51556]1212}
1213
[85553]1214/**
1215 * Adds DnD formats to the supported formats list.
1216 *
1217 * @returns HRESULT
1218 * @param aFormats List of DnD formats to add.
1219 */
[57221]1220HRESULT GuestDnDBase::i_addFormats(const GuestDnDMIMEList &aFormats)
[51556]1221{
1222 for (size_t i = 0; i < aFormats.size(); ++i)
1223 {
1224 Utf8Str strFormat = aFormats.at(i);
[57221]1225 if (std::find(m_lstFmtSupported.begin(),
1226 m_lstFmtSupported.end(), strFormat) == m_lstFmtSupported.end())
[51556]1227 {
[57221]1228 m_lstFmtSupported.push_back(strFormat);
[51556]1229 }
1230 }
1231
1232 return S_OK;
1233}
1234
[85553]1235/**
1236 * Removes DnD formats from tehh supported formats list.
1237 *
1238 * @returns HRESULT
1239 * @param aFormats List of DnD formats to remove.
1240 */
[57221]1241HRESULT GuestDnDBase::i_removeFormats(const GuestDnDMIMEList &aFormats)
[51556]1242{
1243 for (size_t i = 0; i < aFormats.size(); ++i)
1244 {
1245 Utf8Str strFormat = aFormats.at(i);
[57221]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);
[51556]1250 }
1251
1252 return S_OK;
1253}
1254
[58230]1255/**
[85553]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 */
[55571]1261int GuestDnDBase::msgQueueAdd(GuestDnDMsg *pMsg)
1262{
[85402]1263 m_DataBase.lstMsgOut.push_back(pMsg);
[55571]1264 return VINF_SUCCESS;
1265}
1266
[85553]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 */
[55571]1272GuestDnDMsg *GuestDnDBase::msgQueueGetNext(void)
1273{
[85402]1274 if (m_DataBase.lstMsgOut.empty())
[55571]1275 return NULL;
[85402]1276 return m_DataBase.lstMsgOut.front();
[55571]1277}
1278
[85553]1279/**
1280 * Removes the next guest DnD message from the internal message queue.
1281 */
[55571]1282void GuestDnDBase::msgQueueRemoveNext(void)
1283{
[85402]1284 if (!m_DataBase.lstMsgOut.empty())
[55571]1285 {
[85402]1286 GuestDnDMsg *pMsg = m_DataBase.lstMsgOut.front();
[55571]1287 if (pMsg)
1288 delete pMsg;
[85402]1289 m_DataBase.lstMsgOut.pop_front();
[55571]1290 }
1291}
1292
[85553]1293/**
1294 * Clears the internal message queue.
1295 */
[55571]1296void GuestDnDBase::msgQueueClear(void)
1297{
[85402]1298 LogFlowFunc(("cMsg=%zu\n", m_DataBase.lstMsgOut.size()));
[58212]1299
[85402]1300 GuestDnDMsgList::iterator itMsg = m_DataBase.lstMsgOut.begin();
1301 while (itMsg != m_DataBase.lstMsgOut.end())
[55571]1302 {
[58212]1303 GuestDnDMsg *pMsg = *itMsg;
1304 if (pMsg)
1305 delete pMsg;
1306
1307 itMsg++;
[55571]1308 }
1309
[85402]1310 m_DataBase.lstMsgOut.clear();
[55571]1311}
1312
[85553]1313/**
1314 * Sends a request to the guest side to cancel the current DnD operation.
1315 *
1316 * @returns VBox status code.
1317 */
[55571]1318int GuestDnDBase::sendCancel(void)
1319{
[76891]1320 GuestDnDMsg Msg;
[85745]1321 Msg.setType(HOST_DND_FN_CANCEL);
[85744]1322 if (m_pState->m_uProtocolVersion >= 3)
[85554]1323 Msg.appendUInt32(0); /** @todo ContextID not used yet. */
[55571]1324
[85537]1325 LogRel2(("DnD: Cancelling operation on guest ...\n"));
[76891]1326
[85537]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;
[55571]1332}
1333
[85553]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.
[85744]1339 * @param pState Guest state to update its progress object for.
[85553]1340 * @param cbDataAdd By how much data (in bytes) to update the progress.
1341 */
[85744]1342int GuestDnDBase::updateProgress(GuestDnDData *pData, GuestDnDState *pState,
[85402]1343 size_t cbDataAdd /* = 0 */)
[58212]1344{
1345 AssertPtrReturn(pData, VERR_INVALID_POINTER);
[85744]1346 AssertPtrReturn(pState, VERR_INVALID_POINTER);
[58212]1347 /* cbDataAdd is optional. */
1348
[85402]1349 LogFlowFunc(("cbExtra=%zu, cbProcessed=%zu, cbRemaining=%zu, cbDataAdd=%zu\n",
[85371]1350 pData->cbExtra, pData->cbProcessed, pData->getRemaining(), cbDataAdd));
[58212]1351
[85744]1352 if ( !pState
[85423]1353 || !cbDataAdd) /* Only update if something really changes. */
[58212]1354 return VINF_SUCCESS;
1355
1356 if (cbDataAdd)
1357 pData->addProcessed(cbDataAdd);
1358
[85423]1359 const uint8_t uPercent = pData->getPercentComplete();
1360
1361 LogRel2(("DnD: Transfer %RU8%% complete\n", uPercent));
1362
[85744]1363 int rc = pState->setProgress(uPercent,
1364 pData->isComplete()
1365 ? DND_PROGRESS_COMPLETE
1366 : DND_PROGRESS_RUNNING);
[58212]1367 LogFlowFuncLeaveRC(rc);
1368 return rc;
1369}
1370
[85553]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.
[85744]1376 * @param pState Guest state to update.
[85553]1377 * @param msTimeout Timeout (in ms) to wait.
1378 */
[85744]1379int GuestDnDBase::waitForEvent(GuestDnDCallbackEvent *pEvent, GuestDnDState *pState, RTMSINTERVAL msTimeout)
[55571]1380{
[58212]1381 AssertPtrReturn(pEvent, VERR_INVALID_POINTER);
[85744]1382 AssertPtrReturn(pState, VERR_INVALID_POINTER);
[58212]1383
[55571]1384 int rc;
1385
[59842]1386 LogFlowFunc(("msTimeout=%RU32\n", msTimeout));
1387
[55571]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
[55963]1394 * respond, do busy waiting here.
[55571]1395 */
[58212]1396 rc = pEvent->Wait(500 /* ms */);
[55571]1397 if (RT_SUCCESS(rc))
1398 {
[58212]1399 rc = pEvent->Result();
[55571]1400 LogFlowFunc(("Callback done, result is %Rrc\n", rc));
1401 break;
1402 }
1403 else if (rc == VERR_TIMEOUT) /* Continue waiting. */
[56494]1404 rc = VINF_SUCCESS;
[55571]1405
1406 if ( msTimeout != RT_INDEFINITE_WAIT
1407 && RTTimeMilliTS() - tsStart > msTimeout)
1408 {
1409 rc = VERR_TIMEOUT;
[56494]1410 LogRel2(("DnD: Error: Guest did not respond within time\n"));
[55571]1411 }
[85744]1412 else if (pState->isProgressCanceled())
[55571]1413 {
[56494]1414 LogRel2(("DnD: Operation was canceled by user\n"));
[55571]1415 rc = VERR_CANCELLED;
1416 }
1417
1418 } while (RT_SUCCESS(rc));
1419
1420 LogFlowFuncLeaveRC(rc);
1421 return rc;
1422}
[42261]1423#endif /* VBOX_WITH_DRAG_AND_DROP */
1424
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use