From 54df954ed6c4f45b3a2b27bcb19dc01445589b3e Mon Sep 17 00:00:00 2001 From: Andrew Kesterson Date: Fri, 31 Jul 2026 11:19:56 -0400 Subject: [PATCH] File the keystroke ring's missing modifier state as item 10 akbasic built a line editor on akgl_controller_poll_key() and found the ring carries a keycode and nothing else. No shifted character is reachable, so a BASIC string literal cannot be typed at all. Co-Authored-By: Claude Opus 5 (1M context) --- TODO.md | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/TODO.md b/TODO.md index d542467..3f9fb1d 100644 --- a/TODO.md +++ b/TODO.md @@ -883,6 +883,11 @@ Doing that turned up **items 5 through 9**, filed below. Four are things `akbasi around to build at all; each workaround is commented at its site over there with the words "filed upstream", so they can be found and deleted when these land. +Building the *standalone* SDL frontend on top of all that — a real window, a real event pump +and a line editor — turned up **item 10**, and re-confirmed items 6 and 7: both workarounds +had to be written a second time, in `src/frontend_akgl.c`, because a host that owns a window is +exactly the caller they inconvenience. + These are filed here rather than worked around in `akbasic` because growing `libakgl` to serve a consumer is the wanted outcome. Each entry says what the BASIC verb needs, what the `akgl_*` entry point should look like, and what would cover it. @@ -1099,6 +1104,48 @@ a consumer is the wanted outcome. Each entry says what the BASIC verb needs, wha hand and assert the frame at which the pitch has moved, exactly as `tests/audio.c` already does for the envelope stages. +10. **The keystroke ring carries a keycode and nothing else, so Shift is invisible.** + `akgl_controller_handle_event()` (`src/controller.c:104-105`) pushes `event->key.key` into + the ring and drops the rest of the event, and `akgl_controller_poll_key(int *keycode, bool + *available)` hands back only that. Item 4 asked for a non-blocking keystroke read and got + one; what it did not ask for, and what a *line editor* turns out to need, is which + character the keystroke actually produced. + + `akbasic` has now built one — an `INPUT` and a REPL prompt drawn in the window, in + `src/sink_akgl.c` — and the consequence is concrete: no shifted character is reachable, so + `"`, `!`, `(`, `)`, `:` and `;` cannot be typed at all, and lower case cannot be typed + either. A BASIC line editor that cannot type a double quote cannot enter a string literal. + The interpreter folds letters to upper case, which is what a C128 does anyway and is the + right resolution for letters; it is not a resolution for punctuation, and there is nothing + the caller can do about it because the modifier state was discarded two layers down. + + Note this is not solvable by the caller polling `SDL_GetModState()` at read time: by then + the key is long released, and the whole point of a ring is that reads are decoupled from + events. + + **Fix:** widen the ring entry to carry the modifier state and the composed text SDL already + computes. Either + + ```c + typedef struct akgl_Keystroke { + SDL_Keycode key; + SDL_Keymod mod; + char text[8]; /* UTF-8, from SDL_EVENT_TEXT_INPUT; "" for a non-printing key */ + } akgl_Keystroke; + + akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_poll_keystroke(akgl_Keystroke *dest, bool *available); + ``` + + alongside the existing `akgl_controller_poll_key()`, which stays as it is so nothing + breaks — a game asking "was the up arrow pressed" wants exactly what it already gets. + Filling `text` means handling `SDL_EVENT_TEXT_INPUT` as well as `SDL_EVENT_KEY_DOWN`, which + is the only correct way to get a character out of SDL anyway: it is what makes a keyboard + layout, a compose key and a dead key work, none of which a keycode can express. + + Tests would push a shifted key-down plus its text-input event through + `akgl_controller_handle_event()` and assert both the keycode and the composed character + come back, mirroring what `tests/controller.c` already does for the plain ring. + ## Carried over 1. **Make character-to-sprite state bindings release their references symmetrically.**