[Keymap] major keymap overhaul (#10185)

* experiment with userspace

* reorganise

* readme

* missing oneshot shift from ignored keys

* recombine hands in layout macro
This commit is contained in:
Callum Oakley 2020-09-09 23:37:34 +01:00 committed by GitHub
parent 6e948feb6a
commit 3d4f0028d6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 382 additions and 297 deletions

View file

@ -1,260 +0,0 @@
#include "planck.h"
#include "action_layer.h"
#define a KC_A
#define b KC_B
#define c KC_C
#define d KC_D
#define e KC_E
#define f KC_F
#define g KC_G
#define h KC_H
#define i KC_I
#define j KC_J
#define k KC_K
#define l KC_L
#define m KC_M
#define n KC_N
#define o KC_O
#define p KC_P
#define q KC_Q
#define r KC_R
#define s KC_S
#define t KC_T
#define u KC_U
#define v KC_V
#define w KC_W
#define x KC_X
#define y KC_Y
#define z KC_Z
#define lalt KC_LALT
#define lctl KC_LCTL
#define lsft KC_LSFT
#define ralt KC_RALT
#define rctl KC_RCTL
#define rsft KC_RSFT
#define n0 KC_0
#define n1 KC_1
#define n2 KC_2
#define n3 KC_3
#define n4 KC_4
#define n5 KC_5
#define n6 KC_6
#define n7 KC_7
#define n8 KC_8
#define n9 KC_9
#define ampr KC_AMPR
#define astr KC_ASTR
#define at KC_AT
#define bsls KC_BSLS
#define bspc KC_BSPC
#define caps KC_CAPS
#define circ KC_CIRC
#define comm KC_COMM
#define dash A(KC_MINS) // en-dash (); or with shift: em-dash (—)
#define del KC_DEL
#define dlr KC_DLR
#define dot KC_DOT
#define ent KC_ENT
#define eql KC_EQL
#define esc KC_ESC
#define exlm KC_EXLM
#define grv KC_GRV
#define hash KC_HASH
#define lbrc KC_LBRC
#define lcbr KC_LCBR
#define lprn KC_LPRN
#define mins KC_MINS
#define perc KC_PERC
#define pipe KC_PIPE
#define plus KC_PLUS
#define quot KC_QUOT
#define rbrc KC_RBRC
#define rcbr KC_RCBR
#define rprn KC_RPRN
#define scln KC_SCLN
#define slsh KC_SLSH
#define spc KC_SPC
#define tab KC_TAB
#define tild KC_TILD
#define down KC_DOWN
#define home G(KC_LEFT)
#define end G(KC_RGHT)
#define up KC_UP
#define pgdn KC_PGDN
#define pgup KC_PGUP
#define left KC_LEFT
#define rght KC_RGHT
#define tabl G(S(KC_LBRC))
#define tabr G(S(KC_RBRC))
#define fwd G(KC_RBRC)
#define back G(KC_LBRC)
#define slup S(A(KC_UP)) // Previous unread in Slack
#define sldn S(A(KC_DOWN)) // Next unread in Slack
#define ctl1 C(KC_1) // Desktop 1 (6 with shift)
#define ctl2 C(KC_2) // Desktop 2 (7 with shift)
#define ctl3 C(KC_3) // Desktop 3 (8 with shift)
#define ctl4 C(KC_4) // Desktop 4 (9 with shift)
#define ctl5 C(KC_5) // Desktop 5 (10 with shift)
#define ctl6 C(KC_6) // Screenshot
#define ctl7 C(KC_7) // Brightness up
#define ctl8 C(KC_8) // Brightness down
#define f1 KC_F1
#define f2 KC_F2
#define f3 KC_F3
#define f4 KC_F4
#define f5 KC_F5
#define f6 KC_F6
#define f7 KC_F7
#define f8 KC_F8
#define f9 KC_F9
#define f10 KC_F10
#define f11 KC_F11
#define f12 KC_F12
#define f13 KC_F13
#define f14 KC_F14
#define f15 KC_F15
#define f16 KC_F16
#define f17 KC_F17
#define f18 KC_F18
#define f19 KC_F19
#define f20 KC_F20
#define mute KC_MUTE
#define next KC_MNXT
#define play KC_MPLY
#define prev KC_MPRV
#define vold KC_VOLD
#define volu KC_VOLU
#define symb MO(SYMB)
#define move MO(MOVE)
#define func MO(FUNC)
#define rset RESET
#define powr KC_POWER
#define ____ KC_TRNS
#define xxxx KC_NO
extern keymap_config_t keymap_config;
enum planck_layers {
BASE,
SYMB,
MOVE,
FUNC,
};
enum planck_keycodes {
// Curly quotes
lcqt = SAFE_RANGE,
rcqt,
// "Smart" mods
cmd,
};
const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
[BASE] = LAYOUT_planck_grid(
tab, q, w, f, p, g, j, l, u, y, scln, mins,
bspc, a, r, s, t, d, h, n, e, i, o, quot,
lsft, z, x, c, v, b, k, m, comm, dot, slsh, rsft,
func, lctl, lalt, cmd, move, ent, spc, symb, cmd, ralt, rctl, func
),
[SYMB] = LAYOUT_planck_grid(
esc, n7, n5, n3, n1, n9, n8, n0, n2, n4, n6, dash,
lcqt, at, dlr, eql, lprn, lbrc, rbrc, rprn, astr, hash, plus, rcqt,
____, grv, pipe, bsls, lcbr, tild, circ, rcbr, ampr, exlm, perc, ____,
____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____
),
[MOVE] = LAYOUT_planck_grid(
esc, ctl1, ctl2, ctl3, ctl4, ctl5, ctl6, home, up, end, xxxx, xxxx,
del, play, volu, tabl, tabr, slup, ctl7, left, down, rght, caps, xxxx,
____, mute, vold, back, fwd, sldn, ctl8, pgdn, pgup, xxxx, xxxx, ____,
____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____
),
[FUNC] = LAYOUT_planck_grid(
rset, f7, f5, f3, f1, f9, f8, f10, f2, f4, f6, xxxx,
xxxx, f17, f15, f13, f11, f19, f18, f20, f12, f14, f16, xxxx,
____, xxxx, xxxx, xxxx, xxxx, xxxx, xxxx, xxxx, xxxx, xxxx, xxxx, ____,
____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____
),
};
bool send_string_if_keydown(
keyrecord_t *record,
const char *unshifted,
const char *shifted) {
if (record->event.pressed) {
if (shifted) {
uint8_t shifts = get_mods() & MOD_MASK_SHIFT;
if (shifts) {
del_mods(shifts);
send_string(shifted);
add_mods(shifts);
} else {
send_string(unshifted);
}
} else {
send_string(unshifted);
}
}
return true;
}
// Holding both cmd keys will instead register as cmd + ctl
bool smart_cmd(keyrecord_t *record) {
static int cmd_keys_down = 0;
if (record->event.pressed) {
if (cmd_keys_down == 0) {
register_code(KC_LCMD);
} else {
register_code(KC_LCTL);
}
cmd_keys_down++;
} else {
if (cmd_keys_down == 1) {
unregister_code(KC_LCMD);
} else {
unregister_code(KC_LCTL);
}
cmd_keys_down--;
}
return true;
}
bool process_record_user(uint16_t keycode, keyrecord_t *record) {
switch (keycode) {
// The macOS shortcuts for curly quotes are horrible, so this rebinds
// them so that shift toggles singledouble instead of leftright, and
// then both varieties of left quote can share one key, and both
// varieties of right quote share another.
case lcqt:
return send_string_if_keydown(
record,
SS_LALT("]"), // left single quote ()
SS_LALT("[")); // left double quote (“)
case rcqt:
return send_string_if_keydown(
record,
SS_LALT(SS_LSFT("]")), // right single quote ()
SS_LALT(SS_LSFT("["))); // right double quote (”)
// cmd + cmd -> cmd + ctl
case cmd:
return smart_cmd(record);
}
return true;
}

