#!/bin/bash set -e set -f set -o pipefail shopt -s lastpipe PROGNAME=${0##*/} BOT_SIZE=8 BOT_TITLE=input if [ $# = 0 ] then TOP_CMD="$SHELL -i" BOT_CMD=background TOP_EXIT=prompt #BOT_CMD=start #TOP_EXIT=restart else TOP_CMD=$* BOT_CMD=start TOP_EXIT=quit fi TOP_TITLE="Command: $TOP_CMD" TWOPANE=$(mktemp -d) export TWOPANE TOP_CMD BOT_CMD TOP_TITLE BOT_TITLE BOT_SIZE trap 'rm -r "$TWOPANE"' EXIT STY=twopane.${TWOPANE##*/} save_file() { cat > "$TWOPANE"/"${1:?$PROGNAME: Error: filename cannot be empty string}" } save_screenrc() { save_file screenrc"${1:+.$1}" } save_screenrc <<'.' # Disable keybindings. unbindall escape \0\0 # Disable messages. This is needed for screen -X/-Q to work reasonably. msgwait 0 msgminwait 0 # Try to disable blocking the terminal on ^S. # Doesn't seem to work here. # More is needed. Xterm? nonblock on caption string '%t' layout new split focus bottom resize $BOT_SIZE screen -ln -t "$BOT_TITLE" 0 bash --noprofile --rcfile "$TWOPANE"/bashrc -i layout save 0 . check_top() { screen -p1 -Q info >/dev/null } kill_top() { while check_top do screen -p1 -X kill done } start_top() { if check_top then return fi if [ $# = 0 ] then set -f set -- ${TOP_CMD:-bash -i} fi TOP_TITLE="Command: ${*@Q}" screen -X focus top screen -X screen -ln -t "$TOP_TITLE" 1 "$@" screen -p1 -X exec .!. sh -c 'exec socat UNIX-LISTEN:"$TWOPANE"/socket STDIN,cfmakeraw!!STDOUT' } restart_top() { kill_top start_top "$@" } socat_connect() { socat STDIN!!STDOUT UNIX-CONNECT:"$TWOPANE"/socket,forever } # Start SOCAT if necessary. # Connect the running SOCAT to file descriptors. # Optionally assign the file descriptors to the specified variables. # Optionally assign the socat PID to the specified variable. # # The copied file descriptors (unlike the original coprocess file # descriptors, in ${SOCAT[0]} and ${SOCAT[1]}) can be passed to external # processes (e.g.: other socat(1) instances) and used in subshells. connect() { case $# in 3 | 5 ) declare -n pid="$1" std0="$2" std1="$3" shift 3 ;; 2 | 4 ) local pid declare -n std0="$1" std1="$2" shift 2 ;; 0 ) local std0 std1 pid ;; * ) return 1 ;; esac if ! [ "${SOCAT[0]}" ] then coproc SOCAT { socat_connect; } fi pid=${SOCAT_PID} eval "exec {std0}<&${SOCAT[0]} {std1}>&${SOCAT[1]}" } sendc() { [ "${SOCAT[0]}" ] || connect printf '%s' "$*" >&${SOCAT[1]} } send() { [ "${SOCAT[0]}" ] || connect printf '%s\n' "$*" >&${SOCAT[1]} } disconnect() { if [ $# = 2 ] then declare -n std0="$1" std1="$2" eval "exec $std0<&- $std1>&-" unset std0 std1 fi wait -f "$SOCAT_PID" 2>/dev/null } restart() { start "$@" } start() { foreground "$@" } background() { start_top "$@" focus bottom connect stdin stdout } foreground() { while true do start_top "$@" connect stdin stdout forward disconnect stdin stdout case "$TOP_EXIT" in restart ) continue ;; quit ) exit ;; prompt | * ) focus bottom break ;; esac done } forward() { forward_input "$@" } forward_input() { if ! check_top then echo "$0: Warning: Nothing to forward. Starting anew." >&2 background "$@" fi focus top socat FD:100,cfmakeraw!!STDOUT - <&$stdin | tee >(output_filter | soft_cursor >&101) >&$stdout stty "$old_stty" focus bottom } quit() { screen -X quit } focus() { screen -X focus "$@" } output_filter() { exec {OUTPUT_FILTER}> >(exec cat -v) while read -r -N1 do case "$REPLY" in # Encode literal '^' as escape. This allows # a '^' in the output to be interpreted # unambiguously as a control sequence later # downstream, where it will be displayed in # bold. '^' ) echo -n $'\e' continue ;; $'\t' ) echo -n '^I' continue ;; $'\n' ) echo -n '^J' continue ;; esac # Add unicode support to cat through bash primitives! if [[ "$REPLY" =~ [[:print:]] ]] then printf '%s' "$REPLY" else printf '%s' "$REPLY" >&${OUTPUT_FILTER} fi done } soft_cursor() { FMT=$'%s \b\e[%sm \e[m\b' REPLY= while printf "$FMT" "$REPLY" $(( 101 + RANDOM % 7 )) do read -r -N1 || break case "$REPLY" in '^' ) read -r -N1 || break REPLY=$'\e[1m^'$REPLY$'\e[m' ;; $'\e' ) REPLY='^' ;; esac done } our_bashrc_main() { set -f set -o pipefail shopt -s lastpipe trap "screen -X quit" EXIT export PS1="$BOT_TITLE\\\$ " } save_file bashrc <<. BASH_ARGV0=twopane ${TOP_CMD@A} ${BOT_CMD@A} ${TOP_TITLE@A} ${BOT_TITLE@A} ${TOP_EXIT@A} $(declare -f) our_bashrc_main \$BOT_CMD . main() { screen -c "$TWOPANE"/screenrc -m -S "$STY" -ln } main "$@"