#!/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.
#
# Whenever we set the resolution then we also add other modes as fallbacks.

SAFE_RESOLUTION="1024x768"
FALLBACK_RESOLUTION="1280x1024"

ME=${0##*/}

DO_XORG=

VBOX_OPTS="HORIZ=28-70  DRIVER=vesa RESOLUTION=1280x1024"
DRIVER_DIR=/usr/lib/xorg/modules/drivers
DRIVER_EXT=_drv.so

DEFAULT_DEPTH=
DEFAULT_DRIVER="vesa"

VALID_DRIVERS=$(ls $DRIVER_DIR | sed -n "s/$DRIVER_EXT//p")

 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>,<resolution>|auto|default|safe|vbox]

Create an xorg.conf file based on the arguments given on the command
line.  We expect one command line parameter with possibly multiple
xorg options separated by spaces.  Example:

    $ME fbdev,1600x900

Later parameters override earlier ones if what they do overlaps.
There are two exceptions.  The "default" parameter is always the
lowest priority and then "safe" is the next lowest regardless of
the order.

    $ME fbdev,vesa

NOTE: "$DEFAULT_DRIVER" and "default" and "safe" all do the same thing now.

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:
A comma delimited list of options.  Note that "composite" can be
abbreviated as "c" and "depth" can be abbrevaiated as "d".

    safe              Set driver to "$DEFAUL_DRIVER"
    auto              Set default resolutions
    c, composite      Tell xorg to use compositor (for graphics effects)
    default           Set driver to "$DEFAULT_DRIVER"
    HHHHxWWWW         Set the resolution
    d=xx, depth=xx    Set the default color depth
    h=xxxx            Set horizontal sync frequency range
    v=yyyy            Set vertical refresh frequency range
    vbox              Set the driver and horizontal frequency:
                        $VBOX_OPTS

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:
$(echo "$VALID_DRIVERS" | column -c 70)

Usage
    exit $ret
}

main() {

    # Read command line params that start with "-" and remove them from "$@"
    ORIG_ARGS="$*"
    local SHIFT
    read_params "$@"
    shift $SHIFT

    if [ $# -gt 0 ]; then
        read_cmdline "$1"
        shift
    fi

    read_params "$@"
    shift $SHIFT

    [ $# -gt 0 ] && error "Extra command line parameter(s): $*"

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

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

read_params() {
    local param
    SHIFT=0
    while [ $# -gt 0 -a "${#1}" -gt 0 -a -z "${1##-*}" ]; do
        param=${1#-}
        shift
        SHIFT=$((SHIFT +  1))
        case $param in
               -force|f) FORCE=true        ;;
                -help|h) usage             ;;
              -output|o) [ $# -gt 0 ] || error "Expected a filename after -$param"
                         FILE=$1; shift; SHIFT=$((SHIFT + 1))   ;;
          -output=*|o=*) FILE=${param#*=}  ;;
                      -) break             ;;
                      *) error "Unknown command line argument -$param"
        esac
    done
}

read_cmdline() {
    local param value

    # Lowest priority gets evaluted first.
    # Yes, these both do the same thing (for now).
    case ,$1, in
         *,default,*) DRIVER=$DEFAULT_DRIVER ;;
    esac

    case ,$1, in
            *,safe,*) DRIVER=$DEFAULT_DRIVER ;;
    esac

    for param in ${1//,/ };  do
        value=${param#*=}

        case $param in

                   res=*) RESOLUTION=$value    ;;
             composite|c) ADD_COMPOSITE=true   ;;
             depth=*|d=*) DEFAULT_DEPTH=$value ;;
            safe|default)                      ;;
                    vbox) eval "$VBOX_OPTS"    ;;
                     h=*)      HORIZ=$value    ;;
                     v=*)       VERT=$value    ;;
                    auto) RESOLUTION=default   ;;

                 uxa|sna) ADD_INTEL_ACCEL=$value
                          DRIVER=intel ;;

               *)   if echo $param | grep -q "^[0-9]\+x[0-9]\+$"; then
                        RESOLUTION=$param
                        continue
                    fi
                    DRIVER=$param ;;
        esac
    done
}

