From 01b64bb036a3fb6199ff6051ea723d140477cade Mon Sep 17 00:00:00 2001 From: Pierre Le Marre Date: Tue, 3 Dec 2024 10:12:03 +0100 Subject: [PATCH] state: add server API for updating latched and locked mods & layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Up to now, the “server state” `xkb_state` API only offered one entry point to update the server state – `xkb_state_update_key`, which reflects the direct keyboard keys state. But some updates come out-of-band from keyboard input events stream, for example, a GUI layout switcher. The X11 XKB protocol has a request which allows for such updates, `XkbLatchLockState`[^1], but xkbcommon does not have similar functionality. So server applications ended up using `xkb_state_update_state` for this, but that’s a function intended for client applications, not servers. Add support for updating the latched & locked state of the mods and layout. Note that the depressed states cannot be updated in this way -- XKB does not expect them to be updated out of band. [^1]: https://www.x.org/releases/X11R7.7/doc/kbproto/xkbproto.html#Querying_and_Changing_Keyboard_State Fixes: #310 Signed-off-by: Ran Benita Co-authored-by: Ran Benita Co-authored-by: Pierre Le Marre --- include/xkbcommon/xkbcommon.h | 57 ++++++ src/keymap.h | 13 ++ src/state.c | 160 +++++++++++++++- test/data/rules/evdev | 2 + test/data/symbols/group | 16 ++ test/state.c | 350 +++++++++++++++++++++++++++++++++- test/test.h | 5 + xkbcommon.map | 5 + 8 files changed, 602 insertions(+), 6 deletions(-) diff --git a/include/xkbcommon/xkbcommon.h b/include/xkbcommon/xkbcommon.h index acf010c6c..dbc028dad 100644 --- a/include/xkbcommon/xkbcommon.h +++ b/include/xkbcommon/xkbcommon.h @@ -82,6 +82,7 @@ #define _XKBCOMMON_H_ #include +#include #include #include @@ -1471,6 +1472,62 @@ enum xkb_state_component xkb_state_update_key(struct xkb_state *state, xkb_keycode_t key, enum xkb_key_direction direction); +/** + * Update the keyboard state to change the latched and locked state of + * the modifiers and layout. + * + * This entry point is intended for *server* applications and should not be used + * by *client* applications; see @ref server-client-state for details. + * + * Use this function to update the latched and locked state according to + * “out of band” (non-device) inputs, such as UI layout switchers. + * + * @par Layout out of range + * @parblock + * + * If the effective layout, after taking into account the depressed, latched and + * locked layout, is out of range (negative or greater than the maximum layout), + * it is brought into range. Currently, the layout is wrapped using integer + * modulus (with negative values wrapping from the end). The wrapping behavior + * may be made configurable in the future. + * + * @endparblock + * + * @param affect_latched_mods + * @param latched_mods + * Modifiers to set as latched or unlatched. Only modifiers in + * `affect_latched_mods` are considered. + * @param affect_latched_layout + * @param latched_layout + * Layout to latch. Only considered if `affect_latched_layout` is true. + * Maybe be out of range (including negative) -- see note above. + * @param affect_locked_mods + * @param locked_mods + * Modifiers to set as locked or unlocked. Only modifiers in + * `affect_locked_mods` are considered. + * @param affect_locked_layout + * @param locked_layout + * Layout to lock. Only considered if `affect_locked_layout` is true. + * Maybe be out of range (including negative) -- see note above. + * + * @returns A mask of state components that have changed as a result of + * the update. If nothing in the state has changed, returns 0. + * + * @memberof xkb_state + * + * @sa xkb_state_update_mask() + */ +enum xkb_state_component +xkb_state_update_latched_locked(struct xkb_state *state, + xkb_mod_mask_t affect_latched_mods, + xkb_mod_mask_t latched_mods, + bool affect_latched_layout, + int32_t latched_layout, + xkb_mod_mask_t affect_locked_mods, + xkb_mod_mask_t locked_mods, + bool affect_locked_layout, + int32_t locked_layout); + /** * Update a keyboard state from a set of explicit masks. * diff --git a/src/keymap.h b/src/keymap.h index 4be42e63e..fb8ad3c26 100644 --- a/src/keymap.h +++ b/src/keymap.h @@ -137,6 +137,7 @@ enum xkb_action_type { ACTION_TYPE_CTRL_SET, ACTION_TYPE_CTRL_LOCK, ACTION_TYPE_PRIVATE, + ACTION_TYPE_INTERNAL, /* Action specific and internal to xkbcommon */ _ACTION_TYPE_NUM_ENTRIES }; @@ -234,6 +235,17 @@ struct xkb_private_action { uint8_t data[7]; }; +enum xkb_internal_action_flags { + INTERNAL_BREAKS_GROUP_LATCH = (1 << 0), + INTERNAL_BREAKS_MOD_LATCH = (1 << 1), +}; + +/* Action specific and internal to xkbcommon */ +struct xkb_internal_action { + enum xkb_action_type type; + enum xkb_internal_action_flags flags; +}; + union xkb_action { enum xkb_action_type type; struct xkb_mod_action mods; @@ -244,6 +256,7 @@ union xkb_action { struct xkb_pointer_action ptr; struct xkb_pointer_button_action btn; struct xkb_private_action priv; + struct xkb_internal_action internal; }; struct xkb_key_type_entry { diff --git a/src/state.c b/src/state.c index 929a498bd..4c184f587 100644 --- a/src/state.c +++ b/src/state.c @@ -322,7 +322,8 @@ xkb_filter_group_lock_func(struct xkb_state *state, } static bool -xkb_action_breaks_latch(const union xkb_action *action) +xkb_action_breaks_latch(const union xkb_action *action, + enum xkb_internal_action_flags flag) { switch (action->type) { case ACTION_TYPE_NONE: @@ -333,6 +334,8 @@ xkb_action_breaks_latch(const union xkb_action *action) case ACTION_TYPE_SWITCH_VT: case ACTION_TYPE_TERMINATE: return true; + case ACTION_TYPE_INTERNAL: + return action->internal.flags & flag; default: return false; } @@ -415,7 +418,8 @@ xkb_filter_group_latch_func(struct xkb_state *state, /* XXX beep beep! */ return XKB_FILTER_CONSUME; } - else if (xkb_action_breaks_latch(&(actions[k]))) { + else if (xkb_action_breaks_latch(&(actions[k]), + INTERNAL_BREAKS_GROUP_LATCH)) { /* Breaks the latch */ state->components.latched_group = 0; filter->func = NULL; @@ -571,9 +575,10 @@ xkb_filter_mod_latch_func(struct xkb_state *state, /* XXX beep beep! */ return XKB_FILTER_CONSUME; } - else if (xkb_action_breaks_latch(&(actions[k]))) { + else if (xkb_action_breaks_latch(&(actions[k]), + INTERNAL_BREAKS_MOD_LATCH)) { /* XXX: This may be totally broken, we might need to break the - * latch in the next run after this press? */ + * latch in the next run after this press? */ state->components.latched_mods &= ~filter->action.mods.mods.mask; filter->func = NULL; return XKB_FILTER_CONTINUE; @@ -903,6 +908,151 @@ xkb_state_update_key(struct xkb_state *state, xkb_keycode_t kc, return get_state_component_changes(&prev_components, &state->components); } +/* We need fake keys for update_latch_modifiers and update_latch_group. + * These keys must have at least one level in order to break latches. We need 2 + * keys with specific actions in order to update group/mod latches without + * affecting each other. */ +static struct xkb_key_type_entry synthetic_key_level_entry = { 0 }; +static struct xkb_key_type synthetic_key_type = { + .num_entries = 1, + .num_levels = 1, + .entries = &synthetic_key_level_entry +}; +static struct xkb_level synthetic_key_level_break_group_latch = { + .num_syms = 1, + .s = { XKB_KEY_NoSymbol }, + .a = { { .internal = { + .type = ACTION_TYPE_INTERNAL, + .flags = INTERNAL_BREAKS_GROUP_LATCH + } } } +}; +static struct xkb_group synthetic_key_group_break_group_latch = { + .type = &synthetic_key_type, + .levels = &synthetic_key_level_break_group_latch +}; +static const struct xkb_key synthetic_key_break_group_latch = { + .num_groups = 1, + .groups = &synthetic_key_group_break_group_latch +}; +static struct xkb_level synthetic_key_level_break_mod_latch = { + .num_syms = 1, + .s = { XKB_KEY_NoSymbol }, + .a = { { .internal = { + .type = ACTION_TYPE_INTERNAL, + .flags = INTERNAL_BREAKS_MOD_LATCH + } } } +}; +static struct xkb_group synthetic_key_group_break_mod_latch = { + .type = &synthetic_key_type, + .levels = &synthetic_key_level_break_mod_latch +}; +static const struct xkb_key synthetic_key_break_mod_latch = { + .num_groups = 1, + .groups = &synthetic_key_group_break_mod_latch +}; + +/* Transcription from xserver: XkbLatchModifiers */ +static void +update_latch_modifiers(struct xkb_state *state, + xkb_mod_mask_t mask, xkb_mod_mask_t latches) +{ + const struct xkb_key *key = &synthetic_key_break_mod_latch; + + /* Clear affected latched modifiers */ + const xkb_mod_mask_t clear = + mod_mask_get_effective(state->keymap, mask & ~latches); + state->components.latched_mods &= ~clear; + + /* Clear any pending latch to locks. */ + xkb_filter_apply_all(state, key, XKB_KEY_DOWN); + + /* Simulate tapping a key with a modifier latch action */ + const union xkb_action latch_mods = { + .mods = { + .type = ACTION_TYPE_MOD_LATCH, + .mods = { + .mask = mod_mask_get_effective(state->keymap, mask & latches) + }, + .flags = 0, + }, + }; + struct xkb_filter *filter = xkb_filter_new(state); + filter->key = key; + filter->func = xkb_filter_mod_latch_func; + filter->action = latch_mods; + xkb_filter_mod_latch_new(state, filter); + /* We added the filter manually, so only fire “up” event */ + xkb_filter_mod_latch_func(state, filter, key, XKB_KEY_UP); +} + +/* Transcription from xserver: XkbLatchGroup */ +static void +update_latch_group(struct xkb_state *state, int32_t group) +{ + /* Simulate tapping a key with a group latch action, but in isolation: i.e. + * without affecting the other filters. */ + const struct xkb_key *key = &synthetic_key_break_group_latch; + + /* Clear any pending latch to locks. */ + xkb_filter_apply_all(state, key, XKB_KEY_DOWN); + + const union xkb_action latch_group = { + .group = { + .type = ACTION_TYPE_GROUP_LATCH, + .flags = 0, + .group = group, + }, + }; + struct xkb_filter *filter = xkb_filter_new(state); + filter->key = key; + filter->func = xkb_filter_group_latch_func; + filter->action = latch_group; + xkb_filter_group_latch_new(state, filter); + /* We added the filter manually, so only fire “up” event */ + xkb_filter_group_latch_func(state, filter, key, XKB_KEY_UP); +} + +XKB_EXPORT enum xkb_state_component +xkb_state_update_latched_locked(struct xkb_state *state, + xkb_mod_mask_t affect_latched_mods, + xkb_mod_mask_t latched_mods, + bool affect_latched_layout, + int32_t latched_layout, + xkb_mod_mask_t affect_locked_mods, + xkb_mod_mask_t locked_mods, + bool affect_locked_layout, + int32_t locked_layout) +{ + const struct state_components prev_components = state->components; + /* Only include modifiers which exist in the keymap. */ + const xkb_mod_mask_t mask = + (xkb_mod_mask_t) ((1ull << xkb_keymap_num_mods(state->keymap)) - 1u); + + /* Update locks */ + affect_locked_mods &= mask; + if (affect_locked_mods) { + state->components.locked_mods &= ~affect_locked_mods; + state->components.locked_mods |= locked_mods & affect_locked_mods; + state->components.locked_mods = + mod_mask_get_effective(state->keymap, state->components.locked_mods); + } + if (affect_locked_layout) { + state->components.locked_group = locked_layout; + } + + /* Update latches */ + affect_latched_mods &= mask; + if (affect_latched_mods) { + update_latch_modifiers(state, affect_latched_mods, latched_mods); + } + if (affect_latched_layout) { + update_latch_group(state, latched_layout); + } + + xkb_state_update_derived(state); + return get_state_component_changes(&prev_components, &state->components); +} + /** * Updates the state from a set of explicit masks as gained from * xkb_state_serialize_mods and xkb_state_serialize_groups. As noted in the @@ -936,7 +1086,7 @@ xkb_state_update_mask(struct xkb_state *state, * * It might seem more reasonable to do this only for components.mods * in xkb_state_update_derived(), rather than for each component - * seperately. That would allow to distinguish between "really" + * separately. That would allow to distinguish between "really" * depressed mods (would be in MODS_DEPRESSED) and indirectly * depressed to to a mapping (would only be in MODS_EFFECTIVE). * However, the traditional behavior of xkb_state_update_key() is that diff --git a/test/data/rules/evdev b/test/data/rules/evdev index ea8652dda..f14db1293 100644 --- a/test/data/rules/evdev +++ b/test/data/rules/evdev @@ -993,7 +993,9 @@ grp:alt_space_toggle = +group(alt_space_toggle) grp:menu_toggle = +group(menu_toggle) grp:lwin_toggle = +group(lwin_toggle) + grp:lwin_latch = +group(lwin_latch) grp:rwin_toggle = +group(rwin_toggle) + grp:rwin_latch_lock_clear = +group(rwin_latch_lock_clear) grp:lshift_toggle = +group(lshift_toggle) grp:rshift_toggle = +group(rshift_toggle) grp:rctrl_switch = +group(rctrl_switch) diff --git a/test/data/symbols/group b/test/data/symbols/group index 3661e7937..c62222966 100644 --- a/test/data/symbols/group +++ b/test/data/symbols/group @@ -34,6 +34,14 @@ xkb_symbols "lwin_switch" { }; }; +partial modifier_keys +xkb_symbols "lwin_latch" { + key { + symbols[1] = [ ISO_Group_Latch ], + actions[1] = [ LatchGroup(group=+1) ] + }; +}; + // The right Win key (while pressed) chooses the second keyboard group. // (Using this map, you should declare your keyboard as pc101 or pc102 // instead of pc104 or pc105.) @@ -45,6 +53,14 @@ xkb_symbols "rwin_switch" { }; }; +partial modifier_keys +xkb_symbols "rwin_latch_lock_clear" { + key { + symbols[1] = [ ISO_Group_Latch ], + actions[1] = [ LatchGroup(group=+1, latchToLock, clearLocks) ] + }; +}; + // The right Menu key (while pressed) chooses the second keyboard group. // while Shift+Menu acts as Menu. partial modifier_keys diff --git a/test/state.c b/test/state.c index 7fd6d4790..6f639dae9 100644 --- a/test/state.c +++ b/test/state.c @@ -30,6 +30,8 @@ #include #include "evdev-scancodes.h" +#include "src/keysym.h" +#include "src/keymap.h" #include "test.h" /* Offset between evdev keycodes (where KEY_ESCAPE is 1), and the evdev XKB @@ -44,6 +46,24 @@ _xkb_keymap_mod_get_index(struct xkb_keymap *keymap, const char *name) return mod; } +static inline xkb_led_index_t +_xkb_keymap_led_get_index(struct xkb_keymap *keymap, const char *name) +{ + xkb_led_index_t led = xkb_keymap_led_get_index(keymap, name); + assert(led != XKB_LED_INVALID); + return led; +} + +static void +print_modifiers_serialization(struct xkb_state *state) +{ + xkb_mod_mask_t base = xkb_state_serialize_mods(state, XKB_STATE_MODS_DEPRESSED); + xkb_mod_mask_t latched = xkb_state_serialize_mods(state, XKB_STATE_MODS_LATCHED); + xkb_mod_mask_t locked = xkb_state_serialize_mods(state, XKB_STATE_MODS_LOCKED); + xkb_mod_mask_t effective = xkb_state_serialize_mods(state, XKB_STATE_MODS_EFFECTIVE); + fprintf(stderr, "\tMods: Base: 0x%x, Latched: 0x%x, Locked: 0x%x, Effective: 0x%x\n", base, latched, locked, effective); +} + static void print_state(struct xkb_state *state) { @@ -111,6 +131,123 @@ print_state(struct xkb_state *state) } } +enum test_entry_input_type { + INPUT_TYPE_COMPONENTS = 0, + INPUT_TYPE_KEY, + INPUT_TYPE_RESET +}; + +struct test_state_components { + enum test_entry_input_type input_type; + union { + struct { + bool affect_latched_group; + int32_t latched_group; + bool affect_locked_group; + int32_t locked_group; + xkb_mod_mask_t affect_latched_mods; + xkb_mod_mask_t latched_mods; + xkb_mod_mask_t affect_locked_mods; + xkb_mod_mask_t locked_mods; + } input; + struct { + xkb_keycode_t keycode; + enum key_seq_state direction; + xkb_keysym_t keysym; + } key; + }; + + /* Same as state_components, but it is not public */ + int32_t base_group; /**< depressed */ + int32_t latched_group; + int32_t locked_group; + xkb_layout_index_t group; /**< effective */ + xkb_mod_mask_t base_mods; /**< depressed */ + xkb_mod_mask_t latched_mods; + xkb_mod_mask_t locked_mods; + xkb_mod_mask_t mods; /**< effective */ + xkb_led_mask_t leds; + + enum xkb_state_component changes; +}; + +#define check_serialize_layout(components, expected, got) \ + xkb_state_serialize_layout(expected, components) == \ + xkb_state_serialize_layout(got, components) + +#define check_serialize_mods(components, expected, got) \ + xkb_state_serialize_mods(expected, components) == \ + xkb_state_serialize_mods(got, components) + +static bool +check_state(struct xkb_state *expected, struct xkb_state *got) +{ + bool ok = check_serialize_layout(XKB_STATE_LAYOUT_DEPRESSED, expected, got) && + check_serialize_layout(XKB_STATE_LAYOUT_LATCHED, expected, got) && + check_serialize_layout(XKB_STATE_LAYOUT_LOCKED, expected, got) && + check_serialize_layout(XKB_STATE_LAYOUT_EFFECTIVE, expected, got) && + check_serialize_mods(XKB_STATE_MODS_DEPRESSED, expected, got) && + check_serialize_mods(XKB_STATE_MODS_LATCHED, expected, got) && + check_serialize_mods(XKB_STATE_MODS_LOCKED, expected, got) && + check_serialize_mods(XKB_STATE_MODS_EFFECTIVE, expected, got); + + struct xkb_keymap *keymap = xkb_state_get_keymap(expected); + + for (xkb_led_index_t led = 0; led < xkb_keymap_num_leds(keymap); led++) { + if (xkb_state_led_index_is_active(expected, led) != + xkb_state_led_index_is_active(got, led)) { + ok = false; + break; + } + } + + if (!ok) { + fprintf(stderr, "Expected state:\n"); + print_state(expected); + print_modifiers_serialization(expected); + fprintf(stderr, "Got state:\n"); + print_state(got); + print_modifiers_serialization(got); + } + return ok; +} + +static bool +check_update_state(struct xkb_keymap *keymap, + const struct test_state_components *components, + struct xkb_state *expected, struct xkb_state *got, + xkb_keysym_t keysym, enum xkb_state_component changes) +{ + xkb_state_update_mask(expected, + mod_mask_get_effective(keymap, components->base_mods), + mod_mask_get_effective(keymap, components->latched_mods), + mod_mask_get_effective(keymap, components->locked_mods), + components->base_group, + components->latched_group, components->locked_group); + + if (changes != components->changes) { + fprintf(stderr, "Expected state change: %u, but got: %u\n", + components->changes, changes); + fprintf(stderr, "Expected state:\n"); + print_state(expected); + fprintf(stderr, "Got state:\n"); + print_state(got); + return false; + } else if (components->input_type == INPUT_TYPE_KEY) { + if (keysym != components->key.keysym) { + char buf[XKB_KEYSYM_NAME_MAX_SIZE]; + xkb_keysym_get_name(components->key.keysym, buf, sizeof(buf)); + fprintf(stderr, "Expected keysym: %s, ", buf); + xkb_keysym_get_name(keysym, buf, sizeof(buf)); + fprintf(stderr, "but got: %s\n", buf); + return false; + } + } else if (keysym != XKB_KEY_NoSymbol) { + return false; + } + return check_state(expected, got); +} + static void test_update_key(struct xkb_keymap *keymap) { @@ -310,6 +447,216 @@ struct test_active_mods_entry { xkb_mod_mask_t active; }; +static void +test_update_latched_locked(struct xkb_keymap *keymap) +{ + enum xkb_state_component changes; + struct xkb_state *state = xkb_state_new(keymap); + struct xkb_state *expected = xkb_state_new(keymap); + assert(state); + xkb_led_index_t capslock_led_idx = _xkb_keymap_led_get_index(keymap, XKB_LED_NAME_CAPS); + xkb_led_mask_t capslock_led = 1u << capslock_led_idx; + xkb_led_index_t group2_led_idx = _xkb_keymap_led_get_index(keymap, "Group 2"); + xkb_led_mask_t group2_led = 1u << group2_led_idx; + xkb_mod_index_t shift_idx = _xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_SHIFT); + xkb_mod_mask_t shift = 1u << shift_idx; + xkb_mod_index_t capslock_idx = _xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_CAPS); + xkb_mod_mask_t capslock = 1u << capslock_idx; + xkb_mod_index_t control_idx = _xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_CTRL); + xkb_mod_mask_t control = 1u << control_idx; + xkb_mod_index_t level3_idx = _xkb_keymap_mod_get_index(keymap, XKB_VMOD_NAME_LEVEL3); + xkb_mod_mask_t level3 = 1u << level3_idx; + + /* Helper to create test data that tests xkb_state_update_latched_locked */ +#define COMPONENTS_ENTRY( \ + _alatched_group, _ilatched_group, _alocked_group, _ilocked_group, \ + _base_group, _latched_group, _locked_group, _group, \ + _alatched_mods, _ilatched_mods, _alocked_mods, _ilocked_mods, \ + _base_mods, _latched_mods, _locked_mods, _mods, _leds, _changes) \ + { .input_type = INPUT_TYPE_COMPONENTS, \ + .input = { \ + .affect_latched_group = _alatched_group, .latched_group = _ilatched_group, \ + .affect_locked_group = _alocked_group, .locked_group = _ilocked_group, \ + .affect_latched_mods = _alatched_mods, .latched_mods = _ilatched_mods, \ + .affect_locked_mods = _alocked_mods, .locked_mods = _ilocked_mods \ + }, \ + .base_group = _base_group, .latched_group = _latched_group, \ + .locked_group = _locked_group, .group = _group, \ + .base_mods = _base_mods, .latched_mods = _latched_mods, \ + .locked_mods = _locked_mods, .mods = _mods, .leds = _leds, \ + .changes = _changes } + + /* Helper to create test data that tests xkb_state_update_key */ +#define KEY_ENTRY(_keycode, _direction, _keysym, \ + _base_group, _latched_group, _locked_group, _group, \ + _base_mods, _latched_mods, _locked_mods, _mods, _leds, _changes) \ + { .input_type = INPUT_TYPE_KEY, \ + .key = { \ + .keycode = _keycode + EVDEV_OFFSET, \ + .direction = _direction, \ + .keysym = _keysym, \ + }, \ + .base_group = _base_group, .latched_group = _latched_group, \ + .locked_group = _locked_group, .group = _group, \ + .base_mods = _base_mods, .latched_mods = _latched_mods, \ + .locked_mods = _locked_mods, .mods = _mods, .leds = _leds, \ + .changes = _changes } + + /* Helper to create test data that reset the state */ +#define RESET_STATE { .input_type = INPUT_TYPE_RESET } + + const struct test_state_components test_data[] = { + KEY_ENTRY(KEY_A, BOTH, XKB_KEY_a, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), + + /* Groups: lock */ +#define GROUP_LOCK_ENTRY(igroup, ogroup, led, changes) \ + COMPONENTS_ENTRY(false, 0, true, igroup, 0, 0, ogroup, ogroup, \ + false, 0, false, 0, 0, 0, 0, 0, led, changes) +#define GROUP_LOCK_CHANGES \ + (XKB_STATE_LAYOUT_LOCKED | XKB_STATE_LAYOUT_EFFECTIVE | XKB_STATE_LEDS) + + GROUP_LOCK_ENTRY(1, 1, group2_led, GROUP_LOCK_CHANGES), + KEY_ENTRY(KEY_A, BOTH, XKB_KEY_Cyrillic_ef, 0, 0, 1, 1, 0, 0, 0, 0, group2_led, 0), + GROUP_LOCK_ENTRY(0, 0, 0, GROUP_LOCK_CHANGES), + GROUP_LOCK_ENTRY(0, 0, 0, 0), + GROUP_LOCK_ENTRY(1, 1, group2_led, GROUP_LOCK_CHANGES), + GROUP_LOCK_ENTRY(1, 1, group2_led, 0), + GROUP_LOCK_ENTRY(XKB_MAX_GROUPS, 0, 0, GROUP_LOCK_CHANGES), + + /* Groups: latch */ +#define GROUP_LATCH_ENTRY(igroup, ogroup, led, changes) \ + COMPONENTS_ENTRY(true, igroup, false, 0, 0, ogroup, 0, ogroup, \ + false, 0, false, 0, 0, 0, 0, 0, led, changes) + + RESET_STATE, + KEY_ENTRY(KEY_A, BOTH, XKB_KEY_a, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), + GROUP_LATCH_ENTRY(1, 1, group2_led, + XKB_STATE_LAYOUT_LATCHED | XKB_STATE_LAYOUT_EFFECTIVE | XKB_STATE_LEDS), + KEY_ENTRY(KEY_A, DOWN, XKB_KEY_Cyrillic_ef, 0, 0, 0, 0, 0, 0, 0, 0, 0, + XKB_STATE_LAYOUT_LATCHED | XKB_STATE_LAYOUT_EFFECTIVE | XKB_STATE_LEDS), + KEY_ENTRY(KEY_A, UP, XKB_KEY_a, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), + KEY_ENTRY(KEY_A, DOWN, XKB_KEY_a, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), + KEY_ENTRY(KEY_A, UP, XKB_KEY_a, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), + + /* Modifiers: lock */ +#define MOD_LOCK_ENTRY(mask, ilock, olock, led, changes) \ + COMPONENTS_ENTRY(false, 0, false, 0, 0, 0, 0, 0, \ + false, 0, mask, ilock, 0, 0, olock, olock, led, changes) + + RESET_STATE, + MOD_LOCK_ENTRY(capslock, capslock, capslock, capslock_led, + XKB_STATE_MODS_LOCKED | XKB_STATE_MODS_EFFECTIVE | XKB_STATE_LEDS), + MOD_LOCK_ENTRY(capslock, capslock, capslock, capslock_led, 0), + MOD_LOCK_ENTRY(control, control, control | capslock, capslock_led, + XKB_STATE_MODS_LOCKED | XKB_STATE_MODS_EFFECTIVE), + MOD_LOCK_ENTRY(capslock, 0, control, 0, + XKB_STATE_MODS_LOCKED | XKB_STATE_MODS_EFFECTIVE | XKB_STATE_LEDS), + MOD_LOCK_ENTRY(level3 | control, level3, level3, 0, + XKB_STATE_MODS_LOCKED | XKB_STATE_MODS_EFFECTIVE), + + /* Modifiers: latch */ +#define MODS_LATCH_ENTRY(mask, imods, omods, led, changes) \ + COMPONENTS_ENTRY(false, 0, false, 0, 0, 0, 0, 0, \ + mask, imods, false, 0, 0, imods, 0, omods, led, changes) + + RESET_STATE, + KEY_ENTRY(KEY_A, BOTH, XKB_KEY_a, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), + MODS_LATCH_ENTRY(shift, shift, shift, 0, + XKB_STATE_MODS_LATCHED | XKB_STATE_MODS_EFFECTIVE), + KEY_ENTRY(KEY_A, DOWN, XKB_KEY_A, 0, 0, 0, 0, 0, 0, 0, 0, 0, + XKB_STATE_MODS_LATCHED | XKB_STATE_MODS_EFFECTIVE), + KEY_ENTRY(KEY_A, UP, XKB_KEY_a, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), + KEY_ENTRY(KEY_A, BOTH, XKB_KEY_a, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), + // TODO + + /* Mix + * When updating latches, mod/group changes should not affect each other */ + RESET_STATE, + COMPONENTS_ENTRY( + true, 1, false, 0, 0, 1, 0, 1, \ + control, control, 0, 0, 0, control, 0, control, group2_led, + XKB_STATE_LAYOUT_LATCHED | XKB_STATE_LAYOUT_EFFECTIVE | XKB_STATE_LEDS | + XKB_STATE_MODS_LATCHED | XKB_STATE_MODS_EFFECTIVE), + KEY_ENTRY(KEY_A, DOWN, XKB_KEY_Cyrillic_ef, 0, 0, 0, 0, 0, 0, 0, 0, 0, + XKB_STATE_LAYOUT_LATCHED | XKB_STATE_LAYOUT_EFFECTIVE | XKB_STATE_LEDS | + XKB_STATE_MODS_LATCHED | XKB_STATE_MODS_EFFECTIVE), + RESET_STATE, + COMPONENTS_ENTRY( + false, 0, true, 1, 0, 0, 1, 1, \ + false, 0, control, control, 0, 0, control, control, group2_led, + XKB_STATE_LAYOUT_LOCKED | XKB_STATE_LAYOUT_EFFECTIVE | XKB_STATE_LEDS | + XKB_STATE_MODS_LOCKED | XKB_STATE_MODS_EFFECTIVE), + RESET_STATE, + COMPONENTS_ENTRY( + false, 0, true, 1, 0, 0, 1, 1, \ + false, 0, control, control, 0, 0, control, control, group2_led, + XKB_STATE_LAYOUT_LOCKED | XKB_STATE_LAYOUT_EFFECTIVE | XKB_STATE_LEDS | + XKB_STATE_MODS_LOCKED | XKB_STATE_MODS_EFFECTIVE), + RESET_STATE, + KEY_ENTRY(KEY_LEFTMETA, BOTH, XKB_KEY_ISO_Group_Latch, 0, 1, 0, 1, 0, 0, 0, 0, group2_led, + XKB_STATE_LAYOUT_LATCHED | XKB_STATE_LAYOUT_DEPRESSED), + /* Pending group latch */ + COMPONENTS_ENTRY( + false, 0, false, 0, 0, 1, 0, 1, \ + shift, shift, 0, 0, 0, shift, 0, shift, group2_led, + XKB_STATE_MODS_LATCHED | XKB_STATE_MODS_EFFECTIVE), + KEY_ENTRY(KEY_A, DOWN, XKB_KEY_Cyrillic_EF, 0, 0, 0, 0, 0, 0, 0, 0, group2_led, + XKB_STATE_LAYOUT_LATCHED | XKB_STATE_LAYOUT_EFFECTIVE | XKB_STATE_LEDS | + XKB_STATE_MODS_LATCHED | XKB_STATE_MODS_EFFECTIVE), + KEY_ENTRY(KEY_RIGHTMETA, BOTH, XKB_KEY_ISO_Group_Latch, 0, 1, 0, 1, 0, 0, 0, 0, group2_led, + XKB_STATE_LAYOUT_LATCHED | XKB_STATE_LAYOUT_DEPRESSED), + /* Pending group latch (with latch to lock + clear) */ + COMPONENTS_ENTRY( + false, 0, false, 0, 0, 1, 0, 1, \ + shift, shift, 0, 0, 0, shift, 0, shift, group2_led, + XKB_STATE_MODS_LATCHED | XKB_STATE_MODS_EFFECTIVE), + KEY_ENTRY(KEY_A, DOWN, XKB_KEY_Cyrillic_EF, 0, 0, 0, 0, 0, 0, 0, 0, group2_led, + XKB_STATE_LAYOUT_LATCHED | XKB_STATE_LAYOUT_EFFECTIVE | XKB_STATE_LEDS | + XKB_STATE_MODS_LATCHED | XKB_STATE_MODS_EFFECTIVE), + // TODO + }; + for (size_t k = 0; k < ARRAY_SIZE(test_data); k++) { + xkb_keysym_t keysym = XKB_KEY_NoSymbol; + switch (test_data[k].input_type) { + case INPUT_TYPE_COMPONENTS: + changes = xkb_state_update_latched_locked( + state, + test_data[k].input.affect_latched_mods, + test_data[k].input.latched_mods, + test_data[k].input.affect_latched_group, + test_data[k].input.latched_group, + test_data[k].input.affect_locked_mods, + test_data[k].input.locked_mods, + test_data[k].input.affect_locked_group, + test_data[k].input.locked_group); + break; + case INPUT_TYPE_KEY: + keysym = xkb_state_key_get_one_sym(state, test_data[k].key.keycode); + if (test_data[k].key.direction == DOWN || + test_data[k].key.direction == BOTH) + changes = xkb_state_update_key(state, test_data[k].key.keycode, XKB_KEY_DOWN); + if (test_data[k].key.direction == UP || + test_data[k].key.direction == BOTH) + changes = xkb_state_update_key(state, test_data[k].key.keycode, XKB_KEY_UP); + break; + case INPUT_TYPE_RESET: + xkb_state_unref(state); + xkb_state_unref(expected); + state = xkb_state_new(keymap); + expected = xkb_state_new(keymap); + continue; + default: + assert(false); + } + assert_printf(check_update_state(keymap, &test_data[k], expected, state, keysym, changes), + "test_update_latched_locked #%zu\n", k); + } + + xkb_state_unref(expected); + xkb_state_unref(state); +#undef COMPONENTS_ENTRY +} + static void test_serialisation(struct xkb_keymap *keymap) { @@ -1157,10 +1504,11 @@ main(void) xkb_state_unref(NULL); keymap = test_compile_rules(context, "evdev", "pc104", "us,ru", NULL, - "grp:menu_toggle"); + "grp:menu_toggle,grp:lwin_latch,grp:rwin_latch_lock_clear"); assert(keymap); test_update_key(keymap); + test_update_latched_locked(keymap); test_serialisation(keymap); test_update_mask_mods(keymap); test_repeat(keymap); diff --git a/test/test.h b/test/test.h index 8a6e04178..249745db2 100644 --- a/test/test.h +++ b/test/test.h @@ -46,6 +46,11 @@ assert_printf(streq_not_null(expected, got), \ test_name ". Expected \"%s\", got: \"%s\"\n", expected, got) +#define assert_eq(test_name, expected, got, format, ...) \ + assert_printf(expected == got, \ + test_name ". Expected " format ", got: " format "\n", \ + __VA_ARGS__, expected, got) + void test_init(void); diff --git a/xkbcommon.map b/xkbcommon.map index b2507272e..4907686cd 100644 --- a/xkbcommon.map +++ b/xkbcommon.map @@ -119,3 +119,8 @@ global: xkb_compose_table_iterator_free; xkb_compose_table_iterator_next; } V_1.0.0; + +V_1.8.0 { +global: + xkb_state_update_latched_locked; +} V_1.6.0;