Skip to content

Commit

Permalink
rules: Add support for :all qualifier
Browse files Browse the repository at this point in the history
Some layout options require to be applied to every group to maintain
consistency (e.g. a group switcher). Currently this must be done manually
for all layout indexes. This is error prone and prevents the increase of
the maximum group count.

This commit introduces the `:all` qualifier for KcCGST values. When a
rule with this qualifier is matched, it will expands the qualified
value (and its optional merge mode) for every layout, e.g.
`+group(toggle):all` (respectively `|group(toggle)`) would expand to
`+group(toggle):1+group(toggle):2` (respectively
`|group(toggle):1|group(toggle):2`) if there are 2 layouts, etc.

If there is no merge mode, it defaults to *override* `+`, e.g. `x:all`
expands to `x:1+x:2+x:3` for 3 layouts.

Note that only the qualified *value* is expanded, e.g. `x+y:all` expands
to `x+y:1+y:2` for 2 layouts.

`:all` can be used in combination with special layout indexes. Since
this can lead to an unexpected behaviour, a warning will be raised.
  • Loading branch information
wismill committed Sep 26, 2024
1 parent 1f9d39e commit 7cdfe09
Show file tree
Hide file tree
Showing 8 changed files with 367 additions and 35 deletions.
5 changes: 3 additions & 2 deletions changes/api/+modern-rules.feature.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ Rules: Added support for special layouts indexes:
`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.
Also added:
- the special index `%i` which correspond to the index of the matched layout.
- the `:all` qualifier: it applies the qualified item to all layouts.

See the [documentation](https://xkbcommon.org/doc/current/rule-file-format.html)
for further information.
131 changes: 113 additions & 18 deletions doc/rules-format.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,8 @@ Kccgst ::= "keycodes" | "symbols" | "types" | "compat" | "geometry"
Rule ::= { MlvoValue } "=" { KccgstValue } "\n"
MlvoValue ::= "*" | GroupName | <ident>
KccgstValue ::= <ident>
KccgstValue ::= <ident> [ { Qualifier } ]
Qualifier ::= ":" ({ NumericIndex } | "all")
```

<!--
Expand Down Expand Up @@ -250,7 +251,7 @@ or %%H seems to do the job though.
</dd>
<dt>
@anchor rules-i-expansion
`%%i`,
`:%%i`,
`%%l[%%i]`,
`%(l[%%i])`,
etc.
Expand All @@ -267,6 +268,61 @@ or %%H seems to do the job though.
instead of <code>(\%v[1])</code>. See @ref rules-symbols-example for
an illustration.

- @anchor rules-all-qualifier
Since version `1.8.0`, if a `Rule` is matched, the `:all` *qualifier* in the
`KccgstValue` applies the qualified value (and its optional merge mode) to all
layouts. If there is is no merge mode, it defaults to *override* `+`.

<table>
<caption>Examples of `:all` qualified use</caption>
<tr>
<th>`KccgstValue`</th>
<th>Layouts count</th>
<th>Final `KccgstValue`</th>
</tr>
<tr>
<td rowspan="2">`x:all`</td>
<td>1</td>
<td>`x:1`</td>
</tr>
<tr>
<td>2</td>
<td>`x:1+x:2`</td>
</tr>
<tr>
<td rowspan="2">`+x:all`</td>
<td>1</td>
<td>`+x:1`</td>
</tr>
<tr>
<td>3</td>
<td>`+x:1+x:2+x:3`</td>
</tr>
<tr>
<td rowspan="2">`|x:all`</td>
<td>1</td>
<td>`|x:1`</td>
</tr>
<tr>
<td>4</td>
<td>`|x:1|x:2|x:3|x:4`</td>
</tr>
<tr>
<td rowspan="2">`x|y:all`</td>
<td>1</td>
<td>`x|y:1`</td>
</tr>
<tr>
<td>3</td>
<td>`x|y:1|y:2|y:3`</td>
</tr>
<tr>
<td>`x:all+y|z:all`</td>
<td>2</td>
<td>`x:1+x:2+y|z:1|z:2`</td>
</tr>
</table>

