Commit d92a248c48227d09f6fdcfafaf339a5ff586e042

Jaroslaw Kubik 2020-02-05T17:42:06

API to query modifier set required to type a keysym The new API is useful to implement features like auto-type and desktop automation. Since the inputs for these features is usually specified in terms of the symbols that need to be typed, the implementation needs to be able to invert the keycode->keysym transformation and produce a sequence of keycodes that can be used to type the requested character(s).

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
@@ -1163,6 +1163,48 @@ 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.
  *