VirtualBox

source: vbox/trunk/src/VBox/Main/src-client/GuestDnDImpl.cpp@ 50265

Last change on this file since 50265 was 50265, checked in by vboxsync, 11 years ago

DnD: First working implementation for Windows guest->host support; still work in progress. As for now only pure text data can be dragged over.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 37.3 KB
Line 
1/* $Id: GuestDnDImpl.cpp 50265 2014-01-29 11:12:44Z vboxsync $ */
2/** @file
3 * VirtualBox COM class implementation: Guest Drag and Drop parts
4 */
5
6/*
7 * Copyright (C) 2011-2014 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#include "GuestImpl.h"
19#include "AutoCaller.h"
20
21#ifdef VBOX_WITH_DRAG_AND_DROP
22# include "ConsoleImpl.h"
23# include "ProgressImpl.h"
24# include "GuestDnDImpl.h"
25
26# include <VMMDev.h>
27
28# include <VBox/com/list.h>
29# include <VBox/HostServices/DragAndDropSvc.h>
30
31# ifdef LOG_GROUP
32 # undef LOG_GROUP
33# endif
34# define LOG_GROUP LOG_GROUP_GUEST_DND
35# include <VBox/log.h>
36
37# include <iprt/stream.h>
38# include <iprt/semaphore.h>
39# include <iprt/cpp/utils.h>
40
41/* How does this work:
42 *
43 * Drag and Drop is handled over the internal HGCM service for the host <->
44 * guest communication. Beside that we need to map the Drag and Drop protocols
45 * of the various OS's we support to our internal channels, this is also highly
46 * communicative in both directions. Unfortunately HGCM isn't really designed
47 * for that. Next we have to foul some of the components. This includes to
48 * trick X11 on the guest side, but also Qt needs to be tricked on the host
49 * side a little bit.
50 *
51 * The following components are involved:
52 *
53 * 1. GUI: Uses the Qt classes for Drag and Drop and mainly forward the content
54 * of it to the Main IGuest interface (see UIDnDHandler.cpp).
55 * 2. Main: Public interface for doing Drag and Drop. Also manage the IProgress
56 * interfaces for blocking the caller by showing a progress dialog (see
57 * this file).
58 * 3. HGCM service: Handle all messages from the host to the guest at once and
59 * encapsulate the internal communication details (see dndmanager.cpp and
60 * friends).
61 * 4. Guest additions: Split into the platform neutral part (see
62 * VBoxGuestR3LibDragAndDrop.cpp) and the guest OS specific parts.
63 * Receive/send message from/to the HGCM service and does all guest specific
64 * operations. Currently only X11 is supported (see draganddrop.cpp within
65 * VBoxClient).
66 *
67 * Host -> Guest:
68 * 1. There are DnD Enter, Move, Leave events which are send exactly like this
69 * to the guest. The info includes the pos, mimetypes and allowed actions.
70 * The guest has to respond with an action it would accept, so the GUI could
71 * change the cursor.
72 * 2. On drop, first a drop event is send. If this is accepted a drop data
73 * event follows. This blocks the GUI and shows some progress indicator.
74 *
75 * Guest -> Host:
76 * 1. The GUI is asking the guest if a DnD event is pending when the user moves
77 * the cursor out of the view window. If so, this returns the mimetypes and
78 * allowed actions.
79 * (2. On every mouse move this is asked again, to make sure the DnD event is
80 * still valid.)
81 * 3. On drop the host request the data from the guest. This blocks the GUI and
82 * shows some progress indicator.
83 *
84 * Some hints:
85 * m_sstrAllowedMimeTypes here in this file defines the allowed mime-types.
86 * This is necessary because we need special handling for some of the
87 * mime-types. E.g. for URI lists we need to transfer the actual dirs and
88 * files. Text EOL may to be changed. Also unknown mime-types may need special
89 * handling as well, which may lead to undefined behavior in the host/guest, if
90 * not done.
91 *
92 * Dropping of a directory, means recursively transferring _all_ the content.
93 *
94 * Directories and files are placed into the user's temporary directory on the
95 * guest (e.g. /tmp/VirtualBox Dropped Files). We can't delete them after the
96 * DnD operation, because we didn't know what the DnD target does with it. E.g.
97 * it could just be opened in place. This could lead ofc to filling up the disk
98 * within the guest. To inform the user about this, a small app could be
99 * developed which scans this directory regularly and inform the user with a
100 * tray icon hint (and maybe the possibility to clean this up instantly). The
101 * same has to be done in the G->H direction when it is implemented.
102 *
103 * Of course only regularly files are supported. Symlinks are resolved and
104 * transfered as regularly files. First we don't know if the other side support
105 * symlinks at all and second they could point to somewhere in a directory tree
106 * which not exists on the other side.
107 *
108 * The code tries to preserve the file modes of the transfered dirs/files. This
109 * is useful (and maybe necessary) for two things:
110 * 1. If a file is executable, it should be also after the transfer, so the
111 * user can just execute it, without manually tweaking the modes first.
112 * 2. If a dir/file is not accessible by group/others in the host, it shouldn't
113 * be in the guest.
114 * In any case, the user mode is always set to rwx (so that we can access it
115 * ourself, in e.g. for a cleanup case after cancel).
116 *
117 * Cancel is supported in both directions and cleans up all previous steps
118 * (thats is: deleting already transfered dirs/files).
119 *
120 * In general I propose the following changes in the VBox HGCM infrastructure
121 * for the future:
122 * - Currently it isn't really possible to send messages to the guest from the
123 * host. The host informs the guest just that there is something, the guest
124 * than has to ask which message and depending on that send the appropriate
125 * message to the host, which is filled with the right data.
126 * - There is no generic interface for sending bigger memory blocks to/from the
127 * guest. This is now done here, but I guess was also necessary for e.g.
128 * guest execution. So something generic which brake this up into smaller
129 * blocks and send it would be nice (with all the error handling and such
130 * ofc).
131 * - I developed a "protocol" for the DnD communication here. So the host and
132 * the guest have always to match in the revision. This is ofc bad, because
133 * the additions could be outdated easily. So some generic protocol number
134 * support in HGCM for asking the host and the guest of the support version,
135 * would be nice. Ofc at least the host should be able to talk to the guest,
136 * even when the version is below the host one.
137 * All this stuff would be useful for the current services, but also for future
138 * onces.
139 *
140 * Todo:
141 * - Dragging out of the guest (partly done)
142 * - ESC doesn't really work (on Windows guests it's already implemented)
143 * - transfer of URIs (that is the files and patching of the data)
144 * - testing in a multi monitor setup
145 * ... in any case it seems a little bit difficult to handle from the Qt
146 * side. Maybe also a host specific implementation becomes necessary ...
147 * this would be really worst ofc.
148 * - Win guest support (maybe there have to be done a mapping between the
149 * official mime-types and Win Clipboard formats (see QWindowsMime, for an
150 * idea), for VBox internally only mime-types should be used)
151 * - EOL handling on text (should be shared with the clipboard code)
152 * - add configuration (GH, HG, Bidirectional, None), like for the clipboard
153 * - HG->GH and GH->HG-switch: Handle the case the user drags something out of
154 * the guest and than return to the source view (or another window in the
155 * multiple guest screen scenario).
156 * - add support for more mime-types (especially images, csv)
157 * - test unusual behavior:
158 * - DnD service crash in the guest during a DnD op (e.g. crash of VBoxClient or X11)
159 * - not expected order of the events between HGCM and the guest
160 * - Security considerations: We transfer a lot of memory between the guest and
161 * the host and even allow the creation of dirs/files. Maybe there should be
162 * limits introduced to preventing DOS attacks or filling up all the memory
163 * (both in the host and the guest).
164 * - test, test, test ...
165 */
166
167class DnDGuestResponse
168{
169
170public:
171
172 DnDGuestResponse(const ComObjPtr<Guest>& pGuest);
173
174 virtual ~DnDGuestResponse(void);
175
176public:
177
178 int notifyAboutGuestResponse(void);
179 int waitForGuestResponse(RTMSINTERVAL msTimeout = 500);
180
181 void setDefAction(uint32_t a) { m_defAction = a; }
182 uint32_t defAction(void) const { return m_defAction; }
183
184 void setAllActions(uint32_t a) { m_allActions = a; }
185 uint32_t allActions() const { return m_allActions; }
186
187 void setFormat(const Utf8Str &strFormat) { m_strFormat = strFormat; }
188 Utf8Str format(void) const { return m_strFormat; }
189
190 int dataAdd(void *pvData, uint32_t cbData, uint32_t *pcbCurSize);
191 void reset(void);
192 const void *data(void) { return m_pvData; }
193 size_t size(void) const { return m_cbData; }
194
195 int setProgress(unsigned uPercentage, uint32_t uState, int rcOp = VINF_SUCCESS);
196 HRESULT resetProgress(const ComObjPtr<Guest>& pParent);
197 HRESULT queryProgressTo(IProgress **ppProgress);
198
199private:
200 RTSEMEVENT m_EventSem;
201 uint32_t m_defAction;
202 uint32_t m_allActions;
203 Utf8Str m_strFormat;
204 void *m_pvData;
205 uint32_t m_cbData;
206
207 ComObjPtr<Guest> m_parent;
208 ComObjPtr<Progress> m_progress;
209};
210
211class GuestDnDPrivate
212{
213private:
214 /* todo: currently we only support one response. Maybe this needs to be extended at some time. */
215 GuestDnDPrivate(GuestDnD *q, const ComObjPtr<Guest>& pGuest)
216 : q_ptr(q)
217 , p(pGuest)
218 , m_pDnDResponse(new DnDGuestResponse(pGuest))
219 {}
220 virtual ~GuestDnDPrivate(void) { delete m_pDnDResponse; }
221
222 DnDGuestResponse *response(void) const { return m_pDnDResponse; }
223
224 void adjustCoords(ULONG uScreenId, ULONG *puX, ULONG *puY) const;
225 void hostCall(uint32_t u32Function, uint32_t cParms, PVBOXHGCMSVCPARM paParms) const;
226
227 /* Static helper */
228 static RTCString toFormatString(ComSafeArrayIn(IN_BSTR, formats));
229 static void toFormatSafeArray(const RTCString &strFormats, ComSafeArrayOut(BSTR, formats));
230
231 static DragAndDropAction_T toMainAction(uint32_t uAction);
232 static void toMainActions(uint32_t uActions, ComSafeArrayOut(DragAndDropAction_T, actions));
233 static uint32_t toHGCMAction(DragAndDropAction_T action);
234 static void toHGCMActions(DragAndDropAction_T inDefAction, uint32_t *pOutDefAction, ComSafeArrayIn(DragAndDropAction_T, inAllowedActions), uint32_t *pOutAllowedActions);
235
236 /* Private q and parent pointer */
237 GuestDnD *q_ptr;
238 ComObjPtr<Guest> p;
239
240 /* Private helper members */
241 static const RTCList<RTCString> m_sstrAllowedMimeTypes;
242 DnDGuestResponse *m_pDnDResponse;
243
244 friend class GuestDnD;
245};
246
247/* What mime-types are supported by VirtualBox.
248 * Note: If you add something here, make sure you test it with all guest OS's!
249 ** @todo Make this MIME list configurable / extendable (by extra data?). Currently
250 * this is done hardcoded on every guest platform (POSIX/Windows).
251 */
252/* static */
253const RTCList<RTCString> GuestDnDPrivate::m_sstrAllowedMimeTypes = RTCList<RTCString>()
254 /* Uri's */
255 << "text/uri-list"
256 /* Text */
257 << "text/plain;charset=utf-8"
258 << "UTF8_STRING"
259 << "text/plain"
260 << "COMPOUND_TEXT"
261 << "TEXT"
262 << "STRING"
263 /* OpenOffice formates */
264 << "application/x-openoffice-embed-source-xml;windows_formatname=\"Star Embed Source (XML)\""
265 << "application/x-openoffice-drawing;windows_formatname=\"Drawing Format\"";
266
267DnDGuestResponse::DnDGuestResponse(const ComObjPtr<Guest>& pGuest)
268 : m_EventSem(NIL_RTSEMEVENT)
269 , m_defAction(0)
270 , m_allActions(0)
271 , m_pvData(0)
272 , m_cbData(0)
273 , m_parent(pGuest)
274{
275 int rc = RTSemEventCreate(&m_EventSem);
276 AssertRC(rc);
277}
278
279DnDGuestResponse::~DnDGuestResponse()
280{
281 reset();
282 int rc = RTSemEventDestroy(m_EventSem);
283 AssertRC(rc);
284}
285
286int DnDGuestResponse::notifyAboutGuestResponse()
287{
288 return RTSemEventSignal(m_EventSem);
289}
290
291int DnDGuestResponse::waitForGuestResponse(RTMSINTERVAL msTimeout /*= 500 */)
292{
293 int vrc = RTSemEventWait(m_EventSem, msTimeout);
294#ifdef DEBUG_andy
295 LogFlowFunc(("msTimeout=%RU32, rc=%Rrc\n", msTimeout, vrc));
296#endif
297 return vrc;
298}
299
300int DnDGuestResponse::dataAdd(void *pvData, uint32_t cbData, uint32_t *pcbCurSize)
301{
302 int rc = VINF_SUCCESS;
303
304 /** @todo Make reallocation scheme a bit smarter here. */
305 m_pvData = RTMemRealloc(m_pvData, m_cbData + cbData);
306 if (m_pvData)
307 {
308 memcpy(&static_cast<uint8_t*>(m_pvData)[m_cbData],
309 pvData, cbData);
310 m_cbData += cbData;
311
312 if (pcbCurSize)
313 *pcbCurSize = m_cbData;
314 }
315 else
316 rc = VERR_NO_MEMORY;
317
318 return rc;
319}
320
321void DnDGuestResponse::reset(void)
322{
323 if (m_pvData)
324 {
325 RTMemFree(m_pvData);
326 m_pvData = NULL;
327 }
328
329 m_cbData = 0;
330}
331
332HRESULT DnDGuestResponse::resetProgress(const ComObjPtr<Guest>& pParent)
333{
334 m_progress.setNull();
335 HRESULT rc = m_progress.createObject();
336 if (SUCCEEDED(rc))
337 {
338 rc = m_progress->init(static_cast<IGuest*>(pParent),
339 Bstr(pParent->tr("Dropping data")).raw(),
340 TRUE);
341 }
342 return rc;
343}
344
345int DnDGuestResponse::setProgress(unsigned uPercentage, uint32_t uState, int rcOp /* = VINF_SUCCESS */)
346{
347 LogFlowFunc(("uPercentage=%RU32, uState=%ld, rcOp=%Rrc\n", uPercentage, uState, rcOp));
348
349 int vrc = VINF_SUCCESS;
350 if (!m_progress.isNull())
351 {
352 BOOL fCompleted;
353 HRESULT rc = m_progress->COMGETTER(Completed)(&fCompleted);
354 if (!fCompleted)
355 {
356 if (uState == DragAndDropSvc::DND_PROGRESS_ERROR)
357 {
358 rc = m_progress->notifyComplete(E_FAIL,
359 COM_IIDOF(IGuest),
360 m_parent->getComponentName(),
361 m_parent->tr("Guest error (%Rrc)"), rcOp);
362 }
363 else if (uState == DragAndDropSvc::DND_PROGRESS_CANCELLED)
364 {
365 rc = m_progress->Cancel();
366 vrc = VERR_CANCELLED;
367 }
368 else /* uState == DragAndDropSvc::DND_PROGRESS_RUNNING */
369 {
370 rc = m_progress->SetCurrentOperationProgress(uPercentage);
371#ifndef DEBUG_andy
372 Assert(SUCCEEDED(rc));
373#endif
374 if ( uState == DragAndDropSvc::DND_PROGRESS_COMPLETE
375 || uPercentage >= 100)
376 rc = m_progress->notifyComplete(S_OK);
377 }
378#ifndef DEBUG_andy
379 Assert(SUCCEEDED(rc));
380#endif
381 }
382 }
383
384 return vrc;
385}
386
387HRESULT DnDGuestResponse::queryProgressTo(IProgress **ppProgress)
388{
389 return m_progress.queryInterfaceTo(ppProgress);
390}
391
392void GuestDnDPrivate::adjustCoords(ULONG uScreenId, ULONG *puX, ULONG *puY) const
393{
394 /* For multi-monitor support we need to add shift values to the coordinates
395 * (depending on the screen number). */
396 ComPtr<IDisplay> pDisplay;
397 HRESULT rc = p->mParent->COMGETTER(Display)(pDisplay.asOutParam());
398 if (FAILED(rc)) throw rc;
399 ComPtr<IFramebuffer> pFramebuffer;
400 LONG xShift, yShift;
401 rc = pDisplay->GetFramebuffer(uScreenId, pFramebuffer.asOutParam(), &xShift, &yShift);
402 if (FAILED(rc)) throw rc;
403 *puX += xShift;
404 *puY += yShift;
405}
406
407void GuestDnDPrivate::hostCall(uint32_t u32Function, uint32_t cParms, PVBOXHGCMSVCPARM paParms) const
408{
409 VMMDev *vmmDev = NULL;
410 {
411 /* Make sure mParent is valid, so set the read lock while using.
412 * Do not keep this lock while doing the actual call, because in the meanwhile
413 * another thread could request a write lock which would be a bad idea ... */
414 AutoReadLock alock(p COMMA_LOCKVAL_SRC_POS);
415
416 /* Forward the information to the VMM device. */
417 AssertPtr(p->mParent);
418 vmmDev = p->mParent->getVMMDev();
419 }
420
421 if (!vmmDev)
422 throw p->setError(VBOX_E_VM_ERROR,
423 p->tr("VMM device is not available (is the VM running?)"));
424
425 LogFlowFunc(("hgcmHostCall msg=%RU32, numParms=%RU32\n", u32Function, cParms));
426 int vrc = vmmDev->hgcmHostCall("VBoxDragAndDropSvc",
427 u32Function,
428 cParms, paParms);
429 if (RT_FAILURE(vrc))
430 {
431 LogFlowFunc(("hgcmHostCall error: %Rrc\n", vrc));
432 throw p->setError(VBOX_E_VM_ERROR,
433 p->tr("hgcmHostCall failed (%Rrc)"), vrc);
434 }
435}
436
437/* static */
438RTCString GuestDnDPrivate::toFormatString(ComSafeArrayIn(IN_BSTR, formats))
439{
440 const RTCList<Utf8Str> formatList(ComSafeArrayInArg(formats));
441 RTCString strFormat;
442 for (size_t i = 0; i < formatList.size(); ++i)
443 {
444 const RTCString &f = formatList.at(i);
445 /* Only keep allowed format types. */
446 if (m_sstrAllowedMimeTypes.contains(f))
447 strFormat += f + "\r\n";
448 }
449 return strFormat;
450}
451
452/* static */
453void GuestDnDPrivate::toFormatSafeArray(const RTCString &strFormats, ComSafeArrayOut(BSTR, formats))
454{
455 RTCList<RTCString> list = strFormats.split("\r\n");
456 size_t i = 0;
457 while (i < list.size())
458 {
459 /* Only keep allowed format types. */
460 if (!m_sstrAllowedMimeTypes.contains(list.at(i)))
461 list.removeAt(i);
462 else
463 ++i;
464 }
465 /* Create a safe array out of the cleaned list. */
466 com::SafeArray<BSTR> sfaFormats(list.size());
467 for (i = 0; i < list.size(); ++i)
468 {
469 const RTCString &f = list.at(i);
470 if (m_sstrAllowedMimeTypes.contains(f))
471 {
472 Bstr bstr(f);
473 bstr.detachTo(&sfaFormats[i]);
474 }
475 }
476 sfaFormats.detachTo(ComSafeArrayOutArg(formats));
477}
478
479/* static */
480uint32_t GuestDnDPrivate::toHGCMAction(DragAndDropAction_T action)
481{
482 uint32_t a = DND_IGNORE_ACTION;
483 switch (action)
484 {
485 case DragAndDropAction_Copy: a = DND_COPY_ACTION; break;
486 case DragAndDropAction_Move: a = DND_MOVE_ACTION; break;
487 case DragAndDropAction_Link: /* For now it doesn't seems useful to allow a link action between host & guest. Maybe later! */
488 case DragAndDropAction_Ignore: /* Ignored */ break;
489 default: AssertMsgFailed(("Action %u not recognized!\n", action)); break;
490 }
491 return a;
492}
493
494/* static */
495void GuestDnDPrivate::toHGCMActions(DragAndDropAction_T inDefAction, uint32_t *pOutDefAction, ComSafeArrayIn(DragAndDropAction_T, inAllowedActions), uint32_t *pOutAllowedActions)
496{
497 const com::SafeArray<DragAndDropAction_T> sfaInActions(ComSafeArrayInArg(inAllowedActions));
498
499 /* Defaults */
500 *pOutDefAction = toHGCMAction(inDefAction);;
501 *pOutAllowedActions = DND_IGNORE_ACTION;
502
503 /* First convert the allowed actions to a bit array. */
504 for (size_t i = 0; i < sfaInActions.size(); ++i)
505 *pOutAllowedActions |= toHGCMAction(sfaInActions[i]);
506
507 /* Second check if the default action is a valid action and if not so try
508 * to find an allowed action. */
509 if (isDnDIgnoreAction(*pOutDefAction))
510 {
511 if (hasDnDCopyAction(*pOutAllowedActions))
512 *pOutDefAction = DND_COPY_ACTION;
513 else if (hasDnDMoveAction(*pOutAllowedActions))
514 *pOutDefAction = DND_MOVE_ACTION;
515 }
516}
517
518/* static */
519DragAndDropAction_T GuestDnDPrivate::toMainAction(uint32_t uAction)
520{
521 /* For now it doesn't seems useful to allow a link action between host & guest. Maybe later! */
522 return (isDnDCopyAction(uAction) ? (DragAndDropAction_T)DragAndDropAction_Copy :
523 isDnDMoveAction(uAction) ? (DragAndDropAction_T)DragAndDropAction_Move :
524 (DragAndDropAction_T)DragAndDropAction_Ignore);
525}
526
527/* static */
528void GuestDnDPrivate::toMainActions(uint32_t uActions, ComSafeArrayOut(DragAndDropAction_T, actions))
529{
530 /* For now it doesn't seems useful to allow a link action between host & guest. Maybe later! */
531 RTCList<DragAndDropAction_T> list;
532 if (hasDnDCopyAction(uActions))
533 list.append(DragAndDropAction_Copy);
534 if (hasDnDMoveAction(uActions))
535 list.append(DragAndDropAction_Move);
536
537 com::SafeArray<DragAndDropAction_T> sfaActions(list.size());
538 for (size_t i = 0; i < list.size(); ++i)
539 sfaActions[i] = list.at(i);
540 sfaActions.detachTo(ComSafeArrayOutArg(actions));
541}
542
543GuestDnD::GuestDnD(const ComObjPtr<Guest>& pGuest)
544 : d_ptr(new GuestDnDPrivate(this, pGuest))
545{
546}
547
548GuestDnD::~GuestDnD()
549{
550 delete d_ptr;
551}
552
553HRESULT GuestDnD::dragHGEnter(ULONG uScreenId, ULONG uX, ULONG uY, DragAndDropAction_T defaultAction, ComSafeArrayIn(DragAndDropAction_T, allowedActions), ComSafeArrayIn(IN_BSTR, formats), DragAndDropAction_T *pResultAction)
554{
555 DPTR(GuestDnD);
556 const ComObjPtr<Guest> &p = d->p;
557
558 /* Default is ignoring */
559 *pResultAction = DragAndDropAction_Ignore;
560
561 /* Check & convert the drag & drop actions */
562 uint32_t uDefAction = 0;
563 uint32_t uAllowedActions = 0;
564 d->toHGCMActions(defaultAction, &uDefAction, ComSafeArrayInArg(allowedActions), &uAllowedActions);
565 /* If there is no usable action, ignore this request. */
566 if (isDnDIgnoreAction(uDefAction))
567 return S_OK;
568
569 /* Make a flat data string out of the mime-type list. */
570 RTCString strFormats = d->toFormatString(ComSafeArrayInArg(formats));
571 /* If there is no valid mime-type, ignore this request. */
572 if (strFormats.isEmpty())
573 return S_OK;
574
575 HRESULT rc = S_OK;
576
577 try
578 {
579 /* Adjust the coordinates in a multi-monitor setup. */
580 d->adjustCoords(uScreenId, &uX, &uY);
581
582 VBOXHGCMSVCPARM paParms[7];
583 int i = 0;
584 paParms[i++].setUInt32(uScreenId);
585 paParms[i++].setUInt32(uX);
586 paParms[i++].setUInt32(uY);
587 paParms[i++].setUInt32(uDefAction);
588 paParms[i++].setUInt32(uAllowedActions);
589 paParms[i++].setPointer((void*)strFormats.c_str(), strFormats.length() + 1);
590 paParms[i++].setUInt32(strFormats.length() + 1);
591
592 d->hostCall(DragAndDropSvc::HOST_DND_HG_EVT_ENTER,
593 i,
594 paParms);
595
596 DnDGuestResponse *pDnD = d->response();
597 /* This blocks until the request is answered (or timeout). */
598 if (pDnD->waitForGuestResponse() == VERR_TIMEOUT)
599 return S_OK;
600
601 /* Copy the response info */
602 *pResultAction = d->toMainAction(pDnD->defAction());
603 LogFlowFunc(("*pResultAction=%ld\n", *pResultAction));
604 }
605 catch (HRESULT rc2)
606 {
607 rc = rc2;
608 }
609
610 return rc;
611}
612
613HRESULT GuestDnD::dragHGMove(ULONG uScreenId, ULONG uX, ULONG uY, DragAndDropAction_T defaultAction, ComSafeArrayIn(DragAndDropAction_T, allowedActions), ComSafeArrayIn(IN_BSTR, formats), DragAndDropAction_T *pResultAction)
614{
615 DPTR(GuestDnD);
616 const ComObjPtr<Guest> &p = d->p;
617
618 /* Default is ignoring */
619 *pResultAction = DragAndDropAction_Ignore;
620
621 /* Check & convert the drag & drop actions */
622 uint32_t uDefAction = 0;
623 uint32_t uAllowedActions = 0;
624 d->toHGCMActions(defaultAction, &uDefAction, ComSafeArrayInArg(allowedActions), &uAllowedActions);
625 /* If there is no usable action, ignore this request. */
626 if (isDnDIgnoreAction(uDefAction))
627 return S_OK;
628
629 /* Make a flat data string out of the mime-type list. */
630 RTCString strFormats = d->toFormatString(ComSafeArrayInArg(formats));
631 /* If there is no valid mime-type, ignore this request. */
632 if (strFormats.isEmpty())
633 return S_OK;
634
635 HRESULT rc = S_OK;
636
637 try
638 {
639 /* Adjust the coordinates in a multi-monitor setup. */
640 d->adjustCoords(uScreenId, &uX, &uY);
641
642 VBOXHGCMSVCPARM paParms[7];
643 int i = 0;
644 paParms[i++].setUInt32(uScreenId);
645 paParms[i++].setUInt32(uX);
646 paParms[i++].setUInt32(uY);
647 paParms[i++].setUInt32(uDefAction);
648 paParms[i++].setUInt32(uAllowedActions);
649 paParms[i++].setPointer((void*)strFormats.c_str(), strFormats.length() + 1);
650 paParms[i++].setUInt32(strFormats.length() + 1);
651
652 d->hostCall(DragAndDropSvc::HOST_DND_HG_EVT_MOVE,
653 i,
654 paParms);
655
656 DnDGuestResponse *pDnD = d->response();
657 /* This blocks until the request is answered (or timeout). */
658 if (pDnD->waitForGuestResponse() == VERR_TIMEOUT)
659 return S_OK;
660
661 /* Copy the response info */
662 *pResultAction = d->toMainAction(pDnD->defAction());
663 LogFlowFunc(("*pResultAction=%ld\n", *pResultAction));
664 }
665 catch (HRESULT rc2)
666 {
667 rc = rc2;
668 }
669
670 return rc;
671}
672
673HRESULT GuestDnD::dragHGLeave(ULONG uScreenId)
674{
675 DPTR(GuestDnD);
676 const ComObjPtr<Guest> &p = d->p;
677
678 HRESULT rc = S_OK;
679
680 try
681 {
682 d->hostCall(DragAndDropSvc::HOST_DND_HG_EVT_LEAVE,
683 0,
684 NULL);
685
686 DnDGuestResponse *pDnD = d->response();
687 /* This blocks until the request is answered (or timeout). */
688 pDnD->waitForGuestResponse();
689 }
690 catch (HRESULT rc2)
691 {
692 rc = rc2;
693 }
694
695 return rc;
696}
697
698HRESULT GuestDnD::dragHGDrop(ULONG uScreenId, ULONG uX, ULONG uY, DragAndDropAction_T defaultAction, ComSafeArrayIn(DragAndDropAction_T, allowedActions), ComSafeArrayIn(IN_BSTR, formats), BSTR *pstrFormat, DragAndDropAction_T *pResultAction)
699{
700 DPTR(GuestDnD);
701 const ComObjPtr<Guest> &p = d->p;
702
703 /* Default is ignoring */
704 *pResultAction = DragAndDropAction_Ignore;
705
706 /* Check & convert the drag & drop actions */
707 uint32_t uDefAction = 0;
708 uint32_t uAllowedActions = 0;
709 d->toHGCMActions(defaultAction, &uDefAction, ComSafeArrayInArg(allowedActions), &uAllowedActions);
710 /* If there is no usable action, ignore this request. */
711 if (isDnDIgnoreAction(uDefAction))
712 return S_OK;
713
714 /* Make a flat data string out of the mime-type list. */
715 RTCString strFormats = d->toFormatString(ComSafeArrayInArg(formats));
716 /* If there is no valid mime-type, ignore this request. */
717 if (strFormats.isEmpty())
718 return S_OK;
719
720 HRESULT rc = S_OK;
721
722 try
723 {
724 /* Adjust the coordinates in a multi-monitor setup. */
725 d->adjustCoords(uScreenId, &uX, &uY);
726
727 VBOXHGCMSVCPARM paParms[7];
728 int i = 0;
729 paParms[i++].setUInt32(uScreenId);
730 paParms[i++].setUInt32(uX);
731 paParms[i++].setUInt32(uY);
732 paParms[i++].setUInt32(uDefAction);
733 paParms[i++].setUInt32(uAllowedActions);
734 paParms[i++].setPointer((void*)strFormats.c_str(), strFormats.length() + 1);
735 paParms[i++].setUInt32(strFormats.length() + 1);
736
737 d->hostCall(DragAndDropSvc::HOST_DND_HG_EVT_DROPPED,
738 i,
739 paParms);
740
741 DnDGuestResponse *pDnD = d->response();
742 /* This blocks until the request is answered (or timeout). */
743 if (pDnD->waitForGuestResponse() == VERR_TIMEOUT)
744 return S_OK;
745
746 /* Copy the response info */
747 *pResultAction = d->toMainAction(pDnD->defAction());
748 Bstr(pDnD->format()).cloneTo(pstrFormat);
749
750 LogFlowFunc(("*pResultAction=%ld\n", *pResultAction));
751 }
752 catch (HRESULT rc2)
753 {
754 rc = rc2;
755 }
756
757 return rc;
758}
759
760HRESULT GuestDnD::dragHGPutData(ULONG uScreenId, IN_BSTR bstrFormat,
761 ComSafeArrayIn(BYTE, data), IProgress **ppProgress)
762{
763 DPTR(GuestDnD);
764 const ComObjPtr<Guest> &p = d->p;
765
766 HRESULT rc = S_OK;
767
768 try
769 {
770 Utf8Str strFormat(bstrFormat);
771 com::SafeArray<BYTE> sfaData(ComSafeArrayInArg(data));
772
773 VBOXHGCMSVCPARM paParms[5];
774 int i = 0;
775 paParms[i++].setUInt32(uScreenId);
776 paParms[i++].setPointer((void*)strFormat.c_str(), (uint32_t)strFormat.length() + 1);
777 paParms[i++].setUInt32((uint32_t)strFormat.length() + 1);
778 paParms[i++].setPointer((void*)sfaData.raw(), (uint32_t)sfaData.size());
779 paParms[i++].setUInt32((uint32_t)sfaData.size());
780
781 DnDGuestResponse *pDnD = d->response();
782 /* Reset any old progress status. */
783 pDnD->resetProgress(p);
784
785 d->hostCall(DragAndDropSvc::HOST_DND_HG_SND_DATA,
786 i,
787 paParms);
788
789 /* Query the progress object to the caller. */
790 pDnD->queryProgressTo(ppProgress);
791 }
792 catch (HRESULT rc2)
793 {
794 rc = rc2;
795 }
796
797 return rc;
798}
799
800#ifdef VBOX_WITH_DRAG_AND_DROP_GH
801HRESULT GuestDnD::dragGHPending(ULONG uScreenId,
802 ComSafeArrayOut(BSTR, formats),
803 ComSafeArrayOut(DragAndDropAction_T, allowedActions),
804 DragAndDropAction_T *pDefaultAction)
805{
806 DPTR(GuestDnD);
807 const ComObjPtr<Guest> &p = d->p;
808
809 /* Default is ignoring */
810 *pDefaultAction = DragAndDropAction_Ignore;
811
812 HRESULT rc = S_OK;
813
814 try
815 {
816 VBOXHGCMSVCPARM paParms[1];
817 int i = 0;
818 paParms[i++].setUInt32(uScreenId);
819
820 d->hostCall(DragAndDropSvc::HOST_DND_GH_REQ_PENDING,
821 i,
822 paParms);
823
824 DnDGuestResponse *pDnD = d->response();
825 /* This blocks until the request is answered (or timeout). */
826 if (pDnD->waitForGuestResponse() == VERR_TIMEOUT)
827 return S_OK;
828
829 if (isDnDIgnoreAction(pDnD->defAction()))
830 return S_OK;
831
832 /* Fetch the default action to use. */
833 *pDefaultAction = d->toMainAction(pDnD->defAction());
834 /* Convert the formats strings to a vector of strings. */
835 d->toFormatSafeArray(pDnD->format(), ComSafeArrayOutArg(formats));
836 /* Convert the action bit field to a vector of actions. */
837 d->toMainActions(pDnD->allActions(), ComSafeArrayOutArg(allowedActions));
838
839 LogFlowFunc(("*pDefaultAction=0x%x\n", *pDefaultAction));
840 }
841 catch (HRESULT rc2)
842 {
843 rc = rc2;
844 }
845
846 return rc;
847}
848
849HRESULT GuestDnD::dragGHDropped(IN_BSTR bstrFormat, DragAndDropAction_T action,
850 IProgress **ppProgress)
851{
852 DPTR(GuestDnD);
853 const ComObjPtr<Guest> &p = d->p;
854
855 Utf8Str strFormat(bstrFormat);
856 HRESULT rc = S_OK;
857
858 uint32_t uAction = d->toHGCMAction(action);
859 /* If there is no usable action, ignore this request. */
860 if (isDnDIgnoreAction(uAction))
861 return S_OK;
862
863 try
864 {
865 LogFlowFunc(("strFormat=%s, uAction=0x%x\n", strFormat.c_str(), uAction));
866
867 VBOXHGCMSVCPARM paParms[3];
868 int i = 0;
869 paParms[i++].setPointer((void*)strFormat.c_str(), (uint32_t)strFormat.length() + 1);
870 paParms[i++].setUInt32((uint32_t)strFormat.length() + 1);
871 paParms[i++].setUInt32(uAction);
872
873 DnDGuestResponse *pDnD = d->response();
874 /* Reset any old data and the progress status. */
875 pDnD->reset();
876 pDnD->resetProgress(p);
877
878 d->hostCall(DragAndDropSvc::HOST_DND_GH_EVT_DROPPED,
879 i,
880 paParms);
881
882 /* Query the progress object to the caller. */
883 pDnD->queryProgressTo(ppProgress);
884 }
885 catch (HRESULT rc2)
886 {
887 rc = rc2;
888 }
889
890 return rc;
891}
892
893HRESULT GuestDnD::dragGHGetData(ComSafeArrayOut(BYTE, data))
894{
895 DPTR(GuestDnD);
896 const ComObjPtr<Guest> &p = d->p;
897
898 HRESULT rc = S_OK;
899
900 DnDGuestResponse *pDnD = d->response();
901 if (pDnD)
902 {
903 com::SafeArray<BYTE> sfaData;
904
905 uint32_t cbData = pDnD->size();
906 if (cbData)
907 {
908 /* Copy the data into an safe array of bytes. */
909 const void *pvData = pDnD->data();
910 if (sfaData.resize(cbData))
911 memcpy(sfaData.raw(), pvData, cbData);
912 else
913 rc = E_OUTOFMEMORY;
914 }
915
916#ifdef DEBUG_andy
917 LogFlowFunc(("Received %RU32 bytes\n", cbData));
918#endif
919 /* Detach in any case, regardless of data size. */
920 sfaData.detachTo(ComSafeArrayOutArg(data));
921
922 /* Delete the data. */
923 pDnD->reset();
924 }
925 else
926 rc = VBOX_E_INVALID_OBJECT_STATE;
927
928 return rc;
929}
930#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
931
932DECLCALLBACK(int) GuestDnD::notifyGuestDragAndDropEvent(void *pvExtension, uint32_t u32Function, void *pvParms, uint32_t cbParms)
933{
934 LogFlowFunc(("pvExtension=%p, u32Function=%RU32, pvParms=%p, cbParms=%RU32\n",
935 pvExtension, u32Function, pvParms, cbParms));
936
937 ComObjPtr<Guest> pGuest = reinterpret_cast<Guest*>(pvExtension);
938 if (!pGuest->m_pGuestDnD)
939 return VINF_SUCCESS;
940
941 GuestDnDPrivate *d = static_cast<GuestDnDPrivate*>(pGuest->m_pGuestDnD->d_ptr);
942 const ComObjPtr<Guest> &p = d->p;
943
944 DnDGuestResponse *pResp = d->response();
945 if (pResp == NULL)
946 return VERR_INVALID_PARAMETER;
947
948 int rc = VINF_SUCCESS;
949 switch (u32Function)
950 {
951 case DragAndDropSvc::GUEST_DND_HG_ACK_OP:
952 {
953 DragAndDropSvc::PVBOXDNDCBHGACKOPDATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBHGACKOPDATA>(pvParms);
954 AssertPtr(pCBData);
955 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBHGACKOPDATA) == cbParms, VERR_INVALID_PARAMETER);
956 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_HG_ACK_OP == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
957 pResp->setDefAction(pCBData->uAction);
958 rc = pResp->notifyAboutGuestResponse();
959 break;
960 }
961
962 case DragAndDropSvc::GUEST_DND_HG_REQ_DATA:
963 {
964 DragAndDropSvc::PVBOXDNDCBHGREQDATADATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBHGREQDATADATA>(pvParms);
965 AssertPtr(pCBData);
966 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBHGREQDATADATA) == cbParms, VERR_INVALID_PARAMETER);
967 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_HG_REQ_DATA == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
968 pResp->setFormat(pCBData->pszFormat);
969 rc = pResp->notifyAboutGuestResponse();
970 break;
971 }
972
973 case DragAndDropSvc::GUEST_DND_HG_EVT_PROGRESS:
974 {
975 DragAndDropSvc::PVBOXDNDCBHGEVTPROGRESSDATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBHGEVTPROGRESSDATA>(pvParms);
976 AssertPtr(pCBData);
977 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBHGEVTPROGRESSDATA) == cbParms, VERR_INVALID_PARAMETER);
978 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_HG_EVT_PROGRESS == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
979 rc = pResp->setProgress(pCBData->uPercentage, pCBData->uState, pCBData->rc);
980 break;
981 }
982
983#ifdef VBOX_WITH_DRAG_AND_DROP_GH
984 case DragAndDropSvc::GUEST_DND_GH_ACK_PENDING:
985 {
986 DragAndDropSvc::PVBOXDNDCBGHACKPENDINGDATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBGHACKPENDINGDATA>(pvParms);
987 AssertPtr(pCBData);
988 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBGHACKPENDINGDATA) == cbParms, VERR_INVALID_PARAMETER);
989 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_GH_ACK_PENDING == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
990 pResp->setFormat(pCBData->pszFormat);
991 pResp->setDefAction(pCBData->uDefAction);
992 pResp->setAllActions(pCBData->uAllActions);
993 rc = pResp->notifyAboutGuestResponse();
994 break;
995 }
996
997 case DragAndDropSvc::GUEST_DND_GH_SND_DATA:
998 {
999 DragAndDropSvc::PVBOXDNDCBSNDDATADATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBSNDDATADATA>(pvParms);
1000 AssertPtr(pCBData);
1001 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBSNDDATADATA) == cbParms, VERR_INVALID_PARAMETER);
1002 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_GH_SND_DATA == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
1003
1004 uint32_t cbCurSize = 0;
1005 rc = pResp->dataAdd(pCBData->pvData, pCBData->cbData, &cbCurSize);
1006 if (RT_SUCCESS(rc))
1007 {
1008 uint32_t cbTotalSize = pCBData->cbAllSize;
1009 unsigned int cPercentage;
1010 if (!cbTotalSize) /* Watch out for division by zero. */
1011 cPercentage = 100;
1012 else
1013 cPercentage = cbCurSize * 100.0 / cbTotalSize;
1014
1015 /** @todo Don't use anonymous enums. */
1016 uint32_t uState = DragAndDropSvc::DND_PROGRESS_RUNNING;
1017 if ( pCBData->cbAllSize == cbCurSize
1018 /* Empty data? Should not happen, but anyway ... */
1019 || !pCBData->cbAllSize)
1020 {
1021 uState = DragAndDropSvc::DND_PROGRESS_COMPLETE;
1022 }
1023
1024 rc = pResp->setProgress(cPercentage, uState);
1025 }
1026 /* Todo: for now we instantly confirm the cancel. Check if the
1027 * guest should first clean up stuff itself and than really confirm
1028 * the cancel request by an extra message. */
1029 if (rc == VERR_CANCELLED)
1030 pResp->setProgress(100, DragAndDropSvc::DND_PROGRESS_CANCELLED);
1031 break;
1032 }
1033
1034 case DragAndDropSvc::GUEST_DND_GH_EVT_ERROR:
1035 {
1036 DragAndDropSvc::PVBOXDNDCBEVTERRORDATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBEVTERRORDATA>(pvParms);
1037 AssertPtr(pCBData);
1038 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBEVTERRORDATA) == cbParms, VERR_INVALID_PARAMETER);
1039 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_GH_EVT_ERROR == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
1040
1041 /* Cleanup */
1042 pResp->reset();
1043 rc = pResp->setProgress(100, DragAndDropSvc::DND_PROGRESS_ERROR, pCBData->rc);
1044 break;
1045 }
1046#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
1047 default:
1048 rc = VERR_NOT_SUPPORTED; /* Tell the guest. */
1049 break;
1050 }
1051
1052 LogFlowFunc(("Returning rc=%Rrc\n", rc));
1053 return rc;
1054}
1055
1056#endif /* VBOX_WITH_DRAG_AND_DROP */
1057
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