Feat: Use named pipe instead of signals and sleep

This is a breaking change and will lead to a new major version.
This commit is contained in:
Narvin Singh 2021-01-01 13:17:08 -05:00
parent 4ddf06147d
commit d5c02f8459
3 changed files with 65 additions and 72 deletions

View File

@ -9,19 +9,19 @@ The Daemon
----------
The daemon creates a modular status bar by updating the X root window name
when it receives the USR1 signal. It takes an ordered list of modules as a
parameter, and calls a function in each module to compute a section of the
status bar. Each section is cached and only recomputed upon request. These
requests are made by creating empty files with the same names as the modules
in the `/tmp/avdd` directory, then sending the daemon a USR1 signal.
when it receives a request on its named pipe. It takes an ordered list of
modules as a parameter, and calls a function in each module to compute a
section of the status bar. Each section is cached and only recomputed upon
request. These requests are made by writing to a named pipe that the daemon
creates, `/tmp/avdd-fifo`.
The Scheduler
-------------
The scheduler creates request files in `/tmp/avdd`, then sends the USR1
signal to the daemon. It can send one signal to update multiple status bar
sections by creating a request file for each section to update, and can send
the signal immediately, after some delay, or repeatedly at some interval.
The scheduler creates requests by writing a module name to the named pipe for
each module it wants the daemon to run to update that section of the status
bar. It can send requests immediately, after some delay, or repeatedly at
some interval.
Installation
------------
@ -38,7 +38,7 @@ Usage
The following examples can be executed manually or by putting them in, e.g.,
your `.xinitrc` file. Note the `&` after long-running commands to make them
run in the background. If the directory that you cloned the appliction into
is not in your path, be sure to include the path when calling `avdd` or `avds`.
is not in your path, be sure to specify the path when calling `avdd` or `avds`.
Start the daemon to create a status bar with the default sections, prefix,
separators, and suffix.

64
avdd
View File

@ -44,8 +44,7 @@ DEFAULT_SEP_L='| '
DEFAULT_SEP_R=' '
DEFAULT_SUF=' '
MOD_DIR="$(dirname "$0")"/mod
ACTION_DIR=/tmp/avdd
ACTION_DIR_LEN=${#ACTION_DIR}
FIFO=/tmp/avdd-fifo
mod_list="${1-${DEFAULT_MOD_LIST}}"
pre="${2-${DEFAULT_PRE}}"
@ -96,43 +95,38 @@ draw_status() {
# Draw the initial status
draw_status
# For each file in the action directory, remove the file, and if a module
# for the action is cached, call the module function and update the cache. If
# any cache entries were updated, redraw the status.
process_signal () {
local -a action_paths
local action_path mod is_changed
readarray -d '' action_paths< \
<(find "${ACTION_DIR}" -maxdepth 1 -type f -exec rm -f {} + -print0)
for action_path in "${action_paths[@]}"; do
mod="${action_path:$((ACTION_DIR_LEN + 1))}"
if [[ -v stat_cache[${mod}] ]]; then
stat_cache["${mod}"]="$(eval "$(mod_to_fn "${mod}")")"
is_changed=1
# If the module value is in the cache, indicating that the module controls
# part of the status bar, execute the module function and redraw the status
# bar if that part of the status bar has changed
process_cmd () {
local -r mod="$1"
if [[ -v stat_cache[${mod}] ]]; then
local -r new_val="$(eval "$(mod_to_fn "${mod}")")"
if [[ "${new_val}" != stat_cache["${mod}"] ]]; then
stat_cache["${mod}"]="${new_val}"
draw_status
fi
done
if [[ -v is_changed ]]; then draw_status; fi
fi
}
# Begin trapping signals
mkdir -p "${ACTION_DIR}"
trap process_signal SIGUSR1
# Setup the named pipe to receive commands
if [[ ! -p "${FIFO}" ]]; then mkfifo "${FIFO}"; fi
trap "rm -f ${FIFO}" EXIT
# Wait for signals efficiently. In a loop begin a long-running sleep command
# in the background, then wait on it. If we trap a signal before the wait is
# over and sleep is still running, trap will call process_signal, then code
# execution will resume at the line after the wait statement. So on that line
# we kill the (probably) still running sleep command so they don't pile up,
# and loop to sleep and wait for the next signal. If we don't trap a signal
# during the long running sleep, then the wait ends, we try to kill the
# sleep command that has already exited, so it doesn't matter, and loop to
# sleep and wait again. Note that we don't make the sleep too long because
# if the daemon is killed, the sleep will become an orphaned process until
# the sleep period elapses.
# Each time the pipe is emptied out, the inner while loop will finish, so
# wrap it in an infinte loop to keep blocking until there is data on the pipe
while :; do
sleep 30m &
sleep_pid="$!"
wait "${sleep_pid}"
kill "${sleep_pid}" 2>/dev/null
while read -r cmd; do
case "${cmd}" in
res_quit)
exit 0
;;
res_*)
;;
*)
process_cmd "${cmd}"
;;
esac
done < "${FIFO}"
done

