Custom keycodes in JSON (#19925)

This commit is contained in:
Joel Challis 2023-03-27 20:01:07 +01:00 committed by GitHub
parent e35bb8ebfa
commit 79b0f9168e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 129 additions and 13 deletions

View File

@ -71,6 +71,38 @@
"type": "string", "type": "string",
"pattern": "^[0-9a-z][0-9a-z_/]*$" "pattern": "^[0-9a-z][0-9a-z_/]*$"
}, },
"keycode": {
"type": "string",
"minLength": 2,
"maxLength": 50,
"pattern": "^[A-Z][A-Zs_0-9]*$"
},
"keycode_short": {
"type": "string",
"minLength": 2,
"maxLength": 7,
"pattern": "^[A-Z][A-Zs_0-9]*$"
},
"keycode_decl": {
"type": "object",
"required": [
"key"
],
"properties": {
"key": {"$ref": "#/keycode"},
"label": {"$ref": "#/text_identifier"},
"aliases": {
"type": "array",
"minItems": 1,
"items": {"$ref": "#/keycode_short"}
}
}
},
"keycode_decl_array": {
"type": "array",
"minItems": 1
"items": {"$ref": "#/keycode_decl"}
},
"mcu_pin_array": { "mcu_pin_array": {
"type": "array", "type": "array",
"items": {"$ref": "#/mcu_pin"} "items": {"$ref": "#/mcu_pin"}

View File

@ -259,6 +259,7 @@
"on_state": {"$ref": "qmk.definitions.v1#/bit"} "on_state": {"$ref": "qmk.definitions.v1#/bit"}
} }
}, },
"keycodes": {"$ref": "qmk.definitions.v1#/keycode_decl_array"},
"layout_aliases": { "layout_aliases": {
"type": "object", "type": "object",
"additionalProperties": {"$ref": "qmk.definitions.v1#/layout_macro"} "additionalProperties": {"$ref": "qmk.definitions.v1#/layout_macro"}

View File

@ -8,11 +8,7 @@
"type": "string", "type": "string",
"minLength": 2, "minLength": 2,
"maxLength": 50, "maxLength": 50,
"pattern": "^[A-Zs_0-9]*$" "pattern": "^[A-Z][A-Zs_0-9]*$"
},
"hex_number_4d": {
"type": "string",
"pattern": "^0x[0-9A-F]{4}$"
} }
}, },
"properties": { "properties": {
@ -34,10 +30,10 @@
"keycodes": { "keycodes": {
"type": "object", "type": "object",
"propertyNames": { "propertyNames": {
"$ref": "#/definitions/hex_number_4d" "$ref": "qmk.definitions.v1#/hex_number_4d"
}, },
"additionalProperties": { "additionalProperties": {
"type": "object", "type": "object", // use 'qmk.definitions.v1#/keycode_decl' when problem keycodes are removed
"required": [ "required": [
"key" "key"
], ],

View File

@ -67,6 +67,7 @@
} }
} }
}, },
"keycodes": {"$ref": "qmk.definitions.v1#/keycode_decl_array"},
"config": {"$ref": "qmk.keyboard.v1"}, "config": {"$ref": "qmk.keyboard.v1"},
"notes": { "notes": {
"type": "string" "type": "string"

View File

@ -0,0 +1,12 @@
{
"keyboard": "handwired/pytest/basic",
"keymap": "default_json",
"layout": "LAYOUT_ortho_1x1",
"layers": [["EXAMPLE_1"]],
"keycodes": [
{ "key": "EXAMPLE_1" }
],
"author": "qmk",
"notes": "This file is a keymap.json file for handwired/pytest/basic",
"version": 1
}

View File

@ -67,6 +67,12 @@ def _filtered_copy(src, dst):
dst.write_text(json.dumps(data), encoding='utf-8') dst.write_text(json.dumps(data), encoding='utf-8')
return dst return dst
if dst.suffix == '.jsonschema':
data = json_load(src)
dst.write_text(json.dumps(data), encoding='utf-8')
return dst
return shutil.copy2(src, dst) return shutil.copy2(src, dst)

View File

@ -11,12 +11,9 @@ from qmk.keyboard import keyboard_completer, keyboard_folder
from qmk.constants import COL_LETTERS, ROW_LETTERS, GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE from qmk.constants import COL_LETTERS, ROW_LETTERS, GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE
def _generate_layouts(keyboard): def _generate_layouts(keyboard, kb_info_json):
"""Generates the layouts.h file. """Generates the layouts macros.
""" """
# Build the info.json file
kb_info_json = info_json(keyboard)
if 'matrix_size' not in kb_info_json: if 'matrix_size' not in kb_info_json:
cli.log.error(f'{keyboard}: Invalid matrix config.') cli.log.error(f'{keyboard}: Invalid matrix config.')
return [] return []
@ -65,6 +62,32 @@ def _generate_layouts(keyboard):
return lines return lines
def _generate_keycodes(kb_info_json):
"""Generates keyboard level keycodes.
"""
if 'keycodes' not in kb_info_json:
return []
lines = []
lines.append('enum keyboard_keycodes {')
for index, item in enumerate(kb_info_json.get('keycodes')):
key = item["key"]
if index == 0:
lines.append(f' {key} = QK_KB_0,')
else:
lines.append(f' {key},')
lines.append('};')
for item in kb_info_json.get('keycodes', []):
key = item["key"]
for alias in item.get("aliases", []):
lines.append(f'#define {alias} {key}')
return lines
@cli.argument('-i', '--include', nargs='?', arg_only=True, help='Optional file to include') @cli.argument('-i', '--include', nargs='?', arg_only=True, help='Optional file to include')
@cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to') @cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to')
@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages") @cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages")
@ -73,8 +96,12 @@ def _generate_layouts(keyboard):
def generate_keyboard_h(cli): def generate_keyboard_h(cli):
"""Generates the keyboard.h file. """Generates the keyboard.h file.
""" """
# Build the info.json file
kb_info_json = info_json(cli.args.keyboard)
keyboard_h = cli.args.include keyboard_h = cli.args.include
dd_layouts = _generate_layouts(cli.args.keyboard) dd_layouts = _generate_layouts(cli.args.keyboard, kb_info_json)
dd_keycodes = _generate_keycodes(kb_info_json)
valid_config = dd_layouts or keyboard_h valid_config = dd_layouts or keyboard_h
# Build the layouts.h file. # Build the layouts.h file.
@ -87,6 +114,11 @@ def generate_keyboard_h(cli):
if keyboard_h: if keyboard_h:
keyboard_h_lines.append(f'#include "{Path(keyboard_h).name}"') keyboard_h_lines.append(f'#include "{Path(keyboard_h).name}"')
keyboard_h_lines.append('')
keyboard_h_lines.append('// Keycode content')
if dd_keycodes:
keyboard_h_lines.extend(dd_keycodes)
# Protect against poorly configured keyboards # Protect against poorly configured keyboards
if not valid_config: if not valid_config:
keyboard_h_lines.append('#error("<keyboard>.h is required unless your keyboard uses data-driven configuration. Please rename your keyboard\'s header file to <keyboard>.h")') keyboard_h_lines.append('#error("<keyboard>.h is required unless your keyboard uses data-driven configuration. Please rename your keyboard\'s header file to <keyboard>.h")')

