#!/bin/ash
# Fatdog dialer - replacement for pupdial
#
# Copyright (C) James Budiono 2013
# License: GNU GPL Version 3 or later
#
# Note: the bits in Global Inits, which is part of GTK-server bash init code
#       is (C) Peter van Eerten. Everything else is (C) James Budiono
#
# 131112 L18L internationalisation

# std localisation stanza
export TEXTDOMAIN=fatdog
. gettext.sh
# performance tweak - use "C" if there is no localisation
! [ -e $TEXTDOMAINDIR/${LANG%.*}/LC_MESSAGES/$TEXTDOMAIN.mo ] &&
! [ -e $TEXTDOMAINDIR/${LANG%_*}/LC_MESSAGES/$TEXTDOMAIN.mo ] && LANG=C

### configuration
APPTITLE="$(gettext 'Fatdog Dialer')"
CONFIG_FILE=/etc/fatdog-dialer.conf
RFCOMM_CONF=/etc/bluetooth/rfcomm.conf
PROFILE_DIR=/etc/fatdog-dialer.profile.d/
WVDIAL_CONF=/etc/wvdial.conf
MODEM_DEV_PATTERN="/dev/rfcomm* /dev/ttyS* /dev/ttyUSB* /dev/ttyACM* /dev/bbmodem*"
MODEM_CMD_PIN="AT+CPIN="
MODEM_CMD_INIT=ATI
MODEM_CMD_DIAL=ATDT
MODEM_CMD_SET_APN='AT+CGDCONT=1,"IP","{APN}"'
WPAGUI_SERVICE=/etc/init.d/50-Wpagui
NETWORK_SERVICE=/etc/rc.d/rc.network

### run-time variables
MODEMS= # list of all modems
MODEM_COMBO= # gtk combo box object for modem dropdown list
PROFILE_COMBO= # gtk combo box object for profile dropdown list

### profile variables with default values - these variables are loaded / stored to profile config
P_NAME=
P_COMMENT=
P_MODEM=
P_NUMBER=
P_USERNAME=
P_PASSWORD=
P_APN=
P_PIN=
P_BAUD=115200
P_CARRIER_CHECK=
P_STUPID_MODE=
P_AUTO_RECONNECT=
P_INIT4=	# INIT1,2,3 is used by system for init - AT, ATI, AT+CGDCONT above
P_INIT5=
P_INIT6=
P_WVDIAL_OPTIONS=
P_MODULES=

# must run as root
[ $(id -u) -ne 0 ] && exec gtksu "$APPTITLE" "$0"

# load settings from config file, if any
[ -r $CONFIG_FILE ] && . $CONFIG_FILE

#---------------------------------------------------------- Global Inits
### global variables for proper GTK-server
GTK= NULL="NULL"
PIPE=/tmp/gtk.bash.$$
trap 'kill $GTKSERVER_PID; rm $PIPE; exit' INT HUP TERM 0 # stop GTK-server when shell exit
#trap 'stop-gtk-server -fifo $PIPE; exit' HUP TERM INT # alternative trap line

### start GTK-server
gtk-server -fifo=$PIPE &
GTKSERVER_PID=$!
while [ ! -p $PIPE ]; do sleep 1; continue; done

