#!/usr/bin/tclsh
# Copyright 2010 Zygo Blaxell <duty-idle@mailtoo.hungrycats.org>
# Released under the GNU GPL.
# Usage:
# duty-idle <interval-in-ms> <process-name-list>
# Dependencies:
# The notion of "idle" comes from the 'evtest' package and Linux event
# subsystem. Search for 'evtest' in the program text. The script
# iterates over /dev/input/event* and sets up an 'evtest' process
# to monitor each one.
#
# You'll also need tclsh, if that wasn't obvious from the first line.
# Theory of operation:
#
# When input is detected (and at startup), we execute
# "killall -STOP <process-name-list>".
# When no input is detected for <interval-in-ms> milliseconds, we execute
# "killall -CONT <process-name-list>".
#
# This can be used to implement a human-detecting QoS scheme which stops
# and starts some processes based on whether there's a human in front of
# the console.
# Failure modes:
#
# 'killall' is racy, especially if the process name includes things like 'make' or 'cc1'.
# If a process fork()s and exec()s, its name might change between the 'killall -STOP'
# and 'killall -CONT', which will prevent the process from ever receiving a SIGCONT.
# If someone wants to fix that (e.g. by using 'pidof' to record a PID list in stop,
# and signal just those PIDs in go), patches are welcome.
#
# 'killall' also can't find some processes, especially scripts where
# argv() gets swapped out.
set ::state 1
set ::goWhen [lindex $argv 0]
set ::argv [lrange $argv 1 end]
set ::goAfter {}
set ::fds 0
proc stop {} {
if {$::state} {
puts stderr "\n[clock format [clock seconds]] STOP\n"
set code [catch {eval exec killall -STOP $::argv} rv]
if {$code} {
puts stderr "killall: $::argv: $rv"
}
set ::state 0
} else {
puts -nonewline stderr -
}
after cancel $::goAfter
set ::goAfter [after $::goWhen go]
}
proc go {} {
if {!$::state} {
puts stderr "\n[clock format [clock seconds]] GO\n"
set code [catch {eval exec killall -CONT $::argv} rv]
if {$code} {
puts stderr "killall: $::argv: $rv"
}
set ::state 1
} else {
puts -nonewline stderr +
}
}
proc inputHandler {fd} {
if {[eof $fd]} {
close $fd
if {![incr ::fds -1]} {
set ::exit 0
}
}
set rv [gets $fd line]
if {$rv >= 0} {
stop
}
}
foreach dev [glob /dev/input/event*] {
set fd [open "|evtest $dev" r]
fconfigure $fd -translation binary -buffering line -blocking 0
fileevent $fd readable [list inputHandler $fd]
incr ::fds
}
vwait ::exit