aboutsummaryrefslogtreecommitdiff
path: root/users
diff options
context:
space:
mode:
authorCallum Oakley <hello@callumoakley.net>2020-09-09 23:37:34 +0100
committerGitHub <noreply@github.com>2020-09-09 15:37:34 -0700
commit3d4f0028d60cebc829ac9c947d1a61cc840d05c0 (patch)
treecc54048f38bd75f62de4d2f396359b4fa31b2711 /users
parent6e948feb6a33bcd1da45c5a590d0e6c241e1d879 (diff)
downloadqmk_firmware-3d4f0028d60cebc829ac9c947d1a61cc840d05c0.tar.gz
qmk_firmware-3d4f0028d60cebc829ac9c947d1a61cc840d05c0.zip
[Keymap] major keymap overhaul (#10185)
* experiment with userspace * reorganise * readme * missing oneshot shift from ignored keys * recombine hands in layout macro
Diffstat (limited to 'users')
-rw-r--r--users/callum/callum.c130
-rw-r--r--users/callum/oneshot.c57
-rw-r--r--users/callum/oneshot.h31
-rw-r--r--users/callum/readme.md99
-rw-r--r--users/callum/rules.mk3
-rw-r--r--users/callum/swapper.c27
-rw-r--r--users/callum/swapper.h20
7 files changed, 367 insertions, 0 deletions
diff --git a/users/callum/callum.c b/users/callum/callum.c
new file mode 100644
index 000000000..4661902af
--- /dev/null
+++ b/users/callum/callum.c
@@ -0,0 +1,130 @@
1#include QMK_KEYBOARD_H
2
3#include "oneshot.h"
4#include "swapper.h"
5
6#define HOME G(KC_LEFT)
7#define END G(KC_RGHT)
8#define FWD G(KC_RBRC)
9#define BACK G(KC_LBRC)
10#define TABL G(S(KC_LBRC))
11#define TABR G(S(KC_RBRC))
12#define SPCL A(G(KC_LEFT))
13#define SPCR A(G(KC_RGHT))
14#define LA_SYM MO(SYM)
15#define LA_NAV MO(NAV)
16
17enum layers {
18 DEF,
19 SYM,
20 NAV,
21 NUM,
22};
23
24enum keycodes {
25 // Custom oneshot mod implementation with no timers.
26 OS_SHFT = SAFE_RANGE,
27 OS_CTRL,
28 OS_ALT,
29 OS_CMD,
30
31 SW_WIN, // Switch to next window (cmd-tab)
32 SW_LANG, // Switch to next input language (ctl-spc)
33};
34
35const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
36 [DEF] = LAYOUT_callum(
37 KC_Q, KC_W, KC_F, KC_P, KC_G, KC_J, KC_L, KC_U, KC_Y, KC_QUOT,
38 KC_A, KC_R, KC_S, KC_T, KC_D, KC_H, KC_N, KC_E, KC_I, KC_O,
39 KC_Z, KC_X, KC_C, KC_V, KC_B, KC_K, KC_M, KC_COMM, KC_DOT, KC_SLSH,
40 LA_NAV, KC_LSFT, KC_SPC, LA_SYM
41 ),
42
43 [SYM] = LAYOUT_callum(
44 KC_ESC, KC_LBRC, KC_LCBR, KC_LPRN, KC_TILD, KC_CIRC, KC_RPRN, KC_RCBR, KC_RBRC, KC_GRV,
45 KC_MINS, KC_ASTR, KC_EQL, KC_UNDS, KC_DLR, KC_HASH, OS_CMD, OS_ALT, OS_CTRL, OS_SHFT,
46 KC_PLUS, KC_PIPE, KC_AT, KC_BSLS, KC_PERC, XXXXXXX, KC_AMPR, KC_SCLN, KC_COLN, KC_EXLM,
47 _______, _______, _______, _______
48 ),
49
50 [NAV] = LAYOUT_callum(
51 KC_TAB, SW_WIN, TABL, TABR, KC_VOLU, RESET, HOME, KC_UP, END, KC_DEL,
52 OS_SHFT, OS_CTRL, OS_ALT, OS_CMD, KC_VOLD, KC_CAPS, KC_LEFT, KC_DOWN, KC_RGHT, KC_BSPC,
53 SPCL, SPCR, BACK, FWD, KC_MPLY, XXXXXXX, KC_PGDN, KC_PGUP, SW_LANG, KC_ENT,
54 _______, _______, _______, _______
55 ),
56
57 [NUM] = LAYOUT_callum(
58 KC_7, KC_5, KC_3, KC_1, KC_9, KC_8, KC_0, KC_2, KC_4, KC_6,
59 OS_SHFT, OS_CTRL, OS_ALT, OS_CMD, KC_F11, KC_F10, OS_CMD, OS_ALT, OS_CTRL, OS_SHFT,
60 KC_F7, KC_F5, KC_F3, KC_F1, KC_F9, KC_F8, KC_F12, KC_F2, KC_F4, KC_F6,
61 _______, _______, _______, _______
62 ),
63};
64
65bool is_oneshot_cancel_key(uint16_t keycode) {
66 switch (keycode) {
67 case LA_SYM:
68 case LA_NAV:
69 return true;
70 default:
71 return false;
72 }
73}
74
75bool is_oneshot_ignored_key(uint16_t keycode) {
76 switch (keycode) {
77 case LA_SYM:
78 case LA_NAV:
79 case KC_LSFT:
80 case OS_SHFT:
81 case OS_CTRL:
82 case OS_ALT:
83 case OS_CMD:
84 return true;
85 default:
86 return false;
87 }
88}
89
90bool sw_win_active = false;
91bool sw_lang_active = false;
92
93oneshot_state os_shft_state = os_up_unqueued;
94oneshot_state os_ctrl_state = os_up_unqueued;
95oneshot_state os_alt_state = os_up_unqueued;
96oneshot_state os_cmd_state = os_up_unqueued;
97
98bool process_record_user(uint16_t keycode, keyrecord_t *record) {
99 update_swapper(
100 &sw_win_active, KC_LGUI, KC_TAB, SW_WIN,
101 keycode, record
102 );
103 update_swapper(
104 &sw_lang_active, KC_LCTL, KC_SPC, SW_LANG,
105 keycode, record
106 );
107
108 update_oneshot(
109 &os_shft_state, KC_LSFT, OS_SHFT,
110 keycode, record
111 );
112 update_oneshot(
113 &os_ctrl_state, KC_LCTL, OS_CTRL,
114 keycode, record
115 );
116 update_oneshot(
117 &os_alt_state, KC_LALT, OS_ALT,
118 keycode, record
119 );
120 update_oneshot(
121 &os_cmd_state, KC_LCMD, OS_CMD,
122 keycode, record
123 );
124
125 return true;
126}
127
128layer_state_t layer_state_set_user(layer_state_t state) {
129 return update_tri_layer_state(state, SYM, NAV, NUM);
130}
diff --git a/users/callum/oneshot.c b/users/callum/oneshot.c
new file mode 100644
index 000000000..33ec3895e
--- /dev/null
+++ b/users/callum/oneshot.c
@@ -0,0 +1,57 @@
1#include "oneshot.h"
2
3void update_oneshot(
4 oneshot_state *state,
5 uint16_t mod,
6 uint16_t trigger,
7 uint16_t keycode,
8 keyrecord_t *record
9) {
10 if (keycode == trigger) {
11 if (record->event.pressed) {
12 // Trigger keydown
13 if (*state == os_up_unqueued) {
14 register_code(mod);
15 }
16 *state = os_down_unused;
17 } else {
18 // Trigger keyup
19 switch (*state) {
20 case os_down_unused:
21 // If we didn't use the mod while trigger was held, queue it.
22 *state = os_up_queued;
23 break;
24 case os_down_used:
25 // If we did use the mod while trigger was held, unregister it.
26 *state = os_up_unqueued;
27 unregister_code(mod);
28 break;
29 default:
30 break;
31 }
32 }
33 } else {
34 if (record->event.pressed) {
35 if (is_oneshot_cancel_key(keycode) && *state != os_up_unqueued) {
36 // Cancel oneshot on designated cancel keydown.
37 *state = os_up_unqueued;
38 unregister_code(mod);
39 }
40 } else {
41 if (!is_oneshot_ignored_key(keycode)) {
42 // On non-ignored keyup, consider the oneshot used.
43 switch (*state) {
44 case os_down_unused:
45 *state = os_down_used;
46 break;
47 case os_up_queued:
48 *state = os_up_unqueued;
49 unregister_code(mod);
50 break;
51 default:
52 break;
53 }
54 }
55 }
56 }
57}
diff --git a/users/callum/oneshot.h b/users/callum/oneshot.h
new file mode 100644
index 000000000..a6b8e1774
--- /dev/null
+++ b/users/callum/oneshot.h
@@ -0,0 +1,31 @@
1#pragma once
2
3#include QMK_KEYBOARD_H
4
5// Represents the four states a oneshot key can be in
6typedef enum {
7 os_up_unqueued,
8 os_up_queued,
9 os_down_unused,
10 os_down_used,
11} oneshot_state;
12
13// Custom oneshot mod implementation that doesn't rely on timers. If a mod is
14// used while it is held it will be unregistered on keyup as normal, otherwise
15// it will be queued and only released after the next non-mod keyup.
16void update_oneshot(
17 oneshot_state *state,
18 uint16_t mod,
19 uint16_t trigger,
20 uint16_t keycode,
21 keyrecord_t *record
22);
23
24// To be implemented by the consumer. Defines keys to cancel oneshot mods.
25bool is_oneshot_cancel_key(uint16_t keycode);
26
27// To be implemented by the consumer. Defines keys to ignore when determining
28// whether a oneshot mod has been used. Setting this to modifiers and layer
29// change keys allows stacking multiple oneshot modifiers, and carrying them
30// between layers.
31bool is_oneshot_ignored_key(uint16_t keycode);
diff --git a/users/callum/readme.md b/users/callum/readme.md
new file mode 100644
index 000000000..24b71038b
--- /dev/null
+++ b/users/callum/readme.md
@@ -0,0 +1,99 @@
1A keymap for 34 keys with 4 layers and no mod-tap.
2
3![](https://raw.githubusercontent.com/callum-oakley/keymap/master/keymap.svg)
4
5## Details
6
7- Hold `sym` to activate the symbols layer.
8- Hold `nav` to activate the navigation layer.
9- Hold `sym` and `nav` together to activate the numbers layer.
10- The home row modifiers are oneshot so that it's possible to modify the
11 keys on the base layer, where there are no dedicated modifiers.
12- `swap win` sends `cmd-tab` for changing focus in macOS but holds `cmd`
13 between consecutive presses.
14- `swap lang` behaves similarly but sends `ctrl-space`, for changing input
15 language in macOS.
16
17## Oneshot modifiers
18
19The home row modifiers can either be held and used as normal, or if no other
20keys are pressed while a modifier is down, the modifier will be queued and
21applied to the next non-modifier keypress. For example to type `shift-cmd-t`,
22type `sym-o-n` (or `nav-a-t`), release, then hit `t`.
23
24You can and should hit chords as fast as you like because there are no timers
25involved.
26
27Cancel unused modifiers by tapping `nav` or `sym`.
28
29### Userspace oneshot implementation
30
31For my usage patterns I was hitting stuck modifiers frequently with [`OSM`][]
32(maybe related to [#3963][]?). I'd like to try to help fix this in QMK proper,
33but implementing oneshot mods in userspace first was:
34
351. Fun.
362. A good exploration of how I think oneshot mods should work without timers.
37
38So in the meantime, this [userspace oneshot implementation][] is working well
39for me.
40
41## Swapper
42
43`swap win` sends `cmd-tab`, but holds `cmd` between consecutive keypresses.
44`cmd` is released when some other key is hit or released. For example
45
46 nav down, swap win, swap win, nav up -> cmd down, tab, tab, cmd up
47 nav down, swap win, enter -> cmd down, tab, cmd up, enter
48
49`swap lang` sends `ctrl-space` to swap input languages in macOS and behaves
50similarly.
51
52[Swapper implementation.][]
53
54## Why no mod-tap?
55
56[Mod-tap][] seems to be by far the most popular tool among users of tiny
57keyboards to answer the question of where to put the modifiers, and in the
58right hands it can clearly work brilliantly, but I've always found myself error
59prone and inconsistent with it.
60
61With dedicated modifiers, there are three ways one might type `ctrl-c`:
62
63 ctrl down, ctrl up, c down, c up
64 ctrl down, c down, ctrl up, c up
65 ctrl down, c down, c up, ctrl up
66
67Basically, you never have to worry about the keyups, as long as the keydowns
68occur in the correct order. Similarly, there are three ways one might type
69`ac`:
70
71 a down, a up, c down, c up
72 a down, c down, a up, c up
73 a down, c down, c up, a up
74
75Replace `a` with `ctrl` and this is exactly what we had before! So if we want
76to put `a` and `ctrl` on the same key we have a problem, because without
77considering timing these sequences become ambiguous. So let's consider timing.
78
79The solution to the ambiguity that QMK employs is to configure the
80`TAPPING_TERM` and consider a key held rather than tapped if it is held for
81long enough. My problem with this is that it forces you to slow down to use
82modifiers. By its very nature the tapping term must be longer than the longest
83you would ever hold a key while typing on the slowest laziest Sunday afternoon.
84I'm not typing at 100% speed at all times, but when I am, having to think about
85timing and consciously slow down for certain actions never fails to trip me up.
86
87So alas, mod-tap is not for me -- but if it works for you, more power to you.
88:)
89
90* * *
91
92[My github][]
93
94[`OSM`]: /docs/one_shot_keys.md
95[#3963]: https://github.com/qmk/qmk_firmware/issues/3963
96[userspace oneshot implementation]: oneshot.c
97[swapper implementation.]: swapper.c
98[Mod-tap]: https://github.com/qmk/qmk_firmware/blob/master/docs/mod_tap.md
99[My github]: https://github.com/callum-oakley
diff --git a/users/callum/rules.mk b/users/callum/rules.mk
new file mode 100644
index 000000000..2d98e02c5
--- /dev/null
+++ b/users/callum/rules.mk
@@ -0,0 +1,3 @@
1SRC += callum.c
2SRC += oneshot.c
3SRC += swapper.c
diff --git a/users/callum/swapper.c b/users/callum/swapper.c
new file mode 100644
index 000000000..736b2fef0
--- /dev/null
+++ b/users/callum/swapper.c
@@ -0,0 +1,27 @@
1#include "swapper.h"
2
3void update_swapper(
4 bool *active,
5 uint16_t cmdish,
6 uint16_t tabish,
7 uint16_t trigger,
8 uint16_t keycode,
9 keyrecord_t *record
10) {
11 if (keycode == trigger) {
12 if (record->event.pressed) {
13 if (!*active) {
14 *active = true;
15 register_code(cmdish);
16 }
17 register_code(tabish);
18 } else {
19 unregister_code(tabish);
20 // Don't unregister cmdish until some other key is hit or released.
21 }
22 } else if (*active) {
23 unregister_code(cmdish);
24 *active = false;
25 }
26}
27
diff --git a/users/callum/swapper.h b/users/callum/swapper.h
new file mode 100644
index 000000000..ad47fd96c
--- /dev/null
+++ b/users/callum/swapper.h
@@ -0,0 +1,20 @@
1#pragma once
2
3#include QMK_KEYBOARD_H
4
5// Implements cmd-tab like behaviour on a single key. On first tap of trigger
6// cmdish is held and tabish is tapped -- cmdish then remains held until some
7// other key is hit or released. For example:
8//
9// trigger, trigger, a -> cmd down, tab, tab, cmd up, a
10// nav down, trigger, nav up -> nav down, cmd down, tab, cmd up, nav up
11//
12// This behaviour is useful for more than just cmd-tab, hence: cmdish, tabish.
13void update_swapper(
14 bool *active,
15 uint16_t cmdish,
16 uint16_t tabish,
17 uint16_t trigger,
18 uint16_t keycode,
19 keyrecord_t *record
20);