Esempio n. 1
0
def set_config(section, option, value):
    """Set a config key in the running config.
    """
    log_string = '%s.%s{fg_cyan}:{fg_reset} %s {fg_cyan}->{fg_reset} %s'
    if cli.args.read_only:
        log_string += ' {fg_red}(change not written)'

    cli.echo(log_string, section, option, cli.config[section][option], value)

    if not cli.args.read_only:
        if value == 'None':
            del cli.config[section][option]
        else:
            cli.config[section][option] = value
Esempio n. 2
0
def show_keymap(kb_info_json, title_caps=True):
    """Render the keymap in ascii art.
    """
    keymap_path = locate_keymap(cli.config.info.keyboard, cli.config.info.keymap)

    if keymap_path and keymap_path.suffix == '.json':
        keymap_data = json.load(keymap_path.open(encoding='utf-8'))
        layout_name = keymap_data['layout']
        layout_name = kb_info_json.get('layout_aliases', {}).get(layout_name, layout_name)  # Resolve alias names

        for layer_num, layer in enumerate(keymap_data['layers']):
            if title_caps:
                cli.echo('{fg_cyan}Keymap %s Layer %s{fg_reset}:', cli.config.info.keymap, layer_num)
            else:
                cli.echo('{fg_cyan}keymap.%s.layer.%s{fg_reset}:', cli.config.info.keymap, layer_num)
Esempio n. 3
0
def flash(cli):
    """Compile and or flash QMK Firmware or keyboard/layout

    If a Configurator JSON export is supplied this command will create a new keymap. Keymap and Keyboard arguments
    will be ignored.

    If no file is supplied, keymap and keyboard are expected.

    If bootloader is omitted, the one according to the rules.mk will be used.

    """
    if cli.args.bootloaders:
        # Provide usage and list bootloaders
        cli.echo(
            'usage: qmk flash [-h] [-b] [-kb KEYBOARD] [-km KEYMAP] [-bl BOOTLOADER] [filename]'
        )
        print_bootloader_help()
        return False

    if cli.args.filename:
        # Handle compiling a configurator JSON
        user_keymap = parse_configurator_json(cli.args.filename)
        keymap_path = qmk.path.keymap(user_keymap['keyboard'])
        command = compile_configurator_json(user_keymap, cli.args.bootloader)

        cli.log.info('Wrote keymap to {fg_cyan}%s/%s/keymap.c', keymap_path,
                     user_keymap['keymap'])

    else:
        # Perform the action the user specified
        user_keyboard, user_keymap = find_keyboard_keymap()
        if user_keyboard and user_keymap:
            # Generate the make command for a specific keyboard/keymap.
            command = create_make_command(user_keyboard, user_keymap,
                                          cli.args.bootloader)

        else:
            cli.log.error(
                'You must supply a configurator export or both `--keyboard` and `--keymap`.'
            )
            cli.echo(
                'usage: qmk flash [-h] [-b] [-kb KEYBOARD] [-km KEYMAP] [-bl BOOTLOADER] [filename]'
            )
            return False

    cli.log.info('Flashing keymap with {fg_cyan}%s\n\n', ' '.join(command))
    subprocess.run(command)
Esempio n. 4
0
def list_keyboards(cli):
    """List the keyboards currently defined within QMK
    """

    base_path = os.path.join(os.getcwd(), "keyboards") + os.path.sep
    kb_path_wildcard = os.path.join(base_path, "**", "rules.mk")

    # find everywhere we have rules.mk where keymaps isn't in the path
    paths = [path for path in glob.iglob(kb_path_wildcard, recursive=True) if 'keymaps' not in path]

    # strip the keyboard directory path prefix and rules.mk suffix and alphabetize
    find_name = lambda path: path.replace(base_path, "").replace(os.path.sep + "rules.mk", "")
    names = sorted(map(find_name, paths))

    for name in names:
        # We echo instead of cli.log.info to allow easier piping of this output
        cli.echo(name)
Esempio n. 5
0
def print_text_output(kb_info_json):
    """Print the info.json in a plain text format.
    """
    for key in sorted(kb_info_json):
        if key == 'layouts':
            cli.echo('{fg_blue}layouts{fg_reset}: %s', ', '.join(sorted(kb_info_json['layouts'].keys())))
        else:
            cli.echo('{fg_blue}%s{fg_reset}: %s', key, kb_info_json[key])

    if cli.config.info.layouts:
        show_layouts(kb_info_json, False)

    if cli.config.info.matrix:
        show_matrix(kb_info_json, False)

    if cli.config_source.info.keymap and cli.config_source.info.keymap != 'config_file':
        show_keymap(kb_info_json, False)
Esempio n. 6
0
def compile(cli):
    """Compile a QMK Firmware.

    If a Configurator export is supplied this command will create a new keymap, overwriting an existing keymap if one exists.

    If a keyboard and keymap are provided this command will build a firmware based on that.
    """
    command = None

    if cli.args.filename:
        # If a configurator JSON was provided generate a keymap and compile it
        # FIXME(skullydazed): add code to check and warn if the keymap already exists when compiling a json keymap.
        user_keymap = parse_configurator_json(cli.args.filename)
        command = compile_configurator_json(user_keymap)

    else:
        if cli.config.compile.keyboard and cli.config.compile.keymap:
            # Generate the make command for a specific keyboard/keymap.
            command = create_make_command(cli.config.compile.keyboard,
                                          cli.config.compile.keymap)

        elif not cli.config.compile.keyboard:
            cli.log.error('Could not determine keyboard!')
        elif not cli.config.compile.keymap:
            cli.log.error('Could not determine keymap!')

    # Compile the firmware, if we're able to
    if command:
        cli.log.info('Compiling keymap with {fg_cyan}%s', ' '.join(command))
        if not cli.args.dry_run:
            cli.echo('\n')
            compile = subprocess.run(command)
            return compile.returncode

    else:
        cli.log.error(
            'You must supply a configurator export, both `--keyboard` and `--keymap`, or be in a directory for a keyboard or keymap.'
        )
        cli.echo(
            'usage: qmk compile [-h] [-b] [-kb KEYBOARD] [-km KEYMAP] [filename]'
        )
        return False
Esempio n. 7
0
def list_keymaps(cli):
    """List the keymaps for a specific keyboard
    """
    try:
        for name in qmk.keymap.list_keymaps(cli.config.list_keymaps.keyboard):
            # We echo instead of cli.log.info to allow easier piping of this output
            cli.echo('%s', name)
    except NoSuchKeyboardError as e:
        cli.echo("{fg_red}%s: %s", cli.config.list_keymaps.keyboard, e.message)
    except (FileNotFoundError, PermissionError) as e:
        cli.echo("{fg_red}%s: %s", cli.config.list_keymaps.keyboard, e)
    except TypeError:
        cli.echo("{fg_red}Something went wrong. Did you specify a keyboard?")
Esempio n. 8
0
    def run_forever(self):
        while True:
            try:
                message = {**self.hid_device, 'text': self.read_line()}
                identifier = (
                    int2hex(message['vendor_id']),
                    int2hex(message['product_id'])) if self.numeric else (
                        message['manufacturer_string'],
                        message['product_string'])
                message['identifier'] = ':'.join(identifier)
                message['ts'] = '{style_dim}{fg_green}%s{style_reset_all} ' % (
                    strftime(cli.config.general.datetime_fmt),
                ) if cli.args.timestamp else ''

                cli.echo(
                    '%(ts)s%(color)s%(identifier)s:%(index)d{style_reset_all}: %(text)s'
                    % message)

            except hid.HIDException:
                break
Esempio n. 9
0
def show_matrix(kb_info_json, title_caps=True):
    """Render the layout with matrix labels in ascii art.
    """
    for layout_name, layout in kb_info_json['layouts'].items():
        # Build our label list
        labels = []
        for key in layout['layout']:
            if 'matrix' in key:
                row = ROW_LETTERS[key['matrix'][0]]
                col = COL_LETTERS[key['matrix'][1]]

                labels.append(row + col)
            else:
                labels.append('')

        # Print the header
        if title_caps:
            cli.echo('{fg_blue}Matrix for "%s"{fg_reset}:', layout_name)
        else:
            cli.echo('{fg_blue}matrix_%s{fg_reset}:', layout_name)
Esempio n. 10
0
def flash(cli):
    """Compile and or flash QMK Firmware or keyboard/layout

    If a Configurator JSON export is supplied this command will create a new keymap. Keymap and Keyboard arguments
    will be ignored.

    If no file is supplied, keymap and keyboard are expected.

    If bootloader is omitted the make system will use the configured bootloader for that keyboard.
    """
    command = ''

    if cli.args.bootloaders:
        # Provide usage and list bootloaders
        cli.echo(
            'usage: qmk flash [-h] [-b] [-n] [-kb KEYBOARD] [-km KEYMAP] [-bl BOOTLOADER] [filename]'
        )
        print_bootloader_help()
        return False

    if cli.args.filename:
        # Handle compiling a configurator JSON
        user_keymap = parse_configurator_json(cli.args.filename)
        keymap_path = qmk.path.keymap(user_keymap['keyboard'])
        command = compile_configurator_json(user_keymap, cli.args.bootloader)

        cli.log.info('Wrote keymap to {fg_cyan}%s/%s/keymap.c', keymap_path,
                     user_keymap['keymap'])

    else:
        if cli.config.flash.keyboard and cli.config.flash.keymap:
            # Generate the make command for a specific keyboard/keymap.
            command = create_make_command(cli.config.flash.keyboard,
                                          cli.config.flash.keymap,
                                          cli.args.bootloader)

        elif not cli.config.flash.keyboard:
            cli.log.error('Could not determine keyboard!')
        elif not cli.config.flash.keymap:
            cli.log.error('Could not determine keymap!')

    # Compile the firmware, if we're able to
    if command:
        cli.log.info('Compiling keymap with {fg_cyan}%s', ' '.join(command))
        if not cli.args.dry_run:
            cli.echo('\n')
            compile = subprocess.run(command)
            return compile.returncode

    else:
        cli.log.error(
            'You must supply a configurator export, both `--keyboard` and `--keymap`, or be in a directory for a keyboard or keymap.'
        )
        cli.echo(
            'usage: qmk flash [-h] [-b] [-n] [-kb KEYBOARD] [-km KEYMAP] [-bl BOOTLOADER] [filename]'
        )
        return False
