diff options
author | Sean Young <sean@mess.org> | 2019-08-24 21:06:39 +0100 |
---|---|---|
committer | Sean Young <sean@mess.org> | 2019-08-27 12:31:23 +0100 |
commit | 83bfb96dc35e103854ef9494dd996b85261bb449 (patch) | |
tree | edc12790012ae9cdf6a029904079291e2d728f8b /utils/keytable/keymap.c | |
parent | 2350a23b92ff94967e20c09687b4c5692e989cf7 (diff) |
keytable: move keymap parsing into its own file
We would like to reuse keymap parsing for transmitting IR based on keymap
in ir-ctl.
This also reduces the size of our huge keytable.c, and keeps knowledge
toml localised to the keymap parser.
Signed-off-by: Sean Young <sean@mess.org>
Diffstat (limited to 'utils/keytable/keymap.c')
-rw-r--r-- | utils/keytable/keymap.c | 459 |
1 files changed, 459 insertions, 0 deletions
diff --git a/utils/keytable/keymap.c b/utils/keytable/keymap.c new file mode 100644 index 00000000..bb193131 --- /dev/null +++ b/utils/keytable/keymap.c @@ -0,0 +1,459 @@ +/* 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 "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_keyfile(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_keyfile"); + return ENOMEM; + } + + if (verbose) + fprintf(stderr, _("Parsing %s keycode file as plain text\n"), fname); + + fin = fopen(fname, "r"); + if (!fin) { + return errno; + } + + 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_keyfile"); + fclose(fin); + return ENOMEM; + } + + se->scancode = strtoul(scancode, NULL, 0); + se->keycode = keycode; + se->next = map->scancode; + map->scancode = se; + } + fclose(fin); + + *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_toml_raw_part(const char *fname, struct toml_array_t *raw, struct keymap *map) +{ + struct toml_table_t *t; + struct toml_array_t *rawarray; + struct raw_entry *re; + const char *rkeycode; + int ind = 0, length; + char *keycode; + + while ((t = toml_table_at(raw, ind++)) != NULL) { + rkeycode = toml_raw_in(t, "keycode"); + if (!rkeycode) { + fprintf(stderr, _("%s: invalid keycode for raw entry %d\n"), + fname, ind); + return EINVAL; + } + + if (toml_rtos(rkeycode, &keycode)) { + fprintf(stderr, _("%s: bad value `%s' for keycode\n"), + fname, rkeycode); + return EINVAL; + } + + rawarray = toml_array_in(t, "raw"); + if (!rawarray) { + fprintf(stderr, _("%s: missing raw array for entry %d\n"), + fname, ind); + return EINVAL; + } + + // determine length of array + length = 0; + while (toml_raw_at(rawarray, length) != NULL) + length++; + + if (!(length % 2)) { + fprintf(stderr, _("%s: raw array must have odd length rather than %d\n"), + fname, length); + return EINVAL; + } + + re = calloc(1, sizeof(*re) + sizeof(re->raw[0]) * length); + if (!re) { + fprintf(stderr, _("Failed to allocate memory")); + return EINVAL; + } + + for (int i=0; i<length; i++) { + const char *s = toml_raw_at(rawarray, i); + int64_t v; + + if (toml_rtoi(s, &v) || v == 0) { + fprintf(stderr, _("%s: incorrect raw value `%s'"), + fname, s); + return EINVAL; + } + + if (v <= 0 || v > USHRT_MAX) { + fprintf(stderr, _("%s: raw value %ld out of range"), + fname, v); + return EINVAL; + } + + re->raw[i] = v; + } + + re->raw_length = length; + 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); + return EINVAL; + } + + if (toml_rtos(raw, &p)) { + fprintf(stderr, _("%s: bad value `%s' for protocol\n"), fname, raw); + return EINVAL; + } + + map->protocol = strdup(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); + return EINVAL; + } + + map->variant = strdup(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); + return EINVAL; + } + + map->name = strdup(p); + } + + rawarray = toml_array_in(proot, "raw"); + if (rawarray) { + if (toml_raw_in(proot, "scancodes")) { + fprintf(stderr, _("Cannot have both [raw] and [scancode] sections")); + return EINVAL; + } + if (!have_raw_protocol) { + fprintf(stderr, _("Keymap with raw entries must have raw protocol")); + return EINVAL; + } + error_t err = parse_toml_raw_part(fname, rawarray, map); + if (err != 0) + return err; + + } else if (have_raw_protocol) { + fprintf(stderr, _("Keymap with raw protocol must have raw entries")); + return EINVAL; + } + + scancodes = toml_table_in(proot, "scancodes"); + if (!scancodes) { + if (verbose) + fprintf(stderr, _("%s: no [protocols.scancodes] section\n"), fname); + return 0; + } + + for (i = 0; (key = toml_key_in(proot, i)) != NULL; i++) { + long int 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); + } + } + + 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); + return EINVAL; + } + + if (toml_rtos(raw, &keycode)) { + fprintf(stderr, _("%s: bad value `%s' for keycode\n"), + fname, keycode); + return EINVAL; + } + + se = calloc(1, sizeof(*se)); + if (!se) { + perror("parse_keyfile"); + return ENOMEM; + } + + se->scancode = strtoul(scancode, NULL, 0); + se->keycode = keycode; + se->next = map->scancode; + map->scancode = se; + } + + return 0; +} + +static error_t parse_toml_keyfile(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) + return errno; + + root = toml_parse_file(fin, buf, sizeof(buf)); + fclose(fin); + if (!root) { + fprintf(stderr, _("%s: failed to parse toml: %s\n"), fname, buf); + return EINVAL; + } + + arr = toml_array_in(root, "protocols"); + if (!arr) { + fprintf(stderr, _("%s: missing [protocols] section\n"), fname); + return EINVAL; + } + + 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_keyfile(char *fname, struct keymap **keymap, bool verbose) +{ + size_t len = strlen(fname); + + if (len >= 5 && strcasecmp(fname + len - 5, ".toml") == 0) + return parse_toml_keyfile(fname, keymap, verbose); + else + return parse_plain_keyfile(fname, keymap, verbose); +} |