#!/bin/sh
#
# This shell script (un)installs Cygwin Prompt Here functionality.
# Requires: regtool, uname, id, cygpath, getopt, bash
# Optionally: sed
#
# TODO
# ----
# 1. When the runtime check is used, the initial sh stays in the background.
#	Can't use exec to get rid of it. Can it be done? Fixed with -2
# 2. Use runas to make sure regtool has the right privileges?
#	Will need to call myself with runas so the user only enters pwd once.
#
# KNOWN ERRORS
#
# cmd and command cannot cd to network paths like \\server\share.
#
# Dave Kilroy
# Oct 2004
#
VERSION=0.4
#	Add r to synopsis
#	Remove redundant TERM_CMDs and CYG_DIR
#	Use run.exe if I can find it
#	Add -1 and -2 options to determine how to start the shell. 2 is default
#	-2 fixes reported issues with network paths and tcsh/ash login shells
#		Thanks to Igor Petchanksi, Andrew Grimm, Munehiro (haro) Matsuda
#	-2 untested on XP/NT/95/98/ME
#VERSION=0.3
#	Use forward slashes for command to fix XP problems (CGF)
#	Correct quoting (CGF)
#	Add -r option to read registry entries to stdout to help debugging
#VERSION=0.2
#	Use consistent registry key based on shell id
#	Fixup windows version check
#	Use correct cmd/command quoting
#	Add list option, install is no longer default
#	Check regtool/sed is present
#	Add xterm -e arg
#	Check the term/shell is present before installing (except cmd)
#	Add information on window title and login shells
#	Set pdksh, tcsh, zsh to start login shells
#	Case statements ash compatible
#	Look in passwd for shell if not specified
#	Mental runtime check of passwd added. Use at own risk
#	Changed to use getopt and removed [[ ]] tests. Ash compatible.
#	Updated shebang
#VERSION=0.1
#	Initial implementation.
#	Bash required.
#	Possible quoting problems.
#	Windows uninstall only posible if script present

# Need to use eval to force correct quote evaluation
REGTOOL="eval regtool"
REGTOOL_=regtool

KNOWN_TERMS="cmd rxvt xterm"
KNOWN_SHELLS="ash bash cmd pdksh tcsh zsh passwd"

ACTION=nothing
DO_WIN_UINST=t
FORCE=f
LIST=f
READ=f
METHOD=2

# Default terminal and shell if not specified
this_term=cmd
#this_shell=passwd

####################### Parse command line #######################
GETOPTED_ARGS=`getopt -u -o aciulrnmpfh12t:s: -- $*`
set -- $GETOPTED_ARGS
for ARG; do
  case $ARG in
    -i ) shift; ACTION=i;;
    -u ) shift; ACTION=u;;
    -l ) shift; LIST=t;;
    -r ) shift; READ=t;;
    -n ) shift; DO_WIN_UINST=t;;
    -m ) shift; DO_WIN_UINST=f;;
    -p ) shift; REGTOOL="echo regtool";;
    -t ) shift; this_term=$1; shift;;
    -s ) shift; this_shell=$1; shift;;
    -f ) shift; FORCE=t;;
    -1 ) shift; METHOD=1;;
    -2 ) shift; METHOD=2;;
    -h ) shift; cat <<-EOF
	$0 version $VERSION

	Usage:
	$0 -<iulr> [-aclrnmfph12] [-t <term>] [-s <shell>]

	Adds the stated terminal/shell combination to the folder context menu
	for all users. This allows you to right click a folder in Windows Explorer
	and open a Cygwin shell in that folder.

	If the -i flag is passed without specifying a shell, the shell specified
	in /etc/passwd for the current user is added. If the terminal is not
	specified, cmd is used.

	You will require appropriate access rights for this to work. i.e. you
	need to have Administrator rights.

	Where possible, a login shell will be started. i.e. /etc/profile,
	~/profile, or their equivalents are run as well as the shells usual
	rc files. This does not apply to cmd.

	The title of the window is not set. It will default to whatever the
	terminal sets it to. This can usually be controlled from within the
	terminal.

	Options:
	  i - Install
	  u - Uninstall
	  l - List currently installed Shell Here
	  n - Be Nice and provide Control Panel uninstall option (Default)
	  m - Minimal, no Control Panel uninstall
	  f - Force write (overwrite existing, ignore missing files)
	  p - Print regtool commands to stdout rather than running them
	  r - Read all chere registry entries to stdout
	  x - Run the command line
	  1 - Use original method. This doesn\'t work with ash, tcsh or
	      network shares.
	  2 - Start via shell script. Relies on windows to change directory,
	      and login scripts avoiding doing a cd $HOME 
	  h - Help

	  t <term> - Use terminal term. Supported terminals are:
		$KNOWN_TERMS

	  s <shell> - Use the named shell. Supported shells are:
		$KNOWN_SHELLS

	Note:
	The \'passwd\' shell tries to determine your preferred shell at runtime
	from your passwd entry. This works for me, but consider it experimental.

	Example:
	  $0 -il -t rxvt -s tcsh
	Install tcsh, using rxvt as the terminal, then list what is installed.

	Helpful hints
	-------------
	If you are using cmd as a terminal because you don't want to install X,
	consider using rxvt, which does not require X.

	If you really do like cmd, you can still use it as a shell in rxvt
	or xterm.

	Use ~/.Xdefaults to set terminal resources (colour, font etc)
	EOF
	# Close quote for syntax highlighting '
	exit;;
  esac
