summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rwxr-xr-xsrc/twopane.bash424
1 files changed, 424 insertions, 0 deletions
diff --git a/src/twopane.bash b/src/twopane.bash
new file mode 100755
index 0000000..39a6bfb
--- /dev/null
+++ b/src/twopane.bash
@@ -0,0 +1,424 @@
1#!/bin/bash
2BASH_ARGV0=twopane.bash
3echo -n "$0" >/proc/$$/comm
4set -e
5set -f
6set -o pipefail
7shopt -s lastpipe
8
9BOT_SIZE=8
10BOT_TITLE=input
11
12if [ $# = 0 ]
13then
14 TOP_CMD="$SHELL -i"
15 BOT_CMD=background
16 TOP_EXIT=prompt
17 #BOT_CMD=start
18 #TOP_EXIT=restart
19else
20 TOP_CMD=$*
21 BOT_CMD=start
22 TOP_EXIT=quit
23fi
24TOP_TITLE="Command: $TOP_CMD"
25
26TWOPANE=$(mktemp -d)
27export TWOPANE TOP_CMD BOT_CMD TOP_TITLE BOT_TITLE BOT_SIZE
28trap 'rm -r "$TWOPANE"' EXIT
29STY=twopane.${TWOPANE##*/}
30
31save_file()
32{
33 cat > "$TWOPANE"/"${1:?$0: Error: filename cannot be empty string}"
34}
35
36save_screenrc()
37{
38 save_file screenrc"${1:+.$1}"
39}
40
41save_screenrc <<'.'
42# Disable keybindings.
43unbindall
44escape \0\0
45# Disable messages. This is needed for screen -X/-Q to work reasonably.
46msgwait 0
47msgminwait 0
48# Try to disable blocking the terminal on ^S.
49# Doesn't seem to work here.
50# More is needed. Xterm?
51nonblock on
52
53caption string '%t'
54layout new
55split
56focus bottom
57resize $BOT_SIZE
58screen -ln -t "$BOT_TITLE" 0 bash --noprofile --rcfile "$TWOPANE"/bashrc -i
59layout save 0
60.
61
62check_top()
63{
64 screen -p1 -Q info >/dev/null
65}
66
67kill_top()
68{
69 while check_top
70 do
71 screen -p1 -X kill
72 done
73}
74
75start_top()
76{
77 if check_top
78 then
79 return
80 fi
81 if [ $# = 0 ]
82 then
83 set -f
84 set -- ${TOP_CMD:-bash -i}
85 fi
86 TOP_TITLE="Command: ${*@Q}"
87 screen -X focus top
88 screen -X screen -ln -t "$TOP_TITLE" 1 "$@"
89 screen -p1 -X exec .!. sh -c 'exec socat UNIX-LISTEN:"$TWOPANE"/socket STDIN,cfmakeraw!!STDOUT'
90}
91
92restart_top()
93{
94 kill_top
95 start_top "$@"
96}
97
98socat_connect()
99{
100 socat STDIN!!STDOUT UNIX-CONNECT:"$TWOPANE"/socket,forever
101}
102
103# Start SOCAT if necessary.
104# Connect the running SOCAT to file descriptors.
105# Optionally assign the file descriptors to the specified variables.
106# Optionally assign the socat PID to the specified variable.
107#
108# The copied file descriptors (unlike the original coprocess file
109# descriptors, in ${SOCAT[0]} and ${SOCAT[1]}) can be passed to external
110# processes (e.g.: other socat(1) instances) and used in subshells.
111connect()
112{
113 case $# in
114 3 | 5 )
115 declare -n pid="$1" std0="$2" std1="$3"
116 shift 3
117 ;;
118 2 | 4 )
119 local pid
120 declare -n std0="$1" std1="$2"
121 shift 2
122 ;;
123 0 )
124 local std0 std1 pid
125 ;;
126 * )
127 return 1
128 ;;
129 esac
130 if ! [ "${SOCAT[0]}" ]
131 then
132 local STDERR
133 exec {STDERR}>&2
134 {
135 coproc SOCAT { socat_connect; } 2>&$STDERR
136 } 2>/dev/null
137 eval "exec $STDERR>&-"
138 fi
139 pid=${SOCAT_PID}
140 exec {std0}<&${SOCAT[0]} {std1}>&${SOCAT[1]}
141 disown
142}
143
144sendc()
145{
146 [ "${SOCAT[0]}" ] || connect
147 printf '%s' "$*" >&${SOCAT[1]}
148}
149
150send()
151{
152 [ "${SOCAT[0]}" ] || connect
153 printf '%s\n' "$*" >&${SOCAT[1]}
154}
155
156disconnect()
157{
158 if [ $# = 2 ]
159 then
160 declare -i -n std0="$1" std1="$2"
161 eval "exec $std0<&- $std1>&-"
162 unset std0 std1
163 fi
164 wait -f "$SOCAT_PID" 2>/dev/null
165}
166
167restart()
168{
169 start "$@"
170}
171
172start()
173{
174 foreground "$@"
175}
176
177background()
178{
179 start_top "$@"
180 focus bottom
181 connect stdin stdout
182}
183
184foreground_loop()
185{
186 while true
187 do
188 start_top "$@"
189 connect stdin stdout
190 forward
191 disconnect stdin stdout
192
193 case "$TOP_EXIT" in
194 restart )
195 continue ;;
196 quit )
197 exit ;;
198 prompt | * )
199 focus bottom
200 break
201 ;;
202 esac
203 done
204}
205
206forwarding()
207{
208 [ "$FORWARD_PID" ] || return
209 FORWARD_JOBSPEC=$(jobs -sl | pid_to_jobspec "$FORWARD_PID")
210 [ "$FORWARD_JOBSPEC" ]
211}
212
213forward()
214{
215 declare -g FORWARD_PID
216 if ! check_top
217 then
218 echo "$0: Warning: Nothing to forward. Starting anew." >&2
219 background "$@"
220 elif forwarding
221 then
222 resume_forward
223 return
224 fi
225
226 focus top
227 old_stty=$(stty -g)
228 # Lowercase $stdin/$stdout are the SOCAT coprocess connected to
229 # the other pane's terminal. Uppercase $STDIN/$STDOUT are the
230 # real stdin/stdout of this function, connected to the lower
231 # pane's terminal. Socat here merges inputs from both sources.
232 exec {STDIN}<&0 {STDOUT}>&1 {STDERR}>&2
233
234 # The input is put out raw back over the socket. The input is
235 # copied to stdout after being filtered (to display control
236 # characters with carrot-encoding like '^[' etc).
237 exec {TEE}> >(tee >(output_filter >&$STDOUT) >&$stdout)
238 stty=cfmakeraw,opost=1,onlcr=1
239 {
240 socat FD:$STDIN,$stty!!STDOUT - <&$stdin >&$TEE 2>&$STDERR &
241 } 2>/dev/null
242 FORWARD_PID=$!
243 printf '%s\n' "#!/bin/bash" "kill $!" "screen -X focus bottom" > "$TWOPANE"/unforward
244 chmod +x "$TWOPANE"/unforward
245 fg >/dev/null
246 stty "$old_stty"
247 focus bottom
248 echo
249}
250
251cfmakeraw()
252{
253 cmd=(stty -ignbrk -brkint -parmrk -istrip -inlcr -igncr -icrnl
254 -ixon -opost -echo -echonl -icanon -isig -iexten -parenb cs8)
255 "${cmd[@]}" "$@"
256}
257
258pid_to_jobspec()
259{
260 while read jspec pid status cmd
261 do
262 [ "$pid" = "$1" ] || continue
263 jspec=${jspec%]*}
264 jspec=%${jspec#[}
265 echo $jspec
266 return
267 done
268 false
269}
270
271resume_forward()
272{
273 old_stty=$(stty -g)
274 cfmakeraw opost onlcr
275 focus top
276 fg "$FORWARD_JOBSPEC" >/dev/null
277 stty "$old_stty"
278 focus bottom
279}
280
281foreground()
282{
283 if forwarding
284 then
285 resume_forward
286 else
287 foreground_loop "$@"
288 fi
289}
290
291twopane()
292{
293 start "$@"
294}
295
296quit()
297{
298 screen -X quit
299}
300
301focus()
302{
303 screen -X focus "$@"
304}
305
306output_filter()
307{
308 tokenize | colorize | soft_cursor
309}
310
311# if (show_nonprinting) {
312# while (true)
313# {
314# if (ch >= 32) {
315# if (ch < 127) *bpout++ = ch;
316# else if (ch == 127) {*bpout++ = '^'; *bpout++ = '?';}
317# else {
318# *bpout++ = 'M';
319# *bpout++ = '-';
320# if (ch >= 128 + 32) {
321# if (ch < 128 + 127) *bpout++ = ch - 128;
322# else {*bpout++ = '^'; *bpout++ = '?';}}
323# else {*bpout++ = '^'; *bpout++ = ch - 128 + 64;}}}
324# else if (ch == '\t' && !show_tabs) *bpout++ = '\t';
325# else if (ch == '\n') {newlines = -1; break;}
326# else {*bpout++ = '^'; *bpout++ = ch + 64;}
327# ch = *bpin++;}}
328
329chr()
330{
331 declare -i n="$*"
332 printf "\\$(printf %o "$n")"
333}
334
335colorize()
336{
337 while read -r
338 do
339 case "$REPLY" in
340 \\[0-7][0-7][0-7] ) ;;
341 * )
342 printf '%s\n' "$REPLY"
343 continue
344 ;;
345 esac
346 declare -i c=8#"${REPLY#?}"
347 if (( c > 128 + 127 ))
348 then
349 :
350 elif (( c > 128 + 32 ))
351 then
352 printf -v REPLY "M-$(chr c - 128)"
353 elif (( c > 127 ))
354 then
355 printf -v REPLY "M-^$(chr c - 128 + 64)"
356 elif (( c < 32 ))
357 then
358 printf -v REPLY "^$(chr c + 64)"
359 fi
360 printf $'\e[1m%s\e[m\n' "$REPLY"
361 done
362}
363
364tokenize()
365{
366 while read -r -N1
367 do
368 if [[ "$REPLY" =~ [[:print:]] ]]
369 then
370 # Output one printable character per line. It may be a
371 # multibyte unicode character.
372 printf '%s\n' "$REPLY"
373 continue
374 else
375 # If it is a non-printable, then we output a
376 # multi-character line. In this case we colorize it
377 # later so that it won't be confused with multiple
378 # printable characters.
379 printf '\\%.3o\n' "'$REPLY"
380 continue
381 fi
382 done
383}
384
385soft_cursor()
386{
387 FMT=$'%s \b\e[%sm \e[m\b'
388 REPLY=
389 while printf "$FMT" "$REPLY" $(( 101 + RANDOM % 7 ))
390 do
391 read -r || break
392 done
393}
394
395our_bashrc_main()
396{
397 set -f
398 set -o pipefail
399 trap "screen -X quit" EXIT
400 export PS1="$BOT_TITLE\\\$ "
401}
402
403save_file bashrc <<.
404BASH_ARGV0=twopane
405echo -n "\$0" >/proc/\$\$/comm
406${TOP_CMD@A}
407${BOT_CMD@A}
408${TOP_TITLE@A}
409${BOT_TITLE@A}
410
411${TOP_EXIT@A}
412
413$(declare -f)
414our_bashrc_main
415
416$BOT_CMD
417.
418
419main()
420{
421 screen -c "$TWOPANE"/screenrc -m -S "$STY" -ln
422}
423
424main "$@"