1
/*********************************************************
2
* Copyright (C) 2009 VMware, Inc. All rights reserved.
4
* This program is free software; you can redistribute it and/or modify it
5
* under the terms of the GNU Lesser General Public License as published
6
* by the Free Software Foundation version 2.1 and no later version.
8
* This program is distributed in the hope that it will be useful, but
9
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10
* or FITNESS FOR A PARTICULAR PURPOSE. See the Lesser GNU General Public
11
* License for more details.
13
* You should have received a copy of the GNU Lesser General Public License
14
* along with this program; if not, write to the Free Software Foundation, Inc.,
15
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
17
*********************************************************/
20
* @file dndUIX11.cpp --
22
* This class implements stubs for the methods that allow DnD between
26
#define G_LOG_DOMAIN "dndcp"
29
#include "guestDnDCPMgr.hh"
34
#include "copyPasteCompat.h"
37
#include "dndClipboard.h"
39
#include "cpNameUtil.h"
42
#include "eventManager.h"
45
#include <X11/extensions/XTest.h> /* for XTest*() */
46
#include "vmware/guestrpc/tclodefs.h"
49
/* IsXExtensionPointer may be not defined with old Xorg. */
50
#ifndef IsXExtensionPointer
51
#define IsXExtensionPointer 4
55
#include "copyPasteDnDWrapper.h"
62
DnDUIX11::DnDUIX11(ToolsAppCtx *ctx)
67
m_HGGetDataInProgress(false),
69
m_GHDnDInProgress(false),
70
m_GHDnDDataReceived(false),
74
m_fileTransferStarted(false),
78
m_numPendingRequest(0),
81
g_debug("%s: enter\n", __FUNCTION__);
92
g_debug("%s: enter\n", __FUNCTION__);
96
CPClipboard_Destroy(&m_clipboard);
102
* Initialize DnDUIX11 object.
108
g_debug("%s: enter\n", __FUNCTION__);
111
CPClipboard_Init(&m_clipboard);
113
GuestDnDCPMgr *p = GuestDnDCPMgr::GetInstance();
115
m_DnD = p->GetDnDMgr();
118
m_detWnd = new DragDetWnd();
120
g_debug("%s: unable to allocate DragDetWnd object\n", __FUNCTION__);
124
#if defined(DETWNDDEBUG)
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
133
m_detWnd->SetAttributes();
136
SetTargetsAndCallbacks();
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));
148
m_DnD->destCancelChanged.connect(
149
sigc::mem_fun(this, &DnDUIX11::CommonDestCancelCB));
150
m_DnD->privDropChanged.connect(
151
sigc::mem_fun(this, &DnDUIX11::CommonDestPrivateDropCB));
153
m_DnD->updateDetWndChanged.connect(
154
sigc::mem_fun(this, &DnDUIX11::CommonUpdateDetWndCB));
155
m_DnD->moveMouseChanged.connect(
156
sigc::mem_fun(this, &DnDUIX11::CommonUpdateMouseCB));
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));
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));
191
CommonUpdateDetWndCB(false, 0, 0);
192
CommonUpdateUnityDetWndCB(false, 0, false);
211
* Setup targets we support, claim ourselves as a drag destination, and
212
* register callbacks for Gtk+ drag and drop callbacks the platform will
217
DnDUIX11::SetTargetsAndCallbacks()
219
g_debug("%s: enter\n", __FUNCTION__);
221
/* Construct supported target list for HG DnD. */
222
std::list<Gtk::TargetEntry> targets;
225
targets.push_back(Gtk::TargetEntry(DRAG_TARGET_NAME_URI_LIST));
228
targets.push_back(Gtk::TargetEntry(TARGET_NAME_APPLICATION_RTF));
229
targets.push_back(Gtk::TargetEntry(TARGET_NAME_TEXT_RICHTEXT));
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));
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.
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.
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));
258
/* Begin of callbacks issued by common layer code */
262
* Reset Callback to reset dnd ui state.
266
DnDUIX11::CommonResetCB(void)
268
g_debug("%s: entering\n", __FUNCTION__);
269
m_GHDnDDataReceived = false;
270
m_HGGetDataInProgress = false;
271
m_GHDnDInProgress = false;
272
m_effect = DROP_NONE;
275
m_fileTransferStarted = false;
280
/* Source functions for HG DnD. */
284
* Called when host successfully detected a pending HG drag.
286
* param[in] clip cross-platform clipboard
287
* param[in] stagingDir associated staging directory
291
DnDUIX11::CommonDragStartCB(const CPClipboard *clip, std::string stagingDir)
293
Glib::RefPtr<Gtk::TargetList> targets;
294
Gdk::DragAction actions;
295
GdkEventMotion event;
297
CPClipboard_Clear(&m_clipboard);
298
CPClipboard_Copy(&m_clipboard, clip);
300
g_debug("%s: enter\n", __FUNCTION__);
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.
307
SendFakeXEvents(false, true, false, false, false, 0, 0);
308
SendFakeXEvents(true, true, true, false, true, 0, 0);
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.
314
targets = Gtk::TargetList::create(std::list<Gtk::TargetEntry>());
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. */
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()));
325
targets->add(Glib::ustring(pid));
331
if (CPClipboard_ItemExists(&m_clipboard, CPFORMAT_FILECONTENTS)) {
332
if (WriteFileContentsToStagingDir()) {
333
targets->add(Glib::ustring(DRAG_TARGET_NAME_URI_LIST));
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));
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));
349
actions = Gdk::ACTION_COPY | Gdk::ACTION_MOVE;
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;
359
event.state = GDK_BUTTON1_MASK;
361
event.device = gdk_device_get_core_pointer();
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);
378
* Cancel current HG DnD.
382
DnDUIX11::CommonSourceCancelCB(void)
384
g_debug("%s: entering\n", __FUNCTION__);
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.
392
CommonUpdateDetWndCB(true, 0, 0);
393
SendFakeXEvents(true, true, false, true, true, 0, 0);
394
CommonUpdateDetWndCB(false, 0, 0);
396
m_HGGetDataInProgress = false;
397
m_effect = DROP_NONE;
404
* Handle common layer private drop CB.
406
* @param[in] x position to release the mouse button (ignored).
407
* @param[in] y position to release the mouse button (ignored).
409
* @note We ignore the coordinates, because we just need to release the mouse
410
* in its current position.
414
DnDUIX11::CommonDestPrivateDropCB(int32 x,
417
g_debug("%s: entering\n", __FUNCTION__);
418
/* Unity manager in host side may already send the drop into guest. */
419
if (m_GHDnDInProgress) {
422
* Release the mouse button.
424
SendFakeXEvents(false, true, false, false, false, 0, 0);
432
* Cancel current DnD (G->H only).
436
DnDUIX11::CommonDestCancelCB(void)
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);
444
* Show the window, move it to the mouse position, and release the
447
SendFakeXEvents(true, true, false, true, false, 0, 0);
449
m_destDropTime = GetTimeInMillis();
456
* Got drop from host side. Release the mouse button in the detection window
460
DnDUIX11::CommonSourceDropCB(void)
462
g_debug("%s: enter\n", __FUNCTION__);
463
CommonUpdateDetWndCB(true, 0, 0);
466
* Move the mouse to the saved coordinates, and release the mouse button.
468
SendFakeXEvents(false, true, false, false, true, m_mousePosX, m_mousePosY);
469
CommonUpdateDetWndCB(false, 0, 0);
475
* Callback when file transfer is done, which finishes the file
476
* copying from host to guest staging directory.
478
* @param[in] success if true, transfer was successful
482
DnDUIX11::CommonSourceFileCopyDoneCB(bool success)
484
g_debug("%s: %s\n", __FUNCTION__, success ? "success" : "failed");
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.
492
m_HGGetDataInProgress = false;
504
* Shows/hides drag detection windows based on the mask.
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
512
DnDUIX11::CommonUpdateDetWndCB(bool bShow,
516
g_debug("%s: enter 0x%lx show %d x %d y %d\n",
518
(unsigned long) m_detWnd->get_window()->gobj(), bShow, x, y);
520
/* If the window is being shown, move it to the right place. */
522
x = MAX(x - DRAG_DET_WINDOW_WIDTH / 2, 0);
523
y = MAX(y - DRAG_DET_WINDOW_WIDTH / 2, 0);
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);
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.
535
SendFakeMouseMove(x + 2, y + 2);
536
m_detWnd->SetIsVisible(true);
538
g_debug("%s: hide\n", __FUNCTION__);
540
m_detWnd->SetIsVisible(false);
547
* Shows/hides full-screen Unity drag detection window.
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.
555
DnDUIX11::CommonUpdateUnityDetWndCB(bool bShow,
559
g_debug("%s: enter 0x%lx unityID 0x%x\n",
561
(unsigned long) m_detWnd->get_window()->gobj(),
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);
572
g_debug("%s: show, (0, 0, %d, %d)\n", __FUNCTION__, width, height);
574
if (m_detWnd->GetIsVisible() == true) {
578
* Show and move detection window to current mouse position
581
SendFakeXEvents(true, false, true, true, false, 0, 0);
585
g_debug("%s: hide\n", __FUNCTION__);
593
* Move detection windows to current cursor position.
597
DnDUIX11::CommonMoveDetWndToMousePos(void)
599
SendFakeXEvents(true, false, true, true, false, 0, 0);
605
* Handle request from common layer to update mouse position.
607
* @param[in] x x coordinate of pointer
608
* @param[in] y y coordinate of pointer
612
DnDUIX11::CommonUpdateMouseCB(int32 x,
615
// Position the pointer, and record its position.
617
SendFakeXEvents(false, false, false, false, true, x, y);
621
if (m_dc && !m_GHDnDInProgress) {
623
// If we are the context of a DnD, send DnD feedback to the source.
625
DND_DROPEFFECT effect;
626
effect = ToDropEffect((Gdk::DragAction)(m_dc->action));
627
if (effect != m_effect) {
629
g_debug("%s: Updating feedback\n", __FUNCTION__);
630
SourceUpdateFeedback(m_effect);
635
/* Beginning of Gtk+ Callbacks */
638
* Source callbacks from Gtk+. Most are seen only when we are acting as a
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.
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
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
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.
667
DnDUIX11::GtkDestDragMotionCB(const Glib::RefPtr<Gdk::DragContext> &dc,
673
* If this is a Host to Guest drag, we are done here, so return.
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);
684
g_debug("%s: not ignored %ld %ld %ld\n", __FUNCTION__,
685
curTime, m_destDropTime, curTime - m_destDropTime);
687
if (m_inHGDrag || m_HGGetDataInProgress) {
688
g_debug("%s: ignored not in hg drag or not getting hg data\n", __FUNCTION__);
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);
697
if (!m_DnD->IsDnDAllowed()) {
698
g_debug("%s: No dnd allowed!\n", __FUNCTION__);
699
dc->drag_status(dndAction, timeValue);
703
/* Check if dnd began from this vm. */
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.
710
Glib::ListHandle<std::string, Gdk::AtomStringTraits> targets(
711
dc->gobj()->targets, Glib::OWNERSHIP_NONE);
713
std::list<Glib::ustring> as = targets;
714
std::list<Glib::ustring>::iterator result;
716
pid = Str_Asprintf(NULL, "guest-dnd-target %d", static_cast<int>(getpid()));
718
result = std::find(as.begin(), as.end(), std::string(pid));
723
if (result != as.end()) {
724
g_debug("%s: found re-entrant drop target, pid %s\n", __FUNCTION__, pid );
732
* We give preference to the suggested action from the source, and prefer
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;
744
dndAction = (Gdk::DragAction)0;
747
dndAction = (Gdk::DragAction)0;
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__);
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.
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__);
767
g_debug("%s: Multiple drag motions before gh data has been received.\n",
771
g_debug("%s: Invalid drag\n", __FUNCTION__);
780
* "drag_leave" signal handler for GTK. Log the reception of this signal,
781
* but otherwise unhandled in our implementation.
783
* @param[in] dc drag context
784
* @param[in] time time of the drag
788
DnDUIX11::GtkDestDragLeaveCB(const Glib::RefPtr<Gdk::DragContext> &dc,
791
g_debug("%s: enter dc %p, m_dc %p\n", __FUNCTION__,
792
dc ? dc->gobj() : NULL, m_dc ? m_dc : NULL);
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
800
* See http://bugzilla.eng.vmware.com/show_bug.cgi?id=528320
802
if (!m_dc || dc->gobj() != m_dc) {
803
g_debug("%s: calling drag_finish\n", __FUNCTION__);
804
dc->drag_finish(true, false, time);
810
* Gtk+ callbacks that are seen when we are a drag source.
815
* "drag_begin" signal handler for GTK.
817
* @param[in] context drag context
821
DnDUIX11::GtkSourceDragBeginCB(const Glib::RefPtr<Gdk::DragContext>& context)
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();
831
* "drag_data_get" handler for GTK. We don't send drop until we are done.
833
* @param[in] dc drag state
834
* @param[in] selection_data buffer for data
835
* @param[in] info unused
836
* @param[in] time timestamp
838
* @note if the drop has occurred, the files are copied from the guest.
840
*-----------------------------------------------------------------------------
844
DnDUIX11::GtkSourceDragDataGetCB(const Glib::RefPtr<Gdk::DragContext> &dc,
845
Gtk::SelectionData& selection_data,
852
std::string stagingDirName;
855
utf::utf8string hgData;
860
const utf::string target = selection_data.get_target().c_str();
862
selection_data.set(target.c_str(), "");
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,
869
g_debug("%s: not in drag, return\n", __FUNCTION__);
873
if (target == DRAG_TARGET_NAME_URI_LIST &&
874
CPClipboard_GetItem(&m_clipboard, CPFORMAT_FILELIST, &buf, &sz)) {
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());
884
if (!fList.FromCPClipboard(buf, sz)) {
885
g_debug("%s: Can't get data from clipboard\n", __FUNCTION__);
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;
897
g_debug("%s: Unknown request target: %s\n", __FUNCTION__,
898
selection_data.get_target().c_str());
902
/* Provide path within vmblock file system instead of actual path. */
903
hgData = fList.GetRelPathsStr();
905
/* Provide URIs for each path in the guest's file list. */
906
while ((str = GetNextPath(hgData, index).c_str()).length() != 0) {
908
if (DnD_BlockIsReady(m_blockCtrl)) {
909
uriList += m_blockCtrl->blockRoot;
910
uriList += DIRSEPS + stagingDirName + DIRSEPS + str + post;
912
uriList += DIRSEPS + m_HGStagingDir + DIRSEPS + str + post;
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
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.
933
if (!m_blockAdded && m_inHGDrag && !m_fileTransferStarted) {
934
m_HGGetDataInProgress = true;
935
m_fileTransferStarted = true;
938
g_debug("%s: not calling AddBlock\n", __FUNCTION__);
940
selection_data.set(DRAG_TARGET_NAME_URI_LIST, uriList.c_str());
941
g_debug("%s: exit\n", __FUNCTION__);
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());
950
selection_data.set(DRAG_TARGET_NAME_URI_LIST,
951
m_HGFileContentsUriList.c_str());
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);
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);
973
/* Can not get any valid data, cancel this HG DnD. */
974
g_debug("%s: no valid data for HG DnD\n", __FUNCTION__);
981
* "drag_end" handler for GTK. Received by drag source.
983
* @param[in] dc drag state
987
DnDUIX11::GtkSourceDragEndCB(const Glib::RefPtr<Gdk::DragContext> &dc)
989
g_debug("%s: entering dc %p, m_dc %p\n", __FUNCTION__,
990
dc ? dc->gobj() : NULL, m_dc ? m_dc : NULL);
993
* We may see a drag end for the previous DnD, but after a new
994
* DnD has started. If so, ignore it.
996
if (m_dc && dc && (m_dc != dc->gobj())) {
997
g_debug("%s: got old dc (new DnD started), ignoring\n", __FUNCTION__);
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
1006
if (!m_fileTransferStarted || !m_HGGetDataInProgress) {
1012
/* Gtk+ callbacks seen when we are a drag destination. */
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.
1019
* This is for G->H DnD.
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
1027
* @param[in] time the timestamp at which the data was received.
1031
DnDUIX11::GtkDestDragDataReceivedCB(const Glib::RefPtr<Gdk::DragContext> &dc,
1034
const Gtk::SelectionData& sd,
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__);
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.
1052
if (SetCPClipboardFromGtk(sd) == false) {
1053
g_debug("%s: Failed to set CP clipboard.\n", __FUNCTION__);
1058
m_numPendingRequest--;
1059
if (m_numPendingRequest > 0) {
1063
if (CPClipboard_IsEmpty(&m_clipboard)) {
1064
g_debug("%s: Failed getting item.\n", __FUNCTION__);
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.
1077
* Note that we prevent against sending multiple "dragStart"s or "drop"s for
1080
if (!m_GHDnDDataReceived) {
1081
g_debug("%s: Drag entering.\n", __FUNCTION__);
1082
m_GHDnDDataReceived = true;
1085
g_debug("%s: not !m_GHDnDDataReceived\n", __FUNCTION__);
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.
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
1100
* @return true on success, false otherwise.
1104
DnDUIX11::GtkDestDragDropCB(const Glib::RefPtr<Gdk::DragContext> &dc,
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);
1112
Glib::ustring target;
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);
1119
g_debug("%s: No valid data on clipboard.\n", __FUNCTION__);
1123
if (CPClipboard_IsEmpty(&m_clipboard)) {
1124
g_debug("%s: No valid data on m_clipboard.\n", __FUNCTION__);
1131
/* General utility functions */
1135
* Try to construct cross-platform clipboard data from selection data
1136
* provided to us by Gtk+.
1138
* @param[in] sd Gtk selection data to convert to CP clipboard data
1140
* @return false on failure, true on success
1144
DnDUIX11::SetCPClipboardFromGtk(const Gtk::SelectionData& sd) // IN
1150
DnDFileList fileList;
1152
uint64 totalSize = 0;
1155
const utf::string target = sd.get_target().c_str();
1157
/* Try to get file list. */
1158
if (m_DnD->CheckCapability(DND_CP_CAP_FILE_DND) && target == DRAG_TARGET_NAME_URI_LIST) {
1160
* Turn the uri list into two \0 delimited lists. One for full paths and
1161
* one for just the last path component.
1163
utf::string source = sd.get_data_as_string().c_str();
1164
g_debug("%s: Got file list: [%s]\n", __FUNCTION__, source.c_str());
1166
if (sd.get_data_as_string().length() == 0) {
1167
g_debug("%s: empty file list!\n", __FUNCTION__);
1172
* In gnome, before file list there may be a extra line indicating it
1175
if (source.length() >= 5 && source.compare(0, 5, "copy\n") == 0) {
1176
source = source.erase(0, 5);
1179
if (source.length() >= 4 && source.compare(0, 4, "cut\n") == 0) {
1180
source = source.erase(0, 4);
1183
while (source.length() > 0 &&
1184
(source[0] == '\n' || source[0] == '\r' || source[0] == ' ')) {
1185
source = source.erase(0, 1);
1188
while ((newPath = DnD_UriListGetNextFile(source.c_str(),
1190
&newPathLen)) != NULL) {
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);
1197
g_debug("%s: g_file_new_for_uri failed\n", __FUNCTION__);
1200
newPath = g_file_get_path(file);
1201
g_object_unref(file);
1203
g_debug("%s: g_file_get_path failed\n", __FUNCTION__);
1209
* Parse relative path.
1211
newRelPath = Str_Strrchr(newPath, DIRSEPC) + 1; // Point to char after '/'
1213
/* Keep track of how big the dnd files are. */
1214
if ((size = File_GetSizeEx(newPath)) >= 0) {
1217
g_debug("%s: unable to get file size for %s\n", __FUNCTION__, newPath);
1219
g_debug("%s: Adding newPath '%s' newRelPath '%s'\n", __FUNCTION__,
1220
newPath, newRelPath);
1221
fileList.AddFile(newPath, newRelPath);
1226
fileList.SetFileSize(totalSize);
1227
if (fileList.ToCPClipboard(&buf, false)) {
1228
CPClipboard_SetItem(&m_clipboard, CPFORMAT_FILELIST, DynBuf_Get(&buf),
1229
DynBuf_GetSize(&buf));
1231
DynBuf_Destroy(&buf);
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());
1248
g_debug("%s: Failed to get text\n", __FUNCTION__);
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());
1266
g_debug("%s: Failed to get text\n", __FUNCTION__ );
1276
* Ask for clipboard data from drag source.
1278
* @param[in] dc Associated drag context
1279
* @param[in] time Time of the request
1281
* @return true if there is any data request, false otherwise.
1285
DnDUIX11::RequestData(const Glib::RefPtr<Gdk::DragContext> &dc,
1288
Glib::RefPtr<Gtk::TargetList> targets;
1289
targets = Gtk::TargetList::create(std::list<Gtk::TargetEntry>());
1291
CPClipboard_Clear(&m_clipboard);
1292
m_numPendingRequest = 0;
1295
* First check file list. If file list is available, all other formats will
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));
1302
m_detWnd->drag_get_data(dc, target, time);
1303
m_numPendingRequest++;
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));
1318
m_detWnd->drag_get_data(dc, target, time);
1319
m_numPendingRequest++;
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));
1329
m_detWnd->drag_get_data(dc, target, time);
1330
m_numPendingRequest++;
1332
return (m_numPendingRequest > 0);
1338
* Try to get last directory name from a full path name.
1340
* @param[in] str pathname to process
1342
* @return last dir name in the full path name if sucess, empty str otherwise
1346
DnDUIX11::GetLastDirName(const std::string &str)
1352
end = str.size() - 1;
1353
if (end >= 0 && DIRSEPC == str[end]) {
1357
if (end <= 0 || str[0] != DIRSEPC) {
1363
while (str[start] != DIRSEPC) {
1367
return str.substr(start + 1, end - start);
1373
* Provide a substring containing the next path from the provided
1374
* NUL-delimited string starting at the provided index.
1376
* @param[in] str NUL-delimited path list
1377
* @param[in] index current index into string
1379
* @return a string with the next path or "" if there are no more paths.
1383
DnDUIX11::GetNextPath(utf::utf8string& str,
1386
utf::utf8string ret;
1389
if (index >= str.length()) {
1393
for (start = index; str[index] != '\0' && index < str.length(); index++) {
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.
1399
static char const Dec2Hex[] = {
1400
'0', '1', '2', '3', '4', '5', '6', '7',
1401
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
1404
unsigned char ubyte = str[index];
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]);
1419
ret = str.substr(start, index - start);
1420
g_debug("%s: nextpath: %s", __FUNCTION__, ret.c_str());
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
1432
* @param[in] x x-coordinate of location to move mouse to.
1433
* @param[in] y y-coordinate of location to move mouse to.
1435
* @return true on success, false on failure.
1439
DnDUIX11::SendFakeMouseMove(const int x,
1442
return SendFakeXEvents(false, false, false, false, true, x, y);
1448
* Fake X mouse events and window movement for the provided Gtk widget.
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.
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
1462
* @note todo this code should be implemented using GDK APIs.
1463
* @note todo this code should be moved into the detection window class
1465
* @return true on success, false on failure.
1469
DnDUIX11::SendFakeXEvents(const bool showWidget,
1470
const bool buttonEvent,
1471
const bool buttonPress,
1472
const bool moveWindow,
1473
const bool coordsProvided,
1480
Display *dndXDisplay;
1490
unsigned int maskReturn;
1495
widget = GetDetWndAsWidget();
1498
g_debug("%s: unable to get widget\n", __FUNCTION__);
1502
dndXDisplay = GDK_WINDOW_XDISPLAY(widget->window);
1503
dndXWindow = GDK_WINDOW_XWINDOW(widget->window);
1504
rootWnd = RootWindow(dndXDisplay, DefaultScreen(dndXDisplay));
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
1512
XSynchronize(dndXDisplay, True);
1515
g_debug("%s: showing Gtk widget\n", __FUNCTION__);
1516
gtk_widget_show(widget);
1517
gdk_window_show(widget->window);
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,
1525
Warning("%s: XQueryPointer() returned False.\n", __FUNCTION__);
1529
g_debug("%s: current mouse is at (%d, %d)\n", __FUNCTION__,
1530
rootXReturn, rootYReturn);
1533
* Position away from the edge of the window.
1535
int width = m_detWnd->GetScreenWidth();
1536
int height = m_detWnd->GetScreenHeight();
1537
bool change = false;
1543
* first do left and top edges.
1557
* next, move result away from right and bottom edges.
1559
if (x > width - 5) {
1563
if (y > height - 5) {
1569
g_debug("%s: adjusting mouse position. root %d, %d, adjusted %d, %d\n",
1570
__FUNCTION__, rootXReturn, rootYReturn, x, y);
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
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);
1588
* Generate mouse movements over the window. The second one makes ungrabs
1589
* happen more reliably on KDE, but isn't necessary on GNOME.
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);
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);
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
1609
if (!XQueryPointer(dndXDisplay, rootWnd, &rootReturn, &childReturn,
1610
&rootXReturn, &rootYReturn, &winXReturn,
1611
&winYReturn, &maskReturn)) {
1612
Warning("%s: XQueryPointer returned False.\n", __FUNCTION__);
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",
1624
ret = TryXTestFakeDeviceButtonEvent();
1626
g_debug("%s: XTestFakeButtonEvent was working for button release.\n",
1636
XSynchronize(dndXDisplay, False);
1642
* Fake X mouse events in device level.
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.
1648
* @return true on success, false on failure.
1652
DnDUIX11::TryXTestFakeDeviceButtonEvent(void)
1654
XDeviceInfo *list = NULL;
1655
XDeviceInfo *list2 = NULL;
1656
XDevice *tdev = NULL;
1657
XDevice *buttonDevice = NULL;
1661
XInputClassInfo *ip = NULL;
1663
Display *dndXDisplay;
1665
widget = GetDetWndAsWidget();
1668
g_debug("%s: unable to get widget\n", __FUNCTION__);
1672
dndXDisplay = GDK_WINDOW_XDISPLAY(widget->window);
1674
/* First get list of all input device. */
1675
if (!(list = XListInputDevices (dndXDisplay, &numDevices))) {
1676
g_debug("%s: XListInputDevices failed\n", __FUNCTION__);
1679
g_debug("%s: XListInputDevices got %d devices\n", __FUNCTION__, numDevices);
1684
for (i = 0; i < numDevices; i++, list++) {
1685
/* We only care about mouse device. */
1686
if (list->use != IsXExtensionPointer) {
1690
tdev = XOpenDevice(dndXDisplay, list->id);
1692
g_debug("%s: XOpenDevice failed\n", __FUNCTION__);
1696
for (ip = tdev->classes, j = 0; j < tdev->num_classes; j++, ip++) {
1697
if (ip->input_class == ButtonClass) {
1698
buttonDevice = tdev;
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;
1710
XCloseDevice(dndXDisplay, tdev);
1712
XFreeDeviceList(list2);
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.
1725
* @return a pointer to the GtkWidget for the detection window, or NULL
1730
DnDUIX11::GetDetWndAsWidget()
1732
GtkInvisible *window;
1733
GtkWidget *widget = NULL;
1738
window = m_detWnd->gobj();
1740
widget = GTK_WIDGET(window);
1748
* Add a block for the current H->G file transfer. Must be paired with a
1749
* call to RemoveBlock() on finish or cancellation.
1753
DnDUIX11::AddBlock()
1755
g_debug("%s: enter\n", __FUNCTION__);
1757
g_debug("%s: block already added\n", __FUNCTION__);
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());
1765
m_blockAdded = false;
1766
g_debug("%s: unable to add block dir %s.\n", __FUNCTION__, m_HGStagingDir.c_str());
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.
1779
DnDUIX11::RemoveBlock()
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());
1788
m_blockAdded = false;
1790
g_debug("%s: not removing block m_blockAdded %d m_HGGetDataInProgress %d\n",
1793
m_HGGetDataInProgress);
1800
* Convert a Gdk::DragAction value to its corresponding DND_DROPEFFECT.
1802
* @param[in] the Gdk::DragAction value to return.
1804
* @return the corresponding DND_DROPEFFECT, with DROP_UNKNOWN returned
1805
* if no mapping is supported.
1807
* @note DROP_NONE is not mapped in this function.
1811
DnDUIX11::ToDropEffect(Gdk::DragAction action)
1813
DND_DROPEFFECT effect;
1816
case Gdk::ACTION_COPY:
1817
case Gdk::ACTION_DEFAULT:
1820
case Gdk::ACTION_MOVE:
1823
case Gdk::ACTION_LINK:
1826
case Gdk::ACTION_PRIVATE:
1827
case Gdk::ACTION_ASK:
1829
effect = DROP_UNKNOWN;
1838
* Try to extract file contents from m_clipboard. Write all files to a
1839
* temporary staging directory. Construct uri list.
1841
* @return true if success, false otherwise.
1845
DnDUIX11::WriteFileContentsToStagingDir(void)
1850
CPFileContents fileContents;
1851
CPFileContentsList *contentsList = NULL;
1853
CPFileItem *fileItem = NULL;
1854
Unicode tempDir = NULL;
1858
if (!CPClipboard_GetItem(&m_clipboard, CPFORMAT_FILECONTENTS, &buf, &sz)) {
1862
/* Extract file contents from buf. */
1863
xdrmem_create(&xdrs, (char *)buf, sz, XDR_DECODE);
1864
memset(&fileContents, 0, sizeof fileContents);
1866
if (!xdr_CPFileContents(&xdrs, &fileContents)) {
1867
g_debug("%s: xdr_CPFileContents failed.\n", __FUNCTION__);
1873
contentsList = fileContents.CPFileContents_u.fileContentsV1;
1874
if (!contentsList) {
1875
g_debug("%s: invalid contentsList.\n", __FUNCTION__);
1879
nFiles = contentsList->fileItem.fileItem_len;
1881
g_debug("%s: invalid nFiles.\n", __FUNCTION__);
1885
fileItem = contentsList->fileItem.fileItem_val;
1887
g_debug("%s: invalid fileItem.\n", __FUNCTION__);
1892
* Write files into a temporary staging directory. These files will be moved
1893
* to final destination, or deleted on next reboot.
1895
tempDir = DnD_CreateStagingDirectory();
1897
g_debug("%s: DnD_CreateStagingDirectory failed.\n", __FUNCTION__);
1901
m_HGFileContentsUriList = "";
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;
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);
1918
* '\0' is used as directory separator in cross-platform name. Now turn
1919
* '\0' in data into DIRSEPC.
1921
* Note that we don't convert the final '\0' into DIRSEPC so the string
1922
* is NUL terminated.
1924
CPNameUtil_CharReplace(fileItem[i].cpName.cpName_val,
1925
fileItem[i].cpName.cpName_len - 1,
1928
fileName = fileItem[i].cpName.cpName_val;
1929
filePathName = tempDir;
1930
filePathName += DIRSEPS + fileName;
1932
if (fileItem[i].validFlags & CP_FILE_VALID_TYPE &&
1933
CP_FILE_TYPE_DIRECTORY == fileItem[i].type) {
1934
if (!File_CreateDirectory(filePathName.c_str())) {
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;
1944
FileIO_Invalidate(&file);
1946
fileErr = FileIO_Open(&file,
1947
filePathName.c_str(),
1948
FILEIO_ACCESS_WRITE,
1949
FILEIO_OPEN_CREATE_EMPTY);
1950
if (!FileIO_IsSuccess(fileErr)) {
1954
fileErr = FileIO_Write(&file,
1955
fileItem[i].content.content_val,
1956
fileItem[i].content.content_len,
1959
FileIO_Close(&file);
1960
g_debug("%s: created file [%s].\n",
1961
__FUNCTION__, filePathName.c_str());
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.
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;
1981
if (!File_SetTimes(filePathName.c_str(),
1986
/* Not a critical error, only log it. */
1987
g_debug("%s: File_SetTimes failed with file [%s].\n",
1988
__FUNCTION__, filePathName.c_str());
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());
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.
2005
if (fileName.find(DIRSEPS, 0) == utf::string::npos) {
2006
m_HGFileContentsUriList += "file://" + filePathName + "\r\n";
2009
g_debug("%s: created uri list [%s].\n",
2010
__FUNCTION__, m_HGFileContentsUriList.c_str());
2014
xdr_free((xdrproc_t) xdr_CPFileContents, (char *)&fileContents);
2015
if (tempDir && !ret) {
2016
DnD_DeleteStagingFiles(tempDir, false);
2024
* Tell host that we are done with HG DnD initialization.
2028
DnDUIX11::SourceDragStartDone(void)
2030
g_debug("%s: enter\n", __FUNCTION__);
2032
m_DnD->SrcUIDragBeginDone();
2037
* Set block control member.
2039
* @param[in] block control as setup by vmware-user.
2043
DnDUIX11::SetBlockControl(DnDBlockControl *blockCtrl)
2045
m_blockCtrl = blockCtrl;
2050
* Got feedback from our DropSource, send it over to host. Called by
2051
* drag motion callback.
2053
* @param[in] effect feedback to send to the UI-independent DnD layer.
2057
DnDUIX11::SourceUpdateFeedback(DND_DROPEFFECT effect)
2059
g_debug("%s: entering\n", __FUNCTION__);
2060
m_DnD->SrcUIUpdateFeedback(effect);
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.
2070
DnDUIX11::TargetDragEnter(void)
2072
g_debug("%s: entering\n", __FUNCTION__);
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);
2081
* Show the window, and position it under the current mouse position.
2082
* This is particularly important for KDE 3.5 guests.
2084
SendFakeXEvents(true, false, true, true, false, 0, 0);
2089
* Get Unix time in milliseconds. See man 2 gettimeofday for details.
2091
* @return unix time in milliseconds.
2095
DnDUIX11::GetTimeInMillis(void)
2099
Hostinfo_GetTimeOfDay(&atime);
2100
return((unsigned long)(atime / 1000));
2105
* Update version information in m_DnD.
2107
* @param[ignored] chan RpcChannel pointer
2108
* @param[in] version the version negotiated with host.
2112
DnDUIX11::VmxDnDVersionChanged(RpcChannel *chan, uint32 version)
2115
m_DnD->VmxDnDVersionChanged(version);
2120
* Track enter events on detection window.
2122
* @param[ignored] event event data (ignored)
2124
* @return always true.
2128
DnDUIX11::GtkEnterEventCB(GdkEventCrossing *ignored)
2130
g_debug("%s: enter\n", __FUNCTION__);
2135
* Track enter events on detection window.
2137
* @param[ignored] event event data (ignored)
2139
* @return always true.
2143
DnDUIX11::GtkLeaveEventCB(GdkEventCrossing *ignored)
2145
g_debug("%s: enter\n", __FUNCTION__);
2150
* Track enter events on detection window.
2152
* @param[ignored] event event data (ignored)
2154
* @return always true.
2158
DnDUIX11::GtkMapEventCB(GdkEventAny *event)
2160
g_debug("%s: enter\n", __FUNCTION__);
2166
* Track enter events on detection window.
2168
* @param[ignored] event event data (ignored)
2170
* @return always true.
2174
DnDUIX11::GtkUnmapEventCB(GdkEventAny *event)
2176
g_debug("%s: enter\n", __FUNCTION__);
2182
* Track realize events on detection window.
2186
DnDUIX11::GtkRealizeEventCB()
2188
g_debug("%s: enter\n", __FUNCTION__);
2193
* Track unrealize events on detection window.
2197
DnDUIX11::GtkUnrealizeEventCB()
2199
g_debug("%s: enter\n", __FUNCTION__);
2203
* Track motion notify events on detection window.
2205
* @param[in] event event data
2207
* @return always true.
2211
DnDUIX11::GtkMotionNotifyEventCB(GdkEventMotion *event)
2213
g_debug("%s: enter x %f y %f state 0x%x\n", __FUNCTION__,
2214
event->x, event->y, event->state);
2220
* Track configure events on detection window.
2222
* @param[in] event event data
2224
* @return always true.
2228
DnDUIX11::GtkConfigureEventCB(GdkEventConfigure *event)
2230
g_debug("%s: enter x %d y %d width %d height %d\n",
2231
__FUNCTION__, event->x, event->y, event->width, event->height);
2237
* Track button press events on detection window.
2239
* @param[ignored] event event data (ignored)
2241
* @return always true.
2245
DnDUIX11::GtkButtonPressEventCB(GdkEventButton *event)
2247
g_debug("%s: enter\n", __FUNCTION__);
2253
* Track button release events on detection window.
2255
* @param[ignored] event event data (ignored)
2257
* @return always true.
2261
DnDUIX11::GtkButtonReleaseEventCB(GdkEventButton *event)
2263
g_debug("%s: enter\n", __FUNCTION__);