#!/bin/bash

# Create a minimal xorg.conf that sets the resolution and/or the video driver.
#
# If our name is "buildxconfig" then we mimic the behavior of the older version.
# Otherwise we behave better and take all of our input via the command line.
#

ME=${0##*/}

DO_XORG=

DRIVER_DIR=/usr/lib/xorg/modules/drivers
DRIVER_EXT=_drv.so

DEFAULT_DRIVER="vesa"
test -e /sys/firmare/efi  && DEFAULT_DRIVER="fbdev"

 WHITE=$(printf "\e[1;37m")
   RED=$(printf "\e[0;31m")
YELLOW=$(printf "\e[1;33m")
    NC=$(printf "\e[0m")


usage() {
    local ret=${1:-0}
    cat <<Usage
Usage: $ME [options] [<driver>,uxa,sna,busid=<busid>]

Create an xorg.conf file based on the arguments given on the command
line.

Later parameters override earlier ones if what they do overlaps.

    $ME fbdev,vesa

NOTE: the default driver is "vesa" when booting legacy and "fbdev" when
bootig UEFI.

Options:
    -f --force          Force creation even if driver specfied is not found
    -h --help           Show this usage
    -o --output=<file>  Write output to <file> instead of stdout
    --                  Optional delimiter to indicate the end of options

Xorg Arguments:

    uxa|sna            Use Intel driver with the specified acceleration
    busid=<busid>      Set PCI Bus-ID to <busid>
    raw-busid=<busid>  Set Bus-IS exactly as given
    safe               Set driver to default-driver (for compatibility)
    default            Set driver to default-driver (for compatibility)

Any other argument is considered to be the name of a graphics driver.
Use --force to create an xorg.conf that specifies a driver that is not
on the host system.

Available drivers:
$(valid_drivers | column -c 70)

Usage
    exit $ret
}

main() {

    local ORIG_ARGS="$*"
    local short_stack="fho"  SHIFT DASH_BREAK

    local cmds  cnt=0
    while [ $# -gt 0 -a $cnt -lt 20 ]; do
        read_options "$@"
        shift $SHIFT

        while [ $# -gt 0 ]; do
            [ -n "$1" -a  -z "${1##-*}" -a -z "$DASH_BREAK" ] && break
            cmds="$cmds $1"
            shift
        done
        loop_cnt=$((loop_cnt + 1))
    done

    cmds="$cmds $*"
    local cmd
    for cmd in ${cmds//,/ };  do
        value=${cmd#*=}

        case $cmd in
                 busid=*) BUS_ID=$(convert_bus_id "$value")  ;;
             raw-busid=*)  BUS_ID=$value         ;;
             composite|c) ADD_COMPOSITE=true     ;;
            safe|default)                        ;;
                 uxa|sna) ADD_INTEL_ACCEL=$value
                           DRIVER=intel          ;;
                      *)  if [ -n "$FORCE" ] || echo "$cmd" | grep -q "^[0-9a-z_-]\+$"; then
                            DRIVER=$cmd
                          else
                            driver_error "$cmd"
                         fi ;;
        esac
    done


    if [ -z "$FORCE" -a -n "$DRIVER" ]; then
        test -e $DRIVER_DIR/$DRIVER$DRIVER_EXT || driver_error $DRIVER
    fi

    if [ "$OUT_FILE" ]; then
        mkdir -p $(dirname "$OUT_FILE")
        [ -e "$OUT_FILE" -a ! -e "$OUT_FILE.bak" ] && mv $OUT_FILE $OUT_FILE.bak
        write_xorg_conf "$RESOLUTION" "$DRIVER" > $OUT_FILE
    else
        write_xorg_conf "$RESOLUTION" "$DRIVER"
    fi
}

#------------------------------------------------------------------------------
#
#------------------------------------------------------------------------------
driver_entry() {
    local driver=$1
    [ "$driver" ] || return

    local newline="\n"
    if [ "$DRIVER_ERROR" ]; then
        newline=""
        echo ; echo
        cat<<Driver_Error
    #=========================================================================
    # WARNING: the "$DRIVER_ERROR" driver was not found or looked suspicious
    # Use the --force option to force the use of any driver.
    #=========================================================================
Driver_Error
    else
        printf "$newline    Driver     \"$driver\""
    fi
}

