VirtualBox

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

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

DnD/Main: Added some more (verbose) release logging for the drop target implementation.

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