#!/bin/bash set -e set -f set -o pipefail shopt -s lastpipe PROGNAME=${0##*/} BOT_SIZE=8 BOT_TITLE=input BOT_CMD=start TOP_CMD='PS1="$TOP_TITLE\$ " bash --noprofile --norc -i' TOP_TITLE=output 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 <<'.' unbindall escape \0\0 caption string '%t' layout new split focus bottom resize $BOT_SIZE screen -ln -t "$BOT_TITLE" 0 bash --noprofile --rcfile "$TWOPANE"/bashrc -i source "$TWOPANE"/screenrc.startpane . save_screenrc 'startpane' <<'.' focus top screen -ln -t "$TOP_TITLE" 1 sh -c "$TOP_CMD" exec .!. socat -u UNIX-RECV:"$TWOPANE"/socket STDOUT title 'output' focus bottom layout save 0 . save_screenrc 'restart' <<. layout new split focus bottom resize $BOT_SIZE select 0 title 'input' source "$TWOPANE"/screenrc.startpane . restart() { screen -X source "$TWOPANE"/screenrc.restart } sendp() { trap "kill -SIGUSR1 $1" EXIT socat -u STDIN UNIX-SENDTO:"$TWOPANE"/socket || restart } sendc() { [ "${COPROC[1]}" ] || coproc { sendp $$; } printf '%s' "$*" >&${COPROC[1]} } send() { [ "${COPROC[1]}" ] || coproc { sendp $$; } printf '%s' "$*"$'\n' >&${COPROC[1]} } sendcc() { sendc $'\003'; } raw() { stty_opts=( cbreak raw -isig -iexten -nl intr undef quit undef #erase undef #kill undef eof undef start undef stop undef susp undef lnext undef ) stty "${stty_opts[@]}" } start() { trap ': SIGPIPE ${COPROC[@]@A}' SIGPIPE trap ': SIGCHLD ${COPROC[@]@A}' SIGCHLD trap ': SIGUSR1 ${COPROC[@]@A}; COPROC=()' SIGUSR1 raw while read -r -N1 || [ $? -gt 128 ] do if [ "$REPLY" ] then sendc "$REPLY" fi done } save_file bashrc <<. set -f set -b set -o pipefail shopt -s lastpipe trap "screen -X quit" EXIT $(declare -f) export PS1="$BOT_TITLE\$ " \$BOT_CMD . main() { screen -c "$TWOPANE"/screenrc -m -S "$STY" -ln } main "$@"