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
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)
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)
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)
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)
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
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?")
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
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)
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
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])
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)
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))
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)
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), ))
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
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)
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
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')
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
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]
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}}." )
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')
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
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.')
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])
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.')