diff --git a/docs/rg35xx-plus.md b/docs/rg35xx-plus.md index a1816d6..6e90b0a 100644 --- a/docs/rg35xx-plus.md +++ b/docs/rg35xx-plus.md @@ -87,21 +87,22 @@ The `dmenu_ln` script already supports switching the startup binary via config f ### Setup -1. **Copy the binary** to the device: +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 ``` 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/sdlamp2 /mnt/sdcard/Music" + CMD="/mnt/vendor/bin/rg35xx-wrapper.sh" fi ``` - The unquoted `$CMD` in `app_scheduling` undergoes word splitting, so the path argument is passed correctly. + The wrapper script handles device-specific concerns (WiFi hotspot, power button monitoring) and launches sdlamp2 as its main foreground process. See `tools/rg35xx-wrapper.sh` for details. 3. **Enable sdlamp2 on boot:** @@ -121,7 +122,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** — system shutdown scripts unaffected +- **Clean shutdown** — sdlamp2 handles SIGTERM/SIGINT, saving position and volume before exit. The wrapper script can trigger `poweroff` after sdlamp2 exits - **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 096d0ec..d39f915 100644 --- a/docs/sdlamp2-fsd.md +++ b/docs/sdlamp2-fsd.md @@ -43,6 +43,10 @@ This document specifies the functional requirements for an SDL2 based media play ## 6. Changelog +### 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. diff --git a/src/sdlamp2.c b/src/sdlamp2.c index edb831c..21bc41a 100644 --- a/src/sdlamp2.c +++ b/src/sdlamp2.c @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -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; @@ -656,6 +666,10 @@ int main(int argc, char** argv) { panic_and_abort("Could not create controls texture!", SDL_GetError()); } + /* 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(); @@ -669,6 +683,8 @@ 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 */ diff --git a/tools/rg35xx-wrapper.sh b/tools/rg35xx-wrapper.sh new file mode 100755 index 0000000..dacd3d1 --- /dev/null +++ b/tools/rg35xx-wrapper.sh @@ -0,0 +1,63 @@ +#!/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. Start WiFi hotspot (AP mode for SSH access) +# 2. Launch sdlamp2 as the foreground process +# 3. Monitor power button and trigger clean shutdown +# +# 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" + +# --- WiFi hotspot --- +# TODO: Investigate how the stock menu starts AP mode. +# Likely candidates: hostapd, nmcli, or a vendor script in /mnt/vendor/ctrl/. +# Run these on the device to find out: +# grep -r 'hostapd\|hotspot\|ap_mode\|wifi' /mnt/vendor/ctrl/ +# systemctl list-units | grep -i net +# nmcli general status && nmcli connection show +# +# Placeholder — uncomment/replace once the mechanism is known: +# nmcli connection up hotspot 2>/dev/null || true + +# --- Power button monitor --- +# TODO: Investigate power button input device. +# Run on device: +# cat /proc/bus/input/devices (find the power button event device) +# evtest /dev/input/eventN (confirm KEY_POWER event code) +# cat /etc/systemd/logind.conf (check HandlePowerKey setting) +# +# Strategy: read key events from the power button input device in background. +# When KEY_POWER (code 116) is detected, send SIGTERM to sdlamp2 and poweroff. +# +# POWER_EVENT_DEV="/dev/input/eventN" # TBD: set after investigation +# +# monitor_power_button() { +# # evtest writes one line per event; filter for KEY_POWER press (value 1) +# 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 +# ;; +# esac +# done +# } +# monitor_power_button & +# MONITOR_PID=$! + +# --- Launch sdlamp2 --- +"$SDLAMP2" "$AUDIO_DIR" +SDLAMP2_EXIT=$? + +# --- Cleanup --- +# Kill the power button monitor if it's still running +# kill "$MONITOR_PID" 2>/dev/null + +exit "$SDLAMP2_EXIT"