diff --git a/builddefs/build_keyboard.mk b/builddefs/build_keyboard.mk index 7d58c29462..e4661a7f3f 100644 --- a/builddefs/build_keyboard.mk +++ b/builddefs/build_keyboard.mk @@ -224,6 +224,20 @@ $(INTERMEDIATE_OUTPUT)/src/keymap.h: $(KEYMAP_JSON) generated-files: $(INTERMEDIATE_OUTPUT)/src/config.h $(INTERMEDIATE_OUTPUT)/src/keymap.c $(INTERMEDIATE_OUTPUT)/src/keymap.h +$(INTERMEDIATE_OUTPUT)/src/community_modules.h: $(KEYMAP_JSON) + @$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD) + $(eval CMD=$(QMK_BIN) generate-community-modules-h --quiet --output $(INTERMEDIATE_OUTPUT)/src/community_modules.h $(KEYMAP_JSON)) + @$(BUILD_CMD) + +$(INTERMEDIATE_OUTPUT)/src/community_modules.c: $(KEYMAP_JSON) + @$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD) + $(eval CMD=$(QMK_BIN) generate-community-modules-c --quiet --output $(INTERMEDIATE_OUTPUT)/src/community_modules.c $(KEYMAP_JSON)) + @$(BUILD_CMD) + +SRC += $(INTERMEDIATE_OUTPUT)/src/community_modules.c + +generated-files: $(INTERMEDIATE_OUTPUT)/src/community_modules.h $(INTERMEDIATE_OUTPUT)/src/community_modules.c + endif include $(BUILDDEFS_PATH)/converters.mk diff --git a/data/schemas/keymap.jsonschema b/data/schemas/keymap.jsonschema index e967e45c53..b92a536c2c 100644 --- a/data/schemas/keymap.jsonschema +++ b/data/schemas/keymap.jsonschema @@ -71,6 +71,12 @@ "config": {"$ref": "qmk.keyboard.v1"}, "notes": { "type": "string" + }, + "modules": { + "type": "array", + "items": { + "type": "string" + } } } } diff --git a/lib/python/qmk/cli/__init__.py b/lib/python/qmk/cli/__init__.py index 2d63dfb447..bfe15f8b8c 100644 --- a/lib/python/qmk/cli/__init__.py +++ b/lib/python/qmk/cli/__init__.py @@ -49,6 +49,7 @@ subcommands = [ 'qmk.cli.generate.api', 'qmk.cli.generate.autocorrect_data', 'qmk.cli.generate.compilation_database', + 'qmk.cli.generate.community_modules', 'qmk.cli.generate.config_h', 'qmk.cli.generate.develop_pr_list', 'qmk.cli.generate.dfu_header', diff --git a/lib/python/qmk/cli/generate/community_modules.py b/lib/python/qmk/cli/generate/community_modules.py new file mode 100644 index 0000000000..b9a75c50d4 --- /dev/null +++ b/lib/python/qmk/cli/generate/community_modules.py @@ -0,0 +1,75 @@ +from argcomplete.completers import FilesCompleter + +from milc import cli +from milc.attrdict import AttrDict + +import qmk.path +from qmk.commands import dump_lines, parse_configurator_json +from qmk.constants import GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE + +class ModuleAPI(AttrDict): + def __init__(self, **kwargs): + super().__init__() + for key, value in kwargs.items(): + self[key] = value + +MODULE_API_LIST = [ + ModuleAPI(ret_type='void', name='keyboard_pre_init', args='void', call_params='', ret_val=None), + ModuleAPI(ret_type='void', name='keyboard_post_init', args='void', call_params='', ret_val=None), + ModuleAPI(ret_type='void', name='housekeeping_task', args='void', call_params='', ret_val=None), + ModuleAPI(ret_type='bool', name='process_record', args='uint16_t keycode, keyrecord_t *record', call_params='keycode, record', ret_val='true'), +] + +@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('filename', type=qmk.path.FileType('r'), arg_only=True, completer=FilesCompleter('.json'), help='Configurator JSON file') +@cli.subcommand('Creates a community_modules.h from a keymap.json file.') +def generate_community_modules_h(cli): + """Creates a community_modules.h from a keymap.json file + """ + if cli.args.output and cli.args.output.name == '-': + cli.args.output = None + + lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '#pragma once', ''] + keymap_json = parse_configurator_json(cli.args.filename) + if keymap_json and 'modules' in keymap_json: + for module in keymap_json['modules']: + for api in MODULE_API_LIST: + lines.append(f'{api.ret_type} {api.name}_{module}({api.args});') + dump_lines(cli.args.output, lines, cli.args.quiet) + +@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('filename', type=qmk.path.FileType('r'), arg_only=True, completer=FilesCompleter('.json'), help='Configurator JSON file') +@cli.subcommand('Creates a community_modules.c from a keymap.json file.') +def generate_community_modules_c(cli): + """Creates a community_modules.c from a keymap.json file + """ + if cli.args.output and cli.args.output.name == '-': + cli.args.output = None + + lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '#include QMK_KEYBOARD_H', '#include "community_modules.h"'] + keymap_json = parse_configurator_json(cli.args.filename) + if keymap_json and 'modules' in keymap_json: + for module in keymap_json['modules']: + for api in MODULE_API_LIST: + lines.append('') + lines.append(f'__attribute__((weak)) {api.ret_type} {api.name}_{module}({api.args}) {{ ') + if api.ret_val: + lines.append(f' return {api.ret_val};') + lines.append('}') + + for api in MODULE_API_LIST: + lines.append('') + lines.append(f'{api.ret_type} {api.name}_modules({api.args}) {{') + if api.ret_type == 'bool': + lines.append(' return true') + for module in keymap_json['modules']: + if api.ret_type == 'bool': + lines.append(f' && {api.name}_{module}({api.call_params})') + else: + lines.append(f' {api.name}_{module}({api.call_params});') + if api.ret_type == 'bool': + lines.append(' ;') + lines.append('}') + dump_lines(cli.args.output, lines, cli.args.quiet) diff --git a/lib/python/qmk/cli/generate/rules_mk.py b/lib/python/qmk/cli/generate/rules_mk.py index 5291556109..5b06c283d9 100755 --- a/lib/python/qmk/cli/generate/rules_mk.py +++ b/lib/python/qmk/cli/generate/rules_mk.py @@ -55,6 +55,7 @@ def process_mapping_rule(kb_info_json, rules_key, info_dict): def generate_rules_mk(cli): """Generates a rules.mk file from info.json. """ + user_keymap = None converter = None # Determine our keyboard/keymap if cli.args.filename: @@ -99,6 +100,14 @@ def generate_rules_mk(cli): if converter: rules_mk_lines.append(generate_rule('CONVERT_TO', converter)) + if user_keymap and 'modules' in user_keymap: + for module in user_keymap['modules']: + rules_mk_lines.append('') + rules_mk_lines.append(f'COMMUNITY_MODULES += {module}') + rules_mk_lines.append(f'OPT_DEFS += -DMODULE_{module.upper()}=TRUE') + rules_mk_lines.append(f'VPATH += modules/{module}') + rules_mk_lines.append(f'include modules/{module}/rules.mk') + # Show the results dump_lines(cli.args.output, rules_mk_lines) diff --git a/lib/python/qmk/keymap.py b/lib/python/qmk/keymap.py index 9dd043c4a8..8e36461722 100644 --- a/lib/python/qmk/keymap.py +++ b/lib/python/qmk/keymap.py @@ -334,33 +334,6 @@ def write_json(keyboard, keymap, layout, layers, macros=None): return write_file(keymap_file, keymap_content) -def write(keymap_json): - """Generate the `keymap.c` and write it to disk. - - Returns the filename written to. - - `keymap_json` should be a dict with the following keys: - keyboard - The name of the keyboard - - keymap - The name of the keymap - - layout - The LAYOUT macro this keymap uses. - - layers - An array of arrays describing the keymap. Each item in the inner array should be a string that is a valid QMK keycode. - - macros - A list of macros for this keymap. - """ - keymap_content = generate_c(keymap_json) - keymap_file = qmk.path.keymaps(keymap_json['keyboard'])[0] / keymap_json['keymap'] / 'keymap.c' - - return write_file(keymap_file, keymap_content) - - def locate_keymap(keyboard, keymap, force_layout=None): """Returns the path to a keymap for a specific keyboard. """ diff --git a/modules/hello_world/hello_world.c b/modules/hello_world/hello_world.c new file mode 100644 index 0000000000..078d641ba5 --- /dev/null +++ b/modules/hello_world/hello_world.c @@ -0,0 +1,17 @@ +// Copyright 2025 Nick Brassel (@tzarc) +// SPDX-License-Identifier: GPL-2.0-or-later +#include QMK_KEYBOARD_H + +static uint32_t init_timestamp = 0; + +void keyboard_post_init_hello_world(void) { + init_timestamp = timer_read32(); +} + +void housekeeping_task_hello_world(void) { + static bool welcomed = false; + if(!welcomed && timer_elapsed32(init_timestamp) > 1000) { + welcomed = true; + print("Hello, world! I'm a QMK based keyboard!\n"); + } +} diff --git a/modules/hello_world/rules.mk b/modules/hello_world/rules.mk new file mode 100644 index 0000000000..0326df8935 --- /dev/null +++ b/modules/hello_world/rules.mk @@ -0,0 +1 @@ +SRC += hello_world.c