done

# Quick check of common utilities (from sh-utils, cygwin packages)
if [ ! -x /bin/uname ] || [ ! -x /bin/cygpath ] || [ ! -x /bin/id ]; then
 echo $0 Error: uname / id / cygpath not found
 echo Aborting
 exit
fi

# Check windows version and cygwin install directory
VER=`uname -s`
ID_USER=`id -nu`
RUN_EXE=""
ASH_EXE=`cygpath -w /bin/sh`
BASH_EXE=`cygpath -w /bin/bash`

if [ -x /bin/which ]; then
 # Enable prepending of run.exe if we can find it.
 # I'm assuming run has been placed in the path.
 # We are doing this because run.exe is currently packaged in X-startup-scripts.
 # It is also likely that many non-X users will have run available on their path.
 # If and when run.exe migrates to cygutils, this will still work.
 #
 # However when that happens, anyone using the run command will need to rerun
 # chere to pick up the new path to run.
 RUN_EXE=`which run.exe 2>/dev/null`
 if [ -n "$RUN_EXE" ]; then
  # Convert to windows path
  RUN_EXE=`cygpath -w "$RUN_EXE"`
 fi
fi

# Check we have regtool (from cygwin package)
if [ ! -x /bin/regtool ]; then
 echo $0 Error: /bin/regtool not found
 echo
 echo You need regtool installed for this script to work.
 echo Aborting.
 exit
fi

# Identify the registry keys for each OS
# Same for all?
DIR_KEY=/HKCR/Directory/Shell
DRIVE_KEY=/HKCR/Drive/Shell
UINST_KEY=/HKLM/Software/Microsoft/Windows/CurrentVersion/Uninstall

