Skip to content

Commit 08ce014

Browse files
skullydazedtzarc
andauthored
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>
1 parent 8181b15 commit 08ce014

File tree

16 files changed

+319
-33
lines changed

16 files changed

+319
-33
lines changed

data/mappings/info_config.json

+1
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
"QMK_KEYS_PER_SCAN": {"info_key": "qmk.keys_per_scan", "value_type": "int"},
7777
"QMK_LED": {"info_key": "qmk_lufa_bootloader.led"},
7878
"QMK_SPEAKER": {"info_key": "qmk_lufa_bootloader.speaker"},
79+
"SENDSTRING_BELL": {"info_key": "audio.macro_beep", "value_type": "bool"},
7980
"SPLIT_MODS_ENABLE": {"info_key": "split.transport.sync_modifiers", "value_type": "bool"},
8081
"SPLIT_TRANSPORT_MIRROR": {"info_key": "split.transport.sync_matrix_state", "value_type": "bool"},
8182
"SPLIT_USB_DETECT": {"info_key": "split.usb_detect.enabled", "value_type": "bool"},

data/schemas/keyboard.jsonschema

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"type": "object",
2020
"additionalProperties": false,
2121
"properties": {
22+
"macro_beep": {"type": "boolean"},
2223
"pins": {"$ref": "qmk.definitions.v1#/mcu_pin_array"},
2324
"voices": {"type": "boolean"}
2425
}

data/schemas/keymap.jsonschema

+34-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"type": "object",
66
"properties": {
77
"author": {"type": "string"},
8+
"host_language": {"$ref": "qmk.definitions.v1#/text_identifier"},
89
"keyboard": {"$ref": "qmk.definitions.v1#/text_identifier"},
910
"keymap": {"$ref": "qmk.definitions.v1#/text_identifier"},
1011
"layout": {"$ref": "qmk.definitions.v1#/layout_macro"},
@@ -15,10 +16,42 @@
1516
"items": {"type": "string"}
1617
}
1718
},
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+
},
1851
"config": {"$ref": "qmk.keyboard.v1"},
1952
"notes": {
2053
"type": "string",
2154
"description": "asdf"
2255
}
2356
}
24-
}
57+
}

docs/feature_macros.md

+117-17
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,107 @@ Macros allow you to send multiple keystrokes when pressing just one key. QMK has
44

55
!> **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.
66

7-
## `SEND_STRING()` & `process_record_user`
7+
## Using Macros In JSON Keymaps
8+
9+
You 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+
43+
If 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+
60+
The 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+
78+
Each 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+
82+
All objects have one required key: `action`. This tells QMK what the object does. There are currently 5 actions: beep, delay, down, tap, up
83+
84+
Only 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`
8108

9109
Sometimes 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`).
10110

@@ -91,7 +191,7 @@ const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
91191
};
92192
```
93193

94-
### Advanced Macros
194+
#### Advanced Macros
95195

96196
In 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.
97197

@@ -131,7 +231,7 @@ void post_process_record_user(uint16_t keycode, keyrecord_t *record) {
131231
```
132232
133233
134-
### TAP, DOWN and UP
234+
#### TAP, DOWN and UP
135235
136236
You may want to use keys in your macros that you can't write down, such as `Ctrl` or `Home`.
137237
You can send arbitrary keycodes by wrapping them in:
@@ -178,15 +278,15 @@ They can be used like this:
178278
179279
Which 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.
180280
181-
### Alternative Keymaps
281+
#### Alternative Keymaps
182282
183283
By 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:
184284
185285
```c
186286
#include "sendstring_colemak.h"
187287
```
188288

189-
### Strings in Memory
289+
#### Strings in Memory
190290

191291
If 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:
192292

@@ -205,13 +305,13 @@ SEND_STRING(".."SS_TAP(X_END));
205305
```
206306

207307

208-
## Advanced Macro Functions
308+
### Advanced Macro Functions
209309

210310
There 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.
211311

212312
?> 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.
213313

214-
### `record->event.pressed`
314+
#### `record->event.pressed`
215315

216316
This is a boolean value that can be tested to see if the switch is being pressed or released. An example of this is
217317

@@ -223,47 +323,47 @@ This is a boolean value that can be tested to see if the switch is being pressed
223323
}
224324
```
225325
226-
### `register_code(<kc>);`
326+
#### `register_code(<kc>);`
227327
228328
This 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`.
229329
230-
### `unregister_code(<kc>);`
330+
#### `unregister_code(<kc>);`
231331
232332
Parallel 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.
233333
234-
### `tap_code(<kc>);`
334+
#### `tap_code(<kc>);`
235335
236336
Sends `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).
237337
238338
If `TAP_CODE_DELAY` is defined (default 0), this function waits that many milliseconds before calling `unregister_code(<kc>)`. This can be useful when you are having issues with taps (un)registering.
239339
240340
If 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.
241341
242-
### `tap_code_delay(<kc>, <delay>);`
342+
#### `tap_code_delay(<kc>, <delay>);`
243343
244344
Like `tap_code(<kc>)`, but with a `delay` parameter for specifying arbitrary intervals before sending the unregister event.
245345
246-
### `register_code16(<kc>);`, `unregister_code16(<kc>);` and `tap_code16(<kc>);`
346+
#### `register_code16(<kc>);`, `unregister_code16(<kc>);` and `tap_code16(<kc>);`
247347
248348
These functions work similar to their regular counterparts, but allow you to use modded keycodes (with Shift, Alt, Control, and/or GUI applied to them).
249349
250350
Eg, you could use `register_code16(S(KC_5));` instead of registering the mod, then registering the keycode.
251351
252-
### `clear_keyboard();`
352+
#### `clear_keyboard();`
253353
254354
This will clear all mods and keys currently pressed.
255355
256-
### `clear_mods();`
356+
#### `clear_mods();`
257357
258358
This will clear all mods currently pressed.
259359
260-
### `clear_keyboard_but_mods();`
360+
#### `clear_keyboard_but_mods();`
261361
262362
This will clear all keys besides the mods currently pressed.
263363
264-
## Advanced Example:
364+
### Advanced Example:
265365
266-
### Super ALT↯TAB
366+
#### Super ALT↯TAB
267367
268368
This 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.
269369

keyboards/handwired/pytest/macro/.noci

Whitespace-only changes.
+10
Original file line numberDiff line numberDiff line change
@@ -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+
}
Original file line numberDiff line numberDiff line change
@@ -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+
}

keyboards/handwired/pytest/macro/readme.md

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
MCU = atmega32u4

lib/python/qmk/cli/json2c.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ def json2c(cli):
3333
cli.args.output = None
3434

3535
# 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)
3737

3838
if cli.args.output:
3939
cli.args.output.parent.mkdir(parents=True, exist_ok=True)

lib/python/qmk/commands.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ def compile_configurator_json(user_keymap, bootloader=None, parallel=1, **env_va
190190
target = f'{keyboard_filesafe}_{user_keymap["keymap"]}'
191191
keyboard_output = Path(f'{KEYBOARD_OUTPUT_PREFIX}{keyboard_filesafe}')
192192
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)
194194
keymap_dir = keymap_output / 'src'
195195
keymap_c = keymap_dir / 'keymap.c'
196196

0 commit comments

Comments
 (0)