Gavin Sinclair, January 2022
I use Karabiner (configured with Gosu) to make advanced key mappings on my Apple computer. Karabiner allows you to create “layers”, perhaps simulating those on a programmable mechanical keyboard. I make good use of these layers to give me easy access (home-row or nearby) to all symbols and navigational controls, and even a numpad.
The motivation is to keep hand movement to a minimum. Decades of coding on standard keyboards has unfortunately left me with hand and wrist pain. I will soon enough own a small split keyboard which will force me to use layers to access symbols etc., so this Karabiner solution, which has evolved over months, is a training run for that.
What is a layer? A simple example is that when I hold down f I get access to nearly all delimeter pairs: ( ) [ ] { }
. The key idea is that I activate a layer with my left hand and select the character with my right hand.
Note that this is all taking place on a QWERTY keyboard, but it’s the key positions that are important, not the letters.
This document exists to share the idea and the details for others who are interested. Share at will.
My layer keys are all easily accessible with the left hand:
w e r
a s d f
c v
The selection keys are easily accessible with the right hand, in most cases using only the main three fingers:
u i o
(h) j k l
m , .
The a layer provides arrow movement with hjkl. It also gives access to Tab, Enter and Page Down/Up. This ASCII diagram attempts to demonstrate the a layer.
U I Tab Tab
H J K L Left Down Up Right
M , . Enter P-Dn P-Up
Often, though, we want to move a word at a time, or to the beginning or end of the line. The r-e-w layers provide range of motion for three operations:
- delete (r)
- move (e)
- select (w)
The target keys here are laid out below to emphasise their position:
U I < >
H J K L <<- <- -> ->>
Just to be clear, this is the meaning of the symbols above:
< left one character
<- left one word
<<- beginning of line
So with the correct combination of left-hand and right-hand I can achieve any normal motion, selection or deletion. This entirely replaces Option-Left and Command-Right and Shift-Option-Left, etc.
For me, the most common operations are delete character left and delete word left. Moving left and right a word at a time is also common. Moving or deleting to the beginning or ending of the line are somewhat common. All of these are easily done with my layer design and I much prefer not having to press Command or Option to achieve this simple things, and I definitely do not want to move my right hand to an arrow cluster for this sort of thing ever again!
Selecting text is something I don’t find myself doing often, but it is easy to do because it follows the same right-hand logic as the other operations (moving and deleting).
To bring numbers to the home row I wanted to make a number pad. To bring all symbols to the home row several layers are required. To have any hope of memorising them all some thought is needed to logical groupings. I’m reasonably happy with the choices I’ve settled on, but other people could make equally valid decisions.
Broadly speaking the layers are designed as follows, in descending order of left-hand friendliness
- delimiters (f) [plus a few other symbols]
- arithmetic symbols (d)
- punctuation symbols (s)
- number pad (v)
- remaining symbols (c)
In every case priority is given to right-hand comfort by using the keys:
U I O
J K L
M , .
Access to delimiters is super important for a programmer, so I placed this under the most convenient key (f).
{ } !
( ) ?
[ ] $
For a long time there were only six symbols in this layout, but ultimately the logic of making more symbols accessible through the convenient f key won out. Initially I used it for “leftover” symbols, but eventually I realised that having frequently-used symbols here made the most sense.
The hash sign snuck in here because one meaning of it is “number”.
< > #
+ - =
* / %
Quotes are on the top row because they are typographically up high. Comma and period are on the best two fingers because they are so common. Semicolon and colon line up nicely with comma and period. Backtick is a quote, of sorts, so it goes with the other quotes. Ampersand and tilde have no logical reason to be where they are specifically, but ampersand is reasonably common so it’s good to be in a comfortable position.
' " `
, . &
; : ~
The main nine positions here are naturally the numbers 1–9, but a number pad also needs to contain zero and period, the places for which I settled on after som experimentation. It is also handy to have hyphen and backspace, and I included plus and Enter because a normal number pad has them.
BS 7 8 9 +
. 4 5 6 -
0 1 2 3 Enter
Note the way that three of these symbols form a visually logical layout, which helps greatly in remembering them.
^
| @
\ _
This layout has evolved over time until quite recently, and so muscle memory for the various symbols does vary quite a lot. I am fluent with navigation, delimiters, and to some extent arithmetic. I have to think about punctuation, unfortunately. Number pad usage is OK, and will be better when I have a column-aligned keyboard in the future.
I have a handwritten aide-memoire on hand at all times to help me find the right key. And I am thinking of writing a simple command-line app that gives typing practice with symbols, introducing a couple at a time or something.
The biggest difficulty with all this is that the layers are software layers and are very susceptible to mis-timings. That is, I frequently get (say) dl
on the screen instead of =
. This is rather annoying, to say the least, and I hope that when I have a programmable keyboard in future (with hopefully a better implementation of layers) then this situation will be improved. The sad truth is that you can’t just hold down a layer key and take your time selecting the right-hand key. Holding down f for too long (half a second, say), will simply dump an f
on the screen. There are configurations you can make with Karabiner, but frankly I don’t understand them.
But despite this, it is worth it. My typing is pitifully slow and cumbersome (when symbols are involved) and that is very frustrating, but my hands are more comfortable, and I know that the overall situation will improve over time.
Karabiner uses a JSON file for its configuration, but that is extremely unwieldy. Goku (brew install yqrashawn/goku/goku
) is a far better option: it uses a file format called EDN, and when you run goku
it converts that to JSON for you. You can look at basic examples elsewhere. My configuration is below.
{
:devices {
:sculpt-keyboard [{:product_id 1957 :vendor_id 1118}]
}
:simlayers {
:f-mode {:key :f} ; delimeters ( ) [ ] { } and other symbols ~ $ &
:d-mode {:key :d} ; arithmetic + - * / = % < > #
:s-mode {:key :s} ; punctuation ? ! : ; ' " ` ~
:a-mode {:key :a} ; navigation hjkl + tab + enter + page down/up
;
:q-mode {:key :q} ; General shortcuts (browser etc.) - not settled
:w-mode {:key :w} ; Selection left and right (letter, word, line)
:e-mode {:key :e} ; Movement left and right (letter, word, line)
:r-mode {:key :r} ; Deletion left and right (letter, word, line)
;
:g-mode {:key :g} ; Mouse scroll, desktop left-right, zoom in-out, screenshot (not implemented)
;
:v-mode {:key :v} ; Number pad with + - BS ENTER as well
:c-mode {:key :c} ; Slashes and lines ^ | \ _ @
:x-mode {:key :x} ; Some multi-character shortcuts like <= (not implemented)
}
:main [
{:des "Swap Win and Alt on Sculpt keyboard"
:rules [:sculpt-keyboard
[:left_option :left_command]
[:left_command :left_option]
[:right_option :right_command]
[:application :right_option]
]
}
{:des "CAPSLOCK is CTRL if pressed in combination, otherwise ESC"
:rules [
[:##caps_lock :left_control nil {:alone :escape}]
]}
{:des "f-mode for delimeters and ! ? $"
:rules [:f-mode
;; u i j k m comma -> !Sopen_bracket !Sclose_bracket !S9 !S0 open_bracket close_bracket
[:##u :!Sopen_bracket]
[:##i :!Sclose_bracket]
[:##j :!S9]
[:##k :!S0]
[:##m :open_bracket]
[:##comma :close_bracket]
;; o l period -> !S1 !Sslash !S4
[:##o :!S1]
[:##l :!Sslash]
[:##period :!S4]
]
}
{:des "d-mode for arithmetic" ;; < > # + - = * / %
:rules [:d-mode
[:##u :!Scomma] ; d -> o <
[:##i :!Speriod] ; d -> p >
[:##o :!S3] ; d -> o #
[:##j :!Sequal_sign] ; d -> j +
[:##k :hyphen] ; d -> k -
[:##l :equal_sign] ; d -> l =
[:##m :!S8] ; d -> m *
[:##comma :slash] ; d -> , /
[:##period :!S5] ; d -> . %
]
}
{:des "s-mode for punctuation" ;; ' " ` , . & ; : ~
:rules [:s-mode
[:##u :quote]
[:##i :!Squote]
[:##o :grave_accent_and_tilde]
[:##j :comma]
[:##k :period]
[:##l :!S7]
[:##m :semicolon]
[:##comma :!Ssemicolon]
[:##period :!Sgrave_accent_and_tilde]
]
}
{:des "a-mode for hjkl movement and nm enter and ui tab and ,. PageDn/Up"
:rules [:a-mode
[:##h :left_arrow]
[:##j :down_arrow]
[:##k :up_arrow]
[:##l :right_arrow]
[:##n :return_or_enter]
[:##m :return_or_enter]
[:##u :tab]
[:##i :tab]
[:comma :page_down]
[:period :page_up]
]
}
{:des "r-mode for deleting characters with ui, words with jk and lines with hl"
:rules [:r-mode
[:##u :delete_or_backspace] ; r -> j Delete word backwards
[:##i :delete_forward] ; r -> j Delete word backwards
[:##j :!Odelete_or_backspace] ; r -> j Delete word backwards
[:##k :!Odelete_forward] ; r -> k Delete word forwards
[:##h :!Cdelete_or_backspace] ; r -> h Delete to beginning of line
[:##l :!Cdelete_forward] ; r -> l Delete to end of line
]
}
{:des "e-mode allows for easy back and forth one character, word or line"
:rules [:e-mode
[:##u :left_arrow] ; e -> u Left
[:##i :right_arrow] ; e -> i Right
[:##j :!Oleft_arrow] ; e -> j Opt+Left
[:##k :!Oright_arrow] ; e -> k Opt+Right
[:##h :!Cleft_arrow] ; e -> h Cmd+Left
[:##l :!Cright_arrow] ; e -> l Cmd+Right
[:n :return_or_enter] ; e -> n Enter
[:m :return_or_enter] ; e -> m Enter
]
}
{:des "w-mode = e-mode + SHIFT (i.e. selection, not just movement)"
:rules [:w-mode
[:##u :!Sleft_arrow] ; e -> u Shift+Left
[:##i :!Sright_arrow] ; e -> i Shift+Right
[:##j :!SOleft_arrow] ; e -> j Shift+Opt+Left
[:##k :!SOright_arrow] ; e -> k Shift+Opt+Right
[:##h :!SCleft_arrow] ; e -> h Shift+Cmd+Left
[:##l :!SCright_arrow] ; e -> l Shift+Cmd+Right
]
}
{:des "q-mode for general shortcuts like browser tab navigation"
:rules [:q-mode
[:##j :!CSopen_bracket] ; q -> j tab to the left: Cmd-{
[:##k :!CSclose_bracket] ; q -> k tab to the right: Cmd-}
[:##l :!TCf ] ; q -> l toggle full screen: ^⌘F
[:##u :!Cclose_bracket] ; q -> u browser back: Cmd-[
[:##i :!Cclose_bracket] ; q -> i browser forward: Cmd-]
[:##o :f2 ] ; q -> o F2 (useful in Excel)
[:##p :f4 ] ; q -> p F4 (useful in Excel)
]
}
{:des "v-mode for number pad"
:rules [:v-mode
[:u :7]
[:i :8]
[:o :9]
[:j :4]
[:k :5]
[:l :6]
[:m :1]
[:comma :2]
[:period :3]
[:p :!Sequal_sign]
[:semicolon :hyphen]
[:slash :return_or_enter]
[:y :delete_or_backspace]
[:h :period]
[:n :0]
]
}
{:des "c-mode for remaining symbols ^ | \\ _ @"
:rules [:c-mode
[:##u :!S6]
[:##j :!Sbackslash]
[:##k :!S2]
[:##m :backslash]
[:##comma :!Shyphen]
]
}
#_{:des "x-mode for some programming pairs like <= (not yet implemented)"
:rules [:x-mode
]
}
#_{:des "g-mode for mouse scroll, desktop left-right, zoom in-out, screenshot"
:rules [:g-mode
]
}
{:des "Forward slash is an easier right-shift (if combined)"
:rules [
[:slash :left_shift nil {:alone :slash}]
]}
;; Using keys for CTRL etc (home-row-mods) isn't practical with plain Karabiner.
;; Some changes to timeout settings would be required, and the documentation is
;; not clear enough.
#_{:des "Convenient CTRL (T,Y) and COMMAND (G,H)"
:rules [
[:##t :left_control nil {:alone :t}]
[:##y :left_control nil {:alone :y}]
[:##g :left_command nil {:alone :g}]
[:##h :left_command nil {:alone :h}]
]}
]
}
This is pure Gold! I am happy today as if I had found the treasure of my life.
As a programmer, I appreciate your hard work and enthusiasm for productivity.
I am also in hunting for a mechanical keyboard (Corn-ish Zen, Cantor) , kindly update your choise as well.