Commit | Line | Data |
---|---|---|
a09e091a JB |
1 | /* |
2 | * Copyright 2002-2003 Red Hat Inc., Durham, North Carolina. | |
3 | * | |
4 | * All Rights Reserved. | |
5 | * | |
6 | * Permission is hereby granted, free of charge, to any person obtaining | |
7 | * a copy of this software and associated documentation files (the | |
8 | * "Software"), to deal in the Software without restriction, including | |
9 | * without limitation on the rights to use, copy, modify, merge, | |
10 | * publish, distribute, sublicense, and/or sell copies of the Software, | |
11 | * and to permit persons to whom the Software is furnished to do so, | |
12 | * subject to the following conditions: | |
13 | * | |
14 | * The above copyright notice and this permission notice (including the | |
15 | * next paragraph) shall be included in all copies or substantial | |
16 | * portions of the Software. | |
17 | * | |
18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
19 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
20 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
21 | * NON-INFRINGEMENT. IN NO EVENT SHALL RED HAT AND/OR THEIR SUPPLIERS | |
22 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN | |
23 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | |
24 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
25 | * SOFTWARE. | |
26 | */ | |
27 | ||
28 | /* | |
29 | * Authors: | |
30 | * Rickard E. (Rik) Faith <faith@redhat.com> | |
31 | * | |
32 | */ | |
33 | ||
34 | /** \file | |
35 | * | |
36 | * It is possible for one of the DMX "backend displays" to actually be | |
37 | * smaller than the dimensions of the backend X server. Therefore, it | |
38 | * is possible for more than one of the DMX "backend displays" to be | |
39 | * physically located on the same backend X server. This situation must | |
40 | * be detected so that cursor motion can be handled in an expected | |
41 | * fashion. | |
42 | * | |
43 | * We could analyze the names used for the DMX "backend displays" (e.g., | |
44 | * the names passed to the -display command-line parameter), but there | |
45 | * are many possible names for a single X display, and failing to detect | |
46 | * sameness leads to very unexpected results. Therefore, whenever the | |
47 | * DMX server opens a window on a backend X server, a property value is | |
48 | * queried and set on that backend to detect when another window is | |
49 | * already open on that server. | |
50 | * | |
51 | * Further, it is possible that two different DMX server instantiations | |
52 | * both have windows on the same physical backend X server. This case | |
53 | * is also detected so that pointer input is not taken from that | |
54 | * particular backend X server. | |
55 | * | |
56 | * The routines in this file handle the property management. */ | |
57 | ||
58 | #ifdef HAVE_DMX_CONFIG_H | |
59 | #include <dmx-config.h> | |
60 | #endif | |
61 | ||
62 | #include "dmx.h" | |
63 | #include "dmxprop.h" | |
64 | #include "dmxlog.h" | |
65 | #include <X11/Xmu/SysUtil.h> /* For XmuGetHostname */ | |
66 | ||
67 | /** Holds the window id of all DMX windows on the backend X server. */ | |
68 | #define DMX_ATOMNAME "DMX_NAME" | |
69 | ||
70 | /** The identification string of this DMX server */ | |
71 | #define DMX_IDENT "Xdmx" | |
72 | ||
73 | extern char *display; | |
74 | ||
75 | static int | |
76 | dmxPropertyErrorHandler(Display * dpy, XErrorEvent * ev) | |
77 | { | |
78 | return 0; | |
79 | } | |
80 | ||
81 | static const unsigned char * | |
82 | dmxPropertyIdentifier(void) | |
83 | { | |
84 | /* RATS: These buffers are only used in | |
85 | * length-limited calls. */ | |
86 | char hostname[256]; | |
87 | static char buf[128]; | |
88 | static int initialized = 0; | |
89 | ||
90 | if (initialized++) | |
91 | return (unsigned char *) buf; | |
92 | ||
93 | XmuGetHostname(hostname, sizeof(hostname)); | |
94 | snprintf(buf, sizeof(buf), "%s:%s:%s", DMX_IDENT, hostname, display); | |
95 | return (unsigned char *) buf; | |
96 | } | |
97 | ||
98 | /** Starting with the \a start screen, iterate over all of the screens | |
99 | * on the same physical X server as \a start, calling \a f with the | |
100 | * screen and the \a closure. (The common case is that \a start is the | |
101 | * only DMX window on the backend X server.) */ | |
102 | void * | |
103 | dmxPropertyIterate(DMXScreenInfo * start, | |
104 | void *(*f) (DMXScreenInfo * dmxScreen, void *), | |
105 | void *closure) | |
106 | { | |
107 | DMXScreenInfo *pt; | |
108 | ||
109 | if (!start->next) { | |
110 | if (!start->beDisplay) | |
111 | return NULL; | |
112 | return f(start, closure); | |
113 | } | |
114 | ||
115 | for (pt = start->next; /* condition at end of loop */ ; pt = pt->next) { | |
116 | void *retval; | |
117 | ||
118 | /* beDisplay ban be NULL if a screen was detached */ | |
119 | dmxLog(dmxDebug, "pt = %p\n", pt); | |
120 | dmxLog(dmxDebug, "pt->beDisplay = %p\n", pt->beDisplay); | |
121 | if (pt->beDisplay && (retval = f(pt, closure))) | |
122 | return retval; | |
123 | if (pt == start) | |
124 | break; | |
125 | } | |
126 | return NULL; | |
127 | } | |
128 | ||
129 | /** Returns 0 if this is the only Xdmx session on the display; 1 | |
130 | * otherwise. */ | |
131 | static int | |
132 | dmxPropertyCheckOtherServers(DMXScreenInfo * dmxScreen, Atom atom) | |
133 | { | |
134 | Display *dpy = dmxScreen->beDisplay; | |
135 | XTextProperty tp; | |
136 | XTextProperty tproot; | |
137 | const char *pt; | |
138 | int retcode = 0; | |
139 | char **list = NULL; | |
140 | int count = 0; | |
141 | int i; | |
142 | int (*dmxOldHandler) (Display *, XErrorEvent *); | |
143 | ||
144 | if (!dpy) | |
145 | return 0; | |
146 | ||
147 | if (!XGetTextProperty(dpy, RootWindow(dpy, 0), &tproot, atom) | |
148 | || !tproot.nitems) | |
149 | return 0; | |
150 | ||
151 | /* Ignore BadWindow errors for this | |
152 | * routine because the window id stored | |
153 | * in the property might be old */ | |
154 | dmxOldHandler = XSetErrorHandler(dmxPropertyErrorHandler); | |
155 | for (pt = (const char *) tproot.value; pt && *pt; pt = pt ? pt + 1 : NULL) { | |
156 | if ((pt = strchr(pt, ','))) { | |
157 | Window win = strtol(pt + 1, NULL, 10); | |
158 | ||
159 | if (XGetTextProperty(dpy, win, &tp, atom) && tp.nitems) { | |
160 | if (!strncmp((char *) tp.value, DMX_IDENT, strlen(DMX_IDENT))) { | |
161 | int flag = 0; | |
162 | ||
163 | for (i = 0; i < count; i++) | |
164 | if (!strcmp(list[i], (char *) tp.value)) { | |
165 | ++flag; | |
166 | break; | |
167 | } | |
168 | if (flag) | |
169 | continue; | |
170 | ++retcode; | |
171 | dmxLogOutputWarning(dmxScreen, | |
172 | "%s also running on %s\n", | |
173 | tp.value, dmxScreen->name); | |
174 | list = realloc(list, ++count * sizeof(*list)); | |
175 | list[count - 1] = malloc(tp.nitems + 2); | |
176 | strncpy(list[count - 1], (char *) tp.value, tp.nitems + 1); | |
177 | } | |
178 | XFree(tp.value); | |
179 | } | |
180 | } | |
181 | } | |
182 | XSetErrorHandler(dmxOldHandler); | |
183 | ||
184 | for (i = 0; i < count; i++) | |
185 | free(list[i]); | |
186 | free(list); | |
187 | XFree(tproot.value); | |
188 | if (!retcode) | |
189 | dmxLogOutput(dmxScreen, "No Xdmx server running on backend\n"); | |
190 | return retcode; | |
191 | } | |
192 | ||
193 | /** Returns NULL if this is the only Xdmx window on the display. | |
194 | * Otherwise, returns a pointer to the dmxScreen of the other windows on | |
195 | * the display. */ | |
196 | static DMXScreenInfo * | |
197 | dmxPropertyCheckOtherWindows(DMXScreenInfo * dmxScreen, Atom atom) | |
198 | { | |
199 | Display *dpy = dmxScreen->beDisplay; | |
200 | const unsigned char *id = dmxPropertyIdentifier(); | |
201 | XTextProperty tproot; | |
202 | XTextProperty tp; | |
203 | const char *pt; | |
204 | int (*dmxOldHandler) (Display *, XErrorEvent *); | |
205 | ||
206 | if (!dpy) | |
207 | return NULL; | |
208 | ||
209 | if (!XGetTextProperty(dpy, RootWindow(dpy, 0), &tproot, atom) | |
210 | || !tproot.nitems) | |
211 | return 0; | |
212 | ||
213 | /* Ignore BadWindow errors for this | |
214 | * routine because the window id stored | |
215 | * in the property might be old */ | |
216 | dmxOldHandler = XSetErrorHandler(dmxPropertyErrorHandler); | |
217 | for (pt = (const char *) tproot.value; pt && *pt; pt = pt ? pt + 1 : NULL) { | |
218 | if ((pt = strchr(pt, ','))) { | |
219 | Window win = strtol(pt + 1, NULL, 10); | |
220 | ||
221 | if (XGetTextProperty(dpy, win, &tp, atom) && tp.nitems) { | |
222 | dmxLog(dmxDebug, "On %s/%lu: %s\n", | |
223 | dmxScreen->name, win, tp.value); | |
224 | if (!strncmp((char *) tp.value, (char *) id, | |
225 | strlen((char *) id))) { | |
226 | int idx; | |
227 | ||
228 | if (!(pt = strchr((char *) tp.value, ','))) | |
229 | continue; | |
230 | idx = strtol(pt + 1, NULL, 10); | |
231 | if (idx < 0 || idx >= dmxNumScreens) | |
232 | continue; | |
233 | if (dmxScreens[idx].scrnWin != win) | |
234 | continue; | |
235 | XSetErrorHandler(dmxOldHandler); | |
236 | return &dmxScreens[idx]; | |
237 | } | |
238 | XFree(tp.value); | |
239 | } | |
240 | } | |
241 | } | |
242 | XSetErrorHandler(dmxOldHandler); | |
243 | XFree(tproot.value); | |
244 | return 0; | |
245 | } | |
246 | ||
247 | /** Returns 0 if this is the only Xdmx session on the display; 1 | |
248 | * otherwise. */ | |
249 | int | |
250 | dmxPropertyDisplay(DMXScreenInfo * dmxScreen) | |
251 | { | |
252 | Atom atom; | |
253 | const unsigned char *id = dmxPropertyIdentifier(); | |
254 | Display *dpy = dmxScreen->beDisplay; | |
255 | ||
256 | if (!dpy) | |
257 | return 0; | |
258 | ||
259 | atom = XInternAtom(dpy, DMX_ATOMNAME, False); | |
260 | if (dmxPropertyCheckOtherServers(dmxScreen, atom)) { | |
261 | dmxScreen->shared = 1; | |
262 | return 1; | |
263 | } | |
264 | XChangeProperty(dpy, RootWindow(dpy, 0), atom, XA_STRING, 8, | |
265 | PropModeReplace, id, strlen((char *) id)); | |
266 | return 0; | |
267 | } | |
268 | ||
269 | /** Returns 1 if the dmxScreen and the display in \a name are on the | |
270 | * same display, or 0 otherwise. We can't just compare the display | |
271 | * names because there can be multiple synonyms for the same display, | |
272 | * some of which cannot be determined without accessing the display | |
273 | * itself (e.g., domain aliases or machines with multiple NICs). */ | |
274 | int | |
275 | dmxPropertySameDisplay(DMXScreenInfo * dmxScreen, const char *name) | |
276 | { | |
277 | Display *dpy0 = dmxScreen->beDisplay; | |
278 | Atom atom0; | |
279 | XTextProperty tp0; | |
280 | Display *dpy1 = NULL; | |
281 | Atom atom1; | |
282 | XTextProperty tp1; | |
283 | int retval = 0; | |
284 | ||
285 | if (!dpy0) | |
286 | return 0; | |
287 | ||
288 | tp0.nitems = 0; | |
289 | tp1.nitems = 0; | |
290 | ||
291 | if ((atom0 = XInternAtom(dpy0, DMX_ATOMNAME, True)) == None) { | |
292 | dmxLog(dmxWarning, "No atom on %s\n", dmxScreen->name); | |
293 | return 0; | |
294 | } | |
295 | if (!XGetTextProperty(dpy0, RootWindow(dpy0, 0), &tp0, atom0) | |
296 | || !tp0.nitems) { | |
297 | dmxLog(dmxWarning, "No text property on %s\n", dmxScreen->name); | |
298 | return 0; | |
299 | } | |
300 | ||
301 | if (!(dpy1 = XOpenDisplay(name))) { | |
302 | dmxLog(dmxWarning, "Cannot open %s\n", name); | |
303 | goto cleanup; | |
304 | } | |
305 | atom1 = XInternAtom(dpy1, DMX_ATOMNAME, True); | |
306 | if (atom1 == None) { | |
307 | dmxLog(dmxDebug, "No atom on %s\n", name); | |
308 | goto cleanup; | |
309 | } | |
310 | if (!XGetTextProperty(dpy1, RootWindow(dpy1, 0), &tp1, atom1) | |
311 | || !tp1.nitems) { | |
312 | dmxLog(dmxDebug, "No text property on %s\n", name); | |
313 | goto cleanup; | |
314 | } | |
315 | if (!strcmp((char *) tp0.value, (char *) tp1.value)) | |
316 | retval = 1; | |
317 | ||
318 | cleanup: | |
319 | if (tp0.nitems) | |
320 | XFree(tp0.value); | |
321 | if (tp1.nitems) | |
322 | XFree(tp1.value); | |
323 | if (dpy1) | |
324 | XCloseDisplay(dpy1); | |
325 | return retval; | |
326 | } | |
327 | ||
328 | /** Prints a log message if \a dmxScreen is on the same backend X server | |
329 | * as some other DMX backend (output) screen. Modifies the property | |
330 | * (#DMX_ATOMNAME) on the backend X server to reflect the creation of \a | |
331 | * dmxScreen. | |
332 | * | |
333 | * The root window of the backend X server holds a list of window ids | |
334 | * for all DMX windows (on this DMX server or some other DMX server). | |
335 | * | |
336 | * This list can then be iterated, and the property for each window can | |
337 | * be examined. This property contains the following tuple (no quotes): | |
338 | * | |
339 | * "#DMX_IDENT:<hostname running DMX>:<display name of DMX>,<screen number>" | |
340 | */ | |
341 | void | |
342 | dmxPropertyWindow(DMXScreenInfo * dmxScreen) | |
343 | { | |
344 | Atom atom; | |
345 | const unsigned char *id = dmxPropertyIdentifier(); | |
346 | Display *dpy = dmxScreen->beDisplay; | |
347 | Window win = dmxScreen->scrnWin; | |
348 | DMXScreenInfo *other; | |
349 | char buf[128]; /* RATS: only used with snprintf */ | |
350 | ||
351 | if (!dpy) | |
352 | return; /* FIXME: What should be done here if Xdmx is started | |
353 | * with this screen initially detached? | |
354 | */ | |
355 | ||
356 | atom = XInternAtom(dpy, DMX_ATOMNAME, False); | |
357 | if ((other = dmxPropertyCheckOtherWindows(dmxScreen, atom))) { | |
358 | DMXScreenInfo *tmp = dmxScreen->next; | |
359 | ||
360 | dmxScreen->next = (other->next ? other->next : other); | |
361 | other->next = (tmp ? tmp : dmxScreen); | |
362 | dmxLog(dmxDebug, "%d/%s/%lu and %d/%s/%lu are on the same backend\n", | |
363 | dmxScreen->index, dmxScreen->name, dmxScreen->scrnWin, | |
364 | other->index, other->name, other->scrnWin); | |
365 | } | |
366 | ||
367 | snprintf(buf, sizeof(buf), ".%d,%lu", dmxScreen->index, | |
368 | (long unsigned) win); | |
369 | XChangeProperty(dpy, RootWindow(dpy, 0), atom, XA_STRING, 8, | |
370 | PropModeAppend, (unsigned char *) buf, strlen(buf)); | |
371 | ||
372 | snprintf(buf, sizeof(buf), "%s,%d", id, dmxScreen->index); | |
373 | XChangeProperty(dpy, win, atom, XA_STRING, 8, | |
374 | PropModeAppend, (unsigned char *) buf, strlen(buf)); | |
375 | } |