# shellcheck shell=bash # # The interactive front end. # # This is the whole configuration interface. There is a file behind it, but no # part of the program ever asks anyone to open it: one switch on the front # screen, a settings list for the categories, and a list of the applications # themselves for the cases where the automatic answer is not the wanted one. # Terminal mode. # # bash flips the terminal into non-canonical mode for each `read -sn1` and back # out again in between. That gap matters: in canonical mode DEL is the ERASE # character, so the line discipline eats it instead of delivering it, and a # backspace typed while the interface was between reads simply vanishes. # Holding non-canonical mode for the whole interface removes the gap. MCA_TERM_SAVED='' mca_ui_term_raw() { mca_have stty || return 0 [[ -t 0 ]] || return 0 [[ -n $MCA_TERM_SAVED ]] && return 0 MCA_TERM_SAVED="$(stty -g 2>/dev/null)" || { MCA_TERM_SAVED=''; return 0; } stty -icanon -echo min 1 time 0 2>/dev/null || true } mca_ui_term_restore() { [[ -n $MCA_TERM_SAVED ]] || return 0 stty "$MCA_TERM_SAVED" 2>/dev/null || true MCA_TERM_SAVED='' } # What the last action printed. The menu shows it under the status until the # next key, so nothing has to wait for a key on a screen of its own. MCA_UI_MSG='' # Set while the menu runs. Its status block already says that running # applications need a restart, so the actions leave that line out there. MCA_IN_MENU='' # mca_ui_run # Runs an action in this shell (so what it scans and loads stays) with its # output kept for the menu. None of them asks anything. mca_ui_run() { local tmp printf ' %s%s%s\n' "$MCA_C_DIM" "$(mca_msg "One moment...")" "$MCA_C_RESET" tmp="$(mktemp)" || { "$@"; return; } "$@" > "$tmp" 2>&1 MCA_UI_MSG="$(< "$tmp")" rm -f -- "$tmp" } # mca_read_key # One keypress, resolved to a symbolic name. Arrow keys arrive as ESC [ A, so # the tail of the sequence is consumed here rather than being mistaken for # three separate presses. mca_read_key() { local k rest IFS= read -rsn1 k || return 1 case "$k" in $'\e') if IFS= read -rsn2 -t 0.05 rest; then case "$rest" in '[A') printf 'up\n' ;; '[B') printf 'down\n' ;; '[C') printf 'right\n' ;; '[D') printf 'left\n' ;; *) printf 'escape\n' ;; esac else printf 'escape\n' fi ;; ''|$'\r') printf 'enter\n' ;; $'\x7f'|$'\b') printf 'backspace\n' ;; ' ') printf 'space\n' ;; *) printf '%s\n' "$k" ;; esac } # mca_ui_read_line # A minimal line editor built on mca_read_key, with the result in # MCA_LINE_RESULT. This exists instead of bash's own `read -r` because mixing # line mode into a single-key interface breaks it: after one cooked-mode read # the following `read -sn1` stops receiving keystrokes entirely. MCA_LINE_RESULT='' mca_ui_read_line() { local buf="${1:-}" key MCA_LINE_RESULT='' printf '%s' "$buf" while true; do key="$(mca_read_key)" || { printf '\n'; return 1; } case "$key" in enter) printf '\n' MCA_LINE_RESULT="$buf" return 0 ;; escape) printf '\n' return 1 ;; backspace) if [[ -n $buf ]]; then buf="${buf%?}" printf '\b \b' fi ;; space) buf+=' ' printf ' ' ;; up|down|left|right) ;; *) [[ ${#key} -eq 1 ]] || continue buf+="$key" printf '%s' "$key" ;; esac done } mca_pause() { printf '\n %s' "$(mca_msg "Press any key to continue...")" read -rsn1 _ || true printf '\n' } # _mca_row