1
/*********************************************************
2
* Copyright (C) 2005 VMware, Inc. All rights reserved.
4
* This program is free software; you can redistribute it and/or modify it
5
* under the terms of the GNU Lesser General Public License as published
6
* by the Free Software Foundation version 2.1 and no later version.
8
* This program is distributed in the hope that it will be useful, but
9
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10
* or FITNESS FOR A PARTICULAR PURPOSE. See the Lesser GNU General Public
11
* License for more details.
13
* You should have received a copy of the GNU Lesser General Public License
14
* along with this program; if not, write to the Free Software Foundation, Inc.,
15
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
17
*********************************************************/
22
* Handles the guest side of host<->guest DnD operations.
27
* The DnD process within the guest starts when we receive a "dnd.ungrab" RPC
28
* message from the host, which invokes DnDRpcInMouseUngrabCB(). The MKS
29
* sends this RPC when it sees the mouse stray outside of the clip (guest's
30
* viewable area). RpcInMouseUngrabCB() will determine whether a DnD is
31
* pending by calling DnDDragPending():
32
* o if a DnD is not pending, it replies with a "dnd.notpending" RPC and we
34
* o if a DnD is pending, we send fake X events to the X server that place
35
* our invisible window at the location of the mouse pointer and generate
36
* mouse movements over the window.
38
* Faking mouse movement over our window causes Gtk to send us a "drag_motion"
39
* signal, which invokes DnDGtkDragMotionCB(). Here we find a common target
40
* (drop type) and request the data from the drag source via
41
* gtk_drag_get_data().
43
* When the data is ready, Gtk signals us with a "data_received" signal. We
44
* parse the provided data and send the file names to the host with
45
* a "dnd.data.set" RPC. Then we start the DnD operation with a "dnd.enter"
46
* RPC. Upon receiving the "dnd.enter", the MKS will allow the ungrab of the
47
* mouse from the guest window and the user will be able to select a location
50
* (Note that it is important that the guest reply to the "dnd.ungrab" with
51
* either a "dnd.notpending" or a "dnd.enter" in a timely manner, since the
52
* MKS will delay mouse packets until it has received a reply from the
55
* When the user drops the files, the host will send us a "dnd.data.get.file"
56
* for each file, which invokes DnDRpcInGetNextFileCB(). On each invocation,
57
* we reply with the next file from the Guest->Host file list (obtained from
58
* DnDGHFileListGetNext()), and "|end|" when there are no more files. With
59
* this information, the host copies the files from the guest using HGFS.
61
* When the host has finished copying the files, it sends us a "dnd.finish"
62
* RPC, which invokes DnDRpcInFinishCB(). At this point, we fake X events
63
* that cause a mouse button release over our window.
65
* This button release causes Gtk to send us a "drag_drop" signal, which
66
* invokes DnDGtkDragDropCB(). Here we simply clean up our state and
67
* indicate that the drag finished successfully by calling gtk_drag_finish().
69
* If an error occurs at any point, the host sends us a "dnd.finish cancel"
70
* RPC. We will fake an ESC key press and release to cancel the pending DnD
77
* A host->guest DnD begins with a "dnd.data.set" from the vmx to provide the
78
* list of files being dragged into the guest, then a "dnd.enter" to begin the
79
* DnD operation. When the "dnd.enter" is received, this process will send
80
* a fake mouse button press and mouse movement on its window, starting the
81
* DnD operation within the guest. At this point the mouse still has not been
82
* grabbed by the guest and all mouse movements go only to the host.
84
* As part of the normal DnD protocol on the host, the UI in the host will
85
* receive updates on the location of the mouse within its target window.
86
* This location is translated to guest coordinates and sent to us via the
87
* "dnd.move" RPC, at which point we fake additional mouse movements to that
88
* location. When the user releases the mouse, the host UI is again notified
89
* and sends us a "dnd.drop" RPC.
91
* When the drop occurs, we add a block (via vmblock) on the directory
92
* containing the files to be given to the target application, then fake
93
* a mouse release at the location of the drop. This will cause the target
94
* application to request the data, which we provide through our
95
* "drag_data_get" handler (DnDGtkDataRequestCB()). When the application
96
* attempts to access these files it will be blocked by vmblock.
98
* After the drop is sent, the host will send the files to the hgfs server
99
* running inside this process, and will notify us when that transfer is
100
* complete via the "dnd.data.finish" RPC. If the transfer is successful, we
101
* remove the block to allow the target application to access the files. If
102
* the transfer is unsuccessful, we remove any partially copied files then
103
* remove the block; this has the effect of failing the DnD operation since
104
* the target cannot access the necessary files. Once this is done, we
105
* generate a new file root within the staging directory and send that to the
106
* host for the next DnD operation.
108
* Note that we used to fake the mouse release only after the data transfer
109
* completed (and Windows guests still behave that way), but this was changed
110
* since the Linux UI was modified to allow guest interaction while the
111
* progress dialog (for the file transfer) was displayed and updating. This
112
* caused a lot of instability since the mouse was no longer in a predictable
113
* state when the fake release was sent. vmblock let us work around this by
114
* changing where the block occurred.
120
#include <X11/extensions/XTest.h> /* for XTest*() */
121
#include <X11/keysym.h> /* for XK_Escape */
122
#include <X11/Xatom.h> /* for XA_WINDOW */
124
#include "vmwareuserInt.h"
125
#include "vm_assert.h"
126
#include "vm_basic_defs.h"
127
#include "eventManager.h"
132
#include "guestApp.h"
134
#include "cpNameUtil.h"
137
#include "hgfsVirtualDir.h"
138
#include "hgfsServerPolicy.h"
141
#include "vmware/guestrpc/tclodefs.h"
143
#define DND_MAX_PATH 6144
144
#define DRAG_TARGET_NAME_URI_LIST "text/uri-list"
145
#define DRAG_TARGET_INFO_URI_LIST 0
146
#define DRAG_TARGET_NAME_TEXT_PLAIN "text/plain"
147
#define DRAG_TARGET_INFO_TEXT_PLAIN 1
148
#define DRAG_TARGET_NAME_STRING "STRING"
149
#define DRAG_TARGET_INFO_STRING 2
151
* We support all three drag targets from Host->Guest since we can present
152
* filenames in any of these forms if an application requests. However, we
153
* only support file drag targets (text/uri-list) from Guest->Host since we
154
* can only DnD files across the backdoor.
156
#define NR_DRAG_TARGETS 3
157
#define NR_GH_DRAG_TARGETS 1
159
#define DROPEFFECT_NONE 0
160
#define DROPEFFECT_COPY 1
161
#define DROPEFFECT_MOVE 2
162
#define DROPEFFECT_LINK 4
165
* More friendly names for calling DnDFakeXEvents(). This is really ugly but
166
* it allows us to keep all of the X fake event code in one place.
168
* Operation | showWidget | buttonEvent | buttonPress | moveWindow | coordsProvided
169
* ----------+------------+-------------+-------------+------------+---------------
170
* G->H Drag | Yes | No | n/a | Yes | No
171
* G->H Drop | No | Yes | Release | Yes | No
172
* H->G Drag | Yes | Yes | Press | Yes | No
173
* H->G Move | No | No | n/a | No | Yes
174
* H->G Drop | No | Yes | Release | No | Yes
175
* ----------+------------+-------------+-------------+------------+---------------
177
#define DnDGHFakeDrag(widget) \
178
DnDFakeXEvents(widget, TRUE, FALSE, FALSE, TRUE, FALSE, 0, 0)
179
#define DnDGHFakeDrop(widget) \
180
DnDFakeXEvents(widget, FALSE, TRUE, FALSE, TRUE, FALSE, 0, 0)
181
#define DnDHGFakeDrag(widget) \
182
DnDFakeXEvents(widget, TRUE, TRUE, TRUE, TRUE, FALSE, 0, 0)
183
#define DnDHGFakeMove(widget, x, y) \
184
DnDFakeXEvents(widget, FALSE, FALSE, FALSE, FALSE, TRUE, x, y)
185
#define DnDHGFakeDrop(widget, x, y) \
186
DnDFakeXEvents(widget, FALSE, TRUE, FALSE, FALSE, TRUE, x, y)
189
# define GDKATOM_TO_ATOM(gdkAtom) gdk_x11_atom_to_xatom(gdkAtom)
191
# define GDKATOM_TO_ATOM(gdkAtom) gdkAtom
195
* Forward Declarations
197
static Bool DnDRpcInEnterCB (char const **result, size_t *resultLen,
198
const char *name, const char *args,
199
size_t argsSize,void *clientData);
200
static Bool DnDRpcInDataSetCB (char const **result, size_t *resultLen,
201
const char *name, const char *args,
202
size_t argsSize,void *clientData);
203
static Bool DnDRpcInMoveCB (char const **result, size_t *resultLen,
204
const char *name, const char *args,
205
size_t argsSize,void *clientData);
206
static Bool DnDRpcInDataFinishCB (char const **result, size_t *resultLen,
207
const char *name, const char *args,
208
size_t argsSize,void *clientData);
209
static Bool DnDRpcInDropCB (char const **result, size_t *resultLen,
210
const char *name, const char *args,
211
size_t argsSize,void *clientData);
212
static Bool DnDRpcInMouseUngrabCB(char const **result, size_t *resultLen,
213
const char *name, const char *args,
214
size_t argsSize,void *clientData);
215
static Bool DnDRpcInGetNextFileCB(char const **result, size_t *resultLen,
216
const char *name, const char *args,
217
size_t argsSize,void *clientData);
218
static Bool DnDRpcInFinishCB (char const **result, size_t *resultLen,
219
const char *name, const char *args,
220
size_t argsSize,void *clientData);
223
* Gtk DnD specific event/signal callbacks.
225
/* For Host->Guest DnD */
226
static void DnDGtkBeginCB(GtkWidget *widget, GdkDragContext *dc, gpointer data);
227
static void DnDGtkEndCB(GtkWidget *widget, GdkDragContext *dc, gpointer data);
228
static void DnDGtkDataRequestCB(GtkWidget *widget, GdkDragContext *dc,
229
GtkSelectionData *selection_data,
230
guint info, guint time, gpointer data);
232
/* For Guest->Host DnD */
233
static gboolean DnDGtkDragMotionCB(GtkWidget *widget, GdkDragContext *dc, gint x,
234
gint y, guint time, gpointer data);
235
static void DnDGtkDragDataReceivedCB(GtkWidget *widget, GdkDragContext *dc,
236
gint x, gint y, GtkSelectionData *dragData,
237
guint info, guint time, gpointer data);
238
static gboolean DnDGtkDragDropCB(GtkWidget *widget, GdkDragContext *dc,
239
gint x, gint y, guint time, gpointer data);
244
static Bool DnDSendVmxNewFileRoot(char *rpcCmd);
245
static Bool DnDFakeXEvents(GtkWidget *widget, Bool showWidget,
246
Bool buttonEvent, Bool buttonPress,
248
Bool coordsProvided, int x, int y);
249
static void DnDSendEscapeKey(GtkWidget *mainWnd);
250
static INLINE Bool DnDGHDragPending(GtkWidget *widget);
251
static INLINE Bool DnDGHXdndDragPending(GtkWidget *widget);
252
static INLINE void DnDGHXdndClearPending(GtkWidget *widget);
253
static INLINE Bool DnDGHMotifDragPending(GtkWidget *widget);
254
static INLINE void DnDGHFileListClear(void);
255
static INLINE void DnDGHFileListSet(char *fileList, size_t fileListSize);
256
static INLINE Bool DnDGHFileListGetNext(char **fileName, size_t *fileNameSize);
257
static INLINE void DnDGHStateInit(GtkWidget *widget);
258
static INLINE void DnDHGStateInit(void);
259
static INLINE Bool DnDGHCancel(GtkWidget *widget);
260
static Bool DnDGHXEventTimeout(void *clientData);
269
char *dndFileListNext;
270
size_t dndFileListSize;
271
GdkDragContext *dragContext;
275
static Bool gHGDnDInProgress;
276
static Bool gDoneDragging;
277
static Bool gHGDataPending;
278
static char gDnDData[1024];
279
static GdkDragContext *gDragCtx;
280
static GtkTargetEntry gTargetEntry[NR_DRAG_TARGETS];
281
static GdkAtom gTargetEntryAtom[NR_GH_DRAG_TARGETS];
282
static char gFileRoot[DND_MAX_PATH];
283
static size_t gFileRootSize;
284
static size_t gDnDDataSize;
288
* From vmwareuserInt.h
296
* Host->Guest RPC callback implementations
300
*-----------------------------------------------------------------------------
304
* For Host->Guest operations only.
305
* User has dragged something over this guest's MKS window
308
* TRUE on success, FALSE otherwise
311
* Some GdkEvents are generated which will "drag" the mouse. A directory
314
*-----------------------------------------------------------------------------
318
DnDRpcInEnterCB(char const **result, // OUT
319
size_t *resultLen, // OUT
320
const char *name, // IN
321
const char *args, // IN
322
size_t argsSize, // Ignored
323
void *clientData) // IN
327
unsigned int index = 0;
332
Debug("Got DnDRpcInEnterCB\n");
333
mainWnd = GTK_WIDGET(clientData);
334
if (mainWnd == NULL) {
335
return RpcIn_SetRetVals(result, resultLen,
336
"bad clientData passed to callback", FALSE);
339
if (!DnD_BlockIsReady(&gBlockCtrl)) {
340
Debug("DnDRpcInEnterCB: cannot allow H->G DnD without vmblock.\n");
341
return RpcIn_SetRetVals(result, resultLen,
342
"blocking file system unavailable", FALSE);
345
numFormats = StrUtil_GetNextToken(&index, args, " ");
347
Debug("DnDRpcInEnterCB: Failed to parse numformats\n");
348
return RpcIn_SetRetVals(result, resultLen,
349
"must specify number of formats", FALSE);
352
/* Skip whitespace character. */
355
nFormats = atoi(numFormats);
358
for (i = 0; i < nFormats; i++) {
359
pFormat = StrUtil_GetNextToken(&index, args, ",");
362
Debug("DnDRpcInEnterCB: Failed to parse format list\n");
363
return RpcIn_SetRetVals(result, resultLen,
364
"Failed to read format list", FALSE);
367
* TODO: check that formats are ok for us to handle. For now, this is
368
* ok since there should only be a CF_HDROP. But, we really should figure
369
* out a much more cross-platform format scheme
375
if (!DnDHGFakeDrag(mainWnd)) {
376
Debug("DnDRpcInEnterCB: Failed to fake X events\n");
377
return RpcIn_SetRetVals(result, resultLen,
378
"failed to fake drag", FALSE);
381
RpcIn_SetRetVals(result, resultLen, "", TRUE);
382
RpcOut_sendOne(NULL, NULL, "dnd.feedback copy");
383
Debug("DnDRpcInEnterCB finished\n");
389
*-----------------------------------------------------------------------------
391
* DnDRpcInDataSetCB --
393
* For Host->Guest operations only.
394
* Host is sending data from a DnD operation.
397
* TRUE on success, FALSE otherwise.
402
*-----------------------------------------------------------------------------
406
DnDRpcInDataSetCB(char const **result, // OUT
407
size_t *resultLen, // OUT
408
const char *name, // IN
409
const char *args, // IN
410
size_t argsSize, // IN: Size of args
411
void *clientData) // Ignored
413
char blockDir[DND_MAX_PATH];
414
char *perDnDDir = NULL;
417
unsigned int index = 0;
422
Debug("DnDRpcInDataSetCB: enter\n");
424
if (!DnD_BlockIsReady(&gBlockCtrl)) {
425
Debug("DnDRpcInDataSetCB: blocking file system not available.\n");
426
return RpcIn_SetRetVals(result, resultLen,
427
"blocking file system not available", FALSE);
430
/* Parse the data type & value string. */
431
format = StrUtil_GetNextToken(&index, args, " ");
433
Debug("DnDRpcInDataSetCB: Failed to parse format\n");
434
return RpcIn_SetRetVals(result, resultLen, "need format", FALSE);
437
index++; /* Ignore leading space before data. */
438
dataSize = argsSize - index;
439
data = Util_SafeMalloc(dataSize);
440
memcpy(data, args + index, dataSize);
442
Debug("DnDRpcInDataSetCB: Received data from host: (%s) [%s] (%"FMTSZ"u)\n",
443
format, CPName_Print(data, dataSize), dataSize);
446
* Here we take the last component of the actual file root, which is
447
* a temporary directory for this DnD operation, and append it to the mount
448
* point for vmblock. This is where we want the target application to
449
* access the file since it will enable vmblock to block that application's
450
* progress if necessary.
452
perDnDDir = DnD_GetLastDirName(gFileRoot);
454
Debug("DnDRpcInDataSetCB: cannot obtain dirname of root.\n");
455
retStr = "error obtaining dirname of root";
459
if (strlen(gBlockCtrl.blockRoot) +
460
(sizeof DIRSEPS - 1) * 2 + strlen(perDnDDir) >= sizeof blockDir) {
461
Debug("DnDRpcInDataSetCB: blocking directory path too large.\n");
462
retStr = "blocking directory path too large";
466
Str_Sprintf(blockDir, sizeof blockDir,
467
"%s" DIRSEPS "%s" DIRSEPS, gBlockCtrl.blockRoot, perDnDDir);
469
/* Add the file root to the relative paths received from host */
470
if (!DnD_PrependFileRoot(blockDir, &data, &dataSize)) {
471
Debug("DnDRpcInDataSsetCB: error prepending guest file root\n");
472
retStr = "error prepending file root";
475
if (dataSize + 1 > sizeof gDnDData) {
476
Debug("DnDRpcInDataSetCB: data too large\n");
477
retStr = "data too large";
481
memcpy(gDnDData, data, dataSize + 1);
482
gDnDDataSize = dataSize;
483
Debug("DnDRpcInDataSetCB: prepended file root [%s] (%"FMTSZ"u)\n",
484
CPName_Print(gDnDData, gDnDDataSize), gDnDDataSize);
493
return RpcIn_SetRetVals(result, resultLen, retStr, ret);
498
*-----------------------------------------------------------------------------
502
* For Host->Guest operations only.
503
* Host user is dragging data over this guest's MKS window
506
* TRUE on success, FALSE otherwise.
509
* Send a gdk event that "moves" the mouse.
511
*-----------------------------------------------------------------------------
515
DnDRpcInMoveCB(char const **result, // OUT
516
size_t *resultLen, // OUT
517
const char *name, // IN
518
const char *args, // IN
519
size_t argsSize, // Ignored
520
void *clientData) // IN: pointer to mainWnd
525
unsigned int index = 0;
528
mainWnd = GTK_WIDGET(clientData);
529
if (mainWnd == NULL) {
530
return RpcIn_SetRetVals(result, resultLen,
531
"bad clientData passed to callback", FALSE);
534
sXCoord = StrUtil_GetNextToken(&index, args, " ");
535
sYCoord = StrUtil_GetNextToken(&index, args, " ");
537
if (!sXCoord || !sYCoord) {
538
Debug("DnDRpcInMove: Failed to parse coords\n");
541
return RpcIn_SetRetVals(result, resultLen,
542
"error reading mouse move data", FALSE);
545
xCoord = atoi(sXCoord);
546
yCoord = atoi(sYCoord);
551
/* Fake a mouse move */
552
if (!DnDHGFakeMove(mainWnd, xCoord, yCoord)) {
553
Debug("DnDRpcInMove: Failed to fake mouse movement\n");
554
return RpcIn_SetRetVals(result, resultLen,
555
"failed to move mouse", FALSE);
558
return RpcIn_SetRetVals(result, resultLen, "", TRUE);
563
*-----------------------------------------------------------------------------
565
* DnDRpcInDataFinishCB --
567
* For Host->Guest operations only.
568
* Host has finished transferring DnD data to the guest. We do any post
569
* H->G operation cleanup here, like removing the block on the staging
570
* directory, picking a new file root, and informing the host of the new
574
* TRUE on success, FALSE otherwise
579
*-----------------------------------------------------------------------------
583
DnDRpcInDataFinishCB(char const **result, // OUT
584
size_t *resultLen, // OUT
585
const char *name, // IN
586
const char *args, // IN
587
size_t argsSize, // Ignored
588
void *clientData) // Ignored
590
unsigned int index = 0;
593
Debug("DnDRpcInDataFinishCB: enter\n");
595
state = StrUtil_GetNextToken(&index, args, " ");
597
Debug("DnDRpcInDataFinishCB: could not get dnd finish state.\n");
598
return RpcIn_SetRetVals(result, resultLen,
599
"could not get dnd finish state", FALSE);
603
* If the guest doesn't support vmblock, we'll have bailed out of
604
* DndRpcInDropCB before setting gHGDataPending. Thus, it doesn't make sense
605
* to pop a warning here, but let's keep the message around just in case
606
* there can be a failure worth hearing about.
608
if (!gHGDataPending) {
609
Debug("DnDRpcInDataFinishCB: expected gHGDataPending to be set.\n");
612
gHGDataPending = FALSE;
615
* The host will send us "success" or "error", depending on whether the
616
* transfer finished successfully. In either case we remove the pending
617
* block, but in the "error" case we also need to delete all the files so
618
* the destination application doesn't access the partially copied files and
619
* mistake them for a successful drop.
621
if (strcmp(state, "success") != 0) {
622
/* On any non-success input, delete the files. */
623
DnD_DeleteStagingFiles(gFileRoot, FALSE);
628
if (DnD_BlockIsReady(&gBlockCtrl) &&
629
!gBlockCtrl.RemoveBlock(gBlockCtrl.fd, gFileRoot)) {
630
Warning("DnDRpcInDataFinishCB: could not remove block on %s\n",
634
/* Pick a new file root and send that to the host for the next DnD. */
635
if (!DnDSendVmxNewFileRoot("dnd.setGuestFileRoot")) {
636
Debug("DnDRpcInDataFinishCB: Failed to send dnd.setGuestFileRoot "
637
"message to host\n");
638
return RpcIn_SetRetVals(result, resultLen, "could not send guest root", FALSE);
641
return RpcIn_SetRetVals(result, resultLen, "", TRUE);
646
*-----------------------------------------------------------------------------
650
* For Host->Guest operations only.
651
* Host user has dropped data over this guest's MKS window. We add
652
* a block on the staging directory then send a fake mouse release to
653
* invoke the drop completion (from Gtk's point of view).
656
* TRUE on success, FALSE otherwise.
661
*-----------------------------------------------------------------------------
665
DnDRpcInDropCB(char const **result, // OUT
666
size_t *resultLen, // OUT
667
const char *name, // IN
668
const char *args, // IN
669
size_t argsSize, // Ignored
670
void *clientData) // IN: Gtk window widget
675
unsigned int index = 0;
678
Debug("DnDRpcInDropCB: enter\n");
680
gDoneDragging = TRUE;
682
mainWnd = GTK_WIDGET(clientData);
683
if (mainWnd == NULL) {
684
return RpcIn_SetRetVals(result, resultLen,
685
"bad clientData passed to callback", FALSE);
688
sXCoord = StrUtil_GetNextToken(&index, args, " ");
689
sYCoord = StrUtil_GetNextToken(&index, args, " ");
691
if (!sXCoord || !sYCoord) {
692
Debug("DnDRpcInDropCB: Failed to parse coords\n");
695
return RpcIn_SetRetVals(result, resultLen,
696
"must specify drop coordinates", FALSE);
699
xCoord = atoi(sXCoord);
700
yCoord = atoi(sYCoord);
705
Debug("DnDRpcInDropCB: Received drop notification at (%d,%d)\n",
709
* Add a block on the guest file root, warp the pointer, then fake the mouse
710
* release. Make sure we'll succeed before modifying any mouse state in the
713
if (!DnD_BlockIsReady(&gBlockCtrl)) {
715
* We shouldn't get here since DnDRpcInEnterCB() checks this, but we'll
716
* check rather than ASSERT just in case.
718
return RpcIn_SetRetVals(result, resultLen,
719
"blocking file system unavailable", FALSE);
722
if (!gBlockCtrl.AddBlock(gBlockCtrl.fd, gFileRoot)) {
723
return RpcIn_SetRetVals(result, resultLen, "could not add block", FALSE);
726
/* Update state before causing faking any mouse or keyboard changes. */
727
gHGDataPending = TRUE;
730
if (!DnDHGFakeDrop(mainWnd, xCoord, yCoord)) {
731
Debug("DnDRpcInDropCB: failed to fake drop\n");
732
return RpcIn_SetRetVals(result, resultLen, "failed to fake drop", FALSE);
735
return RpcIn_SetRetVals(result, resultLen, "", TRUE);
740
* Guest->Host RPC callback implementations
744
*----------------------------------------------------------------------------
746
* DnDRpcInMouseUngrabCB --
748
* For Guest->Host operations only.
750
* Called when a mouse ungrab is attempted with the mouse button down. When
751
* the MKS sees mouse movements outside of the clip (the viewable portion of
752
* the guest's display) while a mouse button is down, this function is
753
* called so we can inform the MKS whether to allow the ungrab (and start
754
* a DnD if one is pending).
757
* TRUE on success, FALSE on failure.
760
* The GDK window is moved and resized, and the mouse is moved over it.
762
*----------------------------------------------------------------------------
766
DnDRpcInMouseUngrabCB(char const **result, // OUT
767
size_t *resultLen, // OUT
768
const char *name, // IN
769
const char *args, // IN
770
size_t argsSize, // Ignored
771
void *clientData) // IN
773
unsigned int index = 0;
778
Debug("Got DnDRpcInMouseUngrabCB\n");
779
mainWnd = GTK_WIDGET(clientData);
780
if (mainWnd == NULL) {
781
Warning("DnDRpcInMouseUngrabCB: invalid clientData\n");
782
return RpcIn_SetRetVals(result, resultLen,
783
"bad clientData passed to callback", FALSE);
787
* If there is already a DnD or copy/paste in progress (including the file
788
* transfer), don't allow another.
790
if (gHGDataPending || gGHState.dragInProgress || CopyPaste_InProgress()) {
791
RpcOut_sendOne(NULL, NULL, "dnd.notpending");
792
return RpcIn_SetRetVals(result, resultLen,
793
"dnd already in progress", FALSE);
796
if (!StrUtil_GetNextIntToken(&xPos, &index, args, " ")) {
797
Warning("DnDRpcInMouseUngrabCB: could not parse x coordinate\n");
798
RpcOut_sendOne(NULL, NULL, "dnd.notpending");
799
return RpcIn_SetRetVals(result, resultLen,
800
"Failed to parse x coordinate", FALSE);
803
if (!StrUtil_GetNextIntToken(&yPos, &index, args, " ")) {
804
Warning("DnDRpcInMouseUngrabCB: could not parse y coordinate\n");
805
RpcOut_sendOne(NULL, NULL, "dnd.notpending");
806
return RpcIn_SetRetVals(result, resultLen,
807
"Failed to parse y coordinate", FALSE);
810
Debug("DnDRpcInMouseUngrabCB: Received (%d,%d)\n", xPos, yPos);
813
* If there is no DnD pending, inform the host so the MKS can start sending
814
* mouse packets again.
816
if (!DnDGHDragPending(mainWnd)) {
817
RpcOut_sendOne(NULL, NULL, "dnd.notpending");
818
return RpcIn_SetRetVals(result, resultLen, "DnD not pending", TRUE);
821
/* The host only gives us coordinates within our screen */
826
* Fake mouse movements over the window to try and generate a "drag_motion"
827
* signal from GTK. If a drag is pending, that signal will be sent to our
828
* widget and DnDGtkDragMotionCB will be invoked to start the DnD
831
if (!DnDGHFakeDrag(mainWnd)) {
832
Warning("DnDRpcInMouseUngrabCB: could not fake X events\n");
833
RpcOut_sendOne(NULL, NULL, "dnd.notpending");
834
return RpcIn_SetRetVals(result, resultLen,
835
"error faking X events", FALSE);
839
* Add event to fire and hide our widget if a DnD is not pending. Note that
840
* this is here in case our drag pending heuristic for Xdnd and Motif does
841
* not encompass all cases, or if the X events we generate don't cause the
842
* "drag_motion" for some other reason.
844
gGHState.event = EventManager_Add(gEventQueue, RPCIN_POLL_TIME * 100,
845
DnDGHXEventTimeout, mainWnd);
846
if (!gGHState.event) {
847
Warning("DnDRpcInMouseUngrabCB: could not create event\n");
848
RpcOut_sendOne(NULL, NULL, "dnd.notpending");
849
return RpcIn_SetRetVals(result, resultLen,
850
"could not create timeout event", FALSE);
853
gGHState.dragInProgress = FALSE;
854
gGHState.ungrabReceived = TRUE;
856
Debug("DnDRpcInMouseUngrabCB finished\n");
857
return RpcIn_SetRetVals(result, resultLen, "", TRUE);
862
*----------------------------------------------------------------------------
864
* DnDRpcInGetNextFileCB --
866
* For Guest->Host operations only.
868
* Invoked when the host is compiling its list of files to copy from the
869
* guest. Here we provide the path of the next file in our Guest->Host file
870
* list in guest path format (for display purposes) and CPName format (for
871
* file copy operation).
874
* TRUE on success, FALSE on failure.
877
* Iterator pointer within file list of GH state is iterated to next list
878
* entry (through call to DnDGHFileListGetNext()).
880
*----------------------------------------------------------------------------
884
DnDRpcInGetNextFileCB(char const **result, // OUT
885
size_t *resultLen, // OUT
886
const char *name, // IN
887
const char *args, // IN
888
size_t argsSize, // Ignored
889
void *clientData) // IN
891
static char resultBuffer[DND_MAX_PATH];
898
mainWnd = GTK_WIDGET(clientData);
899
if (mainWnd == NULL) {
901
return RpcIn_SetRetVals(result, resultLen,
902
"bad clientData passed to callback", FALSE);
906
* Retrieve a pointer to the next filename and its size from the list stored
907
* in the G->H DnD state. Note that fileName should not be free(3)d here
908
* since an additional copy is not allocated.
910
res = DnDGHFileListGetNext(&fileName, &fileNameSize);
913
Warning("DnDRpcInGetNextFileCB: error retrieving file name\n");
914
DnDGHCancel(mainWnd);
915
return RpcIn_SetRetVals(result, resultLen, "error getting file", FALSE);
919
/* There are no more files to send */
920
Debug("DnDRpcInGetNextFileCB: reached end of Guest->Host file list\n");
921
return RpcIn_SetRetVals(result, resultLen, "|end|", TRUE);
924
if (fileNameSize + 1 + fileNameSize > sizeof resultBuffer) {
925
Warning("DnDRpcInGetNextFileCB: filename too large (%"FMTSZ"u)\n", fileNameSize);
926
DnDGHCancel(mainWnd);
927
return RpcIn_SetRetVals(result, resultLen, "filename too large", FALSE);
931
* Construct a reply message of the form:
932
* <file name in guest format><NUL><filename in CPName format>
934
memcpy(resultBuffer, fileName, fileNameSize);
935
resultBuffer[fileNameSize] = '\0';
937
cpNameSize = CPNameUtil_ConvertToRoot(fileName,
938
sizeof resultBuffer - (fileNameSize + 1),
939
resultBuffer + fileNameSize + 1);
940
if (cpNameSize < 0) {
941
Warning("DnDRpcInGetNextFileCB: could not convert to CPName\n");
942
DnDGHCancel(mainWnd);
943
return RpcIn_SetRetVals(result, resultLen,
944
"error on CPName conversion", FALSE);
947
/* Set manually because RpcIn_SetRetVals() assumes no NUL characters */
948
*result = resultBuffer;
949
*resultLen = fileNameSize + 1 + cpNameSize;
951
Debug("DnDRpcInGetNextFileCB: [%s] (%"FMTSZ"u)\n",
952
CPName_Print(*result, *resultLen), *resultLen);
959
*----------------------------------------------------------------------------
961
* DnDRpcInFinishCB --
963
* For Guest->Host operations only.
965
* Invoked when host side of DnD operation has finished.
968
* TRUE on success, FALSE on failure.
973
*----------------------------------------------------------------------------
977
DnDRpcInFinishCB(char const **result, // OUT
978
size_t *resultLen, // OUT
979
const char *name, // IN
980
const char *args, // IN
981
size_t argsSize, // Ignored
982
void *clientData) // IN
986
unsigned int index = 0;
990
mainWnd = GTK_WIDGET(clientData);
991
if (mainWnd == NULL) {
992
retStr = "bad clientData passed to callback";
996
effect = StrUtil_GetNextToken(&index, args, " ");
998
Warning("DnDRpcInFinishCB: no drop effect provided\n");
999
retStr = "drop effect not provided";
1003
if (strcmp(effect, "cancel") == 0) {
1004
DnDSendEscapeKey(mainWnd);
1005
DnDGHCancel(mainWnd);
1008
* The drop happened on the host. Fake X events such that our window is
1009
* placed at the mouse's coordinates and raised, then fake a button
1010
* release on the window. This causes us to get a "drag_drop" signal
1011
* from GTK on our widget.
1013
if (!DnDGHFakeDrop(mainWnd)) {
1014
Warning("DnDRpcInFinishCB: could not fake X events\n");
1015
retStr = "error faking X events";
1019
gGHState.event = EventManager_Add(gEventQueue, RPCIN_POLL_TIME * 10,
1020
DnDGHXEventTimeout, mainWnd);
1021
if (!gGHState.event) {
1022
Warning("DnDRpcInFinishCB: could not create event\n");
1023
retStr = "could not create timeout event";
1033
DnDGHCancel(mainWnd);
1037
gGHState.dragInProgress = FALSE;
1038
return RpcIn_SetRetVals(result, resultLen, retStr, ret);
1043
* Host->Guest (drop source) Gtk callback implementations
1047
*-----------------------------------------------------------------------------
1051
* "drag_begin" signal handler for GTK. This signal will be received
1052
* after the fake mouse press sent in DnDRpcInEnterCB() is performed.
1053
* Here we simply initialize our state variables.
1061
*-----------------------------------------------------------------------------
1065
DnDGtkBeginCB(GtkWidget *widget, // IN: the widget under the drag
1066
GdkDragContext *dc, // IN: the drag context maintained by gdk
1067
gpointer data) // IN: unused
1069
GtkWidget *mainWnd = GTK_WIDGET(data);
1071
Debug("DnDGtkBeginCB: entry\n");
1073
if ((widget == NULL) || (mainWnd == NULL) || (dc == NULL)) {
1077
gHGDnDInProgress = TRUE;
1078
gDoneDragging = FALSE;
1079
gHGDataPending = FALSE;
1084
*-----------------------------------------------------------------------------
1088
* "drag_end" signal handler for GTK. This is called when a drag and drop has
1089
* completed. So this function is the last one to be called in any given DnD
1098
*-----------------------------------------------------------------------------
1102
DnDGtkEndCB(GtkWidget *widget, // IN: the widget under the drag
1103
GdkDragContext *dc, // IN: the drag context maintained by gdk
1104
gpointer data) // IN: unused
1107
GtkWidget *mainWnd = GTK_WIDGET(data);
1109
Debug("DnDGtkEndCB: enter\n");
1111
if (mainWnd == NULL || dc == NULL) {
1116
* Do not set gHGDataPending to FALSE since DnD operation completes before
1117
* the data transfer.
1119
gDoneDragging = FALSE;
1120
gHGDnDInProgress = FALSE;
1122
RpcOut_sendOne(NULL, NULL, "dnd.finish %d", DROPEFFECT_COPY);
1127
*-----------------------------------------------------------------------------
1129
* DnDGtkDataRequestCB --
1130
* DnD "drag_data_get" handler, for handling requests for DnD data on the
1131
* specified widget. This function is called when there is need for DnD data
1132
* on thesource, so this function is responsible for setting up the dynamic
1133
* data exchange buffer and sending it out.
1139
* Data is avaiable to drop target.
1141
*-----------------------------------------------------------------------------
1145
DnDGtkDataRequestCB(GtkWidget *widget, // IN
1146
GdkDragContext *dc, // IN
1147
GtkSelectionData *selection_data, // IN/OUT: buffer for the data
1148
guint info, // IN: the requested fo the data
1149
guint time, // IN: unused
1150
gpointer data) // IN: unused
1164
Debug("DnDGtkDataRequestCB: enter\n");
1166
if ((widget == NULL) || (dc == NULL) || (selection_data == NULL)) {
1167
Debug("DnDGtkDataRequestCB: Error, widget or dc or selection_data is invalid\n");
1171
/* Do nothing if we have not finished dragging yet */
1172
if (!gDoneDragging) {
1173
Debug("DnDGtkDataRequestCB: not done dragging yet\n");
1177
/* Setup the format string components */
1179
case DRAG_TARGET_INFO_URI_LIST: /* text/uri-list */
1180
pre = DND_URI_LIST_PRE;
1181
preLen = sizeof DND_URI_LIST_PRE - 1;
1182
post = DND_URI_LIST_POST;
1183
postLen = sizeof DND_URI_LIST_POST - 1;
1184
insertSpace = FALSE;
1186
case DRAG_TARGET_INFO_TEXT_PLAIN: /* text/plain */
1187
pre = DND_TEXT_PLAIN_PRE;
1188
preLen = sizeof DND_TEXT_PLAIN_PRE - 1;
1189
post = DND_TEXT_PLAIN_POST;
1190
postLen = sizeof DND_TEXT_PLAIN_POST - 1;
1193
case DRAG_TARGET_INFO_STRING: /* STRING */
1194
pre = DND_STRING_PRE;
1195
preLen = sizeof DND_STRING_PRE - 1;
1196
post = DND_STRING_POST;
1197
postLen = sizeof DND_STRING_POST - 1;
1201
Log("DnDGtkDataRequestCB: invalid drag target info\n");
1207
* Set begin to first non-NUL character and end to last NUL character to
1208
* prevent errors in calling CPName_GetComponent().
1210
for(begin = gDnDData; *begin == '\0'; begin++)
1212
end = CPNameUtil_Strrchr(gDnDData, gDnDDataSize + 1, '\0');
1215
/* Build up selection data */
1216
while ((len = CPName_GetComponent(begin, end, &next)) != 0) {
1217
const size_t origTextLen = textLen;
1218
Bool freeBegin = FALSE;
1221
Log("DnDGtkDataRequestCB: error getting next component\n");
1229
* A URI list will expect the provided path to be escaped. If we cannot
1230
* escape the path for some reason we just use the unescaped version and
1231
* hope that it works.
1233
if (info == DRAG_TARGET_INFO_URI_LIST) {
1235
char *escapedComponent;
1237
int bytesToEsc[256] = { 0, };
1239
/* We escape the following characters based on RFC 1630. */
1240
bytesToEsc['#'] = 1;
1241
bytesToEsc['?'] = 1;
1242
bytesToEsc['*'] = 1;
1243
bytesToEsc['!'] = 1;
1244
bytesToEsc['%'] = 1; /* Escape character */
1246
/* Escape non-ASCII characters so we can pass UTF-8 filenames */
1247
for (escIndex = 0x80; escIndex < 0x100; escIndex++) {
1248
bytesToEsc[escIndex] = 1;
1251
escapedComponent = Escape_Do('%', bytesToEsc, begin, len, &newLen);
1252
if (escapedComponent) {
1253
begin = escapedComponent;
1260
* Append component. NUL terminator was accounted for by initializing
1261
* textLen to one above.
1263
textLen += preLen + len + postLen + (insertSpace ? 1 : 0);
1264
text = Util_SafeRealloc(text, textLen);
1265
Str_Snprintf(text + origTextLen - 1,
1266
textLen - origTextLen + 1,
1267
"%s%s%s", pre, begin, post);
1269
if (insertSpace && next != end) {
1270
ASSERT(textLen - 2 >= 0);
1271
text[textLen - 2] = ' ';
1272
text[textLen - 1] = '\0';
1276
free((void *)begin);
1279
/* Iterate to next component */
1284
* Send out the data using the selection system. When sending a string, GTK will
1285
* ensure that a null terminating byte is added to the end so we do not need to
1286
* add it. GTK also copies the data so the original will never be modified.
1288
Debug("DnDGtkDataRequestCB: calling gtk_selection_data_set with [%s]\n", text);
1289
gtk_selection_data_set(selection_data, selection_data->target,
1290
8, /* 8 bits per character. */
1297
* Guest->Host (drop target) Gtk callback implementations
1301
*----------------------------------------------------------------------------
1303
* DnDGtkDragMotionCB --
1305
* "drag_motion" signal handler for GTK. This is invoked each time the
1306
* mouse moves over the drag target (destination) window when a DnD is
1310
* TRUE on success, FALSE on failure.
1313
* RPC messages are sent to the host to proxy the DnD over.
1315
*----------------------------------------------------------------------------
1319
DnDGtkDragMotionCB(GtkWidget *widget, // IN: target widget
1320
GdkDragContext *dc, // IN: the GDK drag context
1321
gint x, // IN: x position of mouse
1322
gint y, // IN: y position of mouse
1323
guint time, // IN: time of event
1324
gpointer data) // IN: our private data
1326
GdkAtom commonTarget = 0;
1331
ASSERT(widget == data);
1334
Debug("DnDGtkDragMotionCB: entry (x=%d, y=%d, time=%d)\n", x, y, time);
1337
* We'll get a number of these and should only carry on these operations on
1340
* XXX Unity mode needs to know if there is a g->h->g dnd operation by
1341
* detecting if the mouse has left the detection window. This code is
1342
* currently not in the guest and should be ported from the host.
1344
if (gGHState.dragInProgress && !gUnity) {
1345
Debug("DnDGtkDragMotionCB: drag already in progress\n");
1350
* Sometimes (rarely) real user mouse movements will trigger "drag_motion"
1351
* signals after we have already handled them. Prevent resetting the data
1352
* and trying to start a new DnD operation.
1354
if (!gGHState.ungrabReceived && !gUnity) {
1355
Debug("DnDGtkDragMotionCB: extra drag motion without ungrab\n");
1359
gGHState.ungrabReceived = FALSE;
1361
/* Remove event that hides our widget out of band from the DnD protocol. */
1362
if (gGHState.event) {
1363
Debug("DnDGtkDragMotionCB: removed pending event\n");
1364
EventManager_Remove(gGHState.event);
1365
gGHState.event = NULL;
1369
* Note that gdk_drag_status() is called for us by GTK since we passed in
1370
* GTK_DEST_DEFAULT_MOTION to gtk_drag_dest_set(). We'd handle it
1371
* ourselves, but GTK 1.2.10 has a "bug" that requires us to provide this
1372
* flag to get drag_leave and drag_drop signals.
1376
* We need to try and find a common target format with the list of formats
1377
* offered by the drag source. This list is stored in the drag context's
1378
* targets field, and each list member's data variable is a GdkAtom. We
1379
* translated our supported targets into GdkAtoms in gTargetEntryAtom at
1380
* initialization. Note that the GdkAtom value is an index into a table of
1381
* strings maintained by the X server, so if they are equivalent then
1382
* a common mime type is found.
1384
for (i = 0; i < ARRAYSIZE(gTargetEntryAtom) && !found; i++) {
1385
GList *currContextTarget = dc->targets;
1387
while (currContextTarget) {
1388
if (gTargetEntryAtom[i] == (GdkAtom)currContextTarget->data) {
1389
commonTarget = gTargetEntryAtom[i];
1393
currContextTarget = currContextTarget->next;
1398
Warning("DnDGtkDragMotionCB: could not find a common target format\n");
1399
DnDGHCancel(widget);
1404
* Request the data. A "drag_data_received" signal will be sent to widget
1405
* (that's us) upon completion.
1407
gtk_drag_get_data(widget, dc, commonTarget, time);
1410
gGHState.dragInProgress = TRUE;
1416
*----------------------------------------------------------------------------
1418
* DnDGtkDragDataReceivedCB --
1420
* "drag_data_received" signal handler for GTK. Invoked when the data
1421
* requested by a gtk_drag_get_data() call is ready.
1423
* This function actually begins the drag operation with the host by first
1424
* setting the data ("dnd.data.set" RPC command) and then starting the DnD
1425
* ("dnd.enter" RPC command).
1433
*----------------------------------------------------------------------------
1437
DnDGtkDragDataReceivedCB(GtkWidget *widget, // IN
1438
GdkDragContext *dc, // IN
1441
GtkSelectionData *dragData, // IN
1444
gpointer data) // IN
1446
const char rpcHeader[] = "dnd.data.set CF_HDROP ";
1447
const size_t rpcHeaderSize = sizeof rpcHeader - 1;
1448
char *rpcBody = NULL;
1449
size_t rpcBodySize = 0;
1454
Debug("DnDGtkDragDataReceivedCB: entry\n");
1456
if (dragData->length < 0) {
1457
Warning("DnDGtkDragDataReceivedCB: received length < 0 error\n");
1461
gGHState.dragContext = dc;
1462
gGHState.time = time;
1465
* Construct the body of the RPC message and our Guest->Host file list.
1467
if (dragData->target == gTargetEntryAtom[DRAG_TARGET_INFO_URI_LIST]) {
1471
char *ghFileList = NULL;
1472
size_t ghFileListSize = 0;
1474
Debug("DnDGtkDragDataReceivedCB: uri-list [%s]\n", dragData->data);
1477
* Get the the full filenames and last components from the URI list. The
1478
* body of the RPC message will be these last components delimited with
1479
* NUL characters; the Guest->Host file list will be the full paths
1480
* delimited by NUL characters.
1482
while ((currName = DnD_UriListGetNextFile(dragData->data,
1485
size_t lastComponentSize;
1486
char *lastComponentStart;
1488
/* Append current filename to Guest->Host list */
1489
ghFileList = Util_SafeRealloc(ghFileList,
1490
ghFileListSize + currSize + 1);
1491
memcpy(ghFileList + ghFileListSize, currName, currSize);
1492
ghFileListSize += currSize;
1493
ghFileList[ghFileListSize] = '\0';
1496
/* Append last component to RPC body */
1497
lastComponentStart = CPNameUtil_Strrchr(currName, currSize, DIRSEPC);
1498
if (!lastComponentStart) {
1500
* This shouldn't happen since filenames are absolute, but handle
1501
* it as if the file name is the last component
1503
lastComponentStart = currName;
1505
/* Skip the last directory separator */
1506
lastComponentStart++;
1509
lastComponentSize = currName + currSize - lastComponentStart;
1510
rpcBody = Util_SafeRealloc(rpcBody, rpcBodySize + lastComponentSize + 1);
1511
memcpy(rpcBody + rpcBodySize, lastComponentStart, lastComponentSize);
1512
rpcBodySize += lastComponentSize;
1513
rpcBody[rpcBodySize] = '\0';
1519
if (!ghFileList || !rpcBody) {
1520
Warning("DnDGtkDragDataReceivedCB: no filenames retrieved "
1527
/* Set the list of full paths for use in the "dnd.data.get.file" callback */
1528
DnDGHFileListSet(ghFileList, ghFileListSize);
1530
/* rpcBody (and its size) will always contain a trailing NUL character */
1533
Warning("DnDGtkDragDataReceivedCB: unknown target format used [%s]\n",
1539
* Set the drag data on the host, followed by sending the drag enter
1541
rpcSize = rpcHeaderSize + rpcBodySize;
1542
rpc = Util_SafeMalloc(rpcSize);
1543
memcpy(rpc, rpcHeader, rpcHeaderSize);
1544
memcpy(rpc + rpcHeaderSize, rpcBody, rpcBodySize);
1547
Debug("DnDGtkDragMotionCB: Sending: [%s] (%"FMTSZ"u)\n",
1548
CPName_Print(rpc, rpcSize), rpcSize);
1549
if (!RpcOut_SendOneRaw(rpc, rpcSize, NULL, NULL)) {
1550
Warning("DnDGtkDragMotionCB: failed to send dnd.data.set message\n");
1557
if (!RpcOut_sendOne(NULL, NULL, "dnd.enter 1 CF_HDROP")) {
1558
Warning("DnDGtkDragMotionCB: failed to send dnd.enter message\n");
1565
RpcOut_sendOne(NULL, NULL, "dnd.notpending");
1566
DnDGHCancel(widget);
1571
*----------------------------------------------------------------------------
1573
* DnDGtkDragDropCB --
1575
* "drag_drop" signal handler for GTK. This is invoked when a mouse button
1576
* release occurs on our widget. We generate that mouse button release in
1577
* DnDRpcInFinishCB() when the host indicates that the drop has occurred and
1578
* the files have been successfully transferred to the guest.
1581
* TRUE indicates to GTK that it need not run other handlers, FALSE
1587
*----------------------------------------------------------------------------
1591
DnDGtkDragDropCB(GtkWidget *widget, // IN: widget event occurred on
1592
GdkDragContext *dc, // IN: Destination drag context
1593
gint x, // IN: x coordinate of drop
1594
gint y, // IN: y coordinate of drop
1595
guint time, // IN: time of event
1596
gpointer data) // IN: our private data
1599
ASSERT(widget == data);
1601
Debug("DnDGtkDragDropCB: entry (%d, %d)\n", x, y);
1603
/* Remove timeout callback that was set in case we didn't get here */
1604
if (gGHState.event) {
1605
Debug("DnDGtkDragDropCB: removed pending event\n");
1606
EventManager_Remove(gGHState.event);
1607
gGHState.event = NULL;
1610
/* Hide our window so we don't receive stray signals */
1612
gtk_widget_hide(widget);
1615
gtk_drag_finish(dc, TRUE, FALSE, time);
1617
/* Reset all Guest->Host state */
1618
DnDGHStateInit(widget);
1629
*----------------------------------------------------------------------------
1631
* DnD_GetNewFileRoot --
1633
* Convenience function that gets a new file root for use on a single DnD
1634
* operation and sets the global file root variable accordingly.
1637
* Size of root string (not including NUL terminator).
1642
*----------------------------------------------------------------------------
1646
DnD_GetNewFileRoot(char *fileRoot, // IN/OUT
1647
int bufSize) // IN: sizeof fileRoot
1649
char *newDir = NULL;
1650
size_t fileRootSize;
1652
newDir = DnD_CreateStagingDirectory();
1653
if (newDir == NULL) {
1655
* Fallback on base of file root if we couldn't create a staging
1656
* directory for this DnD operation. This is what Windows DnD does.
1658
Str_Strcpy(fileRoot, DnD_GetFileRoot(), bufSize);
1659
return strlen(fileRoot);
1661
fileRootSize = strlen(newDir);
1662
ASSERT(fileRootSize < bufSize);
1663
memcpy(fileRoot, newDir, fileRootSize);
1664
fileRoot[fileRootSize] = '\0';
1666
return fileRootSize;
1672
*----------------------------------------------------------------------------
1674
* DnDSendVmxNewFileRoot --
1676
* Sends the VMX a new file root with the provided RPC command.
1679
* TRUE on success, FALSE on failure
1682
* gFileRoot is repopulated.
1684
*----------------------------------------------------------------------------
1688
DnDSendVmxNewFileRoot(char *rpcCmd) // IN: RPC command
1690
int32 rpcCommandSize;
1692
int32 rpcMessageSize;
1696
/* Repopulate gFileRoot */
1697
gFileRootSize = DnD_GetNewFileRoot(gFileRoot, sizeof gFileRoot);
1700
* Here we must convert the file root before sending it across the
1701
* backdoor. We can only communicate with new VMXs (v2 DnD), so we only
1702
* need to handle that case here.
1704
* <rpcCmd> <file root in local format><NUL><file root in CPName>
1706
rpcCommandSize = strlen(rpcCmd);
1709
* ConvertToRoot below will append the root share name, so we need to
1710
* make room for it in our buffer.
1712
rpcMessageSize = rpcCommandSize + 1 +
1714
HGFS_STR_LEN(HGFS_SERVER_POLICY_ROOT_SHARE_NAME) + 1 +
1717
rpcMessage = Util_SafeCalloc(1, rpcMessageSize);
1718
memcpy(rpcMessage, rpcCmd, rpcCommandSize);
1719
rpcMessage[rpcCommandSize] = ' ';
1720
cur = rpcMessage + rpcCommandSize + 1;
1722
memcpy(cur, gFileRoot, gFileRootSize);
1723
cur += gFileRootSize;
1727
Debug("DnDSendVmxNewFileRoot: calling CPNameUtil_ConvertToRoot(%s, %"FMTSZ"u, %p)\n",
1728
gFileRoot, rpcMessageSize - (cur - rpcMessage), cur);
1729
cpNameSize = CPNameUtil_ConvertToRoot(gFileRoot,
1730
rpcMessageSize - (cur - rpcMessage),
1732
if (cpNameSize < 0) {
1733
Debug("DnDSendVmxNewFileRoot: Could not convert file root to CPName\n");
1738
/* Readjust message size for actual length */
1739
rpcMessageSize = rpcCommandSize + 1 +
1743
Debug("DnDSendVmxNewFileRoot: sending root [%s] (%d)\n",
1744
CPName_Print(rpcMessage, rpcMessageSize), rpcMessageSize);
1747
* We must use RpcOut_SendOneRaw() here since RpcOut_sendOne() assumes a
1748
* string and we are using CPName format.
1750
if (!RpcOut_SendOneRaw(rpcMessage, rpcMessageSize, NULL, NULL)) {
1751
Debug("DnDSendVmxNewFileRoot: Failed to send %s message to host\n", rpcCmd);
1762
*----------------------------------------------------------------------------
1766
* Fake X mouse events and window movement for the provided Gtk widget.
1768
* This function will optionally show the widget, move the provided widget
1769
* to either the provided location or the current mouse position if no
1770
* coordinates are provided, and cause a button press or release event.
1774
* TRUE on success, FALSE on failure.
1777
* Other X events should be generated from those faked here.
1779
*----------------------------------------------------------------------------
1783
DnDFakeXEvents(GtkWidget *widget, // IN: the Gtk widget
1784
Bool showWidget, // IN: whether to show Gtk widget
1785
Bool buttonEvent, // IN: whether to send a button event
1786
Bool buttonPress, // IN: whether to press or release mouse
1787
Bool moveWindow, // IN: whether to move our window too
1788
Bool coordsProvided,// IN: whether coordinates provided
1789
int xCoord, // IN: x coordinate
1790
int yCoord) // IN: y coordinate
1794
Display *dndXDisplay;
1799
dndXDisplay = GDK_WINDOW_XDISPLAY(widget->window);
1800
dndXWindow = GDK_WINDOW_XWINDOW(widget->window);
1803
* Turn on X synchronization in order to ensure that our X events occur in
1804
* the order called. In particular, we want the window movement to occur
1805
* before the mouse movement so that the events we are coercing do in fact
1808
XSynchronize(dndXDisplay, True);
1811
Debug("DnDFakeXEvents: showing Gtk widget\n");
1812
gtk_widget_show(widget);
1813
gdk_window_show(widget->window);
1816
/* Get the current location of the mouse if coordinates weren't provided. */
1817
if (!coordsProvided) {
1824
unsigned int maskReturn;
1826
rootWnd = RootWindow(dndXDisplay, DefaultScreen(dndXDisplay));
1827
ret = XQueryPointer(dndXDisplay, rootWnd, &rootReturn, &childReturn,
1828
&rootXReturn, &rootYReturn, &winXReturn, &winYReturn,
1831
Warning("DnDFakeXEvents: XQueryPointer() returned False.\n");
1832
XSynchronize(dndXDisplay, False);
1836
Debug("DnDFakeXEvents: mouse is at (%d, %d)\n", rootXReturn, rootYReturn);
1838
xCoord = rootXReturn;
1839
yCoord = rootYReturn;
1844
* Make sure the window is at this point and at the top (raised). The
1845
* window is resized to be a bit larger than we would like to increase
1846
* the likelihood that mouse events are attributed to our window -- this
1847
* is okay since the window is invisible and hidden on cancels and DnD
1850
XMoveResizeWindow(dndXDisplay, dndXWindow, xCoord, yCoord, 25, 25);
1851
XRaiseWindow(dndXDisplay, dndXWindow);
1855
* Generate mouse movements over the window. The second one makes ungrabs
1856
* happen more reliably on KDE, but isn't necessary on GNOME.
1858
XTestFakeMotionEvent(dndXDisplay, -1, xCoord, yCoord, CurrentTime);
1859
XTestFakeMotionEvent(dndXDisplay, -1, xCoord + 1, yCoord + 1, CurrentTime);
1862
Debug("DnDFakeXEvents: faking left mouse button %s\n",
1863
buttonPress ? "press" : "release");
1864
XTestFakeButtonEvent(dndXDisplay, 1, buttonPress, CurrentTime);
1867
XSynchronize(dndXDisplay, False);
1874
*----------------------------------------------------------------------------
1876
* DnDSendEscapeKey --
1878
* Sends the escape key, canceling any pending drag and drop on the guest.
1886
*----------------------------------------------------------------------------
1890
DnDSendEscapeKey(GtkWidget *mainWnd) // IN
1892
Display *dndXDisplay;
1895
Debug("DnDRpcInFinishCB: faking ESC key press/release\n");
1897
dndXDisplay = GDK_WINDOW_XDISPLAY(mainWnd->window);
1898
escKeycode = XKeysymToKeycode(dndXDisplay, XK_Escape);
1900
XTestFakeKeyEvent(dndXDisplay, escKeycode, TRUE, CurrentTime);
1901
XTestFakeKeyEvent(dndXDisplay, escKeycode, FALSE, CurrentTime);
1906
*----------------------------------------------------------------------------
1908
* DnDGHDragPending --
1910
* Determine whether a drag is currently pending within the guest by
1911
* inspecting the internal state of the X server. Note that Gtk supports
1912
* both the Xdnd and Motif protocols, so we check each one of those.
1915
* TRUE if a Drag operation is pending (waiting for a drop), FALSE
1921
*----------------------------------------------------------------------------
1925
DnDGHDragPending(GtkWidget *widget) // IN: our widget
1927
/* Xdnd is much more prevalent, so call it first */
1928
return DnDGHXdndDragPending(widget) || DnDGHMotifDragPending(widget);
1933
*----------------------------------------------------------------------------
1935
* DnDGHXdndDragPending --
1937
* Determines whether an Xdnd protocol drag is pending.
1940
* TRUE is a drag is pending, FALSE otherwise.
1945
*----------------------------------------------------------------------------
1949
DnDGHXdndDragPending(GtkWidget *widget) // IN: our widget
1951
GdkAtom xDnDSelection;
1954
xDnDSelection = gdk_atom_intern("XdndSelection", TRUE);
1955
if (xDnDSelection == None) {
1956
Warning("DnDGHXdndDragPending: could not obtain Xdnd selection atom\n");
1961
* The XdndSelection atom will only have an owner if there is a drag in
1964
owner = XGetSelectionOwner(GDK_WINDOW_XDISPLAY(widget->window),
1965
GDKATOM_TO_ATOM(xDnDSelection));
1967
Debug("DnDGHXdndDragPending: an Xdnd drag is %spending\n",
1968
owner != None ? "" : "not ");
1970
return owner != None;
1975
*----------------------------------------------------------------------------
1977
* DnDGHXdndClearPending --
1979
* Clear the ownership of the XdndSelection selection atom that we use to
1980
* determine if a Xdnd drag is pending.
1982
* Note that this function should only be called when a DnD is not in
1985
* Also note that this is function is only necessary to handle desktop
1986
* environments that don't clear the selection owner themselves (read KDE).
1994
*----------------------------------------------------------------------------
1998
DnDGHXdndClearPending(GtkWidget *widget) // IN: program's widget
2000
GdkAtom xDnDSelection;
2002
ASSERT(!gGHState.dragInProgress);
2004
xDnDSelection = gdk_atom_intern("XdndSelection", TRUE);
2005
if (xDnDSelection == None) {
2009
/* Clear current owner by setting owner to None */
2010
XSetSelectionOwner(GDK_WINDOW_XDISPLAY(widget->window),
2011
GDKATOM_TO_ATOM(xDnDSelection), None, CurrentTime);
2016
*----------------------------------------------------------------------------
2018
* DnDMotifDragPending --
2020
* Determines whether a Motif protocol drag is pending.
2022
* XXX This has not yet been tested (looking for an app that actually uses
2023
* the Motif protocol)
2026
* TRUE if a drag is pending, FALSE otherwise.
2031
*----------------------------------------------------------------------------
2035
DnDGHMotifDragPending(GtkWidget *widget) // IN: our widget
2037
GdkAtom motifDragWindow;
2038
Display *dndXDisplay;
2043
unsigned long nitems;
2044
unsigned long bytesAfter;
2045
unsigned char *prop;
2047
motifDragWindow = gdk_atom_intern("_MOTIF_DRAG_WINDOW", TRUE);
2048
if (motifDragWindow == None) {
2049
Warning("DnDGHMotifDragPending: could not obtain Motif "
2050
"drag window atom\n");
2054
dndXDisplay = GDK_WINDOW_XDISPLAY(widget->window);
2055
rootXWindow = RootWindow(dndXDisplay, DefaultScreen(dndXDisplay));
2058
* Try and get the Motif drag window property from X's root window. If one
2059
* is provided, a DnD is pending.
2061
ret = XGetWindowProperty(dndXDisplay, rootXWindow, GDKATOM_TO_ATOM(motifDragWindow),
2062
0, 1, False, XA_WINDOW,
2063
&type, &format, &nitems, &bytesAfter, &prop);
2064
if (ret != Success) {
2065
Warning("DnDGHMotifDragPending: XGetWindowProperty() error.\n");
2070
Debug("DnDGHXdndDragPending: a Motif drag is not pending\n");
2074
Debug("DnDGHXdndDragPending: a Motif drag is pending\n");
2082
*----------------------------------------------------------------------------
2084
* DnDGHFileListClear --
2086
* Clears existing Guest->Host file list, releasing any used resources.
2094
*----------------------------------------------------------------------------
2098
DnDGHFileListClear(void)
2100
Debug("DnDGHFileListClear: clearing G->H file list\n");
2101
if (gGHState.dndFileList) {
2102
free(gGHState.dndFileList);
2103
gGHState.dndFileList = NULL;
2105
gGHState.dndFileListSize = 0;
2106
gGHState.dndFileListNext = NULL;
2111
*----------------------------------------------------------------------------
2113
* DnDGHFileListSet --
2115
* Sets the Guest->Host file list that is accessed through
2116
* DnDGHFileListGetNext().
2122
* Clears the existing Guest->Host file list if it exists.
2124
*----------------------------------------------------------------------------
2128
DnDGHFileListSet(char *fileList, // IN: new Guest->Host file list
2129
size_t fileListSize) // IN: size of the provided list
2131
DnDGHFileListClear();
2132
gGHState.dndFileList = fileList;
2133
gGHState.dndFileListSize = fileListSize;
2134
gGHState.dndFileListNext = fileList;
2136
Debug("DnDGHFileListSet: [%s] (%"FMTSZ"u)\n",
2137
CPName_Print(gGHState.dndFileList, gGHState.dndFileListSize),
2138
gGHState.dndFileListSize);
2143
*----------------------------------------------------------------------------
2145
* DnDGHFileListGetNext --
2147
* Retrieves the next file in the Guest->Host file list.
2149
* Note that this function may only be called after calling
2150
* DnDGHFileListSet() and before calling DnDGHFileListClear().
2153
* TRUE on success, FALSE on failure. If TRUE is returned, fileName is
2154
* given a pointer to the filename's location or NULL if there are no more
2155
* files, and fileNameSize is given the length of fileName.
2158
* The fileListNext value of the Guest->Host global state is updated.
2160
*----------------------------------------------------------------------------
2164
DnDGHFileListGetNext(char **fileName, // OUT: fill with filename location
2165
size_t *fileNameSize) // OUT: fill with filename length
2171
ASSERT(gGHState.dndFileList);
2172
ASSERT(gGHState.dndFileListNext);
2173
ASSERT(gGHState.dndFileListSize > 0);
2175
/* Ensure end is the last NUL character */
2176
end = CPNameUtil_Strrchr(gGHState.dndFileList, gGHState.dndFileListSize, '\0');
2179
/* Get the length of this filename and a pointer to the next one */
2180
len = CPName_GetComponent(gGHState.dndFileListNext, end, &next);
2182
Warning("DnDGHFileListGetNext: error retrieving next component\n");
2186
/* No more entries in the list */
2188
Debug("DnDGHFileListGetNext: no more entries\n");
2194
Debug("DnDGHFileListGetNext: returning [%s] (%d)\n",
2195
gGHState.dndFileListNext, len);
2197
*fileName = gGHState.dndFileListNext;
2198
*fileNameSize = len;
2199
gGHState.dndFileListNext = (char *)next;
2205
*----------------------------------------------------------------------------
2209
* Initializes the Guest->Host DnD state.
2217
*----------------------------------------------------------------------------
2221
DnDGHStateInit(GtkWidget *widget) // IN
2223
Debug("DnDGHStateInit: initializing guest->host state\n");
2225
gGHState.dragContext = NULL;
2226
gGHState.dragInProgress = FALSE;
2227
gGHState.ungrabReceived = FALSE;
2228
gGHState.event = NULL;
2229
DnDGHXdndClearPending(widget);
2231
gtk_widget_hide(widget);
2237
*----------------------------------------------------------------------------
2241
* Initialize the Host->Guest DnD state.
2249
*----------------------------------------------------------------------------
2253
DnDHGStateInit(void)
2255
gHGDnDInProgress = FALSE;
2256
gDoneDragging = FALSE;
2261
*----------------------------------------------------------------------------
2265
* Resets state and sends a DnD cancel message to the host.
2268
* TRUE on success, FALSE on failure.
2271
* DnD operation is cancelled.
2273
*----------------------------------------------------------------------------
2277
DnDGHCancel(GtkWidget *widget) // IN: program's widget
2279
/* Hide our widget so we don't receive stray signals */
2280
if (widget && !gUnity) {
2281
gtk_widget_hide(widget);
2284
if (gGHState.dragContext) {
2285
gdk_drag_status(gGHState.dragContext, 0, gGHState.time);
2288
gGHState.dragInProgress = FALSE;
2291
* We don't initialize Guest->Host state here since an ungrab/grab/ungrab
2292
* will cause a cancel but we want the drop of the DnD to still work.
2294
return RpcOut_sendOne(NULL, NULL, "dnd.finish cancel");
2299
*----------------------------------------------------------------------------
2301
* DnDGHXEventTimeout --
2303
* Cleans up after fake X events do not cause intended events. Hides the
2304
* provided widget and resets all Guest->Host DnD state.
2306
* Note that this is expected to occur on ungrab if there is not a DnD
2307
* pending, but may also occur at other times (sometimes we do not receive
2308
* the drag drop after the mouse button release is faked on KDE).
2310
* This function is invoked by the event manager; it is added/removed
2311
* to/from the queue in both DnDRpcInMouseUngrabCB() and DnDRpcInFinishCB(),
2312
* and DnDGtkDragMotionCB() and DnDGtkDragDropCB() respectively.
2315
* TRUE always, so the event manager doesn't stop running.
2320
*----------------------------------------------------------------------------
2324
DnDGHXEventTimeout(void *clientData) // IN: our widget
2326
GtkWidget *widget = (GtkWidget *)clientData;
2328
Debug("DnDGHXEventTimeout time out \n");
2330
RpcOut_sendOne(NULL, NULL, "dnd.notpending");
2332
if (!gGHState.dragInProgress && !gUnity) {
2333
gtk_widget_hide(widget);
2336
/* gGHState.event is cleared with the rest of Guest->Host state */
2337
DnDGHStateInit(widget);
2344
* Public functions invoked by the rest of vmware-user
2348
*-----------------------------------------------------------------------------
2350
* DnD_GetVmxDnDVersion --
2352
* Ask the vmx for it's dnd version.
2355
* The dnd version the vmx supports, 0 if the vmx doesn't know
2356
* what we're talking about.
2361
*-----------------------------------------------------------------------------
2365
DnD_GetVmxDnDVersion(void)
2371
if (!RpcOut_sendOne(&reply, &replyLen, "vmx.capability.dnd_version")) {
2372
Debug("DnD_GetVmxDnDVersion: could not get VMX DnD version "
2373
"capability: %s\n", reply ? reply : "NULL");
2376
vmxVersion = atoi(reply);
2377
ASSERT(vmxVersion > 1); /* DnD versions start at 2 */
2386
*-----------------------------------------------------------------------------
2388
* DnD_RegisterCapability --
2390
* Register the "dnd" capability. Sometimes this needs to be done separately
2391
* from the rest of DnD registration, so we provide it separately here.
2400
*-----------------------------------------------------------------------------
2404
DnD_RegisterCapability(void)
2406
/* Tell the VMX about the DnD version we support. */
2407
if (!RpcOut_sendOne(NULL, NULL, "tools.capability.dnd_version 2")) {
2408
Debug("DnD_RegisterCapability: could not set guest DnD version capability\n");
2410
} else if (!DnDSendVmxNewFileRoot("dnd.ready enable")) {
2411
Debug("DnD_RegisterCapability: failed to send dnd.ready message to host\n");
2419
*-----------------------------------------------------------------------------
2423
* Register the DnD capability, setup callbacks, initialize.
2426
* TRUE on success, FALSE otherwise.
2429
* mainWnd will be a dragSource in the guest, and dnd will work from
2432
*-----------------------------------------------------------------------------
2436
DnD_Register(GtkWidget *hgWnd, // IN: The widget to register as a drag source.
2437
GtkWidget *ghWnd) // IN: The widget to register as a drag target.
2446
if (DnD_GetVmxDnDVersion() < 2) {
2451
* We can't pass in NULL to XTestQueryExtension(), so pass in a dummy
2452
* variable to avoid segfaults. If we have a reason to check the major and
2453
* minor numbers of the running extension, that would go here.
2455
if (!XTestQueryExtension(GDK_WINDOW_XDISPLAY(hgWnd->window),
2460
/* Host->Guest RPC callbacks */
2461
RpcIn_RegisterCallback(gRpcIn, "dnd.data.set", DnDRpcInDataSetCB, hgWnd);
2462
RpcIn_RegisterCallback(gRpcIn, "dnd.enter", DnDRpcInEnterCB, hgWnd);
2463
RpcIn_RegisterCallback(gRpcIn, "dnd.move", DnDRpcInMoveCB, hgWnd);
2464
RpcIn_RegisterCallback(gRpcIn, "dnd.drop", DnDRpcInDropCB, hgWnd);
2465
RpcIn_RegisterCallback(gRpcIn, "dnd.data.finish", DnDRpcInDataFinishCB,
2468
/* Guest->Host RPC callbacks */
2469
RpcIn_RegisterCallback(gRpcIn, "dnd.ungrab",
2470
DnDRpcInMouseUngrabCB, ghWnd);
2471
RpcIn_RegisterCallback(gRpcIn, "dnd.data.get.file",
2472
DnDRpcInGetNextFileCB, ghWnd);
2473
RpcIn_RegisterCallback(gRpcIn, "dnd.finish",
2474
DnDRpcInFinishCB, ghWnd);
2477
* Setup mainWnd as a DND source/dest.
2479
* Note that G->H drag targets should come first in this array. Currently
2480
* G->H only supports text/uri-list targets.
2482
gTargetEntry[0].target = DRAG_TARGET_NAME_URI_LIST;
2483
gTargetEntry[0].info = DRAG_TARGET_INFO_URI_LIST;
2484
gTargetEntry[0].flags = 0;
2485
gTargetEntry[1].target = DRAG_TARGET_NAME_TEXT_PLAIN;
2486
gTargetEntry[1].info = DRAG_TARGET_INFO_TEXT_PLAIN;
2487
gTargetEntry[1].flags = 0;
2488
gTargetEntry[2].target = DRAG_TARGET_NAME_STRING;
2489
gTargetEntry[2].info = DRAG_TARGET_INFO_STRING;
2490
gTargetEntry[2].flags = 0;
2492
/* Populate our GdkAtom table for our supported Guest->Host targets */
2494
i < ARRAYSIZE(gTargetEntry) && i < ARRAYSIZE(gTargetEntryAtom);
2496
gTargetEntryAtom[i] = gdk_atom_intern(gTargetEntry[i].target, FALSE);
2499
/* Drag source for Host->Guest */
2500
gtk_drag_source_set(hgWnd, GDK_BUTTON1_MASK,
2501
gTargetEntry, ARRAYSIZE(gTargetEntry),
2502
GDK_ACTION_COPY | GDK_ACTION_MOVE);
2504
gtk_signal_connect(GTK_OBJECT(hgWnd), "drag_begin",
2505
GTK_SIGNAL_FUNC(DnDGtkBeginCB), hgWnd);
2506
gtk_signal_connect(GTK_OBJECT(hgWnd), "drag_end",
2507
GTK_SIGNAL_FUNC(DnDGtkEndCB), hgWnd);
2508
gtk_signal_connect(GTK_OBJECT(hgWnd), "drag_data_get",
2509
GTK_SIGNAL_FUNC(DnDGtkDataRequestCB), hgWnd);
2513
* Drop target (destination) for Guest->Host
2515
* We provide NR_GH_DRAG_TARGETS (rather than ARRAYSIZE(gTargetEntry)) to
2516
* gtk_drag_dest_set() since we support less targets for G->H than H->G.
2518
gtk_drag_dest_set(ghWnd,
2519
GTK_DEST_DEFAULT_MOTION,
2520
gTargetEntry, NR_GH_DRAG_TARGETS,
2521
GDK_ACTION_COPY | GDK_ACTION_MOVE);
2523
gtk_signal_connect(GTK_OBJECT(ghWnd), "drag_motion",
2524
GTK_SIGNAL_FUNC(DnDGtkDragMotionCB), ghWnd);
2525
gtk_signal_connect(GTK_OBJECT(ghWnd), "drag_data_received",
2526
GTK_SIGNAL_FUNC(DnDGtkDragDataReceivedCB),
2528
gtk_signal_connect(GTK_OBJECT(ghWnd), "drag_drop",
2529
GTK_SIGNAL_FUNC(DnDGtkDragDropCB), ghWnd);
2531
DnD_OnReset(hgWnd, ghWnd);
2533
if (DnD_RegisterCapability()) {
2538
* We get here if DnD registration fails for some reason
2541
DnD_Unregister(hgWnd, ghWnd);
2547
*-----------------------------------------------------------------------------
2551
* Cleanup dnd related things.
2557
* DnD is stopped, the rpc channel to the vmx is closed.
2559
*-----------------------------------------------------------------------------
2563
DnD_Unregister(GtkWidget *hgWnd, // IN: The widget for hg dnd
2564
GtkWidget *ghWnd) // IN: The widget for gh dnd
2566
RpcOut_sendOne(NULL, NULL, "dnd.ready disable");
2568
DnDGHFileListClear();
2570
/* Unregister source for Host->Guest DnD. */
2571
gtk_drag_source_unset(hgWnd);
2572
gtk_signal_disconnect_by_func(GTK_OBJECT(hgWnd),
2573
GTK_SIGNAL_FUNC(DnDGtkBeginCB),
2575
gtk_signal_disconnect_by_func(GTK_OBJECT(hgWnd),
2576
GTK_SIGNAL_FUNC(DnDGtkEndCB),
2578
gtk_signal_disconnect_by_func(GTK_OBJECT(hgWnd),
2579
GTK_SIGNAL_FUNC(DnDGtkDataRequestCB),
2582
/* Unregister destination for Guest->Host DnD. */
2583
gtk_drag_dest_unset(ghWnd);
2584
gtk_signal_disconnect_by_func(GTK_OBJECT(ghWnd),
2585
GTK_SIGNAL_FUNC(DnDGtkDragMotionCB),
2587
gtk_signal_disconnect_by_func(GTK_OBJECT(ghWnd),
2588
GTK_SIGNAL_FUNC(DnDGtkDragDataReceivedCB),
2590
gtk_signal_disconnect_by_func(GTK_OBJECT(ghWnd),
2591
GTK_SIGNAL_FUNC(DnDGtkDragDropCB),
2594
RpcIn_UnregisterCallback(gRpcIn, "dnd.data.set");
2595
RpcIn_UnregisterCallback(gRpcIn, "dnd.enter");
2596
RpcIn_UnregisterCallback(gRpcIn, "dnd.move");
2597
RpcIn_UnregisterCallback(gRpcIn, "dnd.drop");
2598
RpcIn_UnregisterCallback(gRpcIn, "dnd.data.finish");
2600
/* Guest->Host RPC callbacks */
2601
RpcIn_UnregisterCallback(gRpcIn, "dnd.ungrab");
2602
RpcIn_UnregisterCallback(gRpcIn, "dnd.data.get.file");
2603
RpcIn_UnregisterCallback(gRpcIn, "dnd.finish");
2608
*----------------------------------------------------------------------------
2612
* Handles reinitializing DnD state on a reset.
2618
* DnD is stopped and restarted.
2620
*----------------------------------------------------------------------------
2624
DnD_OnReset(GtkWidget *hgWnd, // IN: The widget for hg dnd
2625
GtkWidget *ghWnd) // IN: The widget for gh dnd
2628
Debug("DnD_OnReset: entry\n");
2630
/* Cancel file transfer. */
2631
if (gHGDnDInProgress || gHGDataPending) {
2632
DnD_DeleteStagingFiles(gFileRoot, FALSE);
2633
if (DnD_BlockIsReady(&gBlockCtrl) &&
2634
!gBlockCtrl.RemoveBlock(gBlockCtrl.fd, gFileRoot)) {
2635
Warning("DnD_OnReset: could not remove block on %s\n",
2641
* If a DnD in either direction was in progress during suspend, send an
2642
* escape to cancel the operation and reset the pointer state.
2644
if (gHGDnDInProgress) {
2645
Debug("DnD_OnReset: sending hgWnd escape\n");
2646
DnDSendEscapeKey(hgWnd);
2649
if (gGHState.dragInProgress) {
2650
Debug("DnD_OnReset: sending ghWnd escape\n");
2651
DnDSendEscapeKey(ghWnd);
2654
if (gGHState.dragInProgress) {
2655
Debug("DnD_OnReset: canceling host->guest DnD\n");
2659
/* Reset DnD state. */
2661
DnDGHStateInit(ghWnd);
2662
DnDGHFileListClear();
2668
*----------------------------------------------------------------------------
2672
* Indicates whether a DnD (or its data transfer) is currently in progress.
2675
* TRUE if a DnD is in progress, FALSE otherwise.
2680
*----------------------------------------------------------------------------
2684
DnD_InProgress(void)
2686
return gGHState.dragInProgress || gHGDnDInProgress || gHGDataPending;
2691
*----------------------------------------------------------------------------
2695
* Sets dnd mode to single window or unity mode.
2701
* Controls if the g->h det window is automatically hidden.
2703
*----------------------------------------------------------------------------
2707
DnD_SetMode(Bool unity) // IN