VirtualBox

source: vbox/trunk/src/VBox/Main/src-client/GuestDnDTargetImpl.cpp@ 55822

Last change on this file since 55822 was 55822, checked in by vboxsync, 9 years ago

Logging.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 38.0 KB
Line 
1/* $Id: GuestDnDTargetImpl.cpp 55822 2015-05-12 11:44:42Z vboxsync $ */
2/** @file
3 * VBox Console COM Class implementation - Guest drag'n drop target.
4 */
5
6/*
7 * Copyright (C) 2014-2015 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
19/*******************************************************************************
20* Header Files *
21*******************************************************************************/
22#include "GuestImpl.h"
23#include "GuestDnDTargetImpl.h"
24#include "ConsoleImpl.h"
25
26#include "Global.h"
27#include "AutoCaller.h"
28
29#include <algorithm> /* For std::find(). */
30
31#include <iprt/asm.h>
32#include <iprt/file.h>
33#include <iprt/dir.h>
34#include <iprt/path.h>
35#include <iprt/uri.h>
36#include <iprt/cpp/utils.h> /* For unconst(). */
37
38#include <VBox/com/array.h>
39
40#include <VBox/GuestHost/DragAndDrop.h>
41#include <VBox/HostServices/Service.h>
42
43#ifdef LOG_GROUP
44 #undef LOG_GROUP
45#endif
46#define LOG_GROUP LOG_GROUP_GUEST_DND
47#include <VBox/log.h>
48
49
50/**
51 * Base class for a target task.
52 */
53class GuestDnDTargetTask
54{
55public:
56
57 GuestDnDTargetTask(GuestDnDTarget *pTarget)
58 : mTarget(pTarget),
59 mRC(VINF_SUCCESS) { }
60
61 virtual ~GuestDnDTargetTask(void) { }
62
63 int getRC(void) const { return mRC; }
64 bool isOk(void) const { return RT_SUCCESS(mRC); }
65 const ComObjPtr<GuestDnDTarget> &getTarget(void) const { return mTarget; }
66
67protected:
68
69 const ComObjPtr<GuestDnDTarget> mTarget;
70 int mRC;
71};
72
73/**
74 * Task structure for sending data to a target using
75 * a worker thread.
76 */
77class SendDataTask : public GuestDnDTargetTask
78{
79public:
80
81 SendDataTask(GuestDnDTarget *pTarget, PSENDDATACTX pCtx)
82 : GuestDnDTargetTask(pTarget),
83 mpCtx(pCtx) { }
84
85 virtual ~SendDataTask(void)
86 {
87 if (mpCtx)
88 {
89 delete mpCtx;
90 mpCtx = NULL;
91 }
92 }
93
94
95 PSENDDATACTX getCtx(void) { return mpCtx; }
96
97protected:
98
99 /** Pointer to send data context. */
100 PSENDDATACTX mpCtx;
101};
102
103// constructor / destructor
104/////////////////////////////////////////////////////////////////////////////
105
106DEFINE_EMPTY_CTOR_DTOR(GuestDnDTarget)
107
108HRESULT GuestDnDTarget::FinalConstruct(void)
109{
110 /* Set the maximum block size our guests can handle to 64K. This always has
111 * been hardcoded until now. */
112 /* Note: Never ever rely on information from the guest; the host dictates what and
113 * how to do something, so try to negogiate a sensible value here later. */
114 mData.mcbBlockSize = _64K; /** @todo Make this configurable. */
115
116 LogFlowThisFunc(("\n"));
117 return BaseFinalConstruct();
118}
119
120void GuestDnDTarget::FinalRelease(void)
121{
122 LogFlowThisFuncEnter();
123 uninit();
124 BaseFinalRelease();
125 LogFlowThisFuncLeave();
126}
127
128// public initializer/uninitializer for internal purposes only
129/////////////////////////////////////////////////////////////////////////////
130
131int GuestDnDTarget::init(const ComObjPtr<Guest>& pGuest)
132{
133 LogFlowThisFuncEnter();
134
135 /* Enclose the state transition NotReady->InInit->Ready. */
136 AutoInitSpan autoInitSpan(this);
137 AssertReturn(autoInitSpan.isOk(), E_FAIL);
138
139 unconst(m_pGuest) = pGuest;
140
141 /* Confirm a successful initialization when it's the case. */
142 autoInitSpan.setSucceeded();
143
144 return VINF_SUCCESS;
145}
146
147/**
148 * Uninitializes the instance.
149 * Called from FinalRelease().
150 */
151void GuestDnDTarget::uninit(void)
152{
153 LogFlowThisFunc(("\n"));
154
155 /* Enclose the state transition Ready->InUninit->NotReady. */
156 AutoUninitSpan autoUninitSpan(this);
157 if (autoUninitSpan.uninitDone())
158 return;
159}
160
161// implementation of wrapped IDnDBase methods.
162/////////////////////////////////////////////////////////////////////////////
163
164HRESULT GuestDnDTarget::isFormatSupported(const com::Utf8Str &aFormat, BOOL *aSupported)
165{
166#if !defined(VBOX_WITH_DRAG_AND_DROP)
167 ReturnComNotImplemented();
168#else /* VBOX_WITH_DRAG_AND_DROP */
169
170 AutoCaller autoCaller(this);
171 if (FAILED(autoCaller.rc())) return autoCaller.rc();
172
173 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
174
175 return GuestDnDBase::i_isFormatSupported(aFormat, aSupported);
176#endif /* VBOX_WITH_DRAG_AND_DROP */
177}
178
179HRESULT GuestDnDTarget::getFormats(std::vector<com::Utf8Str> &aFormats)
180{
181#if !defined(VBOX_WITH_DRAG_AND_DROP)
182 ReturnComNotImplemented();
183#else /* VBOX_WITH_DRAG_AND_DROP */
184
185 AutoCaller autoCaller(this);
186 if (FAILED(autoCaller.rc())) return autoCaller.rc();
187
188 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
189
190 return GuestDnDBase::i_getFormats(aFormats);
191#endif /* VBOX_WITH_DRAG_AND_DROP */
192}
193
194HRESULT GuestDnDTarget::addFormats(const std::vector<com::Utf8Str> &aFormats)
195{
196#if !defined(VBOX_WITH_DRAG_AND_DROP)
197 ReturnComNotImplemented();
198#else /* VBOX_WITH_DRAG_AND_DROP */
199
200 AutoCaller autoCaller(this);
201 if (FAILED(autoCaller.rc())) return autoCaller.rc();
202
203 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
204
205 return GuestDnDBase::i_addFormats(aFormats);
206#endif /* VBOX_WITH_DRAG_AND_DROP */
207}
208
209HRESULT GuestDnDTarget::removeFormats(const std::vector<com::Utf8Str> &aFormats)
210{
211#if !defined(VBOX_WITH_DRAG_AND_DROP)
212 ReturnComNotImplemented();
213#else /* VBOX_WITH_DRAG_AND_DROP */
214
215 AutoCaller autoCaller(this);
216 if (FAILED(autoCaller.rc())) return autoCaller.rc();
217
218 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
219
220 return GuestDnDBase::i_removeFormats(aFormats);
221#endif /* VBOX_WITH_DRAG_AND_DROP */
222}
223
224HRESULT GuestDnDTarget::getProtocolVersion(ULONG *aProtocolVersion)
225{
226#if !defined(VBOX_WITH_DRAG_AND_DROP)
227 ReturnComNotImplemented();
228#else /* VBOX_WITH_DRAG_AND_DROP */
229
230 AutoCaller autoCaller(this);
231 if (FAILED(autoCaller.rc())) return autoCaller.rc();
232
233 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
234
235 return GuestDnDBase::i_getProtocolVersion(aProtocolVersion);
236#endif /* VBOX_WITH_DRAG_AND_DROP */
237}
238
239// implementation of wrapped IDnDTarget methods.
240/////////////////////////////////////////////////////////////////////////////
241
242HRESULT GuestDnDTarget::enter(ULONG aScreenId, ULONG aX, ULONG aY,
243 DnDAction_T aDefaultAction,
244 const std::vector<DnDAction_T> &aAllowedActions,
245 const std::vector<com::Utf8Str> &aFormats,
246 DnDAction_T *aResultAction)
247{
248#if !defined(VBOX_WITH_DRAG_AND_DROP)
249 ReturnComNotImplemented();
250#else /* VBOX_WITH_DRAG_AND_DROP */
251
252 /* Input validation. */
253 if (aDefaultAction == DnDAction_Ignore)
254 return setError(E_INVALIDARG, tr("No default action specified"));
255 if (!aAllowedActions.size())
256 return setError(E_INVALIDARG, tr("Number of allowed actions is empty"));
257 if (!aFormats.size())
258 return setError(E_INVALIDARG, tr("Number of supported formats is empty"));
259
260 AutoCaller autoCaller(this);
261 if (FAILED(autoCaller.rc())) return autoCaller.rc();
262
263 /* Determine guest DnD protocol to use. */
264 GuestDnDBase::getProtocolVersion(&mDataBase.mProtocolVersion);
265
266 /* Default action is ignoring. */
267 DnDAction_T resAction = DnDAction_Ignore;
268
269 /* Check & convert the drag & drop actions */
270 uint32_t uDefAction = 0;
271 uint32_t uAllowedActions = 0;
272 GuestDnD::toHGCMActions(aDefaultAction, &uDefAction,
273 aAllowedActions, &uAllowedActions);
274 /* If there is no usable action, ignore this request. */
275 if (isDnDIgnoreAction(uDefAction))
276 return S_OK;
277
278 /* Make a flat data string out of the supported format list. */
279 Utf8Str strFormats = GuestDnD::toFormatString(m_strFormats, aFormats);
280 /* If there is no valid supported format, ignore this request. */
281 if (strFormats.isEmpty())
282 return S_OK;
283
284 HRESULT hr = S_OK;
285
286 /* Adjust the coordinates in a multi-monitor setup. */
287 int rc = GuestDnDInst()->adjustScreenCoordinates(aScreenId, &aX, &aY);
288 if (RT_SUCCESS(rc))
289 {
290 GuestDnDMsg Msg;
291 Msg.setType(DragAndDropSvc::HOST_DND_HG_EVT_ENTER);
292 Msg.setNextUInt32(aScreenId);
293 Msg.setNextUInt32(aX);
294 Msg.setNextUInt32(aY);
295 Msg.setNextUInt32(uDefAction);
296 Msg.setNextUInt32(uAllowedActions);
297 Msg.setNextPointer((void*)strFormats.c_str(), strFormats.length() + 1);
298 Msg.setNextUInt32(strFormats.length() + 1);
299
300 rc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
301 if (RT_SUCCESS(rc))
302 {
303 GuestDnDResponse *pResp = GuestDnDInst()->response();
304 if (pResp && RT_SUCCESS(pResp->waitForGuestResponse()))
305 resAction = GuestDnD::toMainAction(pResp->defAction());
306 }
307 }
308
309 if (aResultAction)
310 *aResultAction = resAction;
311
312 LogFlowFunc(("hr=%Rhrc, resAction=%ld\n", hr, resAction));
313 return hr;
314#endif /* VBOX_WITH_DRAG_AND_DROP */
315}
316
317HRESULT GuestDnDTarget::move(ULONG aScreenId, ULONG aX, ULONG aY,
318 DnDAction_T aDefaultAction,
319 const std::vector<DnDAction_T> &aAllowedActions,
320 const std::vector<com::Utf8Str> &aFormats,
321 DnDAction_T *aResultAction)
322{
323#if !defined(VBOX_WITH_DRAG_AND_DROP)
324 ReturnComNotImplemented();
325#else /* VBOX_WITH_DRAG_AND_DROP */
326
327 /* Input validation. */
328
329 AutoCaller autoCaller(this);
330 if (FAILED(autoCaller.rc())) return autoCaller.rc();
331
332 /* Default action is ignoring. */
333 DnDAction_T resAction = DnDAction_Ignore;
334
335 /* Check & convert the drag & drop actions. */
336 uint32_t uDefAction = 0;
337 uint32_t uAllowedActions = 0;
338 GuestDnD::toHGCMActions(aDefaultAction, &uDefAction,
339 aAllowedActions, &uAllowedActions);
340 /* If there is no usable action, ignore this request. */
341 if (isDnDIgnoreAction(uDefAction))
342 return S_OK;
343
344 /* Make a flat data string out of the supported format list. */
345 RTCString strFormats = GuestDnD::toFormatString(m_strFormats, aFormats);
346 /* If there is no valid supported format, ignore this request. */
347 if (strFormats.isEmpty())
348 return S_OK;
349
350 HRESULT hr = S_OK;
351
352 int rc = GuestDnDInst()->adjustScreenCoordinates(aScreenId, &aX, &aY);
353 if (RT_SUCCESS(rc))
354 {
355 GuestDnDMsg Msg;
356 Msg.setType(DragAndDropSvc::HOST_DND_HG_EVT_MOVE);
357 Msg.setNextUInt32(aScreenId);
358 Msg.setNextUInt32(aX);
359 Msg.setNextUInt32(aY);
360 Msg.setNextUInt32(uDefAction);
361 Msg.setNextUInt32(uAllowedActions);
362 Msg.setNextPointer((void*)strFormats.c_str(), strFormats.length() + 1);
363 Msg.setNextUInt32(strFormats.length() + 1);
364
365 rc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
366 if (RT_SUCCESS(rc))
367 {
368 GuestDnDResponse *pResp = GuestDnDInst()->response();
369 if (pResp && RT_SUCCESS(pResp->waitForGuestResponse()))
370 resAction = GuestDnD::toMainAction(pResp->defAction());
371 }
372 }
373
374 if (aResultAction)
375 *aResultAction = resAction;
376
377 LogFlowFunc(("hr=%Rhrc, *pResultAction=%ld\n", hr, resAction));
378 return hr;
379#endif /* VBOX_WITH_DRAG_AND_DROP */
380}
381
382HRESULT GuestDnDTarget::leave(ULONG uScreenId)
383{
384#if !defined(VBOX_WITH_DRAG_AND_DROP)
385 ReturnComNotImplemented();
386#else /* VBOX_WITH_DRAG_AND_DROP */
387
388 AutoCaller autoCaller(this);
389 if (FAILED(autoCaller.rc())) return autoCaller.rc();
390
391 HRESULT hr = S_OK;
392 int rc = GuestDnDInst()->hostCall(DragAndDropSvc::HOST_DND_HG_EVT_LEAVE,
393 0 /* cParms */, NULL /* paParms */);
394 if (RT_SUCCESS(rc))
395 {
396 GuestDnDResponse *pResp = GuestDnDInst()->response();
397 if (pResp)
398 pResp->waitForGuestResponse();
399 }
400
401 LogFlowFunc(("hr=%Rhrc\n", hr));
402 return hr;
403#endif /* VBOX_WITH_DRAG_AND_DROP */
404}
405
406HRESULT GuestDnDTarget::drop(ULONG aScreenId, ULONG aX, ULONG aY,
407 DnDAction_T aDefaultAction,
408 const std::vector<DnDAction_T> &aAllowedActions,
409 const std::vector<com::Utf8Str> &aFormats,
410 com::Utf8Str &aFormat, DnDAction_T *aResultAction)
411{
412#if !defined(VBOX_WITH_DRAG_AND_DROP)
413 ReturnComNotImplemented();
414#else /* VBOX_WITH_DRAG_AND_DROP */
415
416 /* Input validation. */
417
418 /* Everything else is optional. */
419
420 AutoCaller autoCaller(this);
421 if (FAILED(autoCaller.rc())) return autoCaller.rc();
422
423 /* Default action is ignoring. */
424 DnDAction_T resAction = DnDAction_Ignore;
425
426 /* Check & convert the drag & drop actions. */
427 uint32_t uDefAction = 0;
428 uint32_t uAllowedActions = 0;
429 GuestDnD::toHGCMActions(aDefaultAction, &uDefAction,
430 aAllowedActions, &uAllowedActions);
431 /* If there is no usable action, ignore this request. */
432 if (isDnDIgnoreAction(uDefAction))
433 return S_OK;
434
435 /* Make a flat data string out of the supported format list. */
436 Utf8Str strFormats = GuestDnD::toFormatString(m_strFormats, aFormats);
437 /* If there is no valid supported format, ignore this request. */
438 if (strFormats.isEmpty())
439 return S_OK;
440
441 HRESULT hr = S_OK;
442
443 /* Adjust the coordinates in a multi-monitor setup. */
444 int rc = GuestDnDInst()->adjustScreenCoordinates(aScreenId, &aX, &aY);
445 if (RT_SUCCESS(rc))
446 {
447 GuestDnDMsg Msg;
448 Msg.setType(DragAndDropSvc::HOST_DND_HG_EVT_DROPPED);
449 Msg.setNextUInt32(aScreenId);
450 Msg.setNextUInt32(aX);
451 Msg.setNextUInt32(aY);
452 Msg.setNextUInt32(uDefAction);
453 Msg.setNextUInt32(uAllowedActions);
454 Msg.setNextPointer((void*)strFormats.c_str(), strFormats.length() + 1);
455 Msg.setNextUInt32(strFormats.length() + 1);
456
457 rc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
458 if (RT_SUCCESS(rc))
459 {
460 GuestDnDResponse *pResp = GuestDnDInst()->response();
461 if (pResp && RT_SUCCESS(pResp->waitForGuestResponse()))
462 {
463 resAction = GuestDnD::toMainAction(pResp->defAction());
464 aFormat = pResp->format();
465
466 LogFlowFunc(("resFormat=%s, resAction=%RU32\n",
467 pResp->format().c_str(), pResp->defAction()));
468 }
469 }
470 }
471
472 if (aResultAction)
473 *aResultAction = resAction;
474
475 return hr;
476#endif /* VBOX_WITH_DRAG_AND_DROP */
477}
478
479/* static */
480DECLCALLBACK(int) GuestDnDTarget::i_sendDataThread(RTTHREAD Thread, void *pvUser)
481{
482 LogFlowFunc(("pvUser=%p\n", pvUser));
483
484 SendDataTask *pTask = (SendDataTask *)pvUser;
485 AssertPtrReturn(pTask, VERR_INVALID_POINTER);
486
487 const ComObjPtr<GuestDnDTarget> pTarget(pTask->getTarget());
488 Assert(!pTarget.isNull());
489
490 int rc;
491
492 AutoCaller autoCaller(pTarget);
493 if (SUCCEEDED(autoCaller.rc()))
494 {
495 rc = pTarget->i_sendData(pTask->getCtx(), RT_INDEFINITE_WAIT /* msTimeout */);
496 /* Nothing to do here anymore. */
497 }
498 else
499 rc = VERR_COM_INVALID_OBJECT_STATE;
500
501 ASMAtomicWriteBool(&pTarget->mDataBase.mfTransferIsPending, false);
502
503 if (pTask)
504 delete pTask;
505
506 LogFlowFunc(("pTarget=%p returning rc=%Rrc\n", (GuestDnDTarget *)pTarget, rc));
507 return rc;
508}
509
510/**
511 * Initiates a data transfer from the host to the guest. The source is the host whereas the target is the
512 * guest in this case.
513 *
514 * @return HRESULT
515 * @param aScreenId
516 * @param aFormat
517 * @param aData
518 * @param aProgress
519 */
520HRESULT GuestDnDTarget::sendData(ULONG aScreenId, const com::Utf8Str &aFormat, const std::vector<BYTE> &aData,
521 ComPtr<IProgress> &aProgress)
522{
523#if !defined(VBOX_WITH_DRAG_AND_DROP)
524 ReturnComNotImplemented();
525#else /* VBOX_WITH_DRAG_AND_DROP */
526
527 AutoCaller autoCaller(this);
528 if (FAILED(autoCaller.rc())) return autoCaller.rc();
529
530 /* Input validation. */
531 if (RT_UNLIKELY((aFormat.c_str()) == NULL || *(aFormat.c_str()) == '\0'))
532 return setError(E_INVALIDARG, tr("No data format specified"));
533 if (RT_UNLIKELY(!aData.size()))
534 return setError(E_INVALIDARG, tr("No data to send specified"));
535
536 /* Note: At the moment we only support one transfer at a time. */
537 if (ASMAtomicReadBool(&mDataBase.mfTransferIsPending))
538 return setError(E_INVALIDARG, tr("Another send operation already is in progress"));
539
540 ASMAtomicWriteBool(&mDataBase.mfTransferIsPending, true);
541
542 /* Dito. */
543 GuestDnDResponse *pResp = GuestDnDInst()->response();
544 AssertPtr(pResp);
545
546 HRESULT hr = pResp->resetProgress(m_pGuest);
547 if (FAILED(hr))
548 return hr;
549
550 try
551 {
552 PSENDDATACTX pSendCtx = new SENDDATACTX;
553 RT_BZERO(pSendCtx, sizeof(SENDDATACTX));
554
555 pSendCtx->mpTarget = this;
556 pSendCtx->mpResp = pResp;
557 pSendCtx->mScreenID = aScreenId;
558 pSendCtx->mFormat = aFormat;
559 pSendCtx->mData.vecData = aData;
560
561 SendDataTask *pTask = new SendDataTask(this, pSendCtx);
562 AssertReturn(pTask->isOk(), pTask->getRC());
563
564 LogFlowFunc(("Starting thread ...\n"));
565
566 int rc = RTThreadCreate(NULL, GuestDnDTarget::i_sendDataThread,
567 (void *)pTask, 0, RTTHREADTYPE_MAIN_WORKER, 0, "dndTgtSndData");
568 if (RT_SUCCESS(rc))
569 {
570 hr = pResp->queryProgressTo(aProgress.asOutParam());
571 ComAssertComRC(hr);
572
573 /* Note: pTask is now owned by the worker thread. */
574 }
575 else
576 hr = setError(VBOX_E_IPRT_ERROR, tr("Starting thread failed (%Rrc)"), rc);
577
578 if (RT_FAILURE(rc))
579 delete pSendCtx;
580 }
581 catch(std::bad_alloc &)
582 {
583 hr = setError(E_OUTOFMEMORY);
584 }
585
586 /* Note: mDataBase.mfTransferIsPending will be set to false again by i_sendDataThread. */
587
588 LogFlowFunc(("Returning hr=%Rhrc\n", hr));
589 return hr;
590#endif /* VBOX_WITH_DRAG_AND_DROP */
591}
592
593int GuestDnDTarget::i_cancelOperation(void)
594{
595 /** @todo Check for pending cancel requests. */
596
597#if 0 /** @todo Later. */
598 /* Cancel any outstanding waits for guest responses first. */
599 if (pResp)
600 pResp->notifyAboutGuestResponse();
601#endif
602
603 LogFlowFunc(("Cancelling operation, telling guest ...\n"));
604 return GuestDnDInst()->hostCall(DragAndDropSvc::HOST_DND_HG_EVT_CANCEL, 0 /* cParms */, NULL /*paParms*/);
605}
606
607int GuestDnDTarget::i_sendData(PSENDDATACTX pCtx, RTMSINTERVAL msTimeout)
608{
609 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
610
611 GuestDnD *pInst = GuestDnDInst();
612 if (!pInst)
613 return VERR_INVALID_POINTER;
614
615 int rc;
616
617 ASMAtomicWriteBool(&pCtx->mIsActive, true);
618
619 /* Clear all remaining outgoing messages. */
620 mDataBase.mListOutgoing.clear();
621
622 const char *pszFormat = pCtx->mFormat.c_str();
623 uint32_t cbFormat = pCtx->mFormat.length() + 1;
624
625 /* Do we need to build up a file tree? */
626 bool fHasURIList = DnDMIMEHasFileURLs(pszFormat, cbFormat);
627 if (fHasURIList)
628 {
629 rc = i_sendURIData(pCtx, msTimeout);
630 }
631 else
632 {
633 rc = i_sendRawData(pCtx, msTimeout);
634 }
635
636 ASMAtomicWriteBool(&pCtx->mIsActive, false);
637
638#undef DATA_IS_VALID_BREAK
639
640 LogFlowFuncLeaveRC(rc);
641 return rc;
642}
643
644int GuestDnDTarget::i_sendDirectory(PSENDDATACTX pCtx, GuestDnDMsg *pMsg, DnDURIObject &aDirectory)
645{
646 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
647
648 RTCString strPath = aDirectory.GetDestPath();
649 if (strPath.isEmpty())
650 return VERR_INVALID_PARAMETER;
651 if (strPath.length() >= RTPATH_MAX) /* Note: Maximum is RTPATH_MAX on guest side. */
652 return VERR_BUFFER_OVERFLOW;
653
654 LogFlowFunc(("Sending directory \"%s\" using protocol v%RU32 ...\n", strPath.c_str(), mDataBase.mProtocolVersion));
655
656 pMsg->setType(DragAndDropSvc::HOST_DND_HG_SND_DIR);
657 pMsg->setNextString(strPath.c_str()); /* path */
658 pMsg->setNextUInt32((uint32_t)(strPath.length() + 1)); /* path length - note: Maximum is RTPATH_MAX on guest side. */
659 pMsg->setNextUInt32(aDirectory.GetMode()); /* mode */
660
661 return VINF_SUCCESS;
662}
663
664int GuestDnDTarget::i_sendFile(PSENDDATACTX pCtx, GuestDnDMsg *pMsg, DnDURIObject &aFile)
665{
666 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
667
668 RTCString strPathSrc = aFile.GetSourcePath();
669 if (strPathSrc.isEmpty())
670 return VERR_INVALID_PARAMETER;
671
672 int rc = VINF_SUCCESS;
673
674 LogFlowFunc(("Sending \"%s\" (%RU32 bytes buffer) using protocol v%RU32 ...\n",
675 strPathSrc.c_str(), mData.mcbBlockSize, mDataBase.mProtocolVersion));
676
677 bool fOpen = aFile.IsOpen();
678 if (!fOpen)
679 {
680 LogFlowFunc(("Opening \"%s\" ...\n", strPathSrc.c_str()));
681 rc = aFile.OpenEx(strPathSrc, DnDURIObject::File, DnDURIObject::Source,
682 RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE, 0 /* fFlags */);
683 if (RT_FAILURE(rc))
684 LogRel2(("DnD: Error opening host file \"%s\", rc=%Rrc\n", strPathSrc.c_str(), rc));
685 }
686
687 bool fSendFileData = false;
688 if (RT_SUCCESS(rc))
689 {
690 if (mDataBase.mProtocolVersion >= 2)
691 {
692 if (!fOpen)
693 {
694 /*
695 * Since protocol v2 the file header and the actual file contents are
696 * separate messages, so send the file header first.
697 * The just registered callback will be called by the guest afterwards.
698 */
699 pMsg->setType(DragAndDropSvc::HOST_DND_HG_SND_FILE_HDR);
700 pMsg->setNextUInt32(0); /* context ID */
701 rc = pMsg->setNextString(aFile.GetDestPath().c_str()); /* pvName */
702 AssertRC(rc);
703 pMsg->setNextUInt32((uint32_t)(aFile.GetDestPath().length() + 1)); /* cbName */
704 pMsg->setNextUInt32(0); /* uFlags */
705 pMsg->setNextUInt32(aFile.GetMode()); /* fMode */
706 pMsg->setNextUInt64(aFile.GetSize()); /* uSize */
707
708 LogFlowFunc(("Sending file header ...\n"));
709 LogRel2(("DnD: Transferring host file to guest: %s (%RU64 bytes, mode 0x%x)\n",
710 strPathSrc.c_str(), aFile.GetSize(), aFile.GetMode()));
711
712 /** @todo Set progress object title to current file being transferred? */
713 }
714 else
715 {
716 /* File header was sent, so only send the actual file data. */
717 fSendFileData = true;
718 }
719 }
720 else /* Protocol v1. */
721 {
722 /* Always send the file data, every time. */
723 fSendFileData = true;
724 }
725 }
726
727 if ( RT_SUCCESS(rc)
728 && fSendFileData)
729 {
730 rc = i_sendFileData(pCtx, pMsg, aFile);
731 }
732
733 LogFlowFuncLeaveRC(rc);
734 return rc;
735}
736
737int GuestDnDTarget::i_sendFileData(PSENDDATACTX pCtx, GuestDnDMsg *pMsg, DnDURIObject &aFile)
738{
739 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
740 AssertPtrReturn(pMsg, VERR_INVALID_POINTER);
741
742 GuestDnDResponse *pResp = pCtx->mpResp;
743 AssertPtr(pResp);
744
745 /** @todo Don't allow concurrent reads per context! */
746
747 /* Something to transfer? */
748 if ( pCtx->mURI.lstURI.IsEmpty()
749 || !pCtx->mIsActive)
750 {
751 return VERR_WRONG_ORDER;
752 }
753
754 /*
755 * Start sending stuff.
756 */
757
758 /* Set the message type. */
759 pMsg->setType(DragAndDropSvc::HOST_DND_HG_SND_FILE_DATA);
760
761 /* Protocol version 1 sends the file path *every* time with a new file chunk.
762 * In protocol version 2 we only do this once with HOST_DND_HG_SND_FILE_HDR. */
763 if (mDataBase.mProtocolVersion <= 1)
764 {
765 pMsg->setNextString(aFile.GetSourcePath().c_str()); /* pvName */
766 pMsg->setNextUInt32((uint32_t)(aFile.GetSourcePath().length() + 1)); /* cbName */
767 }
768 else
769 {
770 /* Protocol version 2 also sends the context ID. Currently unused. */
771 pMsg->setNextUInt32(0); /* context ID */
772 }
773
774 uint32_t cbRead = 0;
775
776 int rc = aFile.Read(pCtx->mURI.pvScratchBuf, pCtx->mURI.cbScratchBuf, &cbRead);
777 if (RT_SUCCESS(rc))
778 {
779 pCtx->mData.cbProcessed += cbRead;
780
781 if (mDataBase.mProtocolVersion <= 1)
782 {
783 pMsg->setNextPointer(pCtx->mURI.pvScratchBuf, cbRead); /* pvData */
784 pMsg->setNextUInt32(cbRead); /* cbData */
785 pMsg->setNextUInt32(aFile.GetMode()); /* fMode */
786 }
787 else
788 {
789 pMsg->setNextPointer(pCtx->mURI.pvScratchBuf, cbRead); /* pvData */
790 pMsg->setNextUInt32(cbRead); /* cbData */
791 }
792
793 if (aFile.IsComplete()) /* Done reading? */
794 {
795 LogRel2(("DnD: File transfer to guest complete: %s\n", aFile.GetSourcePath().c_str()));
796 LogFlowFunc(("File \"%s\" complete\n", aFile.GetSourcePath().c_str()));
797 rc = VINF_EOF;
798 }
799 }
800
801 LogFlowFuncLeaveRC(rc);
802 return rc;
803}
804
805/* static */
806DECLCALLBACK(int) GuestDnDTarget::i_sendURIDataCallback(uint32_t uMsg, void *pvParms, size_t cbParms, void *pvUser)
807{
808 PSENDDATACTX pCtx = (PSENDDATACTX)pvUser;
809 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
810
811 GuestDnDTarget *pThis = pCtx->mpTarget;
812 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
813
814 LogFlowFunc(("pThis=%p, uMsg=%RU32\n", pThis, uMsg));
815
816 int rc = VINF_SUCCESS;
817
818 switch (uMsg)
819 {
820 case DragAndDropSvc::GUEST_DND_GET_NEXT_HOST_MSG:
821 {
822 DragAndDropSvc::PVBOXDNDCBHGGETNEXTHOSTMSG pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBHGGETNEXTHOSTMSG>(pvParms);
823 AssertPtr(pCBData);
824 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBHGGETNEXTHOSTMSG) == cbParms, VERR_INVALID_PARAMETER);
825 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_HG_GET_NEXT_HOST_MSG == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
826
827 try
828 {
829 GuestDnDMsg *pMsg = new GuestDnDMsg();
830
831 rc = pThis->i_sendURIDataLoop(pCtx, pMsg);
832 if (RT_SUCCESS(rc))
833 {
834 rc = pThis->msgQueueAdd(pMsg);
835 if (RT_SUCCESS(rc)) /* Return message type & required parameter count to the guest. */
836 {
837 LogFlowFunc(("GUEST_DND_GET_NEXT_HOST_MSG -> %RU32 (%RU32 params)\n", pMsg->getType(), pMsg->getCount()));
838 pCBData->uMsg = pMsg->getType();
839 pCBData->cParms = pMsg->getCount();
840 }
841 }
842
843 if (RT_FAILURE(rc))
844 delete pMsg;
845 }
846 catch(std::bad_alloc & /*e*/)
847 {
848 rc = VERR_NO_MEMORY;
849 }
850 break;
851 }
852 case DragAndDropSvc::GUEST_DND_GH_EVT_ERROR:
853 {
854 DragAndDropSvc::PVBOXDNDCBEVTERRORDATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBEVTERRORDATA>(pvParms);
855 AssertPtr(pCBData);
856 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBEVTERRORDATA) == cbParms, VERR_INVALID_PARAMETER);
857 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_GH_EVT_ERROR == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
858
859 pCtx->mpResp->reset();
860 rc = pCtx->mpResp->setProgress(100, DragAndDropSvc::DND_PROGRESS_ERROR, pCBData->rc);
861 if (RT_SUCCESS(rc))
862 rc = pCBData->rc;
863 break;
864 }
865 case DragAndDropSvc::HOST_DND_HG_SND_DIR:
866 case DragAndDropSvc::HOST_DND_HG_SND_FILE_HDR:
867 case DragAndDropSvc::HOST_DND_HG_SND_FILE_DATA:
868 {
869 DragAndDropSvc::PVBOXDNDCBHGGETNEXTHOSTMSGDATA pCBData
870 = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBHGGETNEXTHOSTMSGDATA>(pvParms);
871 AssertPtr(pCBData);
872 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBHGGETNEXTHOSTMSGDATA) == cbParms, VERR_INVALID_PARAMETER);
873 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_HG_GET_NEXT_HOST_MSG_DATA == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
874
875 LogFlowFunc(("pCBData->uMsg=%RU32, paParms=%p, cParms=%RU32\n", pCBData->uMsg, pCBData->paParms, pCBData->cParms));
876
877 GuestDnDMsg *pMsg = pThis->msgQueueGetNext();
878 if (pMsg)
879 {
880 /*
881 * Sanity checks.
882 */
883 if ( pCBData->uMsg != uMsg
884 || pCBData->paParms == NULL
885 || pCBData->cParms != pMsg->getCount())
886 {
887 /* Start over. */
888 pThis->msgQueueClear();
889
890 rc = VERR_INVALID_PARAMETER;
891 }
892
893 if (RT_SUCCESS(rc))
894 {
895 LogFlowFunc(("Returning uMsg=%RU32\n", uMsg));
896 rc = HGCM::Message::copyParms(pMsg->getCount(), pMsg->getParms(), pCBData->paParms);
897 if (RT_SUCCESS(rc))
898 {
899 pCBData->cParms = pMsg->getCount();
900 pThis->msgQueueRemoveNext();
901 }
902 else
903 LogFlowFunc(("Copying parameters failed with rc=%Rrc\n", rc));
904 }
905 }
906 else
907 rc = VERR_NO_DATA;
908
909 LogFlowFunc(("Processing next message ended with rc=%Rrc\n", rc));
910 break;
911 }
912 default:
913 rc = VERR_NOT_SUPPORTED;
914 break;
915 }
916
917 if (RT_FAILURE(rc))
918 {
919 switch (rc)
920 {
921 case VERR_NO_DATA:
922 LogRel2(("DnD: Transfer complete\n"));
923 break;
924
925 case VERR_CANCELLED:
926 LogRel2(("DnD: Transfer canceled\n"));
927 break;
928
929 default:
930 LogRel(("DnD: Error %Rrc occurred, aborting transfer\n", rc));
931 break;
932 }
933
934 /* Unregister this callback. */
935 AssertPtr(pCtx->mpResp);
936 int rc2 = pCtx->mpResp->setCallback(uMsg, NULL /* PFNGUESTDNDCALLBACK */);
937 AssertRC(rc2);
938
939 /* Notify waiters. */
940 rc2 = pCtx->mCallback.Notify(rc);
941 AssertRC(rc2);
942 }
943
944 LogFlowFuncLeaveRC(rc);
945 return rc; /* Tell the guest. */
946}
947
948int GuestDnDTarget::i_sendURIData(PSENDDATACTX pCtx, RTMSINTERVAL msTimeout)
949{
950 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
951 AssertPtr(pCtx->mpResp);
952
953#define URI_DATA_IS_VALID_BREAK(x) \
954 if (!x) \
955 { \
956 LogFlowFunc(("Invalid URI data value for \"" #x "\"\n")); \
957 rc = VERR_INVALID_PARAMETER; \
958 break; \
959 }
960
961 void *pvBuf = RTMemAlloc(mData.mcbBlockSize);
962 if (!pvBuf)
963 return VERR_NO_MEMORY;
964
965 int rc;
966
967#define REGISTER_CALLBACK(x) \
968 rc = pCtx->mpResp->setCallback(x, i_sendURIDataCallback, pCtx); \
969 if (RT_FAILURE(rc)) \
970 return rc;
971
972#define UNREGISTER_CALLBACK(x) \
973 { \
974 int rc2 = pCtx->mpResp->setCallback(x, NULL); \
975 AssertRC(rc2); \
976 }
977
978 rc = pCtx->mCallback.Reset();
979 if (RT_FAILURE(rc))
980 return rc;
981
982 /*
983 * Register callbacks.
984 */
985 /* Guest callbacks. */
986 REGISTER_CALLBACK(DragAndDropSvc::GUEST_DND_GET_NEXT_HOST_MSG);
987 REGISTER_CALLBACK(DragAndDropSvc::GUEST_DND_GH_EVT_ERROR);
988 /* Host callbacks. */
989 REGISTER_CALLBACK(DragAndDropSvc::HOST_DND_HG_SND_DIR);
990 if (mDataBase.mProtocolVersion >= 2)
991 REGISTER_CALLBACK(DragAndDropSvc::HOST_DND_HG_SND_FILE_HDR);
992 REGISTER_CALLBACK(DragAndDropSvc::HOST_DND_HG_SND_FILE_DATA);
993
994 do
995 {
996 /*
997 * Set our scratch buffer.
998 */
999 pCtx->mURI.pvScratchBuf = pvBuf;
1000 pCtx->mURI.cbScratchBuf = mData.mcbBlockSize;
1001
1002 /*
1003 * Extract URI list from byte data.
1004 */
1005 DnDURIList &lstURI = pCtx->mURI.lstURI; /* Use the URI list from the context. */
1006
1007 const char *pszList = (const char *)&pCtx->mData.vecData.front();
1008 URI_DATA_IS_VALID_BREAK(pszList);
1009
1010 uint32_t cbList = pCtx->mData.vecData.size();
1011 URI_DATA_IS_VALID_BREAK(cbList);
1012
1013 RTCList<RTCString> lstURIOrg = RTCString(pszList, cbList).split("\r\n");
1014 URI_DATA_IS_VALID_BREAK(!lstURIOrg.isEmpty());
1015
1016 rc = lstURI.AppendURIPathsFromList(lstURIOrg, 0 /* fFlags */);
1017 if (RT_SUCCESS(rc))
1018 LogFlowFunc(("URI root objects: %zu, total bytes (raw data to transfer): %zu\n",
1019 lstURI.RootCount(), lstURI.TotalBytes()));
1020 else
1021 break;
1022
1023 pCtx->mData.cbProcessed = 0;
1024 pCtx->mData.cbToProcess = lstURI.TotalBytes();
1025
1026 /*
1027 * The first message always is the meta info for the data. The meta
1028 * info *only* contains the root elements of an URI list.
1029 *
1030 * After the meta data we generate the messages required to send the data itself.
1031 */
1032 Assert(!lstURI.IsEmpty());
1033 RTCString strData = lstURI.RootToString().c_str();
1034 size_t cbData = strData.length() + 1; /* Include terminating zero. */
1035
1036 GuestDnDMsg MsgSndData;
1037 MsgSndData.setType(DragAndDropSvc::HOST_DND_HG_SND_DATA);
1038 MsgSndData.setNextUInt32(pCtx->mScreenID);
1039 MsgSndData.setNextPointer((void *)pCtx->mFormat.c_str(), (uint32_t)pCtx->mFormat.length() + 1);
1040 MsgSndData.setNextUInt32((uint32_t)pCtx->mFormat.length() + 1);
1041 MsgSndData.setNextPointer((void*)strData.c_str(), (uint32_t)cbData);
1042 MsgSndData.setNextUInt32((uint32_t)cbData);
1043
1044 rc = GuestDnDInst()->hostCall(MsgSndData.getType(), MsgSndData.getCount(), MsgSndData.getParms());
1045 if (RT_SUCCESS(rc))
1046 rc = waitForEvent(msTimeout, pCtx->mCallback, pCtx->mpResp);
1047
1048 } while (0);
1049
1050 /*
1051 * Unregister callbacks.
1052 */
1053 /* Guest callbacks. */
1054 UNREGISTER_CALLBACK(DragAndDropSvc::GUEST_DND_GET_NEXT_HOST_MSG);
1055 UNREGISTER_CALLBACK(DragAndDropSvc::GUEST_DND_GH_EVT_ERROR);
1056 /* Host callbacks. */
1057 UNREGISTER_CALLBACK(DragAndDropSvc::HOST_DND_HG_SND_DIR);
1058 if (mDataBase.mProtocolVersion >= 2)
1059 UNREGISTER_CALLBACK(DragAndDropSvc::HOST_DND_HG_SND_FILE_HDR);
1060 UNREGISTER_CALLBACK(DragAndDropSvc::HOST_DND_HG_SND_FILE_DATA);
1061
1062#undef REGISTER_CALLBACK
1063#undef UNREGISTER_CALLBACK
1064
1065 /*
1066 * Now that we've cleaned up tell the guest side to cancel.
1067 */
1068 if (rc == VERR_CANCELLED)
1069 {
1070 int rc2 = sendCancel();
1071 AssertRC(rc2);
1072 }
1073
1074 /* Destroy temporary scratch buffer. */
1075 if (pvBuf)
1076 RTMemFree(pvBuf);
1077
1078#undef URI_DATA_IS_VALID_BREAK
1079
1080 LogFlowFuncLeaveRC(rc);
1081 return rc;
1082}
1083
1084int GuestDnDTarget::i_sendURIDataLoop(PSENDDATACTX pCtx, GuestDnDMsg *pMsg)
1085{
1086 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1087
1088 DnDURIList &lstURI = pCtx->mURI.lstURI;
1089
1090 int rc;
1091
1092 uint64_t cbTotal = pCtx->mData.cbToProcess;
1093 uint8_t uPercent = pCtx->mData.cbProcessed * 100 / (cbTotal ? cbTotal : 1);
1094
1095 LogFlowFunc(("%RU64 / %RU64 -- %RU8%%\n", pCtx->mData.cbProcessed, cbTotal, uPercent));
1096
1097 bool fComplete = (uPercent >= 100) || lstURI.IsEmpty();
1098
1099 if (pCtx->mpResp)
1100 {
1101 int rc2 = pCtx->mpResp->setProgress(uPercent,
1102 fComplete
1103 ? DragAndDropSvc::DND_PROGRESS_COMPLETE
1104 : DragAndDropSvc::DND_PROGRESS_RUNNING);
1105 AssertRC(rc2);
1106 }
1107
1108 if (fComplete)
1109 {
1110 LogFlowFunc(("Last URI item processed, bailing out\n"));
1111 return VERR_NO_DATA;
1112 }
1113
1114 Assert(!lstURI.IsEmpty());
1115 DnDURIObject &curObj = lstURI.First();
1116
1117 uint32_t fMode = curObj.GetMode();
1118 LogFlowFunc(("Processing srcPath=%s, dstPath=%s, fMode=0x%x, cbSize=%RU32, fIsDir=%RTbool, fIsFile=%RTbool\n",
1119 curObj.GetSourcePath().c_str(), curObj.GetDestPath().c_str(),
1120 fMode, curObj.GetSize(),
1121 RTFS_IS_DIRECTORY(fMode), RTFS_IS_FILE(fMode)));
1122
1123 if (RTFS_IS_DIRECTORY(fMode))
1124 {
1125 rc = i_sendDirectory(pCtx, pMsg, curObj);
1126 }
1127 else if (RTFS_IS_FILE(fMode))
1128 {
1129 rc = i_sendFile(pCtx, pMsg, curObj);
1130 }
1131 else
1132 {
1133 AssertMsgFailed(("fMode=0x%x is not supported for srcPath=%s, dstPath=%s\n",
1134 fMode, curObj.GetSourcePath().c_str(), curObj.GetDestPath().c_str()));
1135 rc = VERR_NOT_SUPPORTED;
1136 }
1137
1138 bool fRemove = false; /* Remove current entry? */
1139 if ( curObj.IsComplete()
1140 || RT_FAILURE(rc))
1141 {
1142 fRemove = true;
1143 }
1144
1145 if (fRemove)
1146 {
1147 LogFlowFunc(("Removing \"%s\" from list, rc=%Rrc\n", curObj.GetSourcePath().c_str(), rc));
1148 lstURI.RemoveFirst();
1149 }
1150
1151 LogFlowFuncLeaveRC(rc);
1152 return rc;
1153}
1154
1155int GuestDnDTarget::i_sendRawData(PSENDDATACTX pCtx, RTMSINTERVAL msTimeout)
1156{
1157 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1158 NOREF(msTimeout);
1159
1160 GuestDnD *pInst = GuestDnDInst();
1161 AssertPtr(pInst);
1162
1163 /* At the moment we only allow up to 64K raw data. */
1164 size_t cbDataTotal = pCtx->mData.vecData.size();
1165 if ( !cbDataTotal
1166 || cbDataTotal > _64K)
1167 {
1168 return VERR_INVALID_PARAMETER;
1169 }
1170
1171 /* Just copy over the raw data. */
1172 GuestDnDMsg Msg;
1173 Msg.setType(DragAndDropSvc::HOST_DND_HG_SND_DATA);
1174 Msg.setNextUInt32(pCtx->mScreenID);
1175 Msg.setNextPointer((void *)pCtx->mFormat.c_str(), (uint32_t)pCtx->mFormat.length() + 1);
1176 Msg.setNextUInt32((uint32_t)pCtx->mFormat.length() + 1);
1177 Msg.setNextPointer((void*)&pCtx->mData.vecData.front(), (uint32_t)cbDataTotal);
1178 Msg.setNextUInt32(cbDataTotal);
1179
1180 LogFlowFunc(("%zu total bytes of raw data to transfer\n", cbDataTotal));
1181
1182 return pInst->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
1183}
1184
1185HRESULT GuestDnDTarget::cancel(BOOL *aVeto)
1186{
1187#if !defined(VBOX_WITH_DRAG_AND_DROP)
1188 ReturnComNotImplemented();
1189#else /* VBOX_WITH_DRAG_AND_DROP */
1190
1191 int rc = i_cancelOperation();
1192
1193 if (aVeto)
1194 *aVeto = FALSE; /** @todo */
1195
1196 return RT_SUCCESS(rc) ? S_OK : VBOX_E_IPRT_ERROR;
1197#endif /* VBOX_WITH_DRAG_AND_DROP */
1198}
1199
Note: See TracBrowser for help on using the repository browser.

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