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 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 when it receives a request on its named pipe. It takes an ordered list of
parameter, and calls a function in each module to compute a section of the modules as a parameter, and calls a function in each module to compute a
status bar. Each section is cached and only recomputed upon request. These section of the status bar. Each section is cached and only recomputed upon
requests are made by creating empty files with the same names as the modules request. These requests are made by writing to a named pipe that the daemon
in the `/tmp/avdd` directory, then sending the daemon a USR1 signal. creates, `/tmp/avdd-fifo`.
The Scheduler The Scheduler
------------- -------------
The scheduler creates request files in `/tmp/avdd`, then sends the USR1 The scheduler creates requests by writing a module name to the named pipe for
signal to the daemon. It can send one signal to update multiple status bar each module it wants the daemon to run to update that section of the status
sections by creating a request file for each section to update, and can send bar. It can send requests immediately, after some delay, or repeatedly at
the signal immediately, after some delay, or repeatedly at some interval. some interval.
Installation Installation
------------ ------------
@ -38,7 +38,7 @@ Usage
The following examples can be executed manually or by putting them in, e.g., 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 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 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, Start the daemon to create a status bar with the default sections, prefix,
separators, and suffix. separators, and suffix.

62
avdd
View File

@ -44,8 +44,7 @@ DEFAULT_SEP_L='| '
DEFAULT_SEP_R=' ' DEFAULT_SEP_R=' '
DEFAULT_SUF=' ' DEFAULT_SUF=' '
MOD_DIR="$(dirname "$0")"/mod MOD_DIR="$(dirname "$0")"/mod
ACTION_DIR=/tmp/avdd FIFO=/tmp/avdd-fifo
ACTION_DIR_LEN=${#ACTION_DIR}
mod_list="${1-${DEFAULT_MOD_LIST}}" mod_list="${1-${DEFAULT_MOD_LIST}}"
pre="${2-${DEFAULT_PRE}}" pre="${2-${DEFAULT_PRE}}"
@ -96,43 +95,38 @@ draw_status() {
# Draw the initial status # Draw the initial status
draw_status draw_status
# For each file in the action directory, remove the file, and if a module # If the module value is in the cache, indicating that the module controls
# for the action is cached, call the module function and update the cache. If # part of the status bar, execute the module function and redraw the status
# any cache entries were updated, redraw the status. # bar if that part of the status bar has changed
process_signal () { process_cmd () {
local -a action_paths local -r mod="$1"
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 if [[ -v stat_cache[${mod}] ]]; then
stat_cache["${mod}"]="$(eval "$(mod_to_fn "${mod}")")" local -r new_val="$(eval "$(mod_to_fn "${mod}")")"
is_changed=1 if [[ "${new_val}" != stat_cache["${mod}"] ]]; then
stat_cache["${mod}"]="${new_val}"
draw_status
fi
fi fi
done
if [[ -v is_changed ]]; then draw_status; fi
} }
# Begin trapping signals # Setup the named pipe to receive commands
mkdir -p "${ACTION_DIR}" if [[ ! -p "${FIFO}" ]]; then mkfifo "${FIFO}"; fi
trap process_signal SIGUSR1 trap "rm -f ${FIFO}" EXIT
# Wait for signals efficiently. In a loop begin a long-running sleep command # Each time the pipe is emptied out, the inner while loop will finish, so
# in the background, then wait on it. If we trap a signal before the wait is # wrap it in an infinte loop to keep blocking until there is data on the pipe
# 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.
while :; do while :; do
sleep 30m & while read -r cmd; do
sleep_pid="$!" case "${cmd}" in
wait "${sleep_pid}" res_quit)
kill "${sleep_pid}" 2>/dev/null exit 0
;;
res_*)
;;
*)
process_cmd "${cmd}"
;;
esac
done < "${FIFO}"
done done

53
avds
View File

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