Building a Dactyl Manuform Keyboard

Another 3D printed 4x6 dactly manuform. The goal of this post is really just to have a public place I can look up the layout. I really like the layout, each finger is at most one button away from the home row, while there's a bit of learning curve it's much easier to touch type as you only need to learn to stretch one finger from the home row.

At the moment I use 6 layers to get the most out of the limited keys along with tap dance to support some of the key combinations I need for the apps I use.

The paint job is meant to be messy to hide some of the imperfections in the 3D printed case and everything is hand-wired. If I was to build again I think I would use a bit more of tented angle and move the inner thumb keys in just a little as they are a bit of stretch but I'm pretty happy with how this turned out, it's been my daily driver for the past 6 months or so.

#include QMK_KEYBOARD_H
#include "print.h"

extern uint8_t is_master;

#define _QWERTY 0
#define _MSE 1
#define _NUM 2
#define _FUNC 3
#define _NAV 4
#define _ADJ 5

enum custom_keycodes {
QWERTY = SAFE_RANGE,          
NUMBER,
FUNCTION,
ADJUST,
NAVIGATION,
MOUSE,
ALT_TAB,
CTRL_F,
CTRL_LFT,
CTRL_RGT
};

// Fillers to make layering more clear

#define _______ KC_TRNS

#define CTL_TAB LCTL_T(KC_TAB)
#define LCA_TAB LCA_T(KC_TAB)
#define LSHFT KC_LSHIFT
#define RSHFT KC_RSHIFT
#define TT_FUNC TT(_FUNC)
#define TT_NUM TT(_NUM)
#define ALT_BKS LALT_T(KC_BSPC)
#define FUNC_ENT LT(_FUNC, KC_ENT)
#define NUM_SPC LT(_NUM, KC_SPACE)
#define FUNC_LFT LT(_FUNC, KC_LEFT)
#define TO_QWTY TO(_QWERTY)
#define TO_NUM TO(_NUM)
#define TO_FUNC TO(_FUNC)
#define MO_FUNC MO(_FUNC)
#define TO_NAV TO(_NAV)
#define TO_MSE TO(_MSE)
#define TO_ADJ TO(_ADJ)
#define MO_MSE LT(_MSE, KC_BSPC)

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

/* Base (qwerty)
* +-----------------------------------------+ +-----------------------------------------+
* | esc | q | w | e | r | t | | y | u | i | o | p | del |
* |------+------+------+------+------+------| |------+------+------+------+------+------|
* | TAB | a | s | d | f | g | | h | j | k | l | ; | \" |
* |------+------+------+------+------+------| |------+------+------+------+------+------|
* | SHFT | z | x | c | v | b | | n | m | , | . | / | SHFT |
* +------+------+------+------+-------------+ +-------------+------+------+------+------+
* | | | | | | | |
* +------+-------------+ +-------------+------+
* Note we always use left handmodifiers reading the documentation if you want to use dual function keys it will cause all modifies
* specified to be right handed and it is not possible to mix and match
*/

[_QWERTY] = LAYOUT( \
KC_ESC , KC_Q , KC_W , KC_E , KC_R , KC_T , KC_Y , KC_U , KC_I , KC_O , KC_P , KC_BSPC , \
CTL_TAB , KC_A , KC_S , KC_D , KC_F , KC_G , KC_H , KC_J , KC_K , KC_L , KC_SCLN , KC_QUOT , \
LSHFT , KC_Z , KC_X , KC_C , KC_V , KC_B , KC_N , KC_M , KC_COMM , KC_DOT , KC_SLSH , RSHFT , \
TT_FUNC , TT_NUM , MO_MSE , FUNC_ENT, NUM_SPC , KC_LALT \
),

[_MSE] = LAYOUT(
TO_QWTY , _______ , _______ , KC_WH_U , _______ , _______ , _______ , _______ , KC_INS , KC_SLCK , _______ , TO_ADJ , \
_______ , _______ , KC_BTN2 , KC_BTN3 , KC_BTN1 , _______ , _______ , KC_MS_L , KC_MS_U , KC_MS_R , _______ , _______ , \
ALT_TAB , _______ , KC_BTN4 , KC_WH_D , KC_BTN5 , KC_NUMLOCK , _______ , _______ , KC_MS_D , _______ , _______ , KC_LALT , \
_______ , _______ , _______ , TO_FUNC , TO_NUM , TO_NAV \
),

[_NUM] = LAYOUT(
KC_GRV , KC_1 , KC_2 , KC_3 , KC_4 , KC_5 , KC_6 , KC_7 , KC_8 , KC_9 , KC_0 , KC_DEL , \
CTL_TAB , FUNC_LFT, KC_UP , KC_RIGHT, TO_MSE , KC_LGUI , KC_MINS , KC_EQL , KC_LCBR , KC_RCBR , KC_PIPE , KC_DOT , \
LSHFT , FUNC_LFT, KC_DOWN , KC_RIGHT, KC_SPC , KC_APP , KC_UNDS , KC_PLUS , KC_LBRC , KC_RBRC , KC_BSLS , RSHFT , \
FUNCTION, NUMBER , TO_QWTY , KC_ENT , KC_SPC , KC_LALT \
),

[_FUNC] = LAYOUT(
KC_GRV , KC_EXLM , KC_AT , KC_HASH , KC_DLR , KC_PERC , KC_CIRC , KC_AMPR , KC_ASTR , KC_LPRN , KC_RPRN , _______ , \
_______ , KC_F1 , KC_F2 , KC_F3 , KC_F4 , KC_F5 , KC_F6 , KC_F7 , KC_F8 , KC_F9 , KC_F10 , KC_BSLS , \
_______ , KC_F11 , KC_F12 , KC_LEFT , KC_RIGHT, KC_MENU , KC_HOME , KC_END , KC_PGUP , KC_PGDN , _______ , _______ , \
FUNCTION, NUMBER , TO_QWTY , _______ , _______ , _______ \
),

[_NAV] = LAYOUT(
TO_QWTY , _______ , CTRL_RGT, _______ , _______ , _______ , _______ , _______ , _______ , _______ , _______ , _______ , \
_______ , _______ , _______ , _______ , _______ , _______ , KC_LEFT , KC_DOWN , KC_UP , KC_RIGHT, _______ , _______ , \
_______ , _______ , _______ , KC_C , KC_V , CTRL_LFT, KC_HOME , KC_END , KC_PGUP , KC_PGDN , CTRL_F , _______ , \
KC_LCTL , _______ , TO_QWTY , _______ , _______ , _______ \
),

[_ADJ] = LAYOUT(
RESET , _______ , _______ , _______ , _______ , _______ , _______ , _______ , KC_INS , KC_SLCK , _______ , TO_QWTY , \
KC_CAPS , _______ , _______ , _______ , _______ , _______ , _______ , _______ , _______ , KC_L , _______ , _______ , \
_______ , _______ , _______ , _______ , _______ , _______ , _______ , _______ , _______ , _______ , _______ , KC_LGUI , \
FUNCTION, NUMBER , _______ , _______ , _______ , _______ \
)
};


bool is_alt_tab_active = false;
uint16_t alt_tab_timer = 0;

void persistent_default_layer_set(uint16_t default_layer) {
eeconfig_update_default_layer(default_layer);
default_layer_set(default_layer);
}

void matrix_scan_user(void) {
if (is_alt_tab_active) {
if (timer_elapsed(alt_tab_timer) > 800) {
unregister_code(KC_LALT);
is_alt_tab_active = false;
}
}
}

// Debug which layer we're in
layer_state_t layer_state_set_user(layer_state_t state) {
switch(get_highest_layer(state)) {
case _QWERTY:
print("Qwerty Layer\n");
break;
case _MSE:
print("Mouse Layer\n");
break;
case _NUM:
print("Number Layer\n");
break;
case _FUNC:
print("Function Layer\n");
break;
case _NAV:
print("Navigation Layer\n");
break;
case _ADJ:
print("Adjustment Layer\n");
break;
}
return state;
}



bool process_record_user(uint16_t keycode, keyrecord_t *record) {
switch (keycode) {
case QWERTY:
if (record->event.pressed) {
persistent_default_layer_set(1UL<<_QWERTY);
}
return false;
case NUMBER:
if (record->event.pressed) {
layer_on(_NUM);
update_tri_layer(_NUM, _FUNC, _ADJ);
} else {
layer_off(_NUM);
update_tri_layer(_NUM, _FUNC, _ADJ);
}
case FUNCTION:
if (record->event.pressed) {
layer_on(_FUNC);
update_tri_layer(_NUM, _FUNC, _ADJ);
} else {
layer_off(_FUNC);
update_tri_layer(_NUM, _FUNC, _ADJ);
}
case NAVIGATION:
if (record->event.pressed) {
layer_on(_NAV);
} else {
persistent_default_layer_set(1UL<<_QWERTY);
}
return false;
case ALT_TAB:
if (record->event.pressed) {
if (!is_alt_tab_active) {
is_alt_tab_active = true;
register_code(KC_LALT);
}
alt_tab_timer = timer_read();
register_code(KC_TAB);
} else {
unregister_code(KC_TAB);
}
return true;
case CTRL_F:
if (record->event.pressed) {
SEND_STRING(SS_LCTL("f"));
layer_clear();
}
break;
case CTRL_LFT:
if (record->event.pressed) {
SEND_STRING( SS_DOWN(X_LCTRL) SS_TAP(X_LEFT) SS_UP(X_LCTRL) );
}
break;
case CTRL_RGT:
if (record->event.pressed) {
SEND_STRING( SS_DOWN(X_LCTRL) SS_TAP(X_RIGHT) SS_UP(X_LCTRL) );
}
break;
}
return true;
}