Exemplo n.º 1
0
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)
Exemplo n.º 2
0
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)
Exemplo n.º 3
0
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
Exemplo n.º 4
0
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)
Exemplo n.º 5
0
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
Exemplo n.º 6
0
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
Exemplo n.º 7
0
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()]
Exemplo n.º 8
0
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'
Exemplo n.º 9
0
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
Exemplo n.º 10
0
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
Exemplo n.º 11
0
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