VirtualBox

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

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

DnD/VBoxClient: Use RT_BIT macros for the Xdnd flags, renamed DnDEvent -> DNDEVENT to emphasize that it's a POD type + some more renaming.

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

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