typing
This post is a summary of the tools that I'm fiddling with to tweak my keyboard layout every now and then. I recently changed from Dvorak to the less standard Workman Layout with a bit more exotic features that led me to look into options for implementing and customizing the layout.
OSX
For my Macbook, new layouts can be added by creating a bundle in
~/Library/Keyboard Layouts
. There is a Workman bundle with several layout
variations available at
deekayen/workman. I'm
using a variation of Workman Dead,
which trades the number of key presses against the distance
traveled. Additionally, you don't have to press a modifier key to get to the
symbols. Symbols that are usually available via the number row, are accessible
on home row after pressing the dead key. Pressing the dead key, in my case the
comma key, will remap several keys. It changes the layout from normal Workman:
to a more symbol focused one:
For example, the key {
is accessible by pressing ,s
in succession, rather
than shift
+ [
, the key (
is ,h
rather than shift
+ 9
and so on.
The symbol layer is different from the one included in the bundle at
deekayen/workman, more customized to my habits. For example, rather than
splitting parentheses across both hands, I moved them to the left hand. This
means that the right hand can stay on top of the dead key in case I want to
insert a pair. I also traded numbers for more symbols and added ~
close to
the dead key so that I can type ~/
conveniently in a rolling motion.
The customization is quite straight forward with Ukelele. You can open the
main bundle, select the layout you want to modify and enter the symbol layer
to modify just that part of it. Ukelele then updates the file that describes
the layout (*.keylayout
in Workman.bundle/Contents/Resources
). Much more
convenient than editing the XML by hand :)
For some specific key remapping on my Macbook I use Seil and Karabiner, rather than the layout. For example, capslock and return are both mapped to control when pressed in combination with another key, otherwise to their original meaning. The combination of Karabiner and Seil allows all sorts of remappings. For example, in the firmware version I mapped backslash to the capslock key, as I don't really use capslock. I use Seil to map capslock to backslash and Karabiner to map backslash to control when pressed in combination with another key.
You can do this for any other key combo: Identify the key's code via: Karabiner > Misc & Uninstall > Launch EventViewer and use Seil to map the capslock key to that code. Then add your own configuration to Karabiner, similar to the following:
<?xml version="1.0"?>
<root>
<item>
<name>Change backslash to left control key.</name>
<appendix>(Send an backslash key event when backslash key is pressed alone.)</appendix>
<identifier>private.backslash_to_control_escape</identifier>
<autogen>
__KeyOverlaidModifier__
<!-- from -->
KeyCode::BACKSLASH,
<!-- to -->
KeyCode::CONTROL_L,
<!-- alone -->
KeyCode::BACKSLASH,
</autogen>
</item>
</root>
The software support that OSX offers is quite convenient and switching between layouts is fast, in case someone else needs to type. But there are some shortcomings:
-
OSX defaults back to QWERTY when requesting an admin password or when logging out.
-
There are some issues with the symbol layers when using sites like keybr.com or typing.io for practicing. Not sure where the key presses are lost or whether they are translated incorrectly.
-
When pairing with other developers, I fall back to Qwerty as Workman is still quite niche and few systems support it out of the box.
ErgoDox
Hardware to the rescue! I bought an ErgoDox via Massdrop last year and this seemed like the perfect opportunity to learn about its firmware. Compiling my own firmware version addresses the above issues by "escaping" OSX and allowing me to just plug in a keyboard with Workman installed. There are multiple firmware implementations available and I just customized one to fit my needs. This means custom placement of modifier keys and also adding the dead key layer. I'm using Massdrop's configurator for the ErgoDox to get a visual representation of my setup:
This is just the first layer, the next layer is for symbols and the third is for a numpad on the right hand near home row, the full configuration is available here.
The configurator allows you to compile your own firmware version as well, but
currently there is no support for the dead key approach that I'm using. But
luckily benblazak/ergodox-firmware has support for sticky keys, which you can
use to implement the dead key approach. The project is well documented and
it's quite straight-forward to compile your own version on a Mac. The only
external dependency I had to download was the compiler as part of the AVR
MacPack. Change the layout in src/makefile-options
to your target and you're
good to go! Simply issue a make
in the src
sub-folder and then load the
resulting firmware.hex
with a Teensy Loader onto your ErgoDox.
To implement a Workman Dead version, I used the existing Colemak layout that makes use of the sticky keys functionality and adapted the keys to Workman. The layout definition is split across three function invocations, where each one looks similar to the following:
const uint8_t PROGMEM _kb_layout[KB_LAYERS][KB_ROWS][KB_COLUMNS] = {
// LAYER 0
KB_MATRIX_LAYER(
// unused
0,
// left hand
_esc, _1, _2, _3, _4, _5, _grave,
0, _Q, _D, _R, _W, _B, _tab,
_backslash, _A, _S, _H, _T, _G,
_shiftL, _Z, _X, _M, _C, _V, _guiL,
0, 0, 0, 0, _esc,
// left thumb block
0, 0,
0, 0, _altL,
_bs, 2, _ctrlL,
// right hand
_equal, _6, _7, _8, _9, _0, _esc,
_dash, _J, _F, _U, _P, _semicolon, 0,
_Y, _N, _E, _O, _I, _quote,
_guiR, _K, _L, 1, _period, _slash, _shiftR,
_arrowU, _arrowD, _arrowL, _arrowR, 0,
// right thumb block
0, 0,
_altR, 0, 0,
_ctrlR, _enter, _space
),
The above snippet is the definition for the first layer and defines the basic
Workman layout. The 0
's indicate an unused key while the ones with an
underscore reference a given key code. For example, _9
refers to the key code
that a regular USB keyboard emits when you press the 9
key (I'm using the
short name, the full name is more descriptive: KEY_9_LeftParenthesis
).
To implement the symbol layer, I use the comma key as a sticky key, wish
activates a second layer (for one key press). The definition of the sticky
keys is straight-forward: You indicate the layer number on the normal layout
(the 1
in comma position and 2
on the left thumb block in the basic layout
above).
In addition to the call to _kb_layout
, you manage the specific behavior for
press & release by passing mappings to _kb_layout_press
and
_kb_layout_release
. These mappings are analogous to the one passed to
_kb_layout
, but instead of key codes you add references to functions. For the
activation of the symbol layer, you add references to lsticky1
or lsticky2
in
the press & and release mappings.
Not all symbols are accessible without modifiers on a regular keyboard, but one goal of the dead key approach is to get rid of the modifier. For this to work, you can supply a modifier-specific function in the press & release mapping. For example, this is just the layout for the left hand for the symbol layer:
0, 0, 0, 0, 0, 0, 0,
0, _bracketR, _bracketR, _0, _add_kp, _2, 0,
0, _bracketL, _bracketL, _9, _equal_kp, _5,
0, _comma, _period, _backslash, _dash, _dash, 0,
0, 0, 0, 0, 0,
To produce {}
rather than []
in the third column, the press & release
mappings contain calls to kbfun_shift_press_release
(aka sshprre
) rather than
the normal kbfun_press_release
(aka kprrel
):
ktrans, ktrans, ktrans, ktrans, ktrans, ktrans, ktrans,
ktrans, kprrel, sshprre, sshprre, sshprre, sshprre, ktrans,
ktrans, kprrel, sshprre, sshprre, sshprre, sshprre,
ktrans, sshprre, sshprre, sshprre, kprrel, sshprre, ktrans,
ktrans, ktrans, ktrans, ktrans, ktrans,
The tedious bit is to keep the corresponding calls to _kb_layout_press
and
_kb_layout_release
for each layer in sync. Otherwise you might see modifier
keys that remain pressed for no apparent reason. For example, if the press &
release mappings contain different function references, one to kprrel
and the
other to sshprre
, then the shift modifier would not get released properly.
Keeping the different invocations in sync is a bit tedious as all information
is passed in a single call where position defines the meaning of a value and
you only get arity compiler warnings. But your favorite editor might be able
to help you with that ;)
For reference my full layout is available here.