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>
This commit is contained in:
parent
8181b155db
commit
08ce0142ba
16 changed files with 319 additions and 33 deletions
|
@ -76,6 +76,7 @@
|
||||||
"QMK_KEYS_PER_SCAN": {"info_key": "qmk.keys_per_scan", "value_type": "int"},
|
"QMK_KEYS_PER_SCAN": {"info_key": "qmk.keys_per_scan", "value_type": "int"},
|
||||||
"QMK_LED": {"info_key": "qmk_lufa_bootloader.led"},
|
"QMK_LED": {"info_key": "qmk_lufa_bootloader.led"},
|
||||||
"QMK_SPEAKER": {"info_key": "qmk_lufa_bootloader.speaker"},
|
"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_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_TRANSPORT_MIRROR": {"info_key": "split.transport.sync_matrix_state", "value_type": "bool"},
|
||||||
"SPLIT_USB_DETECT": {"info_key": "split.usb_detect.enabled", "value_type": "bool"},
|
"SPLIT_USB_DETECT": {"info_key": "split.usb_detect.enabled", "value_type": "bool"},
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"macro_beep": {"type": "boolean"},
|
||||||
"pins": {"$ref": "qmk.definitions.v1#/mcu_pin_array"},
|
"pins": {"$ref": "qmk.definitions.v1#/mcu_pin_array"},
|
||||||
"voices": {"type": "boolean"}
|
"voices": {"type": "boolean"}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"author": {"type": "string"},
|
"author": {"type": "string"},
|
||||||
|
"host_language": {"$ref": "qmk.definitions.v1#/text_identifier"},
|
||||||
"keyboard": {"$ref": "qmk.definitions.v1#/text_identifier"},
|
"keyboard": {"$ref": "qmk.definitions.v1#/text_identifier"},
|
||||||
"keymap": {"$ref": "qmk.definitions.v1#/text_identifier"},
|
"keymap": {"$ref": "qmk.definitions.v1#/text_identifier"},
|
||||||
"layout": {"$ref": "qmk.definitions.v1#/layout_macro"},
|
"layout": {"$ref": "qmk.definitions.v1#/layout_macro"},
|
||||||
|
@ -15,6 +16,38 @@
|
||||||
"items": {"type": "string"}
|
"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"},
|
"config": {"$ref": "qmk.keyboard.v1"},
|
||||||
"notes": {
|
"notes": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|
|
@ -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.
|
!> **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`).
|
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`).
|
||||||
|
|
||||||
|
@ -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.
|
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.
|
||||||
|
|
||||||
|
@ -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 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:
|
You can send arbitrary keycodes by wrapping them in:
|
||||||
|
@ -178,7 +278,7 @@ 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.
|
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:
|
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:
|
||||||
|
|
||||||
|
@ -186,7 +286,7 @@ By default, it assumes a US keymap with a QWERTY layout; if you want to change t
|
||||||
#include "sendstring_colemak.h"
|
#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:
|
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:
|
||||||
|
|
||||||
|
@ -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.
|
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.
|
?> 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
|
This is a boolean value that can be tested to see if the switch is being pressed or released. An example of this is
|
||||||
|
|
||||||
|
@ -223,15 +323,15 @@ 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`.
|
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.
|
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).
|
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).
|
||||||
|
|
||||||
|
@ -239,31 +339,31 @@ If `TAP_CODE_DELAY` is defined (default 0), this function waits that many millis
|
||||||
|
|
||||||
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.
|
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.
|
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).
|
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.
|
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.
|
This will clear all mods and keys currently pressed.
|
||||||
|
|
||||||
### `clear_mods();`
|
#### `clear_mods();`
|
||||||
|
|
||||||
This will clear all mods currently pressed.
|
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.
|
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.
|
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.
|
||||||
|
|
||||||
|
|
0
keyboards/handwired/pytest/macro/.noci
Normal file
0
keyboards/handwired/pytest/macro/.noci
Normal file
10
keyboards/handwired/pytest/macro/info.json
Normal file
10
keyboards/handwired/pytest/macro/info.json
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"maintainer": "qmk",
|
||||||
|
"layouts": {
|
||||||
|
"LAYOUT_custom": {
|
||||||
|
"layout": [
|
||||||
|
{ "label": "KC_Q", "matrix": [0, 0], "w": 1, "x": 0, "y": 0 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
keyboards/handwired/pytest/macro/keymaps/default/keymap.json
Normal file
15
keyboards/handwired/pytest/macro/keymaps/default/keymap.json
Normal file
|
@ -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
|
||||||
|
}
|
0
keyboards/handwired/pytest/macro/readme.md
Normal file
0
keyboards/handwired/pytest/macro/readme.md
Normal file
1
keyboards/handwired/pytest/macro/rules.mk
Normal file
1
keyboards/handwired/pytest/macro/rules.mk
Normal file
|
@ -0,0 +1 @@
|
||||||
|
MCU = atmega32u4
|
|
@ -33,7 +33,7 @@ def json2c(cli):
|
||||||
cli.args.output = None
|
cli.args.output = None
|
||||||
|
|
||||||
# Generate the keymap
|
# 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:
|
if cli.args.output:
|
||||||
cli.args.output.parent.mkdir(parents=True, exist_ok=True)
|
cli.args.output.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
|
@ -190,7 +190,7 @@ def compile_configurator_json(user_keymap, bootloader=None, parallel=1, **env_va
|
||||||
target = f'{keyboard_filesafe}_{user_keymap["keymap"]}'
|
target = f'{keyboard_filesafe}_{user_keymap["keymap"]}'
|
||||||
keyboard_output = Path(f'{KEYBOARD_OUTPUT_PREFIX}{keyboard_filesafe}')
|
keyboard_output = Path(f'{KEYBOARD_OUTPUT_PREFIX}{keyboard_filesafe}')
|
||||||
keymap_output = Path(f'{keyboard_output}_{user_keymap["keymap"]}')
|
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_dir = keymap_output / 'src'
|
||||||
keymap_c = keymap_dir / 'keymap.c'
|
keymap_c = keymap_dir / 'keymap.c'
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ from qmk.errors import CppError
|
||||||
|
|
||||||
# The `keymap.c` template to use when a keyboard doesn't have its own
|
# The `keymap.c` template to use when a keyboard doesn't have its own
|
||||||
DEFAULT_KEYMAP_C = """#include QMK_KEYBOARD_H
|
DEFAULT_KEYMAP_C = """#include QMK_KEYBOARD_H
|
||||||
|
__INCLUDES__
|
||||||
|
|
||||||
/* THIS FILE WAS GENERATED!
|
/* THIS FILE WAS GENERATED!
|
||||||
*
|
*
|
||||||
|
@ -27,6 +28,7 @@ DEFAULT_KEYMAP_C = """#include QMK_KEYBOARD_H
|
||||||
const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
|
const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
|
||||||
__KEYMAP_GOES_HERE__
|
__KEYMAP_GOES_HERE__
|
||||||
};
|
};
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@ -180,10 +182,11 @@ def generate_json(keymap, keyboard, layout, layers):
|
||||||
return new_keymap
|
return new_keymap
|
||||||
|
|
||||||
|
|
||||||
def generate_c(keyboard, layout, layers):
|
def generate_c(keymap_json):
|
||||||
"""Returns a `keymap.c` or `keymap.json` for the specified keyboard, layout, and layers.
|
"""Returns a `keymap.c`.
|
||||||
|
|
||||||
|
`keymap_json` is a dictionary with the following keys:
|
||||||
|
|
||||||
Args:
|
|
||||||
keyboard
|
keyboard
|
||||||
The name of the keyboard
|
The name of the keyboard
|
||||||
|
|
||||||
|
@ -192,19 +195,89 @@ def generate_c(keyboard, layout, layers):
|
||||||
|
|
||||||
layers
|
layers
|
||||||
An array of arrays describing the keymap. Each item in the inner array should be a string that is a valid QMK keycode.
|
An array of arrays describing the keymap. Each item in the inner array should be a string that is a valid QMK keycode.
|
||||||
|
|
||||||
|
macros
|
||||||
|
A sequence of strings containing macros to implement for this keyboard.
|
||||||
"""
|
"""
|
||||||
new_keymap = template_c(keyboard)
|
new_keymap = template_c(keymap_json['keyboard'])
|
||||||
layer_txt = []
|
layer_txt = []
|
||||||
for layer_num, layer in enumerate(layers):
|
|
||||||
|
for layer_num, layer in enumerate(keymap_json['layers']):
|
||||||
if layer_num != 0:
|
if layer_num != 0:
|
||||||
layer_txt[-1] = layer_txt[-1] + ','
|
layer_txt[-1] = layer_txt[-1] + ','
|
||||||
layer = map(_strip_any, layer)
|
layer = map(_strip_any, layer)
|
||||||
layer_keys = ', '.join(layer)
|
layer_keys = ', '.join(layer)
|
||||||
layer_txt.append('\t[%s] = %s(%s)' % (layer_num, layout, layer_keys))
|
layer_txt.append('\t[%s] = %s(%s)' % (layer_num, keymap_json['layout'], layer_keys))
|
||||||
|
|
||||||
keymap = '\n'.join(layer_txt)
|
keymap = '\n'.join(layer_txt)
|
||||||
new_keymap = new_keymap.replace('__KEYMAP_GOES_HERE__', keymap)
|
new_keymap = new_keymap.replace('__KEYMAP_GOES_HERE__', keymap)
|
||||||
|
|
||||||
|
if keymap_json.get('macros'):
|
||||||
|
macro_txt = [
|
||||||
|
'bool process_record_user(uint16_t keycode, keyrecord_t *record) {',
|
||||||
|
' if (record->event.pressed) {',
|
||||||
|
' switch (keycode) {',
|
||||||
|
]
|
||||||
|
|
||||||
|
for i, macro_array in enumerate(keymap_json['macros']):
|
||||||
|
macro = []
|
||||||
|
|
||||||
|
for macro_fragment in macro_array:
|
||||||
|
if isinstance(macro_fragment, str):
|
||||||
|
macro_fragment = macro_fragment.replace('\\', '\\\\')
|
||||||
|
macro_fragment = macro_fragment.replace('\r\n', r'\n')
|
||||||
|
macro_fragment = macro_fragment.replace('\n', r'\n')
|
||||||
|
macro_fragment = macro_fragment.replace('\r', r'\n')
|
||||||
|
macro_fragment = macro_fragment.replace('\t', r'\t')
|
||||||
|
macro_fragment = macro_fragment.replace('"', r'\"')
|
||||||
|
|
||||||
|
macro.append(f'"{macro_fragment}"')
|
||||||
|
|
||||||
|
elif isinstance(macro_fragment, dict):
|
||||||
|
newstring = []
|
||||||
|
|
||||||
|
if macro_fragment['action'] == 'delay':
|
||||||
|
newstring.append(f"SS_DELAY({macro_fragment['duration']})")
|
||||||
|
|
||||||
|
elif macro_fragment['action'] == 'beep':
|
||||||
|
newstring.append(r'"\a"')
|
||||||
|
|
||||||
|
elif macro_fragment['action'] == 'tap' and len(macro_fragment['keycodes']) > 1:
|
||||||
|
last_keycode = macro_fragment['keycodes'].pop()
|
||||||
|
|
||||||
|
for keycode in macro_fragment['keycodes']:
|
||||||
|
newstring.append(f'SS_DOWN(X_{keycode})')
|
||||||
|
|
||||||
|
newstring.append(f'SS_TAP(X_{last_keycode})')
|
||||||
|
|
||||||
|
for keycode in reversed(macro_fragment['keycodes']):
|
||||||
|
newstring.append(f'SS_UP(X_{keycode})')
|
||||||
|
|
||||||
|
else:
|
||||||
|
for keycode in macro_fragment['keycodes']:
|
||||||
|
newstring.append(f"SS_{macro_fragment['action'].upper()}(X_{keycode})")
|
||||||
|
|
||||||
|
macro.append(''.join(newstring))
|
||||||
|
|
||||||
|
new_macro = "".join(macro)
|
||||||
|
new_macro = new_macro.replace('""', '')
|
||||||
|
macro_txt.append(f' case MACRO_{i}:')
|
||||||
|
macro_txt.append(f' SEND_STRING({new_macro});')
|
||||||
|
macro_txt.append(' return false;')
|
||||||
|
|
||||||
|
macro_txt.append(' }')
|
||||||
|
macro_txt.append(' }')
|
||||||
|
macro_txt.append('\n return true;')
|
||||||
|
macro_txt.append('};')
|
||||||
|
macro_txt.append('')
|
||||||
|
|
||||||
|
new_keymap = '\n'.join((new_keymap, *macro_txt))
|
||||||
|
|
||||||
|
if keymap_json.get('host_language'):
|
||||||
|
new_keymap = new_keymap.replace('__INCLUDES__', f'#include "keymap_{keymap_json["host_language"]}.h"\n#include "sendstring_{keymap_json["host_language"]}.h"\n')
|
||||||
|
else:
|
||||||
|
new_keymap = new_keymap.replace('__INCLUDES__', '')
|
||||||
|
|
||||||
return new_keymap
|
return new_keymap
|
||||||
|
|
||||||
|
|
||||||
|
@ -217,7 +290,7 @@ def write_file(keymap_filename, keymap_content):
|
||||||
return keymap_filename
|
return keymap_filename
|
||||||
|
|
||||||
|
|
||||||
def write_json(keyboard, keymap, layout, layers):
|
def write_json(keyboard, keymap, layout, layers, macros=None):
|
||||||
"""Generate the `keymap.json` and write it to disk.
|
"""Generate the `keymap.json` and write it to disk.
|
||||||
|
|
||||||
Returns the filename written to.
|
Returns the filename written to.
|
||||||
|
@ -235,19 +308,19 @@ def write_json(keyboard, keymap, layout, layers):
|
||||||
layers
|
layers
|
||||||
An array of arrays describing the keymap. Each item in the inner array should be a string that is a valid QMK keycode.
|
An array of arrays describing the keymap. Each item in the inner array should be a string that is a valid QMK keycode.
|
||||||
"""
|
"""
|
||||||
keymap_json = generate_json(keyboard, keymap, layout, layers)
|
keymap_json = generate_json(keyboard, keymap, layout, layers, macros=None)
|
||||||
keymap_content = json.dumps(keymap_json)
|
keymap_content = json.dumps(keymap_json)
|
||||||
keymap_file = qmk.path.keymap(keyboard) / keymap / 'keymap.json'
|
keymap_file = qmk.path.keymap(keyboard) / keymap / 'keymap.json'
|
||||||
|
|
||||||
return write_file(keymap_file, keymap_content)
|
return write_file(keymap_file, keymap_content)
|
||||||
|
|
||||||
|
|
||||||
def write(keyboard, keymap, layout, layers):
|
def write(keymap_json):
|
||||||
"""Generate the `keymap.c` and write it to disk.
|
"""Generate the `keymap.c` and write it to disk.
|
||||||
|
|
||||||
Returns the filename written to.
|
Returns the filename written to.
|
||||||
|
|
||||||
Args:
|
`keymap_json` should be a dict with the following keys:
|
||||||
keyboard
|
keyboard
|
||||||
The name of the keyboard
|
The name of the keyboard
|
||||||
|
|
||||||
|
@ -259,9 +332,12 @@ def write(keyboard, keymap, layout, layers):
|
||||||
|
|
||||||
layers
|
layers
|
||||||
An array of arrays describing the keymap. Each item in the inner array should be a string that is a valid QMK keycode.
|
An array of arrays describing the keymap. Each item in the inner array should be a string that is a valid QMK keycode.
|
||||||
|
|
||||||
|
macros
|
||||||
|
A list of macros for this keymap.
|
||||||
"""
|
"""
|
||||||
keymap_content = generate_c(keyboard, layout, layers)
|
keymap_content = generate_c(keymap_json)
|
||||||
keymap_file = qmk.path.keymap(keyboard) / keymap / 'keymap.c'
|
keymap_file = qmk.path.keymap(keymap_json['keyboard']) / keymap_json['keymap'] / 'keymap.c'
|
||||||
|
|
||||||
return write_file(keymap_file, keymap_content)
|
return write_file(keymap_file, keymap_content)
|
||||||
|
|
||||||
|
|
|
@ -142,6 +142,14 @@ def test_json2c():
|
||||||
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'
|
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'
|
||||||
|
|
||||||
|
|
||||||
|
def test_json2c_macros():
|
||||||
|
result = check_subcommand("json2c", 'keyboards/handwired/pytest/macro/keymaps/default/keymap.json')
|
||||||
|
check_returncode(result)
|
||||||
|
assert 'LAYOUT_ortho_1x1(MACRO_0)' in result.stdout
|
||||||
|
assert 'case MACRO_0:' in result.stdout
|
||||||
|
assert 'SEND_STRING("Hello, World!"SS_TAP(X_ENTER));' in result.stdout
|
||||||
|
|
||||||
|
|
||||||
def test_json2c_stdin():
|
def test_json2c_stdin():
|
||||||
result = check_subcommand_stdin('keyboards/handwired/pytest/has_template/keymaps/default_json/keymap.json', 'json2c', '-')
|
result = check_subcommand_stdin('keyboards/handwired/pytest/has_template/keymaps/default_json/keymap.json', 'json2c', '-')
|
||||||
check_returncode(result)
|
check_returncode(result)
|
||||||
|
|
|
@ -22,7 +22,13 @@ def test_template_json_pytest_has_template():
|
||||||
|
|
||||||
|
|
||||||
def test_generate_c_pytest_has_template():
|
def test_generate_c_pytest_has_template():
|
||||||
templ = qmk.keymap.generate_c('handwired/pytest/has_template', 'LAYOUT', [['KC_A']])
|
keymap_json = {
|
||||||
|
'keyboard': 'handwired/pytest/has_template',
|
||||||
|
'layout': 'LAYOUT',
|
||||||
|
'layers': [['KC_A']],
|
||||||
|
'macros': None,
|
||||||
|
}
|
||||||
|
templ = qmk.keymap.generate_c(keymap_json)
|
||||||
assert templ == '#include QMK_KEYBOARD_H\nconst uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {\t[0] = LAYOUT(KC_A)};\n'
|
assert templ == '#include QMK_KEYBOARD_H\nconst uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {\t[0] = LAYOUT(KC_A)};\n'
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -558,6 +558,40 @@ enum quantum_keycodes {
|
||||||
PROGRAMMABLE_BUTTON_31,
|
PROGRAMMABLE_BUTTON_31,
|
||||||
PROGRAMMABLE_BUTTON_32,
|
PROGRAMMABLE_BUTTON_32,
|
||||||
|
|
||||||
|
// Dedicated macro keys for Configurator and VIA
|
||||||
|
MACRO_0,
|
||||||
|
MACRO_1,
|
||||||
|
MACRO_2,
|
||||||
|
MACRO_3,
|
||||||
|
MACRO_4,
|
||||||
|
MACRO_5,
|
||||||
|
MACRO_6,
|
||||||
|
MACRO_7,
|
||||||
|
MACRO_8,
|
||||||
|
MACRO_9,
|
||||||
|
MACRO_10,
|
||||||
|
MACRO_11,
|
||||||
|
MACRO_12,
|
||||||
|
MACRO_13,
|
||||||
|
MACRO_14,
|
||||||
|
MACRO_15,
|
||||||
|
MACRO_16,
|
||||||
|
MACRO_17,
|
||||||
|
MACRO_18,
|
||||||
|
MACRO_19,
|
||||||
|
MACRO_20,
|
||||||
|
MACRO_21,
|
||||||
|
MACRO_22,
|
||||||
|
MACRO_23,
|
||||||
|
MACRO_24,
|
||||||
|
MACRO_25,
|
||||||
|
MACRO_26,
|
||||||
|
MACRO_27,
|
||||||
|
MACRO_28,
|
||||||
|
MACRO_29,
|
||||||
|
MACRO_30,
|
||||||
|
MACRO_31,
|
||||||
|
|
||||||
// Start of custom keycode range for keyboards and keymaps - always leave at the end
|
// Start of custom keycode range for keyboards and keymaps - always leave at the end
|
||||||
SAFE_RANGE
|
SAFE_RANGE
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,4 +5,5 @@
|
||||||
nose2
|
nose2
|
||||||
flake8
|
flake8
|
||||||
pep8-naming
|
pep8-naming
|
||||||
|
pyflakes
|
||||||
yapf
|
yapf
|
||||||
|
|
Loading…
Reference in a new issue