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!