Sabtu, 24 Mei 2008

MANAGE YOUR LAPTOP'S HOTKEYS ON FEDORA


This document describes how to make your laptop's hotkeys usable on Fedora. I've tested this with Fedora 8 but it should also work with other Fedora versions - and maybe, with a little modification, also with other distributions.

This howto is a practical guide without any warranty - it doesn't cover the theoretical backgrounds. There are many ways to set up such a system - this is the way I chose.

1 Preparation

1.1 Needed Packages

We'll use xbindkeys to assign actions to the hotkeys that don't create an ACPI event.

  • xbindkeys

1.2 Xserver Configuration

To be able to toggle the touchpad on and off we have to adjust the xserver configuration (root privileges needed).

vi /etc/X11/xorg.conf

Search the section "InputDevice" with the identifier "Synaptics" and add the following line into this section.

Option "SHMConfig" "on"

The section should now look like this:

Section "InputDevice"
Identifier "Synaptics"
Driver "synaptics"
Option "Device" "/dev/input/mice"
Option "Protocol" "auto-dev"
Option "Emulate3Buttons" "yes"
Option "SHMConfig" "on"
EndSection

Afterwards save the changes and log out and back in again for the changes to take effect.

1.3 Hotkey Events

Let's find out what happens when we press the hotkeys.

1.3.1 ACPI

Most hotkeys like the "Fn-buttons" will create an ACPI event - so let's have a look at them. Run ...

acpi_listen

... and press a hotkey a few times. You'll see an output like this:

hotkey ATKD 00000031 0000001a
hotkey ATKD 00000031 0000001b
hotkey ATKD 00000031 0000001c

As you can see the first number (position3) is static and the second number dynamic. We need only the static number. Now press all your hotkeys one after another and write down the static numbers - you'll need them later.

1.3.2 Xev

Some hotkeys create a keycode instead of an ACPI event.

xev

A new window will pop up with a little square in it. Move the cursor into the square and press all hotkeys that haven't created an ACPI event in the step before. Don't move your mouse during this time - else you'll see lots of events caused by the movement of your mouse and not from the hotkeys. You'll see the keycode for each hotkey in the terminal window - it could look like this:

KeyPress event, serial 30, synthetic NO, window 0x3a00001,
root 0x13b, subw 0x3a00002, time 3282991713, (49,43), root:(1105,203),
state 0x0, keycode 162 (keysym 0x0, NoSymbol), same_screen YES,
XLookupString gives 0 bytes:
XmbLookupString gives 0 bytes:
XFilterEvent returns: False

KeyRelease event, serial 30, synthetic NO, window 0x3a00001,
root 0x13b, subw 0x3a00002, time 3282991781, (49,43), root:(1105,203),
state 0x0, keycode 162 (keysym 0x0, NoSymbol), same_screen YES,
XLookupString gives 0 bytes:
XFilterEvent returns: False

Write down the keycode numbers - you'll need them later.

2 Handle The ACPI Events

First we have to tell the ACPI event handler that our special script, that we'll create in a moment, shall be executed when a hotkey is pressed (root privileges needed).

vi /etc/acpi/events/hotkeys.conf

The content should look like this:

# Hotkey configuration

event=hotkey (ATKD|HOTK)*
action=/etc/acpi/actions/hotkeys.sh %e

Next we create a special script to work up the hotkey-events (root privileges needed).

vi /etc/acpi/events/hotkeys.sh

In the following I'll describe the single parts of the script. First we have to define the shell and the paths where the script has to search for the applications that you want to execute.

#!/bin/sh

PATH=/sbin:/bin:/usr/bin

The script is able to handle more than one session on multi-user systems. If you're the only user on the system enter your username into the corresponding field.

# Possible values:
# "0" = multi user system
# "your_username" = single user system
user="0"

This is the part that detects the user of the currently active session on multi user systems - nothing to adjust here.

# Detect the currently active user on multi user systems
checkuser()
{
if [ $user = "0" ]
then
uid_session=$(
ck-list-sessions | \
awk '
/^Session[0-9]+:$/ { uid = active = x11 = "" ; next }
{ gsub(/'\''/, "", $3) }
$1 == "uid" { uid = $3 }
$1 == "active" { active = $3 }
$1 == "x11-display" { x11 = $3 }
active == "TRUE" && x11 != "" {
print uid
exit
}'
)
user_data=(`cat /etc/passwd | grep $uid_session | tr ':' ' '`)
user=${user_data[0]}
fi
}

Now we reached the interesting part - here we'll assign actions to the hotkeys. Remember the hotkey events from step 1.3.1 - the third position of the ACPI events contains the static number that we need to distinguish between the events. Because we passed the whole ACPI event to this script we have to tell the script that it has to use the third position of the event - this is in $3.

# Assign actions to the hotkeys
case $3 in

The following hotkey configurations are examples from my laptop (ASUS G1S) - so you have to replace the event numbers so that they fit to your system. We'll start with the email client Evolution. When I press the email hotkey on my laptop the static event number is 00000050. When the button is pressed the function "checkuser" will be called to detect the user of the currently active session. Afterwards the script executes Evolution as the user of the currently active session and sends the output into the nirvana :) This is very important - otherwise the application would block the script until you close the application. So don't forget to add " &> /dev/null &" to commands that execute an application that runs until you close it and " &> /dev/null" to other applications that create an output.

# Start Evolution (email button)
00000050)
checkuser
su - $user -c "evolution --component=mail &> /dev/null &"
;;

The configuration for the webbrowser is almost equal to the one above.

# Start Firefox (browser button)
00000051)
checkuser
su - $user -c "firefox &> /dev/null &"
;;

Now we reached the touchpad configuration. Many laptops have an extra hotkey for the touchpad - we'll use the synclient to toggle our touchpad on and off when this hotkey is pressed (we activated the synclient in step 1.2). This is user independent so there's no need to call the "checkuser"-function.

# Toggle touchpad on|off (touchpad button)
0000006b)
tp_status=(`synclient -l | grep TouchpadOff`)

if [ ${tp_status[2]} = "1" ]
then synclient TouchpadOff=0
else synclient TouchpadOff=1
fi
;;

Next we have to configure the hotkeys for lowering and raising the volume. This is also user independent because there's only one soundcard to control (on most systems).

# Lower volume (Fn + F11)
00000031)
amixer sset Master Playback Volume 5%- &> /dev/null
;;

# Raise volume (Fn + F12)
00000030)
amixer sset Master Playback Volume 5%+ &> /dev/null
;;

We'll use a workaround to mute the volume because the amixer's real mute function causes a big problem - most players will crash when you use it. When the mute hotkey is pressed the script looks if the sound is activated at the moment and if it is, it writes the current state into a file before it sets the volume to 0%. When you press this hotkey again (and the volume is still at 0%) the script will get the last volume state out of the file and restore it.

# Toggle mute (0% - last state) (Fn + F10)
00000032)
snd_status=(`amixer sget Master Playback Volume | egrep "\[on|off\]$"`)
cur_vol=$(echo ${snd_status[4]} | tr '[|]|%' ' ')

if [ $cur_vol != "0" ]
then
cat /dev/null > /opt/.volume
echo $cur_vol > /opt/.volume
amixer sset Master Playback Volume 0% &> /dev/null
else
sav_vol=$(cat /opt/.volume)
if [ $sav_vol = "" ]
then amixer sset Master Playback Volume 50% &> /dev/null
else amixer sset Master Playback Volume $sav_vol% &> /dev/null
fi
fi
;;

In the last step we close the case clause and exit the script.

esac

exit 0

Now save the file and make it executeable.

chmod +x /etc/acpi/events/hotkeys.sh

For example the whole script on my system for better understanding.

#!/bin/sh

PATH=/sbin:/bin:/usr/bin

# Possible values:
# "0" = multi user system
# "your_username" = single user system
user="olli"

