Cherry Blossoms, Osaka, Japan

Kinesis Freestyle 2 and Linux, Part 4: Automation

After a few more months of contemplation and some helpful suggestions from Ed Nisley (you can check out his blog here), I have discovered a viable way to automate running the userspace driver for my keyboard when it is plugged in.

I’ve already had to tangle with udev once during this project, so it came as little surprise that the solution arose from there.

I was already using udev to change the permissions on the files that represented both the uinput virtual device and the raw input files for the keyboard, but I needed to carry it further. Ed suggested that I try creating symlinks to those raw input files that made it clear that they were distinct from the rest of the /dev/hidraw device files. It didn’t take long to discover that this is a trivial task for udev. I just had to add a pattern for the symlink names to my existing udev rule:

KERNEL=="hidraw*", ATTRS{idVendor}=="058f", ATTRS{idProduct}=="9410", MODE:="0660", GROUP="chris", SYMLINK="kinesis%n"

The important part is the SYMLINK="kinesis%n". This asks udev to create a symlink with a name like kinesisX corresponding to each hidrawX device that matches those vendor and product IDs (hopefully just this keyboard). The %n in the rule is replaced by the kernel number for the device (0 for hidraw0, 1 for hidraw1, etc…).

With that rule in place, when I plug in the keyboard two new devices appear in /dev/: kinesisX and kinesisY. Often X=2 and Y=3, but the numbers that are used depend on how many other input devices were connected before the keyboard, so I can’t predict which two numbers they will occupy.

Now that we can isolate which devices are the keyboard, the question remains: How do we determine which one is the device that emits the function key events? I used something of a hack here. It seems that the two devices are always advertised in the same order by the keyboard. It first registers the normal key input device, followed by the special function key one. However, this is anecdotal. I have no proof that this is always the case, it’s just always the case for Ed, myself, and the author of this github issue. That’s a large enough sample that I’m okay making this assumption until it explodes in my face.

Since the input device that advertises the function keys is always found second, it always has the higher kernel number of the two kinesis devices. I wrote a simple script to launch kfreestyle2d pointed at the higher of the two:

#!/bin/sh

exec ./kfreestyle2d $(ls /dev/kinesis* | sort | tail -1)

This seems to always work, though I suspect that I can simplify it if I try harder. Feel free to drop suggestions in the comments.

Anyway, this script works every time. It always chooses the higher device number from among the newly-created symlinks, which is always (as far as I know) the correct device.

At this point, there’s only one thing left to do: automatically run this script when the device is plugged in. From my previous experience with udev, I remembered that udev is not meant to launch daemon processes directly, but it can ask systemd to do so on its behalf. A few searches later and I found this Arch forum post about how to do it.

I just needed to create a systemd service file for launching the daemon and then ask udev to launch it. I looked at the systemd.service man pages and at this site for references on how to write the unit. This what I came up with:

# /etc/systemd/system/kfreestyle2d.service
[Unit]
Description=Userspace driver for Kinesis Freestyle 2 Keyboard

[Service]
Type=simple
Restart=no
TimeoutSec=1
ExecStart=/home/chris/Code/builds/kfreestyle2d/sort-and-run.sh

This would correctly invoke the script, but would fail to start if I tried to start it manually with sudo systemctl start kfreestyle2d.service. It took a long time to discover that the error: 127 that I was getting was because the script didn’t refer to the executable using an absolute path, and systemd’s current working directory was not the directory that I was using to store this stuff. The fixed script looks like this:

#!/bin/sh

exec /home/chris/Code/builds/kfreestyle2d/kfreestyle2d $(ls /dev/kinesis* | sort | tail -1)

At this point, I could use sudo systemctl start kfreestyle2d.service and the daemon would launch (and open the correct input device). All that remained was asking udev to launch it for me.

That was only one quick tweak to the existing udev rule:

KERNEL=="hidraw*", ATTRS{idVendor}=="058f", ATTRS{idProduct}=="9410", MODE:="0660", GROUP="chris", SYMLINK="kinesis%n", RUN+="/bin/systemctl --no-block start kfreestyle2d.service"

The operative part is, of course, RUN+="/bin/systemctl --no-block start kfreestyle2d.service". This asks systemd to start that unit file when this udev rule fires, which is all we need.

After I got the right syntax for that, the whole system started working flawlessly. When I plug the keyboard in, udev matches it, changes permissions, and launches the driver. When I remove the keyboard, the driver gets an I/O error reading the raw input file and kills itself. Then, when I plug the driver back in, the driver is launched again.

This system probably wouldn’t work if there were more than one of this keyboard plugged into the same computer, but that’s not my use-case. Now all I need to do is write a script to automate installing the different components of the driver on a new system. I’d also like to create a new group for the driver to use instead of running as the current user.

In any event, I can now use the function keys easily on at least one computer. Hazzah!

Image by: 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 )

Twitter picture

You are commenting using your Twitter 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.