#!/bin/sh
# -*- mode: shell-script; coding: utf-8 -*-
#
# xrandr-autoswitch
#
# Script to automatically switch XRandR outputs in a sane way.
# Any external outputs are turned on if connected and off otherwise.
# The internal output is turned on if no external output is connected and
# off otherwise
#
# Copyright (C) 2010-2012 Elmar Hoffmann
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
#

set -e

CONFIG='/etc/xrandr-autoswitch.conf'

ENABLE_AUTOSWITCH='yes'
MAX_EXTERNAL_OUTPUTS=0
PREFERRED_EXTERNAL_OUTPUTS=''
EXTRA_INTERNAL_OUTPUT_ARGS=''
EXTRA_EXTERNAL_OUTPUT_ARGS=''

. /lib/elho/shell-tools

invoke_xrandr ()
{
    debug "Invoking 'xrandr $(print_args "$@")'"
    if [ "${dry_run}" -eq 1 ]; then
	echo "xrandr $(print_args "$@")"
    else
	xrandr $(print_args "$@")
    fi
}

usage ()
{
    echo "Usage: $(basename -- "$0") [--verbose] [--debug] [--simulate] [--outputs]"
}

set +e
commandline=$(getopt --name "$(basename -- "$0"): Error" \
    --options='?hvds' --longoptions='help,usage,verbose,debug,simulate,dry-run,no-act,outputs' -- "$@")
if [ $? -ne 0 ]; then
    usage
    exit 2
fi
set -e
eval set -- "${commandline}"
verbose=0
debug=0
dry_run=0
list_outputs=0
while true; do
    case "$1" in
        -\?|-h|--help|--usage)
            usage
            exit
            ;;
        -v|--verbose)
            verbose=1
            ;;
        -d|--debug)
            debug=1
            ;;
        -s|--simulate|--dry-run|--no-act)
	    dry_run=1
            ;;
        --outputs)
	    list_outputs=1
            ;;
        --)
            shift
            break
            ;;
        *)
            error "Invalid getopt(1) output."
            exit 1
            ;;
    esac
    shift
done
if [ $# -ne 0 ]; then
    usage
    exit 2
fi

if [ "${verbose}" -eq 1 -o "${debug}" -eq 1 ]; then
    enable_verbose
fi
if [ "${debug}" -eq 1 ]; then
    enable_debug
fi

if [ "${list_outputs}" -eq 1 ]; then
    verbose "Available outputs:"
    xrandr --query | sed --quiet \
	--expression='s/^\([[:alnum:]-]\+\) \(dis\)\?connected .*$/\1/p'
    exit
fi


if [ -e "${CONFIG}" ]; then
    . "${CONFIG}"
fi

case "${ENABLE_AUTOSWITCH}" in
    1)
	;;
    [Yy][Ee][Ss])
	;;
    [Tt][Rr][Uu][Ee])
	;;
    *)
	exit 0
esac


CONNECTED_OUTPUTS=$(make_set $(xrandr --query | sed --quiet --expression='s/^\([[:alnum:]-]\+\) connected .*$/\1/p'))
DISCONNECTED_OUTPUTS=$(make_set $(xrandr --query | sed --quiet --expression='s/^\([[:alnum:]-]\+\) disconnected .*$/\1/p'))

debug "Connected outputs:    ${CONNECTED_OUTPUTS}"
debug "Disconnected outputs: ${DISCONNECTED_OUTPUTS}"

INTERNAL_OUTPUTS=''
EXTERNAL_OUTPUTS=''
for output in ${CONNECTED_OUTPUTS}; do
    case "${output}" in
	eDP*|LVDS*)
	    INTERNAL_OUTPUTS=$(set_add "${INTERNAL_OUTPUTS}" "${output}")
	    ;;
	DP*|DVI*|HDMI*|VGA*)
	    EXTERNAL_OUTPUTS=$(set_add "${EXTERNAL_OUTPUTS}" "${output}")
	    ;;
	esac
done

debug "Internal outputs:     ${INTERNAL_OUTPUTS}"
debug "External outputs:     ${EXTERNAL_OUTPUTS}"

if [ -z "${INTERNAL_OUTPUTS}" ]; then
    error "No internal outputs available!"
    exit 1
fi

OFF_OUTPUTS="${DISCONNECTED_OUTPUTS}"

if [ -z "${EXTERNAL_OUTPUTS}" ]; then
    ON_INTERNAL_OUTPUTS="${INTERNAL_OUTPUTS}"
else
    ON_INTERNAL_OUTPUTS=''
    OFF_OUTPUTS=$(set_union "${OFF_OUTPUTS}" "${INTERNAL_OUTPUTS}")
fi

if [ "${MAX_EXTERNAL_OUTPUTS}" -gt 0 \
    -a $(words "${EXTERNAL_OUTPUTS}") -gt "${MAX_EXTERNAL_OUTPUTS}" ]; then
    verbose "Too many external outputs connected, choosing preferred one(s)."

    preferred=$(set_intersect "${PREFERRED_EXTERNAL_OUTPUTS}" "${EXTERNAL_OUTPUTS}")
    remaining=$(set_difference "${EXTERNAL_OUTPUTS}" "${preferred}")
    sorted_external=$(set_union "${preferred}" "${remaining}")
    ON_EXTERNAL_OUTPUTS=$(set_head "${sorted_external}" "${MAX_EXTERNAL_OUTPUTS}")
    OFF_OUTPUTS=$(set_union "${OFF_OUTPUTS}" \
	"$(set_difference "${sorted_external}" "${ON_EXTERNAL_OUTPUTS}")")
else
    ON_EXTERNAL_OUTPUTS="${EXTERNAL_OUTPUTS}"
fi

debug "Disabling outputs..."
for output in ${OFF_OUTPUTS}; do
    invoke_xrandr ${EXTRA_ARGS} --output "${output}" --off
done

debug "Enabling internal outputs..."
for output in ${ON_INTERNAL_OUTPUTS}; do
    invoke_xrandr ${EXTRA_ARGS} --output "${output}" --auto ${EXTRA_INTERNAL_OUTPUT_ARGS}
done
debug "Enabling external outputs..."
for output in ${ON_EXTERNAL_OUTPUTS}; do
    invoke_xrandr ${EXTRA_ARGS} --output "${output}" --auto ${EXTRA_EXTERNAL_OUTPUT_ARGS}
done
