Skip to content

Instantly share code, notes, and snippets.

@gsinclair
Last active November 13, 2024 13:14
Show Gist options
  • Save gsinclair/f4ab34da53034374eb6164698a0a8ace to your computer and use it in GitHub Desktop.
Save gsinclair/f4ab34da53034374eb6164698a0a8ace to your computer and use it in GitHub Desktop.

Karabiner layouts for symbols and navigation

Gavin Sinclair, January 2022

Introduction

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.

An overview of the layers

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   ,   .

Navigation and editing

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).

Symbols and numbers

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  ,  .

Delimiters (f)

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.

Arithmetic symbols (d)

The hash sign snuck in here because one meaning of it is “number”.

<   >   #
+   -   =
*   /   %

Punctuation symbols (s)

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.

'   "   `
,   .   &
;   :   ~

Number pad (v)

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

Remaining symbols (c)

Note the way that three of these symbols form a visually logical layout, which helps greatly in remembering them.

^
|   @
\   _

Experience report

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.

The configuration

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}]
          ]}

 ]
}

@farzadmf
Copy link

farzadmf commented Sep 4, 2024

Question for experts:

I'm using the :devices section and configuring the key [change] bindings as shown here, but it doesn't seem to be working unless I do it in Karabiner UI.

:devices {
  :razor-black-widow [{:product_id 521 :vendor_id 5426}]
}
{
  :main [
    {
      :des "[GOKU] keyboard change"
      :rules [
        :razor-black-widow
        [:left_command :left_control]
        [:left_control :left_command]
      ]
    }
    ; ... other modifications ...
  ]
}

EDIT: not sure if it's significant or not, but I see two instances of my device in Karabiner

image link (Github doesn't allow me to upload an image for some reason!)

UPDATE: I think it is working, but not the way I expect/want it. I basically want to swap CTRL and CMD keys, but:

  • If I do it like this, it seems like other applications (SKHD, Hammerspoon, etc.) will still see the non-swapped keys.
  • If I do it through the UI, it seems "global" and everyone sees it the same

So, I ended up doing it through the UI

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment