My custom keyboard layout with dvorak and LEDs
Intro
My keyboard setup has always been weird, and usually glued together with multiple programs. Some time ago I decided to re-do it from scratch and this led to some BIG improvements and simplifications I’m really happy about, and want to describe here.
Context: I regularly type in English, German, Russian, Ukrainian, and write a lot of Python code. I use vim, qutebrowser, tiling WMs and my workflows are very keyboard-centric.
TL;DR: This describes my current custom keyboard layout that has:
- only two sub-layouts (latin/cyrillic)
- the Caps Lock LED indicating the active one
- Caps Lock acting both as Ctrl and Escape
- things like arrow keys, backspace accessible without moving the right hand
- Python characters moved closer to the main row
- UPDATE Feb. 2023: Added numpad keys on level5! But not level5 itself. For now,
-option 'lv5:ralt_switch_lock'
tosetxkbmap
works. For an updated picture, see the github repo.
It looks like this1:
and is available on Github.
How I got into custom keyboard layouts
First, one long summer, I switched to the Dvorak keyboard layout2 and loved it.
Then I saw xkcd’s Randall Munroe’s Mirrorboard: A one-handed keyboard layout for the lazy – xkcd. The idea is that it’s easy to repeat with your left hand movements that you do with your right if they are mirrored. This works for blind typing too - if you type l
with your right pinky finger, probably your left pinky finger ‘knows’ that reflex as well.
I loved the idea. My right hand usually has either a mouse or a cup of tea in it, and casual left-hand typing without needing to learn an entirely new layout sounded really interesting.
I decided to create such a mirrored layout for Dvorak.
This led me to the topic of customizing xkb keyboard layouts (the Arch wiki describes it very well).
At the end I did create a Dvorak Mirrorboard layout and used it more than expected (for example, image editing is easier if you don’t need to move your hand from the mouse).
But almost immediately I realized the potential of editing layouts and started to add things I needed, like Enter/Backspace, ümläüts and ß
etc. - still mirrored, but now not a generic layout anymore. Needing a new name I decided on Pchr8board.
There were N iterations, here’s an old post about one of them: Pchr8board - a mirrored left-hand keyboard layout for Dvorak - serhii.net.
Then I kept adding stuff, in the process abandoning most left-hand features. Slowly we converged to a layout I liked.
Non-xkb weirdness
I had other non-standard changes I was really attached to:
- Since forever I have my Caps Lock key remapped to Ctrl, which I strongly recommend to literally everyone. Ctrl is used often and that position is much easier to reach, and no one needs Caps Lock. An ugly
xmodmap
hack on autostart remapped both keys. - Caps-Lock-but-now-Ctrl, if released quickly, it acted as Escape. Incredibly neat, for vim especially. I used xcape3 for this.
It all worked but not flawlessly
All together the setup was a net positive, but was very brittle.
Xcape is clearly abandoned, and neither xmodmap nor xcape work in Wayland. But there are far worse problems.
You need to run it manually on startup (could never get it to run automatically in a reliable way, believe me I tried) and every time you connect a new keyboard.4
Sometimes it took multiple attempts to get all parts working. And every time you run xmodmap
it resets your layout and you need to re-run setxkbmap
.
Not a hypothetical scenario
…Which you may not be able to do, because you’re stuck with a broken layout or Caps Lock on or a Ukrainian layout and no way to change it, because you can’t open a terminal and type a command to do that.
Also all the WM keybindings relying on the former Ctrl key are broken in the process.
You can GUESS where the Alt
key is now and then try to get into a tty. Then your Ctrl
and Esc
are not where you are used to.
Long story short - it was worth the pain, but the pain was there. The setup was band aids on top of other band aids, some applied at the very beginning when I had no idea what I was doing but hey, it works.
One keyboard layout to rule them all
Then I finally did it all from scratch and for real.
The layout in all its glory
Only the changed keys are labelled, with the exception of the default characters when they help me to identify a key.
The keys are read like this:
The key change is that the Left Alt button becomes a modifier key that makes more symbols/actions available. “Latch” is <Level3>
and is located on Left Alt5.
For example, to get Ä
you press <Shift+Latch+a>
. For ä
you just press <Latch+a>
. A
needs only Shift.
In the layout definition itself this is represented like this:
key <AC01> { [ a, A, adiaeresis, Adiaeresis] };
The Right Alt key still works like a normal Alt
.
Notation
Notation that I made up6:
- Written in full (‘Shift’ or ‘Left Alt’) or capitalized (
<LALT>
) are physical keys on the keyboard. Given as:- keylabel / what xkb calls them inside the layout file (
<RCTRL>
) - default Dvorak value (
<c>
refers to the key that produces ani
in QWERTY orш
in Ukrainian/Russian).
- keylabel / what xkb calls them inside the layout file (
Shift
/LALT
are the logical thing after all remappings and modifiers are applied<Shift+q>
are keybindings, with modifiers given by their logical/remapped values (Shift
can be located anywhere on the keyboard as long as it works as aShift
), and the letters are the usual/normal/Level1 unchanged ones (<Shift+q>
produces aQ
, but alone the key<q>
would be a lowercaseq
).
So <LCTRL>
would be “left physical key on the keyboard with Ctrl written on it”, <Shift-Latch-g>
would be “Press whatever keys / pedals / mouse buttons that are your Shift and Latch modifiers, then the key on the keyboard that in dvorak on normal systems results in a q appearing on the screen”.
Two languages instead of four and one LED
I created two layouts, v6
the latin one with umlauts and everything else, and ruua
, that contains both Russian and Ukrainian characters in the same layout.
Pressing the right Control key once changes the layout:
key <RCTL> { [ISO_Next_Group] };
Having only two layouts means you never have to guess which one comes next or set up indicators in the taskbar. You just let your muscle memory automatically do its thing.7
But unlocking the laptop was still a pain. You don’t know the language you were typing in when you locked it, and things like i3lock don’t tell you the layout by default - and you never know if a wrong password is a typo or a wrong layout.
The grp_led
option allows you to use keyboard LEDs as indicators.
setxkbmap -option -option 'grp_led:caps' vv,ruua
Now anytime I’m typing in Russian or Ukrainian the Caps Lock LED is on, regardless of what is shown on the display. Pressing RCTRL changes the layout and makes the LED turn off, and you know it worked.
Custom modifiers defined in the layout itself
No xmodmap
anymore! Caps Lock is now Ctrl
, with Latch
it becomes an Escape
(and the former Ctrl button is a new modifier key Hyper_L
, guaranteed not to collide with anything).
That took time to get right, the key was making Caps Lock a four-level key5, then we can define what happens to it with Level3
/Latch
/<LALT>
:
key <CAPS> { type[Group1] = "FOUR_LEVEL", symbols[Group1] = [ Control_L, Control_L, Escape, NoSymbol] };
modifier_map Control { <CAPS> };
For more, look into modifier_map
8 and real/virtual modifiers9 on the Arch Linux Wiki.
Arrow keys and Backspace easy to reach
Shown in purple, directly in the right hand resting position:
- Arrow keys (CHTN is the new WASD!).10
<Backspace>
and<Delete>
!
key <AD10> { [ l, L, BackSpace, Delete ] };
key <AC07> { [ h, H, Left, Left ] };
key <AC08> { [ t, T, Down, Down ] };
key <AC09> { [ n, N, Right, Right ] };
Being able to quickly delete text with my ring finger without stopping to type to reach the backspace key feels as good as it sounds.
Best thing, all this works with keyboard shortcuts! <Ctrl-Alt-R>
deletes the entire previous word, etc.
Programming features
Mostly the improvements cluster in two areas:
- Move all brackets closer to resting position.
- ALL OF THEM
- No
Shift
for frequent characters- Python and programming:
+
,-
,=
- Vim and vim-like things:
:
!;
now needs aShift
, a sacrifice I’m ready to make.
- Python and programming:
Left Alt as modifier key works very well for them - it’s easier to reach for my left thumb than Shift was for any finger ever.
Some redundancy and left-hand features
There are two additional Enter
keys, one on Space and the other one under Escape. Both closer than the real one, and the latter needs only the left hand. (I found I need a left-hand Enter
more often than any other.)
On that same tilde key there’s a Compose key too, which allows to type some exotic characters that are used too rarely to get their own key.
There is also more than one way to do slashes, this mostly has to do with old layouts I had and still remember if I’m tired or stressed.
Installing
After you read ArchWiki’s Precautions and preparations and assuming you need both the latin and cyrillic layouts:
- Copy the source files to
/usr/share/X11/xkb/symbols/
. (Or maybe create a symlink to a version-controlled version of the layout, then you can do your modifications and test them more easily.) - Name them something reasonable, the file name will be the name used by
setxkbmap
to refer to the layout.11
For the full experience
- Assuming the layouts are in
/usr/share/X11/xkb/symbols/v6
and/usr/share/X11/xkb/symbols/ruua
, run:setxkbmap -option -option 'grp_led:caps' v6,ruua
- If it works, add that command to autostart, it should work.
Light-mode experience
If you don’t want to go all-out:
- Run
setxkbmap -option us
, now you have it in your terminal history setxkbmap -option -option 'grp_led:caps' v6,us
would give you the new layout and on<RCTRL>
you get a standard QUERTY one.- If something goes wrong, use the arrow keys to find the command
setxkbmap -option us
and press Enter to run it, and you’re back in known territory.
The layouts definitions
The sources, keyboard-layout-editor.com .json and the pictures are all available on Github. Pasting them below too for completeness and redundancy.
View the sources of both layouts
Source:
// My current layout, no connection to dvorak-mirrorboard anymore
default partial alphanumeric_keys modifier_keys
xkb_symbols "sh" {
name[Group1] = "SH Custom layout";
// Using L-Alt as modifier instead of Caps lock.
key <LALT> { type[Group1] = "ONE_LEVEL", symbols[Group1] = [ ISO_Level3_Shift ] };
//// TAB AND FRIENDS
// Mod+Space is return
// TODO
key <SPCE> { [ space, space, Return ] };
// Bsp, Enter, **Compose Key **
key <TLDE> { [ BackSpace, Multi_key, Return, NoSymbol] };
// Tab, LTab, /, b\
key <TAB> { [ Tab, backslash, slash, NoSymbol] };
// Switch groups by RCTL
key <RCTL> { [ISO_Next_Group] };
// Caps is Ctrl, ? <Escape> ?
// Mapping Escape to Caps+Shift doesn't work for some reason
key <CAPS> { type[Group1] = "FOUR_LEVEL", symbols[Group1] = [ Control_L, Control_L, Escape, NoSymbol] };
modifier_map Control { <CAPS> };
key <LCTL> { type[Group1] = "ONE_LEVEL", symbols[Group1] = [Hyper_L] };
modifier_map Mod3 { Hyper_L };
////
//// FIRST ROW
// '"`?
key <AD01> { [ apostrophe, quotedbl, quoteleft, NoSymbol] };
// ,<[?
key <AD02> { [ comma, less, bracketleft, NoSymbol] };
// .>]?
key <AD03> { [ period, greater, bracketright, NoSymbol] };
////
key <AD04> { [ p, P, asciitilde, NoSymbol ] };
key <AD05> {
[y, Y, f, F],
[a, a, a, a]
};
// Umlauts
key <AC01> { [ a, A, adiaeresis, Adiaeresis] };
key <AC02> { [ o, O, odiaeresis, Odiaeresis] };
key <AC03> { [ e, E, ediaeresis, Ediaeresis] };
key <AC04> { [ u, U, udiaeresis, Udiaeresis] };
key <AC05> { [ i, I, d, D ] };
key <AB01> { [ colon, semicolon,z, Z] };
key <AB02> { [ q, Q, v, V ] };
key <AB03> { [ j, J, w, W ] };
key <AB04> { [ k, K, m, M ] };
key <AB05> { [ x, X, b, B ] };
key <AE01> { [ 1, exclam, NoSymbol, NoSymbol ] };
// 2@<{
key <AE02> { [ 2, at, less, NoSymbol ] };
// 3#>}
key <AE03> { [ 3, numbersign, greater, NoSymbol ] };
key <AE04> { [ 4, dollar, EuroSign, NoSymbol ] };
key <AE05> { [ 5, percent, NoSymbol, NoSymbol ] };
//// Backspace, arrow keys, ...
// TODO
// key <AD07> { [ g, G, Prior, NoSymbol ] };
key <AD07> { [ g, G, parenleft, braceleft ] };
key <AD08> { [ c, C, Up, Up ] };
key <AD09> { [ r, R, parenright, braceright ] };
// key <AD09> { [ r, R, Next, Next ] };
key <AD10> { [ l, L, BackSpace, Delete ] };
key <AC07> { [ h, H, Left, Left ] };
key <AC08> { [ t, T, Down, Down ] };
key <AC09> { [ n, N, Right, Right ] };
key <AD06> { [ f, F ] };
// Slash and Backslash
key <AD11> { [ slash, question, backslash, NoSymbol ] };
key <AD12> { [ equal, plus ] };
// TODO
key <AC06> { [ d, D, NoSymbol, NoSymbol ] };
key <AC10> { [ s, S, ssharp, ssharp ] };
key <AC11> { [ minus, underscore ] };
key <AB06> { [ b, B ] };
key <AB07> { [ m, M ] };
key <AB08> { [ w, W ] };
key <AB09> { [ v, V ] };
key <AB10> { [ z, Z ] };
// +|\? - the key that by default has only backslash+bar
key <BKSL> { [ plus, bar, backslash, NoSymbol ] };
key <AE06> { [ 6, asciicircum ] };
key <AE07> { [ 7, ampersand ] };
key <AE08> { [ 8, asterisk ] };
key <AE09> { [ 9, parenleft ] };
key <AE10> { [ 0, parenright ] };
key <AE11> { [ bracketleft, braceleft ] };
key <AE12> { [ bracketright, braceright ] };
};
Russian-Ukrainian layout, I adapted an existing one I found:
// Keyboard layouts for Russia.
// AEN <aen@logic.ru>
// 2001/12/23 by Leon Kanter <leon@blackcatlinux.com>
// 2005/12/09 Valery Inozemtsev <shrek@altlinux.ru>
// 2018/07/15 @a13 (a.k.a. @dbvvmpg) and Stepanenko Andrey <ftvkyo2011@yandex.ru>
// 2021 - Adapted to contain Ukrainian characters - serhii.net
// Windows layout
default partial alphanumeric_keys
xkb_symbols "winkeys" {
include "ruua(ruua)"
name[Group1]= "Russian";
key <AE03> { [ 3, numerosign ] };
key <AE04> { [ 4, semicolon ] };
key <AE05> { [ 5, percent ] };
key <AE06> { [ 6, colon ] };
key <AE07> { [ 7, question ] };
key <AE08> { [ 8, asterisk, U20BD ] };
key <AB10> { [ period, comma ] };
// SH -- now adding the bksp and stuff and removing the Enter thing.
key <SPCE> { [ space] };
// Mod+Tab gives a slash, which I use often (searching etc.)
// Mod+Shift+Tab gives an umlaut on the next character
};
hidden partial alphanumeric_keys
xkb_symbols "ruua" {
key <AE01> { [ 1, exclam ] };
key <AE02> { [ 2, quotedbl ] };
key <AE03> { [ 3, numbersign ] };
key <AE04> { [ 4, asterisk ] };
key <AE05> { [ 5, colon ] };
key <AE06> { [ 6, comma ] };
key <AE07> { [ 7, period ] };
key <AE08> { [ 8, semicolon ] };
key <AE09> { [ 9, parenleft ] };
key <AE10> { [ 0, parenright ] };
key <AE11> { [ minus, underscore ] };
key <AE12> { [ equal, plus ] };
key <BKSL> { [ slash, backslash ] };
key <AB10> { [ slash, question ] };
key <LSGT> { [ slash, bar ] };
key <TLDE> { [ Cyrillic_io, apostrophe, U02BC, Cyrillic_IO ] };
key <AD01> { [ Cyrillic_shorti, Cyrillic_SHORTI ] };
key <AD02> { [ Cyrillic_tse, Cyrillic_TSE ] };
key <AD03> { [ Cyrillic_u, Cyrillic_U ] };
key <AD04> { [ Cyrillic_ka, Cyrillic_KA ] };
key <AD05> { [ Cyrillic_ie, Cyrillic_IE] };
key <AD06> { [ Cyrillic_en, Cyrillic_EN ] };
key <AD07> { [ Cyrillic_ghe, Cyrillic_GHE ] };
key <AD08> { [ Cyrillic_sha, Cyrillic_SHA ] };
key <AD09> { [ Cyrillic_shcha, Cyrillic_SHCHA ] };
key <AD10> { [ Cyrillic_ze, Cyrillic_ZE ] };
key <AD11> { [ Cyrillic_ha, Cyrillic_HA ] };
key <AD12> { [ Cyrillic_hardsign, Cyrillic_HARDSIGN, Ukrainian_yi, Ukrainian_YI] };
key <AC01> { [ Cyrillic_ef, Cyrillic_EF ] };
key <AC02> { [ Cyrillic_yeru, Cyrillic_YERU, Ukrainian_i, Ukrainian_I] };
key <AC03> { [ Cyrillic_ve, Cyrillic_VE ] };
key <AC04> { [ Cyrillic_a, Cyrillic_A ] };
key <AC05> { [ Cyrillic_pe, Cyrillic_PE ] };
key <AC06> { [ Cyrillic_er, Cyrillic_ER ] };
key <AC07> { [ Cyrillic_o, Cyrillic_O ] };
key <AC08> { [ Cyrillic_el, Cyrillic_EL ] };
key <AC09> { [ Cyrillic_de, Cyrillic_DE ] };
key <AC10> { [ Cyrillic_zhe, Cyrillic_ZHE ] };
key <AC11> { [ Cyrillic_e, Cyrillic_E, Ukrainian_ie, Ukrainian_IE] };
key <AB01> { [ Cyrillic_ya, Cyrillic_YA ] };
key <AB02> { [ Cyrillic_che, Cyrillic_CHE ] };
key <AB03> { [ Cyrillic_es, Cyrillic_ES ] };
key <AB04> { [ Cyrillic_em, Cyrillic_EM ] };
key <AB05> { [ Cyrillic_i, Cyrillic_I] };
key <AB06> { [ Cyrillic_te, Cyrillic_TE ] };
key <AB07> { [ Cyrillic_softsign, Cyrillic_SOFTSIGN ] };
key <AB08> { [ Cyrillic_be, Cyrillic_BE ] };
key <AB09> { [ Cyrillic_yu, Cyrillic_YU ] };
include "kpdl(comma)"
};
Parting thoughts
Custom keyboard layouts for the win
Tweaking to your purposes something as fundamental as a keyboard layout is strangely empowering. And adapting to a new layout is like learning a foreign language - if you did it at least once in your life, the next ones are much easier. Especially if it’s small things like moving a key, or just adding more symbols to the existing layout.
Wouldn’t recommend it to everyone, though.
One thing I would recommend to everyone without exception is switching the Ctrl and Caps Lock keys. It can be easily done on any OS, including Linux, where too it can be done without editing any layout files12.
Interesting resources on topic
- X keyboard extension - ArchWiki (of course)
- Custom Keyboard in Linux/X11 | Daniel Paul O’Donnell (“As an early medieval English philologist, I make daily use of a number of characters that are not found on the standard US computer keyboard. These include characters and accented letters from various European languages (e.g. ß, é, ä, ø), IPA symbols (e.g. ŋ, ә, etc.), Middle English ȝ, and various letters and forms used in Old English (æ, þ, ċ, ġ, ƿ, ū, ȳ, etc.).”) - dated but very cool
- tonyaldon/keyboard-layout: keyboard-layout pools all the needed files to set up my custom XKB keyboard layout (takbl) on Linux Ubuntu.
- A simple, humble but comprehensive guide to XKB for linux | by Damiano Venturin | Medium
Thank you for reading!
-
Visualization done with the excellent Keyboard Layout Editor ↩︎
-
xcape: Linux utility to configure modifier keys to act as other keys when pressed and released on their own. ↩︎
-
Can be automated of course, but all tutorials I found gave me the impression it’s a worse can of worms than the one I already had, and I never tried. ↩︎
-
We set
<LALT>
as a one-level key, that is not affected by anything. (If a key can be changed only by Shift it’d be two-level, for example.) And we make it act as Level3 modifier, basically another kind of Shift, closer to AltGr originally (and still in countries like Germany).key <LALT> { type[Group1] = "ONE_LEVEL", symbols[Group1] = [ ISO_Level3_Shift ] };
Any keys that accept it have to also accept Shift and therefore have to be at least four-level. ↩︎ ↩︎
-
I don’t feel like doing the “(keycode, group, state) → keysym” thing in this post, it’s not meant to be a tutorial ↩︎
-
The beauty of two layouts instead of more can only be appreciated by someone who constantly had to switch between multiple ones. ↩︎
-
v6.cpp
was born from my wish to have syntax highlighting in vim, it being late and a.cpp
extension being the easiest way to get some adequate highlighting going. ↩︎ -
keyboard - How do I remap the Caps Lock and Ctrl keys? - Ask Ubuntu ↩︎