diff options
Diffstat (limited to 'lib/python/qmk')
| -rw-r--r-- | lib/python/qmk/cli/__init__.py | 1 | ||||
| -rwxr-xr-x | lib/python/qmk/cli/via2json.py | 145 | ||||
| -rwxr-xr-x | lib/python/qmk/json_encoders.py | 8 | ||||
| -rw-r--r-- | lib/python/qmk/keymap.py | 7 | 
4 files changed, 159 insertions, 2 deletions
| diff --git a/lib/python/qmk/cli/__init__.py b/lib/python/qmk/cli/__init__.py index c51eece955..5f65e677e5 100644 --- a/lib/python/qmk/cli/__init__.py +++ b/lib/python/qmk/cli/__init__.py @@ -69,6 +69,7 @@ subcommands = [      'qmk.cli.new.keymap',      'qmk.cli.pyformat',      'qmk.cli.pytest', +    'qmk.cli.via2json',  ] 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 + + [email protected]('-o', '--output', arg_only=True, type=qmk.path.normpath, help='File to write to') [email protected]('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages") [email protected]('filename', type=qmk.path.FileType('r'), arg_only=True, help='VIA Backup JSON file') [email protected]('-kb', '--keyboard', type=qmk.keyboard.keyboard_folder, completer=qmk.keyboard.keyboard_completer, arg_only=True, required=True, help='The keyboard\'s name') [email protected]('-km', '--keymap', arg_only=True, default='via2json', help='The keymap\'s name') [email protected]('-l', '--layout', arg_only=True, help='The keymap\'s layout') [email protected]('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) diff --git a/lib/python/qmk/json_encoders.py b/lib/python/qmk/json_encoders.py index 72e91973a3..40a5c1dea8 100755 --- a/lib/python/qmk/json_encoders.py +++ b/lib/python/qmk/json_encoders.py @@ -146,7 +146,13 @@ class KeymapJSONEncoder(QMKJSONEncoder):                  if key == 'JSON_NEWLINE':                      layer.append([])                  else: -                    layer[-1].append(f'"{key}"') +                    if isinstance(key, dict): +                        # We have a macro + +                        # TODO: Add proper support for nicely formatting keymap.json macros +                        layer[-1].append(f'{self.encode(key)}') +                    else: +                        layer[-1].append(f'"{key}"')              layer = [f"{self.indent_str*indent_level}{', '.join(row)}" for row in layer] diff --git a/lib/python/qmk/keymap.py b/lib/python/qmk/keymap.py index 00b5a78a5a..ca5be0959b 100644 --- a/lib/python/qmk/keymap.py +++ b/lib/python/qmk/keymap.py @@ -158,7 +158,7 @@ def is_keymap_dir(keymap, c=True, json=True, additional_files=None):              return True -def generate_json(keymap, keyboard, layout, layers): +def generate_json(keymap, keyboard, layout, layers, macros=None):      """Returns a `keymap.json` for the specified keyboard, layout, and layers.      Args: @@ -173,11 +173,16 @@ def generate_json(keymap, keyboard, layout, layers):          layers              An array of arrays describing the keymap. Each item in the inner array should be a string that is a valid QMK keycode. + +        macros +            A sequence of strings containing macros to implement for this keyboard.      """      new_keymap = template_json(keyboard)      new_keymap['keymap'] = keymap      new_keymap['layout'] = layout      new_keymap['layers'] = layers +    if macros: +        new_keymap['macros'] = macros      return new_keymap | 
