VirtualBox

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

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

added a couple of missing Id headers

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

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