+ xkb_layout_index_t base_group,
+ xkb_layout_index_t latched_group,
+ xkb_layout_index_t locked_group)
+{
+ struct state_components prev_components;
+ xkb_mod_mask_t mask;
+
+ prev_components = state->components;
+
+ /* Only include modifiers which exist in the keymap. */
+ mask = (xkb_mod_mask_t) ((1ull << xkb_keymap_num_mods(state->keymap)) - 1u);
+
+ state->components.base_mods = base_mods & mask;
+ state->components.latched_mods = latched_mods & mask;
+ state->components.locked_mods = locked_mods & mask;
+
+ /* Make sure the mods are fully resolved - since we get arbitrary
+ * input, they might not be.
+ *
+ * It might seem more reasonable to do this only for components.mods
+ * in xkb_state_update_derived(), rather than for each component
+ * seperately. That would allow to distinguish between "really"
+ * depressed mods (would be in MODS_DEPRESSED) and indirectly
+ * depressed to to a mapping (would only be in MODS_EFFECTIVE).
+ * However, the traditional behavior of xkb_state_update_key() is that
+ * if a vmod is depressed, its mappings are depressed with it; so we're
+ * expected to do the same here. Also, LEDs (usually) look if a real
+ * mod is locked, not just effective; otherwise it won't be lit.
+ *
+ * We OR here because mod_mask_get_effective() drops vmods. */
+ state->components.base_mods |=
+ mod_mask_get_effective(state->keymap, state->components.base_mods);
+ state->components.latched_mods |=
+ mod_mask_get_effective(state->keymap, state->components.latched_mods);
+ state->components.locked_mods |=
+ mod_mask_get_effective(state->keymap, state->components.locked_mods);
+
+ state->components.base_group = base_group;
+ state->components.latched_group = latched_group;
+ state->components.locked_group = locked_group;
+
+ xkb_state_update_derived(state);
+
+ return get_state_component_changes(&prev_components, &state->components);
+}
+
+/**
+ * Provides the symbols to use for the given key and state. Returns the
+ * number of symbols pointed to in syms_out.
+ */
+XKB_EXPORT int
+xkb_state_key_get_syms(struct xkb_state *state, xkb_keycode_t kc,
+ const xkb_keysym_t **syms_out)
+{
+ xkb_layout_index_t layout;
+ xkb_level_index_t level;
+
+ layout = xkb_state_key_get_layout(state, kc);
+ if (layout == XKB_LAYOUT_INVALID)
+ goto err;
+
+ level = xkb_state_key_get_level(state, kc, layout);
+ if (level == XKB_LEVEL_INVALID)
+ goto err;
+
+ return xkb_keymap_key_get_syms_by_level(state->keymap, kc, layout, level,
+ syms_out);
+
+err:
+ *syms_out = NULL;
+ return 0;
+}
+
+/*
+ * https://www.x.org/releases/current/doc/kbproto/xkbproto.html#Interpreting_the_Lock_Modifier
+ */
+static bool
+should_do_caps_transformation(struct xkb_state *state, xkb_keycode_t kc)
+{
+ xkb_mod_index_t caps =
+ xkb_keymap_mod_get_index(state->keymap, XKB_MOD_NAME_CAPS);
+
+ return
+ xkb_state_mod_index_is_active(state, caps, XKB_STATE_MODS_EFFECTIVE) > 0 &&
+ xkb_state_mod_index_is_consumed(state, kc, caps) == 0;
+}
+
+/*
+ * https://www.x.org/releases/current/doc/kbproto/xkbproto.html#Interpreting_the_Control_Modifier
+ */
+static bool
+should_do_ctrl_transformation(struct xkb_state *state, xkb_keycode_t kc)
+{
+ xkb_mod_index_t ctrl =
+ xkb_keymap_mod_get_index(state->keymap, XKB_MOD_NAME_CTRL);
+
+ return
+ xkb_state_mod_index_is_active(state, ctrl, XKB_STATE_MODS_EFFECTIVE) > 0 &&
+ xkb_state_mod_index_is_consumed(state, kc, ctrl) == 0;
+}
+
+/* Verbatim from libX11:src/xkb/XKBBind.c */
+static char
+XkbToControl(char ch)
+{
+ char c = ch;
+
+ if ((c >= '@' && c < '\177') || c == ' ')
+ c &= 0x1F;
+ else if (c == '2')
+ c = '\000';
+ else if (c >= '3' && c <= '7')
+ c -= ('3' - '\033');
+ else if (c == '8')
+ c = '\177';
+ else if (c == '/')
+ c = '_' & 0x1F;
+ return c;
+}
+
+/**
+ * Provides either exactly one symbol, or XKB_KEY_NoSymbol.
+ */
+XKB_EXPORT xkb_keysym_t
+xkb_state_key_get_one_sym(struct xkb_state *state, xkb_keycode_t kc)
+{
+ const xkb_keysym_t *syms;
+ xkb_keysym_t sym;
+ int num_syms;
+
+ num_syms = xkb_state_key_get_syms(state, kc, &syms);
+ if (num_syms != 1)
+ return XKB_KEY_NoSymbol;
+
+ sym = syms[0];
+
+ if (should_do_caps_transformation(state, kc))
+ sym = xkb_keysym_to_upper(sym);
+
+ return sym;
+}
+
+/*
+ * The caps and ctrl transformations require some special handling,
+ * so we cannot simply use xkb_state_get_one_sym() for them.
+ * In particular, if Control is set, we must try very hard to find
+ * some layout in which the keysym is ASCII and thus can be (maybe)
+ * converted to a control character. libX11 allows to disable this
+ * behavior with the XkbLC_ControlFallback (see XkbSetXlibControls(3)),
+ * but it is enabled by default, yippee.
+ */
+static xkb_keysym_t
+get_one_sym_for_string(struct xkb_state *state, xkb_keycode_t kc)
+{
+ xkb_level_index_t level;
+ xkb_layout_index_t layout, num_layouts;
+ const xkb_keysym_t *syms;
+ int nsyms;
+ xkb_keysym_t sym;
+
+ layout = xkb_state_key_get_layout(state, kc);
+ num_layouts = xkb_keymap_num_layouts_for_key(state->keymap, kc);
+ level = xkb_state_key_get_level(state, kc, layout);
+ if (layout == XKB_LAYOUT_INVALID || num_layouts == 0 ||
+ level == XKB_LEVEL_INVALID)
+ return XKB_KEY_NoSymbol;
+
+ nsyms = xkb_keymap_key_get_syms_by_level(state->keymap, kc,
+ layout, level, &syms);
+ if (nsyms != 1)
+ return XKB_KEY_NoSymbol;
+ sym = syms[0];
+
+ if (should_do_ctrl_transformation(state, kc) && sym > 127u) {
+ for (xkb_layout_index_t i = 0; i < num_layouts; i++) {
+ level = xkb_state_key_get_level(state, kc, i);
+ if (level == XKB_LEVEL_INVALID)
+ continue;
+
+ nsyms = xkb_keymap_key_get_syms_by_level(state->keymap, kc,
+ i, level, &syms);
+ if (nsyms == 1 && syms[0] <= 127u) {
+ sym = syms[0];
+ break;
+ }
+ }