VirtualBox

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

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

DnD: Updates/bugfixes:

  • Added separate VBOXDNDDISCONNECTMSG message for letting Main know about client disconnects.
  • Various cleanups and bugfixes.
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 51.7 KB
Line 
1/* $Id: GuestDnDTargetImpl.cpp 58329 2015-10-20 10:05:12Z 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 if (mDataBase.m_uProtocolVersion >= 3)
306 Msg.setNextUInt32(0); /** @todo ContextID not used yet. */
307 Msg.setNextUInt32(aScreenId);
308 Msg.setNextUInt32(aX);
309 Msg.setNextUInt32(aY);
310 Msg.setNextUInt32(uDefAction);
311 Msg.setNextUInt32(uAllowedActions);
312 Msg.setNextPointer((void*)strFormats.c_str(), strFormats.length() + 1);
313 Msg.setNextUInt32(strFormats.length() + 1);
314
315 rc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
316 if (RT_SUCCESS(rc))
317 {
318 GuestDnDResponse *pResp = GuestDnDInst()->response();
319 if (pResp && RT_SUCCESS(pResp->waitForGuestResponse()))
320 resAction = GuestDnD::toMainAction(pResp->defAction());
321 }
322 }
323
324 if (RT_FAILURE(rc))
325 hr = VBOX_E_IPRT_ERROR;
326
327 if (SUCCEEDED(hr))
328 {
329 if (aResultAction)
330 *aResultAction = resAction;
331 }
332
333 LogFlowFunc(("hr=%Rhrc, resAction=%ld\n", hr, resAction));
334 return hr;
335#endif /* VBOX_WITH_DRAG_AND_DROP */
336}
337
338HRESULT GuestDnDTarget::move(ULONG aScreenId, ULONG aX, ULONG aY,
339 DnDAction_T aDefaultAction,
340 const std::vector<DnDAction_T> &aAllowedActions,
341 const GuestDnDMIMEList &aFormats,
342 DnDAction_T *aResultAction)
343{
344#if !defined(VBOX_WITH_DRAG_AND_DROP)
345 ReturnComNotImplemented();
346#else /* VBOX_WITH_DRAG_AND_DROP */
347
348 /* Input validation. */
349
350 AutoCaller autoCaller(this);
351 if (FAILED(autoCaller.rc())) return autoCaller.rc();
352
353 /* Default action is ignoring. */
354 DnDAction_T resAction = DnDAction_Ignore;
355
356 /* Check & convert the drag & drop actions. */
357 uint32_t uDefAction = 0;
358 uint32_t uAllowedActions = 0;
359 GuestDnD::toHGCMActions(aDefaultAction, &uDefAction,
360 aAllowedActions, &uAllowedActions);
361 /* If there is no usable action, ignore this request. */
362 if (isDnDIgnoreAction(uDefAction))
363 return S_OK;
364
365 /*
366 * Make a flat data string out of the supported format list.
367 * In the GuestDnDTarget case the source formats are from the host,
368 * as GuestDnDTarget acts as a source for the guest.
369 */
370 Utf8Str strFormats = GuestDnD::toFormatString(GuestDnD::toFilteredFormatList(m_lstFmtSupported, aFormats));
371 if (strFormats.isEmpty())
372 return setError(E_INVALIDARG, tr("No or not supported format(s) specified"));
373
374 HRESULT hr = S_OK;
375
376 int rc = GuestDnDInst()->adjustScreenCoordinates(aScreenId, &aX, &aY);
377 if (RT_SUCCESS(rc))
378 {
379 GuestDnDMsg Msg;
380 Msg.setType(HOST_DND_HG_EVT_MOVE);
381 if (mDataBase.m_uProtocolVersion >= 3)
382 Msg.setNextUInt32(0); /** @todo ContextID not used yet. */
383 Msg.setNextUInt32(aScreenId);
384 Msg.setNextUInt32(aX);
385 Msg.setNextUInt32(aY);
386 Msg.setNextUInt32(uDefAction);
387 Msg.setNextUInt32(uAllowedActions);
388 Msg.setNextPointer((void*)strFormats.c_str(), strFormats.length() + 1);
389 Msg.setNextUInt32(strFormats.length() + 1);
390
391 rc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
392 if (RT_SUCCESS(rc))
393 {
394 GuestDnDResponse *pResp = GuestDnDInst()->response();
395 if (pResp && RT_SUCCESS(pResp->waitForGuestResponse()))
396 resAction = GuestDnD::toMainAction(pResp->defAction());
397 }
398 }
399
400 if (RT_FAILURE(rc))
401 hr = VBOX_E_IPRT_ERROR;
402
403 if (SUCCEEDED(hr))
404 {
405 if (aResultAction)
406 *aResultAction = resAction;
407 }
408
409 LogFlowFunc(("hr=%Rhrc, *pResultAction=%ld\n", hr, resAction));
410 return hr;
411#endif /* VBOX_WITH_DRAG_AND_DROP */
412}
413
414HRESULT GuestDnDTarget::leave(ULONG uScreenId)
415{
416#if !defined(VBOX_WITH_DRAG_AND_DROP)
417 ReturnComNotImplemented();
418#else /* VBOX_WITH_DRAG_AND_DROP */
419
420 AutoCaller autoCaller(this);
421 if (FAILED(autoCaller.rc())) return autoCaller.rc();
422
423 HRESULT hr = S_OK;
424 int rc = GuestDnDInst()->hostCall(HOST_DND_HG_EVT_LEAVE,
425 0 /* cParms */, NULL /* paParms */);
426 if (RT_SUCCESS(rc))
427 {
428 GuestDnDResponse *pResp = GuestDnDInst()->response();
429 if (pResp)
430 pResp->waitForGuestResponse();
431 }
432
433 if (RT_FAILURE(rc))
434 hr = VBOX_E_IPRT_ERROR;
435
436 LogFlowFunc(("hr=%Rhrc\n", hr));
437 return hr;
438#endif /* VBOX_WITH_DRAG_AND_DROP */
439}
440
441HRESULT GuestDnDTarget::drop(ULONG aScreenId, ULONG aX, ULONG aY,
442 DnDAction_T aDefaultAction,
443 const std::vector<DnDAction_T> &aAllowedActions,
444 const GuestDnDMIMEList &aFormats,
445 com::Utf8Str &aFormat,
446 DnDAction_T *aResultAction)
447{
448#if !defined(VBOX_WITH_DRAG_AND_DROP)
449 ReturnComNotImplemented();
450#else /* VBOX_WITH_DRAG_AND_DROP */
451
452 if (aDefaultAction == DnDAction_Ignore)
453 return setError(E_INVALIDARG, tr("Invalid default action specified"));
454 if (!aAllowedActions.size())
455 return setError(E_INVALIDARG, tr("Invalid allowed actions specified"));
456 if (!aFormats.size())
457 return setError(E_INVALIDARG, tr("No drop format(s) specified"));
458 /* aResultAction is optional. */
459
460 AutoCaller autoCaller(this);
461 if (FAILED(autoCaller.rc())) return autoCaller.rc();
462
463 /* Default action is ignoring. */
464 DnDAction_T resAction = DnDAction_Ignore;
465
466 /* Check & convert the drag & drop actions to HGCM codes. */
467 uint32_t uDefAction = DND_IGNORE_ACTION;
468 uint32_t uAllowedActions = 0;
469 GuestDnD::toHGCMActions(aDefaultAction, &uDefAction,
470 aAllowedActions, &uAllowedActions);
471 /* If there is no usable action, ignore this request. */
472 if (isDnDIgnoreAction(uDefAction))
473 {
474 aFormat = "";
475 if (aResultAction)
476 *aResultAction = DnDAction_Ignore;
477 return S_OK;
478 }
479
480 /*
481 * Make a flat data string out of the supported format list.
482 * In the GuestDnDTarget case the source formats are from the host,
483 * as GuestDnDTarget acts as a source for the guest.
484 */
485 Utf8Str strFormats = GuestDnD::toFormatString(GuestDnD::toFilteredFormatList(m_lstFmtSupported, aFormats));
486 if (strFormats.isEmpty())
487 return setError(E_INVALIDARG, tr("No or not supported format(s) specified"));
488
489 /* Adjust the coordinates in a multi-monitor setup. */
490 HRESULT hr = GuestDnDInst()->adjustScreenCoordinates(aScreenId, &aX, &aY);
491 if (SUCCEEDED(hr))
492 {
493 GuestDnDMsg Msg;
494 Msg.setType(HOST_DND_HG_EVT_DROPPED);
495 if (mDataBase.m_uProtocolVersion >= 3)
496 Msg.setNextUInt32(0); /** @todo ContextID not used yet. */
497 Msg.setNextUInt32(aScreenId);
498 Msg.setNextUInt32(aX);
499 Msg.setNextUInt32(aY);
500 Msg.setNextUInt32(uDefAction);
501 Msg.setNextUInt32(uAllowedActions);
502 Msg.setNextPointer((void*)strFormats.c_str(), strFormats.length() + 1);
503 Msg.setNextUInt32(strFormats.length() + 1);
504
505 int rc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
506 if (RT_SUCCESS(rc))
507 {
508 GuestDnDResponse *pResp = GuestDnDInst()->response();
509 AssertPtr(pResp);
510
511 rc = pResp->waitForGuestResponse();
512 if (RT_SUCCESS(rc))
513 {
514 resAction = GuestDnD::toMainAction(pResp->defAction());
515
516 GuestDnDMIMEList lstFormats = pResp->formats();
517 if (lstFormats.size() == 1) /* Exactly one format to use specified? */
518 {
519 aFormat = lstFormats.at(0);
520 LogFlowFunc(("resFormat=%s, resAction=%RU32\n", aFormat.c_str(), pResp->defAction()));
521 }
522 else
523 hr = setError(VBOX_E_IPRT_ERROR, tr("Guest returned invalid drop formats (%zu formats)"), lstFormats.size());
524 }
525 else
526 hr = setError(VBOX_E_IPRT_ERROR, tr("Waiting for response of dropped event failed (%Rrc)"), rc);
527 }
528 else
529 hr = setError(VBOX_E_IPRT_ERROR, tr("Sending dropped event to guest failed (%Rrc)"), rc);
530 }
531 else
532 hr = setError(hr, tr("Retrieving drop coordinates failed"));
533
534 if (SUCCEEDED(hr))
535 {
536 if (aResultAction)
537 *aResultAction = resAction;
538 }
539
540 LogFlowFunc(("Returning hr=%Rhrc\n", hr));
541 return hr;
542#endif /* VBOX_WITH_DRAG_AND_DROP */
543}
544
545/* static */
546DECLCALLBACK(int) GuestDnDTarget::i_sendDataThread(RTTHREAD Thread, void *pvUser)
547{
548 LogFlowFunc(("pvUser=%p\n", pvUser));
549
550 SendDataTask *pTask = (SendDataTask *)pvUser;
551 AssertPtrReturn(pTask, VERR_INVALID_POINTER);
552
553 const ComObjPtr<GuestDnDTarget> pThis(pTask->getTarget());
554 Assert(!pThis.isNull());
555
556 AutoCaller autoCaller(pThis);
557 if (FAILED(autoCaller.rc())) return VERR_COM_INVALID_OBJECT_STATE;
558
559 int rc = RTThreadUserSignal(Thread);
560 AssertRC(rc);
561
562 rc = pThis->i_sendData(pTask->getCtx(), RT_INDEFINITE_WAIT /* msTimeout */);
563
564 if (pTask)
565 delete pTask;
566
567 AutoWriteLock alock(pThis COMMA_LOCKVAL_SRC_POS);
568
569 Assert(pThis->mDataBase.m_cTransfersPending);
570 pThis->mDataBase.m_cTransfersPending--;
571
572 LogFlowFunc(("pTarget=%p returning rc=%Rrc\n", (GuestDnDTarget *)pThis, rc));
573 return rc;
574}
575
576/**
577 * Initiates a data transfer from the host to the guest. The source is the host whereas the target is the
578 * guest in this case.
579 *
580 * @return HRESULT
581 * @param aScreenId
582 * @param aFormat
583 * @param aData
584 * @param aProgress
585 */
586HRESULT GuestDnDTarget::sendData(ULONG aScreenId, const com::Utf8Str &aFormat, const std::vector<BYTE> &aData,
587 ComPtr<IProgress> &aProgress)
588{
589#if !defined(VBOX_WITH_DRAG_AND_DROP)
590 ReturnComNotImplemented();
591#else /* VBOX_WITH_DRAG_AND_DROP */
592
593 AutoCaller autoCaller(this);
594 if (FAILED(autoCaller.rc())) return autoCaller.rc();
595
596 /* Input validation. */
597 if (RT_UNLIKELY((aFormat.c_str()) == NULL || *(aFormat.c_str()) == '\0'))
598 return setError(E_INVALIDARG, tr("No data format specified"));
599 if (RT_UNLIKELY(!aData.size()))
600 return setError(E_INVALIDARG, tr("No data to send specified"));
601
602 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
603
604 /* At the moment we only support one transfer at a time. */
605 if (mDataBase.m_cTransfersPending)
606 return setError(E_INVALIDARG, tr("Another drop operation already is in progress"));
607
608 /* Dito. */
609 GuestDnDResponse *pResp = GuestDnDInst()->response();
610 AssertPtr(pResp);
611
612 HRESULT hr = pResp->resetProgress(m_pGuest);
613 if (FAILED(hr))
614 return hr;
615
616 try
617 {
618 PSENDDATACTX pSendCtx = new SENDDATACTX;
619 RT_BZERO(pSendCtx, sizeof(SENDDATACTX));
620
621 pSendCtx->mpTarget = this;
622 pSendCtx->mpResp = pResp;
623 pSendCtx->mScreenID = aScreenId;
624 pSendCtx->mFmtReq = aFormat;
625
626 pSendCtx->mData.getMeta().add(aData);
627
628 SendDataTask *pTask = new SendDataTask(this, pSendCtx);
629 AssertReturn(pTask->isOk(), pTask->getRC());
630
631 LogFlowFunc(("Starting thread ...\n"));
632
633 RTTHREAD threadSnd;
634 int rc = RTThreadCreate(&threadSnd, GuestDnDTarget::i_sendDataThread,
635 (void *)pTask, 0, RTTHREADTYPE_MAIN_WORKER, 0, "dndTgtSndData");
636 if (RT_SUCCESS(rc))
637 {
638 rc = RTThreadUserWait(threadSnd, 30 * 1000 /* 30s timeout */);
639 if (RT_SUCCESS(rc))
640 {
641 mDataBase.m_cTransfersPending++;
642
643 hr = pResp->queryProgressTo(aProgress.asOutParam());
644 ComAssertComRC(hr);
645
646 /* Note: pTask is now owned by the worker thread. */
647 }
648 else
649 hr = setError(VBOX_E_IPRT_ERROR, tr("Waiting for sending thread failed (%Rrc)"), rc);
650 }
651 else
652 hr = setError(VBOX_E_IPRT_ERROR, tr("Starting thread failed (%Rrc)"), rc);
653
654 if (FAILED(hr))
655 delete pSendCtx;
656 }
657 catch(std::bad_alloc &)
658 {
659 hr = setError(E_OUTOFMEMORY);
660 }
661
662 LogFlowFunc(("Returning hr=%Rhrc\n", hr));
663 return hr;
664#endif /* VBOX_WITH_DRAG_AND_DROP */
665}
666
667int GuestDnDTarget::i_cancelOperation(void)
668{
669 /** @todo Check for pending cancel requests. */
670
671#if 0 /** @todo Later. */
672 /* Cancel any outstanding waits for guest responses first. */
673 if (pResp)
674 pResp->notifyAboutGuestResponse();
675#endif
676
677 LogFlowFunc(("Cancelling operation, telling guest ...\n"));
678 return GuestDnDInst()->hostCall(HOST_DND_HG_EVT_CANCEL, 0 /* cParms */, NULL /*paParms*/);
679}
680
681/* static */
682Utf8Str GuestDnDTarget::i_guestErrorToString(int guestRc)
683{
684 Utf8Str strError;
685
686 switch (guestRc)
687 {
688 case VERR_ACCESS_DENIED:
689 strError += Utf8StrFmt(tr("For one or more guest files or directories selected for transferring to the host your guest "
690 "user does not have the appropriate access rights for. Please make sure that all selected "
691 "elements can be accessed and that your guest user has the appropriate rights"));
692 break;
693
694 case VERR_NOT_FOUND:
695 /* Should not happen due to file locking on the guest, but anyway ... */
696 strError += Utf8StrFmt(tr("One or more guest files or directories selected for transferring to the host were not"
697 "found on the guest anymore. This can be the case if the guest files were moved and/or"
698 "altered while the drag and drop operation was in progress"));
699 break;
700
701 case VERR_SHARING_VIOLATION:
702 strError += Utf8StrFmt(tr("One or more guest files or directories selected for transferring to the host were locked. "
703 "Please make sure that all selected elements can be accessed and that your guest user has "
704 "the appropriate rights"));
705 break;
706
707 case VERR_TIMEOUT:
708 strError += Utf8StrFmt(tr("The guest was not able to process the drag and drop data within time"));
709 break;
710
711 default:
712 strError += Utf8StrFmt(tr("Drag and drop error from guest (%Rrc)"), guestRc);
713 break;
714 }
715
716 return strError;
717}
718
719/* static */
720Utf8Str GuestDnDTarget::i_hostErrorToString(int hostRc)
721{
722 Utf8Str strError;
723
724 switch (hostRc)
725 {
726 case VERR_ACCESS_DENIED:
727 strError += Utf8StrFmt(tr("For one or more host files or directories selected for transferring to the guest your host "
728 "user does not have the appropriate access rights for. Please make sure that all selected "
729 "elements can be accessed and that your host user has the appropriate rights."));
730 break;
731
732 case VERR_NOT_FOUND:
733 /* Should not happen due to file locking on the host, but anyway ... */
734 strError += Utf8StrFmt(tr("One or more host files or directories selected for transferring to the host were not"
735 "found on the host anymore. This can be the case if the host files were moved and/or"
736 "altered while the drag and drop operation was in progress."));
737 break;
738
739 case VERR_SHARING_VIOLATION:
740 strError += Utf8StrFmt(tr("One or more host files or directories selected for transferring to the guest were locked. "
741 "Please make sure that all selected elements can be accessed and that your host user has "
742 "the appropriate rights."));
743 break;
744
745 default:
746 strError += Utf8StrFmt(tr("Drag and drop error from host (%Rrc)"), hostRc);
747 break;
748 }
749
750 return strError;
751}
752
753int GuestDnDTarget::i_sendData(PSENDDATACTX pCtx, RTMSINTERVAL msTimeout)
754{
755 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
756
757 int rc;
758
759 ASMAtomicWriteBool(&pCtx->mIsActive, true);
760
761 /* Clear all remaining outgoing messages. */
762 mDataBase.m_lstMsgOut.clear();
763
764 /**
765 * Do we need to build up a file tree?
766 * Note: The decision whether we need to build up a file tree and sending
767 * actual file data only depends on the actual formats offered by this target.
768 * If the guest does not want an URI list ("text/uri-list") but text ("TEXT" and
769 * friends) instead, still send the data over to the guest -- the file as such still
770 * is needed on the guest in this case, as the guest then just wants a simple path
771 * instead of an URI list (pointing to a file on the guest itself).
772 *
773 ** @todo Support more than one format; add a format<->function handler concept. Later. */
774 bool fHasURIList = std::find(m_lstFmtOffered.begin(),
775 m_lstFmtOffered.end(), "text/uri-list") != m_lstFmtOffered.end();
776 if (fHasURIList)
777 {
778 rc = i_sendURIData(pCtx, msTimeout);
779 }
780 else
781 {
782 rc = i_sendRawData(pCtx, msTimeout);
783 }
784
785 ASMAtomicWriteBool(&pCtx->mIsActive, false);
786
787 LogFlowFuncLeaveRC(rc);
788 return rc;
789}
790
791int GuestDnDTarget::i_sendDataBody(PSENDDATACTX pCtx, GuestDnDData *pData)
792{
793 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
794 AssertPtrReturn(pData, VERR_INVALID_POINTER);
795
796 /** @todo Add support for multiple HOST_DND_HG_SND_DATA messages in case of more than 64K data! */
797 if (pData->getMeta().getSize() > _64K)
798 return VERR_NOT_IMPLEMENTED;
799
800 GuestDnDMsg Msg;
801
802 LogFlowFunc(("cbFmt=%RU32, cbMeta=%RU32, cbChksum=%RU32\n",
803 pData->getFmtSize(), pData->getMeta().getSize(), pData->getChkSumSize()));
804
805 Msg.setType(HOST_DND_HG_SND_DATA);
806 if (mDataBase.m_uProtocolVersion < 3)
807 {
808 Msg.setNextUInt32(pCtx->mScreenID); /* uScreenId */
809 Msg.setNextPointer(pData->getFmtMutable(), pData->getFmtSize()); /* pvFormat */
810 Msg.setNextUInt32(pData->getFmtSize()); /* cbFormat */
811 Msg.setNextPointer(pData->getMeta().getDataMutable(), pData->getMeta().getSize()); /* pvData */
812 /* Fill in the current data block size to send.
813 * Note: Only supports uint32_t. */
814 Msg.setNextUInt32((uint32_t)pData->getMeta().getSize()); /* cbData */
815 }
816 else
817 {
818 Msg.setNextUInt32(0); /** @todo ContextID not used yet. */
819 Msg.setNextPointer(pData->getMeta().getDataMutable(), pData->getMeta().getSize()); /* pvData */
820 Msg.setNextUInt32(pData->getMeta().getSize()); /* cbData */
821 Msg.setNextPointer(pData->getChkSumMutable(), pData->getChkSumSize()); /** @todo pvChecksum; not used yet. */
822 Msg.setNextUInt32(pData->getChkSumSize()); /** @todo cbChecksum; not used yet. */
823 }
824
825 int rc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
826 if (RT_SUCCESS(rc))
827 rc = updateProgress(pData, pCtx->mpResp, pData->getMeta().getSize());
828
829 LogFlowFuncLeaveRC(rc);
830 return rc;
831}
832
833int GuestDnDTarget::i_sendDataHeader(PSENDDATACTX pCtx, GuestDnDData *pData, GuestDnDURIData *pURIData /* = NULL */)
834{
835 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
836 AssertPtrReturn(pData, VERR_INVALID_POINTER);
837 /* pURIData is optional. */
838
839 GuestDnDMsg Msg;
840
841 Msg.setType(HOST_DND_HG_SND_DATA_HDR);
842
843 Msg.setNextUInt32(0); /** @todo uContext; not used yet. */
844 Msg.setNextUInt32(0); /** @todo uFlags; not used yet. */
845 Msg.setNextUInt32(pCtx->mScreenID); /* uScreen */
846 Msg.setNextUInt64(pData->getTotal()); /* cbTotal */
847 Msg.setNextUInt32(pData->getMeta().getSize()); /* cbMeta*/
848 Msg.setNextPointer(pData->getFmtMutable(), pData->getFmtSize()); /* pvMetaFmt */
849 Msg.setNextUInt32(pData->getFmtSize()); /* cbMetaFmt */
850 Msg.setNextUInt64(pURIData ? pURIData->getObjToProcess() : 0); /* cObjects */
851 Msg.setNextUInt32(0); /** @todo enmCompression; not used yet. */
852 Msg.setNextUInt32(0); /** @todo enmChecksumType; not used yet. */
853 Msg.setNextPointer(NULL, 0); /** @todo pvChecksum; not used yet. */
854 Msg.setNextUInt32(0); /** @todo cbChecksum; not used yet. */
855
856 int rc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
857
858 LogFlowFuncLeaveRC(rc);
859 return rc;
860}
861
862int GuestDnDTarget::i_sendDirectory(PSENDDATACTX pCtx, GuestDnDURIObjCtx *pObjCtx, GuestDnDMsg *pMsg)
863{
864 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
865 AssertPtrReturn(pObjCtx, VERR_INVALID_POINTER);
866 AssertPtrReturn(pMsg, VERR_INVALID_POINTER);
867
868 DnDURIObject *pObj = pObjCtx->getObj();
869 AssertPtr(pObj);
870
871 RTCString strPath = pObj->GetDestPath();
872 if (strPath.isEmpty())
873 return VERR_INVALID_PARAMETER;
874 if (strPath.length() >= RTPATH_MAX) /* Note: Maximum is RTPATH_MAX on guest side. */
875 return VERR_BUFFER_OVERFLOW;
876
877 LogRel2(("DnD: Transferring host directory to guest: %s\n", strPath.c_str()));
878
879 pMsg->setType(HOST_DND_HG_SND_DIR);
880 if (mDataBase.m_uProtocolVersion >= 3)
881 pMsg->setNextUInt32(0); /** @todo ContextID not used yet. */
882 pMsg->setNextString(strPath.c_str()); /* path */
883 pMsg->setNextUInt32((uint32_t)(strPath.length() + 1)); /* path length (maximum is RTPATH_MAX on guest side). */
884 pMsg->setNextUInt32(pObj->GetMode()); /* mode */
885
886 return VINF_SUCCESS;
887}
888
889int GuestDnDTarget::i_sendFile(PSENDDATACTX pCtx, GuestDnDURIObjCtx *pObjCtx, GuestDnDMsg *pMsg)
890{
891 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
892 AssertPtrReturn(pObjCtx, VERR_INVALID_POINTER);
893 AssertPtrReturn(pMsg, VERR_INVALID_POINTER);
894
895 DnDURIObject *pObj = pObjCtx->getObj();
896 AssertPtr(pObj);
897
898 RTCString strPathSrc = pObj->GetSourcePath();
899 if (strPathSrc.isEmpty())
900 return VERR_INVALID_PARAMETER;
901
902 int rc = VINF_SUCCESS;
903
904 LogFlowFunc(("Sending file with %RU32 bytes buffer, using protocol v%RU32 ...\n",
905 mData.mcbBlockSize, mDataBase.m_uProtocolVersion));
906 LogFlowFunc(("strPathSrc=%s, fIsOpen=%RTbool, cbSize=%RU64\n", strPathSrc.c_str(), pObj->IsOpen(), pObj->GetSize()));
907
908 if (!pObj->IsOpen())
909 {
910 LogRel2(("DnD: Opening host file for transferring to guest: %s\n", strPathSrc.c_str()));
911 rc = pObj->OpenEx(strPathSrc, DnDURIObject::File, DnDURIObject::Source,
912 RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE, 0 /* fFlags */);
913 if (RT_FAILURE(rc))
914 LogRel(("DnD: Error opening host file '%s', rc=%Rrc\n", strPathSrc.c_str(), rc));
915 }
916
917 bool fSendData = false;
918 if (RT_SUCCESS(rc))
919 {
920 if (mDataBase.m_uProtocolVersion >= 2)
921 {
922 uint32_t fState = pObjCtx->getState();
923 if (!(fState & DND_OBJCTX_STATE_HAS_HDR))
924 {
925 /*
926 * Since protocol v2 the file header and the actual file contents are
927 * separate messages, so send the file header first.
928 * The just registered callback will be called by the guest afterwards.
929 */
930 pMsg->setType(HOST_DND_HG_SND_FILE_HDR);
931 pMsg->setNextUInt32(0); /** @todo ContextID not used yet. */
932 pMsg->setNextString(pObj->GetDestPath().c_str()); /* pvName */
933 pMsg->setNextUInt32((uint32_t)(pObj->GetDestPath().length() + 1)); /* cbName */
934 pMsg->setNextUInt32(0); /* uFlags */
935 pMsg->setNextUInt32(pObj->GetMode()); /* fMode */
936 pMsg->setNextUInt64(pObj->GetSize()); /* uSize */
937
938 LogFlowFunc(("Sending file header ...\n"));
939 LogRel2(("DnD: Transferring host file to guest: %s (%RU64 bytes, mode 0x%x)\n",
940 strPathSrc.c_str(), pObj->GetSize(), pObj->GetMode()));
941
942 /** @todo Set progress object title to current file being transferred? */
943
944 pObjCtx->setState(fState | DND_OBJCTX_STATE_HAS_HDR);
945 }
946 else
947 {
948 /* File header was sent, so only send the actual file data. */
949 fSendData = true;
950 }
951 }
952 else /* Protocol v1. */
953 {
954 /* Always send the file data, every time. */
955 fSendData = true;
956 }
957 }
958
959 if ( RT_SUCCESS(rc)
960 && fSendData)
961 {
962 rc = i_sendFileData(pCtx, pObjCtx, pMsg);
963 }
964
965 LogFlowFuncLeaveRC(rc);
966 return rc;
967}
968
969int GuestDnDTarget::i_sendFileData(PSENDDATACTX pCtx, GuestDnDURIObjCtx *pObjCtx, GuestDnDMsg *pMsg)
970{
971 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
972 AssertPtrReturn(pObjCtx, VERR_INVALID_POINTER);
973 AssertPtrReturn(pMsg, VERR_INVALID_POINTER);
974
975 DnDURIObject *pObj = pObjCtx->getObj();
976 AssertPtr(pObj);
977
978 GuestDnDResponse *pResp = pCtx->mpResp;
979 AssertPtr(pResp);
980
981 /** @todo Don't allow concurrent reads per context! */
982
983 /*
984 * Start sending stuff.
985 */
986
987 /* Set the message type. */
988 pMsg->setType(HOST_DND_HG_SND_FILE_DATA);
989
990 /* Protocol version 1 sends the file path *every* time with a new file chunk.
991 * In protocol version 2 we only do this once with HOST_DND_HG_SND_FILE_HDR. */
992 if (mDataBase.m_uProtocolVersion <= 1)
993 {
994 pMsg->setNextString(pObj->GetDestPath().c_str()); /* pvName */
995 pMsg->setNextUInt32((uint32_t)(pObj->GetDestPath().length() + 1)); /* cbName */
996 }
997 else if (mDataBase.m_uProtocolVersion >= 2)
998 {
999 pMsg->setNextUInt32(0); /** @todo ContextID not used yet. */
1000 }
1001
1002 uint32_t cbRead = 0;
1003
1004 int rc = pObj->Read(pCtx->mURI.getBufferMutable(), pCtx->mURI.getBufferSize(), &cbRead);
1005 if (RT_SUCCESS(rc))
1006 {
1007 pCtx->mData.addProcessed(cbRead);
1008 LogFlowFunc(("cbBufSize=%zu, cbRead=%RU32\n", pCtx->mURI.getBufferSize(), cbRead));
1009
1010 if (mDataBase.m_uProtocolVersion <= 1)
1011 {
1012 pMsg->setNextPointer(pCtx->mURI.getBufferMutable(), cbRead); /* pvData */
1013 pMsg->setNextUInt32(cbRead); /* cbData */
1014 pMsg->setNextUInt32(pObj->GetMode()); /* fMode */
1015 }
1016 else /* Protocol v2 and up. */
1017 {
1018 pMsg->setNextPointer(pCtx->mURI.getBufferMutable(), cbRead); /* pvData */
1019 pMsg->setNextUInt32(cbRead); /* cbData */
1020
1021 if (mDataBase.m_uProtocolVersion >= 3)
1022 {
1023 /** @todo Calculate checksum. */
1024 pMsg->setNextPointer(NULL, 0); /* pvChecksum */
1025 pMsg->setNextUInt32(0); /* cbChecksum */
1026 }
1027 }
1028
1029 if (pObj->IsComplete()) /* Done reading? */
1030 {
1031 LogRel2(("DnD: File transfer to guest complete: %s\n", pObj->GetSourcePath().c_str()));
1032 LogFlowFunc(("File '%s' complete\n", pObj->GetSourcePath().c_str()));
1033
1034 /* DnDURIObject::Read() returns VINF_EOF when finished reading the entire fire,
1035 * but we don't want this here -- so just override this with VINF_SUCCESS. */
1036 rc = VINF_SUCCESS;
1037 }
1038 }
1039
1040 LogFlowFuncLeaveRC(rc);
1041 return rc;
1042}
1043
1044/* static */
1045DECLCALLBACK(int) GuestDnDTarget::i_sendURIDataCallback(uint32_t uMsg, void *pvParms, size_t cbParms, void *pvUser)
1046{
1047 PSENDDATACTX pCtx = (PSENDDATACTX)pvUser;
1048 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1049
1050 GuestDnDTarget *pThis = pCtx->mpTarget;
1051 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
1052
1053 LogFlowFunc(("pThis=%p, uMsg=%RU32\n", pThis, uMsg));
1054
1055 int rc = VINF_SUCCESS;
1056 int rcGuest = VINF_SUCCESS; /* Contains error code from guest in case of VERR_GSTDND_GUEST_ERROR. */
1057 bool fNotify = false;
1058
1059 switch (uMsg)
1060 {
1061 case GUEST_DND_CONNECT:
1062 /* Nothing to do here (yet). */
1063 break;
1064
1065 case GUEST_DND_DISCONNECT:
1066 rc = VERR_CANCELLED;
1067 break;
1068
1069 case GUEST_DND_GET_NEXT_HOST_MSG:
1070 {
1071 PVBOXDNDCBHGGETNEXTHOSTMSG pCBData = reinterpret_cast<PVBOXDNDCBHGGETNEXTHOSTMSG>(pvParms);
1072 AssertPtr(pCBData);
1073 AssertReturn(sizeof(VBOXDNDCBHGGETNEXTHOSTMSG) == cbParms, VERR_INVALID_PARAMETER);
1074 AssertReturn(CB_MAGIC_DND_HG_GET_NEXT_HOST_MSG == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1075
1076 try
1077 {
1078 GuestDnDMsg *pMsg = new GuestDnDMsg();
1079
1080 rc = pThis->i_sendURIDataLoop(pCtx, pMsg);
1081 if (rc == VINF_EOF) /* Transfer complete? */
1082 {
1083 LogFlowFunc(("Last URI item processed, bailing out\n"));
1084 }
1085 else if (RT_SUCCESS(rc))
1086 {
1087 rc = pThis->msgQueueAdd(pMsg);
1088 if (RT_SUCCESS(rc)) /* Return message type & required parameter count to the guest. */
1089 {
1090 LogFlowFunc(("GUEST_DND_GET_NEXT_HOST_MSG -> %RU32 (%RU32 params)\n", pMsg->getType(), pMsg->getCount()));
1091 pCBData->uMsg = pMsg->getType();
1092 pCBData->cParms = pMsg->getCount();
1093 }
1094 }
1095
1096 if ( RT_FAILURE(rc)
1097 || rc == VINF_EOF) /* Transfer complete? */
1098 {
1099 delete pMsg;
1100 pMsg = NULL;
1101 }
1102 }
1103 catch(std::bad_alloc & /*e*/)
1104 {
1105 rc = VERR_NO_MEMORY;
1106 }
1107 break;
1108 }
1109 case GUEST_DND_GH_EVT_ERROR:
1110 {
1111 PVBOXDNDCBEVTERRORDATA pCBData = reinterpret_cast<PVBOXDNDCBEVTERRORDATA>(pvParms);
1112 AssertPtr(pCBData);
1113 AssertReturn(sizeof(VBOXDNDCBEVTERRORDATA) == cbParms, VERR_INVALID_PARAMETER);
1114 AssertReturn(CB_MAGIC_DND_GH_EVT_ERROR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1115
1116 pCtx->mpResp->reset();
1117
1118 if (RT_SUCCESS(pCBData->rc))
1119 {
1120 AssertMsgFailed(("Guest has sent an error event but did not specify an actual error code\n"));
1121 pCBData->rc = VERR_GENERAL_FAILURE; /* Make sure some error is set. */
1122 }
1123
1124 rc = pCtx->mpResp->setProgress(100, DND_PROGRESS_ERROR, pCBData->rc,
1125 GuestDnDTarget::i_guestErrorToString(pCBData->rc));
1126 if (RT_SUCCESS(rc))
1127 {
1128 rc = VERR_GSTDND_GUEST_ERROR;
1129 rcGuest = pCBData->rc;
1130 }
1131 break;
1132 }
1133 case HOST_DND_HG_SND_DIR:
1134 case HOST_DND_HG_SND_FILE_HDR:
1135 case HOST_DND_HG_SND_FILE_DATA:
1136 {
1137 PVBOXDNDCBHGGETNEXTHOSTMSGDATA pCBData
1138 = reinterpret_cast<PVBOXDNDCBHGGETNEXTHOSTMSGDATA>(pvParms);
1139 AssertPtr(pCBData);
1140 AssertReturn(sizeof(VBOXDNDCBHGGETNEXTHOSTMSGDATA) == cbParms, VERR_INVALID_PARAMETER);
1141
1142 LogFlowFunc(("pCBData->uMsg=%RU32, paParms=%p, cParms=%RU32\n", pCBData->uMsg, pCBData->paParms, pCBData->cParms));
1143
1144 GuestDnDMsg *pMsg = pThis->msgQueueGetNext();
1145 if (pMsg)
1146 {
1147 /*
1148 * Sanity checks.
1149 */
1150 if ( pCBData->uMsg != uMsg
1151 || pCBData->paParms == NULL
1152 || pCBData->cParms != pMsg->getCount())
1153 {
1154 LogFlowFunc(("Current message does not match:\n"));
1155 LogFlowFunc(("\tCallback: uMsg=%RU32, cParms=%RU32, paParms=%p\n",
1156 pCBData->uMsg, pCBData->cParms, pCBData->paParms));
1157 LogFlowFunc(("\t Next: uMsg=%RU32, cParms=%RU32\n", pMsg->getType(), pMsg->getCount()));
1158
1159 /* Start over. */
1160 pThis->msgQueueClear();
1161
1162 rc = VERR_INVALID_PARAMETER;
1163 }
1164
1165 if (RT_SUCCESS(rc))
1166 {
1167 LogFlowFunc(("Returning uMsg=%RU32\n", uMsg));
1168 rc = HGCM::Message::copyParms(pMsg->getCount(), pMsg->getParms(), pCBData->paParms);
1169 if (RT_SUCCESS(rc))
1170 {
1171 pCBData->cParms = pMsg->getCount();
1172 pThis->msgQueueRemoveNext();
1173 }
1174 else
1175 LogFlowFunc(("Copying parameters failed with rc=%Rrc\n", rc));
1176 }
1177 }
1178 else
1179 rc = VERR_NO_DATA;
1180
1181 LogFlowFunc(("Processing next message ended with rc=%Rrc\n", rc));
1182 break;
1183 }
1184 default:
1185 rc = VERR_NOT_SUPPORTED;
1186 break;
1187 }
1188
1189 int rcToGuest = VINF_SUCCESS; /* Status which will be sent back to the guest. */
1190
1191 /*
1192 * Resolve errors.
1193 */
1194 switch (rc)
1195 {
1196 case VINF_SUCCESS:
1197 break;
1198
1199 case VINF_EOF:
1200 {
1201 LogRel2(("DnD: Transfer to guest complete\n"));
1202
1203 /* Complete operation on host side. */
1204 fNotify = true;
1205
1206 /* The guest expects VERR_NO_DATA if the transfer is complete. */
1207 rcToGuest = VERR_NO_DATA;
1208 break;
1209 }
1210
1211 case VERR_GSTDND_GUEST_ERROR:
1212 {
1213 LogRel(("DnD: Guest reported error %Rrc, aborting transfer to guest\n", rcGuest));
1214 break;
1215 }
1216
1217 case VERR_CANCELLED:
1218 {
1219 LogRel2(("DnD: Transfer to guest canceled\n"));
1220 rcToGuest = VERR_CANCELLED; /* Also cancel on guest side. */
1221 break;
1222 }
1223
1224 default:
1225 {
1226 LogRel(("DnD: Host error %Rrc occurred, aborting transfer to guest\n", rc));
1227 rcToGuest = VERR_CANCELLED; /* Also cancel on guest side. */
1228 break;
1229 }
1230 }
1231
1232 if (RT_FAILURE(rc))
1233 {
1234 /* Unregister this callback. */
1235 AssertPtr(pCtx->mpResp);
1236 int rc2 = pCtx->mpResp->setCallback(uMsg, NULL /* PFNGUESTDNDCALLBACK */);
1237 AssertRC(rc2);
1238
1239 /* Let the waiter(s) know. */
1240 fNotify = true;
1241 }
1242
1243 LogFlowFunc(("fNotify=%RTbool, rc=%Rrc, rcToGuest=%Rrc\n", fNotify, rc, rcToGuest));
1244
1245 if (fNotify)
1246 {
1247 int rc2 = pCtx->mCBEvent.Notify(rc); /** @todo Also pass guest error back? */
1248 AssertRC(rc2);
1249 }
1250
1251 LogFlowFuncLeaveRC(rc);
1252 return rcToGuest; /* Tell the guest. */
1253}
1254
1255int GuestDnDTarget::i_sendURIData(PSENDDATACTX pCtx, RTMSINTERVAL msTimeout)
1256{
1257 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1258 AssertPtr(pCtx->mpResp);
1259
1260#define REGISTER_CALLBACK(x) \
1261 rc = pCtx->mpResp->setCallback(x, i_sendURIDataCallback, pCtx); \
1262 if (RT_FAILURE(rc)) \
1263 return rc;
1264
1265#define UNREGISTER_CALLBACK(x) \
1266 { \
1267 int rc2 = pCtx->mpResp->setCallback(x, NULL); \
1268 AssertRC(rc2); \
1269 }
1270
1271 int rc = pCtx->mURI.init(mData.mcbBlockSize);
1272 if (RT_FAILURE(rc))
1273 return rc;
1274
1275 rc = pCtx->mCBEvent.Reset();
1276 if (RT_FAILURE(rc))
1277 return rc;
1278
1279 /*
1280 * Register callbacks.
1281 */
1282 /* Guest callbacks. */
1283 REGISTER_CALLBACK(GUEST_DND_CONNECT);
1284 REGISTER_CALLBACK(GUEST_DND_DISCONNECT);
1285 REGISTER_CALLBACK(GUEST_DND_GET_NEXT_HOST_MSG);
1286 REGISTER_CALLBACK(GUEST_DND_GH_EVT_ERROR);
1287 /* Host callbacks. */
1288 REGISTER_CALLBACK(HOST_DND_HG_SND_DIR);
1289 if (mDataBase.m_uProtocolVersion >= 2)
1290 REGISTER_CALLBACK(HOST_DND_HG_SND_FILE_HDR);
1291 REGISTER_CALLBACK(HOST_DND_HG_SND_FILE_DATA);
1292
1293 do
1294 {
1295 /*
1296 * Extract URI list from current meta data.
1297 */
1298 GuestDnDData *pData = &pCtx->mData;
1299 GuestDnDURIData *pURI = &pCtx->mURI;
1300
1301 rc = pURI->fromLocalMetaData(pData->getMeta());
1302 if (RT_FAILURE(rc))
1303 break;
1304
1305 LogFlowFunc(("URI root objects: %zu, total bytes (raw data to transfer): %zu\n",
1306 pURI->getURIList().RootCount(), pURI->getURIList().TotalBytes()));
1307
1308 /*
1309 * Set the new meta data with the URI list in it.
1310 */
1311 rc = pData->getMeta().fromURIList(pURI->getURIList());
1312 if (RT_FAILURE(rc))
1313 break;
1314
1315 /*
1316 * Set the estimated data sizes we are going to send.
1317 * The total size also contains the meta data size.
1318 */
1319 const uint32_t cbMeta = pData->getMeta().getSize();
1320 pData->setEstimatedSize(pURI->getURIList().TotalBytes() + cbMeta /* cbTotal */,
1321 cbMeta /* cbMeta */);
1322
1323 /*
1324 * Set the meta format.
1325 */
1326 void *pvFmt = (void *)pCtx->mFmtReq.c_str();
1327 uint32_t cbFmt = pCtx->mFmtReq.length() + 1; /* Include terminating zero. */
1328
1329 pData->setFmt(pvFmt, cbFmt);
1330
1331 /*
1332 * The first message always is the data header. The meta data itself then follows
1333 * and *only* contains the root elements of an URI list.
1334 *
1335 * After the meta data we generate the messages required to send the
1336 * file/directory data itself.
1337 *
1338 * Note: Protocol < v3 use the first data message to tell what's being sent.
1339 */
1340 GuestDnDMsg Msg;
1341
1342 /*
1343 * Send the data header first.
1344 */
1345 if (mDataBase.m_uProtocolVersion >= 3)
1346 rc = i_sendDataHeader(pCtx, pData, &pCtx->mURI);
1347
1348 /*
1349 * Send the (meta) data body.
1350 */
1351 if (RT_SUCCESS(rc))
1352 rc = i_sendDataBody(pCtx, pData);
1353
1354 if (RT_SUCCESS(rc))
1355 {
1356 rc = waitForEvent(&pCtx->mCBEvent, pCtx->mpResp, msTimeout);
1357 if (RT_SUCCESS(rc))
1358 pCtx->mpResp->setProgress(100, DND_PROGRESS_COMPLETE, VINF_SUCCESS);
1359 }
1360
1361 } while (0);
1362
1363 /*
1364 * Unregister callbacks.
1365 */
1366 /* Guest callbacks. */
1367 UNREGISTER_CALLBACK(GUEST_DND_CONNECT);
1368 UNREGISTER_CALLBACK(GUEST_DND_DISCONNECT);
1369 UNREGISTER_CALLBACK(GUEST_DND_GET_NEXT_HOST_MSG);
1370 UNREGISTER_CALLBACK(GUEST_DND_GH_EVT_ERROR);
1371 /* Host callbacks. */
1372 UNREGISTER_CALLBACK(HOST_DND_HG_SND_DIR);
1373 if (mDataBase.m_uProtocolVersion >= 2)
1374 UNREGISTER_CALLBACK(HOST_DND_HG_SND_FILE_HDR);
1375 UNREGISTER_CALLBACK(HOST_DND_HG_SND_FILE_DATA);
1376
1377#undef REGISTER_CALLBACK
1378#undef UNREGISTER_CALLBACK
1379
1380 if (RT_FAILURE(rc))
1381 {
1382 if (rc == VERR_CANCELLED)
1383 pCtx->mpResp->setProgress(100, DND_PROGRESS_CANCELLED, VINF_SUCCESS);
1384 else if (rc != VERR_GSTDND_GUEST_ERROR) /* Guest-side error are already handled in the callback. */
1385 pCtx->mpResp->setProgress(100, DND_PROGRESS_ERROR, rc,
1386 GuestDnDTarget::i_hostErrorToString(rc));
1387 }
1388
1389 /*
1390 * Now that we've cleaned up tell the guest side to cancel.
1391 * This does not imply we're waiting for the guest to react, as the
1392 * host side never must depend on anything from the guest.
1393 */
1394 if (rc == VERR_CANCELLED)
1395 {
1396 int rc2 = sendCancel();
1397 AssertRC(rc2);
1398 }
1399
1400 LogFlowFuncLeaveRC(rc);
1401 return rc;
1402}
1403
1404int GuestDnDTarget::i_sendURIDataLoop(PSENDDATACTX pCtx, GuestDnDMsg *pMsg)
1405{
1406 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1407 AssertPtrReturn(pMsg, VERR_INVALID_POINTER);
1408
1409 int rc = updateProgress(&pCtx->mData, pCtx->mpResp);
1410 AssertRC(rc);
1411
1412 if ( pCtx->mData.isComplete()
1413 && pCtx->mURI.isComplete())
1414 {
1415 return VINF_EOF;
1416 }
1417
1418 GuestDnDURIObjCtx &objCtx = pCtx->mURI.getObjCurrent();
1419 if (!objCtx.isValid())
1420 return VERR_WRONG_ORDER;
1421
1422 DnDURIObject *pCurObj = objCtx.getObj();
1423 AssertPtr(pCurObj);
1424
1425 uint32_t fMode = pCurObj->GetMode();
1426 LogRel3(("DnD: Processing: srcPath=%s, dstPath=%s, fMode=0x%x, cbSize=%RU32, fIsDir=%RTbool, fIsFile=%RTbool\n",
1427 pCurObj->GetSourcePath().c_str(), pCurObj->GetDestPath().c_str(),
1428 fMode, pCurObj->GetSize(),
1429 RTFS_IS_DIRECTORY(fMode), RTFS_IS_FILE(fMode)));
1430
1431 if (RTFS_IS_DIRECTORY(fMode))
1432 {
1433 rc = i_sendDirectory(pCtx, &objCtx, pMsg);
1434 }
1435 else if (RTFS_IS_FILE(fMode))
1436 {
1437 rc = i_sendFile(pCtx, &objCtx, pMsg);
1438 }
1439 else
1440 {
1441 AssertMsgFailed(("fMode=0x%x is not supported for srcPath=%s, dstPath=%s\n",
1442 fMode, pCurObj->GetSourcePath().c_str(), pCurObj->GetDestPath().c_str()));
1443 rc = VERR_NOT_SUPPORTED;
1444 }
1445
1446 bool fRemove = false; /* Remove current entry? */
1447 if ( pCurObj->IsComplete()
1448 || RT_FAILURE(rc))
1449 {
1450 fRemove = true;
1451 }
1452
1453 if (fRemove)
1454 {
1455 LogFlowFunc(("Removing \"%s\" from list, rc=%Rrc\n", pCurObj->GetSourcePath().c_str(), rc));
1456 pCtx->mURI.removeObjCurrent();
1457 }
1458
1459 LogFlowFuncLeaveRC(rc);
1460 return rc;
1461}
1462
1463int GuestDnDTarget::i_sendRawData(PSENDDATACTX pCtx, RTMSINTERVAL msTimeout)
1464{
1465 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1466 NOREF(msTimeout);
1467
1468 GuestDnD *pInst = GuestDnDInst();
1469 AssertPtr(pInst);
1470
1471 GuestDnDData *pData = &pCtx->mData;
1472
1473 /** @todo At the moment we only allow sending up to 64K raw data.
1474 * For protocol v1+v2: Fix this by using HOST_DND_HG_SND_MORE_DATA.
1475 * For protocol v3 : Send another HOST_DND_HG_SND_DATA message. */
1476 if (!pData->getMeta().getSize())
1477 return VINF_SUCCESS;
1478
1479 int rc = VINF_SUCCESS;
1480
1481 /*
1482 * Send the data header first.
1483 */
1484 if (mDataBase.m_uProtocolVersion >= 3)
1485 rc = i_sendDataHeader(pCtx, pData, NULL /* URI list */);
1486
1487 /*
1488 * Send the (meta) data body.
1489 */
1490 if (RT_SUCCESS(rc))
1491 rc = i_sendDataBody(pCtx, pData);
1492
1493 int rc2;
1494 if (RT_FAILURE(rc))
1495 rc2 = pCtx->mpResp->setProgress(100, DND_PROGRESS_ERROR, rc,
1496 GuestDnDTarget::i_hostErrorToString(rc));
1497 else
1498 rc2 = pCtx->mpResp->setProgress(100, DND_PROGRESS_COMPLETE, rc);
1499 AssertRC(rc2);
1500
1501 LogFlowFuncLeaveRC(rc);
1502 return rc;
1503}
1504
1505HRESULT GuestDnDTarget::cancel(BOOL *aVeto)
1506{
1507#if !defined(VBOX_WITH_DRAG_AND_DROP)
1508 ReturnComNotImplemented();
1509#else /* VBOX_WITH_DRAG_AND_DROP */
1510
1511 int rc = i_cancelOperation();
1512
1513 if (aVeto)
1514 *aVeto = FALSE; /** @todo */
1515
1516 HRESULT hr = RT_SUCCESS(rc) ? S_OK : VBOX_E_IPRT_ERROR;
1517
1518 LogFlowFunc(("hr=%Rhrc\n", hr));
1519 return hr;
1520#endif /* VBOX_WITH_DRAG_AND_DROP */
1521}
1522
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