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:
- 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.
- 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:
- 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.
- 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.
- 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.
- Kinesis Freestyle2 User Manual: Simply states that most of the special driverless hotkeys will work with Linux.
Of these,  was the most helpful.
jbastian put me on to several useful tools that I will detail one at a time.
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 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?
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
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/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:
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.
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
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
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:
FnEnter “Function Mode”
FnLeave “Function Mode”
Ctrl+CKill 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|
|Volume Down||0xEA (234)|
|Volume Up||0xE9 (233)|
Ed Nisley, the author of , 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
XF86Calculator using tools like
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:
- 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.
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!