This commit is contained in:
Nick Brassel 2025-07-16 13:57:09 +00:00 committed by GitHub
commit 7068a37f26
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
99 changed files with 7557 additions and 25 deletions

View File

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

View File

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

View File

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

@ -104,6 +104,7 @@ secrets.tar
# Python things
__pycache__
.python-version
*.egg-info
.venv
# Prerequisites for updating ChibiOS

View File

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

View 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"
}
}
}

View 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"
}
}
}

View 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"
}
}
}

View File

@ -0,0 +1,7 @@
{
"secure": {
"unlock_sequence": [ [0,0] ],
"unlock_timeout": 5000,
"idle_timeout": 60000
}
}

View File

@ -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
View 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"
}
}
}

View 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

View 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

View 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

View File

@ -0,0 +1,4 @@
{% for id, message in xap.broadcast_messages.messages | dictsort %}
### {{ message.name }} - `{{ id }}`
{{ message.description }}
{% endfor %}

View 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 %}

View 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 %}

View File

@ -0,0 +1,52 @@
{%- macro gen_payload(name, type, purpose, members) -%}
{%- if type == 'struct' -%}
__{{ name }}:__
{%- for member in members -%}
<br>{{ "&nbsp;"|safe*4 }}* {{ member.name }}: `{{ member.type }}`
{%- endfor -%}
{%- elif purpose -%}
__{{ name }}:__<br>{{ "&nbsp;"|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 %}

View 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 %}

View File

@ -0,0 +1,5 @@
| Name | Definition |
| -- | -- |
{% for type, definition in xap.type_docs | dictsort %}
| _{{ type }}_ | {{ definition }} |
{% endfor %}

View 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 %}

View 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) }}

View 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
View 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
View 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`(&nbsp;) | `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
View 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
View 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
}
}
}
}
}

View File

@ -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
View 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
View 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
View 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>&nbsp;&nbsp;&nbsp;&nbsp;* Vendor ID: `u16`<br>&nbsp;&nbsp;&nbsp;&nbsp;* Product ID: `u16`<br>&nbsp;&nbsp;&nbsp;&nbsp;* Product Version: `u16`<br>&nbsp;&nbsp;&nbsp;&nbsp;* 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`(&nbsp;) | `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
View 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>&nbsp;&nbsp;&nbsp;&nbsp;* Vendor ID: `u16`<br>&nbsp;&nbsp;&nbsp;&nbsp;* Product ID: `u16`<br>&nbsp;&nbsp;&nbsp;&nbsp;* Product Version: `u16`<br>&nbsp;&nbsp;&nbsp;&nbsp;* 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>&nbsp;&nbsp;&nbsp;&nbsp;* Layer: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* Row: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* Column: `u8`<br><br>__Response:__ `u16` | Query the Keycode at the requested location.|
| Get Encoder Keycode | `0x04 0x04` | | __Request:__<br>&nbsp;&nbsp;&nbsp;&nbsp;* Layer: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* Encoder: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* 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>&nbsp;&nbsp;&nbsp;&nbsp;* Layer: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* Row: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* Column: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* Keycode: `u16` | Modify the Keycode at the requested location.|
| Set Encoder Keycode | `0x05 0x04` | __Secure__ | __Request:__<br>&nbsp;&nbsp;&nbsp;&nbsp;* Layer: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* Encoder: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* Clockwise: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* 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`(&nbsp;) | `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
View 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>&nbsp;&nbsp;&nbsp;&nbsp;* Vendor ID: `u16`<br>&nbsp;&nbsp;&nbsp;&nbsp;* Product ID: `u16`<br>&nbsp;&nbsp;&nbsp;&nbsp;* Product Version: `u16`<br>&nbsp;&nbsp;&nbsp;&nbsp;* 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>&nbsp;&nbsp;&nbsp;&nbsp;* Layer: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* Row: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* Column: `u8`<br><br>__Response:__ `u16` | Query the Keycode at the requested location.|
| Get Encoder Keycode | `0x04 0x04` | | __Request:__<br>&nbsp;&nbsp;&nbsp;&nbsp;* Layer: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* Encoder: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* 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>&nbsp;&nbsp;&nbsp;&nbsp;* Layer: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* Row: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* Column: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* Keycode: `u16` | Modify the Keycode at the requested location.|
| Set Encoder Keycode | `0x05 0x04` | __Secure__ | __Request:__<br>&nbsp;&nbsp;&nbsp;&nbsp;* Layer: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* Encoder: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* Clockwise: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* 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>&nbsp;&nbsp;&nbsp;&nbsp;* enable: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* mode: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* val: `u8` | Query the current config.|
| Set Config | `0x06 0x02 0x04` | | __Request:__<br>&nbsp;&nbsp;&nbsp;&nbsp;* enable: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* mode: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* 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>&nbsp;&nbsp;&nbsp;&nbsp;* enable: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* mode: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* hue: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* sat: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* val: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* speed: `u8` | Query the current config.|
| Set Config | `0x06 0x03 0x04` | | __Request:__<br>&nbsp;&nbsp;&nbsp;&nbsp;* enable: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* mode: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* hue: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* sat: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* val: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* 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>&nbsp;&nbsp;&nbsp;&nbsp;* enable: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* mode: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* hue: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* sat: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* val: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* speed: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* flags: `u8` | Query the current config.|
| Set Config | `0x06 0x04 0x04` | | __Request:__<br>&nbsp;&nbsp;&nbsp;&nbsp;* enable: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* mode: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* hue: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* sat: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* val: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* speed: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* 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>&nbsp;&nbsp;&nbsp;&nbsp;* enable: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* clicky_enable: `u8` | Query the current config.|
| Set Config | `0x07 0x04` | | __Request:__<br>&nbsp;&nbsp;&nbsp;&nbsp;* enable: `u8`<br>&nbsp;&nbsp;&nbsp;&nbsp;* 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`(&nbsp;) | `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
View 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)

View 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);
}
}

View File

@ -0,0 +1,2 @@
ENCODER_MAP_ENABLE = yes
XAP_ENABLE = yes

View 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
}
}
}

View 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
View 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)])

View File

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

View File

@ -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.')

View File

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

View File

@ -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=(',', ':'))

View 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)

View File

@ -0,0 +1 @@
from .xap import xap

View 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)

View 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))

View 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)

View 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)

View 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))

View File

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

View File

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

View File

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

View File

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

View 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

View File

140
lib/python/qmk/xap/common.py Executable file
View 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

View 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)

View 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

View 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

View 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)

View 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

View 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

View 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' = '.' }

View 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

View 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

View 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

View File

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

View File

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

View File

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

View 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;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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;
}

View 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;
}

View 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

View 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));
}

View 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

View 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
View 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
View 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);

View 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"

View File

@ -3,8 +3,10 @@
argcomplete
colorama
dotty-dict
fnvhash
hid
hjson
Jinja2
jsonschema>=4
milc>=1.9.0
pygments

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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