qmk_firmware

QMK firmware for my keyboards (Corne, Sweep Ferris) and trackball (Ploopy Adept)
Log | Files | Refs | Submodules | LICENSE

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)