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 Oct 15, 2024
1 parent c50803b commit e278b15
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 14 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
6 changes: 5 additions & 1 deletion src/state.c
Original file line number Diff line number Diff line change
Expand Up @@ -1557,7 +1557,11 @@ xkb_state_mod_index_is_consumed2(struct xkb_state *state, xkb_keycode_t kc,
if (!key || idx >= xkb_keymap_num_mods(state->keymap))
return -1;

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

XKB_EXPORT int
Expand Down
105 changes: 95 additions & 10 deletions test/state.c
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,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 @@ -602,6 +607,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 All @@ -626,20 +636,27 @@ test_overlapping_mods(struct xkb_context *context)
{
struct xkb_keymap *keymap;
struct xkb_state *state;
xkb_mod_index_t altIdx, metaIdx, superIdx, hyperIdx;
xkb_mod_index_t altIdx, metaIdx, superIdx, hyperIdx, scrollIdx;
xkb_mod_mask_t alt, meta, super, hyper;

/* 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);
make_mod_idx_mask(true, keymap, mod1, XKB_MOD_NAME_MOD1);
make_mod_idx_mask(true, keymap, mod3, XKB_MOD_NAME_MOD3);
make_mod_idx_mask(true, keymap, mod4, XKB_MOD_NAME_MOD4);
make_mod_idx_mask(false, keymap, alt, XKB_VMOD_NAME_ALT);
make_mod_idx_mask(false, keymap, meta, XKB_VMOD_NAME_META);
make_mod_idx_mask(false, keymap, super, XKB_VMOD_NAME_SUPER);
make_mod_idx_mask(false, keymap, hyper, XKB_VMOD_NAME_HYPER);
make_mod_idx_mask(true, keymap, shift, XKB_MOD_NAME_SHIFT);
make_mod_idx (true, keymap, capsIdx, XKB_MOD_NAME_CAPS);
make_mod_idx_mask(true, keymap, ctrl, XKB_MOD_NAME_CTRL);
make_mod_idx_mask(true, keymap, mod1, XKB_MOD_NAME_MOD1);
make_mod_idx_mask(true, keymap, mod3, XKB_MOD_NAME_MOD3);
make_mod_idx_mask(true, keymap, mod4, XKB_MOD_NAME_MOD4);
make_mod_idx_mask(true, keymap, mod5, XKB_MOD_NAME_MOD5);
make_mod_idx_mask(false, keymap, alt, XKB_VMOD_NAME_ALT);
make_mod_idx_mask(false, keymap, meta, XKB_VMOD_NAME_META);
make_mod_idx_mask(false, keymap, super, XKB_VMOD_NAME_SUPER);
make_mod_idx_mask(false, keymap, hyper, XKB_VMOD_NAME_HYPER);
/* Note: not mapped */
make_mod_idx (false, keymap, scrollIdx, XKB_VMOD_NAME_SCROLL);
state = xkb_state_new(keymap);
assert(state);

Expand Down Expand Up @@ -668,13 +685,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);
make_mod_idx_mask(false, keymap, alt, XKB_VMOD_NAME_ALT);
make_mod_idx_mask(false, keymap, meta, XKB_VMOD_NAME_META);
Expand Down Expand Up @@ -713,6 +765,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 @@ -777,6 +851,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 e278b15

Please sign in to comment.