VirtualBox

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

Last change on this file since 99114 was 98474, checked in by vboxsync, 2 years ago

Additions: X11: Add possibility restart VBoxClient processes during Guest Additions update, bugref:10359.

This commit makes VBoxClient processes to temporary release reference to vboxguest kernel module and then
restart itself. So module can be reloaded and newly started VBoxClient instance will utilize updated module.

When VBoxClient starts in demonized mode, it forks a child process which represents actual service. Parent
process continues to run and its main function is to restart child when it crashes or terminates with exit
status != 0. This commit makes child process to catch SIGUSR1 and terminate with exit
status VBGLR3EXITCODERELOAD (currently equal to 2). Parent process will detect this and in turn will release
its reference to vboxguest kernel module, allowing to reload it. The parent process will then wait for SIGUSR1
itself. Once received, it will restart itself (loading new, updated VBoxClient process image). This is a part
of the procedure to install and reload/restart Guest Additions kernel modules and user services without
requiring guest reboot.

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