summaryrefslogtreecommitdiff
path: root/keyboards/gboards/g
diff options
context:
space:
mode:
Diffstat (limited to 'keyboards/gboards/g')
-rw-r--r--keyboards/gboards/g/config_default.h90
-rw-r--r--keyboards/gboards/g/engine.c470
-rw-r--r--keyboards/gboards/g/engine.h117
-rw-r--r--keyboards/gboards/g/keymap_combo.h56
-rw-r--r--keyboards/gboards/g/keymap_engine.h122
-rw-r--r--keyboards/gboards/g/rules.mk1
6 files changed, 856 insertions, 0 deletions
diff --git a/keyboards/gboards/g/config_default.h b/keyboards/gboards/g/config_default.h
new file mode 100644
index 0000000000..821c2c4ad5
--- /dev/null
+++ b/keyboards/gboards/g/config_default.h
@@ -0,0 +1,90 @@
+#include "engine.h"
+
+// Configuration options for the engine
+
+#define C_SIZE uint64_t // type for chord
+#define COMBO_MAX 4 // Longest Combo
+
+// Key Aliases, must fit within C_SIZE!
+// These are for Ginni + Asetniop
+#define GQ STN(0)
+#define GW STN(1)
+#define GE STN(2)
+#define GR STN(3)
+#define GT STN(4)
+#define GY STN(5)
+#define GU STN(6)
+#define GI STN(7)
+#define GO STN(8)
+#define GP STN(9)
+
+#define GA STN(10)
+#define GS STN(11)
+#define GD STN(12)
+#define GF STN(13)
+#define GG STN(14)
+#define GH STN(15)
+#define GJ STN(16)
+#define GK STN(17)
+#define GL STN(18)
+#define GCL STN(19)
+
+#define GZ STN(20)
+#define GX STN(21)
+#define GC STN(22)
+#define GV STN(23)
+#define GB STN(24)
+#define GN STN(25)
+#define GM STN(26)
+#define GLT STN(27)
+#define GGT STN(28)
+#define GQU STN(29)
+
+#define GL1 STN(30)
+#define GL2 STN(31)
+#define GL3 STN(32)
+#define GR3 STN(33)
+#define GR2 STN(34)
+#define GR1 STN(35)
+
+// Chord to start buffering strokes
+#define COMMAND_MODE (GL1 | GR1)
+
+// Mapping of QMK Keycodes to chord positions
+#define ENGINE_CONFIG \
+ ENGINE_HOOK(KC_Q, GQ) \
+ ENGINE_HOOK(KC_W, GW) \
+ ENGINE_HOOK(KC_E, GE) \
+ ENGINE_HOOK(KC_R, GR) \
+ ENGINE_HOOK(KC_T, GT) \
+ ENGINE_HOOK(KC_Y, GY) \
+ ENGINE_HOOK(KC_U, GU) \
+ ENGINE_HOOK(KC_I, GI) \
+ ENGINE_HOOK(KC_O, GO) \
+ ENGINE_HOOK(KC_P, GP) \
+ ENGINE_HOOK(KC_A, GA) \
+ ENGINE_HOOK(KC_S, GS) \
+ ENGINE_HOOK(KC_D, GD) \
+ ENGINE_HOOK(KC_F, GF) \
+ ENGINE_HOOK(KC_G, GG) \
+ ENGINE_HOOK(KC_H, GH) \
+ ENGINE_HOOK(KC_J, GJ) \
+ ENGINE_HOOK(KC_K, GK) \
+ ENGINE_HOOK(KC_L, GL) \
+ ENGINE_HOOK(KC_SCLN, GCL) \
+ ENGINE_HOOK(KC_Z, GZ) \
+ ENGINE_HOOK(KC_X, GX) \
+ ENGINE_HOOK(KC_C, GC) \
+ ENGINE_HOOK(KC_V, GV) \
+ ENGINE_HOOK(KC_B, GB) \
+ ENGINE_HOOK(KC_N, GN) \
+ ENGINE_HOOK(KC_M, GM) \
+ ENGINE_HOOK(KC_COMM, GLT) \
+ ENGINE_HOOK(KC_DOT, GGT) \
+ ENGINE_HOOK(KC_SLSH, GQU) \
+ ENGINE_HOOK(KC_F1, GL1) \
+ ENGINE_HOOK(KC_F2, GL2) \
+ ENGINE_HOOK(KC_F3, GL3) \
+ ENGINE_HOOK(KC_F4, GR3) \
+ ENGINE_HOOK(KC_F5, GR2) \
+ ENGINE_HOOK(KC_F6, GR1)
diff --git a/keyboards/gboards/g/engine.c b/keyboards/gboards/g/engine.c
new file mode 100644
index 0000000000..015b48d38f
--- /dev/null
+++ b/keyboards/gboards/g/engine.c
@@ -0,0 +1,470 @@
+/* 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; }
diff --git a/keyboards/gboards/g/engine.h b/keyboards/gboards/g/engine.h
new file mode 100644
index 0000000000..547ea09fdd
--- /dev/null
+++ b/keyboards/gboards/g/engine.h
@@ -0,0 +1,117 @@
+/* 2019, g Heavy Industries
+ Blessed mother of Christ, please keep this readable
+ and protect us from segfaults. For thine is the clock,
+ the slave and the master. Until we return from main.
+
+ Amen.
+
+ This is a stripped down version of the Georgi engine meant for use with
+ . As such serial-Steno features are disabled, chords are 16bits and
+ crap is removed where possible
+*/
+
+#include QMK_KEYBOARD_H
+#pragma once
+#include "keymap.h"
+#include <string.h>
+#include <stdint.h>
+#include <stdio.h>
+#include "config_engine.h"
+#include <avr/pgmspace.h>
+#include "wait.h"
+#ifdef MOUSEKEY_ENABLE
+# include "mousekey.h"
+#endif
+
+// Set defaults
+#ifndef IN_CHORD_MASK
+# define IN_CHORD_MASK 0xFFFFFFFFFFFFFFFF
+#endif
+
+#ifndef COMBO_END
+# define COMBO_END 0x00
+#endif
+
+// In memory chord datatypes
+enum specialActions {
+ SPEC_STICKY,
+ SPEC_REPEAT,
+ SPEC_CLICK,
+ SPEC_SWITCH,
+};
+struct funcEntry {
+ C_SIZE chord;
+ void (*act)(void);
+} funcEntry_t;
+struct stringEntry {
+ C_SIZE chord;
+ PGM_P str;
+} stringEntry_t;
+struct comboEntry {
+ C_SIZE chord;
+ PGM_P keys;
+} comboEntry_t;
+struct keyEntry {
+ C_SIZE chord;
+ uint8_t key;
+} keyEntry_t;
+struct specialEntry {
+ C_SIZE chord;
+ enum specialActions action;
+ uint16_t arg;
+} specialEntry_t;
+
+// Chord Temps
+extern C_SIZE cChord;
+extern C_SIZE test;
+extern size_t keymapsCount; // Total keymaps (exported from keymap.c)
+
+// Function defs
+void processKeysUp(void);
+void processChord(void);
+C_SIZE processQwerty(bool lookup);
+C_SIZE processFakeSteno(bool lookup);
+void saveState(C_SIZE cChord);
+void restoreState(void);
+uint8_t bitpop_v(C_SIZE val);
+
+// Macros for use in keymap.c
+void SEND(uint8_t kc);
+void REPEAT(void);
+void SET_STICKY(C_SIZE);
+void SWITCH_LAYER(int);
+void CLICK_MOUSE(uint8_t);
+C_SIZE process_chord_getnext(C_SIZE cur_chord);
+// Run before hitting the engine. Return false to skip engine processing
+C_SIZE process_engine_pre(C_SIZE cur_chord, uint16_t keycode, keyrecord_t *record);
+// Run after reading a chord.
+C_SIZE process_engine_post(C_SIZE cur_chord, uint16_t keycode, keyrecord_t *record);
+
+// Keymap helpers
+// New Approach, multiple structures
+#define P_KEYMAP(chord, keycode) {chord, keycode},
+
+#define K_KEYMAP(chord, name, ...) {chord, (PGM_P)&name},
+#define K_ACTION(chord, name, ...) const uint8_t name[] PROGMEM = __VA_ARGS__;
+
+#define S_KEYMAP(chord, name, string) {chord, (PGM_P)&name},
+#define S_ACTION(chord, name, string) const char name[] PROGMEM = string;
+
+#define X_KEYMAP(chord, name, func) {chord, name},
+#define X_ACTION(chord, name, func) \
+ void name(void) { func }
+
+#define Z_KEYMAP(chord, act, arg) {chord, act, arg},
+
+#define TEST_COLLISION(chord, ...) \
+ case chord: \
+ break;
+#define BLANK(...)
+
+// Shift to internal representation
+// i.e) S(teno)R(ight)F
+#define STN(n) ((C_SIZE)1 << n)
+#define ENGINE_HOOK(keycode, chord) \
+ case keycode: \
+ pr ? (pressed |= (chord)) : (pressed &= ~(chord)); \
+ break;
diff --git a/keyboards/gboards/g/keymap_combo.h b/keyboards/gboards/g/keymap_combo.h
new file mode 100644
index 0000000000..674d3356cc
--- /dev/null
+++ b/keyboards/gboards/g/keymap_combo.h
@@ -0,0 +1,56 @@
+// Keymap helpers
+
+#define K_ENUM(name, key, ...) name,
+#define K_DATA(name, key, ...) const uint16_t PROGMEM cmb_##name[] = {__VA_ARGS__, COMBO_END};
+#define K_COMB(name, key, ...) [name] = COMBO(cmb_##name, key),
+
+#define A_ENUM(name, string, ...) name,
+#define A_DATA(name, string, ...) const uint16_t PROGMEM cmb_##name[] = {__VA_ARGS__, COMBO_END};
+#define A_COMB(name, string, ...) [name] = COMBO_ACTION(cmb_##name),
+#define A_ACTI(name, string, ...) \
+ case name: \
+ if (pressed) SEND_STRING(string); \
+ break;
+#define BLANK(...)
+
+// Generate data needed for combos/actions
+// Create Enum
+#undef COMB
+#undef SUBS
+#define COMB K_ENUM
+#define SUBS A_ENUM
+enum combos {
+#include "combos.def"
+};
+
+// Bake combos into mem
+#undef COMB
+#undef SUBS
+#define COMB K_DATA
+#define SUBS A_DATA
+#include "combos.def"
+#undef COMB
+#undef SUBS
+
+// Fill combo array
+#define COMB K_COMB
+#define SUBS A_COMB
+combo_t key_combos[] = {
+#include "combos.def"
+};
+#undef COMB
+#undef SUBS
+
+// Export length to combo module
+int COMBO_LEN = sizeof(key_combos) / sizeof(key_combos[0]);
+
+// Fill QMK hook
+#define COMB BLANK
+#define SUBS A_ACTI
+void process_combo_event(uint8_t combo_index, bool pressed) {
+ switch (combo_index) {
+#include "combos.def"
+ }
+}
+#undef COMB
+#undef SUBS
diff --git a/keyboards/gboards/g/keymap_engine.h b/keyboards/gboards/g/keymap_engine.h
new file mode 100644
index 0000000000..93a4423340
--- /dev/null
+++ b/keyboards/gboards/g/keymap_engine.h
@@ -0,0 +1,122 @@
+/* If for some reason you're still here, maybe due to horror, shock or
+ * some other godforsaken reason. Meet X Macros.
+ *
+ * The we abuse the include system to generate data structures that are
+ * used by the internal chording engine. The alternative to this is
+ * using a external generator (Like is done for the ASETNIOP base keymaps)
+ * With this disgusting bodge, you can just edit your .defs and compile!
+ */
+#include "g/engine.h"
+
+// Clear all X Macros
+#define PRES BLANK
+#define KEYS BLANK
+#define SUBS BLANK
+#define EXEC BLANK
+#define SPEC BLANK
+
+// Process single key pushes
+#undef PRES
+#define PRES P_KEYMAP
+const struct keyEntry keyDict[] = {
+#include "dicts.def"
+};
+#undef PRES
+#define PRES BLANK
+
+// Process Combos
+#undef KEYS
+#define KEYS K_ACTION
+#include "dicts.def"
+#undef KEYS
+#define KEYS BLANK
+
+#undef KEYS
+#define KEYS K_KEYMAP
+const struct comboEntry PROGMEM cmbDict[] = {
+#include "dicts.def"
+};
+#undef KEYS
+#define KEYS BLANK
+
+// Process String stubs
+#undef SUBS
+#define SUBS S_ACTION
+#include "dicts.def"
+#undef SUBS
+#define SUBS BLANK
+
+// Generate dict for strings
+#undef SUBS
+#define SUBS S_KEYMAP
+const struct stringEntry PROGMEM strDict[] = {
+#include "dicts.def"
+};
+#undef SUBS
+#define SUBS BLANK
+
+// Generate function stubs
+#undef EXEC
+#define EXEC X_ACTION
+#include "dicts.def"
+#undef EXEC
+#define EXEC BLANK
+
+// Process the function structure
+#undef EXEC
+#define EXEC X_KEYMAP
+const struct funcEntry funDict[] = {
+#include "dicts.def"
+};
+#undef EXEC
+#define EXEC BLANK
+
+// Handle Special calls
+#undef SPEC
+#define SPEC Z_KEYMAP
+const struct specialEntry spcDict[] = {
+#include "dicts.def"
+};
+#undef SPEC
+#define SPEC BLANK
+
+// Test for collisions!
+// Switch statement will explode on duplicate
+// chords. This will be optimized out
+#undef PRES
+#undef KEYS
+#undef SUBS
+#undef EXEC
+#undef SPEC
+#define PRES TEST_COLLISION
+#define KEYS TEST_COLLISION
+#define SUBS TEST_COLLISION
+#define EXEC TEST_COLLISION
+#define SPEC TEST_COLLISION
+void testCollisions(void) {
+ C_SIZE bomb = 0;
+ switch (bomb) {
+#include "dicts.def"
+ }
+}
+
+// Test for unexpected input
+// Should return blank lines for all valid input
+#undef PRES
+#undef KEYS
+#undef SUBS
+#undef EXEC
+#undef SPEC
+#define PRES BLANK
+#define KEYS BLANK
+#define SUBS BLANK
+#define EXEC BLANK
+#define SPEC BLANK
+#include "dicts.def"
+
+// Get size data back into the engine
+size_t funcsLen = sizeof(funDict) / sizeof(funDict[0]);
+size_t stringLen = sizeof(strDict) / sizeof(strDict[0]);
+size_t keyLen = sizeof(keyDict) / sizeof(keyDict[0]);
+size_t comboLen = sizeof(cmbDict) / sizeof(cmbDict[0]);
+size_t specialLen = sizeof(spcDict) / sizeof(spcDict[0]);
diff --git a/keyboards/gboards/g/rules.mk b/keyboards/gboards/g/rules.mk
new file mode 100644
index 0000000000..186f59386d
--- /dev/null
+++ b/keyboards/gboards/g/rules.mk
@@ -0,0 +1 @@
+SRC += engine.c