diff --git a/lib/python/qmk/cli/generate/community_modules.py b/lib/python/qmk/cli/generate/community_modules.py index 28b3a75c9f..88d63396e7 100644 --- a/lib/python/qmk/cli/generate/community_modules.py +++ b/lib/python/qmk/cli/generate/community_modules.py @@ -18,10 +18,15 @@ class ModuleAPI(AttrDict): 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='pre_process_record', args='uint16_t keycode, keyrecord_t *record', call_params='keycode, record', ret_val='true'), ModuleAPI(ret_type='bool', name='process_record', args='uint16_t keycode, keyrecord_t *record', call_params='keycode, record', ret_val='true'), + ModuleAPI(ret_type='void', name='post_process_record', args='uint16_t keycode, keyrecord_t *record', call_params='keycode, record', ret_val=None), + ModuleAPI(ret_type='void', name='housekeeping_task', args='void', call_params='', ret_val=None), + ModuleAPI(ret_type='bool', name='shutdown', args='bool jump_to_bootloader', call_params='jump_to_bootloader', ret_val='true'), ] +MODULE_API_VERSION = '20250122' + @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") @@ -33,13 +38,31 @@ def generate_community_modules_h(cli): if cli.args.output and cli.args.output.name == '-': cli.args.output = None - lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '#pragma once', ''] + lines = [ + GPL2_HEADER_C_LIKE, + GENERATED_HEADER_C_LIKE, + '#pragma once', + '#include ', + '#include ', + '', + f'#define COMMUNITY_MODULES_API_VERSION {MODULE_API_VERSION}', + f'#define ASSERT_COMMUNITY_MODULES_API_VERSION(x) _Static_assert((x) <= COMMUNITY_MODULES_API_VERSION, "Modules API mismatch, module requires higher version of QMK with API >= {MODULE_API_VERSION}.")', + '', + 'typedef struct keyrecord_t keyrecord_t;', + ] keymap_json = parse_configurator_json(cli.args.filename) if keymap_json and 'modules' in keymap_json: for module in keymap_json['modules']: + lines.append('') + lines.append(f'// From module: {module}') 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) + lines.append('') + + lines.append(f'// Parent implementation') + for api in MODULE_API_LIST: + lines.append(f'{api.ret_type} {api.name}_modules({api.args});') + dump_lines(cli.args.output, lines, cli.args.quiet, remove_repeated_newlines=True) @cli.argument('-o', '--output', arg_only=True, type=qmk.path.normpath, help='File to write to') @@ -52,13 +75,14 @@ def generate_community_modules_c(cli): 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"'] + lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '', '#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']: + lines.append('') for api in MODULE_API_LIST: lines.append('') - lines.append(f'__attribute__((weak)) {api.ret_type} {api.name}_{module}({api.args}) {{ ') + 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('}') @@ -76,4 +100,4 @@ def generate_community_modules_c(cli): if api.ret_type == 'bool': lines.append(' ;') lines.append('}') - dump_lines(cli.args.output, lines, cli.args.quiet) + dump_lines(cli.args.output, lines, cli.args.quiet, remove_repeated_newlines=True) diff --git a/lib/python/qmk/cli/generate/rules_mk.py b/lib/python/qmk/cli/generate/rules_mk.py index 5b06c283d9..ea44f1e58a 100755 --- a/lib/python/qmk/cli/generate/rules_mk.py +++ b/lib/python/qmk/cli/generate/rules_mk.py @@ -101,10 +101,12 @@ def generate_rules_mk(cli): rules_mk_lines.append(generate_rule('CONVERT_TO', converter)) if user_keymap and 'modules' in user_keymap: + rules_mk_lines.append('') + rules_mk_lines.append('OPT_DEFS += -DCOMMUNITY_MODULES_ENABLED=TRUE') 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'OPT_DEFS += -DCOMMUNITY_MODULE_{module.upper()}=TRUE') rules_mk_lines.append(f'VPATH += modules/{module}') rules_mk_lines.append(f'include modules/{module}/rules.mk') diff --git a/lib/python/qmk/commands.py b/lib/python/qmk/commands.py index a05b3641b5..38e032765f 100644 --- a/lib/python/qmk/commands.py +++ b/lib/python/qmk/commands.py @@ -98,11 +98,14 @@ def in_virtualenv(): return active_prefix != sys.prefix -def dump_lines(output_file, lines, quiet=True): +def dump_lines(output_file, lines, quiet=True, remove_repeated_newlines=False): """Handle dumping to stdout or file Creates parent folders if required """ generated = '\n'.join(lines) + '\n' + if remove_repeated_newlines: + if '\n\n\n' in generated: + generated = generated.replace('\n\n\n', '\n\n') if output_file and output_file.name != '-': output_file.parent.mkdir(parents=True, exist_ok=True) if output_file.exists(): diff --git a/modules/hello_world/hello_world.c b/modules/hello_world/hello_world.c index 078d641ba5..4fe9bd052e 100644 --- a/modules/hello_world/hello_world.c +++ b/modules/hello_world/hello_world.c @@ -2,6 +2,8 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include QMK_KEYBOARD_H +ASSERT_COMMUNITY_MODULES_API_VERSION(20250122); + static uint32_t init_timestamp = 0; void keyboard_post_init_hello_world(void) { @@ -10,7 +12,7 @@ void keyboard_post_init_hello_world(void) { void housekeeping_task_hello_world(void) { static bool welcomed = false; - if(!welcomed && timer_elapsed32(init_timestamp) > 1000) { + if(!welcomed && timer_elapsed32(init_timestamp) > 10000) { welcomed = true; print("Hello, world! I'm a QMK based keyboard!\n"); } diff --git a/quantum/action.h b/quantum/action.h index d5b15c6f17..7596688f31 100644 --- a/quantum/action.h +++ b/quantum/action.h @@ -45,7 +45,7 @@ typedef struct { } tap_t; /* Key event container for recording */ -typedef struct { +typedef struct keyrecord_t { keyevent_t event; #ifndef NO_ACTION_TAPPING tap_t tap; diff --git a/quantum/keyboard.c b/quantum/keyboard.c index d7836cf36e..ad740de4b3 100644 --- a/quantum/keyboard.c +++ b/quantum/keyboard.c @@ -289,6 +289,21 @@ __attribute__((weak)) void keyboard_pre_init_kb(void) { keyboard_pre_init_user(); } +/** \brief keyboard_pre_init_modules + * + * FIXME: needs doc + */ +__attribute__((weak)) void keyboard_pre_init_modules(void) {} + +/** \brief keyboard_pre_init_quantum + * + * FIXME: needs doc + */ +void keyboard_pre_init_quantum(void) { + keyboard_pre_init_modules(); + keyboard_pre_init_kb(); +} + /** \brief keyboard_post_init_user * * FIXME: needs doc @@ -305,6 +320,23 @@ __attribute__((weak)) void keyboard_post_init_kb(void) { keyboard_post_init_user(); } +/** \brief keyboard_post_init_modules + * + * FIXME: needs doc + */ + +__attribute__((weak)) void keyboard_post_init_modules(void) {} + +/** \brief keyboard_post_init_quantum + * + * FIXME: needs doc + */ + +void keyboard_post_init_quantum(void) { + keyboard_post_init_modules(); + keyboard_post_init_kb(); +} + /** \brief matrix_can_read * * Allows overriding when matrix scanning operations should be executed. @@ -323,7 +355,7 @@ void keyboard_setup(void) { eeprom_driver_init(); #endif matrix_setup(); - keyboard_pre_init_kb(); + keyboard_pre_init_quantum(); } #ifndef SPLIT_KEYBOARD @@ -355,6 +387,13 @@ __attribute__((weak)) bool should_process_keypress(void) { return is_keyboard_master(); } +/** \brief housekeeping_task_modules + * + * Codegen will override this if community modules are enabled. + * This is specific to keyboard-level functionality. + */ +__attribute__((weak)) void housekeeping_task_modules(void) {} + /** \brief housekeeping_task_kb * * Override this function if you have a need to execute code for every keyboard main loop iteration. @@ -374,6 +413,7 @@ __attribute__((weak)) void housekeeping_task_user(void) {} * Invokes hooks for executing code after QMK is done after each loop iteration. */ void housekeeping_task(void) { + housekeeping_task_modules(); housekeeping_task_kb(); housekeeping_task_user(); } @@ -493,7 +533,7 @@ void keyboard_init(void) { debug_enable = true; #endif - keyboard_post_init_kb(); /* Always keep this last */ + keyboard_post_init_quantum(); /* Always keep this last */ } /** \brief key_event_task diff --git a/quantum/quantum.c b/quantum/quantum.c index d4ebd58e7f..66e1cef24d 100644 --- a/quantum/quantum.c +++ b/quantum/quantum.c @@ -162,6 +162,10 @@ __attribute__((weak)) void tap_code16(uint16_t code) { tap_code16_delay(code, code == KC_CAPS_LOCK ? TAP_HOLD_CAPS_DELAY : TAP_CODE_DELAY); } +__attribute__((weak)) bool pre_process_record_modules(uint16_t keycode, keyrecord_t *record) { + return true; +} + __attribute__((weak)) bool pre_process_record_kb(uint16_t keycode, keyrecord_t *record) { return pre_process_record_user(keycode, record); } @@ -174,6 +178,10 @@ __attribute__((weak)) bool process_action_kb(keyrecord_t *record) { return true; } +__attribute__((weak)) bool process_record_modules(uint16_t keycode, keyrecord_t *record) { + return true; +} + __attribute__((weak)) bool process_record_kb(uint16_t keycode, keyrecord_t *record) { return process_record_user(keycode, record); } @@ -182,12 +190,18 @@ __attribute__((weak)) bool process_record_user(uint16_t keycode, keyrecord_t *re return true; } +__attribute__((weak)) void post_process_record_modules(uint16_t keycode, keyrecord_t *record) {} + __attribute__((weak)) void post_process_record_kb(uint16_t keycode, keyrecord_t *record) { post_process_record_user(keycode, record); } __attribute__((weak)) void post_process_record_user(uint16_t keycode, keyrecord_t *record) {} +__attribute__((weak)) bool shutdown_modules(bool jump_to_bootloader) { + return true; +} + void shutdown_quantum(bool jump_to_bootloader) { clear_keyboard(); #if defined(MIDI_ENABLE) && defined(MIDI_BASIC) @@ -199,11 +213,13 @@ void shutdown_quantum(bool jump_to_bootloader) { # endif uint16_t timer_start = timer_read(); PLAY_SONG(goodbye_song); + shutdown_modules(jump_to_bootloader); shutdown_kb(jump_to_bootloader); while (timer_elapsed(timer_start) < 250) wait_ms(1); stop_all_notes(); #else + shutdown_modules(jump_to_bootloader); shutdown_kb(jump_to_bootloader); wait_ms(250); #endif @@ -258,7 +274,7 @@ uint16_t get_event_keycode(keyevent_t event, bool update_layer_cache) { /* Get keycode, and then process pre tapping functionality */ bool pre_process_record_quantum(keyrecord_t *record) { - return pre_process_record_kb(get_record_keycode(record, true), record) && + return pre_process_record_modules(get_record_keycode(record, true), record) && pre_process_record_kb(get_record_keycode(record, true), record) && #ifdef COMBO_ENABLE process_combo(get_record_keycode(record, true), record) && #endif @@ -268,6 +284,7 @@ bool pre_process_record_quantum(keyrecord_t *record) { /* Get keycode, and then call keyboard function */ void post_process_record_quantum(keyrecord_t *record) { uint16_t keycode = get_record_keycode(record, false); + post_process_record_modules(keycode, record); post_process_record_kb(keycode, record); } @@ -335,7 +352,7 @@ bool process_record_quantum(keyrecord_t *record) { #if defined(POINTING_DEVICE_ENABLE) && defined(POINTING_DEVICE_AUTO_MOUSE_ENABLE) process_auto_mouse(keycode, record) && #endif - process_record_kb(keycode, record) && + process_record_modules(keycode, record) && process_record_kb(keycode, record) && #if defined(SECURE_ENABLE) process_secure(keycode, record) && #endif diff --git a/quantum/quantum.h b/quantum/quantum.h index 9db88a54d4..856120334e 100644 --- a/quantum/quantum.h +++ b/quantum/quantum.h @@ -244,6 +244,10 @@ extern layer_state_t layer_state; # include "layer_lock.h" #endif +#ifdef COMMUNITY_MODULES_ENABLED +# include "community_modules.h" +#endif + void set_single_default_layer(uint8_t default_layer); void set_single_persistent_default_layer(uint8_t default_layer);