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

« back to all changes in this revision

Viewing changes to vmware-user/dndUI.cpp

  • 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) 2009 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
 
 * @file dndUI.cpp --
21
 
 *
22
 
 * This class implements stubs for the methods that allow DnD between
23
 
 * host and guest.
24
 
 */
25
 
 
26
 
#include "dndUI.h"
27
 
 
28
 
extern "C" {
29
 
#include "vmwareuserInt.h"
30
 
#include "vmblock.h"
31
 
#include "file.h"
32
 
#include "dnd.h"
33
 
#include "dndMsg.h"
34
 
#include "dndClipboard.h"
35
 
#include "cpName.h"
36
 
#include "debug.h"
37
 
#include "cpNameUtil.h"
38
 
#include "hostinfo.h"
39
 
#include "rpcout.h"
40
 
#include "eventManager.h"
41
 
#include "unity.h"
42
 
#include <gtk/gtk.h>
43
 
#include <X11/extensions/XTest.h>       /* for XTest*() */
44
 
#include "vmware/guestrpc/tclodefs.h"
45
 
}
46
 
 
47
 
#include "dndGuest.h"
48
 
#include "copyPasteDnDWrapper.h"
49
 
 
50
 
/**
51
 
 *
52
 
 * Constructor.
53
 
 */
54
 
 
55
 
DnDUI::DnDUI(DblLnkLst_Links *eventQueue)
56
 
    : m_eventQueue(eventQueue),
57
 
      m_DnD(NULL),
58
 
      m_detWnd(NULL),
59
 
      m_blockCtrl(NULL),
60
 
      m_HGGetDataInProgress(false),
61
 
      m_blockAdded(false),
62
 
      m_GHDnDInProgress(false),
63
 
      m_GHDnDDataReceived(false),
64
 
      m_unityMode(false),
65
 
      m_inHGDrag(false),
66
 
      m_effect(DROP_NONE),
67
 
      m_isFileDnD(false),
68
 
      m_mousePosX(0),
69
 
      m_mousePosY(0),
70
 
      m_dc(NULL),
71
 
      m_destDropTime(0)
72
 
{
73
 
   Debug("%s: enter\n", __FUNCTION__);
74
 
}
75
 
 
76
 
 
77
 
/**
78
 
 *
79
 
 * Destructor.
80
 
 */
81
 
 
82
 
DnDUI::~DnDUI()
83
 
{
84
 
   Debug("%s: enter\n", __FUNCTION__);
85
 
   if (m_DnD) {
86
 
      delete m_DnD;
87
 
   }
88
 
   if (m_detWnd) {
89
 
      delete m_detWnd;
90
 
   }
91
 
   CPClipboard_Destroy(&m_clipboard);
92
 
}
93
 
 
94
 
 
95
 
/**
96
 
 *
97
 
 * Initialize DnDUI object.
98
 
 */
99
 
 
100
 
bool
101
 
DnDUI::Init()
102
 
{
103
 
   Debug("%s: enter\n", __FUNCTION__);
104
 
   char *reply = NULL;
105
 
   size_t replyLen;
106
 
   bool ret = true;
107
 
 
108
 
   ASSERT(m_eventQueue);
109
 
   CPClipboard_Init(&m_clipboard);
110
 
   m_DnD = new DnD(m_eventQueue);
111
 
   if (!m_DnD) {
112
 
      Debug("%s: unable to allocate DnD object\n", __FUNCTION__);
113
 
      goto fail;
114
 
   }
115
 
   m_detWnd = new DragDetWnd();
116
 
   if (!m_detWnd) {
117
 
      Debug("%s: unable to allocate DragDetWnd object\n", __FUNCTION__);
118
 
      goto fail;
119
 
   }
120
 
 
121
 
#if defined(DETWNDDEBUG)
122
 
 
123
 
   /*
124
 
    * This code can only be called when DragDetWnd is derived from
125
 
    * Gtk::Window. The normal case is that DragDetWnd is an instance of
126
 
    * Gtk::Invisible, which doesn't implement the methods that SetAttributes
127
 
    * relies upon.
128
 
    */
129
 
 
130
 
   m_detWnd->SetAttributes();
131
 
#endif
132
 
 
133
 
   SetTargetsAndCallbacks();
134
 
 
135
 
   /* Exchange dnd version information with the VMX */
136
 
   if (!RpcOut_sendOne(NULL, NULL, "tools.capability.dnd_version 3")) {
137
 
      Debug("%s: could not set guest dnd version capability\n", __FUNCTION__);
138
 
      goto fail;
139
 
   } else {
140
 
      if (!RpcOut_sendOne(&reply, &replyLen, "vmx.capability.dnd_version")) {
141
 
         Debug("%s: could not get VMX dnd version capability\n",
142
 
               __FUNCTION__);
143
 
         goto fail;
144
 
      } else if (atoi(reply) < 3) {
145
 
         Debug("%s: VMX DnD version is less than 3.\n", __FUNCTION__);
146
 
         goto fail;
147
 
      }
148
 
   }
149
 
 
150
 
   Debug("%s: VMX version ok: %d\n", __FUNCTION__, atoi(reply));
151
 
 
152
 
   /* Set common layer callbacks. */
153
 
   m_DnD->dragStartChanged.connect(
154
 
      sigc::mem_fun(this, &DnDUI::CommonDragStartCB));
155
 
   m_DnD->fileCopyDoneChanged.connect(
156
 
      sigc::mem_fun(this, &DnDUI::CommonSourceFileCopyDoneCB));
157
 
   m_DnD->updateDetWndChanged.connect(
158
 
      sigc::mem_fun(this, &DnDUI::CommonUpdateDetWndCB));
159
 
   m_DnD->updateUnityDetWndChanged.connect(
160
 
      sigc::mem_fun(this, &DnDUI::CommonUpdateUnityDetWndCB));
161
 
   m_DnD->moveDetWndToMousePos.connect(
162
 
      sigc::mem_fun(this, &DnDUI::CommonMoveDetWndToMousePos));
163
 
   m_DnD->sourceCancelChanged.connect(
164
 
      sigc::mem_fun(this, &DnDUI::CommonSourceCancelCB));
165
 
   m_DnD->targetPrivateDropChanged.connect(
166
 
      sigc::mem_fun(this, &DnDUI::CommonDestPrivateDropCB));
167
 
   m_DnD->ghCancel.connect(
168
 
      sigc::mem_fun(this, &DnDUI::CommonDestCancelCB));
169
 
   m_DnD->sourceDropChanged.connect(
170
 
      sigc::mem_fun(this, &DnDUI::CommonSourceDropCB));
171
 
   m_DnD->updateMouseChanged.connect(
172
 
      sigc::mem_fun(this, &DnDUI::CommonUpdateMouseCB));
173
 
 
174
 
   /* Set Gtk+ callbacks for source. */
175
 
   m_detWnd->signal_drag_begin().connect(
176
 
      sigc::mem_fun(this, &DnDUI::GtkSourceDragBeginCB));
177
 
   m_detWnd->signal_drag_data_get().connect(
178
 
      sigc::mem_fun(this, &DnDUI::GtkSourceDragDataGetCB));
179
 
   m_detWnd->signal_drag_end().connect(
180
 
      sigc::mem_fun(this, &DnDUI::GtkSourceDragEndCB));
181
 
 
182
 
   CommonUpdateDetWndCB(false, 0, 0);
183
 
   CommonUpdateUnityDetWndCB(false, 0, false);
184
 
   goto out;
185
 
fail:
186
 
   ret = false;
187
 
   if (m_DnD) {
188
 
      delete m_DnD;
189
 
      m_DnD = NULL;
190
 
   }
191
 
   if (m_detWnd) {
192
 
      delete m_detWnd;
193
 
      m_detWnd = NULL;
194
 
   }
195
 
out:
196
 
   if (reply) {
197
 
      free(reply);
198
 
   }
199
 
   return ret;
200
 
}
201
 
 
202
 
 
203
 
/**
204
 
 *
205
 
 * Setup targets we support, claim ourselves as a drag destination, and
206
 
 * register callbacks for Gtk+ drag and drop callbacks the platform will
207
 
 * send to us.
208
 
 */
209
 
 
210
 
void
211
 
DnDUI::SetTargetsAndCallbacks()
212
 
{
213
 
   Debug("%s: enter\n", __FUNCTION__);
214
 
 
215
 
   /* Construct supported target list for HG DnD. */
216
 
   std::list<Gtk::TargetEntry> targets;
217
 
 
218
 
   /* File DnD. */
219
 
   targets.push_back(Gtk::TargetEntry(DRAG_TARGET_NAME_URI_LIST));
220
 
 
221
 
   /* RTF text DnD. */
222
 
   targets.push_back(Gtk::TargetEntry(TARGET_NAME_APPLICATION_RTF));
223
 
   targets.push_back(Gtk::TargetEntry(TARGET_NAME_TEXT_RICHTEXT));
224
 
 
225
 
   /* Plain text DnD. */
226
 
   targets.push_back(Gtk::TargetEntry(TARGET_NAME_STRING));
227
 
   targets.push_back(Gtk::TargetEntry(TARGET_NAME_TEXT_PLAIN));
228
 
   targets.push_back(Gtk::TargetEntry(TARGET_NAME_UTF8_STRING));
229
 
   targets.push_back(Gtk::TargetEntry(TARGET_NAME_COMPOUND_TEXT));
230
 
 
231
 
   /*
232
 
    * We don't want Gtk handling any signals for us, we want to
233
 
    * do it ourselves based on the results from the guest.
234
 
    *
235
 
    * Second argument in drag_dest_set defines the automatic behaviour options
236
 
    * of the destination widget. We used to not define it (0) and in some
237
 
    * distributions (like Ubuntu 6.10) DragMotion only get called once,
238
 
    * and not send updated mouse position to guest, and also got cancel
239
 
    * signal when user drop the file (bug 175754). With flag DEST_DEFAULT_MOTION
240
 
    * the bug is fixed. Almost all other example codes use DEST_DEFAULT_ALL
241
 
    * but in our case, we will call drag_get_data during DragMotion, and
242
 
    * will cause X dead with DEST_DEFAULT_ALL. The reason is unclear.
243
 
    */
244
 
   m_detWnd->drag_dest_set(targets, Gtk::DEST_DEFAULT_MOTION,
245
 
                           Gdk::ACTION_COPY | Gdk::ACTION_MOVE);
246
 
   m_detWnd->signal_drag_leave().connect(sigc::mem_fun(this, &DnDUI::GtkDestDragLeaveCB));
247
 
   m_detWnd->signal_drag_motion().connect(sigc::mem_fun(this, &DnDUI::GtkDestDragMotionCB));
248
 
   m_detWnd->signal_drag_drop().connect(sigc::mem_fun(this, &DnDUI::GtkDestDragDropCB));
249
 
   m_detWnd->signal_drag_data_received().connect(sigc::mem_fun(this, &DnDUI::GtkDestDragDataReceivedCB));
250
 
}
251
 
 
252
 
/* Begin of callbacks issued by common layer code */
253
 
 
254
 
/**
255
 
 *
256
 
 * Reset Callback to reset dnd ui state.
257
 
 */
258
 
 
259
 
void
260
 
DnDUI::CommonResetCB(void)
261
 
{
262
 
   Debug("%s: entering\n", __FUNCTION__);
263
 
   m_GHDnDDataReceived = false;
264
 
   m_HGGetDataInProgress = false;
265
 
   m_GHDnDInProgress = false;
266
 
   m_effect = DROP_NONE;
267
 
   m_inHGDrag = false;
268
 
   m_dc = NULL;
269
 
   m_isFileDnD = false;
270
 
   RemoveBlock();
271
 
}
272
 
 
273
 
 
274
 
/**
275
 
 *
276
 
 * Cancel any DnD file transfers and reset.
277
 
 */
278
 
 
279
 
void
280
 
DnDUI::Cancel()
281
 
{
282
 
   Debug("%s: enter\n", __FUNCTION__);
283
 
   if (m_blockAdded) {
284
 
      /*
285
 
       * If we don't do this, the destination will have something to
286
 
       * copy, and likely truncated. So remove it.
287
 
       */
288
 
      DnD_DeleteStagingFiles(m_HGStagingDir.c_str(), false);
289
 
   }
290
 
   CommonResetCB();
291
 
}
292
 
 
293
 
 
294
 
/* Source functions for HG DnD. */
295
 
 
296
 
/**
297
 
 *
298
 
 * Called when host successfully detected a pending HG drag.
299
 
 *
300
 
 * param[in] clip cross-platform clipboard
301
 
 * param[in] stagingDir associated staging directory
302
 
 */
303
 
 
304
 
void
305
 
DnDUI::CommonDragStartCB(const CPClipboard *clip, std::string stagingDir)
306
 
{
307
 
   Glib::RefPtr<Gtk::TargetList> targets;
308
 
   Gdk::DragAction actions;
309
 
   GdkEventMotion event;
310
 
 
311
 
   CPClipboard_Clear(&m_clipboard);
312
 
   CPClipboard_Copy(&m_clipboard, clip);
313
 
 
314
 
   Debug("%s: enter\n", __FUNCTION__);
315
 
 
316
 
   /*
317
 
    * Before the DnD, we should make sure that the mouse is released
318
 
    * otherwise it may be another DnD, not ours. Send a release, then
319
 
    * a press here to cover this case.
320
 
    */
321
 
   SendFakeXEvents(false, true, false, false, false, 0, 0);
322
 
   SendFakeXEvents(true, true, true, false, true, 0, 0);
323
 
 
324
 
   /*
325
 
    * Construct the target and action list, as well as a fake motion notify
326
 
    * event that's consistent with one that would typically start a drag.
327
 
    */
328
 
   targets = Gtk::TargetList::create(std::list<Gtk::TargetEntry>());
329
 
 
330
 
   if (CPClipboard_ItemExists(&m_clipboard, CPFORMAT_FILELIST)) {
331
 
      m_HGStagingDir = stagingDir;
332
 
      if (!m_HGStagingDir.empty()) {
333
 
         targets->add(Glib::ustring(DRAG_TARGET_NAME_URI_LIST));
334
 
         /* Add private data to tag dnd as originating from this vm. */
335
 
         char *pid;
336
 
         Debug("%s: adding re-entrant drop target, pid %d\n", __FUNCTION__, getpid());
337
 
         pid = Str_Asprintf(NULL, "guest-dnd-target %d", static_cast<int>(getpid()));
338
 
         if (pid) {
339
 
            targets->add(Glib::ustring(pid));
340
 
            free(pid);
341
 
         }
342
 
      }
343
 
   }
344
 
 
345
 
   if (CPClipboard_ItemExists(&m_clipboard, CPFORMAT_FILECONTENTS)) {
346
 
      if (WriteFileContentsToStagingDir()) {
347
 
         targets->add(Glib::ustring(DRAG_TARGET_NAME_URI_LIST));
348
 
      }
349
 
   }
350
 
 
351
 
   if (CPClipboard_ItemExists(&m_clipboard, CPFORMAT_TEXT)) {
352
 
      targets->add(Glib::ustring(TARGET_NAME_STRING));
353
 
      targets->add(Glib::ustring(TARGET_NAME_TEXT_PLAIN));
354
 
      targets->add(Glib::ustring(TARGET_NAME_UTF8_STRING));
355
 
      targets->add(Glib::ustring(TARGET_NAME_COMPOUND_TEXT));
356
 
   }
357
 
 
358
 
   if (CPClipboard_ItemExists(&m_clipboard, CPFORMAT_RTF)) {
359
 
      targets->add(Glib::ustring(TARGET_NAME_APPLICATION_RTF));
360
 
      targets->add(Glib::ustring(TARGET_NAME_TEXT_RICHTEXT));
361
 
   }
362
 
 
363
 
   actions = Gdk::ACTION_COPY | Gdk::ACTION_MOVE;
364
 
 
365
 
   /* TODO set the x/y coords to the actual drag initialization point. */
366
 
   event.type = GDK_MOTION_NOTIFY;
367
 
   event.window = m_detWnd->get_window()->gobj();
368
 
   event.send_event = false;
369
 
   event.time = GDK_CURRENT_TIME;
370
 
   event.x = 10;
371
 
   event.y = 10;
372
 
   event.axes = NULL;
373
 
   event.state = GDK_BUTTON1_MASK;
374
 
   event.is_hint = 0;
375
 
   event.device = gdk_device_get_core_pointer();
376
 
   event.x_root = 0;
377
 
   event.y_root = 5;
378
 
 
379
 
   /* Tell Gtk that a drag should be started from this widget. */
380
 
   m_detWnd->drag_begin(targets, actions, 1, (GdkEvent *)&event);
381
 
   m_blockAdded = false;
382
 
   m_isFileDnD = false;
383
 
   SourceDragStartDone();
384
 
   /* Initialize host hide feedback to DROP_NONE. */
385
 
   m_effect = DROP_NONE;
386
 
   SourceUpdateFeedback(m_effect);
387
 
}
388
 
 
389
 
 
390
 
/**
391
 
 *
392
 
 * Cancel current HG DnD.
393
 
 */
394
 
 
395
 
void
396
 
DnDUI::CommonSourceCancelCB(void)
397
 
{
398
 
   Debug("%s: entering\n", __FUNCTION__);
399
 
 
400
 
   /*
401
 
    * Force the window to show, position the mouse over it, and release.
402
 
    * Seems like moving the window to 0, 0 eliminates frequently observed
403
 
    * flybacks when we cancel as user moves mouse in and out of destination
404
 
    * window in a H->G DnD.
405
 
    */
406
 
   CommonUpdateDetWndCB(true, 0, 0);
407
 
   SendFakeXEvents(true, true, false, true, true, 0, 0);
408
 
   CommonUpdateDetWndCB(false, 0, 0);
409
 
   m_inHGDrag = false;
410
 
   m_HGGetDataInProgress = false;
411
 
   m_effect = DROP_NONE;
412
 
   RemoveBlock();
413
 
}
414
 
 
415
 
 
416
 
/**
417
 
 *
418
 
 * Handle common layer private drop CB.
419
 
 *
420
 
 * @param[in] x position to release the mouse button (ignored).
421
 
 * @param[in] y position to release the mouse button (ignored).
422
 
 *
423
 
 * @note We ignore the coordinates, because we just need to release the mouse
424
 
 * in its current position.
425
 
 */
426
 
 
427
 
void
428
 
DnDUI::CommonDestPrivateDropCB(int32 x,
429
 
                               int32 y)
430
 
{
431
 
   Debug("%s: entering\n", __FUNCTION__);
432
 
   /* Unity manager in host side may already send the drop into guest. */
433
 
   if (m_GHDnDInProgress) {
434
 
 
435
 
      /*
436
 
       * Release the mouse button.
437
 
       */
438
 
      SendFakeXEvents(false, true, false, false, false, 0, 0);
439
 
   }
440
 
   CommonResetCB();
441
 
}
442
 
 
443
 
 
444
 
/**
445
 
 *
446
 
 * Cancel current DnD (G->H only).
447
 
 */
448
 
 
449
 
void
450
 
DnDUI::CommonDestCancelCB(void)
451
 
{
452
 
   Debug("%s: entering\n", __FUNCTION__);
453
 
   /* Unity manager in host side may already send the drop into guest. */
454
 
   if (m_GHDnDInProgress) {
455
 
      CommonUpdateDetWndCB(true, 0, 0);
456
 
 
457
 
      /*
458
 
       * Show the window, move it to the mouse position, and release the
459
 
       * mouse button.
460
 
       */
461
 
      SendFakeXEvents(true, true, false, true, false, 0, 0);
462
 
   }
463
 
   m_destDropTime = GetTimeInMillis();
464
 
   CommonResetCB();
465
 
}
466
 
 
467
 
 
468
 
/**
469
 
 *
470
 
 * Got drop from host side. Release the mouse button in the detection window
471
 
 */
472
 
 
473
 
void
474
 
DnDUI::CommonSourceDropCB(void)
475
 
{
476
 
   Debug("%s: enter\n", __FUNCTION__);
477
 
   CommonUpdateDetWndCB(true, 0, 0);
478
 
 
479
 
   /*
480
 
    * Move the mouse to the saved coordinates, and release the mouse button.
481
 
    */
482
 
   SendFakeXEvents(false, true, false, false, true, m_mousePosX, m_mousePosY);
483
 
   CommonUpdateDetWndCB(false, 0, 0);
484
 
}
485
 
 
486
 
 
487
 
/**
488
 
 *
489
 
 * Callback when file transfer is done, which finishes the file
490
 
 * copying from host to guest staging directory.
491
 
 *
492
 
 * @param[in] success if true, transfer was successful
493
 
 * @param[in] path of staging dir (which will have a block that needs removing)
494
 
 */
495
 
 
496
 
void
497
 
DnDUI::CommonSourceFileCopyDoneCB(bool success,
498
 
                                  std::vector<uint8> stagingDir)
499
 
{
500
 
   Debug("%s: %s\n", __FUNCTION__, success ? "success" : "failed");
501
 
   /* Copied files are already removed in common layer. */
502
 
   stagingDir.clear();
503
 
   CommonResetCB();
504
 
   m_HGGetDataInProgress = false;
505
 
}
506
 
 
507
 
 
508
 
/**
509
 
 *
510
 
 * Shows/hides drag detection windows based on the mask.
511
 
 *
512
 
 * @param[in] bShow if true, show the window, else hide it.
513
 
 * @param[in] x x-coordinate to which the detection window needs to be moved
514
 
 * @param[in] y y-coordinate to which the detection window needs to be moved
515
 
 */
516
 
 
517
 
void
518
 
DnDUI::CommonUpdateDetWndCB(bool bShow,
519
 
                            int32 x,
520
 
                            int32 y)
521
 
{
522
 
   Debug("%s: enter 0x%lx show %d x %d y %d\n",
523
 
         __FUNCTION__,
524
 
         (unsigned long) m_detWnd->get_window()->gobj(), bShow, x, y);
525
 
 
526
 
   /* If the window is being shown, move it to the right place. */
527
 
   if (bShow) {
528
 
      x = MAX(x - DRAG_DET_WINDOW_WIDTH / 2, 0);
529
 
      y = MAX(y - DRAG_DET_WINDOW_WIDTH / 2, 0);
530
 
 
531
 
      m_detWnd->Show();
532
 
      m_detWnd->Raise();
533
 
      m_detWnd->SetGeometry(x, y, DRAG_DET_WINDOW_WIDTH * 2, DRAG_DET_WINDOW_WIDTH * 2);
534
 
      Debug("%s: show at (%d, %d, %d, %d)\n", __FUNCTION__, x, y, DRAG_DET_WINDOW_WIDTH * 2, DRAG_DET_WINDOW_WIDTH * 2);
535
 
      /*
536
 
       * Wiggle the mouse here. Especially for G->H DnD, this improves
537
 
       * reliability of making the drag escape the guest window immensly.
538
 
       * Stolen from the legacy V2 DnD code.
539
 
       */
540
 
 
541
 
      SendFakeMouseMove(x, y);
542
 
      m_detWnd->SetIsVisible(true);
543
 
   } else {
544
 
      Debug("%s: hide\n", __FUNCTION__);
545
 
      m_detWnd->Hide();
546
 
      m_detWnd->SetIsVisible(false);
547
 
   }
548
 
}
549
 
 
550
 
 
551
 
/**
552
 
 *
553
 
 * Shows/hides full-screen Unity drag detection window.
554
 
 *
555
 
 * @param[in] bShow if true, show the window, else hide it.
556
 
 * @param[in] unityWndId active front window
557
 
 * @param[in] bottom if true, adjust the z-order to be bottom most.
558
 
 */
559
 
 
560
 
void
561
 
DnDUI::CommonUpdateUnityDetWndCB(bool bShow,
562
 
                                 uint32 unityWndId,
563
 
                                 bool bottom)
564
 
{
565
 
   Debug("%s: enter 0x%lx unityID 0x%x\n",
566
 
         __FUNCTION__,
567
 
         (unsigned long) m_detWnd->get_window()->gobj(),
568
 
         unityWndId);
569
 
   if (bShow && ((unityWndId > 0) || bottom)) {
570
 
      int width = m_detWnd->GetScreenWidth();
571
 
      int height = m_detWnd->GetScreenHeight();
572
 
      m_detWnd->SetGeometry(0, 0, width, height);
573
 
      m_detWnd->Show();
574
 
      if (bottom) {
575
 
         m_detWnd->Lower();
576
 
      }
577
 
 
578
 
      Debug("%s: show, (0, 0, %d, %d)\n", __FUNCTION__, width, height);
579
 
   } else {
580
 
      if (m_detWnd->GetIsVisible() == true) {
581
 
         if (m_unityMode) {
582
 
 
583
 
            /*
584
 
             * Show and move detection window to current mouse position
585
 
             * and resize.
586
 
             */
587
 
            SendFakeXEvents(true, false, true, true, false, 0, 0);
588
 
         }
589
 
      } else {
590
 
         m_detWnd->Hide();
591
 
         Debug("%s: hide\n", __FUNCTION__);
592
 
      }
593
 
   }
594
 
}
595
 
 
596
 
 
597
 
/**
598
 
 *
599
 
 * Move detection windows to current cursor position.
600
 
 */
601
 
 
602
 
void
603
 
DnDUI::CommonMoveDetWndToMousePos(void)
604
 
{
605
 
   SendFakeXEvents(true, false, true, true, false, 0, 0);
606
 
}
607
 
 
608
 
 
609
 
/**
610
 
 *
611
 
 * Handle request from common layer to update mouse position.
612
 
 *
613
 
 * @param[in] x x coordinate of pointer
614
 
 * @param[in] y y coordinate of pointer
615
 
 */
616
 
 
617
 
void
618
 
DnDUI::CommonUpdateMouseCB(int32 x,
619
 
                           int32 y)
620
 
{
621
 
   // Position the pointer, and record its position.
622
 
 
623
 
   SendFakeXEvents(false, false, false, false, true, x, y);
624
 
   m_mousePosX = x;
625
 
   m_mousePosY = y;
626
 
 
627
 
   if (m_dc && !m_GHDnDInProgress) {
628
 
 
629
 
      // If we are the context of a DnD, send DnD feedback to the source.
630
 
 
631
 
      DND_DROPEFFECT effect;
632
 
      effect = ToDropEffect((Gdk::DragAction)(m_dc->action));
633
 
      if (effect != m_effect) {
634
 
         m_effect = effect;
635
 
         Debug("%s: Updating feedback\n", __FUNCTION__);
636
 
         SourceUpdateFeedback(m_effect);
637
 
      }
638
 
   }
639
 
}
640
 
 
641
 
/* Beginning of Gtk+ Callbacks */
642
 
 
643
 
/*
644
 
 * Source callbacks from Gtk+. Most are seen only when we are acting as a
645
 
 * drag source.
646
 
 */
647
 
 
648
 
/**
649
 
 *
650
 
 * "drag_motion" signal handler for GTK. We should respond by setting drag
651
 
 * status. Note that there is no drag enter signal. We need to figure out
652
 
 * if a new drag is happening on our own. Also, we don't respond with a
653
 
 * "allowed" drag status right away, we start a new drag operation over VMDB
654
 
 * (which tries to notify the host of the new operation). Once the host has
655
 
 * responded), we respond with a proper drag status.
656
 
 *
657
 
 * @param[in] dc associated drag context
658
 
 * @param[in] x x coordinate of the drag motion
659
 
 * @param[in] y y coordinate of the drag motion
660
 
 * @param[in] time time of the drag motion
661
 
 *
662
 
 * @return returning false means we won't get notified of future motion. So,
663
 
 * we only return false if we don't recognize the types being offered. We
664
 
 * return true otherwise, even if we don't accept the drag right now for some
665
 
 * other reason.
666
 
 *
667
 
 * @note you may see this callback during DnD when detection window is acting
668
 
 * as a source. In that case it will be ignored. In a future refactoring,
669
 
 * we will try and avoid this.
670
 
 */
671
 
 
672
 
bool
673
 
DnDUI::GtkDestDragMotionCB(const Glib::RefPtr<Gdk::DragContext> &dc,
674
 
                           int x,
675
 
                           int y,
676
 
                           guint timeValue)
677
 
{
678
 
   /*
679
 
    * If this is a Host to Guest drag, we are done here, so return.
680
 
    */
681
 
   unsigned long curTime = GetTimeInMillis();
682
 
   Debug("%s: enter dc %p, m_dc %p\n", __FUNCTION__,
683
 
         dc ? dc->gobj() : NULL, m_dc ? m_dc : NULL);
684
 
   if (curTime - m_destDropTime <= 1000) {
685
 
      Debug("%s: ignored %ld %ld %ld\n", __FUNCTION__,
686
 
            curTime, m_destDropTime, curTime - m_destDropTime);
687
 
      return true;
688
 
   }
689
 
 
690
 
   Debug("%s: not ignored %ld %ld %ld\n", __FUNCTION__,
691
 
         curTime, m_destDropTime, curTime - m_destDropTime);
692
 
 
693
 
   if (m_inHGDrag || m_HGGetDataInProgress) {
694
 
      Debug("%s: ignored not in hg drag or not getting hg data\n", __FUNCTION__);
695
 
      return true;
696
 
   }
697
 
 
698
 
   Gdk::DragAction srcActions;
699
 
   Gdk::DragAction suggestedAction;
700
 
   Gdk::DragAction dndAction = (Gdk::DragAction)0;
701
 
   Glib::ustring target = m_detWnd->drag_dest_find_target(dc);
702
 
 
703
 
   if (!m_DnD->IsDnDAllowed()) {
704
 
      Debug("%s: No dnd allowed!\n", __FUNCTION__);
705
 
      dc->drag_status(dndAction, timeValue);
706
 
      return true;
707
 
   }
708
 
 
709
 
   /* Check if dnd began from this vm. */
710
 
 
711
 
   /*
712
 
    * TODO: Once we upgrade to shipping gtkmm 2.12, we can go back to
713
 
    *       Gdk::DragContext::get_targets, but API/ABI broke between 2.10 and
714
 
    *       2.12, so we work around it like this for now.
715
 
    */
716
 
   Glib::ListHandle<std::string, Gdk::AtomStringTraits> targets(
717
 
      dc->gobj()->targets, Glib::OWNERSHIP_NONE);
718
 
 
719
 
   std::list<Glib::ustring> as = targets;
720
 
   std::list<Glib::ustring>::iterator result;
721
 
   char *pid;
722
 
   pid = Str_Asprintf(NULL, "guest-dnd-target %d", static_cast<int>(getpid()));
723
 
   if (pid) {
724
 
      result = std::find(as.begin(), as.end(), std::string(pid));
725
 
      free(pid);
726
 
   } else {
727
 
      result = as.end();
728
 
   }
729
 
   if (result != as.end()) {
730
 
      Debug("%s: found re-entrant drop target, pid %s\n", __FUNCTION__, pid );
731
 
      return true;
732
 
   }
733
 
 
734
 
   m_dc = dc->gobj();
735
 
 
736
 
   if (target != "") {
737
 
      /*
738
 
       * We give preference to the suggested action from the source, and prefer
739
 
       * copy over move.
740
 
       */
741
 
      suggestedAction = dc->get_suggested_action();
742
 
      srcActions = dc->get_actions();
743
 
      if (suggestedAction == Gdk::ACTION_COPY || suggestedAction == Gdk::ACTION_MOVE) {
744
 
         dndAction = suggestedAction;
745
 
      } else if (srcActions & Gdk::ACTION_COPY) {
746
 
         dndAction= Gdk::ACTION_COPY;
747
 
      } else if (srcActions & Gdk::ACTION_MOVE) {
748
 
         dndAction = Gdk::ACTION_MOVE;
749
 
      } else {
750
 
         dndAction = (Gdk::DragAction)0;
751
 
      }
752
 
   } else {
753
 
      dndAction = (Gdk::DragAction)0;
754
 
   }
755
 
 
756
 
   if (dndAction != (Gdk::DragAction)0) {
757
 
      dc->drag_status(dndAction, timeValue);
758
 
      if (!m_GHDnDInProgress) {
759
 
         Debug("%s: new drag, need to get data for host\n", __FUNCTION__);
760
 
         /*
761
 
          * This is a new drag operation. We need to start a drag thru the
762
 
          * backdoor, and to the host. Before we can tell the host, we have to
763
 
          * retrieve the drop data.
764
 
          */
765
 
         m_GHDnDInProgress = true;
766
 
         /* only begin drag enter after we get the data */
767
 
         /* Need to grab all of the data. */
768
 
         m_detWnd->drag_get_data(dc, target, timeValue);
769
 
      } else {
770
 
         Debug("%s: Multiple drag motions before gh data has been received.\n",
771
 
               __FUNCTION__);
772
 
      }
773
 
   } else {
774
 
      Debug("%s: Invalid drag\n", __FUNCTION__);
775
 
      return false;
776
 
   }
777
 
   return true;
778
 
}
779
 
 
780
 
 
781
 
/**
782
 
 *
783
 
 * "drag_leave" signal handler for GTK. Log the reception of this signal,
784
 
 * but otherwise unhandled in our implementation.
785
 
 *
786
 
 *  @param[in] dc drag context
787
 
 *  @param[in] time time of the drag
788
 
 */
789
 
 
790
 
void
791
 
DnDUI::GtkDestDragLeaveCB(const Glib::RefPtr<Gdk::DragContext> &dc,
792
 
                          guint time)
793
 
{
794
 
   Debug("%s: enter dc %p, m_dc %p\n", __FUNCTION__,
795
 
         dc ? dc->gobj() : NULL, m_dc ? m_dc : NULL);
796
 
 
797
 
   /*
798
 
    * If we reach here after reset DnD, or we are getting a late
799
 
    * DnD drag leave signal (we have started another DnD), then
800
 
    * finish the old DnD. Otherwise, Gtk will not reset and a new
801
 
    * DnD will not start until Gtk+ times out (which appears to
802
 
    * be 5 minutes).
803
 
    * See http://bugzilla.eng.vmware.com/show_bug.cgi?id=528320
804
 
    */
805
 
   if (!m_dc || dc->gobj() != m_dc) {
806
 
      Debug("%s: calling drag_finish\n", __FUNCTION__);
807
 
      dc->drag_finish(true, false, time);
808
 
   }
809
 
}
810
 
 
811
 
 
812
 
/*
813
 
 * Gtk+ callbacks that are seen when we are a drag source.
814
 
 */
815
 
 
816
 
/**
817
 
 *
818
 
 * "drag_begin" signal handler for GTK.
819
 
 *
820
 
 * @param[in] context drag context
821
 
 */
822
 
 
823
 
void
824
 
DnDUI::GtkSourceDragBeginCB(const Glib::RefPtr<Gdk::DragContext>& context)
825
 
{
826
 
   Debug("%s: enter dc %p, m_dc %p\n", __FUNCTION__,
827
 
         context ? context->gobj() : NULL, m_dc ? m_dc : NULL);
828
 
   m_dc = context->gobj();
829
 
}
830
 
 
831
 
 
832
 
/**
833
 
 *
834
 
 * "drag_data_get" handler for GTK. We don't send drop until we are done.
835
 
 *
836
 
 * @param[in] dc drag state
837
 
 * @param[in] selection_data buffer for data
838
 
 * @param[in] info unused
839
 
 * @param[in] time timestamp
840
 
 *
841
 
 * @note if the drop has occurred, the files are copied from the guest.
842
 
 *
843
 
 *-----------------------------------------------------------------------------
844
 
 */
845
 
 
846
 
void
847
 
DnDUI::GtkSourceDragDataGetCB(const Glib::RefPtr<Gdk::DragContext> &dc,
848
 
                              Gtk::SelectionData& selection_data,
849
 
                              guint info,
850
 
                              guint time)
851
 
{
852
 
   size_t index = 0;
853
 
   std::string str;
854
 
   std::string uriList;
855
 
   std::string stagingDirName;
856
 
   void *buf;
857
 
   size_t sz;
858
 
   utf::utf8string hgData;
859
 
   DnDFileList fList;
860
 
   std::string pre;
861
 
   std::string post;
862
 
 
863
 
   const utf::string target = selection_data.get_target().c_str();
864
 
 
865
 
   selection_data.set(target.c_str(), "");
866
 
 
867
 
   Debug("%s: enter dc %p, m_dc %p with target %s\n", __FUNCTION__,
868
 
         dc ? dc->gobj() : NULL, m_dc ? m_dc : NULL,
869
 
         target.c_str());
870
 
 
871
 
   if (!m_inHGDrag) {
872
 
      Debug("%s: not in drag, return\n", __FUNCTION__);
873
 
      return;
874
 
   }
875
 
 
876
 
   if (target == DRAG_TARGET_NAME_URI_LIST &&
877
 
       CPClipboard_GetItem(&m_clipboard, CPFORMAT_FILELIST, &buf, &sz)) {
878
 
 
879
 
      /* Provide path within vmblock file system instead of actual path. */
880
 
      stagingDirName = GetLastDirName(m_HGStagingDir);
881
 
      if (stagingDirName.length() == 0) {
882
 
         Debug("%s: Cannot get staging directory name, stagingDir: %s\n",
883
 
               __FUNCTION__, m_HGStagingDir.c_str());
884
 
         return;
885
 
      }
886
 
 
887
 
      if (!fList.FromCPClipboard(buf, sz)) {
888
 
         Debug("%s: Can't get data from clipboard\n", __FUNCTION__);
889
 
         return;
890
 
      }
891
 
 
892
 
      /* Provide URIs for each path in the guest's file list. */
893
 
      if (FCP_TARGET_INFO_GNOME_COPIED_FILES == info) {
894
 
         pre = FCP_GNOME_LIST_PRE;
895
 
         post = FCP_GNOME_LIST_POST;
896
 
      } else if (FCP_TARGET_INFO_URI_LIST == info) {
897
 
         pre = DND_URI_LIST_PRE_KDE;
898
 
         post = DND_URI_LIST_POST;
899
 
      } else {
900
 
         Debug("%s: Unknown request target: %s\n", __FUNCTION__,
901
 
               selection_data.get_target().c_str());
902
 
         return;
903
 
      }
904
 
 
905
 
      /* Provide path within vmblock file system instead of actual path. */
906
 
      hgData = fList.GetRelPathsStr();
907
 
 
908
 
      /* Provide URIs for each path in the guest's file list. */
909
 
      while ((str = GetNextPath(hgData, index).c_str()).length() != 0) {
910
 
         uriList += pre;
911
 
         if (DnD_BlockIsReady(m_blockCtrl)) {
912
 
            uriList += m_blockCtrl->blockRoot;
913
 
            uriList += DIRSEPS + stagingDirName + DIRSEPS + str + post;
914
 
         } else {
915
 
            uriList += DIRSEPS + m_HGStagingDir + DIRSEPS + str + post;
916
 
         }
917
 
      }
918
 
 
919
 
      /*
920
 
       * This seems to be the best place to do the blocking. If we do
921
 
       * it in the source drop callback from the DnD layer, we often
922
 
       * find ourselves adding the block too late; the user will (in
923
 
       * GNOME, in the dest) be told that it could not find the file,
924
 
       * and if you click retry, it is there, meaning the block was
925
 
       * added too late).
926
 
       *
927
 
       * We find ourselves in this callback twice for each H->G DnD.
928
 
       * We *must* always set the selection data, when called, or else
929
 
       * the DnD for that context will fail, but we *must not* add the
930
 
       * block twice or else things get confused. So we add a check to
931
 
       * see if we are in the right state (no block yet added, and we
932
 
       * are in a HG drag still, both must be true) when adding the block.
933
 
       * Doing both of these addresses bug
934
 
       * http://bugzilla.eng.vmware.com/show_bug.cgi?id=391661.
935
 
       */
936
 
      if (!m_blockAdded && m_inHGDrag) {
937
 
         m_HGGetDataInProgress = true;
938
 
         m_isFileDnD = true;
939
 
         AddBlock();
940
 
      } else {
941
 
         Debug("%s: not calling AddBlock\n", __FUNCTION__);
942
 
      }
943
 
      selection_data.set(DRAG_TARGET_NAME_URI_LIST, uriList.c_str());
944
 
      Debug("%s: exit\n", __FUNCTION__);
945
 
      return;
946
 
   }
947
 
 
948
 
   if (target == DRAG_TARGET_NAME_URI_LIST &&
949
 
       CPClipboard_ItemExists(&m_clipboard, CPFORMAT_FILECONTENTS)) {
950
 
      Debug("%s: Providing uriList [%s] for file contents DnD\n",
951
 
            __FUNCTION__, m_HGFileContentsUriList.c_str());
952
 
 
953
 
      selection_data.set(DRAG_TARGET_NAME_URI_LIST,
954
 
                         m_HGFileContentsUriList.c_str());
955
 
      return;
956
 
   }
957
 
 
958
 
   if ((target == TARGET_NAME_STRING ||
959
 
        target == TARGET_NAME_TEXT_PLAIN ||
960
 
        target == TARGET_NAME_UTF8_STRING ||
961
 
        target == TARGET_NAME_COMPOUND_TEXT) &&
962
 
       CPClipboard_GetItem(&m_clipboard, CPFORMAT_TEXT, &buf, &sz)) {
963
 
      Debug("%s: providing plain text, size %"FMTSZ"u\n", __FUNCTION__, sz);
964
 
      selection_data.set(target.c_str(), (const char *)buf);
965
 
      return;
966
 
   }
967
 
 
968
 
   if ((target == TARGET_NAME_APPLICATION_RTF ||
969
 
        target == TARGET_NAME_TEXT_RICHTEXT) &&
970
 
       CPClipboard_GetItem(&m_clipboard, CPFORMAT_RTF, &buf, &sz)) {
971
 
      Debug("%s: providing rtf text, size %"FMTSZ"u\n", __FUNCTION__, sz);
972
 
      selection_data.set(target.c_str(), (const char *)buf);
973
 
      return;
974
 
   }
975
 
 
976
 
   /* Can not get any valid data, cancel this HG DnD. */
977
 
   Debug("%s: no valid data for HG DnD\n", __FUNCTION__);
978
 
   m_DnD->SourceCancel();
979
 
   CommonResetCB();
980
 
}
981
 
 
982
 
 
983
 
/**
984
 
 *
985
 
 * "drag_end" handler for GTK. Received by drag source.
986
 
 *
987
 
 * @param[in] dc drag state
988
 
 */
989
 
 
990
 
void
991
 
DnDUI::GtkSourceDragEndCB(const Glib::RefPtr<Gdk::DragContext> &dc)
992
 
{
993
 
   Debug("%s: entering dc %p, m_dc %p\n", __FUNCTION__,
994
 
         dc ? dc->gobj() : NULL, m_dc ? m_dc : NULL);
995
 
 
996
 
   /*
997
 
    * We may see a drag end for the previous DnD, but after a new
998
 
    * DnD has started. If so, ignore it.
999
 
    */
1000
 
   if (m_dc && dc && (m_dc != dc->gobj())) {
1001
 
      Debug("%s: got old dc (new DnD started), ignoring\n", __FUNCTION__);
1002
 
      return;
1003
 
   }
1004
 
 
1005
 
   /*
1006
 
    * If we are a file DnD, don't call CommonResetCB() here, since
1007
 
    * we will do so in the fileCopyDoneChanged callback.
1008
 
    */
1009
 
   if (!m_isFileDnD) {
1010
 
      CommonResetCB();
1011
 
   }
1012
 
   m_inHGDrag = false;
1013
 
}
1014
 
 
1015
 
/* Gtk+ callbacks seen when we are a drag destination. */
1016
 
 
1017
 
/**
1018
 
 *
1019
 
 * "drag_data_received" signal handler for GTK. We requested the drag
1020
 
 * data earlier from some drag source on the guest; this is the response.
1021
 
 *
1022
 
 * This is for G->H DnD.
1023
 
 *
1024
 
 * @param[in] dc drag context
1025
 
 * @param[in] x where the drop happened
1026
 
 * @param[in] y where the drop happened
1027
 
 * @param[in] sd the received data
1028
 
 * @param[in] info the info that has been registered with the target in the
1029
 
 * target list.
1030
 
 * @param[in] time the timestamp at which the data was received.
1031
 
 */
1032
 
 
1033
 
void
1034
 
DnDUI::GtkDestDragDataReceivedCB(const Glib::RefPtr<Gdk::DragContext> &dc,
1035
 
                                 int x,
1036
 
                                 int y,
1037
 
                                 const Gtk::SelectionData& sd,
1038
 
                                 guint info,
1039
 
                                 guint time)
1040
 
{
1041
 
   Debug("%s: enter dc %p, m_dc %p\n", __FUNCTION__,
1042
 
         dc ? dc->gobj() : NULL, m_dc ? m_dc : NULL);
1043
 
   /* The GH DnD may already finish before we got response. */
1044
 
   if (!m_GHDnDInProgress) {
1045
 
      Debug("%s: not valid\n", __FUNCTION__);
1046
 
      return;
1047
 
   }
1048
 
 
1049
 
   CPClipboard_Clear(&m_clipboard);
1050
 
 
1051
 
   /*
1052
 
    * Try to get data provided from the source.  If we cannot get any data,
1053
 
    * there is no need to inform the guest of anything. If there is no data,
1054
 
    * reset, so that the next drag_motion callback that we see will be allowed
1055
 
    * to request data again.
1056
 
    */
1057
 
   if (SetCPClipboardFromGtk(sd) == false) {
1058
 
      Debug("%s: Failed to set CP clipboard.\n", __FUNCTION__);
1059
 
      CommonResetCB();
1060
 
      return;
1061
 
   }
1062
 
   if (CPClipboard_IsEmpty(&m_clipboard)) {
1063
 
      Debug("%s: Failed getting item.\n", __FUNCTION__);
1064
 
      CommonResetCB();
1065
 
      return;
1066
 
   }
1067
 
 
1068
 
   /*
1069
 
    * There are two points in the DnD process at which this is called, and both
1070
 
    * are in response to us calling drag_data_get().  The first occurs on the
1071
 
    * first "drag_motion" received and is used to start a drag; at that point
1072
 
    * we need to provide the file list to the guest so we request the data from
1073
 
    * the target.  The second occurs when the "drag_drop" signal is received
1074
 
    * and we confirm this data with the target before starting the drop.
1075
 
    *
1076
 
    * Note that we prevent against sending multiple "dragStart"s or "drop"s for
1077
 
    * each DnD.
1078
 
    */
1079
 
   if (!m_GHDnDDataReceived) {
1080
 
      Debug("%s: Drag entering.\n", __FUNCTION__);
1081
 
      m_GHDnDDataReceived = true;
1082
 
      TargetDragEnter();
1083
 
   } else {
1084
 
      Debug("%s: not !m_GHDnDDataReceived\n", __FUNCTION__);
1085
 
   }
1086
 
}
1087
 
 
1088
 
 
1089
 
/**
1090
 
 *
1091
 
 * "drag_drop" signal handler for GTK. Send the drop to the host (by
1092
 
 * way of the backdoor), then tell the host to get the files.
1093
 
 *
1094
 
 * @param[in] dc drag context
1095
 
 * @param[in] x x location of the drop
1096
 
 * @param[in] y y location of the drop
1097
 
 * @param[in] time timestamp for the drop
1098
 
 *
1099
 
 * @return true on success, false otherwise.
1100
 
 */
1101
 
 
1102
 
bool
1103
 
DnDUI::GtkDestDragDropCB(const Glib::RefPtr<Gdk::DragContext> &dc,
1104
 
                         int x,
1105
 
                         int y,
1106
 
                         guint time)
1107
 
{
1108
 
   Debug("%s: enter dc %p, m_dc %p x %d y %d\n", __FUNCTION__,
1109
 
         (dc ? dc->gobj() : NULL), (m_dc ? m_dc : NULL), x, y);
1110
 
 
1111
 
   Glib::ustring target;
1112
 
 
1113
 
   target = m_detWnd->drag_dest_find_target(dc);
1114
 
   Debug("%s: calling drag_finish\n", __FUNCTION__);
1115
 
   dc->drag_finish(true, false, time);
1116
 
 
1117
 
   if (target == "") {
1118
 
      Debug("%s: No valid data on clipboard.\n", __FUNCTION__);
1119
 
      return false;
1120
 
   }
1121
 
 
1122
 
   if (CPClipboard_IsEmpty(&m_clipboard)) {
1123
 
      Debug("%s: No valid data on m_clipboard.\n", __FUNCTION__);
1124
 
      return false;
1125
 
   }
1126
 
 
1127
 
   return true;
1128
 
}
1129
 
 
1130
 
/* General utility functions */
1131
 
 
1132
 
/**
1133
 
 *
1134
 
 * Try to construct cross-platform clipboard data from selection data
1135
 
 * provided to us by Gtk+.
1136
 
 *
1137
 
 * @param[in] sd Gtk selection data to convert to CP clipboard data
1138
 
 *
1139
 
 * @return false on failure, true on success
1140
 
 */
1141
 
 
1142
 
bool
1143
 
DnDUI::SetCPClipboardFromGtk(const Gtk::SelectionData& sd) // IN
1144
 
{
1145
 
   char *newPath;
1146
 
   char *newRelPath;
1147
 
   size_t newPathLen;
1148
 
   size_t index = 0;
1149
 
   DnDFileList fileList;
1150
 
   DynBuf buf;
1151
 
   uint64 totalSize = 0;
1152
 
   int64 size;
1153
 
 
1154
 
   const utf::string target = sd.get_target().c_str();
1155
 
 
1156
 
   /* Try to get file list. */
1157
 
   if (target == DRAG_TARGET_NAME_URI_LIST) {
1158
 
      /*
1159
 
       * Turn the uri list into two \0  delimited lists. One for full paths and
1160
 
       * one for just the last path component.
1161
 
       */
1162
 
      utf::string source = sd.get_data_as_string().c_str();
1163
 
      Debug("%s: Got file list: [%s]\n", __FUNCTION__, source.c_str());
1164
 
 
1165
 
      if (sd.get_data_as_string().length() == 0) {
1166
 
         Debug("%s: empty file list!\n", __FUNCTION__);
1167
 
         return false;
1168
 
      }
1169
 
 
1170
 
      /*
1171
 
       * In gnome, before file list there may be a extra line indicating it
1172
 
       * is a copy or cut.
1173
 
       */
1174
 
      if (source.length() >= 5 && source.compare(0, 5, "copy\n") == 0) {
1175
 
         source = source.erase(0, 5);
1176
 
      }
1177
 
 
1178
 
      if (source.length() >= 4 && source.compare(0, 4, "cut\n") == 0) {
1179
 
         source = source.erase(0, 4);
1180
 
      }
1181
 
 
1182
 
      while (source.length() > 0 &&
1183
 
             (source[0] == '\n' || source[0] == '\r' || source[0] == ' ')) {
1184
 
         source = source.erase(0, 1);
1185
 
      }
1186
 
 
1187
 
      while ((newPath = DnD_UriListGetNextFile(source.c_str(),
1188
 
                                               &index,
1189
 
                                               &newPathLen)) != NULL) {
1190
 
 
1191
 
         /*
1192
 
          * Parse relative path.
1193
 
          */
1194
 
         newRelPath = Str_Strrchr(newPath, DIRSEPC) + 1; // Point to char after '/'
1195
 
 
1196
 
         if ((size = File_GetSize(newPath)) >= 0) {
1197
 
            totalSize += size;
1198
 
         } else {
1199
 
            Debug("%s: unable to get file size for %s\n", __FUNCTION__, newPath);
1200
 
         }
1201
 
         Debug("%s: Adding newPath '%s' newRelPath '%s'\n", __FUNCTION__,
1202
 
               newPath, newRelPath);
1203
 
         fileList.AddFile(newPath, newRelPath);
1204
 
         free(newPath);
1205
 
      }
1206
 
 
1207
 
      DynBuf_Init(&buf);
1208
 
      fileList.SetFileSize(totalSize);
1209
 
      if (fileList.ToCPClipboard(&buf, false)) {
1210
 
          CPClipboard_SetItem(&m_clipboard, CPFORMAT_FILELIST, DynBuf_Get(&buf),
1211
 
                              DynBuf_GetSize(&buf));
1212
 
      }
1213
 
      DynBuf_Destroy(&buf);
1214
 
      return true;
1215
 
   }
1216
 
 
1217
 
   /* Try to get plain text. */
1218
 
   if (target == TARGET_NAME_STRING ||
1219
 
       target == TARGET_NAME_TEXT_PLAIN ||
1220
 
       target == TARGET_NAME_UTF8_STRING ||
1221
 
       target == TARGET_NAME_COMPOUND_TEXT) {
1222
 
      utf::string source = sd.get_data_as_string().c_str();
1223
 
      if (source.bytes() > 0 &&
1224
 
          source.bytes() < DNDMSG_MAX_ARGSZ &&
1225
 
          CPClipboard_SetItem(&m_clipboard, CPFORMAT_TEXT, source.c_str(),
1226
 
                              source.bytes() + 1)) {
1227
 
         Debug("%s: Got text, size %"FMTSZ"u\n", __FUNCTION__, source.bytes());
1228
 
      } else {
1229
 
         Debug("%s: Failed to get text\n", __FUNCTION__);
1230
 
         return false;
1231
 
      }
1232
 
      return true;
1233
 
   }
1234
 
 
1235
 
   /* Try to get RTF string. */
1236
 
   if (target == TARGET_NAME_APPLICATION_RTF ||
1237
 
       target == TARGET_NAME_TEXT_RICHTEXT) {
1238
 
      utf::string source = sd.get_data_as_string().c_str();
1239
 
      if (source.bytes() > 0 &&
1240
 
          source.bytes() < DNDMSG_MAX_ARGSZ &&
1241
 
          CPClipboard_SetItem(&m_clipboard, CPFORMAT_RTF, source.c_str(),
1242
 
                              source.bytes() + 1)) {
1243
 
         Debug("%s: Got RTF, size %"FMTSZ"u\n", __FUNCTION__, source.bytes());
1244
 
         return true;
1245
 
      } else {
1246
 
         Debug("%s: Failed to get text\n", __FUNCTION__ );
1247
 
         return false;
1248
 
      }
1249
 
   }
1250
 
   return true;
1251
 
}
1252
 
 
1253
 
 
1254
 
/**
1255
 
 *
1256
 
 * Try to get last directory name from a full path name.
1257
 
 *
1258
 
 * @param[in] str pathname to process
1259
 
 *
1260
 
 * @return last dir name in the full path name if sucess, empty str otherwise
1261
 
 */
1262
 
 
1263
 
std::string
1264
 
DnDUI::GetLastDirName(const std::string &str)
1265
 
{
1266
 
   std::string ret;
1267
 
   size_t start;
1268
 
   size_t end;
1269
 
 
1270
 
   end = str.size() - 1;
1271
 
   if (end >= 0 && DIRSEPC == str[end]) {
1272
 
      end--;
1273
 
   }
1274
 
 
1275
 
   if (end <= 0 || str[0] != DIRSEPC) {
1276
 
      return "";
1277
 
   }
1278
 
 
1279
 
   start = end;
1280
 
 
1281
 
   while (str[start] != DIRSEPC) {
1282
 
      start--;
1283
 
   }
1284
 
 
1285
 
   return str.substr(start + 1, end - start);
1286
 
}
1287
 
 
1288
 
 
1289
 
/**
1290
 
 *
1291
 
 * Provide a substring containing the next path from the provided
1292
 
 * NUL-delimited string starting at the provided index.
1293
 
 *
1294
 
 * @param[in] str NUL-delimited path list
1295
 
 * @param[in] index current index into string
1296
 
 *
1297
 
 * @return a string with the next path or "" if there are no more paths.
1298
 
 */
1299
 
 
1300
 
utf::utf8string
1301
 
DnDUI::GetNextPath(utf::utf8string& str,
1302
 
                   size_t& index)
1303
 
{
1304
 
   utf::utf8string ret;
1305
 
   size_t start;
1306
 
 
1307
 
   if (index >= str.length()) {
1308
 
      return "";
1309
 
   }
1310
 
 
1311
 
   for (start = index; str[index] != '\0' && index < str.length(); index++) {
1312
 
      /*
1313
 
       * Escape reserved characters according to RFC 1630.  We'd use
1314
 
       * Escape_Do() if this wasn't a utf::string, but let's use the same table
1315
 
       * replacement approach.
1316
 
       */
1317
 
      static char const Dec2Hex[] = {
1318
 
         '0', '1', '2', '3', '4', '5', '6', '7',
1319
 
         '8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
1320
 
      };
1321
 
 
1322
 
      unsigned char ubyte = str[index];
1323
 
 
1324
 
      if (ubyte == '#' ||   /* Fragment identifier delimiter */
1325
 
          ubyte == '?' ||   /* Query string delimiter */
1326
 
          ubyte == '*' ||   /* "Special significance within specific schemes" */
1327
 
          ubyte == '!' ||   /* "Special significance within specific schemes" */
1328
 
          ubyte == '%' ||   /* Escape character */
1329
 
          ubyte >= 0x80) {  /* UTF-8 encoding bytes */
1330
 
         str.replace(index, 1, "%");
1331
 
         str.insert(index + 1, 1, Dec2Hex[ubyte >> 4]);
1332
 
         str.insert(index + 2, 1, Dec2Hex[ubyte & 0xF]);
1333
 
         index += 2;
1334
 
      }
1335
 
   }
1336
 
 
1337
 
   ret = str.substr(start, index - start);
1338
 
   Debug("%s: nextpath: %s", __FUNCTION__, ret.c_str());
1339
 
   index++;
1340
 
   return ret;
1341
 
}
1342
 
 
1343
 
 
1344
 
/**
1345
 
 *
1346
 
 * Issue a fake mouse move event to the detection window. Code stolen from
1347
 
 * DnD V2 Linux guest implementation, where it was originally defined as a
1348
 
 * macro.
1349
 
 *
1350
 
 * @param[in] x x-coordinate of location to move mouse to.
1351
 
 * @param[in] y y-coordinate of location to move mouse to.
1352
 
 *
1353
 
 * @return true on success, false on failure.
1354
 
 */
1355
 
 
1356
 
bool
1357
 
DnDUI::SendFakeMouseMove(const int x,
1358
 
                         const int y)
1359
 
{
1360
 
   return SendFakeXEvents(false, false, false, false, true, x, y);
1361
 
}
1362
 
 
1363
 
 
1364
 
/**
1365
 
 *
1366
 
 * Fake X mouse events and window movement for the provided Gtk widget.
1367
 
 *
1368
 
 * This function will optionally show the widget, move the provided widget
1369
 
 * to either the provided location or the current mouse position if no
1370
 
 * coordinates are provided, and cause a button press or release event.
1371
 
 *
1372
 
 * @param[in] showWidget       whether to show Gtk widget
1373
 
 * @param[in] buttonEvent      whether to send a button event
1374
 
 * @param[in] buttonPress      whether to press or release mouse
1375
 
 * @param[in] moveWindow:      whether to move our window too
1376
 
 * @param[in] coordsProvided   whether coordinates provided
1377
 
 * @param[in] xCoord           x coordinate
1378
 
 * @param[in] yCoord           y coordinate
1379
 
 *
1380
 
 * @note todo this code should be implemented using GDK APIs.
1381
 
 * @note todo this code should be moved into the detection window class
1382
 
 *
1383
 
 * @return true on success, false on failure.
1384
 
 */
1385
 
 
1386
 
bool
1387
 
DnDUI::SendFakeXEvents(const bool showWidget,
1388
 
                       const bool buttonEvent,
1389
 
                       const bool buttonPress,
1390
 
                       const bool moveWindow,
1391
 
                       const bool coordsProvided,
1392
 
                       const int xCoord,
1393
 
                       const int yCoord)
1394
 
{
1395
 
   GtkWidget *widget;
1396
 
   Window rootWnd;
1397
 
   bool ret = false;
1398
 
   Display *dndXDisplay;
1399
 
   Window dndXWindow;
1400
 
   Window rootReturn;
1401
 
   int x;
1402
 
   int y;
1403
 
   Window childReturn;
1404
 
   int rootXReturn;
1405
 
   int rootYReturn;
1406
 
   int winXReturn;
1407
 
   int winYReturn;
1408
 
   unsigned int maskReturn;
1409
 
 
1410
 
   x = xCoord;
1411
 
   y = yCoord;
1412
 
 
1413
 
   widget = GetDetWndAsWidget();
1414
 
 
1415
 
   if (!widget) {
1416
 
      Debug("%s: unable to get widget\n", __FUNCTION__);
1417
 
      return false;
1418
 
   }
1419
 
 
1420
 
   dndXDisplay = GDK_WINDOW_XDISPLAY(widget->window);
1421
 
   dndXWindow = GDK_WINDOW_XWINDOW(widget->window);
1422
 
   rootWnd = RootWindow(dndXDisplay, DefaultScreen(dndXDisplay));
1423
 
 
1424
 
   /*
1425
 
    * Turn on X synchronization in order to ensure that our X events occur in
1426
 
    * the order called.  In particular, we want the window movement to occur
1427
 
    * before the mouse movement so that the events we are coercing do in fact
1428
 
    * happen.
1429
 
    */
1430
 
   XSynchronize(dndXDisplay, True);
1431
 
 
1432
 
   if (showWidget) {
1433
 
      Debug("%s: showing Gtk widget\n", __FUNCTION__);
1434
 
      gtk_widget_show(widget);
1435
 
      gdk_window_show(widget->window);
1436
 
   }
1437
 
 
1438
 
   /* Get the current location of the mouse if coordinates weren't provided. */
1439
 
   if (!coordsProvided) {
1440
 
      if (!XQueryPointer(dndXDisplay, rootWnd, &rootReturn, &childReturn,
1441
 
                         &rootXReturn, &rootYReturn, &winXReturn, &winYReturn,
1442
 
                         &maskReturn)) {
1443
 
         Warning("%s: XQueryPointer() returned False.\n", __FUNCTION__);
1444
 
         goto exit;
1445
 
      }
1446
 
 
1447
 
      Debug("%s: current mouse is at (%d, %d)\n", __FUNCTION__,
1448
 
            rootXReturn, rootYReturn);
1449
 
 
1450
 
      /*
1451
 
       * Position away from the edge of the window.
1452
 
       */
1453
 
      int width = m_detWnd->GetScreenWidth();
1454
 
      int height = m_detWnd->GetScreenHeight();
1455
 
      bool change = false;
1456
 
 
1457
 
      x = rootXReturn;
1458
 
      y = rootYReturn;
1459
 
 
1460
 
      /*
1461
 
       * first do left and top edges.
1462
 
       */
1463
 
 
1464
 
      if (x <= 5) {
1465
 
         x = 6;
1466
 
         change = true;
1467
 
      }
1468
 
 
1469
 
      if (y <= 5) {
1470
 
         y = 6;
1471
 
         change = true;
1472
 
      }
1473
 
 
1474
 
      /*
1475
 
       * next, move result away from right and bottom edges.
1476
 
       */
1477
 
      if (x > width - 5) {
1478
 
         x = width - 6;
1479
 
         change = true;
1480
 
      }
1481
 
      if (y > height - 5) {
1482
 
         y = height - 6;
1483
 
         change = true;
1484
 
      }
1485
 
 
1486
 
      if (change) {
1487
 
         Debug("%s: adjusting mouse position. root %d, %d, adjusted %d, %d\n",
1488
 
               __FUNCTION__, rootXReturn, rootYReturn, x, y);
1489
 
      }
1490
 
   }
1491
 
 
1492
 
   if (moveWindow) {
1493
 
      /*
1494
 
       * Make sure the window is at this point and at the top (raised).  The
1495
 
       * window is resized to be a bit larger than we would like to increase
1496
 
       * the likelihood that mouse events are attributed to our window -- this
1497
 
       * is okay since the window is invisible and hidden on cancels and DnD
1498
 
       * finish.
1499
 
       */
1500
 
      XMoveResizeWindow(dndXDisplay, dndXWindow, x - 5 , y - 5, 25, 25);
1501
 
      XRaiseWindow(dndXDisplay, dndXWindow);
1502
 
      Debug("%s: move wnd to (%d, %d, %d, %d)\n", __FUNCTION__, x - 5, y - 5 , x + 25, y + 25);
1503
 
   }
1504
 
 
1505
 
   /*
1506
 
    * Generate mouse movements over the window.  The second one makes ungrabs
1507
 
    * happen more reliably on KDE, but isn't necessary on GNOME.
1508
 
    */
1509
 
   XTestFakeMotionEvent(dndXDisplay, -1, x, y, CurrentTime);
1510
 
   XTestFakeMotionEvent(dndXDisplay, -1, x + 1, y + 1, CurrentTime);
1511
 
   Debug("%s: move mouse to (%d, %d) and (%d, %d)\n", __FUNCTION__, x, y, x + 1, y + 1);
1512
 
 
1513
 
   if (buttonEvent) {
1514
 
      Debug("%s: faking left mouse button %s\n", __FUNCTION__,
1515
 
            buttonPress ? "press" : "release");
1516
 
      XTestFakeButtonEvent(dndXDisplay, 1, buttonPress, CurrentTime);
1517
 
      XSync(dndXDisplay, False);
1518
 
 
1519
 
      if (!buttonPress) {
1520
 
         /*
1521
 
          * The button release simulation may be failed with some distributions
1522
 
          * like Ubuntu 10.4 and RHEL 6 for guest->host DnD. So first query
1523
 
          * mouse button status. If some button is still down, we will try
1524
 
          * mouse device level event simulation. For details please refer
1525
 
          * to bug 552807.
1526
 
          */
1527
 
         if (!XQueryPointer(dndXDisplay, rootWnd, &rootReturn, &childReturn,
1528
 
                            &rootXReturn, &rootYReturn, &winXReturn,
1529
 
                            &winYReturn, &maskReturn)) {
1530
 
            Warning("%s: XQueryPointer returned False.\n", __FUNCTION__);
1531
 
            goto exit;
1532
 
         }
1533
 
 
1534
 
         if ((maskReturn & Button1Mask) ||
1535
 
             (maskReturn & Button2Mask) ||
1536
 
             (maskReturn & Button3Mask) ||
1537
 
             (maskReturn & Button4Mask) ||
1538
 
             (maskReturn & Button5Mask)) {
1539
 
            Debug("%s: XTestFakeButtonEvent was not working for button "
1540
 
                  "release, trying XTestFakeDeviceButtonEvent now.\n",
1541
 
                  __FUNCTION__);
1542
 
            ret = TryXTestFakeDeviceButtonEvent();
1543
 
         } else {
1544
 
            Debug("%s: XTestFakeButtonEvent was working for button release.\n",
1545
 
                  __FUNCTION__);
1546
 
            ret = true;
1547
 
         }
1548
 
      } else {
1549
 
         ret = true;
1550
 
      }
1551
 
   }
1552
 
 
1553
 
exit:
1554
 
   XSynchronize(dndXDisplay, False);
1555
 
   return ret;
1556
 
}
1557
 
 
1558
 
 
1559
 
/**
1560
 
 * Fake X mouse events in device level.
1561
 
 *
1562
 
 * XXX The function will only be called if XTestFakeButtonEvent does not work
1563
 
 * for mouse button release. Later on we may only call this one for mouse
1564
 
 * button simulation if this is more reliable.
1565
 
 *
1566
 
 * @return true on success, false on failure.
1567
 
 */
1568
 
 
1569
 
bool
1570
 
DnDUI::TryXTestFakeDeviceButtonEvent(void)
1571
 
{
1572
 
   XDeviceInfo *list = NULL;
1573
 
   XDeviceInfo *list2 = NULL;
1574
 
   XDevice *tdev = NULL;
1575
 
   XDevice *buttonDevice = NULL;
1576
 
   int numDevices = 0;
1577
 
   int i = 0;
1578
 
   int j = 0;
1579
 
   XInputClassInfo *ip = NULL;
1580
 
   GtkWidget *widget;
1581
 
   Display *dndXDisplay;
1582
 
 
1583
 
   widget = GetDetWndAsWidget();
1584
 
 
1585
 
   if (!widget) {
1586
 
      Debug("%s: unable to get widget\n", __FUNCTION__);
1587
 
      return false;
1588
 
   }
1589
 
 
1590
 
   dndXDisplay = GDK_WINDOW_XDISPLAY(widget->window);
1591
 
 
1592
 
   /* First get list of all input device. */
1593
 
   if (!(list = XListInputDevices (dndXDisplay, &numDevices))) {
1594
 
      Debug("%s: XListInputDevices failed\n", __FUNCTION__);
1595
 
      return false;
1596
 
   } else {
1597
 
      Debug("%s: XListInputDevices got %d devices\n", __FUNCTION__, numDevices);
1598
 
   }
1599
 
 
1600
 
   list2 = list;
1601
 
 
1602
 
   for (i = 0; i < numDevices; i++, list++) {
1603
 
      /* We only care about mouse device. */
1604
 
      if (list->use != IsXExtensionPointer) {
1605
 
         continue;
1606
 
      }
1607
 
 
1608
 
      tdev = XOpenDevice(dndXDisplay, list->id);
1609
 
      if (!tdev) {
1610
 
         Debug("%s: XOpenDevice failed\n", __FUNCTION__);
1611
 
         continue;
1612
 
      }
1613
 
 
1614
 
      for (ip = tdev->classes, j = 0; j < tdev->num_classes; j++, ip++) {
1615
 
         if (ip->input_class == ButtonClass) {
1616
 
            buttonDevice = tdev;
1617
 
            break;
1618
 
         }
1619
 
      }
1620
 
 
1621
 
      if (buttonDevice) {
1622
 
         Debug("%s: calling XTestFakeDeviceButtonEvent for %s\n",
1623
 
               __FUNCTION__, list->name);
1624
 
         XTestFakeDeviceButtonEvent(dndXDisplay, buttonDevice, 1, False,
1625
 
                                    NULL, 0, CurrentTime);
1626
 
         buttonDevice = NULL;
1627
 
      }
1628
 
      XCloseDevice(dndXDisplay, tdev);
1629
 
   }
1630
 
   XFreeDeviceList(list2);
1631
 
   return true;
1632
 
}
1633
 
 
1634
 
 
1635
 
/**
1636
 
 *
1637
 
 * Get the GtkWidget pointer for a DragDetWnd object. The X11 Unity
1638
 
 * implementation requires access to the drag detection window as
1639
 
 * a GtkWindow pointer, which it uses to show and hide the detection
1640
 
 * window. This function is also called by the code that issues fake
1641
 
 * X events to the detection window.
1642
 
 *
1643
 
 * @return a pointer to the GtkWidget for the detection window, or NULL
1644
 
 * on failure.
1645
 
 */
1646
 
 
1647
 
GtkWidget *
1648
 
DnDUI::GetDetWndAsWidget()
1649
 
{
1650
 
   GtkInvisible *window;
1651
 
   GtkWidget *widget = NULL;
1652
 
 
1653
 
   if (!m_detWnd) {
1654
 
      return NULL;
1655
 
   }
1656
 
   window = m_detWnd->gobj();
1657
 
   if (window) {
1658
 
      widget = GTK_WIDGET(window);
1659
 
   }
1660
 
   return widget;
1661
 
}
1662
 
 
1663
 
 
1664
 
/**
1665
 
 *
1666
 
 * Add a block for the current H->G file transfer. Must be paired with a
1667
 
 * call to RemoveBlock() on finish or cancellation.
1668
 
 */
1669
 
 
1670
 
void
1671
 
DnDUI::AddBlock()
1672
 
{
1673
 
   Debug("%s: enter\n", __FUNCTION__);
1674
 
   if (m_blockAdded) {
1675
 
      Debug("%s: block already added\n", __FUNCTION__);
1676
 
      return;
1677
 
   }
1678
 
   if (DnD_BlockIsReady(m_blockCtrl) && m_blockCtrl->AddBlock(m_blockCtrl->fd, m_HGStagingDir.c_str())) {
1679
 
      m_blockAdded = true;
1680
 
      Debug("%s: add block for %s.\n", __FUNCTION__, m_HGStagingDir.c_str());
1681
 
   } else {
1682
 
      m_blockAdded = false;
1683
 
      Debug("%s: unable to add block dir %s.\n", __FUNCTION__, m_HGStagingDir.c_str());
1684
 
   }
1685
 
}
1686
 
 
1687
 
 
1688
 
/**
1689
 
 *
1690
 
 * Remove block for the current H->G file transfer. Must be paired with a
1691
 
 * call to AddBlock(), but it will only attempt to remove block if one is
1692
 
 * currently in effect.
1693
 
 */
1694
 
 
1695
 
void
1696
 
DnDUI::RemoveBlock()
1697
 
{
1698
 
   Debug("%s: enter\n", __FUNCTION__);
1699
 
   if (m_blockAdded && !m_HGGetDataInProgress) {
1700
 
      Debug("%s: removing block for %s\n", __FUNCTION__, m_HGStagingDir.c_str());
1701
 
      m_blockCtrl->RemoveBlock(m_blockCtrl->fd, m_HGStagingDir.c_str());
1702
 
      m_blockAdded = false;
1703
 
   } else {
1704
 
      Debug("%s: not removing block m_blockAdded %d m_HGGetDataInProgress %d\n",
1705
 
            __FUNCTION__,
1706
 
            m_blockAdded,
1707
 
            m_HGGetDataInProgress);
1708
 
   }
1709
 
}
1710
 
 
1711
 
 
1712
 
/**
1713
 
 *
1714
 
 * Convert a Gdk::DragAction value to its corresponding DND_DROPEFFECT.
1715
 
 *
1716
 
 * @param[in] the Gdk::DragAction value to return.
1717
 
 *
1718
 
 * @return the corresponding DND_DROPEFFECT, with DROP_UNKNOWN returned
1719
 
 * if no mapping is supported.
1720
 
 *
1721
 
 * @note DROP_NONE is not mapped in this function.
1722
 
 */
1723
 
 
1724
 
DND_DROPEFFECT
1725
 
DnDUI::ToDropEffect(Gdk::DragAction action)
1726
 
{
1727
 
   DND_DROPEFFECT effect;
1728
 
 
1729
 
   switch(action) {
1730
 
   case Gdk::ACTION_COPY:
1731
 
   case Gdk::ACTION_DEFAULT:
1732
 
      effect = DROP_COPY;
1733
 
      break;
1734
 
   case Gdk::ACTION_MOVE:
1735
 
      effect = DROP_MOVE;
1736
 
      break;
1737
 
   case Gdk::ACTION_LINK:
1738
 
      effect = DROP_LINK;
1739
 
      break;
1740
 
   case Gdk::ACTION_PRIVATE:
1741
 
   case Gdk::ACTION_ASK:
1742
 
   default:
1743
 
      effect = DROP_UNKNOWN;
1744
 
      break;
1745
 
   }
1746
 
   return effect;
1747
 
}
1748
 
 
1749
 
 
1750
 
/**
1751
 
 *
1752
 
 * Try to extract file contents from m_clipboard. Write all files to a
1753
 
 * temporary staging directory. Construct uri list.
1754
 
 *
1755
 
 * @return true if success, false otherwise.
1756
 
 */
1757
 
 
1758
 
bool
1759
 
DnDUI::WriteFileContentsToStagingDir(void)
1760
 
{
1761
 
   void *buf = NULL;
1762
 
   size_t sz = 0;
1763
 
   XDR xdrs;
1764
 
   CPFileContents fileContents;
1765
 
   CPFileContentsList *contentsList = NULL;
1766
 
   size_t nFiles = 0;
1767
 
   CPFileItem *fileItem = NULL;
1768
 
   Unicode tempDir = NULL;
1769
 
   size_t i = 0;
1770
 
   bool ret = false;
1771
 
 
1772
 
   if (!CPClipboard_GetItem(&m_clipboard, CPFORMAT_FILECONTENTS, &buf, &sz)) {
1773
 
      return false;
1774
 
   }
1775
 
 
1776
 
   /* Extract file contents from buf. */
1777
 
   xdrmem_create(&xdrs, (char *)buf, sz, XDR_DECODE);
1778
 
   memset(&fileContents, 0, sizeof fileContents);
1779
 
 
1780
 
   if (!xdr_CPFileContents(&xdrs, &fileContents)) {
1781
 
      Debug("%s: xdr_CPFileContents failed.\n", __FUNCTION__);
1782
 
      xdr_destroy(&xdrs);
1783
 
      return false;
1784
 
   }
1785
 
   xdr_destroy(&xdrs);
1786
 
 
1787
 
   contentsList = fileContents.CPFileContents_u.fileContentsV1;
1788
 
   if (!contentsList) {
1789
 
      Debug("%s: invalid contentsList.\n", __FUNCTION__);
1790
 
      goto exit;
1791
 
   }
1792
 
 
1793
 
   nFiles = contentsList->fileItem.fileItem_len;
1794
 
   if (0 == nFiles) {
1795
 
      Debug("%s: invalid nFiles.\n", __FUNCTION__);
1796
 
      goto exit;
1797
 
   }
1798
 
 
1799
 
   fileItem = contentsList->fileItem.fileItem_val;
1800
 
   if (!fileItem) {
1801
 
      Debug("%s: invalid fileItem.\n", __FUNCTION__);
1802
 
      goto exit;
1803
 
   }
1804
 
 
1805
 
   /*
1806
 
    * Write files into a temporary staging directory. These files will be moved
1807
 
    * to final destination, or deleted on next reboot.
1808
 
    */
1809
 
   tempDir = DnD_CreateStagingDirectory();
1810
 
   if (!tempDir) {
1811
 
      Debug("%s: DnD_CreateStagingDirectory failed.\n", __FUNCTION__);
1812
 
      goto exit;
1813
 
   }
1814
 
 
1815
 
   m_HGFileContentsUriList = "";
1816
 
 
1817
 
   for (i = 0; i < nFiles; i++) {
1818
 
      utf::string fileName;
1819
 
      utf::string filePathName;
1820
 
      VmTimeType createTime = -1;
1821
 
      VmTimeType accessTime = -1;
1822
 
      VmTimeType writeTime = -1;
1823
 
      VmTimeType attrChangeTime = -1;
1824
 
 
1825
 
      if (!fileItem[i].cpName.cpName_val ||
1826
 
          0 == fileItem[i].cpName.cpName_len) {
1827
 
         Debug("%s: invalid fileItem[%"FMTSZ"u].cpName.\n", __FUNCTION__, i);
1828
 
         goto exit;
1829
 
      }
1830
 
 
1831
 
      /*
1832
 
       * '\0' is used as directory separator in cross-platform name. Now turn
1833
 
       * '\0' in data into DIRSEPC.
1834
 
       *
1835
 
       * Note that we don't convert the final '\0' into DIRSEPC so the string
1836
 
       * is NUL terminated.
1837
 
       */
1838
 
      CPNameUtil_CharReplace(fileItem[i].cpName.cpName_val,
1839
 
                             fileItem[i].cpName.cpName_len - 1,
1840
 
                             '\0',
1841
 
                             DIRSEPC);
1842
 
      fileName = fileItem[i].cpName.cpName_val;
1843
 
      filePathName = tempDir;
1844
 
      filePathName += DIRSEPS + fileName;
1845
 
 
1846
 
      if (fileItem[i].validFlags & CP_FILE_VALID_TYPE &&
1847
 
          CP_FILE_TYPE_DIRECTORY == fileItem[i].type) {
1848
 
         if (!File_CreateDirectory(filePathName.c_str())) {
1849
 
            goto exit;
1850
 
         }
1851
 
         Debug("%s: created directory [%s].\n",
1852
 
               __FUNCTION__, filePathName.c_str());
1853
 
      } else if (fileItem[i].validFlags & CP_FILE_VALID_TYPE &&
1854
 
                 CP_FILE_TYPE_REGULAR == fileItem[i].type) {
1855
 
         FileIODescriptor file;
1856
 
         FileIOResult fileErr;
1857
 
 
1858
 
         FileIO_Invalidate(&file);
1859
 
 
1860
 
         fileErr = FileIO_Open(&file,
1861
 
                               filePathName.c_str(),
1862
 
                               FILEIO_ACCESS_WRITE,
1863
 
                               FILEIO_OPEN_CREATE_EMPTY);
1864
 
         if (!FileIO_IsSuccess(fileErr)) {
1865
 
            goto exit;
1866
 
         }
1867
 
 
1868
 
         fileErr = FileIO_Write(&file,
1869
 
                                fileItem[i].content.content_val,
1870
 
                                fileItem[i].content.content_len,
1871
 
                                NULL);
1872
 
 
1873
 
         FileIO_Close(&file);
1874
 
         Debug("%s: created file [%s].\n",
1875
 
               __FUNCTION__, filePathName.c_str());
1876
 
      } else {
1877
 
         /*
1878
 
          * Right now only Windows can provide CPFORMAT_FILECONTENTS data.
1879
 
          * Symlink file is not expected. Continue with next file if the
1880
 
          * type is not valid.
1881
 
          */
1882
 
         continue;
1883
 
      }
1884
 
 
1885
 
      /* Update file time attributes. */
1886
 
      createTime = fileItem->validFlags & CP_FILE_VALID_CREATE_TIME ?
1887
 
         fileItem->createTime: -1;
1888
 
      accessTime = fileItem->validFlags & CP_FILE_VALID_ACCESS_TIME ?
1889
 
         fileItem->accessTime: -1;
1890
 
      writeTime = fileItem->validFlags & CP_FILE_VALID_WRITE_TIME ?
1891
 
         fileItem->writeTime: -1;
1892
 
      attrChangeTime = fileItem->validFlags & CP_FILE_VALID_CHANGE_TIME ?
1893
 
         fileItem->attrChangeTime: -1;
1894
 
 
1895
 
      if (!File_SetTimes(filePathName.c_str(),
1896
 
                         createTime,
1897
 
                         accessTime,
1898
 
                         writeTime,
1899
 
                         attrChangeTime)) {
1900
 
         /* Not a critical error, only log it. */
1901
 
         Debug("%s: File_SetTimes failed with file [%s].\n",
1902
 
               __FUNCTION__, filePathName.c_str());
1903
 
      }
1904
 
 
1905
 
      /* Update file permission attributes. */
1906
 
      if (fileItem->validFlags & CP_FILE_VALID_PERMS) {
1907
 
         if (Posix_Chmod(filePathName.c_str(),
1908
 
                         fileItem->permissions) < 0) {
1909
 
            /* Not a critical error, only log it. */
1910
 
            Debug("%s: Posix_Chmod failed with file [%s].\n",
1911
 
                  __FUNCTION__, filePathName.c_str());
1912
 
         }
1913
 
      }
1914
 
 
1915
 
      /*
1916
 
       * If there is no DIRSEPC inside the fileName, this file/directory is a
1917
 
       * top level one. We only put top level name into uri list.
1918
 
       */
1919
 
      if (fileName.find(DIRSEPS, 0) == utf::string::npos) {
1920
 
         m_HGFileContentsUriList += "file://" + filePathName + "\r\n";
1921
 
      }
1922
 
   }
1923
 
   Debug("%s: created uri list [%s].\n",
1924
 
         __FUNCTION__, m_HGFileContentsUriList.c_str());
1925
 
   ret = true;
1926
 
 
1927
 
exit:
1928
 
   xdr_free((xdrproc_t) xdr_CPFileContents, (char *)&fileContents);
1929
 
   if (tempDir && !ret) {
1930
 
      DnD_DeleteStagingFiles(tempDir, false);
1931
 
   }
1932
 
   free(tempDir);
1933
 
   return ret;
1934
 
}
1935
 
 
1936
 
 
1937
 
/**
1938
 
 *
1939
 
 * Tell host that we are done with HG DnD initialization.
1940
 
 */
1941
 
 
1942
 
void
1943
 
DnDUI::SourceDragStartDone(void)
1944
 
{
1945
 
   Debug("%s: enter\n", __FUNCTION__);
1946
 
   m_inHGDrag = true;
1947
 
   m_DnD->HGDragStartDone();
1948
 
}
1949
 
 
1950
 
 
1951
 
/**
1952
 
 * Set block control member.
1953
 
 *
1954
 
 * @param[in] block control as setup by vmware-user.
1955
 
 */
1956
 
 
1957
 
void
1958
 
DnDUI::SetBlockControl(DnDBlockControl *blockCtrl)
1959
 
{
1960
 
   m_blockCtrl = blockCtrl;
1961
 
}
1962
 
 
1963
 
 
1964
 
/**
1965
 
 *
1966
 
 * Got feedback from our DropSource, send it over to host. Called by
1967
 
 * drag motion callback.
1968
 
 *
1969
 
 * @param[in] effect feedback to send to the UI-independent DnD layer.
1970
 
 */
1971
 
 
1972
 
void
1973
 
DnDUI::SourceUpdateFeedback(DND_DROPEFFECT effect)
1974
 
{
1975
 
   Debug("%s: entering\n", __FUNCTION__);
1976
 
   m_DnD->SetFeedback(effect);
1977
 
}
1978
 
 
1979
 
 
1980
 
/**
1981
 
 *
1982
 
 * This is triggered when user drags valid data from guest to host. Try to
1983
 
 * get clip data and notify host to start GH DnD.
1984
 
 */
1985
 
 
1986
 
void
1987
 
DnDUI::TargetDragEnter(void)
1988
 
{
1989
 
   Debug("%s: entering\n", __FUNCTION__);
1990
 
 
1991
 
   /* Check if there is valid data with current detection window. */
1992
 
   if (!CPClipboard_IsEmpty(&m_clipboard)) {
1993
 
      Debug("%s: got valid data from detWnd.\n", __FUNCTION__);
1994
 
      m_DnD->DragEnter(&m_clipboard);
1995
 
   }
1996
 
 
1997
 
   /*
1998
 
    * Show the window, and position it under the current mouse position.
1999
 
    * This is particularly important for KDE 3.5 guests.
2000
 
    */
2001
 
   SendFakeXEvents(true, false, true, true, false, 0, 0);
2002
 
}
2003
 
 
2004
 
 
2005
 
/**
2006
 
 *
2007
 
 * Get Unix time in milliseconds. See man 2 gettimeofday for details.
2008
 
 *
2009
 
 * @return unix time in milliseconds.
2010
 
 */
2011
 
 
2012
 
unsigned long
2013
 
DnDUI::GetTimeInMillis(void)
2014
 
{
2015
 
   VmTimeType atime;
2016
 
 
2017
 
   Hostinfo_GetTimeOfDay(&atime);
2018
 
   return((unsigned long)(atime / 1000));
2019
 
}