diff options
Diffstat (limited to 'layouts/community/ergodox/german-manuneo/compile_keymap.py')
-rw-r--r-- | layouts/community/ergodox/german-manuneo/compile_keymap.py | 710 |
1 files changed, 710 insertions, 0 deletions
diff --git a/layouts/community/ergodox/german-manuneo/compile_keymap.py b/layouts/community/ergodox/german-manuneo/compile_keymap.py new file mode 100644 index 000000000..44f31b1c6 --- /dev/null +++ b/layouts/community/ergodox/german-manuneo/compile_keymap.py | |||
@@ -0,0 +1,710 @@ | |||
1 | #!/usr/bin/env python | ||
2 | # -*- coding: utf-8 -*- | ||
3 | """Compiler for keymap.c files | ||
4 | |||
5 | This scrip will generate a keymap.c file from a simple | ||
6 | markdown file with a specific layout. | ||
7 | |||
8 | Usage: | ||
9 | python compile_keymap.py INPUT_PATH [OUTPUT_PATH] | ||
10 | """ | ||
11 | from __future__ import division | ||
12 | from __future__ import print_function | ||
13 | from __future__ import absolute_import | ||
14 | from __future__ import unicode_literals | ||
15 | |||
16 | import os | ||
17 | import io | ||
18 | import re | ||
19 | import sys | ||
20 | import json | ||
21 | import unicodedata | ||
22 | import collections | ||
23 | import itertools as it | ||
24 | |||
25 | PY2 = sys.version_info.major == 2 | ||
26 | |||
27 | if PY2: | ||
28 | chr = unichr | ||
29 | |||
30 | |||
31 | KEYBOARD_LAYOUTS = { | ||
32 | # These map positions in the parsed layout to | ||
33 | # positions in the LAYOUT_ergodox MATRIX | ||
34 | 'ergodox_ez': [ | ||
35 | [ 0, 1, 2, 3, 4, 5, 6], [38, 39, 40, 41, 42, 43, 44], | ||
36 | [ 7, 8, 9, 10, 11, 12, 13], [45, 46, 47, 48, 49, 50, 51], | ||
37 | [14, 15, 16, 17, 18, 19 ], [ 52, 53, 54, 55, 56, 57], | ||
38 | [20, 21, 22, 23, 24, 25, 26], [58, 59, 60, 61, 62, 63, 64], | ||
39 | [27, 28, 29, 30, 31 ], [ 65, 66, 67, 68, 69], | ||
40 | [ 32, 33], [70, 71 ], | ||
41 | [ 34], [72 ], | ||
42 | [ 35, 36, 37], [73, 74, 75 ], | ||
43 | ] | ||
44 | } | ||
45 | |||
46 | ROW_INDENTS = { | ||
47 | 'ergodox_ez': [0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 5, 0, 6, 0, 4, 0] | ||
48 | } | ||
49 | |||
50 | BLANK_LAYOUTS = [ | ||
51 | # Compact Layout | ||
52 | """ | ||
53 | .------------------------------------.------------------------------------. | ||
54 | | | | | | | | | | | | | | | | | ||
55 | !-----+----+----+----+----+----------!-----+----+----+----+----+----+-----! | ||
56 | | | | | | | | | | | | | | | | | ||
57 | !-----+----+----+----x----x----! ! !----x----x----+----+----+-----! | ||
58 | | | | | | | |-----!-----! | | | | | | | ||
59 | !-----+----+----+----x----x----! ! !----x----x----+----+----+-----! | ||
60 | | | | | | | | | | | | | | | | | ||
61 | '-----+----+----+----+----+----------'----------+----+----+----+----+-----' | ||
62 | | | | | | | ! | | | | | | ||
63 | '------------------------' '------------------------' | ||
64 | .-----------. .-----------. | ||
65 | | | | ! | | | ||
66 | .-----+-----+-----! !-----+-----+-----. | ||
67 | ! ! | | ! | ! ! | ||
68 | ! ! !-----! !-----! ! ! | ||
69 | | | | | ! | | | | ||
70 | '-----------------' '-----------------' | ||
71 | """, | ||
72 | |||
73 | # Wide Layout | ||
74 | """ | ||
75 | .---------------------------------------------. .---------------------------------------------. | ||
76 | | | | | | | | | ! | | | | | | | | ||
77 | !-------+-----+-----+-----+-----+-------------! !-------+-----+-----+-----+-----+-----+-------! | ||
78 | | | | | | | | | ! | | | | | | | | ||
79 | !-------+-----+-----+-----x-----x-----! ! ! !-----x-----x-----+-----+-----+-------! | ||
80 | | | | | | | |-------! !-------! | | | | | | | ||
81 | !-------+-----+-----+-----x-----x-----! ! ! !-----x-----x-----+-----+-----+-------! | ||
82 | | | | | | | | | ! | | | | | | | | ||
83 | '-------+-----+-----+-----+-----+-------------' '-------------+-----+-----+-----+-----+-------' | ||
84 | | | | | | | ! | | | | | | ||
85 | '------------------------------' '------------------------------' | ||
86 | .---------------. .---------------. | ||
87 | | | | ! | | | ||
88 | .-------+-------+-------! !-------+-------+-------. | ||
89 | ! ! | | ! | ! ! | ||
90 | ! ! !-------! !-------! ! ! | ||
91 | | | | | ! | | | | ||
92 | '-----------------------' '-----------------------' | ||
93 | """, | ||
94 | ] | ||
95 | |||
96 | |||
97 | DEFAULT_CONFIG = { | ||
98 | "keymaps_includes": [ | ||
99 | "keymap_common.h", | ||
100 | ], | ||
101 | 'filler': "-+.'!:x", | ||
102 | 'separator': "|", | ||
103 | 'default_key_prefix': ["KC_"], | ||
104 | } | ||
105 | |||
106 | |||
107 | SECTIONS = [ | ||
108 | 'layout_config', | ||
109 | 'layers', | ||
110 | ] | ||
111 | |||
112 | |||
113 | # Markdown Parsing | ||
114 | |||
115 | ONELINE_COMMENT_RE = re.compile(r""" | ||
116 | ^ # comment must be at the start of the line | ||
117 | \s* # arbitrary whitespace | ||
118 | // # start of the comment | ||
119 | (.*) # the comment | ||
120 | $ # until the end of line | ||
121 | """, re.MULTILINE | re.VERBOSE | ||
122 | ) | ||
123 | |||
124 | INLINE_COMMENT_RE = re.compile(r""" | ||
125 | ([\,\"\[\]\{\}\d]) # anythig that might end a expression | ||
126 | \s+ # comment must be preceded by whitespace | ||
127 | // # start of the comment | ||
128 | \s # and succeded by whitespace | ||
129 | (?:[^\"\]\}\{\[]*) # the comment (except things which might be json) | ||
130 | $ # until the end of line | ||
131 | """, re.MULTILINE | re.VERBOSE) | ||
132 | |||
133 | TRAILING_COMMA_RE = re.compile(r""" | ||
134 | , # the comma | ||
135 | (?:\s*) # arbitrary whitespace | ||
136 | $ # only works if the trailing comma is followed by newline | ||
137 | (\s*) # arbitrary whitespace | ||
138 | ([\]\}]) # end of an array or object | ||
139 | """, re.MULTILINE | re.VERBOSE) | ||
140 | |||
141 | |||
142 | def loads(raw_data): | ||
143 | if isinstance(raw_data, bytes): | ||
144 | raw_data = raw_data.decode('utf-8') | ||
145 | |||
146 | raw_data = ONELINE_COMMENT_RE.sub(r"", raw_data) | ||
147 | raw_data = INLINE_COMMENT_RE.sub(r"\1", raw_data) | ||
148 | raw_data = TRAILING_COMMA_RE.sub(r"\1\2", raw_data) | ||
149 | return json.loads(raw_data) | ||
150 | |||
151 | |||
152 | def parse_config(path): | ||
153 | def reset_section(): | ||
154 | section.update({ | ||
155 | 'name': section.get('name', ""), | ||
156 | 'sub_name': "", | ||
157 | 'start_line': -1, | ||
158 | 'end_line': -1, | ||
159 | 'code_lines': [], | ||
160 | }) | ||
161 | |||
162 | def start_section(line_index, line): | ||
163 | end_section() | ||
164 | if line.startswith("# "): | ||
165 | name = line[2:] | ||
166 | elif line.startswith("## "): | ||
167 | name = line[3:] | ||
168 | else: | ||
169 | name = "" | ||
170 | |||
171 | name = name.strip().replace(" ", "_").lower() | ||
172 | if name in SECTIONS: | ||
173 | section['name'] = name | ||
174 | else: | ||
175 | section['sub_name'] = name | ||
176 | section['start_line'] = line_index | ||
177 | |||
178 | def end_section(): | ||
179 | if section['start_line'] >= 0: | ||
180 | if section['name'] == 'layout_config': | ||
181 | config.update(loads("\n".join( | ||
182 | section['code_lines'] | ||
183 | ))) | ||
184 | elif section['sub_name'].startswith('layer'): | ||
185 | layer_name = section['sub_name'] | ||
186 | config['layer_lines'][layer_name] = section['code_lines'] | ||
187 | |||
188 | reset_section() | ||
189 | |||
190 | def amend_section(line_index, line): | ||
191 | section['end_line'] = line_index | ||
192 | section['code_lines'].append(line) | ||
193 | |||
194 | config = DEFAULT_CONFIG.copy() | ||
195 | config.update({ | ||
196 | 'layer_lines': collections.OrderedDict(), | ||
197 | 'macro_ids': {'UM'}, | ||
198 | 'unicode_macros': {}, | ||
199 | }) | ||
200 | |||
201 | section = {} | ||
202 | reset_section() | ||
203 | |||
204 | with io.open(path, encoding="utf-8") as fh: | ||
205 | for i, line in enumerate(fh): | ||
206 | if line.startswith("#"): | ||
207 | start_section(i, line) | ||
208 | elif line.startswith(" "): | ||
209 | amend_section(i, line[4:]) | ||
210 | else: | ||
211 | # TODO: maybe parse description | ||
212 | pass | ||
213 | |||
214 | end_section() | ||
215 | assert 'layout' in config | ||
216 | return config | ||
217 | |||
218 | # header file parsing | ||
219 | |||
220 | IF0_RE = re.compile(r""" | ||
221 | ^ | ||
222 | #if 0 | ||
223 | $.*? | ||
224 | #endif | ||
225 | """, re.MULTILINE | re.DOTALL | re.VERBOSE) | ||
226 | |||
227 | |||
228 | COMMENT_RE = re.compile(r""" | ||
229 | /\* | ||
230 | .*? | ||
231 | \*/" | ||
232 | """, re.MULTILINE | re.DOTALL | re.VERBOSE) | ||
233 | |||
234 | |||
235 | def read_header_file(path): | ||
236 | with io.open(path, encoding="utf-8") as fh: | ||
237 | data = fh.read() | ||
238 | data, _ = COMMENT_RE.subn("", data) | ||
239 | data, _ = IF0_RE.subn("", data) | ||
240 | return data | ||
241 | |||
242 | |||
243 | def regex_partial(re_str_fmt, flags): | ||
244 | def partial(*args, **kwargs): | ||
245 | re_str = re_str_fmt.format(*args, **kwargs) | ||
246 | return re.compile(re_str, flags) | ||
247 | return partial | ||
248 | |||
249 | |||
250 | KEYDEF_REP = regex_partial(r""" | ||
251 | #define | ||
252 | \s | ||
253 | ( | ||
254 | (?:{}) # the prefixes | ||
255 | (?:\w+) # the key name | ||
256 | ) # capture group end | ||
257 | """, re.MULTILINE | re.DOTALL | re.VERBOSE) | ||
258 | |||
259 | |||
260 | ENUM_RE = re.compile(r""" | ||
261 | ( | ||
262 | enum | ||
263 | \s\w+\s | ||
264 | \{ | ||
265 | .*? # the enum content | ||
266 | \} | ||
267 | ; | ||
268 | ) # capture group end | ||
269 | """, re.MULTILINE | re.DOTALL | re.VERBOSE) | ||
270 | |||
271 | |||
272 | ENUM_KEY_REP = regex_partial(r""" | ||
273 | ( | ||
274 | {} # the prefixes | ||
275 | \w+ # the key name | ||
276 | ) # capture group end | ||
277 | """, re.MULTILINE | re.DOTALL | re.VERBOSE) | ||
278 | |||
279 | |||
280 | def parse_keydefs(config, data): | ||
281 | prefix_options = "|".join(config['key_prefixes']) | ||
282 | keydef_re = KEYDEF_REP(prefix_options) | ||
283 | enum_key_re = ENUM_KEY_REP(prefix_options) | ||
284 | for match in keydef_re.finditer(data): | ||
285 | yield match.groups()[0] | ||
286 | |||
287 | for enum_match in ENUM_RE.finditer(data): | ||
288 | enum = enum_match.groups()[0] | ||
289 | for key_match in enum_key_re.finditer(enum): | ||
290 | yield key_match.groups()[0] | ||
291 | |||
292 | |||
293 | def parse_valid_keys(config, out_path): | ||
294 | basepath = os.path.abspath(os.path.join(os.path.dirname(out_path))) | ||
295 | dirpaths = [] | ||
296 | subpaths = [] | ||
297 | while len(subpaths) < 6: | ||
298 | path = os.path.join(basepath, *subpaths) | ||
299 | dirpaths.append(path) | ||
300 | dirpaths.append(os.path.join(path, "tmk_core", "common")) | ||
301 | dirpaths.append(os.path.join(path, "quantum")) | ||
302 | subpaths.append('..') | ||
303 | |||
304 | includes = set(config['keymaps_includes']) | ||
305 | includes.add("keycode.h") | ||
306 | |||
307 | valid_keycodes = set() | ||
308 | for dirpath, include in it.product(dirpaths, includes): | ||
309 | include_path = os.path.join(dirpath, include) | ||
310 | if os.path.exists(include_path): | ||
311 | header_data = read_header_file(include_path) | ||
312 | valid_keycodes.update( | ||
313 | parse_keydefs(config, header_data) | ||
314 | ) | ||
315 | return valid_keycodes | ||
316 | |||
317 | |||
318 | # Keymap Parsing | ||
319 | |||
320 | def iter_raw_codes(layer_lines, filler, separator): | ||
321 | filler_re = re.compile("[" + filler + " ]") | ||
322 | for line in layer_lines: | ||
323 | line, _ = filler_re.subn("", line.strip()) | ||
324 | if not line: | ||
325 | continue | ||
326 | codes = line.split(separator) | ||
327 | for code in codes[1:-1]: | ||
328 | yield code | ||
329 | |||
330 | |||
331 | def iter_indexed_codes(raw_codes, key_indexes): | ||
332 | key_rows = {} | ||
333 | key_indexes_flat = [] | ||
334 | |||
335 | for row_index, key_indexes in enumerate(key_indexes): | ||
336 | for key_index in key_indexes: | ||
337 | key_rows[key_index] = row_index | ||
338 | key_indexes_flat.extend(key_indexes) | ||
339 | assert len(raw_codes) == len(key_indexes_flat) | ||
340 | for raw_code, key_index in zip(raw_codes, key_indexes_flat): | ||
341 | # we keep track of the row mostly for layout purposes | ||
342 | yield raw_code, key_index, key_rows[key_index] | ||
343 | |||
344 | |||
345 | LAYER_CHANGE_RE = re.compile(r""" | ||
346 | (DF|TG|MO)\(\d+\) | ||
347 | """, re.VERBOSE) | ||
348 | |||
349 | |||
350 | MACRO_RE = re.compile(r""" | ||
351 | M\(\w+\) | ||
352 | """, re.VERBOSE) | ||
353 | |||
354 | |||
355 | UNICODE_RE = re.compile(r""" | ||
356 | U[0-9A-F]{4} | ||
357 | """, re.VERBOSE) | ||
358 | |||
359 | |||
360 | NON_CODE = re.compile(r""" | ||
361 | ^[^A-Z0-9_]$ | ||
362 | """, re.VERBOSE) | ||
363 | |||
364 | |||
365 | def parse_uni_code(raw_code): | ||
366 | macro_id = "UC_" + ( | ||
367 | unicodedata.name(raw_code) | ||
368 | .replace(" ", "_") | ||
369 | .replace("-", "_") | ||
370 | ) | ||
371 | code = "M({})".format(macro_id) | ||
372 | uc_hex = "{:04X}".format(ord(raw_code)) | ||
373 | return code, macro_id, uc_hex | ||
374 | |||
375 | |||
376 | def parse_key_code(raw_code, key_prefixes, valid_keycodes): | ||
377 | if raw_code in valid_keycodes: | ||
378 | return raw_code | ||
379 | |||
380 | for prefix in key_prefixes: | ||
381 | code = prefix + raw_code | ||
382 | if code in valid_keycodes: | ||
383 | return code | ||
384 | |||
385 | |||
386 | def parse_code(raw_code, key_prefixes, valid_keycodes): | ||
387 | if not raw_code: | ||
388 | return 'KC_TRNS', None, None | ||
389 | |||
390 | if LAYER_CHANGE_RE.match(raw_code): | ||
391 | return raw_code, None, None | ||
392 | |||
393 | if MACRO_RE.match(raw_code): | ||
394 | macro_id = raw_code[2:-1] | ||
395 | return raw_code, macro_id, None | ||
396 | |||
397 | if UNICODE_RE.match(raw_code): | ||
398 | hex_code = raw_code[1:] | ||
399 | return parse_uni_code(chr(int(hex_code, 16))) | ||
400 | |||
401 | if NON_CODE.match(raw_code): | ||
402 | return parse_uni_code(raw_code) | ||
403 | |||
404 | code = parse_key_code(raw_code, key_prefixes, valid_keycodes) | ||
405 | return code, None, None | ||
406 | |||
407 | |||
408 | def parse_keymap(config, key_indexes, layer_lines, valid_keycodes): | ||
409 | keymap = {} | ||
410 | raw_codes = list(iter_raw_codes( | ||
411 | layer_lines, config['filler'], config['separator'] | ||
412 | )) | ||
413 | indexed_codes = iter_indexed_codes(raw_codes, key_indexes) | ||
414 | key_prefixes = config['key_prefixes'] | ||
415 | for raw_code, key_index, row_index in indexed_codes: | ||
416 | code, macro_id, uc_hex = parse_code( | ||
417 | raw_code, key_prefixes, valid_keycodes | ||
418 | ) | ||
419 | # TODO: line numbers for invalid codes | ||
420 | err_msg = "Could not parse key '{}' on row {}".format( | ||
421 | raw_code, row_index | ||
422 | ) | ||
423 | assert code is not None, err_msg | ||
424 | # print(repr(raw_code), repr(code), macro_id, uc_hex) | ||
425 | if macro_id: | ||
426 | config['macro_ids'].add(macro_id) | ||
427 | if uc_hex: | ||
428 | config['unicode_macros'][macro_id] = uc_hex | ||
429 | keymap[key_index] = (code, row_index) | ||
430 | return keymap | ||
431 | |||
432 | |||
433 | def parse_keymaps(config, valid_keycodes): | ||
434 | keymaps = collections.OrderedDict() | ||
435 | key_indexes = config.get( | ||
436 | 'key_indexes', KEYBOARD_LAYOUTS[config['layout']] | ||
437 | ) | ||
438 | # TODO: maybe validate key_indexes | ||
439 | |||
440 | for layer_name, layer_lines, in config['layer_lines'].items(): | ||
441 | keymaps[layer_name] = parse_keymap( | ||
442 | config, key_indexes, layer_lines, valid_keycodes | ||
443 | ) | ||
444 | return keymaps | ||
445 | |||
446 | # keymap.c output | ||
447 | |||
448 | USERCODE = """ | ||
449 | // Runs just one time when the keyboard initializes. | ||
450 | void matrix_init_user(void) { | ||
451 | |||
452 | }; | ||
453 | |||
454 | // Runs constantly in the background, in a loop. | ||
455 | void matrix_scan_user(void) { | ||
456 | uint8_t layer = biton32(layer_state); | ||
457 | |||
458 | ergodox_board_led_off(); | ||
459 | ergodox_right_led_1_off(); | ||
460 | ergodox_right_led_2_off(); | ||
461 | ergodox_right_led_3_off(); | ||
462 | switch (layer) { | ||
463 | case L1: | ||
464 | ergodox_right_led_1_on(); | ||
465 | break; | ||
466 | case L2: | ||
467 | ergodox_right_led_2_on(); | ||
468 | break; | ||
469 | case L3: | ||
470 | ergodox_right_led_3_on(); | ||
471 | break; | ||
472 | case L4: | ||
473 | ergodox_right_led_1_on(); | ||
474 | ergodox_right_led_2_on(); | ||
475 | break; | ||
476 | case L5: | ||
477 | ergodox_right_led_1_on(); | ||
478 | ergodox_right_led_3_on(); | ||
479 | break; | ||
480 | // case L6: | ||
481 | // ergodox_right_led_2_on(); | ||
482 | // ergodox_right_led_3_on(); | ||
483 | // break; | ||
484 | // case L7: | ||
485 | // ergodox_right_led_1_on(); | ||
486 | // ergodox_right_led_2_on(); | ||
487 | // ergodox_right_led_3_on(); | ||
488 | // break; | ||
489 | default: | ||
490 | ergodox_board_led_off(); | ||
491 | break; | ||
492 | } | ||
493 | }; | ||
494 | """ | ||
495 | |||
496 | MACROCODE = """ | ||
497 | #define UC_MODE_WIN 0 | ||
498 | #define UC_MODE_LINUX 1 | ||
499 | #define UC_MODE_OSX 2 | ||
500 | |||
501 | // TODO: allow default mode to be configured | ||
502 | static uint16_t unicode_mode = UC_MODE_WIN; | ||
503 | |||
504 | uint16_t hextokeycode(uint8_t hex) {{ | ||
505 | if (hex == 0x0) {{ | ||
506 | return KC_P0; | ||
507 | }} | ||
508 | if (hex < 0xA) {{ | ||
509 | return KC_P1 + (hex - 0x1); | ||
510 | }} | ||
511 | return KC_A + (hex - 0xA); | ||
512 | }} | ||
513 | |||
514 | void unicode_action_function(uint16_t hi, uint16_t lo) {{ | ||
515 | switch (unicode_mode) {{ | ||
516 | case UC_MODE_WIN: | ||
517 | register_code(KC_LALT); | ||
518 | |||
519 | register_code(KC_PPLS); | ||
520 | unregister_code(KC_PPLS); | ||
521 | |||
522 | register_code(hextokeycode((hi & 0xF0) >> 4)); | ||
523 | unregister_code(hextokeycode((hi & 0xF0) >> 4)); | ||
524 | register_code(hextokeycode((hi & 0x0F))); | ||
525 | unregister_code(hextokeycode((hi & 0x0F))); | ||
526 | register_code(hextokeycode((lo & 0xF0) >> 4)); | ||
527 | unregister_code(hextokeycode((lo & 0xF0) >> 4)); | ||
528 | register_code(hextokeycode((lo & 0x0F))); | ||
529 | unregister_code(hextokeycode((lo & 0x0F))); | ||
530 | |||
531 | unregister_code(KC_LALT); | ||
532 | break; | ||
533 | case UC_MODE_LINUX: | ||
534 | register_code(KC_LCTL); | ||
535 | register_code(KC_LSFT); | ||
536 | |||
537 | register_code(KC_U); | ||
538 | unregister_code(KC_U); | ||
539 | |||
540 | register_code(hextokeycode((hi & 0xF0) >> 4)); | ||
541 | unregister_code(hextokeycode((hi & 0xF0) >> 4)); | ||
542 | register_code(hextokeycode((hi & 0x0F))); | ||
543 | unregister_code(hextokeycode((hi & 0x0F))); | ||
544 | register_code(hextokeycode((lo & 0xF0) >> 4)); | ||
545 | unregister_code(hextokeycode((lo & 0xF0) >> 4)); | ||
546 | register_code(hextokeycode((lo & 0x0F))); | ||
547 | unregister_code(hextokeycode((lo & 0x0F))); | ||
548 | |||
549 | unregister_code(KC_LCTL); | ||
550 | unregister_code(KC_LSFT); | ||
551 | break; | ||
552 | case UC_MODE_OSX: | ||
553 | break; | ||
554 | }} | ||
555 | }} | ||
556 | |||
557 | const macro_t *action_get_macro(keyrecord_t *record, uint8_t id, uint8_t opt) {{ | ||
558 | if (!record->event.pressed) {{ | ||
559 | return MACRO_NONE; | ||
560 | }} | ||
561 | // MACRODOWN only works in this function | ||
562 | switch(id) {{ | ||
563 | case UM: | ||
564 | unicode_mode = (unicode_mode + 1) % 2; | ||
565 | break; | ||
566 | {macro_cases} | ||
567 | {unicode_macro_cases} | ||
568 | default: | ||
569 | break; | ||
570 | }} | ||
571 | return MACRO_NONE; | ||
572 | }}; | ||
573 | """ | ||
574 | |||
575 | |||
576 | UNICODE_MACRO_TEMPLATE = """ | ||
577 | case {macro_id}: | ||
578 | unicode_action_function(0x{hi:02x}, 0x{lo:02x}); | ||
579 | break; | ||
580 | """.strip() | ||
581 | |||
582 | |||
583 | def unicode_macro_cases(config): | ||
584 | for macro_id, uc_hex in config['unicode_macros'].items(): | ||
585 | hi = int(uc_hex, 16) >> 8 | ||
586 | lo = int(uc_hex, 16) & 0xFF | ||
587 | unimacro_keys = ", ".join( | ||
588 | "T({})".format( | ||
589 | "KP_" + digit if digit.isdigit() else digit | ||
590 | ) for digit in uc_hex | ||
591 | ) | ||
592 | yield UNICODE_MACRO_TEMPLATE.format( | ||
593 | macro_id=macro_id, hi=hi, lo=lo | ||
594 | ) | ||
595 | |||
596 | |||
597 | def iter_keymap_lines(keymap, row_indents=None): | ||
598 | col_widths = {} | ||
599 | col = 0 | ||
600 | # first pass, figure out the column widths | ||
601 | prev_row_index = None | ||
602 | for code, row_index in keymap.values(): | ||
603 | if row_index != prev_row_index: | ||
604 | col = 0 | ||
605 | if row_indents: | ||
606 | col = row_indents[row_index] | ||
607 | col_widths[col] = max(len(code), col_widths.get(col, 0)) | ||
608 | prev_row_index = row_index | ||
609 | col += 1 | ||
610 | |||
611 | # second pass, yield the cell values | ||
612 | col = 0 | ||
613 | prev_row_index = None | ||
614 | for key_index in sorted(keymap): | ||
615 | code, row_index = keymap[key_index] | ||
616 | if row_index != prev_row_index: | ||
617 | col = 0 | ||
618 | yield "\n" | ||
619 | if row_indents: | ||
620 | for indent_col in range(row_indents[row_index]): | ||
621 | pad = " " * (col_widths[indent_col] - 4) | ||
622 | yield (" /*-*/" + pad) | ||
623 | col = row_indents[row_index] | ||
624 | else: | ||
625 | yield pad | ||
626 | yield " {}".format(code) | ||
627 | if key_index < len(keymap) - 1: | ||
628 | yield "," | ||
629 | # This will be yielded on the next iteration when | ||
630 | # we know that we're not at the end of a line. | ||
631 | pad = " " * (col_widths[col] - len(code)) | ||
632 | prev_row_index = row_index | ||
633 | col += 1 | ||
634 | |||
635 | |||
636 | def iter_keymap_parts(config, keymaps): | ||
637 | # includes | ||
638 | for include_path in config['keymaps_includes']: | ||
639 | yield '#include "{}"\n'.format(include_path) | ||
640 | |||
641 | yield "\n" | ||
642 | |||
643 | # definitions | ||
644 | for i, macro_id in enumerate(sorted(config['macro_ids'])): | ||
645 | yield "#define {} {}\n".format(macro_id, i) | ||
646 | |||
647 | yield "\n" | ||
648 | |||
649 | for i, layer_name in enumerate(config['layer_lines']): | ||
650 | yield '#define L{0:<3} {0:<5} // {1}\n'.format(i, layer_name) | ||
651 | |||
652 | yield "\n" | ||
653 | |||
654 | # keymaps | ||
655 | yield "const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {\n" | ||
656 | |||
657 | for i, layer_name in enumerate(config['layer_lines']): | ||
658 | # comment | ||
659 | layer_lines = config['layer_lines'][layer_name] | ||
660 | prefixed_lines = " * " + " * ".join(layer_lines) | ||
661 | yield "/*\n{} */\n".format(prefixed_lines) | ||
662 | |||
663 | # keymap codes | ||
664 | keymap = keymaps[layer_name] | ||
665 | row_indents = ROW_INDENTS.get(config['layout']) | ||
666 | keymap_lines = "".join(iter_keymap_lines(keymap, row_indents)) | ||
667 | yield "[L{0}] = LAYOUT_ergodox({1}\n),\n".format(i, keymap_lines) | ||
668 | |||
669 | yield "};\n\n" | ||
670 | |||
671 | # no idea what this is for | ||
672 | yield "const uint16_t PROGMEM fn_actions[] = {};\n" | ||
673 | |||
674 | # macros | ||
675 | yield MACROCODE.format( | ||
676 | macro_cases="", | ||
677 | unicode_macro_cases="\n".join(unicode_macro_cases(config)), | ||
678 | ) | ||
679 | |||
680 | # TODO: dynamically create blinking lights | ||
681 | yield USERCODE | ||
682 | |||
683 | |||
684 | def main(argv=sys.argv[1:]): | ||
685 | if not argv or '-h' in argv or '--help' in argv: | ||
686 | print(__doc__) | ||
687 | return 0 | ||
688 | |||
689 | in_path = os.path.abspath(argv[0]) | ||
690 | if not os.path.exists(in_path): | ||
691 | print("No such file '{}'".format(in_path)) | ||
692 | return 1 | ||
693 | |||
694 | if len(argv) > 1: | ||
695 | out_path = os.path.abspath(argv[1]) | ||
696 | else: | ||
697 | dirname = os.path.dirname(in_path) | ||
698 | out_path = os.path.join(dirname, "keymap.c") | ||
699 | |||
700 | config = parse_config(in_path) | ||
701 | valid_keys = parse_valid_keys(config, out_path) | ||
702 | keymaps = parse_keymaps(config, valid_keys) | ||
703 | |||
704 | with io.open(out_path, mode="w", encoding="utf-8") as fh: | ||
705 | for part in iter_keymap_parts(config, keymaps): | ||
706 | fh.write(part) | ||
707 | |||
708 | |||
709 | if __name__ == '__main__': | ||
710 | sys.exit(main()) | ||