mirror of https://github.com/nirenjan/libx52.git
677 lines
19 KiB
C
677 lines
19 KiB
C
/*
|
|
* Saitek X52 Pro MFD & LED driver — keyboard layout (.layout) loader
|
|
*
|
|
* Copyright (C) 2026 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
|
*/
|
|
|
|
/*
|
|
* INI grammar (UTF-8 text):
|
|
*
|
|
* [Layout]
|
|
* Name=<short id> # e.g. us
|
|
* Description=<optional>
|
|
*
|
|
* [<one UTF-8 code point>] | [U+XXXX] | [u+XXXX]
|
|
* Key=<vkm_key name>|0xNN # HID usage (page 0x07), hex or symbolic (e.g. A, Enter)
|
|
* Mods=<modifier list> # optional; empty = none. Tokens: Shift LeftShift RightShift
|
|
* # Ctrl LeftCtrl RightCtrl Alt LeftAlt RightAlt
|
|
* # Gui LeftGui RightGui Super Meta
|
|
* # Separate tokens with ',' or '+' (whitespace trimmed).
|
|
*
|
|
* Section names (except Layout) must encode exactly one Unicode scalar value.
|
|
* Later entries replace earlier ones for the same code point.
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <stdbool.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#define PINELOG_MODULE X52D_MOD_LAYOUT
|
|
#include "ini.h"
|
|
#include "pinelog.h"
|
|
#include "x52d_const.h"
|
|
#include "x52d_layout.h"
|
|
|
|
struct x52d_layout_entry {
|
|
uint32_t cp;
|
|
vkm_key key;
|
|
vkm_key_modifiers mods;
|
|
};
|
|
|
|
struct x52d_layout {
|
|
char *name;
|
|
char *description;
|
|
struct x52d_layout_entry *entries;
|
|
size_t n_entries;
|
|
};
|
|
|
|
void x52d_layout_free(struct x52d_layout *layout)
|
|
{
|
|
if (layout == NULL) {
|
|
return;
|
|
}
|
|
free(layout->name);
|
|
free(layout->description);
|
|
free(layout->entries);
|
|
free(layout);
|
|
}
|
|
|
|
const char *x52d_layout_get_name(const struct x52d_layout *layout)
|
|
{
|
|
if (layout == NULL || layout->name == NULL) {
|
|
return "";
|
|
}
|
|
return layout->name;
|
|
}
|
|
|
|
static int cmp_entry_cp(const void *a, const void *b)
|
|
{
|
|
const struct x52d_layout_entry *ea = a;
|
|
const struct x52d_layout_entry *eb = b;
|
|
if (ea->cp < eb->cp) {
|
|
return -1;
|
|
}
|
|
if (ea->cp > eb->cp) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
bool x52d_layout_lookup(const struct x52d_layout *layout, uint32_t cp,
|
|
struct x52d_layout_recipe *out_recipe)
|
|
{
|
|
struct x52d_layout_entry key = {.cp = cp};
|
|
struct x52d_layout_entry *found;
|
|
|
|
if (layout == NULL || out_recipe == NULL || layout->entries == NULL ||
|
|
layout->n_entries == 0U) {
|
|
return false;
|
|
}
|
|
|
|
found = bsearch(&key, layout->entries, layout->n_entries, sizeof(key), cmp_entry_cp);
|
|
if (found == NULL) {
|
|
return false;
|
|
}
|
|
out_recipe->key = found->key;
|
|
out_recipe->mods = found->mods;
|
|
return true;
|
|
}
|
|
|
|
static int utf8_decode_one(const char *s, uint32_t *out_cp, size_t *out_len)
|
|
{
|
|
const unsigned char *u = (const unsigned char *)s;
|
|
uint32_t cp;
|
|
size_t n;
|
|
|
|
if (s == NULL || out_cp == NULL || out_len == NULL) {
|
|
return EINVAL;
|
|
}
|
|
if (u[0] < 0x80U) {
|
|
if (u[0] == 0U) {
|
|
return EINVAL;
|
|
}
|
|
*out_cp = u[0];
|
|
*out_len = 1U;
|
|
return 0;
|
|
}
|
|
if ((u[0] & 0xE0U) == 0xC0U) {
|
|
if ((u[0] & 0x1EU) == 0U) {
|
|
return EINVAL; /* overlong */
|
|
}
|
|
if (u[1] == 0U || (u[1] &0xC0U) != 0x80U) {
|
|
return EINVAL;
|
|
}
|
|
cp = (uint32_t)(u[0] & 0x1FU) << 6 | (uint32_t)(u[1] & 0x3FU);
|
|
n = 2U;
|
|
} else if ((u[0] & 0xF0U) == 0xE0U) {
|
|
if (u[1] == 0U || u[2] == 0U) {
|
|
return EINVAL;
|
|
}
|
|
if ((u[1] & 0xC0U) != 0x80U || (u[2] & 0xC0U) != 0x80U) {
|
|
return EINVAL;
|
|
}
|
|
cp = (uint32_t)(u[0] & 0x0FU) << 12 | (uint32_t)(u[1] & 0x3FU) << 6 |
|
|
(uint32_t)(u[2] & 0x3FU);
|
|
n = 3U;
|
|
if (cp < 0x800U) {
|
|
return EINVAL;
|
|
}
|
|
} else if ((u[0] & 0xF8U) == 0xF0U) {
|
|
if (u[1] == 0U || u[2] == 0U || u[3] == 0U) {
|
|
return EINVAL;
|
|
}
|
|
if ((u[1] & 0xC0U) != 0x80U || (u[2] & 0xC0U) != 0x80U ||
|
|
(u[3] & 0xC0U) != 0x80U) {
|
|
return EINVAL;
|
|
}
|
|
cp = (uint32_t)(u[0] & 0x07U) << 18 | (uint32_t)(u[1] & 0x3FU) << 12 |
|
|
(uint32_t)(u[2] & 0x3FU) << 6 | (uint32_t)(u[3] & 0x3FU);
|
|
n = 4U;
|
|
if (cp < 0x10000U || cp > 0x10FFFFU) {
|
|
return EINVAL;
|
|
}
|
|
} else {
|
|
return EINVAL;
|
|
}
|
|
|
|
*out_cp = cp;
|
|
*out_len = n;
|
|
return 0;
|
|
}
|
|
|
|
static void trim_inplace(char *s)
|
|
{
|
|
char *end;
|
|
size_t i;
|
|
|
|
if (s == NULL) {
|
|
return;
|
|
}
|
|
i = 0;
|
|
while (s[i] != '\0' && isspace((unsigned char)s[i])) {
|
|
i++;
|
|
}
|
|
if (i > 0U) {
|
|
memmove(s, s + i, strlen(s + i) + 1U);
|
|
}
|
|
end = s + strlen(s);
|
|
while (end > s && isspace((unsigned char)end[-1])) {
|
|
end--;
|
|
*end = '\0';
|
|
}
|
|
}
|
|
|
|
static const struct vkm_key_name {
|
|
const char *name;
|
|
vkm_key key;
|
|
} vkm_key_names[] = {
|
|
{"0", VKM_KEY_0}, {"1", VKM_KEY_1},
|
|
{"2", VKM_KEY_2}, {"3", VKM_KEY_3},
|
|
{"4", VKM_KEY_4}, {"5", VKM_KEY_5},
|
|
{"6", VKM_KEY_6}, {"7", VKM_KEY_7},
|
|
{"8", VKM_KEY_8}, {"9", VKM_KEY_9},
|
|
{"A", VKM_KEY_A}, {"Application", VKM_KEY_APPLICATION},
|
|
{"Apostrophe", VKM_KEY_APOSTROPHE},
|
|
{"B", VKM_KEY_B}, {"Backslash", VKM_KEY_BACKSLASH},
|
|
{"Backspace", VKM_KEY_BACKSPACE},
|
|
{"C", VKM_KEY_C}, {"CapsLock", VKM_KEY_CAPS_LOCK},
|
|
{"Comma", VKM_KEY_COMMA}, {"D", VKM_KEY_D},
|
|
{"Delete", VKM_KEY_DELETE_FORWARD},
|
|
{"DownArrow", VKM_KEY_DOWN_ARROW},
|
|
{"E", VKM_KEY_E}, {"End", VKM_KEY_END},
|
|
{"Enter", VKM_KEY_ENTER}, {"Equal", VKM_KEY_EQUAL},
|
|
{"Escape", VKM_KEY_ESCAPE}, {"F", VKM_KEY_F},
|
|
{"F1", VKM_KEY_F1}, {"F10", VKM_KEY_F10},
|
|
{"F11", VKM_KEY_F11}, {"F12", VKM_KEY_F12},
|
|
{"F2", VKM_KEY_F2}, {"F3", VKM_KEY_F3},
|
|
{"F4", VKM_KEY_F4}, {"F5", VKM_KEY_F5},
|
|
{"F6", VKM_KEY_F6}, {"F7", VKM_KEY_F7},
|
|
{"F8", VKM_KEY_F8}, {"F9", VKM_KEY_F9},
|
|
{"G", VKM_KEY_G}, {"GraveAccent", VKM_KEY_GRAVE_ACCENT},
|
|
{"H", VKM_KEY_H}, {"Home", VKM_KEY_HOME},
|
|
{"I", VKM_KEY_I}, {"Insert", VKM_KEY_INSERT},
|
|
{"IntlBackslash", VKM_KEY_INTL_BACKSLASH},
|
|
{"J", VKM_KEY_J}, {"K", VKM_KEY_K},
|
|
{"L", VKM_KEY_L}, {"LeftAlt", VKM_KEY_LEFT_ALT},
|
|
{"LeftArrow", VKM_KEY_LEFT_ARROW},
|
|
{"LeftBracket", VKM_KEY_LEFT_BRACKET},
|
|
{"LeftCtrl", VKM_KEY_LEFT_CTRL},
|
|
{"LeftGui", VKM_KEY_LEFT_GUI},
|
|
{"LeftShift", VKM_KEY_LEFT_SHIFT},
|
|
{"M", VKM_KEY_M}, {"Minus", VKM_KEY_MINUS},
|
|
{"N", VKM_KEY_N}, {"NonUsHash", VKM_KEY_NONUS_HASH},
|
|
{"O", VKM_KEY_O}, {"P", VKM_KEY_P},
|
|
{"PageDown", VKM_KEY_PAGE_DOWN},
|
|
{"PageUp", VKM_KEY_PAGE_UP}, {"Pause", VKM_KEY_PAUSE},
|
|
{"Period", VKM_KEY_PERIOD}, {"PrintScreen", VKM_KEY_PRINT_SCREEN},
|
|
{"Q", VKM_KEY_Q}, {"R", VKM_KEY_R},
|
|
{"Return", VKM_KEY_ENTER}, {"RightAlt", VKM_KEY_RIGHT_ALT},
|
|
{"RightArrow", VKM_KEY_RIGHT_ARROW},
|
|
{"RightBracket", VKM_KEY_RIGHT_BRACKET},
|
|
{"RightCtrl", VKM_KEY_RIGHT_CTRL},
|
|
{"RightGui", VKM_KEY_RIGHT_GUI},
|
|
{"RightShift", VKM_KEY_RIGHT_SHIFT},
|
|
{"S", VKM_KEY_S}, {"ScrollLock", VKM_KEY_SCROLL_LOCK},
|
|
{"Semicolon", VKM_KEY_SEMICOLON},
|
|
{"Slash", VKM_KEY_SLASH}, {"Space", VKM_KEY_SPACE},
|
|
{"T", VKM_KEY_T}, {"Tab", VKM_KEY_TAB},
|
|
{"U", VKM_KEY_U}, {"UpArrow", VKM_KEY_UP_ARROW},
|
|
{"V", VKM_KEY_V}, {"W", VKM_KEY_W},
|
|
{"X", VKM_KEY_X}, {"Y", VKM_KEY_Y},
|
|
{"Z", VKM_KEY_Z},
|
|
{"Keypad0", VKM_KEY_KEYPAD_0},
|
|
{"Keypad1", VKM_KEY_KEYPAD_1},
|
|
{"Keypad2", VKM_KEY_KEYPAD_2},
|
|
{"Keypad3", VKM_KEY_KEYPAD_3},
|
|
{"Keypad4", VKM_KEY_KEYPAD_4},
|
|
{"Keypad5", VKM_KEY_KEYPAD_5},
|
|
{"Keypad6", VKM_KEY_KEYPAD_6},
|
|
{"Keypad7", VKM_KEY_KEYPAD_7},
|
|
{"Keypad8", VKM_KEY_KEYPAD_8},
|
|
{"Keypad9", VKM_KEY_KEYPAD_9},
|
|
{"KeypadComma", VKM_KEY_KEYPAD_COMMA},
|
|
{"KeypadDecimal", VKM_KEY_KEYPAD_DECIMAL},
|
|
{"KeypadDivide", VKM_KEY_KEYPAD_DIVIDE},
|
|
{"KeypadEnter", VKM_KEY_KEYPAD_ENTER},
|
|
{"KeypadMinus", VKM_KEY_KEYPAD_MINUS},
|
|
{"KeypadMultiply", VKM_KEY_KEYPAD_MULTIPLY},
|
|
{"KeypadNumLock", VKM_KEY_KEYPAD_NUM_LOCK},
|
|
{"KeypadPlus", VKM_KEY_KEYPAD_PLUS},
|
|
};
|
|
|
|
static int parse_key_token(const char *value, vkm_key *out_key)
|
|
{
|
|
char tmp[96];
|
|
char *endptr;
|
|
unsigned long u;
|
|
size_t i;
|
|
size_t n;
|
|
|
|
if (value == NULL || out_key == NULL) {
|
|
return EINVAL;
|
|
}
|
|
if (strlen(value) >= sizeof(tmp)) {
|
|
return EINVAL;
|
|
}
|
|
memcpy(tmp, value, strlen(value) + 1U);
|
|
trim_inplace(tmp);
|
|
|
|
if (tmp[0] == '0' && (tmp[1] == 'x' || tmp[1] == 'X')) {
|
|
errno = 0;
|
|
u = strtoul(tmp, &endptr, 16);
|
|
if (errno != 0 || endptr == tmp) {
|
|
return EINVAL;
|
|
}
|
|
while (*endptr != '\0' && isspace((unsigned char)*endptr)) {
|
|
endptr++;
|
|
}
|
|
if (*endptr != '\0' || u >= (unsigned long)VKM_KEY_MAX) {
|
|
return EINVAL;
|
|
}
|
|
*out_key = (vkm_key)u;
|
|
return 0;
|
|
}
|
|
|
|
n = sizeof(vkm_key_names) / sizeof(vkm_key_names[0]);
|
|
for (i = 0; i < n; i++) {
|
|
if (!strcmp(tmp, vkm_key_names[i].name)) {
|
|
*out_key = vkm_key_names[i].key;
|
|
return 0;
|
|
}
|
|
}
|
|
return EINVAL;
|
|
}
|
|
|
|
static int token_to_modifier(const char *tok, vkm_key_modifiers *acc)
|
|
{
|
|
if (!strcasecmp(tok, "Shift") || !strcasecmp(tok, "LeftShift")) {
|
|
*acc = (vkm_key_modifiers)(*acc | VKM_KEY_MOD_SHIFT);
|
|
} else if (!strcasecmp(tok, "RightShift")) {
|
|
*acc = (vkm_key_modifiers)(*acc | VKM_KEY_MOD_RSHIFT);
|
|
} else if (!strcasecmp(tok, "Ctrl") || !strcasecmp(tok, "Control") ||
|
|
!strcasecmp(tok, "LeftCtrl")) {
|
|
*acc = (vkm_key_modifiers)(*acc | VKM_KEY_MOD_CTRL);
|
|
} else if (!strcasecmp(tok, "RightCtrl")) {
|
|
*acc = (vkm_key_modifiers)(*acc | VKM_KEY_MOD_RCTRL);
|
|
} else if (!strcasecmp(tok, "Alt") || !strcasecmp(tok, "LeftAlt")) {
|
|
*acc = (vkm_key_modifiers)(*acc | VKM_KEY_MOD_ALT);
|
|
} else if (!strcasecmp(tok, "RightAlt")) {
|
|
*acc = (vkm_key_modifiers)(*acc | VKM_KEY_MOD_RALT);
|
|
} else if (!strcasecmp(tok, "Gui") || !strcasecmp(tok, "LeftGui") ||
|
|
!strcasecmp(tok, "Super") || !strcasecmp(tok, "Meta")) {
|
|
*acc = (vkm_key_modifiers)(*acc | VKM_KEY_MOD_GUI);
|
|
} else if (!strcasecmp(tok, "RightGui")) {
|
|
*acc = (vkm_key_modifiers)(*acc | VKM_KEY_MOD_RGUI);
|
|
} else if (tok[0] == '\0') {
|
|
return 0;
|
|
} else {
|
|
return EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int parse_modifiers(const char *value, vkm_key_modifiers *out_mods)
|
|
{
|
|
char buf[128];
|
|
char *p;
|
|
char *saveptr = NULL;
|
|
char *tok;
|
|
|
|
if (value == NULL || out_mods == NULL) {
|
|
return EINVAL;
|
|
}
|
|
*out_mods = VKM_KEY_MOD_NONE;
|
|
if (value[0] == '\0') {
|
|
return 0;
|
|
}
|
|
if (strlen(value) >= sizeof(buf)) {
|
|
return EINVAL;
|
|
}
|
|
memcpy(buf, value, strlen(value) + 1U);
|
|
trim_inplace(buf);
|
|
|
|
/* Split on comma first */
|
|
for (p = buf;; p = NULL) {
|
|
char *seg = strtok_r(p, ",", &saveptr);
|
|
if (seg == NULL) {
|
|
break;
|
|
}
|
|
trim_inplace(seg);
|
|
if (seg[0] == '\0') {
|
|
continue;
|
|
}
|
|
/* Then '+' within each segment */
|
|
char *subsave = NULL;
|
|
char *q;
|
|
for (q = seg;; q = NULL) {
|
|
tok = strtok_r(q, "+", &subsave);
|
|
if (tok == NULL) {
|
|
break;
|
|
}
|
|
trim_inplace(tok);
|
|
if (token_to_modifier(tok, out_mods) != 0) {
|
|
return EINVAL;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int layout_entry_replace_or_append(struct x52d_layout *layout, uint32_t cp, vkm_key key,
|
|
vkm_key_modifiers mods)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < layout->n_entries; i++) {
|
|
if (layout->entries[i].cp == cp) {
|
|
layout->entries[i].key = key;
|
|
layout->entries[i].mods = mods;
|
|
return 0;
|
|
}
|
|
}
|
|
{
|
|
struct x52d_layout_entry *ne =
|
|
realloc(layout->entries, (layout->n_entries + 1U) * sizeof(*ne));
|
|
if (ne == NULL) {
|
|
return ENOMEM;
|
|
}
|
|
layout->entries = ne;
|
|
layout->entries[layout->n_entries].cp = cp;
|
|
layout->entries[layout->n_entries].key = key;
|
|
layout->entries[layout->n_entries].mods = mods;
|
|
layout->n_entries++;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
struct layout_parse_ctx {
|
|
struct x52d_layout *layout;
|
|
char current_section[128];
|
|
bool in_layout_meta;
|
|
bool mapping_have_key;
|
|
uint32_t mapping_cp;
|
|
vkm_key mapping_key;
|
|
vkm_key_modifiers mapping_mods;
|
|
int error;
|
|
};
|
|
|
|
static void layout_flush_mapping(struct layout_parse_ctx *ctx)
|
|
{
|
|
int rc;
|
|
|
|
if (!ctx->in_layout_meta && ctx->mapping_have_key && ctx->error == 0) {
|
|
rc = layout_entry_replace_or_append(ctx->layout, ctx->mapping_cp, ctx->mapping_key,
|
|
ctx->mapping_mods);
|
|
if (rc != 0) {
|
|
ctx->error = rc;
|
|
}
|
|
}
|
|
ctx->mapping_have_key = false;
|
|
ctx->mapping_mods = VKM_KEY_MOD_NONE;
|
|
}
|
|
|
|
static int layout_begin_section(struct layout_parse_ctx *ctx, const char *section)
|
|
{
|
|
uint32_t cp;
|
|
size_t consumed;
|
|
int rc;
|
|
size_t slen;
|
|
|
|
if (section == NULL) {
|
|
return EINVAL;
|
|
}
|
|
if (strlen(section) >= sizeof(ctx->current_section)) {
|
|
PINELOG_ERROR(_("Layout section name too long"));
|
|
return EINVAL;
|
|
}
|
|
memcpy(ctx->current_section, section, strlen(section) + 1U);
|
|
|
|
if (!strcasecmp(section, "Layout")) {
|
|
ctx->in_layout_meta = true;
|
|
return 0;
|
|
}
|
|
|
|
ctx->in_layout_meta = false;
|
|
|
|
/* INI section names cannot express ']' as a lone code point (e.g. "[]]" is
|
|
* parsed as an empty section). Allow U+ABCD or u+ABCD (1-6 hex digits). */
|
|
if (section[0] == 'U' || section[0] == 'u') {
|
|
if (section[1] == '+' && section[2] != '\0') {
|
|
const char *p = section + 2;
|
|
unsigned long uh = 0;
|
|
char *endp = NULL;
|
|
|
|
errno = 0;
|
|
uh = strtoul(p, &endp, 16);
|
|
if (errno == 0 && endp != p && *endp == '\0' && uh <= 0x10FFFFUL &&
|
|
(uh < 0xD800UL || uh > 0xDFFFUL)) {
|
|
ctx->mapping_cp = (uint32_t)uh;
|
|
ctx->mapping_mods = VKM_KEY_MOD_NONE;
|
|
ctx->mapping_have_key = false;
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
rc = utf8_decode_one(section, &cp, &consumed);
|
|
if (rc != 0) {
|
|
PINELOG_ERROR(_("Invalid UTF-8 in layout section '%s'"), section);
|
|
return EINVAL;
|
|
}
|
|
slen = strlen(section);
|
|
if (consumed != slen) {
|
|
PINELOG_ERROR(_("Layout section must encode exactly one code point: '%s'"), section);
|
|
return EINVAL;
|
|
}
|
|
ctx->mapping_cp = cp;
|
|
ctx->mapping_mods = VKM_KEY_MOD_NONE;
|
|
ctx->mapping_have_key = false;
|
|
return 0;
|
|
}
|
|
|
|
static int layout_ini_handler(void *user, const char *section, const char *name, const char *value)
|
|
{
|
|
struct layout_parse_ctx *ctx = user;
|
|
int rc;
|
|
vkm_key pk;
|
|
vkm_key_modifiers pm;
|
|
|
|
if (ctx->error != 0) {
|
|
return 0;
|
|
}
|
|
if (section == NULL || name == NULL || value == NULL) {
|
|
ctx->error = EINVAL;
|
|
return 0;
|
|
}
|
|
|
|
if (strcmp(section, ctx->current_section) != 0) {
|
|
layout_flush_mapping(ctx);
|
|
rc = layout_begin_section(ctx, section);
|
|
if (rc != 0) {
|
|
ctx->error = rc;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (ctx->in_layout_meta) {
|
|
if (!strcasecmp(name, "Name")) {
|
|
free(ctx->layout->name);
|
|
ctx->layout->name = strdup(value);
|
|
if (ctx->layout->name == NULL) {
|
|
ctx->error = ENOMEM;
|
|
}
|
|
} else if (!strcasecmp(name, "Description")) {
|
|
free(ctx->layout->description);
|
|
ctx->layout->description = strdup(value);
|
|
if (ctx->layout->description == NULL) {
|
|
ctx->error = ENOMEM;
|
|
}
|
|
}
|
|
return ctx->error != 0 ? 0 : 1;
|
|
}
|
|
|
|
if (!strcasecmp(name, "Key")) {
|
|
rc = parse_key_token(value, &pk);
|
|
if (rc != 0) {
|
|
PINELOG_ERROR(_("Unknown Key value '%s'"), value);
|
|
ctx->error = EINVAL;
|
|
return 0;
|
|
}
|
|
ctx->mapping_key = pk;
|
|
ctx->mapping_have_key = true;
|
|
} else if (!strcasecmp(name, "Mods")) {
|
|
rc = parse_modifiers(value, &pm);
|
|
if (rc != 0) {
|
|
PINELOG_ERROR(_("Invalid Mods value '%s'"), value);
|
|
ctx->error = EINVAL;
|
|
return 0;
|
|
}
|
|
ctx->mapping_mods = pm;
|
|
}
|
|
|
|
/* unknown keys ignored for forward-compatible layout files */
|
|
return ctx->error != 0 ? 0 : 1;
|
|
}
|
|
|
|
static int layout_finalize(struct layout_parse_ctx *ctx)
|
|
{
|
|
layout_flush_mapping(ctx);
|
|
if (ctx->error != 0) {
|
|
return ctx->error;
|
|
}
|
|
if (ctx->layout->n_entries > 0U) {
|
|
qsort(ctx->layout->entries, ctx->layout->n_entries, sizeof(ctx->layout->entries[0]),
|
|
cmp_entry_cp);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static struct x52d_layout *layout_new(void)
|
|
{
|
|
struct x52d_layout *layout = calloc(1, sizeof(*layout));
|
|
return layout;
|
|
}
|
|
|
|
int x52d_layout_load_buffer(struct x52d_layout **out, const char *data, size_t len)
|
|
{
|
|
struct layout_parse_ctx ctx;
|
|
char *nulbuf;
|
|
int ir;
|
|
int rc;
|
|
|
|
if (out == NULL || data == NULL) {
|
|
return EINVAL;
|
|
}
|
|
*out = NULL;
|
|
|
|
ctx.layout = layout_new();
|
|
if (ctx.layout == NULL) {
|
|
return ENOMEM;
|
|
}
|
|
ctx.current_section[0] = '\0';
|
|
ctx.in_layout_meta = false;
|
|
ctx.mapping_have_key = false;
|
|
ctx.mapping_cp = 0;
|
|
ctx.mapping_key = VKM_KEY_NONE;
|
|
ctx.mapping_mods = VKM_KEY_MOD_NONE;
|
|
ctx.error = 0;
|
|
|
|
nulbuf = malloc(len + 1U);
|
|
if (nulbuf == NULL) {
|
|
x52d_layout_free(ctx.layout);
|
|
return ENOMEM;
|
|
}
|
|
memcpy(nulbuf, data, len);
|
|
nulbuf[len] = '\0';
|
|
ir = ini_parse_string(nulbuf, layout_ini_handler, &ctx);
|
|
free(nulbuf);
|
|
|
|
if (ir < 0) {
|
|
x52d_layout_free(ctx.layout);
|
|
return EIO;
|
|
}
|
|
if (ir > 0) {
|
|
PINELOG_ERROR(_("Layout parse error at line %d"), ir);
|
|
x52d_layout_free(ctx.layout);
|
|
return EINVAL;
|
|
}
|
|
|
|
rc = layout_finalize(&ctx);
|
|
if (rc != 0) {
|
|
x52d_layout_free(ctx.layout);
|
|
return rc;
|
|
}
|
|
|
|
*out = ctx.layout;
|
|
return 0;
|
|
}
|
|
|
|
int x52d_layout_load_file(struct x52d_layout **out, const char *path)
|
|
{
|
|
struct layout_parse_ctx ctx;
|
|
int ir;
|
|
int rc;
|
|
|
|
if (out == NULL || path == NULL) {
|
|
return EINVAL;
|
|
}
|
|
*out = NULL;
|
|
|
|
ctx.layout = layout_new();
|
|
if (ctx.layout == NULL) {
|
|
return ENOMEM;
|
|
}
|
|
ctx.current_section[0] = '\0';
|
|
ctx.in_layout_meta = false;
|
|
ctx.mapping_have_key = false;
|
|
ctx.mapping_cp = 0;
|
|
ctx.mapping_key = VKM_KEY_NONE;
|
|
ctx.mapping_mods = VKM_KEY_MOD_NONE;
|
|
ctx.error = 0;
|
|
|
|
ir = ini_parse(path, layout_ini_handler, &ctx);
|
|
if (ir < 0) {
|
|
x52d_layout_free(ctx.layout);
|
|
return EIO;
|
|
}
|
|
if (ir > 0) {
|
|
PINELOG_ERROR(_("Layout parse error in %s at line %d"), path, ir);
|
|
x52d_layout_free(ctx.layout);
|
|
return EINVAL;
|
|
}
|
|
|
|
rc = layout_finalize(&ctx);
|
|
if (rc != 0) {
|
|
x52d_layout_free(ctx.layout);
|
|
return rc;
|
|
}
|
|
|
|
*out = ctx.layout;
|
|
return 0;
|
|
}
|