VirtualBox

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

Last change on this file since 60967 was 60967, checked in by vboxsync, 9 years ago

DnD/VBoxClient: Exit gracefully if DnD is not available on the host.

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