~ubuntu-branches/ubuntu/quantal/open-vm-tools/quantal-201210021442

« back to all changes in this revision

Viewing changes to vmware-user/dnd.c

  • Committer: Bazaar Package Importer
  • Author(s): Serge Hallyn
  • Date: 2011-03-31 14:20:05 UTC
  • mfrom: (1.4.3 upstream)
  • Revision ID: james.westby@ubuntu.com-20110331142005-3n9red91p7ogkweo
Tags: 2011.03.28-387002-0ubuntu1
* Merge latest upstream git tag.  This has the unlocked_ioctl change
  needed to fix dkms build failures (LP: #727342)
* Changes in debian/rules:
  - work around a bug in toolbox/Makefile, where install-exec-hook is
    not happening.  This needs to get fixed the right way.
  - don't install 'vmware-user' which seems to no longer exist
  - move /etc/xdg into open-vm-toolbox (which should be done using .install)
* debian/open-vm-tools.init: add 'modprobe [-r] vmblock'. (LP: #332323)
* debian/rules and debian/open-vm-toolbox.lintian-overrides:
  - Make vmware-user-suid-wrapper suid-root (LP: #332323)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/*********************************************************
2
 
 * Copyright (C) 2005 VMware, Inc. All rights reserved.
3
 
 *
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.
7
 
 *
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.
12
 
 *
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.
16
 
 *
17
 
 *********************************************************/
18
 
 
19
 
/*
20
 
 * dnd.c --
21
 
 *
22
 
 *  Handles the guest side of host<->guest DnD operations.
23
 
 *
24
 
 *  Guest->Host DnD
25
 
 *  ---------------
26
 
 *
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
33
 
 *     are done,
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.
37
 
 *
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().
42
 
 *
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
48
 
 *  to drop the files.
49
 
 *
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
53
 
 *   guest.)
54
 
 *
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.
60
 
 *
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.
64
 
 *
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().
68
 
 *
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
71
 
 *  in the guest.
72
 
 *
73
 
 *
74
 
 *  Host->Guest DnD
75
 
 *  ---------------
76
 
 *
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.
83
 
 *
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.
90
 
 *
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.
97
 
 *
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.
107
 
 *
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.
115
 
 */
116
 
 
117
 
 
118
 
#include <string.h>
119
 
#include <stdlib.h>
120
 
#include <X11/extensions/XTest.h>       /* for XTest*() */
121
 
#include <X11/keysym.h>                 /* for XK_Escape */
122
 
#include <X11/Xatom.h>                  /* for XA_WINDOW */
123
 
 
124
 
#include "vmwareuserInt.h"
125
 
#include "vm_assert.h"
126
 
#include "vm_basic_defs.h"
127
 
#include "eventManager.h"
128
 
#include "debug.h"
129
 
#include "strutil.h"
130
 
#include "str.h"
131
 
#include "file.h"
132
 
#include "guestApp.h"
133
 
#include "cpName.h"
134
 
#include "cpNameUtil.h"
135
 
#include "dnd.h"
136
 
#include "util.h"
137
 
#include "hgfsVirtualDir.h"
138
 
#include "hgfsServerPolicy.h"
139
 
#include "vmblock.h"
140
 
#include "escape.h"
141
 
#include "vmware/guestrpc/tclodefs.h"
142
 
 
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
150
 
/*
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.
155
 
 */
156
 
#define NR_DRAG_TARGETS                 3
157
 
#define NR_GH_DRAG_TARGETS              1
158
 
 
159
 
#define DROPEFFECT_NONE 0
160
 
#define DROPEFFECT_COPY 1
161
 
#define DROPEFFECT_MOVE 2
162
 
#define DROPEFFECT_LINK 4
163
 
 
164
 
/*
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.
167
 
 *
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
 
 * ----------+------------+-------------+-------------+------------+---------------
176
 
 */
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)
187
 
 
188
 
#ifdef GTK2
189
 
# define GDKATOM_TO_ATOM(gdkAtom) gdk_x11_atom_to_xatom(gdkAtom)
190
 
#else
191
 
# define GDKATOM_TO_ATOM(gdkAtom) gdkAtom
192
 
#endif
193
 
 
194
 
/*
195
 
 * Forward Declarations
196
 
 */
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);
221
 
 
222
 
/*
223
 
 * Gtk DnD specific event/signal callbacks.
224
 
 */
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);
231
 
 
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);
240
 
 
241
 
/*
242
 
 * Utility
243
 
 */
244
 
static Bool DnDSendVmxNewFileRoot(char *rpcCmd);
245
 
static Bool DnDFakeXEvents(GtkWidget *widget, Bool showWidget,
246
 
                           Bool buttonEvent, Bool buttonPress,
247
 
                           Bool moveWindow,
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);
261
 
 
262
 
/*
263
 
 * Globals
264
 
 */
265
 
struct ghState {
266
 
   Bool dragInProgress;
267
 
   Bool ungrabReceived;
268
 
   char *dndFileList;
269
 
   char *dndFileListNext;
270
 
   size_t dndFileListSize;
271
 
   GdkDragContext *dragContext;
272
 
   guint time;
273
 
   Event *event;
274
 
} gGHState;
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;
285
 
static Bool gUnity;
286
 
 
287
 
/*
288
 
 * From vmwareuserInt.h
289
 
 */
290
 
RpcIn     *gRpcIn;
291
 
Display   *gXDisplay;
292
 
Window     gXRoot;
293
 
 
294
 
 
295
 
/*
296
 
 * Host->Guest RPC callback implementations
297
 
 */
298
 
 
299
 
/*
300
 
 *-----------------------------------------------------------------------------
301
 
 *
302
 
 * DnDRpcInEnterCB --
303
 
 *
304
 
 *       For Host->Guest operations only.
305
 
 *       User has dragged something over this guest's MKS window
306
 
 *
307
 
 * Results:
308
 
 *       TRUE on success, FALSE otherwise
309
 
 *
310
 
 * Side effects:
311
 
 *       Some GdkEvents are generated which will "drag" the mouse. A directory
312
 
 *       is created.
313
 
 *
314
 
 *-----------------------------------------------------------------------------
315
 
 */
316
 
 
317
 
static Bool
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
324
 
{
325
 
   char *numFormats;
326
 
   char *pFormat;
327
 
   unsigned int index = 0;
328
 
   int nFormats;
329
 
   int i;
330
 
   GtkWidget *mainWnd;
331
 
 
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);
337
 
   }
338
 
 
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);
343
 
   }
344
 
 
345
 
   numFormats = StrUtil_GetNextToken(&index, args, " ");
346
 
   if (!numFormats) {
347
 
      Debug("DnDRpcInEnterCB: Failed to parse numformats\n");
348
 
      return RpcIn_SetRetVals(result, resultLen,
349
 
                              "must specify number of formats", FALSE);
350
 
   }
351
 
 
352
 
   /* Skip whitespace character. */
353
 
   index++;
354
 
 
355
 
   nFormats = atoi(numFormats);
356
 
   free(numFormats);
357
 
 
358
 
   for (i = 0; i < nFormats; i++) {
359
 
      pFormat = StrUtil_GetNextToken(&index, args, ",");
360
 
 
361
 
      if (!pFormat) {
362
 
         Debug("DnDRpcInEnterCB: Failed to parse format list\n");
363
 
         return RpcIn_SetRetVals(result, resultLen,
364
 
                                 "Failed to read format list", FALSE);
365
 
      } else {
366
 
         /*
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
370
 
          */
371
 
         free(pFormat);
372
 
      }
373
 
   }
374
 
 
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);
379
 
   }
380
 
 
381
 
   RpcIn_SetRetVals(result, resultLen, "", TRUE);
382
 
   RpcOut_sendOne(NULL, NULL, "dnd.feedback copy");
383
 
   Debug("DnDRpcInEnterCB finished\n");
384
 
   return TRUE;
385
 
}
386
 
 
387
 
 
388
 
/*
389
 
 *-----------------------------------------------------------------------------
390
 
 *
391
 
 * DnDRpcInDataSetCB --
392
 
 *
393
 
 *       For Host->Guest operations only.
394
 
 *       Host is sending data from a DnD operation.
395
 
 *
396
 
 * Results:
397
 
 *       TRUE on success, FALSE otherwise.
398
 
 *
399
 
 * Side effects:
400
 
 *       None
401
 
 *
402
 
 *-----------------------------------------------------------------------------
403
 
 */
404
 
 
405
 
static Bool
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
412
 
{
413
 
   char blockDir[DND_MAX_PATH];
414
 
   char *perDnDDir = NULL;
415
 
   char *format;
416
 
   char  *data;
417
 
   unsigned int index = 0;
418
 
   size_t dataSize;
419
 
   char *retStr;
420
 
   Bool ret = FALSE;
421
 
 
422
 
   Debug("DnDRpcInDataSetCB: enter\n");
423
 
 
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);
428
 
   }
429
 
 
430
 
   /* Parse the data type & value string. */
431
 
   format = StrUtil_GetNextToken(&index, args, " ");
432
 
   if (!format) {
433
 
      Debug("DnDRpcInDataSetCB: Failed to parse format\n");
434
 
      return RpcIn_SetRetVals(result, resultLen, "need format", FALSE);
435
 
   }
436
 
 
437
 
   index++; /* Ignore leading space before data. */
438
 
   dataSize = argsSize - index;
439
 
   data = Util_SafeMalloc(dataSize);
440
 
   memcpy(data, args + index, dataSize);
441
 
 
442
 
   Debug("DnDRpcInDataSetCB: Received data from host: (%s) [%s] (%"FMTSZ"u)\n",
443
 
         format, CPName_Print(data, dataSize), dataSize);
444
 
 
445
 
   /*
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.
451
 
    */
452
 
   perDnDDir = DnD_GetLastDirName(gFileRoot);
453
 
   if (!perDnDDir) {
454
 
      Debug("DnDRpcInDataSetCB: cannot obtain dirname of root.\n");
455
 
      retStr = "error obtaining dirname of root";
456
 
      goto out;
457
 
   }
