VirtualBox

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

Last change on this file since 73768 was 73003, checked in by vboxsync, 6 years ago

Main: Use setErrorBoth when we've got a VBox status code handy. (The COM status codes aren't too specfic and this may help us decode error messages and provide an alternative to strstr for API clients. setErrorBoth isn't new, btw.)

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 52.4 KB
Line 
1/* $Id: GuestDnDTargetImpl.cpp 73003 2018-07-09 11:09:32Z vboxsync $ */
2/** @file
3 * VBox Console COM Class implementation - Guest drag'n drop target.
4 */
5
6/*
7 * Copyright (C) 2014-2017 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18
19/*********************************************************************************************************************************
20* Header Files *
21*********************************************************************************************************************************/
22#define LOG_GROUP LOG_GROUP_GUEST_DND //LOG_GROUP_MAIN_GUESTDNDTARGET
23#include "LoggingNew.h"
24
25#include "GuestImpl.h"
26#include "GuestDnDTargetImpl.h"
27#include "ConsoleImpl.h"
28
29#include "Global.h"
30#include "AutoCaller.h"
31#include "ThreadTask.h"
32
33#include <algorithm> /* For std::find(). */
34
35#include <iprt/asm.h>
36#include <iprt/file.h>
37#include <iprt/dir.h>
38#include <iprt/path.h>
39#include <iprt/uri.h>
40#include <iprt/cpp/utils.h> /* For unconst(). */
41
42#include <VBox/com/array.h>
43
44#include <VBox/GuestHost/DragAndDrop.h>
45#include <VBox/HostServices/Service.h>
46
47
48/**
49 * Base class for a target task.
50 */
51class GuestDnDTargetTask : public ThreadTask
52{
53public:
54
55 GuestDnDTargetTask(GuestDnDTarget *pTarget)
56 : ThreadTask("GenericGuestDnDTargetTask")
57 , mTarget(pTarget)
58 , mRC(VINF_SUCCESS) { }
59
60 virtual ~GuestDnDTargetTask(void) { }
61
62 int getRC(void) const { return mRC; }
63 bool isOk(void) const { return RT_SUCCESS(mRC); }
64 const ComObjPtr<GuestDnDTarget> &getTarget(void) const { return mTarget; }
65
66protected:
67
68 const ComObjPtr<GuestDnDTarget> mTarget;
69 int mRC;
70};
71
72/**
73 * Task structure for sending data to a target using
74 * a worker thread.
75 */
76class SendDataTask : public GuestDnDTargetTask
77{
78public:
79
80 SendDataTask(GuestDnDTarget *pTarget, PSENDDATACTX pCtx)
81 : GuestDnDTargetTask(pTarget),
82 mpCtx(pCtx)
83 {
84 m_strTaskName = "dndTgtSndData";
85 }
86
87 void handler()
88 {
89 GuestDnDTarget::i_sendDataThreadTask(this);
90 }
91
92 virtual ~SendDataTask(void)
93 {
94 if (mpCtx)
95 {
96 delete mpCtx;
97 mpCtx = NULL;
98 }
99 }
100
101
102 PSENDDATACTX getCtx(void) { return mpCtx; }
103
104protected:
105
106 /** Pointer to send data context. */
107 PSENDDATACTX mpCtx;
108};
109
110// constructor / destructor
111/////////////////////////////////////////////////////////////////////////////
112
113DEFINE_EMPTY_CTOR_DTOR(GuestDnDTarget)
114
115HRESULT GuestDnDTarget::FinalConstruct(void)
116{
117 /* Set the maximum block size our guests can handle to 64K. This always has
118 * been hardcoded until now. */
119 /* Note: Never ever rely on information from the guest; the host dictates what and
120 * how to do something, so try to negogiate a sensible value here later. */
121 mData.mcbBlockSize = _64K; /** @todo Make this configurable. */
122
123 LogFlowThisFunc(("\n"));
124 return BaseFinalConstruct();
125}
126
127void GuestDnDTarget::FinalRelease(void)
128{
129 LogFlowThisFuncEnter();
130 uninit();
131 BaseFinalRelease();
132 LogFlowThisFuncLeave();
133}
134
135// public initializer/uninitializer for internal purposes only
136/////////////////////////////////////////////////////////////////////////////
137
138int GuestDnDTarget::init(const ComObjPtr<Guest>& pGuest)
139{
140 LogFlowThisFuncEnter();
141
142 /* Enclose the state transition NotReady->InInit->Ready. */
143 AutoInitSpan autoInitSpan(this);
144 AssertReturn(autoInitSpan.isOk(), E_FAIL);
145
146 unconst(m_pGuest) = pGuest;
147
148 /* Confirm a successful initialization when it's the case. */
149 autoInitSpan.setSucceeded();
150
151 return VINF_SUCCESS;
152}
153
154/**
155 * Uninitializes the instance.
156 * Called from FinalRelease().
157 */
158void GuestDnDTarget::uninit(void)
159{
160 LogFlowThisFunc(("\n"));
161
162 /* Enclose the state transition Ready->InUninit->NotReady. */
163 AutoUninitSpan autoUninitSpan(this);
164 if (autoUninitSpan.uninitDone())
165 return;
166}
167
168// implementation of wrapped IDnDBase methods.
169/////////////////////////////////////////////////////////////////////////////
170
171HRESULT GuestDnDTarget::isFormatSupported(const com::Utf8Str &aFormat, BOOL *aSupported)
172{
173#if !defined(VBOX_WITH_DRAG_AND_DROP)
174 ReturnComNotImplemented();
175#else /* VBOX_WITH_DRAG_AND_DROP */
176
177 AutoCaller autoCaller(this);
178 if (FAILED(autoCaller.rc())) return autoCaller.rc();
179
180 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
181
182 return GuestDnDBase::i_isFormatSupported(aFormat, aSupported);
183#endif /* VBOX_WITH_DRAG_AND_DROP */
184}
185
186HRESULT GuestDnDTarget::getFormats(GuestDnDMIMEList &aFormats)
187{
188#if !defined(VBOX_WITH_DRAG_AND_DROP)
189 ReturnComNotImplemented();
190#else /* VBOX_WITH_DRAG_AND_DROP */
191
192 AutoCaller autoCaller(this);
193 if (FAILED(autoCaller.rc())) return autoCaller.rc();
194
195 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
196
197 return GuestDnDBase::i_getFormats(aFormats);
198#endif /* VBOX_WITH_DRAG_AND_DROP */
199}
200
201HRESULT GuestDnDTarget::addFormats(const GuestDnDMIMEList &aFormats)
202{
203#if !defined(VBOX_WITH_DRAG_AND_DROP)
204 ReturnComNotImplemented();
205#else /* VBOX_WITH_DRAG_AND_DROP */
206
207 AutoCaller autoCaller(this);
208 if (FAILED(autoCaller.rc())) return autoCaller.rc();
209
210 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
211
212 return GuestDnDBase::i_addFormats(aFormats);
213#endif /* VBOX_WITH_DRAG_AND_DROP */
214}
215
216HRESULT GuestDnDTarget::removeFormats(const 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 (FAILED(autoCaller.rc())) return autoCaller.rc();
224
225 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
226
227 return GuestDnDBase::i_removeFormats(aFormats);
228#endif /* VBOX_WITH_DRAG_AND_DROP */
229}
230
231HRESULT GuestDnDTarget::getProtocolVersion(ULONG *aProtocolVersion)
232{
233#if !defined(VBOX_WITH_DRAG_AND_DROP)
234 ReturnComNotImplemented();
235#else /* VBOX_WITH_DRAG_AND_DROP */
236
237 AutoCaller autoCaller(this);
238 if (FAILED(autoCaller.rc())) return autoCaller.rc();
239
240 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
241
242 return GuestDnDBase::i_getProtocolVersion(aProtocolVersion);
243#endif /* VBOX_WITH_DRAG_AND_DROP */
244}
245
246// implementation of wrapped IDnDTarget methods.
247/////////////////////////////////////////////////////////////////////////////
248
249HRESULT GuestDnDTarget::enter(ULONG aScreenId, ULONG aX, ULONG aY,
250 DnDAction_T aDefaultAction,
251 const std::vector<DnDAction_T> &aAllowedActions,
252 const GuestDnDMIMEList &aFormats,
253 DnDAction_T *aResultAction)
254{
255#if !defined(VBOX_WITH_DRAG_AND_DROP)
256 ReturnComNotImplemented();
257#else /* VBOX_WITH_DRAG_AND_DROP */
258
259 /* Input validation. */
260 if (aDefaultAction == DnDAction_Ignore)
261 return setError(E_INVALIDARG, tr("No default action specified"));
262 if (!aAllowedActions.size())
263 return setError(E_INVALIDARG, tr("Number of allowed actions is empty"));
264 if (!aFormats.size())
265 return setError(E_INVALIDARG, tr("Number of supported formats is empty"));
266
267 AutoCaller autoCaller(this);
268 if (FAILED(autoCaller.rc())) return autoCaller.rc();
269
270 /* Determine guest DnD protocol to use. */
271 GuestDnDBase::getProtocolVersion(&mDataBase.m_uProtocolVersion);
272
273 /* Default action is ignoring. */
274 DnDAction_T resAction = DnDAction_Ignore;
275
276 /* Check & convert the drag & drop actions */
277 uint32_t uDefAction = 0;
278 uint32_t uAllowedActions = 0;
279 GuestDnD::toHGCMActions(aDefaultAction, &uDefAction,
280 aAllowedActions, &uAllowedActions);
281 /* If there is no usable action, ignore this request. */
282 if (isDnDIgnoreAction(uDefAction))
283 return S_OK;
284
285 /*
286 * Make a flat data string out of the supported format list.
287 * In the GuestDnDTarget case the source formats are from the host,
288 * as GuestDnDTarget acts as a source for the guest.
289 */
290 Utf8Str strFormats = GuestDnD::toFormatString(GuestDnD::toFilteredFormatList(m_lstFmtSupported, aFormats));
291 if (strFormats.isEmpty())
292 return setError(E_INVALIDARG, tr("No or not supported format(s) specified"));
293 const uint32_t cbFormats = (uint32_t)strFormats.length() + 1; /* Include terminating zero. */
294
295 LogRel2(("DnD: Offered formats to guest:\n"));
296 RTCList<RTCString> lstFormats = strFormats.split("\r\n");
297 for (size_t i = 0; i < lstFormats.size(); i++)
298 LogRel2(("DnD: \t%s\n", lstFormats[i].c_str()));
299
300 /* Save the formats offered to the guest. This is needed to later
301 * decide what to do with the data when sending stuff to the guest. */
302 m_lstFmtOffered = aFormats;
303 Assert(m_lstFmtOffered.size());
304
305 HRESULT hr = S_OK;
306
307 /* Adjust the coordinates in a multi-monitor setup. */
308 int rc = GuestDnDInst()->adjustScreenCoordinates(aScreenId, &aX, &aY);
309 if (RT_SUCCESS(rc))
310 {
311 GuestDnDMsg Msg;
312 Msg.setType(HOST_DND_HG_EVT_ENTER);
313 if (mDataBase.m_uProtocolVersion >= 3)
314 Msg.setNextUInt32(0); /** @todo ContextID not used yet. */
315 Msg.setNextUInt32(aScreenId);
316 Msg.setNextUInt32(aX);
317 Msg.setNextUInt32(aY);
318 Msg.setNextUInt32(uDefAction);
319 Msg.setNextUInt32(uAllowedActions);
320 Msg.setNextPointer((void *)strFormats.c_str(), cbFormats);
321 Msg.setNextUInt32(cbFormats);
322
323 rc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
324 if (RT_SUCCESS(rc))
325 {
326 GuestDnDResponse *pResp = GuestDnDInst()->response();
327 if (pResp && RT_SUCCESS(pResp->waitForGuestResponse()))
328 resAction = GuestDnD::toMainAction(pResp->defAction());
329 }
330 }
331
332 if (RT_FAILURE(rc))
333 hr = VBOX_E_IPRT_ERROR;
334
335 if (SUCCEEDED(hr))
336 {
337 if (aResultAction)
338 *aResultAction = resAction;
339 }
340
341 LogFlowFunc(("hr=%Rhrc, resAction=%ld\n", hr, resAction));
342 return hr;
343#endif /* VBOX_WITH_DRAG_AND_DROP */
344}
345
346HRESULT GuestDnDTarget::move(ULONG aScreenId, ULONG aX, ULONG aY,
347 DnDAction_T aDefaultAction,
348 const std::vector<DnDAction_T> &aAllowedActions,
349 const GuestDnDMIMEList &aFormats,
350 DnDAction_T *aResultAction)
351{
352#if !defined(VBOX_WITH_DRAG_AND_DROP)
353 ReturnComNotImplemented();
354#else /* VBOX_WITH_DRAG_AND_DROP */
355
356 /* Input validation. */
357
358 AutoCaller autoCaller(this);
359 if (FAILED(autoCaller.rc())) return autoCaller.rc();
360
361 /* Default action is ignoring. */
362 DnDAction_T resAction = DnDAction_Ignore;
363
364 /* Check & convert the drag & drop actions. */
365 uint32_t uDefAction = 0;
366 uint32_t uAllowedActions = 0;
367 GuestDnD::toHGCMActions(aDefaultAction, &uDefAction,
368 aAllowedActions, &uAllowedActions);
369 /* If there is no usable action, ignore this request. */
370 if (isDnDIgnoreAction(uDefAction))
371 return S_OK;
372
373 /*
374 * Make a flat data string out of the supported format list.
375 * In the GuestDnDTarget case the source formats are from the host,
376 * as GuestDnDTarget acts as a source for the guest.
377 */
378 Utf8Str strFormats = GuestDnD::toFormatString(GuestDnD::toFilteredFormatList(m_lstFmtSupported, aFormats));
379 if (strFormats.isEmpty())
380 return setError(E_INVALIDARG, tr("No or not supported format(s) specified"));
381 const uint32_t cbFormats = (uint32_t)strFormats.length() + 1; /* Include terminating zero. */
382
383 HRESULT hr = S_OK;
384
385 int rc = GuestDnDInst()->adjustScreenCoordinates(aScreenId, &aX, &aY);
386 if (RT_SUCCESS(rc))
387 {
388 GuestDnDMsg Msg;
389 Msg.setType(HOST_DND_HG_EVT_MOVE);
390 if (mDataBase.m_uProtocolVersion >= 3)
391 Msg.setNextUInt32(0); /** @todo ContextID not used yet. */
392 Msg.setNextUInt32(aScreenId);
393 Msg.setNextUInt32(aX);
394 Msg.setNextUInt32(aY);
395 Msg.setNextUInt32(uDefAction);
396 Msg.setNextUInt32(uAllowedActions);
397 Msg.setNextPointer((void *)strFormats.c_str(), cbFormats);
398 Msg.setNextUInt32(cbFormats);
399
400 rc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
401 if (RT_SUCCESS(rc))
402 {
403 GuestDnDResponse *pResp = GuestDnDInst()->response();
404 if (pResp && RT_SUCCESS(pResp->waitForGuestResponse()))
405 resAction = GuestDnD::toMainAction(pResp->defAction());
406 }
407 }
408
409 if (RT_FAILURE(rc))
410 hr = VBOX_E_IPRT_ERROR;
411
412 if (SUCCEEDED(hr))
413 {
414 if (aResultAction)
415 *aResultAction = resAction;
416 }
417
418 LogFlowFunc(("hr=%Rhrc, *pResultAction=%ld\n", hr, resAction));
419 return hr;
420#endif /* VBOX_WITH_DRAG_AND_DROP */
421}
422
423HRESULT GuestDnDTarget::leave(ULONG uScreenId)
424{
425 RT_NOREF(uScreenId);
426#if !defined(VBOX_WITH_DRAG_AND_DROP)
427 ReturnComNotImplemented();
428#else /* VBOX_WITH_DRAG_AND_DROP */
429
430 AutoCaller autoCaller(this);
431 if (FAILED(autoCaller.rc())) return autoCaller.rc();
432
433 HRESULT hr = S_OK;
434 int rc = GuestDnDInst()->hostCall(HOST_DND_HG_EVT_LEAVE,
435 0 /* cParms */, NULL /* paParms */);
436 if (RT_SUCCESS(rc))
437 {
438 GuestDnDResponse *pResp = GuestDnDInst()->response();
439 if (pResp)
440 pResp->waitForGuestResponse();
441 }
442
443 if (RT_FAILURE(rc))
444 hr = VBOX_E_IPRT_ERROR;
445
446 LogFlowFunc(("hr=%Rhrc\n", hr));
447 return hr;
448#endif /* VBOX_WITH_DRAG_AND_DROP */
449}
450
451HRESULT GuestDnDTarget::drop(ULONG aScreenId, ULONG aX, ULONG aY,
452 DnDAction_T aDefaultAction,
453 const std::vector<DnDAction_T> &aAllowedActions,
454 const GuestDnDMIMEList &aFormats,
455 com::Utf8Str &aFormat,
456 DnDAction_T *aResultAction)
457{
458#if !defined(VBOX_WITH_DRAG_AND_DROP)
459 ReturnComNotImplemented();
460#else /* VBOX_WITH_DRAG_AND_DROP */
461
462 if (aDefaultAction == DnDAction_Ignore)
463 return setError(E_INVALIDARG, tr("Invalid default action specified"));
464 if (!aAllowedActions.size())
465 return setError(E_INVALIDARG, tr("Invalid allowed actions specified"));
466 if (!aFormats.size())
467 return setError(E_INVALIDARG, tr("No drop format(s) specified"));
468 /* aResultAction is optional. */
469
470 AutoCaller autoCaller(this);
471 if (FAILED(autoCaller.rc())) return autoCaller.rc();
472
473 /* Default action is ignoring. */
474 DnDAction_T resAction = DnDAction_Ignore;
475
476 /* Check & convert the drag & drop actions to HGCM codes. */
477 uint32_t uDefAction = DND_IGNORE_ACTION;
478 uint32_t uAllowedActions = 0;
479 GuestDnD::toHGCMActions(aDefaultAction, &uDefAction,
480 aAllowedActions, &uAllowedActions);
481 /* If there is no usable action, ignore this request. */
482 if (isDnDIgnoreAction(uDefAction))
483 {
484 aFormat = "";
485 if (aResultAction)
486 *aResultAction = DnDAction_Ignore;
487 return S_OK;
488 }
489
490 /*
491 * Make a flat data string out of the supported format list.
492 * In the GuestDnDTarget case the source formats are from the host,
493 * as GuestDnDTarget acts as a source for the guest.
494 */
495 Utf8Str strFormats = GuestDnD::toFormatString(GuestDnD::toFilteredFormatList(m_lstFmtSupported, aFormats));
496 if (strFormats.isEmpty())
497 return setError(E_INVALIDARG, tr("No or not supported format(s) specified"));
498 const uint32_t cbFormats = (uint32_t)strFormats.length() + 1; /* Include terminating zero. */
499
500 /* Adjust the coordinates in a multi-monitor setup. */
501 HRESULT hr = GuestDnDInst()->adjustScreenCoordinates(aScreenId, &aX, &aY);
502 if (SUCCEEDED(hr))
503 {
504 GuestDnDMsg Msg;
505 Msg.setType(HOST_DND_HG_EVT_DROPPED);
506 if (mDataBase.m_uProtocolVersion >= 3)
507 Msg.setNextUInt32(0); /** @todo ContextID not used yet. */
508 Msg.setNextUInt32(aScreenId);
509 Msg.setNextUInt32(aX);
510 Msg.setNextUInt32(aY);
511 Msg.setNextUInt32(uDefAction);
512 Msg.setNextUInt32(uAllowedActions);
513 Msg.setNextPointer((void*)strFormats.c_str(), cbFormats);
514 Msg.setNextUInt32(cbFormats);
515
516 int vrc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
517 if (RT_SUCCESS(vrc))
518 {
519 GuestDnDResponse *pResp = GuestDnDInst()->response();
520 AssertPtr(pResp);
521
522 vrc = pResp->waitForGuestResponse();
523 if (RT_SUCCESS(vrc))
524 {
525 resAction = GuestDnD::toMainAction(pResp->defAction());
526
527 GuestDnDMIMEList lstFormats = pResp->formats();
528 if (lstFormats.size() == 1) /* Exactly one format to use specified? */
529 {
530 aFormat = lstFormats.at(0);
531 LogFlowFunc(("resFormat=%s, resAction=%RU32\n", aFormat.c_str(), pResp->defAction()));
532 }
533 else
534 /** @todo r=bird: This isn't an IPRT error, is it? */
535 hr = setError(VBOX_E_IPRT_ERROR, tr("Guest returned invalid drop formats (%zu formats)"), lstFormats.size());
536 }
537 else
538 hr = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Waiting for response of dropped event failed (%Rrc)"), vrc);
539 }
540 else
541 hr = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Sending dropped event to guest failed (%Rrc)"), vrc);
542 }
543 else
544 hr = setError(hr, tr("Retrieving drop coordinates failed"));
545
546 if (SUCCEEDED(hr))
547 {
548 if (aResultAction)
549 *aResultAction = resAction;
550 }
551
552 LogFlowFunc(("Returning hr=%Rhrc\n", hr));
553 return hr;
554#endif /* VBOX_WITH_DRAG_AND_DROP */
555}
556
557/* static */
558void GuestDnDTarget::i_sendDataThreadTask(SendDataTask *pTask)
559{
560 LogFlowFunc(("pTask=%p\n", pTask));
561
562 AssertPtrReturnVoid(pTask);
563
564 const ComObjPtr<GuestDnDTarget> pThis(pTask->getTarget());
565 Assert(!pThis.isNull());
566
567 AutoCaller autoCaller(pThis);
568 if (FAILED(autoCaller.rc()))
569 return;
570
571 int vrc = pThis->i_sendData(pTask->getCtx(), RT_INDEFINITE_WAIT /* msTimeout */);
572 NOREF(vrc);
573/** @todo
574 *
575 * r=bird: What happens with @a vrc?
576 *
577 */
578
579 AutoWriteLock alock(pThis COMMA_LOCKVAL_SRC_POS);
580
581 Assert(pThis->mDataBase.m_cTransfersPending);
582 if (pThis->mDataBase.m_cTransfersPending)
583 pThis->mDataBase.m_cTransfersPending--;
584
585 LogFlowFunc(("pTarget=%p vrc=%Rrc (ignored)\n", (GuestDnDTarget *)pThis, vrc));
586}
587
588/**
589 * Initiates a data transfer from the host to the guest. The source is the host whereas the target is the
590 * guest in this case.
591 *
592 * @return HRESULT
593 * @param aScreenId
594 * @param aFormat
595 * @param aData
596 * @param aProgress
597 */
598HRESULT GuestDnDTarget::sendData(ULONG aScreenId, const com::Utf8Str &aFormat, const std::vector<BYTE> &aData,
599 ComPtr<IProgress> &aProgress)
600{
601#if !defined(VBOX_WITH_DRAG_AND_DROP)
602 ReturnComNotImplemented();
603#else /* VBOX_WITH_DRAG_AND_DROP */
604
605 AutoCaller autoCaller(this);
606 if (FAILED(autoCaller.rc())) return autoCaller.rc();
607
608 /* Input validation. */
609 if (RT_UNLIKELY((aFormat.c_str()) == NULL || *(aFormat.c_str()) == '\0'))
610 return setError(E_INVALIDARG, tr("No data format specified"));
611 if (RT_UNLIKELY(!aData.size()))
612 return setError(E_INVALIDARG, tr("No data to send specified"));
613
614 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
615
616 /* At the moment we only support one transfer at a time. */
617 if (mDataBase.m_cTransfersPending)
618 return setError(E_INVALIDARG, tr("Another drop operation already is in progress"));
619
620 /* Ditto. */
621 GuestDnDResponse *pResp = GuestDnDInst()->response();
622 AssertPtr(pResp);
623
624 HRESULT hr = pResp->resetProgress(m_pGuest);
625 if (FAILED(hr))
626 return hr;
627
628 SendDataTask *pTask = NULL;
629 PSENDDATACTX pSendCtx = NULL;
630
631 try
632 {
633 //pSendCtx is passed into SendDataTask where one is deleted in destructor
634 pSendCtx = new SENDDATACTX;
635 RT_BZERO(pSendCtx, sizeof(SENDDATACTX));
636
637 pSendCtx->mpTarget = this;
638 pSendCtx->mpResp = pResp;
639 pSendCtx->mScreenID = aScreenId;
640 pSendCtx->mFmtReq = aFormat;
641 pSendCtx->mData.getMeta().add(aData);
642
643 /* pTask is responsible for deletion of pSendCtx after creating */
644 pTask = new SendDataTask(this, pSendCtx);
645 if (!pTask->isOk())
646 {
647 delete pTask;
648 LogRel2(("DnD: Could not create SendDataTask object \n"));
649 throw hr = E_FAIL;
650 }
651
652 /* This function delete pTask in case of exceptions,
653 * so there is no need in the call of delete operator. */
654 hr = pTask->createThreadWithType(RTTHREADTYPE_MAIN_WORKER);
655
656 }
657 catch (std::bad_alloc &)
658 {
659 hr = setError(E_OUTOFMEMORY);
660 }
661 catch (...)
662 {
663 LogRel2(("DnD: Could not create thread for data sending task\n"));
664 hr = E_FAIL;
665 }
666
667 if (SUCCEEDED(hr))
668 {
669 mDataBase.m_cTransfersPending++;
670
671 hr = pResp->queryProgressTo(aProgress.asOutParam());
672 ComAssertComRC(hr);
673
674 /* Note: pTask is now owned by the worker thread. */
675 }
676 else
677 hr = setError(hr, tr("Starting thread for GuestDnDTarget::i_sendDataThread (%Rhrc)"), hr);
678
679 LogFlowFunc(("Returning hr=%Rhrc\n", hr));
680 return hr;
681#endif /* VBOX_WITH_DRAG_AND_DROP */
682}
683
684int GuestDnDTarget::i_cancelOperation(void)
685{
686 /** @todo Check for pending cancel requests. */
687
688#if 0 /** @todo Later. */
689 /* Cancel any outstanding waits for guest responses first. */
690 if (pResp)
691 pResp->notifyAboutGuestResponse();
692#endif
693
694 LogFlowFunc(("Cancelling operation, telling guest ...\n"));
695 return GuestDnDInst()->hostCall(HOST_DND_HG_EVT_CANCEL, 0 /* cParms */, NULL /*paParms*/);
696}
697
698/* static */
699Utf8Str GuestDnDTarget::i_guestErrorToString(int guestRc)
700{
701 Utf8Str strError;
702
703 switch (guestRc)
704 {
705 case VERR_ACCESS_DENIED:
706 strError += Utf8StrFmt(tr("For one or more guest files or directories selected for transferring to the host your guest "
707 "user does not have the appropriate access rights for. Please make sure that all selected "
708 "elements can be accessed and that your guest user has the appropriate rights"));
709 break;
710
711 case VERR_NOT_FOUND:
712 /* Should not happen due to file locking on the guest, but anyway ... */
713 strError += Utf8StrFmt(tr("One or more guest files or directories selected for transferring to the host were not"
714 "found on the guest anymore. This can be the case if the guest files were moved and/or"
715 "altered while the drag and drop operation was in progress"));
716 break;
717
718 case VERR_SHARING_VIOLATION:
719 strError += Utf8StrFmt(tr("One or more guest files or directories selected for transferring to the host were locked. "
720 "Please make sure that all selected elements can be accessed and that your guest user has "
721 "the appropriate rights"));
722 break;
723
724 case VERR_TIMEOUT:
725 strError += Utf8StrFmt(tr("The guest was not able to process the drag and drop data within time"));
726 break;
727
728 default:
729 strError += Utf8StrFmt(tr("Drag and drop error from guest (%Rrc)"), guestRc);
730 break;
731 }
732
733 return strError;
734}
735
736/* static */
737Utf8Str GuestDnDTarget::i_hostErrorToString(int hostRc)
738{
739 Utf8Str strError;
740
741 switch (hostRc)
742 {
743 case VERR_ACCESS_DENIED:
744 strError += Utf8StrFmt(tr("For one or more host files or directories selected for transferring to the guest your host "
745 "user does not have the appropriate access rights for. Please make sure that all selected "
746 "elements can be accessed and that your host user has the appropriate rights."));
747 break;
748
749 case VERR_NOT_FOUND:
750 /* Should not happen due to file locking on the host, but anyway ... */
751 strError += Utf8StrFmt(tr("One or more host files or directories selected for transferring to the host were not"
752 "found on the host anymore. This can be the case if the host files were moved and/or"
753 "altered while the drag and drop operation was in progress."));
754 break;
755
756 case VERR_SHARING_VIOLATION:
757 strError += Utf8StrFmt(tr("One or more host files or directories selected for transferring to the guest were locked. "
758 "Please make sure that all selected elements can be accessed and that your host user has "
759 "the appropriate rights."));
760 break;
761
762 default:
763 strError += Utf8StrFmt(tr("Drag and drop error from host (%Rrc)"), hostRc);
764 break;
765 }
766
767 return strError;
768}
769
770/**
771 * @returns VBox status code that the caller ignores. Not sure if that's
772 * intentional or not.
773 */
774int GuestDnDTarget::i_sendData(PSENDDATACTX pCtx, RTMSINTERVAL msTimeout)
775{
776 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
777
778 int rc;
779
780 ASMAtomicWriteBool(&pCtx->mIsActive, true);
781
782 /* Clear all remaining outgoing messages. */
783 mDataBase.m_lstMsgOut.clear();
784
785 /**
786 * Do we need to build up a file tree?
787 * Note: The decision whether we need to build up a file tree and sending
788 * actual file data only depends on the actual formats offered by this target.
789 * If the guest does not want an URI list ("text/uri-list") but text ("TEXT" and
790 * friends) instead, still send the data over to the guest -- the file as such still
791 * is needed on the guest in this case, as the guest then just wants a simple path
792 * instead of an URI list (pointing to a file on the guest itself).
793 *
794 ** @todo Support more than one format; add a format<->function handler concept. Later. */
795 bool fHasURIList = std::find(m_lstFmtOffered.begin(),
796 m_lstFmtOffered.end(), "text/uri-list") != m_lstFmtOffered.end();
797 if (fHasURIList)
798 {
799 rc = i_sendURIData(pCtx, msTimeout);
800 }
801 else
802 {
803 rc = i_sendRawData(pCtx, msTimeout);
804 }
805
806 ASMAtomicWriteBool(&pCtx->mIsActive, false);
807
808 LogFlowFuncLeaveRC(rc);
809 return rc;
810}
811
812int GuestDnDTarget::i_sendDataBody(PSENDDATACTX pCtx, GuestDnDData *pData)
813{
814 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
815 AssertPtrReturn(pData, VERR_INVALID_POINTER);
816
817 /** @todo Add support for multiple HOST_DND_HG_SND_DATA messages in case of more than 64K data! */
818 if (pData->getMeta().getSize() > _64K)
819 return VERR_NOT_IMPLEMENTED;
820
821 GuestDnDMsg Msg;
822
823 LogFlowFunc(("cbFmt=%RU32, cbMeta=%RU32, cbChksum=%RU32\n",
824 pData->getFmtSize(), pData->getMeta().getSize(), pData->getChkSumSize()));
825
826 Msg.setType(HOST_DND_HG_SND_DATA);
827 if (mDataBase.m_uProtocolVersion < 3)
828 {
829 Msg.setNextUInt32(pCtx->mScreenID); /* uScreenId */
830 Msg.setNextPointer(pData->getFmtMutable(), pData->getFmtSize()); /* pvFormat */
831 Msg.setNextUInt32(pData->getFmtSize()); /* cbFormat */
832 Msg.setNextPointer(pData->getMeta().getDataMutable(), pData->getMeta().getSize()); /* pvData */
833 /* Fill in the current data block size to send.
834 * Note: Only supports uint32_t. */
835 Msg.setNextUInt32((uint32_t)pData->getMeta().getSize()); /* cbData */
836 }
837 else
838 {
839 Msg.setNextUInt32(0); /** @todo ContextID not used yet. */
840 Msg.setNextPointer(pData->getMeta().getDataMutable(), pData->getMeta().getSize()); /* pvData */
841 Msg.setNextUInt32(pData->getMeta().getSize()); /* cbData */
842 Msg.setNextPointer(pData->getChkSumMutable(), pData->getChkSumSize()); /** @todo pvChecksum; not used yet. */
843 Msg.setNextUInt32(pData->getChkSumSize()); /** @todo cbChecksum; not used yet. */
844 }
845
846 int rc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
847 if (RT_SUCCESS(rc))
848 rc = updateProgress(pData, pCtx->mpResp, pData->getMeta().getSize());
849
850 LogFlowFuncLeaveRC(rc);
851 return rc;
852}
853
854int GuestDnDTarget::i_sendDataHeader(PSENDDATACTX pCtx, GuestDnDData *pData, GuestDnDURIData *pURIData /* = NULL */)
855{
856 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
857 AssertPtrReturn(pData, VERR_INVALID_POINTER);
858 /* pURIData is optional. */
859
860 GuestDnDMsg Msg;
861
862 Msg.setType(HOST_DND_HG_SND_DATA_HDR);
863
864 Msg.setNextUInt32(0); /** @todo uContext; not used yet. */
865 Msg.setNextUInt32(0); /** @todo uFlags; not used yet. */
866 Msg.setNextUInt32(pCtx->mScreenID); /* uScreen */
867 Msg.setNextUInt64(pData->getTotal()); /* cbTotal */
868 Msg.setNextUInt32(pData->getMeta().getSize()); /* cbMeta*/
869 Msg.setNextPointer(pData->getFmtMutable(), pData->getFmtSize()); /* pvMetaFmt */
870 Msg.setNextUInt32(pData->getFmtSize()); /* cbMetaFmt */
871 Msg.setNextUInt64(pURIData ? pURIData->getObjToProcess() : 0); /* cObjects */
872 Msg.setNextUInt32(0); /** @todo enmCompression; not used yet. */
873 Msg.setNextUInt32(0); /** @todo enmChecksumType; not used yet. */
874 Msg.setNextPointer(NULL, 0); /** @todo pvChecksum; not used yet. */
875 Msg.setNextUInt32(0); /** @todo cbChecksum; not used yet. */
876
877 int rc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
878
879 LogFlowFuncLeaveRC(rc);
880 return rc;
881}
882
883int GuestDnDTarget::i_sendDirectory(PSENDDATACTX pCtx, GuestDnDURIObjCtx *pObjCtx, GuestDnDMsg *pMsg)
884{
885 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
886 AssertPtrReturn(pObjCtx, VERR_INVALID_POINTER);
887 AssertPtrReturn(pMsg, VERR_INVALID_POINTER);
888
889 DnDURIObject *pObj = pObjCtx->getObj();
890 AssertPtr(pObj);
891
892 RTCString strPath = pObj->GetDestPath();
893 if (strPath.isEmpty())
894 return VERR_INVALID_PARAMETER;
895 if (strPath.length() >= RTPATH_MAX) /* Note: Maximum is RTPATH_MAX on guest side. */
896 return VERR_BUFFER_OVERFLOW;
897
898 LogRel2(("DnD: Transferring host directory to guest: %s\n", strPath.c_str()));
899
900 pMsg->setType(HOST_DND_HG_SND_DIR);
901 if (mDataBase.m_uProtocolVersion >= 3)
902 pMsg->setNextUInt32(0); /** @todo ContextID not used yet. */
903 pMsg->setNextString(strPath.c_str()); /* path */
904 pMsg->setNextUInt32((uint32_t)(strPath.length() + 1)); /* path length (maximum is RTPATH_MAX on guest side). */
905 pMsg->setNextUInt32(pObj->GetMode()); /* mode */
906
907 return VINF_SUCCESS;
908}
909
910int GuestDnDTarget::i_sendFile(PSENDDATACTX pCtx, GuestDnDURIObjCtx *pObjCtx, GuestDnDMsg *pMsg)
911{
912 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
913 AssertPtrReturn(pObjCtx, VERR_INVALID_POINTER);
914 AssertPtrReturn(pMsg, VERR_INVALID_POINTER);
915
916 DnDURIObject *pObj = pObjCtx->getObj();
917 AssertPtr(pObj);
918
919 RTCString strPathSrc = pObj->GetSourcePath();
920 if (strPathSrc.isEmpty())
921 return VERR_INVALID_PARAMETER;
922
923 int rc = VINF_SUCCESS;
924
925 LogFlowFunc(("Sending file with %RU32 bytes buffer, using protocol v%RU32 ...\n",
926 mData.mcbBlockSize, mDataBase.m_uProtocolVersion));
927 LogFlowFunc(("strPathSrc=%s, fIsOpen=%RTbool, cbSize=%RU64\n", strPathSrc.c_str(), pObj->IsOpen(), pObj->GetSize()));
928
929 if (!pObj->IsOpen())
930 {
931 LogRel2(("DnD: Opening host file for transferring to guest: %s\n", strPathSrc.c_str()));
932 rc = pObj->OpenEx(strPathSrc, DnDURIObject::File, DnDURIObject::Source,
933 RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE, 0 /* fFlags */);
934 if (RT_FAILURE(rc))
935 LogRel(("DnD: Error opening host file '%s', rc=%Rrc\n", strPathSrc.c_str(), rc));
936 }
937
938 bool fSendData = false;
939 if (RT_SUCCESS(rc))
940 {
941 if (mDataBase.m_uProtocolVersion >= 2)
942 {
943 uint32_t fState = pObjCtx->getState();
944 if (!(fState & DND_OBJCTX_STATE_HAS_HDR))
945 {
946 /*
947 * Since protocol v2 the file header and the actual file contents are
948 * separate messages, so send the file header first.
949 * The just registered callback will be called by the guest afterwards.
950 */
951 pMsg->setType(HOST_DND_HG_SND_FILE_HDR);
952 pMsg->setNextUInt32(0); /** @todo ContextID not used yet. */
953 pMsg->setNextString(pObj->GetDestPath().c_str()); /* pvName */
954 pMsg->setNextUInt32((uint32_t)(pObj->GetDestPath().length() + 1)); /* cbName */
955 pMsg->setNextUInt32(0); /* uFlags */
956 pMsg->setNextUInt32(pObj->GetMode()); /* fMode */
957 pMsg->setNextUInt64(pObj->GetSize()); /* uSize */
958
959 LogFlowFunc(("Sending file header ...\n"));
960 LogRel2(("DnD: Transferring host file to guest: %s (%RU64 bytes, mode 0x%x)\n",
961 strPathSrc.c_str(), pObj->GetSize(), pObj->GetMode()));
962
963 /** @todo Set progress object title to current file being transferred? */
964
965 pObjCtx->setState(fState | DND_OBJCTX_STATE_HAS_HDR);
966 }
967 else
968 {
969 /* File header was sent, so only send the actual file data. */
970 fSendData = true;
971 }
972 }
973 else /* Protocol v1. */
974 {
975 /* Always send the file data, every time. */
976 fSendData = true;
977 }
978 }
979
980 if ( RT_SUCCESS(rc)
981 && fSendData)
982 {
983 rc = i_sendFileData(pCtx, pObjCtx, pMsg);
984 }
985
986 LogFlowFuncLeaveRC(rc);
987 return rc;
988}
989
990int GuestDnDTarget::i_sendFileData(PSENDDATACTX pCtx, GuestDnDURIObjCtx *pObjCtx, GuestDnDMsg *pMsg)
991{
992 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
993 AssertPtrReturn(pObjCtx, VERR_INVALID_POINTER);
994 AssertPtrReturn(pMsg, VERR_INVALID_POINTER);
995
996 DnDURIObject *pObj = pObjCtx->getObj();
997 AssertPtr(pObj);
998
999 AssertPtr(pCtx->mpResp);
1000
1001 /** @todo Don't allow concurrent reads per context! */
1002
1003 /*
1004 * Start sending stuff.
1005 */
1006
1007 /* Set the message type. */
1008 pMsg->setType(HOST_DND_HG_SND_FILE_DATA);
1009
1010 /* Protocol version 1 sends the file path *every* time with a new file chunk.
1011 * In protocol version 2 we only do this once with HOST_DND_HG_SND_FILE_HDR. */
1012 if (mDataBase.m_uProtocolVersion <= 1)
1013 {
1014 pMsg->setNextString(pObj->GetDestPath().c_str()); /* pvName */
1015 pMsg->setNextUInt32((uint32_t)(pObj->GetDestPath().length() + 1)); /* cbName */
1016 }
1017 else if (mDataBase.m_uProtocolVersion >= 2)
1018 {
1019 pMsg->setNextUInt32(0); /** @todo ContextID not used yet. */
1020 }
1021
1022 uint32_t cbRead = 0;
1023
1024 int rc = pObj->Read(pCtx->mURI.getBufferMutable(), pCtx->mURI.getBufferSize(), &cbRead);
1025 if (RT_SUCCESS(rc))
1026 {
1027 pCtx->mData.addProcessed(cbRead);
1028 LogFlowFunc(("cbBufSize=%zu, cbRead=%RU32\n", pCtx->mURI.getBufferSize(), cbRead));
1029
1030 if (mDataBase.m_uProtocolVersion <= 1)
1031 {
1032 pMsg->setNextPointer(pCtx->mURI.getBufferMutable(), cbRead); /* pvData */
1033 pMsg->setNextUInt32(cbRead); /* cbData */
1034 pMsg->setNextUInt32(pObj->GetMode()); /* fMode */
1035 }
1036 else /* Protocol v2 and up. */
1037 {
1038 pMsg->setNextPointer(pCtx->mURI.getBufferMutable(), cbRead); /* pvData */
1039 pMsg->setNextUInt32(cbRead); /* cbData */
1040
1041 if (mDataBase.m_uProtocolVersion >= 3)
1042 {
1043 /** @todo Calculate checksum. */
1044 pMsg->setNextPointer(NULL, 0); /* pvChecksum */
1045 pMsg->setNextUInt32(0); /* cbChecksum */
1046 }
1047 }
1048
1049 if (pObj->IsComplete()) /* Done reading? */
1050 {
1051 LogRel2(("DnD: File transfer to guest complete: %s\n", pObj->GetSourcePath().c_str()));
1052 LogFlowFunc(("File '%s' complete\n", pObj->GetSourcePath().c_str()));
1053
1054 /* DnDURIObject::Read() returns VINF_EOF when finished reading the entire fire,
1055 * but we don't want this here -- so just override this with VINF_SUCCESS. */
1056 rc = VINF_SUCCESS;
1057 }
1058 }
1059
1060 LogFlowFuncLeaveRC(rc);
1061 return rc;
1062}
1063
1064/* static */
1065DECLCALLBACK(int) GuestDnDTarget::i_sendURIDataCallback(uint32_t uMsg, void *pvParms, size_t cbParms, void *pvUser)
1066{
1067 PSENDDATACTX pCtx = (PSENDDATACTX)pvUser;
1068 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1069
1070 GuestDnDTarget *pThis = pCtx->mpTarget;
1071 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
1072
1073 LogFlowFunc(("pThis=%p, uMsg=%RU32\n", pThis, uMsg));
1074
1075 int rc = VINF_SUCCESS;
1076 int rcGuest = VINF_SUCCESS; /* Contains error code from guest in case of VERR_GSTDND_GUEST_ERROR. */
1077 bool fNotify = false;
1078
1079 switch (uMsg)
1080 {
1081 case GUEST_DND_CONNECT:
1082 /* Nothing to do here (yet). */
1083 break;
1084
1085 case GUEST_DND_DISCONNECT:
1086 rc = VERR_CANCELLED;
1087 break;
1088
1089 case GUEST_DND_GET_NEXT_HOST_MSG:
1090 {
1091 PVBOXDNDCBHGGETNEXTHOSTMSG pCBData = reinterpret_cast<PVBOXDNDCBHGGETNEXTHOSTMSG>(pvParms);
1092 AssertPtr(pCBData);
1093 AssertReturn(sizeof(VBOXDNDCBHGGETNEXTHOSTMSG) == cbParms, VERR_INVALID_PARAMETER);
1094 AssertReturn(CB_MAGIC_DND_HG_GET_NEXT_HOST_MSG == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1095
1096 try
1097 {
1098 GuestDnDMsg *pMsg = new GuestDnDMsg();
1099
1100 rc = pThis->i_sendURIDataLoop(pCtx, pMsg);
1101 if (rc == VINF_EOF) /* Transfer complete? */
1102 {
1103 LogFlowFunc(("Last URI item processed, bailing out\n"));
1104 }
1105 else if (RT_SUCCESS(rc))
1106 {
1107 rc = pThis->msgQueueAdd(pMsg);
1108 if (RT_SUCCESS(rc)) /* Return message type & required parameter count to the guest. */
1109 {
1110 LogFlowFunc(("GUEST_DND_GET_NEXT_HOST_MSG -> %RU32 (%RU32 params)\n", pMsg->getType(), pMsg->getCount()));
1111 pCBData->uMsg = pMsg->getType();
1112 pCBData->cParms = pMsg->getCount();
1113 }
1114 }
1115
1116 if ( RT_FAILURE(rc)
1117 || rc == VINF_EOF) /* Transfer complete? */
1118 {
1119 delete pMsg;
1120 pMsg = NULL;
1121 }
1122 }
1123 catch(std::bad_alloc & /*e*/)
1124 {
1125 rc = VERR_NO_MEMORY;
1126 }
1127 break;
1128 }
1129 case GUEST_DND_GH_EVT_ERROR:
1130 {
1131 PVBOXDNDCBEVTERRORDATA pCBData = reinterpret_cast<PVBOXDNDCBEVTERRORDATA>(pvParms);
1132 AssertPtr(pCBData);
1133 AssertReturn(sizeof(VBOXDNDCBEVTERRORDATA) == cbParms, VERR_INVALID_PARAMETER);
1134 AssertReturn(CB_MAGIC_DND_GH_EVT_ERROR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1135
1136 pCtx->mpResp->reset();
1137
1138 if (RT_SUCCESS(pCBData->rc))
1139 {
1140 AssertMsgFailed(("Guest has sent an error event but did not specify an actual error code\n"));
1141 pCBData->rc = VERR_GENERAL_FAILURE; /* Make sure some error is set. */
1142 }
1143
1144 rc = pCtx->mpResp->setProgress(100, DND_PROGRESS_ERROR, pCBData->rc,
1145 GuestDnDTarget::i_guestErrorToString(pCBData->rc));
1146 if (RT_SUCCESS(rc))
1147 {
1148 rc = VERR_GSTDND_GUEST_ERROR;
1149 rcGuest = pCBData->rc;
1150 }
1151 break;
1152 }
1153 case HOST_DND_HG_SND_DIR:
1154 case HOST_DND_HG_SND_FILE_HDR:
1155 case HOST_DND_HG_SND_FILE_DATA:
1156 {
1157 PVBOXDNDCBHGGETNEXTHOSTMSGDATA pCBData
1158 = reinterpret_cast<PVBOXDNDCBHGGETNEXTHOSTMSGDATA>(pvParms);
1159 AssertPtr(pCBData);
1160 AssertReturn(sizeof(VBOXDNDCBHGGETNEXTHOSTMSGDATA) == cbParms, VERR_INVALID_PARAMETER);
1161
1162 LogFlowFunc(("pCBData->uMsg=%RU32, paParms=%p, cParms=%RU32\n", pCBData->uMsg, pCBData->paParms, pCBData->cParms));
1163
1164 GuestDnDMsg *pMsg = pThis->msgQueueGetNext();
1165 if (pMsg)
1166 {
1167 /*
1168 * Sanity checks.
1169 */
1170 if ( pCBData->uMsg != uMsg
1171 || pCBData->paParms == NULL
1172 || pCBData->cParms != pMsg->getCount())
1173 {
1174 LogFlowFunc(("Current message does not match:\n"));
1175 LogFlowFunc(("\tCallback: uMsg=%RU32, cParms=%RU32, paParms=%p\n",
1176 pCBData->uMsg, pCBData->cParms, pCBData->paParms));
1177 LogFlowFunc(("\t Next: uMsg=%RU32, cParms=%RU32\n", pMsg->getType(), pMsg->getCount()));
1178
1179 /* Start over. */
1180 pThis->msgQueueClear();
1181
1182 rc = VERR_INVALID_PARAMETER;
1183 }
1184
1185 if (RT_SUCCESS(rc))
1186 {
1187 LogFlowFunc(("Returning uMsg=%RU32\n", uMsg));
1188 rc = HGCM::Message::copyParms(pCBData->paParms, pCBData->cParms, pMsg->getParms(), pMsg->getCount());
1189 if (RT_SUCCESS(rc))
1190 {
1191 pCBData->cParms = pMsg->getCount();
1192 pThis->msgQueueRemoveNext();
1193 }
1194 else
1195 LogFlowFunc(("Copying parameters failed with rc=%Rrc\n", rc));
1196 }
1197 }
1198 else
1199 rc = VERR_NO_DATA;
1200
1201 LogFlowFunc(("Processing next message ended with rc=%Rrc\n", rc));
1202 break;
1203 }
1204 default:
1205 rc = VERR_NOT_SUPPORTED;
1206 break;
1207 }
1208
1209 int rcToGuest = VINF_SUCCESS; /* Status which will be sent back to the guest. */
1210
1211 /*
1212 * Resolve errors.
1213 */
1214 switch (rc)
1215 {
1216 case VINF_SUCCESS:
1217 break;
1218
1219 case VINF_EOF:
1220 {
1221 LogRel2(("DnD: Transfer to guest complete\n"));
1222
1223 /* Complete operation on host side. */
1224 fNotify = true;
1225
1226 /* The guest expects VERR_NO_DATA if the transfer is complete. */
1227 rcToGuest = VERR_NO_DATA;
1228 break;
1229 }
1230
1231 case VERR_GSTDND_GUEST_ERROR:
1232 {
1233 LogRel(("DnD: Guest reported error %Rrc, aborting transfer to guest\n", rcGuest));
1234 break;
1235 }
1236
1237 case VERR_CANCELLED:
1238 {
1239 LogRel2(("DnD: Transfer to guest canceled\n"));
1240 rcToGuest = VERR_CANCELLED; /* Also cancel on guest side. */
1241 break;
1242 }
1243
1244 default:
1245 {
1246 LogRel(("DnD: Host error %Rrc occurred, aborting transfer to guest\n", rc));
1247 rcToGuest = VERR_CANCELLED; /* Also cancel on guest side. */
1248 break;
1249 }
1250 }
1251
1252 if (RT_FAILURE(rc))
1253 {
1254 /* Unregister this callback. */
1255 AssertPtr(pCtx->mpResp);
1256 int rc2 = pCtx->mpResp->setCallback(uMsg, NULL /* PFNGUESTDNDCALLBACK */);
1257 AssertRC(rc2);
1258
1259 /* Let the waiter(s) know. */
1260 fNotify = true;
1261 }
1262
1263 LogFlowFunc(("fNotify=%RTbool, rc=%Rrc, rcToGuest=%Rrc\n", fNotify, rc, rcToGuest));
1264
1265 if (fNotify)
1266 {
1267 int rc2 = pCtx->mCBEvent.Notify(rc); /** @todo Also pass guest error back? */
1268 AssertRC(rc2);
1269 }
1270
1271 LogFlowFuncLeaveRC(rc);
1272 return rcToGuest; /* Tell the guest. */
1273}
1274
1275int GuestDnDTarget::i_sendURIData(PSENDDATACTX pCtx, RTMSINTERVAL msTimeout)
1276{
1277 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1278 AssertPtr(pCtx->mpResp);
1279
1280#define REGISTER_CALLBACK(x) \
1281 do { \
1282 rc = pCtx->mpResp->setCallback(x, i_sendURIDataCallback, pCtx); \
1283 if (RT_FAILURE(rc)) \
1284 return rc; \
1285 } while (0)
1286
1287#define UNREGISTER_CALLBACK(x) \
1288 do { \
1289 int rc2 = pCtx->mpResp->setCallback(x, NULL); \
1290 AssertRC(rc2); \
1291 } while (0)
1292
1293 int rc = pCtx->mURI.init(mData.mcbBlockSize);
1294 if (RT_FAILURE(rc))
1295 return rc;
1296
1297 rc = pCtx->mCBEvent.Reset();
1298 if (RT_FAILURE(rc))
1299 return rc;
1300
1301 /*
1302 * Register callbacks.
1303 */
1304 /* Guest callbacks. */
1305 REGISTER_CALLBACK(GUEST_DND_CONNECT);
1306 REGISTER_CALLBACK(GUEST_DND_DISCONNECT);
1307 REGISTER_CALLBACK(GUEST_DND_GET_NEXT_HOST_MSG);
1308 REGISTER_CALLBACK(GUEST_DND_GH_EVT_ERROR);
1309 /* Host callbacks. */
1310 REGISTER_CALLBACK(HOST_DND_HG_SND_DIR);
1311 if (mDataBase.m_uProtocolVersion >= 2)
1312 REGISTER_CALLBACK(HOST_DND_HG_SND_FILE_HDR);
1313 REGISTER_CALLBACK(HOST_DND_HG_SND_FILE_DATA);
1314
1315 do
1316 {
1317 /*
1318 * Extract URI list from current meta data.
1319 */
1320 GuestDnDData *pData = &pCtx->mData;
1321 GuestDnDURIData *pURI = &pCtx->mURI;
1322
1323 rc = pURI->fromLocalMetaData(pData->getMeta());
1324 if (RT_FAILURE(rc))
1325 break;
1326
1327 LogFlowFunc(("URI root objects: %zu, total bytes (raw data to transfer): %zu\n",
1328 pURI->getURIList().RootCount(), pURI->getURIList().TotalBytes()));
1329
1330 /*
1331 * Set the new meta data with the URI list in it.
1332 */
1333 rc = pData->getMeta().fromURIList(pURI->getURIList());
1334 if (RT_FAILURE(rc))
1335 break;
1336
1337 /*
1338 * Set the estimated data sizes we are going to send.
1339 * The total size also contains the meta data size.
1340 */
1341 const uint32_t cbMeta = pData->getMeta().getSize();
1342 pData->setEstimatedSize(pURI->getURIList().TotalBytes() + cbMeta /* cbTotal */,
1343 cbMeta /* cbMeta */);
1344
1345 /*
1346 * Set the meta format.
1347 */
1348 void *pvFmt = (void *)pCtx->mFmtReq.c_str();
1349 uint32_t cbFmt = (uint32_t)pCtx->mFmtReq.length() + 1; /* Include terminating zero. */
1350
1351 pData->setFmt(pvFmt, cbFmt);
1352
1353 /*
1354 * The first message always is the data header. The meta data itself then follows
1355 * and *only* contains the root elements of an URI list.
1356 *
1357 * After the meta data we generate the messages required to send the
1358 * file/directory data itself.
1359 *
1360 * Note: Protocol < v3 use the first data message to tell what's being sent.
1361 */
1362 GuestDnDMsg Msg;
1363
1364 /*
1365 * Send the data header first.
1366 */
1367 if (mDataBase.m_uProtocolVersion >= 3)
1368 rc = i_sendDataHeader(pCtx, pData, &pCtx->mURI);
1369
1370 /*
1371 * Send the (meta) data body.
1372 */
1373 if (RT_SUCCESS(rc))
1374 rc = i_sendDataBody(pCtx, pData);
1375
1376 if (RT_SUCCESS(rc))
1377 {
1378 rc = waitForEvent(&pCtx->mCBEvent, pCtx->mpResp, msTimeout);
1379 if (RT_SUCCESS(rc))
1380 pCtx->mpResp->setProgress(100, DND_PROGRESS_COMPLETE, VINF_SUCCESS);
1381 }
1382
1383 } while (0);
1384
1385 /*
1386 * Unregister callbacks.
1387 */
1388 /* Guest callbacks. */
1389 UNREGISTER_CALLBACK(GUEST_DND_CONNECT);
1390 UNREGISTER_CALLBACK(GUEST_DND_DISCONNECT);
1391 UNREGISTER_CALLBACK(GUEST_DND_GET_NEXT_HOST_MSG);
1392 UNREGISTER_CALLBACK(GUEST_DND_GH_EVT_ERROR);
1393 /* Host callbacks. */
1394 UNREGISTER_CALLBACK(HOST_DND_HG_SND_DIR);
1395 if (mDataBase.m_uProtocolVersion >= 2)
1396 UNREGISTER_CALLBACK(HOST_DND_HG_SND_FILE_HDR);
1397 UNREGISTER_CALLBACK(HOST_DND_HG_SND_FILE_DATA);
1398
1399#undef REGISTER_CALLBACK
1400#undef UNREGISTER_CALLBACK
1401
1402 if (RT_FAILURE(rc))
1403 {
1404 if (rc == VERR_CANCELLED)
1405 pCtx->mpResp->setProgress(100, DND_PROGRESS_CANCELLED, VINF_SUCCESS);
1406 else if (rc != VERR_GSTDND_GUEST_ERROR) /* Guest-side error are already handled in the callback. */
1407 pCtx->mpResp->setProgress(100, DND_PROGRESS_ERROR, rc,
1408 GuestDnDTarget::i_hostErrorToString(rc));
1409 }
1410
1411 /*
1412 * Now that we've cleaned up tell the guest side to cancel.
1413 * This does not imply we're waiting for the guest to react, as the
1414 * host side never must depend on anything from the guest.
1415 */
1416 if (rc == VERR_CANCELLED)
1417 {
1418 int rc2 = sendCancel();
1419 AssertRC(rc2);
1420 }
1421
1422 LogFlowFuncLeaveRC(rc);
1423 return rc;
1424}
1425
1426int GuestDnDTarget::i_sendURIDataLoop(PSENDDATACTX pCtx, GuestDnDMsg *pMsg)
1427{
1428 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1429 AssertPtrReturn(pMsg, VERR_INVALID_POINTER);
1430
1431 int rc = updateProgress(&pCtx->mData, pCtx->mpResp);
1432 AssertRC(rc);
1433
1434 if ( pCtx->mData.isComplete()
1435 && pCtx->mURI.isComplete())
1436 {
1437 return VINF_EOF;
1438 }
1439
1440 GuestDnDURIObjCtx &objCtx = pCtx->mURI.getObjCurrent();
1441 if (!objCtx.isValid())
1442 return VERR_WRONG_ORDER;
1443
1444 DnDURIObject *pCurObj = objCtx.getObj();
1445 AssertPtr(pCurObj);
1446
1447 uint32_t fMode = pCurObj->GetMode();
1448 LogRel3(("DnD: Processing: srcPath=%s, dstPath=%s, fMode=0x%x, cbSize=%RU32, fIsDir=%RTbool, fIsFile=%RTbool\n",
1449 pCurObj->GetSourcePath().c_str(), pCurObj->GetDestPath().c_str(),
1450 fMode, pCurObj->GetSize(),
1451 RTFS_IS_DIRECTORY(fMode), RTFS_IS_FILE(fMode)));
1452
1453 if (RTFS_IS_DIRECTORY(fMode))
1454 {
1455 rc = i_sendDirectory(pCtx, &objCtx, pMsg);
1456 }
1457 else if (RTFS_IS_FILE(fMode))
1458 {
1459 rc = i_sendFile(pCtx, &objCtx, pMsg);
1460 }
1461 else
1462 {
1463 AssertMsgFailed(("fMode=0x%x is not supported for srcPath=%s, dstPath=%s\n",
1464 fMode, pCurObj->GetSourcePath().c_str(), pCurObj->GetDestPath().c_str()));
1465 rc = VERR_NOT_SUPPORTED;
1466 }
1467
1468 bool fRemove = false; /* Remove current entry? */
1469 if ( pCurObj->IsComplete()
1470 || RT_FAILURE(rc))
1471 {
1472 fRemove = true;
1473 }
1474
1475 if (fRemove)
1476 {
1477 LogFlowFunc(("Removing \"%s\" from list, rc=%Rrc\n", pCurObj->GetSourcePath().c_str(), rc));
1478 pCtx->mURI.removeObjCurrent();
1479 }
1480
1481 LogFlowFuncLeaveRC(rc);
1482 return rc;
1483}
1484
1485int GuestDnDTarget::i_sendRawData(PSENDDATACTX pCtx, RTMSINTERVAL msTimeout)
1486{
1487 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1488 NOREF(msTimeout);
1489
1490 GuestDnDData *pData = &pCtx->mData;
1491
1492 /** @todo At the moment we only allow sending up to 64K raw data.
1493 * For protocol v1+v2: Fix this by using HOST_DND_HG_SND_MORE_DATA.
1494 * For protocol v3 : Send another HOST_DND_HG_SND_DATA message. */
1495 if (!pData->getMeta().getSize())
1496 return VINF_SUCCESS;
1497
1498 int rc = VINF_SUCCESS;
1499
1500 /*
1501 * Send the data header first.
1502 */
1503 if (mDataBase.m_uProtocolVersion >= 3)
1504 rc = i_sendDataHeader(pCtx, pData, NULL /* URI list */);
1505
1506 /*
1507 * Send the (meta) data body.
1508 */
1509 if (RT_SUCCESS(rc))
1510 rc = i_sendDataBody(pCtx, pData);
1511
1512 int rc2;
1513 if (RT_FAILURE(rc))
1514 rc2 = pCtx->mpResp->setProgress(100, DND_PROGRESS_ERROR, rc,
1515 GuestDnDTarget::i_hostErrorToString(rc));
1516 else
1517 rc2 = pCtx->mpResp->setProgress(100, DND_PROGRESS_COMPLETE, rc);
1518 AssertRC(rc2);
1519
1520 LogFlowFuncLeaveRC(rc);
1521 return rc;
1522}
1523
1524HRESULT GuestDnDTarget::cancel(BOOL *aVeto)
1525{
1526#if !defined(VBOX_WITH_DRAG_AND_DROP)
1527 ReturnComNotImplemented();
1528#else /* VBOX_WITH_DRAG_AND_DROP */
1529
1530 int rc = i_cancelOperation();
1531
1532 if (aVeto)
1533 *aVeto = FALSE; /** @todo */
1534
1535 HRESULT hr = RT_SUCCESS(rc) ? S_OK : VBOX_E_IPRT_ERROR;
1536
1537 LogFlowFunc(("hr=%Rhrc\n", hr));
1538 return hr;
1539#endif /* VBOX_WITH_DRAG_AND_DROP */
1540}
1541
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use