importers.py 6.3 KB

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