keymap.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. """Functions that help you work with QMK keymaps.
  2. """
  3. import os
  4. from traceback import format_exc
  5. import re
  6. import glob
  7. import qmk.path
  8. import qmk.makefile
  9. from qmk.errors import NoSuchKeyboardError
  10. # The `keymap.c` template to use when a keyboard doesn't have its own
  11. DEFAULT_KEYMAP_C = """#include QMK_KEYBOARD_H
  12. /* THIS FILE WAS GENERATED!
  13. *
  14. * This file was generated by qmk-compile-json. You may or may not want to
  15. * edit it directly.
  16. */
  17. const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
  18. __KEYMAP_GOES_HERE__
  19. };
  20. """
  21. def template(keyboard):
  22. """Returns the `keymap.c` template for a keyboard.
  23. If a template exists in `keyboards/<keyboard>/templates/keymap.c` that
  24. text will be used instead of `DEFAULT_KEYMAP_C`.
  25. Args:
  26. keyboard
  27. The keyboard to return a template for.
  28. """
  29. template_name = 'keyboards/%s/templates/keymap.c' % keyboard
  30. if os.path.exists(template_name):
  31. with open(template_name, 'r') as fd:
  32. return fd.read()
  33. return DEFAULT_KEYMAP_C
  34. def generate(keyboard, layout, layers):
  35. """Returns a keymap.c for the specified keyboard, layout, and layers.
  36. Args:
  37. keyboard
  38. The name of the keyboard
  39. layout
  40. The LAYOUT macro this keymap uses.
  41. layers
  42. An array of arrays describing the keymap. Each item in the inner array should be a string that is a valid QMK keycode.
  43. """
  44. layer_txt = []
  45. for layer_num, layer in enumerate(layers):
  46. if layer_num != 0:
  47. layer_txt[-1] = layer_txt[-1] + ','
  48. layer_keys = ', '.join(layer)
  49. layer_txt.append('\t[%s] = %s(%s)' % (layer_num, layout, layer_keys))
  50. keymap = '\n'.join(layer_txt)
  51. keymap_c = template(keyboard)
  52. return keymap_c.replace('__KEYMAP_GOES_HERE__', keymap)
  53. def write(keyboard, keymap, layout, layers):
  54. """Generate the `keymap.c` and write it to disk.
  55. Returns the filename written to.
  56. Args:
  57. keyboard
  58. The name of the keyboard
  59. keymap
  60. The name of the keymap
  61. layout
  62. The LAYOUT macro this keymap uses.
  63. layers
  64. An array of arrays describing the keymap. Each item in the inner array should be a string that is a valid QMK keycode.
  65. """
  66. keymap_c = generate(keyboard, layout, layers)
  67. keymap_path = qmk.path.keymap(keyboard)
  68. keymap_dir = os.path.join(keymap_path, keymap)
  69. keymap_file = os.path.join(keymap_dir, 'keymap.c')
  70. if not os.path.exists(keymap_dir):
  71. os.makedirs(keymap_dir)
  72. with open(keymap_file, 'w') as keymap_fd:
  73. keymap_fd.write(keymap_c)
  74. return keymap_file
  75. def find_keymaps(base_path, revision = "", community = False):
  76. """ Find the available keymaps for a keyboard and revision pair.
  77. Args:
  78. base_path: The base path of the keyboard.
  79. revision: The keyboard's revision.
  80. community: Set to True for the layouts under layouts/community.
  81. Returns:
  82. a set with the keymaps's names
  83. """
  84. path_wildcard = os.path.join(base_path, "**", "keymap.c")
  85. if community:
  86. path_regex = re.compile(r"^" + re.escape(base_path) + "(\S+)" + os.path.sep + "keymap\.c")
  87. else:
  88. path_regex = re.compile(r"^" + re.escape(base_path) + "(?:" + re.escape(revision) + os.path.sep + ")?keymaps" + os.path.sep + "(\S+)" + os.path.sep + "keymap\.c")
  89. names = [path_regex.sub(lambda name: name.group(1), path) for path in glob.iglob(path_wildcard, recursive = True) if path_regex.search(path)]
  90. return set(names)
  91. def list_keymaps(keyboard_name):
  92. """ List the available keymaps for a keyboard.
  93. Args:
  94. keyboard_name: the keyboards full name with vendor and revision if necessary, example: clueboard/66/rev3
  95. Returns:
  96. a set with the names of the available keymaps
  97. """
  98. if os.path.sep in keyboard_name:
  99. keyboard, revision = os.path.split(os.path.normpath(keyboard_name))
  100. else:
  101. keyboard = keyboard_name
  102. revision = ""
  103. # parse all the rules.mk files for the keyboard
  104. rules_mk = qmk.makefile.get_rules_mk(keyboard, revision)
  105. names = set()
  106. if rules_mk:
  107. # get the keymaps from the keyboard's directory
  108. kb_base_path = os.path.join(os.getcwd(), "keyboards", keyboard) + os.path.sep
  109. names = find_keymaps(kb_base_path, revision)
  110. # if community layouts are supported, get them
  111. if "LAYOUTS" in rules_mk:
  112. for layout in rules_mk["LAYOUTS"]["value"].split():
  113. cl_base_path = os.path.join(os.getcwd(), "layouts", "community", layout) + os.path.sep
  114. names = names.union(find_keymaps(cl_base_path, revision, community = True))
  115. return sorted(names)