#!/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 . kill_top() { while screen -p1 -Q info >/dev/null do screen -p1 -X kill done } start_top() { if screen -p1 -Q info >/dev/null 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 to result variables the file descriptors. # Optionally assign to result variable the SOCAT PID. # The copied file descriptors, unlike the original coprocess file # descriptors ${SOCAT[0]} and ${SOCAT[1]}, can be passed to external # processes (e.g., other socat(1) instances). 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() { [ "$1" -a "$2" ] && eval "exec $1<&- $2>&-" 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_input disconnect $stdin $stdout case "$TOP_EXIT" in restart ) continue ;; quit ) exit ;; prompt | * ) screen -X focus bottom break ;; esac done } forward() { forward_input "$@" } forward_input() { old_stty=$(stty -g) exec 100<&0 101>&1 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 $'\n' ) echo -n '^J' continue ;; esac if [[ "$REPLY" =~ [[:print:]] ]] then printf '%s' "$REPLY" else printf '%s' "$REPLY" >&${OUTPUT_FILTER} fi done } soft_cursor() { FMT=$'%s \b\e[%sm \e[00m\b' COLOR=$(( 101 + RANDOM % 7 )) REPLY= while printf "$FMT" "$REPLY" "$COLOR" do read -r -N1 || break COLOR=$(( 101 + RANDOM % 7 )) 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} ${CURSOR_COLOR@A} ${TOP_EXIT@A} $(declare -f) our_bashrc_main \$BOT_CMD . main() { screen -c "$TWOPANE"/screenrc -m -S "$STY" -ln } main "$@"