Skip to content

Commit

Permalink
state: support querying whether virtual modifiers are active
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_active`
- `xkb_state_mod_indices_are_active`
- `xkb_state_mod_name_is_active`
- `xkb_state_mod_names_are_active`

Note that it may *overmatch* if some modifier mappings overlap. For
example, the default “us” PC layout maps “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 Sep 23, 2024
1 parent 01cabcb commit 946ec04
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 13 deletions.
9 changes: 9 additions & 0 deletions changes/api/+query-virtual-modifiers-state.bugfix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
The following functions now allow to query also *virtual* modifiers, so they work
with *any* modifiers (real *and* virtual):
- `xkb_state_mod_index_is_active`
- `xkb_state_mod_indices_are_active`
- `xkb_state_mod_name_is_active`
- `xkb_state_mod_names_are_active`

Warning: they may overmatch in case there are overlappings virtual-to-real
modifiers mappings.
32 changes: 32 additions & 0 deletions include/xkbcommon/xkbcommon.h
Original file line number Diff line number Diff line change
Expand Up @@ -1706,10 +1706,18 @@ xkb_state_serialize_layout(struct xkb_state *state,
/**
* Test whether a modifier is active in a given keyboard state by name.
*
* @warning For [virtual modifiers], this function may *overmatch* in case
* there are virtual modifiers with overlapping mappings to real modifiers.
*
* @returns 1 if the modifier is active, 0 if it is not. If the modifier
* name does not exist in the keymap, returns -1.
*
* @memberof xkb_state
*
* @since 0.1.0: Works only with *real* modifiers
* @since 1.8.0: Works also with *virtual* modifiers
*
* [virtual modifiers]: @ref virtual-modifier-def
*/
int
xkb_state_mod_name_is_active(struct xkb_state *state, const char *name,
Expand All @@ -1719,6 +1727,9 @@ xkb_state_mod_name_is_active(struct xkb_state *state, const char *name,
* Test whether a set of modifiers are active in a given keyboard state by
* name.
*
* @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 type The component of the state against which to match the
* given modifiers.
Expand All @@ -1731,6 +1742,11 @@ xkb_state_mod_name_is_active(struct xkb_state *state, const char *name,
* the modifier names do not exist in the keymap, returns -1.
*
* @memberof xkb_state
*
* @since 0.1.0: Works only with *real* modifiers
* @since 1.8.0: Works also with *virtual* modifiers
*
* [virtual modifiers]: @ref virtual-modifier-def
*/
int
xkb_state_mod_names_are_active(struct xkb_state *state,
Expand All @@ -1741,10 +1757,18 @@ xkb_state_mod_names_are_active(struct xkb_state *state,
/**
* Test whether a modifier is active in a given keyboard state by index.
*
* @warning For [virtual modifiers], this function may *overmatch* in case
* there are virtual modifiers with overlapping mappings to real modifiers.
*
* @returns 1 if the modifier is active, 0 if it is not. If the modifier
* index is invalid in the keymap, returns -1.
*
* @memberof xkb_state
*
* @since 0.1.0: Works only with *real* modifiers
* @since 1.8.0: Works also with *virtual* modifiers
*
* [virtual modifiers]: @ref virtual-modifier-def
*/
int
xkb_state_mod_index_is_active(struct xkb_state *state, xkb_mod_index_t idx,
Expand All @@ -1754,6 +1778,9 @@ xkb_state_mod_index_is_active(struct xkb_state *state, xkb_mod_index_t idx,
* Test whether a set of modifiers are active in a given keyboard state by
* index.
*
* @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 type The component of the state against which to match the
* given modifiers.
Expand All @@ -1766,6 +1793,11 @@ xkb_state_mod_index_is_active(struct xkb_state *state, xkb_mod_index_t idx,
* the modifier indices are invalid in the keymap, returns -1.
*
* @memberof xkb_state
*
* @since 0.1.0: Works only with *real* modifiers
* @since 1.8.0: Works also with *virtual* modifiers
*
* [virtual modifiers]: @ref virtual-modifier-def
*/
int
xkb_state_mod_indices_are_active(struct xkb_state *state,
Expand Down
30 changes: 27 additions & 3 deletions src/state.c
Original file line number Diff line number Diff line change
Expand Up @@ -1282,6 +1282,11 @@ mod_mask_get_effective(struct xkb_keymap *keymap, xkb_mod_mask_t mods)
return mask;
}

static inline bool
is_real_mod(struct xkb_keymap *keymap, xkb_mod_index_t mod) {
return keymap->mods.mods[mod].type & MOD_REAL;
}

/**
* Returns 1 if the given modifier is active with the specified type(s), 0 if
* not, or -1 if the modifier is invalid.
Expand All @@ -1294,7 +1299,13 @@ xkb_state_mod_index_is_active(struct xkb_state *state,
if (idx >= xkb_keymap_num_mods(state->keymap))
return -1;

return !!(xkb_state_serialize_mods(state, type) & (1u << idx));
if (is_real_mod(state->keymap, idx)) {
return !!(xkb_state_serialize_mods(state, type) & (1u << idx));
} else {
/* WARNING: this may overmatch */
xkb_mod_mask_t mapping = state->keymap->mods.mods[idx].mapping;
return !!((xkb_state_serialize_mods(state, type) & mapping) == mapping);
}
}

/**
Expand All @@ -1318,6 +1329,19 @@ match_mod_masks(struct xkb_state *state,
return (active & wanted) == wanted;
}

/*
* Get the mapping of a modifier.
* We cannot use `mods.mods[idx].mapping` directly, because it is
* not set for real modifiers.
*/
static inline xkb_mod_mask_t
get_mod_mapping(struct xkb_keymap *keymap, xkb_mod_index_t idx)
{
return is_real_mod(keymap, idx)
? (1u << idx)
: keymap->mods.mods[idx].mapping;
}

/**
* Returns 1 if the modifiers are active with the specified type(s), 0 if
* not, or -1 if any of the modifiers are invalid.
Expand All @@ -1342,7 +1366,7 @@ xkb_state_mod_indices_are_active(struct xkb_state *state,
ret = -1;
break;
}
wanted |= (1u << idx);
wanted |= get_mod_mapping(state->keymap, idx);
}
va_end(ap);

Expand Down Expand Up @@ -1393,7 +1417,7 @@ xkb_state_mod_names_are_active(struct xkb_state *state,
ret = -1;
break;
}
wanted |= (1u << idx);
wanted |= get_mod_mapping(state->keymap, idx);
}
va_end(ap);

Expand Down
77 changes: 67 additions & 10 deletions test/state.c
Original file line number Diff line number Diff line change
Expand Up @@ -126,56 +126,104 @@ test_update_key(struct xkb_keymap *keymap)
print_state(state);
assert(xkb_state_mod_name_is_active(state, XKB_MOD_NAME_CTRL,
XKB_STATE_MODS_DEPRESSED) > 0);
assert(xkb_state_mod_name_is_active(state, XKB_MOD_NAME_MOD1,
XKB_STATE_MODS_DEPRESSED) > 0);
assert(xkb_state_mod_name_is_active(state, XKB_MOD_NAME_ALT,
XKB_STATE_MODS_DEPRESSED) > 0);
assert(xkb_state_mod_name_is_active(state, XKB_MOD_NAME_META,
XKB_STATE_MODS_DEPRESSED) > 0);
assert(xkb_state_mod_names_are_active(state, XKB_STATE_MODS_DEPRESSED,
XKB_STATE_MATCH_ALL,
XKB_MOD_NAME_CTRL,
XKB_MOD_NAME_MOD1,
XKB_MOD_NAME_ALT,
XKB_MOD_NAME_META,
NULL) > 0);
assert(xkb_state_mod_indices_are_active(state, XKB_STATE_MODS_DEPRESSED,
XKB_STATE_MATCH_ALL,
xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_CTRL),
xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_MOD1),
xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_ALT),
xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_META),
XKB_MOD_INVALID) > 0);
assert(xkb_state_mod_names_are_active(state, XKB_STATE_MODS_DEPRESSED,
XKB_STATE_MATCH_ALL,
XKB_MOD_NAME_MOD1,
NULL) == 0);
assert(xkb_state_mod_names_are_active(state, XKB_STATE_MODS_DEPRESSED,
XKB_STATE_MATCH_ALL,
XKB_MOD_NAME_ALT,
NULL) == 0);
assert(xkb_state_mod_names_are_active(state, XKB_STATE_MODS_DEPRESSED,
XKB_STATE_MATCH_ALL,
XKB_MOD_NAME_META,
NULL) == 0);
assert(xkb_state_mod_names_are_active(state, XKB_STATE_MODS_DEPRESSED,
XKB_STATE_MATCH_ALL |
XKB_STATE_MATCH_NON_EXCLUSIVE,
XKB_MOD_NAME_MOD1,
NULL) > 0);
assert(xkb_state_mod_names_are_active(state, XKB_STATE_MODS_DEPRESSED,
XKB_STATE_MATCH_ALL |
XKB_STATE_MATCH_NON_EXCLUSIVE,
XKB_MOD_NAME_ALT,
NULL) > 0);
assert(xkb_state_mod_names_are_active(state, XKB_STATE_MODS_DEPRESSED,
XKB_STATE_MATCH_ALL |
XKB_STATE_MATCH_NON_EXCLUSIVE,
XKB_MOD_NAME_META,
NULL) > 0);
assert(xkb_state_mod_names_are_active(state, XKB_STATE_MODS_DEPRESSED,
(XKB_STATE_MATCH_ANY |
XKB_STATE_MATCH_NON_EXCLUSIVE),
XKB_MOD_NAME_MOD1,
NULL) > 0);
assert(xkb_state_mod_names_are_active(state, XKB_STATE_MODS_DEPRESSED,
(XKB_STATE_MATCH_ANY |
XKB_STATE_MATCH_NON_EXCLUSIVE),
XKB_MOD_NAME_ALT,
NULL) > 0);
assert(xkb_state_mod_names_are_active(state, XKB_STATE_MODS_DEPRESSED,
(XKB_STATE_MATCH_ANY |
XKB_STATE_MATCH_NON_EXCLUSIVE),
XKB_MOD_NAME_META,
NULL) > 0);

/* RAlt down */
xkb_state_update_key(state, KEY_LEFTCTRL + EVDEV_OFFSET, XKB_KEY_UP);
fprintf(stderr, "dumping state for RAlt down:\n");
print_state(state);
assert(xkb_state_mod_name_is_active(state, XKB_MOD_NAME_CTRL,
XKB_STATE_MODS_EFFECTIVE) == 0);
assert(xkb_state_mod_name_is_active(state, XKB_MOD_NAME_MOD1,
XKB_STATE_MODS_DEPRESSED) > 0);
assert(xkb_state_mod_name_is_active(state, XKB_MOD_NAME_ALT,
XKB_STATE_MODS_DEPRESSED) > 0);
assert(xkb_state_mod_name_is_active(state, XKB_MOD_NAME_META,
XKB_STATE_MODS_DEPRESSED) > 0);
assert(xkb_state_mod_names_are_active(state, XKB_STATE_MODS_DEPRESSED,
XKB_STATE_MATCH_ANY,
XKB_MOD_NAME_CTRL,
XKB_MOD_NAME_MOD1,
XKB_MOD_NAME_ALT,
XKB_MOD_NAME_META,
NULL) > 0);
assert(xkb_state_mod_names_are_active(state, XKB_STATE_MODS_LATCHED,
XKB_STATE_MATCH_ANY,
XKB_MOD_NAME_CTRL,
XKB_MOD_NAME_MOD1,
XKB_MOD_NAME_ALT,
XKB_MOD_NAME_META,
NULL) == 0);

/* none down */
xkb_state_update_key(state, KEY_RIGHTALT + EVDEV_OFFSET, XKB_KEY_UP);
assert(xkb_state_mod_name_is_active(state, XKB_MOD_NAME_MOD1,
XKB_STATE_MODS_EFFECTIVE) == 0);
assert(xkb_state_mod_name_is_active(state, XKB_MOD_NAME_ALT,
XKB_STATE_MODS_EFFECTIVE) == 0);
assert(xkb_state_mod_name_is_active(state, XKB_MOD_NAME_META,
XKB_STATE_MODS_EFFECTIVE) == 0);

/* Caps locked */
xkb_state_update_key(state, KEY_CAPSLOCK + EVDEV_OFFSET, XKB_KEY_DOWN);
Expand All @@ -201,6 +249,8 @@ test_update_key(struct xkb_keymap *keymap)
XKB_STATE_MODS_LOCKED) > 0);
assert(xkb_state_mod_name_is_active(state, "Mod2",
XKB_STATE_MODS_LOCKED) > 0);
assert(xkb_state_mod_name_is_active(state, XKB_MOD_NAME_NUM,
XKB_STATE_MODS_LOCKED) > 0);
num_syms = xkb_state_key_get_syms(state, KEY_KP1 + EVDEV_OFFSET, &syms);
assert(num_syms == 1 && syms[0] == XKB_KEY_KP_1);
assert(xkb_state_led_name_is_active(state, XKB_LED_NAME_NUM) > 0);
Expand Down Expand Up @@ -302,7 +352,7 @@ static void
test_update_mask_mods(struct xkb_keymap *keymap)
{
struct xkb_state *state = xkb_state_new(keymap);
xkb_mod_index_t caps, shift, num, alt, mod1, mod2;
xkb_mod_index_t caps, shift, num, alt, meta, mod1, mod2;
enum xkb_state_component changed;

assert(state);
Expand All @@ -315,6 +365,8 @@ test_update_mask_mods(struct xkb_keymap *keymap)
assert(num != XKB_MOD_INVALID);
alt = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_ALT);
assert(alt != XKB_MOD_INVALID);
meta = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_META);
assert(meta != XKB_MOD_INVALID);
mod1 = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_MOD1);
assert(mod1 != XKB_MOD_INVALID);
mod2 = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_MOD2);
Expand Down Expand Up @@ -346,6 +398,11 @@ test_update_mask_mods(struct xkb_keymap *keymap)
assert(xkb_state_serialize_mods(state, XKB_STATE_MODS_EFFECTIVE) ==
((1u << alt) | (1u << mod1)));

changed = xkb_state_update_mask(state, (1 << meta), 0, 0, 0, 0, 0);
assert(changed == (XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_EFFECTIVE));
assert(xkb_state_serialize_mods(state, XKB_STATE_MODS_EFFECTIVE) ==
((1u << meta) | (1u << mod1)));

changed = xkb_state_update_mask(state, 0, 0, (1 << num), 0, 0, 0);
assert(changed == (XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LOCKED |
XKB_STATE_MODS_EFFECTIVE | XKB_STATE_LEDS));
Expand Down Expand Up @@ -381,20 +438,20 @@ static void
test_consume(struct xkb_keymap *keymap)
{
struct xkb_state *state;
xkb_mod_index_t alt, shift, caps, ctrl, mod5;
xkb_mod_index_t shift, caps, ctrl, mod1, mod5;
xkb_mod_mask_t mask;

state = xkb_state_new(keymap);
assert(state);

alt = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_ALT);
assert(alt != XKB_MOD_INVALID);
shift = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_SHIFT);
assert(shift != XKB_MOD_INVALID);
caps = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_CAPS);
assert(caps != XKB_MOD_INVALID);
ctrl = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_CTRL);
assert(ctrl != XKB_MOD_INVALID);
mod1 = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_MOD1);
assert(mod1 != XKB_MOD_INVALID);
mod5 = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_MOD5);
assert(mod5 != XKB_MOD_INVALID);

Expand All @@ -407,10 +464,10 @@ test_consume(struct xkb_keymap *keymap)
print_state(state);

mask = xkb_state_serialize_mods(state, XKB_STATE_MODS_EFFECTIVE);
assert(mask == ((1U << alt) | (1U << shift)));
assert(mask == ((1U << mod1) | (1U << shift)));
mask = xkb_state_mod_mask_remove_consumed(state, KEY_EQUAL + EVDEV_OFFSET,
mask);
assert(mask == (1U << alt));
assert(mask == (1U << mod1));

/* Test get_consumed_mods() */
mask = xkb_state_key_get_consumed_mods(state, KEY_EQUAL + EVDEV_OFFSET);
Expand Down Expand Up @@ -450,16 +507,16 @@ test_consume(struct xkb_keymap *keymap)
assert(state);

mask = xkb_state_key_get_consumed_mods(state, KEY_F1 + EVDEV_OFFSET);
assert(mask == ((1U << shift) | (1U << alt) | (1U << ctrl) | (1U << mod5)));
assert(mask == ((1U << shift) | (1U << mod1) | (1U << ctrl) | (1U << mod5)));

/* Shift is preserved. */
xkb_state_update_key(state, KEY_LEFTSHIFT + EVDEV_OFFSET, XKB_KEY_DOWN);
mask = xkb_state_key_get_consumed_mods(state, KEY_F1 + EVDEV_OFFSET);
assert(mask == ((1U << alt) | (1U << ctrl) | (1U << mod5)));
assert(mask == ((1U << mod1) | (1U << ctrl) | (1U << mod5)));
xkb_state_update_key(state, KEY_LEFTSHIFT + EVDEV_OFFSET, XKB_KEY_UP);

mask = xkb_state_key_get_consumed_mods(state, KEY_F1 + EVDEV_OFFSET);
assert(mask == ((1U << shift) | (1U << alt) | (1U << ctrl) | (1U << mod5)));
assert(mask == ((1U << shift) | (1U << mod1) | (1U << ctrl) | (1U << mod5)));

xkb_state_unref(state);

Expand All @@ -479,7 +536,7 @@ test_consume(struct xkb_keymap *keymap)
xkb_state_update_key(state, KEY_LEFTALT + EVDEV_OFFSET, XKB_KEY_DOWN);
mask = xkb_state_key_get_consumed_mods2(state, KEY_F1 + EVDEV_OFFSET,
XKB_CONSUMED_MODE_GTK);
assert(mask == ((1U << alt) | (1U << ctrl)));
assert(mask == ((1U << mod1) | (1U << ctrl)));

xkb_state_unref(state);

Expand Down

0 comments on commit 946ec04

Please sign in to comment.