View file

@ -1,30 +0,0 @@
# callums planck layout
This is a layout for the grid planck, built with a few ideals in mind:
- Consistent and minimal response times should be maintained. Keys that react
differently depending on whether they are tapped or held, keys that react
differently if they are double tapped, etc. should be avoided they
inevitably send their keycode later than a normal key interrupting the
immediate feedback from the screen. Therefore we restrict ourselves to
chording as our only means of getting more than one symbol out of a single
physical key.
- The hands should never need to leave the home position. The usual culprit for
this is the arrow cluster, so the arrow cluster should be as close to home as
possible.
- There should be two of every modifier (one on each side), otherwise certain
long key combinations become hard to make.
- It should be possible to do things you might want to do while using the mouse
with only the left hand (e.g. change tabs, navigate back or forwards in
browser history).
- Symbols should be arranged so that the most frequently used are easiest to
reach. This includes numbers, and lower numbers are more commonly used than
higher ones. (number arrangement borrowed from [dustypomeleaus minidox
layout][]).
[dustypomeleaus minidox layout]: https://github.com/qmk/qmk_firmware/tree/master/keyboards/minidox/keymaps/dustypomerleau
[keymap.c]: keymap.c

View file

@ -1,7 +0,0 @@
BOOTMAGIC_ENABLE = no
MOUSEKEY_ENABLE = no
CONSOLE_ENABLE = no
COMMAND_ENABLE = yes
MIDI_ENABLE = no
AUDIO_ENABLE = yes
RGBLIGHT_ENABLE = no

