VirtualBox

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

Last change on this file since 74459 was 74459, checked in by vboxsync, 7 years ago

DnD/VBoxClient: Logging, renaming.

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

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