/************************************************************
* Copyright (c) 1996 by Silicon Graphics Computer Systems, Inc.
*
* Permission to use, copy, modify, and distribute this
* software and its documentation for any purpose and without
* fee is hereby granted, provided that the above copyright
* notice appear in all copies and that both that copyright
* notice and this permission notice appear in supporting
* documentation, and that the name of Silicon Graphics not be
* used in advertising or publicity pertaining to distribution
* of the software without specific prior written permission.
* Silicon Graphics makes no representation about the suitability
* of this software for any purpose. It is provided "as is"
* without any express or implied warranty.
*
* SILICON GRAPHICS DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
* SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL SILICON
* GRAPHICS BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL
* DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
* DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH
* THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
********************************************************/
#include <stdio.h>
#include <ctype.h>
#include "rules.h"
#include "path.h"
static bool
input_line_get(struct xkb_context *ctx, FILE *file, darray_char *line)
{
int ch;
bool end_of_file = false;
bool space_pending;
bool slash_pending;
bool in_comment;
while (!end_of_file && darray_empty(*line)) {
space_pending = slash_pending = in_comment = false;
while ((ch = getc(file)) != '\n' && ch != EOF) {
if (ch == '\\') {
ch = getc(file);
if (ch == EOF)
break;
if (ch == '\n') {
in_comment = false;
ch = ' ';
}
}
if (in_comment)
continue;
if (ch == '/') {
if (slash_pending) {
in_comment = true;
slash_pending = false;
}
else {
slash_pending = true;
}
continue;
}
if (slash_pending) {
if (space_pending) {
darray_append(*line, ' ');
space_pending = false;
}
darray_append(*line, '/');
slash_pending = false;
}
if (isspace(ch)) {
while (isspace(ch) && ch != '\n' && ch != EOF)
ch = getc(file);
if (ch == EOF)
break;
if (ch != '\n' && !darray_empty(*line))
space_pending = true;
ungetc(ch, file);
}
else {
if (space_pending) {
darray_append(*line, ' ');
space_pending = false;
}
if (ch == '!') {
if (!darray_empty(*line)) {
log_warn(ctx,
"The '!' is legal only at start of line; "
"Line containing '!' ignored\n");
darray_resize(*line, 0);
break;
}
}
darray_append(*line, ch);
}
}
if (ch == EOF)
end_of_file = true;
}
if (darray_empty(*line) && end_of_file)
return false;
darray_append(*line, '\0');
return true;
}
/***====================================================================***/
enum {
/* "Parts" - the MLVO which rules file maps to components. */
MODEL = 0,
LAYOUT,
VARIANT,
OPTION,
#define PART_MASK \
((1 << MODEL) | (1 << LAYOUT) | (1 << VARIANT) | (1 << OPTION))
/* Components */
KEYCODES,
SYMBOLS,
TYPES,
COMPAT,
GEOMETRY,
#define COMPONENT_MASK \
((1 << KEYCODES) | (1 << SYMBOLS) | (1 << TYPES) | (1 << COMPAT) | \
(1 << GEOMETRY))
MAX_WORDS
};
static const char *cname[] = {
[MODEL] = "model",
[LAYOUT] = "layout",
[VARIANT] = "variant",
[OPTION] = "option",
[KEYCODES] = "keycodes",
[SYMBOLS] = "symbols",
[TYPES] = "types",
[COMPAT] = "compat",
[GEOMETRY] = "geometry",
};
struct multi_defs {
const char *model;
const char *layout[XkbNumKbdGroups + 1];
const char *variant[XkbNumKbdGroups + 1];
char *options;
};
struct mapping {
/* Sequential id for the mappings. */
int number;
size_t num_maps;
struct {
int word;
int index;
} map[MAX_WORDS];
};
struct var_desc {
char *name;
char *desc;
};
struct group {
int number;
char *name;
char *words;
};
enum rule_flag {
RULE_FLAG_PENDING_MATCH = (1L << 1),
RULE_FLAG_OPTION = (1L << 2),
RULE_FLAG_APPEND = (1L << 3),
RULE_FLAG_NORMAL = (1L << 4),
};
struct rule {
int number;
char *model;
char *layout;
int layout_num;
char *variant;
int variant_num;
char *option;
/* yields */
char *keycodes;
char *symbols;
char *types;
char *compat;
unsigned flags;
};
struct rules {
darray(struct rule) rules;
darray(struct group) groups;
};
/***====================================================================***/
/*
* Resolve numeric index, such as "[4]" in layout[4]. Missing index
* means zero.
*/
static char *
get_index(char *str, int *ndx)
{
int empty = 0, consumed = 0, num;
sscanf(str, "[%n%d]%n", &empty, &num, &consumed);
if (consumed > 0) {
*ndx = num;
str += consumed;
}
else if (empty > 0) {
*ndx = -1;
}
else {
*ndx = 0;
}
return str;
}
/*
* Match a mapping line which opens a rule, e.g:
* ! model layout[4] variant[4] = symbols geometry
* Which will be followed by lines such as:
* * ben basic = +in(ben):4 nec(pc98)
* So if the MLVO matches the LHS of some line, we'll get the components
* on the RHS.
* In this example, we will get for the second and fourth columns:
* mapping->map[1] = {.word = LAYOUT, .index = 4}
* mapping->map[3] = {.word = SYMBOLS, .index = 0}
*/
static void
match_mapping_line(struct xkb_context *ctx, darray_char *line,
struct mapping *mapping)
{
char *tok;
char *str = darray_mem(*line, 1);
unsigned present = 0, layout_ndx_present = 0, variant_ndx_present = 0;
int i, tmp;
size_t len;
int ndx;
char *strtok_buf;
bool found;
/*
* Remember the last sequential mapping id (incremented if the match
* is successful).
*/
tmp = mapping->number;
memset(mapping, 0, sizeof(*mapping));
mapping->number = tmp;
while ((tok = strtok_r(str, " ", &strtok_buf)) != NULL) {
found = false;
str = NULL;
if (streq(tok, "="))
continue;
for (i = 0; i < MAX_WORDS; i++) {
len = strlen(cname[i]);
if (strncmp(cname[i], tok, len) == 0) {
if (strlen(tok) > len) {
char *end = get_index(tok + len, &ndx);
if ((i != LAYOUT && i != VARIANT) ||
*end != '\0' || ndx == -1) {
log_warn(ctx,
"Illegal %s index: %d\n", cname[i], ndx);
log_warn(ctx, "Can only index layout and variant\n");
break;
}
if (ndx < 1 || ndx > XkbNumKbdGroups) {
log_warn(ctx, "Illegal %s index: %d\n",
cname[i], ndx);
log_warn(ctx, "Index must be in range 1..%d\n",
XkbNumKbdGroups);
break;
}
}
else {
ndx = 0;
}
found = true;
if (present & (1 << i)) {
if ((i == LAYOUT && layout_ndx_present & (1 << ndx)) ||
(i == VARIANT && variant_ndx_present & (1 << ndx))) {
log_warn(ctx,
"Component \"%s\" listed twice; "
"Second definition ignored\n", tok);
break;
}
}
present |= (1 << i);
if (i == LAYOUT)
layout_ndx_present |= 1 << ndx;
if (i == VARIANT)
variant_ndx_present |= 1 << ndx;
mapping->map[mapping->num_maps].word = i;
mapping->map[mapping->num_maps].index = ndx;
mapping->num_maps++;
break;
}
}
if (!found)
log_warn(ctx, "Unknown component \"%s\"; Ignored\n", tok);
}
if ((present & PART_MASK) == 0) {
log_warn(ctx,
"Mapping needs at least one MLVO part; "
"Illegal mapping ignored\n");
mapping->num_maps = 0;
return;
}
if ((present & COMPONENT_MASK) == 0) {
log_warn(ctx,
"Mapping needs at least one component; "
"Illegal mapping ignored\n");
mapping->num_maps = 0;
return;
}
mapping->number++;
}
/*
* Match a line such as:
* ! $pcmodels = pc101 pc102 pc104 pc105
*/
static bool
match_group_line(darray_char *line, struct group *group)
{
int i;
char *name = strchr(darray_mem(*line, 0), '$');
char *words = strchr(name, ' ');
if (!words)
return false;
*words++ = '\0';
for (; *words; words++) {
if (*words != '=' && *words != ' ')
break;
}
if (*words == '\0')
return false;
group->name = strdup(name);
group->words = strdup(words);
words = group->words;
for (i = 1; *words; words++) {
if (*words == ' ') {
*words++ = '\0';
i++;
}
}
group->number = i;
return true;
}
/* Match lines following a mapping (see match_mapping_line comment). */
static bool
match_rule_line(struct xkb_context *ctx, darray_char *line,
struct mapping *mapping, struct rule *rule)
{
char *str, *tok;
int nread, i;
char *strtok_buf;
bool append = false;
const char *names[MAX_WORDS] = { NULL };
if (mapping->num_maps == 0) {
log_warn(ctx,
"Must have a mapping before first line of data; "
"Illegal line of data ignored\n");
return false;
}
str = darray_mem(*line, 0);
for (nread = 0; (tok = strtok_r(str, " ", &strtok_buf)) != NULL;
nread++) {
str = NULL;
if (streq(tok, "=")) {
nread--;
continue;
}
if (nread > mapping->num_maps) {
log_warn(ctx,
"Too many words on a line; "
"Extra word \"%s\" ignored\n", tok);
continue;
}
names[mapping->map[nread].word] = tok;
if (*tok == '+' || *tok == '|')
append = true;
}
if (nread < mapping->num_maps) {
log_warn(ctx, "Too few words on a line: %s; Line ignored\n",
darray_mem(*line, 0));
return false;
}
rule->flags = 0;
rule->number = mapping->number;
if (names[OPTION])
rule->flags |= RULE_FLAG_OPTION;
else if (append)
rule->flags |= RULE_FLAG_APPEND;
else
rule->flags |= RULE_FLAG_NORMAL;
rule->model = strdup_safe(names[MODEL]);
rule->layout = strdup_safe(names[LAYOUT]);
rule->variant = strdup_safe(names[VARIANT]);
rule->option = strdup_safe(names[OPTION]);
rule->keycodes = strdup_safe(names[KEYCODES]);
rule->symbols = strdup_safe(names[SYMBOLS]);
rule->types = strdup_safe(names[TYPES]);
rule->compat = strdup_safe(names[COMPAT]);
rule->layout_num = rule->variant_num = 0;
for (i = 0; i < nread; i++) {
if (mapping->map[i].index) {
if (mapping->map[i].word == LAYOUT)
rule->layout_num = mapping->map[i].index;
if (mapping->map[i].word == VARIANT)
rule->variant_num = mapping->map[i].index;
}
}
return true;
}
static bool
match_line(struct xkb_context *ctx, darray_char *line,
struct mapping *mapping, struct rule *rule,
struct group *group)
{
if (darray_item(*line, 0) != '!')
return match_rule_line(ctx, line, mapping, rule);
if (darray_item(*line, 1) == '$' ||
(darray_item(*line, 1) == ' ' && darray_item(*line, 2) == '$'))
return match_group_line(line, group);
match_mapping_line(ctx, line, mapping);
return false;
}
static void
squeeze_spaces(char *p1)
{
char *p2;
for (p2 = p1; *p2; p2++) {
*p1 = *p2;
if (*p1 != ' ')
p1++;
}
*p1 = '\0';
}
/*
* Expand the layout and variant of the rule_names and remove extraneous
* spaces. If there's one layout/variant, it is kept in
* .layout[0]/.variant[0], else is kept in [1], [2] and so on, and [0]
* remains empty. For example, this rule_names:
* .model = "pc105",
* .layout = "us,il,ru,ca"
* .variant = ",,,multix"
* .options = "grp:alts_toggle, ctrl:nocaps, compose:rwin"
* Is expanded into this multi_defs:
* .model = "pc105"
* .layout = {NULL, "us", "il", "ru", "ca"},
* .variant = {NULL, "", "", "", "multix"},
* .options = "grp:alts_toggle,ctrl:nocaps,compose:rwin"
*/
static bool
make_multi_defs(struct multi_defs *mdefs, const struct xkb_rule_names *mlvo)
{
char *p;
int i;
memset(mdefs, 0, sizeof(*mdefs));
if (mlvo->model) {
mdefs->model = mlvo->model;
}
if (mlvo->options) {
mdefs->options = strdup(mlvo->options);
if (mdefs->options == NULL)
return false;
squeeze_spaces(mdefs->options);
}
if (mlvo->layout) {
if (!strchr(mlvo->layout, ',')) {
mdefs->layout[0] = mlvo->layout;
}
else {
p = strdup(mlvo->layout);
if (p == NULL)
return false;
squeeze_spaces(p);
mdefs->layout[1] = p;
for (i = 2; i <= XkbNumKbdGroups; i++) {
if ((p = strchr(p, ','))) {
*p++ = '\0';
mdefs->layout[i] = p;
}
else {
break;
}
}
if (p && (p = strchr(p, ',')))
*p = '\0';
}
}
if (mlvo->variant) {
if (!strchr(mlvo->variant, ',')) {
mdefs->variant[0] = mlvo->variant;
}
else {
p = strdup(mlvo->variant);
if (p == NULL)
return false;
squeeze_spaces(p);
mdefs->variant[1] = p;
for (i = 2; i <= XkbNumKbdGroups; i++) {
if ((p = strchr(p, ','))) {
*p++ = '\0';
mdefs->variant[i] = p;
}
else {
break;
}
}
if (p && (p = strchr(p, ',')))
*p = '\0';
}
}
return true;
}
static void
free_multi_defs(struct multi_defs *defs)
{
free(defs->options);
/*
* See make_multi_defs comment for the hack; the same strdup'd
* string is split among the indexes, but the one in [0] is const.
*/
free(UNCONSTIFY(defs->layout[1]));
free(UNCONSTIFY(defs->variant[1]));
}
/* See apply_rule below. */
static void
apply(const char *src, char **dst)
{
int ret;
char *tmp;
if (!src)
return;
if (*src == '+' || *src == '!') {
tmp = *dst;
ret = asprintf(dst, "%s%s", *dst, src);
if (ret < 0)
*dst = NULL;
free(tmp);
}
else if (*dst == NULL) {
*dst = strdup(src);
}
}
/*
* Add the info from the matching rule to the resulting
* xkb_component_names. If we already had a match for something
* (e.g. keycodes), and the rule is not an appending one (e.g.
* +whatever), than we don't override but drop the new one.
*/
static void
apply_rule(struct rule *rule, struct xkb_component_names *kccgst)
{
/* Clear the flag because it's applied. */
rule->flags &= ~RULE_FLAG_PENDING_MATCH;
apply(rule->keycodes, &kccgst->keycodes);
apply(rule->symbols, &kccgst->symbols);
apply(rule->types, &kccgst->types);
apply(rule->compat, &kccgst->compat);
}
/*
* Match if name is part of the group, e.g. if the following
* group is defined:
* ! $qwertz = al cz de hr hu ro si sk
* then
* match_group_member(rules, "qwertz", "hr")
* will return true.
*/
static bool
match_group_member(struct rules *rules, const char *group_name,
const char *name)
{
int i;
const char *word;
struct group *iter, *group = NULL;
darray_foreach(iter, rules->groups) {
if (streq(iter->name, group_name)) {
group = iter;
break;
}
}
if (!group)
return false;
word = group->words;
for (i = 0; i < group->number; i++, word += strlen(word) + 1)
if (streq(word, name))
return true;
return false;
}
/* Match @needle out of @sep-seperated @haystack. */
static bool
match_one_of(const char *haystack, const char *needle, char sep)
{
const char *s = haystack;
const size_t len = strlen(needle);
do {
if (strncmp(s, needle, len) == 0 && (s[len] == '\0' || s[len] == sep))
return true;
s = strchr(s, sep);
} while (s++);
return false;
}
static int
apply_rule_if_matches(struct rules *rules, struct rule *rule,
struct multi_defs *mdefs,
struct xkb_component_names *kccgst)
{
bool pending = false;
if (rule->model) {
if (mdefs->model == NULL)
return 0;
if (streq(rule->model, "*")) {
pending = true;
}
else if (rule->model[0] == '$') {
if (!match_group_member(rules, rule->model, mdefs->model))
return 0;
}
else if (!streq(rule->model, mdefs->model)) {
return 0;
}
}
if (rule->option) {
if (mdefs->options == NULL)
return 0;
if (!match_one_of(mdefs->options, rule->option, ','))
return 0;
}
if (rule->layout) {
if (mdefs->layout[rule->layout_num] == NULL)
return 0;
if (streq(rule->layout, "*")) {
pending = true;
}
else if (rule->layout[0] == '$') {
if (!match_group_member(rules, rule->layout,
mdefs->layout[rule->layout_num]))
return 0;
}
else if (!streq(rule->layout, mdefs->layout[rule->layout_num])) {
return 0;
}
}
if (rule->variant) {
if (mdefs->variant[rule->variant_num] == NULL)
return 0;
if (streq(rule->variant, "*")) {
pending = true;
}
else if (rule->variant[0] == '$') {
if (!match_group_member(rules, rule->variant,
mdefs->variant[rule->variant_num]))
return 0;
}
else if (!streq(rule->variant, mdefs->variant[rule->variant_num])) {
return 0;
}
}
if (pending) {
rule->flags |= RULE_FLAG_PENDING_MATCH;
}
else {
/* Exact match, apply it now. */
apply_rule(rule, kccgst);
}
return rule->number;
}
static void
clear_partial_matches(struct rules *rules)
{
struct rule *rule;
darray_foreach(rule, rules->rules)
rule->flags &= ~RULE_FLAG_PENDING_MATCH;
}
static void
apply_partial_matches(struct rules *rules, struct xkb_component_names *kccgst)
{
struct rule *rule;
darray_foreach(rule, rules->rules)
if (rule->flags & RULE_FLAG_PENDING_MATCH)
apply_rule(rule, kccgst);
}
static void
apply_matching_rules(struct rules *rules, struct multi_defs *mdefs,
struct xkb_component_names *kccgst, unsigned int flags)
{
int skip = -1;
struct rule *rule;
darray_foreach(rule, rules->rules) {
if ((rule->flags & flags) != flags)
continue;
if ((flags & RULE_FLAG_OPTION) == 0 && rule->number == skip)
continue;
skip = apply_rule_if_matches(rules, rule, mdefs, kccgst);
}
}
/***====================================================================***/
static char *
substitute_vars(char *name, struct multi_defs *mdefs)
{
char *str, *outstr, *var;
char *orig = name;
size_t len, extra_len;
char pfx, sfx;
int ndx;
if (!name)
return NULL;
str = strchr(name, '%');
if (str == NULL)
return name;
len = strlen(name);
while (str != NULL) {
pfx = str[1];
extra_len = 0;
if (pfx == '+' || pfx == '|' || pfx == '_' || pfx == '-') {
extra_len = 1;
str++;
}
else if (pfx == '(') {
extra_len = 2;
str++;
}
var = str + 1;
str = get_index(var + 1, &ndx);
if (ndx == -1) {
str = strchr(str, '%');
continue;
}
if (*var == 'l' && mdefs->layout[ndx] && *mdefs->layout[ndx])
len += strlen(mdefs->layout[ndx]) + extra_len;
else if (*var == 'm' && mdefs->model)
len += strlen(mdefs->model) + extra_len;
else if (*var == 'v' && mdefs->variant[ndx] && *mdefs->variant[ndx])
len += strlen(mdefs->variant[ndx]) + extra_len;
if (pfx == '(' && *str == ')')
str++;
str = strchr(&str[0], '%');
}
name = malloc(len + 1);
str = orig;
outstr = name;
while (*str != '\0') {
if (str[0] == '%') {
str++;
pfx = str[0];
sfx = '\0';
if (pfx == '+' || pfx == '|' || pfx == '_' || pfx == '-') {
str++;
}
else if (pfx == '(') {
sfx = ')';
str++;
}
else {
pfx = '\0';
}
var = str;
str = get_index(var + 1, &ndx);
if (ndx == -1)
continue;
if (*var == 'l' && mdefs->layout[ndx] && *mdefs->layout[ndx]) {
if (pfx)
*outstr++ = pfx;
strcpy(outstr, mdefs->layout[ndx]);
outstr += strlen(mdefs->layout[ndx]);
if (sfx)
*outstr++ = sfx;
}
else if (*var == 'm' && mdefs->model) {
if (pfx)
*outstr++ = pfx;
strcpy(outstr, mdefs->model);
outstr += strlen(mdefs->model);
if (sfx)
*outstr++ = sfx;
}
else if (*var == 'v' && mdefs->variant[ndx] &&
*mdefs->variant[ndx]) {
if (pfx)
*outstr++ = pfx;
strcpy(outstr, mdefs->variant[ndx]);
outstr += strlen(mdefs->variant[ndx]);
if (sfx)
*outstr++ = sfx;
}
if (pfx == '(' && *str == ')')
str++;
}
else {
*outstr++ = *str++;
}
}
*outstr++ = '\0';
if (orig != name)
free(orig);
return name;
}
/***====================================================================***/
static bool
get_components(struct rules *rules, const struct xkb_rule_names *mlvo,
struct xkb_component_names *kccgst)
{
struct multi_defs mdefs;
make_multi_defs(&mdefs, mlvo);
clear_partial_matches(rules);
apply_matching_rules(rules, &mdefs, kccgst, RULE_FLAG_NORMAL);
apply_partial_matches(rules, kccgst);
apply_matching_rules(rules, &mdefs, kccgst, RULE_FLAG_APPEND);
apply_partial_matches(rules, kccgst);
apply_matching_rules(rules, &mdefs, kccgst, RULE_FLAG_OPTION);
apply_partial_matches(rules, kccgst);
kccgst->keycodes = substitute_vars(kccgst->keycodes, &mdefs);
kccgst->symbols = substitute_vars(kccgst->symbols, &mdefs);
kccgst->types = substitute_vars(kccgst->types, &mdefs);
kccgst->compat = substitute_vars(kccgst->compat, &mdefs);
free_multi_defs(&mdefs);
return kccgst->keycodes && kccgst->symbols && kccgst->types &&
kccgst->compat;
}
static struct rules *
load_rules(struct xkb_context *ctx, FILE *file)
{
darray_char line;
struct mapping mapping;
struct rule trule;
struct group tgroup;
struct rules *rules;
rules = calloc(1, sizeof(*rules));
if (!rules)
return NULL;
darray_init(rules->rules);
darray_growalloc(rules->rules, 16);
memset(&mapping, 0, sizeof(mapping));
memset(&tgroup, 0, sizeof(tgroup));
darray_init(line);
darray_growalloc(line, 128);
while (input_line_get(ctx, file, &line)) {
if (match_line(ctx, &line, &mapping, &trule, &tgroup)) {
if (tgroup.number) {
darray_append(rules->groups, tgroup);
memset(&tgroup, 0, sizeof(tgroup));
}
else {
darray_append(rules->rules, trule);
memset(&trule, 0, sizeof(trule));
}
}
darray_resize(line, 0);
}
darray_free(line);
return rules;
}
static void
free_rules(struct rules *rules)
{
struct rule *rule;
struct group *group;
if (!rules)
return;
darray_foreach(rule, rules->rules) {
free(rule->model);
free(rule->layout);
free(rule->variant);
free(rule->option);
free(rule->keycodes);
free(rule->symbols);
free(rule->types);
free(rule->compat);
}
darray_free(rules->rules);
darray_foreach(group, rules->groups) {
free(group->name);
free(group->words);
}
darray_free(rules->groups);
free(rules);
}
struct xkb_component_names *
xkb_components_from_rules(struct xkb_context *ctx,
const struct xkb_rule_names *rmlvo)
{
FILE *file;
char *path;
char **include;
struct rules *rules;
struct xkb_component_names *kccgst = NULL;
file = XkbFindFileInPath(ctx, rmlvo->rules, FILE_TYPE_RULES, &path);
if (!file) {
log_err(ctx, "could not find \"%s\" rules in XKB path\n",
rmlvo->rules);
log_err(ctx, "%zu include paths searched:\n",
darray_size(ctx->includes));
darray_foreach(include, ctx->includes)
log_err(ctx, "\t%s\n", *include);
log_err(ctx, "%zu include paths could not be added:\n",
darray_size(ctx->failed_includes));
darray_foreach(include, ctx->failed_includes)
log_err(ctx, "\t%s\n", *include);
return NULL;
}
rules = load_rules(ctx, file);
if (!rules) {
log_err(ctx, "failed to load XKB rules \"%s\"\n", path);
goto err;
}
kccgst = calloc(1, sizeof(*kccgst));
if (!kccgst) {
log_err(ctx, "failed to allocate XKB components\n");
goto err;
}
if (!get_components(rules, rmlvo, kccgst)) {
free(kccgst->keycodes);
free(kccgst->types);
free(kccgst->compat);
free(kccgst->symbols);
free(kccgst);
kccgst = NULL;
log_err(ctx, "no components returned from XKB rules \"%s\"\n", path);
goto err;
}
err:
free_rules(rules);
if (file)
fclose(file);
free(path);
return kccgst;
}