Compare commits
302 commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a4c96e446c | ||
![]() |
236ac7cbd1 | ||
![]() |
ea1046948d | ||
![]() |
cd0c15f887 | ||
![]() |
6c4c64f147 | ||
![]() |
f7e436b91c | ||
![]() |
0be699dde6 | ||
![]() |
bce04e6318 | ||
![]() |
54ce775fff | ||
![]() |
9c0937da42 | ||
![]() |
0d1f9d1c8c | ||
![]() |
4c49aaaa70 | ||
![]() |
3ea9154b0a | ||
![]() |
6b5a62e15f | ||
![]() |
8ed1bec0a6 | ||
![]() |
1b63938411 | ||
![]() |
a8cbda7b8a | ||
![]() |
cb7d103ba8 | ||
![]() |
68208278e0 | ||
![]() |
87106e44c8 | ||
![]() |
3d9c2fd845 | ||
![]() |
f88ceb5164 | ||
![]() |
690bca2672 | ||
![]() |
f9f0d84eb0 | ||
![]() |
ea850f1380 | ||
![]() |
5028d6672a | ||
![]() |
41a5dcbfa7 | ||
![]() |
ea92d5ed7d | ||
![]() |
218bd48ebc | ||
![]() |
87b099795c | ||
![]() |
1a13c379ac | ||
![]() |
3cef0d587a | ||
![]() |
df9f15737b | ||
![]() |
c81feb6248 | ||
![]() |
7e819d7945 | ||
![]() |
58642ff40c | ||
![]() |
a2604fd80f | ||
![]() |
de0d6378bc | ||
![]() |
94ec23ea77 | ||
![]() |
cc851142fa | ||
![]() |
c65ec90484 | ||
![]() |
01cd1ac71f | ||
![]() |
c01e8ed75d | ||
![]() |
de45a60432 | ||
![]() |
519ba9be0c | ||
![]() |
72156de175 | ||
![]() |
5a11b4593b | ||
![]() |
c1185d3905 | ||
![]() |
f54dcc7962 | ||
![]() |
d1ec7f2e0c | ||
![]() |
3cc8bcb936 | ||
![]() |
4d9a611780 | ||
![]() |
74880dd26e | ||
![]() |
b5df55448b | ||
![]() |
7e2ece2409 | ||
![]() |
1a9f4eb665 | ||
![]() |
05195af26f | ||
![]() |
f833e34bd0 | ||
![]() |
e7cae3949e | ||
![]() |
496da4267e | ||
![]() |
db9a12c048 | ||
![]() |
5066e8c0d5 | ||
![]() |
90f38c1b24 | ||
![]() |
27549e534f | ||
![]() |
01b831d3da | ||
![]() |
de85113520 | ||
![]() |
90420b1e49 | ||
![]() |
c8ac96c768 | ||
![]() |
8e1a690079 | ||
![]() |
24d394a2aa | ||
![]() |
2e387d2347 | ||
![]() |
20aefbdb77 | ||
![]() |
1bfd694fd8 | ||
![]() |
2483a049aa | ||
![]() |
5900caa877 | ||
![]() |
da18279390 | ||
![]() |
940c53f420 | ||
![]() |
c6e38f46d9 | ||
![]() |
aae9ffea0c | ||
![]() |
6af289bdde | ||
![]() |
3706574002 | ||
![]() |
317e6a3f2a | ||
![]() |
c1923a7c62 | ||
![]() |
a07d10f5a9 | ||
![]() |
3d92e04851 | ||
![]() |
f550113f25 | ||
![]() |
9f9f42848d | ||
![]() |
85bf9c1aa9 | ||
![]() |
e486dc9278 | ||
![]() |
7d83445309 | ||
![]() |
4d895892e5 | ||
![]() |
1306d58774 | ||
![]() |
a49a2e3f34 | ||
![]() |
68c8e61c6a | ||
![]() |
bcd9b23a65 | ||
![]() |
f7f9a9affe | ||
![]() |
e69de0637a | ||
![]() |
620c4e6eb0 | ||
![]() |
9ffd4505a0 | ||
![]() |
d3a4adefc4 | ||
![]() |
3a8ea8a683 | ||
![]() |
6a4de6a962 | ||
![]() |
34ff3ed1ba | ||
![]() |
a0ebf624f1 | ||
![]() |
0c153a4751 | ||
![]() |
e1ccd6cbd2 | ||
![]() |
a80d0036f3 | ||
![]() |
e95ce2fab4 | ||
![]() |
97b1d49015 | ||
![]() |
b6f451194e | ||
![]() |
bf9facddc5 | ||
![]() |
9145458eb6 | ||
![]() |
892f88e7d1 | ||
![]() |
0df5dae0f6 | ||
![]() |
5616513800 | ||
![]() |
abec2e9052 | ||
![]() |
d17aed8e82 | ||
![]() |
3730ddacac | ||
![]() |
1d96fc866d | ||
![]() |
81cce42118 | ||
![]() |
84c3879435 | ||
![]() |
36bc8c0c1a | ||
![]() |
e28727d8ba | ||
![]() |
bdfe1470ab | ||
![]() |
4cfcd51acb | ||
![]() |
3d43baf3f2 | ||
![]() |
9f78fb69e4 | ||
![]() |
1f212894e1 | ||
![]() |
5af8b47bb9 | ||
![]() |
2e3fe5f1b2 | ||
![]() |
f6b7c2ebee | ||
![]() |
6f134870ab | ||
![]() |
b4dc49601e | ||
![]() |
a608afdcf4 | ||
![]() |
b0fbcdd8a4 | ||
![]() |
379dac6ade | ||
![]() |
3c20f00238 | ||
![]() |
c79bfc6ed9 | ||
![]() |
78acf2c1a1 | ||
![]() |
425b80ddd6 | ||
![]() |
331b232ba1 | ||
![]() |
37f92fad65 | ||
![]() |
3f0c7f1921 | ||
![]() |
ff198b384b | ||
![]() |
fcb549f36c | ||
![]() |
3503d1c27b | ||
![]() |
f951028c94 | ||
![]() |
7f4f38dcac | ||
![]() |
c6511ad9bc | ||
![]() |
6add5a8720 | ||
![]() |
85331d56ec | ||
![]() |
e7af9fb808 | ||
![]() |
af26928941 | ||
![]() |
858a55e5d8 | ||
![]() |
3cbf1e455f | ||
![]() |
7d023e4951 | ||
![]() |
1d8ae78806 | ||
![]() |
c1f92cc536 | ||
![]() |
b3bce44385 | ||
![]() |
cd8329ae9f | ||
![]() |
1c13c5885c | ||
![]() |
7107489242 | ||
![]() |
e59b61e8af | ||
![]() |
b0b6594ded | ||
![]() |
2563e7b2a0 | ||
![]() |
f249e33f70 | ||
![]() |
12e8c8d8ee | ||
![]() |
320f161c72 | ||
![]() |
d19285019d | ||
![]() |
b1eab6d3a0 | ||
![]() |
d21f84c521 | ||
![]() |
90429d9657 | ||
![]() |
1ea2928d2a | ||
![]() |
ffb0575eb8 | ||
![]() |
05b5a4c23a | ||
![]() |
79db2df228 | ||
![]() |
3e4de1ebd0 | ||
![]() |
89fab427c4 | ||
![]() |
2c068d08dd | ||
![]() |
aefe98bfea | ||
![]() |
5e33ca8f7c | ||
![]() |
9ac3445454 | ||
![]() |
66beedde80 | ||
![]() |
8d67b9bcc5 | ||
![]() |
9c4119bc15 | ||
![]() |
02b7726323 | ||
![]() |
98b668c675 | ||
![]() |
2de248fc06 | ||
![]() |
af5c3c948a | ||
![]() |
3be420fd9b | ||
![]() |
18aa0d2d19 | ||
![]() |
194d6d65be | ||
![]() |
d339f182ba | ||
![]() |
c1b57354f6 | ||
![]() |
0d59f8b42d | ||
![]() |
2abb01084f | ||
![]() |
4de0f7d9b5 | ||
![]() |
b87f85a81a | ||
![]() |
f0daafb741 | ||
![]() |
99d097de97 | ||
![]() |
e3afa07ca9 | ||
![]() |
df823f8306 | ||
![]() |
567949de19 | ||
![]() |
6a026f8653 | ||
![]() |
72ea4cf574 | ||
![]() |
715da89a6e | ||
![]() |
c9eae1d384 | ||
![]() |
e7d9d6675c | ||
![]() |
d968e6c005 | ||
![]() |
c5842ab9b5 | ||
![]() |
e111b9d017 | ||
![]() |
2c8c9c9928 | ||
![]() |
53052228df | ||
![]() |
ffcdfc6c03 | ||
![]() |
81a53ac5b6 | ||
![]() |
0f5ced0521 | ||
![]() |
fe1a4a52d4 | ||
![]() |
646fdc7d17 | ||
![]() |
73d2228524 | ||
![]() |
6269c6b51c | ||
![]() |
5a099e1ad7 | ||
![]() |
43d6473e00 | ||
![]() |
51e09235a2 | ||
![]() |
7f128c5286 | ||
![]() |
22b8299230 | ||
![]() |
a65ea1a711 | ||
![]() |
13ee88dd21 | ||
![]() |
578fe2d029 | ||
![]() |
56c9f7b7ff | ||
![]() |
05911e9908 | ||
![]() |
7262333857 | ||
![]() |
ff1bb76537 | ||
![]() |
1fbbd72c14 | ||
![]() |
780526848b | ||
![]() |
fc45ff1f07 | ||
![]() |
933888780e | ||
![]() |
80102c0e83 | ||
![]() |
15e0964108 | ||
![]() |
620716b106 | ||
![]() |
cabdab5e75 | ||
![]() |
9c4e294887 | ||
![]() |
d6f2f02428 | ||
![]() |
6a9f0961d6 | ||
![]() |
c325737d9c | ||
![]() |
3d4575a06d | ||
![]() |
4d28978dee | ||
![]() |
728305e961 | ||
![]() |
e31c605bf7 | ||
![]() |
f872fbea7e | ||
![]() |
6281446aee | ||
![]() |
0dba850d43 | ||
![]() |
9ad318efc6 | ||
![]() |
c5e26a8040 | ||
![]() |
9ed471fd33 | ||
![]() |
b365cbce15 | ||
![]() |
29f349b90b | ||
![]() |
ded6a379b2 | ||
![]() |
5df35467b4 | ||
![]() |
8dfe1134cf | ||
![]() |
dd2c2bfa97 | ||
![]() |
3cb3d5b0c9 | ||
![]() |
b73f5d3da6 | ||
![]() |
70c9905cb6 | ||
![]() |
a5204887a8 | ||
![]() |
c27edf4e64 | ||
![]() |
aaf4fcbe5a | ||
![]() |
b73a0b14aa | ||
![]() |
aff373bab2 | ||
![]() |
4d4b013e5b | ||
![]() |
5bb6173cc7 | ||
![]() |
e5e1e54f39 | ||
![]() |
72602a3443 | ||
![]() |
2e8db66201 | ||
![]() |
46256e08eb | ||
![]() |
770c7ab20e | ||
![]() |
31c4864705 | ||
![]() |
68efb1635c | ||
![]() |
9fd4db1fc7 | ||
![]() |
5b2db70d65 | ||
![]() |
7301950bcf | ||
![]() |
d5b6b82361 | ||
![]() |
1bb00c1609 | ||
![]() |
52d3b9dcc5 | ||
![]() |
cddcd8d735 | ||
![]() |
7e8f0d49ea | ||
![]() |
7183516a2b | ||
![]() |
2b4724bd83 | ||
![]() |
b96b862ef9 | ||
![]() |
c3ac89d1c9 | ||
![]() |
575d8c19fc | ||
![]() |
6c7afbb859 | ||
![]() |
69e9c80ec3 | ||
![]() |
1e723e6647 | ||
![]() |
c9ec8a1309 | ||
![]() |
dcf4bf6d29 | ||
![]() |
bf66b91433 | ||
![]() |
ec9a78cc4a | ||
![]() |
942d9f6a09 | ||
![]() |
3c66b9b0ec | ||
![]() |
5aae5a767f | ||
![]() |
437559cd03 | ||
![]() |
eba91c6e28 |
55 changed files with 5177 additions and 41 deletions
|
@ -322,12 +322,18 @@ ifneq ("$(wildcard $(KEYBOARD_PATH_5)/info.json)","")
|
|||
endif
|
||||
|
||||
CONFIG_H += $(KEYBOARD_OUTPUT)/src/info_config.h $(KEYBOARD_OUTPUT)/src/layouts.h
|
||||
KEYBOARD_SRC += $(KEYBOARD_OUTPUT)/src/default_keyboard.c
|
||||
|
||||
$(KEYBOARD_OUTPUT)/src/info_config.h: $(INFO_JSON_FILES)
|
||||
@$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD)
|
||||
$(eval CMD=$(QMK_BIN) generate-config-h --quiet --keyboard $(KEYBOARD) --output $(KEYBOARD_OUTPUT)/src/info_config.h)
|
||||
@$(BUILD_CMD)
|
||||
|
||||
$(KEYBOARD_OUTPUT)/src/default_keyboard.c: $(INFO_JSON_FILES)
|
||||
@$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD)
|
||||
$(eval CMD=$(QMK_BIN) generate-keyboard-c --quiet --keyboard $(KEYBOARD) --output $(KEYBOARD_OUTPUT)/src/default_keyboard.c)
|
||||
@$(BUILD_CMD)
|
||||
|
||||
$(KEYBOARD_OUTPUT)/src/default_keyboard.h: $(INFO_JSON_FILES)
|
||||
@$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD)
|
||||
$(eval CMD=$(QMK_BIN) generate-keyboard-h --quiet --keyboard $(KEYBOARD) --output $(KEYBOARD_OUTPUT)/src/default_keyboard.h)
|
||||
|
@ -338,7 +344,7 @@ $(KEYBOARD_OUTPUT)/src/layouts.h: $(INFO_JSON_FILES)
|
|||
$(eval CMD=$(QMK_BIN) generate-layouts --quiet --keyboard $(KEYBOARD) --output $(KEYBOARD_OUTPUT)/src/layouts.h)
|
||||
@$(BUILD_CMD)
|
||||
|
||||
generated-files: $(KEYBOARD_OUTPUT)/src/info_config.h $(KEYBOARD_OUTPUT)/src/default_keyboard.h $(KEYBOARD_OUTPUT)/src/layouts.h
|
||||
generated-files: $(KEYBOARD_OUTPUT)/src/info_config.h $(KEYBOARD_OUTPUT)/src/default_keyboard.c $(KEYBOARD_OUTPUT)/src/default_keyboard.h $(KEYBOARD_OUTPUT)/src/layouts.h
|
||||
|
||||
.INTERMEDIATE : generated-files
|
||||
|
||||
|
|
|
@ -45,6 +45,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)
|
||||
|
@ -797,6 +801,19 @@ ifeq ($(strip $(USBPD_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
|
||||
|
||||
OPT_DEFS += -DXAP_ENABLE
|
||||
DYNAMIC_KEYMAP_ENABLE := yes
|
||||
SECURE_ENABLE := yes
|
||||
EMBED_INFO_JSON := yes
|
||||
VPATH += $(QUANTUM_DIR)/xap
|
||||
SRC += $(QUANTUM_DIR)/xap/xap.c $(QUANTUM_DIR)/xap/xap_handlers.c
|
||||
endif
|
||||
|
||||
BLUETOOTH_ENABLE ?= no
|
||||
VALID_BLUETOOTH_DRIVER_TYPES := BluefruitLE RN42 custom
|
||||
ifeq ($(strip $(BLUETOOTH_ENABLE)), yes)
|
||||
|
|
24
builddefs/xap.mk
Normal file
24
builddefs/xap.mk
Normal file
|
@ -0,0 +1,24 @@
|
|||
# Copyright 2022 Nick Brassel (@tzarc)
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
# XAP embedded info.json
|
||||
$(KEYMAP_OUTPUT)/src/info_json_gz.h: $(INFO_JSON_FILES)
|
||||
@$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD)
|
||||
$(eval CMD=$(QMK_BIN) xap-generate-info-h -o "$(KEYMAP_OUTPUT)/src/info_json_gz.h" -kb $(KEYBOARD) -km $(KEYMAP))
|
||||
@$(BUILD_CMD)
|
||||
|
||||
XAP_FILES := $(shell ls -1 data/xap/* | sort | xargs echo)
|
||||
|
||||
$(KEYMAP_OUTPUT)/src/xap_generated.inl: $(XAP_FILES)
|
||||
@$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD)
|
||||
$(eval CMD=$(QMK_BIN) xap-generate-qmk-inc -o "$(KEYMAP_OUTPUT)/src/xap_generated.inl")
|
||||
@$(BUILD_CMD)
|
||||
|
||||
$(KEYMAP_OUTPUT)/src/xap_generated.h: $(XAP_FILES)
|
||||
@$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD)
|
||||
$(eval CMD=$(QMK_BIN) xap-generate-qmk-h -o "$(KEYMAP_OUTPUT)/src/xap_generated.h" -kb $(KEYBOARD))
|
||||
@$(BUILD_CMD)
|
||||
|
||||
generated-files: $(KEYMAP_OUTPUT)/src/info_json_gz.h $(KEYMAP_OUTPUT)/src/xap_generated.inl $(KEYMAP_OUTPUT)/src/xap_generated.h
|
||||
|
||||
VPATH += $(KEYMAP_OUTPUT)/src
|
1510
data/constants/keycodes_0.0.1.json
Normal file
1510
data/constants/keycodes_0.0.1.json
Normal file
File diff suppressed because it is too large
Load diff
|
@ -202,6 +202,62 @@
|
|||
"timeout": {"$ref": "qmk.definitions.v1#/unsigned_int"}
|
||||
}
|
||||
},
|
||||
"led_matrix": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"driver": {"type": "string"},
|
||||
"layout": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"matrix": {
|
||||
"type": "array",
|
||||
"minItems": 2,
|
||||
"maxItems": 2,
|
||||
"items": {
|
||||
"type": "number",
|
||||
"min": 0,
|
||||
"multipleOf": 1
|
||||
}
|
||||
},
|
||||
"x": {"$ref": "qmk.definitions.v1#/key_unit"},
|
||||
"y": {"$ref": "qmk.definitions.v1#/key_unit"},
|
||||
"flags": {"$ref": "qmk.definitions.v1#/unsigned_decimal"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"rgb_matrix": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"driver": {"type": "string"},
|
||||
"layout": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"matrix": {
|
||||
"type": "array",
|
||||
"minItems": 2,
|
||||
"maxItems": 2,
|
||||
"items": {
|
||||
"type": "number",
|
||||
"min": 0,
|
||||
"multipleOf": 1
|
||||
}
|
||||
},
|
||||
"x": {"$ref": "qmk.definitions.v1#/key_unit"},
|
||||
"y": {"$ref": "qmk.definitions.v1#/key_unit"},
|
||||
"flags": {"$ref": "qmk.definitions.v1#/unsigned_decimal"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"rgblight": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
|
|
7
data/templates/xap/docs/docs.md.j2
Normal file
7
data/templates/xap/docs/docs.md.j2
Normal file
|
@ -0,0 +1,7 @@
|
|||
{%- for item in xap.documentation.order -%}
|
||||
{%- if not item[0:1] == '!' -%}
|
||||
{{ xap.documentation.get(item) }}
|
||||
{% else %}
|
||||
{%- include item[1:] %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
9
data/templates/xap/docs/response_flags.md.j2
Normal file
9
data/templates/xap/docs/response_flags.md.j2
Normal file
|
@ -0,0 +1,9 @@
|
|||
|{% 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 %}
|
8
data/templates/xap/docs/term_definitions.md.j2
Normal file
8
data/templates/xap/docs/term_definitions.md.j2
Normal file
|
@ -0,0 +1,8 @@
|
|||
| Name | Definition |
|
||||
| -- | -- |
|
||||
{%- for type, definition in xap.term_definitions | dictsort %}
|
||||
| _{{ type }}_ | {{ definition }} |
|
||||
{%- endfor %}
|
||||
{%- for type, definition in xap.type_definitions | dictsort %}
|
||||
| _{{ definition.name }}_ | {{ definition.description }}{% if 'struct' == definition.type %} Takes the format:{% for item in definition.struct_members %}<br>`{{ item.type }}` - {{ item.name }}{%- endfor %}{% endif %} |
|
||||
{%- endfor %}
|
5
data/templates/xap/docs/type_docs.md.j2
Normal file
5
data/templates/xap/docs/type_docs.md.j2
Normal file
|
@ -0,0 +1,5 @@
|
|||
| Name | Definition |
|
||||
| -- | -- |
|
||||
{%- for type, definition in xap.type_docs | dictsort %}
|
||||
| _{{ type }}_ | {{ definition }} |
|
||||
{%- endfor %}
|
224
data/xap/xap_0.0.1.hjson
Executable file
224
data/xap/xap_0.0.1.hjson
Executable file
|
@ -0,0 +1,224 @@
|
|||
{
|
||||
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
|
||||
]
|
||||
|
||||
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, allowing 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.
|
||||
|
||||
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` |
|
||||
'''
|
||||
}
|
||||
|
||||
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.
|
||||
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}.
|
||||
* Response:
|
||||
* `u32` value.
|
||||
'''
|
||||
return_type: u32
|
||||
return_purpose: bcd-version
|
||||
return_constant: XAP_BCD_VERSION
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
361
data/xap/xap_0.1.0.hjson
Executable file
361
data/xap/xap_0.1.0.hjson
Executable file
|
@ -0,0 +1,361 @@
|
|||
{
|
||||
version: 0.1.0
|
||||
|
||||
uses: {
|
||||
keycodes: 0.0.1
|
||||
}
|
||||
|
||||
documentation: {
|
||||
order: [
|
||||
broadcast_messages
|
||||
]
|
||||
|
||||
reserved_tokens:
|
||||
'''
|
||||
Two token values are reserved: `0x0000` and `0xFFFF`:
|
||||
* `0x0000`: 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, with 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.
|
||||
'''
|
||||
}
|
||||
6: {
|
||||
name: Unlocking
|
||||
define: UNLOCK_IN_PROGRESS
|
||||
description:
|
||||
'''
|
||||
When this bit is set, an _unlock sequence_ is in progress.
|
||||
'''
|
||||
}
|
||||
7: {
|
||||
name: Unlocked
|
||||
define: UNLOCKED
|
||||
description:
|
||||
'''
|
||||
When this bit is set, an _unlock sequence_ has completed, and _secure routes_ may be invoked.
|
||||
'''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type_docs: {
|
||||
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 a `u8` signifying the length of the text, followed by the `u8[Length]` containing the text itself.
|
||||
|
||||
**Example Log Broadcast** -- log message "Hello QMK!"
|
||||
| Byte | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| **Purpose** | Token | Token | Broadcast Type | Length | Payload | Payload | Payload | Payload | Payload | Payload | Payload | Payload | Payload | Payload |
|
||||
| **Value** | `0xFF` | `0xFF` | `0x00` | `0x0A`(10) | `0x48`(H) | `0x65`(e) | `0x6C`(l) | `0x6C`(l) | `0x6F`(o) | `0x20`( ) | `0x51`(Q) | `0x4D`(M) | `0x4B`(K) | `0x21`(!) |
|
||||
'''
|
||||
}
|
||||
0x01: {
|
||||
name: Secure Status
|
||||
define: SECURE_STATUS
|
||||
description:
|
||||
'''
|
||||
Secure status has changed.
|
||||
'''
|
||||
return_type: u8
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
'''
|
||||
permissions: ignore
|
||||
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
|
||||
permissions: ignore
|
||||
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}.
|
||||
* Response:
|
||||
* `u32` value.
|
||||
'''
|
||||
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: info.json length
|
||||
define: INFO_LEN_QUERY
|
||||
description: Retrieves the length of info.json
|
||||
return_type: u32
|
||||
return_constant: INFO_JSON_GZ_LEN
|
||||
}
|
||||
0x06: {
|
||||
type: command
|
||||
name: info.json
|
||||
define: INFO_QUERY
|
||||
description: Retrieves a chunk of info.json
|
||||
request_type: u16
|
||||
request_purpose: offset
|
||||
return_type: u8[32]
|
||||
return_execute: get_info_json_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: info.json
|
||||
define: HARDWARE_ID
|
||||
description: Retrieves a unique identifier for the board.
|
||||
return_type: u32[4]
|
||||
return_execute: get_hardware_id
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
0x02: {
|
||||
type: router
|
||||
name: Keyboard
|
||||
define: KB
|
||||
description:
|
||||
'''
|
||||
This subsystem is always present, and reserved for user-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: {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
190
data/xap/xap_0.2.0.hjson
Executable file
190
data/xap/xap_0.2.0.hjson
Executable file
|
@ -0,0 +1,190 @@
|
|||
{
|
||||
version: 0.2.0
|
||||
|
||||
routes: {
|
||||
0x04: {
|
||||
type: router
|
||||
name: Dynamic Keymap
|
||||
define: DYNAMIC_KEYMAP
|
||||
description:
|
||||
'''
|
||||
This subsystem allows for live modifications of the keymap, allowing keys to be reassigned without rebuilding the firmware.
|
||||
'''
|
||||
enable_if_preprocessor: defined(DYNAMIC_KEYMAP_ENABLE)
|
||||
routes: {
|
||||
0x00: {
|
||||
type: command
|
||||
name: Capabilities Query
|
||||
define: CAPABILITIES_QUERY
|
||||
description:
|
||||
'''
|
||||
Dynamic 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_DYNAMIC_KEYMAP_CAPABILITIES
|
||||
}
|
||||
0x01: {
|
||||
type: command
|
||||
name: Get Layer Count
|
||||
define: GET_LAYER_COUNT
|
||||
description: TODO
|
||||
return_type: u8
|
||||
return_constant: DYNAMIC_KEYMAP_LAYER_COUNT
|
||||
}
|
||||
0x02: {
|
||||
type: command
|
||||
name: Get Keycode
|
||||
define: GET_KEYMAP_KEYCODE
|
||||
description: TODO
|
||||
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: dynamic_keymap_get_keycode
|
||||
}
|
||||
0x03: {
|
||||
type: command
|
||||
name: Set Keycode
|
||||
define: SET_KEYMAP_KEYCODE
|
||||
description: TODO
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
0x05: {
|
||||
type: router
|
||||
name: Dynamic Encoders
|
||||
define: DYNAMIC_ENCODER
|
||||
description:
|
||||
'''
|
||||
This subsystem allows for live modifications of the keymap, allowing encoder functionality to be reassigned without rebuilding the firmware.
|
||||
'''
|
||||
enable_if_preprocessor: defined(DYNAMIC_KEYMAP_ENABLE) && defined(ENCODER_MAP_ENABLE)
|
||||
routes: {
|
||||
0x00: {
|
||||
type: command
|
||||
name: Capabilities Query
|
||||
define: CAPABILITIES_QUERY
|
||||
description:
|
||||
'''
|
||||
Dynamic Encoders 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_DYNAMIC_ENCODER_CAPABILITIES
|
||||
}
|
||||
0x02: {
|
||||
type: command
|
||||
name: Get Keycode
|
||||
define: GET_ENCODER_KEYCODE
|
||||
description: TODO
|
||||
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: dynamic_encoder_get_keycode
|
||||
}
|
||||
0x03: {
|
||||
type: command
|
||||
name: Set Keycode
|
||||
define: SET_ENCODER_KEYCODE
|
||||
description: TODO
|
||||
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.
|
||||
'''
|
||||
enable_if_preprocessor: defined(RGBLIGHT_ENABLE) || defined(RGB_MATRIX_ENABLE)
|
||||
routes: {
|
||||
0x00: {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
65
docs/xap_0.0.1.md
Normal file
65
docs/xap_0.0.1.md
Normal file
|
@ -0,0 +1,65 @@
|
|||
# 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. |
|
||||
|
||||
## 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, allowing 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.
|
||||
|
||||
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` |
|
||||
|
82
docs/xap_0.1.0.md
Normal file
82
docs/xap_0.1.0.md
Normal file
|
@ -0,0 +1,82 @@
|
|||
# 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 |
|
||||
| -- | -- |
|
||||
| _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. |
|
||||
|
||||
## 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, allowing 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.
|
||||
|
||||
This token is followed by a `u8` signifying the length of data in the request.
|
||||
|
||||
Two token values are reserved: `0x0000` and `0xFFFF`:
|
||||
* `0x0000`: 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 |
|
||||
| -- | -- | -- | -- | -- | -- | -- | -- |
|
||||
| `UNLOCKED` | `UNLOCK_IN_PROGRESS` | `-` | `-` | `-` | `-` | `SECURE_FAILURE` | `SUCCESS` |
|
||||
|
||||
* Bit 7 (`UNLOCKED`): When this bit is set, an _unlock sequence_ has completed, and _secure routes_ may be invoked.
|
||||
* Bit 6 (`UNLOCK_IN_PROGRESS`): When this bit is set, an _unlock sequence_ is in progress.
|
||||
* 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` |
|
||||
|
||||
## 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, with corresponding _payload_.
|
||||
|
82
docs/xap_0.2.0.md
Normal file
82
docs/xap_0.2.0.md
Normal file
|
@ -0,0 +1,82 @@
|
|||
# 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 |
|
||||
| -- | -- |
|
||||
| _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. |
|
||||
|
||||
## 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, allowing 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.
|
||||
|
||||
This token is followed by a `u8` signifying the length of data in the request.
|
||||
|
||||
Two token values are reserved: `0x0000` and `0xFFFF`:
|
||||
* `0x0000`: 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 |
|
||||
| -- | -- | -- | -- | -- | -- | -- | -- |
|
||||
| `UNLOCKED` | `UNLOCK_IN_PROGRESS` | `-` | `-` | `-` | `-` | `SECURE_FAILURE` | `SUCCESS` |
|
||||
|
||||
* Bit 7 (`UNLOCKED`): When this bit is set, an _unlock sequence_ has completed, and _secure routes_ may be invoked.
|
||||
* Bit 6 (`UNLOCK_IN_PROGRESS`): When this bit is set, an _unlock sequence_ is in progress.
|
||||
* 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` |
|
||||
|
||||
## 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, with corresponding _payload_.
|
||||
|
5
docs/xap_protocol.md
Normal file
5
docs/xap_protocol.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
# XAP Protocol Reference
|
||||
|
||||
* [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)
|
102
keyboards/zvecr/zv48/keymaps/xap/keymap.c
Normal file
102
keyboards/zvecr/zv48/keymaps/xap/keymap.c
Normal file
|
@ -0,0 +1,102 @@
|
|||
// 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(
|
||||
_______, RESET, _______, _______, _______, _______, RGB_TOG, RGB_MOD, RGB_RMOD,RGB_M_G, RESET, _______,
|
||||
_______, _______, _______, _______, _______, _______, 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
|
2
keyboards/zvecr/zv48/keymaps/xap/rules.mk
Normal file
2
keyboards/zvecr/zv48/keymaps/xap/rules.mk
Normal file
|
@ -0,0 +1,2 @@
|
|||
ENCODER_MAP_ENABLE = yes
|
||||
XAP_ENABLE = yes
|
|
@ -1,5 +1,9 @@
|
|||
"""Functions for working with config.h files.
|
||||
"""
|
||||
from pygments.lexers.c_cpp import CLexer
|
||||
from pygments.token import Token
|
||||
from pygments import lex
|
||||
from itertools import islice
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
|
@ -13,6 +17,13 @@ multi_comment_regex = re.compile(r'/\*(.|\n)*?\*/', re.MULTILINE)
|
|||
layout_macro_define_regex = re.compile(r'^#\s*define')
|
||||
|
||||
|
||||
def _get_chunks(it, size):
|
||||
"""Break down a collection into smaller parts
|
||||
"""
|
||||
it = iter(it)
|
||||
return iter(lambda: tuple(islice(it, size)), ())
|
||||
|
||||
|
||||
def strip_line_comment(string):
|
||||
"""Removes comments from a single line string.
|
||||
"""
|
||||
|
@ -170,3 +181,108 @@ def _parse_matrix_locations(matrix, file, macro_name):
|
|||
matrix_locations[identifier] = [row_num, col_num]
|
||||
|
||||
return matrix_locations
|
||||
|
||||
|
||||
def _coerce_led_token(_type, value):
|
||||
""" Convert token to valid info.json content
|
||||
"""
|
||||
value_map = {
|
||||
'NO_LED': None,
|
||||
'LED_FLAG_ALL': 0xFF,
|
||||
'LED_FLAG_NONE': 0x00,
|
||||
'LED_FLAG_MODIFIER': 0x01,
|
||||
'LED_FLAG_UNDERGLOW': 0x02,
|
||||
'LED_FLAG_KEYLIGHT': 0x04,
|
||||
'LED_FLAG_INDICATOR': 0x08,
|
||||
}
|
||||
if _type is Token.Literal.Number.Integer:
|
||||
return int(value)
|
||||
if _type is Token.Literal.Number.Hex:
|
||||
return int(value, 0)
|
||||
if _type is Token.Name and value in value_map.keys():
|
||||
return value_map[value]
|
||||
|
||||
|
||||
def _parse_led_config(file, matrix_cols, matrix_rows):
|
||||
"""Return any 'raw' led/rgb matrix config
|
||||
"""
|
||||
file_contents = file.read_text(encoding='utf-8')
|
||||
file_contents = comment_remover(file_contents)
|
||||
file_contents = file_contents.replace('\\\n', '')
|
||||
|
||||
matrix_raw = []
|
||||
position_raw = []
|
||||
flags = []
|
||||
|
||||
found_led_config = False
|
||||
bracket_count = 0
|
||||
section = 0
|
||||
for _type, value in lex(file_contents, CLexer()):
|
||||
# Assume g_led_config..stuff..;
|
||||
if value == 'g_led_config':
|
||||
found_led_config = True
|
||||
elif value == ';':
|
||||
found_led_config = False
|
||||
elif found_led_config:
|
||||
# Assume bracket count hints to section of config we are within
|
||||
if value == '{':
|
||||
bracket_count += 1
|
||||
if bracket_count == 2:
|
||||
section += 1
|
||||
elif value == '}':
|
||||
bracket_count -= 1
|
||||
else:
|
||||
# Assume any non whitespace value here is important enough to stash
|
||||
if _type in [Token.Literal.Number.Integer, Token.Literal.Number.Hex, Token.Name]:
|
||||
if section == 1 and bracket_count == 3:
|
||||
matrix_raw.append(_coerce_led_token(_type, value))
|
||||
if section == 2 and bracket_count == 3:
|
||||
position_raw.append(_coerce_led_token(_type, value))
|
||||
if section == 3 and bracket_count == 2:
|
||||
flags.append(_coerce_led_token(_type, value))
|
||||
|
||||
# Slightly better intrim format
|
||||
matrix = list(_get_chunks(matrix_raw, matrix_cols))
|
||||
position = list(_get_chunks(position_raw, 2))
|
||||
matrix_indexes = list(filter(lambda x: x is not None, matrix_raw))
|
||||
|
||||
# If we have not found anything - bail
|
||||
if not section:
|
||||
return None
|
||||
|
||||
# TODO: Improve crude parsing/validation
|
||||
if len(matrix) != matrix_rows and len(matrix) != (matrix_rows / 2):
|
||||
raise ValueError("Unable to parse g_led_config matrix data")
|
||||
if len(position) != len(flags):
|
||||
raise ValueError("Unable to parse g_led_config position data")
|
||||
if len(matrix_indexes) and (max(matrix_indexes) >= len(flags)):
|
||||
raise ValueError("OOB within g_led_config matrix data")
|
||||
|
||||
return (matrix, position, flags)
|
||||
|
||||
|
||||
def find_led_config(file, matrix_cols, matrix_rows):
|
||||
"""Search file for led/rgb matrix config
|
||||
"""
|
||||
found = _parse_led_config(file, matrix_cols, matrix_rows)
|
||||
if not found:
|
||||
return None
|
||||
|
||||
# Expand collected content
|
||||
(matrix, position, flags) = found
|
||||
|
||||
# Align to output format
|
||||
led_config = []
|
||||
for index, item in enumerate(position, start=0):
|
||||
led_config.append({
|
||||
'x': item[0],
|
||||
'y': item[1],
|
||||
'flags': flags[index],
|
||||
})
|
||||
for r in range(len(matrix)):
|
||||
for c in range(len(matrix[r])):
|
||||
index = matrix[r][c]
|
||||
if index is not None:
|
||||
led_config[index]['matrix'] = [r, c]
|
||||
|
||||
return led_config
|
||||
|
|
65
lib/python/qmk/casing.py
Executable file
65
lib/python/qmk/casing.py
Executable file
|
@ -0,0 +1,65 @@
|
|||
"""This script handles conversion between snake and camel casing.
|
||||
"""
|
||||
import re
|
||||
|
||||
_words_expr = re.compile(r"([a-zA-Z][^A-Z0-9]*|[0-9]+)")
|
||||
_lower_snake_case_expr = re.compile(r'^[a-z][a-z0-9_]*$')
|
||||
_upper_snake_case_expr = re.compile(r'^[A-Z][A-Z0-9_]*$')
|
||||
|
||||
|
||||
def _is_snake_case(str):
|
||||
"""Checks if the supplied string is already in snake case.
|
||||
"""
|
||||
match = _lower_snake_case_expr.match(str)
|
||||
if match:
|
||||
return True
|
||||
match = _upper_snake_case_expr.match(str)
|
||||
if match:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _split_snake_case(str):
|
||||
"""Splits up a string based on underscores, if it's in snake casing.
|
||||
"""
|
||||
if _is_snake_case(str):
|
||||
return [s.lower() for s in str.split("_")]
|
||||
return str
|
||||
|
||||
|
||||
def _split_camel_case(str):
|
||||
"""Splits up a string based on capitalised camel casing.
|
||||
"""
|
||||
return _words_expr.findall(str)
|
||||
|
||||
|
||||
def _split_cased_words(str):
|
||||
return _split_snake_case(str) if _is_snake_case(str) else _split_camel_case(str)
|
||||
|
||||
|
||||
def to_snake(str):
|
||||
str = "_".join([word.strip().lower() for word in _split_cased_words(str)])
|
||||
|
||||
# Fix acronyms
|
||||
str = str.replace('i_d', 'id')
|
||||
str = str.replace('x_a_p', 'xap')
|
||||
str = str.replace('q_m_k', 'qmk')
|
||||
|
||||
return str
|
||||
|
||||
|
||||
def to_upper_snake(str):
|
||||
return to_snake(str).upper()
|
||||
|
||||
|
||||
def to_camel(str):
|
||||
def _acronym(w):
|
||||
if w.strip().lower() == 'qmk':
|
||||
return 'QMK'
|
||||
elif w.strip().lower() == 'xap':
|
||||
return 'XAP'
|
||||
elif w.strip().lower() == 'id':
|
||||
return 'ID'
|
||||
return w.title()
|
||||
|
||||
return "".join([_acronym(word) for word in _split_cased_words(str)])
|
|
@ -17,7 +17,8 @@ import_names = {
|
|||
'pep8-naming': 'pep8ext_naming',
|
||||
'pyusb': 'usb.core',
|
||||
'qmk-dotty-dict': 'dotty_dict',
|
||||
'pillow': 'PIL'
|
||||
'pillow': 'PIL',
|
||||
'Jinja2': 'jinja2'
|
||||
}
|
||||
|
||||
safe_commands = [
|
||||
|
@ -52,6 +53,7 @@ subcommands = [
|
|||
'qmk.cli.generate.dfu_header',
|
||||
'qmk.cli.generate.docs',
|
||||
'qmk.cli.generate.info_json',
|
||||
'qmk.cli.generate.keyboard_c',
|
||||
'qmk.cli.generate.keyboard_h',
|
||||
'qmk.cli.generate.layouts',
|
||||
'qmk.cli.generate.rgb_breathe_table',
|
||||
|
@ -72,6 +74,10 @@ subcommands = [
|
|||
'qmk.cli.pyformat',
|
||||
'qmk.cli.pytest',
|
||||
'qmk.cli.via2json',
|
||||
'qmk.cli.xap',
|
||||
'qmk.cli.xap.generate_docs',
|
||||
'qmk.cli.xap.generate_json',
|
||||
'qmk.cli.xap.generate_qmk',
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -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(files, 10):
|
||||
for chunk in get_chunks(files, 10):
|
||||
dos2unix = cli.run(['dos2unix', *chunk])
|
||||
|
||||
if dos2unix.returncode:
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
"""
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import hjson
|
||||
import json
|
||||
|
||||
from milc import cli
|
||||
|
@ -11,30 +12,16 @@ from qmk.info import info_json
|
|||
from qmk.json_encoders import InfoJSONEncoder
|
||||
from qmk.json_schema import json_load
|
||||
from qmk.keyboard import find_readme, list_keyboards
|
||||
from qmk.xap.common import get_xap_definition_files, update_xap_definitions
|
||||
|
||||
TEMPLATE_PATH = Path('data/templates/api/')
|
||||
DATA_PATH = Path('data')
|
||||
TEMPLATE_PATH = DATA_PATH / 'templates/api/'
|
||||
BUILD_API_PATH = Path('.build/api_data/')
|
||||
|
||||
|
||||
@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('Creates a new keymap for the keyboard of your choosing', hidden=False if cli.config.user.developer else True)
|
||||
def generate_api(cli):
|
||||
"""Generates the QMK API data.
|
||||
def _filtered_keyboard_list():
|
||||
"""Perform basic filtering of list_keyboards
|
||||
"""
|
||||
if BUILD_API_PATH.exists():
|
||||
shutil.rmtree(BUILD_API_PATH)
|
||||
|
||||
shutil.copytree(TEMPLATE_PATH, BUILD_API_PATH)
|
||||
|
||||
v1_dir = BUILD_API_PATH / 'v1'
|
||||
keyboard_all_file = v1_dir / 'keyboards.json' # A massive JSON containing everything
|
||||
keyboard_list_file = v1_dir / 'keyboard_list.json' # A simple list of keyboard targets
|
||||
keyboard_aliases_file = v1_dir / 'keyboard_aliases.json' # A list of historical keyboard names and their new name
|
||||
keyboard_metadata_file = v1_dir / 'keyboard_metadata.json' # All the data configurator/via needs for initialization
|
||||
usb_file = v1_dir / 'usb.json' # A mapping of USB VID/PID -> keyboard target
|
||||
|
||||
# Filter down when required
|
||||
keyboard_list = list_keyboards()
|
||||
if cli.args.filter:
|
||||
kb_list = []
|
||||
|
@ -42,6 +29,46 @@ def generate_api(cli):
|
|||
if any(i in keyboard_name for i in cli.args.filter):
|
||||
kb_list.append(keyboard_name)
|
||||
keyboard_list = kb_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)
|
||||
def generate_api(cli):
|
||||
"""Generates the QMK API data.
|
||||
"""
|
||||
v1_dir = BUILD_API_PATH / 'v1'
|
||||
keyboard_all_file = v1_dir / 'keyboards.json' # A massive JSON containing everything
|
||||
keyboard_list_file = v1_dir / 'keyboard_list.json' # A simple list of keyboard targets
|
||||
keyboard_aliases_file = v1_dir / 'keyboard_aliases.json' # A list of historical keyboard names and their new name
|
||||
keyboard_metadata_file = v1_dir / 'keyboard_metadata.json' # All the data configurator/via needs for initialization
|
||||
usb_file = v1_dir / 'usb.json' # A mapping of USB VID/PID -> keyboard target
|
||||
|
||||
if BUILD_API_PATH.exists():
|
||||
shutil.rmtree(BUILD_API_PATH)
|
||||
|
||||
shutil.copytree(TEMPLATE_PATH, BUILD_API_PATH)
|
||||
shutil.copytree(DATA_PATH, v1_dir)
|
||||
|
||||
# Filter down when required
|
||||
keyboard_list = _filtered_keyboard_list()
|
||||
|
||||
kb_all = {}
|
||||
usb_list = {}
|
||||
|
@ -86,6 +113,9 @@ def generate_api(cli):
|
|||
'usb': usb_list,
|
||||
}
|
||||
|
||||
# Feature specific handling
|
||||
_resolve_xap_specs(v1_dir / 'xap')
|
||||
|
||||
# Write the global JSON files
|
||||
keyboard_all_json = json.dumps({'last_updated': current_datetime(), 'keyboards': kb_all}, cls=InfoJSONEncoder)
|
||||
usb_json = json.dumps({'last_updated': current_datetime(), 'usb': usb_list}, cls=InfoJSONEncoder)
|
||||
|
|
66
lib/python/qmk/cli/generate/keyboard_c.py
Executable file
66
lib/python/qmk/cli/generate/keyboard_c.py
Executable file
|
@ -0,0 +1,66 @@
|
|||
"""Used by the make system to generate keyboard.c from info.json.
|
||||
"""
|
||||
from milc import cli
|
||||
|
||||
from qmk.info import info_json
|
||||
from qmk.commands import dump_lines
|
||||
from qmk.keyboard import keyboard_completer, keyboard_folder
|
||||
from qmk.path import normpath
|
||||
from qmk.constants import GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE
|
||||
|
||||
|
||||
def _gen_led_config(info_data):
|
||||
"""Convert info.json content to g_led_config
|
||||
"""
|
||||
cols = info_data['matrix_size']['cols']
|
||||
rows = info_data['matrix_size']['rows']
|
||||
|
||||
led_config = None
|
||||
if 'layout' in info_data.get('rgb_matrix', {}):
|
||||
led_config = info_data['rgb_matrix']['layout']
|
||||
elif 'layout' in info_data.get('led_matrix', {}):
|
||||
led_config = info_data['led_matrix']['layout']
|
||||
|
||||
lines = []
|
||||
if not led_config:
|
||||
return lines
|
||||
|
||||
matrix = [['NO_PIN'] * cols for i in range(rows)]
|
||||
pos = []
|
||||
flags = []
|
||||
|
||||
for index, item in enumerate(led_config, start=0):
|
||||
if 'matrix' in item:
|
||||
(x, y) = item['matrix']
|
||||
matrix[x][y] = str(index)
|
||||
pos.append(f'{{ {item.get("x", 0)},{item.get("y", 0)} }}')
|
||||
flags.append(str(item.get('flags', 0)))
|
||||
|
||||
lines.append('__attribute__ ((weak)) led_config_t g_led_config = {')
|
||||
lines.append(' {')
|
||||
for line in matrix:
|
||||
lines.append(f' {{ {",".join(line)} }},')
|
||||
lines.append(' },')
|
||||
lines.append(f' {{ {",".join(pos)} }},')
|
||||
lines.append(f' {{ {",".join(flags)} }},')
|
||||
lines.append('};')
|
||||
|
||||
return lines
|
||||
|
||||
|
||||
@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('-kb', '--keyboard', arg_only=True, type=keyboard_folder, completer=keyboard_completer, required=True, help='Keyboard to generate keyboard.c for.')
|
||||
@cli.subcommand('Used by the make system to generate keyboard.c from info.json', hidden=True)
|
||||
def generate_keyboard_c(cli):
|
||||
"""Generates the keyboard.h file.
|
||||
"""
|
||||
kb_info_json = info_json(cli.args.keyboard)
|
||||
|
||||
# Build the layouts.h file.
|
||||
keyboard_h_lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '#include QMK_KEYBOARD_H', '']
|
||||
|
||||
keyboard_h_lines.extend(_gen_led_config(kb_info_json))
|
||||
|
||||
# Show the results
|
||||
dump_lines(cli.args.output, keyboard_h_lines, cli.args.quiet)
|
1
lib/python/qmk/cli/xap/__init__.py
Executable file
1
lib/python/qmk/cli/xap/__init__.py
Executable file
|
@ -0,0 +1 @@
|
|||
from .xap import xap
|
39
lib/python/qmk/cli/xap/generate_docs.py
Executable file
39
lib/python/qmk/cli/xap/generate_docs.py
Executable file
|
@ -0,0 +1,39 @@
|
|||
"""This script generates the XAP protocol documentation.
|
||||
"""
|
||||
import hjson
|
||||
from qmk.constants import QMK_FIRMWARE
|
||||
from qmk.xap.common import get_xap_definition_files, update_xap_definitions, render_xap_output
|
||||
from milc import cli
|
||||
|
||||
|
||||
@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/`.
|
||||
"""
|
||||
docs_list = []
|
||||
|
||||
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_doc = QMK_FIRMWARE / "docs" / f"{file.stem}.md"
|
||||
docs_list.append(output_doc)
|
||||
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"
|
||||
with open(output_doc, "w", encoding='utf-8') as out_file:
|
||||
out_file.write('''\
|
||||
# XAP Protocol Reference
|
||||
|
||||
''')
|
||||
|
||||
for file in reversed(sorted(docs_list)):
|
||||
ver = file.stem[4:]
|
||||
out_file.write(f'* [XAP Version {ver}]({file.name})\n')
|
13
lib/python/qmk/cli/xap/generate_json.py
Executable file
13
lib/python/qmk/cli/xap/generate_json.py
Executable file
|
@ -0,0 +1,13 @@
|
|||
"""This script generates the consolidated XAP protocol definitions.
|
||||
"""
|
||||
import hjson
|
||||
from milc import cli
|
||||
from qmk.xap.common import latest_xap_defs
|
||||
|
||||
|
||||
@cli.subcommand('Generates the consolidated XAP protocol definitions.', hidden=False if cli.config.user.developer else True)
|
||||
def xap_generate_json(cli):
|
||||
"""Generates the consolidated XAP protocol definitions.
|
||||
"""
|
||||
defs = latest_xap_defs()
|
||||
print(hjson.dumps(defs))
|
51
lib/python/qmk/cli/xap/generate_qmk.py
Executable file
51
lib/python/qmk/cli/xap/generate_qmk.py
Executable file
|
@ -0,0 +1,51 @@
|
|||
"""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.gen_firmware.info_generator import generate_info
|
||||
from qmk.xap.gen_firmware.inline_generator import generate_inline
|
||||
from qmk.xap.gen_firmware.header_generator import generate_header
|
||||
|
||||
|
||||
@cli.argument('-o', '--output', type=normpath, help='File to write to')
|
||||
@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.
|
||||
"""
|
||||
generate_inline(cli.args.output)
|
||||
|
||||
|
||||
@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.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
|
||||
if not cli.args.keyboard:
|
||||
cli.log.error('Missing parameter: --keyboard')
|
||||
cli.subcommands['xap-generate-qmk-h'].print_help()
|
||||
return False
|
||||
generate_header(cli.args.output, cli.args.keyboard)
|
||||
|
||||
|
||||
@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 info.json payload include.', hidden=False if cli.config.user.developer else True)
|
||||
def xap_generate_info_h(cli):
|
||||
"""Generates the XAP info.json 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-info-h'].print_help()
|
||||
return False
|
||||
if not cli.args.keymap:
|
||||
cli.log.error('Missing parameter: --keymap')
|
||||
cli.subcommands['xap-generate-info-h'].print_help()
|
||||
return False
|
||||
|
||||
generate_info(cli.args.output, cli.args.keyboard, cli.args.keymap)
|
336
lib/python/qmk/cli/xap/xap.py
Normal file
336
lib/python/qmk/cli/xap/xap.py
Normal file
|
@ -0,0 +1,336 @@
|
|||
"""Interactions with compatible XAP devices
|
||||
"""
|
||||
import cmd
|
||||
import json
|
||||
import random
|
||||
import gzip
|
||||
from platform import platform
|
||||
|
||||
from milc import cli
|
||||
|
||||
from qmk.keyboard import render_layout
|
||||
from qmk.xap.common import get_xap_keycodes
|
||||
|
||||
KEYCODE_MAP = get_xap_keycodes('latest')
|
||||
|
||||
|
||||
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(cli.args.device.lower())
|
||||
|
||||
|
||||
def _search():
|
||||
devices = filter(_is_xap_usage, hid.enumerate())
|
||||
if cli.args.device:
|
||||
devices = filter(_is_filtered_device, devices)
|
||||
|
||||
return list(devices)
|
||||
|
||||
|
||||
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(sorted(map(str, data))))
|
||||
else:
|
||||
cli.echo(' {fg_blue}%s{fg_reset}: %s', new_prefix, kb_info_json[key])
|
||||
|
||||
|
||||
def _xap_transaction(device, sub, route, *args):
|
||||
# gen token
|
||||
tok = random.getrandbits(16)
|
||||
token = tok.to_bytes(2, byteorder='little')
|
||||
|
||||
# send with padding
|
||||
# TODO: this code is total garbage
|
||||
args_data = []
|
||||
args_len = 2
|
||||
if len(args) == 1:
|
||||
if isinstance(args[0], (bytes, bytearray)):
|
||||
args_len += len(args[0])
|
||||
args_data = args[0]
|
||||
else:
|
||||
args_len += 2
|
||||
args_data = args[0].to_bytes(2, byteorder='little')
|
||||
|
||||
padding_len = 64 - 3 - args_len
|
||||
padding = b"\x00" * padding_len
|
||||
if args_data:
|
||||
padding = args_data + padding
|
||||
buffer = token + args_len.to_bytes(1, byteorder='little') + sub.to_bytes(1, byteorder='little') + route.to_bytes(1, byteorder='little') + padding
|
||||
|
||||
# prepend 0 on windows because reasons...
|
||||
if 'windows' in platform().lower():
|
||||
buffer = b"\x00" + buffer
|
||||
|
||||
device.write(buffer)
|
||||
|
||||
# get resp
|
||||
array_alpha = device.read(64, 250)
|
||||
|
||||
# validate tok sent == resp
|
||||
if str(token) != str(array_alpha[:2]):
|
||||
return None
|
||||
if int(array_alpha[2]) != 0x01:
|
||||
return None
|
||||
|
||||
payload_len = int(array_alpha[3])
|
||||
return array_alpha[4:4 + payload_len]
|
||||
|
||||
|
||||
def _query_device(device):
|
||||
ver_data = _xap_transaction(device, 0x00, 0x00)
|
||||
if not ver_data:
|
||||
return {'xap': 'UNKNOWN', 'secure': 'UNKNOWN'}
|
||||
|
||||
# to u32 to BCD string
|
||||
a = (ver_data[3] << 24) + (ver_data[2] << 16) + (ver_data[1] << 8) + (ver_data[0])
|
||||
ver = f'{a>>24}.{a>>16 & 0xFF}.{a & 0xFFFF}'
|
||||
|
||||
secure = int.from_bytes(_xap_transaction(device, 0x00, 0x03), 'little')
|
||||
secure = 'unlocked' if secure == 2 else 'LOCKED'
|
||||
|
||||
return {'xap': ver, 'secure': secure}
|
||||
|
||||
|
||||
def _query_device_id(device):
|
||||
return _xap_transaction(device, 0x01, 0x08)
|
||||
|
||||
|
||||
def _query_device_info_len(device):
|
||||
len_data = _xap_transaction(device, 0x01, 0x05)
|
||||
if not len_data:
|
||||
return 0
|
||||
|
||||
# to u32
|
||||
return (len_data[3] << 24) + (len_data[2] << 16) + (len_data[1] << 8) + (len_data[0])
|
||||
|
||||
|
||||
def _query_device_info_chunk(device, offset):
|
||||
return _xap_transaction(device, 0x01, 0x06, offset)
|
||||
|
||||
|
||||
def _query_device_info(device):
|
||||
datalen = _query_device_info_len(device)
|
||||
if not datalen:
|
||||
return {}
|
||||
|
||||
data = []
|
||||
offset = 0
|
||||
while offset < datalen:
|
||||
data += _query_device_info_chunk(device, offset)
|
||||
offset += 32
|
||||
str_data = gzip.decompress(bytearray(data[:datalen]))
|
||||
return json.loads(str_data)
|
||||
|
||||
|
||||
def _list_devices():
|
||||
"""Dump out available devices
|
||||
"""
|
||||
cli.log.info('Available devices:')
|
||||
devices = _search()
|
||||
for dev in devices:
|
||||
device = hid.Device(path=dev['path'])
|
||||
|
||||
data = _query_device(device)
|
||||
cli.log.info(" %04x:%04x %s %s [API:%s] %s", dev['vendor_id'], dev['product_id'], dev['manufacturer_string'], dev['product_string'], data['xap'], data['secure'])
|
||||
|
||||
if cli.config.general.verbose:
|
||||
# TODO: better formatting like "lsusb -v"?
|
||||
data = _query_device_info(device)
|
||||
data["_id"] = _query_device_id(device)
|
||||
print_dotted_output(data)
|
||||
|
||||
|
||||
def xap_dummy(device):
|
||||
# get layer count
|
||||
layers = _xap_transaction(device, 0x04, 0x01)
|
||||
layers = int.from_bytes(layers, "little")
|
||||
print(f'layers:{layers}')
|
||||
|
||||
# get keycode [layer:0, row:0, col:0]
|
||||
# keycode = _xap_transaction(device, 0x04, 0x02, b"\x00\x00\x00")
|
||||
|
||||
# get encoder [layer:0, index:0, clockwise:0]
|
||||
keycode = _xap_transaction(device, 0x05, 0x02, b"\x00\x00\x00")
|
||||
|
||||
keycode = int.from_bytes(keycode, "little")
|
||||
print(f'keycode:{KEYCODE_MAP.get(keycode, "unknown")}[{keycode}]')
|
||||
|
||||
# set encoder [layer:0, index:0, clockwise:0, keycode:KC_A]
|
||||
_xap_transaction(device, 0x05, 0x03, b"\x00\x00\x00\x04\00")
|
||||
|
||||
|
||||
def xap_broadcast_listen(device):
|
||||
try:
|
||||
cli.log.info("Listening for XAP broadcasts...")
|
||||
while 1:
|
||||
array_alpha = device.read(64, 100)
|
||||
if str(b"\xFF\xFF") == str(array_alpha[:2]):
|
||||
if array_alpha[2] == 1:
|
||||
cli.log.info(" Broadcast: Secure[%02x]", array_alpha[4])
|
||||
else:
|
||||
cli.log.info(" Broadcast: type[%02x] data:[%02x]", array_alpha[2], array_alpha[4])
|
||||
except KeyboardInterrupt:
|
||||
cli.log.info("Stopping...")
|
||||
|
||||
|
||||
def xap_unlock(device):
|
||||
_xap_transaction(device, 0x00, 0x04)
|
||||
|
||||
|
||||
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 = get_xap_keycodes(_query_device(device)['xap'])
|
||||
|
||||
def do_about(self, arg):
|
||||
"""Prints out the current version of QMK with a build date
|
||||
"""
|
||||
data = _query_device(self.device)
|
||||
print(data)
|
||||
|
||||
def do_unlock(self, arg):
|
||||
"""Initiate secure unlock
|
||||
"""
|
||||
xap_unlock(self.device)
|
||||
print("Done")
|
||||
|
||||
def do_listen(self, arg):
|
||||
"""Log out XAP broadcast messages
|
||||
"""
|
||||
xap_broadcast_listen(self.device)
|
||||
|
||||
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 = _xap_transaction(self.device, 0x04, 0x02, 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 = _query_device_info(self.device)
|
||||
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 = _xap_transaction(self.device, 0x04, 0x02, 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 = _query_device_info(self.device)
|
||||
|
||||
# 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 = _xap_transaction(self.device, 0x04, 0x02, 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
|
||||
|
||||
|
||||
@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='*', default=['listen'], arg_only=True)
|
||||
@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
|
||||
"""
|
||||
# Lazy load to avoid issues
|
||||
global hid
|
||||
import hid
|
||||
|
||||
if cli.args.list:
|
||||
return _list_devices()
|
||||
|
||||
# Connect to first available device
|
||||
devices = _search()
|
||||
if not devices:
|
||||
cli.log.error("No devices found!")
|
||||
return False
|
||||
|
||||
dev = devices[0]
|
||||
device = hid.Device(path=dev['path'])
|
||||
cli.log.info("Connected to:%04x:%04x %s %s", dev['vendor_id'], dev['product_id'], dev['manufacturer_string'], dev['product_string'])
|
||||
|
||||
# shell?
|
||||
if cli.args.interactive:
|
||||
XAPShell(device).loop()
|
||||
return True
|
||||
|
||||
XAPShell(device).onecmd(" ".join(cli.args.action))
|
|
@ -3,6 +3,7 @@
|
|||
import os
|
||||
import sys
|
||||
import shutil
|
||||
from itertools import islice
|
||||
from pathlib import Path
|
||||
|
||||
from milc import cli
|
||||
|
@ -219,6 +220,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):
|
||||
"""Handle dumping to stdout or file
|
||||
Creates parent folders if required
|
||||
|
|
|
@ -8,7 +8,7 @@ from dotty_dict import dotty
|
|||
from milc import cli
|
||||
|
||||
from qmk.constants import CHIBIOS_PROCESSORS, LUFA_PROCESSORS, VUSB_PROCESSORS
|
||||
from qmk.c_parse import find_layouts, parse_config_h_file
|
||||
from qmk.c_parse import find_layouts, parse_config_h_file, find_led_config
|
||||
from qmk.json_schema import deep_update, json_load, validate
|
||||
from qmk.keyboard import config_h, rules_mk
|
||||
from qmk.keymap import list_keymaps, locate_keymap
|
||||
|
@ -76,6 +76,9 @@ def info_json(keyboard):
|
|||
# Ensure that we have matrix row and column counts
|
||||
info_data = _matrix_size(info_data)
|
||||
|
||||
# Merge in data from <keyboard.c>
|
||||
info_data = _extract_led_config(info_data, str(keyboard))
|
||||
|
||||
# Validate against the jsonschema
|
||||
try:
|
||||
validate(info_data, 'qmk.api.keyboard.v1')
|
||||
|
@ -590,6 +593,46 @@ def _extract_rules_mk(info_data, rules):
|
|||
return info_data
|
||||
|
||||
|
||||
def find_keyboard_c(keyboard):
|
||||
"""Find all <keyboard>.c files
|
||||
"""
|
||||
keyboard = Path(keyboard)
|
||||
current_path = Path('keyboards/')
|
||||
|
||||
files = []
|
||||
for directory in keyboard.parts:
|
||||
current_path = current_path / directory
|
||||
keyboard_c_path = current_path / f'{directory}.c'
|
||||
if keyboard_c_path.exists():
|
||||
files.append(keyboard_c_path)
|
||||
|
||||
return files
|
||||
|
||||
|
||||
def _extract_led_config(info_data, keyboard):
|
||||
"""Scan all <keyboard>.c files for led config
|
||||
"""
|
||||
cols = info_data['matrix_size']['cols']
|
||||
rows = info_data['matrix_size']['rows']
|
||||
|
||||
# Assume what feature owns g_led_config
|
||||
feature = "rgb_matrix"
|
||||
if info_data.get("features", {}).get("led_matrix", False):
|
||||
feature = "led_matrix"
|
||||
|
||||
# Process
|
||||
for file in find_keyboard_c(keyboard):
|
||||
try:
|
||||
ret = find_led_config(file, cols, rows)
|
||||
if ret:
|
||||
info_data[feature] = info_data.get(feature, {})
|
||||
info_data[feature]["layout"] = ret
|
||||
except Exception as e:
|
||||
_log_warning(info_data, f'led_config: {file.name}: {e}')
|
||||
|
||||
return info_data
|
||||
|
||||
|
||||
def _matrix_size(info_data):
|
||||
"""Add info_data['matrix_size'] if it doesn't exist.
|
||||
"""
|
||||
|
|
|
@ -75,8 +75,8 @@ class InfoJSONEncoder(QMKJSONEncoder):
|
|||
"""Encode info.json dictionaries.
|
||||
"""
|
||||
if obj:
|
||||
if self.indentation_level == 4:
|
||||
# These are part of a layout, put them on a single line.
|
||||
if set(("x", "y")).issubset(obj.keys()):
|
||||
# These are part of a layout/led_config, put them on a single line.
|
||||
return "{ " + ", ".join(f"{self.encode(key)}: {self.encode(element)}" for key, element in sorted(obj.items())) + " }"
|
||||
|
||||
else:
|
||||
|
|
|
@ -4,6 +4,7 @@ from array import array
|
|||
from math import ceil
|
||||
from pathlib import Path
|
||||
import os
|
||||
import shutil
|
||||
from glob import glob
|
||||
|
||||
import qmk.path
|
||||
|
@ -11,6 +12,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": "┌",
|
||||
|
@ -205,9 +207,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
|
||||
|
@ -234,9 +236,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
|
||||
|
@ -266,9 +268,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 - 2
|
||||
|
|
0
lib/python/qmk/xap/__init__.py
Normal file
0
lib/python/qmk/xap/__init__.py
Normal file
124
lib/python/qmk/xap/common.py
Executable file
124
lib/python/qmk/xap/common.py
Executable file
|
@ -0,0 +1,124 @@
|
|||
"""This script handles the XAP protocol data files.
|
||||
"""
|
||||
import os
|
||||
import hjson
|
||||
from pathlib import Path
|
||||
from typing import OrderedDict
|
||||
from jinja2 import Environment, FileSystemLoader, select_autoescape
|
||||
|
||||
from qmk.constants import QMK_FIRMWARE
|
||||
from qmk.json_schema import json_load
|
||||
from qmk.decorators import lru_cache
|
||||
|
||||
|
||||
def _get_jinja2_env(data_templates_xap_subdir: str):
|
||||
templates_dir = os.path.join(QMK_FIRMWARE, 'data', 'templates', 'xap', data_templates_xap_subdir)
|
||||
j2 = Environment(loader=FileSystemLoader(templates_dir), autoescape=select_autoescape())
|
||||
return j2
|
||||
|
||||
|
||||
def render_xap_output(data_templates_xap_subdir, file_to_render, defs):
|
||||
j2 = _get_jinja2_env(data_templates_xap_subdir)
|
||||
return j2.get_template(file_to_render).render(xap=defs, xap_str=hjson.dumps(defs))
|
||||
|
||||
|
||||
def _merge_ordered_dicts(dicts):
|
||||
"""Merges nested OrderedDict objects resulting from reading a hjson file.
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
result = OrderedDict()
|
||||
|
||||
def add_entry(target, k, v):
|
||||
if k in target and isinstance(v, OrderedDict):
|
||||
if "!reset!" in v:
|
||||
target[k] = v
|
||||
else:
|
||||
target[k] = _merge_ordered_dicts([target[k], v])
|
||||
if "!reset!" in target[k]:
|
||||
del target[k]["!reset!"]
|
||||
elif k in target and isinstance(v, list):
|
||||
if v[0] == '!reset!':
|
||||
target[k] = v[1:]
|
||||
else:
|
||||
target[k] = target[k] + v
|
||||
else:
|
||||
target[k] = v
|
||||
|
||||
for d in dicts:
|
||||
for (k, v) in d.items():
|
||||
add_entry(result, k, v)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def get_xap_definition_files():
|
||||
"""Get the sorted list of XAP definition files, from <QMK>/data/xap.
|
||||
"""
|
||||
xap_defs = 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')
|
||||
|
||||
|
||||
@lru_cache(timeout=5)
|
||||
def get_xap_keycodes(xap_version):
|
||||
"""Gets keycode data for the required version of the XAP definitions.
|
||||
"""
|
||||
defs = get_xap_defs(xap_version)
|
||||
|
||||
# Load DD keycodes for the dependency
|
||||
keycode_version = defs['uses']['keycodes']
|
||||
spec = json_load(Path(f'data/constants/keycodes_{keycode_version}.json'))
|
||||
|
||||
# Transform into something more usable - { raw_value : first alias || keycode }
|
||||
return {int(k, 16): v.get('aliases', [v.get('key')])[0] for k, v in spec['keycodes'].items()}
|
||||
|
||||
|
||||
def route_conditions(route_stack):
|
||||
"""Handles building the C preprocessor conditional based on the current route.
|
||||
"""
|
||||
conditions = []
|
||||
for route in route_stack:
|
||||
if 'enable_if_preprocessor' in route:
|
||||
conditions.append(route['enable_if_preprocessor'])
|
||||
|
||||
if len(conditions) == 0:
|
||||
return None
|
||||
|
||||
return "(" + ' && '.join([f'({c})' for c in conditions]) + ")"
|
0
lib/python/qmk/xap/gen_client_js/__init__.py
Normal file
0
lib/python/qmk/xap/gen_client_js/__init__.py
Normal file
0
lib/python/qmk/xap/gen_firmware/__init__.py
Normal file
0
lib/python/qmk/xap/gen_firmware/__init__.py
Normal file
261
lib/python/qmk/xap/gen_firmware/header_generator.py
Executable file
261
lib/python/qmk/xap/gen_firmware/header_generator.py
Executable file
|
@ -0,0 +1,261 @@
|
|||
"""This script generates the XAP protocol generated header to be compiled into QMK.
|
||||
"""
|
||||
import re
|
||||
from fnvhash import fnv1a_32
|
||||
|
||||
from qmk.casing import to_snake
|
||||
from qmk.commands import dump_lines
|
||||
from qmk.git import git_get_version
|
||||
from qmk.constants import GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE
|
||||
from qmk.xap.common import latest_xap_defs, route_conditions
|
||||
|
||||
|
||||
def _get_c_type(xap_type):
|
||||
if xap_type == 'bool':
|
||||
return 'bool'
|
||||
elif xap_type == 'u8':
|
||||
return 'uint8_t'
|
||||
elif xap_type == 'u16':
|
||||
return 'uint16_t'
|
||||
elif xap_type == 'u32':
|
||||
return 'uint32_t'
|
||||
elif xap_type == 'u64':
|
||||
return 'uint64_t'
|
||||
elif xap_type == 'struct':
|
||||
return 'struct'
|
||||
elif xap_type == 'string':
|
||||
return 'const char *'
|
||||
return 'unknown'
|
||||
|
||||
|
||||
def _append_route_defines(lines, container, container_id=None, route_stack=None):
|
||||
"""Handles building the list of the XAP routes, combining parent and child names together, as well as the route number.
|
||||
"""
|
||||
if route_stack is None:
|
||||
route_stack = [container]
|
||||
else:
|
||||
route_stack.append(container)
|
||||
|
||||
route_name = '_'.join([r['define'] for r in route_stack])
|
||||
|
||||
if container_id:
|
||||
lines.append(f'#define {route_name} {container_id}')
|
||||
|
||||
if 'routes' in container:
|
||||
for route_id in container['routes']:
|
||||
route = container['routes'][route_id]
|
||||
_append_route_defines(lines, route, route_id, route_stack)
|
||||
|
||||
route_stack.pop()
|
||||
|
||||
|
||||
def _append_route_masks(lines, container, container_id=None, route_stack=None):
|
||||
"""Handles creating the equivalent XAP route masks, for capabilities checks. Forces value of `0` if disabled in the firmware.
|
||||
"""
|
||||
if route_stack is None:
|
||||
route_stack = [container]
|
||||
else:
|
||||
route_stack.append(container)
|
||||
|
||||
route_name = '_'.join([r['define'] for r in route_stack])
|
||||
condition = route_conditions(route_stack)
|
||||
|
||||
if container_id:
|
||||
if condition:
|
||||
lines.append('')
|
||||
lines.append(f'#if {condition}')
|
||||
|
||||
lines.append(f'#define {route_name}_MASK (1ul << ({route_name}))')
|
||||
|
||||
if condition:
|
||||
lines.append(f'#else // {condition}')
|
||||
lines.append(f'#define {route_name}_MASK 0')
|
||||
lines.append(f'#endif // {condition}')
|
||||
lines.append('')
|
||||
|
||||
if 'routes' in container:
|
||||
for route_id in container['routes']:
|
||||
route = container['routes'][route_id]
|
||||
_append_route_masks(lines, route, route_id, route_stack)
|
||||
|
||||
route_stack.pop()
|
||||
|
||||
|
||||
def _append_route_capabilities(lines, container, container_id=None, route_stack=None):
|
||||
"""Handles creating the equivalent XAP route masks, for capabilities checks. Forces value of `0` if disabled in the firmware.
|
||||
"""
|
||||
if route_stack is None:
|
||||
route_stack = [container]
|
||||
else:
|
||||
route_stack.append(container)
|
||||
|
||||
route_name = '_'.join([r['define'] for r in route_stack])
|
||||
|
||||
if 'routes' in container:
|
||||
lines.append('')
|
||||
lines.append(f'#define {route_name}_CAPABILITIES (0 \\')
|
||||
|
||||
if 'routes' in container:
|
||||
for route_id in container['routes']:
|
||||
route = container['routes'][route_id]
|
||||
route_stack.append(route)
|
||||
child_name = '_'.join([r['define'] for r in route_stack])
|
||||
lines.append(f' | ({child_name}_MASK) \\')
|
||||
route_stack.pop()
|
||||
|
||||
lines.append(' )')
|
||||
|
||||
if 'routes' in container:
|
||||
for route_id in container['routes']:
|
||||
route = container['routes'][route_id]
|
||||
_append_route_capabilities(lines, route, route_id, route_stack)
|
||||
|
||||
route_stack.pop()
|
||||
|
||||
|
||||
def _append_route_types(lines, container, container_id=None, route_stack=None):
|
||||
"""Handles creating typedefs used by routes
|
||||
"""
|
||||
if route_stack is None:
|
||||
route_stack = [container]
|
||||
else:
|
||||
route_stack.append(container)
|
||||
|
||||
route_name = to_snake('_'.join([r['define'] for r in route_stack]))
|
||||
|
||||
# Inbound
|
||||
if 'request_struct_members' in container:
|
||||
request_struct_members = container['request_struct_members']
|
||||
lines.append('typedef struct {')
|
||||
for member in request_struct_members:
|
||||
member_type = _get_c_type(member['type'])
|
||||
member_name = to_snake(member['name'])
|
||||
lines.append(f' {member_type} {member_name};')
|
||||
lines.append(f'}} __attribute__((__packed__)) {route_name}_arg_t;')
|
||||
|
||||
req_len = container['request_struct_length']
|
||||
lines.append(f'_Static_assert(sizeof({route_name}_arg_t) == {req_len}, "{route_name}_arg_t needs to be {req_len} bytes in size");')
|
||||
|
||||
elif 'request_type' in container:
|
||||
request_type = container['request_type']
|
||||
found = re.search(r'(u\d+)\[(\d+)\]', request_type)
|
||||
if found:
|
||||
request_type, size = found.groups()
|
||||
lines.append(f'typedef struct __attribute__((__packed__)) {{ {_get_c_type(request_type)} x[{size}]; }} {route_name}_arg_t;')
|
||||
else:
|
||||
lines.append(f'typedef {_get_c_type(request_type)} {route_name}_arg_t;')
|
||||
|
||||
# Outbound
|
||||
qualifier = 'const' if 'return_constant' in container else ''
|
||||
if 'return_struct_members' in container:
|
||||
return_struct_members = container['return_struct_members']
|
||||
lines.append('typedef struct {')
|
||||
for member in return_struct_members:
|
||||
member_type = _get_c_type(member['type'])
|
||||
member_name = f'{qualifier} {to_snake(member["name"])}'
|
||||
lines.append(f' {member_type} {member_name};')
|
||||
lines.append(f'}} __attribute__((__packed__)) {route_name}_t;')
|
||||
|
||||
req_len = container['return_struct_length']
|
||||
lines.append(f'_Static_assert(sizeof({route_name}_t) == {req_len}, "{route_name}_t needs to be {req_len} bytes in size");')
|
||||
|
||||
elif 'return_type' in container:
|
||||
return_type = container['return_type']
|
||||
found = re.search(r'(u\d+)\[(\d+)\]', return_type)
|
||||
if found:
|
||||
return_type, size = found.groups()
|
||||
lines.append(f'typedef struct __attribute__((__packed__)) {{ {_get_c_type(return_type)} x[{size}]; }} {route_name}_t;')
|
||||
else:
|
||||
lines.append(f'typedef {_get_c_type(return_type)} {route_name}_t;')
|
||||
|
||||
# Recurse
|
||||
if 'routes' in container:
|
||||
for route_id in container['routes']:
|
||||
route = container['routes'][route_id]
|
||||
_append_route_types(lines, route, route_id, route_stack)
|
||||
|
||||
route_stack.pop()
|
||||
|
||||
|
||||
def _append_internal_types(lines, container):
|
||||
"""Handles creating the various constants, types, defines, etc.
|
||||
"""
|
||||
response_flags = container.get('response_flags', {})
|
||||
prefix = response_flags['define_prefix']
|
||||
for key, value in response_flags['bits'].items():
|
||||
define = value.get('define')
|
||||
lines.append(f'#define {prefix}_{define} (1ul << ({key}))')
|
||||
|
||||
# Add special
|
||||
lines.append(f'#define {prefix}_FAILED 0x00')
|
||||
lines.append('')
|
||||
|
||||
broadcast_messages = container.get('broadcast_messages', {})
|
||||
broadcast_prefix = broadcast_messages['define_prefix']
|
||||
for key, value in broadcast_messages['messages'].items():
|
||||
define = value.get('define')
|
||||
lines.append(f'#define {broadcast_prefix}_{define} {key}')
|
||||
|
||||
# Add special
|
||||
lines.append(f'#define {broadcast_prefix}_TOKEN 0xFFFF')
|
||||
lines.append('')
|
||||
|
||||
additional_types = {}
|
||||
types = container.get('type_definitions', {})
|
||||
for key, value in types.items():
|
||||
data_type = _get_c_type(value['type'])
|
||||
additional_types[key] = f'xap_{key}_t'
|
||||
|
||||
for key, value in types.items():
|
||||
data_type = _get_c_type(value['type'])
|
||||
if data_type == 'struct':
|
||||
members = value['struct_members']
|
||||
|
||||
lines.append(f'typedef {data_type} {{')
|
||||
for member in members:
|
||||
member_name = member["name"]
|
||||
member_type = _get_c_type(member["type"])
|
||||
if member_type == 'unknown':
|
||||
member_type = additional_types[member["type"]]
|
||||
lines.append(f' {member_type} {member_name};')
|
||||
lines.append(f'}} __attribute__((__packed__)) xap_{key}_t;')
|
||||
|
||||
req_len = value['struct_length']
|
||||
lines.append(f'_Static_assert(sizeof(xap_{key}_t) == {req_len}, "xap_{key}_t needs to be {req_len} bytes in size");')
|
||||
else:
|
||||
lines.append(f'typedef {data_type} xap_{key}_t;')
|
||||
|
||||
|
||||
def generate_header(output_file, keyboard):
|
||||
"""Generates the XAP protocol header file, generated during normal build.
|
||||
"""
|
||||
xap_defs = latest_xap_defs()
|
||||
|
||||
# Preamble
|
||||
lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '#pragma once', '']
|
||||
|
||||
# Versions
|
||||
prog = re.compile(r'^(\d+)\.(\d+)\.(\d+)')
|
||||
b = prog.match(xap_defs['version'])
|
||||
lines.append(f'#define XAP_BCD_VERSION 0x{int(b.group(1)):02d}{int(b.group(2)):02d}{int(b.group(3)):04d}ul')
|
||||
b = prog.match(git_get_version() or "")
|
||||
lines.append(f'#define QMK_BCD_VERSION 0x{int(b.group(1)):02d}{int(b.group(2)):02d}{int(b.group(3)):04d}ul')
|
||||
keyboard_id = fnv1a_32(bytes(keyboard, 'utf-8'))
|
||||
lines.append(f'#define XAP_KEYBOARD_IDENTIFIER 0x{keyboard_id:08X}ul')
|
||||
lines.append('')
|
||||
|
||||
# Types
|
||||
_append_internal_types(lines, xap_defs)
|
||||
lines.append('')
|
||||
_append_route_types(lines, xap_defs)
|
||||
lines.append('')
|
||||
|
||||
# Append the route and command defines
|
||||
_append_route_defines(lines, xap_defs)
|
||||
lines.append('')
|
||||
_append_route_masks(lines, xap_defs)
|
||||
lines.append('')
|
||||
_append_route_capabilities(lines, xap_defs)
|
||||
lines.append('')
|
||||
|
||||
dump_lines(output_file, lines)
|
41
lib/python/qmk/xap/gen_firmware/info_generator.py
Normal file
41
lib/python/qmk/xap/gen_firmware/info_generator.py
Normal file
|
@ -0,0 +1,41 @@
|
|||
"""This script generates the XAP info.json payload header to be compiled into QMK.
|
||||
"""
|
||||
import json
|
||||
import gzip
|
||||
|
||||
from qmk.info import keymap_json
|
||||
from qmk.commands import get_chunks, dump_lines
|
||||
|
||||
from qmk.constants import GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE
|
||||
|
||||
|
||||
def generate_info(output_file, keyboard, keymap):
|
||||
# Build the info.json file
|
||||
km_info_json = keymap_json(keyboard, keymap)
|
||||
|
||||
# TODO: Munge to XAP requirements
|
||||
del km_info_json['config_h_features']
|
||||
|
||||
# Minify
|
||||
str_data = json.dumps(km_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', '']
|
||||
|
||||
# Gen output file
|
||||
lines.append('static const unsigned char info_json_gz[] PROGMEM = {')
|
||||
lines.append(data)
|
||||
lines.append('};')
|
||||
lines.append(f'#define INFO_JSON_GZ_LEN {data_len}')
|
||||
|
||||
dump_lines(output_file, lines)
|
283
lib/python/qmk/xap/gen_firmware/inline_generator.py
Executable file
283
lib/python/qmk/xap/gen_firmware/inline_generator.py
Executable file
|
@ -0,0 +1,283 @@
|
|||
"""This script generates the XAP protocol generated header to be compiled into QMK.
|
||||
"""
|
||||
from qmk.casing import to_snake
|
||||
from qmk.commands import dump_lines
|
||||
from qmk.constants import GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE
|
||||
from qmk.xap.common import latest_xap_defs, route_conditions
|
||||
|
||||
|
||||
def _get_c_type(xap_type):
|
||||
if xap_type == 'bool':
|
||||
return 'bool'
|
||||
elif xap_type == 'u8':
|
||||
return 'uint8_t'
|
||||
elif xap_type == 'u16':
|
||||
return 'uint16_t'
|
||||
elif xap_type == 'u32':
|
||||
return 'uint32_t'
|
||||
elif xap_type == 'u64':
|
||||
return 'uint64_t'
|
||||
elif xap_type == 'struct':
|
||||
return 'struct'
|
||||
elif xap_type == 'string':
|
||||
return 'const char *'
|
||||
return 'unknown'
|
||||
|
||||
|
||||
def _get_c_size(xap_type):
|
||||
if xap_type == 'u8':
|
||||
return 'sizeof(uint8_t)'
|
||||
elif xap_type == 'u16':
|
||||
return 'sizeof(uint16_t)'
|
||||
elif xap_type == 'u32':
|
||||
return 'sizeof(uint32_t)'
|
||||
elif xap_type == 'u64':
|
||||
return 8
|
||||
elif xap_type == 'u8[32]':
|
||||
return 32
|
||||
return 0
|
||||
|
||||
|
||||
def _get_route_type(container):
|
||||
if 'routes' in container:
|
||||
return 'XAP_ROUTE'
|
||||
elif 'return_execute' in container:
|
||||
return 'XAP_EXECUTE'
|
||||
elif 'return_value' in container:
|
||||
if container['return_type'] == 'u8':
|
||||
return 'XAP_VALUE'
|
||||
elif 'return_constant' in container:
|
||||
if container['return_type'] == 'u8':
|
||||
return 'XAP_CONST_MEM'
|
||||
elif container['return_type'] == 'u16':
|
||||
return 'XAP_CONST_MEM'
|
||||
elif container['return_type'] == 'u32':
|
||||
return 'XAP_CONST_MEM'
|
||||
elif container['return_type'] == 'struct':
|
||||
return 'XAP_CONST_MEM'
|
||||
elif container['return_type'] == 'string':
|
||||
return 'XAP_CONST_MEM'
|
||||
elif 'return_getter' in container:
|
||||
if container['return_type'] == 'u32':
|
||||
return 'XAP_GETTER'
|
||||
return 'UNSUPPORTED'
|
||||
|
||||
|
||||
def _append_routing_table_declaration(lines, container, container_id, route_stack):
|
||||
route_stack.append(container)
|
||||
|
||||
route_name = to_snake('_'.join([r['define'] for r in route_stack]))
|
||||
|
||||
if 'routes' in container:
|
||||
pass
|
||||
|
||||
elif 'return_execute' in container:
|
||||
execute = container['return_execute']
|
||||
lines.append(f'bool xap_respond_{execute}(xap_token_t token, const uint8_t *data, size_t data_len);')
|
||||
|
||||
# elif 'return_value' in container:
|
||||
# value = container['return_value']
|
||||
# return_type = container['return_type']
|
||||
# lines.append('')
|
||||
# lines.append(f'{_get_c_type(return_type)} {value} = 0;')
|
||||
|
||||
elif 'return_constant' in container:
|
||||
|
||||
if container['return_type'] == 'u8':
|
||||
constant = container['return_constant']
|
||||
lines.append('')
|
||||
lines.append(f'static const uint8_t {route_name}_data PROGMEM = {constant};')
|
||||
|
||||
elif container['return_type'] == 'u16':
|
||||
constant = container['return_constant']
|
||||
lines.append('')
|
||||
lines.append(f'static const uint16_t {route_name}_data PROGMEM = {constant};')
|
||||
|
||||
elif container['return_type'] == 'u32':
|
||||
constant = container['return_constant']
|
||||
lines.append('')
|
||||
lines.append(f'static const uint32_t {route_name}_data PROGMEM = {constant};')
|
||||
|
||||
elif container['return_type'] == 'struct':
|
||||
lines.append('')
|
||||
lines.append(f'static const {route_name}_t {route_name}_data PROGMEM = {{')
|
||||
|
||||
for constant in container['return_constant']:
|
||||
lines.append(f' {constant},')
|
||||
|
||||
lines.append('};')
|
||||
|
||||
elif container['return_type'] == 'string':
|
||||
constant = container['return_constant']
|
||||
lines.append('')
|
||||
lines.append(f'static const char {route_name}_str[] PROGMEM = {constant};')
|
||||
|
||||
elif 'return_getter' in container:
|
||||
|
||||
if container['return_type'] == 'u32':
|
||||
lines.append('')
|
||||
lines.append(f'extern uint32_t {route_name}_getter(void);')
|
||||
|
||||
elif container['return_type'] == 'struct':
|
||||
pass
|
||||
|
||||
route_stack.pop()
|
||||
|
||||
|
||||
def _append_routing_table_entry_flags(lines, container, container_id, route_stack):
|
||||
pem_map = {
|
||||
None: 'ROUTE_PERMISSIONS_INSECURE',
|
||||
'secure': 'ROUTE_PERMISSIONS_SECURE',
|
||||
'ignore': 'ROUTE_PERMISSIONS_IGNORE',
|
||||
}
|
||||
|
||||
is_secure = pem_map[container.get('permissions', None)]
|
||||
|
||||
lines.append(' .flags = {')
|
||||
lines.append(f' .type = {_get_route_type(container)},')
|
||||
lines.append(f' .secure = {is_secure},')
|
||||
lines.append(' },')
|
||||
|
||||
|
||||
def _append_routing_table_entry_route(lines, container, container_id, route_stack):
|
||||
route_name = to_snake('_'.join([r['define'] for r in route_stack]))
|
||||
lines.append(f' .child_routes = {route_name}_table,')
|
||||
lines.append(f' .child_routes_len = sizeof({route_name}_table)/sizeof(xap_route_t),')
|
||||
|
||||
|
||||
def _append_routing_table_entry_execute(lines, container, container_id, route_stack):
|
||||
value = container['return_execute']
|
||||
lines.append(f' .handler = xap_respond_{value},')
|
||||
|
||||
|
||||
def _append_routing_table_entry_value(lines, container, container_id, route_stack):
|
||||
value = container['return_value']
|
||||
lines.append(f' .const_data = &{value},')
|
||||
lines.append(f' .const_data_len = sizeof({value}),')
|
||||
|
||||
|
||||
def _append_routing_table_entry_u32getter(lines, container, container_id, route_stack):
|
||||
route_name = to_snake('_'.join([r['define'] for r in route_stack]))
|
||||
lines.append(f' .u32getter = &{route_name}_getter,')
|
||||
|
||||
|
||||
def _append_routing_table_entry_const_data(lines, container, container_id, route_stack):
|
||||
route_name = to_snake('_'.join([r['define'] for r in route_stack]))
|
||||
lines.append(f' .const_data = &{route_name}_data,')
|
||||
lines.append(f' .const_data_len = sizeof({route_name}_data),')
|
||||
|
||||
|
||||
def _append_routing_table_entry_string(lines, container, container_id, route_stack):
|
||||
route_name = to_snake('_'.join([r['define'] for r in route_stack]))
|
||||
lines.append(f' .const_data = {route_name}_str,')
|
||||
lines.append(f' .const_data_len = sizeof({route_name}_str) - 1,')
|
||||
|
||||
|
||||
def _append_routing_table_entry(lines, container, container_id, route_stack):
|
||||
route_stack.append(container)
|
||||
route_name = '_'.join([r['define'] for r in route_stack])
|
||||
condition = route_conditions(route_stack)
|
||||
|
||||
if condition:
|
||||
lines.append(f'#if {condition}')
|
||||
|
||||
lines.append(f' [{route_name}] = {{')
|
||||
|
||||
_append_routing_table_entry_flags(lines, container, container_id, route_stack)
|
||||
if 'routes' in container:
|
||||
_append_routing_table_entry_route(lines, container, container_id, route_stack)
|
||||
elif 'return_execute' in container:
|
||||
_append_routing_table_entry_execute(lines, container, container_id, route_stack)
|
||||
elif 'return_value' in container:
|
||||
_append_routing_table_entry_value(lines, container, container_id, route_stack)
|
||||
elif 'return_constant' in container:
|
||||
if container['return_type'] == 'u8':
|
||||
_append_routing_table_entry_const_data(lines, container, container_id, route_stack)
|
||||
elif container['return_type'] == 'u16':
|
||||
_append_routing_table_entry_const_data(lines, container, container_id, route_stack)
|
||||
elif container['return_type'] == 'u32':
|
||||
_append_routing_table_entry_const_data(lines, container, container_id, route_stack)
|
||||
elif container['return_type'] == 'struct':
|
||||
_append_routing_table_entry_const_data(lines, container, container_id, route_stack)
|
||||
elif container['return_type'] == 'string':
|
||||
_append_routing_table_entry_string(lines, container, container_id, route_stack)
|
||||
elif 'return_getter' in container:
|
||||
if container['return_type'] == 'u32':
|
||||
_append_routing_table_entry_u32getter(lines, container, container_id, route_stack)
|
||||
|
||||
lines.append(' },')
|
||||
|
||||
if condition:
|
||||
lines.append(f'#endif // {condition}')
|
||||
|
||||
route_stack.pop()
|
||||
|
||||
|
||||
def _append_routing_tables(lines, container, container_id=None, route_stack=None):
|
||||
"""Handles building the list of the XAP routes, combining parent and child names together, as well as the route number.
|
||||
"""
|
||||
if route_stack is None:
|
||||
route_stack = [container]
|
||||
else:
|
||||
route_stack.append(container)
|
||||
|
||||
route_name = to_snake('_'.join([r['define'] for r in route_stack]))
|
||||
condition = route_conditions(route_stack)
|
||||
|
||||
if 'routes' in container:
|
||||
for route_id in container['routes']:
|
||||
route = container['routes'][route_id]
|
||||
_append_routing_tables(lines, route, route_id, route_stack)
|
||||
|
||||
for route_id in container['routes']:
|
||||
route = container['routes'][route_id]
|
||||
_append_routing_table_declaration(lines, route, route_id, route_stack)
|
||||
|
||||
lines.append('')
|
||||
if condition:
|
||||
lines.append(f'#if {condition}')
|
||||
|
||||
lines.append(f'static const xap_route_t {route_name}_table[] PROGMEM = {{')
|
||||
|
||||
for route_id in container['routes']:
|
||||
route = container['routes'][route_id]
|
||||
_append_routing_table_entry(lines, route, route_id, route_stack)
|
||||
|
||||
lines.append('};')
|
||||
|
||||
if condition:
|
||||
lines.append(f'#endif // {condition}')
|
||||
lines.append('')
|
||||
|
||||
route_stack.pop()
|
||||
|
||||
|
||||
def _append_broadcast_messages(lines, container):
|
||||
"""TODO:
|
||||
"""
|
||||
broadcast_messages = container.get('broadcast_messages', {})
|
||||
broadcast_prefix = broadcast_messages['define_prefix']
|
||||
for key, value in broadcast_messages['messages'].items():
|
||||
define = value.get('define')
|
||||
name = to_snake(f'{broadcast_prefix}_{define}')
|
||||
|
||||
if 'return_type' in value:
|
||||
ret_type = _get_c_type(value['return_type'])
|
||||
lines.append(f'void {name}({ret_type} value) {{ xap_broadcast({key}, &value, sizeof(value)); }}')
|
||||
else:
|
||||
lines.append(f'void {name}(const void *data, size_t length){{ xap_broadcast({key}, data, length); }}')
|
||||
|
||||
|
||||
def generate_inline(output_file):
|
||||
"""Generates the XAP protocol header file, generated during normal build.
|
||||
"""
|
||||
xap_defs = latest_xap_defs()
|
||||
|
||||
# Preamble
|
||||
lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '']
|
||||
|
||||
# Add all the generated code
|
||||
_append_broadcast_messages(lines, xap_defs)
|
||||
_append_routing_tables(lines, xap_defs)
|
||||
|
||||
dump_lines(output_file, lines)
|
|
@ -81,6 +81,10 @@ void eeconfig_init_quantum(void) {
|
|||
// properly re-initialized.
|
||||
via_eeprom_set_valid(false);
|
||||
eeconfig_init_via();
|
||||
#elif defined(XAP_ENABLE)
|
||||
// TODO: define XAP reset behaviour
|
||||
void dynamic_keymap_reset(void);
|
||||
dynamic_keymap_reset();
|
||||
#endif
|
||||
|
||||
eeconfig_init_kb();
|
||||
|
|
|
@ -552,6 +552,10 @@ void quantum_task(void) {
|
|||
#ifdef SECURE_ENABLE
|
||||
secure_task();
|
||||
#endif
|
||||
|
||||
#ifdef XAP_ENABLE
|
||||
xap_event_task();
|
||||
#endif
|
||||
}
|
||||
|
||||
/** \brief Keyboard task: Do keyboard routine jobs
|
||||
|
|
|
@ -213,6 +213,10 @@ extern layer_state_t layer_state;
|
|||
# include "joystick.h"
|
||||
#endif
|
||||
|
||||
#ifdef XAP_ENABLE
|
||||
# include "xap.h"
|
||||
#endif
|
||||
|
||||
#ifdef VIA_ENABLE
|
||||
# include "via.h"
|
||||
#endif
|
||||
|
|
172
quantum/xap/xap.c
Normal file
172
quantum/xap/xap.c
Normal file
|
@ -0,0 +1,172 @@
|
|||
/* 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"
|
||||
|
||||
#include "info_json_gz.h"
|
||||
bool get_info_json_chunk(uint16_t offset, uint8_t *data, uint8_t data_len) {
|
||||
if (offset + data_len > INFO_JSON_GZ_LEN) {
|
||||
data_len = INFO_JSON_GZ_LEN - offset;
|
||||
}
|
||||
|
||||
memcpy_P(data, &info_json_gz[offset], data_len);
|
||||
return true;
|
||||
}
|
||||
|
||||
#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_GETTER,
|
||||
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,
|
||||
ROUTE_PERMISSIONS_IGNORE,
|
||||
} 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_GETTER
|
||||
uint32_t (*u32getter)(void);
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
if (secure_is_unlocking() && (route->flags.type != XAP_ROUTE) && (route->flags.secure != ROUTE_PERMISSIONS_IGNORE)) {
|
||||
xap_respond_failure(token, XAP_RESPONSE_FLAG_UNLOCK_IN_PROGRESS);
|
||||
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_GETTER:
|
||||
if (route.u32getter != NULL) {
|
||||
const uint32_t ret = (route.u32getter)();
|
||||
xap_respond_data(token, &ret, sizeof(ret));
|
||||
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);
|
||||
}
|
||||
|
||||
void xap_event_task(void) {
|
||||
#ifdef SECURE_ENABLE
|
||||
static secure_status_t last_status = -1;
|
||||
|
||||
secure_status_t status = secure_get_status();
|
||||
if (last_status != status) {
|
||||
last_status = status;
|
||||
xap_broadcast_secure_status(status);
|
||||
}
|
||||
#endif
|
||||
}
|
40
quantum/xap/xap.h
Normal file
40
quantum/xap/xap.h
Normal file
|
@ -0,0 +1,40 @@
|
|||
/* 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
|
||||
|
||||
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);
|
||||
|
||||
void xap_event_task(void);
|
155
quantum/xap/xap_handlers.c
Normal file
155
quantum/xap/xap_handlers.c
Normal file
|
@ -0,0 +1,155 @@
|
|||
/* 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 "hardware_id.h"
|
||||
#include "secure.h"
|
||||
#ifndef SECURE_ENABLE
|
||||
# define secure_get_status() SECURE_UNLOCKED
|
||||
# define secure_request_unlock()
|
||||
# define secure_lock()
|
||||
#endif
|
||||
|
||||
void xap_respond_success(xap_token_t token) {
|
||||
xap_send(token, XAP_RESPONSE_FLAG_SUCCESS, NULL, 0);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
uint32_t xap_route_qmk_ffffffffffffffff_getter(void) {
|
||||
return 0x12345678;
|
||||
}
|
||||
|
||||
bool xap_respond_get_info_json_chunk(xap_token_t token, const void *data, size_t length) {
|
||||
if (length != sizeof(uint16_t)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16_t offset = *((uint16_t *)data);
|
||||
xap_route_qmk_info_query_t ret = {0};
|
||||
|
||||
bool get_info_json_chunk(uint16_t offset, uint8_t * data, uint8_t data_len);
|
||||
get_info_json_chunk(offset, (uint8_t *)&ret, sizeof(ret));
|
||||
|
||||
return xap_respond_data(token, &ret, sizeof(ret));
|
||||
}
|
||||
|
||||
bool xap_respond_secure_status(xap_token_t token, const void *data, size_t length) {
|
||||
uint8_t ret = secure_get_status();
|
||||
return xap_respond_data(token, &ret, sizeof(ret));
|
||||
}
|
||||
|
||||
bool xap_respond_secure_unlock(xap_token_t token, const void *data, size_t length) {
|
||||
secure_request_unlock();
|
||||
return xap_respond_data(token, NULL, 0);
|
||||
}
|
||||
|
||||
bool xap_respond_secure_lock(xap_token_t token, const void *data, size_t length) {
|
||||
secure_lock();
|
||||
return xap_respond_data(token, NULL, 0);
|
||||
}
|
||||
|
||||
// TODO: how to set this if "custom" is just an empty stub
|
||||
#ifndef BOOTLOADER_JUMP_SUPPORTED
|
||||
# define BOOTLOADER_JUMP_SUPPORTED
|
||||
#endif
|
||||
|
||||
#ifdef BOOTLOADER_JUMP_SUPPORTED
|
||||
bool xap_respond_request_bootloader_jump(xap_token_t token, const void *data, size_t length) {
|
||||
extern uint8_t secure_status;
|
||||
uint8_t ret = secure_status == 2;
|
||||
|
||||
// TODO: post to deferred queue so this request can return?
|
||||
bool res = xap_respond_data(token, &ret, sizeof(ret));
|
||||
reset_keyboard();
|
||||
return res;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool xap_respond_get_hardware_id(xap_token_t token, const void *data, size_t length) {
|
||||
hardware_id_t ret = get_hardware_id();
|
||||
return xap_respond_data(token, &ret, sizeof(ret));
|
||||
}
|
||||
|
||||
#if ((defined(DYNAMIC_KEYMAP_ENABLE)))
|
||||
bool xap_respond_dynamic_keymap_get_keycode(xap_token_t token, const void *data, size_t length) {
|
||||
if (length != sizeof(xap_route_dynamic_keymap_get_keymap_keycode_arg_t)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
xap_route_dynamic_keymap_get_keymap_keycode_arg_t *arg = (xap_route_dynamic_keymap_get_keymap_keycode_arg_t *)data;
|
||||
|
||||
uint16_t keycode = dynamic_keymap_get_keycode(arg->layer, arg->row, arg->column);
|
||||
return xap_respond_data(token, &keycode, sizeof(keycode));
|
||||
}
|
||||
|
||||
bool xap_respond_dynamic_keymap_set_keycode(xap_token_t token, const void *data, size_t length) {
|
||||
if (length != sizeof(xap_route_dynamic_keymap_set_keymap_keycode_arg_t)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
xap_route_dynamic_keymap_set_keymap_keycode_arg_t *arg = (xap_route_dynamic_keymap_set_keymap_keycode_arg_t *)data;
|
||||
|
||||
dynamic_keymap_set_keycode(arg->layer, arg->row, arg->column, arg->keycode);
|
||||
xap_respond_success(token);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ((defined(ENCODER_MAP_ENABLE)))
|
||||
bool xap_respond_dynamic_encoder_get_keycode(xap_token_t token, const void *data, size_t length) {
|
||||
if (length != sizeof(xap_route_dynamic_encoder_get_encoder_keycode_arg_t)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
xap_route_dynamic_encoder_get_encoder_keycode_arg_t *arg = (xap_route_dynamic_encoder_get_encoder_keycode_arg_t *)data;
|
||||
|
||||
uint16_t keycode = dynamic_keymap_get_encoder(arg->layer, arg->encoder, arg->clockwise);
|
||||
return xap_respond_data(token, &keycode, sizeof(keycode));
|
||||
}
|
||||
|
||||
bool xap_respond_dynamic_encoder_set_keycode(xap_token_t token, const void *data, size_t length) {
|
||||
if (length != sizeof(xap_route_dynamic_encoder_set_encoder_keycode_arg_t)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
xap_route_dynamic_encoder_set_encoder_keycode_arg_t *arg = (xap_route_dynamic_encoder_set_encoder_keycode_arg_t *)data;
|
||||
|
||||
dynamic_keymap_set_encoder(arg->layer, arg->encoder, arg->clockwise, arg->keycode);
|
||||
xap_respond_success(token);
|
||||
return true;
|
||||
}
|
||||
#endif
|
|
@ -2,8 +2,10 @@
|
|||
appdirs
|
||||
argcomplete
|
||||
colorama
|
||||
fnvhash
|
||||
hid
|
||||
hjson
|
||||
Jinja2
|
||||
jsonschema>=4
|
||||
milc>=1.4.2
|
||||
pygments
|
||||
|
|
|
@ -74,6 +74,10 @@ void virtser_task(void);
|
|||
void raw_hid_task(void);
|
||||
#endif
|
||||
|
||||
#ifdef XAP_ENABLE
|
||||
void xap_task(void);
|
||||
#endif
|
||||
|
||||
#ifdef CONSOLE_ENABLE
|
||||
void console_task(void);
|
||||
#endif
|
||||
|
@ -218,4 +222,7 @@ void protocol_post_task(void) {
|
|||
#ifdef RAW_ENABLE
|
||||
raw_hid_task();
|
||||
#endif
|
||||
#ifdef XAP_ENABLE
|
||||
xap_task();
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -53,6 +53,10 @@ extern keymap_config_t keymap_config;
|
|||
# include "joystick.h"
|
||||
#endif
|
||||
|
||||
#ifdef XAP_ENABLE
|
||||
# include "xap.h"
|
||||
#endif
|
||||
|
||||
/* ---------------------------------------------------------
|
||||
* Global interface variables and declarations
|
||||
* ---------------------------------------------------------
|
||||
|
@ -313,6 +317,9 @@ typedef struct {
|
|||
#ifdef RAW_ENABLE
|
||||
usb_driver_config_t raw_driver;
|
||||
#endif
|
||||
#ifdef XAP_ENABLE
|
||||
usb_driver_config_t xap_driver;
|
||||
#endif
|
||||
#ifdef MIDI_ENABLE
|
||||
usb_driver_config_t midi_driver;
|
||||
#endif
|
||||
|
@ -350,6 +357,14 @@ static usb_driver_configs_t drivers = {
|
|||
.raw_driver = QMK_USB_DRIVER_CONFIG(RAW, 0, false),
|
||||
#endif
|
||||
|
||||
#ifdef XAP_ENABLE
|
||||
# define XAP_IN_CAPACITY 4
|
||||
# define XAP_OUT_CAPACITY 4
|
||||
# define XAP_IN_MODE USB_EP_MODE_TYPE_INTR
|
||||
# define XAP_OUT_MODE USB_EP_MODE_TYPE_INTR
|
||||
.xap_driver = QMK_USB_DRIVER_CONFIG(XAP, 0, false),
|
||||
#endif
|
||||
|
||||
#ifdef MIDI_ENABLE
|
||||
# define MIDI_STREAM_IN_CAPACITY 4
|
||||
# define MIDI_STREAM_OUT_CAPACITY 4
|
||||
|
@ -1115,6 +1130,69 @@ 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;
|
||||
}
|
||||
chnWrite(&drivers.xap_driver.driver, 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];
|
||||
size_t size = 0;
|
||||
do {
|
||||
size_t size = chnReadTimeout(&drivers.xap_driver.driver, buffer, sizeof(buffer), TIME_IMMEDIATE);
|
||||
if (size > 0) {
|
||||
xap_receive_base(buffer);
|
||||
}
|
||||
} while (size > 0);
|
||||
}
|
||||
#endif // XAP_ENABLE
|
||||
|
||||
#ifdef MIDI_ENABLE
|
||||
|
||||
void send_midi_packet(MIDI_EventPacket_t *event) {
|
||||
|
|
|
@ -86,6 +86,10 @@ extern keymap_config_t keymap_config;
|
|||
# include "raw_hid.h"
|
||||
#endif
|
||||
|
||||
#ifdef XAP_ENABLE
|
||||
# include "xap.h"
|
||||
#endif
|
||||
|
||||
#ifdef JOYSTICK_ENABLE
|
||||
# include "joystick.h"
|
||||
#endif
|
||||
|
@ -209,6 +213,105 @@ static 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);
|
||||
}
|
||||
}
|
||||
|
||||
static 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
|
||||
******************************************************************************/
|
||||
|
@ -471,6 +574,12 @@ void EVENT_USB_Device_ConfigurationChanged(void) {
|
|||
ConfigSuccess &= Endpoint_ConfigureEndpoint((RAW_OUT_EPNUM | ENDPOINT_DIR_OUT), EP_TYPE_INTERRUPT, RAW_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
|
||||
|
||||
#ifdef CONSOLE_ENABLE
|
||||
/* Setup console endpoint */
|
||||
ConfigSuccess &= Endpoint_ConfigureEndpoint((CONSOLE_IN_EPNUM | ENDPOINT_DIR_IN), EP_TYPE_INTERRUPT, CONSOLE_EPSIZE, 1);
|
||||
|
@ -1096,6 +1205,10 @@ void protocol_post_task(void) {
|
|||
raw_hid_task();
|
||||
#endif
|
||||
|
||||
#ifdef XAP_ENABLE
|
||||
xap_task();
|
||||
#endif
|
||||
|
||||
#if !defined(INTERRUPT_CONTROL_ENDPOINT)
|
||||
USB_USBTask();
|
||||
#endif
|
||||
|
|
|
@ -322,6 +322,30 @@ const USB_Descriptor_HIDReport_Datatype_t PROGMEM RawReport[] = {
|
|||
};
|
||||
#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
|
||||
|
||||
#ifdef CONSOLE_ENABLE
|
||||
const USB_Descriptor_HIDReport_Datatype_t PROGMEM ConsoleReport[] = {
|
||||
HID_RI_USAGE_PAGE(16, 0xFF31), // Vendor Defined (PJRC Teensy compatible)
|
||||
|
@ -554,6 +578,56 @@ const USB_Descriptor_Configuration_t PROGMEM ConfigurationDescriptor = {
|
|||
},
|
||||
#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
|
||||
|
||||
#if defined(MOUSE_ENABLE) && !defined(MOUSE_SHARED_EP)
|
||||
/*
|
||||
* Mouse
|
||||
|
@ -1148,6 +1222,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
|
||||
|
||||
#ifdef CONSOLE_ENABLE
|
||||
case CONSOLE_INTERFACE:
|
||||
Address = &ConfigurationDescriptor.Console_HID;
|
||||
|
@ -1205,6 +1287,14 @@ uint16_t get_usb_descriptor(const uint16_t wValue, const uint16_t wIndex, const
|
|||
break;
|
||||
#endif
|
||||
|
||||
#ifdef XAP_ENABLE
|
||||
case XAP_INTERFACE:
|
||||
Address = &XapReport;
|
||||
Size = sizeof(XapReport);
|
||||
|
||||
break;
|
||||
#endif
|
||||
|
||||
#ifdef CONSOLE_ENABLE
|
||||
case CONSOLE_INTERFACE:
|
||||
Address = &ConsoleReport;
|
||||
|
|
|
@ -75,6 +75,14 @@ typedef struct {
|
|||
USB_Descriptor_Endpoint_t Raw_OUTEndpoint;
|
||||
#endif
|
||||
|
||||
#ifdef XAP_ENABLE
|
||||
// Mouse 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
|
||||
|
||||
#if defined(MOUSE_ENABLE) && !defined(MOUSE_SHARED_EP)
|
||||
// Mouse HID Interface
|
||||
USB_Descriptor_Interface_t Mouse_Interface;
|
||||
|
@ -162,6 +170,10 @@ enum usb_interfaces {
|
|||
RAW_INTERFACE,
|
||||
#endif
|
||||
|
||||
#ifdef XAP_ENABLE
|
||||
XAP_INTERFACE,
|
||||
#endif
|
||||
|
||||
#if defined(MOUSE_ENABLE) && !defined(MOUSE_SHARED_EP)
|
||||
MOUSE_INTERFACE,
|
||||
#endif
|
||||
|
@ -223,6 +235,15 @@ enum usb_endpoints {
|
|||
# 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 SHARED_EP_ENABLE
|
||||
SHARED_IN_EPNUM = NEXT_EPNUM,
|
||||
#endif
|
||||
|
@ -296,7 +317,7 @@ enum usb_endpoints {
|
|||
// TODO - ARM_ATSAM
|
||||
|
||||
#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
|
||||
|
@ -309,5 +330,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 void** const DescriptorAddress);
|
||||
|
|
|
@ -39,6 +39,10 @@ void console_task(void);
|
|||
void raw_hid_task(void);
|
||||
#endif
|
||||
|
||||
#ifdef XAP_ENABLE
|
||||
void xap_task(void);
|
||||
#endif
|
||||
|
||||
/* This is from main.c of USBaspLoader */
|
||||
static void initForUsbConnectivity(void) {
|
||||
uint8_t i = 0;
|
||||
|
@ -163,6 +167,14 @@ void protocol_task(void) {
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifdef XAP_ENABLE
|
||||
usbPoll();
|
||||
|
||||
if (usbConfiguration && usbInterruptIsReady4()) {
|
||||
xap_task();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONSOLE_ENABLE
|
||||
usbPoll();
|
||||
|
||||
|
|
|
@ -35,6 +35,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
# include "raw_hid.h"
|
||||
#endif
|
||||
|
||||
#ifdef XAP_ENABLE
|
||||
# include "xap.h"
|
||||
# include <string.h>
|
||||
#endif
|
||||
|
||||
#if defined(CONSOLE_ENABLE)
|
||||
# define RBUF_SIZE 128
|
||||
# include "ring_buffer.h"
|
||||
|
@ -60,6 +65,10 @@ enum usb_interfaces {
|
|||
RAW_INTERFACE = NEXT_INTERFACE,
|
||||
#endif
|
||||
|
||||
#ifdef XAP_ENABLE
|
||||
XAP_INTERFACE = NEXT_INTERFACE,
|
||||
#endif
|
||||
|
||||
#if defined(SHARED_EP_ENABLE) && !defined(KEYBOARD_SHARED_EP)
|
||||
SHARED_INTERFACE = NEXT_INTERFACE,
|
||||
#endif
|
||||
|
@ -137,7 +146,7 @@ void raw_hid_send(uint8_t *data, uint8_t length) {
|
|||
}
|
||||
|
||||
uint8_t *temp = data;
|
||||
for (uint8_t i = 0; i < 4; i++) {
|
||||
for (uint8_t i = 0; i < (RAW_BUFFER_SIZE / RAW_EPSIZE); i++) {
|
||||
while (!usbInterruptIsReady4()) {
|
||||
usbPoll();
|
||||
}
|
||||
|
@ -164,6 +173,85 @@ 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;
|
||||
}
|
||||
|
||||
uint8_t *temp = data;
|
||||
for (uint8_t i = 0; i < (XAP_BUFFER_SIZE / XAP_EPSIZE); i++) {
|
||||
while (!usbInterruptIsReady4()) {
|
||||
usbPoll();
|
||||
}
|
||||
usbSetInterrupt4(temp, 8);
|
||||
temp += 8;
|
||||
}
|
||||
while (!usbInterruptIsReady4()) {
|
||||
usbPoll();
|
||||
}
|
||||
usbSetInterrupt4(0, 0);
|
||||
}
|
||||
|
||||
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) {
|
||||
if (xap_output_received_bytes == XAP_BUFFER_SIZE) {
|
||||
xap_receive_base(xap_output_buffer);
|
||||
xap_output_received_bytes = 0;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/*------------------------------------------------------------------*
|
||||
* Console
|
||||
*------------------------------------------------------------------*/
|
||||
|
@ -402,6 +490,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
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------*
|
||||
|
@ -624,6 +730,29 @@ const PROGMEM uchar raw_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
|
||||
|
||||
#if defined(CONSOLE_ENABLE)
|
||||
const PROGMEM uchar console_hid_report[] = {
|
||||
0x06, 0x31, 0xFF, // Usage Page (Vendor Defined - PJRC Teensy compatible)
|
||||
|
@ -823,6 +952,56 @@ const PROGMEM usbConfigurationDescriptor_t usbConfigurationDescriptor = {
|
|||
},
|
||||
# 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
|
||||
|
||||
# ifdef SHARED_EP_ENABLE
|
||||
/*
|
||||
* Shared
|
||||
|
@ -975,6 +1154,13 @@ USB_PUBLIC usbMsgLen_t usbFunctionDescriptor(struct usbRequest *rq) {
|
|||
break;
|
||||
#endif
|
||||
|
||||
#if defined(XAP_ENABLE)
|
||||
case XAP_INTERFACE:
|
||||
usbMsgPtr = (usbMsgPtr_t)&usbConfigurationDescriptor.xapHID;
|
||||
len = sizeof(usbHIDDescriptor_t);
|
||||
break;
|
||||
#endif
|
||||
|
||||
#ifdef SHARED_EP_ENABLE
|
||||
case SHARED_INTERFACE:
|
||||
usbMsgPtr = (usbMsgPtr_t)&usbConfigurationDescriptor.sharedHID;
|
||||
|
@ -1007,6 +1193,13 @@ USB_PUBLIC usbMsgLen_t usbFunctionDescriptor(struct usbRequest *rq) {
|
|||
break;
|
||||
#endif
|
||||
|
||||
#if defined(XAP_ENABLE)
|
||||
case XAP_INTERFACE:
|
||||
usbMsgPtr = (usbMsgPtr_t)xap_report;
|
||||
len = sizeof(xap_report);
|
||||
break;
|
||||
#endif
|
||||
|
||||
#ifdef SHARED_EP_ENABLE
|
||||
case SHARED_INTERFACE:
|
||||
usbMsgPtr = (usbMsgPtr_t)shared_hid_report;
|
||||
|
|
|
@ -104,6 +104,13 @@ typedef struct usbConfigurationDescriptor {
|
|||
usbEndpointDescriptor_t rawOUTEndpoint;
|
||||
#endif
|
||||
|
||||
#if defined(XAP_ENABLE)
|
||||
usbInterfaceDescriptor_t xapInterface;
|
||||
usbHIDDescriptor_t xapHID;
|
||||
usbEndpointDescriptor_t xapINEndpoint;
|
||||
usbEndpointDescriptor_t xapOUTEndpoint;
|
||||
#endif
|
||||
|
||||
#if defined(SHARED_EP_ENABLE) && !defined(KEYBOARD_SHARED_EP)
|
||||
usbInterfaceDescriptor_t sharedInterface;
|
||||
usbHIDDescriptor_t sharedHID;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue