From 7662c4b34fe23255cfc41378b9cd115184ba2bb9 Mon Sep 17 00:00:00 2001 From: Nick Brassel Date: Sat, 14 Jan 2023 15:46:02 +1100 Subject: [PATCH 1/6] Generate encodermap output from keymap.json. --- data/schemas/keymap.jsonschema | 11 +++ lib/python/qmk/keymap.py | 171 ++++++++++++++++++++------------- 2 files changed, 114 insertions(+), 68 deletions(-) diff --git a/data/schemas/keymap.jsonschema b/data/schemas/keymap.jsonschema index 2fcd5f8a51a0..41cdef23b99a 100644 --- a/data/schemas/keymap.jsonschema +++ b/data/schemas/keymap.jsonschema @@ -21,6 +21,17 @@ "items": {"type": "string"} } }, + "encoders": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "array", + "minItems": 2, + "maxItems": 2, + } + } + }, "macros": { "type": "array", "items": { diff --git a/lib/python/qmk/keymap.py b/lib/python/qmk/keymap.py index ce518e379d6a..a8bc8149d2be 100644 --- a/lib/python/qmk/keymap.py +++ b/lib/python/qmk/keymap.py @@ -30,9 +30,99 @@ __KEYMAP_GOES_HERE__ }; +#if defined(ENCODER_ENABLE) && defined(ENCODER_MAP_ENABLE) +const uint16_t PROGMEM encoder_map[][NUM_ENCODERS][2] = { +__ENCODER_MAP_GOES_HERE__ +}; +#endif // defined(ENCODER_ENABLE) && defined(ENCODER_MAP_ENABLE) + +__MACRO_OUTPUT_GOES_HERE__ + """ +def _generate_keymap_table(keymap_json): + lines = [] + for layer_num, layer in enumerate(keymap_json['layers']): + if layer_num != 0: + lines[-1] = lines[-1] + ',' + layer = map(_strip_any, layer) + layer_keys = ', '.join(layer) + lines.append('\t[%s] = %s(%s)' % (layer_num, keymap_json['layout'], layer_keys)) + return lines + + +def _generate_encodermap_table(keymap_json): + lines = [] + for layer_num, layer in enumerate(keymap_json['encoders']): + if layer_num != 0: + lines[-1] = lines[-1] + ',' + encoder_keycode_txt = ', '.join([f'ENCODER_CCW_CW({_strip_any(e[0])}, {_strip_any(e[1])})' for e in layer]) + lines.append('\t[%s] = {%s}' % (layer_num, encoder_keycode_txt)) + return lines + + +def _generate_macros_function(keymap_json): + 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 QK_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('') + return macro_txt + + def template_json(keyboard): """Returns a `keymap.json` template for a keyboard. @@ -206,78 +296,23 @@ def generate_c(keymap_json): A sequence of strings containing macros to implement for this keyboard. """ new_keymap = template_c(keymap_json['keyboard']) - layer_txt = [] - - 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, keymap_json['layout'], layer_keys)) - + layer_txt = _generate_keymap_table(keymap_json) 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 QK_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('') + if keymap_json.get('encoders'): + encoder_txt = _generate_encodermap_table(keymap_json) + encodermap = '\n'.join(encoder_txt) + new_keymap = new_keymap.replace('__ENCODER_MAP_GOES_HERE__', encodermap) + else: + new_keymap = new_keymap.replace('__ENCODER_MAP_GOES_HERE__', '') - new_keymap = '\n'.join((new_keymap, *macro_txt)) + if keymap_json.get('macros'): + macro_txt = _generate_macros_function(keymap_json) + macros = '\n'.join(macro_txt) + new_keymap = new_keymap.replace('__MACRO_OUTPUT_GOES_HERE__', macros) + else: + new_keymap = new_keymap.replace('__MACRO_OUTPUT_GOES_HERE__', '') 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') From e9a2e76dd75d43135f9629b13192a0a0c5dbb309 Mon Sep 17 00:00:00 2001 From: Nick Brassel Date: Sun, 22 Jan 2023 10:24:32 +1100 Subject: [PATCH 2/6] Objects not arrays --- data/schemas/keymap.jsonschema | 8 +++++--- lib/python/qmk/keymap.py | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/data/schemas/keymap.jsonschema b/data/schemas/keymap.jsonschema index 41cdef23b99a..3d21f6ce9369 100644 --- a/data/schemas/keymap.jsonschema +++ b/data/schemas/keymap.jsonschema @@ -26,9 +26,11 @@ "items": { "type": "array", "items": { - "type": "array", - "minItems": 2, - "maxItems": 2, + "type": "object", + "properties": { + "ccw": {"type": "string"}, + "cw": {"type": "string"} + } } } }, diff --git a/lib/python/qmk/keymap.py b/lib/python/qmk/keymap.py index a8bc8149d2be..9db112a53017 100644 --- a/lib/python/qmk/keymap.py +++ b/lib/python/qmk/keymap.py @@ -57,7 +57,7 @@ def _generate_encodermap_table(keymap_json): for layer_num, layer in enumerate(keymap_json['encoders']): if layer_num != 0: lines[-1] = lines[-1] + ',' - encoder_keycode_txt = ', '.join([f'ENCODER_CCW_CW({_strip_any(e[0])}, {_strip_any(e[1])})' for e in layer]) + encoder_keycode_txt = ', '.join([f'ENCODER_CCW_CW({_strip_any(e["ccw"])}, {_strip_any(e["cw"])})' for e in layer]) lines.append('\t[%s] = {%s}' % (layer_num, encoder_keycode_txt)) return lines From 9e4c1619dfc13211e4642b60bb448727c265147c Mon Sep 17 00:00:00 2001 From: Nick Brassel Date: Sun, 22 Jan 2023 10:45:13 +1100 Subject: [PATCH 3/6] Update lib/python/qmk/keymap.py Co-authored-by: Joel Challis --- lib/python/qmk/keymap.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/python/qmk/keymap.py b/lib/python/qmk/keymap.py index 9db112a53017..7873ac443c6f 100644 --- a/lib/python/qmk/keymap.py +++ b/lib/python/qmk/keymap.py @@ -300,12 +300,11 @@ def generate_c(keymap_json): keymap = '\n'.join(layer_txt) new_keymap = new_keymap.replace('__KEYMAP_GOES_HERE__', keymap) - if keymap_json.get('encoders'): + encodermap = '' + if 'encoders' in keymap_json: encoder_txt = _generate_encodermap_table(keymap_json) encodermap = '\n'.join(encoder_txt) - new_keymap = new_keymap.replace('__ENCODER_MAP_GOES_HERE__', encodermap) - else: - new_keymap = new_keymap.replace('__ENCODER_MAP_GOES_HERE__', '') + new_keymap = new_keymap.replace('__ENCODER_MAP_GOES_HERE__', encodermap) if keymap_json.get('macros'): macro_txt = _generate_macros_function(keymap_json) From 5f5f2fc99a6f310d279beed5ec9344ffdbbecd11 Mon Sep 17 00:00:00 2001 From: Nick Brassel Date: Sun, 22 Jan 2023 10:47:09 +1100 Subject: [PATCH 4/6] More mechanical changes. --- lib/python/qmk/keymap.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/lib/python/qmk/keymap.py b/lib/python/qmk/keymap.py index 7873ac443c6f..d54347a41956 100644 --- a/lib/python/qmk/keymap.py +++ b/lib/python/qmk/keymap.py @@ -306,17 +306,16 @@ def generate_c(keymap_json): encodermap = '\n'.join(encoder_txt) new_keymap = new_keymap.replace('__ENCODER_MAP_GOES_HERE__', encodermap) - if keymap_json.get('macros'): + macros = '' + if 'macros' in keymap_json: macro_txt = _generate_macros_function(keymap_json) macros = '\n'.join(macro_txt) - new_keymap = new_keymap.replace('__MACRO_OUTPUT_GOES_HERE__', macros) - else: - new_keymap = new_keymap.replace('__MACRO_OUTPUT_GOES_HERE__', '') + new_keymap = new_keymap.replace('__MACRO_OUTPUT_GOES_HERE__', macros) - 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__', '') + hostlang = '' + if 'host_language' in 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) return new_keymap From ca11fd0334895fb42f8c4b1558f176b3b335c43b Mon Sep 17 00:00:00 2001 From: Nick Brassel Date: Sun, 22 Jan 2023 11:02:58 +1100 Subject: [PATCH 5/6] not None --- lib/python/qmk/keymap.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/python/qmk/keymap.py b/lib/python/qmk/keymap.py index d54347a41956..dddf6449a7bc 100644 --- a/lib/python/qmk/keymap.py +++ b/lib/python/qmk/keymap.py @@ -301,19 +301,19 @@ def generate_c(keymap_json): new_keymap = new_keymap.replace('__KEYMAP_GOES_HERE__', keymap) encodermap = '' - if 'encoders' in keymap_json: + if 'encoders' in keymap_json and keymap_json['encoders'] is not None: encoder_txt = _generate_encodermap_table(keymap_json) encodermap = '\n'.join(encoder_txt) new_keymap = new_keymap.replace('__ENCODER_MAP_GOES_HERE__', encodermap) macros = '' - if 'macros' in keymap_json: + if 'macros' in keymap_json and keymap_json['macros'] is not None: macro_txt = _generate_macros_function(keymap_json) macros = '\n'.join(macro_txt) new_keymap = new_keymap.replace('__MACRO_OUTPUT_GOES_HERE__', macros) hostlang = '' - if 'host_language' in keymap_json: + if 'host_language' in keymap_json and keymap_json['host_language'] is not None: 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) From c2703f7cd4dd2ffa5f3412fbcfe434f013ef8988 Mon Sep 17 00:00:00 2001 From: Nick Brassel Date: Mon, 23 Jan 2023 11:21:48 +1100 Subject: [PATCH 6/6] Update data/schemas/keymap.jsonschema Co-authored-by: Joel Challis --- data/schemas/keymap.jsonschema | 1 + 1 file changed, 1 insertion(+) diff --git a/data/schemas/keymap.jsonschema b/data/schemas/keymap.jsonschema index 3d21f6ce9369..73aa7c5c2226 100644 --- a/data/schemas/keymap.jsonschema +++ b/data/schemas/keymap.jsonschema @@ -27,6 +27,7 @@ "type": "array", "items": { "type": "object", + "required": ["ccw", "cw"], "properties": { "ccw": {"type": "string"}, "cw": {"type": "string"}