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, [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
MuteF9
Volume DownF10
Volume UpF11
CalculatorFn
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:
- 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. - 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
setxkbmap
orxmodmap
. - 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