VirtualBox

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

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

DnD/Main: Don't mix rc and HRESULT in GuestDnD[Source|Target]::init() functions.

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