Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
dkarter opened this issue Feb 5, 2016 · 8 comments
Closed

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

dkarter opened this issue Feb 5, 2016 · 8 comments

Comments

@dkarter
Copy link

dkarter commented Feb 5, 2016

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
Copy link
Author

dkarter commented Feb 7, 2016

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 dkarter closed this as completed Feb 7, 2016
@dkarter
Copy link
Author

dkarter commented Feb 7, 2016

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 ?

@dkarter dkarter reopened this Feb 7, 2016
@jackhumbert
Copy link
Member

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
Copy link

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
Copy link

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
Copy link
Author

dkarter commented Feb 8, 2016

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

@dkarter dkarter closed this as completed Feb 8, 2016
@ezuk
Copy link
Contributor

ezuk commented Feb 8, 2016

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 [email protected] 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, 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;
};


Reply to this email directly or view it on GitHub
#127 (comment)
.

@mecanogrh
Copy link

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.

danieldirks pushed a commit to danieldirks/qmk_firmware that referenced this issue Jul 21, 2023
Move `LAYOUT` options back into seperate variants
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants