#!/bin/ash
# Fatdog Cursor Choose (replacing puppy's pcur)
# Copyright (C) James Budiono 2012, 2017
#
# 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
#
# 131129 internationalization by L18L

# 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 'Fatdog64 Cursor Chooser')"
CURSOR_THEME_DIRS="/usr/share/icons $HOME/.icons"
PREVIEW_POINTER=left_ptr
SPOT_HOME=$(awk -F: '$1=="spot" {print $6}' /etc/passwd)

### runtime variables 
THUMBNAIL_DIR=$(mktemp -dp /tmp cursor-chooser-XXXXXXXX)
GDK_TYPE_PIXBUF= MAIN_WINDOW= NOTEBOOK=


# tree model
T_TITLE=0 T_PATH=1 T_PIXBUF=2 T_TOOLTIP=3 T_SIZES=4

#---------------------------------------------------------- Global Inits
### global variables for proper GTK-server
GTK= NULL="NULL"
PIPE=/tmp/gtk.bash.$$
trap 'stop-gtk-server -fifo $PIPE; exit' HUP TERM INT # stop GTK-server in case of an error

### start GTK-server
gtk-server -fifo=$PIPE &
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

# add GTK+ API not defined in gtk-server.cfg
add_missing_gtk_api() {
	gtk gtk_server_define gdk_pixbuf_get_type NONE INT 0
	gtk gtk_server_define gtk_label_set_markup NONE NONE 2 WIDGET STRING
	
	# 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_tree_view_get_cursor NONE NONE 3 WIDGET PTR_LONG NULL
	
	# hack for getting unlimited string length and leak-free operation with gtk_tree_get_model
	# see below
	gtk gtk_server_define strcat NONE STRING 2 POINTER STRING
	gtk gtk_server_define memcpy NONE NONE 3 PTR_LONG POINTER INT			
}

### helper - leak-free, unlimited string length from gtk_tree_model_get with unpatched gtk-server
# works with gtk-server 2.3.1 and 2.4.5
# $1-tree $2-iter $3-index $4-result name
tree_model_get_string() {
	local PSTR # pointer to string
	gtk gtk_server_define gtk_tree_model_get NONE NONE 5 WIDGET WIDGET INT PTR_LONG INT # ensure correct definition
	define PSTR gtk gtk_tree_model_get $1 $2 $3 NULL -1 # PSTR --> *buf
	
	if [ $PSTR -ne 0 ]; then
		# with newer gtk-server 2.4.4 onwards you can use this
		#define $4 gtk gtk_server_string_from_pointer $PSTR 
		define $4 gtk strcat $PSTR \"\" # get string point by address --> **buf
		gtk free $PSTR # this was dynamically alloc-ed by gtk_tree_model_get, now free it
	else
		eval $4=\"\"
	fi
}

# get list of available themes, as well as the current themes
# returns THEMES, CURRENT_THEME
get_available_themes() {
	local theme_dir theme_path theme
	THEMES="" CURRENT_THEME="[$(gettext 'None')]"
	for theme_dir in $CURSOR_THEME_DIRS; do
		for theme_path in $theme_dir/*/cursors; do
			theme_path=${theme_path%/cursors}
			theme=${theme_path#$theme_dir/}
			case $theme in
				\*) ;;
				default) CURRENT_THEME=$(readlink -f "$theme_path" | sed 's_.*/__') ;;
				*)	THEMES="$THEMES $theme:$theme_path" 
					mkdir "$THUMBNAIL_DIR/$theme"
					xcur2png -c - -d "$THUMBNAIL_DIR/$theme" "$theme_path/cursors/$PREVIEW_POINTER" |
					awk 'NR!=1 {print $1}' > $THUMBNAIL_DIR/$theme/sizes
			esac
		done
	done
}

