Skip to main content

How I use keyd to remap my keyboard in Ubuntu 22.04 with Wayland

· 13 min read

Think about how you use your keyboard. Imagine how good it would be if your CapsLock can be used as Ctrl, how much better it would be for your left pinky? That is the power of remapping the keyboard. It means you can customize the functionality of each key on your keyboard.

This is a walkthrough of my setup in Ubuntu using keyd. A guide I wish it existed when I first try to find out how to remap my keyboard in Ubuntu.

xkcd 1806

xkcd 1806: borrow your laptop

Why I Remap my Keyboard

After I dual-boot with Ubuntu and used it as my daily driver for work and personal use, one key thing I missed is AutoHotkey. It is a software that I used to remap my keyboard in Windows and create shortcuts and "hotstrings". For example,

  • Remapping CapsLock to Ctrl on hold, but works as Esc when clicked alone
  • z+d to scroll down and z+u to scroll up, and other ways to move my mouse
  • z+e and z+g to insert my email addresses
  • |!=, |->, |<- will be replaced by not equal (≠), right arrow (→) and left arrow (←), etc., automatically

I shared my AHK configuration in this repository if you are interested. I might write about it later too.

I think remapping keyboard is one of the most important change I made to my laptop ever since I know how to use a computer. It is like the first time you discovered you can use Ctrl+C rather than right click and select copy. I highly encourage everyone to try it, especially for keys like CapsLock that is in a very convenient location but is rarely used.

So once I boot to Ubuntu, the first thing I wanted to do is to replicate this setup. Unfortunately, AHK only works for Windows, so I need to look for alternatives. The first difficulty that you might face as well is Ubuntu 22.04 by default uses Wayland as the window system, but a lot of tools available online works for X11 only. For example, there is AutoKey that seems to be popular but only works for X11.

I first tried input-remapper. It looks promising with nice graphical user interface, but it didn't work out when I tried to add more complicated logics. I also tried keyboard, a Python module which allows me to create custom keyboard events in Python code. It's like coding my own daemon, but I found it too much overhead and quite laggy. In the end, I found a list of input remapping utilities provided by Arch Linux wiki. Going through the list and I chose keyd which works for me quite well over the past year.

If you are using Windows, you can check AutoHotkey as linked above. If you are on Mac, I read that Karabiner is good, but I have not used a Mac before. If your keyboard supports it, QMK/VIA might be good for you. Even if keyd doesn't work for you, you may follow along to get some inspiration even though the syntax of the config is not the same.

Basic Concepts in keyd

The very first thing to understand in keyd config is it operates in terms of layers. The most intuitive way for me to understand a layer is the Shift key. When the Shift key is pressed and hold, a different layer is activated and all the keys on your keyboard have a different meaning. And what keyd allows you to do is to define custom layers, that is, defining how the layers are activated and deactivated, and what each key means in each of the defined layer.

The major reason I select keyd is it works in X, sway and gnome in Wayland. Second, it natively supports key overloading, which allows me to configure the CapsLock key to behave as Ctrl on hold while Esc when tapped. From what I test, it is instant and fast too.

To get started, install keyd from source. Clone the repository and build it from source. Follow the instructions in the README to install it.

Here are some helpful commands to know:

  • sudo systemctl enable keyd: start keyd, probably run it once in your lifetime
  • sudo keyd reload: reload the config every time after you edit the config
  • sudo keyd monitor: print key events, useful to debug what is remapped
  • keyd list-keys: list all the valid key names, useful to check the possibilities
  • backspace+escape+enter keyboard combo: terminate keyd anywhere anytime in case you severely messed up (happened once to me)

Sharing my Configuration

Here is the full config if you are interested. I will explain it line by line below.

[ids]

*

[main]

# Maps capslock to escape when pressed and control when held.
capslock = overload(control, esc)

# Maps z to a custom layer, but just 'z' when pressed.
z = overload(z, z)

# Shift layer
[shift:S]

## shift+capslock is capslock
capslock = capslock

# Custom z layer
[z]

## Escape common patterns
i = macro(zi)
o = macro(zo)

## Emails
e = macro(hi@ethanppl.com)
g = macro(hi@ethanppl.com)

## Simplify complicated shortcut keys
v = C-S-v
n = C-S-a
x = A-f4
s = command(systemctl suspend)

## Arrows
j = down
k = up
h = left
l = right

## Media / modifier
m = oneshot(media)

[media]

j = previoussong
k = playpause
l = nextsong

a = macro({ enter 10ms "Aut 10ms hor 10ms iza 10ms tio 10ms n": space "Bea 10ms rer space C-v)

Main layer

Let's go through it line by line.

capslock = overload(control, esc)

This is the most important feature that I need, as introduced in the beginning. According to the man page, overload(<layer>, <action>) "activates the layer on hold while executes the action on tap". This line means CapsLock will act like Ctrl when used with other keys. But when I tap it only, it works as Esc. This makes key combo like Ctrl+C way easier than before, where the Ctrl key is in the bottom left. It also makes Esc easier, which is used a lot in Vim. This single line is the biggest reason why I picked keyd.

z = overload(z, z)

This might seem weird when you first look at it, but think about the key z as its own layer (remember, layer is like the Shift key). So when z is hold, it activates a z layer, like holding the Shift key activate the shift layer, but it acts as z when tap alone. This gives me another modifier key (e.g. Ctrl, Alt, Shift), without overriding what the default keyboard shortcuts that come with software programs. But before we go into this special z layer, we need to fix one thing first.

Shift layer

[shift:S]

capslock = capslock

We don't have a CapsLock key after we remap it. What this two lines do is that, in the shift layer, map CapsLock to work as CapsLock. So to summarize, right now holding CapsLock is Ctrl, tapping CapsLock once is Esc, and doing Shift+CapsLock is CapsLock.

The z layer

i = macro(zi)
o = macro(zo)

First thing is since I did this custom z layer with AutoHotkey in Windows, I realized the character i and o commonly follows the z key (e.g. amazing and amazon). To avoid delay in typing or keys being ignored because I typed i before releasing z. I mapped press and hold z then i (z+i) to output zi and z+o to output zo here.

I use + sign to mean press and hold the first key and type the second key. But keyd use + sign to mean chording, which means two keys to be pressed at the same time. I didn't use chording in my config and most of the documentation for keyboard shortcuts often use + sign like Ctrl+c, so I hope it's easy to understand.

e = macro(hi@ethanppl.com)
g = macro(hi@ethanppl.com)

Next, I mapped z+e and z+g to two emails that I used the most for communication and sign in. You will be amazed how many times you type your email each day. And how much better you don't need to type @ anymore.

v = C-S-v
n = C-S-a
x = A-f4
s = command(systemctl suspend)

Here I simplified some commonly used shortcuts with the z layer. In keyd, capitalized C, S and A means Ctrl, Shift and Alt key respectively. And the hyphen - means press and hold. For example, z+v is an alias of Ctrl+Shift+V which is often used as paste text only or the markdown preview in VSCode. z+n is an alias of Ctrl+Shift+a which shows information of all tabs in Chrome. z+x is an alias of Alt-F4 which closes a window. And z+s run the systemctl suspend command, which will suspend the laptop. I find this helpful, and somehow I trust it to suspend my laptop successfully more than just closing the lid of my laptop.

j = down
k = up
h = left
l = right

Here I mapped j, k, h, l to be arrow keys. For example, holding z+l will produce the right arrow key. The reason for these mappings (e.g. why j is down) are based on Vim motions. These are helpful because arrows are usually unreachable unless I move my palm away from my keyboard. Doing z+l allows me to do things like autocomplete in terminal without moving my palm.

You might also notice that I try to pair keys that are comfortable to reach when holding z down, like I would avoid mapping anything to z+a that is just complicated and unnatural to type.

The z+m layer, a layer on top of a layer

m = oneshot(media)

Still in the z layer, I defined the m key to activate the media layer. It activates this layer as oneshot. The man page defined oneshot as "If tapped, activate the supplied layer for the duration of the next key press". It means the layer is activated once tapped, and it will be toggled off only after another key is pressed. This means the media layer is activated once we tap z+m, and we don't have to hold it for it to be active (unlike the shift or z layer).

[media]

j = previoussong
k = playpause
l = nextsong

I discovered these keys when browsing through the keyd list-keys command. And Ubuntu support these keys. How this works is once I pressed z+m, then tap l, it will emit a nextsong key press. What this allows me to do is whichever active window I am in, I can use z+m, then j, k, or l to go back, pause, or skip a song in Spotify, which I think is pretty amazing.

a = macro({ enter 10ms "Aut 10ms hor 10ms iza 10ms tio 10ms n": space "Bea 10ms rer space C-v)

One last line which doesn't relate to media actually, but I put it in any way. It is used to help me type the authorization header in GraphQL playground. What I have to do is copy the token that I want to use, then type z+m and a, it will help me generate the whole

{
"Authorization": "Bearer <token_copied_here>"
}

It is taking advantage of the GraphQL playground I used that will help me close the { curly braces. With some trial and error I realize I cannot make keyd to type all keys at once, so I leave some delay in between and that works better. Having it run Ctrl+V to paste also helps a lot. I find this saves me quite some time each day.

Other thoughts

There are many other features in keyd that is up to you to explore. I have added different configs in and out over the year until I settle down to this set of commands. For example, initially I also configured a shortcut to type console.log and IO.inspect for TypeScript and Elixir, other than the authorization header shortcut, but I found out I rarely used them and I removed them.

I also tried oneshot(shift) which is recommended in the keyd README. But it didn't work for me. I find out I often tap Shift but changed my mind afterwards, which makes me accidentally typed characters in uppercase. I also find out oneshot(shift) doesn't work well with Shift and drag to select in bulk with mouse because it doesn't understand there is a mouse click and deactivate the shift layer after I realize the Shift key.

Another thing I found is the command() call doesn't always work. I once installed copyq to get clipboard history and I configured z+c to be command(copyq show), but it never worked. It's not a dealbreaker and I didn't spend time to debug why.

One thing you might already notice is there is no more hotstrings, which I had in my AutoHotkey configuration. I can no longer type , , and other special characters that easily. I realize configuration like l = ← doesn't work. After reading the man page, I believe I can make it works by setting up Unicode support, which have some other external configuration required, and I have not spent the time investigating.

There is also no more mouse control. There is no way to move my mouse with keyd because all keyd does is to remap keys. I know there are other daemons in Linux that are designed for that, but I have not tried. I remember I found one before, but it only works for X11.

There are also some minor problems that I wish to solve in the future. For example, Ctrl+<arrows> is a common key combination that I do, but I can't easily do that with CapsLock+z+<hjkl> because the relative position of CapsLock and z is too close.

I would like to try mapping specific shortcuts to specific applications too. But most of the time I find the default configs coming with the app works good enough. Also, I did not configure any keys to launch an application because I find meta+<num> good enough to open the windows that are pinned to the task bar. For example, win+2 always open my browser and win+3 open VSCode.

That is how my configuration in keyd works and some of my reasoning behind it. I hope you like this explanation, and it inspires you to remap your keyboard too. It genuinely improved my life.

You might be interested in this page about keyboards in my Wiki too.