VirtualBox

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

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

DnD: Don't allow simultaneous drop operations for now.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 36.2 KB
Line 
1/* $Id: GuestDnDTargetImpl.cpp 55520 2015-04-29 12:51:40Z 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 mData.mcbBlockSize = _64K; /** @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(&mDataBase.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 GuestDnDMsg Msg;
290 Msg.setType(DragAndDropSvc::HOST_DND_HG_EVT_ENTER);
291 Msg.setNextUInt32(aScreenId);
292 Msg.setNextUInt32(aX);
293 Msg.setNextUInt32(aY);
294 Msg.setNextUInt32(uDefAction);
295 Msg.setNextUInt32(uAllowedActions);
296 Msg.setNextPointer((void*)strFormats.c_str(), strFormats.length() + 1);
297 Msg.setNextUInt32(strFormats.length() + 1);
298
299 rc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
300 if (RT_SUCCESS(rc))
301 {
302 GuestDnDResponse *pResp = GuestDnDInst()->response();
303 if (pResp && RT_SUCCESS(pResp->waitForGuestResponse()))
304 resAction = GuestDnD::toMainAction(pResp->defAction());
305 }
306 }
307
308 if (aResultAction)
309 *aResultAction = resAction;
310
311 LogFlowFunc(("hr=%Rhrc, resAction=%ld\n", hr, resAction));
312 return hr;
313#endif /* VBOX_WITH_DRAG_AND_DROP */
314}
315
316HRESULT GuestDnDTarget::move(ULONG aScreenId, ULONG aX, ULONG aY,
317 DnDAction_T aDefaultAction,
318 const std::vector<DnDAction_T> &aAllowedActions,
319 const std::vector<com::Utf8Str> &aFormats,
320 DnDAction_T *aResultAction)
321{
322#if !defined(VBOX_WITH_DRAG_AND_DROP)
323 ReturnComNotImplemented();
324#else /* VBOX_WITH_DRAG_AND_DROP */
325
326 /* Input validation. */
327
328 AutoCaller autoCaller(this);
329 if (FAILED(autoCaller.rc())) return autoCaller.rc();
330
331 /* Default action is ignoring. */
332 DnDAction_T resAction = DnDAction_Ignore;
333
334 /* Check & convert the drag & drop actions. */
335 uint32_t uDefAction = 0;
336 uint32_t uAllowedActions = 0;
337 GuestDnD::toHGCMActions(aDefaultAction, &uDefAction,
338 aAllowedActions, &uAllowedActions);
339 /* If there is no usable action, ignore this request. */
340 if (isDnDIgnoreAction(uDefAction))
341 return S_OK;
342
343 /* Make a flat data string out of the supported format list. */
344 RTCString strFormats = GuestDnD::toFormatString(m_strFormats, aFormats);
345 /* If there is no valid supported format, ignore this request. */
346 if (strFormats.isEmpty())
347 return S_OK;
348
349 HRESULT hr = S_OK;
350
351 int rc = GuestDnDInst()->adjustScreenCoordinates(aScreenId, &aX, &aY);
352 if (RT_SUCCESS(rc))
353 {
354 GuestDnDMsg Msg;
355 Msg.setType(DragAndDropSvc::HOST_DND_HG_EVT_MOVE);
356 Msg.setNextUInt32(aScreenId);
357 Msg.setNextUInt32(aX);
358 Msg.setNextUInt32(aY);
359 Msg.setNextUInt32(uDefAction);
360 Msg.setNextUInt32(uAllowedActions);
361 Msg.setNextPointer((void*)strFormats.c_str(), strFormats.length() + 1);
362 Msg.setNextUInt32(strFormats.length() + 1);
363
364 rc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
365 if (RT_SUCCESS(rc))
366 {
367 GuestDnDResponse *pResp = GuestDnDInst()->response();
368 if (pResp && RT_SUCCESS(pResp->waitForGuestResponse()))
369 resAction = GuestDnD::toMainAction(pResp->defAction());
370 }
371 }
372
373 if (aResultAction)
374 *aResultAction = resAction;
375
376 LogFlowFunc(("hr=%Rhrc, *pResultAction=%ld\n", hr, resAction));
377 return hr;
378#endif /* VBOX_WITH_DRAG_AND_DROP */
379}
380
381HRESULT GuestDnDTarget::leave(ULONG uScreenId)
382{
383#if !defined(VBOX_WITH_DRAG_AND_DROP)
384 ReturnComNotImplemented();
385#else /* VBOX_WITH_DRAG_AND_DROP */
386
387 AutoCaller autoCaller(this);
388 if (FAILED(autoCaller.rc())) return autoCaller.rc();
389
390 HRESULT hr = S_OK;
391 int rc = GuestDnDInst()->hostCall(DragAndDropSvc::HOST_DND_HG_EVT_LEAVE,
392 0 /* cParms */, NULL /* paParms */);
393 if (RT_SUCCESS(rc))
394 {
395 GuestDnDResponse *pResp = GuestDnDInst()->response();
396 if (pResp)
397 pResp->waitForGuestResponse();
398 }
399
400 LogFlowFunc(("hr=%Rhrc\n", hr));
401 return hr;
402#endif /* VBOX_WITH_DRAG_AND_DROP */
403}
404
405HRESULT GuestDnDTarget::drop(ULONG aScreenId, ULONG aX, ULONG aY,
406 DnDAction_T aDefaultAction,
407 const std::vector<DnDAction_T> &aAllowedActions,
408 const std::vector<com::Utf8Str> &aFormats,
409 com::Utf8Str &aFormat, DnDAction_T *aResultAction)
410{
411#if !defined(VBOX_WITH_DRAG_AND_DROP)
412 ReturnComNotImplemented();
413#else /* VBOX_WITH_DRAG_AND_DROP */
414
415 /* Input validation. */
416
417 /* Everything else is optional. */
418
419 AutoCaller autoCaller(this);
420 if (FAILED(autoCaller.rc())) return autoCaller.rc();
421
422 /* Default action is ignoring. */
423 DnDAction_T resAction = DnDAction_Ignore;
424
425 /* Check & convert the drag & drop actions. */
426 uint32_t uDefAction = 0;
427 uint32_t uAllowedActions = 0;
428 GuestDnD::toHGCMActions(aDefaultAction, &uDefAction,
429 aAllowedActions, &uAllowedActions);
430 /* If there is no usable action, ignore this request. */
431 if (isDnDIgnoreAction(uDefAction))
432 return S_OK;
433
434 /* Make a flat data string out of the supported format list. */
435 Utf8Str strFormats = GuestDnD::toFormatString(m_strFormats, aFormats);
436 /* If there is no valid supported format, ignore this request. */
437 if (strFormats.isEmpty())
438 return S_OK;
439
440 HRESULT hr = S_OK;
441
442 /* Adjust the coordinates in a multi-monitor setup. */
443 int rc = GuestDnDInst()->adjustScreenCoordinates(aScreenId, &aX, &aY);
444 if (RT_SUCCESS(rc))
445 {
446 GuestDnDMsg Msg;
447 Msg.setType(DragAndDropSvc::HOST_DND_HG_EVT_DROPPED);
448 Msg.setNextUInt32(aScreenId);
449 Msg.setNextUInt32(aX);
450 Msg.setNextUInt32(aY);
451 Msg.setNextUInt32(uDefAction);
452 Msg.setNextUInt32(uAllowedActions);
453 Msg.setNextPointer((void*)strFormats.c_str(), strFormats.length() + 1);
454 Msg.setNextUInt32(strFormats.length() + 1);
455
456 rc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
457 if (RT_SUCCESS(rc))
458 {
459 GuestDnDResponse *pResp = GuestDnDInst()->response();
460 if (pResp && RT_SUCCESS(pResp->waitForGuestResponse()))
461 {
462 resAction = GuestDnD::toMainAction(pResp->defAction());
463 aFormat = pResp->format();
464
465 LogFlowFunc(("resFormat=%s, resAction=%RU32\n",
466 pResp->format().c_str(), pResp->defAction()));
467 }
468 }
469 }
470
471 if (aResultAction)
472 *aResultAction = resAction;
473
474 return hr;
475#endif /* VBOX_WITH_DRAG_AND_DROP */
476}
477
478/* static */
479DECLCALLBACK(int) GuestDnDTarget::i_sendDataThread(RTTHREAD Thread, void *pvUser)
480{
481 LogFlowFunc(("pvUser=%p\n", pvUser));
482
483 SendDataTask *pTask = (SendDataTask *)pvUser;
484 AssertPtrReturn(pTask, VERR_INVALID_POINTER);
485
486 const ComObjPtr<GuestDnDTarget> pTarget(pTask->getTarget());
487 Assert(!pTarget.isNull());
488
489 int rc;
490
491 AutoCaller autoCaller(pTarget);
492 if (SUCCEEDED(autoCaller.rc()))
493 {
494 rc = pTarget->i_sendData(pTask->getCtx());
495 /* Nothing to do here anymore. */
496 }
497 else
498 rc = VERR_COM_INVALID_OBJECT_STATE;
499
500 LogFlowFunc(("pTarget=%p returning rc=%Rrc\n", (GuestDnDTarget *)pTarget, rc));
501
502 if (pTask)
503 delete pTask;
504 return rc;
505}
506
507/**
508 * Initiates a data transfer from the host to the guest. The source is the host whereas the target is the
509 * guest in this case.
510 *
511 * @return HRESULT
512 * @param aScreenId
513 * @param aFormat
514 * @param aData
515 * @param aProgress
516 */
517HRESULT GuestDnDTarget::sendData(ULONG aScreenId, const com::Utf8Str &aFormat, const std::vector<BYTE> &aData,
518 ComPtr<IProgress> &aProgress)
519{
520#if !defined(VBOX_WITH_DRAG_AND_DROP)
521 ReturnComNotImplemented();
522#else /* VBOX_WITH_DRAG_AND_DROP */
523
524 /** @todo Add input validation. */
525 /** @todo Check if another sendData() call currently is being processed. */
526
527 AutoCaller autoCaller(this);
528 if (FAILED(autoCaller.rc())) return autoCaller.rc();
529
530 HRESULT hr = S_OK;
531 int vrc;
532
533 /* Note: At the moment we only support one response at a time. */
534 GuestDnDResponse *pResp = GuestDnDInst()->response();
535 if (pResp)
536 {
537 pResp->resetProgress(m_pGuest);
538
539 try
540 {
541 PSENDDATACTX pSendCtx = new SENDDATACTX;
542 RT_BZERO(pSendCtx, sizeof(SENDDATACTX));
543
544 pSendCtx->mpTarget = this;
545 pSendCtx->mpResp = pResp;
546 pSendCtx->mScreenID = aScreenId;
547 pSendCtx->mFormat = aFormat;
548 pSendCtx->mData.vecData = aData;
549
550 SendDataTask *pTask = new SendDataTask(this, pSendCtx);
551 AssertReturn(pTask->isOk(), pTask->getRC());
552
553 vrc = RTThreadCreate(NULL, GuestDnDTarget::i_sendDataThread,
554 (void *)pTask, 0, RTTHREADTYPE_MAIN_WORKER, 0, "dndTgtSndData");
555 if (RT_SUCCESS(vrc))
556 {
557 hr = pResp->queryProgressTo(aProgress.asOutParam());
558 ComAssertComRC(hr);
559
560 /* Note: pTask is now owned by the worker thread. */
561 }
562 else if (pSendCtx)
563 delete pSendCtx;
564 }
565 catch(std::bad_alloc &)
566 {
567 vrc = VERR_NO_MEMORY;
568 }
569
570 /*if (RT_FAILURE(vrc)) ** @todo SetError(...) */
571 }
572 /** @todo SetError(...) */
573
574 return hr;
575#endif /* VBOX_WITH_DRAG_AND_DROP */
576}
577
578int GuestDnDTarget::i_cancelOperation(void)
579{
580 /** @todo Check for pending cancel requests. */
581
582#if 0 /** @todo Later. */
583 /* Cancel any outstanding waits for guest responses first. */
584 if (pResp)
585 pResp->notifyAboutGuestResponse();
586#endif
587
588 LogFlowFunc(("Cancelling operation, telling guest ...\n"));
589 return GuestDnDInst()->hostCall(DragAndDropSvc::HOST_DND_HG_EVT_CANCEL, 0 /* cParms */, NULL /*paParms*/);
590}
591
592int GuestDnDTarget::i_sendData(PSENDDATACTX pCtx)
593{
594 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
595
596#define DATA_IS_VALID_BREAK(x) \
597 if (!x) \
598 { \
599 LogFlowFunc(("Invalid URI data value for \"" #x "\"\n")); \
600 rc = VERR_INVALID_PARAMETER; \
601 break; \
602 }
603
604 GuestDnD *pInst = GuestDnDInst();
605 if (!pInst)
606 return VERR_INVALID_POINTER;
607
608 int rc;
609
610 ASMAtomicWriteBool(&pCtx->mIsActive, true);
611
612 do
613 {
614 const char *pszFormat = pCtx->mFormat.c_str();
615 DATA_IS_VALID_BREAK(pszFormat);
616 uint32_t cbFormat = pCtx->mFormat.length() + 1;
617
618 /* Do we need to build up a file tree? */
619 bool fHasURIList = DnDMIMEHasFileURLs(pszFormat, cbFormat);
620 if (fHasURIList)
621 {
622 rc = i_sendURIData(pCtx);
623 }
624 else
625 {
626 GuestDnDMsg Msg;
627
628 size_t cbDataTotal = pCtx->mData.vecData.size();
629 DATA_IS_VALID_BREAK(cbDataTotal);
630
631 /* Just copy over the raw data. */
632 Msg.setType(DragAndDropSvc::HOST_DND_HG_SND_DATA);
633 Msg.setNextUInt32(pCtx->mScreenID);
634 Msg.setNextPointer((void *)pCtx->mFormat.c_str(), (uint32_t)pCtx->mFormat.length() + 1);
635 Msg.setNextUInt32((uint32_t)pCtx->mFormat.length() + 1);
636 Msg.setNextPointer((void*)&pCtx->mData.vecData.front(), (uint32_t)cbDataTotal);
637 Msg.setNextUInt32(cbDataTotal);
638
639 LogFlowFunc(("%zu total bytes of raw data to transfer\n", cbDataTotal));
640
641 /* Make the initial call to the guest by sending the actual data. This might
642 * be an URI list which in turn can lead to more data to send afterwards. */
643 rc = pInst->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
644 if (RT_FAILURE(rc))
645 break;
646 }
647
648 } while (0);
649
650 ASMAtomicWriteBool(&pCtx->mIsActive, false);
651
652#undef DATA_IS_VALID_BREAK
653
654 LogFlowFuncLeaveRC(rc);
655 return rc;
656}
657
658int GuestDnDTarget::i_sendDirectory(PSENDDATACTX pCtx, GuestDnDMsg *pMsg, DnDURIObject &aDirectory)
659{
660 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
661
662 RTCString strPath = aDirectory.GetDestPath();
663 if (strPath.isEmpty())
664 return VERR_INVALID_PARAMETER;
665 if (strPath.length() >= RTPATH_MAX) /* Note: Maximum is RTPATH_MAX on guest side. */
666 return VERR_BUFFER_OVERFLOW;
667
668 LogFlowFunc(("Sending directory \"%s\" using protocol v%RU32 ...\n", strPath.c_str(), mDataBase.mProtocolVersion));
669
670 pMsg->setType(DragAndDropSvc::HOST_DND_HG_SND_DIR);
671 pMsg->setNextString(strPath.c_str()); /* path */
672 pMsg->setNextUInt32((uint32_t)(strPath.length() + 1)); /* path length - note: Maximum is RTPATH_MAX on guest side. */
673 pMsg->setNextUInt32(aDirectory.GetMode()); /* mode */
674
675 return VINF_SUCCESS;
676}
677
678int GuestDnDTarget::i_sendFile(PSENDDATACTX pCtx, GuestDnDMsg *pMsg, DnDURIObject &aFile)
679{
680 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
681
682 RTCString strPathSrc = aFile.GetSourcePath();
683 if (strPathSrc.isEmpty())
684 return VERR_INVALID_PARAMETER;
685
686 int rc = VINF_SUCCESS;
687
688 LogFlowFunc(("Sending \"%s\" (%RU32 bytes buffer) using protocol v%RU32 ...\n",
689 strPathSrc.c_str(), mData.mcbBlockSize, mDataBase.mProtocolVersion));
690
691 bool fOpen = aFile.IsOpen();
692 if (!fOpen)
693 {
694 LogFlowFunc(("Opening \"%s\" ...\n", strPathSrc.c_str()));
695 rc = aFile.OpenEx(strPathSrc, DnDURIObject::File, DnDURIObject::Source,
696 RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE, 0 /* fFlags */);
697 }
698
699 bool fSendFileData = false;
700 if (RT_SUCCESS(rc))
701 {
702 if (mDataBase.mProtocolVersion >= 2)
703 {
704 if (!fOpen)
705 {
706 /*
707 * Since protocol v2 the file header and the actual file contents are
708 * separate messages, so send the file header first.
709 * The just registered callback will be called by the guest afterwards.
710 */
711 pMsg->setType(DragAndDropSvc::HOST_DND_HG_SND_FILE_HDR);
712 pMsg->setNextUInt32(0); /* context ID */
713 rc = pMsg->setNextString(aFile.GetDestPath().c_str()); /* pvName */
714 AssertRC(rc);
715 pMsg->setNextUInt32((uint32_t)(aFile.GetDestPath().length() + 1)); /* cbName */
716 pMsg->setNextUInt32(0); /* uFlags */
717 pMsg->setNextUInt32(aFile.GetMode()); /* fMode */
718 pMsg->setNextUInt64(aFile.GetSize()); /* uSize */
719
720 LogFlowFunc(("Sending file header ...\n"));
721 }
722 else
723 {
724 /* File header was sent, so only send the actual file data. */
725 fSendFileData = true;
726 }
727 }
728 else /* Protocol v1. */
729 {
730 /* Always send the file data, every time. */
731 fSendFileData = true;
732 }
733 }
734
735 if ( RT_SUCCESS(rc)
736 && fSendFileData)
737 {
738 rc = i_sendFileData(pCtx, pMsg, aFile);
739 }
740
741 LogFlowFuncLeaveRC(rc);
742 return rc;
743}
744
745int GuestDnDTarget::i_sendFileData(PSENDDATACTX pCtx, GuestDnDMsg *pMsg, DnDURIObject &aFile)
746{
747 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
748 AssertPtrReturn(pMsg, VERR_INVALID_POINTER);
749
750 GuestDnDResponse *pResp = pCtx->mpResp;
751 AssertPtr(pResp);
752
753 /** @todo Don't allow concurrent reads per context! */
754
755 /* Something to transfer? */
756 if ( pCtx->mURI.lstURI.IsEmpty()
757 || !pCtx->mIsActive)
758 {
759 return VERR_WRONG_ORDER;
760 }
761
762 /*
763 * Start sending stuff.
764 */
765
766 /* Set the message type. */
767 pMsg->setType(DragAndDropSvc::HOST_DND_HG_SND_FILE_DATA);
768
769 /* Protocol version 1 sends the file path *every* time with a new file chunk.
770 * In protocol version 2 we only do this once with HOST_DND_HG_SND_FILE_HDR. */
771 if (mDataBase.mProtocolVersion <= 1)
772 {
773 pMsg->setNextString(aFile.GetSourcePath().c_str()); /* pvName */
774 pMsg->setNextUInt32((uint32_t)(aFile.GetSourcePath().length() + 1)); /* cbName */
775 }
776 else
777 {
778 /* Protocol version 2 also sends the context ID. Currently unused. */
779 pMsg->setNextUInt32(0); /* context ID */
780 }
781
782 uint32_t cbRead = 0;
783
784 int rc = aFile.Read(pCtx->mURI.pvScratchBuf, pCtx->mURI.cbScratchBuf, &cbRead);
785 if (RT_SUCCESS(rc))
786 {
787 pCtx->mData.cbProcessed += cbRead;
788
789 if (mDataBase.mProtocolVersion <= 1)
790 {
791 pMsg->setNextPointer(pCtx->mURI.pvScratchBuf, cbRead); /* pvData */
792 pMsg->setNextUInt32(cbRead); /* cbData */
793 pMsg->setNextUInt32(aFile.GetMode()); /* fMode */
794 }
795 else
796 {
797 pMsg->setNextPointer(pCtx->mURI.pvScratchBuf, cbRead); /* pvData */
798 pMsg->setNextUInt32(cbRead); /* cbData */
799 }
800
801 if (aFile.IsComplete()) /* Done reading? */
802 {
803 LogFlowFunc(("File \"%s\" complete\n", aFile.GetSourcePath().c_str()));
804 rc = VINF_EOF;
805 }
806 }
807
808 LogFlowFuncLeaveRC(rc);
809 return rc;
810}
811
812/* static */
813DECLCALLBACK(int) GuestDnDTarget::i_sendURIDataCallback(uint32_t uMsg, void *pvParms, size_t cbParms, void *pvUser)
814{
815 PSENDDATACTX pCtx = (PSENDDATACTX)pvUser;
816 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
817
818 GuestDnDTarget *pThis = pCtx->mpTarget;
819 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
820
821 LogFlowFunc(("pThis=%p, uMsg=%RU32\n", pThis, uMsg));
822
823 int rc = VINF_SUCCESS;
824 bool fNotify = false;
825
826 switch (uMsg)
827 {
828 case DragAndDropSvc::GUEST_DND_GET_NEXT_HOST_MSG:
829 {
830 DragAndDropSvc::PVBOXDNDCBHGGETNEXTHOSTMSG pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBHGGETNEXTHOSTMSG>(pvParms);
831 AssertPtr(pCBData);
832 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBHGGETNEXTHOSTMSG) == cbParms, VERR_INVALID_PARAMETER);
833 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_HG_GET_NEXT_HOST_MSG == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
834
835 GuestDnDMsg *pMsg;
836 try
837 {
838 pMsg = new GuestDnDMsg();
839 rc = pThis->i_sendURIDataLoop(pCtx, pMsg);
840 if (RT_SUCCESS(rc))
841 {
842 rc = pThis->addMsg(pMsg);
843 if (RT_SUCCESS(rc)) /* Return message type & required parameter count to the guest. */
844 {
845 LogFlowFunc(("GUEST_DND_GET_NEXT_HOST_MSG -> %RU32 (%RU32 params)\n", pMsg->getType(), pMsg->getCount()));
846 pCBData->uMsg = pMsg->getType();
847 pCBData->cParms = pMsg->getCount();
848 }
849 }
850
851 if (RT_FAILURE(rc))
852 {
853 if (rc == VERR_NO_DATA) /* All URI objects processed? */
854 {
855 /* Unregister this callback. */
856 AssertPtr(pCtx->mpResp);
857 int rc2 = pCtx->mpResp->setCallback(uMsg, NULL /* PFNGUESTDNDCALLBACK */);
858 if (RT_FAILURE(rc2))
859 LogFlowFunc(("Error: Unable to unregister callback for message %RU32, rc=%Rrc\n", uMsg, rc2));
860 }
861
862 delete pMsg;
863 }
864 }
865 catch(std::bad_alloc & /*e*/)
866 {
867 rc = VERR_NO_MEMORY;
868 }
869 break;
870 }
871 case DragAndDropSvc::HOST_DND_HG_SND_DIR:
872 case DragAndDropSvc::HOST_DND_HG_SND_FILE_HDR:
873 case DragAndDropSvc::HOST_DND_HG_SND_FILE_DATA:
874 {
875 DragAndDropSvc::PVBOXDNDCBHGGETNEXTHOSTMSGDATA pCBData
876 = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBHGGETNEXTHOSTMSGDATA>(pvParms);
877 AssertPtr(pCBData);
878 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBHGGETNEXTHOSTMSGDATA) == cbParms, VERR_INVALID_PARAMETER);
879 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_HG_GET_NEXT_HOST_MSG_DATA == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
880
881 LogFlowFunc(("pCBData->uMsg=%RU32, paParms=%p, cParms=%RU32\n", pCBData->uMsg, pCBData->paParms, pCBData->cParms));
882
883 GuestDnDMsg *pMsg = pThis->nextMsg();
884 if (pMsg)
885 {
886 /*
887 * Sanity checks.
888 */
889 if ( pCBData->uMsg != uMsg
890 || pCBData->paParms == NULL
891 || pCBData->cParms != pMsg->getCount())
892 {
893 rc = VERR_INVALID_PARAMETER;
894 }
895
896 if (RT_SUCCESS(rc))
897 {
898 LogFlowFunc(("Returning uMsg=%RU32\n", uMsg));
899 rc = HGCM::Message::copyParms(pMsg->getCount(), pMsg->getParms(), pCBData->paParms);
900 if (RT_SUCCESS(rc))
901 {
902 pCBData->cParms = pMsg->getCount();
903 pThis->removeNext();
904 }
905 else
906 LogFlowFunc(("Copying parameters failed with rc=%Rrc\n", rc));
907 }
908 }
909 else
910 rc = VERR_NO_DATA;
911
912 LogFlowFunc(("Processing next message ended with rc=%Rrc\n", rc));
913 break;
914 }
915 default:
916 rc = VERR_NOT_SUPPORTED;
917 break;
918 }
919
920 if (fNotify)
921 {
922 int rc2 = pCtx->mCallback.Notify(rc);
923 AssertRC(rc2);
924 }
925
926 LogFlowFuncLeaveRC(rc);
927 return rc; /* Tell the guest. */
928}
929
930int GuestDnDTarget::i_sendURIData(PSENDDATACTX pCtx)
931{
932 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
933 AssertPtr(pCtx->mpResp);
934
935#define URI_DATA_IS_VALID_BREAK(x) \
936 if (!x) \
937 { \
938 LogFlowFunc(("Invalid URI data value for \"" #x "\"\n")); \
939 rc = VERR_INVALID_PARAMETER; \
940 break; \
941 }
942
943 void *pvBuf = RTMemAlloc(mData.mcbBlockSize);
944 if (!pvBuf)
945 return VERR_NO_MEMORY;
946
947 int rc;
948
949#define REGISTER_CALLBACK(x) \
950 rc = pCtx->mpResp->setCallback(x, i_sendURIDataCallback, pCtx); \
951 if (RT_FAILURE(rc)) \
952 return rc;
953
954 rc = pCtx->mCallback.Reset();
955 if (RT_FAILURE(rc))
956 return rc;
957
958#define UNREGISTER_CALLBACK(x) \
959 rc = pCtx->mpResp->setCallback(x, NULL); \
960 AssertRC(rc);
961
962 /*
963 * Register callbacks.
964 */
965 /* Generic callbacks. */
966 REGISTER_CALLBACK(DragAndDropSvc::GUEST_DND_GET_NEXT_HOST_MSG);
967 REGISTER_CALLBACK(DragAndDropSvc::GUEST_DND_GH_EVT_ERROR);
968 /* Host callbacks. */
969 REGISTER_CALLBACK(DragAndDropSvc::HOST_DND_HG_SND_DIR);
970 if (mDataBase.mProtocolVersion >= 2)
971 REGISTER_CALLBACK(DragAndDropSvc::HOST_DND_HG_SND_FILE_HDR);
972 REGISTER_CALLBACK(DragAndDropSvc::HOST_DND_HG_SND_FILE_DATA);
973
974 do
975 {
976 /*
977 * Set our scratch buffer.
978 */
979 pCtx->mURI.pvScratchBuf = pvBuf;
980 pCtx->mURI.cbScratchBuf = mData.mcbBlockSize;
981
982 /*
983 * Extract URI list from byte data.
984 */
985 DnDURIList &lstURI = pCtx->mURI.lstURI; /* Use the URI list from the context. */
986
987 const char *pszList = (const char *)&pCtx->mData.vecData.front();
988 URI_DATA_IS_VALID_BREAK(pszList);
989
990 uint32_t cbList = pCtx->mData.vecData.size();
991 URI_DATA_IS_VALID_BREAK(cbList);
992
993 RTCList<RTCString> lstURIOrg = RTCString(pszList, cbList).split("\r\n");
994 URI_DATA_IS_VALID_BREAK(!lstURIOrg.isEmpty());
995
996 rc = lstURI.AppendURIPathsFromList(lstURIOrg, 0 /* fFlags */);
997 if (RT_SUCCESS(rc))
998 LogFlowFunc(("URI root objects: %zu, total bytes (raw data to transfer): %zu\n",
999 lstURI.RootCount(), lstURI.TotalBytes()));
1000 else
1001 break;
1002
1003 pCtx->mData.cbProcessed = 0;
1004 pCtx->mData.cbToProcess = lstURI.TotalBytes();
1005
1006 /*
1007 * The first message always is the meta info for the data. The meta
1008 * info *only* contains the root elements of an URI list.
1009 *
1010 * After the meta data we generate the messages required to send the data itself.
1011 */
1012 Assert(!lstURI.IsEmpty());
1013 RTCString strData = lstURI.RootToString().c_str();
1014 size_t cbData = strData.length() + 1; /* Include terminating zero. */
1015
1016 GuestDnDMsg Msg;
1017 Msg.setType(DragAndDropSvc::HOST_DND_HG_SND_DATA);
1018 Msg.setNextUInt32(pCtx->mScreenID);
1019 Msg.setNextPointer((void *)pCtx->mFormat.c_str(), (uint32_t)pCtx->mFormat.length() + 1);
1020 Msg.setNextUInt32((uint32_t)pCtx->mFormat.length() + 1);
1021 Msg.setNextPointer((void*)strData.c_str(), (uint32_t)cbData);
1022 Msg.setNextUInt32((uint32_t)cbData);
1023
1024 rc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
1025 if (RT_SUCCESS(rc))
1026 {
1027 /*
1028 * Wait until our callback i_sendURIDataCallback triggered the
1029 * wait event.
1030 */
1031 LogFlowFunc(("Waiting for URI callback ...\n"));
1032 rc = pCtx->mCallback.Wait(RT_INDEFINITE_WAIT);
1033 LogFlowFunc(("URI callback done, rc=%Rrc\n", rc));
1034 }
1035
1036 } while (0);
1037
1038 /*
1039 * Unregister callbacksagain.
1040 */
1041 /* Guest callbacks. */
1042 UNREGISTER_CALLBACK(DragAndDropSvc::GUEST_DND_GET_NEXT_HOST_MSG);
1043 UNREGISTER_CALLBACK(DragAndDropSvc::GUEST_DND_GH_EVT_ERROR);
1044 /* Host callbacks. */
1045 UNREGISTER_CALLBACK(DragAndDropSvc::HOST_DND_HG_SND_DIR);
1046 if (mDataBase.mProtocolVersion >= 2)
1047 UNREGISTER_CALLBACK(DragAndDropSvc::HOST_DND_HG_SND_FILE_HDR);
1048 UNREGISTER_CALLBACK(DragAndDropSvc::HOST_DND_HG_SND_FILE_DATA);
1049
1050#undef REGISTER_CALLBACK
1051#undef UNREGISTER_CALLBACK
1052
1053 /* Destroy temporary scratch buffer. */
1054 if (pvBuf)
1055 RTMemFree(pvBuf);
1056
1057#undef URI_DATA_IS_VALID_BREAK
1058
1059 LogFlowFuncLeaveRC(rc);
1060 return rc;
1061}
1062
1063int GuestDnDTarget::i_sendURIDataLoop(PSENDDATACTX pCtx, GuestDnDMsg *pMsg)
1064{
1065 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1066
1067 DnDURIList &lstURI = pCtx->mURI.lstURI;
1068
1069 int rc;
1070
1071 uint64_t cbTotal = pCtx->mData.cbToProcess;
1072 uint8_t uPercent = pCtx->mData.cbProcessed * 100 / (cbTotal ? cbTotal : 1);
1073
1074 LogFlowFunc(("%RU64 / %RU64 -- %RU8%%\n", pCtx->mData.cbProcessed, cbTotal, uPercent));
1075
1076 bool fComplete = (uPercent >= 100) || lstURI.IsEmpty();
1077
1078 if (pCtx->mpResp)
1079 {
1080 int rc2 = pCtx->mpResp->setProgress(uPercent,
1081 fComplete
1082 ? DragAndDropSvc::DND_PROGRESS_COMPLETE
1083 : DragAndDropSvc::DND_PROGRESS_RUNNING);
1084 AssertRC(rc2);
1085 }
1086
1087 if (fComplete)
1088 {
1089 LogFlowFunc(("Last URI item processed, bailing out\n"));
1090 return VERR_NO_DATA;
1091 }
1092
1093 Assert(!lstURI.IsEmpty());
1094 DnDURIObject &curObj = lstURI.First();
1095
1096 uint32_t fMode = curObj.GetMode();
1097 LogFlowFunc(("Processing srcPath=%s, dstPath=%s, fMode=0x%x, cbSize=%RU32, fIsDir=%RTbool, fIsFile=%RTbool\n",
1098 curObj.GetSourcePath().c_str(), curObj.GetDestPath().c_str(),
1099 fMode, curObj.GetSize(),
1100 RTFS_IS_DIRECTORY(fMode), RTFS_IS_FILE(fMode)));
1101
1102 if (RTFS_IS_DIRECTORY(fMode))
1103 {
1104 rc = i_sendDirectory(pCtx, pMsg, curObj);
1105 }
1106 else if (RTFS_IS_FILE(fMode))
1107 {
1108 rc = i_sendFile(pCtx, pMsg, curObj);
1109 }
1110 else
1111 {
1112 AssertMsgFailed(("fMode=0x%x is not supported for srcPath=%s, dstPath=%s\n",
1113 fMode, curObj.GetSourcePath().c_str(), curObj.GetDestPath().c_str()));
1114 rc = VERR_NOT_SUPPORTED;
1115 }
1116
1117 bool fRemove = false; /* Remove current entry? */
1118 if ( curObj.IsComplete()
1119 || RT_FAILURE(rc))
1120 {
1121 fRemove = true;
1122 }
1123
1124 if (fRemove)
1125 {
1126 LogFlowFunc(("Removing \"%s\" from list, rc=%Rrc\n", curObj.GetSourcePath().c_str(), rc));
1127 lstURI.RemoveFirst();
1128 }
1129
1130 if ( pCtx->mpResp
1131 && pCtx->mpResp->isProgressCanceled())
1132 {
1133 LogFlowFunc(("Cancelling ...\n"));
1134
1135 rc = i_cancelOperation();
1136 if (RT_SUCCESS(rc))
1137 rc = VERR_CANCELLED;
1138 }
1139
1140 LogFlowFuncLeaveRC(rc);
1141 return rc;
1142}
1143
1144HRESULT GuestDnDTarget::cancel(BOOL *aVeto)
1145{
1146#if !defined(VBOX_WITH_DRAG_AND_DROP)
1147 ReturnComNotImplemented();
1148#else /* VBOX_WITH_DRAG_AND_DROP */
1149
1150 int rc = i_cancelOperation();
1151
1152 if (aVeto)
1153 *aVeto = FALSE; /** @todo */
1154
1155 return RT_SUCCESS(rc) ? S_OK : VBOX_E_IPRT_ERROR;
1156#endif /* VBOX_WITH_DRAG_AND_DROP */
1157}
1158
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