aboutsummaryrefslogtreecommitdiff
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
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
-rw-r--r--common_features.mk6
-rw-r--r--docs/feature_dynamic_macros.md75
-rw-r--r--docs/keycodes.md10
-rw-r--r--quantum/dynamic_macro.h20
-rw-r--r--quantum/process_keycode/process_dynamic_macro.c257
-rw-r--r--quantum/process_keycode/process_dynamic_macro.h41
-rw-r--r--quantum/quantum.c14
-rw-r--r--quantum/quantum.h4
-rw-r--r--quantum/quantum_keycodes.h16
9 files changed, 384 insertions, 59 deletions
diff --git a/common_features.mk b/common_features.mk
index a1f95955a..e50207b0b 100644
--- a/common_features.mk
+++ b/common_features.mk
@@ -407,8 +407,12 @@ ifeq ($(strip $(SPACE_CADET_ENABLE)), yes)
407 OPT_DEFS += -DSPACE_CADET_ENABLE 407 OPT_DEFS += -DSPACE_CADET_ENABLE
408endif 408endif
409 409
410
411ifeq ($(strip $(DIP_SWITCH_ENABLE)), yes) 410ifeq ($(strip $(DIP_SWITCH_ENABLE)), yes)
412 SRC += $(QUANTUM_DIR)/dip_switch.c 411 SRC += $(QUANTUM_DIR)/dip_switch.c
413 OPT_DEFS += -DDIP_SWITCH_ENABLE 412 OPT_DEFS += -DDIP_SWITCH_ENABLE
414endif 413endif
414
415ifeq ($(strip $(DYNAMIC_MACRO_ENABLE)), yes)
416 SRC += $(QUANTUM_DIR)/process_keycode/process_dynamic_macro.c
417 OPT_DEFS += -DDYNAMIC_MACRO_ENABLE
418endif
diff --git a/docs/feature_dynamic_macros.md b/docs/feature_dynamic_macros.md
index 0d11a2864..b86df6c60 100644
--- a/docs/feature_dynamic_macros.md
+++ b/docs/feature_dynamic_macros.md
@@ -4,51 +4,45 @@ QMK supports temporary macros created on the fly. We call these Dynamic Macros.
4 4
5You can store one or two macros and they may have a combined total of 128 keypresses. You can increase this size at the cost of RAM. 5You can store one or two macros and they may have a combined total of 128 keypresses. You can increase this size at the cost of RAM.
6 6
7To enable them, first add a new element to the end of your `keycodes` enum — `DYNAMIC_MACRO_RANGE`: 7To enable them, first include `DYNAMIC_MACRO_ENABLE = yes` in your `rules.mk`. Then, add the following keys to your keymap:
8 8
9```c 9|Key |Alias |Description |
10enum keycodes { 10|------------------|----------|---------------------------------------------------|
11 QWERTY = SAFE_RANGE, 11|`DYN_REC_START1` |`DM_REC1` |Start recording Macro 1 |
12 COLEMAK, 12|`DYN_REC_START2` |`DM_REC2` |Start recording Macro 2 |
13 DVORAK, 13|`DYN_MACRO_PLAY1` |`DM_PLY1` |Replay Macro 1 |
14 PLOVER, 14|`DYN_MACRO_PLAY2` |`DM_PLY2` |Replay Macro 2 |
15 LOWER, 15|`DYN_REC_STOP` |`DM_RSTP` |Finish the macro that is currently being recorded. |
16 RAISE,
17 BACKLIT,
18 EXT_PLV,
19 DYNAMIC_MACRO_RANGE,
20};
21```
22 16
23Your `keycodes` enum may have a slightly different name. You must add `DYNAMIC_MACRO_RANGE` as the last element because `dynamic_macros.h` will add some more keycodes after it. 17That should be everything necessary.
24 18
25Below it, include the `dynamic_macro.h` header: 19To start recording the macro, press either `DYN_REC_START1` or `DYN_REC_START2`.
26 20
27```c 21To finish the recording, press the `DYN_REC_STOP` layer button.
28 #include "dynamic_macro.h"`
29```
30 22
31Add the following keys to your keymap: 23To replay the macro, press either `DYN_MACRO_PLAY1` or `DYN_MACRO_PLAY2`.
32 24
33* `DYN_REC_START1` — start recording the macro 1, 25It is possible to replay a macro as part of a macro. It's ok to replay macro 2 while recording macro 1 and vice versa but never create recursive macros i.e. macro 1 that replays macro 1. If you do so and the keyboard will get unresponsive, unplug the keyboard and plug it again. You can disable this completly by defining `DYNAMIC_MACRO_NO_NESTING` in your `config.h` file.
34* `DYN_REC_START2` — start recording the macro 2,
35* `DYN_MACRO_PLAY1` — replay the macro 1,
36* `DYN_MACRO_PLAY2` — replay the macro 2,
37* `DYN_REC_STOP` — finish the macro that is currently being recorded.
38 26
39Add the following code to the very beginning of your `process_record_user()` function: 27?> For the details about the internals of the dynamic macros, please read the comments in the `process_dynamic_macro.h` and `process_dynamic_macro.c` files.
40 28
41```c 29## Customization
42 if (!process_record_dynamic_macro(keycode, record)) {
43 return false;
44 }
45```
46 30
47That should be everything necessary. To start recording the macro, press either `DYN_REC_START1` or `DYN_REC_START2`. To finish the recording, press the `DYN_REC_STOP` layer button. To replay the macro, press either `DYN_MACRO_PLAY1` or `DYN_MACRO_PLAY2`. 31There are a number of options added that should allow some additional degree of customization
48 32
49Note that it's possible to replay a macro as part of a macro. It's ok to replay macro 2 while recording macro 1 and vice versa but never create recursive macros i.e. macro 1 that replays macro 1. If you do so and the keyboard will get unresponsive, unplug the keyboard and plug it again. 33|Define |Default |Description |
34|----------------------------|----------------|-----------------------------------------------------------------------------------------------------------------|
35|`DYNAMIC_MACRO_SIZE` |128 |Sets the amount of memory that Dynamic Macros can use. This is a limited resource, dependent on the controller. |
36|`DYNAMIC_MACRO_USER_CALL` |*Not defined* |Defining this falls back to using the user `keymap.c` file to trigger the macro behavior. |
37|`DYNAMIC_MACRO_NO_NESTING` |*Not Defined* |Defining this disables the ability to call a macro from another macro (nested macros). |
50 38
51For users of the earlier versions of dynamic macros: It is still possible to finish the macro recording using just the layer modifier used to access the dynamic macro keys, without a dedicated `DYN_REC_STOP` key. If you want this behavior back, use the following snippet instead of the one above: 39
40If the LEDs start blinking during the recording with each keypress, it means there is no more space for the macro in the macro buffer. To fit the macro in, either make the other macro shorter (they share the same buffer) or increase the buffer size by adding the `DYNAMIC_MACRO_SIZE` define in your `config.h` (default value: 128; please read the comments for it in the header).
41
42
43### DYNAMIC_MACRO_USER_CALL
44
45For users of the earlier versions of dynamic macros: It is still possible to finish the macro recording using just the layer modifier used to access the dynamic macro keys, without a dedicated `DYN_REC_STOP` key. If you want this behavior back, add `#define DYNAMIC_MACRO_USER_CALL` to your `config.h` and insert the following snippet at the beginning of your `process_record_user()` function:
52 46
53```c 47```c
54 uint16_t macro_kc = (keycode == MO(_DYN) ? DYN_REC_STOP : keycode); 48 uint16_t macro_kc = (keycode == MO(_DYN) ? DYN_REC_STOP : keycode);
@@ -58,6 +52,15 @@ For users of the earlier versions of dynamic macros: It is still possible to fin
58 } 52 }
59``` 53```
60 54
61If the LEDs start blinking during the recording with each keypress, it means there is no more space for the macro in the macro buffer. To fit the macro in, either make the other macro shorter (they share the same buffer) or increase the buffer size by setting the `DYNAMIC_MACRO_SIZE` preprocessor macro (default value: 128; please read the comments for it in the header). 55### User Hooks
56
57There are a number of hooks that you can use to add custom functionality and feedback options to Dynamic Macro feature. This allows for some additional degree of customization.
58
59Note, that direction indicates which macro it is, with `1` being Macro 1, `-1` being Macro 2, and 0 being no macro.
60
61* `dynamic_macro_record_start_user(void)` - Triggered when you start recording a macro.
62* `dynamic_macro_play_user(int8_t direction)` - Triggered when you play back a macro.
63* `dynamic_macro_record_key_user(int8_t direction, keyrecord_t *record)` - Triggered on each keypress while recording a macro.
64* `dynamic_macro_record_end_user(int8_t direction)` - Triggered when the macro recording is stopped.
62 65
63For the details about the internals of the dynamic macros, please read the comments in the `dynamic_macro.h` header. 66Additionally, you can call `dynamic_macro_led_blink()` to flash the backlights if that feature is enabled.
diff --git a/docs/keycodes.md b/docs/keycodes.md
index 7dcff03fd..fa01df63d 100644
--- a/docs/keycodes.md
+++ b/docs/keycodes.md
@@ -297,6 +297,16 @@ This is a reference only. Each group of keys links to the page documenting their
297|`OUT_USB` |USB only | 297|`OUT_USB` |USB only |
298|`OUT_BT` |Bluetooth only | 298|`OUT_BT` |Bluetooth only |
299 299
300## [Dynamic Macros](feature_dynamic_macros.md)
301
302|Key |Alias |Description |
303|-----------------|---------|--------------------------------------------------|
304|`DYN_REC_START1` |`DM_REC1`|Start recording Macro 1 |
305|`DYN_REC_START2` |`DM_REC2`|Start recording Macro 2 |
306|`DYN_MACRO_PLAY1`|`DM_PLY1`|Replay Macro 1 |
307|`DYN_MACRO_PLAY2`|`DM_PLY2`|Replay Macro 2 |
308|`DYN_REC_STOP` |`DM_RSTP`|Finish the macro that is currently being recorded.|
309
300## [Layer Switching](feature_advanced_keycodes.md#switching-and-toggling-layers) 310## [Layer Switching](feature_advanced_keycodes.md#switching-and-toggling-layers)
301 311
302|Key |Description | 312|Key |Description |
diff --git a/quantum/dynamic_macro.h b/quantum/dynamic_macro.h
index c7632c004..fe9de6fa6 100644
--- a/quantum/dynamic_macro.h
+++ b/quantum/dynamic_macro.h
@@ -15,8 +15,10 @@
15 */ 15 */
16 16
17/* Author: Wojciech Siewierski < wojciech dot siewierski at onet dot pl > */ 17/* Author: Wojciech Siewierski < wojciech dot siewierski at onet dot pl > */
18#ifndef DYNAMIC_MACROS_H 18#pragma once
19#define DYNAMIC_MACROS_H 19
20/* Warn users that this is now deprecated and they should use the core feature instead. */
21#pragma message "Dynamic Macros is now a core feature. See updated documentation to see how to configure it: https://docs.qmk.fm/#/feature_dynamic_macros"
20 22
21#include "action_layer.h" 23#include "action_layer.h"
22 24
@@ -33,18 +35,6 @@
33# define DYNAMIC_MACRO_SIZE 128 35# define DYNAMIC_MACRO_SIZE 128
34#endif 36#endif
35 37
36/* DYNAMIC_MACRO_RANGE must be set as the last element of user's
37 * "planck_keycodes" enum prior to including this header. This allows
38 * us to 'extend' it.
39 */
40enum dynamic_macro_keycodes {
41 DYN_REC_START1 = DYNAMIC_MACRO_RANGE,
42 DYN_REC_START2,
43 DYN_REC_STOP,
44 DYN_MACRO_PLAY1,
45 DYN_MACRO_PLAY2,
46};
47
48/* Blink the LEDs to notify the user about some event. */ 38/* Blink the LEDs to notify the user about some event. */
49void dynamic_macro_led_blink(void) { 39void dynamic_macro_led_blink(void) {
50#ifdef BACKLIGHT_ENABLE 40#ifdef BACKLIGHT_ENABLE
@@ -272,5 +262,3 @@ bool process_record_dynamic_macro(uint16_t keycode, keyrecord_t *record) {
272#undef DYNAMIC_MACRO_CURRENT_SLOT 262#undef DYNAMIC_MACRO_CURRENT_SLOT
273#undef DYNAMIC_MACRO_CURRENT_LENGTH 263#undef DYNAMIC_MACRO_CURRENT_LENGTH
274#undef DYNAMIC_MACRO_CURRENT_CAPACITY 264#undef DYNAMIC_MACRO_CURRENT_CAPACITY
275
276#endif
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}
diff --git a/quantum/process_keycode/process_dynamic_macro.h b/quantum/process_keycode/process_dynamic_macro.h
new file mode 100644
index 000000000..39036541b
--- /dev/null
+++ b/quantum/process_keycode/process_dynamic_macro.h
@@ -0,0 +1,41 @@
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#pragma once
20
21#include "quantum.h"
22
23/* May be overridden with a custom value. Be aware that the effective
24 * macro length is half of this value: each keypress is recorded twice
25 * because of the down-event and up-event. This is not a bug, it's the
26 * intended behavior.
27 *
28 * Usually it should be fine to set the macro size to at least 256 but
29 * there have been reports of it being too much in some users' cases,
30 * so 128 is considered a safe default.
31 */
32#ifndef DYNAMIC_MACRO_SIZE
33# define DYNAMIC_MACRO_SIZE 128
34#endif
35
36void dynamic_macro_led_blink(void);
37bool process_dynamic_macro(uint16_t keycode, keyrecord_t *record);
38void dynamic_macro_record_start_user(void);
39void dynamic_macro_play_user(int8_t direction);
40void dynamic_macro_record_key_user(int8_t direction, keyrecord_t *record);
41void dynamic_macro_record_end_user(int8_t direction);
diff --git a/quantum/quantum.c b/quantum/quantum.c
index 571dda4c5..1f17c6ff7 100644
--- a/quantum/quantum.c
+++ b/quantum/quantum.c
@@ -26,7 +26,7 @@
26 26
27#ifdef BACKLIGHT_ENABLE 27#ifdef BACKLIGHT_ENABLE
28# include "backlight.h" 28# include "backlight.h"
29 extern backlight_config_t backlight_config; 29extern backlight_config_t backlight_config;
30#endif 30#endif
31 31
32#ifdef FAUXCLICKY_ENABLE 32#ifdef FAUXCLICKY_ENABLE
@@ -89,7 +89,7 @@ static void do_code16(uint16_t code, void (*f)(uint8_t)) {
89 89
90 uint8_t mods_to_send = 0; 90 uint8_t mods_to_send = 0;
91 91
92 if (code & QK_RMODS_MIN) { // Right mod flag is set 92 if (code & QK_RMODS_MIN) { // Right mod flag is set
93 if (code & QK_LCTL) mods_to_send |= MOD_BIT(KC_RCTL); 93 if (code & QK_LCTL) mods_to_send |= MOD_BIT(KC_RCTL);
94 if (code & QK_LSFT) mods_to_send |= MOD_BIT(KC_RSFT); 94 if (code & QK_LSFT) mods_to_send |= MOD_BIT(KC_RSFT);
95 if (code & QK_LALT) mods_to_send |= MOD_BIT(KC_RALT); 95 if (code & QK_LALT) mods_to_send |= MOD_BIT(KC_RALT);
@@ -222,6 +222,10 @@ bool process_record_quantum(keyrecord_t *record) {
222 // Must run first to be able to mask key_up events. 222 // Must run first to be able to mask key_up events.
223 process_key_lock(&keycode, record) && 223 process_key_lock(&keycode, record) &&
224#endif 224#endif
225#if defined(DYNAMIC_MACRO_ENABLE) && !defined(DYNAMIC_MACRO_USER_CALL)
226 // Must run asap to ensure all keypresses are recorded.
227 process_dynamic_macro(keycode, record) &&
228#endif
225#if defined(AUDIO_ENABLE) && defined(AUDIO_CLICKY) 229#if defined(AUDIO_ENABLE) && defined(AUDIO_CLICKY)
226 process_clicky(keycode, record) && 230 process_clicky(keycode, record) &&
227#endif // AUDIO_CLICKY 231#endif // AUDIO_CLICKY
@@ -563,7 +567,7 @@ bool process_record_quantum(keyrecord_t *record) {
563 keymap_config.swap_backslash_backspace = true; 567 keymap_config.swap_backslash_backspace = true;
564 break; 568 break;
565 case MAGIC_HOST_NKRO: 569 case MAGIC_HOST_NKRO:
566 clear_keyboard(); // clear first buffer to prevent stuck keys 570 clear_keyboard(); // clear first buffer to prevent stuck keys
567 keymap_config.nkro = true; 571 keymap_config.nkro = true;
568 break; 572 break;
569 case MAGIC_SWAP_ALT_GUI: 573 case MAGIC_SWAP_ALT_GUI:
@@ -606,7 +610,7 @@ bool process_record_quantum(keyrecord_t *record) {
606 keymap_config.swap_backslash_backspace = false; 610 keymap_config.swap_backslash_backspace = false;
607 break; 611 break;
608 case MAGIC_UNHOST_NKRO: 612 case MAGIC_UNHOST_NKRO:
609 clear_keyboard(); // clear first buffer to prevent stuck keys 613 clear_keyboard(); // clear first buffer to prevent stuck keys
610 keymap_config.nkro = false; 614 keymap_config.nkro = false;
611 break; 615 break;
612 case MAGIC_UNSWAP_ALT_GUI: 616 case MAGIC_UNSWAP_ALT_GUI:
@@ -644,7 +648,7 @@ bool process_record_quantum(keyrecord_t *record) {
644#endif 648#endif
645 break; 649 break;
646 case MAGIC_TOGGLE_NKRO: 650 case MAGIC_TOGGLE_NKRO:
647 clear_keyboard(); // clear first buffer to prevent stuck keys 651 clear_keyboard(); // clear first buffer to prevent stuck keys
648 keymap_config.nkro = !keymap_config.nkro; 652 keymap_config.nkro = !keymap_config.nkro;
649 break; 653 break;
650 case MAGIC_EE_HANDS_LEFT: 654 case MAGIC_EE_HANDS_LEFT:
diff --git a/quantum/quantum.h b/quantum/quantum.h
index 01abe1c0a..87343a15d 100644
--- a/quantum/quantum.h
+++ b/quantum/quantum.h
@@ -149,6 +149,10 @@ extern layer_state_t layer_state;
149 #include "dip_switch.h" 149 #include "dip_switch.h"
150#endif 150#endif
151 151
152#ifdef DYNAMIC_MACRO_ENABLE
153 #include "process_dynamic_macro.h"
154#endif
155
152 156
153// Function substitutions to ease GPIO manipulation 157// Function substitutions to ease GPIO manipulation
154#if defined(__AVR__) 158#if defined(__AVR__)
diff --git a/quantum/quantum_keycodes.h b/quantum/quantum_keycodes.h
index 5fac6a5ca..51a7e290f 100644
--- a/quantum/quantum_keycodes.h
+++ b/quantum/quantum_keycodes.h
@@ -505,6 +505,13 @@ enum quantum_keycodes {
505 MAGIC_EE_HANDS_LEFT, 505 MAGIC_EE_HANDS_LEFT,
506 MAGIC_EE_HANDS_RIGHT, 506 MAGIC_EE_HANDS_RIGHT,
507 507
508 // Dynamic Macros
509 DYN_REC_START1,
510 DYN_REC_START2,
511 DYN_REC_STOP,
512 DYN_MACRO_PLAY1,
513 DYN_MACRO_PLAY2,
514
508 // always leave at the end 515 // always leave at the end
509 SAFE_RANGE 516 SAFE_RANGE
510}; 517};
@@ -757,4 +764,11 @@ enum quantum_keycodes {
757# define SH_OFF (QK_SWAP_HANDS | OP_SH_OFF) 764# define SH_OFF (QK_SWAP_HANDS | OP_SH_OFF)
758#endif 765#endif
759 766
760#endif // QUANTUM_KEYCODES_H 767// Dynamic Macros aliases
768#define DM_REC1 DYN_REC_START1
769#define DM_REC2 DYN_REC_START2
770#define DM_RSTP DYN_REC_STOP
771#define DM_PLY1 DYN_MACRO_PLAY1
772#define DM_PLY2 DYN_MACRO_PLAY2
773
774#endif // QUANTUM_KEYCODES_H