#-------------------------------------------------------------------------------
# Create a mode (resolution) section based on the parameter.  If there
# is no parameter then we don't create this section.  A "safe" parameter
# creates a resolution of $SAFE_RESOLUTION.  A "default" parameter will try to
# use the resolution of the framebuffer if it is bigger than 1024x768.  If the
# parameter looks like a resolution then it is set as the default.  Anything
# else sets the resolution to $FALLBACK_RESOLUTION.
#-------------------------------------------------------------------------------
modes_section() {
    local param=$1
    [ "$param" ] || return
    local res_regex="[0-9]\+x[0-9]\+"

    case $param in
           safe) param=$(good_fb_res $param)  ;;
          #safe) param=$SAFE_RESOLUTION       ;;
        default) param=$(good_fb_res $param)  ;;
    esac

    local resolution=$(echo "$param" | grep -o "$res_regex")
    : ${resolution:=$FALLBACK_RESOLUTION}

    local modes add_modes

    case $resolution in
         1024x768) add_modes=' "800x600"'                                                            ;;
        1280x1024) add_modes=' "1333x768"  "1024x768"  "800x600"'                                    ;;
         1280x800) add_modes=' "1280x768"  "1024x768"  "800x600"'                                    ;;
        1920x1200) add_modes=' "1680x1050" "1440x900"  "1280x1024" "1280x800"  "1024x768" "800x600"' ;;
        1680x1050) add_modes=' "1440x900"  "1280x1024" "1280x800"  "1024x768"  "800x600"'            ;;
        1600x1200) add_modes=' "1450x1050" "1280x1024" "1024x768"  "800x600"'                        ;;
        1920x1080) add_modes=' "1600x1200" "1440x900"  "1400x1050" "1280x1024" "1280x960" "1024x768"';;
        1450x1050) add_modes=' "1280x1024" "1024x768"  "800x600"'                                    ;;
         1440x900) add_modes=' "1280x1024" "1280x800"  "1024x768"  "800x600"'                        ;;
         1280x768) add_modes=' "1280x800"  "1333x768"  "1024x768"  "800x600"'                        ;;
                *) add_modes=' "1280x1024" "1333x768"  "1024x768"  "800x600"'                        ;;
    esac

    add_modes=$(echo "$add_modes" | sed -r -e "s/ \"$resolution\"//" -e "s/\s+/ /g")

    [ -n "$resolution" ] && modes="\"$resolution\""
    [ -n "$add_modes"  ] && modes="$modes$add_modes"

    cat<<Modes_Section

    SubSection "Display"
        Modes $modes
    EndSubSection
Modes_Section
}

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

    local newline="\n"
    if [ "$DRIVER_ERROR" ]; then
        driver=$DEFAULT_DRIVER
        newline=""
        echo ; echo
        cat<<Driver_Error
    #=========================================================================
    # WARNING: the "$DRIVER_ERROR" driver was not found.  Using "$driver" instead.
    # Use the --force option to force the use of any driver.
    #=========================================================================
Driver_Error
    fi

    printf "$newline    Driver     \"$driver\""
}

horizontal_entry() {
    local range=$1
    [ "$range" ] || return
    printf "\n    HorizSync    $range"
}

vertical_entry() {
    local range=$1
    [ "$range" ] || return
    printf "\n    VertRefresh  $range"
}

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 "Monitor"
    Identifier "Monitor0"
    Option "DPMS" "true"$(horizontal_entry $HORIZ)$(vertical_entry $VERT)
EndSection

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

Section "Screen"
    Identifier      "Screen0"
    Monitor         "Monitor0"
    Device          "Device0"$(add_default_depth $DEFAULT_DEPTH)$(modes_section $mode)
EndSection
Xorg_Conf
}

add_default_depth() {
    local depth=$1
    [ -n "$depth" ] || return
    echo -e "\n    DefaultDepth    $depth"
}

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

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

good_fb_res() {
    local param=${1:-unknown} fb_size fb_name x
    local sys_dir=/sys/class/graphics/fb0
    local size_file=$sys_dir/virtual_size
    local name_file=$sys_dir/name

    read fb_size 2>/dev/null <$size_file
    read fb_name 2>/dev/null <$name_file

    # We use the loop for control flow
    for x in 1; do
        case $fb_name in
          "VESA VGA")       ;;
                   *) break ;;
        esac

        local fb_res=${fb_size/,/x}

        echo $fb_res | grep -q "^[0-9]\+x[0-9]\+$" || break

        case $fb_res in
            1024x768|800x600) break ;;
        esac

        local width=${fb_res%%x*}
        [ $width -lt 1024 ] && break

        echo $fb_res
        return
    done
    echo $param
}

driver_error() {
    local driver=$1
    DRIVER_ERROR=$driver
    cat <<Driver_Error >&2
    $WHITE$ME$YELLOW Warning:$RED Unrecognized (uninstalled?) video driver:$WHITE $driver$NC
    ${WHITE}Using $DEFAULT_DRIVER instead$NC
Driver_Error
}

error() {
    echo "$WHITE$ME$YELLOW Error:$RED $*$NC" >&2
    exit 7
}

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

main "$@"
