#!/bin/dash
# GUI for Xorg xkb map selector
# Copyright (C) James Budiono 2012, 2015, 2017
# License: GNU GPL Version 3 or later
# note: requires xml2 package
#131026 internationalisation by L18L
#140106 keyboard test added
#171215 re-written

# 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 'Keyboard Layout Wizard')"
XKB_RULES_PATH=/usr/share/X11/xkb/rules
XORG_CONF_DIR=/etc/X11/xorg.conf.d
XKBLANG_CONF=$XORG_CONF_DIR/xkb-lang.conf  # used by Xorg
KEYMAP_CONFIG=$FATDOG_STATE_DIR/xkeymap	   # used xinitrc
DEFAULT_XKB_OPTION="terminate:ctrl_alt_bksp"

### run-time global variables
ACTIVE_RULE=

##################3
# helpers

dlg() {
	Xdialog --stdout --title "$APPTITLE" "$@"
}

die() {
	dlg --infobox "$*" 0 0 10000
	exit 1
}

die_cancelled() {
	die "$*\n$(gettext 'Operation cancelled. Nothing is changed.')"
}

read_current_settings() {
	[ -e $KEYMAP_CONFIG ] && cat $KEYMAP_CONFIG || echo us
}

# out: ACTIVE_RULE
get_active_rule() {
	# get the name of rule currently in-use
	ACTIVE_RULE=$(setxkbmap -query | awk '/rules:/ { print $2} ')
	[ $ACTIVE_RULE ] || ACTIVE_RULE=evdev
}

# ACTIVE_RULE
get_xdialog_layout_items() {
	local rule_file
	
	# get the corresponding xkb rule file
	rule_file=$XKB_RULES_PATH/${ACTIVE_RULE}.xml

	# xml2 converts xml to flat file, awk script converts into Xdialog treeview selection
	xml2 < $rule_file | awk '
	function qsort(A, left, right,   i, last) {
		if (left >= right)
			return
		swap(A, left, left+int((right-left+1)*rand()))
		last = left
		for (i = left+1; i <= right; i++)
			if (A[i] < A[left])
				swap(A, ++last, i)
		swap(A, left, last)
		qsort(A, left, last-1)
		qsort(A, last+1, right)
	}
	function swap(A, i, j,   t) {
		t = A[i]; A[i] = A[j]; A[j] = t
	}
	/layout/ {
		# start new layout
		if (match($0,/layout\/configItem\/name=/)) {
			sub(/.*=/,"")
			current_layout = $0
			layouts = layouts " " $0
		}
		
		# get layout description
		if (match($0,/layout\/configItem\/description=/)) {
			sub(/.*=/,"")
			desc[current_layout] = $0
		}
		
		# start new variant (for a layout)
		if (match($0,/variant\/configItem\/name=/)) {
			sub(/.*=/,"")
			current_variant=$0
			variants[current_layout] = variants[current_layout] " " $0
		}
		
		# get description for the variant
		if (match($0,/variant\/configItem\/description=/)) {
			sub(/.*=/,"")
			variant_desc[current_layout ":" current_variant] = $0
		}
		
	}
	END {
		# print the layout & variants in Xdialog treeview format
		n = split(layouts, zz, " ")
		qsort(zz,1,n)
		for (i=1; i<n; i++) {
			printf zz[i] " \"" zz[i] " - " desc[zz[i]] "\" off 0 "
			printf zz[i] " \"" "" zz[i] "  -  Standard " desc[zz[i]] "\" off 1 "
			
			nn = split (variants[zz[i]], yy, " ")
			for (k=1; k<nn; k++) {
				printf zz[i] ":" yy[k] " \"" "" zz[i] ":" yy[k] "  -  " variant_desc[zz[i] ":" yy[k]] "\" off 1 "
			}
		}
	}
'
}

############################
# GUI

# $1-msg
select_layout() {
	local msg="$1"
    eval "dlg --ok-label \"$(gettext 'Select and choose next')\" --cancel-label \"$(gettext 'End selection')\" \
    --backtitle '$(gettext "Selecting multiple keyboard layouts.\nChoose the layout you want then click select. End selection to finish.")' \
    --treeview  '$(eval_gettext '$msg')' 30 70 0 $(get_xdialog_layout_items)"
}

#
select_multiple_layout() {
	local ok=yes selected layouts
	
	layouts=""
	while [ $ok ]; do
		if selected=$(select_layout "$(eval_gettext 'Currently selected\n[ $layouts ]')"); then
			layouts="$layouts $selected"
		else
			ok= # break the loop
		fi
		layouts=${layouts# }	
	done
	echo "$layouts"
}

# $@ - layouts, ACTIVE_RULE
get_layout_switcher() {
	local n=$#
	[ $# -eq 1 ] && return 0 # no switcher for one layout

	layout_switcher=
	local rule_file=$XKB_RULES_PATH/${ACTIVE_RULE}.lst
	local choices="$(sed -e '/grp:/!d; s/[ \t]*//; h; ' \
	    -e 's/[ \t].*//; s/.*/"&"/; x; ' \
	    -e 's/[^ \t]*[ \t]//; s/^[ \t]*//; s/.*/ "&"/; '\
	    -e 'H; x; s/\n//; ' \
	    -e '/alt_shift_toggle/! s/.*/& off/; ' \
	    -e '/alt_shift_toggle/ s/.*/& on/' $rule_file)"
	eval dlg --ok-label "\"$(gettext 'Continue')\"" --no-tags --radiolist \
	"\"$(eval_gettext 'You selected $n layouts. Please choose layout switcher.\nDefault is Alt-Shift.')\"" \
	0 0 10 $choices
}


# $1-layouts, $@-xkb settings
test_settings() {
	local current="$(read_current_settings)" layouts="$1"
	shift
	apply_settings "$@"

	dlg --no-cancel --ok-label "$(gettext 'Done')" --inputbox "$(eval_gettext 'Type to test selected layouts:\n[ $layouts ]')" 10 50 \
	"$(gettext 'qwertz/&> @ | %;:_?<')" > /dev/null;

	setxkbmap -option "" $current -synch
	setxkbmap -query > /dev/null # immediate query to workaround odd Xorg bugs
}

# $@-xkb settings
apply_settings() {
	setxkbmap -option "" "$@" -synch
	setxkbmap -query > /dev/null # immediate query to workaround odd Xorg bugs	
}

# $@ layouts
split_layouts() {
	local layouts= variants=
	local ll vv p
	for p; do
		ll=${p%:*}
		vv=${p#*:}
		[ "$vv" = "$ll"	 ] && vv=
		#echo $ll $vv
		layouts="$layouts,$ll"
		variants="$variants,$vv"
	done
	echo "${layouts#,}|${variants#,}"
}

# $1-layouts $2-switcher
build_xkb_cmd() {
	local split options
	split=$(split_layouts $1)
	options="-option $DEFAULT_XKB_OPTION"
	[ "$2" ] && options="$options -option $2"	
	echo "$options ${split%|*} ${split#*|}"
}

# $1-layouts $2-switcher
build_xkb_conf() {
	local split options
	split=$(split_layouts $1)
	options="$DEFAULT_XKB_OPTION"
	[ "$2" ] && options="$options,$2"		
	cat << EOF	
# This file is auto-generated by fatdog-keyboard-wizard.sh
# Don't edit this manually - changes may be lost.
Section "InputClass"
    Identifier "XKB Lang"
    MatchIsKeyboard "yes"
    Option "XkbLayout" "${split%|*}"
    Option "XkbVariant" "${split#*|}"
    Option "XKbOptions" "$options"
EndSection    
EOF
}

###################
# main
get_active_rule

# big giant loop because shell does not have goto
while true; do

	# multiple selection loop
	while true; do
		# 1. Choose first layout. Cancel -> Done. OK -> select next layout
		layouts=$(select_multiple_layout)
		[ -z "$layouts" ] && # must at least have one layout to continue
		die_cancelled "$(gettext 'No layouts were selected. Operation cancelled.')"

		# 2. Confirmation.
		dlg --ok-label "$(gettext 'Yes, continue')" \
			--cancel-label "$(gettext 'Go back and choose again')" \
			--yesno "$(eval_gettext 'You selected\n\n[ $layouts ]\n\nIs this correct?')" \
			0 0 && break
	done

	# 3. If multiple layout select -> choose switcher. 
	switcher=$(get_layout_switcher $layouts) ||
	die_cancelled "$(gettext 'You did not choose layout switcher.')"

	# 4. Test selection
	xkb_cmd=$(build_xkb_cmd "$layouts" "$switcher")
	test_settings "$layouts" $xkb_cmd

	# 5. confirm selection
	if dlg --cancel-label "$(gettext 'No, cancel it')" \
		--ok-label "$(gettext 'Yes, apply and save')" \
		--yesno "$(gettext 'Do you like the new settings?')" 0 0
	then
	
		# 6. Save and apply
		build_xkb_conf "$layouts" "$switcher" > $XKBLANG_CONF
		echo $xkb_cmd > $KEYMAP_CONFIG
		apply_settings $xkb_cmd

		# 7. Good bye
		dlg --infobox "$(eval_gettext 'Keyboard layout now set to\n\n[ $layouts ]\n\nand stored in\n\n${KEYMAP_CONFIG}\nand\n${XKBLANG_CONF}' )" 0 0 10000
		exit

	else
		dlg --cancel-label "$(gettext 'No, I want to leave and exit')" \
		--ok-label "$(gettext 'Yes, I will try again')" \
		--yesno "$(gettext 'Do you like start over?')" 0 0 ||
		die_cancelled ""
	fi

done
