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
66/** Holds the window id of all DMX windows on the backend X server. */
67#define DMX_ATOMNAME "DMX_NAME"
68
69/** The identification string of this DMX server */
70#define DMX_IDENT    "Xdmx"
71
72extern char *display;
73
74static int dmxPropertyErrorHandler(Display *dpy, XErrorEvent *ev)
75{
76    return 0;
77}
78
79static const unsigned char *dmxPropertyIdentifier(void)
80{
81                                /* RATS: These buffers are only used in
82                                 * length-limited calls. */
83    char          hostname[256];
84    static char   buf[128];
85    static int    initialized = 0;
86
87    if (initialized++) return (unsigned char *)buf;
88
89    XmuGetHostname(hostname, sizeof(hostname));
90    XmuSnprintf(buf, sizeof(buf), "%s:%s:%s", DMX_IDENT, hostname, display);
91    return (unsigned char *)buf;
92}
93
94/** Starting with the \a start screen, iterate over all of the screens
95 * on the same physical X server as \a start, calling \a f with the
96 * screen and the \a closure.  (The common case is that \a start is the
97 * only DMX window on the backend X server.) */
98void *dmxPropertyIterate(DMXScreenInfo *start,
99                         void *(*f)(DMXScreenInfo *dmxScreen, void *),
100                         void *closure)
101{
102    DMXScreenInfo *pt;
103
104    if (!start->next) {
105        if (!start->beDisplay) return NULL;
106        return f(start, closure);
107    }
108
109    for (pt = start->next; /* condition at end of loop */; pt = pt->next) {
110        void *retval;
111        /* beDisplay ban be NULL if a screen was detached */
112        dmxLog(dmxDebug, "pt = %p\n", pt);
113        dmxLog(dmxDebug, "pt->beDisplay = %p\n", pt->beDisplay);
114        if (pt->beDisplay && (retval = f(pt, closure))) return retval;
115        if (pt == start) break;
116    }
117    return NULL;
118}
119
120/** Returns 0 if this is the only Xdmx session on the display; 1
121 * otherwise. */
122static int dmxPropertyCheckOtherServers(DMXScreenInfo *dmxScreen, Atom atom)
123{
124    Display       *dpy    = dmxScreen->beDisplay;
125    XTextProperty tp;
126    XTextProperty tproot;
127    const char    *pt;
128    int           retcode = 0;
129    char          **list  = NULL;
130    int           count   = 0;
131    int           i;
132    int           (*dmxOldHandler)(Display *, XErrorEvent *);
133
134    if (!dpy)
135	return 0;
136
137    if (!XGetTextProperty(dpy, RootWindow(dpy,0), &tproot, atom)
138        || !tproot.nitems) return 0;
139
140                                /* Ignore BadWindow errors for this
141                                 * routine because the window id stored
142                                 * in the property might be old */
143    dmxOldHandler = XSetErrorHandler(dmxPropertyErrorHandler);
144    for (pt = (const char *)tproot.value; pt && *pt; pt = pt ? pt + 1 : NULL) {
145        if ((pt = strchr(pt, ','))) {
146            Window win = strtol(pt+1, NULL, 10);
147            if (XGetTextProperty(dpy, win, &tp, atom) && tp.nitems) {
148                if (!strncmp((char *)tp.value, DMX_IDENT, strlen(DMX_IDENT))) {
149                    int flag = 0;
150                    for (i = 0; i < count; i++)
151                        if (!strcmp(list[i], (char *)tp.value)) {
152                            ++flag;
153                            break;
154                        }
155                    if (flag) continue;
156                    ++retcode;
157                    dmxLogOutputWarning(dmxScreen,
158                                        "%s also running on %s\n",
159                                        tp.value, dmxScreen->name);
160                    list = realloc(list, ++count * sizeof(*list));
161                    list[count-1] = malloc(tp.nitems + 2);
162                    strncpy(list[count-1], (char *)tp.value, tp.nitems + 1);
163                }
164                XFree(tp.value);
165            }
166        }
167    }
168    XSetErrorHandler(dmxOldHandler);
169
170    for (i = 0; i < count; i++) free(list[i]);
171    free(list);
172    XFree(tproot.value);
173    if (!retcode)
174        dmxLogOutput(dmxScreen, "No Xdmx server running on backend\n");
175    return retcode;
176}
177
178/** Returns NULL if this is the only Xdmx window on the display.
179 * Otherwise, returns a pointer to the dmxScreen of the other windows on
180 * the display. */
181static DMXScreenInfo *dmxPropertyCheckOtherWindows(DMXScreenInfo *dmxScreen,
182                                                   Atom atom)
183{
184    Display             *dpy = dmxScreen->beDisplay;
185    const unsigned char *id  = dmxPropertyIdentifier();
186    XTextProperty       tproot;
187    XTextProperty       tp;
188    const char          *pt;
189    int                 (*dmxOldHandler)(Display *, XErrorEvent *);
190
191    if (!dpy)
192	return NULL;
193
194    if (!XGetTextProperty(dpy, RootWindow(dpy,0), &tproot, atom)
195        || !tproot.nitems) return 0;
196
197                                /* Ignore BadWindow errors for this
198                                 * routine because the window id stored
199                                 * in the property might be old */
200    dmxOldHandler = XSetErrorHandler(dmxPropertyErrorHandler);
201    for (pt = (const char *)tproot.value; pt && *pt; pt = pt ? pt + 1 : NULL) {
202        if ((pt = strchr(pt, ','))) {
203            Window win = strtol(pt+1, NULL, 10);
204            if (XGetTextProperty(dpy, win, &tp, atom) && tp.nitems) {
205                dmxLog(dmxDebug,"On %s/%lu: %s\n",
206                       dmxScreen->name, win, tp.value);
207                if (!strncmp((char *)tp.value, (char *)id,
208                             strlen((char *)id))) {
209                    int idx;
210
211                    if (!(pt = strchr((char *)tp.value, ','))) continue;
212                    idx = strtol(pt+1, NULL, 10);
213                    if (idx < 0 || idx >= dmxNumScreens) continue;
214                    if (dmxScreens[idx].scrnWin != win) continue;
215                    XSetErrorHandler(dmxOldHandler);
216                    return &dmxScreens[idx];
217                }
218                XFree(tp.value);
219            }
220        }
221    }
222    XSetErrorHandler(dmxOldHandler);
223    XFree(tproot.value);
224    return 0;
225}
226
227/** Returns 0 if this is the only Xdmx session on the display; 1
228 * otherwise. */
229int dmxPropertyDisplay(DMXScreenInfo *dmxScreen)
230{
231    Atom                atom;
232    const unsigned char *id  = dmxPropertyIdentifier();
233    Display             *dpy = dmxScreen->beDisplay;
234
235    if (!dpy)
236	return 0;
237
238    atom = XInternAtom(dpy, DMX_ATOMNAME, False);
239    if (dmxPropertyCheckOtherServers(dmxScreen, atom)) {
240        dmxScreen->shared = 1;
241        return 1;
242    }
243    XChangeProperty(dpy, RootWindow(dpy,0), atom, XA_STRING, 8,
244                    PropModeReplace, id, strlen((char *)id));
245    return 0;
246}
247
248/** Returns 1 if the dmxScreen and the display in \a name are on the
249 * same display, or 0 otherwise.  We can't just compare the display
250 * names because there can be multiple synonyms for the same display,
251 * some of which cannot be determined without accessing the display
252 * itself (e.g., domain aliases or machines with multiple NICs). */
253int dmxPropertySameDisplay(DMXScreenInfo *dmxScreen, const char *name)
254{
255    Display             *dpy0  = dmxScreen->beDisplay;
256    Atom                atom0;
257    XTextProperty       tp0;
258    Display             *dpy1  = NULL;
259    Atom                atom1;
260    XTextProperty       tp1;
261    int                 retval = 0;
262
263    if (!dpy0)
264	return 0;
265
266    tp0.nitems = 0;
267    tp1.nitems = 0;
268
269    if ((atom0 = XInternAtom(dpy0, DMX_ATOMNAME, True)) == None) {
270        dmxLog(dmxWarning, "No atom on %s\n", dmxScreen->name);
271        return 0;
272    }
273    if (!XGetTextProperty(dpy0, RootWindow(dpy0,0), &tp0, atom0)
274        || !tp0.nitems) {
275        dmxLog(dmxWarning, "No text property on %s\n", dmxScreen->name);
276        return 0;
277    }
278
279    if (!(dpy1 = XOpenDisplay(name))) {
280        dmxLog(dmxWarning, "Cannot open %s\n", name);
281        goto cleanup;
282    }
283    atom1 = XInternAtom(dpy1, DMX_ATOMNAME, True);
284    if (atom1 == None) {
285        dmxLog(dmxDebug, "No atom on %s\n", name);
286        goto cleanup;
287    }
288    if (!XGetTextProperty(dpy1, RootWindow(dpy1,0), &tp1, atom1)
289        || !tp1.nitems) {
290        dmxLog(dmxDebug, "No text property on %s\n", name);
291        goto cleanup;
292    }
293    if (!strcmp((char *)tp0.value, (char *)tp1.value)) retval = 1;
294
295  cleanup:
296    if (tp0.nitems) XFree(tp0.value);
297    if (tp1.nitems) XFree(tp1.value);
298    if (dpy1)       XCloseDisplay(dpy1);
299    return retval;
300}
301
302/** Prints a log message if \a dmxScreen is on the same backend X server
303 * as some other DMX backend (output) screen.  Modifies the property
304 * (#DMX_ATOMNAME) on the backend X server to reflect the creation of \a
305 * dmxScreen.
306 *
307 * The root window of the backend X server holds a list of window ids
308 * for all DMX windows (on this DMX server or some other DMX server).
309 *
310 * This list can then be iterated, and the property for each window can
311 * be examined.  This property contains the following tuple (no quotes):
312 *
313 * "#DMX_IDENT:<hostname running DMX>:<display name of DMX>,<screen number>"
314 */
315void dmxPropertyWindow(DMXScreenInfo *dmxScreen)
316{
317    Atom                atom;
318    const unsigned char *id  = dmxPropertyIdentifier();
319    Display             *dpy = dmxScreen->beDisplay;
320    Window              win  = dmxScreen->scrnWin;
321    DMXScreenInfo       *other;
322    char                buf[128]; /* RATS: only used with XmuSnprintf */
323
324    if (!dpy)
325	return; /* FIXME: What should be done here if Xdmx is started
326		 * with this screen initially detached?
327		 */
328
329    atom = XInternAtom(dpy, DMX_ATOMNAME, False);
330    if ((other = dmxPropertyCheckOtherWindows(dmxScreen, atom))) {
331        DMXScreenInfo *tmp = dmxScreen->next;
332        dmxScreen->next    = (other->next ? other->next : other);
333        other->next        = (tmp         ? tmp         : dmxScreen);
334        dmxLog(dmxDebug, "%d/%s/%lu and %d/%s/%lu are on the same backend\n",
335               dmxScreen->index, dmxScreen->name, dmxScreen->scrnWin,
336               other->index, other->name, other->scrnWin);
337    }
338
339    XmuSnprintf(buf, sizeof(buf), ".%d,%lu", dmxScreen->index,
340                (long unsigned)win);
341    XChangeProperty(dpy, RootWindow(dpy,0), atom, XA_STRING, 8,
342                    PropModeAppend, (unsigned char *)buf, strlen(buf));
343
344    XmuSnprintf(buf, sizeof(buf), "%s,%d", id, dmxScreen->index);
345    XChangeProperty(dpy, win, atom, XA_STRING, 8,
346                    PropModeAppend, (unsigned char *)buf, strlen(buf));
347}
348