VirtualBox

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

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

DnD/Main: Renaming; use size_t where appropriate.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 53.3 KB
Line 
1/* $Id: GuestDnDTargetImpl.cpp 85402 2020-07-21 15:10:32Z 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 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 GuestDnDSendDataTask : public GuestDnDTargetTask
77{
78public:
79
80 GuestDnDSendDataTask(GuestDnDTarget *pTarget, GuestDnDSendCtx *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 ~GuestDnDSendDataTask(void)
93 {
94 if (mpCtx)
95 {
96 delete mpCtx;
97 mpCtx = NULL;
98 }
99 }
100
101
102 GuestDnDSendCtx *getCtx(void) { return mpCtx; }
103
104protected:
105
106 /** Pointer to send data context. */
107 GuestDnDSendCtx *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(&m_DataBase.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 (m_DataBase.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 (m_DataBase.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 (m_DataBase.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 (m_DataBase.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/**
566 * Thread handler function for sending data to the guest.
567 *
568 * @param pTask Thread task this handler is associated with.
569 */
570/* static */
571void GuestDnDTarget::i_sendDataThreadTask(GuestDnDSendDataTask *pTask)
572{
573 LogFlowFunc(("pTask=%p\n", pTask));
574 AssertPtrReturnVoid(pTask);
575
576 const ComObjPtr<GuestDnDTarget> pThis(pTask->getTarget());
577 Assert(!pThis.isNull());
578
579 AutoCaller autoCaller(pThis);
580 if (FAILED(autoCaller.rc()))
581 return;
582
583 int vrc = pThis->i_sendData(pTask->getCtx(), RT_INDEFINITE_WAIT /* msTimeout */);
584 if (RT_FAILURE(vrc)) /* In case we missed some error handling within i_sendData(). */
585 {
586 AssertFailed();
587 LogRel(("DnD: Sending data to guest failed with %Rrc\n", vrc));
588 }
589
590 AutoWriteLock alock(pThis COMMA_LOCKVAL_SRC_POS);
591
592 Assert(pThis->m_DataBase.cTransfersPending);
593 if (pThis->m_DataBase.cTransfersPending)
594 pThis->m_DataBase.cTransfersPending--;
595
596 LogFlowFunc(("pTarget=%p, vrc=%Rrc (ignored)\n", (GuestDnDTarget *)pThis, vrc));
597}
598
599/**
600 * Initiates a data transfer from the host to the guest.
601 *
602 * The source is the host, whereas the target is the guest.
603 *
604 * @return HRESULT
605 * @param aScreenId Screen ID where this data transfer was initiated from.
606 * @param aFormat Format of data to send. MIME-style.
607 * @param aData Actual data to send.
608 * @param aProgress Where to return the progress object on success.
609 */
610HRESULT GuestDnDTarget::sendData(ULONG aScreenId, const com::Utf8Str &aFormat, const std::vector<BYTE> &aData,
611 ComPtr<IProgress> &aProgress)
612{
613#if !defined(VBOX_WITH_DRAG_AND_DROP)
614 ReturnComNotImplemented();
615#else /* VBOX_WITH_DRAG_AND_DROP */
616
617 AutoCaller autoCaller(this);
618 if (FAILED(autoCaller.rc())) return autoCaller.rc();
619
620 /* Input validation. */
621 if (RT_UNLIKELY((aFormat.c_str()) == NULL || *(aFormat.c_str()) == '\0'))
622 return setError(E_INVALIDARG, tr("No data format specified"));
623 if (RT_UNLIKELY(!aData.size()))
624 return setError(E_INVALIDARG, tr("No data to send specified"));
625
626 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
627
628 /* At the moment we only support one transfer at a time. */
629 if (m_DataBase.cTransfersPending)
630 return setError(E_INVALIDARG, tr("Another drop operation already is in progress"));
631
632 /* Ditto. */
633 GuestDnDResponse *pResp = GUESTDNDINST()->response();
634 AssertPtr(pResp);
635
636 HRESULT hr = pResp->resetProgress(m_pGuest);
637 if (FAILED(hr))
638 return hr;
639
640 GuestDnDSendDataTask *pTask = NULL;
641 GuestDnDSendCtx *pSendCtx = NULL;
642
643 try
644 {
645 /* pSendCtx is passed into SendDataTask where one is deleted in destructor. */
646 pSendCtx = new GuestDnDSendCtx();
647 pSendCtx->pTarget = this;
648 pSendCtx->pResp = pResp;
649 pSendCtx->uScreenID = aScreenId;
650 pSendCtx->strFmtReq = aFormat;
651
652 pSendCtx->Meta.add(aData);
653
654 /* pTask is responsible for deletion of pSendCtx after creating */
655 pTask = new GuestDnDSendDataTask(this, pSendCtx);
656 if (!pTask->isOk())
657 {
658 delete pTask;
659 LogRel(("DnD: Could not create SendDataTask object\n"));
660 throw hr = E_FAIL;
661 }
662
663 /* This function delete pTask in case of exceptions,
664 * so there is no need in the call of delete operator. */
665 hr = pTask->createThreadWithType(RTTHREADTYPE_MAIN_WORKER);
666 pTask = NULL; /* Note: pTask is now owned by the worker thread. */
667 }
668 catch (std::bad_alloc &)
669 {
670 hr = setError(E_OUTOFMEMORY);
671 }
672 catch (...)
673 {
674 LogRel(("DnD: Could not create thread for data sending task\n"));
675 hr = E_FAIL;
676 }
677
678 if (SUCCEEDED(hr))
679 {
680 m_DataBase.cTransfersPending++;
681
682 hr = pResp->queryProgressTo(aProgress.asOutParam());
683 ComAssertComRC(hr);
684 }
685 else
686 hr = setError(hr, tr("Starting thread for GuestDnDTarget::i_sendDataThread (%Rhrc)"), hr);
687
688 LogFlowFunc(("Returning hr=%Rhrc\n", hr));
689 return hr;
690#endif /* VBOX_WITH_DRAG_AND_DROP */
691}
692
693/* static */
694Utf8Str GuestDnDTarget::i_guestErrorToString(int guestRc)
695{
696 Utf8Str strError;
697
698 switch (guestRc)
699 {
700 case VERR_ACCESS_DENIED:
701 strError += Utf8StrFmt(tr("For one or more guest files or directories selected for transferring to the host your guest "
702 "user does not have the appropriate access rights for. Please make sure that all selected "
703 "elements can be accessed and that your guest user has the appropriate rights"));
704 break;
705
706 case VERR_NOT_FOUND:
707 /* Should not happen due to file locking on the guest, but anyway ... */
708 strError += Utf8StrFmt(tr("One or more guest files or directories selected for transferring to the host were not"
709 "found on the guest anymore. This can be the case if the guest files were moved and/or"
710 "altered while the drag and drop operation was in progress"));
711 break;
712
713 case VERR_SHARING_VIOLATION:
714 strError += Utf8StrFmt(tr("One or more guest files or directories selected for transferring to the host were locked. "
715 "Please make sure that all selected elements can be accessed and that your guest user has "
716 "the appropriate rights"));
717 break;
718
719 case VERR_TIMEOUT:
720 strError += Utf8StrFmt(tr("The guest was not able to process the drag and drop data within time"));
721 break;
722
723 default:
724 strError += Utf8StrFmt(tr("Drag and drop error from guest (%Rrc)"), guestRc);
725 break;
726 }
727
728 return strError;
729}
730
731/* static */
732Utf8Str GuestDnDTarget::i_hostErrorToString(int hostRc)
733{
734 Utf8Str strError;
735
736 switch (hostRc)
737 {
738 case VERR_ACCESS_DENIED:
739 strError += Utf8StrFmt(tr("For one or more host files or directories selected for transferring to the guest your host "
740 "user does not have the appropriate access rights for. Please make sure that all selected "
741 "elements can be accessed and that your host user has the appropriate rights."));
742 break;
743
744 case VERR_NOT_FOUND:
745 /* Should not happen due to file locking on the host, but anyway ... */
746 strError += Utf8StrFmt(tr("One or more host files or directories selected for transferring to the host were not"
747 "found on the host anymore. This can be the case if the host files were moved and/or"
748 "altered while the drag and drop operation was in progress."));
749 break;
750
751 case VERR_SHARING_VIOLATION:
752 strError += Utf8StrFmt(tr("One or more host files or directories selected for transferring to the guest were locked. "
753 "Please make sure that all selected elements can be accessed and that your host user has "
754 "the appropriate rights."));
755 break;
756
757 default:
758 strError += Utf8StrFmt(tr("Drag and drop error from host (%Rrc)"), hostRc);
759 break;
760 }
761
762 return strError;
763}
764
765/**
766 * Main function for sending DnD host data to the guest.
767 *
768 * @returns VBox status code.
769 * @param pCtx Send context to use.
770 * @param msTimeout Timeout (in ms) to wait for getting the data sent.
771 */
772int GuestDnDTarget::i_sendData(GuestDnDSendCtx *pCtx, RTMSINTERVAL msTimeout)
773{
774 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
775
776 /* Is this context already in sending state? */
777 if (ASMAtomicReadBool(&pCtx->fIsActive))
778 return VERR_WRONG_ORDER;
779 ASMAtomicWriteBool(&pCtx->fIsActive, true);
780
781 /* Clear all remaining outgoing messages. */
782 m_DataBase.lstMsgOut.clear();
783
784 /**
785 * Do we need to build up a file tree?
786 * Note: The decision whether we need to build up a file tree and sending
787 * actual file data only depends on the actual formats offered by this target.
788 * If the guest does not want a transfer list ("text/uri-list") but text ("TEXT" and
789 * friends) instead, still send the data over to the guest -- the file as such still
790 * is needed on the guest in this case, as the guest then just wants a simple path
791 * instead of a transfer list (pointing to a file on the guest itself).
792 *
793 ** @todo Support more than one format; add a format<->function handler concept. Later. */
794 int rc;
795 const bool fHasURIList = std::find(m_lstFmtOffered.begin(),
796 m_lstFmtOffered.end(), "text/uri-list") != m_lstFmtOffered.end();
797 if (fHasURIList)
798 {
799 rc = i_sendTransferData(pCtx, msTimeout);
800 }
801 else
802 {
803 rc = i_sendRawData(pCtx, msTimeout);
804 }
805
806 ASMAtomicWriteBool(&pCtx->fIsActive, false);
807
808 LogFlowFuncLeaveRC(rc);
809 return rc;
810}
811
812/**
813 * Sends the common meta data body to the guest.
814 *
815 * @returns VBox status code.
816 * @param pCtx Send context to use.
817 */
818int GuestDnDTarget::i_sendMetaDataBody(GuestDnDSendCtx *pCtx)
819{
820 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
821
822 /** @todo Add support for multiple HOST_DND_HG_SND_DATA messages in case of more than 64K data! */
823 if (pCtx->Meta.cbData > _64K)
824 return VERR_NOT_IMPLEMENTED;
825
826 const uint32_t cbFmt = (uint32_t)pCtx->Meta.strFmt.length() + 1; /* Include terminator. */
827
828 LogFlowFunc(("cbFmt=%RU32, cbMeta=%RU32\n", cbFmt, pCtx->Meta.cbData));
829
830 GuestDnDMsg Msg;
831 Msg.setType(HOST_DND_HG_SND_DATA);
832 if (m_DataBase.uProtocolVersion < 3)
833 {
834 Msg.setNextUInt32(pCtx->uScreenID); /* uScreenId */
835 Msg.setNextPointer(pCtx->Meta.strFmt.mutableRaw(), cbFmt); /* pvFormat */
836 Msg.setNextUInt32(cbFmt); /* cbFormat */
837 Msg.setNextPointer(pCtx->Meta.pvData, (uint32_t)pCtx->Meta.cbData); /* pvData */
838 /* Fill in the current data block size to send.
839 * Note: Only supports uint32_t. */
840 Msg.setNextUInt32((uint32_t)pCtx->Meta.cbData); /* cbData */
841 }
842 else
843 {
844 Msg.setNextUInt32(0); /** @todo ContextID not used yet. */
845 Msg.setNextPointer(pCtx->Meta.pvData, (uint32_t)pCtx->Meta.cbData); /* pvData */
846 Msg.setNextUInt32((uint32_t)pCtx->Meta.cbData); /* cbData */
847 Msg.setNextPointer(NULL, 0); /** @todo pvChecksum; not used yet. */
848 Msg.setNextUInt32(0); /** @todo cbChecksum; not used yet. */
849 }
850
851 int rc = GUESTDNDINST()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
852 if (RT_SUCCESS(rc))
853 {
854 rc = updateProgress(pCtx, pCtx->pResp, pCtx->Meta.cbData);
855 AssertRC(rc);
856 }
857
858 LogFlowFuncLeaveRC(rc);
859 return rc;
860}
861
862/**
863 * Sends the common meta data header to the guest.
864 *
865 * @returns VBox status code.
866 * @param pCtx Send context to use.
867 */
868int GuestDnDTarget::i_sendMetaDataHeader(GuestDnDSendCtx *pCtx)
869{
870 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
871
872 if (m_DataBase.uProtocolVersion < 3) /* Protocol < v3 did not support this, skip. */
873 return VINF_SUCCESS;
874
875 GuestDnDMsg Msg;
876 Msg.setType(HOST_DND_HG_SND_DATA_HDR);
877
878 Msg.setNextUInt32(0); /** @todo uContext; not used yet. */
879 Msg.setNextUInt32(0); /** @todo uFlags; not used yet. */
880 Msg.setNextUInt32(pCtx->uScreenID); /* uScreen */
881 Msg.setNextUInt64(pCtx->getTotal()); /* cbTotal */
882 Msg.setNextUInt32((uint32_t)pCtx->Meta.cbData); /* cbMeta*/
883 Msg.setNextPointer(unconst(pCtx->Meta.strFmt.c_str()), (uint32_t)pCtx->Meta.strFmt.length() + 1); /* pvMetaFmt */
884 Msg.setNextUInt32((uint32_t)pCtx->Meta.strFmt.length() + 1); /* cbMetaFmt */
885 Msg.setNextUInt64(pCtx->Transfer.cObjToProcess); /* cObjects */
886 Msg.setNextUInt32(0); /** @todo enmCompression; not used yet. */
887 Msg.setNextUInt32(0); /** @todo enmChecksumType; not used yet. */
888 Msg.setNextPointer(NULL, 0); /** @todo pvChecksum; not used yet. */
889 Msg.setNextUInt32(0); /** @todo cbChecksum; not used yet. */
890
891 int rc = GUESTDNDINST()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
892
893 LogFlowFuncLeaveRC(rc);
894 return rc;
895}
896
897int GuestDnDTarget::i_sendDirectory(GuestDnDSendCtx *pCtx, PDNDTRANSFEROBJECT pObj, GuestDnDMsg *pMsg)
898{
899 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
900 AssertPtrReturn(pMsg, VERR_INVALID_POINTER);
901
902 const char *pcszDstPath = DnDTransferObjectGetDestPath(pObj);
903 AssertPtrReturn(pcszDstPath, VERR_INVALID_POINTER);
904 const size_t cchPath = RTStrNLen(pcszDstPath, RTPATH_MAX); /* Note: Maximum is RTPATH_MAX on guest side. */
905 AssertReturn(cchPath, VERR_INVALID_PARAMETER);
906
907 LogRel2(("DnD: Transferring host directory '%s' to guest\n", DnDTransferObjectGetSourcePath(pObj)));
908
909 pMsg->setType(HOST_DND_HG_SND_DIR);
910 if (m_DataBase.uProtocolVersion >= 3)
911 pMsg->setNextUInt32(0); /** @todo ContextID not used yet. */
912 pMsg->setNextString(pcszDstPath); /* path */
913 pMsg->setNextUInt32((uint32_t)(cchPath + 1)); /* path length, including terminator. */
914 pMsg->setNextUInt32(DnDTransferObjectGetMode(pObj)); /* mode */
915
916 return VINF_SUCCESS;
917}
918
919/**
920 * Sends a transfer file to the guest.
921 *
922 * @returns VBox status code.
923 * @param pCtx
924 * @param pObj
925 * @param pMsg
926 */
927int GuestDnDTarget::i_sendFile(GuestDnDSendCtx *pCtx,
928 PDNDTRANSFEROBJECT pObj, GuestDnDMsg *pMsg)
929{
930 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
931 AssertPtrReturn(pObj, VERR_INVALID_POINTER);
932 AssertPtrReturn(pMsg, VERR_INVALID_POINTER);
933
934 const char *pcszSrcPath = DnDTransferObjectGetSourcePath(pObj);
935 AssertPtrReturn(pcszSrcPath, VERR_INVALID_POINTER);
936 const char *pcszDstPath = DnDTransferObjectGetDestPath(pObj);
937 AssertPtrReturn(pcszDstPath, VERR_INVALID_POINTER);
938
939 int rc = VINF_SUCCESS;
940
941 if (!DnDTransferObjectIsOpen(pObj))
942 {
943 LogRel2(("DnD: Opening host file '%s' for transferring to guest\n", pcszSrcPath));
944
945 rc = DnDTransferObjectOpen(pObj, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE, 0 /* fMode */,
946 DNDTRANSFEROBJECT_FLAGS_NONE);
947 if (RT_FAILURE(rc))
948 LogRel(("DnD: Opening host file '%s' failed, rc=%Rrc\n", pcszSrcPath, rc));
949 }
950
951 if (RT_FAILURE(rc))
952 return rc;
953
954 bool fSendData = false;
955 if (RT_SUCCESS(rc))
956 {
957 if (m_DataBase.uProtocolVersion >= 2)
958 {
959 if (!(pCtx->Transfer.fObjState & DND_OBJ_STATE_HAS_HDR))
960 {
961 const size_t cchDstPath = RTStrNLen(pcszDstPath, RTPATH_MAX);
962 const size_t cbSize = DnDTransferObjectGetSize(pObj);
963 const RTFMODE fMode = DnDTransferObjectGetMode(pObj);
964
965 /*
966 * Since protocol v2 the file header and the actual file contents are
967 * separate messages, so send the file header first.
968 * The just registered callback will be called by the guest afterwards.
969 */
970 pMsg->setType(HOST_DND_HG_SND_FILE_HDR);
971 pMsg->setNextUInt32(0); /** @todo ContextID not used yet. */
972 pMsg->setNextString(pcszDstPath); /* pvName */
973 pMsg->setNextUInt32((uint32_t)(cchDstPath + 1)); /* cbName */
974 pMsg->setNextUInt32(0); /* uFlags */
975 pMsg->setNextUInt32(fMode); /* fMode */
976 pMsg->setNextUInt64(cbSize); /* uSize */
977
978 LogRel2(("DnD: Transferring host file '%s' to guest (%zu bytes, mode %#x)\n",
979 pcszSrcPath, cbSize, fMode));
980
981 /** @todo Set progress object title to current file being transferred? */
982
983 /* Update object state to reflect that we have sent the file header. */
984 pCtx->Transfer.fObjState |= DND_OBJ_STATE_HAS_HDR;
985 }
986 else
987 {
988 /* File header was sent, so only send the actual file data. */
989 fSendData = true;
990 }
991 }
992 else /* Protocol v1. */
993 {
994 /* Always send the file data, every time. */
995 fSendData = true;
996 }
997 }
998
999 if ( RT_SUCCESS(rc)
1000 && fSendData)
1001 {
1002 rc = i_sendFileData(pCtx, pObj, pMsg);
1003 }
1004
1005 if (RT_FAILURE(rc))
1006 LogRel(("DnD: Sending host file '%s' to guest failed, rc=%Rrc\n", pcszSrcPath, rc));
1007
1008 LogFlowFuncLeaveRC(rc);
1009 return rc;
1010}
1011
1012int GuestDnDTarget::i_sendFileData(GuestDnDSendCtx *pCtx,
1013 PDNDTRANSFEROBJECT pObj, GuestDnDMsg *pMsg)
1014{
1015 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1016 AssertPtrReturn(pObj, VERR_INVALID_POINTER);
1017 AssertPtrReturn(pMsg, VERR_INVALID_POINTER);
1018
1019 AssertPtrReturn(pCtx->pResp, VERR_WRONG_ORDER);
1020
1021 /** @todo Don't allow concurrent reads per context! */
1022
1023 /* Set the message type. */
1024 pMsg->setType(HOST_DND_HG_SND_FILE_DATA);
1025
1026 const char *pcszSrcPath = DnDTransferObjectGetSourcePath(pObj);
1027 const char *pcszDstPath = DnDTransferObjectGetDestPath(pObj);
1028
1029 /* Protocol version 1 sends the file path *every* time with a new file chunk.
1030 * In protocol version 2 we only do this once with HOST_DND_HG_SND_FILE_HDR. */
1031 if (m_DataBase.uProtocolVersion <= 1)
1032 {
1033 const size_t cchDstPath = RTStrNLen(pcszDstPath, RTPATH_MAX);
1034
1035 pMsg->setNextString(pcszDstPath); /* pvName */
1036 pMsg->setNextUInt32((uint32_t)cchDstPath + 1); /* cbName */
1037 }
1038 else if (m_DataBase.uProtocolVersion >= 2)
1039 {
1040 pMsg->setNextUInt32(0); /** @todo ContextID not used yet. */
1041 }
1042
1043 void *pvBuf = pCtx->Transfer.pvScratchBuf;
1044 AssertPtr(pvBuf);
1045 size_t cbBuf = pCtx->Transfer.cbScratchBuf;
1046 Assert(cbBuf);
1047
1048 uint32_t cbRead;
1049
1050 int rc = DnDTransferObjectRead(pObj, pvBuf, cbBuf, &cbRead);
1051 if (RT_SUCCESS(rc))
1052 {
1053 pCtx->addProcessed(cbRead);
1054
1055 LogFlowFunc(("cbBufe=%zu, cbRead=%RU32\n", cbBuf, cbRead));
1056
1057 if (m_DataBase.uProtocolVersion <= 1)
1058 {
1059 pMsg->setNextPointer(pvBuf, cbRead); /* pvData */
1060 pMsg->setNextUInt32(cbRead); /* cbData */
1061 pMsg->setNextUInt32(DnDTransferObjectGetMode(pObj)); /* fMode */
1062 }
1063 else /* Protocol v2 and up. */
1064 {
1065 pMsg->setNextPointer(pvBuf, cbRead); /* pvData */
1066 pMsg->setNextUInt32(cbRead); /* cbData */
1067
1068 if (m_DataBase.uProtocolVersion >= 3)
1069 {
1070 /** @todo Calculate checksum. */
1071 pMsg->setNextPointer(NULL, 0); /* pvChecksum */
1072 pMsg->setNextUInt32(0); /* cbChecksum */
1073 }
1074 }
1075
1076 if (DnDTransferObjectIsComplete(pObj)) /* Done reading? */
1077 {
1078 LogRel2(("DnD: Transferring host file '%s' to guest complete\n", pcszSrcPath));
1079
1080 /* DnDTransferObjectRead() returns VINF_EOF when finished reading the entire file,
1081 * but we don't want this here -- so just set VINF_SUCCESS. */
1082 rc = VINF_SUCCESS;
1083 }
1084 }
1085 else
1086 LogRel(("DnD: Reading from host file '%s' failed, rc=%Rrc\n", pcszSrcPath, rc));
1087
1088 LogFlowFuncLeaveRC(rc);
1089 return rc;
1090}
1091
1092/* static */
1093DECLCALLBACK(int) GuestDnDTarget::i_sendTransferDataCallback(uint32_t uMsg, void *pvParms, size_t cbParms, void *pvUser)
1094{
1095 GuestDnDSendCtx *pCtx = (GuestDnDSendCtx *)pvUser;
1096 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1097
1098 GuestDnDTarget *pThis = pCtx->pTarget;
1099 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
1100
1101 LogFlowFunc(("pThis=%p, uMsg=%RU32\n", pThis, uMsg));
1102
1103 int rc = VINF_SUCCESS;
1104 int rcGuest = VINF_SUCCESS; /* Contains error code from guest in case of VERR_GSTDND_GUEST_ERROR. */
1105 bool fNotify = false;
1106
1107 switch (uMsg)
1108 {
1109 case GUEST_DND_CONNECT:
1110 /* Nothing to do here (yet). */
1111 break;
1112
1113 case GUEST_DND_DISCONNECT:
1114 rc = VERR_CANCELLED;
1115 break;
1116
1117 case GUEST_DND_GET_NEXT_HOST_MSG:
1118 {
1119 PVBOXDNDCBHGGETNEXTHOSTMSG pCBData = reinterpret_cast<PVBOXDNDCBHGGETNEXTHOSTMSG>(pvParms);
1120 AssertPtr(pCBData);
1121 AssertReturn(sizeof(VBOXDNDCBHGGETNEXTHOSTMSG) == cbParms, VERR_INVALID_PARAMETER);
1122 AssertReturn(CB_MAGIC_DND_HG_GET_NEXT_HOST_MSG == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1123
1124 try
1125 {
1126 GuestDnDMsg *pMsg = new GuestDnDMsg();
1127
1128 rc = pThis->i_sendTransferDataLoop(pCtx, pMsg);
1129 if (rc == VINF_EOF) /* Transfer complete? */
1130 {
1131 LogFlowFunc(("Last transfer item processed, bailing out\n"));
1132 }
1133 else if (RT_SUCCESS(rc))
1134 {
1135 rc = pThis->msgQueueAdd(pMsg);
1136 if (RT_SUCCESS(rc)) /* Return message type & required parameter count to the guest. */
1137 {
1138 LogFlowFunc(("GUEST_DND_GET_NEXT_HOST_MSG -> %RU32 (%RU32 params)\n", pMsg->getType(), pMsg->getCount()));
1139 pCBData->uMsg = pMsg->getType();
1140 pCBData->cParms = pMsg->getCount();
1141 }
1142 }
1143
1144 if ( RT_FAILURE(rc)
1145 || rc == VINF_EOF) /* Transfer complete? */
1146 {
1147 delete pMsg;
1148 pMsg = NULL;
1149 }
1150 }
1151 catch(std::bad_alloc & /*e*/)
1152 {
1153 rc = VERR_NO_MEMORY;
1154 }
1155 break;
1156 }
1157 case GUEST_DND_GH_EVT_ERROR:
1158 {
1159 PVBOXDNDCBEVTERRORDATA pCBData = reinterpret_cast<PVBOXDNDCBEVTERRORDATA>(pvParms);
1160 AssertPtr(pCBData);
1161 AssertReturn(sizeof(VBOXDNDCBEVTERRORDATA) == cbParms, VERR_INVALID_PARAMETER);
1162 AssertReturn(CB_MAGIC_DND_GH_EVT_ERROR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1163
1164 pCtx->pResp->reset();
1165
1166 if (RT_SUCCESS(pCBData->rc))
1167 {
1168 AssertMsgFailed(("Guest has sent an error event but did not specify an actual error code\n"));
1169 pCBData->rc = VERR_GENERAL_FAILURE; /* Make sure some error is set. */
1170 }
1171
1172 rc = pCtx->pResp->setProgress(100, DND_PROGRESS_ERROR, pCBData->rc,
1173 GuestDnDTarget::i_guestErrorToString(pCBData->rc));
1174 if (RT_SUCCESS(rc))
1175 {
1176 rc = VERR_GSTDND_GUEST_ERROR;
1177 rcGuest = pCBData->rc;
1178 }
1179 break;
1180 }
1181 case HOST_DND_HG_SND_DIR:
1182 case HOST_DND_HG_SND_FILE_HDR:
1183 case HOST_DND_HG_SND_FILE_DATA:
1184 {
1185 PVBOXDNDCBHGGETNEXTHOSTMSGDATA pCBData
1186 = reinterpret_cast<PVBOXDNDCBHGGETNEXTHOSTMSGDATA>(pvParms);
1187 AssertPtr(pCBData);
1188 AssertReturn(sizeof(VBOXDNDCBHGGETNEXTHOSTMSGDATA) == cbParms, VERR_INVALID_PARAMETER);
1189
1190 LogFlowFunc(("pCBData->uMsg=%RU32, paParms=%p, cParms=%RU32\n", pCBData->uMsg, pCBData->paParms, pCBData->cParms));
1191
1192 GuestDnDMsg *pMsg = pThis->msgQueueGetNext();
1193 if (pMsg)
1194 {
1195 /*
1196 * Sanity checks.
1197 */
1198 if ( pCBData->uMsg != uMsg
1199 || pCBData->paParms == NULL
1200 || pCBData->cParms != pMsg->getCount())
1201 {
1202 LogFlowFunc(("Current message does not match:\n"));
1203 LogFlowFunc(("\tCallback: uMsg=%RU32, cParms=%RU32, paParms=%p\n",
1204 pCBData->uMsg, pCBData->cParms, pCBData->paParms));
1205 LogFlowFunc(("\t Next: uMsg=%RU32, cParms=%RU32\n", pMsg->getType(), pMsg->getCount()));
1206
1207 /* Start over. */
1208 pThis->msgQueueClear();
1209
1210 rc = VERR_INVALID_PARAMETER;
1211 }
1212
1213 if (RT_SUCCESS(rc))
1214 {
1215 LogFlowFunc(("Returning uMsg=%RU32\n", uMsg));
1216 rc = HGCM::Message::CopyParms(pCBData->paParms, pCBData->cParms, pMsg->getParms(), pMsg->getCount(),
1217 false /* fDeepCopy */);
1218 if (RT_SUCCESS(rc))
1219 {
1220 pCBData->cParms = pMsg->getCount();
1221 pThis->msgQueueRemoveNext();
1222 }
1223 else
1224 LogFlowFunc(("Copying parameters failed with rc=%Rrc\n", rc));
1225 }
1226 }
1227 else
1228 rc = VERR_NO_DATA;
1229
1230 LogFlowFunc(("Processing next message ended with rc=%Rrc\n", rc));
1231 break;
1232 }
1233 default:
1234 rc = VERR_NOT_SUPPORTED;
1235 break;
1236 }
1237
1238 int rcToGuest = VINF_SUCCESS; /* Status which will be sent back to the guest. */
1239
1240 /*
1241 * Resolve errors.
1242 */
1243 switch (rc)
1244 {
1245 case VINF_SUCCESS:
1246 break;
1247
1248 case VINF_EOF:
1249 {
1250 LogRel2(("DnD: Transfer to guest complete\n"));
1251
1252 /* Complete operation on host side. */
1253 fNotify = true;
1254
1255 /* The guest expects VERR_NO_DATA if the transfer is complete. */
1256 rcToGuest = VERR_NO_DATA;
1257 break;
1258 }
1259
1260 case VERR_GSTDND_GUEST_ERROR:
1261 {
1262 LogRel(("DnD: Guest reported error %Rrc, aborting transfer to guest\n", rcGuest));
1263 break;
1264 }
1265
1266 case VERR_CANCELLED:
1267 {
1268 LogRel2(("DnD: Transfer to guest canceled\n"));
1269 rcToGuest = VERR_CANCELLED; /* Also cancel on guest side. */
1270 break;
1271 }
1272
1273 default:
1274 {
1275 LogRel(("DnD: Host error %Rrc occurred, aborting transfer to guest\n", rc));
1276 rcToGuest = VERR_CANCELLED; /* Also cancel on guest side. */
1277 break;
1278 }
1279 }
1280
1281 if (RT_FAILURE(rc))
1282 {
1283 /* Unregister this callback. */
1284 AssertPtr(pCtx->pResp);
1285 int rc2 = pCtx->pResp->setCallback(uMsg, NULL /* PFNGUESTDNDCALLBACK */);
1286 AssertRC(rc2);
1287
1288 /* Let the waiter(s) know. */
1289 fNotify = true;
1290 }
1291
1292 LogFlowFunc(("fNotify=%RTbool, rc=%Rrc, rcToGuest=%Rrc\n", fNotify, rc, rcToGuest));
1293
1294 if (fNotify)
1295 {
1296 int rc2 = pCtx->EventCallback.Notify(rc); /** @todo Also pass guest error back? */
1297 AssertRC(rc2);
1298 }
1299
1300 LogFlowFuncLeaveRC(rc);
1301 return rcToGuest; /* Tell the guest. */
1302}
1303
1304/**
1305 * Main function for sending the actual transfer data (i.e. files + directories) to the guest.
1306 *
1307 * @returns VBox status code.
1308 * @param pCtx Send context to use.
1309 * @param msTimeout Timeout (in ms) to use for getting the data sent.
1310 */
1311int GuestDnDTarget::i_sendTransferData(GuestDnDSendCtx *pCtx, RTMSINTERVAL msTimeout)
1312{
1313 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1314 AssertPtr(pCtx->pResp);
1315
1316#define REGISTER_CALLBACK(x) \
1317 do { \
1318 rc = pCtx->pResp->setCallback(x, i_sendTransferDataCallback, pCtx); \
1319 if (RT_FAILURE(rc)) \
1320 return rc; \
1321 } while (0)
1322
1323#define UNREGISTER_CALLBACK(x) \
1324 do { \
1325 int rc2 = pCtx->pResp->setCallback(x, NULL); \
1326 AssertRC(rc2); \
1327 } while (0)
1328
1329 int rc = pCtx->Transfer.init(mData.mcbBlockSize);
1330 if (RT_FAILURE(rc))
1331 return rc;
1332
1333 rc = pCtx->EventCallback.Reset();
1334 if (RT_FAILURE(rc))
1335 return rc;
1336
1337 /*
1338 * Register callbacks.
1339 */
1340 /* Guest callbacks. */
1341 REGISTER_CALLBACK(GUEST_DND_CONNECT);
1342 REGISTER_CALLBACK(GUEST_DND_DISCONNECT);
1343 REGISTER_CALLBACK(GUEST_DND_GET_NEXT_HOST_MSG);
1344 REGISTER_CALLBACK(GUEST_DND_GH_EVT_ERROR);
1345 /* Host callbacks. */
1346 REGISTER_CALLBACK(HOST_DND_HG_SND_DIR);
1347 if (m_DataBase.uProtocolVersion >= 2)
1348 REGISTER_CALLBACK(HOST_DND_HG_SND_FILE_HDR);
1349 REGISTER_CALLBACK(HOST_DND_HG_SND_FILE_DATA);
1350
1351 do
1352 {
1353 /*
1354 * Extract transfer list from current meta data.
1355 */
1356 rc = DnDTransferListAppendPathsFromBuffer(&pCtx->Transfer.List, DNDTRANSFERLISTFMT_NATIVE,
1357 (const char *)pCtx->Meta.pvData, pCtx->Meta.cbData, "\n", DNDTRANSFERLIST_FLAGS_NONE);
1358 if (RT_FAILURE(rc))
1359 break;
1360
1361 /*
1362 * Update internal state to reflect everything we need to work with it.
1363 */
1364 pCtx->cbExtra = DnDTransferListObjTotalBytes(&pCtx->Transfer.List);
1365
1366 /*
1367 * The first message always is the data header. The meta data itself then follows
1368 * and *only* contains the root elements of a transfer list.
1369 *
1370 * After the meta data we generate the messages required to send the
1371 * file/directory data itself.
1372 *
1373 * Note: Protocol < v3 use the first data message to tell what's being sent.
1374 */
1375
1376 /*
1377 * Send the data header first.
1378 */
1379 if (m_DataBase.uProtocolVersion >= 3)
1380 rc = i_sendMetaDataHeader(pCtx);
1381
1382 /*
1383 * Send the (meta) data body.
1384 */
1385 if (RT_SUCCESS(rc))
1386 rc = i_sendMetaDataBody(pCtx);
1387
1388 if (RT_SUCCESS(rc))
1389 {
1390 rc = waitForEvent(&pCtx->EventCallback, pCtx->pResp, msTimeout);
1391 if (RT_SUCCESS(rc))
1392 pCtx->pResp->setProgress(100, DND_PROGRESS_COMPLETE, VINF_SUCCESS);
1393 }
1394
1395 } while (0);
1396
1397 /*
1398 * Unregister callbacks.
1399 */
1400 /* Guest callbacks. */
1401 UNREGISTER_CALLBACK(GUEST_DND_CONNECT);
1402 UNREGISTER_CALLBACK(GUEST_DND_DISCONNECT);
1403 UNREGISTER_CALLBACK(GUEST_DND_GET_NEXT_HOST_MSG);
1404 UNREGISTER_CALLBACK(GUEST_DND_GH_EVT_ERROR);
1405 /* Host callbacks. */
1406 UNREGISTER_CALLBACK(HOST_DND_HG_SND_DIR);
1407 if (m_DataBase.uProtocolVersion >= 2)
1408 UNREGISTER_CALLBACK(HOST_DND_HG_SND_FILE_HDR);
1409 UNREGISTER_CALLBACK(HOST_DND_HG_SND_FILE_DATA);
1410
1411#undef REGISTER_CALLBACK
1412#undef UNREGISTER_CALLBACK
1413
1414 if (RT_FAILURE(rc))
1415 {
1416 if (rc == VERR_CANCELLED) /* Transfer was cancelled by the host. */
1417 {
1418 /*
1419 * Now that we've cleaned up tell the guest side to cancel.
1420 * This does not imply we're waiting for the guest to react, as the
1421 * host side never must depend on anything from the guest.
1422 */
1423 int rc2 = sendCancel();
1424 AssertRC(rc2);
1425
1426 LogRel2(("DnD: Sending transfer data to guest cancelled by user\n"));
1427
1428 rc2 = pCtx->pResp->setProgress(100, DND_PROGRESS_CANCELLED, VINF_SUCCESS);
1429 AssertRC(rc2);
1430 }
1431 else if (rc != VERR_GSTDND_GUEST_ERROR) /* Guest-side error are already handled in the callback. */
1432 {
1433 LogRel(("DnD: Sending transfer data to guest failed with rc=%Rrc\n", rc));
1434 int rc2 = pCtx->pResp->setProgress(100, DND_PROGRESS_ERROR, rc,
1435 GuestDnDTarget::i_hostErrorToString(rc));
1436 AssertRC(rc2);
1437 }
1438
1439 rc = VINF_SUCCESS; /* The error was handled by the setProgress() calls above. */
1440 }
1441
1442 LogFlowFuncLeaveRC(rc);
1443 return rc;
1444}
1445
1446int GuestDnDTarget::i_sendTransferDataLoop(GuestDnDSendCtx *pCtx, GuestDnDMsg *pMsg)
1447{
1448 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1449 AssertPtrReturn(pMsg, VERR_INVALID_POINTER);
1450
1451 int rc = updateProgress(pCtx, pCtx->pResp);
1452 AssertRC(rc);
1453
1454 if ( pCtx->isComplete()
1455 && pCtx->Transfer.isComplete())
1456 {
1457 return VINF_EOF;
1458 }
1459
1460 PDNDTRANSFEROBJECT pObj = DnDTransferListObjGetFirst(&pCtx->Transfer.List);
1461 if (!pObj)
1462 return VERR_WRONG_ORDER;
1463
1464 switch (DnDTransferObjectGetType(pObj))
1465 {
1466 case DNDTRANSFEROBJTYPE_DIRECTORY:
1467 rc = i_sendDirectory(pCtx, pObj, pMsg);
1468 break;
1469
1470 case DNDTRANSFEROBJTYPE_FILE:
1471 rc = i_sendFile(pCtx, pObj, pMsg);
1472 break;
1473
1474 default:
1475 AssertFailedStmt(rc = VERR_NOT_SUPPORTED);
1476 break;
1477 }
1478
1479 if ( DnDTransferObjectIsComplete(pObj)
1480 || RT_FAILURE(rc))
1481 DnDTransferListObjRemoveFirst(&pCtx->Transfer.List);
1482
1483 LogFlowFuncLeaveRC(rc);
1484 return rc;
1485}
1486
1487/**
1488 * Main function for sending raw data (e.g. text, RTF, ...) to the guest.
1489 *
1490 * @returns VBox status code.
1491 * @param pCtx Send context to use.
1492 * @param msTimeout Timeout (in ms) to use for getting the data sent.
1493 */
1494int GuestDnDTarget::i_sendRawData(GuestDnDSendCtx *pCtx, RTMSINTERVAL msTimeout)
1495{
1496 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1497 NOREF(msTimeout);
1498
1499 /** @todo At the moment we only allow sending up to 64K raw data.
1500 * For protocol v1+v2: Fix this by using HOST_DND_HG_SND_MORE_DATA.
1501 * For protocol v3 : Send another HOST_DND_HG_SND_DATA message. */
1502 if (!pCtx->Meta.cbData)
1503 return VINF_SUCCESS;
1504
1505 int rc = i_sendMetaDataHeader(pCtx);
1506 if (RT_SUCCESS(rc))
1507 rc = i_sendMetaDataBody(pCtx);
1508
1509 int rc2;
1510 if (RT_FAILURE(rc))
1511 {
1512 LogRel(("DnD: Sending raw data to guest failed with rc=%Rrc\n", rc));
1513 rc2 = pCtx->pResp->setProgress(100 /* Percent */, DND_PROGRESS_ERROR, rc,
1514 GuestDnDTarget::i_hostErrorToString(rc));
1515 }
1516 else
1517 rc2 = pCtx->pResp->setProgress(100 /* Percent */, DND_PROGRESS_COMPLETE, rc);
1518 AssertRC(rc2);
1519
1520 LogFlowFuncLeaveRC(rc);
1521 return rc;
1522}
1523
1524/**
1525 * Cancels sending DnD data.
1526 *
1527 * @returns VBox status code.
1528 * @param aVeto Whether cancelling was vetoed or not.
1529 * Not implemented yet.
1530 */
1531HRESULT GuestDnDTarget::cancel(BOOL *aVeto)
1532{
1533#if !defined(VBOX_WITH_DRAG_AND_DROP)
1534 ReturnComNotImplemented();
1535#else /* VBOX_WITH_DRAG_AND_DROP */
1536
1537 LogRel2(("DnD: Sending cancelling request to the guest ...\n"));
1538
1539 int rc = GuestDnDBase::sendCancel();
1540
1541 if (aVeto)
1542 *aVeto = FALSE; /** @todo Implement vetoing. */
1543
1544 HRESULT hr = RT_SUCCESS(rc) ? S_OK : VBOX_E_IPRT_ERROR;
1545
1546 LogFlowFunc(("hr=%Rhrc\n", hr));
1547 return hr;
1548#endif /* VBOX_WITH_DRAG_AND_DROP */
1549}
1550
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