Compare commits
No commits in common. "06daec791e1dc9c281a95125000aa92deb7bd494" and "0f653d439520c47d7bfbd266eca8e108d342b0e8" have entirely different histories.
06daec791e
...
0f653d4395
@ -22,39 +22,26 @@ 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):
|
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 |
|
| Library | Soname | Notes |
|
||||||
| :------------ | :----------------------------- | :---------------------------------- |
|
|---------|--------|-------|
|
||||||
| SDL2 | `libSDL2-2.0.so.0.12.0` | SDL 2.0.12 |
|
| 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 |
|
| 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 |
|
| libavcodec | `libavcodec.so.58` | FFmpeg ~4.x |
|
||||||
| libavformat | `libavformat.so.58` | FFmpeg ~4.x |
|
| libavformat | `libavformat.so.58` | FFmpeg ~4.x |
|
||||||
| libavutil | `libavutil.so.56` | FFmpeg ~4.x |
|
| libavutil | `libavutil.so.56` | FFmpeg ~4.x |
|
||||||
| libswresample | `libswresample.so.3` | 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.
|
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
|
## Partition Layout
|
||||||
|
|
||||||
| Device | Mount point | Filesystem | Contents |
|
| Device | Mount point | Filesystem | Contents |
|
||||||
| :--------------- | :------------ | :--------- | :---------------------- |
|
|--------|-------------|------------|----------|
|
||||||
| `/dev/mmcblk0p5` | `/` | ext4 | Root filesystem |
|
| `/dev/mmcblk0p5` | `/` | ext4 | Root filesystem |
|
||||||
| `/dev/mmcblk0p6` | `/mnt/vendor` | ext4 | Firmware, apps, scripts |
|
| `/dev/mmcblk0p6` | `/mnt/vendor` | ext4 | Firmware, apps, scripts |
|
||||||
| `/dev/mmcblk0p7` | `/mnt/data` | ext4 | User data |
|
| `/dev/mmcblk0p7` | `/mnt/data` | ext4 | User data |
|
||||||
| `/dev/mmcblk0p8` | `/mnt/mmc` | vfat | ROMs |
|
| `/dev/mmcblk0p8` | `/mnt/mmc` | vfat | ROMs |
|
||||||
| SD card slot | `/mnt/sdcard` | (varies) | External storage |
|
| SD card slot | `/mnt/sdcard` | (varies) | External storage |
|
||||||
|
|
||||||
## Boot Chain
|
## Boot Chain
|
||||||
|
|
||||||
@ -92,29 +79,6 @@ 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.
|
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
|
## Deploying sdlamp2
|
||||||
|
|
||||||
### Overview
|
### Overview
|
||||||
@ -126,8 +90,8 @@ The `dmenu_ln` script already supports switching the startup binary via config f
|
|||||||
1. **Copy the binary and wrapper** to the device:
|
1. **Copy the binary and wrapper** to the device:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
scp build/sdlamp2 root@rg35xx:/mnt/vendor/bin/sdlamp2
|
scp build/sdlamp2 root@<device>:/mnt/vendor/bin/sdlamp2
|
||||||
scp tools/rg35xx-wrapper.sh root@rg35xx:/mnt/vendor/bin/rg35xx-wrapper.sh
|
scp tools/rg35xx-wrapper.sh root@<device>:/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:
|
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:
|
||||||
@ -158,7 +122,7 @@ Everything else in the boot chain continues to work:
|
|||||||
|
|
||||||
- **Charging mode** — handled in `loadapp.sh` before the restart loop
|
- **Charging mode** — handled in `loadapp.sh` before the restart loop
|
||||||
- **LED/backlight control** — `brightCtrl.bin` started by `launcher.sh`
|
- **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`
|
- **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
|
- **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
|
- **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
|
- **Easy revert** — removing `sdlamp2.ini` restores the stock menu on next boot
|
||||||
|
|||||||
@ -43,12 +43,6 @@ This document specifies the functional requirements for an SDL2 based media play
|
|||||||
|
|
||||||
## 6. Changelog
|
## 6. Changelog
|
||||||
|
|
||||||
### 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
|
### 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.
|
- **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.
|
||||||
|
|||||||
@ -683,9 +683,7 @@ int main(int argc, char** argv) {
|
|||||||
SDL_Event e;
|
SDL_Event e;
|
||||||
|
|
||||||
while (running) {
|
while (running) {
|
||||||
if (got_signal) {
|
if (got_signal) running = SDL_FALSE;
|
||||||
running = SDL_FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Event handling --- */
|
/* --- Event handling --- */
|
||||||
while (SDL_PollEvent(&e)) {
|
while (SDL_PollEvent(&e)) {
|
||||||
|
|||||||
@ -4,9 +4,9 @@
|
|||||||
#
|
#
|
||||||
# Launched by dmenu_ln instead of sdlamp2 directly. Handles device-specific
|
# Launched by dmenu_ln instead of sdlamp2 directly. Handles device-specific
|
||||||
# concerns that don't belong in the player binary:
|
# concerns that don't belong in the player binary:
|
||||||
# 1. Monitor power button and trigger clean shutdown
|
# 1. Start WiFi hotspot (AP mode for SSH access)
|
||||||
# 2. Display the stock firmware's shutdown screen (goodbye.png → /dev/fb0)
|
# 2. Launch sdlamp2 as the foreground process
|
||||||
# 3. Launch sdlamp2 as the main process
|
# 3. Monitor power button and trigger clean shutdown
|
||||||
#
|
#
|
||||||
# Install: copy to /mnt/vendor/bin/rg35xx-wrapper.sh
|
# Install: copy to /mnt/vendor/bin/rg35xx-wrapper.sh
|
||||||
# Config: set CMD in dmenu_ln to point here instead of sdlamp2 directly
|
# Config: set CMD in dmenu_ln to point here instead of sdlamp2 directly
|
||||||
@ -15,69 +15,49 @@ SDLAMP2="/mnt/vendor/bin/sdlamp2"
|
|||||||
AUDIO_DIR="/mnt/sdcard/Music"
|
AUDIO_DIR="/mnt/sdcard/Music"
|
||||||
|
|
||||||
# --- WiFi hotspot ---
|
# --- WiFi hotspot ---
|
||||||
# Deprioritized: connecting the device as a WiFi client to a shared network
|
# TODO: Investigate how the stock menu starts AP mode.
|
||||||
# works fine even when sdlamp2 replaces the stock menu. Hotspot/AP mode isn't
|
# Likely candidates: hostapd, nmcli, or a vendor script in /mnt/vendor/ctrl/.
|
||||||
# needed — SSH access works over the shared network.
|
# 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 ---
|
# --- Power button monitor ---
|
||||||
# axp2202-pek on /dev/input/event0 sends KEY_POWER (code 116).
|
# TODO: Investigate power button input device.
|
||||||
# logind has HandlePowerKey=ignore, so we handle it here.
|
# Run on device:
|
||||||
POWER_EVENT_DEV="/dev/input/event0"
|
# cat /proc/bus/input/devices (find the power button event device)
|
||||||
|
# evtest /dev/input/eventN (confirm KEY_POWER event code)
|
||||||
monitor_power_button() {
|
# cat /etc/systemd/logind.conf (check HandlePowerKey setting)
|
||||||
POWER_TIMER_PID=""
|
#
|
||||||
evtest "$POWER_EVENT_DEV" 2>/dev/null | while read -r line; do
|
# Strategy: read key events from the power button input device in background.
|
||||||
case "$line" in
|
# When KEY_POWER (code 116) is detected, send SIGTERM to sdlamp2 and poweroff.
|
||||||
*"code 116"*"value 1"*)
|
#
|
||||||
# Power button pressed — start 3-second hold timer
|
# POWER_EVENT_DEV="/dev/input/eventN" # TBD: set after investigation
|
||||||
( trap 'exit 0' TERM; sleep 3; touch /tmp/.sdlamp2_shutdown; kill -TERM "$SDLAMP2_PID" 2>/dev/null ) &
|
#
|
||||||
POWER_TIMER_PID=$!
|
# monitor_power_button() {
|
||||||
;;
|
# # evtest writes one line per event; filter for KEY_POWER press (value 1)
|
||||||
*"code 116"*"value 0"*)
|
# evtest "$POWER_EVENT_DEV" 2>/dev/null | while read -r line; do
|
||||||
# Power button released — cancel timer if still running
|
# case "$line" in
|
||||||
if [ -n "$POWER_TIMER_PID" ]; then
|
# *"code 116"*"value 1"*)
|
||||||
kill "$POWER_TIMER_PID" 2>/dev/null
|
# kill -TERM "$SDLAMP2_PID" 2>/dev/null
|
||||||
wait "$POWER_TIMER_PID" 2>/dev/null
|
# sleep 1
|
||||||
POWER_TIMER_PID=""
|
# poweroff
|
||||||
fi
|
# ;;
|
||||||
;;
|
# esac
|
||||||
esac
|
# done
|
||||||
done
|
# }
|
||||||
}
|
# monitor_power_button &
|
||||||
|
# MONITOR_PID=$!
|
||||||
|
|
||||||
# --- Launch sdlamp2 ---
|
# --- Launch sdlamp2 ---
|
||||||
# Run in background so we can capture PID for the power button monitor.
|
"$SDLAMP2" "$AUDIO_DIR"
|
||||||
"$SDLAMP2" "$AUDIO_DIR" &
|
|
||||||
SDLAMP2_PID=$!
|
|
||||||
|
|
||||||
monitor_power_button &
|
|
||||||
MONITOR_PID=$!
|
|
||||||
|
|
||||||
# Wait for sdlamp2 to finish (signal or normal exit).
|
|
||||||
wait "$SDLAMP2_PID"
|
|
||||||
SDLAMP2_EXIT=$?
|
SDLAMP2_EXIT=$?
|
||||||
|
|
||||||
# --- Cleanup ---
|
# --- Cleanup ---
|
||||||
# Kill the power button monitor if it's still running
|
# Kill the power button monitor if it's still running
|
||||||
kill "$MONITOR_PID" 2>/dev/null
|
# 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"
|
exit "$SDLAMP2_EXIT"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user