VirtualBox

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

Last change on this file since 55512 was 55512, checked in by vboxsync, 10 years ago

DnD: Bugfixes, cleanup, spelling.

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