VirtualBox

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

Last change on this file since 78018 was 76891, checked in by vboxsync, 6 years ago

DnD: Renamed HOST_DND_HG_EVT_CANCEL -> HOST_DND_HG_CANCEL, fixes for host-side cancellation.

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