diff --git a/docs/rg35xx-plus.md b/docs/rg35xx-plus.md index a0f11af..9d12487 100644 --- a/docs/rg35xx-plus.md +++ b/docs/rg35xx-plus.md @@ -22,14 +22,14 @@ Device-specific reference for the target hardware. For build instructions see [` 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 | +| 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. @@ -37,24 +37,24 @@ Shared libraries are already present — no need to bundle or build them. A nati 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 | +| 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 | +| 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 @@ -92,6 +92,29 @@ systemd (graphical.target) 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`. + +## 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 @@ -103,8 +126,8 @@ The `dmenu_ln` script already supports switching the startup binary via config f 1. **Copy the binary and wrapper** to the device: ```sh - scp build/sdlamp2 root@:/mnt/vendor/bin/sdlamp2 - scp tools/rg35xx-wrapper.sh root@:/mnt/vendor/bin/rg35xx-wrapper.sh + scp build/sdlamp2 root@rg35xx:/mnt/vendor/bin/sdlamp2 + scp tools/rg35xx-wrapper.sh root@rg35xx:/mnt/vendor/bin/rg35xx-wrapper.sh ``` 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: @@ -135,7 +158,7 @@ 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 script can trigger `poweroff` after sdlamp2 exits +- **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 diff --git a/docs/sdlamp2-fsd.md b/docs/sdlamp2-fsd.md index 587ba48..73ca995 100644 --- a/docs/sdlamp2-fsd.md +++ b/docs/sdlamp2-fsd.md @@ -43,9 +43,11 @@ This document specifies the functional requirements for an SDL2 based media play ## 6. Changelog -### 2026-02-13 — Power button monitor in device wrapper +### 2026-02-13 — Hold-to-shutdown with stock shutdown screen -- **Power button handling**: The `rg35xx-wrapper.sh` script now monitors `/dev/input/event0` (`axp2202-pek`) for `KEY_POWER` press events via `evtest`. On power button press, sends SIGTERM to sdlamp2 (which saves position and volume), waits 1 second, then calls `poweroff` for a clean shutdown. +- **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 diff --git a/src/sdlamp2.c b/src/sdlamp2.c index 21bc41a..a460624 100644 --- a/src/sdlamp2.c +++ b/src/sdlamp2.c @@ -683,7 +683,9 @@ int main(int argc, char** argv) { SDL_Event e; while (running) { - if (got_signal) running = SDL_FALSE; + if (got_signal) { + running = SDL_FALSE; + } /* --- Event handling --- */ while (SDL_PollEvent(&e)) { diff --git a/tools/rg35xx-wrapper.sh b/tools/rg35xx-wrapper.sh index 8c4d259..74df41f 100755 --- a/tools/rg35xx-wrapper.sh +++ b/tools/rg35xx-wrapper.sh @@ -5,7 +5,8 @@ # Launched by dmenu_ln instead of sdlamp2 directly. Handles device-specific # concerns that don't belong in the player binary: # 1. Monitor power button and trigger clean shutdown -# 2. Launch sdlamp2 as the main process +# 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 @@ -24,13 +25,21 @@ AUDIO_DIR="/mnt/sdcard/Music" POWER_EVENT_DEV="/dev/input/event0" monitor_power_button() { - # evtest writes one line per event; filter for KEY_POWER press (value 1) + POWER_TIMER_PID="" evtest "$POWER_EVENT_DEV" 2>/dev/null | while read -r line; do case "$line" in *"code 116"*"value 1"*) - kill -TERM "$SDLAMP2_PID" 2>/dev/null - sleep 1 - poweroff + # Power button pressed — start 3-second hold timer + ( trap 'exit 0' TERM; sleep 3; touch /tmp/.sdlamp2_shutdown; kill -TERM "$SDLAMP2_PID" 2>/dev/null ) & + POWER_TIMER_PID=$! + ;; + *"code 116"*"value 0"*) + # Power button released — cancel timer if still running + if [ -n "$POWER_TIMER_PID" ]; then + kill "$POWER_TIMER_PID" 2>/dev/null + wait "$POWER_TIMER_PID" 2>/dev/null + POWER_TIMER_PID="" + fi ;; esac done @@ -52,4 +61,23 @@ SDLAMP2_EXIT=$? # Kill the power button monitor if it's still running kill "$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 + # 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 " +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"