rain bug fix and new engine

This commit is contained in:
2026-10-13 15:00:04 +02:00
parent d28b602827
commit 32dc96ef0f
3 changed files with 182 additions and 100 deletions
+6 -6
View File
@@ -248,14 +248,14 @@ change the default without having to pass flags every time.
| Key | Default | Description | | Key | Default | Description |
| --- | --- | --- | | --- | --- | --- |
| `RAIN_DEFAULT_SPEED` | `0.1` | Falling speed for `rain` | | `RAIN_DEFAULT_SPEED` | `5` | Delay between frames for `rain`: a value ≥ 1 is in hundredths of a second (`5` = 0.05 s), a value < 1 is in seconds; must be > 0 |
| `RAIN_DEFAULT_COLOR` | `Green` | Colour for `rain` | | `RAIN_DEFAULT_COLOR` | `white` | Colour for `rain` (`green`, `blue`, `red`, `yellow`, `cyan`, `white`; case-insensitive) |
| `RAIN_DEFAULT_DENSITY` | dynamic | Maximum number of simultaneous falling elements for `rain` | | `RAIN_DEFAULT_DENSITY` | dynamic | Maximum number of simultaneous falling elements for `rain` |
| `RAINBOW_DEFAULT_SPEED` | `0.04` | Horizontal color shift speed for `rainbow` | | `RAINBOW_DEFAULT_SPEED` | `4` | Frame delay (hundredths of a second) of the colour shift for `rainbow` |
| `MATRIX_DEFAULT_SPEED` | `0.05` | Falling speed for `matrix` | | `MATRIX_DEFAULT_SPEED` | `3.5` | Frame delay for `matrix`, same scale as `RAIN_DEFAULT_SPEED` |
| `MATRIX_DEFAULT_COLOR` | `Green` | Colour for `matrix` | | `MATRIX_DEFAULT_COLOR` | `green` | Colour for `matrix` (same names as `rain`) |
| `MATRIX_DEFAULT_DENSITY` | dynamic | Maximum number of simultaneous falling elements for `matrix` | | `MATRIX_DEFAULT_DENSITY` | dynamic | Maximum number of simultaneous falling elements for `matrix` |
| `MATRIX_DEFAULT_CHARSET` | `binary` | Character set for `matrix` (`binary`, `kana`, `ascii`) | | `MATRIX_DEFAULT_CHARSET` | `binary` | Character set for `matrix` (`binary`, `kana`, `ascii`; case-insensitive) |
**`[ssh]`** **`[ssh]`**
+12
View File
@@ -140,6 +140,18 @@ Versions follow `MAJOR.MINOR.PATCH-REVISION_STAGE_N` (e.g. `3.99.1-4_rc_1`).
(they were evaluated as arithmetic expressions); `pwdscore` classifies (they were evaluated as arithmetic expressions); `pwdscore` classifies
characters with locale-proof classes; README default of characters with locale-proof classes; README default of
`GENPWD_DEFAULT_OCCURS` corrected. `GENPWD_DEFAULT_OCCURS` corrected.
- `rain.sh`: the terminal was left with the last drop colour active, the cursor
parked over the frozen last frame and the user's `INT`/`TERM`/`WINCH` traps
deleted and `stty` forced to `echo`: the screen is now cleared and the traps
and terminal settings restored; a resize now clears the screen and drops
(drops placed for the old size lingered); a drop vanished for one frame each
time another one left the screen; each frame is written with one `printf`
instead of one per character (output is byte-identical, a lot faster);
colour and charset names are case-insensitive (`RAIN_DEFAULT_COLOR=Green`, as
documented, fell back to white), an unknown colour is an error instead of a
silent white, a null speed no longer spins the CPU, a non-terminal stdin or
stdout is refused instead of looping, and the usage mentions `q` to quit;
README defaults of the `[rain]` keys corrected.
- `compress.sh`: option flags and loop variables no longer leak into the - `compress.sh`: option flags and loop variables no longer leak into the
interactive shell. interactive shell.
+166 -96
View File
@@ -41,9 +41,19 @@ load_conf "rain"
_rain_build_colors() _rain_build_colors()
{ {
local base_color="$1" # Case-insensitive: the configuration documents capitalised names (Green)
local base_color="${1,,}"
local i
RAIN_ENGINE_COLORS=() RAIN_ENGINE_COLORS=()
case "$base_color" in
green|blue|red|yellow|cyan|white) ;;
*)
disp E "Unknown color: ${1} (supported: green, blue, red, yellow, cyan, white)."
return 1
;;
esac
local use_truecolor=0 local use_truecolor=0
# term_set() already sets TERM=*-direct when truecolor is available; # term_set() already sets TERM=*-direct when truecolor is available;
# honour COLORTERM as a belt-and-suspenders fallback. # honour COLORTERM as a belt-and-suspenders fallback.
@@ -63,7 +73,7 @@ _rain_build_colors()
*) r1=40; g1=40; b1=45; r2=220; g2=220; b2=255 ;; *) r1=40; g1=40; b1=45; r2=220; g2=220; b2=255 ;;
esac esac
local i r g b local r g b
for ((i = 0; i < steps; i++)); do for ((i = 0; i < steps; i++)); do
r=$(( r1 + (r2 - r1) * i / (steps - 1) )) r=$(( r1 + (r2 - r1) * i / (steps - 1) ))
g=$(( g1 + (g2 - g1) * i / (steps - 1) )) g=$(( g1 + (g2 - g1) * i / (steps - 1) ))
@@ -134,6 +144,12 @@ _rain_normalize_speed()
return 1 return 1
fi fi
# A null delay is refused: "read -t 0" does not wait, the animation loop
# would spin and use a whole CPU.
if LC_ALL=C awk -v s="$raw_speed" 'BEGIN { exit !(s == 0) }'; then
return 1
fi
if LC_ALL=C awk -v s="$raw_speed" 'BEGIN { exit !(s < 1) }'; then if LC_ALL=C awk -v s="$raw_speed" 'BEGIN { exit !(s < 1) }'; then
printf "%s" "$raw_speed" printf "%s" "$raw_speed"
else else
@@ -205,6 +221,54 @@ _rainbow_build_palette()
fi fi
} }
# Stop request of the animation loops: sets exit_st, a local of the running
# animation (dynamic scoping). Installed as the TERM/INT trap.
_rain_stop()
{
exit_st=1
}
# The animations read a key from the terminal and draw on it, and need tput.
# Usage: _rain_need_tty <command-name>
_rain_need_tty()
{
if [[ ! -t 0 || ! -t 1 ]]; then
disp E "$1: a terminal is required (standard input and output must be a terminal)."
return 1
fi
command -v tput >/dev/null 2>&1 || {
disp E "The program 'tput' is required but not installed."
return 1
}
}
# Prepare the terminal for an animation: save its settings and the TERM, INT
# and WINCH traps of the caller, install the animation traps, disable echo,
# hide the cursor and clear the screen.
# saved_stty and saved_traps are locals of the calling animation.
# Usage: _rain_enter <winch_handler_function>
_rain_enter()
{
saved_stty=$(stty -g 2>/dev/null)
saved_traps=$(trap -p TERM INT WINCH)
trap _rain_stop TERM INT
trap "$1" WINCH
stty -echo
printf '\e[?25l\e[2J'
}
# Undo _rain_enter: reset the colours (the last drawn drop colour would
# otherwise stay active), show the cursor, clear the screen, then restore the
# terminal settings and the caller's traps.
_rain_leave()
{
printf '\e[0m\e[?25h\e[2J\e[H'
[[ -n "$saved_stty" ]] && stty "$saved_stty"
trap - TERM INT WINCH
[[ -n "$saved_traps" ]] && eval "$saved_traps"
return 0
}
_rain_engine() _rain_engine()
{ {
local step_duration="$1" local step_duration="$1"
@@ -213,16 +277,14 @@ _rain_engine()
local charset="$4" local charset="$4"
local density_override="$5" local density_override="$5"
command -v tput >/dev/null 2>&1 || { _rain_need_tty "$mode" || return 1
disp E "The program 'tput' is required but not installed."
return 1
}
_rain_build_colors "$base_color" _rain_build_colors "$base_color" || return 1
_rain_build_chars "$mode" "$charset" || return 1 _rain_build_chars "$mode" "$charset" || return 1
local rain_colors=("${RAIN_ENGINE_COLORS[@]}") local rain_colors=("${RAIN_ENGINE_COLORS[@]}")
local rain_chars=("${RAIN_ENGINE_CHARS[@]}") local rain_chars=("${RAIN_ENGINE_CHARS[@]}")
unset RAIN_ENGINE_COLORS RAIN_ENGINE_CHARS
local rain_color_tab=${#rain_colors[@]} local rain_color_tab=${#rain_colors[@]}
local rain_tab=${#rain_chars[@]} local rain_tab=${#rain_chars[@]}
local matrix_head_color=$'\e[1;97m' local matrix_head_color=$'\e[1;97m'
@@ -235,39 +297,48 @@ _rain_engine()
local new_rain_odd=0 falling_odd=0 local new_rain_odd=0 falling_odd=0
local term_area=0 local term_area=0
local frame_sleep="$step_duration" local frame_sleep="$step_duration"
local saved_stty="" saved_traps=""
sigwinch() # The drops: 5 consecutive entries each (X, Y of the top, character, colour,
# length).
local rains=()
local num_rains=0
local ch=""
# WINCH handler (and initial sizing). Called with the animation running, it
# sees its locals through dynamic scoping.
_rain_sigwinch()
{ {
term_width=$(tput cols) term_width=$(tput cols)
term_height=$(tput lines) term_height=$(tput lines)
((term_area = term_width * term_height)) term_area=$(( term_width * term_height ))
case "$mode" in case "$mode" in
matrix) matrix)
((max_rain_width = term_area / 3)) max_rain_width=$(( term_area / 3 ))
((max_rain_height = term_height < 8 ? 1 : term_height / 6)) max_rain_height=$(( term_height < 8 ? 1 : term_height / 6 ))
((new_rain_odd = term_height > 50 ? 100 : term_height * 2)) new_rain_odd=$(( term_height > 50 ? 100 : term_height * 2 ))
((new_rain_odd = new_rain_odd * 85 / 100)) new_rain_odd=$(( new_rain_odd * 85 / 100 ))
((falling_odd = 100)) falling_odd=100
# Adapt cadence and density to terminal size for smoother rendering. # Adapt cadence and density to terminal size for smoother rendering.
if ((term_area < 1200)); then if ((term_area < 1200)); then
((max_rain_width = term_area / 4)) max_rain_width=$(( term_area / 4 ))
frame_sleep=$(LC_ALL=C awk -v s="$step_duration" 'BEGIN { printf "%.3f", s * 1.15 }') frame_sleep=$(LC_ALL=C awk -v s="$step_duration" 'BEGIN { printf "%.3f", s * 1.15 }')
elif ((term_area > 5000)); then elif ((term_area > 5000)); then
((max_rain_width = term_area / 2)) max_rain_width=$(( term_area / 2 ))
frame_sleep=$(LC_ALL=C awk -v s="$step_duration" 'BEGIN { printf "%.3f", s * 0.85 }') frame_sleep=$(LC_ALL=C awk -v s="$step_duration" 'BEGIN { printf "%.3f", s * 0.85 }')
else else
frame_sleep="$step_duration" frame_sleep="$step_duration"
fi fi
;; ;;
*) *)
((max_rain_width = term_area / 4)) max_rain_width=$(( term_area / 4 ))
((max_rain_height = term_height < 10 ? 1 : term_height / 10)) max_rain_height=$(( term_height < 10 ? 1 : term_height / 10 ))
((new_rain_odd = term_height > 50 ? 100 : term_height * 2)) new_rain_odd=$(( term_height > 50 ? 100 : term_height * 2 ))
((new_rain_odd = new_rain_odd * 75 / 100)) new_rain_odd=$(( new_rain_odd * 75 / 100 ))
((falling_odd = term_height > 25 ? 100 : term_height * 4)) falling_odd=$(( term_height > 25 ? 100 : term_height * 4 ))
((falling_odd = falling_odd * 90 / 100)) falling_odd=$(( falling_odd * 90 / 100 ))
frame_sleep="$step_duration" frame_sleep="$step_duration"
;; ;;
esac esac
@@ -275,16 +346,21 @@ _rain_engine()
if [[ -n "$density_override" ]]; then if [[ -n "$density_override" ]]; then
max_rain_width="$density_override" max_rain_width="$density_override"
fi fi
# The drops were placed for the previous size and the screen content
# does not fit the new one: start again from an empty screen.
rains=()
num_rains=0
printf '\e[2J'
} }
do_exit() # Draw one frame. The whole frame (erasing the previous drop positions,
# then the new ones) is built in a string and written with one printf: a
# write per character made the animation slow and flickering.
_rain_render()
{ {
exit_st=1 local idx=0 y=0 drop_color="" current_char="" render_color="" out=""
} local esc=$'\e'
do_render()
{
local idx=0 y=0 drop_color="" current_char="" render_color=""
for ((idx = 0; idx < num_rains * num_rain_metadata; idx += num_rain_metadata)); do for ((idx = 0; idx < num_rains * num_rain_metadata; idx += num_rain_metadata)); do
X=${rains[idx]} X=${rains[idx]}
@@ -292,15 +368,19 @@ _rain_engine()
drop_length=${rains[idx + 4]} drop_length=${rains[idx + 4]}
for ((y = Y; y < Y + drop_length; y++)); do for ((y = Y; y < Y + drop_length; y++)); do
((y < 1 || y > term_height)) && continue ((y < 1 || y > term_height)) && continue
printf "\e[%d;%dH " "$y" "$X" out+="${esc}[${y};${X}H "
done done
done done
for ((idx = 0; idx < num_rains * num_rain_metadata; idx += num_rain_metadata)); do for ((idx = 0; idx < num_rains * num_rain_metadata; idx += num_rain_metadata)); do
if ((100 * RANDOM / 32768 < falling_odd)); then if ((100 * RANDOM / 32768 < falling_odd)); then
if ((++rains[idx + 1] > term_height)); then if ((++rains[idx + 1] > term_height)); then
rains=("${rains[@]:0:idx}" "${rains[@]:idx+num_rain_metadata:num_rains*num_rain_metadata}") # The drop left the screen: remove it. The next drop moves
# into this slot, which must be processed again (the loop
# increment would skip it, and it would vanish for a frame).
rains=("${rains[@]:0:idx}" "${rains[@]:idx+num_rain_metadata}")
num_rains=$(( num_rains - 1 )) num_rains=$(( num_rains - 1 ))
idx=$(( idx - num_rain_metadata ))
continue continue
fi fi
fi fi
@@ -324,27 +404,24 @@ _rain_engine()
current_char="$rain_drop" current_char="$rain_drop"
render_color="$drop_color" render_color="$drop_color"
fi fi
printf "\e[%d;%dH%b%s" "$y" "$X" "$render_color" "$current_char" # render_color holds a literal \e[...m: hence %b below
out+="${esc}[${y};${X}H${render_color}${current_char}"
done done
done done
[[ -n "$out" ]] && printf '%b' "$out"
return 0
} }
trap do_exit TERM INT _rain_enter _rain_sigwinch
trap sigwinch WINCH _rain_sigwinch
stty -echo
printf "\e[?25l"
printf "\e[2J"
local rains=()
local num_rains=0
local ch=""
sigwinch
while ((exit_st <= 0)); do while ((exit_st <= 0)); do
read -r -n 1 -t "$frame_sleep" ch ch=""
# A timeout makes read fail (status > 128): expected, not an error
read -r -n 1 -t "$frame_sleep" ch || true
case "$ch" in case "$ch" in
q|Q) q|Q)
do_exit _rain_stop
;; ;;
esac esac
@@ -358,18 +435,16 @@ _rain_engine()
fi fi
X=$((term_width * RANDOM / 32768 + 1)) X=$((term_width * RANDOM / 32768 + 1))
Y=$((1 - drop_length)) Y=$((1 - drop_length))
rains=("${rains[@]}" "$X" "$Y" "$rain_drop" "$drop_color" "$drop_length") rains+=("$X" "$Y" "$rain_drop" "$drop_color" "$drop_length")
num_rains=$(( num_rains + 1 )) num_rains=$(( num_rains + 1 ))
fi fi
do_render _rain_render
done done
printf "\e[%d;1H\e[0K" "$term_height" _rain_leave
printf "\e[?25h" unset -f _rain_sigwinch _rain_render
stty echo return 0
trap - TERM INT
trap - WINCH
} }
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
@@ -388,13 +463,14 @@ rain()
printf "\t-d, --density NUM\tMaximum number of simultaneous falling elements.\n" printf "\t-d, --density NUM\tMaximum number of simultaneous falling elements.\n"
printf "\t-c, --color COLOR\tSet the color theme (default: white).\n" printf "\t-c, --color COLOR\tSet the color theme (default: white).\n"
printf "\t-h, --help\t\tDisplay this help message and exit.\n\n" printf "\t-h, --help\t\tDisplay this help message and exit.\n\n"
printf "Available Colors:\n" printf "Available Colors (case-insensitive):\n"
printf "\t\e[32mgreen\e[0m\t: Matrix-like green shades\n" printf "\t\e[32mgreen\e[0m\t: Matrix-like green shades\n"
printf "\t\e[34mblue\e[0m\t: Deep ocean blue gradients\n" printf "\t\e[34mblue\e[0m\t: Deep ocean blue gradients\n"
printf "\t\e[31mred\e[0m\t: Crimson/Blood rain\n" printf "\t\e[31mred\e[0m\t: Crimson/Blood rain\n"
printf "\t\e[33myellow\e[0m\t: Amber and gold tones\n" printf "\t\e[33myellow\e[0m\t: Amber and gold tones\n"
printf "\t\e[36mcyan\e[0m\t: Electric cyan/turquoise\n" printf "\t\e[36mcyan\e[0m\t: Electric cyan/turquoise\n"
printf "\twhite\t: Greyscale and white (original style)\n\n" printf "\twhite\t: Greyscale and white (original style)\n\n"
printf "Press q (or Ctrl-C) to quit.\n"
printf "Example: rain --color green --speed 3\n" printf "Example: rain --color green --speed 3\n"
} }
@@ -413,13 +489,13 @@ rain()
-s|--speed) -s|--speed)
if [[ -n "$2" && ! "$2" =~ ^- ]]; then if [[ -n "$2" && ! "$2" =~ ^- ]]; then
step_duration=$(_rain_normalize_speed "$2") || { step_duration=$(_rain_normalize_speed "$2") || {
disp E "--speed requires a numeric value." disp E "--speed requires a numeric value greater than 0."
_rain_show_usage _rain_show_usage
return 1 return 1
} }
shift shift
else else
disp E "--speed requires a numeric value." disp E "--speed requires a numeric value greater than 0."
_rain_show_usage _rain_show_usage
return 1 return 1
fi fi
@@ -486,6 +562,7 @@ matrix()
printf "\t-c, --color COLOR\tSet color theme (default: green).\n" printf "\t-c, --color COLOR\tSet color theme (default: green).\n"
printf "\t-C, --charset SET\tCharacter set: binary, kana, ascii (default: binary).\n" printf "\t-C, --charset SET\tCharacter set: binary, kana, ascii (default: binary).\n"
printf "\t-h, --help\t\tDisplay this help message and exit.\n\n" printf "\t-h, --help\t\tDisplay this help message and exit.\n\n"
printf "Press q (or Ctrl-C) to quit.\n"
printf "Example: matrix -C kana -c green --speed 2\n" printf "Example: matrix -C kana -c green --speed 2\n"
} }
@@ -493,7 +570,9 @@ matrix()
local step_duration local step_duration
step_duration=$(_rain_normalize_speed "$_raw_speed") || step_duration=0.035 step_duration=$(_rain_normalize_speed "$_raw_speed") || step_duration=0.035
local base_color="${MATRIX_DEFAULT_COLOR:-green}" local base_color="${MATRIX_DEFAULT_COLOR:-green}"
# Lower-cased, like the value of -C: the configuration may say "Kana"
local charset="${MATRIX_DEFAULT_CHARSET:-binary}" local charset="${MATRIX_DEFAULT_CHARSET:-binary}"
charset="${charset,,}"
local density_override="${MATRIX_DEFAULT_DENSITY:-}" local density_override="${MATRIX_DEFAULT_DENSITY:-}"
if [[ -n "$density_override" ]]; then if [[ -n "$density_override" ]]; then
@@ -505,13 +584,13 @@ matrix()
-s|--speed) -s|--speed)
if [[ -n "$2" && ! "$2" =~ ^- ]]; then if [[ -n "$2" && ! "$2" =~ ^- ]]; then
step_duration=$(_rain_normalize_speed "$2") || { step_duration=$(_rain_normalize_speed "$2") || {
disp E "--speed requires a numeric value." disp E "--speed requires a numeric value greater than 0."
_matrix_show_usage _matrix_show_usage
return 1 return 1
} }
shift shift
else else
disp E "--speed requires a numeric value." disp E "--speed requires a numeric value greater than 0."
_matrix_show_usage _matrix_show_usage
return 1 return 1
fi fi
@@ -597,9 +676,8 @@ rainbow()
local frame_sleep local frame_sleep
frame_sleep=$(_rain_normalize_speed "$_raw_speed") || frame_sleep=0.040 frame_sleep=$(_rain_normalize_speed "$_raw_speed") || frame_sleep=0.040
local -a _pargs=() # Restore the getopt-normalised (shell-quoted) argument list
readarray -td '' _pargs < <(printf '%s' "$PARSED" | xargs printf '%s\0') eval set -- "$PARSED"
set -- "${_pargs[@]}"
while true; do while true; do
case "$1" in case "$1" in
-h|--help) -h|--help)
@@ -611,12 +689,12 @@ rainbow()
printf "\t-h, --help\t\tDisplay this help message and exit.\n\n" printf "\t-h, --help\t\tDisplay this help message and exit.\n\n"
printf "The rainbow fills the whole terminal with background colors only.\n" printf "The rainbow fills the whole terminal with background colors only.\n"
printf "It uses truecolor when supported, otherwise ANSI bright backgrounds.\n" printf "It uses truecolor when supported, otherwise ANSI bright backgrounds.\n"
printf "Press q to quit.\n" printf "Press q (or Ctrl-C) to quit.\n"
return 0 return 0
;; ;;
-s|--speed) -s|--speed)
frame_sleep=$(_rain_normalize_speed "$2") || { frame_sleep=$(_rain_normalize_speed "$2") || {
disp E "--speed requires a numeric value." disp E "--speed requires a numeric value greater than 0."
return 1 return 1
} }
shift 2 shift 2
@@ -632,18 +710,17 @@ rainbow()
esac esac
done done
command -v tput >/dev/null 2>&1 || { _rain_need_tty rainbow || return 1
disp E "The program 'tput' is required but not installed."
return 1
}
local exit_st=0 local exit_st=0
local term_height=0 term_width=0 local term_height=0 term_width=0
local use_truecolor=0 local use_truecolor=0
local offset=0 row=0 col=0 palette_width=0 idx=0 local offset=0 row=0 col=0 palette_width=0 idx=0
local row_line="" ch="" local row_line="" screen="" ch=""
local saved_stty="" saved_traps=""
sigwinch() # WINCH handler (and initial sizing): rebuild the palette for the new width
_rainbow_sigwinch()
{ {
term_width=$(tput cols) term_width=$(tput cols)
term_height=$(tput lines) term_height=$(tput lines)
@@ -657,52 +734,45 @@ rainbow()
fi fi
palette_width=$term_width palette_width=$term_width
offset=0
_rainbow_build_palette "$palette_width" "$use_truecolor" _rainbow_build_palette "$palette_width" "$use_truecolor"
} }
do_exit() _rain_enter _rainbow_sigwinch
{ _rainbow_sigwinch
exit_st=1
}
trap do_exit TERM INT
trap sigwinch WINCH
stty -echo
printf "\e[?25l"
printf "\e[2J"
sigwinch
while ((exit_st <= 0)); do while ((exit_st <= 0)); do
read -r -n 1 -t "$frame_sleep" ch ch=""
# A timeout makes read fail (status > 128): expected, not an error
read -r -n 1 -t "$frame_sleep" ch || true
case "$ch" in case "$ch" in
q|Q) q|Q)
do_exit _rain_stop
;; ;;
esac esac
# One row of colours, shifted by offset (col - offset is at least
# 1 - palette_width, so adding palette_width makes it positive)
row_line="" row_line=""
for ((col = 0; col < term_width; col++)); do for ((col = 0; col < term_width; col++)); do
idx=$((col - offset)) idx=$(( (col - offset + palette_width) % palette_width ))
while ((idx < 0)); do
idx=$(( idx + palette_width ))
done
idx=$((idx % palette_width))
row_line+="${RAINBOW_BG_PALETTE[idx]} " row_line+="${RAINBOW_BG_PALETTE[idx]} "
done done
row_line+=$'\e[0m' row_line+=$'\e[0m'
# Every row is the same: the whole screen goes out in one write
screen=""
for ((row = 1; row <= term_height; row++)); do for ((row = 1; row <= term_height; row++)); do
printf "\e[%d;1H%b" "$row" "$row_line" screen+="\e[${row};1H${row_line}"
done
printf '%b' "$screen"
offset=$(( (offset + 1) % palette_width ))
done done
((offset = (offset + 1) % palette_width)) _rain_leave
done unset -f _rainbow_sigwinch
unset RAINBOW_BG_PALETTE
printf "\e[0m\e[2J\e[H" return 0
printf "\e[?25h"
stty echo
trap - TERM INT
trap - WINCH
} }
export -f rainbow export -f rainbow
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------