Esempio n. 11
0
def print_dotted_output(kb_info_json, prefix=''):
    """Print the info.json in a plain text format with dot-joined keys.
    """
    for key in sorted(kb_info_json):
        new_prefix = f'{prefix}.{key}' if prefix else key

        if key in ['parse_errors', 'parse_warnings']:
            continue
        elif key == 'layouts' and prefix == '':
            cli.echo('{fg_blue}layouts{fg_reset}: %s', ', '.join(sorted(kb_info_json['layouts'].keys())))
        elif isinstance(kb_info_json[key], dict):
            print_dotted_output(kb_info_json[key], new_prefix)
        elif isinstance(kb_info_json[key], list):
            cli.echo('{fg_blue}%s{fg_reset}: %s', new_prefix, ', '.join(map(str, sorted(kb_info_json[key]))))
        else:
            cli.echo('{fg_blue}%s{fg_reset}: %s', new_prefix, kb_info_json[key])
Esempio n. 12
0
def flash(cli):
    """Compile and or flash QMK Firmware or keyboard/layout

    If a Configurator JSON export is supplied this command will create a new keymap. Keymap and Keyboard arguments
    will be ignored.

    If no file is supplied, keymap and keyboard are expected.

    If bootloader is omitted, the one according to the rules.mk will be used.

    """
    command = []
    if cli.args.bootloaders:
        # Provide usage and list bootloaders
        cli.echo('usage: qmk flash [-h] [-b] [-kb KEYBOARD] [-km KEYMAP] [-bl BOOTLOADER] [filename]')
        print_bootloader_help()
        return False

    elif cli.args.keymap and not cli.args.keyboard:
        # If only a keymap was given but no keyboard, suggest listing keyboards
        cli.echo('usage: qmk flash [-h] [-b] [-kb KEYBOARD] [-km KEYMAP] [-bl BOOTLOADER] [filename]')
        cli.log.error('run \'qmk list_keyboards\' to find out the supported keyboards')
        return False

    elif cli.args.filename:
        # Get keymap path to log info
        user_keymap = parse_configurator_json(cli.args.filename)
        keymap_path = qmk.path.keymap(user_keymap['keyboard'])

        cli.log.info('Creating {fg_cyan}%s{style_reset_all} keymap in {fg_cyan}%s', user_keymap['keymap'], keymap_path)

        # Convert the JSON into a C file and write it to disk.
        command = compile_configurator_json(user_keymap, cli.args.bootloader)

        cli.log.info('Wrote keymap to {fg_cyan}%s/%s/keymap.c', keymap_path, user_keymap['keymap'])

    elif cli.args.keyboard and cli.args.keymap:
        # Generate the make command for a specific keyboard/keymap.
        command = create_make_command(cli.config.flash.keyboard, cli.config.flash.keymap, cli.args.bootloader)

    else:
        cli.echo('usage: qmk flash [-h] [-b] [-kb KEYBOARD] [-km KEYMAP] [-bl BOOTLOADER] [filename]')
        cli.log.error('You must supply a configurator export or both `--keyboard` and `--keymap`. You can also specify a bootloader with --bootloader. Use --bootloaders to list the available bootloaders.')
        return False

    cli.log.info('Flashing keymap with {fg_cyan}%s\n\n', ' '.join(command))
    subprocess.run(command)
Esempio n. 13
0
def show_keymap(kb_info_json, title_caps=True):
    """Render the keymap in ascii art.
    """
    keymap_path = locate_keymap(cli.config.info.keyboard,
                                cli.config.info.keymap)

    if keymap_path and keymap_path.suffix == '.json':
        if title_caps:
            cli.echo('{fg_blue}Keymap "%s"{fg_reset}:', cli.config.info.keymap)
        else:
            cli.echo('{fg_blue}keymap_%s{fg_reset}:', cli.config.info.keymap)

        keymap_data = json.load(keymap_path.open(encoding='utf-8'))
        layout_name = keymap_data['layout']

        for layer_num, layer in enumerate(keymap_data['layers']):
            if title_caps:
                cli.echo('{fg_cyan}Layer %s{fg_reset}:', layer_num)
            else:
                cli.echo('{fg_cyan}layer_%s{fg_reset}:', layer_num)

            print(
                render_layout(kb_info_json['layouts'][layout_name]['layout'],
                              cli.config.info.ascii, layer))
Esempio n. 14
0
def print_friendly_output(kb_info_json):
    """Print the info.json in a friendly text format.
    """
    cli.echo('{fg_blue}Keyboard Name{fg_reset}: %s',
             kb_info_json.get('keyboard_name', 'Unknown'))
    cli.echo('{fg_blue}Manufacturer{fg_reset}: %s',
             kb_info_json.get('manufacturer', 'Unknown'))
    if 'url' in kb_info_json:
        cli.echo('{fg_blue}Website{fg_reset}: %s', kb_info_json.get('url', ''))
    if kb_info_json.get('maintainer', 'qmk') == 'qmk':
        cli.echo('{fg_blue}Maintainer{fg_reset}: QMK Community')
    else:
        cli.echo('{fg_blue}Maintainer{fg_reset}: %s',
                 kb_info_json['maintainer'])
    cli.echo('{fg_blue}Keyboard Folder{fg_reset}: %s',
             kb_info_json.get('keyboard_folder', 'Unknown'))
    cli.echo('{fg_blue}Layouts{fg_reset}: %s',
             ', '.join(sorted(kb_info_json['layouts'].keys())))
    if 'width' in kb_info_json and 'height' in kb_info_json:
        cli.echo('{fg_blue}Size{fg_reset}: %s x %s' %
                 (kb_info_json['width'], kb_info_json['height']))
    cli.echo('{fg_blue}Processor{fg_reset}: %s',
             kb_info_json.get('processor', 'Unknown'))
    cli.echo('{fg_blue}Bootloader{fg_reset}: %s',
             kb_info_json.get('bootloader', 'Unknown'))
    if 'layout_aliases' in kb_info_json:
        aliases = [
            f'{key}={value}'
            for key, value in kb_info_json['layout_aliases'].items()
        ]
        cli.echo('{fg_blue}Layout aliases:{fg_reset} %s' %
                 (', '.join(aliases), ))

    if cli.config.info.layouts:
        show_layouts(kb_info_json, True)

    if cli.config.info.matrix:
        show_matrix(kb_info_json, True)

    if cli.config_source.info.keymap and cli.config_source.info.keymap != 'config_file':
        show_keymap(kb_info_json, True)
Esempio n. 15
0
def print_friendly_output(kb_info_json):
    """Print the info.json in a friendly text format.
    """
    cli.echo('{fg_blue}Keyboard Name{fg_reset}: %s',
             kb_info_json.get('keyboard_name', 'Unknown'))
    cli.echo('{fg_blue}Manufacturer{fg_reset}: %s',
             kb_info_json.get('manufacturer', 'Unknown'))
    if 'url' in kb_info_json:
        cli.echo('{fg_blue}Website{fg_reset}: %s', kb_info_json.get('url', ''))
    if kb_info_json.get('maintainer', 'qmk') == 'qmk':
        cli.echo('{fg_blue}Maintainer{fg_reset}: QMK Community')
    else:
        cli.echo('{fg_blue}Maintainer{fg_reset}: %s',
                 kb_info_json['maintainer'])
    cli.echo('{fg_blue}Keyboard Folder{fg_reset}: %s',
             kb_info_json.get('keyboard_folder', 'Unknown'))
    cli.echo('{fg_blue}Layouts{fg_reset}: %s',
             ', '.join(sorted(kb_info_json['layouts'].keys())))
    cli.echo('{fg_blue}Processor{fg_reset}: %s',
             kb_info_json.get('processor', 'Unknown'))
    cli.echo('{fg_blue}Bootloader{fg_reset}: %s',
             kb_info_json.get('bootloader', 'Unknown'))
    if 'layout_aliases' in kb_info_json:
        aliases = [
            f'{key}={value}'
            for key, value in kb_info_json['layout_aliases'].items()
        ]
        cli.echo('{fg_blue}Layout aliases:{fg_reset} %s' %
                 (', '.join(aliases), ))
Esempio n. 16
0
def compile(cli):
    """Compile a QMK Firmware.

    If a Configurator export is supplied this command will create a new keymap, overwriting an existing keymap if one exists.

    If a keyboard and keymap are provided this command will build a firmware based on that.
    """
    if cli.args.clean and not cli.args.filename and not cli.args.dry_run:
        if cli.config.compile.keyboard and cli.config.compile.keymap:
            command = create_make_command(cli.config.compile.keyboard,
                                          cli.config.compile.keymap, 'clean')
            cli.run(command, capture_output=False, stdin=DEVNULL)

    # Build the environment vars
    envs = {}
    for env in cli.args.env:
        if '=' in env:
            key, value = env.split('=', 1)
            envs[key] = value
        else:
            cli.log.warning('Invalid environment variable: %s', env)

    # Determine the compile command
    command = None

    if cli.args.filename:
        # If a configurator JSON was provided generate a keymap and compile it
        user_keymap = parse_configurator_json(cli.args.filename)
        command = compile_configurator_json(
            user_keymap, parallel=cli.config.compile.parallel, **envs)

    else:
        if cli.config.compile.keyboard and cli.config.compile.keymap:
            # Generate the make command for a specific keyboard/keymap.
            command = create_make_command(cli.config.compile.keyboard,
                                          cli.config.compile.keymap,
                                          parallel=cli.config.compile.parallel,
                                          **envs)

        elif not cli.config.compile.keyboard:
            cli.log.error('Could not determine keyboard!')
        elif not cli.config.compile.keymap:
            cli.log.error('Could not determine keymap!')

    # Compile the firmware, if we're able to
    if command:
        cli.log.info('Compiling keymap with {fg_cyan}%s', ' '.join(command))
        if not cli.args.dry_run:
            cli.echo('\n')
            # FIXME(skullydazed/anyone): Remove text=False once milc 1.0.11 has had enough time to be installed everywhere.
            compile = cli.run(command, capture_output=False, text=False)
            return compile.returncode

    else:
        cli.log.error(
            'You must supply a configurator export, both `--keyboard` and `--keymap`, or be in a directory for a keyboard or keymap.'
        )
        cli.echo(
            'usage: qmk compile [-h] [-b] [-kb KEYBOARD] [-km KEYMAP] [filename]'
        )
        return False
Esempio n. 17
0
def info(cli):
    """Compile an info.json for a particular keyboard and pretty-print it.
    """
    # Determine our keyboard(s)
    if not is_keyboard(cli.config.info.keyboard):
        cli.log.error('Invalid keyboard: %s!', cli.config.info.keyboard)
        exit(1)

    # Build the info.json file
    kb_info_json = info_json(cli.config.info.keyboard)

    # Output in the requested format
    if cli.args.format == 'json':
        print(json.dumps(kb_info_json))
        exit()

    if cli.args.format == 'text':
        for key in sorted(kb_info_json):
            if key == 'layouts':
                cli.echo('{fg_blue}layouts{fg_reset}: %s',
                         ', '.join(sorted(kb_info_json['layouts'].keys())))
            else:
                cli.echo('{fg_blue}%s{fg_reset}: %s', key, kb_info_json[key])

        if cli.config.info.layouts:
            show_layouts(kb_info_json, False)

        if cli.config.info.matrix:
            show_matrix(kb_info_json, False)

        if cli.config_source.info.keymap and cli.config_source.info.keymap != 'config_file':
            show_keymap(kb_info_json, False)

    elif cli.args.format == 'friendly':
        cli.echo('{fg_blue}Keyboard Name{fg_reset}: %s',
                 kb_info_json.get('keyboard_name', 'Unknown'))
        cli.echo('{fg_blue}Manufacturer{fg_reset}: %s',
                 kb_info_json.get('manufacturer', 'Unknown'))
        if 'url' in kb_info_json:
            cli.echo('{fg_blue}Website{fg_reset}: %s', kb_info_json['url'])
        if kb_info_json.get('maintainer') == 'qmk':
            cli.echo('{fg_blue}Maintainer{fg_reset}: QMK Community')
        else:
            cli.echo('{fg_blue}Maintainer{fg_reset}: %s',
                     kb_info_json.get('maintainer', 'qmk'))
        cli.echo('{fg_blue}Keyboard Folder{fg_reset}: %s',
                 kb_info_json.get('keyboard_folder', 'Unknown'))
        cli.echo('{fg_blue}Layouts{fg_reset}: %s',
                 ', '.join(sorted(kb_info_json['layouts'].keys())))
        if 'width' in kb_info_json and 'height' in kb_info_json:
            cli.echo('{fg_blue}Size{fg_reset}: %s x %s' %
                     (kb_info_json['width'], kb_info_json['height']))
        cli.echo('{fg_blue}Processor{fg_reset}: %s',
                 kb_info_json.get('processor', 'Unknown'))
        cli.echo('{fg_blue}Bootloader{fg_reset}: %s',
                 kb_info_json.get('bootloader', 'Unknown'))

        if cli.config.info.layouts:
            show_layouts(kb_info_json, True)

        if cli.config.info.matrix:
            show_matrix(kb_info_json, True)

        if cli.config_source.info.keymap and cli.config_source.info.keymap != 'config_file':
            show_keymap(kb_info_json, True)

    else:
        cli.log.error('Unknown format: %s', cli.args.format)
Esempio n. 18
0
def config(cli):
    """Read and write config settings.

    This script iterates over the config_tokens supplied as argument. Each config_token has the following form:

        section[.key][=value]

    If only a section (EG 'compile') is supplied all keys for that section will be displayed.

    If section.key is supplied the value for that single key will be displayed.

    If section.key=value is supplied the value for that single key will be set.

    If section.key=None is supplied the key will be deleted.

    No validation is done to ensure that the supplied section.key is actually used by qmk scripts.
    """
    if not cli.args.configs:
        # Walk the config tree
        for section in cli.config:
            for key in cli.config[section]:
                print_config(section, key)

        return True

    # Process config_tokens
    save_config = False

    for argument in cli.args.configs:
        # Split on space in case they quoted multiple config tokens
        for config_token in argument.split(' '):
            # Extract the section, config_key, and value to write from the supplied config_token.
            if '=' in config_token:
                key, value = config_token.split('=')
            else:
                key = config_token
                value = None

            if '.' in key:
                section, config_key = key.split('.', 1)
            else:
                section = key
                config_key = None

            # Validation
            if config_key and '.' in config_key:
                cli.log.error(
                    'Config keys may not have more than one period! "%s" is not valid.',
                    key)
                return False

            # Do what the user wants
            if section and config_key and value:
                # Write a config key
                log_string = '%s.%s{fg_cyan}:{fg_reset} %s {fg_cyan}->{fg_reset} %s'
                if cli.args.read_only:
                    log_string += ' {fg_red}(change not written)'

                cli.echo(log_string, section, config_key,
                         cli.config[section][config_key], value)

                if not cli.args.read_only:
                    if value == 'None':
                        del cli.config[section][config_key]
                    else:
                        cli.config[section][config_key] = value
                    save_config = True

            elif section and config_key:
                # Display a single key
                print_config(section, config_key)

            elif section:
                # Display an entire section
                for key in cli.config[section]:
                    print_config(section, key)

    # Ending actions
    if save_config:
        cli.save_config()

    return True
Esempio n. 19
0
def print_bootloader_help():
    """Prints the available bootloaders listed in docs.qmk.fm.
    """
    cli.log.info('Here are the available bootloaders:')
    cli.echo('\tdfu')
    cli.echo('\tdfu-ee')
    cli.echo('\tdfu-split-left')
    cli.echo('\tdfu-split-right')
    cli.echo('\tavrdude')
    cli.echo('\tBootloadHID')
    cli.echo('\tdfu-util')
    cli.echo('\tdfu-util-split-left')
    cli.echo('\tdfu-util-split-right')
    cli.echo('\tst-link-cli')
    cli.echo('\tst-flash')
    cli.echo('For more info, visit https://docs.qmk.fm/#/flashing')
