def list_keymaps(keyboard): """ List the available keymaps for a keyboard. Args: keyboard: the keyboards full name with vendor and revision if necessary, example: clueboard/66/rev3 Returns: a set with the names of the available keymaps """ # parse all the rules.mk files for the keyboard rules = rules_mk(keyboard) names = set() if rules: # qmk_firmware/keyboards keyboards_dir = Path('keyboards') # path to the keyboard's directory kb_path = keyboards_dir / keyboard # walk up the directory tree until keyboards_dir # and collect all directories' name with keymap.c file in it while kb_path != keyboards_dir: keymaps_dir = kb_path / "keymaps" if keymaps_dir.exists(): names = names.union([keymap.name for keymap in keymaps_dir.iterdir() if is_keymap_dir(keymap)]) kb_path = kb_path.parent # if community layouts are supported, get them if "LAYOUTS" in rules: for layout in rules["LAYOUTS"].split(): cl_path = Path('layouts/community') / layout if cl_path.exists(): names = names.union([keymap.name for keymap in cl_path.iterdir() if is_keymap_dir(keymap)]) return sorted(names)
def list_keymaps(keyboard, c=True, json=True, additional_files=None, fullpath=False): """List the available keymaps for a keyboard. Args: keyboard The keyboards full name with vendor and revision if necessary, example: clueboard/66/rev3 c When true include `keymap.c` keymaps. json When true include `keymap.json` keymaps. additional_files A sequence of additional filenames to check against to determine if a directory is a keymap. All files must exist for a match to happen. For example, if you want to match a C keymap with both a `config.h` and `rules.mk` file: `is_keymap_dir(keymap_dir, json=False, additional_files=['config.h', 'rules.mk'])` fullpath When set to True the full path of the keymap relative to the `qmk_firmware` root will be provided. Returns: a sorted list of valid keymap names. """ # parse all the rules.mk files for the keyboard rules = rules_mk(keyboard) names = set() if rules: keyboards_dir = Path('keyboards') kb_path = keyboards_dir / keyboard # walk up the directory tree until keyboards_dir # and collect all directories' name with keymap.c file in it while kb_path != keyboards_dir: keymaps_dir = kb_path / "keymaps" if keymaps_dir.is_dir(): for keymap in keymaps_dir.iterdir(): if is_keymap_dir(keymap, c, json, additional_files): keymap = keymap if fullpath else keymap.name names.add(keymap) kb_path = kb_path.parent # if community layouts are supported, get them if "LAYOUTS" in rules: for layout in rules["LAYOUTS"].split(): cl_path = Path('layouts/community') / layout if cl_path.is_dir(): for keymap in cl_path.iterdir(): if is_keymap_dir(keymap, c, json, additional_files): keymap = keymap if fullpath else keymap.name names.add(keymap) return sorted(names)
def _extract_rules_mk(info_data): """Pull some keyboard information from existing rules.mk files """ rules = rules_mk(info_data['keyboard_folder']) mcu = rules.get('MCU') if mcu in ARM_PROCESSORS: arm_processor_rules(info_data, rules) elif mcu in AVR_PROCESSORS: avr_processor_rules(info_data, rules) else: cli.log.warning("%s: Unknown MCU: %s" % (info_data['keyboard_folder'], mcu)) unknown_processor_rules(info_data, rules) return info_data
def _extract_rules_mk(info_data): """Pull some keyboard information from existing rules.mk files """ rules = rules_mk(info_data['keyboard_folder']) mcu = rules.get('MCU') if mcu in CHIBIOS_PROCESSORS: return arm_processor_rules(info_data, rules) elif mcu in LUFA_PROCESSORS + VUSB_PROCESSORS: return avr_processor_rules(info_data, rules) msg = "Unknown MCU: " + str(mcu) _log_warning(info_data, msg) return unknown_processor_rules(info_data, rules)
def _find_all_layouts(keyboard): """Looks for layout macros associated with this keyboard. """ layouts = {} rules = rules_mk(keyboard) keyboard_path = Path(rules.get('DEFAULT_FOLDER', keyboard)) # Pull in all layouts defined in the standard files current_path = Path('keyboards/') for directory in keyboard_path.parts: current_path = current_path / directory keyboard_h = '%s.h' % (directory, ) keyboard_h_path = current_path / keyboard_h if keyboard_h_path.exists(): layouts.update(find_layouts(keyboard_h_path)) if not layouts: # If we didn't find any layouts above we widen our search. This is error # prone which is why we want to encourage people to follow the standard above. cli.log.warning( '%s: Falling back to searching for KEYMAP/LAYOUT macros.' % (keyboard)) for file in glob('keyboards/%s/*.h' % keyboard): if file.endswith('.h'): these_layouts = find_layouts(file) if these_layouts: layouts.update(these_layouts) if 'LAYOUTS' in rules: # Match these up against the supplied layouts supported_layouts = rules['LAYOUTS'].strip().split() for layout_name in sorted(layouts): if not layout_name.startswith('LAYOUT_'): continue layout_name = layout_name[7:] if layout_name in supported_layouts: supported_layouts.remove(layout_name) if supported_layouts: cli.log.error('%s: Missing LAYOUT() macro for %s' % (keyboard, ', '.join(supported_layouts))) return layouts
def _extract_rules_mk(info_data): """Pull some keyboard information from existing rules.mk files """ rules = rules_mk(info_data['keyboard_folder']) mcu = rules.get('MCU', info_data.get('processor')) if mcu in CHIBIOS_PROCESSORS: arm_processor_rules(info_data, rules) elif mcu in LUFA_PROCESSORS + VUSB_PROCESSORS: avr_processor_rules(info_data, rules) else: cli.log.warning("%s: Unknown MCU: %s" % (info_data['keyboard_folder'], mcu)) unknown_processor_rules(info_data, rules) _extract_community_layouts(info_data, rules) _extract_features(info_data, rules) _extract_led_drivers(info_data, rules) return info_data
def find_info_json(keyboard): """Finds all the info.json files associated with a keyboard. """ # Find the most specific first base_path = Path('keyboards') keyboard_path = base_path / keyboard keyboard_parent = keyboard_path.parent info_jsons = [keyboard_path / 'info.json'] # Add DEFAULT_FOLDER before parents, if present rules = rules_mk(keyboard) if 'DEFAULT_FOLDER' in rules: info_jsons.append(Path(rules['DEFAULT_FOLDER']) / 'info.json') # Add in parent folders for least specific for _ in range(5): info_jsons.append(keyboard_parent / 'info.json') if keyboard_parent.parent == base_path: break keyboard_parent = keyboard_parent.parent # Return a list of the info.json files that actually exist return [info_json for info_json in info_jsons if info_json.exists()]
def locate_keymap(keyboard, keymap): """Returns the path to a keymap for a specific keyboard. """ if not qmk.path.is_keyboard(keyboard): raise KeyError('Invalid keyboard: ' + repr(keyboard)) # Check the keyboard folder first, last match wins checked_dirs = '' keymap_path = '' for dir in keyboard.split('/'): if checked_dirs: checked_dirs = '/'.join((checked_dirs, dir)) else: checked_dirs = dir keymap_dir = Path('keyboards') / checked_dirs / 'keymaps' if (keymap_dir / keymap / 'keymap.c').exists(): keymap_path = keymap_dir / keymap / 'keymap.c' if (keymap_dir / keymap / 'keymap.json').exists(): keymap_path = keymap_dir / keymap / 'keymap.json' if keymap_path: return keymap_path # Check community layouts as a fallback rules = rules_mk(keyboard) if "LAYOUTS" in rules: for layout in rules["LAYOUTS"].split(): community_layout = Path('layouts/community') / layout / keymap if community_layout.exists(): if (community_layout / 'keymap.json').exists(): return community_layout / 'keymap.json' if (community_layout / 'keymap.c').exists(): return community_layout / 'keymap.c'
def print_parsed_rules_mk(keyboard_name): rules = rules_mk(keyboard_name) for k in sorted(rules.keys()): print('%s = %s' % (k, rules[k])) return
def _extract_rules_mk(info_data): """Pull some keyboard information from existing rules.mk files """ rules = rules_mk(info_data['keyboard_folder']) info_data['processor'] = rules.get('MCU', info_data.get('processor', 'atmega32u4')) if info_data['processor'] in CHIBIOS_PROCESSORS: arm_processor_rules(info_data, rules) elif info_data['processor'] in LUFA_PROCESSORS + VUSB_PROCESSORS: avr_processor_rules(info_data, rules) else: cli.log.warning("%s: Unknown MCU: %s" % (info_data['keyboard_folder'], info_data['processor'])) unknown_processor_rules(info_data, rules) # Pull in data from the json map dotty_info = dotty(info_data) info_rules_map = json_load(Path('data/mappings/info_rules.json')) for rules_key, info_dict in info_rules_map.items(): info_key = info_dict['info_key'] key_type = info_dict.get('value_type', 'str') try: if rules_key in rules and info_dict.get('to_json', True): if dotty_info.get(info_key) and info_dict.get('warn_duplicate', True): _log_warning(info_data, '%s in rules.mk is overwriting %s in info.json' % (rules_key, info_key)) if key_type.startswith('array'): if '.' in key_type: key_type, array_type = key_type.split('.', 1) else: array_type = None rules_value = rules[rules_key].replace('{', '').replace('}', '').strip() if array_type == 'int': dotty_info[info_key] = list(map(int, rules_value.split(','))) else: dotty_info[info_key] = rules_value.split(',') elif key_type == 'list': dotty_info[info_key] = rules[rules_key].split() elif key_type == 'bool': dotty_info[info_key] = rules[rules_key] in true_values elif key_type == 'hex': dotty_info[info_key] = '0x' + rules[rules_key][2:].upper() elif key_type == 'int': dotty_info[info_key] = int(rules[rules_key]) else: dotty_info[info_key] = rules[rules_key] except Exception as e: _log_warning(info_data, f'{rules_key}->{info_key}: {e}') info_data.update(dotty_info) # Merge in config values that can't be easily mapped _extract_features(info_data, rules) return info_data
def info_json(keyboard): """Generate the info.json data for a specific keyboard. """ cur_dir = Path('keyboards') root_rules_mk = parse_rules_mk_file(cur_dir / keyboard / 'rules.mk') if 'DEFAULT_FOLDER' in root_rules_mk: keyboard = root_rules_mk['DEFAULT_FOLDER'] info_data = { 'keyboard_name': str(keyboard), 'keyboard_folder': str(keyboard), 'keymaps': {}, 'layouts': {}, 'parse_errors': [], 'parse_warnings': [], 'maintainer': 'qmk', } # Populate the list of JSON keymaps for keymap in list_keymaps(keyboard, c=False, fullpath=True): info_data['keymaps'][keymap.name] = { 'url': f'https://raw.githubusercontent.com/qmk/qmk_firmware/master/{keymap}/keymap.json' } # Populate layout data layouts, aliases = _search_keyboard_h(keyboard) if aliases: info_data['layout_aliases'] = aliases for layout_name, layout_json in layouts.items(): if not layout_name.startswith('LAYOUT_kc'): layout_json['c_macro'] = True info_data['layouts'][layout_name] = layout_json # Merge in the data from info.json, config.h, and rules.mk info_data = merge_info_jsons(keyboard, info_data) info_data = _process_defaults(info_data) info_data = _extract_rules_mk(info_data, rules_mk(str(keyboard))) info_data = _extract_config_h(info_data, config_h(str(keyboard))) # Ensure that we have matrix row and column counts info_data = _matrix_size(info_data) # Merge in data from <keyboard.c> info_data = _extract_led_config(info_data, str(keyboard)) # Validate against the jsonschema try: validate(info_data, 'qmk.api.keyboard.v1') except jsonschema.ValidationError as e: json_path = '.'.join([str(p) for p in e.absolute_path]) cli.log.error('Invalid API data: %s: %s: %s', keyboard, json_path, e.message) exit(1) # Make sure we have at least one layout if not info_data.get('layouts'): _find_missing_layouts(info_data, keyboard) if not info_data.get('layouts'): _log_error( info_data, 'No LAYOUTs defined! Need at least one layout defined in the keyboard.h or info.json.' ) # Filter out any non-existing community layouts for layout in info_data.get('community_layouts', []): if not _valid_community_layout(layout): # Ignore layout from future checks info_data['community_layouts'].remove(layout) _log_error( info_data, 'Claims to support a community layout that does not exist: %s' % (layout)) # Make sure we supply layout macros for the community layouts we claim to support for layout in info_data.get('community_layouts', []): layout_name = 'LAYOUT_' + layout if layout_name not in info_data.get( 'layouts', {}) and layout_name not in info_data.get( 'layout_aliases', {}): _log_error( info_data, 'Claims to support community layout %s but no %s() macro found' % (layout, layout_name)) # Check that the reported matrix size is consistent with the actual matrix size _check_matrix(info_data) return info_data