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:
parent
4ddf06147d
commit
d5c02f8459
20
README.md
20
README.md
@ -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
64
avdd
@ -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
53
avds
@ -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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user