VirtualBox

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

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

DnD/Main: Also be a bit more informative wrt error reporting to the API client for the other H-> G APIs in IDnDTargetImpl, fixed some HRESULT vs. IPRT rc mixups with adjustScreenCoordinates().

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