Compare commits
665 commits
Author | SHA1 | Date | |
---|---|---|---|
|
9b9dc839d2 | ||
|
f913f126b4 | ||
|
a65f6d33f7 | ||
|
6cbcce37e0 | ||
|
2828cb427c | ||
|
cf2d1eaee9 | ||
|
517eb82ed6 | ||
|
71208ccb09 | ||
|
ced7094ddd | ||
|
6ec0ff387b | ||
|
8b133897dc | ||
|
bf6f88182a | ||
|
8f7fe1c155 | ||
|
a23c40f921 | ||
|
eb98a4e917 | ||
|
74b84ff7cd | ||
|
218f67fa57 | ||
|
1d8a0b75fe | ||
|
b23f9ed0af | ||
|
8842fb9345 | ||
|
84ffb77b8f | ||
|
4fafb916bf | ||
|
b7333ba99a | ||
|
564fa77863 | ||
|
ee538c22b5 | ||
|
ec644bd932 | ||
|
d8d8f51848 | ||
|
d152bc43e2 | ||
|
a467771af9 | ||
|
7506a5a183 | ||
|
ea78aeee9a | ||
|
b87d48dac7 | ||
|
47c263cf8d | ||
|
436417ec7c | ||
|
6dffffab3a | ||
|
13af51006a | ||
|
559ec50325 | ||
|
17ce7bb67f | ||
|
063a5b8e7c | ||
|
25544107aa | ||
|
7bcd48f995 | ||
|
62e0f026ad | ||
|
063e1c20ba | ||
|
fba52bc924 | ||
|
234f47b503 | ||
|
6b6974ad4d | ||
|
48fa7473b8 | ||
|
23153f959b | ||
|
3f5a3b9d15 | ||
|
5854990f6f | ||
|
db93e65e07 | ||
|
8671acbfeb | ||
|
b78fa3fe6a | ||
|
20508a9e54 | ||
|
53f1516036 | ||
|
ba34fc1afe | ||
|
20933e0f15 | ||
|
fb64036e02 | ||
|
31d8978281 | ||
|
c2b2f9feac | ||
|
f0a8d431f6 | ||
|
2064651e27 | ||
|
2196d7cb9c | ||
|
b908b4c4d2 | ||
|
37481d637b | ||
|
1bf4da84dc | ||
|
f617d9af39 | ||
|
e82c364bf0 | ||
|
e3e0d96f0e | ||
|
c0469c7ea6 | ||
|
45a9ae2f20 | ||
|
a9e96caf2c | ||
|
7e43152849 | ||
|
6f805de99a | ||
|
88c9dbbccc | ||
|
5d9db07e3a | ||
|
fdc5f02ce2 | ||
|
fd05e07527 | ||
|
5ca47f9d3a | ||
|
a0d1dd9d75 | ||
|
27c85b0f40 | ||
|
16c590f324 | ||
|
b599b6ab25 | ||
|
3f5ff5a0c6 | ||
|
2020544d88 | ||
|
a065805946 | ||
|
fc5fcd909a | ||
|
b82de7a1b5 | ||
|
c01e62a7f0 | ||
|
0237e99669 | ||
|
204e13a466 | ||
|
4d5fb116e5 | ||
|
562e3ca35b | ||
|
c1b74164b7 | ||
|
8aa580d076 | ||
|
11185ecc14 | ||
|
f6d689a980 | ||
|
6c58f5098e | ||
|
4707117f7a | ||
|
b625a9c10d | ||
|
2b212b6308 | ||
|
89908ad1ca | ||
|
5399c9cc91 | ||
|
726d45ae68 | ||
|
1827417cbf | ||
|
d6fcb277ff | ||
|
2a6753904b | ||
|
ee318e5a26 | ||
|
2592402627 | ||
|
2273c5b4e0 | ||
|
129e9c3952 | ||
|
6361da77f4 | ||
|
6bd449d436 | ||
|
9fcfaa950a | ||
|
5835b478f1 | ||
|
4facfe9554 | ||
|
9505ca5362 | ||
|
6a6176a0aa | ||
|
33da381968 | ||
|
ac23283d9c | ||
|
943ee73b40 | ||
|
bc66b54073 | ||
|
f305edce4d | ||
|
284f7c7e16 | ||
|
ab60a0bba0 | ||
|
b782a1f778 | ||
|
370a7704e9 | ||
|
a2c4283f9d | ||
|
4564769745 | ||
|
78f28900e0 | ||
|
c22fedb5b2 | ||
|
90fc901624 | ||
|
2001fea9b9 | ||
|
223e01b762 | ||
|
ac8eb8acb3 | ||
|
a2f1ea37eb | ||
|
40d7920759 | ||
|
0c9bf637d5 | ||
|
d1d317ff4c | ||
|
39943571c4 | ||
|
b7998829d5 | ||
|
43adf84d65 | ||
|
313e7f64a3 | ||
|
890e25bbe2 | ||
|
fcb13dd0b5 | ||
|
d71026ccc3 | ||
|
cda2901457 | ||
|
d15db17acb | ||
|
21fc1b27c6 | ||
|
84deeb7e44 | ||
|
92e7cacb4c | ||
|
ac3fea3e0f | ||
|
ce9b36cbbf | ||
|
df7d58a12a | ||
|
6287939f23 | ||
|
103df1106b | ||
|
bdc2387a7d | ||
|
dab3a4fde0 | ||
|
d467ac8427 | ||
|
956bd3b7ca | ||
|
6b0bdc30de | ||
|
0b627d5e12 | ||
|
7d2f0172c4 | ||
|
45ff6e8256 | ||
|
36d87291dd | ||
|
e40a0615ca | ||
|
f9f7add22c | ||
|
1f2bbd3fdd | ||
|
8983559ee1 | ||
|
760b3b45e0 | ||
|
9296bcd920 | ||
|
2891daaf00 | ||
|
a7688b2549 | ||
|
3ed6f56cc2 | ||
|
d1bad9f098 | ||
|
e4c2489c04 | ||
|
a96aff3f27 | ||
|
c5498e34d2 | ||
|
ba7770a4e4 | ||
|
f6922d826c | ||
|
3eba8d5613 | ||
|
aba593d41a | ||
|
38f32f7afc | ||
|
8cc009946d | ||
|
e8ed843f44 | ||
|
8e8f5e8371 | ||
|
6c6950ab7f | ||
|
1a250df735 | ||
|
284fc9c3ab | ||
|
333d215a24 | ||
|
491949c303 | ||
|
0ee42261e6 | ||
|
90eb77d347 | ||
|
6732940f9d | ||
|
71a9aef016 | ||
|
9342369c67 | ||
|
ae3ea0fe81 | ||
|
dc9a57fee4 | ||
|
be2d25d9dc | ||
|
45d2a62f73 | ||
|
cbebe5a6a7 | ||
|
811dccc7be | ||
|
017829daec | ||
|
51bca87dd4 | ||
|
2f566af886 | ||
|
8a529cac45 | ||
|
4e9a2ca5f7 | ||
|
43ce7ab31f | ||
|
32d86cee7f | ||
|
d428f579e0 | ||
|
ebc9cb4ed1 | ||
|
14e3248a24 | ||
|
6f8e461cd1 | ||
|
452c0ba4fb | ||
|
55b4fa3a5a | ||
|
61c534947a | ||
|
762496d0b1 | ||
|
5afc776cec | ||
|
b6446026b5 | ||
|
066d7c10f1 | ||
|
ce55c73fac | ||
|
ed00870773 | ||
|
28c9593ce9 | ||
|
c7b6522c7e | ||
|
511c7647f7 | ||
|
114fadd58d | ||
|
47b17a15c6 | ||
|
5387e5068e | ||
|
0abb8ed990 | ||
|
58177d0670 | ||
|
60f84a89f3 | ||
|
91cf4a2b41 | ||
|
c0d291067a | ||
|
8ba396ad11 | ||
|
cc27d0b2dd | ||
|
1c5c6a7f78 | ||
|
ff2ca9cd26 | ||
|
fbeb7a870a | ||
|
555b28f9e5 | ||
|
bf31316957 | ||
|
17676d05c3 | ||
|
35faea32eb | ||
|
96284a6d63 | ||
|
7d522fb933 | ||
|
d255c2f57f | ||
|
3ff05e58df | ||
|
6b14a17b48 | ||
|
1f9200c3a7 | ||
|
ff96f67e92 | ||
|
84672f0950 | ||
|
f1de138ce9 | ||
|
a20454c83a | ||
|
ded347ae04 | ||
|
06f5a54fd5 | ||
|
6d53a07674 | ||
|
001b4f6e3c | ||
|
4d48300ade | ||
|
d923483769 | ||
|
1248afd956 | ||
|
72458dcd76 | ||
|
a06820a18d | ||
|
995921443a | ||
|
292995c8ba | ||
|
6579526ace | ||
|
f227208634 | ||
|
d5d72135e4 | ||
|
df0d0bd2ce | ||
|
9302ee0f81 | ||
|
9aadf14b31 | ||
|
03ca0cc8c9 | ||
|
33d779cc16 | ||
|
9a23ca668c | ||
|
08e93f5ea7 | ||
|
73dbb042bf | ||
|
b11c5702fa | ||
|
23f72ad16c | ||
|
0b53879fc6 | ||
|
d0ed9071a2 | ||
|
5cfb7ef396 | ||
|
ffdba839b0 | ||
|
12b2993fc2 | ||
|
f727692fd1 | ||
|
32404cbb77 | ||
|
61fa26a45d | ||
|
202be7f3b5 | ||
|
a3f4d8064f | ||
|
a40942394d | ||
|
6d93facc16 | ||
|
d83616dce4 | ||
|
f44a988476 | ||
|
429c592289 | ||
|
d1140dd23a | ||
|
9a9c5df2a7 | ||
|
3a1b0cdd2f | ||
|
b620124024 | ||
|
f2d56f5ca1 | ||
|
e04e31cde7 | ||
|
b1632946c9 | ||
|
17dbcedc8b | ||
|
d95d22201a | ||
|
d75330f9c1 | ||
|
a9468f385e | ||
|
d9b045a6fe | ||
|
89ddecb2de | ||
|
4a71fcc98b | ||
|
6567aecb58 | ||
|
cd1d67c930 | ||
|
0dd025de78 | ||
|
badad5c65f | ||
|
c8ee7abd7f | ||
|
384a5a07c2 | ||
|
b6c9366ce0 | ||
|
5ad3150c14 | ||
|
e243f47a6d | ||
|
b666febc72 | ||
|
eb53b08cb4 | ||
|
1e8c96cea3 | ||
|
8356cb615f | ||
|
4e87e4b074 | ||
|
7cac1ee2d1 | ||
|
349a42357d | ||
|
57741e11fd | ||
|
817b2ffb8c | ||
|
de71c9bc0f | ||
|
287ff0066b | ||
|
f6cf52c10b | ||
|
5ef912ead7 | ||
|
237c0fdc3e | ||
|
af5745832b | ||
|
1138b51961 | ||
|
da07c5e44a | ||
|
778e7683f5 | ||
|
25efe0a376 | ||
|
74115d42f5 | ||
|
421a1d1ff3 | ||
|
6e910f5250 | ||
|
b181f46f93 | ||
|
484ec12397 | ||
|
1fee0ae414 | ||
|
89e0d40e3b | ||
|
774d3ce428 | ||
|
38b540d233 | ||
|
0129d2a36a | ||
|
6099b08b76 | ||
|
eff75ce5b7 | ||
|
14dfba2eef | ||
|
f024fdb0bc | ||
|
d6773a133c | ||
|
6932cfa3a2 | ||
|
15ce8c895f | ||
|
7a1ad96e5e | ||
|
9bb91fa4a3 | ||
|
b43cef27b3 | ||
|
ad7d7a2ac7 | ||
|
cf85b91a7f | ||
|
a70f8bec44 | ||
|
6f1097225f | ||
|
476e64937f | ||
|
d89203326b | ||
|
81b68fd7dc | ||
|
5fe5d49cda | ||
|
72c9cb0681 | ||
|
4875711c7b | ||
|
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 |
65 changed files with 5817 additions and 42 deletions
|
@ -352,7 +352,12 @@ $(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.c $(KEYBOARD_OUTPUT)/src/default_keyboard.h $(KEYBOARD_OUTPUT)/src/layouts.h
|
||||
$(KEYBOARD_OUTPUT)/src/keymap_hash.h:
|
||||
@$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD)
|
||||
$(eval CMD=$(QMK_BIN) generate-keymap-hash -q -o "$(KEYMAP_OUTPUT)/src/keymap_hash.h" -kb $(KEYBOARD) -km $(KEYMAP))
|
||||
@$(BUILD_CMD)
|
||||
|
||||
generated-files: $(KEYBOARD_OUTPUT)/src/keymap_hash.h $(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
|
||||
|
||||
|
|
|
@ -44,6 +44,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)
|
||||
|
@ -866,6 +870,20 @@ 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
|
||||
OPT_DEFS += -DBOOTLOADER_JUMP_SUPPORTED
|
||||
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)
|
||||
|
|
41
builddefs/xap.mk
Normal file
41
builddefs/xap.mk
Normal file
|
@ -0,0 +1,41 @@
|
|||
# Copyright 2022 Nick Brassel (@tzarc)
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
XAP_FILES := $(shell ls -1 data/xap/* | sort | xargs echo)
|
||||
ifneq ("$(wildcard $(KEYBOARD_PATH_1)/xap.hjson)","")
|
||||
XAP_FILES += $(KEYBOARD_PATH_1)/xap.hjson
|
||||
endif
|
||||
ifneq ("$(wildcard $(KEYBOARD_PATH_2)/xap.hjson)","")
|
||||
XAP_FILES += $(KEYBOARD_PATH_2)/xap.hjson
|
||||
endif
|
||||
ifneq ("$(wildcard $(KEYBOARD_PATH_3)/xap.hjson)","")
|
||||
XAP_FILES += $(KEYBOARD_PATH_3)/xap.hjson
|
||||
endif
|
||||
ifneq ("$(wildcard $(KEYBOARD_PATH_4)/xap.hjson)","")
|
||||
XAP_FILES += $(KEYBOARD_PATH_4)/xap.hjson
|
||||
endif
|
||||
ifneq ("$(wildcard $(KEYBOARD_PATH_5)/xap.hjson)","")
|
||||
XAP_FILES += $(KEYBOARD_PATH_5)/xap.hjson
|
||||
endif
|
||||
ifneq ("$(wildcard $(KEYMAP_PATH)/xap.hjson)","")
|
||||
XAP_FILES += $(KEYMAP_PATH)/xap.hjson
|
||||
endif
|
||||
|
||||
$(KEYMAP_OUTPUT)/src/config_blob_gz.h: $(INFO_JSON_FILES)
|
||||
@$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD)
|
||||
$(eval CMD=$(QMK_BIN) xap-generate-qmk-blob-h -o "$(KEYMAP_OUTPUT)/src/config_blob_gz.h" -kb $(KEYBOARD) -km $(KEYMAP))
|
||||
@$(BUILD_CMD)
|
||||
|
||||
$(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" -kb $(KEYBOARD) -km $(KEYMAP))
|
||||
@$(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) -km $(KEYMAP))
|
||||
@$(BUILD_CMD)
|
||||
|
||||
generated-files: $(KEYMAP_OUTPUT)/src/config_blob_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
7
data/mappings/xap_defaults.json
Normal file
7
data/mappings/xap_defaults.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"secure": {
|
||||
"unlock_sequence": [ [0,0] ],
|
||||
"unlock_timeout": 5000,
|
||||
"idle_timeout": 60000
|
||||
}
|
||||
}
|
|
@ -12,6 +12,12 @@
|
|||
"minLength": 1,
|
||||
"pattern": "^[0-9a-z_]*$"
|
||||
},
|
||||
"define": {
|
||||
"type": "string",
|
||||
"minLength": 2,
|
||||
"maxLength": 50,
|
||||
"pattern": "^[A-Z_]*$"
|
||||
},
|
||||
"hex_number_2d": {
|
||||
"type": "string",
|
||||
"pattern": "^0x[0-9A-F]{2}$"
|
||||
|
@ -24,6 +30,10 @@
|
|||
"type": "string",
|
||||
"pattern": "^[0-9]{1,2}\\.[0-9]\\.[0-9]$"
|
||||
},
|
||||
"text_unsigned_int": {
|
||||
"type": "string",
|
||||
"pattern": "^[0-8]+$"
|
||||
},
|
||||
"text_identifier": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
|
|
262
data/schemas/xap.jsonschema
Normal file
262
data/schemas/xap.jsonschema
Normal file
|
@ -0,0 +1,262 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"$id": "qmk.xap.v1",
|
||||
"title": "XAP Spec",
|
||||
"definitions": {
|
||||
"data_type": {
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": [
|
||||
"bool",
|
||||
"u8",
|
||||
"u16",
|
||||
"u32",
|
||||
"u64",
|
||||
"struct",
|
||||
"string"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"pattern": "^u\\d{1,2}\\[\\d{1,2}\\]*$"
|
||||
}
|
||||
]
|
||||
},
|
||||
"router_type": {
|
||||
"enum": [
|
||||
"command",
|
||||
"router"
|
||||
]
|
||||
},
|
||||
"permission": {
|
||||
"enum": [
|
||||
"secure",
|
||||
"ignore"
|
||||
]
|
||||
},
|
||||
"struct": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"name",
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"route": {
|
||||
"type": "object",
|
||||
"propertyNames": {
|
||||
"$ref": "qmk.definitions.v1#/hex_number_2d"
|
||||
},
|
||||
"additionalProperties": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type",
|
||||
"define"
|
||||
],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"type": {
|
||||
"$ref": "#/definitions/router_type"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"define": {
|
||||
"$ref": "qmk.definitions.v1#/define"
|
||||
},
|
||||
"permissions": {
|
||||
"$ref": "#/definitions/permission"
|
||||
},
|
||||
"enable_if_preprocessor": {
|
||||
"type": "string"
|
||||
},
|
||||
"request_type": {
|
||||
"$ref": "#/definitions/data_type"
|
||||
},
|
||||
"request_struct_length": {
|
||||
"type": "number"
|
||||
},
|
||||
"request_purpose": {
|
||||
"type": "string"
|
||||
},
|
||||
"return_type": {
|
||||
"$ref": "#/definitions/data_type"
|
||||
},
|
||||
"request_struct_members": {
|
||||
"$ref": "#definitions/struct"
|
||||
},
|
||||
"return_struct_length": {
|
||||
"type": "number"
|
||||
},
|
||||
"return_constant": {
|
||||
"type": [
|
||||
"array",
|
||||
"string"
|
||||
]
|
||||
},
|
||||
"return_struct_members": {
|
||||
"$ref": "#definitions/struct"
|
||||
},
|
||||
"return_purpose": {
|
||||
"type": "string"
|
||||
},
|
||||
"return_execute": {
|
||||
"type": "string"
|
||||
},
|
||||
"routes": {
|
||||
"$ref": "#/definitions/route"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"version"
|
||||
],
|
||||
"properties": {
|
||||
"version": {
|
||||
"$ref": "qmk.definitions.v1#/bcd_version"
|
||||
},
|
||||
"define": {
|
||||
"$ref": "qmk.definitions.v1#/define"
|
||||
},
|
||||
"uses": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "qmk.definitions.v1#/bcd_version"
|
||||
}
|
||||
},
|
||||
"documentation": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"order": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"term_definitions": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type_docs": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type_definitions": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"$ref": "#/definitions/data_type"
|
||||
},
|
||||
"struct_length": {
|
||||
"type": "number"
|
||||
},
|
||||
"struct_members": {
|
||||
"$ref": "#definitions/struct"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"response_flags": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"define_prefix": {
|
||||
"$ref": "qmk.definitions.v1#/define"
|
||||
},
|
||||
"bits": {
|
||||
"type": "object",
|
||||
"propertyNames": {
|
||||
"$ref": "qmk.definitions.v1#/text_unsigned_int"
|
||||
},
|
||||
"additionalProperties": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"define": {
|
||||
"$ref": "qmk.definitions.v1#/define"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"broadcast_messages": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"define_prefix": {
|
||||
"$ref": "qmk.definitions.v1#/define"
|
||||
},
|
||||
"messages": {
|
||||
"type": "object",
|
||||
"propertyNames": {
|
||||
"$ref": "qmk.definitions.v1#/hex_number_2d"
|
||||
},
|
||||
"additionalProperties": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"define": {
|
||||
"$ref": "qmk.definitions.v1#/define"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"return_type": {
|
||||
"$ref": "#/definitions/data_type"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"routes": {
|
||||
"$ref": "#/definitions/route"
|
||||
}
|
||||
}
|
||||
}
|
4
data/templates/xap/docs/broadcast_messages.md.j2
Normal file
4
data/templates/xap/docs/broadcast_messages.md.j2
Normal file
|
@ -0,0 +1,4 @@
|
|||
{%- for id, message in xap.broadcast_messages.messages | dictsort %}
|
||||
### {{ message.name }} - `{{ id }}`
|
||||
{{ message.description }}
|
||||
{%- endfor %}
|
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/route_request.md.j2
Normal file
8
data/templates/xap/docs/route_request.md.j2
Normal file
|
@ -0,0 +1,8 @@
|
|||
{%- if subroute.request_type == 'struct' -%}
|
||||
__Request:__
|
||||
{%- for member in subroute.request_struct_members -%}
|
||||
<br>{{ " "|safe*4 }}* {{ member.name }}: `{{ member.type }}`
|
||||
{%- endfor -%}
|
||||
{%- elif subroute.request_type -%}
|
||||
__Request:__ `{{ subroute.request_type }}`
|
||||
{%- endif -%}
|
8
data/templates/xap/docs/route_response.md.j2
Normal file
8
data/templates/xap/docs/route_response.md.j2
Normal file
|
@ -0,0 +1,8 @@
|
|||
{%- if subroute.return_type == 'struct' -%}
|
||||
__Response:__
|
||||
{%- for member in subroute.return_struct_members -%}
|
||||
<br>{{ " "|safe*4 }}* {{ member.name }}: `{{ member.type }}`
|
||||
{%- endfor -%}
|
||||
{%- elif subroute.return_type -%}
|
||||
__Response:__ `{{ subroute.return_type }}`
|
||||
{%- endif -%}
|
12
data/templates/xap/docs/routes.md.j2
Normal file
12
data/templates/xap/docs/routes.md.j2
Normal file
|
@ -0,0 +1,12 @@
|
|||
{%- for id, route in xap.routes | dictsort %}
|
||||
### {{ route.name }} - `{{ id }}`
|
||||
{{ route.description }}
|
||||
|
||||
{% if route.routes %}
|
||||
| Name | Route | Tags | Payloads | Description |
|
||||
| -- | -- | -- | -- | -- |
|
||||
{%- for subid, subroute in route.routes | dictsort %}
|
||||
| {{ subroute.name }} | `{{ id }} {{ subid }}` | {% if 'secure' == subroute.permissions %}__Secure__{% endif %} | {%- include 'route_request.md.j2' -%}{%- if subroute.return_type and subroute.request_type -%}<br><br>{% endif %}{%- include 'route_response.md.j2' -%} | {{ subroute.description.replace('\n', '<br>') }}|
|
||||
{%- endfor %}
|
||||
{% endif %}
|
||||
{%- endfor %}
|
8
data/templates/xap/docs/term_definitions.md.j2
Normal file
8
data/templates/xap/docs/term_definitions.md.j2
Normal file
|
@ -0,0 +1,8 @@
|
|||
| Name | Definition |
|
||||
| -- | -- |
|
||||
{%- for type, definition in xap.term_definitions | dictsort %}
|
||||
| _{{ type }}_ | {{ definition }} |
|
||||
{%- endfor %}
|
||||
{%- for type, definition in xap.type_definitions | dictsort %}
|
||||
| _{{ definition.name }}_ | {{ definition.description }}{% if 'struct' == definition.type %} Takes the format:{% for item in definition.struct_members %}<br>`{{ item.type }}` - {{ item.name }}{%- endfor %}{% endif %} |
|
||||
{%- endfor %}
|
5
data/templates/xap/docs/type_docs.md.j2
Normal file
5
data/templates/xap/docs/type_docs.md.j2
Normal file
|
@ -0,0 +1,5 @@
|
|||
| Name | Definition |
|
||||
| -- | -- |
|
||||
{%- for type, definition in xap.type_docs | dictsort %}
|
||||
| _{{ type }}_ | {{ definition }} |
|
||||
{%- endfor %}
|
237
data/xap/xap_0.0.1.hjson
Executable file
237
data/xap/xap_0.0.1.hjson
Executable file
|
@ -0,0 +1,237 @@
|
|||
{
|
||||
version: 0.0.1
|
||||
|
||||
// Needed for table generation
|
||||
define: XAP_ROUTE
|
||||
|
||||
// Documentation section is used purely for `qmk xap-generate-docs`.
|
||||
documentation: {
|
||||
order: [
|
||||
page_header
|
||||
type_docs
|
||||
!type_docs.md.j2
|
||||
term_definitions
|
||||
!term_definitions.md.j2
|
||||
request_response
|
||||
reserved_tokens
|
||||
response_flags
|
||||
!response_flags.md.j2
|
||||
example_conversation
|
||||
routes
|
||||
!routes.md.j2
|
||||
]
|
||||
|
||||
page_header:
|
||||
'''
|
||||
# QMK Firmware XAP Specs
|
||||
|
||||
This document describes the requirements of the QMK XAP ("extensible application protocol") API.
|
||||
'''
|
||||
|
||||
type_docs:
|
||||
'''
|
||||
## Types
|
||||
|
||||
**All integral types are little-endian.**
|
||||
'''
|
||||
|
||||
term_definitions:
|
||||
'''
|
||||
## Definitions
|
||||
|
||||
This list defines the terms used across the entire set of XAP protocol documentation.
|
||||
'''
|
||||
|
||||
request_response:
|
||||
'''
|
||||
## Requests and Responses
|
||||
|
||||
Communication generally follows a request/response pattern.
|
||||
|
||||
Each request needs to include a _token_ -- this `u16` value prefixes each outbound request from the host application and its corresponding response.
|
||||
This allows response messages to be correlated with their request, even if multiple host applications are communicating with the firmware simultaneously.
|
||||
Host applications should randomly generate a token ID for **every** outbound request, unless using a reserved token defined below.
|
||||
To ensure host interoperability, valid token values are within the range `0x0100`-`0xFFFF`.
|
||||
|
||||
This token is followed by a `u8` signifying the length of data in the request.
|
||||
'''
|
||||
|
||||
// This documentation section reserved for next version
|
||||
reserved_tokens: ''
|
||||
|
||||
response_flags:
|
||||
'''
|
||||
Response messages will always be prefixed by the originating request _token_, directly followed by that request's _response flags_, then the response payload length:
|
||||
'''
|
||||
|
||||
example_conversation:
|
||||
'''
|
||||
### Example "conversation":
|
||||
|
||||
**Request** -- version query:
|
||||
|
||||
| Byte | 0 | 1 | 2 | 3 | 4 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| **Purpose** | Token | Token | Payload Length | Route | Route |
|
||||
| **Value** | `0x43` | `0x2B` | `0x02` | `0x00` | `0x00` |
|
||||
|
||||
**Response** -- matching token, successful flag, payload of `0x03170192` = 3.17.192:
|
||||
|
||||
| Byte | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| **Purpose** | Token | Token | Response Flags | Payload Length | Payload | Payload | Payload | Payload |
|
||||
| **Value** | `0x43` | `0x2B` | `0x01` | `0x04` | `0x92` | `0x01` | `0x17` | `0x03` |
|
||||
'''
|
||||
|
||||
routes:
|
||||
'''
|
||||
## Routes
|
||||
|
||||
Subsystem validity should be queried through the “Enabled-in-firmware subsystem query” under the QMK subsystem (route=0x00,0x01).
|
||||
This is the primary method for determining if a subsystem has been enabled in the running firmware.
|
||||
'''
|
||||
}
|
||||
|
||||
type_docs: {
|
||||
u8:
|
||||
'''
|
||||
An unsigned 8-bit integral (octet, or byte), commonly seen as `uint8_t` from _stdint.h_.
|
||||
'''
|
||||
u16:
|
||||
'''
|
||||
An unsigned 16-bit integral, commonly seen as `uint16_t` from _stdint.h_.
|
||||
'''
|
||||
u32:
|
||||
'''
|
||||
An unsigned 32-bit integral, commonly seen as `uint32_t` from _stdint.h_.
|
||||
'''
|
||||
"type[n]":
|
||||
'''
|
||||
An array of `type`, with array extent of `N` -- e.g. `u8[2]` signifies two consecutive octets.
|
||||
'''
|
||||
}
|
||||
|
||||
term_definitions: {
|
||||
Subsystem:
|
||||
'''
|
||||
A high-level area of functionality within XAP.
|
||||
'''
|
||||
Route:
|
||||
'''
|
||||
A sequence of _IDs_ describing the route to invoke a _handler_.
|
||||
'''
|
||||
Handler:
|
||||
'''
|
||||
A piece of code that is executed when a specific _route_ is received.
|
||||
'''
|
||||
Response:
|
||||
'''
|
||||
The data sent back to the host during execution of a _handler_.
|
||||
'''
|
||||
Payload:
|
||||
'''
|
||||
Any received data appended to the _route_, which gets delivered to the _handler_ when received.
|
||||
'''
|
||||
}
|
||||
|
||||
type_definitions: {
|
||||
identifier: {
|
||||
name: ID
|
||||
description: A single octet / 8-bit byte, representing Subsystem or Route index.
|
||||
type: u8
|
||||
}
|
||||
|
||||
response_flags: {
|
||||
name: Response Flags
|
||||
description: An `u8` containing the status of the request.
|
||||
type: u8
|
||||
}
|
||||
|
||||
token: {
|
||||
name: Token
|
||||
description: A `u16` associated with a specific request as well as its corresponding response. Valid token values are within the range `0x0100`-`0xFFFF`.
|
||||
type: u16
|
||||
}
|
||||
|
||||
request_header: {
|
||||
name: Request Header
|
||||
description: Packet format for inbound data.
|
||||
type: struct
|
||||
struct_length: 3
|
||||
struct_members: [
|
||||
{
|
||||
type: token
|
||||
name: token
|
||||
},
|
||||
{
|
||||
type: u8
|
||||
name: length
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
response_header: {
|
||||
name: Response Header
|
||||
description: Packet format for outbound data.
|
||||
type: struct
|
||||
struct_length: 4
|
||||
struct_members: [
|
||||
{
|
||||
type: token
|
||||
name: token
|
||||
},
|
||||
{
|
||||
type: response_flags
|
||||
name: flags
|
||||
},
|
||||
{
|
||||
type: u8
|
||||
name: length
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
response_flags: {
|
||||
define_prefix: XAP_RESPONSE_FLAG
|
||||
bits: {
|
||||
0: {
|
||||
name: Success
|
||||
define: SUCCESS
|
||||
description:
|
||||
'''
|
||||
When this bit is set, the request was successfully handled. If not set, all payload data should be disregarded, and the request retried if appropriate (with a new token).
|
||||
'''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
routes: {
|
||||
0x00: {
|
||||
type: router
|
||||
name: XAP
|
||||
define: XAP
|
||||
description:
|
||||
'''
|
||||
This subsystem is always present, and provides the ability to query information about the XAP protocol of the connected device.
|
||||
'''
|
||||
routes: {
|
||||
0x00: {
|
||||
type: command
|
||||
name: Version Query
|
||||
define: VERSION_QUERY
|
||||
description:
|
||||
'''
|
||||
XAP protocol version query.
|
||||
|
||||
* Returns the BCD-encoded version in the format of XX.YY.ZZZZ => `0xXXYYZZZZ`
|
||||
* e.g. 3.2.115 will match `0x03020115`, or bytes {0x15,0x01,0x02,0x03}.
|
||||
'''
|
||||
return_type: u32
|
||||
return_purpose: bcd-version
|
||||
return_constant: XAP_BCD_VERSION
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
385
data/xap/xap_0.1.0.hjson
Executable file
385
data/xap/xap_0.1.0.hjson
Executable file
|
@ -0,0 +1,385 @@
|
|||
{
|
||||
version: 0.1.0
|
||||
|
||||
uses: {
|
||||
keycodes: 0.0.1
|
||||
}
|
||||
|
||||
documentation: {
|
||||
order: [
|
||||
broadcast_messages
|
||||
!broadcast_messages.md.j2
|
||||
]
|
||||
|
||||
reserved_tokens:
|
||||
'''
|
||||
Two token values are reserved: `0xFFFE` and `0xFFFF`:
|
||||
* `0xFFFE`: A message sent by a host application may use this token if no response is to be sent -- a "fire and forget" message.
|
||||
* `0xFFFF`: Signifies a "broadcast" message sent by the firmware without prompting from the host application. Broadcast messages are defined later in this document.
|
||||
|
||||
Any request will generate at least one corresponding response, with the exception of messages using reserved tokens. Maximum total message length is 128 bytes due to RAM constraints.
|
||||
'''
|
||||
|
||||
broadcast_messages:
|
||||
'''
|
||||
## Broadcast messages
|
||||
|
||||
Broadcast messages may be sent by the firmware to the host, without a corresponding inbound request. Each broadcast message uses the token `0xFFFF`, and does not expect a response from the host. Tokens are followed by an _ID_ signifying the type of broadcast, 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. Payloads include a `u8` matching a 'Secure Status' request.
|
||||
|
||||
**Example Secure Status Broadcast** -- secure "Unlocking"
|
||||
|
||||
| Byte | 0 | 1 | 2 | 3 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| **Purpose** | Token | Token | Broadcast Type | Secure Status |
|
||||
| **Value** | `0xFF` | `0xFF` | `0x01` | `0x01` |
|
||||
'''
|
||||
return_type: u8
|
||||
}
|
||||
0x02: {
|
||||
name: Keyboard
|
||||
define: KB
|
||||
description:
|
||||
'''
|
||||
Reserved for vendor-specific functionality. No messages are defined by XAP.
|
||||
'''
|
||||
},
|
||||
|
||||
0x03: {
|
||||
name: User
|
||||
define: USER
|
||||
description:
|
||||
'''
|
||||
Reserved for user-specific functionality. No messages are defined by XAP.
|
||||
'''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
routes: {
|
||||
0x00: {
|
||||
routes: {
|
||||
0x01: {
|
||||
type: command
|
||||
name: Capabilities Query
|
||||
define: CAPABILITIES_QUERY
|
||||
description:
|
||||
'''
|
||||
XAP subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.
|
||||
'''
|
||||
return_type: u32
|
||||
return_purpose: capabilities
|
||||
return_constant: XAP_ROUTE_XAP_CAPABILITIES
|
||||
}
|
||||
0x02: {
|
||||
type: command
|
||||
name: Enabled subsystem query
|
||||
define: SUBSYSTEM_QUERY
|
||||
description:
|
||||
'''
|
||||
XAP protocol subsystem query. Each bit should be considered as a "usable" subsystem. For example, checking `(value & (1 << XAP_ROUTE_QMK) != 0)` means the QMK subsystem is enabled and available for querying.
|
||||
'''
|
||||
return_type: u32
|
||||
return_purpose: capabilities
|
||||
return_constant: XAP_ROUTE_CAPABILITIES
|
||||
}
|
||||
0x03: {
|
||||
type: command
|
||||
name: Secure Status
|
||||
define: SECURE_STATUS
|
||||
description:
|
||||
'''
|
||||
Query secure route status
|
||||
|
||||
* 0 means secure routes are disabled
|
||||
* 1 means unlock sequence initiated but incomplete
|
||||
* 2 means secure routes are allowed
|
||||
* any other value should be interpreted as disabled
|
||||
'''
|
||||
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}.
|
||||
'''
|
||||
return_type: u32
|
||||
return_purpose: bcd-version
|
||||
return_constant: QMK_BCD_VERSION
|
||||
}
|
||||
0x01: {
|
||||
type: command
|
||||
name: Capabilities Query
|
||||
define: CAPABILITIES_QUERY
|
||||
description:
|
||||
'''
|
||||
QMK subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.
|
||||
'''
|
||||
return_type: u32
|
||||
return_purpose: capabilities
|
||||
return_constant: XAP_ROUTE_QMK_CAPABILITIES
|
||||
}
|
||||
0x02: {
|
||||
type: command
|
||||
name: Board identifiers
|
||||
define: BOARD_IDENTIFIERS
|
||||
description:
|
||||
'''
|
||||
Retrieves the set of identifying information for the board.
|
||||
'''
|
||||
return_type: struct
|
||||
return_struct_length: 10
|
||||
return_struct_members: [
|
||||
{
|
||||
type: u16
|
||||
name: Vendor ID
|
||||
},
|
||||
{
|
||||
type: u16
|
||||
name: Product ID
|
||||
},
|
||||
{
|
||||
type: u16
|
||||
name: Product Version
|
||||
},
|
||||
{
|
||||
type: u32
|
||||
name: QMK Unique Identifier
|
||||
}
|
||||
]
|
||||
return_constant: [
|
||||
VENDOR_ID
|
||||
PRODUCT_ID
|
||||
DEVICE_VER
|
||||
XAP_KEYBOARD_IDENTIFIER
|
||||
]
|
||||
}
|
||||
0x03: {
|
||||
type: command
|
||||
name: Board Manufacturer
|
||||
define: BOARD_MANUFACTURER
|
||||
description: Retrieves the name of the manufacturer
|
||||
return_type: string
|
||||
return_constant: QSTR(MANUFACTURER)
|
||||
}
|
||||
0x04: {
|
||||
type: command
|
||||
name: Product Name
|
||||
define: PRODUCT_NAME
|
||||
description: Retrieves the product name
|
||||
return_type: string
|
||||
return_constant: QSTR(PRODUCT)
|
||||
}
|
||||
0x05: {
|
||||
type: command
|
||||
name: Config Blob Length
|
||||
define: CONFIG_BLOB_LEN
|
||||
description: Retrieves the length of the configuration data bundled within the firmware
|
||||
return_type: u32
|
||||
return_constant: CONFIG_BLOB_GZ_LEN
|
||||
}
|
||||
0x06: {
|
||||
type: command
|
||||
name: Config Blob Chunk
|
||||
define: CONFIG_BLOB_CHUNK
|
||||
description: Retrieves a chunk of the configuration data bundled within the firmware
|
||||
request_type: u16
|
||||
request_purpose: offset
|
||||
return_type: u8[32]
|
||||
return_execute: get_config_blob_chunk
|
||||
}
|
||||
0x07: {
|
||||
type: command
|
||||
name: Jump to bootloader
|
||||
define: BOOTLOADER_JUMP
|
||||
permissions: secure
|
||||
enable_if_preprocessor: defined(BOOTLOADER_JUMP_SUPPORTED)
|
||||
description:
|
||||
'''
|
||||
Jump to bootloader
|
||||
|
||||
May not be present – if QMK capabilities query returns “true”, then jump to bootloader is supported
|
||||
|
||||
* 0 means secure routes are disabled, and should be considered as a failure
|
||||
* 1 means successful, board will jump to bootloader
|
||||
'''
|
||||
return_type: u8
|
||||
return_execute: request_bootloader_jump
|
||||
}
|
||||
0x08: {
|
||||
type: command
|
||||
name: Hardware Identifier
|
||||
define: HARDWARE_ID
|
||||
description: Retrieves a unique identifier for the board.
|
||||
return_type: u32[4]
|
||||
return_execute: get_hardware_id
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
0x02: {
|
||||
type: router
|
||||
name: Keyboard
|
||||
define: KB
|
||||
description:
|
||||
'''
|
||||
This subsystem is always present, and reserved for vendor-specific functionality. No routes are defined by XAP.
|
||||
'''
|
||||
routes: {
|
||||
}
|
||||
},
|
||||
|
||||
0x03: {
|
||||
type: router
|
||||
name: User
|
||||
define: USER
|
||||
description:
|
||||
'''
|
||||
This subsystem is always present, and reserved for user-specific functionality. No routes are defined by XAP.
|
||||
'''
|
||||
routes: {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
201
data/xap/xap_0.2.0.hjson
Executable file
201
data/xap/xap_0.2.0.hjson
Executable file
|
@ -0,0 +1,201 @@
|
|||
{
|
||||
version: 0.2.0
|
||||
|
||||
routes: {
|
||||
0x04: {
|
||||
type: router
|
||||
name: Keymap
|
||||
define: KEYMAP
|
||||
description:
|
||||
'''
|
||||
This subsystem allows for query of currently configured keycodes.
|
||||
'''
|
||||
routes: {
|
||||
0x01: {
|
||||
type: command
|
||||
name: Capabilities Query
|
||||
define: CAPABILITIES_QUERY
|
||||
description:
|
||||
'''
|
||||
Keymap subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.
|
||||
'''
|
||||
return_type: u32
|
||||
return_purpose: capabilities
|
||||
return_constant: XAP_ROUTE_KEYMAP_CAPABILITIES
|
||||
}
|
||||
0x02: {
|
||||
type: command
|
||||
name: Get Layer Count
|
||||
define: GET_LAYER_COUNT
|
||||
description: Query maximum number of layers that can be addressed within the keymap.
|
||||
return_type: u8
|
||||
return_execute: keymap_get_layer_count
|
||||
}
|
||||
0x03: {
|
||||
type: command
|
||||
name: Get Keycode
|
||||
define: GET_KEYMAP_KEYCODE
|
||||
description: Query the Keycode at the requested location.
|
||||
request_type: struct
|
||||
request_struct_length: 3
|
||||
request_struct_members: [
|
||||
{
|
||||
type: u8
|
||||
name: Layer
|
||||
},
|
||||
{
|
||||
type: u8
|
||||
name: Row
|
||||
},
|
||||
{
|
||||
type: u8
|
||||
name: Column
|
||||
}
|
||||
]
|
||||
return_type: u16
|
||||
return_execute: get_keymap_keycode
|
||||
}
|
||||
0x04: {
|
||||
type: command
|
||||
name: Get Encoder Keycode
|
||||
define: GET_ENCODER_KEYCODE
|
||||
description: Query the Keycode at the requested location.
|
||||
enable_if_preprocessor: defined(ENCODER_MAP_ENABLE)
|
||||
request_type: struct
|
||||
request_struct_length: 3
|
||||
request_struct_members: [
|
||||
{
|
||||
type: u8
|
||||
name: Layer
|
||||
},
|
||||
{
|
||||
type: u8
|
||||
name: Encoder
|
||||
},
|
||||
{
|
||||
type: u8
|
||||
name: Clockwise
|
||||
}
|
||||
]
|
||||
return_type: u16
|
||||
return_execute: get_encoder_keycode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
0x05: {
|
||||
type: router
|
||||
name: Remapping
|
||||
define: REMAPPING
|
||||
description:
|
||||
'''
|
||||
This subsystem allows for live reassignment of keycodes without rebuilding the firmware.
|
||||
'''
|
||||
enable_if_preprocessor: defined(DYNAMIC_KEYMAP_ENABLE)
|
||||
routes: {
|
||||
0x01: {
|
||||
type: command
|
||||
name: Capabilities Query
|
||||
define: CAPABILITIES_QUERY
|
||||
description:
|
||||
'''
|
||||
Remapping subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.
|
||||
'''
|
||||
return_type: u32
|
||||
return_purpose: capabilities
|
||||
return_constant: XAP_ROUTE_REMAPPING_CAPABILITIES
|
||||
}
|
||||
0x02: {
|
||||
type: command
|
||||
name: Get Layer Count
|
||||
define: GET_DYNAMIC_LAYER_COUNT
|
||||
description: Query maximum number of layers that can be addressed within the keymap.
|
||||
return_type: u8
|
||||
return_constant: DYNAMIC_KEYMAP_LAYER_COUNT
|
||||
}
|
||||
0x03: {
|
||||
type: command
|
||||
name: Set Keycode
|
||||
define: SET_KEYMAP_KEYCODE
|
||||
description: Modify the Keycode at the requested location.
|
||||
permissions: secure
|
||||
request_type: struct
|
||||
request_struct_length: 5
|
||||
request_struct_members: [
|
||||
{
|
||||
type: u8
|
||||
name: Layer
|
||||
},
|
||||
{
|
||||
type: u8
|
||||
name: Row
|
||||
},
|
||||
{
|
||||
type: u8
|
||||
name: Column
|
||||
},
|
||||
{
|
||||
type: u16
|
||||
name: Keycode
|
||||
}
|
||||
]
|
||||
return_execute: dynamic_keymap_set_keycode
|
||||
}
|
||||
0x04: {
|
||||
type: command
|
||||
name: Set Encoder Keycode
|
||||
define: SET_ENCODER_KEYCODE
|
||||
permissions: secure
|
||||
description: Modify the Keycode at the requested location.
|
||||
enable_if_preprocessor: defined(ENCODER_MAP_ENABLE)
|
||||
request_type: struct
|
||||
request_struct_length: 5
|
||||
request_struct_members: [
|
||||
{
|
||||
type: u8
|
||||
name: Layer
|
||||
},
|
||||
{
|
||||
type: u8
|
||||
name: Encoder
|
||||
},
|
||||
{
|
||||
type: u8
|
||||
name: Clockwise
|
||||
},
|
||||
{
|
||||
type: u16
|
||||
name: Keycode
|
||||
}
|
||||
]
|
||||
return_execute: dynamic_encoder_set_keycode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
0x06: {
|
||||
type: router
|
||||
name: Lighting
|
||||
define: LIGHTING
|
||||
description:
|
||||
'''
|
||||
This subsystem allows for control over the lighting subsystem.
|
||||
'''
|
||||
enable_if_preprocessor: defined(RGBLIGHT_ENABLE) || defined(RGB_MATRIX_ENABLE)
|
||||
routes: {
|
||||
0x01: {
|
||||
type: command
|
||||
name: Capabilities Query
|
||||
define: CAPABILITIES_QUERY
|
||||
description:
|
||||
'''
|
||||
Lighting subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.
|
||||
'''
|
||||
return_type: u32
|
||||
return_purpose: capabilities
|
||||
return_constant: XAP_ROUTE_LIGHTING_CAPABILITIES
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -92,6 +92,7 @@
|
|||
* [Unicode](feature_unicode.md)
|
||||
* [Userspace](feature_userspace.md)
|
||||
* [WPM Calculation](feature_wpm.md)
|
||||
* [XAP](xap.md)
|
||||
|
||||
* Hardware Features
|
||||
* Displays
|
||||
|
|
|
@ -128,6 +128,8 @@
|
|||
<script src="//unpkg.com/docsify/lib/docsify.min.js"></script>
|
||||
<script src="//unpkg.com/docsify/lib/plugins/search.min.js"></script>
|
||||
<script src="//unpkg.com/docsify/lib/plugins/emoji.min.js"></script>
|
||||
<script src="//unpkg.com/mermaid/dist/mermaid.js"></script>
|
||||
<script src="//unpkg.com/docsify-mermaid@latest/dist/docsify-mermaid.js"></script>
|
||||
<script src="//unpkg.com/docsify-tabs@1"></script>
|
||||
<script src="//unpkg.com/docsify-copy-code@2"></script>
|
||||
<script src="//unpkg.com/docsify-toc@1.1.0/dist/toc.js"></script>
|
||||
|
@ -143,5 +145,6 @@
|
|||
navigator.serviceWorker.register('sw.js')
|
||||
}
|
||||
</script>
|
||||
<script>mermaid.initialize({ startOnLoad: true });</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
95
docs/xap.md
Normal file
95
docs/xap.md
Normal file
|
@ -0,0 +1,95 @@
|
|||
# XAP
|
||||
|
||||
XAP (“extensible application protocol”) API intends to provide access to various QMK subsystems.
|
||||
|
||||
## Overview
|
||||
|
||||
```mermaid
|
||||
%%{init: {'themeVariables': { 'fontSize': '24px'}}}%%
|
||||
flowchart LR
|
||||
dev[QMK Device] <-- XAP --> host[Host Computer]
|
||||
```
|
||||
|
||||
The intention is to provide access to QMK subsystems through a versioned and documented protocol.
|
||||
|
||||
## Protocol Reference
|
||||
|
||||
[protocol_versions](xap_protocol.md ':include')
|
||||
|
||||
## Clients
|
||||
|
||||
TODO
|
||||
|
||||
## CLI
|
||||
|
||||
The QMK CLI provides a few XAP specific commands for diagnosis purposes.
|
||||
|
||||
### List Connected Devices
|
||||
Simple
|
||||
```
|
||||
$ qmk xap --list
|
||||
Ψ Available devices:
|
||||
Ψ 7844:8450 KPrepublic XD84 Pro [API:0.2.0] LOCKED
|
||||
```
|
||||
|
||||
Verbose
|
||||
```
|
||||
$ qmk --verbose xap --list
|
||||
Ψ Available devices:
|
||||
Ψ 7844:8450 KPrepublic XD84 Pro [API:0.2.0] LOCKED
|
||||
_id: 553831323538150A2113000000000000
|
||||
backlight.pin: F5
|
||||
bootloader: atmel-dfu
|
||||
community_layouts: 75_ansi, 75_iso
|
||||
debounce: 5
|
||||
diode_direction: COL2ROW
|
||||
features.audio: False
|
||||
features.backlight: True
|
||||
features.bootmagic: True
|
||||
features.command: False
|
||||
features.console: False
|
||||
features.extrakey: True
|
||||
features.mousekey: False
|
||||
features.nkro: True
|
||||
features.rgblight: True
|
||||
indicators.caps_lock: B2
|
||||
keyboard_folder: xiudi/xd84pro
|
||||
keyboard_name: XD84 Pro
|
||||
layouts: LAYOUT_75_ansi, LAYOUT_75_iso, LAYOUT_all
|
||||
maintainer: qmk
|
||||
manufacturer: KPrepublic
|
||||
matrix_pins.cols: B1, B3, B4, B5, B6, B7, C6, C7, D4, D6, D7, E6, F0, F1, F7
|
||||
matrix_pins.rows: D0, D1, D2, D3, D5, F4
|
||||
matrix_pins.unused: B0, E2
|
||||
matrix_size.cols: 15
|
||||
matrix_size.rows: 6
|
||||
mouse_key.enabled: False
|
||||
platform: unknown
|
||||
processor: atmega32u4
|
||||
processor_type: avr
|
||||
protocol: LUFA
|
||||
rgblight.animations.all: False
|
||||
rgblight.led_count: 12
|
||||
rgblight.pin: F6
|
||||
rgblight.sleep: False
|
||||
url:
|
||||
usb.device_ver: 0x0001
|
||||
usb.device_version: 0.0.1
|
||||
usb.pid: 0x8450
|
||||
usb.vid: 0x7844
|
||||
```
|
||||
|
||||
### Interactive shell
|
||||
```
|
||||
$ qmk xap -i
|
||||
Ψ Connected to:7844:8450 KPrepublic XD84 Pro
|
||||
Welcome to the XAP shell. Type help or ? to list commands.
|
||||
|
||||
Ψ> help
|
||||
|
||||
Documented commands (type help <topic>):
|
||||
========================================
|
||||
EOF about exit help keycode keymap layer listen unlock
|
||||
|
||||
Ψ>
|
||||
```
|
85
docs/xap_0.0.1.md
Normal file
85
docs/xap_0.0.1.md
Normal file
|
@ -0,0 +1,85 @@
|
|||
# QMK Firmware XAP Specs
|
||||
|
||||
This document describes the requirements of the QMK XAP ("extensible application protocol") API.
|
||||
|
||||
## Types
|
||||
|
||||
**All integral types are little-endian.**
|
||||
|
||||
| Name | Definition |
|
||||
| -- | -- |
|
||||
| _type[n]_ | An array of `type`, with array extent of `N` -- e.g. `u8[2]` signifies two consecutive octets. |
|
||||
| _u16_ | An unsigned 16-bit integral, commonly seen as `uint16_t` from _stdint.h_. |
|
||||
| _u32_ | An unsigned 32-bit integral, commonly seen as `uint32_t` from _stdint.h_. |
|
||||
| _u8_ | An unsigned 8-bit integral (octet, or byte), commonly seen as `uint8_t` from _stdint.h_. |
|
||||
|
||||
## Definitions
|
||||
|
||||
This list defines the terms used across the entire set of XAP protocol documentation.
|
||||
|
||||
| Name | Definition |
|
||||
| -- | -- |
|
||||
| _Handler_ | A piece of code that is executed when a specific _route_ is received. |
|
||||
| _Payload_ | Any received data appended to the _route_, which gets delivered to the _handler_ when received. |
|
||||
| _Response_ | The data sent back to the host during execution of a _handler_. |
|
||||
| _Route_ | A sequence of _IDs_ describing the route to invoke a _handler_. |
|
||||
| _Subsystem_ | A high-level area of functionality within XAP. |
|
||||
| _ID_ | A single octet / 8-bit byte, representing Subsystem or Route index. |
|
||||
| _Request Header_ | Packet format for inbound data. Takes the format:<br>`token` - token<br>`u8` - length |
|
||||
| _Response Flags_ | An `u8` containing the status of the request. |
|
||||
| _Response Header_ | Packet format for outbound data. Takes the format:<br>`token` - token<br>`response_flags` - flags<br>`u8` - length |
|
||||
| _Token_ | A `u16` associated with a specific request as well as its corresponding response. Valid token values are within the range `0x0100`-`0xFFFF`. |
|
||||
|
||||
## Requests and Responses
|
||||
|
||||
Communication generally follows a request/response pattern.
|
||||
|
||||
Each request needs to include a _token_ -- this `u16` value prefixes each outbound request from the host application and its corresponding response.
|
||||
This allows response messages to be correlated with their request, even if multiple host applications are communicating with the firmware simultaneously.
|
||||
Host applications should randomly generate a token ID for **every** outbound request, unless using a reserved token defined below.
|
||||
To ensure host interoperability, valid token values are within the range `0x0100`-`0xFFFF`.
|
||||
|
||||
This token is followed by a `u8` signifying the length of data in the request.
|
||||
|
||||
|
||||
|
||||
Response messages will always be prefixed by the originating request _token_, directly followed by that request's _response flags_, then the response payload length:
|
||||
|
||||
| Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
|
||||
| -- | -- | -- | -- | -- | -- | -- | -- |
|
||||
| `-` | `-` | `-` | `-` | `-` | `-` | `-` | `SUCCESS` |
|
||||
|
||||
* Bit 0 (`SUCCESS`): When this bit is set, the request was successfully handled. If not set, all payload data should be disregarded, and the request retried if appropriate (with a new token).
|
||||
|
||||
|
||||
### Example "conversation":
|
||||
|
||||
**Request** -- version query:
|
||||
|
||||
| Byte | 0 | 1 | 2 | 3 | 4 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| **Purpose** | Token | Token | Payload Length | Route | Route |
|
||||
| **Value** | `0x43` | `0x2B` | `0x02` | `0x00` | `0x00` |
|
||||
|
||||
**Response** -- matching token, successful flag, payload of `0x03170192` = 3.17.192:
|
||||
|
||||
| Byte | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| **Purpose** | Token | Token | Response Flags | Payload Length | Payload | Payload | Payload | Payload |
|
||||
| **Value** | `0x43` | `0x2B` | `0x01` | `0x04` | `0x92` | `0x01` | `0x17` | `0x03` |
|
||||
|
||||
## Routes
|
||||
|
||||
Subsystem validity should be queried through the “Enabled-in-firmware subsystem query” under the QMK subsystem (route=0x00,0x01).
|
||||
This is the primary method for determining if a subsystem has been enabled in the running firmware.
|
||||
|
||||
|
||||
### XAP - `0x00`
|
||||
This subsystem is always present, and provides the ability to query information about the XAP protocol of the connected device.
|
||||
|
||||
|
||||
| Name | Route | Tags | Payloads | Description |
|
||||
| -- | -- | -- | -- | -- |
|
||||
| Version Query | `0x00 0x00` | |__Response:__ `u32`| XAP protocol version query.<br><br>* Returns the BCD-encoded version in the format of XX.YY.ZZZZ => `0xXXYYZZZZ`<br> * e.g. 3.2.115 will match `0x03020115`, or bytes {0x15,0x01,0x02,0x03}.|
|
||||
|
||||
|
157
docs/xap_0.1.0.md
Normal file
157
docs/xap_0.1.0.md
Normal file
|
@ -0,0 +1,157 @@
|
|||
# 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. Valid token values are within the range `0x0100`-`0xFFFF`. |
|
||||
|
||||
## Requests and Responses
|
||||
|
||||
Communication generally follows a request/response pattern.
|
||||
|
||||
Each request needs to include a _token_ -- this `u16` value prefixes each outbound request from the host application and its corresponding response.
|
||||
This allows response messages to be correlated with their request, even if multiple host applications are communicating with the firmware simultaneously.
|
||||
Host applications should randomly generate a token ID for **every** outbound request, unless using a reserved token defined below.
|
||||
To ensure host interoperability, valid token values are within the range `0x0100`-`0xFFFF`.
|
||||
|
||||
This token is followed by a `u8` signifying the length of data in the request.
|
||||
|
||||
Two token values are reserved: `0xFFFE` and `0xFFFF`:
|
||||
* `0xFFFE`: A message sent by a host application may use this token if no response is to be sent -- a "fire and forget" message.
|
||||
* `0xFFFF`: Signifies a "broadcast" message sent by the firmware without prompting from the host application. Broadcast messages are defined later in this document.
|
||||
|
||||
Any request will generate at least one corresponding response, with the exception of messages using reserved tokens. Maximum total message length is 128 bytes due to RAM constraints.
|
||||
|
||||
Response messages will always be prefixed by the originating request _token_, directly followed by that request's _response flags_, then the response payload length:
|
||||
|
||||
| Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
|
||||
| -- | -- | -- | -- | -- | -- | -- | -- |
|
||||
| `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` |
|
||||
|
||||
## Routes
|
||||
|
||||
Subsystem validity should be queried through the “Enabled-in-firmware subsystem query” under the QMK subsystem (route=0x00,0x01).
|
||||
This is the primary method for determining if a subsystem has been enabled in the running firmware.
|
||||
|
||||
|
||||
### XAP - `0x00`
|
||||
This subsystem is always present, and provides the ability to query information about the XAP protocol of the connected device.
|
||||
|
||||
|
||||
| Name | Route | Tags | Payloads | Description |
|
||||
| -- | -- | -- | -- | -- |
|
||||
| Version Query | `0x00 0x00` | |__Response:__ `u32`| XAP protocol version query.<br><br>* Returns the BCD-encoded version in the format of XX.YY.ZZZZ => `0xXXYYZZZZ`<br> * e.g. 3.2.115 will match `0x03020115`, or bytes {0x15,0x01,0x02,0x03}.|
|
||||
| Capabilities Query | `0x00 0x01` | |__Response:__ `u32`| XAP subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.|
|
||||
| Enabled subsystem query | `0x00 0x02` | |__Response:__ `u32`| XAP protocol subsystem query. Each bit should be considered as a "usable" subsystem. For example, checking `(value & (1 << XAP_ROUTE_QMK) != 0)` means the QMK subsystem is enabled and available for querying.|
|
||||
| Secure Status | `0x00 0x03` | |__Response:__ `u8`| Query secure route status<br><br>* 0 means secure routes are disabled<br>* 1 means unlock sequence initiated but incomplete<br>* 2 means secure routes are allowed<br>* any other value should be interpreted as disabled|
|
||||
| Secure Unlock | `0x00 0x04` | || Initiate secure route unlock sequence|
|
||||
| Secure Lock | `0x00 0x05` | || Disable secure routes|
|
||||
|
||||
### QMK - `0x01`
|
||||
This subsystem is always present, and provides the ability to address QMK-specific functionality.
|
||||
|
||||
|
||||
| Name | Route | Tags | Payloads | Description |
|
||||
| -- | -- | -- | -- | -- |
|
||||
| Version Query | `0x01 0x00` | |__Response:__ `u32`| QMK protocol version query.<br><br>* Returns the BCD-encoded version in the format of XX.YY.ZZZZ => `0xXXYYZZZZ`<br> * e.g. 3.2.115 will match `0x03020115`, or bytes {0x15,0x01,0x02,0x03}.|
|
||||
| Capabilities Query | `0x01 0x01` | |__Response:__ `u32`| QMK subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.|
|
||||
| Board identifiers | `0x01 0x02` | |__Response:__<br> * Vendor ID: `u16`<br> * Product ID: `u16`<br> * Product Version: `u16`<br> * QMK Unique Identifier: `u32`| Retrieves the set of identifying information for the board.|
|
||||
| Board Manufacturer | `0x01 0x03` | |__Response:__ `string`| Retrieves the name of the manufacturer|
|
||||
| Product Name | `0x01 0x04` | |__Response:__ `string`| Retrieves the product name|
|
||||
| Config Blob Length | `0x01 0x05` | |__Response:__ `u32`| Retrieves the length of the configuration data bundled within the firmware|
|
||||
| Config Blob Chunk | `0x01 0x06` | |__Request:__ `u16`<br><br>__Response:__ `u8[32]`| Retrieves a chunk of the configuration data bundled within the firmware|
|
||||
| Jump to bootloader | `0x01 0x07` | __Secure__ |__Response:__ `u8`| Jump to bootloader<br><br>May not be present – if QMK capabilities query returns “true”, then jump to bootloader is supported<br><br>* 0 means secure routes are disabled, and should be considered as a failure<br>* 1 means successful, board will jump to bootloader|
|
||||
| Hardware Identifier | `0x01 0x08` | |__Response:__ `u32[4]`| Retrieves a unique identifier for the board.|
|
||||
|
||||
### Keyboard - `0x02`
|
||||
This subsystem is always present, and reserved for vendor-specific functionality. No routes are defined by XAP.
|
||||
|
||||
|
||||
### User - `0x03`
|
||||
This subsystem is always present, and reserved for user-specific functionality. No routes are defined by XAP.
|
||||
|
||||
|
||||
|
||||
## Broadcast messages
|
||||
|
||||
Broadcast messages may be sent by the firmware to the host, without a corresponding inbound request. Each broadcast message uses the token `0xFFFF`, and does not expect a response from the host. Tokens are followed by an _ID_ signifying the type of broadcast, with corresponding _payload_.
|
||||
|
||||
|
||||
### Log message - `0x00`
|
||||
Replicates and replaces the same functionality as if using the standard QMK `CONSOLE_ENABLE = yes` in `rules.mk`. Normal prints within the firmware will manifest as log messages broadcast to the host. `hid_listen` will not be functional with XAP enabled.
|
||||
|
||||
Log message payloads include 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`(!) |
|
||||
### Secure Status - `0x01`
|
||||
Secure status has changed. Payloads include a `u8` matching a 'Secure Status' request.
|
||||
|
||||
**Example Secure Status Broadcast** -- secure "Unlocking"
|
||||
|
||||
| Byte | 0 | 1 | 2 | 3 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| **Purpose** | Token | Token | Broadcast Type | Secure Status |
|
||||
| **Value** | `0xFF` | `0xFF` | `0x01` | `0x01` |
|
||||
### Keyboard - `0x02`
|
||||
Reserved for vendor-specific functionality. No messages are defined by XAP.
|
||||
### User - `0x03`
|
||||
Reserved for user-specific functionality. No messages are defined by XAP.
|
||||
|
187
docs/xap_0.2.0.md
Normal file
187
docs/xap_0.2.0.md
Normal file
|
@ -0,0 +1,187 @@
|
|||
# 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. Valid token values are within the range `0x0100`-`0xFFFF`. |
|
||||
|
||||
## Requests and Responses
|
||||
|
||||
Communication generally follows a request/response pattern.
|
||||
|
||||
Each request needs to include a _token_ -- this `u16` value prefixes each outbound request from the host application and its corresponding response.
|
||||
This allows response messages to be correlated with their request, even if multiple host applications are communicating with the firmware simultaneously.
|
||||
Host applications should randomly generate a token ID for **every** outbound request, unless using a reserved token defined below.
|
||||
To ensure host interoperability, valid token values are within the range `0x0100`-`0xFFFF`.
|
||||
|
||||
This token is followed by a `u8` signifying the length of data in the request.
|
||||
|
||||
Two token values are reserved: `0xFFFE` and `0xFFFF`:
|
||||
* `0xFFFE`: A message sent by a host application may use this token if no response is to be sent -- a "fire and forget" message.
|
||||
* `0xFFFF`: Signifies a "broadcast" message sent by the firmware without prompting from the host application. Broadcast messages are defined later in this document.
|
||||
|
||||
Any request will generate at least one corresponding response, with the exception of messages using reserved tokens. Maximum total message length is 128 bytes due to RAM constraints.
|
||||
|
||||
Response messages will always be prefixed by the originating request _token_, directly followed by that request's _response flags_, then the response payload length:
|
||||
|
||||
| Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
|
||||
| -- | -- | -- | -- | -- | -- | -- | -- |
|
||||
| `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` |
|
||||
|
||||
## Routes
|
||||
|
||||
Subsystem validity should be queried through the “Enabled-in-firmware subsystem query” under the QMK subsystem (route=0x00,0x01).
|
||||
This is the primary method for determining if a subsystem has been enabled in the running firmware.
|
||||
|
||||
|
||||
### XAP - `0x00`
|
||||
This subsystem is always present, and provides the ability to query information about the XAP protocol of the connected device.
|
||||
|
||||
|
||||
| Name | Route | Tags | Payloads | Description |
|
||||
| -- | -- | -- | -- | -- |
|
||||
| Version Query | `0x00 0x00` | |__Response:__ `u32`| XAP protocol version query.<br><br>* Returns the BCD-encoded version in the format of XX.YY.ZZZZ => `0xXXYYZZZZ`<br> * e.g. 3.2.115 will match `0x03020115`, or bytes {0x15,0x01,0x02,0x03}.|
|
||||
| Capabilities Query | `0x00 0x01` | |__Response:__ `u32`| XAP subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.|
|
||||
| Enabled subsystem query | `0x00 0x02` | |__Response:__ `u32`| XAP protocol subsystem query. Each bit should be considered as a "usable" subsystem. For example, checking `(value & (1 << XAP_ROUTE_QMK) != 0)` means the QMK subsystem is enabled and available for querying.|
|
||||
| Secure Status | `0x00 0x03` | |__Response:__ `u8`| Query secure route status<br><br>* 0 means secure routes are disabled<br>* 1 means unlock sequence initiated but incomplete<br>* 2 means secure routes are allowed<br>* any other value should be interpreted as disabled|
|
||||
| Secure Unlock | `0x00 0x04` | || Initiate secure route unlock sequence|
|
||||
| Secure Lock | `0x00 0x05` | || Disable secure routes|
|
||||
|
||||
### QMK - `0x01`
|
||||
This subsystem is always present, and provides the ability to address QMK-specific functionality.
|
||||
|
||||
|
||||
| Name | Route | Tags | Payloads | Description |
|
||||
| -- | -- | -- | -- | -- |
|
||||
| Version Query | `0x01 0x00` | |__Response:__ `u32`| QMK protocol version query.<br><br>* Returns the BCD-encoded version in the format of XX.YY.ZZZZ => `0xXXYYZZZZ`<br> * e.g. 3.2.115 will match `0x03020115`, or bytes {0x15,0x01,0x02,0x03}.|
|
||||
| Capabilities Query | `0x01 0x01` | |__Response:__ `u32`| QMK subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.|
|
||||
| Board identifiers | `0x01 0x02` | |__Response:__<br> * Vendor ID: `u16`<br> * Product ID: `u16`<br> * Product Version: `u16`<br> * QMK Unique Identifier: `u32`| Retrieves the set of identifying information for the board.|
|
||||
| Board Manufacturer | `0x01 0x03` | |__Response:__ `string`| Retrieves the name of the manufacturer|
|
||||
| Product Name | `0x01 0x04` | |__Response:__ `string`| Retrieves the product name|
|
||||
| Config Blob Length | `0x01 0x05` | |__Response:__ `u32`| Retrieves the length of the configuration data bundled within the firmware|
|
||||
| Config Blob Chunk | `0x01 0x06` | |__Request:__ `u16`<br><br>__Response:__ `u8[32]`| Retrieves a chunk of the configuration data bundled within the firmware|
|
||||
| Jump to bootloader | `0x01 0x07` | __Secure__ |__Response:__ `u8`| Jump to bootloader<br><br>May not be present – if QMK capabilities query returns “true”, then jump to bootloader is supported<br><br>* 0 means secure routes are disabled, and should be considered as a failure<br>* 1 means successful, board will jump to bootloader|
|
||||
| Hardware Identifier | `0x01 0x08` | |__Response:__ `u32[4]`| Retrieves a unique identifier for the board.|
|
||||
|
||||
### Keyboard - `0x02`
|
||||
This subsystem is always present, and reserved for vendor-specific functionality. No routes are defined by XAP.
|
||||
|
||||
|
||||
### User - `0x03`
|
||||
This subsystem is always present, and reserved for user-specific functionality. No routes are defined by XAP.
|
||||
|
||||
|
||||
### Keymap - `0x04`
|
||||
This subsystem allows for query of currently configured keycodes.
|
||||
|
||||
|
||||
| Name | Route | Tags | Payloads | Description |
|
||||
| -- | -- | -- | -- | -- |
|
||||
| Capabilities Query | `0x04 0x01` | |__Response:__ `u32`| Keymap subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.|
|
||||
| Get Layer Count | `0x04 0x02` | |__Response:__ `u8`| Query maximum number of layers that can be addressed within the keymap.|
|
||||
| Get Keycode | `0x04 0x03` | |__Request:__<br> * Layer: `u8`<br> * Row: `u8`<br> * Column: `u8`<br><br>__Response:__ `u16`| Query the Keycode at the requested location.|
|
||||
| Get Encoder Keycode | `0x04 0x04` | |__Request:__<br> * Layer: `u8`<br> * Encoder: `u8`<br> * Clockwise: `u8`<br><br>__Response:__ `u16`| Query the Keycode at the requested location.|
|
||||
|
||||
### Remapping - `0x05`
|
||||
This subsystem allows for live reassignment of keycodes without rebuilding the firmware.
|
||||
|
||||
|
||||
| Name | Route | Tags | Payloads | Description |
|
||||
| -- | -- | -- | -- | -- |
|
||||
| Capabilities Query | `0x05 0x01` | |__Response:__ `u32`| Remapping subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.|
|
||||
| Get Layer Count | `0x05 0x02` | |__Response:__ `u8`| Query maximum number of layers that can be addressed within the keymap.|
|
||||
| Set Keycode | `0x05 0x03` | __Secure__ |__Request:__<br> * Layer: `u8`<br> * Row: `u8`<br> * Column: `u8`<br> * Keycode: `u16`| Modify the Keycode at the requested location.|
|
||||
| Set Encoder Keycode | `0x05 0x04` | __Secure__ |__Request:__<br> * Layer: `u8`<br> * Encoder: `u8`<br> * Clockwise: `u8`<br> * Keycode: `u16`| Modify the Keycode at the requested location.|
|
||||
|
||||
### Lighting - `0x06`
|
||||
This subsystem allows for control over the lighting subsystem.
|
||||
|
||||
|
||||
| Name | Route | Tags | Payloads | Description |
|
||||
| -- | -- | -- | -- | -- |
|
||||
| Capabilities Query | `0x06 0x01` | |__Response:__ `u32`| Lighting subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.|
|
||||
|
||||
|
||||
## Broadcast messages
|
||||
|
||||
Broadcast messages may be sent by the firmware to the host, without a corresponding inbound request. Each broadcast message uses the token `0xFFFF`, and does not expect a response from the host. Tokens are followed by an _ID_ signifying the type of broadcast, with corresponding _payload_.
|
||||
|
||||
|
||||
### Log message - `0x00`
|
||||
Replicates and replaces the same functionality as if using the standard QMK `CONSOLE_ENABLE = yes` in `rules.mk`. Normal prints within the firmware will manifest as log messages broadcast to the host. `hid_listen` will not be functional with XAP enabled.
|
||||
|
||||
Log message payloads include 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`(!) |
|
||||
### Secure Status - `0x01`
|
||||
Secure status has changed. Payloads include a `u8` matching a 'Secure Status' request.
|
||||
|
||||
**Example Secure Status Broadcast** -- secure "Unlocking"
|
||||
|
||||
| Byte | 0 | 1 | 2 | 3 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| **Purpose** | Token | Token | Broadcast Type | Secure Status |
|
||||
| **Value** | `0xFF` | `0xFF` | `0x01` | `0x01` |
|
||||
### Keyboard - `0x02`
|
||||
Reserved for vendor-specific functionality. No messages are defined by XAP.
|
||||
### User - `0x03`
|
||||
Reserved for user-specific functionality. No messages are defined by XAP.
|
||||
|
4
docs/xap_protocol.md
Normal file
4
docs/xap_protocol.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
<!-- This file is generated -->
|
||||
* [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
|
16
keyboards/zvecr/zv48/keymaps/xap/xap.hjson
Normal file
16
keyboards/zvecr/zv48/keymaps/xap/xap.hjson
Normal file
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
routes: {
|
||||
0x01: {
|
||||
type: command
|
||||
name: Capabilities Query
|
||||
define: CAPABILITIES_QUERY_USER
|
||||
description:
|
||||
'''
|
||||
USER subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.
|
||||
'''
|
||||
return_type: u32
|
||||
return_purpose: capabilities
|
||||
return_constant: XAP_ROUTE_USER_CAPABILITIES
|
||||
}
|
||||
}
|
||||
}
|
16
keyboards/zvecr/zv48/xap.hjson
Normal file
16
keyboards/zvecr/zv48/xap.hjson
Normal file
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
routes: {
|
||||
0x01: {
|
||||
type: command
|
||||
name: Capabilities Query
|
||||
define: CAPABILITIES_QUERY_KB
|
||||
description:
|
||||
'''
|
||||
KB subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.
|
||||
'''
|
||||
return_type: u32
|
||||
return_purpose: capabilities
|
||||
return_constant: XAP_ROUTE_KB_CAPABILITIES
|
||||
}
|
||||
}
|
||||
}
|
65
lib/python/qmk/casing.py
Executable file
65
lib/python/qmk/casing.py
Executable file
|
@ -0,0 +1,65 @@
|
|||
"""This script handles conversion between snake and camel casing.
|
||||
"""
|
||||
import re
|
||||
|
||||
_words_expr = re.compile(r"([a-zA-Z][^A-Z0-9]*|[0-9]+)")
|
||||
_lower_snake_case_expr = re.compile(r'^[a-z][a-z0-9_]*$')
|
||||
_upper_snake_case_expr = re.compile(r'^[A-Z][A-Z0-9_]*$')
|
||||
|
||||
|
||||
def _is_snake_case(str):
|
||||
"""Checks if the supplied string is already in snake case.
|
||||
"""
|
||||
match = _lower_snake_case_expr.match(str)
|
||||
if match:
|
||||
return True
|
||||
match = _upper_snake_case_expr.match(str)
|
||||
if match:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _split_snake_case(str):
|
||||
"""Splits up a string based on underscores, if it's in snake casing.
|
||||
"""
|
||||
if _is_snake_case(str):
|
||||
return [s.lower() for s in str.split("_")]
|
||||
return str
|
||||
|
||||
|
||||
def _split_camel_case(str):
|
||||
"""Splits up a string based on capitalised camel casing.
|
||||
"""
|
||||
return _words_expr.findall(str)
|
||||
|
||||
|
||||
def _split_cased_words(str):
|
||||
return _split_snake_case(str) if _is_snake_case(str) else _split_camel_case(str)
|
||||
|
||||
|
||||
def to_snake(str):
|
||||
str = "_".join([word.strip().lower() for word in _split_cased_words(str)])
|
||||
|
||||
# Fix acronyms
|
||||
str = str.replace('i_d', 'id')
|
||||
str = str.replace('x_a_p', 'xap')
|
||||
str = str.replace('q_m_k', 'qmk')
|
||||
|
||||
return str
|
||||
|
||||
|
||||
def to_upper_snake(str):
|
||||
return to_snake(str).upper()
|
||||
|
||||
|
||||
def to_camel(str):
|
||||
def _acronym(w):
|
||||
if w.strip().lower() == 'qmk':
|
||||
return 'QMK'
|
||||
elif w.strip().lower() == 'xap':
|
||||
return 'XAP'
|
||||
elif w.strip().lower() == 'id':
|
||||
return 'ID'
|
||||
return w.title()
|
||||
|
||||
return "".join([_acronym(word) for word in _split_cased_words(str)])
|
|
@ -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 = [
|
||||
|
@ -54,6 +55,7 @@ subcommands = [
|
|||
'qmk.cli.generate.info_json',
|
||||
'qmk.cli.generate.keyboard_c',
|
||||
'qmk.cli.generate.keyboard_h',
|
||||
'qmk.cli.generate.keymap_hash',
|
||||
'qmk.cli.generate.layouts',
|
||||
'qmk.cli.generate.rgb_breathe_table',
|
||||
'qmk.cli.generate.rules_mk',
|
||||
|
@ -76,6 +78,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)
|
||||
|
|
36
lib/python/qmk/cli/generate/keymap_hash.py
Normal file
36
lib/python/qmk/cli/generate/keymap_hash.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
"""Used by the make system to generate header.
|
||||
"""
|
||||
from fnvhash import fnv1a_32
|
||||
|
||||
from milc import cli
|
||||
|
||||
from qmk.commands import dump_lines
|
||||
from qmk.keymap import locate_keymap
|
||||
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
|
||||
|
||||
|
||||
@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 header for.')
|
||||
@cli.argument('-km', '--keymap', arg_only=True, required=True, help='Keymap to generate header for.')
|
||||
@cli.subcommand('Used by the make system to generate header', hidden=True)
|
||||
def generate_keymap_hash(cli):
|
||||
# Build the header file.
|
||||
header_lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '#pragma once']
|
||||
|
||||
keymap_folder = locate_keymap(cli.args.keyboard, cli.args.keymap).parent
|
||||
|
||||
keymap_files = list(keymap_folder.glob('**/*'))
|
||||
keymap_files.sort()
|
||||
|
||||
content = ""
|
||||
for file in keymap_files:
|
||||
content += file.read_text(encoding='utf-8')
|
||||
|
||||
val = fnv1a_32(bytes(content, 'utf-8'))
|
||||
header_lines.append(f'#define KEYMAP_HASH 0x{val:08X}ul')
|
||||
|
||||
# Show the results
|
||||
dump_lines(cli.args.output, header_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
|
38
lib/python/qmk/cli/xap/generate_docs.py
Executable file
38
lib/python/qmk/cli/xap/generate_docs.py
Executable file
|
@ -0,0 +1,38 @@
|
|||
"""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('''\
|
||||
<!-- This file is generated -->
|
||||
''')
|
||||
|
||||
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))
|
69
lib/python/qmk/cli/xap/generate_qmk.py
Executable file
69
lib/python/qmk/cli/xap/generate_qmk.py
Executable file
|
@ -0,0 +1,69 @@
|
|||
"""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.blob_generator import generate_blob
|
||||
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.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='Name of the keyboard')
|
||||
@cli.argument('-km', '--keymap', help='The keymap\'s name')
|
||||
@cli.subcommand('Generates the XAP protocol include.', hidden=False if cli.config.user.developer else True)
|
||||
def xap_generate_qmk_inc(cli):
|
||||
"""Generates the XAP protocol inline codegen file, generated during normal build.
|
||||
"""
|
||||
# Determine our keyboard/keymap
|
||||
if not cli.args.keyboard:
|
||||
cli.log.error('Missing parameter: --keyboard')
|
||||
cli.subcommands['xap-generate-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_inline(cli.args.output, cli.args.keyboard, cli.args.keymap)
|
||||
|
||||
|
||||
@cli.argument('-o', '--output', type=normpath, help='File to write to')
|
||||
@cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='Name of the keyboard')
|
||||
@cli.argument('-km', '--keymap', help='The keymap\'s name')
|
||||
@cli.subcommand('Generates the XAP protocol include.', hidden=False if cli.config.user.developer else True)
|
||||
def xap_generate_qmk_h(cli):
|
||||
"""Generates the XAP protocol header file, generated during normal build.
|
||||
"""
|
||||
# Determine our keyboard/keymap
|
||||
if not cli.args.keyboard:
|
||||
cli.log.error('Missing parameter: --keyboard')
|
||||
cli.subcommands['xap-generate-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_header(cli.args.output, cli.args.keyboard, cli.args.keymap)
|
||||
|
||||
|
||||
@cli.argument('-o', '--output', type=normpath, help='File to write to')
|
||||
@cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='Name of the keyboard')
|
||||
@cli.argument('-km', '--keymap', help='The keymap\'s name')
|
||||
@cli.subcommand('Generates the XAP config payload include.', hidden=False if cli.config.user.developer else True)
|
||||
def xap_generate_qmk_blob_h(cli):
|
||||
"""Generates the XAP config payload header file, generated during normal build.
|
||||
"""
|
||||
# Determine our keyboard/keymap
|
||||
if not cli.args.keyboard:
|
||||
cli.log.error('Missing parameter: --keyboard')
|
||||
cli.subcommands['xap-generate-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_blob(cli.args.output, cli.args.keyboard, cli.args.keymap)
|
217
lib/python/qmk/cli/xap/xap.py
Normal file
217
lib/python/qmk/cli/xap/xap.py
Normal file
|
@ -0,0 +1,217 @@
|
|||
"""Interactions with compatible XAP devices
|
||||
"""
|
||||
import cmd
|
||||
|
||||
from milc import cli
|
||||
|
||||
from qmk.keyboard import render_layout
|
||||
from qmk.xap.common import get_xap_keycodes
|
||||
|
||||
from .xap_client import XAPClient, XAPEventType, XAPSecureStatus
|
||||
|
||||
KEYCODE_MAP = get_xap_keycodes('latest')
|
||||
|
||||
|
||||
def print_dotted_output(kb_info_json, prefix=''):
|
||||
"""Print the info.json in a plain text format with dot-joined keys.
|
||||
"""
|
||||
for key in sorted(kb_info_json):
|
||||
new_prefix = f'{prefix}.{key}' if prefix else key
|
||||
|
||||
if key in ['parse_errors', 'parse_warnings']:
|
||||
continue
|
||||
elif key == 'layouts' and prefix == '':
|
||||
cli.echo(' {fg_blue}layouts{fg_reset}: %s', ', '.join(sorted(kb_info_json['layouts'].keys())))
|
||||
elif isinstance(kb_info_json[key], bytes):
|
||||
conv = "".join(["{:02X}".format(b) for b in kb_info_json[key]])
|
||||
cli.echo(' {fg_blue}%s{fg_reset}: %s', new_prefix, conv)
|
||||
elif isinstance(kb_info_json[key], dict):
|
||||
print_dotted_output(kb_info_json[key], new_prefix)
|
||||
elif isinstance(kb_info_json[key], list):
|
||||
data = kb_info_json[key]
|
||||
if len(data) and isinstance(data[0], dict):
|
||||
for index, item in enumerate(data, start=0):
|
||||
cli.echo(' {fg_blue}%s.%s{fg_reset}: %s', new_prefix, index, str(item))
|
||||
else:
|
||||
cli.echo(' {fg_blue}%s{fg_reset}: %s', new_prefix, ', '.join(map(str, data)))
|
||||
else:
|
||||
cli.echo(' {fg_blue}%s{fg_reset}: %s', new_prefix, kb_info_json[key])
|
||||
|
||||
|
||||
def _list_devices():
|
||||
"""Dump out available devices
|
||||
"""
|
||||
cli.log.info('Available devices:')
|
||||
devices = XAPClient.list()
|
||||
for dev in devices:
|
||||
device = XAPClient().connect(dev)
|
||||
|
||||
data = device.info()
|
||||
cli.log.info(' %04x:%04x %s %s [API:%s]', dev['vendor_id'], dev['product_id'], dev['manufacturer_string'], dev['product_string'], data['xap'])
|
||||
|
||||
if cli.config.general.verbose:
|
||||
# TODO: better formatting like 'lsusb -v'?
|
||||
print_dotted_output(data)
|
||||
|
||||
|
||||
class XAPShell(cmd.Cmd):
|
||||
intro = 'Welcome to the XAP shell. Type help or ? to list commands.\n'
|
||||
prompt = 'Ψ> '
|
||||
|
||||
def __init__(self, device):
|
||||
cmd.Cmd.__init__(self)
|
||||
self.device = device
|
||||
# cache keycodes for this device
|
||||
self.keycodes = get_xap_keycodes(device.version()['xap'])
|
||||
|
||||
def do_about(self, arg):
|
||||
"""Prints out the current version of QMK with a build date
|
||||
"""
|
||||
# TODO: request stuff?
|
||||
print(self.device.info()['xap'])
|
||||
|
||||
def do_status(self, arg):
|
||||
"""Prints out the current device state
|
||||
"""
|
||||
status = self.device.status()
|
||||
print('Secure:%s' % status.get('lock', '???'))
|
||||
|
||||
def do_unlock(self, arg):
|
||||
"""Initiate secure unlock
|
||||
"""
|
||||
self.device.unlock()
|
||||
print('Unlock Requested...')
|
||||
|
||||
def do_lock(self, arg):
|
||||
"""Disable secure routes
|
||||
"""
|
||||
self.device.lock()
|
||||
|
||||
def do_reset(self, arg):
|
||||
"""Jump to bootloader if unlocked
|
||||
"""
|
||||
if not self.device.reset():
|
||||
print("Reboot to bootloader failed")
|
||||
return True
|
||||
|
||||
def do_listen(self, arg):
|
||||
"""Log out XAP broadcast messages
|
||||
"""
|
||||
try:
|
||||
cli.log.info('Listening for XAP broadcasts...')
|
||||
while 1:
|
||||
(event, data) = self.device.listen()
|
||||
|
||||
if event == XAPEventType.SECURE:
|
||||
secure_status = XAPSecureStatus(data[0]).name
|
||||
|
||||
cli.log.info(' Secure[%s]', secure_status)
|
||||
else:
|
||||
cli.log.info(' Broadcast: type[%02x] data:[%s]', event, data.hex())
|
||||
|
||||
except KeyboardInterrupt:
|
||||
cli.log.info('Stopping...')
|
||||
|
||||
def do_keycode(self, arg):
|
||||
"""Prints out the keycode value of a certain layer, row, and column
|
||||
"""
|
||||
data = bytes(map(int, arg.split()))
|
||||
if len(data) != 3:
|
||||
cli.log.error('Invalid args')
|
||||
return
|
||||
|
||||
keycode = self.device.transaction(b'\x04\x03', data)
|
||||
keycode = int.from_bytes(keycode, 'little')
|
||||
print(f'keycode:{self.keycodes.get(keycode, "unknown")}[{keycode}]')
|
||||
|
||||
def do_keymap(self, arg):
|
||||
"""Prints out the keycode values of a certain layer
|
||||
"""
|
||||
data = bytes(map(int, arg.split()))
|
||||
if len(data) != 1:
|
||||
cli.log.error('Invalid args')
|
||||
return
|
||||
|
||||
info = self.device.info()
|
||||
rows = info['matrix_size']['rows']
|
||||
cols = info['matrix_size']['cols']
|
||||
|
||||
for r in range(rows):
|
||||
for c in range(cols):
|
||||
q = data + r.to_bytes(1, byteorder='little') + c.to_bytes(1, byteorder='little')
|
||||
keycode = self.device.transaction(b'\x04\x03', q)
|
||||
keycode = int.from_bytes(keycode, 'little')
|
||||
print(f'| {self.keycodes.get(keycode, "unknown").ljust(7)} ', end='', flush=True)
|
||||
print('|')
|
||||
|
||||
def do_layer(self, arg):
|
||||
"""Renders keycode values of a certain layer
|
||||
"""
|
||||
data = bytes(map(int, arg.split()))
|
||||
if len(data) != 1:
|
||||
cli.log.error('Invalid args')
|
||||
return
|
||||
|
||||
info = self.device.info()
|
||||
|
||||
# Assumptions on selected layout rather than prompt
|
||||
first_layout = next(iter(info['layouts']))
|
||||
layout = info['layouts'][first_layout]['layout']
|
||||
|
||||
keycodes = []
|
||||
for item in layout:
|
||||
q = data + bytes(item['matrix'])
|
||||
keycode = self.device.transaction(b'\x04\x03', q)
|
||||
keycode = int.from_bytes(keycode, 'little')
|
||||
keycodes.append(self.keycodes.get(keycode, '???'))
|
||||
|
||||
print(render_layout(layout, False, keycodes))
|
||||
|
||||
def do_exit(self, line):
|
||||
"""Quit shell
|
||||
"""
|
||||
return True
|
||||
|
||||
def do_EOF(self, line): # noqa: N802
|
||||
"""Quit shell (ctrl+D)
|
||||
"""
|
||||
return True
|
||||
|
||||
def loop(self):
|
||||
"""Wrapper for cmdloop that handles ctrl+C
|
||||
"""
|
||||
try:
|
||||
self.cmdloop()
|
||||
print('')
|
||||
except KeyboardInterrupt:
|
||||
print('^C')
|
||||
return False
|
||||
|
||||
|
||||
@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
|
||||
"""
|
||||
if cli.args.list:
|
||||
return _list_devices()
|
||||
|
||||
# Connect to first available device
|
||||
devices = XAPClient.list()
|
||||
if not devices:
|
||||
cli.log.error('No devices found!')
|
||||
return False
|
||||
|
||||
dev = devices[0]
|
||||
cli.log.info('Connecting to:%04x:%04x %s %s', dev['vendor_id'], dev['product_id'], dev['manufacturer_string'], dev['product_string'])
|
||||
device = XAPClient().connect(dev)
|
||||
|
||||
# shell?
|
||||
if cli.args.interactive:
|
||||
XAPShell(device).loop()
|
||||
return True
|
||||
|
||||
XAPShell(device).onecmd(' '.join(cli.args.action))
|
200
lib/python/qmk/cli/xap/xap_client.py
Normal file
200
lib/python/qmk/cli/xap/xap_client.py
Normal file
|
@ -0,0 +1,200 @@
|
|||
"""Dummy XAP Client
|
||||
"""
|
||||
import json
|
||||
import random
|
||||
import gzip
|
||||
import threading
|
||||
import functools
|
||||
from struct import Struct, pack, unpack
|
||||
from collections import namedtuple
|
||||
from enum import IntFlag, IntEnum
|
||||
from platform import platform
|
||||
|
||||
RequestPacket = namedtuple('RequestPacket', 'token length data')
|
||||
RequestStruct = Struct('<HB61s')
|
||||
|
||||
ResponsePacket = namedtuple('ResponsePacket', 'token flags length data')
|
||||
ResponseStruct = Struct('<HBB60s')
|
||||
|
||||
|
||||
def _gen_token():
|
||||
"""Generate XAP token - cannot start with 00xx or 'reserved' (FFFE|FFFF)
|
||||
"""
|
||||
token = random.randrange(0x0100, 0xFFFD)
|
||||
|
||||
# swap endianness
|
||||
return unpack('<H', pack('>H', token))[0]
|
||||
|
||||
|
||||
def _u32toBCD(val): # noqa: N802
|
||||
"""Create BCD string
|
||||
"""
|
||||
return f'{val>>24}.{val>>16 & 0xFF}.{val & 0xFFFF}'
|
||||
|
||||
|
||||
class XAPSecureStatus(IntEnum):
|
||||
LOCKED = 0x00
|
||||
UNLOCKING = 0x01
|
||||
UNLOCKED = 0x02
|
||||
|
||||
|
||||
class XAPFlags(IntFlag):
|
||||
FAILURE = 0
|
||||
SUCCESS = 1 << 0
|
||||
SECURE_FAILURE = 1 << 1
|
||||
UNLOCK_IN_PROGRESS = 1 << 6
|
||||
UNLOCKED = 1 << 7
|
||||
|
||||
|
||||
class XAPEventType(IntEnum):
|
||||
SECURE = 0x01
|
||||
KEYBOARD = 0x02
|
||||
USER = 0x03
|
||||
|
||||
|
||||
class XAPDevice:
|
||||
def __init__(self, dev):
|
||||
"""Constructor opens hid device and starts dependent services
|
||||
"""
|
||||
self.responses = {}
|
||||
|
||||
self.dev = hid.Device(path=dev['path'])
|
||||
|
||||
self.bg = threading.Thread(target=self._read_loop, daemon=True)
|
||||
self.bg.start()
|
||||
|
||||
def _read_loop(self):
|
||||
"""Background thread to signal waiting transactions
|
||||
"""
|
||||
while 1:
|
||||
array_alpha = self.dev.read(64, 100)
|
||||
if array_alpha:
|
||||
token = int.from_bytes(array_alpha[:2], 'little')
|
||||
event = self.responses.get(token)
|
||||
if event:
|
||||
event._ret = array_alpha
|
||||
event.set()
|
||||
|
||||
def _query_device_info(self):
|
||||
datalen = int.from_bytes(self.transaction(b'\x01\x05') or bytes(0), 'little')
|
||||
if not datalen:
|
||||
return {}
|
||||
|
||||
data = []
|
||||
offset = 0
|
||||
while offset < datalen:
|
||||
chunk = self.transaction(b'\x01\x06', offset)
|
||||
data += chunk
|
||||
offset += len(chunk)
|
||||
str_data = gzip.decompress(bytearray(data[:datalen]))
|
||||
return json.loads(str_data)
|
||||
|
||||
def listen(self):
|
||||
"""Receive a 'broadcast' message
|
||||
"""
|
||||
token = 0xFFFF
|
||||
event = threading.Event()
|
||||
self.responses[token] = event
|
||||
|
||||
event.wait()
|
||||
|
||||
r = ResponsePacket._make(ResponseStruct.unpack(event._ret))
|
||||
return (r.flags, r.data[:r.length])
|
||||
|
||||
def transaction(self, *args):
|
||||
"""Request/Receive
|
||||
"""
|
||||
# convert args to array of bytes
|
||||
data = bytes()
|
||||
for arg in args:
|
||||
if isinstance(arg, (bytes, bytearray)):
|
||||
data += arg
|
||||
if isinstance(arg, int): # TODO: remove terrible assumption of u16
|
||||
data += arg.to_bytes(2, byteorder='little')
|
||||
|
||||
token = _gen_token()
|
||||
|
||||
p = RequestPacket(token, len(data), data)
|
||||
buffer = RequestStruct.pack(*list(p))
|
||||
|
||||
event = threading.Event()
|
||||
self.responses[token] = event
|
||||
|
||||
# prepend 0 on windows because reasons...
|
||||
if 'windows' in platform().lower():
|
||||
buffer = b'\x00' + buffer
|
||||
self.dev.write(buffer)
|
||||
|
||||
event.wait(timeout=1)
|
||||
self.responses.pop(token, None)
|
||||
if not hasattr(event, '_ret'):
|
||||
return None
|
||||
|
||||
r = ResponsePacket._make(ResponseStruct.unpack(event._ret))
|
||||
if r.flags != XAPFlags.SUCCESS:
|
||||
return None
|
||||
|
||||
return r.data[:r.length]
|
||||
|
||||
@functools.cache
|
||||
def version(self):
|
||||
ver = int.from_bytes(self.transaction(b'\x00\x00') or bytes(0), 'little')
|
||||
return {'xap': _u32toBCD(ver)}
|
||||
|
||||
@functools.cache
|
||||
def info(self):
|
||||
data = self._query_device_info()
|
||||
data['_id'] = self.transaction(b'\x01\x08')
|
||||
data['xap'] = self.version()['xap']
|
||||
return data
|
||||
|
||||
def status(self):
|
||||
lock = int.from_bytes(self.transaction(b'\x00\x03') or bytes(0), 'little')
|
||||
|
||||
data = {}
|
||||
data['lock'] = XAPSecureStatus(lock).name
|
||||
return data
|
||||
|
||||
def unlock(self):
|
||||
self.transaction(b'\x00\x04')
|
||||
|
||||
def lock(self):
|
||||
self.transaction(b'\x00\x05')
|
||||
|
||||
def reset(self):
|
||||
status = int.from_bytes(self.transaction(b'\x01\x07') or bytes(0), 'little')
|
||||
return status == 1
|
||||
|
||||
|
||||
class XAPClient:
|
||||
@staticmethod
|
||||
def _lazy_imports():
|
||||
# Lazy load to avoid missing dependency issues
|
||||
global hid
|
||||
import hid
|
||||
|
||||
@staticmethod
|
||||
def list(search=None):
|
||||
"""Find compatible XAP devices
|
||||
"""
|
||||
XAPClient._lazy_imports()
|
||||
|
||||
def _is_xap_usage(x):
|
||||
return x['usage_page'] == 0xFF51 and x['usage'] == 0x0058
|
||||
|
||||
def _is_filtered_device(x):
|
||||
name = '%04x:%04x' % (x['vendor_id'], x['product_id'])
|
||||
return name.lower().startswith(search.lower())
|
||||
|
||||
devices = filter(_is_xap_usage, hid.enumerate())
|
||||
if search:
|
||||
devices = filter(_is_filtered_device, devices)
|
||||
|
||||
return list(devices)
|
||||
|
||||
def connect(self, dev):
|
||||
"""Connect to a given XAP device
|
||||
"""
|
||||
XAPClient._lazy_imports()
|
||||
|
||||
return XAPDevice(dev)
|
|
@ -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
|
||||
|
|
|
@ -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
176
lib/python/qmk/xap/common.py
Executable file
176
lib/python/qmk/xap/common.py
Executable file
|
@ -0,0 +1,176 @@
|
|||
"""This script handles the XAP protocol data files.
|
||||
"""
|
||||
import os
|
||||
import hjson
|
||||
import jsonschema
|
||||
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, validate
|
||||
from qmk.decorators import lru_cache
|
||||
from qmk.keymap import locate_keymap
|
||||
from qmk.path import keyboard
|
||||
|
||||
XAP_SPEC = 'xap.hjson'
|
||||
|
||||
|
||||
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 _find_kb_spec(kb):
|
||||
base_path = Path('keyboards')
|
||||
keyboard_parent = keyboard(kb)
|
||||
|
||||
for _ in range(5):
|
||||
if keyboard_parent == base_path:
|
||||
break
|
||||
|
||||
spec = keyboard_parent / XAP_SPEC
|
||||
if spec.exists():
|
||||
return spec
|
||||
|
||||
keyboard_parent = keyboard_parent.parent
|
||||
|
||||
# Just return something we know doesn't exist
|
||||
return keyboard(kb) / XAP_SPEC
|
||||
|
||||
|
||||
def _find_km_spec(kb, km):
|
||||
return locate_keymap(kb, km).parent / XAP_SPEC
|
||||
|
||||
|
||||
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, dict)):
|
||||
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')
|
||||
|
||||
|
||||
def merge_xap_defs(kb, km):
|
||||
"""Gets the latest version of the XAP definitions and merges in optional keyboard/keymap specs
|
||||
"""
|
||||
definitions = [get_xap_defs('latest')]
|
||||
|
||||
kb_xap = _find_kb_spec(kb)
|
||||
if kb_xap.exists():
|
||||
definitions.append({'routes': {'0x02': hjson.load(kb_xap.open(encoding='utf-8'))}})
|
||||
|
||||
km_xap = _find_km_spec(kb, km)
|
||||
if km_xap.exists():
|
||||
definitions.append({'routes': {'0x03': hjson.load(km_xap.open(encoding='utf-8'))}})
|
||||
|
||||
defs = _merge_ordered_dicts(definitions)
|
||||
|
||||
try:
|
||||
validate(defs, 'qmk.xap.v1')
|
||||
|
||||
except jsonschema.ValidationError as e:
|
||||
print(f'Invalid XAP spec: {e.message}')
|
||||
exit(1)
|
||||
|
||||
return defs
|
||||
|
||||
|
||||
@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
57
lib/python/qmk/xap/gen_firmware/blob_generator.py
Normal file
57
lib/python/qmk/xap/gen_firmware/blob_generator.py
Normal file
|
@ -0,0 +1,57 @@
|
|||
"""This script generates the XAP info.json payload header to be compiled into QMK.
|
||||
"""
|
||||
import json
|
||||
import gzip
|
||||
from pathlib import Path
|
||||
|
||||
from qmk.info import keymap_json
|
||||
from qmk.commands import get_chunks, dump_lines
|
||||
from qmk.json_schema import deep_update, json_load
|
||||
|
||||
from qmk.constants import GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE
|
||||
|
||||
|
||||
def _build_info(keyboard, keymap):
|
||||
"""Build the xap version of info.json
|
||||
"""
|
||||
defaults_json = json_load(Path('data/mappings/xap_defaults.json'))
|
||||
km_info_json = keymap_json(keyboard, keymap)
|
||||
|
||||
info_json = {}
|
||||
deep_update(info_json, defaults_json)
|
||||
deep_update(info_json, km_info_json)
|
||||
|
||||
# TODO: Munge to XAP requirements
|
||||
del info_json['config_h_features']
|
||||
|
||||
return info_json
|
||||
|
||||
|
||||
def generate_blob(output_file, keyboard, keymap):
|
||||
"""Generate XAP payload
|
||||
"""
|
||||
info_json = _build_info(keyboard, keymap)
|
||||
|
||||
# Minify
|
||||
str_data = json.dumps(info_json, separators=(',', ':'))
|
||||
|
||||
# Compress
|
||||
compressed = gzip.compress(str_data.encode("utf-8"), compresslevel=9)
|
||||
|
||||
# split into lines to match xxd output
|
||||
hex_array = ["0x{:02X}".format(b) for b in compressed]
|
||||
data_len = len(hex_array)
|
||||
|
||||
data = ""
|
||||
for chunk in get_chunks(hex_array, 12):
|
||||
data += f' {", ".join(chunk)},\n'
|
||||
|
||||
lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '#pragma once', '']
|
||||
|
||||
# Gen output file
|
||||
lines.append('static const unsigned char config_blob_gz[] PROGMEM = {')
|
||||
lines.append(data)
|
||||
lines.append('};')
|
||||
lines.append(f'#define CONFIG_BLOB_GZ_LEN {data_len}')
|
||||
|
||||
dump_lines(output_file, lines)
|
268
lib/python/qmk/xap/gen_firmware/header_generator.py
Executable file
268
lib/python/qmk/xap/gen_firmware/header_generator.py
Executable file
|
@ -0,0 +1,268 @@
|
|||
"""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 merge_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')
|
||||
name = to_snake(f'{broadcast_prefix}_{define}')
|
||||
|
||||
lines.append(f'#define {broadcast_prefix}_{define} {key}')
|
||||
if 'return_type' in value:
|
||||
ret_type = _get_c_type(value['return_type'])
|
||||
lines.append(f'void {name}({ret_type} value);')
|
||||
else:
|
||||
lines.append(f'void {name}(const void *data, size_t length);')
|
||||
|
||||
# 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, keymap):
|
||||
"""Generates the XAP protocol header file, generated during normal build.
|
||||
"""
|
||||
xap_defs = merge_xap_defs(keyboard, keymap)
|
||||
|
||||
# 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.findall(git_get_version() or "") or [('0', '0', '0')]
|
||||
lines.append(f'#define QMK_BCD_VERSION 0x{int(b[0][0]):02d}{int(b[0][1]):02d}{int(b[0][1]):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)
|
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 merge_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, keyboard, keymap):
|
||||
"""Generates the XAP protocol header file, generated during normal build.
|
||||
"""
|
||||
xap_defs = merge_xap_defs(keyboard, keymap)
|
||||
|
||||
# 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)
|
|
@ -16,6 +16,9 @@
|
|||
bool via_eeprom_is_valid(void);
|
||||
void via_eeprom_set_valid(bool valid);
|
||||
void eeconfig_init_via(void);
|
||||
#elif defined(DYNAMIC_KEYMAP_ENABLE)
|
||||
# include "keymap_hash.h"
|
||||
void dynamic_keymap_reset(void);
|
||||
#endif
|
||||
|
||||
/** \brief eeconfig enable
|
||||
|
@ -81,6 +84,9 @@ void eeconfig_init_quantum(void) {
|
|||
// properly re-initialized.
|
||||
via_eeprom_set_valid(false);
|
||||
eeconfig_init_via();
|
||||
#elif defined(DYNAMIC_KEYMAP_ENABLE)
|
||||
dynamic_keymap_reset();
|
||||
eeprom_update_dword(EECONFIG_KEYMAP_HASH, KEYMAP_HASH);
|
||||
#endif
|
||||
|
||||
eeconfig_init_kb();
|
||||
|
@ -119,10 +125,14 @@ void eeconfig_disable(void) {
|
|||
*/
|
||||
bool eeconfig_is_enabled(void) {
|
||||
bool is_eeprom_enabled = (eeprom_read_word(EECONFIG_MAGIC) == EECONFIG_MAGIC_NUMBER);
|
||||
#ifdef VIA_ENABLE
|
||||
#if defined(VIA_ENABLE)
|
||||
if (is_eeprom_enabled) {
|
||||
is_eeprom_enabled = via_eeprom_is_valid();
|
||||
}
|
||||
#elif defined(DYNAMIC_KEYMAP_ENABLE)
|
||||
if (is_eeprom_enabled) {
|
||||
is_eeprom_enabled = (eeprom_read_dword(EECONFIG_KEYMAP_HASH) == KEYMAP_HASH);
|
||||
}
|
||||
#endif
|
||||
return is_eeprom_enabled;
|
||||
}
|
||||
|
@ -133,10 +143,14 @@ bool eeconfig_is_enabled(void) {
|
|||
*/
|
||||
bool eeconfig_is_disabled(void) {
|
||||
bool is_eeprom_disabled = (eeprom_read_word(EECONFIG_MAGIC) == EECONFIG_MAGIC_NUMBER_OFF);
|
||||
#ifdef VIA_ENABLE
|
||||
#if defined(VIA_ENABLE)
|
||||
if (!is_eeprom_disabled) {
|
||||
is_eeprom_disabled = !via_eeprom_is_valid();
|
||||
}
|
||||
#elif defined(DYNAMIC_KEYMAP_ENABLE)
|
||||
if (!is_eeprom_disabled) {
|
||||
is_eeprom_disabled = (eeprom_read_dword(EECONFIG_KEYMAP_HASH) != KEYMAP_HASH);
|
||||
}
|
||||
#endif
|
||||
return is_eeprom_disabled;
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
#include <stdbool.h>
|
||||
|
||||
#ifndef EECONFIG_MAGIC_NUMBER
|
||||
# define EECONFIG_MAGIC_NUMBER (uint16_t)0xFEE8 // When changing, decrement this value to avoid future re-init issues
|
||||
# define EECONFIG_MAGIC_NUMBER (uint16_t)0xFEE7 // When changing, decrement this value to avoid future re-init issues
|
||||
#endif
|
||||
#define EECONFIG_MAGIC_NUMBER_OFF (uint16_t)0xFFFF
|
||||
|
||||
|
@ -53,8 +53,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
|
||||
// TODO: Combine these into a single word and single block of EEPROM
|
||||
#define EECONFIG_KEYMAP_UPPER_BYTE (uint8_t *)34
|
||||
|
||||
#define EECONFIG_KEYMAP_HASH (uint32_t *)35
|
||||
|
||||
// Size of EEPROM being used, other code can refer to this for available EEPROM
|
||||
#define EECONFIG_SIZE 35
|
||||
#define EECONFIG_SIZE 39
|
||||
/* debug bit */
|
||||
#define EECONFIG_DEBUG_ENABLE (1 << 0)
|
||||
#define EECONFIG_DEBUG_MATRIX (1 << 1)
|
||||
|
|
|
@ -579,5 +579,9 @@ void secure_hook_quantum(secure_status_t secure_status) {
|
|||
clear_keyboard();
|
||||
layer_clear();
|
||||
}
|
||||
|
||||
# if defined(XAP_ENABLE)
|
||||
xap_broadcast_secure_status(secure_status);
|
||||
# endif
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -210,6 +210,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
|
||||
|
|
160
quantum/xap/xap.c
Normal file
160
quantum/xap/xap.c
Normal file
|
@ -0,0 +1,160 @@
|
|||
/* 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 "config_blob_gz.h"
|
||||
bool get_config_blob_chunk(uint16_t offset, uint8_t *data, uint8_t data_len) {
|
||||
if (offset + data_len > CONFIG_BLOB_GZ_LEN) {
|
||||
data_len = CONFIG_BLOB_GZ_LEN - offset;
|
||||
}
|
||||
|
||||
memcpy_P(data, &config_blob_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);
|
||||
}
|
38
quantum/xap/xap.h
Normal file
38
quantum/xap/xap.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
/* 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);
|
161
quantum/xap/xap_handlers.c
Normal file
161
quantum/xap/xap_handlers.c
Normal file
|
@ -0,0 +1,161 @@
|
|||
/* 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_config_blob_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_config_blob_chunk_t ret = {0};
|
||||
|
||||
bool get_config_blob_chunk(uint16_t offset, uint8_t * data, uint8_t data_len);
|
||||
get_config_blob_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);
|
||||
}
|
||||
|
||||
#ifdef BOOTLOADER_JUMP_SUPPORTED
|
||||
bool xap_respond_request_bootloader_jump(xap_token_t token, const void *data, size_t length) {
|
||||
uint8_t ret = secure_is_unlocked();
|
||||
|
||||
// TODO: post to deferred queue so this request can return?
|
||||
bool res = xap_respond_data(token, &ret, sizeof(ret));
|
||||
reset_keyboard();
|
||||
return res;
|
||||
}
|
||||
#endif
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
bool xap_respond_keymap_get_layer_count(xap_token_t token, const void *data, size_t length) {
|
||||
uint8_t ret = keymap_layer_count();
|
||||
return xap_respond_data(token, &ret, sizeof(ret));
|
||||
}
|
||||
|
||||
bool xap_respond_get_keymap_keycode(xap_token_t token, const void *data, size_t length) {
|
||||
if (length != sizeof(xap_route_keymap_get_keymap_keycode_arg_t)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
xap_route_keymap_get_keymap_keycode_arg_t *arg = (xap_route_keymap_get_keymap_keycode_arg_t *)data;
|
||||
|
||||
keypos_t pos = MAKE_KEYPOS(arg->row, arg->column);
|
||||
|
||||
uint16_t keycode = keymap_key_to_keycode(arg->layer, pos);
|
||||
return xap_respond_data(token, &keycode, sizeof(keycode));
|
||||
}
|
||||
|
||||
#if ((defined(ENCODER_MAP_ENABLE)))
|
||||
bool xap_respond_get_encoder_keycode(xap_token_t token, const void *data, size_t length) {
|
||||
if (length != sizeof(xap_route_keymap_get_encoder_keycode_arg_t)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
xap_route_keymap_get_encoder_keycode_arg_t *arg = (xap_route_keymap_get_encoder_keycode_arg_t *)data;
|
||||
|
||||
keypos_t pos = MAKE_KEYPOS(arg->clockwise ? KEYLOC_ENCODER_CW : KEYLOC_ENCODER_CCW, arg->encoder);
|
||||
|
||||
uint16_t keycode = keymap_key_to_keycode(arg->layer, pos);
|
||||
return xap_respond_data(token, &keycode, sizeof(keycode));
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ((defined(DYNAMIC_KEYMAP_ENABLE)))
|
||||
bool xap_respond_dynamic_keymap_set_keycode(xap_token_t token, const void *data, size_t length) {
|
||||
if (length != sizeof(xap_route_remapping_set_keymap_keycode_arg_t)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
xap_route_remapping_set_keymap_keycode_arg_t *arg = (xap_route_remapping_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(DYNAMIC_KEYMAP_ENABLE) && defined(ENCODER_MAP_ENABLE)))
|
||||
bool xap_respond_dynamic_encoder_set_keycode(xap_token_t token, const void *data, size_t length) {
|
||||
if (length != sizeof(xap_route_remapping_set_encoder_keycode_arg_t)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
xap_route_remapping_set_encoder_keycode_arg_t *arg = (xap_route_remapping_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
|
||||
}
|
||||
|
|
|
@ -54,6 +54,10 @@ extern keymap_config_t keymap_config;
|
|||
# include "joystick.h"
|
||||
#endif
|
||||
|
||||
#ifdef XAP_ENABLE
|
||||
# include "xap.h"
|
||||
#endif
|
||||
|
||||
/* ---------------------------------------------------------
|
||||
* Global interface variables and declarations
|
||||
* ---------------------------------------------------------
|
||||
|
@ -315,6 +319,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
|
||||
|
@ -352,6 +359,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
|
||||
|
@ -1117,6 +1132,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
|
||||
|
|
|
@ -335,6 +335,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)
|
||||
|
@ -567,6 +591,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
|
||||
|
@ -1161,6 +1235,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;
|
||||
|
@ -1218,6 +1300,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
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------*
|
||||
|
@ -638,6 +744,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)
|
||||
|
@ -837,6 +966,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
|
||||
|
@ -989,6 +1168,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;
|
||||
|
@ -1021,6 +1207,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…
Reference in a new issue