VirtualBox

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

Last change on this file since 73951 was 73941, checked in by vboxsync, 6 years ago

DnD/Main: Fixed including the context ID when using protocol v3 with HOST_DND_HG_EVT_LEAVE and HOST_DND_HG_EVT_CANCEL.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 52.8 KB
Line 
1/* $Id: GuestDnDTargetImpl.cpp 73941 2018-08-29 08:56:02Z 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
435 GuestDnDMsg Msg;
436 Msg.setType(HOST_DND_HG_EVT_LEAVE);
437 if (mDataBase.m_uProtocolVersion >= 3)
438 Msg.setNextUInt32(0); /** @todo ContextID not used yet. */
439
440 int rc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
441 if (RT_SUCCESS(rc))
442 {
443 GuestDnDResponse *pResp = GuestDnDInst()->response();
444 if (pResp)
445 pResp->waitForGuestResponse();
446 }
447
448 if (RT_FAILURE(rc))
449 hr = VBOX_E_IPRT_ERROR;
450
451 LogFlowFunc(("hr=%Rhrc\n", hr));
452 return hr;
453#endif /* VBOX_WITH_DRAG_AND_DROP */
454}
455
456HRESULT GuestDnDTarget::drop(ULONG aScreenId, ULONG aX, ULONG aY,
457 DnDAction_T aDefaultAction,
458 const std::vector<DnDAction_T> &aAllowedActions,
459 const GuestDnDMIMEList &aFormats,
460 com::Utf8Str &aFormat,
461 DnDAction_T *aResultAction)
462{
463#if !defined(VBOX_WITH_DRAG_AND_DROP)
464 ReturnComNotImplemented();
465#else /* VBOX_WITH_DRAG_AND_DROP */
466
467 if (aDefaultAction == DnDAction_Ignore)
468 return setError(E_INVALIDARG, tr("Invalid default action specified"));
469 if (!aAllowedActions.size())
470 return setError(E_INVALIDARG, tr("Invalid allowed actions specified"));
471 if (!aFormats.size())
472 return setError(E_INVALIDARG, tr("No drop format(s) specified"));
473 /* aResultAction is optional. */
474
475 AutoCaller autoCaller(this);
476 if (FAILED(autoCaller.rc())) return autoCaller.rc();
477
478 /* Default action is ignoring. */
479 DnDAction_T resAction = DnDAction_Ignore;
480
481 /* Check & convert the drag & drop actions to HGCM codes. */
482 uint32_t uDefAction = DND_IGNORE_ACTION;
483 uint32_t uAllowedActions = 0;
484 GuestDnD::toHGCMActions(aDefaultAction, &uDefAction,
485 aAllowedActions, &uAllowedActions);
486 /* If there is no usable action, ignore this request. */
487 if (isDnDIgnoreAction(uDefAction))
488 {
489 aFormat = "";
490 if (aResultAction)
491 *aResultAction = DnDAction_Ignore;
492 return S_OK;
493 }
494
495 /*
496 * Make a flat data string out of the supported format list.
497 * In the GuestDnDTarget case the source formats are from the host,
498 * as GuestDnDTarget acts as a source for the guest.
499 */
500 Utf8Str strFormats = GuestDnD::toFormatString(GuestDnD::toFilteredFormatList(m_lstFmtSupported, aFormats));
501 if (strFormats.isEmpty())
502 return setError(E_INVALIDARG, tr("No or not supported format(s) specified"));
503 const uint32_t cbFormats = (uint32_t)strFormats.length() + 1; /* Include terminating zero. */
504
505 /* Adjust the coordinates in a multi-monitor setup. */
506 HRESULT hr = GuestDnDInst()->adjustScreenCoordinates(aScreenId, &aX, &aY);
507 if (SUCCEEDED(hr))
508 {
509 GuestDnDMsg Msg;
510 Msg.setType(HOST_DND_HG_EVT_DROPPED);
511 if (mDataBase.m_uProtocolVersion >= 3)
512 Msg.setNextUInt32(0); /** @todo ContextID not used yet. */
513 Msg.setNextUInt32(aScreenId);
514 Msg.setNextUInt32(aX);
515 Msg.setNextUInt32(aY);
516 Msg.setNextUInt32(uDefAction);
517 Msg.setNextUInt32(uAllowedActions);
518 Msg.setNextPointer((void*)strFormats.c_str(), cbFormats);
519 Msg.setNextUInt32(cbFormats);
520
521 int vrc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
522 if (RT_SUCCESS(vrc))
523 {
524 GuestDnDResponse *pResp = GuestDnDInst()->response();
525 AssertPtr(pResp);
526
527 vrc = pResp->waitForGuestResponse();
528 if (RT_SUCCESS(vrc))
529 {
530 resAction = GuestDnD::toMainAction(pResp->defAction());
531
532 GuestDnDMIMEList lstFormats = pResp->formats();
533 if (lstFormats.size() == 1) /* Exactly one format to use specified? */
534 {
535 aFormat = lstFormats.at(0);
536 LogFlowFunc(("resFormat=%s, resAction=%RU32\n", aFormat.c_str(), pResp->defAction()));
537 }
538 else
539 /** @todo r=bird: This isn't an IPRT error, is it? */
540 hr = setError(VBOX_E_IPRT_ERROR, tr("Guest returned invalid drop formats (%zu formats)"), lstFormats.size());
541 }
542 else
543 hr = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Waiting for response of dropped event failed (%Rrc)"), vrc);
544 }
545 else
546 hr = setErrorBoth(VBOX_E_IPRT_ERROR, vrc, tr("Sending dropped event to guest failed (%Rrc)"), vrc);
547 }
548 else
549 hr = setError(hr, tr("Retrieving drop coordinates failed"));
550
551 if (SUCCEEDED(hr))
552 {
553 if (aResultAction)
554 *aResultAction = resAction;
555 }
556
557 LogFlowFunc(("Returning hr=%Rhrc\n", hr));
558 return hr;
559#endif /* VBOX_WITH_DRAG_AND_DROP */
560}
561
562/* static */
563void GuestDnDTarget::i_sendDataThreadTask(SendDataTask *pTask)
564{
565 LogFlowFunc(("pTask=%p\n", pTask));
566
567 AssertPtrReturnVoid(pTask);
568
569 const ComObjPtr<GuestDnDTarget> pThis(pTask->getTarget());
570 Assert(!pThis.isNull());
571
572 AutoCaller autoCaller(pThis);
573 if (FAILED(autoCaller.rc()))
574 return;
575
576 int vrc = pThis->i_sendData(pTask->getCtx(), RT_INDEFINITE_WAIT /* msTimeout */);
577 NOREF(vrc);
578/** @todo
579 *
580 * r=bird: What happens with @a vrc?
581 *
582 */
583
584 AutoWriteLock alock(pThis COMMA_LOCKVAL_SRC_POS);
585
586 Assert(pThis->mDataBase.m_cTransfersPending);
587 if (pThis->mDataBase.m_cTransfersPending)
588 pThis->mDataBase.m_cTransfersPending--;
589
590 LogFlowFunc(("pTarget=%p vrc=%Rrc (ignored)\n", (GuestDnDTarget *)pThis, vrc));
591}
592
593/**
594 * Initiates a data transfer from the host to the guest. The source is the host whereas the target is the
595 * guest in this case.
596 *
597 * @return HRESULT
598 * @param aScreenId
599 * @param aFormat
600 * @param aData
601 * @param aProgress
602 */
603HRESULT GuestDnDTarget::sendData(ULONG aScreenId, const com::Utf8Str &aFormat, const std::vector<BYTE> &aData,
604 ComPtr<IProgress> &aProgress)
605{
606#if !defined(VBOX_WITH_DRAG_AND_DROP)
607 ReturnComNotImplemented();
608#else /* VBOX_WITH_DRAG_AND_DROP */
609
610 AutoCaller autoCaller(this);
611 if (FAILED(autoCaller.rc())) return autoCaller.rc();
612
613 /* Input validation. */
614 if (RT_UNLIKELY((aFormat.c_str()) == NULL || *(aFormat.c_str()) == '\0'))
615 return setError(E_INVALIDARG, tr("No data format specified"));
616 if (RT_UNLIKELY(!aData.size()))
617 return setError(E_INVALIDARG, tr("No data to send specified"));
618
619 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
620
621 /* At the moment we only support one transfer at a time. */
622 if (mDataBase.m_cTransfersPending)
623 return setError(E_INVALIDARG, tr("Another drop operation already is in progress"));
624
625 /* Ditto. */
626 GuestDnDResponse *pResp = GuestDnDInst()->response();
627 AssertPtr(pResp);
628
629 HRESULT hr = pResp->resetProgress(m_pGuest);
630 if (FAILED(hr))
631 return hr;
632
633 SendDataTask *pTask = NULL;
634 PSENDDATACTX pSendCtx = NULL;
635
636 try
637 {
638 //pSendCtx is passed into SendDataTask where one is deleted in destructor
639 pSendCtx = new SENDDATACTX;
640 RT_BZERO(pSendCtx, sizeof(SENDDATACTX));
641
642 pSendCtx->mpTarget = this;
643 pSendCtx->mpResp = pResp;
644 pSendCtx->mScreenID = aScreenId;
645 pSendCtx->mFmtReq = aFormat;
646 pSendCtx->mData.getMeta().add(aData);
647
648 /* pTask is responsible for deletion of pSendCtx after creating */
649 pTask = new SendDataTask(this, pSendCtx);
650 if (!pTask->isOk())
651 {
652 delete pTask;
653 LogRel2(("DnD: Could not create SendDataTask object \n"));
654 throw hr = E_FAIL;
655 }
656
657 /* This function delete pTask in case of exceptions,
658 * so there is no need in the call of delete operator. */
659 hr = pTask->createThreadWithType(RTTHREADTYPE_MAIN_WORKER);
660
661 }
662 catch (std::bad_alloc &)
663 {
664 hr = setError(E_OUTOFMEMORY);
665 }
666 catch (...)
667 {
668 LogRel2(("DnD: Could not create thread for data sending task\n"));
669 hr = E_FAIL;
670 }
671
672 if (SUCCEEDED(hr))
673 {
674 mDataBase.m_cTransfersPending++;
675
676 hr = pResp->queryProgressTo(aProgress.asOutParam());
677 ComAssertComRC(hr);
678
679 /* Note: pTask is now owned by the worker thread. */
680 }
681 else
682 hr = setError(hr, tr("Starting thread for GuestDnDTarget::i_sendDataThread (%Rhrc)"), hr);
683
684 LogFlowFunc(("Returning hr=%Rhrc\n", hr));
685 return hr;
686#endif /* VBOX_WITH_DRAG_AND_DROP */
687}
688
689int GuestDnDTarget::i_cancelOperation(void)
690{
691 /** @todo Check for pending cancel requests. */
692
693#if 0 /** @todo Later. */
694 /* Cancel any outstanding waits for guest responses first. */
695 if (pResp)
696 pResp->notifyAboutGuestResponse();
697#endif
698
699 LogFlowFunc(("Cancelling operation, telling guest ...\n"));
700
701 GuestDnDMsg Msg;
702 Msg.setType(HOST_DND_HG_EVT_CANCEL);
703 if (mDataBase.m_uProtocolVersion >= 3)
704 Msg.setNextUInt32(0); /** @todo ContextID not used yet. */
705
706 return GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
707}
708
709/* static */
710Utf8Str GuestDnDTarget::i_guestErrorToString(int guestRc)
711{
712 Utf8Str strError;
713
714 switch (guestRc)
715 {
716 case VERR_ACCESS_DENIED:
717 strError += Utf8StrFmt(tr("For one or more guest files or directories selected for transferring to the host your guest "
718 "user does not have the appropriate access rights for. Please make sure that all selected "
719 "elements can be accessed and that your guest user has the appropriate rights"));
720 break;
721
722 case VERR_NOT_FOUND:
723 /* Should not happen due to file locking on the guest, but anyway ... */
724 strError += Utf8StrFmt(tr("One or more guest files or directories selected for transferring to the host were not"
725 "found on the guest anymore. This can be the case if the guest files were moved and/or"
726 "altered while the drag and drop operation was in progress"));
727 break;
728
729 case VERR_SHARING_VIOLATION:
730 strError += Utf8StrFmt(tr("One or more guest files or directories selected for transferring to the host were locked. "
731 "Please make sure that all selected elements can be accessed and that your guest user has "
732 "the appropriate rights"));
733 break;
734
735 case VERR_TIMEOUT:
736 strError += Utf8StrFmt(tr("The guest was not able to process the drag and drop data within time"));
737 break;
738
739 default:
740 strError += Utf8StrFmt(tr("Drag and drop error from guest (%Rrc)"), guestRc);
741 break;
742 }
743
744 return strError;
745}
746
747/* static */
748Utf8Str GuestDnDTarget::i_hostErrorToString(int hostRc)
749{
750 Utf8Str strError;
751
752 switch (hostRc)
753 {
754 case VERR_ACCESS_DENIED:
755 strError += Utf8StrFmt(tr("For one or more host files or directories selected for transferring to the guest your host "
756 "user does not have the appropriate access rights for. Please make sure that all selected "
757 "elements can be accessed and that your host user has the appropriate rights."));
758 break;
759
760 case VERR_NOT_FOUND:
761 /* Should not happen due to file locking on the host, but anyway ... */
762 strError += Utf8StrFmt(tr("One or more host files or directories selected for transferring to the host were not"
763 "found on the host anymore. This can be the case if the host files were moved and/or"
764 "altered while the drag and drop operation was in progress."));
765 break;
766
767 case VERR_SHARING_VIOLATION:
768 strError += Utf8StrFmt(tr("One or more host files or directories selected for transferring to the guest were locked. "
769 "Please make sure that all selected elements can be accessed and that your host user has "
770 "the appropriate rights."));
771 break;
772
773 default:
774 strError += Utf8StrFmt(tr("Drag and drop error from host (%Rrc)"), hostRc);
775 break;
776 }
777
778 return strError;
779}
780
781/**
782 * @returns VBox status code that the caller ignores. Not sure if that's
783 * intentional or not.
784 */
785int GuestDnDTarget::i_sendData(PSENDDATACTX pCtx, RTMSINTERVAL msTimeout)
786{
787 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
788
789 int rc;
790
791 ASMAtomicWriteBool(&pCtx->mIsActive, true);
792
793 /* Clear all remaining outgoing messages. */
794 mDataBase.m_lstMsgOut.clear();
795
796 /**
797 * Do we need to build up a file tree?
798 * Note: The decision whether we need to build up a file tree and sending
799 * actual file data only depends on the actual formats offered by this target.
800 * If the guest does not want an URI list ("text/uri-list") but text ("TEXT" and
801 * friends) instead, still send the data over to the guest -- the file as such still
802 * is needed on the guest in this case, as the guest then just wants a simple path
803 * instead of an URI list (pointing to a file on the guest itself).
804 *
805 ** @todo Support more than one format; add a format<->function handler concept. Later. */
806 bool fHasURIList = std::find(m_lstFmtOffered.begin(),
807 m_lstFmtOffered.end(), "text/uri-list") != m_lstFmtOffered.end();
808 if (fHasURIList)
809 {
810 rc = i_sendURIData(pCtx, msTimeout);
811 }
812 else
813 {
814 rc = i_sendRawData(pCtx, msTimeout);
815 }
816
817 ASMAtomicWriteBool(&pCtx->mIsActive, false);
818
819 LogFlowFuncLeaveRC(rc);
820 return rc;
821}
822
823int GuestDnDTarget::i_sendDataBody(PSENDDATACTX pCtx, GuestDnDData *pData)
824{
825 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
826 AssertPtrReturn(pData, VERR_INVALID_POINTER);
827
828 /** @todo Add support for multiple HOST_DND_HG_SND_DATA messages in case of more than 64K data! */
829 if (pData->getMeta().getSize() > _64K)
830 return VERR_NOT_IMPLEMENTED;
831
832 GuestDnDMsg Msg;
833
834 LogFlowFunc(("cbFmt=%RU32, cbMeta=%RU32, cbChksum=%RU32\n",
835 pData->getFmtSize(), pData->getMeta().getSize(), pData->getChkSumSize()));
836
837 Msg.setType(HOST_DND_HG_SND_DATA);
838 if (mDataBase.m_uProtocolVersion < 3)
839 {
840 Msg.setNextUInt32(pCtx->mScreenID); /* uScreenId */
841 Msg.setNextPointer(pData->getFmtMutable(), pData->getFmtSize()); /* pvFormat */
842 Msg.setNextUInt32(pData->getFmtSize()); /* cbFormat */
843 Msg.setNextPointer(pData->getMeta().getDataMutable(), pData->getMeta().getSize()); /* pvData */
844 /* Fill in the current data block size to send.
845 * Note: Only supports uint32_t. */
846 Msg.setNextUInt32((uint32_t)pData->getMeta().getSize()); /* cbData */
847 }
848 else
849 {
850 Msg.setNextUInt32(0); /** @todo ContextID not used yet. */
851 Msg.setNextPointer(pData->getMeta().getDataMutable(), pData->getMeta().getSize()); /* pvData */
852 Msg.setNextUInt32(pData->getMeta().getSize()); /* cbData */
853 Msg.setNextPointer(pData->getChkSumMutable(), pData->getChkSumSize()); /** @todo pvChecksum; not used yet. */
854 Msg.setNextUInt32(pData->getChkSumSize()); /** @todo cbChecksum; not used yet. */
855 }
856
857 int rc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
858 if (RT_SUCCESS(rc))
859 rc = updateProgress(pData, pCtx->mpResp, pData->getMeta().getSize());
860
861 LogFlowFuncLeaveRC(rc);
862 return rc;
863}
864
865int GuestDnDTarget::i_sendDataHeader(PSENDDATACTX pCtx, GuestDnDData *pData, GuestDnDURIData *pURIData /* = NULL */)
866{
867 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
868 AssertPtrReturn(pData, VERR_INVALID_POINTER);
869 /* pURIData is optional. */
870
871 GuestDnDMsg Msg;
872
873 Msg.setType(HOST_DND_HG_SND_DATA_HDR);
874
875 Msg.setNextUInt32(0); /** @todo uContext; not used yet. */
876 Msg.setNextUInt32(0); /** @todo uFlags; not used yet. */
877 Msg.setNextUInt32(pCtx->mScreenID); /* uScreen */
878 Msg.setNextUInt64(pData->getTotal()); /* cbTotal */
879 Msg.setNextUInt32(pData->getMeta().getSize()); /* cbMeta*/
880 Msg.setNextPointer(pData->getFmtMutable(), pData->getFmtSize()); /* pvMetaFmt */
881 Msg.setNextUInt32(pData->getFmtSize()); /* cbMetaFmt */
882 Msg.setNextUInt64(pURIData ? pURIData->getObjToProcess() : 0); /* cObjects */
883 Msg.setNextUInt32(0); /** @todo enmCompression; not used yet. */
884 Msg.setNextUInt32(0); /** @todo enmChecksumType; not used yet. */
885 Msg.setNextPointer(NULL, 0); /** @todo pvChecksum; not used yet. */
886 Msg.setNextUInt32(0); /** @todo cbChecksum; not used yet. */
887
888 int rc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
889
890 LogFlowFuncLeaveRC(rc);
891 return rc;
892}
893
894int GuestDnDTarget::i_sendDirectory(PSENDDATACTX pCtx, GuestDnDURIObjCtx *pObjCtx, GuestDnDMsg *pMsg)
895{
896 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
897 AssertPtrReturn(pObjCtx, VERR_INVALID_POINTER);
898 AssertPtrReturn(pMsg, VERR_INVALID_POINTER);
899
900 DnDURIObject *pObj = pObjCtx->getObj();
901 AssertPtr(pObj);
902
903 RTCString strPath = pObj->GetDestPath();
904 if (strPath.isEmpty())
905 return VERR_INVALID_PARAMETER;
906 if (strPath.length() >= RTPATH_MAX) /* Note: Maximum is RTPATH_MAX on guest side. */
907 return VERR_BUFFER_OVERFLOW;
908
909 LogRel2(("DnD: Transferring host directory to guest: %s\n", strPath.c_str()));
910
911 pMsg->setType(HOST_DND_HG_SND_DIR);
912 if (mDataBase.m_uProtocolVersion >= 3)
913 pMsg->setNextUInt32(0); /** @todo ContextID not used yet. */
914 pMsg->setNextString(strPath.c_str()); /* path */
915 pMsg->setNextUInt32((uint32_t)(strPath.length() + 1)); /* path length (maximum is RTPATH_MAX on guest side). */
916 pMsg->setNextUInt32(pObj->GetMode()); /* mode */
917
918 return VINF_SUCCESS;
919}
920
921int GuestDnDTarget::i_sendFile(PSENDDATACTX pCtx, GuestDnDURIObjCtx *pObjCtx, GuestDnDMsg *pMsg)
922{
923 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
924 AssertPtrReturn(pObjCtx, VERR_INVALID_POINTER);
925 AssertPtrReturn(pMsg, VERR_INVALID_POINTER);
926
927 DnDURIObject *pObj = pObjCtx->getObj();
928 AssertPtr(pObj);
929
930 RTCString strPathSrc = pObj->GetSourcePath();
931 if (strPathSrc.isEmpty())
932 return VERR_INVALID_PARAMETER;
933
934 int rc = VINF_SUCCESS;
935
936 LogFlowFunc(("Sending file with %RU32 bytes buffer, using protocol v%RU32 ...\n",
937 mData.mcbBlockSize, mDataBase.m_uProtocolVersion));
938 LogFlowFunc(("strPathSrc=%s, fIsOpen=%RTbool, cbSize=%RU64\n", strPathSrc.c_str(), pObj->IsOpen(), pObj->GetSize()));
939
940 if (!pObj->IsOpen())
941 {
942 LogRel2(("DnD: Opening host file for transferring to guest: %s\n", strPathSrc.c_str()));
943 rc = pObj->OpenEx(strPathSrc, DnDURIObject::File, DnDURIObject::Source,
944 RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE, 0 /* fFlags */);
945 if (RT_FAILURE(rc))
946 LogRel(("DnD: Error opening host file '%s', rc=%Rrc\n", strPathSrc.c_str(), rc));
947 }
948
949 bool fSendData = false;
950 if (RT_SUCCESS(rc))
951 {
952 if (mDataBase.m_uProtocolVersion >= 2)
953 {
954 uint32_t fState = pObjCtx->getState();
955 if (!(fState & DND_OBJCTX_STATE_HAS_HDR))
956 {
957 /*
958 * Since protocol v2 the file header and the actual file contents are
959 * separate messages, so send the file header first.
960 * The just registered callback will be called by the guest afterwards.
961 */
962 pMsg->setType(HOST_DND_HG_SND_FILE_HDR);
963 pMsg->setNextUInt32(0); /** @todo ContextID not used yet. */
964 pMsg->setNextString(pObj->GetDestPath().c_str()); /* pvName */
965 pMsg->setNextUInt32((uint32_t)(pObj->GetDestPath().length() + 1)); /* cbName */
966 pMsg->setNextUInt32(0); /* uFlags */
967 pMsg->setNextUInt32(pObj->GetMode()); /* fMode */
968 pMsg->setNextUInt64(pObj->GetSize()); /* uSize */
969
970 LogFlowFunc(("Sending file header ...\n"));
971 LogRel2(("DnD: Transferring host file to guest: %s (%RU64 bytes, mode 0x%x)\n",
972 strPathSrc.c_str(), pObj->GetSize(), pObj->GetMode()));
973
974 /** @todo Set progress object title to current file being transferred? */
975
976 pObjCtx->setState(fState | DND_OBJCTX_STATE_HAS_HDR);
977 }
978 else
979 {
980 /* File header was sent, so only send the actual file data. */
981 fSendData = true;
982 }
983 }
984 else /* Protocol v1. */
985 {
986 /* Always send the file data, every time. */
987 fSendData = true;
988 }
989 }
990
991 if ( RT_SUCCESS(rc)
992 && fSendData)
993 {
994 rc = i_sendFileData(pCtx, pObjCtx, pMsg);
995 }
996
997 LogFlowFuncLeaveRC(rc);
998 return rc;
999}
1000
1001int GuestDnDTarget::i_sendFileData(PSENDDATACTX pCtx, GuestDnDURIObjCtx *pObjCtx, GuestDnDMsg *pMsg)
1002{
1003 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1004 AssertPtrReturn(pObjCtx, VERR_INVALID_POINTER);
1005 AssertPtrReturn(pMsg, VERR_INVALID_POINTER);
1006
1007 DnDURIObject *pObj = pObjCtx->getObj();
1008 AssertPtr(pObj);
1009
1010 AssertPtr(pCtx->mpResp);
1011
1012 /** @todo Don't allow concurrent reads per context! */
1013
1014 /*
1015 * Start sending stuff.
1016 */
1017
1018 /* Set the message type. */
1019 pMsg->setType(HOST_DND_HG_SND_FILE_DATA);
1020
1021 /* Protocol version 1 sends the file path *every* time with a new file chunk.
1022 * In protocol version 2 we only do this once with HOST_DND_HG_SND_FILE_HDR. */
1023 if (mDataBase.m_uProtocolVersion <= 1)
1024 {
1025 pMsg->setNextString(pObj->GetDestPath().c_str()); /* pvName */
1026 pMsg->setNextUInt32((uint32_t)(pObj->GetDestPath().length() + 1)); /* cbName */
1027 }
1028 else if (mDataBase.m_uProtocolVersion >= 2)
1029 {
1030 pMsg->setNextUInt32(0); /** @todo ContextID not used yet. */
1031 }
1032
1033 uint32_t cbRead = 0;
1034
1035 int rc = pObj->Read(pCtx->mURI.getBufferMutable(), pCtx->mURI.getBufferSize(), &cbRead);
1036 if (RT_SUCCESS(rc))
1037 {
1038 pCtx->mData.addProcessed(cbRead);
1039 LogFlowFunc(("cbBufSize=%zu, cbRead=%RU32\n", pCtx->mURI.getBufferSize(), cbRead));
1040
1041 if (mDataBase.m_uProtocolVersion <= 1)
1042 {
1043 pMsg->setNextPointer(pCtx->mURI.getBufferMutable(), cbRead); /* pvData */
1044 pMsg->setNextUInt32(cbRead); /* cbData */
1045 pMsg->setNextUInt32(pObj->GetMode()); /* fMode */
1046 }
1047 else /* Protocol v2 and up. */
1048 {
1049 pMsg->setNextPointer(pCtx->mURI.getBufferMutable(), cbRead); /* pvData */
1050 pMsg->setNextUInt32(cbRead); /* cbData */
1051
1052 if (mDataBase.m_uProtocolVersion >= 3)
1053 {
1054 /** @todo Calculate checksum. */
1055 pMsg->setNextPointer(NULL, 0); /* pvChecksum */
1056 pMsg->setNextUInt32(0); /* cbChecksum */
1057 }
1058 }
1059
1060 if (pObj->IsComplete()) /* Done reading? */
1061 {
1062 LogRel2(("DnD: File transfer to guest complete: %s\n", pObj->GetSourcePath().c_str()));
1063 LogFlowFunc(("File '%s' complete\n", pObj->GetSourcePath().c_str()));
1064
1065 /* DnDURIObject::Read() returns VINF_EOF when finished reading the entire fire,
1066 * but we don't want this here -- so just override this with VINF_SUCCESS. */
1067 rc = VINF_SUCCESS;
1068 }
1069 }
1070
1071 LogFlowFuncLeaveRC(rc);
1072 return rc;
1073}
1074
1075/* static */
1076DECLCALLBACK(int) GuestDnDTarget::i_sendURIDataCallback(uint32_t uMsg, void *pvParms, size_t cbParms, void *pvUser)
1077{
1078 PSENDDATACTX pCtx = (PSENDDATACTX)pvUser;
1079 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1080
1081 GuestDnDTarget *pThis = pCtx->mpTarget;
1082 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
1083
1084 LogFlowFunc(("pThis=%p, uMsg=%RU32\n", pThis, uMsg));
1085
1086 int rc = VINF_SUCCESS;
1087 int rcGuest = VINF_SUCCESS; /* Contains error code from guest in case of VERR_GSTDND_GUEST_ERROR. */
1088 bool fNotify = false;
1089
1090 switch (uMsg)
1091 {
1092 case GUEST_DND_CONNECT:
1093 /* Nothing to do here (yet). */
1094 break;
1095
1096 case GUEST_DND_DISCONNECT:
1097 rc = VERR_CANCELLED;
1098 break;
1099
1100 case GUEST_DND_GET_NEXT_HOST_MSG:
1101 {
1102 PVBOXDNDCBHGGETNEXTHOSTMSG pCBData = reinterpret_cast<PVBOXDNDCBHGGETNEXTHOSTMSG>(pvParms);
1103 AssertPtr(pCBData);
1104 AssertReturn(sizeof(VBOXDNDCBHGGETNEXTHOSTMSG) == cbParms, VERR_INVALID_PARAMETER);
1105 AssertReturn(CB_MAGIC_DND_HG_GET_NEXT_HOST_MSG == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1106
1107 try
1108 {
1109 GuestDnDMsg *pMsg = new GuestDnDMsg();
1110
1111 rc = pThis->i_sendURIDataLoop(pCtx, pMsg);
1112 if (rc == VINF_EOF) /* Transfer complete? */
1113 {
1114 LogFlowFunc(("Last URI item processed, bailing out\n"));
1115 }
1116 else if (RT_SUCCESS(rc))
1117 {
1118 rc = pThis->msgQueueAdd(pMsg);
1119 if (RT_SUCCESS(rc)) /* Return message type & required parameter count to the guest. */
1120 {
1121 LogFlowFunc(("GUEST_DND_GET_NEXT_HOST_MSG -> %RU32 (%RU32 params)\n", pMsg->getType(), pMsg->getCount()));
1122 pCBData->uMsg = pMsg->getType();
1123 pCBData->cParms = pMsg->getCount();
1124 }
1125 }
1126
1127 if ( RT_FAILURE(rc)
1128 || rc == VINF_EOF) /* Transfer complete? */
1129 {
1130 delete pMsg;
1131 pMsg = NULL;
1132 }
1133 }
1134 catch(std::bad_alloc & /*e*/)
1135 {
1136 rc = VERR_NO_MEMORY;
1137 }
1138 break;
1139 }
1140 case GUEST_DND_GH_EVT_ERROR:
1141 {
1142 PVBOXDNDCBEVTERRORDATA pCBData = reinterpret_cast<PVBOXDNDCBEVTERRORDATA>(pvParms);
1143 AssertPtr(pCBData);
1144 AssertReturn(sizeof(VBOXDNDCBEVTERRORDATA) == cbParms, VERR_INVALID_PARAMETER);
1145 AssertReturn(CB_MAGIC_DND_GH_EVT_ERROR == pCBData->hdr.uMagic, VERR_INVALID_PARAMETER);
1146
1147 pCtx->mpResp->reset();
1148
1149 if (RT_SUCCESS(pCBData->rc))
1150 {
1151 AssertMsgFailed(("Guest has sent an error event but did not specify an actual error code\n"));
1152 pCBData->rc = VERR_GENERAL_FAILURE; /* Make sure some error is set. */
1153 }
1154
1155 rc = pCtx->mpResp->setProgress(100, DND_PROGRESS_ERROR, pCBData->rc,
1156 GuestDnDTarget::i_guestErrorToString(pCBData->rc));
1157 if (RT_SUCCESS(rc))
1158 {
1159 rc = VERR_GSTDND_GUEST_ERROR;
1160 rcGuest = pCBData->rc;
1161 }
1162 break;
1163 }
1164 case HOST_DND_HG_SND_DIR:
1165 case HOST_DND_HG_SND_FILE_HDR:
1166 case HOST_DND_HG_SND_FILE_DATA:
1167 {
1168 PVBOXDNDCBHGGETNEXTHOSTMSGDATA pCBData
1169 = reinterpret_cast<PVBOXDNDCBHGGETNEXTHOSTMSGDATA>(pvParms);
1170 AssertPtr(pCBData);
1171 AssertReturn(sizeof(VBOXDNDCBHGGETNEXTHOSTMSGDATA) == cbParms, VERR_INVALID_PARAMETER);
1172
1173 LogFlowFunc(("pCBData->uMsg=%RU32, paParms=%p, cParms=%RU32\n", pCBData->uMsg, pCBData->paParms, pCBData->cParms));
1174
1175 GuestDnDMsg *pMsg = pThis->msgQueueGetNext();
1176 if (pMsg)
1177 {
1178 /*
1179 * Sanity checks.
1180 */
1181 if ( pCBData->uMsg != uMsg
1182 || pCBData->paParms == NULL
1183 || pCBData->cParms != pMsg->getCount())
1184 {
1185 LogFlowFunc(("Current message does not match:\n"));
1186 LogFlowFunc(("\tCallback: uMsg=%RU32, cParms=%RU32, paParms=%p\n",
1187 pCBData->uMsg, pCBData->cParms, pCBData->paParms));
1188 LogFlowFunc(("\t Next: uMsg=%RU32, cParms=%RU32\n", pMsg->getType(), pMsg->getCount()));
1189
1190 /* Start over. */
1191 pThis->msgQueueClear();
1192
1193 rc = VERR_INVALID_PARAMETER;
1194 }
1195
1196 if (RT_SUCCESS(rc))
1197 {
1198 LogFlowFunc(("Returning uMsg=%RU32\n", uMsg));
1199 rc = HGCM::Message::CopyParms(pCBData->paParms, pCBData->cParms, pMsg->getParms(), pMsg->getCount(),
1200 false /* fDeepCopy */);
1201 if (RT_SUCCESS(rc))
1202 {
1203 pCBData->cParms = pMsg->getCount();
1204 pThis->msgQueueRemoveNext();
1205 }
1206 else
1207 LogFlowFunc(("Copying parameters failed with rc=%Rrc\n", rc));
1208 }
1209 }
1210 else
1211 rc = VERR_NO_DATA;
1212
1213 LogFlowFunc(("Processing next message ended with rc=%Rrc\n", rc));
1214 break;
1215 }
1216 default:
1217 rc = VERR_NOT_SUPPORTED;
1218 break;
1219 }
1220
1221 int rcToGuest = VINF_SUCCESS; /* Status which will be sent back to the guest. */
1222
1223 /*
1224 * Resolve errors.
1225 */
1226 switch (rc)
1227 {
1228 case VINF_SUCCESS:
1229 break;
1230
1231 case VINF_EOF:
1232 {
1233 LogRel2(("DnD: Transfer to guest complete\n"));
1234
1235 /* Complete operation on host side. */
1236 fNotify = true;
1237
1238 /* The guest expects VERR_NO_DATA if the transfer is complete. */
1239 rcToGuest = VERR_NO_DATA;
1240 break;
1241 }
1242
1243 case VERR_GSTDND_GUEST_ERROR:
1244 {
1245 LogRel(("DnD: Guest reported error %Rrc, aborting transfer to guest\n", rcGuest));
1246 break;
1247 }
1248
1249 case VERR_CANCELLED:
1250 {
1251 LogRel2(("DnD: Transfer to guest canceled\n"));
1252 rcToGuest = VERR_CANCELLED; /* Also cancel on guest side. */
1253 break;
1254 }
1255
1256 default:
1257 {
1258 LogRel(("DnD: Host error %Rrc occurred, aborting transfer to guest\n", rc));
1259 rcToGuest = VERR_CANCELLED; /* Also cancel on guest side. */
1260 break;
1261 }
1262 }
1263
1264 if (RT_FAILURE(rc))
1265 {
1266 /* Unregister this callback. */
1267 AssertPtr(pCtx->mpResp);
1268 int rc2 = pCtx->mpResp->setCallback(uMsg, NULL /* PFNGUESTDNDCALLBACK */);
1269 AssertRC(rc2);
1270
1271 /* Let the waiter(s) know. */
1272 fNotify = true;
1273 }
1274
1275 LogFlowFunc(("fNotify=%RTbool, rc=%Rrc, rcToGuest=%Rrc\n", fNotify, rc, rcToGuest));
1276
1277 if (fNotify)
1278 {
1279 int rc2 = pCtx->mCBEvent.Notify(rc); /** @todo Also pass guest error back? */
1280 AssertRC(rc2);
1281 }
1282
1283 LogFlowFuncLeaveRC(rc);
1284 return rcToGuest; /* Tell the guest. */
1285}
1286
1287int GuestDnDTarget::i_sendURIData(PSENDDATACTX pCtx, RTMSINTERVAL msTimeout)
1288{
1289 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1290 AssertPtr(pCtx->mpResp);
1291
1292#define REGISTER_CALLBACK(x) \
1293 do { \
1294 rc = pCtx->mpResp->setCallback(x, i_sendURIDataCallback, pCtx); \
1295 if (RT_FAILURE(rc)) \
1296 return rc; \
1297 } while (0)
1298
1299#define UNREGISTER_CALLBACK(x) \
1300 do { \
1301 int rc2 = pCtx->mpResp->setCallback(x, NULL); \
1302 AssertRC(rc2); \
1303 } while (0)
1304
1305 int rc = pCtx->mURI.init(mData.mcbBlockSize);
1306 if (RT_FAILURE(rc))
1307 return rc;
1308
1309 rc = pCtx->mCBEvent.Reset();
1310 if (RT_FAILURE(rc))
1311 return rc;
1312
1313 /*
1314 * Register callbacks.
1315 */
1316 /* Guest callbacks. */
1317 REGISTER_CALLBACK(GUEST_DND_CONNECT);
1318 REGISTER_CALLBACK(GUEST_DND_DISCONNECT);
1319 REGISTER_CALLBACK(GUEST_DND_GET_NEXT_HOST_MSG);
1320 REGISTER_CALLBACK(GUEST_DND_GH_EVT_ERROR);
1321 /* Host callbacks. */
1322 REGISTER_CALLBACK(HOST_DND_HG_SND_DIR);
1323 if (mDataBase.m_uProtocolVersion >= 2)
1324 REGISTER_CALLBACK(HOST_DND_HG_SND_FILE_HDR);
1325 REGISTER_CALLBACK(HOST_DND_HG_SND_FILE_DATA);
1326
1327 do
1328 {
1329 /*
1330 * Extract URI list from current meta data.
1331 */
1332 GuestDnDData *pData = &pCtx->mData;
1333 GuestDnDURIData *pURI = &pCtx->mURI;
1334
1335 rc = pURI->fromLocalMetaData(pData->getMeta());
1336 if (RT_FAILURE(rc))
1337 break;
1338
1339 LogFlowFunc(("URI root objects: %zu, total bytes (raw data to transfer): %zu\n",
1340 pURI->getURIList().RootCount(), pURI->getURIList().TotalBytes()));
1341
1342 /*
1343 * Set the new meta data with the URI list in it.
1344 */
1345 rc = pData->getMeta().fromURIList(pURI->getURIList());
1346 if (RT_FAILURE(rc))
1347 break;
1348
1349 /*
1350 * Set the estimated data sizes we are going to send.
1351 * The total size also contains the meta data size.
1352 */
1353 const uint32_t cbMeta = pData->getMeta().getSize();
1354 pData->setEstimatedSize(pURI->getURIList().TotalBytes() + cbMeta /* cbTotal */,
1355 cbMeta /* cbMeta */);
1356
1357 /*
1358 * Set the meta format.
1359 */
1360 void *pvFmt = (void *)pCtx->mFmtReq.c_str();
1361 uint32_t cbFmt = (uint32_t)pCtx->mFmtReq.length() + 1; /* Include terminating zero. */
1362
1363 pData->setFmt(pvFmt, cbFmt);
1364
1365 /*
1366 * The first message always is the data header. The meta data itself then follows
1367 * and *only* contains the root elements of an URI list.
1368 *
1369 * After the meta data we generate the messages required to send the
1370 * file/directory data itself.
1371 *
1372 * Note: Protocol < v3 use the first data message to tell what's being sent.
1373 */
1374 GuestDnDMsg Msg;
1375
1376 /*
1377 * Send the data header first.
1378 */
1379 if (mDataBase.m_uProtocolVersion >= 3)
1380 rc = i_sendDataHeader(pCtx, pData, &pCtx->mURI);
1381
1382 /*
1383 * Send the (meta) data body.
1384 */
1385 if (RT_SUCCESS(rc))
1386 rc = i_sendDataBody(pCtx, pData);
1387
1388 if (RT_SUCCESS(rc))
1389 {
1390 rc = waitForEvent(&pCtx->mCBEvent, pCtx->mpResp, msTimeout);
1391 if (RT_SUCCESS(rc))
1392 pCtx->mpResp->setProgress(100, DND_PROGRESS_COMPLETE, VINF_SUCCESS);
1393 }
1394
1395 } while (0);
1396
1397 /*
1398 * Unregister callbacks.
1399 */
1400 /* Guest callbacks. */
1401 UNREGISTER_CALLBACK(GUEST_DND_CONNECT);
1402 UNREGISTER_CALLBACK(GUEST_DND_DISCONNECT);
1403 UNREGISTER_CALLBACK(GUEST_DND_GET_NEXT_HOST_MSG);
1404 UNREGISTER_CALLBACK(GUEST_DND_GH_EVT_ERROR);
1405 /* Host callbacks. */
1406 UNREGISTER_CALLBACK(HOST_DND_HG_SND_DIR);
1407 if (mDataBase.m_uProtocolVersion >= 2)
1408 UNREGISTER_CALLBACK(HOST_DND_HG_SND_FILE_HDR);
1409 UNREGISTER_CALLBACK(HOST_DND_HG_SND_FILE_DATA);
1410
1411#undef REGISTER_CALLBACK
1412#undef UNREGISTER_CALLBACK
1413
1414 if (RT_FAILURE(rc))
1415 {
1416 if (rc == VERR_CANCELLED)
1417 pCtx->mpResp->setProgress(100, DND_PROGRESS_CANCELLED, VINF_SUCCESS);
1418 else if (rc != VERR_GSTDND_GUEST_ERROR) /* Guest-side error are already handled in the callback. */
1419 pCtx->mpResp->setProgress(100, DND_PROGRESS_ERROR, rc,
1420 GuestDnDTarget::i_hostErrorToString(rc));
1421 }
1422
1423 /*
1424 * Now that we've cleaned up tell the guest side to cancel.
1425 * This does not imply we're waiting for the guest to react, as the
1426 * host side never must depend on anything from the guest.
1427 */
1428 if (rc == VERR_CANCELLED)
1429 {
1430 int rc2 = sendCancel();
1431 AssertRC(rc2);
1432 }
1433
1434 LogFlowFuncLeaveRC(rc);
1435 return rc;
1436}
1437
1438int GuestDnDTarget::i_sendURIDataLoop(PSENDDATACTX pCtx, GuestDnDMsg *pMsg)
1439{
1440 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1441 AssertPtrReturn(pMsg, VERR_INVALID_POINTER);
1442
1443 int rc = updateProgress(&pCtx->mData, pCtx->mpResp);
1444 AssertRC(rc);
1445
1446 if ( pCtx->mData.isComplete()
1447 && pCtx->mURI.isComplete())
1448 {
1449 return VINF_EOF;
1450 }
1451
1452 GuestDnDURIObjCtx &objCtx = pCtx->mURI.getObjCurrent();
1453 if (!objCtx.isValid())
1454 return VERR_WRONG_ORDER;
1455
1456 DnDURIObject *pCurObj = objCtx.getObj();
1457 AssertPtr(pCurObj);
1458
1459 uint32_t fMode = pCurObj->GetMode();
1460 LogRel3(("DnD: Processing: srcPath=%s, dstPath=%s, fMode=0x%x, cbSize=%RU32, fIsDir=%RTbool, fIsFile=%RTbool\n",
1461 pCurObj->GetSourcePath().c_str(), pCurObj->GetDestPath().c_str(),
1462 fMode, pCurObj->GetSize(),
1463 RTFS_IS_DIRECTORY(fMode), RTFS_IS_FILE(fMode)));
1464
1465 if (RTFS_IS_DIRECTORY(fMode))
1466 {
1467 rc = i_sendDirectory(pCtx, &objCtx, pMsg);
1468 }
1469 else if (RTFS_IS_FILE(fMode))
1470 {
1471 rc = i_sendFile(pCtx, &objCtx, pMsg);
1472 }
1473 else
1474 {
1475 AssertMsgFailed(("fMode=0x%x is not supported for srcPath=%s, dstPath=%s\n",
1476 fMode, pCurObj->GetSourcePath().c_str(), pCurObj->GetDestPath().c_str()));
1477 rc = VERR_NOT_SUPPORTED;
1478 }
1479
1480 bool fRemove = false; /* Remove current entry? */
1481 if ( pCurObj->IsComplete()
1482 || RT_FAILURE(rc))
1483 {
1484 fRemove = true;
1485 }
1486
1487 if (fRemove)
1488 {
1489 LogFlowFunc(("Removing \"%s\" from list, rc=%Rrc\n", pCurObj->GetSourcePath().c_str(), rc));
1490 pCtx->mURI.removeObjCurrent();
1491 }
1492
1493 LogFlowFuncLeaveRC(rc);
1494 return rc;
1495}
1496
1497int GuestDnDTarget::i_sendRawData(PSENDDATACTX pCtx, RTMSINTERVAL msTimeout)
1498{
1499 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1500 NOREF(msTimeout);
1501
1502 GuestDnDData *pData = &pCtx->mData;
1503
1504 /** @todo At the moment we only allow sending up to 64K raw data.
1505 * For protocol v1+v2: Fix this by using HOST_DND_HG_SND_MORE_DATA.
1506 * For protocol v3 : Send another HOST_DND_HG_SND_DATA message. */
1507 if (!pData->getMeta().getSize())
1508 return VINF_SUCCESS;
1509
1510 int rc = VINF_SUCCESS;
1511
1512 /*
1513 * Send the data header first.
1514 */
1515 if (mDataBase.m_uProtocolVersion >= 3)
1516 rc = i_sendDataHeader(pCtx, pData, NULL /* URI list */);
1517
1518 /*
1519 * Send the (meta) data body.
1520 */
1521 if (RT_SUCCESS(rc))
1522 rc = i_sendDataBody(pCtx, pData);
1523
1524 int rc2;
1525 if (RT_FAILURE(rc))
1526 rc2 = pCtx->mpResp->setProgress(100, DND_PROGRESS_ERROR, rc,
1527 GuestDnDTarget::i_hostErrorToString(rc));
1528 else
1529 rc2 = pCtx->mpResp->setProgress(100, DND_PROGRESS_COMPLETE, rc);
1530 AssertRC(rc2);
1531
1532 LogFlowFuncLeaveRC(rc);
1533 return rc;
1534}
1535
1536HRESULT GuestDnDTarget::cancel(BOOL *aVeto)
1537{
1538#if !defined(VBOX_WITH_DRAG_AND_DROP)
1539 ReturnComNotImplemented();
1540#else /* VBOX_WITH_DRAG_AND_DROP */
1541
1542 int rc = i_cancelOperation();
1543
1544 if (aVeto)
1545 *aVeto = FALSE; /** @todo */
1546
1547 HRESULT hr = RT_SUCCESS(rc) ? S_OK : VBOX_E_IPRT_ERROR;
1548
1549 LogFlowFunc(("hr=%Rhrc\n", hr));
1550 return hr;
1551#endif /* VBOX_WITH_DRAG_AND_DROP */
1552}
1553
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