Home | History | Annotate | Line # | Download | only in dmx
      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 (at) 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 
     72 extern char *display;
     73 
     74 static int dmxPropertyErrorHandler(Display *dpy, XErrorEvent *ev)
     75 {
     76     return 0;
     77 }
     78 
     79 static 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.) */
     98 void *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. */
    122 static 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. */
    181 static 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. */
    229 int 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). */
    253 int 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  */
    315 void 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