mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-07-04 11:43:10 +02:00
perf(idle-off): replace controller-watch fork storm with a single systemd-inhibit
turn_off_auto_idle_screen_shutdown.sh --watch-controller forked 4 xset +
1 xdotool per joystick event (blocking on a dd read, debounced by sleep
0.3) — ~21 procs/s while a controller was connected during gaming. The
xset poking was redundant: startup already disables DPMS/blanking.
Replace reset_idle_activity / watch_js_device / the polling watcher with
one long-lived `systemd-inhibit --what=idle:sleep` held only while a
/dev/input/js* device is present, re-evaluated on udev input add/remove
events (event-driven; 30s presence-poll fallback). EXIT+INT/TERM traps
release the inhibitor on every termination path.
Verified live: subtree stays {systemd-inhibit, udevadm} with zero
dd/xset/xdotool; exactly one inhibitor held; clean release on SIGTERM,
no orphans. Takes that loop from ~21 forks/s to 0.
Behavior change: keeps the session awake while a controller is connected
(not only during active input).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
20d5d1f89b
commit
fcd3f7ed2f
@ -0,0 +1,38 @@
|
||||
{
|
||||
"intent": "Eliminate the fork storm from turn_off_auto_idle_screen_shutdown.sh's controller watcher. Previously, while a game controller was connected, each joystick event forked 4 xset + 1 xdotool + a dd read + a sleep (~21 forks/s during gaming). The session must still be kept awake while a controller is connected, but with no per-event forks.",
|
||||
"scope": [
|
||||
"linux_configuration/scripts/single_use/utils/turn_off_auto_idle_screen_shutdown.sh",
|
||||
"Non-goal: the one-shot idle-disable steps (xset/gsettings at startup) are unchanged",
|
||||
"Non-goal: external chronyc forks (~1/s) which originate outside this repo"
|
||||
],
|
||||
"changes": [
|
||||
"Replaced reset_idle_activity + watch_js_device + the polling start_controller_watchers with a single long-lived `systemd-inhibit --what=idle:sleep` lock held only while a /dev/input/js* device is present.",
|
||||
"Controller presence is re-evaluated on udev input add/remove events (event-driven, no polling), with a 30 s presence-poll fallback when udevadm is absent.",
|
||||
"Cleanup hardened: EXIT plus INT/TERM traps release the inhibitor on any termination path; presence check is a pure-bash glob (zero forks)."
|
||||
],
|
||||
"verification": [
|
||||
{
|
||||
"command": "bash turn_off_auto_idle_screen_shutdown.sh --watch-controller (with js0 connected)",
|
||||
"result": "pass",
|
||||
"evidence": "systemd-inhibit --list shows exactly one 'game controller connected' lock; the watcher subtree stays {systemd-inhibit, udevadm} with zero dd/xset/xdotool over a 3 s sample, versus the old watcher still churning dd."
|
||||
},
|
||||
{
|
||||
"command": "kill -TERM <watcher>; systemd-inhibit --list",
|
||||
"result": "pass",
|
||||
"evidence": "Watcher exits cleanly, 0 inhibitors remain, no orphaned systemd-inhibit reparented to init."
|
||||
},
|
||||
{
|
||||
"command": "bash -n + shellcheck",
|
||||
"result": "pass",
|
||||
"evidence": "syntax OK; shellcheck clean."
|
||||
}
|
||||
],
|
||||
"risks": [
|
||||
"Semantics changed from 'awake only during active controller input' to 'awake while a controller is connected'; a permanently-plugged controller will keep the session from auto-idling.",
|
||||
"If the holding process is SIGKILLed outside a systemd cgroup (e.g. i3 crash), the inhibitor lingers until session end; EXIT trap covers normal termination."
|
||||
],
|
||||
"rollback": [
|
||||
"git checkout the script to restore the previous controller watcher.",
|
||||
"Re-run with --watch-controller and confirm whether xset/dd churn returns."
|
||||
]
|
||||
}
|
||||
@ -12,7 +12,7 @@
|
||||
# Optional persistence (requires sudo):
|
||||
# --persist-systemd -> Set IdleAction=ignore in /etc/systemd/logind.conf and restart logind
|
||||
# Optional activity watcher:
|
||||
# --watch-controller -> Treat game controller (e.g., Xbox) input as user activity to keep session awake
|
||||
# --watch-controller -> Hold a systemd idle/sleep inhibitor while a game controller is connected (keeps the session awake, fork-free)
|
||||
#
|
||||
# Notes:
|
||||
# - This script focuses on keeping the screen on and unlocked. Use with care on shared systems.
|
||||
@ -42,7 +42,7 @@ Disables idle detection, screen blanking, and auto-lock for the current session.
|
||||
|
||||
Options:
|
||||
--persist-systemd Also set IdleAction=ignore in /etc/systemd/logind.conf (needs sudo)
|
||||
--watch-controller Watch game controllers and generate activity to keep the session awake
|
||||
--watch-controller Hold an idle/sleep inhibitor while a game controller is connected
|
||||
-h, --help Show this help and exit
|
||||
|
||||
What this does:
|
||||
@ -52,7 +52,7 @@ What this does:
|
||||
- Sway: kill swayidle if running
|
||||
- TTY: setterm -blank 0 -powersave off -powerdown 0
|
||||
- Optional: systemd-logind IdleAction=ignore
|
||||
- Optional: watch controller input and reset idle timers
|
||||
- Optional: hold a systemd idle inhibitor while a controller is connected
|
||||
EOF
|
||||
exit 0
|
||||
;;
|
||||
@ -136,76 +136,85 @@ disable_tty_idle() {
|
||||
fi
|
||||
}
|
||||
|
||||
reset_idle_activity() {
|
||||
# Trigger activity hints depending on environment
|
||||
if [[ -n ${DISPLAY:-} ]]; then
|
||||
if has_cmd xset; then
|
||||
xset s reset || true
|
||||
xset -dpms || true
|
||||
xset s off || true
|
||||
xset s noblank || true
|
||||
fi
|
||||
if has_cmd xdotool; then
|
||||
# No-op mousemove to generate X11 activity without visible movement
|
||||
xdotool mousemove_relative -- 0 0 2> /dev/null || true
|
||||
fi
|
||||
# PID of the single long-lived idle/sleep inhibitor we hold while a controller
|
||||
# is connected. Empty when no inhibitor is active.
|
||||
inhibit_pid=""
|
||||
|
||||
start_idle_inhibit() {
|
||||
# Hold one systemd idle/sleep inhibitor for the whole time a controller is
|
||||
# connected. This replaces the previous per-event fork storm (4 xset + an
|
||||
# xdotool + a dd read + a sleep on *every* joystick event, ~21 forks/s while
|
||||
# gaming): a single long-lived process keeps logind from idling, suspending,
|
||||
# or locking, while X11 blanking stays off thanks to the one-shot
|
||||
# disable_x11_idle above. Idempotent — a live inhibitor is reused.
|
||||
if [[ -n $inhibit_pid ]] && kill -0 "$inhibit_pid" 2> /dev/null; then
|
||||
return 0
|
||||
fi
|
||||
systemd-inhibit --what=idle:sleep --who="idle-off" \
|
||||
--why="game controller connected" sleep infinity &
|
||||
inhibit_pid=$!
|
||||
log "Holding idle/sleep inhibitor (pid ${inhibit_pid}) while a controller is connected"
|
||||
}
|
||||
|
||||
watch_js_device() {
|
||||
local dev="$1"
|
||||
log "Watching controller device: $dev"
|
||||
while :; do
|
||||
if [[ ! -e $dev ]]; then
|
||||
warn "Device disappeared: $dev"
|
||||
break
|
||||
fi
|
||||
# Joystick API event size is 8 bytes; block until an event arrives
|
||||
if dd if="$dev" bs=8 count=1 status=none of=/dev/null; then
|
||||
reset_idle_activity
|
||||
# Debounce bursts of events
|
||||
sleep 0.3
|
||||
else
|
||||
# On read error (e.g., permission), backoff
|
||||
sleep 1
|
||||
fi
|
||||
stop_idle_inhibit() {
|
||||
if [[ -z $inhibit_pid ]]; then
|
||||
return 0
|
||||
fi
|
||||
kill "$inhibit_pid" 2> /dev/null || true
|
||||
wait "$inhibit_pid" 2> /dev/null || true
|
||||
inhibit_pid=""
|
||||
log "Released idle/sleep inhibitor; normal idle behaviour resumes"
|
||||
}
|
||||
|
||||
controller_connected() {
|
||||
# Pure-bash glob check — zero forks. True if any /dev/input/js* node exists.
|
||||
local dev
|
||||
for dev in /dev/input/js*; do
|
||||
[[ -e $dev ]] && return 0
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
sync_inhibit_to_controllers() {
|
||||
# Hold the inhibitor exactly when a controller is present.
|
||||
if controller_connected; then
|
||||
start_idle_inhibit
|
||||
else
|
||||
stop_idle_inhibit
|
||||
fi
|
||||
}
|
||||
|
||||
start_controller_watchers() {
|
||||
# Attempt to watch all /dev/input/js* devices; rescan periodically for new ones
|
||||
declare -A pids
|
||||
|
||||
# Initial permission check
|
||||
local any_js=false any_readable=false
|
||||
for dev in /dev/input/js*; do
|
||||
[[ -e $dev ]] || continue
|
||||
any_js=true
|
||||
if [[ -r $dev ]]; then any_readable=true; fi
|
||||
done
|
||||
if [[ $any_js == true && $any_readable == false ]]; then
|
||||
warn "No read permission to /dev/input/js*; add your user to the 'input' group or create udev rules."
|
||||
# Event-driven and fork-free in the hot path: react only to input-device
|
||||
# add/remove (rare udev events), never to individual joystick *input* events,
|
||||
# and hold a single systemd-inhibit lock while a controller is present.
|
||||
if ! has_cmd systemd-inhibit; then
|
||||
warn "systemd-inhibit not found; cannot hold an idle inhibitor"
|
||||
return 0
|
||||
fi
|
||||
# EXIT covers every termination path (including a SIGTERM that interrupts the
|
||||
# blocking read below); INT/TERM additionally give a clean exit status.
|
||||
trap 'stop_idle_inhibit' EXIT
|
||||
trap 'exit 0' INT TERM
|
||||
|
||||
while :; do
|
||||
local found_any=false
|
||||
for dev in /dev/input/js*; do
|
||||
[[ -e $dev ]] || continue
|
||||
found_any=true
|
||||
if [[ -z ${pids[$dev]:-} ]] || ! kill -0 "${pids[$dev]}" 2> /dev/null; then
|
||||
# Start a watcher for this device in background
|
||||
watch_js_device "$dev" &
|
||||
pids[$dev]=$!
|
||||
fi
|
||||
sync_inhibit_to_controllers # apply current state once at startup
|
||||
|
||||
if has_cmd udevadm; then
|
||||
log "Watching controller hotplug via udev (no polling)"
|
||||
# Process substitution (not a pipe) keeps the loop in this shell so
|
||||
# inhibit_pid persists across events.
|
||||
while read -r _; do
|
||||
sync_inhibit_to_controllers
|
||||
done < <(udevadm monitor --udev --subsystem-match=input 2> /dev/null)
|
||||
else
|
||||
# Fallback when udevadm is unavailable: a low-frequency presence poll. One
|
||||
# sleep per 30 s cycle (~0.03 forks/s) versus the old ~21 forks/s.
|
||||
warn "udevadm not found; falling back to a 30 s presence poll"
|
||||
while :; do
|
||||
sync_inhibit_to_controllers
|
||||
sleep 30
|
||||
done
|
||||
if [[ $found_any == false ]]; then
|
||||
# No joystick devices; quiet rescan
|
||||
sleep 5
|
||||
else
|
||||
# Rescan less frequently when active
|
||||
sleep 2
|
||||
fi
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
persist_with_systemd_logind() {
|
||||
@ -255,14 +264,9 @@ main() {
|
||||
persist_with_systemd_logind
|
||||
|
||||
if [[ $watch_controller == true ]]; then
|
||||
log "Controller activity watcher enabled"
|
||||
# Keep the script alive to watch controllers
|
||||
start_controller_watchers &
|
||||
watcher_pid=$!
|
||||
log "Watcher PID: $watcher_pid"
|
||||
# Wait indefinitely and forward termination
|
||||
trap 'log "Stopping controller watcher"; kill "$watcher_pid" 2>/dev/null || true; exit 0' INT TERM
|
||||
wait "$watcher_pid"
|
||||
log "Controller activity watcher enabled (idle-inhibitor mode)"
|
||||
# Blocks until terminated; releases the inhibitor on exit via its own trap.
|
||||
start_controller_watchers
|
||||
else
|
||||
log "Done. The screen should no longer blank, lock, or power down automatically."
|
||||
fi
|
||||
|
||||
Loading…
Reference in New Issue
Block a user