initial commit

This commit is contained in:
Richard Acayan 2024-04-15 22:11:00 -04:00
commit 1c606d0274
No known key found for this signature in database
GPG key ID: 0346F4894879DB73
36 changed files with 9908 additions and 0 deletions

460
parser.c Normal file
View file

@ -0,0 +1,460 @@
// 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 <errno.h>
#include <expat.h>
#include <fcntl.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <xkbcommon/xkbcommon.h>
#include <xkbcommon/xkbcommon-keysyms.h>
#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;
}