Compare commits

...
Sign in to create a new pull request.
Author SHA1 Message Date
Nick Brassel
519bd5ac45
Keycode alias support. 2025-02-07 23:51:53 +11:00
Nick Brassel
4758f6c695
Header ordering ftw. 2025-02-07 23:38:24 +11:00
Nick Brassel
b4981aefe4
Ordering so module introspection types can be used in keymap.c. 2025-02-07 23:33:55 +11:00
Nick Brassel
dd2ea4aae0
Add introspection.h support and example. 2025-02-07 23:30:48 +11:00
Nick Brassel
6f4db948e3
Add introspection.c example. 2025-02-07 23:20:02 +11:00
Nick Brassel
3a5c075cf7
Add support for module-level introspection.c to be automatically injected. 2025-02-07 23:07:57 +11:00
Nick Brassel
e86d8d248f
Preprocessor consistency. 2025-02-06 16:49:33 +11:00
Nick Brassel
368972afa6
Merge issue. 2025-02-06 10:06:27 +11:00
Nick Brassel
ad90132710
Subdirs and relative pathing. 2025-02-05 21:26:59 +11:00
Nick Brassel
520b5bf980
Merge remote-tracking branch 'upstream/develop' into community-modules 2025-02-05 20:40:33 +11:00
Nick Brassel
d7e374be12
Allow for modules in subdirectories. 2025-01-29 23:57:43 +11:00
Nick Brassel
37e3ee051c
Add onekey. 2025-01-24 23:06:35 +11:00
Nick Brassel
8b47562c5d
Userspace modules. 2025-01-24 22:57:54 +11:00
Nick Brassel
d3300daaa7
Automatic addition of community_module label. 2025-01-24 21:49:17 +11:00
Nick Brassel
fa53413a5d
Ensure modules are subject to formatting rules. 2025-01-24 21:47:45 +11:00
Nick Brassel
c6454b5585
Add support for features object. 2025-01-24 21:42:33 +11:00
Nick Brassel
1c4c6ae280
Appease the gods of formatting. 2025-01-24 09:26:14 +11:00
Nick Brassel
1354424fd7
First stab at keycodes. 2025-01-24 09:22:49 +11:00
Nick Brassel
54673745a6
nitpicks 2025-01-24 01:17:16 +11:00
Nick Brassel
c9ee24ba86
keyboard and keymap modules 2025-01-24 01:11:45 +11:00
Nick Brassel
d4733cc574
user/kb module hooks. 2025-01-23 23:21:07 +11:00
Nick Brassel
fe3eac28ba
Preprocessor guards, OS detection hooks. 2025-01-22 17:00:06 +11:00
Nick Brassel
5a70aecb6b
Suspend callbacks. 2025-01-22 14:19:56 +11:00
Nick Brassel
8f79ed1f23
More APIs added, versioning. 2025-01-22 14:13:20 +11:00
Nick Brassel
11b9ff0c4f
Appease the gods of formatting. 2025-01-22 00:59:50 +11:00
Nick Brassel
e3fe9fac60
First stab at codegen. 2025-01-22 00:50:55 +11:00
31 changed files with 746 additions and 82 deletions

4
.github/labeler.yml vendored
View file

