#! /bin/sh
# adesklets - Shell script frontend for the adesklets interpreter
# ------------------------------------------------------------------------------
# Copyright (C) 2005, 2006 Sylvain Fourmanoit <syfou@users.sourceforge.net>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies of the Software and its documentation and acknowledgment shall be
# given in the documentation and software packages that this Software was
# used.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.   
# ------------------------------------------------------------------------------
# This is the shell script frontend for the adesklets interpreter, introduced 
# in adesklets 0.4.11. It serves a couple simple purposes:
#
# - Bring all the fake-root windows detection code out of the binary 
# interpreter, and detect them from a front-end. It makes it far simpler 
# and quicker to adapt to new situations, as this code is no longer written 
# in C, but in a sh-compatible scripting form. Hopefully, in this new format, 
# people will be able to contribute new detection routines for specific
# Window Manager easily.
#
# - Print out a couple of warnings to hopefully prevent some people that
# would not read the fourth chapter from the manual from not invoking 
# adesklets right.
# 
# - Add a couple of functionalities, such as configuration file cleanup, and
# desklets killing.
# 
# PORTABILITY NOTICE:
# ===================
#
# This shell script was made to comply with the POSIX 1003.2 and 1003.2a
# specification for the shell, and not use any vendor-specific extension
# for any program invoked. It has been tested on both BASH 3.00.16 and NetBSD
# ash 1.6.1 on a variety of platforms. It also uses some low-level, fairly 
# common utilities or builtin, all also described in POSIX, namely:
#
# 1) a Streaming EDitor (sed). It as been tested on GNU sed 4.0.9, and FreeBSD 
#    4.9 sed
# 2) test, conforming to POSIX 1003.2
# 3) mkdir and rmdir
# 4) sleep
# 5) kill
# 6) ls
# 7) ps (only for the -w switch)
#
# The xprop and xwinfinfo programs can also be used, if a given fake-root 
# window detection routine is explicitely invoked. Both XFree86 and X.org
# implementations of these utilities have been tested. Window Manager specific
# detection will also potentially need WM specific programs:
#
# kde: need consolde dcop client
#
# ------------------------------------------------------------------------------
# Lock/unlock function
# Based on the fact that creating a directory is atomic
#
LOCKFILE=/tmp/adesklets_frontend_$UID.lock

lock() {
    while : ; do
	mkdir $LOCKFILE > /dev/null 2> /dev/null && break
	sleep .2
    done
}

unlock() {
    rmdir $LOCKFILE > /dev/null 2> /dev/null
}

# ------------------------------------------------------------------------------
# Kill all desklets function
#
kill_desklets() {
    LOCK=`ls /tmp/adesklets_uid$UID_*.lock 2> /dev/null`
    test "x$LOCK" = "x" || {
	PIDS=`cat $LOCK`
	kill $PIDS > /dev/null 2> /dev/null
	sleep 1
	kill -9 $PIDS > /dev/null 2> /dev/null
    }
}

# ------------------------------------------------------------------------------
# Detect potential fake root windows function
#
roots() {
    local GEOM=`xwininfo -root | sed -n '/geometry/{s/^.*geometry[ \t]*//;p}'`
    local ID
    if test $# -eq 0 ; then
	ID="-root"
    else
	ID="-id $*"
	echo $*
    fi
    xwininfo $ID -tree | sed -n "/$GEOM/"'{s/^[ \t]*\(0x[0-9a-f]\+\).*/\1/;p}'
}

# ------------------------------------------------------------------------------
# Error handling function
#
error () {
    test $# -gt 0 && echo "Error: $*" && echo
    test $# -eq 0; exit
}

