VirtualBox

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

Last change on this file since 85019 was 85019, checked in by vboxsync, 4 years ago

DnD/Main: Don't use RT_BZERO on non-POD types.

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