Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Macros in JSON keymaps #14374

Merged
merged 26 commits into from
Nov 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
658fd4c
macros in json keymaps
skullydazed Sep 10, 2021
6834c44
add advanced macro support to json
skullydazed Sep 12, 2021
87d0d3d
add a note about escaping macro strings
skullydazed Sep 12, 2021
60118de
add simple examples
skullydazed Sep 12, 2021
f7e55de
format json
skullydazed Sep 12, 2021
3ef20a9
add support for language specific keymap extras
skullydazed Sep 13, 2021
59136fe
switch to dictionaries instead of inline text for macros
skullydazed Sep 13, 2021
9d8b7da
use SS_TAP on the innermost tap keycode
skullydazed Sep 13, 2021
65b4cbd
add the new macro format to the schema
skullydazed Sep 13, 2021
e3daef1
document the macro limit
skullydazed Sep 13, 2021
acb3325
add the json keyword for syntax highlighting
skullydazed Sep 13, 2021
2c31f96
fix format that vscode screwed up
skullydazed Sep 13, 2021
aa79730
Update feature_macros.md
skullydazed Sep 13, 2021
5b9f297
add tests for macros
skullydazed Sep 14, 2021
840cafa
change ding to beep
skullydazed Sep 14, 2021
3312a08
add json support for SENDSTRING_BELL
skullydazed Sep 14, 2021
cc7fd4a
update doc based on feedback from sigprof
skullydazed Sep 15, 2021
49f64f9
document host_layout
skullydazed Sep 15, 2021
c677ee4
remove unused var
skullydazed Sep 15, 2021
3554472
improve carriage return handling
skullydazed Oct 3, 2021
368bd6e
support tab characters as well
skullydazed Oct 3, 2021
cd31826
Update docs/feature_macros.md
skullydazed Nov 20, 2021
1949e45
escape backslash characters
skullydazed Nov 20, 2021
61d9677
format
skullydazed Nov 20, 2021
d6156ac
flake8
skullydazed Nov 20, 2021
d9c1aea
Update quantum/quantum_keycodes.h
tzarc Nov 21, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions data/mappings/info_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
"QMK_KEYS_PER_SCAN": {"info_key": "qmk.keys_per_scan", "value_type": "int"},
"QMK_LED": {"info_key": "qmk_lufa_bootloader.led"},
"QMK_SPEAKER": {"info_key": "qmk_lufa_bootloader.speaker"},
"SENDSTRING_BELL": {"info_key": "audio.macro_beep", "value_type": "bool"},
"SPLIT_MODS_ENABLE": {"info_key": "split.transport.sync_modifiers", "value_type": "bool"},
"SPLIT_TRANSPORT_MIRROR": {"info_key": "split.transport.sync_matrix_state", "value_type": "bool"},
"SPLIT_USB_DETECT": {"info_key": "split.usb_detect.enabled", "value_type": "bool"},
Expand Down
1 change: 1 addition & 0 deletions data/schemas/keyboard.jsonschema
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"type": "object",
"additionalProperties": false,
"properties": {
"macro_beep": {"type": "boolean"},
"pins": {"$ref": "qmk.definitions.v1#/mcu_pin_array"},
"voices": {"type": "boolean"}
}
Expand Down
35 changes: 34 additions & 1 deletion data/schemas/keymap.jsonschema
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"type": "object",
"properties": {
"author": {"type": "string"},
"host_language": {"$ref": "qmk.definitions.v1#/text_identifier"},
"keyboard": {"$ref": "qmk.definitions.v1#/text_identifier"},
"keymap": {"$ref": "qmk.definitions.v1#/text_identifier"},
"layout": {"$ref": "qmk.definitions.v1#/layout_macro"},
Expand All @@ -15,10 +16,42 @@
"items": {"type": "string"}
}
},
"macros": {
"type": "array",
"items": {
"type": "array",
"items": {
"oneOf": [
{
"type": "string"
},
{
"type": "object",
"additionalProperties": false,
"properties": {
"action": {
"type": "string",
"enum": ['beep', 'delay', 'down', 'tap', 'up']
},
"keycodes": {
"type": "array",
"items": {
"$ref": "qmk.definitions.v1#/text_identifier"
}
},
"duration": {
"$ref": "qmk.definitions.v1#/unsigned_int"
}
}
}
]
}
}
},
"config": {"$ref": "qmk.keyboard.v1"},
"notes": {
"type": "string",
"description": "asdf"
}
}
}
}
134 changes: 117 additions & 17 deletions docs/feature_macros.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,107 @@ Macros allow you to send multiple keystrokes when pressing just one key. QMK has

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

