Fix screen wake for d-pad, grab inputs while screen off, auto-shutdown on idle

- Broaden wake events beyond EV_KEY so d-pad (EV_ABS) wakes the screen
- Use EVIOCGRAB for exclusive input while screen is off, preventing the
  wake button press from also acting in sdlamp2
- Auto-shutdown after 10 minutes of no input and no audio playback,
  detected by monitoring the audio file's read position via /proc fdinfo

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Michael Smith
2026-02-13 22:47:47 +01:00
parent 8123b9a99f
commit fbd32d7fb8
2 changed files with 92 additions and 12 deletions

View File

@@ -4,8 +4,14 @@ Screen idle timeout, power button toggle, and long-press shutdown for RG35XX Plu
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.
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.
@@ -19,7 +25,8 @@ import struct
import sys
import time
IDLE_TIMEOUT = 15 # seconds
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
@@ -28,9 +35,15 @@ 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)
@@ -53,6 +66,34 @@ 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)
@@ -68,8 +109,9 @@ def main():
screen_on = True
power_press_time = None
# Restore brightness on exit so goodbye.png is visible during shutdown
# 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)
@@ -92,6 +134,10 @@ def main():
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
@@ -99,6 +145,7 @@ def main():
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)
@@ -109,6 +156,7 @@ def main():
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
@@ -117,9 +165,23 @@ def main():
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:
@@ -133,11 +195,13 @@ def main():
INPUT_EVENT_FORMAT, data
)
if ev_type != EV_KEY:
if ev_type == EV_SYN:
continue
# Power button handling
if ev_code == KEY_POWER:
any_activity = True
# 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
@@ -148,20 +212,30 @@ def main():
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 other key press — wake screen if off
if ev_value == 1 and not screen_on:
set_brightness(disp_fd, original_brightness)
screen_on = 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."""
"""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)