summaryrefslogtreecommitdiff
path: root/lib/python/qmk/cli
diff options
context:
space:
mode:
Diffstat (limited to 'lib/python/qmk/cli')
-rw-r--r--lib/python/qmk/cli/c2json.py59
-rwxr-xr-xlib/python/qmk/cli/generate/config_h.py59
-rwxr-xr-xlib/python/qmk/cli/generate/keyboard_c.py27
-rw-r--r--lib/python/qmk/cli/generate/keycodes.py8
-rwxr-xr-xlib/python/qmk/cli/generate/rules_mk.py26
5 files changed, 119 insertions, 60 deletions
diff --git a/lib/python/qmk/cli/c2json.py b/lib/python/qmk/cli/c2json.py
index 7f6aca070a..f7f1f2ffba 100644
--- a/lib/python/qmk/cli/c2json.py
+++ b/lib/python/qmk/cli/c2json.py
@@ -1,45 +1,57 @@
"""Generate a keymap.json from a keymap.c file.
"""
+import re
import json
from argcomplete.completers import FilesCompleter
from milc import cli
-import qmk.keymap
import qmk.path
from qmk.json_encoders import InfoJSONEncoder
+from qmk.decorators import automagic_keyboard, automagic_keymap
from qmk.keyboard import keyboard_completer, keyboard_folder
+from qmk.keymap import locate_keymap, find_keymap_from_dir, generate_json, c2json as c2json_impl
from qmk.errors import CppError
+from qmk.commands import dump_lines
@cli.argument('--no-cpp', arg_only=True, action='store_false', help='Do not use \'cpp\' on keymap.c')
@cli.argument('-o', '--output', arg_only=True, type=qmk.path.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='The keyboard\'s name')
-@cli.argument('-km', '--keymap', arg_only=True, required=True, help='The keymap\'s name')
-@cli.argument('filename', arg_only=True, completer=FilesCompleter('.c'), help='keymap.c file')
+@cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='The keyboard\'s name')
+@cli.argument('-km', '--keymap', help='The keymap\'s name')
+@cli.argument('filename', nargs='?', type=qmk.path.FileType('r'), arg_only=True, completer=FilesCompleter('.c'), help='keymap.c file')
@cli.subcommand('Creates a keymap.json from a keymap.c file.')
+@automagic_keyboard
+@automagic_keymap
def c2json(cli):
"""Generate a keymap.json from a keymap.c file.
This command uses the `qmk.keymap` module to generate a keymap.json from a keymap.c file. The generated keymap is written to stdout, or to a file if -o is provided.
"""
- if cli.args.filename != '-':
- cli.args.filename = qmk.path.normpath(cli.args.filename)
+ filename = cli.args.filename
+ keyboard = cli.config.c2json.keyboard
+ keymap = cli.config.c2json.keymap
- # Error checking
- if not cli.args.filename.exists():
- cli.log.error('C file does not exist!')
- cli.print_usage()
- return False
+ if filename:
+ if not keyboard and not keymap:
+ # fallback to inferring keyboard/keymap from path
+ (keymap, found_type) = find_keymap_from_dir(filename)
+ if found_type == 'keymap_directory':
+ keyboard = re.search(fr"keyboards/(.+)/keymaps/{keymap}/.*", filename.as_posix()).group(1)
- # Environment processing
- if cli.args.output == ('-'):
- cli.args.output = None
+ elif keyboard and keymap:
+ if not filename:
+ # fallback to inferring keyboard/keymap from path
+ filename = locate_keymap(keyboard, keymap)
+
+ if not all((filename, keyboard, keymap)):
+ cli.log.error('You must supply keyboard and keymap, a path to a keymap.c within qmk_firmware, or absolute filename and keyboard and keymap')
+ cli.print_help()
+ return False
- # Parse the keymap.c
try:
- keymap_json = qmk.keymap.c2json(cli.args.keyboard, cli.args.keymap, cli.args.filename, use_cpp=cli.args.no_cpp)
+ keymap_json = c2json_impl(keyboard, keymap, filename, use_cpp=cli.args.no_cpp)
except CppError as e:
if cli.config.general.verbose:
cli.log.debug('The C pre-processor ran into a fatal error: %s', e)
@@ -48,19 +60,14 @@ def c2json(cli):
# Generate the keymap.json
try:
- keymap_json = qmk.keymap.generate_json(keymap_json['keymap'], keymap_json['keyboard'], keymap_json['layout'], keymap_json['layers'])
+ keymap_json = generate_json(keymap_json['keymap'], keymap_json['keyboard'], keymap_json['layout'], keymap_json['layers'])
except KeyError:
cli.log.error('Something went wrong. Try to use --no-cpp.')
return False
if cli.args.output:
- cli.args.output.parent.mkdir(parents=True, exist_ok=True)
- if cli.args.output.exists():
- cli.args.output.replace(cli.args.output.parent / (cli.args.output.name + '.bak'))
- cli.args.output.write_text(json.dumps(keymap_json, cls=InfoJSONEncoder, sort_keys=True))
-
- if not cli.args.quiet:
- cli.log.info('Wrote keymap to %s.', cli.args.output)
-
+ keymap_lines = [json.dumps(keymap_json, cls=InfoJSONEncoder, sort_keys=True)]
else:
- print(json.dumps(keymap_json))
+ keymap_lines = [json.dumps(keymap_json)]
+
+ dump_lines(cli.args.output, keymap_lines, cli.args.quiet)
diff --git a/lib/python/qmk/cli/generate/config_h.py b/lib/python/qmk/cli/generate/config_h.py
index 64d4db6ffe..00fb1d9585 100755
--- a/lib/python/qmk/cli/generate/config_h.py
+++ b/lib/python/qmk/cli/generate/config_h.py
@@ -15,7 +15,12 @@ from qmk.constants import GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE
def generate_define(define, value=None):
+ is_keymap = cli.args.filename
value = f' {value}' if value is not None else ''
+ if is_keymap:
+ return f"""
+#undef {define}
+#define {define}{value}"""
return f"""
#ifndef {define}
# define {define}{value}
@@ -67,6 +72,19 @@ def generate_matrix_size(kb_info_json, config_h_lines):
config_h_lines.append(generate_define('MATRIX_ROWS', kb_info_json['matrix_size']['rows']))
+def generate_matrix_masked(kb_info_json, config_h_lines):
+ """"Enable matrix mask if required"""
+ mask_required = False
+
+ if 'matrix_grid' in kb_info_json.get('dip_switch', {}):
+ mask_required = True
+ if 'matrix_grid' in kb_info_json.get('split', {}).get('handedness', {}):
+ mask_required = True
+
+ if mask_required:
+ config_h_lines.append(generate_define('MATRIX_MASKED'))
+
+
def generate_config_items(kb_info_json, config_h_lines):
"""Iterate through the info_config map to generate basic config values.
"""
@@ -75,9 +93,9 @@ 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', 'raw')
- to_config = info_dict.get('to_config', True)
+ to_c = info_dict.get('to_c', True)
- if not to_config:
+ if not to_c:
continue
try:
@@ -130,23 +148,11 @@ def generate_encoder_config(encoder_json, config_h_lines, 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']:
- if kb_info_json['split']['primary'] in ('left', 'right'):
- config_h_lines.append('')
- config_h_lines.append('#ifndef MASTER_LEFT')
- config_h_lines.append('# ifndef MASTER_RIGHT')
- if kb_info_json['split']['primary'] == 'left':
- config_h_lines.append('# define MASTER_LEFT')
- elif kb_info_json['split']['primary'] == 'right':
- config_h_lines.append('# define MASTER_RIGHT')
- config_h_lines.append('# endif // MASTER_RIGHT')
- config_h_lines.append('#endif // MASTER_LEFT')
- elif kb_info_json['split']['primary'] == 'pin':
- config_h_lines.append(generate_define('SPLIT_HAND_PIN'))
- elif kb_info_json['split']['primary'] == 'matrix_grid':
- config_h_lines.append(generate_define('SPLIT_HAND_MATRIX_GRID', f'{{ {",".join(kb_info_json["split"]["matrix_grid"])} }}'))
- elif kb_info_json['split']['primary'] == 'eeprom':
- config_h_lines.append(generate_define('EE_HANDS'))
+ if 'handedness' in kb_info_json['split']:
+ # TODO: change SPLIT_HAND_MATRIX_GRID to require brackets
+ handedness = kb_info_json['split']['handedness']
+ if 'matrix_grid' in handedness:
+ config_h_lines.append(generate_define('SPLIT_HAND_MATRIX_GRID', ', '.join(handedness['matrix_grid'])))
if 'protocol' in kb_info_json['split'].get('transport', {}):
if kb_info_json['split']['transport']['protocol'] == 'i2c':
@@ -159,10 +165,13 @@ def generate_split_config(kb_info_json, config_h_lines):
generate_encoder_config(kb_info_json['split']['encoder']['right'], config_h_lines, '_RIGHT')
-def generate_led_animations_config(led_feature_json, config_h_lines, prefix):
+def generate_led_animations_config(feature, led_feature_json, config_h_lines, enable_prefix, animation_prefix):
+ if 'animation' in led_feature_json.get('default', {}):
+ config_h_lines.append(generate_define(f'{feature.upper()}_DEFAULT_MODE', f'{animation_prefix}{led_feature_json["default"]["animation"].upper()}'))
+
for animation in led_feature_json.get('animations', {}):
if led_feature_json['animations'][animation]:
- config_h_lines.append(generate_define(f'{prefix}{animation.upper()}'))
+ config_h_lines.append(generate_define(f'{enable_prefix}{animation.upper()}'))
@cli.argument('filename', nargs='?', arg_only=True, type=FileType('r'), completer=FilesCompleter('.json'), help='A configurator export JSON to be compiled and flashed or a pre-compiled binary firmware file (bin/hex) to be flashed.')
@@ -191,6 +200,8 @@ def generate_config_h(cli):
generate_matrix_size(kb_info_json, config_h_lines)
+ generate_matrix_masked(kb_info_json, config_h_lines)
+
if 'matrix_pins' in kb_info_json:
config_h_lines.append(matrix_pins(kb_info_json['matrix_pins']))
@@ -201,13 +212,13 @@ def generate_config_h(cli):
generate_split_config(kb_info_json, config_h_lines)
if 'led_matrix' in kb_info_json:
- generate_led_animations_config(kb_info_json['led_matrix'], config_h_lines, 'ENABLE_LED_MATRIX_')
+ generate_led_animations_config('led_matrix', kb_info_json['led_matrix'], config_h_lines, 'ENABLE_LED_MATRIX_', 'LED_MATRIX_')
if 'rgb_matrix' in kb_info_json:
- generate_led_animations_config(kb_info_json['rgb_matrix'], config_h_lines, 'ENABLE_RGB_MATRIX_')
+ generate_led_animations_config('rgb_matrix', kb_info_json['rgb_matrix'], config_h_lines, 'ENABLE_RGB_MATRIX_', 'RGB_MATRIX_')
if 'rgblight' in kb_info_json:
- generate_led_animations_config(kb_info_json['rgblight'], config_h_lines, 'RGBLIGHT_EFFECT_')
+ generate_led_animations_config('rgblight', kb_info_json['rgblight'], config_h_lines, 'RGBLIGHT_EFFECT_', 'RGBLIGHT_MODE_')
# Show the results
dump_lines(cli.args.output, config_h_lines, cli.args.quiet)
diff --git a/lib/python/qmk/cli/generate/keyboard_c.py b/lib/python/qmk/cli/generate/keyboard_c.py
index 9004b41abb..f8a2372cf3 100755
--- a/lib/python/qmk/cli/generate/keyboard_c.py
+++ b/lib/python/qmk/cli/generate/keyboard_c.py
@@ -57,6 +57,32 @@ def _gen_led_config(info_data):
return lines
+def _gen_matrix_mask(info_data):
+ """Convert info.json content to matrix_mask
+ """
+ cols = info_data['matrix_size']['cols']
+ rows = info_data['matrix_size']['rows']
+
+ # Default mask to everything disabled
+ mask = [['0'] * cols for i in range(rows)]
+
+ # Mirror layout macros squashed on top of each other
+ for layout_data in info_data['layouts'].values():
+ for key_data in layout_data['layout']:
+ row, col = key_data['matrix']
+ mask[row][col] = '1'
+
+ lines = []
+ lines.append('#ifdef MATRIX_MASKED')
+ lines.append('__attribute__((weak)) const matrix_row_t matrix_mask[] = {')
+ for i in range(rows):
+ lines.append(f' 0b{"".join(reversed(mask[i]))},')
+ 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.')
@@ -70,6 +96,7 @@ def generate_keyboard_c(cli):
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))
+ keyboard_h_lines.extend(_gen_matrix_mask(kb_info_json))
# Show the results
dump_lines(cli.args.output, keyboard_h_lines, cli.args.quiet)
diff --git a/lib/python/qmk/cli/generate/keycodes.py b/lib/python/qmk/cli/generate/keycodes.py
index ed8b6827bd..719fced5d5 100644
--- a/lib/python/qmk/cli/generate/keycodes.py
+++ b/lib/python/qmk/cli/generate/keycodes.py
@@ -94,6 +94,14 @@ def _generate_helpers(lines, keycodes):
hi = keycodes["keycodes"][f'0x{codes[1]:04X}']['key']
lines.append(f'#define IS_{ _translate_group(group).upper() }_KEYCODE(code) ((code) >= {lo} && (code) <= {hi})')
+ lines.append('')
+ lines.append('// Switch statement Helpers')
+ for group, codes in temp.items():
+ lo = keycodes["keycodes"][f'0x{codes[0]:04X}']['key']
+ hi = keycodes["keycodes"][f'0x{codes[1]:04X}']['key']
+ name = f'{ _translate_group(group).upper() }_KEYCODE_RANGE'
+ lines.append(f'#define { name.ljust(35) } {lo} ... {hi}')
+
def _generate_aliases(lines, keycodes):
# Work around ChibiOS ch.h include guard
diff --git a/lib/python/qmk/cli/generate/rules_mk.py b/lib/python/qmk/cli/generate/rules_mk.py
index fc272da6c6..5291556109 100755
--- a/lib/python/qmk/cli/generate/rules_mk.py
+++ b/lib/python/qmk/cli/generate/rules_mk.py
@@ -14,6 +14,12 @@ from qmk.path import normpath, FileType
from qmk.constants import GPL2_HEADER_SH_LIKE, GENERATED_HEADER_SH_LIKE
+def generate_rule(rules_key, rules_value):
+ is_keymap = cli.args.filename
+ rule_assignment_operator = '=' if is_keymap else '?='
+ return f'{rules_key} {rule_assignment_operator} {rules_value}'
+
+
def process_mapping_rule(kb_info_json, rules_key, info_dict):
"""Return the rules.mk line(s) for a mapping rule.
"""
@@ -29,15 +35,15 @@ def process_mapping_rule(kb_info_json, rules_key, info_dict):
return None
if key_type in ['array', 'list']:
- return f'{rules_key} ?= {" ".join(rules_value)}'
+ return generate_rule(rules_key, " ".join(rules_value))
elif key_type == 'bool':
- return f'{rules_key} ?= {"yes" if rules_value else "no"}'
+ return generate_rule(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()])
+ return '\n'.join([generate_rule(key, value) for key, value in rules_value.items()])
elif key_type == 'str':
- return f'{rules_key} ?= "{rules_value}"'
+ return generate_rule(rules_key, f'"{rules_value}"')
- return f'{rules_key} ?= {rules_value}'
+ return generate_rule(rules_key, rules_value)
@cli.argument('filename', nargs='?', arg_only=True, type=FileType('r'), completer=FilesCompleter('.json'), help='A configurator export JSON to be compiled and flashed or a pre-compiled binary firmware file (bin/hex) to be flashed.')
@@ -77,21 +83,21 @@ def generate_rules_mk(cli):
for feature, enabled in kb_info_json['features'].items():
feature = feature.upper()
enabled = 'yes' if enabled else 'no'
- rules_mk_lines.append(f'{feature}_ENABLE ?= {enabled}')
+ rules_mk_lines.append(generate_rule(f'{feature}_ENABLE', enabled))
# Set SPLIT_TRANSPORT, if needed
if kb_info_json.get('split', {}).get('transport', {}).get('protocol') == 'custom':
- rules_mk_lines.append('SPLIT_TRANSPORT ?= custom')
+ rules_mk_lines.append(generate_rule('SPLIT_TRANSPORT', 'custom'))
# Set CUSTOM_MATRIX, if needed
if kb_info_json.get('matrix_pins', {}).get('custom'):
if kb_info_json.get('matrix_pins', {}).get('custom_lite'):
- rules_mk_lines.append('CUSTOM_MATRIX ?= lite')
+ rules_mk_lines.append(generate_rule('CUSTOM_MATRIX', 'lite'))
else:
- rules_mk_lines.append('CUSTOM_MATRIX ?= yes')
+ rules_mk_lines.append(generate_rule('CUSTOM_MATRIX', 'yes'))
if converter:
- rules_mk_lines.append(f'CONVERT_TO ?= {converter}')
+ rules_mk_lines.append(generate_rule('CONVERT_TO', converter))
# Show the results
dump_lines(cli.args.output, rules_mk_lines)