#!/bin/bash
#
# Available at http://ferrari.databa.se/3400/f8/dual-head.sh
#
# A script to manipulate connected graphical outputs,
# including TV-out. It includes the most common operations
# and may also be configured to run on an XF86Display event.
# Typically Fn-F5 or similar on a laptop.
#
# Prerequisites:
# This script will work with one graphics card only.
# Furthermore it will only work with two CRTCs,
# i.e. two outputs may be simultaneous active.
#
# Comments:
# In order to optimize performance and minimize flicker
# xrandr is at most called twice. Once to read the current
# state and once for setting the new state. Thus, some code
# may look strange at the first glance.
#
# By Sven-Göran Bergh, 2008-01-01
#

### Edit user settings here: ######################################
                       #
DEFAULTACTION="toggle" # May be changed with argument 1
DEFAULTSIDE="left"     # May be changed with argument 2
                       #
###################################################################

### Edit hardware specific settings here: #########################
                       #
INTERNAL="LVDS"        # Specify the name of the internal display
TVOUTPUT="S-video"     # Specify the name of the TV-output
TVMODE="pal"           # Specify TV mode, PAL or NTSC
                       #
###################################################################

# Get X user
#XUSER=$(w | awk '$3 ~ /^:[0-9]$/ {print $1; nextfile}')

# Get X display (current or first running)
DISPLAY=${DISPLAY:= \
    $( w | awk '$3 ~ /^:[0-9]$/ {print $3; nextfile}' )}

# Quit if no X-server is running
[ "${DISPLAY}" ] || exit 1

function usage() {
    printf "Usage: %s [init|toggle|status|internal|tv [side]]\n" \
	"`basename $0`"
}

ME=`basename $0 .sh`

# What to do?
ACTION="${1:-$DEFAULTACTION}"

# Which side of $INTERNAL should the external output be shown?
SIDE="${2:-$DEFAULTSIDE}"

# If invalid action or help, quit before calling xrandr
case `echo "$ACTION" | tr A-Z a-z` in
    init | toggle | status | internal | tv )
	# Recognized actions. Do nothing and continue...
	;;
    help | usage )
	usage
	exit 0
	;;
    * )
	printf "%s: Unknown operation, %s\n" "$ME" "$ACTION"
	usage
	exit 1
	;;
esac

# Get information about all outputs
ALLINFO=( $( \
      xrandr -q \
    | awk -- '/connected/ {
              printf " %s", $1;
                if ($2 !~ /dis/) {
                  if ($3 ~ /[0-9]+x/)
                    printf "@%s", $3;
                  else
                    printf "@";
                }
              }' \
    ) )

# Initialize some useful variables
for (( i=0; i<${#ALLINFO[*]}; i++ )) {
    # Array with all outputs
    ALLOUT[$i]=$( \
	  echo "${ALLINFO[$i]}" \
	| awk -F@ -- '{print $1}' \
	)
    # Array with all connected outputs
    CONCTD[$i]=$( \
	  echo "${ALLINFO[$i]}" \
	| awk -F@ -- '/@/ {print $1}' \
	)
    # Array with all active outputs
    ACTIVE[$i]=$( \
	  echo "${ALLINFO[$i]}" \
	| awk -F@ -- '/@[0-9]+x/ {print $1}' \
	)
    # Array with all resolutions
    ALLRES[$i]=$( \
	  echo "${ALLINFO[$i]}" \
	| awk -F@ -- '{print $2}' \
	| awk -F+ -- '{print $1}' \
	)
    # Array with all positions
    ALLPOS[$i]=$( \
	  echo "${ALLINFO[$i]}" \
	| awk -F@ -- '{print $2}' \
	| awk -F+ -- '/+/ {print $2 "+" $3}' \
	)
    # Index for the internal output
    [ "${ALLOUT[$i]}" == "$INTERNAL" ] && INTNDX=$i
    # Index for the currently active external output
    [ "${ACTIVE[$i]}" -a "${ACTIVE[$i]}" != "$INTERNAL" ] \
	&& CURNDX=$i
}

CURNDX=${CURNDX:-$INTNDX}
CURRENT=${ALLOUT[$CURNDX]}

case `echo "${SIDE}" | tr A-Z a-z` in
    left)
	LOCATION="--left-of $INTERNAL"
	;;
    right)
	LOCATION="--right-of $INTERNAL"
	;;
    top|above)
	LOCATION="--above $INTERNAL"
	;;
    bottom|below)
	LOCATION="--below $INTERNAL"
	;;
