Wheat plants

Kinesis Freestyle 2 and Linux, Part 2: The Userspace Fix

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.

The Plan

At the end of the last post, I developed a plan for trying to solve this mess:

  1. Decode the usbmon output that was generated when I pressed the keys so that I understand what the messages that the kernel receives from the keyboard are.
  2. Figure out which part of the kernel is handling these events and try to discover what it’s doing with them.
  3. 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”.
  4. Ensure that X is receiving events from the kernel when I press these keys.
  5. Remap those events to the proper keysyms using setxkbmap or xmodmap.
  6. Map those keysyms to actions with sxhkd or 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

Using the usbmon kernel documentation, I decoded the output into the following fields:

URB Tag Timestamp Event Type Address URB Status Data length Data tag Data
ffff9be1878abf00 3991596568 C Ii:1:003:1 0:8 8 = 00000000 00000000
ffff9be1878abf00 3991596592 S Ii:1:003:1 -115:8 8 <
ffff9be185c78240 3996996471 C Ii:1:003:2 0:128 3 = 03e200
ffff9be185c78240 3996996486 S Ii:1:003:2 -115:128 3 <
ffff9be185c78240 3997124436 C Ii:1:003:2 0:128 3 = 030000
ffff9be185c78240 3997124446 S Ii:1:003:2 -115:128 3 <
ffff9be185c78240 3998276638 C Ii:1:003:2 0:128 3 = 03ea00
ffff9be185c78240 3998276657 S Ii:1:003:2 -115:128 3 <
ffff9be185c78240 3998404740 C Ii:1:003:2 0:128 3 = 030000
ffff9be185c78240 3998404749 S Ii:1:003:2 -115:128 3 <
ffff9be185c78240 3999556653 C Ii:1:003:2 0:128 3 = 03e900
ffff9be185c78240 3999556669 S Ii:1:003:2 -115:128 3 <
ffff9be185c78240 3999684686 C Ii:1:003:2 0:128 3 = 030000
ffff9be185c78240 3999684703 S Ii:1:003:2 -115:128 3 <
ffff9be185c78240 4000836674 C Ii:1:003:2 0:128 3 = 039201
ffff9be185c78240 4000836693 S Ii:1:003:2 -115:128 3 <
ffff9be185c78240 4000964678 C Ii:1:003:2 0:128 3 = 030000
ffff9be185c78240 4000964688 S Ii:1:003:2 -115:128 3 <
ffff9be1878abf00 4003468780 C Ii:1:003:1 0:8 8 = 01000000 00000000
ffff9be1878abf00 4003468801 S Ii:1:003:1 -115:8 8 <
ffff9be1878abf00 4003844595 C Ii:1:003:1 0:8 8 = 01000600 00000000
ffff9be1878abf00 4003844612 S Ii:1:003:1 -115:8 8 <
  • 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 Ii means 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 usbmon captured data, this is how much it captured.
  • Data tag:
  • a = indicates that the captured data is included in the usbmon output
  • a Hypothesis: Does hid-generic fail 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 0x030000.

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 Data column.

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.

End Edit

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 hidraw device 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 dwm, i3, and sway in the future. I think I’ll save handing step 5 for a future blog post when I’m using those window managers.

Recap

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 /dev/hidrawX.

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!

Image Credit: Monica Hackett
Image License: CC BY-NC 2.0

5 thoughts on “Kinesis Freestyle 2 and Linux, Part 2: The Userspace Fix”

  1. Nice work! It’s a shame that the hardware’s Fn key is not momentary, so that the volume keys only work if the built-in numeric keypad is on (making normal typing impossible). I just gave up and mapped F8-F11 to the volume and calculator functions. Too bad this keyboard has to be so “special”.

    Like

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.