initial commit
This commit is contained in:
commit
1c606d0274
36 changed files with 9908 additions and 0 deletions
460
parser.c
Normal file
460
parser.c
Normal 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;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue