VirtualBox

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

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

DnD: Renaming -- prefix all HGCM function number defines with [GUEST|HOST]_DND_FN_ to better distinguish from other defines.

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