# 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='' } # Runs an action with the terminal handed back to normal line mode, so anything # it prints - or prompts for - behaves the way a program expects. mca_ui_cooked() { mca_ui_term_restore "$@" local rc=$? mca_ui_term_raw return $rc } # 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