qmk / qmk_firmware

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

Brainstorming on chording #168

Closed ezuk closed 4 years ago

ezuk commented 8 years ago

Some notes from a call with @jackhumbert on chording:

would be good to implement chording as CC_ (instead of KC_). And then a key where CC_ is used would only fire on keyup.

There would be a dictionary under the layout that looks something like:

CC_A && CC_R: LALT(F4) // sends Alt+F4 when you hold down A and R and then let go

If you define CC_S for example but then not define a chord for it, it would just send S on keyup. This way you get consistent behavior in your layout if you want it -- all keys firing on keyup if you want them to.

We would not make a distinction about which key in the chord gets pressed first.

We would support up to 2 keys per individual chord initially.

You'd need to include chording.h to enable this on your layout.

There would be a makefile option to flip this off to save on filesize if needed.

Tuckie commented 8 years ago

I would love for a way to support this with a timeout rather than key up (perhaps in addition to?), that way I could press and hold a "key"

jackhumbert commented 8 years ago

Timeouts are kind of difficult on the implementation side of things (no event to go on, just a callback), but yeah, it'd be neat to have that in combination with the keyup.

mecanogrh commented 8 years ago

I think we can have timeout for this as it won't conflict with a following key press, I mean if the key is hold and another non matching key is pressed we interrupt the timer and just cancel the hold. Now that I read what I wrote I wonder what would be the benefit of holding a key in a two keys chording. @Tuckie can you explain what you are thinking about? I have a working timer/timeout code here I came with because I tried to implement a "clean" double tap combined with single tap and mods on hold on one single key (which of course failed because you'll then need a buffer to store key pressed while the timeout has not ran out to identify the single tap, works great for slooow typists though :)). So basically the timeout routine uses ATMEGA32 TIMER3 and can be stopped from the outside

/* timer3.h
 * f. marlier 2016
 */
void tap_timeout(uint8_t ms);
void timer3_stop(void);
bool halted_from_code;
/* timer3.c
 * f. marlier 2016
 * ATMEGA32U4 8 bit AVR 16 MHz
 * CTC mode, the OCR2 defines the top value for the counter hence it resolution.
 * an interrupt can be generated using the OFC2 value.
 * The interrupt handling routine can be used to for updating the top value.
 *
 */
#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdint.h>
#include "debug.h"
#include "timer3.h"
static uint8_t timer3_out;
volatile uint8_t timer3_count;
bool halted_from_code;
void timer3_init(void)
{
    TCCR3A = 0x0;
    // prescaler fCPU/64 | CTC top OCR3A update immediate TOV3 Max
    TCCR3B = (1 << CS30)|(1 << CS31)|(1 << WGM32);
    // 250 increments to go to 1ms
    OCR3A = 250;
}
void timer3_start(void)
{
    TIMSK3 |= (1 << OCIE3A);
    dprintf("TIMER STARTED\n");
}
void timer3_stop(void)
{
    halted_from_code=true;
    dprintf("TIMER HALTED FROM OUTSIDE\n");
}
void tap_timeout(uint8_t ms)
{
    timer3_out = ms;
    timer3_count = 0;
    halted_from_code = false;
    uint8_t timer3_count_local;
    timer3_init();
    timer3_start();
    // loop
    while (timer3_count_local < timer3_out && !halted_from_code) {
        timer3_count_local = timer3_count;
        // scan for input
        keyboard_task();
        dprintf("timer3(%u)\n",timer3_count);
    }
    TCCR3B = 0x0;
    dprintf("TIMER HALTED\n");
}
ISR(TIMER3_COMPA_vect)
{
    timer3_count++;
}
ezuk commented 8 years ago

One thing @jackhumbert and I tried to do here is keep it simple, to get a basic implementation out and nail the macro syntax and the data structure. That's also why we want to limit to just two-key chords at first. I think once we have something simple working we can go from there, just imho.

Tuckie commented 8 years ago

can you explain what you are thinking about?

@mecanogrh Mainly in use for software that treats a held button differently than a tap.

mecanogrh commented 8 years ago

@Tuckie mmm you mean Autokeys or Karabiner? I actually got a planck to get rid of these :) Are we really talking about planting hooks to be able to support software input handlers?!

Tuckie commented 8 years ago

@mecanogrh no, just key mapping in games :smiley: (where the mapping is hard-coded for held keys)

eltang commented 8 years ago

I have some ideas.

  1. The chords should be stored as sorted arrays. When a user presses a chord, the keycodes registered get loaded one by one into an array. After the chord ends, the array is then sorted and compared to see if it matches a chord array that has been defined.
  2. Call matrix_key_count on every key release. If it returns zero, a chord has just been finished.
eltang commented 8 years ago

@ezuk What is your goal with this? A firmware implementation of Plover?

ezuk commented 8 years ago

No.

On Wed, May 18, 2016, 23:26 Eric Tang notifications@github.com wrote:

@ezuk https://github.com/ezuk What is your goal with this? A firmware implementation of Plover?

— You are receiving this because you were mentioned.

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

ezuk commented 8 years ago

It's for keyboard shortcuts, not steno.

tshort commented 8 years ago

FYI, @FromtonRouge has implemented a cool chording system in QMK:

