#include QMK_KEYBOARD_H
#include <string.h>
dynamic_macro_t dynamic_macros[DYNAMIC_MACRO_COUNT];
void dynamic_macro_init(void) {
memset(&dynamic_macros, 0, DYNAMIC_MACRO_COUNT * sizeof(dynamic_macro_t));
}
void dynamic_macro_led_blink(void) {
#ifdef BACKLIGHT_ENABLE
backlight_toggle();
wait_ms(100);
backlight_toggle();
#else
led_set(host_keyboard_leds() ^ 0xFF);
wait_ms(100);
led_set(host_keyboard_leds());
#endif
}
void dynamic_macro_record_start(uint8_t macro_id) {
dprintf("dynamic macro recording: started for slot %d\n", macro_id);
dynamic_macro_led_blink();
clear_keyboard();
layer_clear();
dynamic_macros[macro_id].length = 0;
}
void dynamic_macro_play(uint8_t macro_id) {
dprintf("dynamic macro: slot %d playback, length %d\n", macro_id, dynamic_macros[macro_id].length);
uint32_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 = saved_layer_state;
}
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 (!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_led_blink();
}
dprintf("dynamic macro: slot %d length: %d/%d\n", macro_id, length, DYNAMIC_MACRO_SIZE);
}
void dynamic_macro_record_end(uint8_t macro_id) {
dynamic_macro_led_blink();
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;
}
#ifdef DYNAMIC_MACRO_EEPROM_STORAGE
macro->checksum = dynamic_macro_calc_crc(macro);
dynamic_macro_save_eeprom(macro_id);
#endif
dprintf("dynamic macro: slot %d saved, length: %d\n", macro_id, length);
}
bool process_record_dynamic_macro(uint16_t keycode, keyrecord_t* record) {
static uint8_t macro_id = 255;
static uint8_t recording_state = STATE_NOT_RECORDING;
if (STATE_NOT_RECORDING == recording_state) {
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;
}
if (keycode >= DYN_MACRO_KEY1 && keycode <= DYN_MACRO_KEY12 && record->event.pressed) {
dynamic_macro_play(keycode - DYN_MACRO_KEY1);
return false;
}
return true;
} else if (STATE_RECORD_KEY_PRESSED == recording_state) {
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 (keycode >= DYN_MACRO_KEY1 && keycode <= DYN_MACRO_KEY12 && record->event.pressed) {
macro_id = keycode - DYN_MACRO_KEY1;
recording_state = STATE_CURRENTLY_RECORDING;
dynamic_macro_record_start(macro_id);
return false;
}
return false;
} else if (STATE_CURRENTLY_RECORDING == recording_state) {
if (keycode == DYN_MACRO_PROG && record->event.pressed) {
dynamic_macro_record_end(macro_id);
recording_state = STATE_NOT_RECORDING;
return false;
}
else if (keycode >= DYN_MACRO_KEY1 && keycode <= DYN_MACRO_KEY12 && record->event.pressed) {
dprintln("dynamic macro: playback key ignored in programming mode.");
return false;
}
else {
dynamic_macro_record_key(macro_id, record);
return false;
}
}
return true;
}
#ifdef __AVR__
# include <util/crc16.h>
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;
}
#endif
inline void* dynamic_macro_eeprom_macro_addr(uint8_t macro_id) {
return DYNAMIC_MACRO_EEPROM_BLOCK0_ADDR + sizeof(dynamic_macro_t) * macro_id;
}
bool dynamic_macro_header_correct(void) {
return eeprom_read_word(DYNAMIC_MACRO_EEPROM_MAGIC_ADDR) == DYNAMIC_MACRO_EEPROM_MAGIC;
}
void dynamic_macro_load_eeprom_all(void) {
if (!dynamic_macro_header_correct()) {
dprintf("dynamic_macro: eeprom header not valid, not restoring macros.\n");
return;
}
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));
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) {
if (!dynamic_macro_header_correct()) {
eeprom_write_word(DYNAMIC_MACRO_EEPROM_MAGIC_ADDR, DYNAMIC_MACRO_EEPROM_MAGIC);
dprintf("dynamic macro: writing magic eeprom header\n");
}
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);
}