460 lines
10 KiB
C
460 lines
10 KiB
C
// 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;
|
|
}
|