/*
* Copyright © 2025 Pierre Le Marre <dev@wismill.eu>
* SPDX-License-Identifier: MIT
*/
#include "config.h"
#include <assert.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;
}
struct xkb_rmlvo_builder *
xkb_rmlvo_builder_ref(struct xkb_rmlvo_builder *rmlvo)
{
assert(rmlvo->refcnt > 0);
rmlvo->refcnt++;
return rmlvo;
}
void
xkb_rmlvo_builder_unref(struct xkb_rmlvo_builder *rmlvo)
{
assert(!rmlvo || rmlvo->refcnt > 0);
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;
}