tmk_keyboard icon indicating copy to clipboard operation
tmk_keyboard copied to clipboard

Tapping: MODS_TAP_KEY doesn't work on alpha key area

Open tmk opened this issue 11 years ago • 5 comments

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 1b. mod/key: handled as modifier mainly, almost can be used as usual modifier

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 avatar Dec 16 '13 12:12 tmk

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

tmk avatar Apr 24 '14 16:04 tmk

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;
}


adamgordonbell avatar Jan 29 '15 23:01 adamgordonbell

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 avatar May 22 '15 20:05 tmk

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 avatar Jun 03 '17 21:06 tmk

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

tmk avatar Jun 03 '17 21:06 tmk