summaryrefslogtreecommitdiff
path: root/src/read_chars.bash
blob: 5856e92ef14ec521c3cd313227765ff54431d207 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# =================
#  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
}