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
*********************************************************/
22
* This class implements stubs for the methods that allow DnD between
29
#include "vmwareuserInt.h"
34
#include "dndClipboard.h"
37
#include "cpNameUtil.h"
40
#include "eventManager.h"
43
#include <X11/extensions/XTest.h> /* for XTest*() */
44
#include "vmware/guestrpc/tclodefs.h"
48
#include "copyPasteDnDWrapper.h"
55
DnDUI::DnDUI(DblLnkLst_Links *eventQueue)
56
: m_eventQueue(eventQueue),
60
m_HGGetDataInProgress(false),
62
m_GHDnDInProgress(false),
63
m_GHDnDDataReceived(false),
73
Debug("%s: enter\n", __FUNCTION__);
84
Debug("%s: enter\n", __FUNCTION__);
91
CPClipboard_Destroy(&m_clipboard);
97
* Initialize DnDUI object.
103
Debug("%s: enter\n", __FUNCTION__);
108
ASSERT(m_eventQueue);
109
CPClipboard_Init(&m_clipboard);
110
m_DnD = new DnD(m_eventQueue);
112
Debug("%s: unable to allocate DnD object\n", __FUNCTION__);
115
m_detWnd = new DragDetWnd();
117
Debug("%s: unable to allocate DragDetWnd object\n", __FUNCTION__);
121
#if defined(DETWNDDEBUG)
124
* This code can only be called when DragDetWnd is derived from
125
* Gtk::Window. The normal case is that DragDetWnd is an instance of
126
* Gtk::Invisible, which doesn't implement the methods that SetAttributes
130
m_detWnd->SetAttributes();
133
SetTargetsAndCallbacks();
135
/* Exchange dnd version information with the VMX */
136
if (!RpcOut_sendOne(NULL, NULL, "tools.capability.dnd_version 3")) {
137
Debug("%s: could not set guest dnd version capability\n", __FUNCTION__);
140
if (!RpcOut_sendOne(&reply, &replyLen, "vmx.capability.dnd_version")) {
141
Debug("%s: could not get VMX dnd version capability\n",
144
} else if (atoi(reply) < 3) {
145
Debug("%s: VMX DnD version is less than 3.\n", __FUNCTION__);
150
Debug("%s: VMX version ok: %d\n", __FUNCTION__, atoi(reply));
152
/* Set common layer callbacks. */
153
m_DnD->dragStartChanged.connect(
154
sigc::mem_fun(this, &DnDUI::CommonDragStartCB));
155
m_DnD->fileCopyDoneChanged.connect(
156
sigc::mem_fun(this, &DnDUI::CommonSourceFileCopyDoneCB));
157
m_DnD->updateDetWndChanged.connect(
158
sigc::mem_fun(this, &DnDUI::CommonUpdateDetWndCB));
159
m_DnD->updateUnityDetWndChanged.connect(
160
sigc::mem_fun(this, &DnDUI::CommonUpdateUnityDetWndCB));
161
m_DnD->moveDetWndToMousePos.connect(
162
sigc::mem_fun(this, &DnDUI::CommonMoveDetWndToMousePos));
163
m_DnD->sourceCancelChanged.connect(
164
sigc::mem_fun(this, &DnDUI::CommonSourceCancelCB));
165
m_DnD->targetPrivateDropChanged.connect(
166
sigc::mem_fun(this, &DnDUI::CommonDestPrivateDropCB));
167
m_DnD->ghCancel.connect(
168
sigc::mem_fun(this, &DnDUI::CommonDestCancelCB));
169
m_DnD->sourceDropChanged.connect(
170
sigc::mem_fun(this, &DnDUI::CommonSourceDropCB));
171
m_DnD->updateMouseChanged.connect(
172
sigc::mem_fun(this, &DnDUI::CommonUpdateMouseCB));
174
/* Set Gtk+ callbacks for source. */
175
m_detWnd->signal_drag_begin().connect(
176
sigc::mem_fun(this, &DnDUI::GtkSourceDragBeginCB));
177
m_detWnd->signal_drag_data_get().connect(
178
sigc::mem_fun(this, &DnDUI::GtkSourceDragDataGetCB));
179
m_detWnd->signal_drag_end().connect(
180
sigc::mem_fun(this, &DnDUI::GtkSourceDragEndCB));
182
CommonUpdateDetWndCB(false, 0, 0);
183
CommonUpdateUnityDetWndCB(false, 0, false);
205
* Setup targets we support, claim ourselves as a drag destination, and
206
* register callbacks for Gtk+ drag and drop callbacks the platform will
211
DnDUI::SetTargetsAndCallbacks()
213
Debug("%s: enter\n", __FUNCTION__);
215
/* Construct supported target list for HG DnD. */
216
std::list<Gtk::TargetEntry> targets;
219
targets.push_back(Gtk::TargetEntry(DRAG_TARGET_NAME_URI_LIST));
222
targets.push_back(Gtk::TargetEntry(TARGET_NAME_APPLICATION_RTF));
223
targets.push_back(Gtk::TargetEntry(TARGET_NAME_TEXT_RICHTEXT));
225
/* Plain text DnD. */
226
targets.push_back(Gtk::TargetEntry(TARGET_NAME_STRING));
227
targets.push_back(Gtk::TargetEntry(TARGET_NAME_TEXT_PLAIN));
228
targets.push_back(Gtk::TargetEntry(TARGET_NAME_UTF8_STRING));
229
targets.push_back(Gtk::TargetEntry(TARGET_NAME_COMPOUND_TEXT));
232
* We don't want Gtk handling any signals for us, we want to
233
* do it ourselves based on the results from the guest.
235
* Second argument in drag_dest_set defines the automatic behaviour options
236
* of the destination widget. We used to not define it (0) and in some
237
* distributions (like Ubuntu 6.10) DragMotion only get called once,
238
* and not send updated mouse position to guest, and also got cancel
239
* signal when user drop the file (bug 175754). With flag DEST_DEFAULT_MOTION
240
* the bug is fixed. Almost all other example codes use DEST_DEFAULT_ALL
241
* but in our case, we will call drag_get_data during DragMotion, and
242
* will cause X dead with DEST_DEFAULT_ALL. The reason is unclear.
244
m_detWnd->drag_dest_set(targets, Gtk::DEST_DEFAULT_MOTION,
245
Gdk::ACTION_COPY | Gdk::ACTION_MOVE);
246
m_detWnd->signal_drag_leave().connect(sigc::mem_fun(this, &DnDUI::GtkDestDragLeaveCB));
247
m_detWnd->signal_drag_motion().connect(sigc::mem_fun(this, &DnDUI::GtkDestDragMotionCB));
248
m_detWnd->signal_drag_drop().connect(sigc::mem_fun(this, &DnDUI::GtkDestDragDropCB));
249
m_detWnd->signal_drag_data_received().connect(sigc::mem_fun(this, &DnDUI::GtkDestDragDataReceivedCB));
252
/* Begin of callbacks issued by common layer code */
256
* Reset Callback to reset dnd ui state.
260
DnDUI::CommonResetCB(void)
262
Debug("%s: entering\n", __FUNCTION__);
263
m_GHDnDDataReceived = false;
264
m_HGGetDataInProgress = false;
265
m_GHDnDInProgress = false;
266
m_effect = DROP_NONE;
276
* Cancel any DnD file transfers and reset.
282
Debug("%s: enter\n", __FUNCTION__);
285
* If we don't do this, the destination will have something to
286
* copy, and likely truncated. So remove it.
288
DnD_DeleteStagingFiles(m_HGStagingDir.c_str(), false);
294
/* Source functions for HG DnD. */
298
* Called when host successfully detected a pending HG drag.
300
* param[in] clip cross-platform clipboard
301
* param[in] stagingDir associated staging directory
305
DnDUI::CommonDragStartCB(const CPClipboard *clip, std::string stagingDir)
307
Glib::RefPtr<Gtk::TargetList> targets;
308
Gdk::DragAction actions;
309
GdkEventMotion event;
311
CPClipboard_Clear(&m_clipboard);
312
CPClipboard_Copy(&m_clipboard, clip);
314
Debug("%s: enter\n", __FUNCTION__);
317
* Before the DnD, we should make sure that the mouse is released
318
* otherwise it may be another DnD, not ours. Send a release, then
319
* a press here to cover this case.
321
SendFakeXEvents(false, true, false, false, false, 0, 0);
322
SendFakeXEvents(true, true, true, false, true, 0, 0);
325
* Construct the target and action list, as well as a fake motion notify
326
* event that's consistent with one that would typically start a drag.
328
targets = Gtk::TargetList::create(std::list<Gtk::TargetEntry>());
330
if (CPClipboard_ItemExists(&m_clipboard, CPFORMAT_FILELIST)) {
331
m_HGStagingDir = stagingDir;
332
if (!m_HGStagingDir.empty()) {
333
targets->add(Glib::ustring(DRAG_TARGET_NAME_URI_LIST));
334
/* Add private data to tag dnd as originating from this vm. */
336
Debug("%s: adding re-entrant drop target, pid %d\n", __FUNCTION__, getpid());
337
pid = Str_Asprintf(NULL, "guest-dnd-target %d", static_cast<int>(getpid()));
339
targets->add(Glib::ustring(pid));
345
if (CPClipboard_ItemExists(&m_clipboard, CPFORMAT_FILECONTENTS)) {
346
if (WriteFileContentsToStagingDir()) {
347
targets->add(Glib::ustring(DRAG_TARGET_NAME_URI_LIST));
351
if (CPClipboard_ItemExists(&m_clipboard, CPFORMAT_TEXT)) {
352
targets->add(Glib::ustring(TARGET_NAME_STRING));
353
targets->add(Glib::ustring(TARGET_NAME_TEXT_PLAIN));
354
targets->add(Glib::ustring(TARGET_NAME_UTF8_STRING));
355
targets->add(Glib::ustring(TARGET_NAME_COMPOUND_TEXT));
358
if (CPClipboard_ItemExists(&m_clipboard, CPFORMAT_RTF)) {
359
targets->add(Glib::ustring(TARGET_NAME_APPLICATION_RTF));
360
targets->add(Glib::ustring(TARGET_NAME_TEXT_RICHTEXT));
363
actions = Gdk::ACTION_COPY | Gdk::ACTION_MOVE;
365
/* TODO set the x/y coords to the actual drag initialization point. */
366
event.type = GDK_MOTION_NOTIFY;
367
event.window = m_detWnd->get_window()->gobj();
368
event.send_event = false;
369
event.time = GDK_CURRENT_TIME;
373
event.state = GDK_BUTTON1_MASK;
375
event.device = gdk_device_get_core_pointer();
379
/* Tell Gtk that a drag should be started from this widget. */
380
m_detWnd->drag_begin(targets, actions, 1, (GdkEvent *)&event);
381
m_blockAdded = false;
383
SourceDragStartDone();
384
/* Initialize host hide feedback to DROP_NONE. */
385
m_effect = DROP_NONE;
386
SourceUpdateFeedback(m_effect);
392
* Cancel current HG DnD.
396
DnDUI::CommonSourceCancelCB(void)
398
Debug("%s: entering\n", __FUNCTION__);
401
* Force the window to show, position the mouse over it, and release.
402
* Seems like moving the window to 0, 0 eliminates frequently observed
403
* flybacks when we cancel as user moves mouse in and out of destination
404
* window in a H->G DnD.
406
CommonUpdateDetWndCB(true, 0, 0);
407
SendFakeXEvents(true, true, false, true, true, 0, 0);
408
CommonUpdateDetWndCB(false, 0, 0);
410
m_HGGetDataInProgress = false;
411
m_effect = DROP_NONE;
418
* Handle common layer private drop CB.
420
* @param[in] x position to release the mouse button (ignored).
421
* @param[in] y position to release the mouse button (ignored).
423
* @note We ignore the coordinates, because we just need to release the mouse
424
* in its current position.
428
DnDUI::CommonDestPrivateDropCB(int32 x,
431
Debug("%s: entering\n", __FUNCTION__);
432
/* Unity manager in host side may already send the drop into guest. */
433
if (m_GHDnDInProgress) {
436
* Release the mouse button.
438
SendFakeXEvents(false, true, false, false, false, 0, 0);
446
* Cancel current DnD (G->H only).
450
DnDUI::CommonDestCancelCB(void)
452
Debug("%s: entering\n", __FUNCTION__);
453
/* Unity manager in host side may already send the drop into guest. */
454
if (m_GHDnDInProgress) {
455
CommonUpdateDetWndCB(true, 0, 0);
458
* Show the window, move it to the mouse position, and release the
461
SendFakeXEvents(true, true, false, true, false, 0, 0);
463
m_destDropTime = GetTimeInMillis();
470
* Got drop from host side. Release the mouse button in the detection window
474
DnDUI::CommonSourceDropCB(void)
476
Debug("%s: enter\n", __FUNCTION__);
477
CommonUpdateDetWndCB(true, 0, 0);
480
* Move the mouse to the saved coordinates, and release the mouse button.
482
SendFakeXEvents(false, true, false, false, true, m_mousePosX, m_mousePosY);
483
CommonUpdateDetWndCB(false, 0, 0);
489
* Callback when file transfer is done, which finishes the file
490
* copying from host to guest staging directory.
492
* @param[in] success if true, transfer was successful
493
* @param[in] path of staging dir (which will have a block that needs removing)
497
DnDUI::CommonSourceFileCopyDoneCB(bool success,
498
std::vector<uint8> stagingDir)
500
Debug("%s: %s\n", __FUNCTION__, success ? "success" : "failed");
501
/* Copied files are already removed in common layer. */
504
m_HGGetDataInProgress = false;
510
* Shows/hides drag detection windows based on the mask.
512
* @param[in] bShow if true, show the window, else hide it.
513
* @param[in] x x-coordinate to which the detection window needs to be moved
514
* @param[in] y y-coordinate to which the detection window needs to be moved
518
DnDUI::CommonUpdateDetWndCB(bool bShow,
522
Debug("%s: enter 0x%lx show %d x %d y %d\n",
524
(unsigned long) m_detWnd->get_window()->gobj(), bShow, x, y);
526
/* If the window is being shown, move it to the right place. */
528
x = MAX(x - DRAG_DET_WINDOW_WIDTH / 2, 0);
529
y = MAX(y - DRAG_DET_WINDOW_WIDTH / 2, 0);
533
m_detWnd->SetGeometry(x, y, DRAG_DET_WINDOW_WIDTH * 2, DRAG_DET_WINDOW_WIDTH * 2);
534
Debug("%s: show at (%d, %d, %d, %d)\n", __FUNCTION__, x, y, DRAG_DET_WINDOW_WIDTH * 2, DRAG_DET_WINDOW_WIDTH * 2);
536
* Wiggle the mouse here. Especially for G->H DnD, this improves
537
* reliability of making the drag escape the guest window immensly.
538
* Stolen from the legacy V2 DnD code.
541
SendFakeMouseMove(x, y);
542
m_detWnd->SetIsVisible(true);
544
Debug("%s: hide\n", __FUNCTION__);
546
m_detWnd->SetIsVisible(false);
553
* Shows/hides full-screen Unity drag detection window.
555
* @param[in] bShow if true, show the window, else hide it.
556
* @param[in] unityWndId active front window
557
* @param[in] bottom if true, adjust the z-order to be bottom most.
561
DnDUI::CommonUpdateUnityDetWndCB(bool bShow,
565
Debug("%s: enter 0x%lx unityID 0x%x\n",
567
(unsigned long) m_detWnd->get_window()->gobj(),
569
if (bShow && ((unityWndId > 0) || bottom)) {
570
int width = m_detWnd->GetScreenWidth();
571
int height = m_detWnd->GetScreenHeight();
572
m_detWnd->SetGeometry(0, 0, width, height);
578
Debug("%s: show, (0, 0, %d, %d)\n", __FUNCTION__, width, height);
580
if (m_detWnd->GetIsVisible() == true) {
584
* Show and move detection window to current mouse position
587
SendFakeXEvents(true, false, true, true, false, 0, 0);
591
Debug("%s: hide\n", __FUNCTION__);
599
* Move detection windows to current cursor position.
603
DnDUI::CommonMoveDetWndToMousePos(void)
605
SendFakeXEvents(true, false, true, true, false, 0, 0);
611
* Handle request from common layer to update mouse position.
613
* @param[in] x x coordinate of pointer
614
* @param[in] y y coordinate of pointer
618
DnDUI::CommonUpdateMouseCB(int32 x,
621
// Position the pointer, and record its position.
623
SendFakeXEvents(false, false, false, false, true, x, y);
627
if (m_dc && !m_GHDnDInProgress) {
629
// If we are the context of a DnD, send DnD feedback to the source.
631
DND_DROPEFFECT effect;
632
effect = ToDropEffect((Gdk::DragAction)(m_dc->action));
633
if (effect != m_effect) {
635
Debug("%s: Updating feedback\n", __FUNCTION__);
636
SourceUpdateFeedback(m_effect);
641
/* Beginning of Gtk+ Callbacks */
644
* Source callbacks from Gtk+. Most are seen only when we are acting as a
650
* "drag_motion" signal handler for GTK. We should respond by setting drag
651
* status. Note that there is no drag enter signal. We need to figure out
652
* if a new drag is happening on our own. Also, we don't respond with a
653
* "allowed" drag status right away, we start a new drag operation over VMDB
654
* (which tries to notify the host of the new operation). Once the host has
655
* responded), we respond with a proper drag status.
657
* @param[in] dc associated drag context
658
* @param[in] x x coordinate of the drag motion
659
* @param[in] y y coordinate of the drag motion
660
* @param[in] time time of the drag motion
662
* @return returning false means we won't get notified of future motion. So,
663
* we only return false if we don't recognize the types being offered. We
664
* return true otherwise, even if we don't accept the drag right now for some
667
* @note you may see this callback during DnD when detection window is acting
668
* as a source. In that case it will be ignored. In a future refactoring,
669
* we will try and avoid this.
673
DnDUI::GtkDestDragMotionCB(const Glib::RefPtr<Gdk::DragContext> &dc,
679
* If this is a Host to Guest drag, we are done here, so return.
681
unsigned long curTime = GetTimeInMillis();
682
Debug("%s: enter dc %p, m_dc %p\n", __FUNCTION__,
683
dc ? dc->gobj() : NULL, m_dc ? m_dc : NULL);
684
if (curTime - m_destDropTime <= 1000) {
685
Debug("%s: ignored %ld %ld %ld\n", __FUNCTION__,
686
curTime, m_destDropTime, curTime - m_destDropTime);
690
Debug("%s: not ignored %ld %ld %ld\n", __FUNCTION__,
691
curTime, m_destDropTime, curTime - m_destDropTime);
693
if (m_inHGDrag || m_HGGetDataInProgress) {
694
Debug("%s: ignored not in hg drag or not getting hg data\n", __FUNCTION__);
698
Gdk::DragAction srcActions;
699
Gdk::DragAction suggestedAction;
700
Gdk::DragAction dndAction = (Gdk::DragAction)0;
701
Glib::ustring target = m_detWnd->drag_dest_find_target(dc);
703
if (!m_DnD->IsDnDAllowed()) {
704
Debug("%s: No dnd allowed!\n", __FUNCTION__);
705
dc->drag_status(dndAction, timeValue);
709
/* Check if dnd began from this vm. */
712
* TODO: Once we upgrade to shipping gtkmm 2.12, we can go back to
713
* Gdk::DragContext::get_targets, but API/ABI broke between 2.10 and
714
* 2.12, so we work around it like this for now.
716
Glib::ListHandle<std::string, Gdk::AtomStringTraits> targets(
717
dc->gobj()->targets, Glib::OWNERSHIP_NONE);
719
std::list<Glib::ustring> as = targets;
720
std::list<Glib::ustring>::iterator result;
722
pid = Str_Asprintf(NULL, "guest-dnd-target %d", static_cast<int>(getpid()));
724
result = std::find(as.begin(), as.end(), std::string(pid));
729
if (result != as.end()) {
730
Debug("%s: found re-entrant drop target, pid %s\n", __FUNCTION__, pid );
738
* We give preference to the suggested action from the source, and prefer
741
suggestedAction = dc->get_suggested_action();
742
srcActions = dc->get_actions();
743
if (suggestedAction == Gdk::ACTION_COPY || suggestedAction == Gdk::ACTION_MOVE) {
744
dndAction = suggestedAction;
745
} else if (srcActions & Gdk::ACTION_COPY) {
746
dndAction= Gdk::ACTION_COPY;
747
} else if (srcActions & Gdk::ACTION_MOVE) {
748
dndAction = Gdk::ACTION_MOVE;
750
dndAction = (Gdk::DragAction)0;
753
dndAction = (Gdk::DragAction)0;
756
if (dndAction != (Gdk::DragAction)0) {
757
dc->drag_status(dndAction, timeValue);
758
if (!m_GHDnDInProgress) {
759
Debug("%s: new drag, need to get data for host\n", __FUNCTION__);
761
* This is a new drag operation. We need to start a drag thru the
762
* backdoor, and to the host. Before we can tell the host, we have to
763
* retrieve the drop data.
765
m_GHDnDInProgress = true;
766
/* only begin drag enter after we get the data */
767
/* Need to grab all of the data. */
768
m_detWnd->drag_get_data(dc, target, timeValue);
770
Debug("%s: Multiple drag motions before gh data has been received.\n",
774
Debug("%s: Invalid drag\n", __FUNCTION__);
783
* "drag_leave" signal handler for GTK. Log the reception of this signal,
784
* but otherwise unhandled in our implementation.
786
* @param[in] dc drag context
787
* @param[in] time time of the drag
791
DnDUI::GtkDestDragLeaveCB(const Glib::RefPtr<Gdk::DragContext> &dc,
794
Debug("%s: enter dc %p, m_dc %p\n", __FUNCTION__,
795
dc ? dc->gobj() : NULL, m_dc ? m_dc : NULL);
798
* If we reach here after reset DnD, or we are getting a late
799
* DnD drag leave signal (we have started another DnD), then
800
* finish the old DnD. Otherwise, Gtk will not reset and a new
801
* DnD will not start until Gtk+ times out (which appears to
803
* See http://bugzilla.eng.vmware.com/show_bug.cgi?id=528320
805
if (!m_dc || dc->gobj() != m_dc) {
806
Debug("%s: calling drag_finish\n", __FUNCTION__);
807
dc->drag_finish(true, false, time);
813
* Gtk+ callbacks that are seen when we are a drag source.
818
* "drag_begin" signal handler for GTK.
820
* @param[in] context drag context
824
DnDUI::GtkSourceDragBeginCB(const Glib::RefPtr<Gdk::DragContext>& context)
826
Debug("%s: enter dc %p, m_dc %p\n", __FUNCTION__,
827
context ? context->gobj() : NULL, m_dc ? m_dc : NULL);
828
m_dc = context->gobj();
834
* "drag_data_get" handler for GTK. We don't send drop until we are done.
836
* @param[in] dc drag state
837
* @param[in] selection_data buffer for data
838
* @param[in] info unused
839
* @param[in] time timestamp
841
* @note if the drop has occurred, the files are copied from the guest.
843
*-----------------------------------------------------------------------------
847
DnDUI::GtkSourceDragDataGetCB(const Glib::RefPtr<Gdk::DragContext> &dc,
848
Gtk::SelectionData& selection_data,
855
std::string stagingDirName;
858
utf::utf8string hgData;
863
const utf::string target = selection_data.get_target().c_str();
865
selection_data.set(target.c_str(), "");
867
Debug("%s: enter dc %p, m_dc %p with target %s\n", __FUNCTION__,
868
dc ? dc->gobj() : NULL, m_dc ? m_dc : NULL,
872
Debug("%s: not in drag, return\n", __FUNCTION__);
876
if (target == DRAG_TARGET_NAME_URI_LIST &&
877
CPClipboard_GetItem(&m_clipboard, CPFORMAT_FILELIST, &buf, &sz)) {
879
/* Provide path within vmblock file system instead of actual path. */
880
stagingDirName = GetLastDirName(m_HGStagingDir);
881
if (stagingDirName.length() == 0) {
882
Debug("%s: Cannot get staging directory name, stagingDir: %s\n",
883
__FUNCTION__, m_HGStagingDir.c_str());
887
if (!fList.FromCPClipboard(buf, sz)) {
888
Debug("%s: Can't get data from clipboard\n", __FUNCTION__);
892
/* Provide URIs for each path in the guest's file list. */
893
if (FCP_TARGET_INFO_GNOME_COPIED_FILES == info) {
894
pre = FCP_GNOME_LIST_PRE;
895
post = FCP_GNOME_LIST_POST;
896
} else if (FCP_TARGET_INFO_URI_LIST == info) {
897
pre = DND_URI_LIST_PRE_KDE;
898
post = DND_URI_LIST_POST;
900
Debug("%s: Unknown request target: %s\n", __FUNCTION__,
901
selection_data.get_target().c_str());
905
/* Provide path within vmblock file system instead of actual path. */
906
hgData = fList.GetRelPathsStr();
908
/* Provide URIs for each path in the guest's file list. */
909
while ((str = GetNextPath(hgData, index).c_str()).length() != 0) {
911
if (DnD_BlockIsReady(m_blockCtrl)) {
912
uriList += m_blockCtrl->blockRoot;
913
uriList += DIRSEPS + stagingDirName + DIRSEPS + str + post;
915
uriList += DIRSEPS + m_HGStagingDir + DIRSEPS + str + post;
920
* This seems to be the best place to do the blocking. If we do
921
* it in the source drop callback from the DnD layer, we often
922
* find ourselves adding the block too late; the user will (in
923
* GNOME, in the dest) be told that it could not find the file,
924
* and if you click retry, it is there, meaning the block was
927
* We find ourselves in this callback twice for each H->G DnD.
928
* We *must* always set the selection data, when called, or else
929
* the DnD for that context will fail, but we *must not* add the
930
* block twice or else things get confused. So we add a check to
931
* see if we are in the right state (no block yet added, and we
932
* are in a HG drag still, both must be true) when adding the block.
933
* Doing both of these addresses bug
934
* http://bugzilla.eng.vmware.com/show_bug.cgi?id=391661.
936
if (!m_blockAdded && m_inHGDrag) {
937
m_HGGetDataInProgress = true;
941
Debug("%s: not calling AddBlock\n", __FUNCTION__);
943
selection_data.set(DRAG_TARGET_NAME_URI_LIST, uriList.c_str());
944
Debug("%s: exit\n", __FUNCTION__);
948
if (target == DRAG_TARGET_NAME_URI_LIST &&
949
CPClipboard_ItemExists(&m_clipboard, CPFORMAT_FILECONTENTS)) {
950
Debug("%s: Providing uriList [%s] for file contents DnD\n",
951
__FUNCTION__, m_HGFileContentsUriList.c_str());
953
selection_data.set(DRAG_TARGET_NAME_URI_LIST,
954
m_HGFileContentsUriList.c_str());
958
if ((target == TARGET_NAME_STRING ||
959
target == TARGET_NAME_TEXT_PLAIN ||
960
target == TARGET_NAME_UTF8_STRING ||
961
target == TARGET_NAME_COMPOUND_TEXT) &&
962
CPClipboard_GetItem(&m_clipboard, CPFORMAT_TEXT, &buf, &sz)) {
963
Debug("%s: providing plain text, size %"FMTSZ"u\n", __FUNCTION__, sz);
964
selection_data.set(target.c_str(), (const char *)buf);
968
if ((target == TARGET_NAME_APPLICATION_RTF ||
969
target == TARGET_NAME_TEXT_RICHTEXT) &&
970
CPClipboard_GetItem(&m_clipboard, CPFORMAT_RTF, &buf, &sz)) {
971
Debug("%s: providing rtf text, size %"FMTSZ"u\n", __FUNCTION__, sz);
972
selection_data.set(target.c_str(), (const char *)buf);
976
/* Can not get any valid data, cancel this HG DnD. */
977
Debug("%s: no valid data for HG DnD\n", __FUNCTION__);
978
m_DnD->SourceCancel();
985
* "drag_end" handler for GTK. Received by drag source.
987
* @param[in] dc drag state
991
DnDUI::GtkSourceDragEndCB(const Glib::RefPtr<Gdk::DragContext> &dc)
993
Debug("%s: entering dc %p, m_dc %p\n", __FUNCTION__,
994
dc ? dc->gobj() : NULL, m_dc ? m_dc : NULL);
997
* We may see a drag end for the previous DnD, but after a new
998
* DnD has started. If so, ignore it.
1000
if (m_dc && dc && (m_dc != dc->gobj())) {
1001
Debug("%s: got old dc (new DnD started), ignoring\n", __FUNCTION__);
1006
* If we are a file DnD, don't call CommonResetCB() here, since
1007
* we will do so in the fileCopyDoneChanged callback.
1015
/* Gtk+ callbacks seen when we are a drag destination. */
1019
* "drag_data_received" signal handler for GTK. We requested the drag
1020
* data earlier from some drag source on the guest; this is the response.
1022
* This is for G->H DnD.
1024
* @param[in] dc drag context
1025
* @param[in] x where the drop happened
1026
* @param[in] y where the drop happened
1027
* @param[in] sd the received data
1028
* @param[in] info the info that has been registered with the target in the
1030
* @param[in] time the timestamp at which the data was received.
1034
DnDUI::GtkDestDragDataReceivedCB(const Glib::RefPtr<Gdk::DragContext> &dc,
1037
const Gtk::SelectionData& sd,
1041
Debug("%s: enter dc %p, m_dc %p\n", __FUNCTION__,
1042
dc ? dc->gobj() : NULL, m_dc ? m_dc : NULL);
1043
/* The GH DnD may already finish before we got response. */
1044
if (!m_GHDnDInProgress) {
1045
Debug("%s: not valid\n", __FUNCTION__);
1049
CPClipboard_Clear(&m_clipboard);
1052
* Try to get data provided from the source. If we cannot get any data,
1053
* there is no need to inform the guest of anything. If there is no data,
1054
* reset, so that the next drag_motion callback that we see will be allowed
1055
* to request data again.
1057
if (SetCPClipboardFromGtk(sd) == false) {
1058
Debug("%s: Failed to set CP clipboard.\n", __FUNCTION__);
1062
if (CPClipboard_IsEmpty(&m_clipboard)) {
1063
Debug("%s: Failed getting item.\n", __FUNCTION__);
1069
* There are two points in the DnD process at which this is called, and both
1070
* are in response to us calling drag_data_get(). The first occurs on the
1071
* first "drag_motion" received and is used to start a drag; at that point
1072
* we need to provide the file list to the guest so we request the data from
1073
* the target. The second occurs when the "drag_drop" signal is received
1074
* and we confirm this data with the target before starting the drop.
1076
* Note that we prevent against sending multiple "dragStart"s or "drop"s for
1079
if (!m_GHDnDDataReceived) {
1080
Debug("%s: Drag entering.\n", __FUNCTION__);
1081
m_GHDnDDataReceived = true;
1084
Debug("%s: not !m_GHDnDDataReceived\n", __FUNCTION__);
1091
* "drag_drop" signal handler for GTK. Send the drop to the host (by
1092
* way of the backdoor), then tell the host to get the files.
1094
* @param[in] dc drag context
1095
* @param[in] x x location of the drop
1096
* @param[in] y y location of the drop
1097
* @param[in] time timestamp for the drop
1099
* @return true on success, false otherwise.
1103
DnDUI::GtkDestDragDropCB(const Glib::RefPtr<Gdk::DragContext> &dc,
1108
Debug("%s: enter dc %p, m_dc %p x %d y %d\n", __FUNCTION__,
1109
(dc ? dc->gobj() : NULL), (m_dc ? m_dc : NULL), x, y);
1111
Glib::ustring target;
1113
target = m_detWnd->drag_dest_find_target(dc);
1114
Debug("%s: calling drag_finish\n", __FUNCTION__);
1115
dc->drag_finish(true, false, time);
1118
Debug("%s: No valid data on clipboard.\n", __FUNCTION__);
1122
if (CPClipboard_IsEmpty(&m_clipboard)) {
1123
Debug("%s: No valid data on m_clipboard.\n", __FUNCTION__);
1130
/* General utility functions */
1134
* Try to construct cross-platform clipboard data from selection data
1135
* provided to us by Gtk+.
1137
* @param[in] sd Gtk selection data to convert to CP clipboard data
1139
* @return false on failure, true on success
1143
DnDUI::SetCPClipboardFromGtk(const Gtk::SelectionData& sd) // IN
1149
DnDFileList fileList;
1151
uint64 totalSize = 0;
1154
const utf::string target = sd.get_target().c_str();
1156
/* Try to get file list. */
1157
if (target == DRAG_TARGET_NAME_URI_LIST) {
1159
* Turn the uri list into two \0 delimited lists. One for full paths and
1160
* one for just the last path component.
1162
utf::string source = sd.get_data_as_string().c_str();
1163
Debug("%s: Got file list: [%s]\n", __FUNCTION__, source.c_str());
1165
if (sd.get_data_as_string().length() == 0) {
1166
Debug("%s: empty file list!\n", __FUNCTION__);
1171
* In gnome, before file list there may be a extra line indicating it
1174
if (source.length() >= 5 && source.compare(0, 5, "copy\n") == 0) {
1175
source = source.erase(0, 5);
1178
if (source.length() >= 4 && source.compare(0, 4, "cut\n") == 0) {
1179
source = source.erase(0, 4);
1182
while (source.length() > 0 &&
1183
(source[0] == '\n' || source[0] == '\r' || source[0] == ' ')) {
1184
source = source.erase(0, 1);
1187
while ((newPath = DnD_UriListGetNextFile(source.c_str(),
1189
&newPathLen)) != NULL) {
1192
* Parse relative path.
1194
newRelPath = Str_Strrchr(newPath, DIRSEPC) + 1; // Point to char after '/'
1196
if ((size = File_GetSize(newPath)) >= 0) {
1199
Debug("%s: unable to get file size for %s\n", __FUNCTION__, newPath);
1201
Debug("%s: Adding newPath '%s' newRelPath '%s'\n", __FUNCTION__,
1202
newPath, newRelPath);
1203
fileList.AddFile(newPath, newRelPath);
1208
fileList.SetFileSize(totalSize);
1209
if (fileList.ToCPClipboard(&buf, false)) {
1210
CPClipboard_SetItem(&m_clipboard, CPFORMAT_FILELIST, DynBuf_Get(&buf),
1211
DynBuf_GetSize(&buf));
1213
DynBuf_Destroy(&buf);
1217
/* Try to get plain text. */
1218
if (target == TARGET_NAME_STRING ||
1219
target == TARGET_NAME_TEXT_PLAIN ||
1220
target == TARGET_NAME_UTF8_STRING ||
1221
target == TARGET_NAME_COMPOUND_TEXT) {
1222
utf::string source = sd.get_data_as_string().c_str();
1223
if (source.bytes() > 0 &&
1224
source.bytes() < DNDMSG_MAX_ARGSZ &&
1225
CPClipboard_SetItem(&m_clipboard, CPFORMAT_TEXT, source.c_str(),
1226
source.bytes() + 1)) {
1227
Debug("%s: Got text, size %"FMTSZ"u\n", __FUNCTION__, source.bytes());
1229
Debug("%s: Failed to get text\n", __FUNCTION__);
1235
/* Try to get RTF string. */
1236
if (target == TARGET_NAME_APPLICATION_RTF ||
1237
target == TARGET_NAME_TEXT_RICHTEXT) {
1238
utf::string source = sd.get_data_as_string().c_str();
1239
if (source.bytes() > 0 &&
1240
source.bytes() < DNDMSG_MAX_ARGSZ &&
1241
CPClipboard_SetItem(&m_clipboard, CPFORMAT_RTF, source.c_str(),
1242
source.bytes() + 1)) {
1243
Debug("%s: Got RTF, size %"FMTSZ"u\n", __FUNCTION__, source.bytes());
1246
Debug("%s: Failed to get text\n", __FUNCTION__ );
1256
* Try to get last directory name from a full path name.
1258
* @param[in] str pathname to process
1260
* @return last dir name in the full path name if sucess, empty str otherwise
1264
DnDUI::GetLastDirName(const std::string &str)
1270
end = str.size() - 1;
1271
if (end >= 0 && DIRSEPC == str[end]) {
1275
if (end <= 0 || str[0] != DIRSEPC) {
1281
while (str[start] != DIRSEPC) {
1285
return str.substr(start + 1, end - start);
1291
* Provide a substring containing the next path from the provided
1292
* NUL-delimited string starting at the provided index.
1294
* @param[in] str NUL-delimited path list
1295
* @param[in] index current index into string
1297
* @return a string with the next path or "" if there are no more paths.
1301
DnDUI::GetNextPath(utf::utf8string& str,
1304
utf::utf8string ret;
1307
if (index >= str.length()) {
1311
for (start = index; str[index] != '\0' && index < str.length(); index++) {
1313
* Escape reserved characters according to RFC 1630. We'd use
1314
* Escape_Do() if this wasn't a utf::string, but let's use the same table
1315
* replacement approach.
1317
static char const Dec2Hex[] = {
1318
'0', '1', '2', '3', '4', '5', '6', '7',
1319
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
1322
unsigned char ubyte = str[index];
1324
if (ubyte == '#' || /* Fragment identifier delimiter */
1325
ubyte == '?' || /* Query string delimiter */
1326
ubyte == '*' || /* "Special significance within specific schemes" */
1327
ubyte == '!' || /* "Special significance within specific schemes" */
1328
ubyte == '%' || /* Escape character */
1329
ubyte >= 0x80) { /* UTF-8 encoding bytes */
1330
str.replace(index, 1, "%");
1331
str.insert(index + 1, 1, Dec2Hex[ubyte >> 4]);
1332
str.insert(index + 2, 1, Dec2Hex[ubyte & 0xF]);
1337
ret = str.substr(start, index - start);
1338
Debug("%s: nextpath: %s", __FUNCTION__, ret.c_str());
1346
* Issue a fake mouse move event to the detection window. Code stolen from
1347
* DnD V2 Linux guest implementation, where it was originally defined as a
1350
* @param[in] x x-coordinate of location to move mouse to.
1351
* @param[in] y y-coordinate of location to move mouse to.
1353
* @return true on success, false on failure.
1357
DnDUI::SendFakeMouseMove(const int x,
1360
return SendFakeXEvents(false, false, false, false, true, x, y);
1366
* Fake X mouse events and window movement for the provided Gtk widget.
1368
* This function will optionally show the widget, move the provided widget
1369
* to either the provided location or the current mouse position if no
1370
* coordinates are provided, and cause a button press or release event.
1372
* @param[in] showWidget whether to show Gtk widget
1373
* @param[in] buttonEvent whether to send a button event
1374
* @param[in] buttonPress whether to press or release mouse
1375
* @param[in] moveWindow: whether to move our window too
1376
* @param[in] coordsProvided whether coordinates provided
1377
* @param[in] xCoord x coordinate
1378
* @param[in] yCoord y coordinate
1380
* @note todo this code should be implemented using GDK APIs.
1381
* @note todo this code should be moved into the detection window class
1383
* @return true on success, false on failure.
1387
DnDUI::SendFakeXEvents(const bool showWidget,
1388
const bool buttonEvent,
1389
const bool buttonPress,
1390
const bool moveWindow,
1391
const bool coordsProvided,
1398
Display *dndXDisplay;
1408
unsigned int maskReturn;
1413
widget = GetDetWndAsWidget();
1416
Debug("%s: unable to get widget\n", __FUNCTION__);
1420
dndXDisplay = GDK_WINDOW_XDISPLAY(widget->window);
1421
dndXWindow = GDK_WINDOW_XWINDOW(widget->window);
1422
rootWnd = RootWindow(dndXDisplay, DefaultScreen(dndXDisplay));
1425
* Turn on X synchronization in order to ensure that our X events occur in
1426
* the order called. In particular, we want the window movement to occur
1427
* before the mouse movement so that the events we are coercing do in fact
1430
XSynchronize(dndXDisplay, True);
1433
Debug("%s: showing Gtk widget\n", __FUNCTION__);
1434
gtk_widget_show(widget);
1435
gdk_window_show(widget->window);
1438
/* Get the current location of the mouse if coordinates weren't provided. */
1439
if (!coordsProvided) {
1440
if (!XQueryPointer(dndXDisplay, rootWnd, &rootReturn, &childReturn,
1441
&rootXReturn, &rootYReturn, &winXReturn, &winYReturn,
1443
Warning("%s: XQueryPointer() returned False.\n", __FUNCTION__);
1447
Debug("%s: current mouse is at (%d, %d)\n", __FUNCTION__,
1448
rootXReturn, rootYReturn);
1451
* Position away from the edge of the window.
1453
int width = m_detWnd->GetScreenWidth();
1454
int height = m_detWnd->GetScreenHeight();
1455
bool change = false;
1461
* first do left and top edges.
1475
* next, move result away from right and bottom edges.
1477
if (x > width - 5) {
1481
if (y > height - 5) {
1487
Debug("%s: adjusting mouse position. root %d, %d, adjusted %d, %d\n",
1488
__FUNCTION__, rootXReturn, rootYReturn, x, y);
1494
* Make sure the window is at this point and at the top (raised). The
1495
* window is resized to be a bit larger than we would like to increase
1496
* the likelihood that mouse events are attributed to our window -- this
1497
* is okay since the window is invisible and hidden on cancels and DnD
1500
XMoveResizeWindow(dndXDisplay, dndXWindow, x - 5 , y - 5, 25, 25);
1501
XRaiseWindow(dndXDisplay, dndXWindow);
1502
Debug("%s: move wnd to (%d, %d, %d, %d)\n", __FUNCTION__, x - 5, y - 5 , x + 25, y + 25);
1506
* Generate mouse movements over the window. The second one makes ungrabs
1507
* happen more reliably on KDE, but isn't necessary on GNOME.
1509
XTestFakeMotionEvent(dndXDisplay, -1, x, y, CurrentTime);
1510
XTestFakeMotionEvent(dndXDisplay, -1, x + 1, y + 1, CurrentTime);
1511
Debug("%s: move mouse to (%d, %d) and (%d, %d)\n", __FUNCTION__, x, y, x + 1, y + 1);
1514
Debug("%s: faking left mouse button %s\n", __FUNCTION__,
1515
buttonPress ? "press" : "release");
1516
XTestFakeButtonEvent(dndXDisplay, 1, buttonPress, CurrentTime);
1517
XSync(dndXDisplay, False);
1521
* The button release simulation may be failed with some distributions
1522
* like Ubuntu 10.4 and RHEL 6 for guest->host DnD. So first query
1523
* mouse button status. If some button is still down, we will try
1524
* mouse device level event simulation. For details please refer
1527
if (!XQueryPointer(dndXDisplay, rootWnd, &rootReturn, &childReturn,
1528
&rootXReturn, &rootYReturn, &winXReturn,
1529
&winYReturn, &maskReturn)) {
1530
Warning("%s: XQueryPointer returned False.\n", __FUNCTION__);
1534
if ((maskReturn & Button1Mask) ||
1535
(maskReturn & Button2Mask) ||
1536
(maskReturn & Button3Mask) ||
1537
(maskReturn & Button4Mask) ||
1538
(maskReturn & Button5Mask)) {
1539
Debug("%s: XTestFakeButtonEvent was not working for button "
1540
"release, trying XTestFakeDeviceButtonEvent now.\n",
1542
ret = TryXTestFakeDeviceButtonEvent();
1544
Debug("%s: XTestFakeButtonEvent was working for button release.\n",
1554
XSynchronize(dndXDisplay, False);
1560
* Fake X mouse events in device level.
1562
* XXX The function will only be called if XTestFakeButtonEvent does not work
1563
* for mouse button release. Later on we may only call this one for mouse
1564
* button simulation if this is more reliable.
1566
* @return true on success, false on failure.
1570
DnDUI::TryXTestFakeDeviceButtonEvent(void)
1572
XDeviceInfo *list = NULL;
1573
XDeviceInfo *list2 = NULL;
1574
XDevice *tdev = NULL;
1575
XDevice *buttonDevice = NULL;
1579
XInputClassInfo *ip = NULL;
1581
Display *dndXDisplay;
1583
widget = GetDetWndAsWidget();
1586
Debug("%s: unable to get widget\n", __FUNCTION__);
1590
dndXDisplay = GDK_WINDOW_XDISPLAY(widget->window);
1592
/* First get list of all input device. */
1593
if (!(list = XListInputDevices (dndXDisplay, &numDevices))) {
1594
Debug("%s: XListInputDevices failed\n", __FUNCTION__);
1597
Debug("%s: XListInputDevices got %d devices\n", __FUNCTION__, numDevices);
1602
for (i = 0; i < numDevices; i++, list++) {
1603
/* We only care about mouse device. */
1604
if (list->use != IsXExtensionPointer) {
1608
tdev = XOpenDevice(dndXDisplay, list->id);
1610
Debug("%s: XOpenDevice failed\n", __FUNCTION__);
1614
for (ip = tdev->classes, j = 0; j < tdev->num_classes; j++, ip++) {
1615
if (ip->input_class == ButtonClass) {
1616
buttonDevice = tdev;
1622
Debug("%s: calling XTestFakeDeviceButtonEvent for %s\n",
1623
__FUNCTION__, list->name);
1624
XTestFakeDeviceButtonEvent(dndXDisplay, buttonDevice, 1, False,
1625
NULL, 0, CurrentTime);
1626
buttonDevice = NULL;
1628
XCloseDevice(dndXDisplay, tdev);
1630
XFreeDeviceList(list2);
1637
* Get the GtkWidget pointer for a DragDetWnd object. The X11 Unity
1638
* implementation requires access to the drag detection window as
1639
* a GtkWindow pointer, which it uses to show and hide the detection
1640
* window. This function is also called by the code that issues fake
1641
* X events to the detection window.
1643
* @return a pointer to the GtkWidget for the detection window, or NULL
1648
DnDUI::GetDetWndAsWidget()
1650
GtkInvisible *window;
1651
GtkWidget *widget = NULL;
1656
window = m_detWnd->gobj();
1658
widget = GTK_WIDGET(window);
1666
* Add a block for the current H->G file transfer. Must be paired with a
1667
* call to RemoveBlock() on finish or cancellation.
1673
Debug("%s: enter\n", __FUNCTION__);
1675
Debug("%s: block already added\n", __FUNCTION__);
1678
if (DnD_BlockIsReady(m_blockCtrl) && m_blockCtrl->AddBlock(m_blockCtrl->fd, m_HGStagingDir.c_str())) {
1679
m_blockAdded = true;
1680
Debug("%s: add block for %s.\n", __FUNCTION__, m_HGStagingDir.c_str());
1682
m_blockAdded = false;
1683
Debug("%s: unable to add block dir %s.\n", __FUNCTION__, m_HGStagingDir.c_str());
1690
* Remove block for the current H->G file transfer. Must be paired with a
1691
* call to AddBlock(), but it will only attempt to remove block if one is
1692
* currently in effect.
1696
DnDUI::RemoveBlock()
1698
Debug("%s: enter\n", __FUNCTION__);
1699
if (m_blockAdded && !m_HGGetDataInProgress) {
1700
Debug("%s: removing block for %s\n", __FUNCTION__, m_HGStagingDir.c_str());
1701
m_blockCtrl->RemoveBlock(m_blockCtrl->fd, m_HGStagingDir.c_str());
1702
m_blockAdded = false;
1704
Debug("%s: not removing block m_blockAdded %d m_HGGetDataInProgress %d\n",
1707
m_HGGetDataInProgress);
1714
* Convert a Gdk::DragAction value to its corresponding DND_DROPEFFECT.
1716
* @param[in] the Gdk::DragAction value to return.
1718
* @return the corresponding DND_DROPEFFECT, with DROP_UNKNOWN returned
1719
* if no mapping is supported.
1721
* @note DROP_NONE is not mapped in this function.
1725
DnDUI::ToDropEffect(Gdk::DragAction action)
1727
DND_DROPEFFECT effect;
1730
case Gdk::ACTION_COPY:
1731
case Gdk::ACTION_DEFAULT:
1734
case Gdk::ACTION_MOVE:
1737
case Gdk::ACTION_LINK:
1740
case Gdk::ACTION_PRIVATE:
1741
case Gdk::ACTION_ASK:
1743
effect = DROP_UNKNOWN;
1752
* Try to extract file contents from m_clipboard. Write all files to a
1753
* temporary staging directory. Construct uri list.
1755
* @return true if success, false otherwise.
1759
DnDUI::WriteFileContentsToStagingDir(void)
1764
CPFileContents fileContents;
1765
CPFileContentsList *contentsList = NULL;
1767
CPFileItem *fileItem = NULL;
1768
Unicode tempDir = NULL;
1772
if (!CPClipboard_GetItem(&m_clipboard, CPFORMAT_FILECONTENTS, &buf, &sz)) {
1776
/* Extract file contents from buf. */
1777
xdrmem_create(&xdrs, (char *)buf, sz, XDR_DECODE);
1778
memset(&fileContents, 0, sizeof fileContents);
1780
if (!xdr_CPFileContents(&xdrs, &fileContents)) {
1781
Debug("%s: xdr_CPFileContents failed.\n", __FUNCTION__);
1787
contentsList = fileContents.CPFileContents_u.fileContentsV1;
1788
if (!contentsList) {
1789
Debug("%s: invalid contentsList.\n", __FUNCTION__);
1793
nFiles = contentsList->fileItem.fileItem_len;
1795
Debug("%s: invalid nFiles.\n", __FUNCTION__);
1799
fileItem = contentsList->fileItem.fileItem_val;
1801
Debug("%s: invalid fileItem.\n", __FUNCTION__);
1806
* Write files into a temporary staging directory. These files will be moved
1807
* to final destination, or deleted on next reboot.
1809
tempDir = DnD_CreateStagingDirectory();
1811
Debug("%s: DnD_CreateStagingDirectory failed.\n", __FUNCTION__);
1815
m_HGFileContentsUriList = "";
1817
for (i = 0; i < nFiles; i++) {
1818
utf::string fileName;
1819
utf::string filePathName;
1820
VmTimeType createTime = -1;
1821
VmTimeType accessTime = -1;
1822
VmTimeType writeTime = -1;
1823
VmTimeType attrChangeTime = -1;
1825
if (!fileItem[i].cpName.cpName_val ||
1826
0 == fileItem[i].cpName.cpName_len) {
1827
Debug("%s: invalid fileItem[%"FMTSZ"u].cpName.\n", __FUNCTION__, i);
1832
* '\0' is used as directory separator in cross-platform name. Now turn
1833
* '\0' in data into DIRSEPC.
1835
* Note that we don't convert the final '\0' into DIRSEPC so the string
1836
* is NUL terminated.
1838
CPNameUtil_CharReplace(fileItem[i].cpName.cpName_val,
1839
fileItem[i].cpName.cpName_len - 1,
1842
fileName = fileItem[i].cpName.cpName_val;
1843
filePathName = tempDir;
1844
filePathName += DIRSEPS + fileName;
1846
if (fileItem[i].validFlags & CP_FILE_VALID_TYPE &&
1847
CP_FILE_TYPE_DIRECTORY == fileItem[i].type) {
1848
if (!File_CreateDirectory(filePathName.c_str())) {
1851
Debug("%s: created directory [%s].\n",
1852
__FUNCTION__, filePathName.c_str());
1853
} else if (fileItem[i].validFlags & CP_FILE_VALID_TYPE &&
1854
CP_FILE_TYPE_REGULAR == fileItem[i].type) {
1855
FileIODescriptor file;
1856
FileIOResult fileErr;
1858
FileIO_Invalidate(&file);
1860
fileErr = FileIO_Open(&file,
1861
filePathName.c_str(),
1862
FILEIO_ACCESS_WRITE,
1863
FILEIO_OPEN_CREATE_EMPTY);
1864
if (!FileIO_IsSuccess(fileErr)) {
1868
fileErr = FileIO_Write(&file,
1869
fileItem[i].content.content_val,
1870
fileItem[i].content.content_len,
1873
FileIO_Close(&file);
1874
Debug("%s: created file [%s].\n",
1875
__FUNCTION__, filePathName.c_str());
1878
* Right now only Windows can provide CPFORMAT_FILECONTENTS data.
1879
* Symlink file is not expected. Continue with next file if the
1880
* type is not valid.
1885
/* Update file time attributes. */
1886
createTime = fileItem->validFlags & CP_FILE_VALID_CREATE_TIME ?
1887
fileItem->createTime: -1;
1888
accessTime = fileItem->validFlags & CP_FILE_VALID_ACCESS_TIME ?
1889
fileItem->accessTime: -1;
1890
writeTime = fileItem->validFlags & CP_FILE_VALID_WRITE_TIME ?
1891
fileItem->writeTime: -1;
1892
attrChangeTime = fileItem->validFlags & CP_FILE_VALID_CHANGE_TIME ?
1893
fileItem->attrChangeTime: -1;
1895
if (!File_SetTimes(filePathName.c_str(),
1900
/* Not a critical error, only log it. */
1901
Debug("%s: File_SetTimes failed with file [%s].\n",
1902
__FUNCTION__, filePathName.c_str());
1905
/* Update file permission attributes. */
1906
if (fileItem->validFlags & CP_FILE_VALID_PERMS) {
1907
if (Posix_Chmod(filePathName.c_str(),
1908
fileItem->permissions) < 0) {
1909
/* Not a critical error, only log it. */
1910
Debug("%s: Posix_Chmod failed with file [%s].\n",
1911
__FUNCTION__, filePathName.c_str());
1916
* If there is no DIRSEPC inside the fileName, this file/directory is a
1917
* top level one. We only put top level name into uri list.
1919
if (fileName.find(DIRSEPS, 0) == utf::string::npos) {
1920
m_HGFileContentsUriList += "file://" + filePathName + "\r\n";
1923
Debug("%s: created uri list [%s].\n",
1924
__FUNCTION__, m_HGFileContentsUriList.c_str());
1928
xdr_free((xdrproc_t) xdr_CPFileContents, (char *)&fileContents);
1929
if (tempDir && !ret) {
1930
DnD_DeleteStagingFiles(tempDir, false);
1939
* Tell host that we are done with HG DnD initialization.
1943
DnDUI::SourceDragStartDone(void)
1945
Debug("%s: enter\n", __FUNCTION__);
1947
m_DnD->HGDragStartDone();
1952
* Set block control member.
1954
* @param[in] block control as setup by vmware-user.
1958
DnDUI::SetBlockControl(DnDBlockControl *blockCtrl)
1960
m_blockCtrl = blockCtrl;
1966
* Got feedback from our DropSource, send it over to host. Called by
1967
* drag motion callback.
1969
* @param[in] effect feedback to send to the UI-independent DnD layer.
1973
DnDUI::SourceUpdateFeedback(DND_DROPEFFECT effect)
1975
Debug("%s: entering\n", __FUNCTION__);
1976
m_DnD->SetFeedback(effect);
1982
* This is triggered when user drags valid data from guest to host. Try to
1983
* get clip data and notify host to start GH DnD.
1987
DnDUI::TargetDragEnter(void)
1989
Debug("%s: entering\n", __FUNCTION__);
1991
/* Check if there is valid data with current detection window. */
1992
if (!CPClipboard_IsEmpty(&m_clipboard)) {
1993
Debug("%s: got valid data from detWnd.\n", __FUNCTION__);
1994
m_DnD->DragEnter(&m_clipboard);
1998
* Show the window, and position it under the current mouse position.
1999
* This is particularly important for KDE 3.5 guests.
2001
SendFakeXEvents(true, false, true, true, false, 0, 0);
2007
* Get Unix time in milliseconds. See man 2 gettimeofday for details.
2009
* @return unix time in milliseconds.
2013
DnDUI::GetTimeInMillis(void)
2017
Hostinfo_GetTimeOfDay(&atime);
2018
return((unsigned long)(atime / 1000));