#!/usr/bin/bash
#
# Simple frontend for MAME using BASH
#
# (c) Peter van Eerten 2006, 2007, 2008 GPL license
# 
# Tested with:
#   -BASH 3.1 on SLAMD 12 and GTK-server 2.2.3
#   -BASH 3.1 on Zenwalk 4.8, 5.2 and GTK-server 2.2.5
#
# October 14, 2006: Initial release
# October 17, 2006: Improved configpanel
# November 2, 2006: Improved start sequence
# November 19, 2006:
#   -Fixed bug during startup
#   -Fixed bug counting games
#   -Improved window placement
# January 10, 2007:
#   -Fixed small bug in configpanel
# January 16, 2007:
#   -Improved embedded GTK4Bash
#   -Added signalling sync
# August 20, 2007:
#   -Improved embedded GTK for GTK-server 2.1.4
# September 9, 2007:
#   -Improved creating gamelist
# February 9, 2008:
#   -Improved GTK-server configfile lookup
#   -Compatible with SDLmame now
#   -Some improvements in GUI
#   -Improved error handling
#   -Implemented doubleclick to start game
#-------------------------------------------------------------------------------------------------Setup embedded GTK

# Set the extending globbing option in BASH
shopt -s extglob

declare PIPE="/tmp/gtk.bash.\$$"

# Find GTK-server configfile first
if [[ -f gtk-server.cfg ]]; then
    CFG=gtk-server.cfg
elif [[ -f /etc/gtk-server.cfg ]]; then
    CFG=/etc/gtk-server.cfg
elif [[ -f /usr/local/etc/gtk-server.cfg ]]; then
    CFG=/usr/local/etc/gtk-server.cfg
else
    echo "No GTK-server configfile found! Please install GTK-server..."
    exit 1
fi

