text: /* $XFree86$ */
  /*
   * Copyright 2002-2003 Red Hat Inc., Durham, North Carolina.
   *
   * All Rights Reserved.
   *
   * Permission is hereby granted, free of charge, to any person obtaining
   * a copy of this software and associated documentation files (the
   * "Software"), to deal in the Software without restriction, including
   * without limitation on the rights to use, copy, modify, merge,
   * publish, distribute, sublicense, and/or sell copies of the Software,
   * and to permit persons to whom the Software is furnished to do so,
   * subject to the following conditions:
   *
   * The above copyright notice and this permission notice (including the
   * next paragraph) shall be included in all copies or substantial
   * portions of the Software.
   *
   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
   * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
   * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
   * NON-INFRINGEMENT.  IN NO EVENT SHALL RED HAT AND/OR THEIR SUPPLIERS
   * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
   * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
   * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
   * SOFTWARE.
   */
  
  /*
   * Authors:
   *   Rickard E. (Rik) Faith <faith@redhat.com>
   *
   */
  
  /** \file
   * Provides interface for reading DMX configuration files and for
   * combining that information with command-line configuration parameters. */
      
  
  #ifdef HAVE_DMX_CONFIG_H
  #include <dmx-config.h>
  #endif
  
  #include "dmx.h"
  #include "dmxinput.h"
  #include "dmxconfig.h"
  #include "dmxparse.h"
  #include "dmxlog.h"
  #include "dmxcb.h"
  #include "dmxstat.h"
  #include "parser.h"
  
  extern int  yyparse(void);
  extern FILE *yyin;
  
  static char *dmxXkbRules;
  static char *dmxXkbModel;
  static char *dmxXkbLayout;
  static char *dmxXkbVariant;
  static char *dmxXkbOptions;
  
  /** Stores lists of configuration information. */
  typedef struct DMXConfigListStruct {
      const char                 *name;
      struct DMXConfigListStruct *next;
  } DMXConfigList, *DMXConfigListPtr;
  
  /** This stucture stores the parsed configuration information. */
  typedef struct DMXConfigCmdStruct {
      const char    *filename;
      const char    *config;
      DMXConfigList *displays;
      DMXConfigList *inputs;
      DMXConfigList *xinputs;
  } DMXConfigCmd, *DMXConfigCmdPtr;
  
  DMXConfigEntryPtr    dmxConfigEntry;
  static DMXConfigCmd  dmxConfigCmd;
  
  static int dmxDisplaysFromCommandLine;
  
  /** Make a note that \a display is the name of an X11 display that
   * should be initialized as a backend (output) display.  Called from
   * #ddxProcessArgument. */
  void dmxConfigStoreDisplay(const char *display)
  {
      DMXConfigListPtr entry = malloc(sizeof(*entry));
      entry->name = strdup(display);
      entry->next = NULL;
      if (!dmxConfigCmd.displays) dmxConfigCmd.displays = entry;
      else {
          DMXConfigList *pt;
          for (pt = dmxConfigCmd.displays; pt->next; pt = pt->next);
          if (!pt)
              dmxLog(dmxFatal, "dmxConfigStoreDisplay: end of list non-NULL\n");
          pt->next = entry;
      }
      ++dmxDisplaysFromCommandLine;
  }
  
  /** Make a note that \a input is the name of an X11 display that should
   * be used for input (either a backend or a console input device). */
  void dmxConfigStoreInput(const char *input)
  {
      DMXConfigListPtr entry = malloc(sizeof(*entry));
      entry->name = strdup(input);
      entry->next = NULL;
      if (!dmxConfigCmd.inputs) dmxConfigCmd.inputs = entry;
      else {
          DMXConfigList *pt;
          for (pt = dmxConfigCmd.inputs; pt->next; pt = pt->next);
          if (!pt)
              dmxLog(dmxFatal, "dmxConfigStoreInput: end of list non-NULL\n");
          pt->next = entry;
      }
  }
  
  /** Make a note that \a input is the name of an X11 display that should
   * be used for input from XInput extension devices. */
  void dmxConfigStoreXInput(const char *input)
  {
      DMXConfigListPtr entry = malloc(sizeof(*entry));
      entry->name = strdup(input);
      entry->next = NULL;
      if (!dmxConfigCmd.xinputs) dmxConfigCmd.xinputs = entry;
      else {
          DMXConfigList *pt;
          for (pt = dmxConfigCmd.xinputs; pt->next; pt = pt->next);
          if (!pt)
              dmxLog(dmxFatal, "dmxConfigStoreXInput: end of list non-NULL\n");
          pt->next = entry;
      }
  }
  
  /** Make a note that \a file is the configuration file. */
  void dmxConfigStoreFile(const char *file)
  {
      if (dmxConfigCmd.filename)
          dmxLog(dmxFatal, "Only one -configfile allowed\n");
      dmxConfigCmd.filename = strdup(file);
  }
  
  /** Make a note that \a config should be used as the configuration for
   * current instantiation of the DMX server. */
  void dmxConfigStoreConfig(const char *config)
  {
      if (dmxConfigCmd.config) dmxLog(dmxFatal, "Only one -config allowed\n");
      dmxConfigCmd.config = strdup(config);
  }
  
  static int dmxConfigReadFile(const char *filename, int debug)
  {
      FILE *str;
  
      if (!(str = fopen(filename, "r"))) return -1;
      dmxLog(dmxInfo, "Reading configuration file \"%s\"\n", filename);
      yyin    = str;
      yydebug = debug;
      yyparse();
      fclose(str);
      return 0;
  }
  
  static const char *dmxConfigMatch(const char *target, DMXConfigEntryPtr entry)
  {
      DMXConfigVirtualPtr v     = entry->virtual;
      const char          *name = NULL;
  
      if (v && v->name) name = v->name;
  
      if (v && !dmxConfigCmd.config) return v->name ? v->name : "<noname>";
      if (!name)                     return NULL;
      if (!strcmp(name, target))     return name;
      return NULL;
  }
  
  static DMXScreenInfo *dmxConfigAddDisplay(const char *name,
                                            int scrnWidth,   int scrnHeight,
                                            int scrnX,       int scrnY,
                                            int scrnXSign,   int scrnYSign,
                                            int rootWidth,   int rootHeight,
                                            int rootX,       int rootY,
                                            int rootXSign,   int rootYSign)
  {
      DMXScreenInfo *dmxScreen;
      
      if (!(dmxScreens = realloc(dmxScreens,
                                 (dmxNumScreens+1) * sizeof(*dmxScreens))))
          dmxLog(dmxFatal,
                 "dmxConfigAddDisplay: realloc failed for screen %d (%s)\n",
                 dmxNumScreens, name);
      
      dmxScreen = &dmxScreens[dmxNumScreens];
      memset(dmxScreen, 0, sizeof(*dmxScreen));
      dmxScreen->name       = name;
      dmxScreen->index      = dmxNumScreens;
      dmxScreen->scrnWidth  = scrnWidth;
      dmxScreen->scrnHeight = scrnHeight;
      dmxScreen->scrnX      = scrnX;
      dmxScreen->scrnY      = scrnY;
      dmxScreen->scrnXSign  = scrnXSign;
      dmxScreen->scrnYSign  = scrnYSign;
      dmxScreen->rootWidth  = rootWidth;
      dmxScreen->rootHeight = rootHeight;
      dmxScreen->rootX      = rootX;
      dmxScreen->rootY      = rootY;
      dmxScreen->stat       = dmxStatAlloc();
      ++dmxNumScreens;
      return dmxScreen;
  }
  
  DMXInputInfo *dmxConfigAddInput(const char *name, int core)
  {
      DMXInputInfo *dmxInput;
  
      if (!(dmxInputs = realloc(dmxInputs,
                                (dmxNumInputs+1) * sizeof(*dmxInputs))))
          dmxLog(dmxFatal,
                 "dmxConfigAddInput: realloc failed for input %d (%s)\n",
                 dmxNumInputs, name);
  
      dmxInput = &dmxInputs[dmxNumInputs];
  
      memset(dmxInput, 0, sizeof(*dmxInput));
      dmxInput->name     = name;
      dmxInput->inputIdx = dmxNumInputs;
      dmxInput->scrnIdx  = -1;
      dmxInput->core     = core;
      ++dmxNumInputs;
      return dmxInput;
  }
  
  static void dmxConfigCopyFromDisplay(DMXConfigDisplayPtr d)
  {
      DMXScreenInfo *dmxScreen;
  
      dmxScreen         = dmxConfigAddDisplay(d->name,
                                              d->scrnWidth, d->scrnHeight,
                                              d->scrnX,     d->scrnY,
                                              d->scrnXSign, d->scrnYSign,
                                              d->rootWidth, d->rootHeight,
                                              d->rootX,     d->rootY,
                                              d->rootXSign, d->rootXSign);
      dmxScreen->where  = PosAbsolute;
      dmxScreen->whereX = d->rootXOrigin;
      dmxScreen->whereY = d->rootYOrigin;
  }
  
  static void dmxConfigCopyFromWall(DMXConfigWallPtr w)
  {
      DMXConfigStringPtr pt;
      DMXScreenInfo      *dmxScreen;
      int                edge = dmxNumScreens;
      int                last = dmxNumScreens;
  
      if (!w->xwall && !w->ywall) { /* Try to make it square */
          int count;
          for (pt = w->nameList, count = 0; pt; pt = pt->next) ++count;
          w->xwall = sqrt(count) + .5;
      }
  
      for (pt = w->nameList; pt; pt = pt->next) {
          dmxScreen = dmxConfigAddDisplay(pt->string, w->width, w->height,
                                          0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
          if (pt == w->nameList) { /* Upper left */
              dmxScreen->where  = PosAbsolute;
              dmxScreen->whereX = 0;
              dmxScreen->whereY = 0;
          } else if (w->xwall) {  /* Tile left to right, then top to bottom */
              if (!((dmxNumScreens-1) % w->xwall)) {
                  dmxScreen->where          = PosBelow;
                  dmxScreen->whereRefScreen = edge;
                  edge                      = dmxNumScreens-1;
              } else {
                  dmxScreen->where          = PosRightOf;
                  dmxScreen->whereRefScreen = last;
              }
          } else {                /* Tile top to bottom, then left to right */
              if (!((dmxNumScreens-1) % w->ywall)) {
                  dmxScreen->where          = PosRightOf;
                  dmxScreen->whereRefScreen = edge;
                  edge                      = dmxNumScreens-1;
              } else {
                  dmxScreen->where          = PosBelow;
                  dmxScreen->whereRefScreen = last;
              }
  
          }
          last = dmxNumScreens-1;
          if (dmxScreen->where == PosAbsolute)
              dmxLog(dmxInfo, "Added %s at %d %d\n",
                     pt->string, dmxScreen->whereX, dmxScreen->whereY);
          else
              dmxLog(dmxInfo, "Added %s %s %s\n",
                     pt->string,
                     dmxScreen->where == PosBelow ? "below" : "right of",
                     dmxScreens[dmxScreen->whereRefScreen].name);
      }
  }
  
  static void dmxConfigCopyFromOption(DMXConfigOptionPtr o)
  {
      DMXConfigStringPtr pt;
      int                argc   = 0;
      char               **argv = NULL;
  
      if (serverGeneration != 1) return; /* FIXME: only do once, for now */
      if (!o || !o->string) return;
      for (pt = o->option; pt; pt = pt->next) {
          if (pt->string) {
              ++argc;
              argv = realloc(argv, (argc+1) * sizeof(*argv));
              argv[argc] = (char *)pt->string;
          }
      }
      argv[0] = NULL;
      ProcessCommandLine(argc+1, argv);
      free(argv);
  }
  
  static void dmxConfigCopyFromParam(DMXConfigParamPtr p)
  {
      const char **argv;
      int        argc;
      
      if ((argv = dmxConfigLookupParam(p, "xkbrules", &argc)) && argc == 2) {
          dmxConfigSetXkbRules(argv[1]);
      } else if ((argv = dmxConfigLookupParam(p, "xkbmodel", &argc))
                 && argc == 2) {
          dmxConfigSetXkbModel(argv[1]);
      } else if ((argv = dmxConfigLookupParam(p, "xkblayout", &argc))
                 && argc == 2) {
          dmxConfigSetXkbLayout(argv[1]);
      } else if ((argv = dmxConfigLookupParam(p, "xkbvariant", &argc))
                 && argc == 2) {
          dmxConfigSetXkbVariant(argv[1]);
      } else if ((argv = dmxConfigLookupParam(p, "xkboptions", &argc))
                 && argc == 2) {
          dmxConfigSetXkbOptions(argv[1]);
      }
  }
  
  static void dmxConfigCopyData(DMXConfigVirtualPtr v)
  {
      DMXConfigSubPtr sub;
      
      if (v->dim) dmxSetWidthHeight(v->dim->x, v->dim->y);
      else        dmxSetWidthHeight(0, 0);
      for (sub = v->subentry; sub; sub = sub->next) {
          switch (sub->type) {
          case dmxConfigDisplay: dmxConfigCopyFromDisplay(sub->display); break;
          case dmxConfigWall:    dmxConfigCopyFromWall(sub->wall);       break;
          case dmxConfigOption:  dmxConfigCopyFromOption(sub->option);   break;
          case dmxConfigParam:   dmxConfigCopyFromParam(sub->param);     break;
          default:
              dmxLog(dmxFatal,
                     "dmxConfigCopyData: not a display, wall, or value\n");
          }
      }
  }
  
  static void dmxConfigFromCommandLine(void)
  {
      DMXConfigListPtr pt;
      
      dmxLog(dmxInfo, "Using configuration from command line\n");
      for (pt = dmxConfigCmd.displays; pt; pt = pt->next) {
          DMXScreenInfo *dmxScreen = dmxConfigAddDisplay(pt->name,
                                                         0, 0, 0, 0, 0, 0,
                                                         0, 0, 0, 0, 0, 0);
          if (dmxNumScreens == 1) {
              dmxScreen->where  = PosAbsolute;
              dmxScreen->whereX = 0;
              dmxScreen->whereY = 0;
              dmxLog(dmxInfo, "Added %s at %d %d\n",
                     dmxScreen->name, dmxScreen->whereX, dmxScreen->whereY);
          } else {
              dmxScreen->where          = PosRightOf;
              dmxScreen->whereRefScreen = dmxNumScreens - 2;
              if (dmxScreen->whereRefScreen < 0) dmxScreen->whereRefScreen = 0;
              dmxLog(dmxInfo, "Added %s %s %s\n",
                     dmxScreen->name,
                     dmxScreen->where == PosBelow ? "below" : "right of",
                     dmxScreens[dmxScreen->whereRefScreen].name);
          }
      }
  }
  
  static void dmxConfigFromConfigFile(void)
  {
      DMXConfigEntryPtr pt;
      const char        *name;
  
      for (pt = dmxConfigEntry; pt; pt = pt->next) {
                                  /* FIXME -- if an input is specified, use it */
          if (pt->type != dmxConfigVirtual) continue;
          if ((name = dmxConfigMatch(dmxConfigCmd.config, pt))) {
              dmxLog(dmxInfo, "Using configuration \"%s\"\n", name);
              dmxConfigCopyData(pt->virtual);
              return;
          }
      }
      dmxLog(dmxFatal, "Could not find configuration \"%s\" in \"%s\"\n",
             dmxConfigCmd.config, dmxConfigCmd.filename);
  }
  
  static void dmxConfigConfigInputs(void)
  {
      DMXConfigListPtr pt;
  
      if (dmxNumInputs) return;
      
      if (dmxConfigCmd.inputs) {   /* Use command line */
          for (pt = dmxConfigCmd.inputs; pt; pt = pt->next)
              dmxConfigAddInput(pt->name, TRUE);
      } else if (dmxNumScreens) { /* Use first display */
          dmxConfigAddInput(dmxScreens[0].name, TRUE);
      } else {                     /* Use dummy */
          dmxConfigAddInput("dummy", TRUE);
      }
  
      if (dmxConfigCmd.xinputs) {  /* Non-core devices from command line */
          for (pt = dmxConfigCmd.xinputs; pt; pt = pt->next)
              dmxConfigAddInput(pt->name, FALSE);
      }
  }
  
  /** Set up the appropriate global variables so that the DMX server will
   * be initialized using the configuration specified in the config file
   * and on the command line. */
  void dmxConfigConfigure(void)
  {
      if (dmxConfigEntry) {
          dmxConfigFreeEntry(dmxConfigEntry);
          dmxConfigEntry = NULL;
      }
      if (dmxConfigCmd.filename) {
          if (dmxConfigCmd.displays)
              dmxLog(dmxWarning,
                     "Using configuration file \"%s\" instead of command line\n",
                     dmxConfigCmd.filename);
          dmxConfigReadFile(dmxConfigCmd.filename, 0);
          dmxConfigFromConfigFile();
      } else {
          if (dmxConfigCmd.config)
              dmxLog(dmxWarning,
                     "Configuration name (%s) without configuration file\n",
                     dmxConfigCmd.config);
          dmxConfigFromCommandLine();
      }
      dmxConfigConfigInputs();
  }
  
  /** This function determines the number of displays we WILL have and
   * sets MAXSCREENS to that value.  This is difficult since the number
   * depends on the command line (which is easy to count) or on the config
   * file, which has to be parsed. */
  void dmxConfigSetMaxScreens(void)
  {
      static int processing = 0;
  
      if (processing) return;     /* Prevent reentry via ProcessCommandLine */
      processing = 1;
      if (dmxConfigCmd.filename) {
          if (!dmxNumScreens)
              dmxConfigConfigure();
  #ifndef MAXSCREENS
          SetMaxScreens(dmxNumScreens);
  #endif
      } else
  #ifndef MAXSCREENS
          SetMaxScreens(dmxDisplaysFromCommandLine);
  #endif
      processing = 0;
  }
  
  /** This macro is used to generate the following access methods:
   * - dmxConfig{Set,Get}rules
   * - dmxConfig{Set,Get}model
   * - dmxConfig{Set,Get}layout
   * - dmxConfig{Set,Get}variant
   * - dmxConfig{Set,Get}options
   * These methods are used to read and write information about the keyboard. */
  
  #define GEN(param,glob,def)                                                   \
   void dmxConfigSet##glob(const char *param) {                                 \
       if (dmx##glob) free((void *)dmx##glob);                                  \
       dmx##glob = strdup(param);                                               \
   }                                                                            \
   char *dmxConfigGet##glob(void) {                                             \
       return (char *)(dmx##glob ? dmx##glob : def);                            \
   }
  
  GEN(rules,   XkbRules,   DMX_DEFAULT_XKB_RULES)
  GEN(model,   XkbModel,   DMX_DEFAULT_XKB_MODEL)
  GEN(layout,  XkbLayout,  DMX_DEFAULT_XKB_LAYOUT)
  GEN(variant, XkbVariant, DMX_DEFAULT_XKB_VARIANT)
  GEN(options, XkbOptions, DMX_DEFAULT_XKB_OPTIONS)
      
  