458
 
 
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";
463
 
      goto out;
464
 
   }
465
 
 
466
 
   Str_Sprintf(blockDir, sizeof blockDir,
467
 
               "%s" DIRSEPS "%s" DIRSEPS, gBlockCtrl.blockRoot, perDnDDir);
468
 
 
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";
473
 
      goto out;
474
 
   }
475
 
   if (dataSize + 1 > sizeof gDnDData) {
476
 
      Debug("DnDRpcInDataSetCB: data too large\n");
477
 
      retStr = "data too large";
478
 
      goto out;
479
 
   }
480
 
 
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);
485
 
 
486
 
   retStr = "";
487
 
   ret = TRUE;
488
 
 
489
 
out:
490
 
   free(format);
491
 
   free(data);
492
 
   free(perDnDDir);
493
 
   return RpcIn_SetRetVals(result, resultLen, retStr, ret);
494
 
}
495
 
 
496
 
 
497
 
/*
498
 
 *-----------------------------------------------------------------------------
499
 
 *
500
 
 * DnDRpcInMoveCB --
501
 
 *
502
 
 *       For Host->Guest operations only.
503
 
 *       Host user is dragging data over this guest's MKS window
504
 
 *
505
 
 * Results:
506
 
 *       TRUE on success, FALSE otherwise.
507
 
 *
508
 
 * Side effects:
509
 
 *       Send a gdk event that "moves" the mouse.
510
 
 *
511
 
 *-----------------------------------------------------------------------------
512
 
 */
513
 
 
514
 
static Bool
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
521
 
{
522
 
   GtkWidget *mainWnd;
523
 
   char *sXCoord;
524
 
   char *sYCoord;
525
 
   unsigned int index = 0;
526
 
   int xCoord, yCoord;
527
 
 
528
 
   mainWnd = GTK_WIDGET(clientData);
529
 
   if (mainWnd == NULL) {
530
 
      return RpcIn_SetRetVals(result, resultLen,
531
 
                              "bad clientData passed to callback", FALSE);
532
 
   }
533
 
 
534
 
   sXCoord = StrUtil_GetNextToken(&index, args, " ");
535
 
   sYCoord = StrUtil_GetNextToken(&index, args, " ");
536
 
 
537
 
   if (!sXCoord || !sYCoord) {
538
 
      Debug("DnDRpcInMove: Failed to parse coords\n");
539
 
      free(sXCoord);
540
 
      free(sYCoord);
541
 
      return RpcIn_SetRetVals(result, resultLen,
542
 
                              "error reading mouse move data", FALSE);
543
 
   }
544
 
 
545
 
   xCoord = atoi(sXCoord);
546
 
   yCoord = atoi(sYCoord);
547
 
 
548
 
   free(sXCoord);
549
 
   free(sYCoord);
550
 
 
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);
556
 
   }
557
 
 
558
 
   return RpcIn_SetRetVals(result, resultLen, "", TRUE);
559
 
}
560
 
 
561
 
 
562
 
/*
563
 
 *-----------------------------------------------------------------------------
564
 
 *
565
 
 * DnDRpcInDataFinishCB --
566
 
 *
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
571
 
 *       root.
572
 
 *
573
 
 * Results:
574
 
 *       TRUE on success, FALSE otherwise
575
 
 *
576
 
 * Side effects:
577
 
 *       None
578
 
 *
579
 
 *-----------------------------------------------------------------------------
580
 
 */
581
 
 
582
 
static Bool
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
589
 
{
590
 
   unsigned int index = 0;
591
 
   char *state;
592
 
 
593
 
   Debug("DnDRpcInDataFinishCB: enter\n");
594
 
 
595
 
   state = StrUtil_GetNextToken(&index, args, " ");
596
 
   if (!state) {
597
 
      Debug("DnDRpcInDataFinishCB: could not get dnd finish state.\n");
598
 
      return RpcIn_SetRetVals(result, resultLen,
599
 
                              "could not get dnd finish state", FALSE);
600
 
   }
601
 
 
602
 
   /* 
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.
607
 
    */
608
 
   if (!gHGDataPending) {
609
 
      Debug("DnDRpcInDataFinishCB: expected gHGDataPending to be set.\n");
610
 
   }
611
 
 
612
 
   gHGDataPending = FALSE;
613
 
 
614
 
   /*
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.
620
 
    */
621
 
   if (strcmp(state, "success") != 0) {
622
 
      /* On any non-success input, delete the files. */
623
 
      DnD_DeleteStagingFiles(gFileRoot, FALSE);
624
 
   }
625
 
 
626
 
   free(state);
627
 
 
628
 
   if (DnD_BlockIsReady(&gBlockCtrl) &&
629
 
       !gBlockCtrl.RemoveBlock(gBlockCtrl.fd, gFileRoot)) {
630
 
      Warning("DnDRpcInDataFinishCB: could not remove block on %s\n",
631
 
              gFileRoot);
632
 
   }
633
 
 
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);
639
 
   }
640
 
 
641
 
   return RpcIn_SetRetVals(result, resultLen, "", TRUE);
642
 
}
643
 
 
644
 
 
645
 
/*
646
 
 *-----------------------------------------------------------------------------
647
 
 *
648
 
 * DnDRpcInDropCB --
649
 
 *
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).
654
 
 *
655
 
 * Results:
656
 
 *       TRUE on success, FALSE otherwise.
657
 
 *
658
 
 * Side effects:
659
 
 *       None
660
 
 *
661
 
 *-----------------------------------------------------------------------------
662
 
 */
663
 
 
664
 
static Bool
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
671
 
{
672
 
   GtkWidget *mainWnd;
673
 
   char *sXCoord;
674
 
   char *sYCoord;
675
 
   unsigned int index = 0;
676
 
   int xCoord, yCoord;
677
 
 
678
 
   Debug("DnDRpcInDropCB: enter\n");
679
 
 
680
 
   gDoneDragging = TRUE;
681
 
 
682
 
   mainWnd = GTK_WIDGET(clientData);
683
 
   if (mainWnd == NULL) {
684
 
      return RpcIn_SetRetVals(result, resultLen,
685
 
                              "bad clientData passed to callback", FALSE);
686
 
   }
687
 
 
688
 
   sXCoord = StrUtil_GetNextToken(&index, args, " ");
689
 
   sYCoord = StrUtil_GetNextToken(&index, args, " ");
690
 
 
691
 
   if (!sXCoord || !sYCoord) {
692
 
      Debug("DnDRpcInDropCB: Failed to parse coords\n");
693
 
      free(sXCoord);
694
 
      free(sYCoord);
695
 
      return RpcIn_SetRetVals(result, resultLen,
696
 
                              "must specify drop coordinates", FALSE);
697
 
   }
698
 
 
699
 
   xCoord = atoi(sXCoord);
700
 
   yCoord = atoi(sYCoord);
701
 
 
702
 
   free(sXCoord);
703
 
   free(sYCoord);
704
 
 
705
 
   Debug("DnDRpcInDropCB: Received drop notification at (%d,%d)\n",
706
 
         xCoord, yCoord);
707
 
 
708
 
   /*
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
711
 
    * guest.
712
 
    */
713
 
   if (!DnD_BlockIsReady(&gBlockCtrl)) {
714
 
      /*
715
 
       * We shouldn't get here since DnDRpcInEnterCB() checks this, but we'll
716
 
       * check rather than ASSERT just in case.
717
 
       */
718
 
      return RpcIn_SetRetVals(result, resultLen,
719
 
                              "blocking file system unavailable", FALSE);
720
 
   }
721
 
 
722
 
   if (!gBlockCtrl.AddBlock(gBlockCtrl.fd, gFileRoot)) {
723
 
      return RpcIn_SetRetVals(result, resultLen, "could not add block", FALSE);
724
 
   }
725
 
 
726
 
   /* Update state before causing faking any mouse or keyboard changes. */
727
 
   gHGDataPending = TRUE;
728
 
 
729
 
 
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);
733
 
   }
734
 
 
735
 
   return RpcIn_SetRetVals(result, resultLen, "", TRUE);
736
 
}
737
 
 
738
 
 
739
 
/*
740
 
 * Guest->Host RPC callback implementations
741
 
 */
742
 
 
743
 
/*
744
 
 *----------------------------------------------------------------------------
745
 
 *
746
 
 * DnDRpcInMouseUngrabCB --
747
 
 *
748
 
 *    For Guest->Host operations only.
749
 
 *
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).
755
 
 *
756
 
 * Results:
757
 
 *    TRUE on success, FALSE on failure.
758
 
 *
759
 
 * Side effects:
760
 
 *    The GDK window is moved and resized, and the mouse is moved over it.
761
 
 *
762
 
 *----------------------------------------------------------------------------
763
 
 */
764
 
 
765
 
static Bool
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
772
 
{
773
 
   unsigned int index = 0;
774
 
   GtkWidget *mainWnd;
775
 
   int32 xPos;
776
 
   int32 yPos;
777
 
 
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);
784
 
   }
785
 
 
786
 
   /*
787
 
    * If there is already a DnD or copy/paste in progress (including the file
788
 
    * transfer), don't allow another.
789
 
    */
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);
794
 
   }
795
 
 
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);
801
 
   }
802
 
 
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);
808
 
   }
809
 
 
810
 
   Debug("DnDRpcInMouseUngrabCB: Received (%d,%d)\n", xPos, yPos);
811
 
 
812
 
   /*
813
 
    * If there is no DnD pending, inform the host so the MKS can start sending
814
 
    * mouse packets again.
815
 
    */
816
 
   if (!DnDGHDragPending(mainWnd)) {
817
 
      RpcOut_sendOne(NULL, NULL, "dnd.notpending");
818
 
      return RpcIn_SetRetVals(result, resultLen, "DnD not pending", TRUE);
819
 
   }
820
 
 
821
 
   /* The host only gives us coordinates within our screen */
822
 
   ASSERT(xPos >= 0);
823
 
   ASSERT(yPos >= 0);
824
 
 
825
 
   /*
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
829
 
    * operation.
830
 
    */
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);
836
 
   }
837
 
 
838
 
   /*
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.
843
 
    */
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);
851
 
   }
852
 
 
853
 
   gGHState.dragInProgress = FALSE;
854
 
   gGHState.ungrabReceived = TRUE;
855
 
 
856
 
   Debug("DnDRpcInMouseUngrabCB finished\n");
857
 
   return RpcIn_SetRetVals(result, resultLen, "", TRUE);
858
 
}
859
 
 
860
 
 
861
 
/*
862
 
 *----------------------------------------------------------------------------
863
 
 *
864
 
 * DnDRpcInGetNextFileCB --
865
 
 *
866
 
 *    For Guest->Host operations only.
867
 
 *
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).
872
 
 *
873
 
 * Results:
874
 
 *    TRUE on success, FALSE on failure.
875
 
 *
876
 
 * Side effects:
877
 
 *    Iterator pointer within file list of GH state is iterated to next list
878
 
 *    entry (through call to DnDGHFileListGetNext()).
879
 
 *
880
 
 *----------------------------------------------------------------------------
881
 
 */
882
 
 
883
 
static Bool
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
890
 
{
891
 
   static char resultBuffer[DND_MAX_PATH];
892
 
   char *fileName;
893
 
   size_t fileNameSize;
894
 
   uint32 cpNameSize;
895
 
   GtkWidget *mainWnd;
896
 
   Bool res;
897
 
 
898
 
   mainWnd = GTK_WIDGET(clientData);
899
 
   if (mainWnd == NULL) {
900
 
      DnDGHCancel(NULL);
901
 
      return RpcIn_SetRetVals(result, resultLen,
902
 
                              "bad clientData passed to callback", FALSE);
903
 
   }
904
 
 
905
 
   /*
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.
909
 
    */
910
 
   res = DnDGHFileListGetNext(&fileName, &fileNameSize);
911
 
 
912
 
   if (!res) {
913
 
      Warning("DnDRpcInGetNextFileCB: error retrieving file name\n");
914
 
      DnDGHCancel(mainWnd);
915
 
      return RpcIn_SetRetVals(result, resultLen, "error getting file", FALSE);
916
 
   }
917
 
 
918
 
   if (!fileName) {
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);
922
 
   }
923
 
 
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);
928
 
   }
929
 
 
930
 
   /*
931
 
    * Construct a reply message of the form:
932
 
    * <file name in guest format><NUL><filename in CPName format>
933
 
    */
934
 
   memcpy(resultBuffer, fileName, fileNameSize);
935
 
   resultBuffer[fileNameSize] = '\0';
936
 
 
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);
945
 
   }
946
 
 
947
 
   /* Set manually because RpcIn_SetRetVals() assumes no NUL characters */
948
 
   *result = resultBuffer;
949
 
   *resultLen = fileNameSize + 1 + cpNameSize;
950
 
 
951
 
   Debug("DnDRpcInGetNextFileCB: [%s] (%"FMTSZ"u)\n",
952
 
         CPName_Print(*result, *resultLen), *resultLen);
953
 
 
954
 
   return TRUE;
955
 
}
956
 
 
957
 
 
958
 
/*
959
 
 *----------------------------------------------------------------------------
960
 
 *
961
 
 * DnDRpcInFinishCB --
962
 
 *
963
 
 *    For Guest->Host operations only.
964
 
 *
965
 
 *    Invoked when host side of DnD operation has finished.
966
 
 *
967
 
 * Results:
968
 
 *    TRUE on success, FALSE on failure.
969
 
 *
970
 
 * Side effects:
971
 
 *    None.
972
 
 *
973
 
 *----------------------------------------------------------------------------
974
 
 */
975
 
 
976
 
static Bool
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
983
 
{
984
 
   GtkWidget *mainWnd;
985
 
   char *effect = NULL;
986
 
   unsigned int index = 0;
987
 
   char *retStr;
988
 
   Bool ret = FALSE;
989
 
 
990
 
   mainWnd = GTK_WIDGET(clientData);
991
 
   if (mainWnd == NULL) {
992
 
      retStr = "bad clientData passed to callback";
993
 
      goto exit;
994
 
   }
995
 
 
996
 
   effect = StrUtil_GetNextToken(&index, args, " ");
997
 
   if (!effect) {
998
 
      Warning("DnDRpcInFinishCB: no drop effect provided\n");
999
 
      retStr = "drop effect not provided";
1000
 
      goto exit;
1001
 
   }
1002
 
 
1003
 
   if (strcmp(effect, "cancel") == 0) {
1004
 
      DnDSendEscapeKey(mainWnd);
1005
 
      DnDGHCancel(mainWnd);
1006
 
   } else {
1007
 
      /*
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.
1012
 
       */
1013
 
      if (!DnDGHFakeDrop(mainWnd)) {
1014
 
         Warning("DnDRpcInFinishCB: could not fake X events\n");
1015
 
         retStr = "error faking X events";
1016
 
         goto exit;
1017
 
      }
1018
 
 
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";
1024
 
         goto exit;
1025
 
      }
1026
 
   }
1027
 
 
1028
 
   retStr = "";
1029
 
   ret = TRUE;
1030
 
 
1031
 
exit:
1032
 
   if (!ret) {
1033
 
      DnDGHCancel(mainWnd);
1034
 
   }
1035
 
 
1036
 
   free(effect);
1037
 
   gGHState.dragInProgress = FALSE;
1038
 
   return RpcIn_SetRetVals(result, resultLen, retStr, ret);
1039
 
}
1040
 
 
1041
 
 
1042
 
/*
1043
 
 * Host->Guest (drop source) Gtk callback implementations
1044
 
 */
1045
 
 
1046
 
/*
1047
 
 *-----------------------------------------------------------------------------
1048
 
 *
1049
 
 * DnDGtkBeginCB --
1050
 
 *
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.
1054
 
 *
1055
 
 * Results:
1056
 
 *       None
1057
 
 *
1058
 
 * Side effects:
1059
 
 *       None
1060
 
 *
1061
 
 *-----------------------------------------------------------------------------
1062
 
 */
1063
 
 
1064
 
static void
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
1068
 
{
1069
 
   GtkWidget *mainWnd = GTK_WIDGET(data);
1070
 
 
1071
 
   Debug("DnDGtkBeginCB: entry\n");
1072
 
 
1073
 
   if ((widget == NULL) || (mainWnd == NULL) || (dc == NULL)) {
1074
 
      return;
1075
 
   }
1076
 
 
1077
 
   gHGDnDInProgress = TRUE;
1078
 
   gDoneDragging = FALSE;
1079
 
   gHGDataPending = FALSE;
1080
 
}
1081
 
 
1082
 
 
1083
 
/*
1084
 
 *-----------------------------------------------------------------------------
1085
 
 *
1086
 
 * DnDGtkEndCB --
1087
 
 *
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
1090
 
 *       operation.
1091
 
 *
1092
 
 * Results:
1093
 
 *       None
1094
 
 *
1095
 
 * Side effects:
1096
 
 *       None
1097
 
 *
1098
 
 *-----------------------------------------------------------------------------
1099
 
 */
1100
 
 
1101
 
static void
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
1105
 
{
1106
 
 
1107
 
   GtkWidget *mainWnd = GTK_WIDGET(data);
1108
 
 
1109
 
   Debug("DnDGtkEndCB: enter\n");
1110
 
 
1111
 
   if (mainWnd == NULL || dc == NULL) {
1112
 
      return;
1113
 
   }
1114
 
 
1115
 
   /*
1116
 
    * Do not set gHGDataPending to FALSE since DnD operation completes before
1117
 
    * the data transfer.
1118
 
    */
1119
 
   gDoneDragging = FALSE;
1120
 
   gHGDnDInProgress = FALSE;
1121
 
 
1122
 
   RpcOut_sendOne(NULL, NULL, "dnd.finish %d", DROPEFFECT_COPY);
1123
 
}
1124
 
 
1125
 
 
1126
 
/*
1127
 
 *-----------------------------------------------------------------------------
1128
 
 *
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.
1134
 
 *
1135
 
 * Results:
1136
 
 *       None
1137
 
 *
1138
 
 * Side effects:
1139
 
 *       Data is avaiable to drop target.
1140
 
 *
1141
 
 *-----------------------------------------------------------------------------
1142
 
 */
1143
 
 
1144
 
static void
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
1151
 
