< expLog

Ergodox EZ Layout

I recently bought an Ergodox EZ shine to use at home, with blue switches. Clicky keys are definitely very satisfying to type with, though even I occasionally get annoyed by the sound.

As a long time Kinesis user, I've reconfigured the keyboard to work similarly to a Kinesis layout along with some quirks that I like having (I have an Escape key at the same place as the Ergodox does by default), all configured to run as Dvoark with the laptop also set to Dvorak — which is something that's more painful to set up in a Kinesis.

Anyways, you can see my layout here (originally downloaded from the automatic configuration tool on Ergodox's website, then modified)

#include "ergodox.h"
#include "debug.h"
#include "action_layer.h"
#include "version.h"

#include "keymap_german.h"
#include "keymap_nordic.h"

#include "keymap_dvorak.h"

enum custom_keycodes {
  PLACEHOLDER = SAFE_RANGE, // can always be here
  EPRM,
  VRSN,
  RGB_SLD,
};

const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {

  [0] = KEYMAP(
 DV_EQL    , KC_1        , KC_2      , KC_3      , KC_4      , KC_5    , KC_LEFT      ,
 KC_TAB    , KC_Q        , KC_W      , KC_E      , KC_R      , KC_T    , TG(1)        ,
 KC_LCTRL  , KC_A        , KC_S      , KC_D      , KC_F      , KC_G    ,
 KC_LSHIFT , CTL_T(KC_Z) , KC_X      , KC_C      , KC_V      , KC_B    , ALL_T(KC_NO) ,
 KC_ESCAPE , KC_GRAVE    , KC_BSLASH , KC_LEFT   , KC_RIGHT  ,
                                                   KC_LGUI   , KC_LALT ,
                                                               KC_HOME ,
                                       KC_BSPACE , KC_DELETE , KC_END  ,

 KC_RIGHT     , KC_6      , KC_7      , KC_8     , KC_9    , KC_0      , DV_MINS   ,
 TG(1)        , KC_Y      , KC_U      , KC_I     , KC_O    , KC_P      , DV_SLSH   ,
                KC_H      , KC_J      , KC_K     , KC_L    , KC_SCOLON , DV_BSLS   ,
 MEH_T(KC_NO) , KC_N      , KC_M      , KC_COMMA , KC_DOT  , KC_SLASH  , KC_LSHIFT ,
                            KC_UP     , KC_DOWN  , DV_LBRC , DV_RBRC   , MO(1)     ,
                KC_LALT   , KC_ESCAPE ,
                KC_PGUP   ,
                KC_PGDOWN , KC_ENTER  , KC_SPACE
),

  [1] = KEYMAP(
 M(0)           , KC_F1          , KC_F2          , KC_F3          , KC_F4          , KC_F5          , KC_TRANSPARENT ,
 KC_TRANSPARENT , KC_EXLM        , KC_AT          , KC_LCBR        , KC_RCBR        , KC_PIPE        , KC_TRANSPARENT ,
 KC_TRANSPARENT , KC_HASH        , KC_DLR         , KC_LPRN        , KC_RPRN        , KC_GRAVE       ,
 KC_TRANSPARENT , KC_PERC        , KC_CIRC        , KC_LBRACKET    , KC_RBRACKET    , KC_TILD        , KC_TRANSPARENT ,
 KC_TRANSPARENT , KC_TRANSPARENT , KC_TRANSPARENT , KC_TRANSPARENT , KC_TRANSPARENT ,
                                                                     RGB_MOD        , KC_TRANSPARENT ,
                                                                                      KC_TRANSPARENT ,
                                                    RGB_VAD        , RGB_VAI        , KC_TRANSPARENT ,

 KC_TRANSPARENT , KC_F6          , KC_F7          , KC_F8   , KC_F , KC_F10    , KC_F11         ,
 KC_TRANSPARENT , KC_UP          , KC_7           , KC_8    , KC_9 , KC_ASTR   , KC_F12         ,
                  KC_DOWN        , KC_4           , KC_5    , KC_6 , KC_PLUS   , KC_TRANSPARENT ,
 KC_TRANSPARENT , KC_AMPR        , KC_1           , KC_2    , KC_3 , KC_BSLASH , KC_TRANSPARENT ,
                                   KC_TRANSPARENT , KC_DOT  , KC_0 , KC_EQUAL  , KC_TRANSPARENT ,
                  RGB_TOG        , RGB_SLD        ,
                  KC_TRANSPARENT ,
                  KC_TRANSPARENT , RGB_HUD        , RGB_HUI

),

  [2] = KEYMAP(
 KC_TRANSPARENT , KC_TRANSPARENT , KC_TRANSPARENT , KC_TRANSPARENT , KC_TRANSPARENT , KC_TRANSPARENT , KC_TRANSPARENT ,
 KC_TRANSPARENT , KC_TRANSPARENT , KC_TRANSPARENT , KC_MS_UP       , KC_TRANSPARENT , KC_TRANSPARENT , KC_TRANSPARENT ,
 KC_TRANSPARENT , KC_TRANSPARENT , KC_MS_LEFT     , KC_MS_DOWN     , KC_MS_RIGHT    , KC_TRANSPARENT ,
 KC_TRANSPARENT , KC_TRANSPARENT , KC_TRANSPARENT , KC_TRANSPARENT , KC_TRANSPARENT , KC_TRANSPARENT , KC_TRANSPARENT ,
 KC_TRANSPARENT , KC_TRANSPARENT , KC_TRANSPARENT , KC_MS_BTN1     , KC_MS_BTN2     ,
                                                                     KC_TRANSPARENT , KC_TRANSPARENT ,
                                                                                      KC_TRANSPARENT ,
                                                    KC_TRANSPARENT , KC_TRANSPARENT , KC_TRANSPARENT ,

 KC_TRANSPARENT , KC_TRANSPARENT , KC_TRANSPARENT  , KC_TRANSPARENT      , KC_TRANSPARENT      , KC_TRANSPARENT , KC_TRANSPARENT      ,
 KC_TRANSPARENT , KC_TRANSPARENT , KC_TRANSPARENT  , KC_TRANSPARENT      , KC_TRANSPARENT      , KC_TRANSPARENT , KC_TRANSPARENT      ,
                  KC_TRANSPARENT , KC_TRANSPARENT  , KC_TRANSPARENT      , KC_TRANSPARENT      , KC_TRANSPARENT , KC_MEDIA_PLAY_PAUSE ,
 KC_TRANSPARENT , KC_TRANSPARENT , KC_TRANSPARENT  , KC_MEDIA_PREV_TRACK , KC_MEDIA_NEXT_TRACK , KC_TRANSPARENT , KC_TRANSPARENT      ,
                                   KC_AUDIO_VOL_UP , KC_AUDIO_VOL_DOWN   , KC_AUDIO_MUTE       , KC_TRANSPARENT , KC_TRANSPARENT      ,
                  KC_TRANSPARENT , KC_TRANSPARENT  ,
                  KC_TRANSPARENT ,
                  KC_TRANSPARENT , KC_TRANSPARENT  , KC_WWW_BACK

),

};

const uint16_t PROGMEM fn_actions[] = {
  [1] = ACTION_LAYER_TAP_TOGGLE(1)
};

// leaving this in place for compatibilty with old keymaps cloned and re-compiled.
const macro_t *action_get_macro(keyrecord_t *record, uint8_t id, uint8_t opt)
{
      switch(id) {
        case 0:
        if (record->event.pressed) {
          SEND_STRING (QMK_KEYBOARD "/" QMK_KEYMAP " @ " QMK_VERSION);
        }
        break;
      }
    return MACRO_NONE;
};

bool process_record_user(uint16_t keycode, keyrecord_t *record) {
  switch (keycode) {
    // dynamically generate these.
    case EPRM:
      if (record->event.pressed) {
        eeconfig_init();
      }
      return false;
      break;
    case VRSN:
      if (record->event.pressed) {
        SEND_STRING (QMK_KEYBOARD "/" QMK_KEYMAP " @ " QMK_VERSION);
      }
      return false;
      break;
    case RGB_SLD:
      if (record->event.pressed) {
        rgblight_mode(1);
      }
      return false;
      break;

  }
  return true;
}

void matrix_scan_user(void) {

    uint8_t layer = biton32(layer_state);

    ergodox_board_led_off();
    ergodox_right_led_1_off();
    ergodox_right_led_2_off();
    ergodox_right_led_3_off();
    switch (layer) {
        case 1:
            ergodox_right_led_1_on();
            break;
        case 2:
            ergodox_right_led_2_on();
            break;
        case 3:
            ergodox_right_led_3_on();
            break;
        case 4:
            ergodox_right_led_1_on();
            ergodox_right_led_2_on();
            break;
        case 5:
            ergodox_right_led_1_on();
            ergodox_right_led_3_on();
            break;
        case 6:
            ergodox_right_led_2_on();
            ergodox_right_led_3_on();
            break;
        case 7:
            ergodox_right_led_1_on();
            ergodox_right_led_2_on();
            ergodox_right_led_3_on();
            break;
        default:
            break;
    }

};

I also ended up writing a mildly interesting script to pretty print the layout, because I was fairly certain I didn't want to realign keys every time I modified any layer. You can grab that here:

#!/usr/local/bin/python3

import itertools
import re
import sys

row_len = 15
left_hand_layout = """
 + + + + + + +
 + + + + + + +
 + + + + + + .
 + + + + + + +
 + + + + + . .
 . . . . + + .
 . . . . . + .
 . . . + + + .
"""

right_hand_layout = ""
for row in left_hand_layout.split("\n"):
    right_hand_layout += row.ljust(row_len)[::-1] + "\n"

def tokenize(keys_string):
    tokens = []

    token = ""
    brackets = 0
    for c in keys_string:
        if c == "," and brackets == 0:
            tokens.append(token)
            token = ""
        else:
            if c == "(":
                brackets += 1
            elif c == ")":
                brackets -= 1
            token += c
    tokens.append(token)
    return [token.strip() for token in tokens]

def pretty_print(keys, layout):
    word_width = max(len(x) for x in keys) + 2
    replaced = layout.replace(".", "{{:^{}}}".format(word_width + 1).format(" "))
    replaced = replaced.replace("+", "{{:<{}}},".format(word_width))
    replaced = replaced.format(*keys)

    filled_columns = map(
        lambda x: [c == ' ' for c in x],
        replaced.strip().split('\n'))
    mask = list(map(lambda x: all(x), itertools.zip_longest(*filled_columns, fillvalue=True)))
    mask_str = ""
    for c in mask:
        mask_str += "+" if c else "-"

    # print()
    # print(mask_str)
    # print(replaced)

    result = ""
    for row in replaced.split('\n'):
        result += ''.join(map(lambda x: x[0], filter(lambda x: not x[1], zip(row, mask)))) + "\n"

    return result

def reformat(keys):
    left_covered = len(re.findall("\+", left_hand_layout))
    tokens = tokenize(keys)
    print(pretty_print(tokens[:left_covered], left_hand_layout).strip('\n'))
    print(pretty_print(tokens[left_covered:], right_hand_layout).strip('\n'))

def main():
    reformat(sys.stdin.read())

if __name__ == "__main__":
    main()

I generally apply it by using C-u M-| followed by prettyprint.py on the key layout to reformat the keys in emacs.