VirtualBox

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

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

DnD/Main: Added ability to report back the guest-side rc when waiting for a guest response + the guest source / target implementations now make use of the COM object error setting functions provided by GuestDnDBase. How (and if at all) those errors will be reported will depend on the client using those APIs.

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