Note: HAL is deprecated. This task now falls to ACPID, which handles it like this.

Power Buttons Under HAL

I wanted to re-purpose my power button in GNU/Linux. This is how I did it.

Understanding normal delivery of the power button event

From about 2006 through 2010, HAL managed the routing of ACPI hardware events. HAL decoupled system events (like pressing the power button) from actions (like calling /sbin/shutdown -h). Here's how it worked:

The kernel delivers the fact that the power button has been pressed to userland in at least two ways. HAL caught both of them. The first path is that the kernel writes to /proc/acpi/event. No history is available here -- reads of this file block until an event happens. So, to receive these events, a daemon has to hold this file open. hald-addon-acpi did this:

# lsof -n | grep /proc/acpi/event
hald-addo 12735  haldaemon    4r      REG        0,3        0 4026531935 /proc/acpi/event
# ps -opid,command -p 12735
  PID COMMAND
12735 hald-addon-acpi: listening on acpi kernel interface /proc/acpi/event

The second way power button events are delivered is via the /dev/input/event* devices. HAL's configuration for the power button is visible in the output of hal-device:

...
58: udi = '/org/freedesktop/Hal/devices/computer_logicaldev_input'
  linux.sysfs_path = '/sys/devices/LNXSYSTM:00/LNXPWRBN:00/input/input1/event1'  (string)
  info.parent = '/org/freedesktop/Hal/devices/computer'  (string)
  info.subsystem = 'input'  (string)
  info.product = 'Power Button'  (string)
  info.udi = '/org/freedesktop/Hal/devices/computer_logicaldev_input'  (string)
  linux.device_file = '/dev/input/event1'  (string)
  info.category = 'input'  (string)
  info.capabilities = { 'input', 'button' } (string list)
  input.device = '/dev/input/event1'  (string)
  input.product = 'Power Button'  (string)
  button.has_state = false  (bool)
  button.type = 'power'  (string)
  linux.subsystem = 'input'  (string)
  linux.hotplug_type = 2  (0x2)  (int)
  info.addons.singleton = { 'hald-addon-input' } (string list)
...

So the power button on this machine is /dev/input/event1. HAL has a daemon listening on this device also:

# lsof -n | grep /dev/input/event1
hald-addo 12720       root    4r      CHR      13,65      0t0       5347 /dev/input/event1
# ps -opid,command -p 12720
  PID COMMAND
12720 hald-addon-input: Listening on /dev/input/event1

It is this second input that generates a message on the System DBUS announcing the event. You can use dbus-monitor --system to see these messages:

signal sender=:1.0 -> dest=(null destination) path=/org/freedesktop/Hal/devices/computer_logicaldev_input;
                                              interface=org.freedesktop.Hal.Device; member=Condition
   string "ButtonPressed"
   string "power"

At this point, HAL thinks it's done. It has intercepted the system event and turned it into a DBUS message. HAL has not called /sbin/shutdown -h, and it won't. The decision of whether or not to do this falls to some listener on the System DBUS.

If nothing is listening, no action will result. On headless systems that use HAL but do not host a Gnome, KDE, etc. session, the power button is likely to do nothing at all. (And here I thought I would have to fight with HAL to override some default action. Haha.)

Normally, in Gnome, KDE, etc., the HAL client would receive the power button message, possibly confirm shutdown with the user in the GUI, and then call the org.freedesktop.Hal.Device.SystemPowerManagement Shutdown() method via DBUS:

/usr/share/hal/fdi/policy/10osvendor/10-power-mgmt-policy.fdi provides a Shutdown method:

<deviceinfo version="0.2">
...
  <device>
    <match key="info.udi" string="/org/freedesktop/Hal/devices/computer">
      <append key="info.interfaces" type="strlist">org.freedesktop.Hal.Device.SystemPowerManagement</append>
...
      <append key="org.freedesktop.Hal.Device.SystemPowerManagement.method_names" type="strlist">Shutdown</append>
      <append key="org.freedesktop.Hal.Device.SystemPowerManagement.method_signatures" type="strlist"></append>
      <append key="org.freedesktop.Hal.Device.SystemPowerManagement.method_argnames" type="strlist"></append>
      <append key="org.freedesktop.Hal.Device.SystemPowerManagement.method_execpaths" type="strlist">hal-system-power-shutdown</append>

This policy is visible in the output of hal-device as part the computer device:

8: udi = '/org/freedesktop/Hal/devices/computer'
  power_management.quirk.vga_mode_3 = true  (bool)
  info.addons = { 'hald-addon-cpufreq', 'hald-addon-acpi' } (string list)
  org.freedesktop.Hal.Device.SystemPowerManagement.method_names =
      { 'Suspend', 'SuspendHybrid', 'Hibernate', 'Shutdown', 'Reboot', 'SetPowerSave' } (string list)
  org.freedesktop.Hal.Device.SystemPowerManagement.method_signatures =
      { 'i', 'i', '', '','', 'b' } (string list)
  org.freedesktop.Hal.Device.SystemPowerManagement.method_argnames =
      { 'num_seconds_to_sleep', 'num_seconds_to_sleep', '', '', '', 'enable_power_save' } (string list)
  org.freedesktop.Hal.Device.SystemPowerManagement.method_execpaths =
      { 'hal-system-power-suspend', 'hal-system-power-suspend-hybrid', 'hal-system-power-hibernate',
        'hal-system-power-shutdown', 'hal-system-power-reboot', 'hal-system-power-set-power-save' } (string list)

hal-system-power-shutdown is a shell script in /usr/lib/hal/scripts/ that checks permissions and invokes hal_exec_backend:

#!/bin/sh

. hal-functions

if [ "$CK_NUM_SESSIONS" -gt "1" ] ; then
    hal_check_priv org.freedesktop.hal.power-management.shutdown-multiple-sessions
else
    hal_check_priv org.freedesktop.hal.power-management.shutdown
fi

hal_exec_backend

hal_exec_backend is defined in /usr/lib/hal/scripts/hal-functions. It sticks 'linux' in a few places and calls /usr/lib/hal/scripts/linux/hal-system-power-shutdown-linux:

hal_exec_backend() {
    local PROGRAM
    PROGRAM=$(basename $0)
    if [ -n "$HALD_UNAME_S" -a -x ./$HALD_UNAME_S/$PROGRAM-$HALD_UNAME_S ]; then
        exec ./$HALD_UNAME_S/$PROGRAM-$HALD_UNAME_S $@
    else
        echo "org.freedesktop.Hal.Device.UnknownError" >&2
        echo "No back-end for your operating system" >&2
        exit 1
    fi
}

/usr/lib/hal/scripts/linux/hal-system-power-shutdown-linux finally does the actual /sbin/shutdown -h. Whew!

#!/bin/sh

unsupported() {
        echo "org.freedesktop.Hal.Device.SystemPowerManagement.NotSupported" >&2
        echo "No shutdown command found" >&2
        exit 1
}

#Try for common tools
if [ -x "/sbin/shutdown" ] ; then
        /sbin/shutdown -h now
        exit $?
elif [ -x "/usr/sbin/shutdown" ] ; then
        /usr/sbin/shutdown -h now
        exit $?
else
        unsupported
fi

Performing an action on power button press

So, to receive that power button event, we have to listen on the System DBUS. This means connecting to /var/run/dbus/system_bus_socket and speaking the DBUS protocol. The path of least resistance from here is to pull in a DBUS library. Python's DBUS binding is relatively straightforward. Here's a simple script to listen for power button events and perform some action on receiving them (here, as an example, printing a message and launching a process).

#!/usr/bin/python

import dbus
from dbus.mainloop.glib import DBusGMainLoop
import gobject
import subprocess

def action(*args):
  if len(args) == 2 and args[0] == "ButtonPressed" and args[1] == "power":
    # The action to perform when the power button is pressed
    print "Power button pressed!"
    subprocess.call(["beep", "-f104", "-l40", "-r2"]);

# Initialize the event loop
DBusGMainLoop(set_as_default=True)

# Connect to the System DBUS
system_bus = dbus.SystemBus()

# Declare an interest in DBUS signals
system_bus.add_signal_receiver(action)

# Wait for events
gobject.MainLoop().run()

Preventing desktop environment default actions

If you are running a Gnome, KDE, etc. session, you may need to prevent its DBUS listener from shutting down the machine. In Gnome, this configuration is in System :: Preferences :: Power Management :: General tab :: Actions :: When the power button is pressed. If you need to change this setting to 'Do Nothing' programmatically, gconftool is your friend:

gconftool-2 -s '/apps/gnome-power-manager/buttons/power' --type string nothing