diff options
author | Zach White <skullydazed@gmail.com> | 2021-11-22 11:11:35 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-11-22 11:11:35 -0800 |
commit | 08ce0142bad40f22d05d33fdef8a7c8907154e96 (patch) | |
tree | 5b5da4650a76ec902a550e2719b79ffc2a73d74d | |
parent | 8181b155dbfd07561200b30b52a4046f2da92248 (diff) | |
download | qmk_firmware-08ce0142bad40f22d05d33fdef8a7c8907154e96.tar.gz qmk_firmware-08ce0142bad40f22d05d33fdef8a7c8907154e96.zip |
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>
-rw-r--r-- | data/mappings/info_config.json | 1 | ||||
-rw-r--r-- | data/schemas/keyboard.jsonschema | 1 | ||||
-rw-r--r-- | data/schemas/keymap.jsonschema | 35 | ||||
-rw-r--r-- | docs/feature_macros.md | 134 | ||||
-rw-r--r-- | keyboards/handwired/pytest/macro/.noci | 0 | ||||
-rw-r--r-- | keyboards/handwired/pytest/macro/info.json | 10 | ||||
-rw-r--r-- | keyboards/handwired/pytest/macro/keymaps/default/keymap.json | 15 | ||||
-rw-r--r-- | keyboards/handwired/pytest/macro/readme.md | 0 | ||||
-rw-r--r-- | keyboards/handwired/pytest/macro/rules.mk | 1 | ||||
-rwxr-xr-x | lib/python/qmk/cli/json2c.py | 2 | ||||
-rw-r--r-- | lib/python/qmk/commands.py | 2 | ||||
-rw-r--r-- | lib/python/qmk/keymap.py | 100 | ||||
-rw-r--r-- | lib/python/qmk/tests/test_cli_commands.py | 8 | ||||
-rw-r--r-- | lib/python/qmk/tests/test_qmk_keymap.py | 8 | ||||
-rw-r--r-- | quantum/quantum_keycodes.h | 34 | ||||
-rw-r--r-- | requirements-dev.txt | 1 |
16 files changed, 319 insertions, 33 deletions
diff --git a/data/mappings/info_config.json b/data/mappings/info_config.json index 50694e386..3cb64dd3a 100644 --- a/data/mappings/info_config.json +++ b/data/mappings/info_config.json | |||
@@ -76,6 +76,7 @@ | |||
76 | "QMK_KEYS_PER_SCAN": {"info_key": "qmk.keys_per_scan", "value_type": "int"}, | 76 | "QMK_KEYS_PER_SCAN": {"info_key": "qmk.keys_per_scan", "value_type": "int"}, |
77 | "QMK_LED": {"info_key": "qmk_lufa_bootloader.led"}, | 77 | "QMK_LED": {"info_key": "qmk_lufa_bootloader.led"}, |
78 | "QMK_SPEAKER": {"info_key": "qmk_lufa_bootloader.speaker"}, | 78 | "QMK_SPEAKER": {"info_key": "qmk_lufa_bootloader.speaker"}, |
79 | "SENDSTRING_BELL": {"info_key": "audio.macro_beep", "value_type": "bool"}, | ||
79 | "SPLIT_MODS_ENABLE": {"info_key": "split.transport.sync_modifiers", "value_type": "bool"}, | 80 | "SPLIT_MODS_ENABLE": {"info_key": "split.transport.sync_modifiers", "value_type": "bool"}, |
80 | "SPLIT_TRANSPORT_MIRROR": {"info_key": "split.transport.sync_matrix_state", "value_type": "bool"}, | 81 | "SPLIT_TRANSPORT_MIRROR": {"info_key": "split.transport.sync_matrix_state", "value_type": "bool"}, |
81 | "SPLIT_USB_DETECT": {"info_key": "split.usb_detect.enabled", "value_type": "bool"}, | 82 | "SPLIT_USB_DETECT": {"info_key": "split.usb_detect.enabled", "value_type": "bool"}, |
diff --git a/data/schemas/keyboard.jsonschema b/data/schemas/keyboard.jsonschema index 65d44c94d..37a0643ab 100644 --- a/data/schemas/keyboard.jsonschema +++ b/data/schemas/keyboard.jsonschema | |||
@@ -19,6 +19,7 @@ | |||
19 | "type": "object", | 19 | "type": "object", |
20 | "additionalProperties": false, | 20 | "additionalProperties": false, |
21 | "properties": { | 21 | "properties": { |
22 | "macro_beep": {"type": "boolean"}, | ||
22 | "pins": {"$ref": "qmk.definitions.v1#/mcu_pin_array"}, | 23 | "pins": {"$ref": "qmk.definitions.v1#/mcu_pin_array"}, |
23 | "voices": {"type": "boolean"} | 24 | "voices": {"type": "boolean"} |
24 | } | 25 | } |
diff --git a/data/schemas/keymap.jsonschema b/data/schemas/keymap.jsonschema index a4bdab966..faa250a94 100644 --- a/data/schemas/keymap.jsonschema +++ b/data/schemas/keymap.jsonschema | |||
@@ -5,6 +5,7 @@ | |||
5 | "type": "object", | 5 | "type": "object", |
6 | "properties": { | 6 | "properties": { |
7 | "author": {"type": "string"}, | 7 | "author": {"type": "string"}, |
8 | "host_language": {"$ref": "qmk.definitions.v1#/text_identifier"}, | ||
8 | "keyboard": {"$ref": "qmk.definitions.v1#/text_identifier"}, | 9 | "keyboard": {"$ref": "qmk.definitions.v1#/text_identifier"}, |
9 | "keymap": {"$ref": "qmk.definitions.v1#/text_identifier"}, | 10 | "keymap": {"$ref": "qmk.definitions.v1#/text_identifier"}, |
10 | "layout": {"$ref": "qmk.definitions.v1#/layout_macro"}, | 11 | "layout": {"$ref": "qmk.definitions.v1#/layout_macro"}, |
@@ -15,10 +16,42 @@ | |||
15 | "items": {"type": "string"} | 16 | "items": {"type": "string"} |
16 | } | 17 | } |
17 | }, | 18 | }, |
19 | "macros": { | ||
20 | "type": "array", | ||
21 | "items": { | ||
22 | "type": "array", | ||
23 | "items": { | ||
24 | "oneOf": [ | ||
25 | { | ||
26 | "type": "string" | ||
27 | }, | ||
28 | { | ||
29 | "type": "object", | ||
30 | "additionalProperties": false, | ||
31 | "properties": { | ||
32 | "action": { | ||
33 | "type": "string", | ||
34 | "enum": ['beep', 'delay', 'down', 'tap', 'up'] | ||
35 | }, | ||
36 | "keycodes": { | ||
37 | "type": "array", | ||
38 | "items": { | ||
39 | "$ref": "qmk.definitions.v1#/text_identifier" | ||
40 | } | ||
41 | }, | ||
42 | "duration": { | ||
43 | "$ref": "qmk.definitions.v1#/unsigned_int" | ||
44 | } | ||
45 | } | ||
46 | } | ||
47 | ] | ||
48 | } | ||
49 | } | ||
50 | }, | ||
18 | "config": {"$ref": "qmk.keyboard.v1"}, | 51 | "config": {"$ref": "qmk.keyboard.v1"}, |
19 | "notes": { | 52 | "notes": { |
20 | "type": "string", | 53 | "type": "string", |
21 | "description": "asdf" | 54 | "description": "asdf" |
22 | } | 55 | } |
23 | } | 56 | } |
24 | } \ No newline at end of file | 57 | } |
diff --git a/docs/feature_macros.md b/docs/feature_macros.md index 6807111ca..81ade5859 100644 --- a/docs/feature_macros.md +++ b/docs/feature_macros.md | |||
@@ -4,7 +4,107 @@ Macros allow you to send multiple keystrokes when pressing just one key. QMK has | |||
4 | 4 | ||
5 | !> **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. | 5 | !> **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. |
6 | 6 | ||
7 | ## `SEND_STRING()` & `process_record_user` | 7 | ## Using Macros In JSON Keymaps |
8 | |||
9 | You can define up to 32 macros in a `keymap.json` file, as used by [Configurator](newbs_building_firmware_configurator.md), and `qmk compile`. You can define these macros in a list under the `macros` keyword, like this: | ||
10 | |||
11 | ```json | ||
12 | { | ||
13 | "keyboard": "handwired/my_macropad", | ||
14 | "keymap": "my_keymap", | ||
15 | "macros": [ | ||
16 | [ | ||
17 | {"action":"down", "keycodes": ["LSFT"]}, | ||
18 | "hello world1", | ||
19 | {"action": "up","keycodes": ["LSFT"]} | ||
20 | ], | ||
21 | [ | ||
22 | {"action":"tap", "keycodes": ["LCTL", "LALT", "DEL"]} | ||
23 | ], | ||
24 | [ | ||
25 | "ding!", | ||
26 | {"action":"beep"} | ||
27 | ], | ||
28 | [ | ||
29 | {"action":"tap", "keycodes": ["F1"]}, | ||
30 | {"action":"delay", "duration": "1000"}, | ||
31 | {"action":"tap", "keycodes": ["PGDN"]} | ||
32 | ] | ||
33 | ], | ||
34 | "layout": "LAYOUT_all", | ||
35 | "layers": [ | ||
36 | ["MACRO_0", "MACRO_1", "MACRO_2", "MACRO_3"] | ||
37 | ] | ||
38 | } | ||
39 | ``` | ||
40 | |||
41 | ### Selecting Your Host Keyboard Layout | ||
42 | |||
43 | If you type in a language other than English, or use a non-QWERTY layout like Colemak, Dvorak, or Workman, you may have set your computer's input language to match this layout. This presents a challenge when creating macros- you may need to type different keys to get the same letters! To address this you can add the `host_language` key to your keymap.json, like so: | ||
44 | |||
45 | ```json | ||
46 | { | ||
47 | "keyboard": "handwired/my_macropad", | ||
48 | "keymap": "my_keymap", | ||
49 | "host_layout": "dvorak", | ||
50 | "macros": [ | ||
51 | ["Hello, World!"] | ||
52 | ], | ||
53 | "layout": "LAYOUT_all", | ||
54 | "layers": [ | ||
55 | ["MACRO_0"] | ||
56 | ] | ||
57 | } | ||
58 | ``` | ||
59 | |||
60 | The current list of available languages is: | ||
61 | |||
62 | | belgian | bepo | br_abnt2 | canadian_multilingual | | ||
63 | |:-------:|:----:|:--------:|:---------------------:| | ||
64 | | **colemak** | **croatian** | **czech** | **danish** | | ||
65 | | **dvorak_fr** | **dvorak** | **dvp** | **estonian** | | ||
66 | | **finnish** | **fr_ch** | **french_afnor** | **french** | | ||
67 | | **french_osx** | **german_ch** | **german** | **german_osx** | | ||
68 | | **hungarian** | **icelandic** | **italian** | **italian_osx_ansi** | | ||
69 | | **italian_osx_iso** | **jis** | **latvian** | **lithuanian_azerty** | | ||
70 | | **lithuanian_qwerty** | **norman** | **norwegian** | **portuguese** | | ||
71 | | **portuguese_osx_iso** | **romanian** | **serbian_latin** | **slovak** | | ||
72 | | **slovenian** | **spanish_dvorak** | **spanish** | **swedish** | | ||
73 | | **turkish_f** | **turkish_q** | **uk** | **us_international** | | ||
74 | | **workman** | **workman_zxcvm** | | ||
75 | |||
76 | ### Macro Basics | ||
77 | |||
78 | Each macro is an array consisting of strings and objects (dictionaries.) Strings are typed to your computer while objects allow you to control how your macro is typed out. | ||
79 | |||
80 | #### Object Format | ||
81 | |||
82 | All objects have one required key: `action`. This tells QMK what the object does. There are currently 5 actions: beep, delay, down, tap, up | ||
83 | |||
84 | Only basic keycodes (prefixed by `KC_`) are supported. Do not include the `KC_` prefix when listing keycodes. | ||
85 | |||
86 | * `beep` | ||
87 | * Play a bell if the keyboard has [audio enabled](feature_audio.md). | ||
88 | * Example: `{"action": "beep"}` | ||
89 | * `delay` | ||
90 | * Pause macro playback. Duration is specified in milliseconds (ms). | ||
91 | * Example: `{"action": "delay", "duration": 500}` | ||
92 | * `down` | ||
93 | * Send a key down event for one or more keycodes. | ||
94 | * Example, single key: `{"action":"down", "keycodes": ["LSFT"]}` | ||
95 | * Example, multiple keys: `{"action":"down", "keycodes": ["CTRL", "LSFT"]}` | ||
96 | * `tap` | ||
97 | * Type a chord, which sends a down event for each key followed by an up event for each key. | ||
98 | * Example, single key: `{"action":"tap", "keycodes": ["F13"]}` | ||
99 | * Example, multiple keys: `{"action":"tap", "keycodes": ["CTRL", "LALT", "DEL"]}` | ||
100 | * `up` | ||
101 | * Send a key up event for one or more keycodes. | ||
102 | * Example, single key: `{"action":"up", "keycodes": ["LSFT"]}` | ||
103 | * Example, multiple keys: `{"action":"up", "keycodes": ["CTRL", "LSFT"]}` | ||
104 | |||
105 | ## Using Macros in C Keymaps | ||
106 | |||
107 | ### `SEND_STRING()` & `process_record_user` | ||
8 | 108 | ||
9 | 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`). | 109 | 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`). |
10 | 110 | ||
@@ -91,7 +191,7 @@ const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { | |||
91 | }; | 191 | }; |
92 | ``` | 192 | ``` |
93 | 193 | ||
94 | ### Advanced Macros | 194 | #### Advanced Macros |
95 | 195 | ||
96 | 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. | 196 | 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. |
97 | 197 | ||
@@ -131,7 +231,7 @@ void post_process_record_user(uint16_t keycode, keyrecord_t *record) { | |||
131 | ``` | 231 | ``` |
132 | 232 | ||
133 | 233 | ||
134 | ### TAP, DOWN and UP | 234 | #### TAP, DOWN and UP |
135 | 235 | ||
136 | You may want to use keys in your macros that you can't write down, such as `Ctrl` or `Home`. | 236 | You may want to use keys in your macros that you can't write down, such as `Ctrl` or `Home`. |
137 | You can send arbitrary keycodes by wrapping them in: | 237 | You can send arbitrary keycodes by wrapping them in: |
@@ -178,7 +278,7 @@ They can be used like this: | |||
178 | 278 | ||
179 | 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. | 279 | 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. |
180 | 280 | ||
181 | ### Alternative Keymaps | 281 | #### Alternative Keymaps |
182 | 282 | ||
183 | 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: | 283 | 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: |
184 | 284 | ||
@@ -186,7 +286,7 @@ By default, it assumes a US keymap with a QWERTY layout; if you want to change t | |||
186 | #include "sendstring_colemak.h" | 286 | #include "sendstring_colemak.h" |
187 | ``` | 287 | ``` |
188 | 288 | ||
189 | ### Strings in Memory | 289 | #### Strings in Memory |
190 | 290 | ||
191 | 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: | 291 | 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: |
192 | 292 | ||
@@ -205,13 +305,13 @@ SEND_STRING(".."SS_TAP(X_END)); | |||
205 | ``` | 305 | ``` |
206 | 306 | ||
207 | 307 | ||
208 | ## Advanced Macro Functions | 308 | ### Advanced Macro Functions |
209 | 309 | ||
210 | 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. | 310 | 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. |
211 | 311 | ||
212 | ?> 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. | 312 | ?> 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. |
213 | 313 | ||
214 | ### `record->event.pressed` | 314 | #### `record->event.pressed` |
215 | 315 | ||
216 | This is a boolean value that can be tested to see if the switch is being pressed or released. An example of this is | 316 | This is a boolean value that can be tested to see if the switch is being pressed or released. An example of this is |
217 | 317 | ||
@@ -223,15 +323,15 @@ This is a boolean value that can be tested to see if the switch is being pressed | |||
223 | } | 323 | } |
224 | ``` | 324 | ``` |
225 | 325 | ||
226 | ### `register_code(<kc>);` | 326 | #### `register_code(<kc>);` |
227 | 327 | ||
228 | 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`. | 328 | 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`. |
229 | 329 | ||
230 | ### `unregister_code(<kc>);` | 330 | #### `unregister_code(<kc>);` |
231 | 331 | ||
232 | 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. | 332 | 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. |
233 | 333 | ||
234 | ### `tap_code(<kc>);` | 334 | #### `tap_code(<kc>);` |
235 | 335 | ||
236 | 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). | 336 | 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). |
237 | 337 | ||
@@ -239,31 +339,31 @@ If `TAP_CODE_DELAY` is defined (default 0), this function waits that many millis | |||
239 | 339 | ||
240 | 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. | 340 | 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. |
241 | 341 | ||
242 | ### `tap_code_delay(<kc>, <delay>);` | 342 | #### `tap_code_delay(<kc>, <delay>);` |
243 | 343 | ||
244 | Like `tap_code(<kc>)`, but with a `delay` parameter for specifying arbitrary intervals before sending the unregister event. | 344 | Like `tap_code(<kc>)`, but with a `delay` parameter for specifying arbitrary intervals before sending the unregister event. |
245 | 345 | ||
246 | ### `register_code16(<kc>);`, `unregister_code16(<kc>);` and `tap_code16(<kc>);` | 346 | #### `register_code16(<kc>);`, `unregister_code16(<kc>);` and `tap_code16(<kc>);` |
247 | 347 | ||
248 | 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). | 348 | 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). |
249 | 349 | ||
250 | Eg, you could use `register_code16(S(KC_5));` instead of registering the mod, then registering the keycode. | 350 | Eg, you could use `register_code16(S(KC_5));` instead of registering the mod, then registering the keycode. |
251 | 351 | ||
252 | ### `clear_keyboard();` | 352 | #### `clear_keyboard();` |
253 | 353 | ||
254 | This will clear all mods and keys currently pressed. | 354 | This will clear all mods and keys currently pressed. |
255 | 355 | ||
256 | ### `clear_mods();` | 356 | #### `clear_mods();` |
257 | 357 | ||
258 | This will clear all mods currently pressed. | 358 | This will clear all mods currently pressed. |
259 | 359 | ||
260 | ### `clear_keyboard_but_mods();` | 360 | #### `clear_keyboard_but_mods();` |
261 | 361 | ||
262 | This will clear all keys besides the mods currently pressed. | 362 | This will clear all keys besides the mods currently pressed. |
263 | 363 | ||
264 | ## Advanced Example: | 364 | ### Advanced Example: |
265 | 365 | ||
266 | ### Super ALT↯TAB | 366 | #### Super ALT↯TAB |
267 | 367 | ||
268 | 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. | 368 | 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. |
269 | 369 | ||
diff --git a/keyboards/handwired/pytest/macro/.noci b/keyboards/handwired/pytest/macro/.noci new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/keyboards/handwired/pytest/macro/.noci | |||
diff --git a/keyboards/handwired/pytest/macro/info.json b/keyboards/handwired/pytest/macro/info.json new file mode 100644 index 000000000..ed052a14a --- /dev/null +++ b/keyboards/handwired/pytest/macro/info.json | |||
@@ -0,0 +1,10 @@ | |||
1 | { | ||
2 | "maintainer": "qmk", | ||
3 | "layouts": { | ||
4 | "LAYOUT_custom": { | ||
5 | "layout": [ | ||
6 | { "label": "KC_Q", "matrix": [0, 0], "w": 1, "x": 0, "y": 0 } | ||
7 | ] | ||
8 | } | ||
9 | } | ||
10 | } | ||
diff --git a/keyboards/handwired/pytest/macro/keymaps/default/keymap.json b/keyboards/handwired/pytest/macro/keymaps/default/keymap.json new file mode 100644 index 000000000..f319d862d --- /dev/null +++ b/keyboards/handwired/pytest/macro/keymaps/default/keymap.json | |||
@@ -0,0 +1,15 @@ | |||
1 | { | ||
2 | "keyboard": "handwired/pytest/basic", | ||
3 | "keymap": "default_json", | ||
4 | "layout": "LAYOUT_ortho_1x1", | ||
5 | "layers": [["MACRO_0"]], | ||
6 | "macros": [ | ||
7 | [ | ||
8 | "Hello, World!", | ||
9 | {"action":"tap", "keycodes":["ENTER"]} | ||
10 | ] | ||
11 | ], | ||
12 | "author": "qmk", | ||
13 | "notes": "This file is a keymap.json file for handwired/pytest/basic", | ||
14 | "version": 1 | ||
15 | } | ||
diff --git a/keyboards/handwired/pytest/macro/readme.md b/keyboards/handwired/pytest/macro/readme.md new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/keyboards/handwired/pytest/macro/readme.md | |||
diff --git a/keyboards/handwired/pytest/macro/rules.mk b/keyboards/handwired/pytest/macro/rules.mk new file mode 100644 index 000000000..6b42774db --- /dev/null +++ b/keyboards/handwired/pytest/macro/rules.mk | |||
@@ -0,0 +1 @@ | |||
MCU = atmega32u4 | |||
diff --git a/lib/python/qmk/cli/json2c.py b/lib/python/qmk/cli/json2c.py index a90578c02..ae8248e6b 100755 --- a/lib/python/qmk/cli/json2c.py +++ b/lib/python/qmk/cli/json2c.py | |||
@@ -33,7 +33,7 @@ def json2c(cli): | |||
33 | cli.args.output = None | 33 | cli.args.output = None |
34 | 34 | ||
35 | # Generate the keymap | 35 | # Generate the keymap |
36 | keymap_c = qmk.keymap.generate_c(user_keymap['keyboard'], user_keymap['layout'], user_keymap['layers']) | 36 | keymap_c = qmk.keymap.generate_c(user_keymap) |
37 | 37 | ||
38 | if cli.args.output: | 38 | if cli.args.output: |
39 | cli.args.output.parent.mkdir(parents=True, exist_ok=True) | 39 | cli.args.output.parent.mkdir(parents=True, exist_ok=True) |
diff --git a/lib/python/qmk/commands.py b/lib/python/qmk/commands.py index 2995a5fda..5a0194377 100644 --- a/lib/python/qmk/commands.py +++ b/lib/python/qmk/commands.py | |||
@@ -190,7 +190,7 @@ def compile_configurator_json(user_keymap, bootloader=None, parallel=1, **env_va | |||
190 | target = f'{keyboard_filesafe}_{user_keymap["keymap"]}' | 190 | target = f'{keyboard_filesafe}_{user_keymap["keymap"]}' |
191 | keyboard_output = Path(f'{KEYBOARD_OUTPUT_PREFIX}{keyboard_filesafe}') | 191 | keyboard_output = Path(f'{KEYBOARD_OUTPUT_PREFIX}{keyboard_filesafe}') |
192 | keymap_output = Path(f'{keyboard_output}_{user_keymap["keymap"]}') | 192 | keymap_output = Path(f'{keyboard_output}_{user_keymap["keymap"]}') |
193 | c_text = qmk.keymap.generate_c(user_keymap['keyboard'], user_keymap['layout'], user_keymap['layers']) | 193 | c_text = qmk.keymap.generate_c(user_keymap) |
194 | keymap_dir = keymap_output / 'src' | 194 | keymap_dir = keymap_output / 'src' |
195 | keymap_c = keymap_dir / 'keymap.c' | 195 | keymap_c = keymap_dir / 'keymap.c' |
196 | 196 | ||
diff --git a/lib/python/qmk/keymap.py b/lib/python/qmk/keymap.py index 6eec49cfd..00b5a78a5 100644 --- a/lib/python/qmk/keymap.py +++ b/lib/python/qmk/keymap.py | |||
@@ -17,6 +17,7 @@ from qmk.errors import CppError | |||
17 | 17 | ||
18 | # The `keymap.c` template to use when a keyboard doesn't have its own | 18 | # The `keymap.c` template to use when a keyboard doesn't have its own |
19 | DEFAULT_KEYMAP_C = """#include QMK_KEYBOARD_H | 19 | DEFAULT_KEYMAP_C = """#include QMK_KEYBOARD_H |
20 | __INCLUDES__ | ||
20 | 21 | ||
21 | /* THIS FILE WAS GENERATED! | 22 | /* THIS FILE WAS GENERATED! |
22 | * | 23 | * |
@@ -27,6 +28,7 @@ DEFAULT_KEYMAP_C = """#include QMK_KEYBOARD_H | |||
27 | const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { | 28 | const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { |
28 | __KEYMAP_GOES_HERE__ | 29 | __KEYMAP_GOES_HERE__ |
29 | }; | 30 | }; |
31 | |||
30 | """ | 32 | """ |
31 | 33 | ||
32 | 34 | ||
@@ -180,10 +182,11 @@ def generate_json(keymap, keyboard, layout, layers): | |||
180 | return new_keymap | 182 | return new_keymap |
181 | 183 | ||
182 | 184 | ||
183 | def generate_c(keyboard, layout, layers): | 185 | def generate_c(keymap_json): |
184 | """Returns a `keymap.c` or `keymap.json` for the specified keyboard, layout, and layers. | 186 | """Returns a `keymap.c`. |
187 | |||
188 | `keymap_json` is a dictionary with the following keys: | ||
185 | 189 | ||
186 | Args: | ||
187 | keyboard | 190 | keyboard |
188 | The name of the keyboard | 191 | The name of the keyboard |
189 | 192 | ||
@@ -192,19 +195,89 @@ def generate_c(keyboard, layout, layers): | |||
192 | 195 | ||
193 | layers | 196 | layers |
194 | An array of arrays describing the keymap. Each item in the inner array should be a string that is a valid QMK keycode. | 197 | An array of arrays describing the keymap. Each item in the inner array should be a string that is a valid QMK keycode. |
198 | |||
199 | macros | ||
200 | A sequence of strings containing macros to implement for this keyboard. | ||
195 | """ | 201 | """ |
196 | new_keymap = template_c(keyboard) | 202 | new_keymap = template_c(keymap_json['keyboard']) |
197 | layer_txt = [] | 203 | layer_txt = [] |
198 | for layer_num, layer in enumerate(layers): | 204 | |
205 | for layer_num, layer in enumerate(keymap_json['layers']): | ||
199 | if layer_num != 0: | 206 | if layer_num != 0: |
200 | layer_txt[-1] = layer_txt[-1] + ',' | 207 | layer_txt[-1] = layer_txt[-1] + ',' |
201 | layer = map(_strip_any, layer) | 208 | layer = map(_strip_any, layer) |
202 | layer_keys = ', '.join(layer) | 209 | layer_keys = ', '.join(layer) |
203 | layer_txt.append('\t[%s] = %s(%s)' % (layer_num, layout, layer_keys)) | 210 | layer_txt.append('\t[%s] = %s(%s)' % (layer_num, keymap_json['layout'], layer_keys)) |
204 | 211 | ||
205 | keymap = '\n'.join(layer_txt) | 212 | keymap = '\n'.join(layer_txt) |
206 | new_keymap = new_keymap.replace('__KEYMAP_GOES_HERE__', keymap) | 213 | new_keymap = new_keymap.replace('__KEYMAP_GOES_HERE__', keymap) |
207 | 214 | ||
215 | if keymap_json.get('macros'): | ||
216 | macro_txt = [ | ||
217 | 'bool process_record_user(uint16_t keycode, keyrecord_t *record) {', | ||
218 | ' if (record->event.pressed) {', | ||
219 | ' switch (keycode) {', | ||
220 | ] | ||
221 | |||
222 | for i, macro_array in enumerate(keymap_json['macros']): | ||
223 | macro = [] | ||
224 | |||
225 | for macro_fragment in macro_array: | ||
226 | if isinstance(macro_fragment, str): | ||
227 | macro_fragment = macro_fragment.replace('\\', '\\\\') | ||
228 | macro_fragment = macro_fragment.replace('\r\n', r'\n') | ||
229 | macro_fragment = macro_fragment.replace('\n', r'\n') | ||
230 | macro_fragment = macro_fragment.replace('\r', r'\n') | ||
231 | macro_fragment = macro_fragment.replace('\t', r'\t') | ||
232 | macro_fragment = macro_fragment.replace('"', r'\"') | ||
233 | |||
234 | macro.append(f'"{macro_fragment}"') | ||
235 | |||
236 | elif isinstance(macro_fragment, dict): | ||
237 | newstring = [] | ||
238 | |||
239 | if macro_fragment['action'] == 'delay': | ||
240 | newstring.append(f"SS_DELAY({macro_fragment['duration']})") | ||
241 | |||
242 | elif macro_fragment['action'] == 'beep': | ||
243 | newstring.append(r'"\a"') | ||
244 | |||
245 | elif macro_fragment['action'] == 'tap' and len(macro_fragment['keycodes']) > 1: | ||
246 | last_keycode = macro_fragment['keycodes'].pop() | ||
247 | |||
248 | for keycode in macro_fragment['keycodes']: | ||
249 | newstring.append(f'SS_DOWN(X_{keycode})') | ||
250 | |||
251 | newstring.append(f'SS_TAP(X_{last_keycode})') | ||
252 | |||
253 | for keycode in reversed(macro_fragment['keycodes']): | ||
254 | newstring.append(f'SS_UP(X_{keycode})') | ||
255 | |||
256 | else: | ||
257 | for keycode in macro_fragment['keycodes']: | ||
258 | newstring.append(f"SS_{macro_fragment['action'].upper()}(X_{keycode})") | ||
259 | |||
260 | macro.append(''.join(newstring)) | ||
261 | |||
262 | new_macro = "".join(macro) | ||
263 | new_macro = new_macro.replace('""', '') | ||
264 | macro_txt.append(f' case MACRO_{i}:') | ||
265 | macro_txt.append(f' SEND_STRING({new_macro});') | ||
266 | macro_txt.append(' return false;') | ||
267 | |||
268 | macro_txt.append(' }') | ||
269 | macro_txt.append(' }') | ||
270 | macro_txt.append('\n return true;') | ||
271 | macro_txt.append('};') | ||
272 | macro_txt.append('') | ||
273 | |||
274 | new_keymap = '\n'.join((new_keymap, *macro_txt)) | ||
275 | |||
276 | if keymap_json.get('host_language'): | ||
277 | new_keymap = new_keymap.replace('__INCLUDES__', f'#include "keymap_{keymap_json["host_language"]}.h"\n#include "sendstring_{keymap_json["host_language"]}.h"\n') | ||
278 | else: | ||
279 | new_keymap = new_keymap.replace('__INCLUDES__', '') | ||
280 | |||
208 | return new_keymap | 281 | return new_keymap |
209 | 282 | ||
210 | 283 | ||
@@ -217,7 +290,7 @@ def write_file(keymap_filename, keymap_content): | |||
217 | return keymap_filename | 290 | return keymap_filename |
218 | 291 | ||
219 | 292 | ||
220 | def write_json(keyboard, keymap, layout, layers): | 293 | def write_json(keyboard, keymap, layout, layers, macros=None): |
221 | """Generate the `keymap.json` and write it to disk. | 294 | """Generate the `keymap.json` and write it to disk. |
222 | 295 | ||
223 | Returns the filename written to. | 296 | Returns the filename written to. |
@@ -235,19 +308,19 @@ def write_json(keyboard, keymap, layout, layers): | |||
235 | layers | 308 | layers |
236 | An array of arrays describing the keymap. Each item in the inner array should be a string that is a valid QMK keycode. | 309 | An array of arrays describing the keymap. Each item in the inner array should be a string that is a valid QMK keycode. |
237 | """ | 310 | """ |
238 | keymap_json = generate_json(keyboard, keymap, layout, layers) | 311 | keymap_json = generate_json(keyboard, keymap, layout, layers, macros=None) |
239 | keymap_content = json.dumps(keymap_json) | 312 | keymap_content = json.dumps(keymap_json) |
240 | keymap_file = qmk.path.keymap(keyboard) / keymap / 'keymap.json' | 313 | keymap_file = qmk.path.keymap(keyboard) / keymap / 'keymap.json' |
241 | 314 | ||
242 | return write_file(keymap_file, keymap_content) | 315 | return write_file(keymap_file, keymap_content) |
243 | 316 | ||
244 | 317 | ||
245 | def write(keyboard, keymap, layout, layers): | 318 | def write(keymap_json): |
246 | """Generate the `keymap.c` and write it to disk. | 319 | """Generate the `keymap.c` and write it to disk. |
247 | 320 | ||
248 | Returns the filename written to. | 321 | Returns the filename written to. |
249 | 322 | ||
250 | Args: | 323 | `keymap_json` should be a dict with the following keys: |
251 | keyboard | 324 | keyboard |
252 | The name of the keyboard | 325 | The name of the keyboard |
253 | 326 | ||
@@ -259,9 +332,12 @@ def write(keyboard, keymap, layout, layers): | |||
259 | 332 | ||
260 | layers | 333 | layers |
261 | An array of arrays describing the keymap. Each item in the inner array should be a string that is a valid QMK keycode. | 334 | An array of arrays describing the keymap. Each item in the inner array should be a string that is a valid QMK keycode. |
335 | |||
336 | macros | ||
337 | A list of macros for this keymap. | ||
262 | """ | 338 | """ |
263 | keymap_content = generate_c(keyboard, layout, layers) | 339 | keymap_content = generate_c(keymap_json) |
264 | keymap_file = qmk.path.keymap(keyboard) / keymap / 'keymap.c' | 340 | keymap_file = qmk.path.keymap(keymap_json['keyboard']) / keymap_json['keymap'] / 'keymap.c' |
265 | 341 | ||
266 | return write_file(keymap_file, keymap_content) | 342 | return write_file(keymap_file, keymap_content) |
267 | 343 | ||
diff --git a/lib/python/qmk/tests/test_cli_commands.py b/lib/python/qmk/tests/test_cli_commands.py index 1e3c64e73..2973f8170 100644 --- a/lib/python/qmk/tests/test_cli_commands.py +++ b/lib/python/qmk/tests/test_cli_commands.py | |||
@@ -142,6 +142,14 @@ def test_json2c(): | |||
142 | 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' | 142 | 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' |
143 | 143 | ||
144 | 144 | ||
145 | def test_json2c_macros(): | ||
146 | result = check_subcommand("json2c", 'keyboards/handwired/pytest/macro/keymaps/default/keymap.json') | ||
147 | check_returncode(result) | ||
148 | assert 'LAYOUT_ortho_1x1(MACRO_0)' in result.stdout | ||
149 | assert 'case MACRO_0:' in result.stdout | ||
150 | assert 'SEND_STRING("Hello, World!"SS_TAP(X_ENTER));' in result.stdout | ||
151 | |||
152 | |||
145 | def test_json2c_stdin(): | 153 | def test_json2c_stdin(): |
146 | result = check_subcommand_stdin('keyboards/handwired/pytest/has_template/keymaps/default_json/keymap.json', 'json2c', '-') | 154 | result = check_subcommand_stdin('keyboards/handwired/pytest/has_template/keymaps/default_json/keymap.json', 'json2c', '-') |
147 | check_returncode(result) | 155 | check_returncode(result) |
diff --git a/lib/python/qmk/tests/test_qmk_keymap.py b/lib/python/qmk/tests/test_qmk_keymap.py index b9e80df67..5e2efc123 100644 --- a/lib/python/qmk/tests/test_qmk_keymap.py +++ b/lib/python/qmk/tests/test_qmk_keymap.py | |||
@@ -22,7 +22,13 @@ def test_template_json_pytest_has_template(): | |||
22 | 22 | ||
23 | 23 | ||
24 | def test_generate_c_pytest_has_template(): | 24 | def test_generate_c_pytest_has_template(): |
25 | templ = qmk.keymap.generate_c('handwired/pytest/has_template', 'LAYOUT', [['KC_A']]) | 25 | keymap_json = { |
26 | 'keyboard': 'handwired/pytest/has_template', | ||
27 | 'layout': 'LAYOUT', | ||
28 | 'layers': [['KC_A']], | ||
29 | 'macros': None, | ||
30 | } | ||
31 | templ = qmk.keymap.generate_c(keymap_json) | ||
26 | assert templ == '#include QMK_KEYBOARD_H\nconst uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {\t[0] = LAYOUT(KC_A)};\n' | 32 | assert templ == '#include QMK_KEYBOARD_H\nconst uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {\t[0] = LAYOUT(KC_A)};\n' |
27 | 33 | ||
28 | 34 | ||
diff --git a/quantum/quantum_keycodes.h b/quantum/quantum_keycodes.h index cde97074d..d013a6a16 100644 --- a/quantum/quantum_keycodes.h +++ b/quantum/quantum_keycodes.h | |||
@@ -558,6 +558,40 @@ enum quantum_keycodes { | |||
558 | PROGRAMMABLE_BUTTON_31, | 558 | PROGRAMMABLE_BUTTON_31, |
559 | PROGRAMMABLE_BUTTON_32, | 559 | PROGRAMMABLE_BUTTON_32, |
560 | 560 | ||
561 | // Dedicated macro keys for Configurator and VIA | ||
562 | MACRO_0, | ||
563 | MACRO_1, | ||
564 | MACRO_2, | ||
565 | MACRO_3, | ||
566 | MACRO_4, | ||
567 | MACRO_5, | ||
568 | MACRO_6, | ||
569 | MACRO_7, | ||
570 | MACRO_8, | ||
571 | MACRO_9, | ||
572 | MACRO_10, | ||
573 | MACRO_11, | ||
574 | MACRO_12, | ||
575 | MACRO_13, | ||
576 | MACRO_14, | ||
577 | MACRO_15, | ||
578 | MACRO_16, | ||
579 | MACRO_17, | ||
580 | MACRO_18, | ||
581 | MACRO_19, | ||
582 | MACRO_20, | ||
583 | MACRO_21, | ||
584 | MACRO_22, | ||
585 | MACRO_23, | ||
586 | MACRO_24, | ||
587 | MACRO_25, | ||
588 | MACRO_26, | ||
589 | MACRO_27, | ||
590 | MACRO_28, | ||
591 | MACRO_29, | ||
592 | MACRO_30, | ||
593 | MACRO_31, | ||
594 | |||
561 | // Start of custom keycode range for keyboards and keymaps - always leave at the end | 595 | // Start of custom keycode range for keyboards and keymaps - always leave at the end |
562 | SAFE_RANGE | 596 | SAFE_RANGE |
563 | }; | 597 | }; |
diff --git a/requirements-dev.txt b/requirements-dev.txt index 1db3b6d73..721370733 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt | |||
@@ -5,4 +5,5 @@ | |||
5 | nose2 | 5 | nose2 |
6 | flake8 | 6 | flake8 |
7 | pep8-naming | 7 | pep8-naming |
8 | pyflakes | ||
8 | yapf | 9 | yapf |