# ================= # read_chars.bash # ================= # # Bash library for reading from tty and writing to standard out. # Can be suspended and resumed from bash (or with unix job control in # any shell). # # exporting for users: # # read_chars() <-- calls readchar in a loop # # readchar() <-- reads one character # # readchar_init() <-- must be called before readchar() in the same subshell # # The function read_chars is also available as the executable read-tty cfmakeraw() { cmd=(stty -ignbrk -brkint -parmrk -istrip -inlcr -igncr -icrnl -ixon -opost -echo -echonl -icanon -isig -iexten -parenb cs8) "${cmd[@]}" "$@" >/dev/null } i() { if [ "$DEBUG" ] then printf 'I: %s\n' "$*" >&2 fi } x() { if [ "$DEBUG" ] then printf '+ %s\n' "${*@Q}" >&2 fi "$@" } readchar_init() { declare -g SOCAT= SOCAT_PID= declare -g t declare -g -a readchar_termopts [ -t 1 ] && t=y || t= readchar_termopts=( opost onlcr isig intr undef quit undef ) trap "i EXIT; kill $SOCAT_PID 2>/dev/null; kill -CONT $SOCAT_PID 2>/dev/null" EXIT trap "i CONT; readchar_SIGCONT" CONT for sig in TSTP TTIN TTOU do trap "i $sig; x kill -STOP \$BASHPID \$SOCAT_PID 2>/dev/null" $sig done for sig in INT TERM do trap "i $sig; exit" $sig done } readchar_SIGCONT() { if check_foreground then if [ -t 0 ] then x cfmakeraw "${readchar_termopts[@]}" fi x kill -CONT $SOCAT_PID 2>/dev/null else x kill -STOP $BASHPID $SOCAT_PID 2>/dev/null fi } check_foreground() { read _pid _comm _state _ppid pgrp _session _tty_nr tpgid _rest < /proc/self/stat [ "$tpgid" = "$pgrp" ] } SOCAT() { #BASH_ARGV0=socat-tty-sleeper #echo -n "$0" >/proc/$BASHPID/comm #trap 'sleep .25' SIGTSTP #trap 'kill -INT $BASHPID 0' SIGTTIN SIGTTOU exec -a user-ended-connection socat - - } readchar() { declare -n REPLY="${1:-CHARACTER}" if [ -t 0 ] then if check_foreground then if ! [ "$SOCAT_PID" ] then x cfmakeraw "${readchar_termopts[@]}" fi else sleep .25 return fi fi if ! [ "$SOCAT_PID" ] then exec {SOCAT}< <(SOCAT) SOCAT_PID=$! fi if POSIXLY_CORRECT=y read -n 1 -d '' -r -s -u "$SOCAT" then if [ "$t" ] then printf -v quoted_reply "%s" "${REPLY@Q}" if (( (count += 1 + ${#quoted_reply}) < ${COLUMNS:-80} - 10 )) then seperate=' ' else seperate=$'\n' count=0 fi printf "%s" "$seperate" "$quoted_reply" else if [ "$REPLY" ] then printf "%s" "$REPLY" else printf "\0" fi fi else [ $? -gt 128 ] fi } read_chars() { readchar_init while readchar do continue done }