#------------------------------------------------------------------------------
#
#------------------------------------------------------------------------------
write_xorg_conf() {
    local mode=$1  driver=$2

    cat <<Xorg_Conf
#-----------------------------------------------------------------------------
# xorg.conf file
#
# Generated by $ME sometime around $(date)
#
# If you want to save customizations, delete the line above or this
# file will get automatically deleted on the next live boot.
#
# Command line parameters: $ORIG_ARGS
#-----------------------------------------------------------------------------

Section "Device"
    Identifier "Device0"$(driver_entry $driver)$(add_intel_accel)$(add_bus_id)
EndSection$(composite_section $ADD_COMPOSITE)
Xorg_Conf
}

#------------------------------------------------------------------------------
#
#------------------------------------------------------------------------------
composite_section() {
    local flag=$1
    [ -n "$flag" ] || return
    echo ; echo
    cat<<Composite_Section
Section "Extensions"
   Option "Composite" "Enable"
EndSection
Composite_Section
}

#------------------------------------------------------------------------------
# Make sure there are at least 2 input paramters and make sure the 2nd one
# does not start with a "-".
#------------------------------------------------------------------------------
need_param() {
    [ $# -lt 2 ] && fatal "A parameter is required after option %s" "$1"
    [ -n "$2" -a -z "${2##-*}" ] && fatal "Suspicious parameter after option %s" "$1"
}

add_intel_accel() {
    [ "$ADD_INTEL_ACCEL" ] || return
    echo -e "\n    Option     \"AccelMethod\"  \"$ADD_INTEL_ACCEL\""
}

add_bus_id() {
    [ "$BUS_ID" ] || return
    BUS_ID=$(echo $BUS_ID | sed -r "s/\.([0-9a-f]+)$/:\1/")
    echo -e "\n    BusID      \"$BUS_ID\""
}

#------------------------------------------------------------------------------
# Convert a bus-id from lspci to the format used in xorg.conf
# Convert from hex to decimal, add PCI prefix, and use" :" instead of "."
#------------------------------------------------------------------------------
convert_bus_id() {
    local bus_id=$1
    if echo "$bus_id" | grep -iEq "^[0-9a-f]+:[0-9a-f]+:[0-9a-f]+[:.][0-9a-f]+$"; then
        printf "PCI:%02d@%d:%02d:%d\n" $(echo "$bus_id" | sed -r "s/([0-9a-f]+):([0-9a-f]+):([0-9a-f]+)[:.]([0-9a-f]+)/0x\2 0x\1 0x\3 0x\4/i") 
    elif
        echo "$bus_id" | grep -iEq "^[0-9a-f]+:[0-9a-f]+[:.][0-9a-f]+$"; then
        printf "PCI:%02d:%02d:%d\n" $(echo "$bus_id" | sed -r "s/([0-9a-f]+):([0-9a-f]+)[:.]([0-9a-f]+)/0x\1 0x\2 0x\3/i")
    else
        warn "This does not look like a bus-id %s" "$bus_id"
    fi
}

driver_error() {
    local driver=$1
    DRIVER_ERROR=$driver
    cat <<Driver_Error >&2
    $WHITE$ME$YELLOW Warning:$RED Unrecognized (or uninstalled) video driver:$WHITE $driver$NC
    ${YELLOW}Use the --force option to force the use of any driver$NC
Driver_Error
}

valid_drivers() {
    ls $DRIVER_DIR | sed -n "s/$DRIVER_EXT//p"
}
#------------------------------------------------------------------------------
#
#------------------------------------------------------------------------------
fatal() {
    local fmt=$1 ; shift;
    printf "$WHITE$ME$YELLOW Error:$RED $fmt$NC\n" "$@" >&2
    exit 7
}

warn() {
    echo "$ME$YELLOW Warning: $*$NC" >&2
}

#------------------------------------------------------------------------------
# Read options.  Record how many times we shift
#------------------------------------------------------------------------------
read_options() {
    SHIFT=0
    while [ $# -gt 0 -a -n "$1" -a -z "${1##-*}" ]; do
        local arg=${1#-} ; shift ; SHIFT=$((SHIFT + 1))
        local value=${arg#*=}

        #--- unstack stacked single-letter options ---
        case $arg in
            [$short_stack][$short_stack]*)
                if echo "$arg" | grep -q "^[$short_stack]\+$"; then
                    set -- $(echo $arg | sed -r 's/([a-zA-Z])/ -\1 /g') "$@"
                    continue
                fi;;
        esac

        case $arg in
              -force|f) FORCE=true                    ;;
               -help|h) usage                         ;;
             -output|o) need_param  "-$arg" "$@"
                        OUT_FILE=$1 ; 
                        shift  ; SHIFT=$((SHIFT + 1)) ;;
             -output=*) OUT_FILE=$value               ;;
                     -) DASH_BREAK=true ; break       ;;

             *) fatal "Unknown option %s" "-$arg" ;;
        esac
    done
}

main "$@"
