VirtualBox

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

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

DnD/Main: Added various i_setErrorXXX() methods for GuestDnDBase, which GuestDnDSource + GuestDnDTarget get derived from. To set COM objects errors usable for API clients. Not used yet.

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