if [ $ACTION = i ]; then

 # If no shell specified at this stage, grab one from /etc/passwd if it is present
 if [ ! $this_shell ] && [ -x /bin/sed ]; then
  if [ -f /etc/passwd ]; then
   this_shell=`sed -n "s?$ID_USER:.*:/bin/\(.*\)?\1?gp" /etc/passwd`
   echo Shell defaulting to $this_shell defined for $ID_USER
  else
   echo $0 Error: No shell specified, and /etc/passwd not present
   echo
   echo Can\'t guess what shell you want.
   echo Use -s to specify the shell.
   exit
  fi
 fi

 #################### Define terminals ########################
 # For each terminal, indicate the executable in TERM_EXE.
 # Unless it is cmd, it is assumed that -e passes the startup
 # command
 case $this_term in
  cmd )
	case $VER in
	CYGWIN_NT* )
	 TERM_EXE=cmd.exe;;
	* )
	 TERM_EXE=command.com;;
	esac;;
  rxvt )
	TERM_EXE="/bin/rxvt.exe";;
  xterm )
	TERM_EXE="/bin/xterm.exe";;
  * )
	echo $0 Error: Unknown terminal $this_term
	echo
	echo Supported terminals:
	echo $KNOWN_TERMS
	echo
	echo Use -h for help
	exit;;
 esac

 #################### Define shells #############################
 # For each shell, specify:
 # the command that should be used to start it in the directory %1, and keep it open.
 # the location of the executable to be checked on install
 # the accelerator to be displayed on the menu
 # the description text to be displayed in control panel uninstall window
 case $this_shell in
  bash )
	SHELL_EXE="/bin/bash.exe"
	SHELL_CMD="-l -c \\\"cd '%L'; exec $SHELL_EXE\\\""
	ACCEL="&Bash Here"
	CPH_DESC="Cygwin Bash Prompt Here";;
  ash )
	# TODO How to make this a login shell? Is -l undocumented?
	SHELL_EXE="/bin/sh.exe"
	SHELL_CMD="-c \\\"cd '%L'; exec $SHELL_EXE\\\"";
	ACCEL="&Ash Here"
	CPH_DESC="Cygwin Ash Prompt Here";;
  pdksh )
	SHELL_EXE="/bin/pdksh.exe"
	SHELL_CMD="-l -c \\\"cd '%L'; exec $SHELL_EXE\\\""
	ACCEL="&Pdksh Here"
	CPH_DESC="Cygwin Pdksh Prompt Here";;
  tcsh )
	# Apparently -l only applies if it is the only argument
	# so this may not work
	SHELL_EXE="/bin/tcsh.exe"
	SHELL_CMD="-l -c \\\"cd '%L'; exec $SHELL_EXE\\\""
	ACCEL="&Tcsh Here"
	CPH_DESC="Cygwin Tcsh Prompt Here";;
  zsh )
	SHELL_EXE="/bin/zsh.exe"
	SHELL_CMD="-l -c \\\"cd '%L'; exec $SHELL_EXE\\\""
	ACCEL="&Zsh Here"
	CPH_DESC="Cygwin Zsh Prompt Here";;
  cmd )
	case $VER in
	CYGWIN_NT* )
	 SHELL_EXE=cmd.exe
	 SHELL_CMD="/k cd %L";;
	* )
	 SHELL_EXE=command.com
	 SHELL_CMD="/k cd \\\"%1\\\"";;
	esac
	ACCEL="&Command Prompt Here"
	CPH_DESC="Command Prompt Here (cygwin)";;
  passwd )
	# Experimental
	SHELL_EXE="/bin/sh"
	# Quoting nightmare. Step through it all
	# c:\cygwin\bin\sh -c "scmd=`sed -n \"s?\`id -un\`:.*:\\\(.*\\\)?\\\1?gp\" /etc/passwd`; $scmd -l -c \"cd 'c:/program files'; exec $scmd\""
	# works from the command line
	# In registry it needs to read the same:
	# c:\cygwin\bin\sh -c "scmd=`sed -n \"s?\`id -un\`:.*:\\\(.*\\\)?\\\1?gp\" /etc/passwd`; $scmd -l -c \"cd '%1'; exec $scmd\""
	# When passed to regtool, need to requote for the shell:
	# "c:\cygwin\bin\sh -c \"scmd=\`sed -n \\\"s?\\\`id -un\\\`:.*:\\\\\\(.*\\\\\\)?\\\\\\1?gp\\\" /etc/passwd\`; \$scmd -l -c \\\"cd '%1'; exec \$scmd\\\"\""
	# When evaluated into a variable, need another level of quoting:
	# "c:\cygwin\bin\sh -c \\\"scmd=\\\`sed -n \\\\\\\"s?\\\\\\\`id -un\\\\\\\`:.*:\\\\\\\\\\\\(.*\\\\\\\\\\\\)?\\\\\\\\\\\\1?gp\\\\\\\" /etc/passwd\\\`; \\\$scmd -l -c \\\\\\\"cd '%1'; exec \\\$scmd\\\\\\\"\\\""
	# Ouch. If you think it can be quoted better, let me know.
	SHELL_CMD="-c \\\"scmd=\\\`sed -n \\\\\\\"s?\\\\\\\`id -un\\\\\\\`:.*:\\\\\\\\\\\\(.*\\\\\\\\\\\\)?\\\\\\\\\\\\1?gp\\\\\\\" /etc/passwd\\\`; \\\$scmd -l -c \\\\\\\"cd '%L'; exec \\\$scmd\\\\\\\"\\\""
	ACCEL="Shell Prompt &Here"
	CPH_DESC="Cygwin Prompt Here"

	# Extra check before installing passwd
	if [ ! -f /etc/passwd ]; then
	 if [ $FORCE = t ]; then
	  echo $0 Warning: /etc/passwd not found
	  echo
	  echo This file is required for the runtime context menu item to work.
	  echo Run mkpasswd
	 else
	  echo $0 Error: /etc/passwd not found
	  echo
	  echo This file is required for the runtime context menu item to work.
	  echo Run mkpasswd to initialise the file.
	  echo Use -f to install anyway.
	  exit
	 fi
	fi;;
  * )
	echo $0 Error: Unknown shell $this_shell
	echo
	echo Supported shells:
	echo $KNOWN_SHELLS
	echo
	echo Use -h for help
	exit;;
 esac

 # Check TERM and SHELL are present
 if [ ! -x "$TERM_EXE" ] && [ "$this_term" != cmd ]; then
  if [ $FORCE = t ]; then
   echo $0 Warning: $TERM_EXE not found
  else
   echo $0 Error: $TERM_EXE not found
   echo
   echo $TERM_EXE is where I expect to find your $this_term
   echo Use -f to install anyway.
   exit
  fi
 fi
 if [ ! -x "$SHELL_EXE" ] && [ "$this_shell" != cmd ]; then
  if [ $FORCE = t ]; then
   echo $0 Warning: $SHELL_EXE not found
  else
   echo $0 Error: $SHELL_EXE not found
   echo
   echo $SHELL_EXE is where I expect to find $this_shell
   echo Use -f to install anyway.
   exit
  fi
 fi

 # TERM_EXE needs to be called by a windows path, even from run.exe
 TERM_WIN_EXE=`cygpath -w "$TERM_EXE"`
 echo method is $METHOD
 if [ $METHOD = 1 ]; then
  if [ $this_term != cmd ]; then
   if [ -n "$RUN_EXE" ]; then
    START_CMD="$RUN_EXE $TERM_WIN_EXE -e $SHELL_EXE"
   else
    START_CMD="$TERM_WIN_EXE -e $SHELL_EXE"
   fi
  elif [ $this_shell != cmd ]; then
   # With cmd (term), the shell executable needs to be converted to a windows path
   # With cmd, we ignore TERM_CMD.
   START_CMD=`cygpath -w "$SHELL_EXE"`
  else
   # term and shell are cmd
   START_CMD=$SHELL_EXE
  fi
 else
  # METHOD 2
  XHERE="/bin/xhere"
  SHELL_CMD=""
  if [ $this_shell = cmd ]; then
   # Clear XHERE for when running command from rxvt/xterm
   XHERE="";
  elif [ $this_shell = passwd ]; then
   # Have XHERE do the call rather than bung it in the registry
   SHELL_EXE="/etc/passwd"
  fi
  
  if [ $this_term != cmd ]; then
   if [ -n "$RUN_EXE" ]; then
    START_CMD="$RUN_EXE $TERM_WIN_EXE -e $XHERE $SHELL_EXE"
   else
    START_CMD="$TERM_WIN_EXE -e $XHERE $SHELL_EXE"
   fi
  elif [ $this_shell != cmd ]; then
   START_CMD="`cygpath -w \"$BASH_EXE\"` -c \\\"$XHERE $SHELL_EXE\\\""
  else
   # The command shell won't cd anywhere anyway
   START_CMD=$SHELL_EXE
  fi
 fi

 ####### Install ###########
 if [ $FORCE = t ] || ! $REGTOOL_ check $DRIVE_KEY/cygwin_$this_shell 2> /dev/null ; then
  # Add Registry Key for Drives
  $REGTOOL add $DRIVE_KEY/cygwin_$this_shell
  $REGTOOL -s set $DRIVE_KEY/cygwin_$this_shell/ \"$ACCEL\"
  $REGTOOL add $DRIVE_KEY/cygwin_$this_shell/command
  $REGTOOL -s set $DRIVE_KEY/cygwin_$this_shell/command/ \"$START_CMD $SHELL_CMD\"
 else
  echo $0 Warning: Not overriding existing entry
  echo
  echo Entry for $this_shell already exists in the Registry Drive Key
  echo Use -f to override existing key.
  echo
 fi

 if [ $FORCE = t ] || ! $REGTOOL_ check $DIR_KEY/cygwin_$this_shell 2> /dev/null; then
  # Add Registry Key for Directories/Folders
  $REGTOOL add $DIR_KEY/cygwin_$this_shell
  $REGTOOL -s set $DIR_KEY/cygwin_$this_shell/ \"$ACCEL\"
  $REGTOOL add $DIR_KEY/cygwin_$this_shell/command
  $REGTOOL -s set $DIR_KEY/cygwin_$this_shell/command/ \"$START_CMD $SHELL_CMD\"
 else
  echo $0 Warning: Not overriding existing entry
  echo
  echo Entry for $this_shell already exists in the Registry Directory Key
  echo Use -f to override existing key.
  echo
 fi

 if [ $DO_WIN_UINST = t ]; then
  # Add uninstall registry entry
  if [ $FORCE = t ] || ! $REGTOOL_ check $UINST_KEY/cygwin_$this_shell 2> /dev/null ; then
   # Actually, should create an .inf so windows can get rid of the menu entries
   # even after the cygwin directory is wiped :(
   $REGTOOL add $UINST_KEY/cygwin_$this_shell
   $REGTOOL -s set $UINST_KEY/cygwin_$this_shell/DisplayName \"$CPH_DESC\"
   $REGTOOL -s set $UINST_KEY/cygwin_$this_shell/UnInstallString \"$ASH_EXE -c \\\"/bin/chere -u -s $this_shell\\\"\"
  else
   echo $0 Warning: Not overriding existing entry
   echo
   echo Entry for $this_shell already exists in the Registry uninstall section
   echo Use -f to override existing key.
   echo
  fi
 fi
