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

« back to all changes in this revision

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