VirtualBox

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

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

DnD/Main: Made the progress object description a bit more flexible, to also know which operation currently is going on.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 63.3 KB
Line 
1/* $Id: GuestDnDTargetImpl.cpp 97802 2022-12-14 14:58:10Z 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 /* ignore rc */ pThis->i_sendData(mpCtx, RT_INDEFINITE_WAIT /* msTimeout */);
110 }
111
112 virtual ~GuestDnDSendDataTask(void) { }
113
114protected:
115
116 /** Pointer to send data context. */
117 GuestDnDSendCtx *mpCtx;
118};
119
120// constructor / destructor
121/////////////////////////////////////////////////////////////////////////////
122
123GuestDnDTarget::GuestDnDTarget(void)
124 : GuestDnDBase(this) { }
125
126GuestDnDTarget::~GuestDnDTarget(void) { }
127
128HRESULT GuestDnDTarget::FinalConstruct(void)
129{
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. */
134 mData.mcbBlockSize = DND_DEFAULT_CHUNK_SIZE; /** @todo Make this configurable. */
135
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
151HRESULT GuestDnDTarget::init(const ComObjPtr<Guest>& pGuest)
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
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 */
168 m_pState = GuestDnDInst()->getState();
169 AssertPtrReturn(m_pState, E_POINTER);
170
171 /* Confirm a successful initialization when it's the case. */
172 autoInitSpan.setSucceeded();
173
174 return S_OK;
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
191// implementation of wrapped IDnDBase methods.
192/////////////////////////////////////////////////////////////////////////////
193
194HRESULT GuestDnDTarget::isFormatSupported(const com::Utf8Str &aFormat, BOOL *aSupported)
195{
196#if !defined(VBOX_WITH_DRAG_AND_DROP)
197 ReturnComNotImplemented();
198#else /* VBOX_WITH_DRAG_AND_DROP */
199
200 AutoCaller autoCaller(this);
201 if (autoCaller.isNotOk()) return autoCaller.rc();
202
203 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
204
205 *aSupported = GuestDnDBase::i_isFormatSupported(aFormat) ? TRUE : FALSE;
206
207 return S_OK;
208#endif /* VBOX_WITH_DRAG_AND_DROP */
209}
210
211HRESULT GuestDnDTarget::getFormats(GuestDnDMIMEList &aFormats)
212{
213#if !defined(VBOX_WITH_DRAG_AND_DROP)
214 ReturnComNotImplemented();
215#else /* VBOX_WITH_DRAG_AND_DROP */
216
217 AutoCaller autoCaller(this);
218 if (autoCaller.isNotOk()) return autoCaller.rc();
219
220 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
221
222 aFormats = GuestDnDBase::i_getFormats();
223
224 return S_OK;
225#endif /* VBOX_WITH_DRAG_AND_DROP */
226}
227
228HRESULT GuestDnDTarget::addFormats(const GuestDnDMIMEList &aFormats)
229{
230#if !defined(VBOX_WITH_DRAG_AND_DROP)
231 ReturnComNotImplemented();
232#else /* VBOX_WITH_DRAG_AND_DROP */
233
234 AutoCaller autoCaller(this);
235 if (autoCaller.isNotOk()) return autoCaller.rc();
236
237 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
238
239 return GuestDnDBase::i_addFormats(aFormats);
240#endif /* VBOX_WITH_DRAG_AND_DROP */
241}
242
243HRESULT GuestDnDTarget::removeFormats(const GuestDnDMIMEList &aFormats)
244{
245#if !defined(VBOX_WITH_DRAG_AND_DROP)
246 ReturnComNotImplemented();
247#else /* VBOX_WITH_DRAG_AND_DROP */
248
249 AutoCaller autoCaller(this);
250 if (autoCaller.isNotOk()) return autoCaller.rc();
251
252 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
253
254 return GuestDnDBase::i_removeFormats(aFormats);
255#endif /* VBOX_WITH_DRAG_AND_DROP */
256}
257
258// implementation of wrapped IDnDTarget methods.
259/////////////////////////////////////////////////////////////////////////////
260
261HRESULT GuestDnDTarget::enter(ULONG aScreenId, ULONG aX, ULONG aY,
262 DnDAction_T aDefaultAction,
263 const std::vector<DnDAction_T> &aAllowedActions,
264 const GuestDnDMIMEList &aFormats,
265 DnDAction_T *aResultAction)
266{
267#if !defined(VBOX_WITH_DRAG_AND_DROP)
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);
280 if (autoCaller.isNotOk()) return autoCaller.rc();
281
282 /* Default action is ignoring. */
283 DnDAction_T resAction = DnDAction_Ignore;
284
285 /* Check & convert the drag & drop actions. */
286 VBOXDNDACTION dndActionDefault = 0;
287 VBOXDNDACTIONLIST dndActionListAllowed = 0;
288 GuestDnD::toHGCMActions(aDefaultAction, &dndActionDefault,
289 aAllowedActions, &dndActionListAllowed);
290
291 /* If there is no usable action, ignore this request. */
292 if (isDnDIgnoreAction(dndActionDefault))
293 return S_OK;
294
295 GuestDnDState *pState = GuestDnDInst()->getState();
296 AssertPtrReturn(pState, E_POINTER);
297
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 */
303 Utf8Str strFormats = GuestDnD::toFormatString(GuestDnD::toFilteredFormatList(m_lstFmtSupported, aFormats));
304 if (strFormats.isEmpty())
305 return setError(E_INVALIDARG, tr("No or not supported format(s) specified"));
306 const uint32_t cbFormats = (uint32_t)strFormats.length() + 1; /* Include terminating zero. */
307
308 LogRel2(("DnD: Offered formats to guest:\n"));
309 RTCList<RTCString> lstFormats = strFormats.split(DND_PATH_SEPARATOR_STR);
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. */
315 m_lstFmtOffered = aFormats;
316 Assert(m_lstFmtOffered.size());
317
318 /* Adjust the coordinates in a multi-monitor setup. */
319 HRESULT hrc = GuestDnDInst()->adjustScreenCoordinates(aScreenId, &aX, &aY);
320 if (SUCCEEDED(hrc))
321 {
322 GuestDnDMsg Msg;
323 Msg.setType(HOST_DND_FN_HG_EVT_ENTER);
324 if (m_pState->m_uProtocolVersion >= 3)
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);
333
334 int vrc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
335 if (RT_SUCCESS(vrc))
336 {
337 int vrcGuest;
338 if (RT_SUCCESS(vrc = pState->waitForGuestResponse(&vrcGuest)))
339 {
340 resAction = GuestDnD::toMainAction(m_pState->getActionDefault());
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)));
344
345 pState->set(VBOXDNDSTATE_ENTERED);
346 }
347 else
348 hrc = i_setErrorAndReset(vrc == VERR_DND_GUEST_ERROR ? vrcGuest : vrc, tr("Entering VM window failed"));
349 }
350 else
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 }
372 }
373
374 if (SUCCEEDED(hrc))
375 {
376 if (aResultAction)
377 *aResultAction = resAction;
378 }
379
380 LogFlowFunc(("hrc=%Rhrc, resAction=%ld\n", hrc, resAction));
381 return hrc;
382#endif /* VBOX_WITH_DRAG_AND_DROP */
383}
384
385HRESULT GuestDnDTarget::move(ULONG aScreenId, ULONG aX, ULONG aY,
386 DnDAction_T aDefaultAction,
387 const std::vector<DnDAction_T> &aAllowedActions,
388 const GuestDnDMIMEList &aFormats,
389 DnDAction_T *aResultAction)
390{
391#if !defined(VBOX_WITH_DRAG_AND_DROP)
392 ReturnComNotImplemented();
393#else /* VBOX_WITH_DRAG_AND_DROP */
394
395 /* Input validation. */
396
397 AutoCaller autoCaller(this);
398 if (autoCaller.isNotOk()) return autoCaller.rc();
399
400 /* Default action is ignoring. */
401 DnDAction_T resAction = DnDAction_Ignore;
402
403 /* Check & convert the drag & drop actions. */
404 VBOXDNDACTION dndActionDefault = 0;
405 VBOXDNDACTIONLIST dndActionListAllowed = 0;
406 GuestDnD::toHGCMActions(aDefaultAction, &dndActionDefault,
407 aAllowedActions, &dndActionListAllowed);
408
409 /* If there is no usable action, ignore this request. */
410 if (isDnDIgnoreAction(dndActionDefault))
411 return S_OK;
412
413 GuestDnDState *pState = GuestDnDInst()->getState();
414 AssertPtrReturn(pState, E_POINTER);
415
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 */
421 Utf8Str strFormats = GuestDnD::toFormatString(GuestDnD::toFilteredFormatList(m_lstFmtSupported, aFormats));
422 if (strFormats.isEmpty())
423 return setError(E_INVALIDARG, tr("No or not supported format(s) specified"));
424 const uint32_t cbFormats = (uint32_t)strFormats.length() + 1; /* Include terminating zero. */
425
426 HRESULT hrc = GuestDnDInst()->adjustScreenCoordinates(aScreenId, &aX, &aY);
427 if (SUCCEEDED(hrc))
428 {
429 GuestDnDMsg Msg;
430 Msg.setType(HOST_DND_FN_HG_EVT_MOVE);
431 if (m_pState->m_uProtocolVersion >= 3)
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);
440
441 int vrc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
442 if (RT_SUCCESS(vrc))
443 {
444 int vrcGuest;
445 if (RT_SUCCESS(vrc = pState->waitForGuestResponse(&vrcGuest)))
446 {
447 resAction = GuestDnD::toMainAction(pState->getActionDefault());
448
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)));
451
452 pState->set(VBOXDNDSTATE_DRAGGING);
453 }
454 else
455 hrc = i_setErrorAndReset(vrc == VERR_DND_GUEST_ERROR ? vrcGuest : vrc,
456 tr("Moving to %RU32,%RU32 (screen %u) failed"), aX, aY, aScreenId);
457 }
458 else
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 }
480 }
481 else
482 hrc = i_setErrorAndReset(tr("Retrieving move coordinates failed"));
483
484 if (SUCCEEDED(hrc))
485 {
486 if (aResultAction)
487 *aResultAction = resAction;
488 }
489
490 LogFlowFunc(("hrc=%Rhrc, *pResultAction=%ld\n", hrc, resAction));
491 return hrc;
492#endif /* VBOX_WITH_DRAG_AND_DROP */
493}
494
495HRESULT GuestDnDTarget::leave(ULONG uScreenId)
496{
497 RT_NOREF(uScreenId);
498#if !defined(VBOX_WITH_DRAG_AND_DROP)
499 ReturnComNotImplemented();
500#else /* VBOX_WITH_DRAG_AND_DROP */
501
502 AutoCaller autoCaller(this);
503 if (autoCaller.isNotOk()) return autoCaller.rc();
504
505 GuestDnDState *pState = GuestDnDInst()->getState();
506 AssertPtrReturn(pState, E_POINTER);
507
508 if (pState->get() == VBOXDNDSTATE_DROP_STARTED)
509 return S_OK;
510
511 HRESULT hrc = S_OK;
512
513 LogRel2(("DnD: Host left the VM window (screen %u)\n", uScreenId));
514
515 GuestDnDMsg Msg;
516 Msg.setType(HOST_DND_FN_HG_EVT_LEAVE);
517 if (m_pState->m_uProtocolVersion >= 3)
518 Msg.appendUInt32(0); /** @todo ContextID not used yet. */
519
520 int vrc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
521 if (RT_SUCCESS(vrc))
522 {
523 int vrcGuest;
524 if (RT_SUCCESS(vrc = pState->waitForGuestResponse(&vrcGuest)))
525 {
526 pState->set(VBOXDNDSTATE_LEFT);
527 }
528 else
529 hrc = i_setErrorAndReset(vrc == VERR_DND_GUEST_ERROR ? vrcGuest : vrc, tr("Leaving VM window failed"));
530 }
531 else
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 }
540
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
554 LogFlowFunc(("hrc=%Rhrc\n", hrc));
555 return hrc;
556#endif /* VBOX_WITH_DRAG_AND_DROP */
557}
558
559HRESULT GuestDnDTarget::drop(ULONG aScreenId, ULONG aX, ULONG aY,
560 DnDAction_T aDefaultAction,
561 const std::vector<DnDAction_T> &aAllowedActions,
562 const GuestDnDMIMEList &aFormats,
563 com::Utf8Str &aFormat,
564 DnDAction_T *aResultAction)
565{
566#if !defined(VBOX_WITH_DRAG_AND_DROP)
567 ReturnComNotImplemented();
568#else /* VBOX_WITH_DRAG_AND_DROP */
569
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. */
577
578 AutoCaller autoCaller(this);
579 if (FAILED(autoCaller.rc())) return autoCaller.rc();
580
581 /* Default action is ignoring. */
582 DnDAction_T resAct = DnDAction_Ignore;
583 Utf8Str resFmt;
584
585 /* Check & convert the drag & drop actions to HGCM codes. */
586 VBOXDNDACTION dndActionDefault = VBOX_DND_ACTION_IGNORE;
587 VBOXDNDACTIONLIST dndActionListAllowed = 0;
588 GuestDnD::toHGCMActions(aDefaultAction, &dndActionDefault,
589 aAllowedActions, &dndActionListAllowed);
590
591 /* If there is no usable action, ignore this request. */
592 if (isDnDIgnoreAction(dndActionDefault))
593 {
594 aFormat = "";
595 if (aResultAction)
596 *aResultAction = DnDAction_Ignore;
597 return S_OK;
598 }
599
600 GuestDnDState *pState = GuestDnDInst()->getState();
601 AssertPtrReturn(pState, E_POINTER);
602
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 */
608 Utf8Str strFormats = GuestDnD::toFormatString(GuestDnD::toFilteredFormatList(m_lstFmtSupported, aFormats));
609 if (strFormats.isEmpty())
610 return setError(E_INVALIDARG, tr("No or not supported format(s) specified"));
611 const uint32_t cbFormats = (uint32_t)strFormats.length() + 1; /* Include terminating zero. */
612
613 /* Adjust the coordinates in a multi-monitor setup. */
614 HRESULT hrc = GuestDnDInst()->adjustScreenCoordinates(aScreenId, &aX, &aY);
615 if (SUCCEEDED(hrc))
616 {
617 GuestDnDMsg Msg;
618 Msg.setType(HOST_DND_FN_HG_EVT_DROPPED);
619 if (m_pState->m_uProtocolVersion >= 3)
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);
628
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
632 int vrc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
633 if (RT_SUCCESS(vrc))
634 {
635 int vrcGuest;
636 if (RT_SUCCESS(vrc = pState->waitForGuestResponse(&vrcGuest)))
637 {
638 resAct = GuestDnD::toMainAction(pState->getActionDefault());
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);
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);
650 }
651 else
652 {
653 if (lstFormats.size() == 0)
654 hrc = i_setErrorAndReset(VERR_DND_GUEST_ERROR, tr("Guest accepted drop, but did not specify the format"));
655 else
656 hrc = i_setErrorAndReset(VERR_DND_GUEST_ERROR, tr("Guest accepted drop, but returned more than one drop format (%zu formats)"),
657 lstFormats.size());
658 }
659 }
660 }
661 else
662 hrc = i_setErrorAndReset(vrc == VERR_DND_GUEST_ERROR ? vrcGuest : vrc, tr("Dropping into VM failed"));
663 }
664 else
665 hrc = i_setErrorAndReset(vrc, tr("Sending dropped event to guest failed"));
666 }
667 else
668 hrc = i_setErrorAndReset(hrc, tr("Retrieving drop coordinates failed"));
669
670 if (SUCCEEDED(hrc))
671 {
672 aFormat = resFmt;
673 if (aResultAction)
674 *aResultAction = resAct;
675 }
676
677 return hrc;
678#endif /* VBOX_WITH_DRAG_AND_DROP */
679}
680
681/**
682 * Initiates a data transfer from the host to the guest.
683 *
684 * The source is the host, whereas the target is the guest.
685 *
686 * @return HRESULT
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.
691 */
692HRESULT GuestDnDTarget::sendData(ULONG aScreenId, const com::Utf8Str &aFormat, const std::vector<BYTE> &aData,
693 ComPtr<IProgress> &aProgress)
694{
695#if !defined(VBOX_WITH_DRAG_AND_DROP)
696 ReturnComNotImplemented();
697#else /* VBOX_WITH_DRAG_AND_DROP */
698
699 AutoCaller autoCaller(this);
700 if (FAILED(autoCaller.rc())) return autoCaller.rc();
701
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
708 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
709
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
714 /* At the moment we only support one transfer at a time. */
715 if (GuestDnDInst()->getTargetCount())
716 return setError(E_INVALIDARG, tr("Another drag and drop operation to the guest already is in progress"));
717
718 /* Reset progress object. */
719 GuestDnDState *pState = GuestDnDInst()->getState();
720 AssertPtr(pState);
721 HRESULT hr = pState->resetProgress(m_pGuest, tr("Dropping data to guest"));
722 if (FAILED(hr))
723 return hr;
724
725 GuestDnDSendDataTask *pTask = NULL;
726
727 try
728 {
729 mData.mSendCtx.reset();
730
731 mData.mSendCtx.pTarget = this;
732 mData.mSendCtx.pState = pState;
733 mData.mSendCtx.uScreenID = aScreenId;
734
735 mData.mSendCtx.Meta.strFmt = aFormat;
736 mData.mSendCtx.Meta.add(aData);
737
738 LogRel2(("DnD: Host sends data in format '%s'\n", aFormat.c_str()));
739
740 pTask = new GuestDnDSendDataTask(this, &mData.mSendCtx);
741 if (!pTask->isOk())
742 {
743 delete pTask;
744 LogRel(("DnD: Could not create SendDataTask object\n"));
745 throw hr = E_FAIL;
746 }
747
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);
751 pTask = NULL; /* Note: pTask is now owned by the worker thread. */
752 }
753 catch (std::bad_alloc &)
754 {
755 hr = E_OUTOFMEMORY;
756 }
757 catch (...)
758 {
759 LogRel(("DnD: Could not create thread for data sending task\n"));
760 hr = E_FAIL;
761 }
762
763 if (SUCCEEDED(hr))
764 {
765 /* Register ourselves at the DnD manager. */
766 GuestDnDInst()->registerTarget(this);
767
768 /* Return progress to caller. */
769 hr = pState->queryProgressTo(aProgress.asOutParam());
770 ComAssertComRC(hr);
771 }
772 else
773 hr = i_setErrorAndReset(tr("Starting thread for GuestDnDTarget failed (%Rhrc)"), hr);
774
775 LogFlowFunc(("Returning hr=%Rhrc\n", hr));
776 return hr;
777#endif /* VBOX_WITH_DRAG_AND_DROP */
778}
779
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 */
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 "
796 "elements can be accessed and that your guest user has the appropriate rights"));
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"
803 "altered while the drag and drop operation was in progress"));
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 "
809 "the appropriate rights"));
810 break;
811
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
816 default:
817 strError += Utf8StrFmt(tr("Drag and drop error from guest (%Rrc)"), guestRc);
818 break;
819 }
820
821 return strError;
822}
823
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 */
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
864/**
865 * Resets all internal data and state.
866 */
867void GuestDnDTarget::i_reset(void)
868{
869 LogRel2(("DnD: Target reset\n"));
870
871 mData.mSendCtx.reset();
872
873 m_fIsPending = false;
874
875 /* Unregister ourselves from the DnD manager. */
876 GuestDnDInst()->unregisterTarget(this);
877}
878
879/**
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.
885 */
886int GuestDnDTarget::i_sendData(GuestDnDSendCtx *pCtx, RTMSINTERVAL msTimeout)
887{
888 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
889
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"));
893
894 /* Clear all remaining outgoing messages. */
895 m_DataBase.lstMsgOut.clear();
896
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.
901 * If the guest does not want a transfer list ("text/uri-list") but text ("TEXT" and
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
904 * instead of a transfer list (pointing to a file on the guest itself).
905 *
906 ** @todo Support more than one format; add a format<->function handler concept. Later. */
907 int vrc;
908 const bool fHasURIList = std::find(m_lstFmtOffered.begin(),
909 m_lstFmtOffered.end(), "text/uri-list") != m_lstFmtOffered.end();
910 if (fHasURIList)
911 {
912 vrc = i_sendTransferData(pCtx, msTimeout);
913 }
914 else
915 {
916 vrc = i_sendRawData(pCtx, msTimeout);
917 }
918
919 GuestDnDState *pState = GuestDnDInst()->getState();
920 AssertPtrReturn(pState, E_POINTER);
921
922 if (RT_SUCCESS(vrc))
923 {
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. */
940 sendCancel();
941 }
942
943 /* Reset state. */
944 i_reset();
945
946 LogFlowFuncLeaveRC(vrc);
947 return vrc;
948}
949
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)
957{
958 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
959
960 uint8_t *pvData = (uint8_t *)pCtx->Meta.pvData;
961 size_t cbData = pCtx->Meta.cbData;
962
963 int vrc = VINF_SUCCESS;
964
965 const size_t cbFmt = pCtx->Meta.strFmt.length() + 1; /* Include terminator. */
966 const char *pcszFmt = pCtx->Meta.strFmt.c_str();
967
968 LogFlowFunc(("uProtoVer=%RU32, szFmt=%s, cbFmt=%RU32, cbData=%zu\n", m_pState->m_uProtocolVersion, pcszFmt, cbFmt, cbData));
969
970 LogRel2(("DnD: Sending meta data to guest as '%s' (%zu bytes)\n", pcszFmt, cbData));
971
972#ifdef DEBUG
973 RTCList<RTCString> lstFilesURI = RTCString((char *)pvData, cbData).split(DND_PATH_SEPARATOR_STR);
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)
982 {
983 GuestDnDMsg Msg;
984 Msg.setType(HOST_DND_FN_HG_SND_DATA);
985
986 if (m_pState->m_uProtocolVersion < 3)
987 {
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 */
992 /* Fill in the current data block size to send.
993 * Note: Only supports uint32_t. */
994 Msg.appendUInt32((uint32_t)cbChunk); /* cbData */
995 }
996 else
997 {
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. */
1003 }
1004
1005 vrc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
1006 if (RT_FAILURE(vrc))
1007 break;
1008
1009 pvChunk += cbChunk;
1010 AssertBreakStmt(cbData >= cbChunk, VERR_BUFFER_UNDERFLOW);
1011 cbData -= cbChunk;
1012 }
1013
1014 if (RT_SUCCESS(vrc))
1015 {
1016 vrc = updateProgress(pCtx, pCtx->pState, (uint32_t)pCtx->Meta.cbData);
1017 AssertRC(vrc);
1018 }
1019
1020 LogFlowFuncLeaveRC(vrc);
1021 return vrc;
1022}
1023
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)
1031{
1032 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1033
1034 if (m_pState->m_uProtocolVersion < 3) /* Protocol < v3 did not support this, skip. */
1035 return VINF_SUCCESS;
1036
1037 GuestDnDMsg Msg;
1038 Msg.setType(HOST_DND_FN_HG_SND_DATA_HDR);
1039
1040 LogRel2(("DnD: Sending meta data header to guest (%RU64 bytes total data, %RU32 bytes meta data, %RU64 objects)\n",
1041 pCtx->getTotalAnnounced(), pCtx->Meta.cbData, pCtx->Transfer.cObjToProcess));
1042
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. */
1055
1056 int vrc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
1057
1058 LogFlowFuncLeaveRC(vrc);
1059 return vrc;
1060}
1061
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 */
1070int GuestDnDTarget::i_sendDirectory(GuestDnDSendCtx *pCtx, PDNDTRANSFEROBJECT pObj, GuestDnDMsg *pMsg)
1071{
1072 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1073 AssertPtrReturn(pMsg, VERR_INVALID_POINTER);
1074
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);
1079
1080 LogRel2(("DnD: Transferring host directory '%s' to guest\n", DnDTransferObjectGetSourcePath(pObj)));
1081
1082 pMsg->setType(HOST_DND_FN_HG_SND_DIR);
1083 if (m_pState->m_uProtocolVersion >= 3)
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 */
1088
1089 return VINF_SUCCESS;
1090}
1091
1092/**
1093 * Sends a file to the guest.
1094 *
1095 * @returns VBox status code.
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.
1099 */
1100int GuestDnDTarget::i_sendFile(GuestDnDSendCtx *pCtx,
1101 PDNDTRANSFEROBJECT pObj, GuestDnDMsg *pMsg)
1102{
1103 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1104 AssertPtrReturn(pObj, VERR_INVALID_POINTER);
1105 AssertPtrReturn(pMsg, VERR_INVALID_POINTER);
1106
1107 const char *pcszSrcPath = DnDTransferObjectGetSourcePath(pObj);
1108 AssertPtrReturn(pcszSrcPath, VERR_INVALID_POINTER);
1109 const char *pcszDstPath = DnDTransferObjectGetDestPath(pObj);
1110 AssertPtrReturn(pcszDstPath, VERR_INVALID_POINTER);
1111
1112 int vrc = VINF_SUCCESS;
1113
1114 if (!DnDTransferObjectIsOpen(pObj))
1115 {
1116 LogRel2(("DnD: Opening host file '%s' for transferring to guest\n", pcszSrcPath));
1117
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))
1121 LogRel(("DnD: Opening host file '%s' failed, rc=%Rrc\n", pcszSrcPath, vrc));
1122 }
1123
1124 if (RT_FAILURE(vrc))
1125 return vrc;
1126
1127 bool fSendData = false;
1128 if (RT_SUCCESS(vrc)) /** @todo r=aeichner Could save an identation level here as there is a error check above already... */
1129 {
1130 if (m_pState->m_uProtocolVersion >= 2)
1131 {
1132 if (!(pCtx->Transfer.fObjState & DND_OBJ_STATE_HAS_HDR))
1133 {
1134 const size_t cchDstPath = RTStrNLen(pcszDstPath, RTPATH_MAX);
1135 const size_t cbSize = DnDTransferObjectGetSize(pObj);
1136 const RTFMODE fMode = DnDTransferObjectGetMode(pObj);
1137
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 */
1143 pMsg->setType(HOST_DND_FN_HG_SND_FILE_HDR);
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 */
1150
1151 LogRel2(("DnD: Transferring host file '%s' to guest (as '%s', %zu bytes, mode %#x)\n",
1152 pcszSrcPath, pcszDstPath, cbSize, fMode));
1153
1154 /** @todo Set progress object title to current file being transferred? */
1155
1156 /* Update object state to reflect that we have sent the file header. */
1157 pCtx->Transfer.fObjState |= DND_OBJ_STATE_HAS_HDR;
1158 }
1159 else
1160 {
1161 /* File header was sent, so only send the actual file data. */
1162 fSendData = true;
1163 }
1164 }
1165 else /* Protocol v1. */
1166 {
1167 /* Always send the file data, every time. */
1168 fSendData = true;
1169 }
1170 }
1171
1172 if ( RT_SUCCESS(vrc)
1173 && fSendData)
1174 {
1175 vrc = i_sendFileData(pCtx, pObj, pMsg);
1176 }
1177
1178 if (RT_FAILURE(vrc))
1179 LogRel(("DnD: Sending host file '%s' to guest failed, rc=%Rrc\n", pcszSrcPath, vrc));
1180
1181 LogFlowFuncLeaveRC(vrc);
1182 return vrc;
1183}
1184
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 */
1193int GuestDnDTarget::i_sendFileData(GuestDnDSendCtx *pCtx,
1194 PDNDTRANSFEROBJECT pObj, GuestDnDMsg *pMsg)
1195{
1196 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1197 AssertPtrReturn(pObj, VERR_INVALID_POINTER);
1198 AssertPtrReturn(pMsg, VERR_INVALID_POINTER);
1199
1200 AssertPtrReturn(pCtx->pState, VERR_WRONG_ORDER);
1201
1202 /** @todo Don't allow concurrent reads per context! */
1203
1204 /* Set the message type. */
1205 pMsg->setType(HOST_DND_FN_HG_SND_FILE_DATA);
1206
1207 const char *pcszSrcPath = DnDTransferObjectGetSourcePath(pObj);
1208 const char *pcszDstPath = DnDTransferObjectGetDestPath(pObj);
1209
1210 /* Protocol version 1 sends the file path *every* time with a new file chunk.
1211 * In protocol version 2 we only do this once with HOST_DND_FN_HG_SND_FILE_HDR. */
1212 if (m_pState->m_uProtocolVersion <= 1)
1213 {
1214 const size_t cchDstPath = RTStrNLen(pcszDstPath, RTPATH_MAX);
1215
1216 pMsg->appendString(pcszDstPath); /* pvName */
1217 pMsg->appendUInt32((uint32_t)cchDstPath + 1); /* cbName */
1218 }
1219 else if (m_pState->m_uProtocolVersion >= 2)
1220 {
1221 pMsg->appendUInt32(0); /** @todo ContextID not used yet. */
1222 }
1223
1224 void *pvBuf = pCtx->Transfer.pvScratchBuf;
1225 AssertPtr(pvBuf);
1226 size_t cbBuf = pCtx->Transfer.cbScratchBuf;
1227 Assert(cbBuf);
1228
1229 uint32_t cbRead;
1230
1231 int vrc = DnDTransferObjectRead(pObj, pvBuf, cbBuf, &cbRead);
1232 if (RT_SUCCESS(vrc))
1233 {
1234 LogFlowFunc(("cbBufe=%zu, cbRead=%RU32\n", cbBuf, cbRead));
1235
1236 if (m_pState->m_uProtocolVersion <= 1)
1237 {
1238 pMsg->appendPointer(pvBuf, cbRead); /* pvData */
1239 pMsg->appendUInt32(cbRead); /* cbData */
1240 pMsg->appendUInt32(DnDTransferObjectGetMode(pObj)); /* fMode */
1241 }
1242 else /* Protocol v2 and up. */
1243 {
1244 pMsg->appendPointer(pvBuf, cbRead); /* pvData */
1245 pMsg->appendUInt32(cbRead); /* cbData */
1246
1247 if (m_pState->m_uProtocolVersion >= 3)
1248 {
1249 /** @todo Calculate checksum. */
1250 pMsg->appendPointer(NULL, 0); /* pvChecksum */
1251 pMsg->appendUInt32(0); /* cbChecksum */
1252 }
1253 }
1254
1255 int vrc2 = updateProgress(pCtx, pCtx->pState, (uint32_t)cbRead);
1256 AssertRC(vrc2);
1257
1258 /* DnDTransferObjectRead() will return VINF_EOF if reading is complete. */
1259 if (vrc == VINF_EOF)
1260 vrc = VINF_SUCCESS;
1261
1262 if (DnDTransferObjectIsComplete(pObj)) /* Done reading? */
1263 LogRel2(("DnD: Transferring host file '%s' to guest complete\n", pcszSrcPath));
1264 }
1265 else
1266 LogRel(("DnD: Reading from host file '%s' failed, vrc=%Rrc\n", pcszSrcPath, vrc));
1267
1268 LogFlowFuncLeaveRC(vrc);
1269 return vrc;
1270}
1271
1272/**
1273 * Static HGCM service callback which handles sending transfer data to the guest.
1274 *
1275 * @returns VBox status code. Will get sent back to the host service.
1276 * @param uMsg HGCM message ID (function number).
1277 * @param pvParms Pointer to additional message data. Optional and can be NULL.
1278 * @param cbParms Size (in bytes) additional message data. Optional and can be 0.
1279 * @param pvUser User-supplied pointer on callback registration.
1280 */
1281/* static */
1282DECLCALLBACK(int) GuestDnDTarget::i_sendTransferDataCallback(uint32_t uMsg, void *pvParms, size_t cbParms, void *pvUser)
1283{
1284 GuestDnDSendCtx *pCtx = (GuestDnDSendCtx *)pvUser;
1285 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1286
1287 GuestDnDTarget *pThis = pCtx->pTarget;
1288 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
1289
1290 /* At the moment we only have one transfer list per transfer. */
1291 PDNDTRANSFERLIST pList = &pCtx->Transfer.List;
1292
1293 LogFlowFunc(("pThis=%p, pList=%p, uMsg=%RU32\n", pThis, pList, uMsg));
1294
1295 int vrc = VINF_SUCCESS;
1296 int vrcGuest = VINF_SUCCESS; /* Contains error code from guest in case of VERR_DND_GUEST_ERROR. */
1297 bool fNotify = false;
1298
1299 switch (uMsg)
1300 {
1301 case GUEST_DND_FN_CONNECT:
1302 /* Nothing to do here (yet). */
1303 break;
1304
1305 case GUEST_DND_FN_DISCONNECT:
1306 vrc = VERR_CANCELLED;
1307 break;
1308
1309 case GUEST_DND_FN_GET_NEXT_HOST_MSG:
1310 {
1311 PVBOXDNDCBHGGETNEXTHOSTMSG pCBData = reinterpret_cast<PVBOXDNDCBHGGETNEXTHOSTMSG>(pvParms);
1312 AssertPtr(pCBData);
1313 AssertReturn(sizeof(VBOXDNDCBHGGETNEXTHOSTMSG) == cbParms, VERR_INVALID_PARAMETER);
1314 AssertReturn(CB_MAGIC_DND_HG_GET_NEXT_HOST_MSG == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1315
1316 try
1317 {
1318 GuestDnDMsg *pMsg = new GuestDnDMsg();
1319
1320 vrc = pThis->i_sendTransferListObject(pCtx, pList, pMsg);
1321 if (vrc == VINF_EOF) /* Transfer complete? */
1322 {
1323 LogFlowFunc(("Last transfer item processed, bailing out\n"));
1324 }
1325 else if (RT_SUCCESS(vrc))
1326 {
1327 vrc = pThis->msgQueueAdd(pMsg);
1328 if (RT_SUCCESS(vrc)) /* Return message type & required parameter count to the guest. */
1329 {
1330 LogFlowFunc(("GUEST_DND_FN_GET_NEXT_HOST_MSG -> %RU32 (%RU32 params)\n", pMsg->getType(), pMsg->getCount()));
1331 pCBData->uMsg = pMsg->getType();
1332 pCBData->cParms = pMsg->getCount();
1333 }
1334 }
1335
1336 if ( RT_FAILURE(vrc)
1337 || vrc == VINF_EOF) /* Transfer complete? */
1338 {
1339 delete pMsg;
1340 pMsg = NULL;
1341 }
1342 }
1343 catch(std::bad_alloc & /*e*/)
1344 {
1345 vrc = VERR_NO_MEMORY;
1346 }
1347 break;
1348 }
1349 case GUEST_DND_FN_EVT_ERROR:
1350 {
1351 PVBOXDNDCBEVTERRORDATA pCBData = reinterpret_cast<PVBOXDNDCBEVTERRORDATA>(pvParms);
1352 AssertPtr(pCBData);
1353 AssertReturn(sizeof(VBOXDNDCBEVTERRORDATA) == cbParms, VERR_INVALID_PARAMETER);
1354 AssertReturn(CB_MAGIC_DND_EVT_ERROR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1355
1356 pCtx->pState->reset();
1357
1358 if (RT_SUCCESS(pCBData->rc))
1359 {
1360 AssertMsgFailed(("Guest has sent an error event but did not specify an actual error code\n"));
1361 pCBData->rc = VERR_GENERAL_FAILURE; /* Make sure some error is set. */
1362 }
1363
1364 vrc = pCtx->pState->setProgress(100, DND_PROGRESS_ERROR, pCBData->rc,
1365 GuestDnDTarget::i_guestErrorToString(pCBData->rc));
1366 if (RT_SUCCESS(vrc))
1367 {
1368 vrc = VERR_DND_GUEST_ERROR;
1369 vrcGuest = pCBData->rc;
1370 }
1371 break;
1372 }
1373 case HOST_DND_FN_HG_SND_DIR:
1374 case HOST_DND_FN_HG_SND_FILE_HDR:
1375 case HOST_DND_FN_HG_SND_FILE_DATA:
1376 {
1377 PVBOXDNDCBHGGETNEXTHOSTMSGDATA pCBData
1378 = reinterpret_cast<PVBOXDNDCBHGGETNEXTHOSTMSGDATA>(pvParms);
1379 AssertPtr(pCBData);
1380 AssertReturn(sizeof(VBOXDNDCBHGGETNEXTHOSTMSGDATA) == cbParms, VERR_INVALID_PARAMETER);
1381
1382 LogFlowFunc(("pCBData->uMsg=%RU32, paParms=%p, cParms=%RU32\n", pCBData->uMsg, pCBData->paParms, pCBData->cParms));
1383
1384 GuestDnDMsg *pMsg = pThis->msgQueueGetNext();
1385 if (pMsg)
1386 {
1387 /*
1388 * Sanity checks.
1389 */
1390 if ( pCBData->uMsg != uMsg
1391 || pCBData->paParms == NULL
1392 || pCBData->cParms != pMsg->getCount())
1393 {
1394 LogFlowFunc(("Current message does not match:\n"));
1395 LogFlowFunc(("\tCallback: uMsg=%RU32, cParms=%RU32, paParms=%p\n",
1396 pCBData->uMsg, pCBData->cParms, pCBData->paParms));
1397 LogFlowFunc(("\t Next: uMsg=%RU32, cParms=%RU32\n", pMsg->getType(), pMsg->getCount()));
1398
1399 /* Start over. */
1400 pThis->msgQueueClear();
1401
1402 vrc = VERR_INVALID_PARAMETER;
1403 }
1404
1405 if (RT_SUCCESS(vrc))
1406 {
1407 LogFlowFunc(("Returning uMsg=%RU32\n", uMsg));
1408 vrc = HGCM::Message::CopyParms(pCBData->paParms, pCBData->cParms, pMsg->getParms(), pMsg->getCount(),
1409 false /* fDeepCopy */);
1410 if (RT_SUCCESS(vrc))
1411 {
1412 pCBData->cParms = pMsg->getCount();
1413 pThis->msgQueueRemoveNext();
1414 }
1415 else
1416 LogFlowFunc(("Copying parameters failed with vrc=%Rrc\n", vrc));
1417 }
1418 }
1419 else
1420 vrc = VERR_NO_DATA;
1421
1422 LogFlowFunc(("Processing next message ended with vrc=%Rrc\n", vrc));
1423 break;
1424 }
1425 default:
1426 vrc = VERR_NOT_SUPPORTED;
1427 break;
1428 }
1429
1430 int vrcToGuest = VINF_SUCCESS; /* Status which will be sent back to the guest. */
1431
1432 /*
1433 * Resolve errors.
1434 */
1435 switch (vrc)
1436 {
1437 case VINF_SUCCESS:
1438 break;
1439
1440 case VINF_EOF:
1441 {
1442 LogRel2(("DnD: Transfer to guest complete\n"));
1443
1444 /* Complete operation on host side. */
1445 fNotify = true;
1446
1447 /* The guest expects VERR_NO_DATA if the transfer is complete. */
1448 vrcToGuest = VERR_NO_DATA;
1449 break;
1450 }
1451
1452 case VERR_DND_GUEST_ERROR:
1453 {
1454 LogRel(("DnD: Guest reported error %Rrc, aborting transfer to guest\n", vrcGuest));
1455 break;
1456 }
1457
1458 case VERR_CANCELLED:
1459 {
1460 LogRel2(("DnD: Transfer to guest canceled\n"));
1461 vrcToGuest = VERR_CANCELLED; /* Also cancel on guest side. */
1462 break;
1463 }
1464
1465 default:
1466 {
1467 LogRel(("DnD: Host error %Rrc occurred, aborting transfer to guest\n", vrc));
1468 vrcToGuest = VERR_CANCELLED; /* Also cancel on guest side. */
1469 break;
1470 }
1471 }
1472
1473 if (RT_FAILURE(vrc))
1474 {
1475 /* Unregister this callback. */
1476 AssertPtr(pCtx->pState);
1477 int vrc2 = pCtx->pState->setCallback(uMsg, NULL /* PFNGUESTDNDCALLBACK */);
1478 AssertRC(vrc2);
1479
1480 /* Let the waiter(s) know. */
1481 fNotify = true;
1482 }
1483
1484 LogFlowFunc(("fNotify=%RTbool, vrc=%Rrc, vrcToGuest=%Rrc\n", fNotify, vrc, vrcToGuest));
1485
1486 if (fNotify)
1487 {
1488 int vrc2 = pCtx->EventCallback.Notify(vrc); /** @todo Also pass guest error back? */
1489 AssertRC(vrc2);
1490 }
1491
1492 LogFlowFuncLeaveRC(vrc);
1493 return vrcToGuest; /* Tell the guest. */
1494}
1495
1496/**
1497 * Main function for sending the actual transfer data (i.e. files + directories) to the guest.
1498 *
1499 * @returns VBox status code.
1500 * @param pCtx Send context to use.
1501 * @param msTimeout Timeout (in ms) to use for getting the data sent.
1502 */
1503int GuestDnDTarget::i_sendTransferData(GuestDnDSendCtx *pCtx, RTMSINTERVAL msTimeout)
1504{
1505 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1506 AssertPtr(pCtx->pState);
1507
1508#define REGISTER_CALLBACK(x) \
1509 do { \
1510 vrc = pCtx->pState->setCallback(x, i_sendTransferDataCallback, pCtx); \
1511 if (RT_FAILURE(vrc)) \
1512 return vrc; \
1513 } while (0)
1514
1515#define UNREGISTER_CALLBACK(x) \
1516 do { \
1517 int vrc2 = pCtx->pState->setCallback(x, NULL); \
1518 AssertRC(vrc2); \
1519 } while (0)
1520
1521 int vrc = pCtx->Transfer.init(mData.mcbBlockSize);
1522 if (RT_FAILURE(vrc))
1523 return vrc;
1524
1525 vrc = pCtx->EventCallback.Reset();
1526 if (RT_FAILURE(vrc))
1527 return vrc;
1528
1529 /*
1530 * Register callbacks.
1531 */
1532 /* Guest callbacks. */
1533 REGISTER_CALLBACK(GUEST_DND_FN_CONNECT);
1534 REGISTER_CALLBACK(GUEST_DND_FN_DISCONNECT);
1535 REGISTER_CALLBACK(GUEST_DND_FN_GET_NEXT_HOST_MSG);
1536 REGISTER_CALLBACK(GUEST_DND_FN_EVT_ERROR);
1537 /* Host callbacks. */
1538 REGISTER_CALLBACK(HOST_DND_FN_HG_SND_DIR);
1539 if (m_pState->m_uProtocolVersion >= 2)
1540 REGISTER_CALLBACK(HOST_DND_FN_HG_SND_FILE_HDR);
1541 REGISTER_CALLBACK(HOST_DND_FN_HG_SND_FILE_DATA);
1542
1543 do
1544 {
1545 /*
1546 * Extract transfer list from current meta data.
1547 */
1548 vrc = DnDTransferListAppendPathsFromBuffer(&pCtx->Transfer.List, DNDTRANSFERLISTFMT_URI,
1549 (const char *)pCtx->Meta.pvData, pCtx->Meta.cbData, DND_PATH_SEPARATOR_STR,
1550 DNDTRANSFERLIST_FLAGS_RECURSIVE);
1551 if (RT_FAILURE(vrc))
1552 break;
1553
1554 /*
1555 * Update internal state to reflect everything we need to work with it.
1556 */
1557 pCtx->cbExtra = DnDTransferListObjTotalBytes(&pCtx->Transfer.List);
1558 /* cbExtra can be 0, if all files are of 0 bytes size. */
1559 pCtx->Transfer.cObjToProcess = DnDTransferListObjCount(&pCtx->Transfer.List);
1560 AssertBreakStmt(pCtx->Transfer.cObjToProcess, vrc = VERR_INVALID_PARAMETER);
1561
1562 /* Update the meta data to have the current root transfer entries in the right shape. */
1563 if (DnDMIMEHasFileURLs(pCtx->Meta.strFmt.c_str(), RTSTR_MAX))
1564 {
1565 /* Save original format we're still going to use after updating the actual meta data. */
1566 Utf8Str strFmt = pCtx->Meta.strFmt;
1567
1568 /* Reset stale data. */
1569 pCtx->Meta.reset();
1570
1571 void *pvData;
1572 size_t cbData;
1573#ifdef DEBUG
1574 vrc = DnDTransferListGetRootsEx(&pCtx->Transfer.List, DNDTRANSFERLISTFMT_URI, "" /* pcszPathBase */,
1575 "\n" /* pcszSeparator */, (char **)&pvData, &cbData);
1576 AssertRCReturn(vrc, vrc);
1577 LogFlowFunc(("URI data:\n%s", (char *)pvData));
1578 RTMemFree(pvData);
1579 cbData = 0;
1580#endif
1581 vrc = DnDTransferListGetRoots(&pCtx->Transfer.List, DNDTRANSFERLISTFMT_URI,
1582 (char **)&pvData, &cbData);
1583 AssertRCReturn(vrc, vrc);
1584
1585 /* pCtx->Meta now owns the allocated data. */
1586 pCtx->Meta.strFmt = strFmt;
1587 pCtx->Meta.pvData = pvData;
1588 pCtx->Meta.cbData = cbData;
1589 pCtx->Meta.cbAllocated = cbData;
1590 pCtx->Meta.cbAnnounced = cbData;
1591 }
1592
1593 /*
1594 * The first message always is the data header. The meta data itself then follows
1595 * and *only* contains the root elements of a transfer list.
1596 *
1597 * After the meta data we generate the messages required to send the
1598 * file/directory data itself.
1599 *
1600 * Note: Protocol < v3 use the first data message to tell what's being sent.
1601 */
1602
1603 /*
1604 * Send the data header first.
1605 */
1606 if (m_pState->m_uProtocolVersion >= 3)
1607 vrc = i_sendMetaDataHeader(pCtx);
1608
1609 /*
1610 * Send the (meta) data body.
1611 */
1612 if (RT_SUCCESS(vrc))
1613 vrc = i_sendMetaDataBody(pCtx);
1614
1615 if (RT_SUCCESS(vrc))
1616 {
1617 vrc = waitForEvent(&pCtx->EventCallback, pCtx->pState, msTimeout);
1618 if (RT_SUCCESS(vrc))
1619 pCtx->pState->setProgress(100, DND_PROGRESS_COMPLETE, VINF_SUCCESS);
1620 }
1621
1622 } while (0);
1623
1624 /*
1625 * Unregister callbacks.
1626 */
1627 /* Guest callbacks. */
1628 UNREGISTER_CALLBACK(GUEST_DND_FN_CONNECT);
1629 UNREGISTER_CALLBACK(GUEST_DND_FN_DISCONNECT);
1630 UNREGISTER_CALLBACK(GUEST_DND_FN_GET_NEXT_HOST_MSG);
1631 UNREGISTER_CALLBACK(GUEST_DND_FN_EVT_ERROR);
1632 /* Host callbacks. */
1633 UNREGISTER_CALLBACK(HOST_DND_FN_HG_SND_DIR);
1634 if (m_pState->m_uProtocolVersion >= 2)
1635 UNREGISTER_CALLBACK(HOST_DND_FN_HG_SND_FILE_HDR);
1636 UNREGISTER_CALLBACK(HOST_DND_FN_HG_SND_FILE_DATA);
1637
1638#undef REGISTER_CALLBACK
1639#undef UNREGISTER_CALLBACK
1640
1641 if (RT_FAILURE(vrc))
1642 {
1643 if (vrc == VERR_CANCELLED) /* Transfer was cancelled by the host. */
1644 {
1645 /*
1646 * Now that we've cleaned up tell the guest side to cancel.
1647 * This does not imply we're waiting for the guest to react, as the
1648 * host side never must depend on anything from the guest.
1649 */
1650 int vrc2 = sendCancel();
1651 AssertRC(vrc2);
1652
1653 LogRel2(("DnD: Sending transfer data to guest cancelled by user\n"));
1654
1655 vrc2 = pCtx->pState->setProgress(100, DND_PROGRESS_CANCELLED, VINF_SUCCESS);
1656 AssertRC(vrc2);
1657
1658 /* Cancelling is not an error, just set success here. */
1659 vrc = VINF_SUCCESS;
1660 }
1661 else if (vrc != VERR_DND_GUEST_ERROR) /* Guest-side error are already handled in the callback. */
1662 {
1663 LogRel(("DnD: Sending transfer data to guest failed with vrc=%Rrc\n", vrc));
1664 int vrc2 = pCtx->pState->setProgress(100, DND_PROGRESS_ERROR, vrc,
1665 GuestDnDTarget::i_hostErrorToString(vrc));
1666 AssertRC(vrc2);
1667 }
1668 }
1669
1670 LogFlowFuncLeaveRC(vrc);
1671 return vrc;
1672}
1673
1674/**
1675 * Sends the next object of a transfer list to the guest.
1676 *
1677 * @returns VBox status code. VINF_EOF if the transfer list is complete.
1678 * @param pCtx Send context to use.
1679 * @param pList Transfer list to use.
1680 * @param pMsg Message to store send data into.
1681 */
1682int GuestDnDTarget::i_sendTransferListObject(GuestDnDSendCtx *pCtx, PDNDTRANSFERLIST pList, GuestDnDMsg *pMsg)
1683{
1684 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1685 AssertPtrReturn(pList, VERR_INVALID_POINTER);
1686 AssertPtrReturn(pMsg, VERR_INVALID_POINTER);
1687
1688 int vrc = updateProgress(pCtx, pCtx->pState);
1689 AssertRCReturn(vrc, vrc);
1690
1691 PDNDTRANSFEROBJECT pObj = DnDTransferListObjGetFirst(pList);
1692 if (!pObj) /* Transfer complete? */
1693 return VINF_EOF;
1694
1695 switch (DnDTransferObjectGetType(pObj))
1696 {
1697 case DNDTRANSFEROBJTYPE_DIRECTORY:
1698 vrc = i_sendDirectory(pCtx, pObj, pMsg);
1699 break;
1700
1701 case DNDTRANSFEROBJTYPE_FILE:
1702 vrc = i_sendFile(pCtx, pObj, pMsg);
1703 break;
1704
1705 default:
1706 AssertFailedStmt(vrc = VERR_NOT_SUPPORTED);
1707 break;
1708 }
1709
1710 if ( RT_SUCCESS(vrc)
1711 && DnDTransferObjectIsComplete(pObj))
1712 {
1713 DnDTransferListObjRemove(pList, pObj);
1714 pObj = NULL;
1715
1716 AssertReturn(pCtx->Transfer.cObjProcessed + 1 <= pCtx->Transfer.cObjToProcess, VERR_WRONG_ORDER);
1717 pCtx->Transfer.cObjProcessed++;
1718
1719 pCtx->Transfer.fObjState = DND_OBJ_STATE_NONE;
1720 }
1721
1722 LogFlowFuncLeaveRC(vrc);
1723 return vrc;
1724}
1725
1726/**
1727 * Main function for sending raw data (e.g. text, RTF, ...) to the guest.
1728 *
1729 * @returns VBox status code.
1730 * @param pCtx Send context to use.
1731 * @param msTimeout Timeout (in ms) to use for getting the data sent.
1732 */
1733int GuestDnDTarget::i_sendRawData(GuestDnDSendCtx *pCtx, RTMSINTERVAL msTimeout)
1734{
1735 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1736 NOREF(msTimeout);
1737
1738 /** @todo At the moment we only allow sending up to 64K raw data.
1739 * For protocol v1+v2: Fix this by using HOST_DND_FN_HG_SND_MORE_DATA.
1740 * For protocol v3 : Send another HOST_DND_FN_HG_SND_DATA message. */
1741 if (!pCtx->Meta.cbData)
1742 return VINF_SUCCESS;
1743
1744 int vrc = i_sendMetaDataHeader(pCtx);
1745 if (RT_SUCCESS(vrc))
1746 vrc = i_sendMetaDataBody(pCtx);
1747
1748 int vrc2;
1749 if (RT_FAILURE(vrc))
1750 {
1751 LogRel(("DnD: Sending raw data to guest failed with vrc=%Rrc\n", vrc));
1752 vrc2 = pCtx->pState->setProgress(100 /* Percent */, DND_PROGRESS_ERROR, vrc,
1753 GuestDnDTarget::i_hostErrorToString(vrc));
1754 }
1755 else
1756 vrc2 = pCtx->pState->setProgress(100 /* Percent */, DND_PROGRESS_COMPLETE, vrc);
1757 AssertRC(vrc2);
1758
1759 LogFlowFuncLeaveRC(vrc);
1760 return vrc;
1761}
1762
1763/**
1764 * Cancels sending DnD data.
1765 *
1766 * @returns VBox status code.
1767 * @param aVeto Whether cancelling was vetoed or not.
1768 * Not implemented yet.
1769 */
1770HRESULT GuestDnDTarget::cancel(BOOL *aVeto)
1771{
1772#if !defined(VBOX_WITH_DRAG_AND_DROP)
1773 ReturnComNotImplemented();
1774#else /* VBOX_WITH_DRAG_AND_DROP */
1775
1776 LogRel2(("DnD: Sending cancelling request to the guest ...\n"));
1777
1778 int vrc = GuestDnDBase::sendCancel();
1779
1780 if (aVeto)
1781 *aVeto = FALSE; /** @todo Implement vetoing. */
1782
1783 HRESULT hrc = RT_SUCCESS(vrc) ? S_OK : VBOX_E_DND_ERROR;
1784
1785 LogFlowFunc(("hrc=%Rhrc\n", hrc));
1786 return hrc;
1787#endif /* VBOX_WITH_DRAG_AND_DROP */
1788}
1789
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