# Command line error handling function
#
usage () {
    test $# -gt 0 && echo "Error: $*" && echo
    if test "x$ADESKLETS_EMBEDDED" = "x" ; then
	DESC='Usage: adesklets [OPTION]... [string_id]'
    else
	DESC='Possible options are:'
    fi
    cat<<EOF 
$DESC

Fake root window detection
   --e16          Enlightenment 16, version 0.16.8 and later
   --kde          KDE >= 3.4.1 desktop detection
   --nautilus     Nautilus desktop detection
   --rox          ROX-Filer detection (incomplete)
   --user         Interactive detection (by user click)
   --xfce4	  Xfce4 desktop detection (tested on Xfce4 4.2.x, 
                  with xfdesktop managing the icons)
   --xffm         Xffm desktop window detection (tested on xffm 4.3.3.1,
                  with xffm-deskview managing the icons)

   --do-it-once   When applicable, do not run the detection for each desklet, 
                  but only once for all. Of course, desklets on multiple 
                  screens will unlikely detect the right fake root window, but
                  it will speed things up for single screen settings. 
Miscellany
   -h,--help	  Print out this message and exit
   -w progname    Wait for at least one 'progname' instance to run under 
                  the current user id (UID) before performing any further
                  action
   -d delay       Wait for a given delay (in seconds) before performing any 
                  further action
   -e editor      Use an alternate editor for configuring the desklets; it 
                  needs to be a standalone executable that does not need 
                  a pseudo terminal
EOF
    if test "x$ADESKLETS_EMBEDDED" = "x" ; then
	cat<<EOF
   -v,--version   Printout adesklets version
   -k,--killall   Kill all running, registered desklets
   -c,--cleanup   Remove all dead entries from \$HOME/.adesklets
                  (this implies \`--killall')
   -i,--installer Invoke the desklet installer (requires Python)
   -f script	  Execute command set from the \`script' file

If no \`string_id' is given, adesklets acts as a launcher (unless 
the \`-f' switch is involved). Otherwise, adesklets acts as 
an interpreter, if no \`-k' or \`-c' switch is used.

The most usual invokation of adesklets is the bare \`adesklets' command, 
without arguments: it makes adesklets launch every registered desklets
from \$HOME/.adesklets, without any fake root window detection.
EOF
    fi
    test $# -eq 0; exit
}

# ------------------------------------------------------------------------------
# Initial test
#
test -e $HOME/.adesklets && { 
    test -f $HOME/.adesklets || \
	error "$HOME/.adesklets is not a configuration file; please remove it."
}

# ------------------------------------------------------------------------------
# Parse the command line.
#
SELF=$0
OPTS=
MODE=
DO_IT_ONCE=0
while test $# -gt 0 ; do
  case "$1" in
      --e16|--kde|--nautilus|--rox|--user|--xfce4|--xffm)
	  MODE=`echo $1 | sed 's/--//'`
	  ;;
      --do-it-once)
	  DO_IT_ONCE=1
	  ;;
      -h|--help)
          usage
	  ;;
      -d)
	  test $# -gt 1 || usage "no delay given after -d switch."
	  sleep $2 > /dev/null 2> /dev/null || \
	      usage "Invalid delay of \`$2' seconds given."
	  shift
	  ;;
      -w)
	  test $# -gt 1 || usage "no progname given after -w switch."
	  while : ; do
	      test -z "`ps -u $UID -U $UID -o comm | sed -n "/$2/p"`" || break
	      sleep 10
	  done
	  shift
	  ;;
      -e)
	  test $# -gt 1 || usage "no editor given after -e switch."
	  ADESKLETS_EDITOR="$HOME/.adesklets_editor.sh"
	  echo '#! /bin/sh'     > $ADESKLETS_EDITOR
	  echo "$2 &"          >> $ADESKLETS_EDITOR
	  echo 'kill -9 $PPID' >> $ADESKLETS_EDITOR
          chmod +x $ADESKLETS_EDITOR
	  test -x $ADESKLETS_EDITOR || usage "could not create editor wrapper"
	  export EDITOR="$ADESKLETS_EDITOR"
	  shift
	  ;;
      -v|--version)
	  echo "adesklets 0.6.1"
	  echo "Written By Sylvain Fourmanoit <syfou@users.sourceforge.net>"
	  exit 0
	  ;;
      -k|--killall)
	  kill_desklets
	  exit 0
	  ;;
      -c|--cleanup)
	  # The cleanup routine assumes the file is EXACTLY formatted as
	  # it is output by the binary interpreter.
	  kill_desklets
	  LINE=
	  for DESKLET in `sed -n 's/^\[//;s/]$//;/^\//{=;p}' $HOME/.adesklets`
	    do
	    if test "$DESKLET" -gt 0 2> /dev/null ; then
		ID=$DESKLET
	    else
		test -x "$DESKLET" || LINE="$LINE $ID"
	    fi
	  done
	  sed -i "`echo "$LINE" | sed 's/[0-9]\+/&,+1d;/g'`" $HOME/.adesklets
	  exit 0
	  ;;
      -i|--installer)
	  shift
	  exec adesklets_installer $*
	  exit 1
	  ;;
      -f)
	  test $# -gt 1 || usage "no file name given after -f switch."
	  test -r $2 || usage "could not read file \`$2'."
	  OPTS="-f $2"
	  shift
	  ;;
      -*)
	  usage "invalid option \`$1'."
	  ;;
      *)
	  test "x$OPTS" = "x" || \
	      usage "invalid combination, \`$OPTS' and \`$1'."
	  OPTS="$1"
	  ;;
  esac
  shift
done

# ------------------------------------------------------------------------------
# Fake root window detection code
#
test "x$MODE" = "x" || ADESKLETS_MODE="$MODE"
test "x$ADESKLETS_MODE" = "x" || {
    case "$ADESKLETS_MODE" in
	xfce4|nautilus|e16)
	    # Identify the lead desktop window from root property,
	    # then find the last child with the proper dimension
	    # For now on, we take advantage from the "compatibility code" 
	    # included in xfce4.
	    DESKTOP=`xprop -root | \
             sed -n '/^NAUTILUS_DESKTOP_WINDOW_ID/{s/.* \(0x[0-9a-f]\+\)/\1/;p}'`
	    for ROOT in `roots $DESKTOP` ; do : ; done
	    ADESKLETS_ROOT=$ROOT
	    ;;
	xffm)
	    # Get the first child of the last window named 'xffm-deskview'.
	    # The bet is on: how long before this changes?
	    DESKTOP=`xwininfo -root -tree | \
             sed -n '/xffm-deskview/{s/[ \t]*\(0x[0-9a-f]\+\).*/\1/;p}' | \
             sed -n '$p'`
	    ADESKLETS_ROOT=`xwininfo -id $DESKTOP -tree | \
	     sed -n '/^[ \t]*0x[0-9a-f]\+/{s/[ \t]*\(0x[0-9a-f]\+\).*/\1/;p}' | \
             sed '1q'`
	    ;;
	kde)
	    test "x`dcop kdesktop default isIconsEnabled`" = "xtrue" && {
		# First, we need to sync. the real root pixmap
		# with the desktop: the fake root window is using a
		# ParentRelative backgroundPixmap, with the real image
		# from one of its parent, but the root being empty.
		# We are very lucky this work... But for how long?
		test "x$MODE" = "x" || {
		    # Just do this at high level
		    for I in 0 1; do
			dcop kdesktop default setIconsEnabled $I
		    done
		}
		# Detect the fake root window
		for ROOT in `roots` ; do
		    ADESKLETS_ROOT=`xprop -id $ROOT | \
                       sed -n '/__SWM_VROOT/{s/.*\(0x[0-9a-f]\+\)/\1/;p}'`
		    test "x$ADESKLETS_ROOT" = "x" || break
		done
	    }
	    ;;
	rox)
	    # Find first full screen window with a Rox-Filer somewhere in 
	    # its property
	    for ROOT in `roots` ; do
		test "x`xprop -id $ROOT | sed -n '/ROX-Filer/p'`" = "x" || break
	    done
	    ADESKLETS_ROOT=$ROOT
	    ;;
	user)
	    # In this special mode, the pseudo-root is determined 
	    # interactively using xwininfo. Since xwininfo needs to
	    # grab the pointer, just make sure we serialize all calls
	    # using a lock.
	    lock
	    ADESKLETS_ROOT=`xwininfo  | \
               sed -n '/Window id/{s/.*Window id: \([^ ]*\) .*/\1/p}'`
	    unlock
	    ;;
	*)
	    error "unknown mode."
	    ;;
    esac
    test "$DO_IT_ONCE" -eq 0 && export ADESKLETS_MODE
    export ADESKLETS_ROOT
}

# ------------------------------------------------------------------------------
# In case of launcher, just make preliminary FIFO cleanup
#
test "x$OPTS" = "x" && \
    rm -f `ls /tmp/*adesklets_fifo* 2>/dev/null` > /dev/null 2>&1

# ------------------------------------------------------------------------------
# Now, re-invoke the binary
#
export ADESKLETS_FRONTEND=1
exec adesklets $OPTS