## `SEND_STRING()` & `process_record_user`
## Using Macros In JSON Keymaps

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:

```json
{
"keyboard": "handwired/my_macropad",
"keymap": "my_keymap",
"macros": [
[
{"action":"down", "keycodes": ["LSFT"]},
"hello world1",
{"action": "up","keycodes": ["LSFT"]}
],
[
{"action":"tap", "keycodes": ["LCTL", "LALT", "DEL"]}
],
[
"ding!",
{"action":"beep"}
],
[
{"action":"tap", "keycodes": ["F1"]},
{"action":"delay", "duration": "1000"},
{"action":"tap", "keycodes": ["PGDN"]}
]
],
"layout": "LAYOUT_all",
"layers": [
["MACRO_0", "MACRO_1", "MACRO_2", "MACRO_3"]
]
}
```

### Selecting Your Host Keyboard Layout

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:

```json
{
"keyboard": "handwired/my_macropad",
"keymap": "my_keymap",
"host_layout": "dvorak",
"macros": [
["Hello, World!"]
],
"layout": "LAYOUT_all",
"layers": [
["MACRO_0"]
]
}
```

The current list of available languages is:

| belgian | bepo | br_abnt2 | canadian_multilingual |
|:-------:|:----:|:--------:|:---------------------:|
| **colemak** | **croatian** | **czech** | **danish** |
| **dvorak_fr** | **dvorak** | **dvp** | **estonian** |
| **finnish** | **fr_ch** | **french_afnor** | **french** |
| **french_osx** | **german_ch** | **german** | **german_osx** |
| **hungarian** | **icelandic** | **italian** | **italian_osx_ansi** |
| **italian_osx_iso** | **jis** | **latvian** | **lithuanian_azerty** |
| **lithuanian_qwerty** | **norman** | **norwegian** | **portuguese** |
| **portuguese_osx_iso** | **romanian** | **serbian_latin** | **slovak** |
| **slovenian** | **spanish_dvorak** | **spanish** | **swedish** |
| **turkish_f** | **turkish_q** | **uk** | **us_international** |
| **workman** | **workman_zxcvm** |

### Macro Basics

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.

#### Object Format

All objects have one required key: `action`. This tells QMK what the object does. There are currently 5 actions: beep, delay, down, tap, up

Only basic keycodes (prefixed by `KC_`) are supported. Do not include the `KC_` prefix when listing keycodes.

* `beep`
* Play a bell if the keyboard has [audio enabled](feature_audio.md).
* Example: `{"action": "beep"}`
* `delay`
* Pause macro playback. Duration is specified in milliseconds (ms).
* Example: `{"action": "delay", "duration": 500}`
* `down`
* Send a key down event for one or more keycodes.
* Example, single key: `{"action":"down", "keycodes": ["LSFT"]}`
* Example, multiple keys: `{"action":"down", "keycodes": ["CTRL", "LSFT"]}`
* `tap`
* Type a chord, which sends a down event for each key followed by an up event for each key.
* Example, single key: `{"action":"tap", "keycodes": ["F13"]}`
* Example, multiple keys: `{"action":"tap", "keycodes": ["CTRL", "LALT", "DEL"]}`
* `up`
* Send a key up event for one or more keycodes.
* Example, single key: `{"action":"up", "keycodes": ["LSFT"]}`
* Example, multiple keys: `{"action":"up", "keycodes": ["CTRL", "LSFT"]}`

## Using Macros in C Keymaps

### `SEND_STRING()` & `process_record_user`

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`).

Expand Down Expand Up @@ -91,7 +191,7 @@ const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
};
```

### Advanced Macros
#### Advanced Macros

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.

Expand Down Expand Up @@ -131,7 +231,7 @@ void post_process_record_user(uint16_t keycode, keyrecord_t *record) {
```
### TAP, DOWN and UP
#### TAP, DOWN and UP
You may want to use keys in your macros that you can't write down, such as `Ctrl` or `Home`.
You can send arbitrary keycodes by wrapping them in:
Expand Down Expand Up @@ -178,15 +278,15 @@ They can be used like this:
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.
### Alternative Keymaps
#### Alternative Keymaps
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:
```c
#include "sendstring_colemak.h"
```

### Strings in Memory
#### Strings in Memory

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:

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


## Advanced Macro Functions
### Advanced Macro Functions

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.

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

### `record->event.pressed`
#### `record->event.pressed`

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

Expand All @@ -223,47 +323,47 @@ This is a boolean value that can be tested to see if the switch is being pressed
}
```
### `register_code(<kc>);`
#### `register_code(<kc>);`
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`.
### `unregister_code(<kc>);`
#### `unregister_code(<kc>);`
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.
### `tap_code(<kc>);`
#### `tap_code(<kc>);`
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).
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.
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.
### `tap_code_delay(<kc>, <delay>);`
#### `tap_code_delay(<kc>, <delay>);`
Like `tap_code(<kc>)`, but with a `delay` parameter for specifying arbitrary intervals before sending the unregister event.
### `register_code16(<kc>);`, `unregister_code16(<kc>);` and `tap_code16(<kc>);`
#### `register_code16(<kc>);`, `unregister_code16(<kc>);` and `tap_code16(<kc>);`
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).
Eg, you could use `register_code16(S(KC_5));` instead of registering the mod, then registering the keycode.
### `clear_keyboard();`
#### `clear_keyboard();`
This will clear all mods and keys currently pressed.
### `clear_mods();`
#### `clear_mods();`
This will clear all mods currently pressed.
### `clear_keyboard_but_mods();`
#### `clear_keyboard_but_mods();`
This will clear all keys besides the mods currently pressed.
## Advanced Example:
### Advanced Example:
### Super ALT↯TAB
#### Super ALT↯TAB
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.
Expand Down
Empty file.
10 changes: 10 additions & 0 deletions keyboards/handwired/pytest/macro/info.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"maintainer": "qmk",
"layouts": {
"LAYOUT_custom": {
"layout": [
{ "label": "KC_Q", "matrix": [0, 0], "w": 1, "x": 0, "y": 0 }
]
}
}
}
15 changes: 15 additions & 0 deletions keyboards/handwired/pytest/macro/keymaps/default/keymap.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"keyboard": "handwired/pytest/basic",
"keymap": "default_json",
"layout": "LAYOUT_ortho_1x1",
"layers": [["MACRO_0"]],
"macros": [
[
"Hello, World!",
{"action":"tap", "keycodes":["ENTER"]}
]
],
"author": "qmk",
"notes": "This file is a keymap.json file for handwired/pytest/basic",
"version": 1
}
Empty file.
1 change: 1 addition & 0 deletions keyboards/handwired/pytest/macro/rules.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
MCU = atmega32u4
2 changes: 1 addition & 1 deletion lib/python/qmk/cli/json2c.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def json2c(cli):
cli.args.output = None

# Generate the keymap
keymap_c = qmk.keymap.generate_c(user_keymap['keyboard'], user_keymap['layout'], user_keymap['layers'])
keymap_c = qmk.keymap.generate_c(user_keymap)

if cli.args.output:
cli.args.output.parent.mkdir(parents=True, exist_ok=True)
Expand Down
2 changes: 1 addition & 1 deletion lib/python/qmk/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ def compile_configurator_json(user_keymap, bootloader=None, parallel=1, **env_va
target = f'{keyboard_filesafe}_{user_keymap["keymap"]}'
keyboard_output = Path(f'{KEYBOARD_OUTPUT_PREFIX}{keyboard_filesafe}')
keymap_output = Path(f'{keyboard_output}_{user_keymap["keymap"]}')
c_text = qmk.keymap.generate_c(user_keymap['keyboard'], user_keymap['layout'], user_keymap['layers'])
c_text = qmk.keymap.generate_c(user_keymap)
keymap_dir = keymap_output / 'src'
keymap_c = keymap_dir / 'keymap.c'

Expand Down
Loading