VirtualBox

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

Last change on this file since 55571 was 55571, checked in by vboxsync, 10 years ago

DnD: Simplified cancellation logic.

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