@ -54,3 +54,7 @@ dd:
- data/constants/**
- data/mappings/**
- data/schemas/**
community_module:
- changed-files:
- any-glob-to-any-file:
- modules/**

View file

@ -10,6 +10,7 @@ on:
- 'lib/arm_atsam/**'
- 'lib/lib8tion/**'
- 'lib/python/**'
- 'modules/**'
- 'platforms/**'
- 'quantum/**'
- 'tests/**'

View file

@ -112,6 +112,39 @@ endif
ifneq ("$(wildcard $(KEYBOARD_PATH_1)/rules.mk)","")
include $(KEYBOARD_PATH_1)/rules.mk
endif
# Create dependencies on DD keyboard config - structure validated elsewhere
DD_CONFIG_FILES :=
ifneq ("$(wildcard $(KEYBOARD_PATH_1)/info.json)","")
DD_CONFIG_FILES += $(KEYBOARD_PATH_1)/info.json
endif
ifneq ("$(wildcard $(KEYBOARD_PATH_2)/info.json)","")
DD_CONFIG_FILES += $(KEYBOARD_PATH_2)/info.json
endif
ifneq ("$(wildcard $(KEYBOARD_PATH_3)/info.json)","")
DD_CONFIG_FILES += $(KEYBOARD_PATH_3)/info.json
endif
ifneq ("$(wildcard $(KEYBOARD_PATH_4)/info.json)","")
DD_CONFIG_FILES += $(KEYBOARD_PATH_4)/info.json
endif
ifneq ("$(wildcard $(KEYBOARD_PATH_5)/info.json)","")
DD_CONFIG_FILES += $(KEYBOARD_PATH_5)/info.json
endif
ifneq ("$(wildcard $(KEYBOARD_PATH_1)/keyboard.json)","")
DD_CONFIG_FILES += $(KEYBOARD_PATH_1)/keyboard.json
endif
ifneq ("$(wildcard $(KEYBOARD_PATH_2)/keyboard.json)","")
DD_CONFIG_FILES += $(KEYBOARD_PATH_2)/keyboard.json
endif
ifneq ("$(wildcard $(KEYBOARD_PATH_3)/keyboard.json)","")
DD_CONFIG_FILES += $(KEYBOARD_PATH_3)/keyboard.json
endif
ifneq ("$(wildcard $(KEYBOARD_PATH_4)/keyboard.json)","")
DD_CONFIG_FILES += $(KEYBOARD_PATH_4)/keyboard.json
endif
ifneq ("$(wildcard $(KEYBOARD_PATH_5)/keyboard.json)","")
DD_CONFIG_FILES += $(KEYBOARD_PATH_5)/keyboard.json
endif
MAIN_KEYMAP_PATH_1 := $(KEYBOARD_PATH_1)/keymaps/$(KEYMAP)
MAIN_KEYMAP_PATH_2 := $(KEYBOARD_PATH_2)/keymaps/$(KEYMAP)
@ -207,17 +240,17 @@ ifneq ("$(wildcard $(KEYMAP_JSON))", "")
include $(INFO_RULES_MK)
# Add rules to generate the keymap files - indentation here is important
$(INTERMEDIATE_OUTPUT)/src/keymap.c: $(KEYMAP_JSON)
$(INTERMEDIATE_OUTPUT)/src/keymap.c: $(KEYMAP_JSON) $(DD_CONFIG_FILES)
@$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD)
$(eval CMD=$(QMK_BIN) json2c --quiet --output $(KEYMAP_C) $(KEYMAP_JSON))
@$(BUILD_CMD)
$(INTERMEDIATE_OUTPUT)/src/config.h: $(KEYMAP_JSON)
$(INTERMEDIATE_OUTPUT)/src/config.h: $(KEYMAP_JSON) $(DD_CONFIG_FILES)
@$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD)
$(eval CMD=$(QMK_BIN) generate-config-h --quiet --output $(KEYMAP_H) $(KEYMAP_JSON))
@$(BUILD_CMD)
$(INTERMEDIATE_OUTPUT)/src/keymap.h: $(KEYMAP_JSON)
$(INTERMEDIATE_OUTPUT)/src/keymap.h: $(KEYMAP_JSON) $(DD_CONFIG_FILES)
@$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD)
$(eval CMD=$(QMK_BIN) generate-keymap-h --quiet --output $(INTERMEDIATE_OUTPUT)/src/keymap.h $(KEYMAP_JSON))
@$(BUILD_CMD)
@ -226,6 +259,32 @@ generated-files: $(INTERMEDIATE_OUTPUT)/src/config.h $(INTERMEDIATE_OUTPUT)/src/
endif
# Community modules
$(INTERMEDIATE_OUTPUT)/src/community_modules.h: $(KEYMAP_JSON) $(DD_CONFIG_FILES)
@$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD)
$(eval CMD=$(QMK_BIN) generate-community-modules-h -kb $(KEYBOARD) --quiet --output $(INTERMEDIATE_OUTPUT)/src/community_modules.h $(KEYMAP_JSON))
@$(BUILD_CMD)
$(INTERMEDIATE_OUTPUT)/src/community_modules.c: $(KEYMAP_JSON) $(DD_CONFIG_FILES)
@$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD)
$(eval CMD=$(QMK_BIN) generate-community-modules-c -kb $(KEYBOARD) --quiet --output $(INTERMEDIATE_OUTPUT)/src/community_modules.c $(KEYMAP_JSON))
@$(BUILD_CMD)
$(INTERMEDIATE_OUTPUT)/src/community_modules_introspection.c: $(KEYMAP_JSON) $(DD_CONFIG_FILES)
@$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD)
$(eval CMD=$(QMK_BIN) generate-community-modules-introspection-c -kb $(KEYBOARD) --quiet --output $(INTERMEDIATE_OUTPUT)/src/community_modules_introspection.c $(KEYMAP_JSON))
@$(BUILD_CMD)
$(INTERMEDIATE_OUTPUT)/src/community_modules_introspection.h: $(KEYMAP_JSON) $(DD_CONFIG_FILES)
@$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD)
$(eval CMD=$(QMK_BIN) generate-community-modules-introspection-h -kb $(KEYBOARD) --quiet --output $(INTERMEDIATE_OUTPUT)/src/community_modules_introspection.h $(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 $(INTERMEDIATE_OUTPUT)/src/community_modules_introspection.c $(INTERMEDIATE_OUTPUT)/src/community_modules_introspection.h
include $(BUILDDEFS_PATH)/converters.mk
# Generate the board's version.h file.
@ -348,40 +407,6 @@ ifneq ("$(wildcard $(KEYBOARD_PATH_5)/post_config.h)","")
POST_CONFIG_H += $(KEYBOARD_PATH_5)/post_config.h
endif
# Create dependencies on DD keyboard config - structure validated elsewhere
DD_CONFIG_FILES :=
ifneq ("$(wildcard $(KEYBOARD_PATH_1)/info.json)","")
DD_CONFIG_FILES += $(KEYBOARD_PATH_1)/info.json
endif
ifneq ("$(wildcard $(KEYBOARD_PATH_2)/info.json)","")
DD_CONFIG_FILES += $(KEYBOARD_PATH_2)/info.json
endif
ifneq ("$(wildcard $(KEYBOARD_PATH_3)/info.json)","")
DD_CONFIG_FILES += $(KEYBOARD_PATH_3)/info.json
endif
ifneq ("$(wildcard $(KEYBOARD_PATH_4)/info.json)","")
DD_CONFIG_FILES += $(KEYBOARD_PATH_4)/info.json
endif
ifneq ("$(wildcard $(KEYBOARD_PATH_5)/info.json)","")
DD_CONFIG_FILES += $(KEYBOARD_PATH_5)/info.json
endif
ifneq ("$(wildcard $(KEYBOARD_PATH_1)/keyboard.json)","")
DD_CONFIG_FILES += $(KEYBOARD_PATH_1)/keyboard.json
endif
ifneq ("$(wildcard $(KEYBOARD_PATH_2)/keyboard.json)","")
DD_CONFIG_FILES += $(KEYBOARD_PATH_2)/keyboard.json
endif
ifneq ("$(wildcard $(KEYBOARD_PATH_3)/keyboard.json)","")
DD_CONFIG_FILES += $(KEYBOARD_PATH_3)/keyboard.json
endif
ifneq ("$(wildcard $(KEYBOARD_PATH_4)/keyboard.json)","")
DD_CONFIG_FILES += $(KEYBOARD_PATH_4)/keyboard.json
endif
ifneq ("$(wildcard $(KEYBOARD_PATH_5)/keyboard.json)","")
DD_CONFIG_FILES += $(KEYBOARD_PATH_5)/keyboard.json
endif
CONFIG_H += $(INTERMEDIATE_OUTPUT)/src/info_config.h
KEYBOARD_SRC += $(INTERMEDIATE_OUTPUT)/src/default_keyboard.c

View file

@ -0,0 +1,7 @@
{
"ranges": {
"0x77C0/0x003F": {
"define": "QK_COMMUNITY_MODULE"
}
}
}

View file

@ -0,0 +1,17 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema#",
"$id": "qmk.community_module.v1",
"title": "Community Module Information",
"type": "object",
"required": ["module_name", "maintainer"]
"properties": {
"module_name": {"$ref": "qmk.definitions.v1#/text_identifier"},
"maintainer": {"$ref": "qmk.definitions.v1#/text_identifier"},
"url": {
"type": "string",
"format": "uri"
},
"keycodes": {"$ref": "qmk.definitions.v1#/keycode_decl_array"},
"features": {"$ref": "qmk.keyboard.v1#/definitions/features_config"},
}
}

View file

@ -31,6 +31,11 @@
"pins": {"$ref": "qmk.definitions.v1#/mcu_pin_array"}
}
}
"features_config": {
"$ref": "qmk.definitions.v1#/boolean_array",
"propertyNames": {"$ref": "qmk.definitions.v1#/snake_case"},
"not": {"required": ["lto"]}
},
},
"type": "object",
"not": {"required": ["vendorId", "productId"]}, // reject via keys...
@ -328,11 +333,7 @@
"enabled": {"type": "boolean"}
}
},
"features": {
"$ref": "qmk.definitions.v1#/boolean_array",
"propertyNames": {"$ref": "qmk.definitions.v1#/snake_case"},
"not": {"required": ["lto"]}
},
"features": { "$ref": "#/definitions/features_config" },
"indicators": {
"type": "object",
"properties": {
@ -467,6 +468,12 @@
"rows": {"$ref": "qmk.definitions.v1#/mcu_pin_array"}
}
},
"modules": {
"type": "array",
"items": {
"type": "string"
}
},
"mouse_key": {
"type": "object",
"properties": {

View file

@ -71,6 +71,12 @@
"config": {"$ref": "qmk.keyboard.v1"},
"notes": {
"type": "string"
},
"modules": {
"type": "array",
"items": {
"type": "string"
}
}
}
}

View file

@ -0,0 +1,7 @@
// Copyright 2025 Nick Brassel (@tzarc)
// SPDX-License-Identifier: GPL-2.0-or-later
#include QMK_KEYBOARD_H
const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
LAYOUT_ortho_1x1(CM_HELLO)
};

View file

@ -0,0 +1,3 @@
{
"modules": ["hello_world"]
}

View file

@ -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',

View file

@ -10,7 +10,7 @@ from qmk.path import normpath
from qmk.c_parse import c_source_files
c_file_suffixes = ('c', 'h', 'cpp', 'hpp')
core_dirs = ('drivers', 'quantum', 'tests', 'tmk_core', 'platforms')
core_dirs = ('drivers', 'quantum', 'tests', 'tmk_core', 'platforms', 'modules')
ignored = ('tmk_core/protocol/usb_hid', 'platforms/chibios/boards')

View file

@ -9,7 +9,7 @@ from milc import cli
from qmk.info import info_json
from qmk.json_schema import json_load, validate
from qmk.json_encoders import InfoJSONEncoder, KeymapJSONEncoder, UserspaceJSONEncoder
from qmk.json_encoders import InfoJSONEncoder, KeymapJSONEncoder, UserspaceJSONEncoder, CommunityModuleJSONEncoder
from qmk.path import normpath
@ -30,6 +30,13 @@ def _detect_json_format(file, json_data):
except ValidationError:
pass
if json_encoder is None:
try:
validate(json_data, 'qmk.community_module.v1')
json_encoder = CommunityModuleJSONEncoder
except ValidationError:
pass
if json_encoder is None:
try:
validate(json_data, 'qmk.keyboard.v1')
@ -54,6 +61,8 @@ def _get_json_encoder(file, json_data):
json_encoder = KeymapJSONEncoder
elif cli.args.format == 'userspace':
json_encoder = UserspaceJSONEncoder
elif cli.args.format == 'community_module':
json_encoder = CommunityModuleJSONEncoder
else:
# This should be impossible
cli.log.error('Unknown format: %s', cli.args.format)
@ -61,7 +70,7 @@ def _get_json_encoder(file, json_data):
@cli.argument('json_file', arg_only=True, type=normpath, help='JSON file to format')
@cli.argument('-f', '--format', choices=['auto', 'keyboard', 'keymap', 'userspace'], default='auto', arg_only=True, help='JSON formatter to use (Default: autodetect)')
@cli.argument('-f', '--format', choices=['auto', 'keyboard', 'keymap', 'userspace', 'community_module'], default='auto', arg_only=True, help='JSON formatter to use (Default: autodetect)')
@cli.argument('-i', '--inplace', action='store_true', arg_only=True, help='If set, will operate in-place on the input file')
@cli.argument('-p', '--print', action='store_true', arg_only=True, help='If set, will print the formatted json to stdout ')
@cli.subcommand('Generate an info.json file for a keyboard.', hidden=False if cli.config.user.developer else True)

View file

@ -0,0 +1,258 @@
import contextlib
from argcomplete.completers import FilesCompleter
from pathlib import Path
from milc import cli
import qmk.path
from qmk.info import get_modules
from qmk.keyboard import keyboard_completer, keyboard_folder
from qmk.commands import dump_lines
from qmk.constants import GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE
from qmk.community_modules import MODULE_API_LIST, MODULE_API_VERSION, load_module_jsons, find_module_path
@contextlib.contextmanager
def _render_api_guard(lines, api):
if api.guard:
lines.append(f'#if {api.guard}')
yield
if api.guard:
lines.append(f'#endif // {api.guard}')
def _render_api_header(api):
lines = []
if api.header:
lines.append('')
with _render_api_guard(lines, api):
lines.append(f'#include <{api.header}>')
return lines
def _render_keycodes(module_jsons):
lines = []
lines.append('')
lines.append('enum {')
first = True
for module_json in module_jsons:
module_name = Path(module_json['module']).name
lines.append(f' // From module: {module_name}')
keycodes = module_json.get('keycodes', [])
if len(keycodes) > 0:
for keycode in keycodes:
key = keycode.get('key', None)
if first:
lines.append(f' {key} = QK_COMMUNITY_MODULE,')
first = False
else:
lines.append(f' {key},')
for alias in keycode.get('aliases', []):
lines.append(f' {alias} = {key},')
lines.append('')
lines.append(' LAST_COMMUNITY_MODULE_KEY')
lines.append('};')
lines.append('_Static_assert((int)LAST_COMMUNITY_MODULE_KEY <= (int)(QK_COMMUNITY_MODULE_MAX+1), "Too many community module keycodes");')
return lines
def _render_api_declarations(api, module, user_kb=True):
lines = []
lines.append('')
with _render_api_guard(lines, api):
if user_kb:
lines.append(f'{api.ret_type} {api.name}_{module}_user({api.args});')
lines.append(f'{api.ret_type} {api.name}_{module}_kb({api.args});')
lines.append(f'{api.ret_type} {api.name}_{module}({api.args});')
return lines
def _render_api_implementations(api, module):
module_name = Path(module).name
lines = []
lines.append('')
with _render_api_guard(lines, api):
# _user
lines.append(f'__attribute__((weak)) {api.ret_type} {api.name}_{module_name}_user({api.args}) {{')
if api.ret_type == 'bool':
lines.append(' return true;')
else:
pass
lines.append('}')
lines.append('')
# _kb
lines.append(f'__attribute__((weak)) {api.ret_type} {api.name}_{module_name}_kb({api.args}) {{')
if api.ret_type == 'bool':
lines.append(f' if(!{api.name}_{module_name}_user({api.call_params})) {{ return false; }}')
lines.append(' return true;')
else:
lines.append(f' {api.name}_{module_name}_user({api.call_params});')
lines.append('}')
lines.append('')
# module (non-suffixed)
lines.append(f'__attribute__((weak)) {api.ret_type} {api.name}_{module_name}({api.args}) {{')
if api.ret_type == 'bool':
lines.append(f' if(!{api.name}_{module_name}_kb({api.call_params})) {{ return false; }}')
lines.append(' return true;')
else:
lines.append(f' {api.name}_{module_name}_kb({api.call_params});')
lines.append('}')
return lines
def _render_core_implementation(api, modules):
lines = []
lines.append('')
with _render_api_guard(lines, api):
lines.append(f'{api.ret_type} {api.name}_modules({api.args}) {{')
if api.ret_type == 'bool':
lines.append(' return true')
for module in modules:
module_name = Path(module).name
if api.ret_type == 'bool':
lines.append(f' && {api.name}_{module_name}({api.call_params})')
else:
lines.append(f' {api.name}_{module_name}({api.call_params});')
if api.ret_type == 'bool':
lines.append(' ;')
lines.append('}')
return lines
@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, help='Keyboard to generate community_modules.h for.')
@cli.argument('filename', nargs='?', 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',
'#include <stdint.h>',
'#include <stdbool.h>',
'#include <keycodes.h>',
'',
f'#define COMMUNITY_MODULES_API_VERSION {MODULE_API_VERSION}',
f'#define ASSERT_COMMUNITY_MODULES_MIN_API_VERSION(x) _Static_assert((x) <= COMMUNITY_MODULES_API_VERSION, "Community module requires higher version of QMK modules API -- needs: " #x ", current: {MODULE_API_VERSION}.")',
'',
'typedef struct keyrecord_t keyrecord_t; // forward declaration so we don\'t need to include quantum.h',
'',
]
modules = get_modules(cli.args.keyboard, cli.args.filename)
module_jsons = load_module_jsons(modules)
if len(modules) > 0:
lines.extend(_render_keycodes(module_jsons))
for api in MODULE_API_LIST:
lines.extend(_render_api_header(api))
for module in modules:
lines.append('')
lines.append(f'// From module: {module}')
for api in MODULE_API_LIST:
lines.extend(_render_api_declarations(api, Path(module).name))
lines.append('')
lines.append('// Core wrapper')
for api in MODULE_API_LIST:
lines.extend(_render_api_declarations(api, 'modules', user_kb=False))
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')
@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, help='Keyboard to generate community_modules.c for.')
@cli.argument('filename', nargs='?', 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 "community_modules.h"',
]
modules = get_modules(cli.args.keyboard, cli.args.filename)
if len(modules) > 0:
for module in modules:
for api in MODULE_API_LIST:
lines.extend(_render_api_implementations(api, Path(module).name))
for api in MODULE_API_LIST:
lines.extend(_render_core_implementation(api, modules))
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')
@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, help='Keyboard to generate community_modules.c for.')
@cli.argument('filename', nargs='?', type=qmk.path.FileType('r'), arg_only=True, completer=FilesCompleter('.json'), help='Configurator JSON file')
@cli.subcommand('Creates a community_modules_introspection.h from a keymap.json file.')
def generate_community_modules_introspection_h(cli):
"""Creates a community_modules_introspection.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,
'',
]
modules = get_modules(cli.args.keyboard, cli.args.filename)
if len(modules) > 0:
for module in modules:
module_path = find_module_path(module)
lines.append(f'#if __has_include("{module_path}/introspection.h")')
lines.append(f'#include "{module_path}/introspection.h"')
lines.append(f'#endif // __has_include("{module_path}/introspection.h")')
lines.append('')
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')
@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, help='Keyboard to generate community_modules.c for.')
@cli.argument('filename', nargs='?', type=qmk.path.FileType('r'), arg_only=True, completer=FilesCompleter('.json'), help='Configurator JSON file')
@cli.subcommand('Creates a community_modules_introspection.c from a keymap.json file.')
def generate_community_modules_introspection_c(cli):
"""Creates a community_modules_introspection.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,
'',
]
modules = get_modules(cli.args.keyboard, cli.args.filename)
if len(modules) > 0:
for module in modules:
module_path = find_module_path(module)
lines.append(f'#if __has_include("{module_path}/introspection.c")')
lines.append(f'#include "{module_path}/introspection.c"')
lines.append(f'#endif // __has_include("{module_path}/introspection.c")')
lines.append('')
dump_lines(cli.args.output, lines, cli.args.quiet, remove_repeated_newlines=True)

View file

@ -6,12 +6,13 @@ from dotty_dict import dotty
from argcomplete.completers import FilesCompleter
from milc import cli
from qmk.info import info_json
from qmk.info import info_json, get_modules
from qmk.json_schema import json_load
from qmk.keyboard import keyboard_completer, keyboard_folder
from qmk.commands import dump_lines, parse_configurator_json
from qmk.path import normpath, FileType
from qmk.constants import GPL2_HEADER_SH_LIKE, GENERATED_HEADER_SH_LIKE
from qmk.community_modules import find_module_path, load_module_jsons
def generate_rule(rules_key, rules_value):
@ -46,6 +47,41 @@ def process_mapping_rule(kb_info_json, rules_key, info_dict):
return generate_rule(rules_key, rules_value)
def generate_features_rules(features_dict):
lines = []
for feature, enabled in features_dict.items():
feature = feature.upper()
enabled = 'yes' if enabled else 'no'
lines.append(generate_rule(f'{feature}_ENABLE', enabled))
return lines
def generate_modules_rules(keyboard, filename):
lines = []
modules = get_modules(keyboard, filename)
if len(modules) > 0:
lines.append('')
lines.append('OPT_DEFS += -DCOMMUNITY_MODULES_ENABLE=TRUE')
for module in modules:
module_path = find_module_path(module)
if not module_path:
raise FileNotFoundError(f"Module '{module}' not found.")
lines.append('')
lines.append(f'COMMUNITY_MODULES += {module_path.name}') # use module_path here instead of module as it may be a subdirectory
lines.append(f'OPT_DEFS += -DCOMMUNITY_MODULE_{module_path.name.upper()}_ENABLE=TRUE')
lines.append(f'VPATH += {module_path}')
lines.append(f'SRC += $(wildcard {module_path}/{module_path.name}.c)')
lines.append(f'-include {module_path}/rules.mk')
module_jsons = load_module_jsons(modules)
for module_json in module_jsons:
if 'features' in module_json:
lines.append('')
lines.append(f'# Module: {module_json["module_name"]}')
lines.extend(generate_features_rules(module_json['features']))
return lines
@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.')
@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")
@ -80,10 +116,7 @@ def generate_rules_mk(cli):
# Iterate through features to enable/disable them
if 'features' in kb_info_json:
for feature, enabled in kb_info_json['features'].items():
feature = feature.upper()
enabled = 'yes' if enabled else 'no'
rules_mk_lines.append(generate_rule(f'{feature}_ENABLE', enabled))
rules_mk_lines.extend(generate_features_rules(kb_info_json['features']))
# Set SPLIT_TRANSPORT, if needed
if kb_info_json.get('split', {}).get('transport', {}).get('protocol') == 'custom':
@ -99,6 +132,8 @@ def generate_rules_mk(cli):
if converter:
rules_mk_lines.append(generate_rule('CONVERT_TO', converter))
rules_mk_lines.extend(generate_modules_rules(cli.args.keyboard, cli.args.filename))
# Show the results
dump_lines(cli.args.output, rules_mk_lines)

View file

@ -52,6 +52,11 @@ def show_keymap(kb_info_json, title_caps=True):
if keymap_path and keymap_path.suffix == '.json':
keymap_data = json.load(keymap_path.open(encoding='utf-8'))
# cater for layout-less keymap.json
if 'layout' not in keymap_data:
return
layout_name = keymap_data['layout']
layout_name = kb_info_json.get('layout_aliases', {}).get(layout_name, layout_name) # Resolve alias names

View file

@ -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:
while '\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():

View file

@ -0,0 +1,93 @@
import os
from pathlib import Path
from milc.attrdict import AttrDict
from qmk.json_schema import json_load, validate
from qmk.util import truthy
from qmk.constants import QMK_FIRMWARE, QMK_USERSPACE, HAS_QMK_USERSPACE
from qmk.path import under_qmk_firmware, under_qmk_userspace
COMMUNITY_MODULE_JSON_FILENAME = 'qmk_module.json'
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='', guard=None, header=None),
ModuleAPI(ret_type='void', name='keyboard_post_init', args='void', call_params='', guard=None, header=None),
ModuleAPI(ret_type='bool', name='pre_process_record', args='uint16_t keycode, keyrecord_t *record', call_params='keycode, record', guard=None, header=None),
ModuleAPI(ret_type='bool', name='process_record', args='uint16_t keycode, keyrecord_t *record', call_params='keycode, record', guard=None, header=None),
ModuleAPI(ret_type='void', name='post_process_record', args='uint16_t keycode, keyrecord_t *record', call_params='keycode, record', guard=None, header=None),
ModuleAPI(ret_type='void', name='housekeeping_task', args='void', call_params='', guard=None, header=None),
ModuleAPI(ret_type='void', name='suspend_power_down', args='void', call_params='', guard=None, header=None),
ModuleAPI(ret_type='void', name='suspend_wakeup_init', args='void', call_params='', guard=None, header=None),
ModuleAPI(ret_type='bool', name='shutdown', args='bool jump_to_bootloader', call_params='jump_to_bootloader', guard=None, header=None),
ModuleAPI(ret_type='bool', name='process_detected_host_os', args='os_variant_t os', call_params='os', guard="defined(OS_DETECTION_ENABLE)", header="os_detection.h"),
]
MODULE_API_VERSION = '20250122'
def find_available_module_paths():
"""Find all available modules.
"""
search_dirs = []
if HAS_QMK_USERSPACE:
search_dirs.append(QMK_USERSPACE / 'modules')
search_dirs.append(QMK_FIRMWARE / 'modules')
modules = []
for search_dir in search_dirs:
for module_json_path in search_dir.rglob(COMMUNITY_MODULE_JSON_FILENAME):
modules.append(module_json_path.parent)
return modules
def find_module_path(module):
"""Find a module by name.
"""
for module_path in find_available_module_paths():
# Ensure the module directory is under QMK Firmware or QMK Userspace
relative_path = under_qmk_firmware(module_path)
if not relative_path:
relative_path = under_qmk_userspace(module_path)
if not relative_path:
continue
lhs = str(relative_path.as_posix())[len('modules/'):]
rhs = str(Path(module).as_posix())
if relative_path and lhs == rhs:
return module_path
return None
def load_module_json(module):
"""Load a module JSON file.
"""
module_path = find_module_path(module)
if not module_path:
raise FileNotFoundError(f'Module not found: {module}')
module_json = json_load(module_path / COMMUNITY_MODULE_JSON_FILENAME)
if not truthy(os.environ.get('SKIP_SCHEMA_VALIDATION'), False):
validate(module_json, 'qmk.community_module.v1')
module_json['module'] = module
module_json['module_path'] = module_path
return module_json
def load_module_jsons(modules):
"""Load the module JSON files, matching the specified order.
"""
return list(map(load_module_json, modules))

View file

@ -1059,3 +1059,30 @@ def keymap_json(keyboard, keymap, force_layout=None):
_extract_config_h(kb_info_json, parse_config_h_file(keymap_config))
return kb_info_json
def get_modules(keyboard, keymap_filename):
"""Get the modules for a keyboard/keymap.
"""
modules = []
if keymap_filename:
keymap_json = parse_configurator_json(keymap_filename)
if keymap_json:
kb = keymap_json.get('keyboard', None)
if not kb:
kb = keyboard
if kb:
kb_info_json = info_json(kb)
if kb_info_json:
modules.extend(kb_info_json.get('modules', []))
modules.extend(keymap_json.get('modules', []))
elif keyboard:
kb_info_json = info_json(keyboard)
modules.extend(kb_info_json.get('modules', []))
return list(dict.fromkeys(modules)) # remove dupes

View file

@ -235,3 +235,31 @@ class UserspaceJSONEncoder(QMKJSONEncoder):
return '01build_targets'
return key
class CommunityModuleJSONEncoder(QMKJSONEncoder):
"""Custom encoder to make qmk_module.json's a little nicer to work with.
"""
def sort_dict(self, item):
"""Sorts the hashes in a nice way.
"""
key = item[0]
if self.indentation_level == 1:
if key == 'module_name':
return '00module_name'
if key == 'maintainer':
return '01maintainer'
if key == 'url':
return '02url'
if key == 'features':
return '03features'
if key == 'keycodes':
return '04keycodes'
elif self.indentation_level == 3: # keycodes
if key == 'key':
return '00key'
if key == 'aliases':
return '01aliases'
return key

View file

@ -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.
"""

View file

@ -0,0 +1,33 @@
// Copyright 2025 Nick Brassel (@tzarc)
// SPDX-License-Identifier: GPL-2.0-or-later
#include QMK_KEYBOARD_H
#include "introspection.h"
ASSERT_COMMUNITY_MODULES_MIN_API_VERSION(20250122);
uint32_t delayed_hello_world(uint32_t trigger_time, void *cb_arg) {
printf("Hello, world! I'm a QMK based keyboard! The keymap array size is %d bytes.\n", (int)hello_world_introspection().total_size);
return 0;
}
void keyboard_post_init_hello_world(void) {
keyboard_post_init_hello_world_kb();
defer_exec(10000, delayed_hello_world, NULL);
}
bool process_record_hello_world(uint16_t keycode, keyrecord_t *record) {
if (!process_record_hello_world_kb(keycode, record)) {
return false;
}
switch (keycode) {
case COMMUNITY_MODULE_HELLO:
if (record->event.pressed) {
SEND_STRING("Hello there.");
break;
}
}
return true;
}

View file

@ -0,0 +1,10 @@
// Copyright 2025 Nick Brassel (@tzarc)
// SPDX-License-Identifier: GPL-2.0-or-later
hello_world_introspection_t hello_world_introspection(void) {
hello_world_introspection_t introspection = {
.total_size = sizeof(keymaps),
.layer_count = sizeof(keymaps) / sizeof(keymaps[0]),
};
return introspection;
}

View file

@ -0,0 +1,10 @@
// Copyright 2025 Nick Brassel (@tzarc)
// SPDX-License-Identifier: GPL-2.0-or-later
#include QMK_KEYBOARD_H
typedef struct hello_world_introspection_t {
int16_t total_size;
int16_t layer_count;
} hello_world_introspection_t;
hello_world_introspection_t hello_world_introspection(void);

View file

@ -0,0 +1,13 @@
{
"module_name": "Hello World",
"maintainer": "QMK Maintainers",
"features": {
"deferred_exec": true
},
"keycodes": [
{
"key": "COMMUNITY_MODULE_HELLO",
"aliases": ["CM_HELO"]
}
]
}

View file

@ -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;

View file

@ -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

View file

@ -76,6 +76,8 @@ enum qk_keycode_ranges {
QK_MACRO_MAX = 0x777F,
QK_CONNECTION = 0x7780,
QK_CONNECTION_MAX = 0x77BF,
QK_COMMUNITY_MODULE = 0x77C0,
QK_COMMUNITY_MODULE_MAX = 0x77FF,
QK_LIGHTING = 0x7800,
QK_LIGHTING_MAX = 0x78FF,
QK_QUANTUM = 0x7C00,
@ -1476,6 +1478,7 @@ enum qk_keycode_defines {
#define IS_QK_STENO(code) ((code) >= QK_STENO && (code) <= QK_STENO_MAX)
#define IS_QK_MACRO(code) ((code) >= QK_MACRO && (code) <= QK_MACRO_MAX)
#define IS_QK_CONNECTION(code) ((code) >= QK_CONNECTION && (code) <= QK_CONNECTION_MAX)
#define IS_QK_COMMUNITY_MODULE(code) ((code) >= QK_COMMUNITY_MODULE && (code) <= QK_COMMUNITY_MODULE_MAX)
#define IS_QK_LIGHTING(code) ((code) >= QK_LIGHTING && (code) <= QK_LIGHTING_MAX)
#define IS_QK_QUANTUM(code) ((code) >= QK_QUANTUM && (code) <= QK_QUANTUM_MAX)
#define IS_QK_KB(code) ((code) >= QK_KB && (code) <= QK_KB_MAX)

View file

@ -1,6 +1,10 @@
// Copyright 2022 Nick Brassel (@tzarc)
// SPDX-License-Identifier: GPL-2.0-or-later
#if defined(COMMUNITY_MODULES_ENABLE)
# include "community_modules_introspection.h"
#endif // defined(COMMUNITY_MODULES_ENABLE)
// Pull the actual keymap code so that we can inspect stuff from it
#include KEYMAP_C
@ -171,3 +175,10 @@ __attribute__((weak)) const key_override_t* key_override_get(uint16_t key_overri
}
#endif // defined(KEY_OVERRIDE_ENABLE)
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Community modules (must be last in this file!)
#if defined(COMMUNITY_MODULES_ENABLE)
# include "community_modules_introspection.c"
#endif // defined(COMMUNITY_MODULES_ENABLE)

View file

@ -72,6 +72,8 @@ static volatile struct usb_device_state maxprev_usb_device_state = {.configure_s
static volatile bool debouncing = false;
static volatile fast_timer_t last_time = 0;
bool process_detected_host_os_modules(os_variant_t os);
void os_detection_task(void) {
#ifdef OS_DETECTION_KEYBOARD_RESET
// resetting the keyboard on the USB device state change callback results in instability, so delegate that to this task
@ -96,12 +98,17 @@ void os_detection_task(void) {
if (detected_os != reported_os || first_report) {
first_report = false;
reported_os = detected_os;
process_detected_host_os_modules(detected_os);
process_detected_host_os_kb(detected_os);
}
}
}
}
__attribute__((weak)) bool process_detected_host_os_modules(os_variant_t os) {
return true;
}
__attribute__((weak)) bool process_detected_host_os_kb(os_variant_t detected_os) {
return process_detected_host_os_user(detected_os);
}

View file

@ -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,22 @@ __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;
}
__attribute__((weak)) void suspend_power_down_modules(void) {}
__attribute__((weak)) void suspend_wakeup_init_modules(void) {}
void shutdown_quantum(bool jump_to_bootloader) {
clear_keyboard();
#if defined(MIDI_ENABLE) && defined(MIDI_BASIC)
@ -199,11 +217,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 +278,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 +288,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);
}
@ -332,6 +353,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_modules(keycode, record) && // modules must run before kb
process_record_kb(keycode, record) &&
#if defined(VIA_ENABLE)
process_record_via(keycode, record) &&
@ -526,6 +548,7 @@ __attribute__((weak)) bool shutdown_kb(bool jump_to_bootloader) {
}
void suspend_power_down_quantum(void) {
suspend_power_down_modules();
suspend_power_down_kb();
#ifndef NO_SUSPEND_POWER_DOWN
// Turn off backlight
@ -593,6 +616,7 @@ __attribute__((weak)) void suspend_wakeup_init_quantum(void) {
#if defined(RGB_MATRIX_ENABLE)
rgb_matrix_set_suspend_state(false);
#endif
suspend_wakeup_init_modules();
suspend_wakeup_init_kb();
}

View file

@ -244,6 +244,10 @@ extern layer_state_t layer_state;
# include "layer_lock.h"
#endif
#ifdef COMMUNITY_MODULES_ENABLE
# include "community_modules.h"
#endif
void set_single_default_layer(uint8_t default_layer);
void set_single_persistent_default_layer(uint8_t default_layer);