1 /************************************************************
2 Copyright (c) 1996 by Silicon Graphics Computer Systems, Inc.
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.
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.
25 ********************************************************/
34 input_line_get(FILE *file, darray_char *line)
37 bool end_of_file = false;
42 while (!end_of_file && darray_empty(*line)) {
43 space_pending = slash_pending = in_comment = false;
45 while ((ch = getc(file)) != '\n' && ch != EOF) {
64 slash_pending = false;
75 darray_append(*line, ' ');
76 space_pending = false;
79 darray_append(*line, '/');
80 slash_pending = false;
84 while (isspace(ch) && ch != '\n' && ch != EOF)
90 if (ch != '\n' && !darray_empty(*line))
97 darray_append(*line, ' ');
98 space_pending = false;
102 if (!darray_empty(*line)) {
103 WARN("The '!' is legal only at start of line\n");
104 ACTION("Line containing '!' ignored\n");
105 darray_resize(*line, 0);
110 darray_append(*line, ch);
118 if (darray_empty(*line) && end_of_file)
121 darray_append(*line, '\0');
125 /***====================================================================***/
128 /* "Parts" - the MLVO which rules file maps to components. */
135 ((1 << MODEL) | (1 << LAYOUT) | (1 << VARIANT) | (1 << OPTION))
144 #define COMPONENT_MASK \
145 ((1 << KEYCODES) | (1 << SYMBOLS) | (1 << TYPES) | (1 << COMPAT) | \
151 static const char *cname[] = {
154 [VARIANT] = "variant",
157 [KEYCODES] = "keycodes",
158 [SYMBOLS] = "symbols",
161 [GEOMETRY] = "geometry",
166 const char *layout[XkbNumKbdGroups + 1];
167 const char *variant[XkbNumKbdGroups + 1];
172 /* Sequential id for the mappings. */
194 RULE_FLAG_PENDING_MATCH = (1L << 1),
195 RULE_FLAG_OPTION = (1L << 2),
196 RULE_FLAG_APPEND = (1L << 3),
197 RULE_FLAG_NORMAL = (1L << 4),
220 darray(struct rule) rules;
221 darray(struct group) groups;
224 /***====================================================================***/
227 * Resolve numeric index, such as "[4]" in layout[4]. Missing index
231 get_index(char *str, int *ndx)
233 int empty = 0, consumed = 0, num;
235 sscanf(str, "[%n%d]%n", &empty, &num, &consumed);
239 } else if (empty > 0) {
249 * Match a mapping line which opens a rule, e.g:
250 * ! model layout[4] variant[4] = symbols geometry
251 * Which will be followed by lines such as:
252 * * ben basic = +in(ben):4 nec(pc98)
253 * So if the MLVO matches the LHS of some line, we'll get the components
255 * In this example, we will get for the second and fourth columns:
256 * mapping->map[1] = {.word = LAYOUT, .index = 4}
257 * mapping->map[3] = {.word = SYMBOLS, .index = 0}
260 match_mapping_line(darray_char *line, struct mapping *mapping)
263 char *str = darray_mem(*line, 1);
264 unsigned present = 0, layout_ndx_present = 0, variant_ndx_present = 0;
272 * Remember the last sequential mapping id (incremented if the match
275 tmp = mapping->number;
276 memset(mapping, 0, sizeof(*mapping));
277 mapping->number = tmp;
279 while ((tok = strtok_r(str, " ", &strtok_buf)) != NULL) {
283 if (strcmp(tok, "=") == 0)
286 for (i = 0; i < MAX_WORDS; i++) {
287 len = strlen(cname[i]);
289 if (strncmp(cname[i], tok, len) == 0) {
290 if (strlen(tok) > len) {
291 char *end = get_index(tok + len, &ndx);
293 if ((i != LAYOUT && i != VARIANT) ||
294 *end != '\0' || ndx == -1) {
295 WARN("Illegal %s index: %d\n", cname[i], ndx);
296 WARN("Can only index layout and variant\n");
300 if (ndx < 1 || ndx > XkbNumKbdGroups) {
301 WARN("Illegal %s index: %d\n", cname[i], ndx);
302 WARN("Index must be in range 1..%d\n", XkbNumKbdGroups);
311 if (present & (1 << i)) {
312 if ((i == LAYOUT && layout_ndx_present & (1 << ndx)) ||
313 (i == VARIANT && variant_ndx_present & (1 << ndx))) {
314 WARN("Component \"%s\" listed twice\n", tok);
315 ACTION("Second definition ignored\n");
322 layout_ndx_present |= 1 << ndx;
324 variant_ndx_present |= 1 << ndx;
326 mapping->map[mapping->num_maps].word = i;
327 mapping->map[mapping->num_maps].index = ndx;
334 WARN("Unknown component \"%s\"\n", tok);
339 if ((present & PART_MASK) == 0) {
340 WARN("Mapping needs at least one MLVO part\n");
341 ACTION("Illegal mapping ignored\n");
342 mapping->num_maps = 0;
346 if ((present & COMPONENT_MASK) == 0) {
347 WARN("Mapping needs at least one component\n");
348 ACTION("Illegal mapping ignored\n");
349 mapping->num_maps = 0;
357 * Match a line such as:
358 * ! $pcmodels = pc101 pc102 pc104 pc105
361 match_group_line(darray_char *line, struct group *group)
364 char *name = strchr(darray_mem(*line, 0), '$');
365 char *words = strchr(name, ' ');
372 for (; *words; words++) {
373 if (*words != '=' && *words != ' ')
380 group->name = strdup(name);
381 group->words = strdup(words);
383 words = group->words;
384 for (i = 1; *words; words++) {
396 /* Match lines following a mapping (see match_mapping_line comment). */
398 match_rule_line(darray_char *line, struct mapping *mapping,
405 const char *names[MAX_WORDS] = { NULL };
407 if (mapping->num_maps == 0) {
408 WARN("Must have a mapping before first line of data\n");
409 ACTION("Illegal line of data ignored\n");
413 str = darray_mem(*line, 0);
415 for (nread = 0; (tok = strtok_r(str, " ", &strtok_buf)) != NULL; nread++) {
418 if (strcmp(tok, "=") == 0) {
423 if (nread > mapping->num_maps) {
424 WARN("Too many words on a line\n");
425 ACTION("Extra word \"%s\" ignored\n", tok);
429 names[mapping->map[nread].word] = tok;
430 if (*tok == '+' || *tok == '|')
434 if (nread < mapping->num_maps) {
435 WARN("Too few words on a line: %s\n", darray_mem(*line, 0));
436 ACTION("line ignored\n");
441 rule->number = mapping->number;
444 rule->flags |= RULE_FLAG_OPTION;
446 rule->flags |= RULE_FLAG_APPEND;
448 rule->flags |= RULE_FLAG_NORMAL;
450 rule->model = uDupString(names[MODEL]);
451 rule->layout = uDupString(names[LAYOUT]);
452 rule->variant = uDupString(names[VARIANT]);
453 rule->option = uDupString(names[OPTION]);
455 rule->keycodes = uDupString(names[KEYCODES]);
456 rule->symbols = uDupString(names[SYMBOLS]);
457 rule->types = uDupString(names[TYPES]);
458 rule->compat = uDupString(names[COMPAT]);
460 rule->layout_num = rule->variant_num = 0;
461 for (i = 0; i < nread; i++) {
462 if (mapping->map[i].index) {
463 if (mapping->map[i].word == LAYOUT)
464 rule->layout_num = mapping->map[i].index;
465 if (mapping->map[i].word == VARIANT)
466 rule->variant_num = mapping->map[i].index;
474 match_line(darray_char *line, struct mapping *mapping,
475 struct rule *rule, struct group *group)
477 if (darray_item(*line, 0) != '!')
478 return match_rule_line(line, mapping, rule);
480 if (darray_item(*line, 1) == '$' ||
481 (darray_item(*line, 1) == ' ' && darray_item(*line, 2) == '$'))
482 return match_group_line(line, group);
484 match_mapping_line(line, mapping);
489 squeeze_spaces(char *p1)
493 for (p2 = p1; *p2; p2++) {
503 * Expand the layout and variant of the rule_names and remove extraneous
504 * spaces. If there's one layout/variant, it is kept in
505 * .layout[0]/.variant[0], else is kept in [1], [2] and so on, and [0]
506 * remains empty. For example, this rule_names:
508 * .layout = "us,il,ru,ca"
509 * .variant = ",,,multix"
510 * .options = "grp:alts_toggle, ctrl:nocaps, compose:rwin"
511 * Is expanded into this multi_defs:
513 * .layout = {NULL, "us", "il", "ru", "ca"},
514 * .variant = {NULL, "", "", "", "multix"},
515 * .options = "grp:alts_toggle,ctrl:nocaps,compose:rwin"
518 make_multi_defs(struct multi_defs *mdefs, const struct xkb_rule_names *mlvo)
523 memset(mdefs, 0, sizeof(*mdefs));
526 mdefs->model = mlvo->model;
530 mdefs->options = strdup(mlvo->options);
531 if (mdefs->options == NULL)
534 squeeze_spaces(mdefs->options);
538 if (!strchr(mlvo->layout, ',')) {
539 mdefs->layout[0] = mlvo->layout;
542 p = strdup(mlvo->layout);
547 mdefs->layout[1] = p;
549 for (i = 2; i <= XkbNumKbdGroups; i++) {
550 if ((p = strchr(p, ','))) {
552 mdefs->layout[i] = p;
559 if (p && (p = strchr(p, ',')))
565 if (!strchr(mlvo->variant, ',')) {
566 mdefs->variant[0] = mlvo->variant;
569 p = strdup(mlvo->variant);
574 mdefs->variant[1] = p;
576 for (i = 2; i <= XkbNumKbdGroups; i++) {
577 if ((p = strchr(p, ','))) {
579 mdefs->variant[i] = p;
585 if (p && (p = strchr(p, ',')))
594 free_multi_defs(struct multi_defs *defs)
598 * See make_multi_defs comment for the hack; the same strdup'd
599 * string is split among the indexes, but the one in [0] is const.
601 free(UNCONSTIFY(defs->layout[1]));
602 free(UNCONSTIFY(defs->variant[1]));
605 /* See apply_rule below. */
607 apply(const char *src, char **dst)
615 if (*src == '+' || *src == '!') {
617 ret = asprintf(dst, "%s%s", *dst, src);
622 else if (*dst == NULL) {
628 * Add the info from the matching rule to the resulting
629 * xkb_component_names. If we already had a match for something
630 * (e.g. keycodes), and the rule is not an appending one (e.g.
631 * +whatever), than we don't override but drop the new one.
634 apply_rule(struct rule *rule, struct xkb_component_names *kccgst)
636 /* Clear the flag because it's applied. */
637 rule->flags &= ~RULE_FLAG_PENDING_MATCH;
639 apply(rule->keycodes, &kccgst->keycodes);
640 apply(rule->symbols, &kccgst->symbols);
641 apply(rule->types, &kccgst->types);
642 apply(rule->compat, &kccgst->compat);
646 * Match if name is part of the group, e.g. if the following
648 * ! $qwertz = al cz de hr hu ro si sk
650 * match_group_member(rules, "qwertz", "hr")
654 match_group_member(struct rules *rules, const char *group_name,
659 struct group *iter, *group = NULL;
661 darray_foreach(iter, rules->groups) {
662 if (strcmp(iter->name, group_name) == 0) {
672 for (i = 0; i < group->number; i++, word += strlen(word) + 1)
673 if (strcmp(word, name) == 0)
679 /* Match @needle out of @sep-seperated @haystack. */
681 match_one_of(const char *haystack, const char *needle, char sep)
683 const char *s = strstr(haystack, needle);
688 if (s != haystack && *s != sep)
692 if (*s != '\0' && *s != sep)
699 apply_rule_if_matches(struct rules *rules, struct rule *rule,
700 struct multi_defs *mdefs,
701 struct xkb_component_names *kccgst)
703 bool pending = false;
706 if (mdefs->model == NULL)
709 if (strcmp(rule->model, "*") == 0) {
712 else if (rule->model[0] == '$') {
713 if (!match_group_member(rules, rule->model, mdefs->model))
716 else if (strcmp(rule->model, mdefs->model) != 0) {
722 if (mdefs->options == NULL)
725 if (!match_one_of(mdefs->options, rule->option, ','))
730 if (mdefs->layout[rule->layout_num] == NULL)
733 if (strcmp(rule->layout, "*") == 0) {
736 else if (rule->layout[0] == '$') {
737 if (!match_group_member(rules, rule->layout,
738 mdefs->layout[rule->layout_num]))
741 else if (strcmp(rule->layout,
742 mdefs->layout[rule->layout_num]) != 0) {
748 if (mdefs->variant[rule->variant_num] == NULL)
751 if (strcmp(rule->variant, "*") == 0) {
753 } else if (rule->variant[0] == '$') {
754 if (!match_group_member(rules, rule->variant,
755 mdefs->variant[rule->variant_num]))
758 else if (strcmp(rule->variant,
759 mdefs->variant[rule->variant_num]) != 0) {
765 rule->flags |= RULE_FLAG_PENDING_MATCH;
767 /* Exact match, apply it now. */
768 apply_rule(rule, kccgst);
775 clear_partial_matches(struct rules *rules)
779 darray_foreach(rule, rules->rules)
780 rule->flags &= ~RULE_FLAG_PENDING_MATCH;
784 apply_partial_matches(struct rules *rules, struct xkb_component_names *kccgst)
788 darray_foreach(rule, rules->rules)
789 if (rule->flags & RULE_FLAG_PENDING_MATCH)
790 apply_rule(rule, kccgst);
794 apply_matching_rules(struct rules *rules, struct multi_defs *mdefs,
795 struct xkb_component_names *kccgst, unsigned int flags)
800 darray_foreach(rule, rules->rules) {
801 if ((rule->flags & flags) != flags)
804 if ((flags & RULE_FLAG_OPTION) == 0 && rule->number == skip)
807 skip = apply_rule_if_matches(rules, rule, mdefs, kccgst);
811 /***====================================================================***/
814 substitute_vars(char *name, struct multi_defs *mdefs)
816 char *str, *outstr, *var;
818 size_t len, extra_len;
825 str = strchr(name, '%');
831 while (str != NULL) {
835 if (pfx == '+' || pfx == '|' || pfx == '_' || pfx == '-') {
839 else if (pfx == '(') {
845 str = get_index(var + 1, &ndx);
847 str = strchr(str, '%');
851 if (*var == 'l' && mdefs->layout[ndx] && *mdefs->layout[ndx])
852 len += strlen(mdefs->layout[ndx]) + extra_len;
853 else if (*var == 'm' && mdefs->model)
854 len += strlen(mdefs->model) + extra_len;
855 else if (*var == 'v' && mdefs->variant[ndx] && *mdefs->variant[ndx])
856 len += strlen(mdefs->variant[ndx]) + extra_len;
858 if (pfx == '(' && *str == ')')
861 str = strchr(&str[0], '%');
864 name = malloc(len + 1);
868 while (*str != '\0') {
874 if (pfx == '+' || pfx == '|' || pfx == '_' || pfx == '-') {
877 else if (pfx == '(') {
886 str = get_index(var + 1, &ndx);
890 if (*var == 'l' && mdefs->layout[ndx] && *mdefs->layout[ndx]) {
894 strcpy(outstr, mdefs->layout[ndx]);
895 outstr += strlen(mdefs->layout[ndx]);
900 else if (*var == 'm' && mdefs->model) {
904 strcpy(outstr, mdefs->model);
905 outstr += strlen(mdefs->model);
910 else if (*var == 'v' && mdefs->variant[ndx] && *mdefs->variant[ndx]) {
914 strcpy(outstr, mdefs->variant[ndx]);
915 outstr += strlen(mdefs->variant[ndx]);
921 if (pfx == '(' && *str == ')')
937 /***====================================================================***/
940 get_components(struct rules *rules, const struct xkb_rule_names *mlvo,
941 struct xkb_component_names *kccgst)
943 struct multi_defs mdefs;
945 make_multi_defs(&mdefs, mlvo);
947 clear_partial_matches(rules);
949 apply_matching_rules(rules, &mdefs, kccgst, RULE_FLAG_NORMAL);
950 apply_partial_matches(rules, kccgst);
952 apply_matching_rules(rules, &mdefs, kccgst, RULE_FLAG_APPEND);
953 apply_partial_matches(rules, kccgst);
955 apply_matching_rules(rules, &mdefs, kccgst, RULE_FLAG_OPTION);
956 apply_partial_matches(rules, kccgst);
958 kccgst->keycodes = substitute_vars(kccgst->keycodes, &mdefs);
959 kccgst->symbols = substitute_vars(kccgst->symbols, &mdefs);
960 kccgst->types = substitute_vars(kccgst->types, &mdefs);
961 kccgst->compat = substitute_vars(kccgst->compat, &mdefs);
963 free_multi_defs(&mdefs);
966 kccgst->keycodes && kccgst->symbols && kccgst->types && kccgst->compat;
969 static struct rules *
970 load_rules(FILE *file)
973 struct mapping mapping;
978 rules = calloc(1, sizeof(*rules));
981 darray_init(rules->rules);
982 darray_growalloc(rules->rules, 16);
984 memset(&mapping, 0, sizeof(mapping));
985 memset(&tgroup, 0, sizeof(tgroup));
987 darray_growalloc(line, 128);
989 while (input_line_get(file, &line)) {
990 if (match_line(&line, &mapping, &trule, &tgroup)) {
992 darray_append(rules->groups, tgroup);
993 memset(&tgroup, 0, sizeof(tgroup));
995 darray_append(rules->rules, trule);
996 memset(&trule, 0, sizeof(trule));
1000 darray_resize(line, 0);
1008 free_rules(struct rules *rules)
1011 struct group *group;
1016 darray_foreach(rule, rules->rules) {
1019 free(rule->variant);
1021 free(rule->keycodes);
1022 free(rule->symbols);
1026 darray_free(rules->rules);
1028 darray_foreach(group, rules->groups) {
1032 darray_free(rules->groups);
1037 struct xkb_component_names *
1038 xkb_components_from_rules(struct xkb_context *ctx,
1039 const struct xkb_rule_names *rmlvo)
1044 struct rules *rules;
1045 struct xkb_component_names *kccgst = NULL;
1047 file = XkbFindFileInPath(ctx, rmlvo->rules, FILE_TYPE_RULES, &path);
1049 ERROR("could not find \"%s\" rules in XKB path\n", rmlvo->rules);
1050 ERROR("%d include paths searched:\n",
1051 xkb_context_num_include_paths(ctx));
1052 for (i = 0; i < xkb_context_num_include_paths(ctx); i++)
1053 ERROR("\t%s\n", xkb_context_include_path_get(ctx, i));
1057 rules = load_rules(file);
1059 ERROR("failed to load XKB rules \"%s\"\n", path);
1063 kccgst = calloc(1, sizeof(*kccgst));
1065 ERROR("failed to allocate XKB components\n");
1069 if (!get_components(rules, rmlvo, kccgst)) {
1070 free(kccgst->keycodes);
1071 free(kccgst->types);
1072 free(kccgst->compat);
1073 free(kccgst->symbols);
1076 ERROR("no components returned from XKB rules \"%s\"\n", path);