https://github.com/FromtonRouge/qmk_firmware/tree/master/keyboard/ergodox_ez/keymaps/fromtonrouge

algernon commented 8 years ago

I have an idea how to implement chording simply, for any reasonable (say, 24, but easily changeable with overriding a #define) number of keys. Wouldn't support timeouts, though. Will need to experiment a bit with some C preprocessor macro magic to see if I can make it easy to use too.

(My goal is to be able to implement Plover or another Steno variant in the firmware, or at least, be able to do so, using the chording support as a base.)

cdlm commented 8 years ago

What's the current status of this? Looks like process_chording.c hardcodes KC_A for an enter+space chord…

algernon commented 8 years ago

There's some existing chording stuff, what I want to do, exists in my head only for now. Hopefully I'll have some time tomorrow to prepare a PoC.

wezzynl commented 8 years ago

Any progress on chording ideas? I would love the ability to map two simultaneous key presses (KC_S and KC_D) to temporarily switching to a different layer when held down. This would enable me to keep my hands on the home row when the layer I switch to maps hjkl to VIM-style arrow keys.

millipz commented 6 years ago

Hi! Any update on this? I'm wanting to set up a Plover/steno based one handed chording layout. @algernon, the idea of being able to set up Plover in firmware is super exciting!

dunkarooftop commented 6 years ago

@wezzynl It's not QMK solution but if you are on Mac, you can look into "karabiner-element. It's a very low level keyboard tweak https://pqrs.org/osx/karabiner/

Here are some of the example https://pqrs.org/osx/karabiner/complex_modifications/ (search for Vim)

wezzynl commented 6 years ago

@dunkarooftop Hey, thanks for the heads-up. However, it is possible now to at least do what I want with process_combo. E.g. holding down the S and D keys simultanously will go into SuperDuper mode and shifts layers so H, J, K, L become arrow keys and some more fun stuff.

See this issue: #1214

wezzynl commented 6 years ago

@dunkarooftop LOL, I only just noticed that #1214 is actually your own issue :) 👍

and3rson commented 6 years ago

I was also playing around with the timers while looking for a way to execute some code once certain delay passes. The closest I managed to do is using matrix_scan_user. I was thinking if something similar exists already.

Here's the code I use for my preonic:

#include "preonic.h"

// Scheduler implementation
// Usage:
//  run_after(150, cb, 1337)

#define SCHED_SIZE 16

typedef void (*SCHEDULED_CALLBACK)(int);
typedef struct scheduled_t {
    int timer;
    int ms;
    int data;
    SCHEDULED_CALLBACK cb;
} SCHEDULED;

SCHEDULED scheduled[SCHED_SIZE] = {0};
int sched_i = 0;

int run_after(int ms, SCHEDULED_CALLBACK cb, int data) {
    int index = sched_i;
    SCHEDULED sch = {.timer = timer_read(), .ms = ms, .data = data, .cb = cb};
    scheduled[sched_i] = sch;
    sched_i = (sched_i + 1) % SCHED_SIZE;
    return index;
}

void cancel(int id) {
    scheduled[id].cb = 0;
}

bool cancelled(int id) {
    return scheduled[id].cb == 0;
}

void scheduler_process(void) {
    for (int i = 0; i < SCHED_SIZE; i++) {
        if (scheduled[i].cb && timer_elapsed(scheduled[i].timer) > scheduled[i].ms) {
            SCHEDULED_CALLBACK cb = scheduled[i].cb;
            scheduled[i].cb = 0;
            cb(scheduled[i].data);
        }
    }
}

void matrix_scan_user(void) {
    scheduler_process();
}

Worked pretty great.

fhombsch commented 5 years ago

@eltang & @millipz : About plover inside the firmware: I don't think the plover dictionary fits on my Ergodox EZ shine. On the github project openstenoproject/plover, plover is claimed to have 140,000 dictionary entries. My compiler tells me that there is 32,256 bytes for the firmware, meaning you would have to compress a dictionary entry into less than two bits. You can try to accomplish this by making some standard rule and then save the exceptions, so the word "tub" doens't need a dictionary entry.

I am busy making a chorded keyboard (fhombsch/qmk_firmware:dev_branch on ergodox_ez keybaord) using the Macro feature, so my complete keyboard is handled by the process_record_user() function. It doesn't contain timers. My approach might be useful for macros, as well. It is not much limited in the number of keys to be pressed simultaneously.

I use small dictionaries for initial consonant, vowel and final consonant separately, and they don't need to store natural combinations. This makes up for a total of about 300 dictionary entries. For symbols, I just use more basic lookup tables which would also be suitable for macros. So I am done with roughly 500×8 bytes = 4k memory for the keymap itself. Maybe parts of my code can be useful for you. Kind regards, Fabian

stale[bot] commented 4 years ago

This issue has been automatically marked as stale because it has not had activity in the last 90 days. It will be closed in the next 30 days unless it is tagged properly or other activity occurs. For maintainers: Please label with bug, in progress, on hold, discussion or to do to prevent the issue from being re-flagged.

stale[bot] commented 4 years ago

This issue has been automatically closed because it has not had activity in the last 30 days. If this issue is still valid, re-open the issue and let us know.