Closed tmk closed 3 days ago
Tapping events of Dual role key
===============================
2014/04/24
Dual role variations
--------------------
From user's point of view there can be two variations.
1. key/mod mainly used as a key
2. mod/key mainly used as a modifier
Configurations
--------------
TAPPING_TERM: elapsed time between press and release of tap must be smaller than this period.
No delay mod: registers mod without delay of TAPPING_TERM. Optimization for mod/key.
Tap and hold repeat: provides key repeat. Optimization for key/mod.
Send immediately: occurs event immediately without delay when a key is typed during TAPPING_TERM
Event notation
--------------
(P|R)<n>i?
P: press
R: release
<n>: tap count(0:not tap 1:first tap)
i: interrupted
Not tap
-------
(a)
|<--->| TAPPING_TERM
D~~_________~~~
P0 R0 registers mod after delay to prevent from sending 'false mod'
P0 R0 registers mod without delay to behave as real modifier as possible(No delay mod)
(b)
D~~_________~~~
P0i R0i interrupted(typed) [P0i, P, R, R0i]
k~~~~___~~~~~~~
P R events are delayed after TAPPING_TERM
(c)
D~~_________~~~
P0 R0i interrupted after TAPPING_TERM [P0, P, R0i, R]
k~~~~~~~~~___~~
P R
(d)
D~~_________~~~
P0i R0i interrupted during TAPPING_TERM(typed) [P0i, P, R, R0i](Send immediately)
k~~~~___~~~~~~~
P R without delay. Optimization for mod/key variant.
(e) compare to Tap-(c)
D~~_____~~~~~~~
P0i R0i interrupted during TAPPING_TERM(typed) [P0i, P, R, R0i](Send immediately)
k~~~~__~~~~~~~~
P R without delay. Optimization for mod/key variant.
Tap
---
(a)
|<--->| TAPPING_TERM
D~~____~~~~~~~~
P1 R1 tap
P0 R0 P1 R1 registers mod without delay to behave as real modifier as possible
and cancels 'false mod' before send tap events(No dealy mod)
(b)
|<--->|
D~~____~~~~~~~~
P1i R1i interrupted [P1i, P, R1i, R]
k~~~~___~~~~~~~
P R
(c) compare to Not tap-(e)
D~~_____~~~~~~~
P1i R1i interrupted during TAPPING_TERM(typed) [P0i, P, R, R0i]
k~~~~__~~~~~~~~
P R with delay. Optimization for key/mod variant.
Sequential tap
--------------
(a)
|<--->| TAPPING_TERM
|<--->| |<--->| TAPPING_TERM
D~~___~~~~~___~~~~~
P1 R1 tap1
P2 R2 tap2
P2 R2 provides better key reponse and repeat(Tap and hold repeat)
(b)
|<--->| TAPPING_TERM
|<--->| |<--->| TAPPING_TERM
D~~___~~~~~________
P1 R1 tap
P2 provides better key reponse and repeat(Tap and hold repeat)
(c)
|<--->| TAPPING_TERM
|<--->| |<--->| TAPPING_TERM
D~~___~~~~~~~~~___~~~~~
P1 R1 P1 R1 two different taps(not sequential) when gap between taps is lager than TAPPING_TERM.
EOF
Hi, So I got this working for my ergodox keyboard by using the ACTION_LAYER_TAP_KEY and then having the layer underneath all be modified. So my D key on hold drops to an all CTRL all the time layer. This is for sure a just a quick hack, but it works quite well.
I'm not sure how things differ for ACTION_LAYER_TAP_KEY vs ACTION_MODS_TAP_KEY but when I tried using ACTION_MODS_TAP_KEY it was messing up my typing all the time by thinking I made the modifier.
Doing this only covers problem #1 you listed. I lose repeating.
Here is my whole messy file, in case its helpful:
static const uint8_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
KEYMAP( // Layer0: default, leftled:none
// left hand
FN6, 1, 2, 3, 4, 5, MINS,
FN1, Q, W, E, R, T, TAB,
FN2, A, S, FN8, FN5, G,
FN4, Z, X, C, V, B, BSPC,
BSLS,SPC,SPC,SPC,SPC,
ESC,LBRC,
MINS,
SPC, ENT, NO,
// right hand
EQL, 6, 7, 8, 9, 0, EQL,
TAB, Y, U, I, O, P, FN0,
H, FN7, FN9, L, SCLN,FN3,
DELETE, N, M, COMM,DOT, QUOT,FN4,
SPC,SPC, SPC,SPC,SLSH,
RBRC,ESC,
EQL,
NO, ENT, SPC
),
KEYMAP( // Layer 1 F lock
// left hand
TRNS,F1 ,F2 ,F3 ,F4 ,F5 ,F11,
TRNS,TRNS,TRNS,TRNS,TRNS,TRNS,TRNS,
TRNS,TRNS,TRNS,TRNS,TRNS,TRNS,
TRNS,TRNS,TRNS,TRNS,TRNS,TRNS,TRNS,
TRNS,TRNS,TRNS,TRNS,TRNS,
TRNS,TRNS,
TRNS,
F,TRNS,TRNS,
// right hand
F12 ,F6 ,F7 ,F8 ,F9 ,F10 ,TRNS,
TRNS,TRNS,HOME,PGUP,TRNS,TRNS,TRNS,
LEFT,DOWN,UP, RIGHT,TRNS,TRNS,
TRNS,TRNS,END,PGDOWN,TRNS,TRNS,TRNS,
TRNS,TRNS,TRNS,TRNS,TRNS,
TRNS,TRNS,
TRNS,
TRNS,TRNS,F
),
KEYMAP( // Layer 2 J lock
// left hand
TRNS,F1 ,F2 ,F3 ,F4 ,F5 ,F11,
TRNS,TRNS,TRNS,TRNS,TRNS,TRNS,TRNS,
TRNS,TRNS,TRNS,TRNS,TRNS,TRNS,
TRNS,TRNS,TRNS,TRNS,TRNS,TRNS,TRNS,
TRNS,TRNS,TRNS,TRNS,TRNS,
TRNS,TRNS,
TRNS,
J,TRNS,TRNS,
// right hand
F12 ,F6 ,F7 ,F8 ,F9 ,F10 ,TRNS,
TRNS,TRNS,HOME,PGUP,TRNS,TRNS,TRNS,
LEFT,TRNS,UP, RIGHT,TRNS,TRNS,
TRNS,TRNS,END,PGDOWN,TRNS,TRNS,TRNS,
TRNS,TRNS,TRNS,TRNS,TRNS,
TRNS,TRNS,
TRNS,
TRNS,TRNS,J
),
KEYMAP( // Layer 3 D_CTL lock
// left hand
TRNS,TRNS,TRNS,TRNS,TRNS,TRNS,TRNS,
TRNS,FN0, FN1, FN2, FN3, FN4, FN27,
TRNS,FN10,FN11,FN31,FN13,FN14,
TRNS,FN20,FN21,FN22,FN23,FN24,TRNS,
TRNS,TRNS,TRNS,TRNS,TRNS,
FN28,FN29,
TRNS,
D,TRNS,TRNS,
// right hand
TRNS,TRNS,TRNS,TRNS,TRNS,TRNS,TRNS,
FN27,FN5, FN6, FN7, FN8, FN9, TRNS,
FN15,FN16,FN17,FN18,FN19, TRNS,
TRNS,FN25,FN26,TRNS,TRNS,TRNS, TRNS,
TRNS,TRNS,TRNS,TRNS,TRNS,
FN28,FN29,
TRNS,
TRNS,TRNS,D
),
KEYMAP( // Layer 4 K_CTL lock
// left hand
TRNS,TRNS,TRNS,TRNS,TRNS,TRNS,TRNS,
TRNS,FN0, FN1, FN2, FN3, FN4, FN27,
TRNS,FN10,FN11,FN12,FN13,FN14,
TRNS,FN20,FN21,FN22,FN23,FN24,TRNS,
TRNS,TRNS,TRNS,TRNS,TRNS,
FN28,FN29,
TRNS,
D,TRNS,TRNS,
// right hand
TRNS,TRNS,TRNS,TRNS,TRNS,TRNS,TRNS,
FN27,FN5, FN6, FN7, FN8, FN9, TRNS,
FN15,FN16,FN30,FN18,FN19, TRNS,
TRNS,FN25,FN26,TRNS,TRNS,TRNS,TRNS,
TRNS,TRNS,TRNS,TRNS,TRNS,
FN29,FN28,
TRNS,
TRNS,TRNS,K
),
};
enum function_id {
LCTL_LPAREN,
RCTL_LPAREN
};
/*
* Fn action definition
*/
static const uint16_t PROGMEM fn_actions[] = {
[0] = ACTION_MODS_TAP_KEY(MOD_RALT, KC_RBRC), // FN0 - CTRL + J
[1] = ACTION_MODS_TAP_KEY(MOD_LALT, KC_LBRC), // FN1 - CTRL + F
[2] = ACTION_FUNCTION_TAP(LCTL_LPAREN), // FN2
[3] = ACTION_FUNCTION_TAP(RCTL_LPAREN), // FN3
[4] = ACTION_MODS_ONESHOT(MOD_LSFT), // FN4
[5] = ACTION_LAYER_TAP_KEY(1, KC_F), // FN5
[6] = ACTION_MODS_TAP_KEY(MOD_LGUI, KC_ESC), // FN6
[7] = ACTION_LAYER_TAP_KEY(2, KC_J), // FN7
[8] = ACTION_LAYER_TAP_KEY(3, KC_D), // FN8
[9] = ACTION_LAYER_TAP_KEY(4, KC_K), //FN9
[31] = ACTION_LAYER_TAP_KEY(1, KC_F),
};
static const uint16_t PROGMEM fn_actions_ctl [] = {
[30] = ACTION_LAYER_TAP_KEY(4, KC_K), //K return function
[31] = ACTION_LAYER_TAP_KEY(3, KC_D), // D return function
[0] = ACTION_MODS_KEY(MOD_LCTL, KC_Q),
[1] = ACTION_MODS_KEY(MOD_LCTL, KC_W),
[2] = ACTION_MODS_KEY(MOD_LCTL, KC_E),
[3] = ACTION_MODS_KEY(MOD_LCTL, KC_R),
[4] = ACTION_MODS_KEY(MOD_LCTL, KC_T),
[5] = ACTION_MODS_KEY(MOD_LCTL, KC_Y),
[6] = ACTION_MODS_KEY(MOD_LCTL, KC_U),
[7] = ACTION_MODS_KEY(MOD_LCTL, KC_I),
[8] = ACTION_MODS_KEY(MOD_LCTL, KC_O),
[9] = ACTION_MODS_KEY(MOD_LCTL, KC_P),
[10] = ACTION_MODS_KEY(MOD_LCTL, KC_A),
[11] = ACTION_MODS_KEY(MOD_LCTL, KC_S),
[12] = ACTION_MODS_KEY(MOD_LCTL, KC_D),
[13] = ACTION_MODS_KEY(MOD_LCTL, KC_F),
[14] = ACTION_MODS_KEY(MOD_LCTL, KC_G),
[15] = ACTION_MODS_KEY(MOD_LCTL, KC_H),
[16] = ACTION_MODS_KEY(MOD_LCTL, KC_J),
[17] = ACTION_MODS_KEY(MOD_LCTL, KC_K),
[18] = ACTION_MODS_KEY(MOD_LCTL, KC_L),
[19] = ACTION_MODS_KEY(MOD_LCTL, KC_X),
[20] = ACTION_MODS_KEY(MOD_LCTL, KC_Z),
[21] = ACTION_MODS_KEY(MOD_LCTL, KC_X),
[22] = ACTION_MODS_KEY(MOD_LCTL, KC_C),
[23] = ACTION_MODS_KEY(MOD_LCTL, KC_V),
[24] = ACTION_MODS_KEY(MOD_LCTL, KC_B),
[25] = ACTION_MODS_KEY(MOD_LCTL, KC_N),
[26] = ACTION_MODS_KEY(MOD_LCTL, KC_M),
[27] = ACTION_MODS_KEY(MOD_LCTL, KC_TAB),
[28] = ACTION_MODS_KEY(MOD_LCTL, KC_ENTER),
[29] = ACTION_MODS_KEY(MOD_LCTL, KC_SPACE)
};
/*
* user defined action function
*/
void action_function(keyrecord_t *record, uint8_t id, uint8_t opt)
{
// if (record->event.pressed) print("Press"); else print("Release");
// if (record->tap.interrupted) print("interrupted");
// print("\n");
switch (id) {
case LCTL_LPAREN:
// Shift parentheses example: LShft + tap '('
// http://stevelosh.com/blog/2012/10/a-modern-space-cadet/#shift-parentheses
// http://geekhack.org/index.php?topic=41989.msg1304899#msg1304899
if (record->event.pressed) {
if (record->tap.count > 0 && !record->tap.interrupted) {
if (record->tap.interrupted) {
print("tap interrupted\n");
register_mods(MOD_BIT(KC_LCTL));
}
} else {
register_mods(MOD_BIT(KC_LCTL));
}
} else {
if (record->tap.count > 0 && !(record->tap.interrupted)) {
add_weak_mods(MOD_BIT(KC_LSHIFT));
send_keyboard_report();
register_code(KC_LBRACKET);
unregister_code(KC_LBRACKET);
del_weak_mods(MOD_BIT(KC_LSHIFT));
send_keyboard_report();
record->tap.count = 0; // ad hoc: cancel tap
} else {
unregister_mods(MOD_BIT(KC_LCTL));
}
}
break;
case RCTL_LPAREN:
// Shift parentheses example: LShft + tap '('
// http://stevelosh.com/blog/2012/10/a-modern-space-cadet/#shift-parentheses
// http://geekhack.org/index.php?topic=41989.msg1304899#msg1304899
if (record->event.pressed) {
if (record->tap.count > 0 && !record->tap.interrupted) {
if (record->tap.interrupted) {
print("tap interrupted\n");
register_mods(MOD_BIT(KC_RCTL));
}
} else {\
register_mods(MOD_BIT(KC_RCTL));
}
} else {
if (record->tap.count > 0 && !(record->tap.interrupted)) {
add_weak_mods(MOD_BIT(KC_RSHIFT));
send_keyboard_report();
register_code(KC_RBRACKET);
unregister_code(KC_RBRACKET);
del_weak_mods(MOD_BIT(KC_RSHIFT));
send_keyboard_report();
record->tap.count = 0; // ad hoc: cancel tap
} else {
unregister_mods(MOD_BIT(KC_RCTL));
}
}
break;
}
}
#define FN_ACTIONS_SIZE (sizeof(fn_actions) / sizeof(fn_actions[0]))
#define FN_ACTIONS_CTL_SIZE (sizeof(fn_actions_ctl) / sizeof(fn_actions_ctl[0]))
/*
* translates Fn keycode to action
* for some layers, use different translation table
*/
action_t keymap_fn_to_action(uint8_t keycode)
{
uint8_t layer = biton32(layer_state);
action_t action;
action.code = ACTION_NO;
// CTRL Layer
if ((layer == 3 || layer == 4) && FN_INDEX(keycode) < FN_ACTIONS_CTL_SIZE) {
action.code = pgm_read_word(&fn_actions_ctl[FN_INDEX(keycode)]);
}
// by default, use fn_actions from debug_hexdefault layer 0
// this is needed to get mapping for same key, that was used switch to some layer,
// to have possibility to switch layers back
else if (action.code == ACTION_NO && FN_INDEX(keycode) < FN_ACTIONS_SIZE) {
action.code = pgm_read_word(&fn_actions[FN_INDEX(keycode)]);
}
debug("FN:\t"); debug_dec(FN_INDEX(keycode));
debug("\tLayer: \t"); debug_dec(layer);
debug("\tCode: \t"); debug_hex(action.code);
debug("\n");
return action;
}
Current implementation of ACTION_MODS_TAP_KEY
works like 'modifier key' rather than 'alpha key'.
(See #215) Probably it works well when it is placed on 'modifier key' but not when on 'alpha key'.
Meanwhile, ACTION_LAYER_TAP_KEY
behaves reversely.
ACTION_MODS_TAP_KEY
is 'mod/key' configuration and ACTION_LAYER_TAP_KEY
is 'key/mod' in terminology in the first comment.
For refernece, QMK has build option IGNORE_MOD_TAP_INTERRUPT to change ACTION_MODS_TAP_KEY behaviour as discussed in #215. https://github.com/qmk/qmk_firmware/blob/732a115b32a9c6aa529c53ef52a9689b5901411d/tmk_core/common/action.c
It seems to be reasonable and third soution would be preferable. https://github.com/qmk/qmk_firmware/issues/941
ACTION_TAP_KEY_MODS
can be used on alpha keys. This behaves as 'key/mod' configuration.
To test this placed MODS_TAP_KEY on home row, which plays dual role of both shift and normal key.
I found these problems:
1. MODS_TAP_KEY placed on alpha part often misinterpret as modifier.
2. no key repeat is very annoying on bash(C+f) and vim(h/j/k/l)
Work around:
1. dual role key should has two configuration: key/mod or mod/key
1a. key/mod: handled as key mainly, almost can be used as usual key
Implemented
ACTION_TAP_KEY_MODS
at 22865cf.1b. mod/key: handled as modifier mainly, almost can be used as usual modifier
Use
ACTION_MODS_TAP_KEY
for this purpose.2.
instead of timeout cancel do key repeat (depending on configuration)This is not good idea. You should get used to 'Double Tap Repeat' or give up Tap Key.
Testing keymap of HHKB: