summaryrefslogtreecommitdiffstats
path: root/quantum/process_keycode/process_steno.c
blob: 20b8b9db4bb64f6b532447f1a202d4caee162b51 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
/* Copyright 2017, 2022 Joseph Wasson, Vladislav Kucheriavykh
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#include "process_steno.h"
#include "quantum_keycodes.h"
#include "keymap_steno.h"
#include <string.h>
#ifdef VIRTSER_ENABLE
#    include "virtser.h"
#endif
#ifdef STENO_ENABLE_ALL
#    include "eeprom.h"
#endif

// All steno keys that have been pressed to form this chord,
// stored in MAX_STROKE_SIZE groups of 8-bit arrays.
static uint8_t chord[MAX_STROKE_SIZE] = {0};
// The number of physical keys actually being held down.
// This is not always equal to the number of 1 bits in `chord` because it is possible to
// simultaneously press down four keys, then release three of those four keys and then press yet
// another key while the fourth finger is still holding down its key.
// At the end of this scenario given as an example, `chord` would have five bits set to 1 but
// `n_pressed_keys` would be set to 2 because there are only two keys currently being pressed down.
static int8_t n_pressed_keys = 0;

#ifdef STENO_ENABLE_ALL
static steno_mode_t mode;
#elif defined(STENO_ENABLE_GEMINI)
static const steno_mode_t mode = STENO_MODE_GEMINI;
#elif defined(STENO_ENABLE_BOLT)
static const steno_mode_t mode = STENO_MODE_BOLT;
#endif

static inline void steno_clear_chord(void) {
    memset(chord, 0, sizeof(chord));
}

#ifdef STENO_ENABLE_GEMINI

#    ifdef VIRTSER_ENABLE
void send_steno_chord_gemini(void) {
    // Set MSB to 1 to indicate the start of packet
    chord[0] |= 0x80;
    for (uint8_t i = 0; i < GEMINI_STROKE_SIZE; ++i) {
        virtser_send(chord[i]);
    }
}
#    else
#        pragma message "VIRTSER_ENABLE = yes is required for Gemini PR to work properly out of the box!"
#    endif // VIRTSER_ENABLE

/**
 * @precondition: `key` is pressed
 */
bool add_gemini_key_to_chord(uint8_t key) {
    // Although each group of the packet is 8 bits long, the MSB is reserved
    // to indicate whether that byte is the first byte of the packet (MSB=1)
    // or one of the remaining five bytes of the packet (MSB=0).
    // As a consequence, only 7 out of the 8 bits are left to be used as a bit array
    // for the steno keys of that group.
    const int group_idx       = key / 7;
    const int intra_group_idx = key - group_idx * 7;
    // The 0th steno key of the group has bit=0b01000000, the 1st has bit=0b00100000, etc.
    const uint8_t bit = 1 << (6 - intra_group_idx);
    chord[group_idx] |= bit;
    return false;
}
#endif // STENO_ENABLE_GEMINI

#ifdef STENO_ENABLE_BOLT

#    define TXB_GRP0 0b00000000
#    define TXB_GRP1 0b01000000
#    define TXB_GRP2 0b10000000
#    define TXB_GRP3 0b11000000
#    define TXB_GRPMASK 0b11000000

#    define TXB_GET_GROUP(code) ((code & TXB_GRPMASK) >> 6)

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};

#    ifdef VIRTSER_ENABLE
static void send_steno_chord_bolt(void) {
    for (uint8_t i = 0; i < BOLT_STROKE_SIZE; ++i) {
        // TX Bolt uses variable length packets where each byte corresponds to a bit array of certain keys.
        // If a user chorded the keys of the first group with keys of the last group, for example, there
        // would be bytes of 0x00 in `chord` for the middle groups which we mustn't send.
        if (chord[i]) {
            virtser_send(chord[i]);
        }
    }
    // Sending a null packet is not always necessary, but it is simpler and more reliable
    // to unconditionally send it every time instead of keeping track of more states and
    // creating more branches in the execution of the program.
    virtser_send(0);
}
#    else
#        pragma message "VIRTSER_ENABLE = yes is required for TX Bolt to work properly out of the box!"
#    endif // VIRTSER_ENABLE

/**
 * @precondition: `key` is pressed
 */
static bool add_bolt_key_to_chord(uint8_t key) {
    uint8_t boltcode = pgm_read_byte(boltmap + key);
    chord[TXB_GET_GROUP(boltcode)] |= boltcode;
    return false;
}
#endif // STENO_ENABLE_BOLT

