VirtualBox

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

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

Main: a couple of whitespace fixes

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

© 2023 Oracle
ContactPrivacy policyTerms of Use