Merge pull request #542 from algernon/f/hold-the-dance

Tap-dance: support for holding keys
This commit is contained in:
Erez Zukerman 2016-07-24 11:36:05 -07:00 committed by GitHub
commit 1e944a4991
3 changed files with 90 additions and 99 deletions

View File

@ -2,81 +2,70 @@
static qk_tap_dance_state_t qk_tap_dance_state; static qk_tap_dance_state_t qk_tap_dance_state;
static void _process_tap_dance_action_pair (qk_tap_dance_state_t *state, void qk_tap_dance_pair_finished (qk_tap_dance_state_t *state, void *user_data) {
uint16_t kc1, uint16_t kc2) { qk_tap_dance_pair_t *pair = (qk_tap_dance_pair_t *)user_data;
uint16_t kc;
if (state->count == 0) if (state->count == 1) {
return; register_code (pair->kc1);
} else if (state->count == 2) {
kc = (state->count == 1) ? kc1 : kc2; register_code (pair->kc2);
register_code (kc);
unregister_code (kc);
if (state->count >= 2) {
reset_tap_dance (state);
} }
} }
static void _process_tap_dance_action_fn (qk_tap_dance_state_t *state, void qk_tap_dance_pair_reset (qk_tap_dance_state_t *state, void *user_data) {
qk_tap_dance_pair_t *pair = (qk_tap_dance_pair_t *)user_data;
if (state->count == 1) {
unregister_code (pair->kc1);
} else if (state->count == 2) {
unregister_code (pair->kc2);
}
}
static inline void _process_tap_dance_action_fn (qk_tap_dance_state_t *state,
void *user_data,
qk_tap_dance_user_fn_t fn) qk_tap_dance_user_fn_t fn)
{ {
if (fn) { if (fn) {
fn(state); fn(state, user_data);
} }
} }
void process_tap_dance_action_on_each_tap (uint16_t keycode) static inline void process_tap_dance_action_on_each_tap (qk_tap_dance_action_t action)
{ {
uint16_t idx = keycode - QK_TAP_DANCE; _process_tap_dance_action_fn (&qk_tap_dance_state, action.user_data, action.fn.on_each_tap);
qk_tap_dance_action_t action;
action = tap_dance_actions[idx];
switch (action.type) {
case QK_TAP_DANCE_TYPE_FN:
_process_tap_dance_action_fn (&qk_tap_dance_state, action.fn.on_each_tap);
break;
default:
break;
}
} }
void process_tap_dance_action_on_dance_finished (uint16_t keycode) static inline void process_tap_dance_action_on_dance_finished (qk_tap_dance_action_t action)
{ {
uint16_t idx = keycode - QK_TAP_DANCE; _process_tap_dance_action_fn (&qk_tap_dance_state, action.user_data, action.fn.on_dance_finished);
qk_tap_dance_action_t action;
action = tap_dance_actions[idx];
switch (action.type) {
case QK_TAP_DANCE_TYPE_PAIR:
_process_tap_dance_action_pair (&qk_tap_dance_state,
action.pair.kc1, action.pair.kc2);
break;
case QK_TAP_DANCE_TYPE_FN:
_process_tap_dance_action_fn (&qk_tap_dance_state, action.fn.on_dance_finished);
break;
default:
break;
} }
static inline void process_tap_dance_action_on_reset (qk_tap_dance_action_t action)
{
_process_tap_dance_action_fn (&qk_tap_dance_state, action.user_data, action.fn.on_reset);
} }
bool process_tap_dance(uint16_t keycode, keyrecord_t *record) { bool process_tap_dance(uint16_t keycode, keyrecord_t *record) {
bool r = true; bool r = true;
uint16_t idx = keycode - QK_TAP_DANCE;
qk_tap_dance_action_t action;
switch(keycode) { switch(keycode) {
case QK_TAP_DANCE ... QK_TAP_DANCE_MAX: case QK_TAP_DANCE ... QK_TAP_DANCE_MAX:
process_tap_dance_action_on_each_tap (qk_tap_dance_state.keycode); action = tap_dance_actions[idx];
process_tap_dance_action_on_each_tap (action);
if (qk_tap_dance_state.keycode && qk_tap_dance_state.keycode != keycode) { if (qk_tap_dance_state.keycode && qk_tap_dance_state.keycode != keycode) {
process_tap_dance_action_on_dance_finished (qk_tap_dance_state.keycode); process_tap_dance_action_on_dance_finished (action);
} else if (qk_tap_dance_state.active && qk_tap_dance_state.pressed) {
reset_tap_dance (&qk_tap_dance_state);
} else { } else {
r = false; r = false;
} }
qk_tap_dance_state.active = true;
qk_tap_dance_state.pressed = record->event.pressed;
if (record->event.pressed) { if (record->event.pressed) {
qk_tap_dance_state.keycode = keycode; qk_tap_dance_state.keycode = keycode;
qk_tap_dance_state.timer = timer_read (); qk_tap_dance_state.timer = timer_read ();
@ -87,9 +76,13 @@ bool process_tap_dance(uint16_t keycode, keyrecord_t *record) {
default: default:
if (qk_tap_dance_state.keycode) { if (qk_tap_dance_state.keycode) {
// if we are here, the tap dance was interrupted by a different key // if we are here, the tap dance was interrupted by a different key
process_tap_dance_action_on_each_tap (qk_tap_dance_state.keycode); idx = qk_tap_dance_state.keycode - QK_TAP_DANCE;
process_tap_dance_action_on_dance_finished (qk_tap_dance_state.keycode); action = tap_dance_actions[idx];
process_tap_dance_action_on_each_tap (action);
process_tap_dance_action_on_dance_finished (action);
reset_tap_dance (&qk_tap_dance_state); reset_tap_dance (&qk_tap_dance_state);
qk_tap_dance_state.active = false;
} }
break; break;
} }
@ -98,9 +91,12 @@ bool process_tap_dance(uint16_t keycode, keyrecord_t *record) {
} }
void matrix_scan_tap_dance () { void matrix_scan_tap_dance () {
if (qk_tap_dance_state.keycode && timer_elapsed (qk_tap_dance_state.timer) > TAPPING_TERM) { if (qk_tap_dance_state.active && timer_elapsed (qk_tap_dance_state.timer) > TAPPING_TERM) {
// if we are here, the tap dance was timed out // if we are here, the tap dance was timed out
process_tap_dance_action_on_dance_finished (qk_tap_dance_state.keycode); uint16_t idx = qk_tap_dance_state.keycode - QK_TAP_DANCE;
qk_tap_dance_action_t action = tap_dance_actions[idx];
process_tap_dance_action_on_dance_finished (action);
reset_tap_dance (&qk_tap_dance_state); reset_tap_dance (&qk_tap_dance_state);
} }
} }
@ -109,18 +105,13 @@ void reset_tap_dance (qk_tap_dance_state_t *state) {
uint16_t idx = state->keycode - QK_TAP_DANCE; uint16_t idx = state->keycode - QK_TAP_DANCE;
qk_tap_dance_action_t action; qk_tap_dance_action_t action;
action = tap_dance_actions[idx]; if (state->pressed)
switch (action.type) { return;
case QK_TAP_DANCE_TYPE_FN:
if (action.fn.on_reset) {
action.fn.on_reset(state);
}
break;
default: action = tap_dance_actions[idx];
break; process_tap_dance_action_on_reset (action);
}
state->keycode = 0; state->keycode = 0;
state->count = 0; state->count = 0;
state->active = false;
} }

View File

@ -11,46 +11,40 @@ typedef struct
uint8_t count; uint8_t count;
uint16_t keycode; uint16_t keycode;
uint16_t timer; uint16_t timer;
bool active:1;
bool pressed:1;
} qk_tap_dance_state_t; } qk_tap_dance_state_t;
#define TD(n) (QK_TAP_DANCE + n) #define TD(n) (QK_TAP_DANCE + n)
typedef enum typedef void (*qk_tap_dance_user_fn_t) (qk_tap_dance_state_t *state, void *user_data);
{
QK_TAP_DANCE_TYPE_PAIR,
QK_TAP_DANCE_TYPE_FN,
} qk_tap_dance_type_t;
typedef void (*qk_tap_dance_user_fn_t) (qk_tap_dance_state_t *state);
typedef struct typedef struct
{ {
qk_tap_dance_type_t type;
union {
struct {
uint16_t kc1;
uint16_t kc2;
} pair;
struct { struct {
qk_tap_dance_user_fn_t on_each_tap; qk_tap_dance_user_fn_t on_each_tap;
qk_tap_dance_user_fn_t on_dance_finished; qk_tap_dance_user_fn_t on_dance_finished;
qk_tap_dance_user_fn_t on_reset; qk_tap_dance_user_fn_t on_reset;
} fn; } fn;
}; void *user_data;
} qk_tap_dance_action_t; } qk_tap_dance_action_t;
typedef struct
{
uint16_t kc1;
uint16_t kc2;
} qk_tap_dance_pair_t;
#define ACTION_TAP_DANCE_DOUBLE(kc1, kc2) { \ #define ACTION_TAP_DANCE_DOUBLE(kc1, kc2) { \
.type = QK_TAP_DANCE_TYPE_PAIR, \ .fn = { NULL, qk_tap_dance_pair_finished, qk_tap_dance_pair_reset }, \
.pair = { kc1, kc2 } \ .user_data = (void *)&((qk_tap_dance_pair_t) { kc1, kc2 }) \
} }
#define ACTION_TAP_DANCE_FN(user_fn) { \ #define ACTION_TAP_DANCE_FN(user_fn) { \
.type = QK_TAP_DANCE_TYPE_FN, \
.fn = { NULL, user_fn, NULL } \ .fn = { NULL, user_fn, NULL } \
} }
#define ACTION_TAP_DANCE_FN_ADVANCED(user_fn_on_each_tap, user_fn_on_dance_finished, user_fn_on_reset) { \ #define ACTION_TAP_DANCE_FN_ADVANCED(user_fn_on_each_tap, user_fn_on_dance_finished, user_fn_on_reset) { \
.type = QK_TAP_DANCE_TYPE_FN, \
.fn = { user_fn_on_each_tap, user_fn_on_dance_finished, user_fn_on_reset } \ .fn = { user_fn_on_each_tap, user_fn_on_dance_finished, user_fn_on_reset } \
} }
@ -62,6 +56,9 @@ bool process_tap_dance(uint16_t keycode, keyrecord_t *record);
void matrix_scan_tap_dance (void); void matrix_scan_tap_dance (void);
void reset_tap_dance (qk_tap_dance_state_t *state); void reset_tap_dance (qk_tap_dance_state_t *state);
void qk_tap_dance_pair_finished (qk_tap_dance_state_t *state, void *user_data);
void qk_tap_dance_pair_reset (qk_tap_dance_state_t *state, void *user_data);
#else #else
#define TD(n) KC_NO #define TD(n) KC_NO

