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