53
avds
View File

@ -1,43 +1,44 @@
#!/bin/bash
USAGE="
USAGE: avds <action> [<when>] [<repeat>]
USAGE: avds <mod_list> [<when>] [<repeat>]
action A comma or space separated list of actions to associate with the
update signal.
mod_list
A comma or space separated list of modules to request that
the daemon execute.
when The integer number of milliseconds to wait before sending the
update signal, or one of the following values:
- m to send the update signal at the top of the next minute
- h to send the update signal at the top of the next hour
- d to send the update signal at the top of the next day
If not present, the update signal will be sent immediately.
request, or one of the following values:
- m to send the update request at the top of the next minute
- h to send the update request at the top of the next hour
- d to send the update request at the top of the next day
If not present, the request will be sent immediately.
repeat If present, the update signal will be sent repeatedly according
repeat If present, the request will be sent repeatedly according
to <when>.
EXAMPLES:
If the daemon interprets the actions 'vol' and 'bl' to mean update the
volume and backlight statuses, respectively, send a signal to
immediately update both of those statuses.
If there are volume and backlight modules named 'vol' and 'bl' that
update the volume and backlight statuses, send a requst to update
both of those statuses immediately.
avds 'vol,bl'
If the daemon interprets the actions 'cpu' and 'mem' to mean update the
cpu and memory usage statuses, respectively, send a signal to update
both of those statuses every 5 seconds.
If there are cpu and memory usage modules named 'cpu' and 'mem'
that update the cpu and memory usage statuses, send a requst to
update both of those statuses every 5 seconds.
avds 'cpu,mem' 5000 1
If the daemon interprets the actions 'bat' and 'dt' to mean update the
battery and date/time statuses, respectively, send a signal to update
both of those statuses at the top of every minute.
If there are battery and date/time modules named 'bat' and 'dt'
that update the battery and date/time statuses, send a requst to
update both of those statuses at the top of every minute.
avds 'bat,dt' m true
"
DAEMON=avdd
ACTION_DIR=/tmp/"${DAEMON}"
FIFO=/tmp/"${DAEMON}"-fifo
# Convert integer milliseconds to floating point seconds
ms_to_s () {
@ -50,7 +51,7 @@ if [[ "$#" -lt 1 || "$#" -gt 3 ]]; then
exit 128
fi
IFS=', ' read -r -a actions <<< "$1"
IFS=', ' read -r -a mods <<< "$1"
when="${2:-0}"
repeat="$3"
@ -59,12 +60,12 @@ if [[ ! "${when}" =~ ^[0-9]+|[mhd]$ ]]; then
exit 128
fi
# Send the signal if this is the first run or if repeat is on
# Write to the pipe if this is the first run or if repeat is on
first_run=1
while [[ "${first_run}" -eq 1 || -n "${repeat}" ]]; do
first_run=0
# Sleep until it's time to send the signal
# Sleep until it's time to write to the pipe
if [[ "${when}" != '0' ]]; then
if [[ "${when}" =~ ^[0-9]+$ ]]; then
sleep "$(ms_to_s "${when}")"
@ -87,13 +88,11 @@ while [[ "${first_run}" -eq 1 || -n "${repeat}" ]]; do
fi
fi
# Create the signal data and send the signal to the daemon
daemon_pid="$(pgrep --newest --exact "${DAEMON}")"
if [[ -z "${daemon_pid}" ]]; then
# Write each command to the pipe
if [[ ! -p "${FIFO}" ]]; then
printf 'The daemon %s is not running\n' "${DAEMON}" 1>&2
exit 1
fi
for action in "${actions[@]}"; do touch "${ACTION_DIR}/${action}"; done
kill -USR1 "${daemon_pid}"
for mod in "${mods[@]}"; do printf '%s\n' "${mod}" >> "${FIFO}"; done
done