1/************************************************************
2 Copyright (c) 1996 by Silicon Graphics Computer Systems, Inc.
3
4 Permission to use, copy, modify, and distribute this
5 software and its documentation for any purpose and without
6 fee is hereby granted, provided that the above copyright
7 notice appear in all copies and that both that copyright
8 notice and this permission notice appear in supporting
9 documentation, and that the name of Silicon Graphics not be
10 used in advertising or publicity pertaining to distribution
11 of the software without specific prior written permission.
12 Silicon Graphics makes no representation about the suitability
13 of this software for any purpose. It is provided "as is"
14 without any express or implied warranty.
15
16 SILICON GRAPHICS DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
17 SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
18 AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL SILICON
19 GRAPHICS BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL
20 DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
21 DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
22 OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION  WITH
23 THE USE OR PERFORMANCE OF THIS SOFTWARE.
24
25 ********************************************************/
26
27#ifdef HAVE_CONFIG_H
28#include "config.h"
29#endif
30#include <stdio.h>
31#include <stdlib.h>
32#include <locale.h>
33#include <limits.h>
34#include <ctype.h>
35#include <X11/Xlib.h>
36#include <X11/Xos.h>
37#include <X11/XKBlib.h>
38#include <X11/extensions/XKBfile.h>
39#include <X11/extensions/XKBconfig.h>
40#include <X11/extensions/XKBrules.h>
41#include <X11/extensions/Xrandr.h>
42
43#ifndef PATH_MAX
44#ifdef MAXPATHLEN
45#define PATH_MAX MAXPATHLEN
46#else
47#define PATH_MAX 1024
48#endif
49#endif
50
51#ifndef DFLT_XKB_CONFIG_ROOT
52#define DFLT_XKB_CONFIG_ROOT "/usr/share/X11/xkb"
53#endif
54#ifndef DFLT_XKB_RULES_FILE
55#define DFLT_XKB_RULES_FILE "base"
56#endif
57#ifndef DFLT_XKB_LAYOUT
58#define DFLT_XKB_LAYOUT "us"
59#endif
60#ifndef DFLT_XKB_MODEL
61#define DFLT_XKB_MODEL "pc105"
62#endif
63
64/* Constants to state how a value was obtained. The order of these
65 * is important, the bigger the higher the priority.
66 * e.g. FROM_CONFIG overrides FROM_SERVER */
67enum source {
68    UNDEFINED = 0,
69    FROM_SERVER,          /* Retrieved from server at runtime. */
70    FROM_RULES,           /* Xkb rules file. */
71    FROM_CONFIG,          /* Command-line specified config file. */
72    FROM_CMD_LINE,        /* Specified at the cmdline. */
73    NUM_SOURCES
74};
75
76/***====================================================================***/
77static Bool print = False;
78static Bool query = False;
79static Bool synch = False;
80static int verbose = 5;
81
82static Display *dpy;
83
84/**
85 * human-readable versions of FROM_CONFIG, FROM_SERVER, etc. Used for error
86 * reporting.
87 */
88static const char *srcName[NUM_SOURCES] = {
89    "undefined", "X server", "rules file", "config file", "command line"
90};
91
92struct setting {
93    char const  *name;  /* Human-readable setting name. Used for error reporting. */
94    char        *value; /* Holds the value. */
95    enum source  src;   /* Holds the source. */
96};
97
98typedef struct setting setting_t;
99
100struct settings {
101    setting_t rules;     /* Rules file */
102    setting_t config;    /* Config file (if used) */
103    setting_t display;   /* X display name */
104    setting_t locale;    /* Machine's locale */
105    setting_t model;
106    setting_t layout;
107    setting_t variant;
108    setting_t keycodes;
109    setting_t types;
110    setting_t compat;
111    setting_t symbols;
112    setting_t geometry;
113    setting_t keymap;
114};
115
116typedef struct settings settings_t;
117
118static settings_t settings = {
119    { "rules file",         NULL, UNDEFINED },
120    { "config file",        NULL, UNDEFINED },
121    { "X display",          NULL, UNDEFINED },
122    { "locale",             NULL, UNDEFINED },
123    { "keyboard model",     NULL, UNDEFINED },
124    { "keyboard layout",    NULL, UNDEFINED },
125    { "layout variant",     NULL, UNDEFINED },
126    { "keycodes",           NULL, UNDEFINED },
127    { "types",              NULL, UNDEFINED },
128    { "compatibility map",  NULL, UNDEFINED },
129    { "symbols",            NULL, UNDEFINED },
130    { "geometry",           NULL, UNDEFINED },
131    { "keymap",             NULL, UNDEFINED }
132};
133
134static XkbConfigRtrnRec cfgResult;
135
136static XkbRF_VarDefsRec rdefs;
137
138static Bool clearOptions = False;
139
140struct list {
141    char  **item;   /* Array of items. */
142    int     sz;     /* Size of array. */
143    int     num;    /* Number of used elements. */
144};
145
146typedef struct list list_t;
147
148static list_t options = { NULL, 0, 0 };
149
150static list_t inclPath = { NULL, 0, 0 };
151
152static XkbDescPtr xkb = NULL;
153
154static int deviceSpec = XkbUseCoreKbd;
155
156/***====================================================================***/
157
158#define streq(s1,s2)    (strcmp(s1,s2)==0)
159#define strpfx(s1,s2)   (strncmp(s1,s2,strlen(s2))==0)
160
161#define MSG(s)          printf(s)
162#define MSG1(s,a)       printf(s,a)
163#define MSG2(s,a,b)     printf(s,a,b)
164#define MSG3(s,a,b,c)   printf(s,a,b,c)
165
166#define VMSG(l,s)        if (verbose>(l)) printf(s)
167#define VMSG1(l,s,a)     if (verbose>(l)) printf(s,a)
168#define VMSG2(l,s,a,b)   if (verbose>(l)) printf(s,a,b)
169#define VMSG3(l,s,a,b,c) if (verbose>(l)) printf(s,a,b,c)
170
171#define ERR(s)          fprintf(stderr,s)
172#define ERR1(s,a)       fprintf(stderr,s,a)
173#define ERR2(s,a,b)     fprintf(stderr,s,a,b)
174#define ERR3(s,a,b,c)   fprintf(stderr,s,a,b,c)
175
176#define OOM(ptr)        do { if ((ptr) == NULL) { ERR("Out of memory.\n"); exit(1); } } while (0)
177
178/***====================================================================***/
179
180static Bool addToList(list_t *list, const char *newVal);
181static void usage(int argc, char **argv);
182static void dumpNames(Bool wantRules, Bool wantCNames);
183static void trySetString(setting_t * setting, char *newVal, enum source src);
184static Bool setOptString(int *arg, int argc, char **argv, setting_t *setting, enum source src);
185static int parseArgs(int argc, char **argv);
186static Bool getDisplay(int argc, char **argv);
187static Bool getServerValues(void);
188static FILE *findFileInPath(char *name);
189static Bool addStringToOptions(char *opt_str, list_t *opts);
190static char *stringFromOptions(char *orig, list_t *newOpts);
191static Bool applyConfig(char *name);
192static XkbRF_RulesPtr tryLoadRules(char *name, char *locale, Bool wantDesc, Bool wantRules);
193static Bool applyRules(void);
194static Bool applyComponentNames(void);
195static void printKeymap(void);
196
197/***====================================================================***/
198
199/*
200    If newVal is NULL or empty string, the list is cleared.
201    Otherwise newVal is added to the end of the list (if it is not present in the list yet).
202*/
203
204static Bool
205addToList(list_t *list, const char *newVal)
206{
207    register int i;
208
209    if ((!newVal) || (!newVal[0]))
210    {
211        list->num = 0;
212        return True;
213    }
214    for (i = 0; i < list->num; i++)
215    {
216        if (streq(list->item[i], newVal))
217            return True;
218    }
219    if ((list->item == NULL) || (list->sz < 1))
220    {
221        list->num = 0;
222        list->sz = 4;
223        list->item = (char **) calloc(list->sz, sizeof(char *));
224        OOM(list->item);
225    }
226    else if (list->num >= list->sz)
227    {
228        list->sz *= 2;
229        list->item = (char **) realloc(list->item, (list->sz) * sizeof(char *));
230        OOM(list->item);
231    }
232    list->item[list->num] = strdup(newVal);
233    OOM(list->item[list->num]);
234    list->num += 1;
235    return True;
236}
237
238/***====================================================================***/
239
240static void
241usage(int argc, char **argv)
242{
243    MSG1(
244        "Usage: %s [options] [<layout> [<variant> [<option> ... ]]]\n"
245        "Options:\n"
246        "  -?, -help           Print this message\n"
247        "  -compat <name>      Specifies compatibility map component name\n"
248        "  -config <file>      Specifies configuration file to use\n"
249        "  -device <deviceid>  Specifies the device ID to use\n"
250        "  -display <dpy>      Specifies display to use\n"
251        "  -geometry <name>    Specifies geometry component name\n"
252        "  -I <dir>            Add <dir> to list of directories to be used\n"
253        "  -keycodes <name>    Specifies keycodes component name\n"
254        "  -keymap <name>      Specifies name of keymap to load\n"
255        "  -layout <name>      Specifies layout used to choose component names\n"
256        "  -model <name>       Specifies model used to choose component names\n"
257        "  -option <name>      Adds an option used to choose component names\n"
258        "  -print              Print a complete xkb_keymap description and exit\n"
259        "  -query              Print the current layout settings and exit\n"
260        "  -rules <name>       Name of rules file to use\n"
261        "  -symbols <name>     Specifies symbols component name\n"
262        "  -synch              Synchronize request with X server\n"
263        "  -types <name>       Specifies types component name\n"
264        "  -v[erbose] [<lvl>]  Sets verbosity (1..10); higher values yield more messages\n"
265        "  -version            Print the program's version number\n"
266        "  -variant <name>     Specifies layout variant used to choose component names\n",
267        argv[0]
268    );
269}
270
271static void
272dumpNames(Bool wantRules, Bool wantCNames)
273{
274    if (wantRules)
275    {
276        if (settings.rules.value)
277            MSG1("rules:      %s\n", settings.rules.value);
278        if (settings.model.value)
279            MSG1("model:      %s\n", settings.model.value);
280        if (settings.layout.value)
281            MSG1("layout:     %s\n", settings.layout.value);
282        if (settings.variant.value)
283            MSG1("variant:    %s\n", settings.variant.value);
284        if (options.item)
285        {
286            char *opt_str = stringFromOptions(NULL, &options);
287            MSG1("options:    %s\n", opt_str);
288            free(opt_str);
289        }
290    }
291    if (wantCNames)
292    {
293        if (settings.keymap.value)
294            MSG1("keymap:     %s\n", settings.keymap.value);
295        if (settings.keycodes.value)
296            MSG1("keycodes:   %s\n", settings.keycodes.value);
297        if (settings.types.value)
298            MSG1("types:      %s\n", settings.types.value);
299        if (settings.compat.value)
300            MSG1("compat:     %s\n", settings.compat.value);
301        if (settings.symbols.value)
302            MSG1("symbols:    %s\n", settings.symbols.value);
303        if (settings.geometry.value)
304            MSG1("geometry:   %s\n", settings.geometry.value);
305    }
306    return;
307}
308
309/***====================================================================***/
310
311/**
312 * Set the given string (obtained from src) in the svValue/svSrc globals.
313 * If the given item is already set, it is overridden if the original source
314 * is less significant than the given one.
315 *
316 * @param which What value is it (one of RULES_NDX, CONFIG_NDX, ...)
317 */
318static void
319trySetString(setting_t *setting, char *newVal, enum source src)
320{
321    if (setting->value != NULL)
322    {
323        if (setting->src == src)
324        {
325            VMSG2(0, "Warning! More than one %s from %s\n",
326                  setting->name, srcName[src]);
327            VMSG2(0, "         Using \"%s\", ignoring \"%s\"\n",
328                  setting->value, newVal);
329            return;
330        }
331        else if (setting->src > src)
332        {
333            VMSG1(5, "Warning! Multiple definitions of %s\n", setting->name);
334            VMSG2(5, "         Using %s, ignoring %s\n",
335                  srcName[setting->src], srcName[src]);
336            return;
337        }
338    }
339    setting->src = src;
340    setting->value = newVal;
341    return;
342}
343
344static Bool
345setOptString(int *arg, int argc, char **argv, setting_t *setting, enum source src)
346{
347    int ndx;
348    char *opt;
349
350    ndx = *arg;
351    opt = argv[ndx];
352    if (ndx >= argc - 1)
353    {
354        VMSG1(0, "No %s specified on the command line\n", setting->name);
355        VMSG1(0, "Trailing %s option ignored\n", opt);
356        return True;
357    }
358    ndx++;
359    *arg = ndx;
360    if (setting->value != NULL)
361    {
362        if (setting->src == src)
363        {
364            VMSG2(0, "More than one %s on %s\n", setting->name, srcName[src]);
365            VMSG2(0, "Using \"%s\", ignoring \"%s\"\n", setting->value,
366                  argv[ndx]);
367            return True;
368        }
369        else if (setting->src > src)
370        {
371            VMSG1(5, "Multiple definitions of %s\n", setting->name);
372            VMSG2(5, "Using %s, ignoring %s\n", srcName[setting->src],
373                  srcName[src]);
374            return True;
375        }
376    }
377    setting->src = src;
378    setting->value = argv[ndx];
379    return True;
380}
381
382/***====================================================================***/
383
384/**
385 * Parse commandline arguments.
386 * Return True on success or False if an unrecognized option has been
387 * specified.
388 */
389static int
390parseArgs(int argc, char **argv)
391{
392    int i;
393    Bool ok;
394    unsigned present;
395
396    ok = True;
397    addToList(&inclPath, ".");
398    addToList(&inclPath, DFLT_XKB_CONFIG_ROOT);
399    for (i = 1; (i < argc) && ok; i++)
400    {
401        if (argv[i][0] != '-')
402        {
403            /* Allow a call like "setxkbmap us" to work. Layout is default,
404               if -layout is given, then try parsing variant, then options */
405            if (!settings.layout.src)
406                trySetString(&settings.layout, argv[i], FROM_CMD_LINE);
407            else if (!settings.variant.src)
408                trySetString(&settings.variant, argv[i], FROM_CMD_LINE);
409            else
410                ok = addToList(&options, argv[i]);
411        }
412        else if (streq(argv[i], "-compat"))
413            ok = setOptString(&i, argc, argv, &settings.compat, FROM_CMD_LINE);
414        else if (streq(argv[i], "-config"))
415            ok = setOptString(&i, argc, argv, &settings.config, FROM_CMD_LINE);
416        else if (streq(argv[i], "-device"))
417        {
418            if ( ++i < argc ) {
419                deviceSpec = atoi(argv[i]); /* only allow device IDs, not names */
420            } else {
421                usage(argc, argv);
422                exit(1);
423            }
424        }
425        else if (streq(argv[i], "-display"))
426            ok = setOptString(&i, argc, argv, &settings.display, FROM_CMD_LINE);
427        else if (streq(argv[i], "-geometry"))
428            ok = setOptString(&i, argc, argv, &settings.geometry, FROM_CMD_LINE);
429        else if (streq(argv[i], "-help") || streq(argv[i], "-?"))
430        {
431            usage(argc, argv);
432            exit(0);
433        }
434        else if (streq(argv[i], "-I")) /* space between -I and path */
435        {
436            if ( ++i < argc )
437                ok = addToList(&inclPath, argv[i]);
438            else
439                VMSG(0, "No directory specified on the command line\n"
440                     "Trailing -I option ignored\n");
441        }
442        else if (strpfx(argv[i], "-I")) /* no space between -I and path */
443            ok = addToList(&inclPath, &argv[i][2]);
444        else if (streq(argv[i], "-keycodes"))
445            ok = setOptString(&i, argc, argv, &settings.keycodes, FROM_CMD_LINE);
446        else if (streq(argv[i], "-keymap"))
447            ok = setOptString(&i, argc, argv, &settings.keymap, FROM_CMD_LINE);
448        else if (streq(argv[i], "-layout"))
449            ok = setOptString(&i, argc, argv, &settings.layout, FROM_CMD_LINE);
450        else if (streq(argv[i], "-model"))
451            ok = setOptString(&i, argc, argv, &settings.model, FROM_CMD_LINE);
452        else if (streq(argv[i], "-option"))
453        {
454            if ((i == argc - 1) || (argv[i + 1][0] == '\0')
455                || (argv[i + 1][0] == '-'))
456            {
457                clearOptions = True;
458                ok = addToList(&options, "");
459                if (i < argc - 1 && argv[i + 1][0] == '\0')
460                    i++;
461            }
462            else
463            {
464                ok = addToList(&options, argv[++i]);
465            }
466        }
467        else if (streq(argv[i], "-print"))
468            print = True;
469        else if (streq(argv[i], "-query"))
470            query = True;
471        else if (streq(argv[i], "-rules"))
472            ok = setOptString(&i, argc, argv, &settings.rules, FROM_CMD_LINE);
473        else if (streq(argv[i], "-symbols"))
474            ok = setOptString(&i, argc, argv, &settings.symbols, FROM_CMD_LINE);
475        else if (streq(argv[i], "-synch"))
476            synch = True;
477        else if (streq(argv[i], "-types"))
478            ok = setOptString(&i, argc, argv, &settings.types, FROM_CMD_LINE);
479        else if (streq(argv[i], "-version"))
480        {
481            MSG1("setxkbmap %s\n", PACKAGE_VERSION);
482            exit(0);
483        }
484        else if (streq(argv[i], "-verbose") || (streq(argv[i], "-v")))
485        {
486            if ((i < argc - 1) && (isdigit(argv[i + 1][0])))
487                verbose = atoi(argv[++i]);
488            else
489                verbose++;
490            if (verbose < 0)
491            {
492                ERR1("Illegal verbose level %d.  Reset to 0\n", verbose);
493                verbose = 0;
494            }
495            else if (verbose > 10)
496            {
497                ERR1("Illegal verbose level %d.  Reset to 10\n", verbose);
498                verbose = 10;
499            }
500            VMSG1(7, "Setting verbose level to %d\n", verbose);
501        }
502        else if (streq(argv[i], "-variant"))
503            ok = setOptString(&i, argc, argv, &settings.variant, FROM_CMD_LINE);
504        else
505        {
506            ERR1("Error!   Option \"%s\" not recognized\n", argv[i]);
507            ok = False;
508        }
509    }
510
511    present = 0;
512    if (settings.types.value)
513        present++;
514    if (settings.compat.value)
515        present++;
516    if (settings.symbols.value)
517        present++;
518    if (settings.keycodes.value)
519        present++;
520    if (settings.geometry.value)
521        present++;
522    if (settings.config.value)
523        present++;
524    if (settings.model.value)
525        present++;
526    if (settings.layout.value)
527        present++;
528    if (settings.variant.value)
529        present++;
530    if (settings.keymap.value && present)
531    {
532        ERR("No other components can be specified when a keymap is present\n");
533        return False;
534    }
535    return ok;
536}
537
538/**
539 * Open a connection to the display and print error if it fails.
540 *
541 * @return True on success or False otherwise.
542 */
543static Bool
544getDisplay(int argc, char **argv)
545{
546    int major, minor, why;
547
548    major = XkbMajorVersion;
549    minor = XkbMinorVersion;
550    dpy =
551        XkbOpenDisplay(settings.display.value, NULL, NULL, &major, &minor,
552                       &why);
553    if (!dpy)
554    {
555        if (settings.display.value == NULL)
556            settings.display.value = getenv("DISPLAY");
557        if (settings.display.value == NULL)
558            settings.display.value = (char *) "default display";
559        switch (why)
560        {
561        case XkbOD_BadLibraryVersion:
562            ERR3("%s was compiled with XKB version %d.%02d\n", argv[0],
563                 XkbMajorVersion, XkbMinorVersion);
564            ERR2("Xlib supports incompatible version %d.%02d\n",
565                 major, minor);
566            break;
567        case XkbOD_ConnectionRefused:
568            ERR1("Cannot open display \"%s\"\n", settings.display.value);
569            break;
570        case XkbOD_NonXkbServer:
571            ERR1("XKB extension not present on %s\n", settings.display.value);
572            break;
573        case XkbOD_BadServerVersion:
574            ERR3("%s was compiled with XKB version %d.%02d\n", argv[0],
575                 XkbMajorVersion, XkbMinorVersion);
576            ERR3("Server %s uses incompatible version %d.%02d\n",
577                 settings.display.value, major, minor);
578            break;
579        default:
580            ERR1("Unknown error %d from XkbOpenDisplay\n", why);
581            break;
582        }
583        return False;
584    }
585    if (synch)
586        XSynchronize(dpy, True);
587    return True;
588}
589
590/***====================================================================***/
591
592/**
593 * Retrieve xkb values from the XKB_RULES_NAMES property and store their
594 * contents in svValues.
595 * If the property cannot be read, the built-in defaults are used.
596 *
597 * @return True.
598 */
599static Bool
600getServerValues(void)
601{
602    XkbRF_VarDefsRec vd;
603    char *tmp = NULL;
604
605    if (!XkbRF_GetNamesProp(dpy, &tmp, &vd) || !tmp)
606    {
607        VMSG1(3, "Couldn't interpret %s property\n", _XKB_RF_NAMES_PROP_ATOM);
608        tmp = (char *) DFLT_XKB_RULES_FILE;
609        vd.model = (char *) DFLT_XKB_MODEL;
610        vd.layout = (char *) DFLT_XKB_LAYOUT;
611        vd.variant = NULL;
612        vd.options = NULL;
613        VMSG3(3, "Use defaults: rules - '%s' model - '%s' layout - '%s'\n",
614              tmp, vd.model, vd.layout);
615    }
616    if (tmp)
617        trySetString(&settings.rules, tmp, FROM_SERVER);
618    if (vd.model)
619        trySetString(&settings.model, vd.model, FROM_SERVER);
620    if (vd.layout)
621        trySetString(&settings.layout, vd.layout, FROM_SERVER);
622    if (vd.variant)
623        trySetString(&settings.variant, vd.variant, FROM_SERVER);
624    if ((vd.options) && (!clearOptions))
625    {
626        addStringToOptions(vd.options, &options);
627        XFree(vd.options);
628    }
629    return True;
630}
631
632/***====================================================================***/
633
634static FILE *
635findFileInPath(char *name)
636{
637    register int i;
638    char buf[PATH_MAX];
639    FILE *fp;
640
641    if (name[0] == '/')
642    {
643        fp = fopen(name, "r");
644        if ((verbose > 7) || ((!fp) && (verbose > 0)))
645            MSG2("%s file %s\n", (fp ? "Found" : "Didn't find"), name);
646        return fp;
647    }
648    for (i = 0; (i < inclPath.num); i++)
649    {
650        if (snprintf(buf, PATH_MAX, "%s/%s", inclPath.item[i], name) >=
651            PATH_MAX)
652        {
653            VMSG2(0, "Path too long (%s/%s). Ignored.\n", inclPath.item[i],
654                  name);
655            continue;
656        }
657        fp = fopen(buf, "r");
658        if ((verbose > 7) || ((!fp) && (verbose > 5)))
659            MSG2("%s file %s\n", (fp ? "Found" : "Didn't find"), buf);
660        if (fp != NULL)
661            return fp;
662    }
663    return NULL;
664}
665
666/***====================================================================***/
667
668static Bool
669addStringToOptions(char *opt_str, list_t *opts)
670{
671    char *tmp, *str, *next;
672    Bool ok = True;
673
674    str = strdup(opt_str);
675    OOM(str);
676    for (tmp = str; (tmp && *tmp != '\0') && ok; tmp = next)
677    {
678        next = strchr(str, ',');
679        if (next)
680        {
681            *next = '\0';
682            next++;
683        }
684        ok = addToList(opts, tmp) && ok;
685    }
686    free(str);
687    return ok;
688}
689
690/***====================================================================***/
691
692static char *
693stringFromOptions(char *orig, list_t *newOpts)
694{
695    size_t len;
696    int i, nOut;
697
698    if (orig)
699        len = strlen(orig) + 1;
700    else
701        len = 0;
702    for (i = 0; i < newOpts->num; i++)
703    {
704        if (newOpts->item[i])
705            len += strlen(newOpts->item[i]) + 1;
706    }
707    if (len < 1)
708        return NULL;
709    if (orig)
710    {
711        orig = (char *) realloc(orig, len);
712        OOM(orig);
713        nOut = 1;
714    }
715    else
716    {
717        orig = (char *) calloc(len, 1);
718        OOM(orig);
719        nOut = 0;
720    }
721    for (i = 0; i < newOpts->num; i++)
722    {
723        if (!newOpts->item[i])
724            continue;
725        if (nOut > 0)
726        {
727            strcat(orig, ",");
728            strcat(orig, newOpts->item[i]);
729        }
730        else
731            strcpy(orig, newOpts->item[i]);
732        nOut++;
733    }
734    return orig;
735}
736
737/***====================================================================***/
738
739static Bool
740applyConfig(char *name)
741{
742    FILE *fp;
743    Bool ok;
744
745    if ((fp = findFileInPath(name)) == NULL)
746    {
747        ERR1("Couldn't find configuration file \"%s\"\n", name);
748        return False;
749    }
750    ok = XkbCFParse(fp, XkbCFDflts, NULL, &cfgResult);
751    fclose(fp);
752    if (!ok)
753    {
754        ERR1("Couldn't parse configuration file \"%s\"\n", name);
755        return False;
756    }
757    if (cfgResult.rules_file)
758    {
759        trySetString(&settings.rules, cfgResult.rules_file, FROM_CONFIG);
760        cfgResult.rules_file = NULL;
761    }
762    if (cfgResult.model)
763    {
764        trySetString(&settings.model, cfgResult.model, FROM_CONFIG);
765        cfgResult.model = NULL;
766    }
767    if (cfgResult.layout)
768    {
769        trySetString(&settings.layout, cfgResult.layout, FROM_CONFIG);
770        cfgResult.layout = NULL;
771    }
772    if (cfgResult.variant)
773    {
774        trySetString(&settings.variant, cfgResult.variant, FROM_CONFIG);
775        cfgResult.variant = NULL;
776    }
777    if (cfgResult.options)
778    {
779        addStringToOptions(cfgResult.options, &options);
780        cfgResult.options = NULL;
781    }
782    if (cfgResult.keymap)
783    {
784        trySetString(&settings.keymap, cfgResult.keymap, FROM_CONFIG);
785        cfgResult.keymap = NULL;
786    }
787    if (cfgResult.keycodes)
788    {
789        trySetString(&settings.keycodes, cfgResult.keycodes, FROM_CONFIG);
790        cfgResult.keycodes = NULL;
791    }
792    if (cfgResult.geometry)
793    {
794        trySetString(&settings.geometry, cfgResult.geometry, FROM_CONFIG);
795        cfgResult.geometry = NULL;
796    }
797    if (cfgResult.symbols)
798    {
799        trySetString(&settings.symbols, cfgResult.symbols, FROM_CONFIG);
800        cfgResult.symbols = NULL;
801    }
802    if (cfgResult.types)
803    {
804        trySetString(&settings.types, cfgResult.types, FROM_CONFIG);
805        cfgResult.types = NULL;
806    }
807    if (cfgResult.compat)
808    {
809        trySetString(&settings.compat, cfgResult.compat, FROM_CONFIG);
810        cfgResult.compat = NULL;
811    }
812    if (verbose > 5)
813    {
814        MSG("After config file:\n");
815        dumpNames(True, True);
816    }
817    return True;
818}
819
820static XkbRF_RulesPtr
821tryLoadRules(char *name, char *locale, Bool wantDesc, Bool wantRules)
822{
823    XkbRF_RulesPtr rules = NULL;
824    VMSG1(7, "Trying to load rules file %s...\n", name);
825    rules = XkbRF_Load(name, locale, wantDesc, wantRules);
826    if (rules)
827    {
828        VMSG(7, "Success.\n");
829    }
830    return rules;
831}
832
833/**
834 * If any of model, layout, variant or options is specified, then compile the
835 * options into the
836 *
837 * @return True on success or false otherwise.
838 */
839static Bool
840applyRules(void)
841{
842    int i;
843    char *rfName;
844    XkbRF_RulesPtr rules = NULL;
845
846    if (settings.model.src || settings.layout.src || settings.variant.src
847        || options.item)
848    {
849        char buf[PATH_MAX];
850        XkbComponentNamesRec rnames;
851
852        if (settings.variant.src < settings.layout.src)
853            settings.variant.value = NULL;
854
855        rdefs.model = settings.model.value;
856        rdefs.layout = settings.layout.value;
857        rdefs.variant = settings.variant.value;
858        if (options.item)
859            rdefs.options =
860                stringFromOptions(rdefs.options, &options);
861
862        if (settings.rules.src)
863            rfName = settings.rules.value;
864        else
865            rfName = (char *) DFLT_XKB_RULES_FILE;
866
867        if (rfName[0] == '/')
868        {
869            rules = tryLoadRules(rfName, settings.locale.value, True, True);
870        }
871        else
872        {
873            /* try to load rules files from all include paths until the first
874             * we succeed with */
875            for (i = 0; (i < inclPath.num) && (!rules); i++)
876            {
877                if (snprintf(buf, PATH_MAX, "%s/rules/%s",
878                             inclPath.item[i], rfName) >= PATH_MAX)
879                {
880                    VMSG2(0, "Path too long (%s/rules/%s). Ignored.\n",
881                          inclPath.item[i], rfName);
882                    continue;
883                }
884                rules = tryLoadRules(buf, settings.locale.value, True, True);
885            }
886        }
887        if (!rules)
888        {
889            ERR1("Couldn't find rules file (%s) \n", rfName);
890            return False;
891        }
892        /* Let the rules file to the magic, then update the svValues with
893         * those returned after processing the rules */
894        XkbRF_GetComponents(rules, &rdefs, &rnames);
895        if (rnames.keycodes)
896        {
897            trySetString(&settings.keycodes, rnames.keycodes, FROM_RULES);
898            rnames.keycodes = NULL;
899        }
900        if (rnames.symbols)
901        {
902            trySetString(&settings.symbols, rnames.symbols, FROM_RULES);
903            rnames.symbols = NULL;
904        }
905        if (rnames.types)
906        {
907            trySetString(&settings.types, rnames.types, FROM_RULES);
908            rnames.types = NULL;
909        }
910        if (rnames.compat)
911        {
912            trySetString(&settings.compat, rnames.compat, FROM_RULES);
913            rnames.compat = NULL;
914        }
915        if (rnames.geometry)
916        {
917            trySetString(&settings.geometry, rnames.geometry, FROM_RULES);
918            rnames.geometry = NULL;
919        }
920        if (rnames.keymap)
921        {
922            trySetString(&settings.keymap, rnames.keymap, FROM_RULES);
923            rnames.keymap = NULL;
924        }
925        if (verbose > 6)
926        {
927            MSG1("Applied rules from %s:\n", rfName);
928            dumpNames(True, False);
929        }
930    }
931    else if (verbose > 6)
932    {
933        MSG("No rules variables specified.  Rules file ignored\n");
934    }
935    return True;
936}
937
938/* Primitive sanity check - filter out 'map names' (inside parenthesis) */
939/* that can confuse xkbcomp parser */
940static Bool
941checkName(char *name, const char *string)
942{
943    char *i = name, *opar = NULL;
944    Bool ret = True;
945
946    if (!name)
947        return True;
948
949    while (*i)
950    {
951        if (opar == NULL)
952        {
953            if (*i == '(')
954                opar = i;
955        }
956        else
957        {
958            if ((*i == '(') || (*i == '|') || (*i == '+'))
959            {
960                ret = False;
961                break;
962            }
963            if (*i == ')')
964                opar = NULL;
965        }
966        i++;
967    }
968    if (opar)
969        ret = False;
970    if (!ret)
971    {
972        char c;
973        int n = 1;
974        for (i = opar + 1; *i && n; i++)
975        {
976            if (*i == '(')
977                n++;
978            if (*i == ')')
979                n--;
980        }
981        if (*i)
982            i++;
983        c = *i;
984        *i = '\0';
985        ERR1("Illegal map name '%s' ", opar);
986        *i = c;
987        ERR2("in %s name '%s'\n", string, name);
988    }
989    return ret;
990}
991
992static void
993printKeymap(void)
994{
995    MSG("xkb_keymap {\n");
996    if (settings.keycodes.value)
997        MSG1("\txkb_keycodes  { include \"%s\"\t};\n", settings.keycodes.value);
998    if (settings.types.value)
999        MSG1("\txkb_types     { include \"%s\"\t};\n", settings.types.value);
1000    if (settings.compat.value)
1001        MSG1("\txkb_compat    { include \"%s\"\t};\n", settings.compat.value);
1002    if (settings.symbols.value)
1003        MSG1("\txkb_symbols   { include \"%s\"\t};\n", settings.symbols.value);
1004    if (settings.geometry.value)
1005        MSG1("\txkb_geometry  { include \"%s\"\t};\n", settings.geometry.value);
1006    MSG("};\n");
1007}
1008
1009static Bool
1010applyComponentNames(void)
1011{
1012    if (!checkName(settings.types.value, "types"))
1013        return False;
1014    if (!checkName(settings.compat.value, "compat"))
1015        return False;
1016    if (!checkName(settings.symbols.value, "symbols"))
1017        return False;
1018    if (!checkName(settings.keycodes.value, "keycodes"))
1019        return False;
1020    if (!checkName(settings.geometry.value, "geometry"))
1021        return False;
1022    if (!checkName(settings.keymap.value, "keymap"))
1023        return False;
1024
1025    if (verbose > 5)
1026    {
1027        MSG("Trying to build keymap using the following components:\n");
1028        dumpNames(False, True);
1029    }
1030    /* Upload the new description to the server. */
1031    if (dpy && !print && !query)
1032    {
1033        XkbComponentNamesRec cmdNames = {
1034            .keymap = settings.keymap.value,
1035            .keycodes = settings.keycodes.value,
1036            .types = settings.types.value,
1037            .compat = settings.compat.value,
1038            .symbols = settings.symbols.value,
1039            .geometry = settings.geometry.value
1040        };
1041
1042        xkb = XkbGetKeyboardByName(dpy, deviceSpec, &cmdNames,
1043                                   XkbGBN_AllComponentsMask,
1044                                   XkbGBN_AllComponentsMask &
1045                                   (~XkbGBN_GeometryMask), True);
1046        if (!xkb)
1047        {
1048            ERR("Error loading new keyboard description\n");
1049            return False;
1050        }
1051        /* update the XKB root property */
1052        if (settings.rules.value && (rdefs.model || rdefs.layout))
1053        {
1054            if (!XkbRF_SetNamesProp(dpy, settings.rules.value, &rdefs))
1055            {
1056                VMSG(0, "Error updating the XKB names property\n");
1057            }
1058        }
1059    }
1060    if (print)
1061    {
1062        printKeymap();
1063    }
1064    if (query)
1065    {
1066        dumpNames(True, False);
1067    }
1068    return True;
1069}
1070
1071static Bool
1072is_xwayland(void)
1073{
1074    /* Code copied from xisxwayland.c */
1075    Bool rc = False;
1076    XRRScreenResources *resources = NULL;
1077    XRROutputInfo *output = NULL;
1078    int opcode, event_base, error_base, major, minor;
1079
1080    /* With Xwayland 23.1 and above, the definitive way of checking for an
1081     * Xwayland server is to check for the "XWAYLAND" extension.
1082     */
1083    if (XQueryExtension(dpy, "XWAYLAND", &opcode, &event_base, &error_base)) {
1084        rc = True;
1085        goto out;
1086    }
1087
1088    /* For previous versions of Xwayland, there is no definitive way of checking
1089     * for an Xwayland server, but the two working methods are:
1090     * - RandR output names in Xwayland are XWAYLAND0, XWAYLAND1, etc.
1091     * - XI devices are xwayland-pointer:10, xwayland-keyboard:11
1092     * Let's go with the XRandR check here because it's slightly less
1093     * code to write.
1094     */
1095
1096    if (!XRRQueryExtension(dpy, &event_base, &error_base) ||
1097            !XRRQueryVersion(dpy, &major, &minor)) {
1098        /* e.g. Xnest, but definitely not Xwayland */
1099        goto out;
1100    }
1101    if (major < 1 || (major == 1 && minor < 3)) {
1102        goto out;
1103    }
1104
1105    resources = XRRGetScreenResourcesCurrent(dpy, DefaultRootWindow(dpy));
1106    if (!resources || resources->noutput < 1) {
1107        goto out;
1108    }
1109
1110    output = XRRGetOutputInfo(dpy, resources, resources->outputs[0]);
1111    if (!output) {
1112        goto out;
1113    }
1114
1115    if (strncmp(output->name, "XWAYLAND", 8) == 0)
1116        rc = True;
1117
1118    XRRFreeOutputInfo(output);
1119out:
1120    if (resources)
1121        XRRFreeScreenResources(resources);
1122
1123    return rc;
1124}
1125
1126int
1127main(int argc, char **argv)
1128{
1129    if ((!parseArgs(argc, argv)) || (!getDisplay(argc, argv)))
1130        exit(1);
1131
1132    if (is_xwayland())
1133	    ERR("WARNING: Running setxkbmap against an Xwayland server\n");
1134
1135    settings.locale.value = setlocale(LC_ALL, settings.locale.value);
1136    settings.locale.src = FROM_SERVER;
1137    VMSG1(7, "locale is %s\n", settings.locale.value);
1138    if (dpy)
1139        getServerValues();
1140    if (settings.config.value && (!applyConfig(settings.config.value)))
1141        exit(3);
1142    if (!applyRules())
1143        exit(4);
1144    if (!applyComponentNames())
1145        exit(5);
1146    if (dpy)
1147        XCloseDisplay(dpy);
1148    exit(0);
1149}
1150