VirtualBox

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

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

scm copyright and license note update

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

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