# Now create global functionnames from GTK API
if [[ ! -f $HOME/.gtk4bash || $CFG -nt $HOME/.gtk4bash ]]
then
    echo "#!/usr/bin/bash" > $HOME/.gtk4bash
    echo "gtk-server fifo=$PIPE &" >> $HOME/.gtk4bash
    echo "while [ ! -p $PIPE ]; do continue; done" >> $HOME/.gtk4bash
    while read LINE
    do
	if [[ $LINE = FUNCTION_NAME* && $LINE = +(*gtk_*|*gdk_*|*g_*|*glade_*) ]]
	then
	    LINE=${LINE#*= }
	    printf "\nfunction ${LINE%%,*}\n" >> $HOME/.gtk4bash
	    printf "{\n/bin/echo ${LINE%%,*} \$@ > $PIPE" >> $HOME/.gtk4bash
	    printf "\nread GTK < $PIPE\n}\n" >> $HOME/.gtk4bash
	fi
    done < $CFG
    printf "\nfunction gtk()\n{\necho \$1 > $PIPE; read GTK < $PIPE;\n}\n" >> $HOME/.gtk4bash
fi

# Declare global variables
declare GTK NULL="NULL"

# Make these available again
unset CFG PIPE LINE

# Include the generated '.gtk4bash'-file in the shellscript to use embedded GTK functions
. $HOME/.gtk4bash

#-------------------------------------------------------------------------------------------------The launch procedure

function Launch_Game
{
# BASH mechanism to declare some local variables
typeset PTH ROW ZIP VALSIZE MODE="soft" VGA=0 NR=0

while [[ $NR -lt $TOTAL ]]
do
    gtk_tree_path_new_from_string $NR
    PTH=$GTK
    gtk_tree_selection_path_is_selected $SEL $PTH
    ROW=$GTK
    gtk_tree_path_free $PTH
    if [[ $ROW = "1" ]]
    then
	break
    else
	((NR+=1))
    fi
done
if [[ $NR == $TOTAL ]]
then
    gtk_widget_show $WARNING
else
    if [[ ${GAME[$NR]##*.} != "zip" ]]
    then
	gtk_widget_show $NOGAME
    else
	ZIP=`$UNZIP -l ${DIR##* }/${GAME[$NR]} 2>&1 | wc -l`
	gtk_progress_configure $BAR 0.0 0.0 $ZIP
	gtk_server_callback "update"
	gtk_range_get_value $SCALE
	let VOL=32-${GTK%%.*}
	gtk_spin_button_get_value_as_int $SIZE
	VALSIZE=$GTK
	gtk_toggle_button_get_active $RADIO1
	if [[ $GTK -ne 1 ]]
	then
	    gtk_toggle_button_get_active $RADIO2
	    if [[ $GTK -ne 1 ]]
	    then
		MODE="opengl"
		VGA=2
	    else
		VGA=1
	    fi
	fi
	gtk_toggle_button_get_active $CHECK
	if [[ $GTK -eq 1 ]]
	then
	    if [[ $MAME = *x11* ]]
	    then
		($MAME -volume -$VOL -vidmod $VGA -fullscreen -grabmouse ${GAME[$NR]%%.zip} > $MAMELOG 2>&1 &)
	    else
		($MAME -volume -$VOL -video $MODE ${GAME[$NR]%%.zip} > $MAMELOG 2>&1 &)
	    fi
	else
	    if [[ $MAME = *x11* ]]
	    then
		($MAME -volume -$VOL -vidmod $VGA -nofullscreen -nograbmouse ${GAME[$NR]%%.zip} -scale $VALSIZE > $MAMELOG 2>&1 &)
	    else
		($MAME -volume -$VOL -video $MODE -window ${GAME[$NR]%%.zip} > $MAMELOG 2>&1 &)
	    fi
	fi
	# Only xmame writes extensive logging
	if [[ $MAME = *x11* ]]
	then
	    gtk_widget_show_all $PROGRESS
	    while [[ `cat $MAMELOG` != *done* && `cat $MAMELOG` != *mode*not*available* && `cat $MAMELOG` != *game*cannot*be*run* ]]
	    do
		NR=`wc -l $MAMELOG`
		gtk_progress_set_value $BAR ${NR%% *}
		gtk_server_callback "update"
	    done
	fi
	gtk_widget_hide $PROGRESS
	gtk_server_callback "update"
    fi
fi
}

#-------------------------------------------------------------------------------------------------Main program

# Output of mame binaries to this file
MAMELOG=/tmp/mame.log

# Check if UNZIP is installed
UNZIP=`which unzip 2>/dev/null`
if [[ -z $UNZIP ]]
then
    echo "UNZIP not found on your system!"
    exit 1
fi

# Check if MAME is installed
MAME=`which xmame.x11 2>/dev/null`
if [[ -z $MAME ]]
then
    MAME=`which mame 2>/dev/null`
    if [[ -z $MAME ]]
    then
	echo "(x)MAME not found on your system!"
	echo "Download and install xMAME from http://x.mame.net or MAME from http://rbelmont.mameworld.info!"
	exit 1
    else
	CFGDIR=$HOME/.mame/
	CFGFILE=mame.ini
    fi
else
    CFGDIR=$HOME/.xmame/
    CFGFILE=xmamerc
fi

# Check existence of configfile in case of Xmame
if [[ ! -f $CFGDIR$CFGFILE ]]
then
    echo "No MAME configfile found at $CFGDIR$CFGFILE!"
    echo "Please setup a configfile as follows:"
    echo
    echo "xmame.x11 -showconfig > $HOME/.xmame/xmamerc"
    echo "-or-"
    echo "mame -showconfig > $HOME/.mame/mame.ini"
    echo
    echo "Edit the settings to your needs and run this frontend again."
    echo
    exit 1
fi

# Check if list needs to be regenerated
if [[ -f $CFGDIR/gamelist ]]
then
    if [[ $MAME = *x11* && `head -1 $CFGDIR/gamelist` != `$MAME -version 2>/dev/null` ]]
    then
	echo "New version of MAME detected!"
	rm $CFGDIR/gamelist
    elif [[ $MAME != *x11* && `head -1 $CFGDIR/gamelist` != `$MAME -help | head -1` ]]
    then
	echo "New version of MAME detected!"
	rm $CFGDIR/gamelist
    fi
fi

# Create list with titles of all known games
if [[ ! -f $CFGDIR/gamelist ]]
then
    echo "Regenerating gamelist..."
    if [[ $MAME = *x11* ]]
    then
	$MAME -version > $CFGDIR/gamelist 2>/dev/null
    else
	$MAME -help | head -1 > $CFGDIR/gamelist 2>&1
    fi
    $MAME -listfull >> $CFGDIR/gamelist 2>&1
fi

# Find the directory where the ROMS are stored
while read DIR
do
    if [[ $DIR = rompath* ]]
    then
	DIR=${DIR##* }
	break
    fi
done  < $CFGDIR$CFGFILE

# Check if there are new ROMS
LS=`ls -l $DIR/*.zip | wc -l`

if [[ -f $CFGDIR/titles ]]
then
    WC=`wc -l $CFGDIR/titles`
    if [[ ${WC% *} -ne $LS ]]
    then
	echo "ROM list has changed!"
	rm -f $CFGDIR/titles
    fi
fi

# Create progress dialog
gtk_init $NULL $NULL
gtk_window_new 0
PROGRESS=$GTK
gtk_widget_set_size_request $PROGRESS 330 30
gtk_window_set_resizable $PROGRESS 0
gtk_window_set_position $PROGRESS 1
gtk_window_set_icon_name $PROGRESS "joystick"
gtk_progress_bar_new
BAR=$GTK
gtk_container_add $PROGRESS $BAR

# Setup existing titles
if [[ ! -f $CFGDIR/titles ]]
then
    gtk_window_set_title $PROGRESS "'ROM list changed!'"
    gtk_progress_configure $BAR 0.0 0.0 $LS
    gtk_progress_bar_set_text $BAR "'Creating index for changed ROM list...'"
    gtk_widget_show_all $PROGRESS
    gtk_server_callback "update"
    NR=0
    LS=`ls $DIR`
    for WHICH in $LS
    do
	FOUND=0
	while read GLIST
	do
	    if [[ ${WHICH%%.*} = ${GLIST%% *} ]]
	    then
		echo ${GLIST//\'} >> $CFGDIR/titles
		((NR+=1))
		FOUND=1
		break
	    fi
	done < $CFGDIR/gamelist
	if [[ $FOUND -eq 0 ]]
	then
	    echo "*** Game not in database: ${WHICH} ***" >> $CFGDIR/titles
	fi
	gtk_progress_set_value $BAR $NR
	gtk_server_callback "update"
    done
    gtk_widget_hide $PROGRESS
    gtk_server_callback "update"
fi

# Define GUI
gtk_window_new 0
WIN=$GTK
gtk_window_set_title $WIN "'BASH (x)Mame Frontend'"
gtk_window_set_position $WIN 1
gtk_window_set_resizable $WIN 1
gtk_widget_set_size_request $WIN 425 370
gtk_window_set_icon_name $WIN "joystick"
# Use async functionality for errors. Signal every 250 msecs, max time of doubleclick
gtk_server_connect $WIN "show" "check-error-and-doubleclick"
gtk_server_timeout 250 $WIN "show"
# Presets
gtk_frame_new NULL
ITER=$GTK
gtk_list_store_new 1 64
LST=$GTK
gtk_tree_view_new_with_model $LST
CHOICE=$GTK
gtk_server_connect $CHOICE "button-press-event" $CHOICE 1
gtk_tree_view_set_headers_visible $CHOICE 1
gtk_tree_view_get_selection $CHOICE
SEL=$GTK
gtk_cell_renderer_text_new
CELL=$GTK
gtk_tree_view_column_new_with_attributes "'Games in $DIR - doubleclick to start'" $CELL "text" 0 $NULL
COLUMN=$GTK
gtk_tree_view_append_column $CHOICE $COLUMN
gtk_tree_view_column_set_resizable $COLUMN 1
gtk_tree_view_column_set_clickable $COLUMN 1
gtk_scrolled_window_new $NULL $NULL
SW=$GTK
gtk_scrolled_window_set_policy $SW 1 1
gtk_scrolled_window_set_shadow_type $SW 1
gtk_container_add $SW $CHOICE
TOTAL=0
while read TITLE
do
    if [[ ${TITLE%% *} != "***" ]]
    then
	gtk_list_store_append $LST $ITER
	gtk_list_store_set $LST $ITER 0 "${TITLE#* }" -1
	GAME[$TOTAL]=${TITLE%% *}.zip
	((TOTAL+=1))
    fi
done < $CFGDIR/titles
gtk_tree_view_get_hadjustment $CHOICE
ADJ=$GTK
gtk_button_new_with_label "Launch!"
LAUNCH=$GTK
gtk_widget_set_size_request $LAUNCH 50 25
gtk_button_new_with_label "About"
ABOUT=$GTK
gtk_widget_set_size_request $ABOUT 50 25
gtk_vseparator_new
VSEP=$GTK
gtk_check_button_new_with_label "Fullscreen"
CHECK=$GTK
gtk_hscale_new_with_range 0 32 1
SCALE=$GTK
gtk_scale_set_draw_value $SCALE 1
gtk_scale_set_value_pos $SCALE 1
gtk_widget_set_size_request $SCALE 80 20
gtk_label_new "'Startup volume:  '"
LABEL2=$GTK
gtk_hseparator_new
HSEP=$GTK
gtk_spin_button_new_with_range 1 5 1
SIZE=$GTK
gtk_spin_button_set_value $SIZE 2
gtk_label_new "'Scale:  '"
LABEL1=$GTK
gtk_radio_button_new_with_label_from_widget $NULL "'Software video'"
RADIO1=$GTK
gtk_radio_button_new_with_label_from_widget $RADIO1 "'Xvideo layer'"
RADIO2=$GTK
gtk_radio_button_new_with_label_from_widget $RADIO1 "'OpenGL renderer'"
RADIO3=$GTK
while read CONFIG
do
    if [[ $CONFIG = volume* ]]
    then
	if [[ $MAME = *x11* ]]
	then
	    gtk_range_set_value $SCALE ${CONFIG##* -}
	else
	    gtk_range_set_value $SCALE ${CONFIG##* }
	fi
    fi
    if [[ $CONFIG = fullscreen* ]]
    then
	gtk_toggle_button_set_active $CHECK ${CONFIG##* }
	if [[ ${CONFIG##* } -eq 1 ]]
	then
	    gtk_widget_set_sensitive $SIZE 0
	    gtk_widget_set_sensitive $LABEL1 0
	fi
    fi
    if [[ $CONFIG = window* ]]
    then
	let TMP=1-${CONFIG##* }
	gtk_toggle_button_set_active $CHECK $TMP
	if [[ $TMP -eq 1 ]]
	then
	    gtk_widget_set_sensitive $SIZE 0
	    gtk_widget_set_sensitive $LABEL1 0
	fi
    fi
    if [[ $CONFIG = video* ]]
    then
	case ${CONFIG##* } in
	    0)
		gtk_toggle_button_set_active $RADIO1 1;;
	    1)
		gtk_toggle_button_set_active $RADIO2 1;;
	    2)
		gtk_toggle_button_set_active $RADIO3 1;;
	    "soft")
		gtk_toggle_button_set_active $RADIO1 1;;
	    "opengl")
		gtk_toggle_button_set_active $RADIO3 1;;
	esac
    fi
done < $CFGDIR$CFGFILE
gtk_frame_new NULL
FRAME1=$GTK
gtk_widget_set_size_request $FRAME1 200 70
gtk_frame_new NULL
FRAME2=$GTK
gtk_widget_set_size_request $FRAME2 155 70
gtk_frame_new NULL
FRAME3=$GTK
gtk_widget_set_size_request $FRAME3 60 70
# Window layout
gtk_expander_new "Options"
EXP=$GTK
gtk_vbox_new 0 0
VBOX1=$GTK
gtk_box_pack_start $VBOX1 $SW 1 1 1
gtk_box_pack_start $VBOX1 $HSEP 0 0 1
gtk_box_pack_start $VBOX1 $EXP 0 0 1
gtk_container_add $WIN $VBOX1
# Configpanel construction
gtk_fixed_new
FIXED=$GTK
gtk_fixed_put $FIXED $FRAME1 1 1
gtk_fixed_put $FIXED $CHECK 5 10
gtk_fixed_put $FIXED $SIZE 140 10
gtk_fixed_put $FIXED $LABEL1 100 13
gtk_fixed_put $FIXED $LABEL2 5 45
gtk_fixed_put $FIXED $SCALE 105 45
gtk_fixed_put $FIXED $FRAME2 205 1
gtk_fixed_put $FIXED $RADIO1 210 5
gtk_fixed_put $FIXED $RADIO2 210 25 
gtk_fixed_put $FIXED $RADIO3 210 45
gtk_fixed_put $FIXED $FRAME3 365 1
gtk_fixed_put $FIXED $ABOUT 370 10
gtk_fixed_put $FIXED $LAUNCH 370 40
gtk_container_add $EXP $FIXED
gtk_widget_show_all $WIN
gtk_widget_grab_focus $LAUNCH
gtk_adjustment_set_value $ADJ 0.0
gtk_window_set_transient_for $PROGRESS $WIN
gtk_window_set_position $PROGRESS 4
# Create warning
gtk_message_dialog_new $WIN 0 3 2 "'\nERROR: select a game to launch!'" "''"
WARNING=$GTK
gtk_window_set_title $WARNING "'Error occured'"
# Create error
gtk_message_dialog_new $WIN 0 3 2 "'\nERROR: the game cannot be run!\n\nCheck \"$MAMELOG\" for details.'" "''"
ERROR=$GTK
gtk_window_set_title $ERROR "'Error occured'"
# Create error
gtk_message_dialog_new $WIN 0 3 2 "'\nERROR: not a game file!'" "''"
NOGAME=$GTK
gtk_window_set_title $NOGAME "'Error occured'"
# Create error
gtk_message_dialog_new $WIN 0 3 2 "'\nERROR: selected videomode not supported by \"${MAME##*/}\"!'" "''"
NOMODE=$GTK
gtk_window_set_title $NOMODE "'Error occured'"
# Create ABOUT box
gtk_server_version
TMP="\t\t***  BASH Xmame Launcher ***\r\rBASH ${BASH_VERSION} with GTK-server $GTK.\r\r\tUsing \"${MAME##*/}\" to launch your games!\r\rVisit http://www.gtk-server.org/ for info on GTK-server!"
gtk_message_dialog_new $WIN 0 0 2 "'$TMP'" "''"
DIALOG=$GTK
gtk_window_set_title $DIALOG "'About this program...'"
gtk_window_set_title $PROGRESS "'Launching in progress!'"
gtk_progress_bar_set_text $BAR "'Please wait...'"
# Disable some options for MAME
if [[ $MAME != *x11* ]]
then
    gtk_widget_set_sensitive $RADIO2 0
    gtk_widget_set_sensitive $SIZE 0
fi

# Initialize variables
EVENT=0

# Mainloop
while [[ $EVENT != $EXIT && $EVENT != $WIN ]]
do
    gtk_server_callback "wait"
    EVENT=$GTK

    case $EVENT in

	$LAUNCH)
	    Launch_Game;;

        $CHOICE)
	    gtk_server_mouse 2
	    if [[ $GTK -eq 1 ]]
	    then
		((DOUBLE+=1))
		if [[ $DOUBLE -eq 2 ]]
		then
		    let DOUBLE=0
		    Launch_Game
		fi
	    fi;;

	$WARNING)
	    gtk_widget_hide $WARNING;;

	$ABOUT)
	    gtk_widget_show $DIALOG;;

	$DIALOG)
	    gtk_widget_hide $DIALOG;;

	$ERROR)
	    rm -f $MAMELOG
	    gtk_widget_hide $ERROR;;

	$NOGAME)
	    gtk_widget_hide $NOGAME;;

	$NOMODE)
	    rm -f $MAMELOG
	    gtk_widget_hide $NOMODE;;

	$CHECK)
	    gtk_toggle_button_get_active $CHECK
	    if [[ $GTK -eq 1 ]]
	    then
		gtk_widget_set_sensitive $SIZE 0
		gtk_widget_set_sensitive $LABEL1 0
	    else
		if [[ $MAME = *x11* ]]
		then
		    gtk_widget_set_sensitive $SIZE 1
		    gtk_widget_set_sensitive $LABEL1 1
		fi
	    fi;;
	"check-error-and-doubleclick")
	    # Here the doubleclick counter is reset as the timeout has passed
	    let DOUBLE=0
	    # Use async to check if there are errors, (x)mame does not continue in such a situation
	    if [[ -f $MAMELOG ]]
	    then
		if [[ `cat $MAMELOG` = *game*cannot*be*run* ]]
		then
		    gtk_widget_show $ERROR
		elif [[ `cat $MAMELOG` = *mode*not*available* ]]
		then
		    gtk_widget_show $NOMODE
		fi
	    fi;;
    esac

done

# Remove temp file
rm -f $MAMELOG

# Exit GTK
gtk_server_exit

# Exit shell
exit 1
