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

Keyboard combination triggers #960

Merged
merged 5 commits into from
Feb 7, 2017
Merged

Conversation

ofples
Copy link
Contributor

@ofples ofples commented Dec 16, 2016

I added a feature that allows the user to specify key combos that trigger sending a keycode or calling a function.
For example: KC_Q + KC_W + KC_E could trigger the keyboard to reset.
Alternatively, it's also possible to have the combo call the process_combo_event function, passing as an argument the index of the combo in the key_combos array.
I will soon document the full usage and features of this addition in the wiki.
Feel free to CR and comment on the code.

@fredizzimo
Copy link
Contributor

I'm haven't checked the actual implementation and what we already have in QMK. But isn't this very similar to Brainstorming on chording?

@ofples
Copy link
Contributor Author

ofples commented Dec 28, 2016

Same concept different implementation.
Chording wasn't ready yet and I wanted this for my keyboard.
I already have some ideas for how to make it better.
The worst issue as of now being that with combos defined, keys may seem to fire in the wrong order because the combo keys are only fired on keyup or after a timeout.
The solution I think (now talking about my implementation) would be to not support multiple combos at the same time but instead cause any key that is not part of the current combo to "stop" the combo, sending out all of the currently held keys in the correct order, followed by the key that was pressed.

@jackhumbert
Copy link
Member

Cool! Thanks for adding :)

@jackhumbert jackhumbert merged commit 4348fb5 into qmk:master Feb 7, 2017
@dunkarooftop
Copy link

@ofples This is exactly what I am looking for, can you show me a example how to use it in Keymap file?
Thank you !! :)

I am looking for pretty simple function say press A+B send CMD+C for copy

@ofples
Copy link
Contributor Author

ofples commented Apr 20, 2017

@dunkarooftop Wow I totally forgot to add docs. Will try to get to that soon.

Anyway, as you can probably see from my previous comment on this PR, my current implementation is flawed in that keys that are part of combos may fire in the wrong order if a combo isn't completed.
If you don't mind that, here is a usage snippet:

In your rules.mk:
COMBO_ENABLE ?=yes # Enable key combinations

In your config.h:
#define COMBO_COUNT 1

And in keymap.c:

const uint16_t PROGMEM test_combo[] = {KC_A, KC_B, COMBO_END};
combo_t key_combos[COMBO_COUNT] = {COMBO(test_combo, KC_ESC)};

In this example hitting A+B would send the ESCAPE key.

If you wanted to do something more complicated, you could override the process_combo_event and add a case for your combo index:

const uint16_t PROGMEM meta_combo[] = {KC_FN0, KC_FN1, COMBO_END};
combo_t key_combos[COMBO_COUNT] = {
    [META_LAYER_COMBO_INDEX] = COMBO_ACTION(meta_combo),
};

void process_combo_event(uint8_t combo_index, bool pressed) {
    switch(combo_index) {
    case META_LAYER_COMBO_INDEX:
        if (pressed) {
            layer_on(_META);
        }
        break;
    }
}

If your having any trouble (or if you find any bugs), feel free to message me.

Good luck!

@dunkarooftop
Copy link

dunkarooftop commented Apr 20, 2017

@ofples dude, thank you sooo much this is working so well :)

one question the time out time is set to TAP_TERM in config.h correct ?

I found it from process_combo.h

#ifndef COMBO_TERM
#define COMBO_TERM TAPPING_TERM
#endif

@dunkarooftop
Copy link

@ofples I get the 1st example to work but get some trouble try the more complex one, do you mind take a look at my hack? (I don't know C, everything I do is reading someone else's code and and guess)

What I want to have is when press W+F send CMD+C and W+T send CMD+V

enum process_combo_event {
  WF_COPY, 
  WT_PASTE, 
  };

const uint16_t PROGMEM copy_combo[] = {KC_W, KC_F, COMBO_END};
combo_t key_combos[COMBO_COUNT] = {
    [WF_COPY] = COMBO_ACTION(copy_combo),
};

const uint16_t PROGMEM paste_combo[] = {KC_W, KC_T, COMBO_END};
combo_t key_combos[COMBO_COUNT] = {
    [WT_PASTE] = COMBO_ACTION(paste_combo),
};

void process_combo_event(uint8_t combo_index, bool pressed) {
    switch(combo_index) {
    case WF_COPY:
        if (pressed) {
                add_weak_mods(MOD_BIT(KC_LGUI));
                send_keyboard_report();
                register_code(KC_C);
                unregister_code(KC_C);
                del_weak_mods(MOD_BIT(KC_LGUI));
                send_keyboard_report();
        }
        break;

    case WT_PASTE:
        if (pressed) {
                add_weak_mods(MOD_BIT(KC_LGUI));
                send_keyboard_report();
                register_code(KC_V);
                unregister_code(KC_V);
                del_weak_mods(MOD_BIT(KC_LGUI));
                send_keyboard_report();
        }
        break;
    }
}

