diff options
| -rw-r--r-- | data/schemas/keyboard.jsonschema | 33 | ||||
| -rw-r--r-- | lib/python/qmk/c_parse.py | 26 | ||||
| -rwxr-xr-x | lib/python/qmk/cli/generate/api.py | 2 | ||||
| -rwxr-xr-x | lib/python/qmk/cli/generate/rules_mk.py | 9 | ||||
| -rw-r--r-- | lib/python/qmk/info.py | 82 |
5 files changed, 117 insertions, 35 deletions
diff --git a/data/schemas/keyboard.jsonschema b/data/schemas/keyboard.jsonschema index 75e792b64..9355ee49b 100644 --- a/data/schemas/keyboard.jsonschema +++ b/data/schemas/keyboard.jsonschema | |||
| @@ -90,6 +90,9 @@ | |||
| 90 | "type": "object", | 90 | "type": "object", |
| 91 | "additionalProperties": false, | 91 | "additionalProperties": false, |
| 92 | "properties": { | 92 | "properties": { |
| 93 | "filename": { | ||
| 94 | "type": "string" | ||
| 95 | }, | ||
| 93 | "c_macro": { | 96 | "c_macro": { |
| 94 | "type": "boolean" | 97 | "type": "boolean" |
| 95 | }, | 98 | }, |
| @@ -119,6 +122,18 @@ | |||
| 119 | "type": "number", | 122 | "type": "number", |
| 120 | "min": 0.25 | 123 | "min": 0.25 |
| 121 | }, | 124 | }, |
| 125 | "r": { | ||
| 126 | "type": "number", | ||
| 127 | "min": 0 | ||
| 128 | }, | ||
| 129 | "rx": { | ||
| 130 | "type": "number", | ||
| 131 | "min": 0 | ||
| 132 | }, | ||
| 133 | "ry": { | ||
| 134 | "type": "number", | ||
| 135 | "min": 0 | ||
| 136 | }, | ||
| 122 | "w": { | 137 | "w": { |
| 123 | "type": "number", | 138 | "type": "number", |
| 124 | "min": 0.25 | 139 | "min": 0.25 |
| @@ -199,6 +214,12 @@ | |||
| 199 | "min": 0, | 214 | "min": 0, |
| 200 | "multipleOf": 1 | 215 | "multipleOf": 1 |
| 201 | }, | 216 | }, |
| 217 | "max_brightness": { | ||
| 218 | "type": "number", | ||
| 219 | "min": 0, | ||
| 220 | "max": 255, | ||
| 221 | "multipleOf": 1 | ||
| 222 | }, | ||
| 202 | "pin": { | 223 | "pin": { |
| 203 | "type": "string", | 224 | "type": "string", |
| 204 | "pattern": "^[A-K]\\d{1,2}$" | 225 | "pattern": "^[A-K]\\d{1,2}$" |
| @@ -207,6 +228,18 @@ | |||
| 207 | "type": "number", | 228 | "type": "number", |
| 208 | "min": 0, | 229 | "min": 0, |
| 209 | "multipleOf": 1 | 230 | "multipleOf": 1 |
| 231 | }, | ||
| 232 | "sleep": {"type": "boolean"}, | ||
| 233 | "split": {"type": "boolean"}, | ||
| 234 | "split_count": { | ||
| 235 | "type": "array", | ||
| 236 | "minLength": 2, | ||
| 237 | "maxLength": 2, | ||
| 238 | "items": { | ||
| 239 | "type": "number", | ||
| 240 | "min": 0, | ||
| 241 | "multipleOf": 1 | ||
| 242 | } | ||
| 210 | } | 243 | } |
| 211 | } | 244 | } |
| 212 | }, | 245 | }, |
diff --git a/lib/python/qmk/c_parse.py b/lib/python/qmk/c_parse.py index e41e271a4..67e196f0e 100644 --- a/lib/python/qmk/c_parse.py +++ b/lib/python/qmk/c_parse.py | |||
| @@ -1,12 +1,27 @@ | |||
| 1 | """Functions for working with config.h files. | 1 | """Functions for working with config.h files. |
| 2 | """ | 2 | """ |
| 3 | from pathlib import Path | 3 | from pathlib import Path |
| 4 | import re | ||
| 4 | 5 | ||
| 5 | from milc import cli | 6 | from milc import cli |
| 6 | 7 | ||
| 7 | from qmk.comment_remover import comment_remover | 8 | from qmk.comment_remover import comment_remover |
| 8 | 9 | ||
| 9 | default_key_entry = {'x': -1, 'y': 0, 'w': 1} | 10 | default_key_entry = {'x': -1, 'y': 0, 'w': 1} |
| 11 | single_comment_regex = re.compile(r' */[/*].*$') | ||
| 12 | multi_comment_regex = re.compile(r'/\*(.|\n)*\*/', re.MULTILINE) | ||
| 13 | |||
| 14 | |||
| 15 | def strip_line_comment(string): | ||
| 16 | """Removes comments from a single line string. | ||
| 17 | """ | ||
| 18 | return single_comment_regex.sub('', string) | ||
| 19 | |||
| 20 | |||
| 21 | def strip_multiline_comment(string): | ||
| 22 | """Removes comments from a single line string. | ||
| 23 | """ | ||
| 24 | return multi_comment_regex.sub('', string) | ||
| 10 | 25 | ||
| 11 | 26 | ||
| 12 | def c_source_files(dir_names): | 27 | def c_source_files(dir_names): |
| @@ -53,7 +68,8 @@ def find_layouts(file): | |||
| 53 | parsed_layout = [_default_key(key) for key in layout.split(',')] | 68 | parsed_layout = [_default_key(key) for key in layout.split(',')] |
| 54 | 69 | ||
| 55 | for key in parsed_layout: | 70 | for key in parsed_layout: |
| 56 | key['matrix'] = matrix_locations.get(key['label']) | 71 | if key['label'] in matrix_locations: |
| 72 | key['matrix'] = matrix_locations[key['label']] | ||
| 57 | 73 | ||
| 58 | parsed_layouts[macro_name] = { | 74 | parsed_layouts[macro_name] = { |
| 59 | 'key_count': len(parsed_layout), | 75 | 'key_count': len(parsed_layout), |
| @@ -88,12 +104,10 @@ def parse_config_h_file(config_h_file, config_h=None): | |||
| 88 | if config_h_file.exists(): | 104 | if config_h_file.exists(): |
| 89 | config_h_text = config_h_file.read_text() | 105 | config_h_text = config_h_file.read_text() |
| 90 | config_h_text = config_h_text.replace('\\\n', '') | 106 | config_h_text = config_h_text.replace('\\\n', '') |
| 107 | config_h_text = strip_multiline_comment(config_h_text) | ||
| 91 | 108 | ||
| 92 | for linenum, line in enumerate(config_h_text.split('\n')): | 109 | for linenum, line in enumerate(config_h_text.split('\n')): |
| 93 | line = line.strip() | 110 | line = strip_line_comment(line).strip() |
| 94 | |||
| 95 | if '//' in line: | ||
| 96 | line = line[:line.index('//')].strip() | ||
| 97 | 111 | ||
| 98 | if not line: | 112 | if not line: |
| 99 | continue | 113 | continue |
| @@ -156,6 +170,6 @@ def _parse_matrix_locations(matrix, file, macro_name): | |||
| 156 | row = row.replace('{', '').replace('}', '') | 170 | row = row.replace('{', '').replace('}', '') |
| 157 | for col_num, identifier in enumerate(row.split(',')): | 171 | for col_num, identifier in enumerate(row.split(',')): |
| 158 | if identifier != 'KC_NO': | 172 | if identifier != 'KC_NO': |
| 159 | matrix_locations[identifier] = (row_num, col_num) | 173 | matrix_locations[identifier] = [row_num, col_num] |
| 160 | 174 | ||
| 161 | return matrix_locations | 175 | return matrix_locations |
diff --git a/lib/python/qmk/cli/generate/api.py b/lib/python/qmk/cli/generate/api.py index 6d111f244..2fda38fc9 100755 --- a/lib/python/qmk/cli/generate/api.py +++ b/lib/python/qmk/cli/generate/api.py | |||
| @@ -48,7 +48,7 @@ def generate_api(cli): | |||
| 48 | if 'vid' in usb and usb['vid'] not in usb_list['devices']: | 48 | if 'vid' in usb and usb['vid'] not in usb_list['devices']: |
| 49 | usb_list['devices'][usb['vid']] = {} | 49 | usb_list['devices'][usb['vid']] = {} |
| 50 | 50 | ||
| 51 | if 'pid' in usb and usb['pid'] not in usb_list['devices'][usb['vid']]: | 51 | if 'vid' in usb and usb['pid'] not in usb_list['devices'][usb['vid']]: |
| 52 | usb_list['devices'][usb['vid']][usb['pid']] = {} | 52 | usb_list['devices'][usb['vid']][usb['pid']] = {} |
| 53 | 53 | ||
| 54 | if 'vid' in usb and 'pid' in usb: | 54 | if 'vid' in usb and 'pid' in usb: |
diff --git a/lib/python/qmk/cli/generate/rules_mk.py b/lib/python/qmk/cli/generate/rules_mk.py index 72ed3c45f..570ef5a0d 100755 --- a/lib/python/qmk/cli/generate/rules_mk.py +++ b/lib/python/qmk/cli/generate/rules_mk.py | |||
| @@ -41,9 +41,12 @@ def generate_rules_mk(cli): | |||
| 41 | # Find features that should be enabled | 41 | # Find features that should be enabled |
| 42 | if 'features' in kb_info_json: | 42 | if 'features' in kb_info_json: |
| 43 | for feature, enabled in kb_info_json['features'].items(): | 43 | for feature, enabled in kb_info_json['features'].items(): |
| 44 | feature = feature.upper() | 44 | if feature == 'bootmagic_lite' and enabled: |
| 45 | enabled = 'yes' if enabled else 'no' | 45 | rules_mk_lines.append(f'BOOTMAGIC_ENABLE := lite') |
| 46 | rules_mk_lines.append(f'{feature}_ENABLE := {enabled}') | 46 | else: |
| 47 | feature = feature.upper() | ||
| 48 | enabled = 'yes' if enabled else 'no' | ||
| 49 | rules_mk_lines.append(f'{feature}_ENABLE := {enabled}') | ||
| 47 | 50 | ||
| 48 | # Set the LED driver | 51 | # Set the LED driver |
| 49 | if 'led_matrix' in kb_info_json and 'driver' in kb_info_json['led_matrix']: | 52 | if 'led_matrix' in kb_info_json and 'driver' in kb_info_json['led_matrix']: |
diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py index 1cf12190d..39af88f79 100644 --- a/lib/python/qmk/info.py +++ b/lib/python/qmk/info.py | |||
| @@ -26,13 +26,12 @@ led_matrix_properties = { | |||
| 26 | } | 26 | } |
| 27 | 27 | ||
| 28 | rgblight_properties = { | 28 | rgblight_properties = { |
| 29 | 'led_count': 'RGBLED_NUM', | 29 | 'led_count': ('RGBLED_NUM', int), |
| 30 | 'pin': 'RGB_DI_PIN', | 30 | 'pin': ('RGB_DI_PIN', str), |
| 31 | 'split_count': 'RGBLED_SPLIT', | 31 | 'max_brightness': ('RGBLIGHT_LIMIT_VAL', int), |
| 32 | 'max_brightness': 'RGBLIGHT_LIMIT_VAL', | 32 | 'hue_steps': ('RGBLIGHT_HUE_STEP', int), |
| 33 | 'hue_steps': 'RGBLIGHT_HUE_STEP', | 33 | 'saturation_steps': ('RGBLIGHT_SAT_STEP', int), |
| 34 | 'saturation_steps': 'RGBLIGHT_SAT_STEP', | 34 | 'brightness_steps': ('RGBLIGHT_VAL_STEP', int) |
| 35 | 'brightness_steps': 'RGBLIGHT_VAL_STEP' | ||
| 36 | } | 35 | } |
| 37 | 36 | ||
| 38 | rgblight_toggles = { | 37 | rgblight_toggles = { |
| @@ -54,6 +53,8 @@ rgblight_animations = { | |||
| 54 | 'twinkle': 'RGBLIGHT_EFFECT_TWINKLE' | 53 | 'twinkle': 'RGBLIGHT_EFFECT_TWINKLE' |
| 55 | } | 54 | } |
| 56 | 55 | ||
| 56 | usb_properties = {'vid': 'VENDOR_ID', 'pid': 'PRODUCT_ID', 'device_ver': 'DEVICE_VER'} | ||
| 57 | |||
| 57 | true_values = ['1', 'on', 'yes'] | 58 | true_values = ['1', 'on', 'yes'] |
| 58 | false_values = ['0', 'off', 'no'] | 59 | false_values = ['0', 'off', 'no'] |
| 59 | 60 | ||
| @@ -97,7 +98,8 @@ def info_json(keyboard): | |||
| 97 | keyboard_api_validate(info_data) | 98 | keyboard_api_validate(info_data) |
| 98 | 99 | ||
| 99 | except jsonschema.ValidationError as e: | 100 | except jsonschema.ValidationError as e: |
| 100 | cli.log.error('Invalid info.json data: %s', e.message) | 101 | json_path = '.'.join([str(p) for p in e.absolute_path]) |
| 102 | cli.log.error('Invalid API data: %s: %s: %s', keyboard, json_path, e.message) | ||
| 101 | print(dir(e)) | 103 | print(dir(e)) |
| 102 | exit() | 104 | exit() |
| 103 | 105 | ||
| @@ -198,6 +200,9 @@ def _extract_indicators(info_data, config_c): | |||
| 198 | if json_key in info_data.get('indicators', []) and config_key in config_c: | 200 | if json_key in info_data.get('indicators', []) and config_key in config_c: |
| 199 | _log_warning(info_data, f'Indicator {json_key} is specified in both info.json and config.h, the config.h value wins.') | 201 | _log_warning(info_data, f'Indicator {json_key} is specified in both info.json and config.h, the config.h value wins.') |
| 200 | 202 | ||
| 203 | if 'indicators' not in info_data: | ||
| 204 | info_data['indicators'] = {} | ||
| 205 | |||
| 201 | if config_key in config_c: | 206 | if config_key in config_c: |
| 202 | if 'indicators' not in info_data: | 207 | if 'indicators' not in info_data: |
| 203 | info_data['indicators'] = {} | 208 | info_data['indicators'] = {} |
| @@ -226,10 +231,23 @@ def _extract_community_layouts(info_data, rules): | |||
| 226 | def _extract_features(info_data, rules): | 231 | def _extract_features(info_data, rules): |
| 227 | """Find all the features enabled in rules.mk. | 232 | """Find all the features enabled in rules.mk. |
| 228 | """ | 233 | """ |
| 234 | # Special handling for bootmagic which also supports a "lite" mode. | ||
| 235 | if rules.get('BOOTMAGIC_ENABLE') == 'lite': | ||
| 236 | rules['BOOTMAGIC_LITE_ENABLE'] = 'on' | ||
| 237 | del(rules['BOOTMAGIC_ENABLE']) | ||
| 238 | if rules.get('BOOTMAGIC_ENABLE') == 'full': | ||
| 239 | rules['BOOTMAGIC_ENABLE'] = 'on' | ||
| 240 | |||
| 241 | # Skip non-boolean features we haven't implemented special handling for | ||
| 242 | for feature in 'HAPTIC_ENABLE', 'QWIIC_ENABLE': | ||
| 243 | if rules.get(feature): | ||
| 244 | del(rules[feature]) | ||
| 245 | |||
| 246 | # Process the rest of the rules as booleans | ||
| 229 | for key, value in rules.items(): | 247 | for key, value in rules.items(): |
| 230 | if key.endswith('_ENABLE'): | 248 | if key.endswith('_ENABLE'): |
| 231 | key = '_'.join(key.split('_')[:-1]).lower() | 249 | key = '_'.join(key.split('_')[:-1]).lower() |
| 232 | value = True if value in true_values else False if value in false_values else value | 250 | value = True if value.lower() in true_values else False if value.lower() in false_values else value |
| 233 | 251 | ||
| 234 | if 'config_h_features' not in info_data: | 252 | if 'config_h_features' not in info_data: |
| 235 | info_data['config_h_features'] = {} | 253 | info_data['config_h_features'] = {} |
| @@ -280,12 +298,21 @@ def _extract_rgblight(info_data, config_c): | |||
| 280 | rgblight = info_data.get('rgblight', {}) | 298 | rgblight = info_data.get('rgblight', {}) |
| 281 | animations = rgblight.get('animations', {}) | 299 | animations = rgblight.get('animations', {}) |
| 282 | 300 | ||
| 283 | for json_key, config_key in rgblight_properties.items(): | 301 | if 'RGBLED_SPLIT' in config_c: |
| 302 | raw_split = config_c.get('RGBLED_SPLIT', '').replace('{', '').replace('}', '').strip() | ||
| 303 | rgblight['split_count'] = [int(i) for i in raw_split.split(',')] | ||
| 304 | |||
| 305 | for json_key, config_key_type in rgblight_properties.items(): | ||
| 306 | config_key, config_type = config_key_type | ||
| 307 | |||
| 284 | if config_key in config_c: | 308 | if config_key in config_c: |
| 285 | if json_key in rgblight: | 309 | if json_key in rgblight: |
| 286 | _log_warning(info_data, 'RGB Light: %s is specified in both info.json and config.h, the config.h value wins.' % (json_key,)) | 310 | _log_warning(info_data, 'RGB Light: %s is specified in both info.json and config.h, the config.h value wins.' % (json_key,)) |
| 287 | 311 | ||
| 288 | rgblight[json_key] = config_c[config_key] | 312 | try: |
| 313 | rgblight[json_key] = config_type(config_c[config_key]) | ||
| 314 | except ValueError as e: | ||
| 315 | cli.log.error('%s: config.h: Could not convert "%s" to %s: %s', info_data['keyboard_folder'], config_c[config_key], config_type.__name__, e) | ||
| 289 | 316 | ||
| 290 | for json_key, config_key in rgblight_toggles.items(): | 317 | for json_key, config_key in rgblight_toggles.items(): |
| 291 | if config_key in config_c: | 318 | if config_key in config_c: |
| @@ -332,11 +359,16 @@ def _extract_matrix_info(info_data, config_c): | |||
| 332 | 359 | ||
| 333 | info_data['matrix_pins'] = {} | 360 | info_data['matrix_pins'] = {} |
| 334 | 361 | ||
| 362 | # FIXME(skullydazed/anyone): Should really check every pin, not just the first | ||
| 335 | if row_pins: | 363 | if row_pins: |
| 336 | info_data['matrix_pins']['rows'] = row_pins.split(',') | 364 | row_pins = [pin.strip() for pin in row_pins.split(',') if pin] |
| 365 | if row_pins[0][0] in 'ABCDEFGHIJK' and row_pins[0][1].isdigit(): | ||
| 366 | info_data['matrix_pins']['rows'] = row_pins | ||
| 337 | 367 | ||
| 338 | if col_pins: | 368 | if col_pins: |
| 339 | info_data['matrix_pins']['cols'] = col_pins.split(',') | 369 | col_pins = [pin.strip() for pin in col_pins.split(',') if pin] |
| 370 | if col_pins[0][0] in 'ABCDEFGHIJK' and col_pins[0][1].isdigit(): | ||
| 371 | info_data['matrix_pins']['cols'] = col_pins | ||
| 340 | 372 | ||
| 341 | if direct_pins: | 373 | if direct_pins: |
| 342 | if 'matrix_pins' in info_data: | 374 | if 'matrix_pins' in info_data: |
| @@ -345,6 +377,9 @@ def _extract_matrix_info(info_data, config_c): | |||
| 345 | info_data['matrix_pins'] = {} | 377 | info_data['matrix_pins'] = {} |
| 346 | direct_pin_array = [] | 378 | direct_pin_array = [] |
| 347 | 379 | ||
| 380 | while direct_pins[-1] != '}': | ||
| 381 | direct_pins = direct_pins[:-1] | ||
| 382 | |||
| 348 | for row in direct_pins.split('},{'): | 383 | for row in direct_pins.split('},{'): |
| 349 | if row.startswith('{'): | 384 | if row.startswith('{'): |
| 350 | row = row[1:] | 385 | row = row[1:] |
| @@ -368,8 +403,6 @@ def _extract_matrix_info(info_data, config_c): | |||
| 368 | def _extract_usb_info(info_data, config_c): | 403 | def _extract_usb_info(info_data, config_c): |
| 369 | """Populate the USB information. | 404 | """Populate the USB information. |
| 370 | """ | 405 | """ |
| 371 | usb_properties = {'vid': 'VENDOR_ID', 'pid': 'PRODUCT_ID', 'device_ver': 'DEVICE_VER'} | ||
| 372 | |||
| 373 | if 'usb' not in info_data: | 406 | if 'usb' not in info_data: |
| 374 | info_data['usb'] = {} | 407 | info_data['usb'] = {} |
| 375 | 408 | ||
| @@ -378,10 +411,7 @@ def _extract_usb_info(info_data, config_c): | |||
| 378 | if info_name in info_data['usb']: | 411 | if info_name in info_data['usb']: |
| 379 | _log_warning(info_data, '%s in config.h is overwriting usb.%s in info.json' % (config_name, info_name)) | 412 | _log_warning(info_data, '%s in config.h is overwriting usb.%s in info.json' % (config_name, info_name)) |
| 380 | 413 | ||
| 381 | info_data['usb'][info_name] = config_c[config_name] | 414 | info_data['usb'][info_name] = '0x' + config_c[config_name][2:].upper() |
| 382 | |||
| 383 | elif info_name not in info_data['usb']: | ||
| 384 | _log_error(info_data, '%s not specified in config.h, and %s not specified in info.json. One is required.' % (config_name, info_name)) | ||
| 385 | 415 | ||
| 386 | return info_data | 416 | return info_data |
| 387 | 417 | ||
| @@ -519,8 +549,9 @@ def arm_processor_rules(info_data, rules): | |||
| 519 | info_data['processor'] = 'unknown' | 549 | info_data['processor'] = 'unknown' |
| 520 | 550 | ||
| 521 | if 'BOOTLOADER' in rules: | 551 | if 'BOOTLOADER' in rules: |
| 522 | if 'bootloader' in info_data: | 552 | # FIXME(skullydazed/anyone): need to remove the massive amounts of duplication first |
| 523 | _log_warning(info_data, 'Bootloader is specified in both info.json and rules.mk, the rules.mk value wins.') | 553 | # if 'bootloader' in info_data: |
| 554 | # _log_warning(info_data, 'Bootloader is specified in both info.json and rules.mk, the rules.mk value wins.') | ||
| 524 | 555 | ||
| 525 | info_data['bootloader'] = rules['BOOTLOADER'] | 556 | info_data['bootloader'] = rules['BOOTLOADER'] |
| 526 | 557 | ||
| @@ -558,8 +589,9 @@ def avr_processor_rules(info_data, rules): | |||
| 558 | info_data['processor'] = 'unknown' | 589 | info_data['processor'] = 'unknown' |
| 559 | 590 | ||
| 560 | if 'BOOTLOADER' in rules: | 591 | if 'BOOTLOADER' in rules: |
| 561 | if 'bootloader' in info_data: | 592 | # FIXME(skullydazed/anyone): need to remove the massive amounts of duplication first |
| 562 | _log_warning(info_data, 'Bootloader is specified in both info.json and rules.mk, the rules.mk value wins.') | 593 | # if 'bootloader' in info_data: |
| 594 | # _log_warning(info_data, 'Bootloader is specified in both info.json and rules.mk, the rules.mk value wins.') | ||
| 563 | 595 | ||
| 564 | info_data['bootloader'] = rules['BOOTLOADER'] | 596 | info_data['bootloader'] = rules['BOOTLOADER'] |
| 565 | else: | 597 | else: |
| @@ -593,8 +625,8 @@ def merge_info_jsons(keyboard, info_data): | |||
| 593 | keyboard_validate(new_info_data) | 625 | keyboard_validate(new_info_data) |
| 594 | 626 | ||
| 595 | except jsonschema.ValidationError as e: | 627 | except jsonschema.ValidationError as e: |
| 596 | cli.log.error('Invalid info.json data: %s', e.message) | 628 | json_path = '.'.join([str(p) for p in e.absolute_path]) |
| 597 | cli.log.error('Not including file %s', info_file) | 629 | cli.log.error('Invalid info.json data: %s: %s: %s', info_file, json_path, e.message) |
| 598 | continue | 630 | continue |
| 599 | 631 | ||
| 600 | if not isinstance(new_info_data, dict): | 632 | if not isinstance(new_info_data, dict): |
