Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add cli command to import keyboard|keymap|kbfirmware #16668

Merged
merged 5 commits into from
Jul 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions docs/cli_commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,73 @@ $ qmk via2json -kb ai03/polaris -o polaris_keymap.json polaris_via_backup.json
Ψ Wrote keymap to /home/you/qmk_firmware/polaris_keymap.json
```

## `qmk import-keyboard`

This command imports a data-driven `info.json` keyboard into the repo.

**Usage**:

```
usage: qmk import-keyboard [-h] filename
```

**Example:**

```
$ qmk import-keyboard ~/Downloads/forever60.json
Ψ Importing forever60.json.
Ψ Imported a new keyboard named forever60.
Ψ To start working on things, `cd` into keyboards/forever60,
Ψ or open the directory in your preferred text editor.
Ψ And build with qmk compile -kb forever60 -km default.
```

## `qmk import-keymap`

This command imports a data-driven `keymap.json` keymap into the repo.

**Usage**:

```
usage: qmk import-keymap [-h] filename
```

**Example:**

```
qmk import-keymap ~/Downloads/asdf2.json
Ψ Importing asdf2.json.
Ψ Imported a new keymap named asdf2.
Ψ To start working on things, `cd` into keyboards/takashicompany/dogtag/keymaps/asdf2,
Ψ or open the directory in your preferred text editor.
Ψ And build with qmk compile -kb takashicompany/dogtag -km asdf2.
```

## `qmk import-kbfirmware`

This command creates a new keyboard based on a [Keyboard Firmware Builder](https://kbfirmware.com/) export.

**Usage**:

```
usage: qmk import-kbfirmware [-h] filename
```

**Example:**

```
$ qmk import-kbfirmware ~/Downloads/gh62.json
Ψ Importing gh62.json.
⚠ Support here is basic - Consider using 'qmk new-keyboard' instead
Ψ Imported a new keyboard named gh62.
Ψ To start working on things, `cd` into keyboards/gh62,
Ψ or open the directory in your preferred text editor.
Ψ And build with qmk compile -kb gh62 -km default.
```

---

# Developer Commands
Expand Down Expand Up @@ -527,3 +594,4 @@ This command converts a TTF font to an intermediate format for editing, before c
## `qmk painter-convert-font-image`

This command converts an intermediate font image to the QFF File Format. See the [Quantum Painter](quantum_painter.md?id=quantum-painter-cli) documentation for more information on this command.

3 changes: 3 additions & 0 deletions lib/python/qmk/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@
'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',
Expand Down
Empty file.
25 changes: 25 additions & 0 deletions lib/python/qmk/cli/import/kbfirmware.py
Original file line number Diff line number Diff line change
@@ -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


@cli.argument('filename', type=FileType('r'), nargs='+', arg_only=True, help='file')
@cli.subcommand('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}}.")
23 changes: 23 additions & 0 deletions lib/python/qmk/cli/import/keyboard.py
Original file line number Diff line number Diff line change
@@ -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


@cli.argument('filename', type=FileType('r'), nargs='+', arg_only=True, help='file')
@cli.subcommand('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}}.")
23 changes: 23 additions & 0 deletions lib/python/qmk/cli/import/keymap.py
Original file line number Diff line number Diff line change
@@ -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


@cli.argument('filename', type=FileType('r'), nargs='+', arg_only=True, help='file')
@cli.subcommand('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}}.")
148 changes: 148 additions & 0 deletions lib/python/qmk/importers.py
Original file line number Diff line number Diff line change
@@ -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)