Compare commits
34 Commits
52d5bbd971
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e348c79113 | ||
|
|
b8d7a2e405 | ||
|
|
8a638acdd8 | ||
|
|
02ab142d96 | ||
|
|
a5e3a0c522 | ||
|
|
25824d4728 | ||
|
|
201a8fae97 | ||
|
|
1ea1490e60 | ||
|
|
f9fcb9f121 | ||
|
|
fbd32d7fb8 | ||
|
|
8123b9a99f | ||
|
|
3fcae8ea5e | ||
|
|
2142ed7629 | ||
|
|
06daec791e | ||
|
|
3728e9499c | ||
|
|
0f653d4395 | ||
|
|
a5b04fcd08 | ||
|
|
4f6d1de8e2 | ||
|
|
a0f95c7252 | ||
|
|
2daf9f7955 | ||
|
|
b6728a7482 | ||
|
|
71debd0be2 | ||
|
|
6bd858d05b | ||
|
|
a58dafdff5 | ||
|
|
a3a8831286 | ||
|
|
a1bafece22 | ||
|
|
7597f17f9e | ||
|
|
b1c72ef876 | ||
|
|
a09d1e0279 | ||
|
|
d68e1d0f22 | ||
|
|
cbe3d67132 | ||
|
|
2fd764f60f | ||
|
|
dbc3f11797 | ||
|
|
97162f8ec7 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,4 +5,5 @@ positions.txt
|
||||
volume.txt
|
||||
.claude/
|
||||
audio/
|
||||
docker-arm64/.build
|
||||
|
||||
|
||||
24
CLAUDE.md
24
CLAUDE.md
@@ -7,24 +7,34 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
||||
SDLamp2 is a simple SDL2-based audio player written in C, designed for a child to use. It plays long m4a/mp3 files (fairy tale cassette rips) with a cassette-player-like UI: rewind, stop, play, fast-forward, next tape. Inspired by Winamp.
|
||||
|
||||
See `docs/sdlamp2-fsd.md` for the full functional specification and changelog.
|
||||
See `docs/TOOLCHAIN.md` for the arm64 Docker build container.
|
||||
See `docs/rg35xx-plus.md` for target device details, boot chain, and deployment.
|
||||
|
||||
## Build and Run
|
||||
|
||||
**Dependencies:** SDL2, SDL2_image, FFmpeg libraries (libavformat, libavcodec, libavutil, libswresample). Installed via system package manager; resolved at build time with `sdl2-config` and `pkgconf`.
|
||||
**Dependencies:** SDL2, SDL2_image, FFmpeg libraries (libavformat, libavcodec, libavutil, libswresample). Installed via system package manager (native) or via apt inside the arm64 Docker container.
|
||||
|
||||
```sh
|
||||
./build.sh # macOS — builds to build/sdlamp2
|
||||
./build_aarch64.sh # Linux aarch64 variant
|
||||
./build/sdlamp2 [audio_directory]
|
||||
make # native build — uses pkg-config
|
||||
make clean # remove build artifacts
|
||||
./build/sdlamp2 audio # run with the repo's audio directory
|
||||
```
|
||||
|
||||
Building for the arm64 target device via the Docker container (from the `docker-arm64/` directory):
|
||||
|
||||
```sh
|
||||
make build # one-shot: build sdlamp2 inside arm64 container
|
||||
make shell # interactive shell inside the arm64 container
|
||||
make clean # remove the Docker image
|
||||
```
|
||||
|
||||
The controls spritesheet (`controls.png`) is embedded in the binary. If an external `controls.png` exists in the current working directory it takes precedence, allowing custom skins. Audio directory defaults to cwd if not specified.
|
||||
|
||||
No test suite, no linter, no Makefile/CMake.
|
||||
No test suite, no linter.
|
||||
|
||||
## Architecture
|
||||
|
||||
Single-file C program: `src/sdlamp2.c` (~650 lines). One generated header: `src/controls_png.h` (embedded PNG byte array — regenerate with `./tools/embed_png.py assets/controls.png src/controls_png.h` if the spritesheet changes).
|
||||
Single-file C program: `src/sdlamp2.c` (~650 lines). One generated header: `src/controls_png.h` (embedded PNG byte array — regenerate with `python3 tools/embed_png.py assets/controls.png src/controls_png.h` if the spritesheet changes). Skin template: `python3 tools/gen_skin_template.py [output.png]` generates a labeled grid template for creating custom spritesheets (requires Pillow). Device-specific scripts live in `device/rg35xx/`.
|
||||
|
||||
Key sections in order:
|
||||
- **Decoder struct** — holds all FFmpeg state (format/codec contexts, swr resampler, album art texture)
|
||||
@@ -45,3 +55,5 @@ Uses SDL2 (not SDL3). Uses `#if LIBAVUTIL_VERSION_INT` preprocessor checks to su
|
||||
- Minimize dependencies; discuss with owner before adding any
|
||||
- Non-fatal errors go to stderr and continue; fatal errors (SDL init failures) abort via `panic_and_abort()`
|
||||
- Update the changelog in `docs/sdlamp2-fsd.md` when making changes
|
||||
- Never run privileged Docker containers or make system-wide changes without explicit approval; explain what's needed and let the owner do it manually
|
||||
- Never install global Python packages; use a temporary venv in `/tmp` when Python dependencies are needed (e.g. `python3 -m venv /tmp/venv && source /tmp/venv/bin/activate && pip install ...`)
|
||||
|
||||
32
Makefile
Normal file
32
Makefile
Normal file
@@ -0,0 +1,32 @@
|
||||
CC = $(CROSS_COMPILE)gcc
|
||||
|
||||
CFLAGS = -Wall -Wno-unused -O2
|
||||
LDLIBS = -lSDL2 -lSDL2_image -lavformat -lavcodec -lavutil -lswresample
|
||||
|
||||
ifdef PREFIX
|
||||
# Cross-compile: headers and libraries live under PREFIX
|
||||
CFLAGS += -I$(PREFIX)/include -I$(PREFIX)/include/SDL2
|
||||
LDLIBS := -L$(PREFIX)/lib $(LDLIBS)
|
||||
else
|
||||
# Native: use pkg-config
|
||||
PKG_CONFIG ?= pkg-config
|
||||
PKGS = sdl2 SDL2_image libavformat libavcodec libavutil libswresample
|
||||
CFLAGS += $(shell $(PKG_CONFIG) --cflags $(PKGS))
|
||||
LDLIBS = $(shell $(PKG_CONFIG) --libs $(PKGS))
|
||||
endif
|
||||
|
||||
BUILD_DIR = build
|
||||
TARGET = $(BUILD_DIR)/sdlamp2
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): src/sdlamp2.c src/controls_png.h | $(BUILD_DIR)
|
||||
$(CC) $(CFLAGS) -o $@ $< $(LDLIBS)
|
||||
|
||||
$(BUILD_DIR):
|
||||
mkdir -p $@
|
||||
|
||||
clean:
|
||||
rm -f $(TARGET)
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 10 KiB |
BIN
assets/controls_template.xcf
Normal file
BIN
assets/controls_template.xcf
Normal file
Binary file not shown.
BIN
assets/skin_template.png
Normal file
BIN
assets/skin_template.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.5 KiB |
15
build.sh
15
build.sh
@@ -1,15 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
pushd build 2>&1 >/dev/null
|
||||
|
||||
gcc -Wall -Wno-unused -O0 -ggdb3 \
|
||||
-o sdlamp2 \
|
||||
`sdl2-config --cflags --libs` \
|
||||
`pkgconf --cflags --libs libavformat` \
|
||||
`pkgconf --cflags --libs libavcodec` \
|
||||
`pkgconf --cflags --libs libavutil` \
|
||||
`pkgconf --cflags --libs libswresample` \
|
||||
-lSDL2_image \
|
||||
../src/sdlamp2.c
|
||||
|
||||
popd 2>&1 >/dev/null
|
||||
@@ -1,15 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
pushd build 2>&1 >/dev/null
|
||||
|
||||
gcc -Wall -Wno-unused \
|
||||
-o sdlamp2 \
|
||||
`sdl2-config --cflags --libs` \
|
||||
-I/usr/include/aarch64-linux-gnu -L/usr/lib/aarch64-linux-gnu -lavformat \
|
||||
-I/usr/include/aarch64-linux-gnu -L/usr/lib/aarch64-linux-gnu -lavcodec \
|
||||
-I/usr/include/aarch64-linux-gnu -L/usr/lib/aarch64-linux-gnu -lavutil \
|
||||
-I/usr/include/aarch64-linux-gnu -L/usr/lib/aarch64-linux-gnu -lswresample \
|
||||
-lSDL2_image \
|
||||
../src/sdlamp2.c
|
||||
|
||||
popd 2>&1 >/dev/null
|
||||
253
device/rg35xx/rg35xx-screen-monitor.py
Executable file
253
device/rg35xx/rg35xx-screen-monitor.py
Executable file
@@ -0,0 +1,253 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Screen idle timeout, power button toggle, and long-press shutdown for RG35XX Plus.
|
||||
|
||||
Monitors /dev/input/event{0,1,2} for activity. Turns off the screen
|
||||
(via Allwinner /dev/disp SET_BRIGHTNESS ioctl) after 15s of no input.
|
||||
Any input event (keys, d-pad, joystick) wakes the screen. While the
|
||||
screen is off, inputs are grabbed (EVIOCGRAB) so SDL in sdlamp2 does
|
||||
not receive the wake event.
|
||||
|
||||
A short power button press (<2s) toggles the screen on/off. A long
|
||||
press (3s+) triggers clean shutdown. If the device is idle (no input
|
||||
and no audio playback) for 10 minutes, it auto-shuts down to save
|
||||
battery.
|
||||
|
||||
Launched by rg35xx-wrapper.sh alongside sdlamp2. Killed on cleanup.
|
||||
|
||||
Usage: rg35xx-screen-monitor.py <sdlamp2_pid>
|
||||
"""
|
||||
import fcntl
|
||||
import os
|
||||
import select
|
||||
import signal
|
||||
import struct
|
||||
import sys
|
||||
import time
|
||||
|
||||
IDLE_TIMEOUT = 15 # seconds — screen off after no input
|
||||
IDLE_SHUTDOWN_TIMEOUT = 600 # seconds — auto-shutdown after no input AND no playback
|
||||
POWER_SHORT_THRESHOLD = 2.0 # seconds — short press if released before this
|
||||
POWER_LONG_THRESHOLD = 3.0 # seconds — shutdown if held this long
|
||||
|
||||
# Allwinner /dev/disp ioctl commands
|
||||
DISP_GET_BRIGHTNESS = 0x103
|
||||
DISP_SET_BRIGHTNESS = 0x102
|
||||
|
||||
# Input event constants
|
||||
EV_SYN = 0
|
||||
EV_KEY = 1
|
||||
KEY_POWER = 116
|
||||
|
||||
# EVIOCGRAB — exclusive access to input device (_IOW('E', 0x90, int))
|
||||
EVIOCGRAB = 0x40044590
|
||||
|
||||
AUDIO_EXTENSIONS = ('.m4a', '.mp3', '.wav', '.ogg')
|
||||
|
||||
# struct input_event on aarch64: struct timeval (2x long=8 bytes each) + __u16 type + __u16 code + __s32 value
|
||||
INPUT_EVENT_FORMAT = "@llHHi"
|
||||
INPUT_EVENT_SIZE = struct.calcsize(INPUT_EVENT_FORMAT)
|
||||
|
||||
EVENT_DEVICES = ["/dev/input/event0", "/dev/input/event1", "/dev/input/event2"]
|
||||
|
||||
|
||||
def disp_ioctl(fd, cmd, screen=0, value=0):
|
||||
# Allwinner disp ioctls take an array of 4 unsigned longs as arg
|
||||
args = struct.pack("@4L", screen, value, 0, 0)
|
||||
result = fcntl.ioctl(fd, cmd, args)
|
||||
return struct.unpack("@4L", result)[0]
|
||||
|
||||
|
||||
def get_brightness(disp_fd):
|
||||
return disp_ioctl(disp_fd, DISP_GET_BRIGHTNESS)
|
||||
|
||||
|
||||
def set_brightness(disp_fd, value):
|
||||
disp_ioctl(disp_fd, DISP_SET_BRIGHTNESS, value=value)
|
||||
|
||||
|
||||
def grab_inputs(event_fds, grab):
|
||||
"""Grab or release exclusive access to input devices."""
|
||||
for fd in event_fds:
|
||||
try:
|
||||
fcntl.ioctl(fd, EVIOCGRAB, 1 if grab else 0)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
def get_audio_file_pos(pid):
|
||||
"""Return the file offset of sdlamp2's open audio file, or None."""
|
||||
fd_dir = f"/proc/{pid}/fd"
|
||||
try:
|
||||
for fd_name in os.listdir(fd_dir):
|
||||
try:
|
||||
target = os.readlink(f"{fd_dir}/{fd_name}")
|
||||
if any(target.endswith(ext) for ext in AUDIO_EXTENSIONS):
|
||||
with open(f"/proc/{pid}/fdinfo/{fd_name}") as f:
|
||||
for line in f:
|
||||
if line.startswith("pos:"):
|
||||
return int(line.split()[1])
|
||||
except OSError:
|
||||
continue
|
||||
except OSError:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: rg35xx-screen-monitor.py <sdlamp2_pid>", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
sdlamp2_pid = int(sys.argv[1])
|
||||
|
||||
disp_fd = os.open("/dev/disp", os.O_RDWR)
|
||||
original_brightness = get_brightness(disp_fd)
|
||||
if original_brightness == 0:
|
||||
original_brightness = 50 # sensible default if already off
|
||||
|
||||
screen_on = True
|
||||
power_press_time = None
|
||||
|
||||
# Restore brightness and release grabs on exit so goodbye.png is visible during shutdown
|
||||
def restore_and_exit(signum, frame):
|
||||
grab_inputs(event_fds, False)
|
||||
if not screen_on:
|
||||
set_brightness(disp_fd, original_brightness)
|
||||
os.close(disp_fd)
|
||||
sys.exit(0)
|
||||
|
||||
signal.signal(signal.SIGTERM, restore_and_exit)
|
||||
signal.signal(signal.SIGINT, restore_and_exit)
|
||||
|
||||
# Open input devices (non-blocking)
|
||||
event_fds = []
|
||||
for path in EVENT_DEVICES:
|
||||
try:
|
||||
fd = os.open(path, os.O_RDONLY | os.O_NONBLOCK)
|
||||
event_fds.append(fd)
|
||||
except OSError as e:
|
||||
print(f"screen-monitor: cannot open {path}: {e}", file=sys.stderr)
|
||||
|
||||
if not event_fds:
|
||||
print("screen-monitor: no input devices available, exiting", file=sys.stderr)
|
||||
os.close(disp_fd)
|
||||
sys.exit(1)
|
||||
|
||||
# Idle auto-shutdown state
|
||||
last_active_time = time.monotonic()
|
||||
last_audio_pos = get_audio_file_pos(sdlamp2_pid)
|
||||
|
||||
while True:
|
||||
# Dynamic timeout: if power button is held, shorten timeout to detect 3s mark
|
||||
timeout = IDLE_TIMEOUT
|
||||
if power_press_time is not None:
|
||||
remaining = POWER_LONG_THRESHOLD - (time.monotonic() - power_press_time)
|
||||
if remaining <= 0:
|
||||
# Already past threshold — trigger shutdown now
|
||||
grab_inputs(event_fds, False)
|
||||
touch_and_shutdown(disp_fd, original_brightness, screen_on, sdlamp2_pid)
|
||||
return
|
||||
timeout = min(timeout, remaining)
|
||||
|
||||
readable, _, _ = select.select(event_fds, [], [], timeout)
|
||||
|
||||
# Check long-press threshold (whether select returned due to timeout or input)
|
||||
if power_press_time is not None:
|
||||
held = time.monotonic() - power_press_time
|
||||
if held >= POWER_LONG_THRESHOLD:
|
||||
grab_inputs(event_fds, False)
|
||||
touch_and_shutdown(disp_fd, original_brightness, screen_on, sdlamp2_pid)
|
||||
return
|
||||
|
||||
if not readable:
|
||||
# Timeout with no input — turn off screen if idle
|
||||
if screen_on and power_press_time is None:
|
||||
set_brightness(disp_fd, 0)
|
||||
screen_on = False
|
||||
grab_inputs(event_fds, True)
|
||||
|
||||
# Check audio playback activity for idle auto-shutdown
|
||||
audio_pos = get_audio_file_pos(sdlamp2_pid)
|
||||
if audio_pos is not None and audio_pos != last_audio_pos:
|
||||
last_active_time = time.monotonic()
|
||||
last_audio_pos = audio_pos
|
||||
|
||||
# Auto-shutdown if idle long enough (no input + no playback)
|
||||
if time.monotonic() - last_active_time >= IDLE_SHUTDOWN_TIMEOUT:
|
||||
grab_inputs(event_fds, False)
|
||||
touch_and_shutdown(disp_fd, original_brightness, screen_on, sdlamp2_pid)
|
||||
return
|
||||
continue
|
||||
|
||||
# Process input events from all readable fds
|
||||
any_activity = False
|
||||
for fd in readable:
|
||||
while True:
|
||||
try:
|
||||
data = os.read(fd, INPUT_EVENT_SIZE)
|
||||
except BlockingIOError:
|
||||
break
|
||||
if len(data) < INPUT_EVENT_SIZE:
|
||||
break
|
||||
|
||||
_sec, _usec, ev_type, ev_code, ev_value = struct.unpack(
|
||||
INPUT_EVENT_FORMAT, data
|
||||
)
|
||||
|
||||
if ev_type == EV_SYN:
|
||||
continue
|
||||
|
||||
# Power button handling (EV_KEY only)
|
||||
if ev_type == EV_KEY and ev_code == KEY_POWER:
|
||||
if ev_value == 1: # press
|
||||
power_press_time = time.monotonic()
|
||||
elif ev_value == 0 and power_press_time is not None: # release
|
||||
hold_duration = time.monotonic() - power_press_time
|
||||
power_press_time = None
|
||||
if hold_duration < POWER_SHORT_THRESHOLD:
|
||||
# Short press — toggle screen
|
||||
if screen_on:
|
||||
set_brightness(disp_fd, 0)
|
||||
screen_on = False
|
||||
grab_inputs(event_fds, True)
|
||||
else:
|
||||
grab_inputs(event_fds, False)
|
||||
set_brightness(disp_fd, original_brightness)
|
||||
screen_on = True
|
||||
# Between SHORT and LONG threshold: ignore (release before 3s)
|
||||
continue
|
||||
|
||||
any_activity = True
|
||||
|
||||
# Any input activity resets idle shutdown timer
|
||||
if any_activity:
|
||||
last_active_time = time.monotonic()
|
||||
|
||||
# Any activity wakes screen (d-pad, face buttons, etc.)
|
||||
if any_activity and not screen_on:
|
||||
grab_inputs(event_fds, False)
|
||||
set_brightness(disp_fd, original_brightness)
|
||||
screen_on = True
|
||||
|
||||
|
||||
def touch_and_shutdown(disp_fd, original_brightness, screen_on, sdlamp2_pid):
|
||||
"""Signal sdlamp2 to exit and flag for shutdown.
|
||||
|
||||
Caller must release EVIOCGRAB before calling this.
|
||||
"""
|
||||
if not screen_on:
|
||||
set_brightness(disp_fd, original_brightness)
|
||||
os.close(disp_fd)
|
||||
try:
|
||||
open("/tmp/.sdlamp2_shutdown", "w").close()
|
||||
except OSError:
|
||||
pass
|
||||
try:
|
||||
os.kill(sdlamp2_pid, signal.SIGTERM)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
69
device/rg35xx/rg35xx-wrapper.sh
Executable file
69
device/rg35xx/rg35xx-wrapper.sh
Executable file
@@ -0,0 +1,69 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# RG35XX Plus wrapper for sdlamp2
|
||||
#
|
||||
# Launched by dmenu_ln instead of sdlamp2 directly. Handles device-specific
|
||||
# concerns that don't belong in the player binary:
|
||||
# 1. Screen idle timeout, power button screen toggle, and long-press shutdown
|
||||
# 2. Display the stock firmware's shutdown screen (goodbye.png → /dev/fb0)
|
||||
# 3. Launch sdlamp2 as the main process
|
||||
#
|
||||
# Install: copy to /mnt/vendor/bin/rg35xx-wrapper.sh
|
||||
# Config: set CMD in dmenu_ln to point here instead of sdlamp2 directly
|
||||
|
||||
SDLAMP2="/mnt/vendor/bin/sdlamp2"
|
||||
AUDIO_DIR="/mnt/sdcard/Music"
|
||||
SCREEN_MONITOR="/mnt/vendor/bin/rg35xx-screen-monitor.py"
|
||||
|
||||
# --- WiFi hotspot ---
|
||||
# Deprioritized: connecting the device as a WiFi client to a shared network
|
||||
# works fine even when sdlamp2 replaces the stock menu. Hotspot/AP mode isn't
|
||||
# needed — SSH access works over the shared network.
|
||||
|
||||
# --- Launch sdlamp2 ---
|
||||
"$SDLAMP2" "$AUDIO_DIR" &
|
||||
SDLAMP2_PID=$!
|
||||
|
||||
# --- Screen monitor (idle timeout + power button toggle + long-press shutdown) ---
|
||||
# Must launch after sdlamp2 so PID is available. Reads /dev/input/event0
|
||||
# directly for power button events — no evtest dependency.
|
||||
python3 "$SCREEN_MONITOR" "$SDLAMP2_PID" &
|
||||
SCREEN_MONITOR_PID=$!
|
||||
|
||||
# Wait for sdlamp2 to finish (signal or normal exit).
|
||||
wait "$SDLAMP2_PID"
|
||||
SDLAMP2_EXIT=$?
|
||||
|
||||
# --- Cleanup ---
|
||||
# Kill the screen monitor (SIGTERM restores brightness).
|
||||
kill "$SCREEN_MONITOR_PID" 2>/dev/null
|
||||
wait "$SCREEN_MONITOR_PID" 2>/dev/null
|
||||
|
||||
# If this was a shutdown, call poweroff and block so the loadapp.sh restart
|
||||
# loop doesn't relaunch dmenu_ln (which would take over the framebuffer and
|
||||
# overwrite sdlamp2's shutdown screen).
|
||||
if [ -f /tmp/.sdlamp2_shutdown ]; then
|
||||
rm -f /tmp/.sdlamp2_shutdown
|
||||
# Restore backlight brightness before writing the shutdown screen.
|
||||
# The screen monitor's SIGTERM handler tries to restore brightness, but
|
||||
# the Allwinner /dev/disp driver resets it to 0 when the fd is closed.
|
||||
# Re-set it here so goodbye.png is actually visible.
|
||||
# Then display the stock firmware's shutdown screen via framebuffer.
|
||||
# goodbye.png is 640x480 RGB — exactly matches the display.
|
||||
# /dev/fb0 is 32bpp BGRA, so we swap R/B channels and write raw pixels.
|
||||
python3 -c "
|
||||
import struct, fcntl
|
||||
disp = open('/dev/disp', 'wb')
|
||||
fcntl.ioctl(disp, 0x102, struct.pack('@4L', 0, 50, 0, 0))
|
||||
disp.close()
|
||||
from PIL import Image
|
||||
img = Image.open('/mnt/vendor/res1/shutdown/goodbye.png').convert('RGBA')
|
||||
r, g, b, a = img.split()
|
||||
with open('/dev/fb0', 'wb') as f:
|
||||
f.write(Image.merge('RGBA', (b, g, r, a)).tobytes())
|
||||
" 2>/dev/null
|
||||
poweroff
|
||||
sleep 30
|
||||
fi
|
||||
|
||||
exit "$SDLAMP2_EXIT"
|
||||
8
docker-arm64/Dockerfile
Normal file
8
docker-arm64/Dockerfile
Normal file
@@ -0,0 +1,8 @@
|
||||
FROM ubuntu:22.04
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
build-essential pkg-config \
|
||||
libsdl2-dev libsdl2-image-dev \
|
||||
libavformat-dev libavcodec-dev libavutil-dev libswresample-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
WORKDIR /workspace
|
||||
25
docker-arm64/Makefile
Normal file
25
docker-arm64/Makefile
Normal file
@@ -0,0 +1,25 @@
|
||||
.PHONY: shell build clean
|
||||
|
||||
IMAGE_NAME = arm64-dev
|
||||
PROJECT_DIR := $(shell cd .. && pwd)
|
||||
|
||||
CONTAINER_NAME = $(shell docker ps -f "ancestor=$(IMAGE_NAME)" --format "{{.Names}}")
|
||||
|
||||
.build: Dockerfile
|
||||
docker build --platform linux/arm64 -t $(IMAGE_NAME) .
|
||||
touch .build
|
||||
|
||||
ifeq ($(CONTAINER_NAME),)
|
||||
shell: .build
|
||||
docker run --platform linux/arm64 -it --rm -v "$(PROJECT_DIR)":/workspace $(IMAGE_NAME) /bin/bash
|
||||
else
|
||||
shell:
|
||||
docker exec -it $(CONTAINER_NAME) /bin/bash
|
||||
endif
|
||||
|
||||
build: .build
|
||||
docker run --platform linux/arm64 --rm -v "$(PROJECT_DIR)":/workspace $(IMAGE_NAME) make clean all
|
||||
|
||||
clean:
|
||||
docker rmi $(IMAGE_NAME)
|
||||
rm -f .build
|
||||
@@ -1,45 +0,0 @@
|
||||
FROM debian/eol:buster-slim
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
|
||||
ENV TZ=Europe/Brussels
|
||||
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
|
||||
|
||||
RUN apt-get -y update && apt-get -y install \
|
||||
build-essential \
|
||||
# bc \
|
||||
# bzip2 \
|
||||
# bzr \
|
||||
# cmake \
|
||||
# cmake-curses-gui \
|
||||
# cpio \
|
||||
# device-tree-compiler \
|
||||
git \
|
||||
# imagemagick \
|
||||
# libncurses5-dev \
|
||||
# locales \
|
||||
# make \
|
||||
# p7zip-full \
|
||||
# rsync \
|
||||
# sharutils \
|
||||
# scons \
|
||||
# tree \
|
||||
# unzip \
|
||||
# vim \
|
||||
# wget \
|
||||
# zip \
|
||||
libsdl2-dev \
|
||||
libsdl2-image-dev \
|
||||
libavformat-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN mkdir -p /root/workspace
|
||||
WORKDIR /root
|
||||
|
||||
# COPY support .
|
||||
# RUN ./build-toolchain.sh
|
||||
# RUN cat ./setup-env.sh >> .bashrc
|
||||
|
||||
VOLUME /root/workspace
|
||||
WORKDIR /root/workspace
|
||||
|
||||
CMD ["/bin/bash"]
|
||||
@@ -1,30 +0,0 @@
|
||||
.PHONY: shell
|
||||
.PHONY: clean
|
||||
|
||||
TOOLCHAIN_NAME=rg35xx-toolchain
|
||||
WORKSPACE_DIR := $(shell pwd)/workspace
|
||||
|
||||
CONTAINER_NAME=$(shell docker ps -f "ancestor=$(TOOLCHAIN_NAME)" --format "{{.Names}}")
|
||||
BOLD=$(shell tput bold)
|
||||
NORM=$(shell tput sgr0)
|
||||
|
||||
.build: Dockerfile
|
||||
$(info $(BOLD)Building $(TOOLCHAIN_NAME)...$(NORM))
|
||||
mkdir -p ./workspace
|
||||
docker build -t $(TOOLCHAIN_NAME) .
|
||||
touch .build
|
||||
|
||||
ifeq ($(CONTAINER_NAME),)
|
||||
shell: .build
|
||||
$(info $(BOLD)Starting $(TOOLCHAIN_NAME)...$(NORM))
|
||||
docker run -it --rm -v "$(WORKSPACE_DIR)":/root/workspace $(TOOLCHAIN_NAME) /bin/bash
|
||||
else
|
||||
shell:
|
||||
$(info $(BOLD)Connecting to running $(TOOLCHAIN_NAME)...$(NORM))
|
||||
docker exec -it $(CONTAINER_NAME) /bin/bash
|
||||
endif
|
||||
|
||||
clean:
|
||||
$(info $(BOLD)Removing $(TOOLCHAIN_NAME)...$(NORM))
|
||||
docker rmi $(TOOLCHAIN_NAME)
|
||||
rm -f .build
|
||||
@@ -1,41 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
BUILDROOT_VERSION=2017.11
|
||||
|
||||
set -xe
|
||||
|
||||
if [ -d ~/buildroot ]; then
|
||||
rm -rf ~/buildroot
|
||||
else
|
||||
sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen
|
||||
locale-gen
|
||||
fi
|
||||
|
||||
cd ~
|
||||
|
||||
BUILDROOT_NAME=buildroot-$BUILDROOT_VERSION
|
||||
wget https://buildroot.org/downloads/$BUILDROOT_NAME.tar.gz
|
||||
tar -xf ./$BUILDROOT_NAME.tar.gz
|
||||
rm -f ./$BUILDROOT_NAME.tar.gz
|
||||
mv ./$BUILDROOT_NAME ./buildroot
|
||||
|
||||
# patches for buildroot packages
|
||||
cd ~/patches
|
||||
for FILE in $(find . -type f -name "*.patch" 2>/dev/null); do
|
||||
cp $FILE ~/buildroot/$FILE
|
||||
done
|
||||
|
||||
cd ~/buildroot
|
||||
# patches for buildroot itself
|
||||
patch -p1 < ~/toolchain-expose-BR2_TOOLCHAIN_EXTRA_EXTERNAL_LIBS-for-all-toolchain-types-2017.11.1.diff
|
||||
|
||||
cp ~/rg35xx-buildroot-$BUILDROOT_VERSION.config ./.config
|
||||
if [ -f ~/rg35xx-toolchain.tar.xz ]; then
|
||||
tar -xf ~/rg35xx-toolchain.tar.xz -C /opt
|
||||
else
|
||||
export FORCE_UNSAFE_CONFIGURE=1
|
||||
make oldconfig
|
||||
make world
|
||||
|
||||
~/install-toolchain.sh
|
||||
fi
|
||||
@@ -1,41 +0,0 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
|
||||
#ifndef __ASMARM_HWCAP_H
|
||||
#define __ASMARM_HWCAP_H
|
||||
|
||||
/*
|
||||
* HWCAP flags - for elf_hwcap (in kernel) and AT_HWCAP
|
||||
*/
|
||||
#define HWCAP_SWP (1 << 0)
|
||||
#define HWCAP_HALF (1 << 1)
|
||||
#define HWCAP_THUMB (1 << 2)
|
||||
#define HWCAP_26BIT (1 << 3) /* Play it safe */
|
||||
#define HWCAP_FAST_MULT (1 << 4)
|
||||
#define HWCAP_FPA (1 << 5)
|
||||
#define HWCAP_VFP (1 << 6)
|
||||
#define HWCAP_EDSP (1 << 7)
|
||||
#define HWCAP_JAVA (1 << 8)
|
||||
#define HWCAP_IWMMXT (1 << 9)
|
||||
#define HWCAP_CRUNCH (1 << 10)
|
||||
#define HWCAP_THUMBEE (1 << 11)
|
||||
#define HWCAP_NEON (1 << 12)
|
||||
#define HWCAP_VFPv3 (1 << 13)
|
||||
#define HWCAP_VFPv3D16 (1 << 14) /* also set for VFPv4-D16 */
|
||||
#define HWCAP_TLS (1 << 15)
|
||||
#define HWCAP_VFPv4 (1 << 16)
|
||||
#define HWCAP_IDIVA (1 << 17)
|
||||
#define HWCAP_IDIVT (1 << 18)
|
||||
#define HWCAP_VFPD32 (1 << 19) /* set if VFP has 32 regs (not 16) */
|
||||
#define HWCAP_IDIV (HWCAP_IDIVA | HWCAP_IDIVT)
|
||||
#define HWCAP_LPAE (1 << 20)
|
||||
#define HWCAP_EVTSTRM (1 << 21)
|
||||
|
||||
/*
|
||||
* HWCAP2 flags - for elf_hwcap2 (in kernel) and AT_HWCAP2
|
||||
*/
|
||||
#define HWCAP2_AES (1 << 0)
|
||||
#define HWCAP2_PMULL (1 << 1)
|
||||
#define HWCAP2_SHA1 (1 << 2)
|
||||
#define HWCAP2_SHA2 (1 << 3)
|
||||
#define HWCAP2_CRC32 (1 << 4)
|
||||
|
||||
#endif /* __ASMARM_HWCAP_H */
|
||||
@@ -1,12 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
mkdir -p /opt/rg35xx-toolchain
|
||||
if [ -d /opt/rg35xx-toolchain/usr ]; then
|
||||
rm -fr /opt/rg35xx-toolchain/usr
|
||||
fi
|
||||
cp -rf ~/buildroot/output/host/usr/ /opt/rg35xx-toolchain/
|
||||
# this version of buildroot doesn't have relocate-sdk.sh yet so we bring our own
|
||||
cp ~/relocate-sdk.sh /opt/rg35xx-toolchain/
|
||||
cp ~/sdk-location /opt/rg35xx-toolchain/
|
||||
cp ~/hwcap.h /opt/rg35xx-toolchain/usr/arm-buildroot-linux-gnueabihf/sysroot/usr/include/asm/
|
||||
/opt/rg35xx-toolchain/relocate-sdk.sh
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
cp ~/buildroot/output/images/rootfs.ext2 ~/workspace/rootfs.img
|
||||
@@ -1,7 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
cd /opt/
|
||||
tar --xz -cvf rg35xx-toolchain.tar.xz rg35xx-toolchain/
|
||||
mv rg35xx-toolchain.tar.xz ~/workspace/
|
||||
|
||||
printf "rg35xx-toolchain.tar.xz can be shared as a blob\nby placing in support before calling 'make shell'\n"
|
||||
@@ -1,33 +0,0 @@
|
||||
Subject: Workaround change in glibc
|
||||
|
||||
Temporary workaround to compile with glibc 2.28, which
|
||||
deprecated some constants
|
||||
|
||||
Based on the workaround made for the tools/m4 package
|
||||
|
||||
--- a/lib/stdio-impl.h
|
||||
+++ b/lib/stdio-impl.h
|
||||
@@ -18,6 +18,12 @@
|
||||
the same implementation of stdio extension API, except that some fields
|
||||
have different naming conventions, or their access requires some casts. */
|
||||
|
||||
+/* Glibc 2.28 made _IO_IN_BACKUP private. For now, work around this
|
||||
+ problem by defining it ourselves. FIXME: Do not rely on glibc
|
||||
+ internals. */
|
||||
+#if !defined _IO_IN_BACKUP && defined _IO_EOF_SEEN
|
||||
+# define _IO_IN_BACKUP 0x100
|
||||
+#endif
|
||||
|
||||
/* BSD stdio derived implementations. */
|
||||
|
||||
--- a/lib/fseterr.c
|
||||
+++ b/lib/fseterr.c
|
||||
@@ -29,7 +29,7 @@
|
||||
/* Most systems provide FILE as a struct and the necessary bitmask in
|
||||
<stdio.h>, because they need it for implementing getc() and putc() as
|
||||
fast macros. */
|
||||
-#if defined _IO_ftrylockfile || __GNU_LIBRARY__ == 1 /* GNU libc, BeOS, Haiku, Linux libc5 */
|
||||
+#if defined _IO_EOF_SEEN || __GNU_LIBRARY__ == 1 /* GNU libc, BeOS, Haiku, Linux libc5 */
|
||||
fp->_flags |= _IO_ERR_SEEN;
|
||||
#elif defined __sferror || defined __DragonFly__ /* FreeBSD, NetBSD, OpenBSD, DragonFly, Mac OS X, Cygwin */
|
||||
fp_->_flags |= __SERR;
|
||||
@@ -1,40 +0,0 @@
|
||||
This patch prevents a conflict with glibc
|
||||
|
||||
--- a/misc/create_inode.c
|
||||
+++ b/misc/create_inode.c
|
||||
@@ -392,7 +392,7 @@ static ssize_t my_pread(int fd, void *buf, size_t count, off_t offset)
|
||||
}
|
||||
#endif /* !defined HAVE_PREAD64 && !defined HAVE_PREAD */
|
||||
|
||||
-static errcode_t copy_file_range(ext2_filsys fs, int fd, ext2_file_t e2_file,
|
||||
+static errcode_t copy_file_chunk(ext2_filsys fs, int fd, ext2_file_t e2_file,
|
||||
off_t start, off_t end, char *buf,
|
||||
char *zerobuf)
|
||||
{
|
||||
@@ -466,7 +466,7 @@ static errcode_t try_lseek_copy(ext2_filsys fs, int fd, struct stat *statbuf,
|
||||
|
||||
data_blk = data & ~(fs->blocksize - 1);
|
||||
hole_blk = (hole + (fs->blocksize - 1)) & ~(fs->blocksize - 1);
|
||||
- err = copy_file_range(fs, fd, e2_file, data_blk, hole_blk, buf,
|
||||
+ err = copy_file_chunk(fs, fd, e2_file, data_blk, hole_blk, buf,
|
||||
zerobuf);
|
||||
if (err)
|
||||
return err;
|
||||
@@ -516,7 +516,7 @@ static errcode_t try_fiemap_copy(ext2_filsys fs, int fd, ext2_file_t e2_file,
|
||||
}
|
||||
for (i = 0, ext = ext_buf; i < fiemap_buf->fm_mapped_extents;
|
||||
i++, ext++) {
|
||||
- err = copy_file_range(fs, fd, e2_file, ext->fe_logical,
|
||||
+ err = copy_file_chunk(fs, fd, e2_file, ext->fe_logical,
|
||||
ext->fe_logical + ext->fe_length,
|
||||
buf, zerobuf);
|
||||
if (err)
|
||||
@@ -569,7 +569,7 @@ static errcode_t copy_file(ext2_filsys fs, int fd, struct stat *statbuf,
|
||||
goto out;
|
||||
#endif
|
||||
|
||||
- err = copy_file_range(fs, fd, e2_file, 0, statbuf->st_size, buf,
|
||||
+ err = copy_file_chunk(fs, fd, e2_file, 0, statbuf->st_size, buf,
|
||||
zerobuf);
|
||||
out:
|
||||
ext2fs_free_mem(&zerobuf);
|
||||
@@ -1,131 +0,0 @@
|
||||
From c79aedf13fe693da0fc5c4ff727aed5bd43526dc Mon Sep 17 00:00:00 2001
|
||||
From: Hutson Betts <hutson@hyper-expanse.net>
|
||||
Date: Thu, 10 Dec 2020 21:13:54 -0600
|
||||
Subject: [PATCH] glibc 2.28
|
||||
|
||||
|
||||
diff --git a/lib/fflush.c b/lib/fflush.c
|
||||
index ef2a7f1..787790d 100644
|
||||
--- a/lib/fflush.c
|
||||
+++ b/lib/fflush.c
|
||||
@@ -33,7 +33,7 @@
|
||||
#undef fflush
|
||||
|
||||
|
||||
-#if defined _IO_ftrylockfile || __GNU_LIBRARY__ == 1 /* GNU libc, BeOS, Haiku, Linux libc5 */
|
||||
+#if defined _IO_EOF_SEEN || __GNU_LIBRARY__ == 1 /* GNU libc, BeOS, Haiku, Linux libc5 */
|
||||
|
||||
/* Clear the stream's ungetc buffer, preserving the value of ftello (fp). */
|
||||
static void
|
||||
@@ -72,7 +72,7 @@ clear_ungetc_buffer (FILE *fp)
|
||||
|
||||
#endif
|
||||
|
||||
-#if ! (defined _IO_ftrylockfile || __GNU_LIBRARY__ == 1 /* GNU libc, BeOS, Haiku, Linux libc5 */)
|
||||
+#if ! (defined _IO_EOF_SEEN || __GNU_LIBRARY__ == 1 /* GNU libc, BeOS, Haiku, Linux libc5 */)
|
||||
|
||||
# if (defined __sferror || defined __DragonFly__ || defined __ANDROID__) && defined __SNPT
|
||||
/* FreeBSD, NetBSD, OpenBSD, DragonFly, Mac OS X, Cygwin, Android */
|
||||
@@ -148,7 +148,7 @@ rpl_fflush (FILE *stream)
|
||||
if (stream == NULL || ! freading (stream))
|
||||
return fflush (stream);
|
||||
|
||||
-#if defined _IO_ftrylockfile || __GNU_LIBRARY__ == 1 /* GNU libc, BeOS, Haiku, Linux libc5 */
|
||||
+#if defined _IO_EOF_SEEN || __GNU_LIBRARY__ == 1 /* GNU libc, BeOS, Haiku, Linux libc5 */
|
||||
|
||||
clear_ungetc_buffer_preserving_position (stream);
|
||||
|
||||
diff --git a/lib/fpending.c b/lib/fpending.c
|
||||
index ce93604..9fe7ffb 100644
|
||||
--- a/lib/fpending.c
|
||||
+++ b/lib/fpending.c
|
||||
@@ -32,7 +32,7 @@ __fpending (FILE *fp)
|
||||
/* Most systems provide FILE as a struct and the necessary bitmask in
|
||||
<stdio.h>, because they need it for implementing getc() and putc() as
|
||||
fast macros. */
|
||||
-#if defined _IO_ftrylockfile || __GNU_LIBRARY__ == 1 /* GNU libc, BeOS, Haiku, Linux libc5 */
|
||||
+#if defined _IO_EOF_SEEN || __GNU_LIBRARY__ == 1 /* GNU libc, BeOS, Haiku, Linux libc5 */
|
||||
return fp->_IO_write_ptr - fp->_IO_write_base;
|
||||
#elif defined __sferror || defined __DragonFly__ || defined __ANDROID__
|
||||
/* FreeBSD, NetBSD, OpenBSD, DragonFly, Mac OS X, Cygwin, Android */
|
||||
diff --git a/lib/fpurge.c b/lib/fpurge.c
|
||||
index 53ee68c..7cba3a3 100644
|
||||
--- a/lib/fpurge.c
|
||||
+++ b/lib/fpurge.c
|
||||
@@ -62,7 +62,7 @@ fpurge (FILE *fp)
|
||||
/* Most systems provide FILE as a struct and the necessary bitmask in
|
||||
<stdio.h>, because they need it for implementing getc() and putc() as
|
||||
fast macros. */
|
||||
-# if defined _IO_ftrylockfile || __GNU_LIBRARY__ == 1 /* GNU libc, BeOS, Haiku, Linux libc5 */
|
||||
+# if defined _IO_EOF_SEEN || __GNU_LIBRARY__ == 1 /* GNU libc, BeOS, Haiku, Linux libc5 */
|
||||
fp->_IO_read_end = fp->_IO_read_ptr;
|
||||
fp->_IO_write_ptr = fp->_IO_write_base;
|
||||
/* Avoid memory leak when there is an active ungetc buffer. */
|
||||
diff --git a/lib/freadahead.c b/lib/freadahead.c
|
||||
index cfc969b..5e43e13 100644
|
||||
--- a/lib/freadahead.c
|
||||
+++ b/lib/freadahead.c
|
||||
@@ -25,7 +25,7 @@
|
||||
size_t
|
||||
freadahead (FILE *fp)
|
||||
{
|
||||
-#if defined _IO_ftrylockfile || __GNU_LIBRARY__ == 1 /* GNU libc, BeOS, Haiku, Linux libc5 */
|
||||
+#if defined _IO_EOF_SEEN || __GNU_LIBRARY__ == 1 /* GNU libc, BeOS, Haiku, Linux libc5 */
|
||||
if (fp->_IO_write_ptr > fp->_IO_write_base)
|
||||
return 0;
|
||||
return (fp->_IO_read_end - fp->_IO_read_ptr)
|
||||
diff --git a/lib/freading.c b/lib/freading.c
|
||||
index 05cb0b8..f1da5b9 100644
|
||||
--- a/lib/freading.c
|
||||
+++ b/lib/freading.c
|
||||
@@ -31,7 +31,7 @@ freading (FILE *fp)
|
||||
/* Most systems provide FILE as a struct and the necessary bitmask in
|
||||
<stdio.h>, because they need it for implementing getc() and putc() as
|
||||
fast macros. */
|
||||
-# if defined _IO_ftrylockfile || __GNU_LIBRARY__ == 1 /* GNU libc, BeOS, Haiku, Linux libc5 */
|
||||
+# if defined _IO_EOF_SEEN || __GNU_LIBRARY__ == 1 /* GNU libc, BeOS, Haiku, Linux libc5 */
|
||||
return ((fp->_flags & _IO_NO_WRITES) != 0
|
||||
|| ((fp->_flags & (_IO_NO_READS | _IO_CURRENTLY_PUTTING)) == 0
|
||||
&& fp->_IO_read_base != NULL));
|
||||
diff --git a/lib/fseeko.c b/lib/fseeko.c
|
||||
index 0c01c4f..0601619 100644
|
||||
--- a/lib/fseeko.c
|
||||
+++ b/lib/fseeko.c
|
||||
@@ -47,7 +47,7 @@ fseeko (FILE *fp, off_t offset, int whence)
|
||||
#endif
|
||||
|
||||
/* These tests are based on fpurge.c. */
|
||||
-#if defined _IO_ftrylockfile || __GNU_LIBRARY__ == 1 /* GNU libc, BeOS, Haiku, Linux libc5 */
|
||||
+#if defined _IO_EOF_SEEN || __GNU_LIBRARY__ == 1 /* GNU libc, BeOS, Haiku, Linux libc5 */
|
||||
if (fp->_IO_read_end == fp->_IO_read_ptr
|
||||
&& fp->_IO_write_ptr == fp->_IO_write_base
|
||||
&& fp->_IO_save_base == NULL)
|
||||
@@ -123,7 +123,7 @@ fseeko (FILE *fp, off_t offset, int whence)
|
||||
return -1;
|
||||
}
|
||||
|
||||
-#if defined _IO_ftrylockfile || __GNU_LIBRARY__ == 1 /* GNU libc, BeOS, Haiku, Linux libc5 */
|
||||
+#if defined _IO_EOF_SEEN || __GNU_LIBRARY__ == 1 /* GNU libc, BeOS, Haiku, Linux libc5 */
|
||||
fp->_flags &= ~_IO_EOF_SEEN;
|
||||
fp->_offset = pos;
|
||||
#elif defined __sferror || defined __DragonFly__ || defined __ANDROID__
|
||||
diff --git a/lib/stdio-impl.h b/lib/stdio-impl.h
|
||||
index 766d693..75fe3ad 100644
|
||||
--- a/lib/stdio-impl.h
|
||||
+++ b/lib/stdio-impl.h
|
||||
@@ -18,6 +18,12 @@
|
||||
the same implementation of stdio extension API, except that some fields
|
||||
have different naming conventions, or their access requires some casts. */
|
||||
|
||||
+/* Glibc 2.28 made _IO_IN_BACKUP private. For now, work around this
|
||||
+ problem by defining it ourselves. FIXME: Do not rely on glibc
|
||||
+ internals. */
|
||||
+#if !defined _IO_IN_BACKUP && defined _IO_EOF_SEEN
|
||||
+# define _IO_IN_BACKUP 0x100
|
||||
+#endif
|
||||
|
||||
/* BSD stdio derived implementations. */
|
||||
|
||||
--
|
||||
2.25.1
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
diff --git forkSrcPrefix/src/video/fbcon/SDL_fbkeys.h forkDstPrefix/src/video/fbcon/SDL_fbkeys.h
|
||||
index 2b01b6b2e3588426e50dbf109e09e0fdb8755091..4acd13df4302185d27964859a58f8b09c25f13f3 100644
|
||||
--- forkSrcPrefix/src/video/fbcon/SDL_fbkeys.h
|
||||
+++ forkDstPrefix/src/video/fbcon/SDL_fbkeys.h
|
||||
@@ -114,6 +114,15 @@
|
||||
#define SCANCODE_F11 87
|
||||
#define SCANCODE_F12 88
|
||||
|
||||
+/* RG35XX */
|
||||
+#define SCANCODE_KATAKANA 90
|
||||
+#define SCANCODE_HIRAGANA 91
|
||||
+#define SCANCODE_HENKAN 92
|
||||
+#define SCANCODE_KATAKANAHIRAGANA 93
|
||||
+#define SCANCODE_MUHENKAN 94
|
||||
+#define SCANCODE_KPJPCOMMA 95
|
||||
+#define SCANCODE_POWER 116
|
||||
+
|
||||
#define SCANCODE_KEYPADENTER 96
|
||||
#define SCANCODE_RIGHTCONTROL 97
|
||||
#define SCANCODE_CONTROL 97
|
||||
diff --git forkSrcPrefix/include/SDL_keysym.h forkDstPrefix/include/SDL_keysym.h
|
||||
index f2ad12b81ef5725e3d975c9ad3a775fa50aa6cb6..7c348322a4d0434f4cd7349fdbd62026d4e65911 100644
|
||||
--- forkSrcPrefix/include/SDL_keysym.h
|
||||
+++ forkDstPrefix/include/SDL_keysym.h
|
||||
@@ -296,6 +296,19 @@ typedef enum {
|
||||
SDLK_UNDO = 322, /**< Atari keyboard has Undo */
|
||||
/*@}*/
|
||||
|
||||
+ /** @name RG35XX keys */
|
||||
+ /*@{*/
|
||||
+ SDLK_KATAKANA = 323,
|
||||
+ SDLK_HIRAGANA = 324,
|
||||
+ SDLK_HENKAN = 325,
|
||||
+ SDLK_KATAKANAHIRAGANA = 326,
|
||||
+ SDLK_MUHENKAN = 327,
|
||||
+ SDLK_KP_JPCOMMA = 328,
|
||||
+ SDLK_KP_SLASH = 329,
|
||||
+ SDLK_CURSORBLOCKUP = 330,
|
||||
+ SDLK_CURSORBLOCKDOWN = 331,
|
||||
+ /*@}*/
|
||||
+
|
||||
/* Add any other keys here */
|
||||
|
||||
SDLK_LAST
|
||||
diff --git forkSrcPrefix/src/video/fbcon/SDL_fbevents.c forkDstPrefix/src/video/fbcon/SDL_fbevents.c
|
||||
index 5e369a4a89c3157206abed1f4c4b8e27aef17024..c15d1b85b7f06757b24fb7f17dc73e6bab1c2148 100644
|
||||
--- forkSrcPrefix/src/video/fbcon/SDL_fbevents.c
|
||||
+++ forkDstPrefix/src/video/fbcon/SDL_fbevents.c
|
||||
@@ -1133,6 +1133,41 @@ void FB_InitOSKeymap(_THIS)
|
||||
case 127:
|
||||
keymap[i] = SDLK_MENU;
|
||||
break;
|
||||
+
|
||||
+ /* RG35XX */
|
||||
+ case SCANCODE_KATAKANA:
|
||||
+ keymap[i] = SDLK_KATAKANA;
|
||||
+ break;
|
||||
+ case SCANCODE_HIRAGANA:
|
||||
+ keymap[i] = SDLK_HIRAGANA;
|
||||
+ break;
|
||||
+ case SCANCODE_HENKAN:
|
||||
+ keymap[i] = SDLK_HENKAN;
|
||||
+ break;
|
||||
+ case SCANCODE_KATAKANAHIRAGANA:
|
||||
+ keymap[i] = SDLK_KATAKANAHIRAGANA;
|
||||
+ break;
|
||||
+ case SCANCODE_MUHENKAN:
|
||||
+ keymap[i] = SDLK_MUHENKAN;
|
||||
+ break;
|
||||
+ case SCANCODE_KPJPCOMMA:
|
||||
+ keymap[i] = SDLK_KP_JPCOMMA;
|
||||
+ break;
|
||||
+ case SCANCODE_KEYPADENTER:
|
||||
+ keymap[i] = SDLK_KP_ENTER;
|
||||
+ break;
|
||||
+ case SCANCODE_CURSORBLOCKUP:
|
||||
+ keymap[i] = SDLK_CURSORBLOCKUP;
|
||||
+ break;
|
||||
+ case SCANCODE_CURSORBLOCKDOWN:
|
||||
+ keymap[i] = SDLK_CURSORBLOCKDOWN;
|
||||
+ break;
|
||||
+ case SCANCODE_POWER: // this doesn't stick so we override below
|
||||
+ keymap[i] = SDLK_POWER; // leaving this in just in case
|
||||
+ break;
|
||||
+
|
||||
+
|
||||
+
|
||||
/* this should take care of all standard ascii keys */
|
||||
default:
|
||||
keymap[i] = KVAL(vga_keymap[0][i]);
|
||||
@@ -1208,6 +1243,8 @@ void FB_InitOSKeymap(_THIS)
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
+
|
||||
+ keymap[116] = SDLK_POWER; // requires hard override for some reason
|
||||
}
|
||||
|
||||
static SDL_keysym *TranslateKey(int scancode, SDL_keysym *keysym)
|
||||
@@ -1,35 +0,0 @@
|
||||
--- a/src/video/fbcon/SDL_fbvideo.c
|
||||
+++ b/src/video/fbcon/SDL_fbvideo.c
|
||||
@@ -1907,15 +1907,12 @@
|
||||
static void FB_VideoQuit(_THIS)
|
||||
{
|
||||
int i, j;
|
||||
+ const char *dontClearPixels = SDL_getenv("SDL_FBCON_DONT_CLEAR");
|
||||
|
||||
if ( this->screen ) {
|
||||
- /* Clear screen and tell SDL not to free the pixels */
|
||||
-
|
||||
- const char *dontClearPixels = SDL_getenv("SDL_FBCON_DONT_CLEAR");
|
||||
-
|
||||
/* If the framebuffer is not to be cleared, make sure that we won't
|
||||
* display the previous frame when disabling double buffering. */
|
||||
- if ( dontClearPixels && flip_page == 0 ) {
|
||||
+ if ( dontClearPixels && (this->screen->flags & SDL_DOUBLEBUF) && flip_page == 0 ) {
|
||||
SDL_memcpy(flip_address[0], flip_address[1], this->screen->pitch * this->screen->h);
|
||||
}
|
||||
|
||||
@@ -1969,7 +1966,13 @@
|
||||
|
||||
/* Restore the original video mode and palette */
|
||||
if ( FB_InGraphicsMode(this) ) {
|
||||
- FB_RestorePalette(this);
|
||||
+ if (dontClearPixels) {
|
||||
+ /* Restore only panning, keep current mode */
|
||||
+ ioctl(console_fd, FBIOGET_VSCREENINFO, &saved_vinfo);
|
||||
+ saved_vinfo.yoffset = saved_vinfo.xoffset = 0;
|
||||
+ } else {
|
||||
+ FB_RestorePalette(this);
|
||||
+ }
|
||||
ioctl(console_fd, FBIOPUT_VSCREENINFO, &saved_vinfo);
|
||||
}
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
if [ "$#" -ne 0 ]; then
|
||||
echo "Run this script to relocate the buildroot SDK at that location"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
FILEPATH="$(readlink -f "$0")"
|
||||
NEWPATH="$(dirname "${FILEPATH}")"
|
||||
|
||||
cd "${NEWPATH}"
|
||||
LOCFILE="sdk-location"
|
||||
if [ ! -r "${LOCFILE}" ]; then
|
||||
echo "Previous location of the buildroot SDK not found!"
|
||||
exit 1
|
||||
fi
|
||||
OLDPATH="$(cat "${LOCFILE}")"
|
||||
|
||||
if [ "${NEWPATH}" = "${OLDPATH}" ]; then
|
||||
echo "This buildroot SDK has already been relocated!"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check if the path substitution does work properly, e.g. a tree
|
||||
# "/a/b/c" copied into "/a/b/c/a/b/c/" would not be allowed.
|
||||
newpath="$(sed -e "s|${OLDPATH}|${NEWPATH}|g" "${LOCFILE}")"
|
||||
if [ "${NEWPATH}" != "${newpath}" ]; then
|
||||
echo "Something went wrong with substituting the path!"
|
||||
echo "Please choose another location for your SDK!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Relocating the buildroot SDK from ${OLDPATH} to ${NEWPATH} ..."
|
||||
|
||||
# Make sure file uses the right language
|
||||
export LC_ALL=C
|
||||
# Replace the old path with the new one in all text files
|
||||
grep -lr "${OLDPATH}" . | while read -r FILE ; do
|
||||
if file -b --mime-type "${FILE}" | grep -q '^text/' && [ "${FILE}" != "${LOCFILE}" ]
|
||||
then
|
||||
sed -i "s|${OLDPATH}|${NEWPATH}|g" "${FILE}"
|
||||
fi
|
||||
done
|
||||
|
||||
# At the very end, we update the location file to not break the
|
||||
# SDK if this script gets interruted.
|
||||
sed -i "s|${OLDPATH}|${NEWPATH}|g" ${LOCFILE}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1 +0,0 @@
|
||||
/root/buildroot/output/host
|
||||
@@ -1,4 +0,0 @@
|
||||
export PATH="/opt/rg35xx-toolchain/usr/bin:${PATH}:/opt/rg35xx-toolchain/usr/arm-buildroot-linux-gnueabihf/sysroot/bin"
|
||||
export CROSS_COMPILE=/opt/rg35xx-toolchain/usr/bin/arm-buildroot-linux-gnueabihf-
|
||||
export PREFIX=/opt/rg35xx-toolchain/usr/arm-buildroot-linux-gnueabihf/sysroot/usr
|
||||
export UNION_PLATFORM=rg35xx
|
||||
@@ -1,89 +0,0 @@
|
||||
diff --git a/Config.in.legacy b/Config.in.legacy
|
||||
index d4f3d04062..b3086300e6 100644
|
||||
--- a/Config.in.legacy
|
||||
+++ b/Config.in.legacy
|
||||
@@ -147,6 +147,17 @@ endif
|
||||
|
||||
comment "Legacy options removed in 2017.11"
|
||||
|
||||
+config BR2_TOOLCHAIN_EXTRA_EXTERNAL_LIBS
|
||||
+ string "toolchain-external extra libs option has been renamed"
|
||||
+ help
|
||||
+ The option BR2_TOOLCHAIN_EXTRA_EXTERNAL_LIBS has
|
||||
+ been renamed to BR2_TOOLCHAIN_EXTRA_LIBS.
|
||||
+
|
||||
+config BR2_TOOLCHAIN_EXTRA_EXTERNAL_LIBS_WRAP
|
||||
+ bool
|
||||
+ default y if BR2_TOOLCHAIN_EXTRA_EXTERNAL_LIBS != ""
|
||||
+ select BR2_LEGACY
|
||||
+
|
||||
config BR2_PACKAGE_RFKILL
|
||||
bool "rfkill package removed"
|
||||
select BR2_LEGACY
|
||||
diff --git a/package/gcc/gcc-final/gcc-final.mk b/package/gcc/gcc-final/gcc-final.mk
|
||||
index 30fb87856c..24d034b720 100644
|
||||
--- a/package/gcc/gcc-final/gcc-final.mk
|
||||
+++ b/package/gcc/gcc-final/gcc-final.mk
|
||||
@@ -187,6 +187,8 @@ ifeq ($(BR2_GCC_ENABLE_OPENMP),y)
|
||||
HOST_GCC_FINAL_USR_LIBS += libgomp
|
||||
endif
|
||||
|
||||
+HOST_GCC_FINAL_USR_LIBS += $(call qstrip,$(BR2_TOOLCHAIN_EXTRA_LIBS))
|
||||
+
|
||||
ifneq ($(HOST_GCC_FINAL_USR_LIBS),)
|
||||
define HOST_GCC_FINAL_INSTALL_STATIC_LIBS
|
||||
for i in $(HOST_GCC_FINAL_USR_LIBS) ; do \
|
||||
diff --git a/toolchain/Config.in b/toolchain/Config.in
|
||||
index c9aa95985f..8f990cacb9 100644
|
||||
--- a/toolchain/toolchain-common.in
|
||||
+++ b/toolchain/toolchain-common.in
|
||||
@@ -82,6 +82,19 @@ config BR2_TOOLCHAIN_GLIBC_GCONV_LIBS_LIST
|
||||
|
||||
Note: the full set of gconv libs are ~8MiB (on ARM).
|
||||
|
||||
+config BR2_TOOLCHAIN_EXTRA_LIBS
|
||||
+ string "Extra toolchain libraries to be copied to target"
|
||||
+ default ""
|
||||
+ help
|
||||
+ If your toolchain provides extra libraries that need to be
|
||||
+ copied to the target filesystem, enter them here, separated
|
||||
+ by spaces.
|
||||
+
|
||||
+ NOTE: The library name should not include a suffix or wildcard.
|
||||
+
|
||||
+ Examples where this can be useful is for adding debug libraries
|
||||
+ to the target like the GCC libsanitizer (libasan/liblsan/...).
|
||||
+
|
||||
# This boolean is true if the toolchain provides a built-in full
|
||||
# featured gettext implementation (glibc), and false if only a stub
|
||||
# gettext implementation is provided (uclibc, musl)
|
||||
diff --git a/toolchain/toolchain-external/pkg-toolchain-external.mk b/toolchain/toolchain-external/pkg-toolchain-external.mk
|
||||
index 5147da0104..e339773a96 100644
|
||||
--- a/toolchain/toolchain-external/pkg-toolchain-external.mk
|
||||
+++ b/toolchain/toolchain-external/pkg-toolchain-external.mk
|
||||
@@ -156,7 +156,7 @@ ifeq ($(BR2_TOOLCHAIN_HAS_DLANG),y)
|
||||
TOOLCHAIN_EXTERNAL_LIBS += libgdruntime.so* libgphobos.so*
|
||||
endif
|
||||
|
||||
-TOOLCHAIN_EXTERNAL_LIBS += $(call qstrip,$(BR2_TOOLCHAIN_EXTRA_EXTERNAL_LIBS))
|
||||
+TOOLCHAIN_EXTERNAL_LIBS += $(call qstrip,$(BR2_TOOLCHAIN_EXTRA_LIBS))
|
||||
|
||||
|
||||
#
|
||||
diff --git a/toolchain/toolchain-external/toolchain-external-custom/Config.in.options b/toolchain/toolchain-external/toolchain-external-custom/Config.in.options
|
||||
index a36747f490..fd95f8201b 100644
|
||||
--- a/toolchain/toolchain-external/toolchain-external-custom/Config.in.options
|
||||
+++ b/toolchain/toolchain-external/toolchain-external-custom/Config.in.options
|
||||
@@ -438,12 +438,4 @@ config BR2_TOOLCHAIN_EXTERNAL_OPENMP
|
||||
support. If you don't know, leave the default value,
|
||||
Buildroot will tell you if it's correct or not.
|
||||
|
||||
-config BR2_TOOLCHAIN_EXTRA_EXTERNAL_LIBS
|
||||
- string "Extra toolchain libraries to be copied to target"
|
||||
- help
|
||||
- If your external toolchain provides extra libraries that
|
||||
- need to be copied to the target filesystem, enter them
|
||||
- here, separated by spaces. They will be copied to the
|
||||
- target's /lib directory.
|
||||
-
|
||||
endif
|
||||
@@ -1 +0,0 @@
|
||||
../
|
||||
34
docs/TOOLCHAIN.md
Normal file
34
docs/TOOLCHAIN.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Build Container
|
||||
|
||||
arm64 Docker build container for producing device binaries. For target device details see [`rg35xx-plus.md`](rg35xx-plus.md).
|
||||
|
||||
The `docker-arm64/` directory contains a Docker setup that runs an arm64 Ubuntu 22.04 container via QEMU user-mode emulation. This matches the target device exactly — same distro, same glibc 2.35, same library versions. The project's native `make` (using `pkg-config`) works as-is inside the container, no cross-compilation flags needed.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
QEMU binfmt handlers must be registered on the host (one-time setup, persists across reboots):
|
||||
|
||||
```sh
|
||||
docker run --privileged --rm tonistiigi/binfmt --install arm64
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
From the `docker-arm64/` directory:
|
||||
|
||||
```sh
|
||||
make build # one-shot: build sdlamp2 inside the container
|
||||
make shell # interactive bash shell for development
|
||||
make clean # remove the Docker image
|
||||
```
|
||||
|
||||
The container mounts the project root at `/workspace`. Output binary is `build/sdlamp2` (`ELF 64-bit ARM aarch64`).
|
||||
|
||||
### Image Contents
|
||||
|
||||
- **Base:** `ubuntu:22.04` (arm64)
|
||||
- **Build tools:** `build-essential`, `pkg-config`
|
||||
- **SDL2:** `libsdl2-dev`, `libsdl2-image-dev`
|
||||
- **FFmpeg:** `libavformat-dev`, `libavcodec-dev`, `libavutil-dev`, `libswresample-dev`
|
||||
|
||||
The image is tagged `arm64-dev` and is generic enough to reuse for other projects targeting the same device.
|
||||
181
docs/rg35xx-plus.md
Normal file
181
docs/rg35xx-plus.md
Normal file
@@ -0,0 +1,181 @@
|
||||
# Anbernic RG35XX Plus
|
||||
|
||||
Device-specific reference for the target hardware. For build instructions see [`TOOLCHAIN.md`](TOOLCHAIN.md).
|
||||
|
||||
## Hardware
|
||||
|
||||
- **Device:** Anbernic RG35XX Plus
|
||||
- **SoC:** Allwinner H700 — quad-core ARM Cortex-A53 @ 1.5 GHz (AArch64 / ARMv8-A)
|
||||
- **GPU:** Mali G31 MP2
|
||||
- **RAM:** 1 GB LPDDR4
|
||||
- **Display:** 640×480 IPS
|
||||
|
||||
## Software
|
||||
|
||||
- **OS:** Ubuntu 22.04 LTS (Jammy Jellyfish) — [modified stock firmware](https://github.com/cbepx-me/Anbernic-H700-RG-xx-StockOS-Modification)
|
||||
- **Kernel:** Linux 4.9.170 aarch64 (proprietary, no source released by Anbernic)
|
||||
- **Architecture:** aarch64 / arm64 (confirmed via `dpkg --print-architecture`)
|
||||
- **glibc:** 2.35
|
||||
- **Userland:** Debian/Ubuntu-based (`dpkg`, `apt-get`, `systemd`)
|
||||
|
||||
## Libraries on Device
|
||||
|
||||
All required shared libraries are pre-installed. Most are at `/usr/lib/`, some at `/usr/lib/aarch64-linux-gnu/` (Debian multiarch path):
|
||||
|
||||
| Library | Soname | Notes |
|
||||
| :------------ | :----------------------------- | :---------------------------------- |
|
||||
| SDL2 | `libSDL2-2.0.so.0.12.0` | SDL 2.0.12 |
|
||||
| SDL2_image | `libSDL2_image-2.0.so.0.900.0` | SDL2_image 2.0.9, at multiarch path |
|
||||
| libavcodec | `libavcodec.so.58` | FFmpeg ~4.x |
|
||||
| libavformat | `libavformat.so.58` | FFmpeg ~4.x |
|
||||
| libavutil | `libavutil.so.56` | FFmpeg ~4.x |
|
||||
| libswresample | `libswresample.so.3` | FFmpeg ~4.x |
|
||||
|
||||
Shared libraries are already present — no need to bundle or build them. A native aarch64 compile (e.g. inside the arm64 Docker container) produces working binaries. Must link against glibc ≤ 2.35.
|
||||
|
||||
## Input Devices
|
||||
|
||||
Three input devices are registered via `/proc/bus/input/devices`:
|
||||
|
||||
| Device | Handlers | Purpose |
|
||||
| :------------------- | :-------------- | :----------------------------------- |
|
||||
| `axp2202-pek` | `event0` | Power button (`KEY_POWER`, code 116) |
|
||||
| `ANBERNIC-keys` | `event1`, `js0` | Gamepad (d-pad, face buttons) |
|
||||
| `dierct-keys-polled` | `event2` | Shoulder buttons, menu/function keys |
|
||||
|
||||
- **logind**: `HandlePowerKey=ignore` in `/etc/systemd/logind.conf` — systemd does not act on the power button, leaving it free for userspace handling.
|
||||
- **evtest**: Available at `/usr/bin/evtest` for debugging input events.
|
||||
|
||||
## Partition Layout
|
||||
|
||||
| Device | Mount point | Filesystem | Contents |
|
||||
| :--------------- | :------------ | :--------- | :---------------------- |
|
||||
| `/dev/mmcblk0p5` | `/` | ext4 | Root filesystem |
|
||||
| `/dev/mmcblk0p6` | `/mnt/vendor` | ext4 | Firmware, apps, scripts |
|
||||
| `/dev/mmcblk0p7` | `/mnt/data` | ext4 | User data |
|
||||
| `/dev/mmcblk0p8` | `/mnt/mmc` | vfat | ROMs |
|
||||
| SD card slot | `/mnt/sdcard` | (varies) | External storage |
|
||||
|
||||
## Boot Chain
|
||||
|
||||
The device uses systemd but boots its UI through a SysV init script that systemd auto-wraps via `systemd-sysv-generator`. The chain is intentionally layered: `launcher.sh` runs before partitions are mounted (hardware setup), `loadapp.sh` runs after mounts (filesystem/network setup + app restart loop), and `dmenu_ln` is the app dispatcher.
|
||||
|
||||
```
|
||||
systemd (graphical.target)
|
||||
└─ launcher.service (SysV → auto-generated by systemd-sysv-generator)
|
||||
└─ /etc/init.d/launcher.sh start
|
||||
├── mounts /mnt/vendor
|
||||
├── starts brightCtrl.bin (backlight daemon) &
|
||||
├── starts cexpert &
|
||||
└── starts loadapp.sh &
|
||||
├── resizes root partition (first boot only)
|
||||
├── mounts /mnt/data, /mnt/mmc
|
||||
├── charging mode: if boot_mode==1 → charger UI → poweroff
|
||||
├── normal mode: starts NetworkManager, bluetooth, wifi
|
||||
├── syncs RTC, loads kernel modules, mounts SD card
|
||||
├── runs /mnt/mod/ctrl/autostart (if exists)
|
||||
└── RESTART LOOP: runs /mnt/vendor/ctrl/dmenu_ln repeatedly
|
||||
└─ /mnt/vendor/ctrl/dmenu_ln
|
||||
├── selects binary (default: dmenu.bin)
|
||||
├── config overrides: muos.ini → muos.bin, vpRun.ini → ...
|
||||
├── runs binary via app_scheduling()
|
||||
└── on exit: executes /tmp/.next (app dispatch for menu)
|
||||
```
|
||||
|
||||
### Why the indirection?
|
||||
|
||||
- **`launcher.sh`** — Pre-mount hardware setup: mounts `/mnt/vendor`, starts backlight and system daemons. Runs as a SysV init script so it integrates with systemd's boot ordering.
|
||||
- **`loadapp.sh`** — Post-mount filesystem/network setup: mounts data partitions, handles charging mode, starts networking, then enters the restart loop. Runs in the background (`&`) so `launcher.sh` can return.
|
||||
- **`dmenu_ln`** — App dispatcher: selects which binary to run based on config files, wraps execution in `app_scheduling()` (which sleeps 30s on crash before retrying), and supports `/tmp/.next` for menu-driven app switching.
|
||||
|
||||
### `app_scheduling` behavior
|
||||
|
||||
The `app_scheduling` function in `dmenu_ln` runs the selected binary. If it exits with a non-zero status, it sleeps 30 seconds before returning, which prevents crash loops from consuming all CPU. The outer `while true` loop in `loadapp.sh` then re-invokes `dmenu_ln`, restarting the application.
|
||||
|
||||
## Framebuffer
|
||||
|
||||
| Property | Value |
|
||||
| :------------- | :------------------- |
|
||||
| Device | `/dev/fb0` |
|
||||
| Visible size | 640x480 |
|
||||
| Virtual size | 640x960 (double-buf) |
|
||||
| Bits per pixel | 32 (BGRA) |
|
||||
| Stride | 2560 bytes (640 * 4) |
|
||||
|
||||
No standard framebuffer image tools (`fbv`, `fbi`, `psplash`) are installed. To display a PNG on the framebuffer, decode with Python3+PIL and write raw BGRA pixels to `/dev/fb0`.
|
||||
|
||||
## Backlight Control
|
||||
|
||||
There is no sysfs backlight interface (`/sys/class/backlight/` is empty) and the stock `brightCtrl.bin` daemon does not expose a usable control mechanism. The backlight is controlled via the Allwinner `/dev/disp` ioctl interface:
|
||||
|
||||
| Ioctl | Number | Description |
|
||||
| :------------------ | :------- | :----------------------------------- |
|
||||
| `DISP_SET_BRIGHTNESS` | `0x102` | Set backlight brightness (0–255) |
|
||||
| `DISP_GET_BRIGHTNESS` | `0x103` | Get current backlight brightness |
|
||||
|
||||
Both ioctls take an argument buffer of 4 unsigned longs (`struct { unsigned long args[4]; }`):
|
||||
- `args[0]` = screen index (always 0)
|
||||
- `args[1]` = brightness value (for SET; ignored for GET)
|
||||
- Return value (for GET) is in `args[0]` after the ioctl call
|
||||
|
||||
Setting brightness to 0 turns the screen off completely. The original value (typically ~50) can be restored to turn it back on. This is used by `rg35xx-screen-monitor.py` for idle timeout and power button toggle.
|
||||
|
||||
## Stock Firmware Assets
|
||||
|
||||
Shutdown-related assets in `/mnt/vendor/res1/shutdown/`:
|
||||
|
||||
| File | Description | Size |
|
||||
| :------------- | :------------------------------ | :------ |
|
||||
| `goodbye.png` | Shutdown screen (RGB, 640x480) | Matches display |
|
||||
| `lowpower.png` | Low battery warning | — |
|
||||
|
||||
The boot logo is at `/mnt/vendor/res1/boot/logo.png`.
|
||||
|
||||
## Deploying sdlamp2
|
||||
|
||||
### Overview
|
||||
|
||||
The `dmenu_ln` script already supports switching the startup binary via config files (e.g. `muos.ini` → `muos.bin`). We follow the same pattern to launch sdlamp2 instead of the default menu.
|
||||
|
||||
### Setup
|
||||
|
||||
1. **Copy the binary, wrapper, and screen monitor** to the device:
|
||||
|
||||
```sh
|
||||
scp build/sdlamp2 root@rg35xx:/mnt/vendor/bin/sdlamp2
|
||||
scp device/rg35xx/rg35xx-wrapper.sh root@rg35xx:/mnt/vendor/bin/rg35xx-wrapper.sh
|
||||
scp device/rg35xx/rg35xx-screen-monitor.py root@rg35xx:/mnt/vendor/bin/rg35xx-screen-monitor.py
|
||||
```
|
||||
|
||||
2. **Add the config check** to `/mnt/vendor/ctrl/dmenu_ln`. In the section where `CMD` overrides are checked (after the existing `muos.ini` / `vpRun.ini` checks, before the `app_scheduling` call), add:
|
||||
|
||||
```bash
|
||||
if [ -f "/mnt/vendor/sdlamp2.ini" ];then
|
||||
CMD="/mnt/vendor/bin/rg35xx-wrapper.sh"
|
||||
fi
|
||||
```
|
||||
|
||||
The wrapper script handles device-specific concerns (WiFi hotspot, power button monitoring) and launches sdlamp2 as its main foreground process. See `device/rg35xx/rg35xx-wrapper.sh` for details.
|
||||
|
||||
3. **Enable sdlamp2 on boot:**
|
||||
|
||||
```sh
|
||||
touch /mnt/vendor/sdlamp2.ini
|
||||
```
|
||||
|
||||
4. **Disable** (revert to normal menu):
|
||||
|
||||
```sh
|
||||
rm /mnt/vendor/sdlamp2.ini
|
||||
```
|
||||
|
||||
### What's preserved
|
||||
|
||||
Everything else in the boot chain continues to work:
|
||||
|
||||
- **Charging mode** — handled in `loadapp.sh` before the restart loop
|
||||
- **LED/backlight control** — `brightCtrl.bin` started by `launcher.sh`
|
||||
- **Clean shutdown** — sdlamp2 handles SIGTERM/SIGINT, saving position and volume before exit. The wrapper displays the stock `goodbye.png` on the framebuffer and calls `poweroff`
|
||||
- **Restart on exit** — if sdlamp2 exits cleanly (status 0), the restart loop in `loadapp.sh` re-launches it immediately
|
||||
- **Crash recovery** — if sdlamp2 crashes (non-zero exit), `app_scheduling` sleeps 30s then the loop retries
|
||||
- **Easy revert** — removing `sdlamp2.ini` restores the stock menu on next boot
|
||||
@@ -7,7 +7,7 @@
|
||||
| Version | 1.0 |
|
||||
| Status | Draft |
|
||||
| Created | 2026-02-10 |
|
||||
| Updated | 2026-02-10 |
|
||||
| Updated | 2026-02-15 |
|
||||
|
||||
## 1. Purpose
|
||||
|
||||
@@ -30,7 +30,7 @@ This document specifies the functional requirements for an SDL2 based media play
|
||||
## 4. Design principles
|
||||
|
||||
- Version control (git) must be used
|
||||
- Compilation should be performed by a simple shell script or batch file, not a complicated build system like make or cmake
|
||||
- Compilation should be performed by a simple Makefile supporting both native and cross-compilation
|
||||
- C source code files should be formatted using "Google" style with an additional change of `ColumnLimit` set to 100
|
||||
- Less is more, minimize dependencies, avoid pulling in extra libraries, always talk through with owner first
|
||||
- Keep it simple, apply Casey Muratori's `semantic compression` principles, don't refactor too soon or write code that's too clever for its own good
|
||||
@@ -43,6 +43,101 @@ This document specifies the functional requirements for an SDL2 based media play
|
||||
|
||||
## 6. Changelog
|
||||
|
||||
### 2026-02-15 — Transparent controls spritesheet support
|
||||
|
||||
- **Alpha blending on controls texture**: `SDL_SetTextureBlendMode(controls_texture, SDL_BLENDMODE_BLEND)` enables alpha transparency for the controls spritesheet. Sprite icons now float cleanly on any background color instead of showing white cell backgrounds.
|
||||
- **Transparent skin template**: `gen_skin_template.py` now generates cells with transparent backgrounds (RGBA) instead of white. Gutters use bright magenta (`#FF00FF`) so they're clearly distinguishable from transparent content areas.
|
||||
|
||||
### 2026-02-14 — Skin template system and device script reorganization
|
||||
|
||||
- **Skin template generator**: New `tools/gen_skin_template.py` (requires Pillow) generates a 642x420 PNG template showing the sprite grid layout with labeled gutters. Skin creators can draw over the white 200x200 cells; the 20px gray gutters (never rendered by the app) identify each cell's purpose.
|
||||
- **Separate Prev sprite**: `prev_sprite` now uses the bottom-center cell `{220, 220}` instead of sharing the bottom-right cell with `next_sprite`. This gives Prev and Next distinct sprites in the spritesheet.
|
||||
- **Device scripts moved**: `rg35xx-wrapper.sh` and `rg35xx-screen-monitor.py` moved from `tools/` to `device/rg35xx/`, separating device-specific scripts from dev tools.
|
||||
|
||||
### 2026-02-14 — Softer background, remove panel divider
|
||||
|
||||
- **Background color**: Changed from white (`#FFFFFF`) to a medium gray (`#979797`) for a gentler appearance.
|
||||
- **Remove divider**: Removed the vertical separator line between the controls panel and the album art.
|
||||
|
||||
### 2026-02-14 — Fix residual audio on cassette switch
|
||||
|
||||
- **Clear device queue on switch**: `switch_file()` now calls `SDL_ClearQueuedAudio()` after pausing the audio device, preventing a brief snippet of the previous cassette from playing when the new one starts.
|
||||
|
||||
### 2026-02-14 — Split-screen layout with artwork focus
|
||||
|
||||
- **Vertical left panel**: Transport controls (Prev, Rewind, Play/Stop, FF, Next) are stacked vertically in a 200px-wide left panel. Play/Stop is slightly larger (72x72) at the vertical center; other buttons are 56x56.
|
||||
- **Full-height artwork**: Album art now fills the right panel (420x460 max bounds, centered at x=420, y=240), giving cassette covers nearly the full screen height instead of being constrained to the upper portion.
|
||||
- **Vertical navigation**: D-pad UP/DOWN (and keyboard arrows) now navigate between buttons vertically instead of LEFT/RIGHT horizontally, matching the new stacked layout.
|
||||
- **Dedicated volume controls**: Volume is no longer a focusable UI element. Adjusted via `+`/`-` keys, `SDLK_VOLUMEUP`/`SDLK_VOLUMEDOWN`, or controller shoulder buttons (L1/R1).
|
||||
- **Sprite scaling quality**: Linear filtering (`SDL_HINT_RENDER_SCALE_QUALITY`) enabled for smoother downscaling of 200x200 sprite source to 56x56 button destinations.
|
||||
- **No-art placeholder**: When a file has no embedded album art, a circle sprite from the spritesheet is rendered as a placeholder in the right panel.
|
||||
- **Thin progress bar**: Progress bar moved to the bottom of the left panel (160x4px) as a subtle position indicator.
|
||||
|
||||
### 2026-02-13 — Fix power button screen toggle regression
|
||||
|
||||
- **Power button screen off stays off**: Fixed regression from fbd32d7 where short-pressing the power button to turn off the screen would instantly turn it back on. The generic wake logic (`any_activity`) was being triggered by power button events themselves. Moved `any_activity = True` below the power button handler's `continue` so power events are handled exclusively by the power button handler and don't trigger the wake path.
|
||||
|
||||
### 2026-02-13 — Screen wake fixes and idle auto-shutdown
|
||||
|
||||
- **D-pad wakes screen**: The screen monitor now wakes on any input event type (EV_ABS, EV_KEY, etc.), not just EV_KEY. This fixes d-pad presses (which generate EV_ABS hat events) not waking the screen.
|
||||
- **Wake button doesn't act in app**: Uses EVIOCGRAB to take exclusive access to input devices while the screen is off. SDL in sdlamp2 receives no events while grabbed, so the button press that wakes the screen doesn't also trigger play/stop or switch cassettes. Grabs are released when the screen turns back on.
|
||||
- **Idle auto-shutdown**: After 10 minutes of no input AND no audio playback, the device auto-shuts down to save battery. Playback detection works by monitoring the audio file's read position via `/proc/<pid>/fdinfo/` — if the file offset is advancing, audio is being decoded. The timer resets on any input event or any detected playback activity.
|
||||
- **Goodbye screen on auto-shutdown**: The wrapper now restores backlight brightness via `/dev/disp` `DISP_SET_BRIGHTNESS` ioctl before writing `goodbye.png` to `/dev/fb0`. Previously the screen monitor's SIGTERM handler restored brightness, but the Allwinner driver resets it to 0 when the fd is closed, so the goodbye screen was never visible on idle auto-shutdown.
|
||||
|
||||
### 2026-02-13 — Remember last cassette and pause on switch
|
||||
|
||||
- **Remember last cassette**: The current cassette filename is saved to `last_cassette.txt` in the audio directory on every file switch. On startup, the player resumes the last-loaded cassette instead of always starting with the first file. Falls back to the first file if the saved file is missing or not found.
|
||||
- **Pause on cassette switch**: Switching cassettes (prev/next) now always lands in a paused state, even if the player was playing. This avoids the jarring effect of immediately resuming from a saved position in a different cassette.
|
||||
|
||||
### 2026-02-13 — Fix power button shutdown regression
|
||||
|
||||
- **Consolidated input handling**: Power button long-press shutdown is now handled by `rg35xx-screen-monitor.py` instead of a separate `evtest`-based monitor in the wrapper. Both monitors were reading `/dev/input/event0` simultaneously, causing the `evtest` parser to miss power button events on the device's Linux 4.9 kernel.
|
||||
- **Timer-based long press**: The screen monitor uses a dynamic `select()` timeout to detect the 3-second hold threshold while the button is still held, rather than waiting for release. On long press, it touches `/tmp/.sdlamp2_shutdown` and sends SIGTERM to sdlamp2.
|
||||
- **Removed evtest dependency**: The `monitor_power_button()` function and `evtest` pipe are removed from `rg35xx-wrapper.sh`. The screen monitor accepts the sdlamp2 PID as a command-line argument and launches after sdlamp2.
|
||||
|
||||
### 2026-02-13 — Screen idle timeout and power button toggle
|
||||
|
||||
- **Screen idle timeout**: New Python screen monitor (`rg35xx-screen-monitor.py`) turns off the display after 15 seconds of no input on any device. Audio continues playing. Any button press wakes the screen.
|
||||
- **Power button screen toggle**: A short press (<2s) of the power button toggles the screen on/off. Long press (3s+) still triggers shutdown via the existing wrapper logic.
|
||||
- **Allwinner /dev/disp backlight**: Uses `SET_BRIGHTNESS` / `GET_BRIGHTNESS` ioctls on `/dev/disp` since the device has no sysfs backlight interface. Brightness is restored on SIGTERM so the shutdown screen remains visible.
|
||||
- **Wrapper integration**: `rg35xx-wrapper.sh` launches the screen monitor alongside sdlamp2 and kills it during cleanup.
|
||||
|
||||
### 2026-02-13 — Hold-to-shutdown with stock shutdown screen
|
||||
|
||||
- **Hold-to-shutdown**: The power button monitor in `rg35xx-wrapper.sh` requires a 3-second hold before triggering shutdown. A quick tap does nothing — a background timer subshell starts on press and is cancelled on release. If held past 3 seconds, sends SIGTERM to sdlamp2 then triggers shutdown.
|
||||
- **Stock shutdown screen**: The wrapper displays the stock firmware's `goodbye.png` (`/mnt/vendor/res1/shutdown/goodbye.png`) by decoding it with Python3+PIL and writing raw BGRA pixels to `/dev/fb0`. This reuses the same shutdown image that `dmenu.bin` shows, keeping the experience consistent. The shutdown display is handled entirely in the wrapper — sdlamp2 just exits cleanly on SIGTERM.
|
||||
- **Restart loop blocked**: The wrapper uses a `/tmp/.sdlamp2_shutdown` flag file to distinguish shutdown from normal exit. On shutdown, the wrapper calls `poweroff` and blocks (sleep 30), preventing `loadapp.sh`'s restart loop from relaunching `dmenu_ln` and overwriting the shutdown screen.
|
||||
|
||||
### 2026-02-13 — Clean shutdown on SIGTERM/SIGINT
|
||||
|
||||
- **Signal handling**: sdlamp2 now catches SIGTERM and SIGINT via a `sig_atomic_t` flag checked in the main loop. On signal, the existing cleanup path runs (save position, save volume, close decoder, SDL_Quit) instead of being killed instantly. This ensures position is saved when the system shuts down or the process is terminated by a wrapper script.
|
||||
|
||||
### 2026-02-13 — Combine Play/Stop, add Previous Cassette button
|
||||
|
||||
- **Play/Stop combined**: The separate Stop and Play buttons are merged into a single toggle button that shows the play icon (▶) when paused and the stop icon (■) when playing.
|
||||
- **Previous Cassette**: A new "Previous Cassette" button is added at the left of the transport controls, mirroring "Next Cassette". Wraps from the first tape to the last.
|
||||
- **New layout**: `[Volume] [Prev] [Rewind] [Play/Stop] [FF] [Next]` — same 6 focusable elements, same positions.
|
||||
|
||||
### 2026-02-13 — Start paused, fix next-tape autoplay, quiet joystick log
|
||||
|
||||
- **Start paused**: The player no longer autoplays on startup; it opens the last file at the saved position but waits for the user to press Play.
|
||||
- **Next tape respects pause state**: `switch_file()` no longer forces playback. Pressing "next tape" while paused switches the file and stays paused; pressing it while playing switches and continues playing.
|
||||
- **Joystick log behind --debug**: The "Joystick: ..." message is now only printed when `--debug` is passed.
|
||||
|
||||
### 2026-02-13 — arm64 Docker build container
|
||||
|
||||
- **New build container**: Replaced the broken Buildroot cross-compilation toolchain (`docker/`) with an arm64 Ubuntu 22.04 Docker container (`docker-arm64/`). Runs via QEMU user-mode emulation on x86 hosts and matches the target device exactly (same distro, same glibc, same library versions). The project's native `make` works as-is inside the container — no cross-compilation flags needed.
|
||||
|
||||
### 2026-02-13 — Replace build scripts with Makefile
|
||||
|
||||
- **Makefile**: Replaced `build.sh` (macOS) and `build_aarch64.sh` (Linux ARM) with a single `Makefile`. Native builds use `pkg-config` to resolve SDL2 and FFmpeg flags; cross-compilation uses `CROSS_COMPILE` and `PREFIX` environment variables set by the Docker/Buildroot toolchain.
|
||||
|
||||
### 2026-02-13 — Raw joystick fallback for non-standard controllers
|
||||
|
||||
- **Joystick fallback**: When no SDL GameController mapping exists for a connected device, the joystick is now opened directly via `SDL_JoystickOpen()` as a fallback. This fixes d-pad and face button input on devices like the Anbernic retro handheld whose GUID is not in SDL's GameController database.
|
||||
- **Hat/button handling**: `SDL_JOYHATMOTION` events drive d-pad navigation (left/right to move focus, up/down for volume), and `SDL_JOYBUTTONDOWN` button 0 (BTN_SOUTH / A) activates the focused button.
|
||||
- **Hot-unplug**: Raw joystick is properly closed on `SDL_JOYDEVICEREMOVED`.
|
||||
|
||||
### 2026-02-13 — Embed controls spritesheet into binary
|
||||
|
||||
- **Embedded asset**: `controls.png` is now compiled into the binary as a C byte array (`src/controls_png.h`), eliminating the requirement to run from the `build/` directory.
|
||||
|
||||
2771
src/controls_png.h
2771
src/controls_png.h
File diff suppressed because it is too large
Load Diff
219
src/sdlamp2.c
219
src/sdlamp2.c
@@ -5,6 +5,7 @@
|
||||
#include <libswresample/swresample.h>
|
||||
|
||||
#include <dirent.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -38,6 +39,15 @@ typedef struct {
|
||||
int art_height;
|
||||
} Decoder;
|
||||
|
||||
/* --- Signal handling --- */
|
||||
|
||||
static volatile sig_atomic_t got_signal = 0;
|
||||
|
||||
static void signal_handler(int sig) {
|
||||
(void)sig;
|
||||
got_signal = 1;
|
||||
}
|
||||
|
||||
/* --- Globals --- */
|
||||
|
||||
static SDL_Window* window = NULL;
|
||||
@@ -49,24 +59,24 @@ static SDL_bool paused = SDL_TRUE;
|
||||
static Decoder decoder = {0};
|
||||
|
||||
static char audio_dir[512] = ".";
|
||||
static char current_file[512] = "";
|
||||
static char current_file[256] = "";
|
||||
static char audio_files[MAX_FILES][256];
|
||||
static int num_audio_files = 0;
|
||||
static int current_file_index = 0;
|
||||
|
||||
/* --- Focus / navigation --- */
|
||||
|
||||
#define FOCUS_VOLUME 0
|
||||
#define FOCUS_PREV 0
|
||||
#define FOCUS_REWIND 1
|
||||
#define FOCUS_STOP 2
|
||||
#define FOCUS_PLAY 3
|
||||
#define FOCUS_FF 4
|
||||
#define FOCUS_NEXT 5
|
||||
#define FOCUS_COUNT 6
|
||||
#define FOCUS_PLAYSTOP 2
|
||||
#define FOCUS_FF 3
|
||||
#define FOCUS_NEXT 4
|
||||
#define FOCUS_COUNT 5
|
||||
|
||||
static float volume = 0.5f;
|
||||
static int focus_index = FOCUS_PLAY;
|
||||
static int focus_index = FOCUS_PLAYSTOP;
|
||||
static SDL_GameController* controller = NULL;
|
||||
static SDL_Joystick* joystick = NULL;
|
||||
static int debug_mode = 0;
|
||||
|
||||
/* --- Utility --- */
|
||||
@@ -110,8 +120,7 @@ static void save_position(const char* filename, double seconds) {
|
||||
snprintf(lines[num_lines], sizeof(lines[0]), "%s\t%.2f", filename, seconds);
|
||||
found = SDL_TRUE;
|
||||
} else {
|
||||
strncpy(lines[num_lines], line, sizeof(lines[0]) - 1);
|
||||
lines[num_lines][sizeof(lines[0]) - 1] = '\0';
|
||||
snprintf(lines[num_lines], sizeof(lines[0]), "%s", line);
|
||||
}
|
||||
num_lines++;
|
||||
}
|
||||
@@ -187,6 +196,37 @@ static void adjust_volume(float delta) {
|
||||
save_volume();
|
||||
}
|
||||
|
||||
/* --- Last cassette persistence --- */
|
||||
|
||||
static void save_last_cassette(const char* filename) {
|
||||
char path[1024];
|
||||
snprintf(path, sizeof(path), "%s/last_cassette.txt", audio_dir);
|
||||
FILE* f = fopen(path, "w");
|
||||
if (f) {
|
||||
fprintf(f, "%s\n", filename);
|
||||
fclose(f);
|
||||
}
|
||||
}
|
||||
|
||||
static int load_last_cassette(void) {
|
||||
char path[1024];
|
||||
snprintf(path, sizeof(path), "%s/last_cassette.txt", audio_dir);
|
||||
FILE* f = fopen(path, "r");
|
||||
if (!f) return 0;
|
||||
char name[256];
|
||||
if (!fgets(name, sizeof(name), f)) {
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
fclose(f);
|
||||
char* nl = strchr(name, '\n');
|
||||
if (nl) *nl = '\0';
|
||||
for (int i = 0; i < num_audio_files; i++) {
|
||||
if (strcmp(audio_files[i], name) == 0) return i;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* --- File scanning --- */
|
||||
|
||||
static int has_audio_extension(const char* name) {
|
||||
@@ -209,8 +249,7 @@ static void scan_audio_files(const char* dir) {
|
||||
while ((entry = readdir(d)) != NULL && num_audio_files < MAX_FILES) {
|
||||
if (entry->d_name[0] == '.') continue;
|
||||
if (has_audio_extension(entry->d_name)) {
|
||||
strncpy(audio_files[num_audio_files], entry->d_name, 255);
|
||||
audio_files[num_audio_files][255] = '\0';
|
||||
snprintf(audio_files[num_audio_files], sizeof(audio_files[0]), "%s", entry->d_name);
|
||||
num_audio_files++;
|
||||
}
|
||||
}
|
||||
@@ -475,12 +514,15 @@ static void switch_file(int index) {
|
||||
decoder_seek(pos);
|
||||
}
|
||||
|
||||
save_last_cassette(current_file);
|
||||
|
||||
char title[768];
|
||||
snprintf(title, sizeof(title), "SDLamp2 - %s", current_file);
|
||||
SDL_SetWindowTitle(window, title);
|
||||
|
||||
paused = SDL_FALSE;
|
||||
SDL_PauseAudioDevice(audio_device, 0);
|
||||
paused = SDL_TRUE;
|
||||
SDL_PauseAudioDevice(audio_device, 1);
|
||||
SDL_ClearQueuedAudio(audio_device);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -496,13 +538,22 @@ static void switch_file(int index) {
|
||||
|
||||
static void activate_focused_button(void) {
|
||||
switch (focus_index) {
|
||||
case FOCUS_PREV:
|
||||
if (num_audio_files > 0) {
|
||||
int prev = (current_file_index - 1 + num_audio_files) % num_audio_files;
|
||||
switch_file(prev);
|
||||
}
|
||||
break;
|
||||
case FOCUS_REWIND: {
|
||||
double pos = get_current_seconds() - SEEK_SECONDS;
|
||||
decoder_seek(pos < 0.0 ? 0.0 : pos);
|
||||
break;
|
||||
}
|
||||
case FOCUS_STOP:
|
||||
if (!paused) {
|
||||
case FOCUS_PLAYSTOP:
|
||||
if (paused && num_audio_files > 0) {
|
||||
paused = SDL_FALSE;
|
||||
SDL_PauseAudioDevice(audio_device, 0);
|
||||
} else if (!paused) {
|
||||
paused = SDL_TRUE;
|
||||
SDL_PauseAudioDevice(audio_device, 1);
|
||||
if (current_file[0]) {
|
||||
@@ -510,12 +561,6 @@ static void activate_focused_button(void) {
|
||||
}
|
||||
}
|
||||
break;
|
||||
case FOCUS_PLAY:
|
||||
if (paused && num_audio_files > 0) {
|
||||
paused = SDL_FALSE;
|
||||
SDL_PauseAudioDevice(audio_device, 0);
|
||||
}
|
||||
break;
|
||||
case FOCUS_FF: {
|
||||
double pos = get_current_seconds() + SEEK_SECONDS;
|
||||
double dur = get_duration_seconds();
|
||||
@@ -551,29 +596,28 @@ int main(int argc, char** argv) {
|
||||
audio_dir[sizeof(audio_dir) - 1] = '\0';
|
||||
}
|
||||
|
||||
/* Volume slider (left of buttons) */
|
||||
const SDL_Rect volume_bg = {25, 390, 30, 80};
|
||||
|
||||
/* Button positions (bottom of window, centered) */
|
||||
const SDL_Rect rewind_btn = {80, 390, 80, 80};
|
||||
const SDL_Rect stop_btn = {180, 390, 80, 80};
|
||||
const SDL_Rect play_btn = {280, 390, 80, 80};
|
||||
const SDL_Rect ff_btn = {380, 390, 80, 80};
|
||||
const SDL_Rect next_btn = {480, 390, 80, 80};
|
||||
/* Left panel: buttons stacked vertically, centered at x=100 */
|
||||
const SDL_Rect prev_btn = {72, 34, 56, 56};
|
||||
const SDL_Rect rewind_btn = {72, 120, 56, 56};
|
||||
const SDL_Rect playstop_btn = {64, 206, 72, 72};
|
||||
const SDL_Rect ff_btn = {72, 308, 56, 56};
|
||||
const SDL_Rect next_btn = {72, 394, 56, 56};
|
||||
|
||||
/* Array of focusable rects indexed by FOCUS_* constants */
|
||||
const SDL_Rect* focus_rects[FOCUS_COUNT] = {&volume_bg, &rewind_btn, &stop_btn,
|
||||
&play_btn, &ff_btn, &next_btn};
|
||||
const SDL_Rect* focus_rects[FOCUS_COUNT] = {&prev_btn, &rewind_btn, &playstop_btn, &ff_btn,
|
||||
&next_btn};
|
||||
|
||||
/* Sprite sheet source rects */
|
||||
const SDL_Rect rewind_sprite = {0, 0, 200, 200};
|
||||
const SDL_Rect play_sprite = {220, 0, 200, 200};
|
||||
const SDL_Rect ff_sprite = {440, 0, 200, 200};
|
||||
const SDL_Rect stop_sprite = {0, 220, 200, 200};
|
||||
const SDL_Rect prev_sprite = {220, 220, 200, 200};
|
||||
const SDL_Rect next_sprite = {440, 220, 200, 200};
|
||||
const SDL_Rect circle_sprite = {440, 220, 200, 200}; /* placeholder for no-art */
|
||||
|
||||
/* Progress bar area */
|
||||
const SDL_Rect progress_bg = {20, 360, 600, 15};
|
||||
/* Progress bar — thin bar at bottom of left panel */
|
||||
const SDL_Rect progress_bg = {20, 466, 160, 4};
|
||||
|
||||
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_GAMECONTROLLER) != 0) {
|
||||
panic_and_abort("SDL_Init failed!", SDL_GetError());
|
||||
@@ -627,6 +671,18 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!controller) {
|
||||
for (int i = 0; i < num_joy; i++) {
|
||||
joystick = SDL_JoystickOpen(i);
|
||||
if (joystick) {
|
||||
if (debug_mode) printf("Joystick: %s\n", SDL_JoystickName(joystick));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1");
|
||||
|
||||
SDL_Surface* controls_surface = IMG_Load("controls.png");
|
||||
if (!controls_surface) {
|
||||
SDL_RWops* rw = SDL_RWFromConstMem(controls_png_data, controls_png_size);
|
||||
@@ -643,12 +699,17 @@ int main(int argc, char** argv) {
|
||||
if (!controls_texture) {
|
||||
panic_and_abort("Could not create controls texture!", SDL_GetError());
|
||||
}
|
||||
SDL_SetTextureBlendMode(controls_texture, SDL_BLENDMODE_BLEND);
|
||||
|
||||
/* Handle SIGTERM/SIGINT for clean shutdown (save position before exit) */
|
||||
signal(SIGTERM, signal_handler);
|
||||
signal(SIGINT, signal_handler);
|
||||
|
||||
/* Scan directory and open first file */
|
||||
scan_audio_files(audio_dir);
|
||||
volume = load_volume();
|
||||
if (num_audio_files > 0) {
|
||||
switch_file(0);
|
||||
switch_file(load_last_cassette());
|
||||
} else {
|
||||
SDL_SetWindowTitle(window, "SDLamp2 - No audio files found");
|
||||
}
|
||||
@@ -657,6 +718,10 @@ int main(int argc, char** argv) {
|
||||
SDL_Event e;
|
||||
|
||||
while (running) {
|
||||
if (got_signal) {
|
||||
running = SDL_FALSE;
|
||||
}
|
||||
|
||||
/* --- Event handling --- */
|
||||
while (SDL_PollEvent(&e)) {
|
||||
/* Debug: log all input-related events */
|
||||
@@ -724,17 +789,20 @@ int main(int argc, char** argv) {
|
||||
|
||||
case SDL_KEYDOWN:
|
||||
switch (e.key.keysym.sym) {
|
||||
case SDLK_LEFT:
|
||||
case SDLK_UP:
|
||||
focus_index = (focus_index - 1 + FOCUS_COUNT) % FOCUS_COUNT;
|
||||
break;
|
||||
case SDLK_RIGHT:
|
||||
case SDLK_DOWN:
|
||||
focus_index = (focus_index + 1) % FOCUS_COUNT;
|
||||
break;
|
||||
case SDLK_UP:
|
||||
if (focus_index == FOCUS_VOLUME) adjust_volume(0.05f);
|
||||
case SDLK_EQUALS:
|
||||
case SDLK_PLUS:
|
||||
case SDLK_VOLUMEUP:
|
||||
adjust_volume(0.05f);
|
||||
break;
|
||||
case SDLK_DOWN:
|
||||
if (focus_index == FOCUS_VOLUME) adjust_volume(-0.05f);
|
||||
case SDLK_MINUS:
|
||||
case SDLK_VOLUMEDOWN:
|
||||
adjust_volume(-0.05f);
|
||||
break;
|
||||
case SDLK_RETURN:
|
||||
case SDLK_KP_ENTER:
|
||||
@@ -745,17 +813,17 @@ int main(int argc, char** argv) {
|
||||
|
||||
case SDL_CONTROLLERBUTTONDOWN:
|
||||
switch (e.cbutton.button) {
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_LEFT:
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_UP:
|
||||
focus_index = (focus_index - 1 + FOCUS_COUNT) % FOCUS_COUNT;
|
||||
break;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_RIGHT:
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_DOWN:
|
||||
focus_index = (focus_index + 1) % FOCUS_COUNT;
|
||||
break;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_UP:
|
||||
if (focus_index == FOCUS_VOLUME) adjust_volume(0.05f);
|
||||
case SDL_CONTROLLER_BUTTON_LEFTSHOULDER:
|
||||
adjust_volume(-0.05f);
|
||||
break;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_DOWN:
|
||||
if (focus_index == FOCUS_VOLUME) adjust_volume(-0.05f);
|
||||
case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER:
|
||||
adjust_volume(0.05f);
|
||||
break;
|
||||
case SDL_CONTROLLER_BUTTON_A:
|
||||
activate_focused_button();
|
||||
@@ -763,6 +831,22 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
break;
|
||||
|
||||
case SDL_JOYHATMOTION: {
|
||||
Uint8 hat = e.jhat.value;
|
||||
if (hat & SDL_HAT_UP) {
|
||||
focus_index = (focus_index - 1 + FOCUS_COUNT) % FOCUS_COUNT;
|
||||
} else if (hat & SDL_HAT_DOWN) {
|
||||
focus_index = (focus_index + 1) % FOCUS_COUNT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_JOYBUTTONDOWN:
|
||||
if (e.jbutton.button == 0) {
|
||||
activate_focused_button();
|
||||
}
|
||||
break;
|
||||
|
||||
case SDL_CONTROLLERDEVICEADDED:
|
||||
if (!controller && SDL_IsGameController(e.cdevice.which)) {
|
||||
controller = SDL_GameControllerOpen(e.cdevice.which);
|
||||
@@ -786,6 +870,13 @@ int main(int argc, char** argv) {
|
||||
printf("Controller removed\n");
|
||||
}
|
||||
break;
|
||||
|
||||
case SDL_JOYDEVICEREMOVED:
|
||||
if (joystick && e.jdevice.which == SDL_JoystickInstanceID(joystick)) {
|
||||
SDL_JoystickClose(joystick);
|
||||
joystick = NULL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -825,19 +916,23 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
|
||||
/* --- Rendering --- */
|
||||
SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0xFF, 0xFF);
|
||||
SDL_SetRenderDrawColor(renderer, 0x97, 0x97, 0x97, 0xFF);
|
||||
SDL_RenderClear(renderer);
|
||||
|
||||
/* Album art (centered, aspect-preserving, in upper area) */
|
||||
/* Album art (right panel, centered, aspect-preserving) */
|
||||
if (decoder.album_art && decoder.art_width > 0 && decoder.art_height > 0) {
|
||||
int max_w = 600, max_h = 340;
|
||||
int max_w = 420, max_h = 460;
|
||||
float scale_w = (float)max_w / decoder.art_width;
|
||||
float scale_h = (float)max_h / decoder.art_height;
|
||||
float scale = scale_w < scale_h ? scale_w : scale_h;
|
||||
int draw_w = (int)(decoder.art_width * scale);
|
||||
int draw_h = (int)(decoder.art_height * scale);
|
||||
SDL_Rect art_rect = {(640 - draw_w) / 2, (350 - draw_h) / 2, draw_w, draw_h};
|
||||
SDL_Rect art_rect = {420 - draw_w / 2, 240 - draw_h / 2, draw_w, draw_h};
|
||||
SDL_RenderCopy(renderer, decoder.album_art, NULL, &art_rect);
|
||||
} else {
|
||||
/* No-art placeholder: circle sprite scaled up in right panel */
|
||||
SDL_Rect placeholder = {420 - 100, 240 - 100, 200, 200};
|
||||
SDL_RenderCopy(renderer, controls_texture, &circle_sprite, &placeholder);
|
||||
}
|
||||
|
||||
/* Progress bar */
|
||||
@@ -855,28 +950,19 @@ int main(int argc, char** argv) {
|
||||
SDL_RenderFillRect(renderer, &fill);
|
||||
}
|
||||
|
||||
/* Volume slider */
|
||||
SDL_SetRenderDrawColor(renderer, 0xC0, 0xC0, 0xC0, 0xFF);
|
||||
SDL_RenderFillRect(renderer, &volume_bg);
|
||||
{
|
||||
int fill_h = (int)(volume_bg.h * volume);
|
||||
SDL_Rect vol_fill = {volume_bg.x, volume_bg.y + volume_bg.h - fill_h, volume_bg.w, fill_h};
|
||||
SDL_SetRenderDrawColor(renderer, 0x50, 0x50, 0x50, 0xFF);
|
||||
SDL_RenderFillRect(renderer, &vol_fill);
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
SDL_RenderCopy(renderer, controls_texture, &prev_sprite, &prev_btn);
|
||||
SDL_RenderCopy(renderer, controls_texture, &rewind_sprite, &rewind_btn);
|
||||
SDL_RenderCopy(renderer, controls_texture, &stop_sprite, &stop_btn);
|
||||
SDL_RenderCopy(renderer, controls_texture, &play_sprite, &play_btn);
|
||||
SDL_RenderCopy(renderer, controls_texture, paused ? &play_sprite : &stop_sprite,
|
||||
&playstop_btn);
|
||||
SDL_RenderCopy(renderer, controls_texture, &ff_sprite, &ff_btn);
|
||||
SDL_RenderCopy(renderer, controls_texture, &next_sprite, &next_btn);
|
||||
|
||||
/* Focus highlight — 3px blue border around focused element */
|
||||
/* Focus highlight — 3px red border around focused element */
|
||||
{
|
||||
const SDL_Rect r = *focus_rects[focus_index];
|
||||
const int t = 3;
|
||||
SDL_SetRenderDrawColor(renderer, 0x00, 0x80, 0xFF, 0xFF);
|
||||
SDL_SetRenderDrawColor(renderer, 0xFF, 0x00, 0x00, 0xFF);
|
||||
SDL_Rect top = {r.x - t, r.y - t, r.w + 2 * t, t};
|
||||
SDL_Rect bot = {r.x - t, r.y + r.h, r.w + 2 * t, t};
|
||||
SDL_Rect lft = {r.x - t, r.y, t, r.h};
|
||||
@@ -898,6 +984,7 @@ int main(int argc, char** argv) {
|
||||
decoder_close();
|
||||
SDL_FreeAudioStream(stream);
|
||||
SDL_DestroyTexture(controls_texture);
|
||||
if (joystick) SDL_JoystickClose(joystick);
|
||||
if (controller) SDL_GameControllerClose(controller);
|
||||
SDL_CloseAudioDevice(audio_device);
|
||||
SDL_DestroyRenderer(renderer);
|
||||
|
||||
96
tools/gen_skin_template.py
Normal file
96
tools/gen_skin_template.py
Normal file
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
gen_skin_template.py — Generate a skin template PNG for sdlamp2.
|
||||
|
||||
Creates a 642x420 PNG showing the sprite grid layout with labeled gutters.
|
||||
Each 200x200 cell is transparent (ready to draw on). The 20px gutters between
|
||||
cells are a bright magenta so they're clearly distinguishable from content areas.
|
||||
|
||||
Grid layout:
|
||||
Col 0 (0-199) Col 1 (220-419) Col 2 (440-639)
|
||||
Row 0: REWIND PLAY FF
|
||||
----gutter y=200-219 with labels----
|
||||
Row 1: STOP PREV NEXT
|
||||
|
||||
Two extra pixels on the right (640-641) are gutter fill to reach 642px width,
|
||||
matching the spritesheet dimensions.
|
||||
|
||||
Usage: python3 tools/gen_skin_template.py [output.png]
|
||||
|
||||
Requires Pillow.
|
||||
"""
|
||||
import sys
|
||||
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
CELL = 200
|
||||
GAP = 20
|
||||
COLS = 3
|
||||
ROWS = 2
|
||||
WIDTH = COLS * CELL + (COLS - 1) * GAP + 2 # 642
|
||||
HEIGHT = ROWS * CELL + (ROWS - 1) * GAP # 420
|
||||
|
||||
CELL_COLOR = (0, 0, 0, 0)
|
||||
GUTTER_COLOR = (255, 0, 255, 255)
|
||||
TEXT_COLOR = (255, 255, 255, 255)
|
||||
|
||||
LABELS = [
|
||||
["REWIND", "PLAY", "FF"],
|
||||
["STOP", "PREV", "NEXT"],
|
||||
]
|
||||
|
||||
|
||||
def cell_x(col):
|
||||
return col * (CELL + GAP)
|
||||
|
||||
|
||||
def cell_y(row):
|
||||
return row * (CELL + GAP)
|
||||
|
||||
|
||||
def main():
|
||||
output_path = sys.argv[1] if len(sys.argv) > 1 else "skin_template.png"
|
||||
|
||||
img = Image.new("RGBA", (WIDTH, HEIGHT), GUTTER_COLOR)
|
||||
draw = ImageDraw.Draw(img)
|
||||
|
||||
# Try to load a small font for labels
|
||||
try:
|
||||
font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 11)
|
||||
except OSError:
|
||||
try:
|
||||
font = ImageFont.truetype("/System/Library/Fonts/Helvetica.ttc", 11)
|
||||
except OSError:
|
||||
font = ImageFont.load_default()
|
||||
|
||||
# Draw white cells
|
||||
for row in range(ROWS):
|
||||
for col in range(COLS):
|
||||
x = cell_x(col)
|
||||
y = cell_y(row)
|
||||
draw.rectangle([x, y, x + CELL - 1, y + CELL - 1], fill=CELL_COLOR)
|
||||
|
||||
# Label the horizontal gutter (y = 200..219)
|
||||
gutter_y = CELL # 200
|
||||
for col in range(COLS):
|
||||
cx = cell_x(col) + CELL // 2
|
||||
|
||||
# Row 0 label above center of gutter
|
||||
label_0 = LABELS[0][col]
|
||||
bbox = draw.textbbox((0, 0), label_0, font=font)
|
||||
tw = bbox[2] - bbox[0]
|
||||
draw.text((cx - tw // 2, gutter_y + 1), label_0, fill=TEXT_COLOR, font=font)
|
||||
|
||||
# Row 1 label below center of gutter
|
||||
label_1 = LABELS[1][col]
|
||||
bbox = draw.textbbox((0, 0), label_1, font=font)
|
||||
tw = bbox[2] - bbox[0]
|
||||
th = bbox[3] - bbox[1]
|
||||
draw.text((cx - tw // 2, gutter_y + GAP - th - 2), label_1, fill=TEXT_COLOR, font=font)
|
||||
|
||||
img.save(output_path)
|
||||
print(f"Skin template saved to {output_path} ({WIDTH}x{HEIGHT})")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user