diff --git a/changes/api/+modern-rules.feature.md b/changes/api/+modern-rules.feature.md
new file mode 100644
index 00000000..d27af48b
--- /dev/null
+++ b/changes/api/+modern-rules.feature.md
@@ -0,0 +1,14 @@
+Rules: Added support for special layouts indexes:
+- *single*: matches a single layout; `layout[single]` is the same as without
+ explicit index: `layout`.
+- *first*: matches the first layout/variant, no matter how many layouts are in
+ the RMLVO configuration. Acts as both `layout` and `layout[1]`.
+- *later*: matches all but the first layout. This is an index range. Acts as
+ `layout[2]` .. `layout[4]`.
+- *any*: matches layout at any position. This is an index range.
+
+Also added the special index `%i` which correspond to the index of the matched
+layout.
+
+See the [documentation](https://xkbcommon.org/doc/current/rule-file-format.html)
+for further information.
diff --git a/doc/rules-format.md b/doc/rules-format.md
index 7162e34c..99c4dc80 100644
--- a/doc/rules-format.md
+++ b/doc/rules-format.md
@@ -143,7 +143,9 @@ RuleSet ::= Mapping { Rule }
Mapping ::= { Mlvo } "=" { Kccgst } "\n"
Mlvo ::= "model" | "option" | ("layout" | "variant") [ Index ]
-Index ::= "[" 1..XKB_NUM_GROUPS "]"
+Index ::= "[" ({ NumericIndex } | { SpecialIndex }) "]"
+NumericIndex ::= 1..XKB_MAX_GROUPS
+SpecialIndex ::= "single" | "first" | "later" | "any"
Kccgst ::= "keycodes" | "symbols" | "types" | "compat" | "geometry"
Rule ::= { MlvoValue } "=" { KccgstValue } "\n"
@@ -175,13 +177,46 @@ or %%H seems to do the job though.
`/usr/share/X11/xkb/rules`).
+ **Note:** This feature is supported by libxkbcommon but not by the legacy X11
+ tools.
+
+- @anchor rules-special-indexes
+ (Since version `1.8.0`)
+ The following *special indexes* can be used to avoid repetition and clarify
+ the semantics:
+
+
+ - `single`
+ -
+ Matches a single layout; `layout[single]` is the same as without
+ explicit index: `layout`.
+
+ - `first`
+ -
+ Matches the first layout/variant, no matter how many layouts are in
+ the RMLVO configuration. Acts as both `layout` and `layout[1]`.
+
+ - `later`
+ -
+ Matches all but the first layout. This is an index *range*.
+ Acts as `layout[2]` .. `layout[4]`.
+
+ - any
+ -
+ Matches layout at any position. This is an index *range*.
+ Acts as `layout`, `layout[1]` .. `layout[4]`.
+
+
+
+ When using a layout index *range* (`later`, `any`), the @ref rules-i-expansion "%i expansion"
+ can be used in the `KccgstValue` to refer to the index of the matched layout.
- The order of values in a `Rule` must be the same as the `Mapping` it
follows. The mapping line determines the meaning of the values in
the rules which follow in the `RuleSet`.
- If a `Rule` is matched, **%-expansion** is performed on the
-`KccgstValue`, as follows:
+ `KccgstValue`, as follows:
\%m
, \%l
, \%v
@@ -216,6 +251,18 @@ or %%H seems to do the job though.
-
As above, but prefixed by ‘(’ and suffixed by ‘)’.
+ -
+ @anchor rules-i-expansion
+ `%%i`,
+ `%%l[%%i]`,
+ `%(l[%%i])`,
+ etc.
+
+ -
+ (Since version `1.8.0`)
+ In case the mapping uses a @ref rules-special-indexes "special index",
+ `%%i` corresponds to the index of the matched layout.
+
In case the expansion is *invalid*, as described above, it is *skipped*
@@ -329,6 +376,16 @@ we would have the following resolutions of [symbols]:
| `us,es` | | `pc+us+es:2` | #2, #3 |
| `us,es,fr` | `intl,,bepo` | `pc+us(intl)+es:2+fr(bepo):3` | #2, #3, #4 |
+Since version `1.8.0`, the previous code can be replaced with simply:
+
+```c
+! layout[first] = symbols
+ * = pc+%l[%i]%(v[%i])
+
+! layout[later] = symbols
+ * = +%l[%i]%(v[%i]):%i
+```
+
### Example: layout, option and symbols {#rules-options-example}
Using the following example:
diff --git a/src/darray.h b/src/darray.h
index b75d85f9..b559d7b9 100644
--- a/src/darray.h
+++ b/src/darray.h
@@ -83,6 +83,7 @@ typedef darray (unsigned long) darray_ulong;
/*** Access ***/
#define darray_item(arr, i) ((arr).item[i])
+#define darray_items(arr) ((arr).item)
#define darray_size(arr) ((arr).size)
#define darray_empty(arr) ((arr).size == 0)
diff --git a/src/xkbcomp/rules.c b/src/xkbcomp/rules.c
index 4b3f793f..cf5d9da6 100644
--- a/src/xkbcomp/rules.c
+++ b/src/xkbcomp/rules.c
@@ -49,6 +49,8 @@
#include "config.h"
+#include
+
#include "xkbcomp-priv.h"
#include "rules.h"
#include "include.h"
@@ -225,11 +227,26 @@ struct mapping {
int mlvo_at_pos[_MLVO_NUM_ENTRIES];
unsigned int num_mlvo;
unsigned int defined_mlvo_mask;
- xkb_layout_index_t layout_idx, variant_idx;
+ bool has_layout_idx_range;
+ /* This member has 2 uses:
+ * • Keep track of layout and variant indexes while parsing MLVO headers.
+ * • Store layout/variant range afterwards.
+ * Thus provide 2 structs to reflect these semantics in the code. */
+ union {
+ struct { xkb_layout_index_t layout_idx, variant_idx; };
+ struct { xkb_layout_index_t layout_idx_min, layout_idx_max; };
+ };
+ /* This member has 2 uses:
+ * • Check if the mapping is active by interpreting the value as a boolean.
+ * • Keep track of the remaining layout indexes to match.
+ * Thus provide 2 names to reflect these semantics in the code. */
+ union {
+ xkb_layout_mask_t active;
+ xkb_layout_mask_t layouts_candidates_mask;
+ };
int kccgst_at_pos[_KCCGST_NUM_ENTRIES];
unsigned int num_kccgst;
unsigned int defined_kccgst_mask;
- bool skip;
};
enum mlvo_match_type {
@@ -499,29 +516,91 @@ matcher_mapping_start_new(struct matcher *m)
m->mapping.mlvo_at_pos[i] = -1;
for (unsigned i = 0; i < _KCCGST_NUM_ENTRIES; i++)
m->mapping.kccgst_at_pos[i] = -1;
+ m->mapping.has_layout_idx_range = false;
m->mapping.layout_idx = m->mapping.variant_idx = XKB_LAYOUT_INVALID;
m->mapping.num_mlvo = m->mapping.num_kccgst = 0;
m->mapping.defined_mlvo_mask = 0;
m->mapping.defined_kccgst_mask = 0;
- m->mapping.skip = false;
+ m->mapping.active = true;
}
+/* Parse Kccgst layout index:
+ * "[%i]" or "[n]", where "n" is a decimal number */
static int
extract_layout_index(const char *s, size_t max_len, xkb_layout_index_t *out)
{
/* This function is pretty stupid, but works for now. */
*out = XKB_LAYOUT_INVALID;
- if (max_len < 3)
- return -1;
- if (s[0] != '[' || !is_digit(s[1]) || s[2] != ']')
+ if (max_len < 3 || s[0] != '[')
return -1;
- if (s[1] - '0' < 1 || s[1] - '0' > XKB_MAX_GROUPS)
+ if (max_len > 3 && s[1] == '%' && s[2] == 'i' && s[3] == ']') {
+ /* Special index: %i */
+ return 4; /* == length "[%i]" */
+ }
+ /* Numeric index */
+
+#define parse_layout_int_index(s, endptr, index, out) \
+ char *endptr; \
+ long index = strtol(&s[1], &endptr, 10); \
+ if (index < 0 || endptr[0] != ']' || index > XKB_MAX_GROUPS) \
+ return -1; \
+ /* To zero-based index. */ \
+ *out = (xkb_layout_index_t)index - 1; \
+ return (int)(endptr - s + 1) /* == length "[index]" */
+
+ parse_layout_int_index(s, endptr, index, out);
+}
+
+/* Special layout indexes */
+#define LAYOUT_INDEX_SINGLE XKB_LAYOUT_INVALID
+#define LAYOUT_INDEX_FIRST (XKB_LAYOUT_INVALID - 3)
+#define LAYOUT_INDEX_LATER (XKB_LAYOUT_INVALID - 2)
+#define LAYOUT_INDEX_ANY (XKB_LAYOUT_INVALID - 1)
+
+#if XKB_MAX_GROUPS >= LAYOUT_INDEX_FIRST
+ #error "Cannot define special indexes"
+#endif
+#if LAYOUT_INDEX_FIRST >= LAYOUT_INDEX_LATER || \
+ LAYOUT_INDEX_LATER >= LAYOUT_INDEX_ANY || \
+ LAYOUT_INDEX_ANY >= XKB_LAYOUT_INVALID || \
+ LAYOUT_INDEX_SINGLE != XKB_LAYOUT_INVALID
+ #error "Special indexes must respect certain order"
+#endif
+
+#define LAYOUT_INDEX_SINGLE_STR "single"
+#define LAYOUT_INDEX_FIRST_STR "first"
+#define LAYOUT_INDEX_LATER_STR "later"
+#define LAYOUT_INDEX_ANY_STR "any"
+
+/* Parse index of layout/variant in MLVO mapping */
+static int
+extract_mapping_layout_index(const char *s, size_t max_len,
+ xkb_layout_index_t *out)
+{
+ *out = XKB_LAYOUT_INVALID;
+ if (max_len < 3 || s[0] != '[')
return -1;
- /* To zero-based index. */
- *out = s[1] - '0' - 1;
- return 3;
+#define if_index(s, index, out) \
+ /* Compare s against "index]" */ \
+ if (strncmp(s, index##_STR "]", sizeof(index##_STR)) == 0) { \
+ *out = index; \
+ return sizeof(index##_STR) + 1; /* == length("[index]") */ \
+ }
+ else if_index(&s[1], LAYOUT_INDEX_SINGLE, out)
+ else if_index(&s[1], LAYOUT_INDEX_FIRST, out)
+ else if_index(&s[1], LAYOUT_INDEX_LATER, out)
+ else if_index(&s[1], LAYOUT_INDEX_ANY, out)
+ else {
+ /* Numeric index */
+ parse_layout_int_index(s, endptr, index, out);
+ }
+#undef if_index
+#undef LAYOUT_INDEX_SINGLE
}
+#define is_mlvo_mask_defined(m, mlvo) \
+ ((m)->mapping.defined_mlvo_mask & (1u << (mlvo)))
+
static void
matcher_mapping_set_mlvo(struct matcher *m, struct scanner *s,
struct sval ident)
@@ -542,30 +621,31 @@ matcher_mapping_set_mlvo(struct matcher *m, struct scanner *s,
"invalid mapping: %.*s is not a valid value here; "
"ignoring rule set",
ident.len, ident.start);
- m->mapping.skip = true;
+ m->mapping.active = false;
return;
}
- if (m->mapping.defined_mlvo_mask & (1u << mlvo)) {
+ if (is_mlvo_mask_defined(m, mlvo)) {
scanner_err(s, XKB_LOG_MESSAGE_NO_ID,
"invalid mapping: %.*s appears twice on the same line; "
"ignoring rule set",
mlvo_sval.len, mlvo_sval.start);
- m->mapping.skip = true;
+ m->mapping.active = false;
return;
}
/* If there are leftovers still, it must be an index. */
if (mlvo_sval.len < ident.len) {
xkb_layout_index_t idx;
- int consumed = extract_layout_index(ident.start + mlvo_sval.len,
- ident.len - mlvo_sval.len, &idx);
+ int consumed = extract_mapping_layout_index(ident.start + mlvo_sval.len,
+ ident.len - mlvo_sval.len,
+ &idx);
if ((int) (ident.len - mlvo_sval.len) != consumed) {
scanner_err(s, XKB_LOG_MESSAGE_NO_ID,
"invalid mapping: \"%.*s\" may only be followed by a "
"valid group index; ignoring rule set",
mlvo_sval.len, mlvo_sval.start);
- m->mapping.skip = true;
+ m->mapping.active = false;
return;
}
@@ -580,16 +660,66 @@ matcher_mapping_set_mlvo(struct matcher *m, struct scanner *s,
"invalid mapping: \"%.*s\" cannot be followed by a group "
"index; ignoring rule set",
mlvo_sval.len, mlvo_sval.start);
- m->mapping.skip = true;
+ m->mapping.active = false;
return;
}
}
+ /* Check that if both layout and variant are defined, then they must have
+ the same index */
+ if (((mlvo == MLVO_LAYOUT && is_mlvo_mask_defined(m, MLVO_VARIANT)) ||
+ (mlvo == MLVO_VARIANT && is_mlvo_mask_defined(m, MLVO_LAYOUT))) &&
+ m->mapping.layout_idx != m->mapping.variant_idx) {
+ scanner_err(s, XKB_LOG_MESSAGE_NO_ID,
+ "invalid mapping: \"layout\" index must be the same as the "
+ "\"variant\" index");
+ m->mapping.active = false;
+ return;
+ }
+
m->mapping.mlvo_at_pos[m->mapping.num_mlvo] = mlvo;
m->mapping.defined_mlvo_mask |= 1u << mlvo;
m->mapping.num_mlvo++;
}
+static void
+matcher_mapping_set_layout_bounds(struct matcher *m)
+{
+ /* Handle case where one of the index is XKB_LAYOUT_INVALID */
+ xkb_layout_index_t idx = MIN(m->mapping.layout_idx, m->mapping.variant_idx);
+ switch (idx) {
+ case LAYOUT_INDEX_LATER:
+ m->mapping.has_layout_idx_range = true;
+ m->mapping.layout_idx_min = 1;
+ m->mapping.layout_idx_max = MIN(XKB_MAX_GROUPS,
+ darray_size(m->rmlvo.layouts));
+ m->mapping.layouts_candidates_mask =
+ /* All but the first layout */
+ ((1u << m->mapping.layout_idx_max) - 1) & ~1;
+ break;
+ case LAYOUT_INDEX_ANY:
+ m->mapping.has_layout_idx_range = true;
+ m->mapping.layout_idx_min = 0;
+ m->mapping.layout_idx_max = MIN(XKB_MAX_GROUPS,
+ darray_size(m->rmlvo.layouts));
+ m->mapping.layouts_candidates_mask =
+ /* All layouts */
+ (1u << m->mapping.layout_idx_max) - 1;
+ break;
+ case LAYOUT_INDEX_FIRST:
+ case XKB_LAYOUT_INVALID:
+ /* No index or first index */
+ idx = 0;
+ /* fallthrough */
+ default:
+ /* Mere layout index */
+ m->mapping.has_layout_idx_range = false;
+ m->mapping.layout_idx_min = idx;
+ m->mapping.layout_idx_max = idx + 1;
+ m->mapping.layouts_candidates_mask = 1u << idx;
+ }
+}
+
static void
matcher_mapping_set_kccgst(struct matcher *m, struct scanner *s, struct sval ident)
{
@@ -609,7 +739,7 @@ matcher_mapping_set_kccgst(struct matcher *m, struct scanner *s, struct sval ide
"invalid mapping: %.*s is not a valid value here; "
"ignoring rule set",
ident.len, ident.start);
- m->mapping.skip = true;
+ m->mapping.active = false;
return;
}
@@ -618,7 +748,7 @@ matcher_mapping_set_kccgst(struct matcher *m, struct scanner *s, struct sval ide
"invalid mapping: %.*s appears twice on the same line; "
"ignoring rule set",
kccgst_sval.len, kccgst_sval.start);
- m->mapping.skip = true;
+ m->mapping.active = false;
return;
}
@@ -627,7 +757,7 @@ matcher_mapping_set_kccgst(struct matcher *m, struct scanner *s, struct sval ide
m->mapping.num_kccgst++;
}
-static void
+static bool
matcher_mapping_verify(struct matcher *m, struct scanner *s)
{
if (m->mapping.num_mlvo == 0) {
@@ -649,41 +779,62 @@ matcher_mapping_verify(struct matcher *m, struct scanner *s)
* See the "Notes" section in the overview above.
*/
- if (m->mapping.defined_mlvo_mask & (1u << MLVO_LAYOUT)) {
- if (m->mapping.layout_idx == XKB_LAYOUT_INVALID) {
- if (darray_size(m->rmlvo.layouts) > 1)
- goto skip;
- }
- else {
- if (darray_size(m->rmlvo.layouts) == 1 ||
- m->mapping.layout_idx >= darray_size(m->rmlvo.layouts))
- goto skip;
+ if (is_mlvo_mask_defined(m, MLVO_LAYOUT)) {
+ switch (m->mapping.layout_idx) {
+ case XKB_LAYOUT_INVALID:
+ /* Layout rule without index matches when
+ * exactly 1 layout is specified */
+ if (darray_size(m->rmlvo.layouts) > 1)
+ goto skip;
+ break;
+ case LAYOUT_INDEX_ANY:
+ case LAYOUT_INDEX_LATER:
+ case LAYOUT_INDEX_FIRST:
+ /* No restrictions */
+ break;
+ default:
+ /* Layout rule with index matches when at least 2 layouts are
+ * specified. Index must be in valid range. */
+ if (darray_size(m->rmlvo.layouts) < 2 ||
+ m->mapping.layout_idx >= darray_size(m->rmlvo.layouts))
+ goto skip;
}
}
- if (m->mapping.defined_mlvo_mask & (1u << MLVO_VARIANT)) {
- if (m->mapping.variant_idx == XKB_LAYOUT_INVALID) {
- if (darray_size(m->rmlvo.variants) > 1)
- goto skip;
- }
- else {
- if (darray_size(m->rmlvo.variants) == 1 ||
- m->mapping.variant_idx >= darray_size(m->rmlvo.variants))
- goto skip;
+ if (is_mlvo_mask_defined(m, MLVO_VARIANT)) {
+ switch (m->mapping.variant_idx) {
+ case XKB_LAYOUT_INVALID:
+ /* Variant rule without index matches
+ * when exactly 1 variant is specified */
+ if (darray_size(m->rmlvo.variants) > 1)
+ goto skip;
+ break;
+ case LAYOUT_INDEX_ANY:
+ case LAYOUT_INDEX_LATER:
+ case LAYOUT_INDEX_FIRST:
+ /* No restriction */
+ break;
+ default:
+ /* Variant rule with index matches when at least 2 variants are
+ * specified. Index must be in valid range. */
+ if (darray_size(m->rmlvo.variants) < 2 ||
+ m->mapping.variant_idx >= darray_size(m->rmlvo.variants))
+ goto skip;
}
}
- return;
+ return true;
skip:
- m->mapping.skip = true;
+ m->mapping.active = false;
+ return false;
}
static void
matcher_rule_start_new(struct matcher *m)
{
memset(&m->rule, 0, sizeof(m->rule));
- m->rule.skip = m->mapping.skip;
+ m->rule.skip = !m->mapping.active;
}
static void
@@ -795,114 +946,168 @@ match_value_and_mark(struct matcher *m, struct sval val,
/*
* This function performs %-expansion on @value (see overview above),
- * and appends the result to @to.
+ * and appends the result to @expanded.
*/
static bool
-append_expanded_kccgst_value(struct matcher *m, struct scanner *s,
- darray_char *to, struct sval value)
+expand_rmlvo_in_kccgst_value(struct matcher *m, struct scanner *s,
+ struct sval value, xkb_layout_index_t layout_idx,
+ darray_char *expanded, unsigned *i)
{
const char *str = value.start;
- darray_char expanded = darray_new();
- char ch;
- bool expanded_plus, to_plus;
/*
* Some ugly hand-lexing here, but going through the scanner is more
* trouble than it's worth, and the format is ugly on its own merit.
*/
- for (unsigned i = 0; i < value.len; ) {
- enum rules_mlvo mlv;
- xkb_layout_index_t idx;
- char pfx, sfx;
- struct matched_sval *expanded_value;
+ enum rules_mlvo mlv;
+ xkb_layout_index_t idx;
+ char pfx, sfx;
+ struct matched_sval *expanded_value;
+
+ /* %i not as layout/variant index "%l[%i]" but as qualifier ":%i" */
+ if (str[*i] == 'i' &&
+ (*i + 1 == value.len || is_merge_mode_prefix(str[*i + 1])))
+ {
+ (*i)++;
+ char index_str[MAX_LAYOUT_INDEX_STR_LENGTH + 1];
+ int count = snprintf(index_str, sizeof(index_str), "%"PRIu32,
+ layout_idx + 1);
+ darray_appends_nullterminate(*expanded, index_str, count);
+ return true;
+ }
- /* Check if that's a start of an expansion. */
- if (str[i] != '%') {
- /* Just a normal character. */
- darray_appends_nullterminate(expanded, &str[i++], 1);
- continue;
- }
- if (++i >= value.len) goto error;
+ pfx = sfx = 0;
- pfx = sfx = 0;
+ /* Check for prefix. */
+ if (str[*i] == '(' ||
+ is_merge_mode_prefix(str[*i]) ||
+ str[*i] == '_' || str[*i] == '-') {
+ pfx = str[*i];
+ if (str[*i] == '(') sfx = ')';
+ if (++(*i) >= value.len) goto error;
+ }
- /* Check for prefix. */
- if (str[i] == '(' ||
- is_merge_mode_prefix(str[i]) ||
- str[i] == '_' || str[i] == '-') {
- pfx = str[i];
- if (str[i] == '(') sfx = ')';
- if (++i >= value.len) goto error;
- }
+ /* Mandatory model/layout/variant specifier. */
+ switch (str[(*i)++]) {
+ case 'm': mlv = MLVO_MODEL; break;
+ case 'l': mlv = MLVO_LAYOUT; break;
+ case 'v': mlv = MLVO_VARIANT; break;
+ default: goto error;
+ }
- /* Mandatory model/layout/variant specifier. */
- switch (str[i++]) {
- case 'm': mlv = MLVO_MODEL; break;
- case 'l': mlv = MLVO_LAYOUT; break;
- case 'v': mlv = MLVO_VARIANT; break;
- default: goto error;
+ /* Check for index. */
+ idx = XKB_LAYOUT_INVALID;
+ bool expanded_index = false;
+ if (*i < value.len && str[*i] == '[') {
+ if (mlv != MLVO_LAYOUT && mlv != MLVO_VARIANT) {
+ scanner_err(s, XKB_LOG_MESSAGE_NO_ID,
+ "invalid index in %%-expansion; "
+ "may only index layout or variant");
+ goto error;
}
- /* Check for index. */
- idx = XKB_LAYOUT_INVALID;
- if (i < value.len && str[i] == '[') {
- int consumed;
-
- if (mlv != MLVO_LAYOUT && mlv != MLVO_VARIANT) {
- scanner_err(s, XKB_LOG_MESSAGE_NO_ID,
- "invalid index in %%-expansion; "
- "may only index layout or variant");
- goto error;
- }
-
- consumed = extract_layout_index(str + i, value.len - i, &idx);
- if (consumed == -1) goto error;
- i += consumed;
+ int consumed = extract_layout_index(str + (*i), value.len - (*i), &idx);
+ if (consumed == -1) goto error;
+ if (idx == XKB_LAYOUT_INVALID) {
+ /* %i encountered */
+ idx = layout_idx;
+ expanded_index = true;
}
+ *i += consumed;
+ }
- /* Check for suffix, if there supposed to be one. */
- if (sfx != 0) {
- if (i >= value.len) goto error;
- if (str[i++] != sfx) goto error;
- }
+ /* Check for suffix, if there supposed to be one. */
+ if (sfx != 0) {
+ if (*i >= value.len) goto error;
+ if (str[(*i)++] != sfx) goto error;
+ }
- /* Get the expanded value. */
- expanded_value = NULL;
+ /* Get the expanded value. */
+ expanded_value = NULL;
- if (mlv == MLVO_LAYOUT) {
- if (idx != XKB_LAYOUT_INVALID &&
- idx < darray_size(m->rmlvo.layouts) &&
- darray_size(m->rmlvo.layouts) > 1)
- expanded_value = &darray_item(m->rmlvo.layouts, idx);
- else if (idx == XKB_LAYOUT_INVALID &&
- darray_size(m->rmlvo.layouts) == 1)
+ if (mlv == MLVO_LAYOUT) {
+ if (idx == XKB_LAYOUT_INVALID) {
+ /* No index provided: match only if single layout */
+ if (darray_size(m->rmlvo.layouts) == 1)
expanded_value = &darray_item(m->rmlvo.layouts, 0);
+ /* Some index provided: expand only if it is %i or
+ * if there are multiple layouts */
+ } else if (idx < darray_size(m->rmlvo.layouts) &&
+ (expanded_index || darray_size(m->rmlvo.layouts) > 1)) {
+ expanded_value = &darray_item(m->rmlvo.layouts, idx);
}
- else if (mlv == MLVO_VARIANT) {
- if (idx != XKB_LAYOUT_INVALID &&
- idx < darray_size(m->rmlvo.variants) &&
- darray_size(m->rmlvo.variants) > 1)
- expanded_value = &darray_item(m->rmlvo.variants, idx);
- else if (idx == XKB_LAYOUT_INVALID &&
- darray_size(m->rmlvo.variants) == 1)
+ }
+ else if (mlv == MLVO_VARIANT) {
+ if (idx == XKB_LAYOUT_INVALID) {
+ /* No index provided: match only if single variant */
+ if (darray_size(m->rmlvo.variants) == 1)
expanded_value = &darray_item(m->rmlvo.variants, 0);
+ /* Some index provided: expand only if it is %i or
+ * if there are multiple variants */
+ } else if (idx < darray_size(m->rmlvo.variants) &&
+ (expanded_index || darray_size(m->rmlvo.variants) > 1)) {
+ expanded_value = &darray_item(m->rmlvo.variants, idx);
}
- else if (mlv == MLVO_MODEL) {
- expanded_value = &m->rmlvo.model;
- }
+ }
+ else if (mlv == MLVO_MODEL) {
+ expanded_value = &m->rmlvo.model;
+ }
- /* If we didn't get one, skip silently. */
- if (!expanded_value || expanded_value->sval.len == 0)
- continue;
-
- if (pfx != 0)
- darray_appends_nullterminate(expanded, &pfx, 1);
- darray_appends_nullterminate(expanded,
- expanded_value->sval.start,
- expanded_value->sval.len);
- if (sfx != 0)
- darray_appends_nullterminate(expanded, &sfx, 1);
- expanded_value->matched = true;
+ /* If we didn't get one, skip silently. */
+ if (!expanded_value || expanded_value->sval.len == 0) {
+ return true;
+ }
+
+ if (pfx != 0)
+ darray_appends_nullterminate(*expanded, &pfx, 1);
+ darray_appends_nullterminate(*expanded,
+ expanded_value->sval.start,
+ expanded_value->sval.len);
+ if (sfx != 0)
+ darray_appends_nullterminate(*expanded, &sfx, 1);
+ expanded_value->matched = true;
+
+ return true;
+
+error:
+ scanner_err(s, XKB_LOG_MESSAGE_NO_ID,
+ "invalid %%-expansion in value; not used");
+ return false;
+}
+
+/*
+ * This function performs %-expansion on @value (see overview above),
+ * and appends the result to @to.
+ */
+static bool
+append_expanded_kccgst_value(struct matcher *m, struct scanner *s,
+ darray_char *to, struct sval value,
+ xkb_layout_index_t layout_idx)
+{
+ const char *str = value.start;
+ darray_char expanded = darray_new();
+ char ch;
+ bool expanded_plus, to_plus;
+
+ for (unsigned i = 0; i < value.len; ) {
+ /* Check if that's a start of an expansion */
+ switch (str[i]) {
+ /* Expansion */
+ case '%':
+ if (++i >= value.len ||
+ !expand_rmlvo_in_kccgst_value(m, s, value, layout_idx,
+ &expanded, &i))
+ goto error;
+ break;
+ /* New item */
+ case MERGE_OVERRIDE_PREFIX:
+ case MERGE_AUGMENT_PREFIX:
+ darray_appends_nullterminate(expanded, &str[i++], 1);
+ break;
+ /* Just a normal character. */
+ default:
+ darray_appends_nullterminate(expanded, &str[i++], 1);
+ }
}
/*
@@ -918,17 +1123,19 @@ append_expanded_kccgst_value(struct matcher *m, struct scanner *s,
to_plus = is_merge_mode_prefix(ch);
if (expanded_plus || darray_empty(*to))
- darray_appends_nullterminate(*to, expanded.item, expanded.size);
+ darray_appends_nullterminate(*to,
+ darray_items(expanded),
+ darray_size(expanded));
else if (to_plus)
- darray_prepends_nullterminate(*to, expanded.item, expanded.size);
+ darray_prepends_nullterminate(*to,
+ darray_items(expanded),
+ darray_size(expanded));
darray_free(expanded);
return true;
error:
darray_free(expanded);
- scanner_err(s, XKB_LOG_MESSAGE_NO_ID,
- "invalid %%-expansion in value; not used");
return false;
}
@@ -947,6 +1154,10 @@ matcher_rule_verify(struct matcher *m, struct scanner *s)
static void
matcher_rule_apply_if_matches(struct matcher *m, struct scanner *s)
{
+ /* Initial candidates (used if m->mapping.has_layout_idx_range == true) */
+ xkb_layout_mask_t candidate_layouts = m->mapping.layouts_candidates_mask;
+ xkb_layout_index_t idx;
+ /* Loop over MLVO pattern components */
for (unsigned i = 0; i < m->mapping.num_mlvo; i++) {
enum rules_mlvo mlvo = m->mapping.mlvo_at_pos[i];
struct sval value = m->rule.mlvo_value_at_pos[i];
@@ -962,20 +1173,44 @@ matcher_rule_apply_if_matches(struct matcher *m, struct scanner *s)
matched = match_value_and_mark(m, value, to, match_type,
WILDCARD_MATCH_ALL);
}
+#define process_component(_component, m, value, idx, candidate_layouts, to, \
+ match_type, matched) \
+ if (m->mapping.has_layout_idx_range) { \
+ /* Special index: loop over the index range */ \
+ for (idx = m->mapping.layout_idx_min; \
+ idx < m->mapping.layout_idx_max; \
+ idx++) \
+ { \
+ /* Process only if index not skipped */ \
+ xkb_layout_mask_t mask = 1u << idx; \
+ if (candidate_layouts & mask) { \
+ to = &darray_item(m->rmlvo._component, idx); \
+ if (match_value_and_mark(m, value, to, match_type, \
+ WILDCARD_MATCH_NONEMPTY)) { \
+ /* Mark matched, keep index */ \
+ matched = true; \
+ } else { \
+ /* Not matched, remove index */ \
+ candidate_layouts &= ~mask; \
+ } \
+ } \
+ } \
+ } else { \
+ /* Numeric index or no index */ \
+ to = &darray_item(m->rmlvo._component, \
+ m->mapping.layout_idx_min); \
+ matched = match_value_and_mark(m, value, to, match_type, \
+ WILDCARD_MATCH_NONEMPTY); \
+ }
else if (mlvo == MLVO_LAYOUT) {
- xkb_layout_index_t idx = m->mapping.layout_idx;
- idx = (idx == XKB_LAYOUT_INVALID ? 0 : idx);
- to = &darray_item(m->rmlvo.layouts, idx);
- matched = match_value_and_mark(m, value, to, match_type,
- WILDCARD_MATCH_NONEMPTY);
+ process_component(layouts, m, value, idx, candidate_layouts, to,
+ match_type, matched)
}
else if (mlvo == MLVO_VARIANT) {
- xkb_layout_index_t idx = m->mapping.variant_idx;
- idx = (idx == XKB_LAYOUT_INVALID ? 0 : idx);
- to = &darray_item(m->rmlvo.variants, idx);
- matched = match_value_and_mark(m, value, to, match_type,
- WILDCARD_MATCH_NONEMPTY);
+ process_component(variants, m, value, idx, candidate_layouts, to,
+ match_type, matched)
}
+#undef process_component
else if (mlvo == MLVO_OPTION) {
darray_foreach(to, m->rmlvo.options) {
matched = match_value_and_mark(m, value, to, match_type,
@@ -989,10 +1224,29 @@ matcher_rule_apply_if_matches(struct matcher *m, struct scanner *s)
return;
}
- for (unsigned i = 0; i < m->mapping.num_kccgst; i++) {
- enum rules_kccgst kccgst = m->mapping.kccgst_at_pos[i];
- struct sval value = m->rule.kccgst_value_at_pos[i];
- append_expanded_kccgst_value(m, s, &m->kccgst[kccgst], value);
+ if (m->mapping.has_layout_idx_range) {
+ /* Special index: loop over the index range */
+ for (idx = m->mapping.layout_idx_min;
+ idx < m->mapping.layout_idx_max;
+ idx++)
+ {
+ if (candidate_layouts & (1u << idx)) {
+ for (unsigned i = 0; i < m->mapping.num_kccgst; i++) {
+ enum rules_kccgst kccgst = m->mapping.kccgst_at_pos[i];
+ struct sval value = m->rule.kccgst_value_at_pos[i];
+ append_expanded_kccgst_value(m, s, &m->kccgst[kccgst],
+ value, idx);
+ }
+ }
+ }
+ } else {
+ /* Numeric index or no index */
+ for (unsigned i = 0; i < m->mapping.num_kccgst; i++) {
+ enum rules_kccgst kccgst = m->mapping.kccgst_at_pos[i];
+ struct sval value = m->rule.kccgst_value_at_pos[i];
+ append_expanded_kccgst_value(m, s, &m->kccgst[kccgst], value,
+ m->mapping.layout_idx_min);
+ }
}
/*
@@ -1000,8 +1254,9 @@ matcher_rule_apply_if_matches(struct matcher *m, struct scanner *s)
* skipped. However, rule sets matching against options may contain
* several legitimate rules, so they are processed entirely.
*/
- if (!(m->mapping.defined_mlvo_mask & (1 << MLVO_OPTION)))
- m->mapping.skip = true;
+ if (!(is_mlvo_mask_defined(m, MLVO_OPTION))) {
+ m->mapping.layouts_candidates_mask &= ~candidate_layouts;
+ }
}
static enum rules_token
@@ -1087,7 +1342,7 @@ matcher_match(struct matcher *m, struct scanner *s,
mapping_mlvo:
switch (tok = gettok(m, s)) {
case TOK_IDENTIFIER:
- if (!m->mapping.skip)
+ if (m->mapping.active)
matcher_mapping_set_mlvo(m, s, m->val.string);
goto mapping_mlvo;
case TOK_EQUALS:
@@ -1099,12 +1354,12 @@ matcher_match(struct matcher *m, struct scanner *s,
mapping_kccgst:
switch (tok = gettok(m, s)) {
case TOK_IDENTIFIER:
- if (!m->mapping.skip)
+ if (m->mapping.active)
matcher_mapping_set_kccgst(m, s, m->val.string);
goto mapping_kccgst;
case TOK_END_OF_LINE:
- if (!m->mapping.skip)
- matcher_mapping_verify(m, s);
+ if (m->mapping.active && matcher_mapping_verify(m, s))
+ matcher_mapping_set_layout_bounds(m);
goto rule_mlvo_first;
default:
goto unexpected;
diff --git a/src/xkbcomp/rules.h b/src/xkbcomp/rules.h
index 5381b156..46b754c9 100644
--- a/src/xkbcomp/rules.h
+++ b/src/xkbcomp/rules.h
@@ -29,4 +29,15 @@ xkb_components_from_rules(struct xkb_context *ctx,
const struct xkb_rule_names *rmlvo,
struct xkb_component_names *out);
+/* Maximum length of a layout index string:
+ * [NOTE] Currently XKB_MAX_GROUPS is 4, but the following code is
+ * future-proof for all possible indexes.
+ *
+ * length = ceiling (bitsize(xkb_layout_index_t) * logBase 10 2)
+ * < ceiling (bitsize(xkb_layout_index_t) * 5 / 16)
+ * < 1 + floor (bitsize(xkb_layout_index_t) * 5 / 16)
+ */
+#define MAX_LAYOUT_INDEX_STR_LENGTH \
+ (1 + ((sizeof(xkb_layout_index_t) * CHAR_BIT * 5) >> 4))
+
#endif
diff --git a/test/data/.editorconfig b/test/data/.editorconfig
new file mode 100644
index 00000000..329c45c4
--- /dev/null
+++ b/test/data/.editorconfig
@@ -0,0 +1,8 @@
+[*]
+charset = utf-8
+end_of_line = lf
+trim_trailing_whitespace = true
+insert_final_newline = true
+indent_style = tab
+indent_size = 8
+max_line_length = 80
diff --git a/test/data/rules/evdev-modern b/test/data/rules/evdev-modern
new file mode 100644
index 00000000..ea334788
--- /dev/null
+++ b/test/data/rules/evdev-modern
@@ -0,0 +1,715 @@
+// DO NOT EDIT THIS FILE - IT WAS AUTOGENERATED BY merge.py FROM rules/*.part
+//
+//
+// Rules for resolving XKB components for use with XFree86
+// Copyright 1996 by Joseph Moss
+//
+// 2002 Modifier: Ivan Pascal The XFree86 Project
+//
+
+// If you want non-latin layouts implicitly include the en_US layout
+// uncomment lines below
+//! $nonlatin = am ara ben bd bg bt by cs deva ge gh gr guj guru il \
+// in ir iku jp kan kh kr la lao lk mk mm mn mv mal olck \
+// ori pk ru scc sy syr tel th tj tam ua uz
+
+// PC models
+! $pcmodels = pc86 pc101 pc102 pc104 pc104alt pc105
+
+// Jolla devices and keyboards
+! $jollamodels = jollasbj
+
+// Microsoft models (using MS geometry)
+! $msmodels = microsoft microsoft4000 microsoft7000 microsoftpro microsoftprousb microsoftprose microsoftsurface
+
+// Nokia devices and keyboards
+! $nokiamodels = nokiasu8w nokiarx44 nokiarx51
+
+// TypeMatrix geometries
+! $tmgeometries = tm2020 tm2030PS2 tm2030USB tm2030USB-102 tm2030USB-106
+
+// Layouts that provide further specializations for the OLPC
+! $olpclayouts = af am ara br ca es et fr it kh kz in mn np ru th tr us
+
+! $macbooks = macbook78 macbook79
+! $maclaptop = ibook powerbook macbook78 macbook79
+! $applealu = applealu_ansi applealu_iso applealu_jis
+! $macs = macintosh macintosh_old ibook powerbook macbook78 macbook79
+
+! $macvendorlayouts = ch de dk fi fr gb is it latam nl no pt se us
+
+! $azerty = be fr
+! $qwertz = al cz de hr hu ro si sk
+
+
+// all layouts with 3rd and 4th groups
+! $threelevellayouts = al az \
+ be br bt \
+ ca ch cs cz \
+ de dk \
+ ee es \
+ fi fo fr \
+ gb gr \
+ hu \
+ ie ir is it \
+ latam \
+ lk lt \
+ mn mt \
+ nl no \
+ pl pt \
+ ro \
+ se sk \
+ tr \
+ us \
+ vn \
+ za
+
+! $thinkpads = thinkpad thinkpad60 thinkpadz60
+
+! $sun = sun_type6_jp sun_type6_usb sun_type6_euro_usb \
+ sun_type6_jp_usb sun_type6_unix_usb sun_type7_jp_usb \
+ sun_type7_usb sun_type7_euro_usb sun_type7_unix_usb
+
+! $sun_jp = sun_type6_jp sun_type6_jp_usb sun_type7_jp_usb
+
+// Sun Type_6_7 keyboards with custom layouts
+! $sun_custom = ara be br ca ch cz de dk \
+ ee es fi fr gb gr it jp \
+ kr lt lv nl no pl pt ro \
+ ru se sk tr tw ua us
+
+! $sun_var = sun_type6 sun_type6_suncompat sun_type6_de sun_type6_fr \
+ sun_type7 sun_type7_suncompat suncompat
+
+! $sun_compat = sun_type6 sun_type6_suncompat sun_type7_suncompat suncompat
+
+
+! $evdevkbds = ibm_spacesaver
+
+! $dvoraklayouts = br ca de ee es fr gb no pl se us
+
+! model = keycodes
+ applealu_jis = evdev+macintosh(jisevdev)
+ $jollamodels = evdev+jolla(jolla)
+ olpc = evdev+olpc(olpc)
+ olpcm = evdev+olpc(olpcm)
+ * = evdev
+
+! layout[1] = keycodes
+ $azerty = +aliases(azerty)
+ $qwertz = +aliases(qwertz)
+ * = +aliases(qwerty)
+
+! layout = keycodes
+ $azerty = +aliases(azerty)
+ $qwertz = +aliases(qwertz)
+ * = +aliases(qwerty)
+
+! option = keycodes
+
+! model layout = geometry
+ thinkpad us = thinkpad(us)
+
+! model = geometry
+ microsoftelite = microsoft(elite)
+ $msmodels = microsoft(natural)
+ dell101 = dell(dell101)
+ dellm65 = dell(dellm65)
+ latitude = dell(latitude)
+ flexpro = keytronic(FlexPro)
+ hp6000 = hp(omnibook)
+ hpmini110 = hp(mini110)
+ hpdv5 = hp(dv5)
+ omnikey101 = northgate(omnikey101)
+ sanwaskbkg3 = sanwa(sanwaskbkg3)
+ $pcmodels = pc(%m)
+ everex = everex(STEPnote)
+ thinkpad = thinkpad(intl)
+ thinkpad60 = thinkpad(60)
+ thinkpadz60 = thinkpad(60)
+ apex300 = steelseries(apex300)
+ $tmgeometries = typematrix(%m)
+ winbook = winbook(XP5)
+ pc98 = nec(pc98)
+ $applealu = macintosh(%m)
+ $macbooks = macintosh(%m)
+ $macs = macintosh(macintosh)
+ hhk = hhk(basic)
+ kinesis = kinesis(model100)
+ $nokiamodels = nokia(%m)
+ sun_type6_jp = sun(type6jp)
+ sun_type6_usb = sun(type6)
+ sun_type6_euro_usb = sun(type6tuv)
+ sun_type6_jp_usb = sun(type6jp)
+ sun_type6_unix_usb = sun(type6unix)
+ sun_type7_jp_usb = sun(type6jp)
+ sun_type7_usb = sun(type7)
+ sun_type7_euro_usb = sun(type7tuv)
+ sun_type7_unix_usb = sun(type7unix)
+ * = pc(pc104)
+
+! model layout[first] variant[first] = symbols
+ * ben basic = pc+in(ben)
+ * ben probhat = pc+in(ben_probhat)
+ * dev basic = pc+in(deva)
+ * dvorak $dvoraklayouts = pc+%v(dvorak)
+ * dvorak basic = pc+us(dvorak)
+ * dvorak pl_basic = pc+pl(dvorak)
+ * dvorak pl = pc+pl(dvorak_quotes)
+ * dvorak pl_altquotes = pc+pl(dvorak_altquotes)
+ * dzdwi basic = pc+bt(basic)
+ * fi basic = pc+fi(classic)
+ * ge azerty_tskapo = pc+fr(geo)
+ * guj basic = pc+in(guj)
+ * gur basic = pc+in(guru)
+ * ie laptop = pc+ie(basic)
+ * ie CloGaelachLaptop = pc+ie(CloGaelach)
+ * in urd = pc+in(urd-phonetic)
+ * iu basic = pc+ca(ike)
+ * lo basic = pc+la(basic)
+ * kan basic = pc+in(kan)
+ * mal basic = pc+in(mal)
+ * mal mlplusnum = pc+in(mal)
+ * ogham basic = pc+ie(ogam)
+ * ogham laptop = pc+ie(ogam)
+ * ogham is434 = pc+ie(ogam_is434)
+ * ogham is434laptop = pc+ie(ogam_is434)
+ * ori basic = pc+in(ori)
+ * ro de = pc+ro(winkeys)
+ * ro us = pc+ro(std)
+ * ro academic = pc+ro(std)
+ * ro std_comma = pc+ro(std)
+ * ro comma = pc+ro(basic)
+ * ru os = pc+ru(os_legacy)
+ * pk urd = pc+pk(urd-phonetic)
+ * sapmi basic = pc+no(smi)
+ * sapmi nodeadkeys = pc+no(smi_nodeadkeys)
+ * sapmi sefi = pc+fi(smi)
+ * sin phonetic-static = pc+in(sin_phonetic)
+ * syr basic = pc+sy(syc)
+ * syr phonetic = pc+sy(syc_phonetic)
+ * tam INSCRIPT = pc+in(tam)
+ * tam UNI = pc+in(tam_unicode)
+ * tam NUMERAL-KEYBOARD = pc+in(tam_keyboard_with_numerals)
+ * tam TAB = pc+in(tam_TAB)
+ * tam TSCII = pc+in(tam_TSCII)
+ * tel basic = pc+in(tel)
+ * yu basic = pc+srp(latin)
+ * yu unicode = pc+srp(latinunicode)
+ * yu yz = pc+srp(latinyz)
+ * yu unicodeyz = pc+srp(latinunicodeyz)
+ classmate us intl = pc+us(classmate-intl)
+ classmate us alt-intl = pc+us(classmate-alt-intl)
+ classmate us altgr-intl = pc+us(classmate-altgr-intl)
+ nokiarx51 cz qwerty = nokia_vndr/rx-51(cz_qwerty)
+ * $sun_custom $sun_var = pc+sun_vndr/%l%(v)
+
+! model layout[first] = symbols
+ * ar = pc+ara
+ * ben = pc+in(ben)
+ * bs = pc+ba
+ * cs = pc+rs
+ * cz_qwerty = pc+cz(qwerty)
+ * dev = pc+in(deva)
+ * dvorak = pc+us(dvorak)
+ * dzdwi = pc+bt
+ * el = pc+gr
+ * en_US = pc+latin
+ * guj = pc+in(guj)
+ * gur = pc+in(guru)
+ * iu = pc+ca(ike)
+ * lo = pc+la
+ * kan = pc+in(kan)
+ * mi = pc+mao
+ * ogham = pc+ie(ogam)
+ * ori = pc+ie(ori)
+ * sapmi = pc+no(smi)
+ * sr = pc+srp
+ * syr = pc+sy(syc)
+ * tel = pc+in(tel)
+ * tml = pc+in(tam)
+ * yu = pc+srp
+ * fr-latin9 = pc+fr(latin9)
+ * us_intl = pc+us(alt-intl)
+ * ben(basic) = pc+in(ben)
+ * ben(probhat) = pc+in(ben_probhat)
+ * dev(basic) = pc+in(deva)
+ * dvorak($dvoraklayouts) = pc+%v(dvorak)
+ * dvorak(basic) = pc+us(dvorak)
+ * dvorak(pl_basic) = pc+pl(dvorak)
+ * dvorak(pl) = pc+pl(dvorak_quotes)
+ * dvorak(pl_altquotes) = pc+pl(dvorak_altquotes)
+ * dzdwi(basic) = pc+bt(basic)
+ * fi(basic) = pc+fi(classic)
+ * ge(azerty_tskapo) = pc+fr(geo)
+ * guj(basic) = pc+in(guj)
+ * gur(basic) = pc+in(guru)
+ * ie(laptop) = pc+ie(basic)
+ * ie(CloGaelachLaptop) = pc+ie(CloGaelach)
+ * in(urd) = pc+in(urd-phonetic)
+ * iu(basic) = pc+ca(ike)
+ * lo(basic) = pc+la(basic)
+ * kan(basic) = pc+in(kan)
+ * mal(basic) = pc+in(mal)
+ * mal(mlplusnum) = pc+in(mal)
+ * ogham(basic) = pc+ie(ogam)
+ * ogham(laptop) = pc+ie(ogam)
+ * ogham(is434) = pc+ie(ogam_is434)
+ * ogham(is434laptop) = pc+ie(ogam_is434)
+ * ori(basic) = pc+in(ori)
+ * ro(de) = pc+ro(winkeys)
+ * ro(us) = pc+ro(std)
+ * ro(academic) = pc+ro(std)
+ * ro(std_comma) = pc+ro(std)
+ * ro(comma) = pc+ro(basic)
+ * ru(os) = pc+ru(os_legacy)
+ * pk(urd) = pc+pk(urd-phonetic)
+ * sapmi(basic) = pc+no(smi)
+ * sapmi(nodeadkeys) = pc+no(smi_nodeadkeys)
+ * sapmi(sefi) = pc+fi(smi)
+ * sin(phonetic-static) = pc+in(sin_phonetic)
+ * syr(basic) = pc+sy(syc)
+ * syr(phonetic) = pc+sy(syc_phonetic)
+ * tam(INSCRIPT) = pc+in(tam)
+ * tam(UNI) = pc+in(tam_unicode)
+ * tam(NUMERAL-KEYBOARD) = pc+in(tam_keyboard_with_numerals)
+ * tam(TAB) = pc+in(tam_TAB)
+ * tam(TSCII) = pc+in(tam_TSCII)
+ * tel(basic) = pc+in(tel)
+ * yu(basic) = pc+srp(latin)
+ * yu(unicode) = pc+srp(latinunicode)
+ * yu(yz) = pc+srp(latinyz)
+ * yu(unicodeyz) = pc+srp(latinunicodeyz)
+
+! model layout = symbols
+ ataritt $nonlatin = xfree68_vndr/ataritt(us)+%l[%i]%(v[%i]):2
+ ataritt * = xfree68_vndr/ataritt(us)+%l[%i]%(v[%i])
+ amiga $nonlatin = xfree68_vndr/amiga(usa1)+%l[%i]%(v[%i]):2
+ amiga * = xfree68_vndr/amiga(usa1)+%l[%i]%(v[%i])
+ classmate us = pc+%l[%i](classmate)
+ empty * = empty(basic)
+ * empty = empty(basic)
+ jollasbj $nonlatin = jolla_vndr/sbj(common)+us+%l[%i]%(v[%i]):2
+ jollasbj * = jolla_vndr/sbj(common)+%l[%i]%(v[%i])
+ $sun $sun_custom = pc+sun_vndr/%l[%i]%(v[%i])
+ pc98 nec_vndr/jp = nec_vndr/jp(pc98)
+ macintosh_old us = macintosh_vndr/us(oldmac)
+ macintosh_old en_US = macintosh_vndr/us(oldmac)
+ macintosh_old $macvendorlayouts = macintosh_vndr/us(oldmac)+macintosh_vndr/%l%(v)
+ macintosh_old $nonlatin = macintosh_vndr/us(oldmac)+%l[%i]%(v[%i]):2
+ macintosh_old * = macintosh_vndr/us(oldmac)+%l[%i]%(v[%i])
+ applealu_jis jp = macintosh_vndr/apple(alukbd)+macintosh_vndr/jp(usmac)+macintosh_vndr/jp(mac):2
+ applealu_jis * = macintosh_vndr/apple(alukbd)+%l[%i]%(v[%i])+macintosh_vndr/jp(mac):2
+ $applealu $macvendorlayouts = macintosh_vndr/apple(alukbd)+macintosh_vndr/%l[%i]%(v[%i])
+ $applealu * = macintosh_vndr/apple(alukbd)+%l[%i]%(v[%i])
+ $macs en_US = pc+macintosh_vndr/us(extended)
+ $macs $macvendorlayouts = pc+macintosh_vndr/%l[%i]%(v[%i])
+ nokiarx44 * = nokia_vndr/rx-44(%l[%i])
+ nokiarx51 cz(qwerty) = nokia_vndr/rx-51(common)+nokia_vndr/rx-51(cz_qwerty)
+ nokiarx51 * = nokia_vndr/rx-51(common)+nokia_vndr/rx-51(%l[%i]%_v[%i])
+ nokiasu8w * = nokia_vndr/su-8w(%l[%i])
+ olpc $olpclayouts = olpc+%l[%i]%(m)
+ olpc * = olpc+%l[%i]%(v[%i])
+ olpcm $olpclayouts = olpc+%l[%i]%(m)
+ olpcm * = olpc+%l[%i]%(v[%i])
+ $thinkpads br = pc+br(thinkpad)
+ sl-c3x00 * = pc+sharp_vndr/sl-c3x00(basic)
+ ws003sh * = pc+sharp_vndr/ws003sh(basic)
+ ws007sh * = pc+sharp_vndr/ws007sh(basic)
+ ws011sh * = pc+sharp_vndr/ws011sh(basic)
+ ws020sh * = pc+sharp_vndr/ws020sh(basic)
+ * $nonlatin = pc+us+%l[%i]%(v[%i]):2
+
+! model layout[first] = symbols
+ * * = pc+%l[%i]%(v[%i])
+
+! model layout[later] = symbols
+ * ar = +ara%(v[%i]):%i
+ * ben = +in(ben):%i
+ * bs = +ba%(v[%i]):%i
+ * cs = +rs%(v[%i]):%i
+ * cz_qwerty = +cz(qwerty):%i
+ * dev = +in(deva):%i
+ * dvorak = +us(dvorak):%i
+ * dzdwi = +bt%(v[%i]):%i
+ * el = +gr%(v[%i]):%i
+ * en_US = +latin%(v[%i]):%i
+ * guj = +in(guj):%i
+ * gur = +in(guru):%i
+ * iu = +ca(ike):%i
+ * lo = +la%(v[%i]):%i
+ * kan = +in(kan):%i
+ * mi = +mao%(v[%i]):%i
+ * ogham = +ie(ogam):%i
+ * ori = +ie(ori):%i
+ * sapmi = +no(smi):%i
+ * sr = +srp%(v[%i]):%i
+ * syr = +sy(syc):%i
+ * tel = +in(tel):%i
+ * tml = +in(tam):%i
+ * yu = +srp%(v[%i]):%i
+ * fr-latin9 = +fr(latin9):%i
+ * us_intl = +us(alt-intl):%i
+ * ben(basic) = +in(ben):%i
+ * ben(probhat) = +in(ben_probhat):%i
+ * dev(basic) = +in(deva):%i
+ * dvorak($dvoraklayouts) = +%v(dvorak):%i
+ * dvorak(basic) = +us(dvorak):%i
+ * dvorak(pl_basic) = +pl(dvorak):%i
+ * dvorak(pl) = +pl(dvorak_quotes):%i
+ * dvorak(pl_altquotes) = +pl(dvorak_altquotes):%i
+ * dzdwi(basic) = +bt(basic):%i
+ * fi(basic) = +fi(classic):%i
+ * ge(azerty_tskapo) = +fr(geo):%i
+ * guj(basic) = +in(guj):%i
+ * gur(basic) = +in(guru):%i
+ * ie(laptop) = +ie(basic):%i
+ * ie(CloGaelachLaptop) = +ie(CloGaelach):%i
+ * in(urd) = +in(urd-phonetic):%i
+ * iu(basic) = +ca(ike):%i
+ * lo(basic) = +la(basic):%i
+ * kan(basic) = +in(kan):%i
+ * mal(basic) = +in(mal):%i
+ * mal(mlplusnum) = +in(mal):%i
+ * ogham(basic) = +ie(ogam):%i
+ * ogham(laptop) = +ie(ogam):%i
+ * ogham(is434) = +ie(ogam_is434):%i
+ * ogham(is434laptop) = +ie(ogam_is434):%i
+ * ori(basic) = +in(ori):%i
+ * ro(de) = +ro(winkeys):%i
+ * ro(us) = +ro(std):%i
+ * ro(academic) = +ro(std):%i
+ * ro(std_comma) = +ro(std):%i
+ * ro(comma) = +ro(basic):%i
+ * ru(os) = +ru(os_legacy):%i
+ * pk(urd) = +pk(urd-phonetic):%i
+ * sapmi(basic) = +no(smi):%i
+ * sapmi(nodeadkeys) = +no(smi_nodeadkeys):%i
+ * sapmi(sefi) = +fi(smi):%i
+ * sin(phonetic-static) = +in(sin_phonetic):%i
+ * syr(basic) = +sy(syc):%i
+ * syr(phonetic) = +sy(syc_phonetic):%i
+ * tam(INSCRIPT) = +in(tam):%i
+ * tam(UNI) = +in(tam_unicode):%i
+ * tam(NUMERAL-KEYBOARD) = +in(tam_keyboard_with_numerals):%i
+ * tam(TAB) = +in(tam_TAB):%i
+ * tam(TSCII) = +in(tam_TSCII):%i
+ * tel(basic) = +in(tel):%i
+ * yu(basic) = +srp(latin):%i
+ * yu(unicode) = +srp(latinunicode):%i
+ * yu(yz) = +srp(latinyz):%i
+ * yu(unicodeyz) = +srp(latinunicodeyz):%i
+ nokiarx51 cz(qwerty) = +nokia_vndr/rx-51(cz_qwerty):%i
+ nokiarx51 * = +nokia_vndr/rx-51(%l[%i]%_v[%i]):%i
+ $sun $sun_custom = +sun_vndr/%l[%i]%(v[%i]):%i
+ * * = +%l[%i]%(v[%i]):%i
+
+! model layout[later] variant[later] = symbols
+ * ben basic = +in(ben):%i
+ * ben probhat = +in(ben_probhat):%i
+ * dev basic = +in(deva):%i
+ * dvorak $dvoraklayouts = +%v(dvorak):%i
+ * dvorak basic = +us(dvorak):%i
+ * dvorak pl_basic = +pl(dvorak):%i
+ * dvorak pl = +pl(dvorak_quotes):%i
+ * dvorak pl_altquotes = +pl(dvorak_altquotes):%i
+ * dzdwi basic = +bt(basic):%i
+ * fi basic = +fi(classic):%i
+ * ge azerty_tskapo = +fr(geo):%i
+ * guj basic = +in(guj):%i
+ * gur basic = +in(guru):%i
+ * ie laptop = +ie(basic):%i
+ * ie CloGaelachLaptop = +ie(CloGaelach):%i
+ * in urd = +in(urd-phonetic):%i
+ * iu basic = +ca(ike):%i
+ * lo basic = +la(basic):%i
+ * kan basic = +in(kan):%i
+ * mal basic = +in(mal):%i
+ * mal mlplusnum = +in(mal):%i
+ * ogham basic = +ie(ogam):%i
+ * ogham laptop = +ie(ogam):%i
+ * ogham is434 = +ie(ogam_is434):%i
+ * ogham is434laptop = +ie(ogam_is434):%i
+ * ori basic = +in(ori):%i
+ * ro de = +ro(winkeys):%i
+ * ro us = +ro(std):%i
+ * ro academic = +ro(std):%i
+ * ro std_comma = +ro(std):%i
+ * ro comma = +ro(basic):%i
+ * ru os = +ru(os_legacy):%i
+ * pk urd = +pk(urd-phonetic):%i
+ * sapmi basic = +no(smi):%i
+ * sapmi nodeadkeys = +no(smi_nodeadkeys):%i
+ * sapmi sefi = +fi(smi):%i
+ * sin phonetic-static = +in(sin_phonetic):%i
+ * syr basic = +sy(syc):%i
+ * syr phonetic = +sy(syc_phonetic):%i
+ * tam INSCRIPT = +in(tam):%i
+ * tam UNI = +in(tam_unicode):%i
+ * tam NUMERAL-KEYBOARD = +in(tam_keyboard_with_numerals):%i
+ * tam TAB = +in(tam_TAB):%i
+ * tam TSCII = +in(tam_TSCII):%i
+ * tel basic = +in(tel):%i
+ * yu basic = +srp(latin):%i
+ * yu unicode = +srp(latinunicode):%i
+ * yu yz = +srp(latinyz):%i
+ * yu unicodeyz = +srp(latinunicodeyz):%i
+
+! model = symbols
+ $evdevkbds = +inet(evdev)+inet(%m)
+ chromebook = +inet(evdev)+inet(chromebook)
+ applealu_jis = +inet(evdev)+macintosh_vndr/jp(alujiskeys)
+ * = +inet(evdev)
+
+! layout[any] variant[any] = compat
+ de neo = +caps(caps_lock):%i+misc(assign_shift_left_action):%i+level5(level5_lock):%i
+ de adnw = +caps(caps_lock):%i+misc(assign_shift_left_action):%i+level5(level5_lock):%i
+ de koy = +caps(caps_lock):%i+misc(assign_shift_left_action):%i+level5(level5_lock):%i
+ de bone = +caps(caps_lock):%i+misc(assign_shift_left_action):%i+level5(level5_lock):%i
+ de bone_eszett_home = +caps(caps_lock):%i+misc(assign_shift_left_action):%i+level5(level5_lock):%i
+ de neo_qwertz = +caps(caps_lock):%i+misc(assign_shift_left_action):%i+level5(level5_lock):%i
+ de neo_qwerty = +caps(caps_lock):%i+misc(assign_shift_left_action):%i+level5(level5_lock):%i
+ jp $sun_compat = +complete+japan(kana_lock):%i
+
+! model layout[single] = compat
+ pc98 nec_vndr/jp = pc98(basic)
+ * jp = complete+japan
+ olpc * = olpc
+ olpcm * = olpc
+ * * = complete
+
+! model layout[first] = compat
+ * * = complete
+
+! model = types
+ $macs = complete+numpad(mac)
+ $applealu = complete+numpad(mac)
+ $nokiamodels = complete+nokia
+ * = complete
+
+! layout[any] option = symbols
+ $threelevellayouts grp:alts_toggle = +level3(ralt_switch_for_alts_toggle):%i
+ * misc:typo = +typo(base):%i
+ * misc:apl = +apl(level3):%i
+
+! option = symbols
+ grp:shift_toggle = +group(shifts_toggle)
+ altwin:menu = +altwin(menu)
+ altwin:menu_win = +altwin(menu_win)
+ altwin:meta_alt = +altwin(meta_alt)
+ altwin:alt_win = +altwin(alt_win)
+ altwin:ctrl_win = +altwin(ctrl_win)
+ altwin:ctrl_alt_win = +altwin(ctrl_alt_win)
+ altwin:meta_win = +altwin(meta_win)
+ altwin:left_meta_win = +altwin(left_meta_win)
+ altwin:hyper_win = +altwin(hyper_win)
+ altwin:alt_super_win = +altwin(alt_super_win)
+ altwin:swap_lalt_lwin = +altwin(swap_lalt_lwin)
+ altwin:swap_alt_win = +altwin(swap_alt_win)
+ altwin:prtsc_rwin = +altwin(prtsc_rwin)
+ grab:debug = +srvr_ctrl(grab_debug)
+ grp:switch = +group(switch)
+ grp:lswitch = +group(lswitch)
+ grp:win_switch = +group(win_switch)
+ grp:lwin_switch = +group(lwin_switch)
+ grp:rwin_switch = +group(rwin_switch)
+ grp:menu_switch = +group(menu_switch)
+ grp:toggle = +group(toggle)
+ grp:shifts_toggle = +group(shifts_toggle)
+ grp:ctrls_toggle = +group(ctrls_toggle)
+ grp:alts_toggle = +group(alts_toggle)
+ grp:caps_toggle = +capslock(grouplock)
+ grp:caps_switch = +capslock(groupshift)
+ grp:shift_caps_toggle = +group(shift_caps_toggle)
+ grp:shift_caps_switch = +group(shift_caps_switch)
+ grp:win_space_toggle = +group(win_space_toggle)
+ grp:win_menu_switch = +group(win_menu_switch)
+ grp:alt_caps_toggle = +group(alt_caps_toggle)
+ grp:alt_space_toggle = +group(alt_space_toggle)
+ grp:menu_toggle = +group(menu_toggle)
+ grp:lwin_toggle = +group(lwin_toggle)
+ grp:rwin_toggle = +group(rwin_toggle)
+ grp:lshift_toggle = +group(lshift_toggle)
+ grp:rshift_toggle = +group(rshift_toggle)
+ grp:rctrl_switch = +group(rctrl_switch)
+ grp:lctrl_toggle = +group(lctrl_toggle)
+ grp:rctrl_toggle = +group(rctrl_toggle)
+ grp:lalt_toggle = +group(lalt_toggle)
+ grp:sclk_toggle = +group(sclk_toggle)
+ grp:lctrl_rctrl_switch = +group(lctrl_rctrl_switch)
+ grp:lctrl_lwin_rctrl_menu = +group(lctrl_lwin_rctrl_menu)
+ grp:lctrl_lalt_toggle = +group(lctrl_lalt_toggle)
+ grp:rctrl_ralt_toggle = +group(rctrl_ralt_toggle)
+ grp:ctrl_alt_toggle = +group(ctrl_alt_toggle)
+ grp:ctrl_alt_toggle_bidir = +group(ctrl_alt_toggle_bidir)
+ grp:lctrl_lshift_toggle = +group(lctrl_lshift_toggle)
+ grp:rctrl_rshift_toggle = +group(rctrl_rshift_toggle)
+ grp:ctrl_shift_toggle = +group(ctrl_shift_toggle)
+ grp:ctrl_shift_toggle_bidir = +group(ctrl_shift_toggle_bidir)
+ grp:lalt_lshift_toggle = +group(lalt_lshift_toggle)
+ grp:ralt_rshift_toggle = +group(ralt_rshift_toggle)
+ grp:alt_shift_toggle = +group(alt_shift_toggle)
+ grp:alt_shift_toggle_bidir = +group(alt_shift_toggle_bidir)
+ grp:lctrl_lwin_toggle = +group(lctrl_lwin_toggle)
+ grp:menu_latch_group2 = +group(menu_latch_group2)
+ grp:menu_latch_group2_lock = +group(menu_latch_group2_lock)
+ grp:menu_latch = +group(menu_latch)
+ grp:menu_latch_lock = +group(menu_latch_lock)
+ grp:menu_latch_negative = +group(menu_latch_negative)
+ grp:menu_latch_negative_lock = +group(menu_latch_negative_lock)
+ lv3:switch = +level3(switch)
+ lv3:ralt_switch = +level3(ralt_switch)
+ lv3:ralt_switch_multikey = +level3(ralt_switch_multikey)
+ lv3:ralt_alt = +level3(ralt_alt)
+ lv3:lalt_switch = +level3(lalt_switch)
+ lv3:alt_switch = +level3(alt_switch)
+ lv3:menu_switch = +level3(menu_switch)
+ lv3:win_switch = +level3(win_switch)
+ lv3:lwin_switch = +level3(lwin_switch)
+ lv3:rwin_switch = +level3(rwin_switch)
+ lv3:enter_switch = +level3(enter_switch)
+ lv3:4_switch_isolated = +level3(4_switch_isolated)
+ lv3:9_switch_isolated = +level3(9_switch_isolated)
+ caps:capslock = +capslock(capslock)
+ caps:numlock = +capslock(numlock)
+ caps:shiftlock = +capslock(shiftlock)
+ caps:swapescape = +capslock(swapescape)
+ caps:escape = +capslock(escape)
+ caps:escape_shifted_capslock = +capslock(escape_shifted_capslock)
+ caps:backspace = +capslock(backspace)
+ caps:super = +capslock(super)
+ caps:hyper = +capslock(hyper)
+ caps:menu = +capslock(menu)
+ caps:none = +capslock(none)
+ caps:ctrl_modifier = +capslock(ctrl_modifier)
+ ctrl:nocaps = +ctrl(nocaps)
+ ctrl:lctrl_meta = +ctrl(lctrl_meta)
+ ctrl:swapcaps = +ctrl(swapcaps)
+ ctrl:swapcaps_hyper = +ctrl(swapcaps_hyper)
+ ctrl:swapcaps_and_switch_layout = +ctrl(swapcaps_and_switch_layout)
+ ctrl:ac_ctrl = +ctrl(ac_ctrl)
+ ctrl:aa_ctrl = +ctrl(aa_ctrl)
+ ctrl:rctrl_ralt = +ctrl(rctrl_ralt)
+ ctrl:menu_rctrl = +ctrl(menu_rctrl)
+ ctrl:ralt_rctrl = +ctrl(ralt_rctrl)
+ ctrl:swap_lalt_lctl = +ctrl(swap_lalt_lctl)
+ ctrl:swap_lwin_lctl = +ctrl(swap_lwin_lctl)
+ ctrl:swap_rwin_rctl = +ctrl(swap_rwin_rctl)
+ ctrl:swap_lalt_lctl_lwin = +ctrl(swap_lalt_lctl_lwin)
+ compose:ralt = +compose(ralt)
+ compose:lwin = +compose(lwin)
+ compose:lwin-altgr = +compose(lwin-altgr)
+ compose:rwin = +compose(rwin)
+ compose:rwin-altgr = +compose(rwin-altgr)
+ compose:menu = +compose(menu)
+ compose:menu-altgr = +compose(menu-altgr)
+ compose:lctrl = +compose(lctrl)
+ compose:lctrl-altgr = +compose(lctrl-altgr)
+ compose:rctrl = +compose(rctrl)
+ compose:rctrl-altgr = +compose(rctrl-altgr)
+ compose:caps = +compose(caps)
+ compose:caps-altgr = +compose(caps-altgr)
+ compose:102 = +compose(102)
+ compose:102-altgr = +compose(102-altgr)
+ compose:paus = +compose(paus)
+ compose:prsc = +compose(prsc)
+ compose:sclk = +compose(sclk)
+ srvrkeys:none = +srvr_ctrl(no_srvr_keys)
+ eurosign:e = +eurosign(e)
+ eurosign:2 = +eurosign(2)
+ eurosign:4 = +eurosign(4)
+ eurosign:5 = +eurosign(5)
+ rupeesign:4 = +rupeesign(4)
+ keypad:oss = +keypad(oss)
+ keypad:legacy = +keypad(legacy)
+ keypad:legacy_wang = +keypad(legacy_wang)
+ keypad:oss_wang = +keypad(oss_wang)
+ keypad:future = +keypad(future)
+ keypad:future_wang = +keypad(future_wang)
+ keypad:hex = +keypad(ops)+keypad(hex)
+ keypad:atm = +keypad(ops)+keypad(hex)+keypad(atm)
+ nbsp:none = +nbsp(none)
+ nbsp:level2 = +nbsp(level2)
+ nbsp:level3 = +nbsp(level3)
+ nbsp:level3s = +nbsp(level3s)
+ nbsp:level3n = +nbsp(level3n)
+ nbsp:level4 = +nbsp(level4)
+ nbsp:level4n = +nbsp(level4n)
+ nbsp:level4nl = +nbsp(level4nl)
+ nbsp:zwnj2 = +nbsp(zwnj2)
+ nbsp:zwnj2zwj3 = +nbsp(zwnj2zwj3)
+ nbsp:zwnj2zwj3nb4 = +nbsp(zwnj2zwj3nb4)
+ nbsp:zwnj2nb3 = +nbsp(zwnj2nb3)
+ nbsp:zwnj2nb3s = +nbsp(zwnj2nb3s)
+ nbsp:zwnj2nb3zwj4 = +nbsp(zwnj2nb3zwj4)
+ nbsp:zwnj2nb3nnb4 = +nbsp(zwnj2nb3nnb4)
+ nbsp:zwnj3zwj4 = +nbsp(zwnj3zwj4)
+ japan:nicola_f_bs = +jp(nicola_f_bs)
+ japan:hztg_escape = +jp(hztg_escape)
+ korean:ralt_hangul = +kr(ralt_hangul)
+ korean:rctrl_hangul = +kr(rctrl_hangul)
+ korean:ralt_hanja = +kr(ralt_hanja)
+ korean:rctrl_hanja = +kr(rctrl_hanja)
+ kpdl:dot = +kpdl(dot)
+ kpdl:comma = +kpdl(comma)
+ kpdl:dotoss = +kpdl(dotoss)
+ kpdl:dotoss_latin9 = +kpdl(dotoss_latin9)
+ kpdl:commaoss = +kpdl(commaoss)
+ kpdl:momayyezoss = +kpdl(momayyezoss)
+ kpdl:kposs = +kpdl(kposs)
+ kpdl:semi = +kpdl(semi)
+ shift:breaks_caps = +shift(breaks_caps)
+ esperanto:qwerty = +epo(qwerty)
+ esperanto:dvorak = +epo(dvorak)
+ esperanto:colemak = +epo(colemak)
+ terminate:ctrl_alt_bksp = +terminate(ctrl_alt_bksp)
+ keypad:pointerkeys = +keypad(pointerkeys)
+ apple:alupckeys = +macintosh_vndr/apple(alupckeys)
+ shift:both_capslock = +shift(both_capslock)
+ shift:lshift_both_capslock = +shift(lshift_both_capslock)
+ shift:rshift_both_capslock = +shift(rshift_both_capslock)
+ shift:both_capslock_cancel = +shift(both_capslock_cancel)
+ shift:lshift_both_capslock_cancel = +shift(lshift_both_capslock_cancel)
+ shift:rshift_both_capslock_cancel = +shift(rshift_both_capslock_cancel)
+ shift:both_shiftlock = +shift(both_shiftlock)
+ shift:lshift_both_shiftlock = +shift(lshift_both_shiftlock)
+ shift:rshift_both_shiftlock = +shift(rshift_both_shiftlock)
+ solaris:sun_compat = +sun_vndr/solaris(sun_compat)
+ lv3:caps_switch = +level3(caps_switch)
+ lv3:bksl_switch = +level3(bksl_switch)
+ lv3:lsgt_switch = +level3(lsgt_switch)
+ lv3:caps_switch_latch = +level3(caps_switch_latch)
+ lv3:bksl_switch_latch = +level3(bksl_switch_latch)
+ lv3:lsgt_switch_latch = +level3(lsgt_switch_latch)
+ lv5:lsgt_switch = +level5(lsgt_switch)
+ lv5:ralt_switch = +level5(ralt_switch)
+ lv5:lsgt_switch_lock = +level5(lsgt_switch_lock)
+ lv5:ralt_switch_lock = +level5(ralt_switch_lock)
+ lv5:lwin_switch_lock = +level5(lwin_switch_lock)
+ lv5:rwin_switch_lock = +level5(rwin_switch_lock)
+ lv5:lsgt_switch_lock_cancel = +level5(lsgt_switch_lock_cancel)
+ lv5:ralt_switch_lock_cancel = +level5(ralt_switch_lock_cancel)
+ lv5:lwin_switch_lock_cancel = +level5(lwin_switch_lock_cancel)
+ lv5:rwin_switch_lock_cancel = +level5(rwin_switch_lock_cancel)
+ parens:swap_brackets = +parens(swap_brackets)
+
+
+! option = compat
+ grp_led:num = +lednum(group_lock)
+ grp_led:caps = +ledcaps(group_lock)
+ grp_led:scroll = +ledscroll(group_lock)
+ mod_led:compose = +ledcompose(compose)
+ japan:kana_lock = +japan(kana_lock)
+ caps:shiftlock = +ledcaps(shift_lock)
+ grab:break_actions = +xfree86(grab_break)
+
+
+! option = types
+ caps:internal = +caps(internal)
+ caps:internal_nocancel = +caps(internal_nocancel)
+ caps:shift = +caps(shift)
+ caps:shift_nocancel = +caps(shift_nocancel)
+ numpad:pc = +numpad(pc)
+ numpad:mac = +numpad(mac)
+ numpad:microsoft = +numpad(microsoft)
+ numpad:shift3 = +numpad(shift3)
diff --git a/test/data/rules/special_indexes b/test/data/rules/special_indexes
new file mode 100644
index 00000000..ccb69d78
--- /dev/null
+++ b/test/data/rules/special_indexes
@@ -0,0 +1,70 @@
+! model = keycodes
+ my_model = my_keycodes
+ * = default_keycodes
+
+! layout[single] variant = symbols // valid
+ layout_a my_variant = symbols_a+extra_variant
+
+! layout[single] = symbols
+ layout_a = symbols_A
+
+! layout = symbols
+ layout_b = symbols_B
+ layout_c = symbols_C:%i // valid, but unusual
+ layout_d = symbols_D
+ layout_e = symbols_E
+ * = %l[%i]%(v[%i]) // valid, but unusual
+
+! layout[first] = symbols
+ layout_a = symbols_a:1
+ layout_b = symbols_b:1
+ layout_c = symbols_c:1
+ layout_d = symbols_d:%i // valid, but unusual
+ layout_e = symbols_e:1
+ * = %l[%i]%(v[%i]) // valid, cannot be easily expressed otherwise
+
+! layout[first] = symbols
+ layout_e = %+l // different output if single or multiple layouts
+
+! layout[later] = symbols
+ layout_a = +symbols_x:%i
+ layout_b = +symbols_y:%i
+ * = +%l[%i]%(v[%i]):%i
+
+! layout[any] = symbols
+ layout_c = +symbols_z:%i
+
+! layout[any] variant[any] = symbols
+ * extra = +foo:%i|bar:%i
+
+! layout[1] variant = symbols // invalid mapping
+ * * = +symbols_AAA:%i
+
+! layout variant[1] = symbols // invalid mapping
+ * * = +symbols_BBB:%i
+
+! layout[1] variant[2] = symbols // invalid mapping
+ * * = +symbols_CCC:%i
+
+! layout[any] variant = symbols // invalid mapping
+ * * = +symbols_DDD:%i
+
+! layout variant[any] = symbols // invalid mapping
+ * * = +symbols_EEE:%i
+
+! layout[any] variant[1] = symbols // invalid mapping
+ * * = +symbols_FFF:%i
+
+! layout[any] variant[first] = symbols // invalid mapping
+ * * = +symbols_GGG:%i
+
+! model = types
+ my_model = my_types
+ * = default_types
+
+! model = compat
+ my_model = my_compat
+ * = default_compat
+
+! option = symbols
+ my_option = +extra_option
diff --git a/test/data/rules/special_indexes-limit b/test/data/rules/special_indexes-limit
new file mode 100644
index 00000000..2e85d2dc
--- /dev/null
+++ b/test/data/rules/special_indexes-limit
@@ -0,0 +1,16 @@
+! model = keycodes
+ * = default_keycodes
+
+! layout[first] = symbols
+ * = x:1
+
+! layout[later] = symbols
+ * = +x:%i
+
+! model = types
+ * = default_types
+
+! model = compat
+ * = default_compat
+
+! option = symbols
diff --git a/test/log.c b/test/log.c
index e23ec34d..fdcf9608 100644
--- a/test/log.c
+++ b/test/log.c
@@ -109,9 +109,9 @@ main(void)
log_err(ctx, XKB_LOG_MESSAGE_NO_ID, "third error: %lu\n", 115415UL);
log_vrb(ctx, 0, XKB_LOG_MESSAGE_NO_ID, "third verbose 0\n");
- printf("%s", log_string.item);
+ printf("%s", darray_items(log_string));
- assert(streq(log_string.item,
+ assert(streq(darray_items(log_string),
"warning: first warning: 87\n"
"error: first error: 115415\n"
"warning: first verbose 5\n"
diff --git a/test/rules-file.c b/test/rules-file.c
index e17d1dc4..3968db97 100644
--- a/test/rules-file.c
+++ b/test/rules-file.c
@@ -73,10 +73,10 @@ test_rules(struct xkb_context *ctx, struct test_data *data)
fprintf(stderr, "Received : %s\t%s\t%s\t%s\n",
kccgst.keycodes, kccgst.types, kccgst.compat, kccgst.symbols);
- passed = streq(kccgst.keycodes, data->keycodes) &&
- streq(kccgst.types, data->types) &&
- streq(kccgst.compat, data->compat) &&
- streq(kccgst.symbols, data->symbols);
+ passed = streq_not_null(kccgst.keycodes, data->keycodes) &&
+ streq_not_null(kccgst.types, data->types) &&
+ streq_not_null(kccgst.compat, data->compat) &&
+ streq_not_null(kccgst.symbols, data->symbols);
free(kccgst.keycodes);
free(kccgst.types);
@@ -251,6 +251,115 @@ main(int argc, char *argv[])
assert(test_rules(ctx, &wildcard_data[k]));
}
+ /* Prepare data with too much layouts */
+ char too_much_layouts[(2 + MAX_LAYOUT_INDEX_STR_LENGTH) * (XKB_MAX_GROUPS + 1)] = { 0 };
+ char too_much_symbols[(3 + MAX_LAYOUT_INDEX_STR_LENGTH) * XKB_MAX_GROUPS] = { 0 };
+ size_t i = 0;
+ for (xkb_layout_index_t l = 0; l <= XKB_MAX_GROUPS; l++) {
+ i += snprintf(&too_much_layouts[i], sizeof(too_much_layouts) - i,
+ "x%"PRIu32",", l + 1);
+ }
+ too_much_layouts[--i] = '\0';
+ i = 0;
+ for (xkb_layout_index_t l = 0; l < XKB_MAX_GROUPS; l++) {
+ i += snprintf(&too_much_symbols[i], sizeof(too_much_symbols) - i,
+ "x:%"PRIu32"+", l + 1);
+ }
+ too_much_symbols[--i] = '\0';
+
+#define ENTRY2(_rules, _model, _layout, _variant, _options, \
+ _keycodes, _types, _compat, _symbols, _should_fail) \
+ { .rules = _rules, .model = _model, \
+ .layout = _layout, .variant = _variant, .options = _options, \
+ .keycodes = _keycodes, \
+ .types = _types, \
+ .compat = _compat, \
+ .symbols = _symbols , .should_fail = _should_fail }
+#define ENTRY(layout, variant, options, symbols, should_fail) \
+ ENTRY2("special_indexes", NULL, layout, variant, options, \
+ "default_keycodes", "default_types", "default_compat", symbols, \
+ should_fail)
+ struct test_data special_indexes_first_data[] = {
+ /* Test index ranges: layout vs layout[first] */
+ ENTRY("layout_a", NULL, NULL, "symbols_A", false),
+ ENTRY("layout_e", NULL, NULL, "symbols_E+layout_e", false),
+ ENTRY("a", NULL, NULL, "a", false),
+ ENTRY("a", "1", NULL, "a(1)", false),
+ /* Test index ranges: invalid layout qualifier */
+ ENTRY("layout_c", NULL, NULL, "symbols_C:1+symbols_z:1", false),
+ /* Test index ranges: invalid layout[first] qualifier */
+ ENTRY("layout_d", NULL, NULL, "symbols_D", false),
+ /* Test index ranges: multiple layouts */
+ ENTRY("a,b", NULL, NULL, "a+b:2", false),
+ ENTRY("a,b", ",c", NULL, "a+b(c):2", false),
+ ENTRY("layout_e,layout_a", NULL, NULL, "symbols_e:1+symbols_x:2", false),
+ ENTRY("layout_a,layout_b,layout_c,layout_d", NULL, NULL,
+ "symbols_a:1+symbols_y:2+layout_c:3+layout_d:4+symbols_z:3", false),
+ ENTRY("layout_a,layout_b,layout_c,layout_d",
+ "extra,,,extra", NULL,
+ "symbols_a:1+symbols_y:2+layout_c:3+layout_d(extra):4+symbols_z:3"
+ "+foo:1|bar:1+foo:4|bar:4", false),
+ /* NOTE: 5 layouts is intentional;
+ * will require update when raising XKB_MAX_LAYOUTS */
+ ENTRY("layout_a,layout_b,layout_c,layout_d,layout_e", NULL, NULL,
+ "symbols_a:1+symbols_y:2+layout_c:3+layout_d:4+symbols_z:3", false),
+#undef ENTRY
+ /* Test index ranges: too much layouts */
+ ENTRY2("special_indexes-limit", NULL, too_much_layouts, NULL, NULL,
+ "default_keycodes", "default_types", "default_compat", too_much_symbols, false),
+#define ENTRY(model, layout, variant, options, compat, symbols, should_fail) \
+ ENTRY2("evdev-modern", model, layout, variant, options, \
+ "evdev+aliases(qwerty)", "complete", compat, symbols, should_fail)
+ /* evdev-modern: 1 layout */
+ ENTRY("whatever", "ar", NULL, NULL, "complete", "pc+ara+inet(evdev)", false),
+ ENTRY("whatever", "ben", "probhat", NULL, "complete", "pc+in(ben_probhat)+inet(evdev)", false),
+ ENTRY("ataritt", "es", NULL, NULL, "complete", "xfree68_vndr/ataritt(us)+es+inet(evdev)", false),
+ ENTRY("ataritt", "jp", NULL, NULL, "complete+japan", "xfree68_vndr/ataritt(us)+jp+inet(evdev)", false),
+ ENTRY2("evdev-modern", "olpc", "us", NULL, NULL,
+ "evdev+olpc(olpc)+aliases(qwerty)", "complete", "olpc", "olpc+us(olpc)+inet(evdev)", false),
+ ENTRY2("evdev-modern", "olpc", "jp", NULL, NULL,
+ "evdev+olpc(olpc)+aliases(qwerty)", "complete", "complete+japan", "olpc+jp+inet(evdev)", false),
+ ENTRY("pc104", "jp", NULL, NULL, "complete+japan", "pc+jp+inet(evdev)", false),
+ ENTRY("pc104", "jp", "xxx", NULL, "complete+japan", "pc+jp(xxx)+inet(evdev)", false),
+ ENTRY("pc104", "es", NULL, NULL, "complete", "pc+es+inet(evdev)", false),
+ ENTRY("pc104", "es", "xxx", NULL, "complete", "pc+es(xxx)+inet(evdev)", false),
+ ENTRY2("evdev-modern", "pc104", "de", "neo", NULL,
+ "evdev+aliases(qwertz)", "complete",
+ "complete+caps(caps_lock):1+misc(assign_shift_left_action):1+level5(level5_lock):1",
+ "pc+de(neo)+inet(evdev)", false),
+ ENTRY("pc104", "br", NULL, "misc:typo,misc:apl", "complete",
+ "pc+br+inet(evdev)+typo(base):1+apl(level3):1", false),
+ /* evdev-modern: 2 layouts */
+ ENTRY("whatever", "ar,pt", NULL, NULL, "complete", "pc+ara+pt:2+inet(evdev)", false),
+ ENTRY("whatever", "pt,ar", NULL, NULL, "complete", "pc+pt+ara:2+inet(evdev)", false),
+ ENTRY("whatever", "ben,gb", "probhat,", NULL, "complete",
+ "pc+in(ben_probhat)+gb:2+inet(evdev)", false),
+ ENTRY("whatever", "gb,ben", ",probhat", NULL, "complete",
+ "pc+gb+in(ben):2+in(ben_probhat):2+inet(evdev)", false),
+ ENTRY("whatever", "ben,ar", "probhat,", NULL, "complete",
+ "pc+in(ben_probhat)+ara:2+inet(evdev)", false),
+ ENTRY("ataritt", "jp,es", NULL, NULL, "complete", "pc+jp+es:2+inet(evdev)", false),
+ ENTRY("ataritt", "es,jp", NULL, NULL, "complete", "pc+es+jp:2+inet(evdev)", false),
+ ENTRY2("evdev-modern", "olpc", "jp,es", NULL, NULL,
+ "evdev+olpc(olpc)+aliases(qwerty)", "complete", "complete", "pc+jp+es:2+inet(evdev)", false),
+ ENTRY2("evdev-modern", "olpc", "es,jp", NULL, NULL,
+ "evdev+olpc(olpc)+aliases(qwerty)", "complete", "complete", "pc+es+jp:2+inet(evdev)", false),
+ ENTRY("pc104", "jp,es", NULL, NULL, "complete", "pc+jp+es:2+inet(evdev)", false),
+ ENTRY("pc104", "jp,es", "xxx,yyy", NULL, "complete", "pc+jp(xxx)+es(yyy):2+inet(evdev)", false),
+ ENTRY("pc104", "latin,jp", NULL, NULL, "complete", "pc+latin+jp:2+inet(evdev)", false),
+ ENTRY("pc104", "latin,jp", "xxx,yyy", NULL, "complete", "pc+latin(xxx)+jp(yyy):2+inet(evdev)", false),
+ ENTRY2("evdev-modern", "pc104", "gb,de", ",neo", NULL,
+ "evdev+aliases(qwerty)", "complete",
+ "complete+caps(caps_lock):2+misc(assign_shift_left_action):2+level5(level5_lock):2",
+ "pc+gb+de(neo):2+inet(evdev)", false),
+ ENTRY("pc104", "ca,br", NULL, "misc:typo,misc:apl", "complete",
+ "pc+ca+br:2+inet(evdev)+typo(base):1+typo(base):2+apl(level3):1+apl(level3):2", false),
+#undef ENTRY
+ };
+ for (size_t k = 0; k < ARRAY_SIZE(special_indexes_first_data); k++) {
+ assert(test_rules(ctx, &special_indexes_first_data[k]));
+ }
+
xkb_context_unref(ctx);
return 0;
}