aboutsummaryrefslogtreecommitdiff
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
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
-rw-r--r--keyboards/planck/keymaps/callum/keymap.c260
-rw-r--r--keyboards/planck/keymaps/callum/readme.md30
-rw-r--r--keyboards/planck/keymaps/callum/rules.mk7
-rw-r--r--layouts/community/ortho_4x12/callum/config.h14
-rw-r--r--layouts/community/ortho_4x12/callum/keymap.c1
-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
12 files changed, 382 insertions, 297 deletions
diff --git a/keyboards/planck/keymaps/callum/keymap.c b/keyboards/planck/keymaps/callum/keymap.c
deleted file mode 100644
index 50c0122a9..000000000
--- a/keyboards/planck/keymaps/callum/keymap.c
+++ /dev/null
@@ -1,260 +0,0 @@
1#include "planck.h"
2#include "action_layer.h"
3
4#define a KC_A
5#define b KC_B
6#define c KC_C
7#define d KC_D
8#define e KC_E
9#define f KC_F
10#define g KC_G
11#define h KC_H
12#define i KC_I
13#define j KC_J
14#define k KC_K
15#define l KC_L
16#define m KC_M
17#define n KC_N
18#define o KC_O
19#define p KC_P
20#define q KC_Q
21#define r KC_R
22#define s KC_S
23#define t KC_T
24#define u KC_U
25#define v KC_V
26#define w KC_W
27#define x KC_X
28#define y KC_Y
29#define z KC_Z
30
31#define lalt KC_LALT
32#define lctl KC_LCTL
33#define lsft KC_LSFT
34#define ralt KC_RALT
35#define rctl KC_RCTL
36#define rsft KC_RSFT
37
38#define n0 KC_0
39#define n1 KC_1
40#define n2 KC_2
41#define n3 KC_3
42#define n4 KC_4
43#define n5 KC_5
44#define n6 KC_6
45#define n7 KC_7
46#define n8 KC_8
47#define n9 KC_9
48
49#define ampr KC_AMPR
50#define astr KC_ASTR
51#define at KC_AT
52#define bsls KC_BSLS
53#define bspc KC_BSPC
54#define caps KC_CAPS
55#define circ KC_CIRC
56#define comm KC_COMM
57#define dash A(KC_MINS) // en-dash (–); or with shift: em-dash (—)
58#define del KC_DEL
59#define dlr KC_DLR
60#define dot KC_DOT
61#define ent KC_ENT
62#define eql KC_EQL
63#define esc KC_ESC
64#define exlm KC_EXLM
65#define grv KC_GRV
66#define hash KC_HASH
67#define lbrc KC_LBRC
68#define lcbr KC_LCBR
69#define lprn KC_LPRN
70#define mins KC_MINS
71#define perc KC_PERC
72#define pipe KC_PIPE
73#define plus KC_PLUS
74#define quot KC_QUOT
75#define rbrc KC_RBRC
76#define rcbr KC_RCBR
77#define rprn KC_RPRN
78#define scln KC_SCLN
79#define slsh KC_SLSH
80#define spc KC_SPC
81#define tab KC_TAB
82#define tild KC_TILD
83
84#define down KC_DOWN
85#define home G(KC_LEFT)
86#define end G(KC_RGHT)
87#define up KC_UP
88#define pgdn KC_PGDN
89#define pgup KC_PGUP
90#define left KC_LEFT
91#define rght KC_RGHT
92
93#define tabl G(S(KC_LBRC))
94#define tabr G(S(KC_RBRC))
95#define fwd G(KC_RBRC)
96#define back G(KC_LBRC)
97#define slup S(A(KC_UP)) // Previous unread in Slack
98#define sldn S(A(KC_DOWN)) // Next unread in Slack
99
100#define ctl1 C(KC_1) // Desktop 1 (6 with shift)
101#define ctl2 C(KC_2) // Desktop 2 (7 with shift)
102#define ctl3 C(KC_3) // Desktop 3 (8 with shift)
103#define ctl4 C(KC_4) // Desktop 4 (9 with shift)
104#define ctl5 C(KC_5) // Desktop 5 (10 with shift)
105#define ctl6 C(KC_6) // Screenshot
106#define ctl7 C(KC_7) // Brightness up
107#define ctl8 C(KC_8) // Brightness down
108
109#define f1 KC_F1
110#define f2 KC_F2
111#define f3 KC_F3
112#define f4 KC_F4
113#define f5 KC_F5
114#define f6 KC_F6
115#define f7 KC_F7
116#define f8 KC_F8
117#define f9 KC_F9
118#define f10 KC_F10
119#define f11 KC_F11
120#define f12 KC_F12
121#define f13 KC_F13
122#define f14 KC_F14
123#define f15 KC_F15
124#define f16 KC_F16
125#define f17 KC_F17
126#define f18 KC_F18
127#define f19 KC_F19
128#define f20 KC_F20
129
130#define mute KC_MUTE
131#define next KC_MNXT
132#define play KC_MPLY
133#define prev KC_MPRV
134#define vold KC_VOLD
135#define volu KC_VOLU
136
137#define symb MO(SYMB)
138#define move MO(MOVE)
139#define func MO(FUNC)
140
141#define rset RESET
142#define powr KC_POWER
143
144#define ____ KC_TRNS
145#define xxxx KC_NO
146
147extern keymap_config_t keymap_config;
148
149enum planck_layers {
150 BASE,
151 SYMB,
152 MOVE,
153 FUNC,
154};
155
156enum planck_keycodes {
157 // Curly quotes
158 lcqt = SAFE_RANGE,
159 rcqt,
160
161 // "Smart" mods
162 cmd,
163};
164
165const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
166 [BASE] = LAYOUT_planck_grid(
167 tab, q, w, f, p, g, j, l, u, y, scln, mins,
168 bspc, a, r, s, t, d, h, n, e, i, o, quot,
169 lsft, z, x, c, v, b, k, m, comm, dot, slsh, rsft,
170 func, lctl, lalt, cmd, move, ent, spc, symb, cmd, ralt, rctl, func
171 ),
172
173 [SYMB] = LAYOUT_planck_grid(
174 esc, n7, n5, n3, n1, n9, n8, n0, n2, n4, n6, dash,
175 lcqt, at, dlr, eql, lprn, lbrc, rbrc, rprn, astr, hash, plus, rcqt,
176 ____, grv, pipe, bsls, lcbr, tild, circ, rcbr, ampr, exlm, perc, ____,
177 ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____
178 ),
179
180 [MOVE] = LAYOUT_planck_grid(
181 esc, ctl1, ctl2, ctl3, ctl4, ctl5, ctl6, home, up, end, xxxx, xxxx,
182 del, play, volu, tabl, tabr, slup, ctl7, left, down, rght, caps, xxxx,
183 ____, mute, vold, back, fwd, sldn, ctl8, pgdn, pgup, xxxx, xxxx, ____,
184 ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____
185 ),
186
187 [FUNC] = LAYOUT_planck_grid(
188 rset, f7, f5, f3, f1, f9, f8, f10, f2, f4, f6, xxxx,
189 xxxx, f17, f15, f13, f11, f19, f18, f20, f12, f14, f16, xxxx,
190 ____, xxxx, xxxx, xxxx, xxxx, xxxx, xxxx, xxxx, xxxx, xxxx, xxxx, ____,
191 ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____
192 ),
193};
194
195bool send_string_if_keydown(
196 keyrecord_t *record,
197 const char *unshifted,
198 const char *shifted) {
199 if (record->event.pressed) {
200 if (shifted) {
201 uint8_t shifts = get_mods() & MOD_MASK_SHIFT;
202 if (shifts) {
203 del_mods(shifts);
204 send_string(shifted);
205 add_mods(shifts);
206 } else {
207 send_string(unshifted);
208 }
209 } else {
210 send_string(unshifted);
211 }
212 }
213 return true;
214}
215
216// Holding both cmd keys will instead register as cmd + ctl
217bool smart_cmd(keyrecord_t *record) {
218 static int cmd_keys_down = 0;
219
220 if (record->event.pressed) {
221 if (cmd_keys_down == 0) {
222 register_code(KC_LCMD);
223 } else {
224 register_code(KC_LCTL);
225 }
226 cmd_keys_down++;
227 } else {
228 if (cmd_keys_down == 1) {
229 unregister_code(KC_LCMD);
230 } else {
231 unregister_code(KC_LCTL);
232 }
233 cmd_keys_down--;
234 }
235 return true;
236}
237
238bool process_record_user(uint16_t keycode, keyrecord_t *record) {
239 switch (keycode) {
240 // The macOS shortcuts for curly quotes are horrible, so this rebinds
241 // them so that shift toggles single–double instead of left–right, and
242 // then both varieties of left quote can share one key, and both
243 // varieties of right quote share another.
244 case lcqt:
245 return send_string_if_keydown(
246 record,
247 SS_LALT("]"), // left single quote (‘)
248 SS_LALT("[")); // left double quote (“)
249 case rcqt:
250 return send_string_if_keydown(
251 record,
252 SS_LALT(SS_LSFT("]")), // right single quote (’)
253 SS_LALT(SS_LSFT("["))); // right double quote (”)
254
255 // cmd + cmd -> cmd + ctl
256 case cmd:
257 return smart_cmd(record);
258 }
259 return true;
260}
diff --git a/keyboards/planck/keymaps/callum/readme.md b/keyboards/planck/keymaps/callum/readme.md
deleted file mode 100644
index 471de2b74..000000000
--- a/keyboards/planck/keymaps/callum/readme.md
+++ /dev/null
@@ -1,30 +0,0 @@
1# callum’s planck layout
2
3This is a layout for the grid planck, built with a few ideals in mind:
4
5- Consistent and minimal response times should be maintained. Keys that react
6 differently depending on whether they are tapped or held, keys that react
7 differently if they are double tapped, etc. should be avoided – they
8 inevitably send their keycode later than a normal key – interrupting the
9 immediate feedback from the screen. Therefore we restrict ourselves to
10 chording as our only means of getting more than one symbol out of a single
11 physical key.
12
13- The hands should never need to leave the home position. The usual culprit for
14 this is the arrow cluster, so the arrow cluster should be as close to home as
15 possible.
16
17- There should be two of every modifier (one on each side), otherwise certain
18 long key combinations become hard to make.
19
20- It should be possible to do things you might want to do while using the mouse
21 with only the left hand (e.g. change tabs, navigate back or forwards in
22 browser history).
23
24- Symbols should be arranged so that the most frequently used are easiest to
25 reach. This includes numbers, and lower numbers are more commonly used than
26 higher ones. (number arrangement borrowed from [dustypomeleau’s minidox
27 layout][]).
28
29[dustypomeleau’s minidox layout]: https://github.com/qmk/qmk_firmware/tree/master/keyboards/minidox/keymaps/dustypomerleau
30[keymap.c]: keymap.c
diff --git a/keyboards/planck/keymaps/callum/rules.mk b/keyboards/planck/keymaps/callum/rules.mk
deleted file mode 100644
index 9615222d1..000000000
--- a/keyboards/planck/keymaps/callum/rules.mk
+++ /dev/null
@@ -1,7 +0,0 @@
1BOOTMAGIC_ENABLE = no
2MOUSEKEY_ENABLE = no
3CONSOLE_ENABLE = no
4COMMAND_ENABLE = yes
5MIDI_ENABLE = no
6AUDIO_ENABLE = yes
7RGBLIGHT_ENABLE = no
diff --git a/layouts/community/ortho_4x12/callum/config.h b/layouts/community/ortho_4x12/callum/config.h
new file mode 100644
index 000000000..8034fe51b
--- /dev/null
+++ b/layouts/community/ortho_4x12/callum/config.h
@@ -0,0 +1,14 @@
1#pragma once
2
3#define LAYOUT_callum( \
4 KEY00, KEY01, KEY02, KEY03, KEY04, KEY05, KEY06, KEY07, KEY08, KEY09, \
5 KEY10, KEY11, KEY12, KEY13, KEY14, KEY15, KEY16, KEY17, KEY18, KEY19, \
6 KEY20, KEY21, KEY22, KEY23, KEY24, KEY25, KEY26, KEY27, KEY28, KEY29, \
7 KEY30, KEY31, KEY32, KEY33 \
8) \
9LAYOUT_ortho_4x12( \
10 KEY00, KEY01, KEY02, KEY03, KEY04, KC_NO, KC_NO, KEY05, KEY06, KEY07, KEY08, KEY09, \
11 KEY10, KEY11, KEY12, KEY13, KEY14, KC_NO, KC_NO, KEY15, KEY16, KEY17, KEY18, KEY19, \
12 KEY20, KEY21, KEY22, KEY23, KEY24, KC_NO, KC_NO, KEY25, KEY26, KEY27, KEY28, KEY29, \
13 KC_NO, KC_NO, KC_NO, KEY30, KEY31, KC_NO, KC_NO, KEY32, KEY33, KC_NO, KC_NO, KC_NO \
14)
diff --git a/layouts/community/ortho_4x12/callum/keymap.c b/layouts/community/ortho_4x12/callum/keymap.c
new file mode 100644
index 000000000..acff75905
--- /dev/null
+++ b/layouts/community/ortho_4x12/callum/keymap.c
@@ -0,0 +1 @@
// Intentionally empty. See /users/callum/readme.md.
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);