VirtualBox

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

Last change on this file since 85489 was 85451, checked in by vboxsync, 5 years ago

DnD/Main: Release and re-acquire write locks before/after creating worker threads.

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

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette