Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Community modules #24848

Open
wants to merge 16 commits into
base: develop
Choose a base branch
from
Open
4 changes: 4 additions & 0 deletions .github/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,7 @@ dd:
- data/constants/**
- data/mappings/**
- data/schemas/**
community_module:
- changed-files:
- any-glob-to-any-file:
- modules/**
1 change: 1 addition & 0 deletions .github/workflows/format.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ on:
- 'lib/arm_atsam/**'
- 'lib/lib8tion/**'
- 'lib/python/**'
- 'modules/**'
- 'platforms/**'
- 'quantum/**'
- 'tests/**'
Expand Down
89 changes: 52 additions & 37 deletions builddefs/build_keyboard.mk
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -226,6 +259,22 @@ 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)

SRC += $(INTERMEDIATE_OUTPUT)/src/community_modules.c

generated-files: $(INTERMEDIATE_OUTPUT)/src/community_modules.h $(INTERMEDIATE_OUTPUT)/src/community_modules.c


include $(BUILDDEFS_PATH)/converters.mk

# Generate the board's version.h file.
Expand Down Expand Up @@ -348,40 +397,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

Expand Down
7 changes: 7 additions & 0 deletions data/constants/keycodes/keycodes_0.0.7.hjson
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"ranges": {
"0x77C0/0x003F": {
"define": "QK_COMMUNITY_MODULE"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see range QK_COMMUNITY_MODULE to QK_COMMUNITY_MODULE_MAX is reserved for modules, which is helpful. If multiple modules are used at once, and each needs a few keycodes, is there a way that each module could get part of this range without overlapping with other modules? It would be great if modules could each allocate keycodes in a standardized way, essentially, an automatic way to enumerate the range like:

enum module_keycodes {
  // Keycodes for module Foo.
  FOO_KC1 = QK_COMMUNITY_MODULE,
  FOO_KC2,
  FOO_KC3,
  FOO_KC4,
  // Keycodes for module Bar.
  BAR_KC1,
  BAR_KC2,
  // Keycodes for more modules...
};

Copy link
Member Author

@tzarc tzarc Jan 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All module keycodes are assigned automatically, with keyboard module keycodes preceding keymap module keycodes, in the module order specified in the keyboard or keymap json.

Basically does exactly what you specified but with stable ordering. The define is mainly just to allocate a range, rather than using the keycode directly.

}
}
}
17 changes: 17 additions & 0 deletions data/schemas/community_module.jsonschema
Original file line number Diff line number Diff line change
@@ -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"},
}
}
17 changes: 12 additions & 5 deletions data/schemas/keyboard.jsonschema
Original file line number Diff line number Diff line change
Expand Up @@ -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...
Expand Down Expand Up @@ -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": {
Expand Down Expand Up @@ -463,6 +464,12 @@
"rows": {"$ref": "qmk.definitions.v1#/mcu_pin_array"}
}
},
"modules": {
"type": "array",
"items": {
"type": "string"
}
},
"mouse_key": {
"type": "object",
"properties": {
Expand Down
6 changes: 6 additions & 0 deletions data/schemas/keymap.jsonschema
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@
"config": {"$ref": "qmk.keyboard.v1"},
"notes": {
"type": "string"
},
"modules": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
7 changes: 7 additions & 0 deletions keyboards/handwired/onekey/keymaps/community_module/keymap.c
Original file line number Diff line number Diff line change
@@ -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)
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"modules": ["hello_world"]
}
1 change: 1 addition & 0 deletions lib/python/qmk/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
'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',
Expand Down
2 changes: 1 addition & 1 deletion lib/python/qmk/cli/format/c.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
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')


Expand Down
13 changes: 11 additions & 2 deletions lib/python/qmk/cli/format/json.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

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


Expand All @@ -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')
Expand All @@ -54,14 +61,16 @@ 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)
return json_encoder


@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)
Expand Down
Loading
Loading