Edit

kc3-lang/libxkbcommon/src/xkbcomp/keymap.c

Branch :

  • Show log

    Commit

  • Author : Pierre Le Marre
    Date : 2025-05-12 18:20:47
    Hash : 61d8ec67
    Message : misc: Fix string format specifiers Ensure better portability.

  • src/xkbcomp/keymap.c
  • /*
     * Copyright © 2009 Dan Nicholson
     * Copyright © 2012 Intel Corporation
     * Copyright © 2012 Ran Benita <ran234@gmail.com>
     * SPDX-License-Identifier: MIT
     *
     * Author: Dan Nicholson <dbn.lists@gmail.com>
     * Author: Daniel Stone <daniel@fooishbar.org>
     * Author: Ran Benita <ran234@gmail.com>
     */
    
    #include "config.h"
    
    #include <string.h>
    #include <stdbool.h>
    
    #include "keymap.h"
    #include "darray.h"
    #include "xkbcomp-priv.h"
    #include "text.h"
    
    static bool
    has_unbound_vmods(struct xkb_keymap *keymap, xkb_mod_mask_t mask)
    {
        xkb_mod_index_t k;
        struct xkb_mod *mod;
        xkb_vmods_enumerate(k, mod, &keymap->mods)
            if ((mask & (UINT32_C(1) << k)) && mod->mapping == 0)
                return true;
        return false;
    }
    
    static inline void
    ComputeEffectiveMask(struct xkb_keymap *keymap, struct xkb_mods *mods)
    {
        /*
         * Since we accept numeric values for the vmod mask in the keymap, we may
         * have extra bits set that encode no real/virtual modifier. Keep them
         * unchanged for consistency.
         */
        const xkb_mod_mask_t unknown_mods = ~(xkb_mod_mask_t)
            ((UINT64_C(1) << keymap->mods.num_mods) - UINT64_C(1));
        mods->mask = mod_mask_get_effective(keymap, mods->mods)
                   | (mods->mods & unknown_mods);
    }
    
    static void
    UpdateActionMods(struct xkb_keymap *keymap, union xkb_action *act,
                     xkb_mod_mask_t modmap)
    {
        switch (act->type) {
        case ACTION_TYPE_MOD_SET:
        case ACTION_TYPE_MOD_LATCH:
        case ACTION_TYPE_MOD_LOCK:
            if (act->mods.flags & ACTION_MODS_LOOKUP_MODMAP)
                act->mods.mods.mods = modmap;
            ComputeEffectiveMask(keymap, &act->mods.mods);
            break;
        default:
            break;
        }
    }
    
    const struct xkb_sym_interpret default_interpret = {
        /* Keysym unused for when applying interpretation, but used as a default
         * entry when dumping the keymap */
        .sym = XKB_KEY_VoidSymbol,
        .repeat = true,
        .match = MATCH_ANY_OR_NONE,
        .mods = 0,
        .virtual_mod = XKB_MOD_INVALID,
        .num_actions = 0,
        .a = { .action = { .type = ACTION_TYPE_NONE } },
    };
    
    typedef darray(const struct xkb_sym_interpret*) xkb_sym_interprets;
    
    /**
     * Find an interpretation which applies to this particular level, either by
     * finding an exact match for the symbol and modifier combination, or a
     * generic XKB_KEY_NoSymbol match.
     */
    static bool
    FindInterpForKey(struct xkb_keymap *keymap, const struct xkb_key *key,
                     xkb_layout_index_t group, xkb_level_index_t level,
                     xkb_sym_interprets *interprets)
    {
        const xkb_keysym_t *syms;
        int num_syms;
    
        num_syms = xkb_keymap_key_get_syms_by_level(keymap, key->keycode, group,
                                                    level, &syms);
        if (num_syms <= 0)
            return false;
    
        /*
         * There may be multiple matchings interprets; we should always return
         * the most specific. Here we rely on compat.c to set up the
         * sym_interprets array from the most specific to the least specific,
         * such that when we find a match we return immediately.
         */
        for (int s = 0; s < num_syms; s++) {
            bool found = false;
            for (darray_size_t i = 0; i < keymap->num_sym_interprets; i++) {
                const struct xkb_sym_interpret *interp = &keymap->sym_interprets[i];
                xkb_mod_mask_t mods;
    
                found = false;
    
                if (interp->sym != syms[s] && interp->sym != XKB_KEY_NoSymbol)
                    continue;
    
                if (interp->level_one_only && level != 0)
                    mods = 0;
                else
                    mods = key->modmap;
    
                switch (interp->match) {
                case MATCH_NONE:
                    found = !(interp->mods & mods);
                    break;
                case MATCH_ANY_OR_NONE:
                    found = (!mods || (interp->mods & mods));
                    break;
                case MATCH_ANY:
                    found = (interp->mods & mods);
                    break;
                case MATCH_ALL:
                    found = ((interp->mods & mods) == interp->mods);
                    break;
                case MATCH_EXACTLY:
                    found = (interp->mods == mods);
                    break;
                }
    
                if (found && i > 0 && interp->sym == XKB_KEY_NoSymbol) {
                    /*
                     * For an interpretation matching Any keysym, we may get the
                     * same interpretation for multiple keysyms. This may result in
                     * unwanted duplicate actions. So set this interpretation only
                     * if no previous keysym was matched with this interpret at this
                     * level, else set the default interpretation.
                     */
                    struct xkb_sym_interpret const **previous_interp;
                    darray_foreach(previous_interp, *interprets) {
                        if (*previous_interp == interp) {
                            /* Keep as a safeguard against refactoring
                             * NOLINTNEXTLINE(clang-analyzer-deadcode.DeadStores) */
                            found = false;
                            log_warn(keymap->ctx, XKB_LOG_MESSAGE_NO_ID,
                                     "Repeated interpretation ignored for keysym "
                                     "#%d \"%s\" at level %"PRIu32"/group %"PRIu32" "
                                     "on key %s.\n",
                                     s + 1, KeysymText(keymap->ctx, syms[s]),
                                     level + 1, group + 1,
                                     KeyNameText(keymap->ctx, key->name));
                            goto not_found;
                        }
                    }
                }
                if (found) {
                    darray_append(*interprets, interp);
                    break;
                }
            }
            if (!found)
    not_found:
                darray_append(*interprets, &default_interpret);
        }
        return true;
    }
    
    static bool
    ApplyInterpsToKey(struct xkb_keymap *keymap, struct xkb_key *key)
    {
        xkb_mod_mask_t vmodmap = 0;
        xkb_level_index_t level;
        // FIXME: do not use darray, add actions directly in FindInterpForKey
        xkb_sym_interprets interprets = darray_new();
        darray(union xkb_action) actions = darray_new();
    
        for (xkb_layout_index_t group = 0; group < key->num_groups; group++) {
            /* Skip any interpretation for this group if it has explicit actions */
            if (key->groups[group].explicit_actions)
                continue;
    
            for (level = 0; level < XkbKeyNumLevels(key, group); level++) {
                assert(key->groups[group].levels[level].num_actions == 0);
    
                const struct xkb_sym_interpret **interp_iter;
                const struct xkb_sym_interpret *interp;
                size_t k;
                darray_resize(interprets, 0);
    
                const bool found = FindInterpForKey(keymap, key, group, level, &interprets);
                if (!found)
                    continue;
    
                darray_enumerate(k, interp_iter, interprets) {
                    interp = *interp_iter;
                    /* Infer default key behaviours from the base level. */
                    if (group == 0 && level == 0)
                        if (!(key->explicit & EXPLICIT_REPEAT) && interp->repeat)
                            key->repeats = true;
    
                    if ((group == 0 && level == 0) || !interp->level_one_only)
                        if (interp->virtual_mod != XKB_MOD_INVALID)
                            vmodmap |= (UINT32_C(1) << interp->virtual_mod);
    
                    switch (interp->num_actions) {
                    case 0:
                        break;
                    case 1:
                        darray_append(actions, interp->a.action);
                        break;
                    default:
                        darray_append_items(actions, interp->a.actions,
                                            interp->num_actions);
                    }
                }
    
                /* Copy the actions */
                if (unlikely(darray_size(actions)) > MAX_ACTIONS_PER_LEVEL) {
                    log_warn(keymap->ctx, XKB_LOG_MESSAGE_NO_ID,
                             "Could not append interpret actions to key %s: "
                             "maximum is %u, got: %u. Dropping excessive actions\n",
                             KeyNameText(keymap->ctx, key->name),
                             MAX_ACTIONS_PER_LEVEL, darray_size(actions));
                    key->groups[group].levels[level].num_actions =
                        MAX_ACTIONS_PER_LEVEL;
                } else {
                    key->groups[group].levels[level].num_actions =
                        (xkb_action_count_t) darray_size(actions);
                }
                switch (darray_size(actions)) {
                    case 0:
                        key->groups[group].levels[level].a.action =
                            (union xkb_action) { .type = ACTION_TYPE_NONE };
                        break;
                    case 1:
                        key->groups[group].levels[level].a.action =
                            darray_item(actions, 0);
                        break;
                    default:
                        key->groups[group].levels[level].a.actions =
                            memdup(darray_items(actions),
                                   key->groups[group].levels[level].num_actions,
                                   sizeof(*darray_items(actions)));
                        if (!key->groups[group].levels[level].a.actions) {
                            log_err(keymap->ctx, XKB_ERROR_ALLOCATION_ERROR,
                                    "Could not allocate interpret actions\n");
                            darray_free(actions);
                            darray_free(interprets);
                            return false;
                        }
                }
    
                /* Do not free here */
                darray_resize(actions, 0);
            }
        }
        darray_free(actions);
        darray_free(interprets);
    
        if (!(key->explicit & EXPLICIT_VMODMAP))
            key->vmodmap = vmodmap;
    
        return true;
    }
    
    static inline bool
    is_mod_action(union xkb_action *action)
    {
        return action->type == ACTION_TYPE_MOD_SET ||
               action->type == ACTION_TYPE_MOD_LATCH ||
               action->type == ACTION_TYPE_MOD_LOCK;
    }
    
    static inline bool
    is_group_action(union xkb_action *action)
    {
        return action->type == ACTION_TYPE_GROUP_SET ||
               action->type == ACTION_TYPE_GROUP_LATCH ||
               action->type == ACTION_TYPE_GROUP_LOCK;
    }
    
    /* Check for mixing actions of the same category.
     * We do not support that yet, because it needs a careful refactor of the state
     * handling. See: `xkb_filter_apply_all`. */
    static void
    CheckMultipleActionsCategories(struct xkb_keymap *keymap, struct xkb_key *key)
    {
        for (xkb_layout_index_t g = 0; g < key->num_groups; g++) {
            for (xkb_level_index_t l = 0; l < XkbKeyNumLevels(key, g); l++) {
                struct xkb_level *level = &key->groups[g].levels[l];
                if (level->num_actions <= 1)
                    continue;
                for (xkb_action_count_t i = 0; i < level->num_actions; i++) {
                    union xkb_action *action1 = &level->a.actions[i];
                    bool mod_action = is_mod_action(action1);
                    bool group_action = is_group_action(action1);
                    if (!(mod_action || group_action))
                        continue;
                    for (xkb_action_count_t j = i + 1; j < level->num_actions; j++) {
                        union xkb_action *action2 = &level->a.actions[j];
                        if ((mod_action && is_mod_action(action2)) ||
                            (group_action && is_group_action(action2)))
                        {
                            log_err(keymap->ctx, XKB_LOG_MESSAGE_NO_ID,
                                    "Cannot use multiple %s actions "
                                    "in the same level. Action #%u "
                                    "for key %s in group %"PRIu32"/level %"PRIu32" "
                                    "ignored.\n",
                                    (mod_action ? "modifiers" : "group"),
                                    j + 1, KeyNameText(keymap->ctx, key->name),
                                    g + 1, l + 1);
                            action2->type = ACTION_TYPE_NONE;
                        }
                    }
                }
            }
        }
    }
    
    /**
     * This collects a bunch of disparate functions which was done in the server
     * at various points that really should've been done within xkbcomp.  Turns out
     * your actions and types are a lot more useful when any of your modifiers
     * other than Shift actually do something ...
     */
    static bool
    UpdateDerivedKeymapFields(struct xkb_keymap *keymap)
    {
        /* Find all the interprets for the key and bind them to actions,
         * which will also update the vmodmap. */
        struct xkb_key *key;
        xkb_keys_foreach(key, keymap) {
            if (!ApplyInterpsToKey(keymap, key))
                return false;
            CheckMultipleActionsCategories(keymap, key);
        }
    
        /* Update keymap->mods, the virtual -> real mod mapping. */
        xkb_mod_index_t idx;
        struct xkb_mod *mod;
        xkb_keys_foreach(key, keymap)
            xkb_vmods_enumerate(idx, mod, &keymap->mods)
                if (key->vmodmap & (UINT32_C(1) << idx))
                    mod->mapping |= key->modmap;
    
        /* Update the canonical modifiers state mask */
        assert(keymap->canonical_state_mask == MOD_REAL_MASK_ALL);
        xkb_mod_mask_t extra_canonical_mods = 0;
        xkb_vmods_enumerate(idx, mod, &keymap->mods) {
            extra_canonical_mods |= mod->mapping;
        }
        keymap->canonical_state_mask |= extra_canonical_mods;
    
        /* Now update the level masks for all the types to reflect the vmods. */
        for (darray_size_t i = 0; i < keymap->num_types; i++) {
            ComputeEffectiveMask(keymap, &keymap->types[i].mods);
    
            for (darray_size_t j = 0; j < keymap->types[i].num_entries; j++) {
                if (has_unbound_vmods(keymap,
                                      keymap->types[i].entries[j].mods.mods)) {
                    /*
                     * Map entries which specify unbound virtual modifiers are not
                     * considered (see the XKB protocol, section “Determining the
                     * KeySym Associated with a Key Event”).
                     *
                     * Deactivate entry by zeroing its mod mask and skipping further
                     * processing.
                     *
                     * See also: `entry_is_active`.
                     */
                    keymap->types[i].entries[j].mods.mask = 0;
                    continue;
                }
                ComputeEffectiveMask(keymap, &keymap->types[i].entries[j].mods);
                ComputeEffectiveMask(keymap, &keymap->types[i].entries[j].preserve);
            }
        }
    
        /* Update action modifiers. */
        xkb_keys_foreach(key, keymap) {
            for (xkb_layout_index_t i = 0; i < key->num_groups; i++) {
                for (xkb_level_index_t j = 0; j < XkbKeyNumLevels(key, i); j++) {
                    if (key->groups[i].levels[j].num_actions == 1) {
                        UpdateActionMods(keymap, &key->groups[i].levels[j].a.action,
                                         key->modmap);
                    } else {
                        for (xkb_action_count_t k = 0;
                             k < key->groups[i].levels[j].num_actions; k++) {
                            UpdateActionMods(keymap,
                                             &key->groups[i].levels[j].a.actions[k],
                                             key->modmap);
                        }
                    }
                }
            }
        }
    
        /* Update vmod -> led maps. */
        struct xkb_led *led;
        xkb_leds_foreach(led, keymap)
            ComputeEffectiveMask(keymap, &led->mods);
    
        /* Find maximum number of groups out of all keys in the keymap. */
        xkb_keys_foreach(key, keymap)
            keymap->num_groups = MAX(keymap->num_groups, key->num_groups);
    
        return true;
    }
    
    typedef bool (*compile_file_fn)(XkbFile *file, struct xkb_keymap *keymap);
    
    static const compile_file_fn compile_file_fns[LAST_KEYMAP_FILE_TYPE + 1] = {
        [FILE_TYPE_KEYCODES] = CompileKeycodes,
        [FILE_TYPE_TYPES] = CompileKeyTypes,
        [FILE_TYPE_COMPAT] = CompileCompatMap,
        [FILE_TYPE_SYMBOLS] = CompileSymbols,
    };
    
    bool
    CompileKeymap(XkbFile *file, struct xkb_keymap *keymap)
    {
        XkbFile *files[LAST_KEYMAP_FILE_TYPE + 1] = { NULL };
        enum xkb_file_type type;
        struct xkb_context *ctx = keymap->ctx;
    
        /* Collect section files and check for duplicates. */
        for (file = (XkbFile *) file->defs; file;
             file = (XkbFile *) file->common.next) {
            if (file->file_type < FIRST_KEYMAP_FILE_TYPE ||
                file->file_type > LAST_KEYMAP_FILE_TYPE) {
                if (file->file_type == FILE_TYPE_GEOMETRY) {
                    log_vrb(ctx, 1,
                            XKB_WARNING_UNSUPPORTED_GEOMETRY_SECTION,
                            "Geometry sections are not supported; ignoring\n");
                } else {
                    log_err(ctx, XKB_LOG_MESSAGE_NO_ID,
                            "Cannot define %s in a keymap file\n",
                            xkb_file_type_to_string(file->file_type));
                }
                continue;
            }
    
            if (files[file->file_type]) {
                log_err(ctx, XKB_LOG_MESSAGE_NO_ID,
                        "More than one %s section in keymap file; "
                        "All sections after the first ignored\n",
                        xkb_file_type_to_string(file->file_type));
                continue;
            }
    
            files[file->file_type] = file;
        }
    
        /*
         * Compile sections
         *
         * NOTE: Any component is optional.
         */
        for (type = FIRST_KEYMAP_FILE_TYPE;
             type <= LAST_KEYMAP_FILE_TYPE;
             type++) {
            if (files[type] == NULL) {
                log_dbg(ctx, XKB_LOG_MESSAGE_NO_ID,
                        "Component %s not provided in keymap\n",
                        xkb_file_type_to_string(type));
            } else {
                log_dbg(ctx, XKB_LOG_MESSAGE_NO_ID,
                        "Compiling %s \"%s\"\n",
                        xkb_file_type_to_string(type), safe_map_name(files[type]));
            }
    
            /* Missing components are initialized with defaults */
            const bool ok = compile_file_fns[type](files[type], keymap);
            if (!ok) {
                log_err(ctx, XKB_LOG_MESSAGE_NO_ID,
                        "Failed to compile %s\n",
                        xkb_file_type_to_string(type));
                return false;
            }
        }
    
        return UpdateDerivedKeymapFields(keymap);
    }