RMLVO resolution process {#rmlvo-resolution}
------------------------

Expand Down Expand Up @@ -392,22 +448,61 @@ Using the following example:
! layout = symbols
* = pc+%l%(v)

! layout option = symbols
$azerty caps:digits_row = +capslock(digits_row)
* misc:typo = +typo(base)
* lv3:ralt_alt = +level3(ralt_alt)
```
! layout[1] = symbols
* = pc+%l[1]%(v[1])

! layout[2] = symbols
* = +%l[2]%(v[2])
// Repeat the previous rules set with indexes 3 and 4

! layout option = symbols
$azerty caps:digits_row = +capslock(digits_row)
* misc:typo = +typo(base)
* lv3:ralt_alt = +level3(ralt_alt)

! layout[1] option = symbols
$azerty caps:digits_row = +capslock(digits_row):1
* misc:typo = +typo(base):1
* lv3:ralt_alt = +level3(ralt_alt):1
// Repeat the previous rules set for indexes 2 to 4
```

we would have the following resolutions of <em>[symbols]</em>:

| Layout | Option | Symbols |
| ------- | ---------------------------------------- | -------------------------------------------------------- |
| `be` | `caps:digits_row` | `pc+be+capslock(digits_row)` |
| `gb` | `caps:digits_row` | `pc+gb` |
| `fr` | `misc:typo` | `pc+fr+typo(base)` |
| `fr` | `misc:typo,caps:digits_row` | `pc+fr+capslock(digits_row)+typo(base)` |
| `fr` | `lv3:ralt_alt,caps:digits_row,misc:typo` | `pc+fr+capslock(digits_row)+typo(base)+level3(ralt_alt)` |

Note that the configuration with `gb` [layout] has no match for the [option]
and that the order of the [options] in the [RMLVO] configuration has no
influence on the resulting [symbols].
| Layout | Option | Symbols |
| ------- | ---------------------------------------- | ----------------------------------------------------------- |
| `be` | `caps:digits_row` | `pc+be+capslock(digits_row)` |
| `gb` | `caps:digits_row` | `pc+gb` |
| `fr` | `misc:typo` | `pc+fr+typo(base)` |
| `fr` | `misc:typo,caps:digits_row` | `pc+fr+capslock(digits_row)+typo(base)` |
| `fr` | `lv3:ralt_alt,caps:digits_row,misc:typo` | `pc+fr+capslock(digits_row)+typo(base)+level3(ralt_alt)` |
| `fr,gb` | `caps:digits_row,misc:typo` | `pc+fr+gb:2+capslock(digits_row)+typo(base):1+typo(base):2` |

Note that the configuration with `gb` [layout] has no match for the
[option] `caps:digits_row` and that the order of the [options] in the
[RMLVO] configuration has no influence on the resulting [symbols], as it
depends solely on their order in the rules.

Since version `1.8.0`, the previous code can be replaced with simply:

```c
! $azerty = be fr

! layout[first] = symbols
* = pc+%l[%i]%(v[%i])

! layout[later] = symbols
* = +%l[%i]%(v[%i])

! layout[any] option = symbols
$azerty caps:digits_row = +capslock(digits_row):%i

! option = symbols
misc:typo = +typo(base):all
lv3:ralt_alt = +level3(ralt_alt):all

// The previous is equivalent to:
! layout[any] option = symbols
* misc:typo = +typo(base):%i
* lv3:ralt_alt = +level3(ralt_alt):%i
```
24 changes: 15 additions & 9 deletions src/scanner-utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,18 +57,24 @@ struct scanner {
void *priv;
};

#define scanner_log_with_code(scanner, level, log_msg_id, fmt, ...) \
xkb_log_with_code((scanner)->ctx, (level), 0, log_msg_id, \
"%s:%zu:%zu: " fmt "\n", \
(scanner)->file_name, \
(scanner)->token_line, \
#define scanner_log_with_code(scanner, level, verbosity, log_msg_id, fmt, ...) \
xkb_log_with_code((scanner)->ctx, (level), verbosity, log_msg_id, \
"%s:%zu:%zu: " fmt "\n", \
(scanner)->file_name, \
(scanner)->token_line, \
(scanner)->token_column, ##__VA_ARGS__)

#define scanner_err(scanner, id, fmt, ...) \
scanner_log_with_code(scanner, XKB_LOG_LEVEL_ERROR, id, fmt, ##__VA_ARGS__)
#define scanner_err(scanner, id, fmt, ...) \
scanner_log_with_code(scanner, XKB_LOG_LEVEL_ERROR, 0, id, \
fmt, ##__VA_ARGS__)

#define scanner_warn(scanner, id, fmt, ...) \
scanner_log_with_code(scanner, XKB_LOG_LEVEL_WARNING, id, fmt, ##__VA_ARGS__)
#define scanner_warn(scanner, id, fmt, ...) \
scanner_log_with_code(scanner, XKB_LOG_LEVEL_WARNING, 0, id, \
fmt, ##__VA_ARGS__)

#define scanner_vrb(scanner, verbosity, id, fmt, ...) \
scanner_log_with_code(scanner, XKB_LOG_LEVEL_WARNING, verbosity, id, \
fmt, ##__VA_ARGS__)

static inline void
scanner_init(struct scanner *s, struct xkb_context *ctx,
Expand Down
65 changes: 62 additions & 3 deletions src/xkbcomp/rules.c
Original file line number Diff line number Diff line change
Expand Up @@ -1076,8 +1076,55 @@ expand_rmlvo_in_kccgst_value(struct matcher *m, struct scanner *s,
}

/*
* This function performs %-expansion on @value (see overview above),
* and appends the result to @to.
* This function performs :all replacement on @value (see overview above),
* and appends the result to @expanded.
*/
static void
expand_qualifier_in_kccgst_value(
struct matcher *m, struct scanner *s,
struct sval value, darray_char *expanded,
bool has_layout_idx_range, bool has_separator,
unsigned int prefix_idx, unsigned int *i)
{
const char *str = value.start;

/* “all” followed by nothing or by a layout separator */
if ((*i + 3 <= value.len || is_merge_mode_prefix(str[*i + 3])) &&
str[*i] == 'a' && str[*i+1] == 'l' && str[*i+2] == 'l') {
if (has_layout_idx_range)
scanner_vrb(s, 2, XKB_LOG_MESSAGE_NO_ID,
"Using :all qualifier with indexes range "
"is not recommended.");
/* Add at least one layout */
darray_appends_nullterminate(*expanded, "1", 1);
/* Check for more layouts (slow path) */
if (darray_size(m->rmlvo.layouts) > 1) {
char layout_index[MAX_LAYOUT_INDEX_STR_LENGTH + 1];
const size_t prefix_length = darray_size(*expanded) - prefix_idx - 1;
xkb_layout_index_t l;
for (l = 1;
l < MIN(XKB_MAX_GROUPS, darray_size(m->rmlvo.layouts));
l++)
{
if (!has_separator)
darray_append(*expanded, MERGE_DEFAULT_PREFIX);
/* Append prefix */
darray_appends_nullterminate(*expanded,
&darray_item(*expanded, prefix_idx),
prefix_length);
/* Append index */
int count = snprintf(layout_index, sizeof(layout_index),
"%"PRIu32, l + 1);
darray_appends_nullterminate(*expanded, layout_index, count);
}
}
*i += 3;
}
}

/*
* This function performs %-expansion and :all-expansion on @value
* (see overview above), and appends the result to @to.
*/
static bool
append_expanded_kccgst_value(struct matcher *m, struct scanner *s,
Expand All @@ -1088,10 +1135,20 @@ append_expanded_kccgst_value(struct matcher *m, struct scanner *s,
darray_char expanded = darray_new();
char ch;
bool expanded_plus, to_plus;
unsigned int last_item_idx = 0;
bool has_separator = false;

for (unsigned i = 0; i < value.len; ) {
/* Check if that's a start of an expansion */
/* Check if that's a start of an expansion or qualifier */
switch (str[i]) {
/* Qualifier */
case ':':
darray_appends_nullterminate(expanded, &str[i++], 1);
expand_qualifier_in_kccgst_value(m, s, value, &expanded,
m->mapping.has_layout_idx_range,
has_separator,
last_item_idx, &i);
break;
/* Expansion */
case '%':
if (++i >= value.len ||
Expand All @@ -1103,6 +1160,8 @@ append_expanded_kccgst_value(struct matcher *m, struct scanner *s,
case MERGE_OVERRIDE_PREFIX:
case MERGE_AUGMENT_PREFIX:
darray_appends_nullterminate(expanded, &str[i++], 1);
last_item_idx = darray_size(expanded) - 1;
has_separator = true;
break;
/* Just a normal character. */
default:
Expand Down
59 changes: 59 additions & 0 deletions test/data/rules/all_qualifier
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
! model = keycodes
my_model = my_keycodes
* = default_keycodes

! layout variant = symbols
layout_a my_variant = symbols_a+extra_variant

! layout = symbols
layout_a = symbols_a
layout_b = symbols_b
* = default_symbols

! layout[1] = symbols
layout_a = symbols_a:1
layout_b = symbols_b:1
layout_x = base:all // strange but valid
* = default_symbols:1

! layout[2] = symbols
layout_a = +symbols_a:2
layout_b = +symbols_b:2
* = +default_symbols:2

! layout[3] = symbols
layout_a = +symbols_a:3
layout_b = +symbols_b:3
* = +default_symbols:3

! layout[4] = symbols
layout_a = +symbols_a:4
layout_b = +symbols_b:4
* = +default_symbols:4

// WARNING: Invalid at the moment. Here for future test
! layout[5] = symbols
layout_a = +symbols_a:5
layout_b = +symbols_b:5
layout_c = +symbols_c:5
* = +default_symbols:5

// Combine with special indexes
! layout[first] variant[first] = symbols
* extra1 = +extra_symbols:all

// Combine with special indexes (valid but raises a warning)
! layout[any] variant[any] = symbols
* extra2 = +extra_symbols1:%i+extra_symbols2:all
* extra3 = +extra_symbols2:all+extra_symbols1:%i

! model = types
my_model = my_types
* = default_types

! model = compat
my_model = my_compat
* = default_compat

! option = symbols
my_option = +extra_option:all
18 changes: 18 additions & 0 deletions test/data/rules/all_qualifier-limit
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
! model = keycodes
* = default_keycodes

! layout[1] = symbols
* = x:all // force x on all groups

! layout[later] = symbols
// skipped, as it has no explicit merge mode and
// thus is not appended to previous
* = skip

! model = types
* = default_types

! model = compat
* = default_compat

! option = symbols
2 changes: 1 addition & 1 deletion test/data/rules/evdev-modern
Original file line number Diff line number Diff line change
Expand Up @@ -489,7 +489,6 @@

! 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
Expand Down Expand Up @@ -692,6 +691,7 @@
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)
misc:typo = +typo(base):all


! option = compat
Expand Down
Loading

0 comments on commit 7cdfe09

Please sign in to comment.