/* This is a stripped down version of the Georgi engine meant for use with * Ginni. As such serial-Steno features are disabled, chords are 16bits and * crap is removed where possible * * Do not use this on anything other then Ginny if you want to be sane */ #include "engine.h" // Chord state C_SIZE cChord = 0; // Current Chord int chordIndex = 0; // Keys in previousachord C_SIZE pressed = 0; // number of held keys C_SIZE chordState[32]; // Full Chord history #define QWERBUF 24 // Size of chords to buffer for output bool repeatFlag = false; // Should we repeat? C_SIZE pChord = 0; // Previous Chord C_SIZE stickyBits = 0; // Or'd with every incoming press int pChordIndex = 0; // Keys in previousachord C_SIZE pChordState[32]; // Previous chord sate // Key Dicts extern const struct keyEntry keyDict[]; extern const struct comboEntry cmbDict[]; extern const struct funcEntry funDict[]; extern const struct stringEntry strDict[]; extern const struct specialEntry spcDict[]; extern size_t specialLen; extern size_t stringLen; extern size_t funcsLen; extern size_t keyLen; extern size_t comboLen; // Mode state enum MODE { STENO = 0, QWERTY, COMMAND }; enum MODE pMode; enum MODE cMode = QWERTY; // Command State #define MAX_CMD_BUF 20 uint8_t CMDLEN = 0; uint8_t CMDBUF[MAX_CMD_BUF]; // Key Repeat state bool inChord = false; bool repEngaged = false; uint16_t repTimer = 0; #define REP_INIT_DELAY 750 #define REP_DELAY 25 // Mousekeys state bool inMouse = false; int8_t mousePress; // All processing done at chordUp goes through here void processKeysUp() { // Check for mousekeys, this is release #ifdef MOUSEKEY_ENABLE if (inMouse) { inMouse = false; mousekey_off(mousePress); mousekey_send(); } #endif // handle command mode #ifdef COMMAND_MODE if (cChord == COMMAND_MODE) { # ifndef NO_DEBUG uprintf("COMMAND Toggle\n"); # endif if (cMode != COMMAND) { // Entering Command Mode CMDLEN = 0; pMode = cMode; cMode = COMMAND; } else { // Exiting Command Mode cMode = pMode; // Press all and release all for (int i = 0; i < CMDLEN; i++) { register_code(CMDBUF[i]); } clear_keyboard(); } } #endif // Process and reset state processChord(); cChord = pressed; inChord = false; chordIndex = 0; clear_keyboard(); repEngaged = false; for (int i = 0; i < 32; i++) chordState[i] = 0xFFFF; } // Update Chord State bool process_record_kb(uint16_t keycode, keyrecord_t *record) { // Check if we should run at all if (process_engine_pre(cChord, keycode, record) == false) return true; // Everything happens in here when steno keys come in. // Bail on keyup // Update key repeat timers repTimer = timer_read(); bool pr = record->event.pressed; // Switch on the press adding to chord switch (keycode) { ENGINE_CONFIG default: return true; } // Handle any postprocessing // All keys up, send it! if (inChord && !pr && (pressed & IN_CHORD_MASK) == 0) { processKeysUp(); return false; } if (pressed == 0 && !pr) { processKeysUp(); return false; } cChord |= pressed; cChord = process_engine_post(cChord, keycode, record); inChord = (cChord & IN_CHORD_MASK) != 0; // Store previous state for fastQWER if (pr) { chordState[chordIndex] = cChord; chordIndex++; } #ifndef NO_DEBUG uprintf("Chord: %u\n", cChord); #endif return false; } void matrix_scan_user(void) { // We abuse this for early sending of key // Key repeat only on QWER/SYMB layers if (cMode != QWERTY || !inChord) return; // Check timers #ifndef NO_HOLD if (!repEngaged && timer_elapsed(repTimer) > REP_INIT_DELAY) { // Process Key for report processChord(); // Send report to host send_keyboard_report(); repEngaged = true; } #endif }; // Try and match cChord C_SIZE mapKeys(C_SIZE chord, bool lookup) { lookup = lookup || repEngaged; #ifndef NO_DEBUG if (!lookup) uprint("SENT!\n"); #endif // Single key chords for (int i = 0; i < keyLen; i++) { if (keyDict[i].chord == chord) { if (!lookup) SEND(keyDict[i].key); return chord; } } // strings for (int i = 0; i < stringLen; i++) { struct stringEntry fromPgm; memcpy_P(&fromPgm, &strDict[i], sizeof(stringEntry_t)); if (fromPgm.chord == chord) { if (!lookup) { if (get_mods() & (MOD_LSFT | MOD_RSFT)) { set_mods(get_mods() & ~(MOD_LSFT | MOD_RSFT)); set_oneshot_mods(MOD_LSFT); } send_string_P((PGM_P)(fromPgm.str)); } return chord; } } // combos for (int i = 0; i < comboLen; i++) { struct comboEntry fromPgm; memcpy_P(&fromPgm, &cmbDict[i], sizeof(comboEntry_t)); if (fromPgm.chord == chord) { #ifndef NO_DEBUG uprintf("%d found combo\n", i); #endif if (!lookup) { uint8_t comboKeys[COMBO_MAX]; memcpy_P(&comboKeys, fromPgm.keys, sizeof(uint8_t) * COMBO_MAX); for (int j = 0; j < COMBO_MAX; j++) #ifndef NO_DEBUG uprintf("Combo [%u]: %u\n", j, comboKeys[j]); #endif for (int j = 0; (j < COMBO_MAX) && (comboKeys[j] != COMBO_END); j++) { #ifndef NO_DEBUG uprintf("Combo [%u]: %u\n", j, comboKeys[j]); #endif SEND(comboKeys[j]); } } return chord; } } // functions for (int i = 0; i < funcsLen; i++) { if (funDict[i].chord == chord) { if (!lookup) funDict[i].act(); return chord; } } // Special handling for (int i = 0; i < specialLen; i++) { if (spcDict[i].chord == chord) { if (!lookup) { uint16_t arg = spcDict[i].arg; switch (spcDict[i].action) { case SPEC_STICKY: SET_STICKY(arg); break; case SPEC_REPEAT: REPEAT(); break; case SPEC_CLICK: CLICK_MOUSE((uint8_t)arg); break; case SPEC_SWITCH: SWITCH_LAYER(arg); break; default: SEND_STRING("Invalid Special in Keymap"); } } return chord; } } if ((chord & IN_CHORD_MASK) && (chord & IN_CHORD_MASK) != chord && mapKeys((chord & IN_CHORD_MASK), true) == (chord & IN_CHORD_MASK)) { #ifndef NO_DEBUG uprintf("Try with ignore mask:%u\n", (chord & IN_CHORD_MASK)); #endif mapKeys((chord & ~IN_CHORD_MASK), lookup); mapKeys((chord & IN_CHORD_MASK), lookup); return chord; } #ifndef NO_DEBUG uprintf("Reached end\n"); #endif return 0; } // Traverse the chord history to a given point // Returns the mask to use void processChord(void) { // Save the clean chord state C_SIZE savedChord = cChord; // Apply Stick Bits if needed if (stickyBits != 0) { cChord |= stickyBits; for (int i = 0; i <= chordIndex; i++) chordState[i] |= stickyBits; } // First we test if a whole chord was passsed // If so we just run it handling repeat logic if (mapKeys(cChord, true) == cChord) { mapKeys(cChord, false); // Repeat logic if (repeatFlag) { #ifndef NO_DEBUG uprintf("repeating?\n"); #endif restoreState(); repeatFlag = false; processChord(); } else { saveState(cChord); } return; } C_SIZE next = process_chord_getnext(cChord); if (next && next != cChord) { #ifndef NO_DEBUG uprintf("Trying next candidate: %u\n", next); #endif if (mapKeys(next, true) == next) { mapKeys(next, false); // Repeat logic if (repeatFlag) { #ifndef NO_DEBUG uprintf("repeating?\n"); #endif restoreState(); repeatFlag = false; processChord(); } else { saveState(cChord); } return; } } #ifndef NO_DEBUG uprintf("made it past the maw\n"); #endif // Iterate through chord picking out the individual // and longest chords C_SIZE bufChords[QWERBUF]; int bufLen = 0; C_SIZE mask = 0; // We iterate over it multiple times to catch the longest // chord. Then that gets addded to the mask and re run. while (savedChord != mask) { C_SIZE test = 0; C_SIZE longestChord = 0; for (int i = 0; i <= chordIndex; i++) { cChord = chordState[i] & ~mask; if (cChord == 0) continue; test = mapKeys(cChord, true); if (test != 0) { longestChord = test; } } mask |= longestChord; bufChords[bufLen] = longestChord; bufLen++; // That's a loop of sorts, halt processing if (bufLen >= QWERBUF) { #ifndef NO_DEBUG uprintf("looped. exiting"); #endif return; } } // Now that the buffer is populated, we run it for (int i = 0; i < bufLen; i++) { cChord = bufChords[i]; #ifndef NO_DEBUG uprintf("sending: %u\n", cChord); #endif mapKeys(cChord, false); } // Save state in case of repeat if (!repeatFlag) { saveState(savedChord); } // Restore cChord for held repeat cChord = savedChord; return; } void saveState(C_SIZE cleanChord) { pChord = cleanChord; pChordIndex = chordIndex; for (int i = 0; i < 32; i++) pChordState[i] = chordState[i]; } void restoreState() { cChord = pChord; chordIndex = pChordIndex; for (int i = 0; i < 32; i++) chordState[i] = pChordState[i]; } // Macros for calling from keymap.c void SEND(uint8_t kc) { // Send Keycode, Does not work for Quantum Codes if (cMode == COMMAND && CMDLEN < MAX_CMD_BUF) { #ifndef NO_DEBUG uprintf("CMD LEN: %d BUF: %d\n", CMDLEN, MAX_CMD_BUF); #endif CMDBUF[CMDLEN] = kc; CMDLEN++; } if (cMode != COMMAND) register_code(kc); return; } void REPEAT(void) { if (cMode != QWERTY) return; repeatFlag = true; return; } void SET_STICKY(C_SIZE stick) { stickyBits ^= stick; return; } void CLICK_MOUSE(uint8_t kc) { #ifdef MOUSEKEY_ENABLE mousekey_on(kc); mousekey_send(); // Store state for later use inMouse = true; mousePress = kc; #endif } void SWITCH_LAYER(int layer) { #ifndef NO_ACTION_LAYER if (keymapsCount >= layer) { layer_clear(); layer_on(layer); } #endif } uint8_t bitpop_v(C_SIZE val) { #if C_SIZE == uint8_t return bitpop(val); #elif C_SIZE == uint16_t return bitpop16(val); #elif C_SIZE == uint32_t return bitpop32(val); #elif C_SIZE == uint64_t uint8_t n = 0; if (bits >> 32) { bits >>= 32; n += 32; } if (bits >> 16) { bits >>= 16; n += 16; } if (bits >> 8) { bits >>= 8; n += 8; } if (bits >> 4) { bits >>= 4; n += 4; } if (bits >> 2) { bits >>= 2; n += 2; } if (bits >> 1) { bits >>= 1; n += 1; } return n; #else # error unsupported C_SIZE #endif } // See engine.h for what these hooks do __attribute__((weak)) C_SIZE process_engine_post(C_SIZE cur_chord, uint16_t keycode, keyrecord_t *record) { return cur_chord; } __attribute__((weak)) C_SIZE process_engine_pre(C_SIZE cur_chord, uint16_t keycode, keyrecord_t *record) { return true; } __attribute__((weak)) C_SIZE process_chord_getnext(C_SIZE cur_chord) { return 0; }