Problem: Folding@Home's built-in idle detection doesn't work for me. So, I wrote a script to start and stop it when X11 is idle. This doesn't work for Wayland.
First we need to replace F@H's terrible init.d script with a reasonable
systemd service unit file in /etc/systemd/system/FAHClient.service
.
Note that it should be named that (with capital letters!) so that it overrides
the automatically generated service unit that systemd produces to call the
init.d script. This service unit file should help
whether you use the idle-detection script or not.
[Unit]
Description=Folding@Home Client
After=graphical.target
Requisite=graphical.target
After=multi-user.target
Requisite=multi-user.target
[Service]
Environment=EXTRA_OPTS=
Environment=ENABLE=true
EnvironmentFile=-/etc/default/fahclient
Type=idle
StateDirectory=fahclient
WorkingDirectory=/var/lib/fahclient
PIDFile=/run/fahclient.pid
PassEnvironment=ENABLE
# Make sure ENABLE is set to true in /etc/defaults/fahclient. This is here
# to emulate the behavior of the original init.d script.
ExecCondition=sh -c '[ "x$ENABLE" = "xtrue" ] || exit 255'
User=fahclient
ExecStartPre=/bin/sleep 10
ExecStart=/usr/bin/FAHClient /etc/fahclient/config.xml --pid-file=/run/fahclient.pid --pid
ExecReload=kill -SIGUSR1 $MAINPID
KillMode=mixed
TimeoutStartSec=20
TimeoutStopSec=10
Restart=on-abnormal
RestartSec=60
[Install]
WantedBy=graphical.target
Our idle-detection script uses the xprintidle
command provided by the
xprintidle
package, so install that.
Then we're going to create a script /opt/fahidle.sh
which is our idle-detection
service:
#!/bin/bash
IDLE=300 # time in seconds
function have_command {
# Determine if we have a command/alias/function/bash builtin
# without running it.
# || return $? protects us from set -o errexit
type -t "$1" >/dev/null || return $?
}
function die {
echo Error: "$@" >&2
exit 1
}
function find_xauths {
# Find X11 servers by looking for the -auth argument passed to them.
# We need this info to connect to them anyway.
# -Aww causes ps to print out All procs, and not cut off args (-ww)
# -o args causes ps to only print out argument strings
# The filename given after -auth is matched & printed by grep
# -P is PCRE, allowing us to use negative-width lookbehind assertions
# (?<=)
# If this fails, it's probably because there's no X11s running,
# the script will crash and systemd will restart it to try again later
ps -Aww --no-headers -o args | grep -o -P -- '(?<=-[a]uth )\S+'
}
function check_idle {
local f
local dpy
# Use 2^31-1 as a canary value to make sure we found at least one X server
local -i int_max=2147483647
local -i min_idle=int_max
for f in $( find_xauths )
do
# xauth will retrieve the magic cookie X authority file, and tell us
# what display its for (and how to connect to it)
# in the first column, whcich we isolate with cut
for dpy in $( xauth -f "$f" list | cut -d' ' -f 1 )
do
# Despite everything I can see online indicating that a
# display value of DISPLAY=host/unix:0 should work, it doesn't,
# so if the display reported by xauth has that format, we just
# cut it down to something like :0 by removing the */unix prefix.
export DISPLAY="${dpy##*/unix}"
export XAUTHORITY="$f"
local -i idle
# Use xprintidle to get us the idle time in milliseconds
if idle="$(xprintidle 2>/dev/null)" ; then
# Convert milliseconds to seconds
idle=$(( idle / 1000 ))
# Record the idle time in seconds of the least idle X server
if ((idle < min_idle)) ; then
min_idle=idle
fi
fi
done
done
if (( min_idle > IDLE && min_idle < int_max )) ; then
# echo "Idle for $min_idle seconds, starting FAHClient"
systemctl start FAHClient
else
# echo "Idle for $min_idle seconds, stopping FAHClient"
systemctl stop FAHClient
fi
}
function main {
set -o nounset
set -o pipefail
set -o errexit
if ! have_command xprintidle ; then
die "Need xprintidle"
fi
if [[ ! -v IDLE ]] ; then
IDLE=300 # seconds
fi
# Every 10 seconds, we check if all the X servers are idle
while sleep 10 ; do
check_idle
done
}
# using a main function forces bash to read the entire file before doing
# anything, which is necessary because bash reads scripts line by line
# otherwise so if the script file changes while its running, strange stuff
# can happen
main "$@"
And a systemd service unit file to activate our idle script in /etc/systemd/system/FAHIdle.service
:
[Unit]
Description=Folding@Home Client Idler
After=graphical.target
Requisite=graphical.target
After=multi-user.target
Requisite=multi-user.target
[Service]
Environment=ENABLE=true
EnvironmentFile=-/etc/default/fahclient
Type=idle
PassEnvironment=ENABLE
ExecCondition=sh -c '[ "x$ENABLE" = "xtrue" ] || exit 255'
ExecStart=/opt/fahidle.sh
Restart=always
RestartSec=60
[Install]
WantedBy=graphical.target
Make sure you unpause F@H and set it to run always (not just at idle) before starting FAHIdle. Finally, reload systemd and enable and start FAHIdle.
systemctl daemon-reload
systemctl disable FAHClient
systemctl stop FAHClient
systemctl enable FAHIdle
systemctl start FAHIdle
Remember that if you want to stop folding
systemctl disable FAHClient
and
systemctl stop FAHClient
will no longer work properly, because FAHIdle
will be starting and stopping it, so systemctl stop FAHIdle
or systemctl disable FAHIdle
instead.
There are probably better ways to handle some of the things the script needs to do, and if you use Gnome you can use D-BUS instead, which would be much more efficient.