29 Commits

Author SHA1 Message Date
b8d7a2e405 Transparent controls spritesheet, red focus highlight
Enable alpha blending on the controls texture so sprite icons
float on any background without white cell artifacts. Regenerate
skin template with transparent cells and magenta gutters. Change
focus highlight from blue to red.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 00:29:09 +01:00
8a638acdd8 Skin template system, separate prev sprite, reorganize device scripts
Add tools/gen_skin_template.py to generate a labeled 642x420 PNG template
for creating custom spritesheets. Move rg35xx device scripts from tools/
to device/rg35xx/. Point prev_sprite at its own cell (bottom-center) so
Prev and Next can have distinct icons.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 23:47:21 +01:00
02ab142d96 Adjust background gray to #979797
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 23:08:56 +01:00
a5e3a0c522 Softer background color, remove panel divider
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 23:03:55 +01:00
25824d4728 Fix residual audio from previous cassette on file switch
Clear the SDL audio device queue in switch_file() so leftover
samples from the previous cassette don't play when the new one starts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 11:50:46 +01:00
201a8fae97 Split-screen layout: vertical controls left, full-height artwork right
Redesign the 640x480 UI from horizontal bottom-bar controls to a split-screen
layout that maximizes album art visibility for the child user. Buttons stack
vertically in a 200px left panel; artwork fills the remaining right panel at
up to 420x460. Volume removed from focus cycle in favor of dedicated keys
(+/-, volume buttons, shoulder buttons). Navigation changed to UP/DOWN.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 11:45:32 +01:00
1ea1490e60 Fix power button screen toggle instantly turning back on
Power button events were setting any_activity before the power handler's
continue, causing the generic wake logic to immediately re-enable the screen.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 23:18:42 +01:00
f9fcb9f121 Fix goodbye.png not visible on idle auto-shutdown
The Allwinner /dev/disp driver resets brightness to 0 when the fd is
closed, so the screen monitor's SIGTERM brightness restore was undone
before the wrapper wrote goodbye.png. Restore brightness in the wrapper
itself, right before the framebuffer write.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 23:12:10 +01:00
fbd32d7fb8 Fix screen wake for d-pad, grab inputs while screen off, auto-shutdown on idle
- Broaden wake events beyond EV_KEY so d-pad (EV_ABS) wakes the screen
- Use EVIOCGRAB for exclusive input while screen is off, preventing the
  wake button press from also acting in sdlamp2
- Auto-shutdown after 10 minutes of no input and no audio playback,
  detected by monitoring the audio file's read position via /proc fdinfo

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 22:47:47 +01:00
8123b9a99f Remember last cassette on startup, always pause on cassette switch
Persist the current cassette filename to last_cassette.txt so the player
resumes the same cassette after restart. Switching cassettes now always
lands in a paused state to avoid jarring mid-playback jumps.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 21:55:48 +01:00
3fcae8ea5e Fix power button shutdown by consolidating input handling in screen monitor
Both the evtest-based power monitor and the Python screen monitor were
reading /dev/input/event0 simultaneously, causing missed events on the
device's Linux 4.9 kernel. Moved long-press shutdown into the screen
monitor (which already reads event0 directly) and removed the evtest
dependency entirely.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 21:47:48 +01:00
2142ed7629 Add screen idle timeout and power button screen toggle
New Python screen monitor uses Allwinner /dev/disp ioctls to turn off
the display after 15s of no input and toggle it with a short power
button press. Launched by the wrapper alongside sdlamp2.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 21:34:43 +01:00
06daec791e Move shutdown screen to wrapper, reuse stock firmware goodbye.png
The in-app shutdown visual didn't work because SDL cleanup wiped the
framebuffer. Instead of hacking around that, move the shutdown display
to the device wrapper where it belongs. The wrapper now decodes the
stock firmware's goodbye.png with Python3+PIL and writes raw BGRA
pixels directly to /dev/fb0 before calling poweroff.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 21:07:03 +01:00
3728e9499c Implement power button monitor, document device input devices
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 20:07:37 +01:00
0f653d4395 Handle SIGTERM/SIGINT for clean shutdown, add device wrapper script
sdlamp2 now catches SIGTERM and SIGINT via a sig_atomic_t flag checked
in the main loop, ensuring position and volume are saved before exit.
Previously, a kill signal would terminate instantly without saving.

