VirtualBox

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

Last change on this file since 83224 was 82968, checked in by vboxsync, 5 years ago

Copyright year updates by scm.

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