Skip to content

Commit

Permalink
Macros in JSON keymaps (#14374)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>

* escape backslash characters

* format

* flake8

* Update quantum/quantum_keycodes.h

Co-authored-by: Nick Brassel <[email protected]>
  • Loading branch information
skullydazed and tzarc authored Nov 22, 2021
1 parent 8181b15 commit 08ce014
Show file tree
Hide file tree
Showing 16 changed files with 319 additions and 33 deletions.
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

0 comments on commit 08ce014

Please sign in to comment.