qmk / qmk_firmware

Open-source keyboard firmware for Atmel AVR and Arm USB families
https://qmk.fm
GNU General Public License v2.0
17.52k stars 37.8k forks source link

{HOWTO} Can I set a double tap on semicolon ; to send a colon : #127

Closed dkarter closed 8 years ago

dkarter commented 8 years ago

I want to be able to double tap the semicolon button and send a colon but have it behave normally when I press it once, is that possible? What would be the easiest way to do that?

Single press -> semicolon Double tap -> colon Shift + Single press -> colon

I'm currently doing that with Karabiner on Mac but would like to have it programmed into the keyboard so that I can take it with me.

dkarter commented 8 years ago

Solved by @Eric-L-T in this gist: https://gist.github.com/Eric-L-T/09fd1423106119c65aa3

case 0:
  if (record->event.pressed) {
    if (record->tap.count <= 1) {
      register_code(KC_SCLN);
    } else if (record->tap.count = 2) {
      register_code(KC_LSFT);
      register_code(KC_SCLN);
      unregister_code(KC_SCLN);
      unregister_code(KC_LSFT);
      record->tap.count = 0;
    }
  } else if (record->tap.count <= 1) {
    unregister_code(KC_SCLN);
    record->tap.count = 0;
  }
break;

Thanks!!!

dkarter commented 8 years ago

Actually it did not work, that was Karabiner on my machine that made it "work" - reopening.

The tap.count is not being incremented at all. Stays at 0.

I have set the TAPPING_TERM to 300, mapped the key using M(1) and tested using the following:

const macro_t *action_get_macro(keyrecord_t *record, uint8_t id, uint8_t opt)
{
  // MACRODOWN only works in this function
  switch(id) {
    case 0:
       /* ... */
       break;
    case 1:
      print("macro pressed \n");
      if (record->event.pressed) {
        print("taps: ");
        pdec(record->tap.count);
        print("\n");
        if (record->tap.count == 1) {
          register_code(KC_SCLN);
        } else if (record->tap.count == 2) {
          register_code(KC_COLN);
        }
      } else {
        if (record->tap.count == 1) {
          unregister_code(KC_SCLN);
        } else if (record->tap.count == 2) {
          unregister_code(KC_COLN);        
        }
      }
      break;
  }
  return MACRO_NONE;
};

I am getting back:

print("macro pressed \n");
taps: 0
print("macro pressed \n");

Any ideas what I'm doing wrong?

@jackhumbert / @ezuk ?

jackhumbert commented 8 years ago

I'm not sure how tap.count is implemented, but this might be easier with a timer and a initialiser variable. The timer would be necessary to make sure the semicolon isn't sent every time.

Also, register_code(KC_COLN); is incorrect - you can't use the special keycodes with register_code. You'd need to send the shift key like in your first example.

mecanogrh commented 8 years ago

As Jack said this will never work as first tap of your double tap will be identified as tap.count==1 A timer is definitively needed here, get a read on press and another one on depress, then it's just a matter of adjusting the threshold for taps to your liking.

mecanogrh commented 8 years ago

Oh and you need the timer interrupt in loop to check for single tap timeout on fast single tap when no second tap is done but I'm not sure it will allow to register well when taping other keys then, the following code kinda works but do not register fast single tap (note that it handles layer momentary switch on hold as well) :


