diff options
| author | Nick Brassel <nick@tzarc.org> | 2025-02-26 22:25:41 +1100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-02-26 22:25:41 +1100 |
| commit | 1efc82403bebe759272d1ba7a79d9dfa0d5df506 (patch) | |
| tree | c356ae7afeb0849926534beb5dd4515f693abf99 | |
| parent | 63b095212b157c4522bdeda3de144fb87213085d (diff) | |
Community modules (#24848)
37 files changed, 987 insertions, 84 deletions
diff --git a/.github/labeler.yml b/.github/labeler.yml index 270cd1a813..82f9672bb7 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml | |||
| @@ -54,3 +54,7 @@ dd: | |||
| 54 | - data/constants/** | 54 | - data/constants/** |
| 55 | - data/mappings/** | 55 | - data/mappings/** |
| 56 | - data/schemas/** | 56 | - data/schemas/** |
| 57 | community_module: | ||
| 58 | - changed-files: | ||
| 59 | - any-glob-to-any-file: | ||
| 60 | - modules/** | ||
diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 74c518fe05..b4e32f981b 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml | |||
| @@ -10,6 +10,7 @@ on: | |||
| 10 | - 'lib/arm_atsam/**' | 10 | - 'lib/arm_atsam/**' |
| 11 | - 'lib/lib8tion/**' | 11 | - 'lib/lib8tion/**' |
| 12 | - 'lib/python/**' | 12 | - 'lib/python/**' |
| 13 | - 'modules/**' | ||
| 13 | - 'platforms/**' | 14 | - 'platforms/**' |
| 14 | - 'quantum/**' | 15 | - 'quantum/**' |
| 15 | - 'tests/**' | 16 | - 'tests/**' |
diff --git a/builddefs/build_keyboard.mk b/builddefs/build_keyboard.mk index 7d58c29462..c5fc9cd25d 100644 --- a/builddefs/build_keyboard.mk +++ b/builddefs/build_keyboard.mk | |||
| @@ -112,6 +112,39 @@ endif | |||
| 112 | ifneq ("$(wildcard $(KEYBOARD_PATH_1)/rules.mk)","") | 112 | ifneq ("$(wildcard $(KEYBOARD_PATH_1)/rules.mk)","") |
| 113 | include $(KEYBOARD_PATH_1)/rules.mk | 113 | include $(KEYBOARD_PATH_1)/rules.mk |
| 114 | endif | 114 | endif |
| 115 | # Create dependencies on DD keyboard config - structure validated elsewhere | ||
| 116 | DD_CONFIG_FILES := | ||
| 117 | ifneq ("$(wildcard $(KEYBOARD_PATH_1)/info.json)","") | ||
| 118 | DD_CONFIG_FILES += $(KEYBOARD_PATH_1)/info.json | ||
| 119 | endif | ||
| 120 | ifneq ("$(wildcard $(KEYBOARD_PATH_2)/info.json)","") | ||
| 121 | DD_CONFIG_FILES += $(KEYBOARD_PATH_2)/info.json | ||
| 122 | endif | ||
| 123 | ifneq ("$(wildcard $(KEYBOARD_PATH_3)/info.json)","") | ||
| 124 | DD_CONFIG_FILES += $(KEYBOARD_PATH_3)/info.json | ||
| 125 | endif | ||
| 126 | ifneq ("$(wildcard $(KEYBOARD_PATH_4)/info.json)","") | ||
| 127 | DD_CONFIG_FILES += $(KEYBOARD_PATH_4)/info.json | ||
| 128 | endif | ||
| 129 | ifneq ("$(wildcard $(KEYBOARD_PATH_5)/info.json)","") | ||
| 130 | DD_CONFIG_FILES += $(KEYBOARD_PATH_5)/info.json | ||
| 131 | endif | ||
| 132 | |||
| 133 | ifneq ("$(wildcard $(KEYBOARD_PATH_1)/keyboard.json)","") | ||
| 134 | DD_CONFIG_FILES += $(KEYBOARD_PATH_1)/keyboard.json | ||
| 135 | endif | ||
| 136 | ifneq ("$(wildcard $(KEYBOARD_PATH_2)/keyboard.json)","") | ||
| 137 | DD_CONFIG_FILES += $(KEYBOARD_PATH_2)/keyboard.json | ||
| 138 | endif | ||
| 139 | ifneq ("$(wildcard $(KEYBOARD_PATH_3)/keyboard.json)","") | ||
| 140 | DD_CONFIG_FILES += $(KEYBOARD_PATH_3)/keyboard.json | ||
| 141 | endif | ||
| 142 | ifneq ("$(wildcard $(KEYBOARD_PATH_4)/keyboard.json)","") | ||
| 143 | DD_CONFIG_FILES += $(KEYBOARD_PATH_4)/keyboard.json | ||
| 144 | endif | ||
| 145 | ifneq ("$(wildcard $(KEYBOARD_PATH_5)/keyboard.json)","") | ||
| 146 | DD_CONFIG_FILES += $(KEYBOARD_PATH_5)/keyboard.json | ||
| 147 | endif | ||
| 115 | 148 | ||
| 116 | MAIN_KEYMAP_PATH_1 := $(KEYBOARD_PATH_1)/keymaps/$(KEYMAP) | 149 | MAIN_KEYMAP_PATH_1 := $(KEYBOARD_PATH_1)/keymaps/$(KEYMAP) |
| 117 | MAIN_KEYMAP_PATH_2 := $(KEYBOARD_PATH_2)/keymaps/$(KEYMAP) | 150 | MAIN_KEYMAP_PATH_2 := $(KEYBOARD_PATH_2)/keymaps/$(KEYMAP) |
| @@ -207,17 +240,17 @@ ifneq ("$(wildcard $(KEYMAP_JSON))", "") | |||
| 207 | include $(INFO_RULES_MK) | 240 | include $(INFO_RULES_MK) |
| 208 | 241 | ||
| 209 | # Add rules to generate the keymap files - indentation here is important | 242 | # Add rules to generate the keymap files - indentation here is important |
| 210 | $(INTERMEDIATE_OUTPUT)/src/keymap.c: $(KEYMAP_JSON) | 243 | $(INTERMEDIATE_OUTPUT)/src/keymap.c: $(KEYMAP_JSON) $(DD_CONFIG_FILES) |
| 211 | @$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD) | 244 | @$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD) |
| 212 | $(eval CMD=$(QMK_BIN) json2c --quiet --output $(KEYMAP_C) $(KEYMAP_JSON)) | 245 | $(eval CMD=$(QMK_BIN) json2c --quiet --output $(KEYMAP_C) $(KEYMAP_JSON)) |
| 213 | @$(BUILD_CMD) | 246 | @$(BUILD_CMD) |
| 214 | 247 | ||
| 215 | $(INTERMEDIATE_OUTPUT)/src/config.h: $(KEYMAP_JSON) | 248 | $(INTERMEDIATE_OUTPUT)/src/config.h: $(KEYMAP_JSON) $(DD_CONFIG_FILES) |
| 216 | @$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD) | 249 | @$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD) |
| 217 | $(eval CMD=$(QMK_BIN) generate-config-h --quiet --output $(KEYMAP_H) $(KEYMAP_JSON)) | 250 | $(eval CMD=$(QMK_BIN) generate-config-h --quiet --output $(KEYMAP_H) $(KEYMAP_JSON)) |
| 218 | @$(BUILD_CMD) | 251 | @$(BUILD_CMD) |
| 219 | 252 | ||
| 220 | $(INTERMEDIATE_OUTPUT)/src/keymap.h: $(KEYMAP_JSON) | 253 | $(INTERMEDIATE_OUTPUT)/src/keymap.h: $(KEYMAP_JSON) $(DD_CONFIG_FILES) |
| 221 | @$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD) | 254 | @$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD) |
| 222 | $(eval CMD=$(QMK_BIN) generate-keymap-h --quiet --output $(INTERMEDIATE_OUTPUT)/src/keymap.h $(KEYMAP_JSON)) | 255 | $(eval CMD=$(QMK_BIN) generate-keymap-h --quiet --output $(INTERMEDIATE_OUTPUT)/src/keymap.h $(KEYMAP_JSON)) |
| 223 | @$(BUILD_CMD) | 256 | @$(BUILD_CMD) |
| @@ -226,6 +259,32 @@ generated-files: $(INTERMEDIATE_OUTPUT)/src/config.h $(INTERMEDIATE_OUTPUT)/src/ | |||
| 226 | 259 | ||
| 227 | endif | 260 | endif |
| 228 | 261 | ||
| 262 | # Community modules | ||
| 263 | $(INTERMEDIATE_OUTPUT)/src/community_modules.h: $(KEYMAP_JSON) $(DD_CONFIG_FILES) | ||
| 264 | @$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD) | ||
| 265 | $(eval CMD=$(QMK_BIN) generate-community-modules-h -kb $(KEYBOARD) --quiet --output $(INTERMEDIATE_OUTPUT)/src/community_modules.h $(KEYMAP_JSON)) | ||
| 266 | @$(BUILD_CMD) | ||
| 267 | |||
| 268 | $(INTERMEDIATE_OUTPUT)/src/community_modules.c: $(KEYMAP_JSON) $(DD_CONFIG_FILES) | ||
| 269 | @$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD) | ||
| 270 | $(eval CMD=$(QMK_BIN) generate-community-modules-c -kb $(KEYBOARD) --quiet --output $(INTERMEDIATE_OUTPUT)/src/community_modules.c $(KEYMAP_JSON)) | ||
| 271 | @$(BUILD_CMD) | ||
| 272 | |||
| 273 | $(INTERMEDIATE_OUTPUT)/src/community_modules_introspection.c: $(KEYMAP_JSON) $(DD_CONFIG_FILES) | ||
| 274 | @$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD) | ||
| 275 | $(eval CMD=$(QMK_BIN) generate-community-modules-introspection-c -kb $(KEYBOARD) --quiet --output $(INTERMEDIATE_OUTPUT)/src/community_modules_introspection.c $(KEYMAP_JSON)) | ||
| 276 | @$(BUILD_CMD) | ||
| 277 | |||
| 278 | $(INTERMEDIATE_OUTPUT)/src/community_modules_introspection.h: $(KEYMAP_JSON) $(DD_CONFIG_FILES) | ||
| 279 | @$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD) | ||
| 280 | $(eval CMD=$(QMK_BIN) generate-community-modules-introspection-h -kb $(KEYBOARD) --quiet --output $(INTERMEDIATE_OUTPUT)/src/community_modules_introspection.h $(KEYMAP_JSON)) | ||
| 281 | @$(BUILD_CMD) | ||
| 282 | |||
| 283 | SRC += $(INTERMEDIATE_OUTPUT)/src/community_modules.c | ||
| 284 | |||
| 285 | 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 | ||
| 286 | |||
| 287 | |||
| 229 | include $(BUILDDEFS_PATH)/converters.mk | 288 | include $(BUILDDEFS_PATH)/converters.mk |
| 230 | 289 | ||
| 231 | # Generate the board's version.h file. | 290 | # Generate the board's version.h file. |
| @@ -315,6 +374,14 @@ endif | |||
| 315 | 374 | ||
| 316 | # Find all of the config.h files and add them to our CONFIG_H define. | 375 | # Find all of the config.h files and add them to our CONFIG_H define. |
| 317 | CONFIG_H := | 376 | CONFIG_H := |
| 377 | |||
| 378 | define config_h_community_module_appender | ||
| 379 | ifneq ("$(wildcard $(1)/config.h)","") | ||
| 380 | CONFIG_H += $(1)/config.h | ||
| 381 | endif | ||
| 382 | endef | ||
| 383 | $(foreach module,$(COMMUNITY_MODULE_PATHS),$(eval $(call config_h_community_module_appender,$(module)))) | ||
| 384 | |||
| 318 | ifneq ("$(wildcard $(KEYBOARD_PATH_5)/config.h)","") | 385 | ifneq ("$(wildcard $(KEYBOARD_PATH_5)/config.h)","") |
| 319 | CONFIG_H += $(KEYBOARD_PATH_5)/config.h | 386 | CONFIG_H += $(KEYBOARD_PATH_5)/config.h |
| 320 | endif | 387 | endif |
| @@ -332,6 +399,14 @@ ifneq ("$(wildcard $(KEYBOARD_PATH_1)/config.h)","") | |||
| 332 | endif | 399 | endif |
| 333 | 400 | ||
| 334 | POST_CONFIG_H := | 401 | POST_CONFIG_H := |
| 402 | |||
| 403 | define post_config_h_community_module_appender | ||
| 404 | ifneq ("$(wildcard $(1)/post_config.h)","") | ||
| 405 | POST_CONFIG_H += $(1)/post_config.h | ||
| 406 | endif | ||
| 407 | endef | ||
| 408 | $(foreach module,$(COMMUNITY_MODULE_PATHS),$(eval $(call post_config_h_community_module_appender,$(module)))) | ||
| 409 | |||
| 335 | ifneq ("$(wildcard $(KEYBOARD_PATH_1)/post_config.h)","") | 410 | ifneq ("$(wildcard $(KEYBOARD_PATH_1)/post_config.h)","") |
| 336 | POST_CONFIG_H += $(KEYBOARD_PATH_1)/post_config.h | 411 | POST_CONFIG_H += $(KEYBOARD_PATH_1)/post_config.h |
| 337 | endif | 412 | endif |
| @@ -348,40 +423,6 @@ ifneq ("$(wildcard $(KEYBOARD_PATH_5)/post_config.h)","") | |||
| 348 | POST_CONFIG_H += $(KEYBOARD_PATH_5)/post_config.h | 423 | POST_CONFIG_H += $(KEYBOARD_PATH_5)/post_config.h |
| 349 | endif | 424 | endif |
| 350 | 425 | ||
| 351 | # Create dependencies on DD keyboard config - structure validated elsewhere | ||
| 352 | DD_CONFIG_FILES := | ||
| 353 | ifneq ("$(wildcard $(KEYBOARD_PATH_1)/info.json)","") | ||
| 354 | DD_CONFIG_FILES += $(KEYBOARD_PATH_1)/info.json | ||
| 355 | endif | ||
| 356 | ifneq ("$(wildcard $(KEYBOARD_PATH_2)/info.json)","") | ||
| 357 | DD_CONFIG_FILES += $(KEYBOARD_PATH_2)/info.json | ||
| 358 | endif | ||
| 359 | ifneq ("$(wildcard $(KEYBOARD_PATH_3)/info.json)","") | ||
| 360 | DD_CONFIG_FILES += $(KEYBOARD_PATH_3)/info.json | ||
| 361 | endif | ||
| 362 | ifneq ("$(wildcard $(KEYBOARD_PATH_4)/info.json)","") | ||
| 363 | DD_CONFIG_FILES += $(KEYBOARD_PATH_4)/info.json | ||
| 364 | endif | ||
| 365 | ifneq ("$(wildcard $(KEYBOARD_PATH_5)/info.json)","") | ||
| 366 | DD_CONFIG_FILES += $(KEYBOARD_PATH_5)/info.json | ||
| 367 | endif | ||
| 368 | |||
| 369 | ifneq ("$(wildcard $(KEYBOARD_PATH_1)/keyboard.json)","") | ||
| 370 | DD_CONFIG_FILES += $(KEYBOARD_PATH_1)/keyboard.json | ||
| 371 | endif | ||
| 372 | ifneq ("$(wildcard $(KEYBOARD_PATH_2)/keyboard.json)","") | ||
| 373 | DD_CONFIG_FILES += $(KEYBOARD_PATH_2)/keyboard.json | ||
| 374 | endif | ||
| 375 | ifneq ("$(wildcard $(KEYBOARD_PATH_3)/keyboard.json)","") | ||
| 376 | DD_CONFIG_FILES += $(KEYBOARD_PATH_3)/keyboard.json | ||
| 377 | endif | ||
| 378 | ifneq ("$(wildcard $(KEYBOARD_PATH_4)/keyboard.json)","") | ||
| 379 | DD_CONFIG_FILES += $(KEYBOARD_PATH_4)/keyboard.json | ||
| 380 | endif | ||
| 381 | ifneq ("$(wildcard $(KEYBOARD_PATH_5)/keyboard.json)","") | ||
| 382 | DD_CONFIG_FILES += $(KEYBOARD_PATH_5)/keyboard.json | ||
| 383 | endif | ||
| 384 | |||
| 385 | CONFIG_H += $(INTERMEDIATE_OUTPUT)/src/info_config.h | 426 | CONFIG_H += $(INTERMEDIATE_OUTPUT)/src/info_config.h |
| 386 | KEYBOARD_SRC += $(INTERMEDIATE_OUTPUT)/src/default_keyboard.c | 427 | KEYBOARD_SRC += $(INTERMEDIATE_OUTPUT)/src/default_keyboard.c |
| 387 | 428 | ||
| @@ -462,6 +503,13 @@ ifneq ("$(wildcard $(KEYBOARD_PATH_5)/post_rules.mk)","") | |||
| 462 | include $(KEYBOARD_PATH_5)/post_rules.mk | 503 | include $(KEYBOARD_PATH_5)/post_rules.mk |
| 463 | endif | 504 | endif |
| 464 | 505 | ||
| 506 | define post_rules_mk_community_module_includer | ||
| 507 | ifneq ("$(wildcard $(1)/post_rules.mk)","") | ||
| 508 | include $(1)/post_rules.mk | ||
| 509 | endif | ||
| 510 | endef | ||
| 511 | $(foreach module,$(COMMUNITY_MODULE_PATHS),$(eval $(call post_rules_mk_community_module_includer,$(module)))) | ||
| 512 | |||
| 465 | ifneq ("$(wildcard $(KEYMAP_PATH)/config.h)","") | 513 | ifneq ("$(wildcard $(KEYMAP_PATH)/config.h)","") |
| 466 | CONFIG_H += $(KEYMAP_PATH)/config.h | 514 | CONFIG_H += $(KEYMAP_PATH)/config.h |
| 467 | endif | 515 | endif |
diff --git a/data/constants/keycodes/keycodes_0.0.7.hjson b/data/constants/keycodes/keycodes_0.0.7.hjson new file mode 100644 index 0000000000..52e1a50443 --- /dev/null +++ b/data/constants/keycodes/keycodes_0.0.7.hjson | |||
| @@ -0,0 +1,7 @@ | |||
| 1 | { | ||
| 2 | "ranges": { | ||
| 3 | "0x77C0/0x003F": { | ||
| 4 | "define": "QK_COMMUNITY_MODULE" | ||
| 5 | } | ||
| 6 | } | ||
| 7 | } | ||
diff --git a/data/constants/module_hooks/0.1.0.hjson b/data/constants/module_hooks/0.1.0.hjson new file mode 100644 index 0000000000..c77f4c297e --- /dev/null +++ b/data/constants/module_hooks/0.1.0.hjson | |||
| @@ -0,0 +1,25 @@ | |||
| 1 | { | ||
| 2 | keyboard_pre_init: { | ||
| 3 | ret_type: void | ||
| 4 | args: void | ||
| 5 | } | ||
| 6 | keyboard_post_init: { | ||
| 7 | ret_type: void | ||
| 8 | args: void | ||
| 9 | } | ||
| 10 | pre_process_record: { | ||
| 11 | ret_type: bool | ||
| 12 | args: uint16_t keycode, keyrecord_t *record | ||
| 13 | call_params: keycode, record | ||
| 14 | } | ||
| 15 | process_record: { | ||
| 16 | ret_type: bool | ||
| 17 | args: uint16_t keycode, keyrecord_t *record | ||
| 18 | call_params: keycode, record | ||
| 19 | } | ||
| 20 | post_process_record: { | ||
| 21 | ret_type: void | ||
| 22 | args: uint16_t keycode, keyrecord_t *record | ||
| 23 | call_params: keycode, record | ||
| 24 | } | ||
| 25 | } | ||
diff --git a/data/constants/module_hooks/1.0.0.hjson b/data/constants/module_hooks/1.0.0.hjson new file mode 100644 index 0000000000..4e7bf30412 --- /dev/null +++ b/data/constants/module_hooks/1.0.0.hjson | |||
| @@ -0,0 +1,26 @@ | |||
| 1 | { | ||
| 2 | housekeeping_task: { | ||
| 3 | ret_type: void | ||
| 4 | args: void | ||
| 5 | } | ||
| 6 | suspend_power_down: { | ||
| 7 | ret_type: void | ||
| 8 | args: void | ||
| 9 | } | ||
| 10 | suspend_wakeup_init: { | ||
| 11 | ret_type: void | ||
| 12 | args: void | ||
| 13 | } | ||
| 14 | shutdown: { | ||
| 15 | ret_type: bool | ||
| 16 | args: bool jump_to_bootloader | ||
| 17 | call_params: jump_to_bootloader | ||
| 18 | } | ||
| 19 | process_detected_host_os: { | ||
| 20 | ret_type: bool | ||
| 21 | args: os_variant_t os | ||
| 22 | call_params: os | ||
| 23 | guard: defined(OS_DETECTION_ENABLE) | ||
| 24 | header: os_detection.h | ||
| 25 | } | ||
| 26 | } | ||
diff --git a/data/schemas/community_module.jsonschema b/data/schemas/community_module.jsonschema new file mode 100644 index 0000000000..a3474476df --- /dev/null +++ b/data/schemas/community_module.jsonschema | |||
| @@ -0,0 +1,17 @@ | |||
| 1 | { | ||
| 2 | "$schema": "https://json-schema.org/draft/2020-12/schema#", | ||
| 3 | "$id": "qmk.community_module.v1", | ||
| 4 | "title": "Community Module Information", | ||
| 5 | "type": "object", | ||
| 6 | "required": ["module_name", "maintainer"] | ||
| 7 | "properties": { | ||
| 8 | "module_name": {"$ref": "qmk.definitions.v1#/text_identifier"}, | ||
| 9 | "maintainer": {"$ref": "qmk.definitions.v1#/text_identifier"}, | ||
| 10 | "url": { | ||
| 11 | "type": "string", | ||
| 12 | "format": "uri" | ||
| 13 | }, | ||
| 14 | "keycodes": {"$ref": "qmk.definitions.v1#/keycode_decl_array"}, | ||
| 15 | "features": {"$ref": "qmk.keyboard.v1#/definitions/features_config"}, | ||
| 16 | } | ||
| 17 | } | ||
diff --git a/data/schemas/keyboard.jsonschema b/data/schemas/keyboard.jsonschema index 8b6cc7032b..9b63f62d45 100644 --- a/data/schemas/keyboard.jsonschema +++ b/data/schemas/keyboard.jsonschema | |||
| @@ -31,6 +31,11 @@ | |||
| 31 | "pins": {"$ref": "qmk.definitions.v1#/mcu_pin_array"} | 31 | "pins": {"$ref": "qmk.definitions.v1#/mcu_pin_array"} |
| 32 | } | 32 | } |
| 33 | } | 33 | } |
| 34 | "features_config": { | ||
| 35 | "$ref": "qmk.definitions.v1#/boolean_array", | ||
| 36 | "propertyNames": {"$ref": "qmk.definitions.v1#/snake_case"}, | ||
| 37 | "not": {"required": ["lto"]} | ||
| 38 | }, | ||
| 34 | }, | 39 | }, |
| 35 | "type": "object", | 40 | "type": "object", |
| 36 | "not": {"required": ["vendorId", "productId"]}, // reject via keys... | 41 | "not": {"required": ["vendorId", "productId"]}, // reject via keys... |
| @@ -328,11 +333,7 @@ | |||
| 328 | "enabled": {"type": "boolean"} | 333 | "enabled": {"type": "boolean"} |
| 329 | } | 334 | } |
| 330 | }, | 335 | }, |
| 331 | "features": { | 336 | "features": { "$ref": "#/definitions/features_config" }, |
| 332 | "$ref": "qmk.definitions.v1#/boolean_array", | ||
| 333 | "propertyNames": {"$ref": "qmk.definitions.v1#/snake_case"}, | ||
| 334 | "not": {"required": ["lto"]} | ||
| 335 | }, | ||
| 336 | "indicators": { | 337 | "indicators": { |
| 337 | "type": "object", | 338 | "type": "object", |
| 338 | "properties": { | 339 | "properties": { |
| @@ -467,6 +468,12 @@ | |||
| 467 | "rows": {"$ref": "qmk.definitions.v1#/mcu_pin_array"} | 468 | "rows": {"$ref": "qmk.definitions.v1#/mcu_pin_array"} |
| 468 | } | 469 | } |
| 469 | }, | 470 | }, |
| 471 | "modules": { | ||
| 472 | "type": "array", | ||
| 473 | "items": { | ||
| 474 | "type": "string" | ||
| 475 | } | ||
| 476 | }, | ||
| 470 | "mouse_key": { | 477 | "mouse_key": { |
| 471 | "type": "object", | 478 | "type": "object", |
| 472 | "properties": { | 479 | "properties": { |
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 @@ | |||
| 71 | "config": {"$ref": "qmk.keyboard.v1"}, | 71 | "config": {"$ref": "qmk.keyboard.v1"}, |
| 72 | "notes": { | 72 | "notes": { |
| 73 | "type": "string" | 73 | "type": "string" |
| 74 | }, | ||
| 75 | "modules": { | ||
| 76 | "type": "array", | ||
| 77 | "items": { | ||
| 78 | "type": "string" | ||
| 79 | } | ||
| 74 | } | 80 | } |
| 75 | } | 81 | } |
| 76 | } | 82 | } |
diff --git a/docs/_sidebar.json b/docs/_sidebar.json index 95601be7de..0b5f297018 100644 --- a/docs/_sidebar.json +++ b/docs/_sidebar.json | |||
| @@ -60,6 +60,7 @@ | |||
| 60 | "items": [ | 60 | "items": [ |
| 61 | { "text": "Customizing Functionality", "link": "/custom_quantum_functions" }, | 61 | { "text": "Customizing Functionality", "link": "/custom_quantum_functions" }, |
| 62 | { "text": "Driver Installation with Zadig", "link": "/driver_installation_zadig" }, | 62 | { "text": "Driver Installation with Zadig", "link": "/driver_installation_zadig" }, |
| 63 | { "text": "Community Modules", "link": "/features/community_modules" }, | ||
| 63 | { "text": "Keymap Overview", "link": "/keymap" }, | 64 | { "text": "Keymap Overview", "link": "/keymap" }, |
| 64 | { | 65 | { |
| 65 | "text": "Development Environments", | 66 | "text": "Development Environments", |
diff --git a/docs/custom_quantum_functions.md b/docs/custom_quantum_functions.md index 1479eb53f6..c69beb055e 100644 --- a/docs/custom_quantum_functions.md +++ b/docs/custom_quantum_functions.md | |||
| @@ -9,12 +9,19 @@ This page does not assume any special knowledge about QMK, but reading [Understa | |||
| 9 | We have structured QMK as a hierarchy: | 9 | We have structured QMK as a hierarchy: |
| 10 | 10 | ||
| 11 | * Core (`_quantum`) | 11 | * Core (`_quantum`) |
| 12 | * Community Module (`_<module>`) | ||
| 13 | * Community Module -> Keyboard/Revision (`_<module>_kb`) | ||
| 14 | * Community Module -> Keymap (`_<module>_user`) | ||
| 12 | * Keyboard/Revision (`_kb`) | 15 | * Keyboard/Revision (`_kb`) |
| 13 | * Keymap (`_user`) | 16 | * Keymap (`_user`) |
| 14 | 17 | ||
| 15 | Each of the functions described below can be defined with a `_kb()` suffix or a `_user()` suffix. We intend for you to use the `_kb()` suffix at the Keyboard/Revision level, while the `_user()` suffix should be used at the Keymap level. | 18 | Each of the functions described below can be defined with a `_kb()` suffix or a `_user()` suffix. We intend for you to use the `_kb()` suffix at the Keyboard/Revision level, while the `_user()` suffix should be used at the Keymap level. |
| 16 | 19 | ||
| 17 | When defining functions at the Keyboard/Revision level it is important that your `_kb()` implementation call `_user()` before executing anything else- otherwise the keymap level function will never be called. | 20 | When defining functions at the Keyboard/Revision level it is important that your `_kb()` implementation call `_user()` at an appropriate location, otherwise the keymap level function will never be called. |
| 21 | |||
| 22 | Functions at the `_<module>_xxx()` level are intended to allow keyboards or keymaps to override or enhance the processing associated with a [community module](/features/community_modules). | ||
| 23 | |||
| 24 | When defining module overrides such as `process_record_<module>()`, the same pattern should be used; the module must invoke `process_record_<module>_kb()` as appropriate. | ||
| 18 | 25 | ||
| 19 | # Custom Keycodes | 26 | # Custom Keycodes |
| 20 | 27 | ||
| @@ -99,7 +106,7 @@ These are the three main initialization functions, listed in the order that they | |||
| 99 | * `keyboard_post_init_*` - Happens at the end of the firmware's startup process. This is where you'd want to put "customization" code, for the most part. | 106 | * `keyboard_post_init_*` - Happens at the end of the firmware's startup process. This is where you'd want to put "customization" code, for the most part. |
| 100 | 107 | ||
| 101 | ::: warning | 108 | ::: warning |
| 102 | For most people, the `keyboard_post_init_user` function is what you want to call. For instance, this is where you want to set up things for RGB Underglow. | 109 | For most people, the `keyboard_post_init_user` function is what you want to implement. For instance, this is where you want to set up things for RGB Underglow. |
| 103 | ::: | 110 | ::: |
| 104 | 111 | ||
| 105 | ## Keyboard Pre Initialization code | 112 | ## Keyboard Pre Initialization code |
diff --git a/docs/features/community_modules.md b/docs/features/community_modules.md new file mode 100644 index 0000000000..3a1a82e7bc --- /dev/null +++ b/docs/features/community_modules.md | |||
| @@ -0,0 +1,142 @@ | |||
| 1 | # Community Modules | ||
| 2 | |||
| 3 | Community Modules are a feature within QMK which allows code to be implemented by third parties, making it available for other people to import into their own builds. | ||
| 4 | |||
| 5 | These modules can provide implementations which override or enhance normal QMK processing; initialization, key processing, suspend, and shutdown are some of the provided hooks which modules may implement. | ||
| 6 | |||
| 7 | ## Adding a Community Module to your build | ||
| 8 | |||
| 9 | Community Modules have first-class support for [External Userspace](/newbs_external_userspace), and QMK strongly recommends using External Userspace for hosting keymaps and Community Modules together. | ||
| 10 | |||
| 11 | Modules must live in either of two locations: | ||
| 12 | |||
| 13 | * `<QMK_USERSPACE>/modules/` | ||
| 14 | * `<QMK_FIRMWARE>/modules/` | ||
| 15 | |||
| 16 | A basic module is provided within QMK itself -- `qmk/hello_world` -- which prints out a notification over [HID console](/faq_debug) after 10 seconds, and adds a new keycode, `COMMUNITY_MODULE_HELLO` (aliased to `CM_HELO`) which types `Hello there.` to the active application when the corresponding key is pressed. | ||
| 17 | |||
| 18 | To add this module to your build, in your keymap's directory create a `keymap.json` with the following content: | ||
| 19 | |||
| 20 | ```json | ||
| 21 | { | ||
| 22 | "modules": [ | ||
| 23 | "qmk/hello_world" | ||
| 24 | ] | ||
| 25 | } | ||
| 26 | ``` | ||
| 27 | |||
| 28 | If you already have a `keymap.json`, you'll need to manually merge the `modules` section into your keymap. | ||
| 29 | |||
| 30 | ::: warning | ||
| 31 | Community Modules are not supported by QMK Configurator. If you wish to use Community Modules, you must build your own firmware. | ||
| 32 | ::: | ||
| 33 | |||
| 34 | ## Adding a Community Module to your External Userspace | ||
| 35 | |||
| 36 | Module authors are encouraged to provide a git repository on GitHub which may be imported into a user's external userspace. If a user wishes to import a module repository, they can do the following: | ||
| 37 | |||
| 38 | ```sh | ||
| 39 | cd /path/to/your/external/userspace | ||
| 40 | mkdir -p modules | ||
| 41 | # Replace the following {user} and {repo} with the author's community module repository | ||
| 42 | git submodule add https://github.com/{user}/{repo}.git modules/{user} | ||
| 43 | git submdule update --init --recursive | ||
| 44 | ``` | ||
| 45 | |||
| 46 | This will ensure the copy of the module is made in your userspace. | ||
| 47 | |||
| 48 | Add a new entry into your `keymap.json` with the desired modules, replacing `{user}` and `{module_name}` as appropriate: | ||
| 49 | |||
| 50 | ```json | ||
| 51 | { | ||
| 52 | "modules": [ | ||
| 53 | "qmk/hello_world", | ||
| 54 | "{user}/{module_name}" | ||
| 55 | ] | ||
| 56 | } | ||
| 57 | ``` | ||
| 58 | |||
| 59 | ::: info | ||
| 60 | The module listed in `keymap.json` is the relative path within the `modules/` directory. So long as the module is present _somewhere_ under `modules/`, then the `keymap.json` can refer to that path. | ||
| 61 | ::: | ||
| 62 | |||
| 63 | ## Writing a QMK Community Module | ||
| 64 | |||
| 65 | As stated earlier, Community Module authors are strongly encouraged to provide their modules through git, allowing users to leverage submodules to import functionality. | ||
| 66 | |||
| 67 | ### `qmk_module.json` | ||
| 68 | |||
| 69 | A Community Module is denoted by a `qmk_module.json` file such as the following: | ||
| 70 | |||
| 71 | ```json | ||
| 72 | { | ||
| 73 | "module_name": "Hello World", | ||
| 74 | "maintainer": "QMK Maintainers", | ||
| 75 | "features": { | ||
| 76 | "deferred_exec": true | ||
| 77 | }, | ||
| 78 | "keycodes": [ | ||
| 79 | { | ||
| 80 | "key": "COMMUNITY_MODULE_HELLO", | ||
| 81 | "aliases": ["CM_HELO"] | ||
| 82 | } | ||
| 83 | ] | ||
| 84 | } | ||
| 85 | ``` | ||
| 86 | |||
| 87 | At minimum, the module must provide the `module_name` and `maintainer` fields. | ||
| 88 | |||
| 89 | The use of `features` matches the definition normally provided within `keyboard.json` and `info.json`, allowing a module to signal to the build system that it has its own dependencies. In the example above, it enables the _deferred executor_ feature whenever the above module is used in a build. | ||
| 90 | |||
| 91 | The `keycodes` array allows a module to provide new keycodes (as well as corresponding aliases) to a keymap. | ||
| 92 | |||
| 93 | ### `rules.mk` / `post_rules.mk` | ||
| 94 | |||
| 95 | These two files follows standard QMK build system logic, allowing for `Makefile`-style customisation as if it were present in the keyboard or keymap. | ||
| 96 | |||
| 97 | ### `<module>.c` | ||
| 98 | |||
| 99 | This file will be automatically added to the build if the filename matches the directory name. For example, the `qmk/hello_world` module contains a `hello_world.c` file, which is automatically added to the build. | ||
| 100 | |||
| 101 | ::: info | ||
| 102 | Other files intended to be included must use the normal method of `SRC += my_file.c` inside `rules.mk`. | ||
| 103 | ::: | ||
| 104 | |||
| 105 | ::: tip | ||
| 106 | This file should use `ASSERT_COMMUNITY_MODULES_MIN_API_VERSION(1,0,0);` to enforce a minimum version of the API that it requires, ensuring the Community Module is built with a compatible version of QMK. The list of APIs and corresponding version is given at the bottom of this document. Note the use of commas instead of periods. | ||
| 107 | ::: | ||
| 108 | |||
| 109 | ### `introspection.c` / `introspection.h` | ||
| 110 | |||
| 111 | These two files hook into the keymap introspection logic -- the header is prepended before the user keymap, and the C source file is appended after the user keymap. | ||
| 112 | |||
| 113 | The header may provide definitions which are useful to the user's `keymap.c`. | ||
| 114 | |||
| 115 | The source file may provide functions which allow access to information specified in the user's `keymap.c`. | ||
| 116 | |||
| 117 | ::: warning | ||
| 118 | Introspection is a relatively advanced topic within QMK, and existing patterns should be followed. If you need help please [open an issue](https://github.com/qmk/qmk_firmware/issues/new) or [chat with us on Discord](https://discord.gg/qmk). | ||
| 119 | ::: | ||
| 120 | |||
| 121 | ### Compatible APIs | ||
| 122 | |||
| 123 | Community Modules may provide specializations for the following APIs: | ||
| 124 | |||
| 125 | | Base API | API Format | Example (`hello_world` module) | API Version | | ||
| 126 | |----------------------------|-------------------------------------|----------------------------------------|-------------| | ||
| 127 | | `keyboard_pre_init` | `keyboard_pre_init_<module>` | `keyboard_pre_init_hello_world` | `0.1.0` | | ||
| 128 | | `keyboard_post_init` | `keyboard_post_init_<module>` | `keyboard_post_init_hello_world` | `0.1.0` | | ||
| 129 | | `pre_process_record` | `pre_process_record_<module>` | `pre_process_record_hello_world` | `0.1.0` | | ||
| 130 | | `process_record` | `process_record_<module>` | `process_record_hello_world` | `0.1.0` | | ||
| 131 | | `post_process_record` | `post_process_record_<module>` | `post_process_record_hello_world` | `0.1.0` | | ||
| 132 | | `housekeeping_task` | `housekeeping_task_<module>` | `housekeeping_task_hello_world` | `1.0.0` | | ||
| 133 | | `suspend_power_down` | `suspend_power_down_<module>` | `suspend_power_down_hello_world` | `1.0.0` | | ||
| 134 | | `suspend_wakeup_init` | `suspend_wakeup_init_<module>` | `suspend_wakeup_init_hello_world` | `1.0.0` | | ||
| 135 | | `shutdown` | `shutdown_<module>` | `shutdown_hello_world` | `1.0.0` | | ||
| 136 | | `process_detected_host_os` | `process_detected_host_os_<module>` | `process_detected_host_os_hello_world` | `1.0.0` | | ||
| 137 | |||
| 138 | ::: info | ||
| 139 | An unspecified API is disregarded if a Community Module does not provide a specialization for it. | ||
| 140 | ::: | ||
| 141 | |||
| 142 | Each API has an equivalent `_<module>_kb()` and `_<module>_user()` hook, as per the normal QMK [`_quantum`, `_kb`, and `_user` functions](/custom_quantum_functions#a-word-on-core-vs-keyboards-vs-keymap). | ||
diff --git a/keyboards/handwired/onekey/keymaps/community_module/keymap.c b/keyboards/handwired/onekey/keymaps/community_module/keymap.c new file mode 100644 index 0000000000..5115a9fea0 --- /dev/null +++ b/keyboards/handwired/onekey/keymaps/community_module/keymap.c | |||
| @@ -0,0 +1,7 @@ | |||
| 1 | // Copyright 2025 Nick Brassel (@tzarc) | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | #include QMK_KEYBOARD_H | ||
| 4 | |||
| 5 | const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { | ||
| 6 | LAYOUT_ortho_1x1(CM_HELO) | ||
| 7 | }; | ||
diff --git a/keyboards/handwired/onekey/keymaps/community_module/keymap.json b/keyboards/handwired/onekey/keymaps/community_module/keymap.json new file mode 100644 index 0000000000..e3b4051b9e --- /dev/null +++ b/keyboards/handwired/onekey/keymaps/community_module/keymap.json | |||
| @@ -0,0 +1,3 @@ | |||
| 1 | { | ||
| 2 | "modules": ["qmk/hello_world"] | ||
| 3 | } | ||
diff --git a/lib/python/qmk/cli/__init__.py b/lib/python/qmk/cli/__init__.py index bb47ea26ed..3f2ba9ce3c 100644 --- a/lib/python/qmk/cli/__init__.py +++ b/lib/python/qmk/cli/__init__.py | |||
| @@ -49,6 +49,7 @@ subcommands = [ | |||
| 49 | 'qmk.cli.generate.api', | 49 | 'qmk.cli.generate.api', |
| 50 | 'qmk.cli.generate.autocorrect_data', | 50 | 'qmk.cli.generate.autocorrect_data', |
| 51 | 'qmk.cli.generate.compilation_database', | 51 | 'qmk.cli.generate.compilation_database', |
| 52 | 'qmk.cli.generate.community_modules', | ||
| 52 | 'qmk.cli.generate.config_h', | 53 | 'qmk.cli.generate.config_h', |
| 53 | 'qmk.cli.generate.develop_pr_list', | 54 | 'qmk.cli.generate.develop_pr_list', |
| 54 | 'qmk.cli.generate.dfu_header', | 55 | 'qmk.cli.generate.dfu_header', |
diff --git a/lib/python/qmk/cli/format/c.py b/lib/python/qmk/cli/format/c.py index a58aef3fbc..65818155b0 100644 --- a/lib/python/qmk/cli/format/c.py +++ b/lib/python/qmk/cli/format/c.py | |||
| @@ -10,7 +10,7 @@ from qmk.path import normpath | |||
| 10 | from qmk.c_parse import c_source_files | 10 | from qmk.c_parse import c_source_files |
| 11 | 11 | ||
| 12 | c_file_suffixes = ('c', 'h', 'cpp', 'hpp') | 12 | c_file_suffixes = ('c', 'h', 'cpp', 'hpp') |
| 13 | core_dirs = ('drivers', 'quantum', 'tests', 'tmk_core', 'platforms') | 13 | core_dirs = ('drivers', 'quantum', 'tests', 'tmk_core', 'platforms', 'modules') |
| 14 | ignored = ('tmk_core/protocol/usb_hid', 'platforms/chibios/boards') | 14 | ignored = ('tmk_core/protocol/usb_hid', 'platforms/chibios/boards') |
| 15 | 15 | ||
| 16 | 16 | ||
diff --git a/lib/python/qmk/cli/format/json.py b/lib/python/qmk/cli/format/json.py index 3670294434..61f5254184 100755 --- a/lib/python/qmk/cli/format/json.py +++ b/lib/python/qmk/cli/format/json.py | |||
| @@ -9,7 +9,7 @@ from milc import cli | |||
| 9 | 9 | ||
| 10 | from qmk.info import info_json | 10 | from qmk.info import info_json |
| 11 | from qmk.json_schema import json_load, validate | 11 | from qmk.json_schema import json_load, validate |
| 12 | from qmk.json_encoders import InfoJSONEncoder, KeymapJSONEncoder, UserspaceJSONEncoder | 12 | from qmk.json_encoders import InfoJSONEncoder, KeymapJSONEncoder, UserspaceJSONEncoder, CommunityModuleJSONEncoder |
| 13 | from qmk.path import normpath | 13 | from qmk.path import normpath |
| 14 | 14 | ||
| 15 | 15 | ||
| @@ -32,6 +32,13 @@ def _detect_json_format(file, json_data): | |||
| 32 | 32 | ||
| 33 | if json_encoder is None: | 33 | if json_encoder is None: |
| 34 | try: | 34 | try: |
| 35 | validate(json_data, 'qmk.community_module.v1') | ||
| 36 | json_encoder = CommunityModuleJSONEncoder | ||
| 37 | except ValidationError: | ||
| 38 | pass | ||
| 39 | |||
| 40 | if json_encoder is None: | ||
| 41 | try: | ||
| 35 | validate(json_data, 'qmk.keyboard.v1') | 42 | validate(json_data, 'qmk.keyboard.v1') |
| 36 | json_encoder = InfoJSONEncoder | 43 | json_encoder = InfoJSONEncoder |
| 37 | except ValidationError as e: | 44 | except ValidationError as e: |
| @@ -54,6 +61,8 @@ def _get_json_encoder(file, json_data): | |||
| 54 | json_encoder = KeymapJSONEncoder | 61 | json_encoder = KeymapJSONEncoder |
| 55 | elif cli.args.format == 'userspace': | 62 | elif cli.args.format == 'userspace': |
| 56 | json_encoder = UserspaceJSONEncoder | 63 | json_encoder = UserspaceJSONEncoder |
| 64 | elif cli.args.format == 'community_module': | ||
| 65 | json_encoder = CommunityModuleJSONEncoder | ||
| 57 | else: | 66 | else: |
| 58 | # This should be impossible | 67 | # This should be impossible |
| 59 | cli.log.error('Unknown format: %s', cli.args.format) | 68 | cli.log.error('Unknown format: %s', cli.args.format) |
| @@ -61,7 +70,7 @@ def _get_json_encoder(file, json_data): | |||
| 61 | 70 | ||
| 62 | 71 | ||
| 63 | @cli.argument('json_file', arg_only=True, type=normpath, help='JSON file to format') | 72 | @cli.argument('json_file', arg_only=True, type=normpath, help='JSON file to format') |
| 64 | @cli.argument('-f', '--format', choices=['auto', 'keyboard', 'keymap', 'userspace'], default='auto', arg_only=True, help='JSON formatter to use (Default: autodetect)') | 73 | @cli.argument('-f', '--format', choices=['auto', 'keyboard', 'keymap', 'userspace', 'community_module'], default='auto', arg_only=True, help='JSON formatter to use (Default: autodetect)') |
| 65 | @cli.argument('-i', '--inplace', action='store_true', arg_only=True, help='If set, will operate in-place on the input file') | 74 | @cli.argument('-i', '--inplace', action='store_true', arg_only=True, help='If set, will operate in-place on the input file') |
| 66 | @cli.argument('-p', '--print', action='store_true', arg_only=True, help='If set, will print the formatted json to stdout ') | 75 | @cli.argument('-p', '--print', action='store_true', arg_only=True, help='If set, will print the formatted json to stdout ') |
| 67 | @cli.subcommand('Generate an info.json file for a keyboard.', hidden=False if cli.config.user.developer else True) | 76 | @cli.subcommand('Generate an info.json file for a keyboard.', hidden=False if cli.config.user.developer else True) |
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..23678a2fb5 --- /dev/null +++ b/lib/python/qmk/cli/generate/community_modules.py | |||
| @@ -0,0 +1,263 @@ | |||
| 1 | import contextlib | ||
| 2 | from argcomplete.completers import FilesCompleter | ||
| 3 | from pathlib import Path | ||
| 4 | |||
| 5 | from milc import cli | ||
| 6 | |||
| 7 | import qmk.path | ||
| 8 | from qmk.info import get_modules | ||
| 9 | from qmk.keyboard import keyboard_completer, keyboard_folder | ||
| 10 | from qmk.commands import dump_lines | ||
| 11 | from qmk.constants import GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE | ||
| 12 | from qmk.community_modules import module_api_list, load_module_jsons, find_module_path | ||
| 13 | |||
| 14 | |||
| 15 | @contextlib.contextmanager | ||
| 16 | def _render_api_guard(lines, api): | ||
| 17 | if api.guard: | ||
| 18 | lines.append(f'#if {api.guard}') | ||
| 19 | yield | ||
| 20 | if api.guard: | ||
| 21 | lines.append(f'#endif // {api.guard}') | ||
| 22 | |||
| 23 | |||
| 24 | def _render_api_header(api): | ||
| 25 | lines = [] | ||
| 26 | if api.header: | ||
| 27 | lines.append('') | ||
| 28 | with _render_api_guard(lines, api): | ||
| 29 | lines.append(f'#include <{api.header}>') | ||
| 30 | return lines | ||
| 31 | |||
| 32 | |||
| 33 | def _render_keycodes(module_jsons): | ||
| 34 | lines = [] | ||
| 35 | lines.append('') | ||
| 36 | lines.append('enum {') | ||
| 37 | first = True | ||
| 38 | for module_json in module_jsons: | ||
| 39 | module_name = Path(module_json['module']).name | ||
| 40 | keycodes = module_json.get('keycodes', []) | ||
| 41 | if len(keycodes) > 0: | ||
| 42 | lines.append(f' // From module: {module_name}') | ||
| 43 | for keycode in keycodes: | ||
| 44 | key = keycode.get('key', None) | ||
| 45 | if first: | ||
| 46 | lines.append(f' {key} = QK_COMMUNITY_MODULE,') | ||
| 47 | first = False | ||
| 48 | else: | ||
| 49 | lines.append(f' {key},') | ||
| 50 | for alias in keycode.get('aliases', []): | ||
| 51 | lines.append(f' {alias} = {key},') | ||
| 52 | lines.append('') | ||
| 53 | lines.append(' LAST_COMMUNITY_MODULE_KEY') | ||
| 54 | lines.append('};') | ||
| 55 | lines.append('_Static_assert((int)LAST_COMMUNITY_MODULE_KEY <= (int)(QK_COMMUNITY_MODULE_MAX+1), "Too many community module keycodes");') | ||
| 56 | return lines | ||
| 57 | |||
| 58 | |||
| 59 | def _render_api_declarations(api, module, user_kb=True): | ||
| 60 | lines = [] | ||
| 61 | lines.append('') | ||
| 62 | with _render_api_guard(lines, api): | ||
| 63 | if user_kb: | ||
| 64 | lines.append(f'{api.ret_type} {api.name}_{module}_user({api.args});') | ||
| 65 | lines.append(f'{api.ret_type} {api.name}_{module}_kb({api.args});') | ||
| 66 | lines.append(f'{api.ret_type} {api.name}_{module}({api.args});') | ||
| 67 | return lines | ||
| 68 | |||
| 69 | |||
| 70 | def _render_api_implementations(api, module): | ||
| 71 | module_name = Path(module).name | ||
| 72 | lines = [] | ||
| 73 | lines.append('') | ||
| 74 | with _render_api_guard(lines, api): | ||
| 75 | # _user | ||
| 76 | lines.append(f'__attribute__((weak)) {api.ret_type} {api.name}_{module_name}_user({api.args}) {{') | ||
| 77 | if api.ret_type == 'bool': | ||
| 78 | lines.append(' return true;') | ||
| 79 | else: | ||
| 80 | pass | ||
| 81 | lines.append('}') | ||
| 82 | lines.append('') | ||
| 83 | |||
| 84 | # _kb | ||
| 85 | lines.append(f'__attribute__((weak)) {api.ret_type} {api.name}_{module_name}_kb({api.args}) {{') | ||
| 86 | if api.ret_type == 'bool': | ||
| 87 | lines.append(f' if(!{api.name}_{module_name}_user({api.call_params})) {{ return false; }}') | ||
| 88 | lines.append(' return true;') | ||
| 89 | else: | ||
| 90 | lines.append(f' {api.name}_{module_name}_user({api.call_params});') | ||
| 91 | lines.append('}') | ||
| 92 | lines.append('') | ||
| 93 | |||
| 94 | # module (non-suffixed) | ||
| 95 | lines.append(f'__attribute__((weak)) {api.ret_type} {api.name}_{module_name}({api.args}) {{') | ||
| 96 | if api.ret_type == 'bool': | ||
| 97 | lines.append(f' if(!{api.name}_{module_name}_kb({api.call_params})) {{ return false; }}') | ||
| 98 | lines.append(' return true;') | ||
| 99 | else: | ||
| 100 | lines.append(f' {api.name}_{module_name}_kb({api.call_params});') | ||
| 101 | lines.append('}') | ||
| 102 | return lines | ||
| 103 | |||
| 104 | |||
| 105 | def _render_core_implementation(api, modules): | ||
| 106 | lines = [] | ||
| 107 | lines.append('') | ||
| 108 | with _render_api_guard(lines, api): | ||
| 109 | lines.append(f'{api.ret_type} {api.name}_modules({api.args}) {{') | ||
| 110 | if api.ret_type == 'bool': | ||
| 111 | lines.append(' return true') | ||
| 112 | for module in modules: | ||
| 113 | module_name = Path(module).name | ||
| 114 | if api.ret_type == 'bool': | ||
| 115 | lines.append(f' && {api.name}_{module_name}({api.call_params})') | ||
| 116 | else: | ||
| 117 | lines.append(f' {api.name}_{module_name}({api.call_params});') | ||
| 118 | if api.ret_type == 'bool': | ||
| 119 | lines.append(' ;') | ||
| 120 | lines.append('}') | ||
| 121 | return lines | ||
| 122 | |||
| 123 | |||
| 124 | @cli.argument('-o', '--output', arg_only=True, type=qmk.path.normpath, help='File to write to') | ||
| 125 | @cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages") | ||
| 126 | @cli.argument('-kb', '--keyboard', arg_only=True, type=keyboard_folder, completer=keyboard_completer, help='Keyboard to generate community_modules.h for.') | ||
| 127 | @cli.argument('filename', nargs='?', type=qmk.path.FileType('r'), arg_only=True, completer=FilesCompleter('.json'), help='Configurator JSON file') | ||
| 128 | @cli.subcommand('Creates a community_modules.h from a keymap.json file.') | ||
| 129 | def generate_community_modules_h(cli): | ||
| 130 | """Creates a community_modules.h from a keymap.json file | ||
| 131 | """ | ||
| 132 | if cli.args.output and cli.args.output.name == '-': | ||
| 133 | cli.args.output = None | ||
| 134 | |||
| 135 | api_list, api_version, ver_major, ver_minor, ver_patch = module_api_list() | ||
| 136 | |||
| 137 | lines = [ | ||
| 138 | GPL2_HEADER_C_LIKE, | ||
| 139 | GENERATED_HEADER_C_LIKE, | ||
| 140 | '#pragma once', | ||
| 141 | '#include <stdint.h>', | ||
| 142 | '#include <stdbool.h>', | ||
| 143 | '#include <keycodes.h>', | ||
| 144 | '', | ||
| 145 | '#define COMMUNITY_MODULES_API_VERSION_BUILDER(ver_major,ver_minor,ver_patch) (((((uint32_t)(ver_major))&0xFF) << 24) | ((((uint32_t)(ver_minor))&0xFF) << 16) | (((uint32_t)(ver_patch))&0xFF))', | ||
| 146 | f'#define COMMUNITY_MODULES_API_VERSION COMMUNITY_MODULES_API_VERSION_BUILDER({ver_major},{ver_minor},{ver_patch})', | ||
| 147 | f'#define ASSERT_COMMUNITY_MODULES_MIN_API_VERSION(ver_major,ver_minor,ver_patch) _Static_assert(COMMUNITY_MODULES_API_VERSION_BUILDER(ver_major,ver_minor,ver_patch) <= COMMUNITY_MODULES_API_VERSION, "Community module requires a newer version of QMK modules API -- needs: " #ver_major "." #ver_minor "." #ver_patch ", current: {api_version}.")', | ||
| 148 | '', | ||
| 149 | 'typedef struct keyrecord_t keyrecord_t; // forward declaration so we don\'t need to include quantum.h', | ||
| 150 | '', | ||
| 151 | ] | ||
| 152 | |||
| 153 | modules = get_modules(cli.args.keyboard, cli.args.filename) | ||
| 154 | module_jsons = load_module_jsons(modules) | ||
| 155 | if len(modules) > 0: | ||
| 156 | lines.extend(_render_keycodes(module_jsons)) | ||
| 157 | |||
| 158 | for api in api_list: | ||
| 159 | lines.extend(_render_api_header(api)) | ||
| 160 | |||
| 161 | for module in modules: | ||
| 162 | lines.append('') | ||
| 163 | lines.append(f'// From module: {module}') | ||
| 164 | for api in api_list: | ||
| 165 | lines.extend(_render_api_declarations(api, Path(module).name)) | ||
| 166 | lines.append('') | ||
| 167 | |||
| 168 | lines.append('// Core wrapper') | ||
| 169 | for api in api_list: | ||
| 170 | lines.extend(_render_api_declarations(api, 'modules', user_kb=False)) | ||
| 171 | |||
| 172 | dump_lines(cli.args.output, lines, cli.args.quiet, remove_repeated_newlines=True) | ||
| 173 | |||
| 174 | |||
| 175 | @cli.argument('-o', '--output', arg_only=True, type=qmk.path.normpath, help='File to write to') | ||
| 176 | @cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages") | ||
| 177 | @cli.argument('-kb', '--keyboard', arg_only=True, type=keyboard_folder, completer=keyboard_completer, help='Keyboard to generate community_modules.c for.') | ||
| 178 | @cli.argument('filename', nargs='?', type=qmk.path.FileType('r'), arg_only=True, completer=FilesCompleter('.json'), help='Configurator JSON file') | ||
| 179 | @cli.subcommand('Creates a community_modules.c from a keymap.json file.') | ||
| 180 | def generate_community_modules_c(cli): | ||
| 181 | """Creates a community_modules.c from a keymap.json file | ||
| 182 | """ | ||
| 183 | if cli.args.output and cli.args.output.name == '-': | ||
| 184 | cli.args.output = None | ||
| 185 | |||
| 186 | api_list, _, _, _, _ = module_api_list() | ||
| 187 | |||
| 188 | lines = [ | ||
| 189 | GPL2_HEADER_C_LIKE, | ||
| 190 | GENERATED_HEADER_C_LIKE, | ||
| 191 | '', | ||
| 192 | '#include "community_modules.h"', | ||
| 193 | ] | ||
| 194 | |||
| 195 | modules = get_modules(cli.args.keyboard, cli.args.filename) | ||
| 196 | if len(modules) > 0: | ||
| 197 | |||
| 198 | for module in modules: | ||
| 199 | for api in api_list: | ||
| 200 | lines.extend(_render_api_implementations(api, Path(module).name)) | ||
| 201 | |||
| 202 | for api in api_list: | ||
| 203 | lines.extend(_render_core_implementation(api, modules)) | ||
| 204 | |||
| 205 | dump_lines(cli.args.output, lines, cli.args.quiet, remove_repeated_newlines=True) | ||
| 206 | |||
| 207 | |||
| 208 | @cli.argument('-o', '--output', arg_only=True, type=qmk.path.normpath, help='File to write to') | ||
| 209 | @cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages") | ||
| 210 | @cli.argument('-kb', '--keyboard', arg_only=True, type=keyboard_folder, completer=keyboard_completer, help='Keyboard to generate community_modules.c for.') | ||
| 211 | @cli.argument('filename', nargs='?', type=qmk.path.FileType('r'), arg_only=True, completer=FilesCompleter('.json'), help='Configurator JSON file') | ||
| 212 | @cli.subcommand('Creates a community_modules_introspection.h from a keymap.json file.') | ||
| 213 | def generate_community_modules_introspection_h(cli): | ||
| 214 | """Creates a community_modules_introspection.h from a keymap.json file | ||
| 215 | """ | ||
| 216 | if cli.args.output and cli.args.output.name == '-': | ||
| 217 | cli.args.output = None | ||
| 218 | |||
| 219 | lines = [ | ||
| 220 | GPL2_HEADER_C_LIKE, | ||
| 221 | GENERATED_HEADER_C_LIKE, | ||
| 222 | '', | ||
| 223 | ] | ||
| 224 | |||
| 225 | modules = get_modules(cli.args.keyboard, cli.args.filename) | ||
| 226 | if len(modules) > 0: | ||
| 227 | for module in modules: | ||
| 228 | module_path = find_module_path(module) | ||
| 229 | lines.append(f'#if __has_include("{module_path}/introspection.h")') | ||
| 230 | lines.append(f'#include "{module_path}/introspection.h"') | ||
| 231 | lines.append(f'#endif // __has_include("{module_path}/introspection.h")') | ||
| 232 | lines.append('') | ||
| 233 | |||
| 234 | dump_lines(cli.args.output, lines, cli.args.quiet, remove_repeated_newlines=True) | ||
| 235 | |||
| 236 | |||
| 237 | @cli.argument('-o', '--output', arg_only=True, type=qmk.path.normpath, help='File to write to') | ||
| 238 | @cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages") | ||
| 239 | @cli.argument('-kb', '--keyboard', arg_only=True, type=keyboard_folder, completer=keyboard_completer, help='Keyboard to generate community_modules.c for.') | ||
| 240 | @cli.argument('filename', nargs='?', type=qmk.path.FileType('r'), arg_only=True, completer=FilesCompleter('.json'), help='Configurator JSON file') | ||
| 241 | @cli.subcommand('Creates a community_modules_introspection.c from a keymap.json file.') | ||
| 242 | def generate_community_modules_introspection_c(cli): | ||
| 243 | """Creates a community_modules_introspection.c from a keymap.json file | ||
| 244 | """ | ||
| 245 | if cli.args.output and cli.args.output.name == '-': | ||
| 246 | cli.args.output = None | ||
| 247 | |||
| 248 | lines = [ | ||
| 249 | GPL2_HEADER_C_LIKE, | ||
| 250 | GENERATED_HEADER_C_LIKE, | ||
| 251 | '', | ||
| 252 | ] | ||
| 253 | |||
| 254 | modules = get_modules(cli.args.keyboard, cli.args.filename) | ||
| 255 | if len(modules) > 0: | ||
| 256 | for module in modules: | ||
| 257 | module_path = find_module_path(module) | ||
| 258 | lines.append(f'#if __has_include("{module_path}/introspection.c")') | ||
| 259 | lines.append(f'#include "{module_path}/introspection.c"') | ||
| 260 | lines.append(f'#endif // __has_include("{module_path}/introspection.c")') | ||
| 261 | lines.append('') | ||
| 262 | |||
| 263 | 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 5291556109..cae9b07c3e 100755 --- a/lib/python/qmk/cli/generate/rules_mk.py +++ b/lib/python/qmk/cli/generate/rules_mk.py | |||
| @@ -6,12 +6,13 @@ from dotty_dict import dotty | |||
| 6 | from argcomplete.completers import FilesCompleter | 6 | from argcomplete.completers import FilesCompleter |
| 7 | from milc import cli | 7 | from milc import cli |
| 8 | 8 | ||
| 9 | from qmk.info import info_json | 9 | from qmk.info import info_json, get_modules |
| 10 | from qmk.json_schema import json_load | 10 | from qmk.json_schema import json_load |
| 11 | from qmk.keyboard import keyboard_completer, keyboard_folder | 11 | from qmk.keyboard import keyboard_completer, keyboard_folder |
| 12 | from qmk.commands import dump_lines, parse_configurator_json | 12 | from qmk.commands import dump_lines, parse_configurator_json |
| 13 | from qmk.path import normpath, FileType | 13 | from qmk.path import normpath, FileType |
| 14 | from qmk.constants import GPL2_HEADER_SH_LIKE, GENERATED_HEADER_SH_LIKE | 14 | from qmk.constants import GPL2_HEADER_SH_LIKE, GENERATED_HEADER_SH_LIKE |
| 15 | from qmk.community_modules import find_module_path, load_module_jsons | ||
| 15 | 16 | ||
| 16 | 17 | ||
| 17 | def generate_rule(rules_key, rules_value): | 18 | def generate_rule(rules_key, rules_value): |
| @@ -46,6 +47,42 @@ def process_mapping_rule(kb_info_json, rules_key, info_dict): | |||
| 46 | return generate_rule(rules_key, rules_value) | 47 | return generate_rule(rules_key, rules_value) |
| 47 | 48 | ||
| 48 | 49 | ||
| 50 | def generate_features_rules(features_dict): | ||
| 51 | lines = [] | ||
| 52 | for feature, enabled in features_dict.items(): | ||
| 53 | feature = feature.upper() | ||
| 54 | enabled = 'yes' if enabled else 'no' | ||
| 55 | lines.append(generate_rule(f'{feature}_ENABLE', enabled)) | ||
| 56 | return lines | ||
| 57 | |||
| 58 | |||
| 59 | def generate_modules_rules(keyboard, filename): | ||
| 60 | lines = [] | ||
| 61 | modules = get_modules(keyboard, filename) | ||
| 62 | if len(modules) > 0: | ||
| 63 | lines.append('') | ||
| 64 | lines.append('OPT_DEFS += -DCOMMUNITY_MODULES_ENABLE=TRUE') | ||
| 65 | for module in modules: | ||
| 66 | module_path = find_module_path(module) | ||
| 67 | if not module_path: | ||
| 68 | raise FileNotFoundError(f"Module '{module}' not found.") | ||
| 69 | lines.append('') | ||
| 70 | lines.append(f'COMMUNITY_MODULES += {module_path.name}') # use module_path here instead of module as it may be a subdirectory | ||
| 71 | lines.append(f'OPT_DEFS += -DCOMMUNITY_MODULE_{module_path.name.upper()}_ENABLE=TRUE') | ||
| 72 | lines.append(f'COMMUNITY_MODULE_PATHS += {module_path}') | ||
| 73 | lines.append(f'VPATH += {module_path}') | ||
| 74 | lines.append(f'SRC += $(wildcard {module_path}/{module_path.name}.c)') | ||
| 75 | lines.append(f'-include {module_path}/rules.mk') | ||
| 76 | |||
| 77 | module_jsons = load_module_jsons(modules) | ||
| 78 | for module_json in module_jsons: | ||
| 79 | if 'features' in module_json: | ||
| 80 | lines.append('') | ||
| 81 | lines.append(f'# Module: {module_json["module_name"]}') | ||
| 82 | lines.extend(generate_features_rules(module_json['features'])) | ||
| 83 | return lines | ||
| 84 | |||
| 85 | |||
| 49 | @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.') | 86 | @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.') |
| 50 | @cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to') | 87 | @cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to') |
| 51 | @cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages") | 88 | @cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages") |
| @@ -80,10 +117,7 @@ def generate_rules_mk(cli): | |||
| 80 | 117 | ||
| 81 | # Iterate through features to enable/disable them | 118 | # Iterate through features to enable/disable them |
| 82 | if 'features' in kb_info_json: | 119 | if 'features' in kb_info_json: |
| 83 | for feature, enabled in kb_info_json['features'].items(): | 120 | rules_mk_lines.extend(generate_features_rules(kb_info_json['features'])) |
| 84 | feature = feature.upper() | ||
| 85 | enabled = 'yes' if enabled else 'no' | ||
| 86 | rules_mk_lines.append(generate_rule(f'{feature}_ENABLE', enabled)) | ||
| 87 | 121 | ||
| 88 | # Set SPLIT_TRANSPORT, if needed | 122 | # Set SPLIT_TRANSPORT, if needed |
| 89 | if kb_info_json.get('split', {}).get('transport', {}).get('protocol') == 'custom': | 123 | if kb_info_json.get('split', {}).get('transport', {}).get('protocol') == 'custom': |
| @@ -99,6 +133,8 @@ def generate_rules_mk(cli): | |||
| 99 | if converter: | 133 | if converter: |
| 100 | rules_mk_lines.append(generate_rule('CONVERT_TO', converter)) | 134 | rules_mk_lines.append(generate_rule('CONVERT_TO', converter)) |
| 101 | 135 | ||
| 136 | rules_mk_lines.extend(generate_modules_rules(cli.args.keyboard, cli.args.filename)) | ||
| 137 | |||
| 102 | # Show the results | 138 | # Show the results |
| 103 | dump_lines(cli.args.output, rules_mk_lines) | 139 | dump_lines(cli.args.output, rules_mk_lines) |
| 104 | 140 | ||
diff --git a/lib/python/qmk/cli/info.py b/lib/python/qmk/cli/info.py index e662407474..5925b57258 100755 --- a/lib/python/qmk/cli/info.py +++ b/lib/python/qmk/cli/info.py | |||
| @@ -52,6 +52,11 @@ def show_keymap(kb_info_json, title_caps=True): | |||
| 52 | 52 | ||
| 53 | if keymap_path and keymap_path.suffix == '.json': | 53 | if keymap_path and keymap_path.suffix == '.json': |
| 54 | keymap_data = json.load(keymap_path.open(encoding='utf-8')) | 54 | keymap_data = json.load(keymap_path.open(encoding='utf-8')) |
| 55 | |||
| 56 | # cater for layout-less keymap.json | ||
| 57 | if 'layout' not in keymap_data: | ||
| 58 | return | ||
| 59 | |||
| 55 | layout_name = keymap_data['layout'] | 60 | layout_name = keymap_data['layout'] |
| 56 | layout_name = kb_info_json.get('layout_aliases', {}).get(layout_name, layout_name) # Resolve alias names | 61 | layout_name = kb_info_json.get('layout_aliases', {}).get(layout_name, layout_name) # Resolve alias names |
| 57 | 62 | ||
diff --git a/lib/python/qmk/commands.py b/lib/python/qmk/commands.py index a05b3641b5..0e1876ca7a 100644 --- a/lib/python/qmk/commands.py +++ b/lib/python/qmk/commands.py | |||
| @@ -98,11 +98,14 @@ def in_virtualenv(): | |||
| 98 | return active_prefix != sys.prefix | 98 | return active_prefix != sys.prefix |
| 99 | 99 | ||
| 100 | 100 | ||
| 101 | def dump_lines(output_file, lines, quiet=True): | 101 | def dump_lines(output_file, lines, quiet=True, remove_repeated_newlines=False): |
| 102 | """Handle dumping to stdout or file | 102 | """Handle dumping to stdout or file |
| 103 | Creates parent folders if required | 103 | Creates parent folders if required |
| 104 | """ | 104 | """ |
| 105 | generated = '\n'.join(lines) + '\n' | 105 | generated = '\n'.join(lines) + '\n' |
| 106 | if remove_repeated_newlines: | ||
| 107 | while '\n\n\n' in generated: | ||
| 108 | generated = generated.replace('\n\n\n', '\n\n') | ||
| 106 | if output_file and output_file.name != '-': | 109 | if output_file and output_file.name != '-': |
| 107 | output_file.parent.mkdir(parents=True, exist_ok=True) | 110 | output_file.parent.mkdir(parents=True, exist_ok=True) |
| 108 | if output_file.exists(): | 111 | if output_file.exists(): |
diff --git a/lib/python/qmk/community_modules.py b/lib/python/qmk/community_modules.py new file mode 100644 index 0000000000..f7e96a6b93 --- /dev/null +++ b/lib/python/qmk/community_modules.py | |||
| @@ -0,0 +1,100 @@ | |||
| 1 | import os | ||
| 2 | |||
| 3 | from pathlib import Path | ||
| 4 | from functools import lru_cache | ||
| 5 | |||
| 6 | from milc.attrdict import AttrDict | ||
| 7 | |||
| 8 | from qmk.json_schema import json_load, validate, merge_ordered_dicts | ||
| 9 | from qmk.util import truthy | ||
| 10 | from qmk.constants import QMK_FIRMWARE, QMK_USERSPACE, HAS_QMK_USERSPACE | ||
| 11 | from qmk.path import under_qmk_firmware, under_qmk_userspace | ||
| 12 | |||
| 13 | COMMUNITY_MODULE_JSON_FILENAME = 'qmk_module.json' | ||
| 14 | |||
| 15 | |||
| 16 | class ModuleAPI(AttrDict): | ||
| 17 | def __init__(self, **kwargs): | ||
| 18 | super().__init__() | ||
| 19 | for key, value in kwargs.items(): | ||
| 20 | self[key] = value | ||
| 21 | |||
| 22 | |||
| 23 | @lru_cache(maxsize=1) | ||
| 24 | def module_api_list(): | ||
| 25 | module_definition_files = sorted(set(QMK_FIRMWARE.glob('data/constants/module_hooks/*.hjson'))) | ||
| 26 | module_definition_jsons = [json_load(f) for f in module_definition_files] | ||
| 27 | module_definitions = merge_ordered_dicts(module_definition_jsons) | ||
| 28 | latest_module_version = module_definition_files[-1].stem | ||
| 29 | latest_module_version_parts = latest_module_version.split('.') | ||
| 30 | |||
| 31 | api_list = [] | ||
| 32 | for name, mod in module_definitions.items(): | ||
| 33 | api_list.append(ModuleAPI( | ||
| 34 | ret_type=mod['ret_type'], | ||
| 35 | name=name, | ||
| 36 | args=mod['args'], | ||
| 37 | call_params=mod.get('call_params', ''), | ||
| 38 | guard=mod.get('guard', None), | ||
| 39 | header=mod.get('header', None), | ||
| 40 | )) | ||
| 41 | |||
| 42 | return api_list, latest_module_version, latest_module_version_parts[0], latest_module_version_parts[1], latest_module_version_parts[2] | ||
| 43 | |||
| 44 | |||
| 45 | def find_available_module_paths(): | ||
| 46 | """Find all available modules. | ||
| 47 | """ | ||
| 48 | search_dirs = [] | ||
| 49 | if HAS_QMK_USERSPACE: | ||
| 50 | search_dirs.append(QMK_USERSPACE / 'modules') | ||
| 51 | search_dirs.append(QMK_FIRMWARE / 'modules') | ||
| 52 | |||
| 53 | modules = [] | ||
| 54 | for search_dir in search_dirs: | ||
| 55 | for module_json_path in search_dir.rglob(COMMUNITY_MODULE_JSON_FILENAME): | ||
| 56 | modules.append(module_json_path.parent) | ||
| 57 | return modules | ||
| 58 | |||
| 59 | |||
| 60 | def find_module_path(module): | ||
| 61 | """Find a module by name. | ||
| 62 | """ | ||
| 63 | for module_path in find_available_module_paths(): | ||
| 64 | # Ensure the module directory is under QMK Firmware or QMK Userspace | ||
| 65 | relative_path = under_qmk_firmware(module_path) | ||
| 66 | if not relative_path: | ||
| 67 | relative_path = under_qmk_userspace(module_path) | ||
| 68 | if not relative_path: | ||
| 69 | continue | ||
| 70 | |||
| 71 | lhs = str(relative_path.as_posix())[len('modules/'):] | ||
| 72 | rhs = str(Path(module).as_posix()) | ||
| 73 | |||
| 74 | if relative_path and lhs == rhs: | ||
| 75 | return module_path | ||
| 76 | return None | ||
| 77 | |||
| 78 | |||
| 79 | def load_module_json(module): | ||
| 80 | """Load a module JSON file. | ||
| 81 | """ | ||
| 82 | module_path = find_module_path(module) | ||
| 83 | if not module_path: | ||
| 84 | raise FileNotFoundError(f'Module not found: {module}') | ||
| 85 | |||
| 86 | module_json = json_load(module_path / COMMUNITY_MODULE_JSON_FILENAME) | ||
| 87 | |||
| 88 | if not truthy(os.environ.get('SKIP_SCHEMA_VALIDATION'), False): | ||
| 89 | validate(module_json, 'qmk.community_module.v1') | ||
| 90 | |||
| 91 | module_json['module'] = module | ||
| 92 | module_json['module_path'] = module_path | ||
| 93 | |||
| 94 | return module_json | ||
| 95 | |||
| 96 | |||
| 97 | def load_module_jsons(modules): | ||
| 98 | """Load the module JSON files, matching the specified order. | ||
| 99 | """ | ||
| 100 | return list(map(load_module_json, modules)) | ||
diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py index d70e7ee1b3..93eba7376a 100644 --- a/lib/python/qmk/info.py +++ b/lib/python/qmk/info.py | |||
| @@ -1059,3 +1059,30 @@ def keymap_json(keyboard, keymap, force_layout=None): | |||
| 1059 | _extract_config_h(kb_info_json, parse_config_h_file(keymap_config)) | 1059 | _extract_config_h(kb_info_json, parse_config_h_file(keymap_config)) |
| 1060 | 1060 | ||
| 1061 | return kb_info_json | 1061 | return kb_info_json |
| 1062 | |||
| 1063 | |||
| 1064 | def get_modules(keyboard, keymap_filename): | ||
| 1065 | """Get the modules for a keyboard/keymap. | ||
| 1066 | """ | ||
| 1067 | modules = [] | ||
| 1068 | |||
| 1069 | if keymap_filename: | ||
| 1070 | keymap_json = parse_configurator_json(keymap_filename) | ||
| 1071 | |||
| 1072 | if keymap_json: | ||
| 1073 | kb = keymap_json.get('keyboard', None) | ||
| 1074 | if not kb: | ||
| 1075 | kb = keyboard | ||
| 1076 | |||
| 1077 | if kb: | ||
| 1078 | kb_info_json = info_json(kb) | ||
| 1079 | if kb_info_json: | ||
| 1080 | modules.extend(kb_info_json.get('modules', [])) | ||
| 1081 | |||
| 1082 | modules.extend(keymap_json.get('modules', [])) | ||
| 1083 | |||
| 1084 | elif keyboard: | ||
| 1085 | kb_info_json = info_json(keyboard) | ||
| 1086 | modules.extend(kb_info_json.get('modules', [])) | ||
| 1087 | |||
| 1088 | return list(dict.fromkeys(modules)) # remove dupes | ||
diff --git a/lib/python/qmk/json_encoders.py b/lib/python/qmk/json_encoders.py index 0e4ad1d220..e83a381d52 100755 --- a/lib/python/qmk/json_encoders.py +++ b/lib/python/qmk/json_encoders.py | |||
| @@ -235,3 +235,31 @@ class UserspaceJSONEncoder(QMKJSONEncoder): | |||
| 235 | return '01build_targets' | 235 | return '01build_targets' |
| 236 | 236 | ||
| 237 | return key | 237 | return key |
| 238 | |||
| 239 | |||
| 240 | class CommunityModuleJSONEncoder(QMKJSONEncoder): | ||
| 241 | """Custom encoder to make qmk_module.json's a little nicer to work with. | ||
| 242 | """ | ||
| 243 | def sort_dict(self, item): | ||
| 244 | """Sorts the hashes in a nice way. | ||
| 245 | """ | ||
| 246 | key = item[0] | ||
| 247 | |||
| 248 | if self.indentation_level == 1: | ||
| 249 | if key == 'module_name': | ||
| 250 | return '00module_name' | ||
| 251 | if key == 'maintainer': | ||
| 252 | return '01maintainer' | ||
| 253 | if key == 'url': | ||
| 254 | return '02url' | ||
| 255 | if key == 'features': | ||
| 256 | return '03features' | ||
| 257 | if key == 'keycodes': | ||
| 258 | return '04keycodes' | ||
| 259 | elif self.indentation_level == 3: # keycodes | ||
| 260 | if key == 'key': | ||
| 261 | return '00key' | ||
| 262 | if key == 'aliases': | ||
| 263 | return '01aliases' | ||
| 264 | |||
| 265 | return key | ||
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): | |||
| 334 | return write_file(keymap_file, keymap_content) | 334 | return write_file(keymap_file, keymap_content) |
| 335 | 335 | ||
| 336 | 336 | ||
| 337 | def write(keymap_json): | ||
| 338 | """Generate the `keymap.c` and write it to disk. | ||
| 339 | |||
| 340 | Returns the filename written to. | ||
| 341 | |||
| 342 | `keymap_json` should be a dict with the following keys: | ||
| 343 | keyboard | ||
| 344 | The name of the keyboard | ||
| 345 | |||
| 346 | keymap | ||
| 347 | The name of the keymap | ||
| 348 | |||
| 349 | layout | ||
| 350 | The LAYOUT macro this keymap uses. | ||
| 351 | |||
| 352 | layers | ||
| 353 | An array of arrays describing the keymap. Each item in the inner array should be a string that is a valid QMK keycode. | ||
| 354 | |||
| 355 | macros | ||
| 356 | A list of macros for this keymap. | ||
| 357 | """ | ||
| 358 | keymap_content = generate_c(keymap_json) | ||
| 359 | keymap_file = qmk.path.keymaps(keymap_json['keyboard'])[0] / keymap_json['keymap'] / 'keymap.c' | ||
| 360 | |||
| 361 | return write_file(keymap_file, keymap_content) | ||
| 362 | |||
| 363 | |||
| 364 | def locate_keymap(keyboard, keymap, force_layout=None): | 337 | def locate_keymap(keyboard, keymap, force_layout=None): |
| 365 | """Returns the path to a keymap for a specific keyboard. | 338 | """Returns the path to a keymap for a specific keyboard. |
| 366 | """ | 339 | """ |
diff --git a/modules/qmk/hello_world/hello_world.c b/modules/qmk/hello_world/hello_world.c new file mode 100644 index 0000000000..d9dd366100 --- /dev/null +++ b/modules/qmk/hello_world/hello_world.c | |||
| @@ -0,0 +1,33 @@ | |||
| 1 | // Copyright 2025 Nick Brassel (@tzarc) | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | #include QMK_KEYBOARD_H | ||
| 4 | |||
| 5 | #include "introspection.h" | ||
| 6 | |||
| 7 | ASSERT_COMMUNITY_MODULES_MIN_API_VERSION(1, 0, 0); | ||
| 8 | |||
| 9 | uint32_t delayed_hello_world(uint32_t trigger_time, void *cb_arg) { | ||
| 10 | printf("Hello, world! I'm a QMK based keyboard! The keymap array size is %d bytes.\n", (int)hello_world_introspection().total_size); | ||
| 11 | return 0; | ||
| 12 | } | ||
| 13 | |||
| 14 | void keyboard_post_init_hello_world(void) { | ||
| 15 | keyboard_post_init_hello_world_kb(); | ||
| 16 | defer_exec(10000, delayed_hello_world, NULL); | ||
| 17 | } | ||
| 18 | |||
| 19 | bool process_record_hello_world(uint16_t keycode, keyrecord_t *record) { | ||
| 20 | if (!process_record_hello_world_kb(keycode, record)) { | ||
| 21 | return false; | ||
| 22 | } | ||
| 23 | |||
| 24 | switch (keycode) { | ||
| 25 | case COMMUNITY_MODULE_HELLO: | ||
| 26 | if (record->event.pressed) { | ||
| 27 | SEND_STRING("Hello there."); | ||
| 28 | break; | ||
| 29 | } | ||
| 30 | } | ||
| 31 | |||
| 32 | return true; | ||
| 33 | } | ||
diff --git a/modules/qmk/hello_world/introspection.c b/modules/qmk/hello_world/introspection.c new file mode 100644 index 0000000000..2c32a074f5 --- /dev/null +++ b/modules/qmk/hello_world/introspection.c | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | // Copyright 2025 Nick Brassel (@tzarc) | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | hello_world_introspection_t hello_world_introspection(void) { | ||
| 5 | hello_world_introspection_t introspection = { | ||
| 6 | .total_size = sizeof(keymaps), | ||
| 7 | .layer_count = sizeof(keymaps) / sizeof(keymaps[0]), | ||
| 8 | }; | ||
| 9 | return introspection; | ||
| 10 | } | ||
diff --git a/modules/qmk/hello_world/introspection.h b/modules/qmk/hello_world/introspection.h new file mode 100644 index 0000000000..fd3d7f24a0 --- /dev/null +++ b/modules/qmk/hello_world/introspection.h | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | // Copyright 2025 Nick Brassel (@tzarc) | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | #include QMK_KEYBOARD_H | ||
| 4 | |||
| 5 | typedef struct hello_world_introspection_t { | ||
| 6 | int16_t total_size; | ||
| 7 | int16_t layer_count; | ||
| 8 | } hello_world_introspection_t; | ||
| 9 | |||
| 10 | hello_world_introspection_t hello_world_introspection(void); | ||
diff --git a/modules/qmk/hello_world/qmk_module.json b/modules/qmk/hello_world/qmk_module.json new file mode 100644 index 0000000000..4f269cb4e9 --- /dev/null +++ b/modules/qmk/hello_world/qmk_module.json | |||
| @@ -0,0 +1,13 @@ | |||
| 1 | { | ||
| 2 | "module_name": "Hello World", | ||
| 3 | "maintainer": "QMK Maintainers", | ||
| 4 | "features": { | ||
| 5 | "deferred_exec": true | ||
| 6 | }, | ||
| 7 | "keycodes": [ | ||
| 8 | { | ||
| 9 | "key": "COMMUNITY_MODULE_HELLO", | ||
| 10 | "aliases": ["CM_HELO"] | ||
| 11 | } | ||
| 12 | ] | ||
| 13 | } | ||
diff --git a/modules/qmk/hello_world/rules.mk b/modules/qmk/hello_world/rules.mk new file mode 100644 index 0000000000..91806fb1e3 --- /dev/null +++ b/modules/qmk/hello_world/rules.mk | |||
| @@ -0,0 +1,2 @@ | |||
| 1 | # Just a simple rules.mk which tests that they work from a community module. | ||
| 2 | $(shell $(QMK_BIN) hello -n "from QMK's hello world community module") | ||
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 { | |||
| 45 | } tap_t; | 45 | } tap_t; |
| 46 | 46 | ||
| 47 | /* Key event container for recording */ | 47 | /* Key event container for recording */ |
| 48 | typedef struct { | 48 | typedef struct keyrecord_t { |
| 49 | keyevent_t event; | 49 | keyevent_t event; |
| 50 | #ifndef NO_ACTION_TAPPING | 50 | #ifndef NO_ACTION_TAPPING |
| 51 | tap_t tap; | 51 | 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) { | |||
| 289 | keyboard_pre_init_user(); | 289 | keyboard_pre_init_user(); |
| 290 | } | 290 | } |
| 291 | 291 | ||
| 292 | /** \brief keyboard_pre_init_modules | ||
| 293 | * | ||
| 294 | * FIXME: needs doc | ||
| 295 | */ | ||
| 296 | __attribute__((weak)) void keyboard_pre_init_modules(void) {} | ||
| 297 | |||
| 298 | /** \brief keyboard_pre_init_quantum | ||
| 299 | * | ||
| 300 | * FIXME: needs doc | ||
| 301 | */ | ||
| 302 | void keyboard_pre_init_quantum(void) { | ||
| 303 | keyboard_pre_init_modules(); | ||
| 304 | keyboard_pre_init_kb(); | ||
| 305 | } | ||
| 306 | |||
| 292 | /** \brief keyboard_post_init_user | 307 | /** \brief keyboard_post_init_user |
| 293 | * | 308 | * |
| 294 | * FIXME: needs doc | 309 | * FIXME: needs doc |
| @@ -305,6 +320,23 @@ __attribute__((weak)) void keyboard_post_init_kb(void) { | |||
| 305 | keyboard_post_init_user(); | 320 | keyboard_post_init_user(); |
| 306 | } | 321 | } |
| 307 | 322 | ||
| 323 | /** \brief keyboard_post_init_modules | ||
| 324 | * | ||
| 325 | * FIXME: needs doc | ||
| 326 | */ | ||
| 327 | |||
| 328 | __attribute__((weak)) void keyboard_post_init_modules(void) {} | ||
| 329 | |||
| 330 | /** \brief keyboard_post_init_quantum | ||
| 331 | * | ||
| 332 | * FIXME: needs doc | ||
| 333 | */ | ||
| 334 | |||
| 335 | void keyboard_post_init_quantum(void) { | ||
| 336 | keyboard_post_init_modules(); | ||
| 337 | keyboard_post_init_kb(); | ||
| 338 | } | ||
| 339 | |||
| 308 | /** \brief matrix_can_read | 340 | /** \brief matrix_can_read |
| 309 | * | 341 | * |
| 310 | * Allows overriding when matrix scanning operations should be executed. | 342 | * Allows overriding when matrix scanning operations should be executed. |
| @@ -323,7 +355,7 @@ void keyboard_setup(void) { | |||
| 323 | eeprom_driver_init(); | 355 | eeprom_driver_init(); |
| 324 | #endif | 356 | #endif |
| 325 | matrix_setup(); | 357 | matrix_setup(); |
| 326 | keyboard_pre_init_kb(); | 358 | keyboard_pre_init_quantum(); |
| 327 | } | 359 | } |
| 328 | 360 | ||
| 329 | #ifndef SPLIT_KEYBOARD | 361 | #ifndef SPLIT_KEYBOARD |
| @@ -355,6 +387,13 @@ __attribute__((weak)) bool should_process_keypress(void) { | |||
| 355 | return is_keyboard_master(); | 387 | return is_keyboard_master(); |
| 356 | } | 388 | } |
| 357 | 389 | ||
| 390 | /** \brief housekeeping_task_modules | ||
| 391 | * | ||
| 392 | * Codegen will override this if community modules are enabled. | ||
| 393 | * This is specific to keyboard-level functionality. | ||
| 394 | */ | ||
| 395 | __attribute__((weak)) void housekeeping_task_modules(void) {} | ||
| 396 | |||
| 358 | /** \brief housekeeping_task_kb | 397 | /** \brief housekeeping_task_kb |
| 359 | * | 398 | * |
| 360 | * Override this function if you have a need to execute code for every keyboard main loop iteration. | 399 | * 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) {} | |||
| 374 | * Invokes hooks for executing code after QMK is done after each loop iteration. | 413 | * Invokes hooks for executing code after QMK is done after each loop iteration. |
| 375 | */ | 414 | */ |
| 376 | void housekeeping_task(void) { | 415 | void housekeeping_task(void) { |
| 416 | housekeeping_task_modules(); | ||
| 377 | housekeeping_task_kb(); | 417 | housekeeping_task_kb(); |
| 378 | housekeeping_task_user(); | 418 | housekeeping_task_user(); |
| 379 | } | 419 | } |
| @@ -493,7 +533,7 @@ void keyboard_init(void) { | |||
| 493 | debug_enable = true; | 533 | debug_enable = true; |
| 494 | #endif | 534 | #endif |
| 495 | 535 | ||
| 496 | keyboard_post_init_kb(); /* Always keep this last */ | 536 | keyboard_post_init_quantum(); /* Always keep this last */ |
| 497 | } | 537 | } |
| 498 | 538 | ||
| 499 | /** \brief key_event_task | 539 | /** \brief key_event_task |
diff --git a/quantum/keycodes.h b/quantum/keycodes.h index 5929e35687..b4fc38f5ff 100644 --- a/quantum/keycodes.h +++ b/quantum/keycodes.h | |||
| @@ -76,6 +76,8 @@ enum qk_keycode_ranges { | |||
| 76 | QK_MACRO_MAX = 0x777F, | 76 | QK_MACRO_MAX = 0x777F, |
| 77 | QK_CONNECTION = 0x7780, | 77 | QK_CONNECTION = 0x7780, |
| 78 | QK_CONNECTION_MAX = 0x77BF, | 78 | QK_CONNECTION_MAX = 0x77BF, |
| 79 | QK_COMMUNITY_MODULE = 0x77C0, | ||
| 80 | QK_COMMUNITY_MODULE_MAX = 0x77FF, | ||
| 79 | QK_LIGHTING = 0x7800, | 81 | QK_LIGHTING = 0x7800, |
| 80 | QK_LIGHTING_MAX = 0x78FF, | 82 | QK_LIGHTING_MAX = 0x78FF, |
| 81 | QK_QUANTUM = 0x7C00, | 83 | QK_QUANTUM = 0x7C00, |
| @@ -1476,6 +1478,7 @@ enum qk_keycode_defines { | |||
| 1476 | #define IS_QK_STENO(code) ((code) >= QK_STENO && (code) <= QK_STENO_MAX) | 1478 | #define IS_QK_STENO(code) ((code) >= QK_STENO && (code) <= QK_STENO_MAX) |
| 1477 | #define IS_QK_MACRO(code) ((code) >= QK_MACRO && (code) <= QK_MACRO_MAX) | 1479 | #define IS_QK_MACRO(code) ((code) >= QK_MACRO && (code) <= QK_MACRO_MAX) |
| 1478 | #define IS_QK_CONNECTION(code) ((code) >= QK_CONNECTION && (code) <= QK_CONNECTION_MAX) | 1480 | #define IS_QK_CONNECTION(code) ((code) >= QK_CONNECTION && (code) <= QK_CONNECTION_MAX) |
| 1481 | #define IS_QK_COMMUNITY_MODULE(code) ((code) >= QK_COMMUNITY_MODULE && (code) <= QK_COMMUNITY_MODULE_MAX) | ||
| 1479 | #define IS_QK_LIGHTING(code) ((code) >= QK_LIGHTING && (code) <= QK_LIGHTING_MAX) | 1482 | #define IS_QK_LIGHTING(code) ((code) >= QK_LIGHTING && (code) <= QK_LIGHTING_MAX) |
| 1480 | #define IS_QK_QUANTUM(code) ((code) >= QK_QUANTUM && (code) <= QK_QUANTUM_MAX) | 1483 | #define IS_QK_QUANTUM(code) ((code) >= QK_QUANTUM && (code) <= QK_QUANTUM_MAX) |
| 1481 | #define IS_QK_KB(code) ((code) >= QK_KB && (code) <= QK_KB_MAX) | 1484 | #define IS_QK_KB(code) ((code) >= QK_KB && (code) <= QK_KB_MAX) |
diff --git a/quantum/keymap_introspection.c b/quantum/keymap_introspection.c index 236b54ce98..23e842353a 100644 --- a/quantum/keymap_introspection.c +++ b/quantum/keymap_introspection.c | |||
| @@ -1,6 +1,10 @@ | |||
| 1 | // Copyright 2022 Nick Brassel (@tzarc) | 1 | // Copyright 2022 Nick Brassel (@tzarc) |
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | 2 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | 3 | ||
| 4 | #if defined(COMMUNITY_MODULES_ENABLE) | ||
| 5 | # include "community_modules_introspection.h" | ||
| 6 | #endif // defined(COMMUNITY_MODULES_ENABLE) | ||
| 7 | |||
| 4 | // Pull the actual keymap code so that we can inspect stuff from it | 8 | // Pull the actual keymap code so that we can inspect stuff from it |
| 5 | #include KEYMAP_C | 9 | #include KEYMAP_C |
| 6 | 10 | ||
| @@ -171,3 +175,10 @@ __attribute__((weak)) const key_override_t* key_override_get(uint16_t key_overri | |||
| 171 | } | 175 | } |
| 172 | 176 | ||
| 173 | #endif // defined(KEY_OVERRIDE_ENABLE) | 177 | #endif // defined(KEY_OVERRIDE_ENABLE) |
| 178 | |||
| 179 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | ||
| 180 | // Community modules (must be last in this file!) | ||
| 181 | |||
| 182 | #if defined(COMMUNITY_MODULES_ENABLE) | ||
| 183 | # include "community_modules_introspection.c" | ||
| 184 | #endif // defined(COMMUNITY_MODULES_ENABLE) | ||
diff --git a/quantum/os_detection.c b/quantum/os_detection.c index 84bbeeed54..9a9f9052f2 100644 --- a/quantum/os_detection.c +++ b/quantum/os_detection.c | |||
| @@ -72,6 +72,8 @@ static volatile struct usb_device_state maxprev_usb_device_state = {.configure_s | |||
| 72 | static volatile bool debouncing = false; | 72 | static volatile bool debouncing = false; |
| 73 | static volatile fast_timer_t last_time = 0; | 73 | static volatile fast_timer_t last_time = 0; |
| 74 | 74 | ||
| 75 | bool process_detected_host_os_modules(os_variant_t os); | ||
| 76 | |||
| 75 | void os_detection_task(void) { | 77 | void os_detection_task(void) { |
| 76 | #ifdef OS_DETECTION_KEYBOARD_RESET | 78 | #ifdef OS_DETECTION_KEYBOARD_RESET |
| 77 | // resetting the keyboard on the USB device state change callback results in instability, so delegate that to this task | 79 | // 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) { | |||
| 96 | if (detected_os != reported_os || first_report) { | 98 | if (detected_os != reported_os || first_report) { |
| 97 | first_report = false; | 99 | first_report = false; |
| 98 | reported_os = detected_os; | 100 | reported_os = detected_os; |
| 101 | process_detected_host_os_modules(detected_os); | ||
| 99 | process_detected_host_os_kb(detected_os); | 102 | process_detected_host_os_kb(detected_os); |
| 100 | } | 103 | } |
| 101 | } | 104 | } |
| 102 | } | 105 | } |
| 103 | } | 106 | } |
| 104 | 107 | ||
| 108 | __attribute__((weak)) bool process_detected_host_os_modules(os_variant_t os) { | ||
| 109 | return true; | ||
| 110 | } | ||
| 111 | |||
| 105 | __attribute__((weak)) bool process_detected_host_os_kb(os_variant_t detected_os) { | 112 | __attribute__((weak)) bool process_detected_host_os_kb(os_variant_t detected_os) { |
| 106 | return process_detected_host_os_user(detected_os); | 113 | return process_detected_host_os_user(detected_os); |
| 107 | } | 114 | } |
diff --git a/quantum/quantum.c b/quantum/quantum.c index b63300add8..adb14d64b6 100644 --- a/quantum/quantum.c +++ b/quantum/quantum.c | |||
| @@ -162,6 +162,10 @@ __attribute__((weak)) void tap_code16(uint16_t code) { | |||
| 162 | tap_code16_delay(code, code == KC_CAPS_LOCK ? TAP_HOLD_CAPS_DELAY : TAP_CODE_DELAY); | 162 | tap_code16_delay(code, code == KC_CAPS_LOCK ? TAP_HOLD_CAPS_DELAY : TAP_CODE_DELAY); |
| 163 | } | 163 | } |
| 164 | 164 | ||
| 165 | __attribute__((weak)) bool pre_process_record_modules(uint16_t keycode, keyrecord_t *record) { | ||
| 166 | return true; | ||
| 167 | } | ||
| 168 | |||
| 165 | __attribute__((weak)) bool pre_process_record_kb(uint16_t keycode, keyrecord_t *record) { | 169 | __attribute__((weak)) bool pre_process_record_kb(uint16_t keycode, keyrecord_t *record) { |
| 166 | return pre_process_record_user(keycode, record); | 170 | return pre_process_record_user(keycode, record); |
| 167 | } | 171 | } |
| @@ -174,6 +178,10 @@ __attribute__((weak)) bool process_action_kb(keyrecord_t *record) { | |||
| 174 | return true; | 178 | return true; |
| 175 | } | 179 | } |
| 176 | 180 | ||
| 181 | __attribute__((weak)) bool process_record_modules(uint16_t keycode, keyrecord_t *record) { | ||
| 182 | return true; | ||
| 183 | } | ||
| 184 | |||
| 177 | __attribute__((weak)) bool process_record_kb(uint16_t keycode, keyrecord_t *record) { | 185 | __attribute__((weak)) bool process_record_kb(uint16_t keycode, keyrecord_t *record) { |
| 178 | return process_record_user(keycode, record); | 186 | return process_record_user(keycode, record); |
| 179 | } | 187 | } |
| @@ -182,12 +190,22 @@ __attribute__((weak)) bool process_record_user(uint16_t keycode, keyrecord_t *re | |||
| 182 | return true; | 190 | return true; |
| 183 | } | 191 | } |
| 184 | 192 | ||
| 193 | __attribute__((weak)) void post_process_record_modules(uint16_t keycode, keyrecord_t *record) {} | ||
| 194 | |||
| 185 | __attribute__((weak)) void post_process_record_kb(uint16_t keycode, keyrecord_t *record) { | 195 | __attribute__((weak)) void post_process_record_kb(uint16_t keycode, keyrecord_t *record) { |
| 186 | post_process_record_user(keycode, record); | 196 | post_process_record_user(keycode, record); |
| 187 | } | 197 | } |
| 188 | 198 | ||
| 189 | __attribute__((weak)) void post_process_record_user(uint16_t keycode, keyrecord_t *record) {} | 199 | __attribute__((weak)) void post_process_record_user(uint16_t keycode, keyrecord_t *record) {} |
| 190 | 200 | ||
| 201 | __attribute__((weak)) bool shutdown_modules(bool jump_to_bootloader) { | ||
| 202 | return true; | ||
| 203 | } | ||
| 204 | |||
| 205 | __attribute__((weak)) void suspend_power_down_modules(void) {} | ||
| 206 | |||
| 207 | __attribute__((weak)) void suspend_wakeup_init_modules(void) {} | ||
| 208 | |||
| 191 | void shutdown_quantum(bool jump_to_bootloader) { | 209 | void shutdown_quantum(bool jump_to_bootloader) { |
| 192 | clear_keyboard(); | 210 | clear_keyboard(); |
| 193 | #if defined(MIDI_ENABLE) && defined(MIDI_BASIC) | 211 | #if defined(MIDI_ENABLE) && defined(MIDI_BASIC) |
| @@ -199,11 +217,13 @@ void shutdown_quantum(bool jump_to_bootloader) { | |||
| 199 | # endif | 217 | # endif |
| 200 | uint16_t timer_start = timer_read(); | 218 | uint16_t timer_start = timer_read(); |
| 201 | PLAY_SONG(goodbye_song); | 219 | PLAY_SONG(goodbye_song); |
| 220 | shutdown_modules(jump_to_bootloader); | ||
| 202 | shutdown_kb(jump_to_bootloader); | 221 | shutdown_kb(jump_to_bootloader); |
| 203 | while (timer_elapsed(timer_start) < 250) | 222 | while (timer_elapsed(timer_start) < 250) |
| 204 | wait_ms(1); | 223 | wait_ms(1); |
| 205 | stop_all_notes(); | 224 | stop_all_notes(); |
| 206 | #else | 225 | #else |
| 226 | shutdown_modules(jump_to_bootloader); | ||
| 207 | shutdown_kb(jump_to_bootloader); | 227 | shutdown_kb(jump_to_bootloader); |
| 208 | wait_ms(250); | 228 | wait_ms(250); |
| 209 | #endif | 229 | #endif |
| @@ -258,7 +278,7 @@ uint16_t get_event_keycode(keyevent_t event, bool update_layer_cache) { | |||
| 258 | 278 | ||
| 259 | /* Get keycode, and then process pre tapping functionality */ | 279 | /* Get keycode, and then process pre tapping functionality */ |
| 260 | bool pre_process_record_quantum(keyrecord_t *record) { | 280 | bool pre_process_record_quantum(keyrecord_t *record) { |
| 261 | return pre_process_record_kb(get_record_keycode(record, true), record) && | 281 | return pre_process_record_modules(get_record_keycode(record, true), record) && pre_process_record_kb(get_record_keycode(record, true), record) && |
| 262 | #ifdef COMBO_ENABLE | 282 | #ifdef COMBO_ENABLE |
| 263 | process_combo(get_record_keycode(record, true), record) && | 283 | process_combo(get_record_keycode(record, true), record) && |
| 264 | #endif | 284 | #endif |
| @@ -268,6 +288,7 @@ bool pre_process_record_quantum(keyrecord_t *record) { | |||
| 268 | /* Get keycode, and then call keyboard function */ | 288 | /* Get keycode, and then call keyboard function */ |
| 269 | void post_process_record_quantum(keyrecord_t *record) { | 289 | void post_process_record_quantum(keyrecord_t *record) { |
| 270 | uint16_t keycode = get_record_keycode(record, false); | 290 | uint16_t keycode = get_record_keycode(record, false); |
| 291 | post_process_record_modules(keycode, record); | ||
| 271 | post_process_record_kb(keycode, record); | 292 | post_process_record_kb(keycode, record); |
| 272 | } | 293 | } |
| 273 | 294 | ||
| @@ -332,6 +353,7 @@ bool process_record_quantum(keyrecord_t *record) { | |||
| 332 | #if defined(POINTING_DEVICE_ENABLE) && defined(POINTING_DEVICE_AUTO_MOUSE_ENABLE) | 353 | #if defined(POINTING_DEVICE_ENABLE) && defined(POINTING_DEVICE_AUTO_MOUSE_ENABLE) |
| 333 | process_auto_mouse(keycode, record) && | 354 | process_auto_mouse(keycode, record) && |
| 334 | #endif | 355 | #endif |
| 356 | process_record_modules(keycode, record) && // modules must run before kb | ||
| 335 | process_record_kb(keycode, record) && | 357 | process_record_kb(keycode, record) && |
| 336 | #if defined(VIA_ENABLE) | 358 | #if defined(VIA_ENABLE) |
| 337 | process_record_via(keycode, record) && | 359 | process_record_via(keycode, record) && |
| @@ -526,6 +548,7 @@ __attribute__((weak)) bool shutdown_kb(bool jump_to_bootloader) { | |||
| 526 | } | 548 | } |
| 527 | 549 | ||
| 528 | void suspend_power_down_quantum(void) { | 550 | void suspend_power_down_quantum(void) { |
| 551 | suspend_power_down_modules(); | ||
| 529 | suspend_power_down_kb(); | 552 | suspend_power_down_kb(); |
| 530 | #ifndef NO_SUSPEND_POWER_DOWN | 553 | #ifndef NO_SUSPEND_POWER_DOWN |
| 531 | // Turn off backlight | 554 | // Turn off backlight |
| @@ -593,6 +616,7 @@ __attribute__((weak)) void suspend_wakeup_init_quantum(void) { | |||
| 593 | #if defined(RGB_MATRIX_ENABLE) | 616 | #if defined(RGB_MATRIX_ENABLE) |
| 594 | rgb_matrix_set_suspend_state(false); | 617 | rgb_matrix_set_suspend_state(false); |
| 595 | #endif | 618 | #endif |
| 619 | suspend_wakeup_init_modules(); | ||
| 596 | suspend_wakeup_init_kb(); | 620 | suspend_wakeup_init_kb(); |
| 597 | } | 621 | } |
| 598 | 622 | ||
diff --git a/quantum/quantum.h b/quantum/quantum.h index 9db88a54d4..59a415ead4 100644 --- a/quantum/quantum.h +++ b/quantum/quantum.h | |||
| @@ -244,6 +244,10 @@ extern layer_state_t layer_state; | |||
| 244 | # include "layer_lock.h" | 244 | # include "layer_lock.h" |
| 245 | #endif | 245 | #endif |
| 246 | 246 | ||
| 247 | #ifdef COMMUNITY_MODULES_ENABLE | ||
| 248 | # include "community_modules.h" | ||
| 249 | #endif | ||
| 250 | |||
| 247 | void set_single_default_layer(uint8_t default_layer); | 251 | void set_single_default_layer(uint8_t default_layer); |
| 248 | void set_single_persistent_default_layer(uint8_t default_layer); | 252 | void set_single_persistent_default_layer(uint8_t default_layer); |
| 249 | 253 | ||
