summaryrefslogtreecommitdiff
path: root/lib/python/qmk/cli/via2json.py
blob: 6edc9dfbe5ed93f2cfe82c0a589a5203048a428d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
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)