# Detect the currently active user on multi user systems
checkuser()
{
if [ $user = "0" ]
then
uid_session=$(
ck-list-sessions | \
awk '
/^Session[0-9]+:$/ { uid = active = x11 = "" ; next }
{ gsub(/'\''/, "", $3) }
$1 == "uid" { uid = $3 }
$1 == "active" { active = $3 }
$1 == "x11-display" { x11 = $3 }
active == "TRUE" && x11 != "" {
print uid
exit
}'
)
user_data=(`cat /etc/passwd | grep $uid_session | tr ':' ' '`)
user=${user_data[0]}
fi
}

# Assign actions to the hotkeys
case $3 in

# Start Evolution (email button)
00000050)
checkuser
su - $user -c "evolution --component=mail &> /dev/null &"
;;

# Start Firefox (browser button)
00000051)
checkuser
su - $user -c "firefox &> /dev/null &"
;;

# Toggle touchpad on|off (touchpad button)
0000006b)
tp_status=(`synclient -l | grep TouchpadOff`)

if [ ${tp_status[2]} = "1" ]
then
synclient TouchpadOff=0
echo 1 > /sys/class/leds/asus:touchpad/brightness
else
synclient TouchpadOff=1
echo 0 > /sys/class/leds/asus:touchpad/brightness
fi
;;

# Lower volume (Fn + F11)
00000031)
amixer sset Master Playback Volume 5%- &> /dev/null
;;

# Raise volume (Fn + F12)
00000030)
amixer sset Master Playback Volume 5%+ &> /dev/null
;;

# Toggle mute (0% - last state) (Fn + F10)
00000032)
snd_status=(`amixer sget Master Playback Volume | egrep "\[on|off\]$"`)
cur_vol=$(echo ${snd_status[4]} | tr '[|]|%' ' ')

if [ $cur_vol != "0" ]
then
cat /dev/null > /opt/.volume
echo $cur_vol > /opt/.volume
amixer sset Master Playback Volume 0% &> /dev/null
else
sav_vol=$(cat /opt/.volume)
if [ $sav_vol = "" ]
then amixer sset Master Playback Volume 50% &> /dev/null
else amixer sset Master Playback Volume $sav_vol% &> /dev/null
fi
fi
;;

esac

exit 0

One thing is still to do. There's a bug that will prevent the ACPI daemon to execute the commands in our script after you started/restarted your system. You have to restart the ACPI daemon everytime after you logged in into a GNOME session. I think you don't want to do that manually :) so I found a workaround (root privileges needed).

vi /etc/gdm/Init/Default

Add the following lines at the bottom of the file - before the exit.

# acpid workaround
/etc/init.d/acpid restart

The corresponding part of the file should look like this:

[...]

# acpid workaround
/etc/init.d/acpid restart

exit 0

Now the ACPI daemon will be restarted automatically after each login.

3 Handle The Xev Events

Maybe you found a few hotkeys that have not created an ACPI event in step 1.3.1 but a keycode event in step 1.3.2. Now it's time to configure them - first we have to create the needed configuration file (without root privileges!).

xbindkeys --defaults > $HOME/.xbindkeysrc

Now let's edit it.

vi ~/.xbindkeysrc

You'll see an example configuration and some notes within the file. The configuration is very easy - simply add a line with the command that shall be executed and below the keycode (c:%keycode%) that belongs to this action. For example the configuration on my laptop (a few multimedia hotkeys like start/pause, stop etc...).

# MM button actions

# MM play/pause
"/usr/bin/audacious -p"
c:162

# MM stop
"/usr/bin/audacious -s"
c:164

# MM rewind
"/usr/bin/audacious -r"
c:144

# MM forward
"/usr/bin/audacious -f"
c:153

Now save the changes and add xbindkeys (/usr/bin/xbindkeys) to the autostart programs (System - Preferences - Personal - Sessions). Please note that you have to log out and back in again for the changes to take effect.

Tidak ada komentar: