Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom keycodes in JSON #19925

Merged
merged 2 commits into from
Mar 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions data/schemas/definitions.jsonschema
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,38 @@
"type": "string",
"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": {
"type": "array",
"items": {"$ref": "#/mcu_pin"}
Expand Down
1 change: 1 addition & 0 deletions data/schemas/keyboard.jsonschema
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@
"on_state": {"$ref": "qmk.definitions.v1#/bit"}
}
},
"keycodes": {"$ref": "qmk.definitions.v1#/keycode_decl_array"},
"layout_aliases": {
"type": "object",
"additionalProperties": {"$ref": "qmk.definitions.v1#/layout_macro"}
Expand Down
10 changes: 3 additions & 7 deletions data/schemas/keycodes.jsonschema
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,7 @@
"type": "string",
"minLength": 2,
"maxLength": 50,
"pattern": "^[A-Zs_0-9]*$"
},
"hex_number_4d": {
"type": "string",
"pattern": "^0x[0-9A-F]{4}$"
"pattern": "^[A-Z][A-Zs_0-9]*$"
}
},
"properties": {
Expand All @@ -34,10 +30,10 @@
"keycodes": {
"type": "object",
"propertyNames": {
"$ref": "#/definitions/hex_number_4d"
"$ref": "qmk.definitions.v1#/hex_number_4d"
},
"additionalProperties": {
"type": "object",
"type": "object", // use 'qmk.definitions.v1#/keycode_decl' when problem keycodes are removed
"required": [
"key"
],
Expand Down
1 change: 1 addition & 0 deletions data/schemas/keymap.jsonschema
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
}
}
},
"keycodes": {"$ref": "qmk.definitions.v1#/keycode_decl_array"},
"config": {"$ref": "qmk.keyboard.v1"},
"notes": {
"type": "string"
Expand Down
12 changes: 12 additions & 0 deletions keyboards/handwired/pytest/basic/keymaps/dd_keycodes/keymap.json
Original file line number Diff line number Diff line change
@@ -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
}
6 changes: 6 additions & 0 deletions lib/python/qmk/cli/generate/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ def _filtered_copy(src, dst):
dst.write_text(json.dumps(data), encoding='utf-8')
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)


Expand Down
44 changes: 38 additions & 6 deletions lib/python/qmk/cli/generate/keyboard_h.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,9 @@
from qmk.constants import COL_LETTERS, ROW_LETTERS, GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE


def _generate_layouts(keyboard):
"""Generates the layouts.h file.
def _generate_layouts(keyboard, kb_info_json):
"""Generates the layouts macros.
"""
# Build the info.json file
kb_info_json = info_json(keyboard)

if 'matrix_size' not in kb_info_json:
cli.log.error(f'{keyboard}: Invalid matrix config.')
return []
Expand Down Expand Up @@ -65,6 +62,32 @@ def _generate_layouts(keyboard):
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('-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")
Expand All @@ -73,8 +96,12 @@ def _generate_layouts(keyboard):
def generate_keyboard_h(cli):
"""Generates the keyboard.h file.
"""
# Build the info.json file
kb_info_json = info_json(cli.args.keyboard)

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

# Build the layouts.h file.
Expand All @@ -87,6 +114,11 @@ def generate_keyboard_h(cli):
if keyboard_h:
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
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")')
Expand Down
6 changes: 6 additions & 0 deletions lib/python/qmk/info.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@ def _validate(keyboard, info_data):
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))

# 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):
"""Generate the info.json data for a specific keyboard.
Expand Down
30 changes: 30 additions & 0 deletions lib/python/qmk/keymap.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
* This file was generated by qmk json2c. You may or may not want to
* edit it directly.
*/
__KEYCODE_OUTPUT_GOES_HERE__

const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
__KEYMAP_GOES_HERE__
Expand Down Expand Up @@ -123,6 +124,29 @@ def _generate_macros_function(keymap_json):
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):
"""Returns a `keymap.json` template for a keyboard.

Expand Down Expand Up @@ -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'
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


Expand Down