diff --git a/src/keymap.c b/src/keymap.c index 8e6cb67..54ac7c0 100644 --- a/src/keymap.c +++ b/src/keymap.c @@ -409,6 +409,39 @@ xkb_keymap_led_get_index(struct xkb_keymap *keymap, const char *name) return XKB_LED_INVALID; } +XKB_EXPORT size_t +xkb_keymap_key_get_mods_for_level(struct xkb_keymap *keymap, + xkb_keycode_t kc, + xkb_layout_index_t layout, + xkb_level_index_t level, + xkb_mod_mask_t *masks_out, + size_t masks_size) +{ + const struct xkb_key *key = XkbKey(keymap, kc); + if (!key) + return 0; + + layout = XkbWrapGroupIntoRange(layout, key->num_groups, + key->out_of_range_group_action, + key->out_of_range_group_number); + if (layout == XKB_LAYOUT_INVALID) + return 0; + + if (level >= XkbKeyNumLevels(key, layout)) + return 0; + + const struct xkb_key_type *type = key->groups[layout].type; + + size_t count = 0; + for (unsigned i = 0; i < type->num_entries && count < masks_size; i++) + if (entry_is_active(&type->entries[i]) && + type->entries[i].level == level) { + masks_out[count] = type->entries[i].mods.mask; + ++count; + } + return count; +} + /** * As below, but takes an explicit layout/level rather than state. */ diff --git a/src/keymap.h b/src/keymap.h index c15052b..7c5341d 100644 --- a/src/keymap.h +++ b/src/keymap.h @@ -438,6 +438,17 @@ XkbKeyNumLevels(const struct xkb_key *key, xkb_layout_index_t layout) return key->groups[layout].type->num_levels; } +/* + * If the virtual modifiers are not bound to anything, the entry + * is not active and should be skipped. xserver does this with + * cached entry->active field. + */ +static inline bool +entry_is_active(const struct xkb_key_type_entry *entry) +{ + return entry->mods.mods == 0 || entry->mods.mask != 0; +} + struct xkb_keymap * xkb_keymap_new(struct xkb_context *ctx, enum xkb_keymap_format format, diff --git a/src/state.c b/src/state.c index 2d07be4..b269e6d 100644 --- a/src/state.c +++ b/src/state.c @@ -118,17 +118,6 @@ struct xkb_state { struct xkb_keymap *keymap; }; -/* - * If the virtual modifiers are not bound to anything, the entry - * is not active and should be skipped. xserver does this with - * cached entry->active field. - */ -static bool -entry_is_active(const struct xkb_key_type_entry *entry) -{ - return entry->mods.mods == 0 || entry->mods.mask != 0; -} - static const struct xkb_key_type_entry * get_entry_for_mods(const struct xkb_key_type *type, xkb_mod_mask_t mods) { diff --git a/test/keymap.c b/test/keymap.c index 75b92c1..75e59f3 100644 --- a/test/keymap.c +++ b/test/keymap.c @@ -38,6 +38,11 @@ main(void) struct xkb_keymap *keymap; xkb_keycode_t kc; const char *keyname; + xkb_mod_mask_t masks_out[4] = { 0, 0, 0, 0 }; + size_t mask_count; + xkb_mod_mask_t shift_mask; + xkb_mod_mask_t lock_mask; + xkb_mod_mask_t mod2_mask; assert(context); @@ -59,6 +64,43 @@ main(void) keyname = xkb_keymap_key_get_name(keymap, kc); assert(streq(keyname, "COMP")); + kc = xkb_keymap_key_by_name(keymap, "AC01"); + assert(kc != XKB_KEYCODE_INVALID); + + // AC01 level 0 ('a') requires no modifiers on us-pc104 + mask_count = xkb_keymap_key_get_mods_for_level(keymap, kc, 0, 0, masks_out, 4); + assert(mask_count == 0); + + shift_mask = 1 << xkb_keymap_mod_get_index(keymap, "Shift"); + lock_mask = 1 << xkb_keymap_mod_get_index(keymap, "Lock"); + mod2_mask = 1 << xkb_keymap_mod_get_index(keymap, "Mod2"); + + // AC01 level 1 ('A') requires either Shift or Lock modifiers on us-pc104 + mask_count = xkb_keymap_key_get_mods_for_level(keymap, kc, 0, 1, masks_out, 4); + assert(mask_count == 2); + assert(masks_out[0] == shift_mask); + assert(masks_out[1] == lock_mask); + + kc = xkb_keymap_key_by_name(keymap, "KP1"); + + // KP1 level 0 ('End') requires no modifiers or Shift+Mod2 on us-pc104 + mask_count = xkb_keymap_key_get_mods_for_level(keymap, kc, 0, 0, masks_out, 4); + assert(mask_count == 2); + assert(masks_out[0] == 0); + assert(masks_out[1] == (shift_mask | mod2_mask)); + + // KP1 level 1 ('1') requires either Shift or Mod2 modifiers on us-pc104 + mask_count = xkb_keymap_key_get_mods_for_level(keymap, kc, 0, 1, masks_out, 4); + assert(mask_count == 2); + assert(masks_out[0] == shift_mask); + assert(masks_out[1] == mod2_mask); + + // Return key is not affected by modifiers on us-pc104 + kc = xkb_keymap_key_by_name(keymap, "RTRN"); + mask_count = xkb_keymap_key_get_mods_for_level(keymap, kc, 0, 0, masks_out, 4); + assert(mask_count == 1); + assert(masks_out[0] == 0); + xkb_keymap_unref(keymap); xkb_context_unref(context); } diff --git a/xkbcommon.map b/xkbcommon.map index eede3e7..b9bfd26 100644 --- a/xkbcommon.map +++ b/xkbcommon.map @@ -107,4 +107,5 @@ global: V_0.11.0 { global: xkb_utf32_to_keysym; + xkb_keymap_key_get_mods_for_level; } V_0.8.0; diff --git a/xkbcommon/xkbcommon.h b/xkbcommon/xkbcommon.h index 9d18121..5090eab 100644 --- a/xkbcommon/xkbcommon.h +++ b/xkbcommon/xkbcommon.h @@ -1162,6 +1162,48 @@ xkb_level_index_t xkb_keymap_num_levels_for_key(struct xkb_keymap *keymap, xkb_keycode_t key, xkb_layout_index_t layout); +/** + * Retrieves every possible modifier mask that produces the specified + * shift level for a specific key and layout. + * + * This API is useful for inverse key transformation; i.e. finding out + * which modifiers need to be active in order to be able to type the + * keysym(s) corresponding to the specific key code, layout and level. + * + * @warning It returns only up to masks_size modifier masks. If the + * buffer passed is too small, some of the possible modifier combinations + * will not be returned. + * + * @param[in] keymap The keymap. + * @param[in] key The keycode of the key. + * @param[in] layout The layout for which to get modifiers. + * @param[in] level The shift level in the layout for which to get the + * modifiers. This should be smaller than: + * @code xkb_keymap_num_levels_for_key(keymap, key) @endcode + * @param[out] masks_out A buffer in which the requested masks should be + * stored. + * @param[out] masks_size The size of the buffer pointed to by masks_out. + * + * If @c layout is out of range for this key (that is, larger or equal to + * the value returned by xkb_keymap_num_layouts_for_key()), it is brought + * back into range in a manner consistent with xkb_state_key_get_layout(). + * + * @returns The number of modifier masks stored in the masks_out array. + * If the key is not in the keymap or if the specified shift level cannot + * be reached it returns 0 and does not modify the masks_out buffer. + * + * @sa xkb_level_index_t + * @sa xkb_mod_mask_t + * @memberof xkb_keymap + */ +size_t +xkb_keymap_key_get_mods_for_level(struct xkb_keymap *keymap, + xkb_keycode_t key, + xkb_layout_index_t layout, + xkb_level_index_t level, + xkb_mod_mask_t *masks_out, + size_t masks_size); + /** * Get the keysyms obtained from pressing a key in a given layout and * shift level.