load_cursor_themes() {
	local sizes comment name
	define ITER gtk gtk_server_opaque
	gtk gtk_label_set_markup $INFO_LABEL \"$(gettext 'Current cursor theme is ')"<b>$CURRENT_THEME</b>"\"
	
	# redefine for our use
	gtk gtk_server_define gtk_list_store_set NONE NONE 13 WIDGET WIDGET INT STRING INT WIDGET INT STRING INT STRING INT STRING INT
		
	for theme in $THEMES; do
		theme_path=${theme#*:} comment="" name="${theme%%:*}"
		# get the comment, if any
		while read p; do
			case $p in
				Comment*=*) comment=${p#*=}; comment=${comment# } ;;
				Name*=*)    name=${p#*=}; name=${name# } ;;
			esac
		done < $theme_path/index.theme

		gtk gtk_list_store_append $LISTSTORE $ITER NULL
		define icon gtk gdk_pixbuf_new_from_file \"$THUMBNAIL_DIR/${theme%%:*}/${PREVIEW_POINTER}_000.png\" NULL	# actual size
		sizes=$(cat $THUMBNAIL_DIR/${theme%%:*}/sizes)
		gtk gtk_list_store_set $LISTSTORE $ITER $T_TITLE \"$name\" $T_PIXBUF $icon $T_TOOLTIP \"${comment}\" $T_PATH \"${theme_path}\" $T_SIZES \"$sizes\" -1
	done
	gtk gtk_list_store_append $LISTSTORE $ITER NULL
	gtk gtk_list_store_set $LISTSTORE $ITER $T_TITLE \"[--System default--]\" $T_PIXBUF NULL $T_TOOLTIP \"Use system default cursor shape\" $T_PATH \"SystemDefault\" $T_SIZES \"\" -1
	
	gtk g_free $ITER 
}

# returns THEME_PATH, THEME_SIZES
get_selected_theme() {
	local BUF ITER SELECTED_PATH
	define ITER gtk gtk_server_opaque
	
	THEME_PATH=
	define SELECTED_PATH gtk gtk_tree_view_get_cursor $TREEVIEW NULL
	if [ $SELECTED_PATH -ne 0 ]; then
		gtk gtk_tree_model_get_iter $LISTSTORE $ITER $SELECTED_PATH	
		gtk gtk_tree_path_free $SELECTED_PATH

		# $1-tree $2-iter $3-index $4-result name
		tree_model_get_string $LISTSTORE $ITER $T_PATH THEME_PATH
		tree_model_get_string $LISTSTORE $ITER $T_SIZES THEME_SIZES
	fi
	
	gtk free $ITER
}

# $1-theme path
install_symlink() {
	# delete existing symlink
	rm -f $HOME/.icons/default
	[ $(id -u) -eq 0 ] && [ -e $SPOT_HOME/.icons ] &&
	rm -f $SPOT_HOME/.icons/default

	# install the new one
	if [ "$1" ]; then
		ln -sf "$1" $HOME/.icons/default
		if [ $(id -u) -eq 0 ]; then
			if ! [ -e $SPOT_HOME/.icons ]; then
				cp -a $HOME/.icons $SPOT_HOME
				chown -Rh spot:spot $SPOT_HOME/.icons
			else
				ln -sf "$1" $SPOT_HOME/.icons/default
				chown -h spot:spot $SPOT_HOME/.icons/default			
			fi
		fi
	fi
}

# $1-available sizes
set_cursor_size() {
	local p pp
	
	# remove existing size settings
	sed -i -e '/X[Cc]ursor.size/d' $HOME/.Xresources
	[ $(id -u) -eq 0 ] && [ -e $SPOT_HOME/.Xresources ] && sed -i -e '/X[Cc]ursor.size/d' $SPOT_HOME/.Xresources
	[ -z "$1" ] && return # if there is nothing else, that's it

	pp=""; for p in $1; do pp="$pp $p $p"; done
	pp=$(Xdialog --stdout --no-cancel --no-tags --title "$APP_TITLE" \
		--menubox "$(gettext 'Choose cursor sizes (in pixels)')" 0 0 5 \
		auto "$(gettext 'Auto')" $pp)
		
	# set the sizes and apply it
	case $pp in
		""|auto) ;; # don't do anything else
		*)	echo "Xcursor.size: $pp" >> $HOME/.Xresources
			if [ $(id -u) -eq 0 ]; then
				echo "Xcursor.size: $pp" >> $SPOT_HOME/.Xresources
				chown -h spot:spot $SPOT_HOME/.Xresources
			fi
			xrdb -merge -nocpp $HOME/.Xresources # reload settings
			;;
	esac
}

apply_changes() {
	restartwm
}

################### 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

get_available_themes
if [ -z "$THEMES" ]; then
	Xdialog --title "$APPTITLE" --infobox "$(gettext 'No cursor themes found. Install one from package manager first.')" 0 0 10000
	rm -rf $THUMBNAIL_DIR
	exit
fi


### init gui
add_missing_gtk_api
gtk gtk_init
define GDK_TYPE_PIXBUF gtk gdk_pixbuf_get_type

define BUILDER gtk gtk_builder_new
define RESULT gtk gtk_builder_add_from_file $BUILDER "${0}.xml"
gtk gtk_builder_connect_signals $BUILDER

define MAIN_WINDOW gtk gtk_builder_get_object $BUILDER main_window
define OK_BUTTON gtk gtk_builder_get_object $BUILDER ok-button
define CANCEL_BUTTON gtk gtk_builder_get_object $BUILDER cancel-button
define INFO_LABEL gtk gtk_builder_get_object $BUILDER info-label
define LISTSTORE gtk gtk_builder_get_object $BUILDER liststore
define TREEVIEW gtk gtk_builder_get_object $BUILDER treeview

# connect signals
gtk gtk_server_connect $MAIN_WINDOW delete-event quit
gtk gtk_server_connect $OK_BUTTON clicked save
gtk gtk_server_connect $CANCEL_BUTTON clicked quit

# load cursor themes
load_cursor_themes

### gtk main loop
gtk gtk_widget_show_all $MAIN_WINDOW
while true; do 
	define EVENT gtk gtk_server_callback wait
	case $EVENT in
		quit)	break ;;
		save)	get_selected_theme
				if [ $THEME_PATH ]; then
					case $THEME_PATH in
						SystemDefault) install_symlink "" ;; # just delete
						*) install_symlink "$THEME_PATH" 
						   set_cursor_size "$THEME_SIZES" ;;
					esac
					apply_changes
					T=${THEME_PATH##*/}
					Xdialog --title "$APPTITLE" --infobox "$(eval_gettext 'Cursor has been set to ${T}.')
$(gettext 'It will take effect after X server restart.')" 0 0 10000
					break
				fi ;;
	esac
done
gtk gtk_server_exit 
rm -rf $THUMBNAIL_DIR
