VirtualBox

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

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

DnD/Main: Cleaned up error path handling a bit.

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