Monday, April 24, 2006

» Mapping unsupported keys with xmodmap

Nowadays, many keyboard have additional, so called "multimedia" keys (e.g. to increase/decrease volume, write an email, etc...). On notebooks, keys for volume up/down/mute are the rule rather than the exception. Those keys don't work out-of-the-box on Linux, or rather on X-Window (be it Xorg or XFree86). Let's see how to make something useful out of those keys.

The problem

Those keys are very well supported from a hardware point-of-view. Pressing them does trigger input events in the kernel, and they are even received by X-Window. But the problem is that you can only bind them to specific actions if they are mapped to a "keysym" (key symbol). Examples of key symbols: a, 8, EuroSign, Return, Escape, Home or F10. Key events are received as keycodes (numeric values) by X-Window, mapped internally to keysyms and then X-Window (or KDE, or GNOME, or whatever) reacts to those X events (KeyPress and KeyRelease + the keysym) accordingly. Those special keys on your keyboard don't work because although they send keycodes, they are not mapped to any keysym.

The solution

Here is the plan:
  • bind the currently unassigned keycodes to unused keysyms (e.g. F13, F14, ...)
  • use xbindkeys or khotkeys (KDE) to execute commands (scripts, DCOP, ...) when those keys are pressed
To bind keycodes to keysyms, we are going to use xmodmap.

xmodmap

xmodmap comes with X-Window, e.g. on SUSE Linux it's in the package xorg-x11. We're going to add keycode-to-keysym mappings in ~/.Xmodmap, which will be read by xmodmap on X server startup (actually, /etc/X11/xdm/Xsession and /etc/X11/xinit/xinitrc run xmodmap $HOME/.Xmodmap when that file exists). Note that you don't have a .Xmodmap file under your home directory by default (the system-wide /usr/X11R6/lib/X11/Xmodmap is used, that itself is a symlink to /etc/X11/Xmodmap). The syntax of ~/.Xmodmap for assigning keycodes to keysyms is as follows: keycode <keycode> = <keysym> Here, for example, the for the keysyms Return and Home: keycode 36 = Return keycode 97 = Home The format is very simple, but the most complicated part has to be done first: we have to find out the keycodes that are emitted by those special keys.

Finding the keycodes

To find out what keycodes are sent to X-Window by those special keys, we are going to use xev, that is part of the core X-Window package as well (xorg-x11 on SUSE Linux). xev is a handy little tool that opens a window and traces all the X events that are triggered when your mouse is above that window. It also tracks focus and mouse events that are of no interest to us in this case and that generate a lot of bloat. Start xev as follows to only show the KeyRelease events:
xev | grep -A2 --line-buffered '^KeyRelease' | sed -n '/keycode /s/^.*keycode \([0-9]*\).* (.*, \(.*\)).*$/\1 \2/p'
(just copy/paste that command on a shell and run it with your normal, non-root user) When the xev window appears, move your mouse above that window. Then press one of those special keys on your keyboard, and you will see the appropriate keycode in the terminal window where you typed the command above, for example:
160 NoSymbol
174 NoSymbol
176 NoSymbol
Note that after pressing the key, you will most probably have to move your mouse around above the xev window, as it seems to queue the X events and will only show the keycode after a few other X events have stacked up (MotionNotify events in this case, doesn't matter, will be enough to flush out the event queue and the keycode will show up in the terminal). Take good note of those numbers (160, 174 and 176 in our example above) and with what keys they are associated. Note that the numbers from our example correspond to the "volume mute", "volume increase" and "volume decrease" special keys on a Dell notebook. We're now finished with xev, so you can terminate by typing Control+C in the terminal where you entered the xev | ... command above.

Back to xmodmap

Now that we have the keycodes, we can write them down in our very own xmodmap configuration, namely in the file ~/.Xmodmap. We just have to decide to which keysyms we shall map them. I would recommend to use keysyms that are usually not bound to any keys, like F13, F14, and so on. In this example, we're going to bind the keycode 160 to the keysym F13, 174 to F14 and 176 to F15. Use your favorite text editor (vim, emacs, kate, nano, whatever) and put the corresponding lines in a file named .Xmodmap (note the dot and the capital X) under your home directory, like this:
keycode 160 = F13
keycode 174 = F14
keycode 176 = F15

Applying the xmodmap configuration

The ~/.Xmodmap file will automatically be interpreted by xmodmap every time you start the X server with startx or when you log into an X session using XDM/KDM/GDM. But to avoid restarting our current X session, here is how to apply our configuration immediately:
xmodmap ~/.Xmodmap

KHotKeys

Now the keys can effectively be used in applications like KDE's khotkeys, to be bound to commands or actions. As an example, here is how to bind our new F13 keysym to mute the sound volume, by sending a DCOP call to kmix: 1) start the KDE Control Center (kcontrol) 2) select "Regional & Accessibility" 3) select "Input Actions" 4) in the "General" tab, create a new action (using the "New Action" button), and give it a name in the "Action Name" text box (e.g. "mute volume") 5) still in the "General" tag, select "Keyboard Shortcut -> DCOP Call (simple)" for the "Action type": 6) go to the "Keyboard Shortcut" tab, click on the box that shows "None" and type the special key you want to assign to that action - as for our example, the box should then show "F13" 7) go the the last tab, "DCOP Call Settings" and enter the following data: * Remote application = kmix * Remote object = Mixer0 * Called function = toggleMute * Arguments 0 Then press the "OK" button and... we're done :) If you want something that works with any window manager, have a look at xbindkeys (SUSE Linux RPMs of the latest xbindkeys version here)

11 Comments:

Blogger James Ots said...

What's even better is to map things to keycodes specifically designed for those keys. For my DELL Inspiron 8200 I have:

keycode 133 = XF86AudioPlay
keycode 134 = XF86AudioStop
keycode 135 = XF86AudioPrev
keycode 140 = XF86AudioNext
keycode 176 = XF86AudioRaiseVolume
keycode 174 = XF86AudioLowerVolume
keycode 160 = XF86AudioMute

If you have KMilo running in your KDE Services then the volume keys will then automatically control the volume.

However, although I have them in ~/.Xmodmap, they don't get applied automatically, and I have to run xmodmap manually for some reason.

00:10  
Blogger Loki said...

Thanks James, very good point.
I knew I once saw something like that.
For some weird reason, those keysyms don't show up in /usr/X11R6/include/X11/keysym.h or /usr/X11R6/include/X11/keysymdef.h, but didn't check /usr/X11R6/include/X11/XF86keysym.h ;)

As for ~/.Xmodmap being loaded on X session startup, it's a bit weird indeed. The code is definitely in the xsession scripts.
Well, it works for sure if you
cp ~/.xinitrc.template ~/.xinitrc

00:20  
Anonymous Anonymous said...

Very decent little guide, I'll remember this one. Just a note that in kmix's particular case it can be a good idea to use the Global Shortcuts option it already has, instead of making a new khotkeys entry. Though I do understand, it demonstrates that DCOP action part very clear. :)

Good stuff.

09:49  
Anonymous Anonymous said...

Nice tutorial! Thanks! Now I have some observations:

(1) Keys on Compaq Evo N1020v:
239 = Mute
174 = Volume down
176 = Volume up

CD keys:
162 = Play/Pause
164 = Stop
144 = Previous
153 = Next

(2) Could you please tell us how to make the other keys work? :)

(3) My Compaq laptop still has some keys that doesn't even show on xev (mainly e-mail and search). Any info anyone could give would be much appreciated.

21:10  
Blogger Loki said...

Hey Mauricio.

For other keys you could still use DCOP commands if kmilo doesn't work. James suggests using specific keycodes for that, so try that way first.

The problem with play/pause, next, etc... is that it depends on your audio player.
If you use amaroK (RPMs),
here are the corresponding DCOP commands:
dcop amarok player playPause
dcop amarok player stop
dcop amarok player prev
dcop amarok player next

In the khotkeys configuration it would be like this, e.g.:
* Remote application = amarok
* Remote object = player
* Called function = playPause
(with Arguments left empty)

For the keys that don't trigger events and keycodes, you might want to have a look at keyfuzz.
As always, SUSE RPMs are in my repository.

23:24  
Anonymous Anonymous said...

I've just been through an exercise like this on FC5/KDE. It took a little trial and error. Most of the multimedia keys on my MCK-91 keyboard did nothing or the wrong thing. Same with my Keyspan 17B USB IR remote.

A few of the keys were stomping on each other so first I set/reset the Linux kernel keycode for some of the multimedia keys by adding setkeycodes commands in /etc/rc.local (executed after Linux but before X Window)

/etc/rc.local
#for Ortek MCK-91
setkeycodes e023 158 #Back
setkeycodes e017 159 #Forward
setkeycodes e024 150 #WWW
setkeycodes e022 155 #Mail
setkeycodes e018 113 #Mute
setkeycodes e030 114 #Decrease
setkeycodes e05a 115 #Increase
setkeycodes e012 128 #Stop
setkeycodes e02e 119 #PlayPause
#for Keyspan 17B
setkeycodes e028 164 #Play

I used command "showkey -s" to get the kernel scancodes, "showkey -k" to get the kernel keycodes and /usr/include/linux/input.h to lookup the intended kernel keycodes. To list all scan- and keycodes I used command "getkeycodes" to check for conflicts.

Next I assigned X Window keysyms to the X Window keycodes by adding xmodmap commands in ~/.kde/Autostart/xmodmap (executed after X Window)

~/.kde/Autostart/xmodmap
#!/bin/bash
#for Ortek MCK-91
xmodmap -e 'keycode 234 = XF86Back'
xmodmap -e 'keycode 233 = XF86Forward'
xmodmap -e 'keycode 178 = XF86WWW'
xmodmap -e 'keycode 236 = XF86Mail'
xmodmap -e 'keycode 160 = XF86AudioMute'
xmodmap -e 'keycode 174 = XF86AudioLowerVolume'
xmodmap -e 'keycode 176 = XF86AudioRaiseVolume'
xmodmap -e 'keycode 144 = XF86AudioPrev'
xmodmap -e 'keycode 232 = XF86AudioStop'
xmodmap -e 'keycode 168 = XF86AudioPlay'
xmodmap -e 'keycode 153 = XF86AudioNext'
#for Keyspan 17B
xmodmap -e 'keycode 110 = XF86AudioPause'
xmodmap -e 'keycode 152 = XF86Back'
xmodmap -e 'keycode 158 = XF86MenuKB' # for the KMenu

I used command "xev" to get the X Window keycodes and /usr/share/X11/XKeysymDB to lookup the X Window keysyms. A more appropriate home for the xmodmap commands might be
$KDEDIR/share/config/kdm/Xsession.

Play and Pause are the same button on my keyboard. On the remote they are separate buttons. There is no XF86AudioPlayPause keysym. But fortunately XF86AudioPause toggles Play and Pause from both keyboard and remote - at least in Kaffeine.

Lastly I setup dcop commands as the article describes for control of Kaffeine. The available commands for Kaffeine are listed by entering "dcop kaffeine KaffeineIface". For simple launch of apps such as browser and mail the KMenu editor allows a shortcut to be assigned.

Everything works - except auto-repeat of the "Up" and "Down" arrow keys on the remote doesn't work allthough the scan- and keycodes are the same as those from the keyboard where auto-repeat does work. Maybe a timing issue in the Keyspan driver.

10:23  
Blogger Loki said...

Thanks for this useful addition.

Note that on SUSE Linux, the right place to put those "setkeycodes" commands would be /etc/init.d/boot.local instead of rc.local

11:00  
Anonymous Anonymous said...

Some applications (Amarok and KMix are such) support global hotkeys setting so you don't need the dcop magic. Just look into KMix right click menu when you click on one of the controls or in Amarok systray menu.

23:19  
Anonymous Anonymous said...

I'm anonymous at 10:23 above. My Keyspan remote unit lacks mouse move control functions. Sure, maybe one of its keys could be tied to do "Alt F12" to get its cursor keys to work as mouse moves. But lately I thought what I really want is a wireless keyboard. A device with the following features

1. wireless using RF (as opposed to IR)
2. works as much as possible as a regular keyboard
3. builtin mouse function (the less gadgets the better)
4. works with Linux
5. multimedia keys
6. small (mini keyboard sized - I have no need for a numeric keypad)

I searched the web and found one - but only one. Belkin's MediaPilot. I bought it - well prepared for a can of worms. But - so far I'm pleasantly surprised. Everything I need from it works - and out of the box. Again, I'm using Linux (FC5). In addition it has the following features

1. docking station that also recharges the keyboard battery
2. can also work as remote for home theater components
3. build quality appears to be good

I'm puzzled that this keyboard is not more popular than the web leaves the impression of. It is not like it is expensive compared to others. The response to typing is prompt without drops or delays - I can't tell if I'm typing from the wired or wireless keyboard. The builtin mouse is as good as such can get I think. In one side of the keyboard is one part of it - a pad sensitive to pressure along the circular perimeter. The more pressure the faster the mouse cursor moves. And the speed steps are calibrated well I think. Not too fast and not too slow. In the other side is the rest of it - the left and right mouse buttons and the scroll wheel with the middle button. A touchpad would have made the keyboard big. A stick would have had - well, that stick sticking up. The range appears more than adequate. I tested at 7m and it works. At that distance binoculars are needed to discern detail even on a large but regular computer monitor.

The Keyspan unit as well as my old conventional mouse both work simultanously with the new keyboard. When I'm at the desktop I'll probably be using my old mouse. It is after all the quickest option to get the mouse cursor from A to B. But I won't need the Keyspan anymore.

BTC's 9019 URF was my runner up. But it falls short in several areas and offers nothing unique itself as far as I can tell. Besides, it is too bulky and funky for me. Another option was MS's Remote Keyboard for XP Media Center. But unless MS made a mistake then that most likely does not work with anything else but XP Media Center.

I did not find anything on the web about this keyboard on Linux. Now there is at least this for someone looking as I was. Another post on the web said that coupon code "12345" gives a nice rebate at Belkins site. I got mine at a regular store.

07:47  
Anonymous Anonymous said...

Have you all heard of keyTouch? I tried it on my system and it is basically a GUI based method of accomplishing what you are doing here. I'm not sure if its a better method then what you have, but it seems easier ;)

http://keytouch.sourceforge.net/about.html

21:33  
Blogger Unknown said...

I wish I had found your blog a long time ago. Simple and everything works. Thank you so much!!!

12:52  

Post a Comment

<< Home