VirtualBox

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

Last change on this file since 78750 was 76553, checked in by vboxsync, 6 years ago

scm --update-copyright-year

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

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette