keyboard stuff
1"""Used by the make system to generate keycodes.h from keycodes_{version}.json
2"""
3from milc import cli
4
5from qmk.constants import GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE
6from qmk.commands import dump_lines
7from qmk.path import normpath
8from qmk.keycodes import load_spec
9
10
11def _translate_group(group):
12 """Fix up any issues with badly chosen values
13 """
14 if group == 'modifiers':
15 return 'modifier'
16 if group == 'media':
17 return 'consumer'
18 return group
19
20
21def _render_key(key):
22 width = 7
23 if 'S(' in key:
24 width += len('S()')
25 if 'A(' in key:
26 width += len('A()')
27 if 'RCTL(' in key:
28 width += len('RCTL()')
29 if 'ALGR(' in key:
30 width += len('ALGR()')
31 return key.ljust(width)
32
33
34def _render_label(label):
35 label = label.replace("\\", "(backslash)")
36 return label
37
38
39def _generate_ranges(lines, keycodes):
40 lines.append('')
41 lines.append('enum qk_keycode_ranges {')
42 lines.append('// Ranges')
43 for key, value in keycodes["ranges"].items():
44 lo, mask = map(lambda x: int(x, 16), key.split("/"))
45 hi = lo + mask
46 define = value.get("define")
47 lines.append(f' {define.ljust(30)} = 0x{lo:04X},')
48 lines.append(f' {(define + "_MAX").ljust(30)} = 0x{hi:04X},')
49 lines.append('};')
50
51
52def _generate_defines(lines, keycodes):
53 lines.append('')
54 lines.append('enum qk_keycode_defines {')
55 lines.append('// Keycodes')
56 for key, value in keycodes["keycodes"].items():
57 lines.append(f' {value.get("key")} = {key},')
58
59 lines.append('')
60 lines.append('// Alias')
61 for key, value in keycodes["keycodes"].items():
62 temp = value.get("key")
63 for alias in value.get("aliases", []):
64 lines.append(f' {alias.ljust(10)} = {temp},')
65
66 lines.append('};')
67
68
69def _generate_helpers(lines, keycodes):
70 lines.append('')
71 lines.append('// Range Helpers')
72 for value in keycodes["ranges"].values():
73 define = value.get("define")
74 lines.append(f'#define IS_{define}(code) ((code) >= {define} && (code) <= {define + "_MAX"})')
75
76 # extract min/max
77 temp = {}
78 for key, value in keycodes["keycodes"].items():
79 group = value.get('group', None)
80 if not group:
81 continue
82 if group not in temp:
83 temp[group] = [0xFFFF, 0]
84 key = int(key, 16)
85 if key < temp[group][0]:
86 temp[group][0] = key
87 if key > temp[group][1]:
88 temp[group][1] = key
89
90 lines.append('')
91 lines.append('// Group Helpers')
92 for group, codes in temp.items():
93 lo = keycodes["keycodes"][f'0x{codes[0]:04X}']['key']
94 hi = keycodes["keycodes"][f'0x{codes[1]:04X}']['key']
95 lines.append(f'#define IS_{_translate_group(group).upper()}_KEYCODE(code) ((code) >= {lo} && (code) <= {hi})')
96
97 lines.append('')
98 lines.append('// Switch statement Helpers')
99 for group, codes in temp.items():
100 lo = keycodes["keycodes"][f'0x{codes[0]:04X}']['key']
101 hi = keycodes["keycodes"][f'0x{codes[1]:04X}']['key']
102 name = f'{_translate_group(group).upper()}_KEYCODE_RANGE'
103 lines.append(f'#define {name.ljust(35)} {lo} ... {hi}')
104
105
106def _generate_aliases(lines, keycodes):
107 # Work around ChibiOS ch.h include guard
108 if 'CH_H' in [value['key'] for value in keycodes['aliases'].values()]:
109 lines.append('')
110 lines.append('#undef CH_H')
111
112 lines.append('')
113 lines.append('// Aliases')
114 for key, value in keycodes["aliases"].items():
115 define = _render_key(value.get("key"))
116 val = _render_key(key)
117 if 'label' in value:
118 lines.append(f'#define {define} {val} // {_render_label(value.get("label"))}')
119 else:
120 lines.append(f'#define {define} {val}')
121
122 lines.append('')
123 for key, value in keycodes["aliases"].items():
124 for alias in value.get("aliases", []):
125 lines.append(f'#define {alias} {value.get("key")}')
126
127
128def _generate_version(lines, keycodes, prefix=''):
129 version = keycodes['version']
130 major, minor, patch = map(int, version.split('.'))
131
132 bcd = f'0x{major:02d}{minor:02d}{patch:04d}'
133
134 lines.append('')
135 lines.append(f'#define QMK_{prefix}KEYCODES_VERSION "{version}"')
136 lines.append(f'#define QMK_{prefix}KEYCODES_VERSION_BCD {bcd}')
137 lines.append(f'#define QMK_{prefix}KEYCODES_VERSION_MAJOR {major}')
138 lines.append(f'#define QMK_{prefix}KEYCODES_VERSION_MINOR {minor}')
139 lines.append(f'#define QMK_{prefix}KEYCODES_VERSION_PATCH {patch}')
140
141
142@cli.argument('-v', '--version', arg_only=True, required=True, help='Version of keycodes to generate.')
143@cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to')
144@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages")
145@cli.subcommand('Used by the make system to generate keycodes.h from keycodes_{version}.json', hidden=True)
146def generate_keycodes(cli):
147 """Generates the keycodes.h file.
148 """
149
150 # Build the keycodes.h file.
151 keycodes_h_lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '#pragma once', '// clang-format off']
152
153 keycodes = load_spec(cli.args.version)
154
155 _generate_version(keycodes_h_lines, keycodes)
156 _generate_ranges(keycodes_h_lines, keycodes)
157 _generate_defines(keycodes_h_lines, keycodes)
158 _generate_helpers(keycodes_h_lines, keycodes)
159
160 # Show the results
161 dump_lines(cli.args.output, keycodes_h_lines, cli.args.quiet)
162
163
164@cli.argument('-v', '--version', arg_only=True, required=True, help='Version of keycodes to generate.')
165@cli.argument('-l', '--lang', arg_only=True, required=True, help='Language of keycodes to generate.')
166@cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to')
167@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages")
168@cli.subcommand('Used by the make system to generate keymap_{lang}.h from keycodes_{lang}_{version}.json', hidden=True)
169def generate_keycode_extras(cli):
170 """Generates the header file.
171 """
172
173 # Build the header file.
174 keycodes_h_lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '#pragma once', '#include "keycodes.h"', '// clang-format off']
175
176 keycodes = load_spec(cli.args.version, cli.args.lang)
177
178 _generate_version(keycodes_h_lines, keycodes, f'{cli.args.lang.upper()}_')
179 _generate_aliases(keycodes_h_lines, keycodes)
180
181 # Show the results
182 dump_lines(cli.args.output, keycodes_h_lines, cli.args.quiet)