VirtualBox

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

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

DnD/X11: Doxygen build fix.

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