Random thoughts

Yeah, well, that's just, like, your opinion, man.

Jeff Lebowski

Home row mod with kmonad and systemd

Here are a few notes and explanations about how I use kmonad under linux to apply home row modifier keys. For a fantastic introduction and configuration guide about home row modifiiers, read this first: A guide to home row mods

I’ll base my kmonad configuration files on what’s written in this aforementioned guide.

If you first want to read about kmonad and what it can do (spoiler: a lot of things I thought only possible with firmware), the official project is hosted there: https://github.com/kmonad/kmonad

Making it work with hotplug

The thing that I find missing, is how to have this working for devices that you hotplug, may it be USB or Bluetooth. This is where both systemd and udev come in handy.

udev is used for device identification and reacting to changes. systemd and udev can work together to manage the associated kmonad process and its arguments, as a service. Then it’s the kmonad process that will do the work of making the home row modifiers magic.

Device and udev rule creation

Let’s start with the device and its file. We’ll need a way to identify it with the udevadm tool and let kmonad know which device file to use. If you know the input event device file, you can use something like that to help you build a udev rule:

udevadm info --attribute-walk /dev/input/by-id/usb-SONiX_USB_DEVICE-event-kbd

For a bluetooth device, you can identify the device by its Bluetooth address as displayed by bluetoothctl. But you need to convert it to lowercase if you want to find it in udev’s tree.

BT_ADDRESS="00:1F:20:76:41:30"
BT_ADDRESS_LC=$(echo ${BT_ADDRESS,,}) # bash lowercase
udevadm info --tree | grep -C 20 $BT_ADDRESS_LC

You should try to find a device from the input subsystem and its event sub-device. Once you have it, use the same kind of udevadm command as for the USB keyboard. For my USB keyboard, I have a device alias (a symbolic link) that is stable in /dev/input/by-id/, so I’ll use that in the kmonad config. For the Bluetooth device, I’ll use the udev rule SYMLINK field to create a stable device name in /dev.

For udev rules, here is an exemple file to put in /etc/udev/rules.d/60-keyboard-homerowmod.rules:

SUBSYSTEMS=="input", TAG="systemd", ATTRS{name}=="SONiX USB DEVICE Keyboard", ENV{SYSTEMD_WANTS}+="kmonad@rk61.service", ENV{ID_MODEL}="Royal Kludge RK61 USB keyboard"

Or, for my bluetooth variant, also creating an alias to find it easily later:

SUBSYSTEM=="input", TAG="systemd", ATTRS{name}=="RK-Bluetooth keyboard", ENV{SYSTEMD_WANTS}+="kmonad@rk61bt.service", SYMLINK+="rk61bt"

As you might have seen in the rules, there is a way to make udev and systemd work together.

systemd to the rescue

For the systemd service unit, here is a possible solution using templating, defined as a /etc/systemd/system/kmonad@.service file:

[Unit]
Description=kmonad advanced keyboard daemon
StopWhenUnneeded=true

[Service]
Restart=on-abort
RestartSec=3
ExecStart=-/usr/local/bin/kmonad %E/kmonad/%i.kbd
Nice=-20

[Install]
WantedBy=default.target

The %i part is the name of your configuration file and the instance name of this template.

kmonad configuration

For each instance of the service, as defined in your udev rules, you’ll need a kmonad configuration file. They should be at the path defined in the systemd service. For instance: /etc/kmonad/rk61bt.kbd

Inside your configuration files, you need to use the same stable device files as the ones created by udev.

For my case, here are the two stable devices files:

And here is a snippet of the lines for the two inputs in my two kbd files:

input  (device-file "/dev/rk61bt")
input  (device-file "/dev/input/by-id/usb-SONiX_USB_DEVICE-event-kbd")

With all this, the kmonad process is started when the device is plugged and stopped when it disappears.