1
/*********************************************************
2
* Copyright (C) 2005 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
* copyPasteCompatX11.c --
22
* Set of functions in guest side for copy/paste (both file and text).
23
* Currently there are 2 versions copy/paste. Version 1 only supports
24
* text copy/paste, and based on backdoor cmd. Version 2 supports both
25
* text and file copy/paste, and based on guestRPC.
27
* G->H Text Copy/Paste (version 1)
28
* --------------------
29
* When Ungrab, CopyPaste_RequestSelection got called, which try to get
30
* selection text and send to backdoor.
32
* H->G Text Copy/Paste (version 1)
33
* --------------------
34
* When grab, CopyPaste_GetBackdoorSelections got called, which first
35
* get host selection text, then claim as selection owner. If some app
36
* asks for selection, CopyPasteSelectionGetCB will reply with host
40
#include "dndPluginIntX11.h"
47
#include "vm_assert.h"
50
#include "eventManager.h"
55
#include "cpNameUtil.h"
62
#include "vmware/guestrpc/tclodefs.h"
64
#include "vmware/tools/plugin.h"
67
* Gtk 1.2 doesn't know about the CLIPBOARD selection, but that doesn't matter, we
68
* just create the atom we need directly in main().
70
#ifndef GDK_SELECTION_CLIPBOARD
71
GdkAtom GDK_SELECTION_CLIPBOARD;
74
#ifndef GDK_SELECTION_TYPE_TIMESTAMP
75
GdkAtom GDK_SELECTION_TYPE_TIMESTAMP;
78
#ifndef GDK_SELECTION_TYPE_UTF8_STRING
79
GdkAtom GDK_SELECTION_TYPE_UTF8_STRING;
83
* Currently there are 2 versions copy/paste.
84
* Key points in copy/paste version 1:
85
* 1. Only text copy/paste
86
* 2. copy/paste is based on backdoor directly
88
* Key points in copy/paste version 2:
89
* 1. Support both file/text copy/paste
90
* 2. Both file/text copy/paste are based on guestRPC
92
static int32 gVmxCopyPasteVersion = 1;
95
* Getting a selection is an asyncronous event, so we have to keep track of both
96
* selections globablly in order to decide which one to use.
98
static Bool gWaitingOnGuestSelection = FALSE;
99
static char gGuestSelPrimaryBuf[MAX_SELECTION_BUFFER_LENGTH];
100
static char gGuestSelClipboardBuf[MAX_SELECTION_BUFFER_LENGTH];
101
static uint64 gGuestSelPrimaryTime = 0;
102
static uint64 gGuestSelClipboardTime = 0;
103
static char gHostClipboardBuf[MAX_SELECTION_BUFFER_LENGTH + 1];
105
static Bool gIsOwner;
106
static ToolsAppCtx *gCtx = NULL;
109
* Forward Declarations
111
static gboolean IsCtxMainLoopActive(void);
112
static INLINE void CopyPasteStateInit(void);
113
static void CopyPasteSelectionReceivedCB(GtkWidget *widget,
114
GtkSelectionData *selection_data,
116
static void CopyPasteSelectionGetCB(GtkWidget *widget,
117
GtkSelectionData *selection_data,
121
static gint CopyPasteSelectionClearCB(GtkWidget *widget,
122
GdkEventSelection *event,
125
static void CopyPasteSetBackdoorSelections(void);
127
/* This struct is only used by CopyPasteSelectionRemoveTarget. */
128
struct SelectionTargetList {
135
*-----------------------------------------------------------------------------
137
* CopyPasteSelectionRemoveTarget --
139
* To remove a target from a selection target list. The reason to develop
140
* this function is that in gtk there is only gtk_selection_add_target to
141
* add supported target to selection list, but no function to remove one.
147
* If no more target, the selection list will be removed too.
149
*-----------------------------------------------------------------------------
153
CopyPasteSelectionRemoveTarget(GtkWidget *widget,
157
const char *selection_handler_key = "gtk-selection-handlers";
158
struct SelectionTargetList *targetList;
160
GList *selectionLists;
162
/* Get selection list. */
163
selectionLists = gtk_object_get_data(GTK_OBJECT (widget), selection_handler_key);
164
tempList = selectionLists;
166
/* Enumerate the list to find the selection. */
167
targetList = tempList->data;
168
if (targetList->selection == selection) {
170
gtk_target_list_remove(targetList->list, target);
171
/* If no more target, remove selection from list. */
172
if (!targetList->list->list) {
173
/* Free target list. */
174
gtk_target_list_unref(targetList->list);
176
/* Remove and free selection node. */
177
selectionLists = g_list_remove_link(selectionLists, tempList);
178
g_list_free_1(tempList);
182
tempList = tempList->next;
184
/* Put new selection list back. */
185
gtk_object_set_data (GTK_OBJECT (widget), selection_handler_key, selectionLists);
190
*-----------------------------------------------------------------------------
192
* CopyPaste_RequestSelection --
194
* Request the guest's text clipboard (asynchronously), we'll give it to
195
* the host when the request completes. For version 1 guest->host text
199
* TRUE on success, FALSE otherwise.
202
* The owner of the clipboard will get a request from our application.
204
*-----------------------------------------------------------------------------
208
CopyPaste_RequestSelection(void)
210
if (gVmxCopyPasteVersion > 1) {
215
* Ask for both the PRIMARY and CLIPBOARD selections.
217
gGuestSelPrimaryBuf[0] = '\0';
218
gGuestSelClipboardBuf[0] = '\0';
220
/* Only send out request if we are not the owner. */
222
/* Try to get timestamp for primary and clipboard. */
223
gWaitingOnGuestSelection = TRUE;
224
gtk_selection_convert(gUserMainWidget,
225
GDK_SELECTION_PRIMARY,
226
GDK_SELECTION_TYPE_TIMESTAMP,
228
while (IsCtxMainLoopActive() && gWaitingOnGuestSelection) {
229
gtk_main_iteration();
232
gWaitingOnGuestSelection = TRUE;
233
gtk_selection_convert(gUserMainWidget,
234
GDK_SELECTION_CLIPBOARD,
235
GDK_SELECTION_TYPE_TIMESTAMP,
237
while (IsCtxMainLoopActive() && gWaitingOnGuestSelection) {
238
gtk_main_iteration();
241
/* Try to get utf8 text from primary and clipboard. */
242
gWaitingOnGuestSelection = TRUE;
243
gtk_selection_convert(gUserMainWidget,
244
GDK_SELECTION_PRIMARY,
245
GDK_SELECTION_TYPE_UTF8_STRING,
247
while (IsCtxMainLoopActive() && gWaitingOnGuestSelection) {
248
gtk_main_iteration();
251
gWaitingOnGuestSelection = TRUE;
252
gtk_selection_convert(gUserMainWidget,
253
GDK_SELECTION_CLIPBOARD,
254
GDK_SELECTION_TYPE_UTF8_STRING,
256
while (IsCtxMainLoopActive() && gWaitingOnGuestSelection) {
257
gtk_main_iteration();
260
if (gGuestSelPrimaryBuf[0] == '\0' && gGuestSelClipboardBuf[0] == '\0') {
262
* If we cannot get utf8 text, try to get localized text from primary
265
gWaitingOnGuestSelection = TRUE;
266
gtk_selection_convert(gUserMainWidget,
267
GDK_SELECTION_PRIMARY,
268
GDK_SELECTION_TYPE_STRING,
270
while (IsCtxMainLoopActive() && gWaitingOnGuestSelection) {
271
gtk_main_iteration();
274
gWaitingOnGuestSelection = TRUE;
275
gtk_selection_convert(gUserMainWidget,
276
GDK_SELECTION_CLIPBOARD,
277
GDK_SELECTION_TYPE_STRING,
279
while (IsCtxMainLoopActive() && gWaitingOnGuestSelection) {
280
gtk_main_iteration();
284
/* Send text to host. */
285
g_debug("CopyPaste_RequestSelection: Prim is [%s], Clip is [%s]\n",
286
gGuestSelPrimaryBuf, gGuestSelClipboardBuf);
287
CopyPasteSetBackdoorSelections();
293
* Check to see if we are running in tools main loop.
295
* @return TRUE if we are, FALSE if we are not running in main loop.
299
IsCtxMainLoopActive(void)
302
return g_main_loop_is_running(gCtx->mainLoop);
307
*-----------------------------------------------------------------------------
309
* CopyPasteSelectionReceivedCB --
311
* Callback for the gtk signal "selection_received".
312
* Called because we previously requested a copy/paste selection and
313
* finally got results of that asynchronous operation. After some basic
314
* sanity checks, send the result (in selection_data) thru the backdoor
315
* (version 1) or guestRPC (version 2) so the vmx can copy it to host
318
* We made several requests for selections, the string (actual data) and
319
* file list for each of PRIMARY and CLIPBOARD selections. So this funtion
320
* will get called several times, once for each request.
322
* For guest->host copy/paste (both text and file).
330
*-----------------------------------------------------------------------------
334
CopyPasteSelectionReceivedCB(GtkWidget *widget, // IN: unused
335
GtkSelectionData *selection_data, // IN: requested data
336
gpointer data) // IN: unused
339
char *utf8Str = NULL;
343
if ((widget == NULL) || (selection_data == NULL)) {
344
g_debug("CopyPasteSelectionReceivedCB: Error, widget or selection_data is invalid\n");
348
if (selection_data->length < 0) {
349
g_debug("CopyPasteSelectionReceivedCB: Error, length less than 0\n");
353
/* Try to get clipboard or selection timestamp. */
354
if (selection_data->target == GDK_SELECTION_TYPE_TIMESTAMP) {
355
if (selection_data->selection == GDK_SELECTION_PRIMARY) {
356
if (selection_data->length == 4) {
357
gGuestSelPrimaryTime = *(uint32 *)selection_data->data;
358
g_debug("CopyPasteSelectionReceivedCB: Got pri time [%"FMT64"u]\n",
359
gGuestSelPrimaryTime);
360
} else if (selection_data->length == 8) {
361
gGuestSelPrimaryTime = *(uint64 *)selection_data->data;
362
g_debug("CopyPasteSelectionReceivedCB: Got pri time [%"FMT64"u]\n",
363
gGuestSelPrimaryTime);
365
g_debug("CopyPasteSelectionReceivedCB: Unknown pri time. Size %d\n",
366
selection_data->length);
369
if (selection_data->selection == GDK_SELECTION_CLIPBOARD) {
370
if (selection_data->length == 4) {
371
gGuestSelClipboardTime = *(uint32 *)selection_data->data;
372
g_debug("CopyPasteSelectionReceivedCB: Got clip time [%"FMT64"u]\n",
373
gGuestSelClipboardTime);
374
} else if (selection_data->length == 8) {
375
gGuestSelClipboardTime = *(uint64 *)selection_data->data;
376
g_debug("CopyPasteSelectionReceivedCB: Got clip time [%"FMT64"u]\n",
377
gGuestSelClipboardTime);
379
g_debug("CopyPasteSelectionReceivedCB: Unknown clip time. Size %d\n",
380
selection_data->length);
386
if (selection_data->selection == GDK_SELECTION_PRIMARY) {
387
target = gGuestSelPrimaryBuf;
388
} else if (selection_data->selection == GDK_SELECTION_CLIPBOARD) {
389
target = gGuestSelClipboardBuf;
394
utf8Str = selection_data->data;
395
len = strlen(selection_data->data);
397
if (selection_data->target != GDK_SELECTION_TYPE_STRING &&
398
selection_data->target != GDK_SELECTION_TYPE_UTF8_STRING) {
399
/* It is a file list. */
400
if (len >= MAX_SELECTION_BUFFER_LENGTH - 1) {
401
Warning("CopyPasteSelectionReceivedCB file list too long\n");
403
memcpy(target, selection_data->data, len + 1);
409
* If target is GDK_SELECTION_TYPE_STRING, assume encoding is local code
410
* set. Convert to utf8 before send to vmx.
412
if (selection_data->target == GDK_SELECTION_TYPE_STRING &&
413
!CodeSet_CurrentToUtf8(selection_data->data,
414
selection_data->length,
417
g_debug("CopyPasteSelectionReceivedCB: Couldn't convert to utf8 code set\n");
418
gWaitingOnGuestSelection = FALSE;
423
* String in backdoor communication is 4 bytes by 4 bytes, so the len
424
* should be aligned to 4;
426
aligned_len = (len + 4) & ~3;
427
if (aligned_len >= MAX_SELECTION_BUFFER_LENGTH) {
428
/* With alignment, len is still possible to be less than max. */
429
if (len < (MAX_SELECTION_BUFFER_LENGTH - 1)) {
430
memcpy(target, utf8Str, len + 1);
432
memcpy(target, utf8Str, MAX_SELECTION_BUFFER_LENGTH - 1);
433
target[MAX_SELECTION_BUFFER_LENGTH - 1] ='\0';
436
memcpy(target, utf8Str, len + 1);
440
if (selection_data->target == GDK_SELECTION_TYPE_STRING) {
443
gWaitingOnGuestSelection = FALSE;
448
*-----------------------------------------------------------------------------
450
* CopyPasteSelectionGetCB --
452
* Callback for the gtk signal "selection_get".
453
* This is called when some other app requests the copy/paste selection,
454
* probably because we declare oursleves the selection owner on mouse
455
* grab. In text copy/paste case, we simply respond with contents of
456
* gHostClipboardBuf, which should have been set on mouse grab. In file
457
* copy/paste case, send file transfer request to host vmx, then return
458
* file list with right format according to different request.
459
* For host->guest copy/paste (both text and file).
465
* An X message is sent to the requesting app containing the data, it
466
* will likely act on it in some way. In FCP case, may first start a
467
* host->guest file transfer. Add block if blocking driver is available,
468
* otherwise wait till file copy done.
470
*-----------------------------------------------------------------------------
474
CopyPasteSelectionGetCB(GtkWidget *widget, // IN: unused
475
GtkSelectionData *selection_data, // IN: requested type
476
// OUT:the data to be sent
477
guint info, // IN: unused
478
guint time_stamp, // IN: unsued
479
gpointer data) // IN: unused
481
if ((widget == NULL) || (selection_data == NULL)) {
482
g_debug("CopyPasteSelectionGetCB: Error, widget or selection_data is invalid\n");
486
/* If it is text copy paste, return gHostClipboardBuf. */
487
if (GDK_SELECTION_TYPE_STRING == selection_data->target ||
488
GDK_SELECTION_TYPE_UTF8_STRING == selection_data->target) {
489
char *outBuf = gHostClipboardBuf;
490
size_t len = strlen(gHostClipboardBuf);
493
* If target is GDK_SELECTION_TYPE_STRING, assume encoding is local code
494
* set. Convert from utf8 to local one.
496
if (GDK_SELECTION_TYPE_STRING == selection_data->target &&
497
!CodeSet_Utf8ToCurrent(gHostClipboardBuf,
498
strlen(gHostClipboardBuf),
501
g_debug("CopyPasteSelectionGetCB: can not convert to current codeset\n");
505
gtk_selection_data_set(selection_data, selection_data->target, 8,
507
g_debug("CopyPasteSelectionGetCB: Set text [%s]\n", outBuf);
509
if (GDK_SELECTION_TYPE_STRING == selection_data->target) {
519
*-----------------------------------------------------------------------------
521
* CopyPasteSelectionClearCB --
523
* Callback for the gtk signal "selection_clear".
531
*-----------------------------------------------------------------------------
535
CopyPasteSelectionClearCB(GtkWidget *widget, // IN: unused
536
GdkEventSelection *event, // IN: unused
537
gpointer data) // IN: unused
539
g_debug("CopyPasteSelectionClearCB got clear signal\n");
546
*-----------------------------------------------------------------------------
548
* CopyPasteSetBackdoorSelections --
550
* Set the clipboard one of two ways, the old way or the new way.
551
* The old way uses GuestApp_SetSel and there's only one selection.
552
* Set backdoor selection with either primary selection or clipboard.
553
* The primary selection is the first priority, then clipboard.
554
* If both unavailable, set backdoor selection length to be 0.
555
* This will be used by older VMXs or VMXs on Windows hosts (which
556
* has only one clipboard). Doing this gives us backwards
559
* The new way uses new sets both PRIMARY and CLIPBOARD. Newer Linux
560
* VMXs will use these rather than the above method and have the two
561
* selections set separately.
563
* XXX: The "new way" doesn't exist yet, the vmx has no support for it.
569
* The VMX probably changes some string buffers.
571
*-----------------------------------------------------------------------------
575
CopyPasteSetBackdoorSelections(void)
584
primaryLen = strlen(gGuestSelPrimaryBuf);
585
clipboardLen = strlen(gGuestSelClipboardBuf);
587
if (primaryLen && clipboardLen) {
588
/* Pick latest one if both are available. */
589
p = gGuestSelPrimaryTime >= gGuestSelClipboardTime ?
590
(uint32 const *)gGuestSelPrimaryBuf :
591
(uint32 const *)gGuestSelClipboardBuf;
592
} else if (primaryLen) {
594
* Send primary selection to backdoor if it exists.
596
p = (uint32 const *)gGuestSelPrimaryBuf;
597
} else if (clipboardLen) {
599
* Otherwise send clipboard to backdoor if it exists.
601
p = (uint32 const *)gGuestSelClipboardBuf;
604
* Neither selection is set
610
GuestApp_SetSelLength(0);
611
g_debug("CopyPasteSetBackdoorSelections Set empty text.\n");
613
len = strlen((char *)p);
614
g_debug("CopyPasteSetBackdoorSelections Set text [%s].\n", (char *)p);
615
aligned_len = (len + 4) & ~3;
617
/* Here long string should already be truncated. */
618
ASSERT(aligned_len <= MAX_SELECTION_BUFFER_LENGTH);
620
GuestApp_SetSelLength(len);
621
for (i = 0; i < len; i += 4, p++) {
622
GuestApp_SetNextPiece(*p);
629
*-----------------------------------------------------------------------------
631
* CopyPaste_GetBackdoorSelections --
633
* Get the clipboard "the old way".
634
* The old way uses GuestApp_SetSel and there's only one selection.
635
* We don't have to do anything for the "new way", since the host
636
* will just push PRIMARY and/or CLIPBOARD when they are available
639
* XXX: the "new way" isn't availble yet because the vmx doesn't
640
* implement separate clipboards. Even when it does this
641
* function will still exist for backward compatibility
644
* TRUE if selection length>=0, FALSE otherwise.
647
* This application becomes the selection owner for PRIMARY and/or
648
CLIPBOARD selections.
650
*-----------------------------------------------------------------------------
654
CopyPaste_GetBackdoorSelections(void)
658
if (gVmxCopyPasteVersion > 1) {
662
selLength = GuestApp_GetHostSelectionLen();
663
if (selLength < 0 || selLength > MAX_SELECTION_BUFFER_LENGTH) {
665
} else if (selLength > 0) {
666
GuestApp_GetHostSelection(selLength, gHostClipboardBuf);
667
gHostClipboardBuf[selLength] = 0;
668
g_debug("CopyPaste_GetBackdoorSelections Get text [%s].\n", gHostClipboardBuf);
669
gtk_selection_owner_set(gUserMainWidget,
670
GDK_SELECTION_CLIPBOARD,
672
gtk_selection_owner_set(gUserMainWidget,
673
GDK_SELECTION_PRIMARY,
682
*-----------------------------------------------------------------------------
684
* CopyPaste_Register --
686
* Setup callbacks, initialize.
694
*-----------------------------------------------------------------------------
698
CopyPaste_Register(GtkWidget* mainWnd, // IN
699
ToolsAppCtx *ctx) // IN
701
g_debug("%s: enter\n", __FUNCTION__);
706
/* Text copy/paste initialization for all versions. */
707
#ifndef GDK_SELECTION_CLIPBOARD
708
GDK_SELECTION_CLIPBOARD = gdk_atom_intern("CLIPBOARD", FALSE);
711
#ifndef GDK_SELECTION_TYPE_TIMESTAMP
712
GDK_SELECTION_TYPE_TIMESTAMP = gdk_atom_intern("TIMESTAMP", FALSE);
715
#ifndef GDK_SELECTION_TYPE_UTF8_STRING
716
GDK_SELECTION_TYPE_UTF8_STRING = gdk_atom_intern("UTF8_STRING", FALSE);
720
* String is always in supported list. FCP atoms will dynamically be
723
gtk_selection_add_target(mainWnd, GDK_SELECTION_PRIMARY,
724
GDK_SELECTION_TYPE_STRING, 0);
725
gtk_selection_add_target(mainWnd, GDK_SELECTION_CLIPBOARD,
726
GDK_SELECTION_TYPE_STRING, 0);
727
gtk_selection_add_target(mainWnd, GDK_SELECTION_PRIMARY,
728
GDK_SELECTION_TYPE_UTF8_STRING, 0);
729
gtk_selection_add_target(mainWnd, GDK_SELECTION_CLIPBOARD,
730
GDK_SELECTION_TYPE_UTF8_STRING, 0);
732
gtk_signal_connect(GTK_OBJECT(mainWnd), "selection_received",
733
GTK_SIGNAL_FUNC(CopyPasteSelectionReceivedCB), mainWnd);
734
gtk_signal_connect(GTK_OBJECT(mainWnd), "selection_get",
735
GTK_SIGNAL_FUNC(CopyPasteSelectionGetCB), mainWnd);
736
gtk_signal_connect(GTK_OBJECT(mainWnd), "selection_clear_event",
737
GTK_SIGNAL_FUNC(CopyPasteSelectionClearCB), mainWnd);
739
CopyPasteStateInit();
746
*-----------------------------------------------------------------------------
748
* CopyPaste_Unregister --
750
* Cleanup copy/paste related things.
756
* copy/paste is stopped, the rpc channel to the vmx is closed.
758
*-----------------------------------------------------------------------------
762
CopyPaste_Unregister(GtkWidget* mainWnd)
764
g_debug("%s: enter\n", __FUNCTION__);
765
gtk_signal_disconnect_by_func(GTK_OBJECT(mainWnd),
766
GTK_SIGNAL_FUNC(CopyPasteSelectionReceivedCB),
768
gtk_signal_disconnect_by_func(GTK_OBJECT(mainWnd),
769
GTK_SIGNAL_FUNC(CopyPasteSelectionGetCB),
771
gtk_signal_disconnect_by_func(GTK_OBJECT(mainWnd),
772
GTK_SIGNAL_FUNC(CopyPasteSelectionClearCB),
778
*----------------------------------------------------------------------------
780
* CopyPaste_IsRpcCPSupported --
782
* Check if RPC copy/paste is supported by vmx or not.
790
*-----------------------------------------------------------------------------
794
CopyPaste_IsRpcCPSupported(void)
796
return gVmxCopyPasteVersion > 1;
801
*----------------------------------------------------------------------------
803
* CopyPasteStateInit --
805
* Initalialize CopyPaste State.
813
*----------------------------------------------------------------------------
817
CopyPasteStateInit(void)
819
g_debug("%s: enter\n", __FUNCTION__);
820
gHostClipboardBuf[0] = '\0';
821
gGuestSelPrimaryBuf[0] = '\0';
822
gGuestSelClipboardBuf[0] = '\0';
828
* Set the copy paste version.
830
* @param[in] version version to set.
834
CopyPaste_SetVersion(int version)
836
g_debug("%s: enter version %d\n", __FUNCTION__, version);
837
gVmxCopyPasteVersion = version;