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
dmxPropertyErrorHandler(Display * dpy,XErrorEvent * ev)76 dmxPropertyErrorHandler(Display * dpy, XErrorEvent * ev)
77 {
78     return 0;
79 }
80 
81 static const unsigned char *
dmxPropertyIdentifier(void)82 dmxPropertyIdentifier(void)
83 {
84     /* RATS: These buffers are only used in
85      * length-limited calls. */
86     char hostname[256];
87     static char buf[512];
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 *
dmxPropertyIterate(DMXScreenInfo * start,void * (* f)(DMXScreenInfo * dmxScreen,void *),void * closure)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
dmxPropertyCheckOtherServers(DMXScreenInfo * dmxScreen,Atom atom)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 = reallocarray(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 *
dmxPropertyCheckOtherWindows(DMXScreenInfo * dmxScreen,Atom atom)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, (unsigned long) 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
dmxPropertyDisplay(DMXScreenInfo * dmxScreen)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
dmxPropertySameDisplay(DMXScreenInfo * dmxScreen,const char * name)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
dmxPropertyWindow(DMXScreenInfo * dmxScreen)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[1024];              /* 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, (unsigned long) dmxScreen->scrnWin,
364                other->index, other->name, (unsigned long) 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 }
376