Ok so my program was running for a night and it still works so I post the code. It is a bit fugly but works. I will also write about how I did it because it will be useful for people with not exactly my keyboard. The program needs a recent enough libpcap and wireshark. The debugfs needs to be mounted (mount -t debugfs none_debugs /sys/kernel/debug) and the usbmon module loaded (modprobe -v usbmon).
This is the program that runs in the background:
#!/usr/bin/python
# This program should be run as the logged in user. The user must have
# permissions to execute tshark as root.
from pexpect import spawn
from pexpect import TIMEOUT
from subprocess import PIPE
from subprocess import Popen
# Configuration variables
## Device ID from lsusb output
deviceID = "0458:0708"
## Output filter for tshark
filter = "usb.endpoint_number == 0x82 && usb.data != 00:00:00:00"
## Tshark command to execute
tsharkCmd = "/home/stribika/bin/tshark-wrapper"
## Keypress - command mapping
### Key: USB Application data in hex ":" between bytes "\r\n" at the end.
### Value: The command to execute. See subprocess.Popen.
commands = {
"00:00:20:00\r\n":[
"qdbus", "org.freedesktop.ScreenSaver", "/ScreenSaver",
"org.freedesktop.ScreenSaver.Lock"
],
"00:00:40:00\r\n":[
"qdbus", "org.kde.amarok", "/Player", "org.freedesktop.MediaPlayer.Prev"
],
"00:00:10:00\r\n":[
"qdbus", "org.kde.amarok", "/Player", "org.freedesktop.MediaPlayer.Next"
],
"02:00:00:00\r\n":[
"qdbus", "org.kde.amarok", "/Player", "org.freedesktop.MediaPlayer.Pause"
],
"04:00:00:00\r\n":[
"qdbus", "org.kde.amarok", "/Player", "org.freedesktop.MediaPlayer.Stop"
],
"00:04:00:00\r\n":[
"qdbus", "org.kde.amarok", "/Player", "org.freedesktop.MediaPlayer.Mute"
],
"20:00:00:00\r\n":[
"qdbus", "org.kde.kwin", "/KWin", "org.kde.KWin.setCurrentDesktop", "1"
],
"40:00:00:00\r\n":[
"qdbus", "org.kde.kwin", "/KWin", "org.kde.KWin.setCurrentDesktop", "2"
],
"00:00:80:00\r\n":[
"qdbus", "org.kde.kwin", "/KWin", "org.kde.KWin.setCurrentDesktop", "3"
],
"00:00:00:08\r\n":[
"qdbus", "org.kde.kwin", "/KWin", "org.kde.KWin.setCurrentDesktop", "4"
],
"00:00:00:20\r\n":[
"qdbus", "org.kde.kwin", "/KWin", "org.kde.KWin.setCurrentDesktop", "5"
],
"00:00:00:10\r\n":[
"qdbus", "org.kde.kwin", "/KWin", "org.kde.KWin.setCurrentDesktop", "6"
],
}
# USB interface names change across reboots. This determines what is the correct
# interface called this week. If this turns out to be the case with endpoint
# numbers lsusb can tell that too.
lsusbCmd = [ "lsusb", "-d", deviceID ]
sedCmd = [
"sed", "-r",
"s/^Bus ([0-9]{3}) Device [0-9]{3}: ID " + deviceID + ".*$/\\1/;s/^0+/usbmon/"
]
lsusb = Popen(lsusbCmd, stdin = PIPE, stdout = PIPE, stderr = PIPE)
sed = Popen(sedCmd, stdin = lsusb.stdout, stdout = PIPE, stderr = PIPE)
usbIface = sed.stdout.readline().rstrip()
# Arguments for Tshark
## -i is the interface (usbmon[0-9]+)
## -R is the output filter
tsharkArgs = [
"-T", "fields", "-e", "usb.data",
"-i", usbIface,
"-R", filter
]
# Start capturing
## pexpect is needed to disable buffering. (Nothing else actally disables it
## don't belive the lies about Popen's bufsize=0)
tshark = spawn(tsharkCmd, tsharkArgs, timeout = 3600)
line = "----"
# Read keypresses while tshark is running and execute the proper command.
while line != "":
try:
line = tshark.readline()
Popen(commands[line], stdin = PIPE, stdout = PIPE, stderr = PIPE)
# We do not care about timeout.
except TIMEOUT:
pass
As you can see there is a big commands array indexed with the application data from the USB packets. The values are the issued commands. I am using DBus to do what needs to be done but you can use xvkbd to generate real key press events (I found xvkbd very slow it takes seconds to send a simple key combination). tshark-wrapper is a simple wrapper around tshark it executes tshark as root and disables stderr.
#!/bin/sh
sudo tshark "$@" 2> /dev/null
There is the problem. The user needs permission to execute tshark as root without password. This is really really bad thing. The risk could be reduced by putting more aruments in the wrapper and less in the python script and allowing users to execute the wrapper as root.
Now about the process of doing this with other keyboards. I know almost nothing about USB and still it was not that hard. Most of my time was spent figuring out how to do unbuffered read from a pipe. I knew from lsusb output that my keyboard is on the 2nd USB interface. So I started capturing with wireshark on usbmon2. The mouse and other hardwares generate a lot of noise so unplug them or at least do not move the mouse.
The first thing I noticed was that the extra keys have endpoint ID 0x82 and the normal keys have endpoint ID 0x81. There were some packets at the beginning with 0x80. This is good it can be easily filtered:
usb.endpoint_number == 0x82
Normal key press:
Extra key press:
It was easy to see that a key press generates 4 USB packets: 2 for press, 2 for release. In every pair the first packet was sent by the keyboard to the PC and the second was the other way around. It seemed like ACK-s with TCP. The "ACK" was URB-SUBMIT and the normal packet was URB-COMPLETE type. So I decided to filter the "ACK"s and only show normal packets:
usb.urb_type == "C\x01\x82\x03\x02"
USB "ACK":
Now there were only 2 packets per key press. Every second had zero application value field and everything else had different values. So I filtered the zeroes and used the other values to identify keys.
usb.data != 00:00:00:00
Extra key release:
My keyboard is a Slimstar 220 (I hope this does not qualify as spam If it does I remove it.) If you have the same type chances are the unmodified program will work. Otherwise I think at least the application value stuff will be different.
If anyone feels like writing a real driver based on this data please let me know. I don't like my ugly hack.
Update: The code is now hopefully reboot-proof.
M1-M5 are in fact regular keys - they just need to be specifically enabled before pressing them will generate a scancode. tux_mark_5 developed a small Haskell program which sends the correct SET_REPORT message to Razer keyboards to enable these keys, and ex-parrot ported the same code to Python.
On Arch Linux systems the Python port has been packaged and is available from https://aur.archlinux.org/packages.php?ID=60518.
On Debian or Ubuntu systems setting up the Python port of the code is relatively easy. You need to install PyUSB and libusb (as root):
aptitude install python-usb
Then grab the blackwidow_enable.py
file from http://finch.am/projects/blackwidow/ and execute it (also as root):
chmod +x blackwidow_enable.py
./blackwidow_enable.py
This will enable the keys until the keyboard is unplugged or the machine is rebooted. To make this permanent call the script from whatever style of startup script you most prefer. For instructions on how to set this up in Debian have a look at the Debian documentation.
To use tux_mark_5's Haskell code you'll need to install Haskell and compile the code yourself. These instructions are for a Debian-like system (including Ubuntu).
Install GHC, libusb-1.0-0-dev and cabal (as root):
aptitude install ghc libusb-1.0-0-dev cabal-install git pkg-config
Fetch the list of packages:
cabal update
Install USB bindings for Haskell (no need for root):
cabal install usb
Download the utility:
git clone git://github.com/tuxmark5/EnableRazer.git
Build the utility:
cabal configure
cabal build
Run the utility (also as root):
./dist/build/EnableRazer/EnableRazer
After this you can copy EnableRazer binary anywhere you want and run it at startup.
Immediately after execution, X server should see M1 as XF86Tools, M2 as XF86Launch5, M3 as XF86Launch6, M4 as XF86Launch7 and M5 as XF86Launch8. Events for FN are emitted as well.
These keys can be bound within xbindkeys or KDE's system settings to arbitrary actions.
Since your keyboard might be different, you might need to change the product ID in Main.hs line 64:
withDevice 0x1532 0x<HERE GOES YOUR KEYBOARD's PRODUCT ID> $ \dev -> do
Best Answer
You'll need to find out what key code those buttons generate. You can do this by using a program called "xev". Once you have the key codes, you can use "xmodmap" to map those key codes to keys (usually function keys) and then use them for key combos in your favorite flavor of desktop manager.
This following link has a good tutorial on doing many of the things you need:
http://dev-loki.blogspot.com/2006/04/mapping-unsupported-keys-with-xmodmap.html
Keep in mind that the KHotKeys portion is KDE specific, so you'll have to adapt it to your particular window manager (GNOME, LXDE, etc...).
Edit:
Some "special" keys, especially those on "internet" keyboards, don't give a key code because they are generally software driven. Some options for these keys include:
keyTouch: http://keytouch.sourceforge.net
LinEAK: http://lineak.sourceforge.net/