View File

@ -373,7 +373,7 @@ As you can see, you have three function. you can use - `SEQ_ONE_KEY` for single-
### 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
Hit the semicolon key once, send a semicolon. Hit it twice, rapidly -- send a colon. Hit it three times, and your keyboard's LEDs do a wild dance. That's just one example of what Tap Dance can do. It's one of the nicest community-contributed features in the firmware, conceived and created by [algernon](https://github.com/algernon) in [#451](https://github.com/jackhumbert/qmk_firmware/pull/451). Here's how Algernon describes the feature: Hit the semicolon key once, send a semicolon. Hit it twice, rapidly -- send a colon. Hit it three times, and your keyboard's LEDs do a wild dance. That's just one example of what Tap Dance can do. It's one of the nicest community-contributed features in the firmware, conceived and created by [algernon](https://github.com/algernon) in [#451](https://github.com/jackhumbert/qmk_firmware/pull/451). Here's how algernon describes the feature:
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. 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.
@ -389,7 +389,7 @@ First, you will need `TAP_DANCE_ENABLE=yes` in your `Makefile`, because the feat
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 three possible options:
* `ACTION_TAP_DANCE_DOUBLE(kc1, kc2)`: Sends the `kc1` keycode when tapped once, `kc2` otherwise. * `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_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(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_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_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.
@ -397,8 +397,6 @@ The first option is enough for a lot of cases, that just want dual roles. For ex
And that's the bulk of it! And that's the bulk of it!
Do note, however, that this implementation does have some consequences: keys do not register until either they reach the tapping ceiling, or they time out. This means that if you hold the key, nothing happens, no repeat, no nothing. It is possible to detect held state, and register an action then too, but that's not implemented yet. Keys also unregister immediately after being registered, so you can't even hold the second tap. This is intentional, to be consistent.
And now, on to the explanation of how it works! And now, on to the explanation of how it works!
The main entry point is `process_tap_dance()`, called from `process_record_quantum()`, which is run for every keypress, and our handler gets to run early. This function checks whether the key pressed is a tap-dance key. If it is not, and a tap-dance was in action, we handle that first, and enqueue the newly pressed key. If it is a tap-dance key, then we check if it is the same as the already active one (if there's one active, that is). If it is not, we fire off the old one first, then register the new one. If it was the same, we increment the counter and the timer. The main entry point is `process_tap_dance()`, called from `process_record_quantum()`, which is run for every keypress, and our handler gets to run early. This function checks whether the key pressed is a tap-dance key. If it is not, and a tap-dance was in action, we handle that first, and enqueue the newly pressed key. If it is a tap-dance key, then we check if it is the same as the already active one (if there's one active, that is). If it is not, we fire off the old one first, then register the new one. If it was the same, we increment the counter and the timer.
@ -421,20 +419,25 @@ enum {
/* Have the above three on the keymap, TD(CT_SE), etc... */ /* Have the above three on the keymap, TD(CT_SE), etc... */
void dance_cln (qk_tap_dance_state_t *state) { void dance_cln_finished (qk_tap_dance_state_t *state, void *user_data) {
if (state->count == 1) { if (state->count == 1) {
register_code (KC_RSFT); register_code (KC_RSFT);
register_code (KC_SCLN); register_code (KC_SCLN);
unregister_code (KC_SCLN);
unregister_code (KC_RSFT);
} else { } else {
register_code (KC_SCLN); register_code (KC_SCLN);
unregister_code (KC_SCLN);
reset_tap_dance (state);
} }
} }
void dance_egg (qk_tap_dance_state_t *state) { void dance_cln_reset (qk_tap_dance_state_t *state, void *user_data) {
if (state->count == 1) {
unregister_code (KC_RSFT);
unregister_code (KC_SCLN);
} else {
unregister_code (KC_SCLN);
}
}
void dance_egg (qk_tap_dance_state_t *state, void *user_data) {
if (state->count >= 100) { if (state->count >= 100) {
SEND_STRING ("Safety dance!"); SEND_STRING ("Safety dance!");
reset_tap_dance (state); reset_tap_dance (state);
@ -443,7 +446,7 @@ void dance_egg (qk_tap_dance_state_t *state) {
// on each tap, light up one led, from right to left // on each tap, light up one led, from right to left
// on the forth tap, turn them off from right to left // on the forth tap, turn them off from right to left
void dance_flsh_each(qk_tap_dance_state_t *state) { void dance_flsh_each(qk_tap_dance_state_t *state, void *user_data) {
switch (state->count) { switch (state->count) {
case 1: case 1:
ergodox_right_led_3_on(); ergodox_right_led_3_on();
@ -464,7 +467,7 @@ void dance_flsh_each(qk_tap_dance_state_t *state) {
} }
// on the fourth tap, set the keyboard on flash state // on the fourth tap, set the keyboard on flash state
void dance_flsh_finished(qk_tap_dance_state_t *state) { void dance_flsh_finished(qk_tap_dance_state_t *state, void *user_data) {
if (state->count >= 4) { if (state->count >= 4) {
reset_keyboard(); reset_keyboard();
reset_tap_dance(state); reset_tap_dance(state);
@ -472,7 +475,7 @@ void dance_flsh_finished(qk_tap_dance_state_t *state) {
} }
// if the flash state didnt happen, then turn off leds, left to right // if the flash state didnt happen, then turn off leds, left to right
void dance_flsh_reset(qk_tap_dance_state_t *state) { void dance_flsh_reset(qk_tap_dance_state_t *state, void *user_data) {
ergodox_right_led_1_off(); ergodox_right_led_1_off();
_delay_ms(50); _delay_ms(50);
ergodox_right_led_2_off(); ergodox_right_led_2_off();
@ -482,7 +485,7 @@ void dance_flsh_reset(qk_tap_dance_state_t *state) {
const qk_tap_dance_action_t tap_dance_actions[] = { const qk_tap_dance_action_t tap_dance_actions[] = {
[CT_SE] = ACTION_TAP_DANCE_DOUBLE (KC_SPC, KC_ENT) [CT_SE] = ACTION_TAP_DANCE_DOUBLE (KC_SPC, KC_ENT)
,[CT_CLN] = ACTION_TAP_DANCE_FN (dance_cln) ,[CT_CLN] = ACTION_TAP_DANCE_FN_ADVANCED (NULL, dance_cln_finished, dance_cln_reset)
,[CT_EGG] = ACTION_TAP_DANCE_FN (dance_egg) ,[CT_EGG] = ACTION_TAP_DANCE_FN (dance_egg)
,[CT_FLSH] = ACTION_TAP_DANCE_FN_ADVANCED (dance_flsh_each, dance_flsh_finished, dance_flsh_reset) ,[CT_FLSH] = ACTION_TAP_DANCE_FN_ADVANCED (dance_flsh_each, dance_flsh_finished, dance_flsh_reset)
}; };