importers.py (6730B)
1 from dotty_dict import dotty 2 from datetime import date 3 from pathlib import Path 4 import json 5 6 from qmk.git import git_get_username 7 from qmk.json_schema import validate 8 from qmk.path import keyboard, keymaps 9 from qmk.constants import MCU2BOOTLOADER, LEGACY_KEYCODES 10 from qmk.json_encoders import InfoJSONEncoder, KeymapJSONEncoder 11 from qmk.json_schema import deep_update, json_load 12 13 TEMPLATE = Path('data/templates/keyboard/') 14 15 16 def replace_placeholders(src, dest, tokens): 17 """Replaces the given placeholders in each template file. 18 """ 19 content = src.read_text() 20 for key, value in tokens.items(): 21 content = content.replace(f'%{key}%', value) 22 23 dest.write_text(content) 24 25 26 def _gen_dummy_keymap(name, info_data): 27 # Pick the first layout macro and just dump in KC_NOs or something? 28 (layout_name, layout_data), *_ = info_data["layouts"].items() 29 layout_length = len(layout_data["layout"]) 30 31 keymap_data = { 32 "keyboard": name, 33 "layout": layout_name, 34 "layers": [["KC_NO" for _ in range(0, layout_length)]], 35 } 36 37 return keymap_data 38 39 40 def _extract_kbfirmware_layout(kbf_data): 41 layout = [] 42 for key in kbf_data['keyboard.keys']: 43 item = { 44 'matrix': [key['row'], key['col']], 45 'x': key['state']['x'], 46 'y': key['state']['y'], 47 } 48 if key['state']['w'] != 1: 49 item['w'] = key['state']['w'] 50 if key['state']['h'] != 1: 51 item['h'] = key['state']['h'] 52 layout.append(item) 53 54 return layout 55 56 57 def _extract_kbfirmware_keymap(kbf_data): 58 keymap_data = { 59 'keyboard': kbf_data['keyboard.settings.name'].lower(), 60 'layout': 'LAYOUT', 61 'layers': [], 62 } 63 64 for i in range(15): 65 layer = [] 66 for key in kbf_data['keyboard.keys']: 67 keycode = key['keycodes'][i]['id'] 68 keycode = LEGACY_KEYCODES.get(keycode, keycode) 69 if '()' in keycode: 70 fields = key['keycodes'][i]['fields'] 71 keycode = f'{keycode.split(")")[0]}{",".join(map(str, fields))})' 72 layer.append(keycode) 73 if set(layer) == {'KC_TRNS'}: 74 break 75 keymap_data['layers'].append(layer) 76 77 return keymap_data 78 79 80 def import_keymap(keymap_data): 81 # Validate to ensure we don't have to deal with bad data - handles stdin/file 82 validate(keymap_data, 'qmk.keymap.v1') 83 84 kb_name = keymap_data['keyboard'] 85 km_name = keymap_data['keymap'] 86 87 km_folder = keymaps(kb_name)[0] / km_name 88 keyboard_keymap = km_folder / 'keymap.json' 89 90 # This is the deepest folder in the expected tree 91 keyboard_keymap.parent.mkdir(parents=True, exist_ok=True) 92 93 # Dump out all those lovely files 94 keyboard_keymap.write_text(json.dumps(keymap_data, cls=KeymapJSONEncoder, sort_keys=True)) 95 96 return (kb_name, km_name) 97 98 99 def import_keyboard(info_data, keymap_data=None): 100 # Validate to ensure we don't have to deal with bad data - handles stdin/file 101 validate(info_data, 'qmk.api.keyboard.v1') 102 103 # And validate some more as everything is optional 104 if not all(key in info_data for key in ['keyboard_name', 'layouts']): 105 raise ValueError('invalid json config') 106 107 kb_name = info_data['keyboard_name'] 108 109 # bail 110 kb_folder = keyboard(kb_name) 111 if kb_folder.exists(): 112 raise ValueError(f'Keyboard {{fg_cyan}}{kb_name}{{fg_reset}} already exists! Please choose a different name.') 113 114 if not keymap_data: 115 # TODO: if supports community then grab that instead 116 keymap_data = _gen_dummy_keymap(kb_name, info_data) 117 118 keyboard_json = kb_folder / 'keyboard.json' 119 keyboard_keymap = kb_folder / 'keymaps' / 'default' / 'keymap.json' 120 121 # begin with making the deepest folder in the tree 122 keyboard_keymap.parent.mkdir(parents=True, exist_ok=True) 123 124 user_name = git_get_username() 125 if not user_name: 126 user_name = 'TODO' 127 128 tokens = { # Comment here is to force multiline formatting 129 'YEAR': str(date.today().year), 130 'KEYBOARD': kb_name, 131 'USER_NAME': user_name, 132 'REAL_NAME': user_name, 133 } 134 135 # Dump out all those lovely files 136 for file in list(TEMPLATE.iterdir()): 137 replace_placeholders(file, kb_folder / file.name, tokens) 138 139 temp = json_load(keyboard_json) 140 deep_update(temp, info_data) 141 142 keyboard_json.write_text(json.dumps(temp, cls=InfoJSONEncoder, sort_keys=True)) 143 keyboard_keymap.write_text(json.dumps(keymap_data, cls=KeymapJSONEncoder, sort_keys=True)) 144 145 return kb_name 146 147 148 def import_kbfirmware(kbfirmware_data): 149 kbf_data = dotty(kbfirmware_data) 150 151 diode_direction = ["COL2ROW", "ROW2COL"][kbf_data['keyboard.settings.diodeDirection']] 152 mcu = ["atmega32u2", "atmega32u4", "at90usb1286"][kbf_data['keyboard.controller']] 153 bootloader = MCU2BOOTLOADER.get(mcu, "custom") 154 155 layout = _extract_kbfirmware_layout(kbf_data) 156 keymap_data = _extract_kbfirmware_keymap(kbf_data) 157 158 # convert to d/d info.json 159 info_data = dotty({ 160 "keyboard_name": kbf_data['keyboard.settings.name'].lower(), 161 "processor": mcu, 162 "bootloader": bootloader, 163 "diode_direction": diode_direction, 164 "matrix_pins": { 165 "cols": kbf_data['keyboard.pins.col'], 166 "rows": kbf_data['keyboard.pins.row'], 167 }, 168 "layouts": { 169 "LAYOUT": { 170 "layout": layout, 171 } 172 } 173 }) 174 175 if kbf_data['keyboard.pins.num'] or kbf_data['keyboard.pins.caps'] or kbf_data['keyboard.pins.scroll']: 176 if kbf_data['keyboard.pins.num']: 177 info_data['indicators.num_lock'] = kbf_data['keyboard.pins.num'] 178 if kbf_data['keyboard.pins.caps']: 179 info_data['indicators.caps_lock'] = kbf_data['keyboard.pins.caps'] 180 if kbf_data['keyboard.pins.scroll']: 181 info_data['indicators.scroll_lock'] = kbf_data['keyboard.pins.scroll'] 182 183 if kbf_data['keyboard.pins.rgb']: 184 info_data['rgblight.animations'] = { # Comment here is to force multiline formatting 185 "breathing": True, 186 "rainbow_mood": True, 187 "rainbow_swirl": True, 188 "snake": True, 189 "knight": True, 190 "static_gradient": True, 191 "twinkle": True 192 } 193 info_data['rgblight.led_count'] = kbf_data['keyboard.settings.rgbNum'] 194 info_data['ws2812.pin'] = kbf_data['keyboard.pins.rgb'] 195 196 if kbf_data['keyboard.pins.led']: 197 info_data['backlight.levels'] = kbf_data['keyboard.settings.backlightLevels'] 198 info_data['backlight.pin'] = kbf_data['keyboard.pins.led'] 199 200 # delegate as if it were a regular keyboard import 201 return import_keyboard(info_data.to_dict(), keymap_data)