New tools/rg35xx-wrapper.sh replaces sdlamp2 as the dmenu_ln CMD on
the RG35XX Plus. Skeleton includes placeholders for WiFi hotspot and
power button monitoring (TBD after on-device investigation).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 15:49:38 +01:00
a5b04fcd08 Document RG35XX Plus boot chain, reorganize device docs
Move device-specific content (hardware, software, libraries) from
TOOLCHAIN.md into new docs/rg35xx-plus.md. Add partition layout,
full boot chain trace (systemd → launcher.sh → loadapp.sh → dmenu_ln),
and sdlamp2 deployment instructions via dmenu_ln config toggle.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 15:18:59 +01:00
a0f95c7252 Combine Play/Stop into toggle, add Previous Cassette button
Merge the separate Stop and Play buttons into one slot that toggles
between play/stop and shows the corresponding icon. Use the freed
slot for a Previous Cassette button, mirroring Next Cassette.

Layout: [Volume] [Prev] [Rewind] [Play/Stop] [FF] [Next]

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 14:38:17 +01:00
2daf9f7955 Start paused, respect pause state on next-tape, quiet joystick log
Player was autoplaying on startup and on file switch. Now switch_file()
preserves the current paused state so the app launches paused and
"next tape" only continues playing if already playing. Joystick name
printf moved behind --debug flag.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 14:30:33 +01:00
71debd0be2 Update docs to reflect arm64 Docker build container
Replace Buildroot toolchain references with the new docker-arm64 setup
in CLAUDE.md, TOOLCHAIN.md, and the changelog.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 14:08:58 +01:00
7597f17f9e Document target device hardware, software, and library inventory
Adds a Target Device section to TOOLCHAIN.md with specs gathered over
SSH from the Anbernic RG35XX Plus. Confirms the device runs Ubuntu 22.04
with all required shared libraries (SDL2, FFmpeg) pre-installed, making
the Buildroot toolchain unnecessary. Updates Possible Approaches to
reflect that native arm64 container builds are confirmed working.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 13:29:53 +01:00
b1c72ef876 Document cross-compilation toolchain findings and aarch64 mismatch
The Docker/Buildroot toolchain produces 32-bit ARM binaries but the
RG35XX target runs a 64-bit aarch64 userland. Add docs/TOOLCHAIN.md
covering the full pipeline, config details, patches, and three possible
approaches to fix the architecture mismatch.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 13:04:12 +01:00
cbe3d67132 Replace build scripts with a Makefile
Single Makefile supports native builds (pkg-config) and cross-compilation
(CROSS_COMPILE/PREFIX env vars). Fixes -Wformat-truncation and
-Wstringop-truncation warnings at -O2 by sizing current_file to match
audio_files (256) and replacing strncpy with snprintf.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 11:54:36 +01:00
2fd764f60f Add raw joystick fallback for non-standard controllers
Open the joystick via SDL_JoystickOpen() when no GameController mapping
exists, fixing d-pad and face button input on devices like the Anbernic
retro handheld (GUID not in SDL's database). Handles JOYHATMOTION for
d-pad navigation and JOYBUTTONDOWN button 0 for activation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 11:37:19 +01:00
7e67589150 Embed controls.png into binary so it can run from any directory
The spritesheet is compiled in as a C byte array. An external
controls.png in cwd still takes precedence for skinning. Includes
tools/embed_png.py to regenerate the header if the asset changes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 10:25:22 +01:00
e3a2bca794 Add --debug flag for diagnosing controller input on handheld
Logs joystick enumeration at startup (name, GameController status, GUID)
and all SDL input events in the main loop to help diagnose why the retro
handheld's d-pad/buttons aren't recognized by the GameController API.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 16:08:13 +01:00
6209a087d7 Add lossless M4A concatenation tool
Shell script to recombine individual story m4a files back into
per-cassette files using ffmpeg concat demuxer (stream copy, no
re-encoding). Generates chapter markers from input filenames and
preserves album art.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 09:45:27 +01:00
3ba7b31148 Add volume control and d-pad/keyboard navigation
Replace mouse input with cursor-based navigation (arrow keys / d-pad +
Enter / A button) and add app-level volume control with a persistent
vertical slider, enabling use on a handheld gaming device without mouse
or system mixer access.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 09:06:00 +01:00
9db8dfdd48 Implement streaming decoder, seeking, persistence, file switching, album art
Replace fire-and-forget decode_audio() with a streaming Decoder that uses
libswresample to convert planar float to interleaved stereo, fixing the
sped-up audio bug and eliminating multi-GB memory usage for long files.

Add 10-second rewind/fast-forward, stop (pause in place), position
persistence per file via positions.txt, directory scanning with file
switching, embedded album art display, and a progress bar. Handles both
old and new FFmpeg channel layout APIs via version preprocessor check.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 20:41:40 +01:00
d03d0a1f8b Add functional specification document 2026-02-10 20:00:19 +01:00