{
1152
 
   const char *begin;
1153
 
   const char *end;
1154
 
   const char *next;
1155
 
   const char *pre;
1156
 
   const char *post;
1157
 
   size_t preLen;
1158
 
   size_t postLen;
1159
 
   int len;
1160
 
   Bool insertSpace;
1161
 
   char *text = NULL;
1162
 
   size_t textLen = 1;
1163
 
 
1164
 
   Debug("DnDGtkDataRequestCB: enter\n");
1165
 
 
1166
 
   if ((widget == NULL) || (dc == NULL) || (selection_data == NULL)) {
1167
 
      Debug("DnDGtkDataRequestCB: Error, widget or dc or selection_data is invalid\n");
1168
 
      return;
1169
 
   }
1170
 
 
1171
 
   /* Do nothing if we have not finished dragging yet */
1172
 
   if (!gDoneDragging) {
1173
 
      Debug("DnDGtkDataRequestCB: not done dragging yet\n");
1174
 
      return;
1175
 
   }
1176
 
 
1177
 
   /* Setup the format string components */
1178
 
   switch (info) {
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;
1185
 
      break;
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;
1191
 
      insertSpace = TRUE;
1192
 
      break;
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;
1198
 
      insertSpace = TRUE;
1199
 
      break;
1200
 
   default:
1201
 
      Log("DnDGtkDataRequestCB: invalid drag target info\n");
1202
 
      return;
1203
 
   }
1204
 
 
1205
 
 
1206
 
   /*
1207
 
    * Set begin to first non-NUL character and end to last NUL character to
1208
 
    * prevent errors in calling CPName_GetComponent().
1209
 
    */
1210
 
   for(begin = gDnDData; *begin == '\0'; begin++)
1211
 
      ;
1212
 
   end = CPNameUtil_Strrchr(gDnDData, gDnDDataSize + 1, '\0');
1213
 
   ASSERT(end);
1214
 
 
1215
 
   /* Build up selection data */
1216
 
   while ((len = CPName_GetComponent(begin, end, &next)) != 0) {
1217
 
      const size_t origTextLen = textLen;
1218
 
      Bool freeBegin = FALSE;
1219
 
 
1220
 
      if (len < 0) {
1221
 
         Log("DnDGtkDataRequestCB: error getting next component\n");
1222
 
         if (text) {
1223
 
            free(text);
1224
 
         }
1225
 
         return;
1226
 
      }
1227
 
 
1228
 
      /*
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.
1232
 
       */
1233
 
      if (info == DRAG_TARGET_INFO_URI_LIST) {
1234
 
         size_t newLen;
1235
 
         char *escapedComponent;
1236
 
         int escIndex;
1237
 
         int bytesToEsc[256] = { 0, };
1238
 
 
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 */
1245
 
 
1246
 
         /* Escape non-ASCII characters so we can pass UTF-8 filenames */
1247
 
         for (escIndex = 0x80; escIndex < 0x100; escIndex++) {
1248
 
            bytesToEsc[escIndex] = 1;
1249
 
         }
1250
 
 
1251
 
         escapedComponent = Escape_Do('%', bytesToEsc, begin, len, &newLen);
1252
 
         if (escapedComponent) {
1253
 
            begin = escapedComponent;
1254
 
            len = newLen;
1255
 
            freeBegin = TRUE;
1256
 
         }
1257
 
      }
1258
 
 
1259
 
      /*
1260
 
       * Append component.  NUL terminator was accounted for by initializing
1261
 
       * textLen to one above.
1262
 
       */
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);
1268
 
 
1269
 
      if (insertSpace && next != end) {
1270
 
         ASSERT(textLen - 2 >= 0);
1271
 
         text[textLen - 2] = ' ';
1272
 
         text[textLen - 1] = '\0';
1273
 
      }
1274
 
 
1275
 
      if (freeBegin) {
1276
 
         free((void *)begin);
1277
 
      }
1278
 
 
1279
 
      /* Iterate to next component */
1280
 
      begin = next;
1281
 
   }
1282
 
 
1283
 
   /*
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.
1287
 
    */
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. */
1291
 
                          text, textLen);
1292
 
   free(text);
1293
 
}
1294
 
 
1295
 
 
1296
 
/*
1297
 
 * Guest->Host (drop target) Gtk callback implementations
1298
 
 */
1299
 
 
1300
 
/*
1301
 
 *----------------------------------------------------------------------------
1302
 
 *
1303
 
 * DnDGtkDragMotionCB --
1304
 
 *
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
1307
 
 *    pending.
1308
 
 *
1309
 
 * Results:
1310
 
 *    TRUE on success, FALSE on failure.
1311
 
 *
1312
 
 * Side effects:
1313
 
 *    RPC messages are sent to the host to proxy the DnD over.
1314
 
 *
1315
 
 *----------------------------------------------------------------------------
1316
 
 */
1317
 
 
1318
 
static gboolean
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
1325
 
{
1326
 
   GdkAtom commonTarget = 0;
1327
 
   Bool found = FALSE;
1328
 
   uint32 i;
1329
 
 
1330
 
   ASSERT(widget);
1331
 
   ASSERT(widget == data);
1332
 
   ASSERT(dc);
1333
 
 
1334
 
   Debug("DnDGtkDragMotionCB: entry (x=%d, y=%d, time=%d)\n", x, y, time);
1335
 
 
1336
 
   /*
1337
 
    * We'll get a number of these and should only carry on these operations on
1338
 
    * the first one.
1339
 
    *
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.
1343
 
    */
1344
 
   if (gGHState.dragInProgress && !gUnity) {
1345
 
      Debug("DnDGtkDragMotionCB: drag already in progress\n");
1346
 
      return FALSE;
1347
 
   }
1348
 
 
1349
 
   /*
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.
1353
 
    */
1354
 
   if (!gGHState.ungrabReceived && !gUnity) {
1355
 
      Debug("DnDGtkDragMotionCB: extra drag motion without ungrab\n");
1356
 
      return FALSE;
1357
 
   }
1358
 
 
1359
 
   gGHState.ungrabReceived = FALSE;
1360
 
 
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;
1366
 
   }
1367
 
 
1368
 
   /*
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.
1373
 
    */
1374
 
 
1375
 
   /*
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.
1383
 
    */
1384
 
   for (i = 0; i < ARRAYSIZE(gTargetEntryAtom) && !found; i++) {
1385
 
      GList *currContextTarget = dc->targets;
1386
 
 
1387
 
      while (currContextTarget) {
1388
 
         if (gTargetEntryAtom[i] == (GdkAtom)currContextTarget->data) {
1389
 
            commonTarget = gTargetEntryAtom[i];
1390
 
            found = TRUE;
1391
 
            break;
1392
 
         }
1393
 
         currContextTarget = currContextTarget->next;
1394
 
      }
1395
 
   }
1396
 
 
1397
 
   if (!found) {
1398
 
      Warning("DnDGtkDragMotionCB: could not find a common target format\n");
1399
 
      DnDGHCancel(widget);
1400
 
      return FALSE;
1401
 
   }
1402
 
 
1403
 
   /*
1404
 
    * Request the data.  A "drag_data_received" signal will be sent to widget
1405
 
    * (that's us) upon completion.
1406
 
    */
1407
 
   gtk_drag_get_data(widget, dc, commonTarget, time);
1408
 
 
1409
 
 
1410
 
   gGHState.dragInProgress = TRUE;
1411
 
   return TRUE;
1412
 
}
1413
 
 
1414
 
 
1415
 
/*
1416
 
 *----------------------------------------------------------------------------
1417
 
 *
1418
 
 * DnDGtkDragDataReceivedCB --
1419
 
 *
1420
 
 *    "drag_data_received" signal handler for GTK.  Invoked when the data
1421
 
 *    requested by a gtk_drag_get_data() call is ready.
1422
 
 *
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).
1426
 
 *
1427
 
 * Results:
1428
 
 *    None.
1429
 
 *
1430
 
 * Side effects:
1431
 
 *    None.
1432
 
 *
1433
 
 *----------------------------------------------------------------------------
1434
 
 */
1435
 
 
1436
 
static void
1437
 
DnDGtkDragDataReceivedCB(GtkWidget *widget,          // IN
1438
 
                         GdkDragContext *dc,         // IN
1439
 
                         gint x,                     // IN
1440
 
                         gint y,                     // IN
1441
 
                         GtkSelectionData *dragData, // IN
1442
 
                         guint info,                 // IN
1443
 
                         guint time,                 // IN
1444
 
                         gpointer data)              // IN
1445
 
{
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;
1450
 
   char *rpc;
1451
 
   size_t rpcSize;
1452
 
 
1453
 
 
1454
 
   Debug("DnDGtkDragDataReceivedCB: entry\n");
1455
 
 
1456
 
   if (dragData->length < 0) {
1457
 
      Warning("DnDGtkDragDataReceivedCB: received length < 0 error\n");
1458
 
      goto error;
1459
 
   }
1460
 
 
1461
 
   gGHState.dragContext = dc;
1462
 
   gGHState.time = time;
1463
 
 
1464
 
   /*
1465
 
    * Construct the body of the RPC message and our Guest->Host file list.
1466
 
    */
1467
 
   if (dragData->target == gTargetEntryAtom[DRAG_TARGET_INFO_URI_LIST]) {
1468
 
      char *currName;
1469
 
      size_t currSize;
1470
 
      size_t index = 0;
1471
 
      char *ghFileList = NULL;
1472
 
      size_t ghFileListSize = 0;
1473
 
 
1474
 
      Debug("DnDGtkDragDataReceivedCB: uri-list [%s]\n", dragData->data);
1475
 
 
1476
 
      /*
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.
1481
 
       */
1482
 
      while ((currName = DnD_UriListGetNextFile(dragData->data,
1483
 
                                                &index,
1484
 
                                                &currSize))) {
1485
 
         size_t lastComponentSize;
1486
 
         char *lastComponentStart;
1487
 
 
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';
1494
 
         ghFileListSize++;
1495
 
 
1496
 
         /* Append last component to RPC body */
1497
 
         lastComponentStart = CPNameUtil_Strrchr(currName, currSize, DIRSEPC);
1498
 
         if (!lastComponentStart) {
1499
 
            /*
1500
 
             * This shouldn't happen since filenames are absolute, but handle
1501
 
             * it as if the file name is the last component
1502
 
             */
1503
 
            lastComponentStart = currName;
1504
 
         } else {
1505
 
            /* Skip the last directory separator */
1506
 
            lastComponentStart++;
1507
 
         }
1508
 
 
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';
1514
 
         rpcBodySize++;
1515
 
 
1516
 
         free(currName);
1517
 
      }
1518
 
 
1519
 
      if (!ghFileList || !rpcBody) {
1520
 
         Warning("DnDGtkDragDataReceivedCB: no filenames retrieved "
1521
 
                 "from URI list\n");
1522
 
         free(ghFileList);
1523
 
         free(rpcBody);
1524
 
         goto error;
1525
 
      }
1526
 
 
1527
 
      /* Set the list of full paths for use in the "dnd.data.get.file" callback */
1528
 
      DnDGHFileListSet(ghFileList, ghFileListSize);
1529
 
 
1530
 
      /* rpcBody (and its size) will always contain a trailing NUL character */
1531
 
      rpcBodySize--;
1532
 
   } else {
1533
 
      Warning("DnDGtkDragDataReceivedCB: unknown target format used [%s]\n",
1534
 
              dragData->data);
1535
 
      goto error;
1536
 
   }
1537
 
 
1538
 
   /*
1539
 
    * Set the drag data on the host, followed by sending the drag enter
1540
 
    */
1541
 
   rpcSize = rpcHeaderSize + rpcBodySize;
1542
 
   rpc = Util_SafeMalloc(rpcSize);
1543
 
   memcpy(rpc, rpcHeader, rpcHeaderSize);
1544
 
   memcpy(rpc + rpcHeaderSize, rpcBody, rpcBodySize);
1545
 
   free(rpcBody);
1546
 
 
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");
1551
 
      free(rpc);
1552
 
      goto error;
1553
 
   }
