VirtualBox

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

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

DnD: Lots of documentation.

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