VirtualBox

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

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

No more auto_ptr.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 35.8 KB
Line 
1/* $Id: GuestDnDTargetImpl.cpp 55427 2015-04-24 14:49:11Z 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 delete pTask;
506 return rc;
507}
508
509/**
510 * Initiates a data transfer from the host to the guest. The source is the host whereas the target is the
511 * guest in this case.
512 *
513 * @return HRESULT
514 * @param aScreenId
515 * @param aFormat
516 * @param aData
517 * @param aProgress
518 */
519HRESULT GuestDnDTarget::sendData(ULONG aScreenId, const com::Utf8Str &aFormat, const std::vector<BYTE> &aData,
520 ComPtr<IProgress> &aProgress)
521{
522#if !defined(VBOX_WITH_DRAG_AND_DROP)
523 ReturnComNotImplemented();
524#else /* VBOX_WITH_DRAG_AND_DROP */
525
526 /** @todo Add input validation. */
527 /** @todo Check if another sendData() call currently is being processed. */
528
529 AutoCaller autoCaller(this);
530 if (FAILED(autoCaller.rc())) return autoCaller.rc();
531
532 HRESULT hr;
533 int vrc;
534
535 /* Note: At the moment we only support one response at a time. */
536 GuestDnDResponse *pResp = GuestDnDInst()->response();
537 if (pResp)
538 {
539 pResp->resetProgress(m_pGuest);
540
541 try
542 {
543 PSENDDATACTX pSendCtx = new SENDDATACTX;
544 RT_BZERO(pSendCtx, sizeof(SENDDATACTX));
545
546 pSendCtx->mpTarget = this;
547 pSendCtx->mpResp = pResp;
548 pSendCtx->mScreenID = aScreenId;
549 pSendCtx->mFormat = aFormat;
550 pSendCtx->mData = aData;
551
552 SendDataTask *pTask = new SendDataTask(this, pSendCtx);
553 AssertReturn(pTask->isOk(), pTask->getRC());
554
555 vrc = RTThreadCreate(NULL, GuestDnDTarget::i_sendDataThread,
556 (void *)pTask, 0, RTTHREADTYPE_MAIN_WORKER, 0, "dndTgtSndData");
557 if (RT_SUCCESS(vrc))
558 {
559 hr = pResp->queryProgressTo(aProgress.asOutParam());
560 ComAssertComRC(hr);
561
562 /* Note: pTask is now owned by the worker thread. */
563 }
564 else if (pSendCtx)
565 delete pSendCtx;
566 }
567 catch(std::bad_alloc &)
568 {
569 vrc = VERR_NO_MEMORY;
570 }
571
572 /*if (RT_FAILURE(vrc)) ** @todo SetError(...) */
573 }
574 /** @todo SetError(...) */
575
576 return hr;
577#endif /* VBOX_WITH_DRAG_AND_DROP */
578}
579
580int GuestDnDTarget::i_cancelOperation(void)
581{
582 /** @todo Check for pending cancel requests. */
583
584#if 0 /** @todo Later. */
585 /* Cancel any outstanding waits for guest responses first. */
586 if (pResp)
587 pResp->notifyAboutGuestResponse();
588#endif
589
590 LogFlowFunc(("Cancelling operation, telling guest ...\n"));
591 return GuestDnDInst()->hostCall(DragAndDropSvc::HOST_DND_HG_EVT_CANCEL, 0 /* cParms */, NULL /*paParms*/);
592}
593
594int GuestDnDTarget::i_sendData(PSENDDATACTX pCtx)
595{
596 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
597
598#define DATA_IS_VALID_BREAK(x) \
599 if (!x) \
600 { \
601 LogFlowFunc(("Invalid URI data value for \"" #x "\"\n")); \
602 rc = VERR_INVALID_PARAMETER; \
603 break; \
604 }
605
606 GuestDnD *pInst = GuestDnDInst();
607 if (!pInst)
608 return VERR_INVALID_POINTER;
609
610 int rc;
611
612 ASMAtomicWriteBool(&pCtx->mIsActive, true);
613
614 do
615 {
616 const char *pszFormat = pCtx->mFormat.c_str();
617 DATA_IS_VALID_BREAK(pszFormat);
618 uint32_t cbFormat = pCtx->mFormat.length() + 1;
619
620 /* Do we need to build up a file tree? */
621 bool fHasURIList = DnDMIMEHasFileURLs(pszFormat, cbFormat);
622 if (fHasURIList)
623 {
624 rc = i_sendURIData(pCtx);
625 }
626 else
627 {
628 GuestDnDMsg Msg;
629
630 size_t cbDataTotal = pCtx->mData.size();
631 DATA_IS_VALID_BREAK(cbDataTotal);
632
633 /* Just copy over the raw data. */
634 Msg.setType(DragAndDropSvc::HOST_DND_HG_SND_DATA);
635 Msg.setNextUInt32(pCtx->mScreenID);
636 Msg.setNextPointer((void *)pCtx->mFormat.c_str(), (uint32_t)pCtx->mFormat.length() + 1);
637 Msg.setNextUInt32((uint32_t)pCtx->mFormat.length() + 1);
638 Msg.setNextPointer((void*)&pCtx->mData.front(), (uint32_t)cbDataTotal);
639 Msg.setNextUInt32(cbDataTotal);
640
641 LogFlowFunc(("%zu total bytes of raw data to transfer\n", cbDataTotal));
642
643 /* Make the initial call to the guest by sending the actual data. This might
644 * be an URI list which in turn can lead to more data to send afterwards. */
645 rc = pInst->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
646 if (RT_FAILURE(rc))
647 break;
648 }
649
650 } while (0);
651
652 ASMAtomicWriteBool(&pCtx->mIsActive, false);
653
654#undef DATA_IS_VALID_BREAK
655
656 LogFlowFuncLeaveRC(rc);
657 return rc;
658}
659
660int GuestDnDTarget::i_sendDirectory(PSENDDATACTX pCtx, GuestDnDMsg *pMsg, DnDURIObject &aDirectory)
661{
662 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
663
664 RTCString strPath = aDirectory.GetDestPath();
665 if (strPath.isEmpty())
666 return VERR_INVALID_PARAMETER;
667 if (strPath.length() >= RTPATH_MAX) /* Note: Maximum is RTPATH_MAX on guest side. */
668 return VERR_BUFFER_OVERFLOW;
669
670 LogFlowFunc(("Sending directory \"%s\" using protocol v%RU32 ...\n", strPath.c_str(), mData.mProtocolVersion));
671
672 pMsg->setType(DragAndDropSvc::HOST_DND_HG_SND_DIR);
673 pMsg->setNextString(strPath.c_str()); /* path */
674 pMsg->setNextUInt32((uint32_t)(strPath.length() + 1)); /* path length - note: Maximum is RTPATH_MAX on guest side. */
675 pMsg->setNextUInt32(aDirectory.GetMode()); /* mode */
676
677 return VINF_SUCCESS;
678}
679
680int GuestDnDTarget::i_sendFile(PSENDDATACTX pCtx, GuestDnDMsg *pMsg, DnDURIObject &aFile)
681{
682 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
683
684 RTCString strPath = aFile.GetDestPath();
685 if (strPath.isEmpty())
686 return VERR_INVALID_PARAMETER;
687
688 int rc = VINF_SUCCESS;
689
690 LogFlowFunc(("Sending \"%s\" (%RU32 bytes buffer) using protocol v%RU32 ...\n",
691 aFile.GetDestPath().c_str(), m_cbBlockSize, mData.mProtocolVersion));
692
693 bool fSendFileData = false;
694 if (mData.mProtocolVersion >= 2)
695 {
696 if (!aFile.IsOpen())
697 {
698 rc = aFile.OpenEx(aFile.GetSourcePath(), DnDURIObject::File, DnDURIObject::Source,
699 RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE, 0 /* fFlags */);
700 if (RT_SUCCESS(rc))
701 {
702 /*
703 * Since protocol v2 the file header and the actual file contents are
704 * separate messages, so send the file header first.
705 * The just registered callback will be called by the guest afterwards.
706 */
707 pMsg->setType(DragAndDropSvc::HOST_DND_HG_SND_FILE_HDR);
708 pMsg->setNextUInt32(0); /* context ID */
709 pMsg->setNextString(strPath.c_str()); /* pvName */
710 pMsg->setNextUInt32((uint32_t)(strPath.length() + 1)); /* cbName */
711 pMsg->setNextUInt32(0); /* uFlags */
712 pMsg->setNextUInt32(aFile.GetMode()); /* fMode */
713 pMsg->setNextUInt64(aFile.GetSize()); /* uSize */
714
715 LogFlowFunc(("Sending file header ...\n"));
716 }
717 }
718 else
719 {
720 /* File header was sent, so only send the actual file data. */
721 fSendFileData = true;
722 }
723 }
724 else /* Protocol v1. */
725 {
726 /* Always send the file data, every time. */
727 fSendFileData = true;
728 }
729
730 if ( RT_SUCCESS(rc)
731 && fSendFileData)
732 {
733 rc = i_sendFileData(pCtx, pMsg, aFile);
734 }
735
736 LogFlowFuncLeaveRC(rc);
737 return rc;
738}
739
740int GuestDnDTarget::i_sendFileData(PSENDDATACTX pCtx, GuestDnDMsg *pMsg, DnDURIObject &aFile)
741{
742 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
743 AssertPtrReturn(pMsg, VERR_INVALID_POINTER);
744
745 GuestDnDResponse *pResp = pCtx->mpResp;
746 AssertPtr(pResp);
747
748 /** @todo Don't allow concurrent reads per context! */
749
750 /* Something to transfer? */
751 if ( pCtx->mURI.lstURI.IsEmpty()
752 || !pCtx->mIsActive)
753 {
754 return VERR_WRONG_ORDER;
755 }
756
757 /*
758 * Start sending stuff.
759 */
760
761 /* Set the message type. */
762 pMsg->setType(DragAndDropSvc::HOST_DND_HG_SND_FILE_DATA);
763
764 /* Protocol version 1 sends the file path *every* time with a new file chunk.
765 * In protocol version 2 we only do this once with HOST_DND_HG_SND_FILE_HDR. */
766 if (mData.mProtocolVersion <= 1)
767 {
768 pMsg->setNextUInt32(0); /* context ID */
769 pMsg->setNextString(aFile.GetSourcePath().c_str()); /* pvName */
770 pMsg->setNextUInt32((uint32_t)(aFile.GetSourcePath().length() + 1)); /* cbName */
771 }
772
773 uint32_t cbRead = 0;
774
775 int rc = aFile.Read(pCtx->mURI.pvScratchBuf, pCtx->mURI.cbScratchBuf, &cbRead);
776 if (RT_SUCCESS(rc))
777 {
778 pCtx->mURI.cbProcessed += cbRead;
779
780 if (mData.mProtocolVersion <= 1)
781 {
782 pMsg->setNextPointer(pCtx->mURI.pvScratchBuf, cbRead); /* pvData */
783 pMsg->setNextUInt32(cbRead); /* cbData */
784 pMsg->setNextUInt32(aFile.GetMode()); /* fMode */
785 }
786 else
787 {
788 pMsg->setNextPointer(pCtx->mURI.pvScratchBuf, cbRead); /* pvData */
789 pMsg->setNextUInt32(cbRead); /* cbData */
790 }
791
792 if (aFile.IsComplete()) /* Done reading? */
793 {
794 LogFlowFunc(("File \"%s\" complete\n", aFile.GetSourcePath().c_str()));
795 rc = VINF_EOF;
796 }
797 }
798
799 LogFlowFuncLeaveRC(rc);
800 return rc;
801}
802
803/* static */
804DECLCALLBACK(int) GuestDnDTarget::i_sendURIDataCallback(uint32_t uMsg, void *pvParms, size_t cbParms, void *pvUser)
805{
806 PSENDDATACTX pCtx = (PSENDDATACTX)pvUser;
807 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
808
809 GuestDnDTarget *pThis = pCtx->mpTarget;
810 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
811
812 LogFlowFunc(("pThis=%p, uMsg=%RU32\n", pThis, uMsg));
813
814 int rc = VINF_SUCCESS;
815
816 switch (uMsg)
817 {
818 case DragAndDropSvc::GUEST_DND_GET_NEXT_HOST_MSG:
819 {
820 DragAndDropSvc::PVBOXDNDCBHGGETNEXTHOSTMSG pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBHGGETNEXTHOSTMSG>(pvParms);
821 AssertPtr(pCBData);
822 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBHGGETNEXTHOSTMSG) == cbParms, VERR_INVALID_PARAMETER);
823 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_HG_GET_NEXT_HOST_MSG == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
824
825 GuestDnDMsg *pMsg;
826 try
827 {
828 pMsg = new GuestDnDMsg();
829 rc = pThis->i_sendURIDataLoop(pCtx, pMsg);
830 if (RT_SUCCESS(rc))
831 {
832 rc = pThis->addMsg(pMsg);
833 if (RT_SUCCESS(rc)) /* Return message type & required parameter count to the guest. */
834 {
835 LogFlowFunc(("GUEST_DND_GET_NEXT_HOST_MSG -> %RU32 (%RU32 params)\n", pMsg->getType(), pMsg->getCount()));
836 pCBData->uMsg = pMsg->getType();
837 pCBData->cParms = pMsg->getCount();
838 }
839 }
840
841 if (RT_FAILURE(rc))
842 {
843 if (rc == VERR_NO_DATA) /* All URI objects processed? */
844 {
845 /* Unregister this callback. */
846 AssertPtr(pCtx->mpResp);
847 int rc2 = pCtx->mpResp->setCallback(uMsg, NULL /* PFNGUESTDNDCALLBACK */);
848 if (RT_FAILURE(rc2))
849 LogFlowFunc(("Error: Unable to unregister callback for message %RU32, rc=%Rrc\n", uMsg, rc2));
850 }
851
852 delete pMsg;
853 }
854 }
855 catch(std::bad_alloc & /*e*/)
856 {
857 rc = VERR_NO_MEMORY;
858 }
859 break;
860 }
861 case DragAndDropSvc::HOST_DND_HG_SND_DIR:
862 case DragAndDropSvc::HOST_DND_HG_SND_FILE_HDR:
863 case DragAndDropSvc::HOST_DND_HG_SND_FILE_DATA:
864 {
865 DragAndDropSvc::PVBOXDNDCBHGGETNEXTHOSTMSGDATA pCBData
866 = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBHGGETNEXTHOSTMSGDATA>(pvParms);
867 AssertPtr(pCBData);
868 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBHGGETNEXTHOSTMSGDATA) == cbParms, VERR_INVALID_PARAMETER);
869 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_HG_GET_NEXT_HOST_MSG_DATA == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
870
871 GuestDnDMsg *pMsg = pThis->nextMsg();
872 if (pMsg)
873 {
874 /*
875 * Sanity checks.
876 */
877 if ( pCBData->uMsg != uMsg
878 || pCBData->paParms == NULL
879 || pCBData->cParms != pMsg->getCount())
880 {
881 rc = VERR_INVALID_PARAMETER;
882 }
883
884 if (RT_SUCCESS(rc))
885 {
886 LogFlowFunc(("Sending uMsg=%RU32, cParms=%RU32 ...\n", uMsg, pCBData->cParms));
887 rc = HGCM::Message::copyParms(pMsg->getCount(), pMsg->getParms(), pCBData->paParms);
888 if (RT_SUCCESS(rc))
889 {
890 pCBData->cParms = pMsg->getCount();
891 pThis->removeNext();
892 }
893 }
894 }
895 else
896 rc = VERR_NO_DATA;
897
898 LogFlowFunc(("Returning msg %RU32, rc=%Rrc\n", uMsg, rc));
899 break;
900 }
901 default:
902 rc = VERR_NOT_SUPPORTED;
903 break;
904 }
905
906 if (RT_FAILURE(rc))
907 {
908 if (pCtx->mURI.SemEvent != NIL_RTSEMEVENT)
909 {
910 LogFlowFunc(("Signalling ...\n"));
911 int rc2 = RTSemEventSignal(pCtx->mURI.SemEvent);
912 AssertRC(rc2);
913 }
914 }
915
916 LogFlowFuncLeaveRC(rc);
917 return rc; /* Tell the guest. */
918}
919
920int GuestDnDTarget::i_sendURIData(PSENDDATACTX pCtx)
921{
922 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
923 AssertPtr(pCtx->mpResp);
924
925#define URI_DATA_IS_VALID_BREAK(x) \
926 if (!x) \
927 { \
928 LogFlowFunc(("Invalid URI data value for \"" #x "\"\n")); \
929 rc = VERR_INVALID_PARAMETER; \
930 break; \
931 }
932
933 void *pvBuf = RTMemAlloc(m_cbBlockSize);
934 if (!pvBuf)
935 return VERR_NO_MEMORY;
936
937 int rc;
938
939#define REGISTER_CALLBACK(x) \
940 rc = pCtx->mpResp->setCallback(x, i_sendURIDataCallback, pCtx); \
941 if (RT_FAILURE(rc)) \
942 return rc;
943
944#define UNREGISTER_CALLBACK(x) \
945 rc = pCtx->mpResp->setCallback(x, NULL); \
946 AssertRC(rc);
947
948 /*
949 * Register callbacks.
950 */
951 /* Generic callbacks. */
952 REGISTER_CALLBACK(DragAndDropSvc::GUEST_DND_GET_NEXT_HOST_MSG);
953 /* Host callbacks. */
954 REGISTER_CALLBACK(DragAndDropSvc::HOST_DND_HG_SND_DIR);
955 if (mData.mProtocolVersion >= 2)
956 REGISTER_CALLBACK(DragAndDropSvc::HOST_DND_HG_SND_FILE_HDR);
957 REGISTER_CALLBACK(DragAndDropSvc::HOST_DND_HG_SND_FILE_DATA);
958
959 do
960 {
961 /*
962 * Set our scratch buffer.
963 */
964 pCtx->mURI.pvScratchBuf = pvBuf;
965 pCtx->mURI.cbScratchBuf = m_cbBlockSize;
966
967 /* Create event semaphore. */
968 pCtx->mURI.SemEvent = NIL_RTSEMEVENT;
969 rc = RTSemEventCreate(&pCtx->mURI.SemEvent);
970 if (RT_FAILURE(rc))
971 break;
972
973 /*
974 * Extract URI list from byte data.
975 */
976 DnDURIList &lstURI = pCtx->mURI.lstURI; /* Use the URI list from the context. */
977
978 const char *pszList = (const char *)&pCtx->mData.front();
979 URI_DATA_IS_VALID_BREAK(pszList);
980
981 uint32_t cbList = pCtx->mData.size();
982 URI_DATA_IS_VALID_BREAK(cbList);
983
984 RTCList<RTCString> lstURIOrg = RTCString(pszList, cbList).split("\r\n");
985 URI_DATA_IS_VALID_BREAK(!lstURIOrg.isEmpty());
986
987 rc = lstURI.AppendURIPathsFromList(lstURIOrg, 0 /* fFlags */);
988 if (RT_SUCCESS(rc))
989 LogFlowFunc(("URI root objects: %zu, total bytes (raw data to transfer): %zu\n",
990 lstURI.RootCount(), lstURI.TotalBytes()));
991 else
992 break;
993
994 /*
995 * The first message always is the meta info for the data. The meta
996 * info *only* contains the root elements of an URI list.
997 *
998 * After the meta data we generate the messages required to send the data itself.
999 */
1000 Assert(!lstURI.IsEmpty());
1001 RTCString strData = lstURI.RootToString().c_str();
1002 size_t cbData = strData.length() + 1; /* Include terminating zero. */
1003
1004 GuestDnDMsg Msg;
1005 Msg.setType(DragAndDropSvc::HOST_DND_HG_SND_DATA);
1006 Msg.setNextUInt32(pCtx->mScreenID);
1007 Msg.setNextPointer((void *)pCtx->mFormat.c_str(), (uint32_t)pCtx->mFormat.length() + 1);
1008 Msg.setNextUInt32((uint32_t)pCtx->mFormat.length() + 1);
1009 Msg.setNextPointer((void*)strData.c_str(), (uint32_t)cbData);
1010 Msg.setNextUInt32((uint32_t)cbData);
1011
1012 rc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
1013 if (RT_SUCCESS(rc))
1014 {
1015 /*
1016 * Wait until our callback i_sendURIDataCallback triggered the
1017 * wait event.
1018 */
1019 LogFlowFunc(("Waiting for URI callback ...\n"));
1020 rc = RTSemEventWait(pCtx->mURI.SemEvent, RT_INDEFINITE_WAIT);
1021 LogFlowFunc(("URI callback done\n"));
1022 }
1023
1024 } while (0);
1025
1026 if (pCtx->mURI.SemEvent != NIL_RTSEMEVENT)
1027 {
1028 RTSemEventDestroy(pCtx->mURI.SemEvent);
1029 pCtx->mURI.SemEvent = NIL_RTSEMEVENT;
1030 }
1031
1032 /*
1033 * Unregister callbacksagain.
1034 */
1035 /* Guest callbacks. */
1036 UNREGISTER_CALLBACK(DragAndDropSvc::GUEST_DND_GET_NEXT_HOST_MSG);
1037 /* Host callbacks. */
1038 UNREGISTER_CALLBACK(DragAndDropSvc::HOST_DND_HG_SND_DIR);
1039 if (mData.mProtocolVersion >= 2)
1040 UNREGISTER_CALLBACK(DragAndDropSvc::HOST_DND_HG_SND_FILE_HDR);
1041 UNREGISTER_CALLBACK(DragAndDropSvc::HOST_DND_HG_SND_FILE_DATA);
1042
1043#undef REGISTER_CALLBACK
1044#undef UNREGISTER_CALLBACK
1045
1046 if (pvBuf)
1047 RTMemFree(pvBuf);
1048
1049#undef URI_DATA_IS_VALID_BREAK
1050
1051 LogFlowFuncLeaveRC(rc);
1052 return rc;
1053}
1054
1055int GuestDnDTarget::i_sendURIDataLoop(PSENDDATACTX pCtx, GuestDnDMsg *pMsg)
1056{
1057 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1058
1059 DnDURIList &lstURI = pCtx->mURI.lstURI;
1060
1061 int rc;
1062
1063 uint64_t cbTotal = pCtx->mURI.lstURI.TotalBytes();
1064 uint8_t uPercent = pCtx->mURI.cbProcessed * 100 / (cbTotal ? cbTotal : 1);
1065 Assert(uPercent <= 100);
1066
1067 LogFlowFunc(("%RU64 / %RU64 -- %RU8%%\n", pCtx->mURI.cbProcessed, cbTotal, uPercent));
1068
1069 bool fComplete = (uPercent >= 100) || lstURI.IsEmpty();
1070
1071 if (pCtx->mpResp)
1072 {
1073 int rc2 = pCtx->mpResp->setProgress(uPercent,
1074 fComplete
1075 ? DragAndDropSvc::DND_PROGRESS_COMPLETE
1076 : DragAndDropSvc::DND_PROGRESS_RUNNING);
1077 AssertRC(rc2);
1078 }
1079
1080 if (fComplete)
1081 {
1082 LogFlowFunc(("Last URI item processed, bailing out\n"));
1083 return VERR_NO_DATA;
1084 }
1085
1086 Assert(!lstURI.IsEmpty());
1087 DnDURIObject &curObj = lstURI.First();
1088
1089 uint32_t fMode = curObj.GetMode();
1090 LogFlowFunc(("Processing srcPath=%s, dstPath=%s, fMode=0x%x, cbSize=%RU32, fIsDir=%RTbool, fIsFile=%RTbool\n",
1091 curObj.GetSourcePath().c_str(), curObj.GetDestPath().c_str(),
1092 fMode, curObj.GetSize(),
1093 RTFS_IS_DIRECTORY(fMode), RTFS_IS_FILE(fMode)));
1094
1095 if (RTFS_IS_DIRECTORY(fMode))
1096 {
1097 rc = i_sendDirectory(pCtx, pMsg, curObj);
1098 }
1099 else if (RTFS_IS_FILE(fMode))
1100 {
1101 rc = i_sendFile(pCtx, pMsg, curObj);
1102 }
1103 else
1104 {
1105 AssertMsgFailed(("fMode=0x%x is not supported for srcPath=%s, dstPath=%s\n",
1106 fMode, curObj.GetSourcePath().c_str(), curObj.GetDestPath().c_str()));
1107 rc = VERR_NOT_SUPPORTED;
1108 }
1109
1110 bool fRemove = false; /* Remove current entry? */
1111 if ( curObj.IsComplete()
1112 || RT_FAILURE(rc))
1113 {
1114 fRemove = true;
1115 }
1116
1117 if (fRemove)
1118 {
1119 LogFlowFunc(("Removing \"%s\" from list, rc=%Rrc\n", curObj.GetSourcePath().c_str(), rc));
1120 lstURI.RemoveFirst();
1121 }
1122
1123 if ( pCtx->mpResp
1124 && pCtx->mpResp->isProgressCanceled())
1125 {
1126 LogFlowFunc(("Cancelling ...\n"));
1127
1128 rc = i_cancelOperation();
1129 if (RT_SUCCESS(rc))
1130 rc = VERR_CANCELLED;
1131 }
1132
1133 LogFlowFuncLeaveRC(rc);
1134 return rc;
1135}
1136
1137HRESULT GuestDnDTarget::cancel(BOOL *aVeto)
1138{
1139#if !defined(VBOX_WITH_DRAG_AND_DROP)
1140 ReturnComNotImplemented();
1141#else /* VBOX_WITH_DRAG_AND_DROP */
1142
1143 int rc = i_cancelOperation();
1144
1145 if (aVeto)
1146 *aVeto = FALSE; /** @todo */
1147
1148 return RT_SUCCESS(rc) ? S_OK : VBOX_E_IPRT_ERROR;
1149#endif /* VBOX_WITH_DRAG_AND_DROP */
1150}
1151
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