summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoel Challis <git@zvecr.com>2023-03-27 20:01:07 +0100
committerGitHub <noreply@github.com>2023-03-27 20:01:07 +0100
commit79b0f9168eafef1642f3223500197bf630a2433f (patch)
treef73f4b61d1e635f7aac380c99bf98571e1eeb2f9
parente35bb8ebfad8b8ce40de98e9607fb6dbf8d8fff4 (diff)
Custom keycodes in JSON (#19925)
-rw-r--r--data/schemas/definitions.jsonschema32
-rw-r--r--data/schemas/keyboard.jsonschema1
-rw-r--r--data/schemas/keycodes.jsonschema10
-rw-r--r--data/schemas/keymap.jsonschema1
-rw-r--r--keyboards/handwired/pytest/basic/keymaps/dd_keycodes/keymap.json12
-rwxr-xr-xlib/python/qmk/cli/generate/api.py6
-rwxr-xr-xlib/python/qmk/cli/generate/keyboard_h.py44
-rw-r--r--lib/python/qmk/info.py6
-rw-r--r--lib/python/qmk/keymap.py30
9 files changed, 129 insertions, 13 deletions
diff --git a/data/schemas/definitions.jsonschema b/data/schemas/definitions.jsonschema
index 94a94157c0..b9c64a55ec 100644
--- a/data/schemas/definitions.jsonschema
+++ b/data/schemas/definitions.jsonschema
@@ -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"}
diff --git a/data/schemas/keyboard.jsonschema b/data/schemas/keyboard.jsonschema
index aa1be6efa7..18b3514aa5 100644
--- a/data/schemas/keyboard.jsonschema
+++ b/data/schemas/keyboard.jsonschema
@@ -259,6 +259,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"}
diff --git a/data/schemas/keycodes.jsonschema b/data/schemas/keycodes.jsonschema
index 77a8347b3b..df6ce95a83 100644
--- a/data/schemas/keycodes.jsonschema
+++ b/data/schemas/keycodes.jsonschema
@@ -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": {
@@ -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"
],
diff --git a/data/schemas/keymap.jsonschema b/data/schemas/keymap.jsonschema
index 73aa7c5c22..7233e896e9 100644
--- a/data/schemas/keymap.jsonschema
+++ b/data/schemas/keymap.jsonschema
@@ -67,6 +67,7 @@
}
}
},
+ "keycodes": {"$ref": "qmk.definitions.v1#/keycode_decl_array"},
"config": {"$ref": "qmk.keyboard.v1"},
"notes": {
"type": "string"
diff --git a/keyboards/handwired/pytest/basic/keymaps/dd_keycodes/keymap.json b/keyboards/handwired/pytest/basic/keymaps/dd_keycodes/keymap.json
new file mode 100644
index 0000000000..a64a65a80a
--- /dev/null
+++ b/keyboards/handwired/pytest/basic/keymaps/dd_keycodes/keymap.json
@@ -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
+}
diff --git a/lib/python/qmk/cli/generate/api.py b/lib/python/qmk/cli/generate/api.py
index 11d4616199..cfea3f3946 100755
--- a/lib/python/qmk/cli/generate/api.py
+++ b/lib/python/qmk/cli/generate/api.py
@@ -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)
diff --git a/lib/python/qmk/cli/generate/keyboard_h.py b/lib/python/qmk/cli/generate/keyboard_h.py
index 152921bdce..fa4036e39a 100755
--- a/lib/python/qmk/cli/generate/keyboard_h.py
+++ b/lib/python/qmk/cli/generate/keyboard_h.py
@@ -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
-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 []
@@ -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")
@@ -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.
@@ -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")')
diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py
index b7ee055eef..f4dcc507ef 100644
--- a/lib/python/qmk/info.py
+++ b/lib/python/qmk/info.py
@@ -103,6 +103,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.
diff --git a/lib/python/qmk/keymap.py b/lib/python/qmk/keymap.py
index dddf6449a7..8ae8a5ed19 100644
--- a/lib/python/qmk/keymap.py
+++ b/lib/python/qmk/keymap.py
@@ -25,6 +25,7 @@ __INCLUDES__
* 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__
@@ -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.
@@ -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