VirtualBox

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

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

DnD/Main: Got rid of protocol-guessing via Guest Additions version and pass down the reported (now marked as deprecated) protocol version and guest feature bits to Main.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 45.9 KB
Line 
1/* $Id: GuestDnDPrivate.cpp 85739 2020-08-13 07:08:34Z vboxsync $ */
2/** @file
3 * Private guest drag and drop code, used by GuestDnDTarget + GuestDnDSource.
4 */
5
6/*
7 * Copyright (C) 2011-2020 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18#define LOG_GROUP LOG_GROUP_GUEST_DND
19#include "LoggingNew.h"
20
21#include "GuestImpl.h"
22#include "AutoCaller.h"
23
24#ifdef VBOX_WITH_DRAG_AND_DROP
25# include "ConsoleImpl.h"
26# include "ProgressImpl.h"
27# include "GuestDnDPrivate.h"
28
29# include <algorithm>
30
31# include <iprt/dir.h>
32# include <iprt/path.h>
33# include <iprt/stream.h>
34# include <iprt/semaphore.h>
35# include <iprt/cpp/utils.h>
36
37# include <VMMDev.h>
38
39# include <VBox/GuestHost/DragAndDrop.h>
40# include <VBox/HostServices/DragAndDropSvc.h>
41# include <VBox/version.h>
42
43/** @page pg_main_dnd Dungeons & Dragons - Overview
44 * Overview:
45 *
46 * Drag and Drop is handled over the internal HGCM service for the host <->
47 * guest communication. Beside that we need to map the Drag and Drop protocols
48 * of the various OS's we support to our internal channels, this is also highly
49 * communicative in both directions. Unfortunately HGCM isn't really designed
50 * for that. Next we have to foul some of the components. This includes to
51 * trick X11 on the guest side, but also Qt needs to be tricked on the host
52 * side a little bit.
53 *
54 * The following components are involved:
55 *
56 * 1. GUI: Uses the Qt classes for Drag and Drop and mainly forward the content
57 * of it to the Main IGuest / IGuestDnDSource / IGuestDnDTarget interfaces.
58 * 2. Main: Public interface for doing Drag and Drop. Also manage the IProgress
59 * interfaces for blocking the caller by showing a progress dialog (see
60 * this file).
61 * 3. HGCM service: Handle all messages from the host to the guest at once and
62 * encapsulate the internal communication details (see dndmanager.cpp and
63 * friends).
64 * 4. Guest Additions: Split into the platform neutral part (see
65 * VBoxGuestR3LibDragAndDrop.cpp) and the guest OS specific parts.
66 * Receive/send message from/to the HGCM service and does all guest specific
67 * operations. For Windows guests VBoxTray is in charge, whereas on UNIX-y guests
68 * VBoxClient will be used.
69 *
70 * Terminology:
71 *
72 * All transfers contain a MIME format and according meta data. This meta data then can
73 * be interpreted either as raw meta data or something else. When raw meta data is
74 * being handled, this gets passed through to the destination (guest / host) without
75 * modification. Other meta data (like URI lists) can and will be modified by the
76 * receiving side before passing to OS. How and when modifications will be applied
77 * depends on the MIME format.
78 *
79 * Host -> Guest:
80 * 1. There are DnD Enter, Move, Leave events which are send exactly like this
81 * to the guest. The info includes the position, MIME types and allowed actions.
82 * The guest has to respond with an action it would accept, so the GUI could
83 * change the cursor accordingly.
84 * 2. On drop, first a drop event is sent. If this is accepted a drop data
85 * event follows. This blocks the GUI and shows some progress indicator.
86 *
87 * Guest -> Host:
88 * 1. The GUI is asking the guest if a DnD event is pending when the user moves
89 * the cursor out of the view window. If so, this returns the mimetypes and
90 * allowed actions.
91 * (2. On every mouse move this is asked again, to make sure the DnD event is
92 * still valid.)
93 * 3. On drop the host request the data from the guest. This blocks the GUI and
94 * shows some progress indicator.
95 *
96 * Implementation hints:
97 * m_strSupportedFormats here in this file defines the allowed mime-types.
98 * This is necessary because we need special handling for some of the
99 * mime-types. E.g. for URI lists we need to transfer the actual dirs and
100 * files. Text EOL may to be changed. Also unknown mime-types may need special
101 * handling as well, which may lead to undefined behavior in the host/guest, if
102 * not done.
103 *
104 * Dropping of a directory, means recursively transferring _all_ the content.
105 *
106 * Directories and files are placed into the user's temporary directory on the
107 * guest (e.g. /tmp/VirtualBox Dropped Files). We can't delete them after the
108 * DnD operation, because we didn't know what the DnD target does with it. E.g.
109 * it could just be opened in place. This could lead ofc to filling up the disk
110 * within the guest. To inform the user about this, a small app could be
111 * developed which scans this directory regularly and inform the user with a
112 * tray icon hint (and maybe the possibility to clean this up instantly). The
113 * same has to be done in the G->H direction when it is implemented.
114 *
115 * Only regular files are supported; symlinks are not allowed.
116 *
117 * Transfers currently are an all-succeed or all-fail operation (see todos).
118 *
119 * On MacOS hosts we had to implement own DnD "promises" support for file transfers,
120 * as Qt does not support this out-of-the-box.
121 *
122 * The code tries to preserve the file modes of the transfered directories / files.
123 * This is useful (and maybe necessary) for two things:
124 * 1. If a file is executable, it should be also after the transfer, so the
125 * user can just execute it, without manually tweaking the modes first.
126 * 2. If a dir/file is not accessible by group/others in the host, it shouldn't
127 * be in the guest.
128 * In any case, the user mode is always set to rwx (so that we can access it
129 * ourself, in e.g. for a cleanup case after cancel).
130 *
131 * ACEs / ACLs currently are not supported.
132 *
133 * Cancelling ongoing transfers is supported in both directions by the guest
134 * and/or host side and cleans up all previous steps. This also involves
135 * removing partially transferred directories / files in the temporary directory.
136 *
137 ** @todo
138 * - ESC doesn't really work (on Windows guests it's already implemented)
139 * ... in any case it seems a little bit difficult to handle from the Qt side.
140 * - Transfers currently do not have any interactive (UI) callbacks / hooks which
141 * e.g. would allow to skip / replace / rename and entry, or abort the operation on failure.
142 * - Add support for more MIME types (especially images, csv)
143 * - Test unusual behavior:
144 * - DnD service crash in the guest during a DnD op (e.g. crash of VBoxClient or X11)
145 * - Not expected order of the events between HGCM and the guest
146 * - Security considerations: We transfer a lot of memory between the guest and
147 * the host and even allow the creation of dirs/files. Maybe there should be
148 * limits introduced to preventing DoS attacks or filling up all the memory
149 * (both in the host and the guest).
150 */
151
152
153/*********************************************************************************************************************************
154 * Internal macros. *
155 ********************************************************************************************************************************/
156
157/** Tries locking the GuestDnD object and returns on failure. */
158#define GUESTDND_LOCK() \
159 { \
160 int rcLock = RTCritSectEnter(&m_CritSect); \
161 if (RT_FAILURE(rcLock)) \
162 return rcLock; \
163 }
164
165/** Tries locking the GuestDnD object and returns a_Ret failure. */
166#define GUESTDND_LOCK_RET(a_Ret) \
167 { \
168 int rcLock = RTCritSectEnter(&m_CritSect); \
169 if (RT_FAILURE(rcLock)) \
170 return a_Ret; \
171 }
172
173/** Unlocks a formerly locked GuestDnD object. */
174#define GUESTDND_UNLOCK() \
175 { \
176 int rcUnlock = RTCritSectLeave(&m_CritSect); RT_NOREF(rcUnlock); \
177 AssertRC(rcUnlock); \
178 }
179
180/*********************************************************************************************************************************
181 * GuestDnDSendCtx implementation. *
182 ********************************************************************************************************************************/
183
184GuestDnDSendCtx::GuestDnDSendCtx(void)
185 : pTarget(NULL)
186 , pResp(NULL)
187{
188 reset();
189}
190
191/**
192 * Resets a GuestDnDSendCtx object.
193 */
194void GuestDnDSendCtx::reset(void)
195{
196 if (pResp)
197 pResp->reset();
198
199 uScreenID = 0;
200
201 Transfer.reset();
202
203 int rc2 = EventCallback.Reset();
204 AssertRC(rc2);
205
206 GuestDnDData::reset();
207}
208
209/*********************************************************************************************************************************
210 * GuestDnDRecvCtx implementation. *
211 ********************************************************************************************************************************/
212
213GuestDnDRecvCtx::GuestDnDRecvCtx(void)
214 : pSource(NULL)
215 , pResp(NULL)
216{
217 reset();
218}
219
220/**
221 * Resets a GuestDnDRecvCtx object.
222 */
223void GuestDnDRecvCtx::reset(void)
224{
225 if (pResp)
226 pResp->reset();
227
228 lstFmtOffered.clear();
229 strFmtReq = "";
230 strFmtRecv = "";
231 enmAction = 0;
232
233 Transfer.reset();
234
235 int rc2 = EventCallback.Reset();
236 AssertRC(rc2);
237
238 GuestDnDData::reset();
239}
240
241/*********************************************************************************************************************************
242 * GuestDnDCallbackEvent implementation. *
243 ********************************************************************************************************************************/
244
245GuestDnDCallbackEvent::~GuestDnDCallbackEvent(void)
246{
247 if (NIL_RTSEMEVENT != m_SemEvent)
248 RTSemEventDestroy(m_SemEvent);
249}
250
251/**
252 * Resets a GuestDnDCallbackEvent object.
253 */
254int GuestDnDCallbackEvent::Reset(void)
255{
256 int rc = VINF_SUCCESS;
257
258 if (NIL_RTSEMEVENT == m_SemEvent)
259 rc = RTSemEventCreate(&m_SemEvent);
260
261 m_Rc = VINF_SUCCESS;
262 return rc;
263}
264
265/**
266 * Completes a callback event by notifying the waiting side.
267 *
268 * @returns VBox status code.
269 * @param rc Result code to use for the event completion.
270 */
271int GuestDnDCallbackEvent::Notify(int rc /* = VINF_SUCCESS */)
272{
273 m_Rc = rc;
274 return RTSemEventSignal(m_SemEvent);
275}
276
277/**
278 * Waits on a callback event for being notified.
279 *
280 * @returns VBox status code.
281 * @param msTimeout Timeout (in ms) to wait for callback event.
282 */
283int GuestDnDCallbackEvent::Wait(RTMSINTERVAL msTimeout)
284{
285 return RTSemEventWait(m_SemEvent, msTimeout);
286}
287
288/********************************************************************************************************************************
289 *
290 ********************************************************************************************************************************/
291
292GuestDnDResponse::GuestDnDResponse(const ComObjPtr<Guest>& pGuest)
293 : m_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
305GuestDnDResponse::~GuestDnDResponse(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 GuestDnDResponse::notifyAboutGuestResponse(void) const
317{
318 return RTSemEventSignal(m_EventSem);
319}
320
321/**
322 * Resets a GuestDnDResponse object.
323 */
324void GuestDnDResponse::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 GuestDnDResponse::resetProgress(const ComObjPtr<Guest>& pParent)
341{
342 m_pProgress.setNull();
343
344 HRESULT hr = m_pProgress.createObject();
345 if (SUCCEEDED(hr))
346 {
347 hr = m_pProgress->init(static_cast<IGuest *>(pParent),
348 Bstr(pParent->tr("Dropping data")).raw(),
349 TRUE /* aCancelable */);
350 }
351
352 return hr;
353}
354
355/**
356 * Returns whether the progress object has been canceled or not.
357 *
358 * @returns \c true if canceled, \c false if not.
359 */
360bool GuestDnDResponse::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 GuestDnDResponse::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 GuestDnDResponse::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 GuestDnDResponse::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_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_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_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_HG_ACK_OP:
568 {
569 DragAndDropSvc::PVBOXDNDCBHGACKOPDATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBHGACKOPDATA>(pvParms);
570 AssertPtr(pCBData);
571 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBHGACKOPDATA) == cbParms, VERR_INVALID_PARAMETER);
572 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_HG_ACK_OP == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
573
574 setActionDefault(pCBData->uAction);
575 rc = notifyAboutGuestResponse();
576 break;
577 }
578
579 case DragAndDropSvc::GUEST_DND_HG_REQ_DATA:
580 {
581 DragAndDropSvc::PVBOXDNDCBHGREQDATADATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBHGREQDATADATA>(pvParms);
582 AssertPtr(pCBData);
583 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBHGREQDATADATA) == cbParms, VERR_INVALID_PARAMETER);
584 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_HG_REQ_DATA == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
585
586 if ( pCBData->cbFormat == 0
587 || pCBData->cbFormat > _64K /** @todo Make this configurable? */
588 || pCBData->pszFormat == NULL)
589 {
590 rc = VERR_INVALID_PARAMETER;
591 }
592 else if (!RTStrIsValidEncoding(pCBData->pszFormat))
593 {
594 rc = VERR_INVALID_PARAMETER;
595 }
596 else
597 {
598 setFormats(GuestDnD::toFormatList(pCBData->pszFormat));
599 rc = VINF_SUCCESS;
600 }
601
602 int rc2 = notifyAboutGuestResponse();
603 if (RT_SUCCESS(rc))
604 rc = rc2;
605 break;
606 }
607
608 case DragAndDropSvc::GUEST_DND_HG_EVT_PROGRESS:
609 {
610 DragAndDropSvc::PVBOXDNDCBHGEVTPROGRESSDATA pCBData =
611 reinterpret_cast<DragAndDropSvc::PVBOXDNDCBHGEVTPROGRESSDATA>(pvParms);
612 AssertPtr(pCBData);
613 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBHGEVTPROGRESSDATA) == cbParms, VERR_INVALID_PARAMETER);
614 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_HG_EVT_PROGRESS == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
615
616 rc = setProgress(pCBData->uPercentage, pCBData->uStatus, pCBData->rc);
617 if (RT_SUCCESS(rc))
618 rc = notifyAboutGuestResponse();
619 break;
620 }
621#ifdef VBOX_WITH_DRAG_AND_DROP_GH
622 case DragAndDropSvc::GUEST_DND_GH_ACK_PENDING:
623 {
624 DragAndDropSvc::PVBOXDNDCBGHACKPENDINGDATA pCBData =
625 reinterpret_cast<DragAndDropSvc::PVBOXDNDCBGHACKPENDINGDATA>(pvParms);
626 AssertPtr(pCBData);
627 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBGHACKPENDINGDATA) == cbParms, VERR_INVALID_PARAMETER);
628 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_GH_ACK_PENDING == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
629
630 if ( pCBData->cbFormat == 0
631 || pCBData->cbFormat > _64K /** @todo Make the maximum size configurable? */
632 || pCBData->pszFormat == NULL)
633 {
634 rc = VERR_INVALID_PARAMETER;
635 }
636 else if (!RTStrIsValidEncoding(pCBData->pszFormat))
637 {
638 rc = VERR_INVALID_PARAMETER;
639 }
640 else
641 {
642 setFormats (GuestDnD::toFormatList(pCBData->pszFormat));
643 setActionDefault (pCBData->uDefAction);
644 setActionsAllowed(pCBData->uAllActions);
645
646 rc = VINF_SUCCESS;
647 }
648
649 int rc2 = notifyAboutGuestResponse();
650 if (RT_SUCCESS(rc))
651 rc = rc2;
652 break;
653 }
654#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
655 default:
656 /* * Try if the event is covered by a registered callback. */
657 fTryCallbacks = true;
658 break;
659 }
660
661 /*
662 * Try the host's installed callbacks (if any).
663 */
664 if (fTryCallbacks)
665 {
666 GuestDnDCallbackMap::const_iterator it = m_mapCallbacks.find(u32Function);
667 if (it != m_mapCallbacks.end())
668 {
669 AssertPtr(it->second.pfnCallback);
670 rc = it->second.pfnCallback(u32Function, pvParms, cbParms, it->second.pvUser);
671 }
672 else
673 {
674 LogFlowFunc(("No callback for function %RU32 defined (%zu callbacks total)\n", u32Function, m_mapCallbacks.size()));
675 rc = VERR_NOT_SUPPORTED; /* Tell the guest. */
676 }
677 }
678
679 LogFlowFunc(("Returning rc=%Rrc\n", rc));
680 return rc;
681}
682
683/**
684 * Helper function to query the internal progress object to an IProgress interface.
685 *
686 * @returns HRESULT
687 * @param ppProgress Where to query the progress object to.
688 */
689HRESULT GuestDnDResponse::queryProgressTo(IProgress **ppProgress)
690{
691 return m_pProgress.queryInterfaceTo(ppProgress);
692}
693
694/**
695 * Waits for a guest response to happen.
696 *
697 * @returns VBox status code.
698 * @param msTimeout Timeout (in ms) for waiting. Optional, waits 500 ms if not specified.
699 */
700int GuestDnDResponse::waitForGuestResponse(RTMSINTERVAL msTimeout /*= 500 */) const
701{
702 int rc = RTSemEventWait(m_EventSem, msTimeout);
703#ifdef DEBUG_andy
704 LogFlowFunc(("msTimeout=%RU32, rc=%Rrc\n", msTimeout, rc));
705#endif
706 return rc;
707}
708
709/*********************************************************************************************************************************
710 * GuestDnD implementation. *
711 ********************************************************************************************************************************/
712
713/** Static (Singleton) instance of the GuestDnD object. */
714GuestDnD* GuestDnD::s_pInstance = NULL;
715
716GuestDnD::GuestDnD(const ComObjPtr<Guest> &pGuest)
717 : m_pGuest(pGuest)
718 , m_cTransfersPending(0)
719{
720 LogFlowFuncEnter();
721
722 try
723 {
724 m_pResponse = new GuestDnDResponse(pGuest);
725 }
726 catch (std::bad_alloc &)
727 {
728 throw VERR_NO_MEMORY;
729 }
730
731 int rc = RTCritSectInit(&m_CritSect);
732 if (RT_FAILURE(rc))
733 throw rc;
734
735 /* List of supported default MIME types. */
736 LogRel2(("DnD: Supported default host formats:\n"));
737 const com::Utf8Str arrEntries[] = { VBOX_DND_FORMATS_DEFAULT };
738 for (size_t i = 0; i < RT_ELEMENTS(arrEntries); i++)
739 {
740 m_strDefaultFormats.push_back(arrEntries[i]);
741 LogRel2(("DnD: \t%s\n", arrEntries[i].c_str()));
742 }
743}
744
745GuestDnD::~GuestDnD(void)
746{
747 LogFlowFuncEnter();
748
749 Assert(m_cTransfersPending == 0); /* Sanity. */
750
751 RTCritSectDelete(&m_CritSect);
752
753 if (m_pResponse)
754 delete m_pResponse;
755}
756
757/**
758 * Adjusts coordinations to a given screen.
759 *
760 * @returns HRESULT
761 * @param uScreenId ID of screen to adjust coordinates to.
762 * @param puX Pointer to X coordinate to adjust. Will return the adjusted value on success.
763 * @param puY Pointer to Y coordinate to adjust. Will return the adjusted value on success.
764 */
765HRESULT GuestDnD::adjustScreenCoordinates(ULONG uScreenId, ULONG *puX, ULONG *puY) const
766{
767 /** @todo r=andy Save the current screen's shifting coordinates to speed things up.
768 * Only query for new offsets when the screen ID or the screen's resolution has changed. */
769
770 /* For multi-monitor support we need to add shift values to the coordinates
771 * (depending on the screen number). */
772 ComObjPtr<Console> pConsole = m_pGuest->i_getConsole();
773 ComPtr<IDisplay> pDisplay;
774 HRESULT hr = pConsole->COMGETTER(Display)(pDisplay.asOutParam());
775 if (FAILED(hr))
776 return hr;
777
778 ULONG dummy;
779 LONG xShift, yShift;
780 GuestMonitorStatus_T monitorStatus;
781 hr = pDisplay->GetScreenResolution(uScreenId, &dummy, &dummy, &dummy,
782 &xShift, &yShift, &monitorStatus);
783 if (FAILED(hr))
784 return hr;
785
786 if (puX)
787 *puX += xShift;
788 if (puY)
789 *puY += yShift;
790
791 LogFlowFunc(("uScreenId=%RU32, x=%RU32, y=%RU32\n", uScreenId, puX ? *puX : 0, puY ? *puY : 0));
792 return S_OK;
793}
794
795/**
796 * Sends a (blocking) message to the host side of the host service.
797 *
798 * @returns VBox status code.
799 * @param u32Function HGCM message ID to send.
800 * @param cParms Number of parameters to send.
801 * @param paParms Array of parameters to send. Must match \c cParms.
802 */
803int GuestDnD::hostCall(uint32_t u32Function, uint32_t cParms, PVBOXHGCMSVCPARM paParms) const
804{
805 Assert(!m_pGuest.isNull());
806 ComObjPtr<Console> pConsole = m_pGuest->i_getConsole();
807
808 /* Forward the information to the VMM device. */
809 Assert(!pConsole.isNull());
810 VMMDev *pVMMDev = pConsole->i_getVMMDev();
811 if (!pVMMDev)
812 return VERR_COM_OBJECT_NOT_FOUND;
813
814 return pVMMDev->hgcmHostCall("VBoxDragAndDropSvc", u32Function, cParms, paParms);
815}
816
817/**
818 * Registers a GuestDnDSource object with the GuestDnD manager.
819 *
820 * Currently only one source is supported at a time.
821 *
822 * @returns VBox status code.
823 * @param Source Source to register.
824 */
825int GuestDnD::registerSource(const ComObjPtr<GuestDnDSource> &Source)
826{
827 GUESTDND_LOCK();
828
829 Assert(m_lstSrc.size() == 0); /* We only support one source at a time at the moment. */
830 m_lstSrc.push_back(Source);
831
832 GUESTDND_UNLOCK();
833 return VINF_SUCCESS;
834}
835
836/**
837 * Unregisters a GuestDnDSource object from the GuestDnD manager.
838 *
839 * @returns VBox status code.
840 * @param Source Source to unregister.
841 */
842int GuestDnD::unregisterSource(const ComObjPtr<GuestDnDSource> &Source)
843{
844 GUESTDND_LOCK();
845
846 GuestDnDSrcList::iterator itSrc = std::find(m_lstSrc.begin(), m_lstSrc.end(), Source);
847 if (itSrc != m_lstSrc.end())
848 m_lstSrc.erase(itSrc);
849
850 GUESTDND_UNLOCK();
851 return VINF_SUCCESS;
852}
853
854/**
855 * Returns the current number of registered sources.
856 *
857 * @returns Current number of registered sources.
858 */
859size_t GuestDnD::getSourceCount(void)
860{
861 GUESTDND_LOCK_RET(0);
862
863 size_t cSources = m_lstSrc.size();
864
865 GUESTDND_UNLOCK();
866 return cSources;
867}
868
869/**
870 * Registers a GuestDnDTarget object with the GuestDnD manager.
871 *
872 * Currently only one target is supported at a time.
873 *
874 * @returns VBox status code.
875 * @param Target Target to register.
876 */
877int GuestDnD::registerTarget(const ComObjPtr<GuestDnDTarget> &Target)
878{
879 GUESTDND_LOCK();
880
881 Assert(m_lstTgt.size() == 0); /* We only support one target at a time at the moment. */
882 m_lstTgt.push_back(Target);
883
884 GUESTDND_UNLOCK();
885 return VINF_SUCCESS;
886}
887
888/**
889 * Unregisters a GuestDnDTarget object from the GuestDnD manager.
890 *
891 * @returns VBox status code.
892 * @param Target Target to unregister.
893 */
894int GuestDnD::unregisterTarget(const ComObjPtr<GuestDnDTarget> &Target)
895{
896 GUESTDND_LOCK();
897
898 GuestDnDTgtList::iterator itTgt = std::find(m_lstTgt.begin(), m_lstTgt.end(), Target);
899 if (itTgt != m_lstTgt.end())
900 m_lstTgt.erase(itTgt);
901
902 GUESTDND_UNLOCK();
903 return VINF_SUCCESS;
904}
905
906/**
907 * Returns the current number of registered targets.
908 *
909 * @returns Current number of registered targets.
910 */
911size_t GuestDnD::getTargetCount(void)
912{
913 GUESTDND_LOCK_RET(0);
914
915 size_t cTargets = m_lstTgt.size();
916
917 GUESTDND_UNLOCK();
918 return cTargets;
919}
920
921/**
922 * Static main dispatcher function to handle callbacks from the DnD host service.
923 *
924 * @returns VBox status code.
925 * @param pvExtension Pointer to service extension.
926 * @param u32Function Callback HGCM message ID.
927 * @param pvParms Pointer to optional data provided for a particular message. Optional.
928 * @param cbParms Size (in bytes) of \a pvParms.
929 */
930/* static */
931DECLCALLBACK(int) GuestDnD::notifyDnDDispatcher(void *pvExtension, uint32_t u32Function,
932 void *pvParms, uint32_t cbParms)
933{
934 LogFlowFunc(("pvExtension=%p, u32Function=%RU32, pvParms=%p, cbParms=%RU32\n",
935 pvExtension, u32Function, pvParms, cbParms));
936
937 GuestDnD *pGuestDnD = reinterpret_cast<GuestDnD*>(pvExtension);
938 AssertPtrReturn(pGuestDnD, VERR_INVALID_POINTER);
939
940 /** @todo In case we need to handle multiple guest DnD responses at a time this
941 * would be the place to lookup and dispatch to those. For the moment we
942 * only have one response -- simple. */
943 GuestDnDResponse *pResp = pGuestDnD->m_pResponse;
944 if (pResp)
945 return pResp->onDispatch(u32Function, pvParms, cbParms);
946
947 return VERR_NOT_SUPPORTED;
948}
949
950/**
951 * Static helper function to determine whether a format is part of a given MIME list.
952 *
953 * @returns \c true if found, \c false if not.
954 * @param strFormat Format to search for.
955 * @param lstFormats MIME list to search in.
956 */
957/* static */
958bool GuestDnD::isFormatInFormatList(const com::Utf8Str &strFormat, const GuestDnDMIMEList &lstFormats)
959{
960 return std::find(lstFormats.begin(), lstFormats.end(), strFormat) != lstFormats.end();
961}
962
963/**
964 * Static helper function to create a GuestDnDMIMEList out of a format list string.
965 *
966 * @returns MIME list object.
967 * @param strFormats List of formats to convert.
968 * @param strSep Separator to use. If not specified, DND_FORMATS_SEPARATOR will be used.
969 */
970/* static */
971GuestDnDMIMEList GuestDnD::toFormatList(const com::Utf8Str &strFormats, const com::Utf8Str &strSep /* = DND_FORMATS_SEPARATOR */)
972{
973 GuestDnDMIMEList lstFormats;
974 RTCList<RTCString> lstFormatsTmp = strFormats.split(strSep);
975
976 for (size_t i = 0; i < lstFormatsTmp.size(); i++)
977 lstFormats.push_back(com::Utf8Str(lstFormatsTmp.at(i)));
978
979 return lstFormats;
980}
981
982/**
983 * Static helper function to create a format list string from a given GuestDnDMIMEList object.
984 *
985 * @returns Format list string.
986 * @param lstFormats GuestDnDMIMEList to convert.
987 */
988/* static */
989com::Utf8Str GuestDnD::toFormatString(const GuestDnDMIMEList &lstFormats)
990{
991 com::Utf8Str strFormat;
992 for (size_t i = 0; i < lstFormats.size(); i++)
993 {
994 const com::Utf8Str &f = lstFormats.at(i);
995 strFormat += f + DND_FORMATS_SEPARATOR;
996 }
997
998 return strFormat;
999}
1000
1001/**
1002 * Static helper function to create a filtered GuestDnDMIMEList object from supported and wanted formats.
1003 *
1004 * @returns Filtered MIME list object.
1005 * @param lstFormatsSupported MIME list of supported formats.
1006 * @param lstFormatsWanted MIME list of wanted formats in returned object.
1007 */
1008/* static */
1009GuestDnDMIMEList GuestDnD::toFilteredFormatList(const GuestDnDMIMEList &lstFormatsSupported, const GuestDnDMIMEList &lstFormatsWanted)
1010{
1011 GuestDnDMIMEList lstFormats;
1012
1013 for (size_t i = 0; i < lstFormatsWanted.size(); i++)
1014 {
1015 /* Only keep supported format types. */
1016 if (std::find(lstFormatsSupported.begin(),
1017 lstFormatsSupported.end(), lstFormatsWanted.at(i)) != lstFormatsSupported.end())
1018 {
1019 lstFormats.push_back(lstFormatsWanted[i]);
1020 }
1021 }
1022
1023 return lstFormats;
1024}
1025
1026/**
1027 * Static helper function to create a filtered GuestDnDMIMEList object from supported and wanted formats.
1028 *
1029 * @returns Filtered MIME list object.
1030 * @param lstFormatsSupported MIME list of supported formats.
1031 * @param strFormatsWanted Format list string of wanted formats in returned object.
1032 */
1033/* static */
1034GuestDnDMIMEList GuestDnD::toFilteredFormatList(const GuestDnDMIMEList &lstFormatsSupported, const com::Utf8Str &strFormatsWanted)
1035{
1036 GuestDnDMIMEList lstFmt;
1037
1038 RTCList<RTCString> lstFormats = strFormatsWanted.split(DND_FORMATS_SEPARATOR);
1039 size_t i = 0;
1040 while (i < lstFormats.size())
1041 {
1042 /* Only keep allowed format types. */
1043 if (std::find(lstFormatsSupported.begin(),
1044 lstFormatsSupported.end(), lstFormats.at(i)) != lstFormatsSupported.end())
1045 {
1046 lstFmt.push_back(lstFormats[i]);
1047 }
1048 i++;
1049 }
1050
1051 return lstFmt;
1052}
1053
1054/**
1055 * Static helper function to convert a Main DnD action an internal DnD action.
1056 *
1057 * @returns Internal DnD action, or VBOX_DND_ACTION_IGNORE if not found / supported.
1058 * @param enmAction Main DnD action to convert.
1059 */
1060/* static */
1061VBOXDNDACTION GuestDnD::toHGCMAction(DnDAction_T enmAction)
1062{
1063 VBOXDNDACTION dndAction = VBOX_DND_ACTION_IGNORE;
1064 switch (enmAction)
1065 {
1066 case DnDAction_Copy:
1067 dndAction = VBOX_DND_ACTION_COPY;
1068 break;
1069 case DnDAction_Move:
1070 dndAction = VBOX_DND_ACTION_MOVE;
1071 break;
1072 case DnDAction_Link:
1073 /* For now it doesn't seems useful to allow a link
1074 action between host & guest. Later? */
1075 case DnDAction_Ignore:
1076 /* Ignored. */
1077 break;
1078 default:
1079 AssertMsgFailed(("Action %RU32 not recognized!\n", enmAction));
1080 break;
1081 }
1082
1083 return dndAction;
1084}
1085
1086/**
1087 * Static helper function to convert a Main DnD default action and allowed Main actions to their
1088 * corresponding internal representations.
1089 *
1090 * @param enmDnDActionDefault Default Main action to convert.
1091 * @param pDnDActionDefault Where to store the converted default action.
1092 * @param vecDnDActionsAllowed Allowed Main actions to convert.
1093 * @param pDnDLstActionsAllowed Where to store the converted allowed actions.
1094 */
1095/* static */
1096void GuestDnD::toHGCMActions(DnDAction_T enmDnDActionDefault,
1097 VBOXDNDACTION *pDnDActionDefault,
1098 const std::vector<DnDAction_T> vecDnDActionsAllowed,
1099 VBOXDNDACTIONLIST *pDnDLstActionsAllowed)
1100{
1101 VBOXDNDACTIONLIST dndLstActionsAllowed = VBOX_DND_ACTION_IGNORE;
1102 VBOXDNDACTION dndActionDefault = toHGCMAction(enmDnDActionDefault);
1103
1104 if (!vecDnDActionsAllowed.empty())
1105 {
1106 /* First convert the allowed actions to a bit array. */
1107 for (size_t i = 0; i < vecDnDActionsAllowed.size(); i++)
1108 dndLstActionsAllowed |= toHGCMAction(vecDnDActionsAllowed[i]);
1109
1110 /*
1111 * If no default action is set (ignoring), try one of the
1112 * set allowed actions, preferring copy, move (in that order).
1113 */
1114 if (isDnDIgnoreAction(dndActionDefault))
1115 {
1116 if (hasDnDCopyAction(dndLstActionsAllowed))
1117 dndActionDefault = VBOX_DND_ACTION_COPY;
1118 else if (hasDnDMoveAction(dndLstActionsAllowed))
1119 dndActionDefault = VBOX_DND_ACTION_MOVE;
1120 }
1121 }
1122
1123 if (pDnDActionDefault)
1124 *pDnDActionDefault = dndActionDefault;
1125 if (pDnDLstActionsAllowed)
1126 *pDnDLstActionsAllowed = dndLstActionsAllowed;
1127}
1128
1129/**
1130 * Static helper function to convert an internal DnD action to its Main representation.
1131 *
1132 * @returns Converted Main DnD action.
1133 * @param dndAction DnD action to convert.
1134 */
1135/* static */
1136DnDAction_T GuestDnD::toMainAction(VBOXDNDACTION dndAction)
1137{
1138 /* For now it doesn't seems useful to allow a
1139 * link action between host & guest. Maybe later! */
1140 return isDnDCopyAction(dndAction) ? DnDAction_Copy
1141 : isDnDMoveAction(dndAction) ? DnDAction_Move
1142 : DnDAction_Ignore;
1143}
1144
1145/**
1146 * Static helper function to convert an internal DnD action list to its Main representation.
1147 *
1148 * @returns Converted Main DnD action list.
1149 * @param dndActionList DnD action list to convert.
1150 */
1151/* static */
1152std::vector<DnDAction_T> GuestDnD::toMainActions(VBOXDNDACTIONLIST dndActionList)
1153{
1154 std::vector<DnDAction_T> vecActions;
1155
1156 /* For now it doesn't seems useful to allow a
1157 * link action between host & guest. Maybe later! */
1158 RTCList<DnDAction_T> lstActions;
1159 if (hasDnDCopyAction(dndActionList))
1160 lstActions.append(DnDAction_Copy);
1161 if (hasDnDMoveAction(dndActionList))
1162 lstActions.append(DnDAction_Move);
1163
1164 for (size_t i = 0; i < lstActions.size(); ++i)
1165 vecActions.push_back(lstActions.at(i));
1166
1167 return vecActions;
1168}
1169
1170/*********************************************************************************************************************************
1171 * GuestDnDBase implementation. *
1172 ********************************************************************************************************************************/
1173
1174GuestDnDBase::GuestDnDBase(void)
1175 : m_fIsPending(false)
1176{
1177 /* Initialize public attributes. */
1178 m_lstFmtSupported = GuestDnDInst()->defaultFormats();
1179}
1180
1181/**
1182 * Checks whether a given DnD format is supported or not.
1183 *
1184 * @returns \c true if supported, \c false if not.
1185 * @param aFormat DnD format to check.
1186 */
1187bool GuestDnDBase::i_isFormatSupported(const com::Utf8Str &aFormat) const
1188{
1189 return std::find(m_lstFmtSupported.begin(), m_lstFmtSupported.end(), aFormat) != m_lstFmtSupported.end();
1190}
1191
1192/**
1193 * Returns the currently supported DnD formats.
1194 *
1195 * @returns List of the supported DnD formats.
1196 */
1197const GuestDnDMIMEList &GuestDnDBase::i_getFormats(void) const
1198{
1199 return m_lstFmtSupported;
1200}
1201
1202/**
1203 * Adds DnD formats to the supported formats list.
1204 *
1205 * @returns HRESULT
1206 * @param aFormats List of DnD formats to add.
1207 */
1208HRESULT GuestDnDBase::i_addFormats(const GuestDnDMIMEList &aFormats)
1209{
1210 for (size_t i = 0; i < aFormats.size(); ++i)
1211 {
1212 Utf8Str strFormat = aFormats.at(i);
1213 if (std::find(m_lstFmtSupported.begin(),
1214 m_lstFmtSupported.end(), strFormat) == m_lstFmtSupported.end())
1215 {
1216 m_lstFmtSupported.push_back(strFormat);
1217 }
1218 }
1219
1220 return S_OK;
1221}
1222
1223/**
1224 * Removes DnD formats from tehh supported formats list.
1225 *
1226 * @returns HRESULT
1227 * @param aFormats List of DnD formats to remove.
1228 */
1229HRESULT GuestDnDBase::i_removeFormats(const GuestDnDMIMEList &aFormats)
1230{
1231 for (size_t i = 0; i < aFormats.size(); ++i)
1232 {
1233 Utf8Str strFormat = aFormats.at(i);
1234 GuestDnDMIMEList::iterator itFormat = std::find(m_lstFmtSupported.begin(),
1235 m_lstFmtSupported.end(), strFormat);
1236 if (itFormat != m_lstFmtSupported.end())
1237 m_lstFmtSupported.erase(itFormat);
1238 }
1239
1240 return S_OK;
1241}
1242
1243/**
1244 * Adds a new guest DnD message to the internal message queue.
1245 *
1246 * @returns VBox status code.
1247 * @param pMsg Pointer to message to add.
1248 */
1249int GuestDnDBase::msgQueueAdd(GuestDnDMsg *pMsg)
1250{
1251 m_DataBase.lstMsgOut.push_back(pMsg);
1252 return VINF_SUCCESS;
1253}
1254
1255/**
1256 * Returns the next guest DnD message in the internal message queue (FIFO).
1257 *
1258 * @returns Pointer to guest DnD message, or NULL if none found.
1259 */
1260GuestDnDMsg *GuestDnDBase::msgQueueGetNext(void)
1261{
1262 if (m_DataBase.lstMsgOut.empty())
1263 return NULL;
1264 return m_DataBase.lstMsgOut.front();
1265}
1266
1267/**
1268 * Removes the next guest DnD message from the internal message queue.
1269 */
1270void GuestDnDBase::msgQueueRemoveNext(void)
1271{
1272 if (!m_DataBase.lstMsgOut.empty())
1273 {
1274 GuestDnDMsg *pMsg = m_DataBase.lstMsgOut.front();
1275 if (pMsg)
1276 delete pMsg;
1277 m_DataBase.lstMsgOut.pop_front();
1278 }
1279}
1280
1281/**
1282 * Clears the internal message queue.
1283 */
1284void GuestDnDBase::msgQueueClear(void)
1285{
1286 LogFlowFunc(("cMsg=%zu\n", m_DataBase.lstMsgOut.size()));
1287
1288 GuestDnDMsgList::iterator itMsg = m_DataBase.lstMsgOut.begin();
1289 while (itMsg != m_DataBase.lstMsgOut.end())
1290 {
1291 GuestDnDMsg *pMsg = *itMsg;
1292 if (pMsg)
1293 delete pMsg;
1294
1295 itMsg++;
1296 }
1297
1298 m_DataBase.lstMsgOut.clear();
1299}
1300
1301/**
1302 * Sends a request to the guest side to cancel the current DnD operation.
1303 *
1304 * @returns VBox status code.
1305 */
1306int GuestDnDBase::sendCancel(void)
1307{
1308 GuestDnDMsg Msg;
1309 Msg.setType(HOST_DND_CANCEL);
1310 if (m_pResp->m_uProtocolVersion >= 3)
1311 Msg.appendUInt32(0); /** @todo ContextID not used yet. */
1312
1313 LogRel2(("DnD: Cancelling operation on guest ...\n"));
1314
1315 int rc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
1316 if (RT_FAILURE(rc))
1317 LogRel(("DnD: Cancelling operation on guest failed with %Rrc\n", rc));
1318
1319 return rc;
1320}
1321
1322/**
1323 * Helper function to update the progress based on given a GuestDnDData object.
1324 *
1325 * @returns VBox status code.
1326 * @param pData GuestDnDData object to use for accounting.
1327 * @param pResp GuestDnDResponse to update its progress object for.
1328 * @param cbDataAdd By how much data (in bytes) to update the progress.
1329 */
1330int GuestDnDBase::updateProgress(GuestDnDData *pData, GuestDnDResponse *pResp,
1331 size_t cbDataAdd /* = 0 */)
1332{
1333 AssertPtrReturn(pData, VERR_INVALID_POINTER);
1334 AssertPtrReturn(pResp, VERR_INVALID_POINTER);
1335 /* cbDataAdd is optional. */
1336
1337 LogFlowFunc(("cbExtra=%zu, cbProcessed=%zu, cbRemaining=%zu, cbDataAdd=%zu\n",
1338 pData->cbExtra, pData->cbProcessed, pData->getRemaining(), cbDataAdd));
1339
1340 if ( !pResp
1341 || !cbDataAdd) /* Only update if something really changes. */
1342 return VINF_SUCCESS;
1343
1344 if (cbDataAdd)
1345 pData->addProcessed(cbDataAdd);
1346
1347 const uint8_t uPercent = pData->getPercentComplete();
1348
1349 LogRel2(("DnD: Transfer %RU8%% complete\n", uPercent));
1350
1351 int rc = pResp->setProgress(uPercent,
1352 pData->isComplete()
1353 ? DND_PROGRESS_COMPLETE
1354 : DND_PROGRESS_RUNNING);
1355 LogFlowFuncLeaveRC(rc);
1356 return rc;
1357}
1358
1359/**
1360 * Waits for a specific guest callback event to get signalled.
1361 *
1362 ** @todo GuestDnDResponse *pResp needs to go.
1363 *
1364 * @returns VBox status code. Will return VERR_CANCELLED if the user has cancelled the progress object.
1365 * @param pEvent Callback event to wait for.
1366 * @param pResp Response to update.
1367 * @param msTimeout Timeout (in ms) to wait.
1368 */
1369int GuestDnDBase::waitForEvent(GuestDnDCallbackEvent *pEvent, GuestDnDResponse *pResp, RTMSINTERVAL msTimeout)
1370{
1371 AssertPtrReturn(pEvent, VERR_INVALID_POINTER);
1372 AssertPtrReturn(pResp, VERR_INVALID_POINTER);
1373
1374 int rc;
1375
1376 LogFlowFunc(("msTimeout=%RU32\n", msTimeout));
1377
1378 uint64_t tsStart = RTTimeMilliTS();
1379 do
1380 {
1381 /*
1382 * Wait until our desired callback triggered the
1383 * wait event. As we don't want to block if the guest does not
1384 * respond, do busy waiting here.
1385 */
1386 rc = pEvent->Wait(500 /* ms */);
1387 if (RT_SUCCESS(rc))
1388 {
1389 rc = pEvent->Result();
1390 LogFlowFunc(("Callback done, result is %Rrc\n", rc));
1391 break;
1392 }
1393 else if (rc == VERR_TIMEOUT) /* Continue waiting. */
1394 rc = VINF_SUCCESS;
1395
1396 if ( msTimeout != RT_INDEFINITE_WAIT
1397 && RTTimeMilliTS() - tsStart > msTimeout)
1398 {
1399 rc = VERR_TIMEOUT;
1400 LogRel2(("DnD: Error: Guest did not respond within time\n"));
1401 }
1402 else if (pResp->isProgressCanceled()) /** @todo GuestDnDResponse *pResp needs to go. */
1403 {
1404 LogRel2(("DnD: Operation was canceled by user\n"));
1405 rc = VERR_CANCELLED;
1406 }
1407
1408 } while (RT_SUCCESS(rc));
1409
1410 LogFlowFuncLeaveRC(rc);
1411 return rc;
1412}
1413#endif /* VBOX_WITH_DRAG_AND_DROP */
1414
Note: See TracBrowser for help on using the repository browser.

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