aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZach White <skullydazed@gmail.com>2021-11-22 11:11:35 -0800
committerGitHub <noreply@github.com>2021-11-22 11:11:35 -0800
commit08ce0142bad40f22d05d33fdef8a7c8907154e96 (patch)
tree5b5da4650a76ec902a550e2719b79ffc2a73d74d
parent8181b155dbfd07561200b30b52a4046f2da92248 (diff)
downloadqmk_firmware-08ce0142bad40f22d05d33fdef8a7c8907154e96.tar.gz
qmk_firmware-08ce0142bad40f22d05d33fdef8a7c8907154e96.zip
Macros in JSON keymaps (#14374)
* macros in json keymaps * add advanced macro support to json * add a note about escaping macro strings * add simple examples * format json * add support for language specific keymap extras * switch to dictionaries instead of inline text for macros * use SS_TAP on the innermost tap keycode * add the new macro format to the schema * document the macro limit * add the json keyword for syntax highlighting * fix format that vscode screwed up * Update feature_macros.md * add tests for macros * change ding to beep * add json support for SENDSTRING_BELL * update doc based on feedback from sigprof * document host_layout * remove unused var * improve carriage return handling * support tab characters as well * Update docs/feature_macros.md Co-authored-by: Nick Brassel <nick@tzarc.org> * escape backslash characters * format * flake8 * Update quantum/quantum_keycodes.h Co-authored-by: Nick Brassel <nick@tzarc.org>
-rw-r--r--data/mappings/info_config.json1
-rw-r--r--data/schemas/keyboard.jsonschema1
-rw-r--r--data/schemas/keymap.jsonschema35
-rw-r--r--docs/feature_macros.md134
-rw-r--r--keyboards/handwired/pytest/macro/.noci0
-rw-r--r--keyboards/handwired/pytest/macro/info.json10
-rw-r--r--keyboards/handwired/pytest/macro/keymaps/default/keymap.json15
-rw-r--r--keyboards/handwired/pytest/macro/readme.md0
-rw-r--r--keyboards/handwired/pytest/macro/rules.mk1
-rwxr-xr-xlib/python/qmk/cli/json2c.py2
-rw-r--r--lib/python/qmk/commands.py2
-rw-r--r--lib/python/qmk/keymap.py100
-rw-r--r--lib/python/qmk/tests/test_cli_commands.py8
-rw-r--r--lib/python/qmk/tests/test_qmk_keymap.py8
-rw-r--r--quantum/quantum_keycodes.h34
-rw-r--r--requirements-dev.txt1
16 files changed, 319 insertions, 33 deletions
diff --git a/data/mappings/info_config.json b/data/mappings/info_config.json
index 50694e386..3cb64dd3a 100644
--- a/data/mappings/info_config.json
+++ b/data/mappings/info_config.json
@@ -76,6 +76,7 @@
76 "QMK_KEYS_PER_SCAN": {"info_key": "qmk.keys_per_scan", "value_type": "int"}, 76 "QMK_KEYS_PER_SCAN": {"info_key": "qmk.keys_per_scan", "value_type": "int"},
77 "QMK_LED": {"info_key": "qmk_lufa_bootloader.led"}, 77 "QMK_LED": {"info_key": "qmk_lufa_bootloader.led"},
78 "QMK_SPEAKER": {"info_key": "qmk_lufa_bootloader.speaker"}, 78 "QMK_SPEAKER": {"info_key": "qmk_lufa_bootloader.speaker"},
79 "SENDSTRING_BELL": {"info_key": "audio.macro_beep", "value_type": "bool"},
79 "SPLIT_MODS_ENABLE": {"info_key": "split.transport.sync_modifiers", "value_type": "bool"}, 80 "SPLIT_MODS_ENABLE": {"info_key": "split.transport.sync_modifiers", "value_type": "bool"},
80 "SPLIT_TRANSPORT_MIRROR": {"info_key": "split.transport.sync_matrix_state", "value_type": "bool"}, 81 "SPLIT_TRANSPORT_MIRROR": {"info_key": "split.transport.sync_matrix_state", "value_type": "bool"},
81 "SPLIT_USB_DETECT": {"info_key": "split.usb_detect.enabled", "value_type": "bool"}, 82 "SPLIT_USB_DETECT": {"info_key": "split.usb_detect.enabled", "value_type": "bool"},
diff --git a/data/schemas/keyboard.jsonschema b/data/schemas/keyboard.jsonschema
index 65d44c94d..37a0643ab 100644
--- a/data/schemas/keyboard.jsonschema
+++ b/data/schemas/keyboard.jsonschema
@@ -19,6 +19,7 @@
19 "type": "object", 19 "type": "object",
20 "additionalProperties": false, 20 "additionalProperties": false,
21 "properties": { 21 "properties": {
22 "macro_beep": {"type": "boolean"},
22 "pins": {"$ref": "qmk.definitions.v1#/mcu_pin_array"}, 23 "pins": {"$ref": "qmk.definitions.v1#/mcu_pin_array"},
23 "voices": {"type": "boolean"} 24 "voices": {"type": "boolean"}
24 } 25 }
diff --git a/data/schemas/keymap.jsonschema b/data/schemas/keymap.jsonschema
index a4bdab966..faa250a94 100644
--- a/data/schemas/keymap.jsonschema
+++ b/data/schemas/keymap.jsonschema
@@ -5,6 +5,7 @@
5 "type": "object", 5 "type": "object",
6 "properties": { 6 "properties": {
7 "author": {"type": "string"}, 7 "author": {"type": "string"},
8 "host_language": {"$ref": "qmk.definitions.v1#/text_identifier"},
8 "keyboard": {"$ref": "qmk.definitions.v1#/text_identifier"}, 9 "keyboard": {"$ref": "qmk.definitions.v1#/text_identifier"},
9 "keymap": {"$ref": "qmk.definitions.v1#/text_identifier"}, 10 "keymap": {"$ref": "qmk.definitions.v1#/text_identifier"},
10 "layout": {"$ref": "qmk.definitions.v1#/layout_macro"}, 11 "layout": {"$ref": "qmk.definitions.v1#/layout_macro"},
@@ -15,10 +16,42 @@
15 "items": {"type": "string"} 16 "items": {"type": "string"}
16 } 17 }
17 }, 18 },
19 "macros": {
20 "type": "array",
21 "items": {
22 "type": "array",
23 "items": {
24 "oneOf": [
25 {
26 "type": "string"
27 },
28 {
29 "type": "object",
30 "additionalProperties": false,
31 "properties": {
32 "action": {
33 "type": "string",
34 "enum": ['beep', 'delay', 'down', 'tap', 'up']
35 },
36 "keycodes": {
37 "type": "array",
38 "items": {
39 "$ref": "qmk.definitions.v1#/text_identifier"
40 }
41 },
42 "duration": {
43 "$ref": "qmk.definitions.v1#/unsigned_int"
44 }
45 }
46 }
47 ]
48 }
49 }
50 },
18 "config": {"$ref": "qmk.keyboard.v1"}, 51 "config": {"$ref": "qmk.keyboard.v1"},
19 "notes": { 52 "notes": {
20 "type": "string", 53 "type": "string",
21 "description": "asdf" 54 "description": "asdf"
22 } 55 }
23 } 56 }
24} \ No newline at end of file 57}
diff --git a/docs/feature_macros.md b/docs/feature_macros.md
index 6807111ca..81ade5859 100644
--- a/docs/feature_macros.md
+++ b/docs/feature_macros.md
@@ -4,7 +4,107 @@ Macros allow you to send multiple keystrokes when pressing just one key. QMK has
4 4
5!> **Security Note**: While it is possible to use macros to send passwords, credit card numbers, and other sensitive information it is a supremely bad idea to do so. Anyone who gets a hold of your keyboard will be able to access that information by opening a text editor. 5!> **Security Note**: While it is possible to use macros to send passwords, credit card numbers, and other sensitive information it is a supremely bad idea to do so. Anyone who gets a hold of your keyboard will be able to access that information by opening a text editor.
6 6
7## `SEND_STRING()` & `process_record_user` 7## Using Macros In JSON Keymaps
8
9You can define up to 32 macros in a `keymap.json` file, as used by [Configurator](newbs_building_firmware_configurator.md), and `qmk compile`. You can define these macros in a list under the `macros` keyword, like this:
10
11```json
12{
13 "keyboard": "handwired/my_macropad",
14 "keymap": "my_keymap",
15 "macros": [
16 [
17 {"action":"down", "keycodes": ["LSFT"]},
18 "hello world1",
19 {"action": "up","keycodes": ["LSFT"]}
20 ],
21 [
22 {"action":"tap", "keycodes": ["LCTL", "LALT", "DEL"]}
23 ],
24 [
25 "ding!",
26 {"action":"beep"}
27 ],
28 [
29 {"action":"tap", "keycodes": ["F1"]},
30 {"action":"delay", "duration": "1000"},
31 {"action":"tap", "keycodes": ["PGDN"]}
32 ]
33 ],
34 "layout": "LAYOUT_all",
35 "layers": [
36 ["MACRO_0", "MACRO_1", "MACRO_2", "MACRO_3"]
37 ]
38}
39```
40
41### Selecting Your Host Keyboard Layout
42
43If you type in a language other than English, or use a non-QWERTY layout like Colemak, Dvorak, or Workman, you may have set your computer's input language to match this layout. This presents a challenge when creating macros- you may need to type different keys to get the same letters! To address this you can add the `host_language` key to your keymap.json, like so:
44
45```json
46{
47 "keyboard": "handwired/my_macropad",
48 "keymap": "my_keymap",
49 "host_layout": "dvorak",
50 "macros": [
51 ["Hello, World!"]
52 ],
53 "layout": "LAYOUT_all",
54 "layers": [
55 ["MACRO_0"]
56 ]
57}
58```
59
60The current list of available languages is:
61
62| belgian | bepo | br_abnt2 | canadian_multilingual |
63|:-------:|:----:|:--------:|:---------------------:|
64| **colemak** | **croatian** | **czech** | **danish** |
65| **dvorak_fr** | **dvorak** | **dvp** | **estonian** |
66| **finnish** | **fr_ch** | **french_afnor** | **french** |
67| **french_osx** | **german_ch** | **german** | **german_osx** |
68| **hungarian** | **icelandic** | **italian** | **italian_osx_ansi** |
69| **italian_osx_iso** | **jis** | **latvian** | **lithuanian_azerty** |
70| **lithuanian_qwerty** | **norman** | **norwegian** | **portuguese** |
71| **portuguese_osx_iso** | **romanian** | **serbian_latin** | **slovak** |
72| **slovenian** | **spanish_dvorak** | **spanish** | **swedish** |
73| **turkish_f** | **turkish_q** | **uk** | **us_international** |
74| **workman** | **workman_zxcvm** |
75
76### Macro Basics
77
78Each macro is an array consisting of strings and objects (dictionaries.) Strings are typed to your computer while objects allow you to control how your macro is typed out.
79
80#### Object Format
81
82All objects have one required key: `action`. This tells QMK what the object does. There are currently 5 actions: beep, delay, down, tap, up
83
84Only basic keycodes (prefixed by `KC_`) are supported. Do not include the `KC_` prefix when listing keycodes.
85
86* `beep`
87 * Play a bell if the keyboard has [audio enabled](feature_audio.md).
88 * Example: `{"action": "beep"}`
89* `delay`
90 * Pause macro playback. Duration is specified in milliseconds (ms).
91 * Example: `{"action": "delay", "duration": 500}`
92* `down`
93 * Send a key down event for one or more keycodes.
94 * Example, single key: `{"action":"down", "keycodes": ["LSFT"]}`
95 * Example, multiple keys: `{"action":"down", "keycodes": ["CTRL", "LSFT"]}`
96* `tap`
97 * Type a chord, which sends a down event for each key followed by an up event for each key.
98 * Example, single key: `{"action":"tap", "keycodes": ["F13"]}`
99 * Example, multiple keys: `{"action":"tap", "keycodes": ["CTRL", "LALT", "DEL"]}`
100* `up`
101 * Send a key up event for one or more keycodes.
102 * Example, single key: `{"action":"up", "keycodes": ["LSFT"]}`
103 * Example, multiple keys: `{"action":"up", "keycodes": ["CTRL", "LSFT"]}`
104
105## Using Macros in C Keymaps
106
107### `SEND_STRING()` & `process_record_user`
8 108
9Sometimes you want a key to type out words or phrases. For the most common situations, we've provided `SEND_STRING()`, which will type out a string (i.e. a sequence of characters) for you. All ASCII characters that are easily translatable to a keycode are supported (e.g. `qmk 123\n\t`). 109Sometimes you want a key to type out words or phrases. For the most common situations, we've provided `SEND_STRING()`, which will type out a string (i.e. a sequence of characters) for you. All ASCII characters that are easily translatable to a keycode are supported (e.g. `qmk 123\n\t`).
10 110
@@ -91,7 +191,7 @@ const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
91}; 191};
92``` 192```
93 193
94### Advanced Macros 194#### Advanced Macros
95 195
96In addition to the `process_record_user()` function, is the `post_process_record_user()` function. This runs after `process_record` and can be used to do things after a keystroke has been sent. This is useful if you want to have a key pressed before and released after a normal key, for instance. 196In addition to the `process_record_user()` function, is the `post_process_record_user()` function. This runs after `process_record` and can be used to do things after a keystroke has been sent. This is useful if you want to have a key pressed before and released after a normal key, for instance.
97 197
@@ -131,7 +231,7 @@ void post_process_record_user(uint16_t keycode, keyrecord_t *record) {
131``` 231```
132 232
133 233
134### TAP, DOWN and UP 234#### TAP, DOWN and UP
135 235
136You may want to use keys in your macros that you can't write down, such as `Ctrl` or `Home`. 236You may want to use keys in your macros that you can't write down, such as `Ctrl` or `Home`.
137You can send arbitrary keycodes by wrapping them in: 237You can send arbitrary keycodes by wrapping them in:
@@ -178,7 +278,7 @@ They can be used like this:
178 278
179Which would send Left Control+`a` (Left Control down, `a`, Left Control up) - notice that they take strings (eg `"k"`), and not the `X_K` keycodes. 279Which would send Left Control+`a` (Left Control down, `a`, Left Control up) - notice that they take strings (eg `"k"`), and not the `X_K` keycodes.
180 280
181### Alternative Keymaps 281#### Alternative Keymaps
182 282
183By default, it assumes a US keymap with a QWERTY layout; if you want to change that (e.g. if your OS uses software Colemak), include this somewhere in your keymap: 283By default, it assumes a US keymap with a QWERTY layout; if you want to change that (e.g. if your OS uses software Colemak), include this somewhere in your keymap:
184 284
@@ -186,7 +286,7 @@ By default, it assumes a US keymap with a QWERTY layout; if you want to change t
186#include "sendstring_colemak.h" 286#include "sendstring_colemak.h"
187``` 287```
188 288
189### Strings in Memory 289#### Strings in Memory
190 290
191If for some reason you're manipulating strings and need to print out something you just generated (instead of being a literal, constant string), you can use `send_string()`, like this: 291If for some reason you're manipulating strings and need to print out something you just generated (instead of being a literal, constant string), you can use `send_string()`, like this:
192 292
@@ -205,13 +305,13 @@ SEND_STRING(".."SS_TAP(X_END));
205``` 305```
206 306
207 307
208## Advanced Macro Functions 308### Advanced Macro Functions
209 309
210There are some functions you may find useful in macro-writing. Keep in mind that while you can write some fairly advanced code within a macro, if your functionality gets too complex you may want to define a custom keycode instead. Macros are meant to be simple. 310There are some functions you may find useful in macro-writing. Keep in mind that while you can write some fairly advanced code within a macro, if your functionality gets too complex you may want to define a custom keycode instead. Macros are meant to be simple.
211 311
212?> You can also use the functions described in [Useful function](ref_functions.md) and [Checking modifier state](feature_advanced_keycodes#checking-modifier-state) for additional functionality. For example, `reset_keyboard()` allows you to reset the keyboard as part of a macro and `get_mods() & MOD_MASK_SHIFT` lets you check for the existence of active shift modifiers. 312?> You can also use the functions described in [Useful function](ref_functions.md) and [Checking modifier state](feature_advanced_keycodes#checking-modifier-state) for additional functionality. For example, `reset_keyboard()` allows you to reset the keyboard as part of a macro and `get_mods() & MOD_MASK_SHIFT` lets you check for the existence of active shift modifiers.
213 313
214### `record->event.pressed` 314#### `record->event.pressed`
215 315
216This is a boolean value that can be tested to see if the switch is being pressed or released. An example of this is 316This is a boolean value that can be tested to see if the switch is being pressed or released. An example of this is
217 317
@@ -223,15 +323,15 @@ This is a boolean value that can be tested to see if the switch is being pressed
223 } 323 }
224``` 324```
225 325
226### `register_code(<kc>);` 326#### `register_code(<kc>);`
227 327
228This sends the `<kc>` keydown event to the computer. Some examples would be `KC_ESC`, `KC_C`, `KC_4`, and even modifiers such as `KC_LSFT` and `KC_LGUI`. 328This sends the `<kc>` keydown event to the computer. Some examples would be `KC_ESC`, `KC_C`, `KC_4`, and even modifiers such as `KC_LSFT` and `KC_LGUI`.
229 329
230### `unregister_code(<kc>);` 330#### `unregister_code(<kc>);`
231 331
232Parallel to `register_code` function, this sends the `<kc>` keyup event to the computer. If you don't use this, the key will be held down until it's sent. 332Parallel to `register_code` function, this sends the `<kc>` keyup event to the computer. If you don't use this, the key will be held down until it's sent.
233 333
234### `tap_code(<kc>);` 334#### `tap_code(<kc>);`
235 335
236Sends `register_code(<kc>)` and then `unregister_code(<kc>)`. This is useful if you want to send both the press and release events ("tap" the key, rather than hold it). 336Sends `register_code(<kc>)` and then `unregister_code(<kc>)`. This is useful if you want to send both the press and release events ("tap" the key, rather than hold it).
237 337
@@ -239,31 +339,31 @@ If `TAP_CODE_DELAY` is defined (default 0), this function waits that many millis
239 339
240If the keycode is `KC_CAPS`, it waits `TAP_HOLD_CAPS_DELAY` milliseconds instead (default 80), as macOS prevents accidental Caps Lock activation by waiting for the key to be held for a certain amount of time. 340If the keycode is `KC_CAPS`, it waits `TAP_HOLD_CAPS_DELAY` milliseconds instead (default 80), as macOS prevents accidental Caps Lock activation by waiting for the key to be held for a certain amount of time.
241 341
242### `tap_code_delay(<kc>, <delay>);` 342#### `tap_code_delay(<kc>, <delay>);`
243 343
244Like `tap_code(<kc>)`, but with a `delay` parameter for specifying arbitrary intervals before sending the unregister event. 344Like `tap_code(<kc>)`, but with a `delay` parameter for specifying arbitrary intervals before sending the unregister event.
245 345
246### `register_code16(<kc>);`, `unregister_code16(<kc>);` and `tap_code16(<kc>);` 346#### `register_code16(<kc>);`, `unregister_code16(<kc>);` and `tap_code16(<kc>);`
247 347
248These functions work similar to their regular counterparts, but allow you to use modded keycodes (with Shift, Alt, Control, and/or GUI applied to them). 348These functions work similar to their regular counterparts, but allow you to use modded keycodes (with Shift, Alt, Control, and/or GUI applied to them).
249 349
250Eg, you could use `register_code16(S(KC_5));` instead of registering the mod, then registering the keycode. 350Eg, you could use `register_code16(S(KC_5));` instead of registering the mod, then registering the keycode.
251 351
252### `clear_keyboard();` 352#### `clear_keyboard();`
253 353
254This will clear all mods and keys currently pressed. 354This will clear all mods and keys currently pressed.
255 355
256### `clear_mods();` 356#### `clear_mods();`
257 357
258This will clear all mods currently pressed. 358This will clear all mods currently pressed.
259 359
260### `clear_keyboard_but_mods();` 360#### `clear_keyboard_but_mods();`
261 361
262This will clear all keys besides the mods currently pressed. 362This will clear all keys besides the mods currently pressed.
263 363
264## Advanced Example: 364### Advanced Example:
265 365
266### Super ALT↯TAB 366#### Super ALT↯TAB
267 367
268This macro will register `KC_LALT` and tap `KC_TAB`, then wait for 1000ms. If the key is tapped again, it will send another `KC_TAB`; if there is no tap, `KC_LALT` will be unregistered, thus allowing you to cycle through windows. 368This macro will register `KC_LALT` and tap `KC_TAB`, then wait for 1000ms. If the key is tapped again, it will send another `KC_TAB`; if there is no tap, `KC_LALT` will be unregistered, thus allowing you to cycle through windows.
269 369
diff --git a/keyboards/handwired/pytest/macro/.noci b/keyboards/handwired/pytest/macro/.noci
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/keyboards/handwired/pytest/macro/.noci
diff --git a/keyboards/handwired/pytest/macro/info.json b/keyboards/handwired/pytest/macro/info.json
new file mode 100644
index 000000000..ed052a14a
--- /dev/null
+++ b/keyboards/handwired/pytest/macro/info.json
@@ -0,0 +1,10 @@
1{
2 "maintainer": "qmk",
3 "layouts": {
4 "LAYOUT_custom": {
5 "layout": [
6 { "label": "KC_Q", "matrix": [0, 0], "w": 1, "x": 0, "y": 0 }
7 ]
8 }
9 }
10}
diff --git a/keyboards/handwired/pytest/macro/keymaps/default/keymap.json b/keyboards/handwired/pytest/macro/keymaps/default/keymap.json
new file mode 100644
index 000000000..f319d862d
--- /dev/null
+++ b/keyboards/handwired/pytest/macro/keymaps/default/keymap.json
@@ -0,0 +1,15 @@
1{
2 "keyboard": "handwired/pytest/basic",
3 "keymap": "default_json",
4 "layout": "LAYOUT_ortho_1x1",
5 "layers": [["MACRO_0"]],
6 "macros": [
7 [
8 "Hello, World!",
9 {"action":"tap", "keycodes":["ENTER"]}
10 ]
11 ],
12 "author": "qmk",
13 "notes": "This file is a keymap.json file for handwired/pytest/basic",
14 "version": 1
15}
diff --git a/keyboards/handwired/pytest/macro/readme.md b/keyboards/handwired/pytest/macro/readme.md
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/keyboards/handwired/pytest/macro/readme.md
diff --git a/keyboards/handwired/pytest/macro/rules.mk b/keyboards/handwired/pytest/macro/rules.mk
new file mode 100644
index 000000000..6b42774db
--- /dev/null
+++ b/keyboards/handwired/pytest/macro/rules.mk
@@ -0,0 +1 @@
MCU = atmega32u4
diff --git a/lib/python/qmk/cli/json2c.py b/lib/python/qmk/cli/json2c.py
index a90578c02..ae8248e6b 100755
--- a/lib/python/qmk/cli/json2c.py
+++ b/lib/python/qmk/cli/json2c.py
@@ -33,7 +33,7 @@ def json2c(cli):
33 cli.args.output = None 33 cli.args.output = None
34 34
35 # Generate the keymap 35 # Generate the keymap
36 keymap_c = qmk.keymap.generate_c(user_keymap['keyboard'], user_keymap['layout'], user_keymap['layers']) 36 keymap_c = qmk.keymap.generate_c(user_keymap)
37 37
38 if cli.args.output: 38 if cli.args.output:
39 cli.args.output.parent.mkdir(parents=True, exist_ok=True) 39 cli.args.output.parent.mkdir(parents=True, exist_ok=True)
diff --git a/lib/python/qmk/commands.py b/lib/python/qmk/commands.py
index 2995a5fda..5a0194377 100644
--- a/lib/python/qmk/commands.py
+++ b/lib/python/qmk/commands.py
@@ -190,7 +190,7 @@ def compile_configurator_json(user_keymap, bootloader=None, parallel=1, **env_va
190 target = f'{keyboard_filesafe}_{user_keymap["keymap"]}' 190 target = f'{keyboard_filesafe}_{user_keymap["keymap"]}'
191 keyboard_output = Path(f'{KEYBOARD_OUTPUT_PREFIX}{keyboard_filesafe}') 191 keyboard_output = Path(f'{KEYBOARD_OUTPUT_PREFIX}{keyboard_filesafe}')
192 keymap_output = Path(f'{keyboard_output}_{user_keymap["keymap"]}') 192 keymap_output = Path(f'{keyboard_output}_{user_keymap["keymap"]}')
193 c_text = qmk.keymap.generate_c(user_keymap['keyboard'], user_keymap['layout'], user_keymap['layers']) 193 c_text = qmk.keymap.generate_c(user_keymap)
194 keymap_dir = keymap_output / 'src' 194 keymap_dir = keymap_output / 'src'
195 keymap_c = keymap_dir / 'keymap.c' 195 keymap_c = keymap_dir / 'keymap.c'
196 196
diff --git a/lib/python/qmk/keymap.py b/lib/python/qmk/keymap.py
index 6eec49cfd..00b5a78a5 100644
--- a/lib/python/qmk/keymap.py
+++ b/lib/python/qmk/keymap.py
@@ -17,6 +17,7 @@ from qmk.errors import CppError
17 17
18# The `keymap.c` template to use when a keyboard doesn't have its own 18# The `keymap.c` template to use when a keyboard doesn't have its own
19DEFAULT_KEYMAP_C = """#include QMK_KEYBOARD_H 19DEFAULT_KEYMAP_C = """#include QMK_KEYBOARD_H
20__INCLUDES__
20 21
21/* THIS FILE WAS GENERATED! 22/* THIS FILE WAS GENERATED!
22 * 23 *
@@ -27,6 +28,7 @@ DEFAULT_KEYMAP_C = """#include QMK_KEYBOARD_H
27const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { 28const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
28__KEYMAP_GOES_HERE__ 29__KEYMAP_GOES_HERE__
29}; 30};
31
30""" 32"""
31 33
32 34
@@ -180,10 +182,11 @@ def generate_json(keymap, keyboard, layout, layers):
180 return new_keymap 182 return new_keymap
181 183
182 184
183def generate_c(keyboard, layout, layers): 185def generate_c(keymap_json):
184 """Returns a `keymap.c` or `keymap.json` for the specified keyboard, layout, and layers. 186 """Returns a `keymap.c`.
187
188 `keymap_json` is a dictionary with the following keys:
185 189
186 Args:
187 keyboard 190 keyboard
188 The name of the keyboard 191 The name of the keyboard
189 192
@@ -192,19 +195,89 @@ def generate_c(keyboard, layout, layers):
192 195
193 layers 196 layers
194 An array of arrays describing the keymap. Each item in the inner array should be a string that is a valid QMK keycode. 197 An array of arrays describing the keymap. Each item in the inner array should be a string that is a valid QMK keycode.
198
199 macros
200 A sequence of strings containing macros to implement for this keyboard.
195 """ 201 """
196 new_keymap = template_c(keyboard) 202 new_keymap = template_c(keymap_json['keyboard'])
197 layer_txt = [] 203 layer_txt = []
198 for layer_num, layer in enumerate(layers): 204
205 for layer_num, layer in enumerate(keymap_json['layers']):
199 if layer_num != 0: 206 if layer_num != 0:
200 layer_txt[-1] = layer_txt[-1] + ',' 207 layer_txt[-1] = layer_txt[-1] + ','
201 layer = map(_strip_any, layer) 208 layer = map(_strip_any, layer)
202 layer_keys = ', '.join(layer) 209 layer_keys = ', '.join(layer)
203 layer_txt.append('\t[%s] = %s(%s)' % (layer_num, layout, layer_keys)) 210 layer_txt.append('\t[%s] = %s(%s)' % (layer_num, keymap_json['layout'], layer_keys))
204 211
205 keymap = '\n'.join(layer_txt) 212 keymap = '\n'.join(layer_txt)
206 new_keymap = new_keymap.replace('__KEYMAP_GOES_HERE__', keymap) 213 new_keymap = new_keymap.replace('__KEYMAP_GOES_HERE__', keymap)
207 214
215 if keymap_json.get('macros'):
216 macro_txt = [
217 'bool process_record_user(uint16_t keycode, keyrecord_t *record) {',
218 ' if (record->event.pressed) {',
219 ' switch (keycode) {',
220 ]
221
222 for i, macro_array in enumerate(keymap_json['macros']):
223 macro = []
224
225 for macro_fragment in macro_array:
226 if isinstance(macro_fragment, str):
227 macro_fragment = macro_fragment.replace('\\', '\\\\')
228 macro_fragment = macro_fragment.replace('\r\n', r'\n')
229 macro_fragment = macro_fragment.replace('\n', r'\n')
230 macro_fragment = macro_fragment.replace('\r', r'\n')
231 macro_fragment = macro_fragment.replace('\t', r'\t')
232 macro_fragment = macro_fragment.replace('"', r'\"')
233
234 macro.append(f'"{macro_fragment}"')
235
236 elif isinstance(macro_fragment, dict):
237 newstring = []
238
239 if macro_fragment['action'] == 'delay':
240 newstring.append(f"SS_DELAY({macro_fragment['duration']})")
241
242 elif macro_fragment['action'] == 'beep':
243 newstring.append(r'"\a"')
244
245 elif macro_fragment['action'] == 'tap' and len(macro_fragment['keycodes']) > 1:
246 last_keycode = macro_fragment['keycodes'].pop()
247
248 for keycode in macro_fragment['keycodes']:
249 newstring.append(f'SS_DOWN(X_{keycode})')
250
251 newstring.append(f'SS_TAP(X_{last_keycode})')
252
253 for keycode in reversed(macro_fragment['keycodes']):
254 newstring.append(f'SS_UP(X_{keycode})')
255
256 else:
257 for keycode in macro_fragment['keycodes']:
258 newstring.append(f"SS_{macro_fragment['action'].upper()}(X_{keycode})")
259
260 macro.append(''.join(newstring))
261
262 new_macro = "".join(macro)
263 new_macro = new_macro.replace('""', '')
264 macro_txt.append(f' case MACRO_{i}:')
265 macro_txt.append(f' SEND_STRING({new_macro});')
266 macro_txt.append(' return false;')
267
268 macro_txt.append(' }')
269 macro_txt.append(' }')
270 macro_txt.append('\n return true;')
271 macro_txt.append('};')
272 macro_txt.append('')
273
274 new_keymap = '\n'.join((new_keymap, *macro_txt))
275
276 if keymap_json.get('host_language'):
277 new_keymap = new_keymap.replace('__INCLUDES__', f'#include "keymap_{keymap_json["host_language"]}.h"\n#include "sendstring_{keymap_json["host_language"]}.h"\n')
278 else:
279 new_keymap = new_keymap.replace('__INCLUDES__', '')
280
208 return new_keymap 281 return new_keymap
209 282
210 283
@@ -217,7 +290,7 @@ def write_file(keymap_filename, keymap_content):
217 return keymap_filename 290 return keymap_filename
218 291
219 292
220def write_json(keyboard, keymap, layout, layers): 293def write_json(keyboard, keymap, layout, layers, macros=None):
221 """Generate the `keymap.json` and write it to disk. 294 """Generate the `keymap.json` and write it to disk.
222 295
223 Returns the filename written to. 296 Returns the filename written to.
@@ -235,19 +308,19 @@ def write_json(keyboard, keymap, layout, layers):
235 layers 308 layers
236 An array of arrays describing the keymap. Each item in the inner array should be a string that is a valid QMK keycode. 309 An array of arrays describing the keymap. Each item in the inner array should be a string that is a valid QMK keycode.
237 """ 310 """
238 keymap_json = generate_json(keyboard, keymap, layout, layers) 311 keymap_json = generate_json(keyboard, keymap, layout, layers, macros=None)
239 keymap_content = json.dumps(keymap_json) 312 keymap_content = json.dumps(keymap_json)
240 keymap_file = qmk.path.keymap(keyboard) / keymap / 'keymap.json' 313 keymap_file = qmk.path.keymap(keyboard) / keymap / 'keymap.json'
241 314
242 return write_file(keymap_file, keymap_content) 315 return write_file(keymap_file, keymap_content)
243 316
244 317
245def write(keyboard, keymap, layout, layers): 318def write(keymap_json):
246 """Generate the `keymap.c` and write it to disk. 319 """Generate the `keymap.c` and write it to disk.
247 320
248 Returns the filename written to. 321 Returns the filename written to.
249 322
250 Args: 323 `keymap_json` should be a dict with the following keys:
251 keyboard 324 keyboard
252 The name of the keyboard 325 The name of the keyboard
253 326
@@ -259,9 +332,12 @@ def write(keyboard, keymap, layout, layers):
259 332
260 layers 333 layers
261 An array of arrays describing the keymap. Each item in the inner array should be a string that is a valid QMK keycode. 334 An array of arrays describing the keymap. Each item in the inner array should be a string that is a valid QMK keycode.
335
336 macros
337 A list of macros for this keymap.
262 """ 338 """
263 keymap_content = generate_c(keyboard, layout, layers) 339 keymap_content = generate_c(keymap_json)
264 keymap_file = qmk.path.keymap(keyboard) / keymap / 'keymap.c' 340 keymap_file = qmk.path.keymap(keymap_json['keyboard']) / keymap_json['keymap'] / 'keymap.c'
265 341
266 return write_file(keymap_file, keymap_content) 342 return write_file(keymap_file, keymap_content)
267 343
diff --git a/lib/python/qmk/tests/test_cli_commands.py b/lib/python/qmk/tests/test_cli_commands.py
index 1e3c64e73..2973f8170 100644
--- a/lib/python/qmk/tests/test_cli_commands.py
+++ b/lib/python/qmk/tests/test_cli_commands.py
@@ -142,6 +142,14 @@ def test_json2c():
142 assert result.stdout == '#include QMK_KEYBOARD_H\nconst uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {\t[0] = LAYOUT_ortho_1x1(KC_A)};\n\n' 142 assert result.stdout == '#include QMK_KEYBOARD_H\nconst uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {\t[0] = LAYOUT_ortho_1x1(KC_A)};\n\n'
143 143
144 144
145def test_json2c_macros():
146 result = check_subcommand("json2c", 'keyboards/handwired/pytest/macro/keymaps/default/keymap.json')
147 check_returncode(result)
148 assert 'LAYOUT_ortho_1x1(MACRO_0)' in result.stdout
149 assert 'case MACRO_0:' in result.stdout
150 assert 'SEND_STRING("Hello, World!"SS_TAP(X_ENTER));' in result.stdout
151
152
145def test_json2c_stdin(): 153def test_json2c_stdin():
146 result = check_subcommand_stdin('keyboards/handwired/pytest/has_template/keymaps/default_json/keymap.json', 'json2c', '-') 154 result = check_subcommand_stdin('keyboards/handwired/pytest/has_template/keymaps/default_json/keymap.json', 'json2c', '-')
147 check_returncode(result) 155 check_returncode(result)
diff --git a/lib/python/qmk/tests/test_qmk_keymap.py b/lib/python/qmk/tests/test_qmk_keymap.py
index b9e80df67..5e2efc123 100644
--- a/lib/python/qmk/tests/test_qmk_keymap.py
+++ b/lib/python/qmk/tests/test_qmk_keymap.py
@@ -22,7 +22,13 @@ def test_template_json_pytest_has_template():
22 22
23 23
24def test_generate_c_pytest_has_template(): 24def test_generate_c_pytest_has_template():
25 templ = qmk.keymap.generate_c('handwired/pytest/has_template', 'LAYOUT', [['KC_A']]) 25 keymap_json = {
26 'keyboard': 'handwired/pytest/has_template',
27 'layout': 'LAYOUT',
28 'layers': [['KC_A']],
29 'macros': None,
30 }
31 templ = qmk.keymap.generate_c(keymap_json)
26 assert templ == '#include QMK_KEYBOARD_H\nconst uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {\t[0] = LAYOUT(KC_A)};\n' 32 assert templ == '#include QMK_KEYBOARD_H\nconst uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {\t[0] = LAYOUT(KC_A)};\n'
27 33
28 34
diff --git a/quantum/quantum_keycodes.h b/quantum/quantum_keycodes.h
index cde97074d..d013a6a16 100644
--- a/quantum/quantum_keycodes.h
+++ b/quantum/quantum_keycodes.h
@@ -558,6 +558,40 @@ enum quantum_keycodes {
558 PROGRAMMABLE_BUTTON_31, 558 PROGRAMMABLE_BUTTON_31,
559 PROGRAMMABLE_BUTTON_32, 559 PROGRAMMABLE_BUTTON_32,
560 560
561 // Dedicated macro keys for Configurator and VIA
562 MACRO_0,
563 MACRO_1,
564 MACRO_2,
565 MACRO_3,
566 MACRO_4,
567 MACRO_5,
568 MACRO_6,
569 MACRO_7,
570 MACRO_8,
571 MACRO_9,
572 MACRO_10,
573 MACRO_11,
574 MACRO_12,
575 MACRO_13,
576 MACRO_14,
577 MACRO_15,
578 MACRO_16,
579 MACRO_17,
580 MACRO_18,
581 MACRO_19,
582 MACRO_20,
583 MACRO_21,
584 MACRO_22,
585 MACRO_23,
586 MACRO_24,
587 MACRO_25,
588 MACRO_26,
589 MACRO_27,
590 MACRO_28,
591 MACRO_29,
592 MACRO_30,
593 MACRO_31,
594
561 // Start of custom keycode range for keyboards and keymaps - always leave at the end 595 // Start of custom keycode range for keyboards and keymaps - always leave at the end
562 SAFE_RANGE 596 SAFE_RANGE
563}; 597};
diff --git a/requirements-dev.txt b/requirements-dev.txt
index 1db3b6d73..721370733 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -5,4 +5,5 @@
5nose2 5nose2
6flake8 6flake8
7pep8-naming 7pep8-naming
8pyflakes
8yapf 9yapf