VirtualBox

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

Last change on this file since 52091 was 51556, checked in by vboxsync, 11 years ago

DnD: Added support for dynamically managing formats on the host.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 80.8 KB
Line 
1/** @file
2 * X11 guest client - Drag and Drop.
3 */
4
5/*
6 * Copyright (C) 2011-2014 Oracle Corporation
7 *
8 * This file is part of VirtualBox Open Source Edition (OSE), as
9 * available from http://www.virtualbox.org. This file is free software;
10 * you can redistribute it and/or modify it under the terms of the GNU
11 * General Public License (GPL) as published by the Free Software
12 * Foundation, in version 2 as it comes in the "COPYING" file of the
13 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
14 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
15 */
16
17#include <X11/Xlib.h>
18#include <X11/Xatom.h>
19#ifdef VBOX_DND_WITH_XTEST
20# include <X11/extensions/XTest.h>
21#endif
22
23#include <iprt/asm.h>
24#include <iprt/critsect.h>
25#include <iprt/thread.h>
26#include <iprt/time.h>
27
28#include <iprt/cpp/mtlist.h>
29#include <iprt/cpp/ministring.h>
30
31#include <limits.h>
32
33# ifdef LOG_GROUP
34 # undef LOG_GROUP
35# endif
36#define LOG_GROUP LOG_GROUP_GUEST_DND
37#include <VBox/log.h>
38#include <VBox/VBoxGuestLib.h>
39
40#include "VBox/HostServices/DragAndDropSvc.h"
41
42#include "VBoxClient.h"
43
44/* Enable this define to see the proxy window(s) when debugging
45 * their behavior. Don't have this enabled in release builds! */
46#ifdef DEBUG
47//# define VBOX_DND_DEBUG_WND
48#endif
49
50/* For X11 guest xDnD is used. See http://www.acc.umu.se/~vatten/XDND.html for
51 * a walk trough.
52 *
53 * H->G:
54 * For X11 this means mainly forwarding all the events from HGCM to the
55 * appropriate X11 events. There exists a proxy window, which is invisible and
56 * used for all the X11 communication. On a HGCM Enter event, we set our proxy
57 * window as XdndSelection owner with the given mime-types. On every HGCM move
58 * event, we move the X11 mouse cursor to the new position and query for the
59 * window below that position. Depending on if it is XdndAware, a new window or
60 * a known window, we send the appropriate X11 messages to it. On HGCM drop, we
61 * send a XdndDrop message to the current window and wait for a X11
62 * SelectionMessage from the target window. Because we didn't have the data in
63 * the requested mime-type, yet, we save that message and ask the host for the
64 * data. When the data is successfully received from the host, we put the data
65 * as a property to the window and send a X11 SelectionNotify event to the
66 * target window.
67 *
68 * G->H:
69 * This is a lot more trickery than H->G. When a pending event from HGCM
70 * arrives, we ask if there currently is an owner of the XdndSelection
71 * property. If so, our proxy window is shown (1x1, but without backing store)
72 * and some mouse event is triggered. This should be followed by an XdndEnter
73 * event send to the proxy window. From this event we can fetch the necessary
74 * info of the MIME types and allowed actions and send this back to the host.
75 * On a drop request from the host, we query for the selection and should get
76 * the data in the specified mime-type. This data is send back to the host.
77 * After that we send a XdndLeave event to the source window.
78 *
79 * To-do:
80 * - Cancelling (e.g. with ESC key) doesn't work.
81 *
82 * To-do:
83 * - INCR (incremental transfers) support.
84 * - Make this much more robust for crashes of the other party.
85 * - Really check for the Xdnd version and the supported features.
86 */
87
88#define VBOX_XDND_VERSION (4)
89#define VBOX_MAX_XPROPERTIES (LONG_MAX-1)
90
91/* Shared struct used for adding new X11 events and HGCM messages to a single
92 * event queue. */
93struct DnDEvent
94{
95 enum DnDEventType
96 {
97 HGCM_Type = 1,
98 X11_Type
99 };
100 DnDEventType type;
101 union
102 {
103 VBGLR3DNDHGCMEVENT hgcm;
104 XEvent x11;
105 };
106};
107
108enum XA_Type
109{
110 /* States */
111 XA_WM_STATE = 0,
112 /* Properties */
113 XA_TARGETS,
114 XA_MULTIPLE,
115 XA_INCR,
116 /* Mime Types */
117 XA_image_bmp,
118 XA_image_jpg,
119 XA_image_tiff,
120 XA_image_png,
121 XA_text_uri_list,
122 XA_text_uri,
123 XA_text_plain,
124 XA_TEXT,
125 /* xDnD */
126 XA_XdndSelection,
127 XA_XdndAware,
128 XA_XdndEnter,
129 XA_XdndLeave,
130 XA_XdndTypeList,
131 XA_XdndActionList,
132 XA_XdndPosition,
133 XA_XdndActionCopy,
134 XA_XdndActionMove,
135 XA_XdndActionLink,
136 XA_XdndStatus,
137 XA_XdndDrop,
138 XA_XdndFinished,
139 /* Our own stop marker */
140 XA_dndstop,
141 /* End marker */
142 XA_End
143};
144
145class DragAndDropService;
146
147/*******************************************************************************
148 *
149 * xHelpers Declaration
150 *
151 ******************************************************************************/
152
153class xHelpers
154{
155public:
156
157 static xHelpers *getInstance(Display *pDisplay = 0)
158 {
159 if (!m_pInstance)
160 {
161 AssertPtrReturn(pDisplay, NULL);
162 m_pInstance = new xHelpers(pDisplay);
163 }
164
165 return m_pInstance;
166 }
167
168 inline Display *display() const { return m_pDisplay; }
169 inline Atom xAtom(XA_Type e) const { return m_xAtoms[e]; }
170
171 inline Atom stringToxAtom(const char *pcszString) const
172 {
173 return XInternAtom(m_pDisplay, pcszString, False);
174 }
175 inline RTCString xAtomToString(Atom atom) const
176 {
177 if (atom == None) return "None";
178
179 char* pcsAtom = XGetAtomName(m_pDisplay, atom);
180 RTCString strAtom(pcsAtom);
181 XFree(pcsAtom);
182
183 return strAtom;
184 }
185
186 inline RTCString xAtomListToString(const RTCList<Atom> &formatList)
187 {
188 RTCString format;
189 for (size_t i = 0; i < formatList.size(); ++i)
190 format += xAtomToString(formatList.at(i)) + "\r\n";
191 return format;
192 }
193
194 RTCString xErrorToString(int xrc) const;
195 Window applicationWindowBelowCursor(Window parentWin) const;
196
197private:
198 xHelpers(Display *pDisplay)
199 : m_pDisplay(pDisplay)
200 {
201 /* Not all x11 atoms we use are defined in the headers. Create the
202 * additional one we need here. */
203 for (int i = 0; i < XA_End; ++i)
204 m_xAtoms[i] = XInternAtom(m_pDisplay, m_xAtomNames[i], False);
205 };
206
207 /* Private member vars */
208 static xHelpers *m_pInstance;
209 Display *m_pDisplay;
210 Atom m_xAtoms[XA_End];
211 static const char *m_xAtomNames[XA_End];
212};
213
214/* Some xHelpers convenience defines. */
215#define gX11 xHelpers::getInstance()
216#define xAtom(xa) gX11->xAtom((xa))
217#define xAtomToString(xa) gX11->xAtomToString((xa))
218
219/*******************************************************************************
220 *
221 * xHelpers Implementation
222 *
223 ******************************************************************************/
224
225xHelpers *xHelpers::m_pInstance = 0;
226/* Has to be in sync with the XA_Type enum. */
227const char *xHelpers::m_xAtomNames[] =
228{
229 /* States */
230 "WM_STATE",
231 /* Properties */
232 "TARGETS",
233 "MULTIPLE",
234 "INCR",
235 /* Mime Types */
236 "image/bmp",
237 "image/jpg",
238 "image/tiff",
239 "image/png",
240 "text/uri-list",
241 "text/uri",
242 "text/plain",
243 "TEXT",
244 /* xDnD */
245 "XdndSelection",
246 "XdndAware",
247 "XdndEnter",
248 "XdndLeave",
249 "XdndTypeList",
250 "XdndActionList",
251 "XdndPosition",
252 "XdndActionCopy",
253 "XdndActionMove",
254 "XdndActionLink",
255 "XdndStatus",
256 "XdndDrop",
257 "XdndFinished",
258 /* Our own stop marker */
259 "dndstop"
260};
261
262RTCString xHelpers::xErrorToString(int xrc) const
263{
264 switch (xrc)
265 {
266 case Success: return RTCStringFmt("%d (Success)", xrc); break;
267 case BadRequest: return RTCStringFmt("%d (BadRequest)", xrc); break;
268 case BadValue: return RTCStringFmt("%d (BadValue)", xrc); break;
269 case BadWindow: return RTCStringFmt("%d (BadWindow)", xrc); break;
270 case BadPixmap: return RTCStringFmt("%d (BadPixmap)", xrc); break;
271 case BadAtom: return RTCStringFmt("%d (BadAtom)", xrc); break;
272 case BadCursor: return RTCStringFmt("%d (BadCursor)", xrc); break;
273 case BadFont: return RTCStringFmt("%d (BadFont)", xrc); break;
274 case BadMatch: return RTCStringFmt("%d (BadMatch)", xrc); break;
275 case BadDrawable: return RTCStringFmt("%d (BadDrawable)", xrc); break;
276 case BadAccess: return RTCStringFmt("%d (BadAccess)", xrc); break;
277 case BadAlloc: return RTCStringFmt("%d (BadAlloc)", xrc); break;
278 case BadColor: return RTCStringFmt("%d (BadColor)", xrc); break;
279 case BadGC: return RTCStringFmt("%d (BadGC)", xrc); break;
280 case BadIDChoice: return RTCStringFmt("%d (BadIDChoice)", xrc); break;
281 case BadName: return RTCStringFmt("%d (BadName)", xrc); break;
282 case BadLength: return RTCStringFmt("%d (BadLength)", xrc); break;
283 case BadImplementation: return RTCStringFmt("%d (BadImplementation)", xrc); break;
284 }
285 return RTCStringFmt("%d (unknown)", xrc);
286}
287
288/* Todo: make this iterative */
289Window xHelpers::applicationWindowBelowCursor(Window wndParent) const
290{
291 /* No parent, nothing to do. */
292 if(wndParent == 0)
293 return 0;
294
295 Window wndApp = 0;
296 int cProps = -1;
297 /* Fetch all x11 window properties of the parent window. */
298 Atom *pProps = XListProperties(m_pDisplay, wndParent, &cProps);
299 if (cProps > 0)
300 {
301 /* We check the window for the WM_STATE property. */
302 for (int i = 0; i < cProps; ++i)
303 if (pProps[i] == xAtom(XA_WM_STATE))
304 {
305 /* Found it. */
306 wndApp = wndParent;
307 break;
308 }
309 /* Cleanup */
310 XFree(pProps);
311 }
312
313 if (!wndApp)
314 {
315 Window wndChild, wndTemp;
316 int tmp;
317 unsigned int utmp;
318
319 /* Query the next child window of the parent window at the current
320 * mouse position. */
321 XQueryPointer(m_pDisplay, wndParent, &wndTemp, &wndChild, &tmp, &tmp, &tmp, &tmp, &utmp);
322
323 /* Recursive call our self to dive into the child tree. */
324 wndApp = applicationWindowBelowCursor(wndChild);
325 }
326
327 return wndApp;
328}
329
330/*******************************************************************************
331 *
332 * DragInstance Declaration
333 *
334 ******************************************************************************/
335
336/* For now only one DragInstance will exits when the app is running. In the
337 * future the support for having more than one D&D operation supported at the
338 * time will be necessary. */
339class DragInstance
340{
341public:
342
343 enum State
344 {
345 Uninitialized = 0,
346 Initialized,
347 Dragging,
348 Dropped
349 };
350
351 enum Mode
352 {
353 Unknown = 0,
354 HG,
355 GH
356 };
357
358 DragInstance(Display *pDisplay, DragAndDropService *pParent);
359
360public:
361
362 int init(uint32_t u32ScreenId);
363 void uninit(void);
364 void reset(void);
365
366 /* X11 message processing. */
367 int onX11ClientMessage(const XEvent &e);
368 int onX11SelectionNotify(const XEvent &e);
369 int onX11SelectionRequest(const XEvent &e);
370 int onX11Event(const XEvent &e);
371 bool waitForX11Msg(XEvent &evX, int iType, RTMSINTERVAL uTimeoutMS = 100);
372 bool waitForX11ClientMsg(XClientMessageEvent &evMsg, Atom aType, RTMSINTERVAL uTimeoutMS = 100);
373
374 /* H->G */
375 int hgEnter(const RTCList<RTCString> &formats, uint32_t actions);
376 int hgMove(uint32_t u32xPos, uint32_t u32yPos, uint32_t action);
377 int hgDrop();
378 int hgDataReceived(void *pvData, uint32_t cData);
379
380#ifdef VBOX_WITH_DRAG_AND_DROP_GH
381 /* G->H */
382 int ghIsDnDPending();
383 int ghDropped(const RTCString &strFormat, uint32_t action);
384#endif
385
386 /* X11 helpers. */
387 int mouseCursorMove(int iPosX, int iPosY) const;
388 void mouseButtonSet(Window wndDest, int rx, int ry, int iButton, bool fPress) const;
389 int proxyWinShow(int *piRootX = NULL, int *piRootY = NULL, bool fMouseMove = false) const;
390 int proxyWinHide(void);
391 void registerForEvents(Window w) const;
392
393 void setActionsWindowProperty(Window wndThis, const RTCList<Atom> &lstActions) const;
394 void clearActionsWindowProperty(Window wndThis) const;
395 void setFormatsWindowProperty(Window wndThis, Atom property) const;
396 void clearFormatsWindowProperty(Window wndThis) const;
397
398 RTCList<Atom> toAtomList(const RTCList<RTCString> &lstFormats) const;
399 RTCList<Atom> toAtomList(void *pvData, uint32_t cbData) const;
400 static Atom toX11Action(uint32_t uAction);
401 static RTCList<Atom> toX11Actions(uint32_t uActions);
402 static uint32_t toHGCMAction(Atom atom);
403 static uint32_t toHGCMActions(const RTCList<Atom> &actionsList);
404
405protected:
406
407 uint32_t m_uClientID;
408 DragAndDropService *m_pParent;
409 Display *m_pDisplay;
410 int m_screenId;
411 Screen *m_pScreen;
412 Window m_wndRoot;
413 Window m_wndProxy;
414 Window m_wndCur;
415 long m_curVer;
416 RTCList<Atom> m_formats;
417 RTCList<Atom> m_actions;
418 /** Deferred host to guest selection event for sending to the
419 * target window as soon as data from the host arrived. */
420 XEvent m_eventHgSelection;
421 /** Current operation mode. */
422 Mode m_mode;
423 /** Current state of operation mode. */
424 State m_state;
425 /** The instance's own X event queue. */
426 RTCMTList<XEvent> m_eventQueue;
427 /** Critical section for providing serialized access to list
428 * event queue's contents. */
429 RTCRITSECT m_eventQueueCS;
430 /** Event for notifying this instance in case of a new
431 * event. */
432 RTSEMEVENT m_hEventSem;
433 /** List of allowed formats. */
434 RTCList<RTCString> m_lstAllowedFormats;
435};
436
437/*******************************************************************************
438 *
439 * DragAndDropService Declaration
440 *
441 ******************************************************************************/
442
443class DragAndDropService : public VBoxClient::Service
444{
445public:
446 DragAndDropService(void)
447 : m_pDisplay(0)
448 , m_hHGCMThread(NIL_RTTHREAD)
449 , m_hX11Thread(NIL_RTTHREAD)
450 , m_hEventSem(NIL_RTSEMEVENT)
451 , m_pCurDnD(0)
452 , m_fSrvStopping(false)
453 {}
454
455 virtual const char *getPidFilePath() { return ".vboxclient-draganddrop.pid"; }
456
457 virtual int run(bool fDaemonised = false);
458
459 virtual void cleanup(void)
460 {
461 /* Nothing to do, everything should be cleaned up automatically when the
462 * user process/X11 client exits. */
463 };
464
465private:
466 int x11DragAndDropInit(void);
467 static int hgcmEventThread(RTTHREAD hThread, void *pvUser);
468 static int x11EventThread(RTTHREAD hThread, void *pvUser);
469
470 void clearEventQueue();
471
472 /* Usually XCheckMaskEvent could be used for querying selected x11 events.
473 * Unfortunately this doesn't work exactly with the events we need. So we
474 * use this predicate method below and XCheckIfEvent. */
475 static bool isDnDRespondEvent(Display * /* pDisplay */, XEvent *pEvent, char *pUser)
476 {
477 if (!pEvent)
478 return false;
479 if ( pEvent->type == SelectionClear
480 || pEvent->type == ClientMessage
481 || pEvent->type == MotionNotify
482 || pEvent->type == SelectionRequest)
483// || ( pEvent->type == ClientMessage
484// && reinterpret_cast<XClientMessageEvent*>(pEvent)->window == reinterpret_cast<Window>(pUser))
485// || ( pEvent->type == SelectionRequest
486// && reinterpret_cast<XSelectionRequestEvent*>(pEvent)->requestor == reinterpret_cast<Window>(pUser)))
487 return true;
488 return false;
489 }
490
491 /* Private member vars */
492 Display *m_pDisplay;
493
494 /** Our (thread-safe) event queue with
495 * mixed events (DnD HGCM / X11). */
496 RTCMTList<DnDEvent> m_eventQueue;
497 /** Critical section for providing serialized access to list
498 * event queue's contents. */
499 RTCRITSECT m_eventQueueCS;
500 RTTHREAD m_hHGCMThread;
501 RTTHREAD m_hX11Thread;
502 RTSEMEVENT m_hEventSem;
503 DragInstance *m_pCurDnD;
504 bool m_fSrvStopping;
505
506 friend class DragInstance;
507};
508
509/*******************************************************************************
510 *
511 * DragInstanc Implementation
512 *
513 ******************************************************************************/
514
515DragInstance::DragInstance(Display *pDisplay, DragAndDropService *pParent)
516 : m_uClientID(0)
517 , m_pParent(pParent)
518 , m_pDisplay(pDisplay)
519 , m_pScreen(0)
520 , m_wndRoot(0)
521 , m_wndProxy(0)
522 , m_wndCur(0)
523 , m_curVer(-1)
524 , m_mode(Unknown)
525 , m_state(Uninitialized)
526{
527 uninit();
528}
529
530void DragInstance::uninit(void)
531{
532 reset();
533 if (m_wndProxy != 0)
534 XDestroyWindow(m_pDisplay, m_wndProxy);
535
536 if (m_uClientID)
537 {
538 VbglR3DnDDisconnect(m_uClientID);
539 m_uClientID = 0;
540 }
541
542 m_state = Uninitialized;
543 m_screenId = -1;
544 m_pScreen = 0;
545 m_wndRoot = 0;
546 m_wndProxy = 0;
547}
548
549void DragInstance::reset(void)
550{
551 /* Hide the proxy win. */
552 proxyWinHide();
553
554 /* If we are currently the Xdnd selection owner, clear that. */
555 Window w = XGetSelectionOwner(m_pDisplay, xAtom(XA_XdndSelection));
556 if (w == m_wndProxy)
557 XSetSelectionOwner(m_pDisplay, xAtom(XA_XdndSelection), None, CurrentTime);
558
559 /* Clear any other DnD specific data on the proxy win. */
560 clearFormatsWindowProperty(m_wndProxy);
561 clearActionsWindowProperty(m_wndProxy);
562
563 /* Reset the internal state. */
564 m_actions.clear();
565 m_formats.clear();
566 m_wndCur = 0;
567 m_curVer = -1;
568 m_state = Initialized;
569 m_eventQueue.clear();
570}
571
572int DragInstance::init(uint32_t u32ScreenId)
573{
574 int rc;
575
576 do
577 {
578 uninit();
579
580 rc = VbglR3DnDConnect(&m_uClientID);
581 if (RT_FAILURE(rc))
582 break;
583
584 rc = RTSemEventCreate(&m_hEventSem);
585 if (RT_FAILURE(rc))
586 break;
587
588 rc = RTCritSectInit(&m_eventQueueCS);
589 if (RT_FAILURE(rc))
590 break;
591
592 /*
593 * Enough screens configured in the x11 server?
594 */
595 if ((int)u32ScreenId > ScreenCount(m_pDisplay))
596 {
597 rc = VERR_INVALID_PARAMETER;
598 break;
599 }
600 /* Get the screen number from the x11 server. */
601// pDrag->screen = ScreenOfDisplay(m_pDisplay, u32ScreenId);
602// if (!pDrag->screen)
603// {
604// rc = VERR_GENERAL_FAILURE;
605// break;
606// }
607 m_screenId = u32ScreenId;
608 /* Now query the corresponding root window of this screen. */
609 m_wndRoot = RootWindow(m_pDisplay, m_screenId);
610 if (!m_wndRoot)
611 {
612 rc = VERR_GENERAL_FAILURE;
613 break;
614 }
615
616 /*
617 * Create an invisible window which will act as proxy for the DnD
618 * operation. This window will be used for both the GH and HG
619 * direction.
620 */
621 XSetWindowAttributes attr;
622 RT_ZERO(attr);
623 attr.event_mask = EnterWindowMask | LeaveWindowMask
624 | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask;
625 attr.do_not_propagate_mask = NoEventMask;
626 attr.override_redirect = True;
627#ifdef VBOX_DND_DEBUG_WND
628 attr.background_pixel = WhitePixel(m_pDisplay, m_screenId);
629#endif
630 m_wndProxy = XCreateWindow(m_pDisplay, m_wndRoot /* Parent */,
631 0, 0, /* Position */
632 1, 1, /* Width + height */
633 0, /* Border width */
634 CopyFromParent, /* Depth */
635 InputOnly, /* Class */
636 CopyFromParent, /* Visual */
637 CWOverrideRedirect | CWDontPropagate, /* Value mask */
638 &attr /* Attributes for value mask */);
639 if (!m_wndProxy)
640 {
641 rc = VERR_GENERAL_FAILURE;
642 break;
643 }
644
645 LogFlowThisFunc(("Created proxy window 0x%x at m_wndRoot=0x%x ...\n",
646 m_wndProxy, m_wndRoot));
647
648 /* Set the window's name for easier lookup. */
649 XStoreName(m_pDisplay, m_wndProxy, "VBoxClientWndDnD");
650
651 /* Make the new window Xdnd aware. */
652 Atom ver = VBOX_XDND_VERSION;
653 XChangeProperty(m_pDisplay, m_wndProxy, xAtom(XA_XdndAware), XA_ATOM, 32, PropModeReplace,
654 reinterpret_cast<unsigned char*>(&ver), 1);
655 } while (0);
656
657 if (RT_SUCCESS(rc))
658 {
659 m_state = Initialized;
660 }
661 else
662 LogRel(("DnD: Initializing drag instance for screen %RU32 failed with rc=%Rrc\n",
663 u32ScreenId, rc));
664
665 LogFlowFuncLeaveRC(rc);
666 return rc;
667}
668
669int DragInstance::onX11ClientMessage(const XEvent &e)
670{
671 AssertReturn(e.type == ClientMessage, VERR_INVALID_PARAMETER);
672
673 LogFlowThisFunc(("m_mode=%d, m_state=%d\n", m_mode, m_state));
674 LogFlowThisFunc(("Event wnd=%#x, msg=%s\n",
675 e.xclient.window,
676 xAtomToString(e.xclient.message_type).c_str()));
677 int rc;
678
679 switch (m_mode)
680 {
681 case HG:
682 {
683 /* Client messages are used to inform us about the status of a XdndAware
684 * window, in response of some events we send to them. */
685 if ( e.xclient.message_type == xAtom(XA_XdndStatus)
686 && m_wndCur == static_cast<Window>(e.xclient.data.l[0]))
687 {
688 /* The XdndStatus message tell us if the window will accept the DnD
689 * event and with which action. We immediately send this info down to
690 * the host as a response of a previous DnD message. */
691 LogFlowThisFunc(("XA_XdndStatus wnd=%#x, accept=%RTbool, action=%s\n",
692 e.xclient.data.l[0],
693 ASMBitTest(&e.xclient.data.l[1], 0),
694 xAtomToString(e.xclient.data.l[4]).c_str()));
695
696 uint32_t uAction = DND_IGNORE_ACTION;
697 /** @todo Compare this with the allowed actions. */
698 if (ASMBitTest(&e.xclient.data.l[1], 0))
699 uAction = toHGCMAction(static_cast<Atom>(e.xclient.data.l[4]));
700
701 rc = VbglR3DnDHGAcknowledgeOperation(m_uClientID, uAction);
702 }
703 else if (e.xclient.message_type == xAtom(XA_XdndFinished))
704 {
705 rc = VINF_SUCCESS;
706
707 /* This message is send on a un/successful DnD drop request. */
708 LogFlowThisFunc(("XA_XdndFinished: wnd=%#x, success=%RTbool, action=%s\n",
709 e.xclient.data.l[0],
710 ASMBitTest(&e.xclient.data.l[1], 0),
711 xAtomToString(e.xclient.data.l[2]).c_str()));
712 reset();
713 }
714 else
715 {
716 LogFlowThisFunc(("Unhandled: wnd=%#x, msg=%s\n",
717 e.xclient.data.l[0], xAtomToString(e.xclient.message_type).c_str()));
718 rc = VERR_NOT_SUPPORTED;
719 }
720
721 break;
722 }
723
724 case GH:
725 {
726 LogFlowThisFunc(("Enqueuing ClientMessage\n"));
727
728 m_eventQueue.append(e);
729 rc = RTSemEventSignal(m_hEventSem);
730 break;
731 }
732
733 default:
734 {
735 LogFlowThisFunc(("Unhandled: wnd=%#x, msg=%s\n",
736 e.xclient.data.l[0], xAtomToString(e.xclient.message_type).c_str()));
737 rc = VERR_INVALID_STATE;
738 break;
739 }
740 }
741
742 LogFlowThisFunc(("Returning rc=%Rrc\n", rc));
743 return rc;
744}
745
746int DragInstance::onX11SelectionNotify(const XEvent &e)
747{
748 AssertReturn(e.type == SelectionNotify, VERR_INVALID_PARAMETER);
749
750 LogFlowThisFunc(("m_mode=%d, m_state=%d\n", m_mode, m_state));
751
752 int rc = VINF_SUCCESS;
753
754 switch (m_mode)
755 {
756 case GH:
757 {
758 if (m_state == Dropped)
759 {
760 LogFlowThisFunc(("Enqueuing SelectionNotify\n"));
761
762 m_eventQueue.append(e);
763 rc = RTSemEventSignal(m_hEventSem);
764 }
765 break;
766 }
767
768 default:
769 {
770 LogFlowThisFunc(("Unhandled: wnd=%#x, msg=%s\n",
771 e.xclient.data.l[0], xAtomToString(e.xclient.message_type).c_str()));
772 rc = VERR_INVALID_STATE;
773 break;
774 }
775 }
776
777 LogFlowThisFunc(("Returning rc=%Rrc\n", rc));
778 return rc;
779}
780
781
782int DragInstance::onX11SelectionRequest(const XEvent &e)
783{
784 AssertReturn(e.type == SelectionRequest, VERR_INVALID_PARAMETER);
785
786 LogFlowThisFunc(("m_mode=%d, m_state=%d\n", m_mode, m_state));
787 LogFlowThisFunc(("Event owner=%#x, requestor=%#x, selection=%s, target=%s, prop=%s, time=%u\n",
788 e.xselectionrequest.owner,
789 e.xselectionrequest.requestor,
790 xAtomToString(e.xselectionrequest.selection).c_str(),
791 xAtomToString(e.xselectionrequest.target).c_str(),
792 xAtomToString(e.xselectionrequest.property).c_str(),
793 e.xselectionrequest.time));
794 int rc;
795
796 switch (m_mode)
797 {
798 case HG:
799 {
800 rc = VINF_SUCCESS;
801
802 /*
803 * A window is asking for some data. Normally here the data would be copied
804 * into the selection buffer and send to the requestor. Obviously we can't
805 * do that, because we first need to ask the host for the data of the
806 * requested MIME type. This is done and later answered with the correct
807 * data -- see DragInstance::hgDataReceived().
808 */
809
810 /* Is the requestor asking for the possible MIME types? */
811 if (e.xselectionrequest.target == xAtom(XA_TARGETS))
812 {
813 LogFlowThisFunc(("wnd=%#x asking for target list\n", e.xselectionrequest.requestor));
814
815 /* If so, set the window property with the formats on the requestor
816 * window. */
817 setFormatsWindowProperty(e.xselectionrequest.requestor, e.xselectionrequest.property);
818
819 XEvent s;
820 RT_ZERO(s);
821 s.xselection.type = SelectionNotify;
822 s.xselection.display = e.xselection.display;
823 s.xselection.time = e.xselectionrequest.time;
824 s.xselection.selection = e.xselectionrequest.selection;
825 s.xselection.requestor = e.xselectionrequest.requestor;
826 s.xselection.target = e.xselectionrequest.target;
827 s.xselection.property = e.xselectionrequest.property;
828
829 int xrc = XSendEvent(e.xselection.display, e.xselectionrequest.requestor, False, 0, &s);
830 if (RT_UNLIKELY(xrc == 0))
831 LogFlowThisFunc(("Error sending SelectionNotify event to wnd=%#x\n", e.xselectionrequest.requestor));
832 }
833 /* Is the requestor asking for a specific MIME type (we support)? */
834 else if (m_formats.contains(e.xselectionrequest.target))
835 {
836 LogFlowThisFunc(("wnd=%#x asking for data, format=%s\n",
837 e.xselectionrequest.requestor, xAtomToString(e.xselectionrequest.target).c_str()));
838
839 /* If so, we need to inform the host about this request. Save the
840 * selection request event for later use. */
841 if ( m_state != Dropped)
842 // || m_curWin != e.xselectionrequest.requestor)
843 {
844 LogFlowThisFunc(("Wrong state, refusing request\n"));
845
846 XEvent s;
847 RT_ZERO(s);
848 s.xselection.type = SelectionNotify;
849 s.xselection.display = e.xselection.display;
850 s.xselection.time = e.xselectionrequest.time;
851 s.xselection.selection = e.xselectionrequest.selection;
852 s.xselection.requestor = e.xselectionrequest.requestor;
853 s.xselection.target = None;
854 s.xselection.property = e.xselectionrequest.property;
855
856 int xrc = XSendEvent(e.xselection.display, e.xselectionrequest.requestor, False, 0, &s);
857 if (RT_UNLIKELY(xrc == 0))
858 LogFlowThisFunc(("Error sending SelectionNotify event to wnd=%#x\n", e.xselectionrequest.requestor));
859 }
860 else
861 {
862 LogFlowThisFunc(("Saving selection notify message\n"));
863
864 memcpy(&m_eventHgSelection, &e, sizeof(XEvent));
865
866 RTCString strFormat = xAtomToString(e.xselectionrequest.target);
867 Assert(strFormat.isNotEmpty());
868 rc = VbglR3DnDHGRequestData(m_uClientID, strFormat.c_str());
869 LogFlowThisFunc(("Requesting data from host as \"%s\", rc=%Rrc\n",
870 strFormat.c_str(), rc));
871 }
872 }
873 /* Anything else. */
874 else
875 {
876 LogFlowThisFunc(("Refusing unknown command\n"));
877
878 /* We don't understand this request message and therefore answer with an
879 * refusal messages. */
880 XEvent s;
881 RT_ZERO(s);
882 s.xselection.type = SelectionNotify;
883 s.xselection.display = e.xselection.display;
884 s.xselection.time = e.xselectionrequest.time;
885 s.xselection.selection = e.xselectionrequest.selection;
886 s.xselection.requestor = e.xselectionrequest.requestor;
887 s.xselection.target = None; /* default is refusing */
888 s.xselection.property = None; /* default is refusing */
889 int xrc = XSendEvent(e.xselection.display, e.xselectionrequest.requestor, False, 0, &s);
890 if (RT_UNLIKELY(xrc == 0))
891 LogFlowThisFunc(("Error sending SelectionNotify event to wnd=%#x\n", e.xselectionrequest.requestor));
892 }
893
894 break;
895 }
896
897 default:
898 {
899 LogFlowThisFunc(("Unhandled: wnd=%#x, msg=%s\n",
900 e.xclient.data.l[0], xAtomToString(e.xclient.message_type).c_str()));
901 rc = VERR_INVALID_STATE;
902 break;
903 }
904 }
905
906 LogFlowThisFunc(("Returning rc=%Rrc\n", rc));
907 return rc;
908}
909
910int DragInstance::onX11Event(const XEvent &e)
911{
912 int rc;
913
914 LogFlowThisFunc(("X11 event, type=%d\n", e.type));
915 switch (e.type)
916 {
917 case ButtonPress:
918 LogFlowThisFunc(("ButtonPress\n"));
919 rc = VINF_SUCCESS;
920 break;
921
922 case ButtonRelease:
923 LogFlowThisFunc(("ButtonRelease\n"));
924 rc = VINF_SUCCESS;
925 break;
926
927 case ClientMessage:
928 rc = onX11ClientMessage(e);
929 break;
930
931 case SelectionClear:
932 LogFlowThisFunc(("SelectionClear\n"));
933 reset();
934 rc = VINF_SUCCESS;
935 break;
936
937 case SelectionNotify:
938 rc = onX11SelectionNotify(e);
939 break;
940
941 case SelectionRequest:
942 rc = onX11SelectionRequest(e);
943 break;
944
945 /*case MotionNotify:
946 hide();
947 break;*/
948
949 default:
950 rc = VERR_NOT_IMPLEMENTED;
951 break;
952 }
953
954 LogFlowThisFunc(("rc=%Rrc\n", rc));
955 return rc;
956}
957
958#ifdef VBOX_WITH_DRAG_AND_DROP_GH
959bool DragInstance::waitForX11Msg(XEvent &evX, int iType,
960 RTMSINTERVAL uTimeoutMS /* = 100 */)
961{
962 LogFlowThisFunc(("iType=%d, uTimeoutMS=%RU32, cEventQueue=%zu\n",
963 iType, uTimeoutMS, m_eventQueue.size()));
964
965 bool fFound = false;
966 const uint64_t uiStart = RTTimeMilliTS();
967 do
968 {
969 /* Check if there is a client message in the queue. */
970 for (size_t i = 0; i < m_eventQueue.size(); i++)
971 {
972 int rc2 = RTCritSectEnter(&m_eventQueueCS);
973 if (RT_SUCCESS(rc2))
974 {
975 XEvent e = m_eventQueue.at(i);
976
977 fFound = e.type == iType;
978 if (fFound)
979 {
980 m_eventQueue.removeAt(i);
981 evX = e;
982 }
983
984 rc2 = RTCritSectLeave(&m_eventQueueCS);
985 AssertRC(rc2);
986
987 if (fFound)
988 break;
989 }
990 }
991
992 if (fFound)
993 break;
994
995 int rc2 = RTSemEventWait(m_hEventSem, 25 /* ms */);
996 if ( RT_FAILURE(rc2)
997 && rc2 != VERR_TIMEOUT)
998 {
999 LogFlowFunc(("Waiting failed with rc=%Rrc\n", rc2));
1000 break;
1001 }
1002 }
1003 while (RTTimeMilliTS() - uiStart < uTimeoutMS);
1004
1005 LogFlowThisFunc(("Returning fFound=%RTbool, msRuntime=%RU64\n",
1006 fFound, RTTimeMilliTS() - uiStart));
1007 return fFound;
1008}
1009
1010bool DragInstance::waitForX11ClientMsg(XClientMessageEvent &evMsg, Atom aType,
1011 RTMSINTERVAL uTimeoutMS /*= 100 */)
1012{
1013 LogFlowThisFunc(("aType=%s, uTimeoutMS=%RU32, cEventQueue=%zu\n",
1014 xAtomToString(aType).c_str(), uTimeoutMS, m_eventQueue.size()));
1015
1016 bool fFound = false;
1017 const uint64_t uiStart = RTTimeMilliTS();
1018 do
1019 {
1020 /* Check if there is a client message in the queue. */
1021 for (size_t i = 0; i < m_eventQueue.size(); i++)
1022 {
1023 int rc2 = RTCritSectEnter(&m_eventQueueCS);
1024 if (RT_SUCCESS(rc2))
1025 {
1026 XEvent e = m_eventQueue.at(i);
1027 if (e.type == ClientMessage)
1028 {
1029 m_eventQueue.removeAt(i);
1030 evMsg = e.xclient;
1031
1032 fFound = true;
1033 }
1034
1035 rc2 = RTCritSectLeave(&m_eventQueueCS);
1036 AssertRC(rc2);
1037
1038 if (fFound)
1039 break;
1040 }
1041 }
1042
1043 if (fFound)
1044 break;
1045
1046 int rc2 = RTSemEventWait(m_hEventSem, 25 /* ms */);
1047 if ( RT_FAILURE(rc2)
1048 && rc2 != VERR_TIMEOUT)
1049 {
1050 LogFlowFunc(("Waiting failed with rc=%Rrc\n", rc2));
1051 break;
1052 }
1053 }
1054 while (RTTimeMilliTS() - uiStart < uTimeoutMS);
1055
1056 LogFlowThisFunc(("Returning fFound=%RTbool, msRuntime=%RU64\n",
1057 fFound, RTTimeMilliTS() - uiStart));
1058 return fFound;
1059}
1060#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
1061
1062/*
1063 * Host -> Guest
1064 */
1065
1066int DragInstance::hgEnter(const RTCList<RTCString> &formats, uint32_t uActions)
1067{
1068 reset();
1069
1070#ifdef DEBUG
1071 LogFlowThisFunc(("uActions=0x%x, lstFormats=%zu: ", uActions, formats.size()));
1072 for (size_t i = 0; i < formats.size(); ++i)
1073 LogFlow(("'%s' ", formats.at(i).c_str()));
1074 LogFlow(("\n"));
1075#endif
1076
1077 m_formats = toAtomList(formats);
1078
1079 /* If we have more than 3 formats we have to use the type list extension. */
1080 if (m_formats.size() > 3)
1081 setFormatsWindowProperty(m_wndProxy, xAtom(XA_XdndTypeList));
1082
1083 /* Announce the possible actions */
1084 setActionsWindowProperty(m_wndProxy, toX11Actions(uActions));
1085
1086 /* Set the DnD selection owner to our window. */
1087 XSetSelectionOwner(m_pDisplay, xAtom(XA_XdndSelection), m_wndProxy, CurrentTime);
1088
1089 m_mode = HG;
1090 m_state = Dragging;
1091
1092 return VINF_SUCCESS;
1093}
1094
1095int DragInstance::hgMove(uint32_t u32xPos, uint32_t u32yPos, uint32_t uAction)
1096{
1097 LogFlowThisFunc(("u32xPos=%RU32, u32yPos=%RU32, uAction=%RU32\n",
1098 u32xPos, u32yPos, uAction));
1099
1100 if ( m_mode != HG
1101 || m_state != Dragging)
1102 return VERR_INVALID_STATE;
1103
1104 int rc = VINF_SUCCESS;
1105 int xrc = Success;
1106
1107 /* Move the mouse cursor within the guest. */
1108 mouseCursorMove(u32xPos, u32yPos);
1109
1110 long newVer = -1; /* This means the current window is _not_ XdndAware. */
1111
1112 /* Search for the application window below the cursor. */
1113 Window wndCursor = gX11->applicationWindowBelowCursor(m_wndRoot);
1114 if (wndCursor != None)
1115 {
1116 /* Temp stuff for the XGetWindowProperty call. */
1117 Atom atmp;
1118 int fmt;
1119 unsigned long cItems, cbRemaining;
1120 unsigned char *pcData = NULL;
1121
1122 /* Query the XdndAware property from the window. We are interested in
1123 * the version and if it is XdndAware at all. */
1124 xrc = XGetWindowProperty(m_pDisplay, wndCursor, xAtom(XA_XdndAware),
1125 0, 2, False, AnyPropertyType,
1126 &atmp, &fmt, &cItems, &cbRemaining, &pcData);
1127
1128 if (RT_UNLIKELY(xrc != Success))
1129 LogFlowThisFunc(("Error in getting the window property: %s\n", gX11->xErrorToString(xrc).c_str()));
1130 else
1131 {
1132 if (RT_UNLIKELY(pcData == NULL || fmt != 32 || cItems != 1))
1133 LogFlowThisFunc(("Wrong properties pcData=%#x, iFmt=%u, cItems=%u\n", pcData, fmt, cItems));
1134 else
1135 {
1136 newVer = reinterpret_cast<long*>(pcData)[0];
1137 LogFlowThisFunc(("wndCursor=%#x, XdndAware=%u\n", newVer));
1138 }
1139
1140 XFree(pcData);
1141 }
1142 }
1143
1144 /*
1145 * Is the window under the cursor another one than our current one?
1146 */
1147 if ( wndCursor != m_wndCur
1148 && m_curVer != -1)
1149 {
1150 LogFlowThisFunc(("Leaving window=%#x\n", m_wndCur));
1151
1152 /* We left the current XdndAware window. Announce this to the window. */
1153 XClientMessageEvent m;
1154 RT_ZERO(m);
1155 m.type = ClientMessage;
1156 m.display = m_pDisplay;
1157 m.window = m_wndCur;
1158 m.message_type = xAtom(XA_XdndLeave);
1159 m.format = 32;
1160 m.data.l[0] = m_wndProxy;
1161
1162 xrc = XSendEvent(m_pDisplay, m_wndCur, False, NoEventMask, reinterpret_cast<XEvent*>(&m));
1163 if (RT_UNLIKELY(xrc == 0))
1164 LogFlowThisFunc(("Error sending XA_XdndLeave to old window=%#x\n", m_wndCur));
1165 }
1166
1167 /*
1168 * Do we have a new window which now is under the cursor?
1169 */
1170 if (wndCursor != m_wndCur && newVer != -1)
1171 {
1172 LogFlowThisFunc(("Entering window=%#x\n", wndCursor));
1173
1174 /*
1175 * We enter a new window. Announce the XdndEnter event to the new
1176 * window. The first three mime types are attached to the event (the
1177 * others could be requested by the XdndTypeList property from the
1178 * window itself).
1179 */
1180 XClientMessageEvent m;
1181 RT_ZERO(m);
1182 m.type = ClientMessage;
1183 m.display = m_pDisplay;
1184 m.window = wndCursor;
1185 m.message_type = xAtom(XA_XdndEnter);
1186 m.format = 32;
1187 m.data.l[0] = m_wndProxy;
1188 m.data.l[1] = RT_MAKE_U32_FROM_U8(m_formats.size() > 3 ? 1 : 0, 0, 0, RT_MIN(VBOX_XDND_VERSION, newVer));
1189 m.data.l[2] = m_formats.value(0, None);
1190 m.data.l[3] = m_formats.value(1, None);
1191 m.data.l[4] = m_formats.value(2, None);
1192
1193 xrc = XSendEvent(m_pDisplay, wndCursor, False, NoEventMask, reinterpret_cast<XEvent*>(&m));
1194 if (RT_UNLIKELY(xrc == 0))
1195 LogFlowThisFunc(("Error sending XA_XdndEnter to new window=%#x\n", wndCursor));
1196 }
1197
1198 if (newVer != -1)
1199 {
1200 LogFlowThisFunc(("Moving window=%#x, xPos=%RU32, yPos=%RU32\n",
1201 wndCursor, u32xPos, u32yPos));
1202
1203 /*
1204 * Send a XdndPosition event with the proposed action to the guest.
1205 */
1206 Atom pa = toX11Action(uAction);
1207 LogFlowThisFunc(("strAction=%s\n", xAtomToString(pa).c_str()));
1208
1209 XClientMessageEvent m;
1210 RT_ZERO(m);
1211 m.type = ClientMessage;
1212 m.display = m_pDisplay;
1213 m.window = wndCursor;
1214 m.message_type = xAtom(XA_XdndPosition);
1215 m.format = 32;
1216 m.data.l[0] = m_wndProxy;
1217 m.data.l[2] = RT_MAKE_U32(u32yPos, u32xPos);
1218 m.data.l[3] = CurrentTime;
1219 m.data.l[4] = pa;
1220
1221 xrc = XSendEvent(m_pDisplay, wndCursor, False, NoEventMask, reinterpret_cast<XEvent*>(&m));
1222 if (RT_UNLIKELY(xrc == 0))
1223 LogFlowThisFunc(("Error sending XA_XdndPosition to window=%#x\n", wndCursor));
1224 }
1225
1226 if ( wndCursor == None
1227 && newVer == -1)
1228 {
1229 /* No window to process, so send a ignore ack event to the host. */
1230 rc = VbglR3DnDHGAcknowledgeOperation(m_uClientID, DND_IGNORE_ACTION);
1231 }
1232
1233 m_wndCur = wndCursor;
1234 m_curVer = RT_MIN(VBOX_XDND_VERSION, newVer);
1235
1236 LogFlowFuncLeaveRC(rc);
1237 return rc;
1238}
1239
1240int DragInstance::hgDrop(void)
1241{
1242 LogFlowThisFunc(("wndCur=%#x, mMode=%RU32, mState=%RU32\n",
1243 m_wndCur, m_mode, m_state));
1244
1245 if ( m_mode != HG
1246 || m_state != Dragging)
1247 return VERR_INVALID_STATE;
1248
1249 int rc = VINF_SUCCESS;
1250
1251 /* Send a drop event to the current window and reset our DnD status. */
1252 XClientMessageEvent m;
1253 RT_ZERO(m);
1254 m.type = ClientMessage;
1255 m.display = m_pDisplay;
1256 m.window = m_wndCur;
1257 m.message_type = xAtom(XA_XdndDrop);
1258 m.format = 32;
1259 m.data.l[0] = m_wndProxy;
1260 m.data.l[2] = CurrentTime;
1261
1262 int xrc = XSendEvent(m_pDisplay, m_wndCur, False, NoEventMask, reinterpret_cast<XEvent*>(&m));
1263 if (RT_UNLIKELY(xrc == 0))
1264 LogFlowThisFunc(("Error sending XA_XdndDrop to window=%#x\n", m_wndCur));
1265
1266 m_wndCur = None;
1267 m_curVer = -1;
1268
1269 m_state = Dropped;
1270
1271 LogFlowFuncLeaveRC(rc);
1272 return rc;
1273}
1274
1275int DragInstance::hgDataReceived(void *pvData, uint32_t cData)
1276{
1277 if ( m_mode != HG
1278 || m_state != Dropped)
1279 return VERR_INVALID_STATE;
1280
1281 if (RT_UNLIKELY( pvData == NULL
1282 || cData == 0))
1283 return VERR_INVALID_PARAMETER;
1284
1285 if (RT_UNLIKELY(m_state != Dropped))
1286 return VERR_INVALID_STATE;
1287
1288 /* Make a copy of the data. The xserver will become the new owner. */
1289 void *pvNewData = RTMemAlloc(cData);
1290 if (RT_UNLIKELY(!pvNewData))
1291 return VERR_NO_MEMORY;
1292 memcpy(pvNewData, pvData, cData);
1293
1294 /*
1295 * The host send us the DnD data in the requested mime type. This allows us
1296 * to fill the XdndSelection property of the requestor window with the data
1297 * and afterwards inform the host about the new status.
1298 */
1299 XEvent s;
1300 RT_ZERO(s);
1301 s.xselection.type = SelectionNotify;
1302 s.xselection.display = m_eventHgSelection.xselection.display;
1303// s.xselection.owner = m_selEvent.xselectionrequest.owner;
1304 s.xselection.time = m_eventHgSelection.xselectionrequest.time;
1305 s.xselection.selection = m_eventHgSelection.xselectionrequest.selection;
1306 s.xselection.requestor = m_eventHgSelection.xselectionrequest.requestor;
1307 s.xselection.target = m_eventHgSelection.xselectionrequest.target;
1308 s.xselection.property = m_eventHgSelection.xselectionrequest.property;
1309
1310 LogFlowThisFunc(("owner=%#x, requestor=%#x, sel_atom=%s, target_atom=%s, prop_atom=%s, time=%u\n",
1311 m_eventHgSelection.xselectionrequest.owner,
1312 s.xselection.requestor,
1313 xAtomToString(s.xselection.selection).c_str(),
1314 xAtomToString(s.xselection.target).c_str(),
1315 xAtomToString(s.xselection.property).c_str(),
1316 s.xselection.time));
1317
1318 /* Fill up the property with the data. */
1319 XChangeProperty(s.xselection.display, s.xselection.requestor, s.xselection.property, s.xselection.target, 8, PropModeReplace,
1320 reinterpret_cast<const unsigned char*>(pvNewData), cData);
1321 int xrc = XSendEvent(s.xselection.display, s.xselection.requestor, True, 0, &s);
1322 if (RT_UNLIKELY(xrc == 0))
1323 LogFlowThisFunc(("Error sending SelectionNotify event to wnd=%#x\n", s.xselection.requestor));
1324
1325 return VINF_SUCCESS;
1326}
1327
1328#ifdef VBOX_WITH_DRAG_AND_DROP_GH
1329int DragInstance::ghIsDnDPending(void)
1330{
1331 int rc = VINF_SUCCESS;
1332 Window wndSelection = XGetSelectionOwner(m_pDisplay, xAtom(XA_XdndSelection));
1333 LogFlowThisFunc(("Checking pending wndSelection=%#x, wndProxy=%#x\n", wndSelection, m_wndProxy));
1334
1335 /* Is this another window which has a Xdnd selection than our current one? */
1336 if ( wndSelection
1337 && wndSelection != m_wndProxy)
1338 {
1339 m_mode = GH;
1340
1341 /* Map the window on the current cursor position, which should provoke
1342 * an XdndEnter event. */
1343 proxyWinShow();
1344
1345 XEvent e;
1346 if (waitForX11Msg(e, ClientMessage))
1347 {
1348 int xRc;
1349 XClientMessageEvent *pEventClient = reinterpret_cast<XClientMessageEvent*>(&e);
1350 AssertPtr(pEventClient);
1351
1352 LogFlowThisFunc(("Received event=%s\n",
1353 gX11->xAtomToString(pEventClient->message_type).c_str()));
1354
1355 if (pEventClient->message_type == xAtom(XA_XdndEnter))
1356 {
1357 Atom type = None;
1358 int f;
1359 unsigned long n, a;
1360 unsigned char *ret = 0;
1361
1362 reset();
1363
1364 m_state = Dragging;
1365 m_wndCur = wndSelection;
1366 Assert(m_wndCur == (Window)pEventClient->data.l[0]);
1367
1368 LogFlowThisFunc(("XA_XdndEnter\n"));
1369#ifdef DEBUG
1370 XWindowAttributes xwa;
1371 XGetWindowAttributes(m_pDisplay, m_wndCur, &xwa);
1372 LogFlowThisFunc(("m_wndCur=%#x, x=%d, y=%d, width=%d, height=%d\n",
1373 m_wndCur, xwa.x, xwa.y, xwa.width, xwa.height));
1374#endif
1375 /* Check if the MIME types are in the message itself or if we need
1376 * to fetch the XdndTypeList property from the window. */
1377 if (!ASMBitTest(&pEventClient->data.l[1], 0))
1378 {
1379 for (int i = 2; i < 5; ++i)
1380 {
1381 LogFlowThisFunc(("Received format via message: %s\n",
1382 gX11->xAtomToString(pEventClient->data.l[i]).c_str()));
1383
1384 m_formats.append(pEventClient->data.l[i]);
1385 }
1386 }
1387 else
1388 {
1389 xRc = XGetWindowProperty(m_pDisplay, wndSelection,
1390 xAtom(XA_XdndTypeList),
1391 0, VBOX_MAX_XPROPERTIES,
1392 False, XA_ATOM, &type, &f, &n, &a, &ret);
1393 if ( xRc == Success
1394 && n > 0
1395 && ret)
1396 {
1397 Atom *data = reinterpret_cast<Atom*>(ret);
1398 for (unsigned i = 0; i < RT_MIN(VBOX_MAX_XPROPERTIES, n); ++i)
1399 {
1400 LogFlowThisFunc(("Received format via XdndTypeList: %s\n",
1401 gX11->xAtomToString(data[i]).c_str()));
1402
1403 m_formats.append(data[i]);
1404 }
1405
1406 XFree(ret);
1407 }
1408 }
1409
1410 /* Fetch the possible list of actions, if this property is set. */
1411 xRc = XGetWindowProperty(m_pDisplay, wndSelection,
1412 xAtom(XA_XdndActionList),
1413 0, VBOX_MAX_XPROPERTIES,
1414 False, XA_ATOM, &type, &f, &n, &a, &ret);
1415 if ( xRc == Success
1416 && n > 0
1417 && ret)
1418 {
1419 Atom *data = reinterpret_cast<Atom*>(ret);
1420 for (unsigned i = 0; i < RT_MIN(VBOX_MAX_XPROPERTIES, n); ++i)
1421 {
1422 LogFlowThisFunc(("Received action: %s\n",
1423 gX11->xAtomToString(data[i]).c_str()));
1424
1425 m_actions.append(data[i]);
1426 }
1427
1428 XFree(ret);
1429 }
1430
1431 /* Acknowledge the event by sending a Status msg back to the
1432 * window. */
1433 XClientMessageEvent m;
1434 RT_ZERO(m);
1435 m.type = ClientMessage;
1436 m.display = m_pDisplay;
1437 m.window = m_wndCur;
1438 m.message_type = xAtom(XA_XdndStatus);
1439 m.format = 32;
1440 m.data.l[0] = m_wndProxy;
1441 m.data.l[1] = RT_BIT(0); /* Accept the drop. */
1442 m.data.l[4] = xAtom(XA_XdndActionCopy); /** @todo Make the accepted action configurable. */
1443
1444 xRc = XSendEvent(m_pDisplay, m_wndCur,
1445 False, 0, reinterpret_cast<XEvent*>(&m));
1446 if (RT_UNLIKELY(xRc == 0))
1447 LogFlowThisFunc(("Error sending xEvent\n"));
1448 }
1449 /* Did the source tell us where the cursor currently is? */
1450 else if (pEventClient->message_type == xAtom(XA_XdndPosition))
1451 {
1452 LogFlowThisFunc(("XA_XdndPosition\n"));
1453
1454 /* Reply with a XdndStatus message to tell the source whether
1455 * the data can be dropped or not. */
1456 XClientMessageEvent m;
1457 RT_ZERO(m);
1458 m.type = ClientMessage;
1459 m.display = m_pDisplay;
1460 m.window = m_wndCur;
1461 m.message_type = xAtom(XA_XdndStatus);
1462 m.format = 32;
1463 m.data.l[0] = m_wndProxy;
1464 m.data.l[1] = RT_BIT(0); /* Accept the drop. */
1465 m.data.l[4] = xAtom(XA_XdndActionCopy); /** @todo Make the accepted action configurable. */
1466
1467 xRc = XSendEvent(m_pDisplay, m_wndCur,
1468 False, 0, reinterpret_cast<XEvent*>(&m));
1469 if (RT_UNLIKELY(xRc == 0))
1470 LogFlowThisFunc(("Error sending xEvent\n"));
1471 }
1472 else if (pEventClient->message_type == xAtom(XA_XdndLeave))
1473 {
1474 LogFlowThisFunc(("XA_XdndLeave\n"));
1475 }
1476 }
1477
1478 /* Do we need to acknowledge at least one format to the host? */
1479 if (!m_formats.isEmpty())
1480 {
1481 RTCString strFormats = gX11->xAtomListToString(m_formats);
1482 uint32_t uDefAction = DND_COPY_ACTION; /** @todo Handle default action! */
1483 uint32_t uAllActions = toHGCMActions(m_actions);
1484
1485 rc = VbglR3DnDGHAcknowledgePending(m_uClientID, uDefAction, uAllActions, strFormats.c_str());
1486 LogFlowThisFunc(("Acknowledging m_uClientID=%RU32, allActions=0x%x, strFormats=%s, rc=%Rrc\n",
1487 m_uClientID, uAllActions, strFormats.c_str(), rc));
1488 }
1489 }
1490
1491 LogFlowFuncLeaveRC(rc);
1492 return rc;
1493}
1494
1495int DragInstance::ghDropped(const RTCString &strFormat, uint32_t uAction)
1496{
1497 LogFlowThisFunc(("strFormat=%s, uAction=%RU32\n",
1498 strFormat.c_str(), uAction));
1499
1500 int rc = VINF_SUCCESS;
1501
1502 m_state = Dropped;
1503
1504 /* Show the proxy window, so that the source will find it. */
1505 int iRootX, iRootY;
1506 proxyWinShow(&iRootX, &iRootY);
1507 XFlush(m_pDisplay);
1508
1509#ifdef DEBUG
1510 XWindowAttributes xwa;
1511 XGetWindowAttributes(m_pDisplay, m_wndCur, &xwa);
1512 LogFlowThisFunc(("m_wndCur=%#x, x=%d, y=%d, width=%d, height=%d\n",
1513 m_wndCur, xwa.x, xwa.y, xwa.width, xwa.height));
1514
1515 iRootX = xwa.x;
1516 iRootY = xwa.y;
1517#endif
1518
1519 /* We send a fake release event to the current window, cause
1520 * this should have the grab. */
1521 mouseButtonSet(m_wndCur /* Source window */, iRootX, iRootY,
1522 1 /* Button */, false /* Release button */);
1523
1524 /* The fake button release event should lead to a XdndDrop event from the
1525 * source. Because of showing our proxy window, other Xdnd events can
1526 * occur before, e.g. a XdndPosition event. We are not interested
1527 * in those, so just try to get the right one. */
1528
1529 XClientMessageEvent evDnDDrop;
1530 bool fDrop = waitForX11ClientMsg(evDnDDrop, xAtom(XA_XdndDrop),
1531 5 * 1000 /* Timeout in ms */);
1532 if (fDrop)
1533 {
1534 /* Request to convert the selection in the specific format and
1535 * place it to our proxy window as property. */
1536 Window wndSource = evDnDDrop.data.l[0]; /* Source window which sent the message. */
1537 Assert(wndSource == m_wndCur);
1538 Atom aFormat = gX11->stringToxAtom(strFormat.c_str());
1539
1540 XConvertSelection(m_pDisplay, xAtom(XA_XdndSelection),
1541 aFormat, xAtom(XA_XdndSelection),
1542 m_wndProxy, evDnDDrop.data.l[2]);
1543
1544 /* Wait for the selection notify event. */
1545 XEvent evSelNotify;
1546 RT_ZERO(evSelNotify);
1547 if (waitForX11Msg(evSelNotify, SelectionNotify))
1548 {
1549 bool fCancel = false;
1550
1551 /* Make some paranoid checks. */
1552 if ( evSelNotify.xselection.type == SelectionNotify
1553 && evSelNotify.xselection.display == m_pDisplay
1554 && evSelNotify.xselection.selection == xAtom(XA_XdndSelection)
1555 && evSelNotify.xselection.requestor == m_wndProxy
1556 && evSelNotify.xselection.target == aFormat)
1557 {
1558 LogFlowThisFunc(("Selection notfiy (from wnd=%#x)\n", m_wndCur));
1559
1560 Atom aPropType;
1561 int iPropFormat;
1562 unsigned long cItems, cbRemaining;
1563 unsigned char *pcData = NULL;
1564 XGetWindowProperty(m_pDisplay, m_wndProxy,
1565 xAtom(XA_XdndSelection) /* Property */,
1566 0 /* Offset */,
1567 VBOX_MAX_XPROPERTIES /* Length of 32-bit multiples */,
1568 True /* Delete property? */,
1569 AnyPropertyType, /* Property type */
1570 &aPropType, &iPropFormat, &cItems, &cbRemaining, &pcData);
1571
1572 LogFlowThisFunc(("strType=%s, iPropFormat=%d, cItems=%RU32, cbRemaining=%RU32\n",
1573 gX11->xAtomToString(aPropType).c_str(), iPropFormat, cItems, cbRemaining));
1574
1575 if ( aPropType != None
1576 && pcData != NULL
1577 && iPropFormat >= 8
1578 && cItems > 0
1579 && cbRemaining == 0)
1580 {
1581 size_t cbData = cItems * (iPropFormat / 8);
1582 LogFlowThisFunc(("cbData=%zu\n", cbData));
1583
1584 /* For whatever reason some of the string MIME types are not
1585 * zero terminated. Check that and correct it when necessary,
1586 * because the guest side wants this in any case. */
1587 if ( m_lstAllowedFormats.contains(strFormat)
1588 && pcData[cbData - 1] != '\0')
1589 {
1590 unsigned char *pvDataTmp = static_cast<unsigned char*>(RTMemAlloc(cbData + 1));
1591 if (pvDataTmp)
1592 {
1593 memcpy(pvDataTmp, pcData, cbData);
1594 pvDataTmp[cbData++] = '\0';
1595
1596 rc = VbglR3DnDGHSendData(m_uClientID, strFormat.c_str(),
1597 pvDataTmp, cbData);
1598 RTMemFree(pvDataTmp);
1599 }
1600 else
1601 rc = VERR_NO_MEMORY;
1602 }
1603 else
1604 {
1605 /* Send the raw data to the host. */
1606 rc = VbglR3DnDGHSendData(m_uClientID, strFormat.c_str(),
1607 pcData, cbData);
1608 }
1609
1610 LogFlowThisFunc(("Sent strFormat=%s with rc=%Rrc\n",
1611 strFormat.c_str(), rc));
1612
1613 if (RT_SUCCESS(rc))
1614 {
1615 /* Confirm the result of the transfer to the source window. */
1616 XClientMessageEvent m;
1617 RT_ZERO(m);
1618 m.type = ClientMessage;
1619 m.display = m_pDisplay;
1620 m.window = m_wndProxy;
1621 m.message_type = xAtom(XA_XdndFinished);
1622 m.format = 32;
1623 m.data.l[0] = m_wndProxy; /* Target window. */
1624 m.data.l[1] = 0; /* Don't accept the drop to not make the guest stuck. */
1625 m.data.l[2] = RT_SUCCESS(rc) ? toX11Action(uAction) : None; /* Action used on success */
1626
1627 int xrc = XSendEvent(m_pDisplay, wndSource, True, NoEventMask, reinterpret_cast<XEvent*>(&m));
1628 if (RT_UNLIKELY(xrc == 0))
1629 LogFlowThisFunc(("Error sending xEvent\n"));
1630 }
1631 else
1632 fCancel = true;
1633 }
1634 else
1635 {
1636 if (aPropType == xAtom(XA_INCR))
1637 {
1638 /** @todo Support incremental transfers. */
1639 AssertMsgFailed(("Incrementally transfers are not supported yet\n"));
1640 rc = VERR_NOT_IMPLEMENTED;
1641 }
1642 else
1643 {
1644 AssertMsgFailed(("Not supported data type (%s)\n",
1645 gX11->xAtomToString(aPropType).c_str()));
1646 rc = VERR_INVALID_PARAMETER;
1647 }
1648
1649 fCancel = true;
1650 }
1651
1652 if (fCancel)
1653 {
1654 LogFlowFunc(("Cancelling drop ...\n"));
1655
1656 /* Cancel this. */
1657 XClientMessageEvent m;
1658 RT_ZERO(m);
1659 m.type = ClientMessage;
1660 m.display = m_pDisplay;
1661 m.window = m_wndProxy;
1662 m.message_type = xAtom(XA_XdndFinished);
1663 m.format = 32;
1664 m.data.l[0] = m_wndProxy; /* Target window. */
1665 m.data.l[1] = 0; /* Don't accept the drop to not make the guest stuck. */
1666
1667 int xrc = XSendEvent(m_pDisplay, wndSource, False, NoEventMask, reinterpret_cast<XEvent*>(&m));
1668 if (RT_UNLIKELY(xrc == 0))
1669 LogFlowThisFunc(("Error sending cancel event\n"));
1670 }
1671
1672 /* Cleanup. */
1673 if (pcData)
1674 XFree(pcData);
1675 }
1676 else
1677 rc = VERR_INVALID_PARAMETER;
1678 }
1679 else
1680 rc = VERR_TIMEOUT;
1681 }
1682 else
1683 rc = VERR_TIMEOUT;
1684
1685 /* Inform the host on error. */
1686 if (RT_FAILURE(rc))
1687 {
1688 int rc2 = VbglR3DnDGHSendError(m_uClientID, rc);
1689 AssertRC(rc2);
1690 }
1691
1692 /* At this point, we have either successfully transfered any data or not.
1693 * So reset our internal state because we are done here for this transaction. */
1694 reset();
1695
1696 LogFlowFuncLeaveRC(rc);
1697 return rc;
1698}
1699#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
1700
1701/*
1702 * Helpers
1703 */
1704
1705int DragInstance::mouseCursorMove(int iPosX, int iPosY) const
1706{
1707 LogFlowThisFunc(("iPosX=%d, iPosY=%d\n", iPosX, iPosY));
1708
1709 /* Move the guest pointer to the DnD position, so we can find the window
1710 * below that position. */
1711 XWarpPointer(m_pDisplay, None, m_wndRoot, 0, 0, 0, 0, iPosX, iPosY);
1712 return VINF_SUCCESS;
1713}
1714
1715void DragInstance::mouseButtonSet(Window wndDest, int rx, int ry, int iButton, bool fPress) const
1716{
1717 LogFlowThisFunc(("wndDest=%#x, rx=%d, ry=%d, iBtn=%d, fPress=%RTbool\n",
1718 wndDest, rx, ry, iButton, fPress));
1719#if 0
1720 // Create and setting up the event
1721 XEvent event;
1722 memset (&event, 0, sizeof (event));
1723 event.xbutton.button = iButton;
1724 event.xbutton.same_screen = True;
1725 event.xbutton.subwindow = DefaultRootWindow (m_pDisplay);
1726
1727 while (event.xbutton.subwindow)
1728 {
1729 event.xbutton.window = event.xbutton.subwindow;
1730 XQueryPointer (m_pDisplay, event.xbutton.window,
1731 &event.xbutton.root, &event.xbutton.subwindow,
1732 &event.xbutton.x_root, &event.xbutton.y_root,
1733 &event.xbutton.x, &event.xbutton.y,
1734 &event.xbutton.state);
1735 }
1736 // Press
1737 event.type = ButtonPress;
1738 if (XSendEvent (m_pDisplay, PointerWindow, True, ButtonPressMask, &event) == 0)
1739 LogFlowThisFunc(("Error sending XTestFakeButtonEvent event\n"));
1740 XFlush (m_pDisplay);
1741
1742 // Release
1743 event.type = ButtonRelease;
1744 if (XSendEvent (m_pDisplay, PointerWindow, True, ButtonReleaseMask, &event) == 0)
1745 LogFlowThisFunc(("Error sending XTestFakeButtonEvent event\n"));
1746 XFlush (m_pDisplay);
1747
1748#else
1749#ifdef VBOX_DND_WITH_XTEST
1750 /** @todo Make this check run only once. */
1751 int ev, er, ma, mi;
1752 if (XTestQueryExtension(m_pDisplay, &ev, &er, &ma, &mi))
1753 {
1754 LogFlowThisFunc(("XText extension available\n"));
1755
1756 int xrc = XTestFakeButtonEvent(m_pDisplay, 1, fPress ? True : False, CurrentTime);
1757 if (RT_UNLIKELY(xrc == 0))
1758 LogFlowThisFunc(("Error sending XTestFakeButtonEvent event\n"));
1759 XFlush(m_pDisplay);
1760 }
1761 else
1762 {
1763#endif
1764 LogFlowThisFunc(("XText extension not available or disabled\n"));
1765
1766 XButtonEvent eBtn;
1767 RT_ZERO(eBtn);
1768
1769 eBtn.display = m_pDisplay;
1770 eBtn.root = m_wndRoot;
1771 eBtn.window = wndDest;
1772 eBtn.subwindow = None;
1773 eBtn.same_screen = True;
1774 eBtn.time = CurrentTime;
1775 eBtn.button = iButton;
1776 eBtn.state |= iButton == 1 ? Button1Mask /*:
1777 iButton == 2 ? Button2MotionMask :
1778 iButton == 3 ? Button3MotionMask :
1779 iButton == 4 ? Button4MotionMask :
1780 iButton == 5 ? Button5MotionMask*/ : 0;
1781 eBtn.type = fPress ? ButtonPress : ButtonRelease;
1782 eBtn.send_event = False;
1783 eBtn.x_root = rx;
1784 eBtn.y_root = ry;
1785
1786 //XTranslateCoordinates(m_pDisplay, eBtn.root, eBtn.window, eBtn.x_root, eBtn.y_root, &eBtn.x, &eBtn.y, &eBtn.subwindow);
1787#if 0
1788 int xrc = XSendEvent(m_pDisplay, eBtn.window, True /* fPropagate */,
1789 fPress
1790 ? ButtonPressMask : ButtonReleaseMask,
1791 reinterpret_cast<XEvent*>(&eBtn));
1792#else
1793 int xrc = XSendEvent(m_pDisplay, eBtn.window, False /* fPropagate */,
1794 0 /* Mask */,
1795 reinterpret_cast<XEvent*>(&eBtn));
1796#endif
1797 if (RT_UNLIKELY(xrc == 0))
1798 LogFlowThisFunc(("Error sending XSendEvent\n"));
1799
1800#ifdef DEBUG
1801 Window wndTemp, wndChild;
1802 int wx, wy; unsigned int mask;
1803 XQueryPointer(m_pDisplay, m_wndRoot, &wndTemp, &wndChild, &rx, &ry, &wx, &wy, &mask);
1804 LogFlowFunc(("cursorRootX=%d, cursorRootY=%d\n", rx, ry));
1805#endif
1806
1807#ifdef VBOX_DND_WITH_XTEST
1808 }
1809#endif
1810#endif
1811}
1812
1813int DragInstance::proxyWinShow(int *piRootX /*= NULL*/,
1814 int *piRootY /*= NULL*/,
1815 bool fMouseMove /*= false */) const
1816{
1817 /* piRootX is optional. */
1818 /* piRootY is optional. */
1819
1820 LogFlowThisFuncEnter();
1821
1822 int iRootX, iRootY;
1823 int iChildX, iChildY;
1824 unsigned int iMask;
1825 Window wndRoot, wndChild;
1826
1827// XTestGrabControl(m_pDisplay, False);
1828 Bool fInRootWnd = XQueryPointer(m_pDisplay, m_wndRoot, &wndRoot, &wndChild,
1829 &iRootX, &iRootY,
1830 &iChildX, &iChildY, &iMask);
1831#ifdef DEBUG_andy
1832 LogFlowThisFunc(("fInRootWnd=%RTbool, wndRoot=0x%x, wndChild=0x%x, iRootX=%d, iRootY=%d\n",
1833 RT_BOOL(fInRootWnd), wndRoot, wndChild, iRootX, iRootY));
1834#endif
1835
1836 if (piRootX)
1837 *piRootX = iRootX;
1838 if (piRootY)
1839 *piRootY = iRootY;
1840
1841 XSynchronize(m_pDisplay, True /* Enable sync */);
1842
1843 XMapWindow(m_pDisplay, m_wndProxy);
1844 XRaiseWindow(m_pDisplay, m_wndProxy);
1845 XMoveResizeWindow(m_pDisplay, m_wndProxy, iRootX, iRootY, 100, 100);
1846
1847 if (fMouseMove)
1848 XWarpPointer(m_pDisplay, None, m_wndRoot, 0, 0, 0, 0, iRootX, iRootY);
1849
1850 XSynchronize(m_pDisplay, False /* Disable sync */);
1851// XTestGrabControl(m_pDisplay, True);
1852
1853 return VINF_SUCCESS; /** @todo Add error checking. */
1854}
1855
1856int DragInstance::proxyWinHide(void)
1857{
1858 LogFlowFuncEnter();
1859
1860 XUnmapWindow(m_pDisplay, m_wndProxy);
1861 m_eventQueue.clear();
1862
1863 return VINF_SUCCESS; /** @todo Add error checking. */
1864}
1865
1866/* Currently not used. */
1867/** @todo Is this function still needed? */
1868void DragInstance::registerForEvents(Window wndThis) const
1869{
1870 if (wndThis == m_wndProxy)
1871 return;
1872
1873 LogFlowThisFunc(("%x\n", wndThis));
1874// XSelectInput(m_pDisplay, w, Button1MotionMask | Button2MotionMask | Button3MotionMask | Button4MotionMask | Button5MotionMask);//| SubstructureNotifyMask);
1875// XSelectInput(m_pDisplay, w, ButtonMotionMask); //PointerMotionMask);
1876 XSelectInput(m_pDisplay, wndThis, PointerMotionMask); //PointerMotionMask);
1877 Window hRealRoot, hParent;
1878 Window *phChildrenRaw = NULL;
1879 unsigned cChildren;
1880 if (XQueryTree(m_pDisplay, wndThis, &hRealRoot, &hParent, &phChildrenRaw, &cChildren))
1881 {
1882 for (unsigned i = 0; i < cChildren; ++i)
1883 registerForEvents(phChildrenRaw[i]);
1884 XFree(phChildrenRaw);
1885 }
1886}
1887
1888void DragInstance::setActionsWindowProperty(Window wndThis,
1889 const RTCList<Atom> &lstActions) const
1890{
1891 if (lstActions.isEmpty())
1892 return;
1893
1894 XChangeProperty(m_pDisplay, wndThis,
1895 xAtom(XA_XdndActionList),
1896 XA_ATOM, 32, PropModeReplace,
1897 reinterpret_cast<const unsigned char*>(lstActions.raw()),
1898 lstActions.size());
1899}
1900
1901void DragInstance::clearActionsWindowProperty(Window wndThis) const
1902{
1903 XDeleteProperty(m_pDisplay, wndThis, xAtom(XA_XdndActionList));
1904}
1905
1906void DragInstance::setFormatsWindowProperty(Window wndThis,
1907 Atom property) const
1908{
1909 if (m_formats.isEmpty())
1910 return;
1911
1912 /* We support TARGETS and the data types. */
1913 RTCList<Atom> targets(m_formats.size() + 1);
1914 targets.append(xAtom(XA_TARGETS));
1915 targets.append(m_formats);
1916
1917 /* Add the property with the property data to the window. */
1918 XChangeProperty(m_pDisplay, wndThis, property,
1919 XA_ATOM, 32, PropModeReplace,
1920 reinterpret_cast<const unsigned char*>(targets.raw()),
1921 targets.size());
1922}
1923
1924void DragInstance::clearFormatsWindowProperty(Window wndThis) const
1925{
1926 XDeleteProperty(m_pDisplay, wndThis,
1927 xAtom(XA_XdndTypeList));
1928}
1929
1930RTCList<Atom> DragInstance::toAtomList(const RTCList<RTCString> &lstFormats) const
1931{
1932 RTCList<Atom> atomList;
1933 for (size_t i = 0; i < lstFormats.size(); ++i)
1934 atomList.append(XInternAtom(m_pDisplay, lstFormats.at(i).c_str(), False));
1935
1936 return atomList;
1937}
1938
1939RTCList<Atom> DragInstance::toAtomList(void *pvData, uint32_t cbData) const
1940{
1941 if ( !pvData
1942 || !cbData)
1943 return RTCList<Atom>();
1944
1945 char *pszStr = (char*)pvData;
1946 uint32_t cbStr = cbData;
1947
1948 RTCList<Atom> lstAtom;
1949 while (cbStr)
1950 {
1951 size_t cbSize = RTStrNLen(pszStr, cbStr);
1952
1953 /* Create a copy with max N chars, so that we are on the save side,
1954 * even if the data isn't zero terminated. */
1955 char *pszTmp = RTStrDupN(pszStr, cbSize);
1956 LogFlowThisFunc(("f: %s\n", pszTmp));
1957 lstAtom.append(XInternAtom(m_pDisplay, pszTmp, False));
1958 RTStrFree(pszTmp);
1959 pszStr += cbSize + 1;
1960 cbStr -= cbSize + 1;
1961 }
1962
1963 return lstAtom;
1964}
1965
1966/* static */
1967Atom DragInstance::toX11Action(uint32_t uAction)
1968{
1969 /* Ignore is None. */
1970 return (isDnDCopyAction(uAction) ? xAtom(XA_XdndActionCopy) :
1971 isDnDMoveAction(uAction) ? xAtom(XA_XdndActionMove) :
1972 isDnDLinkAction(uAction) ? xAtom(XA_XdndActionLink) :
1973 None);
1974}
1975
1976/* static */
1977RTCList<Atom> DragInstance::toX11Actions(uint32_t uActions)
1978{
1979 RTCList<Atom> actionList;
1980 if (hasDnDCopyAction(uActions))
1981 actionList.append(xAtom(XA_XdndActionCopy));
1982 if (hasDnDMoveAction(uActions))
1983 actionList.append(xAtom(XA_XdndActionMove));
1984 if (hasDnDLinkAction(uActions))
1985 actionList.append(xAtom(XA_XdndActionLink));
1986
1987 return actionList;
1988}
1989
1990/* static */
1991uint32_t DragInstance::toHGCMAction(Atom atom)
1992{
1993 uint32_t uAction = DND_IGNORE_ACTION;
1994
1995 if (atom == xAtom(XA_XdndActionCopy))
1996 uAction = DND_COPY_ACTION;
1997 else if (atom == xAtom(XA_XdndActionMove))
1998 uAction = DND_MOVE_ACTION;
1999 else if (atom == xAtom(XA_XdndActionLink))
2000 uAction = DND_LINK_ACTION;
2001
2002 return uAction;
2003}
2004
2005/* static */
2006uint32_t DragInstance::toHGCMActions(const RTCList<Atom> &actionsList)
2007{
2008 uint32_t uActions = DND_IGNORE_ACTION;
2009
2010 for (size_t i = 0; i < actionsList.size(); ++i)
2011 uActions |= toHGCMAction(actionsList.at(i));
2012
2013 return uActions;
2014}
2015
2016/** @todo Replace by DnDURIList? */
2017RTCList<RTCString> toStringList(const void *pvData, uint32_t cbData)
2018{
2019 if ( !pvData
2020 || !cbData)
2021 return RTCList<RTCString>();
2022
2023 const char *pszStr = (char*)pvData;
2024 uint32_t cbStr = cbData;
2025
2026 RTCList<RTCString> lstString;
2027 while (cbStr > 0)
2028 {
2029 size_t cbSize = RTStrNLen(pszStr, cbStr);
2030
2031 /* Create a copy with max N chars, so that we are on the save side,
2032 * even if the data isn't zero terminated. */
2033 char *pszTmp = RTStrDupN(pszStr, cbSize);
2034 lstString.append(pszTmp);
2035 RTStrFree(pszTmp);
2036 pszStr += cbSize + 1;
2037 cbStr -= cbSize + 1;
2038 }
2039
2040 return lstString;
2041}
2042
2043/** @todo Is this function really needed? */
2044void DragAndDropService::clearEventQueue(void)
2045{
2046 LogFlowThisFuncEnter();
2047 m_eventQueue.clear();
2048}
2049
2050/*******************************************************************************
2051 * DragAndDropService implementation.
2052 ******************************************************************************/
2053
2054int DragAndDropService::run(bool fDaemonised /* = false */)
2055{
2056 LogFlowThisFunc(("fDaemonised=%RTbool\n", fDaemonised));
2057
2058 int rc;
2059 do
2060 {
2061 /* Initialize X11 DnD. */
2062 rc = x11DragAndDropInit();
2063 if (RT_FAILURE(rc))
2064 break;
2065
2066 m_pCurDnD = new DragInstance(m_pDisplay, this);
2067 if (!m_pCurDnD)
2068 {
2069 rc = VERR_NO_MEMORY;
2070 break;
2071 }
2072
2073 /* Note: For multiple screen support in VBox it is not necessary to use
2074 * another screen number than zero. Maybe in the future it will become
2075 * necessary if VBox supports multiple X11 screens. */
2076 rc = m_pCurDnD->init(0);
2077 if (RT_FAILURE(rc))
2078 break;
2079
2080 LogRel(("DnD: Started\n"));
2081
2082 /* Enter the main event processing loop. */
2083 do
2084 {
2085 DnDEvent e;
2086 RT_ZERO(e);
2087
2088 LogFlowFunc(("Waiting for new event ...\n"));
2089 rc = RTSemEventWait(m_hEventSem, RT_INDEFINITE_WAIT);
2090 if (RT_FAILURE(rc))
2091 break;
2092
2093 AssertMsg(m_eventQueue.size(),
2094 ("Event queue is empty when it shouldn't\n"));
2095
2096 e = m_eventQueue.first();
2097 m_eventQueue.removeFirst();
2098
2099 if (e.type == DnDEvent::HGCM_Type)
2100 {
2101 LogFlowThisFunc(("HGCM event, type=%RU32\n", e.hgcm.uType));
2102 switch (e.hgcm.uType)
2103 {
2104 case DragAndDropSvc::HOST_DND_HG_EVT_ENTER:
2105 {
2106 if (e.hgcm.cbFormats)
2107 {
2108 RTCList<RTCString> lstFormats
2109 = RTCString(e.hgcm.pszFormats, e.hgcm.cbFormats - 1).split("\r\n");
2110 m_pCurDnD->hgEnter(lstFormats, e.hgcm.u.a.uAllActions);
2111
2112 /* Enter is always followed by a move event. */
2113 }
2114 else /* Invalid parameter, skip. */
2115 break;
2116 /* Not breaking unconditionally is intentional. See comment above. */
2117 }
2118 case DragAndDropSvc::HOST_DND_HG_EVT_MOVE:
2119 {
2120 m_pCurDnD->hgMove(e.hgcm.u.a.uXpos, e.hgcm.u.a.uYpos,
2121 e.hgcm.u.a.uDefAction);
2122 break;
2123 }
2124 case DragAndDropSvc::HOST_DND_HG_EVT_LEAVE:
2125 {
2126 m_pCurDnD->reset();
2127 break;
2128 }
2129 case DragAndDropSvc::HOST_DND_HG_EVT_DROPPED:
2130 {
2131 m_pCurDnD->hgDrop();
2132 break;
2133 }
2134 case DragAndDropSvc::HOST_DND_HG_SND_DATA:
2135 {
2136 m_pCurDnD->hgDataReceived(e.hgcm.u.b.pvData, e.hgcm.u.b.cbData);
2137 break;
2138 }
2139 case DragAndDropSvc::HOST_DND_GH_REQ_PENDING:
2140 {
2141#ifdef VBOX_WITH_DRAG_AND_DROP_GH
2142 m_pCurDnD->ghIsDnDPending();
2143#endif
2144 break;
2145 }
2146 case DragAndDropSvc::HOST_DND_GH_EVT_DROPPED:
2147 {
2148#ifdef VBOX_WITH_DRAG_AND_DROP_GH
2149 m_pCurDnD->ghDropped(e.hgcm.pszFormats,
2150 e.hgcm.u.a.uDefAction);
2151#endif
2152 break;
2153 }
2154
2155 default:
2156 LogFlowThisFunc(("Unknown message: %RU32\n", e.hgcm.uType));
2157 break;
2158 }
2159
2160 /* Some messages require cleanup. */
2161 switch (e.hgcm.uType)
2162 {
2163 case DragAndDropSvc::HOST_DND_HG_EVT_ENTER:
2164 case DragAndDropSvc::HOST_DND_HG_EVT_MOVE:
2165 case DragAndDropSvc::HOST_DND_HG_EVT_DROPPED:
2166#ifdef VBOX_WITH_DRAG_AND_DROP_GH
2167 case DragAndDropSvc::HOST_DND_GH_EVT_DROPPED:
2168#endif
2169 {
2170 if (e.hgcm.pszFormats)
2171 RTMemFree(e.hgcm.pszFormats);
2172 break;
2173 }
2174
2175 case DragAndDropSvc::HOST_DND_HG_SND_DATA:
2176 {
2177 if (e.hgcm.pszFormats)
2178 RTMemFree(e.hgcm.pszFormats);
2179 if (e.hgcm.u.b.pvData)
2180 RTMemFree(e.hgcm.u.b.pvData);
2181 break;
2182 }
2183
2184 default:
2185 break;
2186 }
2187 }
2188 else if (e.type == DnDEvent::X11_Type)
2189 {
2190 m_pCurDnD->onX11Event(e.x11);
2191 }
2192 else
2193 AssertMsgFailed(("Unknown event queue type %d\n", e.type));
2194
2195 /* Make sure that any X11 requests have actually been sent to the
2196 * server, since we are waiting for responses using poll() on
2197 * another thread which will not automatically trigger flushing. */
2198 XFlush(m_pDisplay);
2199
2200 } while (!ASMAtomicReadBool(&m_fSrvStopping));
2201
2202 } while (0);
2203
2204 LogRel(("DnD: Stopped with rc=%Rrc\n", rc));
2205 return rc;
2206}
2207
2208int DragAndDropService::x11DragAndDropInit(void)
2209{
2210 /* Connect to the x11 server. */
2211 m_pDisplay = XOpenDisplay(NULL);
2212 if (!m_pDisplay)
2213 /** @todo Correct errors. */
2214 return VERR_NOT_FOUND;
2215
2216 xHelpers *pHelpers = xHelpers::getInstance(m_pDisplay);
2217 if (!pHelpers)
2218 return VERR_NO_MEMORY;
2219
2220 int rc = VINF_SUCCESS;
2221 do
2222 {
2223 rc = RTSemEventCreate(&m_hEventSem);
2224 if (RT_FAILURE(rc))
2225 break;
2226
2227 rc = RTCritSectInit(&m_eventQueueCS);
2228 if (RT_FAILURE(rc))
2229 break;
2230
2231 /* Event thread for events coming from the HGCM device. */
2232 rc = RTThreadCreate(&m_hHGCMThread, hgcmEventThread, this,
2233 0, RTTHREADTYPE_MSG_PUMP, RTTHREADFLAGS_WAITABLE,
2234 "HGCM-NOTIFY");
2235 if (RT_FAILURE(rc))
2236 break;
2237
2238 /* Event thread for events coming from the x11 system. */
2239 rc = RTThreadCreate(&m_hX11Thread, x11EventThread, this,
2240 0, RTTHREADTYPE_MSG_PUMP, RTTHREADFLAGS_WAITABLE,
2241 "X11-NOTIFY");
2242 } while (0);
2243
2244 /* No clean-up code for now, as we have no good way of testing it and things
2245 * should get cleaned up when the user process/X11 client exits. */
2246 if (RT_FAILURE(rc))
2247 LogRel(("DnD: Failed to start, rc=%Rrc\n", rc));
2248
2249 return rc;
2250}
2251
2252/* static */
2253int DragAndDropService::hgcmEventThread(RTTHREAD hThread, void *pvUser)
2254{
2255 AssertPtrReturn(pvUser, VERR_INVALID_PARAMETER);
2256 DragAndDropService *pThis = static_cast<DragAndDropService*>(pvUser);
2257 AssertPtr(pThis);
2258
2259 uint32_t uClientID;
2260 int rc = VbglR3DnDConnect(&uClientID);
2261 if (RT_FAILURE(rc))
2262 {
2263 LogRel(("DnD: Unable to connect to drag'n drop service, rc=%Rrc\n", rc));
2264 return rc;
2265 }
2266
2267 /* Number of invalid messages skipped in a row. */
2268 int cMsgSkippedInvalid = 0;
2269 DnDEvent e;
2270
2271 do
2272 {
2273 RT_ZERO(e);
2274 e.type = DnDEvent::HGCM_Type;
2275
2276 /* Wait for new events. */
2277 rc = VbglR3DnDProcessNextMessage(uClientID, &e.hgcm);
2278 if (RT_SUCCESS(rc))
2279 {
2280 cMsgSkippedInvalid = 0; /* Reset skipped messages count. */
2281
2282 LogFlowFunc(("Adding new HGCM event ...\n"));
2283 pThis->m_eventQueue.append(e);
2284
2285 rc = RTSemEventSignal(pThis->m_hEventSem);
2286 if (RT_FAILURE(rc))
2287 break;
2288 }
2289 else
2290 {
2291 /* Old(er) hosts either are broken regarding DnD support or otherwise
2292 * don't support the stuff we do on the guest side, so make sure we
2293 * don't process invalid messages forever. */
2294 if (rc == VERR_INVALID_PARAMETER)
2295 cMsgSkippedInvalid++;
2296 if (cMsgSkippedInvalid > 3)
2297 {
2298 LogRel(("DnD: Too many invalid/skipped messages from host, exiting ...\n"));
2299 break;
2300 }
2301 }
2302
2303 } while (!ASMAtomicReadBool(&pThis->m_fSrvStopping));
2304
2305 VbglR3DnDDisconnect(uClientID);
2306
2307 LogFlowFuncLeaveRC(rc);
2308 return rc;
2309}
2310
2311/* static */
2312int DragAndDropService::x11EventThread(RTTHREAD hThread, void *pvUser)
2313{
2314 AssertPtrReturn(pvUser, VERR_INVALID_PARAMETER);
2315 DragAndDropService *pThis = static_cast<DragAndDropService*>(pvUser);
2316 AssertPtr(pThis);
2317
2318 int rc = VINF_SUCCESS;
2319
2320 DnDEvent e;
2321 do
2322 {
2323 /*
2324 * Wait for new events. We can't use XIfEvent here, cause this locks
2325 * the window connection with a mutex and if no X11 events occurs this
2326 * blocks any other calls we made to X11. So instead check for new
2327 * events and if there are not any new one, sleep for a certain amount
2328 * of time.
2329 */
2330 if (XEventsQueued(pThis->m_pDisplay, QueuedAfterFlush) > 0)
2331 {
2332 RT_ZERO(e);
2333 e.type = DnDEvent::X11_Type;
2334
2335 /* XNextEvent will block until a new X event becomes available. */
2336 XNextEvent(pThis->m_pDisplay, &e.x11);
2337 {
2338#ifdef DEBUG
2339 switch (e.x11.type)
2340 {
2341 case ClientMessage:
2342 {
2343 XClientMessageEvent *pEvent = reinterpret_cast<XClientMessageEvent*>(&e);
2344 AssertPtr(pEvent);
2345
2346 RTCString strType = xAtomToString(pEvent->message_type);
2347 LogFlowFunc(("ClientMessage: %s (%RU32), serial=%RU32, wnd=%#x\n", strType.c_str(),
2348 pEvent->message_type, pEvent->serial, pEvent->window));
2349
2350 if (pEvent->message_type == xAtom(XA_XdndPosition))
2351 {
2352 int32_t dwPos = pEvent->data.l[2];
2353 int32_t dwAction = pEvent->data.l[4];
2354
2355 LogFlowFunc(("XA_XdndPosition x=%RI32, y=%RI32, dwAction=%RI32\n",
2356 RT_HIWORD(dwPos), RT_LOWORD(dwPos), dwAction));
2357 }
2358 else if (pEvent->message_type == xAtom(XA_XdndDrop))
2359 {
2360 LogFlowFunc(("XA_XdndDrop\n"));
2361 }
2362
2363 break;
2364 }
2365
2366 default:
2367 LogFlowFunc(("Received X event type=%d\n", e.x11.type));
2368 break;
2369 }
2370#endif
2371 LogFlowFunc(("Adding new X11 event ...\n"));
2372
2373 /* At the moment we only have one drag instance. */
2374 DragInstance *pInstance = pThis->m_pCurDnD;
2375
2376 AssertPtr(pInstance);
2377 pInstance->onX11Event(e.x11);
2378 }
2379 }
2380 else
2381 RTThreadSleep(25 /* ms */);
2382
2383 } while (!ASMAtomicReadBool(&pThis->m_fSrvStopping));
2384
2385 LogFlowFuncLeaveRC(rc);
2386 return rc;
2387}
2388
2389/* Static factory */
2390VBoxClient::Service *VBoxClient::GetDragAndDropService(void)
2391{
2392 DragAndDropService *pService = new DragAndDropService();
2393 return pService;
2394}
2395
Note: See TracBrowser for help on using the repository browser.

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