From b1855fabd2324d71ea347eda82648746870abbfe Mon Sep 17 00:00:00 2001 From: Pierre Le Marre Date: Tue, 1 Oct 2024 16:13:16 +0200 Subject: [PATCH] WIP: state: add API for updating latched and locked mods & layout in server state 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 +++++++ meson.build | 2 +- src/state.c | 119 ++++++++++++++ src/state.h | 20 +++ test/state.c | 298 +++++++++++++++++++++++++++++++++- test/test.h | 5 + xkbcommon.map | 5 + 7 files changed, 504 insertions(+), 2 deletions(-) create mode 100644 src/state.h diff --git a/include/xkbcommon/xkbcommon.h b/include/xkbcommon/xkbcommon.h index 219d197ba..3ce08e966 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/meson.build b/meson.build index 552e816b8..19efeabba 100644 --- a/meson.build +++ b/meson.build @@ -769,7 +769,7 @@ test( ) test( 'state', - executable('test-state', 'test/state.c', dependencies: test_dep), + executable('test-state', 'test/state.c', 'src/state.h', dependencies: test_dep), env: test_env, ) test( diff --git a/src/state.c b/src/state.c index 287f0eef8..93ba18ccf 100644 --- a/src/state.c +++ b/src/state.c @@ -64,6 +64,9 @@ #include "keymap.h" #include "keysym.h" #include "utf8.h" +#ifdef ENABLE_PRIVATE_APIS +#include "state.h" +#endif struct xkb_filter { union xkb_action action; @@ -864,6 +867,33 @@ get_state_component_changes(const struct state_components *a, return mask; } +#ifdef ENABLE_PRIVATE_APIS +void +xkb_state_set_components( + struct xkb_state *state, + int32_t base_group, + int32_t latched_group, + int32_t locked_group, + xkb_layout_index_t group, + xkb_mod_mask_t base_mods, + xkb_mod_mask_t latched_mods, + xkb_mod_mask_t locked_mods, + xkb_mod_mask_t mods, + xkb_led_mask_t leds) +{ + state->components.base_group = base_group; + state->components.base_group = base_group; + state->components.latched_group = latched_group; + state->components.locked_group = locked_group; + state->components.group = group; + state->components.base_mods = base_mods; + state->components.latched_mods = latched_mods; + state->components.locked_mods = locked_mods; + state->components.mods = mods; + state->components.leds = leds; +} +#endif + /** * Given a particular key event, updates the state structure to reflect the * new modifiers. @@ -911,6 +941,95 @@ xkb_state_update_key(struct xkb_state *state, xkb_keycode_t kc, return get_state_component_changes(&prev_components, &state->components); } +/* We need a fake key for XkbLatchModifiers and XkbLatchGroup */ +static const struct xkb_key synthetic_key = { 0 }; + +// XXX transcription from xserver +static void +XkbLatchModifiers(struct xkb_state *state, xkb_mod_mask_t mask, xkb_mod_mask_t latches) +{ + const struct xkb_key *key = &synthetic_key; + + xkb_mod_mask_t clear = mask & (~latches); + state->components.latched_mods &= ~clear; + /* Clear any pending latch to locks. */ + xkb_filter_apply_all(state, key, XKB_KEY_DOWN); + + union xkb_action latch_mods = { + .mods = { + .type = ACTION_TYPE_MOD_LATCH, + .mods = { + .mask = mask & latches, + }, + .flags = 0, + }, + }; + struct xkb_filter *filter = xkb_filter_new(state); + filter->key = key; + filter->func = filter_action_funcs[latch_mods.type].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); +} + +// XXX transcription from xserver +static void +XkbLatchGroup(struct xkb_state *state, int32_t group) +{ + const struct xkb_key *key = &synthetic_key; + + 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 = filter_action_funcs[latch_group.type].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) +{ + struct state_components prev_components = state->components; + + if (affect_locked_mods) { + state->components.locked_mods &= ~affect_locked_mods; + state->components.locked_mods |= locked_mods & affect_locked_mods; + } + + if (affect_locked_layout) { + state->components.locked_group = locked_layout; + } + + if (affect_latched_mods) { + XkbLatchModifiers(state, affect_latched_mods, latched_mods); + } + + if (affect_latched_layout) { + XkbLatchGroup(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 diff --git a/src/state.h b/src/state.h new file mode 100644 index 000000000..6b86b40cb --- /dev/null +++ b/src/state.h @@ -0,0 +1,20 @@ +#ifndef STATE_H +#define STATE_H + +#include "xkbcommon/xkbcommon.h" +#include "keymap.h" + +void +xkb_state_set_components( + struct xkb_state *state, + int32_t base_group, + int32_t latched_group, + int32_t locked_group, + xkb_layout_index_t group, + xkb_mod_mask_t base_mods, + xkb_mod_mask_t latched_mods, + xkb_mod_mask_t locked_mods, + xkb_mod_mask_t mods, + xkb_led_mask_t leds); + +#endif diff --git a/test/state.c b/test/state.c index 283c47163..c29de37b1 100644 --- a/test/state.c +++ b/test/state.c @@ -30,6 +30,8 @@ #include #include "evdev-scancodes.h" +#include "src/state.h" +#include "src/keysym.h" #include "test.h" /* Offset between evdev keycodes (where KEY_ESCAPE is 1), and the evdev XKB @@ -103,6 +105,124 @@ 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); + fprintf(stderr, "Got state:\n"); + print_state(got); + } + return ok; +} + +static bool +check_update_state(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_set_components(expected, + components->base_group, + components->latched_group, + components->locked_group, + components->group, + components->base_mods, + components->latched_mods, + components->locked_mods, + components->mods, + components->leds); + + 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) { @@ -245,6 +365,181 @@ test_update_key(struct xkb_keymap *keymap) xkb_state_unref(state); } +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); + assert(capslock_led_idx != XKB_LED_INVALID); + 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"); + assert(group2_led_idx != XKB_LED_INVALID); + 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); + assert(shift_idx != XKB_MOD_INVALID); + xkb_mod_mask_t shift = 1u << shift_idx; + xkb_mod_index_t capslock_idx = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_CAPS); + assert(capslock_idx != XKB_MOD_INVALID); + xkb_mod_mask_t capslock = 1u << capslock_idx; + xkb_mod_index_t control_idx = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_CTRL); + assert(control_idx != XKB_MOD_INVALID); + xkb_mod_mask_t control = 1u << control_idx; + xkb_mod_index_t level3_idx = xkb_keymap_mod_get_index(keymap, "LevelThree"); + assert(level3_idx != XKB_MOD_INVALID); + xkb_mod_mask_t level3 = 1u << level3_idx; + +#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 } + +#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 } + +#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 */ + 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) + }; + 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(&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) { @@ -714,7 +1009,7 @@ main(void) { test_init(); - struct xkb_context *context = test_get_context(0); + struct xkb_context *context = test_get_context(CONTEXT_NO_FLAG); struct xkb_keymap *keymap; assert(context); @@ -728,6 +1023,7 @@ main(void) 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;