mirror of
https://github.com/qmk/qmk_firmware.git
synced 2025-07-17 21:22:05 +00:00
Merge ff77db93cd
into 507c948ed8
This commit is contained in:
commit
7068a37f26
1
.github/workflows/docs.yml
vendored
1
.github/workflows/docs.yml
vendored
@ -36,6 +36,7 @@ jobs:
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
pip3 install -r requirements-dev.txt
|
||||
apt-get update && apt-get install -y rsync doxygen
|
||||
# install nvm
|
||||
touch $HOME/.bashrc
|
||||
|
4
.github/workflows/regen.yml
vendored
4
.github/workflows/regen.yml
vendored
@ -21,6 +21,10 @@ jobs:
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install python reqs
|
||||
run: |
|
||||
python3 -m pip install -r requirements.txt
|
||||
|
||||
- name: Run qmk generators
|
||||
run: |
|
||||
util/regen.sh
|
||||
|
4
.github/workflows/regen_push.yml
vendored
4
.github/workflows/regen_push.yml
vendored
@ -21,6 +21,10 @@ jobs:
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
pip3 install -r requirements-dev.txt
|
||||
|
||||
- name: Run qmk generators
|
||||
run: |
|
||||
util/regen.sh
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -104,6 +104,7 @@ secrets.tar
|
||||
# Python things
|
||||
__pycache__
|
||||
.python-version
|
||||
*.egg-info
|
||||
.venv
|
||||
|
||||
# Prerequisites for updating ChibiOS
|
||||
|
@ -49,6 +49,10 @@ else ifeq ($(strip $(DEBUG_MATRIX_SCAN_RATE_ENABLE)), api)
|
||||
OPT_DEFS += -DDEBUG_MATRIX_SCAN_RATE
|
||||
endif
|
||||
|
||||
ifeq ($(strip $(XAP_ENABLE)), yes)
|
||||
include $(BUILDDEFS_PATH)/xap.mk
|
||||
endif
|
||||
|
||||
AUDIO_ENABLE ?= no
|
||||
ifeq ($(strip $(AUDIO_ENABLE)), yes)
|
||||
ifeq ($(PLATFORM),CHIBIOS)
|
||||
@ -640,6 +644,22 @@ ifeq ($(strip $(VIA_ENABLE)), yes)
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(strip $(XAP_ENABLE)), yes)
|
||||
ifeq ($(strip $(VIA_ENABLE)), yes)
|
||||
$(error 'XAP_ENABLE = $(XAP_ENABLE)' deprecates 'VIA_ENABLE = $(VIA_ENABLE)'. Please set 'VIA_ENABLE = no')
|
||||
endif
|
||||
|
||||
DYNAMIC_KEYMAP_ENABLE := yes
|
||||
FNV_ENABLE := yes
|
||||
SECURE_ENABLE := yes
|
||||
BOOTMAGIC_ENABLE := yes
|
||||
|
||||
OPT_DEFS += -DXAP_ENABLE
|
||||
OPT_DEFS += -DBOOTLOADER_JUMP_SUPPORTED
|
||||
VPATH += $(QUANTUM_DIR)/xap
|
||||
SRC += $(QUANTUM_DIR)/xap/xap.c $(QUANTUM_DIR)/xap/xap_handlers.c
|
||||
endif
|
||||
|
||||
ifeq ($(strip $(RAW_ENABLE)), yes)
|
||||
OPT_DEFS += -DRAW_ENABLE
|
||||
SRC += raw_hid.c
|
||||
|
44
builddefs/xap.mk
Normal file
44
builddefs/xap.mk
Normal file
@ -0,0 +1,44 @@
|
||||
# Copyright 2022 Nick Brassel (@tzarc)
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
XAP_FILES := $(shell ls -1 data/xap/* | sort | xargs echo)
|
||||
ifneq ("$(wildcard $(KEYBOARD_PATH_1)/xap.hjson)","")
|
||||
XAP_FILES += $(KEYBOARD_PATH_1)/xap.hjson
|
||||
endif
|
||||
ifneq ("$(wildcard $(KEYBOARD_PATH_2)/xap.hjson)","")
|
||||
XAP_FILES += $(KEYBOARD_PATH_2)/xap.hjson
|
||||
endif
|
||||
ifneq ("$(wildcard $(KEYBOARD_PATH_3)/xap.hjson)","")
|
||||
XAP_FILES += $(KEYBOARD_PATH_3)/xap.hjson
|
||||
endif
|
||||
ifneq ("$(wildcard $(KEYBOARD_PATH_4)/xap.hjson)","")
|
||||
XAP_FILES += $(KEYBOARD_PATH_4)/xap.hjson
|
||||
endif
|
||||
ifneq ("$(wildcard $(KEYBOARD_PATH_5)/xap.hjson)","")
|
||||
XAP_FILES += $(KEYBOARD_PATH_5)/xap.hjson
|
||||
endif
|
||||
ifneq ("$(wildcard $(KEYMAP_PATH)/xap.hjson)","")
|
||||
XAP_FILES += $(KEYMAP_PATH)/xap.hjson
|
||||
endif
|
||||
ifneq ("$(wildcard $(USER_NAME)/xap.hjson)","")
|
||||
XAP_FILES += $(USER_NAME)/xap.hjson
|
||||
endif
|
||||
|
||||
$(INTERMEDIATE_OUTPUT)/src/config_blob_gz.h: $(INFO_JSON_FILES)
|
||||
@$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD)
|
||||
$(eval CMD=$(QMK_BIN) xap-generate-qmk-blob-h -o "$(INTERMEDIATE_OUTPUT)/src/config_blob_gz.h" -kb $(KEYBOARD) -km $(KEYMAP))
|
||||
@$(BUILD_CMD)
|
||||
|
||||
$(INTERMEDIATE_OUTPUT)/src/xap_generated.inl: $(XAP_FILES)
|
||||
@$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD)
|
||||
$(eval CMD=$(QMK_BIN) xap-generate-qmk-inc -o "$(INTERMEDIATE_OUTPUT)/src/xap_generated.inl" -kb $(KEYBOARD) -km $(KEYMAP))
|
||||
@$(BUILD_CMD)
|
||||
|
||||
$(INTERMEDIATE_OUTPUT)/src/xap_generated.h: $(XAP_FILES)
|
||||
@$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD)
|
||||
$(eval CMD=$(QMK_BIN) xap-generate-qmk-h -o "$(INTERMEDIATE_OUTPUT)/src/xap_generated.h" -kb $(KEYBOARD) -km $(KEYMAP))
|
||||
@$(BUILD_CMD)
|
||||
|
||||
generated-files: $(INTERMEDIATE_OUTPUT)/src/config_blob_gz.h $(INTERMEDIATE_OUTPUT)/src/xap_generated.inl $(INTERMEDIATE_OUTPUT)/src/xap_generated.h
|
||||
|
||||
VPATH += $(INTERMEDIATE_OUTPUT)/src
|
82
data/constants/lighting/led_matrix_0.0.1.hjson
Normal file
82
data/constants/lighting/led_matrix_0.0.1.hjson
Normal file
@ -0,0 +1,82 @@
|
||||
{
|
||||
"groups": {
|
||||
"reactive": {
|
||||
"define": "LED_MATRIX_KEYREACTIVE_ENABLED"
|
||||
}
|
||||
},
|
||||
"effects": {
|
||||
"0x00": {
|
||||
"key": "SOLID"
|
||||
},
|
||||
"0x01": {
|
||||
"key": "ALPHAS_MODS"
|
||||
},
|
||||
"0x02": {
|
||||
"key": "BREATHING"
|
||||
},
|
||||
"0x03": {
|
||||
"key": "BAND"
|
||||
},
|
||||
"0x04": {
|
||||
"key": "BAND_PINWHEEL"
|
||||
},
|
||||
"0x05": {
|
||||
"key": "BAND_SPIRAL"
|
||||
},
|
||||
"0x06": {
|
||||
"key": "CYCLE_LEFT_RIGHT"
|
||||
},
|
||||
"0x07": {
|
||||
"key": "CYCLE_UP_DOWN"
|
||||
},
|
||||
"0x08": {
|
||||
"key": "CYCLE_OUT_IN"
|
||||
},
|
||||
"0x09": {
|
||||
"key": "DUAL_BEACON"
|
||||
},
|
||||
"0x0A": {
|
||||
"key": "WAVE_LEFT_RIGHT"
|
||||
},
|
||||
"0x0B": {
|
||||
"key": "WAVE_UP_DOWN"
|
||||
},
|
||||
|
||||
"0x0C": {
|
||||
"key": "SOLID_REACTIVE_SIMPLE",
|
||||
"group": "reactive"
|
||||
},
|
||||
"0x0D": {
|
||||
"key": "SOLID_REACTIVE_WIDE",
|
||||
"group": "reactive"
|
||||
},
|
||||
"0x0E": {
|
||||
"key": "SOLID_REACTIVE_MULTIWIDE",
|
||||
"group": "reactive"
|
||||
},
|
||||
"0x0F": {
|
||||
"key": "SOLID_REACTIVE_CROSS",
|
||||
"group": "reactive"
|
||||
},
|
||||
"0x10": {
|
||||
"key": "SOLID_REACTIVE_MULTICROSS",
|
||||
"group": "reactive"
|
||||
},
|
||||
"0x11": {
|
||||
"key": "SOLID_REACTIVE_NEXUS",
|
||||
"group": "reactive"
|
||||
},
|
||||
"0x12": {
|
||||
"key": "SOLID_REACTIVE_MULTINEXUS",
|
||||
"group": "reactive"
|
||||
},
|
||||
"0x13": {
|
||||
"key": "SOLID_SPLASH",
|
||||
"group": "reactive"
|
||||
},
|
||||
"0x14": {
|
||||
"key": "SOLID_MULTISPLASH",
|
||||
"group": "reactive"
|
||||
}
|
||||
}
|
||||
}
|
175
data/constants/lighting/rgb_matrix_0.0.1.hjson
Normal file
175
data/constants/lighting/rgb_matrix_0.0.1.hjson
Normal file
@ -0,0 +1,175 @@
|
||||
{
|
||||
"groups": {
|
||||
"framebuffer": {
|
||||
"define": "RGB_MATRIX_FRAMEBUFFER_EFFECTS"
|
||||
},
|
||||
"reactive": {
|
||||
"define": "RGB_MATRIX_KEYREACTIVE_ENABLED"
|
||||
}
|
||||
},
|
||||
"effects": {
|
||||
"0x00": {
|
||||
"key": "SOLID_COLOR"
|
||||
},
|
||||
"0x01": {
|
||||
"key": "ALPHAS_MODS"
|
||||
},
|
||||
"0x02": {
|
||||
"key": "GRADIENT_UP_DOWN"
|
||||
},
|
||||
"0x03": {
|
||||
"key": "GRADIENT_LEFT_RIGHT"
|
||||
},
|
||||
"0x04": {
|
||||
"key": "BREATHING"
|
||||
},
|
||||
"0x05": {
|
||||
"key": "BAND_SAT"
|
||||
},
|
||||
"0x06": {
|
||||
"key": "BAND_VAL"
|
||||
},
|
||||
"0x07": {
|
||||
"key": "BAND_PINWHEEL_SAT"
|
||||
},
|
||||
"0x08": {
|
||||
"key": "BAND_PINWHEEL_VAL"
|
||||
},
|
||||
"0x09": {
|
||||
"key": "BAND_SPIRAL_SAT"
|
||||
},
|
||||
"0x0A": {
|
||||
"key": "BAND_SPIRAL_VAL"
|
||||
},
|
||||
"0x0B": {
|
||||
"key": "CYCLE_ALL"
|
||||
},
|
||||
"0x0C": {
|
||||
"key": "CYCLE_LEFT_RIGHT"
|
||||
},
|
||||
"0x0D": {
|
||||
"key": "CYCLE_UP_DOWN"
|
||||
},
|
||||
"0x0E": {
|
||||
"key": "CYCLE_OUT_IN"
|
||||
},
|
||||
"0x0F": {
|
||||
"key": "CYCLE_OUT_IN_DUAL"
|
||||
},
|
||||
"0x10": {
|
||||
"key": "RAINBOW_MOVING_CHEVRON"
|
||||
},
|
||||
"0x11": {
|
||||
"key": "CYCLE_PINWHEEL"
|
||||
},
|
||||
"0x12": {
|
||||
"key": "CYCLE_SPIRAL"
|
||||
},
|
||||
"0x13": {
|
||||
"key": "DUAL_BEACON"
|
||||
},
|
||||
"0x14": {
|
||||
"key": "RAINBOW_BEACON"
|
||||
},
|
||||
"0x15": {
|
||||
"key": "RAINBOW_PINWHEELS"
|
||||
},
|
||||
"0x16": {
|
||||
"key": "RAINDROPS"
|
||||
},
|
||||
"0x17": {
|
||||
"key": "JELLYBEAN_RAINDROPS"
|
||||
},
|
||||
"0x18": {
|
||||
"key": "HUE_BREATHING"
|
||||
},
|
||||
"0x19": {
|
||||
"key": "HUE_PENDULUM"
|
||||
},
|
||||
"0x1A": {
|
||||
"key": "HUE_WAVE"
|
||||
},
|
||||
"0x1B": {
|
||||
"key": "PIXEL_FRACTAL"
|
||||
},
|
||||
"0x1C": {
|
||||
"key": "PIXEL_FLOW"
|
||||
},
|
||||
"0x1D": {
|
||||
"key": "PIXEL_RAIN"
|
||||
},
|
||||
|
||||
"0x1E": {
|
||||
"key": "TYPING_HEATMAP",
|
||||
"group": "framebuffer"
|
||||
},
|
||||
"0x1F": {
|
||||
"key": "DIGITAL_RAIN",
|
||||
"group": "framebuffer"
|
||||
},
|
||||
|
||||
"0x20": {
|
||||
"key": "SOLID_REACTIVE_SIMPLE",
|
||||
"group": "reactive"
|
||||
},
|
||||
"0x21": {
|
||||
"key": "SOLID_REACTIVE",
|
||||
"group": "reactive"
|
||||
},
|
||||
"0x22": {
|
||||
"key": "SOLID_REACTIVE_WIDE",
|
||||
"group": "reactive"
|
||||
},
|
||||
"0x23": {
|
||||
"key": "SOLID_REACTIVE_MULTIWIDE",
|
||||
"group": "reactive"
|
||||
},
|
||||
"0x24": {
|
||||
"key": "SOLID_REACTIVE_CROSS",
|
||||
"group": "reactive"
|
||||
},
|
||||
"0x25": {
|
||||
"key": "SOLID_REACTIVE_MULTICROSS",
|
||||
"group": "reactive"
|
||||
},
|
||||
"0x26": {
|
||||
"key": "SOLID_REACTIVE_NEXUS",
|
||||
"group": "reactive"
|
||||
},
|
||||
"0x27": {
|
||||
"key": "SOLID_REACTIVE_MULTINEXUS",
|
||||
"group": "reactive"
|
||||
},
|
||||
"0x28": {
|
||||
"key": "SPLASH",
|
||||
"group": "reactive"
|
||||
},
|
||||
"0x29": {
|
||||
"key": "MULTISPLASH",
|
||||
"group": "reactive"
|
||||
},
|
||||
"0x2A": {
|
||||
"key": "SOLID_SPLASH",
|
||||
"group": "reactive"
|
||||
},
|
||||
"0x2B": {
|
||||
"key": "SOLID_MULTISPLASH",
|
||||
"group": "reactive"
|
||||
},
|
||||
"0x2C": {
|
||||
"key": "FLOWER_BLOOMING"
|
||||
},
|
||||
"0x2D": {
|
||||
"key": "STARLIGHT"
|
||||
},
|
||||
"0x2E": {
|
||||
"key": "STARLIGHT_DUAL_SAT"
|
||||
},
|
||||
"0x2F": {
|
||||
"key": "STARLIGHT_DUAL_HUE"
|
||||
},
|
||||
"0x30": {
|
||||
"key": "RIVERFLOW"
|
||||
}
|
||||
}
|
||||
}
|
130
data/constants/lighting/rgblight_0.0.1.hjson
Normal file
130
data/constants/lighting/rgblight_0.0.1.hjson
Normal file
@ -0,0 +1,130 @@
|
||||
{
|
||||
"effects": {
|
||||
"0x00": {
|
||||
"key": "STATIC_LIGHT"
|
||||
},
|
||||
"0x01": {
|
||||
"key": "BREATHING"
|
||||
},
|
||||
"0x02": {
|
||||
"key": "BREATHING_2"
|
||||
},
|
||||
"0x03": {
|
||||
"key": "BREATHING_3"
|
||||
},
|
||||
"0x04": {
|
||||
"key": "BREATHING_4"
|
||||
},
|
||||
"0x05": {
|
||||
"key": "RAINBOW_MOOD"
|
||||
},
|
||||
"0x06": {
|
||||
"key": "RAINBOW_MOOD_2"
|
||||
},
|
||||
"0x07": {
|
||||
"key": "RAINBOW_MOOD_3"
|
||||
},
|
||||
"0x08": {
|
||||
"key": "RAINBOW_SWIRL"
|
||||
},
|
||||
"0x09": {
|
||||
"key": "RAINBOW_SWIRL_2"
|
||||
},
|
||||
"0x0A": {
|
||||
"key": "RAINBOW_SWIRL_3"
|
||||
},
|
||||
"0x0B": {
|
||||
"key": "RAINBOW_SWIRL_4"
|
||||
},
|
||||
"0x0C": {
|
||||
"key": "RAINBOW_SWIRL_5"
|
||||
},
|
||||
"0x0D": {
|
||||
"key": "RAINBOW_SWIRL_6"
|
||||
},
|
||||
"0x0E": {
|
||||
"key": "SNAKE"
|
||||
},
|
||||
"0x0F": {
|
||||
"key": "SNAKE_2"
|
||||
},
|
||||
"0x10": {
|
||||
"key": "SNAKE_3"
|
||||
},
|
||||
"0x11": {
|
||||
"key": "SNAKE_4"
|
||||
},
|
||||
"0x12": {
|
||||
"key": "SNAKE_5"
|
||||
},
|
||||
"0x13": {
|
||||
"key": "SNAKE_6"
|
||||
},
|
||||
"0x14": {
|
||||
"key": "KNIGHT"
|
||||
},
|
||||
"0x15": {
|
||||
"key": "KNIGHT_2"
|
||||
},
|
||||
"0x16": {
|
||||
"key": "KNIGHT_3"
|
||||
},
|
||||
"0x17": {
|
||||
"key": "CHRISTMAS"
|
||||
},
|
||||
"0x18": {
|
||||
"key": "STATIC_GRADIENT"
|
||||
},
|
||||
"0x19": {
|
||||
"key": "STATIC_GRADIENT_2"
|
||||
},
|
||||
"0x1A": {
|
||||
"key": "STATIC_GRADIENT_3"
|
||||
},
|
||||
"0x1B": {
|
||||
"key": "STATIC_GRADIENT_4"
|
||||
},
|
||||
"0x1C": {
|
||||
"key": "STATIC_GRADIENT_5"
|
||||
},
|
||||
"0x1D": {
|
||||
"key": "STATIC_GRADIENT_6"
|
||||
},
|
||||
"0x1E": {
|
||||
"key": "STATIC_GRADIENT_7"
|
||||
},
|
||||
"0x1F": {
|
||||
"key": "STATIC_GRADIENT_8"
|
||||
},
|
||||
"0x20": {
|
||||
"key": "STATIC_GRADIENT_9"
|
||||
},
|
||||
"0x21": {
|
||||
"key": "STATIC_GRADIENT_10"
|
||||
},
|
||||
"0x22": {
|
||||
"key": "RGB_TEST"
|
||||
},
|
||||
"0x23": {
|
||||
"key": "ALTERNATING"
|
||||
},
|
||||
"0x24": {
|
||||
"key": "TWINKLE"
|
||||
},
|
||||
"0x25": {
|
||||
"key": "TWINKLE_2"
|
||||
},
|
||||
"0x26": {
|
||||
"key": "TWINKLE_3"
|
||||
},
|
||||
"0x27": {
|
||||
"key": "TWINKLE_4"
|
||||
},
|
||||
"0x28": {
|
||||
"key": "TWINKLE_5"
|
||||
},
|
||||
"0x29": {
|
||||
"key": "TWINKLE_6"
|
||||
}
|
||||
}
|
||||
}
|
7
data/mappings/xap_defaults.json
Normal file
7
data/mappings/xap_defaults.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"secure": {
|
||||
"unlock_sequence": [ [0,0] ],
|
||||
"unlock_timeout": 5000,
|
||||
"idle_timeout": 60000
|
||||
}
|
||||
}
|
@ -16,6 +16,18 @@
|
||||
"type": "object",
|
||||
"additionalProperties": {"type": "boolean"}
|
||||
},
|
||||
"build_target": {
|
||||
"oneOf": [
|
||||
{"$ref": "#/keyboard_keymap_tuple"},
|
||||
{"$ref": "#/json_file_path"}
|
||||
]
|
||||
},
|
||||
"define": {
|
||||
"type": "string",
|
||||
"minLength": 2,
|
||||
"maxLength": 50,
|
||||
"pattern": "^[A-Z_]*$"
|
||||
},
|
||||
"filename": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
@ -187,6 +199,10 @@
|
||||
"minLength": 1,
|
||||
"maxLength": 250
|
||||
},
|
||||
"text_unsigned_int": {
|
||||
"type": "string",
|
||||
"pattern": "^[0-8]+$"
|
||||
},
|
||||
"unsigned_decimal": {
|
||||
"type": "number",
|
||||
"minimum": 0
|
||||
|
255
data/schemas/xap.jsonschema
Normal file
255
data/schemas/xap.jsonschema
Normal file
@ -0,0 +1,255 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"$id": "qmk.xap.v1",
|
||||
"title": "XAP Spec",
|
||||
"definitions": {
|
||||
"data_type": {
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": [
|
||||
"bool",
|
||||
"u8",
|
||||
"u16",
|
||||
"u32",
|
||||
"u64",
|
||||
"struct",
|
||||
"string"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"pattern": "^u\\d{1,2}\\[\\d{1,2}\\]*$"
|
||||
}
|
||||
]
|
||||
},
|
||||
"router_type": {
|
||||
"enum": [
|
||||
"command",
|
||||
"router"
|
||||
]
|
||||
},
|
||||
"permission": {
|
||||
"enum": [
|
||||
"secure"
|
||||
]
|
||||
},
|
||||
"struct": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"name",
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"route": {
|
||||
"type": "object",
|
||||
"propertyNames": {
|
||||
"$ref": "qmk.definitions.v1#/hex_number_2d"
|
||||
},
|
||||
"additionalProperties": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type",
|
||||
"define"
|
||||
],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"type": {
|
||||
"$ref": "#/definitions/router_type"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"define": {
|
||||
"$ref": "qmk.definitions.v1#/define"
|
||||
},
|
||||
"permissions": {
|
||||
"$ref": "#/definitions/permission"
|
||||
},
|
||||
"enable_if_preprocessor": {
|
||||
"type": "string"
|
||||
},
|
||||
"request_type": {
|
||||
"$ref": "#/definitions/data_type"
|
||||
},
|
||||
"request_struct_length": {
|
||||
"type": "number"
|
||||
},
|
||||
"request_purpose": {
|
||||
"type": "string"
|
||||
},
|
||||
"return_type": {
|
||||
"$ref": "#/definitions/data_type"
|
||||
},
|
||||
"request_struct_members": {
|
||||
"$ref": "#definitions/struct"
|
||||
},
|
||||
"return_struct_length": {
|
||||
"type": "number"
|
||||
},
|
||||
"return_constant": {
|
||||
"type": [
|
||||
"array",
|
||||
"string"
|
||||
]
|
||||
},
|
||||
"return_struct_members": {
|
||||
"$ref": "#definitions/struct"
|
||||
},
|
||||
"return_purpose": {
|
||||
"type": "string"
|
||||
},
|
||||
"return_execute": {
|
||||
"type": "string"
|
||||
},
|
||||
"routes": {
|
||||
"$ref": "#/definitions/route"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"version"
|
||||
],
|
||||
"properties": {
|
||||
"version": {
|
||||
"$ref": "qmk.definitions.v1#/bcd_version"
|
||||
},
|
||||
"define": {
|
||||
"$ref": "qmk.definitions.v1#/define"
|
||||
},
|
||||
"documentation": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"order": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"term_definitions": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type_docs": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type_definitions": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"$ref": "#/definitions/data_type"
|
||||
},
|
||||
"struct_length": {
|
||||
"type": "number"
|
||||
},
|
||||
"struct_members": {
|
||||
"$ref": "#definitions/struct"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"response_flags": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"define_prefix": {
|
||||
"$ref": "qmk.definitions.v1#/define"
|
||||
},
|
||||
"bits": {
|
||||
"type": "object",
|
||||
"propertyNames": {
|
||||
"$ref": "qmk.definitions.v1#/text_unsigned_int"
|
||||
},
|
||||
"additionalProperties": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"define": {
|
||||
"$ref": "qmk.definitions.v1#/define"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"broadcast_messages": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"define_prefix": {
|
||||
"$ref": "qmk.definitions.v1#/define"
|
||||
},
|
||||
"messages": {
|
||||
"type": "object",
|
||||
"propertyNames": {
|
||||
"$ref": "qmk.definitions.v1#/hex_number_2d"
|
||||
},
|
||||
"additionalProperties": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"define": {
|
||||
"$ref": "qmk.definitions.v1#/define"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"return_type": {
|
||||
"$ref": "#/definitions/data_type"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"routes": {
|
||||
"$ref": "#/definitions/route"
|
||||
}
|
||||
}
|
||||
}
|
19
data/templates/xap/client/python/constants.py.j2
Normal file
19
data/templates/xap/client/python/constants.py.j2
Normal file
@ -0,0 +1,19 @@
|
||||
{{ constants.GPL2_HEADER_SH_LIKE }}
|
||||
{{ constants.GENERATED_HEADER_SH_LIKE }}
|
||||
from enum import IntEnum
|
||||
|
||||
|
||||
# version: 0.0.1
|
||||
class RgblightModes(IntEnum):
|
||||
{% for id, effect in specs.rgblight.effects | dictsort %}
|
||||
{{ effect.key }} = {{ id }}
|
||||
{% endfor %}
|
||||
|
||||
|
||||
# version: 0.0.1
|
||||
class RgbMatrixModes(IntEnum):
|
||||
{% for id, effect in specs.rgb_matrix.effects | dictsort %}
|
||||
{{ effect.key }} = {{ id }}
|
||||
{% endfor %}
|
||||
|
||||
# noqa: W391
|
22
data/templates/xap/client/python/routes.py.j2
Normal file
22
data/templates/xap/client/python/routes.py.j2
Normal file
@ -0,0 +1,22 @@
|
||||
{{ constants.GPL2_HEADER_SH_LIKE }}
|
||||
{{ constants.GENERATED_HEADER_SH_LIKE }}
|
||||
class XAPRouteError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class XAPRoutes():
|
||||
{% for id, route in xap.routes | dictsort %}
|
||||
{% if route.routes %}
|
||||
# {{route.define}}
|
||||
{% for subid, subroute in route.routes | dictsort %}
|
||||
{{route.define}}_{{subroute.define}} = b'\x{{ '%02d' % id|int(base=16) }}\x{{ '%02d' % subid|int(base=16) }}'
|
||||
{% if subroute.routes %}
|
||||
{% for subsubid, subsubroute in subroute.routes | dictsort %}
|
||||
{{route.define}}_{{subroute.define}}_{{subsubroute.define}} = b'\x{{ '%02d' % id|int(base=16) }}\x{{ '%02d' % subid|int(base=16) }}\x{{ '%02d' % subsubid|int(base=16) }}'
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
# noqa: W391
|
68
data/templates/xap/client/python/types.py.j2
Normal file
68
data/templates/xap/client/python/types.py.j2
Normal file
@ -0,0 +1,68 @@
|
||||
{{ constants.GPL2_HEADER_SH_LIKE }}
|
||||
{{ constants.GENERATED_HEADER_SH_LIKE }}
|
||||
from collections import namedtuple
|
||||
from enum import IntFlag, IntEnum
|
||||
from struct import Struct
|
||||
|
||||
|
||||
{% macro gen_struct(name, members, fmt) -%}
|
||||
class {{ name }}(namedtuple('{{ name }}', '{{ members }}')):
|
||||
fmt = Struct('{{ fmt }}')
|
||||
|
||||
def __new__(cls, *args):
|
||||
return super().__new__(cls, *args)
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls, data):
|
||||
return cls._make(cls.fmt.unpack(data))
|
||||
|
||||
def to_bytes(self):
|
||||
return self.fmt.pack(*list(self))
|
||||
|
||||
|
||||
{% endmacro -%}
|
||||
|
||||
{% set type_definitions = [
|
||||
{'name':'XAPRequest', 'members': 'token length data', 'fmt':'<HB61s'},
|
||||
{'name':'XAPResponse', 'members': 'token flags length data', 'fmt':'<HBB60s'},
|
||||
{'name':'XAPBroadcast', 'members': 'token event length data', 'fmt':'<HBB60s'},
|
||||
{'name':'XAPConfigBacklight', 'members': 'enable mode val', 'fmt':'<BBB'},
|
||||
{'name':'XAPConfigRgblight', 'members': 'enable mode hue sat val speed', 'fmt':'<BBBBBB'},
|
||||
{'name':'XAPConfigRgbMatrix', 'members': 'enable mode hue sat val speed flags', 'fmt':'<BBBBBBB'}
|
||||
] %}
|
||||
{% for item in type_definitions -%}{{ gen_struct(item.name, item.members, item.fmt) }}{% endfor -%}
|
||||
|
||||
# Spec structs
|
||||
{% for item in xap.routes.values() recursive %}
|
||||
{%- if item.routes -%}
|
||||
{{ loop(item.routes.values()) }}
|
||||
{%- endif -%}
|
||||
{%- if item.request_struct_members %}
|
||||
# TODO: gen inbound object for {{ item.define | to_snake }}
|
||||
{% endif -%}
|
||||
{%- if item.return_struct_members %}
|
||||
# TODO: gen outbound object for {{ item.define | to_snake }}
|
||||
{% endif %}
|
||||
{%- endfor %}
|
||||
|
||||
|
||||
class XAPSecureStatus(IntEnum):
|
||||
LOCKED = 0x00
|
||||
UNLOCKING = 0x01
|
||||
UNLOCKED = 0x02
|
||||
|
||||
|
||||
class XAPEventType(IntEnum):
|
||||
{% for id, message in xap.broadcast_messages.messages | dictsort %}
|
||||
{{ message.define }} = {{ id }}
|
||||
{% endfor %}
|
||||
|
||||
|
||||
class XAPFlags(IntFlag):
|
||||
{% for bitnum, bitinfo in xap.response_flags.bits | dictsort %}
|
||||
{% if bitinfo.define != "-" %}
|
||||
{{ bitinfo.define }} = 1 << {{ bitnum }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
# noqa: W391
|
4
data/templates/xap/docs/broadcast_messages.md.j2
Normal file
4
data/templates/xap/docs/broadcast_messages.md.j2
Normal file
@ -0,0 +1,4 @@
|
||||
{% for id, message in xap.broadcast_messages.messages | dictsort %}
|
||||
### {{ message.name }} - `{{ id }}`
|
||||
{{ message.description }}
|
||||
{% endfor %}
|
11
data/templates/xap/docs/docs.md.j2
Normal file
11
data/templates/xap/docs/docs.md.j2
Normal file
@ -0,0 +1,11 @@
|
||||
{{ constants.GPL2_HEADER_XML_LIKE }}
|
||||
{{ constants.GENERATED_HEADER_XML_LIKE }}
|
||||
{%- for item in xap.documentation.order %}
|
||||
{% if not item[0:1] == '!' %}
|
||||
{{ xap.documentation.get(item) }}
|
||||
|
||||
{% else %}
|
||||
{% include item[1:] %}
|
||||
|
||||
{% endif %}
|
||||
{% endfor %}
|
12
data/templates/xap/docs/response_flags.md.j2
Normal file
12
data/templates/xap/docs/response_flags.md.j2
Normal file
@ -0,0 +1,12 @@
|
||||
|{% for bitnum, bitinfo in xap.response_flags.bits | dictsort | reverse %} Bit {{ bitnum }} |{% endfor %}
|
||||
|
||||
|{% for bitnum, bitinfo in xap.response_flags.bits | dictsort | reverse %} -- |{% endfor %}
|
||||
|
||||
|{% for bitnum, bitinfo in xap.response_flags.bits | dictsort | reverse %} `{{ bitinfo.define }}` |{% endfor %}
|
||||
|
||||
|
||||
{% for bitnum, bitinfo in xap.response_flags.bits | dictsort | reverse %}
|
||||
{% if bitinfo.define != "-" -%}
|
||||
* Bit {{ bitnum }} (`{{ bitinfo.define }}`): {{ bitinfo.description }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
52
data/templates/xap/docs/routes.md.j2
Normal file
52
data/templates/xap/docs/routes.md.j2
Normal file
@ -0,0 +1,52 @@
|
||||
{%- macro gen_payload(name, type, purpose, members) -%}
|
||||
{%- if type == 'struct' -%}
|
||||
__{{ name }}:__
|
||||
{%- for member in members -%}
|
||||
<br>{{ " "|safe*4 }}* {{ member.name }}: `{{ member.type }}`
|
||||
{%- endfor -%}
|
||||
{%- elif purpose -%}
|
||||
__{{ name }}:__<br>{{ " "|safe*4 }}* {{ purpose }}: `{{ type }}`
|
||||
{%- elif type -%}
|
||||
__{{ name }}:__ `{{ type }}`
|
||||
{%- endif -%}
|
||||
{%- endmacro -%}
|
||||
|
||||
{%- macro gen_payloads(route) -%}
|
||||
{{ gen_payload('Request', route.request_type, route.request_purpose, route.request_struct_members) }}{%- if route.return_type and route.request_type -%}<br><br>{% endif %}{{ gen_payload('Response', route.return_type, null, route.return_struct_members) }}
|
||||
{%- endmacro -%}
|
||||
|
||||
{%- macro gen_tags(route) -%}
|
||||
{% if 'secure' == route.permissions %}__Secure__{% endif %}
|
||||
{%- endmacro -%}
|
||||
|
||||
{% for id, route in xap.routes | dictsort %}
|
||||
### {{ route.name }} - `{{ id }}`
|
||||
{{ route.description }}
|
||||
|
||||
{% if route.routes %}
|
||||
| Name | Route | Tags | Payloads | Description |
|
||||
| -- | -- | -- | -- | -- |
|
||||
{% for subid, subroute in route.routes | dictsort %}
|
||||
{% if not subroute.routes %}
|
||||
| {{ subroute.name }} | `{{ id }} {{ subid }}` | {{ gen_tags(subroute) }} | {{ gen_payloads(subroute) }} | {{ subroute.description | newline_to_br }}|
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% for subid, subroute in route.routes | dictsort %}
|
||||
{%- if subroute.routes %}
|
||||
|
||||
#### {{ subroute.name }} - `{{ id }} {{ subid }}`
|
||||
{{ subroute.description }}
|
||||
|
||||
| Name | Route | Tags | Payloads | Description |
|
||||
| -- | -- | -- | -- | -- |
|
||||
{% for subsubid, subsubroute in subroute.routes | dictsort %}
|
||||
{% if not subsubroute.routes %}
|
||||
| {{ subsubroute.name }} | `{{ id }} {{ subid }} {{ subsubid }}` | {{ gen_tags(subsubroute) }} | {{ gen_payloads(subsubroute) }} | {{ subsubroute.description | newline_to_br }}|
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
8
data/templates/xap/docs/term_definitions.md.j2
Normal file
8
data/templates/xap/docs/term_definitions.md.j2
Normal file
@ -0,0 +1,8 @@
|
||||
| Name | Definition |
|
||||
| -- | -- |
|
||||
{% for type, definition in xap.term_definitions | dictsort %}
|
||||
| _{{ type }}_ | {{ definition }} |
|
||||
{% endfor %}
|
||||
{% for type, definition in xap.type_definitions | dictsort %}
|
||||
| _{{ definition.name }}_ | {{ definition.description }}{% if 'struct' == definition.type %} Takes the format:{% for item in definition.struct_members %}<br>`{{ item.type }}` - {{ item.name }}{%- endfor %}{% endif %} |
|
||||
{% endfor %}
|
5
data/templates/xap/docs/type_docs.md.j2
Normal file
5
data/templates/xap/docs/type_docs.md.j2
Normal file
@ -0,0 +1,5 @@
|
||||
| Name | Definition |
|
||||
| -- | -- |
|
||||
{% for type, definition in xap.type_docs | dictsort %}
|
||||
| _{{ type }}_ | {{ definition }} |
|
||||
{% endfor %}
|
6
data/templates/xap/docs/versions.md.j2
Normal file
6
data/templates/xap/docs/versions.md.j2
Normal file
@ -0,0 +1,6 @@
|
||||
{{ constants.GPL2_HEADER_XML_LIKE }}
|
||||
{{ constants.GENERATED_HEADER_XML_LIKE }}
|
||||
|
||||
{%- for ver in versions | reverse -%}
|
||||
* [XAP Version {{ ver }}](xap_{{ ver }}.md)
|
||||
{% endfor %}
|
171
data/templates/xap/firmware/xap_generated.h.j2
Executable file
171
data/templates/xap/firmware/xap_generated.h.j2
Executable file
@ -0,0 +1,171 @@
|
||||
{{ constants.GPL2_HEADER_C_LIKE }}
|
||||
{{ constants.GENERATED_HEADER_C_LIKE }}
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Versions and identifiers
|
||||
|
||||
#define XAP_BCD_VERSION UINT32_C({{ xap.version | triplet_to_bcd }})
|
||||
#define QMK_BCD_VERSION UINT32_C({{ qmk_version | triplet_to_bcd }})
|
||||
#define XAP_KEYBOARD_IDENTIFIER UINT32_C({{ keyboard | fnv1a_32 }})
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Response flag definitions
|
||||
|
||||
{% for bit,data in xap.response_flags.bits | dictsort %}
|
||||
#define {{ xap.response_flags.define_prefix }}_{{ data.define | to_snake | upper }} (UINT32_C(1) << ({{ bit }}))
|
||||
{% endfor %}
|
||||
#define {{ xap.response_flags.define_prefix }}_FAILED 0x00
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Broadcast message definitions
|
||||
|
||||
{% for message_id,data in xap.broadcast_messages.messages | dictsort %}
|
||||
#define {{ xap.broadcast_messages.define_prefix }}_{{ data.define | to_snake | upper }} {{ message_id }}
|
||||
{% if 'return_type' in data %}
|
||||
void {{ xap.broadcast_messages.define_prefix | lower }}_{{ data.define | to_snake | lower }}({{ data.return_type | type_to_c('value') }});
|
||||
{% else %}
|
||||
void {{ xap.broadcast_messages.define_prefix | lower }}_{{ data.define | to_snake | lower }}(const void *data, size_t length);
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
#define XAP_BROADCAST_TOKEN 0xFFFF
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Type definitions
|
||||
|
||||
{% for name,data in xap.type_definitions | dictsort %}
|
||||
{% if data.type != 'struct' %}
|
||||
typedef {{ data.type | type_to_c('xap_'+(name|to_snake|lower)+'_t') }};
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% for name,data in xap.type_definitions | dictsort %}
|
||||
{% if data.type == 'struct' %}
|
||||
typedef struct {
|
||||
{% for member in data.struct_members %}
|
||||
{{ member.type | type_to_c(member.name) }};
|
||||
{% endfor %}
|
||||
} __attribute__((__packed__)) xap_{{ name | to_snake | lower }}_t{{ data.type | type_to_c_after }};
|
||||
_Static_assert(sizeof(xap_{{ name | to_snake | lower }}_t) == {{ data.struct_length }}, "xap_{{ name | to_snake | lower }}_t needs to be {{ data.struct_length }} bytes in size");
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Route definitions
|
||||
|
||||
{% macro export_route_types(prefix, container) %}
|
||||
{% if 'routes' in container %}
|
||||
{% for route, data in container.routes | dictsort %}
|
||||
{% set this_prefix_uc = (prefix + '_' + data.define) | upper %}
|
||||
{% set this_prefix_lc = this_prefix_uc | lower %}
|
||||
|
||||
{% if 'request_struct_members' in data %}
|
||||
typedef struct {
|
||||
{% for member in data.request_struct_members %}
|
||||
{{ member.type | type_to_c(member.name|lower) }};
|
||||
{% endfor %}
|
||||
} __attribute__((__packed__)) {{ this_prefix_lc | to_snake | lower }}_arg_t;
|
||||
_Static_assert(sizeof({{ this_prefix_lc | to_snake | lower }}_arg_t) == {{ data.request_struct_length }}, "{{ this_prefix_lc | to_snake | lower }}_arg_t needs to be {{ data.request_struct_length }} bytes in size");
|
||||
{% elif 'request_type' in data %}
|
||||
{% if '[' in data.request_type %}
|
||||
typedef struct __attribute__((__packed__)) { {{ data.request_type | type_to_c('x') }}; } {{ this_prefix_lc }}_arg_t;
|
||||
{% else %}
|
||||
typedef {{ data.request_type | type_to_c(this_prefix_lc+'_arg_t') }};
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if 'return_struct_members' in data %}
|
||||
typedef struct {
|
||||
{% for member in data.return_struct_members %}
|
||||
{{ member.type | type_to_c(member.name|lower) }};
|
||||
{% endfor %}
|
||||
} __attribute__((__packed__)) {{ this_prefix_lc | to_snake | lower }}_t;
|
||||
_Static_assert(sizeof({{ this_prefix_lc | to_snake | lower }}_t) == {{ data.return_struct_length }}, "{{ this_prefix_lc | to_snake | lower }}_t needs to be {{ data.return_struct_length }} bytes in size");
|
||||
{% elif 'return_type' in data %}
|
||||
{% if '[' in data.return_type %}
|
||||
typedef struct __attribute__((__packed__)) { {{ data.return_type | type_to_c('x') }}; } {{ this_prefix_lc }}_t;
|
||||
{% else %}
|
||||
typedef {{ data.return_type | type_to_c(this_prefix_lc+'_t') }};
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{{ export_route_types(this_prefix_lc, data) }}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{{ export_route_types('xap_route', xap) }}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Capabilities IDs
|
||||
|
||||
{% macro export_route_ids(prefix, container) %}
|
||||
{% if 'routes' in container %}
|
||||
{% for route, data in container.routes | dictsort %}
|
||||
{% set this_prefix_uc = (prefix + '_' + data.define) | upper %}
|
||||
{% set this_prefix_lc = this_prefix_uc | lower %}
|
||||
#define {{ this_prefix_uc }} {{ route }}
|
||||
{{ export_route_ids(this_prefix_uc, data) }}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{{ export_route_ids('XAP_ROUTE', xap) }}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Capabilities Masks
|
||||
|
||||
{% macro export_route_masks(prefix, container, preprocessor_condition) %}
|
||||
{% if 'routes' in container %}
|
||||
{% for route, data in container.routes | dictsort %}
|
||||
{% set this_prefix_uc = (prefix + '_' + data.define) | upper %}
|
||||
{% set this_prefix_lc = this_prefix_uc | lower %}
|
||||
{% if 'enable_if_preprocessor' in data %}
|
||||
{% if preprocessor_condition == 'TRUE' %}
|
||||
{% set condition = "(" + data.enable_if_preprocessor + ")" %}
|
||||
{% else %}
|
||||
{% set condition = "(" + preprocessor_condition + " && (" + data.enable_if_preprocessor + "))" %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% set condition = preprocessor_condition %}
|
||||
{% endif %}
|
||||
{% if condition == 'TRUE' %}
|
||||
#define {{ this_prefix_uc }}_MASK (UINT32_C(1) << ({{ this_prefix_uc }}))
|
||||
{% else %}
|
||||
#if ({{ condition }})
|
||||
#define {{ this_prefix_uc }}_MASK (UINT32_C(1) << ({{ this_prefix_uc }}))
|
||||
#else // ({{ condition }})
|
||||
#define {{ this_prefix_uc }}_MASK 0
|
||||
#endif // ({{ condition }})
|
||||
{% endif %}
|
||||
{{ export_route_masks(this_prefix_uc, data, condition) }}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{{ export_route_masks('XAP_ROUTE', xap, 'TRUE') }}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Capabilities Values
|
||||
|
||||
{% macro export_route_capabilities(prefix, container) %}
|
||||
{% if 'routes' in container %}
|
||||
#define {{ prefix }}_CAPABILITIES (0 \
|
||||
{% for route, data in container.routes | dictsort %}
|
||||
{% set this_prefix_uc = (prefix + '_' + data.define) | upper %}
|
||||
| ({{ this_prefix_uc }}_MASK) \
|
||||
{% endfor %}
|
||||
)
|
||||
{% for route, data in container.routes | dictsort %}
|
||||
{% set this_prefix_uc = (prefix + '_' + data.define) | upper %}
|
||||
{{ export_route_capabilities(this_prefix_uc, data) }}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{{ export_route_capabilities('XAP_ROUTE', xap) }}
|
166
data/templates/xap/firmware/xap_generated.inl.j2
Executable file
166
data/templates/xap/firmware/xap_generated.inl.j2
Executable file
@ -0,0 +1,166 @@
|
||||
{{ constants.GPL2_HEADER_C_LIKE }}
|
||||
{{ constants.GENERATED_HEADER_C_LIKE }}
|
||||
|
||||
{% macro route_conditions(route_stack) %}
|
||||
{% set conditions = [] %}
|
||||
{% for data in route_stack %}
|
||||
{% if 'enable_if_preprocessor' in data %}
|
||||
{{ conditions.append(data.enable_if_preprocessor) or '' }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% if conditions %}
|
||||
#if ({{ conditions | join(' && ') }})
|
||||
{% endif %}
|
||||
{{ caller() }}
|
||||
{%- if conditions %}
|
||||
#endif // ({{ conditions | join(' && ') }})
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Broadcast
|
||||
|
||||
{% for message_id,data in xap.broadcast_messages.messages | dictsort %}
|
||||
{% if 'return_type' in data %}
|
||||
void {{ xap.broadcast_messages.define_prefix | lower }}_{{ data.define | to_snake | lower }}({{ data.return_type | type_to_c('value') }}) { xap_broadcast({{ message_id }}, &value, sizeof(value)); }
|
||||
{% else %}
|
||||
void {{ xap.broadcast_messages.define_prefix | lower }}_{{ data.define | to_snake | lower }}(const void *data, size_t length) { xap_broadcast({{ message_id }}, data, length); }
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Decl
|
||||
|
||||
{% macro export_route_declaration(prefix, container) %}
|
||||
{% if 'routes' in container %}
|
||||
{% for route, data in container.routes | dictsort %}
|
||||
{% set this_prefix_uc = (prefix + '_' + data.define) | upper %}
|
||||
{% set this_prefix_lc = this_prefix_uc | lower %}
|
||||
{% if 'return_execute' in data %}
|
||||
|
||||
{% if 'request_struct_members' in data %}
|
||||
{% set arg_type = ( this_prefix_lc | to_snake | lower ) + '_arg_t' %}
|
||||
{% set arg_var = arg_type + ' arg' %}
|
||||
{% elif 'request_type' in data %}
|
||||
{% set arg_type = data.request_type | type_to_c() %}
|
||||
{% set arg_var = data.request_type | type_to_c('arg') %}
|
||||
{% endif %}
|
||||
|
||||
__attribute__((weak)) bool xap_execute_{{ data.return_execute }}(xap_token_t token{% if arg_type %}, {{ (arg_type + '* arg') if 'xap_route' in arg_type else arg_var }}{% endif %}) { return false; }
|
||||
__attribute__((weak)) bool xap_respond_{{ data.return_execute }}(xap_token_t token, const uint8_t *data, size_t data_len) {
|
||||
{% if arg_type %}
|
||||
if (data_len != sizeof({{ arg_type }})) {
|
||||
return false;
|
||||
}
|
||||
{{ arg_var }};
|
||||
memcpy(&arg, data, sizeof({{ arg_type }}));
|
||||
{% endif %}
|
||||
|
||||
return xap_execute_{{ data.return_execute }}(token{% if arg_type %}, {{ '&' if 'xap_route' in arg_type else '' }}arg{% endif %});
|
||||
}
|
||||
{% endif %}
|
||||
{{ export_route_declaration(this_prefix_lc, data) }}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{{ export_route_declaration('xap_route', xap) }}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Data
|
||||
|
||||
{% macro export_route_data(prefix, container, route_stack) %}
|
||||
{% set this_route_stack = route_stack.copy() %}
|
||||
{{ this_route_stack.append(container) or '' }}
|
||||
{% if 'routes' in container %}
|
||||
{% for route, data in container.routes | dictsort %}
|
||||
{% set this_prefix_uc = (prefix + '_' + data.define) | upper %}
|
||||
{% set this_prefix_lc = this_prefix_uc | lower %}
|
||||
{% if 'return_constant' in data %}
|
||||
{% if data.return_type == 'struct' %}
|
||||
{% call route_conditions(this_route_stack) %}
|
||||
static const {{ this_prefix_lc }}_t {{ this_prefix_lc }}_data PROGMEM = {
|
||||
{% for member in data.return_constant %}
|
||||
{{ member }},
|
||||
{% endfor %}
|
||||
};
|
||||
{% endcall %}
|
||||
{% elif data.return_type == 'string' %}
|
||||
{% call route_conditions(this_route_stack) %}
|
||||
static const char {{ this_prefix_lc }}_str[] PROGMEM = {{ data.return_constant }};
|
||||
{% endcall %}
|
||||
{% else %}
|
||||
{% call route_conditions(this_route_stack) %}
|
||||
static const {{ data.return_type | type_to_c_before }} {{ this_prefix_lc }}_data PROGMEM = {{ data.return_constant }};
|
||||
{% endcall %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{{ export_route_data(this_prefix_lc, data, this_route_stack) }}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{{ export_route_data('XAP_ROUTE', xap, []) }}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Routes
|
||||
|
||||
{% macro append_routing_table(prefix, container, route_stack) %}
|
||||
{% set this_route_stack = route_stack.copy() %}
|
||||
{{ this_route_stack.append(container) or '' }}
|
||||
{% if 'routes' in container %}
|
||||
{% for route, data in container.routes | dictsort %}
|
||||
{% set this_prefix_uc = (prefix + '_' + data.define) | upper %}
|
||||
{% set this_prefix_lc = this_prefix_uc | lower %}
|
||||
{{ append_routing_table(this_prefix_lc, data, this_route_stack) }}
|
||||
{% endfor %}
|
||||
{% call route_conditions(this_route_stack) %}
|
||||
static const xap_route_t {{ prefix | lower}}_table[] PROGMEM = {
|
||||
{% for route, data in container.routes | dictsort %}
|
||||
{% set inner_route_stack = this_route_stack.copy() %}
|
||||
{{ inner_route_stack.append(data) or '' }}
|
||||
{% if 'permissions' in data %}
|
||||
{% set secure_status = 'ROUTE_PERMISSIONS_SECURE' %}
|
||||
{% else %}
|
||||
{% set secure_status = 'ROUTE_PERMISSIONS_INSECURE' %}
|
||||
{% endif %}
|
||||
{% call route_conditions(inner_route_stack) %}
|
||||
[{{ prefix | upper }}_{{ data.define }}] = {
|
||||
{% if 'routes' in data %}
|
||||
.flags = {
|
||||
.type = XAP_ROUTE,
|
||||
.secure = {{ secure_status }},
|
||||
},
|
||||
.child_routes = {{ prefix | lower }}_{{ data.define | lower }}_table,
|
||||
.child_routes_len = sizeof({{ prefix | lower }}_{{ data.define | lower }}_table)/sizeof(xap_route_t),
|
||||
{% elif 'return_execute' in data %}
|
||||
.flags = {
|
||||
.type = XAP_EXECUTE,
|
||||
.secure = {{ secure_status }},
|
||||
},
|
||||
.handler = xap_respond_{{ data.return_execute | lower }},
|
||||
{% elif 'return_constant' in data and data.return_type == 'string' %}
|
||||
.flags = {
|
||||
.type = XAP_CONST_MEM,
|
||||
.secure = {{ secure_status }},
|
||||
},
|
||||
.const_data = {{ prefix | lower }}_{{ data.define | lower }}_str,
|
||||
.const_data_len = sizeof({{ prefix | lower }}_{{ data.define | lower }}_str) - 1,
|
||||
{% elif 'return_constant' in data %}
|
||||
.flags = {
|
||||
.type = XAP_CONST_MEM,
|
||||
.secure = {{ secure_status }},
|
||||
},
|
||||
.const_data = &{{ prefix | lower }}_{{ data.define | lower }}_data,
|
||||
.const_data_len = sizeof({{ prefix | lower }}_{{ data.define | lower }}_data),
|
||||
{% endif %}
|
||||
},
|
||||
{% endcall %}
|
||||
{% endfor %}
|
||||
};
|
||||
{% endcall %}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{{ append_routing_table("xap_route", xap, []) }}
|
237
data/xap/xap_0.0.1.hjson
Executable file
237
data/xap/xap_0.0.1.hjson
Executable file
@ -0,0 +1,237 @@
|
||||
{
|
||||
version: 0.0.1
|
||||
|
||||
// Needed for table generation
|
||||
define: XAP_ROUTE
|
||||
|
||||
// Documentation section is used purely for `qmk xap-generate-docs`.
|
||||
documentation: {
|
||||
order: [
|
||||
page_header
|
||||
type_docs
|
||||
!type_docs.md.j2
|
||||
term_definitions
|
||||
!term_definitions.md.j2
|
||||
request_response
|
||||
reserved_tokens
|
||||
response_flags
|
||||
!response_flags.md.j2
|
||||
example_conversation
|
||||
routes
|
||||
!routes.md.j2
|
||||
]
|
||||
|
||||
page_header:
|
||||
'''
|
||||
# QMK Firmware XAP Specs
|
||||
|
||||
This document describes the requirements of the QMK XAP ("extensible application protocol") API.
|
||||
'''
|
||||
|
||||
type_docs:
|
||||
'''
|
||||
## Types
|
||||
|
||||
**All integral types are little-endian.**
|
||||
'''
|
||||
|
||||
term_definitions:
|
||||
'''
|
||||
## Definitions
|
||||
|
||||
This list defines the terms used across the entire set of XAP protocol documentation.
|
||||
'''
|
||||
|
||||
request_response:
|
||||
'''
|
||||
## Requests and Responses
|
||||
|
||||
Communication generally follows a request/response pattern.
|
||||
|
||||
Each request needs to include a _token_ -- this `u16` value prefixes each outbound request from the host application and its corresponding response.
|
||||
This allows response messages to be correlated with their request, even if multiple host applications are communicating with the firmware simultaneously.
|
||||
Host applications should randomly generate a token ID for **every** outbound request, unless using a reserved token defined below.
|
||||
To ensure host interoperability, valid token values are within the range `0x0100`-`0xFFFF`.
|
||||
|
||||
This token is followed by a `u8` signifying the length of data in the request.
|
||||
'''
|
||||
|
||||
// This documentation section reserved for next version
|
||||
reserved_tokens: ''
|
||||
|
||||
response_flags:
|
||||
'''
|
||||
Response messages will always be prefixed by the originating request _token_, directly followed by that request's _response flags_, then the response payload length:
|
||||
'''
|
||||
|
||||
example_conversation:
|
||||
'''
|
||||
### Example "conversation":
|
||||
|
||||
**Request** -- version query:
|
||||
|
||||
| Byte | 0 | 1 | 2 | 3 | 4 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| **Purpose** | Token | Token | Payload Length | Route | Route |
|
||||
| **Value** | `0x43` | `0x2B` | `0x02` | `0x00` | `0x00` |
|
||||
|
||||
**Response** -- matching token, successful flag, payload of `0x03170192` = 3.17.192:
|
||||
|
||||
| Byte | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| **Purpose** | Token | Token | Response Flags | Payload Length | Payload | Payload | Payload | Payload |
|
||||
| **Value** | `0x43` | `0x2B` | `0x01` | `0x04` | `0x92` | `0x01` | `0x17` | `0x03` |
|
||||
'''
|
||||
|
||||
routes:
|
||||
'''
|
||||
## Routes
|
||||
|
||||
Subsystem validity should be queried through the “Enabled-in-firmware subsystem query” under the QMK subsystem (route=0x00,0x01).
|
||||
This is the primary method for determining if a subsystem has been enabled in the running firmware.
|
||||
'''
|
||||
}
|
||||
|
||||
type_docs: {
|
||||
u8:
|
||||
'''
|
||||
An unsigned 8-bit integral (octet, or byte), commonly seen as `uint8_t` from _stdint.h_.
|
||||
'''
|
||||
u16:
|
||||
'''
|
||||
An unsigned 16-bit integral, commonly seen as `uint16_t` from _stdint.h_.
|
||||
'''
|
||||
u32:
|
||||
'''
|
||||
An unsigned 32-bit integral, commonly seen as `uint32_t` from _stdint.h_.
|
||||
'''
|
||||
"type[n]":
|
||||
'''
|
||||
An array of `type`, with array extent of `N` -- e.g. `u8[2]` signifies two consecutive octets.
|
||||
'''
|
||||
}
|
||||
|
||||
term_definitions: {
|
||||
Subsystem:
|
||||
'''
|
||||
A high-level area of functionality within XAP.
|
||||
'''
|
||||
Route:
|
||||
'''
|
||||
A sequence of _IDs_ describing the route to invoke a _handler_.
|
||||
'''
|
||||
Handler:
|
||||
'''
|
||||
A piece of code that is executed when a specific _route_ is received.
|
||||
'''
|
||||
Response:
|
||||
'''
|
||||
The data sent back to the host during execution of a _handler_.
|
||||
'''
|
||||
Payload:
|
||||
'''
|
||||
Any received data appended to the _route_, which gets delivered to the _handler_ when received.
|
||||
'''
|
||||
}
|
||||
|
||||
type_definitions: {
|
||||
identifier: {
|
||||
name: ID
|
||||
description: A single octet / 8-bit byte, representing Subsystem or Route index.
|
||||
type: u8
|
||||
}
|
||||
|
||||
response_flags: {
|
||||
name: Response Flags
|
||||
description: An `u8` containing the status of the request.
|
||||
type: u8
|
||||
}
|
||||
|
||||
token: {
|
||||
name: Token
|
||||
description: A `u16` associated with a specific request as well as its corresponding response. Valid token values are within the range `0x0100`-`0xFFFF`.
|
||||
type: u16
|
||||
}
|
||||
|
||||
request_header: {
|
||||
name: Request Header
|
||||
description: Packet format for inbound data.
|
||||
type: struct
|
||||
struct_length: 3
|
||||
struct_members: [
|
||||
{
|
||||
type: token
|
||||
name: token
|
||||
},
|
||||
{
|
||||
type: u8
|
||||
name: length
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
response_header: {
|
||||
name: Response Header
|
||||
description: Packet format for outbound data.
|
||||
type: struct
|
||||
struct_length: 4
|
||||
struct_members: [
|
||||
{
|
||||
type: token
|
||||
name: token
|
||||
},
|
||||
{
|
||||
type: response_flags
|
||||
name: flags
|
||||
},
|
||||
{
|
||||
type: u8
|
||||
name: length
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
response_flags: {
|
||||
define_prefix: XAP_RESPONSE_FLAG
|
||||
bits: {
|
||||
0: {
|
||||
name: Success
|
||||
define: SUCCESS
|
||||
description:
|
||||
'''
|
||||
When this bit is set, the request was successfully handled. If not set, all payload data should be disregarded, and the request retried if appropriate (with a new token).
|
||||
'''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
routes: {
|
||||
0x00: {
|
||||
type: router
|
||||
name: XAP
|
||||
define: XAP
|
||||
description:
|
||||
'''
|
||||
This subsystem is always present, and provides the ability to query information about the XAP protocol of the connected device.
|
||||
'''
|
||||
routes: {
|
||||
0x00: {
|
||||
type: command
|
||||
name: Version Query
|
||||
define: VERSION_QUERY
|
||||
description:
|
||||
'''
|
||||
XAP protocol version query.
|
||||
|
||||
* Returns the BCD-encoded version in the format of XX.YY.ZZZZ => `0xXXYYZZZZ`
|
||||
* e.g. 3.2.115 will match `0x03020115`, or bytes {0x15,0x01,0x02,0x03}.
|
||||
'''
|
||||
return_type: u32
|
||||
return_purpose: bcd-version
|
||||
return_constant: XAP_BCD_VERSION
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
385
data/xap/xap_0.1.0.hjson
Executable file
385
data/xap/xap_0.1.0.hjson
Executable file
@ -0,0 +1,385 @@
|
||||
{
|
||||
version: 0.1.0
|
||||
|
||||
documentation: {
|
||||
order: [
|
||||
broadcast_messages
|
||||
!broadcast_messages.md.j2
|
||||
]
|
||||
|
||||
reserved_tokens:
|
||||
'''
|
||||
Two token values are reserved: `0xFFFE` and `0xFFFF`:
|
||||
* `0xFFFE`: A message sent by a host application may use this token if no response is to be sent -- a "fire and forget" message.
|
||||
* `0xFFFF`: Signifies a "broadcast" message sent by the firmware without prompting from the host application. Broadcast messages are defined later in this document.
|
||||
|
||||
Any request will generate at least one corresponding response, with the exception of messages using reserved tokens. Maximum total message length is 128 bytes due to RAM constraints.
|
||||
'''
|
||||
|
||||
broadcast_messages:
|
||||
'''
|
||||
## Broadcast messages
|
||||
|
||||
Broadcast messages may be sent by the firmware to the host, without a corresponding inbound request. Each broadcast message uses the token `0xFFFF`, and does not expect a response from the host. Tokens are followed by an _ID_ signifying the type of broadcast, then the response _payload_ length, and finally the corresponding _payload_.
|
||||
'''
|
||||
}
|
||||
|
||||
response_flags: {
|
||||
bits: {
|
||||
1: {
|
||||
name: Secure Failure
|
||||
define: SECURE_FAILURE
|
||||
description:
|
||||
'''
|
||||
When this bit is set, the requested _route_ was marked _secure_ but an _unlock sequence_ has not completed.
|
||||
'''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type_docs: {
|
||||
bool:
|
||||
'''
|
||||
Data type that contains values 0 and 1. Implemented as an alias of `u8`.
|
||||
'''
|
||||
u64:
|
||||
'''
|
||||
An unsigned 64-bit integral, commonly seen as `uint64_t` from _stdint.h_.
|
||||
'''
|
||||
"struct{}":
|
||||
'''
|
||||
A structure of data, packing different objects together. Data is "compacted" -- there are no padding bytes between fields. Equivalent to a packed C-style `struct`. The order in which they're defined matches the order of the data in the response packet.
|
||||
'''
|
||||
}
|
||||
|
||||
term_definitions: {
|
||||
Capability:
|
||||
'''
|
||||
A way to determine if certain functionality is enabled in the firmware. Any _subsystem_ that provides build-time restriction of functionality must provide a _route_ for a _capabilities query_.
|
||||
'''
|
||||
"Secure Route":
|
||||
'''
|
||||
A _route_ which has potentially destructive consequences, necessitating prior approval by the user before executing.
|
||||
'''
|
||||
"Unlock sequence":
|
||||
'''
|
||||
A physical sequence initiated by the user to enable execution of _secure routes_.
|
||||
'''
|
||||
}
|
||||
|
||||
type_definitions: {
|
||||
broadcast_header: {
|
||||
name: Broadcast Header
|
||||
description: Packet format for broadcast messages.
|
||||
type: struct
|
||||
struct_length: 4
|
||||
struct_members: [
|
||||
{
|
||||
type: token
|
||||
name: token
|
||||
},
|
||||
{
|
||||
type: u8
|
||||
name: type
|
||||
},
|
||||
{
|
||||
type: u8
|
||||
name: length
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
broadcast_messages: {
|
||||
define_prefix: XAP_BROADCAST
|
||||
messages: {
|
||||
0x00: {
|
||||
name: Log message
|
||||
define: LOG_MESSAGE
|
||||
description:
|
||||
'''
|
||||
Replicates and replaces the same functionality as if using the standard QMK `CONSOLE_ENABLE = yes` in `rules.mk`. Normal prints within the firmware will manifest as log messages broadcast to the host. `hid_listen` will not be functional with XAP enabled.
|
||||
|
||||
Log message payloads include `u8[Length]` containing the text, where the length of the text is the _broadcast_header.length_ field.
|
||||
|
||||
**Example Log Broadcast** -- log message "Hello QMK!"
|
||||
|
||||
| Byte | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| **Purpose** | Token | Token | Broadcast Type | Length | Payload | Payload | Payload | Payload | Payload | Payload | Payload | Payload | Payload | Payload |
|
||||
| **Value** | `0xFF` | `0xFF` | `0x00` | `0x0A`(10) | `0x48`(H) | `0x65`(e) | `0x6C`(l) | `0x6C`(l) | `0x6F`(o) | `0x20`( ) | `0x51`(Q) | `0x4D`(M) | `0x4B`(K) | `0x21`(!) |
|
||||
'''
|
||||
}
|
||||
0x01: {
|
||||
name: Secure Status
|
||||
define: SECURE_STATUS
|
||||
description:
|
||||
'''
|
||||
Secure status has changed. Payloads include a `u8` matching a 'Secure Status' request.
|
||||
|
||||
**Example Secure Status Broadcast** -- secure "Unlocking"
|
||||
|
||||
| Byte | 0 | 1 | 2 | 3 | 4 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| **Purpose** | Token | Token | Broadcast Type | Length | Secure Status |
|
||||
| **Value** | `0xFF` | `0xFF` | `0x01` | `0x01` | `0x01` |
|
||||
'''
|
||||
return_type: u8
|
||||
}
|
||||
0x02: {
|
||||
name: Keyboard
|
||||
define: KB
|
||||
description:
|
||||
'''
|
||||
Reserved for vendor-specific functionality. No messages are defined by XAP.
|
||||
'''
|
||||
},
|
||||
|
||||
0x03: {
|
||||
name: User
|
||||
define: USER
|
||||
description:
|
||||
'''
|
||||
Reserved for user-specific functionality. No messages are defined by XAP.
|
||||
'''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
routes: {
|
||||
0x00: {
|
||||
routes: {
|
||||
0x01: {
|
||||
type: command
|
||||
name: Capabilities Query
|
||||
define: CAPABILITIES_QUERY
|
||||
description:
|
||||
'''
|
||||
XAP subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.
|
||||
'''
|
||||
return_type: u32
|
||||
return_purpose: capabilities
|
||||
return_constant: XAP_ROUTE_XAP_CAPABILITIES
|
||||
}
|
||||
0x02: {
|
||||
type: command
|
||||
name: Enabled subsystem query
|
||||
define: SUBSYSTEM_QUERY
|
||||
description:
|
||||
'''
|
||||
XAP protocol subsystem query. Each bit should be considered as a "usable" subsystem. For example, checking `(value & (1 << XAP_ROUTE_QMK) != 0)` means the QMK subsystem is enabled and available for querying.
|
||||
'''
|
||||
return_type: u32
|
||||
return_purpose: capabilities
|
||||
return_constant: XAP_ROUTE_CAPABILITIES
|
||||
}
|
||||
0x03: {
|
||||
type: command
|
||||
name: Secure Status
|
||||
define: SECURE_STATUS
|
||||
description:
|
||||
'''
|
||||
Query secure route status
|
||||
|
||||
* 0 means secure routes are disabled
|
||||
* 1 means unlock sequence initiated but incomplete
|
||||
* 2 means secure routes are allowed
|
||||
* any other value should be interpreted as disabled
|
||||
'''
|
||||
return_type: u8
|
||||
return_execute: secure_status
|
||||
}
|
||||
0x04: {
|
||||
type: command
|
||||
name: Secure Unlock
|
||||
define: SECURE_UNLOCK
|
||||
description: Initiate secure route unlock sequence
|
||||
return_execute: secure_unlock
|
||||
}
|
||||
0x05: {
|
||||
type: command
|
||||
name: Secure Lock
|
||||
define: SECURE_LOCK
|
||||
description: Disable secure routes
|
||||
return_execute: secure_lock
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
0x01: {
|
||||
type: router
|
||||
name: QMK
|
||||
define: QMK
|
||||
description:
|
||||
'''
|
||||
This subsystem is always present, and provides the ability to address QMK-specific functionality.
|
||||
'''
|
||||
routes: {
|
||||
0x00: {
|
||||
type: command
|
||||
name: Version Query
|
||||
define: VERSION_QUERY
|
||||
description:
|
||||
'''
|
||||
QMK protocol version query.
|
||||
|
||||
* Returns the BCD-encoded version in the format of XX.YY.ZZZZ => `0xXXYYZZZZ`
|
||||
* e.g. 3.2.115 will match `0x03020115`, or bytes {0x15,0x01,0x02,0x03}.
|
||||
'''
|
||||
return_type: u32
|
||||
return_purpose: bcd-version
|
||||
return_constant: QMK_BCD_VERSION
|
||||
}
|
||||
0x01: {
|
||||
type: command
|
||||
name: Capabilities Query
|
||||
define: CAPABILITIES_QUERY
|
||||
description:
|
||||
'''
|
||||
QMK subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.
|
||||
'''
|
||||
return_type: u32
|
||||
return_purpose: capabilities
|
||||
return_constant: XAP_ROUTE_QMK_CAPABILITIES
|
||||
}
|
||||
0x02: {
|
||||
type: command
|
||||
name: Board identifiers
|
||||
define: BOARD_IDENTIFIERS
|
||||
description:
|
||||
'''
|
||||
Retrieves the set of identifying information for the board.
|
||||
'''
|
||||
return_type: struct
|
||||
return_struct_length: 10
|
||||
return_struct_members: [
|
||||
{
|
||||
type: u16
|
||||
name: Vendor ID
|
||||
},
|
||||
{
|
||||
type: u16
|
||||
name: Product ID
|
||||
},
|
||||
{
|
||||
type: u16
|
||||
name: Product Version
|
||||
},
|
||||
{
|
||||
type: u32
|
||||
name: QMK Unique Identifier
|
||||
}
|
||||
]
|
||||
return_constant: [
|
||||
VENDOR_ID
|
||||
PRODUCT_ID
|
||||
DEVICE_VER
|
||||
XAP_KEYBOARD_IDENTIFIER
|
||||
]
|
||||
}
|
||||
0x03: {
|
||||
type: command
|
||||
name: Board Manufacturer
|
||||
define: BOARD_MANUFACTURER
|
||||
description: Retrieves the name of the manufacturer
|
||||
return_type: string
|
||||
return_constant: QSTR(MANUFACTURER)
|
||||
}
|
||||
0x04: {
|
||||
type: command
|
||||
name: Product Name
|
||||
define: PRODUCT_NAME
|
||||
description: Retrieves the product name
|
||||
return_type: string
|
||||
return_constant: QSTR(PRODUCT)
|
||||
}
|
||||
0x05: {
|
||||
type: command
|
||||
name: Config Blob Length
|
||||
define: CONFIG_BLOB_LEN
|
||||
description: Retrieves the length of the configuration data bundled within the firmware
|
||||
return_type: u16
|
||||
return_constant: CONFIG_BLOB_GZ_LEN
|
||||
}
|
||||
0x06: {
|
||||
type: command
|
||||
name: Config Blob Chunk
|
||||
define: CONFIG_BLOB_CHUNK
|
||||
description: Retrieves a chunk of the configuration data bundled within the firmware
|
||||
request_type: u16
|
||||
request_purpose: offset
|
||||
return_type: u8[32]
|
||||
return_execute: get_config_blob_chunk
|
||||
}
|
||||
0x07: {
|
||||
type: command
|
||||
name: Jump to bootloader
|
||||
define: BOOTLOADER_JUMP
|
||||
permissions: secure
|
||||
enable_if_preprocessor: defined(BOOTLOADER_JUMP_SUPPORTED)
|
||||
description:
|
||||
'''
|
||||
Jump to bootloader
|
||||
|
||||
May not be present - if QMK capabilities query returns “true”, then jump to bootloader is supported
|
||||
|
||||
* 0 means secure routes are disabled, and should be considered as a failure
|
||||
* 1 means successful, board will jump to bootloader
|
||||
'''
|
||||
return_type: u8
|
||||
return_execute: request_bootloader_jump
|
||||
}
|
||||
0x08: {
|
||||
type: command
|
||||
name: Hardware Identifier
|
||||
define: HARDWARE_ID
|
||||
description: Retrieves a unique identifier for the board.
|
||||
return_type: u32[4]
|
||||
return_execute: get_hardware_id
|
||||
}
|
||||
0x09: {
|
||||
type: command
|
||||
name: Reinitialize EEPROM
|
||||
define: EEPROM_RESET
|
||||
permissions: secure
|
||||
enable_if_preprocessor: !defined(NO_RESET)
|
||||
description:
|
||||
'''
|
||||
Reinitializes the keyboard's EEPROM (persistent memory)
|
||||
|
||||
May not be present - if QMK capabilities query returns “true”, then reinitialize is supported
|
||||
|
||||
* 0 means secure routes are disabled, and should be considered as a failure
|
||||
* 1 means successful, board will reinitialize and then reboot
|
||||
'''
|
||||
return_type: u8
|
||||
return_execute: request_eeprom_reset
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
0x02: {
|
||||
type: router
|
||||
name: Keyboard
|
||||
define: KB
|
||||
description:
|
||||
'''
|
||||
This subsystem is always present, and reserved for vendor-specific functionality. No routes are defined by XAP.
|
||||
'''
|
||||
routes: {
|
||||
}
|
||||
},
|
||||
|
||||
0x03: {
|
||||
type: router
|
||||
name: User
|
||||
define: USER
|
||||
description:
|
||||
'''
|
||||
This subsystem is always present, and reserved for user-specific functionality. No routes are defined by XAP.
|
||||
'''
|
||||
routes: {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
200
data/xap/xap_0.2.0.hjson
Executable file
200
data/xap/xap_0.2.0.hjson
Executable file
@ -0,0 +1,200 @@
|
||||
{
|
||||
version: 0.2.0
|
||||
|
||||
routes: {
|
||||
0x04: {
|
||||
type: router
|
||||
name: Keymap
|
||||
define: KEYMAP
|
||||
description:
|
||||
'''
|
||||
This subsystem allows for query of currently configured keycodes.
|
||||
'''
|
||||
routes: {
|
||||
0x01: {
|
||||
type: command
|
||||
name: Capabilities Query
|
||||
define: CAPABILITIES_QUERY
|
||||
description:
|
||||
'''
|
||||
Keymap subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.
|
||||
'''
|
||||
return_type: u32
|
||||
return_purpose: capabilities
|
||||
return_constant: XAP_ROUTE_KEYMAP_CAPABILITIES
|
||||
}
|
||||
0x02: {
|
||||
type: command
|
||||
name: Get Layer Count
|
||||
define: GET_LAYER_COUNT
|
||||
description: Query maximum number of layers that can be addressed within the keymap.
|
||||
return_type: u8
|
||||
return_execute: keymap_get_layer_count
|
||||
}
|
||||
0x03: {
|
||||
type: command
|
||||
name: Get Keycode
|
||||
define: GET_KEYMAP_KEYCODE
|
||||
description: Query the Keycode at the requested location.
|
||||
request_type: struct
|
||||
request_struct_length: 3
|
||||
request_struct_members: [
|
||||
{
|
||||
type: u8
|
||||
name: Layer
|
||||
},
|
||||
{
|
||||
type: u8
|
||||
name: Row
|
||||
},
|
||||
{
|
||||
type: u8
|
||||
name: Column
|
||||
}
|
||||
]
|
||||
return_type: u16
|
||||
return_execute: get_keymap_keycode
|
||||
}
|
||||
0x04: {
|
||||
type: command
|
||||
name: Get Encoder Keycode
|
||||
define: GET_ENCODER_KEYCODE
|
||||
description: Query the Keycode at the requested location.
|
||||
enable_if_preprocessor: defined(ENCODER_MAP_ENABLE)
|
||||
request_type: struct
|
||||
request_struct_length: 3
|
||||
request_struct_members: [
|
||||
{
|
||||
type: u8
|
||||
name: Layer
|
||||
},
|
||||
{
|
||||
type: u8
|
||||
name: Encoder
|
||||
},
|
||||
{
|
||||
type: u8
|
||||
name: Clockwise
|
||||
}
|
||||
]
|
||||
return_type: u16
|
||||
return_execute: get_encoder_keycode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
0x05: {
|
||||
type: router
|
||||
name: Remapping
|
||||
define: REMAPPING
|
||||
description:
|
||||
'''
|
||||
This subsystem allows for live reassignment of keycodes without rebuilding the firmware.
|
||||
'''
|
||||
enable_if_preprocessor: defined(DYNAMIC_KEYMAP_ENABLE)
|
||||
routes: {
|
||||
0x01: {
|
||||
type: command
|
||||
name: Capabilities Query
|
||||
define: CAPABILITIES_QUERY
|
||||
description:
|
||||
'''
|
||||
Remapping subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.
|
||||
'''
|
||||
return_type: u32
|
||||
return_purpose: capabilities
|
||||
return_constant: XAP_ROUTE_REMAPPING_CAPABILITIES
|
||||
}
|
||||
0x02: {
|
||||
type: command
|
||||
name: Get Layer Count
|
||||
define: GET_DYNAMIC_LAYER_COUNT
|
||||
description: Query maximum number of layers that can be addressed within the keymap.
|
||||
return_type: u8
|
||||
return_constant: DYNAMIC_KEYMAP_LAYER_COUNT
|
||||
}
|
||||
0x03: {
|
||||
type: command
|
||||
name: Set Keycode
|
||||
define: SET_KEYMAP_KEYCODE
|
||||
description: Modify the Keycode at the requested location.
|
||||
permissions: secure
|
||||
request_type: struct
|
||||
request_struct_length: 5
|
||||
request_struct_members: [
|
||||
{
|
||||
type: u8
|
||||
name: Layer
|
||||
},
|
||||
{
|
||||
type: u8
|
||||
name: Row
|
||||
},
|
||||
{
|
||||
type: u8
|
||||
name: Column
|
||||
},
|
||||
{
|
||||
type: u16
|
||||
name: Keycode
|
||||
}
|
||||
]
|
||||
return_execute: dynamic_keymap_set_keycode
|
||||
}
|
||||
0x04: {
|
||||
type: command
|
||||
name: Set Encoder Keycode
|
||||
define: SET_ENCODER_KEYCODE
|
||||
permissions: secure
|
||||
description: Modify the Keycode at the requested location.
|
||||
enable_if_preprocessor: defined(ENCODER_MAP_ENABLE)
|
||||
request_type: struct
|
||||
request_struct_length: 5
|
||||
request_struct_members: [
|
||||
{
|
||||
type: u8
|
||||
name: Layer
|
||||
},
|
||||
{
|
||||
type: u8
|
||||
name: Encoder
|
||||
},
|
||||
{
|
||||
type: u8
|
||||
name: Clockwise
|
||||
},
|
||||
{
|
||||
type: u16
|
||||
name: Keycode
|
||||
}
|
||||
]
|
||||
return_execute: dynamic_encoder_set_keycode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
0x06: {
|
||||
type: router
|
||||
name: Lighting
|
||||
define: LIGHTING
|
||||
description:
|
||||
'''
|
||||
This subsystem allows for control over the lighting subsystem.
|
||||
'''
|
||||
routes: {
|
||||
0x01: {
|
||||
type: command
|
||||
name: Capabilities Query
|
||||
define: CAPABILITIES_QUERY
|
||||
description:
|
||||
'''
|
||||
Lighting subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.
|
||||
'''
|
||||
return_type: u32
|
||||
return_purpose: capabilities
|
||||
return_constant: XAP_ROUTE_LIGHTING_CAPABILITIES
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
396
data/xap/xap_0.3.0.hjson
Normal file
396
data/xap/xap_0.3.0.hjson
Normal file
@ -0,0 +1,396 @@
|
||||
{
|
||||
version: 0.3.0
|
||||
|
||||
routes: {
|
||||
|
||||
0x06: {
|
||||
routes: {
|
||||
|
||||
0x02: {
|
||||
type: router
|
||||
name: backlight
|
||||
define: BACKLIGHT
|
||||
description:
|
||||
'''
|
||||
This subsystem allows for control over the backlight subsystem.
|
||||
'''
|
||||
enable_if_preprocessor: defined(BACKLIGHT_ENABLE)
|
||||
routes: {
|
||||
0x01: {
|
||||
type: command
|
||||
name: Capabilities Query
|
||||
define: CAPABILITIES_QUERY
|
||||
description:
|
||||
'''
|
||||
backlight subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.
|
||||
'''
|
||||
return_type: u32
|
||||
return_purpose: capabilities
|
||||
return_constant: XAP_ROUTE_LIGHTING_BACKLIGHT_CAPABILITIES
|
||||
}
|
||||
0x02: {
|
||||
type: command
|
||||
name: Get Enabled Effects
|
||||
define: GET_ENABLED_EFFECTS
|
||||
description: Each bit should be considered as a "usable" effect id
|
||||
return_type: u8
|
||||
return_constant: ENABLED_BACKLIGHT_EFFECTS
|
||||
}
|
||||
0x03: {
|
||||
type: command
|
||||
name: Get Config
|
||||
define: GET_CONFIG
|
||||
description: Query the current config.
|
||||
return_type: struct
|
||||
return_struct_length: 3
|
||||
return_struct_members: [
|
||||
{
|
||||
type: u8
|
||||
name: enable
|
||||
},
|
||||
{
|
||||
type: u8
|
||||
name: mode
|
||||
},
|
||||
{
|
||||
type: u8
|
||||
name: val
|
||||
},
|
||||
]
|
||||
return_execute: get_backlight_config
|
||||
}
|
||||
0x04: {
|
||||
type: command
|
||||
name: Set Config
|
||||
define: SET_CONFIG
|
||||
description: Set the current config.
|
||||
request_type: struct
|
||||
request_struct_length: 3
|
||||
request_struct_members: [
|
||||
{
|
||||
type: u8
|
||||
name: enable
|
||||
},
|
||||
{
|
||||
type: u8
|
||||
name: mode
|
||||
},
|
||||
{
|
||||
type: u8
|
||||
name: val
|
||||
},
|
||||
]
|
||||
return_execute: set_backlight_config
|
||||
}
|
||||
0x05: {
|
||||
type: command
|
||||
name: Save Config
|
||||
define: SAVE_CONFIG
|
||||
description: Save the current config.
|
||||
return_execute: save_backlight_config
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
0x03: {
|
||||
type: router
|
||||
name: rgblight
|
||||
define: RGBLIGHT
|
||||
description:
|
||||
'''
|
||||
This subsystem allows for control over the rgblight subsystem.
|
||||
'''
|
||||
enable_if_preprocessor: defined(RGBLIGHT_ENABLE)
|
||||
routes: {
|
||||
0x01: {
|
||||
type: command
|
||||
name: Capabilities Query
|
||||
define: CAPABILITIES_QUERY
|
||||
description:
|
||||
'''
|
||||
rgblight subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.
|
||||
'''
|
||||
return_type: u32
|
||||
return_purpose: capabilities
|
||||
return_constant: XAP_ROUTE_LIGHTING_RGBLIGHT_CAPABILITIES
|
||||
}
|
||||
0x02: {
|
||||
type: command
|
||||
name: Get Enabled Effects
|
||||
define: GET_ENABLED_EFFECTS
|
||||
description: Each bit should be considered as a "usable" effect id
|
||||
return_type: u64
|
||||
return_constant: ENABLED_RGBLIGHT_EFFECTS
|
||||
}
|
||||
0x03: {
|
||||
type: command
|
||||
name: Get Config
|
||||
define: GET_CONFIG
|
||||
description: Query the current config.
|
||||
return_type: struct
|
||||
return_struct_length: 6
|
||||
return_struct_members: [
|
||||
{
|
||||
type: u8
|
||||
name: enable
|
||||
},
|
||||
{
|
||||
type: u8
|
||||
name: mode
|
||||
},
|
||||
{
|
||||
type: u8
|
||||
name: hue
|
||||
},
|
||||
{
|
||||
type: u8
|
||||
name: sat
|
||||
},
|
||||
{
|
||||
type: u8
|
||||
name: val
|
||||
},
|
||||
{
|
||||
type: u8
|
||||
name: speed
|
||||
},
|
||||
]
|
||||
return_execute: get_rgblight_config
|
||||
}
|
||||
0x04: {
|
||||
type: command
|
||||
name: Set Config
|
||||
define: SET_CONFIG
|
||||
description: Set the current config.
|
||||
request_type: struct
|
||||
request_struct_length: 6
|
||||
request_struct_members: [
|
||||
{
|
||||
type: u8
|
||||
name: enable
|
||||
},
|
||||
{
|
||||
type: u8
|
||||
name: mode
|
||||
},
|
||||
{
|
||||
type: u8
|
||||
name: hue
|
||||
},
|
||||
{
|
||||
type: u8
|
||||
name: sat
|
||||
},
|
||||
{
|
||||
type: u8
|
||||
name: val
|
||||
},
|
||||
{
|
||||
type: u8
|
||||
name: speed
|
||||
},
|
||||
]
|
||||
return_execute: set_rgblight_config
|
||||
}
|
||||
0x05: {
|
||||
type: command
|
||||
name: Save Config
|
||||
define: SAVE_CONFIG
|
||||
description: Save the current config.
|
||||
return_execute: save_rgblight_config
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
0x04: {
|
||||
type: router
|
||||
name: rgbmatrix
|
||||
define: RGB_MATRIX
|
||||
description:
|
||||
'''
|
||||
This subsystem allows for control over the rgb matrix subsystem.
|
||||
'''
|
||||
enable_if_preprocessor: defined(RGB_MATRIX_ENABLE)
|
||||
routes: {
|
||||
0x01: {
|
||||
type: command
|
||||
name: Capabilities Query
|
||||
define: CAPABILITIES_QUERY
|
||||
description:
|
||||
'''
|
||||
rgb matrix subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.
|
||||
'''
|
||||
return_type: u32
|
||||
return_purpose: capabilities
|
||||
return_constant: XAP_ROUTE_LIGHTING_RGB_MATRIX_CAPABILITIES
|
||||
}
|
||||
0x02: {
|
||||
type: command
|
||||
name: Get Enabled Effects
|
||||
define: GET_ENABLED_EFFECTS
|
||||
description: Each bit should be considered as a "usable" effect id
|
||||
return_type: u64
|
||||
return_constant: ENABLED_RGB_MATRIX_EFFECTS
|
||||
}
|
||||
0x03: {
|
||||
type: command
|
||||
name: Get Config
|
||||
define: GET_CONFIG
|
||||
description: Query the current config.
|
||||
return_type: struct
|
||||
return_struct_length: 7
|
||||
return_struct_members: [
|
||||
{
|
||||
type: u8
|
||||
name: enable
|
||||
},
|
||||
{
|
||||
type: u8
|
||||
name: mode
|
||||
},
|
||||
{
|
||||
type: u8
|
||||
name: hue
|
||||
},
|
||||
{
|
||||
type: u8
|
||||
name: sat
|
||||
},
|
||||
{
|
||||
type: u8
|
||||
name: val
|
||||
},
|
||||
{
|
||||
type: u8
|
||||
name: speed
|
||||
},
|
||||
{
|
||||
type: u8
|
||||
name: flags
|
||||
},
|
||||
]
|
||||
return_execute: get_rgb_matrix_config
|
||||
}
|
||||
0x04: {
|
||||
type: command
|
||||
name: Set Config
|
||||
define: SET_CONFIG
|
||||
description: Set the current config.
|
||||
request_type: struct
|
||||
request_struct_length: 7
|
||||
request_struct_members: [
|
||||
{
|
||||
type: u8
|
||||
name: enable
|
||||
},
|
||||
{
|
||||
type: u8
|
||||
name: mode
|
||||
},
|
||||
{
|
||||
type: u8
|
||||
name: hue
|
||||
},
|
||||
{
|
||||
type: u8
|
||||
name: sat
|
||||
},
|
||||
{
|
||||
type: u8
|
||||
name: val
|
||||
},
|
||||
{
|
||||
type: u8
|
||||
name: speed
|
||||
},
|
||||
{
|
||||
type: u8
|
||||
name: flags
|
||||
},
|
||||
]
|
||||
return_execute: set_rgb_matrix_config
|
||||
}
|
||||
0x05: {
|
||||
type: command
|
||||
name: Save Config
|
||||
define: SAVE_CONFIG
|
||||
description: Save the current config.
|
||||
return_execute: save_rgb_matrix_config
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
0x07: {
|
||||
type: router
|
||||
name: Audio
|
||||
define: AUDIO
|
||||
description:
|
||||
'''
|
||||
This subsystem allows for control over the audio subsystem.
|
||||
'''
|
||||
enable_if_preprocessor: defined(AUDIO_ENABLE)
|
||||
routes: {
|
||||
0x01: {
|
||||
type: command
|
||||
name: Capabilities Query
|
||||
define: CAPABILITIES_QUERY
|
||||
description:
|
||||
'''
|
||||
Audio subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.
|
||||
'''
|
||||
return_type: u32
|
||||
return_purpose: capabilities
|
||||
return_constant: XAP_ROUTE_AUDIO_CAPABILITIES
|
||||
}
|
||||
|
||||
0x03: {
|
||||
type: command
|
||||
name: Get Config
|
||||
define: GET_CONFIG
|
||||
description: Query the current config.
|
||||
return_type: struct
|
||||
return_struct_length: 2
|
||||
return_struct_members: [
|
||||
{
|
||||
type: u8
|
||||
name: enable
|
||||
},
|
||||
{
|
||||
type: u8
|
||||
name: clicky_enable
|
||||
},
|
||||
]
|
||||
return_execute: get_audio_config
|
||||
}
|
||||
0x04: {
|
||||
type: command
|
||||
name: Set Config
|
||||
define: SET_CONFIG
|
||||
description: Set the current config.
|
||||
request_type: struct
|
||||
request_struct_length: 2
|
||||
request_struct_members: [
|
||||
{
|
||||
type: u8
|
||||
name: enable
|
||||
},
|
||||
{
|
||||
type: u8
|
||||
name: clicky_enable
|
||||
},
|
||||
]
|
||||
return_execute: set_audio_config
|
||||
}
|
||||
0x05: {
|
||||
type: command
|
||||
name: Save Config
|
||||
define: SAVE_CONFIG
|
||||
description: Save the current config.
|
||||
return_execute: save_audio_config
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -138,7 +138,8 @@
|
||||
{ "text": "Tri Layer", "link": "/features/tri_layer" },
|
||||
{ "text": "Unicode", "link": "/features/unicode" },
|
||||
{ "text": "Userspace", "link": "/feature_userspace" },
|
||||
{ "text": "WPM Calculation", "link": "/features/wpm" }
|
||||
{ "text": "WPM Calculation", "link": "/features/wpm" },
|
||||
{ "text": "XAP", "link": "/xap" }
|
||||
]
|
||||
},
|
||||
{
|
||||
|
95
docs/xap.md
Normal file
95
docs/xap.md
Normal file
@ -0,0 +1,95 @@
|
||||
# XAP
|
||||
|
||||
XAP (“extensible application protocol”) API intends to provide access to various QMK subsystems.
|
||||
|
||||
## Overview
|
||||
|
||||
```mermaid
|
||||
%%{init: {'themeVariables': { 'fontSize': '24px'}}}%%
|
||||
flowchart LR
|
||||
dev[QMK Device] <-- XAP --> host[Host Computer]
|
||||
```
|
||||
|
||||
The intention is to provide access to QMK subsystems through a versioned and documented protocol.
|
||||
|
||||
## Protocol Reference
|
||||
|
||||
[protocol_versions](xap_protocol.md ':include')
|
||||
|
||||
## Clients
|
||||
|
||||
TODO
|
||||
|
||||
## CLI
|
||||
|
||||
The QMK CLI provides a few XAP specific commands for diagnosis purposes.
|
||||
|
||||
### List Connected Devices
|
||||
Simple
|
||||
```
|
||||
$ qmk xap --list
|
||||
Ψ Available devices:
|
||||
Ψ 7844:8450 KPrepublic XD84 Pro [API:0.2.0] LOCKED
|
||||
```
|
||||
|
||||
Verbose
|
||||
```
|
||||
$ qmk --verbose xap --list
|
||||
Ψ Available devices:
|
||||
Ψ 7844:8450 KPrepublic XD84 Pro [API:0.2.0] LOCKED
|
||||
_id: 553831323538150A2113000000000000
|
||||
backlight.pin: F5
|
||||
bootloader: atmel-dfu
|
||||
community_layouts: 75_ansi, 75_iso
|
||||
debounce: 5
|
||||
diode_direction: COL2ROW
|
||||
features.audio: False
|
||||
features.backlight: True
|
||||
features.bootmagic: True
|
||||
features.command: False
|
||||
features.console: False
|
||||
features.extrakey: True
|
||||
features.mousekey: False
|
||||
features.nkro: True
|
||||
features.rgblight: True
|
||||
indicators.caps_lock: B2
|
||||
keyboard_folder: xiudi/xd84pro
|
||||
keyboard_name: XD84 Pro
|
||||
layouts: LAYOUT_75_ansi, LAYOUT_75_iso, LAYOUT_all
|
||||
maintainer: qmk
|
||||
manufacturer: KPrepublic
|
||||
matrix_pins.cols: B1, B3, B4, B5, B6, B7, C6, C7, D4, D6, D7, E6, F0, F1, F7
|
||||
matrix_pins.rows: D0, D1, D2, D3, D5, F4
|
||||
matrix_pins.unused: B0, E2
|
||||
matrix_size.cols: 15
|
||||
matrix_size.rows: 6
|
||||
mouse_key.enabled: False
|
||||
platform: unknown
|
||||
processor: atmega32u4
|
||||
processor_type: avr
|
||||
protocol: LUFA
|
||||
rgblight.animations.all: False
|
||||
rgblight.led_count: 12
|
||||
rgblight.pin: F6
|
||||
rgblight.sleep: False
|
||||
url:
|
||||
usb.device_ver: 0x0001
|
||||
usb.device_version: 0.0.1
|
||||
usb.pid: 0x8450
|
||||
usb.vid: 0x7844
|
||||
```
|
||||
|
||||
### Interactive shell
|
||||
```
|
||||
$ qmk xap -i
|
||||
Ψ Connected to:7844:8450 KPrepublic XD84 Pro
|
||||
Welcome to the XAP shell. Type help or ? to list commands.
|
||||
|
||||
Ψ> help
|
||||
|
||||
Documented commands (type help <topic>):
|
||||
========================================
|
||||
EOF about exit help keycode keymap layer listen unlock
|
||||
|
||||
Ψ>
|
||||
```
|
108
docs/xap_0.0.1.md
Normal file
108
docs/xap_0.0.1.md
Normal file
@ -0,0 +1,108 @@
|
||||
<!--- Copyright 2024 QMK --->
|
||||
<!--- SPDX-License-Identifier: GPL-2.0-or-later --->
|
||||
|
||||
<!---
|
||||
*******************************************************************************
|
||||
88888888888 888 d8b .d888 d8b 888 d8b
|
||||
888 888 Y8P d88P" Y8P 888 Y8P
|
||||
888 888 888 888
|
||||
888 88888b. 888 .d8888b 888888 888 888 .d88b. 888 .d8888b
|
||||
888 888 "88b 888 88K 888 888 888 d8P Y8b 888 88K
|
||||
888 888 888 888 "Y8888b. 888 888 888 88888888 888 "Y8888b.
|
||||
888 888 888 888 X88 888 888 888 Y8b. 888 X88
|
||||
888 888 888 888 88888P' 888 888 888 "Y8888 888 88888P'
|
||||
888 888
|
||||
888 888
|
||||
888 888
|
||||
.d88b. .d88b. 88888b. .d88b. 888d888 8888b. 888888 .d88b. .d88888
|
||||
d88P"88b d8P Y8b 888 "88b d8P Y8b 888P" "88b 888 d8P Y8b d88" 888
|
||||
888 888 88888888 888 888 88888888 888 .d888888 888 88888888 888 888
|
||||
Y88b 888 Y8b. 888 888 Y8b. 888 888 888 Y88b. Y8b. Y88b 888
|
||||
"Y88888 "Y8888 888 888 "Y8888 888 "Y888888 "Y888 "Y8888 "Y88888
|
||||
888
|
||||
Y8b d88P
|
||||
"Y88P"
|
||||
*******************************************************************************
|
||||
--->
|
||||
# QMK Firmware XAP Specs
|
||||
|
||||
This document describes the requirements of the QMK XAP ("extensible application protocol") API.
|
||||
|
||||
## Types
|
||||
|
||||
**All integral types are little-endian.**
|
||||
|
||||
| Name | Definition |
|
||||
| -- | -- |
|
||||
| _type[n]_ | An array of `type`, with array extent of `N` -- e.g. `u8[2]` signifies two consecutive octets. |
|
||||
| _u16_ | An unsigned 16-bit integral, commonly seen as `uint16_t` from _stdint.h_. |
|
||||
| _u32_ | An unsigned 32-bit integral, commonly seen as `uint32_t` from _stdint.h_. |
|
||||
| _u8_ | An unsigned 8-bit integral (octet, or byte), commonly seen as `uint8_t` from _stdint.h_. |
|
||||
|
||||
## Definitions
|
||||
|
||||
This list defines the terms used across the entire set of XAP protocol documentation.
|
||||
|
||||
| Name | Definition |
|
||||
| -- | -- |
|
||||
| _Handler_ | A piece of code that is executed when a specific _route_ is received. |
|
||||
| _Payload_ | Any received data appended to the _route_, which gets delivered to the _handler_ when received. |
|
||||
| _Response_ | The data sent back to the host during execution of a _handler_. |
|
||||
| _Route_ | A sequence of _IDs_ describing the route to invoke a _handler_. |
|
||||
| _Subsystem_ | A high-level area of functionality within XAP. |
|
||||
| _ID_ | A single octet / 8-bit byte, representing Subsystem or Route index. |
|
||||
| _Request Header_ | Packet format for inbound data. Takes the format:<br>`token` - token<br>`u8` - length |
|
||||
| _Response Flags_ | An `u8` containing the status of the request. |
|
||||
| _Response Header_ | Packet format for outbound data. Takes the format:<br>`token` - token<br>`response_flags` - flags<br>`u8` - length |
|
||||
| _Token_ | A `u16` associated with a specific request as well as its corresponding response. Valid token values are within the range `0x0100`-`0xFFFF`. |
|
||||
|
||||
## Requests and Responses
|
||||
|
||||
Communication generally follows a request/response pattern.
|
||||
|
||||
Each request needs to include a _token_ -- this `u16` value prefixes each outbound request from the host application and its corresponding response.
|
||||
This allows response messages to be correlated with their request, even if multiple host applications are communicating with the firmware simultaneously.
|
||||
Host applications should randomly generate a token ID for **every** outbound request, unless using a reserved token defined below.
|
||||
To ensure host interoperability, valid token values are within the range `0x0100`-`0xFFFF`.
|
||||
|
||||
This token is followed by a `u8` signifying the length of data in the request.
|
||||
|
||||
|
||||
|
||||
Response messages will always be prefixed by the originating request _token_, directly followed by that request's _response flags_, then the response payload length:
|
||||
|
||||
| Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
|
||||
| -- | -- | -- | -- | -- | -- | -- | -- |
|
||||
| `-` | `-` | `-` | `-` | `-` | `-` | `-` | `SUCCESS` |
|
||||
|
||||
* Bit 0 (`SUCCESS`): When this bit is set, the request was successfully handled. If not set, all payload data should be disregarded, and the request retried if appropriate (with a new token).
|
||||
|
||||
### Example "conversation":
|
||||
|
||||
**Request** -- version query:
|
||||
|
||||
| Byte | 0 | 1 | 2 | 3 | 4 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| **Purpose** | Token | Token | Payload Length | Route | Route |
|
||||
| **Value** | `0x43` | `0x2B` | `0x02` | `0x00` | `0x00` |
|
||||
|
||||
**Response** -- matching token, successful flag, payload of `0x03170192` = 3.17.192:
|
||||
|
||||
| Byte | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| **Purpose** | Token | Token | Response Flags | Payload Length | Payload | Payload | Payload | Payload |
|
||||
| **Value** | `0x43` | `0x2B` | `0x01` | `0x04` | `0x92` | `0x01` | `0x17` | `0x03` |
|
||||
|
||||
## Routes
|
||||
|
||||
Subsystem validity should be queried through the “Enabled-in-firmware subsystem query” under the QMK subsystem (route=0x00,0x01).
|
||||
This is the primary method for determining if a subsystem has been enabled in the running firmware.
|
||||
|
||||
### XAP - `0x00`
|
||||
This subsystem is always present, and provides the ability to query information about the XAP protocol of the connected device.
|
||||
|
||||
| Name | Route | Tags | Payloads | Description |
|
||||
| -- | -- | -- | -- | -- |
|
||||
| Version Query | `0x00 0x00` | | __Response:__ `u32` | XAP protocol version query.<br><br>* Returns the BCD-encoded version in the format of XX.YY.ZZZZ => `0xXXYYZZZZ`<br> * e.g. 3.2.115 will match `0x03020115`, or bytes {0x15,0x01,0x02,0x03}.|
|
||||
|
||||
|
176
docs/xap_0.1.0.md
Normal file
176
docs/xap_0.1.0.md
Normal file
@ -0,0 +1,176 @@
|
||||
<!--- Copyright 2024 QMK --->
|
||||
<!--- SPDX-License-Identifier: GPL-2.0-or-later --->
|
||||
|
||||
<!---
|
||||
*******************************************************************************
|
||||
88888888888 888 d8b .d888 d8b 888 d8b
|
||||
888 888 Y8P d88P" Y8P 888 Y8P
|
||||
888 888 888 888
|
||||
888 88888b. 888 .d8888b 888888 888 888 .d88b. 888 .d8888b
|
||||
888 888 "88b 888 88K 888 888 888 d8P Y8b 888 88K
|
||||
888 888 888 888 "Y8888b. 888 888 888 88888888 888 "Y8888b.
|
||||
888 888 888 888 X88 888 888 888 Y8b. 888 X88
|
||||
888 888 888 888 88888P' 888 888 888 "Y8888 888 88888P'
|
||||
888 888
|
||||
888 888
|
||||
888 888
|
||||
.d88b. .d88b. 88888b. .d88b. 888d888 8888b. 888888 .d88b. .d88888
|
||||
d88P"88b d8P Y8b 888 "88b d8P Y8b 888P" "88b 888 d8P Y8b d88" 888
|
||||
888 888 88888888 888 888 88888888 888 .d888888 888 88888888 888 888
|
||||
Y88b 888 Y8b. 888 888 Y8b. 888 888 888 Y88b. Y8b. Y88b 888
|
||||
"Y88888 "Y8888 888 888 "Y8888 888 "Y888888 "Y888 "Y8888 "Y88888
|
||||
888
|
||||
Y8b d88P
|
||||
"Y88P"
|
||||
*******************************************************************************
|
||||
--->
|
||||
# QMK Firmware XAP Specs
|
||||
|
||||
This document describes the requirements of the QMK XAP ("extensible application protocol") API.
|
||||
|
||||
## Types
|
||||
|
||||
**All integral types are little-endian.**
|
||||
|
||||
| Name | Definition |
|
||||
| -- | -- |
|
||||
| _bool_ | Data type that contains values 0 and 1. Implemented as an alias of `u8`. |
|
||||
| _struct{}_ | A structure of data, packing different objects together. Data is "compacted" -- there are no padding bytes between fields. Equivalent to a packed C-style `struct`. The order in which they're defined matches the order of the data in the response packet. |
|
||||
| _type[n]_ | An array of `type`, with array extent of `N` -- e.g. `u8[2]` signifies two consecutive octets. |
|
||||
| _u16_ | An unsigned 16-bit integral, commonly seen as `uint16_t` from _stdint.h_. |
|
||||
| _u32_ | An unsigned 32-bit integral, commonly seen as `uint32_t` from _stdint.h_. |
|
||||
| _u64_ | An unsigned 64-bit integral, commonly seen as `uint64_t` from _stdint.h_. |
|
||||
| _u8_ | An unsigned 8-bit integral (octet, or byte), commonly seen as `uint8_t` from _stdint.h_. |
|
||||
|
||||
## Definitions
|
||||
|
||||
This list defines the terms used across the entire set of XAP protocol documentation.
|
||||
|
||||
| Name | Definition |
|
||||
| -- | -- |
|
||||
| _Capability_ | A way to determine if certain functionality is enabled in the firmware. Any _subsystem_ that provides build-time restriction of functionality must provide a _route_ for a _capabilities query_. |
|
||||
| _Handler_ | A piece of code that is executed when a specific _route_ is received. |
|
||||
| _Payload_ | Any received data appended to the _route_, which gets delivered to the _handler_ when received. |
|
||||
| _Response_ | The data sent back to the host during execution of a _handler_. |
|
||||
| _Route_ | A sequence of _IDs_ describing the route to invoke a _handler_. |
|
||||
| _Secure Route_ | A _route_ which has potentially destructive consequences, necessitating prior approval by the user before executing. |
|
||||
| _Subsystem_ | A high-level area of functionality within XAP. |
|
||||
| _Unlock sequence_ | A physical sequence initiated by the user to enable execution of _secure routes_. |
|
||||
| _Broadcast Header_ | Packet format for broadcast messages. Takes the format:<br>`token` - token<br>`u8` - type<br>`u8` - length |
|
||||
| _ID_ | A single octet / 8-bit byte, representing Subsystem or Route index. |
|
||||
| _Request Header_ | Packet format for inbound data. Takes the format:<br>`token` - token<br>`u8` - length |
|
||||
| _Response Flags_ | An `u8` containing the status of the request. |
|
||||
| _Response Header_ | Packet format for outbound data. Takes the format:<br>`token` - token<br>`response_flags` - flags<br>`u8` - length |
|
||||
| _Token_ | A `u16` associated with a specific request as well as its corresponding response. Valid token values are within the range `0x0100`-`0xFFFF`. |
|
||||
|
||||
## Requests and Responses
|
||||
|
||||
Communication generally follows a request/response pattern.
|
||||
|
||||
Each request needs to include a _token_ -- this `u16` value prefixes each outbound request from the host application and its corresponding response.
|
||||
This allows response messages to be correlated with their request, even if multiple host applications are communicating with the firmware simultaneously.
|
||||
Host applications should randomly generate a token ID for **every** outbound request, unless using a reserved token defined below.
|
||||
To ensure host interoperability, valid token values are within the range `0x0100`-`0xFFFF`.
|
||||
|
||||
This token is followed by a `u8` signifying the length of data in the request.
|
||||
|
||||
Two token values are reserved: `0xFFFE` and `0xFFFF`:
|
||||
* `0xFFFE`: A message sent by a host application may use this token if no response is to be sent -- a "fire and forget" message.
|
||||
* `0xFFFF`: Signifies a "broadcast" message sent by the firmware without prompting from the host application. Broadcast messages are defined later in this document.
|
||||
|
||||
Any request will generate at least one corresponding response, with the exception of messages using reserved tokens. Maximum total message length is 128 bytes due to RAM constraints.
|
||||
|
||||
Response messages will always be prefixed by the originating request _token_, directly followed by that request's _response flags_, then the response payload length:
|
||||
|
||||
| Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
|
||||
| -- | -- | -- | -- | -- | -- | -- | -- |
|
||||
| `-` | `-` | `-` | `-` | `-` | `-` | `SECURE_FAILURE` | `SUCCESS` |
|
||||
|
||||
* Bit 1 (`SECURE_FAILURE`): When this bit is set, the requested _route_ was marked _secure_ but an _unlock sequence_ has not completed.
|
||||
* Bit 0 (`SUCCESS`): When this bit is set, the request was successfully handled. If not set, all payload data should be disregarded, and the request retried if appropriate (with a new token).
|
||||
|
||||
### Example "conversation":
|
||||
|
||||
**Request** -- version query:
|
||||
|
||||
| Byte | 0 | 1 | 2 | 3 | 4 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| **Purpose** | Token | Token | Payload Length | Route | Route |
|
||||
| **Value** | `0x43` | `0x2B` | `0x02` | `0x00` | `0x00` |
|
||||
|
||||
**Response** -- matching token, successful flag, payload of `0x03170192` = 3.17.192:
|
||||
|
||||
| Byte | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| **Purpose** | Token | Token | Response Flags | Payload Length | Payload | Payload | Payload | Payload |
|
||||
| **Value** | `0x43` | `0x2B` | `0x01` | `0x04` | `0x92` | `0x01` | `0x17` | `0x03` |
|
||||
|
||||
## Routes
|
||||
|
||||
Subsystem validity should be queried through the “Enabled-in-firmware subsystem query” under the QMK subsystem (route=0x00,0x01).
|
||||
This is the primary method for determining if a subsystem has been enabled in the running firmware.
|
||||
|
||||
### XAP - `0x00`
|
||||
This subsystem is always present, and provides the ability to query information about the XAP protocol of the connected device.
|
||||
|
||||
| Name | Route | Tags | Payloads | Description |
|
||||
| -- | -- | -- | -- | -- |
|
||||
| Version Query | `0x00 0x00` | | __Response:__ `u32` | XAP protocol version query.<br><br>* Returns the BCD-encoded version in the format of XX.YY.ZZZZ => `0xXXYYZZZZ`<br> * e.g. 3.2.115 will match `0x03020115`, or bytes {0x15,0x01,0x02,0x03}.|
|
||||
| Capabilities Query | `0x00 0x01` | | __Response:__ `u32` | XAP subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.|
|
||||
| Enabled subsystem query | `0x00 0x02` | | __Response:__ `u32` | XAP protocol subsystem query. Each bit should be considered as a "usable" subsystem. For example, checking `(value & (1 << XAP_ROUTE_QMK) != 0)` means the QMK subsystem is enabled and available for querying.|
|
||||
| Secure Status | `0x00 0x03` | | __Response:__ `u8` | Query secure route status<br><br>* 0 means secure routes are disabled<br>* 1 means unlock sequence initiated but incomplete<br>* 2 means secure routes are allowed<br>* any other value should be interpreted as disabled|
|
||||
| Secure Unlock | `0x00 0x04` | | | Initiate secure route unlock sequence|
|
||||
| Secure Lock | `0x00 0x05` | | | Disable secure routes|
|
||||
|
||||
### QMK - `0x01`
|
||||
This subsystem is always present, and provides the ability to address QMK-specific functionality.
|
||||
|
||||
| Name | Route | Tags | Payloads | Description |
|
||||
| -- | -- | -- | -- | -- |
|
||||
| Version Query | `0x01 0x00` | | __Response:__ `u32` | QMK protocol version query.<br><br>* Returns the BCD-encoded version in the format of XX.YY.ZZZZ => `0xXXYYZZZZ`<br> * e.g. 3.2.115 will match `0x03020115`, or bytes {0x15,0x01,0x02,0x03}.|
|
||||
| Capabilities Query | `0x01 0x01` | | __Response:__ `u32` | QMK subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.|
|
||||
| Board identifiers | `0x01 0x02` | | __Response:__<br> * Vendor ID: `u16`<br> * Product ID: `u16`<br> * Product Version: `u16`<br> * QMK Unique Identifier: `u32` | Retrieves the set of identifying information for the board.|
|
||||
| Board Manufacturer | `0x01 0x03` | | __Response:__ `string` | Retrieves the name of the manufacturer|
|
||||
| Product Name | `0x01 0x04` | | __Response:__ `string` | Retrieves the product name|
|
||||
| Config Blob Length | `0x01 0x05` | | __Response:__ `u16` | Retrieves the length of the configuration data bundled within the firmware|
|
||||
| Config Blob Chunk | `0x01 0x06` | | __Request:__ `u16`<br><br>__Response:__ `u8[32]` | Retrieves a chunk of the configuration data bundled within the firmware|
|
||||
| Jump to bootloader | `0x01 0x07` | __Secure__ | __Response:__ `u8` | Jump to bootloader<br><br>May not be present - if QMK capabilities query returns “true”, then jump to bootloader is supported<br><br>* 0 means secure routes are disabled, and should be considered as a failure<br>* 1 means successful, board will jump to bootloader|
|
||||
| Hardware Identifier | `0x01 0x08` | | __Response:__ `u32[4]` | Retrieves a unique identifier for the board.|
|
||||
| Reinitialize EEPROM | `0x01 0x09` | __Secure__ | __Response:__ `u8` | Reinitializes the keyboard's EEPROM (persistent memory)<br><br>May not be present - if QMK capabilities query returns “true”, then reinitialize is supported<br><br>* 0 means secure routes are disabled, and should be considered as a failure<br>* 1 means successful, board will reinitialize and then reboot|
|
||||
|
||||
### Keyboard - `0x02`
|
||||
This subsystem is always present, and reserved for vendor-specific functionality. No routes are defined by XAP.
|
||||
|
||||
### User - `0x03`
|
||||
This subsystem is always present, and reserved for user-specific functionality. No routes are defined by XAP.
|
||||
|
||||
|
||||
## Broadcast messages
|
||||
|
||||
Broadcast messages may be sent by the firmware to the host, without a corresponding inbound request. Each broadcast message uses the token `0xFFFF`, and does not expect a response from the host. Tokens are followed by an _ID_ signifying the type of broadcast, then the response _payload_ length, and finally the corresponding _payload_.
|
||||
|
||||
### Log message - `0x00`
|
||||
Replicates and replaces the same functionality as if using the standard QMK `CONSOLE_ENABLE = yes` in `rules.mk`. Normal prints within the firmware will manifest as log messages broadcast to the host. `hid_listen` will not be functional with XAP enabled.
|
||||
|
||||
Log message payloads include `u8[Length]` containing the text, where the length of the text is the _broadcast_header.length_ field.
|
||||
|
||||
**Example Log Broadcast** -- log message "Hello QMK!"
|
||||
|
||||
| Byte | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| **Purpose** | Token | Token | Broadcast Type | Length | Payload | Payload | Payload | Payload | Payload | Payload | Payload | Payload | Payload | Payload |
|
||||
| **Value** | `0xFF` | `0xFF` | `0x00` | `0x0A`(10) | `0x48`(H) | `0x65`(e) | `0x6C`(l) | `0x6C`(l) | `0x6F`(o) | `0x20`( ) | `0x51`(Q) | `0x4D`(M) | `0x4B`(K) | `0x21`(!) |
|
||||
### Secure Status - `0x01`
|
||||
Secure status has changed. Payloads include a `u8` matching a 'Secure Status' request.
|
||||
|
||||
**Example Secure Status Broadcast** -- secure "Unlocking"
|
||||
|
||||
| Byte | 0 | 1 | 2 | 3 | 4 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| **Purpose** | Token | Token | Broadcast Type | Length | Secure Status |
|
||||
| **Value** | `0xFF` | `0xFF` | `0x01` | `0x01` | `0x01` |
|
||||
### Keyboard - `0x02`
|
||||
Reserved for vendor-specific functionality. No messages are defined by XAP.
|
||||
### User - `0x03`
|
||||
Reserved for user-specific functionality. No messages are defined by XAP.
|
||||
|
203
docs/xap_0.2.0.md
Normal file
203
docs/xap_0.2.0.md
Normal file
@ -0,0 +1,203 @@
|
||||
<!--- Copyright 2024 QMK --->
|
||||
<!--- SPDX-License-Identifier: GPL-2.0-or-later --->
|
||||
|
||||
<!---
|
||||
*******************************************************************************
|
||||
88888888888 888 d8b .d888 d8b 888 d8b
|
||||
888 888 Y8P d88P" Y8P 888 Y8P
|
||||
888 888 888 888
|
||||
888 88888b. 888 .d8888b 888888 888 888 .d88b. 888 .d8888b
|
||||
888 888 "88b 888 88K 888 888 888 d8P Y8b 888 88K
|
||||
888 888 888 888 "Y8888b. 888 888 888 88888888 888 "Y8888b.
|
||||
888 888 888 888 X88 888 888 888 Y8b. 888 X88
|
||||
888 888 888 888 88888P' 888 888 888 "Y8888 888 88888P'
|
||||
888 888
|
||||
888 888
|
||||
888 888
|
||||
.d88b. .d88b. 88888b. .d88b. 888d888 8888b. 888888 .d88b. .d88888
|
||||
d88P"88b d8P Y8b 888 "88b d8P Y8b 888P" "88b 888 d8P Y8b d88" 888
|
||||
888 888 88888888 888 888 88888888 888 .d888888 888 88888888 888 888
|
||||
Y88b 888 Y8b. 888 888 Y8b. 888 888 888 Y88b. Y8b. Y88b 888
|
||||
"Y88888 "Y8888 888 888 "Y8888 888 "Y888888 "Y888 "Y8888 "Y88888
|
||||
888
|
||||
Y8b d88P
|
||||
"Y88P"
|
||||
*******************************************************************************
|
||||
--->
|
||||
# QMK Firmware XAP Specs
|
||||
|
||||
This document describes the requirements of the QMK XAP ("extensible application protocol") API.
|
||||
|
||||
## Types
|
||||
|
||||
**All integral types are little-endian.**
|
||||
|
||||
| Name | Definition |
|
||||
| -- | -- |
|
||||
| _bool_ | Data type that contains values 0 and 1. Implemented as an alias of `u8`. |
|
||||
| _struct{}_ | A structure of data, packing different objects together. Data is "compacted" -- there are no padding bytes between fields. Equivalent to a packed C-style `struct`. The order in which they're defined matches the order of the data in the response packet. |
|
||||
| _type[n]_ | An array of `type`, with array extent of `N` -- e.g. `u8[2]` signifies two consecutive octets. |
|
||||
| _u16_ | An unsigned 16-bit integral, commonly seen as `uint16_t` from _stdint.h_. |
|
||||
| _u32_ | An unsigned 32-bit integral, commonly seen as `uint32_t` from _stdint.h_. |
|
||||
| _u64_ | An unsigned 64-bit integral, commonly seen as `uint64_t` from _stdint.h_. |
|
||||
| _u8_ | An unsigned 8-bit integral (octet, or byte), commonly seen as `uint8_t` from _stdint.h_. |
|
||||
|
||||
## Definitions
|
||||
|
||||
This list defines the terms used across the entire set of XAP protocol documentation.
|
||||
|
||||
| Name | Definition |
|
||||
| -- | -- |
|
||||
| _Capability_ | A way to determine if certain functionality is enabled in the firmware. Any _subsystem_ that provides build-time restriction of functionality must provide a _route_ for a _capabilities query_. |
|
||||
| _Handler_ | A piece of code that is executed when a specific _route_ is received. |
|
||||
| _Payload_ | Any received data appended to the _route_, which gets delivered to the _handler_ when received. |
|
||||
| _Response_ | The data sent back to the host during execution of a _handler_. |
|
||||
| _Route_ | A sequence of _IDs_ describing the route to invoke a _handler_. |
|
||||
| _Secure Route_ | A _route_ which has potentially destructive consequences, necessitating prior approval by the user before executing. |
|
||||
| _Subsystem_ | A high-level area of functionality within XAP. |
|
||||
| _Unlock sequence_ | A physical sequence initiated by the user to enable execution of _secure routes_. |
|
||||
| _Broadcast Header_ | Packet format for broadcast messages. Takes the format:<br>`token` - token<br>`u8` - type<br>`u8` - length |
|
||||
| _ID_ | A single octet / 8-bit byte, representing Subsystem or Route index. |
|
||||
| _Request Header_ | Packet format for inbound data. Takes the format:<br>`token` - token<br>`u8` - length |
|
||||
| _Response Flags_ | An `u8` containing the status of the request. |
|
||||
| _Response Header_ | Packet format for outbound data. Takes the format:<br>`token` - token<br>`response_flags` - flags<br>`u8` - length |
|
||||
| _Token_ | A `u16` associated with a specific request as well as its corresponding response. Valid token values are within the range `0x0100`-`0xFFFF`. |
|
||||
|
||||
## Requests and Responses
|
||||
|
||||
Communication generally follows a request/response pattern.
|
||||
|
||||
Each request needs to include a _token_ -- this `u16` value prefixes each outbound request from the host application and its corresponding response.
|
||||
This allows response messages to be correlated with their request, even if multiple host applications are communicating with the firmware simultaneously.
|
||||
Host applications should randomly generate a token ID for **every** outbound request, unless using a reserved token defined below.
|
||||
To ensure host interoperability, valid token values are within the range `0x0100`-`0xFFFF`.
|
||||
|
||||
This token is followed by a `u8` signifying the length of data in the request.
|
||||
|
||||
Two token values are reserved: `0xFFFE` and `0xFFFF`:
|
||||
* `0xFFFE`: A message sent by a host application may use this token if no response is to be sent -- a "fire and forget" message.
|
||||
* `0xFFFF`: Signifies a "broadcast" message sent by the firmware without prompting from the host application. Broadcast messages are defined later in this document.
|
||||
|
||||
Any request will generate at least one corresponding response, with the exception of messages using reserved tokens. Maximum total message length is 128 bytes due to RAM constraints.
|
||||
|
||||
Response messages will always be prefixed by the originating request _token_, directly followed by that request's _response flags_, then the response payload length:
|
||||
|
||||
| Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
|
||||
| -- | -- | -- | -- | -- | -- | -- | -- |
|
||||
| `-` | `-` | `-` | `-` | `-` | `-` | `SECURE_FAILURE` | `SUCCESS` |
|
||||
|
||||
* Bit 1 (`SECURE_FAILURE`): When this bit is set, the requested _route_ was marked _secure_ but an _unlock sequence_ has not completed.
|
||||
* Bit 0 (`SUCCESS`): When this bit is set, the request was successfully handled. If not set, all payload data should be disregarded, and the request retried if appropriate (with a new token).
|
||||
|
||||
### Example "conversation":
|
||||
|
||||
**Request** -- version query:
|
||||
|
||||
| Byte | 0 | 1 | 2 | 3 | 4 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| **Purpose** | Token | Token | Payload Length | Route | Route |
|
||||
| **Value** | `0x43` | `0x2B` | `0x02` | `0x00` | `0x00` |
|
||||
|
||||
**Response** -- matching token, successful flag, payload of `0x03170192` = 3.17.192:
|
||||
|
||||
| Byte | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| **Purpose** | Token | Token | Response Flags | Payload Length | Payload | Payload | Payload | Payload |
|
||||
| **Value** | `0x43` | `0x2B` | `0x01` | `0x04` | `0x92` | `0x01` | `0x17` | `0x03` |
|
||||
|
||||
## Routes
|
||||
|
||||
Subsystem validity should be queried through the “Enabled-in-firmware subsystem query” under the QMK subsystem (route=0x00,0x01).
|
||||
This is the primary method for determining if a subsystem has been enabled in the running firmware.
|
||||
|
||||
### XAP - `0x00`
|
||||
This subsystem is always present, and provides the ability to query information about the XAP protocol of the connected device.
|
||||
|
||||
| Name | Route | Tags | Payloads | Description |
|
||||
| -- | -- | -- | -- | -- |
|
||||
| Version Query | `0x00 0x00` | | __Response:__ `u32` | XAP protocol version query.<br><br>* Returns the BCD-encoded version in the format of XX.YY.ZZZZ => `0xXXYYZZZZ`<br> * e.g. 3.2.115 will match `0x03020115`, or bytes {0x15,0x01,0x02,0x03}.|
|
||||
| Capabilities Query | `0x00 0x01` | | __Response:__ `u32` | XAP subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.|
|
||||
| Enabled subsystem query | `0x00 0x02` | | __Response:__ `u32` | XAP protocol subsystem query. Each bit should be considered as a "usable" subsystem. For example, checking `(value & (1 << XAP_ROUTE_QMK) != 0)` means the QMK subsystem is enabled and available for querying.|
|
||||
| Secure Status | `0x00 0x03` | | __Response:__ `u8` | Query secure route status<br><br>* 0 means secure routes are disabled<br>* 1 means unlock sequence initiated but incomplete<br>* 2 means secure routes are allowed<br>* any other value should be interpreted as disabled|
|
||||
| Secure Unlock | `0x00 0x04` | | | Initiate secure route unlock sequence|
|
||||
| Secure Lock | `0x00 0x05` | | | Disable secure routes|
|
||||
|
||||
### QMK - `0x01`
|
||||
This subsystem is always present, and provides the ability to address QMK-specific functionality.
|
||||
|
||||
| Name | Route | Tags | Payloads | Description |
|
||||
| -- | -- | -- | -- | -- |
|
||||
| Version Query | `0x01 0x00` | | __Response:__ `u32` | QMK protocol version query.<br><br>* Returns the BCD-encoded version in the format of XX.YY.ZZZZ => `0xXXYYZZZZ`<br> * e.g. 3.2.115 will match `0x03020115`, or bytes {0x15,0x01,0x02,0x03}.|
|
||||
| Capabilities Query | `0x01 0x01` | | __Response:__ `u32` | QMK subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.|
|
||||
| Board identifiers | `0x01 0x02` | | __Response:__<br> * Vendor ID: `u16`<br> * Product ID: `u16`<br> * Product Version: `u16`<br> * QMK Unique Identifier: `u32` | Retrieves the set of identifying information for the board.|
|
||||
| Board Manufacturer | `0x01 0x03` | | __Response:__ `string` | Retrieves the name of the manufacturer|
|
||||
| Product Name | `0x01 0x04` | | __Response:__ `string` | Retrieves the product name|
|
||||
| Config Blob Length | `0x01 0x05` | | __Response:__ `u16` | Retrieves the length of the configuration data bundled within the firmware|
|
||||
| Config Blob Chunk | `0x01 0x06` | | __Request:__ `u16`<br><br>__Response:__ `u8[32]` | Retrieves a chunk of the configuration data bundled within the firmware|
|
||||
| Jump to bootloader | `0x01 0x07` | __Secure__ | __Response:__ `u8` | Jump to bootloader<br><br>May not be present - if QMK capabilities query returns “true”, then jump to bootloader is supported<br><br>* 0 means secure routes are disabled, and should be considered as a failure<br>* 1 means successful, board will jump to bootloader|
|
||||
| Hardware Identifier | `0x01 0x08` | | __Response:__ `u32[4]` | Retrieves a unique identifier for the board.|
|
||||
| Reinitialize EEPROM | `0x01 0x09` | __Secure__ | __Response:__ `u8` | Reinitializes the keyboard's EEPROM (persistent memory)<br><br>May not be present - if QMK capabilities query returns “true”, then reinitialize is supported<br><br>* 0 means secure routes are disabled, and should be considered as a failure<br>* 1 means successful, board will reinitialize and then reboot|
|
||||
|
||||
### Keyboard - `0x02`
|
||||
This subsystem is always present, and reserved for vendor-specific functionality. No routes are defined by XAP.
|
||||
|
||||
### User - `0x03`
|
||||
This subsystem is always present, and reserved for user-specific functionality. No routes are defined by XAP.
|
||||
|
||||
### Keymap - `0x04`
|
||||
This subsystem allows for query of currently configured keycodes.
|
||||
|
||||
| Name | Route | Tags | Payloads | Description |
|
||||
| -- | -- | -- | -- | -- |
|
||||
| Capabilities Query | `0x04 0x01` | | __Response:__ `u32` | Keymap subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.|
|
||||
| Get Layer Count | `0x04 0x02` | | __Response:__ `u8` | Query maximum number of layers that can be addressed within the keymap.|
|
||||
| Get Keycode | `0x04 0x03` | | __Request:__<br> * Layer: `u8`<br> * Row: `u8`<br> * Column: `u8`<br><br>__Response:__ `u16` | Query the Keycode at the requested location.|
|
||||
| Get Encoder Keycode | `0x04 0x04` | | __Request:__<br> * Layer: `u8`<br> * Encoder: `u8`<br> * Clockwise: `u8`<br><br>__Response:__ `u16` | Query the Keycode at the requested location.|
|
||||
|
||||
### Remapping - `0x05`
|
||||
This subsystem allows for live reassignment of keycodes without rebuilding the firmware.
|
||||
|
||||
| Name | Route | Tags | Payloads | Description |
|
||||
| -- | -- | -- | -- | -- |
|
||||
| Capabilities Query | `0x05 0x01` | | __Response:__ `u32` | Remapping subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.|
|
||||
| Get Layer Count | `0x05 0x02` | | __Response:__ `u8` | Query maximum number of layers that can be addressed within the keymap.|
|
||||
| Set Keycode | `0x05 0x03` | __Secure__ | __Request:__<br> * Layer: `u8`<br> * Row: `u8`<br> * Column: `u8`<br> * Keycode: `u16` | Modify the Keycode at the requested location.|
|
||||
| Set Encoder Keycode | `0x05 0x04` | __Secure__ | __Request:__<br> * Layer: `u8`<br> * Encoder: `u8`<br> * Clockwise: `u8`<br> * Keycode: `u16` | Modify the Keycode at the requested location.|
|
||||
|
||||
### Lighting - `0x06`
|
||||
This subsystem allows for control over the lighting subsystem.
|
||||
|
||||
| Name | Route | Tags | Payloads | Description |
|
||||
| -- | -- | -- | -- | -- |
|
||||
| Capabilities Query | `0x06 0x01` | | __Response:__ `u32` | Lighting subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.|
|
||||
|
||||
|
||||
## Broadcast messages
|
||||
|
||||
Broadcast messages may be sent by the firmware to the host, without a corresponding inbound request. Each broadcast message uses the token `0xFFFF`, and does not expect a response from the host. Tokens are followed by an _ID_ signifying the type of broadcast, then the response _payload_ length, and finally the corresponding _payload_.
|
||||
|
||||
### Log message - `0x00`
|
||||
Replicates and replaces the same functionality as if using the standard QMK `CONSOLE_ENABLE = yes` in `rules.mk`. Normal prints within the firmware will manifest as log messages broadcast to the host. `hid_listen` will not be functional with XAP enabled.
|
||||
|
||||
Log message payloads include `u8[Length]` containing the text, where the length of the text is the _broadcast_header.length_ field.
|
||||
|
||||
**Example Log Broadcast** -- log message "Hello QMK!"
|
||||
|
||||
| Byte | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| **Purpose** | Token | Token | Broadcast Type | Length | Payload | Payload | Payload | Payload | Payload | Payload | Payload | Payload | Payload | Payload |
|
||||
| **Value** | `0xFF` | `0xFF` | `0x00` | `0x0A`(10) | `0x48`(H) | `0x65`(e) | `0x6C`(l) | `0x6C`(l) | `0x6F`(o) | `0x20`( ) | `0x51`(Q) | `0x4D`(M) | `0x4B`(K) | `0x21`(!) |
|
||||
### Secure Status - `0x01`
|
||||
Secure status has changed. Payloads include a `u8` matching a 'Secure Status' request.
|
||||
|
||||
**Example Secure Status Broadcast** -- secure "Unlocking"
|
||||
|
||||
| Byte | 0 | 1 | 2 | 3 | 4 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| **Purpose** | Token | Token | Broadcast Type | Length | Secure Status |
|
||||
| **Value** | `0xFF` | `0xFF` | `0x01` | `0x01` | `0x01` |
|
||||
### Keyboard - `0x02`
|
||||
Reserved for vendor-specific functionality. No messages are defined by XAP.
|
||||
### User - `0x03`
|
||||
Reserved for user-specific functionality. No messages are defined by XAP.
|
||||
|
249
docs/xap_0.3.0.md
Normal file
249
docs/xap_0.3.0.md
Normal file
@ -0,0 +1,249 @@
|
||||
<!--- Copyright 2024 QMK --->
|
||||
<!--- SPDX-License-Identifier: GPL-2.0-or-later --->
|
||||
|
||||
<!---
|
||||
*******************************************************************************
|
||||
88888888888 888 d8b .d888 d8b 888 d8b
|
||||
888 888 Y8P d88P" Y8P 888 Y8P
|
||||
888 888 888 888
|
||||
888 88888b. 888 .d8888b 888888 888 888 .d88b. 888 .d8888b
|
||||
888 888 "88b 888 88K 888 888 888 d8P Y8b 888 88K
|
||||
888 888 888 888 "Y8888b. 888 888 888 88888888 888 "Y8888b.
|
||||
888 888 888 888 X88 888 888 888 Y8b. 888 X88
|
||||
888 888 888 888 88888P' 888 888 888 "Y8888 888 88888P'
|
||||
888 888
|
||||
888 888
|
||||
888 888
|
||||
.d88b. .d88b. 88888b. .d88b. 888d888 8888b. 888888 .d88b. .d88888
|
||||
d88P"88b d8P Y8b 888 "88b d8P Y8b 888P" "88b 888 d8P Y8b d88" 888
|
||||
888 888 88888888 888 888 88888888 888 .d888888 888 88888888 888 888
|
||||
Y88b 888 Y8b. 888 888 Y8b. 888 888 888 Y88b. Y8b. Y88b 888
|
||||
"Y88888 "Y8888 888 888 "Y8888 888 "Y888888 "Y888 "Y8888 "Y88888
|
||||
888
|
||||
Y8b d88P
|
||||
"Y88P"
|
||||
*******************************************************************************
|
||||
--->
|
||||
# QMK Firmware XAP Specs
|
||||
|
||||
This document describes the requirements of the QMK XAP ("extensible application protocol") API.
|
||||
|
||||
## Types
|
||||
|
||||
**All integral types are little-endian.**
|
||||
|
||||
| Name | Definition |
|
||||
| -- | -- |
|
||||
| _bool_ | Data type that contains values 0 and 1. Implemented as an alias of `u8`. |
|
||||
| _struct{}_ | A structure of data, packing different objects together. Data is "compacted" -- there are no padding bytes between fields. Equivalent to a packed C-style `struct`. The order in which they're defined matches the order of the data in the response packet. |
|
||||
| _type[n]_ | An array of `type`, with array extent of `N` -- e.g. `u8[2]` signifies two consecutive octets. |
|
||||
| _u16_ | An unsigned 16-bit integral, commonly seen as `uint16_t` from _stdint.h_. |
|
||||
| _u32_ | An unsigned 32-bit integral, commonly seen as `uint32_t` from _stdint.h_. |
|
||||
| _u64_ | An unsigned 64-bit integral, commonly seen as `uint64_t` from _stdint.h_. |
|
||||
| _u8_ | An unsigned 8-bit integral (octet, or byte), commonly seen as `uint8_t` from _stdint.h_. |
|
||||
|
||||
## Definitions
|
||||
|
||||
This list defines the terms used across the entire set of XAP protocol documentation.
|
||||
|
||||
| Name | Definition |
|
||||
| -- | -- |
|
||||
| _Capability_ | A way to determine if certain functionality is enabled in the firmware. Any _subsystem_ that provides build-time restriction of functionality must provide a _route_ for a _capabilities query_. |
|
||||
| _Handler_ | A piece of code that is executed when a specific _route_ is received. |
|
||||
| _Payload_ | Any received data appended to the _route_, which gets delivered to the _handler_ when received. |
|
||||
| _Response_ | The data sent back to the host during execution of a _handler_. |
|
||||
| _Route_ | A sequence of _IDs_ describing the route to invoke a _handler_. |
|
||||
| _Secure Route_ | A _route_ which has potentially destructive consequences, necessitating prior approval by the user before executing. |
|
||||
| _Subsystem_ | A high-level area of functionality within XAP. |
|
||||
| _Unlock sequence_ | A physical sequence initiated by the user to enable execution of _secure routes_. |
|
||||
| _Broadcast Header_ | Packet format for broadcast messages. Takes the format:<br>`token` - token<br>`u8` - type<br>`u8` - length |
|
||||
| _ID_ | A single octet / 8-bit byte, representing Subsystem or Route index. |
|
||||
| _Request Header_ | Packet format for inbound data. Takes the format:<br>`token` - token<br>`u8` - length |
|
||||
| _Response Flags_ | An `u8` containing the status of the request. |
|
||||
| _Response Header_ | Packet format for outbound data. Takes the format:<br>`token` - token<br>`response_flags` - flags<br>`u8` - length |
|
||||
| _Token_ | A `u16` associated with a specific request as well as its corresponding response. Valid token values are within the range `0x0100`-`0xFFFF`. |
|
||||
|
||||
## Requests and Responses
|
||||
|
||||
Communication generally follows a request/response pattern.
|
||||
|
||||
Each request needs to include a _token_ -- this `u16` value prefixes each outbound request from the host application and its corresponding response.
|
||||
This allows response messages to be correlated with their request, even if multiple host applications are communicating with the firmware simultaneously.
|
||||
Host applications should randomly generate a token ID for **every** outbound request, unless using a reserved token defined below.
|
||||
To ensure host interoperability, valid token values are within the range `0x0100`-`0xFFFF`.
|
||||
|
||||
This token is followed by a `u8` signifying the length of data in the request.
|
||||
|
||||
Two token values are reserved: `0xFFFE` and `0xFFFF`:
|
||||
* `0xFFFE`: A message sent by a host application may use this token if no response is to be sent -- a "fire and forget" message.
|
||||
* `0xFFFF`: Signifies a "broadcast" message sent by the firmware without prompting from the host application. Broadcast messages are defined later in this document.
|
||||
|
||||
Any request will generate at least one corresponding response, with the exception of messages using reserved tokens. Maximum total message length is 128 bytes due to RAM constraints.
|
||||
|
||||
Response messages will always be prefixed by the originating request _token_, directly followed by that request's _response flags_, then the response payload length:
|
||||
|
||||
| Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
|
||||
| -- | -- | -- | -- | -- | -- | -- | -- |
|
||||
| `-` | `-` | `-` | `-` | `-` | `-` | `SECURE_FAILURE` | `SUCCESS` |
|
||||
|
||||
* Bit 1 (`SECURE_FAILURE`): When this bit is set, the requested _route_ was marked _secure_ but an _unlock sequence_ has not completed.
|
||||
* Bit 0 (`SUCCESS`): When this bit is set, the request was successfully handled. If not set, all payload data should be disregarded, and the request retried if appropriate (with a new token).
|
||||
|
||||
### Example "conversation":
|
||||
|
||||
**Request** -- version query:
|
||||
|
||||
| Byte | 0 | 1 | 2 | 3 | 4 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| **Purpose** | Token | Token | Payload Length | Route | Route |
|
||||
| **Value** | `0x43` | `0x2B` | `0x02` | `0x00` | `0x00` |
|
||||
|
||||
**Response** -- matching token, successful flag, payload of `0x03170192` = 3.17.192:
|
||||
|
||||
| Byte | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| **Purpose** | Token | Token | Response Flags | Payload Length | Payload | Payload | Payload | Payload |
|
||||
| **Value** | `0x43` | `0x2B` | `0x01` | `0x04` | `0x92` | `0x01` | `0x17` | `0x03` |
|
||||
|
||||
## Routes
|
||||
|
||||
Subsystem validity should be queried through the “Enabled-in-firmware subsystem query” under the QMK subsystem (route=0x00,0x01).
|
||||
This is the primary method for determining if a subsystem has been enabled in the running firmware.
|
||||
|
||||
### XAP - `0x00`
|
||||
This subsystem is always present, and provides the ability to query information about the XAP protocol of the connected device.
|
||||
|
||||
| Name | Route | Tags | Payloads | Description |
|
||||
| -- | -- | -- | -- | -- |
|
||||
| Version Query | `0x00 0x00` | | __Response:__ `u32` | XAP protocol version query.<br><br>* Returns the BCD-encoded version in the format of XX.YY.ZZZZ => `0xXXYYZZZZ`<br> * e.g. 3.2.115 will match `0x03020115`, or bytes {0x15,0x01,0x02,0x03}.|
|
||||
| Capabilities Query | `0x00 0x01` | | __Response:__ `u32` | XAP subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.|
|
||||
| Enabled subsystem query | `0x00 0x02` | | __Response:__ `u32` | XAP protocol subsystem query. Each bit should be considered as a "usable" subsystem. For example, checking `(value & (1 << XAP_ROUTE_QMK) != 0)` means the QMK subsystem is enabled and available for querying.|
|
||||
| Secure Status | `0x00 0x03` | | __Response:__ `u8` | Query secure route status<br><br>* 0 means secure routes are disabled<br>* 1 means unlock sequence initiated but incomplete<br>* 2 means secure routes are allowed<br>* any other value should be interpreted as disabled|
|
||||
| Secure Unlock | `0x00 0x04` | | | Initiate secure route unlock sequence|
|
||||
| Secure Lock | `0x00 0x05` | | | Disable secure routes|
|
||||
|
||||
### QMK - `0x01`
|
||||
This subsystem is always present, and provides the ability to address QMK-specific functionality.
|
||||
|
||||
| Name | Route | Tags | Payloads | Description |
|
||||
| -- | -- | -- | -- | -- |
|
||||
| Version Query | `0x01 0x00` | | __Response:__ `u32` | QMK protocol version query.<br><br>* Returns the BCD-encoded version in the format of XX.YY.ZZZZ => `0xXXYYZZZZ`<br> * e.g. 3.2.115 will match `0x03020115`, or bytes {0x15,0x01,0x02,0x03}.|
|
||||
| Capabilities Query | `0x01 0x01` | | __Response:__ `u32` | QMK subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.|
|
||||
| Board identifiers | `0x01 0x02` | | __Response:__<br> * Vendor ID: `u16`<br> * Product ID: `u16`<br> * Product Version: `u16`<br> * QMK Unique Identifier: `u32` | Retrieves the set of identifying information for the board.|
|
||||
| Board Manufacturer | `0x01 0x03` | | __Response:__ `string` | Retrieves the name of the manufacturer|
|
||||
| Product Name | `0x01 0x04` | | __Response:__ `string` | Retrieves the product name|
|
||||
| Config Blob Length | `0x01 0x05` | | __Response:__ `u16` | Retrieves the length of the configuration data bundled within the firmware|
|
||||
| Config Blob Chunk | `0x01 0x06` | | __Request:__ `u16`<br><br>__Response:__ `u8[32]` | Retrieves a chunk of the configuration data bundled within the firmware|
|
||||
| Jump to bootloader | `0x01 0x07` | __Secure__ | __Response:__ `u8` | Jump to bootloader<br><br>May not be present - if QMK capabilities query returns “true”, then jump to bootloader is supported<br><br>* 0 means secure routes are disabled, and should be considered as a failure<br>* 1 means successful, board will jump to bootloader|
|
||||
| Hardware Identifier | `0x01 0x08` | | __Response:__ `u32[4]` | Retrieves a unique identifier for the board.|
|
||||
| Reinitialize EEPROM | `0x01 0x09` | __Secure__ | __Response:__ `u8` | Reinitializes the keyboard's EEPROM (persistent memory)<br><br>May not be present - if QMK capabilities query returns “true”, then reinitialize is supported<br><br>* 0 means secure routes are disabled, and should be considered as a failure<br>* 1 means successful, board will reinitialize and then reboot|
|
||||
|
||||
### Keyboard - `0x02`
|
||||
This subsystem is always present, and reserved for vendor-specific functionality. No routes are defined by XAP.
|
||||
|
||||
### User - `0x03`
|
||||
This subsystem is always present, and reserved for user-specific functionality. No routes are defined by XAP.
|
||||
|
||||
### Keymap - `0x04`
|
||||
This subsystem allows for query of currently configured keycodes.
|
||||
|
||||
| Name | Route | Tags | Payloads | Description |
|
||||
| -- | -- | -- | -- | -- |
|
||||
| Capabilities Query | `0x04 0x01` | | __Response:__ `u32` | Keymap subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.|
|
||||
| Get Layer Count | `0x04 0x02` | | __Response:__ `u8` | Query maximum number of layers that can be addressed within the keymap.|
|
||||
| Get Keycode | `0x04 0x03` | | __Request:__<br> * Layer: `u8`<br> * Row: `u8`<br> * Column: `u8`<br><br>__Response:__ `u16` | Query the Keycode at the requested location.|
|
||||
| Get Encoder Keycode | `0x04 0x04` | | __Request:__<br> * Layer: `u8`<br> * Encoder: `u8`<br> * Clockwise: `u8`<br><br>__Response:__ `u16` | Query the Keycode at the requested location.|
|
||||
|
||||
### Remapping - `0x05`
|
||||
This subsystem allows for live reassignment of keycodes without rebuilding the firmware.
|
||||
|
||||
| Name | Route | Tags | Payloads | Description |
|
||||
| -- | -- | -- | -- | -- |
|
||||
| Capabilities Query | `0x05 0x01` | | __Response:__ `u32` | Remapping subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.|
|
||||
| Get Layer Count | `0x05 0x02` | | __Response:__ `u8` | Query maximum number of layers that can be addressed within the keymap.|
|
||||
| Set Keycode | `0x05 0x03` | __Secure__ | __Request:__<br> * Layer: `u8`<br> * Row: `u8`<br> * Column: `u8`<br> * Keycode: `u16` | Modify the Keycode at the requested location.|
|
||||
| Set Encoder Keycode | `0x05 0x04` | __Secure__ | __Request:__<br> * Layer: `u8`<br> * Encoder: `u8`<br> * Clockwise: `u8`<br> * Keycode: `u16` | Modify the Keycode at the requested location.|
|
||||
|
||||
### Lighting - `0x06`
|
||||
This subsystem allows for control over the lighting subsystem.
|
||||
|
||||
| Name | Route | Tags | Payloads | Description |
|
||||
| -- | -- | -- | -- | -- |
|
||||
| Capabilities Query | `0x06 0x01` | | __Response:__ `u32` | Lighting subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.|
|
||||
|
||||
|
||||
#### backlight - `0x06 0x02`
|
||||
This subsystem allows for control over the backlight subsystem.
|
||||
|
||||
| Name | Route | Tags | Payloads | Description |
|
||||
| -- | -- | -- | -- | -- |
|
||||
| Capabilities Query | `0x06 0x02 0x01` | | __Response:__ `u32` | backlight subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.|
|
||||
| Get Enabled Effects | `0x06 0x02 0x02` | | __Response:__ `u8` | Each bit should be considered as a "usable" effect id|
|
||||
| Get Config | `0x06 0x02 0x03` | | __Response:__<br> * enable: `u8`<br> * mode: `u8`<br> * val: `u8` | Query the current config.|
|
||||
| Set Config | `0x06 0x02 0x04` | | __Request:__<br> * enable: `u8`<br> * mode: `u8`<br> * val: `u8` | Set the current config.|
|
||||
| Save Config | `0x06 0x02 0x05` | | | Save the current config.|
|
||||
|
||||
|
||||
#### rgblight - `0x06 0x03`
|
||||
This subsystem allows for control over the rgblight subsystem.
|
||||
|
||||
| Name | Route | Tags | Payloads | Description |
|
||||
| -- | -- | -- | -- | -- |
|
||||
| Capabilities Query | `0x06 0x03 0x01` | | __Response:__ `u32` | rgblight subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.|
|
||||
| Get Enabled Effects | `0x06 0x03 0x02` | | __Response:__ `u64` | Each bit should be considered as a "usable" effect id|
|
||||
| Get Config | `0x06 0x03 0x03` | | __Response:__<br> * enable: `u8`<br> * mode: `u8`<br> * hue: `u8`<br> * sat: `u8`<br> * val: `u8`<br> * speed: `u8` | Query the current config.|
|
||||
| Set Config | `0x06 0x03 0x04` | | __Request:__<br> * enable: `u8`<br> * mode: `u8`<br> * hue: `u8`<br> * sat: `u8`<br> * val: `u8`<br> * speed: `u8` | Set the current config.|
|
||||
| Save Config | `0x06 0x03 0x05` | | | Save the current config.|
|
||||
|
||||
|
||||
#### rgbmatrix - `0x06 0x04`
|
||||
This subsystem allows for control over the rgb matrix subsystem.
|
||||
|
||||
| Name | Route | Tags | Payloads | Description |
|
||||
| -- | -- | -- | -- | -- |
|
||||
| Capabilities Query | `0x06 0x04 0x01` | | __Response:__ `u32` | rgb matrix subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.|
|
||||
| Get Enabled Effects | `0x06 0x04 0x02` | | __Response:__ `u64` | Each bit should be considered as a "usable" effect id|
|
||||
| Get Config | `0x06 0x04 0x03` | | __Response:__<br> * enable: `u8`<br> * mode: `u8`<br> * hue: `u8`<br> * sat: `u8`<br> * val: `u8`<br> * speed: `u8`<br> * flags: `u8` | Query the current config.|
|
||||
| Set Config | `0x06 0x04 0x04` | | __Request:__<br> * enable: `u8`<br> * mode: `u8`<br> * hue: `u8`<br> * sat: `u8`<br> * val: `u8`<br> * speed: `u8`<br> * flags: `u8` | Set the current config.|
|
||||
| Save Config | `0x06 0x04 0x05` | | | Save the current config.|
|
||||
|
||||
### Audio - `0x07`
|
||||
This subsystem allows for control over the audio subsystem.
|
||||
|
||||
| Name | Route | Tags | Payloads | Description |
|
||||
| -- | -- | -- | -- | -- |
|
||||
| Capabilities Query | `0x07 0x01` | | __Response:__ `u32` | Audio subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.|
|
||||
| Get Config | `0x07 0x03` | | __Response:__<br> * enable: `u8`<br> * clicky_enable: `u8` | Query the current config.|
|
||||
| Set Config | `0x07 0x04` | | __Request:__<br> * enable: `u8`<br> * clicky_enable: `u8` | Set the current config.|
|
||||
| Save Config | `0x07 0x05` | | | Save the current config.|
|
||||
|
||||
|
||||
## Broadcast messages
|
||||
|
||||
Broadcast messages may be sent by the firmware to the host, without a corresponding inbound request. Each broadcast message uses the token `0xFFFF`, and does not expect a response from the host. Tokens are followed by an _ID_ signifying the type of broadcast, then the response _payload_ length, and finally the corresponding _payload_.
|
||||
|
||||
### Log message - `0x00`
|
||||
Replicates and replaces the same functionality as if using the standard QMK `CONSOLE_ENABLE = yes` in `rules.mk`. Normal prints within the firmware will manifest as log messages broadcast to the host. `hid_listen` will not be functional with XAP enabled.
|
||||
|
||||
Log message payloads include `u8[Length]` containing the text, where the length of the text is the _broadcast_header.length_ field.
|
||||
|
||||
**Example Log Broadcast** -- log message "Hello QMK!"
|
||||
|
||||
| Byte | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| **Purpose** | Token | Token | Broadcast Type | Length | Payload | Payload | Payload | Payload | Payload | Payload | Payload | Payload | Payload | Payload |
|
||||
| **Value** | `0xFF` | `0xFF` | `0x00` | `0x0A`(10) | `0x48`(H) | `0x65`(e) | `0x6C`(l) | `0x6C`(l) | `0x6F`(o) | `0x20`( ) | `0x51`(Q) | `0x4D`(M) | `0x4B`(K) | `0x21`(!) |
|
||||
### Secure Status - `0x01`
|
||||
Secure status has changed. Payloads include a `u8` matching a 'Secure Status' request.
|
||||
|
||||
**Example Secure Status Broadcast** -- secure "Unlocking"
|
||||
|
||||
| Byte | 0 | 1 | 2 | 3 | 4 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| **Purpose** | Token | Token | Broadcast Type | Length | Secure Status |
|
||||
| **Value** | `0xFF` | `0xFF` | `0x01` | `0x01` | `0x01` |
|
||||
### Keyboard - `0x02`
|
||||
Reserved for vendor-specific functionality. No messages are defined by XAP.
|
||||
### User - `0x03`
|
||||
Reserved for user-specific functionality. No messages are defined by XAP.
|
||||
|
30
docs/xap_protocol.md
Normal file
30
docs/xap_protocol.md
Normal file
@ -0,0 +1,30 @@
|
||||
<!--- Copyright 2024 QMK --->
|
||||
<!--- SPDX-License-Identifier: GPL-2.0-or-later --->
|
||||
|
||||
<!---
|
||||
*******************************************************************************
|
||||
88888888888 888 d8b .d888 d8b 888 d8b
|
||||
888 888 Y8P d88P" Y8P 888 Y8P
|
||||
888 888 888 888
|
||||
888 88888b. 888 .d8888b 888888 888 888 .d88b. 888 .d8888b
|
||||
888 888 "88b 888 88K 888 888 888 d8P Y8b 888 88K
|
||||
888 888 888 888 "Y8888b. 888 888 888 88888888 888 "Y8888b.
|
||||
888 888 888 888 X88 888 888 888 Y8b. 888 X88
|
||||
888 888 888 888 88888P' 888 888 888 "Y8888 888 88888P'
|
||||
888 888
|
||||
888 888
|
||||
888 888
|
||||
.d88b. .d88b. 88888b. .d88b. 888d888 8888b. 888888 .d88b. .d88888
|
||||
d88P"88b d8P Y8b 888 "88b d8P Y8b 888P" "88b 888 d8P Y8b d88" 888
|
||||
888 888 88888888 888 888 88888888 888 .d888888 888 88888888 888 888
|
||||
Y88b 888 Y8b. 888 888 Y8b. 888 888 888 Y88b. Y8b. Y88b 888
|
||||
"Y88888 "Y8888 888 888 "Y8888 888 "Y888888 "Y888 "Y8888 "Y88888
|
||||
888
|
||||
Y8b d88P
|
||||
"Y88P"
|
||||
*******************************************************************************
|
||||
--->
|
||||
* [XAP Version 0.3.0](xap_0.3.0.md)
|
||||
* [XAP Version 0.2.0](xap_0.2.0.md)
|
||||
* [XAP Version 0.1.0](xap_0.1.0.md)
|
||||
* [XAP Version 0.0.1](xap_0.0.1.md)
|
113
keyboards/zvecr/zv48/keymaps/xap/keymap.c
Normal file
113
keyboards/zvecr/zv48/keymaps/xap/keymap.c
Normal file
@ -0,0 +1,113 @@
|
||||
// Copyright 2020 zvecr <git@zvecr.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#include QMK_KEYBOARD_H
|
||||
|
||||
// Defines names for use in layer keycodes and the keymap
|
||||
enum layer_names {
|
||||
_QWERTY,
|
||||
_LOWER,
|
||||
_RAISE,
|
||||
_ADJUST,
|
||||
};
|
||||
|
||||
#define LOWER MO(_LOWER)
|
||||
#define RAISE MO(_RAISE)
|
||||
|
||||
const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
|
||||
/* Qwerty
|
||||
* ,-----------------------------------------------------------------------------------.
|
||||
* | Esc | Q | W | E | R | T | Y | U | I | O | P | Bksp |
|
||||
* |------+------+------+------+------+-------------+------+------+------+------+------|
|
||||
* | Tab | A | S | D | F | G | H | J | K | L | ; | " |
|
||||
* |------+------+------+------+------+------|------+------+------+------+------+------|
|
||||
* | Shift| Z | X | C | V | B | N | M | , | . | / |Enter |
|
||||
* |------+------+------+------+------+------+------+------+------+------+------+------|
|
||||
* | Ctrl | GUI | Alt | App |Lower | Space |Raise | Left | Down | Up |Right |
|
||||
* `-----------------------------------------------------------------------------------'
|
||||
*/
|
||||
[_QWERTY] = LAYOUT_ortho_4x12(
|
||||
KC_ESC, KC_Q, KC_W, KC_E, KC_R, KC_T, KC_Y, KC_U, KC_I, KC_O, KC_P, KC_BSPC,
|
||||
KC_TAB, KC_A, KC_S, KC_D, KC_F, KC_G, KC_H, KC_J, KC_K, KC_L, KC_SCLN, KC_QUOT,
|
||||
KC_LSFT, KC_Z, KC_X, KC_C, KC_V, KC_B, KC_N, KC_M, KC_COMM, KC_DOT, KC_SLSH, KC_ENT ,
|
||||
KC_LCTL, KC_LGUI, KC_LALT, KC_APP, LOWER, KC_SPC, KC_SPC, RAISE, KC_LEFT, KC_DOWN, KC_UP, KC_RGHT
|
||||
),
|
||||
|
||||
/* Lower
|
||||
* ,-----------------------------------------------------------------------------------.
|
||||
* | ~ | ! | @ | # | $ | % | ^ | & | * | ( | ) | Del |
|
||||
* |------+------+------+------+------+-------------+------+------+------+------+------|
|
||||
* | Del | F1 | F2 | F3 | F4 | F5 | F6 | _ | + | { | } | | |
|
||||
* |------+------+------+------+------+------|------+------+------+------+------+------|
|
||||
* | | F7 | F8 | F9 | F10 | F11 | F12 |ISO ~ |ISO | | | | |
|
||||
* |------+------+------+------+------+------+------+------+------+------+------+------|
|
||||
* | | | | | | | | | Next | Vol- | Vol+ | Play |
|
||||
* `-----------------------------------------------------------------------------------'
|
||||
*/
|
||||
[_LOWER] = LAYOUT_ortho_4x12(
|
||||
KC_TILD, KC_EXLM, KC_AT, KC_HASH, KC_DLR, KC_PERC, KC_CIRC, KC_AMPR, KC_ASTR, KC_LPRN, KC_RPRN, KC_DEL,
|
||||
KC_DEL, KC_F1, KC_F2, KC_F3, KC_F4, KC_F5, KC_F6, KC_UNDS, KC_PLUS, KC_LCBR, KC_RCBR, KC_PIPE,
|
||||
_______, KC_F7, KC_F8, KC_F9, KC_F10, KC_F11, KC_F12,S(KC_NUHS),S(KC_NUBS),_______, _______, _______,
|
||||
_______, _______, _______, _______, _______, _______, _______, _______, KC_MNXT, KC_VOLD, KC_VOLU, KC_MPLY
|
||||
),
|
||||
|
||||
/* Raise
|
||||
* ,-----------------------------------------------------------------------------------.
|
||||
* | ` | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0 | Del |
|
||||
* |------+------+------+------+------+-------------+------+------+------+------+------|
|
||||
* | Del | F1 | F2 | F3 | F4 | F5 | F6 | - | = | [ | ] | \ |
|
||||
* |------+------+------+------+------+------|------+------+------+------+------+------|
|
||||
* | | F7 | F8 | F9 | F10 | F11 | F12 |ISO # |ISO / | | | |
|
||||
* |------+------+------+------+------+------+------+------+------+------+------+------|
|
||||
* | | | | | | | | | Next | Vol- | Vol+ | Play |
|
||||
* `-----------------------------------------------------------------------------------'
|
||||
*/
|
||||
[_RAISE] = LAYOUT_ortho_4x12(
|
||||
KC_GRV, KC_1, KC_2, KC_3, KC_4, KC_5, KC_6, KC_7, KC_8, KC_9, KC_0, KC_DEL,
|
||||
KC_DEL, KC_F1, KC_F2, KC_F3, KC_F4, KC_F5, KC_F6, KC_MINS, KC_EQL, KC_LBRC, KC_RBRC, KC_BSLS,
|
||||
_______, KC_F7, KC_F8, KC_F9, KC_F10, KC_F11, KC_F12, KC_NUHS, KC_NUBS, _______, _______, _______,
|
||||
_______, _______, _______, _______, _______, _______, _______, _______, KC_MNXT, KC_VOLD, KC_VOLU, KC_MPLY
|
||||
),
|
||||
|
||||
/* Adjust (Lower + Raise)
|
||||
* ,-----------------------------------------------------------------------------------.
|
||||
* | | Reset| | | | |R Tog |R Mode|R Rev |R Grad| Reset| |
|
||||
* |------+------+------+------+------+-------------+------+------+------+------+------|
|
||||
* | | | | | | |R HUI|R SAI|R VAI| | | |
|
||||
* |------+------+------+------+------+------|------+------+------+------+------+------|
|
||||
* | | | | | | |R HUD|R SAD|R VAD| | | |
|
||||
* |------+------+------+------+------+------+------+------+------+------+------+------|
|
||||
* | | | | | | | | | | | | |
|
||||
* `-----------------------------------------------------------------------------------'
|
||||
*/
|
||||
[_ADJUST] = LAYOUT_ortho_4x12(
|
||||
_______, QK_BOOT, _______, _______, _______, _______, RGB_TOG, RGB_MOD, RGB_RMOD,RGB_M_G, QK_BOOT, _______,
|
||||
_______, _______, _______, _______, _______, _______, RGB_HUI, RGB_SAI, RGB_VAI, _______, _______, _______,
|
||||
_______, _______, _______, _______, _______, _______, RGB_HUD, RGB_SAD, RGB_VAD, _______, _______, _______,
|
||||
_______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______
|
||||
)
|
||||
|
||||
};
|
||||
|
||||
layer_state_t layer_state_set_user(layer_state_t state) {
|
||||
return update_tri_layer_state(state, _LOWER, _RAISE, _ADJUST);
|
||||
}
|
||||
|
||||
#if defined(ENCODER_MAP_ENABLE)
|
||||
const uint16_t PROGMEM encoder_map[][NUM_ENCODERS][2] = {
|
||||
[_QWERTY] = { ENCODER_CCW_CW(KC_MS_WH_UP, KC_MS_WH_DOWN), ENCODER_CCW_CW(KC_VOLD, KC_VOLU) },
|
||||
[_LOWER] = { ENCODER_CCW_CW(RGB_HUD, RGB_HUI), ENCODER_CCW_CW(RGB_SAD, RGB_SAI) },
|
||||
[_RAISE] = { ENCODER_CCW_CW(RGB_VAD, RGB_VAI), ENCODER_CCW_CW(RGB_SPD, RGB_SPI) },
|
||||
[_ADJUST] = { ENCODER_CCW_CW(RGB_RMOD, RGB_MOD), ENCODER_CCW_CW(KC_RIGHT, KC_LEFT) },
|
||||
};
|
||||
#endif
|
||||
|
||||
void housekeeping_task_user(void) {
|
||||
static uint32_t timer = 0;
|
||||
static uint8_t count = 0;
|
||||
if (timer_elapsed32(timer) > 1000) {
|
||||
timer = timer_read32();
|
||||
count++;
|
||||
|
||||
xap_broadcast(0x03, &count, 1);
|
||||
}
|
||||
}
|
2
keyboards/zvecr/zv48/keymaps/xap/rules.mk
Normal file
2
keyboards/zvecr/zv48/keymaps/xap/rules.mk
Normal file
@ -0,0 +1,2 @@
|
||||
ENCODER_MAP_ENABLE = yes
|
||||
XAP_ENABLE = yes
|
16
keyboards/zvecr/zv48/keymaps/xap/xap.hjson
Normal file
16
keyboards/zvecr/zv48/keymaps/xap/xap.hjson
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
routes: {
|
||||
0x01: {
|
||||
type: command
|
||||
name: Capabilities Query
|
||||
define: CAPABILITIES_QUERY_USER
|
||||
description:
|
||||
'''
|
||||
USER subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.
|
||||
'''
|
||||
return_type: u32
|
||||
return_purpose: capabilities
|
||||
return_constant: XAP_ROUTE_USER_CAPABILITIES
|
||||
}
|
||||
}
|
||||
}
|
16
keyboards/zvecr/zv48/xap.hjson
Normal file
16
keyboards/zvecr/zv48/xap.hjson
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
routes: {
|
||||
0x01: {
|
||||
type: command
|
||||
name: Capabilities Query
|
||||
define: CAPABILITIES_QUERY_KB
|
||||
description:
|
||||
'''
|
||||
KB subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.
|
||||
'''
|
||||
return_type: u32
|
||||
return_purpose: capabilities
|
||||
return_constant: XAP_ROUTE_KB_CAPABILITIES
|
||||
}
|
||||
}
|
||||
}
|
65
lib/python/qmk/casing.py
Executable file
65
lib/python/qmk/casing.py
Executable file
@ -0,0 +1,65 @@
|
||||
"""This script handles conversion between snake and camel casing.
|
||||
"""
|
||||
import re
|
||||
|
||||
_words_expr = re.compile(r"([a-zA-Z][^A-Z0-9]*|[0-9]+)")
|
||||
_lower_snake_case_expr = re.compile(r'^[a-z][a-z0-9_]*$')
|
||||
_upper_snake_case_expr = re.compile(r'^[A-Z][A-Z0-9_]*$')
|
||||
|
||||
|
||||
def _is_snake_case(str):
|
||||
"""Checks if the supplied string is already in snake case.
|
||||
"""
|
||||
match = _lower_snake_case_expr.match(str)
|
||||
if match:
|
||||
return True
|
||||
match = _upper_snake_case_expr.match(str)
|
||||
if match:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _split_snake_case(str):
|
||||
"""Splits up a string based on underscores, if it's in snake casing.
|
||||
"""
|
||||
if _is_snake_case(str):
|
||||
return [s.lower() for s in str.split("_")]
|
||||
return str
|
||||
|
||||
|
||||
def _split_camel_case(str):
|
||||
"""Splits up a string based on capitalised camel casing.
|
||||
"""
|
||||
return _words_expr.findall(str)
|
||||
|
||||
|
||||
def _split_cased_words(str):
|
||||
return _split_snake_case(str) if _is_snake_case(str) else _split_camel_case(str)
|
||||
|
||||
|
||||
def to_snake(str):
|
||||
str = "_".join([word.strip().lower() for word in _split_cased_words(str)])
|
||||
|
||||
# Fix acronyms
|
||||
str = str.replace('i_d', 'id')
|
||||
str = str.replace('x_a_p', 'xap')
|
||||
str = str.replace('q_m_k', 'qmk')
|
||||
|
||||
return str
|
||||
|
||||
|
||||
def to_upper_snake(str):
|
||||
return to_snake(str).upper()
|
||||
|
||||
|
||||
def to_camel(str):
|
||||
def _acronym(w):
|
||||
if w.strip().lower() == 'qmk':
|
||||
return 'QMK'
|
||||
elif w.strip().lower() == 'xap':
|
||||
return 'XAP'
|
||||
elif w.strip().lower() == 'id':
|
||||
return 'ID'
|
||||
return w.title()
|
||||
|
||||
return "".join([_acronym(word) for word in _split_cased_words(str)])
|
@ -18,7 +18,8 @@ import_names = {
|
||||
'pyserial': 'serial',
|
||||
'pyusb': 'usb.core',
|
||||
'qmk-dotty-dict': 'dotty_dict',
|
||||
'pillow': 'PIL'
|
||||
'pillow': 'PIL',
|
||||
'Jinja2': 'jinja2'
|
||||
}
|
||||
|
||||
safe_commands = [
|
||||
@ -58,6 +59,7 @@ subcommands = [
|
||||
'qmk.cli.generate.keyboard_c',
|
||||
'qmk.cli.generate.keyboard_h',
|
||||
'qmk.cli.generate.keycodes',
|
||||
'qmk.cli.generate.lighting_map',
|
||||
'qmk.cli.generate.keymap_h',
|
||||
'qmk.cli.generate.make_dependencies',
|
||||
'qmk.cli.generate.rgb_breathe_table',
|
||||
@ -91,6 +93,11 @@ subcommands = [
|
||||
'qmk.cli.userspace.path',
|
||||
'qmk.cli.userspace.remove',
|
||||
'qmk.cli.via2json',
|
||||
'qmk.cli.xap',
|
||||
'qmk.cli.xap.generate_docs',
|
||||
'qmk.cli.xap.generate_json',
|
||||
'qmk.cli.xap.generate_python',
|
||||
'qmk.cli.xap.generate_qmk',
|
||||
]
|
||||
|
||||
|
||||
|
@ -12,7 +12,7 @@ py_dirs = ['lib/python', 'util/ci']
|
||||
|
||||
def yapf_run(files):
|
||||
edit = '--diff' if cli.args.dry_run else '--in-place'
|
||||
yapf_cmd = ['yapf', '-vv', '--recursive', edit, *files]
|
||||
yapf_cmd = ['yapf', '-vv', '--exclude', '**/xap_client/*', '--recursive', edit, *files]
|
||||
try:
|
||||
cli.run(yapf_cmd, check=True, capture_output=False, stdin=DEVNULL)
|
||||
cli.log.info('Successfully formatted the python code.')
|
||||
|
@ -1,24 +1,17 @@
|
||||
"""Ensure text files have the proper line endings.
|
||||
"""
|
||||
from itertools import islice
|
||||
from subprocess import DEVNULL
|
||||
|
||||
from milc import cli
|
||||
|
||||
from qmk.path import normpath
|
||||
|
||||
|
||||
def _get_chunks(it, size):
|
||||
"""Break down a collection into smaller parts
|
||||
"""
|
||||
it = iter(it)
|
||||
return iter(lambda: tuple(islice(it, size)), ())
|
||||
from qmk.commands import get_chunks
|
||||
|
||||
|
||||
def dos2unix_run(files):
|
||||
"""Spawn multiple dos2unix subprocess avoiding too long commands on formatting everything
|
||||
"""
|
||||
for chunk in _get_chunks([normpath(file).as_posix() for file in files], 10):
|
||||
for chunk in get_chunks([normpath(file).as_posix() for file in files], 10):
|
||||
dos2unix = cli.run(['dos2unix', *chunk])
|
||||
|
||||
if dos2unix.returncode:
|
||||
|
@ -2,6 +2,7 @@
|
||||
"""
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import hjson
|
||||
import json
|
||||
|
||||
from milc import cli
|
||||
@ -13,6 +14,7 @@ from qmk.json_schema import json_load
|
||||
from qmk.keymap import list_keymaps
|
||||
from qmk.keyboard import find_readme, list_keyboards, keyboard_alias_definitions
|
||||
from qmk.keycodes import load_spec, list_versions, list_languages
|
||||
from qmk.xap.common import get_xap_definition_files, update_xap_definitions
|
||||
|
||||
DATA_PATH = Path('data')
|
||||
TEMPLATE_PATH = DATA_PATH / 'templates/api/'
|
||||
@ -89,6 +91,22 @@ def _filtered_keyboard_list():
|
||||
return keyboard_list
|
||||
|
||||
|
||||
def _resolve_xap_specs(output_folder):
|
||||
"""To make it easier for consumers, publish pre-merged spec files
|
||||
"""
|
||||
overall = None
|
||||
for file in get_xap_definition_files():
|
||||
overall = update_xap_definitions(overall, hjson.load(file.open(encoding='utf-8')))
|
||||
|
||||
# Inject dummy bits for unspecified response flags
|
||||
for n in range(0, 8):
|
||||
if str(n) not in overall['response_flags']['bits']:
|
||||
overall['response_flags']['bits'][str(n)] = {'name': '', 'description': '', 'define': '-'}
|
||||
|
||||
output_file = output_folder / (file.stem + ".json")
|
||||
output_file.write_text(json.dumps(overall, indent=4), encoding='utf-8')
|
||||
|
||||
|
||||
@cli.argument('-n', '--dry-run', arg_only=True, action='store_true', help="Don't write the data to disk.")
|
||||
@cli.argument('-f', '--filter', arg_only=True, action='append', default=[], help="Filter the list of keyboards based on partial name matches the supplied value. May be passed multiple times.")
|
||||
@cli.subcommand('Generate QMK API data', hidden=False if cli.config.user.developer else True)
|
||||
@ -186,6 +204,7 @@ def generate_api(cli):
|
||||
|
||||
# Feature specific handling
|
||||
_resolve_keycode_specs(v1_dir)
|
||||
_resolve_xap_specs(v1_dir / 'xap')
|
||||
|
||||
# Write the global JSON files
|
||||
keyboard_all_json = json.dumps({'last_updated': current_datetime(), 'keyboards': kb_all}, separators=(',', ':'))
|
||||
|
126
lib/python/qmk/cli/generate/lighting_map.py
Normal file
126
lib/python/qmk/cli/generate/lighting_map.py
Normal file
@ -0,0 +1,126 @@
|
||||
from milc import cli
|
||||
|
||||
from qmk.path import normpath
|
||||
from qmk.commands import dump_lines
|
||||
from qmk.lighting import load_lighting_spec
|
||||
from qmk.constants import GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE
|
||||
|
||||
PREFIX_MAP = {
|
||||
'rgblight': {
|
||||
'ifdef': 'RGBLIGHT_EFFECT',
|
||||
'def': 'RGBLIGHT_MODE',
|
||||
},
|
||||
'rgb_matrix': {
|
||||
'ifdef': 'ENABLE_RGB_MATRIX',
|
||||
'def': 'RGB_MATRIX',
|
||||
},
|
||||
'led_matrix': {
|
||||
'ifdef': 'ENABLE_LED_MATRIX',
|
||||
'def': 'LED_MATRIX',
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _always_enabled(id):
|
||||
"""Assumption that first effect is always enabled
|
||||
"""
|
||||
return id == '0x00'
|
||||
|
||||
|
||||
def _wrap_ifdef(line, define):
|
||||
return f'''
|
||||
#ifdef {define}
|
||||
{line}
|
||||
#endif'''
|
||||
|
||||
|
||||
def _append_lighting_map(lines, feature, spec):
|
||||
"""Translate effect to 'constant id'->'firmware id' lookup table
|
||||
"""
|
||||
groups = spec.get('groups', {})
|
||||
ifdef_prefix = PREFIX_MAP[feature]['ifdef']
|
||||
def_prefix = PREFIX_MAP[feature]['def']
|
||||
|
||||
lines.append(f'static const uint8_t {feature}_effect_map[][2] PROGMEM = {{')
|
||||
for id, obj in spec.get('effects', {}).items():
|
||||
define = obj['define']
|
||||
offset = f' + {obj["offset"]}' if obj['offset'] else ''
|
||||
|
||||
line = f'{{ {id}, {def_prefix}_{define}{offset}}},'
|
||||
|
||||
if not _always_enabled(id):
|
||||
line = _wrap_ifdef(line, f'{ifdef_prefix}_{define}')
|
||||
|
||||
group = groups.get(obj.get('group', None), {}).get('define', None)
|
||||
if group:
|
||||
line = _wrap_ifdef(line, group)
|
||||
|
||||
lines.append(line)
|
||||
|
||||
lines.append('};')
|
||||
|
||||
# add helper funcs
|
||||
lines.append(
|
||||
f'''
|
||||
uint8_t {feature}_effect_to_id(uint8_t val) {{
|
||||
for(uint8_t i = 0; i < ARRAY_SIZE({feature}_effect_map); i++) {{
|
||||
if (pgm_read_byte(&{feature}_effect_map[i][1]) == val)
|
||||
return pgm_read_byte(&{feature}_effect_map[i][0]);
|
||||
}}
|
||||
return 0xFF;
|
||||
}}
|
||||
|
||||
uint8_t {feature}_id_to_effect(uint8_t val) {{
|
||||
for(uint8_t i = 0; i < ARRAY_SIZE({feature}_effect_map); i++) {{
|
||||
if (pgm_read_byte(&{feature}_effect_map[i][0]) == val)
|
||||
return pgm_read_byte(&{feature}_effect_map[i][1]);
|
||||
}}
|
||||
return 0xFF;
|
||||
}}'''
|
||||
)
|
||||
|
||||
|
||||
def _append_lighting_bit_field(lines, feature, spec):
|
||||
"""Translate effect to bit of bit-field
|
||||
"""
|
||||
groups = spec.get('groups', {})
|
||||
ifdef_prefix = PREFIX_MAP[feature]['ifdef']
|
||||
|
||||
lines.append(f'enum {{ ENABLED_{feature.upper()}_EFFECTS = 0')
|
||||
for id, obj in spec.get('effects', {}).items():
|
||||
define = obj['define']
|
||||
|
||||
line = f' | (1ULL << {id})'
|
||||
|
||||
if not _always_enabled(id):
|
||||
line = _wrap_ifdef(line, f'{ifdef_prefix}_{define}')
|
||||
|
||||
group = groups.get(obj.get('group', None), {}).get('define', None)
|
||||
if group:
|
||||
line = _wrap_ifdef(line, group)
|
||||
|
||||
lines.append(line)
|
||||
|
||||
lines.append('};')
|
||||
|
||||
|
||||
def _append_lighting_mapping(lines, feature):
|
||||
"""Generate lookup table and bit-field of effect
|
||||
"""
|
||||
spec = load_lighting_spec(feature)
|
||||
|
||||
_append_lighting_bit_field(lines, feature, spec)
|
||||
_append_lighting_map(lines, feature, spec)
|
||||
|
||||
|
||||
@cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to')
|
||||
@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help='Quiet mode, only output error messages')
|
||||
@cli.argument('-f', '--feature', required=True, help='Feature to generate map', choices=PREFIX_MAP.keys())
|
||||
@cli.subcommand('Generates effect header.')
|
||||
def generate_lighting_map(cli):
|
||||
# Preamble
|
||||
lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '#pragma once', '// clang-format off']
|
||||
|
||||
_append_lighting_mapping(lines, cli.args.feature)
|
||||
|
||||
dump_lines(cli.args.output, lines, cli.args.quiet)
|
1
lib/python/qmk/cli/xap/__init__.py
Executable file
1
lib/python/qmk/cli/xap/__init__.py
Executable file
@ -0,0 +1 @@
|
||||
from .xap import xap
|
58
lib/python/qmk/cli/xap/generate_docs.py
Executable file
58
lib/python/qmk/cli/xap/generate_docs.py
Executable file
@ -0,0 +1,58 @@
|
||||
"""This script generates the XAP protocol documentation.
|
||||
"""
|
||||
import hjson
|
||||
|
||||
from milc import cli
|
||||
|
||||
from qmk.constants import QMK_FIRMWARE
|
||||
from qmk.path import normpath
|
||||
from qmk.commands import dump_lines
|
||||
from qmk.keyboard import keyboard_completer, keyboard_folder
|
||||
from qmk.xap.common import get_xap_definition_files, update_xap_definitions, merge_xap_defs, render_xap_output
|
||||
|
||||
|
||||
def _patch_spec_for_docs(spec):
|
||||
# Inject dummy bits for unspecified response flags
|
||||
for n in range(0, 8):
|
||||
if str(n) not in spec['response_flags']['bits']:
|
||||
spec['response_flags']['bits'][str(n)] = {'name': '', 'description': '', 'define': '-'}
|
||||
|
||||
|
||||
@cli.subcommand('Generates the XAP protocol documentation.', hidden=False if cli.config.user.developer else True)
|
||||
def xap_generate_docs(cli):
|
||||
"""Generates the XAP protocol documentation by merging the definitions files, and producing the corresponding Markdown document under `/docs/`.
|
||||
"""
|
||||
versions = []
|
||||
|
||||
overall = None
|
||||
for file in get_xap_definition_files():
|
||||
overall = update_xap_definitions(overall, hjson.load(file.open(encoding='utf-8')))
|
||||
|
||||
_patch_spec_for_docs(overall)
|
||||
|
||||
output_doc = QMK_FIRMWARE / "docs" / f"{file.stem}.md"
|
||||
versions.append(overall['version'])
|
||||
output = render_xap_output('docs', 'docs.md.j2', overall)
|
||||
with open(output_doc, "w", encoding='utf-8') as out_file:
|
||||
out_file.write(output)
|
||||
|
||||
output_doc = QMK_FIRMWARE / "docs" / "xap_protocol.md"
|
||||
output = render_xap_output('docs', 'versions.md.j2', overall, versions=versions)
|
||||
with open(output_doc, "w", encoding='utf-8') as out_file:
|
||||
out_file.write(output)
|
||||
|
||||
|
||||
@cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to')
|
||||
@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages")
|
||||
@cli.argument('-km', '--keymap', help='The keymap\'s name - "default" if not specified')
|
||||
@cli.argument('-kb', '--keyboard', required=True, type=keyboard_folder, completer=keyboard_completer, help='Name of the keyboard')
|
||||
@cli.subcommand('Generates the XAP protocol documentation for a given keyboard/keymap.', hidden=False if cli.config.user.developer else True)
|
||||
def xap_generate_keyboard_docs(cli):
|
||||
"""Generates the XAP protocol documentation for a given keyboard/keymap and producing the corresponding Markdown.
|
||||
"""
|
||||
spec = merge_xap_defs(cli.args.keyboard, cli.args.keymap or 'default')
|
||||
|
||||
_patch_spec_for_docs(spec)
|
||||
|
||||
output = render_xap_output('docs', 'docs.md.j2', spec)
|
||||
dump_lines(cli.args.output, output.split('\n'), cli.args.quiet)
|
13
lib/python/qmk/cli/xap/generate_json.py
Executable file
13
lib/python/qmk/cli/xap/generate_json.py
Executable file
@ -0,0 +1,13 @@
|
||||
"""This script generates the consolidated XAP protocol definitions.
|
||||
"""
|
||||
import hjson
|
||||
from milc import cli
|
||||
from qmk.xap.common import latest_xap_defs
|
||||
|
||||
|
||||
@cli.subcommand('Generates the consolidated XAP protocol definitions.', hidden=False if cli.config.user.developer else True)
|
||||
def xap_generate_json(cli):
|
||||
"""Generates the consolidated XAP protocol definitions.
|
||||
"""
|
||||
defs = latest_xap_defs()
|
||||
print(hjson.dumps(defs))
|
19
lib/python/qmk/cli/xap/generate_python.py
Normal file
19
lib/python/qmk/cli/xap/generate_python.py
Normal file
@ -0,0 +1,19 @@
|
||||
"""This script generates the python XAP client.
|
||||
"""
|
||||
from milc import cli
|
||||
|
||||
from qmk.commands import dump_lines
|
||||
from qmk.constants import QMK_FIRMWARE
|
||||
from qmk.xap.common import latest_xap_defs, render_xap_output
|
||||
|
||||
|
||||
@cli.subcommand('Generates the python XAP client.', hidden=False if cli.config.user.developer else True)
|
||||
def xap_generate_python(cli):
|
||||
defs = latest_xap_defs()
|
||||
|
||||
parent = QMK_FIRMWARE / 'lib' / 'python' / 'xap_client'
|
||||
for name in ['types.py', 'routes.py', 'constants.py']:
|
||||
output = render_xap_output('client/python', f'{name}.j2', defs)
|
||||
lines = output.split('\n')
|
||||
|
||||
dump_lines(parent / name, lines)
|
78
lib/python/qmk/cli/xap/generate_qmk.py
Executable file
78
lib/python/qmk/cli/xap/generate_qmk.py
Executable file
@ -0,0 +1,78 @@
|
||||
"""This script generates the XAP protocol generated sources to be compiled into QMK firmware.
|
||||
"""
|
||||
from milc import cli
|
||||
|
||||
from qmk.path import normpath
|
||||
from qmk.keyboard import keyboard_completer, keyboard_folder
|
||||
from qmk.xap.common import render_xap_output, merge_xap_defs
|
||||
from qmk.xap.gen_firmware.blob_generator import generate_blob
|
||||
|
||||
|
||||
@cli.argument('-o', '--output', type=normpath, help='File to write to')
|
||||
@cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='Name of the keyboard')
|
||||
@cli.argument('-km', '--keymap', help='The keymap\'s name')
|
||||
@cli.subcommand('Generates the XAP protocol include.', hidden=False if cli.config.user.developer else True)
|
||||
def xap_generate_qmk_inc(cli):
|
||||
"""Generates the XAP protocol inline codegen file, generated during normal build.
|
||||
"""
|
||||
# Determine our keyboard/keymap
|
||||
if not cli.args.keyboard:
|
||||
cli.log.error('Missing parameter: --keyboard')
|
||||
cli.subcommands['xap-generate-qmk-inc'].print_help()
|
||||
return False
|
||||
if not cli.args.keymap:
|
||||
cli.log.error('Missing parameter: --keymap')
|
||||
cli.subcommands['xap-generate-qmk-inc'].print_help()
|
||||
return False
|
||||
|
||||
defs = merge_xap_defs(cli.args.keyboard, cli.args.keymap)
|
||||
with open(cli.args.output, 'w', encoding='utf-8') as out_file:
|
||||
r = render_xap_output('firmware', 'xap_generated.inl.j2', defs, keyboard=cli.args.keyboard, keymap=cli.args.keymap)
|
||||
while r.find('\n\n\n') != -1:
|
||||
r = r.replace('\n\n\n', '\n\n')
|
||||
out_file.write(r)
|
||||
|
||||
|
||||
@cli.argument('-o', '--output', type=normpath, help='File to write to')
|
||||
@cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='Name of the keyboard')
|
||||
@cli.argument('-km', '--keymap', help='The keymap\'s name')
|
||||
@cli.subcommand('Generates the XAP protocol include.', hidden=False if cli.config.user.developer else True)
|
||||
def xap_generate_qmk_h(cli):
|
||||
"""Generates the XAP protocol header file, generated during normal build.
|
||||
"""
|
||||
# Determine our keyboard/keymap
|
||||
if not cli.args.keyboard:
|
||||
cli.log.error('Missing parameter: --keyboard')
|
||||
cli.subcommands['xap-generate-qmk-h'].print_help()
|
||||
return False
|
||||
if not cli.args.keymap:
|
||||
cli.log.error('Missing parameter: --keymap')
|
||||
cli.subcommands['xap-generate-qmk-h'].print_help()
|
||||
return False
|
||||
|
||||
defs = merge_xap_defs(cli.args.keyboard, cli.args.keymap)
|
||||
with open(cli.args.output, 'w', encoding='utf-8') as out_file:
|
||||
r = render_xap_output('firmware', 'xap_generated.h.j2', defs, keyboard=cli.args.keyboard, keymap=cli.args.keymap)
|
||||
while r.find('\n\n\n') != -1:
|
||||
r = r.replace('\n\n\n', '\n\n')
|
||||
out_file.write(r)
|
||||
|
||||
|
||||
@cli.argument('-o', '--output', type=normpath, help='File to write to')
|
||||
@cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='Name of the keyboard')
|
||||
@cli.argument('-km', '--keymap', help='The keymap\'s name')
|
||||
@cli.subcommand('Generates the XAP config payload include.', hidden=False if cli.config.user.developer else True)
|
||||
def xap_generate_qmk_blob_h(cli):
|
||||
"""Generates the XAP config payload header file, generated during normal build.
|
||||
"""
|
||||
# Determine our keyboard/keymap
|
||||
if not cli.args.keyboard:
|
||||
cli.log.error('Missing parameter: --keyboard')
|
||||
cli.subcommands['xap-generate-qmk-blob-h'].print_help()
|
||||
return False
|
||||
if not cli.args.keymap:
|
||||
cli.log.error('Missing parameter: --keymap')
|
||||
cli.subcommands['xap-generate-qmk-blob-h'].print_help()
|
||||
return False
|
||||
|
||||
generate_blob(cli.args.output, cli.args.keyboard, cli.args.keymap)
|
283
lib/python/qmk/cli/xap/xap.py
Normal file
283
lib/python/qmk/cli/xap/xap.py
Normal file
@ -0,0 +1,283 @@
|
||||
"""Interactions with compatible XAP devices
|
||||
"""
|
||||
import cmd
|
||||
|
||||
from milc import cli
|
||||
|
||||
from qmk.keycodes import load_spec
|
||||
from qmk.decorators import lru_cache
|
||||
from qmk.keyboard import render_layout
|
||||
|
||||
from xap_client import XAPClient, XAPEventType, XAPSecureStatus, XAPConfigRgblight, XAPConfigBacklight, XAPConfigRgbMatrix, XAPRoutes
|
||||
|
||||
|
||||
def print_dotted_output(kb_info_json, prefix=''):
|
||||
"""Print the info.json in a plain text format with dot-joined keys.
|
||||
"""
|
||||
for key in sorted(kb_info_json):
|
||||
new_prefix = f'{prefix}.{key}' if prefix else key
|
||||
|
||||
if key in ['parse_errors', 'parse_warnings']:
|
||||
continue
|
||||
elif key == 'layouts' and prefix == '':
|
||||
cli.echo(' {fg_blue}layouts{fg_reset}: %s', ', '.join(sorted(kb_info_json['layouts'].keys())))
|
||||
elif isinstance(kb_info_json[key], bytes):
|
||||
conv = "".join(["{:02X}".format(b) for b in kb_info_json[key]])
|
||||
cli.echo(' {fg_blue}%s{fg_reset}: %s', new_prefix, conv)
|
||||
elif isinstance(kb_info_json[key], dict):
|
||||
print_dotted_output(kb_info_json[key], new_prefix)
|
||||
elif isinstance(kb_info_json[key], list):
|
||||
data = kb_info_json[key]
|
||||
if len(data) and isinstance(data[0], dict):
|
||||
for index, item in enumerate(data, start=0):
|
||||
cli.echo(' {fg_blue}%s.%s{fg_reset}: %s', new_prefix, index, str(item))
|
||||
else:
|
||||
cli.echo(' {fg_blue}%s{fg_reset}: %s', new_prefix, ', '.join(map(str, data)))
|
||||
else:
|
||||
cli.echo(' {fg_blue}%s{fg_reset}: %s', new_prefix, kb_info_json[key])
|
||||
|
||||
|
||||
@lru_cache(timeout=5)
|
||||
def _load_keycodes(keycode_version):
|
||||
"""Gets keycode data for the required version of the XAP definitions.
|
||||
"""
|
||||
spec = load_spec(keycode_version)
|
||||
|
||||
# Transform into something more usable - { raw_value : first alias || keycode }
|
||||
ret = {int(k, 16): v.get('aliases', [v.get('key')])[0] for k, v in spec['keycodes'].items()}
|
||||
|
||||
# TODO: handle non static keycodes
|
||||
for k, v in spec['ranges'].items():
|
||||
lo, mask = map(lambda x: int(x, 16), k.split('/'))
|
||||
hi = lo + mask
|
||||
define = v.get("define")
|
||||
for i in range(lo, hi):
|
||||
if i not in ret:
|
||||
if define == 'QK_TO':
|
||||
layer = i & 0x1F
|
||||
ret[i] = f'TO({layer})'
|
||||
elif define == 'QK_MOMENTARY':
|
||||
layer = i & 0x1F
|
||||
ret[i] = f'MO({layer})'
|
||||
elif define == 'QK_LAYER_TAP':
|
||||
layer = (((i) >> 8) & 0xF)
|
||||
keycode = ((i) & 0xFF)
|
||||
ret[i] = f'LT({layer}, {ret.get(keycode, "???")})'
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def _list_devices():
|
||||
"""Dump out available devices
|
||||
"""
|
||||
cli.log.info('Available devices:')
|
||||
for dev in XAPClient.devices():
|
||||
device = XAPClient().connect(dev)
|
||||
ver = device.version()
|
||||
|
||||
cli.log.info(' %04x:%04x %s %s [API:%s]', dev['vendor_id'], dev['product_id'], dev['manufacturer_string'], dev['product_string'], ver['xap'])
|
||||
|
||||
if cli.args.verbose:
|
||||
data = device.info()
|
||||
# TODO: better formatting like 'lsusb -v'?
|
||||
print_dotted_output(data)
|
||||
|
||||
|
||||
class XAPShell(cmd.Cmd):
|
||||
intro = 'Welcome to the XAP shell. Type help or ? to list commands.\n'
|
||||
prompt = 'Ψ> '
|
||||
|
||||
def __init__(self, device):
|
||||
cmd.Cmd.__init__(self)
|
||||
self.device = device
|
||||
# cache keycodes for this device
|
||||
self.keycodes = _load_keycodes(device.version().get('keycodes', 'latest'))
|
||||
|
||||
# TODO: dummy code is only to PoC kb/user keycodes
|
||||
kb_keycodes = self.device.info().get('keycodes', [])
|
||||
for index, item in enumerate(kb_keycodes):
|
||||
self.keycodes[0x7E00 + index] = item['key']
|
||||
|
||||
user_keycodes = self.device.info().get('user_keycodes', [])
|
||||
for index, item in enumerate(user_keycodes):
|
||||
self.keycodes[0x7E40 + index] = item['key']
|
||||
|
||||
def do_about(self, arg):
|
||||
"""Prints out the version info of QMK
|
||||
"""
|
||||
data = self.device.version()
|
||||
print_dotted_output(data)
|
||||
|
||||
def do_status(self, arg):
|
||||
"""Prints out the current device state
|
||||
"""
|
||||
status = self.device.status()
|
||||
print('Secure:%s' % status.get('lock', '???'))
|
||||
|
||||
def do_unlock(self, arg):
|
||||
"""Initiate secure unlock
|
||||
"""
|
||||
self.device.unlock()
|
||||
print('Unlock Requested...')
|
||||
|
||||
def do_lock(self, arg):
|
||||
"""Disable secure routes
|
||||
"""
|
||||
self.device.lock()
|
||||
|
||||
def do_reset(self, arg):
|
||||
"""Jump to bootloader if unlocked
|
||||
"""
|
||||
if not self.device.reset():
|
||||
print("Reboot to bootloader failed")
|
||||
return True
|
||||
|
||||
def do_listen(self, arg):
|
||||
"""Log out XAP broadcast messages
|
||||
"""
|
||||
try:
|
||||
cli.log.info('Listening for XAP broadcasts...')
|
||||
while 1:
|
||||
(event, data) = self.device.listen()
|
||||
|
||||
if event == XAPEventType.SECURE_STATUS:
|
||||
secure_status = XAPSecureStatus(data[0]).name
|
||||
|
||||
cli.log.info(' Secure[%s]', secure_status)
|
||||
else:
|
||||
cli.log.info(' Broadcast: type[%02x] data:[%s]', event, data.hex())
|
||||
|
||||
except KeyboardInterrupt:
|
||||
cli.log.info('Stopping...')
|
||||
|
||||
def do_keycode(self, arg):
|
||||
"""Prints out the keycode value of a certain layer, row, and column
|
||||
"""
|
||||
data = bytes(map(int, arg.split()))
|
||||
if len(data) != 3:
|
||||
cli.log.error('Invalid args')
|
||||
return
|
||||
|
||||
keycode = self.device.transaction(b'\x04\x03', data)
|
||||
keycode = int.from_bytes(keycode, 'little')
|
||||
print(f'keycode:{self.keycodes.get(keycode, "unknown")}[{keycode}]')
|
||||
|
||||
def do_keymap(self, arg):
|
||||
"""Prints out the keycode values of a certain layer
|
||||
"""
|
||||
data = bytes(map(int, arg.split()))
|
||||
if len(data) != 1:
|
||||
cli.log.error('Invalid args')
|
||||
return
|
||||
|
||||
info = self.device.info()
|
||||
rows = info['matrix_size']['rows']
|
||||
cols = info['matrix_size']['cols']
|
||||
|
||||
for r in range(rows):
|
||||
for c in range(cols):
|
||||
q = data + r.to_bytes(1, byteorder='little') + c.to_bytes(1, byteorder='little')
|
||||
keycode = self.device.transaction(b'\x04\x03', q)
|
||||
keycode = int.from_bytes(keycode, 'little')
|
||||
print(f'| {self.keycodes.get(keycode, "unknown").ljust(7)} ', end='', flush=True)
|
||||
print('|')
|
||||
|
||||
def do_layer(self, arg):
|
||||
"""Renders keycode values of a certain layer
|
||||
"""
|
||||
data = bytes(map(int, arg.split()))
|
||||
if len(data) != 1:
|
||||
cli.log.error('Invalid args')
|
||||
return
|
||||
|
||||
info = self.device.info()
|
||||
|
||||
# Assumptions on selected layout rather than prompt
|
||||
first_layout = next(iter(info['layouts']))
|
||||
layout = info['layouts'][first_layout]['layout']
|
||||
|
||||
keycodes = []
|
||||
for item in layout:
|
||||
q = data + bytes(item['matrix'])
|
||||
keycode = self.device.transaction(b'\x04\x03', q)
|
||||
keycode = int.from_bytes(keycode, 'little')
|
||||
keycodes.append(self.keycodes.get(keycode, '???'))
|
||||
|
||||
print(render_layout(layout, False, keycodes))
|
||||
|
||||
def do_exit(self, line):
|
||||
"""Quit shell
|
||||
"""
|
||||
return True
|
||||
|
||||
def do_EOF(self, line): # noqa: N802
|
||||
"""Quit shell (ctrl+D)
|
||||
"""
|
||||
return True
|
||||
|
||||
def loop(self):
|
||||
"""Wrapper for cmdloop that handles ctrl+C
|
||||
"""
|
||||
try:
|
||||
self.cmdloop()
|
||||
print('')
|
||||
except KeyboardInterrupt:
|
||||
print('^C')
|
||||
return False
|
||||
|
||||
def do_dump(self, line):
|
||||
caps = self.device.int_transaction(XAPRoutes.LIGHTING_CAPABILITIES_QUERY)
|
||||
|
||||
if caps & (1 << XAPRoutes.LIGHTING_BACKLIGHT[-1]):
|
||||
ret = self.device.transaction(XAPRoutes.LIGHTING_BACKLIGHT_GET_CONFIG)
|
||||
ret = XAPConfigBacklight.from_bytes(ret)
|
||||
print(ret)
|
||||
|
||||
ret = self.device.int_transaction(XAPRoutes.LIGHTING_BACKLIGHT_GET_ENABLED_EFFECTS)
|
||||
print(f'XAPEffectBacklight(enabled={bin(ret)})')
|
||||
|
||||
if caps & (1 << XAPRoutes.LIGHTING_RGBLIGHT[-1]):
|
||||
ret = self.device.transaction(XAPRoutes.LIGHTING_RGBLIGHT_GET_CONFIG)
|
||||
ret = XAPConfigRgblight.from_bytes(ret)
|
||||
print(ret)
|
||||
|
||||
ret = self.device.int_transaction(XAPRoutes.LIGHTING_RGBLIGHT_GET_ENABLED_EFFECTS)
|
||||
print(f'XAPEffectRgblight(enabled={bin(ret)})')
|
||||
|
||||
if caps & (1 << XAPRoutes.LIGHTING_RGB_MATRIX[-1]):
|
||||
ret = self.device.transaction(XAPRoutes.LIGHTING_RGB_MATRIX_GET_CONFIG)
|
||||
ret = XAPConfigRgbMatrix.from_bytes(ret)
|
||||
print(ret)
|
||||
|
||||
ret = self.device.int_transaction(XAPRoutes.LIGHTING_RGB_MATRIX_GET_ENABLED_EFFECTS)
|
||||
print(f'XAPEffectRgbMatrix(enabled={bin(ret)})')
|
||||
|
||||
|
||||
@cli.argument('-v', '--verbose', arg_only=True, action='store_true', help='Turns on verbose output.')
|
||||
@cli.argument('-d', '--device', help='device to select - uses format <pid>:<vid>.')
|
||||
@cli.argument('-l', '--list', arg_only=True, action='store_true', help='List available devices.')
|
||||
@cli.argument('-i', '--interactive', arg_only=True, action='store_true', help='Start interactive shell.')
|
||||
@cli.argument('action', nargs='*', arg_only=True, default=['listen'], help='Shell command and any arguments to run standalone')
|
||||
@cli.subcommand('Acquire debugging information from usb XAP devices.', hidden=False if cli.config.user.developer else True)
|
||||
def xap(cli):
|
||||
"""Acquire debugging information from XAP devices
|
||||
"""
|
||||
if cli.args.list:
|
||||
return _list_devices()
|
||||
|
||||
# Connect to first available device
|
||||
devices = XAPClient.devices()
|
||||
if not devices:
|
||||
cli.log.error('No devices found!')
|
||||
return False
|
||||
|
||||
dev = devices[0]
|
||||
cli.log.info('Connecting to: %04x:%04x %s %s', dev['vendor_id'], dev['product_id'], dev['manufacturer_string'], dev['product_string'])
|
||||
device = XAPClient().connect(dev)
|
||||
|
||||
# shell?
|
||||
if cli.args.interactive:
|
||||
XAPShell(device).loop()
|
||||
return True
|
||||
|
||||
XAPShell(device).onecmd(' '.join(cli.args.action))
|
@ -3,6 +3,7 @@
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
from itertools import islice
|
||||
from pathlib import Path
|
||||
|
||||
from milc import cli
|
||||
@ -99,6 +100,13 @@ def in_virtualenv():
|
||||
return active_prefix != sys.prefix
|
||||
|
||||
|
||||
def get_chunks(it, size):
|
||||
"""Break down a collection into smaller parts
|
||||
"""
|
||||
it = iter(it)
|
||||
return iter(lambda: tuple(islice(it, size)), ())
|
||||
|
||||
|
||||
def dump_lines(output_file, lines, quiet=True, remove_repeated_newlines=False):
|
||||
"""Handle dumping to stdout or file
|
||||
Creates parent folders if required
|
||||
|
@ -150,6 +150,11 @@ GPL2_HEADER_SH_LIKE = f'''\
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
'''
|
||||
|
||||
GPL2_HEADER_XML_LIKE = f'''\
|
||||
<!--- Copyright {date.today().year} QMK --->
|
||||
<!--- SPDX-License-Identifier: GPL-2.0-or-later --->
|
||||
'''
|
||||
|
||||
GENERATED_HEADER_C_LIKE = '''\
|
||||
/*******************************************************************************
|
||||
88888888888 888 d8b .d888 d8b 888 d8b
|
||||
@ -201,6 +206,32 @@ GENERATED_HEADER_SH_LIKE = '''\
|
||||
################################################################################
|
||||
'''
|
||||
|
||||
GENERATED_HEADER_XML_LIKE = '''\
|
||||
<!---
|
||||
*******************************************************************************
|
||||
88888888888 888 d8b .d888 d8b 888 d8b
|
||||
888 888 Y8P d88P" Y8P 888 Y8P
|
||||
888 888 888 888
|
||||
888 88888b. 888 .d8888b 888888 888 888 .d88b. 888 .d8888b
|
||||
888 888 "88b 888 88K 888 888 888 d8P Y8b 888 88K
|
||||
888 888 888 888 "Y8888b. 888 888 888 88888888 888 "Y8888b.
|
||||
888 888 888 888 X88 888 888 888 Y8b. 888 X88
|
||||
888 888 888 888 88888P' 888 888 888 "Y8888 888 88888P'
|
||||
888 888
|
||||
888 888
|
||||
888 888
|
||||
.d88b. .d88b. 88888b. .d88b. 888d888 8888b. 888888 .d88b. .d88888
|
||||
d88P"88b d8P Y8b 888 "88b d8P Y8b 888P" "88b 888 d8P Y8b d88" 888
|
||||
888 888 88888888 888 888 88888888 888 .d888888 888 88888888 888 888
|
||||
Y88b 888 Y8b. 888 888 Y8b. 888 888 888 Y88b. Y8b. Y88b 888
|
||||
"Y88888 "Y8888 888 888 "Y8888 888 "Y888888 "Y888 "Y8888 "Y88888
|
||||
888
|
||||
Y8b d88P
|
||||
"Y88P"
|
||||
*******************************************************************************
|
||||
--->
|
||||
'''
|
||||
|
||||
LICENSE_TEXTS = [
|
||||
(
|
||||
'GPL-2.0-or-later', [
|
||||
|
@ -1040,7 +1040,13 @@ def keymap_json_config(keyboard, keymap, force_layout=None):
|
||||
keymap_folder = locate_keymap(keyboard, keymap, force_layout=force_layout).parent
|
||||
|
||||
km_info_json = parse_configurator_json(keymap_folder / 'keymap.json')
|
||||
return km_info_json.get('config', {})
|
||||
ret = km_info_json.get('config', {})
|
||||
|
||||
# TODO: dummy code is only to PoC kb/user keycodes
|
||||
if 'keycodes' in km_info_json:
|
||||
ret['user_keycodes'] = km_info_json['keycodes']
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def keymap_json(keyboard, keymap, force_layout=None):
|
||||
|
@ -5,6 +5,7 @@ from functools import lru_cache
|
||||
from math import ceil
|
||||
from pathlib import Path
|
||||
import os
|
||||
import shutil
|
||||
from glob import glob
|
||||
|
||||
import qmk.path
|
||||
@ -12,6 +13,7 @@ from qmk.c_parse import parse_config_h_file
|
||||
from qmk.json_schema import json_load
|
||||
from qmk.makefile import parse_rules_mk_file
|
||||
|
||||
KEY_WIDTH = 4 if shutil.get_terminal_size().columns < 160 else 6
|
||||
BOX_DRAWING_CHARACTERS = {
|
||||
"unicode": {
|
||||
"tl": "┌",
|
||||
@ -277,9 +279,9 @@ def render_layouts(info_json, render_ascii):
|
||||
|
||||
def render_key_rect(textpad, x, y, w, h, label, style):
|
||||
box_chars = BOX_DRAWING_CHARACTERS[style]
|
||||
x = ceil(x * 4)
|
||||
x = ceil(x * KEY_WIDTH)
|
||||
y = ceil(y * 3)
|
||||
w = ceil(w * 4)
|
||||
w = ceil(w * KEY_WIDTH)
|
||||
h = ceil(h * 3)
|
||||
|
||||
label_len = w - 2
|
||||
@ -306,9 +308,9 @@ def render_key_rect(textpad, x, y, w, h, label, style):
|
||||
|
||||
def render_key_isoenter(textpad, x, y, w, h, label, style):
|
||||
box_chars = BOX_DRAWING_CHARACTERS[style]
|
||||
x = ceil(x * 4)
|
||||
x = ceil(x * KEY_WIDTH)
|
||||
y = ceil(y * 3)
|
||||
w = ceil(w * 4)
|
||||
w = ceil(w * KEY_WIDTH)
|
||||
h = ceil(h * 3)
|
||||
|
||||
label_len = w - 1
|
||||
@ -338,9 +340,9 @@ def render_key_isoenter(textpad, x, y, w, h, label, style):
|
||||
|
||||
def render_key_baenter(textpad, x, y, w, h, label, style):
|
||||
box_chars = BOX_DRAWING_CHARACTERS[style]
|
||||
x = ceil(x * 4)
|
||||
x = ceil(x * KEY_WIDTH)
|
||||
y = ceil(y * 3)
|
||||
w = ceil(w * 4)
|
||||
w = ceil(w * KEY_WIDTH)
|
||||
h = ceil(h * 3)
|
||||
|
||||
label_len = w + 1
|
||||
|
37
lib/python/qmk/lighting.py
Normal file
37
lib/python/qmk/lighting.py
Normal file
@ -0,0 +1,37 @@
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
from qmk.json_schema import json_load
|
||||
|
||||
|
||||
def list_lighting_versions(feature):
|
||||
"""Return available versions - sorted newest first
|
||||
"""
|
||||
ret = []
|
||||
for file in Path('data/constants/lighting/').glob(f'{feature}_[0-9].[0-9].[0-9].hjson'):
|
||||
ret.append(file.stem.split('_')[-1])
|
||||
|
||||
ret.sort(reverse=True)
|
||||
return ret
|
||||
|
||||
|
||||
def load_lighting_spec(feature, version='latest'):
|
||||
"""Build lighting data from the requested spec file
|
||||
"""
|
||||
if version == 'latest':
|
||||
version = list_lighting_versions(feature)[0]
|
||||
|
||||
spec = json_load(Path(f'data/constants/lighting/{feature}_{version}.hjson'))
|
||||
|
||||
# preprocess for gross rgblight "mode + n"
|
||||
for obj in spec.get('effects', {}).values():
|
||||
define = obj['key']
|
||||
offset = 0
|
||||
found = re.match('(.*)_(\\d+)$', define)
|
||||
if found:
|
||||
define = found.group(1)
|
||||
offset = int(found.group(2)) - 1
|
||||
obj['define'] = define
|
||||
obj['offset'] = offset
|
||||
|
||||
return spec
|
0
lib/python/qmk/xap/__init__.py
Normal file
0
lib/python/qmk/xap/__init__.py
Normal file
140
lib/python/qmk/xap/common.py
Executable file
140
lib/python/qmk/xap/common.py
Executable file
@ -0,0 +1,140 @@
|
||||
"""This script handles the XAP protocol data files.
|
||||
"""
|
||||
import hjson
|
||||
import jsonschema
|
||||
from pathlib import Path
|
||||
from typing import OrderedDict
|
||||
from jinja2 import Environment, FileSystemLoader, select_autoescape
|
||||
|
||||
import qmk.constants
|
||||
from qmk.git import git_get_version
|
||||
from qmk.lighting import load_lighting_spec
|
||||
from qmk.json_schema import validate, merge_ordered_dicts
|
||||
from qmk.makefile import parse_rules_mk_file
|
||||
from qmk.decorators import lru_cache
|
||||
from qmk.keymap import locate_keymap
|
||||
from qmk.path import keyboard
|
||||
from qmk.xap.jinja2_filters import attach_filters
|
||||
|
||||
USERSPACE_DIR = Path('users')
|
||||
XAP_SPEC = 'xap.hjson'
|
||||
|
||||
|
||||
def _get_jinja2_env(data_templates_xap_subdir: str):
|
||||
templates_dir = qmk.constants.QMK_FIRMWARE / 'data/templates/xap' / data_templates_xap_subdir
|
||||
j2 = Environment(loader=FileSystemLoader(templates_dir), autoescape=select_autoescape(), lstrip_blocks=True, trim_blocks=True)
|
||||
return j2
|
||||
|
||||
|
||||
def render_xap_output(data_templates_xap_subdir, file_to_render, defs=None, **kwargs):
|
||||
if defs is None:
|
||||
defs = latest_xap_defs()
|
||||
j2 = _get_jinja2_env(data_templates_xap_subdir)
|
||||
|
||||
attach_filters(j2)
|
||||
|
||||
specs = {}
|
||||
for feature in ['rgblight', 'rgb_matrix', 'led_matrix']:
|
||||
specs[feature] = load_lighting_spec(feature)
|
||||
|
||||
return j2.get_template(file_to_render).render(xap=defs, qmk_version=git_get_version(), xap_str=hjson.dumps(defs), specs=specs, constants=qmk.constants, **kwargs)
|
||||
|
||||
|
||||
def _find_kb_spec(kb):
|
||||
base_path = Path('keyboards')
|
||||
keyboard_parent = keyboard(kb)
|
||||
|
||||
for _ in range(5):
|
||||
if keyboard_parent == base_path:
|
||||
break
|
||||
|
||||
spec = keyboard_parent / XAP_SPEC
|
||||
if spec.exists():
|
||||
return spec
|
||||
|
||||
keyboard_parent = keyboard_parent.parent
|
||||
|
||||
# Just return something we know doesn't exist
|
||||
return keyboard(kb) / XAP_SPEC
|
||||
|
||||
|
||||
def _find_km_spec(kb, km):
|
||||
keymap_dir = locate_keymap(kb, km).parent
|
||||
if not keymap_dir.exists():
|
||||
return None
|
||||
|
||||
# Resolve any potential USER_NAME overrides - default back to keymap name
|
||||
keymap_rules_mk = parse_rules_mk_file(keymap_dir / 'rules.mk')
|
||||
username = keymap_rules_mk.get('USER_NAME', km)
|
||||
|
||||
keymap_spec = keymap_dir / XAP_SPEC
|
||||
userspace_spec = USERSPACE_DIR / username / XAP_SPEC
|
||||
|
||||
# In the case of both userspace and keymap - keymap wins
|
||||
return keymap_spec if keymap_spec.exists() else userspace_spec
|
||||
|
||||
|
||||
def get_xap_definition_files():
|
||||
"""Get the sorted list of XAP definition files, from <QMK>/data/xap.
|
||||
"""
|
||||
xap_defs = qmk.constants.QMK_FIRMWARE / "data" / "xap"
|
||||
return list(sorted(xap_defs.glob('**/xap_*.hjson')))
|
||||
|
||||
|
||||
def update_xap_definitions(original, new):
|
||||
"""Creates a new XAP definition object based on an original and the new supplied object.
|
||||
|
||||
Both inputs must be of type OrderedDict.
|
||||
Later input dicts overrides earlier dicts for plain values.
|
||||
Arrays will be appended. If the first entry of an array is "!reset!", the contents of the array will be cleared and replaced with RHS.
|
||||
Dictionaries will be recursively merged. If any entry is "!reset!", the contents of the dictionary will be cleared and replaced with RHS.
|
||||
"""
|
||||
if original is None:
|
||||
original = OrderedDict()
|
||||
return merge_ordered_dicts([original, new])
|
||||
|
||||
|
||||
@lru_cache(timeout=5)
|
||||
def get_xap_defs(version):
|
||||
"""Gets the required version of the XAP definitions.
|
||||
"""
|
||||
files = get_xap_definition_files()
|
||||
|
||||
# Slice off anything newer than specified version
|
||||
if version != 'latest':
|
||||
index = [idx for idx, s in enumerate(files) if version in str(s)][0]
|
||||
files = files[:(index + 1)]
|
||||
|
||||
definitions = [hjson.load(file.open(encoding='utf-8')) for file in files]
|
||||
return merge_ordered_dicts(definitions)
|
||||
|
||||
|
||||
def latest_xap_defs():
|
||||
"""Gets the latest version of the XAP definitions.
|
||||
"""
|
||||
return get_xap_defs('latest')
|
||||
|
||||
|
||||
def merge_xap_defs(kb, km):
|
||||
"""Gets the latest version of the XAP definitions and merges in optional keyboard/keymap specs
|
||||
"""
|
||||
definitions = [get_xap_defs('latest')]
|
||||
|
||||
kb_xap = _find_kb_spec(kb)
|
||||
if kb_xap.exists():
|
||||
definitions.append({'routes': {'0x02': hjson.load(kb_xap.open(encoding='utf-8'))}})
|
||||
|
||||
km_xap = _find_km_spec(kb, km)
|
||||
if km_xap.exists():
|
||||
definitions.append({'routes': {'0x03': hjson.load(km_xap.open(encoding='utf-8'))}})
|
||||
|
||||
defs = merge_ordered_dicts(definitions)
|
||||
|
||||
try:
|
||||
validate(defs, 'qmk.xap.v1')
|
||||
|
||||
except jsonschema.ValidationError as e:
|
||||
print(f'Invalid XAP spec: {e.message}')
|
||||
exit(1)
|
||||
|
||||
return defs
|
0
lib/python/qmk/xap/gen_client_js/__init__.py
Normal file
0
lib/python/qmk/xap/gen_client_js/__init__.py
Normal file
0
lib/python/qmk/xap/gen_firmware/__init__.py
Normal file
0
lib/python/qmk/xap/gen_firmware/__init__.py
Normal file
72
lib/python/qmk/xap/gen_firmware/blob_generator.py
Normal file
72
lib/python/qmk/xap/gen_firmware/blob_generator.py
Normal file
@ -0,0 +1,72 @@
|
||||
"""This script generates the XAP info.json payload header to be compiled into QMK.
|
||||
"""
|
||||
import json
|
||||
import gzip
|
||||
from pathlib import Path
|
||||
|
||||
from qmk.info import keymap_json
|
||||
from qmk.commands import get_chunks, dump_lines
|
||||
from qmk.json_schema import deep_update, json_load
|
||||
from qmk.json_encoders import InfoJSONEncoder
|
||||
from qmk.constants import GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE
|
||||
|
||||
|
||||
def _build_info(keyboard, keymap):
|
||||
"""Build the xap version of info.json
|
||||
"""
|
||||
defaults_json = json_load(Path('data/mappings/xap_defaults.json'))
|
||||
km_info_json = keymap_json(keyboard, keymap)
|
||||
|
||||
info_json = {}
|
||||
deep_update(info_json, defaults_json)
|
||||
deep_update(info_json, km_info_json)
|
||||
|
||||
# TODO: Munge to XAP requirements
|
||||
info_json.pop('config_h_features', None)
|
||||
info_json.pop('keymaps', None)
|
||||
info_json.pop('parse_errors', None)
|
||||
info_json.pop('parse_warnings', None)
|
||||
info_json.get('usb', {}).pop('device_ver', None)
|
||||
for layout in info_json.get('layouts', {}).values():
|
||||
layout.pop('filename', None)
|
||||
layout.pop('c_macro', None)
|
||||
for item in layout.get('layout', []):
|
||||
item.pop('label', None)
|
||||
|
||||
return info_json
|
||||
|
||||
|
||||
def generate_blob(output_file, keyboard, keymap):
|
||||
"""Generate XAP payload
|
||||
"""
|
||||
info_json = _build_info(keyboard, keymap)
|
||||
|
||||
# Minify
|
||||
str_data = json.dumps(info_json, separators=(',', ':'))
|
||||
|
||||
# Compress
|
||||
compressed = gzip.compress(str_data.encode("utf-8"), compresslevel=9)
|
||||
|
||||
# split into lines to match xxd output
|
||||
hex_array = ["0x{:02X}".format(b) for b in compressed]
|
||||
data_len = len(hex_array)
|
||||
|
||||
data = ""
|
||||
for chunk in get_chunks(hex_array, 12):
|
||||
data += f' {", ".join(chunk)},\n'
|
||||
|
||||
lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '#pragma once', '']
|
||||
|
||||
lines.append('#if 0')
|
||||
lines.append('// Blob contains a minified+gzipped version of the following:')
|
||||
lines.append(json.dumps(info_json, cls=InfoJSONEncoder))
|
||||
lines.append('#endif')
|
||||
lines.append('')
|
||||
|
||||
# Gen output file
|
||||
lines.append('static const unsigned char config_blob_gz[] PROGMEM = {')
|
||||
lines.append(data)
|
||||
lines.append('};')
|
||||
lines.append(f'#define CONFIG_BLOB_GZ_LEN {data_len}')
|
||||
|
||||
dump_lines(output_file, lines)
|
77
lib/python/qmk/xap/jinja2_filters.py
Normal file
77
lib/python/qmk/xap/jinja2_filters.py
Normal file
@ -0,0 +1,77 @@
|
||||
"""This script enables attachment of XAP-specific filters to Jinja2
|
||||
"""
|
||||
import re
|
||||
from fnvhash import fnv1a_32
|
||||
from jinja2 import Environment
|
||||
|
||||
from qmk.casing import to_snake
|
||||
|
||||
TRIPLET_PATTERN = re.compile(r'^(\d+)\.(\d+)\.(\d+)')
|
||||
TYPE_ARRAY_PATTERN = re.compile(r'^([^\[]+)\[([^[\]]+)\]$')
|
||||
|
||||
|
||||
def _fnv1a_32(s: str):
|
||||
res = fnv1a_32(bytes(s, 'utf-8'))
|
||||
return f'0x{res:08X}'
|
||||
|
||||
|
||||
def _xap_type_to_c_before(xt: str):
|
||||
m = TYPE_ARRAY_PATTERN.match(xt)
|
||||
if m:
|
||||
return _xap_type_to_c(m.group(1))
|
||||
if xt == 'u8':
|
||||
return 'uint8_t'
|
||||
elif xt == 'u16':
|
||||
return 'uint16_t'
|
||||
elif xt == 'u32':
|
||||
return 'uint32_t'
|
||||
elif xt == 'u64':
|
||||
return 'uint64_t'
|
||||
elif xt == 'bool':
|
||||
return 'uint8_t'
|
||||
elif xt == 'string':
|
||||
return 'const char*'
|
||||
elif xt == 'token':
|
||||
return 'xap_token_t'
|
||||
elif xt == 'response_flags':
|
||||
return 'xap_response_flags_t'
|
||||
raise TypeError(f'Unknown XAP type: {xt}')
|
||||
|
||||
|
||||
def _xap_type_to_c_after(xt: str):
|
||||
m = TYPE_ARRAY_PATTERN.match(xt)
|
||||
if m:
|
||||
try:
|
||||
extent = int(m.group(2))
|
||||
return f'[{extent}]'
|
||||
except ValueError:
|
||||
return '[]'
|
||||
return ''
|
||||
|
||||
|
||||
def _xap_type_to_c(xt: str, name: str = None):
|
||||
if name is not None:
|
||||
name = re.sub(' ', '_', name)
|
||||
return f'{(_xap_type_to_c_before(xt))} {name}{(_xap_type_to_c_after(xt))}'
|
||||
return f'{(_xap_type_to_c_before(xt))}{(_xap_type_to_c_after(xt))}'
|
||||
|
||||
|
||||
def _triplet_to_bcd(value: str):
|
||||
m = TRIPLET_PATTERN.match(value)
|
||||
if not m:
|
||||
return '0'
|
||||
return f'0x{int(m.group(1)):02d}{int(m.group(2)):02d}{int(m.group(3)):04d}'
|
||||
|
||||
|
||||
def _newline_to_br(value: str):
|
||||
return value.replace('\n', '<br>')
|
||||
|
||||
|
||||
def attach_filters(j2: Environment):
|
||||
j2.filters['to_snake'] = to_snake
|
||||
j2.filters['newline_to_br'] = _newline_to_br
|
||||
j2.filters['triplet_to_bcd'] = _triplet_to_bcd
|
||||
j2.filters['fnv1a_32'] = _fnv1a_32
|
||||
j2.filters['type_to_c'] = _xap_type_to_c
|
||||
j2.filters['type_to_c_before'] = _xap_type_to_c_before
|
||||
j2.filters['type_to_c_after'] = _xap_type_to_c_after
|
6
lib/python/xap_client/__init__.py
Normal file
6
lib/python/xap_client/__init__.py
Normal file
@ -0,0 +1,6 @@
|
||||
# Copyright 2022 QMK
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
from .types import * # noqa: F403
|
||||
from .client import * # noqa: F403
|
||||
from .routes import * # noqa: F403
|
||||
from .constants import * # noqa: F403
|
39
lib/python/xap_client/client.py
Normal file
39
lib/python/xap_client/client.py
Normal file
@ -0,0 +1,39 @@
|
||||
# Copyright 2022 QMK
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
from typing import List
|
||||
|
||||
|
||||
class XAPClient:
|
||||
"""XAP device discovery
|
||||
"""
|
||||
@staticmethod
|
||||
def devices(search: str = None) -> List[dict]:
|
||||
"""Find compatible XAP devices
|
||||
|
||||
Args:
|
||||
search: optional search string to filter results by
|
||||
"""
|
||||
def _is_xap_usage(x):
|
||||
return x['usage_page'] == 0xFF51 and x['usage'] == 0x0058
|
||||
|
||||
def _is_filtered_device(x):
|
||||
name = '%04x:%04x' % (x['vendor_id'], x['product_id'])
|
||||
return name.lower().startswith(search.lower())
|
||||
|
||||
# lazy import to avoid compile issues
|
||||
import hid
|
||||
|
||||
devices = filter(_is_xap_usage, hid.enumerate())
|
||||
if search:
|
||||
devices = filter(_is_filtered_device, devices)
|
||||
|
||||
return list(devices)
|
||||
|
||||
def connect(self, device: dict):
|
||||
"""Connect to a given XAP device
|
||||
Args:
|
||||
device: item from a previous `XAPClient.devices()` call
|
||||
"""
|
||||
from .device import XAPDevice
|
||||
|
||||
return XAPDevice(device)
|
125
lib/python/xap_client/constants.py
Normal file
125
lib/python/xap_client/constants.py
Normal file
@ -0,0 +1,125 @@
|
||||
# Copyright 2023 QMK
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
################################################################################
|
||||
#
|
||||
# 88888888888 888 d8b .d888 d8b 888 d8b
|
||||
# 888 888 Y8P d88P" Y8P 888 Y8P
|
||||
# 888 888 888 888
|
||||
# 888 88888b. 888 .d8888b 888888 888 888 .d88b. 888 .d8888b
|
||||
# 888 888 "88b 888 88K 888 888 888 d8P Y8b 888 88K
|
||||
# 888 888 888 888 "Y8888b. 888 888 888 88888888 888 "Y8888b.
|
||||
# 888 888 888 888 X88 888 888 888 Y8b. 888 X88
|
||||
# 888 888 888 888 88888P' 888 888 888 "Y8888 888 88888P'
|
||||
#
|
||||
# 888 888
|
||||
# 888 888
|
||||
# 888 888
|
||||
# .d88b. .d88b. 88888b. .d88b. 888d888 8888b. 888888 .d88b. .d88888
|
||||
# d88P"88b d8P Y8b 888 "88b d8P Y8b 888P" "88b 888 d8P Y8b d88" 888
|
||||
# 888 888 88888888 888 888 88888888 888 .d888888 888 88888888 888 888
|
||||
# Y88b 888 Y8b. 888 888 Y8b. 888 888 888 Y88b. Y8b. Y88b 888
|
||||
# "Y88888 "Y8888 888 888 "Y8888 888 "Y888888 "Y888 "Y8888 "Y88888
|
||||
# 888
|
||||
# Y8b d88P
|
||||
# "Y88P"
|
||||
#
|
||||
################################################################################
|
||||
|
||||
from enum import IntEnum
|
||||
|
||||
|
||||
# version: 0.0.1
|
||||
class RgblightModes(IntEnum):
|
||||
STATIC_LIGHT = 0x00
|
||||
BREATHING = 0x01
|
||||
BREATHING_2 = 0x02
|
||||
BREATHING_3 = 0x03
|
||||
BREATHING_4 = 0x04
|
||||
RAINBOW_MOOD = 0x05
|
||||
RAINBOW_MOOD_2 = 0x06
|
||||
RAINBOW_MOOD_3 = 0x07
|
||||
RAINBOW_SWIRL = 0x08
|
||||
RAINBOW_SWIRL_2 = 0x09
|
||||
RAINBOW_SWIRL_3 = 0x0A
|
||||
RAINBOW_SWIRL_4 = 0x0B
|
||||
RAINBOW_SWIRL_5 = 0x0C
|
||||
RAINBOW_SWIRL_6 = 0x0D
|
||||
SNAKE = 0x0E
|
||||
SNAKE_2 = 0x0F
|
||||
SNAKE_3 = 0x10
|
||||
SNAKE_4 = 0x11
|
||||
SNAKE_5 = 0x12
|
||||
SNAKE_6 = 0x13
|
||||
KNIGHT = 0x14
|
||||
KNIGHT_2 = 0x15
|
||||
KNIGHT_3 = 0x16
|
||||
CHRISTMAS = 0x17
|
||||
STATIC_GRADIENT = 0x18
|
||||
STATIC_GRADIENT_2 = 0x19
|
||||
STATIC_GRADIENT_3 = 0x1A
|
||||
STATIC_GRADIENT_4 = 0x1B
|
||||
STATIC_GRADIENT_5 = 0x1C
|
||||
STATIC_GRADIENT_6 = 0x1D
|
||||
STATIC_GRADIENT_7 = 0x1E
|
||||
STATIC_GRADIENT_8 = 0x1F
|
||||
STATIC_GRADIENT_9 = 0x20
|
||||
STATIC_GRADIENT_10 = 0x21
|
||||
RGB_TEST = 0x22
|
||||
ALTERNATING = 0x23
|
||||
TWINKLE = 0x24
|
||||
TWINKLE_2 = 0x25
|
||||
TWINKLE_3 = 0x26
|
||||
TWINKLE_4 = 0x27
|
||||
TWINKLE_5 = 0x28
|
||||
TWINKLE_6 = 0x29
|
||||
|
||||
|
||||
# version: 0.0.1
|
||||
class RgbMatrixModes(IntEnum):
|
||||
SOLID_COLOR = 0x00
|
||||
ALPHAS_MODS = 0x01
|
||||
GRADIENT_UP_DOWN = 0x02
|
||||
GRADIENT_LEFT_RIGHT = 0x03
|
||||
BREATHING = 0x04
|
||||
BAND_SAT = 0x05
|
||||
BAND_VAL = 0x06
|
||||
BAND_PINWHEEL_SAT = 0x07
|
||||
BAND_PINWHEEL_VAL = 0x08
|
||||
BAND_SPIRAL_SAT = 0x09
|
||||
BAND_SPIRAL_VAL = 0x0A
|
||||
CYCLE_ALL = 0x0B
|
||||
CYCLE_LEFT_RIGHT = 0x0C
|
||||
CYCLE_UP_DOWN = 0x0D
|
||||
CYCLE_OUT_IN = 0x0E
|
||||
CYCLE_OUT_IN_DUAL = 0x0F
|
||||
RAINBOW_MOVING_CHEVRON = 0x10
|
||||
CYCLE_PINWHEEL = 0x11
|
||||
CYCLE_SPIRAL = 0x12
|
||||
DUAL_BEACON = 0x13
|
||||
RAINBOW_BEACON = 0x14
|
||||
RAINBOW_PINWHEELS = 0x15
|
||||
RAINDROPS = 0x16
|
||||
JELLYBEAN_RAINDROPS = 0x17
|
||||
HUE_BREATHING = 0x18
|
||||
HUE_PENDULUM = 0x19
|
||||
HUE_WAVE = 0x1A
|
||||
PIXEL_FRACTAL = 0x1B
|
||||
PIXEL_FLOW = 0x1C
|
||||
PIXEL_RAIN = 0x1D
|
||||
TYPING_HEATMAP = 0x1E
|
||||
DIGITAL_RAIN = 0x1F
|
||||
SOLID_REACTIVE_SIMPLE = 0x20
|
||||
SOLID_REACTIVE = 0x21
|
||||
SOLID_REACTIVE_WIDE = 0x22
|
||||
SOLID_REACTIVE_MULTIWIDE = 0x23
|
||||
SOLID_REACTIVE_CROSS = 0x24
|
||||
SOLID_REACTIVE_MULTICROSS = 0x25
|
||||
SOLID_REACTIVE_NEXUS = 0x26
|
||||
SOLID_REACTIVE_MULTINEXUS = 0x27
|
||||
SPLASH = 0x28
|
||||
MULTISPLASH = 0x29
|
||||
SOLID_SPLASH = 0x2A
|
||||
SOLID_MULTISPLASH = 0x2B
|
||||
|
||||
# noqa: W391
|
238
lib/python/xap_client/device.py
Normal file
238
lib/python/xap_client/device.py
Normal file
@ -0,0 +1,238 @@
|
||||
# Copyright 2022 QMK
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
import json
|
||||
import time
|
||||
import gzip
|
||||
import random
|
||||
import threading
|
||||
import functools
|
||||
from typing import Optional
|
||||
from struct import pack, unpack
|
||||
from platform import platform
|
||||
|
||||
from .types import XAPSecureStatus, XAPFlags, XAPRequest, XAPResponse, XAPBroadcast
|
||||
from .routes import XAPRoutes, XAPRouteError
|
||||
|
||||
|
||||
def _u32_to_bcd(val: bytes) -> str: # noqa: N802
|
||||
"""Create BCD string
|
||||
"""
|
||||
tmp = "{:08x}".format(val)
|
||||
major = int(tmp[0:2])
|
||||
minor = int(tmp[2:4])
|
||||
patch = int(tmp[4:8])
|
||||
|
||||
return f'{major}.{minor}.{patch}'
|
||||
|
||||
|
||||
def _gen_token() -> bytes:
|
||||
"""Generate XAP token - cannot start with 00xx or 'reserved' (FFFE|FFFF)
|
||||
"""
|
||||
token = random.randrange(0x0100, 0xFFFD)
|
||||
|
||||
# swap endianness
|
||||
return unpack('<H', pack('>H', token))[0]
|
||||
|
||||
|
||||
class XAPDeviceBase:
|
||||
"""Raw XAP interactions
|
||||
"""
|
||||
def __init__(self, dev: dict, timeout: int = 1.0):
|
||||
"""Constructor opens hid device and starts dependent services
|
||||
"""
|
||||
self.responses = {}
|
||||
self.timeout = timeout
|
||||
self.running = True
|
||||
|
||||
# lazy import to avoid compile issues
|
||||
import hid
|
||||
|
||||
self.dev = hid.Device(path=dev['path'])
|
||||
|
||||
self.bg = threading.Thread(target=self._read_loop, daemon=True)
|
||||
self.bg.start()
|
||||
|
||||
def close(self):
|
||||
"""Close device and stop dependent services
|
||||
"""
|
||||
self.running = False
|
||||
time.sleep(1)
|
||||
self.dev.close()
|
||||
|
||||
def _read_loop(self):
|
||||
"""Background thread to signal waiting transactions
|
||||
"""
|
||||
while self.running:
|
||||
data = self.dev.read(XAPResponse.fmt.size, 100)
|
||||
if data:
|
||||
r = XAPResponse.from_bytes(data)
|
||||
event = self.responses.get(r.token)
|
||||
if event:
|
||||
event._ret = data
|
||||
event.set()
|
||||
|
||||
def transaction(self, *args) -> Optional[bytes]:
|
||||
"""Request/Receive Helper
|
||||
"""
|
||||
# convert args to array of bytes
|
||||
data = bytes()
|
||||
for arg in args:
|
||||
if isinstance(arg, (bytes, bytearray)):
|
||||
data += arg
|
||||
if isinstance(arg, int): # TODO: remove terrible assumption of u16
|
||||
data += arg.to_bytes(2, byteorder='little')
|
||||
|
||||
token = _gen_token()
|
||||
|
||||
buffer = XAPRequest(token, len(data), data).to_bytes()
|
||||
|
||||
event = threading.Event()
|
||||
self.responses[token] = event
|
||||
|
||||
# prepend 0 on windows because reasons...
|
||||
if 'windows' in platform().lower():
|
||||
buffer = b'\x00' + buffer
|
||||
self.dev.write(buffer)
|
||||
|
||||
event.wait(timeout=self.timeout)
|
||||
self.responses.pop(token, None)
|
||||
if not hasattr(event, '_ret'):
|
||||
return None
|
||||
|
||||
r = XAPResponse.from_bytes(event._ret)
|
||||
if r.flags & XAPFlags.SUCCESS == 0:
|
||||
return None
|
||||
|
||||
return r.data[:r.length]
|
||||
|
||||
def listen(self) -> dict:
|
||||
"""Receive a single 'broadcast' message
|
||||
"""
|
||||
token = 0xFFFF
|
||||
event = threading.Event()
|
||||
self.responses[token] = event
|
||||
|
||||
# emulate a blocking read while allowing `ctrl+c` on windows
|
||||
while not hasattr(event, '_ret'):
|
||||
event.wait(timeout=0.25)
|
||||
|
||||
r = XAPBroadcast.from_bytes(event._ret)
|
||||
return (r.event, r.data[:r.length])
|
||||
|
||||
|
||||
class XAPDevice(XAPDeviceBase):
|
||||
"""XAP device interaction
|
||||
"""
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, exc_traceback):
|
||||
self.close()
|
||||
|
||||
def _query_device_info(self) -> dict:
|
||||
"""Helper to reconstruct info.json from requested chunks
|
||||
"""
|
||||
datalen = self.int_transaction(XAPRoutes.QMK_CONFIG_BLOB_LEN)
|
||||
if not datalen:
|
||||
return {}
|
||||
|
||||
data = []
|
||||
offset = 0
|
||||
while offset < datalen:
|
||||
chunk = self.transaction(XAPRoutes.QMK_CONFIG_BLOB_CHUNK, offset)
|
||||
data += chunk
|
||||
offset += len(chunk)
|
||||
str_data = gzip.decompress(bytearray(data[:datalen]))
|
||||
return json.loads(str_data)
|
||||
|
||||
def _ensure_route(self, route: bytes):
|
||||
"""Check a route can be accessed
|
||||
|
||||
Raises:
|
||||
XAPRouteError: Access to invalid route attempted
|
||||
"""
|
||||
# TODO: Remove assumption that capability is always xx01
|
||||
(remain, sub, rt) = (route[:-2], route[-2], route[-1])
|
||||
cap = remain + bytes([sub, 1])
|
||||
|
||||
# recurse for nested routes
|
||||
if remain:
|
||||
self._ensure_route(remain + bytes([sub]))
|
||||
|
||||
if self.subsystems() & (1 << sub) == 0:
|
||||
raise XAPRouteError("subsystem not available")
|
||||
if self.capability(cap) & (1 << rt) == 0:
|
||||
raise XAPRouteError("route not available")
|
||||
|
||||
def transaction(self, route: bytes, *args):
|
||||
"""Request/Receive to XAP device
|
||||
|
||||
Raises:
|
||||
XAPRouteError: Access to invalid route attempted
|
||||
"""
|
||||
self._ensure_route(route)
|
||||
|
||||
return super().transaction(route, *args)
|
||||
|
||||
def int_transaction(self, route: bytes, *args):
|
||||
"""transaction with int parsing
|
||||
"""
|
||||
return int.from_bytes(self.transaction(route, *args) or bytes(0), 'little')
|
||||
|
||||
@functools.lru_cache
|
||||
def capability(self, route: bytes):
|
||||
# use parent transaction as we want to ignore capability checks
|
||||
return int.from_bytes(super().transaction(route) or bytes(0), 'little')
|
||||
|
||||
@functools.lru_cache
|
||||
def subsystems(self):
|
||||
# use parent transaction as we want to ignore capability checks
|
||||
return int.from_bytes(super().transaction(XAPRoutes.XAP_SUBSYSTEM_QUERY) or bytes(0), 'little')
|
||||
|
||||
@functools.lru_cache
|
||||
def version(self) -> dict:
|
||||
"""Query version data from device
|
||||
"""
|
||||
xap = self.int_transaction(XAPRoutes.XAP_VERSION_QUERY)
|
||||
qmk = self.int_transaction(XAPRoutes.QMK_VERSION_QUERY)
|
||||
return {'xap': _u32_to_bcd(xap), 'qmk': _u32_to_bcd(qmk)}
|
||||
|
||||
@functools.lru_cache
|
||||
def info(self) -> dict:
|
||||
"""Query config data from device
|
||||
"""
|
||||
data = self._query_device_info()
|
||||
data['_id'] = self.transaction(XAPRoutes.QMK_HARDWARE_ID)
|
||||
data['_version'] = self.version()
|
||||
return data
|
||||
|
||||
def status(self) -> dict:
|
||||
"""Query current device state
|
||||
"""
|
||||
lock = self.int_transaction(XAPRoutes.XAP_SECURE_STATUS)
|
||||
|
||||
data = {}
|
||||
data['lock'] = XAPSecureStatus(lock).name
|
||||
return data
|
||||
|
||||
def unlock(self):
|
||||
"""Initiate unlock procedure
|
||||
"""
|
||||
self.transaction(XAPRoutes.XAP_SECURE_UNLOCK)
|
||||
|
||||
def lock(self):
|
||||
"""Lock device
|
||||
"""
|
||||
self.transaction(XAPRoutes.XAP_SECURE_LOCK)
|
||||
|
||||
def reset(self):
|
||||
"""Request device reboot to bootloader - Requires previous unlock
|
||||
"""
|
||||
status = self.int_transaction(XAPRoutes.QMK_BOOTLOADER_JUMP)
|
||||
return status == 1
|
||||
|
||||
def reinit(self):
|
||||
"""Request device reset EEPROM - Requires previous unlock
|
||||
"""
|
||||
status = self.int_transaction(XAPRoutes.QMK_EEPROM_RESET)
|
||||
return status == 1
|
11
lib/python/xap_client/pyproject.toml
Normal file
11
lib/python/xap_client/pyproject.toml
Normal file
@ -0,0 +1,11 @@
|
||||
[project]
|
||||
name = "xap_client"
|
||||
description = "XAP Client"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"hid ~= 1.0.5",
|
||||
]
|
||||
|
||||
[tool.setuptools]
|
||||
packages = ['xap_client']
|
||||
package-dir = { 'xap_client' = '.' }
|
18
lib/python/xap_client/readme.md
Normal file
18
lib/python/xap_client/readme.md
Normal file
@ -0,0 +1,18 @@
|
||||
# XAP python3 bindings
|
||||
|
||||
## Example
|
||||
```python
|
||||
from xap_client import XAPClient
|
||||
|
||||
# List Available Devices
|
||||
devices = XAPClient.devices()
|
||||
selected = devices[0]
|
||||
|
||||
# Connect then run commands
|
||||
with XAPClient().connect(selected) as dev:
|
||||
print(dev.version())
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
TODO
|
88
lib/python/xap_client/routes.py
Normal file
88
lib/python/xap_client/routes.py
Normal file
@ -0,0 +1,88 @@
|
||||
# Copyright 2023 QMK
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
################################################################################
|
||||
#
|
||||
# 88888888888 888 d8b .d888 d8b 888 d8b
|
||||
# 888 888 Y8P d88P" Y8P 888 Y8P
|
||||
# 888 888 888 888
|
||||
# 888 88888b. 888 .d8888b 888888 888 888 .d88b. 888 .d8888b
|
||||
# 888 888 "88b 888 88K 888 888 888 d8P Y8b 888 88K
|
||||
# 888 888 888 888 "Y8888b. 888 888 888 88888888 888 "Y8888b.
|
||||
# 888 888 888 888 X88 888 888 888 Y8b. 888 X88
|
||||
# 888 888 888 888 88888P' 888 888 888 "Y8888 888 88888P'
|
||||
#
|
||||
# 888 888
|
||||
# 888 888
|
||||
# 888 888
|
||||
# .d88b. .d88b. 88888b. .d88b. 888d888 8888b. 888888 .d88b. .d88888
|
||||
# d88P"88b d8P Y8b 888 "88b d8P Y8b 888P" "88b 888 d8P Y8b d88" 888
|
||||
# 888 888 88888888 888 888 88888888 888 .d888888 888 88888888 888 888
|
||||
# Y88b 888 Y8b. 888 888 Y8b. 888 888 888 Y88b. Y8b. Y88b 888
|
||||
# "Y88888 "Y8888 888 888 "Y8888 888 "Y888888 "Y888 "Y8888 "Y88888
|
||||
# 888
|
||||
# Y8b d88P
|
||||
# "Y88P"
|
||||
#
|
||||
################################################################################
|
||||
|
||||
class XAPRouteError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class XAPRoutes():
|
||||
# XAP
|
||||
XAP_VERSION_QUERY = b'\x00\x00'
|
||||
XAP_CAPABILITIES_QUERY = b'\x00\x01'
|
||||
XAP_SUBSYSTEM_QUERY = b'\x00\x02'
|
||||
XAP_SECURE_STATUS = b'\x00\x03'
|
||||
XAP_SECURE_UNLOCK = b'\x00\x04'
|
||||
XAP_SECURE_LOCK = b'\x00\x05'
|
||||
# QMK
|
||||
QMK_VERSION_QUERY = b'\x01\x00'
|
||||
QMK_CAPABILITIES_QUERY = b'\x01\x01'
|
||||
QMK_BOARD_IDENTIFIERS = b'\x01\x02'
|
||||
QMK_BOARD_MANUFACTURER = b'\x01\x03'
|
||||
QMK_PRODUCT_NAME = b'\x01\x04'
|
||||
QMK_CONFIG_BLOB_LEN = b'\x01\x05'
|
||||
QMK_CONFIG_BLOB_CHUNK = b'\x01\x06'
|
||||
QMK_BOOTLOADER_JUMP = b'\x01\x07'
|
||||
QMK_HARDWARE_ID = b'\x01\x08'
|
||||
QMK_EEPROM_RESET = b'\x01\x09'
|
||||
# KEYMAP
|
||||
KEYMAP_CAPABILITIES_QUERY = b'\x04\x01'
|
||||
KEYMAP_GET_LAYER_COUNT = b'\x04\x02'
|
||||
KEYMAP_GET_KEYMAP_KEYCODE = b'\x04\x03'
|
||||
KEYMAP_GET_ENCODER_KEYCODE = b'\x04\x04'
|
||||
# REMAPPING
|
||||
REMAPPING_CAPABILITIES_QUERY = b'\x05\x01'
|
||||
REMAPPING_GET_DYNAMIC_LAYER_COUNT = b'\x05\x02'
|
||||
REMAPPING_SET_KEYMAP_KEYCODE = b'\x05\x03'
|
||||
REMAPPING_SET_ENCODER_KEYCODE = b'\x05\x04'
|
||||
# LIGHTING
|
||||
LIGHTING_CAPABILITIES_QUERY = b'\x06\x01'
|
||||
LIGHTING_BACKLIGHT = b'\x06\x02'
|
||||
LIGHTING_BACKLIGHT_CAPABILITIES_QUERY = b'\x06\x02\x01'
|
||||
LIGHTING_BACKLIGHT_GET_ENABLED_EFFECTS = b'\x06\x02\x02'
|
||||
LIGHTING_BACKLIGHT_GET_CONFIG = b'\x06\x02\x03'
|
||||
LIGHTING_BACKLIGHT_SET_CONFIG = b'\x06\x02\x04'
|
||||
LIGHTING_BACKLIGHT_SAVE_CONFIG = b'\x06\x02\x05'
|
||||
LIGHTING_RGBLIGHT = b'\x06\x03'
|
||||
LIGHTING_RGBLIGHT_CAPABILITIES_QUERY = b'\x06\x03\x01'
|
||||
LIGHTING_RGBLIGHT_GET_ENABLED_EFFECTS = b'\x06\x03\x02'
|
||||
LIGHTING_RGBLIGHT_GET_CONFIG = b'\x06\x03\x03'
|
||||
LIGHTING_RGBLIGHT_SET_CONFIG = b'\x06\x03\x04'
|
||||
LIGHTING_RGBLIGHT_SAVE_CONFIG = b'\x06\x03\x05'
|
||||
LIGHTING_RGB_MATRIX = b'\x06\x04'
|
||||
LIGHTING_RGB_MATRIX_CAPABILITIES_QUERY = b'\x06\x04\x01'
|
||||
LIGHTING_RGB_MATRIX_GET_ENABLED_EFFECTS = b'\x06\x04\x02'
|
||||
LIGHTING_RGB_MATRIX_GET_CONFIG = b'\x06\x04\x03'
|
||||
LIGHTING_RGB_MATRIX_SET_CONFIG = b'\x06\x04\x04'
|
||||
LIGHTING_RGB_MATRIX_SAVE_CONFIG = b'\x06\x04\x05'
|
||||
# AUDIO
|
||||
AUDIO_CAPABILITIES_QUERY = b'\x07\x01'
|
||||
AUDIO_GET_CONFIG = b'\x07\x03'
|
||||
AUDIO_SET_CONFIG = b'\x07\x04'
|
||||
AUDIO_SAVE_CONFIG = b'\x07\x05'
|
||||
|
||||
# noqa: W391
|
151
lib/python/xap_client/types.py
Normal file
151
lib/python/xap_client/types.py
Normal file
@ -0,0 +1,151 @@
|
||||
# Copyright 2023 QMK
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
################################################################################
|
||||
#
|
||||
# 88888888888 888 d8b .d888 d8b 888 d8b
|
||||
# 888 888 Y8P d88P" Y8P 888 Y8P
|
||||
# 888 888 888 888
|
||||
# 888 88888b. 888 .d8888b 888888 888 888 .d88b. 888 .d8888b
|
||||
# 888 888 "88b 888 88K 888 888 888 d8P Y8b 888 88K
|
||||
# 888 888 888 888 "Y8888b. 888 888 888 88888888 888 "Y8888b.
|
||||
# 888 888 888 888 X88 888 888 888 Y8b. 888 X88
|
||||
# 888 888 888 888 88888P' 888 888 888 "Y8888 888 88888P'
|
||||
#
|
||||
# 888 888
|
||||
# 888 888
|
||||
# 888 888
|
||||
# .d88b. .d88b. 88888b. .d88b. 888d888 8888b. 888888 .d88b. .d88888
|
||||
# d88P"88b d8P Y8b 888 "88b d8P Y8b 888P" "88b 888 d8P Y8b d88" 888
|
||||
# 888 888 88888888 888 888 88888888 888 .d888888 888 88888888 888 888
|
||||
# Y88b 888 Y8b. 888 888 Y8b. 888 888 888 Y88b. Y8b. Y88b 888
|
||||
# "Y88888 "Y8888 888 888 "Y8888 888 "Y888888 "Y888 "Y8888 "Y88888
|
||||
# 888
|
||||
# Y8b d88P
|
||||
# "Y88P"
|
||||
#
|
||||
################################################################################
|
||||
|
||||
from collections import namedtuple
|
||||
from enum import IntFlag, IntEnum
|
||||
from struct import Struct
|
||||
|
||||
|
||||
class XAPRequest(namedtuple('XAPRequest', 'token length data')):
|
||||
fmt = Struct('<HB61s')
|
||||
|
||||
def __new__(cls, *args):
|
||||
return super().__new__(cls, *args)
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls, data):
|
||||
return cls._make(cls.fmt.unpack(data))
|
||||
|
||||
def to_bytes(self):
|
||||
return self.fmt.pack(*list(self))
|
||||
|
||||
|
||||
class XAPResponse(namedtuple('XAPResponse', 'token flags length data')):
|
||||
fmt = Struct('<HBB60s')
|
||||
|
||||
def __new__(cls, *args):
|
||||
return super().__new__(cls, *args)
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls, data):
|
||||
return cls._make(cls.fmt.unpack(data))
|
||||
|
||||
def to_bytes(self):
|
||||
return self.fmt.pack(*list(self))
|
||||
|
||||
|
||||
class XAPBroadcast(namedtuple('XAPBroadcast', 'token event length data')):
|
||||
fmt = Struct('<HBB60s')
|
||||
|
||||
def __new__(cls, *args):
|
||||
return super().__new__(cls, *args)
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls, data):
|
||||
return cls._make(cls.fmt.unpack(data))
|
||||
|
||||
def to_bytes(self):
|
||||
return self.fmt.pack(*list(self))
|
||||
|
||||
|
||||
class XAPConfigBacklight(namedtuple('XAPConfigBacklight', 'enable mode val')):
|
||||
fmt = Struct('<BBB')
|
||||
|
||||
def __new__(cls, *args):
|
||||
return super().__new__(cls, *args)
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls, data):
|
||||
return cls._make(cls.fmt.unpack(data))
|
||||
|
||||
def to_bytes(self):
|
||||
return self.fmt.pack(*list(self))
|
||||
|
||||
|
||||
class XAPConfigRgblight(namedtuple('XAPConfigRgblight', 'enable mode hue sat val speed')):
|
||||
fmt = Struct('<BBBBBB')
|
||||
|
||||
def __new__(cls, *args):
|
||||
return super().__new__(cls, *args)
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls, data):
|
||||
return cls._make(cls.fmt.unpack(data))
|
||||
|
||||
def to_bytes(self):
|
||||
return self.fmt.pack(*list(self))
|
||||
|
||||
|
||||
class XAPConfigRgbMatrix(namedtuple('XAPConfigRgbMatrix', 'enable mode hue sat val speed flags')):
|
||||
fmt = Struct('<BBBBBBB')
|
||||
|
||||
def __new__(cls, *args):
|
||||
return super().__new__(cls, *args)
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls, data):
|
||||
return cls._make(cls.fmt.unpack(data))
|
||||
|
||||
def to_bytes(self):
|
||||
return self.fmt.pack(*list(self))
|
||||
|
||||
|
||||
# Spec structs
|
||||
# TODO: gen outbound object for board_identifiers
|
||||
# TODO: gen inbound object for get_keymap_keycode
|
||||
# TODO: gen inbound object for get_encoder_keycode
|
||||
# TODO: gen inbound object for set_keymap_keycode
|
||||
# TODO: gen inbound object for set_encoder_keycode
|
||||
# TODO: gen outbound object for get_config
|
||||
# TODO: gen inbound object for set_config
|
||||
# TODO: gen outbound object for get_config
|
||||
# TODO: gen inbound object for set_config
|
||||
# TODO: gen outbound object for get_config
|
||||
# TODO: gen inbound object for set_config
|
||||
# TODO: gen outbound object for get_config
|
||||
# TODO: gen inbound object for set_config
|
||||
|
||||
|
||||
class XAPSecureStatus(IntEnum):
|
||||
LOCKED = 0x00
|
||||
UNLOCKING = 0x01
|
||||
UNLOCKED = 0x02
|
||||
|
||||
|
||||
class XAPEventType(IntEnum):
|
||||
LOG_MESSAGE = 0x00
|
||||
SECURE_STATUS = 0x01
|
||||
KB = 0x02
|
||||
USER = 0x03
|
||||
|
||||
|
||||
class XAPFlags(IntFlag):
|
||||
SUCCESS = 1 << 0
|
||||
SECURE_FAILURE = 1 << 1
|
||||
|
||||
# noqa: W391
|
@ -20,8 +20,13 @@
|
||||
#include "action.h"
|
||||
#include "send_string.h"
|
||||
#include "keycodes.h"
|
||||
#include "nvm_eeconfig.h"
|
||||
#include "nvm_dynamic_keymap.h"
|
||||
|
||||
#ifdef FNV_ENABLE
|
||||
# include "fnv.h"
|
||||
#endif
|
||||
|
||||
#ifdef ENCODER_ENABLE
|
||||
# include "encoder.h"
|
||||
#else
|
||||
@ -54,6 +59,50 @@ void dynamic_keymap_set_encoder(uint8_t layer, uint8_t encoder_id, bool clockwis
|
||||
}
|
||||
#endif // ENCODER_MAP_ENABLE
|
||||
|
||||
static uint32_t dynamic_keymap_compute_hash(void) {
|
||||
#ifdef FNV_ENABLE
|
||||
Fnv32_t hash = FNV1_32A_INIT;
|
||||
|
||||
uint16_t keycode;
|
||||
for (int layer = 0; layer < DYNAMIC_KEYMAP_LAYER_COUNT; layer++) {
|
||||
for (int row = 0; row < MATRIX_ROWS; row++) {
|
||||
for (int column = 0; column < MATRIX_COLS; column++) {
|
||||
keycode = keycode_at_keymap_location_raw(layer, row, column);
|
||||
hash = fnv_32a_buf(&keycode, sizeof(keycode), hash);
|
||||
}
|
||||
}
|
||||
# ifdef ENCODER_MAP_ENABLE
|
||||
for (int encoder = 0; encoder < NUM_ENCODERS; encoder++) {
|
||||
keycode = keycode_at_encodermap_location_raw(layer, encoder, true);
|
||||
hash = fnv_32a_buf(&keycode, sizeof(keycode), hash);
|
||||
|
||||
keycode = keycode_at_encodermap_location_raw(layer, encoder, false);
|
||||
hash = fnv_32a_buf(&keycode, sizeof(keycode), hash);
|
||||
}
|
||||
# endif // ENCODER_MAP_ENABLE
|
||||
}
|
||||
return hash;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
static uint32_t dynamic_keymap_hash(void) {
|
||||
static uint32_t hash = 0;
|
||||
|
||||
static uint8_t s_init = 0;
|
||||
if (!s_init) {
|
||||
s_init = 1;
|
||||
|
||||
hash = dynamic_keymap_compute_hash();
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
bool dynamic_keymap_is_valid(void) {
|
||||
return nvm_eeconfig_read_keymap_hash() == dynamic_keymap_hash();
|
||||
}
|
||||
|
||||
void dynamic_keymap_reset(void) {
|
||||
// Erase the keymaps, if necessary.
|
||||
nvm_dynamic_keymap_erase();
|
||||
@ -72,6 +121,7 @@ void dynamic_keymap_reset(void) {
|
||||
}
|
||||
#endif // ENCODER_MAP_ENABLE
|
||||
}
|
||||
nvm_eeconfig_update_keymap_hash(dynamic_keymap_hash());
|
||||
}
|
||||
|
||||
void dynamic_keymap_get_buffer(uint16_t offset, uint16_t size, uint8_t *data) {
|
||||
|
@ -33,7 +33,10 @@ void dynamic_keymap_set_keycode(uint8_t layer, uint8_t row, uint8_t column,
|
||||
uint16_t dynamic_keymap_get_encoder(uint8_t layer, uint8_t encoder_id, bool clockwise);
|
||||
void dynamic_keymap_set_encoder(uint8_t layer, uint8_t encoder_id, bool clockwise, uint16_t keycode);
|
||||
#endif // ENCODER_MAP_ENABLE
|
||||
|
||||
bool dynamic_keymap_is_valid(void);
|
||||
void dynamic_keymap_reset(void);
|
||||
|
||||
// These get/set the keycodes as stored in the EEPROM buffer
|
||||
// Data is big-endian 16-bit values (the keycodes)
|
||||
// Order is by layer/row/column
|
||||
|
@ -44,7 +44,9 @@ bool via_eeprom_is_valid(void);
|
||||
void via_eeprom_set_valid(bool valid);
|
||||
void eeconfig_init_via(void);
|
||||
#else
|
||||
bool dynamic_keymap_is_valid(void);
|
||||
void dynamic_keymap_reset(void);
|
||||
void dynamic_keymap_macro_reset(void);
|
||||
#endif // VIA_ENABLE
|
||||
|
||||
#ifndef NKRO_DEFAULT_ON
|
||||
@ -155,6 +157,7 @@ void eeconfig_init_quantum(void) {
|
||||
eeconfig_init_via();
|
||||
#elif defined(DYNAMIC_KEYMAP_ENABLE)
|
||||
dynamic_keymap_reset();
|
||||
dynamic_keymap_macro_reset();
|
||||
#endif
|
||||
|
||||
eeconfig_init_kb();
|
||||
@ -183,21 +186,29 @@ void eeconfig_disable(void) {
|
||||
|
||||
bool eeconfig_is_enabled(void) {
|
||||
bool is_eeprom_enabled = nvm_eeconfig_is_enabled();
|
||||
#ifdef VIA_ENABLE
|
||||
#if defined(VIA_ENABLE)
|
||||
if (is_eeprom_enabled) {
|
||||
is_eeprom_enabled = via_eeprom_is_valid();
|
||||
}
|
||||
#endif // VIA_ENABLE
|
||||
#elif defined(DYNAMIC_KEYMAP_ENABLE)
|
||||
if (is_eeprom_enabled) {
|
||||
is_eeprom_enabled = dynamic_keymap_is_valid();
|
||||
}
|
||||
#endif
|
||||
return is_eeprom_enabled;
|
||||
}
|
||||
|
||||
bool eeconfig_is_disabled(void) {
|
||||
bool is_eeprom_disabled = nvm_eeconfig_is_disabled();
|
||||
#ifdef VIA_ENABLE
|
||||
#if defined(VIA_ENABLE)
|
||||
if (!is_eeprom_disabled) {
|
||||
is_eeprom_disabled = !via_eeprom_is_valid();
|
||||
}
|
||||
#endif // VIA_ENABLE
|
||||
#elif defined(DYNAMIC_KEYMAP_ENABLE)
|
||||
if (!is_eeprom_disabled) {
|
||||
is_eeprom_disabled = !dynamic_keymap_is_valid();
|
||||
}
|
||||
#endif
|
||||
return is_eeprom_disabled;
|
||||
}
|
||||
|
||||
|
263
quantum/led_matrix/lighting_map.h
Normal file
263
quantum/led_matrix/lighting_map.h
Normal file
@ -0,0 +1,263 @@
|
||||
// Copyright 2025 QMK
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
/*******************************************************************************
|
||||
88888888888 888 d8b .d888 d8b 888 d8b
|
||||
888 888 Y8P d88P" Y8P 888 Y8P
|
||||
888 888 888 888
|
||||
888 88888b. 888 .d8888b 888888 888 888 .d88b. 888 .d8888b
|
||||
888 888 "88b 888 88K 888 888 888 d8P Y8b 888 88K
|
||||
888 888 888 888 "Y8888b. 888 888 888 88888888 888 "Y8888b.
|
||||
888 888 888 888 X88 888 888 888 Y8b. 888 X88
|
||||
888 888 888 888 88888P' 888 888 888 "Y8888 888 88888P'
|
||||
888 888
|
||||
888 888
|
||||
888 888
|
||||
.d88b. .d88b. 88888b. .d88b. 888d888 8888b. 888888 .d88b. .d88888
|
||||
d88P"88b d8P Y8b 888 "88b d8P Y8b 888P" "88b 888 d8P Y8b d88" 888
|
||||
888 888 88888888 888 888 88888888 888 .d888888 888 88888888 888 888
|
||||
Y88b 888 Y8b. 888 888 Y8b. 888 888 888 Y88b. Y8b. Y88b 888
|
||||
"Y88888 "Y8888 888 888 "Y8888 888 "Y888888 "Y888 "Y8888 "Y88888
|
||||
888
|
||||
Y8b d88P
|
||||
"Y88P"
|
||||
*******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
// clang-format off
|
||||
enum { ENABLED_LED_MATRIX_EFFECTS = 0
|
||||
| (1ULL << 0x00)
|
||||
|
||||
#ifdef ENABLE_LED_MATRIX_ALPHAS_MODS
|
||||
| (1ULL << 0x01)
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_LED_MATRIX_BREATHING
|
||||
| (1ULL << 0x02)
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_LED_MATRIX_BAND
|
||||
| (1ULL << 0x03)
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_LED_MATRIX_BAND_PINWHEEL
|
||||
| (1ULL << 0x04)
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_LED_MATRIX_BAND_SPIRAL
|
||||
| (1ULL << 0x05)
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_LED_MATRIX_CYCLE_LEFT_RIGHT
|
||||
| (1ULL << 0x06)
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_LED_MATRIX_CYCLE_UP_DOWN
|
||||
| (1ULL << 0x07)
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_LED_MATRIX_CYCLE_OUT_IN
|
||||
| (1ULL << 0x08)
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_LED_MATRIX_DUAL_BEACON
|
||||
| (1ULL << 0x09)
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_LED_MATRIX_WAVE_LEFT_RIGHT
|
||||
| (1ULL << 0x0A)
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_LED_MATRIX_WAVE_UP_DOWN
|
||||
| (1ULL << 0x0B)
|
||||
#endif
|
||||
|
||||
#ifdef LED_MATRIX_KEYREACTIVE_ENABLED
|
||||
|
||||
#ifdef ENABLE_LED_MATRIX_SOLID_REACTIVE_SIMPLE
|
||||
| (1ULL << 0x0C)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef LED_MATRIX_KEYREACTIVE_ENABLED
|
||||
|
||||
#ifdef ENABLE_LED_MATRIX_SOLID_REACTIVE_WIDE
|
||||
| (1ULL << 0x0D)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef LED_MATRIX_KEYREACTIVE_ENABLED
|
||||
|
||||
#ifdef ENABLE_LED_MATRIX_SOLID_REACTIVE_MULTIWIDE
|
||||
| (1ULL << 0x0E)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef LED_MATRIX_KEYREACTIVE_ENABLED
|
||||
|
||||
#ifdef ENABLE_LED_MATRIX_SOLID_REACTIVE_CROSS
|
||||
| (1ULL << 0x0F)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef LED_MATRIX_KEYREACTIVE_ENABLED
|
||||
|
||||
#ifdef ENABLE_LED_MATRIX_SOLID_REACTIVE_MULTICROSS
|
||||
| (1ULL << 0x10)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef LED_MATRIX_KEYREACTIVE_ENABLED
|
||||
|
||||
#ifdef ENABLE_LED_MATRIX_SOLID_REACTIVE_NEXUS
|
||||
| (1ULL << 0x11)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef LED_MATRIX_KEYREACTIVE_ENABLED
|
||||
|
||||
#ifdef ENABLE_LED_MATRIX_SOLID_REACTIVE_MULTINEXUS
|
||||
| (1ULL << 0x12)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef LED_MATRIX_KEYREACTIVE_ENABLED
|
||||
|
||||
#ifdef ENABLE_LED_MATRIX_SOLID_SPLASH
|
||||
| (1ULL << 0x13)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef LED_MATRIX_KEYREACTIVE_ENABLED
|
||||
|
||||
#ifdef ENABLE_LED_MATRIX_SOLID_MULTISPLASH
|
||||
| (1ULL << 0x14)
|
||||
#endif
|
||||
#endif
|
||||
};
|
||||
static const uint8_t led_matrix_effect_map[][2] PROGMEM = {
|
||||
{ 0x00, LED_MATRIX_SOLID},
|
||||
|
||||
#ifdef ENABLE_LED_MATRIX_ALPHAS_MODS
|
||||
{ 0x01, LED_MATRIX_ALPHAS_MODS},
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_LED_MATRIX_BREATHING
|
||||
{ 0x02, LED_MATRIX_BREATHING},
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_LED_MATRIX_BAND
|
||||
{ 0x03, LED_MATRIX_BAND},
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_LED_MATRIX_BAND_PINWHEEL
|
||||
{ 0x04, LED_MATRIX_BAND_PINWHEEL},
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_LED_MATRIX_BAND_SPIRAL
|
||||
{ 0x05, LED_MATRIX_BAND_SPIRAL},
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_LED_MATRIX_CYCLE_LEFT_RIGHT
|
||||
{ 0x06, LED_MATRIX_CYCLE_LEFT_RIGHT},
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_LED_MATRIX_CYCLE_UP_DOWN
|
||||
{ 0x07, LED_MATRIX_CYCLE_UP_DOWN},
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_LED_MATRIX_CYCLE_OUT_IN
|
||||
{ 0x08, LED_MATRIX_CYCLE_OUT_IN},
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_LED_MATRIX_DUAL_BEACON
|
||||
{ 0x09, LED_MATRIX_DUAL_BEACON},
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_LED_MATRIX_WAVE_LEFT_RIGHT
|
||||
{ 0x0A, LED_MATRIX_WAVE_LEFT_RIGHT},
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_LED_MATRIX_WAVE_UP_DOWN
|
||||
{ 0x0B, LED_MATRIX_WAVE_UP_DOWN},
|
||||
#endif
|
||||
|
||||
#ifdef LED_MATRIX_KEYREACTIVE_ENABLED
|
||||
|
||||
#ifdef ENABLE_LED_MATRIX_SOLID_REACTIVE_SIMPLE
|
||||
{ 0x0C, LED_MATRIX_SOLID_REACTIVE_SIMPLE},
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef LED_MATRIX_KEYREACTIVE_ENABLED
|
||||
|
||||
#ifdef ENABLE_LED_MATRIX_SOLID_REACTIVE_WIDE
|
||||
{ 0x0D, LED_MATRIX_SOLID_REACTIVE_WIDE},
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef LED_MATRIX_KEYREACTIVE_ENABLED
|
||||
|
||||
#ifdef ENABLE_LED_MATRIX_SOLID_REACTIVE_MULTIWIDE
|
||||
{ 0x0E, LED_MATRIX_SOLID_REACTIVE_MULTIWIDE},
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef LED_MATRIX_KEYREACTIVE_ENABLED
|
||||
|
||||
#ifdef ENABLE_LED_MATRIX_SOLID_REACTIVE_CROSS
|
||||
{ 0x0F, LED_MATRIX_SOLID_REACTIVE_CROSS},
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef LED_MATRIX_KEYREACTIVE_ENABLED
|
||||
|
||||
#ifdef ENABLE_LED_MATRIX_SOLID_REACTIVE_MULTICROSS
|
||||
{ 0x10, LED_MATRIX_SOLID_REACTIVE_MULTICROSS},
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef LED_MATRIX_KEYREACTIVE_ENABLED
|
||||
|
||||
#ifdef ENABLE_LED_MATRIX_SOLID_REACTIVE_NEXUS
|
||||
{ 0x11, LED_MATRIX_SOLID_REACTIVE_NEXUS},
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef LED_MATRIX_KEYREACTIVE_ENABLED
|
||||
|
||||
#ifdef ENABLE_LED_MATRIX_SOLID_REACTIVE_MULTINEXUS
|
||||
{ 0x12, LED_MATRIX_SOLID_REACTIVE_MULTINEXUS},
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef LED_MATRIX_KEYREACTIVE_ENABLED
|
||||
|
||||
#ifdef ENABLE_LED_MATRIX_SOLID_SPLASH
|
||||
{ 0x13, LED_MATRIX_SOLID_SPLASH},
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef LED_MATRIX_KEYREACTIVE_ENABLED
|
||||
|
||||
#ifdef ENABLE_LED_MATRIX_SOLID_MULTISPLASH
|
||||
{ 0x14, LED_MATRIX_SOLID_MULTISPLASH},
|
||||
#endif
|
||||
#endif
|
||||
};
|
||||
|
||||
uint8_t led_matrix_effect_to_id(uint8_t val) {
|
||||
for(uint8_t i = 0; i < ARRAY_SIZE(led_matrix_effect_map); i++) {
|
||||
if (pgm_read_byte(&led_matrix_effect_map[i][1]) == val)
|
||||
return pgm_read_byte(&led_matrix_effect_map[i][0]);
|
||||
}
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
uint8_t led_matrix_id_to_effect(uint8_t val) {
|
||||
for(uint8_t i = 0; i < ARRAY_SIZE(led_matrix_effect_map); i++) {
|
||||
if (pgm_read_byte(&led_matrix_effect_map[i][0]) == val)
|
||||
return pgm_read_byte(&led_matrix_effect_map[i][1]);
|
||||
}
|
||||
return 0xFF;
|
||||
}
|
@ -55,6 +55,11 @@ int main(void) {
|
||||
raw_hid_task();
|
||||
#endif
|
||||
|
||||
#ifdef XAP_ENABLE
|
||||
void xap_task(void);
|
||||
xap_task();
|
||||
#endif
|
||||
|
||||
#ifdef CONSOLE_ENABLE
|
||||
void console_task(void);
|
||||
console_task();
|
||||
|
@ -216,6 +216,13 @@ void nvm_eeconfig_update_handedness(bool val) {
|
||||
eeprom_update_byte(EECONFIG_HANDEDNESS, !!val);
|
||||
}
|
||||
|
||||
uint32_t nvm_eeconfig_read_keymap_hash(void) {
|
||||
return eeprom_read_dword(EECONFIG_KEYMAP_HASH);
|
||||
}
|
||||
void nvm_eeconfig_update_keymap_hash(uint32_t val) {
|
||||
eeprom_update_dword(EECONFIG_KEYMAP_HASH, val);
|
||||
}
|
||||
|
||||
#if (EECONFIG_KB_DATA_SIZE) > 0
|
||||
|
||||
bool nvm_eeconfig_is_kb_datablock_valid(void) {
|
||||
|
@ -30,6 +30,7 @@ typedef struct PACKED {
|
||||
uint32_t haptic;
|
||||
uint8_t rgblight_ext;
|
||||
uint8_t connection;
|
||||
uint32_t keymap_hash;
|
||||
} eeprom_core_t;
|
||||
|
||||
/* EEPROM parameter address */
|
||||
@ -50,6 +51,7 @@ typedef struct PACKED {
|
||||
#define EECONFIG_HAPTIC (uint32_t *)(offsetof(eeprom_core_t, haptic))
|
||||
#define EECONFIG_RGBLIGHT_EXTENDED (uint8_t *)(offsetof(eeprom_core_t, rgblight_ext))
|
||||
#define EECONFIG_CONNECTION (uint8_t *)(offsetof(eeprom_core_t, connection))
|
||||
#define EECONFIG_KEYMAP_HASH (uint32_t *)(offsetof(eeprom_core_t, keymap_hash))
|
||||
|
||||
// Size of EEPROM being used for core data storage
|
||||
#define EECONFIG_BASE_SIZE ((uint8_t)sizeof(eeprom_core_t))
|
||||
|
@ -96,6 +96,9 @@ void nvm_eeconfig_update_connection(const connectio
|
||||
bool nvm_eeconfig_read_handedness(void);
|
||||
void nvm_eeconfig_update_handedness(bool val);
|
||||
|
||||
uint32_t nvm_eeconfig_read_keymap_hash(void);
|
||||
void nvm_eeconfig_update_keymap_hash(uint32_t val);
|
||||
|
||||
#if (EECONFIG_KB_DATA_SIZE) > 0
|
||||
bool nvm_eeconfig_is_kb_datablock_valid(void);
|
||||
uint32_t nvm_eeconfig_read_kb_datablock(void *data, uint32_t offset, uint32_t length);
|
||||
|
@ -629,5 +629,9 @@ void secure_hook_quantum(secure_status_t secure_status) {
|
||||
clear_keyboard();
|
||||
layer_clear();
|
||||
}
|
||||
|
||||
# if defined(XAP_ENABLE)
|
||||
xap_broadcast_secure_status(secure_status);
|
||||
# endif
|
||||
}
|
||||
#endif
|
||||
|
@ -195,6 +195,10 @@ extern layer_state_t layer_state;
|
||||
# include "digitizer.h"
|
||||
#endif
|
||||
|
||||
#ifdef XAP_ENABLE
|
||||
# include "xap.h"
|
||||
#endif
|
||||
|
||||
#ifdef VIA_ENABLE
|
||||
# include "via.h"
|
||||
#endif
|
||||
|
517
quantum/rgb_matrix/lighting_map.h
Normal file
517
quantum/rgb_matrix/lighting_map.h
Normal file
@ -0,0 +1,517 @@
|
||||
// Copyright 2025 QMK
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
/*******************************************************************************
|
||||
88888888888 888 d8b .d888 d8b 888 d8b
|
||||
888 888 Y8P d88P" Y8P 888 Y8P
|
||||
888 888 888 888
|
||||
888 88888b. 888 .d8888b 888888 888 888 .d88b. 888 .d8888b
|
||||
888 888 "88b 888 88K 888 888 888 d8P Y8b 888 88K
|
||||
888 888 888 888 "Y8888b. 888 888 888 88888888 888 "Y8888b.
|
||||
888 888 888 888 X88 888 888 888 Y8b. 888 X88
|
||||
888 888 888 888 88888P' 888 888 888 "Y8888 888 88888P'
|
||||
888 888
|
||||
888 888
|
||||
888 888
|
||||
.d88b. .d88b. 88888b. .d88b. 888d888 8888b. 888888 .d88b. .d88888
|
||||
d88P"88b d8P Y8b 888 "88b d8P Y8b 888P" "88b 888 d8P Y8b d88" 888
|
||||
888 888 88888888 888 888 88888888 888 .d888888 888 88888888 888 888
|
||||
Y88b 888 Y8b. 888 888 Y8b. 888 888 888 Y88b. Y8b. Y88b 888
|
||||
"Y88888 "Y8888 888 888 "Y8888 888 "Y888888 "Y888 "Y8888 "Y88888
|
||||
888
|
||||
Y8b d88P
|
||||
"Y88P"
|
||||
*******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
// clang-format off
|
||||
enum { ENABLED_RGB_MATRIX_EFFECTS = 0
|
||||
| (1ULL << 0x00)
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_ALPHAS_MODS
|
||||
| (1ULL << 0x01)
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_GRADIENT_UP_DOWN
|
||||
| (1ULL << 0x02)
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_GRADIENT_LEFT_RIGHT
|
||||
| (1ULL << 0x03)
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_BREATHING
|
||||
| (1ULL << 0x04)
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_BAND_SAT
|
||||
| (1ULL << 0x05)
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_BAND_VAL
|
||||
| (1ULL << 0x06)
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_BAND_PINWHEEL_SAT
|
||||
| (1ULL << 0x07)
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_BAND_PINWHEEL_VAL
|
||||
| (1ULL << 0x08)
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_BAND_SPIRAL_SAT
|
||||
| (1ULL << 0x09)
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_BAND_SPIRAL_VAL
|
||||
| (1ULL << 0x0A)
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_CYCLE_ALL
|
||||
| (1ULL << 0x0B)
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_CYCLE_LEFT_RIGHT
|
||||
| (1ULL << 0x0C)
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_CYCLE_UP_DOWN
|
||||
| (1ULL << 0x0D)
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_CYCLE_OUT_IN
|
||||
| (1ULL << 0x0E)
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_CYCLE_OUT_IN_DUAL
|
||||
| (1ULL << 0x0F)
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_RAINBOW_MOVING_CHEVRON
|
||||
| (1ULL << 0x10)
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_CYCLE_PINWHEEL
|
||||
| (1ULL << 0x11)
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_CYCLE_SPIRAL
|
||||
| (1ULL << 0x12)
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_DUAL_BEACON
|
||||
| (1ULL << 0x13)
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_RAINBOW_BEACON
|
||||
| (1ULL << 0x14)
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_RAINBOW_PINWHEELS
|
||||
| (1ULL << 0x15)
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_RAINDROPS
|
||||
| (1ULL << 0x16)
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_JELLYBEAN_RAINDROPS
|
||||
| (1ULL << 0x17)
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_HUE_BREATHING
|
||||
| (1ULL << 0x18)
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_HUE_PENDULUM
|
||||
| (1ULL << 0x19)
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_HUE_WAVE
|
||||
| (1ULL << 0x1A)
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_PIXEL_FRACTAL
|
||||
| (1ULL << 0x1B)
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_PIXEL_FLOW
|
||||
| (1ULL << 0x1C)
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_PIXEL_RAIN
|
||||
| (1ULL << 0x1D)
|
||||
#endif
|
||||
|
||||
#ifdef RGB_MATRIX_FRAMEBUFFER_EFFECTS
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_TYPING_HEATMAP
|
||||
| (1ULL << 0x1E)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef RGB_MATRIX_FRAMEBUFFER_EFFECTS
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_DIGITAL_RAIN
|
||||
| (1ULL << 0x1F)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef RGB_MATRIX_KEYREACTIVE_ENABLED
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_SOLID_REACTIVE_SIMPLE
|
||||
| (1ULL << 0x20)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef RGB_MATRIX_KEYREACTIVE_ENABLED
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_SOLID_REACTIVE
|
||||
| (1ULL << 0x21)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef RGB_MATRIX_KEYREACTIVE_ENABLED
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_SOLID_REACTIVE_WIDE
|
||||
| (1ULL << 0x22)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef RGB_MATRIX_KEYREACTIVE_ENABLED
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_SOLID_REACTIVE_MULTIWIDE
|
||||
| (1ULL << 0x23)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef RGB_MATRIX_KEYREACTIVE_ENABLED
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_SOLID_REACTIVE_CROSS
|
||||
| (1ULL << 0x24)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef RGB_MATRIX_KEYREACTIVE_ENABLED
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_SOLID_REACTIVE_MULTICROSS
|
||||
| (1ULL << 0x25)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef RGB_MATRIX_KEYREACTIVE_ENABLED
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_SOLID_REACTIVE_NEXUS
|
||||
| (1ULL << 0x26)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef RGB_MATRIX_KEYREACTIVE_ENABLED
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_SOLID_REACTIVE_MULTINEXUS
|
||||
| (1ULL << 0x27)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef RGB_MATRIX_KEYREACTIVE_ENABLED
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_SPLASH
|
||||
| (1ULL << 0x28)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef RGB_MATRIX_KEYREACTIVE_ENABLED
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_MULTISPLASH
|
||||
| (1ULL << 0x29)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef RGB_MATRIX_KEYREACTIVE_ENABLED
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_SOLID_SPLASH
|
||||
| (1ULL << 0x2A)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef RGB_MATRIX_KEYREACTIVE_ENABLED
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_SOLID_MULTISPLASH
|
||||
| (1ULL << 0x2B)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_FLOWER_BLOOMING
|
||||
| (1ULL << 0x2C)
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_STARLIGHT
|
||||
| (1ULL << 0x2D)
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_STARLIGHT_DUAL_SAT
|
||||
| (1ULL << 0x2E)
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_STARLIGHT_DUAL_HUE
|
||||
| (1ULL << 0x2F)
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_RIVERFLOW
|
||||
| (1ULL << 0x30)
|
||||
#endif
|
||||
};
|
||||
static const uint8_t rgb_matrix_effect_map[][2] PROGMEM = {
|
||||
{ 0x00, RGB_MATRIX_SOLID_COLOR},
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_ALPHAS_MODS
|
||||
{ 0x01, RGB_MATRIX_ALPHAS_MODS},
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_GRADIENT_UP_DOWN
|
||||
{ 0x02, RGB_MATRIX_GRADIENT_UP_DOWN},
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_GRADIENT_LEFT_RIGHT
|
||||
{ 0x03, RGB_MATRIX_GRADIENT_LEFT_RIGHT},
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_BREATHING
|
||||
{ 0x04, RGB_MATRIX_BREATHING},
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_BAND_SAT
|
||||
{ 0x05, RGB_MATRIX_BAND_SAT},
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_BAND_VAL
|
||||
{ 0x06, RGB_MATRIX_BAND_VAL},
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_BAND_PINWHEEL_SAT
|
||||
{ 0x07, RGB_MATRIX_BAND_PINWHEEL_SAT},
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_BAND_PINWHEEL_VAL
|
||||
{ 0x08, RGB_MATRIX_BAND_PINWHEEL_VAL},
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_BAND_SPIRAL_SAT
|
||||
{ 0x09, RGB_MATRIX_BAND_SPIRAL_SAT},
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_BAND_SPIRAL_VAL
|
||||
{ 0x0A, RGB_MATRIX_BAND_SPIRAL_VAL},
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_CYCLE_ALL
|
||||
{ 0x0B, RGB_MATRIX_CYCLE_ALL},
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_CYCLE_LEFT_RIGHT
|
||||
{ 0x0C, RGB_MATRIX_CYCLE_LEFT_RIGHT},
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_CYCLE_UP_DOWN
|
||||
{ 0x0D, RGB_MATRIX_CYCLE_UP_DOWN},
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_CYCLE_OUT_IN
|
||||
{ 0x0E, RGB_MATRIX_CYCLE_OUT_IN},
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_CYCLE_OUT_IN_DUAL
|
||||
{ 0x0F, RGB_MATRIX_CYCLE_OUT_IN_DUAL},
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_RAINBOW_MOVING_CHEVRON
|
||||
{ 0x10, RGB_MATRIX_RAINBOW_MOVING_CHEVRON},
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_CYCLE_PINWHEEL
|
||||
{ 0x11, RGB_MATRIX_CYCLE_PINWHEEL},
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_CYCLE_SPIRAL
|
||||
{ 0x12, RGB_MATRIX_CYCLE_SPIRAL},
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_DUAL_BEACON
|
||||
{ 0x13, RGB_MATRIX_DUAL_BEACON},
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_RAINBOW_BEACON
|
||||
{ 0x14, RGB_MATRIX_RAINBOW_BEACON},
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_RAINBOW_PINWHEELS
|
||||
{ 0x15, RGB_MATRIX_RAINBOW_PINWHEELS},
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_RAINDROPS
|
||||
{ 0x16, RGB_MATRIX_RAINDROPS},
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_JELLYBEAN_RAINDROPS
|
||||
{ 0x17, RGB_MATRIX_JELLYBEAN_RAINDROPS},
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_HUE_BREATHING
|
||||
{ 0x18, RGB_MATRIX_HUE_BREATHING},
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_HUE_PENDULUM
|
||||
{ 0x19, RGB_MATRIX_HUE_PENDULUM},
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_HUE_WAVE
|
||||
{ 0x1A, RGB_MATRIX_HUE_WAVE},
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_PIXEL_FRACTAL
|
||||
{ 0x1B, RGB_MATRIX_PIXEL_FRACTAL},
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_PIXEL_FLOW
|
||||
{ 0x1C, RGB_MATRIX_PIXEL_FLOW},
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_PIXEL_RAIN
|
||||
{ 0x1D, RGB_MATRIX_PIXEL_RAIN},
|
||||
#endif
|
||||
|
||||
#ifdef RGB_MATRIX_FRAMEBUFFER_EFFECTS
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_TYPING_HEATMAP
|
||||
{ 0x1E, RGB_MATRIX_TYPING_HEATMAP},
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef RGB_MATRIX_FRAMEBUFFER_EFFECTS
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_DIGITAL_RAIN
|
||||
{ 0x1F, RGB_MATRIX_DIGITAL_RAIN},
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef RGB_MATRIX_KEYREACTIVE_ENABLED
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_SOLID_REACTIVE_SIMPLE
|
||||
{ 0x20, RGB_MATRIX_SOLID_REACTIVE_SIMPLE},
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef RGB_MATRIX_KEYREACTIVE_ENABLED
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_SOLID_REACTIVE
|
||||
{ 0x21, RGB_MATRIX_SOLID_REACTIVE},
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef RGB_MATRIX_KEYREACTIVE_ENABLED
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_SOLID_REACTIVE_WIDE
|
||||
{ 0x22, RGB_MATRIX_SOLID_REACTIVE_WIDE},
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef RGB_MATRIX_KEYREACTIVE_ENABLED
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_SOLID_REACTIVE_MULTIWIDE
|
||||
{ 0x23, RGB_MATRIX_SOLID_REACTIVE_MULTIWIDE},
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef RGB_MATRIX_KEYREACTIVE_ENABLED
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_SOLID_REACTIVE_CROSS
|
||||
{ 0x24, RGB_MATRIX_SOLID_REACTIVE_CROSS},
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef RGB_MATRIX_KEYREACTIVE_ENABLED
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_SOLID_REACTIVE_MULTICROSS
|
||||
{ 0x25, RGB_MATRIX_SOLID_REACTIVE_MULTICROSS},
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef RGB_MATRIX_KEYREACTIVE_ENABLED
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_SOLID_REACTIVE_NEXUS
|
||||
{ 0x26, RGB_MATRIX_SOLID_REACTIVE_NEXUS},
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef RGB_MATRIX_KEYREACTIVE_ENABLED
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_SOLID_REACTIVE_MULTINEXUS
|
||||
{ 0x27, RGB_MATRIX_SOLID_REACTIVE_MULTINEXUS},
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef RGB_MATRIX_KEYREACTIVE_ENABLED
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_SPLASH
|
||||
{ 0x28, RGB_MATRIX_SPLASH},
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef RGB_MATRIX_KEYREACTIVE_ENABLED
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_MULTISPLASH
|
||||
{ 0x29, RGB_MATRIX_MULTISPLASH},
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef RGB_MATRIX_KEYREACTIVE_ENABLED
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_SOLID_SPLASH
|
||||
{ 0x2A, RGB_MATRIX_SOLID_SPLASH},
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef RGB_MATRIX_KEYREACTIVE_ENABLED
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_SOLID_MULTISPLASH
|
||||
{ 0x2B, RGB_MATRIX_SOLID_MULTISPLASH},
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_FLOWER_BLOOMING
|
||||
{ 0x2C, RGB_MATRIX_FLOWER_BLOOMING},
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_STARLIGHT
|
||||
{ 0x2D, RGB_MATRIX_STARLIGHT},
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_STARLIGHT_DUAL_SAT
|
||||
{ 0x2E, RGB_MATRIX_STARLIGHT_DUAL_SAT},
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_STARLIGHT_DUAL_HUE
|
||||
{ 0x2F, RGB_MATRIX_STARLIGHT_DUAL_HUE},
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RGB_MATRIX_RIVERFLOW
|
||||
{ 0x30, RGB_MATRIX_RIVERFLOW},
|
||||
#endif
|
||||
};
|
||||
|
||||
uint8_t rgb_matrix_effect_to_id(uint8_t val) {
|
||||
for(uint8_t i = 0; i < ARRAY_SIZE(rgb_matrix_effect_map); i++) {
|
||||
if (pgm_read_byte(&rgb_matrix_effect_map[i][1]) == val)
|
||||
return pgm_read_byte(&rgb_matrix_effect_map[i][0]);
|
||||
}
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
uint8_t rgb_matrix_id_to_effect(uint8_t val) {
|
||||
for(uint8_t i = 0; i < ARRAY_SIZE(rgb_matrix_effect_map); i++) {
|
||||
if (pgm_read_byte(&rgb_matrix_effect_map[i][0]) == val)
|
||||
return pgm_read_byte(&rgb_matrix_effect_map[i][1]);
|
||||
}
|
||||
return 0xFF;
|
||||
}
|
377
quantum/rgblight/lighting_map.h
Normal file
377
quantum/rgblight/lighting_map.h
Normal file
@ -0,0 +1,377 @@
|
||||
// Copyright 2025 QMK
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
/*******************************************************************************
|
||||
88888888888 888 d8b .d888 d8b 888 d8b
|
||||
888 888 Y8P d88P" Y8P 888 Y8P
|
||||
888 888 888 888
|
||||
888 88888b. 888 .d8888b 888888 888 888 .d88b. 888 .d8888b
|
||||
888 888 "88b 888 88K 888 888 888 d8P Y8b 888 88K
|
||||
888 888 888 888 "Y8888b. 888 888 888 88888888 888 "Y8888b.
|
||||
888 888 888 888 X88 888 888 888 Y8b. 888 X88
|
||||
888 888 888 888 88888P' 888 888 888 "Y8888 888 88888P'
|
||||
888 888
|
||||
888 888
|
||||
888 888
|
||||
.d88b. .d88b. 88888b. .d88b. 888d888 8888b. 888888 .d88b. .d88888
|
||||
d88P"88b d8P Y8b 888 "88b d8P Y8b 888P" "88b 888 d8P Y8b d88" 888
|
||||
888 888 88888888 888 888 88888888 888 .d888888 888 88888888 888 888
|
||||
Y88b 888 Y8b. 888 888 Y8b. 888 888 888 Y88b. Y8b. Y88b 888
|
||||
"Y88888 "Y8888 888 888 "Y8888 888 "Y888888 "Y888 "Y8888 "Y88888
|
||||
888
|
||||
Y8b d88P
|
||||
"Y88P"
|
||||
*******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
// clang-format off
|
||||
enum { ENABLED_RGBLIGHT_EFFECTS = 0
|
||||
| (1ULL << 0x00)
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_BREATHING
|
||||
| (1ULL << 0x01)
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_BREATHING
|
||||
| (1ULL << 0x02)
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_BREATHING
|
||||
| (1ULL << 0x03)
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_BREATHING
|
||||
| (1ULL << 0x04)
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_RAINBOW_MOOD
|
||||
| (1ULL << 0x05)
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_RAINBOW_MOOD
|
||||
| (1ULL << 0x06)
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_RAINBOW_MOOD
|
||||
| (1ULL << 0x07)
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_RAINBOW_SWIRL
|
||||
| (1ULL << 0x08)
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_RAINBOW_SWIRL
|
||||
| (1ULL << 0x09)
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_RAINBOW_SWIRL
|
||||
| (1ULL << 0x0A)
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_RAINBOW_SWIRL
|
||||
| (1ULL << 0x0B)
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_RAINBOW_SWIRL
|
||||
| (1ULL << 0x0C)
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_RAINBOW_SWIRL
|
||||
| (1ULL << 0x0D)
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_SNAKE
|
||||
| (1ULL << 0x0E)
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_SNAKE
|
||||
| (1ULL << 0x0F)
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_SNAKE
|
||||
| (1ULL << 0x10)
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_SNAKE
|
||||
| (1ULL << 0x11)
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_SNAKE
|
||||
| (1ULL << 0x12)
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_SNAKE
|
||||
| (1ULL << 0x13)
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_KNIGHT
|
||||
| (1ULL << 0x14)
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_KNIGHT
|
||||
| (1ULL << 0x15)
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_KNIGHT
|
||||
| (1ULL << 0x16)
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_CHRISTMAS
|
||||
| (1ULL << 0x17)
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_STATIC_GRADIENT
|
||||
| (1ULL << 0x18)
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_STATIC_GRADIENT
|
||||
| (1ULL << 0x19)
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_STATIC_GRADIENT
|
||||
| (1ULL << 0x1A)
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_STATIC_GRADIENT
|
||||
| (1ULL << 0x1B)
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_STATIC_GRADIENT
|
||||
| (1ULL << 0x1C)
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_STATIC_GRADIENT
|
||||
| (1ULL << 0x1D)
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_STATIC_GRADIENT
|
||||
| (1ULL << 0x1E)
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_STATIC_GRADIENT
|
||||
| (1ULL << 0x1F)
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_STATIC_GRADIENT
|
||||
| (1ULL << 0x20)
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_STATIC_GRADIENT
|
||||
| (1ULL << 0x21)
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_RGB_TEST
|
||||
| (1ULL << 0x22)
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_ALTERNATING
|
||||
| (1ULL << 0x23)
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_TWINKLE
|
||||
| (1ULL << 0x24)
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_TWINKLE
|
||||
| (1ULL << 0x25)
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_TWINKLE
|
||||
| (1ULL << 0x26)
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_TWINKLE
|
||||
| (1ULL << 0x27)
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_TWINKLE
|
||||
| (1ULL << 0x28)
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_TWINKLE
|
||||
| (1ULL << 0x29)
|
||||
#endif
|
||||
};
|
||||
static const uint8_t rgblight_effect_map[][2] PROGMEM = {
|
||||
{ 0x00, RGBLIGHT_MODE_STATIC_LIGHT},
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_BREATHING
|
||||
{ 0x01, RGBLIGHT_MODE_BREATHING},
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_BREATHING
|
||||
{ 0x02, RGBLIGHT_MODE_BREATHING + 1},
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_BREATHING
|
||||
{ 0x03, RGBLIGHT_MODE_BREATHING + 2},
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_BREATHING
|
||||
{ 0x04, RGBLIGHT_MODE_BREATHING + 3},
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_RAINBOW_MOOD
|
||||
{ 0x05, RGBLIGHT_MODE_RAINBOW_MOOD},
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_RAINBOW_MOOD
|
||||
{ 0x06, RGBLIGHT_MODE_RAINBOW_MOOD + 1},
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_RAINBOW_MOOD
|
||||
{ 0x07, RGBLIGHT_MODE_RAINBOW_MOOD + 2},
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_RAINBOW_SWIRL
|
||||
{ 0x08, RGBLIGHT_MODE_RAINBOW_SWIRL},
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_RAINBOW_SWIRL
|
||||
{ 0x09, RGBLIGHT_MODE_RAINBOW_SWIRL + 1},
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_RAINBOW_SWIRL
|
||||
{ 0x0A, RGBLIGHT_MODE_RAINBOW_SWIRL + 2},
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_RAINBOW_SWIRL
|
||||
{ 0x0B, RGBLIGHT_MODE_RAINBOW_SWIRL + 3},
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_RAINBOW_SWIRL
|
||||
{ 0x0C, RGBLIGHT_MODE_RAINBOW_SWIRL + 4},
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_RAINBOW_SWIRL
|
||||
{ 0x0D, RGBLIGHT_MODE_RAINBOW_SWIRL + 5},
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_SNAKE
|
||||
{ 0x0E, RGBLIGHT_MODE_SNAKE},
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_SNAKE
|
||||
{ 0x0F, RGBLIGHT_MODE_SNAKE + 1},
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_SNAKE
|
||||
{ 0x10, RGBLIGHT_MODE_SNAKE + 2},
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_SNAKE
|
||||
{ 0x11, RGBLIGHT_MODE_SNAKE + 3},
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_SNAKE
|
||||
{ 0x12, RGBLIGHT_MODE_SNAKE + 4},
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_SNAKE
|
||||
{ 0x13, RGBLIGHT_MODE_SNAKE + 5},
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_KNIGHT
|
||||
{ 0x14, RGBLIGHT_MODE_KNIGHT},
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_KNIGHT
|
||||
{ 0x15, RGBLIGHT_MODE_KNIGHT + 1},
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_KNIGHT
|
||||
{ 0x16, RGBLIGHT_MODE_KNIGHT + 2},
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_CHRISTMAS
|
||||
{ 0x17, RGBLIGHT_MODE_CHRISTMAS},
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_STATIC_GRADIENT
|
||||
{ 0x18, RGBLIGHT_MODE_STATIC_GRADIENT},
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_STATIC_GRADIENT
|
||||
{ 0x19, RGBLIGHT_MODE_STATIC_GRADIENT + 1},
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_STATIC_GRADIENT
|
||||
{ 0x1A, RGBLIGHT_MODE_STATIC_GRADIENT + 2},
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_STATIC_GRADIENT
|
||||
{ 0x1B, RGBLIGHT_MODE_STATIC_GRADIENT + 3},
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_STATIC_GRADIENT
|
||||
{ 0x1C, RGBLIGHT_MODE_STATIC_GRADIENT + 4},
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_STATIC_GRADIENT
|
||||
{ 0x1D, RGBLIGHT_MODE_STATIC_GRADIENT + 5},
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_STATIC_GRADIENT
|
||||
{ 0x1E, RGBLIGHT_MODE_STATIC_GRADIENT + 6},
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_STATIC_GRADIENT
|
||||
{ 0x1F, RGBLIGHT_MODE_STATIC_GRADIENT + 7},
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_STATIC_GRADIENT
|
||||
{ 0x20, RGBLIGHT_MODE_STATIC_GRADIENT + 8},
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_STATIC_GRADIENT
|
||||
{ 0x21, RGBLIGHT_MODE_STATIC_GRADIENT + 9},
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_RGB_TEST
|
||||
{ 0x22, RGBLIGHT_MODE_RGB_TEST},
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_ALTERNATING
|
||||
{ 0x23, RGBLIGHT_MODE_ALTERNATING},
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_TWINKLE
|
||||
{ 0x24, RGBLIGHT_MODE_TWINKLE},
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_TWINKLE
|
||||
{ 0x25, RGBLIGHT_MODE_TWINKLE + 1},
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_TWINKLE
|
||||
{ 0x26, RGBLIGHT_MODE_TWINKLE + 2},
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_TWINKLE
|
||||
{ 0x27, RGBLIGHT_MODE_TWINKLE + 3},
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_TWINKLE
|
||||
{ 0x28, RGBLIGHT_MODE_TWINKLE + 4},
|
||||
#endif
|
||||
|
||||
#ifdef RGBLIGHT_EFFECT_TWINKLE
|
||||
{ 0x29, RGBLIGHT_MODE_TWINKLE + 5},
|
||||
#endif
|
||||
};
|
||||
|
||||
uint8_t rgblight_effect_to_id(uint8_t val) {
|
||||
for(uint8_t i = 0; i < ARRAY_SIZE(rgblight_effect_map); i++) {
|
||||
if (pgm_read_byte(&rgblight_effect_map[i][1]) == val)
|
||||
return pgm_read_byte(&rgblight_effect_map[i][0]);
|
||||
}
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
uint8_t rgblight_id_to_effect(uint8_t val) {
|
||||
for(uint8_t i = 0; i < ARRAY_SIZE(rgblight_effect_map); i++) {
|
||||
if (pgm_read_byte(&rgblight_effect_map[i][0]) == val)
|
||||
return pgm_read_byte(&rgblight_effect_map[i][1]);
|
||||
}
|
||||
return 0xFF;
|
||||
}
|
33
quantum/xap/handlers/audio.c
Normal file
33
quantum/xap/handlers/audio.c
Normal file
@ -0,0 +1,33 @@
|
||||
// Copyright 2022 QMK
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "quantum.h"
|
||||
#include "xap.h"
|
||||
|
||||
#if ((defined(AUDIO_ENABLE)))
|
||||
# include "audio.h"
|
||||
|
||||
extern audio_config_t audio_config;
|
||||
|
||||
bool xap_execute_get_audio_config(xap_token_t token) {
|
||||
xap_route_audio_get_config_t ret;
|
||||
|
||||
ret.enable = audio_config.enable;
|
||||
ret.clicky_enable = audio_config.clicky_enable;
|
||||
|
||||
return xap_respond_data(token, &ret, sizeof(ret));
|
||||
}
|
||||
|
||||
bool xap_execute_set_audio_config(xap_token_t token, xap_route_audio_set_config_arg_t* arg) {
|
||||
audio_config.enable = arg->enable;
|
||||
audio_config.clicky_enable = arg->clicky_enable;
|
||||
|
||||
return xap_respond_success(token);
|
||||
}
|
||||
|
||||
bool xap_execute_save_audio_config(xap_token_t token) {
|
||||
eeconfig_update_audio_current();
|
||||
|
||||
return xap_respond_success(token);
|
||||
}
|
||||
#endif
|
60
quantum/xap/handlers/core.c
Normal file
60
quantum/xap/handlers/core.c
Normal file
@ -0,0 +1,60 @@
|
||||
// Copyright 2022 QMK
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "quantum.h"
|
||||
#include "xap.h"
|
||||
#include "hardware_id.h"
|
||||
|
||||
bool xap_execute_get_config_blob_chunk(xap_token_t token, uint16_t offset) {
|
||||
xap_route_qmk_config_blob_chunk_t ret = {0};
|
||||
|
||||
bool get_config_blob_chunk(uint16_t offset, uint8_t * data, uint8_t data_len);
|
||||
if (!get_config_blob_chunk(offset, (uint8_t *)&ret, sizeof(ret))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return xap_respond_data(token, &ret, sizeof(ret));
|
||||
}
|
||||
|
||||
bool xap_execute_secure_status(xap_token_t token) {
|
||||
uint8_t ret = secure_get_status();
|
||||
return xap_respond_data(token, &ret, sizeof(ret));
|
||||
}
|
||||
|
||||
bool xap_execute_secure_unlock(xap_token_t token) {
|
||||
secure_request_unlock();
|
||||
return xap_respond_success(token);
|
||||
}
|
||||
|
||||
bool xap_execute_secure_lock(xap_token_t token) {
|
||||
secure_lock();
|
||||
return xap_respond_success(token);
|
||||
}
|
||||
|
||||
#ifdef BOOTLOADER_JUMP_SUPPORTED
|
||||
bool xap_execute_request_bootloader(xap_token_t token) {
|
||||
uint8_t ret = secure_is_unlocked();
|
||||
|
||||
// TODO: post to deferred queue so this request can return?
|
||||
bool res = xap_respond_data(token, &ret, sizeof(ret));
|
||||
reset_keyboard();
|
||||
return res;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef NO_RESET
|
||||
bool xap_execute_request_eeprom_reset(xap_token_t token) {
|
||||
uint8_t ret = secure_is_unlocked();
|
||||
|
||||
// TODO: post to deferred queue so this request can return?
|
||||
bool res = xap_respond_data(token, &ret, sizeof(ret));
|
||||
eeconfig_disable();
|
||||
soft_reset_keyboard();
|
||||
return res;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool xap_execute_get_hardware_id(xap_token_t token) {
|
||||
hardware_id_t ret = get_hardware_id();
|
||||
return xap_respond_data(token, &ret, sizeof(ret));
|
||||
}
|
137
quantum/xap/handlers/lighting.c
Normal file
137
quantum/xap/handlers/lighting.c
Normal file
@ -0,0 +1,137 @@
|
||||
// Copyright 2022 QMK
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "quantum.h"
|
||||
#include "xap.h"
|
||||
|
||||
#define INVALID_EFFECT 0xFF
|
||||
|
||||
#if ((defined(BACKLIGHT_ENABLE)))
|
||||
# include "backlight.h"
|
||||
|
||||
extern backlight_config_t backlight_config;
|
||||
|
||||
bool xap_execute_get_backlight_config(xap_token_t token) {
|
||||
xap_route_lighting_backlight_get_config_t ret;
|
||||
|
||||
ret.enable = backlight_config.enable;
|
||||
ret.mode = backlight_config.breathing;
|
||||
ret.val = backlight_config.level;
|
||||
|
||||
return xap_respond_data(token, &ret, sizeof(ret));
|
||||
}
|
||||
|
||||
bool xap_execute_set_backlight_config(xap_token_t token, xap_route_lighting_backlight_set_config_arg_t* arg) {
|
||||
if (arg->enable) {
|
||||
backlight_level_noeeprom(arg->val);
|
||||
} else {
|
||||
backlight_level_noeeprom(0);
|
||||
}
|
||||
|
||||
# ifdef BACKLIGHT_BREATHING
|
||||
if (arg->mode) {
|
||||
backlight_enable_breathing();
|
||||
} else {
|
||||
backlight_disable_breathing();
|
||||
}
|
||||
# endif
|
||||
|
||||
return xap_respond_success(token);
|
||||
}
|
||||
|
||||
bool xap_execute_save_backlight_config(xap_token_t token) {
|
||||
eeconfig_update_backlight_current();
|
||||
|
||||
return xap_respond_success(token);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ((defined(RGBLIGHT_ENABLE)))
|
||||
# include "rgblight.h"
|
||||
|
||||
extern rgblight_config_t rgblight_config;
|
||||
|
||||
uint8_t rgblight_effect_to_id(uint8_t val);
|
||||
uint8_t rgblight_id_to_effect(uint8_t val);
|
||||
|
||||
bool xap_execute_get_rgblight_config(xap_token_t token) {
|
||||
xap_route_lighting_rgblight_get_config_t ret;
|
||||
|
||||
ret.enable = rgblight_config.enable;
|
||||
ret.mode = rgblight_effect_to_id(rgblight_config.mode);
|
||||
ret.hue = rgblight_config.hue;
|
||||
ret.sat = rgblight_config.sat;
|
||||
ret.val = rgblight_config.val;
|
||||
ret.speed = rgblight_config.speed;
|
||||
|
||||
return xap_respond_data(token, &ret, sizeof(ret));
|
||||
}
|
||||
|
||||
bool xap_execute_set_rgblight_config(xap_token_t token, xap_route_lighting_rgblight_set_config_arg_t* arg) {
|
||||
uint8_t mode = rgblight_id_to_effect(arg->mode);
|
||||
if (mode == INVALID_EFFECT) {
|
||||
return false;
|
||||
}
|
||||
|
||||
rgblight_enabled_noeeprom(arg->enable);
|
||||
rgblight_mode_noeeprom(mode);
|
||||
rgblight_sethsv_noeeprom(arg->hue, arg->sat, arg->val);
|
||||
rgblight_set_speed_noeeprom(arg->speed);
|
||||
|
||||
return xap_respond_success(token);
|
||||
}
|
||||
|
||||
bool xap_execute_save_rgblight_config(xap_token_t token) {
|
||||
eeconfig_update_rgblight_current();
|
||||
|
||||
return xap_respond_success(token);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ((defined(RGB_MATRIX_ENABLE)))
|
||||
# include "rgb_matrix.h"
|
||||
|
||||
extern rgb_config_t rgb_matrix_config;
|
||||
|
||||
uint8_t rgb_matrix_effect_to_id(uint8_t val);
|
||||
uint8_t rgb_matrix_id_to_effect(uint8_t val);
|
||||
|
||||
void rgb_matrix_enabled_noeeprom(bool val) {
|
||||
val ? rgb_matrix_enable_noeeprom() : rgb_matrix_disable_noeeprom();
|
||||
}
|
||||
|
||||
bool xap_execute_get_rgb_matrix_config(xap_token_t token) {
|
||||
xap_route_lighting_rgb_matrix_get_config_t ret;
|
||||
|
||||
ret.enable = rgb_matrix_config.enable;
|
||||
ret.mode = rgb_matrix_effect_to_id(rgb_matrix_config.mode);
|
||||
ret.hue = rgb_matrix_config.hsv.h;
|
||||
ret.sat = rgb_matrix_config.hsv.s;
|
||||
ret.val = rgb_matrix_config.hsv.v;
|
||||
ret.speed = rgb_matrix_config.speed;
|
||||
ret.flags = rgb_matrix_config.flags;
|
||||
|
||||
return xap_respond_data(token, &ret, sizeof(ret));
|
||||
}
|
||||
|
||||
bool xap_execute_set_rgb_matrix_config(xap_token_t token, xap_route_lighting_rgb_matrix_set_config_arg_t* arg) {
|
||||
uint8_t mode = rgb_matrix_id_to_effect(arg->mode);
|
||||
if (mode == INVALID_EFFECT) {
|
||||
return false;
|
||||
}
|
||||
|
||||
rgb_matrix_enabled_noeeprom(arg->enable);
|
||||
rgb_matrix_mode_noeeprom(mode);
|
||||
rgb_matrix_sethsv_noeeprom(arg->hue, arg->sat, arg->val);
|
||||
rgb_matrix_set_speed_noeeprom(arg->speed);
|
||||
rgb_matrix_set_flags_noeeprom(arg->flags);
|
||||
|
||||
return xap_respond_success(token);
|
||||
}
|
||||
|
||||
bool xap_execute_save_rgb_matrix_config(xap_token_t token) {
|
||||
eeconfig_force_flush_rgb_matrix();
|
||||
|
||||
return xap_respond_success(token);
|
||||
}
|
||||
#endif
|
60
quantum/xap/handlers/remapping.c
Normal file
60
quantum/xap/handlers/remapping.c
Normal file
@ -0,0 +1,60 @@
|
||||
// Copyright 2022 QMK
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "quantum.h"
|
||||
#include "xap.h"
|
||||
#include "keymap_introspection.h"
|
||||
|
||||
#ifdef DYNAMIC_KEYMAP_ENABLE
|
||||
# include "dynamic_keymap.h"
|
||||
# define keymap_max_layer_count() DYNAMIC_KEYMAP_LAYER_COUNT
|
||||
#else
|
||||
# define keymap_max_layer_count() keymap_layer_count()
|
||||
#endif
|
||||
|
||||
bool xap_execute_keymap_get_layer_count(xap_token_t token) {
|
||||
uint8_t ret = keymap_max_layer_count();
|
||||
return xap_respond_data(token, &ret, sizeof(ret));
|
||||
}
|
||||
|
||||
bool xap_execute_get_keymap_keycode(xap_token_t token, const xap_route_keymap_get_keymap_keycode_arg_t *arg) {
|
||||
if (arg->layer >= keymap_max_layer_count()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16_t keycode = keycode_at_keymap_location(arg->layer, arg->row, arg->column);
|
||||
return xap_respond_data(token, &keycode, sizeof(keycode));
|
||||
}
|
||||
|
||||
#if ((defined(ENCODER_MAP_ENABLE)))
|
||||
bool xap_execute_get_encoder_keycode(xap_token_t token, const xap_route_keymap_get_encoder_keycode_arg_t *arg) {
|
||||
if (arg->layer >= keymap_max_layer_count()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16_t keycode = keycode_at_encodermap_location(arg->layer, arg->encoder, arg->clockwise);
|
||||
return xap_respond_data(token, &keycode, sizeof(keycode));
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ((defined(DYNAMIC_KEYMAP_ENABLE)))
|
||||
bool xap_execute_dynamic_keymap_set_keycode(xap_token_t token, const xap_route_remapping_set_keymap_keycode_arg_t *arg) {
|
||||
if (arg->layer >= keymap_max_layer_count()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
dynamic_keymap_set_keycode(arg->layer, arg->row, arg->column, arg->keycode);
|
||||
return xap_respond_success(token);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ((defined(DYNAMIC_KEYMAP_ENABLE) && defined(ENCODER_MAP_ENABLE)))
|
||||
bool xap_execute_dynamic_encoder_set_keycode(xap_token_t token, const xap_route_remapping_set_encoder_keycode_arg_t *arg) {
|
||||
if (arg->layer >= keymap_max_layer_count()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
dynamic_keymap_set_encoder(arg->layer, arg->encoder, arg->clockwise, arg->keycode);
|
||||
return xap_respond_success(token);
|
||||
}
|
||||
#endif
|
163
quantum/xap/xap.c
Normal file
163
quantum/xap/xap.c
Normal file
@ -0,0 +1,163 @@
|
||||
/* Copyright 2021 Nick Brassel (@tzarc)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <quantum.h>
|
||||
#include <xap.h>
|
||||
#include "secure.h"
|
||||
|
||||
#ifdef RGBLIGHT_ENABLE
|
||||
# include "rgblight/lighting_map.h"
|
||||
#endif
|
||||
#ifdef RGB_MATRIX_ENABLE
|
||||
# include "rgb_matrix/lighting_map.h"
|
||||
#endif
|
||||
#ifdef LED_MATRIX_ENABLE
|
||||
# include "led_matrix/lighting_map.h"
|
||||
#endif
|
||||
|
||||
#include "config_blob_gz.h"
|
||||
bool get_config_blob_chunk(uint16_t offset, uint8_t *data, uint8_t data_len) {
|
||||
if (offset >= CONFIG_BLOB_GZ_LEN) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (offset + data_len >= CONFIG_BLOB_GZ_LEN) {
|
||||
data_len = (CONFIG_BLOB_GZ_LEN - 1) - offset;
|
||||
}
|
||||
|
||||
memcpy_P(data, &config_blob_gz[offset], data_len);
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: move to better location?
|
||||
#ifdef BACKLIGHT_BREATHING
|
||||
# define ENABLED_BACKLIGHT_EFFECTS 0b00000001
|
||||
#else
|
||||
# define ENABLED_BACKLIGHT_EFFECTS 0b00000000
|
||||
#endif
|
||||
|
||||
#define QSTR2(z) #z
|
||||
#define QSTR(z) QSTR2(z)
|
||||
|
||||
typedef enum xap_route_type_t {
|
||||
XAP_UNUSED = 0, // "Unused" needs to be zero -- undefined routes (through preprocessor) will be skipped
|
||||
XAP_ROUTE,
|
||||
XAP_EXECUTE,
|
||||
XAP_VALUE,
|
||||
XAP_CONST_MEM,
|
||||
TOTAL_XAP_ROUTE_TYPES
|
||||
} xap_route_type_t;
|
||||
|
||||
#define XAP_ROUTE_TYPE_BIT_COUNT 3
|
||||
|
||||
typedef enum xap_route_secure_t {
|
||||
ROUTE_PERMISSIONS_INSECURE,
|
||||
ROUTE_PERMISSIONS_SECURE,
|
||||
} xap_route_secure_t;
|
||||
|
||||
#define XAP_ROUTE_SECURE_BIT_COUNT 2
|
||||
|
||||
typedef struct __attribute__((packed)) xap_route_flags_t {
|
||||
xap_route_type_t type : XAP_ROUTE_TYPE_BIT_COUNT;
|
||||
xap_route_secure_t secure : XAP_ROUTE_SECURE_BIT_COUNT;
|
||||
} xap_route_flags_t;
|
||||
|
||||
_Static_assert(TOTAL_XAP_ROUTE_TYPES <= (1 << (XAP_ROUTE_TYPE_BIT_COUNT)), "Number of XAP route types is too large for XAP_ROUTE_TYPE_BITS.");
|
||||
_Static_assert(sizeof(xap_route_flags_t) == 1, "xap_route_flags_t is not length of 1");
|
||||
|
||||
typedef struct xap_route_t xap_route_t;
|
||||
struct __attribute__((packed)) xap_route_t {
|
||||
const xap_route_flags_t flags;
|
||||
union {
|
||||
// XAP_ROUTE
|
||||
struct {
|
||||
const xap_route_t *child_routes;
|
||||
const uint8_t child_routes_len;
|
||||
};
|
||||
|
||||
// XAP_EXECUTE
|
||||
bool (*handler)(xap_token_t token, const uint8_t *data, size_t data_len);
|
||||
|
||||
// XAP_VALUE / XAP_CONST_MEM
|
||||
struct {
|
||||
const void * const_data;
|
||||
const uint8_t const_data_len;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
#include <xap_generated.inl>
|
||||
|
||||
bool xap_pre_execute_route(xap_token_t token, const xap_route_t *route) {
|
||||
#ifdef SECURE_ENABLE
|
||||
if (!secure_is_unlocked() && (route->flags.secure == ROUTE_PERMISSIONS_SECURE)) {
|
||||
xap_respond_failure(token, XAP_RESPONSE_FLAG_SECURE_FAILURE);
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: XAP messages extend unlocked timeout?
|
||||
secure_activity_event();
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
void xap_execute_route(xap_token_t token, const xap_route_t *routes, size_t max_routes, const uint8_t *data, size_t data_len) {
|
||||
if (data_len == 0) return;
|
||||
xap_identifier_t id = data[0];
|
||||
|
||||
if (id < max_routes) {
|
||||
xap_route_t route;
|
||||
memcpy_P(&route, &routes[id], sizeof(xap_route_t));
|
||||
|
||||
if (xap_pre_execute_route(token, &route)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (route.flags.type) {
|
||||
case XAP_ROUTE:
|
||||
if (route.child_routes != NULL && route.child_routes_len > 0 && data_len > 0) {
|
||||
xap_execute_route(token, route.child_routes, route.child_routes_len, &data[1], data_len - 1);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case XAP_EXECUTE:
|
||||
if (route.handler != NULL) {
|
||||
bool ok = (route.handler)(token, data_len == 1 ? NULL : &data[1], data_len - 1);
|
||||
if (ok) return;
|
||||
}
|
||||
break;
|
||||
|
||||
case XAP_VALUE:
|
||||
xap_respond_data(token, route.const_data, route.const_data_len);
|
||||
return;
|
||||
|
||||
case XAP_CONST_MEM:
|
||||
xap_respond_data_P(token, route.const_data, route.const_data_len);
|
||||
return;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Nothing got handled, so we respond with failure.
|
||||
xap_respond_failure(token, XAP_RESPONSE_FLAG_FAILED);
|
||||
}
|
||||
|
||||
void xap_receive(xap_token_t token, const uint8_t *data, size_t length) {
|
||||
xap_execute_route(token, xap_route_table, sizeof(xap_route_table) / sizeof(xap_route_t), data, length);
|
||||
}
|
39
quantum/xap/xap.h
Normal file
39
quantum/xap/xap.h
Normal file
@ -0,0 +1,39 @@
|
||||
/* Copyright 2021 Nick Brassel (@tzarc)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <xap_generated.h>
|
||||
|
||||
#ifndef XAP_SUBSYSTEM_VERSION_KB
|
||||
# define XAP_SUBSYSTEM_VERSION_KB 0
|
||||
#endif
|
||||
|
||||
#ifndef XAP_SUBSYSTEM_VERSION_USER
|
||||
# define XAP_SUBSYSTEM_VERSION_USER 0
|
||||
#endif
|
||||
|
||||
bool xap_respond_success(xap_token_t token);
|
||||
void xap_respond_failure(xap_token_t token, xap_response_flags_t response_flags);
|
||||
bool xap_respond_u32(xap_token_t token, uint32_t value);
|
||||
bool xap_respond_data(xap_token_t token, const void *data, size_t length);
|
||||
bool xap_respond_data_P(xap_token_t token, const void *data, size_t length);
|
||||
|
||||
void xap_send(xap_token_t token, xap_response_flags_t response_flags, const void *data, size_t length);
|
||||
void xap_broadcast(uint8_t type, const void *data, size_t length);
|
56
quantum/xap/xap_handlers.c
Normal file
56
quantum/xap/xap_handlers.c
Normal file
@ -0,0 +1,56 @@
|
||||
/* Copyright 2021 Nick Brassel (@tzarc)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include "quantum.h"
|
||||
#include "xap.h"
|
||||
|
||||
#include "secure.h"
|
||||
#ifndef SECURE_ENABLE
|
||||
# define secure_get_status() SECURE_UNLOCKED
|
||||
# define secure_request_unlock()
|
||||
# define secure_lock()
|
||||
#endif
|
||||
|
||||
bool xap_respond_success(xap_token_t token) {
|
||||
xap_send(token, XAP_RESPONSE_FLAG_SUCCESS, NULL, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
void xap_respond_failure(xap_token_t token, xap_response_flags_t response_flags) {
|
||||
xap_send(token, response_flags, NULL, 0);
|
||||
}
|
||||
|
||||
bool xap_respond_data(xap_token_t token, const void *data, size_t length) {
|
||||
xap_send(token, XAP_RESPONSE_FLAG_SUCCESS, data, length);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool xap_respond_data_P(xap_token_t token, const void *data, size_t length) {
|
||||
uint8_t blob[length];
|
||||
memcpy_P(blob, data, length);
|
||||
return xap_respond_data(token, blob, length);
|
||||
}
|
||||
|
||||
bool xap_respond_u32(xap_token_t token, uint32_t value) {
|
||||
xap_send(token, XAP_RESPONSE_FLAG_SUCCESS, &value, sizeof(value));
|
||||
return true;
|
||||
}
|
||||
|
||||
#include "handlers/core.c"
|
||||
#include "handlers/remapping.c"
|
||||
#include "handlers/lighting.c"
|
||||
#include "handlers/audio.c"
|
@ -3,8 +3,10 @@
|
||||
argcomplete
|
||||
colorama
|
||||
dotty-dict
|
||||
fnvhash
|
||||
hid
|
||||
hjson
|
||||
Jinja2
|
||||
jsonschema>=4
|
||||
milc>=1.9.0
|
||||
pygments
|
||||
|
@ -80,6 +80,14 @@ usb_endpoint_in_t usb_endpoints_in[USB_ENDPOINT_IN_COUNT] = {
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if defined(XAP_ENABLE)
|
||||
# if defined(USB_ENDPOINTS_ARE_REORDERABLE)
|
||||
[USB_ENDPOINT_IN_XAP] = QMK_USB_ENDPOINT_IN_SHARED(USB_EP_MODE_TYPE_INTR, XAP_EPSIZE, XAP_IN_EPNUM, XAP_IN_CAPACITY, NULL, QMK_USB_REPORT_STORAGE_DEFAULT(XAP_EPSIZE)),
|
||||
# else
|
||||
[USB_ENDPOINT_IN_XAP] = QMK_USB_ENDPOINT_IN(USB_EP_MODE_TYPE_INTR, XAP_EPSIZE, XAP_IN_EPNUM, XAP_IN_CAPACITY, NULL, QMK_USB_REPORT_STORAGE_DEFAULT(XAP_EPSIZE)),
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if defined(MIDI_ENABLE)
|
||||
# if defined(USB_ENDPOINTS_ARE_REORDERABLE)
|
||||
[USB_ENDPOINT_IN_MIDI] = QMK_USB_ENDPOINT_IN_SHARED(USB_EP_MODE_TYPE_BULK, MIDI_STREAM_EPSIZE, MIDI_STREAM_IN_EPNUM, MIDI_STREAM_IN_CAPACITY, NULL, NULL),
|
||||
@ -107,6 +115,10 @@ usb_endpoint_in_lut_t usb_endpoint_interface_lut[TOTAL_INTERFACES] = {
|
||||
[RAW_INTERFACE] = USB_ENDPOINT_IN_RAW,
|
||||
#endif
|
||||
|
||||
#if defined(XAP_ENABLE)
|
||||
[XAP_INTERFACE] = USB_ENDPOINT_IN_XAP,
|
||||
#endif
|
||||
|
||||
#if defined(MOUSE_ENABLE) && !defined(MOUSE_SHARED_EP)
|
||||
[MOUSE_INTERFACE] = USB_ENDPOINT_IN_MOUSE,
|
||||
#endif
|
||||
@ -142,6 +154,10 @@ usb_endpoint_out_t usb_endpoints_out[USB_ENDPOINT_OUT_COUNT] = {
|
||||
[USB_ENDPOINT_OUT_RAW] = QMK_USB_ENDPOINT_OUT(USB_EP_MODE_TYPE_INTR, RAW_EPSIZE, RAW_OUT_EPNUM, RAW_OUT_CAPACITY),
|
||||
#endif
|
||||
|
||||
#if defined(XAP_ENABLE)
|
||||
[USB_ENDPOINT_OUT_XAP] = QMK_USB_ENDPOINT_OUT(USB_EP_MODE_TYPE_INTR, XAP_EPSIZE, XAP_OUT_EPNUM, XAP_OUT_CAPACITY),
|
||||
#endif
|
||||
|
||||
#if defined(MIDI_ENABLE)
|
||||
[USB_ENDPOINT_OUT_MIDI] = QMK_USB_ENDPOINT_OUT(USB_EP_MODE_TYPE_BULK, MIDI_STREAM_EPSIZE, MIDI_STREAM_OUT_EPNUM, MIDI_STREAM_OUT_CAPACITY),
|
||||
#endif
|
||||
|
@ -43,6 +43,14 @@
|
||||
# define RAW_OUT_CAPACITY USB_DEFAULT_BUFFER_CAPACITY
|
||||
#endif
|
||||
|
||||
#if !defined(XAP_IN_CAPACITY)
|
||||
# define XAP_IN_CAPACITY USB_DEFAULT_BUFFER_CAPACITY
|
||||
#endif
|
||||
|
||||
#if !defined(XAP_OUT_CAPACITY)
|
||||
# define XAP_OUT_CAPACITY USB_DEFAULT_BUFFER_CAPACITY
|
||||
#endif
|
||||
|
||||
#if !defined(MIDI_STREAM_IN_CAPACITY)
|
||||
# define MIDI_STREAM_IN_CAPACITY USB_DEFAULT_BUFFER_CAPACITY
|
||||
#endif
|
||||
@ -90,6 +98,10 @@ typedef enum {
|
||||
USB_ENDPOINT_IN_RAW,
|
||||
#endif
|
||||
|
||||
#if defined(XAP_ENABLE)
|
||||
USB_ENDPOINT_IN_XAP,
|
||||
#endif
|
||||
|
||||
#if defined(MIDI_ENABLE)
|
||||
USB_ENDPOINT_IN_MIDI,
|
||||
#endif
|
||||
@ -127,6 +139,9 @@ typedef enum {
|
||||
#if defined(RAW_ENABLE)
|
||||
USB_ENDPOINT_OUT_RAW,
|
||||
#endif
|
||||
#if defined(XAP_ENABLE)
|
||||
USB_ENDPOINT_OUT_XAP,
|
||||
#endif
|
||||
#if defined(MIDI_ENABLE)
|
||||
USB_ENDPOINT_OUT_MIDI,
|
||||
#endif
|
||||
|
@ -38,6 +38,10 @@
|
||||
extern keymap_config_t keymap_config;
|
||||
#endif
|
||||
|
||||
#ifdef XAP_ENABLE
|
||||
# include "xap.h"
|
||||
#endif
|
||||
|
||||
/* ---------------------------------------------------------
|
||||
* Global interface variables and declarations
|
||||
* ---------------------------------------------------------
|
||||
@ -523,6 +527,65 @@ void raw_hid_task(void) {
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef XAP_ENABLE
|
||||
extern void xap_receive(xap_token_t token, const uint8_t *data, size_t length);
|
||||
|
||||
void xap_send_base(uint8_t *data, uint8_t length) {
|
||||
if (length != XAP_EPSIZE) {
|
||||
return;
|
||||
}
|
||||
|
||||
send_report(USB_ENDPOINT_IN_XAP, data, length);
|
||||
}
|
||||
|
||||
void xap_send(xap_token_t token, xap_response_flags_t response_flags, const void *data, size_t length) {
|
||||
uint8_t rdata[XAP_EPSIZE] = {0};
|
||||
xap_response_header_t *header = (xap_response_header_t *)&rdata[0];
|
||||
header->token = token;
|
||||
|
||||
if (length > (XAP_EPSIZE - sizeof(xap_response_header_t))) response_flags &= ~(XAP_RESPONSE_FLAG_SUCCESS);
|
||||
header->flags = response_flags;
|
||||
|
||||
if (response_flags & (XAP_RESPONSE_FLAG_SUCCESS)) {
|
||||
header->length = (uint8_t)length;
|
||||
if (data != NULL) {
|
||||
memcpy(&rdata[sizeof(xap_response_header_t)], data, length);
|
||||
}
|
||||
}
|
||||
xap_send_base(rdata, sizeof(rdata));
|
||||
}
|
||||
|
||||
void xap_broadcast(uint8_t type, const void *data, size_t length) {
|
||||
uint8_t rdata[XAP_EPSIZE] = {0};
|
||||
xap_broadcast_header_t *header = (xap_broadcast_header_t *)&rdata[0];
|
||||
header->token = XAP_BROADCAST_TOKEN;
|
||||
header->type = type;
|
||||
|
||||
if (length > (XAP_EPSIZE - sizeof(xap_broadcast_header_t))) return;
|
||||
|
||||
header->length = (uint8_t)length;
|
||||
if (data != NULL) {
|
||||
memcpy(&rdata[sizeof(xap_broadcast_header_t)], data, length);
|
||||
}
|
||||
xap_send_base(rdata, sizeof(rdata));
|
||||
}
|
||||
|
||||
void xap_receive_base(const void *data) {
|
||||
const uint8_t * u8data = (const uint8_t *)data;
|
||||
xap_request_header_t *header = (xap_request_header_t *)&u8data[0];
|
||||
if (header->length <= (XAP_EPSIZE - sizeof(xap_request_header_t))) {
|
||||
xap_receive(header->token, &u8data[sizeof(xap_request_header_t)], header->length);
|
||||
}
|
||||
}
|
||||
|
||||
void xap_task(void) {
|
||||
uint8_t buffer[XAP_EPSIZE];
|
||||
while (receive_report(USB_ENDPOINT_OUT_XAP, buffer, sizeof(buffer))) {
|
||||
xap_receive_base(buffer);
|
||||
}
|
||||
}
|
||||
#endif // XAP_ENABLE
|
||||
|
||||
#ifdef MIDI_ENABLE
|
||||
|
||||
void send_midi_packet(MIDI_EventPacket_t *event) {
|
||||
|
@ -64,6 +64,10 @@
|
||||
# include "raw_hid.h"
|
||||
#endif
|
||||
|
||||
#ifdef XAP_ENABLE
|
||||
# include "xap.h"
|
||||
#endif
|
||||
|
||||
#ifdef WAIT_FOR_USB
|
||||
// TODO: Remove backwards compatibility with old define
|
||||
# define USB_WAIT_FOR_ENUMERATION
|
||||
@ -179,6 +183,105 @@ void raw_hid_task(void) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef XAP_ENABLE
|
||||
extern void xap_receive(xap_token_t token, const uint8_t *data, size_t length);
|
||||
|
||||
void xap_send_base(uint8_t *data, uint8_t length) {
|
||||
// TODO: implement variable size packet
|
||||
if (length != XAP_EPSIZE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (USB_DeviceState != DEVICE_STATE_Configured) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: decide if we allow calls to raw_hid_send() in the middle
|
||||
// of other endpoint usage.
|
||||
uint8_t ep = Endpoint_GetCurrentEndpoint();
|
||||
|
||||
Endpoint_SelectEndpoint(XAP_IN_EPNUM);
|
||||
|
||||
// Check to see if the host is ready to accept another packet
|
||||
if (Endpoint_IsINReady()) {
|
||||
// Write data
|
||||
Endpoint_Write_Stream_LE(data, XAP_EPSIZE, NULL);
|
||||
// Finalize the stream transfer to send the last packet
|
||||
Endpoint_ClearIN();
|
||||
}
|
||||
|
||||
Endpoint_SelectEndpoint(ep);
|
||||
}
|
||||
|
||||
void xap_send(xap_token_t token, xap_response_flags_t response_flags, const void *data, size_t length) {
|
||||
uint8_t rdata[XAP_EPSIZE] = {0};
|
||||
xap_response_header_t *header = (xap_response_header_t *)&rdata[0];
|
||||
header->token = token;
|
||||
|
||||
if (length > (XAP_EPSIZE - sizeof(xap_response_header_t))) response_flags &= ~(XAP_RESPONSE_FLAG_SUCCESS);
|
||||
header->flags = response_flags;
|
||||
|
||||
if (response_flags & (XAP_RESPONSE_FLAG_SUCCESS)) {
|
||||
header->length = (uint8_t)length;
|
||||
if (data != NULL) {
|
||||
memcpy(&rdata[sizeof(xap_response_header_t)], data, length);
|
||||
}
|
||||
}
|
||||
xap_send_base(rdata, sizeof(rdata));
|
||||
}
|
||||
|
||||
void xap_broadcast(uint8_t type, const void *data, size_t length) {
|
||||
uint8_t rdata[XAP_EPSIZE] = {0};
|
||||
xap_broadcast_header_t *header = (xap_broadcast_header_t *)&rdata[0];
|
||||
header->token = XAP_BROADCAST_TOKEN;
|
||||
header->type = type;
|
||||
|
||||
if (length > (XAP_EPSIZE - sizeof(xap_broadcast_header_t))) return;
|
||||
|
||||
header->length = (uint8_t)length;
|
||||
if (data != NULL) {
|
||||
memcpy(&rdata[sizeof(xap_broadcast_header_t)], data, length);
|
||||
}
|
||||
xap_send_base(rdata, sizeof(rdata));
|
||||
}
|
||||
|
||||
void xap_receive_base(const void *data) {
|
||||
const uint8_t * u8data = (const uint8_t *)data;
|
||||
xap_request_header_t *header = (xap_request_header_t *)&u8data[0];
|
||||
if (header->length <= (XAP_EPSIZE - sizeof(xap_request_header_t))) {
|
||||
xap_receive(header->token, &u8data[sizeof(xap_request_header_t)], header->length);
|
||||
}
|
||||
}
|
||||
|
||||
void xap_task(void) {
|
||||
// Create a temporary buffer to hold the read in data from the host
|
||||
uint8_t data[XAP_EPSIZE];
|
||||
bool data_read = false;
|
||||
|
||||
// Device must be connected and configured for the task to run
|
||||
if (USB_DeviceState != DEVICE_STATE_Configured) return;
|
||||
|
||||
Endpoint_SelectEndpoint(XAP_OUT_EPNUM);
|
||||
|
||||
// Check to see if a packet has been sent from the host
|
||||
if (Endpoint_IsOUTReceived()) {
|
||||
// Check to see if the packet contains data
|
||||
if (Endpoint_IsReadWriteAllowed()) {
|
||||
/* Read data */
|
||||
Endpoint_Read_Stream_LE(data, sizeof(data), NULL);
|
||||
data_read = true;
|
||||
}
|
||||
|
||||
// Finalize the stream transfer to receive the last packet
|
||||
Endpoint_ClearOUT();
|
||||
|
||||
if (data_read) {
|
||||
xap_receive_base(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // XAP_ENABLE
|
||||
|
||||
/*******************************************************************************
|
||||
* Console
|
||||
******************************************************************************/
|
||||
@ -375,6 +478,12 @@ void EVENT_USB_Device_ConfigurationChanged(void) {
|
||||
ConfigSuccess &= Endpoint_ConfigureEndpoint((DIGITIZER_IN_EPNUM | ENDPOINT_DIR_IN), EP_TYPE_INTERRUPT, DIGITIZER_EPSIZE, 1);
|
||||
#endif
|
||||
|
||||
#ifdef XAP_ENABLE
|
||||
/* Setup XAP endpoints */
|
||||
ConfigSuccess &= Endpoint_ConfigureEndpoint((XAP_IN_EPNUM | ENDPOINT_DIR_IN), EP_TYPE_INTERRUPT, XAP_EPSIZE, 1);
|
||||
ConfigSuccess &= Endpoint_ConfigureEndpoint((XAP_OUT_EPNUM | ENDPOINT_DIR_OUT), EP_TYPE_INTERRUPT, XAP_EPSIZE, 1);
|
||||
#endif // XAP_ENABLE
|
||||
|
||||
usb_device_state_set_configuration(USB_DeviceState == DEVICE_STATE_Configured, USB_Device_ConfigurationNumber);
|
||||
}
|
||||
|
||||
|
@ -489,6 +489,30 @@ const USB_Descriptor_HIDReport_Datatype_t PROGMEM ConsoleReport[] = {
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef XAP_ENABLE
|
||||
const USB_Descriptor_HIDReport_Datatype_t PROGMEM XapReport[] = {
|
||||
HID_RI_USAGE_PAGE(16, 0xFF51), // Vendor Defined ('Q')
|
||||
HID_RI_USAGE(8, 0x58), // Vendor Defined ('X')
|
||||
HID_RI_COLLECTION(8, 0x01), // Application
|
||||
// Data to host
|
||||
HID_RI_USAGE(8, 0x62), // Vendor Defined
|
||||
HID_RI_LOGICAL_MINIMUM(8, 0x00),
|
||||
HID_RI_LOGICAL_MAXIMUM(16, 0x00FF),
|
||||
HID_RI_REPORT_COUNT(8, XAP_EPSIZE),
|
||||
HID_RI_REPORT_SIZE(8, 0x08),
|
||||
HID_RI_INPUT(8, HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE),
|
||||
|
||||
// Data from host
|
||||
HID_RI_USAGE(8, 0x63), // Vendor Defined
|
||||
HID_RI_LOGICAL_MINIMUM(8, 0x00),
|
||||
HID_RI_LOGICAL_MAXIMUM(16, 0x00FF),
|
||||
HID_RI_REPORT_COUNT(8, XAP_EPSIZE),
|
||||
HID_RI_REPORT_SIZE(8, 0x08),
|
||||
HID_RI_OUTPUT(8, HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE | HID_IOF_NON_VOLATILE),
|
||||
HID_RI_END_COLLECTION(0),
|
||||
};
|
||||
#endif // XAP_ENABLE
|
||||
|
||||
/*
|
||||
* Device descriptor
|
||||
*/
|
||||
@ -1097,6 +1121,56 @@ const USB_Descriptor_Configuration_t PROGMEM ConfigurationDescriptor = {
|
||||
.PollingIntervalMS = USB_POLLING_INTERVAL_MS
|
||||
},
|
||||
#endif
|
||||
|
||||
#ifdef XAP_ENABLE
|
||||
/*
|
||||
* QMK XAP
|
||||
*/
|
||||
.Xap_Interface = {
|
||||
.Header = {
|
||||
.Size = sizeof(USB_Descriptor_Interface_t),
|
||||
.Type = DTYPE_Interface
|
||||
},
|
||||
.InterfaceNumber = XAP_INTERFACE,
|
||||
.AlternateSetting = 0x00,
|
||||
.TotalEndpoints = 2,
|
||||
.Class = HID_CSCP_HIDClass,
|
||||
.SubClass = HID_CSCP_NonBootSubclass,
|
||||
.Protocol = HID_CSCP_NonBootProtocol,
|
||||
.InterfaceStrIndex = NO_DESCRIPTOR
|
||||
},
|
||||
.Xap_HID = {
|
||||
.Header = {
|
||||
.Size = sizeof(USB_HID_Descriptor_HID_t),
|
||||
.Type = HID_DTYPE_HID
|
||||
},
|
||||
.HIDSpec = VERSION_BCD(1, 1, 1),
|
||||
.CountryCode = 0x00,
|
||||
.TotalReportDescriptors = 1,
|
||||
.HIDReportType = HID_DTYPE_Report,
|
||||
.HIDReportLength = sizeof(XapReport)
|
||||
},
|
||||
.Xap_INEndpoint = {
|
||||
.Header = {
|
||||
.Size = sizeof(USB_Descriptor_Endpoint_t),
|
||||
.Type = DTYPE_Endpoint
|
||||
},
|
||||
.EndpointAddress = (ENDPOINT_DIR_IN | XAP_IN_EPNUM),
|
||||
.Attributes = (EP_TYPE_INTERRUPT | ENDPOINT_ATTR_NO_SYNC | ENDPOINT_USAGE_DATA),
|
||||
.EndpointSize = XAP_EPSIZE,
|
||||
.PollingIntervalMS = 0x01
|
||||
},
|
||||
.Xap_OUTEndpoint = {
|
||||
.Header = {
|
||||
.Size = sizeof(USB_Descriptor_Endpoint_t),
|
||||
.Type = DTYPE_Endpoint
|
||||
},
|
||||
.EndpointAddress = (ENDPOINT_DIR_OUT | XAP_OUT_EPNUM),
|
||||
.Attributes = (EP_TYPE_INTERRUPT | ENDPOINT_ATTR_NO_SYNC | ENDPOINT_USAGE_DATA),
|
||||
.EndpointSize = XAP_EPSIZE,
|
||||
.PollingIntervalMS = 0x01
|
||||
},
|
||||
#endif
|
||||
};
|
||||
|
||||
/*
|
||||
@ -1287,12 +1361,14 @@ uint16_t get_usb_descriptor(const uint16_t wValue, const uint16_t wIndex, const
|
||||
|
||||
break;
|
||||
#endif
|
||||
|
||||
#if defined(JOYSTICK_ENABLE) && !defined(JOYSTICK_SHARED_EP)
|
||||
case JOYSTICK_INTERFACE:
|
||||
Address = &ConfigurationDescriptor.Joystick_HID;
|
||||
Size = sizeof(USB_HID_Descriptor_HID_t);
|
||||
break;
|
||||
#endif
|
||||
|
||||
#if defined(DIGITIZER_ENABLE) && !defined(DIGITIZER_SHARED_EP)
|
||||
case DIGITIZER_INTERFACE:
|
||||
Address = &ConfigurationDescriptor.Digitizer_HID;
|
||||
@ -1300,6 +1376,14 @@ uint16_t get_usb_descriptor(const uint16_t wValue, const uint16_t wIndex, const
|
||||
|
||||
break;
|
||||
#endif
|
||||
|
||||
#ifdef XAP_ENABLE
|
||||
case XAP_INTERFACE:
|
||||
Address = &ConfigurationDescriptor.Xap_HID;
|
||||
Size = sizeof(USB_HID_Descriptor_HID_t);
|
||||
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
|
||||
break;
|
||||
@ -1356,6 +1440,14 @@ uint16_t get_usb_descriptor(const uint16_t wValue, const uint16_t wIndex, const
|
||||
Size = sizeof(DigitizerReport);
|
||||
break;
|
||||
#endif
|
||||
|
||||
#ifdef XAP_ENABLE
|
||||
case XAP_INTERFACE:
|
||||
Address = &XapReport;
|
||||
Size = sizeof(XapReport);
|
||||
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
|
||||
break;
|
||||
|
@ -144,6 +144,14 @@ typedef struct {
|
||||
USB_HID_Descriptor_HID_t Digitizer_HID;
|
||||
USB_Descriptor_Endpoint_t Digitizer_INEndpoint;
|
||||
#endif
|
||||
|
||||
#ifdef XAP_ENABLE
|
||||
// XAP HID Interface
|
||||
USB_Descriptor_Interface_t Xap_Interface;
|
||||
USB_HID_Descriptor_HID_t Xap_HID;
|
||||
USB_Descriptor_Endpoint_t Xap_INEndpoint;
|
||||
USB_Descriptor_Endpoint_t Xap_OUTEndpoint;
|
||||
#endif
|
||||
} USB_Descriptor_Configuration_t;
|
||||
|
||||
/*
|
||||
@ -193,6 +201,10 @@ enum usb_interfaces {
|
||||
#if defined(DIGITIZER_ENABLE) && !defined(DIGITIZER_SHARED_EP)
|
||||
DIGITIZER_INTERFACE,
|
||||
#endif
|
||||
|
||||
#ifdef XAP_ENABLE
|
||||
XAP_INTERFACE,
|
||||
#endif
|
||||
TOTAL_INTERFACES
|
||||
};
|
||||
|
||||
@ -269,6 +281,15 @@ enum usb_endpoints {
|
||||
# define DIGITIZER_IN_EPNUM SHARED_IN_EPNUM
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifdef XAP_ENABLE
|
||||
XAP_IN_EPNUM = NEXT_EPNUM,
|
||||
# if STM32_USB_USE_OTG1
|
||||
# define XAP_OUT_EPNUM XAP_IN_EPNUM
|
||||
# else
|
||||
XAP_OUT_EPNUM = NEXT_EPNUM,
|
||||
# endif
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifdef PROTOCOL_LUFA
|
||||
@ -280,7 +301,7 @@ enum usb_endpoints {
|
||||
#endif
|
||||
|
||||
#if (NEXT_EPNUM - 1) > MAX_ENDPOINTS
|
||||
# error There are not enough available endpoints to support all functions. Please disable one or more of the following: Mouse Keys, Extra Keys, Console, NKRO, MIDI, Serial, Steno
|
||||
# error There are not enough available endpoints to support all functions. Please disable one or more of the following: Mouse Keys, Extra Keys, Console, NKRO, MIDI, Serial, Steno, XAP
|
||||
#endif
|
||||
|
||||
#define KEYBOARD_EPSIZE 8
|
||||
@ -293,5 +314,6 @@ enum usb_endpoints {
|
||||
#define CDC_EPSIZE 16
|
||||
#define JOYSTICK_EPSIZE 8
|
||||
#define DIGITIZER_EPSIZE 8
|
||||
#define XAP_EPSIZE 64
|
||||
|
||||
uint16_t get_usb_descriptor(const uint16_t wValue, const uint16_t wIndex, const uint16_t wLength, const void** const DescriptorAddress);
|
||||
|
@ -41,6 +41,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
# include "joystick.h"
|
||||
#endif
|
||||
|
||||
#ifdef XAP_ENABLE
|
||||
# include "xap.h"
|
||||
# include <string.h>
|
||||
#endif
|
||||
|
||||
#if defined(CONSOLE_ENABLE)
|
||||
# define RBUF_SIZE 128
|
||||
# include "ring_buffer.h"
|
||||
@ -76,6 +81,10 @@ enum usb_interfaces {
|
||||
CONSOLE_INTERFACE,
|
||||
#endif
|
||||
|
||||
#ifdef XAP_ENABLE
|
||||
XAP_INTERFACE,
|
||||
#endif
|
||||
|
||||
TOTAL_INTERFACES
|
||||
};
|
||||
|
||||
@ -150,7 +159,7 @@ static void send_raw_hid(uint8_t *data, uint8_t length) {
|
||||
return;
|
||||
}
|
||||
|
||||
send_report(4, data, 32);
|
||||
send_report(4, data, RAW_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
void raw_hid_task(void) {
|
||||
@ -167,6 +176,80 @@ void raw_hid_task(void) {
|
||||
}
|
||||
#endif
|
||||
|
||||
/*------------------------------------------------------------------*
|
||||
* XAP
|
||||
*------------------------------------------------------------------*/
|
||||
#ifdef XAP_ENABLE
|
||||
# define XAP_BUFFER_SIZE 64
|
||||
# define XAP_EPSIZE 8
|
||||
|
||||
static uint8_t xap_output_buffer[XAP_BUFFER_SIZE];
|
||||
static uint8_t xap_output_received_bytes = 0;
|
||||
|
||||
extern void xap_receive(xap_token_t token, const uint8_t *data, size_t length);
|
||||
|
||||
void xap_send_base(uint8_t *data, uint8_t length) {
|
||||
if (length != XAP_BUFFER_SIZE) {
|
||||
return;
|
||||
}
|
||||
|
||||
send_report(4, data, XAP_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
void xap_send(xap_token_t token, xap_response_flags_t response_flags, const void *data, size_t length) {
|
||||
uint8_t rdata[XAP_BUFFER_SIZE] = {0};
|
||||
xap_response_header_t *header = (xap_response_header_t *)&rdata[0];
|
||||
header->token = token;
|
||||
|
||||
if (length > (XAP_BUFFER_SIZE - sizeof(xap_response_header_t))) response_flags &= ~(XAP_RESPONSE_FLAG_SUCCESS);
|
||||
header->flags = response_flags;
|
||||
|
||||
if (response_flags & (XAP_RESPONSE_FLAG_SUCCESS)) {
|
||||
header->length = (uint8_t)length;
|
||||
if (data != NULL) {
|
||||
memcpy(&rdata[sizeof(xap_response_header_t)], data, length);
|
||||
}
|
||||
}
|
||||
xap_send_base(rdata, sizeof(rdata));
|
||||
}
|
||||
|
||||
void xap_broadcast(uint8_t type, const void *data, size_t length) {
|
||||
uint8_t rdata[XAP_BUFFER_SIZE] = {0};
|
||||
xap_broadcast_header_t *header = (xap_broadcast_header_t *)&rdata[0];
|
||||
header->token = XAP_BROADCAST_TOKEN;
|
||||
header->type = type;
|
||||
|
||||
if (length > (XAP_BUFFER_SIZE - sizeof(xap_broadcast_header_t))) return;
|
||||
|
||||
header->length = (uint8_t)length;
|
||||
if (data != NULL) {
|
||||
memcpy(&rdata[sizeof(xap_broadcast_header_t)], data, length);
|
||||
}
|
||||
xap_send_base(rdata, sizeof(rdata));
|
||||
}
|
||||
|
||||
void xap_receive_base(const void *data) {
|
||||
const uint8_t * u8data = (const uint8_t *)data;
|
||||
xap_request_header_t *header = (xap_request_header_t *)&u8data[0];
|
||||
if (header->length <= (XAP_BUFFER_SIZE - sizeof(xap_request_header_t))) {
|
||||
xap_receive(header->token, &u8data[sizeof(xap_request_header_t)], header->length);
|
||||
}
|
||||
}
|
||||
|
||||
void xap_task(void) {
|
||||
usbPoll();
|
||||
|
||||
if (!usbConfiguration || !usbInterruptIsReady4()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (xap_output_received_bytes == XAP_BUFFER_SIZE) {
|
||||
xap_receive_base(xap_output_buffer);
|
||||
xap_output_received_bytes = 0;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/*------------------------------------------------------------------*
|
||||
* Console
|
||||
*------------------------------------------------------------------*/
|
||||
@ -382,6 +465,24 @@ void usbFunctionWriteOut(uchar *data, uchar len) {
|
||||
raw_output_received_bytes += len;
|
||||
}
|
||||
#endif
|
||||
#ifdef XAP_ENABLE
|
||||
// Data from host must be divided every 8bytes
|
||||
if (len != 8) {
|
||||
dprint("XAP: invalid length\n");
|
||||
xap_output_received_bytes = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (xap_output_received_bytes + len > XAP_BUFFER_SIZE) {
|
||||
dprint("XAP: buffer full\n");
|
||||
xap_output_received_bytes = 0;
|
||||
} else {
|
||||
for (uint8_t i = 0; i < 8; i++) {
|
||||
xap_output_buffer[xap_output_received_bytes + i] = data[i];
|
||||
}
|
||||
xap_output_received_bytes += len;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------*
|
||||
@ -787,6 +888,29 @@ const PROGMEM uchar console_hid_report[] = {
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef XAP_ENABLE
|
||||
const PROGMEM uchar xap_report[] = {
|
||||
0x06, 0x51, 0xFF, // Usage Page (Vendor Defined)
|
||||
0x09, 0x58, // Usage (Vendor Defined)
|
||||
0xA1, 0x01, // Collection (Application)
|
||||
// Data to host
|
||||
0x09, 0x62, // Usage (Vendor Defined)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x26, 0xFF, 0x00, // Logical Maximum (255)
|
||||
0x95, XAP_BUFFER_SIZE, // Report Count
|
||||
0x75, 0x08, // Report Size (8)
|
||||
0x81, 0x02, // Input (Data, Variable, Absolute)
|
||||
// Data from host
|
||||
0x09, 0x63, // Usage (Vendor Defined)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x26, 0xFF, 0x00, // Logical Maximum (255)
|
||||
0x95, XAP_BUFFER_SIZE, // Report Count
|
||||
0x75, 0x08, // Report Size (8)
|
||||
0x91, 0x02, // Output (Data, Variable, Absolute)
|
||||
0xC0 // End Collection
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifndef USB_MAX_POWER_CONSUMPTION
|
||||
# define USB_MAX_POWER_CONSUMPTION 500
|
||||
#endif
|
||||
@ -1051,6 +1175,56 @@ const PROGMEM usbConfigurationDescriptor_t usbConfigurationDescriptor = {
|
||||
.bInterval = 0x01
|
||||
},
|
||||
# endif
|
||||
|
||||
# if defined(XAP_ENABLE)
|
||||
/*
|
||||
* XAP
|
||||
*/
|
||||
.xapInterface = {
|
||||
.header = {
|
||||
.bLength = sizeof(usbInterfaceDescriptor_t),
|
||||
.bDescriptorType = USBDESCR_INTERFACE
|
||||
},
|
||||
.bInterfaceNumber = XAP_INTERFACE,
|
||||
.bAlternateSetting = 0x00,
|
||||
.bNumEndpoints = 2,
|
||||
.bInterfaceClass = 0x03,
|
||||
.bInterfaceSubClass = 0x00,
|
||||
.bInterfaceProtocol = 0x00,
|
||||
.iInterface = 0x00
|
||||
},
|
||||
.xapHID = {
|
||||
.header = {
|
||||
.bLength = sizeof(usbHIDDescriptor_t),
|
||||
.bDescriptorType = USBDESCR_HID
|
||||
},
|
||||
.bcdHID = 0x0101,
|
||||
.bCountryCode = 0x00,
|
||||
.bNumDescriptors = 1,
|
||||
.bDescriptorType = USBDESCR_HID_REPORT,
|
||||
.wDescriptorLength = sizeof(xap_report)
|
||||
},
|
||||
.xapINEndpoint = {
|
||||
.header = {
|
||||
.bLength = sizeof(usbEndpointDescriptor_t),
|
||||
.bDescriptorType = USBDESCR_ENDPOINT
|
||||
},
|
||||
.bEndpointAddress = (USBRQ_DIR_DEVICE_TO_HOST | USB_CFG_EP4_NUMBER),
|
||||
.bmAttributes = 0x03,
|
||||
.wMaxPacketSize = XAP_EPSIZE,
|
||||
.bInterval = USB_POLLING_INTERVAL_MS
|
||||
},
|
||||
.xapOUTEndpoint = {
|
||||
.header = {
|
||||
.bLength = sizeof(usbEndpointDescriptor_t),
|
||||
.bDescriptorType = USBDESCR_ENDPOINT
|
||||
},
|
||||
.bEndpointAddress = (USBRQ_DIR_HOST_TO_DEVICE | USB_CFG_EP4_NUMBER),
|
||||
.bmAttributes = 0x03,
|
||||
.wMaxPacketSize = XAP_EPSIZE,
|
||||
.bInterval = USB_POLLING_INTERVAL_MS
|
||||
},
|
||||
# endif
|
||||
};
|
||||
|
||||
// clang-format on
|
||||
@ -1121,6 +1295,13 @@ USB_PUBLIC usbMsgLen_t usbFunctionDescriptor(struct usbRequest *rq) {
|
||||
len = sizeof(usbHIDDescriptor_t);
|
||||
break;
|
||||
#endif
|
||||
|
||||
#if defined(XAP_ENABLE)
|
||||
case XAP_INTERFACE:
|
||||
usbMsgPtr = (usbMsgPtr_t)&usbConfigurationDescriptor.xapHID;
|
||||
len = sizeof(usbHIDDescriptor_t);
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
case USBDESCR_HID_REPORT:
|
||||
@ -1153,6 +1334,13 @@ USB_PUBLIC usbMsgLen_t usbFunctionDescriptor(struct usbRequest *rq) {
|
||||
len = sizeof(console_hid_report);
|
||||
break;
|
||||
#endif
|
||||
|
||||
#if defined(XAP_ENABLE)
|
||||
case XAP_INTERFACE:
|
||||
usbMsgPtr = (usbMsgPtr_t)xap_report;
|
||||
len = sizeof(xap_report);
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -115,6 +115,13 @@ typedef struct usbConfigurationDescriptor {
|
||||
usbHIDDescriptor_t consoleHID;
|
||||
usbEndpointDescriptor_t consoleINEndpoint;
|
||||
#endif
|
||||
|
||||
#if defined(XAP_ENABLE)
|
||||
usbInterfaceDescriptor_t xapInterface;
|
||||
usbHIDDescriptor_t xapHID;
|
||||
usbEndpointDescriptor_t xapINEndpoint;
|
||||
usbEndpointDescriptor_t xapOUTEndpoint;
|
||||
#endif
|
||||
} __attribute__((packed)) usbConfigurationDescriptor_t;
|
||||
|
||||
extern bool vusb_suspended;
|
||||
|
@ -1,6 +1,9 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
qmk generate-lighting-map -f rgblight -o quantum/rgblight/lighting_map.h
|
||||
qmk generate-lighting-map -f rgb_matrix -o quantum/rgb_matrix/lighting_map.h
|
||||
qmk generate-lighting-map -f led_matrix -o quantum/led_matrix/lighting_map.h
|
||||
qmk generate-rgb-breathe-table -o quantum/rgblight/rgblight_breathe_table.h
|
||||
qmk generate-keycodes --version latest -o quantum/keycodes.h
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user