and the error message I get it

s/hhkb/keymaps/dunk/keymap.c:475:9: error: redefinition of 'key_combos'
 combo_t key_combos[COMBO_COUNT] = {
         ^
keyboards/hhkb/keymaps/dunk/keymap.c:470:9: note: previous definition of 'key_combos' was here
 combo_t key_combos[COMBO_COUNT] = {
         ^
keyboards/hhkb/keymaps/dunk/keymap.c:476:5: error: array index in initializer exceeds array bounds
     [WT_PASTE] = COMBO_ACTION(paste_combo),
     ^
keyboards/hhkb/keymaps/dunk/keymap.c:476:5: error: (near initialization for 'key_combos')
 [ERRORS]
 | 
 | 
 | 
make[1]: *** [.build/obj_hhkb_dunk/keyboards/hhkb/keymaps/dunk/keymap.o] Error 1
make: *** [hhkb-allsp-allkm] Error 1
Make finished with errors

Thank you again !!

@dunkarooftop
Copy link

dunkarooftop commented Apr 20, 2017

After some monkeying around, I change #define COMBO_COUNT to 2 in config.h

and fix the fallowing code:

enum process_combo_event {
  WF_COPY, 
  WT_PASTE, 
  };

const uint16_t PROGMEM copy_combo[] = {KC_W, KC_F, COMBO_END};
const uint16_t PROGMEM paste_combo[] = {KC_W, KC_T, COMBO_END};

combo_t key_combos[COMBO_COUNT] = {
    [WF_COPY] = COMBO_ACTION(copy_combo),
    [WT_PASTE] = COMBO_ACTION(paste_combo),
};

void process_combo_event(uint8_t combo_index, bool pressed) {
    switch(combo_index) {
    case WF_COPY:
        if (pressed) {
                add_weak_mods(MOD_BIT(KC_LGUI));
                send_keyboard_report();
                register_code(KC_C);
                unregister_code(KC_C);
                del_weak_mods(MOD_BIT(KC_LGUI));
                send_keyboard_report();
        }
        break;

    case WT_PASTE:
        if (pressed) {
                add_weak_mods(MOD_BIT(KC_LGUI));
                send_keyboard_report();
                register_code(KC_V);
                unregister_code(KC_V);
                del_weak_mods(MOD_BIT(KC_LGUI));
                send_keyboard_report();
        }
        break;
    }
}

It compile ok but now when ever I type w it send ww and when do the combo it send W, CMD+C. There is extra W being send in the begining. Wondering where I screw up.

EDIT: After trying a few different setting I think the problem is I use W in both case. It work fine when I set to W+F and N+E when the character doesn't shadow each other.

Is there a work around for this? :)

@ofples
Copy link
Contributor Author

ofples commented Apr 22, 2017

I see, looks like you found a bug!
Will try to fix it today :)

@dunkarooftop
Copy link

dunkarooftop commented Apr 22, 2017

@ofples Thank you for spending the time looking into it. :)

On mac there is a really powerful tool call "Karabiner" it does have Simultaneous Key Presses function, it's written in C++ (sadly due to Apple written the keyboard driver, it no longer work in the latest macOS.) Maybe you can find some inspiration there. Too bad it's too hard for me as non coder to understand.

https://pqrs.org/osx/karabiner/xml.html.en
scroll down to __SimultaneousKeyPresses__ of autogen syntax

Edit. Forget to mention the source code is on github

@dunkarooftop
Copy link

@ofples
Wondering if you ever get a chance to fix the bug, if you are busy don't have time it's not a big deal. Just wanna touch base on the progress. Thanks :)

@ofples
Copy link
Contributor Author

ofples commented Jun 4, 2017

@dunkarooftop
Sadly I haven't been able to get to fixing it.
I'll make sure to let you know when I do!

@dunkarooftop
Copy link

@ofples
No worry at all, I understand people are busy now days :)

One problem here, out of no where I start to get compile error, I pull the latest and did the simple tweak as in your example with only one combo on one the HHKB default keymap (not my crazy keymap) result the same error

Compiling: quantum/process_keycode/process_combo.c                                                 quantum/process_keycode/process_combo.c: In function 'process_combo':
quantum/process_keycode/process_combo.c:120:26: error: array subscript is above array bounds [-Werror=array-bounds]
         combo_t *combo = &key_combos[current_combo_index];
                          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
cc1: all warnings being treated as errors
 [ERRORS]
 | 
 | 
 | 
make[1]: *** [.build/obj_hhkb_default/quantum/process_keycode/process_combo.o] Error 1
make: *** [hhkb-allsp-allkm] Error 1
Make finished with errors

Can you take a look when you have time thanks

@fredizzimo
Copy link
Contributor

