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 applies, it will expands for every layout, e.g.
`+group(toggle):all` would expand to `+group(toggle):1+group(toggle):2`
if there are 2 layouts, etc.

`: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 25, 2024
1 parent 410971f commit 715b386
Show file tree
Hide file tree
Showing 5 changed files with 248 additions and 12 deletions.
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 @@ -1064,8 +1064,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 || str[*i + 3] == '+' || 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];
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 */
snprintf(layout_index, sizeof(layout_index), "%"PRIu32, l + 1);
darray_appends_nullterminate(*expanded, layout_index,
strlen(layout_index));
}
}
*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 @@ -1076,10 +1123,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 @@ -1091,6 +1148,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
94 changes: 94 additions & 0 deletions test/rules-file.c
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,100 @@ main(int argc, char *argv[])
};
assert(test_rules(ctx, &special_indexes_too_much_layouts));

/* Test :all qualifier without special indexes, with option */
struct test_data all_qualified_alone1 = {
.rules = "all_qualifier",

.model = "my_model",
/* NOTE: 5 layouts is intentional;
* will require update when raising XKB_MAX_LAYOUTS */
.layout = "layout_a,layout_b,layout_a,layout_b,layout_c",
.variant = "",
.options = "my_option",

.keycodes = "my_keycodes", .types = "my_types",
.compat = "my_compat",
.symbols = "symbols_a:1+symbols_b:2+symbols_a:3+symbols_b:4+extra_option:1"
"+extra_option:2+extra_option:3+extra_option:4",
};
assert(test_rules(ctx, &all_qualified_alone1));

/* Test :all qualifier without special indexes, base for all layout */
struct test_data all_qualified_alone2 = {
.rules = "all_qualifier",

.model = "my_model",
/* NOTE: 5 layouts is intentional;
* will require update when raising XKB_MAX_LAYOUTS */
.layout = "layout_x,layout_a,layout_b,layout_c,layout_d",
.variant = "",
.options = "",

.keycodes = "my_keycodes", .types = "my_types",
.compat = "my_compat",
.symbols = "base:1+base:2+base:3+base:4"
"+symbols_a:2+symbols_b:3+default_symbols:4",
};
assert(test_rules(ctx, &all_qualified_alone2));

/* Test :all qualifier without special indexes, with option, too much layouts */
struct test_data all_qualified_invalid_layouts = {
.rules = "all_qualifier-limit",

.model = "my_model",
.layout = too_much_layouts,
.variant = "",
.options = "",

.keycodes = "default_keycodes", .types = "default_types",
.compat = "default_compat",
.symbols = too_much_symbols,
};
assert(test_rules(ctx, &all_qualified_invalid_layouts));

/* Test :all qualifier with special indexes */
struct test_data all_qualified_with_special_indexes1 = {
.rules = "all_qualifier",

.model = "my_model",
/* NOTE: 5 layouts is intentional;
* will require update when raising XKB_MAX_LAYOUTS */
.layout = "layout_a,layout_b,layout_a,layout_b,layout_c",
.variant = "extra1,,,,",
.options = "my_option",

.keycodes = "my_keycodes", .types = "my_types",
.compat = "my_compat",
.symbols = "symbols_a:1+symbols_b:2+symbols_a:3+symbols_b:4"
"+extra_symbols:1+extra_symbols:2+extra_symbols:3+extra_symbols:4"
"+extra_option:1+extra_option:2+extra_option:3+extra_option:4",
};
assert(test_rules(ctx, &all_qualified_with_special_indexes1));

/* Test :all qualifier with special indexes
* It uses :all combined with layout[any], which is valid but
* :%i was probably the intended qualified, so raises warning */
struct test_data all_qualified_with_special_indexes2 = {
.rules = "all_qualifier",

.model = "my_model",
/* NOTE: 5 layouts is intentional;
* will require update when raising XKB_MAX_LAYOUTS */
.layout = "layout_a,layout_b,layout_a,layout_b,layout_c",
.variant = "extra2,,extra3,,",
.options = "my_option",

.keycodes = "my_keycodes", .types = "my_types",
.compat = "my_compat",
.symbols = "symbols_a:1+symbols_b:2+symbols_a:3+symbols_b:4"
"+extra_symbols1:1+extra_symbols2:1+extra_symbols2:2+extra_symbols2:3+extra_symbols2:4"
"+extra_symbols2:1+extra_symbols2:2+extra_symbols2:3+extra_symbols2:4"
"+extra_symbols1:3"
"+extra_option:1"
"+extra_option:2+extra_option:3+extra_option:4",
};
assert(test_rules(ctx, &all_qualified_with_special_indexes2));

xkb_context_unref(ctx);
return 0;
}

0 comments on commit 715b386

Please sign in to comment.