// 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 #include #include #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; }