VirtualBox

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

Last change on this file since 105301 was 104635, checked in by vboxsync, 7 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
Line 
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/*
7 * Copyright (C) 2014-2023 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 pThis->i_sendData(mpCtx, RT_INDEFINITE_WAIT /* msTimeout */); /* ignore return code */
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.hrc();
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.hrc();
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.hrc();
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.hrc();
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.hrc();
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.hrc();
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.hrc();
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.hrc())) return autoCaller.hrc();
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.hrc())) return autoCaller.hrc();
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, vrc = 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, vrc=%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, vrc=%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 AssertReturn(m_pState->m_uProtocolVersion, VERR_WRONG_ORDER);
1213 if (m_pState->m_uProtocolVersion == 1)
1214 {
1215 const size_t cchDstPath = RTStrNLen(pcszDstPath, RTPATH_MAX);
1216
1217 pMsg->appendString(pcszDstPath); /* pvName */
1218 pMsg->appendUInt32((uint32_t)cchDstPath + 1); /* cbName */
1219 }
1220 else if (m_pState->m_uProtocolVersion >= 2)
1221 {
1222 pMsg->appendUInt32(0); /** @todo ContextID not used yet. */
1223 }
1224
1225 void *pvBuf = pCtx->Transfer.pvScratchBuf;
1226 AssertPtr(pvBuf);
1227 size_t cbBuf = pCtx->Transfer.cbScratchBuf;
1228 Assert(cbBuf);
1229
1230 uint32_t cbRead;
1231
1232 int vrc = DnDTransferObjectRead(pObj, pvBuf, cbBuf, &cbRead);
1233 if (RT_SUCCESS(vrc))
1234 {
1235 LogFlowFunc(("cbBufe=%zu, cbRead=%RU32\n", cbBuf, cbRead));
1236
1237 if (m_pState->m_uProtocolVersion <= 1)
1238 {
1239 pMsg->appendPointer(pvBuf, cbRead); /* pvData */
1240 pMsg->appendUInt32(cbRead); /* cbData */
1241 pMsg->appendUInt32(DnDTransferObjectGetMode(pObj)); /* fMode */
1242 }
1243 else /* Protocol v2 and up. */
1244 {
1245 pMsg->appendPointer(pvBuf, cbRead); /* pvData */
1246 pMsg->appendUInt32(cbRead); /* cbData */
1247
1248 if (m_pState->m_uProtocolVersion >= 3)
1249 {
1250 /** @todo Calculate checksum. */
1251 pMsg->appendPointer(NULL, 0); /* pvChecksum */
1252 pMsg->appendUInt32(0); /* cbChecksum */
1253 }
1254 }
1255
1256 int vrc2 = updateProgress(pCtx, pCtx->pState, (uint32_t)cbRead);
1257 AssertRC(vrc2);
1258
1259 /* DnDTransferObjectRead() will return VINF_EOF if reading is complete. */
1260 if (vrc == VINF_EOF)
1261 vrc = VINF_SUCCESS;
1262
1263 if (DnDTransferObjectIsComplete(pObj)) /* Done reading? */
1264 LogRel2(("DnD: Transferring host file '%s' to guest complete\n", pcszSrcPath));
1265 }
1266 else
1267 LogRel(("DnD: Reading from host file '%s' failed, vrc=%Rrc\n", pcszSrcPath, vrc));
1268
1269 LogFlowFuncLeaveRC(vrc);
1270 return vrc;
1271}
1272
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 */
1282/* static */
1283DECLCALLBACK(int) GuestDnDTarget::i_sendTransferDataCallback(uint32_t uMsg, void *pvParms, size_t cbParms, void *pvUser)
1284{
1285 GuestDnDSendCtx *pCtx = (GuestDnDSendCtx *)pvUser;
1286 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1287
1288 GuestDnDTarget *pThis = pCtx->pTarget;
1289 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
1290
1291 /* At the moment we only have one transfer list per transfer. */
1292 PDNDTRANSFERLIST pList = &pCtx->Transfer.List;
1293
1294 LogFlowFunc(("pThis=%p, pList=%p, uMsg=%RU32\n", pThis, pList, uMsg));
1295
1296 int vrc = VINF_SUCCESS;
1297 int vrcGuest = VINF_SUCCESS; /* Contains error code from guest in case of VERR_DND_GUEST_ERROR. */
1298 bool fNotify = false;
1299
1300 switch (uMsg)
1301 {
1302 case GUEST_DND_FN_CONNECT:
1303 /* Nothing to do here (yet). */
1304 break;
1305
1306 case GUEST_DND_FN_DISCONNECT:
1307 vrc = VERR_CANCELLED;
1308 break;
1309
1310 case GUEST_DND_FN_GET_NEXT_HOST_MSG:
1311 {
1312 PVBOXDNDCBHGGETNEXTHOSTMSG pCBData = reinterpret_cast<PVBOXDNDCBHGGETNEXTHOSTMSG>(pvParms);
1313 AssertPtr(pCBData);
1314 AssertReturn(sizeof(VBOXDNDCBHGGETNEXTHOSTMSG) == cbParms, VERR_INVALID_PARAMETER);
1315 AssertReturn(CB_MAGIC_DND_HG_GET_NEXT_HOST_MSG == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1316
1317 try
1318 {
1319 GuestDnDMsg *pMsg = new GuestDnDMsg();
1320
1321 vrc = pThis->i_sendTransferListObject(pCtx, pList, pMsg);
1322 if (vrc == VINF_EOF) /* Transfer complete? */
1323 {
1324 LogFlowFunc(("Last transfer item processed, bailing out\n"));
1325 }
1326 else if (RT_SUCCESS(vrc))
1327 {
1328 vrc = pThis->msgQueueAdd(pMsg);
1329 if (RT_SUCCESS(vrc)) /* Return message type & required parameter count to the guest. */
1330 {
1331 LogFlowFunc(("GUEST_DND_FN_GET_NEXT_HOST_MSG -> %RU32 (%RU32 params)\n", pMsg->getType(), pMsg->getCount()));
1332 pCBData->uMsg = pMsg->getType();
1333 pCBData->cParms = pMsg->getCount();
1334 }
1335 }
1336
1337 if ( RT_FAILURE(vrc)
1338 || vrc == VINF_EOF) /* Transfer complete? */
1339 {
1340 delete pMsg;
1341 pMsg = NULL;
1342 }
1343 }
1344 catch(std::bad_alloc & /*e*/)
1345 {
1346 vrc = VERR_NO_MEMORY;
1347 }
1348 break;
1349 }
1350 case GUEST_DND_FN_EVT_ERROR:
1351 {
1352 PVBOXDNDCBEVTERRORDATA pCBData = reinterpret_cast<PVBOXDNDCBEVTERRORDATA>(pvParms);
1353 AssertPtr(pCBData);
1354 AssertReturn(sizeof(VBOXDNDCBEVTERRORDATA) == cbParms, VERR_INVALID_PARAMETER);
1355 AssertReturn(CB_MAGIC_DND_EVT_ERROR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1356
1357 pCtx->pState->reset();
1358
1359 if (RT_SUCCESS(pCBData->rc))
1360 {
1361 AssertMsgFailed(("Guest has sent an error event but did not specify an actual error code\n"));
1362 pCBData->rc = VERR_GENERAL_FAILURE; /* Make sure some error is set. */
1363 }
1364
1365 vrc = pCtx->pState->setProgress(100, DND_PROGRESS_ERROR, pCBData->rc,
1366 GuestDnDTarget::i_guestErrorToString(pCBData->rc));
1367 if (RT_SUCCESS(vrc))
1368 {
1369 vrc = VERR_DND_GUEST_ERROR;
1370 vrcGuest = pCBData->rc;
1371 }
1372 break;
1373 }
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:
1377 {
1378 PVBOXDNDCBHGGETNEXTHOSTMSGDATA pCBData
1379 = reinterpret_cast<PVBOXDNDCBHGGETNEXTHOSTMSGDATA>(pvParms);
1380 AssertPtr(pCBData);
1381 AssertReturn(sizeof(VBOXDNDCBHGGETNEXTHOSTMSGDATA) == cbParms, VERR_INVALID_PARAMETER);
1382
1383 LogFlowFunc(("pCBData->uMsg=%RU32, paParms=%p, cParms=%RU32\n", pCBData->uMsg, pCBData->paParms, pCBData->cParms));
1384
1385 GuestDnDMsg *pMsg = pThis->msgQueueGetNext();
1386 if (pMsg)
1387 {
1388 /*
1389 * Sanity checks.
1390 */
1391 if ( pCBData->uMsg != uMsg
1392 || pCBData->paParms == NULL
1393 || pCBData->cParms != pMsg->getCount())
1394 {
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
1400 /* Start over. */
1401 pThis->msgQueueClear();
1402
1403 vrc = VERR_INVALID_PARAMETER;
1404 }
1405
1406 if (RT_SUCCESS(vrc))
1407 {
1408 LogFlowFunc(("Returning uMsg=%RU32\n", uMsg));
1409 vrc = HGCM::Message::CopyParms(pCBData->paParms, pCBData->cParms, pMsg->getParms(), pMsg->getCount(),
1410 false /* fDeepCopy */);
1411 if (RT_SUCCESS(vrc))
1412 {
1413 pCBData->cParms = pMsg->getCount();
1414 pThis->msgQueueRemoveNext();
1415 }
1416 else
1417 LogFlowFunc(("Copying parameters failed with vrc=%Rrc\n", vrc));
1418 }
1419 }
1420 else
1421 vrc = VERR_NO_DATA;
1422
1423 LogFlowFunc(("Processing next message ended with vrc=%Rrc\n", vrc));
1424 break;
1425 }
1426 default:
1427 vrc = VERR_NOT_SUPPORTED;
1428 break;
1429 }
1430
1431 int vrcToGuest = VINF_SUCCESS; /* Status which will be sent back to the guest. */
1432
1433 /*
1434 * Resolve errors.
1435 */
1436 switch (vrc)
1437 {
1438 case VINF_SUCCESS:
1439 break;
1440
1441 case VINF_EOF:
1442 {
1443 LogRel2(("DnD: Transfer to guest complete\n"));
1444
1445 /* Complete operation on host side. */
1446 fNotify = true;
1447
1448 /* The guest expects VERR_NO_DATA if the transfer is complete. */
1449 vrcToGuest = VERR_NO_DATA;
1450 break;
1451 }
1452
1453 case VERR_DND_GUEST_ERROR:
1454 {
1455 LogRel(("DnD: Guest reported error %Rrc, aborting transfer to guest\n", vrcGuest));
1456 break;
1457 }
1458
1459 case VERR_CANCELLED:
1460 {
1461 LogRel2(("DnD: Transfer to guest canceled\n"));
1462 vrcToGuest = VERR_CANCELLED; /* Also cancel on guest side. */
1463 break;
1464 }
1465
1466 default:
1467 {
1468 LogRel(("DnD: Host error %Rrc occurred, aborting transfer to guest\n", vrc));
1469 vrcToGuest = VERR_CANCELLED; /* Also cancel on guest side. */
1470 break;
1471 }
1472 }
1473
1474 if (RT_FAILURE(vrc))
1475 {
1476 /* Unregister this callback. */
1477 AssertPtr(pCtx->pState);
1478 int vrc2 = pCtx->pState->setCallback(uMsg, NULL /* PFNGUESTDNDCALLBACK */);
1479 AssertRC(vrc2);
1480
1481 /* Let the waiter(s) know. */
1482 fNotify = true;
1483 }
1484
1485 LogFlowFunc(("fNotify=%RTbool, vrc=%Rrc, vrcToGuest=%Rrc\n", fNotify, vrc, vrcToGuest));
1486
1487 if (fNotify)
1488 {
1489 int vrc2 = pCtx->EventCallback.Notify(vrc); /** @todo Also pass guest error back? */
1490 AssertRC(vrc2);
1491 }
1492
1493 LogFlowFuncLeaveRC(vrc);
1494 return vrcToGuest; /* Tell the guest. */
1495}
1496
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)
1505{
1506 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1507 AssertPtr(pCtx->pState);
1508
1509#define REGISTER_CALLBACK(x) \
1510 do { \
1511 vrc = pCtx->pState->setCallback(x, i_sendTransferDataCallback, pCtx); \
1512 if (RT_FAILURE(vrc)) \
1513 return vrc; \
1514 } while (0)
1515
1516#define UNREGISTER_CALLBACK(x) \
1517 do { \
1518 int vrc2 = pCtx->pState->setCallback(x, NULL); \
1519 AssertRC(vrc2); \
1520 } while (0)
1521
1522 int vrc = pCtx->Transfer.init(mData.mcbBlockSize);
1523 if (RT_FAILURE(vrc))
1524 return vrc;
1525
1526 vrc = pCtx->EventCallback.Reset();
1527 if (RT_FAILURE(vrc))
1528 return vrc;
1529
1530 /*
1531 * Register callbacks.
1532 */
1533 /* Guest callbacks. */
1534 REGISTER_CALLBACK(GUEST_DND_FN_CONNECT);
1535 REGISTER_CALLBACK(GUEST_DND_FN_DISCONNECT);
1536 REGISTER_CALLBACK(GUEST_DND_FN_GET_NEXT_HOST_MSG);
1537 REGISTER_CALLBACK(GUEST_DND_FN_EVT_ERROR);
1538 /* Host callbacks. */
1539 REGISTER_CALLBACK(HOST_DND_FN_HG_SND_DIR);
1540 if (m_pState->m_uProtocolVersion >= 2)
1541 REGISTER_CALLBACK(HOST_DND_FN_HG_SND_FILE_HDR);
1542 REGISTER_CALLBACK(HOST_DND_FN_HG_SND_FILE_DATA);
1543
1544 do
1545 {
1546 /*
1547 * Extract transfer list from current meta data.
1548 */
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))
1553 break;
1554
1555 /*
1556 * Update internal state to reflect everything we need to work with it.
1557 */
1558 pCtx->cbExtra = DnDTransferListObjTotalBytes(&pCtx->Transfer.List);
1559 /* cbExtra can be 0, if all files are of 0 bytes size. */
1560 pCtx->Transfer.cObjToProcess = DnDTransferListObjCount(&pCtx->Transfer.List);
1561 AssertBreakStmt(pCtx->Transfer.cObjToProcess, vrc = VERR_INVALID_PARAMETER);
1562
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
1575 vrc = DnDTransferListGetRootsEx(&pCtx->Transfer.List, DNDTRANSFERLISTFMT_URI, "" /* pcszPathBase */,
1576 "\n" /* pcszSeparator */, (char **)&pvData, &cbData);
1577 AssertRCReturn(vrc, vrc);
1578 LogFlowFunc(("URI data:\n%s", (char *)pvData));
1579 RTMemFree(pvData);
1580 cbData = 0;
1581#endif
1582 vrc = DnDTransferListGetRoots(&pCtx->Transfer.List, DNDTRANSFERLISTFMT_URI,
1583 (char **)&pvData, &cbData);
1584 AssertRCReturn(vrc, vrc);
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;
1591 pCtx->Meta.cbAnnounced = cbData;
1592 }
1593
1594 /*
1595 * The first message always is the data header. The meta data itself then follows
1596 * and *only* contains the root elements of a transfer list.
1597 *
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.
1602 */
1603
1604 /*
1605 * Send the data header first.
1606 */
1607 if (m_pState->m_uProtocolVersion >= 3)
1608 vrc = i_sendMetaDataHeader(pCtx);
1609
1610 /*
1611 * Send the (meta) data body.
1612 */
1613 if (RT_SUCCESS(vrc))
1614 vrc = i_sendMetaDataBody(pCtx);
1615
1616 if (RT_SUCCESS(vrc))
1617 {
1618 vrc = waitForEvent(&pCtx->EventCallback, pCtx->pState, msTimeout);
1619 if (RT_SUCCESS(vrc))
1620 pCtx->pState->setProgress(100, DND_PROGRESS_COMPLETE, VINF_SUCCESS);
1621 }
1622
1623 } while (0);
1624
1625 /*
1626 * Unregister callbacks.
1627 */
1628 /* Guest callbacks. */
1629 UNREGISTER_CALLBACK(GUEST_DND_FN_CONNECT);
1630 UNREGISTER_CALLBACK(GUEST_DND_FN_DISCONNECT);
1631 UNREGISTER_CALLBACK(GUEST_DND_FN_GET_NEXT_HOST_MSG);
1632 UNREGISTER_CALLBACK(GUEST_DND_FN_EVT_ERROR);
1633 /* Host callbacks. */
1634 UNREGISTER_CALLBACK(HOST_DND_FN_HG_SND_DIR);
1635 if (m_pState->m_uProtocolVersion >= 2)
1636 UNREGISTER_CALLBACK(HOST_DND_FN_HG_SND_FILE_HDR);
1637 UNREGISTER_CALLBACK(HOST_DND_FN_HG_SND_FILE_DATA);
1638
1639#undef REGISTER_CALLBACK
1640#undef UNREGISTER_CALLBACK
1641
1642 if (RT_FAILURE(vrc))
1643 {
1644 if (vrc == VERR_CANCELLED) /* Transfer was cancelled by the host. */
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 */
1651 int vrc2 = sendCancel();
1652 AssertRC(vrc2);
1653
1654 LogRel2(("DnD: Sending transfer data to guest cancelled by user\n"));
1655
1656 vrc2 = pCtx->pState->setProgress(100, DND_PROGRESS_CANCELLED, VINF_SUCCESS);
1657 AssertRC(vrc2);
1658
1659 /* Cancelling is not an error, just set success here. */
1660 vrc = VINF_SUCCESS;
1661 }
1662 else if (vrc != VERR_DND_GUEST_ERROR) /* Guest-side error are already handled in the callback. */
1663 {
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);
1668 }
1669 }
1670
1671 LogFlowFuncLeaveRC(vrc);
1672 return vrc;
1673}
1674
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)
1684{
1685 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1686 AssertPtrReturn(pList, VERR_INVALID_POINTER);
1687 AssertPtrReturn(pMsg, VERR_INVALID_POINTER);
1688
1689 int vrc = updateProgress(pCtx, pCtx->pState);
1690 AssertRCReturn(vrc, vrc);
1691
1692 PDNDTRANSFEROBJECT pObj = DnDTransferListObjGetFirst(pList);
1693 if (!pObj) /* Transfer complete? */
1694 return VINF_EOF;
1695
1696 switch (DnDTransferObjectGetType(pObj))
1697 {
1698 case DNDTRANSFEROBJTYPE_DIRECTORY:
1699 vrc = i_sendDirectory(pCtx, pObj, pMsg);
1700 break;
1701
1702 case DNDTRANSFEROBJTYPE_FILE:
1703 vrc = i_sendFile(pCtx, pObj, pMsg);
1704 break;
1705
1706 default:
1707 AssertFailedStmt(vrc = VERR_NOT_SUPPORTED);
1708 break;
1709 }
1710
1711 if ( RT_SUCCESS(vrc)
1712 && DnDTransferObjectIsComplete(pObj))
1713 {
1714 DnDTransferListObjRemove(pList, pObj);
1715 pObj = NULL;
1716
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
1723 LogFlowFuncLeaveRC(vrc);
1724 return vrc;
1725}
1726
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 */
1734int GuestDnDTarget::i_sendRawData(GuestDnDSendCtx *pCtx, RTMSINTERVAL msTimeout)
1735{
1736 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1737 NOREF(msTimeout);
1738
1739 /** @todo At the moment we only allow sending up to 64K raw data.
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. */
1742 if (!pCtx->Meta.cbData)
1743 return VINF_SUCCESS;
1744
1745 int vrc = i_sendMetaDataHeader(pCtx);
1746 if (RT_SUCCESS(vrc))
1747 vrc = i_sendMetaDataBody(pCtx);
1748
1749 int vrc2;
1750 if (RT_FAILURE(vrc))
1751 {
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));
1755 }
1756 else
1757 vrc2 = pCtx->pState->setProgress(100 /* Percent */, DND_PROGRESS_COMPLETE, vrc);
1758 AssertRC(vrc2);
1759
1760 LogFlowFuncLeaveRC(vrc);
1761 return vrc;
1762}
1763
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 */
1771HRESULT GuestDnDTarget::cancel(BOOL *aVeto)
1772{
1773#if !defined(VBOX_WITH_DRAG_AND_DROP)
1774 ReturnComNotImplemented();
1775#else /* VBOX_WITH_DRAG_AND_DROP */
1776
1777 LogRel2(("DnD: Sending cancelling request to the guest ...\n"));
1778
1779 int vrc = GuestDnDBase::sendCancel();
1780
1781 if (aVeto)
1782 *aVeto = FALSE; /** @todo Implement vetoing. */
1783
1784 HRESULT hrc = RT_SUCCESS(vrc) ? S_OK : VBOX_E_DND_ERROR;
1785
1786 LogFlowFunc(("hrc=%Rhrc\n", hrc));
1787 return hrc;
1788#endif /* VBOX_WITH_DRAG_AND_DROP */
1789}
1790
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