Custom hot key programming with acpid

Key In


A little research from the command line and a short script bends your keyboard to your will.

By Hal Pomeranz

Nikolai Sorokin, Fotolia

Linux support for laptops has improved by leaps and bounds in the last decade. At this point, if you buy a laptop from any of the major manufacturers and load a Linux distro with a reasonably recent kernel version, things pretty much "just work" - problems with proprietary display drivers aside.

What can sometimes not work, however, are some of the vendor-specific hot keys on the keyboard. For example, I do a lot of presentations, and I'd like the hot key that's supposed to switch between my laptop display and an external monitor (Fn+F7 on my ThinkPad) to work under Linux. In this article, I describe my solution for this problem and offer some pointers for customizing hot key events.

Getting to Know acpid

Most Linux systems support ACPI (Advanced Configuration and Power Interface [1]), a popular standard for device configuration and power management. The ACPI standard specifies a structure for defining and customizing hardware events. In Linux, the acpid daemon [2] listens for ACPI events and maps each event to an action. Hot key events are typically handled by acpid.

The trick to customizing your hot key configuration is getting acpid to divulge exactly which event numbers are occurring when you press a particular button. The easiest way to do this is to put acpid into debugging mode with the -d switch. To do this, you need to kill the current acpid process and restart it, as shown in Listing 1.

Listing 1: Kill acpid and Restart in Debug Mode
01 # ps -ef | grep acpid
02 ...
03 root     18847     1  0 11:20 ?        00:00:00 /usr/sbin/acpid -c /etc/acpi/events -s /var/run/acpid.socket
04 # kill 18847
05 # /usr/sbin/acpid -c /etc/acpi/events -s /var/run/acpid.socket -d

Note that I'm being careful to use the same command-line options that were used in the original acpid invocation; then I add the -d flag to capture the debugging output. At this point, quite a bit of output is directed to your terminal as acpid starts up. Once that output settles down, you can go ahead and hit the hot key you're interested in programming. When I hit Fn+F7 on my laptop, I get a report similar to the output shown in Listing 2.

The relevant bit here is the event identifier, "ibm/hotkey HKEY 00000080 00001007", because you will need this string when you are programming custom events into acpid. The acpid daemon stores the different event actions in configuration files that go into the /etc/acpi/events directory. To see whether a configuration file related to your Fn+F7 events already exists, enter the following code:

# grep -l "ibm/hotkey HKEY 00000080 00001007" /etc/acpi/events/* /etc/acpi/events/ibm-videobtn
# cat /etc/acpi/events/ibm-videobtn
# /etc/acpi/events/ibm-videobtn
# This is called when the user presses the video button. It is
# currently a placeholder.

event=ibm/hotkey HKEY 00000080 00001007
action=/bin/true

As you can see, a configuration file is related to this event, but the final line, action=/bin/true, tells you that pressing this hot key does not switch the monitor. (Note that a close reading of the acpid debugging output In Listing 2 would lead you to the same conclusion.) To get this hot key working, you'll need a little script.

Listing 2: acpid Output for Function Keys
01 acpid: received event "ibm/hotkey HKEY 00000080 00001007"
02 acpid: rule from 7044[0:0] matched
03 acpid: notifying client 7044[0:0]
04 acpid: rule from 6812[109:118] matched
05 acpid: notifying client 6812[109:118]
06 acpid: rule from /etc/acpi/events/ibm-videobtn matched
07 acpid: executing action "/bin/true"
08 BEGIN HANDLER MESSAGES
09 END HANDLER MESSAGES
10 acpid: action exited with status 0
11 acpid: 3 total rules matched
12 acpid: completed event "ibm/hotkey HKEY 00000080 00001007"

Fun with xrandr

To create a script, you'll need a command-line method for controlling the internal and external displays. Happily, modern Linux distributions come with xrandr (short for "X Rotate AND Resize"), which has all the necessary functionality. For example, to discover the current display settings, enter xrandr -q (Listing 3).

Listing 3: Show Current Display Settings
01 $ xrandr -q
02 Screen 0: minimum 320 x 200, current 1920 x 1200, maximum 1920 x 1920
03 VGA connected 1920x1200+0+0 (normal left inverted right x axis y axis) 519mm x 324mm
04    1920x1200      60.0*+
05    1600x1200      60.0     60.0
06    1680x1050      60.0     60.0
07    ...
08 LVDS connected (normal left inverted right x axis y axis)
09    1024x768       50.0 +   60.0     60.0     40.0
10    800x600        60.3     56.2
11    640x480        60.0     59.9

In Listing 3, VGA is the external monitor and LVDS is the internal laptop video display. For each display, xrandr lists the various screen resolutions supported and marks the currently active screen(s) with an asterisk (*). In the example shown in Listing 3, the external monitor is running at the maximum resolution of 1920x1200, and the laptop display is currently shut off. Additionally, you can use xrandr to change your video settings (Listing 4).

The xrandr command has a lot of other tricks up its sleeve, including the ability to create a single virtual display out of multiple screens, the ability to rotate screens between portrait and landscape mode, and so on. However, the basic commands I have presented here are sufficient for the needs of this simple example.

Listing 4: Changing Video Settings with xrandr
01 # internal display at max resolution, external display off
02 $ xrandr --output LVDS --auto --output VGA -off
03
04 # internal/external displays showing same 1024x768 screen
05 $ xrandr --output LVDS --auto \
06          --output VGA --mode 1024x768 --same-as LVDS
07
08 # external display at max resolution and internal display off
09 $ xrandr --output LVDS --off --output VGA -auto

Getting Your Script On

When I press Fn+F7, I want to rotate through three possible video modes corresponding to the xrandr commands in Listing 4. Those modes are:

Although I could have added additional options, I figured these modes were the basics of what I needed when giving presentations. If I needed to, I could always just run xrandr from the command line for any additional options.

I freely admit I cribbed a lot of good ideas for this script from an article on the ThinkWiki site [3] (which is a hugely useful site, by the way, even if you are not using a ThinkPad). One of the most critical tricks in this particular situation is to remember that acpid is going to be running your script with root privileges, but the display is owned by the user on the console of the machine. For your script to use xrandr effectively, it needs to run all commands through su to get access to the display as the console user.

My sample Perl script is shown in Listing 5. The ThinkWiki article provides alternative Bash and Python scripts. The first block of code is devoted to figuring out which user is currently on the console. Then the script runs xrandr -q and parses out the configuration of the currently active displays.

Finally, a big if block figures out which state the system is currently in and chooses the appropriate xrandr command to move to the next screen mode.

Listing 5: Sample video-switch Script
01 #!/usr/bin/perl
02
03 use strict;
04
05 my $SU = '/bin/su';
06 my $WHO = '/usr/bin/who';
07 my $XRANDR = '/usr/bin/xrandr -d :0';
08
09 my($consoleuser, $sudcmd) = ();
10 open(WHO, "$WHO |") || die "'who' command failed: $@\n";
11 while (<WHO>) {
12     if (/\(:0\W/) {
13         ($consoleuser) = (split(/\s+/))[0];
14         last;
15     }
16 }
17 close(WHO);
18 die "Unable to determine who's on the console" unless (length($consoleuser));
19
20 my $cmd = "$XRANDR -q";
21 $cmd = "$SU $consoleuser -c '$cmd'" if ($consoleuser ne 'root');
22 open(QRY, "$cmd |") || die "'xrandr -q' failed: $@\n";
23
24 my($curr, %status) = ();
25 while (<QRY>) {
26     if (/ connected /) {
27         $curr = (split(' '))[0];
28     }
29     elsif (/\*/) {
30         $status{$curr} = (split(' '))[0];
31     }
32 }
33 close(QRY);
34
35 $cmd = "$XRANDR --output LVDS --auto --output VGA --off";     #failsafe
36 if ($status{'VGA'} && $status{'LVDS'}) {
37     $cmd = "$XRANDR --output LVDS --auto --output VGA --off";
38 }
39 elsif ($status{'LVDS'}) {
40     $cmd = "$XRANDR --output LVDS --off --output VGA --auto";
41 }
42 elsif ($status{'VGA'}) {
43     $cmd = "$XRANDR --output LVDS --auto --output VGA --mode 1024x768 --same-as LVDS";
44 }
45 $cmd = "$SU $consoleuser -c '$cmd'" if ($consoleuser ne 'root');
46
47 system($cmd);

Hooking the Script to acpid

Getting acpid to use your script is really just a matter of editing the /etc/acpi/event/ibm-videobtn file and replacing the action= line with a pointer to your script. On my system, the script is /usr/local/sbin/video-switch, so my ibm-videobtn file looks like this:

event=ibm/hotkey HKEY 00000080 00001007
action=/usr/local/sbin/video-switch

When you modify the files under /etc/acpi/events, you'll need to restart acpid to get it to pick up the changes.

Additionally, you'll want to get acpid out of debug mode, so hit Ctrl+C in the window in which acpid is currently running then enter /etc/init.d/acpid start as root to fire up the daemon normally. At this point, hitting Fn+F7 on the laptop should cycle it through the three display settings programmed in the script. Because this very simple hack works as advertised, I've gotten tons of "geek cred" at various Linux User Group meetings.

Summary

Programming your own hot key hacks is equally simple. First, run acpid in debug mode to figure out exactly which event is triggered by your key presses. Then check the /etc/acpi/event directory to see whether a configuration file related to this event already exists. If not, feel free to create one from scratch - you really only need the event= and action= lines for most simple events. From there, it's just a matter of scripting what you want to happen. In many cases, you might be able to find example scripts on the web to use as a starting point.

INFO
[1] Advanced Configuration and Power Interface: http://www.acpi.info/
[2] acpid: http://sourceforge.net/projects/acpid/
[3] ThinkWiki sample script: http://www.thinkwiki.org/wiki/Sample_Fn-F7_script
THE AUTHOR

Hal Pomeranz (hal@deer-run.com) is an independent consultant with more than 20 years of experience with Unix. He is a Faculty Fellow of the SANS Institute and the course author and primary instructor for their Linux/Unix Security certification track. Hal is also a recipient of the SAGE Outstanding Achievement award for his teaching and leadership in the field of Systems Administration.