Esempio n. 20
0
def question(prompt,
             *args,
             default=None,
             confirm=False,
             answer_type=str,
             validate=None,
             **kwargs):
    """Prompt the user to answer a question with a free-form input.

    Arguments:
        prompt
            The prompt to present to the user. Can include ANSI and format strings like milc's `cli.echo()`.

        default
            The value to return when the user doesn't enter any value. Use None to prompt until they enter a value.

        confirm
            Present the user with a confirmation dialog before accepting their answer.

        answer_type
            Specify a type function for the answer. Will re-prompt the user if the function raises any errors. Common choices here include int, float, and decimal.Decimal.

        validate
            This is an optional function that can be used to validate the answer. It should return True or False and have the following signature:

                def function_name(answer, *args, **kwargs):
    """
    if not args and kwargs:
        args = kwargs

    if default is not None:
        prompt = '%s [%s] ' % (prompt, default)

    while True:
        cli.echo('')
        answer = input(format_ansi(prompt % args))
        cli.echo('')

        if answer:
            if validate is not None and not validate(answer, *args, **kwargs):
                continue

            elif confirm:
                if yesno('Is the answer "%s" correct?', answer, default=True):
                    try:
                        return answer_type(answer)
                    except Exception as e:
                        cli.log.error(
                            'Could not convert answer (%s) to type %s: %s',
                            answer, answer_type.__name__, str(e))

            else:
                try:
                    return answer_type(answer)
                except Exception as e:
                    cli.log.error(
                        'Could not convert answer (%s) to type %s: %s', answer,
                        answer_type.__name__, str(e))

        elif default is not None:
            return default
Esempio n. 21
0
def choice(heading,
           options,
           *args,
           default=None,
           confirm=False,
           prompt='Please enter your choice: ',
           **kwargs):
    """Present the user with a list of options and let them pick one.

    Users can enter either the number or the text of their choice.

    This will return the value of the item they choose, not the numerical index.

    Arguments:
        heading
            The text to place above the list of options.

        options
            A sequence of items to choose from.

        default
            The index of the item to return when the user doesn't enter any value. Use None to prompt until they enter a value.

        confirm
            Present the user with a confirmation dialog before accepting their answer.

        prompt
            The prompt to present to the user. Can include ANSI and format strings like milc's `cli.echo()`.
    """
    if not args and kwargs:
        args = kwargs

    if prompt and default:
        prompt = prompt + ' [%s] ' % (default + 1, )

    while True:
        # Prompt for an answer.
        cli.echo('')
        cli.echo(heading % args)
        cli.echo('')
        for i, option in enumerate(options, 1):
            cli.echo('\t{fg_cyan}%d.{fg_reset} %s', i, option)

        cli.echo('')
        answer = input(format_ansi(prompt))
        cli.echo('')

        # If the user types in one of the options exactly use that
        if answer in options:
            return answer

        # Massage the answer into a valid integer
        if answer == '' and default:
            answer = default
        else:
            try:
                answer = int(answer) - 1
            except Exception:
                # Normally we would log the exception here, but in the interest of clean UI we do not.
                cli.log.error('Invalid choice: %s', answer + 1)
                continue

        # Validate the answer
        if answer >= len(options) or answer < 0:
            cli.log.error('Invalid choice: %s', answer + 1)
            continue

        if confirm and not yesno(
                'Is the answer "%s" correct?', answer + 1, default=True):
            continue

        # Return the answer they chose.
        return options[answer]
Esempio n. 22
0
def new_keyboard(cli):
    """Creates a new keyboard.
    """
    cli.log.info(
        '{style_bright}Generating a new QMK keyboard directory{style_normal}')
    cli.echo('')

    kb_name = cli.args.keyboard if cli.args.keyboard else prompt_keyboard()
    user_name = cli.config.new_keyboard.name if cli.config.new_keyboard.name else prompt_user(
    )
    real_name = cli.args.realname or cli.config.new_keyboard.name if cli.args.realname or cli.config.new_keyboard.name else prompt_name(
        user_name)
    default_layout = cli.args.layout if cli.args.layout else prompt_layout()
    mcu = cli.args.type if cli.args.type else prompt_mcu()

    if not validate_keyboard_name(kb_name):
        cli.log.error(
            'Keyboard names must contain only {fg_cyan}lowercase a-z{fg_reset}, {fg_cyan}0-9{fg_reset}, and {fg_cyan}_{fg_reset}! Please choose a different name.'
        )
        return 1

    if keyboard(kb_name).exists():
        cli.log.error(
            f'Keyboard {{fg_cyan}}{kb_name}{{fg_reset}} already exists! Please choose a different name.'
        )
        return 1

    # Preprocess any development_board presets
    if mcu in dev_boards:
        defaults_map = json_load(Path('data/mappings/defaults.json'))
        board = defaults_map['development_board'][mcu]

        mcu = board['processor']
        bootloader = board['bootloader']
    else:
        bootloader = select_default_bootloader(mcu)

    tokens = {  # Comment here is to force multiline formatting
        'YEAR': str(date.today().year),
        'KEYBOARD': kb_name,
        'USER_NAME': user_name,
        'REAL_NAME': real_name,
        'LAYOUT': default_layout,
        'MCU': mcu,
        'BOOTLOADER': bootloader
    }

    if cli.config.general.verbose:
        cli.log.info("Creating keyboard with:")
        for key, value in tokens.items():
            cli.echo(f"    {key.ljust(10)}:   {value}")

    # TODO: detach community layout and rename to just "LAYOUT"
    if default_layout == 'none of the above':
        default_layout = "ortho_4x4"

    # begin with making the deepest folder in the tree
    keymaps_path = keyboard(kb_name) / 'keymaps/'
    keymaps_path.mkdir(parents=True)

    # copy in keymap.c or keymap.json
    community_keymap = Path(COMMUNITY /
                            f'{default_layout}/default_{default_layout}/')
    shutil.copytree(community_keymap, keymaps_path / 'default')

    # process template files
    for file in list(TEMPLATE.iterdir()):
        replace_placeholders(file, keyboard(kb_name) / file.name, tokens)

    # merge in infos
    community_info = Path(COMMUNITY / f'{default_layout}/info.json')
    augment_community_info(community_info,
                           keyboard(kb_name) / community_info.name)

    cli.log.info(
        f'{{fg_green}}Created a new keyboard called {{fg_cyan}}{kb_name}{{fg_green}}.{{fg_reset}}'
    )
    cli.log.info(
        f'To start working on things, `cd` into {{fg_cyan}}keyboards/{kb_name}{{fg_reset}},'
    )
    cli.log.info('or open the directory in your preferred text editor.')
    cli.log.info(
        f"And build with {{fg_yellow}}qmk compile -kb {kb_name} -km default{{fg_reset}}."
    )
