keymap.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. """This script automates the copying of the default keymap into your own keymap.
  2. """
  3. import re
  4. import json
  5. import shutil
  6. from pathlib import Path
  7. from milc import cli
  8. from milc.questions import question, choice
  9. from qmk.constants import HAS_QMK_USERSPACE, QMK_USERSPACE
  10. from qmk.errors import NoSuchKeyboardError
  11. from qmk.path import is_keyboard, keymaps, keymap
  12. from qmk.git import git_get_username
  13. from qmk.decorators import automagic_keyboard, automagic_keymap
  14. from qmk.keyboard import keyboard_completer, keyboard_folder
  15. from qmk.userspace import UserspaceDefs
  16. from qmk.json_schema import json_load
  17. from qmk.json_encoders import KeymapJSONEncoder
  18. from qmk.info import info_json
  19. def _list_available_converters(kb_name):
  20. """Search for converters that can be applied to a given keyboard
  21. """
  22. if not is_keyboard(kb_name):
  23. return None
  24. info = info_json(kb_name)
  25. pin_compatible = info.get('pin_compatible')
  26. if not pin_compatible:
  27. return None
  28. return sorted(folder.name.split('_to_')[-1] for folder in Path('platforms').glob(f'*/converters/{pin_compatible}_to_*'))
  29. def _set_converter(file, converter):
  30. """add/overwrite any existing converter specified in keymap.json
  31. """
  32. json_data = json_load(file) if file.exists() else {}
  33. json_data['converter'] = converter
  34. output = json.dumps(json_data, cls=KeymapJSONEncoder, sort_keys=True)
  35. file.write_text(output + '\n', encoding='utf-8')
  36. def validate_keymap_name(name):
  37. """Returns True if the given keymap name contains only a-z, 0-9 and underscore characters.
  38. """
  39. regex = re.compile(r'^[a-zA-Z0-9][a-zA-Z0-9_]+$')
  40. return bool(regex.match(name))
  41. def prompt_keyboard():
  42. prompt = """{fg_yellow}Select Keyboard{style_reset_all}
  43. If you're unsure you can view a full list of supported keyboards with {fg_yellow}qmk list-keyboards{style_reset_all}.
  44. Keyboard Name? """
  45. kb_name = question(prompt)
  46. try:
  47. # Resolve any keyboard alias
  48. return keyboard_folder(kb_name)
  49. except ValueError:
  50. return None
  51. def prompt_user():
  52. prompt = """
  53. {fg_yellow}Name Your Keymap{style_reset_all}
  54. Keymap name? """
  55. return question(prompt, default=git_get_username())
  56. def prompt_converter(kb_name):
  57. prompt = """
  58. {fg_yellow}Configure Development Board{style_reset_all}
  59. For more information, see:
  60. https://docs.qmk.fm/feature_converters
  61. Use converter? """
  62. converters = _list_available_converters(kb_name)
  63. if not converters:
  64. return None
  65. choices = ['No (default)', *converters]
  66. answer = choice(prompt, options=choices, default=0)
  67. return None if choices.index(answer) == 0 else answer
  68. @cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='Specify keyboard name. Example: 1upkeyboards/1up60hse')
  69. @cli.argument('-km', '--keymap', help='Specify the name for the new keymap directory')
  70. @cli.argument('--converter', help='Specify the name of a converter to configure')
  71. @cli.argument('--skip-converter', arg_only=True, action='store_true', help='Skip converter')
  72. @cli.subcommand('Creates a new keymap for the keyboard of your choosing')
  73. @automagic_keyboard
  74. @automagic_keymap
  75. def new_keymap(cli):
  76. """Creates a new keymap for the keyboard of your choosing.
  77. """
  78. cli.log.info('{style_bright}Generating a new keymap{style_normal}')
  79. cli.echo('')
  80. # ask for user input if keyboard or keymap was not provided in the command line
  81. kb_name = cli.config.new_keymap.keyboard if cli.config.new_keymap.keyboard else prompt_keyboard()
  82. user_name = cli.config.new_keymap.keymap if cli.config.new_keymap.keymap else prompt_user()
  83. converter = cli.config.new_keymap.converter if cli.args.skip_converter or cli.config.new_keymap.converter else prompt_converter(kb_name)
  84. # check directories
  85. if not is_keyboard(kb_name):
  86. cli.log.error(f'Keyboard {{fg_cyan}}{kb_name}{{fg_reset}} does not exist! Please choose a valid name.')
  87. return False
  88. # validate before any keymap ops
  89. try:
  90. keymaps_dirs = keymaps(kb_name)
  91. keymap_path_new = keymaps_dirs[0] / user_name
  92. except NoSuchKeyboardError:
  93. cli.log.error(f'Keymap folder for {{fg_cyan}}{kb_name}{{fg_reset}} does not exist!')
  94. return False
  95. keymap_path_default = keymap(kb_name, 'default')
  96. if not keymap_path_default:
  97. cli.log.error(f'Default keymap for {{fg_cyan}}{kb_name}{{fg_reset}} does not exist!')
  98. return False
  99. if not validate_keymap_name(user_name):
  100. cli.log.error(f'Keymap name {{fg_cyan}}{user_name}{{fg_reset}} must contain only {{fg_cyan}}a-z{{fg_reset}}, {{fg_cyan}}0-9{{fg_reset}} and {{fg_cyan}}_{{fg_reset}}! Please choose a different name.')
  101. return False
  102. if keymap_path_new.exists():
  103. cli.log.error(f'Keymap {{fg_cyan}}{user_name}{{fg_reset}} already exists! Please choose a different name.')
  104. return False
  105. # create user directory with default keymap files
  106. shutil.copytree(keymap_path_default, keymap_path_new, symlinks=True)
  107. if converter:
  108. _set_converter(keymap_path_new / 'keymap.json', converter)
  109. # end message to user
  110. cli.log.info(f'{{fg_green}}Created a new keymap called {{fg_cyan}}{user_name}{{fg_green}} in: {{fg_cyan}}{keymap_path_new}{{fg_reset}}.')
  111. cli.log.info(f"Compile a firmware with your new keymap by typing: {{fg_yellow}}qmk compile -kb {kb_name} -km {user_name}{{fg_reset}}.")
  112. # Add to userspace compile if we have userspace available
  113. if HAS_QMK_USERSPACE:
  114. userspace = UserspaceDefs(QMK_USERSPACE / 'qmk.json')
  115. userspace.add_target(keyboard=kb_name, keymap=user_name, do_print=False)
  116. return userspace.save()