VirtualBox

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

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

Reverted r139888 to change commit message.

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