tmk / tmk_keyboard

Keyboard firmwares for Atmel AVR and Cortex-M
3.99k stars 1.71k forks source link

Tapping: MODS_TAP_KEY doesn't work on alpha key area #85

Closed tmk closed 3 days ago

tmk commented 10 years ago

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:

    KEYMAP(ESC, 1,   2,   3,   4,   5,   6,   7,   8,   9,   0,   MINS,EQL, BSLS,GRV, \
           TAB, Q,   W,   E,   R,   T,   Y,   U,   I,   O,   P,   LBRC,RBRC,BSPC, \
           LCTL,FN10,FN11,FN12,FN13,FN14,FN15,FN16,FN17,FN18,FN3, QUOT,FN4, \
           FN5, Z,   X,   C,   V,   B,   N,   M,   COMM,DOT, FN2, RSFT,FN1, \
                LGUI,LALT,          FN6,                RALT,RGUI),

    [10] = ACTION_MODS_TAP_KEY(MOD_LSFT, KC_A),
    [11] = ACTION_MODS_TAP_KEY(MOD_LSFT, KC_S),
    [12] = ACTION_MODS_TAP_KEY(MOD_LSFT, KC_D),
    [13] = ACTION_MODS_TAP_KEY(MOD_LSFT, KC_F),
    [14] = ACTION_MODS_TAP_KEY(MOD_LSFT, KC_G),
    [15] = ACTION_MODS_TAP_KEY(MOD_LSFT, KC_H),
    [16] = ACTION_MODS_TAP_KEY(MOD_LSFT, KC_J),
    [17] = ACTION_MODS_TAP_KEY(MOD_LSFT, KC_K),
    [18] = ACTION_MODS_TAP_KEY(MOD_LSFT, KC_L),
tmk commented 10 years 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
adamgordonbell commented 9 years ago

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;
}
tmk commented 9 years ago

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.

tmk commented 7 years ago

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

tmk commented 7 years ago

It seems to be reasonable and third soution would be preferable. https://github.com/qmk/qmk_firmware/issues/941

tmk commented 3 days ago

ACTION_TAP_KEY_MODS can be used on alpha keys. This behaves as 'key/mod' configuration.