VirtualBox

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

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

DnD: ErrorInfo handling, formatting, validation.

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