#!/bin/bash
# Copyright (c) 2002 SuSE Linux AG, Nuernberg, Germany.
# All rights reserved.
#
# Author: Christian Zoz <zoz@suse.de>
# 
# acpid_proxy - program dispatcher for ACPI daemon
#
# This shell script is called by the ACPI daemon (acpid) when the state
# of any power management function has changed.  The exact events that
# trigger the calling of acpid_proxy depend on how acpid was configured
# in /etc/acpi/*.
#
# Within this script the system administrator can put any additional 
# commands or actions which should be performed upon state transitions.
# But for basic customizing you should use the variables in 
# /etc/sysconfig/powermanagement first.
#
# acpid_proxy is called with specific arguments that describe the event
# that has occurred.  It is this script's responsibility to query the
# hardware or the ACPI service (via /proc/acpi) for more information,
# and to take the appropriate action.
#
# SIMPLIFIED CONFIGURATION  
#
# The operation of this script can be controlled either by setting the
# according variables in /etc/sysconfig/powermanagement appropriately.

. /etc/sysconfig/powermanagement

###########################################################
# Internal variables and functions

# needed commands
SHUTDOWN=/sbin/shutdown
LOGGER="/bin/logger -t acpid_proxy[$$]"

# cpufreq binary for sending USR1 (slow dowon) and USR2 (speed up) signals
CPUFREQD_BIN=/usr/sbin/cpufreqd

