VirtualBox

source: vbox/trunk/src/VBox/Main/src-client/GuestDnDPrivate.cpp@ 57372

Last change on this file since 57372 was 57295, checked in by vboxsync, 9 years ago

Logging instead of assertion.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 29.8 KB
Line 
1/* $Id: GuestDnDPrivate.cpp 57295 2015-08-12 13:44:12Z vboxsync $ */
2/** @file
3 * Private guest drag and drop code, used by GuestDnDTarget +
4 * GuestDnDSource.
5 */
6
7/*
8 * Copyright (C) 2011-2015 Oracle Corporation
9 *
10 * This file is part of VirtualBox Open Source Edition (OSE), as
11 * available from http://www.virtualbox.org. This file is free software;
12 * you can redistribute it and/or modify it under the terms of the GNU
13 * General Public License (GPL) as published by the Free Software
14 * Foundation, in version 2 as it comes in the "COPYING" file of the
15 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
16 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
17 */
18
19#include "GuestImpl.h"
20#include "AutoCaller.h"
21
22#ifdef VBOX_WITH_DRAG_AND_DROP
23# include "ConsoleImpl.h"
24# include "ProgressImpl.h"
25# include "GuestDnDPrivate.h"
26
27# include <algorithm>
28
29# include <iprt/dir.h>
30# include <iprt/path.h>
31# include <iprt/stream.h>
32# include <iprt/semaphore.h>
33# include <iprt/cpp/utils.h>
34
35# include <VMMDev.h>
36
37# include <VBox/GuestHost/DragAndDrop.h>
38# include <VBox/HostServices/DragAndDropSvc.h>
39# include <VBox/version.h>
40
41# ifdef LOG_GROUP
42# undef LOG_GROUP
43# endif
44# define LOG_GROUP LOG_GROUP_GUEST_DND
45# include <VBox/log.h>
46
47/**
48 * Overview:
49 *
50 * Drag and Drop is handled over the internal HGCM service for the host <->
51 * guest communication. Beside that we need to map the Drag and Drop protocols
52 * of the various OS's we support to our internal channels, this is also highly
53 * communicative in both directions. Unfortunately HGCM isn't really designed
54 * for that. Next we have to foul some of the components. This includes to
55 * trick X11 on the guest side, but also Qt needs to be tricked on the host
56 * side a little bit.
57 *
58 * The following components are involved:
59 *
60 * 1. GUI: Uses the Qt classes for Drag and Drop and mainly forward the content
61 * of it to the Main IGuest interface (see UIDnDHandler.cpp).
62 * 2. Main: Public interface for doing Drag and Drop. Also manage the IProgress
63 * interfaces for blocking the caller by showing a progress dialog (see
64 * this file).
65 * 3. HGCM service: Handle all messages from the host to the guest at once and
66 * encapsulate the internal communication details (see dndmanager.cpp and
67 * friends).
68 * 4. Guest additions: Split into the platform neutral part (see
69 * VBoxGuestR3LibDragAndDrop.cpp) and the guest OS specific parts.
70 * Receive/send message from/to the HGCM service and does all guest specific
71 * operations. Currently only X11 is supported (see draganddrop.cpp within
72 * VBoxClient).
73 *
74 * Host -> Guest:
75 * 1. There are DnD Enter, Move, Leave events which are send exactly like this
76 * to the guest. The info includes the pos, mimetypes and allowed actions.
77 * The guest has to respond with an action it would accept, so the GUI could
78 * change the cursor.
79 * 2. On drop, first a drop event is sent. If this is accepted a drop data
80 * event follows. This blocks the GUI and shows some progress indicator.
81 *
82 * Guest -> Host:
83 * 1. The GUI is asking the guest if a DnD event is pending when the user moves
84 * the cursor out of the view window. If so, this returns the mimetypes and
85 * allowed actions.
86 * (2. On every mouse move this is asked again, to make sure the DnD event is
87 * still valid.)
88 * 3. On drop the host request the data from the guest. This blocks the GUI and
89 * shows some progress indicator.
90 *
91 * Some hints:
92 * m_strSupportedFormats here in this file defines the allowed mime-types.
93 * This is necessary because we need special handling for some of the
94 * mime-types. E.g. for URI lists we need to transfer the actual dirs and
95 * files. Text EOL may to be changed. Also unknown mime-types may need special
96 * handling as well, which may lead to undefined behavior in the host/guest, if
97 * not done.
98 *
99 * Dropping of a directory, means recursively transferring _all_ the content.
100 *
101 * Directories and files are placed into the user's temporary directory on the
102 * guest (e.g. /tmp/VirtualBox Dropped Files). We can't delete them after the
103 * DnD operation, because we didn't know what the DnD target does with it. E.g.
104 * it could just be opened in place. This could lead ofc to filling up the disk
105 * within the guest. To inform the user about this, a small app could be
106 * developed which scans this directory regularly and inform the user with a
107 * tray icon hint (and maybe the possibility to clean this up instantly). The
108 * same has to be done in the G->H direction when it is implemented.
109 *
110 * Of course only regularly files are supported. Symlinks are resolved and
111 * transfered as regularly files. First we don't know if the other side support
112 * symlinks at all and second they could point to somewhere in a directory tree
113 * which not exists on the other side.
114 *
115 * The code tries to preserve the file modes of the transfered dirs/files. This
116 * is useful (and maybe necessary) for two things:
117 * 1. If a file is executable, it should be also after the transfer, so the
118 * user can just execute it, without manually tweaking the modes first.
119 * 2. If a dir/file is not accessible by group/others in the host, it shouldn't
120 * be in the guest.
121 * In any case, the user mode is always set to rwx (so that we can access it
122 * ourself, in e.g. for a cleanup case after cancel).
123 *
124 * Cancel is supported in both directions and cleans up all previous steps
125 * (thats is: deleting already transfered dirs/files).
126 *
127 * In general I propose the following changes in the VBox HGCM infrastructure
128 * for the future:
129 * - Currently it isn't really possible to send messages to the guest from the
130 * host. The host informs the guest just that there is something, the guest
131 * than has to ask which message and depending on that send the appropriate
132 * message to the host, which is filled with the right data.
133 * - There is no generic interface for sending bigger memory blocks to/from the
134 * guest. This is now done here, but I guess was also necessary for e.g.
135 * guest execution. So something generic which brake this up into smaller
136 * blocks and send it would be nice (with all the error handling and such
137 * ofc).
138 * - I developed a "protocol" for the DnD communication here. So the host and
139 * the guest have always to match in the revision. This is ofc bad, because
140 * the additions could be outdated easily. So some generic protocol number
141 * support in HGCM for asking the host and the guest of the support version,
142 * would be nice. Ofc at least the host should be able to talk to the guest,
143 * even when the version is below the host one.
144 * All this stuff would be useful for the current services, but also for future
145 * onces.
146 *
147 ** @todo
148 * - ESC doesn't really work (on Windows guests it's already implemented)
149 * ... in any case it seems a little bit difficult to handle from the Qt
150 * side. Maybe also a host specific implementation becomes necessary ...
151 * this would be really worst ofc.
152 * - Add support for more mime-types (especially images, csv)
153 * - Test unusual behavior:
154 * - DnD service crash in the guest during a DnD op (e.g. crash of VBoxClient or X11)
155 * - Not expected order of the events between HGCM and the guest
156 * - Security considerations: We transfer a lot of memory between the guest and
157 * the host and even allow the creation of dirs/files. Maybe there should be
158 * limits introduced to preventing DOS attacks or filling up all the memory
159 * (both in the host and the guest).
160 */
161
162GuestDnDCallbackEvent::~GuestDnDCallbackEvent(void)
163{
164 if (NIL_RTSEMEVENT != mSemEvent)
165 RTSemEventDestroy(mSemEvent);
166}
167
168int GuestDnDCallbackEvent::Reset(void)
169{
170 int rc = VINF_SUCCESS;
171
172 if (NIL_RTSEMEVENT == mSemEvent)
173 rc = RTSemEventCreate(&mSemEvent);
174
175 mRc = VINF_SUCCESS;
176 return rc;
177}
178
179int GuestDnDCallbackEvent::Notify(int rc /* = VINF_SUCCESS */)
180{
181 mRc = rc;
182 return RTSemEventSignal(mSemEvent);
183}
184
185int GuestDnDCallbackEvent::Wait(RTMSINTERVAL msTimeout)
186{
187 return RTSemEventWait(mSemEvent, msTimeout);
188}
189
190///////////////////////////////////////////////////////////////////////////////
191
192GuestDnDResponse::GuestDnDResponse(const ComObjPtr<Guest>& pGuest)
193 : m_EventSem(NIL_RTSEMEVENT)
194 , m_defAction(0)
195 , m_allActions(0)
196 , m_parent(pGuest)
197{
198 int rc = RTSemEventCreate(&m_EventSem);
199 if (RT_FAILURE(rc))
200 throw rc;
201}
202
203GuestDnDResponse::~GuestDnDResponse(void)
204{
205 reset();
206
207 int rc = RTSemEventDestroy(m_EventSem);
208 AssertRC(rc);
209}
210
211int GuestDnDResponse::notifyAboutGuestResponse(void) const
212{
213 return RTSemEventSignal(m_EventSem);
214}
215
216void GuestDnDResponse::reset(void)
217{
218 LogFlowThisFuncEnter();
219
220 m_defAction = 0;
221 m_allActions = 0;
222
223 m_lstFormats.clear();
224}
225
226HRESULT GuestDnDResponse::resetProgress(const ComObjPtr<Guest>& pParent)
227{
228 m_progress.setNull();
229
230 HRESULT hr = m_progress.createObject();
231 if (SUCCEEDED(hr))
232 {
233 hr = m_progress->init(static_cast<IGuest *>(pParent),
234 Bstr(pParent->tr("Dropping data")).raw(),
235 TRUE /* aCancelable */);
236 }
237
238 return hr;
239}
240
241bool GuestDnDResponse::isProgressCanceled(void) const
242{
243 BOOL fCanceled;
244 if (!m_progress.isNull())
245 {
246 HRESULT hr = m_progress->COMGETTER(Canceled)(&fCanceled);
247 AssertComRC(hr);
248 }
249 else
250 fCanceled = TRUE;
251
252 return RT_BOOL(fCanceled);
253}
254
255int GuestDnDResponse::setCallback(uint32_t uMsg, PFNGUESTDNDCALLBACK pfnCallback, void *pvUser /* = NULL */)
256{
257 GuestDnDCallbackMap::iterator it = m_mapCallbacks.find(uMsg);
258
259 /* Add. */
260 if (pfnCallback)
261 {
262 if (it == m_mapCallbacks.end())
263 {
264 m_mapCallbacks[uMsg] = GuestDnDCallback(pfnCallback, uMsg, pvUser);
265 return VINF_SUCCESS;
266 }
267
268 AssertMsgFailed(("Callback for message %RU32 already registered\n", uMsg));
269 return VERR_ALREADY_EXISTS;
270 }
271
272 /* Remove. */
273 if (it != m_mapCallbacks.end())
274 m_mapCallbacks.erase(it);
275
276 return VINF_SUCCESS;
277}
278
279int GuestDnDResponse::setProgress(unsigned uPercentage,
280 uint32_t uStatus,
281 int rcOp /* = VINF_SUCCESS */, const Utf8Str &strMsg /* = "" */)
282{
283 LogFlowFunc(("uStatus=%RU32, uPercentage=%RU32, rcOp=%Rrc, strMsg=%s\n",
284 uStatus, uPercentage, rcOp, strMsg.c_str()));
285
286 int rc = VINF_SUCCESS;
287 if (!m_progress.isNull())
288 {
289 BOOL fCompleted;
290 HRESULT hr = m_progress->COMGETTER(Completed)(&fCompleted);
291 AssertComRC(hr);
292
293 BOOL fCanceled;
294 hr = m_progress->COMGETTER(Canceled)(&fCanceled);
295 AssertComRC(hr);
296
297 LogFlowFunc(("Current: fCompleted=%RTbool, fCanceled=%RTbool\n", fCompleted, fCanceled));
298
299 if (!fCompleted)
300 {
301 switch (uStatus)
302 {
303 case DragAndDropSvc::DND_PROGRESS_ERROR:
304 {
305 hr = m_progress->i_notifyComplete(VBOX_E_IPRT_ERROR,
306 COM_IIDOF(IGuest),
307 m_parent->getComponentName(), strMsg.c_str());
308 reset();
309 break;
310 }
311
312 case DragAndDropSvc::DND_PROGRESS_CANCELLED:
313 {
314 hr = m_progress->i_notifyComplete(S_OK);
315 AssertComRC(hr);
316
317 reset();
318 break;
319 }
320
321 case DragAndDropSvc::DND_PROGRESS_RUNNING:
322 case DragAndDropSvc::DND_PROGRESS_COMPLETE:
323 {
324 if (!fCanceled)
325 {
326 hr = m_progress->SetCurrentOperationProgress(uPercentage);
327 AssertComRC(hr);
328 if ( uStatus == DragAndDropSvc::DND_PROGRESS_COMPLETE
329 || uPercentage >= 100)
330 {
331 hr = m_progress->i_notifyComplete(S_OK);
332 AssertComRC(hr);
333 }
334 }
335 break;
336 }
337
338 default:
339 break;
340 }
341 }
342
343 hr = m_progress->COMGETTER(Completed)(&fCompleted);
344 AssertComRC(hr);
345 hr = m_progress->COMGETTER(Canceled)(&fCanceled);
346 AssertComRC(hr);
347
348 LogFlowFunc(("New: fCompleted=%RTbool, fCanceled=%RTbool\n", fCompleted, fCanceled));
349 }
350
351 LogFlowFuncLeaveRC(rc);
352 return rc;
353}
354
355int GuestDnDResponse::onDispatch(uint32_t u32Function, void *pvParms, uint32_t cbParms)
356{
357 LogFlowFunc(("u32Function=%RU32, pvParms=%p, cbParms=%RU32\n", u32Function, pvParms, cbParms));
358
359 int rc = VERR_WRONG_ORDER; /* Play safe. */
360 bool fTryCallbacks = false;
361
362 switch (u32Function)
363 {
364 case DragAndDropSvc::GUEST_DND_HG_ACK_OP:
365 {
366 DragAndDropSvc::PVBOXDNDCBHGACKOPDATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBHGACKOPDATA>(pvParms);
367 AssertPtr(pCBData);
368 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBHGACKOPDATA) == cbParms, VERR_INVALID_PARAMETER);
369 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_HG_ACK_OP == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
370
371 setDefAction(pCBData->uAction);
372 rc = notifyAboutGuestResponse();
373 break;
374 }
375
376 case DragAndDropSvc::GUEST_DND_HG_REQ_DATA:
377 {
378 DragAndDropSvc::PVBOXDNDCBHGREQDATADATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBHGREQDATADATA>(pvParms);
379 AssertPtr(pCBData);
380 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBHGREQDATADATA) == cbParms, VERR_INVALID_PARAMETER);
381 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_HG_REQ_DATA == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
382
383 if ( pCBData->cbFormat == 0
384 || pCBData->cbFormat > _64K)
385 {
386 rc = VERR_INVALID_PARAMETER;
387 }
388 else
389 {
390 setFormats(GuestDnD::toFormatList(pCBData->pszFormat));
391 rc = VINF_SUCCESS;
392 }
393
394 int rc2 = notifyAboutGuestResponse();
395 if (RT_SUCCESS(rc))
396 rc = rc2;
397 break;
398 }
399
400 case DragAndDropSvc::GUEST_DND_HG_EVT_PROGRESS:
401 {
402 DragAndDropSvc::PVBOXDNDCBHGEVTPROGRESSDATA pCBData =
403 reinterpret_cast<DragAndDropSvc::PVBOXDNDCBHGEVTPROGRESSDATA>(pvParms);
404 AssertPtr(pCBData);
405 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBHGEVTPROGRESSDATA) == cbParms, VERR_INVALID_PARAMETER);
406 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_HG_EVT_PROGRESS == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
407
408 rc = setProgress(pCBData->uPercentage, pCBData->uStatus, pCBData->rc);
409 if (RT_SUCCESS(rc))
410 rc = notifyAboutGuestResponse();
411 break;
412 }
413#ifdef VBOX_WITH_DRAG_AND_DROP_GH
414 case DragAndDropSvc::GUEST_DND_GH_ACK_PENDING:
415 {
416 DragAndDropSvc::PVBOXDNDCBGHACKPENDINGDATA pCBData =
417 reinterpret_cast<DragAndDropSvc::PVBOXDNDCBGHACKPENDINGDATA>(pvParms);
418 AssertPtr(pCBData);
419 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBGHACKPENDINGDATA) == cbParms, VERR_INVALID_PARAMETER);
420 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_GH_ACK_PENDING == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
421
422 if ( pCBData->cbFormat == 0
423 || pCBData->cbFormat > _64K)
424 {
425 rc = VERR_INVALID_PARAMETER;
426 }
427 else
428 {
429 setFormats (GuestDnD::toFormatList(pCBData->pszFormat));
430 setDefAction (pCBData->uDefAction);
431 setAllActions(pCBData->uAllActions);
432
433 rc = VINF_SUCCESS;
434 }
435
436 int rc2 = notifyAboutGuestResponse();
437 if (RT_SUCCESS(rc))
438 rc = rc2;
439 break;
440 }
441#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
442 default:
443 /* * Try if the event is covered by a registered callback. */
444 fTryCallbacks = true;
445 break;
446 }
447
448 /*
449 * Try the host's installed callbacks (if any).
450 */
451 if (fTryCallbacks)
452 {
453 GuestDnDCallbackMap::const_iterator it = m_mapCallbacks.find(u32Function);
454 if (it != m_mapCallbacks.end())
455 {
456 AssertPtr(it->second.pfnCallback);
457 rc = it->second.pfnCallback(u32Function, pvParms, cbParms, it->second.pvUser);
458 }
459 else
460 {
461 LogFlowFunc(("No callback for function %RU32 defined (%zu callbacks total)\n", u32Function, m_mapCallbacks.size()));
462 rc = VERR_NOT_SUPPORTED; /* Tell the guest. */
463 }
464 }
465
466 LogFlowFunc(("Returning rc=%Rrc\n", rc));
467 return rc;
468}
469
470HRESULT GuestDnDResponse::queryProgressTo(IProgress **ppProgress)
471{
472 return m_progress.queryInterfaceTo(ppProgress);
473}
474
475int GuestDnDResponse::waitForGuestResponse(RTMSINTERVAL msTimeout /*= 500 */) const
476{
477 int rc = RTSemEventWait(m_EventSem, msTimeout);
478#ifdef DEBUG_andy
479 LogFlowFunc(("msTimeout=%RU32, rc=%Rrc\n", msTimeout, rc));
480#endif
481 return rc;
482}
483
484///////////////////////////////////////////////////////////////////////////////
485
486GuestDnD* GuestDnD::s_pInstance = NULL;
487
488GuestDnD::GuestDnD(const ComObjPtr<Guest> &pGuest)
489 : m_pGuest(pGuest)
490{
491 LogFlowFuncEnter();
492
493 m_pResponse = new GuestDnDResponse(pGuest);
494
495 /* List of supported default MIME types. */
496 LogRel2(("DnD: Supported default host formats:\n"));
497 const com::Utf8Str arrEntries[] = { VBOX_DND_FORMATS_DEFAULT };
498 for (size_t i = 0; i < RT_ELEMENTS(arrEntries); i++)
499 {
500 m_strDefaultFormats.push_back(arrEntries[i]);
501 LogRel2(("DnD: \t%s\n", arrEntries[i].c_str()));
502 }
503}
504
505GuestDnD::~GuestDnD(void)
506{
507 LogFlowFuncEnter();
508
509 if (m_pResponse)
510 delete m_pResponse;
511}
512
513HRESULT GuestDnD::adjustScreenCoordinates(ULONG uScreenId, ULONG *puX, ULONG *puY) const
514{
515 /** @todo r=andy Save the current screen's shifting coordinates to speed things up.
516 * Only query for new offsets when the screen ID has changed. */
517
518 /* For multi-monitor support we need to add shift values to the coordinates
519 * (depending on the screen number). */
520 ComObjPtr<Console> pConsole = m_pGuest->i_getConsole();
521 ComPtr<IDisplay> pDisplay;
522 HRESULT hr = pConsole->COMGETTER(Display)(pDisplay.asOutParam());
523 if (FAILED(hr))
524 return hr;
525
526 ULONG dummy;
527 LONG xShift, yShift;
528 GuestMonitorStatus_T monitorStatus;
529 hr = pDisplay->GetScreenResolution(uScreenId, &dummy, &dummy, &dummy,
530 &xShift, &yShift, &monitorStatus);
531 if (FAILED(hr))
532 return hr;
533
534 if (puX)
535 *puX += xShift;
536 if (puY)
537 *puY += yShift;
538
539 LogFlowFunc(("uScreenId=%RU32, x=%RU32, y=%RU32\n", uScreenId, puX ? *puX : 0, puY ? *puY : 0));
540 return S_OK;
541}
542
543int GuestDnD::hostCall(uint32_t u32Function, uint32_t cParms, PVBOXHGCMSVCPARM paParms) const
544{
545 Assert(!m_pGuest.isNull());
546 ComObjPtr<Console> pConsole = m_pGuest->i_getConsole();
547
548 /* Forward the information to the VMM device. */
549 Assert(!pConsole.isNull());
550 VMMDev *pVMMDev = pConsole->i_getVMMDev();
551 if (!pVMMDev)
552 return VERR_COM_OBJECT_NOT_FOUND;
553
554 return pVMMDev->hgcmHostCall("VBoxDragAndDropSvc", u32Function, cParms, paParms);
555}
556
557/* static */
558DECLCALLBACK(int) GuestDnD::notifyDnDDispatcher(void *pvExtension, uint32_t u32Function,
559 void *pvParms, uint32_t cbParms)
560{
561 LogFlowFunc(("pvExtension=%p, u32Function=%RU32, pvParms=%p, cbParms=%RU32\n",
562 pvExtension, u32Function, pvParms, cbParms));
563
564 GuestDnD *pGuestDnD = reinterpret_cast<GuestDnD*>(pvExtension);
565 AssertPtrReturn(pGuestDnD, VERR_INVALID_POINTER);
566
567 /** @todo In case we need to handle multiple guest DnD responses at a time this
568 * would be the place to lookup and dispatch to those. For the moment we
569 * only have one response -- simple. */
570 GuestDnDResponse *pResp = pGuestDnD->m_pResponse;
571 if (pResp)
572 return pResp->onDispatch(u32Function, pvParms, cbParms);
573
574 return VERR_NOT_SUPPORTED;
575}
576
577/* static */
578bool GuestDnD::isFormatInFormatList(const com::Utf8Str &strFormat, const GuestDnDMIMEList &lstFormats)
579{
580 return std::find(lstFormats.begin(), lstFormats.end(), strFormat) != lstFormats.end();
581}
582
583/* static */
584GuestDnDMIMEList GuestDnD::toFormatList(const com::Utf8Str &strFormats)
585{
586 GuestDnDMIMEList lstFormats;
587 RTCList<RTCString> lstFormatsTmp = strFormats.split("\r\n");
588
589 for (size_t i = 0; i < lstFormatsTmp.size(); i++)
590 lstFormats.push_back(com::Utf8Str(lstFormatsTmp.at(i)));
591
592 return lstFormats;
593}
594
595/* static */
596com::Utf8Str GuestDnD::toFormatString(const GuestDnDMIMEList &lstFormats)
597{
598 com::Utf8Str strFormat;
599 for (size_t i = 0; i < lstFormats.size(); i++)
600 {
601 const com::Utf8Str &f = lstFormats.at(i);
602 strFormat += f + "\r\n";
603 }
604
605 return strFormat;
606}
607
608/* static */
609GuestDnDMIMEList GuestDnD::toFilteredFormatList(const GuestDnDMIMEList &lstFormatsSupported, const GuestDnDMIMEList &lstFormatsWanted)
610{
611 GuestDnDMIMEList lstFormats;
612
613 for (size_t i = 0; i < lstFormatsWanted.size(); i++)
614 {
615 /* Only keep allowed format types. */
616 if (std::find(lstFormatsSupported.begin(),
617 lstFormatsSupported.end(), lstFormatsWanted.at(i)) != lstFormatsSupported.end())
618 {
619 lstFormats.push_back(lstFormatsWanted[i]);
620 }
621 }
622
623 return lstFormats;
624}
625
626/* static */
627GuestDnDMIMEList GuestDnD::toFilteredFormatList(const GuestDnDMIMEList &lstFormatsSupported, const com::Utf8Str &strFormatsWanted)
628{
629 GuestDnDMIMEList lstFmt;
630
631 RTCList<RTCString> lstFormats = strFormatsWanted.split("\r\n");
632 size_t i = 0;
633 while (i < lstFormats.size())
634 {
635 /* Only keep allowed format types. */
636 if (std::find(lstFormatsSupported.begin(),
637 lstFormatsSupported.end(), lstFormats.at(i)) != lstFormatsSupported.end())
638 {
639 lstFmt.push_back(lstFormats[i]);
640 }
641 i++;
642 }
643
644 return lstFmt;
645}
646
647/* static */
648uint32_t GuestDnD::toHGCMAction(DnDAction_T enmAction)
649{
650 uint32_t uAction = DND_IGNORE_ACTION;
651 switch (enmAction)
652 {
653 case DnDAction_Copy:
654 uAction = DND_COPY_ACTION;
655 break;
656 case DnDAction_Move:
657 uAction = DND_MOVE_ACTION;
658 break;
659 case DnDAction_Link:
660 /* For now it doesn't seems useful to allow a link
661 action between host & guest. Later? */
662 case DnDAction_Ignore:
663 /* Ignored. */
664 break;
665 default:
666 AssertMsgFailed(("Action %RU32 not recognized!\n", enmAction));
667 break;
668 }
669
670 return uAction;
671}
672
673/* static */
674void GuestDnD::toHGCMActions(DnDAction_T enmDefAction,
675 uint32_t *puDefAction,
676 const std::vector<DnDAction_T> vecAllowedActions,
677 uint32_t *puAllowedActions)
678{
679 uint32_t uAllowedActions = DND_IGNORE_ACTION;
680 uint32_t uDefAction = toHGCMAction(enmDefAction);
681
682 if (!vecAllowedActions.empty())
683 {
684 /* First convert the allowed actions to a bit array. */
685 for (size_t i = 0; i < vecAllowedActions.size(); i++)
686 uAllowedActions |= toHGCMAction(vecAllowedActions[i]);
687
688 /*
689 * If no default action is set (ignoring), try one of the
690 * set allowed actions, preferring copy, move (in that order).
691 */
692 if (isDnDIgnoreAction(uDefAction))
693 {
694 if (hasDnDCopyAction(uAllowedActions))
695 uDefAction = DND_COPY_ACTION;
696 else if (hasDnDMoveAction(uAllowedActions))
697 uDefAction = DND_MOVE_ACTION;
698 }
699 }
700
701 if (puDefAction)
702 *puDefAction = uDefAction;
703 if (puAllowedActions)
704 *puAllowedActions = uAllowedActions;
705}
706
707/* static */
708DnDAction_T GuestDnD::toMainAction(uint32_t uAction)
709{
710 /* For now it doesn't seems useful to allow a
711 * link action between host & guest. Maybe later! */
712 return (isDnDCopyAction(uAction) ? (DnDAction_T)DnDAction_Copy :
713 isDnDMoveAction(uAction) ? (DnDAction_T)DnDAction_Move :
714 (DnDAction_T)DnDAction_Ignore);
715}
716
717/* static */
718std::vector<DnDAction_T> GuestDnD::toMainActions(uint32_t uActions)
719{
720 std::vector<DnDAction_T> vecActions;
721
722 /* For now it doesn't seems useful to allow a
723 * link action between host & guest. Maybe later! */
724 RTCList<DnDAction_T> lstActions;
725 if (hasDnDCopyAction(uActions))
726 lstActions.append(DnDAction_Copy);
727 if (hasDnDMoveAction(uActions))
728 lstActions.append(DnDAction_Move);
729
730 for (size_t i = 0; i < lstActions.size(); ++i)
731 vecActions.push_back(lstActions.at(i));
732
733 return vecActions;
734}
735
736///////////////////////////////////////////////////////////////////////////////
737
738GuestDnDBase::GuestDnDBase(void)
739{
740 mDataBase.mfTransferIsPending = false;
741
742 /*
743 * Initialize public attributes.
744 */
745 m_lstFmtSupported = GuestDnDInst()->defaultFormats();
746}
747
748HRESULT GuestDnDBase::i_isFormatSupported(const com::Utf8Str &aFormat, BOOL *aSupported)
749{
750 *aSupported = std::find(m_lstFmtSupported.begin(),
751 m_lstFmtSupported.end(), aFormat) != m_lstFmtSupported.end()
752 ? TRUE : FALSE;
753 return S_OK;
754}
755
756HRESULT GuestDnDBase::i_getFormats(GuestDnDMIMEList &aFormats)
757{
758 aFormats = m_lstFmtSupported;
759
760 return S_OK;
761}
762
763HRESULT GuestDnDBase::i_addFormats(const GuestDnDMIMEList &aFormats)
764{
765 for (size_t i = 0; i < aFormats.size(); ++i)
766 {
767 Utf8Str strFormat = aFormats.at(i);
768 if (std::find(m_lstFmtSupported.begin(),
769 m_lstFmtSupported.end(), strFormat) == m_lstFmtSupported.end())
770 {
771 m_lstFmtSupported.push_back(strFormat);
772 }
773 }
774
775 return S_OK;
776}
777
778HRESULT GuestDnDBase::i_removeFormats(const GuestDnDMIMEList &aFormats)
779{
780 for (size_t i = 0; i < aFormats.size(); ++i)
781 {
782 Utf8Str strFormat = aFormats.at(i);
783 GuestDnDMIMEList::iterator itFormat = std::find(m_lstFmtSupported.begin(),
784 m_lstFmtSupported.end(), strFormat);
785 if (itFormat != m_lstFmtSupported.end())
786 m_lstFmtSupported.erase(itFormat);
787 }
788
789 return S_OK;
790}
791
792HRESULT GuestDnDBase::i_getProtocolVersion(ULONG *puVersion)
793{
794 int rc = getProtocolVersion((uint32_t *)puVersion);
795 return RT_SUCCESS(rc) ? S_OK : E_FAIL;
796}
797
798int GuestDnDBase::getProtocolVersion(uint32_t *puVersion)
799{
800 AssertPtrReturn(puVersion, VERR_INVALID_POINTER);
801
802 int rc;
803
804 uint32_t uVer, uVerAdditions = 0;
805 if ( m_pGuest
806 && (uVerAdditions = m_pGuest->i_getAdditionsVersion()) > 0)
807 {
808 uint32_t uVBoxMajor = VBOX_FULL_VERSION_GET_MAJOR(uVerAdditions);
809 uint32_t uVBoxMinor = VBOX_FULL_VERSION_GET_MINOR(uVerAdditions);
810
811#if 0 /*def DEBUG_andy*/
812 /* Hardcode the to-used protocol version; nice for testing side effects. */
813 uVer = 2;
814#else
815 uVer = ( uVBoxMajor >= 5)
816 ? 2 /* VBox 5.0 and up: Protocol version 2. */
817 : 1; /* VBox <= 4.3: Protocol version 1. */
818 /* Build revision is ignored. */
819#endif
820
821 LogFlowThisFunc(("uVerAdditions=%RU32 (%RU32.%RU32)\n", uVerAdditions, uVBoxMajor, uVBoxMinor));
822 rc = VINF_SUCCESS;
823 }
824 else
825 {
826 uVer = 1; /* Fallback. */
827 rc = VERR_NOT_FOUND;
828 }
829
830 LogFlowThisFunc(("uVer=%RU32, uVerAdditions=%RU32, rc=%Rrc\n", uVer, uVerAdditions, rc));
831
832 *puVersion = uVer;
833 return rc;
834}
835
836int GuestDnDBase::msgQueueAdd(GuestDnDMsg *pMsg)
837{
838 mDataBase.mListOutgoing.push_back(pMsg);
839 return VINF_SUCCESS;
840}
841
842GuestDnDMsg *GuestDnDBase::msgQueueGetNext(void)
843{
844 if (mDataBase.mListOutgoing.empty())
845 return NULL;
846 return mDataBase.mListOutgoing.front();
847}
848
849void GuestDnDBase::msgQueueRemoveNext(void)
850{
851 if (!mDataBase.mListOutgoing.empty())
852 {
853 GuestDnDMsg *pMsg = mDataBase.mListOutgoing.front();
854 if (pMsg)
855 delete pMsg;
856 mDataBase.mListOutgoing.pop_front();
857 }
858}
859
860void GuestDnDBase::msgQueueClear(void)
861{
862 GuestDnDMsgList::iterator itMsg = mDataBase.mListOutgoing.begin();
863 while (itMsg != mDataBase.mListOutgoing.end())
864 {
865 delete *itMsg;
866 }
867
868 mDataBase.mListOutgoing.clear();
869}
870
871int GuestDnDBase::sendCancel(void)
872{
873 int rc;
874 try
875 {
876 GuestDnDMsg *pMsgCancel = new GuestDnDMsg();
877 pMsgCancel->setType(DragAndDropSvc::HOST_DND_HG_EVT_CANCEL);
878
879 rc = msgQueueAdd(pMsgCancel);
880 }
881 catch(std::bad_alloc & /*e*/)
882 {
883 rc = VERR_NO_MEMORY;
884 }
885
886 LogFlowFunc(("Generated cancelling request, rc=%Rrc\n", rc));
887 return rc;
888}
889
890/** @todo GuestDnDResponse *pResp needs to go. */
891int GuestDnDBase::waitForEvent(RTMSINTERVAL msTimeout, GuestDnDCallbackEvent &Event, GuestDnDResponse *pResp)
892{
893 int rc;
894
895 uint64_t tsStart = RTTimeMilliTS();
896 do
897 {
898 /*
899 * Wait until our desired callback triggered the
900 * wait event. As we don't want to block if the guest does not
901 * respond, do busy waiting here.
902 */
903 rc = Event.Wait(500 /* ms */);
904 if (RT_SUCCESS(rc))
905 {
906 rc = Event.Result();
907 LogFlowFunc(("Callback done, result is %Rrc\n", rc));
908 break;
909 }
910 else if (rc == VERR_TIMEOUT) /* Continue waiting. */
911 rc = VINF_SUCCESS;
912
913 if ( msTimeout != RT_INDEFINITE_WAIT
914 && RTTimeMilliTS() - tsStart > msTimeout)
915 {
916 rc = VERR_TIMEOUT;
917 LogRel2(("DnD: Error: Guest did not respond within time\n"));
918 }
919 else if (pResp->isProgressCanceled()) /** @todo GuestDnDResponse *pResp needs to go. */
920 {
921 LogRel2(("DnD: Operation was canceled by user\n"));
922 rc = VERR_CANCELLED;
923 }
924
925 } while (RT_SUCCESS(rc));
926
927 LogFlowFuncLeaveRC(rc);
928 return rc;
929}
930#endif /* VBOX_WITH_DRAG_AND_DROP */
931
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