ACPI based battery state notifications

Right, so it’s Sunday evening and Quintin just made an entry about send-notify. This immediately made me think of non-root users and ACPI and how I had a script on my previous laptop that popped up notifications when battery state changed. And so inspiration set in and I decided it’s time to redo that hack and publish it.

Pre-requisites

You need a working setup from the non-root ACPI link above. This is very trivial to set up.

You need to have a working dbus-launch configured (it seems most X DMs does this correctly now, but it’s easy enough to do by hand, see below).

Ensuring DBUS-launch is running

Just do ‘echo ${$DBUS_SESSION_BUS_ADDRESS}‘ – if you get anything other than a blank line, you’re good to go. If not, find somewhere that your X11 login passes through and add this line of code:

eval `dbus-launch --sh-syntax --exit-with-session`

Or a more complete example from the dbus-launch man page:

## test for an existing bus daemon, just to be safe
if test -z "$DBUS_SESSION_BUS_ADDRESS" ; then
    ## if not found, launch a new one
    eval `dbus-launch --sh-syntax --exit-with-session`
fi

Enabling access to various X variables and components

From my experience there are basically three variables that X-interactive applications may need access to, so just dump their values into a file in /tmp/ with user-access only:

oumask=$(umask)
umask 0077
for acpivar in DISPLAY XAUTHORITY DBUS_SESSION_BUS_ADDRESS; do
    echo "export ${acpivar}='${!acpivar}'"
done > /tmp/${USER}-acpi-vars.sh
umask "${oumask}"

I’ve got that in my .xsession but you may need to put it elsewhere (check your window managers documentation, or failing everything else, .profile – just protect it with ‘if test -n "${DISPLAY}"; then ... fi‘.

This snippet of code will generate a file in /tmp/${USER}-acpi-vars.sh that contains something similar to this:

export DISPLAY=':0'
export XAUTHORITY='/home/jkroon/.Xauthority'
export DBUS_SESSION_BUS_ADDRESS='unix:abstract=/tmp/dbus-Q9rzwwZyes,guid=6cb00750d435dae773944a1e0000004d'

Strictly speaking other scripts that run from non-interactive locations such as cron could potentially also benefit from this so it’s really slightly misnamed.

The battery ACPI script

The rest is actually quite simple, the script seems longer than what I recall my previous one to be – but it also does slightly more than my previous script. Just store in ~/.acpi/battery and ensure it’s executable and you should start receiving popups as soon as ACPI signals battery events :). The script is mostly self-explanatory, except for the icky hack to load all entries from /proc/acpi/BAT0/{state,info} into bash variables. Basically it just replaces the colon followed by the spaces with a single : then uses read (along with IFS set to :) to read var val pairs and output appropriate shell script lines, which then gets eval’ed into existance.

The time remaining is a linear calculation. To the best of my knowledge this is somewhat inaccurate, if you know how any of the other methods works and would like to improve the calculation, please send me a patch.

#! /bin/bash

# Load variables (for dbus, and X11)
[ -r "/tmp/${USER}-acpi-vars.sh" ] && . /tmp/${USER}-acpi-vars.sh || echo "Failed to load acpi-vars"

eval "$(cat /proc/acpi/battery/BAT0/{info,state} \
  | sed -e 's/:[[:space:]]*/:/' \
  | while IFS=: read var val; do
    echo "${var// /_}=\"${val}\""
  done)"

remaining_percentage=$(( ${remaining_capacity%% *} * 100 / ${last_full_capacity%% *} ))

function calc_time()
{
  if [ "${2}" == "unknown" ]; then
    echo "Unknown";
  else
    rem_hours=$(( ${1} / ${2} ))
    rem_mins=$(( ${1} * 60 / ${2} % 60 ))
    rem_secs=$(( ${1} * 60 * 60 / ${2} % 60 ))

    printf "%d:%02u:%02u" ${rem_hours} ${rem_mins} ${rem_secs}
  fi
}

if [ "${charging_state}" = "charging" ]; then
  rem_time=$(calc_time "$(( ${last_full_capacity%% *} - ${remaining_capacity%% *} ))" "${present_rate%% *}")

  urgency=normal
  msg="Charged to ${remaining_percentage} %
Time to full: ${rem_time}"
elif [ "${charging_state}" = "discharging" ]; then
  rem_time=$(calc_time "${last_full_capacity%% *}" "${present_rate%% *}")

  msg="Remaining capacity: ${remaining_percentage} %
Estimated Time: ${rem_time}"

  [ "${capacity_state}" == "ok" ] && urgency=normal || urgency=critical
elif [ "${charging_state}" = "charged" ]; then
  msg="Battery fully charged"
  urgency=low
else
  msg="Unkown charging_state=${charging_state}."
  urgency=critical
fi

notify-send -u "${urgency}" -t 3000 "Battery ${charging_state}" "${msg}"

Comments are closed.