diff --git a/keyboards/betalupi_ergodox/keymaps/default/keymap.c b/keyboards/betalupi_ergodox/keymaps/default/keymap.c index 22dd1db..e5c4fef 100644 --- a/keyboards/betalupi_ergodox/keymaps/default/keymap.c +++ b/keyboards/betalupi_ergodox/keymaps/default/keymap.c @@ -1,6 +1,8 @@ #include "keymap.h" #include "layers/layers.h" +#include "spellcheck.h" + // Values that should not be saved to git. // Create a `secrets.h` in the keymap directory. // @@ -31,9 +33,6 @@ void matrix_scan_user(void) { // unregister_code(KC_S); // unregister_code(KC_LGUI); //} - - - } } @@ -42,6 +41,7 @@ void matrix_scan_user(void) { // Return FALSE to halt key processing, // Return TRUE to allow QMK to handle keypress. bool process_record_user(uint16_t keycode, keyrecord_t *record) { + if (!process_spellcheck(keycode, record)) { return false; } // Handle macros switch (keycode) { diff --git a/keyboards/betalupi_ergodox/keymaps/default/rules.mk b/keyboards/betalupi_ergodox/keymaps/default/rules.mk index 0fe174b..2d0e6ca 100644 --- a/keyboards/betalupi_ergodox/keymaps/default/rules.mk +++ b/keyboards/betalupi_ergodox/keymaps/default/rules.mk @@ -6,4 +6,5 @@ TAP_DANCE_ENABLE = yes SRC += \ tapdance/wmlayout.c \ tapdance/tapdance.c \ - layers/layers.c + layers/layers.c \ + spellcheck.c diff --git a/keyboards/betalupi_ergodox/keymaps/default/spellcheck.c b/keyboards/betalupi_ergodox/keymaps/default/spellcheck.c new file mode 100644 index 0000000..3d48c0f --- /dev/null +++ b/keyboards/betalupi_ergodox/keymaps/default/spellcheck.c @@ -0,0 +1,110 @@ +#include "spellcheck.h" +#include "rawhid.h" + +uint8_t spellcheck_buffer[SPELLCHECK_BUFFER_MAX] = {0}; +uint8_t spellcheck_buffer_size = 0; + +bool process_spellcheck(uint16_t keycode, keyrecord_t* record) { + + + // Ignore key release; we only process key presses. + if (!record->event.pressed) { return true; } + +#ifndef NO_ACTION_ONESHOT + const uint8_t mods = get_mods() | get_oneshot_mods(); +#else + const uint8_t mods = get_mods(); +#endif + + + // Disable autocorrection while a mod other than shift is active. + if ((mods & ~MOD_MASK_SHIFT) != 0) { + spellcheck_buffer_size = 0; + return true; + } + + // The following switch cases address various kinds of keycodes. This logic is + // split over two switches rather than merged into one. The first switch may + // extract a basic keycode which is then further handled by the second switch, + // e.g. a layer-tap key with Caps Lock `LT(layer, KC_CAPS)`. + switch (keycode) { +#ifndef NO_ACTION_TAPPING + case QK_MOD_TAP ... QK_MOD_TAP_MAX: // Tap-hold keys. +#ifndef NO_ACTION_LAYER + case QK_LAYER_TAP ... QK_LAYER_TAP_MAX: +#endif + // Ignore when tap-hold keys are held. + if (record->tap.count == 0) { return true; } + // Otherwise when tapped, get the basic keycode. + // Fallthrough intended. +#endif + + // Handle shifted keys, e.g. symbols like KC_EXLM = S(KC_1). + case QK_LSFT ... QK_LSFT + 255: + case QK_RSFT ... QK_RSFT + 255: + keycode &= 0xff; // Get the basic keycode. + break; + + // NOTE: Space Cadet keys expose no info to check whether they are being + // tapped vs. held. This makes autocorrection ambiguous, e.g. KC_LCPO might + // be '(', which we would treat as a word break, or it might be shift, which + // we would treat as having no effect. To behave cautiously, we allow Space + // Cadet keycodes to fall to the logic below and clear autocorrection state. + } + + switch (keycode) { + // Ignore shifts, Caps Lock, one-shot mods, and layer switch keys. + case KC_NO: + case KC_LSFT: + case KC_RSFT: + case KC_CAPS: + case QK_ONE_SHOT_MOD ... QK_ONE_SHOT_MOD_MAX: + case QK_TO ... QK_TO_MAX: + case QK_MOMENTARY ... QK_MOMENTARY_MAX: + case QK_DEF_LAYER ... QK_DEF_LAYER_MAX: + case QK_TOGGLE_LAYER ... QK_TOGGLE_LAYER_MAX: + case QK_ONE_SHOT_LAYER ... QK_ONE_SHOT_LAYER_MAX: + case QK_LAYER_TAP_TOGGLE ... QK_LAYER_TAP_TOGGLE_MAX: + case QK_LAYER_MOD ... QK_LAYER_MOD_MAX: + return true; // Ignore these keys. + } + + if (keycode == KC_QUOT) { + // Treat " (shifted ') as a word boundary. + if ((mods & MOD_MASK_SHIFT) != 0) { keycode = KC_SPC; } + } else if (!(KC_A <= keycode && keycode <= KC_Z)) { + if (keycode == KC_BSPC) { + // Remove last character from the buffer. + if (spellcheck_buffer_size > 0) { --spellcheck_buffer_size; } + return true; + } else if (KC_1 <= keycode && keycode <= KC_SLSH && keycode != KC_ESC) { + // Set a word boundary if space, period, digit, etc. is pressed. + // Behave more conservatively for the enter key. Reset, so that enter + // can't be used on a word ending. + if (keycode == KC_ENT) { spellcheck_buffer_size = 0; } + keycode = KC_SPC; + } else { + // Clear state if some other non-alpha key is pressed. + spellcheck_buffer_size = 0; + return true; + } + } + + // If the buffer is full, rotate it to discard the oldest character. + if (spellcheck_buffer_size >= SPELLCHECK_BUFFER_MAX) { + memmove(spellcheck_buffer, spellcheck_buffer + 1, SPELLCHECK_BUFFER_MAX - 1); + spellcheck_buffer_size = SPELLCHECK_BUFFER_MAX - 1; + } + + // NOTE: `keycode` must be a basic keycode (0-255) by this point. + + // A to Z keycodes + if (keycode >= 0x04 && keycode <= 0x1D) { + spellcheck_buffer[spellcheck_buffer_size++] = (uint8_t) keycode; + } else if (spellcheck_buffer_size > 0) { + hid_send_word(); + spellcheck_buffer_size = 0; + } + + return true; +} diff --git a/keyboards/betalupi_ergodox/keymaps/default/spellcheck.h b/keyboards/betalupi_ergodox/keymaps/default/spellcheck.h new file mode 100644 index 0000000..b7a26fd --- /dev/null +++ b/keyboards/betalupi_ergodox/keymaps/default/spellcheck.h @@ -0,0 +1,15 @@ +#pragma once +#include "quantum.h" + + +// spellcheck_buffer holds keycode history. +// It can only contain values 0x00 - 0xFF, aka standard usb hid codes. +// Host software is responsible for turning this into a usable string. +// +// Note that SOME preprocessing is done,see spellcheck.c +#define SPELLCHECK_BUFFER_MAX 16 +uint8_t spellcheck_buffer[SPELLCHECK_BUFFER_MAX]; +uint8_t spellcheck_buffer_size; + + +bool process_spellcheck(uint16_t keycode, keyrecord_t* record); \ No newline at end of file diff --git a/keyboards/betalupi_ergodox/rawhid.c b/keyboards/betalupi_ergodox/rawhid.c index 0046d25..1b6f59f 100644 --- a/keyboards/betalupi_ergodox/rawhid.c +++ b/keyboards/betalupi_ergodox/rawhid.c @@ -1,6 +1,8 @@ #include "rawhid.h" #include "extra_mappings.h" +#include "spellcheck.h" + // See rawhid.h for prococol documentation void raw_hid_receive(uint8_t *data, uint8_t length) { uint8_t cmd = data[0]; @@ -20,6 +22,14 @@ void raw_hid_receive(uint8_t *data, uint8_t length) { break; #endif + case CMD_SPELLCHECK_WORD: + if (data[1]) { + ergodox_right_led_1_on(); + _delay_ms(50); + ergodox_right_led_1_off(); + } + break; + default: break; } @@ -61,6 +71,20 @@ void hid_send_state() { } +void hid_send_word() { + uint8_t packet[RAW_EPSIZE] = { + CMD_SPELLCHECK_WORD, + spellcheck_buffer_size + }; + + for (int i = 0; i < 2 + spellcheck_buffer_size; i++) { + packet[i + 2] = spellcheck_buffer[i]; + } + + raw_hid_send(packet, RAW_EPSIZE); +} + + #ifdef RGB_MATRIX_FRAMEBUFFER_EFFECTS diff --git a/keyboards/betalupi_ergodox/rawhid.h b/keyboards/betalupi_ergodox/rawhid.h index 62854b3..650aee1 100644 --- a/keyboards/betalupi_ergodox/rawhid.h +++ b/keyboards/betalupi_ergodox/rawhid.h @@ -5,6 +5,7 @@ void raw_hid_receive(uint8_t *data, uint8_t length); void hid_send_state(void); +void hid_send_word(void); void cmd_animation(uint8_t *data, uint8_t length); @@ -43,6 +44,23 @@ extern uint8_t g_rgb_frame_buffer[MATRIX_ROWS][MATRIX_COLS]; // 0x02: FFT Animation #define CMD_SEND_STATE 0x02 +// Sent by keyboard to host when a complete word is typed. +// Host checks if this is a known word. +// If it is not, host responds with the same CMD (see below). +// +// Packet structure (sent by keyboard): +// Data: | cmd | word length | keycodes | +// # of Bytes: | 1 | 1 | ? | +// +// word length: number of bytes in `keycodes` block +// +// +// Packet structure (sent by host): +// Data: | cmd | typo? | +// # of Bytes: | 1 | 1 | +// +// typo: If this is 0x01, the word we got was a typo. +#define CMD_SPELLCHECK_WORD 0x04 // Animation data. Sent by host. //