esac

function run() {
    EXEC="$*"
    logger -t $ME "Executing: $EXEC"
    $EXEC | logger -t $ME
}

function init() {
    # Set load detection on all external outputs
    for out in ${ALLOUT[*]}; do
	[ "$out" != "$INTERNAL" ] && \
	    INIT="$INIT --output $out --set load_detection 1"
	[ "$out" == "$TVOUTPUT" ] && \
	    INIT="$INIT --set tv_standard $TVMODE"
    done
    run xrandr $INIT
}

function status() {
    printf "%-10s%-11s%-8s%-10s%-10s\n" \
	"Output" "Connected" "Active" "Position" "Resolution"
    for (( i=0; i<${#ALLOUT[*]}; i++ )) {
	[ "${CONCTD[$i]}" ] && c="yes" || c="no"
	[ "${ACTIVE[$i]}" ] && a="yes" || a="no"
	printf "%-13s%-9s%-7s%-10s%-10s\n" \
	    "${ALLOUT[$i]}" "$c" "$a" \
	    "${ALLPOS[$i]}" "${ALLRES[$i]}"
    }
}

function internal() {
    # Internal output always active
    INT="--output $INTERNAL --auto --pos 0x0"

    # Turn off currently active external output
    [ "$CURRENT" != "$INTERNAL" ] && \
	OFF="--output $CURRENT --off"

    run xrandr $INT $OFF
}

function tvout() {
    # If TV-output already active, exit
    [ "$CURRENT" == "$TVOUTPUT" ] && exit 0

    # Internal output always active
    INT="--output $INTERNAL --auto"

    # Turn off currently active external output
    [ "$CURRENT" != "$INTERNAL" ] && \
	OFF="--output $CURRENT --off"

    TV="--output $TVOUTPUT --auto $LOCATION"

    run xrandr $INT $TV $OFF
}

function toggle() {
# Go to the next connection mode in the toggle sequence:
#   LVDS         (single)       =>
#   LVDS+S-video (mirror)       =>
#   LVDS+S-video (side-by-side) =>
#   LVDS+VGA-0   (mirror)       =>
#   LVDS+VGA-0   (side-by-side) =>
#   LVDS         (single)       =>
#   ...
# Connection modes that include not connected outputs are skipped.
# Ex: with only VGA-0 connected it is only three connection modes:
#
    if [ "$CURRENT" != "$INTERNAL" -a \
	 "${ALLPOS[$INTNDX]}" == "${ALLPOS[$CURNDX]}" ]; then
	NEXT=$CURRENT
    else
	for (( i=0; i<${#ALLOUT[*]}; i++ )) {
	    [ "${CONCTD[$i]}" == "${CURRENT}" ] && break
	}
	until [ "$NEXT" ]; do
	    i=$(( ($i+1)%${#ALLOUT[*]} ))
	    NEXT=${CONCTD[$i]}
	done
    fi

    # Internal output always active
    INT="--output $INTERNAL --auto"

    # Turn off currently active external output
    [ "$CURRENT" != "$INTERNAL" -a "$CURRENT" != "$NEXT" ] && \
	OFF="--output $CURRENT --off"

    # Turn on next connected external output
    [ "$NEXT" != "$INTERNAL" ] && \
	ON="--output $NEXT --auto"

    # If mirror mode, reposition internal output.
    # Otherwise position external output
    [ "$NEXT" != "$CURRENT" ] \
	&& INT="$INT --pos 0x0" \
	|| ON="$ON $LOCATION"

     run xrandr $INT $OFF $ON
}

case `echo "$ACTION" | tr A-Z a-z` in
    init)
	init
	;;
    toggle)
	toggle
	;;
    status)
	status
	;;
    internal)
	internal
	;;
    tv)
	tvout
	;;
    *)
	usage
	;;
esac

