1
/*********************************************************
2
* Copyright (C) 2010 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
* Sets up an X11 lock atom and check it to avoid multiple running instances
26
#include "desktopEventsInt.h"
35
#define LOCK_ATOM_NAME "vmware-user-lock"
39
******************************************************************************
40
* InitGroupLeader -- */ /**
42
* This routine sets a few properties related to our main window created
43
* by {gdk,gtk}_init. Specifically this routine sets the window title,
44
* sets the override_redirect X11 property, and reparents it to the root
47
* In addition, this routine will return Xlib handles for the following
49
* - Main or group leader window
50
* - Display's root window
52
* As a result of this function:
53
* - groupLeader will have a title of VMUSER_TITLE.
54
* - groupLeader, if not already directly parented by the root, will be.
56
* - dpy will point to our default display (ex: $DISPLAY).
57
* - groupLeader will point to the window created by gtk_init().
58
* - rootWindow will point to the root window on $DISPLAY.
60
* @param[out] groupLeader Group leader window.
61
* @param[out] rootWindow Root window.
63
* @return TRUE on success, FALSE on failure.
65
******************************************************************************
69
InitGroupLeader(Window *groupLeader,
74
XSetWindowAttributes attr;
75
GdkDisplay *gdkDisplay;
78
attr.override_redirect = True;
83
gdkDisplay = gdk_display_get_default();
84
gdkLeader = gdk_display_get_default_group(gdkDisplay);
85
myGroupLeader = GDK_WINDOW_XWINDOW(gdkLeader);
86
myRootWindow = GDK_ROOT_WINDOW();
88
ASSERT(myGroupLeader);
91
/* XXX: With g_set_prgname() being called, this can probably go away. */
92
XStoreName(GDK_DISPLAY(), myGroupLeader, VMUSER_TITLE);
95
* Sanity check: Set the override redirect property on our group leader
96
* window (not default), then re-parent it to the root window (default).
97
* This makes sure that (a) a window manager can't re-parent our window,
98
* and (b) that we remain a top-level window.
100
XChangeWindowAttributes(GDK_DISPLAY(), myGroupLeader, CWOverrideRedirect,
102
XReparentWindow(GDK_DISPLAY(), myGroupLeader, myRootWindow, 10, 10);
103
XSync(GDK_DISPLAY(), FALSE);
105
*groupLeader = myGroupLeader;
106
*rootWindow = myRootWindow;
113
******************************************************************************
114
* QueryX11Lock -- */ /**
116
* This is just a wrapper around XGetWindowProperty which queries the
117
* window described by <dpy,w> for the property described by lockAtom.
119
* @param[in] dpy X11 display to query
120
* @param[in] w Window to query
121
* @param[in] lockAtom Atom used for locking
123
* @return TRUE if property defined by parameters exists; FALSE otherwise.
125
******************************************************************************
129
QueryX11Lock(Display *dpy,
133
Atom ptype; // returned property type
134
int pfmt; // returned property format
135
unsigned long np; // returned # of properties
136
unsigned long remaining; // amount of data remaining in property
137
unsigned char *data = NULL;
139
if (XGetWindowProperty(dpy, w, lockAtom, 0, 1, False, lockAtom,
140
&ptype, &pfmt, &np, &remaining, &data) != Success) {
141
g_warning("%s: Unable to query window %lx for property %s\n", __func__, w,
147
* Xlib is wacky. If the following test is true, then our property
148
* didn't exist for the window in question. As a result, `data' is
149
* unset, so don't worry about the lack of XFree(data) here.
156
* We care only about the existence of the property, not its value.
165
******************************************************************************
166
* AcquireDisplayLock -- */ /**
168
* This function "locks" the display against being "claimed" by another
169
* instance of vmware-user. It will succeed if we're the first/only
170
* instance of vmware-user, and fail otherwise.
172
* NB: This routine must be called -after- gtk_init().
174
* Vmware-user enjoys per-display exclusivity using the following algorithm:
176
* 1. Grab X server. (I.e., get exclusive access.)
177
* 2. Search for top-level X windows meeting the following criteria:
178
* a. named "vmware-user"
179
* b. has the property "vmware-user-lock" set.
180
* 3a. If any such windows described above found, then another vmware-user
181
* process is attached to this display, so we consider the display
183
* 3b. Else we're the only one. Set the "vmware-user-lock" property on
184
* our top-level window.
185
* 4. Ungrab the X server.
187
* The first time this routine is ever called during the lifetime of an X
188
* session, a new X11 Atom, "vmware-user-lock" is created for the lifetime
191
* The "vmware-user-lock" property may be set on this process's group leader
194
* @return TRUE if "lock" acquired (i.e., we're the first/only vmware-user
195
* process); otherwise FALSE.
197
******************************************************************************
201
AcquireDisplayLock(void)
203
Display *defaultDisplay; // Current default X11 display.
204
Window rootWindow; // Root window of defaultDisplay; used as root node
205
// passed to XQueryTree().
206
Window groupLeader; // Our instance's window group leader. This is
207
// implicitly created by gtk_init().
209
Window *children = NULL; // Array of windows returned by XQueryTree().
210
unsigned int nchildren; // Length of children.
212
Window dummy1, dummy2; // Throwaway window IDs for XQueryTree().
213
Atom lockAtom; // Refers to the "vmware-user-lock" X11 Atom.
216
Bool alreadyLocked = FALSE; // Set to TRUE if we discover lock is held.
219
defaultDisplay = GDK_DISPLAY();
222
* Reset some of our main window's settings & fetch Xlib handles for
223
* the GDK group leader and root windows.
225
if (InitGroupLeader(&groupLeader, &rootWindow) == FALSE) {
226
g_warning("%s: unable to initialize main window.\n", __func__);
231
* Look up the lock atom, creating it if it doesn't already exist.
233
lockAtom = XInternAtom(defaultDisplay, LOCK_ATOM_NAME, False);
234
if (lockAtom == None) {
235
g_warning("%s: unable to create X11 atom: " LOCK_ATOM_NAME "\n", __func__);
240
* Okay, so at this point the following is done:
242
* 1. Our top-level / group leader window is a child of the display's
244
* 2. The window manager can't get its hands on said window.
245
* 3. We have a handle on the X11 atom which will be used to identify
246
* the X11 property used as our lock.
249
g_debug("%s: Grabbing X server.\n", __func__);
252
* Neither of these can fail, or at least not in the sense that they'd
253
* return an error. Instead we'd likely see an X11 I/O error, tearing
254
* the connection down.
256
* XSync simply blocks until the XGrabServer request is acknowledged
257
* by the server. It makes sure that we don't continue issuing requests,
258
* such as XQueryTree, until the server grants our "grab".
260
XGrabServer(defaultDisplay);
261
XSync(defaultDisplay, False);
264
* WARNING: At this point, we have grabbed the X server. Consider the
265
* UI to be completely frozen. Under -no- circumstances should we return
266
* without ungrabbing the server first.
269
if (XQueryTree(defaultDisplay, rootWindow, &dummy1, &dummy2, &children,
271
g_warning("%s: XQueryTree failed\n", __func__);
276
* Iterate over array of top-level windows. Search for those named
277
* vmware-user and with the property "vmware-user-lock" set.
279
* If any such windows are found, then another process has already
280
* claimed this X session.
282
for (index = 0; (index < nchildren) && !alreadyLocked; index++) {
285
/* Skip unless window is named vmware-user. */
286
if ((XFetchName(defaultDisplay, children[index], &name) == 0) ||
288
strcmp(name, VMUSER_TITLE)) {
294
* Query the window for the "vmware-user-lock" property.
296
alreadyLocked = QueryX11Lock(defaultDisplay, children[index], lockAtom);
301
* Yay. Lock isn't held, so go ahead and acquire it.
303
if (!alreadyLocked) {
304
unsigned char dummy[] = "1";
305
g_debug("%s: Setting property " LOCK_ATOM_NAME "\n", __func__);
307
* NB: Current Xlib always returns one. This may generate a -fatal- IO
310
XChangeProperty(defaultDisplay, groupLeader, lockAtom, lockAtom, 8,
311
PropModeReplace, dummy, sizeof dummy);
316
XUngrabServer(defaultDisplay);
317
XSync(defaultDisplay, False);
325
******************************************************************************
326
* X11Lock_Init -- */ /**
328
* Initializes GTK, and sets up a lock atom to make sure only one vmusr
329
* instance is running.
331
* On error, this function will request that the application's main loop stop
334
* @param[in] ctx Application context.
335
* @param[in] pdata Registration data.
337
* @return TRUE on success, FALSE if another vmusr instance owns the display or
338
* not running in the right container (vmusr).
340
******************************************************************************
344
X11Lock_Init(ToolsAppCtx *ctx,
345
ToolsPluginData *pdata)
348
char *argv[] = { NULL, NULL };
350
if (strcmp(ctx->name, VMTOOLS_USER_SERVICE) != 0) {
351
VMTOOLSAPP_ERROR(ctx, EXIT_FAILURE);
356
* We depend on the window title when performing (primitive) vmware-user
357
* session detection, and unfortunately for us, GTK has a nasty habit of
358
* retitling toplevel windows. That said, we can control GTK's default
359
* title by setting Glib's application or program name.
361
* XXX Consider using g_set_application_name("VMware User Agent") or
364
g_set_prgname(VMUSER_TITLE);
365
argv[0] = VMUSER_TITLE;
367
/* XXX: is calling gtk_init() multiple times safe? */
368
gtk_init(&argc, (char ***) &argv);
370
if (!AcquireDisplayLock()) {
371
g_warning("Another instance of vmware-user already running. Exiting.\n");
372
VMTOOLSAPP_ERROR(ctx, EXIT_FAILURE);