425 lines
13 KiB
C
425 lines
13 KiB
C
// Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
|
// See LICENSE.md for license information.
|
|
// SPDX-License-Identifier: MIT
|
|
//
|
|
// pspKeyboardPath.c - the PSP keyboard's d-pad moves and presses for some
|
|
// typing, for a gate to send the PSP.
|
|
//
|
|
// pspKeyboardPath SOURCE TOKEN...
|
|
//
|
|
// The keyboard (src/hosted/psp/pspKeyboard.c) is read from its own source:
|
|
// its rows of KEY_* entries, each key's width in half keys, what it types,
|
|
// and its legend, and where the cursor starts. Its cursor is followed as
|
|
// the keyboard moves it -- along a row wrapping round, and up or down a row
|
|
// onto the key nearest the middle of the one it was on (ties: down-right
|
|
// going down, up-left going up). Each step is printed on a line of its own:
|
|
//
|
|
// move left|right|up|down one d-pad press
|
|
// press LEGEND cross on the key with that legend
|
|
//
|
|
// A TOKEN is "key:LEGEND" (a key by its legend: ESC, F5, SHIFT, ENTER...)
|
|
// or "type:TEXT" (each character of TEXT, Shift pressed first for a
|
|
// shifted one, which the keyboard lets go after the next key).
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "toolFile.h"
|
|
|
|
#define TOOL_NAME "pspKeyboardPath"
|
|
#define ROWS_MAX 8u
|
|
#define KEYS_MAX 24u
|
|
#define LEGEND_MAX 32u
|
|
#define ARGS_MAX 6u
|
|
#define ARG_MAX 64u
|
|
#define NO_CHAR (-1)
|
|
#define LETTER_HALVES 2u
|
|
#define DECIMAL 10
|
|
#define SHIFT_LEGEND "SHIFT"
|
|
#define ROW_START "#define CURSOR_ROW_START"
|
|
#define COL_START "#define CURSOR_COL_START"
|
|
#define ROW_OPEN "static const BoardKeyT gRow"
|
|
#define ROW_OPEN_TAIL "[] = {"
|
|
#define ROW_CLOSE "\n};"
|
|
|
|
// A key: its width in half keys, its legend, and what it types plain and
|
|
// shifted (NO_CHAR for nothing).
|
|
typedef struct KeyT {
|
|
uint32_t halves;
|
|
char legend[LEGEND_MAX];
|
|
int plain;
|
|
int shifted;
|
|
} KeyT;
|
|
|
|
typedef struct RowT {
|
|
KeyT keys[KEYS_MAX];
|
|
uint32_t count;
|
|
} RowT;
|
|
|
|
typedef struct BoardT {
|
|
RowT rows[ROWS_MAX];
|
|
uint32_t count;
|
|
uint32_t row;
|
|
uint32_t col;
|
|
} BoardT;
|
|
|
|
// The cast charOf looks through.
|
|
static const char kCast[] = "(uint8_t)(";
|
|
|
|
static uint32_t argsSplit(const char *call, size_t len, char out[ARGS_MAX][ARG_MAX]);
|
|
static uint32_t center(const RowT *row, uint32_t col);
|
|
static int charOf(const char *text);
|
|
static uint32_t defineOf(const char *text, const char *name);
|
|
static void find(const BoardT *board, const char *want, bool byLegend, uint32_t *row, uint32_t *col, bool *shifted);
|
|
static void keyAdd(RowT *row, const char *kind, size_t kindLen, char a[ARGS_MAX][ARG_MAX]);
|
|
static void legendCopy(char *legend, const char *text);
|
|
static int mod(int a, int n);
|
|
static void parse(BoardT *board, const char *text);
|
|
static void path(BoardT *board, uint32_t toRow, uint32_t toCol);
|
|
static void press(BoardT *board, const char *want, bool byLegend);
|
|
|
|
|
|
// A KEY_*(...) entry's arguments, split at its top-level commas and
|
|
// trimmed.
|
|
static uint32_t argsSplit(const char *call, size_t len, char out[ARGS_MAX][ARG_MAX]) {
|
|
uint32_t count;
|
|
size_t n;
|
|
size_t i;
|
|
int depth;
|
|
char quote;
|
|
char c;
|
|
|
|
count = 0;
|
|
n = 0;
|
|
depth = 0;
|
|
quote = 0;
|
|
for (i = 0; i <= len && count < ARGS_MAX; i++) {
|
|
c = i < len ? call[i] : ',';
|
|
if (quote != 0) {
|
|
if (c == '\\' && i + 1 < len && n + 2 < ARG_MAX) {
|
|
out[count][n++] = c;
|
|
c = call[++i];
|
|
} else if (c == quote) {
|
|
quote = 0;
|
|
}
|
|
} else if (c == '\'' || c == '"') {
|
|
quote = c;
|
|
} else if (c == '(') {
|
|
depth++;
|
|
} else if (c == ')') {
|
|
depth--;
|
|
} else if (c == ',' && depth == 0) {
|
|
while (n > 0 && (out[count][n - 1u] == ' ' || out[count][n - 1u] == '\t')) {
|
|
n--;
|
|
}
|
|
out[count][n] = 0;
|
|
count++;
|
|
n = 0;
|
|
continue;
|
|
}
|
|
if ((c == ' ' || c == '\t') && n == 0) {
|
|
continue;
|
|
}
|
|
if (n + 1 < ARG_MAX) {
|
|
out[count][n++] = c;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
|
|
// Twice the middle of a key, in half keys.
|
|
static uint32_t center(const RowT *row, uint32_t col) {
|
|
uint32_t start;
|
|
uint32_t i;
|
|
|
|
start = 0;
|
|
for (i = 0; i < col; i++) {
|
|
start += row->keys[i].halves;
|
|
}
|
|
return 2u * start + row->keys[col].halves;
|
|
}
|
|
|
|
|
|
// A C character literal's character ('\'' and '\\' unescaped), or NO_CHAR
|
|
// when the text is not one; a (uint8_t)(...) cast round it is looked
|
|
// through.
|
|
static int charOf(const char *text) {
|
|
char inner[ARG_MAX];
|
|
size_t len;
|
|
|
|
len = strlen(text);
|
|
if (strncmp(text, kCast, sizeof(kCast) - 1u) == 0 && len > sizeof(kCast) && text[len - 1u] == ')') {
|
|
len -= sizeof(kCast);
|
|
memcpy(inner, text + sizeof(kCast) - 1u, len);
|
|
inner[len] = 0;
|
|
return charOf(inner);
|
|
}
|
|
if (len < 3u || text[0] != '\'' || text[len - 1u] != '\'') {
|
|
return NO_CHAR;
|
|
}
|
|
return (uint8_t)(text[1] == '\\' ? text[2] : text[1]);
|
|
}
|
|
|
|
|
|
// The number a "#define NAME 12u" gives.
|
|
static uint32_t defineOf(const char *text, const char *name) {
|
|
const char *at;
|
|
|
|
at = strstr(text, name);
|
|
if (at == NULL) {
|
|
toolFail("no %s in the keyboard's source", name);
|
|
}
|
|
return (uint32_t)strtoul(at + strlen(name), NULL, DECIMAL);
|
|
}
|
|
|
|
|
|
// The key with the legend want, or else the one that types want (shifted
|
|
// when only its shifted character is it).
|
|
static void find(const BoardT *board, const char *want, bool byLegend, uint32_t *row, uint32_t *col, bool *shifted) {
|
|
uint32_t r;
|
|
uint32_t c;
|
|
int pass;
|
|
int got;
|
|
|
|
for (pass = 0; pass < 2; pass++) {
|
|
for (r = 0; r < board->count; r++) {
|
|
for (c = 0; c < board->rows[r].count; c++) {
|
|
got = pass == 0 ? board->rows[r].keys[c].plain : board->rows[r].keys[c].shifted;
|
|
if ((byLegend && pass == 0 && strcmp(board->rows[r].keys[c].legend, want) == 0) || (!byLegend && got != NO_CHAR && got == (uint8_t)want[0])) {
|
|
*row = r;
|
|
*col = c;
|
|
*shifted = pass == 1;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (byLegend) {
|
|
toolFail("no key shows or types '%s'", want);
|
|
}
|
|
toolFail("no key shows or types '%c'", want[0]);
|
|
}
|
|
|
|
|
|
// One KEY_* entry as a key.
|
|
static void keyAdd(RowT *row, const char *kind, size_t kindLen, char a[ARGS_MAX][ARG_MAX]) {
|
|
KeyT *key;
|
|
|
|
if (row->count == KEYS_MAX) {
|
|
toolFail("a keyboard row has more than %u keys", KEYS_MAX);
|
|
}
|
|
key = &row->keys[row->count];
|
|
if (strncmp(kind, "KEY_LETTER", kindLen) == 0) {
|
|
key->halves = LETTER_HALVES;
|
|
legendCopy(key->legend, a[0]);
|
|
key->plain = charOf(a[1]);
|
|
key->shifted = (uint8_t)a[0][0];
|
|
} else if (strncmp(kind, "KEY_DIGIT", kindLen) == 0) {
|
|
key->halves = LETTER_HALVES;
|
|
legendCopy(key->legend, a[0]);
|
|
key->plain = charOf(a[1]);
|
|
key->shifted = charOf(a[2]);
|
|
} else if (strncmp(kind, "KEY_PUNCT", kindLen) == 0) {
|
|
key->halves = (uint32_t)strtoul(a[0], NULL, DECIMAL);
|
|
key->legend[0] = (char)charOf(a[1]);
|
|
key->legend[1] = 0;
|
|
key->plain = charOf(a[1]);
|
|
key->shifted = charOf(a[2]);
|
|
} else if (strncmp(kind, "KEY_NAMED", kindLen) == 0) {
|
|
key->halves = (uint32_t)strtoul(a[0], NULL, DECIMAL);
|
|
legendCopy(key->legend, a[3]);
|
|
key->plain = charOf(a[2]);
|
|
key->shifted = key->plain;
|
|
} else if (strncmp(kind, "KEY_LATCH", kindLen) == 0) {
|
|
key->halves = (uint32_t)strtoul(a[0], NULL, DECIMAL);
|
|
legendCopy(key->legend, a[3]);
|
|
key->plain = NO_CHAR;
|
|
key->shifted = NO_CHAR;
|
|
} else {
|
|
return;
|
|
}
|
|
row->count++;
|
|
}
|
|
|
|
|
|
// A legend from its argument, its quotes taken off.
|
|
static void legendCopy(char *legend, const char *text) {
|
|
size_t len;
|
|
|
|
len = strlen(text);
|
|
if (len >= 2u && text[0] == '"' && text[len - 1u] == '"') {
|
|
text++;
|
|
len -= 2u;
|
|
}
|
|
if (len >= LEGEND_MAX) {
|
|
len = LEGEND_MAX - 1u;
|
|
}
|
|
memcpy(legend, text, len);
|
|
legend[len] = 0;
|
|
}
|
|
|
|
|
|
// a modulo n, never negative.
|
|
static int mod(int a, int n) {
|
|
return ((a % n) + n) % n;
|
|
}
|
|
|
|
|
|
// The keyboard's rows, and the cursor where it starts, from its source.
|
|
static void parse(BoardT *board, const char *text) {
|
|
char a[ARGS_MAX][ARG_MAX];
|
|
const char *at;
|
|
const char *body;
|
|
const char *bodyEnd;
|
|
const char *line;
|
|
const char *lineEnd;
|
|
const char *kind;
|
|
const char *open;
|
|
const char *close;
|
|
size_t kindLen;
|
|
|
|
memset(board, 0, sizeof(*board));
|
|
board->row = defineOf(text, ROW_START);
|
|
board->col = defineOf(text, COL_START);
|
|
for (at = strstr(text, ROW_OPEN); at != NULL; at = strstr(bodyEnd, ROW_OPEN)) {
|
|
body = strstr(at, ROW_OPEN_TAIL);
|
|
bodyEnd = body == NULL ? NULL : strstr(body, ROW_CLOSE);
|
|
if (bodyEnd == NULL || board->count == ROWS_MAX) {
|
|
toolFail("cannot read the keyboard's rows");
|
|
}
|
|
for (line = body + strlen(ROW_OPEN_TAIL); line < bodyEnd; line = lineEnd + 1) {
|
|
lineEnd = memchr(line, '\n', (size_t)(bodyEnd - line));
|
|
lineEnd = lineEnd == NULL ? bodyEnd : lineEnd;
|
|
kind = strstr(line, "KEY_");
|
|
if (kind == NULL || kind >= lineEnd) {
|
|
continue;
|
|
}
|
|
for (kindLen = 4; kind[kindLen] >= 'A' && kind[kindLen] <= 'Z'; kindLen++) {
|
|
}
|
|
open = kind + kindLen;
|
|
close = lineEnd;
|
|
while (close > open && (close[-1] == ' ' || close[-1] == '\t' || close[-1] == '\r')) {
|
|
close--;
|
|
}
|
|
close -= close > open && close[-1] == ',' ? 1 : 0;
|
|
if (*open != '(' || close <= open + 1 || close[-1] != ')') {
|
|
continue;
|
|
}
|
|
memset(a, 0, sizeof(a));
|
|
argsSplit(open + 1, (size_t)(close - 1 - (open + 1)), a);
|
|
keyAdd(&board->rows[board->count], kind, kindLen, a);
|
|
}
|
|
board->count++;
|
|
}
|
|
}
|
|
|
|
|
|
// The moves from the cursor to a key, as the keyboard makes them: rows
|
|
// first, then along the row; the cursor (board->row, board->col) is left
|
|
// on it.
|
|
static void path(BoardT *board, uint32_t toRow, uint32_t toCol) {
|
|
uint32_t row;
|
|
uint32_t col;
|
|
uint32_t middle;
|
|
uint32_t best;
|
|
uint32_t distance;
|
|
uint32_t c;
|
|
int n;
|
|
int right;
|
|
int left;
|
|
bool down;
|
|
|
|
row = board->row;
|
|
col = board->col;
|
|
n = (int)board->count;
|
|
while (row != toRow) {
|
|
middle = center(&board->rows[row], col);
|
|
down = mod((int)toRow - (int)row, n) <= mod((int)row - (int)toRow, n);
|
|
row = (uint32_t)mod((int)row + (down ? 1 : -1), n);
|
|
best = UINT32_MAX;
|
|
for (c = 0; c < board->rows[row].count; c++) {
|
|
distance = center(&board->rows[row], c);
|
|
distance = distance > middle ? distance - middle : middle - distance;
|
|
if (distance < best || (down && distance == best)) {
|
|
best = distance;
|
|
col = c;
|
|
}
|
|
}
|
|
puts(down ? "move down" : "move up");
|
|
}
|
|
n = (int)board->rows[row].count;
|
|
right = mod((int)toCol - (int)col, n);
|
|
left = mod((int)col - (int)toCol, n);
|
|
for (; right <= left && right > 0; right--) {
|
|
puts("move right");
|
|
}
|
|
for (; right > left && left > 0; left--) {
|
|
puts("move left");
|
|
}
|
|
board->row = row;
|
|
board->col = toCol;
|
|
}
|
|
|
|
|
|
// The moves to the key with legend want (or that types it, Shift first for
|
|
// a shifted character) and its press.
|
|
static void press(BoardT *board, const char *want, bool byLegend) {
|
|
uint32_t row;
|
|
uint32_t col;
|
|
uint32_t shiftRow;
|
|
uint32_t shiftCol;
|
|
bool shifted;
|
|
bool unused;
|
|
|
|
find(board, want, byLegend, &row, &col, &shifted);
|
|
if (shifted) {
|
|
find(board, SHIFT_LEGEND, true, &shiftRow, &shiftCol, &unused);
|
|
path(board, shiftRow, shiftCol);
|
|
printf("press %s\n", SHIFT_LEGEND);
|
|
}
|
|
path(board, row, col);
|
|
printf("press %s\n", board->rows[row].keys[col].legend);
|
|
}
|
|
|
|
|
|
int main(int argc, char **argv) {
|
|
BoardT board;
|
|
uint8_t *file;
|
|
size_t len;
|
|
char *text;
|
|
const char *value;
|
|
char one[2];
|
|
int i;
|
|
bool byLegend;
|
|
|
|
toolFileName(TOOL_NAME);
|
|
if (argc < 3) {
|
|
fprintf(stderr, "usage: %s SOURCE TOKEN... (TOKEN: key:LEGEND or type:TEXT)\n", TOOL_NAME);
|
|
return 1;
|
|
}
|
|
if (!toolReadFile(argv[1], &file, &len)) {
|
|
toolFail("cannot read %s", argv[1]);
|
|
}
|
|
text = (char *)realloc(file, len + 1u);
|
|
if (text == NULL) {
|
|
toolFail("out of memory");
|
|
}
|
|
text[len] = 0;
|
|
parse(&board, text);
|
|
for (i = 2; i < argc; i++) {
|
|
value = strchr(argv[i], ':');
|
|
value = value == NULL ? argv[i] + strlen(argv[i]) : value + 1;
|
|
byLegend = strncmp(argv[i], "key:", strlen("key:")) == 0;
|
|
if (byLegend) {
|
|
press(&board, value, true);
|
|
continue;
|
|
}
|
|
for (one[1] = 0; *value != 0; value++) {
|
|
one[0] = *value;
|
|
press(&board, one, false);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|