VirtualBox

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

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

DnD/Main: More fixes.

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