at master 6.3 kB view raw
1"""Generate a keymap.c from a configurator export. 2""" 3import json 4import re 5 6from milc import cli 7 8import qmk.keyboard 9import qmk.path 10from qmk.info import info_json 11from qmk.json_encoders import KeymapJSONEncoder 12from qmk.commands import dump_lines 13from qmk.keymap import generate_json 14 15 16def _find_via_layout_macro(keyboard_data): 17 """Assume layout macro when only 1 is available 18 """ 19 layouts = list(keyboard_data['layouts'].keys()) 20 return layouts[0] if len(layouts) == 1 else None 21 22 23def _convert_macros(via_macros): 24 via_macros = list(filter(lambda f: bool(f), via_macros)) 25 if len(via_macros) == 0: 26 return list() 27 split_regex = re.compile(r'(}\,)|(\,{)') 28 macro_group_regex = re.compile(r'({.+?})') 29 macros = list() 30 for via_macro in via_macros: 31 # Split VIA macro to its elements 32 macro = split_regex.split(via_macro) 33 # Remove junk elements (None, '},' and ',{') 34 macro = list(filter(lambda f: False if f in (None, '},', ',{') else True, macro)) 35 macro_data = list() 36 for m in macro: 37 if '{' in m or '}' in m: 38 # Split macro groups 39 macro_groups = macro_group_regex.findall(m) 40 for macro_group in macro_groups: 41 # Remove whitespaces and curly braces from around group 42 macro_group = macro_group.strip(' {}') 43 44 macro_action = 'tap' 45 macro_keycodes = [] 46 47 if macro_group[0] == '+': 48 macro_action = 'down' 49 macro_keycodes.append(macro_group[1:]) 50 elif macro_group[0] == '-': 51 macro_action = 'up' 52 macro_keycodes.append(macro_group[1:]) 53 else: 54 macro_keycodes.extend(macro_group.split(',') if ',' in macro_group else [macro_group]) 55 56 # Remove the KC prefixes 57 macro_keycodes = list(map(lambda s: s.replace('KC_', ''), macro_keycodes)) 58 59 macro_data.append({"action": macro_action, "keycodes": macro_keycodes}) 60 else: 61 # Found text 62 macro_data.append(m) 63 macros.append(macro_data) 64 65 return macros 66 67 68def _fix_macro_keys(keymap_data): 69 macro_no = re.compile(r'MACRO0?\(([0-9]{1,2})\)') 70 for i in range(0, len(keymap_data)): 71 for j in range(0, len(keymap_data[i])): 72 kc = keymap_data[i][j] 73 m = macro_no.match(kc) 74 if m: 75 keymap_data[i][j] = f'MC_{m.group(1)}' 76 return keymap_data 77 78 79def _via_to_keymap(via_backup, keyboard_data, keymap_layout): 80 # Check if passed LAYOUT is correct 81 layout_data = keyboard_data['layouts'].get(keymap_layout) 82 if not layout_data: 83 cli.log.error(f'LAYOUT macro {keymap_layout} is not a valid one for keyboard {cli.args.keyboard}!') 84 return None 85 86 layout_data = layout_data['layout'] 87 sorting_hat = list() 88 for index, data in enumerate(layout_data): 89 sorting_hat.append([index, data['matrix']]) 90 91 sorting_hat.sort(key=lambda k: (k[1][0], k[1][1])) 92 93 pos = 0 94 for row_num in range(0, keyboard_data['matrix_size']['rows']): 95 for col_num in range(0, keyboard_data['matrix_size']['cols']): 96 if pos >= len(sorting_hat) or sorting_hat[pos][1][0] != row_num or sorting_hat[pos][1][1] != col_num: 97 sorting_hat.insert(pos, [None, [row_num, col_num]]) 98 else: 99 sorting_hat.append([None, [row_num, col_num]]) 100 pos += 1 101 102 keymap_data = list() 103 for layer in via_backup['layers']: 104 pos = 0 105 layer_data = list() 106 for key in layer: 107 if sorting_hat[pos][0] is not None: 108 layer_data.append([sorting_hat[pos][0], key]) 109 pos += 1 110 layer_data.sort() 111 layer_data = [kc[1] for kc in layer_data] 112 keymap_data.append(layer_data) 113 114 return keymap_data 115 116 117@cli.argument('-o', '--output', arg_only=True, type=qmk.path.normpath, help='File to write to') 118@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages") 119@cli.argument('filename', type=qmk.path.FileType('r'), arg_only=True, help='VIA Backup JSON file') 120@cli.argument('-kb', '--keyboard', type=qmk.keyboard.keyboard_folder, completer=qmk.keyboard.keyboard_completer, arg_only=True, required=True, help='The keyboard\'s name') 121@cli.argument('-km', '--keymap', arg_only=True, default='via2json', help='The keymap\'s name') 122@cli.argument('-l', '--layout', arg_only=True, help='The keymap\'s layout') 123@cli.subcommand('Convert a VIA backup json to keymap.json format.') 124def via2json(cli): 125 """Convert a VIA backup json to keymap.json format. 126 127 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. 128 """ 129 # Load the VIA backup json 130 with cli.args.filename.open('r') as fd: 131 via_backup = json.load(fd) 132 133 keyboard_data = info_json(cli.args.keyboard) 134 135 # Find appropriate layout macro 136 keymap_layout = cli.args.layout if cli.args.layout else _find_via_layout_macro(keyboard_data) 137 if not keymap_layout: 138 cli.log.error(f"Couldn't find LAYOUT macro for keyboard {cli.args.keyboard}. Please specify it with the '-l' argument.") 139 return False 140 141 # Get keycode array 142 keymap_data = _via_to_keymap(via_backup, keyboard_data, keymap_layout) 143 if not keymap_data: 144 cli.log.error(f'Could not extract valid keycode data from VIA backup matching keyboard {cli.args.keyboard}!') 145 return False 146 147 # Convert macros 148 macro_data = list() 149 if via_backup.get('macros'): 150 macro_data = _convert_macros(via_backup['macros']) 151 152 # Replace VIA macro keys with JSON keymap ones 153 keymap_data = _fix_macro_keys(keymap_data) 154 155 # Generate the keymap.json 156 keymap_json = generate_json(cli.args.keymap, cli.args.keyboard, keymap_layout, keymap_data, macro_data) 157 158 keymap_lines = [json.dumps(keymap_json, cls=KeymapJSONEncoder, sort_keys=True)] 159 dump_lines(cli.args.output, keymap_lines, cli.args.quiet)