VirtualBox

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

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

DnD/Main: Renamed GuestDnDMsg::setNextXXX() -> GuestDnDMsg::appendXXX().

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