header_generator.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. """This script generates the XAP protocol generated header to be compiled into QMK.
  2. """
  3. import re
  4. from fnvhash import fnv1a_32
  5. from qmk.commands import get_git_version
  6. from qmk.constants import GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE
  7. from qmk.xap.common import latest_xap_defs, route_conditions
  8. def _append_route_defines(lines, container, container_id=None, route_stack=None):
  9. """Handles building the list of the XAP routes, combining parent and child names together, as well as the route number.
  10. """
  11. if route_stack is None:
  12. route_stack = [container]
  13. else:
  14. route_stack.append(container)
  15. route_name = '_'.join([r['define'] for r in route_stack])
  16. if container_id:
  17. lines.append(f'#define {route_name} {container_id}')
  18. if 'routes' in container:
  19. for route_id in container['routes']:
  20. route = container['routes'][route_id]
  21. _append_route_defines(lines, route, route_id, route_stack)
  22. route_stack.pop()
  23. def _append_route_masks(lines, container, container_id=None, route_stack=None):
  24. """Handles creating the equivalent XAP route masks, for capabilities checks. Forces value of `0` if disabled in the firmware.
  25. """
  26. if route_stack is None:
  27. route_stack = [container]
  28. else:
  29. route_stack.append(container)
  30. route_name = '_'.join([r['define'] for r in route_stack])
  31. condition = route_conditions(route_stack)
  32. if container_id:
  33. if condition:
  34. lines.append('')
  35. lines.append(f'#if {condition}')
  36. lines.append(f'#define {route_name}_MASK (1ul << ({route_name}))')
  37. if condition:
  38. lines.append(f'#else // {condition}')
  39. lines.append(f'#define {route_name}_MASK 0')
  40. lines.append(f'#endif // {condition}')
  41. lines.append('')
  42. if 'routes' in container:
  43. for route_id in container['routes']:
  44. route = container['routes'][route_id]
  45. _append_route_masks(lines, route, route_id, route_stack)
  46. route_stack.pop()
  47. def _append_route_capabilities(lines, container, container_id=None, route_stack=None):
  48. """Handles creating the equivalent XAP route masks, for capabilities checks. Forces value of `0` if disabled in the firmware.
  49. """
  50. if route_stack is None:
  51. route_stack = [container]
  52. else:
  53. route_stack.append(container)
  54. route_name = '_'.join([r['define'] for r in route_stack])
  55. if 'routes' in container:
  56. lines.append('')
  57. lines.append(f'#define {route_name}_CAPABILITIES (0 \\')
  58. if 'routes' in container:
  59. for route_id in container['routes']:
  60. route = container['routes'][route_id]
  61. route_stack.append(route)
  62. child_name = '_'.join([r['define'] for r in route_stack])
  63. lines.append(f' | ({child_name}_MASK) \\')
  64. route_stack.pop()
  65. lines.append(' )')
  66. if 'routes' in container:
  67. for route_id in container['routes']:
  68. route = container['routes'][route_id]
  69. _append_route_capabilities(lines, route, route_id, route_stack)
  70. route_stack.pop()
  71. def generate_header(output_file, keyboard):
  72. """Generates the XAP protocol header file, generated during normal build.
  73. """
  74. xap_defs = latest_xap_defs()
  75. # Preamble
  76. lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '#pragma once', '']
  77. # Versions
  78. prog = re.compile(r'^(\d+)\.(\d+)\.(\d+)')
  79. b = prog.match(xap_defs['version'])
  80. lines.append(f'#define XAP_BCD_VERSION 0x{int(b.group(1)):02d}{int(b.group(2)):02d}{int(b.group(3)):04d}ul')
  81. b = prog.match(get_git_version())
  82. lines.append(f'#define QMK_BCD_VERSION 0x{int(b.group(1)):02d}{int(b.group(2)):02d}{int(b.group(3)):04d}ul')
  83. keyboard_id = fnv1a_32(bytes(keyboard, 'utf-8'))
  84. lines.append(f'#define XAP_KEYBOARD_IDENTIFIER 0x{keyboard_id:08X}ul')
  85. lines.append('')
  86. # Append the route and command defines
  87. _append_route_defines(lines, xap_defs)
  88. lines.append('')
  89. _append_route_masks(lines, xap_defs)
  90. lines.append('')
  91. _append_route_capabilities(lines, xap_defs)
  92. lines.append('')
  93. # Generate the full output
  94. xap_generated_inl = '\n'.join(lines)
  95. # Clean up newlines
  96. while "\n\n\n" in xap_generated_inl:
  97. xap_generated_inl = xap_generated_inl.replace("\n\n\n", "\n\n")
  98. if output_file:
  99. if output_file.name == '-':
  100. print(xap_generated_inl)
  101. else:
  102. output_file.parent.mkdir(parents=True, exist_ok=True)
  103. if output_file.exists():
  104. output_file.replace(output_file.parent / (output_file.name + '.bak'))
  105. output_file.write_text(xap_generated_inl)