View file

@ -0,0 +1,14 @@
#pragma once
#define LAYOUT_callum( \
KEY00, KEY01, KEY02, KEY03, KEY04, KEY05, KEY06, KEY07, KEY08, KEY09, \
KEY10, KEY11, KEY12, KEY13, KEY14, KEY15, KEY16, KEY17, KEY18, KEY19, \
KEY20, KEY21, KEY22, KEY23, KEY24, KEY25, KEY26, KEY27, KEY28, KEY29, \
KEY30, KEY31, KEY32, KEY33 \
) \
LAYOUT_ortho_4x12( \
KEY00, KEY01, KEY02, KEY03, KEY04, KC_NO, KC_NO, KEY05, KEY06, KEY07, KEY08, KEY09, \
KEY10, KEY11, KEY12, KEY13, KEY14, KC_NO, KC_NO, KEY15, KEY16, KEY17, KEY18, KEY19, \
KEY20, KEY21, KEY22, KEY23, KEY24, KC_NO, KC_NO, KEY25, KEY26, KEY27, KEY28, KEY29, \
KC_NO, KC_NO, KC_NO, KEY30, KEY31, KC_NO, KC_NO, KEY32, KEY33, KC_NO, KC_NO, KC_NO \
)

View file

@ -0,0 +1 @@
// Intentionally empty. See /users/callum/readme.md.

130
users/callum/callum.c Normal file
View file

@ -0,0 +1,130 @@
#include QMK_KEYBOARD_H
#include "oneshot.h"
#include "swapper.h"
#define HOME G(KC_LEFT)
#define END G(KC_RGHT)
#define FWD G(KC_RBRC)
#define BACK G(KC_LBRC)
#define TABL G(S(KC_LBRC))
#define TABR G(S(KC_RBRC))
#define SPCL A(G(KC_LEFT))
#define SPCR A(G(KC_RGHT))
#define LA_SYM MO(SYM)
#define LA_NAV MO(NAV)
enum layers {
DEF,
SYM,
NAV,
NUM,
};
enum keycodes {
// Custom oneshot mod implementation with no timers.
OS_SHFT = SAFE_RANGE,
OS_CTRL,
OS_ALT,
OS_CMD,
SW_WIN, // Switch to next window (cmd-tab)
SW_LANG, // Switch to next input language (ctl-spc)
};
const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
[DEF] = LAYOUT_callum(
KC_Q, KC_W, KC_F, KC_P, KC_G, KC_J, KC_L, KC_U, KC_Y, KC_QUOT,
KC_A, KC_R, KC_S, KC_T, KC_D, KC_H, KC_N, KC_E, KC_I, KC_O,
KC_Z, KC_X, KC_C, KC_V, KC_B, KC_K, KC_M, KC_COMM, KC_DOT, KC_SLSH,
LA_NAV, KC_LSFT, KC_SPC, LA_SYM
),
[SYM] = LAYOUT_callum(
KC_ESC, KC_LBRC, KC_LCBR, KC_LPRN, KC_TILD, KC_CIRC, KC_RPRN, KC_RCBR, KC_RBRC, KC_GRV,
KC_MINS, KC_ASTR, KC_EQL, KC_UNDS, KC_DLR, KC_HASH, OS_CMD, OS_ALT, OS_CTRL, OS_SHFT,
KC_PLUS, KC_PIPE, KC_AT, KC_BSLS, KC_PERC, XXXXXXX, KC_AMPR, KC_SCLN, KC_COLN, KC_EXLM,
_______, _______, _______, _______
),
[NAV] = LAYOUT_callum(
KC_TAB, SW_WIN, TABL, TABR, KC_VOLU, RESET, HOME, KC_UP, END, KC_DEL,
OS_SHFT, OS_CTRL, OS_ALT, OS_CMD, KC_VOLD, KC_CAPS, KC_LEFT, KC_DOWN, KC_RGHT, KC_BSPC,
SPCL, SPCR, BACK, FWD, KC_MPLY, XXXXXXX, KC_PGDN, KC_PGUP, SW_LANG, KC_ENT,
_______, _______, _______, _______
),
[NUM] = LAYOUT_callum(
KC_7, KC_5, KC_3, KC_1, KC_9, KC_8, KC_0, KC_2, KC_4, KC_6,
OS_SHFT, OS_CTRL, OS_ALT, OS_CMD, KC_F11, KC_F10, OS_CMD, OS_ALT, OS_CTRL, OS_SHFT,
KC_F7, KC_F5, KC_F3, KC_F1, KC_F9, KC_F8, KC_F12, KC_F2, KC_F4, KC_F6,
_______, _______, _______, _______
),
};
bool is_oneshot_cancel_key(uint16_t keycode) {
switch (keycode) {
case LA_SYM:
case LA_NAV:
return true;
default:
return false;
}
}
bool is_oneshot_ignored_key(uint16_t keycode) {
switch (keycode) {
case LA_SYM:
case LA_NAV:
case KC_LSFT:
case OS_SHFT:
case OS_CTRL:
case OS_ALT:
case OS_CMD:
return true;
default:
return false;
}
}
bool sw_win_active = false;
bool sw_lang_active = false;
oneshot_state os_shft_state = os_up_unqueued;
oneshot_state os_ctrl_state = os_up_unqueued;
oneshot_state os_alt_state = os_up_unqueued;
oneshot_state os_cmd_state = os_up_unqueued;
bool process_record_user(uint16_t keycode, keyrecord_t *record) {
update_swapper(
&sw_win_active, KC_LGUI, KC_TAB, SW_WIN,
keycode, record
);
update_swapper(
&sw_lang_active, KC_LCTL, KC_SPC, SW_LANG,
keycode, record
);
update_oneshot(
&os_shft_state, KC_LSFT, OS_SHFT,
keycode, record
);
update_oneshot(
&os_ctrl_state, KC_LCTL, OS_CTRL,
keycode, record
);
update_oneshot(
&os_alt_state, KC_LALT, OS_ALT,
keycode, record
);
update_oneshot(
&os_cmd_state, KC_LCMD, OS_CMD,
keycode, record
);
return true;
}
layer_state_t layer_state_set_user(layer_state_t state) {
return update_tri_layer_state(state, SYM, NAV, NUM);
}

57
users/callum/oneshot.c Normal file
View file

@ -0,0 +1,57 @@
#include "oneshot.h"
void update_oneshot(
oneshot_state *state,
uint16_t mod,
uint16_t trigger,
uint16_t keycode,
keyrecord_t *record
) {
if (keycode == trigger) {
if (record->event.pressed) {
// Trigger keydown
if (*state == os_up_unqueued) {
register_code(mod);
}
*state = os_down_unused;
} else {
// Trigger keyup
switch (*state) {
case os_down_unused:
// If we didn't use the mod while trigger was held, queue it.
*state = os_up_queued;
break;
case os_down_used:
// If we did use the mod while trigger was held, unregister it.
*state = os_up_unqueued;
unregister_code(mod);
break;
default:
break;
}
}
} else {
if (record->event.pressed) {
if (is_oneshot_cancel_key(keycode) && *state != os_up_unqueued) {
// Cancel oneshot on designated cancel keydown.
*state = os_up_unqueued;
unregister_code(mod);
}
} else {
if (!is_oneshot_ignored_key(keycode)) {
// On non-ignored keyup, consider the oneshot used.
switch (*state) {
case os_down_unused:
*state = os_down_used;
break;
case os_up_queued:
*state = os_up_unqueued;
unregister_code(mod);
break;
default:
break;
}
}
}
}
}

31
users/callum/oneshot.h Normal file
View file

@ -0,0 +1,31 @@
#pragma once
#include QMK_KEYBOARD_H
// Represents the four states a oneshot key can be in
typedef enum {
os_up_unqueued,
os_up_queued,
os_down_unused,
os_down_used,
} oneshot_state;
// Custom oneshot mod implementation that doesn't rely on timers. If a mod is
// used while it is held it will be unregistered on keyup as normal, otherwise
// it will be queued and only released after the next non-mod keyup.
void update_oneshot(
oneshot_state *state,
uint16_t mod,
uint16_t trigger,
uint16_t keycode,
keyrecord_t *record
);
// To be implemented by the consumer. Defines keys to cancel oneshot mods.
bool is_oneshot_cancel_key(uint16_t keycode);
// To be implemented by the consumer. Defines keys to ignore when determining
// whether a oneshot mod has been used. Setting this to modifiers and layer
// change keys allows stacking multiple oneshot modifiers, and carrying them
// between layers.
bool is_oneshot_ignored_key(uint16_t keycode);

99
users/callum/readme.md Normal file
View file

