aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKyle Xiao <56144092+txkyel@users.noreply.github.com>2021-08-11 03:19:19 -0400
committerGitHub <noreply@github.com>2021-08-11 00:19:19 -0700
commitaf3627db255752fdb49d2461d2c1fa3ae136d4cc (patch)
treeeb5888d86ccef212b2b9e67bcabd0790413cb575
parent83a1c4763a5b1bda577d26892479462bd1457a6e (diff)
downloadqmk_firmware-af3627db255752fdb49d2461d2c1fa3ae136d4cc.tar.gz
qmk_firmware-af3627db255752fdb49d2461d2c1fa3ae136d4cc.zip
[Userspace] Add custom tap dancing function (#13963)
-rw-r--r--users/txkyel/config.h18
-rw-r--r--users/txkyel/readme.md20
-rw-r--r--users/txkyel/rules.mk5
-rw-r--r--users/txkyel/tap_dance.c51
-rw-r--r--users/txkyel/tap_dance.h33
-rw-r--r--users/txkyel/tap_dance.md173
-rw-r--r--users/txkyel/txkyel.c56
-rw-r--r--users/txkyel/txkyel.h58
8 files changed, 414 insertions, 0 deletions
diff --git a/users/txkyel/config.h b/users/txkyel/config.h
new file mode 100644
index 000000000..beb0a287a
--- /dev/null
+++ b/users/txkyel/config.h
@@ -0,0 +1,18 @@
1/* Copyright 2021 Kyle Xiao
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16#pragma once
17
18#define TAPPING_TERM 200
diff --git a/users/txkyel/readme.md b/users/txkyel/readme.md
new file mode 100644
index 000000000..6f86fe28f
--- /dev/null
+++ b/users/txkyel/readme.md
@@ -0,0 +1,20 @@
1# txkyel's Userspace
2
3Read up on my implementation of tap dancing journey in learning QMK thus far in [tap_dance.md](tap_dance.md).
4
5## Licensing
6
7Copyright 2021 Kyle Xiao kylexiao20@gmail.com @txkyel
8
9This program is free software: you can redistribute it and/or modify
10it under the terms of the GNU General Public License as published by
11the Free Software Foundation, either version 2 of the License, or
12(at your option) any later version.
13
14This program is distributed in the hope that it will be useful,
15but WITHOUT ANY WARRANTY; without even the implied warranty of
16MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17GNU General Public License for more details.
18
19You should have received a copy of the GNU General Public License
20along with this program. If not, see <http://www.gnu.org/licenses/>.
diff --git a/users/txkyel/rules.mk b/users/txkyel/rules.mk
new file mode 100644
index 000000000..f7b729c83
--- /dev/null
+++ b/users/txkyel/rules.mk
@@ -0,0 +1,5 @@
1SRC += txkyel.c
2
3ifeq ($(strip $(TAP_DANCE_ENABLE)), yes)
4 SRC += tap_dance.c
5endif
diff --git a/users/txkyel/tap_dance.c b/users/txkyel/tap_dance.c
new file mode 100644
index 000000000..9c1d27e8d
--- /dev/null
+++ b/users/txkyel/tap_dance.c
@@ -0,0 +1,51 @@
1/* Copyright 2021 Kyle Xiao
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16#include "tap_dance.h"
17
18#ifdef TAP_DANCE_ENABLE
19void qk_tap_dance_tap_hold_on_each_tap(qk_tap_dance_state_t *state, void *user_data) {
20 qk_tap_dance_tap_hold_t *kc = (qk_tap_dance_tap_hold_t *)user_data;
21
22 // second tap within term, pseudo reset tap dance
23 if (state->count == 2) {
24 tap_code16(kc->t);
25 state->count = 1;
26 state->timer = timer_read();
27 }
28}
29
30void qk_tap_dance_tap_hold_on_finish(qk_tap_dance_state_t *state, void *user_data) {
31 qk_tap_dance_tap_hold_t *kc = (qk_tap_dance_tap_hold_t *)user_data;
32
33 kc->tapped = state->interrupted || !state->pressed;
34 if (kc->tapped) {
35 register_code16(kc->t);
36 } else {
37 register_code16(kc->h);
38 }
39}
40
41void qk_tap_dance_tap_hold_on_reset(qk_tap_dance_state_t *state, void *user_data) {
42 qk_tap_dance_tap_hold_t *kc = (qk_tap_dance_tap_hold_t *)user_data;
43
44 if (kc->tapped) {
45 unregister_code16(kc->t);
46 } else {
47 unregister_code16(kc->h);
48 }
49 kc->tapped = true;
50}
51#endif
diff --git a/users/txkyel/tap_dance.h b/users/txkyel/tap_dance.h
new file mode 100644
index 000000000..af56a9863
--- /dev/null
+++ b/users/txkyel/tap_dance.h
@@ -0,0 +1,33 @@
1/* Copyright 2021 Kyle Xiao
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16#pragma once
17#include "txkyel.h"
18
19#ifdef TAP_DANCE_ENABLE
20typedef struct {
21 uint16_t t;
22 uint16_t h;
23 bool tapped;
24} qk_tap_dance_tap_hold_t;
25
26# define ACTION_TAP_HOLD(t, h) \
27 { .fn = {qk_tap_dance_tap_hold_on_each_tap, qk_tap_dance_tap_hold_on_finish, qk_tap_dance_tap_hold_on_reset}, .user_data = (void *)&((qk_tap_dance_tap_hold_t){t, h, true}), }
28# define ACTION_TAP_HOLD_CTL(t) ACTION_TAP_HOLD(t, C(t))
29
30void qk_tap_dance_tap_hold_on_each_tap(qk_tap_dance_state_t *state, void *user_data);
31void qk_tap_dance_tap_hold_on_finish(qk_tap_dance_state_t *state, void *user_data);
32void qk_tap_dance_tap_hold_on_reset(qk_tap_dance_state_t *state, void *user_data);
33#endif
diff --git a/users/txkyel/tap_dance.md b/users/txkyel/tap_dance.md
new file mode 100644
index 000000000..e36001f12
--- /dev/null
+++ b/users/txkyel/tap_dance.md
@@ -0,0 +1,173 @@
1# Tap Hold Custom Tap Dance
2
3This custom tap dance functions similarly to the single tap and hold functionality of '[Quad Function Tap-Dance](https://docs.qmk.fm/#/feature_tap_dance?id=example-4)' by [DanielGGordon](https://github.com/danielggordon) with a reduced size per tap dance declared.
4
5## Motivation
6
7I first discovered tap dancing through [Ben Vallack](https://www.youtube.com/c/BenVallack) and was interested in the functionality of tap dancing demonstrated in his [tap dancing video](https://www.youtube.com/watch?v=pTMbzmf2sz8). And so I set off to implement my own tap dances emulating this functionality.
8
9## Custom Tap Dance Action
10
11Similar to the the action definitions in [`process_tap_dance.h`](../../quantum/process_keycode/process_tap_dance.h); I have created a custom macro and data structure used to declare tap dance actions:
12
13```c
14typedef struct {
15 uint16_t t;
16 uint16_t h;
17 bool tapped;
18} qk_tap_dance_tap_hold_t;
19
20#define ACTION_TAP_HOLD(t, h) \
21 { .fn = {qk_tap_dance_tap_hold_on_each_tap, qk_tap_dance_tap_hold_on_finish, qk_tap_dance_tap_hold_on_reset}, .user_data = (void *)&((qk_tap_dance_tap_hold_t){t, h, true}), }
22```
23
24This allows the user to set the keycode registered when tapping `t`, the keycode registered when holding `h`, as well as prepares a boolean storing logic allowing the the reset function to determine whether a tap or hold was registered `tapped`.
25
26## Custom Tap Dance Functions
27
28The three handlers backing Tap Hold are really simple.
29
30The `on_each_tap` handler triggers a tap if a second tap is made within the tapping term. Following that it performs a pseudo reset, setting the count back to 1 and resetting the timer.
31
32```c
33void qk_tap_dance_tap_hold_on_each_tap(qk_tap_dance_state_t *state, void *user_data) {
34 qk_tap_dance_tap_hold_t *kc = (qk_tap_dance_tap_hold_t *)user_data;
35
36 if (state->count == 2) {
37 tap_code16(kc->t);
38 state->count = 1;
39 state->timer = timer_read();
40 }
41}
42```
43
44The `on_finished` handler determines whether the dance was a tap or a hold, saving the result in `kc->tapped` for `on_reset` later. After, the handler registers the respctive keycode.
45
46```c
47void qk_tap_dance_tap_hold_on_finish(qk_tap_dance_state_t *state, void *user_data) {
48 qk_tap_dance_tap_hold_t *kc = (qk_tap_dance_tap_hold_t *)user_data;
49
50 kc->tapped = state->interrupted || !state->pressed;
51 if (kc->tapped) {
52 register_code16(kc->t);
53 } else {
54 register_code16(kc->h);
55 }
56}
57```
58
59Finally, the `on_reset` handler unregisters the corresponding keycode, and resets `kc->tapped` for subsequent tap dances.
60
61```c
62void qk_tap_dance_tap_hold_on_reset(qk_tap_dance_state_t *state, void *user_data) {
63 qk_tap_dance_tap_hold_t *kc = (qk_tap_dance_tap_hold_t *)user_data;
64
65 if (kc->tapped) {
66 unregister_code16(kc->t);
67 } else {
68 unregister_code16(kc->h);
69 }
70 kc->tapped = true;
71}
72```
73
74## Tap: keycode; Hold: control + keycode (or any modifier + keycode)
75
76The macro `ACTION_TAP_HOLD` allows a user to select the keycode for both the tap and hold action of the tap dance. It goes without saying that you can also send keycodes with modifiers so instead of having to write out `ACTION_TAP_HOLD(kc, C(kc))` for each keycode, we can use more macros:
77
78```c
79#define ACTION_TAP_HOLD_CTL(t) ACTION_TAP_HOLD(t, LCTL(t))
80```
81
82Macros are rock.
83
84# The Journey to Lower Sized Tap Dancing
85
86As I said earlier, I had set out to create my own tap dancing functions with little knowledge of how QMK works. Just as a bonus, I thought I'd share my journey through making my own custom tap dance.
87
88## Research
89
90When looking through the [tap dance documentation](https://beta.docs.qmk.fm/using-qmk/software-features/feature_tap_dance), it was apparent to me that [complex example 4](https://docs.qmk.fm/#/feature_tap_dance?id=example-4) by [DanielGGordon](https://github.com/danielggordon), had the functionality I so desired, I would have to do at least three things.
91
92However, in order to make all the tap dances for at least all of the alpha keys I would have to do the following three things:
93
941. Write a `on_dance_finished` function
952. Write a `on_reset` function
963. And create a static struct instance storing a `boolean` and `td_state_t`
97
98The most outrageous part was that I would have to repeat these three steps for **each and every tap dance!**
99
100Unable to see how I could reduce the number of functions and data structures. I decided to follow through with making functions for each keycode.
101
102## Naive Implementation 1: Macros with `ACTION_TAP_DANCE_FN_ADVANCED`
103
104For my initial implementation, I set out to make use of macros to reduce the amount of apparent duplicate code in my keymap files.
105
106Copying the functions by DanielGGordon, reducing its functionality to single tap and single hold, and converting them into macros, I was able to create all the necessary functions for each tap dance without having to write out the same functions dozens of times.
107
108My issues with this implementation were that, when compiling, this was no different from me writing the same function dozens of time. This meant that the resulting firmware was **HUGE** decreasing the amount of additional features the user is able to enable.
109
110## Naive Implementation 2: Custom Quantum Keycode
111
112Searching for another solution I searched through the files in the qmk repository and stumbled across the implementation of several quantum keycode processing files in [`process_keycode`](../../quantum/process_keycode), namely the files [`process_unicode.h`](../../quantum/process_keycode/process_unicode.h), [`process_unicode.c`](../../quantum/process_keycode/process_unicode.c), and [`process_unicode_common.h`](../../quantum/process_keycode/process_unicode_common.h).
113
114And so I set off to implement my own **custom quantum keycodes** overriding Unicode keycode ranges and [implementing `process_record_user()`](https://docs.qmk.fm/#/custom_quantum_functions?id=custom-keycodes).
115
116Upon initial testing with a single key, it appeared functional save for the fact that keys would only register when releasing the key. Additionally it saved plenty of space due to reuse of functions when processing input.
117
118I was really proud of my implementation and had even shown it off to several of my friends.
119
120Unfortunately, when testing it out on all alpha, everything started to come apart. Because keystrokes would only register when releasing the switch, faster typists would actually actuate some keystrokes in the incorrect order; particularly with space appearing before the final letter of most words.
121
122## Current Implementation: Custom Tap Dance Actions
123
124Upset that I would have to go back to naive implementation 1, I went back to digging for answers. Maybe there was something I was missing, some extra details on how tap dancing gets processed.
125
126Looking again in [`process_keycode`](../../quantum/process_keycode), I found [`process_tap_dance.h`](../../quantum/process_keycode/process_tap_dance.h) and [`process_tap_dance.c`](../../quantum/process_keycode/process_tap_dance.c). And in those files, I found the miracle working struct `qk_tap_dance_action_t`.
127
128Looking more closely, each tap dance action a user defines constructs a `qk_tap_dance_action_t` with the following:
129
130- Three handler functions
131 - An `on_each_tap` function that runs when the tap dance key is pressed within the `TAPPING_TERM`
132 - An `on_dance_finished` function that runs when the `TAPPING_TERM` is complete
133 - An `on_reset` function that runs after finishing the dance
134- A `custom_tapping_term` for the tap dance action
135- Last but not least, the my saving grace: `void *user_data`. A user defined struct that gets passed to each of the handler functions.
136
137With this discovery, I set out to implement my own custom tap dance action in my [personal userspace](.) as seen [`tap_dance.c`](tap_dance.c) and [`tap_dance.h`](tap_dance.h) which I named ACTION_TAP_HOLD.
138
139## Updates and Thoughts
140
141### 08/08/2021
142
143Initially, I thought of allowing the user to hold the hold tap dance action (`KC_LCTL` + `KC_<keycode>`). Unfortunately, I ran into several issues (likely due to my lack of understanding on the runtime flow) causing control to be held indefinitely until the computer is restarted. This is why, I had handler functions perform `tap_code16` opposed to `register_code16` and `unregister_code16`.
144
145When handling a double tap within the `TAPPING_TERM`, the tap keycode gets sent with `tap_code16`, the status timer gets reset, and the counter gets set back to 1 in case the user wishes to tap again or to hold the second tap.
146
147### 09/08/2021
148
149Generalized tap and hold `user_data` struct to store two keycodes; one for the tap action, the other for the hold action.
150
151This way the user can define the tap and hold functionality separately.
152
153### 09/08/2021
154
155Reworked utilizing, `register_code16` and `unregister_code16`. The issues previously experienced were due to my ignorance of the runtime flow for tap dancing.
156
157Previously in both the `on_dance_finished` and `on_reset` functions, I checked if the key was being tapped or held using this statement:
158
159```
160state->interrupted || !state->pressed
161```
162
163In the case of a hold, when accessing the `on_dance_finished` function, `state->interrupted` would be false and `state->pressed` would be true making the above statement false making the statement work as intended.
164
165However, when it comes to `on_reset` the statement no longer works.
166
167In the runtime flow for tap dancing, `on_reset` gets called in the function [`reset_tap_dance`](../../quantum/process_keycode/process_tap_dance.c#L186). In order for the `on_reset` function to be called, **state->pressed must be false**.
168
169This means that the above statement is no longer reliable in determining if a key has been held in this function. In fact, the function will always act as though a tap occured, meaning the hold reset will never be reached.
170
171There were signs of this in [complex examples](https://docs.qmk.fm/#/feature_tap_dance?id=complex-examples) that I hadn't taken into mind when designing this custom tap dance action (mainly through the static variables used to store the state instead of using `state` in both functions).
172
173As such, the fix was as simple as adding a boolean to the `user_data` struct that stores the state of the tap dance, thus allowing us to now be able to properly hold the hold key without any worry of the hold action of a key being stuck on.
diff --git a/users/txkyel/txkyel.c b/users/txkyel/txkyel.c
new file mode 100644
index 000000000..b01a71bb8
--- /dev/null
+++ b/users/txkyel/txkyel.c
@@ -0,0 +1,56 @@
1/* Copyright 2021 Kyle Xiao
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16#include "txkyel.h"
17
18#ifdef TAP_DANCE_ENABLE
19// Default Tap Dance definitions
20qk_tap_dance_action_t tap_dance_actions[] = {
21 [HC_A] = ACTION_TAP_HOLD_CTL(KC_A),
22 [HC_B] = ACTION_TAP_HOLD_CTL(KC_B),
23 [HC_C] = ACTION_TAP_HOLD_CTL(KC_C),
24 [HC_D] = ACTION_TAP_HOLD_CTL(KC_D),
25 [HC_E] = ACTION_TAP_HOLD_CTL(KC_E),
26 [HC_F] = ACTION_TAP_HOLD_CTL(KC_F),
27 [HC_G] = ACTION_TAP_HOLD_CTL(KC_G),
28 [HC_H] = ACTION_TAP_HOLD_CTL(KC_H),
29 [HC_I] = ACTION_TAP_HOLD_CTL(KC_I),
30 [HC_J] = ACTION_TAP_HOLD_CTL(KC_J),
31 [HC_K] = ACTION_TAP_HOLD_CTL(KC_K),
32 [HC_L] = ACTION_TAP_HOLD_CTL(KC_L),
33 [HC_M] = ACTION_TAP_HOLD_CTL(KC_M),
34 [HC_N] = ACTION_TAP_HOLD_CTL(KC_N),
35 [HC_O] = ACTION_TAP_HOLD_CTL(KC_O),
36 [HC_P] = ACTION_TAP_HOLD_CTL(KC_P),
37 [HC_Q] = ACTION_TAP_HOLD_CTL(KC_Q),
38 [HC_R] = ACTION_TAP_HOLD_CTL(KC_R),
39 [HC_S] = ACTION_TAP_HOLD_CTL(KC_S),
40 [HC_T] = ACTION_TAP_HOLD_CTL(KC_T),
41 [HC_U] = ACTION_TAP_HOLD_CTL(KC_U),
42 [HC_V] = ACTION_TAP_HOLD_CTL(KC_V),
43 [HC_W] = ACTION_TAP_HOLD_CTL(KC_W),
44 [HC_X] = ACTION_TAP_HOLD_CTL(KC_X),
45 [HC_Y] = ACTION_TAP_HOLD_CTL(KC_Y),
46 [HC_Z] = ACTION_TAP_HOLD_CTL(KC_Z),
47 [HC_BSPC] = ACTION_TAP_HOLD_CTL(KC_BSPC),
48 [HC_DEL] = ACTION_TAP_HOLD_CTL(KC_DEL),
49 [HC_LEFT] = ACTION_TAP_HOLD_CTL(KC_LEFT),
50 [HC_RGHT] = ACTION_TAP_HOLD_CTL(KC_RGHT),
51 [TH_ESC_TAB] = ACTION_TAP_HOLD(KC_TAB, KC_ESC),
52 [TH_HOME_BSLS] = ACTION_TAP_HOLD(KC_BSLASH, KC_HOME),
53 [TH_HOME_BSLS] = ACTION_TAP_HOLD(KC_PIPE, KC_END),
54 [TH_QUOT_GRV] = ACTION_TAP_HOLD(KC_QUOT, KC_GRV),
55};
56#endif
diff --git a/users/txkyel/txkyel.h b/users/txkyel/txkyel.h
new file mode 100644
index 000000000..0b725961a
--- /dev/null
+++ b/users/txkyel/txkyel.h
@@ -0,0 +1,58 @@
1/* Copyright 2021 Kyle Xiao
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16#pragma once
17#include "quantum.h"
18#include "tap_dance.h"
19
20#ifdef TAP_DANCE_ENABLE
21// Tap Dance declarations for use in keymaps
22enum hold_ctl_enum {
23 HC_A = 1,
24 HC_B,
25 HC_C,
26 HC_D,
27 HC_E,
28 HC_F,
29 HC_G,
30 HC_H,
31 HC_I,
32 HC_J,
33 HC_K,
34 HC_L,
35 HC_M,
36 HC_N,
37 HC_O,
38 HC_P,
39 HC_Q,
40 HC_R,
41 HC_S,
42 HC_T,
43 HC_U,
44 HC_V,
45 HC_W,
46 HC_X,
47 HC_Y,
48 HC_Z,
49 HC_BSPC,
50 HC_DEL,
51 HC_LEFT,
52 HC_RGHT,
53 TH_ESC_TAB,
54 TH_HOME_BSLS,
55 TH_END_PIPE,
56 TH_QUOT_GRV,
57};
58#endif