elif [ $ACTION = u ]; then
###### UnInstall ##########
 # Check each key exists before attempting to remove it
 if $REGTOOL_ check $DRIVE_KEY/cygwin_$this_shell/command 2> /dev/null; then
  $REGTOOL remove $DRIVE_KEY/cygwin_$this_shell/command
 fi
 if $REGTOOL_ check $DRIVE_KEY/cygwin_$this_shell 2> /dev/null; then
  $REGTOOL remove $DRIVE_KEY/cygwin_$this_shell
 fi
 if $REGTOOL_ check $DIR_KEY/cygwin_$this_shell/command 2> /dev/null; then
  $REGTOOL remove $DIR_KEY/cygwin_$this_shell/command
 fi
 if $REGTOOL_ check $DIR_KEY/cygwin_$this_shell 2> /dev/null; then
  $REGTOOL remove $DIR_KEY/cygwin_$this_shell
 fi
 if $REGTOOL_ check $UINST_KEY/cygwin_$this_shell 2> /dev/null; then
  $REGTOOL remove $UINST_KEY/cygwin_$this_shell
 fi
fi

if [ $READ = t ]; then
 # Print some useful information
 echo OS is $VER
 echo chere version $VERSION
 if [ -n "$RUN_EXE" ]; then
  echo run.exe is available at $RUN_EXE
 fi
 echo
 for shell in $KNOWN_SHELLS; do
  echo --- $shell keys ---
  #### Directory entries ####
  if $REGTOOL_ check $DIR_KEY/cygwin_$shell 2> /dev/null; then
    echo Directory menu item
    $REGTOOL get $DIR_KEY/cygwin_$shell/
    echo
  fi
  if $REGTOOL_ check $DIR_KEY/cygwin_$shell/command 2> /dev/null; then
    echo Directory command
    $REGTOOL get $DIR_KEY/cygwin_$shell/command/
    echo
  fi
  #### Drive entries ####
  if $REGTOOL_ check $DRIVE_KEY/cygwin_$shell 2> /dev/null; then
    echo Drive menu item
    $REGTOOL get $DRIVE_KEY/cygwin_$shell/
    echo
  fi
  if $REGTOOL_ check $DRIVE_KEY/cygwin_$shell/command 2> /dev/null; then
    echo Drive command
    $REGTOOL get $DRIVE_KEY/cygwin_$shell/command/
    echo
  fi
  #### UnInstall entries ####
  if $REGTOOL_ check $UINST_KEY/cygwin_$shell 2> /dev/null; then
    echo Uninstall description
    $REGTOOL get $UINST_KEY/cygwin_$shell/DisplayName
    echo
    echo Uninstall command
    $REGTOOL get $UINST_KEY/cygwin_$shell/UnInstallString
    echo
  fi
  echo
 done
fi

# If requested, list what is currently installed
# Rely on the DIR key rather than UINST key, since user may pass -m
if [ $LIST = t ]; then
  if [ ! -x /bin/sed ]; then
    echo You need sed installed for the List option to work
    echo Please have a look at your context menu instead!
    exit
  fi
  echo Currently installed Cygwin Here shells:
  $REGTOOL_ list $DIR_KEY | sed -n 's/cygwin_\(.*\)/\1/gp'
fi

if [ $ACTION = nothing ] && [ $LIST = f ] && [ $READ = f ]; then
 echo $0: No action taken
 echo Use -h for help
fi