I’ve been experimenting with keyboards and layouts lately, but I find a lot of the typical linux keyboard config programs don’t work reliably. I always seem to have some trouble tweaking them or actually getting them to work.

Enter KMonad.

KMonad allows you to create a config file, per keyboard, with a particular layout. It supports layers, key combos, custom key mappings - pretty much whatever you want to do with your keyboard.

One problem I have is that it needs to be run with whatever config and run as root. Typically, I start it like this from the command line:

sudo ./kmonad-0.4.1-linux my_keyboard_config.kbd

Three things I don’t like with this approach:

  • I need to open a terminal and type a command to run a config.
  • If I undock my laptop or unplug a keyboard, the script fails and I need to call it again.
  • I’m trying different keyboards, so I sometimes end up Control-C-ing to run another config. Seems like something I should automate! (For instance, if I’m using an external keyboard and undock I need to run a command for the laptop keyboard.)

Systemd It!

So I decided to configure systemd to run KMonad as a service.

Prerequisites:

  • Install the KMonad binary somewhere, like /opt.
  • Make a KMonad configuration file that you can call.

If you have multiple keyboards you want to use, you need to set up a service for each of them.

Do It with Your Laptop Keyboard

Setting up your laptop is a little different than setting up an external keyboard. Let’s take a look at the laptop setup first:

Let’s start with a systemd unit file. This needs to be copied into the /etc/systemd/system directory:

/etc/systemd/system/keyboard_mylaptop.service:

[Unit]
Description=mylaptop keyboard kmonad
After=network.target

[Service]
Type=simple
ExecStart=/opt/kmonad-0.4.1-linux /home/someuser/.kmonad/mylaptop.kbd
Restart=always
User=root
Group=root

[Install]
WantedBy=multi-user.target

Next, you need to run some commands:

# Reload systemd
sudo systemctl daemon-reload

# Start the keyboard_mylaptop service
sudo systemctl start keyboard_mylaptop

# Enable the keyboard_mylaptop service to start on boot
sudo systemctl enable keyboard_mylaptop

# Check the status of your service
sudo systemctl status keyboard_mylaptop

The service name is the name of the service file above, minus the “.service”

Do It with Your External Keyboard

External keyboard setup is a little different, particularly if you want to unplug the external keyboard. If you unplug using the above setup, the service will fail and won’t automatically restart.

You can use conditionals to decide when to start a service in your service file (man systemd.service has lots of info), but these only work when systemd is initially trying to start your service. If my service “went down”, I needed it to restart when I reconnected the keyboard.

So:

This setup is very similar to the always-connected laptop keyboard, but we’re going to make two changes:

  • Alter the systemd unit file.
  • Call a script that checks for your keyboard device.

For the script, we’re going to use this:

external_keyboard_script:

#!/bin/bash

device_name="some-keyboard-device-id-from-dev-input"

if [[ -e "/dev/input/by-id/${device_name}" ]]; then
    /opt/kmonad-0.4.1-linux /home/someuser/.kmonad/mykeyboard.kbd
fi

This file checks to see if your keyboard is plugged in, and if it is, it’ll run the KMonad script. You can use a value from /dev/input/by-id or /dev/input/by-path.

For our systemd service file, we’re going to use this:

/etc/systemd/system/keyboard_myexternal.service:

[Unit]
Description=myexternal keyboard kmonad
After=network.target

[Service]
Type=simple
ExecStart=/home/someuser/.kmonad/external_keyboard_script
Restart=always
RestartSec=5
User=root
Group=root

[Install]
WantedBy=multi-user.target

Here, you specify the script in ExecStart.

We restart the script on any condition by using Restart=always.

If the script fails, we restart the script on an interval, until it succeeds, with this: RestartSec=5.

Now I never have to call KMonad again!

Ansible Setup

Since I don’t like repeating tasks like this, I added to my local Ansible config. I’m adding to my own roles for convenvience, but you could turn these incomplete parts into a playbook to run yourself:

#######################################################
# Copy external keyboard scripts to your home directory
#######################################################

#######################################################
# Vars
#######################################################
vars:
  keyboards:
    - laptop
    - keyboard_1
    - keyboard_2

#######################################################
# Put these files in the files directory:
#  - keyboard_laptop.service
#  - keyboard_keyboard_1 .service
#  - keyboard_keyboard_2 .service
#######################################################

#######################################################
# tasks
#######################################################
- name: Copy kmonad keyboard service unit files
  ansible.builtin.copy:
    src: "files/keyboard_{{ item }}.service"
    dest: "/etc/systemd/system/keyboard_{{ item }}.service"
    owner: root
    group: root
  loop: "{{ keyboards }}"

- name: Reload systemd and start keyboard services
  ansible.builtin.systemd:
    state: started
    enabled: true
    daemon_reload: yes
    name: "keyboard_{{ item }}"
  loop: "{{ keyboards }}"

Thoughts

Checking for the external keyboard could also be done in a root cron job. I wonder how the repeated polling by systemd or cron will affect my battery life. It’s likely that turning down the screen brightness has a much greater affect!