void action_mods_tap_layer_tmp(keyrecord_t *record, uint8_t layer, uint8_t mod_tap, uint8_t key_tap, uint8_t two_tap)
{
    timer_init(); // needed to take prescaler value into account
    static uint32_t start;
    static uint32_t epress;
    static uint32_t dpress;

    if (record->event.pressed) {
        start = timer_read32();
        if (record->tap.count > 0) {
            if (record->tap.interrupted) {
                record->tap.count = 0;  // ad hoc: cancel tap
                layer_on(layer);
                debug("interrupted layer on\n");
            } else {
              epress = timer_elapsed(start);
              dprintf("epress:(%u)\n",epress);
            }
        } else {
            layer_on(layer);
            debug("layer on\n");
        }
    } else {
        if (record->tap.count > 0) {
            dpress = timer_elapsed32(start);
            dprintf("dpress:(%u)\n",dpress);
            if (record->tap.count >= 2) {
                register_code(two_tap);
                debug("two taps\n");
                unregister_code(two_tap);
                record->tap.count = 0;  // ad hoc: cancel tap
            } else {
                if (epress - dpress > 0) {
                    debug("one tap\n");
                    dprintf("elapsed:(%u)\n",timer_elapsed32(start));
                    if (mod_tap > 0) {
                        add_weak_mods(MOD_BIT(mod_tap));
                        send_keyboard_report();
                    }
                    register_code(key_tap);
                    if (mod_tap > 0) {
                        del_weak_mods(MOD_BIT(mod_tap));
                        send_keyboard_report();
                    }
                    unregister_code(key_tap);
                    record->tap.count = 0;  // ad hoc: cancel tap
                }
            }
        } else {
            layer_off(layer);
        }
    }
}
dkarter commented 8 years ago

The timer approach was the correct one, it worked out great! thank you all for helping out!!!

Here's the my solution:

static uint16_t key_timer;
static uint16_t semi_colon_taps = 0;

const macro_t *action_get_macro(keyrecord_t *record, uint8_t id, uint8_t opt)
{
  // MACRODOWN only works in this function
  switch(id) {
    case 0:
      /* ... */
      break;
    case 1:
      if (record->event.pressed) {
        //  key pressed -> immidiately send ;, start timer
        //  count time elapsed since last key pressed
        //    if bigger than 200ms -> reset timer
        //    if smaller than 200ms -> send backspace and :, reset timer

        if (!key_timer || timer_elapsed(key_timer) > 200) {
          semi_colon_taps = 1;
        } else {
          semi_colon_taps += 1;
        }

        key_timer = timer_read();

        if (semi_colon_taps == 1) {
          register_code(KC_SCLN);
        } else {
          // delete semicolon
          register_code(KC_BSPC);
          unregister_code(KC_BSPC);

          // insert colon
          register_code(KC_LSFT);
          register_code(KC_SCLN);
          unregister_code(KC_SCLN);
          unregister_code(KC_LSFT);
        }
      } else {
        unregister_code(KC_SCLN);
      }
      break;
  }
  return MACRO_NONE;
};

I also implemented sticky shift using a similar idea, if you are interested: https://github.com/dkarter/qmk_firmware/blob/master/keyboard/ergodox_ez/keymaps/doriank_osx/keymap.c

ezuk commented 8 years ago

Just wondering, do you have a layout in keymaps/ that uses this? Would love to see this in a pull request and documented :)

On 7 February 2016 at 20:03, Dorian Karter notifications@github.com wrote:

The timer approach was the correct one, it worked out great! thank you all for helping out!!!

Here's the my solution:

static uint16_t key_timer;static uint16_t semi_colon_taps = 0; const macro_t _action_get_macro(keyrecord_t record, uint8_t id, uint8t opt) { // MACRODOWN only works in this function switch(id) { case 0: / ... / break; case 1: if (record->event.pressed) { // key pressed -> immidiately send ;, start timer // count time elapsed since last key pressed // if bigger than 200ms -> reset timer // if smaller than 200ms -> send backspace and :, reset timer

    if (!key_timer || timer_elapsed(key_timer) > 200) {
      semi_colon_taps = 1;
    } else {
      semi_colon_taps += 1;
    }

    key_timer = timer_read();

    if (semi_colon_taps == 1) {
      register_code(KC_SCLN);
    } else {
      // delete semicolon
      register_code(KC_BSPC);
      unregister_code(KC_BSPC);

      // insert colon
      register_code(KC_LSFT);
      register_code(KC_SCLN);
      unregister_code(KC_SCLN);
      unregister_code(KC_LSFT);
    }
  } else {
    unregister_code(KC_SCLN);
  }
  break;

} return MACRO_NONE; };

— Reply to this email directly or view it on GitHub https://github.com/jackhumbert/qmk_firmware/issues/127#issuecomment-181149975 .

mecanogrh commented 8 years ago

Problem with that solution (which is how Karabiner handles single tap noise by adding a backspace output) is that you can't use keys for inputing sequences in software ie if software is waiting for 3-d and you send 3-x-backspace-d, it won't make it.