VirtualBox

source: vbox/trunk/src/VBox/Additions/x11/VBoxClient/draganddrop.cpp@ 86110

Last change on this file since 86110 was 85927, checked in by vboxsync, 4 years ago

DnD/X11: Added defines + documentation for (not yet implemented) drag'n drop promises support (VBOX_WITH_DRAG_AND_DROP_PROMISES). bugref:9820

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 122.8 KB
Line 
1/* $Id: draganddrop.cpp 85927 2020-08-28 10:57:11Z vboxsync $ */
2/** @file
3 * X11 guest client - Drag and drop implementation.
4 */
5
6/*
7 * Copyright (C) 2011-2020 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 <X11/Xlib.h>
19#include <X11/Xutil.h>
20#include <X11/Xatom.h>
21#ifdef VBOX_DND_WITH_XTEST
22# include <X11/extensions/XTest.h>
23#endif
24
25#include <iprt/asm.h>
26#include <iprt/buildconfig.h>
27#include <iprt/critsect.h>
28#include <iprt/thread.h>
29#include <iprt/time.h>
30
31#include <iprt/cpp/mtlist.h>
32#include <iprt/cpp/ministring.h>
33
34#include <limits.h>
35
36#ifdef LOG_GROUP
37# undef LOG_GROUP
38#endif
39#define LOG_GROUP LOG_GROUP_GUEST_DND
40#include <VBox/log.h>
41#include <VBox/VBoxGuestLib.h>
42
43#include "VBox/HostServices/DragAndDropSvc.h"
44#include "VBoxClient.h"
45
46/* Enable this define to see the proxy window(s) when debugging
47 * their behavior. Don't have this enabled in release builds! */
48#ifdef DEBUG
49//# define VBOX_DND_DEBUG_WND
50#endif
51
52/* Enable this to handle drag'n drop "promises".
53 * This is needed for supporting certain applications (i.e. PcManFM on LXDE),
54 * which require the drag'n drop meta data a lot earlier than actually needed.
55 * That behavior is similar to macOS' drag'n drop promises, hence the name.
56 *
57 * Those applications query the data right while dragging over them (see GtkWidget::drag-motion),
58 * instead of when the source dropped the data (GtkWidget::drag-drop).
59 *
60 * This might be entirely implementation-specific, so not being a bug in GTK/GDK. Also see #9820.
61 */
62#ifdef VBOX_WITH_DRAG_AND_DROP_PROMISES
63# undef VBOX_WITH_DRAG_AND_DROP_PROMISES
64#endif
65
66/**
67 * For X11 guest Xdnd is used. See http://www.acc.umu.se/~vatten/XDND.html for
68 * a walk trough.
69 *
70 * Also useful pages:
71 * - https://www.freedesktop.org/wiki/Draganddropwarts/
72 * - https://www.freedesktop.org/wiki/Specifications/XDNDRevision/
73 *
74 * Host -> Guest:
75 * For X11 this means mainly forwarding all the events from HGCM to the
76 * appropriate X11 events. There exists a proxy window, which is invisible and
77 * used for all the X11 communication. On a HGCM Enter event, we set our proxy
78 * window as XdndSelection owner with the given mime-types. On every HGCM move
79 * event, we move the X11 mouse cursor to the new position and query for the
80 * window below that position. Depending on if it is XdndAware, a new window or
81 * a known window, we send the appropriate X11 messages to it. On HGCM drop, we
82 * send a XdndDrop message to the current window and wait for a X11
83 * SelectionMessage from the target window. Because we didn't have the data in
84 * the requested mime-type, yet, we save that message and ask the host for the
85 * data. When the data is successfully received from the host, we put the data
86 * as a property to the window and send a X11 SelectionNotify event to the
87 * target window.
88 *
89 * Guest -> Host:
90 * This is a lot more trickery than H->G. When a pending event from HGCM
91 * arrives, we ask if there currently is an owner of the XdndSelection
92 * property. If so, our proxy window is shown (1x1, but without backing store)
93 * and some mouse event is triggered. This should be followed by an XdndEnter
94 * event send to the proxy window. From this event we can fetch the necessary
95 * info of the MIME types and allowed actions and send this back to the host.
96 * On a drop request from the host, we query for the selection and should get
97 * the data in the specified mime-type. This data is send back to the host.
98 * After that we send a XdndLeave event to the source window.
99 *
100 ** @todo Cancelling (e.g. with ESC key) doesn't work.
101 ** @todo INCR (incremental transfers) support.
102 ** @todo Really check for the Xdnd version and the supported features.
103 ** @todo Either get rid of the xHelpers class or properly unify the code with the drag instance class.
104 */
105
106/*********************************************************************************************************************************
107 * Definitions *
108 ********************************************************************************************************************************/
109
110/** The Xdnd protocol version we support. */
111#define VBOX_XDND_VERSION (5)
112
113/** Whether the target window accepts the data being dragged over or not. */
114#define VBOX_XDND_STATUS_FLAG_ACCEPT 0x1
115/** Whether the target window wants XdndPosition messages while dragging stuff over it. */
116#define VBOX_XDND_STATUS_FLAG_WANTS_POS 0x2
117
118/** Whether the target window accepted the drop data or not. */
119#define VBOX_XDND_FINISHED_FLAG_SUCCEEDED 0x1
120
121/** How many X properties our proxy window can hold. */
122#define VBOX_MAX_XPROPERTIES (LONG_MAX-1)
123
124/**
125 * Structure for storing new X11 events and HGCM messages
126 * into a single event queue.
127 */
128struct DnDEvent
129{
130 enum DnDEventType
131 {
132 /** Unknown event, do not use. */
133 DnDEventType_Unknown = 0,
134 /** VBGLR3DNDEVENT event. */
135 DnDEventType_HGCM,
136 /** X11 event. */
137 DnDEventType_X11,
138 /** Blow the type up to 32-bit. */
139 DnDEventType_32BIT_HACK = 0x7fffffff
140 };
141 /** Event type. */
142 DnDEventType enmType;
143 union
144 {
145 PVBGLR3DNDEVENT hgcm;
146 XEvent x11;
147 };
148#ifdef IN_GUEST
149 RTMEM_IMPLEMENT_NEW_AND_DELETE();
150#endif
151};
152
153enum XA_Type
154{
155 /* States */
156 XA_WM_STATE = 0,
157 /* Properties */
158 XA_TARGETS,
159 XA_MULTIPLE,
160 XA_INCR,
161 /* Mime Types */
162 XA_image_bmp,
163 XA_image_jpg,
164 XA_image_tiff,
165 XA_image_png,
166 XA_text_uri_list,
167 XA_text_uri,
168 XA_text_plain,
169 XA_TEXT,
170 /* Xdnd */
171 XA_XdndSelection,
172 XA_XdndAware,
173 XA_XdndEnter,
174 XA_XdndLeave,
175 XA_XdndTypeList,
176 XA_XdndActionList,
177 XA_XdndPosition,
178 XA_XdndActionCopy,
179 XA_XdndActionMove,
180 XA_XdndActionLink,
181 XA_XdndStatus,
182 XA_XdndDrop,
183 XA_XdndFinished,
184 /* Our own stop marker */
185 XA_dndstop,
186 /* End marker */
187 XA_End
188};
189
190/**
191 * Xdnd message value indexes, sorted by message type.
192 */
193typedef enum XdndMsg
194{
195 /** XdndEnter. */
196 XdndEnterTypeCount = 3, /* Maximum number of types in XdndEnter message. */
197
198 XdndEnterWindow = 0, /* Source window (sender). */
199 XdndEnterFlags, /* Version in high byte, bit 0 => more data types. */
200 XdndEnterType1, /* First available data type. */
201 XdndEnterType2, /* Second available data type. */
202 XdndEnterType3, /* Third available data type. */
203
204 XdndEnterMoreTypesFlag = 1, /* Set if there are more than XdndEnterTypeCount. */
205 XdndEnterVersionRShift = 24, /* Right shift to position version number. */
206 XdndEnterVersionMask = 0xFF, /* Mask to get version after shifting. */
207
208 /** XdndHere. */
209 XdndHereWindow = 0, /* Source window (sender). */
210 XdndHereFlags, /* Reserved. */
211 XdndHerePt, /* X + Y coordinates of mouse (root window coords). */
212 XdndHereTimeStamp, /* Timestamp for requesting data. */
213 XdndHereAction, /* Action requested by user. */
214
215 /** XdndPosition. */
216 XdndPositionWindow = 0, /* Source window (sender). */
217 XdndPositionFlags, /* Flags. */
218 XdndPositionXY, /* X/Y coordinates of the mouse position relative to the root window. */
219 XdndPositionTimeStamp, /* Time stamp for retrieving the data. */
220 XdndPositionAction, /* Action requested by the user. */
221
222 /** XdndStatus. */
223 XdndStatusWindow = 0, /* Target window (sender).*/
224 XdndStatusFlags, /* Flags returned by target. */
225 XdndStatusNoMsgXY, /* X + Y of "no msg" rectangle (root window coords). */
226 XdndStatusNoMsgWH, /* Width + height of "no msg" rectangle. */
227 XdndStatusAction, /* Action accepted by target. */
228
229 XdndStatusAcceptDropFlag = 1, /* Set if target will accept the drop. */
230 XdndStatusSendHereFlag = 2, /* Set if target wants a stream of XdndPosition. */
231
232 /** XdndLeave. */
233 XdndLeaveWindow = 0, /* Source window (sender). */
234 XdndLeaveFlags, /* Reserved. */
235
236 /** XdndDrop. */
237 XdndDropWindow = 0, /* Source window (sender). */
238 XdndDropFlags, /* Reserved. */
239 XdndDropTimeStamp, /* Timestamp for requesting data. */
240
241 /** XdndFinished. */
242 XdndFinishedWindow = 0, /* Target window (sender). */
243 XdndFinishedFlags, /* Version 5: Bit 0 is set if the current target accepted the drop. */
244 XdndFinishedAction /* Version 5: Contains the action performed by the target. */
245
246} XdndMsg;
247
248class DragAndDropService;
249
250/** List of Atoms. */
251#define VBoxDnDAtomList RTCList<Atom>
252
253class xHelpers
254{
255public:
256
257 static xHelpers *getInstance(Display *pDisplay = 0)
258 {
259 if (!m_pInstance)
260 {
261 AssertPtrReturn(pDisplay, NULL);
262 m_pInstance = new xHelpers(pDisplay);
263 }
264
265 return m_pInstance;
266 }
267
268 static void destroyInstance(void)
269 {
270 if (m_pInstance)
271 {
272 delete m_pInstance;
273 m_pInstance = NULL;
274 }
275 }
276
277 inline Display *display() const { return m_pDisplay; }
278 inline Atom xAtom(XA_Type e) const { return m_xAtoms[e]; }
279
280 inline Atom stringToxAtom(const char *pcszString) const
281 {
282 return XInternAtom(m_pDisplay, pcszString, False);
283 }
284 inline RTCString xAtomToString(Atom atom) const
285 {
286 if (atom == None) return "None";
287
288 char* pcsAtom = XGetAtomName(m_pDisplay, atom);
289 RTCString strAtom(pcsAtom);
290 XFree(pcsAtom);
291
292 return strAtom;
293 }
294
295 inline RTCString xAtomListToString(const VBoxDnDAtomList &formatList)
296 {
297 RTCString format;
298 for (size_t i = 0; i < formatList.size(); ++i)
299 format += xAtomToString(formatList.at(i)) + "\r\n";
300 return format;
301 }
302
303 RTCString xErrorToString(int xRc) const;
304 Window applicationWindowBelowCursor(Window parentWin) const;
305
306private:
307#ifdef RT_NEED_NEW_AND_DELETE
308 RTMEM_IMPLEMENT_NEW_AND_DELETE();
309#endif
310 xHelpers(Display *pDisplay)
311 : m_pDisplay(pDisplay)
312 {
313 /* Not all x11 atoms we use are defined in the headers. Create the
314 * additional one we need here. */
315 for (int i = 0; i < XA_End; ++i)
316 m_xAtoms[i] = XInternAtom(m_pDisplay, m_xAtomNames[i], False);
317 };
318
319 /* Private member vars */
320 static xHelpers *m_pInstance;
321 Display *m_pDisplay;
322 Atom m_xAtoms[XA_End];
323 static const char *m_xAtomNames[XA_End];
324};
325
326/* Some xHelpers convenience defines. */
327#define gX11 xHelpers::getInstance()
328#define xAtom(xa) xHelpers::getInstance()->xAtom((xa))
329#define xAtomToString(xa) xHelpers::getInstance()->xAtomToString((xa))
330
331/*********************************************************************************************************************************
332 * xHelpers implementation. *
333 ********************************************************************************************************************************/
334
335xHelpers *xHelpers::m_pInstance = NULL;
336
337/* Has to be in sync with the XA_Type enum. */
338const char *xHelpers::m_xAtomNames[] =
339{
340 /* States */
341 "WM_STATE",
342 /* Properties */
343 "TARGETS",
344 "MULTIPLE",
345 "INCR",
346 /* Mime Types */
347 "image/bmp",
348 "image/jpg",
349 "image/tiff",
350 "image/png",
351 "text/uri-list",
352 "text/uri",
353 "text/plain",
354 "TEXT",
355 /* Xdnd */
356 "XdndSelection",
357 "XdndAware",
358 "XdndEnter",
359 "XdndLeave",
360 "XdndTypeList",
361 "XdndActionList",
362 "XdndPosition",
363 "XdndActionCopy",
364 "XdndActionMove",
365 "XdndActionLink",
366 "XdndStatus",
367 "XdndDrop",
368 "XdndFinished",
369 /* Our own stop marker */
370 "dndstop"
371};
372
373RTCString xHelpers::xErrorToString(int xRc) const
374{
375 switch (xRc)
376 {
377 case Success: return RTCStringFmt("%d (Success)", xRc); break;
378 case BadRequest: return RTCStringFmt("%d (BadRequest)", xRc); break;
379 case BadValue: return RTCStringFmt("%d (BadValue)", xRc); break;
380 case BadWindow: return RTCStringFmt("%d (BadWindow)", xRc); break;
381 case BadPixmap: return RTCStringFmt("%d (BadPixmap)", xRc); break;
382 case BadAtom: return RTCStringFmt("%d (BadAtom)", xRc); break;
383 case BadCursor: return RTCStringFmt("%d (BadCursor)", xRc); break;
384 case BadFont: return RTCStringFmt("%d (BadFont)", xRc); break;
385 case BadMatch: return RTCStringFmt("%d (BadMatch)", xRc); break;
386 case BadDrawable: return RTCStringFmt("%d (BadDrawable)", xRc); break;
387 case BadAccess: return RTCStringFmt("%d (BadAccess)", xRc); break;
388 case BadAlloc: return RTCStringFmt("%d (BadAlloc)", xRc); break;
389 case BadColor: return RTCStringFmt("%d (BadColor)", xRc); break;
390 case BadGC: return RTCStringFmt("%d (BadGC)", xRc); break;
391 case BadIDChoice: return RTCStringFmt("%d (BadIDChoice)", xRc); break;
392 case BadName: return RTCStringFmt("%d (BadName)", xRc); break;
393 case BadLength: return RTCStringFmt("%d (BadLength)", xRc); break;
394 case BadImplementation: return RTCStringFmt("%d (BadImplementation)", xRc); break;
395 }
396 return RTCStringFmt("%d (unknown)", xRc);
397}
398
399/** @todo Make this iterative. */
400Window xHelpers::applicationWindowBelowCursor(Window wndParent) const
401{
402 /* No parent, nothing to do. */
403 if(wndParent == 0)
404 return 0;
405
406 Window wndApp = 0;
407 int cProps = -1;
408
409 /* Fetch all x11 window properties of the parent window. */
410 Atom *pProps = XListProperties(m_pDisplay, wndParent, &cProps);
411 if (cProps > 0)
412 {
413 /* We check the window for the WM_STATE property. */
414 for (int i = 0; i < cProps; ++i)
415 {
416 if (pProps[i] == xAtom(XA_WM_STATE))
417 {
418 /* Found it. */
419 wndApp = wndParent;
420 break;
421 }
422 }
423
424 /* Cleanup */
425 XFree(pProps);
426 }
427
428 if (!wndApp)
429 {
430 Window wndChild, wndTemp;
431 int tmp;
432 unsigned int utmp;
433
434 /* Query the next child window of the parent window at the current
435 * mouse position. */
436 XQueryPointer(m_pDisplay, wndParent, &wndTemp, &wndChild, &tmp, &tmp, &tmp, &tmp, &utmp);
437
438 /* Recursive call our self to dive into the child tree. */
439 wndApp = applicationWindowBelowCursor(wndChild);
440 }
441
442 return wndApp;
443}
444
445#ifdef DEBUG
446# define VBOX_DND_FN_DECL_LOG(x) inline x /* For LogFlowXXX logging. */
447#else
448# define VBOX_DND_FN_DECL_LOG(x) x
449#endif
450
451/**
452 * Class which handles a single drag'n drop proxy window.
453 ** @todo Move all proxy window-related stuff into this class! Clean up this mess.
454 */
455class VBoxDnDProxyWnd
456{
457
458public:
459#ifdef RT_NEED_NEW_AND_DELETE
460 RTMEM_IMPLEMENT_NEW_AND_DELETE();
461#endif
462 VBoxDnDProxyWnd(void);
463 virtual ~VBoxDnDProxyWnd(void);
464
465public:
466
467 int init(Display *pDisplay);
468 void destroy();
469
470 int sendFinished(Window hWndSource, VBOXDNDACTION dndAction);
471
472public:
473
474 Display *pDisp;
475 /** Proxy window handle. */
476 Window hWnd;
477 int iX;
478 int iY;
479 int iWidth;
480 int iHeight;
481};
482
483/** This class only serve to avoid dragging in generic new() and delete(). */
484class WrappedXEvent
485{
486public:
487 XEvent m_Event;
488
489public:
490#ifdef RT_NEED_NEW_AND_DELETE
491 RTMEM_IMPLEMENT_NEW_AND_DELETE();
492#endif
493 WrappedXEvent(const XEvent &a_rSrcEvent)
494 {
495 m_Event = a_rSrcEvent;
496 }
497
498 WrappedXEvent()
499 {
500 RT_ZERO(m_Event);
501 }
502
503 WrappedXEvent &operator=(const XEvent &a_rSrcEvent)
504 {
505 m_Event = a_rSrcEvent;
506 return *this;
507 }
508};
509
510/**
511 * Class for handling a single drag and drop operation, that is,
512 * one source and one target at a time.
513 *
514 * For now only one DragInstance will exits when the app is running.
515 */
516class DragInstance
517{
518public:
519
520 enum State
521 {
522 Uninitialized = 0,
523 Initialized,
524 Dragging,
525 Dropped,
526 State_32BIT_Hack = 0x7fffffff
527 };
528
529 enum Mode
530 {
531 Unknown = 0,
532 HG,
533 GH,
534 Mode_32Bit_Hack = 0x7fffffff
535 };
536
537#ifdef RT_NEED_NEW_AND_DELETE
538 RTMEM_IMPLEMENT_NEW_AND_DELETE();
539#endif
540 DragInstance(Display *pDisplay, DragAndDropService *pParent);
541
542public:
543
544 int init(uint32_t uScreenID);
545 void uninit(void);
546 void reset(void);
547
548 /* X11 message processing. */
549 int onX11ClientMessage(const XEvent &e);
550 int onX11MotionNotify(const XEvent &e);
551 int onX11SelectionClear(const XEvent &e);
552 int onX11SelectionNotify(const XEvent &e);
553 int onX11SelectionRequest(const XEvent &evReq);
554 int onX11Event(const XEvent &e);
555 int waitForStatusChange(uint32_t enmState, RTMSINTERVAL uTimeoutMS = 30000);
556 bool waitForX11Msg(XEvent &evX, int iType, RTMSINTERVAL uTimeoutMS = 100);
557 bool waitForX11ClientMsg(XClientMessageEvent &evMsg, Atom aType, RTMSINTERVAL uTimeoutMS = 100);
558
559 /* Session handling. */
560 int checkForSessionChange(void);
561
562#ifdef VBOX_WITH_DRAG_AND_DROP_GH
563 /* Guest -> Host handling. */
564 int ghIsDnDPending(void);
565 int ghDropped(const RTCString &strFormat, VBOXDNDACTION dndActionRequested);
566#endif
567
568 /* Host -> Guest handling. */
569 int hgEnter(const RTCList<RTCString> &formats, VBOXDNDACTIONLIST dndListActionsAllowed);
570 int hgLeave(void);
571 int hgMove(uint32_t uPosX, uint32_t uPosY, VBOXDNDACTION dndActionDefault);
572 int hgDrop(uint32_t uPosX, uint32_t uPosY, VBOXDNDACTION dndActionDefault);
573 int hgDataReceive(PVBGLR3GUESTDNDMETADATA pMeta);
574
575 /* X11 helpers. */
576 int mouseCursorFakeMove(void) const;
577 int mouseCursorMove(int iPosX, int iPosY) const;
578 void mouseButtonSet(Window wndDest, int rx, int ry, int iButton, bool fPress);
579 int proxyWinShow(int *piRootX = NULL, int *piRootY = NULL) const;
580 int proxyWinHide(void);
581
582 /* X11 window helpers. */
583 char *wndX11GetNameA(Window wndThis) const;
584
585 /* Xdnd protocol helpers. */
586 void wndXDnDClearActionList(Window wndThis) const;
587 void wndXDnDClearFormatList(Window wndThis) const;
588 int wndXDnDGetActionList(Window wndThis, VBoxDnDAtomList &lstActions) const;
589 int wndXDnDGetFormatList(Window wndThis, VBoxDnDAtomList &lstTypes) const;
590 int wndXDnDSetActionList(Window wndThis, const VBoxDnDAtomList &lstActions) const;
591 int wndXDnDSetFormatList(Window wndThis, Atom atmProp, const VBoxDnDAtomList &lstFormats) const;
592
593 /* Atom / HGCM formatting helpers. */
594 int appendFormatsToList(const RTCList<RTCString> &lstFormats, VBoxDnDAtomList &lstAtoms) const;
595 int appendDataToList(const void *pvData, uint32_t cbData, VBoxDnDAtomList &lstAtoms) const;
596 static Atom toAtomAction(VBOXDNDACTION dndAction);
597 static int toAtomActions(VBOXDNDACTIONLIST dndActionList, VBoxDnDAtomList &lstAtoms);
598 static uint32_t toHGCMAction(Atom atom);
599 static uint32_t toHGCMActions(const VBoxDnDAtomList &actionsList);
600
601protected:
602
603 /** The instance's own DnD context. */
604 VBGLR3GUESTDNDCMDCTX m_dndCtx;
605 /** Pointer to service instance. */
606 DragAndDropService *m_pParent;
607 /** Pointer to X display operating on. */
608 Display *m_pDisplay;
609 /** X screen ID to operate on. */
610 int m_screenID;
611 /** Pointer to X screen operating on. */
612 Screen *m_pScreen;
613 /** Root window handle. */
614 Window m_wndRoot;
615 /** Proxy window. */
616 VBoxDnDProxyWnd m_wndProxy;
617 /** Current source/target window handle. */
618 Window m_wndCur;
619 /** The XDnD protocol version the current
620 * source/target window is using. */
621 long m_curVer;
622 /** List of (Atom) formats the source window supports. */
623 VBoxDnDAtomList m_lstAtomFormats;
624 /** List of (Atom) actions the source window supports. */
625 VBoxDnDAtomList m_lstAtomActions;
626 /** Buffer for answering the target window's selection request. */
627 void *m_pvSelReqData;
628 /** Size (in bytes) of selection request data buffer. */
629 uint32_t m_cbSelReqData;
630 /** Current operation mode. */
631 volatile uint32_t m_enmMode;
632 /** Current state of operation mode. */
633 volatile uint32_t m_enmState;
634 /** The instance's own X event queue. */
635 RTCMTList<WrappedXEvent> m_eventQueueList;
636 /** Critical section for providing serialized access to list
637 * event queue's contents. */
638 RTCRITSECT m_eventQueueCS;
639 /** Event for notifying this instance in case of a new
640 * event. */
641 RTSEMEVENT m_eventQueueEvent;
642 /** Critical section for data access. */
643 RTCRITSECT m_dataCS;
644 /** List of allowed formats. */
645 RTCList<RTCString> m_lstAllowedFormats;
646 /** Number of failed attempts by the host
647 * to query for an active drag and drop operation on the guest. */
648 uint16_t m_cFailedPendingAttempts;
649};
650
651/**
652 * Service class which implements drag'n drop.
653 */
654class DragAndDropService
655{
656public:
657 DragAndDropService(void)
658 : m_pDisplay(NULL)
659 , m_hHGCMThread(NIL_RTTHREAD)
660 , m_hX11Thread(NIL_RTTHREAD)
661 , m_hEventSem(NIL_RTSEMEVENT)
662 , m_pCurDnD(NULL)
663 , m_fStop(false)
664 {
665 RT_ZERO(m_dndCtx);
666 }
667
668 int init(void);
669 int run(bool fDaemonised = false);
670 void cleanup(void);
671
672private:
673
674 static DECLCALLBACK(int) hgcmEventThread(RTTHREAD hThread, void *pvUser);
675 static DECLCALLBACK(int) x11EventThread(RTTHREAD hThread, void *pvUser);
676
677 /* Private member vars */
678 Display *m_pDisplay;
679
680 /** Our (thread-safe) event queue with
681 * mixed events (DnD HGCM / X11). */
682 RTCMTList<DnDEvent> m_eventQueue;
683 /** Critical section for providing serialized access to list
684 * event queue's contents. */
685 RTCRITSECT m_eventQueueCS;
686 RTTHREAD m_hHGCMThread;
687 RTTHREAD m_hX11Thread;
688 /** This service' DnD command context. */
689 VBGLR3GUESTDNDCMDCTX m_dndCtx;
690 RTSEMEVENT m_hEventSem;
691 DragInstance *m_pCurDnD;
692 /** Stop indicator flag to signal the thread that it should shut down. */
693 bool m_fStop;
694
695 friend class DragInstance;
696};
697
698/*********************************************************************************************************************************
699 * DragInstanc implementation. *
700 ********************************************************************************************************************************/
701
702DragInstance::DragInstance(Display *pDisplay, DragAndDropService *pParent)
703 : m_pParent(pParent)
704 , m_pDisplay(pDisplay)
705 , m_pScreen(0)
706 , m_wndRoot(0)
707 , m_wndCur(0)
708 , m_curVer(-1)
709 , m_pvSelReqData(NULL)
710 , m_cbSelReqData(0)
711 , m_enmMode(Unknown)
712 , m_enmState(Uninitialized)
713{
714}
715
716/**
717 * Unitializes (destroys) this drag instance.
718 */
719void DragInstance::uninit(void)
720{
721 LogFlowFuncEnter();
722
723 if (m_wndProxy.hWnd != 0)
724 XDestroyWindow(m_pDisplay, m_wndProxy.hWnd);
725
726 int rc2 = VbglR3DnDDisconnect(&m_dndCtx);
727
728 if (m_pvSelReqData)
729 RTMemFree(m_pvSelReqData);
730
731 rc2 = RTSemEventDestroy(m_eventQueueEvent);
732 AssertRC(rc2);
733
734 rc2 = RTCritSectDelete(&m_eventQueueCS);
735 AssertRC(rc2);
736
737 rc2 = RTCritSectDelete(&m_dataCS);
738 AssertRC(rc2);
739}
740
741/**
742 * Resets this drag instance.
743 */
744void DragInstance::reset(void)
745{
746 LogFlowFuncEnter();
747
748 /* Hide the proxy win. */
749 proxyWinHide();
750
751 int rc2 = RTCritSectEnter(&m_dataCS);
752 if (RT_SUCCESS(rc2))
753 {
754 /* If we are currently the Xdnd selection owner, clear that. */
755 Window pWnd = XGetSelectionOwner(m_pDisplay, xAtom(XA_XdndSelection));
756 if (pWnd == m_wndProxy.hWnd)
757 XSetSelectionOwner(m_pDisplay, xAtom(XA_XdndSelection), None, CurrentTime);
758
759 /* Clear any other DnD specific data on the proxy window. */
760 wndXDnDClearFormatList(m_wndProxy.hWnd);
761 wndXDnDClearActionList(m_wndProxy.hWnd);
762
763 m_lstAtomActions.clear();
764
765 /* First, clear the formats list. */
766 m_lstAtomFormats.clear();
767 /* Append default targets we support.
768 * Note: The order is sorted by preference; be careful when changing this. */
769 m_lstAtomFormats.append(xAtom(XA_TARGETS));
770 m_lstAtomFormats.append(xAtom(XA_MULTIPLE));
771 /** @todo Support INC (incremental transfers). */
772
773 m_wndCur = 0;
774 m_curVer = -1;
775 m_enmState = Initialized;
776 m_enmMode = Unknown;
777 m_eventQueueList.clear();
778 m_cFailedPendingAttempts = 0;
779
780 /* Reset the selection request buffer. */
781 if (m_pvSelReqData)
782 {
783 RTMemFree(m_pvSelReqData);
784 m_pvSelReqData = NULL;
785
786 Assert(m_cbSelReqData);
787 m_cbSelReqData = 0;
788 }
789
790 RTCritSectLeave(&m_dataCS);
791 }
792}
793
794/**
795 * Initializes this drag instance.
796 *
797 * @return IPRT status code.
798 * @param uScreenID X' screen ID to use.
799 */
800int DragInstance::init(uint32_t uScreenID)
801{
802 int rc = VbglR3DnDConnect(&m_dndCtx);
803 /* Note: Can return VINF_PERMISSION_DENIED if HGCM host service is not available. */
804 if (rc != VINF_SUCCESS)
805 return rc;
806
807 do
808 {
809 rc = RTSemEventCreate(&m_eventQueueEvent);
810 if (RT_FAILURE(rc))
811 break;
812
813 rc = RTCritSectInit(&m_eventQueueCS);
814 if (RT_FAILURE(rc))
815 break;
816
817 rc = RTCritSectInit(&m_dataCS);
818 if (RT_FAILURE(rc))
819 break;
820
821 /*
822 * Enough screens configured in the x11 server?
823 */
824 if ((int)uScreenID > ScreenCount(m_pDisplay))
825 {
826 rc = VERR_INVALID_PARAMETER;
827 break;
828 }
829#if 0
830 /* Get the screen number from the x11 server. */
831 pDrag->screen = ScreenOfDisplay(m_pDisplay, uScreenID);
832 if (!pDrag->screen)
833 {
834 rc = VERR_GENERAL_FAILURE;
835 break;
836 }
837#endif
838 m_screenID = uScreenID;
839
840 /* Now query the corresponding root window of this screen. */
841 m_wndRoot = RootWindow(m_pDisplay, m_screenID);
842 if (!m_wndRoot)
843 {
844 rc = VERR_GENERAL_FAILURE;
845 break;
846 }
847
848 /*
849 * Create an invisible window which will act as proxy for the DnD
850 * operation. This window will be used for both the GH and HG
851 * direction.
852 */
853 XSetWindowAttributes attr;
854 RT_ZERO(attr);
855 attr.event_mask = EnterWindowMask | LeaveWindowMask
856 | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask;
857 attr.override_redirect = True;
858 attr.do_not_propagate_mask = NoEventMask;
859#ifdef VBOX_DND_DEBUG_WND
860 attr.background_pixel = XWhitePixel(m_pDisplay, m_screenID);
861 attr.border_pixel = XBlackPixel(m_pDisplay, m_screenID);
862 m_wndProxy.hWnd = XCreateWindow(m_pDisplay, m_wndRoot /* Parent */,
863 100, 100, /* Position */
864 100, 100, /* Width + height */
865 2, /* Border width */
866 CopyFromParent, /* Depth */
867 InputOutput, /* Class */
868 CopyFromParent, /* Visual */
869 CWBackPixel
870 | CWBorderPixel
871 | CWOverrideRedirect
872 | CWDontPropagate, /* Value mask */
873 &attr); /* Attributes for value mask */
874#else
875 m_wndProxy.hWnd = XCreateWindow(m_pDisplay, m_wndRoot /* Parent */,
876 0, 0, /* Position */
877 1, 1, /* Width + height */
878 0, /* Border width */
879 CopyFromParent, /* Depth */
880 InputOnly, /* Class */
881 CopyFromParent, /* Visual */
882 CWOverrideRedirect | CWDontPropagate, /* Value mask */
883 &attr); /* Attributes for value mask */
884#endif
885 if (!m_wndProxy.hWnd)
886 {
887 VBClLogError("Error creating proxy window\n");
888 rc = VERR_GENERAL_FAILURE;
889 break;
890 }
891
892 rc = m_wndProxy.init(m_pDisplay);
893 if (RT_FAILURE(rc))
894 {
895 VBClLogError("Error initializing proxy window, rc=%Rrc\n", rc);
896 break;
897 }
898
899#ifdef VBOX_DND_DEBUG_WND
900 XFlush(m_pDisplay);
901 XMapWindow(m_pDisplay, m_wndProxy.hWnd);
902 XRaiseWindow(m_pDisplay, m_wndProxy.hWnd);
903 XFlush(m_pDisplay);
904#endif
905 VBClLogInfo("Proxy window=%RU32, root window=%RU32 ...\n", m_wndProxy.hWnd, m_wndRoot);
906
907 /* Set the window's name for easier lookup. */
908 XStoreName(m_pDisplay, m_wndProxy.hWnd, "VBoxClientWndDnD");
909
910 /* Make the new window Xdnd aware. */
911 Atom atmVer = VBOX_XDND_VERSION;
912 XChangeProperty(m_pDisplay, m_wndProxy.hWnd, xAtom(XA_XdndAware), XA_ATOM, 32, PropModeReplace,
913 reinterpret_cast<unsigned char*>(&atmVer), 1);
914 } while (0);
915
916 if (RT_SUCCESS(rc))
917 {
918 reset();
919 }
920 else
921 VBClLogError("Initializing drag instance for screen %RU32 failed with rc=%Rrc\n", uScreenID, rc);
922
923 LogFlowFuncLeaveRC(rc);
924 return rc;
925}
926
927/**
928 * Callback handler for a generic client message from a window.
929 *
930 * @return IPRT status code.
931 * @param e X11 event to handle.
932 */
933int DragInstance::onX11ClientMessage(const XEvent &e)
934{
935 AssertReturn(e.type == ClientMessage, VERR_INVALID_PARAMETER);
936
937 LogFlowThisFunc(("mode=%RU32, state=%RU32\n", m_enmMode, m_enmState));
938 LogFlowThisFunc(("Event wnd=%#x, msg=%s\n", e.xclient.window, xAtomToString(e.xclient.message_type).c_str()));
939
940 int rc = VINF_SUCCESS;
941
942 switch (m_enmMode)
943 {
944 case HG:
945 {
946 /*
947 * Client messages are used to inform us about the status of a XdndAware
948 * window, in response of some events we send to them.
949 */
950 if (e.xclient.message_type == xAtom(XA_XdndStatus))
951 {
952 Window wndTarget = static_cast<Window>(e.xclient.data.l[XdndStatusWindow]);
953
954 /* Does the target accept the drop? */
955 const bool fAcceptDrop = e.xclient.data.l[XdndStatusFlags] & VBOX_XDND_STATUS_FLAG_ACCEPT;
956 /* Does the target want XdndPosition messages? */
957 const bool fWantsPosition = e.xclient.data.l[XdndStatusFlags] & VBOX_XDND_STATUS_FLAG_WANTS_POS;
958 RT_NOREF(fWantsPosition);
959
960 char *pszWndName = wndX11GetNameA(m_wndCur);
961 AssertPtr(pszWndName);
962
963 /*
964 * The XdndStatus message tell us if the window will accept the DnD
965 * event and with which action. We immediately send this info down to
966 * the host as a response of a previous DnD message.
967 */
968 RTCString strActions = xAtomToString(e.xclient.data.l[XdndStatusAction]);
969
970 VBClLogInfo("Target window %#x ('%s') %s accept data with actions '%s'\n",
971 wndTarget, pszWndName, fAcceptDrop ? "does" : "does not", strActions.c_str());
972
973 const uint16_t x = RT_HI_U16((uint32_t)e.xclient.data.l[XdndStatusNoMsgXY]);
974 const uint16_t y = RT_LO_U16((uint32_t)e.xclient.data.l[XdndStatusNoMsgXY]);
975 const uint16_t cx = RT_HI_U16((uint32_t)e.xclient.data.l[XdndStatusNoMsgWH]);
976 const uint16_t cy = RT_LO_U16((uint32_t)e.xclient.data.l[XdndStatusNoMsgWH]);
977
978 if (cx && cy)
979 {
980 VBClLogInfo("Target window %#x ('%s') reported dead area at %RU16,%RU16 (%RU16 x %RU16)\n",
981 wndTarget, pszWndName, x, y, cx, cy);
982 /** @todo Save dead area and don't send XdndPosition messages anymore into it. */
983 }
984
985 if (m_wndCur == wndTarget)
986 {
987 VBOXDNDACTION dndAction = VBOX_DND_ACTION_IGNORE; /* Default is ignoring. */
988 /** @todo Compare this with the allowed actions. */
989 if (fAcceptDrop)
990 dndAction = toHGCMAction(static_cast<Atom>(e.xclient.data.l[XdndStatusAction]));
991
992 rc = VbglR3DnDHGSendAckOp(&m_dndCtx, dndAction);
993 }
994 else
995 VBClLogInfo("Target window %#x ('%s') is not our current window, skipping\n", wndTarget, pszWndName);
996
997 RTStrFree(pszWndName);
998 }
999 else if (e.xclient.message_type == xAtom(XA_XdndFinished))
1000 {
1001 Window wndTarget = static_cast<Window>(e.xclient.data.l[XdndFinishedWindow]);
1002
1003 const bool fSucceeded = e.xclient.data.l[XdndFinishedFlags] & VBOX_XDND_FINISHED_FLAG_SUCCEEDED;
1004
1005 char *pszWndName = wndX11GetNameA(wndTarget);
1006 AssertPtr(pszWndName);
1007
1008 const char *pcszAction = xAtomToString(e.xclient.data.l[XdndFinishedAction]).c_str();
1009
1010 /* This message is sent on an un/successful DnD drop request. */
1011 LogFlowThisFunc(("XA_XdndFinished: wnd=%#x ('%s'), success=%RTbool, action=%s\n",
1012 wndTarget, pszWndName, fSucceeded, pcszAction));
1013
1014 VBClLogInfo("Target window %#x ('%s') has %s the data with action '%s'\n",
1015 wndTarget, pszWndName, fSucceeded ? "accepted" : "rejected", pcszAction ? "<None>" : pcszAction);
1016
1017 RTStrFree(pszWndName);
1018
1019 reset();
1020 }
1021 else
1022 {
1023 LogFlowThisFunc(("Unhandled client message '%s'\n", xAtomToString(e.xclient.message_type).c_str()));
1024 rc = VERR_NOT_SUPPORTED;
1025 }
1026
1027 break;
1028 }
1029
1030 case Unknown: /* Mode not set (yet). */
1031 case GH:
1032 {
1033 /*
1034 * This message marks the beginning of a new drag and drop
1035 * operation on the guest.
1036 */
1037 if (e.xclient.message_type == xAtom(XA_XdndEnter))
1038 {
1039 LogFlowFunc(("XA_XdndEnter\n"));
1040
1041 /*
1042 * Get the window which currently has the XA_XdndSelection
1043 * bit set.
1044 */
1045 Window wndSelection = XGetSelectionOwner(m_pDisplay, xAtom(XA_XdndSelection));
1046
1047 char *pszWndName = wndX11GetNameA(wndSelection);
1048 AssertPtr(pszWndName);
1049 LogFlowThisFunc(("wndSelection=%RU32 ('%s'), wndProxy=%RU32\n", wndSelection, pszWndName, m_wndProxy.hWnd));
1050 RTStrFree(pszWndName);
1051
1052 mouseButtonSet(m_wndProxy.hWnd, -1, -1, 1, true /* fPress */);
1053
1054 /*
1055 * Update our state and the window handle to process.
1056 */
1057 int rc2 = RTCritSectEnter(&m_dataCS);
1058 if (RT_SUCCESS(rc2))
1059 {
1060 m_wndCur = wndSelection;
1061 m_curVer = e.xclient.data.l[XdndEnterFlags] >> XdndEnterVersionRShift;
1062 Assert(m_wndCur == (Window)e.xclient.data.l[XdndEnterWindow]); /* Source window. */
1063#ifdef DEBUG
1064 XWindowAttributes xwa;
1065 XGetWindowAttributes(m_pDisplay, m_wndCur, &xwa);
1066 LogFlowThisFunc(("wndCur=%#x, x=%d, y=%d, width=%d, height=%d\n", m_wndCur, xwa.x, xwa.y, xwa.width, xwa.height));
1067#endif
1068 /*
1069 * Retrieve supported formats.
1070 */
1071
1072 /* Check if the MIME types are in the message itself or if we need
1073 * to fetch the XdndTypeList property from the window. */
1074 bool fMoreTypes = e.xclient.data.l[XdndEnterFlags] & XdndEnterMoreTypesFlag;
1075 LogFlowThisFunc(("XdndVer=%d, fMoreTypes=%RTbool\n", m_curVer, fMoreTypes));
1076 if (!fMoreTypes)
1077 {
1078 /* Only up to 3 format types supported. */
1079 /* Start with index 2 (first item). */
1080 for (int i = 2; i < 5; i++)
1081 {
1082 LogFlowThisFunc(("\t%s\n", gX11->xAtomToString(e.xclient.data.l[i]).c_str()));
1083 m_lstAtomFormats.append(e.xclient.data.l[i]);
1084 }
1085 }
1086 else
1087 {
1088 /* More than 3 format types supported. */
1089 rc = wndXDnDGetFormatList(wndSelection, m_lstAtomFormats);
1090 }
1091
1092 /*
1093 * Retrieve supported actions.
1094 */
1095 if (RT_SUCCESS(rc))
1096 {
1097 if (m_curVer >= 2) /* More than one action allowed since protocol version 2. */
1098 {
1099 rc = wndXDnDGetActionList(wndSelection, m_lstAtomActions);
1100 }
1101 else /* Only "copy" action allowed on legacy applications. */
1102 m_lstAtomActions.append(XA_XdndActionCopy);
1103 }
1104
1105 if (RT_SUCCESS(rc))
1106 {
1107 m_enmMode = GH;
1108 m_enmState = Dragging;
1109 }
1110
1111 RTCritSectLeave(&m_dataCS);
1112 }
1113 }
1114 else if ( e.xclient.message_type == xAtom(XA_XdndPosition)
1115 && m_wndCur == static_cast<Window>(e.xclient.data.l[XdndPositionWindow]))
1116 {
1117 if (m_enmState != Dragging) /* Wrong mode? Bail out. */
1118 {
1119 reset();
1120 break;
1121 }
1122#ifdef LOG_ENABLED
1123 int32_t iPos = e.xclient.data.l[XdndPositionXY];
1124 Atom atmAction = m_curVer >= 2 /* Actions other than "copy" or only supported since protocol version 2. */
1125 ? e.xclient.data.l[XdndPositionAction] : xAtom(XA_XdndActionCopy);
1126 LogFlowThisFunc(("XA_XdndPosition: wndProxy=%RU32, wndCur=%RU32, x=%RI32, y=%RI32, strAction=%s\n",
1127 m_wndProxy.hWnd, m_wndCur, RT_HIWORD(iPos), RT_LOWORD(iPos),
1128 xAtomToString(atmAction).c_str()));
1129#endif
1130
1131 bool fAcceptDrop = true;
1132
1133 /* Reply with a XdndStatus message to tell the source whether
1134 * the data can be dropped or not. */
1135 XClientMessageEvent m;
1136 RT_ZERO(m);
1137 m.type = ClientMessage;
1138 m.display = m_pDisplay;
1139 m.window = e.xclient.data.l[XdndPositionWindow];
1140 m.message_type = xAtom(XA_XdndStatus);
1141 m.format = 32;
1142 m.data.l[XdndStatusWindow] = m_wndProxy.hWnd;
1143 m.data.l[XdndStatusFlags] = fAcceptDrop ? RT_BIT(0) : 0; /* Whether to accept the drop or not. */
1144
1145 /* We don't want any new XA_XdndPosition messages while being
1146 * in our proxy window. */
1147 m.data.l[XdndStatusNoMsgXY] = RT_MAKE_U32(m_wndProxy.iY, m_wndProxy.iX);
1148 m.data.l[XdndStatusNoMsgWH] = RT_MAKE_U32(m_wndProxy.iHeight, m_wndProxy.iWidth);
1149
1150 /** @todo Handle default action! */
1151 m.data.l[XdndStatusAction] = fAcceptDrop ? toAtomAction(VBOX_DND_ACTION_COPY) : None;
1152
1153 int xRc = XSendEvent(m_pDisplay, e.xclient.data.l[XdndPositionWindow],
1154 False /* Propagate */, NoEventMask, reinterpret_cast<XEvent *>(&m));
1155 if (xRc == 0)
1156 VBClLogError("Error sending position XA_XdndStatus event to current window=%#x: %s\n",
1157 m_wndCur, gX11->xErrorToString(xRc).c_str());
1158 }
1159 else if ( e.xclient.message_type == xAtom(XA_XdndLeave)
1160 && m_wndCur == static_cast<Window>(e.xclient.data.l[XdndLeaveWindow]))
1161 {
1162 LogFlowThisFunc(("XA_XdndLeave\n"));
1163 VBClLogInfo("Guest to host transfer canceled by the guest source window\n");
1164
1165 /* Start over. */
1166 reset();
1167 }
1168 else if ( e.xclient.message_type == xAtom(XA_XdndDrop)
1169 && m_wndCur == static_cast<Window>(e.xclient.data.l[XdndDropWindow]))
1170 {
1171 LogFlowThisFunc(("XA_XdndDrop\n"));
1172
1173 if (m_enmState != Dropped) /* Wrong mode? Bail out. */
1174 {
1175 /* Can occur when dragging from guest->host, but then back in to the guest again. */
1176 VBClLogInfo("Could not drop on own proxy window\n"); /* Not fatal. */
1177
1178 /* Let the source know. */
1179 rc = m_wndProxy.sendFinished(m_wndCur, VBOX_DND_ACTION_IGNORE);
1180
1181 /* Start over. */
1182 reset();
1183 break;
1184 }
1185
1186 m_eventQueueList.append(e);
1187 rc = RTSemEventSignal(m_eventQueueEvent);
1188 }
1189 else /* Unhandled event, abort. */
1190 {
1191 VBClLogInfo("Unhandled event from wnd=%#x, msg=%s\n", e.xclient.window, xAtomToString(e.xclient.message_type).c_str());
1192
1193 /* Let the source know. */
1194 rc = m_wndProxy.sendFinished(m_wndCur, VBOX_DND_ACTION_IGNORE);
1195
1196 /* Start over. */
1197 reset();
1198 }
1199 break;
1200 }
1201
1202 default:
1203 {
1204 AssertMsgFailed(("Drag and drop mode not implemented: %RU32\n", m_enmMode));
1205 rc = VERR_NOT_IMPLEMENTED;
1206 break;
1207 }
1208 }
1209
1210 LogFlowThisFunc(("Returning rc=%Rrc\n", rc));
1211 return rc;
1212}
1213
1214int DragInstance::onX11MotionNotify(const XEvent &e)
1215{
1216 RT_NOREF1(e);
1217 LogFlowThisFunc(("mode=%RU32, state=%RU32\n", m_enmMode, m_enmState));
1218
1219 return VINF_SUCCESS;
1220}
1221
1222/**
1223 * Callback handler for being notified if some other window now
1224 * is the owner of the current selection.
1225 *
1226 * @return IPRT status code.
1227 * @param e X11 event to handle.
1228 *
1229 * @remark
1230 */
1231int DragInstance::onX11SelectionClear(const XEvent &e)
1232{
1233 RT_NOREF1(e);
1234 LogFlowThisFunc(("mode=%RU32, state=%RU32\n", m_enmMode, m_enmState));
1235
1236 return VINF_SUCCESS;
1237}
1238
1239/**
1240 * Callback handler for a XDnD selection notify from a window. This is needed
1241 * to let the us know if a certain window has drag'n drop data to share with us,
1242 * e.g. our proxy window.
1243 *
1244 * @return IPRT status code.
1245 * @param e X11 event to handle.
1246 */
1247int DragInstance::onX11SelectionNotify(const XEvent &e)
1248{
1249 AssertReturn(e.type == SelectionNotify, VERR_INVALID_PARAMETER);
1250
1251 LogFlowThisFunc(("mode=%RU32, state=%RU32\n", m_enmMode, m_enmState));
1252
1253 int rc;
1254
1255 switch (m_enmMode)
1256 {
1257 case GH:
1258 {
1259 if (m_enmState == Dropped)
1260 {
1261 m_eventQueueList.append(e);
1262 rc = RTSemEventSignal(m_eventQueueEvent);
1263 }
1264 else
1265 rc = VERR_WRONG_ORDER;
1266 break;
1267 }
1268
1269 default:
1270 {
1271 LogFlowThisFunc(("Unhandled: wnd=%#x, msg=%s\n",
1272 e.xclient.data.l[0], xAtomToString(e.xclient.message_type).c_str()));
1273 rc = VERR_INVALID_STATE;
1274 break;
1275 }
1276 }
1277
1278 LogFlowThisFunc(("Returning rc=%Rrc\n", rc));
1279 return rc;
1280}
1281
1282/**
1283 * Callback handler for a XDnD selection request from a window. This is needed
1284 * to retrieve the data required to complete the actual drag'n drop operation.
1285 *
1286 * @returns IPRT status code.
1287 * @param evReq X11 event to handle.
1288 */
1289int DragInstance::onX11SelectionRequest(const XEvent &evReq)
1290{
1291 AssertReturn(evReq.type == SelectionRequest, VERR_INVALID_PARAMETER);
1292
1293 const XSelectionRequestEvent *pEvReq = &evReq.xselectionrequest;
1294
1295 LogFlowThisFunc(("mode=%RU32, state=%RU32\n", m_enmMode, m_enmState));
1296 LogFlowThisFunc(("Event owner=%#x, requestor=%#x, selection=%s, target=%s, prop=%s, time=%u\n",
1297 pEvReq->owner,
1298 pEvReq->requestor,
1299 xAtomToString(pEvReq->selection).c_str(),
1300 xAtomToString(pEvReq->target).c_str(),
1301 xAtomToString(pEvReq->property).c_str(),
1302 pEvReq->time));
1303 int rc;
1304
1305 switch (m_enmMode)
1306 {
1307 case HG:
1308 {
1309 rc = VINF_SUCCESS;
1310
1311 char *pszWndName = wndX11GetNameA(pEvReq->requestor);
1312 AssertPtr(pszWndName);
1313
1314 /*
1315 * Start by creating a refusal selection notify message.
1316 * That way we only need to care for the success case.
1317 */
1318
1319 XEvent evResp;
1320 RT_ZERO(evResp);
1321
1322 XSelectionEvent *pEvResp = &evResp.xselection;
1323
1324 pEvResp->type = SelectionNotify;
1325 pEvResp->display = pEvReq->display;
1326 pEvResp->requestor = pEvReq->requestor;
1327 pEvResp->selection = pEvReq->selection;
1328 pEvResp->target = pEvReq->target;
1329 pEvResp->property = None; /* "None" means refusal. */
1330 pEvResp->time = pEvReq->time;
1331
1332#ifdef DEBUG
1333 LogFlowFunc(("Supported formats:\n"));
1334 for (size_t i = 0; i < m_lstAtomFormats.size(); i++)
1335 LogFlowFunc(("\t%s\n", xAtomToString(m_lstAtomFormats.at(i)).c_str()));
1336#endif
1337 /* Is the requestor asking for the possible MIME types? */
1338 if (pEvReq->target == xAtom(XA_TARGETS))
1339 {
1340 VBClLogInfo("Target window %#x ('%s') asking for target list\n", pEvReq->requestor, pszWndName);
1341
1342 /* If so, set the window property with the formats on the requestor
1343 * window. */
1344 rc = wndXDnDSetFormatList(pEvReq->requestor, pEvReq->property, m_lstAtomFormats);
1345 if (RT_SUCCESS(rc))
1346 pEvResp->property = pEvReq->property;
1347 }
1348 /* Is the requestor asking for a specific MIME type (we support)? */
1349 else if (m_lstAtomFormats.contains(pEvReq->target))
1350 {
1351 VBClLogInfo("Target window %#x ('%s') is asking for data as '%s'\n",
1352 pEvReq->requestor, pszWndName, xAtomToString(pEvReq->target).c_str());
1353
1354#ifdef VBOX_WITH_DRAG_AND_DROP_PROMISES
1355# error "Implement me!"
1356#else
1357 /* Did we not drop our stuff to the guest yet? Bail out. */
1358 if (m_enmState != Dropped)
1359 {
1360 VBClLogError("Data not dropped by the host on the guest yet (client state %RU32, mode %RU32), refusing selection request by guest\n",
1361 m_enmState, m_enmMode);
1362 }
1363 /* Did we not store the requestor's initial selection request yet? Then do so now. */
1364 else
1365 {
1366#endif /* VBOX_WITH_DRAG_AND_DROP_PROMISES */
1367 /* Get the data format the requestor wants from us. */
1368 RTCString strFormat = xAtomToString(pEvReq->target);
1369 Assert(strFormat.isNotEmpty());
1370 VBClLogInfo("Target window=%#x requested data from host as '%s', rc=%Rrc\n",
1371 pEvReq->requestor, strFormat.c_str(), rc);
1372
1373 /* Make a copy of the MIME data to be passed back. The X server will be become
1374 * the new owner of that data, so no deletion needed. */
1375 /** @todo Do we need to do some more conversion here? XConvertSelection? */
1376 void *pvData = RTMemDup(m_pvSelReqData, m_cbSelReqData);
1377 uint32_t cbData = m_cbSelReqData;
1378
1379 /* Always return the requested property. */
1380 evResp.xselection.property = pEvReq->property;
1381
1382 /* Note: Always seems to return BadRequest. Seems fine. */
1383 int xRc = XChangeProperty(pEvResp->display, pEvResp->requestor, pEvResp->property,
1384 pEvResp->target, 8, PropModeReplace,
1385 reinterpret_cast<const unsigned char*>(pvData), cbData);
1386
1387 LogFlowFunc(("Changing property '%s' (target '%s') of window=%RU32: %s\n",
1388 xAtomToString(pEvReq->property).c_str(),
1389 xAtomToString(pEvReq->target).c_str(),
1390 pEvReq->requestor,
1391 gX11->xErrorToString(xRc).c_str()));
1392 RT_NOREF(xRc);
1393#ifndef VBOX_WITH_DRAG_AND_DROP_PROMISES
1394 }
1395#endif
1396 }
1397 /* Anything else. */
1398 else
1399 {
1400 VBClLogError("Refusing unknown command/format '%s' of wnd=%#x ('%s')\n",
1401 xAtomToString(pEvReq->target).c_str(), pEvReq->requestor, pszWndName);
1402 rc = VERR_NOT_SUPPORTED;
1403 }
1404
1405 LogFlowThisFunc(("Offering type '%s', property '%s' to wnd=%#x ...\n",
1406 xAtomToString(pEvReq->target).c_str(),
1407 xAtomToString(pEvReq->property).c_str(), pEvReq->requestor));
1408
1409 int xRc = XSendEvent(pEvReq->display, pEvReq->requestor, True /* Propagate */, 0, &evResp);
1410 if (xRc == 0)
1411 VBClLogError("Error sending SelectionNotify(1) event to wnd=%#x: %s\n",
1412 pEvReq->requestor, gX11->xErrorToString(xRc).c_str());
1413 XFlush(pEvReq->display);
1414
1415 if (pszWndName)
1416 RTStrFree(pszWndName);
1417 break;
1418 }
1419
1420 default:
1421 rc = VERR_INVALID_STATE;
1422 break;
1423 }
1424
1425 LogFlowThisFunc(("Returning rc=%Rrc\n", rc));
1426 return rc;
1427}
1428
1429/**
1430 * Handles X11 events, called by x11EventThread.
1431 *
1432 * @returns IPRT status code.
1433 * @param e X11 event to handle.
1434 */
1435int DragInstance::onX11Event(const XEvent &e)
1436{
1437 int rc;
1438
1439 LogFlowThisFunc(("X11 event, type=%d\n", e.type));
1440 switch (e.type)
1441 {
1442 /*
1443 * This can happen if a guest->host drag operation
1444 * goes back from the host to the guest. This is not what
1445 * we want and thus resetting everything.
1446 */
1447 case ButtonPress:
1448 RT_FALL_THROUGH();
1449 case ButtonRelease:
1450 {
1451 VBClLogInfo("Mouse button %s\n", e.type == ButtonPress ? "pressed" : "released");
1452
1453 reset();
1454
1455 rc = VINF_SUCCESS;
1456 break;
1457 }
1458
1459 case ClientMessage:
1460 rc = onX11ClientMessage(e);
1461 break;
1462
1463 case SelectionClear:
1464 rc = onX11SelectionClear(e);
1465 break;
1466
1467 case SelectionNotify:
1468 rc = onX11SelectionNotify(e);
1469 break;
1470
1471 case SelectionRequest:
1472 rc = onX11SelectionRequest(e);
1473 break;
1474
1475 case MotionNotify:
1476 rc = onX11MotionNotify(e);
1477 break;
1478
1479 default:
1480 rc = VERR_NOT_IMPLEMENTED;
1481 break;
1482 }
1483
1484 LogFlowThisFunc(("rc=%Rrc\n", rc));
1485 return rc;
1486}
1487
1488int DragInstance::waitForStatusChange(uint32_t enmState, RTMSINTERVAL uTimeoutMS /* = 30000 */)
1489{
1490 const uint64_t uiStart = RTTimeMilliTS();
1491 volatile uint32_t enmCurState;
1492
1493 int rc = VERR_TIMEOUT;
1494
1495 LogFlowFunc(("enmState=%RU32, uTimeoutMS=%RU32\n", enmState, uTimeoutMS));
1496
1497 do
1498 {
1499 enmCurState = ASMAtomicReadU32(&m_enmState);
1500 if (enmCurState == enmState)
1501 {
1502 rc = VINF_SUCCESS;
1503 break;
1504 }
1505 }
1506 while (RTTimeMilliTS() - uiStart < uTimeoutMS);
1507
1508 LogFlowThisFunc(("Returning %Rrc\n", rc));
1509 return rc;
1510}
1511
1512#ifdef VBOX_WITH_DRAG_AND_DROP_GH
1513/**
1514 * Waits for an X11 event of a specific type.
1515 *
1516 * @returns IPRT status code.
1517 * @param evX Reference where to store the event into.
1518 * @param iType Event type to wait for.
1519 * @param uTimeoutMS Timeout (in ms) to wait for the event.
1520 */
1521bool DragInstance::waitForX11Msg(XEvent &evX, int iType, RTMSINTERVAL uTimeoutMS /* = 100 */)
1522{
1523 LogFlowThisFunc(("iType=%d, uTimeoutMS=%RU32, cEventQueue=%zu\n", iType, uTimeoutMS, m_eventQueueList.size()));
1524
1525 bool fFound = false;
1526 const uint64_t uiStart = RTTimeMilliTS();
1527
1528 do
1529 {
1530 /* Check if there is a client message in the queue. */
1531 for (size_t i = 0; i < m_eventQueueList.size(); i++)
1532 {
1533 int rc2 = RTCritSectEnter(&m_eventQueueCS);
1534 if (RT_SUCCESS(rc2))
1535 {
1536 XEvent e = m_eventQueueList.at(i).m_Event;
1537
1538 fFound = e.type == iType;
1539 if (fFound)
1540 {
1541 m_eventQueueList.removeAt(i);
1542 evX = e;
1543 }
1544
1545 rc2 = RTCritSectLeave(&m_eventQueueCS);
1546 AssertRC(rc2);
1547
1548 if (fFound)
1549 break;
1550 }
1551 }
1552
1553 if (fFound)
1554 break;
1555
1556 int rc2 = RTSemEventWait(m_eventQueueEvent, 25 /* ms */);
1557 if ( RT_FAILURE(rc2)
1558 && rc2 != VERR_TIMEOUT)
1559 {
1560 LogFlowFunc(("Waiting failed with rc=%Rrc\n", rc2));
1561 break;
1562 }
1563 }
1564 while (RTTimeMilliTS() - uiStart < uTimeoutMS);
1565
1566 LogFlowThisFunc(("Returning fFound=%RTbool, msRuntime=%RU64\n", fFound, RTTimeMilliTS() - uiStart));
1567 return fFound;
1568}
1569
1570/**
1571 * Waits for an X11 client message of a specific type.
1572 *
1573 * @returns IPRT status code.
1574 * @param evMsg Reference where to store the event into.
1575 * @param aType Event type to wait for.
1576 * @param uTimeoutMS Timeout (in ms) to wait for the event.
1577 */
1578bool DragInstance::waitForX11ClientMsg(XClientMessageEvent &evMsg, Atom aType,
1579 RTMSINTERVAL uTimeoutMS /* = 100 */)
1580{
1581 LogFlowThisFunc(("aType=%s, uTimeoutMS=%RU32, cEventQueue=%zu\n",
1582 xAtomToString(aType).c_str(), uTimeoutMS, m_eventQueueList.size()));
1583
1584 bool fFound = false;
1585 const uint64_t uiStart = RTTimeMilliTS();
1586 do
1587 {
1588 /* Check if there is a client message in the queue. */
1589 for (size_t i = 0; i < m_eventQueueList.size(); i++)
1590 {
1591 int rc2 = RTCritSectEnter(&m_eventQueueCS);
1592 if (RT_SUCCESS(rc2))
1593 {
1594 XEvent e = m_eventQueueList.at(i).m_Event;
1595 if ( e.type == ClientMessage
1596 && e.xclient.message_type == aType)
1597 {
1598 m_eventQueueList.removeAt(i);
1599 evMsg = e.xclient;
1600
1601 fFound = true;
1602 }
1603
1604 if (e.type == ClientMessage)
1605 {
1606 LogFlowThisFunc(("Client message: Type=%ld (%s)\n",
1607 e.xclient.message_type, xAtomToString(e.xclient.message_type).c_str()));
1608 }
1609 else
1610 LogFlowThisFunc(("X message: Type=%d\n", e.type));
1611
1612 rc2 = RTCritSectLeave(&m_eventQueueCS);
1613 AssertRC(rc2);
1614
1615 if (fFound)
1616 break;
1617 }
1618 }
1619
1620 if (fFound)
1621 break;
1622
1623 int rc2 = RTSemEventWait(m_eventQueueEvent, 25 /* ms */);
1624 if ( RT_FAILURE(rc2)
1625 && rc2 != VERR_TIMEOUT)
1626 {
1627 LogFlowFunc(("Waiting failed with rc=%Rrc\n", rc2));
1628 break;
1629 }
1630 }
1631 while (RTTimeMilliTS() - uiStart < uTimeoutMS);
1632
1633 LogFlowThisFunc(("Returning fFound=%RTbool, msRuntime=%RU64\n", fFound, RTTimeMilliTS() - uiStart));
1634 return fFound;
1635}
1636#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
1637
1638/*
1639 * Host -> Guest
1640 */
1641
1642/**
1643 * Host -> Guest: Event signalling that the host's (mouse) cursor just entered the VM's (guest's) display
1644 * area.
1645 *
1646 * @returns IPRT status code.
1647 * @param lstFormats List of supported formats from the host.
1648 * @param dndListActionsAllowed (ORed) List of supported actions from the host.
1649 */
1650int DragInstance::hgEnter(const RTCList<RTCString> &lstFormats, uint32_t dndListActionsAllowed)
1651{
1652 LogFlowThisFunc(("mode=%RU32, state=%RU32\n", m_enmMode, m_enmState));
1653
1654 if (m_enmMode != Unknown)
1655 return VERR_INVALID_STATE;
1656
1657 reset();
1658
1659#ifdef DEBUG
1660 LogFlowThisFunc(("dndListActionsAllowed=0x%x, lstFormats=%zu: ", dndListActionsAllowed, lstFormats.size()));
1661 for (size_t i = 0; i < lstFormats.size(); ++i)
1662 LogFlow(("'%s' ", lstFormats.at(i).c_str()));
1663 LogFlow(("\n"));
1664#endif
1665
1666 int rc;
1667
1668 do
1669 {
1670 /* Check if the VM session has changed and reconnect to the HGCM service if necessary. */
1671 rc = checkForSessionChange();
1672 AssertRCBreak(rc);
1673
1674 /* Append all actual (MIME) formats we support to the list.
1675 * These must come last, after the default Atoms above. */
1676 rc = appendFormatsToList(lstFormats, m_lstAtomFormats);
1677 AssertRCBreak(rc);
1678
1679 rc = wndXDnDSetFormatList(m_wndProxy.hWnd, xAtom(XA_XdndTypeList), m_lstAtomFormats);
1680 AssertRCBreak(rc);
1681
1682 /* Announce the possible actions. */
1683 VBoxDnDAtomList lstActions;
1684 rc = toAtomActions(dndListActionsAllowed, lstActions);
1685 AssertRCBreak(rc);
1686
1687 rc = wndXDnDSetActionList(m_wndProxy.hWnd, lstActions);
1688 AssertRCBreak(rc);
1689
1690 /* Set the DnD selection owner to our window. */
1691 /** @todo Don't use CurrentTime -- according to ICCCM section 2.1. */
1692 XSetSelectionOwner(m_pDisplay, xAtom(XA_XdndSelection), m_wndProxy.hWnd, CurrentTime);
1693
1694 m_enmMode = HG;
1695 m_enmState = Dragging;
1696
1697 } while (0);
1698
1699 LogFlowFuncLeaveRC(rc);
1700 return rc;
1701}
1702
1703/**
1704 * Host -> Guest: Event signalling that the host's (mouse) cursor has left the VM's (guest's)
1705 * display area.
1706 */
1707int DragInstance::hgLeave(void)
1708{
1709 if (m_enmMode == HG) /* Only reset if in the right operation mode. */
1710 reset();
1711
1712 return VINF_SUCCESS;
1713}
1714
1715/**
1716 * Host -> Guest: Event signalling that the host's (mouse) cursor has been moved within the VM's
1717 * (guest's) display area.
1718 *
1719 * @returns IPRT status code.
1720 * @param uPosX Relative X position within the guest's display area.
1721 * @param uPosY Relative Y position within the guest's display area.
1722 * @param dndActionDefault Default action the host wants to perform on the guest
1723 * as soon as the operation successfully finishes.
1724 */
1725int DragInstance::hgMove(uint32_t uPosX, uint32_t uPosY, VBOXDNDACTION dndActionDefault)
1726{
1727 LogFlowThisFunc(("mode=%RU32, state=%RU32\n", m_enmMode, m_enmState));
1728 LogFlowThisFunc(("uPosX=%RU32, uPosY=%RU32, dndActionDefault=0x%x\n", uPosX, uPosY, dndActionDefault));
1729
1730 if ( m_enmMode != HG
1731 || m_enmState != Dragging)
1732 {
1733 return VERR_INVALID_STATE;
1734 }
1735
1736 int rc = VINF_SUCCESS;
1737 int xRc = Success;
1738
1739 /* Move the mouse cursor within the guest. */
1740 mouseCursorMove(uPosX, uPosY);
1741
1742 long newVer = -1; /* This means the current window is _not_ XdndAware. */
1743
1744 /* Search for the application window below the cursor. */
1745 Window wndCursor = gX11->applicationWindowBelowCursor(m_wndRoot);
1746 if (wndCursor != None)
1747 {
1748 /* Temp stuff for the XGetWindowProperty call. */
1749 Atom atmTmp;
1750 int fmt;
1751 unsigned long cItems, cbRemaining;
1752 unsigned char *pcData = NULL;
1753
1754 /* Query the XdndAware property from the window. We are interested in
1755 * the version and if it is XdndAware at all. */
1756 xRc = XGetWindowProperty(m_pDisplay, wndCursor, xAtom(XA_XdndAware),
1757 0, 2, False, AnyPropertyType,
1758 &atmTmp, &fmt, &cItems, &cbRemaining, &pcData);
1759 if (xRc != Success)
1760 {
1761 VBClLogError("Error getting properties of cursor window=%#x: %s\n", wndCursor, gX11->xErrorToString(xRc).c_str());
1762 }
1763 else
1764 {
1765 if (pcData == NULL || fmt != 32 || cItems != 1)
1766 {
1767 /** @todo Do we need to deal with this? */
1768 VBClLogError("Wrong window properties for window %#x: pcData=%#x, iFmt=%d, cItems=%ul\n",
1769 wndCursor, pcData, fmt, cItems);
1770 }
1771 else
1772 {
1773 /* Get the current window's Xdnd version. */
1774 newVer = reinterpret_cast<long *>(pcData)[0];
1775 }
1776
1777 XFree(pcData);
1778 }
1779 }
1780
1781#ifdef DEBUG
1782 char *pszNameCursor = wndX11GetNameA(wndCursor);
1783 AssertPtr(pszNameCursor);
1784 char *pszNameCur = wndX11GetNameA(m_wndCur);
1785 AssertPtr(pszNameCur);
1786
1787 LogFlowThisFunc(("wndCursor=%x ('%s', Xdnd version %ld), wndCur=%x ('%s', Xdnd version %ld)\n",
1788 wndCursor, pszNameCursor, newVer, m_wndCur, pszNameCur, m_curVer));
1789
1790 RTStrFree(pszNameCursor);
1791 RTStrFree(pszNameCur);
1792#endif
1793
1794 if ( wndCursor != m_wndCur
1795 && m_curVer != -1)
1796 {
1797 LogFlowThisFunc(("XA_XdndLeave: window=%#x\n", m_wndCur));
1798
1799 char *pszWndName = wndX11GetNameA(m_wndCur);
1800 AssertPtr(pszWndName);
1801 VBClLogInfo("Left old window %#x ('%s'), Xdnd version=%ld\n", m_wndCur, pszWndName, newVer);
1802 RTStrFree(pszWndName);
1803
1804 /* We left the current XdndAware window. Announce this to the current indow. */
1805 XClientMessageEvent m;
1806 RT_ZERO(m);
1807 m.type = ClientMessage;
1808 m.display = m_pDisplay;
1809 m.window = m_wndCur;
1810 m.message_type = xAtom(XA_XdndLeave);
1811 m.format = 32;
1812 m.data.l[XdndLeaveWindow] = m_wndProxy.hWnd;
1813
1814 xRc = XSendEvent(m_pDisplay, m_wndCur, False, NoEventMask, reinterpret_cast<XEvent*>(&m));
1815 if (xRc == 0)
1816 VBClLogError("Error sending XA_XdndLeave event to old window=%#x: %s\n", m_wndCur, gX11->xErrorToString(xRc).c_str());
1817
1818 /* Reset our current window. */
1819 m_wndCur = 0;
1820 m_curVer = -1;
1821 }
1822
1823 /*
1824 * Do we have a new Xdnd-aware window which now is under the cursor?
1825 */
1826 if ( wndCursor != m_wndCur
1827 && newVer != -1)
1828 {
1829 LogFlowThisFunc(("XA_XdndEnter: window=%#x\n", wndCursor));
1830
1831 char *pszWndName = wndX11GetNameA(wndCursor);
1832 AssertPtr(pszWndName);
1833 VBClLogInfo("Entered new window %#x ('%s'), supports Xdnd version=%ld\n", wndCursor, pszWndName, newVer);
1834 RTStrFree(pszWndName);
1835
1836 /*
1837 * We enter a new window. Announce the XdndEnter event to the new
1838 * window. The first three mime types are attached to the event (the
1839 * others could be requested by the XdndTypeList property from the
1840 * window itself).
1841 */
1842 XClientMessageEvent m;
1843 RT_ZERO(m);
1844 m.type = ClientMessage;
1845 m.display = m_pDisplay;
1846 m.window = wndCursor;
1847 m.message_type = xAtom(XA_XdndEnter);
1848 m.format = 32;
1849 m.data.l[XdndEnterWindow] = m_wndProxy.hWnd;
1850 m.data.l[XdndEnterFlags] = RT_MAKE_U32_FROM_U8(
1851 /* Bit 0 is set if the source supports more than three data types. */
1852 m_lstAtomFormats.size() > 3 ? RT_BIT(0) : 0,
1853 /* Reserved for future use. */
1854 0, 0,
1855 /* Protocol version to use. */
1856 RT_MIN(VBOX_XDND_VERSION, newVer));
1857 m.data.l[XdndEnterType1] = m_lstAtomFormats.value(0, None); /* First data type to use. */
1858 m.data.l[XdndEnterType2] = m_lstAtomFormats.value(1, None); /* Second data type to use. */
1859 m.data.l[XdndEnterType3] = m_lstAtomFormats.value(2, None); /* Third data type to use. */
1860
1861 xRc = XSendEvent(m_pDisplay, wndCursor, False, NoEventMask, reinterpret_cast<XEvent*>(&m));
1862 if (xRc == 0)
1863 VBClLogError("Error sending XA_XdndEnter event to window=%#x: %s\n", wndCursor, gX11->xErrorToString(xRc).c_str());
1864 }
1865
1866 if (newVer != -1)
1867 {
1868 Assert(wndCursor != None);
1869
1870 LogFlowThisFunc(("XA_XdndPosition: xPos=%RU32, yPos=%RU32 to window=%#x\n", uPosX, uPosY, wndCursor));
1871
1872 /*
1873 * Send a XdndPosition event with the proposed action to the guest.
1874 */
1875 Atom atmAction = toAtomAction(dndActionDefault);
1876 LogFlowThisFunc(("strAction=%s\n", xAtomToString(atmAction).c_str()));
1877
1878 XClientMessageEvent m;
1879 RT_ZERO(m);
1880 m.type = ClientMessage;
1881 m.display = m_pDisplay;
1882 m.window = wndCursor;
1883 m.message_type = xAtom(XA_XdndPosition);
1884 m.format = 32;
1885 m.data.l[XdndPositionWindow] = m_wndProxy.hWnd; /* X window ID of source window. */
1886 m.data.l[XdndPositionFlags] = 0; /* Reserved, set to 0. */
1887 m.data.l[XdndPositionXY] = RT_MAKE_U32(uPosY, uPosX); /* Cursor coordinates relative to the root window. */
1888 m.data.l[XdndPositionTimeStamp] = CurrentTime; /* Timestamp for retrieving data. */
1889 m.data.l[XdndPositionAction] = atmAction; /* Actions requested by the user. */
1890
1891 xRc = XSendEvent(m_pDisplay, wndCursor, False, NoEventMask, reinterpret_cast<XEvent*>(&m));
1892 if (xRc == 0)
1893 VBClLogError("Error sending XA_XdndPosition event to current window=%#x: %s\n", wndCursor, gX11->xErrorToString(xRc).c_str());
1894 }
1895
1896 if (newVer == -1)
1897 {
1898 /* No window to process, so send a ignore ack event to the host. */
1899 rc = VbglR3DnDHGSendAckOp(&m_dndCtx, VBOX_DND_ACTION_IGNORE);
1900 }
1901 else
1902 {
1903 Assert(wndCursor != None);
1904
1905 m_wndCur = wndCursor;
1906 m_curVer = newVer;
1907 }
1908
1909 LogFlowFuncLeaveRC(rc);
1910 return rc;
1911}
1912
1913/**
1914 * Host -> Guest: Event signalling that the host has dropped the data over the VM (guest) window.
1915 *
1916 * @returns IPRT status code.
1917 * @param uPosX Relative X position within the guest's display area.
1918 * @param uPosY Relative Y position within the guest's display area.
1919 * @param dndActionDefault Default action the host wants to perform on the guest
1920 * as soon as the operation successfully finishes.
1921 */
1922int DragInstance::hgDrop(uint32_t uPosX, uint32_t uPosY, VBOXDNDACTION dndActionDefault)
1923{
1924 RT_NOREF3(uPosX, uPosY, dndActionDefault);
1925 LogFlowThisFunc(("wndCur=%RU32, wndProxy=%RU32, mode=%RU32, state=%RU32\n", m_wndCur, m_wndProxy.hWnd, m_enmMode, m_enmState));
1926 LogFlowThisFunc(("uPosX=%RU32, uPosY=%RU32, dndActionDefault=0x%x\n", uPosX, uPosY, dndActionDefault));
1927
1928 if ( m_enmMode != HG
1929 || m_enmState != Dragging)
1930 {
1931 return VERR_INVALID_STATE;
1932 }
1933
1934 /* Set the state accordingly. */
1935 m_enmState = Dropped;
1936
1937 /*
1938 * Ask the host to send the raw data, as we don't (yet) know which format
1939 * the guest exactly expects. As blocking in a SelectionRequest message turned
1940 * out to be very unreliable (e.g. with KDE apps) we request to start transferring
1941 * file/directory data (if any) here.
1942 */
1943 char szFormat[] = { "text/uri-list" };
1944
1945 int rc = VbglR3DnDHGSendReqData(&m_dndCtx, szFormat);
1946 VBClLogInfo("Drop event from host resulted in: %Rrc\n", rc);
1947
1948 LogFlowFuncLeaveRC(rc);
1949 return rc;
1950}
1951
1952/**
1953 * Host -> Guest: Event signalling that the host has finished sending drag'n drop
1954 * data to the guest for further processing.
1955 *
1956 * @returns IPRT status code.
1957 * @param pMeta Pointer to meta data from host.
1958 */
1959int DragInstance::hgDataReceive(PVBGLR3GUESTDNDMETADATA pMeta)
1960{
1961 LogFlowThisFunc(("enmMode=%RU32, enmState=%RU32\n", m_enmMode, m_enmState));
1962 LogFlowThisFunc(("enmMetaType=%RU32\n", pMeta->enmType));
1963
1964 if ( m_enmMode != HG
1965 || m_enmState != Dropped)
1966 {
1967 return VERR_INVALID_STATE;
1968 }
1969
1970 void *pvData = NULL;
1971 size_t cbData = 0;
1972
1973 int rc = VINF_SUCCESS; /* Shut up GCC. */
1974
1975 switch (pMeta->enmType)
1976 {
1977 case VBGLR3GUESTDNDMETADATATYPE_RAW:
1978 {
1979 AssertBreakStmt(pMeta->u.Raw.pvMeta != NULL, rc = VERR_INVALID_POINTER);
1980 pvData = pMeta->u.Raw.pvMeta;
1981 AssertBreakStmt(pMeta->u.Raw.cbMeta, rc = VERR_INVALID_PARAMETER);
1982 cbData = pMeta->u.Raw.cbMeta;
1983
1984 rc = VINF_SUCCESS;
1985 break;
1986 }
1987
1988 case VBGLR3GUESTDNDMETADATATYPE_URI_LIST:
1989 {
1990 const char *pcszRootPath = DnDTransferListGetRootPathAbs(&pMeta->u.URI.Transfer);
1991 AssertPtrBreakStmt(pcszRootPath, VERR_INVALID_POINTER);
1992
1993 VBClLogInfo("Transfer list root directory is '%s'\n", pcszRootPath);
1994
1995 /* Note: Use the URI format here, as X' DnD spec says so. */
1996 rc = DnDTransferListGetRootsEx(&pMeta->u.URI.Transfer, DNDTRANSFERLISTFMT_URI, pcszRootPath,
1997 DND_PATH_SEPARATOR_STR, (char **)&pvData, &cbData);
1998 break;
1999 }
2000
2001 default:
2002 AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
2003 break;
2004 }
2005
2006 if (RT_FAILURE(rc))
2007 return rc;
2008
2009 /*
2010 * At this point all data needed (including sent files/directories) should
2011 * be on the guest, so proceed working on communicating with the target window.
2012 */
2013 VBClLogInfo("Received %RU32 bytes of meta data from host\n", cbData);
2014
2015 /* Destroy any old data. */
2016 if (m_pvSelReqData)
2017 {
2018 Assert(m_cbSelReqData);
2019
2020 RTMemFree(m_pvSelReqData); /** @todo RTMemRealloc? */
2021 m_cbSelReqData = 0;
2022 }
2023
2024 /** @todo Handle incremental transfers. */
2025
2026 /* Make a copy of the data. This data later then will be used to fill into
2027 * the selection request. */
2028 if (cbData)
2029 {
2030 m_pvSelReqData = RTMemAlloc(cbData);
2031 if (!m_pvSelReqData)
2032 return VERR_NO_MEMORY;
2033
2034 memcpy(m_pvSelReqData, pvData, cbData);
2035 m_cbSelReqData = cbData;
2036 }
2037
2038 /*
2039 * Send a drop event to the current window (target).
2040 * This window in turn then will raise a SelectionRequest message to our proxy window,
2041 * which we will handle in our onX11SelectionRequest handler.
2042 *
2043 * The SelectionRequest will tell us in which format the target wants the data from the host.
2044 */
2045 XClientMessageEvent m;
2046 RT_ZERO(m);
2047 m.type = ClientMessage;
2048 m.display = m_pDisplay;
2049 m.window = m_wndCur;
2050 m.message_type = xAtom(XA_XdndDrop);
2051 m.format = 32;
2052 m.data.l[XdndDropWindow] = m_wndProxy.hWnd; /* Source window. */
2053 m.data.l[XdndDropFlags] = 0; /* Reserved for future use. */
2054 m.data.l[XdndDropTimeStamp] = CurrentTime; /* Our DnD data does not rely on any timing, so just use the current time. */
2055
2056 int xRc = XSendEvent(m_pDisplay, m_wndCur, False /* Propagate */, NoEventMask, reinterpret_cast<XEvent*>(&m));
2057 if (xRc == 0)
2058 VBClLogError("Error sending XA_XdndDrop event to window=%#x: %s\n", m_wndCur, gX11->xErrorToString(xRc).c_str());
2059 XFlush(m_pDisplay);
2060
2061 LogFlowFuncLeaveRC(rc);
2062 return rc;
2063}
2064
2065/**
2066 * Checks if the VM session has changed (can happen when restoring the VM from a saved state)
2067 * and do a reconnect to the DnD HGCM service.
2068 *
2069 * @returns IPRT status code.
2070 */
2071int DragInstance::checkForSessionChange(void)
2072{
2073 uint64_t uSessionID;
2074 int rc = VbglR3GetSessionId(&uSessionID);
2075 if ( RT_SUCCESS(rc)
2076 && uSessionID != m_dndCtx.uSessionID)
2077 {
2078 LogFlowThisFunc(("VM session has changed to %RU64\n", uSessionID));
2079
2080 rc = VbglR3DnDDisconnect(&m_dndCtx);
2081 AssertRC(rc);
2082
2083 rc = VbglR3DnDConnect(&m_dndCtx);
2084 AssertRC(rc);
2085 }
2086
2087 LogFlowFuncLeaveRC(rc);
2088 return rc;
2089}
2090
2091#ifdef VBOX_WITH_DRAG_AND_DROP_GH
2092/**
2093 * Guest -> Host: Event signalling that the host is asking whether there is a pending
2094 * drag event on the guest (to the host).
2095 *
2096 * @returns IPRT status code.
2097 */
2098int DragInstance::ghIsDnDPending(void)
2099{
2100 LogFlowThisFunc(("mode=%RU32, state=%RU32\n", m_enmMode, m_enmState));
2101
2102 int rc;
2103
2104 RTCString strFormats = "\r\n"; /** @todo If empty, IOCTL fails with VERR_ACCESS_DENIED. */
2105 VBOXDNDACTION dndActionDefault = VBOX_DND_ACTION_IGNORE;
2106 VBOXDNDACTIONLIST dndActionList = VBOX_DND_ACTION_IGNORE;
2107
2108 /* Currently in wrong mode? Bail out. */
2109 if (m_enmMode == HG)
2110 {
2111 rc = VERR_INVALID_STATE;
2112 }
2113 /* Message already processed successfully? */
2114 else if ( m_enmMode == GH
2115 && ( m_enmState == Dragging
2116 || m_enmState == Dropped)
2117 )
2118 {
2119 /* No need to query for the source window again. */
2120 rc = VINF_SUCCESS;
2121 }
2122 else
2123 {
2124 /* Check if the VM session has changed and reconnect to the HGCM service if necessary. */
2125 rc = checkForSessionChange();
2126
2127 /* Determine the current window which currently has the XdndSelection set. */
2128 Window wndSelection = XGetSelectionOwner(m_pDisplay, xAtom(XA_XdndSelection));
2129 LogFlowThisFunc(("wndSelection=%#x, wndProxy=%#x, wndCur=%#x\n", wndSelection, m_wndProxy.hWnd, m_wndCur));
2130
2131 /* Is this another window which has a Xdnd selection and not our proxy window? */
2132 if ( RT_SUCCESS(rc)
2133 && wndSelection
2134 && wndSelection != m_wndCur)
2135 {
2136 char *pszWndName = wndX11GetNameA(wndSelection);
2137 AssertPtr(pszWndName);
2138 VBClLogInfo("New guest source window %#x ('%s')\n", wndSelection, pszWndName);
2139
2140 /* Start over. */
2141 reset();
2142
2143 /* Map the window on the current cursor position, which should provoke
2144 * an XdndEnter event. */
2145 rc = proxyWinShow();
2146 if (RT_SUCCESS(rc))
2147 {
2148 rc = mouseCursorFakeMove();
2149 if (RT_SUCCESS(rc))
2150 {
2151 bool fWaitFailed = false; /* Waiting for status changed failed? */
2152
2153 /* Wait until we're in "Dragging" state. */
2154 rc = waitForStatusChange(Dragging, 100 /* 100ms timeout */);
2155
2156 /*
2157 * Note: Don't wait too long here, as this mostly will make
2158 * the drag and drop experience on the host being laggy
2159 * and unresponsive.
2160 *
2161 * Instead, let the host query multiple times with 100ms
2162 * timeout each (see above) and only report an error if
2163 * the overall querying time has been exceeded.<
2164 */
2165 if (RT_SUCCESS(rc))
2166 {
2167 m_enmMode = GH;
2168 }
2169 else if (rc == VERR_TIMEOUT)
2170 {
2171 /** @todo Make m_cFailedPendingAttempts configurable. For slower window managers? */
2172 if (m_cFailedPendingAttempts++ > 50) /* Tolerate up to 5s total (100ms for each slot). */
2173 fWaitFailed = true;
2174 else
2175 rc = VINF_SUCCESS;
2176 }
2177 else if (RT_FAILURE(rc))
2178 fWaitFailed = true;
2179
2180 if (fWaitFailed)
2181 {
2182 VBClLogError("Error mapping proxy window to guest source window %#x ('%s'), rc=%Rrc\n",
2183 wndSelection, pszWndName, rc);
2184
2185 /* Reset the counter in any case. */
2186 m_cFailedPendingAttempts = 0;
2187 }
2188 }
2189 }
2190
2191 RTStrFree(pszWndName);
2192 }
2193 else
2194 VBClLogInfo("No guest source window\n");
2195 }
2196
2197 /*
2198 * Acknowledge to the host in any case, regardless
2199 * if something failed here or not. Be responsive.
2200 */
2201
2202 int rc2 = RTCritSectEnter(&m_dataCS);
2203 if (RT_SUCCESS(rc2))
2204 {
2205 RTCString strFormatsCur = gX11->xAtomListToString(m_lstAtomFormats);
2206 if (!strFormatsCur.isEmpty())
2207 {
2208 strFormats = strFormatsCur;
2209 dndActionDefault = VBOX_DND_ACTION_COPY; /** @todo Handle default action! */
2210 dndActionList = VBOX_DND_ACTION_COPY; /** @todo Ditto. */
2211 dndActionList |= toHGCMActions(m_lstAtomActions);
2212 }
2213
2214 RTCritSectLeave(&m_dataCS);
2215 }
2216
2217 rc2 = VbglR3DnDGHSendAckPending(&m_dndCtx, dndActionDefault, dndActionList,
2218 strFormats.c_str(), strFormats.length() + 1 /* Include termination */);
2219 LogFlowThisFunc(("uClientID=%RU32, dndActionDefault=0x%x, dndActionList=0x%x, strFormats=%s, rc=%Rrc\n",
2220 m_dndCtx.uClientID, dndActionDefault, dndActionList, strFormats.c_str(), rc2));
2221 if (RT_FAILURE(rc2))
2222 {
2223 VBClLogError("Error reporting pending drag and drop operation status to host: %Rrc\n", rc2);
2224 if (RT_SUCCESS(rc))
2225 rc = rc2;
2226 }
2227
2228 LogFlowFuncLeaveRC(rc);
2229 return rc;
2230}
2231
2232/**
2233 * Guest -> Host: Event signalling that the host has dropped the item(s) on the
2234 * host side.
2235 *
2236 * @returns IPRT status code.
2237 * @param strFormat Requested format to send to the host.
2238 * @param dndActionRequested Requested action to perform on the guest.
2239 */
2240int DragInstance::ghDropped(const RTCString &strFormat, VBOXDNDACTION dndActionRequested)
2241{
2242 LogFlowThisFunc(("mode=%RU32, state=%RU32, strFormat=%s, dndActionRequested=0x%x\n",
2243 m_enmMode, m_enmState, strFormat.c_str(), dndActionRequested));
2244
2245 /* Currently in wrong mode? Bail out. */
2246 if ( m_enmMode == Unknown
2247 || m_enmMode == HG)
2248 {
2249 return VERR_INVALID_STATE;
2250 }
2251
2252 if ( m_enmMode == GH
2253 && m_enmState != Dragging)
2254 {
2255 return VERR_INVALID_STATE;
2256 }
2257
2258 int rc = VINF_SUCCESS;
2259
2260 m_enmState = Dropped;
2261
2262#ifdef DEBUG
2263 XWindowAttributes xwa;
2264 XGetWindowAttributes(m_pDisplay, m_wndCur, &xwa);
2265 LogFlowThisFunc(("wndProxy=%RU32, wndCur=%RU32, x=%d, y=%d, width=%d, height=%d\n",
2266 m_wndProxy.hWnd, m_wndCur, xwa.x, xwa.y, xwa.width, xwa.height));
2267
2268 Window wndSelection = XGetSelectionOwner(m_pDisplay, xAtom(XA_XdndSelection));
2269 LogFlowThisFunc(("wndSelection=%#x\n", wndSelection));
2270#endif
2271
2272 /* We send a fake mouse move event to the current window, cause
2273 * this should have the grab. */
2274 mouseCursorFakeMove();
2275
2276 /**
2277 * The fake button release event above should lead to a XdndDrop event from the
2278 * source window. Because of showing our proxy window, other Xdnd events can
2279 * occur before, e.g. a XdndPosition event. We are not interested
2280 * in those, so just try to get the right one.
2281 */
2282
2283 XClientMessageEvent evDnDDrop;
2284 bool fDrop = waitForX11ClientMsg(evDnDDrop, xAtom(XA_XdndDrop), 5 * 1000 /* 5s timeout */);
2285 if (fDrop)
2286 {
2287 LogFlowThisFunc(("XA_XdndDrop\n"));
2288
2289 /* Request to convert the selection in the specific format and
2290 * place it to our proxy window as property. */
2291 Assert(evDnDDrop.message_type == xAtom(XA_XdndDrop));
2292
2293 Window wndSource = evDnDDrop.data.l[XdndDropWindow]; /* Source window which has sent the message. */
2294 Assert(wndSource == m_wndCur);
2295
2296 Atom aFormat = gX11->stringToxAtom(strFormat.c_str());
2297
2298 Time tsDrop;
2299 if (m_curVer >= 1)
2300 tsDrop = evDnDDrop.data.l[XdndDropTimeStamp];
2301 else
2302 tsDrop = CurrentTime;
2303
2304 XConvertSelection(m_pDisplay, xAtom(XA_XdndSelection), aFormat, xAtom(XA_XdndSelection),
2305 m_wndProxy.hWnd, tsDrop);
2306
2307 /* Wait for the selection notify event. */
2308 XEvent evSelNotify;
2309 RT_ZERO(evSelNotify);
2310 if (waitForX11Msg(evSelNotify, SelectionNotify, 5 * 1000 /* 5s timeout */))
2311 {
2312 bool fCancel = false;
2313
2314 /* Make some paranoid checks. */
2315 if ( evSelNotify.xselection.type == SelectionNotify
2316 && evSelNotify.xselection.display == m_pDisplay
2317 && evSelNotify.xselection.selection == xAtom(XA_XdndSelection)
2318 && evSelNotify.xselection.requestor == m_wndProxy.hWnd
2319 && evSelNotify.xselection.target == aFormat)
2320 {
2321 LogFlowThisFunc(("Selection notfiy (from wnd=%#x)\n", m_wndCur));
2322
2323 Atom aPropType;
2324 int iPropFormat;
2325 unsigned long cItems, cbRemaining;
2326 unsigned char *pcData = NULL;
2327 int xRc = XGetWindowProperty(m_pDisplay, m_wndProxy.hWnd,
2328 xAtom(XA_XdndSelection) /* Property */,
2329 0 /* Offset */,
2330 VBOX_MAX_XPROPERTIES /* Length of 32-bit multiples */,
2331 True /* Delete property? */,
2332 AnyPropertyType, /* Property type */
2333 &aPropType, &iPropFormat, &cItems, &cbRemaining, &pcData);
2334 if (xRc != Success)
2335 VBClLogError("Error getting XA_XdndSelection property of proxy window=%#x: %s\n",
2336 m_wndProxy.hWnd, gX11->xErrorToString(xRc).c_str());
2337
2338 LogFlowThisFunc(("strType=%s, iPropFormat=%d, cItems=%RU32, cbRemaining=%RU32\n",
2339 gX11->xAtomToString(aPropType).c_str(), iPropFormat, cItems, cbRemaining));
2340
2341 if ( aPropType != None
2342 && pcData != NULL
2343 && iPropFormat >= 8
2344 && cItems > 0
2345 && cbRemaining == 0)
2346 {
2347 size_t cbData = cItems * (iPropFormat / 8);
2348 LogFlowThisFunc(("cbData=%zu\n", cbData));
2349
2350 /* For whatever reason some of the string MIME types are not
2351 * zero terminated. Check that and correct it when necessary,
2352 * because the guest side wants this in any case. */
2353 if ( m_lstAllowedFormats.contains(strFormat)
2354 && pcData[cbData - 1] != '\0')
2355 {
2356 unsigned char *pvDataTmp = static_cast<unsigned char*>(RTMemAlloc(cbData + 1));
2357 if (pvDataTmp)
2358 {
2359 memcpy(pvDataTmp, pcData, cbData);
2360 pvDataTmp[cbData++] = '\0';
2361
2362 rc = VbglR3DnDGHSendData(&m_dndCtx, strFormat.c_str(), pvDataTmp, cbData);
2363 RTMemFree(pvDataTmp);
2364 }
2365 else
2366 rc = VERR_NO_MEMORY;
2367 }
2368 else
2369 {
2370 /* Send the raw data to the host. */
2371 rc = VbglR3DnDGHSendData(&m_dndCtx, strFormat.c_str(), pcData, cbData);
2372 LogFlowThisFunc(("Sent strFormat=%s, rc=%Rrc\n", strFormat.c_str(), rc));
2373 }
2374
2375 if (RT_SUCCESS(rc))
2376 {
2377 rc = m_wndProxy.sendFinished(wndSource, dndActionRequested);
2378 }
2379 else
2380 fCancel = true;
2381 }
2382 else
2383 {
2384 if (aPropType == xAtom(XA_INCR))
2385 {
2386 /** @todo Support incremental transfers. */
2387 AssertMsgFailed(("Incremental transfers are not supported yet\n"));
2388
2389 VBClLogError("Incremental transfers are not supported yet\n");
2390 rc = VERR_NOT_IMPLEMENTED;
2391 }
2392 else
2393 {
2394 VBClLogError("Not supported data type: %s\n", gX11->xAtomToString(aPropType).c_str());
2395 rc = VERR_NOT_SUPPORTED;
2396 }
2397
2398 fCancel = true;
2399 }
2400
2401 if (fCancel)
2402 {
2403 VBClLogInfo("Cancelling dropping to host\n");
2404
2405 /* Cancel the operation -- inform the source window by
2406 * sending a XdndFinished message so that the source can toss the required data. */
2407 rc = m_wndProxy.sendFinished(wndSource, VBOX_DND_ACTION_IGNORE);
2408 }
2409
2410 /* Cleanup. */
2411 if (pcData)
2412 XFree(pcData);
2413 }
2414 else
2415 rc = VERR_INVALID_PARAMETER;
2416 }
2417 else
2418 rc = VERR_TIMEOUT;
2419 }
2420 else
2421 rc = VERR_TIMEOUT;
2422
2423 /* Inform the host on error. */
2424 if (RT_FAILURE(rc))
2425 {
2426 int rc2 = VbglR3DnDGHSendError(&m_dndCtx, rc);
2427 LogFlowThisFunc(("Sending error %Rrc to host resulted in %Rrc\n", rc, rc2)); RT_NOREF(rc2);
2428 /* This is not fatal for us, just ignore. */
2429 }
2430
2431 /* At this point, we have either successfully transfered any data or not.
2432 * So reset our internal state because we are done here for the current (ongoing)
2433 * drag and drop operation. */
2434 reset();
2435
2436 LogFlowFuncLeaveRC(rc);
2437 return rc;
2438}
2439#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
2440
2441/*
2442 * Helpers
2443 */
2444
2445/**
2446 * Fakes moving the mouse cursor to provoke various drag and drop
2447 * events such as entering a target window or moving within a
2448 * source window.
2449 *
2450 * Not the most elegant and probably correct function, but does
2451 * the work for now.
2452 *
2453 * @returns IPRT status code.
2454 */
2455int DragInstance::mouseCursorFakeMove(void) const
2456{
2457 int iScreenID = XDefaultScreen(m_pDisplay);
2458 /** @todo What about multiple screens? Test this! */
2459
2460 const int iScrX = XDisplayWidth(m_pDisplay, iScreenID);
2461 const int iScrY = XDisplayHeight(m_pDisplay, iScreenID);
2462
2463 int fx, fy, rx, ry;
2464 Window wndTemp, wndChild;
2465 int wx, wy; unsigned int mask;
2466 XQueryPointer(m_pDisplay, m_wndRoot, &wndTemp, &wndChild, &rx, &ry, &wx, &wy, &mask);
2467
2468 /*
2469 * Apply some simple clipping and change the position slightly.
2470 */
2471
2472 /* FakeX */
2473 if (rx == 0) fx = 1;
2474 else if (rx == iScrX) fx = iScrX - 1;
2475 else fx = rx + 1;
2476
2477 /* FakeY */
2478 if (ry == 0) fy = 1;
2479 else if (ry == iScrY) fy = iScrY - 1;
2480 else fy = ry + 1;
2481
2482 /*
2483 * Move the cursor to trigger the wanted events.
2484 */
2485 LogFlowThisFunc(("cursorRootX=%d, cursorRootY=%d\n", fx, fy));
2486 int rc = mouseCursorMove(fx, fy);
2487 if (RT_SUCCESS(rc))
2488 {
2489 /* Move the cursor back to its original position. */
2490 rc = mouseCursorMove(rx, ry);
2491 }
2492
2493 return rc;
2494}
2495
2496/**
2497 * Moves the mouse pointer to a specific position.
2498 *
2499 * @returns IPRT status code.
2500 * @param iPosX Absolute X coordinate.
2501 * @param iPosY Absolute Y coordinate.
2502 */
2503int DragInstance::mouseCursorMove(int iPosX, int iPosY) const
2504{
2505 int iScreenID = XDefaultScreen(m_pDisplay);
2506 /** @todo What about multiple screens? Test this! */
2507
2508 const int iScrX = XDisplayWidth(m_pDisplay, iScreenID);
2509 const int iScrY = XDisplayHeight(m_pDisplay, iScreenID);
2510
2511 iPosX = RT_CLAMP(iPosX, 0, iScrX);
2512 iPosY = RT_CLAMP(iPosY, 0, iScrY);
2513
2514 LogFlowThisFunc(("iPosX=%d, iPosY=%d\n", iPosX, iPosY));
2515
2516 /* Move the guest pointer to the DnD position, so we can find the window
2517 * below that position. */
2518 XWarpPointer(m_pDisplay, None, m_wndRoot, 0, 0, 0, 0, iPosX, iPosY);
2519 return VINF_SUCCESS;
2520}
2521
2522/**
2523 * Sends a mouse button event to a specific window.
2524 *
2525 * @param wndDest Window to send the mouse button event to.
2526 * @param rx X coordinate relative to the root window's origin.
2527 * @param ry Y coordinate relative to the root window's origin.
2528 * @param iButton Mouse button to press/release.
2529 * @param fPress Whether to press or release the mouse button.
2530 */
2531void DragInstance::mouseButtonSet(Window wndDest, int rx, int ry, int iButton, bool fPress)
2532{
2533 LogFlowThisFunc(("wndDest=%#x, rx=%d, ry=%d, iBtn=%d, fPress=%RTbool\n",
2534 wndDest, rx, ry, iButton, fPress));
2535
2536#ifdef VBOX_DND_WITH_XTEST
2537 /** @todo Make this check run only once. */
2538 int ev, er, ma, mi;
2539 if (XTestQueryExtension(m_pDisplay, &ev, &er, &ma, &mi))
2540 {
2541 LogFlowThisFunc(("XText extension available\n"));
2542
2543 int xRc = XTestFakeButtonEvent(m_pDisplay, 1, fPress ? True : False, CurrentTime);
2544 if (Rc == 0)
2545 VBClLogError("Error sending XTestFakeButtonEvent event: %s\n", gX11->xErrorToString(xRc).c_str());
2546 XFlush(m_pDisplay);
2547 }
2548 else
2549 {
2550#endif
2551 LogFlowThisFunc(("Note: XText extension not available or disabled\n"));
2552
2553 unsigned int mask = 0;
2554
2555 if ( rx == -1
2556 && ry == -1)
2557 {
2558 Window wndRoot, wndChild;
2559 int wx, wy;
2560 XQueryPointer(m_pDisplay, m_wndRoot, &wndRoot, &wndChild, &rx, &ry, &wx, &wy, &mask);
2561 LogFlowThisFunc(("Mouse pointer is at root x=%d, y=%d\n", rx, ry));
2562 }
2563
2564 XButtonEvent eBtn;
2565 RT_ZERO(eBtn);
2566
2567 eBtn.display = m_pDisplay;
2568 eBtn.root = m_wndRoot;
2569 eBtn.window = wndDest;
2570 eBtn.subwindow = None;
2571 eBtn.same_screen = True;
2572 eBtn.time = CurrentTime;
2573 eBtn.button = iButton;
2574 eBtn.state = mask | (iButton == 1 ? Button1MotionMask :
2575 iButton == 2 ? Button2MotionMask :
2576 iButton == 3 ? Button3MotionMask :
2577 iButton == 4 ? Button4MotionMask :
2578 iButton == 5 ? Button5MotionMask : 0);
2579 eBtn.type = fPress ? ButtonPress : ButtonRelease;
2580 eBtn.send_event = False;
2581 eBtn.x_root = rx;
2582 eBtn.y_root = ry;
2583
2584 XTranslateCoordinates(m_pDisplay, eBtn.root, eBtn.window, eBtn.x_root, eBtn.y_root, &eBtn.x, &eBtn.y, &eBtn.subwindow);
2585 LogFlowThisFunc(("state=0x%x, x=%d, y=%d\n", eBtn.state, eBtn.x, eBtn.y));
2586
2587 int xRc = XSendEvent(m_pDisplay, wndDest, True /* fPropagate */,
2588 ButtonPressMask,
2589 reinterpret_cast<XEvent*>(&eBtn));
2590 if (xRc == 0)
2591 VBClLogError("Error sending XButtonEvent event to window=%#x: %s\n", wndDest, gX11->xErrorToString(xRc).c_str());
2592
2593 XFlush(m_pDisplay);
2594
2595#ifdef VBOX_DND_WITH_XTEST
2596 }
2597#endif
2598}
2599
2600/**
2601 * Shows the (invisible) proxy window. The proxy window is needed for intercepting
2602 * drags from the host to the guest or from the guest to the host. It acts as a proxy
2603 * between the host and the actual (UI) element on the guest OS.
2604 *
2605 * To not make it miss any actions this window gets spawned across the entire guest
2606 * screen (think of an umbrella) to (hopefully) capture everything. A proxy window
2607 * which follows the cursor would be far too slow here.
2608 *
2609 * @returns IPRT status code.
2610 * @param piRootX X coordinate relative to the root window's origin. Optional.
2611 * @param piRootY Y coordinate relative to the root window's origin. Optional.
2612 */
2613int DragInstance::proxyWinShow(int *piRootX /* = NULL */, int *piRootY /* = NULL */) const
2614{
2615 /* piRootX is optional. */
2616 /* piRootY is optional. */
2617
2618 LogFlowThisFuncEnter();
2619
2620 int rc = VINF_SUCCESS;
2621
2622#if 0
2623# ifdef VBOX_DND_WITH_XTEST
2624 XTestGrabControl(m_pDisplay, False);
2625# endif
2626#endif
2627
2628 /* Get the mouse pointer position and determine if we're on the same screen as the root window
2629 * and return the current child window beneath our mouse pointer, if any. */
2630 int iRootX, iRootY;
2631 int iChildX, iChildY;
2632 unsigned int iMask;
2633 Window wndRoot, wndChild;
2634 Bool fInRootWnd = XQueryPointer(m_pDisplay, m_wndRoot, &wndRoot, &wndChild,
2635 &iRootX, &iRootY, &iChildX, &iChildY, &iMask);
2636
2637 LogFlowThisFunc(("fInRootWnd=%RTbool, wndRoot=%RU32, wndChild=%RU32, iRootX=%d, iRootY=%d\n",
2638 RT_BOOL(fInRootWnd), wndRoot, wndChild, iRootX, iRootY)); RT_NOREF(fInRootWnd);
2639
2640 if (piRootX)
2641 *piRootX = iRootX;
2642 if (piRootY)
2643 *piRootY = iRootY;
2644
2645 XSynchronize(m_pDisplay, True /* Enable sync */);
2646
2647 /* Bring our proxy window into foreground. */
2648 XMapWindow(m_pDisplay, m_wndProxy.hWnd);
2649 XRaiseWindow(m_pDisplay, m_wndProxy.hWnd);
2650
2651 /* Spawn our proxy window over the entire screen, making it an easy drop target for the host's cursor. */
2652 LogFlowThisFunc(("Proxy window x=%d, y=%d, width=%d, height=%d\n",
2653 m_wndProxy.iX, m_wndProxy.iY, m_wndProxy.iWidth, m_wndProxy.iHeight));
2654 XMoveResizeWindow(m_pDisplay, m_wndProxy.hWnd, m_wndProxy.iX, m_wndProxy.iY, m_wndProxy.iWidth, m_wndProxy.iHeight);
2655
2656 XFlush(m_pDisplay);
2657
2658 XSynchronize(m_pDisplay, False /* Disable sync */);
2659
2660#if 0
2661# ifdef VBOX_DND_WITH_XTEST
2662 XTestGrabControl(m_pDisplay, True);
2663# endif
2664#endif
2665
2666 LogFlowFuncLeaveRC(rc);
2667 return rc;
2668}
2669
2670/**
2671 * Hides the (invisible) proxy window.
2672 */
2673int DragInstance::proxyWinHide(void)
2674{
2675 LogFlowFuncEnter();
2676
2677 XUnmapWindow(m_pDisplay, m_wndProxy.hWnd);
2678 XFlush(m_pDisplay);
2679
2680 m_eventQueueList.clear();
2681
2682 return VINF_SUCCESS; /** @todo Add error checking. */
2683}
2684
2685/**
2686 * Allocates the name (title) of an X window.
2687 * The returned pointer must be freed using RTStrFree().
2688 *
2689 * @returns Pointer to the allocated window name.
2690 * @param wndThis Window to retrieve name for.
2691 *
2692 * @remark If the window title is not available, the text
2693 * "<No name>" will be returned.
2694 */
2695char *DragInstance::wndX11GetNameA(Window wndThis) const
2696{
2697 char *pszName = NULL;
2698
2699 XTextProperty propName;
2700 if (XGetWMName(m_pDisplay, wndThis, &propName))
2701 {
2702 if (propName.value)
2703 pszName = RTStrDup((char *)propName.value); /** @todo UTF8? */
2704 XFree(propName.value);
2705 }
2706
2707 if (!pszName) /* No window name found? */
2708 pszName = RTStrDup("<No name>");
2709
2710 return pszName;
2711}
2712
2713/**
2714 * Clear a window's supported/accepted actions list.
2715 *
2716 * @param wndThis Window to clear the list for.
2717 */
2718void DragInstance::wndXDnDClearActionList(Window wndThis) const
2719{
2720 XDeleteProperty(m_pDisplay, wndThis, xAtom(XA_XdndActionList));
2721}
2722
2723/**
2724 * Clear a window's supported/accepted formats list.
2725 *
2726 * @param wndThis Window to clear the list for.
2727 */
2728void DragInstance::wndXDnDClearFormatList(Window wndThis) const
2729{
2730 XDeleteProperty(m_pDisplay, wndThis, xAtom(XA_XdndTypeList));
2731}
2732
2733/**
2734 * Retrieves a window's supported/accepted XDnD actions.
2735 *
2736 * @returns IPRT status code.
2737 * @param wndThis Window to retrieve the XDnD actions for.
2738 * @param lstActions Reference to VBoxDnDAtomList to store the action into.
2739 */
2740int DragInstance::wndXDnDGetActionList(Window wndThis, VBoxDnDAtomList &lstActions) const
2741{
2742 Atom iActType = None;
2743 int iActFmt;
2744 unsigned long cItems, cbData;
2745 unsigned char *pcbData = NULL;
2746
2747 /* Fetch the possible list of actions, if this property is set. */
2748 int xRc = XGetWindowProperty(m_pDisplay, wndThis,
2749 xAtom(XA_XdndActionList),
2750 0, VBOX_MAX_XPROPERTIES,
2751 False, XA_ATOM, &iActType, &iActFmt, &cItems, &cbData, &pcbData);
2752 if (xRc != Success)
2753 {
2754 LogFlowThisFunc(("Error getting XA_XdndActionList atoms from window=%#x: %s\n",
2755 wndThis, gX11->xErrorToString(xRc).c_str()));
2756 return VERR_NOT_FOUND;
2757 }
2758
2759 LogFlowThisFunc(("wndThis=%#x, cItems=%RU32, pcbData=%p\n", wndThis, cItems, pcbData));
2760
2761 if (cItems > 0)
2762 {
2763 AssertPtr(pcbData);
2764 Atom *paData = reinterpret_cast<Atom *>(pcbData);
2765
2766 for (unsigned i = 0; i < RT_MIN(VBOX_MAX_XPROPERTIES, cItems); i++)
2767 {
2768 LogFlowThisFunc(("\t%s\n", gX11->xAtomToString(paData[i]).c_str()));
2769 lstActions.append(paData[i]);
2770 }
2771
2772 XFree(pcbData);
2773 }
2774
2775 return VINF_SUCCESS;
2776}
2777
2778/**
2779 * Retrieves a window's supported/accepted XDnD formats.
2780 *
2781 * @returns IPRT status code.
2782 * @param wndThis Window to retrieve the XDnD formats for.
2783 * @param lstTypes Reference to VBoxDnDAtomList to store the formats into.
2784 */
2785int DragInstance::wndXDnDGetFormatList(Window wndThis, VBoxDnDAtomList &lstTypes) const
2786{
2787 Atom iActType = None;
2788 int iActFmt;
2789 unsigned long cItems, cbData;
2790 unsigned char *pcbData = NULL;
2791
2792 int xRc = XGetWindowProperty(m_pDisplay, wndThis,
2793 xAtom(XA_XdndTypeList),
2794 0, VBOX_MAX_XPROPERTIES,
2795 False, XA_ATOM, &iActType, &iActFmt, &cItems, &cbData, &pcbData);
2796 if (xRc != Success)
2797 {
2798 LogFlowThisFunc(("Error getting XA_XdndTypeList atoms from window=%#x: %s\n",
2799 wndThis, gX11->xErrorToString(xRc).c_str()));
2800 return VERR_NOT_FOUND;
2801 }
2802
2803 LogFlowThisFunc(("wndThis=%#x, cItems=%RU32, pcbData=%p\n", wndThis, cItems, pcbData));
2804
2805 if (cItems > 0)
2806 {
2807 AssertPtr(pcbData);
2808 Atom *paData = reinterpret_cast<Atom *>(pcbData);
2809
2810 for (unsigned i = 0; i < RT_MIN(VBOX_MAX_XPROPERTIES, cItems); i++)
2811 {
2812 LogFlowThisFunc(("\t%s\n", gX11->xAtomToString(paData[i]).c_str()));
2813 lstTypes.append(paData[i]);
2814 }
2815
2816 XFree(pcbData);
2817 }
2818
2819 return VINF_SUCCESS;
2820}
2821
2822/**
2823 * Sets (replaces) a window's XDnD accepted/allowed actions.
2824 *
2825 * @returns IPRT status code.
2826 * @param wndThis Window to set the format list for.
2827 * @param lstActions Reference to list of XDnD actions to set.
2828 */
2829int DragInstance::wndXDnDSetActionList(Window wndThis, const VBoxDnDAtomList &lstActions) const
2830{
2831 if (lstActions.isEmpty())
2832 return VINF_SUCCESS;
2833
2834 XChangeProperty(m_pDisplay, wndThis,
2835 xAtom(XA_XdndActionList),
2836 XA_ATOM, 32, PropModeReplace,
2837 reinterpret_cast<const unsigned char*>(lstActions.raw()),
2838 lstActions.size());
2839
2840 return VINF_SUCCESS;
2841}
2842
2843/**
2844 * Sets (replaces) a window's XDnD accepted format list.
2845 *
2846 * @returns IPRT status code.
2847 * @param wndThis Window to set the format list for.
2848 * @param atmProp Property to set.
2849 * @param lstFormats Reference to list of XDnD formats to set.
2850 */
2851int DragInstance::wndXDnDSetFormatList(Window wndThis, Atom atmProp, const VBoxDnDAtomList &lstFormats) const
2852{
2853 if (lstFormats.isEmpty())
2854 return VERR_INVALID_PARAMETER;
2855
2856 /* Add the property with the property data to the window. */
2857 XChangeProperty(m_pDisplay, wndThis, atmProp,
2858 XA_ATOM, 32, PropModeReplace,
2859 reinterpret_cast<const unsigned char*>(lstFormats.raw()),
2860 lstFormats.size());
2861
2862 return VINF_SUCCESS;
2863}
2864
2865/**
2866 * Appends a RTCString list to VBoxDnDAtomList list.
2867 *
2868 * @returns IPRT status code.
2869 * @param lstFormats Reference to RTCString list to convert.
2870 * @param lstAtoms Reference to VBoxDnDAtomList list to store results in.
2871 */
2872int DragInstance::appendFormatsToList(const RTCList<RTCString> &lstFormats, VBoxDnDAtomList &lstAtoms) const
2873{
2874 for (size_t i = 0; i < lstFormats.size(); ++i)
2875 lstAtoms.append(XInternAtom(m_pDisplay, lstFormats.at(i).c_str(), False));
2876
2877 return VINF_SUCCESS;
2878}
2879
2880/**
2881 * Appends a raw-data string list to VBoxDnDAtomList list.
2882 *
2883 * @returns IPRT status code.
2884 * @param pvData Pointer to string data to convert.
2885 * @param cbData Size (in bytes) to convert.
2886 * @param lstAtoms Reference to VBoxDnDAtomList list to store results in.
2887 */
2888int DragInstance::appendDataToList(const void *pvData, uint32_t cbData, VBoxDnDAtomList &lstAtoms) const
2889{
2890 RT_NOREF1(lstAtoms);
2891 AssertPtrReturn(pvData, VERR_INVALID_POINTER);
2892 AssertReturn(cbData, VERR_INVALID_PARAMETER);
2893
2894 const char *pszStr = (char *)pvData;
2895 uint32_t cbStr = cbData;
2896
2897 int rc = VINF_SUCCESS;
2898
2899 VBoxDnDAtomList lstAtom;
2900 while (cbStr)
2901 {
2902 size_t cbSize = RTStrNLen(pszStr, cbStr);
2903
2904 /* Create a copy with max N chars, so that we are on the save side,
2905 * even if the data isn't zero terminated. */
2906 char *pszTmp = RTStrDupN(pszStr, cbSize);
2907 if (!pszTmp)
2908 {
2909 rc = VERR_NO_MEMORY;
2910 break;
2911 }
2912
2913 lstAtom.append(XInternAtom(m_pDisplay, pszTmp, False));
2914 RTStrFree(pszTmp);
2915
2916 pszStr += cbSize + 1;
2917 cbStr -= cbSize + 1;
2918 }
2919
2920 return rc;
2921}
2922
2923/**
2924 * Converts a HGCM-based drag'n drop action to a Atom-based drag'n drop action.
2925 *
2926 * @returns Converted Atom-based drag'n drop action.
2927 * @param dndAction HGCM drag'n drop actions to convert.
2928 */
2929/* static */
2930Atom DragInstance::toAtomAction(VBOXDNDACTION dndAction)
2931{
2932 /* Ignore is None. */
2933 return (isDnDCopyAction(dndAction) ? xAtom(XA_XdndActionCopy) :
2934 isDnDMoveAction(dndAction) ? xAtom(XA_XdndActionMove) :
2935 isDnDLinkAction(dndAction) ? xAtom(XA_XdndActionLink) :
2936 None);
2937}
2938
2939/**
2940 * Converts HGCM-based drag'n drop actions to a VBoxDnDAtomList list.
2941 *
2942 * @returns IPRT status code.
2943 * @param dndActionList HGCM drag'n drop actions to convert.
2944 * @param lstAtoms Reference to VBoxDnDAtomList to store actions in.
2945 */
2946/* static */
2947int DragInstance::toAtomActions(VBOXDNDACTIONLIST dndActionList, VBoxDnDAtomList &lstAtoms)
2948{
2949 if (hasDnDCopyAction(dndActionList))
2950 lstAtoms.append(xAtom(XA_XdndActionCopy));
2951 if (hasDnDMoveAction(dndActionList))
2952 lstAtoms.append(xAtom(XA_XdndActionMove));
2953 if (hasDnDLinkAction(dndActionList))
2954 lstAtoms.append(xAtom(XA_XdndActionLink));
2955
2956 return VINF_SUCCESS;
2957}
2958
2959/**
2960 * Converts an Atom-based drag'n drop action to a HGCM drag'n drop action.
2961 *
2962 * @returns HGCM drag'n drop action.
2963 * @param atom Atom-based drag'n drop action to convert.
2964 */
2965/* static */
2966uint32_t DragInstance::toHGCMAction(Atom atom)
2967{
2968 uint32_t uAction = VBOX_DND_ACTION_IGNORE;
2969
2970 if (atom == xAtom(XA_XdndActionCopy))
2971 uAction = VBOX_DND_ACTION_COPY;
2972 else if (atom == xAtom(XA_XdndActionMove))
2973 uAction = VBOX_DND_ACTION_MOVE;
2974 else if (atom == xAtom(XA_XdndActionLink))
2975 uAction = VBOX_DND_ACTION_LINK;
2976
2977 return uAction;
2978}
2979
2980/**
2981 * Converts an VBoxDnDAtomList list to an HGCM action list.
2982 *
2983 * @returns ORed HGCM action list.
2984 * @param lstActions List of Atom-based actions to convert.
2985 */
2986/* static */
2987uint32_t DragInstance::toHGCMActions(const VBoxDnDAtomList &lstActions)
2988{
2989 uint32_t uActions = VBOX_DND_ACTION_IGNORE;
2990
2991 for (size_t i = 0; i < lstActions.size(); i++)
2992 uActions |= toHGCMAction(lstActions.at(i));
2993
2994 return uActions;
2995}
2996
2997/*********************************************************************************************************************************
2998 * VBoxDnDProxyWnd implementation. *
2999 ********************************************************************************************************************************/
3000
3001VBoxDnDProxyWnd::VBoxDnDProxyWnd(void)
3002 : pDisp(NULL)
3003 , hWnd(0)
3004 , iX(0)
3005 , iY(0)
3006 , iWidth(0)
3007 , iHeight(0)
3008{
3009
3010}
3011
3012VBoxDnDProxyWnd::~VBoxDnDProxyWnd(void)
3013{
3014 destroy();
3015}
3016
3017int VBoxDnDProxyWnd::init(Display *pDisplay)
3018{
3019 /** @todo What about multiple screens? Test this! */
3020 int iScreenID = XDefaultScreen(pDisplay);
3021
3022 iWidth = XDisplayWidth(pDisplay, iScreenID);
3023 iHeight = XDisplayHeight(pDisplay, iScreenID);
3024 pDisp = pDisplay;
3025
3026 return VINF_SUCCESS;
3027}
3028
3029void VBoxDnDProxyWnd::destroy(void)
3030{
3031
3032}
3033
3034int VBoxDnDProxyWnd::sendFinished(Window hWndSource, VBOXDNDACTION dndAction)
3035{
3036 /* Was the drop accepted by the host? That is, anything than ignoring. */
3037 bool fDropAccepted = dndAction > VBOX_DND_ACTION_IGNORE;
3038
3039 LogFlowFunc(("dndAction=0x%x\n", dndAction));
3040
3041 /* Confirm the result of the transfer to the target window. */
3042 XClientMessageEvent m;
3043 RT_ZERO(m);
3044 m.type = ClientMessage;
3045 m.display = pDisp;
3046 m.window = hWnd;
3047 m.message_type = xAtom(XA_XdndFinished);
3048 m.format = 32;
3049 m.data.l[XdndFinishedWindow] = hWnd; /* Target window. */
3050 m.data.l[XdndFinishedFlags] = fDropAccepted ? RT_BIT(0) : 0; /* Was the drop accepted? */
3051 m.data.l[XdndFinishedAction] = fDropAccepted ? DragInstance::toAtomAction(dndAction) : None; /* Action used on accept. */
3052
3053 int xRc = XSendEvent(pDisp, hWndSource, True, NoEventMask, reinterpret_cast<XEvent*>(&m));
3054 if (xRc == 0)
3055 {
3056 VBClLogError("Error sending XA_XdndFinished event to source window=%#x: %s\n",
3057 hWndSource, gX11->xErrorToString(xRc).c_str());
3058
3059 return VERR_GENERAL_FAILURE; /** @todo Fudge. */
3060 }
3061
3062 return VINF_SUCCESS;
3063}
3064
3065/*********************************************************************************************************************************
3066 * DragAndDropService implementation. *
3067 ********************************************************************************************************************************/
3068
3069/**
3070 * Initializes the drag and drop service.
3071 *
3072 * @returns IPRT status code.
3073 */
3074int DragAndDropService::init(void)
3075{
3076 LogFlowFuncEnter();
3077
3078 /* Connect to the x11 server. */
3079 m_pDisplay = XOpenDisplay(NULL);
3080 if (!m_pDisplay)
3081 {
3082 VBClLogFatalError("Unable to connect to X server -- running in a terminal session?\n");
3083 return VERR_NOT_FOUND;
3084 }
3085
3086 xHelpers *pHelpers = xHelpers::getInstance(m_pDisplay);
3087 if (!pHelpers)
3088 return VERR_NO_MEMORY;
3089
3090 int rc;
3091
3092 do
3093 {
3094 rc = RTSemEventCreate(&m_hEventSem);
3095 AssertRCBreak(rc);
3096
3097 rc = RTCritSectInit(&m_eventQueueCS);
3098 AssertRCBreak(rc);
3099
3100 rc = VbglR3DnDConnect(&m_dndCtx);
3101 AssertRCBreak(rc);
3102
3103 /* Event thread for events coming from the HGCM device. */
3104 rc = RTThreadCreate(&m_hHGCMThread, hgcmEventThread, this,
3105 0, RTTHREADTYPE_MSG_PUMP, RTTHREADFLAGS_WAITABLE, "dndHGCM");
3106 AssertRCBreak(rc);
3107
3108 rc = RTThreadUserWait(m_hHGCMThread, 10 * 1000 /* 10s timeout */);
3109 AssertRCBreak(rc);
3110
3111 if (ASMAtomicReadBool(&m_fStop))
3112 break;
3113
3114 /* Event thread for events coming from the x11 system. */
3115 rc = RTThreadCreate(&m_hX11Thread, x11EventThread, this,
3116 0, RTTHREADTYPE_MSG_PUMP, RTTHREADFLAGS_WAITABLE, "dndX11");
3117 AssertRCBreak(rc);
3118
3119 rc = RTThreadUserWait(m_hX11Thread, 10 * 1000 /* 10s timeout */);
3120 AssertRCBreak(rc);
3121
3122 if (ASMAtomicReadBool(&m_fStop))
3123 break;
3124
3125 } while (0);
3126
3127 if (m_fStop)
3128 rc = VERR_GENERAL_FAILURE; /** @todo Fudge! */
3129
3130 if (RT_FAILURE(rc))
3131 VBClLogError("Failed to initialize, rc=%Rrc\n", rc);
3132
3133 LogFlowFuncLeaveRC(rc);
3134 return rc;
3135}
3136
3137/**
3138 * Main loop for the drag and drop service which does the HGCM message
3139 * processing and routing to the according drag and drop instance(s).
3140 *
3141 * @returns IPRT status code.
3142 * @param fDaemonised Whether to run in daemonized or not. Does not
3143 * apply for this service.
3144 */
3145int DragAndDropService::run(bool fDaemonised /* = false */)
3146{
3147 RT_NOREF1(fDaemonised);
3148 LogFlowThisFunc(("fDaemonised=%RTbool\n", fDaemonised));
3149
3150 int rc;
3151 do
3152 {
3153 m_pCurDnD = new DragInstance(m_pDisplay, this);
3154 if (!m_pCurDnD)
3155 {
3156 rc = VERR_NO_MEMORY;
3157 break;
3158 }
3159
3160 /* Note: For multiple screen support in VBox it is not necessary to use
3161 * another screen number than zero. Maybe in the future it will become
3162 * necessary if VBox supports multiple X11 screens. */
3163 rc = m_pCurDnD->init(0 /* uScreenID */);
3164 /* Note: Can return VINF_PERMISSION_DENIED if HGCM host service is not available. */
3165 if (rc != VINF_SUCCESS)
3166 {
3167 if (RT_FAILURE(rc))
3168 VBClLogError("Unable to connect to drag and drop service, rc=%Rrc\n", rc);
3169 else if (rc == VINF_PERMISSION_DENIED)
3170 VBClLogError("Not available on host, terminating\n");
3171 break;
3172 }
3173
3174 VBClLogInfo("Started\n");
3175 VBClLogInfo("%sr%s\n", RTBldCfgVersion(), RTBldCfgRevisionStr());
3176
3177 /* Enter the main event processing loop. */
3178 do
3179 {
3180 DnDEvent e;
3181 RT_ZERO(e);
3182
3183 LogFlowFunc(("Waiting for new event ...\n"));
3184 rc = RTSemEventWait(m_hEventSem, RT_INDEFINITE_WAIT);
3185 if (RT_FAILURE(rc))
3186 break;
3187
3188 AssertMsg(m_eventQueue.size(), ("Event queue is empty when it shouldn't\n"));
3189
3190 e = m_eventQueue.first();
3191 m_eventQueue.removeFirst();
3192
3193 if (e.enmType == DnDEvent::DnDEventType_HGCM)
3194 {
3195 PVBGLR3DNDEVENT pVbglR3Event = e.hgcm;
3196 AssertPtrBreak(pVbglR3Event);
3197
3198 LogFlowThisFunc(("HGCM event, enmType=%RU32\n", pVbglR3Event->enmType));
3199 switch (pVbglR3Event->enmType)
3200 {
3201 case VBGLR3DNDEVENTTYPE_HG_ENTER:
3202 {
3203 if (pVbglR3Event->u.HG_Enter.cbFormats)
3204 {
3205 RTCList<RTCString> lstFormats =
3206 RTCString(pVbglR3Event->u.HG_Enter.pszFormats, pVbglR3Event->u.HG_Enter.cbFormats - 1).split("\r\n");
3207 rc = m_pCurDnD->hgEnter(lstFormats, pVbglR3Event->u.HG_Enter.dndLstActionsAllowed);
3208 if (RT_FAILURE(rc))
3209 break;
3210 /* Enter is always followed by a move event. */
3211 }
3212 else
3213 {
3214 AssertMsgFailed(("cbFormats is 0\n"));
3215 rc = VERR_INVALID_PARAMETER;
3216 break;
3217 }
3218
3219 /* Note: After HOST_DND_FN_HG_EVT_ENTER there immediately is a move
3220 * event, so fall through is intentional here. */
3221 RT_FALL_THROUGH();
3222 }
3223
3224 case VBGLR3DNDEVENTTYPE_HG_MOVE:
3225 {
3226 rc = m_pCurDnD->hgMove(pVbglR3Event->u.HG_Move.uXpos, pVbglR3Event->u.HG_Move.uYpos,
3227 pVbglR3Event->u.HG_Move.dndActionDefault);
3228 break;
3229 }
3230
3231 case VBGLR3DNDEVENTTYPE_HG_LEAVE:
3232 {
3233 rc = m_pCurDnD->hgLeave();
3234 break;
3235 }
3236
3237 case VBGLR3DNDEVENTTYPE_HG_DROP:
3238 {
3239 rc = m_pCurDnD->hgDrop(pVbglR3Event->u.HG_Drop.uXpos, pVbglR3Event->u.HG_Drop.uYpos,
3240 pVbglR3Event->u.HG_Drop.dndActionDefault);
3241 break;
3242 }
3243
3244 /* Note: VbglR3DnDRecvNextMsg() will return HOST_DND_FN_HG_SND_DATA_HDR when
3245 * the host has finished copying over all the data to the guest.
3246 *
3247 * The actual data transfer (and message processing for it) will be done
3248 * internally by VbglR3DnDRecvNextMsg() to not duplicate any code for different
3249 * platforms.
3250 *
3251 * The data header now will contain all the (meta) data the guest needs in
3252 * order to complete the DnD operation. */
3253 case VBGLR3DNDEVENTTYPE_HG_RECEIVE:
3254 {
3255 rc = m_pCurDnD->hgDataReceive(&pVbglR3Event->u.HG_Received.Meta);
3256 break;
3257 }
3258
3259 case VBGLR3DNDEVENTTYPE_HG_CANCEL:
3260 {
3261 m_pCurDnD->reset(); /** @todo Test this! */
3262 break;
3263 }
3264
3265#ifdef VBOX_WITH_DRAG_AND_DROP_GH
3266 case VBGLR3DNDEVENTTYPE_GH_ERROR:
3267 {
3268 m_pCurDnD->reset();
3269 break;
3270 }
3271
3272 case VBGLR3DNDEVENTTYPE_GH_REQ_PENDING:
3273 {
3274 rc = m_pCurDnD->ghIsDnDPending();
3275 break;
3276 }
3277
3278 case VBGLR3DNDEVENTTYPE_GH_DROP:
3279 {
3280 rc = m_pCurDnD->ghDropped(pVbglR3Event->u.GH_Drop.pszFormat, pVbglR3Event->u.GH_Drop.dndActionRequested);
3281 break;
3282 }
3283#endif
3284 default:
3285 {
3286 VBClLogError("Received unsupported message '%RU32'\n", pVbglR3Event->enmType);
3287 rc = VERR_NOT_SUPPORTED;
3288 break;
3289 }
3290 }
3291
3292 LogFlowFunc(("Message %RU32 processed with %Rrc\n", pVbglR3Event->enmType, rc));
3293 if (RT_FAILURE(rc))
3294 {
3295 /* Tell the user. */
3296 VBClLogError("Processing message %RU32 failed with %Rrc\n", pVbglR3Event->enmType, rc);
3297
3298 /* If anything went wrong, do a reset and start over. */
3299 m_pCurDnD->reset();
3300 }
3301
3302 VbglR3DnDEventFree(e.hgcm);
3303 e.hgcm = NULL;
3304 }
3305 else if (e.enmType == DnDEvent::DnDEventType_X11)
3306 {
3307 m_pCurDnD->onX11Event(e.x11);
3308 }
3309 else
3310 AssertMsgFailed(("Unknown event queue type %RU32\n", e.enmType));
3311
3312 /*
3313 * Make sure that any X11 requests have actually been sent to the
3314 * server, since we are waiting for responses using poll() on
3315 * another thread which will not automatically trigger flushing.
3316 */
3317 XFlush(m_pDisplay);
3318
3319 } while (!ASMAtomicReadBool(&m_fStop));
3320
3321 VBClLogInfo("Stopped with rc=%Rrc\n", rc);
3322
3323 } while (0);
3324
3325 if (m_pCurDnD)
3326 {
3327 delete m_pCurDnD;
3328 m_pCurDnD = NULL;
3329 }
3330
3331 LogFlowFuncLeaveRC(rc);
3332 return rc;
3333}
3334
3335void DragAndDropService::cleanup(void)
3336{
3337 LogFlowFuncEnter();
3338
3339 VBClLogInfo("Terminating ...\n");
3340
3341 /* Set stop flag first. */
3342 ASMAtomicXchgBool(&m_fStop, true);
3343
3344 /* Disconnect from the HGCM host service, which in turn will make the HGCM thread stop. */
3345 VbglR3DnDDisconnect(&m_dndCtx);
3346
3347 /*
3348 * Wait for threads to terminate.
3349 */
3350 int rcThread, rc2;
3351 if (m_hHGCMThread != NIL_RTTHREAD)
3352 {
3353 VBClLogInfo("Terminating HGCM thread ...\n");
3354
3355 rc2 = RTThreadWait(m_hHGCMThread, 30 * 1000 /* 30s timeout */, &rcThread);
3356 if (RT_SUCCESS(rc2))
3357 rc2 = rcThread;
3358
3359 if (RT_FAILURE(rc2))
3360 VBClLogInfo("Error waiting for HGCM thread to terminate: %Rrc\n", rc2);
3361 }
3362
3363 if (m_hX11Thread != NIL_RTTHREAD)
3364 {
3365 VBClLogInfo("Terminating X11 thread ...\n");
3366
3367 rc2 = RTThreadWait(m_hX11Thread, 200 /* 200ms timeout */, &rcThread);
3368 if (RT_SUCCESS(rc2))
3369 rc2 = rcThread;
3370
3371 if (RT_FAILURE(rc2))
3372 VBClLogError("Error waiting for X11 thread to terminate: %Rrc\n", rc2);
3373 }
3374
3375 VBClLogInfo("Terminating threads done\n");
3376
3377 xHelpers::destroyInstance();
3378
3379 VbglR3Term();
3380}
3381
3382/**
3383 * Static callback function for HGCM message processing thread. An internal
3384 * message queue will be filled which then will be processed by the according
3385 * drag'n drop instance.
3386 *
3387 * @returns IPRT status code.
3388 * @param hThread Thread handle to use.
3389 * @param pvUser Pointer to DragAndDropService instance to use.
3390 */
3391/* static */
3392DECLCALLBACK(int) DragAndDropService::hgcmEventThread(RTTHREAD hThread, void *pvUser)
3393{
3394 AssertPtrReturn(pvUser, VERR_INVALID_PARAMETER);
3395 DragAndDropService *pThis = static_cast<DragAndDropService*>(pvUser);
3396
3397 /* Let the service instance know in any case. */
3398 int rc = RTThreadUserSignal(hThread);
3399 AssertRCReturn(rc, rc);
3400
3401 /* Number of invalid messages skipped in a row. */
3402 int cMsgSkippedInvalid = 0;
3403 DnDEvent e;
3404
3405 do
3406 {
3407 RT_ZERO(e);
3408 e.enmType = DnDEvent::DnDEventType_HGCM;
3409
3410 /* Wait for new events. */
3411 rc = VbglR3DnDEventGetNext(&pThis->m_dndCtx, &e.hgcm);
3412 if (RT_SUCCESS(rc))
3413 {
3414 cMsgSkippedInvalid = 0; /* Reset skipped messages count. */
3415 pThis->m_eventQueue.append(e);
3416
3417 rc = RTSemEventSignal(pThis->m_hEventSem);
3418 if (RT_FAILURE(rc))
3419 break;
3420 }
3421 else
3422 {
3423 if (rc == VERR_INTERRUPTED) /* Can happen due to disconnect, for instance. */
3424 rc = VINF_SUCCESS;
3425
3426 if (RT_FAILURE(rc))
3427 {
3428 VBClLogError("Processing next message failed with rc=%Rrc\n", rc);
3429
3430 /* Old(er) hosts either are broken regarding DnD support or otherwise
3431 * don't support the stuff we do on the guest side, so make sure we
3432 * don't process invalid messages forever. */
3433
3434 if (cMsgSkippedInvalid++ > 32)
3435 {
3436 VBClLogError("Too many invalid/skipped messages from host, exiting ...\n");
3437 break;
3438 }
3439 }
3440 }
3441
3442 } while (!ASMAtomicReadBool(&pThis->m_fStop));
3443
3444 LogFlowFuncLeaveRC(rc);
3445 return rc;
3446}
3447
3448/**
3449 * Static callback function for X11 message processing thread. All X11 messages
3450 * will be directly routed to the according drag'n drop instance.
3451 *
3452 * @returns IPRT status code.
3453 * @param hThread Thread handle to use.
3454 * @param pvUser Pointer to DragAndDropService instance to use.
3455 */
3456/* static */
3457DECLCALLBACK(int) DragAndDropService::x11EventThread(RTTHREAD hThread, void *pvUser)
3458{
3459 AssertPtrReturn(pvUser, VERR_INVALID_PARAMETER);
3460 DragAndDropService *pThis = static_cast<DragAndDropService*>(pvUser);
3461 AssertPtr(pThis);
3462
3463 int rc = VINF_SUCCESS;
3464
3465 /* Note: Nothing to initialize here (yet). */
3466
3467 /* Set stop indicator on failure. */
3468 if (RT_FAILURE(rc))
3469 ASMAtomicXchgBool(&pThis->m_fStop, true);
3470
3471 /* Let the service instance know in any case. */
3472 int rc2 = RTThreadUserSignal(hThread);
3473 AssertRC(rc2);
3474
3475 DnDEvent e;
3476 do
3477 {
3478 /*
3479 * Wait for new events. We can't use XIfEvent here, cause this locks
3480 * the window connection with a mutex and if no X11 events occurs this
3481 * blocks any other calls we made to X11. So instead check for new
3482 * events and if there are not any new one, sleep for a certain amount
3483 * of time.
3484 */
3485 if (XEventsQueued(pThis->m_pDisplay, QueuedAfterFlush) > 0)
3486 {
3487 RT_ZERO(e);
3488 e.enmType = DnDEvent::DnDEventType_X11;
3489
3490 /* XNextEvent will block until a new X event becomes available. */
3491 XNextEvent(pThis->m_pDisplay, &e.x11);
3492 {
3493 /* At the moment we only have one drag instance. */
3494 DragInstance *pInstance = pThis->m_pCurDnD;
3495 AssertPtr(pInstance);
3496
3497 pInstance->onX11Event(e.x11);
3498 }
3499 }
3500 else
3501 RTThreadSleep(25 /* ms */);
3502
3503 } while (!ASMAtomicReadBool(&pThis->m_fStop));
3504
3505 LogFlowFuncLeaveRC(rc);
3506 return rc;
3507}
3508
3509/** Drag and drop magic number, start of a UUID. */
3510#define DRAGANDDROPSERVICE_MAGIC 0x67c97173
3511
3512/** VBoxClient service class wrapping the logic for the service while
3513 * the main VBoxClient code provides the daemon logic needed by all services.
3514 */
3515struct DRAGANDDROPSERVICE
3516{
3517 /** The service interface. */
3518 struct VBCLSERVICE *pInterface;
3519 /** Magic number for sanity checks. */
3520 uint32_t uMagic;
3521 /** Service object. */
3522 DragAndDropService mDragAndDrop;
3523};
3524
3525static const char *getName()
3526{
3527 return "Drag and Drop (DnD)";
3528}
3529
3530static const char *getPidFilePath()
3531{
3532 return ".vboxclient-draganddrop.pid";
3533}
3534
3535static int init(struct VBCLSERVICE **ppInterface)
3536{
3537 struct DRAGANDDROPSERVICE *pSelf = (struct DRAGANDDROPSERVICE *)ppInterface;
3538
3539 if (pSelf->uMagic != DRAGANDDROPSERVICE_MAGIC)
3540 VBClLogFatalError("Bad DnD service object!\n");
3541 return pSelf->mDragAndDrop.init();
3542}
3543
3544static int run(struct VBCLSERVICE **ppInterface, bool fDaemonised)
3545{
3546 struct DRAGANDDROPSERVICE *pSelf = (struct DRAGANDDROPSERVICE *)ppInterface;
3547
3548 if (pSelf->uMagic != DRAGANDDROPSERVICE_MAGIC)
3549 VBClLogFatalError("Bad DnD service object!\n");
3550 return pSelf->mDragAndDrop.run(fDaemonised);
3551}
3552
3553static void cleanup(struct VBCLSERVICE **ppInterface)
3554{
3555 struct DRAGANDDROPSERVICE *pSelf = (struct DRAGANDDROPSERVICE *)ppInterface;
3556
3557 if (pSelf->uMagic != DRAGANDDROPSERVICE_MAGIC)
3558 VBClLogFatalError("Bad DnD service object!\n");
3559 return pSelf->mDragAndDrop.cleanup();
3560}
3561
3562struct VBCLSERVICE vbclDragAndDropInterface =
3563{
3564 getName,
3565 getPidFilePath,
3566 init,
3567 run,
3568 cleanup
3569};
3570
3571/* Static factory. */
3572struct VBCLSERVICE **VBClGetDragAndDropService(void)
3573{
3574 struct DRAGANDDROPSERVICE *pService =
3575 (struct DRAGANDDROPSERVICE *)RTMemAlloc(sizeof(*pService));
3576
3577 if (!pService)
3578 VBClLogFatalError("Out of memory\n");
3579 pService->pInterface = &vbclDragAndDropInterface;
3580 pService->uMagic = DRAGANDDROPSERVICE_MAGIC;
3581 new(&pService->mDragAndDrop) DragAndDropService();
3582 return &pService->pInterface;
3583}
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