-# Tap Dance: A single key can do 3, 5, or 100 different things
+# Tap Dance: A Single Key Can Do 3, 5, or 100 Different Things
<!-- FIXME: Break this up into multiple sections -->
With this feature one can specify keys that behave differently, based on the amount of times they have been tapped, and when interrupted, they get handled before the interrupter.
-To make it clear how this is different from `ACTION_FUNCTION_TAP`, lets explore a certain setup! We want one key to send `Space` on single tap, but `Enter` on double-tap.
+To make it clear how this is different from `ACTION_FUNCTION_TAP`, let's explore a certain setup! We want one key to send `Space` on single tap, but `Enter` on double-tap.
-With `ACTION_FUNCTION_TAP`, it is quite a rain-dance to set this up, and has the problem that when the sequence is interrupted, the interrupting key will be send first. Thus, `SPC a` will result in `a SPC` being sent, if they are typed within `TAPPING_TERM`. With the tap dance feature, that'll come out as `SPC a`, correctly.
+With `ACTION_FUNCTION_TAP`, it is quite a rain-dance to set this up, and has the problem that when the sequence is interrupted, the interrupting key will be sent first. Thus, `SPC a` will result in `a SPC` being sent, if they are typed within `TAPPING_TERM`. With the tap dance feature, that'll come out as `SPC a`, correctly.
The implementation hooks into two parts of the system, to achieve this: into `process_record_quantum()`, and the matrix scan. We need the latter to be able to time out a tap sequence even when a key is not being pressed, so `SPC` alone will time out and register after `TAPPING_TERM` time.
First, you will need `TAP_DANCE_ENABLE=yes` in your `rules.mk`, because the feature is disabled by default. This adds a little less than 1k to the firmware size. Next, you will want to define some tap-dance keys, which is easiest to do with the `TD()` macro, that - similar to `F()`, takes a number, which will later be used as an index into the `tap_dance_actions` array.
-This array specifies what actions shall be taken when a tap-dance key is in action. Currently, there are three possible options:
+This array specifies what actions shall be taken when a tap-dance key is in action. Currently, there are five possible options:
* `ACTION_TAP_DANCE_DOUBLE(kc1, kc2)`: Sends the `kc1` keycode when tapped once, `kc2` otherwise. When the key is held, the appropriate keycode is registered: `kc1` when pressed and held, `kc2` when tapped once, then pressed and held.
+* `ACTION_TAP_DANCE_DUAL_ROLE(kc, layer)`: Sends the `kc` keycode when tapped once, or moves to `layer`. (this functions like the `TO` layer keycode).
* `ACTION_TAP_DANCE_FN(fn)`: Calls the specified function - defined in the user keymap - with the final tap count of the tap dance action.
-* `ACTION_TAP_DANCE_FN_ADVANCED(on_each_tap_fn, on_dance_finished_fn, on_dance_reset_fn)`: Calls the first specified function - defined in the user keymap - on every tap, the second function on when the dance action finishes (like the previous option), and the last function when the tap dance action resets.
+* `ACTION_TAP_DANCE_FN_ADVANCED(on_each_tap_fn, on_dance_finished_fn, on_dance_reset_fn)`: Calls the first specified function - defined in the user keymap - on every tap, the second function when the dance action finishes (like the previous option), and the last function when the tap dance action resets.
+* `ACTION_TAP_DANCE_FN_ADVANCED_TIME(on_each_tap_fn, on_dance_finished_fn, on_dance_reset_fn, tap_specific_tapping_term)`: This functions identically to the `ACTION_TAP_DANCE_FN_ADVANCED` function, but uses a custom tapping term for it, instead of the predefined `TAPPING_TERM`.
-The first option is enough for a lot of cases, that just want dual roles. For example, `ACTION_TAP_DANCE(KC_SPC, KC_ENT)` will result in `Space` being sent on single-tap, `Enter` otherwise.
+The first option is enough for a lot of cases, that just want dual roles. For example, `ACTION_TAP_DANCE_DOUBLE(KC_SPC, KC_ENT)` will result in `Space` being sent on single-tap, `Enter` otherwise.
+
+!> Keep in mind that only [basic keycodes](keycodes_basic.md) are supported here. Custom keycodes are not supported.
And that's the bulk of it!
X_TAP_DANCE
};
```
-### Example 1: Send `:` on single tap, `;` on double tap
+### Example 1: Send `:` on Single Tap, `;` on Double Tap
```c
void dance_cln_finished (qk_tap_dance_state_t *state, void *user_data) {
if (state->count == 1) {
[CT_CLN] = ACTION_TAP_DANCE_FN_ADVANCED (NULL, dance_cln_finished, dance_cln_reset)
};
```
-### Example 2: Send "Safety Dance!" after 100 taps
+### Example 2: Send "Safety Dance!" After 100 Taps
```c
void dance_egg (qk_tap_dance_state_t *state, void *user_data) {
if (state->count >= 100) {
};
```
-### Example 3: Turn LED lights on then off, one at a time
+### Example 3: Turn LED Lights On Then Off, One at a Time
```c
// on each tap, light up one led, from right to left
}
}
-// if the flash state didnt happen, then turn off leds, left to right
+// if the flash state didn't happen, then turn off LEDs, left to right
void dance_flsh_reset(qk_tap_dance_state_t *state, void *user_data) {
ergodox_right_led_1_off();
_delay_ms(50);
* Double Tap = Send `Escape`
* Double Tap and Hold = Send `Alt`
-The following example can be easily expanded to more than 4 quite easily:
+## Setup
+
+You will need a few things that can be used for 'Quad Function Tap-Dance'.
+
+You'll need to add these to the top of your `keymap.c` file, before your keymap.
+
```c
-//**************** Definitions needed for quad function to work *********************//
-//Enums used to clearly convey the state of the tap dance
+typedef struct {
+ bool is_press_action;
+ int state;
+} tap;
+
enum {
SINGLE_TAP = 1,
SINGLE_HOLD = 2,
DOUBLE_TAP = 3,
- DOUBLE_HOLD = 4,
- DOUBLE_SINGLE_TAP = 5 //send SINGLE_TAP twice - NOT DOUBLE_TAP
- // Add more enums here if you want for triple, quadruple, etc.
+ DOUBLE_HOLD = 4,
+ DOUBLE_SINGLE_TAP = 5, //send two single taps
+ TRIPLE_TAP = 6,
+ TRIPLE_HOLD = 7
};
-typedef struct {
- bool is_press_action;
- int state;
-} tap;
+//Tap dance enums
+enum {
+ X_CTL = 0,
+ SOME_OTHER_DANCE
+};
+int cur_dance (qk_tap_dance_state_t *state);
+
+//for the x tap dance. Put it here so it can be used in any keymap
+void x_finished (qk_tap_dance_state_t *state, void *user_data);
+void x_reset (qk_tap_dance_state_t *state, void *user_data);
+
+```
+
+Now, at the bottom of your `keymap.c` file, you'll need to add the following:
+
+```c
+/* Return an integer that corresponds to what kind of tap dance should be executed.
+ *
+ * How to figure out tap dance state: interrupted and pressed.
+ *
+ * Interrupted: If the state of a dance dance is "interrupted", that means that another key has been hit
+ * under the tapping term. This is typically indicitive that you are trying to "tap" the key.
+ *
+ * Pressed: Whether or not the key is still being pressed. If this value is true, that means the tapping term
+ * has ended, but the key is still being pressed down. This generally means the key is being "held".
+ *
+ * One thing that is currenlty not possible with qmk software in regards to tap dance is to mimic the "permissive hold"
+ * feature. In general, advanced tap dances do not work well if they are used with commonly typed letters.
+ * For example "A". Tap dances are best used on non-letter keys that are not hit while typing letters.
+ *
+ * Good places to put an advanced tap dance:
+ * z,q,x,j,k,v,b, any function key, home/end, comma, semi-colon
+ *
+ * Criteria for "good placement" of a tap dance key:
+ * Not a key that is hit frequently in a sentence
+ * Not a key that is used frequently to double tap, for example 'tab' is often double tapped in a terminal, or
+ * in a web form. So 'tab' would be a poor choice for a tap dance.
+ * Letters used in common words as a double. For example 'p' in 'pepper'. If a tap dance function existed on the
+ * letter 'p', the word 'pepper' would be quite frustating to type.
+ *
+ * For the third point, there does exist the 'DOUBLE_SINGLE_TAP', however this is not fully tested
+ *
+ */
int cur_dance (qk_tap_dance_state_t *state) {
if (state->count == 1) {
- //If count = 1, and it has been interrupted - it doesn't matter if it is pressed or not: Send SINGLE_TAP
- if (state->interrupted || state->pressed==0) return SINGLE_TAP;
+ if (state->interrupted || !state->pressed) return SINGLE_TAP;
+ //key has not been interrupted, but they key is still held. Means you want to send a 'HOLD'.
else return SINGLE_HOLD;
}
- //If count = 2, and it has been interrupted - assume that user is trying to type the letter associated
- //with single tap. In example below, that means to send `xx` instead of `Escape`.
else if (state->count == 2) {
+ /*
+ * DOUBLE_SINGLE_TAP is to distinguish between typing "pepper", and actually wanting a double tap
+ * action when hitting 'pp'. Suggested use case for this return value is when you want to send two
+ * keystrokes of the key, and not the 'double tap' action/macro.
+ */
if (state->interrupted) return DOUBLE_SINGLE_TAP;
else if (state->pressed) return DOUBLE_HOLD;
else return DOUBLE_TAP;
- }
- else return 6; //magic number. At some point this method will expand to work for more presses
+ }
+ //Assumes no one is trying to type the same letter three times (at least not quickly).
+ //If your tap dance key is 'KC_W', and you want to type "www." quickly - then you will need to add
+ //an exception here to return a 'TRIPLE_SINGLE_TAP', and define that enum just like 'DOUBLE_SINGLE_TAP'
+ if (state->count == 3) {
+ if (state->interrupted || !state->pressed) return TRIPLE_TAP;
+ else return TRIPLE_HOLD;
+ }
+ else return 8; //magic number. At some point this method will expand to work for more presses
}
-//**************** Definitions needed for quad function to work *********************//
-
//instanalize an instance of 'tap' for the 'x' tap dance.
-static tap xtap_state = {
+static tap xtap_state = {
.is_press_action = true,
.state = 0
};
}
xtap_state.state = 0;
}
+
+qk_tap_dance_action_t tap_dance_actions[] = {
+ [X_CTL] = ACTION_TAP_DANCE_FN_ADVANCED(NULL,x_finished, x_reset)
+};
+```
+
+And then simply use `TD(X_CTL)` anywhere in your keymap.
+
+If you want to implement this in your userspace, then you may want to check out how [DanielGGordon](https://github.com/qmk/qmk_firmware/tree/master/users/gordon) has implemented this in their userspace.
+
+### Example 5: Using tap dance for advanced mod-tap and layer-tap keys
+
+Tap dance can be used to emulate `MT()` and `LT()` behavior when the tapped code is not a basic keycode. This is useful to send tapped keycodes that normally require `Shift`, such as parentheses or curly braces—or other modified keycodes, such as `Control + X`.
+
+Below your layers and custom keycodes, add the following:
+
+```c
+// tapdance keycodes
+enum td_keycodes {
+ ALT_LP // Our example key: `LALT` when held, `(` when tapped. Add additional keycodes for each tapdance.
+};
+
+// define a type containing as many tapdance states as you need
+typedef enum {
+ SINGLE_TAP,
+ SINGLE_HOLD,
+ DOUBLE_SINGLE_TAP
+} td_state_t;
+
+// create a global instance of the tapdance state type
+static td_state_t td_state;
+
+// declare your tapdance functions:
+
+// function to determine the current tapdance state
+int cur_dance (qk_tap_dance_state_t *state);
+
+// `finished` and `reset` functions for each tapdance keycode
+void altlp_finished (qk_tap_dance_state_t *state, void *user_data);
+void altlp_reset (qk_tap_dance_state_t *state, void *user_data);
+```
+
+Below your `LAYOUT`, define each of the tapdance functions:
+
+```c
+// determine the tapdance state to return
+int cur_dance (qk_tap_dance_state_t *state) {
+ if (state->count == 1) {
+ if (state->interrupted || !state->pressed) { return SINGLE_TAP; }
+ else { return SINGLE_HOLD; }
+ }
+ if (state->count == 2) { return DOUBLE_SINGLE_TAP; }
+ else { return 3; } // any number higher than the maximum state value you return above
+}
+
+// handle the possible states for each tapdance keycode you define:
+
+void altlp_finished (qk_tap_dance_state_t *state, void *user_data) {
+ td_state = cur_dance(state);
+ switch (td_state) {
+ case SINGLE_TAP:
+ register_code16(KC_LPRN);
+ break;
+ case SINGLE_HOLD:
+ register_mods(MOD_BIT(KC_LALT)); // for a layer-tap key, use `layer_on(_MY_LAYER)` here
+ break;
+ case DOUBLE_SINGLE_TAP: // allow nesting of 2 parens `((` within tapping term
+ tap_code16(KC_LPRN);
+ register_code16(KC_LPRN);
+ }
+}
+
+void altlp_reset (qk_tap_dance_state_t *state, void *user_data) {
+ switch (td_state) {
+ case SINGLE_TAP:
+ unregister_code16(KC_LPRN);
+ break;
+ case SINGLE_HOLD:
+ unregister_mods(MOD_BIT(KC_LALT)); // for a layer-tap key, use `layer_off(_MY_LAYER)` here
+ break;
+ case DOUBLE_SINGLE_TAP:
+ unregister_code16(KC_LPRN);
+ }
+}
+
+// define `ACTION_TAP_DANCE_FN_ADVANCED()` for each tapdance keycode, passing in `finished` and `reset` functions
+qk_tap_dance_action_t tap_dance_actions[] = {
+ [ALT_LP] = ACTION_TAP_DANCE_FN_ADVANCED(NULL, altlp_finished, altlp_reset)
+};
```
-And then simply add this to your list of tap dance functions:
-`[X_TAP_DANCE] = ACTION_TAP_DANCE_FN_ADVANCED(NULL, x_finished, x_reset)`
+
+Wrap each tapdance keycode in `TD()` when including it in your keymap, e.g. `TD(ALT_LP)`.