This chapter describes how to configure ACPI event handling to support all of the ASUS netbook's special keys, which include all “Function+F<something>” keys, the special Zoom and Suspend buttons as well as Lid, AC/Battery, etc. events. The following is a list of ACPI key codes recognized by the netbook, which you should be able to see via acpi_listen after you have installed ACPI.
Lid: button/lid LID 00000080 Fn+F1: button/sleep SLPB 00000080 Fn+F2: hotkey ATKD 0000005d Fn+F2: hotkey ATKD 0000007e Fn+F2: hotkey ATKD 0000007d Fn+F8: hotkey ATKD 00000061 Fn+F8: hotkey ATKD 00000062 Fn+F8: hotkey ATKD 00000063 Fn+F10: hotkey ATKD 00000032 Fn+F11: hotkey ATKD 00000031 Fn+F12: hotkey ATKD 00000030 Fn+C: hotkey ATKD 0000008a Fn+V: hotkey ATKD 00000082 Fn+Space: hotkey ATKD 0000005c Zoom: hotkey ATKD 000000ba Suspend: hotkey ATKD 0000005c
First, set up an event that would tell acpid to call your custom script. While you could modify default.sh and default (the event), this would cause trouble with every update of acpid and force you to merge the new files with your modified once. Since acpid can react to any key code via custom events and call custom scripts the cleaner and simpler solution is to create a file asus in /etc/acpi/events and an asus.sh script in /etc/acpi.
For the event you have to decide what is supposed to trigger your asus.sh script. You can set up the event to trigger on anything from all events, through groups of events, down to single event codes. Of course you could have a separate script for every one of the events from the hotkey group listed above, but that would require one event file per event/action pair. You could also break it down into groups or create any other combination, as long as you keep one event/action per event file. To keep things manageable I decided to have only one custom event and one custom script that reacts to hotkey and button events. My asus event file looks something like this:
# /etc/acpi/events/asus # This is the ACPID default configuration, it takes all # events and passes them to /etc/acpi/default.sh for further # processing. # event keeps a regular expression matching the event. To get # power events only, just use something like "event=button[ /]power.*" # to catch it. # action keeps the command to be executed after an event occurs # In case of the power event above, your entry may look this way: #event=button[ /]power.* #action=/sbin/init 0 # Optionally you can specify the placeholder %e. It will pass # through the whole kernel event message to the program you've # specified. event=hotkey|button action=/etc/acpi/asus.sh %e
The asus.sh script gets all hotkey and button events passed to it and it therefore needs to have a switch-statement that determines which actions is to be taken for what event. The first choice is button (sleep button, lid open/closed) or hotkey (anything with Fn+*); the hotkey section then breaks the actions down based on the above-listed key codes. To keep things manageable and re-usable the asus.sh scripts consists only of the switch-statement and in turn calls other scripts that encapsulate the actual functionality1)
#!/bin/bash
# /etc/acpi/asus.sh
# ACPI script that takes care of ASUS-specific events
set $*
GROUP=${1%%/*}
ACTION=${1#*/}
DEVICE=$2
ID=$3
VALUE=$4
#echo "group: ${GROUP}" >> /etc/acpi/test.txt
#echo "action: ${ACTION}" >> /etc/acpi/test.txt
#echo "device: ${DEVICE}" >> /etc/acpi/test.txt
#echo "value: ${VALUE}" >> /etc/acpi/test.txt
case "${GROUP}" in
button)
case "${ACTION}" in
lid)
case "${ID}" in
open|close|00000080)
echo "Lid Open/Close" >> /etc/acpi/test.txt
;;
esac
;;
sleep)
echo "Sleep Button" >> /etc/acpi/test.txt
;;
esac
;;
hotkey)
case ${ID} in
0000005e|0000005d)
echo "WiFi On/Off" >> /etc/acpi/test.txt
;;
0000007e)
echo "BlueTooth Off" >> /etc/acpi/test.txt
;;
0000007d)
echo "BlueTooth On" >> /etc/acpi/test.txt
;;
00000061|00000062|00000063|000000a0|000000a1)
echo "Screen Toggle" >> /etc/acpi/test.txt
;;
00000032)
echo "Mute" >> /etc/acpi/test.txt
;;
00000031)
echo "Volume Down" >> /etc/acpi/test.txt
;;
00000030)
echo "Volume Up" >> /etc/acpi/test.txt
;;
0000008a)
echo "Screenshot (Whole Screen)" >> /etc/acpi/test.txt
;;
00000082)
echo "Screenshot (Focus)" >> /etc/acpi/test.txt
;;
0000005c)
echo "Suspend" >> /etc/acpi/test.txt
;;
000000ba)
echo "Zoom" >> /etc/acpi/test.txt
;;
esac
;;
esac
The function and F2 key combination triggers WiFi and Bluetooth subsystems on and off. Unfortunately things are not as simple as that. First, the key combination triggers three ACPI key events, not four as you would expect. Furthermore, some of the events arrive with latency, because they are sent after the hardware initialization is finished. Here is the general breakdown:
ATKD 0000005d # generic WiFi key, does not do anything to the hardware ATKD 0000007e # Bluetooth on ATKD 0000007d # Bluetooth off
As you can see, WiFi never get a “off” command, so hardware-wise the key combination just triggers the Bluetooth hardware on/off. This means that you need a WiFi toggle script that will keep track of its state so it can react properly. The Bluetooth script on the other hand does not need to do that. So assuming you configured the WiFi and Bluetooth systems properly, here are the toggle scripts …
The script needs two modes - explicit and implicit. With the explicit mode it does what you tell it to through a parameter (on/off), which is needed by ifplugd (as utilized here). The implicit mode is for the function keys, as you have only one ACPI event to trigger on - in this case the script implicitly knows what to do by figuring out whether the wlan0 interface is up or not. This is accomplished via querying and grep-ing ifconfig. Finally, to make the state changes visible through the netbook's LEDs the script modifies the /sys/devices/platform/asus-laptop/wlan file.
#!/bin/bash
# /usr/local/src/wifi_toggle.sh
# Toggle WiFi on/off
# Override commands
DO=${1}
# Get the WiFi state, or override it if necessary
if [[ "${DO}" = "off" ]]; then
STATE="wlan0"
elif [[ "${DO}" = "on" ]]; then
STATE=""
else
STATE=$( ifconfig | grep "wlan0" | sed "s/[ \t].*//g" )
fi
# Toggle WiFi
if [[ -z "${STATE}" ]]; then
# Is the ath9k kernel module loaded?
ATH9K=$( lsmod | grep "ath9k" )
if [[ -z "${ATH9K}" ]]; then
modprobe ath9k
fi
echo "1" >> /sys/devices/platform/asus-laptop/wlan
/etc/init.d/net.wlan0 --quiet start
else
echo "0" >> /sys/devices/platform/asus-laptop/wlan
/etc/init.d/net.wlan0 --quiet stop
# Remove the kernel modules
modprobe -r ath9k
modprobe -r mac80211
fi
The Bluetooth toggle script does not need to check the state since ACPI provides you with separate on/off events. When turning Bluetooth on you just need to modprobe the required modules and then call /etc/init.d/bluetooth start. Conversely when turning Bluetooth off you need to modprobe -r the modules (and all their dependencies that got started automatically) and call /etc/init.d/bluetooth stop. Luckily the event for turning Bluetooth off comes after the hardware has been disabled and thus at the time of calling the btusb and bluetooth modules are no longer in use. (Otherwise you would get errors when removing the kernel modules.)
#!/bin/bash
# /usr/local/src/bluetooth_toggle.sh
# Script for toggling the Bluetooth sub-system on/off.
# What should we do?
DO=${1}
if [[ "${DO}" = "on" ]]; then
modprobe btusb
modprobe rfcomm
/etc/init.d/bluetooth start
else
/etc/init.d/bluetooth stop
modprobe -r rfcomm
modprobe -r l2cap
modprobe -r btusb
modprobe -r bluetooth
fi
The screen brightness keys (Fn+F5/F6) work automatically as long as the ambient light sensor is on. The ambient light sensor is located next to the camera and can be turned on or off through /sys/devices/platform/asus-laptop/ls_switch2). However, you cannot edit the file directly - you have to echo in the state (0 or 1) you want:
echo "0" > /sys/devices/platform/asus-laptop/ls_switch # to turn the light sensor off
The Fn+F7 key turns off the internal monitor and is BIOS-based as well, i.e. you do not have any control over it. The screen toggle key (Fn+F8) however, needs some work, especially because it needs to work for both GPUs. Detailed instructions on how to get this feature to work can be found here.
Once you have installed the necessary sound software you can enable the sound control keys. The ALSA mixer has a command-line interface called amixer that you can use to control the volume and other functions of the sound system. The basic structure of its call is amixer -c <number of sound card> sset <channel> <parameters>. In your case you probably want to control the first (i.e. 0) sound card, on the “master” channel (Master,0). Parameters will be mute or unmute (pretty self-explanatory) or a discreet or percentile value setting the volume level3).
You can enable F11 (volume down) and F12 (volume up) by simply calling amixer from the /etc/acpi/asus.sh script file:
00000031)
/usr/bin/amixer -q -c 0 sset Master,0 5%-
;;
00000030)
/usr/bin/amixer -q -c 0 sset Master,0 5%+
;;
Mute, however, needs to ascertain the state of the Master,0 channel and check whether it is muted (off) or not (on), otherwise it will not know what to do. To get the state of a channel you can use the sget command and it returns something like this:
amixer -c 0 sget Master,0 Simple mixer control 'Master',0 Capabilities: pvolume, pvolume-joined pswitch pswitch-joined Playback channels: Mono Limits: Playback 0 - 64 Mono: Playback 64 [100%] [0.00dB] [on]
The interesting part is the last [on] which changes to [off] once the Master channel is muted. The script for toggling mute on and off needs to therefore grep for one of these values and based on whether it finds it or not, do the opposite:
#!/bin/bash
# /usr/local/src/mute_toggle.sh
# Toggles mute on/off.
MUTE=$( /usr/bin/amixer -c 0 sget Master,0 | grep "\[off\]" | sed "s/.*\[/[/g" )
if [[ -z "${MUTE}" ]]; then
/usr/bin/amixer -q -c 0 sset Master,0 mute
else
/usr/bin/amixer -q -c 0 sset Master,0 unmute
fi
Finally you can add the mute toggle script to /etc/acpi/asus.sh and all should work.
The lid_toggle.sh script's goal is to react to the lid ACPI events.4) Obviously there are two possible states: open and closed. The script does the following:
/tmp/.screen_state.external./tmp/.screen_state.clone.internal.
To achieve this the script needs to set up xrandr (because it is querying if an external monitor is connected) and heavily depends on the external resolution, since it is the only way to query the nVidia configuration.
! add the sleep/hibernate code!
#!/bin/bash
# /usr/local/src/lid_toggle.sh
# Handles screen toggling based on ACPI's lid state
# Set up xrandr
source /usr/local/src/xrandr_setup.sh
#Get the lid and monitor states
LID_STATE=$( cat /proc/acpi/button/lid/LID/state | grep "open" | sed "s/.*://g" | sed "s/^[ \t]*//" )
VIDEO=$( lspci | grep "-c nVidia" )
if [[ "${VIDEO}" == 1 ]]; then
CONNECTED=$( xrandr | grep "1920x1200[ \t] ")
else
CONNECTED=$( xrandr | grep "^VGA" | grep "con" | sed "s/.*connected //" | sed "s/[+(].*//g")
fi
if [[ -z ${CONNECTED} ]]; then
CONNECTED=false
else
CONNECTED=true
fi
if [[ -z ${LID_STATE} ]]; then
# Closed lid
# Remember the current state
/usr/local/src/screen_toggle.sh state >> /tmp/.screen_state
# Switch to external monitor if one is connected
if [[ ${CONNECTED} ]]; then
/usr/local/src/screen_toggle.sh external
else
# Go to sleep
:
fi
else
# Open lid
# Retrieve the saved state, if any
if [[ -f /tmp/.screen_state ]]; then
STATE=$( cat /tmp/.screen_state )
elif [[ ${CONNECTED} ]]; then
STATE="clone"
else
STATE="internal"
fi
# Restore the state
/usr/local/src/screen_toggle.sh ${STATE}
# Remove the saved state
rm -f /tmp/.screen_state
fi
! add lid_toggle to window manager startup!
Unfortunately, ACPI executes the scripts non-interactively, meaning there is no output console and therefore none of its settings (like $USER, etc.). All STD::OUT output therefore, in order to be visible, needs to be redirected to a file via the » command. Also make sure you redirect STD::ERR to a file too (this tells you how it is done) otherwise you will not see any of the errors you will be getting.
/usr/local/src.open/close or 00000080 depending on whether you have selected Deprecated /proc/acpi/files and directories and /proc/acpi/event support in the kernel (00000080) or not (open/close).