From 08ce0142bad40f22d05d33fdef8a7c8907154e96 Mon Sep 17 00:00:00 2001 From: Zach White Date: Mon, 22 Nov 2021 11:11:35 -0800 Subject: [PATCH] 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 * escape backslash characters * format * flake8 * Update quantum/quantum_keycodes.h Co-authored-by: Nick Brassel --- data/mappings/info_config.json | 1 + data/schemas/keyboard.jsonschema | 1 + data/schemas/keymap.jsonschema | 35 ++++- docs/feature_macros.md | 134 +++++++++++++++--- keyboards/handwired/pytest/macro/.noci | 0 keyboards/handwired/pytest/macro/info.json | 10 ++ .../pytest/macro/keymaps/default/keymap.json | 15 ++ keyboards/handwired/pytest/macro/readme.md | 0 keyboards/handwired/pytest/macro/rules.mk | 1 + lib/python/qmk/cli/json2c.py | 2 +- lib/python/qmk/commands.py | 2 +- lib/python/qmk/keymap.py | 100 +++++++++++-- lib/python/qmk/tests/test_cli_commands.py | 8 ++ lib/python/qmk/tests/test_qmk_keymap.py | 8 +- quantum/quantum_keycodes.h | 34 +++++ requirements-dev.txt | 1 + 16 files changed, 319 insertions(+), 33 deletions(-) create mode 100644 keyboards/handwired/pytest/macro/.noci create mode 100644 keyboards/handwired/pytest/macro/info.json create mode 100644 keyboards/handwired/pytest/macro/keymaps/default/keymap.json create mode 100644 keyboards/handwired/pytest/macro/readme.md create mode 100644 keyboards/handwired/pytest/macro/rules.mk diff --git a/data/mappings/info_config.json b/data/mappings/info_config.json index 50694e38632..3cb64dd3a2f 100644 --- a/data/mappings/info_config.json +++ b/data/mappings/info_config.json @@ -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"}, diff --git a/data/schemas/keyboard.jsonschema b/data/schemas/keyboard.jsonschema index 65d44c94d2c..37a0643ab2e 100644 --- a/data/schemas/keyboard.jsonschema +++ b/data/schemas/keyboard.jsonschema @@ -19,6 +19,7 @@ "type": "object", "additionalProperties": false, "properties": { + "macro_beep": {"type": "boolean"}, "pins": {"$ref": "qmk.definitions.v1#/mcu_pin_array"}, "voices": {"type": "boolean"} } diff --git a/data/schemas/keymap.jsonschema b/data/schemas/keymap.jsonschema index a4bdab966b6..faa250a9429 100644 --- a/data/schemas/keymap.jsonschema +++ b/data/schemas/keymap.jsonschema @@ -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,10 +16,42 @@ "items": {"type": "string"} } }, + "macros": { + "type": "array", + "items": { + "type": "array", + "items": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "action": { + "type": "string", + "enum": ['beep', 'delay', 'down', 'tap', 'up'] + }, + "keycodes": { + "type": "array", + "items": { + "$ref": "qmk.definitions.v1#/text_identifier" + } + }, + "duration": { + "$ref": "qmk.definitions.v1#/unsigned_int" + } + } + } + ] + } + } + }, "config": {"$ref": "qmk.keyboard.v1"}, "notes": { "type": "string", "description": "asdf" } } -} \ No newline at end of file +} diff --git a/docs/feature_macros.md b/docs/feature_macros.md index 6807111ca2d..81ade585926 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 !> **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();` +#### `register_code();` This sends the `` 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();` +#### `unregister_code();` Parallel to `register_code` function, this sends the `` keyup event to the computer. If you don't use this, the key will be held down until it's sent. -### `tap_code();` +#### `tap_code();` Sends `register_code()` and then `unregister_code()`. 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(, );` +#### `tap_code_delay(, );` Like `tap_code()`, but with a `delay` parameter for specifying arbitrary intervals before sending the unregister event. -### `register_code16();`, `unregister_code16();` and `tap_code16();` +#### `register_code16();`, `unregister_code16();` and `tap_code16();` 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. diff --git a/keyboards/handwired/pytest/macro/.noci b/keyboards/handwired/pytest/macro/.noci new file mode 100644 index 00000000000..e69de29bb2d diff --git a/keyboards/handwired/pytest/macro/info.json b/keyboards/handwired/pytest/macro/info.json new file mode 100644 index 00000000000..ed052a14a3e --- /dev/null +++ b/keyboards/handwired/pytest/macro/info.json @@ -0,0 +1,10 @@ +{ + "maintainer": "qmk", + "layouts": { + "LAYOUT_custom": { + "layout": [ + { "label": "KC_Q", "matrix": [0, 0], "w": 1, "x": 0, "y": 0 } + ] + } + } +} 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 00000000000..f319d862d83 --- /dev/null +++ b/keyboards/handwired/pytest/macro/keymaps/default/keymap.json @@ -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 +} diff --git a/keyboards/handwired/pytest/macro/readme.md b/keyboards/handwired/pytest/macro/readme.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/keyboards/handwired/pytest/macro/rules.mk b/keyboards/handwired/pytest/macro/rules.mk new file mode 100644 index 00000000000..6b42774dbff --- /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 a90578c021d..ae8248e6b72 100755 --- a/lib/python/qmk/cli/json2c.py +++ b/lib/python/qmk/cli/json2c.py @@ -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) diff --git a/lib/python/qmk/commands.py b/lib/python/qmk/commands.py index 2995a5fdabe..5a019437734 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 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' diff --git a/lib/python/qmk/keymap.py b/lib/python/qmk/keymap.py index 6eec49cfd1a..00b5a78a5ac 100644 --- a/lib/python/qmk/keymap.py +++ b/lib/python/qmk/keymap.py @@ -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) diff --git a/lib/python/qmk/tests/test_cli_commands.py b/lib/python/qmk/tests/test_cli_commands.py index 1e3c64e7352..2973f817022 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(): 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) diff --git a/lib/python/qmk/tests/test_qmk_keymap.py b/lib/python/qmk/tests/test_qmk_keymap.py index b9e80df6724..5e2efc12329 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(): 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' diff --git a/quantum/quantum_keycodes.h b/quantum/quantum_keycodes.h index cde97074d39..d013a6a169d 100644 --- a/quantum/quantum_keycodes.h +++ b/quantum/quantum_keycodes.h @@ -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 }; diff --git a/requirements-dev.txt b/requirements-dev.txt index 1db3b6d7331..72137073381 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -5,4 +5,5 @@ nose2 flake8 pep8-naming +pyflakes yapf