Kanata logo is a pink keycap with a darker pink K legend
Kanata logo

Computer keyboards were designed with historical baggage and for entirely different workflows than we use today and with very little regard for usability. Ergonomic keyboards are great and I personally use a 42-key Corne keyboard with my desktop and that’s what got me to learn to fully touch type and significantly aleviated shoulder stiffness. But what if I could have a similar experience on a laptop, well with Kanata keyboard remapping I can get some of the important pieces. Kanata is a lot more powerful than what I’ll describe here but this should set someone up enough to dive deeper into customizing.

Disclaimer

By the nature of keyboard re-mapping, you’re essentially running a key-logger. So please use this based on your personal risk profile, as the software will process every keystroke, like your passwords. As of the time of writing this guide, March 2024, I personally believe this software is safe to run. This isn’t meant to scare anyone but it’s meant to inform your consent.

Personal Background / Motivation

I didn’t learn to type at all until I was in tenth grade, due to lack of opportunities. So when we finally got a computer and started attending a school that required submitting typed homework, I had to learn in a hurry. But the real motivator was being able to chat with friends on ICQ1 and MSN Messenger. I was so horrid at typing that instead of SHIFTing to capitalize I would just leave CAPS LOCK on until my friend asked why I was yelling, I had to idea you could SHOUT with text. So I evolved an excellent 7 finger typing style that got up to 70+ words per minute on good days. Switching to the Corne meant learning split ergo key layout along-side proper 10 finger touch typing. But once I got used to that, anything else felt so clunky, especially typing on a laptop keyboard. The biggest things I missed were home row mods, layer switching with my thumbs so I could use the home row for more functions (primarily arrow key navigation with h j k l), and left-thumb space and right thumb backspace, enter Kanata.

Intended audience

Apparently I’m writing guides for very niche audiences, a likely audience of one, perhaps. This particular post is for anyone that is interested in non-standard mappings for their keyboard but slanted more towards those who want to get cross-platform QMK-like functionality with any standard keyboard. Running the application will be geared towards Linux for now, since that’s my primary operating system but I’ll link to instructions for other OSes (since cross-platform is a desired feature for me, no lock-ins ever!).2

Terminology basics

There are some potentially confusing terms so I’ll try to capture those here:

  • Input: this is an overloaded term to mean both the input (key presses), as well as the device (keyboard), and the operating system mapping of the device to a file (especially in Linux since everything is a file in Unix and friends land).
  • Process: this is the actual Kanata application that is running and capturing your keystrokes from input (all three meanings of input) changing them based on your config before the Operating System “sees” the keystroke.
  • Config: the customization that you define for Kanata to do the mapping from the physical keys being pressed to what you what you want to happen.

Installation

This is a potential can of worms and very dependent on your operating system, skill-level, and preferences. So here’s a way to do it and it’s definitely not the best way since it won’t automatically update the software.3

I recommend making a folder in your .local directory to store the executable that can be downloaded from the releases page. Make sure the executable is named kanata

1
2
mkdir -p ~/.local/share/bin
chmod u+x ~/.local/share/bin/kanata

I wouldn’t advice adding the directory to your path, but explicit call Kanata with a full path. During the initial configuration and testing it would be best to just run the application directly, once you’re happy with the original layout (you’ll absolutely be tweaking stuff later) then we’ll write a service to automatically run it.

Permissions

In order for Kanata to run, it needs to be able to read the keyboard input before any other process does. Since this is a security concern the process needs to be run as root (administrator access level) to read input directly. However, that means the process would have root access to everything, which is also not a good idea.

So we’ll follow the principle of least privillege. One way to do that is to create groups and give each group just a specific permission and if an user needs to do a few specific things then that user can be added to those specific groups and get one permission from each specific group.

The documentation in the Kanata covers how to set this up on Linux, but I’ll include commands and commentary here.

  1. Create the specific group that will only have permission to read input devices uinput
  2. Add our user to the input and uinput groups
  3. Define the rules for the group uinput by creating a rules file
  4. Reload the devices with the new permission in place
  5. Reload the drivers
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16

# 1
sudo groupadd uinput

# 2
sudo usermod -aG input $USER
sudo usermod -aG uinput $USER

# 3
sudo echo 'KERNEL=="uinput", MODE="0660", GROUP="uinput", OPTIONS+="static_node=uinput"' > /etc/udev/rules.d/99-input.rules

# 4
sudo udevadm control --reload && udevadm trigger --verbose --sysname-match=uniput

# 5
sudo modprobe uinput

Configuration

Kanata has many different ways of picking up which configuration file to use4. The configuration guide is exhaustive and a great resource but it can also be intimidating so here’s a getting started overview.

Here we’ll stick with the default method of creating a file called kanata.kbd in the ~/.config/kanata/ directory. The minimal.kbd is a good place to start to see how things work. The basic premise is that there are different sections (explained below) all of which follow the structure:

1
2
3
4
5
(section-name
 section-specific-information
)
(next-section
)

defcfg

This section stores the “global” options that applies to all of Kanata. I want Kanata to process every key pressed whether I explicitly told it to remap it or not. And I want it to show me what layer (the current map) it’s on when I press a key to change the layer. We’ll want to set this to no later on after we’ve finalized our mapping to make Kanata run faster.

1
2
3
4
(defcfg
      process-unmapped-keys yes
      log-layer-changes yes
)

defsrc

This section can only appear once in the configuration, it tells Kanata which source keys it should expect to map to other things. There’s no specific reason for which keys appear on what line, I have it organized it the way it appears on my laptop keyboard from top left to right, just for visual ease. You can find a list of all keys in the repo.

1
2
3
4
5
6
7
(defsrc
      esc
      grv
      caps
      a s d f g h j k l scln
      lalt spc ralt
)

deflayer

You can define many layers and activate them by pressing keys you define. I’m just defining two layers: the always active layer which means keys that I want to permanently remap to do something different and a second layer that only activates when I hold down the caps lock, which I’m calling cap-mod, you can pick any key and call the layer anything you want.

Note that the keys that are being remapped need to be in the same order as the source layer above. Also there are _, these are called transparent keys. As in their mapping isn’t changed and passed through transparently. The @ denotes an alias, discussed a bit later.

In this example, the default layer is changing the functionality of all the keys, except g, h, and spc because they’re transparent. I’m also “permanently” changing the left-alt to be escape and the right-alt to be backspace. The cap-mod layer changes h j k l to become the arrow keys.5

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
(deflayer default
      @esc
      @grv
      @cap
      @a @s @d @f _ _ @j @k @l @scln
      esc _ bspc
)
(deflayer cap-mod
      _
      _
      _
      _ _ _ _ _ left down up rght _
      _ _ _
)

defvar

Kanata lets you fine tune behavior quite a bit by specifying things like how long a key should be pressed for it to be counted as “held”. While you can type in those values (in milliseconds) each time, it’s easier to define them as a variable in one place and refer to throughout.

1
2
3
4
(defvar
      tap-time 200
      hold-time 250
)

defalias

This is where we define all the aliases (@) we used in the default layer. An alias take the form of: name-of-alias-without-@sign (type-of-functionality options-for-that-function multiple-options-are-space-separated)

I’m using three types of functionality, what Kanata calls Actions to change the functionality of the esc, `, and the home-row keys to act as home-row modifiers.

  • tap-hold: This let’s us define what happens when we tap a key vs. hold a key. Let’s take @a for example, there are two a’s in there a¹ and a², I’ll explain the difference: a¹ (tap-hold $tap-time $hold-time a² lmet)
    • is the name of the alias, it could be anything; like alpha.
    • tap-hold is the name of the action which takes 4 options.
      • $tap-time the number of milliseconds the key can be pressed while still being considered a single press.
      • $hold-time the number of milliseconds for which the key must remain pressed for it count as a hold.
      • is the key code we want Kanata to use when tapped, so this has to be a if we want to type the letter a.
      • lmet is the key code we want Kanata to use when the key is held, in this case left-meta (Windows or Mac logo key).
  • tap-hold-press: Similar to tap-hold but helps managing pressing timing situations better, especially for caps lock which I use as a layer switcher to activate the cap-mod layer for the movement keys.
  • caps-word: This is an excellent function that lets you type CAPITALIZED_WORDS and automatically turns off the CAPS if it’s not a letter or an underscore. Useful for typing environment variables which are typically written in that form.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
(defalias
      esc (tap-hold-press $tap-time $hold-time esc caps)
      grv (tap-hold-press $tap-time $hold-time S-grv grv)
      capsword (caps-word 2000)
      cap (tap-hold-press $tap-time $hold-time @capsword (layer-toggle cap-mod))
      a (tap-hold $tap-time $hold-time a lmet)
      s (tap-hold $tap-time $hold-time s lalt)
      d (tap-hold $tap-time $hold-time d lsft)
      f (tap-hold $tap-time $hold-time f lctl)
      j (tap-hold $tap-time $hold-time j rctl)
      k (tap-hold $tap-time $hold-time k rsft)
      l (tap-hold $tap-time $hold-time l lalt)
      scln (tap-hold $tap-time $hold-time scln lmet)
)

Putting the config together

Here's the full kanata.kbd file that you can save as ~/.config/kanata/kanata.kbd if you want to try my config (click arrow to expand).
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
;; global configuration options
(defcfg
      process-unmapped-keys yes
      log-layer-changes no
)

;; define keys that will be modified (all keys still processed)
(defsrc
      esc
      grv
      caps
      a s d f g h j k l scln
      lalt spc ralt
)

;; default/base layer modifications always active
(deflayer default
      @esc
      @grv
      @cap
      @a @s @d @f _ _ @j @k @l @scln
      esc _ bspc
)

;; shifted layer activated by holding CAPS lock
(deflayer cap-mod
      _
      _
      _
      _ _ _ _ _ left down up rght _
      _ bspc _
)

;; values used by multiple changes
(defvar
      tap-time 200
      hold-time 250
)

;; remapping between physical keys and functionality
(defalias
      esc (tap-hold-press $tap-time $hold-time esc caps)
      grv (tap-hold-press $tap-time $hold-time S-grv grv)
      capsword (caps-word 2000)
      cap (tap-hold-press $tap-time $hold-time @capsword (layer-toggle cap-mod))
      a (tap-hold $tap-time $hold-time a lmet)
      s (tap-hold $tap-time $hold-time s lalt)
      d (tap-hold $tap-time $hold-time d lsft)
      f (tap-hold $tap-time $hold-time f lctl)
      j (tap-hold $tap-time $hold-time j rctl)
      k (tap-hold $tap-time $hold-time k rsft)
      l (tap-hold $tap-time $hold-time l lalt)
      scln (tap-hold $tap-time $hold-time scln lmet)
)

Testing Kanata

We can finally give it a spin. It’s best to just run Kanata in a terminal window and see the output to make sure it’s doing what you expect. You can specify the full path and the full path to the config to take away any weird path issues:

1
~/.local/share/bin/kanata --cfg ~/.config/kanata/kanata.kbd

If there are any errors in the config or the permissions, Kanata gives really good/descriptive errors:

Console output showing Kanata error in keyword cap being unkown and shows the exact location of the error
Rust style descriptive error message showing exact issue

Continue testing until the layout feels comfortable. Once you’re ready, set log-layer-changes false and now we’re ready to run it as a service.

Running Kanata as a service

I’ve discussed running something as a service in another post, so some of the why’s and details are there. For Kanata, create a file called kanata.service and place it in ~/.config/systemd/user/:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
[Unit]
Description=Kanata keyboard remapper
Documentation=https://github.com/jtroo/kanata

[Service]
Type=simple
ExecStart=/home/%u/.local/share/bin/kanata
Restart=no

[Install]
WantedBy=default.target

To enable the service, so it starts every time you are logged in, run:

1
2
systemctl --user enable kanata.service
systemctl --user start kanata.service

If you need to make changes to your config you have to restart the service or you can setup live re-loading.

1
systemctl --user restart kanata.service

Hope this gets you further in your system crafting and ergonomic computing journey!


  1. Apparently ICQ still exists, I fully expected it to be a historical footnote. Looks like it has had an interesting and somewhat checkered past. ↩︎

  2. There are other key mapping software available as discussed in the Kanata repo but I wanted to stick with the QMK familiarity in terms of terminology, platform independence, and even easier key-mapping. ↩︎

  3. Not automatically updating is generally not a good idea, but unless this package makes it into your distribution’s maintained repo it might be a good idea to just manually update. This might mitigate any malicious repo takeover since you’re literally running a key-logger. ↩︎

  4. Running Kanata with the --cfg option would be a good way to test out a few different configurations by supplying a different file:

    1
    
    ~/.local/share/bin/kanata --cfg ~/dotfiles/kanata/example1.cfg
    

    Kanata also provides the ability to load multiple configuration files and switch at run-time. ↩︎

  5. g is never remapped but since the entire “home-row” is being mapped it’s easier to leave g in there to make visually matching up the config easier. ↩︎