#!/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##*/}

XORG_CONF=./xorg.conf
DO_XORG=

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

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

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.

    safe              Set driver to "vesa" and set default resolutions
    auto              Set default resolutions
    default           Set vesa driver and default resolutions
    HHHHxWWWW         Set the resolution
    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 "$@"
    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=vesa  ; RESOLUTION=default ;;
    esac

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

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

        case $param in
            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 based.  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=$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
    printf "\n    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.
#-----------------------------------------------------------------------------

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

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

add_intel_accel() {
    [ "$ADD_INTEL_ACCEL" ] && 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
    cat <<Driver_Error >&2
$WHITE$ME$YELLOW Error:$RED Unrecognized (uninstalled?) video driver:$WHITE $driver
$RED
Available drivers:
$(echo "$VALID_DRIVERS" | column -c 70)

Use --force to force use of an unrecognized driver$NC
Driver_Error

    exit 3
}

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

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

main "$@"
