summaryrefslogtreecommitdiffstats
path: root/users/drashna/keyrecords/dynamic_macros.c
blob: 43c2336cb60a6a06abfd7a54e0099f38bcb71f2f (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
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
// Copyright 2016 Jack Humbert
// Copyright 2019 Wojciech Siewierski < wojciech dot siewierski at onet dot pl >
// Copyright 2023 Christopher Courtney, aka Drashna Jael're  (@drashna) <drashna@live.com>
// SPDX-License-Identifier: GPL-2.0-or-later

#include "keyrecords/dynamic_macros.h"
#include "keyrecords/process_records.h"
#include "wait.h"
#include "debug.h"
#include "eeprom.h"
#include "eeconfig.h"
#include <string.h>

static uint8_t macro_id        = 255;
static uint8_t recording_state = STATE_NOT_RECORDING;

#if EECONFIG_USER_DATA_SIZE < 4
#    error "EECONFIG_USER_DATA_SIZE not set. Don't step on others eeprom."
#endif
#ifndef DYNAMIC_MACRO_EEPROM_BLOCK0_ADDR
#    define DYNAMIC_MACRO_EEPROM_BLOCK0_ADDR (uint8_t*)(EECONFIG_USER_DATABLOCK + 4)
#endif

dynamic_macro_t dynamic_macros[DYNAMIC_MACRO_COUNT];
_Static_assert((sizeof(dynamic_macros)) <= (EECONFIG_USER_DATA_SIZE - 4), "User Data Size must be large enough to host all macros");

__attribute__((weak)) void dynamic_macro_record_start_user(void) {}

__attribute__((weak)) void dynamic_macro_play_user(uint8_t macro_id) {}

__attribute__((weak)) void dynamic_macro_record_key_user(uint8_t macro_id, keyrecord_t* record) {}

__attribute__((weak)) void dynamic_macro_record_end_user(uint8_t macro_id) {}

/**
 * @brief Gets the current macro ID
 *
 * @return uint8_t
 */
uint8_t dynamic_macro_get_current_id(void) {
    return macro_id;
}

/**
 * @brief Gets the current recording state
 *
 * @return uint8_t
 */
uint8_t dynamic_macro_get_recording_state(void) {
    return recording_state;
}

/**
 * Start recording of the dynamic macro.
 *
 * @param macro_id[in]     The id of macro to be recorded
 */
bool dynamic_macro_record_start(uint8_t macro_id) {
    if (macro_id >= (uint8_t)(DYNAMIC_MACRO_COUNT)) {
        return false;
    }
    dprintf("dynamic macro recording: started for slot %d\n", macro_id);

    dynamic_macro_record_start_user();

    clear_keyboard();
    layer_clear();

    dynamic_macros[macro_id].length = 0;
    return true;
}

/**
 * Play the dynamic macro.
 *
 * @param macro_id[in]     The id of macro to be played
 */
void dynamic_macro_play(uint8_t macro_id) {
    if (macro_id >= (uint8_t)(DYNAMIC_MACRO_COUNT)) {
        return;
    }

    dprintf("dynamic macro: slot %d playback, length %d\n", macro_id, dynamic_macros[macro_id].length);

    layer_state_t saved_layer_state = layer_state;

    clear_keyboard();
    layer_clear();

    for (uint8_t i = 0; i < dynamic_macros[macro_id].length; ++i) {
        process_record(&dynamic_macros[macro_id].events[i]);
    }

    clear_keyboard();

    layer_state_set(saved_layer_state);

    dynamic_macro_play_user(macro_id);
}

/**
 * Record a single key in a dynamic macro.
 *
 * @param macro_id[in] The start of the used macro buffer.
 * @param record[in]     The current keypress.
 */
void dynamic_macro_record_key(uint8_t macro_id, keyrecord_t* record) {
    dynamic_macro_t* macro  = &dynamic_macros[macro_id];
    uint8_t          length = macro->length;

    /* If we've just started recording, ignore all the key releases. */
    if (!record->event.pressed && length == 0) {
        dprintln("dynamic macro: ignoring a leading key-up event");
        return;
    }

    if (length < DYNAMIC_MACRO_SIZE) {
        macro->events[length] = *record;
        macro->length         = ++length;
    } else {
        dynamic_macro_record_key_user(macro_id, record);
    }

    dprintf("dynamic macro: slot %d length: %d/%d\n", macro_id, length, DYNAMIC_MACRO_SIZE);
}

/**
 * End recording of the dynamic macro. Essentially just update the
 * pointer to the end of the macro.
 */
void dynamic_macro_record_end(uint8_t macro_id) {
    if (macro_id >= (uint8_t)(DYNAMIC_MACRO_COUNT)) {
        return;
    }
    dynamic_macro_record_end_user(macro_id);

    dynamic_macro_t* macro  = &dynamic_macros[macro_id];
    uint8_t          length = macro->length;

    keyrecord_t* events_begin   = &(macro->events[0]);
    keyrecord_t* events_pointer = &(macro->events[length - 1]);

    dprintf("dynamic_macro: macro length before trimming: %d\n", macro->length);
    while (events_pointer != events_begin && (events_pointer)->event.pressed) {
        dprintln("dynamic macro: trimming a trailing key-down event");
        --(macro->length);
        --events_pointer;
    }

    macro->checksum = dynamic_macro_calc_crc(macro);
    dynamic_macro_save_eeprom(macro_id);

    dprintf("dynamic macro: slot %d saved, length: %d\n", macro_id, length);
}

bool process_record_dynamic_macro(uint16_t keycode, keyrecord_t* record) {
    if (STATE_NOT_RECORDING == recording_state) {
        /* Program key pressed to request programming mode */
        if (keycode == DYN_MACRO_PROG && record->event.pressed) {
            // dynamic_macro_led_blink();

            recording_state = STATE_RECORD_KEY_PRESSED;
            dprintf("dynamic macro: programming key pressed, waiting for macro slot selection. %d\n", recording_state);

            return false;
        }
        /* Macro key pressed to request macro playback */
        if (IS_DYN_KEYCODE(keycode) && record->event.pressed) {
            dynamic_macro_play(keycode - DYN_MACRO_KEY00);

            return false;
        }

        /* Non-dynamic macro key, process it elsewhere. */
        return true;
    } else if (STATE_RECORD_KEY_PRESSED == recording_state) {
        /* Program key pressed again before a macro selector key, cancel macro recording.
           Blink leds to indicate cancelation. */
        if (keycode == DYN_MACRO_PROG && record->event.pressed) {
            // dynamic_macro_led_blink();

            recording_state = STATE_NOT_RECORDING;
            dprintf("dynamic macro: programming key pressed, programming mode canceled. %d\n", recording_state);

            return false;
        } else if (IS_DYN_KEYCODE(keycode) && record->event.pressed) {
            macro_id = keycode - DYN_MACRO_KEY00;

            if (dynamic_macro_record_start(macro_id)) {
                /* Macro slot selected, enter recording state. */
                recording_state = STATE_CURRENTLY_RECORDING;
            } else {
                recording_state = STATE_NOT_RECORDING;
            }

            return false;
        }
        /* Ignore any non-macro key press while in RECORD_KEY_PRESSED state. */
        return false;
    } else if (STATE_CURRENTLY_RECORDING == recording_state) {
        /* Program key pressed to request end of macro recording. */
        if (keycode == DYN_MACRO_PROG && record->event.pressed) {
            dynamic_macro_record_end(macro_id);
            recording_state = STATE_NOT_RECORDING;

            return false;
        }
        /* Don't record other macro key presses. */
        else if (IS_DYN_KEYCODE(keycode) && record->event.pressed) {
            dprintln("dynamic macro: playback key ignored in programming mode.");
            return false;
        }
        /* Non-macro keypress that should be recorded  */
        else {
            dynamic_macro_record_key(macro_id, record);

            /* Don't output recorded keypress. */
            return false;
        }
    }

    return true;
}

static inline uint16_t crc16_update(uint16_t crc, uint8_t a) {
    crc ^= a;
    for (uint8_t i = 0; i < 8; ++i) {
        if (crc & 1)
            crc = (crc >> 1) ^ 0xA001;
        else
            crc = (crc >> 1);
    }
    return crc;
}

uint16_t dynamic_macro_calc_crc(dynamic_macro_t* macro) {
    uint16_t crc  = 0;
    uint8_t* data = (uint8_t*)macro;

    for (uint16_t i = 0; i < DYNAMIC_MACRO_CRC_LENGTH; ++i) {
        crc = crc16_update(crc, *(data++));
    }
    return crc;
}

inline void* dynamic_macro_eeprom_macro_addr(uint8_t macro_id) {
    return DYNAMIC_MACRO_EEPROM_BLOCK0_ADDR + sizeof(dynamic_macro_t) * macro_id;
}

void dynamic_macro_load_eeprom_all(void) {
    for (uint8_t i = 0; i < DYNAMIC_MACRO_COUNT; ++i) {
        dynamic_macro_load_eeprom(i);
    }
}

void dynamic_macro_load_eeprom(uint8_t macro_id) {
    dynamic_macro_t* dst = &dynamic_macros[macro_id];

    eeprom_read_block(dst, dynamic_macro_eeprom_macro_addr(macro_id), sizeof(dynamic_macro_t));

    /* Validate checksum, ifchecksum is NOT valid for macro, set its length to 0 to prevent its use. */
    if (dynamic_macro_calc_crc(dst) != dst->checksum) {
        dprintf("dynamic macro: slot %d not loaded, checksum mismatch\n", macro_id);
        dst->length = 0;

        return;
    }

    dprintf("dynamic macro: slot %d loaded from eeprom, checksum okay\n", macro_id);
}

void dynamic_macro_save_eeprom(uint8_t macro_id) {
    dynamic_macro_t* src = &dynamic_macros[macro_id];

    eeprom_update_block(src, dynamic_macro_eeprom_macro_addr(macro_id), sizeof(dynamic_macro_t));
    dprintf("dynamic macro: slot %d saved to eeprom\n", macro_id);
}

void dynamic_macro_init(void) {
    /* zero out macro blocks  */
    memset(&dynamic_macros, 0, DYNAMIC_MACRO_COUNT * sizeof(dynamic_macro_t));
    dynamic_macro_load_eeprom_all();
}