Meta Keys

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

Setup

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

Connectivity Keys (Fn+F2)

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 …

WiFi Toggle

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

Bluetooth Toggle

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

Screen Function Keys (Fn+F5/F6/F7/F8)

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.

Sound Function Keys (Fn+F10/F11/F12)

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.

Lid Toggle

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:

  1. Ascertain the current state of the lid.
  2. Figure out if any external displays are connected.
  3. If lid is closed …
    1. Save the current display state/configuration to /tmp/.screen_state.
    2. If an external display is connected …
      1. Toggle the screen configuration to external.
    3. If nothing is connected …
      1. Go to sleep.
  4. If lid is open …
    1. Try to retrieve a saved state from /tmp/.screen_state.
    2. If this fails …
      1. … and a display is connected, set the state to clone.
      2. … and nothing is connected, set the state to internal.
    3. Toggle the screen configuration to the selected state.

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. FIXME! 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

FIXME! add lid_toggle to window manager startup!

Troubleshooting

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.

1) These scripts are located in /usr/local/src.
2) This directory gives you similar control of WiFi, Bluetooth, etc.
3) This value with a + or - appended will increase/decrease the current volume level.
4) Please note that the lid event's ID could be either 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).
 
setup/graphics/metakeys.txt · Last modified: 2009/06/22 03:37 by marco1475
 
Except where otherwise noted, content on this wiki is licensed under the following license:CC Attribution-Noncommercial-Share Alike 3.0 Unported
Recent changes RSS feed Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki