You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

131 lines
3.7 KiB

  1. """Functions that help us work with keyboards.
  2. """
  3. from array import array
  4. from math import ceil
  5. from pathlib import Path
  6. import os
  7. from glob import glob
  8. from qmk.c_parse import parse_config_h_file
  9. from qmk.makefile import parse_rules_mk_file
  10. base_path = os.path.join(os.getcwd(), "keyboards") + os.path.sep
  11. def _find_name(path):
  12. """Determine the keyboard name by stripping off the base_path and rules.mk.
  13. """
  14. return path.replace(base_path, "").replace(os.path.sep + "rules.mk", "")
  15. def list_keyboards():
  16. """Returns a list of all keyboards.
  17. """
  18. # We avoid pathlib here because this is performance critical code.
  19. kb_wildcard = os.path.join(base_path, "**", "rules.mk")
  20. paths = [path for path in glob(kb_wildcard, recursive=True) if 'keymaps' not in path]
  21. return sorted(map(_find_name, paths))
  22. def config_h(keyboard):
  23. """Parses all the config.h files for a keyboard.
  24. Args:
  25. keyboard: name of the keyboard
  26. Returns:
  27. a dictionary representing the content of the entire config.h tree for a keyboard
  28. """
  29. config = {}
  30. cur_dir = Path('keyboards')
  31. rules = rules_mk(keyboard)
  32. keyboard = Path(rules['DEFAULT_FOLDER'] if 'DEFAULT_FOLDER' in rules else keyboard)
  33. for dir in keyboard.parts:
  34. cur_dir = cur_dir / dir
  35. config = {**config, **parse_config_h_file(cur_dir / 'config.h')}
  36. return config
  37. def rules_mk(keyboard):
  38. """Get a rules.mk for a keyboard
  39. Args:
  40. keyboard: name of the keyboard
  41. Returns:
  42. a dictionary representing the content of the entire rules.mk tree for a keyboard
  43. """
  44. keyboard = Path(keyboard)
  45. cur_dir = Path('keyboards')
  46. rules = parse_rules_mk_file(cur_dir / keyboard / 'rules.mk')
  47. if 'DEFAULT_FOLDER' in rules:
  48. keyboard = Path(rules['DEFAULT_FOLDER'])
  49. for i, dir in enumerate(keyboard.parts):
  50. cur_dir = cur_dir / dir
  51. rules = parse_rules_mk_file(cur_dir / 'rules.mk', rules)
  52. return rules
  53. def render_layout(layout_data, key_labels=None):
  54. """Renders a single layout.
  55. """
  56. textpad = [array('u', ' ' * 200) for x in range(50)]
  57. for key in layout_data:
  58. x = ceil(key.get('x', 0) * 4)
  59. y = ceil(key.get('y', 0) * 3)
  60. w = ceil(key.get('w', 1) * 4)
  61. h = ceil(key.get('h', 1) * 3)
  62. if key_labels:
  63. label = key_labels.pop(0)
  64. if label.startswith('KC_'):
  65. label = label[3:]
  66. else:
  67. label = key.get('label', '')
  68. label_len = w - 2
  69. label_leftover = label_len - len(label)
  70. if len(label) > label_len:
  71. label = label[:label_len]
  72. label_blank = ' ' * label_len
  73. label_border = '' * label_len
  74. label_middle = label + ' '*label_leftover # noqa: yapf insists there be no whitespace around *
  75. top_line = array('u', '' + label_border + '')
  76. lab_line = array('u', '' + label_middle + '')
  77. mid_line = array('u', '' + label_blank + '')
  78. bot_line = array('u', '' + label_border + "")
  79. textpad[y][x:x + w] = top_line
  80. textpad[y + 1][x:x + w] = lab_line
  81. for i in range(h - 3):
  82. textpad[y + i + 2][x:x + w] = mid_line
  83. textpad[y + h - 1][x:x + w] = bot_line
  84. lines = []
  85. for line in textpad:
  86. if line.tounicode().strip():
  87. lines.append(line.tounicode().rstrip())
  88. return '\n'.join(lines)
  89. def render_layouts(info_json):
  90. """Renders all the layouts from an `info_json` structure.
  91. """
  92. layouts = {}
  93. for layout in info_json['layouts']:
  94. layout_data = info_json['layouts'][layout]['layout']
  95. layouts[layout] = render_layout(layout_data)
  96. return layouts