# needed files
ACPI_LIB_DIR=/var/lib/acpi
#LID_STATE_FILE=$ACPI_LIB_DIR/lid_state
LID_STATE_FILE=/proc/acpi/button/lid/*/state
PROC_STATE_FILE=$ACPI_LIB_DIR/processor_state
BDFLUSH_ORIG_FILE=$ACPI_LIB_DIR/bdflush
BDFLUSH_PROC_FILE=/proc/sys/vm/bdflush


#
# This function creates output via stdout in the form of:
#      CPU0:yes
#      CPU1:yes(virtual)
#      CPU2:no
#      CPU3:yes
#      CPU4:yes(virtual)
#
#    A "no" means that the processor is not "HyperThreaded" while a "yes"
#    means the processor is "HyperThreaded".  When the cpu is the second
#    processor for a "HyperThreaded" processor it is labeled as "(virtual)"
#
build_CPU_hyper_threaded_data () {
    grep -E 'processor|flags' /proc/cpuinfo \
    | while read processor cpu_number; do
        read flags the_flags
        hyperthreaded="no"
        last_hyperthreaded="${last_hyperthreaded:-no}"
        cpu_number="$(echo ${cpu_number} | cut -d\  -f2-)"
        [ $(echo " $(echo ${the_flags} | cut -d\  -f2-) " | grep -c ' ht ') -ne 0 ] && hyperthreaded="yes"
        [ "${hyperthreaded}" = "yes" ] && [ "${last_hyperthreaded}" = "yes" ] && hyperthreaded="yes(virtual)"
        echo "CPU${cpu_number}:${hyperthreaded}" 
        last_hyperthreaded="${hyperthreaded}"
    done
}

#
#  This function returns the first processor names for the hyperthreaded
#  processors.
#
set_cpu_and_throttle_arrrays () {
    local CPU_COUNT PROCESSOR_NAME
    local TMPFILEPREFIX="/tmp/set_cpu_and_throddle_arrrays__$$_"
    #
    #  Get the list of visiable CPUs.  When the CPU is HyperThreaded, do not include the virtual process as the
    #  two can not be running at seperate speeds (hardware restriction).
    #
    build_CPU_hyper_threaded_data > ${TMPFILEPREFIX}_CPU_HT.d8a
    let CPU_COUNT=0
    for d_name in $(cd /proc/acpi/processor && ls -d CPU*); do
        [ $(grep "^${d_name}:" ${TMPFILEPREFIX}_CPU_HT.d8a | grep -c '(virtual)') -ne 0 ] && continue
        PROCESSOR_NAME[${CPU_COUNT}]="${d_name}"
        let CPU_COUNT++
    done
    rm -f ${TMPFILEPREFIX}_CPU_HT.d8a >/dev/null 2>&1
    echo "${PROCESSOR_NAME[*]}"
}

# Checks if one or more given commands are available and executable
#command_available() {
#  CA=yes
#  test -x $1 || CA=no
#  shift
#  while [ $# -gt 0 ] ; do
#    test -x $1 || CA=no
#    shift
#  done
#  test "$CA" = yes && return
#  test -z "$ACPI_DEBUG" -o "$ACPI_DEBUG" = no && return 1
#  test -x $LOGGER || return 1
#  ${LOGGER} "command $1 not found"
#  return 1
#}

#get_kbd_interrupts() {
#	awk "/ 1:/{print \$2}" /proc/interrupts
#}


get_lid_state() {
	local TEMP STATE
	# there shoud only be one directory in /proc/acpi/button/lid/ 
	read TEMP STATE < $LID_STATE_FILE
	case $STATE in
		clo*|CLO*) echo CLOSE ;;
		*)         echo OPEN  ;;
	esac
}

disk_sleep() {
	${1:+hdparm -S $1 /dev/hda}
}

tweak_bdflush() {
	declare -i FILL_LEVEL BLOCKS_TO_WRITE KUPDATED_INTERVAL DATA_TIMEOUT \
	           BDFLUSH_START BDFLUSH_STOP
	if [ -e "$BDFLUSH_ORIG_FILE" ] ; then
		read FILL_LEVEL_ORIG BLOCKS_TO_WRITE_ORIG DUMMY DUMMY \
		     KUPDATED_INTERVAL_ORIG DATA_TIMEOUT_ORIG \
		     BDFLUSH_START_ORIG BDFLUSH_STOP_ORIG DUMMY \
		     < $BDFLUSH_ORIG_FILE
	else
		cat $BDFLUSH_PROC_FILE > $BDFLUSH_ORIG_FILE
	fi
	read FILL_LEVEL BLOCKS_TO_WRITE DUMMY DUMMY KUPDATED_INTERVAL \
	     DATA_TIMEOUT BDFLUSH_START BDFLUSH_STOP DUMMY \
	     < $BDFLUSH_PROC_FILE
	while [ $# -gt 0 ] ; do
		case $1 in
			kupdate*)
				VAL=${1#kupdate=}
				case $VAL in
					restore)
						KUPDATED_INTERVAL_NEW=$KUPDATED_INTERVAL_ORIG ;;
					throttle)
						KUPDATED_INTERVAL_NEW=`expr 100 \* \
						                       $ACPI_THROTTLED_KUPDATED_INTERVAL \
						                       2>/dev/null` ;;
					*)
						KUPDATED_INTERVAL_NEW="$VAL" ;;
				esac
				if [ -n "$KUPDATED_INTERVAL_NEW" ] ; then
					KUPDATED_INTERVAL=$KUPDATED_INTERVAL_NEW
				fi
				;;
		esac
		shift
	done
	echo -n "Setting $BDFLUSH_PROC_FILE to $FILL_LEVEL $BLOCKS_TO_WRITE 0 0 "
	echo "$KUPDATED_INTERVAL $DATA_TIMEOUT $BDFLUSH_START $BDFLUSH_STOP 0"
	echo $FILL_LEVEL $BLOCKS_TO_WRITE 0 0 $KUPDATED_INTERVAL $DATA_TIMEOUT \
	     $BDFLUSH_START $BDFLUSH_STOP 0 > $BDFLUSH_PROC_FILE
}

###########################################################
# ACTIONS

error() {
   return 1
}

shutdown() {
	$SHUTDOWN -h now
}

reboot() {
	$SHUTDOWN -r now
}

kde_shutdown() {
	local DCOP_USER NOTHING
	read DCOP_USER NOTHING < <(/bin/ps axwo user,args |/bin/grep -i dcopserver)
	
	echo "Shutting down kde for user: $DCOP_USER"
	su $DCOP_USER -c "DISPLAY=:0 /opt/kde3/bin/dcop ksmserver ksmserver logout 0 2 2"
	[ $? != "0" ]  && echo "Could not stop window manager, please choose another action for this event!"
}

kde_term() {
	local DCOP_USER NOTHING
	read DCOP_USER NOTHING < <(/bin/ps axwo user,args |/bin/grep -i dcopserver)
	
	echo "Shutting down kde for user: $DCOP_USER"
	su $DCOP_USER -c "DISPLAY=:0 /opt/kde3/bin/dcop ksmserver ksmserver logout 0 0 0"
	[ $? != "0" ]  && echo "Could not stop window manager, please choose another action for this event!"
}

wmaker_term() {
	killall -TERM wmaker
}

ignore() {
	:
}

standby() {
	echo 1 > /proc/acpi/sleep
}

suspend() {
	case `uname -r` in
		2.4.*)
			echo "Suspension is not supported with 2.4.x kernel" >&2
			return 1
			;;
		*)
			echo 3 > /proc/acpi/sleep
			;;
	esac
}

hibernate() {
	case `uname -r` in
		2.4.*)
			echo "Hibernation is not supported with 2.4.x kernel" >&2
			return 1
			;;
		*)
			echo 4 > /proc/acpi/sleep
			;;
	esac
}

throttle() {
	local D TN TO PL TL
	echo > $PROC_STATE_FILE
	pushd /proc/acpi/processor &>/dev/null || return
    # We only process one of the two hyperthreaded processor otherwise we can not restore the propper state(s).
	for D in `set_cpu_and_throttle_arrrays`; do
		pushd $D &>/dev/null || continue
		if [ -w throttling ] ; then
			TN=`awk "/state count:/{print \\$3}" throttling`
			: $((TN--))
			TO=`awk "/active state:/{print \\$3}" throttling`
			TO=${TO#T}
#			echo $TN > throttling
		fi
		if [ -w limit ] ; then
			read PL TL < <(awk -F: "/user limit:/{print \$2\" \"\$3}" limit)
			PL=${PL#P}
			TL=${TL#T}
			echo $PL:$TN > limit
		fi
		echo $D ${PL:-0} ${TO:-0} ${PL:-0}:${TL:-0} >> $PROC_STATE_FILE
		echo "$D: throttling=$TN limit=$PL:$TN"
		popd &>/dev/null
	done
	popd &>/dev/null
	# send cpufreqd USR1 signal for low performance
	if [ -x $CPUFREQD_BIN ] ; then
		echo Set CPU clock to minimum frequency
		killproc -USR2 $CPUFREQD_BIN
	fi
	disk_sleep $ACPI_THROTTLED_DISK_TIMEOUT
	tweak_bdflush kupdate=throttle
}

dethrottle() {
	local D P T L
	pushd /proc/acpi/processor &>/dev/null || return
	touch $PROC_STATE_FILE
	while read D P T L; do
		test -n "$D" && pushd $D &>/dev/null || continue
		if [ -w limit ] ; then
			echo $L > limit
			echo $T > throttling
		fi
		echo "$D: throttling=$T limit=$L"
		popd &>/dev/null
	done < $PROC_STATE_FILE
	popd &>/dev/null
	# send cpufreqd HUP signal for automatic scaling
	if [ -x $CPUFREQD_BIN ] ; then
		echo Set CPU clock to automatic scaled frequency
		killproc -HUP $CPUFREQD_BIN
	fi
	test -n "$ACPI_THROTTLED_DISK_TIMEOUT" && disk_sleep 0
	tweak_bdflush kupdate=restore
}

lockx() {
	true
}

switch_vt() {
	chvt 1
	chvt 7
}

###########################################################
# DEBUGGING

case "$ACPI_DEBUG" in
  yes)
    # METHOD 1 - Logs errors and any ordinary output
    exec  > >(${LOGGER}-1)
    exec 2> >(${LOGGER}-2)
    ;;
  error)
    # METHOD 2 - Logs cmd line args, errors and any ordinary output
    ${LOGGER} "${*:- }"
    exec  > >(${LOGGER}-1)
    exec 2> >(${LOGGER}-2)
    ;;
  all)
    # METHOD 3 - Logs every command executed by acpid_proxy and anything else
    ${LOGGER} "${*:- }"
    exec  > >(${LOGGER}-1)
    exec 2> >(${LOGGER}-2)
    set -x
    ;;
esac

###########################################################
# Parse arguments

EVENT="$*"
EVENT_TYPE=`echo $1 | /bin/awk -F/ '{print toupper($1);}'`
EVENT_SUBTYPE=`echo $1 | /bin/awk -F/ '{print toupper($2);}'`
EVENT_DEVICE=$2
EVENT_ID=$3
EVENT_NR=$4

###########################################################
# Special treatment for some events

# The 'Button Sleep' events does always occur twice. Therefore we skip all
# events with odd event number.
if [ "$EVENT_TYPE" = BUTTON -a "$EVENT_SUBTYPE" = SLEEP ] ; then
	case $EVENT_NR in *1|*3|*5|*7|*9|*b|*d|*f) exit 0;; esac
fi

# LID event -> get lid state from /button/lid/LID
if [ "$EVENT_TYPE" = BUTTON -a "$EVENT_SUBTYPE" = LID ] ; then
	EVENT_EXT=`get_lid_state`
	EVENT="$EVENT $EVENT_EXT"
fi

if [ "$EVENT_TYPE" = AC_ADAPTER ] ; then
	EVENT_EXT=`/bin/awk '{gsub("-","_",$2); print toupper($2);}' \
	           /proc/acpi/ac_adapter/$EVENT_DEVICE/state`
	EVENT="$EVENT $EVENT_EXT"
fi

###########################################################
# Finally execute the actions

# echo "EVENT_TYPE=$EVENT_TYPE EVENT_SUBTYPE=$EVENT_SUBTYPE EVENT_DEVICE=$EVENT_DEVICE EVENT_ID=$EVENT_ID EVENT_NR=$EVENT_NR EVENT_EXT=$EVENT_EXT"

# The actions for an event are stored in /etc/sysconfig/powermanagement in
# variables named "ACPI_${EVENT_TYPE}_${EVENT_SUBTYPE}_${EVENT_EXT}". And the
# actions are just the names of functions. So we just need to execute every
# function out of "ACPI_${EVENT_TYPE}_${EVENT_SUBTYPE}_${EVENT_EXT}".
# For some events we already determined an event extension (like open/close for
# the lid or on/off-line for the ac adapter)
eval ACTIONS=\$ACPI_${EVENT_TYPE}${EVENT_SUBTYPE:+_$EVENT_SUBTYPE}${EVENT_EXT:+_$EVENT_EXT}
for ACTION in $ACTIONS; do
	if [ "`type -t $ACTION`" = function ] ; then
		echo "execute action $ACTION for event $EVENT"
		if ! $ACTION; then
			echo "Action $ACTION for event $EVENT returned an error" >&2
		fi		
	else
		echo "Action $ACTION for event $EVENT not found" >&2
	fi
done
if [ -z "$ACTIONS" ] ; then
	echo "No action specified for event $EVENT"
fi
	
exit 0
