A frozen leaf

Kinesis Freestyle 2 and Linux, Part 1: Debugging

Like many of my readers, I spend a lot of time typing. This wasn’t historically a problem for me, but in April of this year I started having wrist pain after typing for a while. I searched around for things that I could do to alleviate this, and I wound up taking two steps:

  1. I changed my posture when I type so that I no longer rest my wrists on anything. Instead, my hands hover over the keyboard as I type.
  2. I bought an ergonomic keyboard.

Specifically, I bought a Kinesis Freestyle 2. This is a split keyboard that allows you to angle the sides so that your wrists are straight as you type. I love it. I think that it has helped tremendously with my wrist pain.

The keyboard has many useful extra function keys that can perform actions like “Back” and “Forward” in your browser, copy/paste, and right-clicking where the cursor currently is. I don’t use these frequently, but it’s nice that they’re there. The keyboard also has four keys that I really want to be able to use, but currently can’t. These keys are the volume control keys and a key to launch a calculator.

While I don’t really think I want to be able to launch a calculator, I would like to be able to launch an application of my choice by hitting that key. Additionally, I’d like to be able to control the volume from the keyboard. I’ve grown accustomed to being able to do that on other keyboards that I’ve used, and the lack of that feature is grating.

Kinesis explicitly states that these keys only work on Windows. I knew that when I bought this keyboard, so I’m not complaining. However, I was also pretty sure that pressing those keys would make the keyboard send input to the computer (can’t imagine how this would work if it didn’t). If it sends input, the computer should be able to act on it. In the spirit of open-source, I figured that it shouldn’t be impossible to handle that input myself to make the keys perform their intended function. This document is the record of my journey down this rabbit hole (written in real-time as I worked on it).

Phase 1: Research

I knew that I probably wasn’t the only Linux user who’d bought one of these keyboards, so I checked whether anyone else had solved this problem already.

I found these:

  1. Kinesis FAQ on hot-keys for macOS: Essentially, macOS users can work around the lack of hot-key functionality. This only encouraged me to try harder.
  2. Fedora user (jbastian) asking for help: This guy got much further than I had so far. He communicated with Kinesis about the issue and received helpful device information back from them. I will be leaning heavily on his work in my own attempt.
  3. Blog post from Linux user: This user didn’t really need the hotkey functionality. He did discern the HID usage IDs of each of the non-functional keys, but didn’t pursue it further.
  4. Kinesis Freestyle2 User Manual: Simply states that most of the special driverless hotkeys will work with Linux.

Of these, [2] was the most helpful. jbastian put me on to several useful tools that I will detail one at a time.

evtest

I’m not terribly experienced in the USB input device area, nor in the world of Xorg input drivers. I expect that both of these will need to change for me to solve this problem. Because of this ignorance, I’d never heard of evtest.

evtest is a kernel-level event testing utility. It allows you to monitor the output of the kernel for some particular device. If you see output here when monitoring the device of interest, the kernel is generating output properly. If you don’t, the device is either “grabbed” by some other process such that events are not delivered to you, or the kernel is not emitting events for the device.

The first thing that evtest demonstrates is that the keyboard is actually more than one device on the system. When you run it, it lists all input devices and asks you to select one to monitor:

$ sudo evtest

No device specified, trying to scan all of /dev/input/event*
Available devices:
...
/dev/input/event4:    KINESIS FREESTYLE KB800 KB800 Kinesis Freestyle
/dev/input/event5:    KINESIS FREESTYLE KB800 KB800 Kinesis Freestyle
...
Select the device event number [0-19]: 4
Input driver version is 1.0.1
Input device ID: bus 0x3 vendor 0x58f product 0x9410 version 0x110
Input device name: "KINESIS FREESTYLE KB800 KB800 Kinesis Freestyle"
Supported events:
  Event type 0 (EV_SYN)
  Event type 1 (EV_KEY)
    ... <many other keys>
    Event code 113 (KEY_MUTE)
    Event code 114 (KEY_VOLUMEDOWN)
    Event code 115 (KEY_VOLUMEUP)
    ... <many other keys>
  Event type 4 (EV_MSC)
    Event code 4 (MSC_SCAN)
  Event type 17 (EV_LED)
    Event code 0 (LED_NUML) state 1
    Event code 1 (LED_CAPSL) state 0
    Event code 2 (LED_SCROLLL) state 0
    Event code 3 (LED_COMPOSE) state 0
    Event code 4 (LED_KANA) state 0
Key repeat handling:
  Repeat type 20 (EV_REP)
    Repeat code 0 (REP_DELAY)
      Value    250
    Repeat code 1 (REP_PERIOD)
      Value     33
Properties:
Testing ... (interrupt to exit)

When I type any key, I see an EV_SYN event with a key code corresponding to that key. All seems well until I try to type one of the special keys. Then I see nothing.

Hrm, maybe the other device handles those keys?

$ sudo evtest
No device specified, trying to scan all of /dev/input/event*
Available devices:
... <many irrelevant devices>
/dev/input/event5:    KINESIS FREESTYLE KB800 KB800 Kinesis Freestyle
... <more irrelevant devices>
Select the device event number [0-19]: 5
Input driver version is 1.0.1
Input device ID: bus 0x3 vendor 0x58f product 0x9410 version 0x110
Input device name: "KINESIS FREESTYLE KB800 KB800 Kinesis Freestyle"
Supported events:
  Event type 0 (EV_SYN)
  Event type 1 (EV_KEY)
    ... <many DIFFERENT but irrelevant keys>
    Event code 113 (KEY_MUTE)
    Event code 114 (KEY_VOLUMEDOWN)
    Event code 115 (KEY_VOLUMEUP)
    ... <other DIFFERENT but irrelevant keys>
  Event type 2 (EV_REL)
    Event code 6 (REL_HWHEEL)
  Event type 3 (EV_ABS)
    Event code 32 (ABS_VOLUME)
      Value      0
      Min        0
      Max        1
  Event type 4 (EV_MSC)
    Event code 4 (MSC_SCAN)
Properties:
Testing ... (interrupt to exit)

Here, I couldn’t generate input events at all, both with normal key presses and using the special keys.

Hypothesis: Is this event file “grabbed” by some other process such that I don’t
see the generated events?

The evtest man page suggests how to test this:

If evtest does not show any events even though the device is being used, the device may be grabbed by a process (EVIOCGRAB). This
is usually the case when debugging a synaptics device from within X. VT switching to a TTY or shutting down the X server
terminates this grab and synaptics devices can be debugged.
               
The following command shows the processes with an open file descriptor on the device:
                      
    fuser -v /dev/input/eventX

I tried this:

$ sudo fuser /dev/input/event5
                     USER        PID ACCESS COMMAND
/dev/input/event5:   root       1000 f.... acpid
                     root       1180 F.... Xorg

According to the man pages for fuser, the f under the acpid ACCESS column means that acpid has this file open for reading, while the F in that same column for Xorg means that Xorg has it open for both reading and writing.

Based on these results, it’s plausible that Xorg has a “grab” on this event file. I switched to another TTY to see whether I’d get events there.

Alas, even on TTY1, I couldn’t get either /dev/input/event4 or /dev/input/event5 to emit events when I pressed those keys.

This suggests that the kernel is never emitting events for Xorg when those keys are pressed.

I think that jbastian also reached this conclusion, because he descended a layer here and started looking at the raw events on the USB bus. This introduces the second tool that I learned from him:

usbmon

jbastian observes that the reason there are two device files seems to be because they use features from more than one USB HID Code page (0x07 for normal keyboard input and 0x0C for multimedia). These code pages are defined in this USB HID Standard.

To carry his investigation on to the USB bus itself, jbastian used a tool call usbmon that I’d also never heard of. It wasn’t installed on my system or available via my package manager apt, so I googled around and discovered that it was a kernel capability documented here.

jbastian used debugfs (another kernel feature) to find the bus number of the USB bus that was handling the keyboard’s input. debugfs exposes many kernel subsystems as files in sysfs that you can read and write to debug different kernel features. In this case, a clever grep searches for the string “KINESIS” and prints any matches with the three lines that before the match (this is the functionality of the -B3 flag).

When I run the same command:

$ grep -B3 KINESIS /sys/kernel/debug/usb/devices
T:  Bus=01 Lev=01 Prnt=01 Port=07 Cnt=02 Dev#=  3 Spd=1.5  MxCh= 0
D:  Ver= 1.10 Cls=00(>ifc ) Sub=00 Prot=00 MxPS= 8 #Cfgs=  1
P:  Vendor=058f ProdID=9410 Rev= 1.22
S:  Manufacturer=KINESIS FREESTYLE KB800

The Bus=01 indicates that my keyboard is communicating on USB bus number 1. Now I need to monitor events on only that bus to see whether I can catch events coming from the keyboard when I press those keys.

Note that I didn’t have the usbmon kernel module loaded, so I needed to run sudo modprobe usbmon before the following.

Note also that the 1u in the path below specifies USB bus 1. Using 0 reads on all busses.

$ sudo cat /sys/kernel/debug/usb/usbmon/1u
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 <

That output was generated by my pressing the following sequence of keys:

  • Fn Enter “Function Mode”
  • F8 Mute
  • F9 Volume Down
  • F10 Volume Up
  • F11 Calculator
  • Fn Leave “Function Mode”
  • Ctrl+C Kill the cat program to end output

Eureka! We got USB events when I pressed the volume keys! This is as far as jbastian got as well. He contacted Kinesis tech support and they gave him some information about the keys in question. I’m not yet sure how to use these, but I will reproduce them below for future reference:

Key HID Usage ID
Mute 0xE2 (226)
Volume Down 0xEA (234)
Volume Up 0xE9 (233)
Calculator 0x92 (146)

Ed Nisley, the author of [3], also found these HID Usage IDs, though I don’t know from where. It’s possible that he also read jbastian‘s question and found them there.

This is where jbastian‘s work left off. Kinesis offered to burn him new firmware on his keyboard if he could tell them how they should change the HID Usage IDs for those keys, but he thought that there should be a software solution (as do I).

His original plan was to find a way to get these keys emitted by the kernel and ensure that they were mapped to the keysyms XF86AudioMute, XF86AudioLowerVolume, XF86AudioRaiseVolume, and XF86Calculator using tools like setxkbmap or xmodmap.

That’s a good plan, and I think I’ll roughly attempt the same thing.

Phase 2: The Master Plan

In order to make these keys work, I think I need to:

  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.

I’m going to wrap this post here, but you can read about how well this plan worked out for me in the next post in this series.

Thanks for reading!

Image Credit: Stefan Lins
Image License: CC BY-NC 2.0

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

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