Compare commits
No commits in common. "8123b9a99f164e450d72b17b7d9100cef1c997c5" and "2142ed76299bb40dab9dddd6b3772e4532982520" have entirely different histories.
8123b9a99f
...
2142ed7629
@ -43,17 +43,6 @@ This document specifies the functional requirements for an SDL2 based media play
|
||||
|
||||
## 6. Changelog
|
||||
|
||||
### 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.
|
||||
|
||||
@ -197,37 +197,6 @@ 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) {
|
||||
@ -515,14 +484,11 @@ 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_TRUE;
|
||||
SDL_PauseAudioDevice(audio_device, 1);
|
||||
SDL_PauseAudioDevice(audio_device, paused);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -708,7 +674,7 @@ int main(int argc, char** argv) {
|
||||
scan_audio_files(audio_dir);
|
||||
volume = load_volume();
|
||||
if (num_audio_files > 0) {
|
||||
switch_file(load_last_cassette());
|
||||
switch_file(0);
|
||||
} else {
|
||||
SDL_SetWindowTitle(window, "SDLamp2 - No audio files found");
|
||||
}
|
||||
|
||||
@ -1,27 +1,23 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Screen idle timeout, power button toggle, and long-press shutdown for RG35XX Plus.
|
||||
Screen idle timeout and power button toggle 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 button press wakes the screen. A short power button press (<2s)
|
||||
toggles the screen on/off. A long press (3s+) triggers clean shutdown.
|
||||
toggles the screen on/off.
|
||||
|
||||
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
|
||||
POWER_SHORT_THRESHOLD = 2.0 # seconds — short press if released before this
|
||||
POWER_LONG_THRESHOLD = 3.0 # seconds — shutdown if held this long
|
||||
POWER_HOLD_THRESHOLD = 2.0 # seconds — short press if released before this
|
||||
|
||||
# Allwinner /dev/disp ioctl commands
|
||||
DISP_GET_BRIGHTNESS = 0x103
|
||||
@ -54,12 +50,6 @@ def set_brightness(disp_fd, value):
|
||||
|
||||
|
||||
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:
|
||||
@ -92,29 +82,14 @@ def main():
|
||||
os.close(disp_fd)
|
||||
sys.exit(1)
|
||||
|
||||
import select
|
||||
|
||||
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
|
||||
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:
|
||||
touch_and_shutdown(disp_fd, original_brightness, screen_on, sdlamp2_pid)
|
||||
return
|
||||
readable, _, _ = select.select(event_fds, [], [], IDLE_TIMEOUT)
|
||||
|
||||
if not readable:
|
||||
# Timeout with no input — turn off screen if idle
|
||||
if screen_on and power_press_time is None:
|
||||
# Timeout — no input for IDLE_TIMEOUT seconds
|
||||
if screen_on:
|
||||
set_brightness(disp_fd, 0)
|
||||
screen_on = False
|
||||
continue
|
||||
@ -143,7 +118,7 @@ def main():
|
||||
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:
|
||||
if hold_duration < POWER_HOLD_THRESHOLD:
|
||||
# Short press — toggle screen
|
||||
if screen_on:
|
||||
set_brightness(disp_fd, 0)
|
||||
@ -151,7 +126,6 @@ def main():
|
||||
else:
|
||||
set_brightness(disp_fd, original_brightness)
|
||||
screen_on = True
|
||||
# Between SHORT and LONG threshold: ignore (release before 3s)
|
||||
continue
|
||||
|
||||
# Any other key press — wake screen if off
|
||||
@ -160,20 +134,5 @@ def main():
|
||||
screen_on = True
|
||||
|
||||
|
||||
def touch_and_shutdown(disp_fd, original_brightness, screen_on, sdlamp2_pid):
|
||||
"""Signal sdlamp2 to exit and flag for shutdown."""
|
||||
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()
|
||||
|
||||
@ -4,9 +4,10 @@
|
||||
#
|
||||
# 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
|
||||
# 1. Monitor power button and trigger clean shutdown
|
||||
# 2. Display the stock firmware's shutdown screen (goodbye.png → /dev/fb0)
|
||||
# 3. Launch sdlamp2 as the main process
|
||||
# 3. Screen idle timeout (off after 15s) and power button screen toggle
|
||||
# 4. 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
|
||||
@ -20,24 +21,53 @@ SCREEN_MONITOR="/mnt/vendor/bin/rg35xx-screen-monitor.py"
|
||||
# works fine even when sdlamp2 replaces the stock menu. Hotspot/AP mode isn't
|
||||
# needed — SSH access works over the shared network.
|
||||
|
||||
# --- Power button monitor ---
|
||||
# axp2202-pek on /dev/input/event0 sends KEY_POWER (code 116).
|
||||
# logind has HandlePowerKey=ignore, so we handle it here.
|
||||
POWER_EVENT_DEV="/dev/input/event0"
|
||||
|
||||
monitor_power_button() {
|
||||
POWER_TIMER_PID=""
|
||||
evtest "$POWER_EVENT_DEV" 2>/dev/null | while read -r line; do
|
||||
case "$line" in
|
||||
*"code 116"*"value 1"*)
|
||||
# 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
|
||||
}
|
||||
|
||||
# --- Screen idle timeout + power button screen toggle ---
|
||||
python3 "$SCREEN_MONITOR" &
|
||||
SCREEN_MONITOR_PID=$!
|
||||
|
||||
# --- Launch sdlamp2 ---
|
||||
# Run in background so we can capture PID for the power button monitor.
|
||||
"$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=$!
|
||||
monitor_power_button &
|
||||
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 the screen monitor (SIGTERM restores brightness) and power button monitor.
|
||||
kill "$SCREEN_MONITOR_PID" 2>/dev/null
|
||||
wait "$SCREEN_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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user