500 lines
17 KiB
C
500 lines
17 KiB
C
|
/* $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)
|
||
|
|