VirtualBox

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

Last change on this file since 94521 was 93115, checked in by vboxsync, 2 years ago

scm --update-copyright-year

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

© 2023 Oracle
ContactPrivacy policyTerms of Use