qmk_firmware

QMK firmware for my keyboards (Corne, Sweep Ferris) and trackball (Ploopy Adept)
Log | Files | Refs | Submodules | LICENSE

process_steno.c (9447B)


      1 /* Copyright 2017, 2022 Joseph Wasson, Vladislav Kucheriavykh
      2  *
      3  * This program is free software: you can redistribute it and/or modify
      4  * it under the terms of the GNU General Public License as published by
      5  * the Free Software Foundation, either version 2 of the License, or
      6  * (at your option) any later version.
      7  *
      8  * This program is distributed in the hope that it will be useful,
      9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     11  * GNU General Public License for more details.
     12  *
     13  * You should have received a copy of the GNU General Public License
     14  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
     15  */
     16 #include "process_steno.h"
     17 #include "quantum_keycodes.h"
     18 #include "eeconfig.h"
     19 #include <string.h>
     20 #ifdef VIRTSER_ENABLE
     21 #    include "virtser.h"
     22 #endif
     23 
     24 // All steno keys that have been pressed to form this chord,
     25 // stored in MAX_STROKE_SIZE groups of 8-bit arrays.
     26 static uint8_t chord[MAX_STROKE_SIZE] = {0};
     27 // The number of physical keys actually being held down.
     28 // This is not always equal to the number of 1 bits in `chord` because it is possible to
     29 // simultaneously press down four keys, then release three of those four keys and then press yet
     30 // another key while the fourth finger is still holding down its key.
     31 // At the end of this scenario given as an example, `chord` would have five bits set to 1 but
     32 // `n_pressed_keys` would be set to 2 because there are only two keys currently being pressed down.
     33 static int8_t n_pressed_keys = 0;
     34 
     35 #ifdef STENO_ENABLE_ALL
     36 static steno_mode_t mode;
     37 #elif defined(STENO_ENABLE_GEMINI)
     38 static const steno_mode_t mode = STENO_MODE_GEMINI;
     39 #elif defined(STENO_ENABLE_BOLT)
     40 static const steno_mode_t mode = STENO_MODE_BOLT;
     41 #endif
     42 
     43 static inline void steno_clear_chord(void) {
     44     memset(chord, 0, sizeof(chord));
     45 }
     46 
     47 #ifdef STENO_ENABLE_GEMINI
     48 
     49 #    ifdef VIRTSER_ENABLE
     50 void send_steno_chord_gemini(void) {
     51     // Set MSB to 1 to indicate the start of packet
     52     chord[0] |= 0x80;
     53     for (uint8_t i = 0; i < GEMINI_STROKE_SIZE; ++i) {
     54         virtser_send(chord[i]);
     55     }
     56 }
     57 #    else
     58 #        pragma message "VIRTSER_ENABLE = yes is required for Gemini PR to work properly out of the box!"
     59 #    endif // VIRTSER_ENABLE
     60 
     61 /**
     62  * @precondition: `key` is pressed
     63  */
     64 bool add_gemini_key_to_chord(uint8_t key) {
     65     // Although each group of the packet is 8 bits long, the MSB is reserved
     66     // to indicate whether that byte is the first byte of the packet (MSB=1)
     67     // or one of the remaining five bytes of the packet (MSB=0).
     68     // As a consequence, only 7 out of the 8 bits are left to be used as a bit array
     69     // for the steno keys of that group.
     70     const int group_idx       = key / 7;
     71     const int intra_group_idx = key - group_idx * 7;
     72     // The 0th steno key of the group has bit=0b01000000, the 1st has bit=0b00100000, etc.
     73     const uint8_t bit = 1 << (6 - intra_group_idx);
     74     chord[group_idx] |= bit;
     75     return false;
     76 }
     77 #endif // STENO_ENABLE_GEMINI
     78 
     79 #ifdef STENO_ENABLE_BOLT
     80 
     81 #    define TXB_GRP0 0b00000000
     82 #    define TXB_GRP1 0b01000000
     83 #    define TXB_GRP2 0b10000000
     84 #    define TXB_GRP3 0b11000000
     85 #    define TXB_GRPMASK 0b11000000
     86 
     87 #    define TXB_GET_GROUP(code) ((code & TXB_GRPMASK) >> 6)
     88 
     89 static const uint8_t boltmap[64] PROGMEM = {TXB_NUL, TXB_NUM, TXB_NUM, TXB_NUM, TXB_NUM, TXB_NUM, TXB_NUM, TXB_S_L, TXB_S_L, TXB_T_L, TXB_K_L, TXB_P_L, TXB_W_L, TXB_H_L, TXB_R_L, TXB_A_L, TXB_O_L, TXB_STR, TXB_STR, TXB_NUL, TXB_NUL, TXB_NUL, TXB_STR, TXB_STR, TXB_E_R, TXB_U_R, TXB_F_R, TXB_R_R, TXB_P_R, TXB_B_R, TXB_L_R, TXB_G_R, TXB_T_R, TXB_S_R, TXB_D_R, TXB_NUM, TXB_NUM, TXB_NUM, TXB_NUM, TXB_NUM, TXB_NUM, TXB_Z_R};
     90 
     91 #    ifdef VIRTSER_ENABLE
     92 static void send_steno_chord_bolt(void) {
     93     for (uint8_t i = 0; i < BOLT_STROKE_SIZE; ++i) {
     94         // TX Bolt uses variable length packets where each byte corresponds to a bit array of certain keys.
     95         // If a user chorded the keys of the first group with keys of the last group, for example, there
     96         // would be bytes of 0x00 in `chord` for the middle groups which we mustn't send.
     97         if (chord[i]) {
     98             virtser_send(chord[i]);
     99         }
    100     }
    101     // Sending a null packet is not always necessary, but it is simpler and more reliable
    102     // to unconditionally send it every time instead of keeping track of more states and
    103     // creating more branches in the execution of the program.
    104     virtser_send(0);
    105 }
    106 #    else
    107 #        pragma message "VIRTSER_ENABLE = yes is required for TX Bolt to work properly out of the box!"
    108 #    endif // VIRTSER_ENABLE
    109 
    110 /**
    111  * @precondition: `key` is pressed
    112  */
    113 static bool add_bolt_key_to_chord(uint8_t key) {
    114     uint8_t boltcode = pgm_read_byte(boltmap + key);
    115     chord[TXB_GET_GROUP(boltcode)] |= boltcode;
    116     return false;
    117 }
    118 #endif // STENO_ENABLE_BOLT
    119 
    120 #ifdef STENO_COMBINEDMAP
    121 /* Used to look up when pressing the middle row key to combine two consonant or vowel keys */
    122 static const uint16_t combinedmap_first[] PROGMEM  = {STN_S1, STN_TL, STN_PL, STN_HL, STN_FR, STN_PR, STN_LR, STN_TR, STN_DR, STN_A, STN_E};
    123 static const uint16_t combinedmap_second[] PROGMEM = {STN_S2, STN_KL, STN_WL, STN_RL, STN_RR, STN_BR, STN_GR, STN_SR, STN_ZR, STN_O, STN_U};
    124 #endif
    125 
    126 #ifdef STENO_ENABLE_ALL
    127 void steno_init(void) {
    128     mode = eeconfig_read_steno_mode();
    129 }
    130 
    131 void steno_set_mode(steno_mode_t new_mode) {
    132     steno_clear_chord();
    133     mode = new_mode;
    134     eeconfig_update_steno_mode(mode);
    135 }
    136 #endif // STENO_ENABLE_ALL
    137 
    138 /* override to intercept chords right before they get sent.
    139  * return zero to suppress normal sending behavior.
    140  */
    141 __attribute__((weak)) bool send_steno_chord_user(steno_mode_t mode, uint8_t chord[MAX_STROKE_SIZE]) {
    142     return true;
    143 }
    144 
    145 __attribute__((weak)) bool post_process_steno_user(uint16_t keycode, keyrecord_t *record, steno_mode_t mode, uint8_t chord[MAX_STROKE_SIZE], int8_t n_pressed_keys) {
    146     return true;
    147 }
    148 
    149 __attribute__((weak)) bool process_steno_user(uint16_t keycode, keyrecord_t *record) {
    150     return true;
    151 }
    152 
    153 bool process_steno(uint16_t keycode, keyrecord_t *record) {
    154     if (keycode < QK_STENO || keycode > QK_STENO_MAX) {
    155         return true; // Not a steno key, pass it further along the chain
    156         /*
    157          * Clearing or sending the chord state is not necessary as we intentionally ignore whatever
    158          * normal keyboard keys the user may have tapped while chording steno keys.
    159          */
    160     }
    161     if (IS_NOEVENT(record->event)) {
    162         return true;
    163     }
    164     if (!process_steno_user(keycode, record)) {
    165         return false; // User fully processed the steno key themselves
    166     }
    167     switch (keycode) {
    168 #ifdef STENO_ENABLE_ALL
    169         case QK_STENO_BOLT:
    170             if (record->event.pressed) {
    171                 steno_set_mode(STENO_MODE_BOLT);
    172             }
    173             return false;
    174 
    175         case QK_STENO_GEMINI:
    176             if (record->event.pressed) {
    177                 steno_set_mode(STENO_MODE_GEMINI);
    178             }
    179             return false;
    180 #endif // STENO_ENABLE_ALL
    181 
    182 #ifdef STENO_COMBINEDMAP
    183         case QK_STENO_COMB ... QK_STENO_COMB_MAX: {
    184             bool first_result  = process_steno(combinedmap_first[keycode - QK_STENO_COMB], record);
    185             bool second_result = process_steno(combinedmap_second[keycode - QK_STENO_COMB], record);
    186             return first_result && second_result;
    187         }
    188 #endif // STENO_COMBINEDMAP
    189         case STN__MIN ... STN__MAX:
    190             if (record->event.pressed) {
    191                 n_pressed_keys++;
    192                 switch (mode) {
    193 #ifdef STENO_ENABLE_BOLT
    194                     case STENO_MODE_BOLT:
    195                         add_bolt_key_to_chord(keycode - QK_STENO);
    196                         break;
    197 #endif // STENO_ENABLE_BOLT
    198 #ifdef STENO_ENABLE_GEMINI
    199                     case STENO_MODE_GEMINI:
    200                         add_gemini_key_to_chord(keycode - QK_STENO);
    201                         break;
    202 #endif // STENO_ENABLE_GEMINI
    203                     default:
    204                         return false;
    205                 }
    206                 if (!post_process_steno_user(keycode, record, mode, chord, n_pressed_keys)) {
    207                     return false;
    208                 }
    209             } else { // is released
    210                 n_pressed_keys--;
    211                 if (!post_process_steno_user(keycode, record, mode, chord, n_pressed_keys)) {
    212                     return false;
    213                 }
    214                 if (n_pressed_keys > 0) {
    215                     // User hasn't released all keys yet,
    216                     // so the chord cannot be sent
    217                     return false;
    218                 }
    219                 n_pressed_keys = 0;
    220                 if (!send_steno_chord_user(mode, chord)) {
    221                     steno_clear_chord();
    222                     return false;
    223                 }
    224                 switch (mode) {
    225 #if defined(STENO_ENABLE_BOLT) && defined(VIRTSER_ENABLE)
    226                     case STENO_MODE_BOLT:
    227                         send_steno_chord_bolt();
    228                         break;
    229 #endif // STENO_ENABLE_BOLT && VIRTSER_ENABLE
    230 #if defined(STENO_ENABLE_GEMINI) && defined(VIRTSER_ENABLE)
    231                     case STENO_MODE_GEMINI:
    232                         send_steno_chord_gemini();
    233                         break;
    234 #endif // STENO_ENABLE_GEMINI && VIRTSER_ENABLE
    235                     default:
    236                         break;
    237                 }
    238                 steno_clear_chord();
    239             }
    240             break;
    241     }
    242     return false;
    243 }