Edit

kc3-lang/libxkbcommon/src/rmlvo.c

Branch :

  • Show log

    Commit

  • Author : Pierre Le Marre
    Date : 2025-06-17 11:43:50
    Hash : c4c531da
    Message : rules: Add layout-specific options for RMLVO builder Change the signature of `xkb_rmlvo_builder_append_layout()` to accept an array of options. Also add tests for layout-specific options.

  • src/rmlvo.c
  • /*
     * Copyright © 2025 Pierre Le Marre <dev@wismill.eu>
     * SPDX-License-Identifier: MIT
     */
    
    #include "config.h"
    
    #include <string.h>
    
    #include "xkbcommon/xkbcommon.h"
    #include "context.h"
    #include "keymap.h"
    #include "messages-codes.h"
    #include "rmlvo.h"
    #include "darray.h"
    #include "utils.h"
    #include "xkbcomp/rules.h"
    
    struct xkb_rmlvo_builder*
    xkb_rmlvo_builder_new(struct xkb_context *context,
                          const char *rules, const char *model,
                          enum xkb_rmlvo_builder_flags flags)
    {
        struct xkb_rmlvo_builder * const builder = calloc(1, sizeof(*builder));
        if (!builder)
            goto error;
    
        builder->refcnt = 1;
        builder->ctx = xkb_context_ref(context);
    
        builder->rules = strdup_safe(rules);
        if (!builder->rules && rules)
            goto error;
    
        builder->model = strdup_safe(model);
        if (!builder->model && model)
            goto error;
    
        darray_init(builder->layouts);
        darray_init(builder->options);
    
        return builder;
    
    error:
        log_err(context, XKB_ERROR_ALLOCATION_ERROR,
                "Cannot allocate a RMLVO builder.\n");
        xkb_rmlvo_builder_unref(builder);
        return NULL;
    }
    
    bool
    xkb_rmlvo_builder_append_layout(struct xkb_rmlvo_builder *rmlvo,
                                    const char *layout, const char *variant,
                                    const char* const* options, size_t options_len)
    {
        const xkb_layout_index_t idx = (xkb_layout_index_t)
                                       darray_size(rmlvo->layouts);
    
        if (idx >= XKB_MAX_GROUPS) {
            log_err(rmlvo->ctx, XKB_ERROR_UNSUPPORTED_GROUP_INDEX,
                    "Maximum layout count reached: %"PRIu32"; "
                    "cannot add layout \"%s(%s)\" to the RMLVO builder.\n",
                    XKB_ERROR_UNSUPPORTED_GROUP_INDEX,
                    layout, (variant) ? variant : "");
            return false;
        }
    
        /* Append layout entry */
        const struct xkb_rmlvo_builder_layout new = {
            .layout = strdup_safe(layout),
            .variant = strdup_safe(variant)
        };
    
        if (!new.layout || (!new.variant && variant)) {
            free(new.layout);
            free(new.variant);
            log_err(rmlvo->ctx, XKB_ERROR_ALLOCATION_ERROR,
                    "Cannot allocate layout \"%s(%s)\" to the RMLVO builder.\n",
                    layout, (variant) ? variant : "");
            return false;
        }
    
        darray_append(rmlvo->layouts, new);
    
        if (!options)
            options_len = 0;
    
        /* Append layout-specific options entries */
        for (size_t k = 0; k < options_len; k++) {
            const struct xkb_rmlvo_builder_option option = {
                .option = strdup_safe(options[k]),
                .layout = idx
            };
    
            if (!option.option) {
                log_err(rmlvo->ctx, XKB_ERROR_ALLOCATION_ERROR,
                        "Cannot allocate option \"%s\" of layout \"%s(%s)\" "
                        "to the RMLVO builder.\n",
                        options[k], layout, (variant) ? variant : "");
                return false;
            }
    
            darray_append(rmlvo->options, option);
        }
    
        return true;
    }
    
    bool
    xkb_rmlvo_builder_append_option(struct xkb_rmlvo_builder *rmlvo,
                                    const char *option)
    {
        if (!option)
            return false;
    
        /* Check for previous entry */
        const struct xkb_rmlvo_builder_option *prev;
        darray_foreach(prev, rmlvo->options) {
            if (prev->layout == XKB_LAYOUT_INVALID &&
                strcmp(prev->option, option) == 0)
                return true;
        }
    
        /* Append new entry */
        const struct xkb_rmlvo_builder_option new = {
            .option = strdup_safe(option),
            .layout = XKB_LAYOUT_INVALID
        };
        if (!new.option) {
            log_err(rmlvo->ctx, XKB_ERROR_ALLOCATION_ERROR,
                    "Cannot allocate option \"%s\" to the RMLVO builder.\n",
                    option);
            return false;
        }
        darray_append(rmlvo->options, new);
        return true;
    }
    
    void
    xkb_rmlvo_builder_unref(struct xkb_rmlvo_builder *rmlvo)
    {
        if (!rmlvo || --rmlvo->refcnt > 0)
            return;
    
        free(rmlvo->rules);
        free(rmlvo->model);
    
        const struct xkb_rmlvo_builder_layout *layout;
        darray_foreach(layout, rmlvo->layouts) {
            free(layout->layout);
            free(layout->variant);
        }
        darray_free(rmlvo->layouts);
    
        const struct xkb_rmlvo_builder_option *option;
        darray_foreach(option, rmlvo->options)
            free(option->option);
        darray_free(rmlvo->options);
    
        xkb_context_unref(rmlvo->ctx);
        free(rmlvo);
    }
    
    /* NOTE: the converse function, `xkb_rules_names_to_rmlvo_builder`, is currently
     * only used in tests */
    bool
    xkb_rmlvo_builder_to_rules_names(const struct xkb_rmlvo_builder *builder,
                                     struct xkb_rule_names *rmlvo,
                                     char *buf, size_t buf_size)
    {
        rmlvo->rules = builder->rules;
        rmlvo->model = builder->model;
    
        char *start = buf;
        rmlvo->layout = start;
        darray_size_t k;
        const struct xkb_rmlvo_builder_layout *layout;
        darray_enumerate(k, layout, builder->layouts) {
            int count = snprintf(start, buf_size, "%s%s",
                                 (k > 0 ? "," : ""), layout->layout);
            if (count < 0 || (size_t) count >= buf_size)
                return false;
            buf_size -= count;
            start += count;
        }
        if (buf_size <= 1)
            return false;
        *start = '\0';
        start++;
        buf_size--;
    
        rmlvo->variant = start;
        darray_enumerate(k, layout, builder->layouts) {
            int count = snprintf(start, buf_size, "%s%s",
                                 (k > 0 ? "," : ""),
                                 (layout->variant ? layout->variant : ""));
            if (count < 0 || (size_t) count >= buf_size)
                return false;
            buf_size -= count;
            start += count;
        }
        if (buf_size <= 1)
            return false;
        *start = '\0';
        start++;
        buf_size--;
    
        rmlvo->options = start;
        const struct xkb_rmlvo_builder_option *option;
        darray_enumerate(k, option, builder->options) {
            int count = snprintf(start, buf_size, "%s%s",
                                 (k > 0 ? "," : ""), option->option);
            if (count < 0 || (size_t) count >= buf_size)
                return false;
            buf_size -= count;
            start += count;
            if (option->layout != XKB_LAYOUT_INVALID) {
                count = snprintf(start, buf_size, "%c%"PRIu32,
                                 OPTIONS_GROUP_SPECIFIER_PREFIX, option->layout);
                if (count < 0 || (size_t) count >= buf_size)
                    return false;
                buf_size -= count;
                start += count;
            }
        }
        if (buf_size == 0)
            return false;
        *start = '\0';
        return true;
    }