VirtualBox

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

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

DnD: Update.

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

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette