#!/bin/bash

# gammatrip.sh by B. Watson <urchlay@slackware.uk>.
# Licensed under the WTFPL. See http://www.wtfpl.net/txt/copying/ for details.
# Developed on Slackware 14.2, should work on most modern Linux distros.

# Randomly change gamma in X, crude simulation of an acid trip.
# It's also similar to the Atari 400/800/XL/XE "Attract Mode".
# Runs until killed via ^C or closing its terminal window, at which time
# it restores the original gamma setting, then exits.

#### User-tweakable knobs. If you don't feel like editing the script,
#    just put them on the command line, in front of the command:
#    $ DELAY=0.5 MAX=150 MIN=0 gammatrip.sh

# XRandR output to use. Leaving this blank means to use the
# first output listed by the xrandr command. Usual values are
# e.g. VGA-1 or DVI-1.
OUTPUT=

# Seconds to sleep between gamma changes. Decimal point allowed, so
# long as your system's sleep command allows it.
DELAY=${DELAY:-3}

# Max gamma for each individual color. Lower number here means darker
# colors on average. Units are 1/100ths of xrandr's --gamma floats,
# MAX=100 means 1.0. Maximum value for MAX is 999.
MAX=${MAX:-100}

# Minimum gamma for each color. Don't set higher than MAX!
# Same units as MAX.
MIN=${MIN:-30}

# VERBOSE=y to see the commands as they're executed, anything else
# means don't display them.
VERBOSE=y

#### End of user-tweakable knobs.

printmsg() {
	[ "$VERBOSE" = "y" ] && echo "$@"
}

errmsg() {
	echo "$SELF:" "$@" 1>&2
}

# Check knobs for sane(ish) settings, set up signal handlers,
# guess the active XRandR output if not provided. Get original
# gamma setting, so we can restore it later.
init() {
	SELF="$( basename $0 )"
	err=0

	if [ "$MAX" -ge 1000 ]; then
		errmsg "You can't set MAX >= 1000"
		err=1
	fi

	if [ "$MIN" -ge "$MAX" ]; then
		errmsg "You can't set MIN >= MAX"
		err=1
	fi

	if [ "$#" != "0" ]; then
		errmsg "No arguments accepted."
		errmsg "Edit script or set MIN/MAX/OUTPUT/DELAY/VERBOSE in environment."
		err=1
	fi

	if [ -z "$DISPLAY" ]; then
		errmsg "DISPLAY not set (are you running X?)"
		err=1
	fi

	[ "$err" -gt 0 ] && exit "$err"

	# SIGINT is ^C, SIGHUP is what we get when our xterm is closed.
	# SIGABRT is ^\, I hardly ever remember it exists. Trapping it here
	# doesn't work: ^\ sends the abort signal to the active child
	# process (usually sleep). Oh well. The other signals are 'just in case'.
	trap cleanup SIGINT SIGABRT SIGHUP SIGTERM SIGHUP

	OUTPUT="${OUTPUT:-$( xrandr | grep ' connected' | cut -d' ' -f1 )}"

	if [ -z "$OUTPUT" ]; then
		errmsg "Invalid OUTPUT in environment, or XRandR not supported."
		exit 1
	fi

	# Try to save the old gamma, for cleanup() to restore. Not thoroughly
	# tested (works on my system).
	OLDGAMMA="$( xrandr --verbose | sed -n '/^'$OUTPUT' /,/^[A-Z]/s/.*Gamma: *//p' )"

	# If the above command fails:
	OLDGAMMA="${OLDGAMMA:-1.0:1.0:1.0}"

	printmsg "Using output '$OUTPUT', old gamma '$OLDGAMMA'. Have a nice trip!"
}

# Called when we get a signal we trapped in init().
cleanup() {
	echo
	printmsg "Restoring old gamma"
	xrandr --output "$OUTPUT" --gamma "$OLDGAMMA"
	exit 0
}

# Takes 2 agruments: min and max. Returns a random number between
# min and max (actually >= min and < max).
getrand() {
	min="$1"
	max="$2"
	range=$(( max - min ))
	echo $(( ( RANDOM % range ) + min ))
}

# Pick random RBG values between MIN and MAX, use them to set the gamma.
cycle() {
	r="$( getrand $MIN $MAX )"
	g="$( getrand $MIN $MAX )"
	b="$( getrand $MIN $MAX )"

	cmd="xrandr --output $OUTPUT --gamma "

	for i in $r $g $b; do
		cmd="$cmd$( printf "%03d" $i | sed 's,^.,&.,' ):"
	done

	# Remove trailing colon. Actually, xrandr doesn't care about it,
	# but someday it might.
	cmd="$( echo $cmd | sed 's,:$,,' )"

	printmsg $cmd
	$cmd
}

### main()
init "$@"

while true; do
	cycle
	sleep "$DELAY"
done
