VirtualBox

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

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

DnD: Bugfixes.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 51.2 KB
Line 
1/* $Id: GuestDnDTargetImpl.cpp 58230 2015-10-14 11:31:33Z vboxsync $ */
2/** @file
3 * VBox Console COM Class implementation - Guest drag'n drop target.
4 */
5
6/*
7 * Copyright (C) 2014-2015 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18
19/*********************************************************************************************************************************
20* Header Files *
21*********************************************************************************************************************************/
22#include "GuestImpl.h"
23#include "GuestDnDTargetImpl.h"
24#include "ConsoleImpl.h"
25
26#include "Global.h"
27#include "AutoCaller.h"
28
29#include <algorithm> /* For std::find(). */
30
31#include <iprt/asm.h>
32#include <iprt/file.h>
33#include <iprt/dir.h>
34#include <iprt/path.h>
35#include <iprt/uri.h>
36#include <iprt/cpp/utils.h> /* For unconst(). */
37
38#include <VBox/com/array.h>
39
40#include <VBox/GuestHost/DragAndDrop.h>
41#include <VBox/HostServices/Service.h>
42
43#ifdef LOG_GROUP
44 #undef LOG_GROUP
45#endif
46#define LOG_GROUP LOG_GROUP_GUEST_DND
47#include <VBox/log.h>
48
49
50/**
51 * Base class for a target task.
52 */
53class GuestDnDTargetTask
54{
55public:
56
57 GuestDnDTargetTask(GuestDnDTarget *pTarget)
58 : mTarget(pTarget),
59 mRC(VINF_SUCCESS) { }
60
61 virtual ~GuestDnDTargetTask(void) { }
62
63 int getRC(void) const { return mRC; }
64 bool isOk(void) const { return RT_SUCCESS(mRC); }
65 const ComObjPtr<GuestDnDTarget> &getTarget(void) const { return mTarget; }
66
67protected:
68
69 const ComObjPtr<GuestDnDTarget> mTarget;
70 int mRC;
71};
72
73/**
74 * Task structure for sending data to a target using
75 * a worker thread.
76 */
77class SendDataTask : public GuestDnDTargetTask
78{
79public:
80
81 SendDataTask(GuestDnDTarget *pTarget, PSENDDATACTX pCtx)
82 : GuestDnDTargetTask(pTarget),
83 mpCtx(pCtx) { }
84
85 virtual ~SendDataTask(void)
86 {
87 if (mpCtx)
88 {
89 delete mpCtx;
90 mpCtx = NULL;
91 }
92 }
93
94
95 PSENDDATACTX getCtx(void) { return mpCtx; }
96
97protected:
98
99 /** Pointer to send data context. */
100 PSENDDATACTX mpCtx;
101};
102
103// constructor / destructor
104/////////////////////////////////////////////////////////////////////////////
105
106DEFINE_EMPTY_CTOR_DTOR(GuestDnDTarget)
107
108HRESULT GuestDnDTarget::FinalConstruct(void)
109{
110 /* Set the maximum block size our guests can handle to 64K. This always has
111 * been hardcoded until now. */
112 /* Note: Never ever rely on information from the guest; the host dictates what and
113 * how to do something, so try to negogiate a sensible value here later. */
114 mData.mcbBlockSize = _64K; /** @todo Make this configurable. */
115
116 LogFlowThisFunc(("\n"));
117 return BaseFinalConstruct();
118}
119
120void GuestDnDTarget::FinalRelease(void)
121{
122 LogFlowThisFuncEnter();
123 uninit();
124 BaseFinalRelease();
125 LogFlowThisFuncLeave();
126}
127
128// public initializer/uninitializer for internal purposes only
129/////////////////////////////////////////////////////////////////////////////
130
131int GuestDnDTarget::init(const ComObjPtr<Guest>& pGuest)
132{
133 LogFlowThisFuncEnter();
134
135 /* Enclose the state transition NotReady->InInit->Ready. */
136 AutoInitSpan autoInitSpan(this);
137 AssertReturn(autoInitSpan.isOk(), E_FAIL);
138
139 unconst(m_pGuest) = pGuest;
140
141 /* Confirm a successful initialization when it's the case. */
142 autoInitSpan.setSucceeded();
143
144 return VINF_SUCCESS;
145}
146
147/**
148 * Uninitializes the instance.
149 * Called from FinalRelease().
150 */
151void GuestDnDTarget::uninit(void)
152{
153 LogFlowThisFunc(("\n"));
154
155 /* Enclose the state transition Ready->InUninit->NotReady. */
156 AutoUninitSpan autoUninitSpan(this);
157 if (autoUninitSpan.uninitDone())
158 return;
159}
160
161// implementation of wrapped IDnDBase methods.
162/////////////////////////////////////////////////////////////////////////////
163
164HRESULT GuestDnDTarget::isFormatSupported(const com::Utf8Str &aFormat, BOOL *aSupported)
165{
166#if !defined(VBOX_WITH_DRAG_AND_DROP)
167 ReturnComNotImplemented();
168#else /* VBOX_WITH_DRAG_AND_DROP */
169
170 AutoCaller autoCaller(this);
171 if (FAILED(autoCaller.rc())) return autoCaller.rc();
172
173 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
174
175 return GuestDnDBase::i_isFormatSupported(aFormat, aSupported);
176#endif /* VBOX_WITH_DRAG_AND_DROP */
177}
178
179HRESULT GuestDnDTarget::getFormats(GuestDnDMIMEList &aFormats)
180{
181#if !defined(VBOX_WITH_DRAG_AND_DROP)
182 ReturnComNotImplemented();
183#else /* VBOX_WITH_DRAG_AND_DROP */
184
185 AutoCaller autoCaller(this);
186 if (FAILED(autoCaller.rc())) return autoCaller.rc();
187
188 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
189
190 return GuestDnDBase::i_getFormats(aFormats);
191#endif /* VBOX_WITH_DRAG_AND_DROP */
192}
193
194HRESULT GuestDnDTarget::addFormats(const GuestDnDMIMEList &aFormats)
195{
196#if !defined(VBOX_WITH_DRAG_AND_DROP)
197 ReturnComNotImplemented();
198#else /* VBOX_WITH_DRAG_AND_DROP */
199
200 AutoCaller autoCaller(this);
201 if (FAILED(autoCaller.rc())) return autoCaller.rc();
202
203 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
204
205 return GuestDnDBase::i_addFormats(aFormats);
206#endif /* VBOX_WITH_DRAG_AND_DROP */
207}
208
209HRESULT GuestDnDTarget::removeFormats(const GuestDnDMIMEList &aFormats)
210{
211#if !defined(VBOX_WITH_DRAG_AND_DROP)
212 ReturnComNotImplemented();
213#else /* VBOX_WITH_DRAG_AND_DROP */
214
215 AutoCaller autoCaller(this);
216 if (FAILED(autoCaller.rc())) return autoCaller.rc();
217
218 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
219
220 return GuestDnDBase::i_removeFormats(aFormats);
221#endif /* VBOX_WITH_DRAG_AND_DROP */
222}
223
224HRESULT GuestDnDTarget::getProtocolVersion(ULONG *aProtocolVersion)
225{
226#if !defined(VBOX_WITH_DRAG_AND_DROP)
227 ReturnComNotImplemented();
228#else /* VBOX_WITH_DRAG_AND_DROP */
229
230 AutoCaller autoCaller(this);
231 if (FAILED(autoCaller.rc())) return autoCaller.rc();
232
233 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
234
235 return GuestDnDBase::i_getProtocolVersion(aProtocolVersion);
236#endif /* VBOX_WITH_DRAG_AND_DROP */
237}
238
239// implementation of wrapped IDnDTarget methods.
240/////////////////////////////////////////////////////////////////////////////
241
242HRESULT GuestDnDTarget::enter(ULONG aScreenId, ULONG aX, ULONG aY,
243 DnDAction_T aDefaultAction,
244 const std::vector<DnDAction_T> &aAllowedActions,
245 const GuestDnDMIMEList &aFormats,
246 DnDAction_T *aResultAction)
247{
248#if !defined(VBOX_WITH_DRAG_AND_DROP)
249 ReturnComNotImplemented();
250#else /* VBOX_WITH_DRAG_AND_DROP */
251
252 /* Input validation. */
253 if (aDefaultAction == DnDAction_Ignore)
254 return setError(E_INVALIDARG, tr("No default action specified"));
255 if (!aAllowedActions.size())
256 return setError(E_INVALIDARG, tr("Number of allowed actions is empty"));
257 if (!aFormats.size())
258 return setError(E_INVALIDARG, tr("Number of supported formats is empty"));
259
260 AutoCaller autoCaller(this);
261 if (FAILED(autoCaller.rc())) return autoCaller.rc();
262
263 /* Determine guest DnD protocol to use. */
264 GuestDnDBase::getProtocolVersion(&mDataBase.m_uProtocolVersion);
265
266 /* Default action is ignoring. */
267 DnDAction_T resAction = DnDAction_Ignore;
268
269 /* Check & convert the drag & drop actions */
270 uint32_t uDefAction = 0;
271 uint32_t uAllowedActions = 0;
272 GuestDnD::toHGCMActions(aDefaultAction, &uDefAction,
273 aAllowedActions, &uAllowedActions);
274 /* If there is no usable action, ignore this request. */
275 if (isDnDIgnoreAction(uDefAction))
276 return S_OK;
277
278 /*
279 * Make a flat data string out of the supported format list.
280 * In the GuestDnDTarget case the source formats are from the host,
281 * as GuestDnDTarget acts as a source for the guest.
282 */
283 Utf8Str strFormats = GuestDnD::toFormatString(GuestDnD::toFilteredFormatList(m_lstFmtSupported, aFormats));
284 if (strFormats.isEmpty())
285 return setError(E_INVALIDARG, tr("No or not supported format(s) specified"));
286
287 LogRel2(("DnD: Offered formats to guest:\n"));
288 RTCList<RTCString> lstFormats = strFormats.split("\r\n");
289 for (size_t i = 0; i < lstFormats.size(); i++)
290 LogRel2(("DnD: \t%s\n", lstFormats[i].c_str()));
291
292 /* Save the formats offered to the guest. This is needed to later
293 * decide what to do with the data when sending stuff to the guest. */
294 m_lstFmtOffered = aFormats;
295 Assert(m_lstFmtOffered.size());
296
297 HRESULT hr = S_OK;
298
299 /* Adjust the coordinates in a multi-monitor setup. */
300 int rc = GuestDnDInst()->adjustScreenCoordinates(aScreenId, &aX, &aY);
301 if (RT_SUCCESS(rc))
302 {
303 GuestDnDMsg Msg;
304 Msg.setType(HOST_DND_HG_EVT_ENTER);
305 Msg.setNextUInt32(aScreenId);
306 Msg.setNextUInt32(aX);
307 Msg.setNextUInt32(aY);
308 Msg.setNextUInt32(uDefAction);
309 Msg.setNextUInt32(uAllowedActions);
310 Msg.setNextPointer((void*)strFormats.c_str(), strFormats.length() + 1);
311 Msg.setNextUInt32(strFormats.length() + 1);
312
313 rc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
314 if (RT_SUCCESS(rc))
315 {
316 GuestDnDResponse *pResp = GuestDnDInst()->response();
317 if (pResp && RT_SUCCESS(pResp->waitForGuestResponse()))
318 resAction = GuestDnD::toMainAction(pResp->defAction());
319 }
320 }
321
322 if (RT_FAILURE(rc))
323 hr = VBOX_E_IPRT_ERROR;
324
325 if (SUCCEEDED(hr))
326 {
327 if (aResultAction)
328 *aResultAction = resAction;
329 }
330
331 LogFlowFunc(("hr=%Rhrc, resAction=%ld\n", hr, resAction));
332 return hr;
333#endif /* VBOX_WITH_DRAG_AND_DROP */
334}
335
336HRESULT GuestDnDTarget::move(ULONG aScreenId, ULONG aX, ULONG aY,
337 DnDAction_T aDefaultAction,
338 const std::vector<DnDAction_T> &aAllowedActions,
339 const GuestDnDMIMEList &aFormats,
340 DnDAction_T *aResultAction)
341{
342#if !defined(VBOX_WITH_DRAG_AND_DROP)
343 ReturnComNotImplemented();
344#else /* VBOX_WITH_DRAG_AND_DROP */
345
346 /* Input validation. */
347
348 AutoCaller autoCaller(this);
349 if (FAILED(autoCaller.rc())) return autoCaller.rc();
350
351 /* Default action is ignoring. */
352 DnDAction_T resAction = DnDAction_Ignore;
353
354 /* Check & convert the drag & drop actions. */
355 uint32_t uDefAction = 0;
356 uint32_t uAllowedActions = 0;
357 GuestDnD::toHGCMActions(aDefaultAction, &uDefAction,
358 aAllowedActions, &uAllowedActions);
359 /* If there is no usable action, ignore this request. */
360 if (isDnDIgnoreAction(uDefAction))
361 return S_OK;
362
363 /*
364 * Make a flat data string out of the supported format list.
365 * In the GuestDnDTarget case the source formats are from the host,
366 * as GuestDnDTarget acts as a source for the guest.
367 */
368 Utf8Str strFormats = GuestDnD::toFormatString(GuestDnD::toFilteredFormatList(m_lstFmtSupported, aFormats));
369 if (strFormats.isEmpty())
370 return setError(E_INVALIDARG, tr("No or not supported format(s) specified"));
371
372 HRESULT hr = S_OK;
373
374 int rc = GuestDnDInst()->adjustScreenCoordinates(aScreenId, &aX, &aY);
375 if (RT_SUCCESS(rc))
376 {
377 GuestDnDMsg Msg;
378 Msg.setType(HOST_DND_HG_EVT_MOVE);
379 Msg.setNextUInt32(aScreenId);
380 Msg.setNextUInt32(aX);
381 Msg.setNextUInt32(aY);
382 Msg.setNextUInt32(uDefAction);
383 Msg.setNextUInt32(uAllowedActions);
384 Msg.setNextPointer((void*)strFormats.c_str(), strFormats.length() + 1);
385 Msg.setNextUInt32(strFormats.length() + 1);
386
387 rc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
388 if (RT_SUCCESS(rc))
389 {
390 GuestDnDResponse *pResp = GuestDnDInst()->response();
391 if (pResp && RT_SUCCESS(pResp->waitForGuestResponse()))
392 resAction = GuestDnD::toMainAction(pResp->defAction());
393 }
394 }
395
396 if (RT_FAILURE(rc))
397 hr = VBOX_E_IPRT_ERROR;
398
399 if (SUCCEEDED(hr))
400 {
401 if (aResultAction)
402 *aResultAction = resAction;
403 }
404
405 LogFlowFunc(("hr=%Rhrc, *pResultAction=%ld\n", hr, resAction));
406 return hr;
407#endif /* VBOX_WITH_DRAG_AND_DROP */
408}
409
410HRESULT GuestDnDTarget::leave(ULONG uScreenId)
411{
412#if !defined(VBOX_WITH_DRAG_AND_DROP)
413 ReturnComNotImplemented();
414#else /* VBOX_WITH_DRAG_AND_DROP */
415
416 AutoCaller autoCaller(this);
417 if (FAILED(autoCaller.rc())) return autoCaller.rc();
418
419 HRESULT hr = S_OK;
420 int rc = GuestDnDInst()->hostCall(HOST_DND_HG_EVT_LEAVE,
421 0 /* cParms */, NULL /* paParms */);
422 if (RT_SUCCESS(rc))
423 {
424 GuestDnDResponse *pResp = GuestDnDInst()->response();
425 if (pResp)
426 pResp->waitForGuestResponse();
427 }
428
429 if (RT_FAILURE(rc))
430 hr = VBOX_E_IPRT_ERROR;
431
432 LogFlowFunc(("hr=%Rhrc\n", hr));
433 return hr;
434#endif /* VBOX_WITH_DRAG_AND_DROP */
435}
436
437HRESULT GuestDnDTarget::drop(ULONG aScreenId, ULONG aX, ULONG aY,
438 DnDAction_T aDefaultAction,
439 const std::vector<DnDAction_T> &aAllowedActions,
440 const GuestDnDMIMEList &aFormats,
441 com::Utf8Str &aFormat,
442 DnDAction_T *aResultAction)
443{
444#if !defined(VBOX_WITH_DRAG_AND_DROP)
445 ReturnComNotImplemented();
446#else /* VBOX_WITH_DRAG_AND_DROP */
447
448 if (aDefaultAction == DnDAction_Ignore)
449 return setError(E_INVALIDARG, tr("Invalid default action specified"));
450 if (!aAllowedActions.size())
451 return setError(E_INVALIDARG, tr("Invalid allowed actions specified"));
452 if (!aFormats.size())
453 return setError(E_INVALIDARG, tr("No drop format(s) specified"));
454 /* aResultAction is optional. */
455
456 AutoCaller autoCaller(this);
457 if (FAILED(autoCaller.rc())) return autoCaller.rc();
458
459 /* Default action is ignoring. */
460 DnDAction_T resAction = DnDAction_Ignore;
461
462 /* Check & convert the drag & drop actions to HGCM codes. */
463 uint32_t uDefAction = DND_IGNORE_ACTION;
464 uint32_t uAllowedActions = 0;
465 GuestDnD::toHGCMActions(aDefaultAction, &uDefAction,
466 aAllowedActions, &uAllowedActions);
467 /* If there is no usable action, ignore this request. */
468 if (isDnDIgnoreAction(uDefAction))
469 {
470 aFormat = "";
471 if (aResultAction)
472 *aResultAction = DnDAction_Ignore;
473 return S_OK;
474 }
475
476 /*
477 * Make a flat data string out of the supported format list.
478 * In the GuestDnDTarget case the source formats are from the host,
479 * as GuestDnDTarget acts as a source for the guest.
480 */
481 Utf8Str strFormats = GuestDnD::toFormatString(GuestDnD::toFilteredFormatList(m_lstFmtSupported, aFormats));
482 if (strFormats.isEmpty())
483 return setError(E_INVALIDARG, tr("No or not supported format(s) specified"));
484
485 /* Adjust the coordinates in a multi-monitor setup. */
486 HRESULT hr = GuestDnDInst()->adjustScreenCoordinates(aScreenId, &aX, &aY);
487 if (SUCCEEDED(hr))
488 {
489 GuestDnDMsg Msg;
490 Msg.setType(HOST_DND_HG_EVT_DROPPED);
491 Msg.setNextUInt32(aScreenId);
492 Msg.setNextUInt32(aX);
493 Msg.setNextUInt32(aY);
494 Msg.setNextUInt32(uDefAction);
495 Msg.setNextUInt32(uAllowedActions);
496 Msg.setNextPointer((void*)strFormats.c_str(), strFormats.length() + 1);
497 Msg.setNextUInt32(strFormats.length() + 1);
498
499 int rc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
500 if (RT_SUCCESS(rc))
501 {
502 GuestDnDResponse *pResp = GuestDnDInst()->response();
503 AssertPtr(pResp);
504
505 rc = pResp->waitForGuestResponse();
506 if (RT_SUCCESS(rc))
507 {
508 resAction = GuestDnD::toMainAction(pResp->defAction());
509
510 GuestDnDMIMEList lstFormats = pResp->formats();
511 if (lstFormats.size() == 1) /* Exactly one format to use specified? */
512 {
513 aFormat = lstFormats.at(0);
514 LogFlowFunc(("resFormat=%s, resAction=%RU32\n", aFormat.c_str(), pResp->defAction()));
515 }
516 else
517 hr = setError(VBOX_E_IPRT_ERROR, tr("Guest returned invalid drop formats (%zu formats)"), lstFormats.size());
518 }
519 else
520 hr = setError(VBOX_E_IPRT_ERROR, tr("Waiting for response of dropped event failed (%Rrc)"), rc);
521 }
522 else
523 hr = setError(VBOX_E_IPRT_ERROR, tr("Sending dropped event to guest failed (%Rrc)"), rc);
524 }
525 else
526 hr = setError(hr, tr("Retrieving drop coordinates failed"));
527
528 if (SUCCEEDED(hr))
529 {
530 if (aResultAction)
531 *aResultAction = resAction;
532 }
533
534 LogFlowFunc(("Returning hr=%Rhrc\n", hr));
535 return hr;
536#endif /* VBOX_WITH_DRAG_AND_DROP */
537}
538
539/* static */
540DECLCALLBACK(int) GuestDnDTarget::i_sendDataThread(RTTHREAD Thread, void *pvUser)
541{
542 LogFlowFunc(("pvUser=%p\n", pvUser));
543
544 SendDataTask *pTask = (SendDataTask *)pvUser;
545 AssertPtrReturn(pTask, VERR_INVALID_POINTER);
546
547 const ComObjPtr<GuestDnDTarget> pThis(pTask->getTarget());
548 Assert(!pThis.isNull());
549
550 AutoCaller autoCaller(pThis);
551 if (FAILED(autoCaller.rc())) return VERR_COM_INVALID_OBJECT_STATE;
552
553 int rc = RTThreadUserSignal(Thread);
554 AssertRC(rc);
555
556 rc = pThis->i_sendData(pTask->getCtx(), RT_INDEFINITE_WAIT /* msTimeout */);
557
558 if (pTask)
559 delete pTask;
560
561 AutoWriteLock alock(pThis COMMA_LOCKVAL_SRC_POS);
562
563 Assert(pThis->mDataBase.m_cTransfersPending);
564 pThis->mDataBase.m_cTransfersPending--;
565
566 LogFlowFunc(("pTarget=%p returning rc=%Rrc\n", (GuestDnDTarget *)pThis, rc));
567 return rc;
568}
569
570/**
571 * Initiates a data transfer from the host to the guest. The source is the host whereas the target is the
572 * guest in this case.
573 *
574 * @return HRESULT
575 * @param aScreenId
576 * @param aFormat
577 * @param aData
578 * @param aProgress
579 */
580HRESULT GuestDnDTarget::sendData(ULONG aScreenId, const com::Utf8Str &aFormat, const std::vector<BYTE> &aData,
581 ComPtr<IProgress> &aProgress)
582{
583#if !defined(VBOX_WITH_DRAG_AND_DROP)
584 ReturnComNotImplemented();
585#else /* VBOX_WITH_DRAG_AND_DROP */
586
587 AutoCaller autoCaller(this);
588 if (FAILED(autoCaller.rc())) return autoCaller.rc();
589
590 /* Input validation. */
591 if (RT_UNLIKELY((aFormat.c_str()) == NULL || *(aFormat.c_str()) == '\0'))
592 return setError(E_INVALIDARG, tr("No data format specified"));
593 if (RT_UNLIKELY(!aData.size()))
594 return setError(E_INVALIDARG, tr("No data to send specified"));
595
596 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
597
598 /* At the moment we only support one transfer at a time. */
599 if (mDataBase.m_cTransfersPending)
600 return setError(E_INVALIDARG, tr("Another drop operation already is in progress"));
601
602 /* Dito. */
603 GuestDnDResponse *pResp = GuestDnDInst()->response();
604 AssertPtr(pResp);
605
606 HRESULT hr = pResp->resetProgress(m_pGuest);
607 if (FAILED(hr))
608 return hr;
609
610 try
611 {
612 PSENDDATACTX pSendCtx = new SENDDATACTX;
613 RT_BZERO(pSendCtx, sizeof(SENDDATACTX));
614
615 pSendCtx->mpTarget = this;
616 pSendCtx->mpResp = pResp;
617 pSendCtx->mScreenID = aScreenId;
618 pSendCtx->mFmtReq = aFormat;
619
620 pSendCtx->mData.getMeta().add(aData);
621
622 SendDataTask *pTask = new SendDataTask(this, pSendCtx);
623 AssertReturn(pTask->isOk(), pTask->getRC());
624
625 LogFlowFunc(("Starting thread ...\n"));
626
627 RTTHREAD threadSnd;
628 int rc = RTThreadCreate(&threadSnd, GuestDnDTarget::i_sendDataThread,
629 (void *)pTask, 0, RTTHREADTYPE_MAIN_WORKER, 0, "dndTgtSndData");
630 if (RT_SUCCESS(rc))
631 {
632 rc = RTThreadUserWait(threadSnd, 30 * 1000 /* 30s timeout */);
633 if (RT_SUCCESS(rc))
634 {
635 mDataBase.m_cTransfersPending++;
636
637 hr = pResp->queryProgressTo(aProgress.asOutParam());
638 ComAssertComRC(hr);
639
640 /* Note: pTask is now owned by the worker thread. */
641 }
642 else
643 hr = setError(VBOX_E_IPRT_ERROR, tr("Waiting for sending thread failed (%Rrc)"), rc);
644 }
645 else
646 hr = setError(VBOX_E_IPRT_ERROR, tr("Starting thread failed (%Rrc)"), rc);
647
648 if (FAILED(hr))
649 delete pSendCtx;
650 }
651 catch(std::bad_alloc &)
652 {
653 hr = setError(E_OUTOFMEMORY);
654 }
655
656 LogFlowFunc(("Returning hr=%Rhrc\n", hr));
657 return hr;
658#endif /* VBOX_WITH_DRAG_AND_DROP */
659}
660
661int GuestDnDTarget::i_cancelOperation(void)
662{
663 /** @todo Check for pending cancel requests. */
664
665#if 0 /** @todo Later. */
666 /* Cancel any outstanding waits for guest responses first. */
667 if (pResp)
668 pResp->notifyAboutGuestResponse();
669#endif
670
671 LogFlowFunc(("Cancelling operation, telling guest ...\n"));
672 return GuestDnDInst()->hostCall(HOST_DND_HG_EVT_CANCEL, 0 /* cParms */, NULL /*paParms*/);
673}
674
675/* static */
676Utf8Str GuestDnDTarget::i_guestErrorToString(int guestRc)
677{
678 Utf8Str strError;
679
680 switch (guestRc)
681 {
682 case VERR_ACCESS_DENIED:
683 strError += Utf8StrFmt(tr("For one or more guest files or directories selected for transferring to the host your guest "
684 "user does not have the appropriate access rights for. Please make sure that all selected "
685 "elements can be accessed and that your guest user has the appropriate rights"));
686 break;
687
688 case VERR_NOT_FOUND:
689 /* Should not happen due to file locking on the guest, but anyway ... */
690 strError += Utf8StrFmt(tr("One or more guest files or directories selected for transferring to the host were not"
691 "found on the guest anymore. This can be the case if the guest files were moved and/or"
692 "altered while the drag and drop operation was in progress"));
693 break;
694
695 case VERR_SHARING_VIOLATION:
696 strError += Utf8StrFmt(tr("One or more guest files or directories selected for transferring to the host were locked. "
697 "Please make sure that all selected elements can be accessed and that your guest user has "
698 "the appropriate rights"));
699 break;
700
701 case VERR_TIMEOUT:
702 strError += Utf8StrFmt(tr("The guest was not able to process the drag and drop data within time"));
703 break;
704
705 default:
706 strError += Utf8StrFmt(tr("Drag and drop error from guest (%Rrc)"), guestRc);
707 break;
708 }
709
710 return strError;
711}
712
713/* static */
714Utf8Str GuestDnDTarget::i_hostErrorToString(int hostRc)
715{
716 Utf8Str strError;
717
718 switch (hostRc)
719 {
720 case VERR_ACCESS_DENIED:
721 strError += Utf8StrFmt(tr("For one or more host files or directories selected for transferring to the guest your host "
722 "user does not have the appropriate access rights for. Please make sure that all selected "
723 "elements can be accessed and that your host user has the appropriate rights."));
724 break;
725
726 case VERR_NOT_FOUND:
727 /* Should not happen due to file locking on the host, but anyway ... */
728 strError += Utf8StrFmt(tr("One or more host files or directories selected for transferring to the host were not"
729 "found on the host anymore. This can be the case if the host files were moved and/or"
730 "altered while the drag and drop operation was in progress."));
731 break;
732
733 case VERR_SHARING_VIOLATION:
734 strError += Utf8StrFmt(tr("One or more host files or directories selected for transferring to the guest were locked. "
735 "Please make sure that all selected elements can be accessed and that your host user has "
736 "the appropriate rights."));
737 break;
738
739 default:
740 strError += Utf8StrFmt(tr("Drag and drop error from host (%Rrc)"), hostRc);
741 break;
742 }
743
744 return strError;
745}
746
747int GuestDnDTarget::i_sendData(PSENDDATACTX pCtx, RTMSINTERVAL msTimeout)
748{
749 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
750
751 int rc;
752
753 ASMAtomicWriteBool(&pCtx->mIsActive, true);
754
755 /* Clear all remaining outgoing messages. */
756 mDataBase.m_lstMsgOut.clear();
757
758 /**
759 * Do we need to build up a file tree?
760 * Note: The decision whether we need to build up a file tree and sending
761 * actual file data only depends on the actual formats offered by this target.
762 * If the guest does not want an URI list ("text/uri-list") but text ("TEXT" and
763 * friends) instead, still send the data over to the guest -- the file as such still
764 * is needed on the guest in this case, as the guest then just wants a simple path
765 * instead of an URI list (pointing to a file on the guest itself).
766 *
767 ** @todo Support more than one format; add a format<->function handler concept. Later. */
768 bool fHasURIList = std::find(m_lstFmtOffered.begin(),
769 m_lstFmtOffered.end(), "text/uri-list") != m_lstFmtOffered.end();
770 if (fHasURIList)
771 {
772 rc = i_sendURIData(pCtx, msTimeout);
773 }
774 else
775 {
776 rc = i_sendRawData(pCtx, msTimeout);
777 }
778
779 ASMAtomicWriteBool(&pCtx->mIsActive, false);
780
781 LogFlowFuncLeaveRC(rc);
782 return rc;
783}
784
785int GuestDnDTarget::i_sendDataBody(PSENDDATACTX pCtx, GuestDnDData *pData)
786{
787 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
788 AssertPtrReturn(pData, VERR_INVALID_POINTER);
789
790 /** @todo Add support for multiple HOST_DND_HG_SND_DATA messages in case of more than 64K data! */
791 if (pData->getMeta().getSize() > _64K)
792 return VERR_NOT_IMPLEMENTED;
793
794 GuestDnDMsg Msg;
795
796 LogFlowFunc(("cbFmt=%RU32, cbMeta=%RU32, cbChksum=%RU32\n",
797 pData->getFmtSize(), pData->getMeta().getSize(), pData->getChkSumSize()));
798
799 Msg.setType(HOST_DND_HG_SND_DATA);
800 if (mDataBase.m_uProtocolVersion < 3)
801 {
802 Msg.setNextUInt32(pCtx->mScreenID); /* uScreenId */
803 Msg.setNextPointer(pData->getFmtMutable(), pData->getFmtSize()); /* pvFormat */
804 Msg.setNextUInt32(pData->getFmtSize()); /* cbFormat */
805 Msg.setNextPointer(pData->getMeta().getDataMutable(), pData->getMeta().getSize()); /* pvData */
806 /* Fill in the current data block size to send.
807 * Note: Only supports uint32_t. */
808 Msg.setNextUInt32((uint32_t)pData->getMeta().getSize()); /* cbData */
809 }
810 else
811 {
812 Msg.setNextUInt32(0); /** @todo uContext; not used yet. */
813 Msg.setNextPointer(pData->getMeta().getDataMutable(), pData->getMeta().getSize()); /* pvData */
814 Msg.setNextUInt32(pData->getMeta().getSize()); /* cbData */
815 Msg.setNextPointer(pData->getChkSumMutable(), pData->getChkSumSize()); /** @todo pvChecksum; not used yet. */
816 Msg.setNextUInt32(pData->getChkSumSize()); /** @todo cbChecksum; not used yet. */
817 }
818
819 int rc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
820 if (RT_SUCCESS(rc))
821 rc = updateProgress(pData, pCtx->mpResp, pData->getMeta().getSize());
822
823 LogFlowFuncLeaveRC(rc);
824 return rc;
825}
826
827int GuestDnDTarget::i_sendDataHeader(PSENDDATACTX pCtx, GuestDnDData *pData, GuestDnDURIData *pURIData /* = NULL */)
828{
829 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
830 AssertPtrReturn(pData, VERR_INVALID_POINTER);
831 /* pURIData is optional. */
832
833 GuestDnDMsg Msg;
834
835 Msg.setType(HOST_DND_HG_SND_DATA_HDR);
836
837 Msg.setNextUInt32(0); /** @todo uContext; not used yet. */
838 Msg.setNextUInt32(0); /** @todo uFlags; not used yet. */
839 Msg.setNextUInt32(pCtx->mScreenID); /* uScreen */
840 Msg.setNextUInt64(pData->getTotal()); /* cbTotal */
841 Msg.setNextUInt32(pData->getMeta().getSize()); /* cbMeta*/
842 Msg.setNextPointer(pData->getFmtMutable(), pData->getFmtSize()); /* pvMetaFmt */
843 Msg.setNextUInt32(pData->getFmtSize()); /* cbMetaFmt */
844 Msg.setNextUInt64(pURIData ? pURIData->getObjToProcess() : 0); /* cObjects */
845 Msg.setNextUInt32(0); /** @todo enmCompression; not used yet. */
846 Msg.setNextUInt32(0); /** @todo enmChecksumType; not used yet. */
847 Msg.setNextPointer(NULL, 0); /** @todo pvChecksum; not used yet. */
848 Msg.setNextUInt32(0); /** @todo cbChecksum; not used yet. */
849
850 int rc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
851
852 LogFlowFuncLeaveRC(rc);
853 return rc;
854}
855
856int GuestDnDTarget::i_sendDirectory(PSENDDATACTX pCtx, GuestDnDURIObjCtx *pObjCtx, GuestDnDMsg *pMsg)
857{
858 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
859 AssertPtrReturn(pObjCtx, VERR_INVALID_POINTER);
860 AssertPtrReturn(pMsg, VERR_INVALID_POINTER);
861
862 DnDURIObject *pObj = pObjCtx->pObjURI;
863 AssertPtr(pObj);
864
865 RTCString strPath = pObj->GetDestPath();
866 if (strPath.isEmpty())
867 return VERR_INVALID_PARAMETER;
868 if (strPath.length() >= RTPATH_MAX) /* Note: Maximum is RTPATH_MAX on guest side. */
869 return VERR_BUFFER_OVERFLOW;
870
871 LogRel2(("DnD: Transferring host directory to guest: %s\n", strPath.c_str()));
872
873 pMsg->setType(HOST_DND_HG_SND_DIR);
874 pMsg->setNextString(strPath.c_str()); /* path */
875 pMsg->setNextUInt32((uint32_t)(strPath.length() + 1)); /* path length (maximum is RTPATH_MAX on guest side). */
876 pMsg->setNextUInt32(pObj->GetMode()); /* mode */
877
878 return VINF_SUCCESS;
879}
880
881int GuestDnDTarget::i_sendFile(PSENDDATACTX pCtx, GuestDnDURIObjCtx *pObjCtx, GuestDnDMsg *pMsg)
882{
883 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
884 AssertPtrReturn(pObjCtx, VERR_INVALID_POINTER);
885 AssertPtrReturn(pMsg, VERR_INVALID_POINTER);
886
887 DnDURIObject *pObj = pObjCtx->pObjURI;
888 AssertPtr(pObj);
889
890 RTCString strPathSrc = pObj->GetSourcePath();
891 if (strPathSrc.isEmpty())
892 return VERR_INVALID_PARAMETER;
893
894 int rc = VINF_SUCCESS;
895
896 LogFlowFunc(("Sending file with %RU32 bytes buffer, using protocol v%RU32 ...\n",
897 mData.mcbBlockSize, mDataBase.m_uProtocolVersion));
898 LogFlowFunc(("strPathSrc=%s, fIsOpen=%RTbool, cbSize=%RU64\n", strPathSrc.c_str(), pObj->IsOpen(), pObj->GetSize()));
899
900 if (!pObj->IsOpen())
901 {
902 LogRel2(("DnD: Opening host file for transferring to guest: %s\n", strPathSrc.c_str()));
903 rc = pObj->OpenEx(strPathSrc, DnDURIObject::File, DnDURIObject::Source,
904 RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE, 0 /* fFlags */);
905 if (RT_FAILURE(rc))
906 LogRel(("DnD: Error opening host file '%s', rc=%Rrc\n", strPathSrc.c_str(), rc));
907 }
908
909 bool fSendData = false;
910 if (RT_SUCCESS(rc))
911 {
912 if (mDataBase.m_uProtocolVersion >= 2)
913 {
914 if (!pObjCtx->fHeaderSent)
915 {
916 /*
917 * Since protocol v2 the file header and the actual file contents are
918 * separate messages, so send the file header first.
919 * The just registered callback will be called by the guest afterwards.
920 */
921 pMsg->setType(HOST_DND_HG_SND_FILE_HDR);
922 pMsg->setNextUInt32(0); /* uContextID */
923 rc = pMsg->setNextString(pObj->GetDestPath().c_str()); /* pvName */
924 AssertRC(rc);
925 pMsg->setNextUInt32((uint32_t)(pObj->GetDestPath().length() + 1)); /* cbName */
926 pMsg->setNextUInt32(0); /* uFlags */
927 pMsg->setNextUInt32(pObj->GetMode()); /* fMode */
928 pMsg->setNextUInt64(pObj->GetSize()); /* uSize */
929
930 LogFlowFunc(("Sending file header ...\n"));
931 LogRel2(("DnD: Transferring host file to guest: %s (%RU64 bytes, mode 0x%x)\n",
932 strPathSrc.c_str(), pObj->GetSize(), pObj->GetMode()));
933
934 /** @todo Set progress object title to current file being transferred? */
935
936 pObjCtx->fHeaderSent = true;
937 }
938 else
939 {
940 /* File header was sent, so only send the actual file data. */
941 fSendData = true;
942 }
943 }
944 else /* Protocol v1. */
945 {
946 /* Always send the file data, every time. */
947 fSendData = true;
948 }
949 }
950
951 if ( RT_SUCCESS(rc)
952 && fSendData)
953 {
954 rc = i_sendFileData(pCtx, pObjCtx, pMsg);
955 }
956
957 LogFlowFuncLeaveRC(rc);
958 return rc;
959}
960
961int GuestDnDTarget::i_sendFileData(PSENDDATACTX pCtx, GuestDnDURIObjCtx *pObjCtx, GuestDnDMsg *pMsg)
962{
963 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
964 AssertPtrReturn(pObjCtx, VERR_INVALID_POINTER);
965 AssertPtrReturn(pMsg, VERR_INVALID_POINTER);
966
967 DnDURIObject *pObj = pObjCtx->pObjURI;
968 AssertPtr(pObj);
969
970 GuestDnDResponse *pResp = pCtx->mpResp;
971 AssertPtr(pResp);
972
973 /** @todo Don't allow concurrent reads per context! */
974
975 /*
976 * Start sending stuff.
977 */
978
979 /* Set the message type. */
980 pMsg->setType(HOST_DND_HG_SND_FILE_DATA);
981
982 /* Protocol version 1 sends the file path *every* time with a new file chunk.
983 * In protocol version 2 we only do this once with HOST_DND_HG_SND_FILE_HDR. */
984 if (mDataBase.m_uProtocolVersion <= 1)
985 {
986 pMsg->setNextString(pObj->GetDestPath().c_str()); /* pvName */
987 pMsg->setNextUInt32((uint32_t)(pObj->GetDestPath().length() + 1)); /* cbName */
988 }
989 else if (mDataBase.m_uProtocolVersion >= 2)
990 {
991 /* Since protocol v2 we also send the context ID. Currently unused. */
992 pMsg->setNextUInt32(0); /* uContext */
993 }
994
995 uint32_t cbRead = 0;
996
997 int rc = pObj->Read(pCtx->mURI.getBufferMutable(), pCtx->mURI.getBufferSize(), &cbRead);
998 if (RT_SUCCESS(rc))
999 {
1000 pCtx->mData.addProcessed(cbRead);
1001 LogFlowFunc(("cbBufSize=%zu, cbRead=%RU32\n", pCtx->mURI.getBufferSize(), cbRead));
1002
1003 if (mDataBase.m_uProtocolVersion <= 1)
1004 {
1005 pMsg->setNextPointer(pCtx->mURI.getBufferMutable(), cbRead); /* pvData */
1006 pMsg->setNextUInt32(cbRead); /* cbData */
1007 pMsg->setNextUInt32(pObj->GetMode()); /* fMode */
1008 }
1009 else /* Protocol v2 and up. */
1010 {
1011 pMsg->setNextPointer(pCtx->mURI.getBufferMutable(), cbRead); /* pvData */
1012 pMsg->setNextUInt32(cbRead); /* cbData */
1013
1014 if (mDataBase.m_uProtocolVersion >= 3)
1015 {
1016 /** @todo Calculate checksum. */
1017 pMsg->setNextPointer(NULL, 0); /* pvChecksum */
1018 pMsg->setNextUInt32(0); /* cbChecksum */
1019 }
1020 }
1021
1022 if (pObj->IsComplete()) /* Done reading? */
1023 {
1024 LogRel2(("DnD: File transfer to guest complete: %s\n", pObj->GetSourcePath().c_str()));
1025 LogFlowFunc(("File '%s' complete\n", pObj->GetSourcePath().c_str()));
1026
1027 /* DnDURIObject::Read() returns VINF_EOF when finished reading the entire fire,
1028 * but we don't want this here -- so just override this with VINF_SUCCESS. */
1029 rc = VINF_SUCCESS;
1030 }
1031 }
1032
1033 LogFlowFuncLeaveRC(rc);
1034 return rc;
1035}
1036
1037/* static */
1038DECLCALLBACK(int) GuestDnDTarget::i_sendURIDataCallback(uint32_t uMsg, void *pvParms, size_t cbParms, void *pvUser)
1039{
1040 PSENDDATACTX pCtx = (PSENDDATACTX)pvUser;
1041 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1042
1043 GuestDnDTarget *pThis = pCtx->mpTarget;
1044 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
1045
1046 LogFlowFunc(("pThis=%p, uMsg=%RU32\n", pThis, uMsg));
1047
1048 int rc = VINF_SUCCESS;
1049 int rcGuest = VINF_SUCCESS; /* Contains error code from guest in case of VERR_GSTDND_GUEST_ERROR. */
1050 bool fNotify = false;
1051
1052 switch (uMsg)
1053 {
1054 case GUEST_DND_GET_NEXT_HOST_MSG:
1055 {
1056 PVBOXDNDCBHGGETNEXTHOSTMSG pCBData = reinterpret_cast<PVBOXDNDCBHGGETNEXTHOSTMSG>(pvParms);
1057 AssertPtr(pCBData);
1058 AssertReturn(sizeof(VBOXDNDCBHGGETNEXTHOSTMSG) == cbParms, VERR_INVALID_PARAMETER);
1059 AssertReturn(CB_MAGIC_DND_HG_GET_NEXT_HOST_MSG == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
1060
1061 try
1062 {
1063 GuestDnDMsg *pMsg = new GuestDnDMsg();
1064
1065 rc = pThis->i_sendURIDataLoop(pCtx, pMsg);
1066 if (rc == VINF_EOF) /* Transfer complete? */
1067 {
1068 LogFlowFunc(("Last URI item processed, bailing out\n"));
1069 }
1070 else if (RT_SUCCESS(rc))
1071 {
1072 rc = pThis->msgQueueAdd(pMsg);
1073 if (RT_SUCCESS(rc)) /* Return message type & required parameter count to the guest. */
1074 {
1075 LogFlowFunc(("GUEST_DND_GET_NEXT_HOST_MSG -> %RU32 (%RU32 params)\n", pMsg->getType(), pMsg->getCount()));
1076 pCBData->uMsg = pMsg->getType();
1077 pCBData->cParms = pMsg->getCount();
1078 }
1079 }
1080
1081 if ( RT_FAILURE(rc)
1082 || rc == VINF_EOF) /* Transfer complete? */
1083 {
1084 delete pMsg;
1085 pMsg = NULL;
1086 }
1087 }
1088 catch(std::bad_alloc & /*e*/)
1089 {
1090 rc = VERR_NO_MEMORY;
1091 }
1092 break;
1093 }
1094 case GUEST_DND_GH_EVT_ERROR:
1095 {
1096 PVBOXDNDCBEVTERRORDATA pCBData = reinterpret_cast<PVBOXDNDCBEVTERRORDATA>(pvParms);
1097 AssertPtr(pCBData);
1098 AssertReturn(sizeof(VBOXDNDCBEVTERRORDATA) == cbParms, VERR_INVALID_PARAMETER);
1099 AssertReturn(CB_MAGIC_DND_GH_EVT_ERROR == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
1100
1101 pCtx->mpResp->reset();
1102
1103 if (RT_SUCCESS(pCBData->rc))
1104 {
1105 AssertMsgFailed(("Guest has sent an error event but did not specify an actual error code\n"));
1106 pCBData->rc = VERR_GENERAL_FAILURE; /* Make sure some error is set. */
1107 }
1108
1109 rc = pCtx->mpResp->setProgress(100, DND_PROGRESS_ERROR, pCBData->rc,
1110 GuestDnDTarget::i_guestErrorToString(pCBData->rc));
1111 if (RT_SUCCESS(rc))
1112 {
1113 rc = VERR_GSTDND_GUEST_ERROR;
1114 rcGuest = pCBData->rc;
1115 }
1116 break;
1117 }
1118 case HOST_DND_HG_SND_DIR:
1119 case HOST_DND_HG_SND_FILE_HDR:
1120 case HOST_DND_HG_SND_FILE_DATA:
1121 {
1122 PVBOXDNDCBHGGETNEXTHOSTMSGDATA pCBData
1123 = reinterpret_cast<PVBOXDNDCBHGGETNEXTHOSTMSGDATA>(pvParms);
1124 AssertPtr(pCBData);
1125 AssertReturn(sizeof(VBOXDNDCBHGGETNEXTHOSTMSGDATA) == cbParms, VERR_INVALID_PARAMETER);
1126 AssertReturn(CB_MAGIC_DND_HG_GET_NEXT_HOST_MSG_DATA == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
1127
1128 LogFlowFunc(("pCBData->uMsg=%RU32, paParms=%p, cParms=%RU32\n", pCBData->uMsg, pCBData->paParms, pCBData->cParms));
1129
1130 GuestDnDMsg *pMsg = pThis->msgQueueGetNext();
1131 if (pMsg)
1132 {
1133 /*
1134 * Sanity checks.
1135 */
1136 if ( pCBData->uMsg != uMsg
1137 || pCBData->paParms == NULL
1138 || pCBData->cParms != pMsg->getCount())
1139 {
1140 LogFlowFunc(("Current message does not match:\n"));
1141 LogFlowFunc(("\tCallback: uMsg=%RU32, cParms=%RU32, paParms=%p\n",
1142 pCBData->uMsg, pCBData->cParms, pCBData->paParms));
1143 LogFlowFunc(("\t Next: uMsg=%RU32, cParms=%RU32\n", pMsg->getType(), pMsg->getCount()));
1144
1145 /* Start over. */
1146 pThis->msgQueueClear();
1147
1148 rc = VERR_INVALID_PARAMETER;
1149 }
1150
1151 if (RT_SUCCESS(rc))
1152 {
1153 LogFlowFunc(("Returning uMsg=%RU32\n", uMsg));
1154 rc = HGCM::Message::copyParms(pMsg->getCount(), pMsg->getParms(), pCBData->paParms);
1155 if (RT_SUCCESS(rc))
1156 {
1157 pCBData->cParms = pMsg->getCount();
1158 pThis->msgQueueRemoveNext();
1159 }
1160 else
1161 LogFlowFunc(("Copying parameters failed with rc=%Rrc\n", rc));
1162 }
1163 }
1164 else
1165 rc = VERR_NO_DATA;
1166
1167 LogFlowFunc(("Processing next message ended with rc=%Rrc\n", rc));
1168 break;
1169 }
1170 default:
1171 rc = VERR_NOT_SUPPORTED;
1172 break;
1173 }
1174
1175 int rcToGuest = VINF_SUCCESS; /* Status which will be sent back to the guest. */
1176
1177 /*
1178 * Resolve errors.
1179 */
1180 switch (rc)
1181 {
1182 case VINF_SUCCESS:
1183 break;
1184
1185 case VINF_EOF:
1186 {
1187 LogRel2(("DnD: Transfer to guest complete\n"));
1188
1189 /* Complete operation on host side. */
1190 fNotify = true;
1191
1192 /* The guest expects VERR_NO_DATA if the transfer is complete. */
1193 rcToGuest = VERR_NO_DATA;
1194 break;
1195 }
1196
1197 case VERR_GSTDND_GUEST_ERROR:
1198 {
1199 LogRel(("DnD: Guest reported error %Rrc, aborting transfer to guest\n", rcGuest));
1200 break;
1201 }
1202
1203 case VERR_CANCELLED:
1204 {
1205 LogRel2(("DnD: Transfer to guest canceled\n"));
1206 rcToGuest = VERR_CANCELLED; /* Also cancel on guest side. */
1207 break;
1208 }
1209
1210 default:
1211 {
1212 LogRel(("DnD: Host error %Rrc occurred, aborting transfer to guest\n", rc));
1213 rcToGuest = VERR_CANCELLED; /* Also cancel on guest side. */
1214 break;
1215 }
1216 }
1217
1218 if (RT_FAILURE(rc))
1219 {
1220 /* Unregister this callback. */
1221 AssertPtr(pCtx->mpResp);
1222 int rc2 = pCtx->mpResp->setCallback(uMsg, NULL /* PFNGUESTDNDCALLBACK */);
1223 AssertRC(rc2);
1224
1225 /* Let the waiter(s) know. */
1226 fNotify = true;
1227 }
1228
1229 LogFlowFunc(("fNotify=%RTbool, rc=%Rrc, rcToGuest=%Rrc\n", fNotify, rc, rcToGuest));
1230
1231 if (fNotify)
1232 {
1233 int rc2 = pCtx->mCBEvent.Notify(rc); /** @todo Also pass guest error back? */
1234 AssertRC(rc2);
1235 }
1236
1237 LogFlowFuncLeaveRC(rc);
1238 return rcToGuest; /* Tell the guest. */
1239}
1240
1241int GuestDnDTarget::i_sendURIData(PSENDDATACTX pCtx, RTMSINTERVAL msTimeout)
1242{
1243 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1244 AssertPtr(pCtx->mpResp);
1245
1246#define URI_DATA_IS_VALID_BREAK(x) \
1247 if (!x) \
1248 { \
1249 LogFlowFunc(("Invalid URI data value for \"" #x "\"\n")); \
1250 rc = VERR_INVALID_PARAMETER; \
1251 break; \
1252 }
1253
1254#define REGISTER_CALLBACK(x) \
1255 rc = pCtx->mpResp->setCallback(x, i_sendURIDataCallback, pCtx); \
1256 if (RT_FAILURE(rc)) \
1257 return rc;
1258
1259#define UNREGISTER_CALLBACK(x) \
1260 { \
1261 int rc2 = pCtx->mpResp->setCallback(x, NULL); \
1262 AssertRC(rc2); \
1263 }
1264
1265 int rc = pCtx->mURI.init(mData.mcbBlockSize);
1266 if (RT_FAILURE(rc))
1267 return rc;
1268
1269 rc = pCtx->mCBEvent.Reset();
1270 if (RT_FAILURE(rc))
1271 return rc;
1272
1273 /*
1274 * Register callbacks.
1275 */
1276 /* Guest callbacks. */
1277 REGISTER_CALLBACK(GUEST_DND_GET_NEXT_HOST_MSG);
1278 REGISTER_CALLBACK(GUEST_DND_GH_EVT_ERROR);
1279 /* Host callbacks. */
1280 REGISTER_CALLBACK(HOST_DND_HG_SND_DIR);
1281 if (mDataBase.m_uProtocolVersion >= 2)
1282 REGISTER_CALLBACK(HOST_DND_HG_SND_FILE_HDR);
1283 REGISTER_CALLBACK(HOST_DND_HG_SND_FILE_DATA);
1284
1285 do
1286 {
1287 /*
1288 * Extract URI list from current meta data.
1289 */
1290 GuestDnDData *pData = &pCtx->mData;
1291 GuestDnDURIData *pURI = &pCtx->mURI;
1292
1293 rc = pURI->fromMetaData(pData->getMeta());
1294 if (RT_FAILURE(rc))
1295 break;
1296
1297 LogFlowFunc(("URI root objects: %zu, total bytes (raw data to transfer): %zu\n",
1298 pURI->getURIList().RootCount(), pURI->getURIList().TotalBytes()));
1299
1300 /*
1301 * Set the new meta data with the URI list in it.
1302 */
1303 rc = pData->getMeta().fromURIList(pURI->getURIList());
1304 if (RT_FAILURE(rc))
1305 break;
1306
1307 /*
1308 * Set the additional size we are going to send after the meta data header + meta data.
1309 * This additional data will contain the actual file data we want to transfer.
1310 */
1311 pData->setAdditionalSize(pURI->getURIList().TotalBytes());
1312
1313 void *pvFmt = (void *)pCtx->mFmtReq.c_str();
1314 uint32_t cbFmt = pCtx->mFmtReq.length() + 1; /* Include terminating zero. */
1315
1316 pData->setFmt(pvFmt, cbFmt);
1317
1318 /*
1319 * The first message always is the data header. The meta data itself then follows
1320 * and *only* contains the root elements of an URI list.
1321 *
1322 * After the meta data we generate the messages required to send the
1323 * file/directory data itself.
1324 *
1325 * Note: Protocol < v3 use the first data message to tell what's being sent.
1326 */
1327 GuestDnDMsg Msg;
1328
1329 /*
1330 * Send the data header first.
1331 */
1332 if (mDataBase.m_uProtocolVersion >= 3)
1333 rc = i_sendDataHeader(pCtx, pData, &pCtx->mURI);
1334
1335 /*
1336 * Send the (meta) data body.
1337 */
1338 if (RT_SUCCESS(rc))
1339 rc = i_sendDataBody(pCtx, pData);
1340
1341 if (RT_SUCCESS(rc))
1342 {
1343 rc = waitForEvent(&pCtx->mCBEvent, pCtx->mpResp, msTimeout);
1344 if (RT_FAILURE(rc))
1345 {
1346 if (rc == VERR_CANCELLED)
1347 rc = pCtx->mpResp->setProgress(100, DND_PROGRESS_CANCELLED, VINF_SUCCESS);
1348 else if (rc != VERR_GSTDND_GUEST_ERROR) /* Guest-side error are already handled in the callback. */
1349 rc = pCtx->mpResp->setProgress(100, DND_PROGRESS_ERROR, rc,
1350 GuestDnDTarget::i_hostErrorToString(rc));
1351 }
1352 else
1353 rc = pCtx->mpResp->setProgress(100, DND_PROGRESS_COMPLETE, VINF_SUCCESS);
1354 }
1355
1356 } while (0);
1357
1358 /*
1359 * Unregister callbacks.
1360 */
1361 /* Guest callbacks. */
1362 UNREGISTER_CALLBACK(GUEST_DND_GET_NEXT_HOST_MSG);
1363 UNREGISTER_CALLBACK(GUEST_DND_GH_EVT_ERROR);
1364 /* Host callbacks. */
1365 UNREGISTER_CALLBACK(HOST_DND_HG_SND_DIR);
1366 if (mDataBase.m_uProtocolVersion >= 2)
1367 UNREGISTER_CALLBACK(HOST_DND_HG_SND_FILE_HDR);
1368 UNREGISTER_CALLBACK(HOST_DND_HG_SND_FILE_DATA);
1369
1370#undef REGISTER_CALLBACK
1371#undef UNREGISTER_CALLBACK
1372
1373 /*
1374 * Now that we've cleaned up tell the guest side to cancel.
1375 * This does not imply we're waiting for the guest to react, as the
1376 * host side never must depend on anything from the guest.
1377 */
1378 if (rc == VERR_CANCELLED)
1379 {
1380 int rc2 = sendCancel();
1381 AssertRC(rc2);
1382 }
1383
1384#undef URI_DATA_IS_VALID_BREAK
1385
1386 LogFlowFuncLeaveRC(rc);
1387 return rc;
1388}
1389
1390int GuestDnDTarget::i_sendURIDataLoop(PSENDDATACTX pCtx, GuestDnDMsg *pMsg)
1391{
1392 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1393 AssertPtrReturn(pMsg, VERR_INVALID_POINTER);
1394
1395 int rc = updateProgress(&pCtx->mData, pCtx->mpResp);
1396 AssertRC(rc);
1397
1398 if ( pCtx->mData.isComplete()
1399 && pCtx->mURI.isComplete())
1400 {
1401 return VINF_EOF;
1402 }
1403
1404 GuestDnDURIObjCtx &objCtx = pCtx->mURI.getObjCurrent();
1405 if (!objCtx.isValid())
1406 return VERR_WRONG_ORDER;
1407
1408 DnDURIObject *pCurObj = objCtx.pObjURI;
1409 AssertPtr(pCurObj);
1410
1411 uint32_t fMode = pCurObj->GetMode();
1412 LogRel3(("DnD: Processing: srcPath=%s, dstPath=%s, fMode=0x%x, cbSize=%RU32, fIsDir=%RTbool, fIsFile=%RTbool\n",
1413 pCurObj->GetSourcePath().c_str(), pCurObj->GetDestPath().c_str(),
1414 fMode, pCurObj->GetSize(),
1415 RTFS_IS_DIRECTORY(fMode), RTFS_IS_FILE(fMode)));
1416
1417 if (RTFS_IS_DIRECTORY(fMode))
1418 {
1419 rc = i_sendDirectory(pCtx, &objCtx, pMsg);
1420 }
1421 else if (RTFS_IS_FILE(fMode))
1422 {
1423 rc = i_sendFile(pCtx, &objCtx, pMsg);
1424 }
1425 else
1426 {
1427 AssertMsgFailed(("fMode=0x%x is not supported for srcPath=%s, dstPath=%s\n",
1428 fMode, pCurObj->GetSourcePath().c_str(), pCurObj->GetDestPath().c_str()));
1429 rc = VERR_NOT_SUPPORTED;
1430 }
1431
1432 bool fRemove = false; /* Remove current entry? */
1433 if ( pCurObj->IsComplete()
1434 || RT_FAILURE(rc))
1435 {
1436 fRemove = true;
1437 }
1438
1439 if (fRemove)
1440 {
1441 LogFlowFunc(("Removing \"%s\" from list, rc=%Rrc\n", pCurObj->GetSourcePath().c_str(), rc));
1442 pCtx->mURI.removeObjCurrent();
1443 }
1444
1445 LogFlowFuncLeaveRC(rc);
1446 return rc;
1447}
1448
1449int GuestDnDTarget::i_sendRawData(PSENDDATACTX pCtx, RTMSINTERVAL msTimeout)
1450{
1451 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1452 NOREF(msTimeout);
1453
1454 GuestDnD *pInst = GuestDnDInst();
1455 AssertPtr(pInst);
1456
1457 GuestDnDData *pData = &pCtx->mData;
1458
1459 /** @todo At the moment we only allow sending up to 64K raw data.
1460 * For protocol v1+v2: Fix this by using HOST_DND_HG_SND_MORE_DATA.
1461 * For protocol v3 : Send another HOST_DND_HG_SND_DATA message. */
1462 if (!pData->getMeta().getSize())
1463 return VINF_SUCCESS;
1464
1465 int rc = VINF_SUCCESS;
1466
1467 /*
1468 * Send the data header first.
1469 */
1470 if (mDataBase.m_uProtocolVersion >= 3)
1471 rc = i_sendDataHeader(pCtx, pData, NULL /* URI list */);
1472
1473 /*
1474 * Send the (meta) data body.
1475 */
1476 if (RT_SUCCESS(rc))
1477 rc = i_sendDataBody(pCtx, pData);
1478
1479 int rc2;
1480 if (RT_FAILURE(rc))
1481 rc2 = pCtx->mpResp->setProgress(100, DND_PROGRESS_ERROR, rc,
1482 GuestDnDTarget::i_hostErrorToString(rc));
1483 else
1484 rc2 = pCtx->mpResp->setProgress(100, DND_PROGRESS_COMPLETE, rc);
1485 AssertRC(rc2);
1486
1487 LogFlowFuncLeaveRC(rc);
1488 return rc;
1489}
1490
1491HRESULT GuestDnDTarget::cancel(BOOL *aVeto)
1492{
1493#if !defined(VBOX_WITH_DRAG_AND_DROP)
1494 ReturnComNotImplemented();
1495#else /* VBOX_WITH_DRAG_AND_DROP */
1496
1497 int rc = i_cancelOperation();
1498
1499 if (aVeto)
1500 *aVeto = FALSE; /** @todo */
1501
1502 HRESULT hr = RT_SUCCESS(rc) ? S_OK : VBOX_E_IPRT_ERROR;
1503
1504 LogFlowFunc(("hr=%Rhrc\n", hr));
1505 return hr;
1506#endif /* VBOX_WITH_DRAG_AND_DROP */
1507}
1508
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