forked from mirrors/qmk_userspace
		
	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_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"},
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,6 +19,7 @@
 | 
			
		|||
            "type": "object",
 | 
			
		||||
            "additionalProperties": false,
 | 
			
		||||
            "properties": {
 | 
			
		||||
                "macro_beep": {"type": "boolean"},
 | 
			
		||||
                "pins": {"$ref": "qmk.definitions.v1#/mcu_pin_array"},
 | 
			
		||||
                "voices": {"type": "boolean"}
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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"},
 | 
			
		||||
| 
						 | 
				
			
			@ -15,6 +16,38 @@
 | 
			
		|||
                "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",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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`).
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -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. 
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -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:
 | 
			
		||||
| 
						 | 
				
			
			@ -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.
 | 
			
		||||
 | 
			
		||||
### 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:
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -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"
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### 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:
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -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`.
 | 
			
		||||
 | 
			
		||||
### `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).
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -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.
 | 
			
		||||
 | 
			
		||||
### `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.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										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
 | 
			
		||||
 | 
			
		||||
    # 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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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'
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,6 +17,7 @@ from qmk.errors import CppError
 | 
			
		|||
 | 
			
		||||
# The `keymap.c` template to use when a keyboard doesn't have its own
 | 
			
		||||
DEFAULT_KEYMAP_C = """#include QMK_KEYBOARD_H
 | 
			
		||||
__INCLUDES__
 | 
			
		||||
 | 
			
		||||
/* THIS FILE WAS GENERATED!
 | 
			
		||||
 *
 | 
			
		||||
| 
						 | 
				
			
			@ -27,6 +28,7 @@ DEFAULT_KEYMAP_C = """#include QMK_KEYBOARD_H
 | 
			
		|||
const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
 | 
			
		||||
__KEYMAP_GOES_HERE__
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -180,10 +182,11 @@ def generate_json(keymap, keyboard, layout, layers):
 | 
			
		|||
    return new_keymap
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def generate_c(keyboard, layout, layers):
 | 
			
		||||
    """Returns a `keymap.c` or `keymap.json` for the specified keyboard, layout, and layers.
 | 
			
		||||
def generate_c(keymap_json):
 | 
			
		||||
    """Returns a `keymap.c`.
 | 
			
		||||
 | 
			
		||||
    `keymap_json` is a dictionary with the following keys:
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
        keyboard
 | 
			
		||||
            The name of the keyboard
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -192,19 +195,89 @@ def generate_c(keyboard, layout, 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.
 | 
			
		||||
 | 
			
		||||
        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 = []
 | 
			
		||||
    for layer_num, layer in enumerate(layers):
 | 
			
		||||
 | 
			
		||||
    for layer_num, layer in enumerate(keymap_json['layers']):
 | 
			
		||||
        if layer_num != 0:
 | 
			
		||||
            layer_txt[-1] = layer_txt[-1] + ','
 | 
			
		||||
        layer = map(_strip_any, 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)
 | 
			
		||||
    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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -217,7 +290,7 @@ def write_file(keymap_filename, keymap_content):
 | 
			
		|||
    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.
 | 
			
		||||
 | 
			
		||||
    Returns the filename written to.
 | 
			
		||||
| 
						 | 
				
			
			@ -235,19 +308,19 @@ def write_json(keyboard, keymap, layout, 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.
 | 
			
		||||
    """
 | 
			
		||||
    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_file = qmk.path.keymap(keyboard) / keymap / 'keymap.json'
 | 
			
		||||
 | 
			
		||||
    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.
 | 
			
		||||
 | 
			
		||||
    Returns the filename written to.
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
    `keymap_json` should be a dict with the following keys:
 | 
			
		||||
        keyboard
 | 
			
		||||
            The name of the keyboard
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -259,9 +332,12 @@ def write(keyboard, keymap, layout, 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.
 | 
			
		||||
 | 
			
		||||
        macros
 | 
			
		||||
            A list of macros for this keymap.
 | 
			
		||||
    """
 | 
			
		||||
    keymap_content = generate_c(keyboard, layout, layers)
 | 
			
		||||
    keymap_file = qmk.path.keymap(keyboard) / keymap / 'keymap.c'
 | 
			
		||||
    keymap_content = generate_c(keymap_json)
 | 
			
		||||
    keymap_file = qmk.path.keymap(keymap_json['keyboard']) / keymap_json['keymap'] / 'keymap.c'
 | 
			
		||||
 | 
			
		||||
    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'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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():
 | 
			
		||||
    result = check_subcommand_stdin('keyboards/handwired/pytest/has_template/keymaps/default_json/keymap.json', 'json2c', '-')
 | 
			
		||||
    check_returncode(result)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,7 +22,13 @@ def test_template_json_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'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -558,6 +558,40 @@ enum quantum_keycodes {
 | 
			
		|||
    PROGRAMMABLE_BUTTON_31,
 | 
			
		||||
    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
 | 
			
		||||
    SAFE_RANGE
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,4 +5,5 @@
 | 
			
		|||
nose2
 | 
			
		||||
flake8
 | 
			
		||||
pep8-naming
 | 
			
		||||
pyflakes
 | 
			
		||||
yapf
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue