#!/usr/bin/env bash # TUI timer setter — vim keys, unicode frames, labeled fields # Usage: timer.sh [-v|--verbose] SCRIPT_DIR="$(cd "$(dirname "$(realpath "$0")")" && pwd)" VERBOSE=0 for arg in "$@"; do [[ "$arg" == "-v" || "$arg" == "--verbose" ]] && VERBOSE=1 done cleanup() { tput cnorm; tput rmcup; } trap cleanup EXIT INT TERM tput smcup tput civis R="$(tput sgr0)" NORMAL="$(tput setaf 7)" SELECT="$(tput bold)$(tput setaf 6)" TITLE="$(tput bold)$(tput setaf 5)" DIM="$(tput dim)$(tput setaf 7)" ERR="$(tput bold)$(tput setaf 1)" VALUES=(0 0 0) LIMITS=(23 59 59) LABELS=("HOURS" "MINUTES" "SECONDS") FIELD=0 SHOW_ERR=0 KEY="" # draw_box row col val label active # Box inner width = 10, total = 12, height = 6 draw_box() { local y=$1 x=$2 val=$3 label=$4 active=$5 local num; printf -v num '%02d' "$val" local inner=10 local tl tr bl br bh bv color if [[ $active -eq 1 ]]; then tl='╔' tr='╗' bl='╚' br='╝' bh='═' bv='║' color=$SELECT else tl='┌' tr='┐' bl='└' br='┘' bh='─' bv='│' color=$NORMAL fi local hline; printf -v hline '%*s' $inner ''; hline="${hline// /$bh}" local lpad=$(( (inner - ${#label}) / 2 )) local lrpad=$(( inner - lpad - ${#label} )) local npad=$(( (inner - ${#num}) / 2 )) local nrpad=$(( inner - npad - ${#num} )) tput cup $y $x; printf '%s%s%s%s%s' "$color" "$tl" "$hline" "$tr" "$R" tput cup $((y+1)) $x; printf '%s%s%*s%s%*s%s%s' "$color" "$bv" $lpad '' "$label" $lrpad '' "$bv" "$R" tput cup $((y+2)) $x; printf '%s%s%*s%s%s' "$color" "$bv" $inner '' "$bv" "$R" tput cup $((y+3)) $x; printf '%s%s%*s%s%*s%s%s' "$color" "$bv" $npad '' "$num" $nrpad '' "$bv" "$R" tput cup $((y+4)) $x; printf '%s%s%*s%s%s' "$color" "$bv" $inner '' "$bv" "$R" tput cup $((y+5)) $x; printf '%s%s%s%s%s' "$color" "$bl" "$hline" "$br" "$R" } draw_colon() { local y=$1 x=$2 tput cup $((y+3)) $x printf '%s ∶ %s' "$DIM" "$R" } draw() { local W H W=$(tput cols); H=$(tput lines) printf '\033[2J\033[H' local title="─── T I M E R ───" tput cup 2 $(( (W - ${#title}) / 2 )) printf '%s%s%s' "$TITLE" "$title" "$R" # 3 boxes × 12 wide + 2 colons × 3 wide = 42 total local total=42 local sx=$(( (W - total) / 2 )) local dy=$(( (H - 6) / 2 )) (( dy < 4 )) && dy=4 local -a bx=( $sx $((sx + 15)) $((sx + 30)) ) for i in 0 1 2; do local active=0; [[ $i -eq $FIELD ]] && active=1 draw_box $dy "${bx[$i]}" "${VALUES[$i]}" "${LABELS[$i]}" $active done draw_colon $dy $((sx + 12)) draw_colon $dy $((sx + 27)) if [[ $SHOW_ERR -eq 1 ]]; then local msg="set a non-zero time first" tput cup $((dy + 7)) $(( (W - ${#msg}) / 2 )) printf '%s%s%s' "$ERR" "$msg" "$R" fi local verbose_tag=""; [[ $VERBOSE -eq 1 ]] && verbose_tag=" [verbose]" local hint="h/l field j/k adjust Enter start q cancel${verbose_tag}" tput cup $((H - 2)) $(( (W - ${#hint}) / 2 )) printf '%s%s%s' "$DIM" "$hint" "$R" } read_key() { local seq IFS= read -rsn1 KEY if [[ "$KEY" == $'\033' ]]; then IFS= read -rsn2 -t 0.05 seq 2>/dev/null || true KEY="$KEY$seq" fi } while true; do draw SHOW_ERR=0 read_key case "$KEY" in q|$'\033') exit 0 ;; h|$'\033[D') FIELD=$(( (FIELD + 2) % 3 )) ;; l|$'\033[C') FIELD=$(( (FIELD + 1) % 3 )) ;; k|$'\033[A') VALUES[$FIELD]=$(( (VALUES[$FIELD] + 1) % (LIMITS[$FIELD] + 1) )) ;; j|$'\033[B') VALUES[$FIELD]=$(( (VALUES[$FIELD] - 1 + LIMITS[$FIELD] + 1) % (LIMITS[$FIELD] + 1) )) ;; $'\n'|$'\r') total=$(( VALUES[0] * 3600 + VALUES[1] * 60 + VALUES[2] )) if (( total == 0 )); then SHOW_ERR=1 else label=$(printf '%02d:%02d:%02d' "${VALUES[0]}" "${VALUES[1]}" "${VALUES[2]}") if [[ $VERBOSE -eq 1 ]]; then kitty --title "Timer: $label" bash "$SCRIPT_DIR/timer-notify.sh" "$total" "$label" --verbose & disown else setsid bash "$SCRIPT_DIR/timer-notify.sh" "$total" "$label" &>/dev/null & disown fi exit 0 fi ;; esac done