From 0f8b34771da8757a441e10fcf1ca6cc88233e78b Mon Sep 17 00:00:00 2001 From: Zach White Date: Sat, 2 Jan 2021 17:27:44 -0800 Subject: [PATCH 01/11] build an info.json from KLE --- lib/python/qmk/cli/kle2json.py | 111 +++++++++++++++++++--------- lib/python/qmk/converter.py | 46 +++++++++--- lib/python/qmk/info_json_encoder.py | 11 +++ 3 files changed, 124 insertions(+), 44 deletions(-) diff --git a/lib/python/qmk/cli/kle2json.py b/lib/python/qmk/cli/kle2json.py index 66d504bfc2..c17c184cc3 100755 --- a/lib/python/qmk/cli/kle2json.py +++ b/lib/python/qmk/cli/kle2json.py @@ -7,31 +7,78 @@ from pathlib import Path from milc import cli from kle2xy import KLE2xy +import qmk.path from qmk.converter import kle2qmk +from qmk.decorators import automagic_keyboard +from qmk.info import info_json from qmk.info_json_encoder import InfoJSONEncoder -@cli.argument('filename', help='The KLE raw txt to convert') -@cli.argument('-f', '--force', action='store_true', help='Flag to overwrite current info.json') -@cli.subcommand('Convert a KLE layout to a Configurator JSON', hidden=False if cli.config.user.developer else True) +@cli.argument('kle', arg_only=True, help='A file or KLE id to convert') +@cli.argument('--vid', arg_only=True, default='0x03A8', help='USB VID.') +@cli.argument('--pid', arg_only=True, default='0x0000', help='USB PID.') +@cli.argument('-m', '--manufacturer', arg_only=True, default='', help='Manufacturer of the keyboard.') +@cli.argument('-l', '--layout', arg_only=True, default='LAYOUT', help='The LAYOUT name this KLE represents.') +@cli.argument('-kb', '--keyboard', help='The folder for the keyboard.') +@cli.argument('-d', '--diode', arg_only=True, default='COL2ROW', help='The diode direction for the keyboard. (COL2ROW, ROW2COL)') +@cli.subcommand('Use a KLE layout to build info.json and a default keymap', hidden=False if cli.config.user.developer else True) +@automagic_keyboard def kle2json(cli): """Convert a KLE layout to QMK's layout format. - """ # If filename is a path - if cli.args.filename.startswith("/") or cli.args.filename.startswith("./"): - file_path = Path(cli.args.filename) - # Otherwise assume it is a file name + """ + file_path = Path(os.environ['ORIG_CWD'], cli.args.kle) + + # Find our KLE text + if file_path.exists(): + raw_code = file_path.open().read() + else: - file_path = Path(os.environ['ORIG_CWD'], cli.args.filename) - # Check for valid file_path for more graceful failure - if not file_path.exists(): - cli.log.error('File {fg_cyan}%s{style_reset_all} was not found.', file_path) - return False - out_path = file_path.parent - raw_code = file_path.open().read() - # Check if info.json exists, allow overwrite with force - if Path(out_path, "info.json").exists() and not cli.args.force: - cli.log.error('File {fg_cyan}%s/info.json{style_reset_all} already exists, use -f or --force to overwrite.', out_path) + if cli.args.kle.startswith('http') and '#' in cli.args.kle: + kle_path = cli.args.kle.split('#', 1)[1] + if 'gists' not in kle_path: + cli.log.error('Invalid KLE url: {fg_cyan}%s', cli.args.kle) + return False + else: + print('FIXME: fetch gist') + return False + else: + cli.log.error('File {fg_cyan}%s{style_reset_all} was not found.', file_path) + return False + + # Make sure the user supplied a keyboard + if not cli.config.kle2json.keyboard: + cli.log.error('You must pass --keyboard or be in a keyboard directory!') + cli.print_usage() return False + + # Check for an existing info.json + if qmk.path.is_keyboard(cli.config.kle2json.keyboard): + kb_info_json = info_json(cli.config.kle2json.keyboard) + else: + kb_info_json = { + "manufacturer": cli.args.manufacturer, + "keyboard_name": cli.config.kle2json.keyboard, + "maintainer": "", + "diode_direction": cli.args.diode, + "features": { + "console": True, + "extrakey": True, + "mousekey": True, + "nkro": True + }, + "matrix_pins": { + "cols": [], + "rows": [], + }, + "usb": { + "device_ver": "0x0001", + "pid": cli.args.pid, + "vid": cli.args.vid + }, + "layouts": {}, + } + + # Build and merge in the new layout try: # Convert KLE raw to x/y coordinates (using kle2xy package from skullydazed) kle = KLE2xy(raw_code) @@ -39,22 +86,18 @@ def kle2json(cli): cli.log.error('Could not parse KLE raw data: %s', raw_code) cli.log.exception(e) return False - keyboard = { - 'keyboard_name': kle.name, - 'url': '', - 'maintainer': 'qmk', - 'width': kle.columns, - 'height': kle.rows, - 'layouts': { - 'LAYOUT': { - 'layout': kle2qmk(kle) - } - }, - } + + if 'layouts' not in kb_info_json: + kb_info_json['layouts'] = {} + + if cli.args.layout not in kb_info_json['layouts']: + kb_info_json['layouts'][cli.args.layout] = {} + + kb_info_json['layouts'][cli.args.layout]['layout'] = kle2qmk(kle) # Write our info.json - keyboard = json.dumps(keyboard, indent=4, separators=(', ', ': '), sort_keys=False, cls=InfoJSONEncoder) - info_json_file = out_path / 'info.json' - - info_json_file.write_text(keyboard) - cli.log.info('Wrote out {fg_cyan}%s/info.json', out_path) + keyboard_dir = qmk.path.keyboard(cli.config.kle2json.keyboard) + keyboard_dir.mkdir(exist_ok=True, parents=True) + info_json_file = keyboard_dir / 'info.json' + json.dump(kb_info_json, info_json_file.open('w'), indent=4, separators=(', ', ': '), sort_keys=False, cls=InfoJSONEncoder) + cli.log.info('Wrote file %s', info_json_file) diff --git a/lib/python/qmk/converter.py b/lib/python/qmk/converter.py index bbd3531317..0e31e17b07 100644 --- a/lib/python/qmk/converter.py +++ b/lib/python/qmk/converter.py @@ -1,33 +1,59 @@ """Functions to convert to and from QMK formats """ -from collections import OrderedDict +from pprint import pprint + +from milc import cli def kle2qmk(kle): """Convert a KLE layout to QMK's layout format. """ layout = [] + layout_alternatives = {} + top_left_corner = None + # Iterate through the KLE classifying keys by layout for row in kle: for key in row: if key['decal']: continue - qmk_key = OrderedDict( - label="", - x=key['column'], - y=key['row'], - ) + if key['label_style'] in [0, 4]: + matrix, _, _, alt_layout, layout_name, _, keycode = key['name'].split('\n') + else: + cli.log.error('Unknown label style: %s', key['label_style']) + continue + matrix = list(map(int, matrix.split(',', 1))) + + qmk_key = { + 'label': keycode, + 'x': key['column'], + 'y': key['row'], + 'matrix': matrix, + } + + if not top_left_corner and (not alt_layout or alt_layout.endswith(',0')): + top_left_corner = key['column'], key['row'] + + # Figure out what layout this key is part of + # FIXME(skullydazed): In the future this will populate `layout_options` in info.json + if alt_layout: + alt_group, layout_index = map(int, alt_layout.split(',', 1)) + if layout_index != 0: + continue + + # Set the key size if key['width'] != 1: qmk_key['w'] = key['width'] if key['height'] != 1: qmk_key['h'] = key['height'] - if 'name' in key and key['name']: - qmk_key['label'] = key['name'].split('\n', 1)[0] - else: - del (qmk_key['label']) layout.append(qmk_key) + # Adjust the keys to account for the top-left corner + for key in layout: + key['x'] -= top_left_corner[0] + key['y'] -= top_left_corner[1] + return layout diff --git a/lib/python/qmk/info_json_encoder.py b/lib/python/qmk/info_json_encoder.py index 60dae7247f..44d83aa656 100755 --- a/lib/python/qmk/info_json_encoder.py +++ b/lib/python/qmk/info_json_encoder.py @@ -17,6 +17,17 @@ class InfoJSONEncoder(json.JSONEncoder): if not self.indent: self.indent = 4 + def default(self, obj): + """Fix certain objects that don't encode. + """ + if isinstance(obj, Decimal): + if obj == int(obj): + return int(obj) + + return float(obj) + + return json.JSONEncoder.default(self, obj) + def encode(self, obj): """Encode JSON objects for QMK. """ From 88cfd3554aaffab89eede8810c9f8e544fa38bc6 Mon Sep 17 00:00:00 2001 From: Zach White Date: Sat, 2 Jan 2021 18:35:01 -0800 Subject: [PATCH 02/11] add support for writing a default keymap with 2 layers --- lib/python/qmk/cli/kle2json.py | 48 +++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/lib/python/qmk/cli/kle2json.py b/lib/python/qmk/cli/kle2json.py index c17c184cc3..f79827aaa9 100755 --- a/lib/python/qmk/cli/kle2json.py +++ b/lib/python/qmk/cli/kle2json.py @@ -15,14 +15,14 @@ from qmk.info_json_encoder import InfoJSONEncoder @cli.argument('kle', arg_only=True, help='A file or KLE id to convert') -@cli.argument('--vid', arg_only=True, default='0x03A8', help='USB VID.') -@cli.argument('--pid', arg_only=True, default='0x0000', help='USB PID.') -@cli.argument('-m', '--manufacturer', arg_only=True, default='', help='Manufacturer of the keyboard.') -@cli.argument('-l', '--layout', arg_only=True, default='LAYOUT', help='The LAYOUT name this KLE represents.') -@cli.argument('-kb', '--keyboard', help='The folder for the keyboard.') -@cli.argument('-d', '--diode', arg_only=True, default='COL2ROW', help='The diode direction for the keyboard. (COL2ROW, ROW2COL)') -@cli.subcommand('Use a KLE layout to build info.json and a default keymap', hidden=False if cli.config.user.developer else True) -@automagic_keyboard +@cli.argument('--vid', arg_only=True, default='0x03A8', help='USB VID (Default: 0x03A8)') +@cli.argument('--pid', arg_only=True, default='0x0000', help='USB PID (Default: 0x0000)') +@cli.argument('-m', '--manufacturer', arg_only=True, default='', help='Manufacturer of the keyboard') +@cli.argument('-l', '--layout', arg_only=True, default='LAYOUT', help='The LAYOUT name this KLE represents') +@cli.argument('-kb', '--keyboard', arg_only=True, required=True, help='The folder name for the keyboard') +@cli.argument('-km', '--keymap', arg_only=True, default='default', help='The name of the keymap to write (Default: default)') +@cli.argument('-d', '--diode', arg_only=True, default='COL2ROW', help='The diode direction for the keyboard (COL2ROW, ROW2COL)') +@cli.subcommand('Use a KLE layout to build info.json and a keymap', hidden=False if cli.config.user.developer else True) def kle2json(cli): """Convert a KLE layout to QMK's layout format. """ @@ -46,18 +46,18 @@ def kle2json(cli): return False # Make sure the user supplied a keyboard - if not cli.config.kle2json.keyboard: + if not cli.args.keyboard: cli.log.error('You must pass --keyboard or be in a keyboard directory!') cli.print_usage() return False # Check for an existing info.json - if qmk.path.is_keyboard(cli.config.kle2json.keyboard): - kb_info_json = info_json(cli.config.kle2json.keyboard) + if qmk.path.is_keyboard(cli.args.keyboard): + kb_info_json = info_json(cli.args.keyboard) else: kb_info_json = { "manufacturer": cli.args.manufacturer, - "keyboard_name": cli.config.kle2json.keyboard, + "keyboard_name": cli.args.keyboard, "maintainer": "", "diode_direction": cli.args.diode, "features": { @@ -96,8 +96,30 @@ def kle2json(cli): kb_info_json['layouts'][cli.args.layout]['layout'] = kle2qmk(kle) # Write our info.json - keyboard_dir = qmk.path.keyboard(cli.config.kle2json.keyboard) + keyboard_dir = qmk.path.keyboard(cli.args.keyboard) keyboard_dir.mkdir(exist_ok=True, parents=True) info_json_file = keyboard_dir / 'info.json' + json.dump(kb_info_json, info_json_file.open('w'), indent=4, separators=(', ', ': '), sort_keys=False, cls=InfoJSONEncoder) cli.log.info('Wrote file %s', info_json_file) + + # Generate and write a keymap + keymap = [key.get('label', 'KC_NO') for key in kb_info_json['layouts'][cli.args.layout]['layout']] + keymap_json = { + 'version': 1, + 'documentation': "This file is a QMK Keymap. You can compile it with `qmk compile` or import it at . It can also be used directly with QMK's source code.", + 'author': '', + 'keyboard': kb_info_json['keyboard_name'], + 'keymap': cli.args.keymap, + 'layout': cli.args.layout, + 'layers': [ + keymap, + ['KC_TRNS' for key in keymap], + ], + } + keymap_path = keyboard_dir / 'keymaps' / cli.args.keymap + keymap_path.mkdir(exist_ok=True, parents=True) + keymap_file = keymap_path / 'keymap.json' + + json.dump(keymap_json, keymap_file.open('w'), indent=4, separators=(', ', ': '), sort_keys=False) + cli.log.info('Wrote file %s', keymap_file) From 2040fe3d8a08f126b5778c5e721edb9ca3e479ad Mon Sep 17 00:00:00 2001 From: Zach White Date: Sat, 2 Jan 2021 18:50:55 -0800 Subject: [PATCH 03/11] add support for generating a keymap --- lib/python/qmk/cli/kle2json.py | 38 +++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/lib/python/qmk/cli/kle2json.py b/lib/python/qmk/cli/kle2json.py index f79827aaa9..3d11242078 100755 --- a/lib/python/qmk/cli/kle2json.py +++ b/lib/python/qmk/cli/kle2json.py @@ -101,25 +101,29 @@ def kle2json(cli): info_json_file = keyboard_dir / 'info.json' json.dump(kb_info_json, info_json_file.open('w'), indent=4, separators=(', ', ': '), sort_keys=False, cls=InfoJSONEncoder) - cli.log.info('Wrote file %s', info_json_file) + cli.log.info('Wrote file {fg_cyan}%s', info_json_file) # Generate and write a keymap - keymap = [key.get('label', 'KC_NO') for key in kb_info_json['layouts'][cli.args.layout]['layout']] - keymap_json = { - 'version': 1, - 'documentation': "This file is a QMK Keymap. You can compile it with `qmk compile` or import it at . It can also be used directly with QMK's source code.", - 'author': '', - 'keyboard': kb_info_json['keyboard_name'], - 'keymap': cli.args.keymap, - 'layout': cli.args.layout, - 'layers': [ - keymap, - ['KC_TRNS' for key in keymap], - ], - } keymap_path = keyboard_dir / 'keymaps' / cli.args.keymap - keymap_path.mkdir(exist_ok=True, parents=True) keymap_file = keymap_path / 'keymap.json' - json.dump(keymap_json, keymap_file.open('w'), indent=4, separators=(', ', ': '), sort_keys=False) - cli.log.info('Wrote file %s', keymap_file) + if keymap_path.exists(): + cli.log.warning('{fg_cyan}%s{fg_reset} already exists, not generating a keymap.', keymap_path) + else: + keymap = [key.get('label', 'KC_NO') for key in kb_info_json['layouts'][cli.args.layout]['layout']] + keymap_json = { + 'version': 1, + 'documentation': "This file is a QMK Keymap. You can compile it with `qmk compile` or import it at . It can also be used directly with QMK's source code.", + 'author': '', + 'keyboard': kb_info_json['keyboard_name'], + 'keymap': cli.args.keymap, + 'layout': cli.args.layout, + 'layers': [ + keymap, + ['KC_TRNS' for key in keymap], + ], + } + keymap_path.mkdir(exist_ok=True, parents=True) + + json.dump(keymap_json, keymap_file.open('w'), indent=4, separators=(', ', ': '), sort_keys=False) + cli.log.info('Wrote file %s', keymap_file) From 63472dfde7a6b7b4a25c1ef5aae7d770e0c56e0b Mon Sep 17 00:00:00 2001 From: Zach White Date: Sun, 3 Jan 2021 13:18:34 -0800 Subject: [PATCH 04/11] add support for fetching KLE over http --- lib/python/qmk/cli/kle2json.py | 56 +++++++++++++++++++++++++++++++--- requirements.txt | 1 + 2 files changed, 53 insertions(+), 4 deletions(-) diff --git a/lib/python/qmk/cli/kle2json.py b/lib/python/qmk/cli/kle2json.py index 3d11242078..b6b5647612 100755 --- a/lib/python/qmk/cli/kle2json.py +++ b/lib/python/qmk/cli/kle2json.py @@ -4,6 +4,7 @@ import json import os from pathlib import Path +import requests from milc import cli from kle2xy import KLE2xy @@ -14,6 +15,51 @@ from qmk.info import info_json from qmk.info_json_encoder import InfoJSONEncoder +def fetch_json(url): + """Gets the JSON from a url. + """ + response = fetch_url(url) + + if response.status_code == 200: + return response.json() + + print(f'ERROR: {url} returned {response.status_code}: {response.text}') + return {} + + +def fetch_url(url): + """Fetch a URL. + """ + response = requests.get(url, timeout=30) + response.encoding='utf-8-sig' + + return response + + +def fetch_gist(id): + """Retrieve a gist from gist.github.com + """ + url = f'https://api.github.com/gists/{id}' + gist = fetch_json(url) + + for data in gist['files'].values(): + if data['filename'].endswith('kbd.json'): + if data.get('truncated'): + return fetch_url(data['raw_url']).text + else: + return data['content'] + + return None + + +def fetch_kle(id): + """Fetch the kle data from a gist ID. + """ + gist = fetch_gist(id) + + return gist[1:-1] + + @cli.argument('kle', arg_only=True, help='A file or KLE id to convert') @cli.argument('--vid', arg_only=True, default='0x03A8', help='USB VID (Default: 0x03A8)') @cli.argument('--pid', arg_only=True, default='0x0000', help='USB PID (Default: 0x0000)') @@ -39,11 +85,13 @@ def kle2json(cli): cli.log.error('Invalid KLE url: {fg_cyan}%s', cli.args.kle) return False else: - print('FIXME: fetch gist') - return False + raw_code = fetch_kle(kle_path.split('/')[-1]) + else: - cli.log.error('File {fg_cyan}%s{style_reset_all} was not found.', file_path) - return False + raw_code = fetch_kle(cli.args.kle) + if not raw_code: + cli.log.error('File {fg_cyan}%s{style_reset_all} was not found.', file_path) + return False # Make sure the user supplied a keyboard if not cli.args.keyboard: diff --git a/requirements.txt b/requirements.txt index 6e907cf8e8..cd392223ef 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,3 +5,4 @@ colorama hjson milc pygments +requests From 2ee52e43a72f11ccc345d3a6d1f1928fb12a2b8e Mon Sep 17 00:00:00 2001 From: Zach White Date: Sun, 3 Jan 2021 15:13:15 -0800 Subject: [PATCH 05/11] remove extraneous options --- lib/python/qmk/cli/kle2json.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/lib/python/qmk/cli/kle2json.py b/lib/python/qmk/cli/kle2json.py index b6b5647612..e6069709c0 100755 --- a/lib/python/qmk/cli/kle2json.py +++ b/lib/python/qmk/cli/kle2json.py @@ -61,13 +61,9 @@ def fetch_kle(id): @cli.argument('kle', arg_only=True, help='A file or KLE id to convert') -@cli.argument('--vid', arg_only=True, default='0x03A8', help='USB VID (Default: 0x03A8)') -@cli.argument('--pid', arg_only=True, default='0x0000', help='USB PID (Default: 0x0000)') -@cli.argument('-m', '--manufacturer', arg_only=True, default='', help='Manufacturer of the keyboard') @cli.argument('-l', '--layout', arg_only=True, default='LAYOUT', help='The LAYOUT name this KLE represents') @cli.argument('-kb', '--keyboard', arg_only=True, required=True, help='The folder name for the keyboard') @cli.argument('-km', '--keymap', arg_only=True, default='default', help='The name of the keymap to write (Default: default)') -@cli.argument('-d', '--diode', arg_only=True, default='COL2ROW', help='The diode direction for the keyboard (COL2ROW, ROW2COL)') @cli.subcommand('Use a KLE layout to build info.json and a keymap', hidden=False if cli.config.user.developer else True) def kle2json(cli): """Convert a KLE layout to QMK's layout format. @@ -104,10 +100,8 @@ def kle2json(cli): kb_info_json = info_json(cli.args.keyboard) else: kb_info_json = { - "manufacturer": cli.args.manufacturer, "keyboard_name": cli.args.keyboard, "maintainer": "", - "diode_direction": cli.args.diode, "features": { "console": True, "extrakey": True, @@ -120,8 +114,8 @@ def kle2json(cli): }, "usb": { "device_ver": "0x0001", - "pid": cli.args.pid, - "vid": cli.args.vid + "pid": '0x0000', + "vid": '0x03A8', }, "layouts": {}, } From cd1cfb7c36030da7882ac42340114d3b3cdf0811 Mon Sep 17 00:00:00 2001 From: Zach White Date: Sun, 3 Jan 2021 15:15:11 -0800 Subject: [PATCH 06/11] Document the new kle2json --- docs/_summary.md | 1 + docs/cli_commands.md | 24 +++++++++++------ docs/kle2json_guide.md | 58 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 8 deletions(-) create mode 100644 docs/kle2json_guide.md diff --git a/docs/_summary.md b/docs/_summary.md index 19498f6a20..63a04c34fa 100644 --- a/docs/_summary.md +++ b/docs/_summary.md @@ -22,6 +22,7 @@ * QMK API * [Overview](api_overview.md) * [API Documentation](api_docs.md) + * [KLE To info.json](kle2json_guide.md) * [Keyboard Support](reference_configurator_support.md) * [Adding Default Keymaps](configurator_default_keymaps.md) diff --git a/docs/cli_commands.md b/docs/cli_commands.md index 61ce1aa2a4..c34a184d61 100644 --- a/docs/cli_commands.md +++ b/docs/cli_commands.md @@ -308,24 +308,32 @@ qmk generate-rgb-breathe-table [-q] [-o OUTPUT] [-m MAX] [-c CENTER] ## `qmk kle2json` -This command allows you to convert from raw KLE data to QMK Configurator JSON. It accepts either an absolute file path, or a file name in the current directory. By default it will not overwrite `info.json` if it is already present. Use the `-f` or `--force` flag to overwrite. +This command allows you to convert [Keyboard-Layout-Editor.com](http://keyboard-layout-editor.com) layouts into `info.json` layouts. It will also create a `keymap.json` file for your layout. This saves a lot of time when setting up a new keyboard. + +To use this command your KLE will need to follow a specific format. See [KLE To info.json](kle2json_guide.md) for more details. **Usage**: ``` -qmk kle2json [-f] +qmk kle2json -kb [-km KEYMAP] [-l LAYOUT] ``` **Examples**: -``` -$ qmk kle2json kle.txt -☒ File info.json already exists, use -f or --force to overwrite. -``` +With only a KLE id: ``` -$ qmk kle2json -f kle.txt -f -Ψ Wrote out to info.json +$ qmk kle2json -kb clueboard/new60 70aaa4bed76d0b2f67fd165641239552 +Ψ Wrote file keyboards/clueboard/new60/info.json +Ψ Wrote file keyboards/clueboard/new60/keymaps/default/keymap.json +``` + +With a full URL: + +``` +$ qmk kle2json -kb clueboard/new60 'http://www.keyboard-layout-editor.com/#/gists/70aaa4bed76d0b2f67fd165641239552' +Ψ Wrote file keyboards/clueboard/new60/info.json +Ψ Wrote file keyboards/clueboard/new60/keymaps/default/keymap.json ``` ## `qmk pyformat` diff --git a/docs/kle2json_guide.md b/docs/kle2json_guide.md new file mode 100644 index 0000000000..f50271cc2b --- /dev/null +++ b/docs/kle2json_guide.md @@ -0,0 +1,58 @@ +# KLE To info.json + +This page describes how to create a [Keyboard Layout Editor](http://keyboard-layout-editor.com) layout that works with `qmk kle2json`. You will also be able to use the same KLE layout to create VIA support for your keyboard. + +## Overview + +QMK uses `info.json` to store metadata about the keyboard including the matrix pins, row and column configuration, and keyboard layout. You can encode some of this data into a KLE layout and then convert that to `info.json`, which saves you time and headaches creating files from scratch. + +Our reference layout is here: + +Keep that open in a separate window so that you can refer to it while reading this document. + +## Layout Structure + +The first thing you should notice is that the main layout for the keyboard is in the middle. This is the default layout. Around the sides you'll find layout options. These are groups of keys that can replace specific keys in the default layout. More on that later. + +## Anatomy of a Key + +Every key in KLE has 12 labels- top left, top center, top right, center left, center, center right, bottom left, bottom center, bottom right, front left, front center, front right. We do not use all 12 labels, but we do some of them. + +The labels we use are below: + +| | | | +|-|-|-| +| **Matrix** | (unused) | (unused) | +| **Keycode** | (unused) | (unused) | +| (unused) | (unused) | **Option Group** | +| **Option Name** | (unused) | (unused) | + +### Matrix + +This is the location of the key in the keyboard matrix. It should be two whole numbers separated by a comma representing row and column. For example, the top left location of the keyboard is usually `0,0`, to the right of that key is `0,1`, below that key is `1,0`, and so on. + +### Keycode + +This is the default keycode for layer 0. You should supply this now even if you won't use the generated keymap because QMK will make greater use of it in the future. + +### Option Group + +This identifies what Layout Options (if any) this key is part of. If this value is empty the key is not part of any Layout Options. + +The value is two whole numbers separated by a comma. The first number is the Layout Option Group Number, which identifies the specific Layout Option this key is associated with. The second number is the specific Layout Option Choice this key is associated with. + +### Option Name + +This identifies the name for each Option Group Choice. This will be used in the future to automatically build layouts from each option. All keys with the same Option Group identifier should have the same Option Name. + +## Running qmk kle2json + +Once you have built your KLE layout you are ready to convert it to `info.json`. Start by clicking `Sign In with GitHub` on Keyboard Layout Editor. Once you have signed in you can click `Save` to save your KLE. Once it's saved grab the URL from the bar, it should end end in `/#/gists/`. + +To use the reference layout as an example, I could update the keyboard `clueboard/60` from that KLE using this command: + +```shell +qmk kle2json -kb clueboard/60 -l LAYOUT_60_ansi 'http://www.keyboard-layout-editor.com/#/gists/70aaa4bed76d0b2f67fd165641239552' +``` + +It will fetch the KLE, generate a new `LAYOUT_60_ansi`, and either add a new layout or change the existing layout to match. From df85940a478615a9710eb6ac33e0160e556e8da2 Mon Sep 17 00:00:00 2001 From: Zach White Date: Sun, 3 Jan 2021 15:23:04 -0800 Subject: [PATCH 07/11] add a note about additional layouts --- docs/kle2json_guide.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/kle2json_guide.md b/docs/kle2json_guide.md index f50271cc2b..07ee36da59 100644 --- a/docs/kle2json_guide.md +++ b/docs/kle2json_guide.md @@ -56,3 +56,7 @@ qmk kle2json -kb clueboard/60 -l LAYOUT_60_ansi 'http://www.keyboard-layout-edit ``` It will fetch the KLE, generate a new `LAYOUT_60_ansi`, and either add a new layout or change the existing layout to match. + +## Adding more layouts + +At the current time QMK only supports the base layout. Layout Options are allowed for VIA compatibility purposes and will be implemented in QMK at a later date. For now we recommend you create a KLE for each layout you want to support in QMK and call `qmk kle2json` once per layout to set up the info.json. From 7f398eea50e415aee75ab63714d8abf1acfe06d7 Mon Sep 17 00:00:00 2001 From: Zach White Date: Sun, 3 Jan 2021 17:31:46 -0800 Subject: [PATCH 08/11] make flake8 happy --- lib/python/qmk/cli/kle2json.py | 3 +-- lib/python/qmk/converter.py | 3 --- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/python/qmk/cli/kle2json.py b/lib/python/qmk/cli/kle2json.py index e6069709c0..d5a5908e79 100755 --- a/lib/python/qmk/cli/kle2json.py +++ b/lib/python/qmk/cli/kle2json.py @@ -10,7 +10,6 @@ from kle2xy import KLE2xy import qmk.path from qmk.converter import kle2qmk -from qmk.decorators import automagic_keyboard from qmk.info import info_json from qmk.info_json_encoder import InfoJSONEncoder @@ -31,7 +30,7 @@ def fetch_url(url): """Fetch a URL. """ response = requests.get(url, timeout=30) - response.encoding='utf-8-sig' + response.encoding = 'utf-8-sig' return response diff --git a/lib/python/qmk/converter.py b/lib/python/qmk/converter.py index 0e31e17b07..068de21071 100644 --- a/lib/python/qmk/converter.py +++ b/lib/python/qmk/converter.py @@ -1,7 +1,5 @@ """Functions to convert to and from QMK formats """ -from pprint import pprint - from milc import cli @@ -9,7 +7,6 @@ def kle2qmk(kle): """Convert a KLE layout to QMK's layout format. """ layout = [] - layout_alternatives = {} top_left_corner = None # Iterate through the KLE classifying keys by layout From 0177fbd65b3c5d1ad9a44f14db108582ec953e35 Mon Sep 17 00:00:00 2001 From: Zach White Date: Thu, 7 Jan 2021 20:42:37 -0800 Subject: [PATCH 09/11] rearrange to more reliably detect urls --- lib/python/qmk/cli/kle2json.py | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/lib/python/qmk/cli/kle2json.py b/lib/python/qmk/cli/kle2json.py index d5a5908e79..8ca574c64f 100755 --- a/lib/python/qmk/cli/kle2json.py +++ b/lib/python/qmk/cli/kle2json.py @@ -70,23 +70,22 @@ def kle2json(cli): file_path = Path(os.environ['ORIG_CWD'], cli.args.kle) # Find our KLE text - if file_path.exists(): + if cli.args.kle.startswith('http') and '#' in cli.args.kle: + kle_path = cli.args.kle.split('#', 1)[1] + if 'gists' not in kle_path: + cli.log.error('Invalid KLE url: {fg_cyan}%s', cli.args.kle) + return False + else: + raw_code = fetch_kle(kle_path.split('/')[-1]) + + elif file_path.exists(): raw_code = file_path.open().read() else: - if cli.args.kle.startswith('http') and '#' in cli.args.kle: - kle_path = cli.args.kle.split('#', 1)[1] - if 'gists' not in kle_path: - cli.log.error('Invalid KLE url: {fg_cyan}%s', cli.args.kle) - return False - else: - raw_code = fetch_kle(kle_path.split('/')[-1]) - - else: - raw_code = fetch_kle(cli.args.kle) - if not raw_code: - cli.log.error('File {fg_cyan}%s{style_reset_all} was not found.', file_path) - return False + raw_code = fetch_kle(cli.args.kle) + if not raw_code: + cli.log.error('File {fg_cyan}%s{style_reset_all} was not found.', file_path) + return False # Make sure the user supplied a keyboard if not cli.args.keyboard: From 543e663c68544941c34f1e087a3074192c9ad93a Mon Sep 17 00:00:00 2001 From: Zach White Date: Thu, 7 Jan 2021 21:11:57 -0800 Subject: [PATCH 10/11] more robust key label parsing --- lib/python/qmk/converter.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/python/qmk/converter.py b/lib/python/qmk/converter.py index 068de21071..de2c1b459a 100644 --- a/lib/python/qmk/converter.py +++ b/lib/python/qmk/converter.py @@ -16,7 +16,15 @@ def kle2qmk(kle): continue if key['label_style'] in [0, 4]: - matrix, _, _, alt_layout, layout_name, _, keycode = key['name'].split('\n') + key_name = key['name'].split('\n') + if len(key_name) == 7: + matrix, _, _, alt_layout, layout_name, _, keycode = key_name + elif len(key_name) == 5: + matrix, _, _, alt_layout, layout_name = key_name + cli.log.warning('Missing Keycode for key at matrix %s layout %s.', matrix, alt_layout) + else: + cli.log.error('Unknown label format: %s', repr(key['name'])) + continue else: cli.log.error('Unknown label style: %s', key['label_style']) continue From 69e5e15ccc74f5903e991485bba964e3dfc79df7 Mon Sep 17 00:00:00 2001 From: Zach White Date: Fri, 8 Jan 2021 17:21:55 -0800 Subject: [PATCH 11/11] use unix lineendings even on windows --- lib/python/qmk/cli/chibios/confmigrate.py | 6 +++--- lib/python/qmk/cli/kle2json.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/python/qmk/cli/chibios/confmigrate.py b/lib/python/qmk/cli/chibios/confmigrate.py index b9cfda9614..06c2c5e4f1 100644 --- a/lib/python/qmk/cli/chibios/confmigrate.py +++ b/lib/python/qmk/cli/chibios/confmigrate.py @@ -145,17 +145,17 @@ def chibios_confmigrate(cli): if "CHCONF_H" in input_defs["dict"] or "_CHCONF_H_" in input_defs["dict"]: migrate_chconf_h(to_override, outfile=sys.stdout) if cli.args.overwrite: - with open(cli.args.input, "w") as out_file: + with open(cli.args.input, "w", newline='\n') as out_file: migrate_chconf_h(to_override, outfile=out_file) elif "HALCONF_H" in input_defs["dict"] or "_HALCONF_H_" in input_defs["dict"]: migrate_halconf_h(to_override, outfile=sys.stdout) if cli.args.overwrite: - with open(cli.args.input, "w") as out_file: + with open(cli.args.input, "w", newline='\n') as out_file: migrate_halconf_h(to_override, outfile=out_file) elif "MCUCONF_H" in input_defs["dict"] or "_MCUCONF_H_" in input_defs["dict"]: migrate_mcuconf_h(to_override, outfile=sys.stdout) if cli.args.overwrite: - with open(cli.args.input, "w") as out_file: + with open(cli.args.input, "w", newline='\n') as out_file: migrate_mcuconf_h(to_override, outfile=out_file) diff --git a/lib/python/qmk/cli/kle2json.py b/lib/python/qmk/cli/kle2json.py index 8ca574c64f..53cd9ac1f8 100755 --- a/lib/python/qmk/cli/kle2json.py +++ b/lib/python/qmk/cli/kle2json.py @@ -140,7 +140,7 @@ def kle2json(cli): keyboard_dir.mkdir(exist_ok=True, parents=True) info_json_file = keyboard_dir / 'info.json' - json.dump(kb_info_json, info_json_file.open('w'), indent=4, separators=(', ', ': '), sort_keys=False, cls=InfoJSONEncoder) + json.dump(kb_info_json, info_json_file.open('w', newline='\n'), indent=4, separators=(', ', ': '), sort_keys=False, cls=InfoJSONEncoder) cli.log.info('Wrote file {fg_cyan}%s', info_json_file) # Generate and write a keymap @@ -165,5 +165,5 @@ def kle2json(cli): } keymap_path.mkdir(exist_ok=True, parents=True) - json.dump(keymap_json, keymap_file.open('w'), indent=4, separators=(', ', ': '), sort_keys=False) + json.dump(keymap_json, keymap_file.open('w', newline='\n'), indent=4, separators=(', ', ': '), sort_keys=False) cli.log.info('Wrote file %s', keymap_file)