blob: 47d36152be15f4fa77981497786b5e5d29eaefff [file] [log] [blame]
/* SPDX-License-Identifier: GPL-2.0 */
// Read a toml keymap or plain text keymap (ir-keytable 1.14 or earlier
// format).
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <limits.h>
#include <stdbool.h>
#include <sys/types.h>
#include <argp.h>
#include "keymap.h"
#include "toml.h"
#ifdef ENABLE_NLS
# define _(string) gettext(string)
# include "gettext.h"
# include <locale.h>
# include <langinfo.h>
# include <iconv.h>
#else
# define _(string) string
#endif
void free_keymap(struct keymap *map)
{
struct scancode_entry *se;
struct raw_entry *re;
struct protocol_param *param;
struct keymap *next;
while (map) {
re = map->raw;
while (re) {
struct raw_entry *next = re->next;
free(re->keycode);
free(re);
re = next;
}
se = map->scancode;
while (se) {
struct scancode_entry *next = se->next;
free(se->keycode);
free(se);
se = next;
}
param = map->param;
while (param) {
struct protocol_param *next = param->next;
free(param->name);
free(param);
param = next;
}
next = map->next;
free(map->name);
free(map->protocol);
free(map->variant);
free(map);
map = next;
}
}
static error_t parse_plain_keymap(char *fname, struct keymap **keymap, bool verbose)
{
FILE *fin;
int line = 0;
char *scancode, *keycode, s[2048];
struct scancode_entry *se;
struct keymap *map;
map = calloc(1, sizeof(*map));
if (!map) {
perror("parse_keymap");
return ENOMEM;
}
if (verbose)
fprintf(stderr, _("Parsing %s keycode file as plain text\n"), fname);
fin = fopen(fname, "r");
if (!fin) {
fprintf(stderr, _("%s: error: cannot open: %m\n"), fname);
return EINVAL;
}
while (fgets(s, sizeof(s), fin)) {
char *p = s;
line++;
while (*p == ' ' || *p == '\t')
p++;
if (line==1 && p[0] == '#') {
p++;
p = strtok(p, "\n\t =:");
do {
if (!p)
goto err_einval;
if (!strcmp(p, "table")) {
p = strtok(NULL,"\n, ");
if (!p)
goto err_einval;
map->name = strdup(p);
} else if (!strcmp(p, "type")) {
p = strtok(NULL, " ,\n");
if (!p)
goto err_einval;
while (p) {
if (!map->protocol) {
map->protocol = strdup(p);
} else {
struct keymap *k;
k = calloc(1, sizeof(*k));
k->protocol = strdup(p);
k->next = map->next;
map->next = k;
}
p = strtok(NULL, " ,\n");
}
} else {
goto err_einval;
}
p = strtok(NULL, "\n\t =:");
} while (p);
continue;
}
if (*p == '\n' || *p == '#')
continue;
scancode = strtok(p, "\n\t =:");
if (!scancode)
goto err_einval;
if (!strcasecmp(scancode, "scancode")) {
scancode = strtok(NULL, "\n\t =:");
if (!scancode)
goto err_einval;
}
keycode = strtok(NULL, "\n\t =:(");
if (!keycode)
goto err_einval;
se = calloc(1, sizeof(*se));
if (!se) {
free_keymap(map);
perror("parse_keymap");
fclose(fin);
return ENOMEM;
}
se->scancode = strtoull(scancode, NULL, 0);
se->keycode = strdup(keycode);
se->next = map->scancode;
map->scancode = se;
}
fclose(fin);
if (!map->protocol) {
fprintf(stderr, _("Missing protocol in %s\n"), fname);
return EINVAL;
}
*keymap = map;
return 0;
err_einval:
free_keymap(map);
fprintf(stderr, _("Invalid parameter on line %d of %s\n"),
line, fname);
return EINVAL;
}
static error_t parse_rawir_string(const char *fname, char *str, struct raw_entry **entry)
{
struct raw_entry *re;
const char sep[] = "\n\r\t\v ,";
const char *p;
char *copy;
int i, size = 0;
// First do one scan so that we know the length
copy = strdup(str);
p = strtok(copy, sep);
while (p) {
size++;
p = strtok(NULL, sep);
}
re = calloc(1, sizeof(*re) + sizeof(re->raw[0]) * size);
if (!re) {
fprintf(stderr, _("Failed to allocate memory"));
free(copy);
return EINVAL;
}
// Second scan to extract values and validate
strcpy(copy, str);
p = strtok(copy, sep);
i = 0;
while (p) {
long int value;
char *q;
value = strtoll(p, &q, 10);
if (*q || value == 0 || errno) {
fprintf(stderr, _("%s: incorrect raw value `%s'"),
fname, p);
free(copy);
free(re);
return EINVAL;
}
if (value < 0) {
if (i % 2)
value = -value;
else {
fprintf(stderr, _("%s: negative raw value `%ld` at position %d only allowed for gaps/spaces"),
fname, value, i);
free(copy);
free(re);
return EINVAL;
}
}
if (value <= 0 || value > USHRT_MAX) {
fprintf(stderr, _("%s: raw value %ld out of range"),
fname, value);
free(copy);
free(re);
return EINVAL;
}
re->raw[i++] = value;
p = strtok(NULL, sep);
}
free(copy);
re->raw_length = size;
*entry = re;
return 0;
}
static error_t parse_toml_raw_part(const char *fname, struct toml_array_t *raw, struct keymap *map, bool verbose)
{
char *keycode, *raw_str;
struct toml_table_t *t;
struct raw_entry *re;
const char *traw;
int ind = 0;
while ((t = toml_table_at(raw, ind++)) != NULL) {
traw = toml_raw_in(t, "keycode");
if (!traw) {
fprintf(stderr, _("%s: invalid keycode for raw entry %d\n"),
fname, ind);
return EINVAL;
}
if (toml_rtos(traw, &keycode)) {
fprintf(stderr, _("%s: bad value `%s' for keycode\n"),
fname, traw);
return EINVAL;
}
traw = toml_raw_in(t, "raw");
if (!traw) {
fprintf(stderr, _("%s: missing raw value for entry %d\n"),
fname, ind);
free(keycode);
return EINVAL;
}
if (toml_rtos(traw, &raw_str)) {
fprintf(stderr, _("%s: bad value `%s' for keycode\n"),
fname, traw);
free(keycode);
return EINVAL;
}
if (parse_rawir_string(fname, raw_str, &re)) {
free(keycode);
free(raw_str);
return EINVAL;
}
free(raw_str);
re->keycode = keycode;
re->next = map->raw;
map->raw = re;
}
return 0;
}
static error_t parse_toml_protocol(const char *fname, struct toml_table_t *proot, struct keymap **keymap, bool verbose)
{
struct toml_table_t *scancodes;
struct toml_array_t *rawarray;
const char *raw, *key;
bool have_raw_protocol = false;
struct keymap *map;
char *p;
int i = 0;
map = calloc(1, sizeof(*map));
if (!map) {
perror("parse_toml_protocol");
return ENOMEM;
}
*keymap = map;
raw = toml_raw_in(proot, "protocol");
if (!raw) {
fprintf(stderr, _("%s: protocol missing\n"), fname);
free_keymap(map);
return EINVAL;
}
if (toml_rtos(raw, &p)) {
fprintf(stderr, _("%s: bad value `%s' for protocol\n"), fname, raw);
free_keymap(map);
return EINVAL;
}
map->protocol = p;
if (!strcmp(p, "raw"))
have_raw_protocol = true;
raw = toml_raw_in(proot, "variant");
if (raw) {
if (toml_rtos(raw, &p)) {
fprintf(stderr, _("%s: bad value `%s' for variant\n"), fname, raw);
free_keymap(map);
return EINVAL;
}
map->variant = p;
}
raw = toml_raw_in(proot, "name");
if (raw) {
if (toml_rtos(raw, &p)) {
fprintf(stderr, _("%s: bad value `%s' for name\n"), fname, raw);
free_keymap(map);
return EINVAL;
}
map->name = p;
}
rawarray = toml_array_in(proot, "raw");
if (rawarray) {
if (toml_raw_in(proot, "scancodes")) {
fprintf(stderr, _("Cannot have both [raw] and [scancode] sections"));
free_keymap(map);
return EINVAL;
}
if (!have_raw_protocol) {
fprintf(stderr, _("Keymap with raw entries must have raw protocol"));
free_keymap(map);
return EINVAL;
}
error_t err = parse_toml_raw_part(fname, rawarray, map, verbose);
if (err != 0) {
free_keymap(map);
return err;
}
} else if (have_raw_protocol) {
fprintf(stderr, _("Keymap with raw protocol must have raw entries"));
free_keymap(map);
return EINVAL;
}
for (i = 0; (key = toml_key_in(proot, i)) != NULL; i++) {
int64_t value;
raw = toml_raw_in(proot, key);
if (!toml_rtoi(raw, &value)) {
struct protocol_param *param;
param = malloc(sizeof(*param));
param->name = strdup(key);
param->value = value;
param->next = map->param;
map->param = param;
if (verbose)
fprintf(stderr, _("%s: protocol parameter %s=%ld\n"), fname, param->name, param->value);
}
}
scancodes = toml_table_in(proot, "scancodes");
if (!scancodes) {
if (verbose)
fprintf(stderr, _("%s: no [protocols.scancodes] section\n"), fname);
return 0;
}
struct scancode_entry **next = &map->scancode;
i = 0;
for (;;) {
struct scancode_entry *se;
const char *scancode;
char *keycode;
scancode = toml_key_in(scancodes, i++);
if (!scancode)
break;
raw = toml_raw_in(scancodes, scancode);
if (!raw) {
fprintf(stderr, _("%s: invalid value `%s'\n"), fname, scancode);
free_keymap(map);
return EINVAL;
}
if (toml_rtos(raw, &keycode)) {
fprintf(stderr, _("%s: bad value `%s' for keycode\n"),
fname, keycode);
free_keymap(map);
return EINVAL;
}
se = calloc(1, sizeof(*se));
if (!se) {
perror("parse_keymap");
free(keycode);
free_keymap(map);
return ENOMEM;
}
se->scancode = strtoull(scancode, NULL, 0);
se->keycode = keycode;
*next = se;
next = &se->next;
}
return 0;
}
static error_t parse_toml_keymap(char *fname, struct keymap **keymap, bool verbose)
{
struct toml_table_t *root, *proot;
struct toml_array_t *arr;
int ret, i = 0;
char buf[200];
FILE *fin;
if (verbose)
fprintf(stderr, _("Parsing %s keycode file as toml\n"), fname);
fin = fopen(fname, "r");
if (!fin) {
fprintf(stderr, _("%s: error: cannot open: %m\n"), fname);
return EINVAL;
}
root = toml_parse_file(fin, buf, sizeof(buf));
fclose(fin);
if (!root) {
fprintf(stderr, _("%s: error: %s\n"), fname, buf);
return EINVAL;
}
arr = toml_array_in(root, "protocols");
if (!arr) {
fprintf(stderr, _("%s: missing [protocols] section\n"), fname);
goto out;
}
struct keymap *map = NULL;
for (;;) {
struct keymap *cur_map;
proot = toml_table_at(arr, i);
if (!proot)
break;
ret = parse_toml_protocol(fname, proot, &cur_map, verbose);
if (ret)
goto out;
if (!map) {
map = cur_map;
} else {
cur_map->next = map->next;
map->next = cur_map;
}
i++;
}
if (i == 0) {
fprintf(stderr, _("%s: no protocols found\n"), fname);
goto out;
}
toml_free(root);
*keymap = map;
return 0;
out:
toml_free(root);
return EINVAL;
}
error_t parse_keymap(char *fname, struct keymap **keymap, bool verbose)
{
size_t len = strlen(fname);
if (len >= 5 && strcasecmp(fname + len - 5, ".toml") == 0)
return parse_toml_keymap(fname, keymap, verbose);
return parse_plain_keymap(fname, keymap, verbose);
}
int keymap_param(struct keymap *map, const char *name, int fallback)
{
struct protocol_param *param;
for (param = map->param; param; param = param->next) {
if (!strcmp(param->name, name))
return param->value;
}
return fallback;
}