#ifdef STENO_COMBINEDMAP
/* Used to look up when pressing the middle row key to combine two consonant or vowel keys */
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};
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};
#endif

#ifdef STENO_ENABLE_ALL
void steno_init() {
    if (!eeconfig_is_enabled()) {
        eeconfig_init();
    }
    mode = eeprom_read_byte(EECONFIG_STENOMODE);
}

void steno_set_mode(steno_mode_t new_mode) {
    steno_clear_chord();
    mode = new_mode;
    eeprom_update_byte(EECONFIG_STENOMODE, mode);
}
#endif // STENO_ENABLE_ALL

/* override to intercept chords right before they get sent.
 * return zero to suppress normal sending behavior.
 */
__attribute__((weak)) bool send_steno_chord_user(steno_mode_t mode, uint8_t chord[MAX_STROKE_SIZE]) {
    return true;
}

__attribute__((weak)) bool postprocess_steno_user(uint16_t keycode, keyrecord_t *record, steno_mode_t mode, uint8_t chord[MAX_STROKE_SIZE], int8_t n_pressed_keys) {
    return true;
}

__attribute__((weak)) bool process_steno_user(uint16_t keycode, keyrecord_t *record) {
    return true;
}

bool process_steno(uint16_t keycode, keyrecord_t *record) {
    if (keycode < QK_STENO || keycode > QK_STENO_MAX) {
        return true; // Not a steno key, pass it further along the chain
        /*
         * Clearing or sending the chord state is not necessary as we intentionally ignore whatever
         * normal keyboard keys the user may have tapped while chording steno keys.
         */
    }
    if (IS_NOEVENT(record->event)) {
        return true;
    }
    if (!process_steno_user(keycode, record)) {
        return false; // User fully processed the steno key themselves
    }
    switch (keycode) {
#ifdef STENO_ENABLE_ALL
        case QK_STENO_BOLT:
            if (IS_PRESSED(record->event)) {
                steno_set_mode(STENO_MODE_BOLT);
            }
            return false;

        case QK_STENO_GEMINI:
            if (IS_PRESSED(record->event)) {
                steno_set_mode(STENO_MODE_GEMINI);
            }
            return false;
#endif // STENO_ENABLE_ALL

#ifdef STENO_COMBINEDMAP
        case QK_STENO_COMB ... QK_STENO_COMB_MAX: {
            bool first_result  = process_steno(combinedmap_first[keycode - QK_STENO_COMB], record);
            bool second_result = process_steno(combinedmap_second[keycode - QK_STENO_COMB], record);
            return first_result && second_result;
        }
#endif // STENO_COMBINEDMAP
        case STN__MIN ... STN__MAX:
            if (IS_PRESSED(record->event)) {
                n_pressed_keys++;
                switch (mode) {
#ifdef STENO_ENABLE_BOLT
                    case STENO_MODE_BOLT:
                        add_bolt_key_to_chord(keycode - QK_STENO);
                        break;
#endif // STENO_ENABLE_BOLT
#ifdef STENO_ENABLE_GEMINI
                    case STENO_MODE_GEMINI:
                        add_gemini_key_to_chord(keycode - QK_STENO);
                        break;
#endif // STENO_ENABLE_GEMINI
                    default:
                        return false;
                }
                if (!postprocess_steno_user(keycode, record, mode, chord, n_pressed_keys)) {
                    return false;
                }
            } else { // is released
                n_pressed_keys--;
                if (!postprocess_steno_user(keycode, record, mode, chord, n_pressed_keys)) {
                    return false;
                }
                if (n_pressed_keys > 0) {
                    // User hasn't released all keys yet,
                    // so the chord cannot be sent
                    return false;
                }
                n_pressed_keys = 0;
                if (!send_steno_chord_user(mode, chord)) {
                    steno_clear_chord();
                    return false;
                }
                switch (mode) {
#if defined(STENO_ENABLE_BOLT) && defined(VIRTSER_ENABLE)
                    case STENO_MODE_BOLT:
                        send_steno_chord_bolt();
                        break;
#endif // STENO_ENABLE_BOLT && VIRTSER_ENABLE
#if defined(STENO_ENABLE_GEMINI) && defined(VIRTSER_ENABLE)
                    case STENO_MODE_GEMINI:
                        send_steno_chord_gemini();
                        break;
#endif // STENO_ENABLE_GEMINI && VIRTSER_ENABLE
                    default:
                        break;
                }
                steno_clear_chord();
            }
            break;
    }
    return false;
}