VirtualBox

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

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

Additions/VBoxClient: Big revamp of the internal service handling and termination fixes. A service now runs as part of a worker thread, while the main thread is used for initialization / shutdown and signal handling [build fix].

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