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 vmware-user.cpp
22
* The linux vmware-user app. It's a hidden window app that is supposed
23
* to run on session start. It handles tools features which we want
24
* active all the time, but don't want to impose a visible window on the
28
#include "copyPasteDnDWrapper.h"
38
#include <gtk/gtkinvisible.h>
40
#if defined(__FreeBSD__) && (!defined(USING_AUTOCONF) || defined(HAVE_SYSLIMITS_H))
41
#include <syslimits.h> // PATH_MAX
44
#include "vmwareuserInt.h"
45
#include "vm_assert.h"
46
#include "eventManager.h"
47
#include "hgfsServerManager.h"
52
#include "foundryToolsDaemon.h"
54
#include "conf.h" // for Conf_Load()
56
#include "syncDriver.h"
60
#include "ghIntegration.h"
61
#include "resolution.h"
64
#include "vm_atomic.h"
66
#include "vmwareuser_version.h"
67
#include "embed_version.h"
68
#include "vmware/guestrpc/tclodefs.h"
70
VM_EMBED_VERSION(VMWAREUSER_VERSION_STRING);
75
static char THIS_FILE[] = __FILE__;
78
#define VMUSER_TITLE "vmware-user"
79
#define LOCK_ATOM_NAME "vmware-user-lock"
81
#define INVALID_VALUE "Invalid option"
82
#define INVALID_OPTION "Invalid value"
83
#define INVALID_COMMAND "Invalid command format"
84
#define OPTION_BLOCK_FD "--blockFd"
87
* Forward Declarations
89
void VMwareUser_OnDestroy(GtkWidget *widget, gpointer data);
90
GtkWidget* VMwareUser_CreateWindow(void);
91
gint EventQueuePump(gpointer data);
93
Bool VMwareUserRpcInResetCB (RpcInData *data);
94
Bool VMwareUserRpcInSetOptionCB(char const **result, size_t *resultLen,
95
const char *name, const char *args,
96
size_t argsSize, void *clientData);
97
Bool VMwareUserRpcInCapRegCB (char const **result, size_t *resultLen,
98
const char *name, const char *args,
99
size_t argsSize, void *clientData);
100
void VMwareUserRpcInErrorCB (void *clientdata, char const *status);
102
static Bool InitGroupLeader(Window *groupLeader, Window *rootWindow);
103
static Bool AcquireDisplayLock(void);
104
static Bool QueryX11Lock(Display *dpy, Window w, Atom lockAtom);
105
static void ReloadSelf(void);
106
static void VMwareUserRegisterCopyPaste(bool reg);
107
static void VMwareUserRegisterDnD(bool reg);
114
static Bool gOpenUrlRegistered;
115
static Bool gDnDRegistered;
116
static Bool gHgfsServerRegistered;
117
static pid_t gParentPid;
118
static char gLogFilePath[PATH_MAX];
119
static Bool gRpcInStarted; // Set after ATR handshake. Indicates RpcIn is a go.
122
* The following are flags set by our signal handler. They are evaluated
123
* in main() only if gtk_main() ever returns.
125
static Bool gReloadSelf; // Set by SIGUSR2 + error handlers; triggers reload.
126
static Bool gYieldBlock; // Set by SIGUSR1; triggers DND shutdown
127
static Bool gSigExit; // Set by all but SIGUSR1; triggers app shutdown
130
* From vmwareuserInt.h
134
GtkWidget *gUserMainWidget = NULL;
140
DblLnkLst_Links *gEventQueue;
142
Bool gCanUseVMwareCtrl;
143
Bool gCanUseVMwareCtrlTopologySet;
145
DnDBlockControl gBlockCtrl = { -1 };
149
* . Can terminate the process
150
* . May occur even if the program has no bugs
152
static int const gSignals[] = {
157
SIGUSR1, // yield vmblock, uninit DnD
158
SIGUSR2, // reload vmware-user
164
*-----------------------------------------------------------------------------
166
* VMwareUserCleanupRpc --
168
* Unset capabilities and cleanup the backdoor.
174
* The application will close.
176
*-----------------------------------------------------------------------------
179
void VMwareUserCleanupRpc(Bool isXError) // IN
181
Debug("%s: enter\n", __FUNCTION__);
183
Unity_UnregisterCaps();
186
Resolution_Cleanup();
188
if (gHgfsServerRegistered) {
189
HgfsServerManager_Unregister(gRpcIn, TOOLS_DND_NAME);
190
gHgfsServerRegistered = FALSE;
193
if (!RpcIn_stop(gRpcIn)) {
194
Debug("%s: failed to stop RpcIn loop\n", __FUNCTION__);
196
if (gOpenUrlRegistered) {
197
FoundryToolsDaemon_UnregisterOpenUrl();
198
gOpenUrlRegistered = FALSE;
201
CopyPasteDnDWrapper *p = CopyPasteDnDWrapper::GetInstance();
204
* We can't call the normal APIs to tear down DnD/CP because they
205
* involve Xlib calls that can't be made after X IO error. So, use
206
* an entry point that performs a subset of the cleanup we normally
207
* do on a reset, to ensure that any file transfers in flight get
208
* failed properly. See bug 458626.
219
RpcIn_Destruct(gRpcIn);
226
*-----------------------------------------------------------------------------
228
* VMwareUserSignalHandler --
230
* Handler for Posix signals. We do this to ensure that we exit gracefully.
236
* Application will break out of the gtk_main() loop. One or more of the
237
* signal flags (gReloadSelf, gYieldBlock, gSigExit) may be set. For all
238
* signals but SIGUSR1, VMwareUserCleanupRpc() will be called.
240
*-----------------------------------------------------------------------------
243
void VMwareUserSignalHandler(int sig) // IN
257
#if defined(HAVE_GTKMM)
266
*-----------------------------------------------------------------------------
268
* VMwareUser_OnDestroy --
270
* Callback for the gtk signal "destroy" on the main window.
271
* Exit the gtk loop, causing main() to exit.
277
* The application will close.
279
*-----------------------------------------------------------------------------
283
VMwareUser_OnDestroy(GtkWidget *widget, // IN: Unused
284
gpointer data) // IN: Unused
286
#if defined(HAVE_GTKMM)
295
*-----------------------------------------------------------------------------
299
* Handle events in the event queue. This function is re-registered as a
300
* gtk_timeout every time, since we only want to be called when it is time
301
* for the next event in the queue.
304
* 1 if there were no problems, 0 otherwise
307
* The events in the queue will be called, they could do anything.
309
*-----------------------------------------------------------------------------
313
EventQueuePump(gpointer data) // IN: Unused
318
gtk_timeout_remove(gTimeoutId);
319
ret = EventManager_ProcessNext(gEventQueue, &sleepUsecs);
321
Warning("Unexpected end of EventManager loop: returned value is %d.\n\n",
325
gTimeoutId = gtk_timeout_add(sleepUsecs/1000, &EventQueuePump, NULL);
331
*-----------------------------------------------------------------------------
333
* VMwareUser_CreateWindow --
335
* Create and initializes a hidden input only window for dnd and cp.
338
* An invisible gtk widget.
343
*-----------------------------------------------------------------------------
347
VMwareUser_CreateWindow(void)
351
wnd = gtk_invisible_new();
352
gtk_signal_connect(GTK_OBJECT(wnd), "destroy",
353
GTK_SIGNAL_FUNC(VMwareUser_OnDestroy), NULL);
359
*-----------------------------------------------------------------------------
361
* VMwareUserRpcInResetCB --
363
* Callback called when the vmx has done a reset on the backdoor channel
366
* TRUE if we reply successfully, FALSE otherwise
369
* Send an "ATR" to thru the backdoor.
371
*-----------------------------------------------------------------------------
375
VMwareUserRpcInResetCB(RpcInData *data) // IN/OUT
377
Debug("----------toolbox: Received 'reset' from vmware\n");
378
CopyPasteDnDWrapper *p = CopyPasteDnDWrapper::GetInstance();
383
gRpcInStarted = TRUE;
385
return RPCIN_SETRETVALS(data, "ATR " TOOLS_DND_NAME, TRUE);
390
*-----------------------------------------------------------------------------
392
* VMwareUserRpcInErrorCB --
394
* Callback called when there is some error on the backdoor channel.
397
* This function calls the exit handler, VMwareUser_OnDestroy.
400
* If the RpcIn channel had been up previously, as indicated by performing
401
* the ATR handshake, then this function will set the gSigExit and
402
* gReloadSelf flags. This is only to attempt recovery from an error
403
* occurring while vmware-user was in its steady running state.
405
*-----------------------------------------------------------------------------
409
VMwareUserRpcInErrorCB(void *clientdata, char const *status)
411
Warning("Error in the RPC receive loop: %s\n", status);
412
Warning("Another instance of %s may be running.\n\n", VMUSER_TITLE);
415
Debug("Channel had been up previously. Perhaps we're waking from hibernation?\n");
420
VMwareUser_OnDestroy(NULL, NULL);
425
*-----------------------------------------------------------------------------
427
* VMwareUserRpcInCapRegCB --
429
* Handler for TCLO 'Capabilities_Register'.
432
* TRUE if we can reply, FALSE otherwise.
437
*-----------------------------------------------------------------------------
441
VMwareUserRpcInCapRegCB(char const **result, // OUT
442
size_t *resultLen, // OUT
443
const char *name, // IN
444
const char *args, // IN
445
size_t argsSize, // Unused
446
void *clientData) // Unused
448
Debug("VMwareUserRpcInCapRegCB got called\n");
450
if (!gOpenUrlRegistered) {
451
gOpenUrlRegistered = FoundryToolsDaemon_RegisterOpenUrl(gRpcIn);
453
FoundryToolsDaemon_RegisterOpenUrlCapability();
456
VMwareUserRegisterDnD(TRUE);
457
VMwareUserRegisterCopyPaste(TRUE);
459
if (!HgfsServerManager_CapReg(TOOLS_DND_NAME, gHgfsServerRegistered)) {
460
Debug("VMwareUserRpcInCapRegCB: Failed to register HGFS server capability.\n");
463
Unity_RegisterCaps();
464
Resolution_RegisterCaps();
466
return RpcIn_SetRetVals(result, resultLen, "", TRUE);
471
*-----------------------------------------------------------------------------
473
* VMwareUserRegisterCopyPaste
475
* Call the CopyPasteDnDWrapper singleton to register, or unregister,
476
* copy and paste with the host. The wrapper class will try
477
* whatever versions are supported, in highest to lowest order when
484
* Copy and paste capabilities will be (un)registered.
486
*-----------------------------------------------------------------------------
490
VMwareUserRegisterCopyPaste(bool reg) // IN: if TRUE, register, else unregister
492
CopyPasteDnDWrapper *p = CopyPasteDnDWrapper::GetInstance();
494
p->SetUserData(static_cast<void *>(gUserMainWidget));
505
*-----------------------------------------------------------------------------
507
* VMwareUserRegisterDnD
509
* Call the CopyPasteDnDWrapper singleton to register, or unregister,
510
* drag and drop with the host. The wrapper class will try
511
* whatever versions are supported, in highest to lowest order when
518
* Drag and drop capabilities will be (un)registered.
520
*-----------------------------------------------------------------------------
524
VMwareUserRegisterDnD(bool reg) // IN: if TRUE, register, else unregister
526
CopyPasteDnDWrapper *p = CopyPasteDnDWrapper::GetInstance();
528
p->SetUserData(static_cast<void *>(gUserMainWidget));
529
p->SetEventQueue(gEventQueue);
540
*-----------------------------------------------------------------------------
542
* VMwareUserRpcInSetOptionCB
544
* Parse a "Set_Option" TCLO cmd from the vmx and update the local
545
* copy of the option.
548
* TRUE if the set option command was executed.
549
* FALSE if something failed.
552
* Start or stop processes (like time syncing) that could be affected
553
* by option's new value.
555
*-----------------------------------------------------------------------------
559
VMwareUserRpcInSetOptionCB(char const **result, // OUT
560
size_t *resultLen, // OUT
561
const char *name, // IN
562
const char *args, // IN
563
size_t argsSize, // Unused
564
void *clientData) // Unused
568
unsigned int index = 0;
570
const char *retStr = NULL;
572
/* parse the option & value string */
573
option = StrUtil_GetNextToken(&index, args, " ");
575
retStr = INVALID_COMMAND;
578
index++; // ignore leading space before value
579
value = StrUtil_GetNextToken(&index, args, "");
581
retStr = INVALID_COMMAND;
583
} else if (strlen(value) == 0) {
584
retStr = INVALID_COMMAND;
588
Debug("VMwareUserRpcInSetOptionCB got option [%s], value %s\n",
592
* Register or unregister features based on the Tools option setting or
595
if (strcmp(option, TOOLSOPTION_COPYPASTE) == 0) {
596
if (strcmp(value, "1") == 0) {
597
VMwareUserRegisterCopyPaste(TRUE);
598
} else if (strcmp(value, "0") == 0) {
599
VMwareUserRegisterCopyPaste(FALSE);
601
retStr = INVALID_VALUE;
604
} else if (strcmp(option, TOOLSOPTION_ENABLEDND) == 0) {
605
if (strcmp(value, "1") == 0) {
607
if (!gDnDRegistered) {
608
DnD_Register(gHGWnd, gGHWnd);
609
gDnDRegistered = TRUE;
611
} else if (strcmp(value, "0") == 0) {
613
if (gDnDRegistered) {
614
DnD_Unregister(gHGWnd, gGHWnd);
615
gDnDRegistered = FALSE;
618
retStr = INVALID_VALUE;
622
retStr = INVALID_OPTION;
633
return RpcIn_SetRetVals(result, resultLen, retStr, ret);
638
*-----------------------------------------------------------------------------
640
* VMwareUserXIOErrorHandler --
642
* Handler for all X I/O errors. Xlib documentation says we should not
643
* return when handling I/O errors.
646
* On success, and assuming we're called inside the parent vmware-user
647
* process (see comment below), we attempt to restart ourselves. On
648
* failure, we'll exit with EXIT_FAILURE.
651
* This function does not return.
653
*-----------------------------------------------------------------------------
656
int VMwareUserXIOErrorHandler(Display *dpy)
658
pid_t my_pid = getpid();
661
* ProcMgr_ExecAsync() needs to fork off a child to handle
662
* watching the process being run. When it dies, it will come
663
* through here, so we don't want to let it shut down the Rpc
665
Debug("> %s\n", __FUNCTION__);
666
if (my_pid == gParentPid) {
667
VMwareUserCleanupRpc(TRUE);
671
Debug("%s hit from forked() child, not cleaning Rpc\n", __FUNCTION__);
680
*-----------------------------------------------------------------------------
682
* VMwareUserConfFileLoop --
684
* Run the "conf file reload" loop
692
*-----------------------------------------------------------------------------
696
VMwareUserConfFileLoop(void *clientData) // IN
698
GuestApp_Dict **pConfDict = (GuestApp_Dict **) clientData;
703
* With the addition of the Sync Driver we can get into a state
704
* where the system drive is frozen, preventing the completion of
705
* any disk-based I/O. The event that periodically reloads the conf
706
* file then gets blocked, which blocks the main daemon thread and
707
* prevents any further GuestRPC messages from getting
708
* processed. This effectively deadlocks the tools daemon and among
709
* other things makes it impossible to thaw disk I/O once it's been
712
* So, we keep track of when the disks are frozen and skip doing disk
713
* I/O during that time.
715
if (!SyncDriver_DrivesAreFrozen()) {
716
if (Conf_ReloadFile(pConfDict)) {
717
const char *pathName = GuestApp_GetDictEntry(*pConfDict, CONFNAME_LOGFILE);
719
Debug_Set(GuestApp_GetDictEntryBool(*pConfDict, CONFNAME_LOG),
724
* 2 reasons that should put pid into vmware-user log file name:
726
* 1. guestd runs as super user and creates log file with limited
727
* permission. If log in as non-root user, vmware-user has no
728
* permission to write to the log file. Put log to different
729
* file will resolve this.
730
* 2. If user first log in as root and start vmware-user logging,
731
* the log file is still with limited permission. Later on
732
* if user re-log in as non-root user, vmware-user has no
733
* permission to that file. With pid in the log file name,
734
* everytime if vmware-user is launched, a new log file will
735
* be created with current account.
737
Str_Sprintf(gLogFilePath, sizeof gLogFilePath, "%s.%u",
738
pathName, (unsigned int)getpid());
739
Debug_EnableToFile(gLogFilePath, FALSE);
741
Debug_EnableToFile(NULL, FALSE);
746
EventManager_Add(gEventQueue, CONF_POLL_TIME, VMwareUserConfFileLoop,
753
*-----------------------------------------------------------------------------
760
* Returns either EXIT_SUCCESS or EXIT_FAILURE appropriately.
763
* The linux toolbox ui will run and do a variety of tricks for your
766
*-----------------------------------------------------------------------------
774
struct sigaction olds[ARRAYSIZE(gSignals)];
776
GuestApp_Dict *confDict;
777
const char *pathName;
780
Bool notifyPresent = TRUE;
784
gOpenUrlRegistered = FALSE;
785
gDnDRegistered = FALSE;
786
gHgfsServerRegistered = FALSE;
794
if (!VmCheck_IsVirtualWorld()) {
795
Warning("vmware-user must be run inside a virtual machine.\n");
799
confDict = Conf_Load();
802
* We depend on the window title when performing (primitive) vmware-user
803
* session detection, and unfortunately for us, GTK has a nasty habit of
804
* retitling toplevel windows. That said, we can control GTK's default
805
* title by setting Glib's application or program name.
807
* XXX Consider using g_set_application_name("VMware User Agent") or
810
g_set_prgname(VMUSER_TITLE);
812
/* Set to system locale. */
813
setlocale(LC_CTYPE, "");
815
#if defined(HAVE_GTKMM)
816
Gtk::Main main(&argc, &argv);
818
gtk_init(&argc, &argv);
823
* Running more than 1 VMware user process (vmware-user) per X11 session
824
* invites bad juju. The following routine ensures that only one instance
825
* will run per session.
827
* NB: The lock is tied to this process, so it disappears when we exit.
828
* As such, there is no corresponding unlock routine.
830
if (AcquireDisplayLock() == FALSE) {
831
Warning("Another instance of vmware-user already running. Exiting.\n");
835
gParentPid = getpid();
838
* Parse the command line.
840
* We do it by hand because getopt() doesn't handle long options, and
841
* getopt_long is a GNU extension
843
* argv[0] is the program name, as usual
846
for (index = 1; index < argc; index++) {
847
if (strncmp(argv[index], "-", 1) == 0) {
848
if (strncmp(argv[index], OPTION_BLOCK_FD, sizeof OPTION_BLOCK_FD) == 0) {
850
* vmware-user runs as current active account, and can not initialize
851
* blocking driver if it is not root. If guestd autostarts vmware-user,
852
* guestd will first initialize it and pass block fd in with -blockFd.
854
if (++index >= argc) {
855
Warning("The \""OPTION_BLOCK_FD"\" option on the command line requires an "
859
if (!StrUtil_StrToInt(&blockFd, argv[index])) {
860
Warning("The \""OPTION_BLOCK_FD"\" option on the command line requires a "
864
Debug("vmware-user got blockFd = %d\n", blockFd);
866
Warning("Invalid \"%s\" option on the command line.\n", argv[index]);
872
* vmware-user runs as current active account, and can not initialize blocking
873
* driver if it is not root. If guestd autostarts vmware-user, guestd will first
874
* initialize it and pass block fd in. If manually run vmware-user, here will
875
* try to initialize the blocking driver.
877
usingBlock = blockFd >= 0 ?
878
DnD_CompleteBlockInitialization(blockFd, &gBlockCtrl) :
879
DnD_InitializeBlocking(&gBlockCtrl);
882
Debug("%s: vmware-user failed to initialize blocking driver.\n",
886
if (Signal_SetGroupHandler(gSignals, olds, ARRAYSIZE(gSignals),
887
VMwareUserSignalHandler) == 0 ) {
888
Panic("vmware-user can't set signal handler\n");
891
Debug_Set(GuestApp_GetDictEntryBool(confDict, CONFNAME_LOG), DEBUG_PREFIX);
893
pathName = GuestApp_GetDictEntry(confDict, CONFNAME_LOGFILE);
896
* 2 reasons that should put pid into vmware-user log file name:
898
* 1. guestd runs as super user and creates log file with limited
899
* permission. If log in as non-root user, vmware-user has no
900
* permission to write to the log file. Put log to different
901
* file will resolve this.
902
* 2. If user first log in as root and start vmware-user logging,
903
* the log file is still with limited permission. Later on
904
* if user re-log in as non-root user, vmware-user has no
905
* permission to that file. With pid in the log file name,
906
* everytime if vmware-user is launched, a new log file will
907
* be created with current account.
909
Str_Sprintf(gLogFilePath, sizeof gLogFilePath, "%s.%u", pathName,
910
(unsigned int)getpid());
911
Debug_EnableToFile(gLogFilePath, FALSE);
913
Debug_EnableToFile(NULL, FALSE);
916
gUserMainWidget = VMwareUser_CreateWindow();
918
gHGWnd = VMwareUser_CreateWindow();
919
gGHWnd = VMwareUser_CreateWindow();
921
* I don't want to show the window, but I need it's X window to exist.
923
gtk_widget_realize(gUserMainWidget);
924
gtk_widget_realize(gHGWnd);
925
gtk_widget_realize(gGHWnd);
928
gXDisplay = GDK_WINDOW_XDISPLAY(gUserMainWidget->window);
929
gXRoot = RootWindow(gXDisplay, DefaultScreen(gXDisplay));
931
gEventQueue = EventManager_Init();
932
if (gEventQueue == NULL) {
933
Warning("Unable to create the event queue.\n\n");
937
CopyPasteDnDWrapper *p = CopyPasteDnDWrapper::GetInstance();
939
p->SetUserData(static_cast<void *>(gUserMainWidget));
940
p->SetBlockControl(&gBlockCtrl);
941
p->SetEventQueue(gEventQueue);
946
EventManager_Add(gEventQueue, CONF_POLL_TIME, VMwareUserConfFileLoop,
949
Unity_Init(confDict, NULL, NULL);
950
GHI_Init(NULL, NULL);
951
Resolution_Init(TOOLS_DND_NAME, gXDisplay);
954
if (!Notify_Init(confDict)) {
955
Warning("Unable to initialize notification system.\n\n");
956
notifyPresent = FALSE;
962
gRpcIn = RpcIn_Construct(gEventQueue);
963
if (gRpcIn == NULL) {
964
Warning("Unable to create the RpcIn object.\n\n");
968
if (!RpcIn_start(gRpcIn, RPCIN_POLL_TIME, VMwareUserRpcInResetCB,
969
NULL, VMwareUserRpcInErrorCB, NULL)) {
970
Warning("Unable to start the receive loop.\n\n");
974
RpcIn_RegisterCallback(gRpcIn, "Capabilities_Register",
975
VMwareUserRpcInCapRegCB, NULL);
976
RpcIn_RegisterCallback(gRpcIn, "Set_Option",
977
VMwareUserRpcInSetOptionCB, NULL);
979
Unity_InitBackdoor(gRpcIn);
980
GHI_InitBackdoor(gRpcIn);
981
Resolution_InitBackdoor(gRpcIn);
983
#if !defined(N_PLAT_NLM) && !defined(sun)
985
const char **nativeEnvp;
986
char **utf8NativeEnvp;
989
* Determine our pre-VMware wrapper native environment and pass that to
990
* Foundry so it can spawn applications.
993
nativeEnvp = System_GetNativeEnviron(const_cast<const char **>(envp));
995
/* Foundry takes its strings UTF-8. */
996
utf8NativeEnvp = Unicode_AllocList(const_cast<char **>(nativeEnvp),
997
-1, // nativeEnvp is NULL terminated
998
STRING_ENCODING_DEFAULT);
1000
FoundryToolsDaemon_RegisterRoutines(gRpcIn,
1006
Unicode_FreeList(utf8NativeEnvp, -1);
1007
System_FreeNativeEnviron(nativeEnvp);
1011
gHgfsServerRegistered = HgfsServerManager_Register(gRpcIn, TOOLS_DND_NAME);
1014
* Setup the some events and a pump for the EventManager.
1015
* We use gtk_timeouts for this.
1017
gTimeoutId = gtk_timeout_add(0, &EventQueuePump, NULL);
1019
XSetIOErrorHandler(VMwareUserXIOErrorHandler);
1021
Pointer_Register(gUserMainWidget);
1025
* We'll block here until the window is destroyed or a signal is received.
1027
#if defined(HAVE_GTKMM)
1038
CopyPasteDnDWrapper *p = CopyPasteDnDWrapper::GetInstance();
1044
if (DnD_BlockIsReady(&gBlockCtrl) &&
1045
!DnD_UninitializeBlocking(&gBlockCtrl)) {
1046
Debug("vmware-user failed to uninitialize blocking.\n");
1048
gYieldBlock = FALSE;
1052
Signal_ResetGroupHandler(gSignals, olds, ARRAYSIZE(gSignals));
1054
if (DnD_BlockIsReady(&gBlockCtrl) &&
1055
!DnD_UninitializeBlocking(&gBlockCtrl)) {
1056
Debug("vmware-user failed to uninitialize blocking.\n");
1062
if (notifyPresent) {
1068
* Clean up everything attached to the backdoor before waving goodbye.
1070
VMwareUserCleanupRpc(FALSE);
1073
* SIGUSR2 sets this to TRUE, indicating that we should relaunch ourselves.
1074
* This is useful during a Tools upgrade where we'd like to automatically
1075
* restart a new vmware-user binary.
1077
* NB: This just makes a best effort and relies on the user's PATH
1078
* environment variable. If it fails for any reason, then we'll just exit.
1084
return EXIT_SUCCESS;
1089
*-----------------------------------------------------------------------------
1091
* InitGroupLeader --
1093
* This routine sets a few properties related to our main window created
1094
* by {gdk,gtk}_init. Specifically this routine sets the window title,
1095
* sets the override_redirect X11 property, and reparents it to the root
1098
* In addition, this routine will return Xlib handles for the following
1100
* - Main or group leader window
1101
* - Display's root window
1104
* TRUE on success, FALSE on failure.
1107
* Errors may be sent to stderr.
1108
* Window will have a title of VMUSER_TITLE.
1109
* Window, if not already directly parented by the root, will be.
1111
* dpy will point to our default display (ex: $DISPLAY).
1112
* groupLeader will point to the window created by gtk_init().
1113
* rootWindow will point to the root window on $DISPLAY.
1115
*-----------------------------------------------------------------------------
1119
InitGroupLeader(Window *groupLeader, // OUT: group leader window
1120
Window *rootWindow) // OUT: root window
1122
Window myGroupLeader;
1123
Window myRootWindow;
1124
XSetWindowAttributes attr;
1126
attr.override_redirect = True;
1128
ASSERT(groupLeader);
1131
#if GTK_CHECK_VERSION(2,0,0)
1133
GdkDisplay *gdkDisplay = gdk_display_get_default();
1134
GdkWindow *gdkLeader = gdk_display_get_default_group(gdkDisplay);
1135
myGroupLeader = GDK_WINDOW_XWINDOW(gdkLeader);
1139
* This requires digging around in gdk 1.x private code. However, we'll
1140
* assume that GTK 1.x isn't going anywhere, so this should remain stable.
1142
myGroupLeader = gdk_leader_window;
1145
myRootWindow = GDK_ROOT_WINDOW();
1147
ASSERT(myGroupLeader);
1148
ASSERT(myRootWindow);
1151
* XXX With g_set_prgname() called from main(), this can probably go
1154
XStoreName(GDK_DISPLAY(), myGroupLeader, VMUSER_TITLE);
1157
* Sanity check: Set the override redirect property on our group leader
1158
* window (not default), then re-parent it to the root window (default).
1159
* This makes sure that (a) a window manager can't re-parent our window,
1160
* and (b) that we remain a top-level window.
1162
XChangeWindowAttributes(GDK_DISPLAY(), myGroupLeader, CWOverrideRedirect,
1164
XReparentWindow(GDK_DISPLAY(), myGroupLeader, myRootWindow, 10, 10);
1165
XSync(GDK_DISPLAY(), FALSE);
1167
*groupLeader = myGroupLeader;
1168
*rootWindow = myRootWindow;
1175
*-----------------------------------------------------------------------------
1177
* AcquireDisplayLock --
1179
* This function "locks" the display against being "claimed" by another
1180
* instance of vmware-user. It will succeed if we're the first/only
1181
* instance of vmware-user, and fail otherwise.
1183
* NB: This routine must be called -after- gtk_init().
1185
* Vmware-user enjoys per-display exclusivity using the following algorithm:
1187
* 1. Grab X server. (I.e., get exclusive access.)
1188
* 2. Search for top-level X windows meeting the following criteria:
1189
* a. named "vmware-user"
1190
* b. has the property "vmware-user-lock" set.
1191
* 3a. If any such windows described above found, then another vmware-user
1192
* process is attached to this display, so we consider the display
1194
* 3b. Else we're the only one. Set the "vmware-user-lock" property on
1195
* our top-level window.
1196
* 4. Ungrab the X server.
1199
* TRUE if "lock" acquired (i.e., we're the first/only vmware-user process);
1203
* The first time this routine is ever called during the lifetime of an X
1204
* session, a new X11 Atom, "vmware-user-lock" is created for the lifetime
1207
* The "vmware-user-lock" property may be set on this process's group leader
1210
*-----------------------------------------------------------------------------
1214
AcquireDisplayLock(void)
1216
Display *defaultDisplay; // Current default X11 display.
1217
Window rootWindow; // Root window of defaultDisplay; used as root node
1218
// passed to XQueryTree().
1219
Window groupLeader; // Our instance's window group leader. This is
1220
// implicitly created by gtk_init().
1222
Window *children = NULL; // Array of windows returned by XQueryTree().
1223
unsigned int nchildren; // Length of children.
1225
Window dummy1, dummy2; // Throwaway window IDs for XQueryTree().
1226
Atom lockAtom; // Refers to the "vmware-user-lock" X11 Atom.
1229
Bool alreadyLocked = FALSE; // Set to TRUE if we discover lock is held.
1230
Bool retval = FALSE;
1232
defaultDisplay = GDK_DISPLAY();
1235
* Reset some of our main window's settings & fetch Xlib handles for
1236
* the GDK group leader and root windows.
1238
if (InitGroupLeader(&groupLeader, &rootWindow) == FALSE) {
1239
Warning("%s: unable to initialize main window.\n", __func__);
1244
* Look up the lock atom, creating it if it doesn't already exist.
1246
lockAtom = XInternAtom(defaultDisplay, LOCK_ATOM_NAME, False);
1247
if (lockAtom == None) {
1248
Warning("%s: unable to create X11 atom: " LOCK_ATOM_NAME "\n", __func__);
1253
* Okay, so at this point the following is done:
1255
* 1. Our top-level / group leader window is a child of the display's
1257
* 2. The window manager can't get its hands on said window.
1258
* 3. We have a handle on the X11 atom which will be used to identify
1259
* the X11 property used as our lock.
1262
Debug("%s: Grabbing X server.\n", __func__);
1265
* Neither of these can fail, or at least not in the sense that they'd
1266
* return an error. Instead we'd likely see an X11 I/O error, tearing
1267
* the connection down.
1269
* XSync simply blocks until the XGrabServer request is acknowledged
1270
* by the server. It makes sure that we don't continue issuing requests,
1271
* such as XQueryTree, until the server grants our "grab".
1273
XGrabServer(defaultDisplay);
1274
XSync(defaultDisplay, False);
1277
* WARNING: At this point, we have grabbed the X server. Consider the
1278
* UI to be completely frozen. Under -no- circumstances should we return
1279
* without ungrabbing the server first.
1282
if (XQueryTree(defaultDisplay, rootWindow, &dummy1, &dummy2, &children,
1284
Warning("%s: XQueryTree failed\n", __func__);
1289
* Iterate over array of top-level windows. Search for those named
1290
* vmware-user and with the property "vmware-user-lock" set.
1292
* If any such windows are found, then another process has already
1293
* claimed this X session.
1295
for (index = 0; (index < nchildren) && !alreadyLocked; index++) {
1298
/* Skip unless window is named vmware-user. */
1299
if ((XFetchName(defaultDisplay, children[index], &name) == 0) ||
1301
strcmp(name, VMUSER_TITLE)) {
1307
* Query the window for the "vmware-user-lock" property.
1309
alreadyLocked = QueryX11Lock(defaultDisplay, children[index], lockAtom);
1314
* Yay. Lock isn't held, so go ahead and acquire it.
1316
if (!alreadyLocked) {
1317
unsigned char dummy[] = "1";
1318
Debug("%s: Setting property " LOCK_ATOM_NAME "\n", __func__);
1320
* NB: Current Xlib always returns one. This may generate a -fatal- IO
1323
XChangeProperty(defaultDisplay, groupLeader, lockAtom, lockAtom, 8,
1324
PropModeReplace, dummy, sizeof dummy);
1329
XUngrabServer(defaultDisplay);
1330
XSync(defaultDisplay, False);
1338
*-----------------------------------------------------------------------------
1342
* This is just a wrapper around XGetWindowProperty which queries the
1343
* window described by <dpy,w> for the property described by lockAtom.
1346
* TRUE if property defined by parameters exists; FALSE otherwise.
1351
*-----------------------------------------------------------------------------
1355
QueryX11Lock(Display *dpy, // IN: X11 display to query
1356
Window w, // IN: window to query
1357
Atom lockAtom) // IN: atom used for locking
1359
Atom ptype; // returned property type
1360
int pfmt; // returned property format
1361
unsigned long np; // returned # of properties
1362
unsigned long remaining; // amount of data remaining in property
1363
unsigned char *data = NULL;
1365
if (XGetWindowProperty(dpy, w, lockAtom, 0, 1, False, lockAtom,
1366
&ptype, &pfmt, &np, &remaining, &data) != Success) {
1367
Warning("%s: Unable to query window %lx for property %s\n", __func__, w,
1373
* Xlib is wacky. If the following test is true, then our property
1374
* didn't exist for the window in question. As a result, `data' is
1375
* unset, so don't worry about the lack of XFree(data) here.
1377
if (ptype == None) {
1382
* We care only about the existence of the property, not its value.
1391
*-----------------------------------------------------------------------------
1395
* Re-launch vmware-user by attempting to execute VMUSER_TITLE
1396
* ('vmware-user'), relying on the user's search path.
1399
* On success, vmware-user is relaunched in our stead. On failure, we
1400
* exit with EXIT_FAILURE.
1405
*-----------------------------------------------------------------------------
1411
Debug("> %s\n", __func__);
1412
execlp(VMUSER_TITLE, VMUSER_TITLE, NULL);