diff options
Diffstat (limited to 'lib/python/qmk')
| -rw-r--r-- | lib/python/qmk/cli/__init__.py | 3 | ||||
| -rw-r--r-- | lib/python/qmk/cli/doctor/macos.py | 2 | ||||
| -rw-r--r-- | lib/python/qmk/cli/flash.py | 2 | ||||
| -rwxr-xr-x | lib/python/qmk/cli/generate/config_h.py | 29 | ||||
| -rwxr-xr-x | lib/python/qmk/cli/generate/info_json.py | 8 | ||||
| -rw-r--r-- | lib/python/qmk/cli/import/__init__.py | 0 | ||||
| -rw-r--r-- | lib/python/qmk/cli/import/kbfirmware.py | 25 | ||||
| -rw-r--r-- | lib/python/qmk/cli/import/keyboard.py | 23 | ||||
| -rw-r--r-- | lib/python/qmk/cli/import/keymap.py | 23 | ||||
| -rw-r--r-- | lib/python/qmk/constants.py | 2 | ||||
| -rw-r--r-- | lib/python/qmk/git.py | 4 | ||||
| -rw-r--r-- | lib/python/qmk/importers.py | 148 | ||||
| -rw-r--r-- | lib/python/qmk/info.py | 58 | ||||
| -rw-r--r-- | lib/python/qmk/json_schema.py | 6 | 
14 files changed, 320 insertions, 13 deletions
| diff --git a/lib/python/qmk/cli/__init__.py b/lib/python/qmk/cli/__init__.py index 02c6d1cbf4..8a507677ef 100644 --- a/lib/python/qmk/cli/__init__.py +++ b/lib/python/qmk/cli/__init__.py @@ -59,6 +59,9 @@ subcommands = [      'qmk.cli.generate.rules_mk',      'qmk.cli.generate.version_h',      'qmk.cli.hello', +    'qmk.cli.import.kbfirmware', +    'qmk.cli.import.keyboard', +    'qmk.cli.import.keymap',      'qmk.cli.info',      'qmk.cli.json2c',      'qmk.cli.lint', diff --git a/lib/python/qmk/cli/doctor/macos.py b/lib/python/qmk/cli/doctor/macos.py index 00fb272858..5d088c9492 100644 --- a/lib/python/qmk/cli/doctor/macos.py +++ b/lib/python/qmk/cli/doctor/macos.py @@ -8,6 +8,6 @@ from .check import CheckStatus  def os_test_macos():      """Run the Mac specific tests.      """ -    cli.log.info("Detected {fg_cyan}macOS %s{fg_reset}.", platform.mac_ver()[0]) +    cli.log.info("Detected {fg_cyan}macOS %s (%s){fg_reset}.", platform.mac_ver()[0], 'Apple Silicon' if platform.processor() == 'arm' else 'Intel')      return CheckStatus.OK diff --git a/lib/python/qmk/cli/flash.py b/lib/python/qmk/cli/flash.py index 216896b974..ebe739c50e 100644 --- a/lib/python/qmk/cli/flash.py +++ b/lib/python/qmk/cli/flash.py @@ -33,6 +33,8 @@ def print_bootloader_help():      cli.echo('\tdfu-split-right')      cli.echo('\tdfu-util-split-left')      cli.echo('\tdfu-util-split-right') +    cli.echo('\tuf2-split-left') +    cli.echo('\tuf2-split-right')      cli.echo('For more info, visit https://docs.qmk.fm/#/flashing') diff --git a/lib/python/qmk/cli/generate/config_h.py b/lib/python/qmk/cli/generate/config_h.py index 893892c479..9d50368aba 100755 --- a/lib/python/qmk/cli/generate/config_h.py +++ b/lib/python/qmk/cli/generate/config_h.py @@ -134,6 +134,29 @@ def generate_config_items(kb_info_json, config_h_lines):              config_h_lines.append(f'#endif // {config_key}') +def generate_encoder_config(encoder_json, config_h_lines, postfix=''): +    """Generate the config.h lines for encoders.""" +    a_pads = [] +    b_pads = [] +    resolutions = [] +    for encoder in encoder_json.get("rotary", []): +        a_pads.append(encoder["pin_a"]) +        b_pads.append(encoder["pin_b"]) +        resolutions.append(str(encoder.get("resolution", 4))) + +    config_h_lines.append(f'#ifndef ENCODERS_PAD_A{postfix}') +    config_h_lines.append(f'#   define ENCODERS_PAD_A{postfix} {{ { ", ".join(a_pads) } }}') +    config_h_lines.append(f'#endif // ENCODERS_PAD_A{postfix}') + +    config_h_lines.append(f'#ifndef ENCODERS_PAD_B{postfix}') +    config_h_lines.append(f'#   define ENCODERS_PAD_B{postfix} {{ { ", ".join(b_pads) } }}') +    config_h_lines.append(f'#endif // ENCODERS_PAD_B{postfix}') + +    config_h_lines.append(f'#ifndef ENCODER_RESOLUTIONS{postfix}') +    config_h_lines.append(f'#   define ENCODER_RESOLUTIONS{postfix} {{ { ", ".join(resolutions) } }}') +    config_h_lines.append(f'#endif // ENCODER_RESOLUTIONS{postfix}') + +  def generate_split_config(kb_info_json, config_h_lines):      """Generate the config.h lines for split boards."""      if 'primary' in kb_info_json['split']: @@ -173,6 +196,9 @@ def generate_split_config(kb_info_json, config_h_lines):      if 'right' in kb_info_json['split'].get('matrix_pins', {}):          config_h_lines.append(matrix_pins(kb_info_json['split']['matrix_pins']['right'], '_RIGHT')) +    if 'right' in kb_info_json['split'].get('encoder', {}): +        generate_encoder_config(kb_info_json['split']['encoder']['right'], config_h_lines, '_RIGHT') +  @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") @@ -198,6 +224,9 @@ def generate_config_h(cli):      if 'matrix_pins' in kb_info_json:          config_h_lines.append(matrix_pins(kb_info_json['matrix_pins'])) +    if 'encoder' in kb_info_json: +        generate_encoder_config(kb_info_json['encoder'], config_h_lines) +      if 'split' in kb_info_json:          generate_split_config(kb_info_json, config_h_lines) diff --git a/lib/python/qmk/cli/generate/info_json.py b/lib/python/qmk/cli/generate/info_json.py index 284d1a8510..0dc80f10cc 100755 --- a/lib/python/qmk/cli/generate/info_json.py +++ b/lib/python/qmk/cli/generate/info_json.py @@ -5,7 +5,7 @@ Compile an info.json for a particular keyboard and pretty-print it.  import json  from argcomplete.completers import FilesCompleter -from jsonschema import Draft7Validator, RefResolver, validators +from jsonschema import Draft202012Validator, RefResolver, validators  from milc import cli  from pathlib import Path @@ -18,7 +18,7 @@ from qmk.path import is_keyboard, normpath  def pruning_validator(validator_class): -    """Extends Draft7Validator to remove properties that aren't specified in the schema. +    """Extends Draft202012Validator to remove properties that aren't specified in the schema.      """      validate_properties = validator_class.VALIDATORS["properties"] @@ -37,10 +37,10 @@ def strip_info_json(kb_info_json):      """Remove the API-only properties from the info.json.      """      schema_store = compile_schema_store() -    pruning_draft_7_validator = pruning_validator(Draft7Validator) +    pruning_draft_validator = pruning_validator(Draft202012Validator)      schema = schema_store['qmk.keyboard.v1']      resolver = RefResolver.from_schema(schema_store['qmk.keyboard.v1'], store=schema_store) -    validator = pruning_draft_7_validator(schema, resolver=resolver).validate +    validator = pruning_draft_validator(schema, resolver=resolver).validate      return validator(kb_info_json) diff --git a/lib/python/qmk/cli/import/__init__.py b/lib/python/qmk/cli/import/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/lib/python/qmk/cli/import/__init__.py diff --git a/lib/python/qmk/cli/import/kbfirmware.py b/lib/python/qmk/cli/import/kbfirmware.py new file mode 100644 index 0000000000..9c03737378 --- /dev/null +++ b/lib/python/qmk/cli/import/kbfirmware.py @@ -0,0 +1,25 @@ +from milc import cli + +from qmk.importers import import_kbfirmware as _import_kbfirmware +from qmk.path import FileType +from qmk.json_schema import json_load + + [email protected]('filename', type=FileType('r'), nargs='+', arg_only=True, help='file') [email protected]('Import kbfirmware json export') +def import_kbfirmware(cli): +    filename = cli.args.filename[0] + +    data = json_load(filename) + +    cli.log.info(f'{{style_bright}}Importing {filename.name}.{{style_normal}}') +    cli.echo('') + +    cli.log.warn("Support here is basic - Consider using 'qmk new-keyboard' instead") + +    kb_name = _import_kbfirmware(data) + +    cli.log.info(f'{{fg_green}}Imported a new keyboard named {{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}}.") diff --git a/lib/python/qmk/cli/import/keyboard.py b/lib/python/qmk/cli/import/keyboard.py new file mode 100644 index 0000000000..3a5ed37dee --- /dev/null +++ b/lib/python/qmk/cli/import/keyboard.py @@ -0,0 +1,23 @@ +from milc import cli + +from qmk.importers import import_keyboard as _import_keyboard +from qmk.path import FileType +from qmk.json_schema import json_load + + [email protected]('filename', type=FileType('r'), nargs='+', arg_only=True, help='file') [email protected]('Import data-driven keyboard') +def import_keyboard(cli): +    filename = cli.args.filename[0] + +    data = json_load(filename) + +    cli.log.info(f'{{style_bright}}Importing {filename.name}.{{style_normal}}') +    cli.echo('') + +    kb_name = _import_keyboard(data) + +    cli.log.info(f'{{fg_green}}Imported a new keyboard named {{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}}.") diff --git a/lib/python/qmk/cli/import/keymap.py b/lib/python/qmk/cli/import/keymap.py new file mode 100644 index 0000000000..a499c93480 --- /dev/null +++ b/lib/python/qmk/cli/import/keymap.py @@ -0,0 +1,23 @@ +from milc import cli + +from qmk.importers import import_keymap as _import_keymap +from qmk.path import FileType +from qmk.json_schema import json_load + + [email protected]('filename', type=FileType('r'), nargs='+', arg_only=True, help='file') [email protected]('Import data-driven keymap') +def import_keymap(cli): +    filename = cli.args.filename[0] + +    data = json_load(filename) + +    cli.log.info(f'{{style_bright}}Importing {filename.name}.{{style_normal}}') +    cli.echo('') + +    kb_name, km_name = _import_keymap(data) + +    cli.log.info(f'{{fg_green}}Imported a new keymap named {{fg_cyan}}{km_name}{{fg_green}}.{{fg_reset}}') +    cli.log.info(f'To start working on things, `cd` into {{fg_cyan}}keyboards/{kb_name}/keymaps/{km_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 {km_name}{{fg_reset}}.") diff --git a/lib/python/qmk/constants.py b/lib/python/qmk/constants.py index a54d9058bc..be85a1dbff 100644 --- a/lib/python/qmk/constants.py +++ b/lib/python/qmk/constants.py @@ -14,7 +14,7 @@ QMK_FIRMWARE_UPSTREAM = 'qmk/qmk_firmware'  MAX_KEYBOARD_SUBFOLDERS = 5  # Supported processor types -CHIBIOS_PROCESSORS = 'cortex-m0', 'cortex-m0plus', 'cortex-m3', 'cortex-m4', 'MKL26Z64', 'MK20DX128', 'MK20DX256', 'MK66FX1M0', 'STM32F042', 'STM32F072', 'STM32F103', 'STM32F303', 'STM32F401', 'STM32F405', 'STM32F407', 'STM32F411', 'STM32F446', 'STM32G431', 'STM32G474', 'STM32L412', 'STM32L422', 'STM32L432', 'STM32L433', 'STM32L442', 'STM32L443', 'GD32VF103', 'WB32F3G71', 'WB32FQ95' +CHIBIOS_PROCESSORS = 'cortex-m0', 'cortex-m0plus', 'cortex-m3', 'cortex-m4', 'MKL26Z64', 'MK20DX128', 'MK20DX256', 'MK66FX1M0', 'RP2040', 'STM32F042', 'STM32F072', 'STM32F103', 'STM32F303', 'STM32F401', 'STM32F405', 'STM32F407', 'STM32F411', 'STM32F446', 'STM32G431', 'STM32G474', 'STM32L412', 'STM32L422', 'STM32L432', 'STM32L433', 'STM32L442', 'STM32L443', 'GD32VF103', 'WB32F3G71', 'WB32FQ95'  LUFA_PROCESSORS = 'at90usb162', 'atmega16u2', 'atmega32u2', 'atmega16u4', 'atmega32u4', 'at90usb646', 'at90usb647', 'at90usb1286', 'at90usb1287', None  VUSB_PROCESSORS = 'atmega32a', 'atmega328p', 'atmega328', 'attiny85' diff --git a/lib/python/qmk/git.py b/lib/python/qmk/git.py index f493628492..960184a0a2 100644 --- a/lib/python/qmk/git.py +++ b/lib/python/qmk/git.py @@ -111,9 +111,9 @@ def git_check_deviation(active_branch):  def git_get_ignored_files(check_dir='.'): -    """Return a list of files that would be captured by the current .gitingore +    """Return a list of files that would be captured by the current .gitignore      """ -    invalid = cli.run(['git', 'ls-files', '-c', '-o', '-i', '--exclude-standard', check_dir]) +    invalid = cli.run(['git', 'ls-files', '-c', '-o', '-i', '--exclude-from=.gitignore', check_dir])      if invalid.returncode != 0:          return []      return invalid.stdout.strip().splitlines() diff --git a/lib/python/qmk/importers.py b/lib/python/qmk/importers.py new file mode 100644 index 0000000000..f9ecac02ae --- /dev/null +++ b/lib/python/qmk/importers.py @@ -0,0 +1,148 @@ +from dotty_dict import dotty +import json + +from qmk.json_schema import validate +from qmk.path import keyboard, keymap +from qmk.constants import MCU2BOOTLOADER +from qmk.json_encoders import InfoJSONEncoder, KeymapJSONEncoder + + +def _gen_dummy_keymap(name, info_data): +    # Pick the first layout macro and just dump in KC_NOs or something? +    (layout_name, layout_data), *_ = info_data["layouts"].items() +    layout_length = len(layout_data["layout"]) + +    keymap_data = { +        "keyboard": name, +        "layout": layout_name, +        "layers": [["KC_NO" for _ in range(0, layout_length)]], +    } + +    return json.dumps(keymap_data, cls=KeymapJSONEncoder) + + +def import_keymap(keymap_data): +    # Validate to ensure we don't have to deal with bad data - handles stdin/file +    validate(keymap_data, 'qmk.keymap.v1') + +    kb_name = keymap_data['keyboard'] +    km_name = keymap_data['keymap'] + +    km_folder = keymap(kb_name) / km_name +    keyboard_keymap = km_folder / 'keymap.json' + +    # This is the deepest folder in the expected tree +    keyboard_keymap.parent.mkdir(parents=True, exist_ok=True) + +    # Dump out all those lovely files +    keyboard_keymap.write_text(json.dumps(keymap_data, cls=KeymapJSONEncoder)) + +    return (kb_name, km_name) + + +def import_keyboard(info_data): +    # Validate to ensure we don't have to deal with bad data - handles stdin/file +    validate(info_data, 'qmk.api.keyboard.v1') + +    # And validate some more as everything is optional +    if not all(key in info_data for key in ['keyboard_name', 'layouts']): +        raise ValueError('invalid info.json') + +    kb_name = info_data['keyboard_name'] + +    # bail +    kb_folder = keyboard(kb_name) +    if kb_folder.exists(): +        raise ValueError(f'Keyboard {{fg_cyan}}{kb_name}{{fg_reset}} already exists! Please choose a different name.') + +    keyboard_info = kb_folder / 'info.json' +    keyboard_rules = kb_folder / 'rules.mk' +    keyboard_keymap = kb_folder / 'keymaps' / 'default' / 'keymap.json' + +    # This is the deepest folder in the expected tree +    keyboard_keymap.parent.mkdir(parents=True, exist_ok=True) + +    # Dump out all those lovely files +    keyboard_info.write_text(json.dumps(info_data, cls=InfoJSONEncoder)) +    keyboard_rules.write_text("# This file intentionally left blank") +    keyboard_keymap.write_text(_gen_dummy_keymap(kb_name, info_data)) + +    return kb_name + + +def import_kbfirmware(kbfirmware_data): +    kbf_data = dotty(kbfirmware_data) + +    diode_direction = ["COL2ROW", "ROW2COL"][kbf_data['keyboard.settings.diodeDirection']] +    mcu = ["atmega32u2", "atmega32u4", "at90usb1286"][kbf_data['keyboard.controller']] +    bootloader = MCU2BOOTLOADER.get(mcu, "custom") + +    layout = [] +    for key in kbf_data['keyboard.keys']: +        layout.append({ +            "matrix": [key["row"], key["col"]], +            "x": key["state"]["x"], +            "y": key["state"]["y"], +            "w": key["state"]["w"], +            "h": key["state"]["h"], +        }) + +    # convert to d/d info.json +    info_data = { +        "keyboard_name": kbf_data['keyboard.settings.name'].lower(), +        "manufacturer": "TODO", +        "maintainer": "TODO", +        "processor": mcu, +        "bootloader": bootloader, +        "diode_direction": diode_direction, +        "matrix_pins": { +            "cols": kbf_data['keyboard.pins.col'], +            "rows": kbf_data['keyboard.pins.row'], +        }, +        "usb": { +            "vid": "0xFEED", +            "pid": "0x0000", +            "device_version": "0.0.1", +        }, +        "features": { +            "bootmagic": True, +            "command": False, +            "console": False, +            "extrakey": True, +            "mousekey": True, +            "nkro": True, +        }, +        "layouts": { +            "LAYOUT": { +                "layout": layout, +            } +        } +    } + +    if kbf_data['keyboard.pins.num'] or kbf_data['keyboard.pins.caps'] or kbf_data['keyboard.pins.scroll']: +        indicators = {} +        if kbf_data['keyboard.pins.num']: +            indicators['num_lock'] = kbf_data['keyboard.pins.num'] +        if kbf_data['keyboard.pins.caps']: +            indicators['caps_lock'] = kbf_data['keyboard.pins.caps'] +        if kbf_data['keyboard.pins.scroll']: +            indicators['scroll_lock'] = kbf_data['keyboard.pins.scroll'] +        info_data['indicators'] = indicators + +    if kbf_data['keyboard.pins.rgb']: +        info_data['rgblight'] = { +            'animations': { +                'all': True +            }, +            'led_count': kbf_data['keyboard.settings.rgbNum'], +            'pin': kbf_data['keyboard.pins.rgb'], +        } + +    if kbf_data['keyboard.pins.led']: +        info_data['backlight'] = { +            'levels': kbf_data['keyboard.settings.backlightLevels'], +            'pin': kbf_data['keyboard.pins.led'], +        } + +    # delegate as if it were a regular keyboard import +    return import_keyboard(info_data) diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py index 23761d71b7..d308de9db8 100644 --- a/lib/python/qmk/info.py +++ b/lib/python/qmk/info.py @@ -218,6 +218,62 @@ def _extract_audio(info_data, config_c):          info_data['audio'] = {'pins': audio_pins} +def _extract_encoders_values(config_c, postfix=''): +    """Common encoder extraction logic +    """ +    a_pad = config_c.get(f'ENCODERS_PAD_A{postfix}', '').replace(' ', '')[1:-1] +    b_pad = config_c.get(f'ENCODERS_PAD_B{postfix}', '').replace(' ', '')[1:-1] +    resolutions = config_c.get(f'ENCODER_RESOLUTIONS{postfix}', '').replace(' ', '')[1:-1] + +    default_resolution = config_c.get('ENCODER_RESOLUTION', '4') + +    if a_pad and b_pad: +        a_pad = list(filter(None, a_pad.split(','))) +        b_pad = list(filter(None, b_pad.split(','))) +        resolutions = list(filter(None, resolutions.split(','))) +        resolutions += [default_resolution] * (len(a_pad) - len(resolutions)) + +        encoders = [] +        for index in range(len(a_pad)): +            encoders.append({'pin_a': a_pad[index], 'pin_b': b_pad[index], "resolution": int(resolutions[index])}) + +        return encoders + + +def _extract_encoders(info_data, config_c): +    """Populate data about encoder pins +    """ +    encoders = _extract_encoders_values(config_c) +    if encoders: +        if 'encoder' not in info_data: +            info_data['encoder'] = {} + +        if 'rotary' in info_data['encoder']: +            _log_warning(info_data, 'Encoder config is specified in both config.h and info.json (encoder.rotary) (Value: %s), the config.h value wins.' % info_data['encoder']['rotary']) + +        info_data['encoder']['rotary'] = encoders + + +def _extract_split_encoders(info_data, config_c): +    """Populate data about split encoder pins +    """ +    encoders = _extract_encoders_values(config_c, '_RIGHT') +    if encoders: +        if 'split' not in info_data: +            info_data['split'] = {} + +        if 'encoder' not in info_data['split']: +            info_data['split']['encoder'] = {} + +        if 'right' not in info_data['split']['encoder']: +            info_data['split']['encoder']['right'] = {} + +        if 'rotary' in info_data['split']['encoder']['right']: +            _log_warning(info_data, 'Encoder config is specified in both config.h and info.json (encoder.rotary) (Value: %s), the config.h value wins.' % info_data['split']['encoder']['right']['rotary']) + +        info_data['split']['encoder']['right']['rotary'] = encoders + +  def _extract_secure_unlock(info_data, config_c):      """Populate data about the secure unlock sequence      """ @@ -506,6 +562,8 @@ def _extract_config_h(info_data, config_c):      _extract_split_main(info_data, config_c)      _extract_split_transport(info_data, config_c)      _extract_split_right_pins(info_data, config_c) +    _extract_encoders(info_data, config_c) +    _extract_split_encoders(info_data, config_c)      _extract_device_version(info_data)      return info_data diff --git a/lib/python/qmk/json_schema.py b/lib/python/qmk/json_schema.py index 682346113e..01175146b5 100644 --- a/lib/python/qmk/json_schema.py +++ b/lib/python/qmk/json_schema.py @@ -68,11 +68,7 @@ def create_validator(schema):      schema_store = compile_schema_store()      resolver = jsonschema.RefResolver.from_schema(schema_store[schema], store=schema_store) -    # TODO: Remove this after the jsonschema>=4 requirement had time to reach users -    try: -        return jsonschema.Draft202012Validator(schema_store[schema], resolver=resolver).validate -    except AttributeError: -        return jsonschema.Draft7Validator(schema_store[schema], resolver=resolver).validate +    return jsonschema.Draft202012Validator(schema_store[schema], resolver=resolver).validate  def validate(data, schema): | 
