Skip to content

Commit

Permalink
state: Support querying whether virtual mods are consumed
Browse files Browse the repository at this point in the history
Previously it was not possible to query the status of virtual modifiers
with the following functions:
- `xkb_state_mod_index_is_consumed`
- `xkb_state_mod_index_is_consumed2`

Note that it may *overmatch* if some modifier mappings overlap. For
example, the default “us” PC layout maps both “Alt” and “Meta” to the
real modifier “Mod1”; thus “Mod1”, “Alt” and “Meta” modifiers will
return the same result with these functions.
  • Loading branch information
wismill committed Nov 29, 2024
1 parent 31a841a commit 5fbc0ec
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 7 deletions.
2 changes: 2 additions & 0 deletions changes/api/+query-virtual-modifiers-state.bugfix.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ with *any* modifiers (real *and* virtual):
- `xkb_state_mod_indices_are_active`
- `xkb_state_mod_name_is_active`
- `xkb_state_mod_names_are_active`
- `xkb_state_mod_index_is_consumed`
- `xkb_state_mod_index_is_consumed2`

Warning: they may overmatch in case there are overlappings virtual-to-real
modifiers mappings.
22 changes: 19 additions & 3 deletions include/xkbcommon/xkbcommon.h
Original file line number Diff line number Diff line change
Expand Up @@ -1918,10 +1918,12 @@ enum xkb_consumed_mode {
* @param key The keycode of the key.
* @param mode The consumed modifiers mode to use; see enum description.
*
* @returns a mask of the consumed modifiers.
* @returns a mask of the consumed [real modifiers] modifiers.
*
* @memberof xkb_state
* @since 0.7.0
*
* [real modifiers]: @ref real-modifier-def
*/
xkb_mod_mask_t
xkb_state_key_get_consumed_mods2(struct xkb_state *state, xkb_keycode_t key,
Expand All @@ -1940,6 +1942,9 @@ xkb_state_key_get_consumed_mods(struct xkb_state *state, xkb_keycode_t key);
* Test whether a modifier is consumed by keyboard state translation for
* a key.
*
* @warning For [virtual modifiers], this function may *overmatch* in case
* there are virtual modifiers with overlapping mappings to [real modifiers].
*
* @param state The keyboard state.
* @param key The keycode of the key.
* @param idx The index of the modifier to check.
Expand All @@ -1951,7 +1956,11 @@ xkb_state_key_get_consumed_mods(struct xkb_state *state, xkb_keycode_t key);
* @sa xkb_state_mod_mask_remove_consumed()
* @sa xkb_state_key_get_consumed_mods()
* @memberof xkb_state
* @since 0.7.0
* @since 0.7.0: Works only with *real* modifiers
* @since 1.8.0: Works also with *virtual* modifiers
*
* [virtual modifiers]: @ref virtual-modifier-def
* [real modifiers]: @ref real-modifier-def
*/
int
xkb_state_mod_index_is_consumed2(struct xkb_state *state,
Expand All @@ -1962,8 +1971,15 @@ xkb_state_mod_index_is_consumed2(struct xkb_state *state,
/**
* Same as xkb_state_mod_index_is_consumed2() with mode XKB_CONSUMED_MOD_XKB.
*
* @warning For [virtual modifiers], this function may *overmatch* in case
* there are virtual modifiers with overlapping mappings to [real modifiers].
*
* @memberof xkb_state
* @since 0.4.1
* @since 0.4.1: Works only with *real* modifiers
* @since 1.8.0: Works also with *virtual* modifiers
*
* [virtual modifiers]: @ref virtual-modifier-def
* [real modifiers]: @ref real-modifier-def
*/
int
xkb_state_mod_index_is_consumed(struct xkb_state *state, xkb_keycode_t key,
Expand Down
9 changes: 7 additions & 2 deletions src/state.c
Original file line number Diff line number Diff line change
Expand Up @@ -1547,10 +1547,15 @@ xkb_state_mod_index_is_consumed2(struct xkb_state *state, xkb_keycode_t kc,
{
const struct xkb_key *key = XkbKey(state->keymap, kc);

if (!key || idx >= xkb_keymap_num_mods(state->keymap))
if (unlikely(!key || idx >= xkb_keymap_num_mods(state->keymap)))
return -1;

return !!((1u << idx) & key_get_consumed(state, key, mode));
xkb_mod_mask_t mapping = mod_mapping(&state->keymap->mods.mods[idx], idx);
if (!mapping) {
/* Modifier not mapped */
return 0;
}
return !!((mapping & key_get_consumed(state, key, mode)) == mapping);
}

XKB_EXPORT int
Expand Down
94 changes: 92 additions & 2 deletions test/state.c
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,8 @@ test_consume(struct xkb_keymap *keymap)
xkb_mod_index_t ctrl = _xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_CTRL);
xkb_mod_index_t mod1 = _xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_MOD1);
xkb_mod_index_t mod5 = _xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_MOD5);
xkb_mod_index_t alt = _xkb_keymap_mod_get_index(keymap, XKB_VMOD_NAME_ALT);
xkb_mod_index_t meta = _xkb_keymap_mod_get_index(keymap, XKB_VMOD_NAME_META);
struct xkb_state *state = xkb_state_new(keymap);
assert(state);

Expand All @@ -550,6 +552,11 @@ test_consume(struct xkb_keymap *keymap)
mask = xkb_state_key_get_consumed_mods(state, KEY_ESC + EVDEV_OFFSET);
assert(mask == 0);

assert(xkb_state_mod_index_is_consumed(state, KEY_EQUAL + EVDEV_OFFSET, shift) > 0);
assert(xkb_state_mod_index_is_consumed(state, KEY_EQUAL + EVDEV_OFFSET, mod1) == 0);
assert(xkb_state_mod_index_is_consumed(state, KEY_EQUAL + EVDEV_OFFSET, alt) == 0);
assert(xkb_state_mod_index_is_consumed(state, KEY_EQUAL + EVDEV_OFFSET, meta) == 0);

xkb_state_unref(state);

/* Test is_consumed() - simple ALPHABETIC type. */
Expand Down Expand Up @@ -611,6 +618,11 @@ test_consume(struct xkb_keymap *keymap)
mask = xkb_state_key_get_consumed_mods2(state, KEY_F1 + EVDEV_OFFSET,
XKB_CONSUMED_MODE_GTK);
assert(mask == ((1U << mod1) | (1U << ctrl)));
assert(xkb_state_mod_index_is_consumed(state, KEY_F1 + EVDEV_OFFSET, shift) > 0);
assert(xkb_state_mod_index_is_consumed(state, KEY_F1 + EVDEV_OFFSET, ctrl) > 0);
assert(xkb_state_mod_index_is_consumed(state, KEY_F1 + EVDEV_OFFSET, mod1) > 0);
assert(xkb_state_mod_index_is_consumed(state, KEY_F1 + EVDEV_OFFSET, alt) > 0);
assert(xkb_state_mod_index_is_consumed(state, KEY_F1 + EVDEV_OFFSET, meta) > 0);

xkb_state_unref(state);

Expand Down Expand Up @@ -638,18 +650,28 @@ test_overlapping_mods(struct xkb_context *context)

/* Super and Hyper are overlapping (full overlap) */
keymap = test_compile_rules(context, "evdev", NULL, "us", NULL,
"overlapping_modifiers:super_hyper");
"overlapping_modifiers:super_hyper,"
"grp:win_space_toggle");
assert(keymap);
xkb_mod_index_t shiftIdx = _xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_SHIFT);
xkb_mod_index_t capsIdx = _xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_CAPS);
xkb_mod_index_t ctrlIdx = _xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_CTRL);
xkb_mod_index_t mod1Idx = _xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_MOD1);
xkb_mod_index_t mod3Idx = _xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_MOD3);
xkb_mod_index_t mod4Idx = _xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_MOD4);
xkb_mod_index_t mod5Idx = _xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_MOD5);
xkb_mod_index_t altIdx = _xkb_keymap_mod_get_index(keymap, XKB_VMOD_NAME_ALT);
xkb_mod_index_t metaIdx = _xkb_keymap_mod_get_index(keymap, XKB_VMOD_NAME_META);
xkb_mod_index_t superIdx = _xkb_keymap_mod_get_index(keymap, XKB_VMOD_NAME_SUPER);
xkb_mod_index_t hyperIdx = _xkb_keymap_mod_get_index(keymap, XKB_VMOD_NAME_HYPER);
/* Note: not mapped */
xkb_mod_index_t scrollIdx = _xkb_keymap_mod_get_index(keymap, XKB_VMOD_NAME_SCROLL);
xkb_mod_mask_t shift = (1u << shiftIdx);
xkb_mod_mask_t ctrl = (1u << ctrlIdx);
xkb_mod_mask_t mod1 = (1u << mod1Idx);
xkb_mod_mask_t mod3 = (1u << mod3Idx);
xkb_mod_mask_t mod4 = (1u << mod4Idx);
xkb_mod_mask_t mod5 = (1u << mod5Idx);
xkb_mod_mask_t alt = (1u << altIdx);
xkb_mod_mask_t meta = (1u << metaIdx);
xkb_mod_mask_t super = (1u << superIdx);
Expand Down Expand Up @@ -682,13 +704,48 @@ test_overlapping_mods(struct xkb_context *context)
XKB_STATE_MATCH_ALL,
mod3Idx, mod4Idx, superIdx, hyperIdx,
XKB_MOD_INVALID) > 0);
assert(xkb_state_key_get_consumed_mods2(state, KEY_F1 + EVDEV_OFFSET, XKB_CONSUMED_MODE_XKB) ==
(shift | ctrl | mod1 | mod5));
assert(xkb_state_mod_index_is_consumed2(state, KEY_F1 + EVDEV_OFFSET, shiftIdx, XKB_CONSUMED_MODE_XKB) > 0);
assert(xkb_state_mod_index_is_consumed2(state, KEY_F1 + EVDEV_OFFSET, capsIdx, XKB_CONSUMED_MODE_XKB) == 0);
assert(xkb_state_mod_index_is_consumed2(state, KEY_F1 + EVDEV_OFFSET, ctrlIdx, XKB_CONSUMED_MODE_XKB) > 0);
assert(xkb_state_mod_index_is_consumed2(state, KEY_F1 + EVDEV_OFFSET, mod1Idx, XKB_CONSUMED_MODE_XKB) > 0);
assert(xkb_state_mod_index_is_consumed2(state, KEY_F1 + EVDEV_OFFSET, mod5Idx, XKB_CONSUMED_MODE_XKB) > 0);
assert(xkb_state_mod_index_is_consumed2(state, KEY_F1 + EVDEV_OFFSET, altIdx, XKB_CONSUMED_MODE_XKB) > 0);
assert(xkb_state_mod_index_is_consumed2(state, KEY_F1 + EVDEV_OFFSET, metaIdx, XKB_CONSUMED_MODE_XKB) > 0);
assert(xkb_state_mod_index_is_consumed2(state, KEY_F1 + EVDEV_OFFSET, superIdx, XKB_CONSUMED_MODE_XKB) == 0);
assert(xkb_state_mod_index_is_consumed2(state, KEY_F1 + EVDEV_OFFSET, hyperIdx, XKB_CONSUMED_MODE_XKB) == 0);
assert(xkb_state_mod_index_is_consumed2(state, KEY_F1 + EVDEV_OFFSET, scrollIdx, XKB_CONSUMED_MODE_XKB) == 0);
assert(xkb_state_key_get_consumed_mods2(state, KEY_SPACE + EVDEV_OFFSET, XKB_CONSUMED_MODE_XKB) == mod4);
assert(xkb_state_mod_index_is_consumed2(state, KEY_SPACE + EVDEV_OFFSET, shiftIdx, XKB_CONSUMED_MODE_XKB) == 0);
assert(xkb_state_mod_index_is_consumed2(state, KEY_SPACE + EVDEV_OFFSET, capsIdx, XKB_CONSUMED_MODE_XKB) == 0);
assert(xkb_state_mod_index_is_consumed2(state, KEY_SPACE + EVDEV_OFFSET, ctrlIdx, XKB_CONSUMED_MODE_XKB) == 0);
assert(xkb_state_mod_index_is_consumed2(state, KEY_SPACE + EVDEV_OFFSET, mod1Idx, XKB_CONSUMED_MODE_XKB) == 0);
assert(xkb_state_mod_index_is_consumed2(state, KEY_SPACE + EVDEV_OFFSET, mod5Idx, XKB_CONSUMED_MODE_XKB) == 0);
assert(xkb_state_mod_index_is_consumed2(state, KEY_SPACE + EVDEV_OFFSET, altIdx, XKB_CONSUMED_MODE_XKB) == 0);
assert(xkb_state_mod_index_is_consumed2(state, KEY_SPACE + EVDEV_OFFSET, metaIdx, XKB_CONSUMED_MODE_XKB) == 0);
assert(xkb_state_mod_index_is_consumed2(state, KEY_SPACE + EVDEV_OFFSET, superIdx, XKB_CONSUMED_MODE_XKB) == 0);
assert(xkb_state_mod_index_is_consumed2(state, KEY_SPACE + EVDEV_OFFSET, hyperIdx, XKB_CONSUMED_MODE_XKB) == 0);
assert(xkb_state_mod_index_is_consumed2(state, KEY_SPACE + EVDEV_OFFSET, scrollIdx, XKB_CONSUMED_MODE_XKB) == 0);
xkb_state_update_mask(state, mod4, 0, 0, 0, 0, 0);
assert(xkb_state_mod_index_is_consumed2(state, KEY_SPACE + EVDEV_OFFSET, shiftIdx, XKB_CONSUMED_MODE_XKB) == 0);
assert(xkb_state_mod_index_is_consumed2(state, KEY_SPACE + EVDEV_OFFSET, capsIdx, XKB_CONSUMED_MODE_XKB) == 0);
assert(xkb_state_mod_index_is_consumed2(state, KEY_SPACE + EVDEV_OFFSET, ctrlIdx, XKB_CONSUMED_MODE_XKB) == 0);
assert(xkb_state_mod_index_is_consumed2(state, KEY_SPACE + EVDEV_OFFSET, mod1Idx, XKB_CONSUMED_MODE_XKB) == 0);
assert(xkb_state_mod_index_is_consumed2(state, KEY_SPACE + EVDEV_OFFSET, mod5Idx, XKB_CONSUMED_MODE_XKB) == 0);
assert(xkb_state_mod_index_is_consumed2(state, KEY_SPACE + EVDEV_OFFSET, altIdx, XKB_CONSUMED_MODE_XKB) == 0);
assert(xkb_state_mod_index_is_consumed2(state, KEY_SPACE + EVDEV_OFFSET, metaIdx, XKB_CONSUMED_MODE_XKB) == 0);
assert(xkb_state_mod_index_is_consumed2(state, KEY_SPACE + EVDEV_OFFSET, superIdx, XKB_CONSUMED_MODE_XKB) == 0);
assert(xkb_state_mod_index_is_consumed2(state, KEY_SPACE + EVDEV_OFFSET, hyperIdx, XKB_CONSUMED_MODE_XKB) == 0);
assert(xkb_state_mod_index_is_consumed2(state, KEY_SPACE + EVDEV_OFFSET, scrollIdx, XKB_CONSUMED_MODE_XKB) == 0);
xkb_state_unref(state);
xkb_keymap_unref(keymap);

/* Super and Hyper are overlapping (full overlap).
* Alt overlaps with Meta (incomplete overlap) */
keymap = test_compile_rules(context, "evdev", NULL, "us", NULL,
"overlapping_modifiers:meta");
"overlapping_modifiers:meta,"
"grp:win_space_toggle");
assert(keymap);
altIdx = _xkb_keymap_mod_get_index(keymap, XKB_VMOD_NAME_ALT);
metaIdx = _xkb_keymap_mod_get_index(keymap, XKB_VMOD_NAME_META);
Expand Down Expand Up @@ -731,6 +788,28 @@ test_overlapping_mods(struct xkb_context *context)
mod1Idx, mod3Idx, mod4Idx, altIdx,
metaIdx, superIdx, hyperIdx,
XKB_MOD_INVALID) > 0);
assert(xkb_state_key_get_consumed_mods2(state, KEY_F1 + EVDEV_OFFSET, XKB_CONSUMED_MODE_XKB) ==
(shift | ctrl | mod1 | mod5));
assert(xkb_state_mod_index_is_consumed2(state, KEY_F1 + EVDEV_OFFSET, shiftIdx, XKB_CONSUMED_MODE_XKB) > 0);
assert(xkb_state_mod_index_is_consumed2(state, KEY_F1 + EVDEV_OFFSET, capsIdx, XKB_CONSUMED_MODE_XKB) == 0);
assert(xkb_state_mod_index_is_consumed2(state, KEY_F1 + EVDEV_OFFSET, ctrlIdx, XKB_CONSUMED_MODE_XKB) > 0);
assert(xkb_state_mod_index_is_consumed2(state, KEY_F1 + EVDEV_OFFSET, mod1Idx, XKB_CONSUMED_MODE_XKB) > 0);
assert(xkb_state_mod_index_is_consumed2(state, KEY_F1 + EVDEV_OFFSET, mod5Idx, XKB_CONSUMED_MODE_XKB) > 0);
assert(xkb_state_mod_index_is_consumed2(state, KEY_F1 + EVDEV_OFFSET, altIdx, XKB_CONSUMED_MODE_XKB) > 0);
assert(xkb_state_mod_index_is_consumed2(state, KEY_F1 + EVDEV_OFFSET, metaIdx, XKB_CONSUMED_MODE_XKB) == 0);
assert(xkb_state_mod_index_is_consumed2(state, KEY_F1 + EVDEV_OFFSET, superIdx, XKB_CONSUMED_MODE_XKB) == 0);
assert(xkb_state_mod_index_is_consumed2(state, KEY_F1 + EVDEV_OFFSET, hyperIdx, XKB_CONSUMED_MODE_XKB) == 0);
assert(xkb_state_key_get_consumed_mods2(state, KEY_SPACE + EVDEV_OFFSET, XKB_CONSUMED_MODE_XKB) ==
mod4);
assert(xkb_state_mod_index_is_consumed2(state, KEY_SPACE + EVDEV_OFFSET, shiftIdx, XKB_CONSUMED_MODE_XKB) == 0);
assert(xkb_state_mod_index_is_consumed2(state, KEY_SPACE + EVDEV_OFFSET, capsIdx, XKB_CONSUMED_MODE_XKB) == 0);
assert(xkb_state_mod_index_is_consumed2(state, KEY_SPACE + EVDEV_OFFSET, ctrlIdx, XKB_CONSUMED_MODE_XKB) == 0);
assert(xkb_state_mod_index_is_consumed2(state, KEY_SPACE + EVDEV_OFFSET, mod1Idx, XKB_CONSUMED_MODE_XKB) == 0);
assert(xkb_state_mod_index_is_consumed2(state, KEY_SPACE + EVDEV_OFFSET, mod5Idx, XKB_CONSUMED_MODE_XKB) == 0);
assert(xkb_state_mod_index_is_consumed2(state, KEY_SPACE + EVDEV_OFFSET, altIdx, XKB_CONSUMED_MODE_XKB) == 0);
assert(xkb_state_mod_index_is_consumed2(state, KEY_SPACE + EVDEV_OFFSET, metaIdx, XKB_CONSUMED_MODE_XKB) == 0);
assert(xkb_state_mod_index_is_consumed2(state, KEY_SPACE + EVDEV_OFFSET, superIdx, XKB_CONSUMED_MODE_XKB) > 0);
assert(xkb_state_mod_index_is_consumed2(state, KEY_SPACE + EVDEV_OFFSET, hyperIdx, XKB_CONSUMED_MODE_XKB) > 0);
xkb_state_update_mask(state, mod1, 0, 0, 0, 0, 0);
assert(xkb_state_mod_indices_are_active(state, XKB_STATE_MODS_EFFECTIVE,
XKB_STATE_MATCH_ANY,
Expand Down Expand Up @@ -799,6 +878,17 @@ test_overlapping_mods(struct xkb_context *context)
mod1Idx, mod3Idx, mod4Idx, altIdx,
metaIdx, superIdx, hyperIdx,
XKB_MOD_INVALID) > 0);
assert(xkb_state_key_get_consumed_mods2(state, KEY_F1 + EVDEV_OFFSET, XKB_CONSUMED_MODE_XKB) ==
(shift | ctrl | mod1 | mod5));
assert(xkb_state_mod_index_is_consumed2(state, KEY_F1 + EVDEV_OFFSET, shiftIdx, XKB_CONSUMED_MODE_XKB) > 0);
assert(xkb_state_mod_index_is_consumed2(state, KEY_F1 + EVDEV_OFFSET, capsIdx, XKB_CONSUMED_MODE_XKB) == 0);
assert(xkb_state_mod_index_is_consumed2(state, KEY_F1 + EVDEV_OFFSET, ctrlIdx, XKB_CONSUMED_MODE_XKB) > 0);
assert(xkb_state_mod_index_is_consumed2(state, KEY_F1 + EVDEV_OFFSET, mod1Idx, XKB_CONSUMED_MODE_XKB) > 0);
assert(xkb_state_mod_index_is_consumed2(state, KEY_F1 + EVDEV_OFFSET, mod5Idx, XKB_CONSUMED_MODE_XKB) > 0);
assert(xkb_state_mod_index_is_consumed2(state, KEY_F1 + EVDEV_OFFSET, altIdx, XKB_CONSUMED_MODE_XKB) > 0);
assert(xkb_state_mod_index_is_consumed2(state, KEY_F1 + EVDEV_OFFSET, metaIdx, XKB_CONSUMED_MODE_XKB) == 0);
assert(xkb_state_mod_index_is_consumed2(state, KEY_F1 + EVDEV_OFFSET, superIdx, XKB_CONSUMED_MODE_XKB) == 0);
assert(xkb_state_mod_index_is_consumed2(state, KEY_F1 + EVDEV_OFFSET, hyperIdx, XKB_CONSUMED_MODE_XKB) == 0);
xkb_state_update_mask(state, mod1 | mod3, 0, 0, 0, 0, 0);
assert(xkb_state_mod_indices_are_active(state, XKB_STATE_MODS_EFFECTIVE,
XKB_STATE_MATCH_ANY,
Expand Down

0 comments on commit 5fbc0ec

Please sign in to comment.