diff options
Diffstat (limited to 'lib/python/qmk/cli/via2json.py')
-rwxr-xr-x | lib/python/qmk/cli/via2json.py | 145 |
1 files changed, 145 insertions, 0 deletions
diff --git a/lib/python/qmk/cli/via2json.py b/lib/python/qmk/cli/via2json.py new file mode 100755 index 0000000000..6edc9dfbe5 --- /dev/null +++ b/lib/python/qmk/cli/via2json.py @@ -0,0 +1,145 @@ +"""Generate a keymap.c from a configurator export. +""" +import json +import re + +from milc import cli + +import qmk.keyboard +import qmk.path +from qmk.info import info_json +from qmk.json_encoders import KeymapJSONEncoder +from qmk.commands import parse_configurator_json, dump_lines +from qmk.keymap import generate_json, list_keymaps, locate_keymap, parse_keymap_c + + +def _find_via_layout_macro(keyboard): + keymap_layout = None + if 'via' in list_keymaps(keyboard): + keymap_path = locate_keymap(keyboard, 'via') + if keymap_path.suffix == '.json': + keymap_layout = parse_configurator_json(keymap_path)['layout'] + else: + keymap_layout = parse_keymap_c(keymap_path)['layers'][0]['layout'] + return keymap_layout + + +def _convert_macros(via_macros): + via_macros = list(filter(lambda f: bool(f), via_macros)) + if len(via_macros) == 0: + return list() + split_regex = re.compile(r'(}\,)|(\,{)') + macros = list() + for via_macro in via_macros: + # Split VIA macro to its elements + macro = split_regex.split(via_macro) + # Remove junk elements (None, '},' and ',{') + macro = list(filter(lambda f: False if f in (None, '},', ',{') else True, macro)) + macro_data = list() + for m in macro: + if '{' in m or '}' in m: + # Found keycode(s) + keycodes = m.split(',') + # Remove whitespaces and curly braces from around keycodes + keycodes = list(map(lambda s: s.strip(' {}'), keycodes)) + # Remove the KC prefix + keycodes = list(map(lambda s: s.replace('KC_', ''), keycodes)) + macro_data.append({"action": "tap", "keycodes": keycodes}) + else: + # Found text + macro_data.append(m) + macros.append(macro_data) + + return macros + + +def _fix_macro_keys(keymap_data): + macro_no = re.compile(r'MACRO0?([0-9]{1,2})') + for i in range(0, len(keymap_data)): + for j in range(0, len(keymap_data[i])): + kc = keymap_data[i][j] + m = macro_no.match(kc) + if m: + keymap_data[i][j] = f'MACRO_{m.group(1)}' + return keymap_data + + +def _via_to_keymap(via_backup, keyboard_data, keymap_layout): + # Check if passed LAYOUT is correct + layout_data = keyboard_data['layouts'].get(keymap_layout) + if not layout_data: + cli.log.error(f'LAYOUT macro {keymap_layout} is not a valid one for keyboard {cli.args.keyboard}!') + exit(1) + + layout_data = layout_data['layout'] + sorting_hat = list() + for index, data in enumerate(layout_data): + sorting_hat.append([index, data['matrix']]) + + sorting_hat.sort(key=lambda k: (k[1][0], k[1][1])) + + pos = 0 + for row_num in range(0, keyboard_data['matrix_size']['rows']): + for col_num in range(0, keyboard_data['matrix_size']['cols']): + if pos >= len(sorting_hat) or sorting_hat[pos][1][0] != row_num or sorting_hat[pos][1][1] != col_num: + sorting_hat.insert(pos, [None, [row_num, col_num]]) + else: + sorting_hat.append([None, [row_num, col_num]]) + pos += 1 + + keymap_data = list() + for layer in via_backup['layers']: + pos = 0 + layer_data = list() + for key in layer: + if sorting_hat[pos][0] is not None: + layer_data.append([sorting_hat[pos][0], key]) + pos += 1 + layer_data.sort() + layer_data = [kc[1] for kc in layer_data] + keymap_data.append(layer_data) + + return keymap_data + + +@cli.argument('-o', '--output', arg_only=True, type=qmk.path.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('filename', type=qmk.path.FileType('r'), arg_only=True, help='VIA Backup JSON file') +@cli.argument('-kb', '--keyboard', type=qmk.keyboard.keyboard_folder, completer=qmk.keyboard.keyboard_completer, arg_only=True, required=True, help='The keyboard\'s name') +@cli.argument('-km', '--keymap', arg_only=True, default='via2json', help='The keymap\'s name') +@cli.argument('-l', '--layout', arg_only=True, help='The keymap\'s layout') +@cli.subcommand('Convert a VIA backup json to keymap.json format.') +def via2json(cli): + """Convert a VIA backup json to keymap.json format. + + This command uses the `qmk.keymap` module to generate a keymap.json from a VIA backup json. The generated keymap is written to stdout, or to a file if -o is provided. + """ + # Find appropriate layout macro + keymap_layout = cli.args.layout if cli.args.layout else _find_via_layout_macro(cli.args.keyboard) + if not keymap_layout: + cli.log.error(f"Couldn't find LAYOUT macro for keyboard {cli.args.keyboard}. Please specify it with the '-l' argument.") + exit(1) + + # Load the VIA backup json + with cli.args.filename.open('r') as fd: + via_backup = json.load(fd) + + # Generate keyboard metadata + keyboard_data = info_json(cli.args.keyboard) + + # Get keycode array + keymap_data = _via_to_keymap(via_backup, keyboard_data, keymap_layout) + + # Convert macros + macro_data = list() + if via_backup.get('macros'): + macro_data = _convert_macros(via_backup['macros']) + + # Replace VIA macro keys with JSON keymap ones + keymap_data = _fix_macro_keys(keymap_data) + + # Generate the keymap.json + keymap_json = generate_json(cli.args.keymap, cli.args.keyboard, keymap_layout, keymap_data, macro_data) + + keymap_lines = [json.dumps(keymap_json, cls=KeymapJSONEncoder)] + dump_lines(cli.args.output, keymap_lines, cli.args.quiet) |