View File

@ -103,6 +103,12 @@ def _validate(keyboard, info_data):
if layout_name not in layouts and layout_name not in layout_aliases: if layout_name not in layouts and layout_name not in layout_aliases:
_log_error(info_data, 'Claims to support community layout %s but no %s() macro found' % (layout, layout_name)) _log_error(info_data, 'Claims to support community layout %s but no %s() macro found' % (layout, layout_name))
# keycodes with length > 7 must have short forms for visualisation purposes
for decl in info_data.get('keycodes', []):
if len(decl["key"]) > 7:
if not decl.get("aliases", []):
_log_error(info_data, f'Keycode {decl["key"]} has no short form alias')
def info_json(keyboard): def info_json(keyboard):
"""Generate the info.json data for a specific keyboard. """Generate the info.json data for a specific keyboard.

View File

@ -25,6 +25,7 @@ __INCLUDES__
* This file was generated by qmk json2c. You may or may not want to * This file was generated by qmk json2c. You may or may not want to
* edit it directly. * edit it directly.
*/ */
__KEYCODE_OUTPUT_GOES_HERE__
const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
__KEYMAP_GOES_HERE__ __KEYMAP_GOES_HERE__
@ -123,6 +124,29 @@ def _generate_macros_function(keymap_json):
return macro_txt return macro_txt
def _generate_keycodes_function(keymap_json):
"""Generates keymap level keycodes.
"""
lines = []
lines.append('enum keymap_keycodes {')
for index, item in enumerate(keymap_json.get('keycodes', [])):
key = item["key"]
if index == 0:
lines.append(f' {key} = QK_USER_0,')
else:
lines.append(f' {key},')
lines.append('};')
for item in keymap_json.get('keycodes', []):
key = item["key"]
for alias in item.get("aliases", []):
lines.append(f'#define {alias} {key}')
return lines
def template_json(keyboard): def template_json(keyboard):
"""Returns a `keymap.json` template for a keyboard. """Returns a `keymap.json` template for a keyboard.
@ -317,6 +341,12 @@ def generate_c(keymap_json):
hostlang = f'#include "keymap_{keymap_json["host_language"]}.h"\n#include "sendstring_{keymap_json["host_language"]}.h"\n' hostlang = f'#include "keymap_{keymap_json["host_language"]}.h"\n#include "sendstring_{keymap_json["host_language"]}.h"\n'
new_keymap = new_keymap.replace('__INCLUDES__', hostlang) new_keymap = new_keymap.replace('__INCLUDES__', hostlang)
keycodes = ''
if 'keycodes' in keymap_json and keymap_json['keycodes'] is not None:
keycodes_txt = _generate_keycodes_function(keymap_json)
keycodes = '\n'.join(keycodes_txt)
new_keymap = new_keymap.replace('__KEYCODE_OUTPUT_GOES_HERE__', keycodes)
return new_keymap return new_keymap