I have a Kinesis Freestyle 2 Keyboard. I love it, but the volume control keys are not Linux-compatible by default. I’m trying to make them work anyway.
Please Note: This is the second post in a series about solving this. I highly recommend reading the previous post in this series for context before continuing here.
At the end of the last post, I developed a plan for trying to solve this mess:
- Decode the
usbmonoutput that was generated when I pressed the keys so that I understand what the messages that the kernel receives from the keyboard are.
- Figure out which part of the kernel is handling these events and try to discover what it’s doing with them.
- Depending on why the kernel isn’t emitting events, I might have to go in one of several directions to resolve this. This step stands in for “alter the kernel’s handling of those events so that it emits events”.
- Ensure that X is receiving events from the kernel when I press these keys.
- Remap those events to the proper keysyms using
- Map those keysyms to actions with
sxhkdor my Desktop Environment, depending on which graphical environment that I’m in.
Let’s get to it then, shall we?
Part 1: Decode usbmon output
usbmon kernel documentation, I decoded the output into the following fields:
|URB Tag||Timestamp||Event Type||Address||URB Status||Data length||Data tag||Data|
- URB Tag: I think I can ignore this. It’s an in-kernel address of the URB data structure most of the time. It could be a sequence number, but I’m still not sure that it matters.
- Timestamp: Also probably don’t care, so long as I know which one is which.
- Event Type: S is for submission, C is for callback. E is an error, but I don’t have any of those.
- Address: Format is
(USBtype/direction):(Bus number):(Device address):(Endpoint).
- The fact that mine are all
Iimeans that all of these events are “Interrupt Input”.
- They all came in on bus 1, but we knew that already
- The keyboard’s device address on the bus is 003
- They’re all going to one of two “endpoints” (1 or 2). I do not know the significance of this yet.
- URB Status: Format is
(URBStatus)[:interval][:start frame][:error count]. I’m not sure how to tell which of the second fields that I’m dealing with yet, since mine all have a status and one other number.
- Docs for the URBStatus can be found here, though the numbers for each error type must be looked up in errno.h:
- 0 (on an input event) means the data was successfully received
- 115 means that the URB is still being processed by the USB host controllers. It’s apparently a driver bug if your driver ever sees this value. I’m not sure whether to interpret this as a bad sign or not, given that usbmon saw this and not the driver (not necessarily anyway).
- Data length: if
usbmoncaptured data, this is how much it captured.
- Data tag:
=indicates that the captured data is included in the
- a Hypothesis: Does
hid-genericfail to handle the input because the keyboard doesn’t enable the keys that it’s going to use?
Edit: Somehow a chunk of the explanation in this post was missing when I published it. I’ve re-inserted it below.
The USB data that we receive for the multimedia keys follows a pretty simple pattern. When I press a multimedia key, we get
0x03<KEY_ID><OTHER_BYTE>. When I release the key, we get
So how do we write a program that consumes the input from these multimedia keys to make it useful? Well it turns out that the kernel creates special device files at
/dev/hidrawX (where X is some number) for each HID (Human Interface Device) protocol peripheral plugged in. This means that the Kinesis keyboard creates two
/dev/hidrawX devices (it also creates two
/dev/input/eventX devices), and one of them emits the raw bytes of the HID data. This is the data payload that we are seeing above in the
This information is sufficient to write a simple driver program that opens the raw device input file at
/dev/hidrawX and translates each 3-byte input that it receives into a single keystroke. If the second byte is non-zero, we look it up in a table to see which of the multimedia keys it was.
But how do we emit a different keystroke based on what we read? Enter
uinput! It stands for “userspace input,” and it’s a kernel mechanism for allowing userspace to define virtual input devices. You can use it to create virtual mice, keyboards, and many other input types. We can use it to define a simple software keyboard that emits these media keys when we detect a keypress on the raw input file.
I wrote a simple program to do that here. Literally check the second byte of the three bytes that we read and emit a keystroke accordingly.
This simple driver implementation has some drawbacks:
- Must run as root
- I’m not sure whether I can fix this with clever udev rules or not. I’d prefer something that didn’t need superuser privileges though.
- Doesn’t automatically find the keyboard, you need to supply the path to the
hidrawdevice at runtime.
- I have to fix this, it’s unacceptable because it makes it difficult to start the driver automatically.
- Doesn’t handle actual key releases; when I push a key it simulates both keydown and keyup.
- I did this for simplicity, and I’m not yet certain the extent to which it will ultimately matter. I may need to fix this though.
- Doesn’t handle many fast keypresses well. The current implementation definitely loses keypresses on the volume control keys if you press them too quickly.
- I’m certain that this is because I’m not handling more than one input keypress at a time, and I will likely need to change this too.
My repository is here. I hope to update it to address many or all of the above issues, but we’ll see how far my free time actually stretches.
For now, I’ll move on to the next part of the plan.
Part 4: Ensure X gets events
xev is the equivalent of
evtest for Xorg events. You can use it to check whether certain actions are generating events within X.
When I run
xev (while the userspace driver is running) and press any of the special keys, I see output! It doesn’t seem to vary by which key I press though.
Part 5: Map X events to Keysyms
I haven’t done this yet, but I’ll probably need to when I switch DEs. See Part 6 for why.
Part 6: Map Keysyms to actions in WM or DE
This is already working in Unity 7 on Ubuntu. Once the userspace driver started emitting events properly, everything just fell into place.
Additionally, the keys work flawlessly in GNOME 3. I haven’t spent a lot of time in a minimal desktop environment since I got this working. That being said, I will be using a lot more of
sway in the future. I think I’ll save handing step 5 for a future blog post when I’m using those window managers.
After a silly amount of debugging, I discovered that the events from those keypresses are emitted from a file at
/dev/hidrawX (where X is a number), but not emitted as keyboard input on a corresponding
/dev/input/eventX. I wrote a userspace driver program that uses the
uinput kernel features to simulate proper keypresses for each key when data is emitted from
You can find my driver here.
There are still many things that I wish to do to improve this driver, but my time has been limited thus far. Contributions are welcome!
During the course of this writing, I also stumbled upon this awesome list of Kinesis tricks, and I’ve got some ideas from that which might make their way onto this blog in the future.
Anyway, thanks for reading!