aboutsummaryrefslogtreecommitdiffstats
path: root/keyboards/omnikeyish/dynamic_macro.c
blob: c359b0bdd02d86f8cc116cf7882ddd30fe537840 (plain) (blame)
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
#include QMK_KEYBOARD_H
#include <string.h>

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

/* Blink the LEDs to notify the user about some event. */
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
}

/**
 * Start recording of the dynamic macro.
 *
 * @param macro_id[in]     The id of macro to be recorded
 */
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;
}

/**
 * Play the dynamic macro.
 *
 * @param macro_id[in]     The id of macro to be played
 */
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;
}

/**
 * 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_led_blink();
  }

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

/* Handle the key events related to the dynamic macros. Should be
 * called from process_record_user() like this:
 *
 *   bool process_record_user(uint16_t keycode, keyrecord_t *record) {
 *       if (!process_record_dynamic_macro(keycode, record)) {
 *           return false;
 *       }
 *       <...THE REST OF THE FUNCTION...>
 *   }
 */
bool process_record_dynamic_macro(uint16_t keycode, keyrecord_t* record) {
  /* 0 to DYNAMIC_MACRO_COUNT -1 - macro macro_id is being recorded */
  static uint8_t macro_id        = 255;
  static uint8_t recording_state = STATE_NOT_RECORDING;

  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 (keycode >= DYN_MACRO_KEY1 && keycode <= DYN_MACRO_KEY12 && record->event.pressed) {
      dynamic_macro_play(keycode - DYN_MACRO_KEY1);

      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 (keycode >= DYN_MACRO_KEY1 && keycode <= DYN_MACRO_KEY12 && record->event.pressed) {
      macro_id = keycode - DYN_MACRO_KEY1;

      /* Macro slot selected, enter recording state. */
      recording_state = STATE_CURRENTLY_RECORDING;
      dynamic_macro_record_start(macro_id);

      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 (keycode >= DYN_MACRO_KEY1 && keycode <= DYN_MACRO_KEY12 && 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;
}

#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 /* __AVR__ */

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

  /* 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) {
  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);
}