Esempio n. 23
0
def print_bootloader_help():
    """Prints the available bootloaders listed in docs.qmk.fm.
    """
    cli.log.info('Here are the available bootloaders:')
    cli.echo('\tavrdude')
    cli.echo('\tbootloadhid')
    cli.echo('\tdfu')
    cli.echo('\tdfu-util')
    cli.echo('\tmdloader')
    cli.echo('\tst-flash')
    cli.echo('\tst-link-cli')
    cli.log.info('Enhanced variants for split keyboards:')
    cli.echo('\tavrdude-split-left')
    cli.echo('\tavrdude-split-right')
    cli.echo('\tdfu-ee')
    cli.echo('\tdfu-split-left')
    cli.echo('\tdfu-split-right')
    cli.echo('\tdfu-util-split-left')
    cli.echo('\tdfu-util-split-right')
    cli.echo('For more info, visit https://docs.qmk.fm/#/flashing')
Esempio n. 24
0
def flash(cli):
    """Compile and or flash QMK Firmware or keyboard/layout

    If a Configurator JSON export is supplied this command will create a new keymap. Keymap and Keyboard arguments
    will be ignored.

    If no file is supplied, keymap and keyboard are expected.

    If bootloader is omitted the make system will use the configured bootloader for that keyboard.
    """
    if cli.args.clean and not cli.args.filename and not cli.args.dry_run:
        command = create_make_command(cli.config.flash.keyboard,
                                      cli.config.flash.keymap, 'clean')
        cli.run(command, capture_output=False, stdin=DEVNULL)

    # Build the environment vars
    envs = {}
    for env in cli.args.env:
        if '=' in env:
            key, value = env.split('=', 1)
            envs[key] = value
        else:
            cli.log.warning('Invalid environment variable: %s', env)

    # Determine the compile command
    command = ''

    if cli.args.bootloaders:
        # Provide usage and list bootloaders
        cli.echo(
            'usage: qmk flash [-h] [-b] [-n] [-kb KEYBOARD] [-km KEYMAP] [-bl BOOTLOADER] [filename]'
        )
        print_bootloader_help()
        return False

    if cli.args.filename:
        # Handle compiling a configurator JSON
        user_keymap = parse_configurator_json(cli.args.filename)
        keymap_path = qmk.path.keymap(user_keymap['keyboard'])
        command = compile_configurator_json(user_keymap,
                                            cli.args.bootloader,
                                            parallel=cli.config.flash.parallel,
                                            **envs)

        cli.log.info('Wrote keymap to {fg_cyan}%s/%s/keymap.c', keymap_path,
                     user_keymap['keymap'])

    else:
        if cli.config.flash.keyboard and cli.config.flash.keymap:
            # Generate the make command for a specific keyboard/keymap.
            command = create_make_command(cli.config.flash.keyboard,
                                          cli.config.flash.keymap,
                                          cli.args.bootloader,
                                          parallel=cli.config.flash.parallel,
                                          **envs)

        elif not cli.config.flash.keyboard:
            cli.log.error('Could not determine keyboard!')
        elif not cli.config.flash.keymap:
            cli.log.error('Could not determine keymap!')

    # Compile the firmware, if we're able to
    if command:
        cli.log.info('Compiling keymap with {fg_cyan}%s', ' '.join(command))
        if not cli.args.dry_run:
            cli.echo('\n')
            compile = cli.run(command, capture_output=False, stdin=DEVNULL)
            return compile.returncode

    else:
        cli.log.error(
            'You must supply a configurator export, both `--keyboard` and `--keymap`, or be in a directory for a keyboard or keymap.'
        )
        cli.echo(
            'usage: qmk flash [-h] [-b] [-n] [-kb KEYBOARD] [-km KEYMAP] [-bl BOOTLOADER] [filename]'
        )
        return False
Esempio n. 25
0
def new_keyboard(cli):
    """Creates a new keyboard.
    """
    cli.log.info('{style_bright}Generating a new QMK keyboard directory{style_normal}')
    cli.echo('')

    # Get keyboard name
    new_keyboard_name = None
    while not new_keyboard_name:
        new_keyboard_name = cli.args.keyboard if cli.args.keyboard else question('Keyboard Name:')
        if not validate_keyboard_name(new_keyboard_name):
            cli.log.error('Keyboard names must contain only {fg_cyan}lowercase a-z{fg_reset}, {fg_cyan}0-9{fg_reset}, and {fg_cyan}_{fg_reset}! Please choose a different name.')

            # Exit if passed by arg
            if cli.args.keyboard:
                return False

            new_keyboard_name = None
            continue

        keyboard_path = qmk.path.keyboard(new_keyboard_name)
        if keyboard_path.exists():
            cli.log.error(f'Keyboard {{fg_cyan}}{new_keyboard_name}{{fg_reset}} already exists! Please choose a different name.')

            # Exit if passed by arg
            if cli.args.keyboard:
                return False

            new_keyboard_name = None

    # Get keyboard type
    keyboard_type = cli.args.type if cli.args.type else choice('Keyboard Type:', KEYBOARD_TYPES, default=0)

    # Get username
    user_name = None
    while not user_name:
        user_name = question('Your Name:', default=find_user_name())

        if not user_name:
            cli.log.error('You didn\'t provide a username, and we couldn\'t find one set in your QMK or Git configs. Please try again.')

            # Exit if passed by arg
            if cli.args.username:
                return False

    # Copy all the files
    copy_templates(keyboard_type, keyboard_path)

    # Replace all the placeholders
    keyboard_basename = keyboard_path.name
    replacements = [
        ('%YEAR%', str(date.today().year)),
        ('%KEYBOARD%', keyboard_basename),
        ('%YOUR_NAME%', user_name),
    ]
    filenames = [
        keyboard_path / 'config.h',
        keyboard_path / 'info.json',
        keyboard_path / 'readme.md',
        keyboard_path / f'{keyboard_basename}.c',
        keyboard_path / f'{keyboard_basename}.h',
        keyboard_path / 'keymaps/default/readme.md',
        keyboard_path / 'keymaps/default/keymap.c',
    ]
    replace_placeholders(replacements, filenames)

    cli.echo('')
    cli.log.info(f'{{fg_green}}Created a new keyboard called {{fg_cyan}}{new_keyboard_name}{{fg_green}}.{{fg_reset}}')
    cli.log.info(f'To start working on things, `cd` into {{fg_cyan}}{keyboard_path}{{fg_reset}},')
    cli.log.info('or open the directory in your preferred text editor.')
Esempio n. 26
0
def print_config(section, key):
    """Print a single config setting to stdout.
    """
    cli.echo('%s.%s{fg_cyan}={fg_reset}%s', section, key,
             cli.config[section][key])
Esempio n. 27
0
def new_keyboard(cli):
    """Creates a new keyboard.
    """
    cli.log.info('{style_bright}Generating a new QMK keyboard directory{style_normal}')
    cli.echo('')

    # Get keyboard name
    new_keyboard_name = None
    while not new_keyboard_name:
        new_keyboard_name = cli.args.keyboard if cli.args.keyboard else question('Keyboard Name:')
        if not validate_keyboard_name(new_keyboard_name):
            cli.log.error('Keyboard names must contain only {fg_cyan}lowercase a-z{fg_reset}, {fg_cyan}0-9{fg_reset}, and {fg_cyan}_{fg_reset}! Please choose a different name.')

            # Exit if passed by arg
            if cli.args.keyboard:
                return False

            new_keyboard_name = None
            continue

        keyboard_path = qmk.path.keyboard(new_keyboard_name)
        if keyboard_path.exists():
            cli.log.error(f'Keyboard {{fg_cyan}}{new_keyboard_name}{{fg_reset}} already exists! Please choose a different name.')

            # Exit if passed by arg
            if cli.args.keyboard:
                return False

            new_keyboard_name = None

    # Get keyboard type
    keyboard_type = cli.args.type if cli.args.type else choice('Keyboard Type:', KEYBOARD_TYPES, default=0)

    # Get username
    user_name = None
    while not user_name:
        user_name = question('Your GitHub User Name:', default=find_user_name())

        if not user_name:
            cli.log.error('You didn\'t provide a username, and we couldn\'t find one set in your QMK or Git configs. Please try again.')

            # Exit if passed by arg
            if cli.args.username:
                return False

    real_name = None
    while not real_name:
        real_name = question('Your real name:', default=user_name)

    keyboard_basename = keyboard_path.name
    replacements = {
        "YEAR": str(date.today().year),
        "KEYBOARD": keyboard_basename,
        "USER_NAME": user_name,
        "YOUR_NAME": real_name,
    }

    template_dir = Path('data/templates')
    template_tree(template_dir / 'base', keyboard_path, replacements)
    template_tree(template_dir / keyboard_type, keyboard_path, replacements)

    cli.echo('')
    cli.log.info(f'{{fg_green}}Created a new keyboard called {{fg_cyan}}{new_keyboard_name}{{fg_green}}.{{fg_reset}}')
    cli.log.info(f'To start working on things, `cd` into {{fg_cyan}}{keyboard_path}{{fg_reset}},')
    cli.log.info('or open the directory in your preferred text editor.')