VirtualBox

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

Last change on this file since 42261 was 42261, checked in by vboxsync, 12 years ago

enabled shared clipboard support for Linux hosts (guest=>host only)

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 62.8 KB
Line 
1/** @file
2 * X11 guest client - Drag and Drop.
3 */
4
5/*
6 * Copyright (C) 2011-2012 Oracle Corporation
7 *
8 * This file is part of VirtualBox Open Source Edition (OSE), as
9 * available from http://www.virtualbox.org. This file is free software;
10 * you can redistribute it and/or modify it under the terms of the GNU
11 * General Public License (GPL) as published by the Free Software
12 * Foundation, in version 2 as it comes in the "COPYING" file of the
13 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
14 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
15 */
16
17#include <X11/Xlib.h>
18#include <X11/Xatom.h>
19#include <X11/extensions/XTest.h>
20
21#include <iprt/thread.h>
22#include <iprt/asm.h>
23#include <iprt/time.h>
24
25#include <iprt/cpp/mtlist.h>
26#include <iprt/cpp/ministring.h>
27
28#include <limits.h>
29
30#include <VBox/log.h>
31#include <VBox/VBoxGuestLib.h>
32
33#include "VBox/HostServices/DragAndDropSvc.h"
34
35#include "VBoxClient.h"
36
37/* For X11 guest xDnD is used. See http://www.acc.umu.se/~vatten/XDND.html for
38 * a walk trough.
39 *
40 * H->G:
41 * For X11 this means mainly forwarding all the events from HGCM to the
42 * appropriate X11 events. There exists a proxy window, which is invisible and
43 * used for all the X11 communication. On a HGCM Enter event, we set our proxy
44 * window as XdndSelection owner with the given mime-types. On every HGCM move
45 * event, we move the X11 mouse cursor to the new position and query for the
46 * window below that position. Depending on if it is XdndAware, a new window or
47 * a known window, we send the appropriate X11 messages to it. On HGCM drop, we
48 * send a XdndDrop message to the current window and wait for a X11
49 * SelectionMessage from the target window. Because we didn't have the data in
50 * the requested mime-type, yet, we save that message and ask the host for the
51 * data. When the data is successfully received from the host, we put the data
52 * as a property to the window and send a X11 SelectionNotify event to the
53 * target window.
54 *
55 * G->H:
56 * This is a lot more trickery than H->G. When a pending event from HGCM
57 * arrives, we asks if there is currently an owner of the XdndSelection
58 * property. If so, our proxy window is shown (1x1, but without backing store)
59 * and some mouse event is triggered. This should be followed by an XdndEnter
60 * event send to the proxy window. From this event we can fetch the necessary
61 * info of the mime-types and allowed actions and send this back to the host.
62 * On a drop request from the host, we query for the selection and should get
63 * the data in the specified mime-type. This data is send back to the host.
64 * After that we send a XdndLeave event to the source window.
65 * Todo:
66 * - this isn't finished, yet. Currently the mouse isn't correctly released
67 * in the guest (both, when the drop was successfully or canceled).
68 * - cancel (e.g. with the ESC key) doesn't work
69 *
70 * Todo:
71 * - XdndProxy window support
72 * - INCR support
73 * - make this much more robust for crashes of the other party
74 * - really check for the Xdnd version and the supported features
75 */
76
77#define VERBOSE 1
78
79#if defined(VERBOSE) && defined(DEBUG_poetzsch)
80# include <iprt/stream.h>
81# define DO(s) RTPrintf s
82#else
83# define DO(s) do {} while(0)
84//# define DO(s) Log s
85#endif
86
87#define VBOX_XDND_VERSION (4)
88#define VBOX_MAX_XPROPERTIES (LONG_MAX-1)
89
90/* Shared struct used for adding new X11 events and HGCM messages to a single
91 * event queue. */
92struct DnDEvent
93{
94 enum DnDEventType
95 {
96 HGCM_Type = 1,
97 X11_Type
98 };
99 DnDEventType type;
100 union
101 {
102 VBGLR3DNDHGCMEVENT hgcm;
103 XEvent x11;
104 };
105};
106
107enum XA_Type
108{
109 /* States */
110 XA_WM_STATE = 0,
111 /* Properties */
112 XA_TARGETS,
113 XA_MULTIPLE,
114 XA_INCR,
115 /* Mime Types */
116 XA_image_bmp,
117 XA_image_jpg,
118 XA_image_tiff,
119 XA_image_png,
120 XA_text_uri_list,
121 XA_text_uri,
122 XA_text_plain,
123 XA_TEXT,
124 /* xDnD */
125 XA_XdndSelection,
126 XA_XdndAware,
127 XA_XdndEnter,
128 XA_XdndLeave,
129 XA_XdndTypeList,
130 XA_XdndActionList,
131 XA_XdndPosition,
132 XA_XdndActionCopy,
133 XA_XdndActionMove,
134 XA_XdndActionLink,
135 XA_XdndStatus,
136 XA_XdndDrop,
137 XA_XdndFinished,
138 /* Our own stop marker */
139 XA_dndstop,
140 /* End marker */
141 XA_End
142};
143
144class DragAndDropService;
145
146/*******************************************************************************
147 *
148 * xHelpers Declaration
149 *
150 ******************************************************************************/
151
152class xHelpers
153{
154public:
155
156 static xHelpers *instance(Display *pDisplay = 0)
157 {
158 if (!m_pInstance)
159 {
160 AssertPtrReturn(pDisplay, 0);
161 m_pInstance = new xHelpers(pDisplay);
162 }
163 return m_pInstance;
164 }
165
166 inline Display *display() const { return m_pDisplay; }
167 inline Atom xAtom(XA_Type e) const { return m_xAtoms[e]; }
168
169 inline Atom stringToxAtom(const char *pcszString) const
170 {
171 return XInternAtom(m_pDisplay, pcszString, False);
172 }
173 inline RTCString xAtomToString(Atom atom) const
174 {
175 if (atom == None) return "None";
176
177 char* pcsAtom = XGetAtomName(m_pDisplay, atom);
178 RTCString strAtom(pcsAtom);
179 XFree(pcsAtom);
180
181 return strAtom;
182 }
183
184 inline RTCString xAtomListToString(const RTCList<Atom> &formatList)
185 {
186 RTCString format;
187 for (size_t i = 0; i < formatList.size(); ++i)
188 format += xAtomToString(formatList.at(i)) + "\r\n";
189 return format;
190 }
191
192 RTCString xErrorToString(int xrc) const;
193 Window applicationWindowBelowCursor(Window parentWin) const;
194
195private:
196 xHelpers(Display *pDisplay)
197 : m_pDisplay(pDisplay)
198 {
199 /* Not all x11 atoms we use are defined in the headers. Create the
200 * additional one we need here. */
201 for (int i = 0; i < XA_End; ++i)
202 m_xAtoms[i] = XInternAtom(m_pDisplay, m_xAtomNames[i], False);
203 };
204
205 /* Private member vars */
206 static xHelpers *m_pInstance;
207 Display *m_pDisplay;
208 Atom m_xAtoms[XA_End];
209 static const char *m_xAtomNames[XA_End];
210};
211
212/* Some xHelpers convenience defines. */
213#define gX11 xHelpers::instance()
214#define xAtom(xa) gX11->xAtom((xa))
215#define xAtomToString(xa) gX11->xAtomToString((xa))
216
217/*******************************************************************************
218 *
219 * xHelpers Implementation
220 *
221 ******************************************************************************/
222
223xHelpers *xHelpers::m_pInstance = 0;
224/* Has to be in sync with the XA_Type enum. */
225const char *xHelpers::m_xAtomNames[] =
226{
227 /* States */
228 "WM_STATE",
229 /* Properties */
230 "TARGETS",
231 "MULTIPLE",
232 "INCR",
233 /* Mime Types */
234 "image/bmp",
235 "image/jpg",
236 "image/tiff",
237 "image/png",
238 "text/uri-list",
239 "text/uri",
240 "text/plain",
241 "TEXT",
242 /* xDnD */
243 "XdndSelection",
244 "XdndAware",
245 "XdndEnter",
246 "XdndLeave",
247 "XdndTypeList",
248 "XdndActionList",
249 "XdndPosition",
250 "XdndActionCopy",
251 "XdndActionMove",
252 "XdndActionLink",
253 "XdndStatus",
254 "XdndDrop",
255 "XdndFinished",
256 /* Our own stop marker */
257 "dndstop"
258};
259
260RTCString xHelpers::xErrorToString(int xrc) const
261{
262 switch (xrc)
263 {
264 case Success: return RTCStringFmt("%d (Success)", xrc); break;
265 case BadRequest: return RTCStringFmt("%d (BadRequest)", xrc); break;
266 case BadValue: return RTCStringFmt("%d (BadValue)", xrc); break;
267 case BadWindow: return RTCStringFmt("%d (BadWindow)", xrc); break;
268 case BadPixmap: return RTCStringFmt("%d (BadPixmap)", xrc); break;
269 case BadAtom: return RTCStringFmt("%d (BadAtom)", xrc); break;
270 case BadCursor: return RTCStringFmt("%d (BadCursor)", xrc); break;
271 case BadFont: return RTCStringFmt("%d (BadFont)", xrc); break;
272 case BadMatch: return RTCStringFmt("%d (BadMatch)", xrc); break;
273 case BadDrawable: return RTCStringFmt("%d (BadDrawable)", xrc); break;
274 case BadAccess: return RTCStringFmt("%d (BadAccess)", xrc); break;
275 case BadAlloc: return RTCStringFmt("%d (BadAlloc)", xrc); break;
276 case BadColor: return RTCStringFmt("%d (BadColor)", xrc); break;
277 case BadGC: return RTCStringFmt("%d (BadGC)", xrc); break;
278 case BadIDChoice: return RTCStringFmt("%d (BadIDChoice)", xrc); break;
279 case BadName: return RTCStringFmt("%d (BadName)", xrc); break;
280 case BadLength: return RTCStringFmt("%d (BadLength)", xrc); break;
281 case BadImplementation: return RTCStringFmt("%d (BadImplementation)", xrc); break;
282 }
283 return RTCStringFmt("%d (unknown)", xrc);
284}
285
286/* Todo: make this iterative */
287Window xHelpers::applicationWindowBelowCursor(Window parentWin) const
288{
289 /* No parent, nothing to do. */
290 if(parentWin == 0)
291 return 0;
292
293 Window appWin = 0;
294 int cProps = -1;
295 /* Fetch all x11 window properties of the parent window. */
296 Atom *pProps = XListProperties(m_pDisplay, parentWin, &cProps);
297 if (cProps > 0)
298 {
299 /* We check the window for the WM_STATE property. */
300 for(int i = 0; i < cProps; ++i)
301 if(pProps[i] == xAtom(XA_WM_STATE))
302 {
303 /* Found it. */
304 appWin = parentWin;
305 break;
306 }
307 /* Cleanup */
308 XFree(pProps);
309 }
310
311 if (!appWin)
312 {
313 Window childWin, wtmp;
314 int tmp;
315 unsigned int utmp;
316 /* Query the next child window of the parent window at the current
317 * mouse position. */
318 XQueryPointer(m_pDisplay, parentWin, &wtmp, &childWin, &tmp, &tmp, &tmp, &tmp, &utmp);
319 /* Recursive call our self to dive into the child tree. */
320 appWin = applicationWindowBelowCursor(childWin);
321 }
322
323 return appWin;
324}
325
326/*******************************************************************************
327 *
328 * DragInstance Declaration
329 *
330 ******************************************************************************/
331
332/* For now only one DragInstance will exits when the app is running. In the
333 * future the support for having more than one D&D operation supported at the
334 * time will be necessary. */
335class DragInstance
336{
337public:
338 enum State
339 {
340 Uninitialized,
341 Initialized,
342 Dragging,
343 Dropped
344 };
345
346 enum Mode
347 {
348 Unknown,
349 HG,
350 GH
351 };
352
353 DragInstance(Display *pDisplay, DragAndDropService *pParent);
354 int init(uint32_t u32ScreenId);
355 void uninit();
356 void reset();
357
358 /* H->G */
359 int hgEnter(const RTCList<RTCString> &formats, uint32_t actions);
360 int hgMove(uint32_t u32xPos, uint32_t u32yPos, uint32_t action);
361 int hgX11ClientMessage(const XEvent& e);
362 int hgDrop();
363 int hgX11SelectionRequest(const XEvent& e);
364 int hgDataReceived(void *pvData, uint32_t cData);
365
366#ifdef VBOX_WITH_DRAG_AND_DROP_GH
367 /* G->H */
368 int ghIsDnDPending();
369 int ghDropped(const RTCString &strFormat, uint32_t action);
370#endif
371
372 /* X11 helpers */
373 int moveCursor(uint32_t u32xPos, uint32_t u32yPos);
374 void sendButtonEvent(Window w, int rx, int ry, int button, bool fPress) const;
375 void showProxyWin(int &rx, int &ry) const;
376 void hideProxyWin() const;
377 void registerForEvents(Window w) const;
378
379 void setActionsWindowProperty(Window win, const RTCList<Atom> &actionList) const;
380 void clearActionsWindowProperty(Window win) const;
381 void setFormatsWindowProperty(Window win, Atom property) const;
382 void clearFormatsWindowProperty(Window win) const;
383
384 RTCList<Atom> toAtomList(const RTCList<RTCString> &formatList) const;
385 RTCList<Atom> toAtomList(void *pvData, uint32_t cData) const;
386 static Atom toX11Action(uint32_t uAction);
387 static RTCList<Atom> toX11Actions(uint32_t uActions);
388 static uint32_t toHGCMAction(Atom atom);
389 static uint32_t toHGCMActions(const RTCList<Atom> &actionsList);
390
391 /* Member vars */
392 DragAndDropService *m_pParent;
393 Display *m_pDisplay;
394 int m_screenId;
395 Screen *m_pScreen;
396 Window m_rootWin;
397 Window m_proxyWin;
398 Window m_curWin;
399 long m_curVer;
400 RTCList<Atom> m_formats;
401 RTCList<Atom> m_actions;
402
403 XEvent m_selEvent;
404
405 Mode m_mode;
406 State m_state;
407
408 static const RTCList<RTCString> m_sstrStringMimeTypes;
409};
410
411/*******************************************************************************
412 *
413 * DragAndDropService Declaration
414 *
415 ******************************************************************************/
416
417class DragAndDropService : public VBoxClient::Service
418{
419public:
420 DragAndDropService()
421 : m_pDisplay(0)
422 , m_hHGCMThread(NIL_RTTHREAD)
423 , m_hX11Thread(NIL_RTTHREAD)
424 , m_hEventSem(NIL_RTSEMEVENT)
425 , m_pCurDnD(0)
426 , m_fSrvStopping(false)
427 {}
428
429 virtual const char *getPidFilePath() { return ".vboxclient-draganddrop.pid"; }
430
431 /** @todo Move this part in VbglR3 and just provide a callback for the platform-specific
432 notification stuff, since this is very similar to the VBoxTray code. */
433 virtual int run(bool fDaemonised = false);
434
435 virtual void cleanup()
436 {
437 /* Cleanup */
438 x11DragAndDropTerm();
439 VbglR3DnDTerm();
440 };
441
442private:
443 int x11DragAndDropInit();
444 int x11DragAndDropTerm();
445 static int hgcmEventThread(RTTHREAD hThread, void *pvUser);
446 static int x11EventThread(RTTHREAD hThread, void *pvUser);
447
448 bool waitForXMsg(XEvent &ecm, int type, uint32_t uiMaxMS = 100);
449 void clearEventQueue();
450 /* Usually XCheckMaskEvent could be used for queering selected x11 events.
451 * Unfortunately this doesn't work exactly with the events we need. So we
452 * use this predicate method below and XCheckIfEvent. */
453 static Bool isDnDRespondEvent(Display * /* pDisplay */, XEvent *pEvent, char *pUser)
454 {
455 if (!pEvent)
456 return False;
457 if ( pEvent->type == SelectionClear
458 || pEvent->type == ClientMessage
459 || pEvent->type == MotionNotify
460 || pEvent->type == SelectionRequest)
461// || ( pEvent->type == ClientMessage
462// && reinterpret_cast<XClientMessageEvent*>(pEvent)->window == reinterpret_cast<Window>(pUser))
463// || ( pEvent->type == SelectionRequest
464// && reinterpret_cast<XSelectionRequestEvent*>(pEvent)->requestor == reinterpret_cast<Window>(pUser)))
465 return True;
466 return False;
467 }
468
469 /* Private member vars */
470 Display *m_pDisplay;
471
472 RTCMTList<DnDEvent> m_eventQueue;
473 RTTHREAD m_hHGCMThread;
474 RTTHREAD m_hX11Thread;
475 RTSEMEVENT m_hEventSem;
476 DragInstance *m_pCurDnD;
477 bool m_fSrvStopping;
478
479 friend class DragInstance;
480};
481
482/*******************************************************************************
483 *
484 * DragInstanc Implementation
485 *
486 ******************************************************************************/
487
488DragInstance::DragInstance(Display *pDisplay, DragAndDropService *pParent)
489 : m_pParent(pParent)
490 , m_pDisplay(pDisplay)
491 , m_pScreen(0)
492 , m_rootWin(0)
493 , m_proxyWin(0)
494 , m_curWin(0)
495 , m_curVer(-1)
496 , m_mode(Unknown)
497 , m_state(Uninitialized)
498{
499 uninit();
500}
501
502void DragInstance::uninit()
503{
504 reset();
505 if (m_proxyWin != 0)
506 XDestroyWindow(m_pDisplay, m_proxyWin);
507 m_state = Uninitialized;
508 m_screenId = -1;
509 m_pScreen = 0;
510 m_rootWin = 0;
511 m_proxyWin = 0;
512}
513
514void DragInstance::reset()
515{
516 /* Hide the proxy win. */
517 hideProxyWin();
518 /* If we are currently the Xdnd selection owner, clear that. */
519 Window w = XGetSelectionOwner(m_pDisplay, xAtom(XA_XdndSelection));
520 if (w == m_proxyWin)
521 XSetSelectionOwner(m_pDisplay, xAtom(XA_XdndSelection), None, CurrentTime);
522 /* Clear any other DnD specific data on the proxy win. */
523 clearFormatsWindowProperty(m_proxyWin);
524 clearActionsWindowProperty(m_proxyWin);
525 /* Reset the internal state. */
526 m_formats.clear();
527 m_curWin = 0;
528 m_curVer = -1;
529 m_state = Initialized;
530}
531
532const RTCList<RTCString> DragInstance::m_sstrStringMimeTypes = RTCList<RTCString>()
533 /* Uri's */
534 << "text/uri-list"
535 /* Text */
536 << "text/plain;charset=utf-8"
537 << "UTF8_STRING"
538 << "text/plain"
539 << "COMPOUND_TEXT"
540 << "TEXT"
541 << "STRING"
542 /* OpenOffice formates */
543 << "application/x-openoffice-embed-source-xml;windows_formatname=\"Star Embed Source (XML)\""
544 << "application/x-openoffice-drawing;windows_formatname=\"Drawing Format\"";
545
546int DragInstance::init(uint32_t u32ScreenId)
547{
548 int rc = VINF_SUCCESS;
549 do
550 {
551 uninit();
552 /* Enough screens configured in the x11 server? */
553 if ((int)u32ScreenId > ScreenCount(m_pDisplay))
554 {
555 rc = VERR_GENERAL_FAILURE;
556 break;
557 }
558 /* Get the screen number from the x11 server. */
559// pDrag->screen = ScreenOfDisplay(m_pDisplay, u32ScreenId);
560// if (!pDrag->screen)
561// {
562// rc = VERR_GENERAL_FAILURE;
563// break;
564// }
565 m_screenId = u32ScreenId;
566 /* Now query the corresponding root window of this screen. */
567 m_rootWin = RootWindow(m_pDisplay, m_screenId);
568 if (!m_rootWin)
569 {
570 rc = VERR_GENERAL_FAILURE;
571 break;
572 }
573 /* Create an invisible window which will act as proxy for the DnD
574 * operation. This window will be used for both the GH and HG
575 * direction. */
576 XSetWindowAttributes attr;
577 RT_ZERO(attr);
578 attr.do_not_propagate_mask = 0;
579 attr.override_redirect = True;
580// attr.background_pixel = WhitePixel(m_pDisplay, m_screenId);
581 m_proxyWin = XCreateWindow(m_pDisplay, m_rootWin, 0, 0, 1, 1, 0,
582 CopyFromParent, InputOnly, CopyFromParent,
583 CWOverrideRedirect | CWDontPropagate,
584 &attr);
585
586// m_proxyWin = XCreateSimpleWindow(m_pDisplay, m_rootWin, 0, 0, 50, 50, 0, WhitePixel(m_pDisplay, m_screenId), WhitePixel(m_pDisplay, m_screenId));
587
588 if (!m_proxyWin)
589 {
590 rc = VERR_GENERAL_FAILURE;
591 break;
592 }
593 /* Make the new window Xdnd aware. */
594 Atom ver = VBOX_XDND_VERSION;
595 XChangeProperty(m_pDisplay, m_proxyWin, xAtom(XA_XdndAware), XA_ATOM, 32, PropModeReplace,
596 reinterpret_cast<unsigned char*>(&ver), 1);
597 }while(0);
598
599 m_state = Initialized;
600
601 return rc;
602}
603
604/*
605 * Host -> Guest
606 */
607
608int DragInstance::hgEnter(const RTCList<RTCString> &formats, uint32_t actions)
609{
610 int rc = VINF_SUCCESS;
611
612 reset();
613 DO(("DnD_ENTR: formats=%u: ", formats.size()));
614#if defined(VERBOSE) && defined(DEBUG_poetzsch)
615 for (size_t i = 0; i < formats.size(); ++i)
616 DO(("'%s' ", formats.at(i).c_str()));
617#endif /* DEBUG */
618 DO(("\n"));
619
620 m_formats = toAtomList(formats);
621
622 /* If we have more than 3 formats we have to use the type list extension. */
623 if (m_formats.size() > 3)
624 setFormatsWindowProperty(m_proxyWin, xAtom(XA_XdndTypeList));
625
626 /* Announce the possible actions */
627 setActionsWindowProperty(m_proxyWin, toX11Actions(actions));
628
629 /* Set the DnD selection owner to our window. */
630 XSetSelectionOwner(m_pDisplay, xAtom(XA_XdndSelection), m_proxyWin, CurrentTime);
631
632 m_mode = HG;
633 m_state = Dragging;
634
635 return rc;
636}
637
638int DragInstance::hgMove(uint32_t u32xPos, uint32_t u32yPos, uint32_t action)
639{
640 DO(("DnD_MOVE: "));
641
642 if ( m_mode != HG
643 || m_state != Dragging)
644 return VERR_INVALID_STATE;
645
646 int rc = VINF_SUCCESS;
647 int xrc = Success;
648
649 /* Move the mouse cursor within the guest. */
650 moveCursor(u32xPos, u32yPos);
651
652 Window newWin = None; /* Default to _no_ window below the cursor. */
653 long newVer = -1; /* This means the current window is _not_ XdndAware. */
654
655 /* Search for the application window below the cursor. */
656 newWin = gX11->applicationWindowBelowCursor(m_rootWin);
657 if (newWin != None)
658 {
659 /* Temp stuff for the XGetWindowProperty call. */
660 Atom atmp;
661 int fmt;
662 unsigned long cItems, cbRemaining;
663 unsigned char *pcData = NULL;
664 /* Query the XdndAware property from the window. We are interested in
665 * the version and if it is XdndAware at all. */
666 xrc = XGetWindowProperty(m_pDisplay, newWin, xAtom(XA_XdndAware), 0, 2, False, AnyPropertyType, &atmp, &fmt, &cItems, &cbRemaining, &pcData);
667 if (RT_UNLIKELY(xrc != Success))
668 DO(("DnD_MOVE: error in getting the window property (%s)\n", gX11->xErrorToString(xrc).c_str()));
669 else
670 {
671 if (RT_UNLIKELY(pcData == NULL || fmt != 32 || cItems != 1))
672 DO(("Prop=error[data=%#x,fmt=%u,items=%u] ", pcData, fmt, cItems));
673 else
674 {
675 newVer = reinterpret_cast<long*>(pcData)[0];
676 DO(("XdndAware=%u ", newVer));
677 }
678 XFree(pcData);
679 }
680 }
681
682 if (newWin != m_curWin && m_curVer != -1)
683 {
684 DO(("leave=%#x ", m_curWin));
685
686 /* We left the current XdndAware window. Announce this to the window. */
687
688 XClientMessageEvent m;
689 RT_ZERO(m);
690 m.type = ClientMessage;
691 m.display = m_pDisplay;
692 m.window = m_curWin;
693 m.message_type = xAtom(XA_XdndLeave);
694 m.format = 32;
695 m.data.l[0] = m_proxyWin;
696
697 xrc = XSendEvent(m_pDisplay, m_curWin, False, NoEventMask, reinterpret_cast<XEvent*>(&m));
698 if (RT_UNLIKELY(xrc == 0))
699 DO(("DnD_MOVE: error sending xevent\n"));
700 }
701
702 if (newWin != m_curWin && newVer != -1)
703 {
704 DO(("enter=%#x ", newWin));
705
706 /* We enter a new window. Announce the XdndEnter event to the new
707 * window. The first three mime types are attached to the event (the
708 * others could be requested by the XdndTypeList property from the
709 * window itself). */
710
711 XClientMessageEvent m;
712 RT_ZERO(m);
713 m.type = ClientMessage;
714 m.display = m_pDisplay;
715 m.window = newWin;
716 m.message_type = xAtom(XA_XdndEnter);
717 m.format = 32;
718 m.data.l[0] = m_proxyWin;
719 m.data.l[1] = RT_MAKE_U32_FROM_U8(m_formats.size() > 3 ? 1 : 0, 0, 0, RT_MIN(VBOX_XDND_VERSION, newVer));
720 m.data.l[2] = m_formats.value(0, None);
721 m.data.l[3] = m_formats.value(1, None);
722 m.data.l[4] = m_formats.value(2, None);
723
724 xrc = XSendEvent(m_pDisplay, newWin, False, NoEventMask, reinterpret_cast<XEvent*>(&m));
725 if (RT_UNLIKELY(xrc == 0))
726 DO(("DnD_MOVE: error sending xevent\n"));
727 }
728
729 if (newVer != -1)
730 {
731 DO(("move=%#x pos=%ux%u ", newWin, u32xPos, u32yPos));
732
733 /* Send a XdndPosition event with the proposed action to the guest. */
734
735 Atom pa = toX11Action(action);
736 DO(("action='%s' ", xAtomToString(pa).c_str()));
737
738 XClientMessageEvent m;
739 RT_ZERO(m);
740 m.type = ClientMessage;
741 m.display = m_pDisplay;
742 m.window = newWin;
743 m.message_type = xAtom(XA_XdndPosition);
744 m.format = 32;
745 m.data.l[0] = m_proxyWin;
746 m.data.l[2] = RT_MAKE_U32(u32yPos, u32xPos);
747 m.data.l[3] = CurrentTime;
748 m.data.l[4] = pa;
749
750 xrc = XSendEvent(m_pDisplay, newWin, False, NoEventMask, reinterpret_cast<XEvent*>(&m));
751 if (RT_UNLIKELY(xrc == 0))
752 DO(("DnD_MOVE: error sending xevent\n"));
753 }
754 if (newWin == None && newVer == -1)
755 /* No window to process, so send a ignore ack event to the host. */
756 rc = VbglR3DnDHGAcknowledgeOperation(DND_IGNORE_ACTION);
757
758 m_curWin = newWin;
759 m_curVer = RT_MIN(VBOX_XDND_VERSION, newVer);
760
761 DO(("\n"));
762
763 return rc;
764}
765
766int DragInstance::hgX11ClientMessage(const XEvent& e)
767{
768 if ( m_mode != HG)
769// || m_state != Dragging)
770 return VERR_INVALID_STATE;
771
772 /* Client messages are used to inform us about the status of a XdndAware
773 * window, in response of some events we send to them. */
774 int rc = VINF_SUCCESS;
775 if ( e.xclient.message_type == xAtom(XA_XdndStatus)
776 && m_curWin == static_cast<Window>(e.xclient.data.l[0]))
777 {
778 /* The XdndStatus message tell us if the window will accept the DnD
779 * event and with which action. We immediately send this info down to
780 * the host as a response of a previous DnD message. */
781 DO(("DnD_STAT: win=%#x,accept=%RTbool,action='%s'\n",
782 e.xclient.data.l[0],
783 ASMBitTest(&e.xclient.data.l[1], 0),
784 xAtomToString(e.xclient.data.l[4]).c_str()));
785 uint32_t uAction = DND_IGNORE_ACTION;
786 /* Todo: compare this with the allowed actions. */
787 if (ASMBitTest(&e.xclient.data.l[1], 0))
788 uAction = toHGCMAction(static_cast<Atom>(e.xclient.data.l[4]));
789 rc = VbglR3DnDHGAcknowledgeOperation(uAction);
790 }
791 else if (e.xclient.message_type == xAtom(XA_XdndFinished))
792 {
793 /* This message is send on a un/successful DnD drop request. */
794 DO(("DnD_FINI: win=%#x,success=%RTbool,action='%s'\n",
795 e.xclient.data.l[0],
796 ASMBitTest(&e.xclient.data.l[1], 0),
797 xAtomToString(e.xclient.data.l[2]).c_str()));
798 reset();
799 }
800 else
801 DO(("DnD_CLI: win=%#x,msg='%s'\n", e.xclient.data.l[0], xAtomToString(e.xclient.message_type).c_str()));
802 return rc;
803}
804
805int DragInstance::hgDrop()
806{
807 DO(("DnD_DROP: win=%#x\n", m_curWin));
808
809 if ( m_mode != HG
810 || m_state != Dragging)
811 return VERR_INVALID_STATE;
812
813 int rc = VINF_SUCCESS;
814
815 /* Send a drop event to the current window and reset our DnD status. */
816 XClientMessageEvent m;
817 RT_ZERO(m);
818 m.type = ClientMessage;
819 m.display = m_pDisplay;
820 m.window = m_curWin;
821 m.message_type = xAtom(XA_XdndDrop);
822 m.format = 32;
823 m.data.l[0] = m_proxyWin;
824 m.data.l[2] = CurrentTime;
825
826 int xrc = XSendEvent(m_pDisplay, m_curWin, False, NoEventMask, reinterpret_cast<XEvent*>(&m));
827 if (RT_UNLIKELY(xrc == 0))
828 DO(("DnD_DROP: error sending xevent\n"));
829
830 m_curWin = None;
831 m_curVer = -1;
832
833 m_state = Dropped;
834
835 return rc;
836}
837
838int DragInstance::hgX11SelectionRequest(const XEvent& e)
839{
840 AssertReturn(e.type == SelectionRequest, VERR_INVALID_PARAMETER);
841
842 if ( m_mode != HG)
843// || m_state != D)
844 return VERR_INVALID_STATE;
845
846 DO(("DnD_SELR: owner=%#x,requestor=%#x,sel_atom='%s',tar_atom='%s',prop_atom='%s',time=%u\n",
847 e.xselectionrequest.owner,
848 e.xselectionrequest.requestor,
849 xAtomToString(e.xselectionrequest.selection).c_str(),
850 xAtomToString(e.xselectionrequest.target).c_str(),
851 xAtomToString(e.xselectionrequest.property).c_str(),
852 e.xselectionrequest.time));
853
854 int rc = VINF_SUCCESS;
855
856 /* A window is asking for some data. Normally here the data would be copied
857 * into the selection buffer and send to the requestor. Obviously we can't
858 * do that, cause we first need to ask the host for the data of the
859 * requested mime type. This is done and later answered with the correct
860 * data (s. dataReceived). */
861
862 /* Is the requestor asking for the possible mime types? */
863 if(e.xselectionrequest.target == xAtom(XA_TARGETS))
864 {
865 DO(("DnD_SELR: ask for target list\n"));
866 /* If so, set the window property with the formats on the requestor
867 * window. */
868 setFormatsWindowProperty(e.xselectionrequest.requestor, e.xselectionrequest.property);
869 XEvent s;
870 RT_ZERO(s);
871 s.xselection.type = SelectionNotify;
872 s.xselection.display = e.xselection.display;
873 s.xselection.time = e.xselectionrequest.time;
874 s.xselection.selection = e.xselectionrequest.selection;
875 s.xselection.requestor = e.xselectionrequest.requestor;
876 s.xselection.target = e.xselectionrequest.target;
877 s.xselection.property = e.xselectionrequest.property;
878 int xrc = XSendEvent(e.xselection.display, e.xselectionrequest.requestor, False, 0, &s);
879 if (RT_UNLIKELY(xrc == 0))
880 DO(("DnD_SELR: error sending xevent\n"));
881 }
882 /* Is the requestor asking for a specific mime type (we support)? */
883 else if(m_formats.contains(e.xselectionrequest.target))
884 {
885 DO(("DnD_SELR: ask for data (format='%s')\n", xAtomToString(e.xselectionrequest.target).c_str()));
886 /* If so, we need to inform the host about this request. Save the
887 * selection request event for later use. */
888 if ( m_state != Dropped)
889 // || m_curWin != e.xselectionrequest.requestor)
890 {
891 DO(("DnD_SELR: refuse\n"));
892 XEvent s;
893 RT_ZERO(s);
894 s.xselection.type = SelectionNotify;
895 s.xselection.display = e.xselection.display;
896 s.xselection.time = e.xselectionrequest.time;
897 s.xselection.selection = e.xselectionrequest.selection;
898 s.xselection.requestor = e.xselectionrequest.requestor;
899 s.xselection.target = None;
900 s.xselection.property = e.xselectionrequest.property;
901 int xrc = XSendEvent(e.xselection.display, e.xselectionrequest.requestor, False, 0, &s);
902 if (RT_UNLIKELY(xrc == 0))
903 DO(("DnD_SELR: error sending xevent\n"));
904 }
905 else
906 {
907 memcpy(&m_selEvent, &e, sizeof(XEvent));
908 rc = VbglR3DnDHGRequestData(xAtomToString(e.xselectionrequest.target).c_str());
909 }
910 }
911 /* Anything else. */
912 else
913 {
914 DO(("DnD_SELR: refuse\n"));
915 /* We don't understand this request message and therefore answer with an
916 * refusal messages. */
917 XEvent s;
918 RT_ZERO(s);
919 s.xselection.type = SelectionNotify;
920 s.xselection.display = e.xselection.display;
921 s.xselection.time = e.xselectionrequest.time;
922 s.xselection.selection = e.xselectionrequest.selection;
923 s.xselection.requestor = e.xselectionrequest.requestor;
924 s.xselection.target = None; /* default is refusing */
925 s.xselection.property = None; /* default is refusing */
926 int xrc = XSendEvent(e.xselection.display, e.xselectionrequest.requestor, False, 0, &s);
927 if (RT_UNLIKELY(xrc == 0))
928 DO(("DnD_SELR: error sending xevent\n"));
929 }
930
931 return rc;
932}
933
934int DragInstance::hgDataReceived(void *pvData, uint32_t cData)
935{
936 if ( m_mode != HG
937 || m_state != Dropped)
938 return VERR_INVALID_STATE;
939
940 if (RT_UNLIKELY( pvData == NULL
941 || cData == 0))
942 return VERR_INVALID_PARAMETER;
943
944 if (RT_UNLIKELY(m_state != Dropped))
945 return VERR_INVALID_STATE;
946
947 /* Make a copy of the data. The xserver will become the new owner. */
948 void *pvNewData = RTMemAlloc(cData);
949 if (RT_UNLIKELY(!pvNewData))
950 return VERR_NO_MEMORY;
951 memcpy(pvNewData, pvData, cData);
952
953 /* The host send us the DnD data in the requested mime type. This allows us
954 * to fill the XdndSelection property of the requestor window with the data
955 * and afterwards inform him about the new status. */
956 XEvent s;
957 RT_ZERO(s);
958 s.xselection.type = SelectionNotify;
959 s.xselection.display = m_selEvent.xselection.display;
960// s.xselection.owner = m_selEvent.xselectionrequest.owner;
961 s.xselection.time = m_selEvent.xselectionrequest.time;
962 s.xselection.selection = m_selEvent.xselectionrequest.selection;
963 s.xselection.requestor = m_selEvent.xselectionrequest.requestor;
964 s.xselection.target = m_selEvent.xselectionrequest.target;
965 s.xselection.property = m_selEvent.xselectionrequest.property;
966
967 DO(("DnD_SEND: owner=%#x,requestor=%#x,sel_atom='%s',tar_atom='%s',prop_atom='%s',time=%u\n",
968 m_selEvent.xselectionrequest.owner,
969 s.xselection.requestor,
970 xAtomToString(s.xselection.selection).c_str(),
971 xAtomToString(s.xselection.target).c_str(),
972 xAtomToString(s.xselection.property).c_str(),
973 s.xselection.time));
974
975 /* Fill up the property with the data. */
976 XChangeProperty(s.xselection.display, s.xselection.requestor, s.xselection.property, s.xselection.target, 8, PropModeReplace,
977 reinterpret_cast<const unsigned char*>(pvNewData), cData);
978 int xrc = XSendEvent(s.xselection.display, s.xselection.requestor, True, 0, &s);
979 if (RT_UNLIKELY(xrc == 0))
980 DO(("DnD_SEND: error sending xevent\n"));
981
982 return VINF_SUCCESS;
983}
984
985
986#ifdef VBOX_WITH_DRAG_AND_DROP_GH
987/*
988 * Guest -> Host
989 */
990
991int DragInstance::ghIsDnDPending()
992{
993 int rc = VINF_SUCCESS;
994 Window w = XGetSelectionOwner(m_pDisplay, xAtom(XA_XdndSelection));
995 DO(("Checking pending %X %X\n", w, m_proxyWin));
996 /* Is there someone own the Xdnd selection which aren't we. */
997 if ( w
998 && w != m_proxyWin)
999 {
1000 /* Map the window on the current cursor position, which should provoke
1001 * an XdndEnter event. */
1002 int rx, ry;
1003 showProxyWin(rx, ry);
1004 XEvent e;
1005 if (m_pParent->waitForXMsg(e, ClientMessage))
1006 {
1007 int xrc = Success;
1008 XClientMessageEvent *clme = reinterpret_cast<XClientMessageEvent*>(&e);
1009 DO(("next X event %s\n", gX11->xAtomToString(clme->message_type).c_str()));
1010 if (clme->message_type == xAtom(XA_XdndEnter))
1011 {
1012 Atom type = None;
1013 int f;
1014 unsigned long n, a;
1015 unsigned char *ret = 0;
1016 reset();
1017
1018 m_formats.clear();
1019 m_actions.clear();
1020 m_curWin = w;
1021 DO(("XA_XdndEnter\n"));
1022 /* Check if the mime types are in the msg itself or if we need
1023 * to fetch the XdndTypeList property from the window. */
1024 if (!ASMBitTest(&clme->data.l[1], 0))
1025 {
1026 for (int i = 2; i < 5; ++i)
1027 {
1028 DO(("receive list msg: %s\n", gX11->xAtomToString(clme->data.l[i]).c_str()));
1029 m_formats.append(clme->data.l[i]);
1030 }
1031 }
1032 else
1033 {
1034 xrc = XGetWindowProperty(m_pDisplay, w, xAtom(XA_XdndTypeList), 0, VBOX_MAX_XPROPERTIES, False, XA_ATOM, &type, &f, &n, &a, &ret);
1035 if ( xrc == Success
1036 && n > 0
1037 && ret)
1038 {
1039 Atom *data = reinterpret_cast<Atom*>(ret);
1040 for (int i = 0; i < RT_MIN(VBOX_MAX_XPROPERTIES, n); ++i)
1041 {
1042 DO(("receive list: %s\n", gX11->xAtomToString(data[i]).c_str()));
1043 m_formats.append(data[i]);
1044 }
1045 XFree(ret);
1046 }
1047 }
1048 /* Fetch the possible list of actions, if this property is set. */
1049 xrc = XGetWindowProperty(m_pDisplay, w, xAtom(XA_XdndActionList), 0, VBOX_MAX_XPROPERTIES, False, XA_ATOM, &type, &f, &n, &a, &ret);
1050 if ( xrc == Success
1051 && n > 0
1052 && ret)
1053 {
1054 Atom *data = reinterpret_cast<Atom*>(ret);
1055 for (int i = 0; i < RT_MIN(VBOX_MAX_XPROPERTIES, n); ++i)
1056 {
1057 DO(("receive actions: %s\n", gX11->xAtomToString(data[i]).c_str()));
1058 m_actions.append(data[i]);
1059 }
1060 XFree(ret);
1061 }
1062
1063 m_state = Dragging;
1064 m_mode = GH;
1065 /* Acknowledge the event by sending a Status msg back to the
1066 * window. */
1067 XClientMessageEvent m;
1068 RT_ZERO(m);
1069 m.type = ClientMessage;
1070 m.display = m_pDisplay;
1071 m.window = clme->data.l[0];
1072 m.message_type = xAtom(XA_XdndStatus);
1073 m.format = 32;
1074 m.data.l[0] = m_proxyWin;
1075 m.data.l[1] = 1;
1076 m.data.l[4] = xAtom(XA_XdndActionCopy);
1077 xrc = XSendEvent(m_pDisplay, clme->data.l[0], False, 0, reinterpret_cast<XEvent*>(&m));
1078 if (RT_UNLIKELY(xrc == 0))
1079 DO(("DnD_PNDG: error sending xevent\n"));
1080 }
1081 else if (clme->message_type == xAtom(XA_XdndPosition))
1082 {
1083 DO(("XA_XdndPosition\n"));
1084 XClientMessageEvent m;
1085 RT_ZERO(m);
1086 m.type = ClientMessage;
1087 m.display = m_pDisplay;
1088 m.window = clme->data.l[0];
1089 m.message_type = xAtom(XA_XdndStatus);
1090 m.format = 32;
1091 m.data.l[0] = m_proxyWin;
1092 m.data.l[1] = 1;
1093 m.data.l[4] = clme->data.l[4];
1094 xrc = XSendEvent(m_pDisplay, clme->data.l[0], False, 0, reinterpret_cast<XEvent*>(&m));
1095 if (RT_UNLIKELY(xrc == 0))
1096 DO(("DnD_PNDG: error sending xevent\n"));
1097 }
1098 else if (clme->message_type == xAtom(XA_XdndLeave))
1099 {
1100 }
1101 }
1102 hideProxyWin();
1103
1104 rc = VbglR3DnDGHAcknowledgePending(DND_COPY_ACTION, toHGCMActions(m_actions), gX11->xAtomListToString(m_formats).c_str());
1105 }
1106 return rc;
1107}
1108
1109int DragInstance::ghDropped(const RTCString &strFormat, uint32_t action)
1110{
1111 DO(("DND_DRO: format='%s' action=%d\n", strFormat.c_str(), action));
1112 int rc = VINF_SUCCESS;
1113
1114 /* Show the proxy window, so that the source will find it. */
1115 int rx, ry;
1116 showProxyWin(rx, ry);
1117 XFlush(m_pDisplay);
1118 /* We send a fake release event to the current window, cause
1119 * this should have the grab. */
1120 sendButtonEvent(m_curWin, rx, ry, 1, false);
1121 /* The fake button release event, should lead to an XdndDrop event from the
1122 * source. Because of the showing of the proxy window, sometimes other Xdnd
1123 * events occurs before, like a XdndPosition event. We are not interested
1124 * in those, so try to get the right one. */
1125 XEvent e;
1126 XClientMessageEvent *clme = 0;
1127 RT_ZERO(e);
1128 int tries = 3;
1129 do
1130 {
1131 if (m_pParent->waitForXMsg(e, ClientMessage))
1132 {
1133 if (reinterpret_cast<XClientMessageEvent*>(&e)->message_type == xAtom(XA_XdndDrop))
1134 {
1135 clme = reinterpret_cast<XClientMessageEvent*>(&e);
1136 break;
1137 }
1138 }
1139 }while(tries--);
1140 if (clme)
1141 {
1142 /* Make some paranoid checks. */
1143 if (clme->message_type == xAtom(XA_XdndDrop))
1144 {
1145 /* Request to convert the selection in the specific format and
1146 * place it to our proxy window as property. */
1147 Window srcWin = m_curWin;//clme->data.l[0];
1148 Atom aFormat = gX11->stringToxAtom(strFormat.c_str());
1149 XConvertSelection(m_pDisplay, xAtom(XA_XdndSelection), aFormat, xAtom(XA_XdndSelection), m_proxyWin, clme->data.l[2]);
1150 /* Wait for the selection notify event. */
1151 RT_ZERO(e);
1152 if (m_pParent->waitForXMsg(e, SelectionNotify))
1153 {
1154 /* Make some paranoid checks. */
1155 if ( e.xselection.type == SelectionNotify
1156 && e.xselection.display == m_pDisplay
1157 && e.xselection.selection == xAtom(XA_XdndSelection)
1158 && e.xselection.requestor == m_proxyWin
1159 && e.xselection.target == aFormat)
1160 {
1161 DO(("DND_DRO: selection notfiy (from: %x)\n", m_curWin));
1162 Atom type;
1163 int format;
1164 unsigned long cItems, cbRemaining;
1165 unsigned char *ucData = 0;
1166 XGetWindowProperty(m_pDisplay, m_proxyWin, xAtom(XA_XdndSelection),
1167 0, VBOX_MAX_XPROPERTIES, True, AnyPropertyType,
1168 &type, &format, &cItems, &cbRemaining, &ucData);
1169 DO(("DND_DRO: %s %d %d %s\n", gX11->xAtomToString(type).c_str(), cItems, format, ucData));
1170 if ( type != None
1171 && ucData != NULL
1172 && format >= 8
1173 && cItems > 0
1174 && cbRemaining == 0)
1175 {
1176 size_t cbData = cItems * (format / 8);
1177 /* For whatever reason some of the string mime-types are not
1178 * zero terminated. Check that and correct it when necessary,
1179 * cause the guest side wants this always. */
1180 if ( m_sstrStringMimeTypes.contains(strFormat)
1181 && ucData[cbData - 1] != '\0')
1182 {
1183 DO(("rebuild %u\n", cbData));
1184 unsigned char *ucData1 = static_cast<unsigned char*>(RTMemAlloc(cbData + 1));
1185 if (ucData1)
1186 {
1187 memcpy(ucData1, ucData, cbData);
1188 ucData1[cbData++] = '\0';
1189 /* Got the data and its fully transfered. */
1190 rc = VbglR3DnDGHSendData(ucData1, cbData);
1191 RTMemFree(ucData1);
1192 }
1193 else
1194 rc = VERR_NO_MEMORY;
1195 }
1196 else
1197 /* Just send the data to the host. */
1198 rc = VbglR3DnDGHSendData(ucData, cbData);
1199
1200 DO(("send responce\n"));
1201 /* Confirm the result of the transfer to the source window. */
1202 XClientMessageEvent m;
1203 RT_ZERO(m);
1204 m.type = ClientMessage;
1205 m.display = m_pDisplay;
1206 m.window = srcWin;
1207 m.message_type = xAtom(XA_XdndFinished);
1208 m.format = 32;
1209 m.data.l[0] = m_proxyWin;
1210 m.data.l[1] = RT_SUCCESS(rc) ? 1 : 0; /* Confirm or deny success */
1211 m.data.l[2] = RT_SUCCESS(rc) ? toX11Action(action) : None; /* Action used on success */
1212
1213 int xrc = XSendEvent(m_pDisplay, srcWin, True, NoEventMask, reinterpret_cast<XEvent*>(&m));
1214 if (RT_UNLIKELY(xrc == 0))
1215 DO(("DnD_DRO: error sending xevent\n"));
1216 }
1217 else
1218 {
1219 if (type == xAtom(XA_INCR))
1220 {
1221 /* Todo: */
1222 AssertMsgFailed(("Incrementally transfers are not supported, yet\n"));
1223 rc = VERR_NOT_IMPLEMENTED;
1224 }
1225 else
1226 {
1227 AssertMsgFailed(("Not supported data type\n"));
1228 rc = VERR_INVALID_PARAMETER;
1229 }
1230 /* Cancel this. */
1231 XClientMessageEvent m;
1232 RT_ZERO(m);
1233 m.type = ClientMessage;
1234 m.display = m_pDisplay;
1235 m.window = srcWin;
1236 m.message_type = xAtom(XA_XdndFinished);
1237 m.format = 32;
1238 m.data.l[0] = m_proxyWin;
1239 m.data.l[1] = 0;
1240 m.data.l[2] = None;
1241 int xrc = XSendEvent(m_pDisplay, srcWin, False, NoEventMask, reinterpret_cast<XEvent*>(&m));
1242 if (RT_UNLIKELY(xrc == 0))
1243 DO(("DnD_DRO: error sending xevent\n"));
1244 m_curWin = 0;
1245 }
1246 /* Cleanup */
1247 if (ucData)
1248 XFree(ucData);
1249 }
1250 else
1251 rc = VERR_INVALID_PARAMETER;
1252 }
1253 else
1254 rc = VERR_TIMEOUT;
1255 }
1256 else
1257 rc = VERR_WRONG_ORDER;
1258 }
1259 else
1260 rc = VERR_TIMEOUT;
1261
1262 /* Inform the host on error */
1263 if (RT_FAILURE(rc))
1264 VbglR3DnDGHErrorEvent(rc);
1265
1266 /* At this point, we have either successfully transfered any data or not.
1267 * So reset our internal state, cause we are done. */
1268 reset();
1269
1270 return rc;
1271}
1272
1273#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
1274
1275/*
1276 * Helpers
1277 */
1278
1279int DragInstance::moveCursor(uint32_t u32xPos, uint32_t u32yPos)
1280{
1281 /* Move the guest pointer to the DnD position, so we can find the window
1282 * below that position. */
1283 XWarpPointer(m_pDisplay, None, m_rootWin, 0, 0, 0, 0, u32xPos, u32yPos);
1284 return VINF_SUCCESS;
1285}
1286
1287void DragInstance::sendButtonEvent(Window w, int rx, int ry, int button, bool fPress) const
1288{
1289// XTestFakeMotionEvent(m_pDisplay, -1, rx, ry, CurrentTime);
1290// XTestFakeMotionEvent(m_pDisplay, -1, rx + 1, ry + 1, CurrentTime);
1291// int rc = XTestFakeButtonEvent(m_pDisplay, 1, False, CurrentTime);
1292// if (rc != 0)
1293 {
1294 XButtonEvent be;
1295 RT_ZERO(be);
1296 be.display = m_pDisplay;
1297 be.root = m_rootWin;
1298 be.window = w;
1299 be.subwindow = None;
1300 be.same_screen = True;
1301 be.time = CurrentTime;
1302 be.button = button;
1303 be.state |= button == 1 ? Button1MotionMask :
1304 button == 2 ? Button2MotionMask :
1305 button == 3 ? Button3MotionMask :
1306 button == 4 ? Button4MotionMask :
1307 button == 5 ? Button5MotionMask : 0;
1308 be.type = fPress ? ButtonPress : ButtonRelease;
1309 be.x_root = rx;
1310 be.y_root = ry;
1311 XTranslateCoordinates(m_pDisplay, be.root, be.window, be.x_root, be.y_root, &be.x, &be.y, &be.subwindow);
1312 int xrc = XSendEvent(m_pDisplay, be.window, True, ButtonPressMask, reinterpret_cast<XEvent*>(&be));
1313 if (RT_UNLIKELY(xrc == 0))
1314 DO(("DnD_BTN: error sending xevent\n"));
1315 }
1316
1317}
1318
1319void DragInstance::showProxyWin(int &rx, int &ry) const
1320{
1321 int cx, cy;
1322 unsigned int m;
1323 Window r, c;
1324// XTestGrabControl(m_pDisplay, False);
1325 XQueryPointer(m_pDisplay, m_rootWin, &r, &c, &rx, &ry, &cx, &cy, &m);
1326 XSynchronize(m_pDisplay, True);
1327 XMapWindow(m_pDisplay, m_proxyWin);
1328 XRaiseWindow(m_pDisplay, m_proxyWin);
1329 XMoveResizeWindow(m_pDisplay, m_proxyWin, rx, ry, 1, 1);
1330 XWarpPointer(m_pDisplay, None, m_rootWin, 0, 0, 0, 0, rx , ry);
1331 XSynchronize(m_pDisplay, False);
1332// XTestGrabControl(m_pDisplay, True);
1333}
1334
1335void DragInstance::hideProxyWin() const
1336{
1337 XUnmapWindow(m_pDisplay, m_proxyWin);
1338}
1339
1340/* Currently, not used */
1341void DragInstance::registerForEvents(Window w) const
1342{
1343// if (w == m_proxyWin)
1344// return;
1345
1346 DO(("%x\n", w));
1347// XSelectInput(m_pDisplay, w, Button1MotionMask | Button2MotionMask | Button3MotionMask | Button4MotionMask | Button5MotionMask);//| SubstructureNotifyMask);
1348// XSelectInput(m_pDisplay, w, ButtonMotionMask); //PointerMotionMask);
1349 XSelectInput(m_pDisplay, w, PointerMotionMask); //PointerMotionMask);
1350 Window hRealRoot, hParent;
1351 Window *phChildrenRaw = NULL;
1352 unsigned cChildren;
1353 if (XQueryTree(m_pDisplay, w, &hRealRoot, &hParent, &phChildrenRaw, &cChildren))
1354 {
1355 for (unsigned i = 0; i < cChildren; ++i)
1356 registerForEvents(phChildrenRaw[i]);
1357 XFree(phChildrenRaw);
1358 }
1359}
1360
1361void DragInstance::setActionsWindowProperty(Window win, const RTCList<Atom> &actionList) const
1362{
1363 if (actionList.isEmpty())
1364 return;
1365
1366 XChangeProperty(m_pDisplay, win, xAtom(XA_XdndActionList), XA_ATOM, 32, PropModeReplace,
1367 reinterpret_cast<const unsigned char*>(actionList.raw()), actionList.size());
1368}
1369
1370void DragInstance::clearActionsWindowProperty(Window win) const
1371{
1372 XDeleteProperty(m_pDisplay, win, xAtom(XA_XdndActionList));
1373}
1374
1375void DragInstance::setFormatsWindowProperty(Window win, Atom property) const
1376{
1377 if (m_formats.isEmpty())
1378 return;
1379
1380 /* We support TARGETS and the data types. */
1381 RTCList<Atom> targets(m_formats.size() + 1);
1382 targets.append(xAtom(XA_TARGETS));
1383 targets.append(m_formats);
1384
1385 /* Add the property with the property data to the window. */
1386 XChangeProperty(m_pDisplay, win, property, XA_ATOM, 32, PropModeReplace,
1387 reinterpret_cast<const unsigned char*>(targets.raw()), targets.size());
1388}
1389
1390void DragInstance::clearFormatsWindowProperty(Window win) const
1391{
1392 XDeleteProperty(m_pDisplay, win, xAtom(XA_XdndTypeList));
1393}
1394
1395RTCList<Atom> DragInstance::toAtomList(const RTCList<RTCString> &formatList) const
1396{
1397 RTCList<Atom> atomList;
1398 for (size_t i = 0; i < formatList.size(); ++i)
1399 atomList.append(XInternAtom(m_pDisplay, formatList.at(i).c_str(), False));
1400
1401 return atomList;
1402}
1403
1404RTCList<Atom> DragInstance::toAtomList(void *pvData, uint32_t cData) const
1405{
1406 if ( !pvData
1407 || !cData)
1408 return RTCList<Atom>();
1409 char *pszStr = (char*)pvData;
1410 uint32_t cStr = cData;
1411
1412 RTCList<Atom> atomList;
1413 while (cStr > 0)
1414 {
1415 size_t cSize = RTStrNLen(pszStr, cStr);
1416 /* Create a copy with max N chars, so that we are on the save side,
1417 * even if the data isn't zero terminated. */
1418 char *pszTmp = RTStrDupN(pszStr, cSize);
1419 DO(("f: %s\n", pszTmp));
1420 atomList.append(XInternAtom(m_pDisplay, pszTmp, False));
1421 RTStrFree(pszTmp);
1422 pszStr += cSize + 1;
1423 cStr -= cSize + 1;
1424 }
1425
1426 return atomList;
1427}
1428
1429/* static */
1430Atom DragInstance::toX11Action(uint32_t uAction)
1431{
1432 /* Ignore is None */
1433 return (isDnDCopyAction(uAction) ? xAtom(XA_XdndActionCopy) :
1434 isDnDMoveAction(uAction) ? xAtom(XA_XdndActionMove) :
1435 isDnDLinkAction(uAction) ? xAtom(XA_XdndActionLink) :
1436 None);
1437}
1438
1439/* static */
1440RTCList<Atom> DragInstance::toX11Actions(uint32_t uActions)
1441{
1442 RTCList<Atom> actionList;
1443 if (hasDnDCopyAction(uActions))
1444 actionList.append(xAtom(XA_XdndActionCopy));
1445 if (hasDnDMoveAction(uActions))
1446 actionList.append(xAtom(XA_XdndActionMove));
1447 if (hasDnDLinkAction(uActions))
1448 actionList.append(xAtom(XA_XdndActionLink));
1449
1450 return actionList;
1451}
1452
1453/* static */
1454uint32_t DragInstance::toHGCMAction(Atom atom)
1455{
1456 uint32_t uAction = DND_IGNORE_ACTION;
1457 if (atom == xAtom(XA_XdndActionCopy))
1458 uAction = DND_COPY_ACTION;
1459 else if (atom == xAtom(XA_XdndActionMove))
1460 uAction = DND_MOVE_ACTION;
1461 else if (atom == xAtom(XA_XdndActionLink))
1462 uAction = DND_LINK_ACTION;
1463 return uAction;
1464}
1465
1466/* static */
1467uint32_t DragInstance::toHGCMActions(const RTCList<Atom> &actionsList)
1468{
1469 uint32_t uActions = DND_IGNORE_ACTION;
1470 for (size_t i = 0; i < actionsList.size(); ++i)
1471 uActions |= toHGCMAction(actionsList.at(i));
1472 return uActions;
1473}
1474
1475/*******************************************************************************
1476 *
1477 * DragAndDropService Implementation
1478 *
1479 ******************************************************************************/
1480
1481RTCList<RTCString> toStringList(void *pvData, uint32_t cData)
1482{
1483 if ( !pvData
1484 || !cData)
1485 return RTCList<RTCString>();
1486 char *pszStr = (char*)pvData;
1487 uint32_t cStr = cData;
1488
1489 RTCList<RTCString> strList;
1490 while (cStr > 0)
1491 {
1492 size_t cSize = RTStrNLen(pszStr, cStr);
1493 /* Create a copy with max N chars, so that we are on the save side,
1494 * even if the data isn't zero terminated. */
1495 char *pszTmp = RTStrDupN(pszStr, cSize);
1496 strList.append(pszTmp);
1497 RTStrFree(pszTmp);
1498 pszStr += cSize + 1;
1499 cStr -= cSize + 1;
1500 }
1501
1502 return strList;
1503}
1504
1505bool DragAndDropService::waitForXMsg(XEvent &ecm, int type, uint32_t uiMaxMS /* = 100 */)
1506{
1507 const uint64_t uiStart = RTTimeProgramMilliTS();
1508 do
1509 {
1510 if (!m_eventQueue.isEmpty())
1511 {
1512 DO(("new msg size %d\n", m_eventQueue.size()));
1513 /* Check if there is a client message in the queue. */
1514 for (size_t i = 0; i < m_eventQueue.size(); ++i)
1515 {
1516 DnDEvent e = m_eventQueue.at(i);
1517 if( e.type == DnDEvent::X11_Type)
1518 DO(("new msg\n"));
1519 if( e.type == DnDEvent::X11_Type
1520 && e.x11.type == type)
1521 {
1522 m_eventQueue.removeAt(i);
1523 ecm = e.x11;
1524 return true;
1525 }
1526 }
1527 }
1528 int rc = RTSemEventWait(m_hEventSem, 25);
1529// if (RT_FAILURE(rc))
1530// return false;
1531 }
1532 while(RTTimeProgramMilliTS() - uiStart < uiMaxMS);
1533
1534 return false;
1535}
1536
1537void DragAndDropService::clearEventQueue()
1538{
1539 m_eventQueue.clear();
1540}
1541
1542int DragAndDropService::run(bool fDaemonised /* = false */)
1543{
1544 int rc = VINF_SUCCESS;
1545 LogRelFlowFunc(("\n"));
1546
1547 /* We need to initialize XLib with thread support, otherwise our
1548 * simultaneously access to the display makes trouble (has to be called
1549 * before any usage of XLib). */
1550 if (!XInitThreads())
1551 AssertMsgFailedReturn(("Failed to initialize thread-safe XLib.\n"), VERR_GENERAL_FAILURE);
1552
1553 do
1554 {
1555 /* Initialize our service */
1556 rc = VbglR3DnDInit();
1557 if (RT_FAILURE(rc))
1558 break;
1559
1560 /* Initialize X11 DND */
1561 rc = x11DragAndDropInit();
1562 if (RT_FAILURE(rc))
1563 break;
1564
1565 m_pCurDnD = new DragInstance(m_pDisplay, this);
1566 /* Note: For multiple screen support in VBox it is not necessary to use
1567 * another screen number than zero. Maybe in the future it will become
1568 * necessary if VBox supports multiple X11 screens. */
1569 m_pCurDnD->init(0);
1570 /* Loop over new events */
1571 do
1572 {
1573 DnDEvent e;
1574 RT_ZERO(e);
1575 if (m_eventQueue.isEmpty())
1576 rc = RTSemEventWait(m_hEventSem, 50);
1577 if (!m_eventQueue.isEmpty())
1578 {
1579 e = m_eventQueue.first();
1580 m_eventQueue.removeFirst();
1581 DO(("new msg %d\n", e.type));
1582 if (e.type == DnDEvent::HGCM_Type)
1583 {
1584 switch (e.hgcm.uType)
1585 {
1586 case DragAndDropSvc::HOST_DND_HG_EVT_ENTER:
1587 {
1588 RTCList<RTCString> formats = RTCString(e.hgcm.pszFormats, e.hgcm.cbFormats - 1).split("\r\n");
1589 m_pCurDnD->hgEnter(formats, e.hgcm.u.a.uAllActions);
1590 /* Enter is always followed by a move event. */
1591 }
1592 case DragAndDropSvc::HOST_DND_HG_EVT_MOVE:
1593 {
1594 m_pCurDnD->hgMove(e.hgcm.u.a.uXpos, e.hgcm.u.a.uYpos, e.hgcm.u.a.uDefAction);
1595 break;
1596 }
1597 case DragAndDropSvc::HOST_DND_HG_EVT_LEAVE:
1598 {
1599 m_pCurDnD->reset();
1600 /* Not sure if this is really right! */
1601 clearEventQueue();
1602 break;
1603 }
1604 case DragAndDropSvc::HOST_DND_HG_EVT_DROPPED:
1605 {
1606 m_pCurDnD->hgDrop();
1607 break;
1608 }
1609 case DragAndDropSvc::HOST_DND_HG_SND_DATA:
1610 {
1611 m_pCurDnD->hgDataReceived(e.hgcm.u.b.pvData, e.hgcm.u.b.cbData);
1612 break;
1613 }
1614#ifdef VBOX_WITH_DRAG_AND_DROP_GH
1615 case DragAndDropSvc::HOST_DND_GH_REQ_PENDING:
1616 {
1617 m_pCurDnD->ghIsDnDPending();
1618 break;
1619 }
1620 case DragAndDropSvc::HOST_DND_GH_EVT_DROPPED:
1621 {
1622 m_pCurDnD->ghDropped(e.hgcm.pszFormats, e.hgcm.u.a.uDefAction);
1623 /* Not sure if this is really right! */
1624 clearEventQueue();
1625 break;
1626 }
1627#endif
1628 }
1629 /* Some messages require cleanup. */
1630 switch (e.hgcm.uType)
1631 {
1632 case DragAndDropSvc::HOST_DND_HG_EVT_ENTER:
1633 case DragAndDropSvc::HOST_DND_HG_EVT_MOVE:
1634 case DragAndDropSvc::HOST_DND_HG_EVT_DROPPED:
1635#ifdef VBOX_WITH_DRAG_AND_DROP_GH
1636 case DragAndDropSvc::HOST_DND_GH_EVT_DROPPED:
1637#endif
1638 {
1639 if (e.hgcm.pszFormats)
1640 RTMemFree(e.hgcm.pszFormats);
1641 break;
1642 }
1643 case DragAndDropSvc::HOST_DND_HG_SND_DATA:
1644 {
1645 if (e.hgcm.pszFormats)
1646 RTMemFree(e.hgcm.pszFormats);
1647 if (e.hgcm.u.b.pvData)
1648 RTMemFree(e.hgcm.u.b.pvData);
1649 break;
1650 }
1651 }
1652
1653 }
1654 else if(e.type == DnDEvent::X11_Type)
1655 {
1656 DO(("X11 type: %u\n", e.x11.type));
1657 /* Now the X11 event stuff */
1658 switch (e.x11.type)
1659 {
1660 case SelectionRequest: m_pCurDnD->hgX11SelectionRequest(e.x11); break;
1661 case ClientMessage: m_pCurDnD->hgX11ClientMessage(e.x11); break;
1662 case SelectionClear: DO(("DnD_CLER\n")); break;
1663// case MotionNotify: m_pCurDnD->hide(); break;
1664 }
1665 }
1666 }
1667 }while(!ASMAtomicReadBool(&m_fSrvStopping));
1668 }while(0);
1669
1670 LogRelFlowFunc(("returning %Rrc\n", rc));
1671 return rc;
1672}
1673
1674int DragAndDropService::x11DragAndDropInit()
1675{
1676 /* Connect to the x11 server. */
1677 m_pDisplay = XOpenDisplay(NULL);
1678 if (!m_pDisplay)
1679 /* todo: correct errors */
1680 return VERR_NOT_FOUND;
1681
1682 xHelpers::instance(m_pDisplay);
1683
1684 int rc = VINF_SUCCESS;
1685 do
1686 {
1687 /* Signal a new event to our main loop. */
1688 rc = RTSemEventCreate(&m_hEventSem);
1689 if (RT_FAILURE(rc))
1690 break;
1691 /* Event thread for events coming from the HGCM device. */
1692 rc = RTThreadCreate(&m_hHGCMThread, hgcmEventThread, this,
1693 0, RTTHREADTYPE_MSG_PUMP, RTTHREADFLAGS_WAITABLE,
1694 "HGCM-NOTIFY");
1695 if (RT_FAILURE(rc))
1696 break;
1697 /* Event thread for events coming from the x11 system. */
1698 rc = RTThreadCreate(&m_hX11Thread, x11EventThread, this,
1699 0, RTTHREADTYPE_MSG_PUMP, RTTHREADFLAGS_WAITABLE,
1700 "X11-NOTIFY");
1701 }while(0);
1702
1703 /* Cleanup on failure */
1704 if (RT_FAILURE(rc))
1705 x11DragAndDropTerm();
1706
1707 return rc;
1708}
1709
1710int DragAndDropService::x11DragAndDropTerm()
1711{
1712 /* Mark that we are stopping. */
1713 ASMAtomicWriteBool(&m_fSrvStopping, true);
1714
1715 /* Send a x11 client messages to the x11 event loop. */
1716 XClientMessageEvent m;
1717 RT_ZERO(m);
1718 m.type = ClientMessage;
1719 m.display = m_pDisplay;
1720 m.window = None;
1721 m.message_type = xAtom(XA_dndstop);
1722 m.format = 32;
1723 int xrc = XSendEvent(m_pDisplay, None, True, NoEventMask, reinterpret_cast<XEvent*>(&m));
1724 if (RT_UNLIKELY(xrc == 0))
1725 DO(("DnD_TERM: error sending xevent\n"));
1726 /* Wait for our event threads to stop. */
1727// if (m_hX11Thread)
1728// RTThreadWait(m_hX11Thread, RT_INDEFINITE_WAIT, 0);
1729// if (m_hHGCMThread)
1730// RTThreadWait(m_hHGCMThread, RT_INDEFINITE_WAIT, 0);
1731 /* Cleanup */
1732 /* todo: This doesn't work. The semaphore was interrupted by the user
1733 * signal. It is not possible to destroy a semaphore while it is in interrupted state.
1734 * According to Frank, the cleanup stuff done here is done _wrong_. We just
1735 * should signal the main loop to stop and do the cleanup there. Needs
1736 * adoption in all VBoxClient::Service's. */
1737// if (m_hEventSem)
1738// RTSemEventDestroy(m_hEventSem);
1739 if (m_pDisplay)
1740 XCloseDisplay(m_pDisplay);
1741 return VINF_SUCCESS;
1742}
1743
1744/* static */
1745int DragAndDropService::hgcmEventThread(RTTHREAD hThread, void *pvUser)
1746{
1747 AssertPtrReturn(pvUser, VERR_INVALID_PARAMETER);
1748 DragAndDropService *pSrv = static_cast<DragAndDropService*>(pvUser);
1749 DnDEvent e;
1750 do
1751 {
1752 RT_ZERO(e);
1753 e.type = DnDEvent::HGCM_Type;
1754 /* Wait for new events */
1755 int rc = VbglR3DnDProcessNextMessage(&e.hgcm);
1756 if (RT_SUCCESS(rc))
1757 {
1758 pSrv->m_eventQueue.append(e);
1759 rc = RTSemEventSignal(pSrv->m_hEventSem);
1760 if (RT_FAILURE(rc))
1761 return rc;
1762 }
1763 }while(!ASMAtomicReadBool(&pSrv->m_fSrvStopping));
1764
1765 return VINF_SUCCESS;
1766}
1767
1768/* static */
1769int DragAndDropService::x11EventThread(RTTHREAD hThread, void *pvUser)
1770{
1771 AssertPtrReturn(pvUser, VERR_INVALID_PARAMETER);
1772 DragAndDropService *pSrv = static_cast<DragAndDropService*>(pvUser);
1773 DnDEvent e;
1774 do
1775 {
1776 /* Wait for new events. We can't use XIfEvent here, cause this locks
1777 * the window connection with a mutex and if no X11 events occurs this
1778 * blocks any other calls we made to X11. So instead check for new
1779 * events and if there are not any new one, sleep for a certain amount
1780 * of time. */
1781 if (XEventsQueued(pSrv->m_pDisplay, QueuedAfterFlush) > 0)
1782 {
1783 RT_ZERO(e);
1784 e.type = DnDEvent::X11_Type;
1785 XNextEvent(pSrv->m_pDisplay, &e.x11);
1786 /* Check for a stop message. */
1787// if ( e.x11.type == ClientMessage
1788// && e.x11.xclient.message_type == xAtom(XA_dndstop))
1789// {
1790// break;
1791// }
1792// if (isDnDRespondEvent(pSrv->m_pDisplay, &e.x11, 0))
1793 {
1794 /* Appending makes a copy of the event structure. */
1795 pSrv->m_eventQueue.append(e);
1796 int rc = RTSemEventSignal(pSrv->m_hEventSem);
1797 if (RT_FAILURE(rc))
1798 return rc;
1799 }
1800 }
1801 else
1802 RTThreadSleep(25);
1803 } while(!ASMAtomicReadBool(&pSrv->m_fSrvStopping));
1804
1805 return VINF_SUCCESS;
1806}
1807
1808/* Static factory */
1809VBoxClient::Service *VBoxClient::GetDragAndDropService()
1810{
1811 return new(DragAndDropService);
1812}
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