@ -0,0 +1,99 @@
A keymap for 34 keys with 4 layers and no mod-tap.
![](https://raw.githubusercontent.com/callum-oakley/keymap/master/keymap.svg)
## Details
- Hold `sym` to activate the symbols layer.
- Hold `nav` to activate the navigation layer.
- Hold `sym` and `nav` together to activate the numbers layer.
- The home row modifiers are oneshot so that it's possible to modify the
keys on the base layer, where there are no dedicated modifiers.
- `swap win` sends `cmd-tab` for changing focus in macOS but holds `cmd`
between consecutive presses.
- `swap lang` behaves similarly but sends `ctrl-space`, for changing input
language in macOS.
## Oneshot modifiers
The home row modifiers can either be held and used as normal, or if no other
keys are pressed while a modifier is down, the modifier will be queued and
applied to the next non-modifier keypress. For example to type `shift-cmd-t`,
type `sym-o-n` (or `nav-a-t`), release, then hit `t`.
You can and should hit chords as fast as you like because there are no timers
involved.
Cancel unused modifiers by tapping `nav` or `sym`.
### Userspace oneshot implementation
For my usage patterns I was hitting stuck modifiers frequently with [`OSM`][]
(maybe related to [#3963][]?). I'd like to try to help fix this in QMK proper,
but implementing oneshot mods in userspace first was:
1. Fun.
2. A good exploration of how I think oneshot mods should work without timers.
So in the meantime, this [userspace oneshot implementation][] is working well
for me.
## Swapper
`swap win` sends `cmd-tab`, but holds `cmd` between consecutive keypresses.
`cmd` is released when some other key is hit or released. For example
nav down, swap win, swap win, nav up -> cmd down, tab, tab, cmd up
nav down, swap win, enter -> cmd down, tab, cmd up, enter
`swap lang` sends `ctrl-space` to swap input languages in macOS and behaves
similarly.
[Swapper implementation.][]
## Why no mod-tap?
[Mod-tap][] seems to be by far the most popular tool among users of tiny
keyboards to answer the question of where to put the modifiers, and in the
right hands it can clearly work brilliantly, but I've always found myself error
prone and inconsistent with it.
With dedicated modifiers, there are three ways one might type `ctrl-c`:
ctrl down, ctrl up, c down, c up
ctrl down, c down, ctrl up, c up
ctrl down, c down, c up, ctrl up
Basically, you never have to worry about the keyups, as long as the keydowns
occur in the correct order. Similarly, there are three ways one might type
`ac`:
a down, a up, c down, c up
a down, c down, a up, c up
a down, c down, c up, a up
Replace `a` with `ctrl` and this is exactly what we had before! So if we want
to put `a` and `ctrl` on the same key we have a problem, because without
considering timing these sequences become ambiguous. So let's consider timing.
The solution to the ambiguity that QMK employs is to configure the
`TAPPING_TERM` and consider a key held rather than tapped if it is held for
long enough. My problem with this is that it forces you to slow down to use
modifiers. By its very nature the tapping term must be longer than the longest
you would ever hold a key while typing on the slowest laziest Sunday afternoon.
I'm not typing at 100% speed at all times, but when I am, having to think about
timing and consciously slow down for certain actions never fails to trip me up.
So alas, mod-tap is not for me -- but if it works for you, more power to you.
:)
* * *
[My github][]
[`OSM`]: /docs/one_shot_keys.md
[#3963]: https://github.com/qmk/qmk_firmware/issues/3963
[userspace oneshot implementation]: oneshot.c
[swapper implementation.]: swapper.c
[Mod-tap]: https://github.com/qmk/qmk_firmware/blob/master/docs/mod_tap.md
[My github]: https://github.com/callum-oakley

3
users/callum/rules.mk Normal file
View file

@ -0,0 +1,3 @@
SRC += callum.c
SRC += oneshot.c
SRC += swapper.c

27
users/callum/swapper.c Normal file
View file

@ -0,0 +1,27 @@
#include "swapper.h"
void update_swapper(
bool *active,
uint16_t cmdish,
uint16_t tabish,
uint16_t trigger,
uint16_t keycode,
keyrecord_t *record
) {
if (keycode == trigger) {
if (record->event.pressed) {
if (!*active) {
*active = true;
register_code(cmdish);
}
register_code(tabish);
} else {
unregister_code(tabish);
// Don't unregister cmdish until some other key is hit or released.
}
} else if (*active) {
unregister_code(cmdish);
*active = false;
}
}

20
users/callum/swapper.h Normal file
View file

@ -0,0 +1,20 @@
#pragma once
#include QMK_KEYBOARD_H
// Implements cmd-tab like behaviour on a single key. On first tap of trigger
// cmdish is held and tabish is tapped -- cmdish then remains held until some
// other key is hit or released. For example:
//
// trigger, trigger, a -> cmd down, tab, tab, cmd up, a
// nav down, trigger, nav up -> nav down, cmd down, tab, cmd up, nav up
//
// This behaviour is useful for more than just cmd-tab, hence: cmdish, tabish.
void update_swapper(
bool *active,
uint16_t cmdish,
uint16_t tabish,
uint16_t trigger,
uint16_t keycode,
keyrecord_t *record
);