1554
 
 
1555
 
   free(rpc);
1556
 
 
1557
 
   if (!RpcOut_sendOne(NULL, NULL, "dnd.enter 1 CF_HDROP")) {
1558
 
      Warning("DnDGtkDragMotionCB: failed to send dnd.enter message\n");
1559
 
      goto error;
1560
 
   }
1561
 
 
1562
 
   return;
1563
 
 
1564
 
error:
1565
 
   RpcOut_sendOne(NULL, NULL, "dnd.notpending");
1566
 
   DnDGHCancel(widget);
1567
 
}
1568
 
 
1569
 
 
1570
 
/*
1571
 
 *----------------------------------------------------------------------------
1572
 
 *
1573
 
 * DnDGtkDragDropCB --
1574
 
 *
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.
1579
 
 *
1580
 
 * Results:
1581
 
 *    TRUE indicates to GTK that it need not run other handlers, FALSE
1582
 
 *    otherwise.
1583
 
 *
1584
 
 * Side effects:
1585
 
 *    None.
1586
 
 *
1587
 
 *----------------------------------------------------------------------------
1588
 
 */
1589
 
 
1590
 
static gboolean
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
1597
 
{
1598
 
   ASSERT(widget);
1599
 
   ASSERT(widget == data);
1600
 
 
1601
 
   Debug("DnDGtkDragDropCB: entry (%d, %d)\n", x, y);
1602
 
 
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;
1608
 
   }
1609
 
 
1610
 
   /* Hide our window so we don't receive stray signals */
1611
 
   if (!gUnity) {
1612
 
      gtk_widget_hide(widget);
1613
 
   }
1614
 
 
1615
 
   gtk_drag_finish(dc, TRUE, FALSE, time);
1616
 
 
1617
 
   /* Reset all Guest->Host state */
1618
 
   DnDGHStateInit(widget);
1619
 
 
1620
 
   return FALSE;
1621
 
}
1622
 
 
1623
 
 
1624
 
/*
1625
 
 * Utility functions
1626
 
 */
1627
 
 
1628
 
/*
1629
 
 *----------------------------------------------------------------------------
1630
 
 *
1631
 
 * DnD_GetNewFileRoot --
1632
 
 *
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.
1635
 
 *
1636
 
 * Results:
1637
 
 *    Size of root string (not including NUL terminator).
1638
 
 *
1639
 
 * Side effects:
1640
 
 *    None.
1641
 
 *
1642
 
 *----------------------------------------------------------------------------
1643
 
 */
1644
 
 
1645
 
int
1646
 
DnD_GetNewFileRoot(char *fileRoot,         // IN/OUT
1647
 
                   int bufSize)            // IN: sizeof fileRoot
1648
 
{
1649
 
   char *newDir = NULL;
1650
 
   size_t fileRootSize;
1651
 
 
1652
 
   newDir = DnD_CreateStagingDirectory();
1653
 
   if (newDir == NULL) {
1654
 
      /*
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.
1657
 
       */
1658
 
      Str_Strcpy(fileRoot, DnD_GetFileRoot(), bufSize);
1659
 
      return strlen(fileRoot);
1660
 
   } else {
1661
 
      fileRootSize = strlen(newDir);
1662
 
      ASSERT(fileRootSize < bufSize);
1663
 
      memcpy(fileRoot, newDir, fileRootSize);
1664
 
      fileRoot[fileRootSize] = '\0';
1665
 
      free(newDir);
1666
 
      return fileRootSize;
1667
 
   }
1668
 
}
1669
 
 
1670
 
 
1671
 
/*
1672
 
 *----------------------------------------------------------------------------
1673
 
 *
1674
 
 * DnDSendVmxNewFileRoot --
1675
 
 *
1676
 
 *    Sends the VMX a new file root with the provided RPC command.
1677
 
 *
1678
 
 * Results:
1679
 
 *    TRUE on success, FALSE on failure
1680
 
 *
1681
 
 * Side effects:
1682
 
 *    gFileRoot is repopulated.
1683
 
 *
1684
 
 *----------------------------------------------------------------------------
1685
 
 */
1686
 
 
1687
 
static Bool
1688
 
DnDSendVmxNewFileRoot(char *rpcCmd)     // IN: RPC command
1689
 
{
1690
 
   int32 rpcCommandSize;
1691
 
   int32 cpNameSize;
1692
 
   int32 rpcMessageSize;
1693
 
   char *rpcMessage;
1694
 
   char *cur;
1695
 
 
1696
 
   /* Repopulate gFileRoot */
1697
 
   gFileRootSize = DnD_GetNewFileRoot(gFileRoot, sizeof gFileRoot);
1698
 
 
1699
 
   /*
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.
1703
 
    *
1704
 
    * <rpcCmd> <file root in local format><NUL><file root in CPName>
1705
 
    */
1706
 
   rpcCommandSize = strlen(rpcCmd);
1707
 
 
1708
 
   /*
1709
 
    * ConvertToRoot below will append the root share name, so we need to
1710
 
    * make room for it in our buffer.
1711
 
    */
1712
 
   rpcMessageSize = rpcCommandSize + 1 +
1713
 
                    gFileRootSize + 1 +
1714
 
                    HGFS_STR_LEN(HGFS_SERVER_POLICY_ROOT_SHARE_NAME) + 1 +
1715
 
                    gFileRootSize + 1;
1716
 
 
1717
 
   rpcMessage = Util_SafeCalloc(1, rpcMessageSize);
1718
 
   memcpy(rpcMessage, rpcCmd, rpcCommandSize);
1719
 
   rpcMessage[rpcCommandSize] = ' ';
1720
 
   cur = rpcMessage + rpcCommandSize + 1;
1721
 
 
1722
 
   memcpy(cur, gFileRoot, gFileRootSize);
1723
 
   cur += gFileRootSize;
1724
 
   *cur = '\0';
1725
 
   cur++;
1726
 
 
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),
1731
 
                                         cur);
1732
 
   if (cpNameSize < 0) {
1733
 
      Debug("DnDSendVmxNewFileRoot: Could not convert file root to CPName\n");
1734
 
      free(rpcMessage);
1735
 
      return FALSE;
1736
 
   }
1737
 
 
1738
 
   /* Readjust message size for actual length */
1739
 
   rpcMessageSize = rpcCommandSize + 1 +
1740
 
                    gFileRootSize + 1 +
1741
 
                    cpNameSize + 1;
1742
 
 
1743
 
   Debug("DnDSendVmxNewFileRoot: sending root [%s] (%d)\n",
1744
 
         CPName_Print(rpcMessage, rpcMessageSize), rpcMessageSize);
1745
 
 
1746
 
   /*
1747
 
    * We must use RpcOut_SendOneRaw() here since RpcOut_sendOne() assumes a
1748
 
    * string and we are using CPName format.
1749
 
    */
1750
 
   if (!RpcOut_SendOneRaw(rpcMessage, rpcMessageSize, NULL, NULL)) {
1751
 
      Debug("DnDSendVmxNewFileRoot: Failed to send %s message to host\n", rpcCmd);
1752
 
      free(rpcMessage);
1753
 
      return FALSE;
1754
 
   }
1755
 
 
1756
 
   free(rpcMessage);
1757
 
   return TRUE;
1758
 
}
1759
 
 
1760
 
 
1761
 
/*
1762
 
 *----------------------------------------------------------------------------
1763
 
 *
1764
 
 * DnDFakeXEvents --
1765
 
 *
1766
 
 *    Fake X mouse events and window movement for the provided Gtk widget.
1767
 
 *
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.
1771
 
 *
1772
 
 *
1773
 
 * Results:
1774
 
 *    TRUE on success, FALSE on failure.
1775
 
 *
1776
 
 * Side effects:
1777
 
 *    Other X events should be generated from those faked here.
1778
 
 *
1779
 
 *----------------------------------------------------------------------------
1780
 
 */
1781
 
 
1782
 
static Bool
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
1791
 
{
1792
 
   Window rootWnd;
1793
 
   Bool ret;
1794
 
   Display *dndXDisplay;
1795
 
   Window dndXWindow;
1796
 
 
1797
 
   ASSERT(widget);
1798
 
 
1799
 
   dndXDisplay = GDK_WINDOW_XDISPLAY(widget->window);
1800
 
   dndXWindow = GDK_WINDOW_XWINDOW(widget->window);
1801
 
 
1802
 
   /*
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
1806
 
    * happen.
1807
 
    */
1808
 
   XSynchronize(dndXDisplay, True);
1809
 
 
1810
 
   if (showWidget) {
1811
 
      Debug("DnDFakeXEvents: showing Gtk widget\n");
1812
 
      gtk_widget_show(widget);
1813
 
      gdk_window_show(widget->window);
1814
 
   }
1815
 
 
1816
 
   /* Get the current location of the mouse if coordinates weren't provided. */
1817
 
   if (!coordsProvided) {
1818
 
      Window rootReturn;
1819
 
      Window childReturn;
1820
 
      int rootXReturn;
1821
 
      int rootYReturn;
1822
 
      int winXReturn;
1823
 
      int winYReturn;
1824
 
      unsigned int maskReturn;
1825
 
 
1826
 
      rootWnd = RootWindow(dndXDisplay, DefaultScreen(dndXDisplay));
1827
 
      ret = XQueryPointer(dndXDisplay, rootWnd, &rootReturn, &childReturn,
1828
 
                          &rootXReturn, &rootYReturn, &winXReturn, &winYReturn,
1829
 
                          &maskReturn);
1830
 
      if (ret == False) {
1831
 
         Warning("DnDFakeXEvents: XQueryPointer() returned False.\n");
1832
 
         XSynchronize(dndXDisplay, False);
1833
 
         return FALSE;
1834
 
      }
1835
 
 
1836
 
      Debug("DnDFakeXEvents: mouse is at (%d, %d)\n", rootXReturn, rootYReturn);
1837
 
 
1838
 
      xCoord = rootXReturn;
1839
 
      yCoord = rootYReturn;
1840
 
   }
1841
 
 
1842
 
   if (moveWindow) {
1843
 
      /*
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
1848
 
       * finish.
1849
 
       */
1850
 
      XMoveResizeWindow(dndXDisplay, dndXWindow, xCoord, yCoord, 25, 25);
1851
 
      XRaiseWindow(dndXDisplay, dndXWindow);
1852
 
   }
1853
 
 
1854
 
   /*
1855
 
    * Generate mouse movements over the window.  The second one makes ungrabs
1856
 
    * happen more reliably on KDE, but isn't necessary on GNOME.
1857
 
    */
1858
 
   XTestFakeMotionEvent(dndXDisplay, -1, xCoord, yCoord, CurrentTime);
1859
 
   XTestFakeMotionEvent(dndXDisplay, -1, xCoord + 1, yCoord + 1, CurrentTime);
1860
 
 
1861
 
   if (buttonEvent) {
1862
 
      Debug("DnDFakeXEvents: faking left mouse button %s\n",
1863
 
            buttonPress ? "press" : "release");
1864
 
      XTestFakeButtonEvent(dndXDisplay, 1, buttonPress, CurrentTime);
1865
 
   }
1866
 
 
1867
 
   XSynchronize(dndXDisplay, False);
1868
 
 
1869
 
   return TRUE;
1870
 
}
1871
 
 
1872
 
 
1873
 
/*
1874
 
 *----------------------------------------------------------------------------
1875
 
 *
1876
 
 * DnDSendEscapeKey --
1877
 
 *
1878
 
 *    Sends the escape key, canceling any pending drag and drop on the guest.
1879
 
 *
1880
 
 * Results:
1881
 
 *    None.
1882
 
 *
1883
 
 * Side effects:
1884
 
 *    None.
1885
 
 *
1886
 
 *----------------------------------------------------------------------------
1887
 
 */
1888
 
 
1889
 
static void
1890
 
DnDSendEscapeKey(GtkWidget *mainWnd)  // IN
1891
 
{
1892
 
   Display *dndXDisplay;
1893
 
   uint32 escKeycode;
1894
 
 
1895
 
   Debug("DnDRpcInFinishCB: faking ESC key press/release\n");
1896
 
 
1897
 
   dndXDisplay = GDK_WINDOW_XDISPLAY(mainWnd->window);
1898
 
   escKeycode = XKeysymToKeycode(dndXDisplay, XK_Escape);
1899
 
 
1900
 
   XTestFakeKeyEvent(dndXDisplay, escKeycode, TRUE, CurrentTime);
1901
 
   XTestFakeKeyEvent(dndXDisplay, escKeycode, FALSE, CurrentTime);
1902
 
}
1903
 
 
1904
 
 
1905
 
/*
1906
 
 *----------------------------------------------------------------------------
1907
 
 *
1908
 
 * DnDGHDragPending --
1909
 
 *
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.
1913
 
 *
1914
 
 * Results:
1915
 
 *    TRUE if a Drag operation is pending (waiting for a drop), FALSE
1916
 
 *    otherwise.
1917
 
 *
1918
 
 * Side effects:
1919
 
 *    None.
1920
 
 *
1921
 
 *----------------------------------------------------------------------------
1922
 
 */
1923
 
 
1924
 
static INLINE Bool
1925
 
DnDGHDragPending(GtkWidget *widget) // IN: our widget
1926
 
{
1927
 
   /* Xdnd is much more prevalent, so call it first */
1928
 
   return DnDGHXdndDragPending(widget) || DnDGHMotifDragPending(widget);
1929
 
}
1930
 
 
1931
 
 
1932
 
/*
1933
 
 *----------------------------------------------------------------------------
1934
 
 *
1935
 
 * DnDGHXdndDragPending --
1936
 
 *
1937
 
 *    Determines whether an Xdnd protocol drag is pending.
1938
 
 *
1939
 
 * Results:
1940
 
 *    TRUE is a drag is pending, FALSE otherwise.
1941
 
 *
1942
 
 * Side effects:
1943
 
 *    None.
1944
 
 *
1945
 
 *----------------------------------------------------------------------------
1946
 
 */
1947
 
 
1948
 
static INLINE Bool
1949
 
DnDGHXdndDragPending(GtkWidget *widget) // IN: our widget
1950
 
{
1951
 
   GdkAtom xDnDSelection;
1952
 
   Window owner;
1953
 
 
1954
 
   xDnDSelection = gdk_atom_intern("XdndSelection", TRUE);
1955
 
   if (xDnDSelection == None) {
1956
 
      Warning("DnDGHXdndDragPending: could not obtain Xdnd selection atom\n");
1957
 
      return FALSE;
1958
 
   }
1959
 
 
1960
 
   /*
1961
 
    * The XdndSelection atom will only have an owner if there is a drag in
1962
 
    * progress.
1963
 
    */
1964
 
   owner = XGetSelectionOwner(GDK_WINDOW_XDISPLAY(widget->window),
1965
 
                              GDKATOM_TO_ATOM(xDnDSelection));
1966
 
 
1967
 
   Debug("DnDGHXdndDragPending: an Xdnd drag is %spending\n",
1968
 
         owner != None ? "" : "not ");
1969
 
 
1970
 
   return owner != None;
1971
 
}
1972
 
 
1973
 
 
1974
 
/*
1975
 
 *----------------------------------------------------------------------------
1976
 
 *
1977
 
 * DnDGHXdndClearPending --
1978
 
 *
1979
 
 *    Clear the ownership of the XdndSelection selection atom that we use to
1980
 
 *    determine if a Xdnd drag is pending.
1981
 
 *
1982
 
 *    Note that this function should only be called when a DnD is not in
1983
 
 *    progress.
1984
 
 *
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).
1987
 
 *
1988
 
 * Results:
1989
 
 *    None.
1990
 
 *
1991
 
 * Side effects:
1992
 
 *    None.
1993
 
 *
1994
 
 *----------------------------------------------------------------------------
1995
 
 */
1996
 
 
1997
 
static INLINE void
1998
 
DnDGHXdndClearPending(GtkWidget *widget) // IN: program's widget
1999
 
{
2000
 
   GdkAtom xDnDSelection;
2001
 
 
2002
 
   ASSERT(!gGHState.dragInProgress);
2003
 
 
2004
 
   xDnDSelection = gdk_atom_intern("XdndSelection", TRUE);
2005
 
   if (xDnDSelection == None) {
2006
 
      return;
2007
 
   }
2008
 
 
2009
 
   /* Clear current owner by setting owner to None */
2010
 
   XSetSelectionOwner(GDK_WINDOW_XDISPLAY(widget->window),
2011
 
                      GDKATOM_TO_ATOM(xDnDSelection), None, CurrentTime);
2012
 
}
2013
 
 
2014
 
 
2015
 
/*
2016
 
 *----------------------------------------------------------------------------
2017
 
 *
2018
 
 * DnDMotifDragPending --
2019
 
 *
2020
 
 *    Determines whether a Motif protocol drag is pending.
2021
 
 *
2022
 
 *    XXX This has not yet been tested (looking for an app that actually uses
2023
 
 *    the Motif protocol)
2024
 
 *
2025
 
 * Results:
2026
 
 *    TRUE if a drag is pending, FALSE otherwise.
2027
 
 *
2028
 
 * Side effects:
2029
 
 *    None.
2030
 
 *
2031
 
 *----------------------------------------------------------------------------
2032
 
 */
2033
 
 
2034
 
static INLINE Bool
2035
 
DnDGHMotifDragPending(GtkWidget *widget) // IN: our widget
2036
 
{
2037
 
   GdkAtom motifDragWindow;
2038
 
   Display *dndXDisplay;
2039
 
   int ret;
2040
 
   Window rootXWindow;
2041
 
   Atom type;
2042
 
   int format;
2043
 
   unsigned long nitems;
2044
 
   unsigned long bytesAfter;
2045
 
   unsigned char *prop;
2046
 
 
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");
2051
 
      return FALSE;
2052
 
   }
2053
 
 
2054
 
   dndXDisplay = GDK_WINDOW_XDISPLAY(widget->window);
2055
 
   rootXWindow = RootWindow(dndXDisplay, DefaultScreen(dndXDisplay));
2056
 
 
2057
 
   /*
2058
 
    * Try and get the Motif drag window property from X's root window.  If one
2059
 
    * is provided, a DnD is pending.
2060
 
    */
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");
2066
 
      return FALSE;
2067
 
   }
2068
 
 
2069
 
   if (type == None) {
2070
 
      Debug("DnDGHXdndDragPending: a Motif drag is not pending\n");
2071
 
      return FALSE;
2072
 
   }
2073
 
 
2074
 
   Debug("DnDGHXdndDragPending: a Motif drag is pending\n");
2075
 
 
2076
 
   XFree(prop);
2077
 
   return TRUE;
2078
 
}
2079
 
 
2080
 
 
2081
 
/*
2082
 
 *----------------------------------------------------------------------------
2083
 
 *
2084
 
 * DnDGHFileListClear --
2085
 
 *
2086
 
 *    Clears existing Guest->Host file list, releasing any used resources.
2087
 
 *
2088
 
 * Results:
2089
 
 *    None.
2090
 
 *
2091
 
 * Side effects:
2092
 
 *    None.
2093
 
 *
2094
 
 *----------------------------------------------------------------------------
2095
 
 */
2096
 
 
2097
 
static INLINE void
2098
 
DnDGHFileListClear(void)
2099
 
{
2100
 
   Debug("DnDGHFileListClear: clearing G->H file list\n");
2101
 
   if (gGHState.dndFileList) {
2102
 
      free(gGHState.dndFileList);
2103
 
      gGHState.dndFileList = NULL;
2104
 
   }
2105
 
   gGHState.dndFileListSize = 0;
2106
 
   gGHState.dndFileListNext = NULL;
2107
 
}
2108
 
 
2109
 
 
2110
 
/*
2111
 
 *----------------------------------------------------------------------------
2112
 
 *
2113
 
 * DnDGHFileListSet --
2114
 
 *
2115
 
 *    Sets the Guest->Host file list that is accessed through
2116
 
 *    DnDGHFileListGetNext().
2117
 
 *
2118
 
 * Results:
2119
 
 *    None.
2120
 
 *
2121
 
 * Side effects:
2122
 
 *    Clears the existing Guest->Host file list if it exists.
2123
 
 *
2124
 
 *----------------------------------------------------------------------------
2125
 
 */
2126
 
 
2127
 
static INLINE void
2128
 
DnDGHFileListSet(char *fileList,      // IN: new Guest->Host file list
2129
 
                 size_t fileListSize) // IN: size of the provided list
2130
 
{
2131
 
   DnDGHFileListClear();
2132
 
   gGHState.dndFileList = fileList;
2133
 
   gGHState.dndFileListSize = fileListSize;
2134
 
   gGHState.dndFileListNext = fileList;
2135
 
 
2136
 
   Debug("DnDGHFileListSet: [%s] (%"FMTSZ"u)\n",
2137
 
         CPName_Print(gGHState.dndFileList, gGHState.dndFileListSize),
2138
 
         gGHState.dndFileListSize);
2139
 
}
2140
 
 
2141
 
 
2142
 
/*
2143
 
 *----------------------------------------------------------------------------
2144
 
 *
2145
 
 * DnDGHFileListGetNext --
2146
 
 *
2147
 
 *    Retrieves the next file in the Guest->Host file list.
2148
 
 *
2149
 
 *    Note that this function may only be called after calling
2150
 
 *    DnDGHFileListSet() and before calling DnDGHFileListClear().
2151
 
 *
2152
 
 * Results:
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.
2156
 
 *
2157
 
 * Side effects:
2158
 
 *    The fileListNext value of the Guest->Host global state is updated.
2159
 
 *
2160
 
 *----------------------------------------------------------------------------
2161
 
 */
2162
 
 
2163
 
static INLINE Bool
2164
 
DnDGHFileListGetNext(char **fileName,       // OUT: fill with filename location
2165
 
                     size_t *fileNameSize)  // OUT: fill with filename length
2166
 
{
2167
 
   char const *end;
2168
 
   char const *next;
2169
 
   int len;
2170
 
 
2171
 
   ASSERT(gGHState.dndFileList);
2172
 
   ASSERT(gGHState.dndFileListNext);
2173
 
   ASSERT(gGHState.dndFileListSize > 0);
2174
 
 
2175
 
   /* Ensure end is the last NUL character */
2176
 
   end = CPNameUtil_Strrchr(gGHState.dndFileList, gGHState.dndFileListSize, '\0');
2177
 
   ASSERT(end);
2178
 
 
2179
 
   /* Get the length of this filename and a pointer to the next one */
2180
 
   len = CPName_GetComponent(gGHState.dndFileListNext, end, &next);
2181
 
   if (len < 0) {
2182
 
      Warning("DnDGHFileListGetNext: error retrieving next component\n");
2183
 
      return FALSE;
2184
 
   }
2185
 
 
2186
 
   /* No more entries in the list */
2187
 
   if (len == 0) {
2188
 
      Debug("DnDGHFileListGetNext: no more entries\n");
2189
 
      *fileName = NULL;
2190
 
      *fileNameSize = 0;
2191
 
      return TRUE;
2192
 
   }
2193
 
 
2194
 
   Debug("DnDGHFileListGetNext: returning [%s] (%d)\n",
2195
 
         gGHState.dndFileListNext, len);
2196
 
 
2197
 
   *fileName = gGHState.dndFileListNext;
2198
 
   *fileNameSize = len;
2199
 
   gGHState.dndFileListNext = (char *)next;
2200
 
   return TRUE;
2201
 
}
2202
 
 
2203
 
 
2204
 
/*
2205
 
 *----------------------------------------------------------------------------
2206
 
 *
2207
 
 * DnDGHStateInit --
2208
 
 *
2209
 
 *    Initializes the Guest->Host DnD state.
2210
 
 *
2211
 
 * Results:
2212
 
 *    None.
2213
 
 *
2214
 
 * Side effects:
2215
 
 *    None.
2216
 
 *
2217
 
 *----------------------------------------------------------------------------
2218
 
 */
2219
 
 
2220
 
static INLINE void
2221
 
DnDGHStateInit(GtkWidget *widget)  // IN
2222
 
{
2223
 
   Debug("DnDGHStateInit: initializing guest->host state\n");
2224
 
   gGHState.time = 0;
2225
 
   gGHState.dragContext = NULL;
2226
 
   gGHState.dragInProgress = FALSE;
2227
 
   gGHState.ungrabReceived = FALSE;
2228
 
   gGHState.event = NULL;
2229
 
   DnDGHXdndClearPending(widget);
2230
 
   if (!gUnity) {
2231
 
      gtk_widget_hide(widget);
2232
 
   }
2233
 
}
2234
 
 
2235
 
 
2236
 
/*
2237
 
 *----------------------------------------------------------------------------
2238
 
 *
2239
 
 * DnDHGStateInit --
2240
 
 *
2241
 
 *    Initialize the Host->Guest DnD state.
2242
 
 *
2243
 
 * Results:
2244
 
 *    None.
2245
 
 *
2246
 
 * Side effects:
2247
 
 *    None.
2248
 
 *
2249
 
 *----------------------------------------------------------------------------
2250
 
 */
2251
 
 
2252
 
static INLINE void
2253
 
DnDHGStateInit(void)
2254
 
{
2255
 
   gHGDnDInProgress = FALSE;
2256
 
   gDoneDragging = FALSE;
2257
 
}
2258
 
 
2259
 
 
2260
 
/*
2261
 
 *----------------------------------------------------------------------------
2262
 
 *
2263
 
 * DnDGHCancel --
2264
 
 *
2265
 
 *    Resets state and sends a DnD cancel message to the host.
2266
 
 *
2267
 
 * Results:
2268
 
 *    TRUE on success, FALSE on failure.
2269
 
 *
2270
 
 * Side effects:
2271
 
 *    DnD operation is cancelled.
2272
 
 *
2273
 
 *----------------------------------------------------------------------------
2274
 
 */
2275
 
 
2276
 
static INLINE Bool
2277
 
DnDGHCancel(GtkWidget *widget) // IN: program's widget
2278
 
{
2279
 
   /* Hide our widget so we don't receive stray signals */
2280
 
   if (widget && !gUnity) {
2281
 
      gtk_widget_hide(widget);
2282
 
   }
2283
 
 
2284
 
   if (gGHState.dragContext) {
2285
 
      gdk_drag_status(gGHState.dragContext, 0, gGHState.time);
2286
 
   }
2287
 
 
2288
 
   gGHState.dragInProgress = FALSE;
2289
 
 
2290
 
   /*
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.
2293
 
    */
2294
 
   return RpcOut_sendOne(NULL, NULL, "dnd.finish cancel");
2295
 
}
2296
 
 
2297
 
 
2298
 
/*
2299
 
 *----------------------------------------------------------------------------
2300
 
 *
2301
 
 * DnDGHXEventTimeout --
2302
 
 *
2303
 
 *    Cleans up after fake X events do not cause intended events.  Hides the
2304
 
 *    provided widget and resets all Guest->Host DnD state.
2305
 
 *
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).
2309
 
 *
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.
2313
 
 *
2314
 
 * Results:
2315
 
 *    TRUE always, so the event manager doesn't stop running.
2316
 
 *
2317
 
 * Side effects:
2318
 
 *    None.
2319
 
 *
2320
 
 *----------------------------------------------------------------------------
2321
 
 */
2322
 
 
2323
 
static Bool
2324
 
DnDGHXEventTimeout(void *clientData) // IN: our widget
2325
 
{
2326
 
   GtkWidget *widget = (GtkWidget *)clientData;
2327
 
 
2328
 
   Debug("DnDGHXEventTimeout time out \n");
2329
 
 
2330
 
   RpcOut_sendOne(NULL, NULL, "dnd.notpending");
2331
 
 
2332
 
   if (!gGHState.dragInProgress && !gUnity) {
2333
 
      gtk_widget_hide(widget);
2334
 
   }
2335
 
 
2336
 
   /* gGHState.event is cleared with the rest of Guest->Host state */
2337
 
   DnDGHStateInit(widget);
2338
 
 
2339
 
   return TRUE;
2340
 
}
2341
 
 
2342
 
 
2343
 
/*
2344
 
 * Public functions invoked by the rest of vmware-user
2345
 
 */
2346
 
 
2347
 
/*
2348
 
 *-----------------------------------------------------------------------------
2349
 
 *
2350
 
 * DnD_GetVmxDnDVersion --
2351
 
 *
2352
 
 *      Ask the vmx for it's dnd version.
2353
 
 *
2354
 
 * Results:
2355
 
 *      The dnd version the vmx supports, 0 if the vmx doesn't know
2356
 
 *      what we're talking about.
2357
 
 *
2358
 
 * Side effects:
2359
 
 *      None
2360
 
 *
2361
 
 *-----------------------------------------------------------------------------
2362
 
 */
2363
 
 
2364
 
uint32
2365
 
DnD_GetVmxDnDVersion(void)
2366
 
{
2367
 
   char *reply = NULL;
2368
 
   size_t replyLen;
2369
 
   uint32 vmxVersion;
2370
 
 
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");
2374
 
      vmxVersion = 0;
2375
 
   } else {
2376
 
      vmxVersion = atoi(reply);
2377
 
      ASSERT(vmxVersion > 1);      /* DnD versions start at 2 */
2378
 
   }
2379
 
 
2380
 
   free(reply);
2381
 
   return vmxVersion;
2382
 
}
2383
 
 
2384
 
 
2385
 
/*
2386
 
 *-----------------------------------------------------------------------------
2387
 
 *
2388
 
 * DnD_RegisterCapability --
2389
 
 *
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.
2392
 
 *
2393
 
 * Results:
2394
 
 *      TRUE on success
2395
 
 *      FALSE on failure
2396
 
 *
2397
 
 * Side effects:
2398
 
 *      None
2399
 
 *
2400
 
 *-----------------------------------------------------------------------------
2401
 
 */
2402
 
 
2403
 
Bool
2404
 
DnD_RegisterCapability(void)
2405
 
{
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");
2409
 
      return FALSE;
2410
 
   } else if (!DnDSendVmxNewFileRoot("dnd.ready enable")) {
2411
 
      Debug("DnD_RegisterCapability: failed to send dnd.ready message to host\n");
2412
 
      return FALSE;
2413
 
   }
2414
 
   return TRUE;
2415
 
}
2416
 
 
2417
 
 
2418
 
/*
2419
 
 *-----------------------------------------------------------------------------
2420
 
 *
2421
 
 * DnD_Register --
2422
 
 *
2423
 
 *      Register the DnD capability, setup callbacks, initialize.
2424
 
 *
2425
 
 * Results:
2426
 
 *      TRUE on success, FALSE otherwise.
2427
 
 *
2428
 
 * Side effects:
2429
 
 *      mainWnd will be a dragSource in the guest, and dnd will work from
2430
 
 *      host to guest.
2431
 
 *
2432
 
 *-----------------------------------------------------------------------------
2433
 
 */
2434
 
 
2435
 
Bool
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.
2438
 
{
2439
 
   uint32 i;
2440
 
 
2441
 
   gDragCtx = NULL;
2442
 
 
2443
 
   ASSERT(hgWnd);
2444
 
   ASSERT(ghWnd);
2445
 
 
2446
 
   if (DnD_GetVmxDnDVersion() < 2) {
2447
 
      goto error;
2448
 
   }
2449
 
 
2450
 
   /*
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.
2454
 
    */
2455
 
   if (!XTestQueryExtension(GDK_WINDOW_XDISPLAY(hgWnd->window),
2456
 
                            &i, &i, &i, &i)) {
2457
 
      goto error;
2458
 
   }
2459
 
 
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,
2466
 
                          hgWnd);
2467
 
 
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);
2475
 
 
2476
 
   /*
2477
 
    * Setup mainWnd as a DND source/dest.
2478
 
    *
2479
 
    * Note that G->H drag targets should come first in this array.  Currently
2480
 
    * G->H only supports text/uri-list targets.
2481
 
    */
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;
2491
 
 
2492
 
   /* Populate our GdkAtom table for our supported Guest->Host targets */
2493
 
   for (i = 0;
2494
 
        i < ARRAYSIZE(gTargetEntry) && i < ARRAYSIZE(gTargetEntryAtom);
2495
 
        i++) {
2496
 
      gTargetEntryAtom[i] = gdk_atom_intern(gTargetEntry[i].target, FALSE);
2497
 
   }
2498
 
 
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);
2503
 
 
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);
2510
 
 
2511
 
 
2512
 
   /*
2513
 
    * Drop target (destination) for Guest->Host
2514
 
    *
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.
2517
 
    */
2518
 
   gtk_drag_dest_set(ghWnd,
2519
 
                     GTK_DEST_DEFAULT_MOTION,
2520
 
                     gTargetEntry, NR_GH_DRAG_TARGETS,
2521
 
                     GDK_ACTION_COPY | GDK_ACTION_MOVE);
2522
 
 
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),
2527
 
                      ghWnd);
2528
 
   gtk_signal_connect(GTK_OBJECT(ghWnd), "drag_drop",
2529
 
                      GTK_SIGNAL_FUNC(DnDGtkDragDropCB), ghWnd);
2530
 
 
2531
 
   DnD_OnReset(hgWnd, ghWnd);
2532
 
 
2533
 
   if (DnD_RegisterCapability()) {
2534
 
      return TRUE;
2535
 
   }
2536
 
 
2537
 
   /*
2538
 
    * We get here if DnD registration fails for some reason
2539
 
    */
2540
 
error:
2541
 
   DnD_Unregister(hgWnd, ghWnd);
2542
 
   return FALSE;
2543
 
}
2544
 
 
2545
 
 
2546
 
/*
2547
 
 *-----------------------------------------------------------------------------
2548
 
 *
2549
 
 * DnD_Unregister --
2550
 
 *
2551
 
 *      Cleanup dnd related things.
2552
 
 *
2553
 
 * Results:
2554
 
 *      None.
2555
 
 *
2556
 
 * Side effects:
2557
 
 *      DnD is stopped, the rpc channel to the vmx is closed.
2558
 
 *
2559
 
 *-----------------------------------------------------------------------------
2560
 
 */
2561
 
 
2562
 
void
2563
 
DnD_Unregister(GtkWidget *hgWnd,        // IN: The widget for hg dnd
2564
 
               GtkWidget *ghWnd)        // IN: The widget for gh dnd
2565
 
{
2566
 
   RpcOut_sendOne(NULL, NULL, "dnd.ready disable");
2567
 
 
2568
 
   DnDGHFileListClear();
2569
 
 
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),
2574
 
                                 hgWnd);
2575
 
   gtk_signal_disconnect_by_func(GTK_OBJECT(hgWnd),
2576
 
                                 GTK_SIGNAL_FUNC(DnDGtkEndCB),
2577
 
                                 hgWnd);
2578
 
   gtk_signal_disconnect_by_func(GTK_OBJECT(hgWnd),
2579
 
                                 GTK_SIGNAL_FUNC(DnDGtkDataRequestCB),
2580
 
                                 hgWnd);
2581
 
 
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),
2586
 
                                 ghWnd);
2587
 
   gtk_signal_disconnect_by_func(GTK_OBJECT(ghWnd),
2588
 
                                 GTK_SIGNAL_FUNC(DnDGtkDragDataReceivedCB),
2589
 
                                 ghWnd);
2590
 
   gtk_signal_disconnect_by_func(GTK_OBJECT(ghWnd),
2591
 
                                 GTK_SIGNAL_FUNC(DnDGtkDragDropCB),
2592
 
                                 ghWnd);
2593
 
 
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");
2599
 
 
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");
2604
 
}
2605
 
 
2606
 
 
2607
 
/*
2608
 
 *----------------------------------------------------------------------------
2609
 
 *
2610
 
 * DnD_OnReset --
2611
 
 *
2612
 
 *    Handles reinitializing DnD state on a reset.
2613
 
 *
2614
 
 * Results:
2615
 
 *    None.
2616
 
 *
2617
 
 * Side effects:
2618
 
 *    DnD is stopped and restarted.
2619
 
 *
2620
 
 *----------------------------------------------------------------------------
2621
 
 */
2622
 
 
2623
 
void
2624
 
DnD_OnReset(GtkWidget *hgWnd,   // IN: The widget for hg dnd
2625
 
            GtkWidget *ghWnd)   // IN: The widget for gh dnd
2626
 
 
2627
 
{
2628
 
   Debug("DnD_OnReset: entry\n");
2629
 
 
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",
2636
 
                 gFileRoot);
2637
 
      }
2638
 
   }
2639
 
 
2640
 
   /*
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.
2643
 
    */
2644
 
   if (gHGDnDInProgress) {
2645
 
      Debug("DnD_OnReset: sending hgWnd escape\n");
2646
 
      DnDSendEscapeKey(hgWnd);
2647
 
   }
2648
 
 
2649
 
   if (gGHState.dragInProgress) {
2650
 
      Debug("DnD_OnReset: sending ghWnd escape\n");
2651
 
      DnDSendEscapeKey(ghWnd);
2652
 
   }
2653
 
 
2654
 
   if (gGHState.dragInProgress) {
2655
 
      Debug("DnD_OnReset: canceling host->guest DnD\n");
2656
 
      DnDGHCancel(ghWnd);
2657
 
   }
2658
 
 
2659
 
   /* Reset DnD state. */
2660
 
   DnDHGStateInit();
2661
 
   DnDGHStateInit(ghWnd);
2662
 
   DnDGHFileListClear();
2663
 
   DnD_SetMode(FALSE);
2664
 
}
2665
 
 
2666
 
 
2667
 
/*
2668
 
 *----------------------------------------------------------------------------
2669
 
 *
2670
 
 * DnD_InProgress --
2671
 
 *
2672
 
 *    Indicates whether a DnD (or its data transfer) is currently in progress.
2673
 
 *
2674
 
 * Results:
2675
 
 *    TRUE if a DnD is in progress, FALSE otherwise.
2676
 
 *
2677
 
 * Side effects:
2678
 
 *    None.
2679
 
 *
2680
 
 *----------------------------------------------------------------------------
2681
 
 */
2682
 
 
2683
 
Bool
2684
 
DnD_InProgress(void)
2685
 
{
2686
 
   return gGHState.dragInProgress || gHGDnDInProgress || gHGDataPending;
2687
 
}
2688
 
 
2689
 
 
2690
 
/*
2691
 
 *----------------------------------------------------------------------------
2692
 
 *
2693
 
 * DnD_SetMode --
2694
 
 *
2695
 
 *      Sets dnd mode to single window or unity mode.
2696
 
 *
2697
 
 * Results:
2698
 
 *      None.
2699
 
 *
2700
 
 * Side effects:
2701
 
 *      Controls if the g->h det window is automatically hidden.
2702
 
 *
2703
 
 *----------------------------------------------------------------------------
2704
 
 */
2705
 
 
2706
 
void
2707
 
DnD_SetMode(Bool unity) // IN
2708
 
{
2709
 
   gUnity = unity;
2710
 
}