#!/bin/bash set -e set -f set -o pipefail shopt -s lastpipe PROGNAME=${0##*/} BOT_SIZE=8 BOT_TITLE=input BOT_CMD=start if [ $# = 0 ] then TOP_CMD="$SHELL -i" else TOP_CMD=$* 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 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 . kill_top() { while screen -p1 -Q info >/dev/null do screen -p1 -X kill done } start_top() { if [ $# -gt 0 ] then TOP_CMD=$* TOP_TITLE="Command: $TOP_CMD" fi screen -X focus top screen -X screen -ln -t "$TOP_TITLE" 1 $TOP_CMD screen -p1 -X exec .!. sh -c 'exec socat UNIX-LISTEN:"$TWOPANE"/socket STDIN,rawer!!STDOUT' } restart_top() { kill_top start_top $* } socat_connect() { socat STDIN!!STDOUT UNIX-CONNECT:"$TWOPANE"/socket,forever kill -USR1 $$ [ "$TOP_EXIT" != restart ] || restart_top } connect() { case $# in 2 ) declare -n std0="$1" std1="$2"; local pid ;; 3 ) declare -n pid="$1" std0="$2" std1="$3" ;; 0 ) local std0 std1 pid ;; * ) exit 1 ;; esac std0=${3:-200} std1=${4:-201} disconnect $std0 $std1 coproc SOCAT { socat_connect; } pid=${SOCAT_PID} eval "exec ${std0}<&${SOCAT[0]} ${std1}>&${SOCAT[1]}" } sendc() { [ "${SOCAT[0]}" || connect stdin stdout printf '%s' "$*" >&$stdout } send() { [ "${SOCAT[0]}" || connect stdin stdout printf '%s\n' "$*" >&$stdout } disconnect() { eval "exec ${1:-200}<&- ${2:-201}>&-" wait -f "$SOCAT_PID" 2>/dev/null } restart() { start } start() { if [ "$STARTED" ] then kill_top else STARTED=y fi start_top $* socat_stty_opts=( rawer echo=0 intr=0 quit=0 eof=0 start=0 stop=0 susp=0 lnext=0 opost=1 onlcr=1 onlret=1 ) stty=$(printf %s, "${socat_stty_opts[@]}") trap ': SIGUSR1 ; SOCAT=() ; ' USR1 exec 100<&0 101>&1 while true do connect stdin stdout socat FD:100,${stty%,}!!STDOUT STDIN!!STDOUT <&$stdin | tee >(output_filter | soft_cursor >&101) >&$stdout reset -I -Q -w disconnect case "$TOP_EXIT" in restart ) continue ;; quit ) exit ;; * ) screen -X focus bottom echo break ;; esac done } quit() { screen -X quit } focus() { screen -X focus "$@" } output_filter() { exec 15> >(exec cat -v) while read -r -N1 do case "$REPLY" in $'\n' ) echo -n '^J' continue ;; esac if [[ "$REPLY" =~ [[:print:]] ]] then printf '%s' "$REPLY" else printf '%s' "$REPLY" >&15 fi done } soft_cursor() { cursor=$'\033['${cursor_color:=104}$'m \b\033[00m' printf '%s' "$cursor" while read -r -N1 do case "$REPLY" in $'\r' ) printf '%s' "$REPLY" "$cursor" ;; * ) printf '%s' $' \b' "$REPLY" "$cursor" ;; 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 <<. ${TOP_CMD@A} ${BOT_CMD@A} ${TOP_TITLE@A} ${BOT_TITLE@A} $(declare -f) our_bashrc_main \$BOT_CMD . main() { screen -c "$TWOPANE"/screenrc -m -S "$STY" -ln } main "$@"