A Thinkpad X61 Hotkeys Solution on Debian Linux

On my old Thinkpad X31, there is a nice little program called tpb that makes all Thinkpad hotkeys work on Linux. However, that project hasn't been updated for two years, and tpb does not work with the newer Thinkpad models. On my new X61, notably, the sound volume controls are broken: volume UP and volume Down keys produce the same effect - bring the volume to the half level. It seems that piece of hardware called nvram, on which tpb relies, now produces different values than older models. So, I have to ditch tpb. After some trial and errors, I worked out a mixed solution that made all keys work as expected.

What's needed

* Newer kernel. At least 2.6.16 or above (not sure exact version number).

* acpid. Newer laptops all support ACPI, so acpid should be used. I believe all linux distribution has it.

* thinkpad_acpi. This kernel module deals with Thinkpad hardware and generate ACPI events, such as temperature change, function key press, docking, etc. This module is included in the mainline kernel since 2.6.10. The version included in kernel 2.6.22 is thinkpad_acpi 0.14. The latest version is 0.18. I find 0.14 works fine. Maybe 0.18 is better, I don't know. If this module is loaded, cat /proc/acpi/ibm/driver should show its version number. If not, load the module with modprobe thinkpad_acpi. If you want it autoloaded on boot, put a line thinkpad_acpi in /etc/modules. If your kernel doesn't has this module (unlikely), you will have to download the source and build the module yourself.

* powersaved This daemon is what actually handles ACPI events. In the past, people put ACPI events handling scripts in /etc/acpi directory. Now this is not recommended. So you should not install similar packages such as acpi-support, hibernate scripts, etc. Instead, user space program such as powersaved should handle ACPI events. Therefore, all your ACPI customizations should be done under either /usr/lib/powersave/scripts or /etc/powersave

* uswsusp This user space software suspend package is called by powersaved to actually do sleeping (i.e. saving state in RAM, so sleep and resume are quick) and hibernation (i.e. save state on disk, so it lasts).

All these are already packaged in Debian. Just use apt-get to install them.

What's worked: without configuration

Without any software, sound Mute key and Thinklight key always work, they are hardware controlled I think.

With the above package installed, "Fn+F12 hibernate" worked without any problem. "Fn+F4 sleep" may or may not work. Please follow the suggestions in this s2ram documentation, and test the command options in order, until sleep works. My X61 worked with s2ram -f -a 1, so I stopped testing other options. Once found a successful combination, edit /usr/lib/powersave/sleep, so that these two options reads

SUSPEND2RAM_FORCE="yes"
SUSPEND2RAM_ACPI_SLEEP="1" #use your successful -a number here, mine is 1

"Fn+F5 toggle bluetooth" seems to work, as I can turn the bluetooth LED on and off with it and get corresponding KPowersave notification (I am on KDE).

I do not have a dock, so I can't test "Fn+F9 docking".

"Fn+F2 lock screen", "Fn+F7 toggle display", "Fn+Home brightness up", "Fn+End brightness down", "Fn+space zoom" , and "ThinkVantage", did not work out of box. But ACPI sees them, so I configured powersaved to make them work. See below.

"Volume Up/Down", "Fn+up stop", "Fn+down play", "Fn+left rewind", "Fn+right forward", "Page Left/Right", "Windows", and "Menu" did not work out of box, but X sees them, so I configured Xmodmap and KDE shortcuts to make them work. See below.

There's a battery icon on "Fn+F3", pressing it turned screen blank, but the backlight was still on, so it's useless. I used powersaved to make it a switch to go into power save mode - low LCD brightness and low power level. See below.

From the icon on it, "Fn+F8" seems to be a touchpad/touchpoint switch, and pressing it had no effect, since X61 doesn't have a touchpad. I made it a switch to presentation mode - no screensaver, no auto sleep, etc. See below.

Configure ACPI hotkeys with powersaved

Most Thinkpad function keys generate ACPI events. I use powersaved to hand ACP events. First I tell powersavd to use my own ACPI events handler by editing /etc/powersave/events file, and change the line EVENT_OTHER="ignore" to EVENT_OTHER="thinkpad_acpi_events".

Now I create a file /usr/lib/powersaved/scripts/thinkpad_acpi_events, which looks like this:


#!/bin/bash
###########################################################################
#                                                                         #
#                         Powersave Daemon                                #
#                                                                         #
#          Copyright (C) 2005,2006 SUSE Linux Products GmbH               #
#                                                                         #
# Author(s): Based on code by Stefan Seyfried                             #
#            Hotkey support by Alex Solovey, solovey@us.ibm.com           #
#            Enhancements (docking station support) by Holger Macht       #
#                                                                         #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the   #
# Free Software Foundation; either version 2 of the License, or (at you   #
# option) any later version.                                              #
#                                                                         #
# This program is distributed in the hope that it will be useful, but     #
# WITHOUT ANY WARRANTY; without even the implied warranty of              #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU       #
# General Public License for more details.                                #
#                                                                         #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., #
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA                  #
#                                                                         #
###########################################################################
#
# thinkpad_acpi_events - process ThinkPad specific ACPI events generated by
# ibm_acpi driver and log them to syslog
#
# Configuration changes required for the script to work with powersave package:
# 1) Set EVENT_OTHER="thinkpad_acpi_events" in /etc/sysconfig/powersave/events
# 2) Place this script into /usr/lib/powersave/scripts directory
#
#########################################################################
#
# Customized by Huahai Yang to support Thinkpad X61 ACPI keys on KDE.
# Added on screen visual feedback for key press, the following keys are
# supported on top of existing powersaved supported keys:
#
# - Fn+F1 to start wireless connection
# - Fn+F2 to lock desktop
# - Fn+F3, F6, F9 to switch among Powersave, Performance and Presentation
#   mode
# - Fn+F7 to turn on/off external display
# - Fn+F8 to toggle clone/xinerama dual head display mode
# - Fn+F11 to start LAN connection
# - Fn+Home, Fn+End to increase, decrease LCD brightness
# - Fn+Space to take screenshot
# - ThinkVantage to open konsole
#
#########################################################################
PATH=/bin:/usr/bin:/usr/X11R6/bin:/sbin:/usr/sbin  # be paranoid, we're running as root.

# First, we pull in the helper functions.
. ${0%/*}/helper_functions # `dirname $0`/helper_functions
# get_x_user comes from here...
. ${0%/*}/x_helper_functions # `dirname $0`/x_helper_functions
export PATH

ME=${0##*/} # basename $0

# argument $4 is set to $EV_ID in helper_functions which is included above
if [ -z "$EV_ID" ]; then
    DEBUG "$ME 'Sorry, not enough arguments: $4 is empty.'" WARN
    $SCRIPT_RETURN $EV_ID 1 "$ME finished unsuccessful."
    exit 1
fi

# this script run as root, so we need to pretend we are the normal user, or
# X functionalities won't work.
run_on_xserver() {
    get_x_user
    DEBUG "User $X_USER display $DISP $1 " INFO
    su - $X_USER -c "DISPLAY=$DISP $1"
}

HOTKEY=$3

TYPE=$1

DEBUG "Custom event script for ThinkPad ibm_acpi driver" INFO

# we discard $2 which is the name of the current scheme.
set $HOTKEY  # powersaved gives us "other '<current_scheme_name>' '<content of /proc/acpi/event>'"
        # so we must split $3 to get the contents of /proc/acpi/event.
EVENT=$1   # "ibm/hotkey"
ACPI=$2    # "HOTK"
WHAT=$3    # "00000080"
SERIAL=$4  # "0000100c" Fn+F12

# it is easier to deal with numerical values (for me :-)
declare -i VAL
VAL=0x$WHAT # hex -> decimal
declare -i SER
SER=0x$SERIAL # hex -> decimal

# on screen display, it's always good to have visual feedback
OSD="DISPLAY=:0.0 osd_cat -p bottom -o 80 -A center -c green -l 1 -f -*-lucidatypewriter-*-r-*-*-*-240-*-*-*-*-*-*"

# configration file of kxdocker, needed to change it so kxdocker is started at correct position when display mode
# changed between clone and xinemera mode
KXDOCKER_CONF="/home/huahaiy/.kde/share/apps/kxdocker/kxdocker_conf.xml"

if [ "$EVENT" = "ibm/hotkey" ]; then
  ACTION="log event"
  if [ "$VAL" -eq 128 ]; then
    case $SER in
      4097)   HOTKEY="Fn+F1" 
        ACTION="make wireless connection"
        /home/huahaiy/bin/wireless.sh &
        ;;
      4098)   HOTKEY="Fn+F2" 
        ACTION="lock screen"
        #run_on_xserver "dcop kdesktop KScreensaverIface lock"
        run_on_xserver "xscreensaver-command -lock"
        ;;
      4099)   HOTKEY="Fn+F3"
        #if [ -x /opt/thinkpad/pm/onscreen_pm.sh ] ; then
        #    run_on_xserver "/opt/thinkpad/pm/onscreen_pm.sh start" &
        #    ACTION="start onscreen_pm applet"
        #else
        #    run_on_xserver "xset dpms force off" &
        #    ACTION="blank screen"
        #fi
        ACTION="enter powersave mode"
        powersave -e Powersave
        run_on_xserver "echo 'Power Save Mode' | $OSD" & 
        ;;
      4100)   HOTKEY="Fn+F4"
        ACTION="suspend-to-ram"
        run_on_xserver "xscreensaver-command -lock" &
        run_on_xserver "xset dpms force suspend"
        . $SYSCONF_DIR/sleep
        [ "$DISABLE_USER_SUSPEND2RAM" != "yes" ] && powersave "--$ACTION"
        ;;
      4101)   HOTKEY="Fn+F5" 
        if [ -x /opt/thinkpad/ac/onscreen_ac.sh ] ; then
          run_on_xserver "/opt/thinkpad/ac/onscreen_ac.sh start" &
          ACTION="start onscreen_ac applet"
        elif grep -q "status.*disabled" /proc/acpi/ibm/bluetooth ; then
          echo enable > /proc/acpi/ibm/bluetooth
          ACTION="enable blooetooth"
        else
          echo disable > /proc/acpi/ibm/bluetooth
          ACTION="disable blooetooth"
        fi
        ;;
      4102)   HOTKEY="Fn+F6" 
        powersave -e Performance
        run_on_xserver "echo 'Full Performance Mode' | $OSD" &
        ACTION="enter full performance mode"
        ;;
      4103)   HOTKEY="Fn+F7"
        ACTION="toggle external display"
        #echo video_switch > /proc/acpi/ibm/video
        if run_on_xserver "xrandr -q" | grep "VGA connected"; then 
          if run_on_xserver "xrandr -q" | grep "VGA connected [0-9]\+"; then
            run_on_xserver "echo 'Turn OFF External VGA Display' | $OSD" &
            run_on_xserver "xrandr --output VGA --off"
          else 
            run_on_xserver "echo 'Turn ON External VGA Display' | $OSD" &
            run_on_xserver "xrandr --output VGA --mode 1024x768"
          fi
        else
          # if VGA is unplugged, mark it off, and restart kxdocker with correct position
          run_on_xserver "echo 'External VGA Display is DISCONNECTED' | $OSD" &
          run_on_xserver "dcop kxdocker MainApplication-Interface quit"
          run_on_xserver "xrandr --output VGA --off"
          sed -i -e 's/LeftForce="-512"/LeftForce="0"/' $KXDOCKER_CONF
          run_on_xserver "kxdocker"
        fi
        ;;
      4104)   HOTKEY="Fn+F8" 
        #ACTION="expand screen"
        #echo expand_toggle > /proc/acpi/ibm/video
        ACTION="toggle monitor layout"
        run_on_xserver "dcop kxdocker MainApplication-Interface quit"
        if run_on_xserver "xrandr -q" | grep "VGA connected"; then 
          if run_on_xserver "xrandr -q" | grep "VGA connected 1024x768+1024"; then
            run_on_xserver "echo 'Switch to Clone Mode' | $OSD" & 
            run_on_xserver "xrandr --output VGA --pos 0x0 --fb 2048x768"
          else
            run_on_xserver "echo 'Switch to Xinerama Mode' | $OSD" &
            run_on_xserver "xrandr --output VGA --pos 1024x0 --fb 2048x768"
          fi
          sed -i -e 's/LeftForce="0"/LeftForce="-512"/' $KXDOCKER_CONF
        else
          run_on_xserver "echo 'External VGA Display is DISCONNECTED' | $OSD" &
          run_on_xserver "xrandr --output VGA --off"
          sed -i -e 's/LeftForce="-512"/LeftForce="0"/' $KXDOCKER_CONF
        fi
        run_on_xserver "kxdocker"
        ;;    
      4105)   HOTKEY="Fn+F9"
        #ACTION="undock"
        #echo undock > /proc/acpi/ibm/dock
        powersave -e Presentation
        run_on_xserver "echo 'Presentation Mode' | $OSD" &
        ACTION="enter presentation mode"
        ;;
      4106)   HOTKEY="Fn+F10" ;;
      4107)   HOTKEY="Fn+F11" 
        ACTION="make NIC connection"
        run_on_xserver "echo 'Connecting LAN...' | $OSD" &
        /home/huahaiy/bin/nic.sh &
        ;;
      4108)   HOTKEY="Fn+F12"
        ACTION="suspend-to-disk"
        run_on_xserver "xscreensaver-command -lock" &
        . $SYSCONF_DIR/sleep
        [ "$DISABLE_USER_SUSPEND2DISK" != "yes" ] && powersave "--$ACTION"
        ;;
      4109)   HOTKEY="Fn+Backspace" ;;
      4110)   HOTKEY="Fn+Insert" ;;
      4111)   HOTKEY="Fn+Delete" ;;
      4112)   HOTKEY="Fn+Home"
        powersave -ku
        ACTION="brighter display" 
        ;;
      4113)   HOTKEY="Fn+End"
        powersave -kd
        ACTION="dimmer display" 
        ;;
      4116)   HOTKEY="Fn+Zoom"
        ACTION="toggle screen resolution"
        #run_on_xserver "/home/huahaiy/bin/toggle-zoom.sh"
        run_on_xserver "ksnapshot"
        ;;
      4120)   HOTKEY="Thinkpad"
        run_on_xserver "konsole" &
        ACTION="launch konsole"
        ;;
      *)      HOTKEY="Unidentified" ;;
    esac
  else
    HOTKEY="Unidentified"
  fi  
  DEBUG "$HOTKEY hotkey: keycode $VAL serial $SER. action: $ACTION " INFO
elif [ "$EVENT" = "ibm/bay" ]; then
  case $VAL in
    1)  HOTKEY="Eject lever inserted" ;;
    3)  HOTKEY="Eject Request" ;;
    *)  HOTKEY="Unidentified" ;;
  esac
  DEBUG "$HOTKEY UltraBay event: $EVENT " INFO
elif [ "$EVENT" = "ibm/dock" ]; then
  case $VAL in
    0)  if [ $SER -eq 3 ]; then # X32 has strange dock code
      HOTKEY="Dock requested"
      ACTION="Docking"
      echo dock > /proc/acpi/ibm/dock 2>&1
      fi
      ;;
    1)  HOTKEY="Dock requested"
      ACTION="Docking"
      echo dock > /proc/acpi/ibm/dock 2>&1
      ;;
    3)  HOTKEY="Undock requested"
      ACTION="Undocking"
      echo undock > /proc/acpi/ibm/dock 2>&1
      ;;
    esac
    DEBUG "$HOTKEY: keycode $VAL serial $SER. action: $ACTION " INFO
else
    DEBUG "Unidentified event: $EVENT $ACPI $WHAT $SERIAL" INFO
fi

$SCRIPT_RETURN $EV_ID 0 "$ME finished"
EXIT 0

Notice that you need to install osd_cat package to get on-screen display, or you can comments out them. Also, I used xscreensaver to lock screen, because KDE screen saver and locker do not support ThinkFinger. I like the coolness of login and unlocking screen with a finger swipe :) Finally, I use simple scripts to make network connections. Here is nic.sh:


#!/bin/sh

# This script brings up wired network connection (plug in cable first!). With
# augument "stop", it turns off wireless network interface. Root privilige
# is required to run
#
# Author: Huahai Yang, Oct 15, 2007
#
if [ "$1" = "stop" ]; then
  ifdown eth0
else 
  ifup eth0

  # restart some services that depends on networking
  #/etc/init.d/samba restart
  /etc/init.d/spamassassin restart
fi

And here is wireless.sh


#!/bin/sh
# This script use Matthew Brett's wlan-ui.pl to select a wireless AP to
# connect to. With augument "stop", it turns off wireless network interface.
# Root privilige is required to run
#
# Author: Huahai Yang
#
if [ "$1" = "stop" ];
then
  ifconfig eth1 down
  /etc/init.d/ipw3945d stop
  modprobe -r ipw3945
else 
  /etc/init.d/ipw3945d start
  DISPLAY=:0.0 wlan-ui.pl

  # restart some services that depends on networking
  #/etc/init.d/samba restart
  /etc/init.d/spamassassin restart
fi

wlan-ui.pl is a GTK-2 based little GUI program that scans the available wireless access points, and allows you to connect to one of them. The only configuration needed is to put the name of your wireless card kernel module name in /etc/wlan-uirc, mine has a line:$MODULE='ipw3945'

Configure X hotkeys

ACPI does not deal with the rest of the hotkeys. But most of these can be seen by X server, so it's possible to map them to any functions you like.

First, need to get their keycodes. Use xev to do that and write down the keycodes.

Second, edit your ~/.Xmodmap, put these keycodes in, assign them reasonable XF86 names, and mine is here:

! Page Left, Right
keycode 234 = F19
keycode 233 = F20

! multimedia
keycode 162 = XF86AudioPlay
keycode 164 = XF86AudioStop
keycode 153 = XF86AudioNext
keycode 144 = XF86AudioPrev

! volume up, down
keycode 174 = XF86AudioLowerVolume
keycode 176 = XF86AudioRaiseVolume

! Windows keys
keycode 117 = XF86MenuKB
keycode 115 = XF86Start

! No Caps Lock
clear lock
! Caps Lock as Win key
add mod4 = Caps_Lock

Now try xmodmap ~/.Xmodmap, then go to KDE Control Center > Regional and Accessibility > Input Actions, and associate these keys with whatever actions you like.

To autoload your Xmodmap setting, create a file in ~/.kde/Autostart, and put the xmodmap command in. Mine has:

#!/bin/sh
# map keys
xmodmap ~/.Xmodmap

To properly handle volume up and down keys, I associated these keys with this script:


#!/bin/sh
#########################################################################
# Bring up or down sound volume with amixer, with on screen display
# the total volume range is 58.5dB, so we change 4.5dB each run, it takes
# 13 run to go from 0 to 58.5dB
#
# Usage: audio-volume.sh [up|down]
#
# Author: Huahai Yang
# Last upated: Oct. 16, 2007
########################################################################

if [ "$1" = "up" ]; then
  PERCENT=$(amixer set PCM 4.5dB+ | \
    sed -n -e 's/[^\[]*\[\([0-9]*\)\%\][^\%]*/\1/p' -e 'n')
elif [ "$1" = "down" ]; then
  PERCENT=$(amixer set PCM 4.5dB- | \
                sed -n -e 's/[^\[]*\[\([0-9]*\)\%\][^\%]*/\1/p' -e 'n')
else
  PERCENT=$(amixer get PCM | \
                sed -n -e 's/[^\[]*\[\([0-9]*\)\%\][^\%]*/\1/p' -e 'n')
fi 

osd_cat -b percentage -P $PERCENT -d 1 -p bottom -o 80 -i 100 -A left -c green -T "Sound Volume" &

That's all there is to it. Now all hotkeys work as intended, and with nice on-screen user feedback. Please let me know if I missed anything.

Updated: I've changed Fn+F8 key to toggle between Clone mode and Xinerama mode of dual head display, please see this post for details.

Comments

Post new comment

The content of this field is kept private and will not be shown publicly. If you have a Gravatar account associated with the e-mail address you provide, it will be used to display your avatar.
  • Allowed HTML tags: <a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd> <div> <h1><h2><h3><sub><sup><b><i><u><font><img>
  • You may post code using <code>...</code> (generic) or <?php ... ?> (highlighted PHP) tags.
  • Lines and paragraphs break automatically.

More information about formatting options

To prevent automated spam submissions leave this field empty.
Nice place