### GTK-server helper functions
# Assignment function
define() { $2 $3 $4 $5 $6 $7 $8 $9; eval $1=\'"$GTK"\'; }

# Communicate with GTK-server
gtk()
{
/bin/echo "$@" > $PIPE
read GTK < $PIPE
}

#---------------------------------------------------------- GUI realization

########## gui stuff ##########

init_gtk() {
	# init gtk
	gtk gtk_init
	
	# add GTK+ API not defined in gtk-server.cfg
	gtk gtk_server_define gtk_builder_new NONE WIDGET 0
	gtk gtk_server_define gtk_builder_add_from_file NONE INT 3 WIDGET STRING NULL
	gtk gtk_server_define gtk_builder_add_from_string NONE INT 4 WIDGET STRING INT NULL
	gtk gtk_server_define gtk_builder_get_object NONE WIDGET 2 WIDGET STRING
	gtk gtk_server_define gtk_builder_connect_signals NONE NONE 2 WIDGET NULL
	
	gtk gtk_server_define gtk_box_reorder_child NONE NONE 3 WIDGET WIDGET INT
}

init_gui() {
	# 1.a load GUI
	define BUILDER gtk gtk_builder_new
	define RESULT gtk gtk_builder_add_from_file $BUILDER "${0}.xml"
	gtk gtk_builder_connect_signals $BUILDER
	
	# main window
	define MAIN_WINDOW gtk gtk_builder_get_object $BUILDER main_window
	define MAIN_VBOX gtk gtk_builder_get_object $BUILDER main_vbox
	define CONNECT_BUTTON gtk gtk_builder_get_object $BUILDER connect_button
	define EDITPROFILE_BUTTON gtk gtk_builder_get_object $BUILDER editprofile_button
	define DELETEPROFILE_BUTTON gtk gtk_builder_get_object $BUILDER deleteprofile_button
	define QUIT_BUTTON gtk gtk_builder_get_object $BUILDER quit_button

	# profile window
	define PROFILE_WINDOW gtk gtk_builder_get_object $BUILDER profile_window
	define PROFILE_CANCEL_BUTTON gtk gtk_builder_get_object $BUILDER profile-cancel-button
	define PROFILE_SAVE_BUTTON gtk gtk_builder_get_object $BUILDER profile-save-button
	define PROFILE_MODEM_BOX gtk gtk_builder_get_object $BUILDER modem-box
	define TEST_MODEM_BUTTON gtk gtk_builder_get_object $BUILDER test-modem-button
	define RELOAD_MODEM_BUTTON gtk gtk_builder_get_object $BUILDER reload-modem-list
	
	define PROFILE_NAME_EDIT gtk gtk_builder_get_object $BUILDER profile-name-edit
	define COMMENT_EDIT gtk gtk_builder_get_object $BUILDER comment-edit
	define BAUD_EDIT gtk gtk_builder_get_object $BUILDER baud-edit
	define DIAL_NUMBER_EDIT gtk gtk_builder_get_object $BUILDER dial-number-edit
	define USERNAME_EDIT gtk gtk_builder_get_object $BUILDER username-edit
	define PASSWORD_EDIT gtk gtk_builder_get_object $BUILDER password-edit
	define APN_EDIT gtk gtk_builder_get_object $BUILDER apn-edit
	define PIN_EDIT gtk gtk_builder_get_object $BUILDER pin-edit
	define CARRIER_CKBOX gtk gtk_builder_get_object $BUILDER carrier-check
	define AUTORECONNECT_CKBOX gtk gtk_builder_get_object $BUILDER auto-reconnect
	define STUPIDMODE_CKBOX gtk gtk_builder_get_object $BUILDER stupid-mode
	define INIT4_EDIT gtk gtk_builder_get_object $BUILDER init4-edit
	define INIT5_EDIT gtk gtk_builder_get_object $BUILDER init5-edit
	define INIT6_EDIT gtk gtk_builder_get_object $BUILDER init6-edit
	define WVDIAL_EDIT gtk gtk_builder_get_object $BUILDER wvdial-edit
	define MODULES_EDIT gtk gtk_builder_get_object $BUILDER modules-edit

	gtk g_object_unref $BUILDER

	# 1.b create and init controls  we can't create in glade-3
	init_profile_combo
	init_modem_combo
	
	# 2. connect signals
	gtk gtk_server_connect $MAIN_WINDOW delete-event finish
	gtk gtk_server_connect $QUIT_BUTTON clicked finish
	gtk gtk_server_connect $CONNECT_BUTTON clicked connect
	gtk gtk_server_connect $EDITPROFILE_BUTTON clicked edit-profile
	gtk gtk_server_connect $DELETEPROFILE_BUTTON clicked delete-profile
	
	gtk gtk_server_connect $PROFILE_WINDOW delete-event cancel-profile-edit
	gtk gtk_server_connect $PROFILE_CANCEL_BUTTON clicked cancel-profile-edit
	gtk gtk_server_connect $PROFILE_SAVE_BUTTON clicked save-profile
	gtk gtk_server_connect $TEST_MODEM_BUTTON clicked test-modem
	gtk gtk_server_connect $RELOAD_MODEM_BUTTON clicked reload-modem-list
	
	# 3. show the main window
	gtk gtk_widget_show $MAIN_WINDOW	
}

# load list of profiles to profile_combo
# $1-profile to be made active
init_profile_combo() {
	local p index

	[ "$PROFILE_COMBO" ] && gtk gtk_widget_destroy $PROFILE_COMBO
	define PROFILE_COMBO gtk gtk_combo_box_new_text
	gtk gtk_widget_set_tooltip_text $PROFILE_COMBO \""$(gettext 'Choose the profile to use.')"\"
	gtk gtk_box_pack_start $MAIN_VBOX $PROFILE_COMBO 0 1 0
	gtk gtk_box_reorder_child $MAIN_VBOX $PROFILE_COMBO 0
	gtk gtk_widget_show $PROFILE_COMBO
	
	index=0
	while read -r p; do
		gtk gtk_combo_box_append_text $PROFILE_COMBO \""$p"\"
		[ "$1" = "$p" ] && gtk gtk_combo_box_set_active $PROFILE_COMBO $index
		index=$(( $index + 1 ))
	done << EOF
$(ls -v $PROFILE_DIR)
EOF
}

# load list of available modems
init_modem_combo() {
	local p
	MODEMS=

	[ "$MODEM_COMBO" ] && gtk gtk_widget_destroy $MODEM_COMBO
	define MODEM_COMBO gtk gtk_combo_box_new_text
	gtk gtk_box_pack_start_defaults $PROFILE_MODEM_BOX $MODEM_COMBO 
	gtk gtk_box_reorder_child $PROFILE_MODEM_BOX $MODEM_COMBO 1
	gtk gtk_widget_show $MODEM_COMBO
	
	for p in $MODEM_DEV_PATTERN; do
		case $p in
			*\*) ;; # anything with asterisk still leftover is bad name (=ie not device found)
			*) MODEMS="$MODEMS $p"
			   gtk gtk_combo_box_append_text $MODEM_COMBO \""$p"\"
		esac
	done	
}



########### helper functions ###########

# load profile from selected entry
load_profile() {
	define P_NAME gtk gtk_combo_box_get_active_text $PROFILE_COMBO
	[ -z "$P_NAME" ] && return
	[ -r "$PROFILE_DIR/$P_NAME" ] && . "$PROFILE_DIR/$P_NAME"	# load the profile	
}

flush_gtk() {
	gtk gtk_events_pending
	while [ $GTK -eq 1 ]; do
		gtk gtk_main_iteration
		gtk gtk_events_pending
	done	
}

# $@ - module name with parameters
load_module() {
	case $1 in
		*/*) insmod "$@" ;;
		*) modprobe "$@" ;;	
	esac
}

# $@ - module name with parameters
unload_module() {
	rmmod $1
}

# $1-command, $2-modules list, separated with three colons
load_unload_modules() {
	local rest front cmd
	cmd=$1 rest="$2"
	while [ "$rest" ]; do
		case "$rest" in
			*:::*)	front=${rest%%:::*} rest=${rest#*:::} ;;
			*) front=${rest} rest="" ;;
		esac
		$1 $front
	done
}



######### command handlers ##########

### edit or create new profile
edit_profile() {
	local index p

	# load profile and initialise window values 
	load_profile
	[ -z "$P_NAME" ] && P_NAME="$(gettext 'New Profile')"
	
	# first, entries
	gtk gtk_entry_set_text $PROFILE_NAME_EDIT \""$P_NAME"\"
	gtk gtk_entry_set_text $COMMENT_EDIT \""$P_COMMENT"\"
	gtk gtk_entry_set_text $BAUD_EDIT \""$P_BAUD"\"
	gtk gtk_entry_set_text $DIAL_NUMBER_EDIT \""$P_NUMBER"\"
	gtk gtk_entry_set_text $USERNAME_EDIT \""$P_USERNAME"\"
	gtk gtk_entry_set_text $PASSWORD_EDIT \""$P_PASSWORD"\"
	gtk gtk_entry_set_text $APN_EDIT \""$P_APN"\"
	gtk gtk_entry_set_text $PIN_EDIT \""$P_PIN"\"
	gtk gtk_entry_set_text $INIT4_EDIT \""$P_INIT4"\"
	gtk gtk_entry_set_text $INIT5_EDIT \""$P_INIT5"\"
	gtk gtk_entry_set_text $INIT6_EDIT \""$P_INIT6"\"
	gtk gtk_entry_set_text $WVDIAL_EDIT \""$P_WVDIAL_OPTIONS"\"
	gtk gtk_entry_set_text $MODULES_EDIT \""$P_MODULES"\"
	
	# then checkboxes
	gtk gtk_toggle_button_set_active $CARRIER_CKBOX 0
	gtk gtk_toggle_button_set_active $AUTORECONNECT_CKBOX 0
	gtk gtk_toggle_button_set_active $STUPIDMODE_CKBOX 0
	[ "$P_CARRIER_CHECK" ] && gtk gtk_toggle_button_set_active $CARRIER_CKBOX 1
	[ "$P_AUTO_RECONNECT" ] && gtk gtk_toggle_button_set_active $AUTORECONNECT_CKBOX 1
	[ "$P_STUPID_MODE" ] && gtk gtk_toggle_button_set_active $STUPIDMODE_CKBOX 1
	
	# then the combobox - this is tricky
	if [ "$P_MODEM" ]; then
		index=0
		for p in $MODEMS; do
			if [ "$P_MODEM" = "$p" ]; then
				gtk gtk_combo_box_set_active $MODEM_COMBO $index
				index=-1 # found
				break
			else
				index=$(( $index + 1))
			fi
		done
		if [ $index -ne -1 ]; then # not found
			MODEMS="$MODEMS $P_MODEM"
			gtk gtk_combo_box_append_text $MODEM_COMBO \""$P_MODEM"\"
			gtk gtk_combo_box_set_active $MODEM_COMBO $index
		fi
	fi
		
	# show window
	gtk gtk_widget_show $PROFILE_WINDOW
}

### test the currently selected modem
test_modem() {
	local msgbox
	define P_MODEM gtk gtk_combo_box_get_active_text $MODEM_COMBO
	[ -z "$P_MODEM" ] && return
	
	case $(echo -e "\r\nAT\r\n^X" | microcom -d 1000 -t 1000 $P_MODEM) in
		*OK*) define msgbox gtk gtk_message_dialog_new $PROFILE_WINDOW 3 0 2 \""$(eval_gettext '$P_MODEM works!')"\" \"\" ;;
		*) define msgbox gtk gtk_message_dialog_new $PROFILE_WINDOW 3 3 2 \""$(eval_gettext '$P_MODEM does not work!')"\" \"\" ;;
	esac
	gtk gtk_window_set_title $msgbox \""$(gettext 'Test Modem')"\"
	gtk gtk_dialog_run $msgbox
	gtk gtk_widget_destroy $msgbox
}

### save the newly-created or modified profile
save_profile() {
	local p
	
	# get values from current profile window
	# first, entries
	define P_NAME gtk gtk_entry_get_text $PROFILE_NAME_EDIT
	define P_COMMENT gtk gtk_entry_get_text $COMMENT_EDIT
	define P_BAUD gtk gtk_entry_get_text $BAUD_EDIT
	define P_NUMBER gtk gtk_entry_get_text $DIAL_NUMBER_EDIT
	define P_USERNAME gtk gtk_entry_get_text $USERNAME_EDIT
	define P_PASSWORD gtk gtk_entry_get_text $PASSWORD_EDIT
	define P_APN gtk gtk_entry_get_text $APN_EDIT
	define P_PIN gtk gtk_entry_get_text $PIN_EDIT
	define P_INIT4 gtk gtk_entry_get_text $INIT4_EDIT
	define P_INIT5 gtk gtk_entry_get_text $INIT5_EDIT
	define P_INIT6 gtk gtk_entry_get_text $INIT6_EDIT
	define P_WVDIAL_OPTIONS gtk gtk_entry_get_text $WVDIAL_EDIT
	define P_MODULES gtk gtk_entry_get_text $MODULES_EDIT
	
	# then checkboxes
	P_CARRIER_CHECK= 
	P_AUTO_RECONNECT=
	P_STUPID_MODE=
	gtk gtk_toggle_button_get_active $CARRIER_CKBOX
	[ $GTK = "1" ] && P_CARRIER_CHECK=yes
	gtk gtk_toggle_button_get_active $AUTORECONNECT_CKBOX
	[ $GTK = "1" ] && P_AUTO_RECONNECT=yes
	gtk gtk_toggle_button_get_active $STUPIDMODE_CKBOX
	[ $GTK = "1" ] && P_STUPID_MODE=yes
	
	# then the combobox
	define P_MODEM gtk gtk_combo_box_get_active_text $MODEM_COMBO
	
	# save the profile
	for p in P_COMMENT P_MODEM P_NUMBER P_USERNAME P_PASSWORD P_APN P_PIN P_BAUD P_CARRIER_CHECK P_STUPID_MODE P_AUTO_RECONNECT \
		P_INIT4 P_INIT5 P_INIT6 P_WVDIAL_OPTIONS P_MODULES; do
		eval echo $p=\\\'\$$p\\\'
	done > "$PROFILE_DIR/$P_NAME"
	
	# rebuild the profile list combobox
	init_profile_combo "$P_NAME"
}

### delete existing profile
delete_profile() {
	define P_NAME gtk gtk_combo_box_get_active_text $PROFILE_COMBO
	rm "$PROFILE_DIR/$P_NAME"
	init_profile_combo
}


### connect to wireless
connect() {
	local p TMP WVDIAL_PID
	load_profile
	[ -z "$P_NAME" ] && return
	
	# 1. make sure we are compatible with pupdial (this is stupid!)
	cp /etc/ppp/wvdial* /etc/ppp/peers
	for p in $P_WVDIAL_OPTIONS; do
		echo $p >> /etc/ppp/peers/wvdial
	done
	
	# 2. construct an entry in wvdial.conf file
	# 2.a first delete existing entry in wvdial.conf
	TMP=$(mktemp -p /tmp fatdog-dialer.XXXXXXXX)
	awk '
/\[.*\]/ { dontprint=0 }
/\[Dialer fatdog-dialer\]/ { dontprint=1 }
{ if (!dontprint) print $0 }
' $WVDIAL_CONF > $TMP

	# 2.b then construct it
	{
		cat << EOF
[Dialer fatdog-dialer]
Modem = $P_MODEM
Baud = $P_BAUD
Init1 = $MODEM_CMD_INIT
Init2 = $([ "$P_PIN" ] && echo ${MODEM_CMD_PIN}$P_PIN || echo "AT" )
Init3 = $([ "$P_APN" ] && echo $MODEM_CMD_SET_APN | sed "s/{APN}/${P_APN}/" || echo "AT")
Init4 = $P_INIT4
Init5 = $P_INIT5
Init6 = $P_INIT6
Dial Command = $MODEM_CMD_DIAL
Phone = $P_NUMBER
Username = $P_USERNAME
Password = $P_PASSWORD
Auto Reconnect = $([ "$P_AUTO_RECONNECT" ] && echo yes || echo no)
Carrier Check = $([ "$P_CARRIER_CHECK" ] && echo yes || echo no)
Stupid Mode = $([ "$P_STUPID_MODE" ] && echo yes || echo no)
EOF
	} >> $TMP
	mv $TMP $WVDIAL_CONF
	
	# 3. Load modules, if needed
	load_unload_modules load_module ppp_generic # this is always needed
	load_unload_modules load_module "$P_MODULES"
	
	# 4. Stop 50-Wpagui & rc.network
	RESTART_WPAGUI=
	if sh $WPAGUI_SERVICE status | grep -q running; then
		RESTART_WPAGUI=1
		sh $WPAGUI_SERVICE stop
	fi
	$NETWORK_SERVICE stop
	
	# 5. dial & get wvdial's PID
	wvdial fatdog-dialer > $TMP 2>&1 &
	WVDIAL_PID=$!
	
	# 6. output to window and wait until connection is terminated.
	gtk gtk_widget_hide $MAIN_WINDOW
	flush_gtk	
	Xdialog --no-ok --title "$APPTITLE" --backtitle "
$(gettext 'Connecting ... click Cancel to terminate connection.
')" --tailbox $TMP 20 80
	kill $WVDIAL_PID
	
	# 7. done, cleanup
	load_unload_modules unload_module "$P_MODULES"
	rm $TMP

	# 8. restart wpagui and rc.network
	$NETWORK_SERVICE start &
	[ $RESTART_WPAGUI ] && sh $WPAGUI_SERVICE start &

	gtk gtk_widget_show $MAIN_WINDOW
}

############### main ###############

### gtk-server localisation stanza
gtk gtk_server_define setlocale NONE STRING 2 INT STRING
gtk gtk_server_define bindtextdomain NONE STRING 2 STRING STRING
gtk gtk_server_define textdomain NONE STRING 1 STRING
gtk setlocale 6 \"\" # 6 corresponds to LC_ALL
gtk bindtextdomain $TEXTDOMAIN $TEXTDOMAINDIR
gtk textdomain $TEXTDOMAIN

init_gtk
init_gui
while true; do 
	define EVENT gtk gtk_server_callback wait
	case $EVENT in
		finish) break ;;
		connect) connect ;;

		# profile management
		delete-profile) delete_profile ;;
		edit-profile) edit_profile ;;		
		save-profile)  gtk gtk_widget_hide $PROFILE_WINDOW; save_profile ;;
		cancel-profile-edit) gtk gtk_widget_hide $PROFILE_WINDOW ;;
		
		test-modem) test_modem ;;
		reload-modem-list) init_modem_combo ;;
	esac
done
gtk gtk_server_exit 