The warnings were turned into errors a few weeks ago, so that's the reason the changed behaviour.

The easy fix is to change the array definition at the top of the file into this

__attribute__ ((weak))
combo_t key_combos[COMBO_COUNT] = {
};

So that it includes the size.

Ideally the array should not be weak and defined there at all, just declared so that you get a linker error if the keymap doesn't define it.

@dunkarooftop
Copy link

dunkarooftop commented Jun 5, 2017

@fredizzimo thanks for the reply :)

I added your code to the top of process_combo.c

__attribute__ ((weak))
combo_t key_combos[COMBO_COUNT] = {
};

and get the fallowing error

Compiling: keyboards/hhkb/matrix.c                                                                  [OK]
Compiling: keyboards/hhkb/hhkb.c                                                                    [OK]
Compiling: keyboards/hhkb/keymaps/dunk/keymap.c                                                     [OK]
Compiling: quantum/quantum.c                                                                        [OK]
Compiling: quantum/keymap_common.c                                                                 In file included from ./tmk_core/common/progmem.h:5:0,
                 from ./tmk_core/common/action_macro.h:20,
                 from ./tmk_core/common/action.h:25,
                 from quantum/keymap.h:23,
                 from quantum/keymap_common.c:18:
quantum/keymap_common.c: In function 'keymap_function_id_to_action':
quantum/keymap_common.c:182:23: warning: array subscript is above array bounds [-Warray-bounds]
  return pgm_read_word(&fn_actions[function_id]);
                       ^
 [OK]
Compiling: quantum/keycode_config.c                                                                 [OK]
Compiling: quantum/process_keycode/process_leader.c                                                 [OK]
Compiling: quantum/process_keycode/process_combo.c                                                 quantum/process_keycode/process_combo.c:11:9: error: redefinition of 'key_combos'
 combo_t key_combos[] = {
         ^~~~~~~~~~
quantum/process_keycode/process_combo.c:4:9: note: previous definition of 'key_combos' was here
 combo_t key_combos[COMBO_COUNT] = {
         ^~~~~~~~~~
 [ERRORS]
 | 
 | 
 | 
make[1]: *** [.build/obj_hhkb_dunk/quantum/process_keycode/process_combo.o] Error 1
make: *** [hhkb-allsp-allkm] Error 1
Make finished with errors

@fredizzimo
Copy link
Contributor

You need to replace the current definition, the one without COMBO_COUNT

@dunkarooftop
Copy link

@fredizzimo
Thank you !! Now it compile with a row of pretty [OK] :)

@dunkarooftop
Copy link

@fredizzimo
after your tweak it end up having a weird side effect #1370

Do you know any other way to get around this? Or is it possible to disable warning become error so it can compile like before?

I am a artist and my right hand is always holding a styles, that's why I need to cram everything I need to do on to the left hand. This combo function helps a lot with it. Thanks again.

register_code16(keycode);
send_keyboard_report();
unregister_code16(keycode);
#endif
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this causes the issue #1370, reported by @dunkarooftop.

The code just blindly sends the keypress on release. The fix does not appear to be easy though, it would requre something similar to what are done for the actions.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hey @fredizzimo, sorry for rezzing an old thread but can you explain this a bit more? I'm developing a key profile for the gherkin that relies heavily on combos: http://www.keyboard-layout-editor.com/#/gists/3d8f9946bd7833f0e9938b63977443d9 and I'm starting to notice the out of order keys. When you say 'it would require something similar to what are done for the actions', do you mean directly above or in some other file somewhere? I'm new to this codebase, and it's been a while since I've done C, but my keymap requires this feature, so if it's in my power I'd love to fix it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually that wasn't too crazy, I've got a proof of concept working with a global register. I'd love input around properly re-triggering records and their lifecycle but maybe that's more appropriately resolved in Gitter

@dunkarooftop
Copy link

dunkarooftop commented Sep 5, 2017 via email

@gcaferra
Copy link

Hi I can't compile it I get always this error:
Compiling: quantum/process_keycode/process_combo.c quantum/process_keycode/process_combo.c: In function 'process_combo':
quantum/process_keycode/process_combo.c:120:26: error: array subscript is above array bounds [-Werror=array-bounds]
combo_t *combo = &key_combos[current_combo_index];

in my config.h i have:
#undef COMBO_COUNT
#define COMBO_COUNT 2

anyone can help me to build this?

@deftsp
Copy link
Contributor

deftsp commented Oct 28, 2017

@gcaferra Fixed it with #1920

@ofples ofples deleted the feature/combos branch November 3, 2017 09:01
@dragonfax
Copy link
Contributor

Couldn't use COMBO with RESET keycode. Had to use COMBO_ACTION and issue the reset using reset_keyboard() in process_combo_event

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

Successfully merging this pull request may close these issues.

8 participants