aboutsummaryrefslogtreecommitdiff
path: root/quantum/process_keycode/process_dynamic_macro.c
diff options
context:
space:
mode:
authorDrashna Jaelre <drashna@live.com>2019-11-04 22:59:13 -0800
committerJames Young <18669334+noroadsleft@users.noreply.github.com>2019-11-04 22:59:13 -0800
commit542cb0a8ce3f324c6bd46751d733daf86384a8f6 (patch)
tree08128a4e00a42fa0e78199d86f757a8d562952a9 /quantum/process_keycode/process_dynamic_macro.c
parent0e664f92c4d61d685259607d7257c53f60da5fc0 (diff)
downloadqmk_firmware-542cb0a8ce3f324c6bd46751d733daf86384a8f6.tar.gz
qmk_firmware-542cb0a8ce3f324c6bd46751d733daf86384a8f6.zip
[Core] Convert Dynamic Macro to a Core Feature (#5948)
* Convert Dynamic Macro to a Core Feature This imports the code from Dynamic Macro into the core code, and handles it, as such. This deprecates the old method but does not remove it, for legacy support. This way, no existing user files need to be touched. Additionally, this reorganizes the documentation to better reflect the changes. Also, it adds user hooks to the feature so users can customize the existing functionality. Based heavily on and closes #2976 * Apply suggestions from code review Co-Authored-By: fauxpark <fauxpark@gmail.com> Co-Authored-By: noroadsleft <18669334+noroadsleft@users.noreply.github.com> * Cleanup based on feedback * Add short-form keycodes and document them - add short-form keycodes to quantum/quantum_keycodes.h - document the new aliases in docs/feature_dynamic_macros.md * Add Dynamic Macros section and keycodes to docs/keycodes.md * Make anti-nesting optional * Add documentation for DYNAMIC_MACRO_NO_NESTING option * Fix Merge artifacts * Fix formatting typo in docs Co-Authored-By: James Young <18669334+noroadsleft@users.noreply.github.com> * Remove DYNAMIC_MACRO_RANGE as it's not needed * Fix includes and layer var type
Diffstat (limited to 'quantum/process_keycode/process_dynamic_macro.c')
-rw-r--r--quantum/process_keycode/process_dynamic_macro.c257
1 files changed, 257 insertions, 0 deletions
diff --git a/quantum/process_keycode/process_dynamic_macro.c b/quantum/process_keycode/process_dynamic_macro.c
new file mode 100644
index 000000000..2065f242d
--- /dev/null
+++ b/quantum/process_keycode/process_dynamic_macro.c
@@ -0,0 +1,257 @@
1/* Copyright 2016 Jack Humbert
2 * Copyright 2019 Drashna Jael're (@drashna, aka Christopher Courtney)
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18/* Author: Wojciech Siewierski < wojciech dot siewierski at onet dot pl > */
19#include "process_dynamic_macro.h"
20
21// default feedback method
22void dynamic_macro_led_blink(void) {
23#ifdef BACKLIGHT_ENABLE
24 backlight_toggle();
25 wait_ms(100);
26 backlight_toggle();
27#endif
28}
29
30/* User hooks for Dynamic Macros */
31
32__attribute__((weak)) void dynamic_macro_record_start_user(void) { dynamic_macro_led_blink(); }
33
34__attribute__((weak)) void dynamic_macro_play_user(int8_t direction) { dynamic_macro_led_blink(); }
35
36__attribute__((weak)) void dynamic_macro_record_key_user(int8_t direction, keyrecord_t *record) { dynamic_macro_led_blink(); }
37
38__attribute__((weak)) void dynamic_macro_record_end_user(int8_t direction) { dynamic_macro_led_blink(); }
39
40/* Convenience macros used for retrieving the debug info. All of them
41 * need a `direction` variable accessible at the call site.
42 */
43#define DYNAMIC_MACRO_CURRENT_SLOT() (direction > 0 ? 1 : 2)
44#define DYNAMIC_MACRO_CURRENT_LENGTH(BEGIN, POINTER) ((int)(direction * ((POINTER) - (BEGIN))))
45#define DYNAMIC_MACRO_CURRENT_CAPACITY(BEGIN, END2) ((int)(direction * ((END2) - (BEGIN)) + 1))
46
47/**
48 * Start recording of the dynamic macro.
49 *
50 * @param[out] macro_pointer The new macro buffer iterator.
51 * @param[in] macro_buffer The macro buffer used to initialize macro_pointer.
52 */
53void dynamic_macro_record_start(keyrecord_t **macro_pointer, keyrecord_t *macro_buffer) {
54 dprintln("dynamic macro recording: started");
55
56 dynamic_macro_record_start_user();
57
58 clear_keyboard();
59 layer_clear();
60 *macro_pointer = macro_buffer;
61}
62
63/**
64 * Play the dynamic macro.
65 *
66 * @param macro_buffer[in] The beginning of the macro buffer being played.
67 * @param macro_end[in] The element after the last macro buffer element.
68 * @param direction[in] Either +1 or -1, which way to iterate the buffer.
69 */
70void dynamic_macro_play(keyrecord_t *macro_buffer, keyrecord_t *macro_end, int8_t direction) {
71 dprintf("dynamic macro: slot %d playback\n", DYNAMIC_MACRO_CURRENT_SLOT());
72
73 layer_state_t saved_layer_state = layer_state;
74
75 clear_keyboard();
76 layer_clear();
77
78 while (macro_buffer != macro_end) {
79 process_record(macro_buffer);
80 macro_buffer += direction;
81 }
82
83 clear_keyboard();
84
85 layer_state = saved_layer_state;
86
87 dynamic_macro_play_user(direction);
88}
89
90/**
91 * Record a single key in a dynamic macro.
92 *
93 * @param macro_buffer[in] The start of the used macro buffer.
94 * @param macro_pointer[in,out] The current buffer position.
95 * @param macro2_end[in] The end of the other macro.
96 * @param direction[in] Either +1 or -1, which way to iterate the buffer.
97 * @param record[in] The current keypress.
98 */
99void dynamic_macro_record_key(keyrecord_t *macro_buffer, keyrecord_t **macro_pointer, keyrecord_t *macro2_end, int8_t direction, keyrecord_t *record) {
100 /* If we've just started recording, ignore all the key releases. */
101 if (!record->event.pressed && *macro_pointer == macro_buffer) {
102 dprintln("dynamic macro: ignoring a leading key-up event");
103 return;
104 }
105
106 /* The other end of the other macro is the last buffer element it
107 * is safe to use before overwriting the other macro.
108 */
109 if (*macro_pointer - direction != macro2_end) {
110 **macro_pointer = *record;
111 *macro_pointer += direction;
112 } else {
113 dynamic_macro_record_key_user(direction, record);
114 }
115
116 dprintf("dynamic macro: slot %d length: %d/%d\n", DYNAMIC_MACRO_CURRENT_SLOT(), DYNAMIC_MACRO_CURRENT_LENGTH(macro_buffer, *macro_pointer), DYNAMIC_MACRO_CURRENT_CAPACITY(macro_buffer, macro2_end));
117}
118
119/**
120 * End recording of the dynamic macro. Essentially just update the
121 * pointer to the end of the macro.
122 */
123void dynamic_macro_record_end(keyrecord_t *macro_buffer, keyrecord_t *macro_pointer, int8_t direction, keyrecord_t **macro_end) {
124 dynamic_macro_record_end_user(direction);
125
126 /* Do not save the keys being held when stopping the recording,
127 * i.e. the keys used to access the layer DYN_REC_STOP is on.
128 */
129 while (macro_pointer != macro_buffer && (macro_pointer - direction)->event.pressed) {
130 dprintln("dynamic macro: trimming a trailing key-down event");
131 macro_pointer -= direction;
132 }
133
134 dprintf("dynamic macro: slot %d saved, length: %d\n", DYNAMIC_MACRO_CURRENT_SLOT(), DYNAMIC_MACRO_CURRENT_LENGTH(macro_buffer, macro_pointer));
135
136 *macro_end = macro_pointer;
137}
138
139/* Handle the key events related to the dynamic macros. Should be
140 * called from process_record_user() like this:
141 *
142 * bool process_record_user(uint16_t keycode, keyrecord_t *record) {
143 * if (!process_record_dynamic_macro(keycode, record)) {
144 * return false;
145 * }
146 * <...THE REST OF THE FUNCTION...>
147 * }
148 */
149bool process_dynamic_macro(uint16_t keycode, keyrecord_t *record) {
150 /* Both macros use the same buffer but read/write on different
151 * ends of it.
152 *
153 * Macro1 is written left-to-right starting from the beginning of
154 * the buffer.
155 *
156 * Macro2 is written right-to-left starting from the end of the
157 * buffer.
158 *
159 * &macro_buffer macro_end
160 * v v
161 * +------------------------------------------------------------+
162 * |>>>>>> MACRO1 >>>>>> <<<<<<<<<<<<< MACRO2 <<<<<<<<<<<<<|
163 * +------------------------------------------------------------+
164 * ^ ^
165 * r_macro_end r_macro_buffer
166 *
167 * During the recording when one macro encounters the end of the
168 * other macro, the recording is stopped. Apart from this, there
169 * are no arbitrary limits for the macros' length in relation to
170 * each other: for example one can either have two medium sized
171 * macros or one long macro and one short macro. Or even one empty
172 * and one using the whole buffer.
173 */
174 static keyrecord_t macro_buffer[DYNAMIC_MACRO_SIZE];
175
176 /* Pointer to the first buffer element after the first macro.
177 * Initially points to the very beginning of the buffer since the
178 * macro is empty. */
179 static keyrecord_t *macro_end = macro_buffer;
180
181 /* The other end of the macro buffer. Serves as the beginning of
182 * the second macro. */
183 static keyrecord_t *const r_macro_buffer = macro_buffer + DYNAMIC_MACRO_SIZE - 1;
184
185 /* Like macro_end but for the second macro. */
186 static keyrecord_t *r_macro_end = r_macro_buffer;
187
188 /* A persistent pointer to the current macro position (iterator)
189 * used during the recording. */
190 static keyrecord_t *macro_pointer = NULL;
191
192 /* 0 - no macro is being recorded right now
193 * 1,2 - either macro 1 or 2 is being recorded */
194 static uint8_t macro_id = 0;
195
196 if (macro_id == 0) {
197 /* No macro recording in progress. */
198 if (!record->event.pressed) {
199 switch (keycode) {
200 case DYN_REC_START1:
201 dynamic_macro_record_start(&macro_pointer, macro_buffer);
202 macro_id = 1;
203 return false;
204 case DYN_REC_START2:
205 dynamic_macro_record_start(&macro_pointer, r_macro_buffer);
206 macro_id = 2;
207 return false;
208 case DYN_MACRO_PLAY1:
209 dynamic_macro_play(macro_buffer, macro_end, +1);
210 return false;
211 case DYN_MACRO_PLAY2:
212 dynamic_macro_play(r_macro_buffer, r_macro_end, -1);
213 return false;
214 }
215 }
216 } else {
217 /* A macro is being recorded right now. */
218 switch (keycode) {
219 case DYN_REC_STOP:
220 /* Stop the macro recording. */
221 if (record->event.pressed) { /* Ignore the initial release
222 * just after the recoding
223 * starts. */
224 switch (macro_id) {
225 case 1:
226 dynamic_macro_record_end(macro_buffer, macro_pointer, +1, &macro_end);
227 break;
228 case 2:
229 dynamic_macro_record_end(r_macro_buffer, macro_pointer, -1, &r_macro_end);
230 break;
231 }
232 macro_id = 0;
233 }
234 return false;
235#ifdef DYNAMIC_MACRO_NO_NESTING
236 case DYN_MACRO_PLAY1:
237 case DYN_MACRO_PLAY2:
238 dprintln("dynamic macro: ignoring macro play key while recording");
239 return false;
240#endif
241 default:
242 /* Store the key in the macro buffer and process it normally. */
243 switch (macro_id) {
244 case 1:
245 dynamic_macro_record_key(macro_buffer, &macro_pointer, r_macro_end, +1, record);
246 break;
247 case 2:
248 dynamic_macro_record_key(r_macro_buffer, &macro_pointer, macro_end, -1, record);
249 break;
250 }
251 return true;
252 break;
253 }
254 }
255
256 return true;
257}