Merge remote-tracking branch 'qmk 0.17.0' into firmware21
This commit is contained in:
@@ -16,7 +16,8 @@ import_names = {
|
||||
# A mapping of package name to importable name
|
||||
'pep8-naming': 'pep8ext_naming',
|
||||
'pyusb': 'usb.core',
|
||||
'qmk-dotty-dict': 'dotty_dict'
|
||||
'qmk-dotty-dict': 'dotty_dict',
|
||||
'pillow': 'PIL'
|
||||
}
|
||||
|
||||
safe_commands = [
|
||||
@@ -51,6 +52,7 @@ subcommands = [
|
||||
'qmk.cli.generate.dfu_header',
|
||||
'qmk.cli.generate.docs',
|
||||
'qmk.cli.generate.info_json',
|
||||
'qmk.cli.generate.keyboard_c',
|
||||
'qmk.cli.generate.keyboard_h',
|
||||
'qmk.cli.generate.layouts',
|
||||
'qmk.cli.generate.rgb_breathe_table',
|
||||
@@ -67,6 +69,7 @@ subcommands = [
|
||||
'qmk.cli.multibuild',
|
||||
'qmk.cli.new.keyboard',
|
||||
'qmk.cli.new.keymap',
|
||||
'qmk.cli.painter',
|
||||
'qmk.cli.pyformat',
|
||||
'qmk.cli.pytest',
|
||||
'qmk.cli.via2json',
|
||||
|
||||
@@ -9,7 +9,7 @@ from milc import cli
|
||||
from qmk.path import normpath
|
||||
from qmk.c_parse import c_source_files
|
||||
|
||||
c_file_suffixes = ('c', 'h', 'cpp')
|
||||
c_file_suffixes = ('c', 'h', 'cpp', 'hpp')
|
||||
core_dirs = ('drivers', 'quantum', 'tests', 'tmk_core', 'platforms')
|
||||
ignored = ('tmk_core/protocol/usb_hid', 'platforms/chibios/boards')
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""This script automates the generation of the QMK API data.
|
||||
"""
|
||||
from pathlib import Path
|
||||
from shutil import copyfile
|
||||
import shutil
|
||||
import json
|
||||
|
||||
from milc import cli
|
||||
@@ -12,28 +12,42 @@ from qmk.json_encoders import InfoJSONEncoder
|
||||
from qmk.json_schema import json_load
|
||||
from qmk.keyboard import find_readme, list_keyboards
|
||||
|
||||
TEMPLATE_PATH = Path('data/templates/api/')
|
||||
BUILD_API_PATH = Path('.build/api_data/')
|
||||
|
||||
|
||||
@cli.argument('-n', '--dry-run', arg_only=True, action='store_true', help="Don't write the data to disk.")
|
||||
@cli.argument('-f', '--filter', arg_only=True, action='append', default=[], help="Filter the list of keyboards based on partial name matches the supplied value. May be passed multiple times.")
|
||||
@cli.subcommand('Creates a new keymap for the keyboard of your choosing', hidden=False if cli.config.user.developer else True)
|
||||
def generate_api(cli):
|
||||
"""Generates the QMK API data.
|
||||
"""
|
||||
api_data_dir = Path('api_data')
|
||||
v1_dir = api_data_dir / 'v1'
|
||||
if BUILD_API_PATH.exists():
|
||||
shutil.rmtree(BUILD_API_PATH)
|
||||
|
||||
shutil.copytree(TEMPLATE_PATH, BUILD_API_PATH)
|
||||
|
||||
v1_dir = BUILD_API_PATH / 'v1'
|
||||
keyboard_all_file = v1_dir / 'keyboards.json' # A massive JSON containing everything
|
||||
keyboard_list_file = v1_dir / 'keyboard_list.json' # A simple list of keyboard targets
|
||||
keyboard_aliases_file = v1_dir / 'keyboard_aliases.json' # A list of historical keyboard names and their new name
|
||||
keyboard_metadata_file = v1_dir / 'keyboard_metadata.json' # All the data configurator/via needs for initialization
|
||||
usb_file = v1_dir / 'usb.json' # A mapping of USB VID/PID -> keyboard target
|
||||
|
||||
if not api_data_dir.exists():
|
||||
api_data_dir.mkdir()
|
||||
# Filter down when required
|
||||
keyboard_list = list_keyboards()
|
||||
if cli.args.filter:
|
||||
kb_list = []
|
||||
for keyboard_name in keyboard_list:
|
||||
if any(i in keyboard_name for i in cli.args.filter):
|
||||
kb_list.append(keyboard_name)
|
||||
keyboard_list = kb_list
|
||||
|
||||
kb_all = {}
|
||||
usb_list = {}
|
||||
|
||||
# Generate and write keyboard specific JSON files
|
||||
for keyboard_name in list_keyboards():
|
||||
for keyboard_name in keyboard_list:
|
||||
kb_all[keyboard_name] = info_json(keyboard_name)
|
||||
keyboard_dir = v1_dir / 'keyboards' / keyboard_name
|
||||
keyboard_info = keyboard_dir / 'info.json'
|
||||
@@ -47,7 +61,7 @@ def generate_api(cli):
|
||||
cli.log.debug('Wrote file %s', keyboard_info)
|
||||
|
||||
if keyboard_readme_src:
|
||||
copyfile(keyboard_readme_src, keyboard_readme)
|
||||
shutil.copyfile(keyboard_readme_src, keyboard_readme)
|
||||
cli.log.debug('Copied %s -> %s', keyboard_readme_src, keyboard_readme)
|
||||
|
||||
if 'usb' in kb_all[keyboard_name]:
|
||||
|
||||
@@ -5,10 +5,9 @@ from pathlib import Path
|
||||
from dotty_dict import dotty
|
||||
from milc import cli
|
||||
|
||||
from qmk.info import info_json
|
||||
from qmk.json_schema import json_load, validate
|
||||
from qmk.info import info_json, keymap_json_config
|
||||
from qmk.json_schema import json_load
|
||||
from qmk.keyboard import keyboard_completer, keyboard_folder
|
||||
from qmk.keymap import locate_keymap
|
||||
from qmk.commands import dump_lines
|
||||
from qmk.path import normpath
|
||||
from qmk.constants import GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE
|
||||
@@ -84,7 +83,7 @@ def generate_config_items(kb_info_json, config_h_lines):
|
||||
|
||||
for config_key, info_dict in info_config_map.items():
|
||||
info_key = info_dict['info_key']
|
||||
key_type = info_dict.get('value_type', 'str')
|
||||
key_type = info_dict.get('value_type', 'raw')
|
||||
to_config = info_dict.get('to_config', True)
|
||||
|
||||
if not to_config:
|
||||
@@ -95,7 +94,12 @@ def generate_config_items(kb_info_json, config_h_lines):
|
||||
except KeyError:
|
||||
continue
|
||||
|
||||
if key_type.startswith('array'):
|
||||
if key_type.startswith('array.array'):
|
||||
config_h_lines.append('')
|
||||
config_h_lines.append(f'#ifndef {config_key}')
|
||||
config_h_lines.append(f'# define {config_key} {{ {", ".join(["{" + ",".join(list(map(str, x))) + "}" for x in config_value])} }}')
|
||||
config_h_lines.append(f'#endif // {config_key}')
|
||||
elif key_type.startswith('array'):
|
||||
config_h_lines.append('')
|
||||
config_h_lines.append(f'#ifndef {config_key}')
|
||||
config_h_lines.append(f'# define {config_key} {{ {", ".join(map(str, config_value))} }}')
|
||||
@@ -112,6 +116,11 @@ def generate_config_items(kb_info_json, config_h_lines):
|
||||
config_h_lines.append(f'#ifndef {key}')
|
||||
config_h_lines.append(f'# define {key} {value}')
|
||||
config_h_lines.append(f'#endif // {key}')
|
||||
elif key_type == 'str':
|
||||
config_h_lines.append('')
|
||||
config_h_lines.append(f'#ifndef {config_key}')
|
||||
config_h_lines.append(f'# define {config_key} "{config_value}"')
|
||||
config_h_lines.append(f'#endif // {config_key}')
|
||||
elif key_type == 'bcd_version':
|
||||
(major, minor, revision) = config_value.split('.')
|
||||
config_h_lines.append('')
|
||||
@@ -175,10 +184,7 @@ def generate_config_h(cli):
|
||||
"""
|
||||
# Determine our keyboard/keymap
|
||||
if cli.args.keymap:
|
||||
km = locate_keymap(cli.args.keyboard, cli.args.keymap)
|
||||
km_json = json_load(km)
|
||||
validate(km_json, 'qmk.keymap.v1')
|
||||
kb_info_json = dotty(km_json.get('config', {}))
|
||||
kb_info_json = dotty(keymap_json_config(cli.args.keyboard, cli.args.keymap))
|
||||
else:
|
||||
kb_info_json = dotty(info_json(cli.args.keyboard))
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ DOCS_PATH = Path('docs/')
|
||||
BUILD_PATH = Path('.build/')
|
||||
BUILD_DOCS_PATH = BUILD_PATH / 'docs'
|
||||
DOXYGEN_PATH = BUILD_PATH / 'doxygen'
|
||||
MOXYGEN_PATH = BUILD_DOCS_PATH / 'internals'
|
||||
|
||||
|
||||
@cli.subcommand('Build QMK documentation.', hidden=False if cli.config.user.developer else True)
|
||||
@@ -34,10 +35,10 @@ def generate_docs(cli):
|
||||
'stdin': DEVNULL,
|
||||
}
|
||||
|
||||
cli.log.info('Generating internal docs...')
|
||||
cli.log.info('Generating docs...')
|
||||
|
||||
# Generate internal docs
|
||||
cli.run(['doxygen', 'Doxyfile'], **args)
|
||||
cli.run(['moxygen', '-q', '-g', '-o', BUILD_DOCS_PATH / 'internals_%s.md', DOXYGEN_PATH / 'xml'], **args)
|
||||
cli.run(['moxygen', '-q', '-g', '-o', MOXYGEN_PATH / '%s.md', DOXYGEN_PATH / 'xml'], **args)
|
||||
|
||||
cli.log.info('Successfully generated internal docs to %s.', BUILD_DOCS_PATH)
|
||||
cli.log.info('Successfully generated docs to %s.', BUILD_DOCS_PATH)
|
||||
|
||||
75
lib/python/qmk/cli/generate/keyboard_c.py
Executable file
75
lib/python/qmk/cli/generate/keyboard_c.py
Executable file
@@ -0,0 +1,75 @@
|
||||
"""Used by the make system to generate keyboard.c from info.json.
|
||||
"""
|
||||
from milc import cli
|
||||
|
||||
from qmk.info import info_json
|
||||
from qmk.commands import dump_lines
|
||||
from qmk.keyboard import keyboard_completer, keyboard_folder
|
||||
from qmk.path import normpath
|
||||
from qmk.constants import GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE
|
||||
|
||||
|
||||
def _gen_led_config(info_data):
|
||||
"""Convert info.json content to g_led_config
|
||||
"""
|
||||
cols = info_data['matrix_size']['cols']
|
||||
rows = info_data['matrix_size']['rows']
|
||||
|
||||
config_type = None
|
||||
if 'layout' in info_data.get('rgb_matrix', {}):
|
||||
config_type = 'rgb_matrix'
|
||||
elif 'layout' in info_data.get('led_matrix', {}):
|
||||
config_type = 'led_matrix'
|
||||
|
||||
lines = []
|
||||
if not config_type:
|
||||
return lines
|
||||
|
||||
matrix = [['NO_LED'] * cols for i in range(rows)]
|
||||
pos = []
|
||||
flags = []
|
||||
|
||||
led_config = info_data[config_type]['layout']
|
||||
for index, item in enumerate(led_config, start=0):
|
||||
if 'matrix' in item:
|
||||
(x, y) = item['matrix']
|
||||
matrix[x][y] = str(index)
|
||||
pos.append(f'{{ {item.get("x", 0)},{item.get("y", 0)} }}')
|
||||
flags.append(str(item.get('flags', 0)))
|
||||
|
||||
if config_type == 'rgb_matrix':
|
||||
lines.append('#ifdef RGB_MATRIX_ENABLE')
|
||||
lines.append('#include "rgb_matrix.h"')
|
||||
elif config_type == 'led_matrix':
|
||||
lines.append('#ifdef LED_MATRIX_ENABLE')
|
||||
lines.append('#include "led_matrix.h"')
|
||||
|
||||
lines.append('__attribute__ ((weak)) led_config_t g_led_config = {')
|
||||
lines.append(' {')
|
||||
for line in matrix:
|
||||
lines.append(f' {{ {",".join(line)} }},')
|
||||
lines.append(' },')
|
||||
lines.append(f' {{ {",".join(pos)} }},')
|
||||
lines.append(f' {{ {",".join(flags)} }},')
|
||||
lines.append('};')
|
||||
lines.append('#endif')
|
||||
|
||||
return lines
|
||||
|
||||
|
||||
@cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to')
|
||||
@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages")
|
||||
@cli.argument('-kb', '--keyboard', arg_only=True, type=keyboard_folder, completer=keyboard_completer, required=True, help='Keyboard to generate keyboard.c for.')
|
||||
@cli.subcommand('Used by the make system to generate keyboard.c from info.json', hidden=True)
|
||||
def generate_keyboard_c(cli):
|
||||
"""Generates the keyboard.h file.
|
||||
"""
|
||||
kb_info_json = info_json(cli.args.keyboard)
|
||||
|
||||
# Build the layouts.h file.
|
||||
keyboard_h_lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '#include QMK_KEYBOARD_H', '']
|
||||
|
||||
keyboard_h_lines.extend(_gen_led_config(kb_info_json))
|
||||
|
||||
# Show the results
|
||||
dump_lines(cli.args.output, keyboard_h_lines, cli.args.quiet)
|
||||
@@ -34,7 +34,7 @@ def generate_rgb_breathe_table(cli):
|
||||
"""
|
||||
breathe_values = [0] * 256
|
||||
for pos in range(0, 256):
|
||||
breathe_values[pos] = (int)((math.exp(math.sin((pos/255) * math.pi)) - cli.args.center / math.e) * (cli.args.max / (math.e - 1 / math.e))) # noqa: yapf insists there be no whitespace around /
|
||||
breathe_values[pos] = (int)((math.exp(math.sin((pos / 255) * math.pi)) - cli.args.center / math.e) * (cli.args.max / (math.e - 1 / math.e)))
|
||||
|
||||
values_template = ''
|
||||
for s in range(0, 3):
|
||||
@@ -46,7 +46,7 @@ def generate_rgb_breathe_table(cli):
|
||||
values_template += ' ' if pos % 8 == 0 else ''
|
||||
values_template += '0x{:02X}'.format(breathe_values[pos])
|
||||
values_template += ',' if (pos + step) < 256 else ''
|
||||
values_template += '\n' if (pos+step) % 8 == 0 else ' ' # noqa: yapf insists there be no whitespace around +
|
||||
values_template += '\n' if (pos + step) % 8 == 0 else ' '
|
||||
|
||||
values_template += '#endif'
|
||||
values_template += '\n\n' if s < 2 else ''
|
||||
|
||||
@@ -5,10 +5,9 @@ from pathlib import Path
|
||||
from dotty_dict import dotty
|
||||
from milc import cli
|
||||
|
||||
from qmk.info import info_json
|
||||
from qmk.json_schema import json_load, validate
|
||||
from qmk.info import info_json, keymap_json_config
|
||||
from qmk.json_schema import json_load
|
||||
from qmk.keyboard import keyboard_completer, keyboard_folder
|
||||
from qmk.keymap import locate_keymap
|
||||
from qmk.commands import dump_lines
|
||||
from qmk.path import normpath
|
||||
from qmk.constants import GPL2_HEADER_SH_LIKE, GENERATED_HEADER_SH_LIKE
|
||||
@@ -21,7 +20,7 @@ def process_mapping_rule(kb_info_json, rules_key, info_dict):
|
||||
return None
|
||||
|
||||
info_key = info_dict['info_key']
|
||||
key_type = info_dict.get('value_type', 'str')
|
||||
key_type = info_dict.get('value_type', 'raw')
|
||||
|
||||
try:
|
||||
rules_value = kb_info_json[info_key]
|
||||
@@ -34,6 +33,8 @@ def process_mapping_rule(kb_info_json, rules_key, info_dict):
|
||||
return f'{rules_key} ?= {"yes" if rules_value else "no"}'
|
||||
elif key_type == 'mapping':
|
||||
return '\n'.join([f'{key} ?= {value}' for key, value in rules_value.items()])
|
||||
elif key_type == 'str':
|
||||
return f'{rules_key} ?= "{rules_value}"'
|
||||
|
||||
return f'{rules_key} ?= {rules_value}'
|
||||
|
||||
@@ -49,10 +50,7 @@ def generate_rules_mk(cli):
|
||||
"""
|
||||
# Determine our keyboard/keymap
|
||||
if cli.args.keymap:
|
||||
km = locate_keymap(cli.args.keyboard, cli.args.keymap)
|
||||
km_json = json_load(km)
|
||||
validate(km_json, 'qmk.keymap.v1')
|
||||
kb_info_json = dotty(km_json.get('config', {}))
|
||||
kb_info_json = dotty(keymap_json_config(cli.args.keyboard, cli.args.keymap))
|
||||
else:
|
||||
kb_info_json = dotty(info_json(cli.args.keyboard))
|
||||
|
||||
|
||||
@@ -11,8 +11,8 @@ from qmk.json_encoders import InfoJSONEncoder
|
||||
from qmk.constants import COL_LETTERS, ROW_LETTERS
|
||||
from qmk.decorators import automagic_keyboard, automagic_keymap
|
||||
from qmk.keyboard import keyboard_completer, keyboard_folder, render_layouts, render_layout, rules_mk
|
||||
from qmk.info import info_json, keymap_json
|
||||
from qmk.keymap import locate_keymap
|
||||
from qmk.info import info_json
|
||||
from qmk.path import is_keyboard
|
||||
|
||||
UNICODE_SUPPORT = sys.stdout.encoding.lower().startswith('utf')
|
||||
@@ -135,7 +135,7 @@ def print_parsed_rules_mk(keyboard_name):
|
||||
|
||||
|
||||
@cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='Keyboard to show info for.')
|
||||
@cli.argument('-km', '--keymap', help='Show the layers for a JSON keymap too.')
|
||||
@cli.argument('-km', '--keymap', help='Keymap to show info for (Optional).')
|
||||
@cli.argument('-l', '--layouts', action='store_true', help='Render the layouts.')
|
||||
@cli.argument('-m', '--matrix', action='store_true', help='Render the layouts with matrix information.')
|
||||
@cli.argument('-f', '--format', default='friendly', arg_only=True, help='Format to display the data in (friendly, text, json) (Default: friendly).')
|
||||
@@ -161,8 +161,15 @@ def info(cli):
|
||||
print_parsed_rules_mk(cli.config.info.keyboard)
|
||||
return False
|
||||
|
||||
# default keymap stored in config file should be ignored
|
||||
if cli.config_source.info.keymap == 'config_file':
|
||||
cli.config_source.info.keymap = None
|
||||
|
||||
# Build the info.json file
|
||||
kb_info_json = info_json(cli.config.info.keyboard)
|
||||
if cli.config.info.keymap:
|
||||
kb_info_json = keymap_json(cli.config.info.keyboard, cli.config.info.keymap)
|
||||
else:
|
||||
kb_info_json = info_json(cli.config.info.keyboard)
|
||||
|
||||
# Output in the requested format
|
||||
if cli.args.format == 'json':
|
||||
@@ -178,11 +185,12 @@ def info(cli):
|
||||
cli.log.error('Unknown format: %s', cli.args.format)
|
||||
return False
|
||||
|
||||
# Output requested extras
|
||||
if cli.config.info.layouts:
|
||||
show_layouts(kb_info_json, title_caps)
|
||||
|
||||
if cli.config.info.matrix:
|
||||
show_matrix(kb_info_json, title_caps)
|
||||
|
||||
if cli.config_source.info.keymap and cli.config_source.info.keymap != 'config_file':
|
||||
if cli.config.info.keymap:
|
||||
show_keymap(kb_info_json, title_caps)
|
||||
|
||||
@@ -116,6 +116,13 @@ def lint(cli):
|
||||
if not keymap_check(kb, cli.config.lint.keymap):
|
||||
ok = False
|
||||
|
||||
# Check if all non-data driven macros exist in <keyboard.h>
|
||||
for layout, data in keyboard_info['layouts'].items():
|
||||
# Matrix data should be a list with exactly two integers: [0, 1]
|
||||
if not data['c_macro'] and not all('matrix' in key_data.keys() or len(key_data) == 2 or all(isinstance(n, int) for n in key_data) for key_data in data['layout']):
|
||||
cli.log.error(f'{kb}: "{layout}" has no "matrix" definition in either "info.json" or "<keyboard>.h"!')
|
||||
ok = False
|
||||
|
||||
# Report status
|
||||
if not ok:
|
||||
failed.append(kb)
|
||||
|
||||
@@ -14,7 +14,7 @@ from qmk.git import git_get_username
|
||||
from qmk.json_schema import load_jsonschema
|
||||
from qmk.path import keyboard
|
||||
from qmk.json_encoders import InfoJSONEncoder
|
||||
from qmk.json_schema import deep_update
|
||||
from qmk.json_schema import deep_update, json_load
|
||||
from qmk.constants import MCU2BOOTLOADER
|
||||
|
||||
COMMUNITY = Path('layouts/community/')
|
||||
@@ -23,13 +23,14 @@ TEMPLATE = Path('data/templates/keyboard/')
|
||||
# defaults
|
||||
schema = dotty(load_jsonschema('keyboard'))
|
||||
mcu_types = sorted(schema["properties.processor.enum"], key=str.casefold)
|
||||
dev_boards = sorted(schema["properties.development_board.enum"], key=str.casefold)
|
||||
available_layouts = sorted([x.name for x in COMMUNITY.iterdir() if x.is_dir()])
|
||||
|
||||
|
||||
def mcu_type(mcu):
|
||||
"""Callable for argparse validation.
|
||||
"""
|
||||
if mcu not in mcu_types:
|
||||
if mcu not in (dev_boards + mcu_types):
|
||||
raise ValueError
|
||||
return mcu
|
||||
|
||||
@@ -176,14 +177,14 @@ https://docs.qmk.fm/#/compatible_microcontrollers
|
||||
|
||||
MCU? """
|
||||
# remove any options strictly used for compatibility
|
||||
filtered_mcu = [x for x in mcu_types if not any(xs in x for xs in ['cortex', 'unknown'])]
|
||||
filtered_mcu = [x for x in (dev_boards + mcu_types) if not any(xs in x for xs in ['cortex', 'unknown'])]
|
||||
|
||||
return choice(prompt, filtered_mcu, default=filtered_mcu.index("atmega32u4"))
|
||||
|
||||
|
||||
@cli.argument('-kb', '--keyboard', help='Specify the name for the new keyboard directory', arg_only=True, type=keyboard_name)
|
||||
@cli.argument('-l', '--layout', help='Community layout to bootstrap with', arg_only=True, type=layout_type)
|
||||
@cli.argument('-t', '--type', help='Specify the keyboard MCU type', arg_only=True, type=mcu_type)
|
||||
@cli.argument('-t', '--type', help='Specify the keyboard MCU type (or "development_board" preset)', arg_only=True, type=mcu_type)
|
||||
@cli.argument('-u', '--username', help='Specify your username (default from Git config)', dest='name')
|
||||
@cli.argument('-n', '--realname', help='Specify your real name if you want to use that. Defaults to username', arg_only=True)
|
||||
@cli.subcommand('Creates a new keyboard directory')
|
||||
@@ -198,7 +199,6 @@ def new_keyboard(cli):
|
||||
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()
|
||||
bootloader = select_default_bootloader(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.')
|
||||
@@ -208,6 +208,16 @@ def new_keyboard(cli):
|
||||
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,
|
||||
|
||||
2
lib/python/qmk/cli/painter/__init__.py
Normal file
2
lib/python/qmk/cli/painter/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from . import convert_graphics
|
||||
from . import make_font
|
||||
86
lib/python/qmk/cli/painter/convert_graphics.py
Normal file
86
lib/python/qmk/cli/painter/convert_graphics.py
Normal file
@@ -0,0 +1,86 @@
|
||||
"""This script tests QGF functionality.
|
||||
"""
|
||||
import re
|
||||
import datetime
|
||||
from io import BytesIO
|
||||
from qmk.path import normpath
|
||||
from qmk.painter import render_header, render_source, render_license, render_bytes, valid_formats
|
||||
from milc import cli
|
||||
from PIL import Image
|
||||
|
||||
|
||||
@cli.argument('-v', '--verbose', arg_only=True, action='store_true', help='Turns on verbose output.')
|
||||
@cli.argument('-i', '--input', required=True, help='Specify input graphic file.')
|
||||
@cli.argument('-o', '--output', default='', help='Specify output directory. Defaults to same directory as input.')
|
||||
@cli.argument('-f', '--format', required=True, help='Output format, valid types: %s' % (', '.join(valid_formats.keys())))
|
||||
@cli.argument('-r', '--no-rle', arg_only=True, action='store_true', help='Disables the use of RLE when encoding images.')
|
||||
@cli.argument('-d', '--no-deltas', arg_only=True, action='store_true', help='Disables the use of delta frames when encoding animations.')
|
||||
@cli.subcommand('Converts an input image to something QMK understands')
|
||||
def painter_convert_graphics(cli):
|
||||
"""Converts an image file to a format that Quantum Painter understands.
|
||||
|
||||
This command uses the `qmk.painter` module to generate a Quantum Painter image defintion from an image. The generated definitions are written to a files next to the input -- `INPUT.c` and `INPUT.h`.
|
||||
"""
|
||||
# Work out the input file
|
||||
if cli.args.input != '-':
|
||||
cli.args.input = normpath(cli.args.input)
|
||||
|
||||
# Error checking
|
||||
if not cli.args.input.exists():
|
||||
cli.log.error('Input image file does not exist!')
|
||||
cli.print_usage()
|
||||
return False
|
||||
|
||||
# Work out the output directory
|
||||
if len(cli.args.output) == 0:
|
||||
cli.args.output = cli.args.input.parent
|
||||
cli.args.output = normpath(cli.args.output)
|
||||
|
||||
# Ensure we have a valid format
|
||||
if cli.args.format not in valid_formats.keys():
|
||||
cli.log.error('Output format %s is invalid. Allowed values: %s' % (cli.args.format, ', '.join(valid_formats.keys())))
|
||||
cli.print_usage()
|
||||
return False
|
||||
|
||||
# Work out the encoding parameters
|
||||
format = valid_formats[cli.args.format]
|
||||
|
||||
# Load the input image
|
||||
input_img = Image.open(cli.args.input)
|
||||
|
||||
# Convert the image to QGF using PIL
|
||||
out_data = BytesIO()
|
||||
input_img.save(out_data, "QGF", use_deltas=(not cli.args.no_deltas), use_rle=(not cli.args.no_rle), qmk_format=format, verbose=cli.args.verbose)
|
||||
out_bytes = out_data.getvalue()
|
||||
|
||||
# Work out the text substitutions for rendering the output data
|
||||
subs = {
|
||||
'generated_type': 'image',
|
||||
'var_prefix': 'gfx',
|
||||
'generator_command': f'qmk painter-convert-graphics -i {cli.args.input.name} -f {cli.args.format}',
|
||||
'year': datetime.date.today().strftime("%Y"),
|
||||
'input_file': cli.args.input.name,
|
||||
'sane_name': re.sub(r"[^a-zA-Z0-9]", "_", cli.args.input.stem),
|
||||
'byte_count': len(out_bytes),
|
||||
'bytes_lines': render_bytes(out_bytes),
|
||||
'format': cli.args.format,
|
||||
}
|
||||
|
||||
# Render the license
|
||||
subs.update({'license': render_license(subs)})
|
||||
|
||||
# Render and write the header file
|
||||
header_text = render_header(subs)
|
||||
header_file = cli.args.output / (cli.args.input.stem + ".qgf.h")
|
||||
with open(header_file, 'w') as header:
|
||||
print(f"Writing {header_file}...")
|
||||
header.write(header_text)
|
||||
header.close()
|
||||
|
||||
# Render and write the source file
|
||||
source_text = render_source(subs)
|
||||
source_file = cli.args.output / (cli.args.input.stem + ".qgf.c")
|
||||
with open(source_file, 'w') as source:
|
||||
print(f"Writing {source_file}...")
|
||||
source.write(source_text)
|
||||
source.close()
|
||||
87
lib/python/qmk/cli/painter/make_font.py
Normal file
87
lib/python/qmk/cli/painter/make_font.py
Normal file
@@ -0,0 +1,87 @@
|
||||
"""This script automates the conversion of font files into a format QMK firmware understands.
|
||||
"""
|
||||
|
||||
import re
|
||||
import datetime
|
||||
from io import BytesIO
|
||||
from qmk.path import normpath
|
||||
from qmk.painter_qff import QFFFont
|
||||
from qmk.painter import render_header, render_source, render_license, render_bytes, valid_formats
|
||||
from milc import cli
|
||||
|
||||
|
||||
@cli.argument('-f', '--font', required=True, help='Specify input font file.')
|
||||
@cli.argument('-o', '--output', required=True, help='Specify output image path.')
|
||||
@cli.argument('-s', '--size', default=12, help='Specify font size. Default 12.')
|
||||
@cli.argument('-n', '--no-ascii', arg_only=True, action='store_true', help='Disables output of the full ASCII character set (0x20..0x7E), exporting only the glyphs specified.')
|
||||
@cli.argument('-u', '--unicode-glyphs', default='', help='Also generate the specified unicode glyphs.')
|
||||
@cli.argument('-a', '--no-aa', arg_only=True, action='store_true', help='Disable anti-aliasing on fonts.')
|
||||
@cli.subcommand('Converts an input font to something QMK understands')
|
||||
def painter_make_font_image(cli):
|
||||
# Create the font object
|
||||
font = QFFFont(cli)
|
||||
# Read from the input file
|
||||
cli.args.font = normpath(cli.args.font)
|
||||
font.generate_image(cli.args.font, cli.args.size, include_ascii_glyphs=(not cli.args.no_ascii), unicode_glyphs=cli.args.unicode_glyphs, use_aa=(False if cli.args.no_aa else True))
|
||||
# Render out the data
|
||||
font.save_to_image(normpath(cli.args.output))
|
||||
|
||||
|
||||
@cli.argument('-i', '--input', help='Specify input graphic file.')
|
||||
@cli.argument('-o', '--output', default='', help='Specify output directory. Defaults to same directory as input.')
|
||||
@cli.argument('-n', '--no-ascii', arg_only=True, action='store_true', help='Disables output of the full ASCII character set (0x20..0x7E), exporting only the glyphs specified.')
|
||||
@cli.argument('-u', '--unicode-glyphs', default='', help='Also generate the specified unicode glyphs.')
|
||||
@cli.argument('-f', '--format', required=True, help='Output format, valid types: %s' % (', '.join(valid_formats.keys())))
|
||||
@cli.argument('-r', '--no-rle', arg_only=True, action='store_true', help='Disable the use of RLE to minimise converted image size.')
|
||||
@cli.subcommand('Converts an input font image to something QMK firmware understands')
|
||||
def painter_convert_font_image(cli):
|
||||
# Work out the format
|
||||
format = valid_formats[cli.args.format]
|
||||
|
||||
# Create the font object
|
||||
font = QFFFont(cli.log)
|
||||
|
||||
# Read from the input file
|
||||
cli.args.input = normpath(cli.args.input)
|
||||
font.read_from_image(cli.args.input, include_ascii_glyphs=(not cli.args.no_ascii), unicode_glyphs=cli.args.unicode_glyphs)
|
||||
|
||||
# Work out the output directory
|
||||
if len(cli.args.output) == 0:
|
||||
cli.args.output = cli.args.input.parent
|
||||
cli.args.output = normpath(cli.args.output)
|
||||
|
||||
# Render out the data
|
||||
out_data = BytesIO()
|
||||
font.save_to_qff(format, (False if cli.args.no_rle else True), out_data)
|
||||
|
||||
# Work out the text substitutions for rendering the output data
|
||||
subs = {
|
||||
'generated_type': 'font',
|
||||
'var_prefix': 'font',
|
||||
'generator_command': f'qmk painter-convert-font-image -i {cli.args.input.name} -f {cli.args.format}',
|
||||
'year': datetime.date.today().strftime("%Y"),
|
||||
'input_file': cli.args.input.name,
|
||||
'sane_name': re.sub(r"[^a-zA-Z0-9]", "_", cli.args.input.stem),
|
||||
'byte_count': out_data.getbuffer().nbytes,
|
||||
'bytes_lines': render_bytes(out_data.getbuffer().tobytes()),
|
||||
'format': cli.args.format,
|
||||
}
|
||||
|
||||
# Render the license
|
||||
subs.update({'license': render_license(subs)})
|
||||
|
||||
# Render and write the header file
|
||||
header_text = render_header(subs)
|
||||
header_file = cli.args.output / (cli.args.input.stem + ".qff.h")
|
||||
with open(header_file, 'w') as header:
|
||||
print(f"Writing {header_file}...")
|
||||
header.write(header_text)
|
||||
header.close()
|
||||
|
||||
# Render and write the source file
|
||||
source_text = render_source(subs)
|
||||
source_file = cli.args.output / (cli.args.input.stem + ".qff.c")
|
||||
with open(source_file, 'w') as source:
|
||||
print(f"Writing {source_file}...")
|
||||
source.write(source_text)
|
||||
source.close()
|
||||
@@ -12,8 +12,7 @@ from milc import cli
|
||||
def pytest(cli):
|
||||
"""Run several linting/testing commands.
|
||||
"""
|
||||
nose2 = cli.run(['nose2', '-v', '-t'
|
||||
'lib/python', *cli.args.test], capture_output=False, stdin=DEVNULL)
|
||||
nose2 = cli.run(['nose2', '-v', '-t', 'lib/python', *cli.args.test], capture_output=False, stdin=DEVNULL)
|
||||
flake8 = cli.run(['flake8', 'lib/python'], capture_output=False, stdin=DEVNULL)
|
||||
|
||||
return flake8.returncode | nose2.returncode
|
||||
|
||||
Reference in New Issue
Block a user