diff --git a/include/xkbcommon/xkbcommon.h b/include/xkbcommon/xkbcommon.h index a7148f35b..f5cb36081 100644 --- a/include/xkbcommon/xkbcommon.h +++ b/include/xkbcommon/xkbcommon.h @@ -81,9 +81,10 @@ #ifndef _XKBCOMMON_H_ #define _XKBCOMMON_H_ +#include +#include #include #include -#include #include #include @@ -1441,12 +1442,68 @@ enum xkb_state_component xkb_state_update_key(struct xkb_state *state, xkb_keycode_t key, enum xkb_key_direction direction); +/** + * Update the keyboard state to change the latched and locked state of + * the modifiers and layout. + * + * This entry point is intended for *server* applications and should not be used + * by *client* applications; see @ref server-client-state for details. + * + * Use this function to update the latched and locked state according to + * "out of band" (non-device) inputs, such as UI layout switchers. + * + * @par Layout out of range + * @parblock + * + * If the effective layout, after taking into account the depressed, latched and + * locked layout, is out of range (negative or greater than the maximum layout), + * it is brought into range. Currently, the layout is wrapped using integer + * modulus (with negative values wrapping from the end). The wrapping behavior + * may be made configurable in the future. + * + * @endparblock + * + * @param affect_latched_mods + * @param latched_mods + * Modifiers to set as latched or unlatched. Only modifiers in + * `affect_latched_mods` are considered. + * @param affect_latched_layout + * @param latched_layout + * Layout to latch. Only considered if `affect_latched_layout` is true. + * Maybe be out of range (including negative) -- see note above. + * @param affect_locked_mods + * @param locked_mods + * Modifiers to set as locked or unlocked. Only modifiers in + * `affect_locked_mods` are considered. + * @param affect_locked_layout + * @param locked_layout + * Layout to lock. Only considered if `affect_locked_layout` is true. + * Maybe be out of range (including negative) -- see note above. + * + * @returns A mask of state components that have changed as a result of + * the update. If nothing in the state has changed, returns 0. + * + * @memberof xkb_state + * + * @sa xkb_state_update_mask() + */ +enum xkb_state_component +xkb_state_update_latched_locked(struct xkb_state *state, + xkb_mod_mask_t affect_latched_mods, + xkb_mod_mask_t latched_mods, + bool affect_latched_layout, + int32_t latched_layout, + xkb_mod_mask_t affect_locked_mods, + xkb_mod_mask_t locked_mods, + bool affect_locked_layout, + int32_t locked_layout); + /** * Update a keyboard state from a set of explicit masks. * * This entry point is intended for *client* applications; see @ref * server-client-state for details. *Server* applications should use - * xkb_state_update_key() instead. + * xkb_state_update_key() and xkb_state_update_latched_locked() instead. * * All parameters must always be passed, or the resulting state may be * incoherent. diff --git a/src/state.c b/src/state.c index b269e6d6e..abdac8c73 100644 --- a/src/state.c +++ b/src/state.c @@ -64,6 +64,7 @@ #include "keymap.h" #include "keysym.h" #include "utf8.h" +#include "xkbcommon/xkbcommon.h" struct xkb_filter { union xkb_action action; @@ -781,6 +782,91 @@ xkb_state_update_key(struct xkb_state *state, xkb_keycode_t kc, return get_state_component_changes(&prev_components, &state->components); } +// XXX transcription from xserver +static void +XkbLatchModifiers(struct xkb_state *state, xkb_mod_mask_t mask, xkb_mod_mask_t latches) +{ + const struct xkb_key *key = XkbKey(state->keymap, SYNTHETIC_KEYCODE); + + xkb_mod_mask_t clear = mask & (~latches); + state->components.latched_mods &= ~clear; + union xkb_action none = { + .type = ACTION_TYPE_NONE, + }; + /* Clear any pending latch to locks. */ + xkb_filter_apply_all(state, key, XKB_KEY_DOWN); + + union xkb_action latch_mods = { + .mods = { + .type = ACTION_TYPE_MOD_LATCH, + .mods = { + .mask = mask & latches, + }, + .flags = 0, + }, + }; + struct xkb_filter *filter = xkb_filter_new(state); + filter->key = key; + filter->func = filter_action_funcs[latch_mods.type].func; + filter->action = latch_mods; + xkb_filter_mod_latch_new(state, filter); + xkb_filter_mod_latch_func(state, filter, key, XKB_KEY_DOWN); + xkb_filter_mod_latch_func(state, filter, key, XKB_KEY_UP); +} + +// XXX transcription from xserver +static int +XkbLatchGroup(struct xkb_state *state, int32_t group) +{ + const struct xkb_key *key = XkbKey(state->keymap, SYNTHETIC_KEYCODE); + + union xkb_action latch_group = { + .group = { + .type = ACTION_TYPE_GROUP_LATCH, + .flags = 0, + .group = group, + }, + }; + struct xkb_filter *filter = xkb_filter_new(state); + filter->key = key; + filter->func = filter_action_funcs[latch_group.type].func; + filter->action = latch_group; + xkb_filter_group_latch_new(state, filter); + xkb_filter_group_latch_func(state, filter, key, XKB_KEY_DOWN); + xkb_filter_group_latch_func(state, filter, key, XKB_KEY_UP); +} + +XKB_EXPORT enum xkb_state_component +xkb_state_update_latched_locked(struct xkb_state *state, + xkb_mod_mask_t affect_latched_mods, + xkb_mod_mask_t latched_mods, + bool affect_latched_layout, + int32_t latched_layout, + xkb_mod_mask_t affect_locked_mods, + xkb_mod_mask_t locked_mods, + bool affect_locked_layout, + int32_t locked_layout) +{ + struct state_components prev_components = state->components; + + state->components.locked_mods &= ~affect_locked_mods; + state->components.locked_mods |= locked_mods & affect_locked_mods; + + if (affect_locked_layout) { + state->components.locked_group = locked_layout; + } + + XkbLatchModifiers(state, affect_latched_mods, latched_mods); + + if (affect_latched_layout) { + XkbLatchGroup(state, latched_layout); + } + + xkb_state_update_derived(state); + + return get_state_component_changes(&prev_components, &state->components); +} + /** * Updates the state from a set of explicit masks as gained from * xkb_state_serialize_mods and xkb_state_serialize_groups. As noted in the