// SPDX-License-Identifier: GPL-3.0-only /* * Parser for keyboard layout files in the Unfettered Keyboard format. * * Copyright (c) 2024, Richard Acayan. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include "keymap.h" #include "layout.h" #include "ufkbd.h" #define ARRAY_SIZE(a) (sizeof(a) / sizeof(*(a))) #define BOTTOM_ROW_FILE "bottom_row.xml" struct ufkbd_parser_ctx { struct ufkbd_keymap *keymap; struct ufkbd_layout *layout; struct ufkbd_layout_row *row; int err; }; struct keyid_long { const char *name; int keyid; }; /* * This lookup table is sorted by codepoint so binary searches can be performed * on it. Hash tables are too difficult to define statically. */ static const struct keyid_long keyids_long[] = { { .name = "\\#", .keyid = XKB_KEY_numbersign, }, { .name = "\\?", .keyid = XKB_KEY_question, }, { .name = "\\@", .keyid = XKB_KEY_at, }, { .name = "\\\\", .keyid = XKB_KEY_backslash, }, { .name = "backspace", .keyid = XKB_KEY_BackSpace, }, { .name = "ctrl", .keyid = XKB_KEY_Control_L, }, { .name = "delete", .keyid = XKB_KEY_Delete, }, { .name = "down", .keyid = XKB_KEY_Down, }, { .name = "enter", .keyid = XKB_KEY_Return, }, { .name = "esc", .keyid = XKB_KEY_Escape, }, { .name = "fn", .keyid = XKB_KEY_XF86Fn, }, { .name = "left", .keyid = XKB_KEY_Left, }, { .name = "loc alt", .keyid = XKB_KEY_Alt_L, }, { .name = "right", .keyid = XKB_KEY_Right, }, { .name = "shift", .keyid = XKB_KEY_Shift_L, }, { .name = "space", .keyid = XKB_KEY_space, }, { .name = "tab", .keyid = XKB_KEY_Tab, }, { .name = "up", .keyid = XKB_KEY_Up, }, }; static void fill_lut_part(struct ufkbd_key *key, enum ufkbd_part part, enum ufkbd_part prev, enum ufkbd_part next, enum ufkbd_part prev_far, enum ufkbd_part next_far, size_t idx_p, size_t idx_n) { if (key->keyids[part] == -1) return; key->lut[idx_p] = part; key->lut[idx_n] = part; if (key->keyids[prev] == -1) key->lut[(idx_p - 1) % 16] = part; if (key->keyids[next] == -1) key->lut[(idx_n + 1) % 16] = part; if (key->keyids[prev_far] == -1) { key->lut[(idx_p - 2) % 16] = part; key->lut[(idx_p - 3) % 16] = part; } if (key->keyids[next_far] == -1) { key->lut[(idx_n + 2) % 16] = part; key->lut[(idx_n + 3) % 16] = part; } } static void construct_key_lut(struct ufkbd_key *key) { size_t i; for (i = 0; i < UFKBD_PART_MAX; i++) key->lut[i] = 0; fill_lut_part(key, UFKBD_PART_RIGHT, UFKBD_PART_TR, UFKBD_PART_BR, UFKBD_PART_TOP, UFKBD_PART_BOTTOM, 15, 0); fill_lut_part(key, UFKBD_PART_BR, UFKBD_PART_RIGHT, UFKBD_PART_BOTTOM, UFKBD_PART_TR, UFKBD_PART_BL, 1, 2); fill_lut_part(key, UFKBD_PART_BOTTOM, UFKBD_PART_BR, UFKBD_PART_BL, UFKBD_PART_RIGHT, UFKBD_PART_LEFT, 3, 4); fill_lut_part(key, UFKBD_PART_BL, UFKBD_PART_BOTTOM, UFKBD_PART_LEFT, UFKBD_PART_BR, UFKBD_PART_TL, 5, 6); fill_lut_part(key, UFKBD_PART_LEFT, UFKBD_PART_BL, UFKBD_PART_TL, UFKBD_PART_BOTTOM, UFKBD_PART_TOP, 7, 8); fill_lut_part(key, UFKBD_PART_TL, UFKBD_PART_LEFT, UFKBD_PART_TOP, UFKBD_PART_BL, UFKBD_PART_TR, 9, 10); fill_lut_part(key, UFKBD_PART_TOP, UFKBD_PART_TL, UFKBD_PART_TR, UFKBD_PART_LEFT, UFKBD_PART_RIGHT, 11, 12); fill_lut_part(key, UFKBD_PART_TR, UFKBD_PART_TOP, UFKBD_PART_RIGHT, UFKBD_PART_TL, UFKBD_PART_BR, 13, 14); } static int compare_keyid_long(const void *va, const void *vb) { const struct keyid_long *a = va; const struct keyid_long *b = vb; return strcmp(a->name, b->name); } static void parse_keyid_attr(struct ufkbd_keymap *keymap, int *keyid, xkb_keysym_t *keysym, const XML_Char *val) { struct keyid_long *id_long; struct keyid_long pattern; pattern.name = val; id_long = bsearch(&pattern, keyids_long, ARRAY_SIZE(keyids_long), sizeof(*keyids_long), compare_keyid_long); if (id_long != NULL) { *keysym = id_long->keyid; } else if (val[1] == '\0') { *keysym = val[0]; } else { *keyid = -1; return; } *keyid = ufkbd_keymap_add_key(keymap, *keysym); } static void parse_key_attrs(struct ufkbd_keymap *keymap, struct ufkbd_layout_column *col, double start, const XML_Char **attrs) { struct ufkbd_key *key = col->key; double width = 1; size_t i; for (i = 0; attrs[i] != NULL && attrs[i + 1] != NULL; i += 2) { if (!strcmp(attrs[i], "key0")) parse_keyid_attr(keymap, &key->keyids[UFKBD_PART_CENTER], &key->keysyms[UFKBD_PART_CENTER], attrs[i + 1]); else if (!strcmp(attrs[i], "key1")) parse_keyid_attr(keymap, &key->keyids[UFKBD_PART_TL], &key->keysyms[UFKBD_PART_TL], attrs[i + 1]); else if (!strcmp(attrs[i], "key2")) parse_keyid_attr(keymap, &key->keyids[UFKBD_PART_TR], &key->keysyms[UFKBD_PART_TR], attrs[i + 1]); else if (!strcmp(attrs[i], "key3")) parse_keyid_attr(keymap, &key->keyids[UFKBD_PART_BL], &key->keysyms[UFKBD_PART_BL], attrs[i + 1]); else if (!strcmp(attrs[i], "key4")) parse_keyid_attr(keymap, &key->keyids[UFKBD_PART_BR], &key->keysyms[UFKBD_PART_BR], attrs[i + 1]); else if (!strcmp(attrs[i], "key5")) parse_keyid_attr(keymap, &key->keyids[UFKBD_PART_LEFT], &key->keysyms[UFKBD_PART_LEFT], attrs[i + 1]); else if (!strcmp(attrs[i], "key6")) parse_keyid_attr(keymap, &key->keyids[UFKBD_PART_RIGHT], &key->keysyms[UFKBD_PART_RIGHT], attrs[i + 1]); else if (!strcmp(attrs[i], "key7")) parse_keyid_attr(keymap, &key->keyids[UFKBD_PART_TOP], &key->keysyms[UFKBD_PART_TOP], attrs[i + 1]); else if (!strcmp(attrs[i], "key8")) parse_keyid_attr(keymap, &key->keyids[UFKBD_PART_BOTTOM], &key->keysyms[UFKBD_PART_BOTTOM], attrs[i + 1]); else if (!strcmp(attrs[i], "shift")) start += strtod(attrs[i + 1], NULL); else if (!strcmp(attrs[i], "width")) width = strtod(attrs[i + 1], NULL); else fprintf(stderr, "parser: skipping unknown key attribute %s\n", attrs[i]); } col->x1_unscaled = start; col->x2_unscaled = start + width; } static void parse_row_attrs(struct ufkbd_layout_row *row, double start, const XML_Char **attrs) { double height = 1; size_t i; for (i = 0; attrs[i] != NULL && attrs[i + 1] != NULL; i += 2) { if (!strcmp(attrs[i], "height")) height = strtod(attrs[i + 1], NULL); } row->y1_unscaled = start; row->y2_unscaled = start + height; } static int start_key(struct ufkbd_parser_ctx *ctx, const XML_Char **attrs) { struct ufkbd_layout_column *col, *prev; struct ufkbd_layout_column *cols; struct ufkbd_layout_row *row; struct ufkbd_key *key; double start = 0; size_t i; if (ctx->row == NULL) return -EINVAL; row = ctx->row; key = malloc(sizeof(*key)); if (key == NULL) return -errno; cols = realloc(row->cols, (row->n_cols + 1) * sizeof(*cols)); if (cols == NULL) return -errno; for (i = 0; i < UFKBD_PART_MAX; i++) { key->labels_mask[i] = NULL; key->labels_color[i] = NULL; key->presses[i] = 0; key->keyids[i] = -1; } col = &cols[row->n_cols]; prev = &cols[row->n_cols - 1]; col->key = key; if (row->n_cols > 0) start = prev->x2_unscaled; parse_key_attrs(ctx->keymap, col, start, attrs); construct_key_lut(key); row->cols = cols; row->n_cols++; return 0; } static int start_row(struct ufkbd_parser_ctx *ctx, const XML_Char **attrs) { struct ufkbd_layout *layout = ctx->layout; struct ufkbd_layout_row *rows; struct ufkbd_layout_row *row, *prev; double start = 0; rows = realloc(layout->rows, (layout->n_rows + 1) * sizeof(*rows)); if (rows == NULL) return -errno; row = &rows[layout->n_rows]; prev = &rows[layout->n_rows - 1]; if (layout->n_rows > 0) start = prev->y2_unscaled; parse_row_attrs(row, start, attrs); row->cols = NULL; row->n_cols = 0; layout->rows = rows; layout->n_rows++; ctx->row = row; return 0; } static void start_elem(void *data, const XML_Char *name, const XML_Char **attrs) { struct ufkbd_parser_ctx *ctx = data; int ret = 0; if (!strcmp(name, "key")) ret = start_key(ctx, attrs); else if (!strcmp(name, "row")) ret = start_row(ctx, attrs); if (ret) fprintf(stderr, "parser: skipping %s: %s\n", name, strerror(-ret)); } static void end_elem(void *data, const XML_Char *name) { struct ufkbd_parser_ctx *ctx = data; if (!strcmp(name, "row")) { if (ctx->row == NULL) return; if (ctx->layout->max_cols < ctx->row->n_cols) ctx->layout->max_cols = ctx->row->n_cols; ctx->row = NULL; } } static int parse_until_eof(XML_Parser parser, int fd) { char *buf; ssize_t len; size_t size = 4096; enum XML_Status status; enum XML_Error err; int ret = 0; buf = malloc(size); if (buf == NULL) return -errno; do { len = read(fd, buf, size); if (len == -1) { ret = -errno; goto err; } status = XML_Parse(parser, (const char *) buf, len, len == 0); if (status != XML_STATUS_OK) { err = XML_GetErrorCode(parser); fprintf(stderr, "parser: xml parsing failed: " #ifdef XML_UNICODE_WCHAR_T "%ls\n", #else "%s\n", #endif XML_ErrorString(err)); ret = -EINVAL; goto err; } } while (len != 0); err: free(buf); return ret; } static int parse_file(int dir, const char *file, XML_Parser parser) { int fd, ret; fd = openat(dir, file, O_RDONLY); if (fd == -1) return -errno; ret = parse_until_eof(parser, fd); if (ret) goto err_close; err_close: close(fd); return ret; } int ufkbd_parser_parse(int dir, const char *fname, struct ufkbd_keymap *keymap, struct ufkbd_layout *layout) { struct ufkbd_parser_ctx *ctx; XML_Parser parser; int ret; if (layout == NULL) return -EINVAL; ctx = malloc(sizeof(*ctx)); if (layout == NULL) return -errno; ctx->keymap = keymap; ctx->layout = layout; ctx->row = NULL; ctx->err = 0; parser = XML_ParserCreate(NULL); if (parser == NULL) { ret = -ENOMEM; goto err_free_ctx; } XML_SetElementHandler(parser, start_elem, end_elem); XML_SetUserData(parser, ctx); ret = parse_file(dir, fname, parser); if (ret) goto err_free_parser; XML_ParserReset(parser, NULL); XML_SetElementHandler(parser, start_elem, end_elem); XML_SetUserData(parser, ctx); ret = parse_file(dir, BOTTOM_ROW_FILE, parser); err_free_parser: XML_ParserFree(parser); err_free_ctx: free(ctx); return ret; }