#!/bin/bash
#
# h3disp
#
# Patches fex/script.bin settings to modify display settings. Currently
# rather limited and only supporting HDMI and the available presets.
#
#############################################################################
#
# Background informations:
#
# Only a certain amount of predefined video modes is available on H3 devices
# as can be seen here: http://linux-sunxi.org/Fex_Guide#disp_init_configuration
#
# Purpose of this script is to display the available reasonable settings and
# let the user choose from them to adjust fex file accordingly afterwards.
# In the meantime it's also possible to specify available resolutions on the
# command line. To display possible values use the -h switch.
#
# If HDMI-to-DVI adapters are used another fix has to be applied to the fex
# file: http://linux-sunxi.org/Orange_Pi_One#HDMI_to_DVI_converters -- so ask
# user whether he plans to use such an adapter and adjust fex file accordingly
#
#############################################################################
#
# CHANGES:
#
# v0.4: Added new modes in a combination with kernel patch, but they refuse to work.
#       Additional work is needed for all modes larger than 10
#
# v0.3: Added the ability to set the colour-range HDMI-output and spruced the
#       output of h3disp in general with a splash of colour. Also, don't spit
#       out so much text by default.
#
# v0.2: Added non-interactive mode. You can provide one or two arguments on
#       the command line, eg. 'h3disp -m 1080p60 -d' (1920x1080@60Hz DVI) or 
#       'h3disp -m 720i' (1280x720@30Hz HDMI). Complete list of modes via
#       'h3disp -h'
#
# v0.1: Initial release to adjust display settings in script.bin on commonly
#       used Debian based OS images for H3 devices running Linux.
#
#############################################################################
#
# TODO:
# 
# - implement real user interaction: dialog that asks which of the available 
#   HDMI modes to choose and whether a HDMI-to-DVI converter is used or not
#
# - maybe implement live resolution switching as outlined by Jernej:
#   http://forum.armbian.com/index.php/topic/617-wip-orange-pi-one-support-for-the-upcoming-orange-pi-one/?p=5305
#   (we would also need a working set of fbset modes in /etc/fb.modes
#   of course)
#
# - maybe add the chosen mode to /boot/boot.cmd and create /boot/boot.scr
#   afterwards?
#
#############################################################################

Main() {
	export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
	
	# ensure script is running as root
	if [ "$(id -u)" != "0" ]; then
	        echo "This script must be executed as root. Exiting" >&2
	        exit 1
	fi
	
	# interactive mode if no arguments are provided -- currently not implemented
	# otherwise we try to parse the command line arguments and use these
	if [ $# -eq 0 ]; then
		DisplayUsage ; exit 0
		# Provide a list with possible HDMI display settings and store the chosen
		# one in the HDMIMode variable
		:
		
		# ask the user whether he uses a HDMI-to-DVI converter and if true then
		# set DVIUsed=TRUE
		:
	else
		ParseOptions "$@"
		if [ -v ColourRange ]; then
			re='^[0-9]+$'
			if ! [[ ${ColourRange} =~ ${re} ]] ; then
				echo "$0: Invalid colour-range specified, only values 0-2 are supported for now!"
				exit 1
				fi
			if [[ ${ColourRange} -lt 0 || ${ColourRange} -gt 2 ]] ; then
				echo "$0: Invalid colour-range specified, only values 0-2 are supported for now!"
				exit 1
			fi
		fi
		case ${VideoMode} in
			0|480i) # res
				HDMIMode=0
				pll_video=54
				;;
			1|576i) # res
				HDMIMode=1
				pll_video=54
				;;
			2|480p) # res
				HDMIMode=2
				pll_video=108
				;;
			3|576p) # res
				HDMIMode=3
				pll_video=108
				;;
			4|720p50|1280x720p50) # res
				HDMIMode=4
				pll_video=297
				;;
			5|720p60|720p|1280x720p60) # res
				HDMIMode=5
				pll_video=297
				;;
			6|1080i50|1920x1080i50) # res
				HDMIMode=6
				pll_video=297
				;;
			7|1080i60|1080i|1920x1080i60) # res
				HDMIMode=7
				pll_video=297
				;;
			8|1080p24|1920x1080p24) # res
				HDMIMode=8
				pll_video=297
				;;
			9|1080p50|1920x1080p50) # res
				HDMIMode=9
				pll_video=297
				;;
			10|1080p60|1080p|1920x1080p60) # res
				HDMIMode=10
				pll_video=297
				;;
			11|1080p25|1080p|1920x1080p25) # res
				HDMIMode=11
				pll_video=297
				;;
			12|1080p30|1080p|1920x1080p30) # res
				HDMIMode=12
				pll_video=297
				;;
			13|1080p24_3d|1920x1080p24_3d) # res
				HDMIMode=13
				pll_video=594
				;;
			14|720p50_3d|1280x720p50_3d) # res
				HDMIMode=14
				pll_video=594
				;;
			15|720p60_3d|1280x720p60_3d) # res
				HDMIMode=15
				pll_video=594
				;;
			23|1080p24_3d|1920x1080p24_3d) # res
				HDMIMode=23
				pll_video=594
				;;
			24|720p50_3d|1280x720p50_3d) # res
                		HDMIMode=24
                		pll_video=594
                		;;
            		25|720p60_3d|1280x720p60_3d) # res
                		HDMIMode=25
                		pll_video=594
                		;;
            		26|1080p25|1080p|1920x1080p25) # res
                		HDMIMode=26
                		pll_video=297
                		;;
            		27|1080p30|1080p|1920x1080p30) # res
                		HDMIMode=27
                		pll_video=297
                		;;
            		28|4kp30|3840x2160P30) # res
                		HDMIMode=28
                		pll_video=297
                		;;
            		29|4kp25|3840x2160P25) # res
                		HDMIMode=29
                		pll_video=297
                		;;
			31|800x480) # res
                		HDMIMode=31
                		pll_video=108
                		;;
            		32|1024x768) # res
                		HDMIMode=32
                		pll_video=260
                		;;
            		33|1280x1024) # res
                		HDMIMode=33
                		pll_video=432
                		;;
            		34|1360x768) # res
                		HDMIMode=34
                		pll_video=342
                		;;
            		35|1440x900) # res
                		HDMIMode=35
                		pll_video=432
                		;;
            		36|1680x1050) # res
                		HDMIMode=36
                		pll_video=294
                		;;
		        37|2048x1536) # res
                		HDMIMode=37
                		pll_video=688
                		;; 
			*)
				if [ "X${VideoMode}" = "X" ]; then
					echo -e "$0: missing video mode. Try one of the following:\n"
				else
					echo -e "$0: illegal video mode \"-m ${VideoMode}\". Try one of the following:\n"
				fi
				ShowVideoModes
				exit 0
				;;		
		esac
	fi
	
	echo -e "Now trying to patch script.bin with your settings. \c"
	PatchScriptBin "${HDMIMode}" "${DVIUsed:-FALSE}" "${pll_video}"
	echo "Successfully finished. Please reboot for changes to take effect"
	
	# Let's see whether we have to collect debug output
	case ${Debug} in
		TRUE)
			which curl >/dev/null 2>&1 || apt-get -f -qq -y install curl
			echo -e "\nDebug output has been collected at the following URL: \c"
			(cat "${DebugOutput}"; echo -e "\n\n\nfex contents:\n" ; cat "${MyTmpFile}") \
				| curl -F 'sprunge=<-' http://sprunge.us
			;;
	esac
} # Main

ParseOptions() {
	while getopts 'hHvVdDm:M:c:C:' c ; do
	case ${c} in
		H)
			export FullUsage=TRUE
			DisplayUsage
			exit 0
			;;
		h)
			DisplayUsage
			exit 0
			;;
		v|V)
			# Increase verbosity. Will try to upload debug output from script
			# to ease reporting of bugs or strange behaviour. Use only when 
			# asked for.
			export Debug=TRUE
			DebugOutput="$(mktemp /tmp/${0##*/}.XXXXXX)"
			trap "rm \"${DebugOutput}\" ; exit 0" 0 1 2 3 15
			set -x
			exec 2>"${DebugOutput}"
			;;
		d|D)
			# use HDMI-to-DVI converter
			export DVIUsed=TRUE
			;;
		m|M)
			# The HDMI mode in question
			export VideoMode=${OPTARG}
			;;
		c|C)
			# The colour-range in question
			export ColourRange=${OPTARG}
			;;
	esac
	done
} # ParseOptions

DisplayUsage() {
	# check if stdout is a terminal...
	if test -t 1; then
		# see if it supports colors...
		ncolors=$(tput colors)
		if test -n "$ncolors" && test $ncolors -ge 8; then
			BOLD="$(tput bold)"
			NC='\033[0m' # No Color
			LGREEN='\033[1;32m'
		fi
	fi
	echo -e "Usage: ${BOLD}${0##*/} [-h/-H] -m [video mode] [-d] [-c [0-2]]${NC}\n"
	echo -e "############################################################################"
	if [ ${FullUsage} ]; then
	echo -e "\nDetailed Description:"
	grep "^#" "$0" | grep -v "^#\!/bin/bash" | sed 's/^#//'
	fi
	echo -e "\n This is a tool to set the display resolution of your Orange"
	echo -e " Pi by patching script.bin.\n\n In case you use an HDMI-to-DVI converter\c"
	echo -e " please use the ${LGREEN}-d${NC} switch.\n\n The resolution can be set using the ${LGREEN}-m${NC} switch.\c"
	echo -e " The following resolutions\n are currently supported:\n"
	ShowVideoModes
	echo -e " You can also specify the colour-range for your HDMI-display with the ${LGREEN}-c${NC} switch."
	echo -e "\n The following values for ${LGREEN}-c${NC} are currently supported:\n"
	echo -e "    ${BOLD}0${NC} -- RGB range 16-255 (Default, use \"${LGREEN}-c 0${NC}\")"
	echo -e "    ${BOLD}1${NC} -- RGB range 0-255 (Full range, use \"${LGREEN}-c 1${NC}\")"
	echo -e "    ${BOLD}2${NC} -- RGB range 16-235 (Limited video, \"${LGREEN}-c 2${NC}\")\n"
	echo -e "############################################################################\n"
} # DisplayUsage

ShowVideoModes() {
	# check if stdout is a terminal...
	if test -t 1; then
		# see if it supports colors...
		ncolors=$(tput colors)
		if test -n "$ncolors" && test $ncolors -ge 8; then
			BOLD="$(tput bold)"
			NC='\033[0m' # No Color
			LGREEN='\033[1;32m'
		fi
	fi
	OIFS=${IFS}
	IFS="|"
	awk -F" " '/ # res/ {print $1}' <"${0}" | tr -d ')' | grep -v 'awk' | while read ; do
		set ${REPLY}
		echo -e "    ${BOLD}${2}${NC}\tuse \"${LGREEN}-m ${2}${NC}\" or \"${LGREEN}-m ${1}${NC}\""
	done		
	IFS=${OIFS}
	echo -e "\n Two examples:\n\n    '${BOLD}${0##*/}${NC} ${LGREEN}-m 1080p60 -d${NC}' (1920x1080@60Hz DVI)"
	echo -e "    '${BOLD}${0##*/}${NC} ${LGREEN}-m 720i${NC}' (1280x720@30Hz HDMI)\n"
} # ShowVideoModes

## Usage: FexChange <fexfile> <section> <key> <value> 
## Either changes the value of a given key or adds the key=value to the section
FexChange() {
	# Search in file ($1) in section ($2) for key ($3)
	found=$(sed -n -e "/^\[$2\]/,/^\[/{/^$3\s*=/p}" "$1")
  
	if [ -n "$found" ]; then
		# Replace in file ($1) in section ($2) key ($3) with value ($4)
		sed -i -e "/^\[$2\]/,/^\[/{s/^\($3\s*=\s*\).*/\1$4/}" "$1"
	else
		# Append in file ($1) in section ($2) key ($3) with value ($4)
		sed -i -e "/^\[$2\]/,/^\[/{/^\[/i$3 = $4\n" -e "}" "$1"
	fi
}

PatchScriptBin() {	
	# This function will be called with 3 arguments:
	# $1 HDMI mode to set
	# $2 wether HDMI-to-DVI converter should be used or not (has to be TRUE)
	# $3 pll_video value
	
	# check whether we've the necessary tools available
	Fex2Bin="$(which fex2bin)"
	if [ "X${Fex2Bin}" = "X" ]; then
	        apt-get -f -qq -y install sunxi-tools >/dev/null 2>&1 || InstallSunxiTools >/dev/null 2>&1
	fi
	which fex2bin >/dev/null 2>&1 || (echo -e "Aborted\nfex2bin/bin2fex not found and unable to install. Exiting" >&2 ; exit 1)
	
	# try to find location of script.bin
	Path2ScriptBin=/boot
	if [ ! -f "${Path2ScriptBin}/script.bin" ]; then
		Path2ScriptBin="$(df | awk -F" " '/^\/dev\/mmcblk0p1/ {print $6}')"
		if [ ! -f "${Path2ScriptBin}/script.bin" ]; then
			echo -e "Aborted\nCan not find script.bin. Ensure boot partition is mounted" >&2
			logger "Can not find script.bin. Ensure boot partition is mounted"
			exit 1
		fi
	fi
	
	# resolve symlink to modify the original instead of the link
	ScriptBin="$(readlink -f "${Path2ScriptBin}/script.bin")"

	# create temp file	
	MyTmpFile="$(mktemp /tmp/${0##*/}.XXXXXX)"

	# convert script.bin to temporary fex file	
	bin2fex <"${ScriptBin}" 2>/dev/null >"${MyTmpFile}"
	if [ $? -ne 0 ]; then
		echo -e "Aborted\nCould not convert script.bin to fex. Exiting" >&2
		logger "Could not convert script.bin to fex"
		exit 1
	fi

	# create backup to be able to recover from failed conversion
	cp -p "${ScriptBin}" "${Path2ScriptBin}/script.bin.bak"

	# add or replace ini values
	FexChange "${MyTmpFile}" "disp_init" "screen0_output_type" "3"
	FexChange "${MyTmpFile}" "disp_init" "screen0_output_mode" "$1"
	FexChange "${MyTmpFile}" "disp_init" "screen1_output_type" "3"
	FexChange "${MyTmpFile}" "disp_init" "screen1_output_mode" "$1"
	FexChange "${MyTmpFile}" "clock" "pll_video" "$3"

	if [ -v ColourRange ]; then
		FexChange "${MyTmpFile}" "disp_init" "screen0_out_color_range" "${ColourRange}"
	fi
	
	if [ "X$2" = "XTRUE" ]; then
		# add entries necessary for HDMI-to-DVI adapters	
		FexChange "${MyTmpFile}" "hdmi_para" "hdcp_enable" "0"
		FexChange "${MyTmpFile}" "hdmi_para" "hdmi_cts_compatibility" "1"
	else
		FexChange "${MyTmpFile}" "hdmi_para" "hdcp_enable" "1"
		FexChange "${MyTmpFile}" "hdmi_para" "hdmi_cts_compatibility" "0"
	fi
	
	# convert temporary fex file back to script.bin
	fex2bin "${MyTmpFile}" "${ScriptBin}" 2>/dev/null
	if [ $? -ne 0 ]; then
	        mv "${Path2ScriptBin}/script.bin.bak" "${ScriptBin}"
	        echo -e "Aborted\nWriting script.bin went wrong. Nothing changed." >&2
	        echo -e "You may look at ${MyTmpFile} to see if there is an error there." >&2
	        logger "Writing script.bin went wrong. Nothing changed"
	        exit 1
	else rm "${MyTmpFile}"
	fi
} # PatchScriptBin

InstallSunxiTools() {
	sleep 1
	apt-get -f -qq -y install libusb-1.0-0-dev || (echo -e "Aborted\nNot possible to install a sunxi-tools requirement" ; exit 1)
	cd /tmp
	git clone https://github.com/linux-sunxi/sunxi-tools
	cd sunxi-tools
	make
	make install
} # InstallSunxiTools

Main "$@"