mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-07-04 15:23:03 +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):
|
# Optional persistence (requires sudo):
|
||||||
# --persist-systemd -> Set IdleAction=ignore in /etc/systemd/logind.conf and restart logind
|
# --persist-systemd -> Set IdleAction=ignore in /etc/systemd/logind.conf and restart logind
|
||||||
# Optional activity watcher:
|
# 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:
|
# Notes:
|
||||||
# - This script focuses on keeping the screen on and unlocked. Use with care on shared systems.
|
# - 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:
|
Options:
|
||||||
--persist-systemd Also set IdleAction=ignore in /etc/systemd/logind.conf (needs sudo)
|
--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
|
-h, --help Show this help and exit
|
||||||
|
|
||||||
What this does:
|
What this does:
|
||||||
@ -52,7 +52,7 @@ What this does:
|
|||||||
- Sway: kill swayidle if running
|
- Sway: kill swayidle if running
|
||||||
- TTY: setterm -blank 0 -powersave off -powerdown 0
|
- TTY: setterm -blank 0 -powersave off -powerdown 0
|
||||||
- Optional: systemd-logind IdleAction=ignore
|
- 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
|
EOF
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
@ -136,76 +136,85 @@ disable_tty_idle() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
reset_idle_activity() {
|
# PID of the single long-lived idle/sleep inhibitor we hold while a controller
|
||||||
# Trigger activity hints depending on environment
|
# is connected. Empty when no inhibitor is active.
|
||||||
if [[ -n ${DISPLAY:-} ]]; then
|
inhibit_pid=""
|
||||||
if has_cmd xset; then
|
|
||||||
xset s reset || true
|
start_idle_inhibit() {
|
||||||
xset -dpms || true
|
# Hold one systemd idle/sleep inhibitor for the whole time a controller is
|
||||||
xset s off || true
|
# connected. This replaces the previous per-event fork storm (4 xset + an
|
||||||
xset s noblank || true
|
# xdotool + a dd read + a sleep on *every* joystick event, ~21 forks/s while
|
||||||
fi
|
# gaming): a single long-lived process keeps logind from idling, suspending,
|
||||||
if has_cmd xdotool; then
|
# or locking, while X11 blanking stays off thanks to the one-shot
|
||||||
# No-op mousemove to generate X11 activity without visible movement
|
# disable_x11_idle above. Idempotent — a live inhibitor is reused.
|
||||||
xdotool mousemove_relative -- 0 0 2> /dev/null || true
|
if [[ -n $inhibit_pid ]] && kill -0 "$inhibit_pid" 2> /dev/null; then
|
||||||
fi
|
return 0
|
||||||
fi
|
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() {
|
stop_idle_inhibit() {
|
||||||
local dev="$1"
|
if [[ -z $inhibit_pid ]]; then
|
||||||
log "Watching controller device: $dev"
|
return 0
|
||||||
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
|
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
|
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() {
|
start_controller_watchers() {
|
||||||
# Attempt to watch all /dev/input/js* devices; rescan periodically for new ones
|
# Event-driven and fork-free in the hot path: react only to input-device
|
||||||
declare -A pids
|
# add/remove (rare udev events), never to individual joystick *input* events,
|
||||||
|
# and hold a single systemd-inhibit lock while a controller is present.
|
||||||
# Initial permission check
|
if ! has_cmd systemd-inhibit; then
|
||||||
local any_js=false any_readable=false
|
warn "systemd-inhibit not found; cannot hold an idle inhibitor"
|
||||||
for dev in /dev/input/js*; do
|
return 0
|
||||||
[[ -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."
|
|
||||||
fi
|
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
|
sync_inhibit_to_controllers # apply current state once at startup
|
||||||
local found_any=false
|
|
||||||
for dev in /dev/input/js*; do
|
if has_cmd udevadm; then
|
||||||
[[ -e $dev ]] || continue
|
log "Watching controller hotplug via udev (no polling)"
|
||||||
found_any=true
|
# Process substitution (not a pipe) keeps the loop in this shell so
|
||||||
if [[ -z ${pids[$dev]:-} ]] || ! kill -0 "${pids[$dev]}" 2> /dev/null; then
|
# inhibit_pid persists across events.
|
||||||
# Start a watcher for this device in background
|
while read -r _; do
|
||||||
watch_js_device "$dev" &
|
sync_inhibit_to_controllers
|
||||||
pids[$dev]=$!
|
done < <(udevadm monitor --udev --subsystem-match=input 2> /dev/null)
|
||||||
fi
|
|
||||||
done
|
|
||||||
if [[ $found_any == false ]]; then
|
|
||||||
# No joystick devices; quiet rescan
|
|
||||||
sleep 5
|
|
||||||
else
|
else
|
||||||
# Rescan less frequently when active
|
# Fallback when udevadm is unavailable: a low-frequency presence poll. One
|
||||||
sleep 2
|
# sleep per 30 s cycle (~0.03 forks/s) versus the old ~21 forks/s.
|
||||||
fi
|
warn "udevadm not found; falling back to a 30 s presence poll"
|
||||||
|
while :; do
|
||||||
|
sync_inhibit_to_controllers
|
||||||
|
sleep 30
|
||||||
done
|
done
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
persist_with_systemd_logind() {
|
persist_with_systemd_logind() {
|
||||||
@ -255,14 +264,9 @@ main() {
|
|||||||
persist_with_systemd_logind
|
persist_with_systemd_logind
|
||||||
|
|
||||||
if [[ $watch_controller == true ]]; then
|
if [[ $watch_controller == true ]]; then
|
||||||
log "Controller activity watcher enabled"
|
log "Controller activity watcher enabled (idle-inhibitor mode)"
|
||||||
# Keep the script alive to watch controllers
|
# Blocks until terminated; releases the inhibitor on exit via its own trap.
|
||||||
start_controller_watchers &
|
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"
|
|
||||||
else
|
else
|
||||||
log "Done. The screen should no longer blank, lock, or power down automatically."
|
log "Done. The screen should no longer blank, lock, or power down automatically."
|
||||||
fi
|
fi
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user