Skip to content

Commit

Permalink
Enable out-of-range layout action configuration
Browse files Browse the repository at this point in the history
Introduce 2 new keymap compilation flags to configure the action:
- `XKB_KEYMAP_REDIRECT_OUT_OF_RANGE_LAYOUT` to use “redirect into range”.
- `XKB_KEYMAP_CLAMP_OUT_OF_RANGE_LAYOUT` to use “clamp into range”.

The default action remains “wrap into range” using integer modulus.
  • Loading branch information
wismill committed Sep 18, 2024
1 parent 05ba96d commit 16e9649
Show file tree
Hide file tree
Showing 14 changed files with 255 additions and 23 deletions.
6 changes: 6 additions & 0 deletions changes/api/+out-of-range-layout-config.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Enable the configuration of out-of-range layout handling using 2 new keymap compile flags:
- `XKB_KEYMAP_REDIRECT_OUT_OF_RANGE_LAYOUT`: redirect to a specific layout index.
- `XKB_KEYMAP_CLAMP_OUT_OF_RANGE_LAYOUT`: clamp into range, i.e. invalid indexes are
corrected to the closest valid bound (0 or highest layout index).

When not specified, invalid groups are brought into range using integer modulus.
12 changes: 11 additions & 1 deletion doc/message-registry.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ NOTE: This file has been generated automatically by “update-message-registry.p
-->

This page lists the warnings and errors generated by xkbcommon.
There are currently 55 entries.
There are currently 56 entries.

@todo The documentation of the log messages is a work in progress.

Expand Down Expand Up @@ -53,6 +53,7 @@ There are currently 55 entries.
| [XKB-623] | `invalid-real-modifier` | Invalid _real_ modifier | Error |
| [XKB-645] | `unknown-char-escape-sequence` | Warn on unknown escape sequence in string literal | Warning |
| [XKB-661] | `invalid-included-file` | The target file of an include statement could not be processed | Error |
| [XKB-697] | `multiple-out-of-range-layout-flags` | Multiple out-of-range layout flags were set, but only one is allowed. | Error |
| [XKB-700] | `multiple-groups-at-once` | Warn if a key defines multiple groups at once | Warning |
| [XKB-711] | `unsupported-symbols-field` | A legacy X11 symbol field is not supported | Warning |
| [XKB-769] | `invalid-syntax` | The syntax is invalid and the file cannot be parsed | Error |
Expand Down Expand Up @@ -550,6 +551,14 @@ xkbcommon support the following escape sequences in string literals:
<dt>Summary</dt><dd>The target file of an include statement could not be processed</dd>
</dl>

### XKB-697 – Multiple out of range layout flags {#XKB-697}

<dl>
<dt>Since</dt><dd>1.8.0</dd>
<dt>Type</dt><dd>Error</dd>
<dt>Summary</dt><dd>Multiple out-of-range layout flags were set, but only one is allowed.</dd>
</dl>

### XKB-700 – Multiple groups at once {#XKB-700}

<dl>
Expand Down Expand Up @@ -724,6 +733,7 @@ The modifiers used in `map` or `preserve` entries should be declared using the e
[XKB-623]: @ref XKB-623
[XKB-645]: @ref XKB-645
[XKB-661]: @ref XKB-661
[XKB-697]: @ref XKB-697
[XKB-700]: @ref XKB-700
[XKB-711]: @ref XKB-711
[XKB-769]: @ref XKB-769
Expand Down
5 changes: 5 additions & 0 deletions doc/message-registry.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,11 @@
added: ALWAYS
type: error
description: "The target file of an include statement could not be processed"
- id: "multiple-out-of-range-layout-flags"
code: 697
added: 1.8.0
type: error
description: "Multiple out-of-range layout flags were set, but only one is allowed."
- id: "multiple-groups-at-once"
code: 700
added: ALWAYS
Expand Down
58 changes: 54 additions & 4 deletions include/xkbcommon/xkbcommon.h
Original file line number Diff line number Diff line change
Expand Up @@ -872,10 +872,60 @@ xkb_context_set_log_fn(struct xkb_context *context,
* @{
*/

/** Flags for keymap compilation. */
/**
* Flags for keymap compilation.
*
* @par Out-of-range layout action
* [Effective layout] index may be invalid in some contexts.
* One of the following out-of-range layout action is then used to bring invalid
* layout indexes back into range:
*
* - “redirect into range” (see: ::XKB_KEYMAP_REDIRECT_OUT_OF_RANGE_LAYOUT)
* - “clamp into range” (see: ::XKB_KEYMAP_CLAMP_OUT_OF_RANGE_LAYOUT)
* - “wrap into range” using integer modulus (default)
*
* [Effective layout]: @ref ::XKB_STATE_LAYOUT_EFFECTIVE
*/
enum xkb_keymap_compile_flags {
/** Do not apply any flags. */
XKB_KEYMAP_COMPILE_NO_FLAGS = 0
XKB_KEYMAP_COMPILE_NO_FLAGS = 0,
/*
* 4 least significant bit reserved to encode layout index (0..15) for
* XKB_KEYMAP_REDIRECT_OUT_OF_RANGE_LAYOUT.
*/
/**
* Set the out-of-range layout action to “redirect into range”.
*
* - If the [effective layout] is invalid, it is set to a *target layout*.
* - If the target layout is invalid, it is set to the first one (0).
*
* A *target layout index* (range 0..15) may be provided using the 4 least
* significant bits of the corresponding #xkb_keymap_compile_flags value,
* e.g.:
*
* ```c
* // Set the target layout index to 1.
* enum xkb_keymap_compile_flags flags = XKB_KEYMAP_REDIRECT_OUT_OF_RANGE_LAYOUT | 1;
* ```
*
* @since 1.8.0
*
* [effective layout]: @ref ::XKB_STATE_LAYOUT_EFFECTIVE
*/
XKB_KEYMAP_REDIRECT_OUT_OF_RANGE_LAYOUT = 1 << 4,
/**
* Set the out-of-range layout action to “clamp into range”: if the
* [effective layout] is invalid, it is set to nearest valid layout:
*
* - effective layout larger than the highest supported layout are mapped to
* the highest supported layout;
* - effective layout less than 0 are mapped to 0.
*
* @since 1.8.0
*
* [effective layout]: @ref ::XKB_STATE_LAYOUT_EFFECTIVE
*/
XKB_KEYMAP_CLAMP_OUT_OF_RANGE_LAYOUT = 1 << 5
};

/**
Expand All @@ -886,7 +936,7 @@ enum xkb_keymap_compile_flags {
*
* @param context The context in which to create the keymap.
* @param names The RMLVO names to use. See xkb_rule_names.
* @param flags Optional flags for the keymap, or 0.
* @param flags Optional flags for the keymap, or ::XKB_KEYMAP_COMPILE_NO_FLAGS.
*
* @returns A keymap compiled according to the RMLVO names, or NULL if
* the compilation failed.
Expand All @@ -911,7 +961,7 @@ enum xkb_keymap_format {
* @param context The context in which to create the keymap.
* @param file The keymap file to compile.
* @param format The text format of the keymap file to compile.
* @param flags Optional flags for the keymap, or 0.
* @param flags Optional flags for the keymap, or ::XKB_KEYMAP_COMPILE_NO_FLAGS.
*
* @returns A keymap compiled from the given XKB keymap file, or NULL if
* the compilation failed.
Expand Down
34 changes: 34 additions & 0 deletions src/keymap-priv.c
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,43 @@ update_builtin_keymap_fields(struct xkb_keymap *keymap)
keymap->mods.num_mods = ARRAY_SIZE(builtin_mods);
}

#define XKB_KEYMAP_OUT_OF_RANGE_LAYOUT_FLAGS \
(XKB_KEYMAP_REDIRECT_OUT_OF_RANGE_LAYOUT | \
XKB_KEYMAP_CLAMP_OUT_OF_RANGE_LAYOUT)

struct xkb_keymap *
xkb_keymap_new(struct xkb_context *ctx,
enum xkb_keymap_format format,
enum xkb_keymap_compile_flags flags)
{
struct xkb_keymap *keymap;
enum xkb_range_exceed_type out_of_range_group_action;
xkb_layout_index_t out_of_range_group_number =
flags & XKB_REDIRECT_LAYOUT_INDEX_MASK;

switch (flags & XKB_KEYMAP_OUT_OF_RANGE_LAYOUT_FLAGS) {
case 0:
out_of_range_group_action = RANGE_WRAP;
break;
case XKB_KEYMAP_REDIRECT_OUT_OF_RANGE_LAYOUT:
out_of_range_group_action = RANGE_REDIRECT;
break;
case XKB_KEYMAP_CLAMP_OUT_OF_RANGE_LAYOUT:
out_of_range_group_action = RANGE_SATURATE;
break;
default:
log_err(ctx, XKB_ERROR_MULTIPLE_OUT_OF_RANGE_LAYOUT_FLAGS,
"Cannot mix \"out of range layout\" keymap flags: %#x\n", flags);
return NULL;
}

if (out_of_range_group_number && out_of_range_group_action != RANGE_REDIRECT) {
log_err(ctx, XKB_ERROR_MULTIPLE_OUT_OF_RANGE_LAYOUT_FLAGS,
"Redirect layout index can only be used in combination with "
"XKB_KEYMAP_REDIRECT_OUT_OF_RANGE_LAYOUT flag. "
"Invalid keymap flags: %#x\n", flags);
return NULL;
}

keymap = calloc(1, sizeof(*keymap));
if (!keymap)
Expand All @@ -69,6 +100,9 @@ xkb_keymap_new(struct xkb_context *ctx,
keymap->format = format;
keymap->flags = flags;

keymap->out_of_range_group_action = out_of_range_group_action;
keymap->out_of_range_group_number = out_of_range_group_number;

update_builtin_keymap_fields(keymap);

return keymap;
Expand Down
6 changes: 3 additions & 3 deletions src/keymap.c
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ xkb_keymap_new_from_names(struct xkb_context *ctx,
return NULL;
}

if (flags & ~(XKB_KEYMAP_COMPILE_NO_FLAGS)) {
if (flags & ~XKB_KEYMAP_FLAGS_ALL) {
log_err_func(ctx, "unrecognized flags: %#x\n", flags);
return NULL;
}
Expand Down Expand Up @@ -180,7 +180,7 @@ xkb_keymap_new_from_buffer(struct xkb_context *ctx,
return NULL;
}

if (flags & ~(XKB_KEYMAP_COMPILE_NO_FLAGS)) {
if (flags & ~XKB_KEYMAP_FLAGS_ALL) {
log_err_func(ctx, "unrecognized flags: %#x\n", flags);
return NULL;
}
Expand Down Expand Up @@ -221,7 +221,7 @@ xkb_keymap_new_from_file(struct xkb_context *ctx,
return NULL;
}

if (flags & ~(XKB_KEYMAP_COMPILE_NO_FLAGS)) {
if (flags & ~XKB_KEYMAP_FLAGS_ALL) {
log_err_func(ctx, "unrecognized flags: %#x\n", flags);
return NULL;
}
Expand Down
26 changes: 24 additions & 2 deletions src/keymap.h
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,13 @@ enum mod_type {
};
#define MOD_REAL_MASK_ALL ((xkb_mod_mask_t) 0x000000ff)

#define XKB_REDIRECT_LAYOUT_INDEX_MASK (XKB_KEYMAP_REDIRECT_OUT_OF_RANGE_LAYOUT - 1)

#define XKB_KEYMAP_FLAGS_ALL \
(XKB_REDIRECT_LAYOUT_INDEX_MASK | \
XKB_KEYMAP_REDIRECT_OUT_OF_RANGE_LAYOUT | \
XKB_KEYMAP_CLAMP_OUT_OF_RANGE_LAYOUT)

enum xkb_action_type {
ACTION_TYPE_NONE = 0,
ACTION_TYPE_MOD_SET,
Expand Down Expand Up @@ -307,6 +314,7 @@ enum xkb_range_exceed_type {
RANGE_WRAP = 0,
RANGE_SATURATE,
RANGE_REDIRECT,
#define _XKB_RANGE_EXCEED_TYPE_NUM_ENTRIES 3
};

enum xkb_explicit_components {
Expand All @@ -332,6 +340,17 @@ struct xkb_group {
struct xkb_level *levels;
};

/* Note: enum value may be interpreted as a signed int, so we need an extra bit
* to store the sign. */
#define OUT_OF_RANGE_GROUP_ACTION_SIZE 3
#define OUT_OF_RANGE_GROUP_NUMBER_SIZE (32 - OUT_OF_RANGE_GROUP_ACTION_SIZE)
#if _XKB_RANGE_EXCEED_TYPE_NUM_ENTRIES >= (1 << (OUT_OF_RANGE_GROUP_ACTION_SIZE - 1))
#error "Cannot store enum xkb_range_exceed_type in bitfield out_of_range_group_number"
#endif
#if XKB_REDIRECT_LAYOUT_INDEX_MASK >= (1 << OUT_OF_RANGE_GROUP_NUMBER_SIZE)
#error "Cannot store layout index in bitfield out_of_range_group_number"
#endif

struct xkb_key {
xkb_keycode_t keycode;
xkb_atom_t name;
Expand All @@ -343,8 +362,8 @@ struct xkb_key {

bool repeats;

enum xkb_range_exceed_type out_of_range_group_action;
xkb_layout_index_t out_of_range_group_number;
xkb_layout_index_t out_of_range_group_number:OUT_OF_RANGE_GROUP_NUMBER_SIZE;
enum xkb_range_exceed_type out_of_range_group_action:OUT_OF_RANGE_GROUP_ACTION_SIZE;

xkb_layout_index_t num_groups;
struct xkb_group *groups;
Expand Down Expand Up @@ -392,6 +411,9 @@ struct xkb_keymap {
/* Not all groups must have names. */
xkb_layout_index_t num_group_names;
xkb_atom_t *group_names;
/* groups_wrap control */
xkb_layout_index_t out_of_range_group_number:OUT_OF_RANGE_GROUP_NUMBER_SIZE;
enum xkb_range_exceed_type out_of_range_group_action:OUT_OF_RANGE_GROUP_ACTION_SIZE;

struct xkb_led leds[XKB_MAX_LEDS];
unsigned int num_leds;
Expand Down
2 changes: 2 additions & 0 deletions src/messages-codes.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ enum xkb_message_code {
XKB_WARNING_UNKNOWN_CHAR_ESCAPE_SEQUENCE = 645,
/** The target file of an include statement could not be processed */
XKB_ERROR_INVALID_INCLUDED_FILE = 661,
/** Multiple out-of-range layout flags were set, but only one is allowed. */
XKB_ERROR_MULTIPLE_OUT_OF_RANGE_LAYOUT_FLAGS = 697,
/** Warn if a key defines multiple groups at once */
XKB_WARNING_MULTIPLE_GROUPS_AT_ONCE = 700,
/** A legacy X11 symbol field is not supported */
Expand Down
15 changes: 8 additions & 7 deletions src/state.c
Original file line number Diff line number Diff line change
Expand Up @@ -173,9 +173,9 @@ XkbWrapGroupIntoRange(int32_t group,

switch (out_of_range_group_action) {
case RANGE_REDIRECT:
if (out_of_range_group_number >= num_groups)
return 0;
return out_of_range_group_number;
return (out_of_range_group_number >= num_groups)
? 0
: out_of_range_group_number;

case RANGE_SATURATE:
if (group < 0)
Expand Down Expand Up @@ -815,12 +815,11 @@ xkb_state_update_derived(struct xkb_state *state)
state->components.latched_mods |
state->components.locked_mods);

/* TODO: Use groups_wrap control instead of always RANGE_WRAP. */

/* Lock group must be adjusted, but not base nor latched groups */
wrapped = XkbWrapGroupIntoRange(state->components.locked_group,
state->keymap->num_groups,
RANGE_WRAP, 0);
state->keymap->out_of_range_group_action,
state->keymap->out_of_range_group_number);
state->components.locked_group =
(wrapped == XKB_LAYOUT_INVALID ? 0 : wrapped);

Expand All @@ -829,7 +828,9 @@ xkb_state_update_derived(struct xkb_state *state)
state->components.latched_group +
state->components.locked_group,
state->keymap->num_groups,
RANGE_WRAP, 0);
state->keymap->out_of_range_group_action,
state->keymap->out_of_range_group_number);

state->components.group =
(wrapped == XKB_LAYOUT_INVALID ? 0 : wrapped);

Expand Down
2 changes: 1 addition & 1 deletion src/x11/keymap.c
Original file line number Diff line number Diff line change
Expand Up @@ -1142,7 +1142,7 @@ xkb_x11_keymap_new_from_device(struct xkb_context *ctx,
struct xkb_keymap *keymap;
const enum xkb_keymap_format format = XKB_KEYMAP_FORMAT_TEXT_V1;

if (flags & ~(XKB_KEYMAP_COMPILE_NO_FLAGS)) {
if (flags & ~XKB_KEYMAP_FLAGS_ALL) {
log_err_func(ctx, "unrecognized flags: %#x\n", flags);
return NULL;
}
Expand Down
20 changes: 15 additions & 5 deletions test/common.c
Original file line number Diff line number Diff line change
Expand Up @@ -401,9 +401,10 @@ test_compile_buffer(struct xkb_context *context, const char *buf, size_t len)
}

struct xkb_keymap *
test_compile_rules(struct xkb_context *context, const char *rules,
const char *model, const char *layout,
const char *variant, const char *options)
test_compile_rules_with_flags(struct xkb_context *context, const char *rules,
const char *model, const char *layout,
const char *variant, const char *options,
enum xkb_keymap_compile_flags flags)
{
struct xkb_keymap *keymap;
struct xkb_rule_names rmlvo = {
Expand All @@ -415,9 +416,9 @@ test_compile_rules(struct xkb_context *context, const char *rules,
};

if (!rules && !model && !layout && !variant && !options)
keymap = xkb_keymap_new_from_names(context, NULL, 0);
keymap = xkb_keymap_new_from_names(context, NULL, flags);
else
keymap = xkb_keymap_new_from_names(context, &rmlvo, 0);
keymap = xkb_keymap_new_from_names(context, &rmlvo, flags);

if (!keymap) {
fprintf(stderr,
Expand All @@ -428,3 +429,12 @@ test_compile_rules(struct xkb_context *context, const char *rules,

return keymap;
}

struct xkb_keymap *
test_compile_rules(struct xkb_context *context, const char *rules,
const char *model, const char *layout,
const char *variant, const char *options)
{
return test_compile_rules_with_flags(context, rules, model, layout,
variant, options, XKB_KEYMAP_COMPILE_NO_FLAGS);
}
Loading

0 comments on commit 16e9649

Please sign in to comment.