VirtualBox

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

Last change on this file was 104635, checked in by vboxsync, 4 months ago

Main: Fixed warnings. ​bugref:3409

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

© 2024 Oracle
ContactPrivacy/Do Not Sell My InfoTerms of Use