Compare commits

...
Sign in to create a new pull request.

302 commits
master ... xap

Author SHA1 Message Date
QMK Bot
a4c96e446c Merge remote-tracking branch 'origin/develop' into xap 2022-05-12 06:50:31 +00:00
QMK Bot
236ac7cbd1 Merge remote-tracking branch 'origin/develop' into xap 2022-05-12 05:52:40 +00:00
QMK Bot
ea1046948d Merge remote-tracking branch 'origin/develop' into xap 2022-05-12 05:29:01 +00:00
QMK Bot
cd0c15f887 Merge remote-tracking branch 'origin/develop' into xap 2022-05-12 04:50:49 +00:00
QMK Bot
6c4c64f147 Merge remote-tracking branch 'origin/develop' into xap 2022-05-12 04:49:10 +00:00
QMK Bot
f7e436b91c Merge remote-tracking branch 'origin/develop' into xap 2022-05-12 00:10:20 +00:00
QMK Bot
0be699dde6 Merge remote-tracking branch 'origin/develop' into xap 2022-05-11 23:55:48 +00:00
QMK Bot
bce04e6318 Merge remote-tracking branch 'origin/develop' into xap 2022-05-11 23:40:46 +00:00
QMK Bot
54ce775fff Merge remote-tracking branch 'origin/develop' into xap 2022-05-11 23:38:47 +00:00
QMK Bot
9c0937da42 Merge remote-tracking branch 'origin/develop' into xap 2022-05-11 23:10:22 +00:00
QMK Bot
0d1f9d1c8c Merge remote-tracking branch 'origin/develop' into xap 2022-05-11 22:40:20 +00:00
QMK Bot
4c49aaaa70 Merge remote-tracking branch 'origin/develop' into xap 2022-05-11 19:52:48 +00:00
QMK Bot
3ea9154b0a Merge remote-tracking branch 'origin/develop' into xap 2022-05-11 11:31:16 +00:00
QMK Bot
6b5a62e15f Merge remote-tracking branch 'origin/develop' into xap 2022-05-11 11:13:59 +00:00
QMK Bot
8ed1bec0a6 Merge remote-tracking branch 'origin/develop' into xap 2022-05-11 10:58:49 +00:00
QMK Bot
1b63938411 Merge remote-tracking branch 'origin/develop' into xap 2022-05-11 10:15:47 +00:00
zvecr
a8cbda7b8a lower threshold 2022-05-11 03:25:53 +01:00
zvecr
cb7d103ba8 wider keys? 2022-05-11 01:53:47 +01:00
zvecr
68208278e0 Render layers with 'qmk info' logic 2022-05-11 01:53:47 +01:00
QMK Bot
87106e44c8 Merge remote-tracking branch 'origin/develop' into xap 2022-05-10 18:08:45 +00:00
zvecr
3d9c2fd845 Fix duplicate keys 2022-05-10 18:23:54 +01:00
QMK Bot
f88ceb5164 Merge remote-tracking branch 'origin/develop' into xap 2022-05-10 16:02:02 +00:00
QMK Bot
690bca2672 Merge remote-tracking branch 'origin/develop' into xap 2022-05-10 12:27:00 +00:00
zvecr
f9f0d84eb0 Cache xap specs 2022-05-10 03:49:01 +01:00
QMK Bot
ea850f1380 Merge remote-tracking branch 'origin/develop' into xap 2022-05-10 01:29:47 +00:00
zvecr
5028d6672a Use keycodes for xap version 2022-05-10 02:29:30 +01:00
zvecr
41a5dcbfa7 Add more DD basic keycodes 2022-05-10 01:38:14 +01:00
zvecr
ea92d5ed7d Block out basic keycodes 2022-05-09 23:51:58 +01:00
QMK Bot
218bd48ebc Merge remote-tracking branch 'origin/develop' into xap 2022-05-09 01:06:27 +00:00
QMK Bot
87b099795c Merge remote-tracking branch 'origin/develop' into xap 2022-05-07 12:02:18 +00:00
QMK Bot
1a13c379ac Merge remote-tracking branch 'origin/develop' into xap 2022-05-07 00:02:30 +00:00
QMK Bot
3cef0d587a Merge remote-tracking branch 'origin/develop' into xap 2022-05-07 00:01:29 +00:00
QMK Bot
df9f15737b Merge remote-tracking branch 'origin/develop' into xap 2022-05-06 23:59:12 +00:00
QMK Bot
c81feb6248 Merge remote-tracking branch 'origin/develop' into xap 2022-05-06 23:48:57 +00:00
zvecr
7e819d7945 specs as json? 2022-05-06 23:33:51 +01:00
zvecr
58642ff40c Publish resolved XAP specs? 2022-05-06 23:11:16 +01:00
QMK Bot
a2604fd80f Merge remote-tracking branch 'origin/develop' into xap 2022-05-06 12:19:57 +00:00
QMK Bot
de0d6378bc Merge remote-tracking branch 'origin/develop' into xap 2022-05-06 06:16:45 +00:00
zvecr
94ec23ea77 Remove requirement to quote action args 2022-05-05 22:35:04 +01:00
zvecr
cc851142fa Add cli interactive shell 2022-05-05 22:16:38 +01:00
zvecr
c65ec90484 Fix a few mistakes in docs 2022-05-05 21:05:10 +01:00
zvecr
01cd1ac71f stash 2022-05-05 12:18:57 +01:00
zvecr
c01e8ed75d stash 2022-05-05 12:18:57 +01:00
QMK Bot
de45a60432 Merge remote-tracking branch 'origin/develop' into xap 2022-05-05 10:33:00 +00:00
QMK Bot
519ba9be0c Merge remote-tracking branch 'origin/develop' into xap 2022-05-05 08:09:47 +00:00
QMK Bot
72156de175 Merge remote-tracking branch 'origin/develop' into xap 2022-05-05 02:56:53 +00:00
QMK Bot
5a11b4593b Merge remote-tracking branch 'origin/develop' into xap 2022-05-04 15:12:06 +00:00
QMK Bot
c1185d3905 Merge remote-tracking branch 'origin/develop' into xap 2022-05-04 15:10:02 +00:00
QMK Bot
f54dcc7962 Merge remote-tracking branch 'origin/develop' into xap 2022-05-03 19:49:15 +00:00
QMK Bot
d1ec7f2e0c Merge remote-tracking branch 'origin/develop' into xap 2022-05-03 10:23:20 +00:00
QMK Bot
3cc8bcb936 Merge remote-tracking branch 'origin/develop' into xap 2022-05-02 19:36:23 +00:00
QMK Bot
4d9a611780 Merge remote-tracking branch 'origin/develop' into xap 2022-05-01 18:01:01 +00:00
QMK Bot
74880dd26e Merge remote-tracking branch 'origin/develop' into xap 2022-04-29 22:57:37 +00:00
QMK Bot
b5df55448b Merge remote-tracking branch 'origin/develop' into xap 2022-04-29 10:47:08 +00:00
QMK Bot
7e2ece2409 Merge remote-tracking branch 'origin/develop' into xap 2022-04-29 08:15:08 +00:00
QMK Bot
1a9f4eb665 Merge remote-tracking branch 'origin/develop' into xap 2022-04-29 07:08:06 +00:00
QMK Bot
05195af26f Merge remote-tracking branch 'origin/develop' into xap 2022-04-29 06:59:22 +00:00
QMK Bot
f833e34bd0 Merge remote-tracking branch 'origin/develop' into xap 2022-04-29 00:32:53 +00:00
QMK Bot
e7cae3949e Merge remote-tracking branch 'origin/develop' into xap 2022-04-29 00:24:20 +00:00
QMK Bot
496da4267e Merge remote-tracking branch 'origin/develop' into xap 2022-04-28 23:37:22 +00:00
QMK Bot
db9a12c048 Merge remote-tracking branch 'origin/develop' into xap 2022-04-28 18:17:53 +00:00
QMK Bot
5066e8c0d5 Merge remote-tracking branch 'origin/develop' into xap 2022-04-28 17:39:26 +00:00
QMK Bot
90f38c1b24 Merge remote-tracking branch 'origin/develop' into xap 2022-04-28 17:36:42 +00:00
QMK Bot
27549e534f Merge remote-tracking branch 'origin/develop' into xap 2022-04-27 23:12:51 +00:00
QMK Bot
01b831d3da Merge remote-tracking branch 'origin/develop' into xap 2022-04-26 14:12:05 +00:00
QMK Bot
de85113520 Merge remote-tracking branch 'origin/develop' into xap 2022-04-25 22:55:11 +00:00
QMK Bot
90420b1e49 Merge remote-tracking branch 'origin/develop' into xap 2022-04-25 09:31:39 +00:00
QMK Bot
c8ac96c768 Merge remote-tracking branch 'origin/develop' into xap 2022-04-25 05:09:18 +00:00
QMK Bot
8e1a690079 Merge remote-tracking branch 'origin/develop' into xap 2022-04-24 13:02:28 +00:00
QMK Bot
24d394a2aa Merge remote-tracking branch 'origin/develop' into xap 2022-04-24 02:02:06 +00:00
QMK Bot
2e387d2347 Merge remote-tracking branch 'origin/develop' into xap 2022-04-23 20:58:02 +00:00
QMK Bot
20aefbdb77 Merge remote-tracking branch 'origin/develop' into xap 2022-04-22 11:51:18 +00:00
QMK Bot
1bfd694fd8 Merge remote-tracking branch 'origin/develop' into xap 2022-04-22 08:20:11 +00:00
QMK Bot
2483a049aa Merge remote-tracking branch 'origin/develop' into xap 2022-04-22 08:17:45 +00:00
QMK Bot
5900caa877 Merge remote-tracking branch 'origin/develop' into xap 2022-04-22 08:14:05 +00:00
QMK Bot
da18279390 Merge remote-tracking branch 'origin/develop' into xap 2022-04-22 07:56:37 +00:00
QMK Bot
940c53f420 Merge remote-tracking branch 'origin/develop' into xap 2022-04-22 07:49:03 +00:00
QMK Bot
c6e38f46d9 Merge remote-tracking branch 'origin/develop' into xap 2022-04-22 07:42:57 +00:00
QMK Bot
aae9ffea0c Merge remote-tracking branch 'origin/develop' into xap 2022-04-22 07:40:31 +00:00
QMK Bot
6af289bdde Merge remote-tracking branch 'origin/develop' into xap 2022-04-22 07:38:36 +00:00
QMK Bot
3706574002 Merge remote-tracking branch 'origin/develop' into xap 2022-04-22 07:37:14 +00:00
QMK Bot
317e6a3f2a Merge remote-tracking branch 'origin/develop' into xap 2022-04-22 07:34:30 +00:00
QMK Bot
c1923a7c62 Merge remote-tracking branch 'origin/develop' into xap 2022-04-22 07:33:12 +00:00
QMK Bot
a07d10f5a9 Merge remote-tracking branch 'origin/develop' into xap 2022-04-22 07:22:21 +00:00
QMK Bot
3d92e04851 Merge remote-tracking branch 'origin/develop' into xap 2022-04-22 07:18:42 +00:00
QMK Bot
f550113f25 Merge remote-tracking branch 'origin/develop' into xap 2022-04-21 21:49:59 +00:00
QMK Bot
9f9f42848d Merge remote-tracking branch 'origin/develop' into xap 2022-04-21 21:33:36 +00:00
QMK Bot
85bf9c1aa9 Merge remote-tracking branch 'origin/develop' into xap 2022-04-21 21:04:16 +00:00
QMK Bot
e486dc9278 Merge remote-tracking branch 'origin/develop' into xap 2022-04-21 16:34:52 +00:00
QMK Bot
7d83445309 Merge remote-tracking branch 'origin/develop' into xap 2022-04-21 16:10:16 +00:00
zvecr
4d895892e5 Stubs for ENCODER_MAP 2022-04-20 22:38:06 +01:00
QMK Bot
1306d58774 Merge remote-tracking branch 'origin/develop' into xap 2022-04-20 17:11:54 +00:00
QMK Bot
a49a2e3f34 Merge remote-tracking branch 'origin/develop' into xap 2022-04-20 07:47:48 +00:00
QMK Bot
68c8e61c6a Merge remote-tracking branch 'origin/develop' into xap 2022-04-19 19:20:00 +00:00
QMK Bot
bcd9b23a65 Merge remote-tracking branch 'origin/develop' into xap 2022-04-19 19:16:03 +00:00
QMK Bot
f7f9a9affe Merge remote-tracking branch 'origin/develop' into xap 2022-04-19 19:01:18 +00:00
QMK Bot
e69de0637a Merge remote-tracking branch 'origin/develop' into xap 2022-04-19 17:59:14 +00:00
QMK Bot
620c4e6eb0 Merge remote-tracking branch 'origin/develop' into xap 2022-04-19 11:31:14 +00:00
QMK Bot
9ffd4505a0 Merge remote-tracking branch 'origin/develop' into xap 2022-04-19 11:28:45 +00:00
QMK Bot
d3a4adefc4 Merge remote-tracking branch 'origin/develop' into xap 2022-04-19 11:21:15 +00:00
QMK Bot
3a8ea8a683 Merge remote-tracking branch 'origin/develop' into xap 2022-04-19 11:19:04 +00:00
QMK Bot
6a4de6a962 Merge remote-tracking branch 'origin/develop' into xap 2022-04-19 11:06:27 +00:00
QMK Bot
34ff3ed1ba Merge remote-tracking branch 'origin/develop' into xap 2022-04-19 11:04:53 +00:00
QMK Bot
a0ebf624f1 Merge remote-tracking branch 'origin/develop' into xap 2022-04-19 11:02:55 +00:00
QMK Bot
0c153a4751 Merge remote-tracking branch 'origin/develop' into xap 2022-04-19 11:01:11 +00:00
QMK Bot
e1ccd6cbd2 Merge remote-tracking branch 'origin/develop' into xap 2022-04-19 10:56:53 +00:00
QMK Bot
a80d0036f3 Merge remote-tracking branch 'origin/develop' into xap 2022-04-19 10:54:18 +00:00
QMK Bot
e95ce2fab4 Merge remote-tracking branch 'origin/develop' into xap 2022-04-19 10:40:57 +00:00
QMK Bot
97b1d49015 Merge remote-tracking branch 'origin/develop' into xap 2022-04-19 10:35:21 +00:00
QMK Bot
b6f451194e Merge remote-tracking branch 'origin/develop' into xap 2022-04-19 10:33:23 +00:00
QMK Bot
bf9facddc5 Merge remote-tracking branch 'origin/develop' into xap 2022-04-19 10:32:20 +00:00
QMK Bot
9145458eb6 Merge remote-tracking branch 'origin/develop' into xap 2022-04-19 10:29:53 +00:00
QMK Bot
892f88e7d1 Merge remote-tracking branch 'origin/develop' into xap 2022-04-19 10:18:22 +00:00
QMK Bot
0df5dae0f6 Merge remote-tracking branch 'origin/develop' into xap 2022-04-19 10:07:11 +00:00
QMK Bot
5616513800 Merge remote-tracking branch 'origin/develop' into xap 2022-04-19 08:58:27 +00:00
QMK Bot
abec2e9052 Merge remote-tracking branch 'origin/develop' into xap 2022-04-19 02:34:31 +00:00
Nick Brassel
d17aed8e82 Verify struct sizing at build time. 2022-04-19 12:33:56 +10:00
zvecr
3730ddacac Fix ARM builds due to packing inconsistencies 2022-04-19 02:07:05 +01:00
zvecr
1d96fc866d Add route for hardware_id 2022-04-19 02:07:05 +01:00
QMK Bot
81cce42118 Merge remote-tracking branch 'origin/develop' into xap 2022-04-19 00:24:13 +00:00
QMK Bot
84c3879435 Merge remote-tracking branch 'origin/develop' into xap 2022-04-19 00:19:27 +00:00
QMK Bot
36bc8c0c1a Merge remote-tracking branch 'origin/develop' into xap 2022-04-18 17:02:38 +00:00
QMK Bot
e28727d8ba Merge remote-tracking branch 'origin/develop' into xap 2022-04-18 14:47:37 +00:00
QMK Bot
bdfe1470ab Merge remote-tracking branch 'origin/develop' into xap 2022-04-18 14:40:19 +00:00
QMK Bot
4cfcd51acb Merge remote-tracking branch 'origin/develop' into xap 2022-04-18 10:52:10 +00:00
QMK Bot
3d43baf3f2 Merge remote-tracking branch 'origin/develop' into xap 2022-04-18 09:30:56 +00:00
QMK Bot
9f78fb69e4 Merge remote-tracking branch 'origin/develop' into xap 2022-04-18 09:29:32 +00:00
QMK Bot
1f212894e1 Merge remote-tracking branch 'origin/develop' into xap 2022-04-18 09:16:07 +00:00
QMK Bot
5af8b47bb9 Merge remote-tracking branch 'origin/develop' into xap 2022-04-18 09:13:21 +00:00
QMK Bot
2e3fe5f1b2 Merge remote-tracking branch 'origin/develop' into xap 2022-04-18 09:12:12 +00:00
QMK Bot
f6b7c2ebee Merge remote-tracking branch 'origin/develop' into xap 2022-04-18 09:08:51 +00:00
QMK Bot
6f134870ab Merge remote-tracking branch 'origin/develop' into xap 2022-04-18 09:08:04 +00:00
QMK Bot
b4dc49601e Merge remote-tracking branch 'origin/develop' into xap 2022-04-18 07:38:38 +00:00
QMK Bot
a608afdcf4 Merge remote-tracking branch 'origin/develop' into xap 2022-04-17 19:55:02 +00:00
QMK Bot
b0fbcdd8a4 Merge remote-tracking branch 'origin/develop' into xap 2022-04-17 00:37:39 +00:00
QMK Bot
379dac6ade Merge remote-tracking branch 'origin/develop' into xap 2022-04-16 23:10:03 +00:00
zvecr
3c20f00238 Merge remote-tracking branch 'origin/develop' into xap 2022-04-16 23:49:41 +01:00
QMK Bot
c79bfc6ed9 Merge remote-tracking branch 'origin/develop' into xap 2022-04-16 17:41:49 +00:00
QMK Bot
78acf2c1a1 Merge remote-tracking branch 'origin/develop' into xap 2022-04-16 01:24:26 +00:00
QMK Bot
425b80ddd6 Merge remote-tracking branch 'origin/develop' into xap 2022-04-14 18:25:13 +00:00
QMK Bot
331b232ba1 Merge remote-tracking branch 'origin/develop' into xap 2022-04-14 17:02:35 +00:00
QMK Bot
37f92fad65 Merge remote-tracking branch 'origin/develop' into xap 2022-04-14 16:27:46 +00:00
QMK Bot
3f0c7f1921 Merge remote-tracking branch 'origin/develop' into xap 2022-04-14 05:43:34 +00:00
QMK Bot
ff198b384b Merge remote-tracking branch 'origin/develop' into xap 2022-04-14 03:28:33 +00:00
QMK Bot
fcb549f36c Merge remote-tracking branch 'origin/develop' into xap 2022-04-13 16:46:28 +00:00
QMK Bot
3503d1c27b Merge remote-tracking branch 'origin/develop' into xap 2022-04-13 16:28:29 +00:00
QMK Bot
f951028c94 Merge remote-tracking branch 'origin/develop' into xap 2022-04-13 14:32:05 +00:00
QMK Bot
7f4f38dcac Merge remote-tracking branch 'origin/develop' into xap 2022-04-13 14:31:23 +00:00
QMK Bot
c6511ad9bc Merge remote-tracking branch 'origin/develop' into xap 2022-04-13 14:26:13 +00:00
QMK Bot
6add5a8720 Merge remote-tracking branch 'origin/develop' into xap 2022-04-13 14:12:47 +00:00
Nick Brassel
85331d56ec Merge remote-tracking branch 'upstream/develop' into xap 2022-04-13 20:11:53 +10:00
QMK Bot
e7af9fb808 Merge remote-tracking branch 'origin/develop' into xap 2022-04-13 07:47:13 +00:00
QMK Bot
af26928941 Merge remote-tracking branch 'origin/develop' into xap 2022-04-13 07:05:28 +00:00
QMK Bot
858a55e5d8 Merge remote-tracking branch 'origin/develop' into xap 2022-04-13 07:04:18 +00:00
QMK Bot
3cbf1e455f Merge remote-tracking branch 'origin/develop' into xap 2022-04-13 06:41:30 +00:00
QMK Bot
7d023e4951 Merge remote-tracking branch 'origin/develop' into xap 2022-04-13 06:38:11 +00:00
QMK Bot
1d8ae78806 Merge remote-tracking branch 'origin/develop' into xap 2022-04-13 06:37:35 +00:00
QMK Bot
c1f92cc536 Merge remote-tracking branch 'origin/develop' into xap 2022-04-13 06:21:13 +00:00
QMK Bot
b3bce44385 Merge remote-tracking branch 'origin/develop' into xap 2022-04-13 06:05:39 +00:00
QMK Bot
cd8329ae9f Merge remote-tracking branch 'origin/develop' into xap 2022-04-13 06:04:53 +00:00
QMK Bot
1c13c5885c Merge remote-tracking branch 'origin/develop' into xap 2022-04-13 05:51:41 +00:00
QMK Bot
7107489242 Merge remote-tracking branch 'origin/develop' into xap 2022-04-13 05:47:14 +00:00
QMK Bot
e59b61e8af Merge remote-tracking branch 'origin/develop' into xap 2022-04-13 05:26:22 +00:00
zvecr
b0b6594ded secure keycodes? 2022-04-13 00:42:50 +01:00
zvecr
2563e7b2a0 format 2022-04-12 13:53:12 +01:00
zvecr
f249e33f70 Short term bodge to force dynamic_keymap population 2022-04-12 02:08:18 +01:00
zvecr
12e8c8d8ee Process entire unlock sequence - revert changes to SECURE_UNLOCK_SEQUENCE 2022-04-12 01:58:02 +01:00
zvecr
320f161c72 Process entire unlock sequence 2022-04-12 01:49:30 +01:00
zvecr
d19285019d All other subsystems are disabled during unlock 2022-04-12 01:37:31 +01:00
QMK Bot
b1eab6d3a0 Merge remote-tracking branch 'origin/develop' into xap 2022-04-11 13:12:57 +00:00
QMK Bot
d21f84c521 Merge remote-tracking branch 'origin/develop' into xap 2022-04-11 10:20:02 +00:00
QMK Bot
90429d9657 Merge remote-tracking branch 'origin/develop' into xap 2022-04-11 09:11:31 +00:00
zvecr
1ea2928d2a XAP messages extend timeout? 2022-04-11 02:14:19 +01:00
zvecr
ffb0575eb8 stub out secure data driven config 2022-04-11 01:59:48 +01:00
zvecr
05b5a4c23a format 2022-04-11 01:08:37 +01:00
zvecr
79db2df228 Allow secure to be slightly more optional 2022-04-11 01:07:22 +01:00
zvecr
3e4de1ebd0 format 2022-04-11 00:53:31 +01:00
zvecr
89fab427c4 stub out secure as its own feature 2022-04-11 00:43:18 +01:00
QMK Bot
2c068d08dd Merge remote-tracking branch 'origin/develop' into xap 2022-04-09 22:27:30 +00:00
QMK Bot
aefe98bfea Merge remote-tracking branch 'origin/develop' into xap 2022-04-09 18:50:25 +00:00
QMK Bot
5e33ca8f7c Merge remote-tracking branch 'origin/develop' into xap 2022-04-09 18:07:21 +00:00
Drashna Jael're
9ac3445454
Merge remote-tracking branch 'origin/develop' into xap 2022-04-08 20:34:54 -07:00
QMK Bot
66beedde80 Merge remote-tracking branch 'origin/develop' into xap 2022-04-06 14:46:51 +00:00
QMK Bot
8d67b9bcc5 Merge remote-tracking branch 'origin/develop' into xap 2022-04-06 10:14:08 +00:00
QMK Bot
9c4119bc15 Merge remote-tracking branch 'origin/develop' into xap 2022-04-06 10:07:51 +00:00
QMK Bot
02b7726323 Merge remote-tracking branch 'origin/develop' into xap 2022-04-06 00:23:37 +00:00
QMK Bot
98b668c675 Merge remote-tracking branch 'origin/develop' into xap 2022-04-06 00:09:12 +00:00
QMK Bot
2de248fc06 Merge remote-tracking branch 'origin/develop' into xap 2022-04-05 21:15:35 +00:00
QMK Bot
af5c3c948a Merge remote-tracking branch 'origin/develop' into xap 2022-04-05 19:14:54 +00:00
QMK Bot
3be420fd9b Merge remote-tracking branch 'origin/develop' into xap 2022-04-05 18:46:49 +00:00
zvecr
18aa0d2d19 Merge remote-tracking branch 'origin/develop' into xap 2022-04-05 19:37:58 +01:00
zvecr
194d6d65be format 2022-04-05 19:17:57 +01:00
QMK Bot
d339f182ba Merge remote-tracking branch 'origin/develop' into xap 2022-04-05 18:07:34 +00:00
zvecr
c1b57354f6 Stub out more of broadcast messages 2022-04-05 18:54:28 +01:00
QMK Bot
0d59f8b42d Merge remote-tracking branch 'origin/develop' into xap 2022-04-04 19:28:10 +00:00
QMK Bot
2abb01084f Merge remote-tracking branch 'origin/develop' into xap 2022-04-04 19:21:11 +00:00
QMK Bot
4de0f7d9b5 Merge remote-tracking branch 'origin/develop' into xap 2022-04-04 18:57:29 +00:00
QMK Bot
b87f85a81a Merge remote-tracking branch 'origin/develop' into xap 2022-04-03 21:02:17 +00:00
QMK Bot
f0daafb741 Merge remote-tracking branch 'origin/develop' into xap 2022-04-03 18:18:16 +00:00
QMK Bot
99d097de97 Merge remote-tracking branch 'origin/develop' into xap 2022-04-03 18:03:28 +00:00
QMK Bot
e3afa07ca9 Merge remote-tracking branch 'origin/develop' into xap 2022-04-03 17:45:49 +00:00
QMK Bot
df823f8306 Merge remote-tracking branch 'origin/develop' into xap 2022-04-03 17:23:30 +00:00
QMK Bot
567949de19 Merge remote-tracking branch 'origin/develop' into xap 2022-04-03 12:19:29 +00:00
QMK Bot
6a026f8653 Merge remote-tracking branch 'origin/develop' into xap 2022-04-02 21:29:19 +00:00
QMK Bot
72ea4cf574 Merge remote-tracking branch 'origin/develop' into xap 2022-04-02 21:10:21 +00:00
QMK Bot
715da89a6e Merge remote-tracking branch 'origin/develop' into xap 2022-04-02 14:54:56 +00:00
zvecr
c9eae1d384 format 2022-04-02 00:11:22 +01:00
zvecr
e7d9d6675c Implement codegen for more data types - codegen for return_execute stubs removed 2022-04-01 23:38:13 +01:00
QMK Bot
d968e6c005 Merge remote-tracking branch 'origin/develop' into xap 2022-04-01 09:27:14 +00:00
zvecr
c5842ab9b5 stub out return_execute with zero args 2022-04-01 00:44:27 +01:00
zvecr
e111b9d017 Use slightly more unique data name 2022-03-31 23:31:01 +01:00
zvecr
2c8c9c9928 And sort out docs gen too 2022-03-31 23:16:56 +01:00
zvecr
53052228df Add types codegen 2022-03-31 22:34:25 +01:00
Nick Brassel
ffcdfc6c03 Swap info.json.gz length to #define. 2022-04-01 08:15:33 +11:00
zvecr
81a53ac5b6 gen RESPONSE_FLAG defines 2022-03-31 21:08:18 +01:00
zvecr
0f5ced0521 claim back a few bytes 2022-03-31 19:14:35 +01:00
zvecr
fe1a4a52d4 clang 2022-03-31 01:23:19 +01:00
zvecr
646fdc7d17 Rework code gen for return of dynamic variables 2022-03-31 01:18:26 +01:00
zvecr
73d2228524 format 2022-03-30 23:25:33 +01:00
zvecr
6269c6b51c partial gen for return_execute 2022-03-30 23:20:14 +01:00
QMK Bot
5a099e1ad7 Merge remote-tracking branch 'origin/develop' into xap 2022-03-30 20:55:54 +00:00
QMK Bot
43d6473e00 Merge remote-tracking branch 'origin/develop' into xap 2022-03-30 19:01:01 +00:00
zvecr
51e09235a2 clang 2022-03-30 02:28:03 +01:00
zvecr
7f128c5286 Remove some assumptions on packet format 2022-03-30 02:09:19 +01:00
zvecr
22b8299230 Fix up print_dotted_output dict handling 2022-03-30 00:43:41 +01:00
zvecr
a65ea1a711 Fix codegen for non led boards 2022-03-30 00:43:41 +01:00
zvecr
13ee88dd21 Data driven g_led config 2022-03-30 00:43:41 +01:00
QMK Bot
578fe2d029 Merge remote-tracking branch 'origin/develop' into xap 2022-03-29 19:19:54 +00:00
zvecr
56c9f7b7ff format 2022-03-29 19:25:16 +01:00
zvecr
05911e9908 bodge 'qmk xap -l' for windows 2022-03-29 18:36:08 +01:00
zvecr
7262333857 Use generic 'dump_lines' 2022-03-28 21:18:17 +01:00
zvecr
ff1bb76537 basic info.json handling 2022-03-28 21:06:16 +01:00
QMK Bot
1fbbd72c14 Merge remote-tracking branch 'origin/develop' into xap 2022-03-28 16:47:50 +00:00
QMK Bot
780526848b Merge remote-tracking branch 'origin/develop' into xap 2022-03-27 22:07:57 +00:00
QMK Bot
fc45ff1f07 Merge remote-tracking branch 'origin/develop' into xap 2022-03-27 20:29:43 +00:00
QMK Bot
933888780e Merge remote-tracking branch 'origin/develop' into xap 2022-03-27 02:04:07 +00:00
Drashna Jael're
80102c0e83
Merge remote-tracking branch 'origin/develop' into xap 2022-03-26 13:37:01 -07:00
Drashna Jael're
15e0964108
Merge remote-tracking branch 'origin/develop' into xap 2022-03-26 10:32:27 -07:00
QMK Bot
620716b106 Merge remote-tracking branch 'origin/develop' into xap 2022-03-24 18:09:08 +00:00
QMK Bot
cabdab5e75 Merge remote-tracking branch 'origin/develop' into xap 2022-03-24 17:17:16 +00:00
QMK Bot
9c4e294887 Merge remote-tracking branch 'origin/develop' into xap 2022-03-24 05:24:44 +00:00
QMK Bot
d6f2f02428 Merge remote-tracking branch 'origin/develop' into xap 2022-03-23 18:44:29 +00:00
QMK Bot
6a9f0961d6 Merge remote-tracking branch 'origin/develop' into xap 2022-03-23 16:49:55 +00:00
QMK Bot
c325737d9c Merge remote-tracking branch 'origin/develop' into xap 2022-03-23 06:03:02 +00:00
QMK Bot
3d4575a06d Merge remote-tracking branch 'origin/develop' into xap 2022-03-23 05:07:29 +00:00
QMK Bot
4d28978dee Merge remote-tracking branch 'origin/develop' into xap 2022-03-22 16:40:19 +00:00
QMK Bot
728305e961 Merge remote-tracking branch 'origin/develop' into xap 2022-03-22 10:48:49 +00:00
zvecr
e31c605bf7 revert split logic 2022-03-22 00:04:12 +00:00
zvecr
f872fbea7e Merge remote-tracking branch 'origin/develop' into xap 2022-03-21 12:46:36 +00:00
QMK Bot
6281446aee Merge remote-tracking branch 'origin/develop' into xap 2022-03-21 08:06:45 +00:00
QMK Bot
0dba850d43 Merge remote-tracking branch 'origin/develop' into xap 2022-03-20 17:25:13 +00:00
QMK Bot
9ad318efc6 Merge remote-tracking branch 'origin/develop' into xap 2022-03-20 17:06:57 +00:00
QMK Bot
c5e26a8040 Merge remote-tracking branch 'origin/develop' into xap 2022-03-20 17:03:48 +00:00
QMK Bot
9ed471fd33 Merge remote-tracking branch 'origin/develop' into xap 2022-03-20 04:20:44 +00:00
zvecr
b365cbce15 Merge in keymap level to XAP info.json payload 2022-03-20 01:25:04 +00:00
QMK Bot
29f349b90b Merge remote-tracking branch 'origin/develop' into xap 2022-03-19 20:59:36 +00:00
QMK Bot
ded6a379b2 Merge remote-tracking branch 'origin/develop' into xap 2022-03-19 17:42:45 +00:00
QMK Bot
5df35467b4 Merge remote-tracking branch 'origin/develop' into xap 2022-03-19 16:38:46 +00:00
QMK Bot
8dfe1134cf Merge remote-tracking branch 'origin/develop' into xap 2022-03-19 11:53:39 +00:00
QMK Bot
dd2c2bfa97 Merge remote-tracking branch 'origin/develop' into xap 2022-03-19 11:52:33 +00:00
QMK Bot
3cb3d5b0c9 Merge remote-tracking branch 'origin/develop' into xap 2022-03-19 06:57:50 +00:00
QMK Bot
b73f5d3da6 Merge remote-tracking branch 'origin/develop' into xap 2022-03-19 06:53:44 +00:00
zvecr
70c9905cb6 clang 2022-03-18 21:43:01 +00:00
zvecr
a5204887a8 maybe vusb 2022-03-18 21:37:23 +00:00
zvecr
c27edf4e64 vusb prep 2022-03-18 20:23:28 +00:00
zvecr
aaf4fcbe5a Merge chibios support 2022-03-18 19:51:20 +00:00
QMK Bot
b73a0b14aa Merge remote-tracking branch 'origin/develop' into xap 2022-03-18 18:50:26 +00:00
QMK Bot
aff373bab2 Merge remote-tracking branch 'origin/develop' into xap 2022-03-18 17:25:53 +00:00
zvecr
4d4b013e5b Fixup after merge 2022-03-18 17:03:54 +00:00
zvecr
5bb6173cc7 Fixup after merge 2022-03-18 17:01:02 +00:00
zvecr
e5e1e54f39 Merge remote-tracking branch 'origin/develop' into xap 2022-03-18 16:57:34 +00:00
zvecr
72602a3443 Fixup after merge 2022-03-18 01:53:39 +00:00
zvecr
2e8db66201 Merge remote-tracking branch 'origin/develop' into xap 2022-03-18 01:20:16 +00:00
QMK Bot
46256e08eb Merge remote-tracking branch 'origin/develop' into xap 2022-03-17 21:20:11 +00:00
QMK Bot
770c7ab20e Merge remote-tracking branch 'origin/develop' into xap 2022-03-16 23:46:07 +00:00
zvecr
31c4864705 Crude CLI device discovery 2022-03-16 19:45:42 +00:00
QMK Bot
68efb1635c Merge remote-tracking branch 'origin/develop' into xap 2022-03-16 15:35:27 +00:00
zvecr
9fd4db1fc7 fix up for pytest - remove fstring escaping 2022-03-16 10:58:21 +00:00
QMK Bot
5b2db70d65 Merge remote-tracking branch 'origin/develop' into xap 2022-03-16 04:10:00 +00:00
QMK Bot
7301950bcf Merge remote-tracking branch 'origin/develop' into xap 2022-03-16 03:58:52 +00:00
QMK Bot
d5b6b82361 Merge remote-tracking branch 'origin/develop' into xap 2022-03-16 03:15:37 +00:00
QMK Bot
1bb00c1609 Merge remote-tracking branch 'origin/develop' into xap 2022-03-16 03:14:32 +00:00
zvecr
52d3b9dcc5 fix up for pytest 2022-03-16 00:10:40 +00:00
QMK Bot
cddcd8d735 Merge remote-tracking branch 'origin/develop' into xap 2022-03-16 00:07:59 +00:00
zvecr
7e8f0d49ea Temp bodge for unit tests? 2022-03-15 23:57:59 +00:00
QMK Bot
7183516a2b Merge remote-tracking branch 'origin/develop' into xap 2022-03-15 23:28:59 +00:00
zvecr
2b4724bd83 Convert info_json_gz.h generation to CLI 2022-03-15 17:59:12 +00:00
zvecr
b96b862ef9 Merge remote-tracking branch 'origin/develop' into xap 2022-03-15 13:27:35 +00:00
Nick Brassel
c3ac89d1c9 qmk format-c, qmk format-python 2022-03-09 20:01:20 +11:00
Nick Brassel
575d8c19fc Merge remote-tracking branch 'upstream/develop' into xap 2022-03-09 19:47:31 +11:00
Nick Brassel
6c7afbb859 Migrate XAP docs generator into CLI now that most logic is in Jinja2 files. 2022-02-16 10:53:35 +11:00
Nick Brassel
69e9c80ec3 Reworked docs rendering using jinja2. 2022-02-15 05:19:13 +11:00
Nick Brassel
1e723e6647 Output logging. 2022-02-14 09:46:17 +11:00
Nick Brassel
c9ec8a1309 Merge remote-tracking branch 'upstream/develop' into xap 2022-02-14 09:01:22 +11:00
Nick Brassel
dcf4bf6d29 Merge remote-tracking branch 'upstream/develop' into xap 2022-02-03 03:15:01 +11:00
Nick Brassel
bf66b91433 Merge remote-tracking branch 'upstream/develop' into xap 2021-11-28 12:56:46 +11:00
Nick Brassel
ec9a78cc4a Doco update. 2021-09-16 11:43:19 +10:00
Nick Brassel
942d9f6a09 Merge remote-tracking branch 'upstream/develop' into xap 2021-09-16 07:46:55 +10:00
Nick Brassel
3c66b9b0ec Merge remote-tracking branch 'upstream/develop' into xap 2021-09-15 11:40:29 +10:00
Nick Brassel
5aae5a767f Use the correct input type. 2021-09-15 08:49:51 +10:00
Nick Brassel
437559cd03 Swap to fnvhash due to deps. 2021-09-15 08:45:14 +10:00
Nick Brassel
eba91c6e28 Initial implementation of XAP protocol. 2021-08-11 21:08:32 +10:00
55 changed files with 5177 additions and 41 deletions

View file

@ -322,12 +322,18 @@ ifneq ("$(wildcard $(KEYBOARD_PATH_5)/info.json)","")
endif
CONFIG_H += $(KEYBOARD_OUTPUT)/src/info_config.h $(KEYBOARD_OUTPUT)/src/layouts.h
KEYBOARD_SRC += $(KEYBOARD_OUTPUT)/src/default_keyboard.c
$(KEYBOARD_OUTPUT)/src/info_config.h: $(INFO_JSON_FILES)
@$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD)
$(eval CMD=$(QMK_BIN) generate-config-h --quiet --keyboard $(KEYBOARD) --output $(KEYBOARD_OUTPUT)/src/info_config.h)
@$(BUILD_CMD)
$(KEYBOARD_OUTPUT)/src/default_keyboard.c: $(INFO_JSON_FILES)
@$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD)
$(eval CMD=$(QMK_BIN) generate-keyboard-c --quiet --keyboard $(KEYBOARD) --output $(KEYBOARD_OUTPUT)/src/default_keyboard.c)
@$(BUILD_CMD)
$(KEYBOARD_OUTPUT)/src/default_keyboard.h: $(INFO_JSON_FILES)
@$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD)
$(eval CMD=$(QMK_BIN) generate-keyboard-h --quiet --keyboard $(KEYBOARD) --output $(KEYBOARD_OUTPUT)/src/default_keyboard.h)
@ -338,7 +344,7 @@ $(KEYBOARD_OUTPUT)/src/layouts.h: $(INFO_JSON_FILES)
$(eval CMD=$(QMK_BIN) generate-layouts --quiet --keyboard $(KEYBOARD) --output $(KEYBOARD_OUTPUT)/src/layouts.h)
@$(BUILD_CMD)
generated-files: $(KEYBOARD_OUTPUT)/src/info_config.h $(KEYBOARD_OUTPUT)/src/default_keyboard.h $(KEYBOARD_OUTPUT)/src/layouts.h
generated-files: $(KEYBOARD_OUTPUT)/src/info_config.h $(KEYBOARD_OUTPUT)/src/default_keyboard.c $(KEYBOARD_OUTPUT)/src/default_keyboard.h $(KEYBOARD_OUTPUT)/src/layouts.h
.INTERMEDIATE : generated-files

View file

@ -45,6 +45,10 @@ else ifeq ($(strip $(DEBUG_MATRIX_SCAN_RATE_ENABLE)), api)
OPT_DEFS += -DDEBUG_MATRIX_SCAN_RATE
endif
ifeq ($(strip $(XAP_ENABLE)), yes)
include $(BUILDDEFS_PATH)/xap.mk
endif
AUDIO_ENABLE ?= no
ifeq ($(strip $(AUDIO_ENABLE)), yes)
ifeq ($(PLATFORM),CHIBIOS)
@ -797,6 +801,19 @@ ifeq ($(strip $(USBPD_ENABLE)), yes)
endif
endif
ifeq ($(strip $(XAP_ENABLE)), yes)
ifeq ($(strip $(VIA_ENABLE)), yes)
$(error 'XAP_ENABLE = $(XAP_ENABLE)' deprecates 'VIA_ENABLE = $(VIA_ENABLE)'. Please set 'VIA_ENABLE = no')
endif
OPT_DEFS += -DXAP_ENABLE
DYNAMIC_KEYMAP_ENABLE := yes
SECURE_ENABLE := yes
EMBED_INFO_JSON := yes
VPATH += $(QUANTUM_DIR)/xap
SRC += $(QUANTUM_DIR)/xap/xap.c $(QUANTUM_DIR)/xap/xap_handlers.c
endif
BLUETOOTH_ENABLE ?= no
VALID_BLUETOOTH_DRIVER_TYPES := BluefruitLE RN42 custom
ifeq ($(strip $(BLUETOOTH_ENABLE)), yes)

24
builddefs/xap.mk Normal file
View file

@ -0,0 +1,24 @@
# Copyright 2022 Nick Brassel (@tzarc)
# SPDX-License-Identifier: GPL-2.0-or-later
# XAP embedded info.json
$(KEYMAP_OUTPUT)/src/info_json_gz.h: $(INFO_JSON_FILES)
@$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD)
$(eval CMD=$(QMK_BIN) xap-generate-info-h -o "$(KEYMAP_OUTPUT)/src/info_json_gz.h" -kb $(KEYBOARD) -km $(KEYMAP))
@$(BUILD_CMD)
XAP_FILES := $(shell ls -1 data/xap/* | sort | xargs echo)
$(KEYMAP_OUTPUT)/src/xap_generated.inl: $(XAP_FILES)
@$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD)
$(eval CMD=$(QMK_BIN) xap-generate-qmk-inc -o "$(KEYMAP_OUTPUT)/src/xap_generated.inl")
@$(BUILD_CMD)
$(KEYMAP_OUTPUT)/src/xap_generated.h: $(XAP_FILES)
@$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD)
$(eval CMD=$(QMK_BIN) xap-generate-qmk-h -o "$(KEYMAP_OUTPUT)/src/xap_generated.h" -kb $(KEYBOARD))
@$(BUILD_CMD)
generated-files: $(KEYMAP_OUTPUT)/src/info_json_gz.h $(KEYMAP_OUTPUT)/src/xap_generated.inl $(KEYMAP_OUTPUT)/src/xap_generated.h
VPATH += $(KEYMAP_OUTPUT)/src

File diff suppressed because it is too large Load diff

View file

@ -202,6 +202,62 @@
"timeout": {"$ref": "qmk.definitions.v1#/unsigned_int"}
}
},
"led_matrix": {
"type": "object",
"properties": {
"driver": {"type": "string"},
"layout": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"matrix": {
"type": "array",
"minItems": 2,
"maxItems": 2,
"items": {
"type": "number",
"min": 0,
"multipleOf": 1
}
},
"x": {"$ref": "qmk.definitions.v1#/key_unit"},
"y": {"$ref": "qmk.definitions.v1#/key_unit"},
"flags": {"$ref": "qmk.definitions.v1#/unsigned_decimal"}
}
}
}
}
},
"rgb_matrix": {
"type": "object",
"properties": {
"driver": {"type": "string"},
"layout": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"matrix": {
"type": "array",
"minItems": 2,
"maxItems": 2,
"items": {
"type": "number",
"min": 0,
"multipleOf": 1
}
},
"x": {"$ref": "qmk.definitions.v1#/key_unit"},
"y": {"$ref": "qmk.definitions.v1#/key_unit"},
"flags": {"$ref": "qmk.definitions.v1#/unsigned_decimal"}
}
}
}
}
},
"rgblight": {
"type": "object",
"additionalProperties": false,

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

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

View file

@ -0,0 +1,8 @@
| Name | Definition |
| -- | -- |
{%- for type, definition in xap.term_definitions | dictsort %}
| _{{ type }}_ | {{ definition }} |
{%- endfor %}
{%- for type, definition in xap.type_definitions | dictsort %}
| _{{ definition.name }}_ | {{ definition.description }}{% if 'struct' == definition.type %} Takes the format:{% for item in definition.struct_members %}<br>`{{ item.type }}` - {{ item.name }}{%- endfor %}{% endif %} |
{%- endfor %}

View file

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

224
data/xap/xap_0.0.1.hjson Executable file
View file

@ -0,0 +1,224 @@
{
version: 0.0.1
// Needed for table generation
define: XAP_ROUTE
// Documentation section is used purely for `qmk xap-generate-docs`.
documentation: {
order: [
page_header
type_docs
!type_docs.md.j2
term_definitions
!term_definitions.md.j2
request_response
reserved_tokens
response_flags
!response_flags.md.j2
example_conversation
]
page_header:
'''
# QMK Firmware XAP Specs
This document describes the requirements of the QMK XAP ("extensible application protocol") API.
'''
type_docs:
'''
## Types
**All integral types are little-endian.**
'''
term_definitions:
'''
## Definitions
This list defines the terms used across the entire set of XAP protocol documentation.
'''
request_response:
'''
## Requests and Responses
Communication generally follows a request/response pattern.
Each request needs to include a _token_ -- this `u16` value prefixes each outbound request from the host application and its corresponding response, allowing response messages to be correlated with their request, even if multiple host applications are communicating with the firmware simultaneously. Host applications should randomly generate a token ID for **every** outbound request, unless using a reserved token defined below.
This token is followed by a `u8` signifying the length of data in the request.
'''
// This documentation section reserved for next version
reserved_tokens: ''
response_flags:
'''
Response messages will always be prefixed by the originating request _token_, directly followed by that request's _response flags_, then the response payload length:
'''
example_conversation:
'''
### Example "conversation":
**Request** -- version query:
| Byte | 0 | 1 | 2 | 3 | 4 |
| --- | --- | --- | --- | --- | --- |
| **Purpose** | Token | Token | Payload Length | Route | Route |
| **Value** | `0x43` | `0x2B` | `0x02` | `0x00` | `0x00` |
**Response** -- matching token, successful flag, payload of `0x03170192` = 3.17.192:
| Byte | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| **Purpose** | Token | Token | Response Flags | Payload Length | Payload | Payload | Payload | Payload |
| **Value** | `0x43` | `0x2B` | `0x01` | `0x04` | `0x92` | `0x01` | `0x17` | `0x03` |
'''
}
type_docs: {
u8:
'''
An unsigned 8-bit integral (octet, or byte), commonly seen as `uint8_t` from _stdint.h_.
'''
u16:
'''
An unsigned 16-bit integral, commonly seen as `uint16_t` from _stdint.h_.
'''
u32:
'''
An unsigned 32-bit integral, commonly seen as `uint32_t` from _stdint.h_.
'''
"type[n]":
'''
An array of `type`, with array extent of `N` -- e.g. `u8[2]` signifies two consecutive octets.
'''
}
term_definitions: {
Subsystem:
'''
A high-level area of functionality within XAP.
'''
Route:
'''
A sequence of _IDs_ describing the route to invoke a _handler_.
'''
Handler:
'''
A piece of code that is executed when a specific _route_ is received.
'''
Response:
'''
The data sent back to the host during execution of a _handler_.
'''
Payload:
'''
Any received data appended to the _route_, which gets delivered to the _handler_ when received.
'''
}
type_definitions: {
identifier: {
name: ID
description: A single octet / 8-bit byte, representing Subsystem or Route index.
type: u8
}
response_flags: {
name: Response Flags
description: An `u8` containing the status of the request.
type: u8
}
token: {
name: Token
description: A `u16` associated with a specific request as well as its corresponding response.
type: u16
}
request_header: {
name: Request Header
description: Packet format for inbound data.
type: struct
struct_length: 3
struct_members: [
{
type: token
name: token
},
{
type: u8
name: length
}
]
}
response_header: {
name: Response Header
description: Packet format for outbound data.
type: struct
struct_length: 4
struct_members: [
{
type: token
name: token
},
{
type: response_flags
name: flags
},
{
type: u8
name: length
}
]
}
}
response_flags: {
define_prefix: XAP_RESPONSE_FLAG
bits: {
0: {
name: Success
define: SUCCESS
description:
'''
When this bit is set, the request was successfully handled. If not set, all payload data should be disregarded, and the request retried if appropriate (with a new token).
'''
}
}
}
routes: {
0x00: {
type: router
name: XAP
define: XAP
description:
'''
This subsystem is always present, and provides the ability to query information about the XAP protocol of the connected device.
'''
routes: {
0x00: {
type: command
name: Version Query
define: VERSION_QUERY
description:
'''
XAP protocol version query.
* Returns the BCD-encoded version in the format of XX.YY.ZZZZ => `0xXXYYZZZZ`
* e.g. 3.2.115 will match `0x03020115`, or bytes {0x15,0x01,0x02,0x03}.
* Response:
* `u32` value.
'''
return_type: u32
return_purpose: bcd-version
return_constant: XAP_BCD_VERSION
}
}
}
}
}

361
data/xap/xap_0.1.0.hjson Executable file
View file

@ -0,0 +1,361 @@
{
version: 0.1.0
uses: {
keycodes: 0.0.1
}
documentation: {
order: [
broadcast_messages
]
reserved_tokens:
'''
Two token values are reserved: `0x0000` and `0xFFFF`:
* `0x0000`: A message sent by a host application may use this token if no response is to be sent -- a "fire and forget" message.
* `0xFFFF`: Signifies a "broadcast" message sent by the firmware without prompting from the host application. Broadcast messages are defined later in this document.
Any request will generate at least one corresponding response, with the exception of messages using reserved tokens. Maximum total message length is 128 bytes due to RAM constraints.
'''
broadcast_messages:
'''
## Broadcast messages
Broadcast messages may be sent by the firmware to the host, without a corresponding inbound request. Each broadcast message uses the token `0xFFFF`, and does not expect a response from the host. Tokens are followed by an _ID_ signifying the type of broadcast, with corresponding _payload_.
'''
}
response_flags: {
bits: {
1: {
name: Secure Failure
define: SECURE_FAILURE
description:
'''
When this bit is set, the requested _route_ was marked _secure_ but an _unlock sequence_ has not completed.
'''
}
6: {
name: Unlocking
define: UNLOCK_IN_PROGRESS
description:
'''
When this bit is set, an _unlock sequence_ is in progress.
'''
}
7: {
name: Unlocked
define: UNLOCKED
description:
'''
When this bit is set, an _unlock sequence_ has completed, and _secure routes_ may be invoked.
'''
}
}
}
type_docs: {
u64:
'''
An unsigned 64-bit integral, commonly seen as `uint64_t` from _stdint.h_.
'''
"struct{}":
'''
A structure of data, packing different objects together. Data is "compacted" -- there are no padding bytes between fields. Equivalent to a packed C-style `struct`. The order in which they're defined matches the order of the data in the response packet.
'''
}
term_definitions: {
Capability:
'''
A way to determine if certain functionality is enabled in the firmware. Any _subsystem_ that provides build-time restriction of functionality must provide a _route_ for a _capabilities query_.
'''
"Secure Route":
'''
A _route_ which has potentially destructive consequences, necessitating prior approval by the user before executing.
'''
"Unlock sequence":
'''
A physical sequence initiated by the user to enable execution of _secure routes_.
'''
}
type_definitions: {
broadcast_header: {
name: Broadcast Header
description: Packet format for broadcast messages.
type: struct
struct_length: 4
struct_members: [
{
type: token
name: token
},
{
type: u8
name: type
},
{
type: u8
name: length
}
]
}
}
broadcast_messages: {
define_prefix: XAP_BROADCAST
messages: {
0x00: {
name: Log message
define: LOG_MESSAGE
description:
'''
Replicates and replaces the same functionality as if using the standard QMK `CONSOLE_ENABLE = yes` in `rules.mk`. Normal prints within the firmware will manifest as log messages broadcast to the host. `hid_listen` will not be functional with XAP enabled.
Log message payloads include a `u8` signifying the length of the text, followed by the `u8[Length]` containing the text itself.
**Example Log Broadcast** -- log message "Hello QMK!"
| Byte | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| **Purpose** | Token | Token | Broadcast Type | Length | Payload | Payload | Payload | Payload | Payload | Payload | Payload | Payload | Payload | Payload |
| **Value** | `0xFF` | `0xFF` | `0x00` | `0x0A`(10) | `0x48`(H) | `0x65`(e) | `0x6C`(l) | `0x6C`(l) | `0x6F`(o) | `0x20`(&nbsp;) | `0x51`(Q) | `0x4D`(M) | `0x4B`(K) | `0x21`(!) |
'''
}
0x01: {
name: Secure Status
define: SECURE_STATUS
description:
'''
Secure status has changed.
'''
return_type: u8
}
}
}
routes: {
0x00: {
routes: {
0x01: {
type: command
name: Capabilities Query
define: CAPABILITIES_QUERY
description:
'''
XAP subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.
'''
return_type: u32
return_purpose: capabilities
return_constant: XAP_ROUTE_XAP_CAPABILITIES
}
0x02: {
type: command
name: Enabled subsystem query
define: SUBSYSTEM_QUERY
description:
'''
XAP protocol subsystem query. Each bit should be considered as a "usable" subsystem. For example, checking `(value & (1 << XAP_ROUTE_QMK) != 0)` means the QMK subsystem is enabled and available for querying.
'''
return_type: u32
return_purpose: capabilities
return_constant: XAP_ROUTE_CAPABILITIES
}
0x03: {
type: command
name: Secure Status
define: SECURE_STATUS
description:
'''
Query secure route status
* 0 means secure routes are disabled
* 1 means unlock sequence initiated but incomplete
* 2 means secure routes are allowed
* any other value should be interpreted as disabled
'''
permissions: ignore
return_type: u8
return_execute: secure_status
}
0x04: {
type: command
name: Secure Unlock
define: SECURE_UNLOCK
description: Initiate secure route unlock sequence
return_execute: secure_unlock
}
0x05: {
type: command
name: Secure Lock
define: SECURE_LOCK
permissions: ignore
description: Disable secure routes
return_execute: secure_lock
}
}
},
0x01: {
type: router
name: QMK
define: QMK
description:
'''
This subsystem is always present, and provides the ability to address QMK-specific functionality.
'''
routes: {
0x00: {
type: command
name: Version Query
define: VERSION_QUERY
description:
'''
QMK protocol version query.
* Returns the BCD-encoded version in the format of XX.YY.ZZZZ => `0xXXYYZZZZ`
* e.g. 3.2.115 will match `0x03020115`, or bytes {0x15,0x01,0x02,0x03}.
* Response:
* `u32` value.
'''
return_type: u32
return_purpose: bcd-version
return_constant: QMK_BCD_VERSION
}
0x01: {
type: command
name: Capabilities Query
define: CAPABILITIES_QUERY
description:
'''
QMK subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.
'''
return_type: u32
return_purpose: capabilities
return_constant: XAP_ROUTE_QMK_CAPABILITIES
}
0x02: {
type: command
name: Board identifiers
define: BOARD_IDENTIFIERS
description:
'''
Retrieves the set of identifying information for the board.
'''
return_type: struct
return_struct_length: 10
return_struct_members: [
{
type: u16
name: Vendor ID
},
{
type: u16
name: Product ID
},
{
type: u16
name: Product Version
},
{
type: u32
name: QMK Unique Identifier
}
]
return_constant: [
VENDOR_ID
PRODUCT_ID
DEVICE_VER
XAP_KEYBOARD_IDENTIFIER
]
}
0x03: {
type: command
name: Board Manufacturer
define: BOARD_MANUFACTURER
description: Retrieves the name of the manufacturer
return_type: string
return_constant: QSTR(MANUFACTURER)
}
0x04: {
type: command
name: Product Name
define: PRODUCT_NAME
description: Retrieves the product name
return_type: string
return_constant: QSTR(PRODUCT)
}
0x05: {
type: command
name: info.json length
define: INFO_LEN_QUERY
description: Retrieves the length of info.json
return_type: u32
return_constant: INFO_JSON_GZ_LEN
}
0x06: {
type: command
name: info.json
define: INFO_QUERY
description: Retrieves a chunk of info.json
request_type: u16
request_purpose: offset
return_type: u8[32]
return_execute: get_info_json_chunk
}
0x07: {
type: command
name: Jump to bootloader
define: BOOTLOADER_JUMP
permissions: secure
enable_if_preprocessor: defined(BOOTLOADER_JUMP_SUPPORTED)
description:
'''
Jump to bootloader
May not be present if QMK capabilities query returns “true”, then jump to bootloader is supported
* 0 means secure routes are disabled, and should be considered as a failure
* 1 means successful, board will jump to bootloader
'''
return_type: u8
return_execute: request_bootloader_jump
}
0x08: {
type: command
name: info.json
define: HARDWARE_ID
description: Retrieves a unique identifier for the board.
return_type: u32[4]
return_execute: get_hardware_id
}
}
},
0x02: {
type: router
name: Keyboard
define: KB
description:
'''
This subsystem is always present, and reserved for user-specific functionality. No routes are defined by XAP.
'''
routes: {
}
},
0x03: {
type: router
name: User
define: USER
description:
'''
This subsystem is always present, and reserved for user-specific functionality. No routes are defined by XAP.
'''
routes: {
}
}
}
}

190
data/xap/xap_0.2.0.hjson Executable file
View file

@ -0,0 +1,190 @@
{
version: 0.2.0
routes: {
0x04: {
type: router
name: Dynamic Keymap
define: DYNAMIC_KEYMAP
description:
'''
This subsystem allows for live modifications of the keymap, allowing keys to be reassigned without rebuilding the firmware.
'''
enable_if_preprocessor: defined(DYNAMIC_KEYMAP_ENABLE)
routes: {
0x00: {
type: command
name: Capabilities Query
define: CAPABILITIES_QUERY
description:
'''
Dynamic Keymap subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.
'''
return_type: u32
return_purpose: capabilities
return_constant: XAP_ROUTE_DYNAMIC_KEYMAP_CAPABILITIES
}
0x01: {
type: command
name: Get Layer Count
define: GET_LAYER_COUNT
description: TODO
return_type: u8
return_constant: DYNAMIC_KEYMAP_LAYER_COUNT
}
0x02: {
type: command
name: Get Keycode
define: GET_KEYMAP_KEYCODE
description: TODO
request_type: struct
request_struct_length: 3
request_struct_members: [
{
type: u8
name: Layer
},
{
type: u8
name: Row
},
{
type: u8
name: Column
}
]
return_type: u16
return_execute: dynamic_keymap_get_keycode
}
0x03: {
type: command
name: Set Keycode
define: SET_KEYMAP_KEYCODE
description: TODO
request_type: struct
request_struct_length: 5
request_struct_members: [
{
type: u8
name: Layer
},
{
type: u8
name: Row
},
{
type: u8
name: Column
},
{
type: u16
name: Keycode
}
]
return_execute: dynamic_keymap_set_keycode
}
}
}
0x05: {
type: router
name: Dynamic Encoders
define: DYNAMIC_ENCODER
description:
'''
This subsystem allows for live modifications of the keymap, allowing encoder functionality to be reassigned without rebuilding the firmware.
'''
enable_if_preprocessor: defined(DYNAMIC_KEYMAP_ENABLE) && defined(ENCODER_MAP_ENABLE)
routes: {
0x00: {
type: command
name: Capabilities Query
define: CAPABILITIES_QUERY
description:
'''
Dynamic Encoders subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.
'''
return_type: u32
return_purpose: capabilities
return_constant: XAP_ROUTE_DYNAMIC_ENCODER_CAPABILITIES
}
0x02: {
type: command
name: Get Keycode
define: GET_ENCODER_KEYCODE
description: TODO
request_type: struct
request_struct_length: 3
request_struct_members: [
{
type: u8
name: Layer
},
{
type: u8
name: Encoder
},
{
type: u8
name: Clockwise
}
]
return_type: u16
return_execute: dynamic_encoder_get_keycode
}
0x03: {
type: command
name: Set Keycode
define: SET_ENCODER_KEYCODE
description: TODO
request_type: struct
request_struct_length: 5
request_struct_members: [
{
type: u8
name: Layer
},
{
type: u8
name: Encoder
},
{
type: u8
name: Clockwise
},
{
type: u16
name: Keycode
}
]
return_execute: dynamic_encoder_set_keycode
}
}
}
0x06: {
type: router
name: Lighting
define: LIGHTING
description:
'''
This subsystem allows for control over the lighting subsystem.
'''
enable_if_preprocessor: defined(RGBLIGHT_ENABLE) || defined(RGB_MATRIX_ENABLE)
routes: {
0x00: {
type: command
name: Capabilities Query
define: CAPABILITIES_QUERY
description:
'''
Lighting subsystem capabilities query. Each bit should be considered as a "usable" route within this subsystem.
'''
return_type: u32
return_purpose: capabilities
return_constant: XAP_ROUTE_LIGHTING_CAPABILITIES
}
}
}
}
}

65
docs/xap_0.0.1.md Normal file
View file

@ -0,0 +1,65 @@
# QMK Firmware XAP Specs
This document describes the requirements of the QMK XAP ("extensible application protocol") API.
## Types
**All integral types are little-endian.**
| Name | Definition |
| -- | -- |
| _type[n]_ | An array of `type`, with array extent of `N` -- e.g. `u8[2]` signifies two consecutive octets. |
| _u16_ | An unsigned 16-bit integral, commonly seen as `uint16_t` from _stdint.h_. |
| _u32_ | An unsigned 32-bit integral, commonly seen as `uint32_t` from _stdint.h_. |
| _u8_ | An unsigned 8-bit integral (octet, or byte), commonly seen as `uint8_t` from _stdint.h_. |
## Definitions
This list defines the terms used across the entire set of XAP protocol documentation.
| Name | Definition |
| -- | -- |
| _Handler_ | A piece of code that is executed when a specific _route_ is received. |
| _Payload_ | Any received data appended to the _route_, which gets delivered to the _handler_ when received. |
| _Response_ | The data sent back to the host during execution of a _handler_. |
| _Route_ | A sequence of _IDs_ describing the route to invoke a _handler_. |
| _Subsystem_ | A high-level area of functionality within XAP. |
| _ID_ | A single octet / 8-bit byte, representing Subsystem or Route index. |
| _Request Header_ | Packet format for inbound data. Takes the format:<br>`token` - token<br>`u8` - length |
| _Response Flags_ | An `u8` containing the status of the request. |
| _Response Header_ | Packet format for outbound data. Takes the format:<br>`token` - token<br>`response_flags` - flags<br>`u8` - length |
| _Token_ | A `u16` associated with a specific request as well as its corresponding response. |
## Requests and Responses
Communication generally follows a request/response pattern.
Each request needs to include a _token_ -- this `u16` value prefixes each outbound request from the host application and its corresponding response, allowing response messages to be correlated with their request, even if multiple host applications are communicating with the firmware simultaneously. Host applications should randomly generate a token ID for **every** outbound request, unless using a reserved token defined below.
This token is followed by a `u8` signifying the length of data in the request.
Response messages will always be prefixed by the originating request _token_, directly followed by that request's _response flags_, then the response payload length:
| Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
| -- | -- | -- | -- | -- | -- | -- | -- |
| `-` | `-` | `-` | `-` | `-` | `-` | `-` | `SUCCESS` |
* Bit 0 (`SUCCESS`): When this bit is set, the request was successfully handled. If not set, all payload data should be disregarded, and the request retried if appropriate (with a new token).
### Example "conversation":
**Request** -- version query:
| Byte | 0 | 1 | 2 | 3 | 4 |
| --- | --- | --- | --- | --- | --- |
| **Purpose** | Token | Token | Payload Length | Route | Route |
| **Value** | `0x43` | `0x2B` | `0x02` | `0x00` | `0x00` |
**Response** -- matching token, successful flag, payload of `0x03170192` = 3.17.192:
| Byte | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| **Purpose** | Token | Token | Response Flags | Payload Length | Payload | Payload | Payload | Payload |
| **Value** | `0x43` | `0x2B` | `0x01` | `0x04` | `0x92` | `0x01` | `0x17` | `0x03` |

82
docs/xap_0.1.0.md Normal file
View file

@ -0,0 +1,82 @@
# QMK Firmware XAP Specs
This document describes the requirements of the QMK XAP ("extensible application protocol") API.
## Types
**All integral types are little-endian.**
| Name | Definition |
| -- | -- |
| _struct{}_ | A structure of data, packing different objects together. Data is "compacted" -- there are no padding bytes between fields. Equivalent to a packed C-style `struct`. The order in which they're defined matches the order of the data in the response packet. |
| _type[n]_ | An array of `type`, with array extent of `N` -- e.g. `u8[2]` signifies two consecutive octets. |
| _u16_ | An unsigned 16-bit integral, commonly seen as `uint16_t` from _stdint.h_. |
| _u32_ | An unsigned 32-bit integral, commonly seen as `uint32_t` from _stdint.h_. |
| _u64_ | An unsigned 64-bit integral, commonly seen as `uint64_t` from _stdint.h_. |
| _u8_ | An unsigned 8-bit integral (octet, or byte), commonly seen as `uint8_t` from _stdint.h_. |
## Definitions
This list defines the terms used across the entire set of XAP protocol documentation.
| Name | Definition |
| -- | -- |
| _Capability_ | A way to determine if certain functionality is enabled in the firmware. Any _subsystem_ that provides build-time restriction of functionality must provide a _route_ for a _capabilities query_. |
| _Handler_ | A piece of code that is executed when a specific _route_ is received. |
| _Payload_ | Any received data appended to the _route_, which gets delivered to the _handler_ when received. |
| _Response_ | The data sent back to the host during execution of a _handler_. |
| _Route_ | A sequence of _IDs_ describing the route to invoke a _handler_. |
| _Secure Route_ | A _route_ which has potentially destructive consequences, necessitating prior approval by the user before executing. |
| _Subsystem_ | A high-level area of functionality within XAP. |
| _Unlock sequence_ | A physical sequence initiated by the user to enable execution of _secure routes_. |
| _Broadcast Header_ | Packet format for broadcast messages. Takes the format:<br>`token` - token<br>`u8` - type<br>`u8` - length |
| _ID_ | A single octet / 8-bit byte, representing Subsystem or Route index. |
| _Request Header_ | Packet format for inbound data. Takes the format:<br>`token` - token<br>`u8` - length |
| _Response Flags_ | An `u8` containing the status of the request. |
| _Response Header_ | Packet format for outbound data. Takes the format:<br>`token` - token<br>`response_flags` - flags<br>`u8` - length |
| _Token_ | A `u16` associated with a specific request as well as its corresponding response. |
## Requests and Responses
Communication generally follows a request/response pattern.
Each request needs to include a _token_ -- this `u16` value prefixes each outbound request from the host application and its corresponding response, allowing response messages to be correlated with their request, even if multiple host applications are communicating with the firmware simultaneously. Host applications should randomly generate a token ID for **every** outbound request, unless using a reserved token defined below.
This token is followed by a `u8` signifying the length of data in the request.
Two token values are reserved: `0x0000` and `0xFFFF`:
* `0x0000`: A message sent by a host application may use this token if no response is to be sent -- a "fire and forget" message.
* `0xFFFF`: Signifies a "broadcast" message sent by the firmware without prompting from the host application. Broadcast messages are defined later in this document.
Any request will generate at least one corresponding response, with the exception of messages using reserved tokens. Maximum total message length is 128 bytes due to RAM constraints.
Response messages will always be prefixed by the originating request _token_, directly followed by that request's _response flags_, then the response payload length:
| Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
| -- | -- | -- | -- | -- | -- | -- | -- |
| `UNLOCKED` | `UNLOCK_IN_PROGRESS` | `-` | `-` | `-` | `-` | `SECURE_FAILURE` | `SUCCESS` |
* Bit 7 (`UNLOCKED`): When this bit is set, an _unlock sequence_ has completed, and _secure routes_ may be invoked.
* Bit 6 (`UNLOCK_IN_PROGRESS`): When this bit is set, an _unlock sequence_ is in progress.
* Bit 1 (`SECURE_FAILURE`): When this bit is set, the requested _route_ was marked _secure_ but an _unlock sequence_ has not completed.
* Bit 0 (`SUCCESS`): When this bit is set, the request was successfully handled. If not set, all payload data should be disregarded, and the request retried if appropriate (with a new token).
### Example "conversation":
**Request** -- version query:
| Byte | 0 | 1 | 2 | 3 | 4 |
| --- | --- | --- | --- | --- | --- |
| **Purpose** | Token | Token | Payload Length | Route | Route |
| **Value** | `0x43` | `0x2B` | `0x02` | `0x00` | `0x00` |
**Response** -- matching token, successful flag, payload of `0x03170192` = 3.17.192:
| Byte | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| **Purpose** | Token | Token | Response Flags | Payload Length | Payload | Payload | Payload | Payload |
| **Value** | `0x43` | `0x2B` | `0x01` | `0x04` | `0x92` | `0x01` | `0x17` | `0x03` |
## Broadcast messages
Broadcast messages may be sent by the firmware to the host, without a corresponding inbound request. Each broadcast message uses the token `0xFFFF`, and does not expect a response from the host. Tokens are followed by an _ID_ signifying the type of broadcast, with corresponding _payload_.

82
docs/xap_0.2.0.md Normal file
View file

@ -0,0 +1,82 @@
# QMK Firmware XAP Specs
This document describes the requirements of the QMK XAP ("extensible application protocol") API.
## Types
**All integral types are little-endian.**
| Name | Definition |
| -- | -- |
| _struct{}_ | A structure of data, packing different objects together. Data is "compacted" -- there are no padding bytes between fields. Equivalent to a packed C-style `struct`. The order in which they're defined matches the order of the data in the response packet. |
| _type[n]_ | An array of `type`, with array extent of `N` -- e.g. `u8[2]` signifies two consecutive octets. |
| _u16_ | An unsigned 16-bit integral, commonly seen as `uint16_t` from _stdint.h_. |
| _u32_ | An unsigned 32-bit integral, commonly seen as `uint32_t` from _stdint.h_. |
| _u64_ | An unsigned 64-bit integral, commonly seen as `uint64_t` from _stdint.h_. |
| _u8_ | An unsigned 8-bit integral (octet, or byte), commonly seen as `uint8_t` from _stdint.h_. |
## Definitions
This list defines the terms used across the entire set of XAP protocol documentation.
| Name | Definition |
| -- | -- |
| _Capability_ | A way to determine if certain functionality is enabled in the firmware. Any _subsystem_ that provides build-time restriction of functionality must provide a _route_ for a _capabilities query_. |
| _Handler_ | A piece of code that is executed when a specific _route_ is received. |
| _Payload_ | Any received data appended to the _route_, which gets delivered to the _handler_ when received. |
| _Response_ | The data sent back to the host during execution of a _handler_. |
| _Route_ | A sequence of _IDs_ describing the route to invoke a _handler_. |
| _Secure Route_ | A _route_ which has potentially destructive consequences, necessitating prior approval by the user before executing. |
| _Subsystem_ | A high-level area of functionality within XAP. |
| _Unlock sequence_ | A physical sequence initiated by the user to enable execution of _secure routes_. |
| _Broadcast Header_ | Packet format for broadcast messages. Takes the format:<br>`token` - token<br>`u8` - type<br>`u8` - length |
| _ID_ | A single octet / 8-bit byte, representing Subsystem or Route index. |
| _Request Header_ | Packet format for inbound data. Takes the format:<br>`token` - token<br>`u8` - length |
| _Response Flags_ | An `u8` containing the status of the request. |
| _Response Header_ | Packet format for outbound data. Takes the format:<br>`token` - token<br>`response_flags` - flags<br>`u8` - length |
| _Token_ | A `u16` associated with a specific request as well as its corresponding response. |
## Requests and Responses
Communication generally follows a request/response pattern.
Each request needs to include a _token_ -- this `u16` value prefixes each outbound request from the host application and its corresponding response, allowing response messages to be correlated with their request, even if multiple host applications are communicating with the firmware simultaneously. Host applications should randomly generate a token ID for **every** outbound request, unless using a reserved token defined below.
This token is followed by a `u8` signifying the length of data in the request.
Two token values are reserved: `0x0000` and `0xFFFF`:
* `0x0000`: A message sent by a host application may use this token if no response is to be sent -- a "fire and forget" message.
* `0xFFFF`: Signifies a "broadcast" message sent by the firmware without prompting from the host application. Broadcast messages are defined later in this document.
Any request will generate at least one corresponding response, with the exception of messages using reserved tokens. Maximum total message length is 128 bytes due to RAM constraints.
Response messages will always be prefixed by the originating request _token_, directly followed by that request's _response flags_, then the response payload length:
| Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
| -- | -- | -- | -- | -- | -- | -- | -- |
| `UNLOCKED` | `UNLOCK_IN_PROGRESS` | `-` | `-` | `-` | `-` | `SECURE_FAILURE` | `SUCCESS` |
* Bit 7 (`UNLOCKED`): When this bit is set, an _unlock sequence_ has completed, and _secure routes_ may be invoked.
* Bit 6 (`UNLOCK_IN_PROGRESS`): When this bit is set, an _unlock sequence_ is in progress.
* Bit 1 (`SECURE_FAILURE`): When this bit is set, the requested _route_ was marked _secure_ but an _unlock sequence_ has not completed.
* Bit 0 (`SUCCESS`): When this bit is set, the request was successfully handled. If not set, all payload data should be disregarded, and the request retried if appropriate (with a new token).
### Example "conversation":
**Request** -- version query:
| Byte | 0 | 1 | 2 | 3 | 4 |
| --- | --- | --- | --- | --- | --- |
| **Purpose** | Token | Token | Payload Length | Route | Route |
| **Value** | `0x43` | `0x2B` | `0x02` | `0x00` | `0x00` |
**Response** -- matching token, successful flag, payload of `0x03170192` = 3.17.192:
| Byte | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| **Purpose** | Token | Token | Response Flags | Payload Length | Payload | Payload | Payload | Payload |
| **Value** | `0x43` | `0x2B` | `0x01` | `0x04` | `0x92` | `0x01` | `0x17` | `0x03` |
## Broadcast messages
Broadcast messages may be sent by the firmware to the host, without a corresponding inbound request. Each broadcast message uses the token `0xFFFF`, and does not expect a response from the host. Tokens are followed by an _ID_ signifying the type of broadcast, with corresponding _payload_.

5
docs/xap_protocol.md Normal file
View file

@ -0,0 +1,5 @@
# XAP Protocol Reference
* [XAP Version 0.2.0](xap_0.2.0.md)
* [XAP Version 0.1.0](xap_0.1.0.md)
* [XAP Version 0.0.1](xap_0.0.1.md)

View 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

View file

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

View file

@ -1,5 +1,9 @@
"""Functions for working with config.h files.
"""
from pygments.lexers.c_cpp import CLexer
from pygments.token import Token
from pygments import lex
from itertools import islice
from pathlib import Path
import re
@ -13,6 +17,13 @@ multi_comment_regex = re.compile(r'/\*(.|\n)*?\*/', re.MULTILINE)
layout_macro_define_regex = re.compile(r'^#\s*define')
def _get_chunks(it, size):
"""Break down a collection into smaller parts
"""
it = iter(it)
return iter(lambda: tuple(islice(it, size)), ())
def strip_line_comment(string):
"""Removes comments from a single line string.
"""
@ -170,3 +181,108 @@ def _parse_matrix_locations(matrix, file, macro_name):
matrix_locations[identifier] = [row_num, col_num]
return matrix_locations
def _coerce_led_token(_type, value):
""" Convert token to valid info.json content
"""
value_map = {
'NO_LED': None,
'LED_FLAG_ALL': 0xFF,
'LED_FLAG_NONE': 0x00,
'LED_FLAG_MODIFIER': 0x01,
'LED_FLAG_UNDERGLOW': 0x02,
'LED_FLAG_KEYLIGHT': 0x04,
'LED_FLAG_INDICATOR': 0x08,
}
if _type is Token.Literal.Number.Integer:
return int(value)
if _type is Token.Literal.Number.Hex:
return int(value, 0)
if _type is Token.Name and value in value_map.keys():
return value_map[value]
def _parse_led_config(file, matrix_cols, matrix_rows):
"""Return any 'raw' led/rgb matrix config
"""
file_contents = file.read_text(encoding='utf-8')
file_contents = comment_remover(file_contents)
file_contents = file_contents.replace('\\\n', '')
matrix_raw = []
position_raw = []
flags = []
found_led_config = False
bracket_count = 0
section = 0
for _type, value in lex(file_contents, CLexer()):
# Assume g_led_config..stuff..;
if value == 'g_led_config':
found_led_config = True
elif value == ';':
found_led_config = False
elif found_led_config:
# Assume bracket count hints to section of config we are within
if value == '{':
bracket_count += 1
if bracket_count == 2:
section += 1
elif value == '}':
bracket_count -= 1
else:
# Assume any non whitespace value here is important enough to stash
if _type in [Token.Literal.Number.Integer, Token.Literal.Number.Hex, Token.Name]:
if section == 1 and bracket_count == 3:
matrix_raw.append(_coerce_led_token(_type, value))
if section == 2 and bracket_count == 3:
position_raw.append(_coerce_led_token(_type, value))
if section == 3 and bracket_count == 2:
flags.append(_coerce_led_token(_type, value))
# Slightly better intrim format
matrix = list(_get_chunks(matrix_raw, matrix_cols))
position = list(_get_chunks(position_raw, 2))
matrix_indexes = list(filter(lambda x: x is not None, matrix_raw))
# If we have not found anything - bail
if not section:
return None
# TODO: Improve crude parsing/validation
if len(matrix) != matrix_rows and len(matrix) != (matrix_rows / 2):
raise ValueError("Unable to parse g_led_config matrix data")
if len(position) != len(flags):
raise ValueError("Unable to parse g_led_config position data")
if len(matrix_indexes) and (max(matrix_indexes) >= len(flags)):
raise ValueError("OOB within g_led_config matrix data")
return (matrix, position, flags)
def find_led_config(file, matrix_cols, matrix_rows):
"""Search file for led/rgb matrix config
"""
found = _parse_led_config(file, matrix_cols, matrix_rows)
if not found:
return None
# Expand collected content
(matrix, position, flags) = found
# Align to output format
led_config = []
for index, item in enumerate(position, start=0):
led_config.append({
'x': item[0],
'y': item[1],
'flags': flags[index],
})
for r in range(len(matrix)):
for c in range(len(matrix[r])):
index = matrix[r][c]
if index is not None:
led_config[index]['matrix'] = [r, c]
return led_config

65
lib/python/qmk/casing.py Executable file
View file

@ -0,0 +1,65 @@
"""This script handles conversion between snake and camel casing.
"""
import re
_words_expr = re.compile(r"([a-zA-Z][^A-Z0-9]*|[0-9]+)")
_lower_snake_case_expr = re.compile(r'^[a-z][a-z0-9_]*$')
_upper_snake_case_expr = re.compile(r'^[A-Z][A-Z0-9_]*$')
def _is_snake_case(str):
"""Checks if the supplied string is already in snake case.
"""
match = _lower_snake_case_expr.match(str)
if match:
return True
match = _upper_snake_case_expr.match(str)
if match:
return True
return False
def _split_snake_case(str):
"""Splits up a string based on underscores, if it's in snake casing.
"""
if _is_snake_case(str):
return [s.lower() for s in str.split("_")]
return str
def _split_camel_case(str):
"""Splits up a string based on capitalised camel casing.
"""
return _words_expr.findall(str)
def _split_cased_words(str):
return _split_snake_case(str) if _is_snake_case(str) else _split_camel_case(str)
def to_snake(str):
str = "_".join([word.strip().lower() for word in _split_cased_words(str)])
# Fix acronyms
str = str.replace('i_d', 'id')
str = str.replace('x_a_p', 'xap')
str = str.replace('q_m_k', 'qmk')
return str
def to_upper_snake(str):
return to_snake(str).upper()
def to_camel(str):
def _acronym(w):
if w.strip().lower() == 'qmk':
return 'QMK'
elif w.strip().lower() == 'xap':
return 'XAP'
elif w.strip().lower() == 'id':
return 'ID'
return w.title()
return "".join([_acronym(word) for word in _split_cased_words(str)])

View file

@ -17,7 +17,8 @@ import_names = {
'pep8-naming': 'pep8ext_naming',
'pyusb': 'usb.core',
'qmk-dotty-dict': 'dotty_dict',
'pillow': 'PIL'
'pillow': 'PIL',
'Jinja2': 'jinja2'
}
safe_commands = [
@ -52,6 +53,7 @@ subcommands = [
'qmk.cli.generate.dfu_header',
'qmk.cli.generate.docs',
'qmk.cli.generate.info_json',
'qmk.cli.generate.keyboard_c',
'qmk.cli.generate.keyboard_h',
'qmk.cli.generate.layouts',
'qmk.cli.generate.rgb_breathe_table',
@ -72,6 +74,10 @@ subcommands = [
'qmk.cli.pyformat',
'qmk.cli.pytest',
'qmk.cli.via2json',
'qmk.cli.xap',
'qmk.cli.xap.generate_docs',
'qmk.cli.xap.generate_json',
'qmk.cli.xap.generate_qmk',
]

View file

@ -1,24 +1,17 @@
"""Ensure text files have the proper line endings.
"""
from itertools import islice
from subprocess import DEVNULL
from milc import cli
from qmk.path import normpath
def _get_chunks(it, size):
"""Break down a collection into smaller parts
"""
it = iter(it)
return iter(lambda: tuple(islice(it, size)), ())
from qmk.commands import get_chunks
def dos2unix_run(files):
"""Spawn multiple dos2unix subprocess avoiding too long commands on formatting everything
"""
for chunk in _get_chunks(files, 10):
for chunk in get_chunks(files, 10):
dos2unix = cli.run(['dos2unix', *chunk])
if dos2unix.returncode:

View file

@ -2,6 +2,7 @@
"""
from pathlib import Path
import shutil
import hjson
import json
from milc import cli
@ -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)

View file

@ -0,0 +1,66 @@
"""Used by the make system to generate keyboard.c from info.json.
"""
from milc import cli
from qmk.info import info_json
from qmk.commands import dump_lines
from qmk.keyboard import keyboard_completer, keyboard_folder
from qmk.path import normpath
from qmk.constants import GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE
def _gen_led_config(info_data):
"""Convert info.json content to g_led_config
"""
cols = info_data['matrix_size']['cols']
rows = info_data['matrix_size']['rows']
led_config = None
if 'layout' in info_data.get('rgb_matrix', {}):
led_config = info_data['rgb_matrix']['layout']
elif 'layout' in info_data.get('led_matrix', {}):
led_config = info_data['led_matrix']['layout']
lines = []
if not led_config:
return lines
matrix = [['NO_PIN'] * cols for i in range(rows)]
pos = []
flags = []
for index, item in enumerate(led_config, start=0):
if 'matrix' in item:
(x, y) = item['matrix']
matrix[x][y] = str(index)
pos.append(f'{{ {item.get("x", 0)},{item.get("y", 0)} }}')
flags.append(str(item.get('flags', 0)))
lines.append('__attribute__ ((weak)) led_config_t g_led_config = {')
lines.append(' {')
for line in matrix:
lines.append(f' {{ {",".join(line)} }},')
lines.append(' },')
lines.append(f' {{ {",".join(pos)} }},')
lines.append(f' {{ {",".join(flags)} }},')
lines.append('};')
return lines
@cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to')
@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages")
@cli.argument('-kb', '--keyboard', arg_only=True, type=keyboard_folder, completer=keyboard_completer, required=True, help='Keyboard to generate keyboard.c for.')
@cli.subcommand('Used by the make system to generate keyboard.c from info.json', hidden=True)
def generate_keyboard_c(cli):
"""Generates the keyboard.h file.
"""
kb_info_json = info_json(cli.args.keyboard)
# Build the layouts.h file.
keyboard_h_lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '#include QMK_KEYBOARD_H', '']
keyboard_h_lines.extend(_gen_led_config(kb_info_json))
# Show the results
dump_lines(cli.args.output, keyboard_h_lines, cli.args.quiet)

View file

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

View file

@ -0,0 +1,39 @@
"""This script generates the XAP protocol documentation.
"""
import hjson
from qmk.constants import QMK_FIRMWARE
from qmk.xap.common import get_xap_definition_files, update_xap_definitions, render_xap_output
from milc import cli
@cli.subcommand('Generates the XAP protocol documentation.', hidden=False if cli.config.user.developer else True)
def xap_generate_docs(cli):
"""Generates the XAP protocol documentation by merging the definitions files, and producing the corresponding Markdown document under `/docs/`.
"""
docs_list = []
overall = None
for file in get_xap_definition_files():
overall = update_xap_definitions(overall, hjson.load(file.open(encoding='utf-8')))
# Inject dummy bits for unspecified response flags
for n in range(0, 8):
if str(n) not in overall['response_flags']['bits']:
overall['response_flags']['bits'][str(n)] = {'name': '', 'description': '', 'define': '-'}
output_doc = QMK_FIRMWARE / "docs" / f"{file.stem}.md"
docs_list.append(output_doc)
output = render_xap_output('docs', 'docs.md.j2', overall)
with open(output_doc, "w", encoding='utf-8') as out_file:
out_file.write(output)
output_doc = QMK_FIRMWARE / "docs" / "xap_protocol.md"
with open(output_doc, "w", encoding='utf-8') as out_file:
out_file.write('''\
# XAP Protocol Reference
''')
for file in reversed(sorted(docs_list)):
ver = file.stem[4:]
out_file.write(f'* [XAP Version {ver}]({file.name})\n')

View file

@ -0,0 +1,13 @@
"""This script generates the consolidated XAP protocol definitions.
"""
import hjson
from milc import cli
from qmk.xap.common import latest_xap_defs
@cli.subcommand('Generates the consolidated XAP protocol definitions.', hidden=False if cli.config.user.developer else True)
def xap_generate_json(cli):
"""Generates the consolidated XAP protocol definitions.
"""
defs = latest_xap_defs()
print(hjson.dumps(defs))

View file

@ -0,0 +1,51 @@
"""This script generates the XAP protocol generated sources to be compiled into QMK firmware.
"""
from milc import cli
from qmk.path import normpath
from qmk.keyboard import keyboard_completer, keyboard_folder
from qmk.xap.gen_firmware.info_generator import generate_info
from qmk.xap.gen_firmware.inline_generator import generate_inline
from qmk.xap.gen_firmware.header_generator import generate_header
@cli.argument('-o', '--output', type=normpath, help='File to write to')
@cli.subcommand('Generates the XAP protocol include.', hidden=False if cli.config.user.developer else True)
def xap_generate_qmk_inc(cli):
"""Generates the XAP protocol inline codegen file, generated during normal build.
"""
generate_inline(cli.args.output)
@cli.argument('-o', '--output', type=normpath, help='File to write to')
@cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='Name of the keyboard')
@cli.subcommand('Generates the XAP protocol include.', hidden=False if cli.config.user.developer else True)
def xap_generate_qmk_h(cli):
"""Generates the XAP protocol header file, generated during normal build.
"""
# Determine our keyboard
if not cli.args.keyboard:
cli.log.error('Missing parameter: --keyboard')
cli.subcommands['xap-generate-qmk-h'].print_help()
return False
generate_header(cli.args.output, cli.args.keyboard)
@cli.argument('-o', '--output', type=normpath, help='File to write to')
@cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='Name of the keyboard')
@cli.argument('-km', '--keymap', help='The keymap\'s name')
@cli.subcommand('Generates the XAP info.json payload include.', hidden=False if cli.config.user.developer else True)
def xap_generate_info_h(cli):
"""Generates the XAP info.json payload header file, generated during normal build.
"""
# Determine our keyboard/keymap
if not cli.args.keyboard:
cli.log.error('Missing parameter: --keyboard')
cli.subcommands['xap-generate-info-h'].print_help()
return False
if not cli.args.keymap:
cli.log.error('Missing parameter: --keymap')
cli.subcommands['xap-generate-info-h'].print_help()
return False
generate_info(cli.args.output, cli.args.keyboard, cli.args.keymap)

View file

@ -0,0 +1,336 @@
"""Interactions with compatible XAP devices
"""
import cmd
import json
import random
import gzip
from platform import platform
from milc import cli
from qmk.keyboard import render_layout
from qmk.xap.common import get_xap_keycodes
KEYCODE_MAP = get_xap_keycodes('latest')
def _is_xap_usage(x):
return x['usage_page'] == 0xFF51 and x['usage'] == 0x0058
def _is_filtered_device(x):
name = "%04x:%04x" % (x['vendor_id'], x['product_id'])
return name.lower().startswith(cli.args.device.lower())
def _search():
devices = filter(_is_xap_usage, hid.enumerate())
if cli.args.device:
devices = filter(_is_filtered_device, devices)
return list(devices)
def print_dotted_output(kb_info_json, prefix=''):
"""Print the info.json in a plain text format with dot-joined keys.
"""
for key in sorted(kb_info_json):
new_prefix = f'{prefix}.{key}' if prefix else key
if key in ['parse_errors', 'parse_warnings']:
continue
elif key == 'layouts' and prefix == '':
cli.echo(' {fg_blue}layouts{fg_reset}: %s', ', '.join(sorted(kb_info_json['layouts'].keys())))
elif isinstance(kb_info_json[key], bytes):
conv = "".join(["{:02X}".format(b) for b in kb_info_json[key]])
cli.echo(' {fg_blue}%s{fg_reset}: %s', new_prefix, conv)
elif isinstance(kb_info_json[key], dict):
print_dotted_output(kb_info_json[key], new_prefix)
elif isinstance(kb_info_json[key], list):
data = kb_info_json[key]
if len(data) and isinstance(data[0], dict):
for index, item in enumerate(data, start=0):
cli.echo(' {fg_blue}%s.%s{fg_reset}: %s', new_prefix, index, str(item))
else:
cli.echo(' {fg_blue}%s{fg_reset}: %s', new_prefix, ', '.join(sorted(map(str, data))))
else:
cli.echo(' {fg_blue}%s{fg_reset}: %s', new_prefix, kb_info_json[key])
def _xap_transaction(device, sub, route, *args):
# gen token
tok = random.getrandbits(16)
token = tok.to_bytes(2, byteorder='little')
# send with padding
# TODO: this code is total garbage
args_data = []
args_len = 2
if len(args) == 1:
if isinstance(args[0], (bytes, bytearray)):
args_len += len(args[0])
args_data = args[0]
else:
args_len += 2
args_data = args[0].to_bytes(2, byteorder='little')
padding_len = 64 - 3 - args_len
padding = b"\x00" * padding_len
if args_data:
padding = args_data + padding
buffer = token + args_len.to_bytes(1, byteorder='little') + sub.to_bytes(1, byteorder='little') + route.to_bytes(1, byteorder='little') + padding
# prepend 0 on windows because reasons...
if 'windows' in platform().lower():
buffer = b"\x00" + buffer
device.write(buffer)
# get resp
array_alpha = device.read(64, 250)
# validate tok sent == resp
if str(token) != str(array_alpha[:2]):
return None
if int(array_alpha[2]) != 0x01:
return None
payload_len = int(array_alpha[3])
return array_alpha[4:4 + payload_len]
def _query_device(device):
ver_data = _xap_transaction(device, 0x00, 0x00)
if not ver_data:
return {'xap': 'UNKNOWN', 'secure': 'UNKNOWN'}
# to u32 to BCD string
a = (ver_data[3] << 24) + (ver_data[2] << 16) + (ver_data[1] << 8) + (ver_data[0])
ver = f'{a>>24}.{a>>16 & 0xFF}.{a & 0xFFFF}'
secure = int.from_bytes(_xap_transaction(device, 0x00, 0x03), 'little')
secure = 'unlocked' if secure == 2 else 'LOCKED'
return {'xap': ver, 'secure': secure}
def _query_device_id(device):
return _xap_transaction(device, 0x01, 0x08)
def _query_device_info_len(device):
len_data = _xap_transaction(device, 0x01, 0x05)
if not len_data:
return 0
# to u32
return (len_data[3] << 24) + (len_data[2] << 16) + (len_data[1] << 8) + (len_data[0])
def _query_device_info_chunk(device, offset):
return _xap_transaction(device, 0x01, 0x06, offset)
def _query_device_info(device):
datalen = _query_device_info_len(device)
if not datalen:
return {}
data = []
offset = 0
while offset < datalen:
data += _query_device_info_chunk(device, offset)
offset += 32
str_data = gzip.decompress(bytearray(data[:datalen]))
return json.loads(str_data)
def _list_devices():
"""Dump out available devices
"""
cli.log.info('Available devices:')
devices = _search()
for dev in devices:
device = hid.Device(path=dev['path'])
data = _query_device(device)
cli.log.info(" %04x:%04x %s %s [API:%s] %s", dev['vendor_id'], dev['product_id'], dev['manufacturer_string'], dev['product_string'], data['xap'], data['secure'])
if cli.config.general.verbose:
# TODO: better formatting like "lsusb -v"?
data = _query_device_info(device)
data["_id"] = _query_device_id(device)
print_dotted_output(data)
def xap_dummy(device):
# get layer count
layers = _xap_transaction(device, 0x04, 0x01)
layers = int.from_bytes(layers, "little")
print(f'layers:{layers}')
# get keycode [layer:0, row:0, col:0]
# keycode = _xap_transaction(device, 0x04, 0x02, b"\x00\x00\x00")
# get encoder [layer:0, index:0, clockwise:0]
keycode = _xap_transaction(device, 0x05, 0x02, b"\x00\x00\x00")
keycode = int.from_bytes(keycode, "little")
print(f'keycode:{KEYCODE_MAP.get(keycode, "unknown")}[{keycode}]')
# set encoder [layer:0, index:0, clockwise:0, keycode:KC_A]
_xap_transaction(device, 0x05, 0x03, b"\x00\x00\x00\x04\00")
def xap_broadcast_listen(device):
try:
cli.log.info("Listening for XAP broadcasts...")
while 1:
array_alpha = device.read(64, 100)
if str(b"\xFF\xFF") == str(array_alpha[:2]):
if array_alpha[2] == 1:
cli.log.info(" Broadcast: Secure[%02x]", array_alpha[4])
else:
cli.log.info(" Broadcast: type[%02x] data:[%02x]", array_alpha[2], array_alpha[4])
except KeyboardInterrupt:
cli.log.info("Stopping...")
def xap_unlock(device):
_xap_transaction(device, 0x00, 0x04)
class XAPShell(cmd.Cmd):
intro = 'Welcome to the XAP shell. Type help or ? to list commands.\n'
prompt = 'Ψ> '
def __init__(self, device):
cmd.Cmd.__init__(self)
self.device = device
# cache keycodes for this device
self.keycodes = get_xap_keycodes(_query_device(device)['xap'])
def do_about(self, arg):
"""Prints out the current version of QMK with a build date
"""
data = _query_device(self.device)
print(data)
def do_unlock(self, arg):
"""Initiate secure unlock
"""
xap_unlock(self.device)
print("Done")
def do_listen(self, arg):
"""Log out XAP broadcast messages
"""
xap_broadcast_listen(self.device)
def do_keycode(self, arg):
"""Prints out the keycode value of a certain layer, row, and column
"""
data = bytes(map(int, arg.split()))
if len(data) != 3:
cli.log.error("Invalid args")
return
keycode = _xap_transaction(self.device, 0x04, 0x02, data)
keycode = int.from_bytes(keycode, "little")
print(f'keycode:{self.keycodes.get(keycode, "unknown")}[{keycode}]')
def do_keymap(self, arg):
"""Prints out the keycode values of a certain layer
"""
data = bytes(map(int, arg.split()))
if len(data) != 1:
cli.log.error("Invalid args")
return
info = _query_device_info(self.device)
rows = info['matrix_size']['rows']
cols = info['matrix_size']['cols']
for r in range(rows):
for c in range(cols):
q = data + r.to_bytes(1, byteorder='little') + c.to_bytes(1, byteorder='little')
keycode = _xap_transaction(self.device, 0x04, 0x02, q)
keycode = int.from_bytes(keycode, "little")
print(f'| {self.keycodes.get(keycode, "unknown").ljust(7)} ', end='', flush=True)
print('|')
def do_layer(self, arg):
"""Renders keycode values of a certain layer
"""
data = bytes(map(int, arg.split()))
if len(data) != 1:
cli.log.error("Invalid args")
return
info = _query_device_info(self.device)
# Assumptions on selected layout rather than prompt
first_layout = next(iter(info['layouts']))
layout = info['layouts'][first_layout]['layout']
keycodes = []
for item in layout:
q = data + bytes(item['matrix'])
keycode = _xap_transaction(self.device, 0x04, 0x02, q)
keycode = int.from_bytes(keycode, "little")
keycodes.append(self.keycodes.get(keycode, "???"))
print(render_layout(layout, False, keycodes))
def do_exit(self, line):
"""Quit shell
"""
return True
def do_EOF(self, line): # noqa: N802
"""Quit shell (ctrl+D)
"""
return True
def loop(self):
"""Wrapper for cmdloop that handles ctrl+C
"""
try:
self.cmdloop()
print('')
except KeyboardInterrupt:
print('^C')
return False
@cli.argument('-d', '--device', help='device to select - uses format <pid>:<vid>.')
@cli.argument('-l', '--list', arg_only=True, action='store_true', help='List available devices.')
@cli.argument('-i', '--interactive', arg_only=True, action='store_true', help='Start interactive shell.')
@cli.argument('action', nargs='*', default=['listen'], arg_only=True)
@cli.subcommand('Acquire debugging information from usb XAP devices.', hidden=False if cli.config.user.developer else True)
def xap(cli):
"""Acquire debugging information from XAP devices
"""
# Lazy load to avoid issues
global hid
import hid
if cli.args.list:
return _list_devices()
# Connect to first available device
devices = _search()
if not devices:
cli.log.error("No devices found!")
return False
dev = devices[0]
device = hid.Device(path=dev['path'])
cli.log.info("Connected to:%04x:%04x %s %s", dev['vendor_id'], dev['product_id'], dev['manufacturer_string'], dev['product_string'])
# shell?
if cli.args.interactive:
XAPShell(device).loop()
return True
XAPShell(device).onecmd(" ".join(cli.args.action))

View file

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

View file

@ -8,7 +8,7 @@ from dotty_dict import dotty
from milc import cli
from qmk.constants import CHIBIOS_PROCESSORS, LUFA_PROCESSORS, VUSB_PROCESSORS
from qmk.c_parse import find_layouts, parse_config_h_file
from qmk.c_parse import find_layouts, parse_config_h_file, find_led_config
from qmk.json_schema import deep_update, json_load, validate
from qmk.keyboard import config_h, rules_mk
from qmk.keymap import list_keymaps, locate_keymap
@ -76,6 +76,9 @@ def info_json(keyboard):
# Ensure that we have matrix row and column counts
info_data = _matrix_size(info_data)
# Merge in data from <keyboard.c>
info_data = _extract_led_config(info_data, str(keyboard))
# Validate against the jsonschema
try:
validate(info_data, 'qmk.api.keyboard.v1')
@ -590,6 +593,46 @@ def _extract_rules_mk(info_data, rules):
return info_data
def find_keyboard_c(keyboard):
"""Find all <keyboard>.c files
"""
keyboard = Path(keyboard)
current_path = Path('keyboards/')
files = []
for directory in keyboard.parts:
current_path = current_path / directory
keyboard_c_path = current_path / f'{directory}.c'
if keyboard_c_path.exists():
files.append(keyboard_c_path)
return files
def _extract_led_config(info_data, keyboard):
"""Scan all <keyboard>.c files for led config
"""
cols = info_data['matrix_size']['cols']
rows = info_data['matrix_size']['rows']
# Assume what feature owns g_led_config
feature = "rgb_matrix"
if info_data.get("features", {}).get("led_matrix", False):
feature = "led_matrix"
# Process
for file in find_keyboard_c(keyboard):
try:
ret = find_led_config(file, cols, rows)
if ret:
info_data[feature] = info_data.get(feature, {})
info_data[feature]["layout"] = ret
except Exception as e:
_log_warning(info_data, f'led_config: {file.name}: {e}')
return info_data
def _matrix_size(info_data):
"""Add info_data['matrix_size'] if it doesn't exist.
"""

View file

@ -75,8 +75,8 @@ class InfoJSONEncoder(QMKJSONEncoder):
"""Encode info.json dictionaries.
"""
if obj:
if self.indentation_level == 4:
# These are part of a layout, put them on a single line.
if set(("x", "y")).issubset(obj.keys()):
# These are part of a layout/led_config, put them on a single line.
return "{ " + ", ".join(f"{self.encode(key)}: {self.encode(element)}" for key, element in sorted(obj.items())) + " }"
else:

View file

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

View file

124
lib/python/qmk/xap/common.py Executable file
View file

@ -0,0 +1,124 @@
"""This script handles the XAP protocol data files.
"""
import os
import hjson
from pathlib import Path
from typing import OrderedDict
from jinja2 import Environment, FileSystemLoader, select_autoescape
from qmk.constants import QMK_FIRMWARE
from qmk.json_schema import json_load
from qmk.decorators import lru_cache
def _get_jinja2_env(data_templates_xap_subdir: str):
templates_dir = os.path.join(QMK_FIRMWARE, 'data', 'templates', 'xap', data_templates_xap_subdir)
j2 = Environment(loader=FileSystemLoader(templates_dir), autoescape=select_autoescape())
return j2
def render_xap_output(data_templates_xap_subdir, file_to_render, defs):
j2 = _get_jinja2_env(data_templates_xap_subdir)
return j2.get_template(file_to_render).render(xap=defs, xap_str=hjson.dumps(defs))
def _merge_ordered_dicts(dicts):
"""Merges nested OrderedDict objects resulting from reading a hjson file.
Later input dicts overrides earlier dicts for plain values.
Arrays will be appended. If the first entry of an array is "!reset!", the contents of the array will be cleared and replaced with RHS.
Dictionaries will be recursively merged. If any entry is "!reset!", the contents of the dictionary will be cleared and replaced with RHS.
"""
result = OrderedDict()
def add_entry(target, k, v):
if k in target and isinstance(v, OrderedDict):
if "!reset!" in v:
target[k] = v
else:
target[k] = _merge_ordered_dicts([target[k], v])
if "!reset!" in target[k]:
del target[k]["!reset!"]
elif k in target and isinstance(v, list):
if v[0] == '!reset!':
target[k] = v[1:]
else:
target[k] = target[k] + v
else:
target[k] = v
for d in dicts:
for (k, v) in d.items():
add_entry(result, k, v)
return result
def get_xap_definition_files():
"""Get the sorted list of XAP definition files, from <QMK>/data/xap.
"""
xap_defs = QMK_FIRMWARE / "data" / "xap"
return list(sorted(xap_defs.glob('**/xap_*.hjson')))
def update_xap_definitions(original, new):
"""Creates a new XAP definition object based on an original and the new supplied object.
Both inputs must be of type OrderedDict.
Later input dicts overrides earlier dicts for plain values.
Arrays will be appended. If the first entry of an array is "!reset!", the contents of the array will be cleared and replaced with RHS.
Dictionaries will be recursively merged. If any entry is "!reset!", the contents of the dictionary will be cleared and replaced with RHS.
"""
if original is None:
original = OrderedDict()
return _merge_ordered_dicts([original, new])
@lru_cache(timeout=5)
def get_xap_defs(version):
"""Gets the required version of the XAP definitions.
"""
files = get_xap_definition_files()
# Slice off anything newer than specified version
if version != 'latest':
index = [idx for idx, s in enumerate(files) if version in str(s)][0]
files = files[:(index + 1)]
definitions = [hjson.load(file.open(encoding='utf-8')) for file in files]
return _merge_ordered_dicts(definitions)
def latest_xap_defs():
"""Gets the latest version of the XAP definitions.
"""
return get_xap_defs('latest')
@lru_cache(timeout=5)
def get_xap_keycodes(xap_version):
"""Gets keycode data for the required version of the XAP definitions.
"""
defs = get_xap_defs(xap_version)
# Load DD keycodes for the dependency
keycode_version = defs['uses']['keycodes']
spec = json_load(Path(f'data/constants/keycodes_{keycode_version}.json'))
# Transform into something more usable - { raw_value : first alias || keycode }
return {int(k, 16): v.get('aliases', [v.get('key')])[0] for k, v in spec['keycodes'].items()}
def route_conditions(route_stack):
"""Handles building the C preprocessor conditional based on the current route.
"""
conditions = []
for route in route_stack:
if 'enable_if_preprocessor' in route:
conditions.append(route['enable_if_preprocessor'])
if len(conditions) == 0:
return None
return "(" + ' && '.join([f'({c})' for c in conditions]) + ")"

View file

@ -0,0 +1,261 @@
"""This script generates the XAP protocol generated header to be compiled into QMK.
"""
import re
from fnvhash import fnv1a_32
from qmk.casing import to_snake
from qmk.commands import dump_lines
from qmk.git import git_get_version
from qmk.constants import GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE
from qmk.xap.common import latest_xap_defs, route_conditions
def _get_c_type(xap_type):
if xap_type == 'bool':
return 'bool'
elif xap_type == 'u8':
return 'uint8_t'
elif xap_type == 'u16':
return 'uint16_t'
elif xap_type == 'u32':
return 'uint32_t'
elif xap_type == 'u64':
return 'uint64_t'
elif xap_type == 'struct':
return 'struct'
elif xap_type == 'string':
return 'const char *'
return 'unknown'
def _append_route_defines(lines, container, container_id=None, route_stack=None):
"""Handles building the list of the XAP routes, combining parent and child names together, as well as the route number.
"""
if route_stack is None:
route_stack = [container]
else:
route_stack.append(container)
route_name = '_'.join([r['define'] for r in route_stack])
if container_id:
lines.append(f'#define {route_name} {container_id}')
if 'routes' in container:
for route_id in container['routes']:
route = container['routes'][route_id]
_append_route_defines(lines, route, route_id, route_stack)
route_stack.pop()
def _append_route_masks(lines, container, container_id=None, route_stack=None):
"""Handles creating the equivalent XAP route masks, for capabilities checks. Forces value of `0` if disabled in the firmware.
"""
if route_stack is None:
route_stack = [container]
else:
route_stack.append(container)
route_name = '_'.join([r['define'] for r in route_stack])
condition = route_conditions(route_stack)
if container_id:
if condition:
lines.append('')
lines.append(f'#if {condition}')
lines.append(f'#define {route_name}_MASK (1ul << ({route_name}))')
if condition:
lines.append(f'#else // {condition}')
lines.append(f'#define {route_name}_MASK 0')
lines.append(f'#endif // {condition}')
lines.append('')
if 'routes' in container:
for route_id in container['routes']:
route = container['routes'][route_id]
_append_route_masks(lines, route, route_id, route_stack)
route_stack.pop()
def _append_route_capabilities(lines, container, container_id=None, route_stack=None):
"""Handles creating the equivalent XAP route masks, for capabilities checks. Forces value of `0` if disabled in the firmware.
"""
if route_stack is None:
route_stack = [container]
else:
route_stack.append(container)
route_name = '_'.join([r['define'] for r in route_stack])
if 'routes' in container:
lines.append('')
lines.append(f'#define {route_name}_CAPABILITIES (0 \\')
if 'routes' in container:
for route_id in container['routes']:
route = container['routes'][route_id]
route_stack.append(route)
child_name = '_'.join([r['define'] for r in route_stack])
lines.append(f' | ({child_name}_MASK) \\')
route_stack.pop()
lines.append(' )')
if 'routes' in container:
for route_id in container['routes']:
route = container['routes'][route_id]
_append_route_capabilities(lines, route, route_id, route_stack)
route_stack.pop()
def _append_route_types(lines, container, container_id=None, route_stack=None):
"""Handles creating typedefs used by routes
"""
if route_stack is None:
route_stack = [container]
else:
route_stack.append(container)
route_name = to_snake('_'.join([r['define'] for r in route_stack]))
# Inbound
if 'request_struct_members' in container:
request_struct_members = container['request_struct_members']
lines.append('typedef struct {')
for member in request_struct_members:
member_type = _get_c_type(member['type'])
member_name = to_snake(member['name'])
lines.append(f' {member_type} {member_name};')
lines.append(f'}} __attribute__((__packed__)) {route_name}_arg_t;')
req_len = container['request_struct_length']
lines.append(f'_Static_assert(sizeof({route_name}_arg_t) == {req_len}, "{route_name}_arg_t needs to be {req_len} bytes in size");')
elif 'request_type' in container:
request_type = container['request_type']
found = re.search(r'(u\d+)\[(\d+)\]', request_type)
if found:
request_type, size = found.groups()
lines.append(f'typedef struct __attribute__((__packed__)) {{ {_get_c_type(request_type)} x[{size}]; }} {route_name}_arg_t;')
else:
lines.append(f'typedef {_get_c_type(request_type)} {route_name}_arg_t;')
# Outbound
qualifier = 'const' if 'return_constant' in container else ''
if 'return_struct_members' in container:
return_struct_members = container['return_struct_members']
lines.append('typedef struct {')
for member in return_struct_members:
member_type = _get_c_type(member['type'])
member_name = f'{qualifier} {to_snake(member["name"])}'
lines.append(f' {member_type} {member_name};')
lines.append(f'}} __attribute__((__packed__)) {route_name}_t;')
req_len = container['return_struct_length']
lines.append(f'_Static_assert(sizeof({route_name}_t) == {req_len}, "{route_name}_t needs to be {req_len} bytes in size");')
elif 'return_type' in container:
return_type = container['return_type']
found = re.search(r'(u\d+)\[(\d+)\]', return_type)
if found:
return_type, size = found.groups()
lines.append(f'typedef struct __attribute__((__packed__)) {{ {_get_c_type(return_type)} x[{size}]; }} {route_name}_t;')
else:
lines.append(f'typedef {_get_c_type(return_type)} {route_name}_t;')
# Recurse
if 'routes' in container:
for route_id in container['routes']:
route = container['routes'][route_id]
_append_route_types(lines, route, route_id, route_stack)
route_stack.pop()
def _append_internal_types(lines, container):
"""Handles creating the various constants, types, defines, etc.
"""
response_flags = container.get('response_flags', {})
prefix = response_flags['define_prefix']
for key, value in response_flags['bits'].items():
define = value.get('define')
lines.append(f'#define {prefix}_{define} (1ul << ({key}))')
# Add special
lines.append(f'#define {prefix}_FAILED 0x00')
lines.append('')
broadcast_messages = container.get('broadcast_messages', {})
broadcast_prefix = broadcast_messages['define_prefix']
for key, value in broadcast_messages['messages'].items():
define = value.get('define')
lines.append(f'#define {broadcast_prefix}_{define} {key}')
# Add special
lines.append(f'#define {broadcast_prefix}_TOKEN 0xFFFF')
lines.append('')
additional_types = {}
types = container.get('type_definitions', {})
for key, value in types.items():
data_type = _get_c_type(value['type'])
additional_types[key] = f'xap_{key}_t'
for key, value in types.items():
data_type = _get_c_type(value['type'])
if data_type == 'struct':
members = value['struct_members']
lines.append(f'typedef {data_type} {{')
for member in members:
member_name = member["name"]
member_type = _get_c_type(member["type"])
if member_type == 'unknown':
member_type = additional_types[member["type"]]
lines.append(f' {member_type} {member_name};')
lines.append(f'}} __attribute__((__packed__)) xap_{key}_t;')
req_len = value['struct_length']
lines.append(f'_Static_assert(sizeof(xap_{key}_t) == {req_len}, "xap_{key}_t needs to be {req_len} bytes in size");')
else:
lines.append(f'typedef {data_type} xap_{key}_t;')
def generate_header(output_file, keyboard):
"""Generates the XAP protocol header file, generated during normal build.
"""
xap_defs = latest_xap_defs()
# Preamble
lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '#pragma once', '']
# Versions
prog = re.compile(r'^(\d+)\.(\d+)\.(\d+)')
b = prog.match(xap_defs['version'])
lines.append(f'#define XAP_BCD_VERSION 0x{int(b.group(1)):02d}{int(b.group(2)):02d}{int(b.group(3)):04d}ul')
b = prog.match(git_get_version() or "")
lines.append(f'#define QMK_BCD_VERSION 0x{int(b.group(1)):02d}{int(b.group(2)):02d}{int(b.group(3)):04d}ul')
keyboard_id = fnv1a_32(bytes(keyboard, 'utf-8'))
lines.append(f'#define XAP_KEYBOARD_IDENTIFIER 0x{keyboard_id:08X}ul')
lines.append('')
# Types
_append_internal_types(lines, xap_defs)
lines.append('')
_append_route_types(lines, xap_defs)
lines.append('')
# Append the route and command defines
_append_route_defines(lines, xap_defs)
lines.append('')
_append_route_masks(lines, xap_defs)
lines.append('')
_append_route_capabilities(lines, xap_defs)
lines.append('')
dump_lines(output_file, lines)

View file

@ -0,0 +1,41 @@
"""This script generates the XAP info.json payload header to be compiled into QMK.
"""
import json
import gzip
from qmk.info import keymap_json
from qmk.commands import get_chunks, dump_lines
from qmk.constants import GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE
def generate_info(output_file, keyboard, keymap):
# Build the info.json file
km_info_json = keymap_json(keyboard, keymap)
# TODO: Munge to XAP requirements
del km_info_json['config_h_features']
# Minify
str_data = json.dumps(km_info_json, separators=(',', ':'))
# Compress
compressed = gzip.compress(str_data.encode("utf-8"), compresslevel=9)
# split into lines to match xxd output
hex_array = ["0x{:02X}".format(b) for b in compressed]
data_len = len(hex_array)
data = ""
for chunk in get_chunks(hex_array, 12):
data += f' {", ".join(chunk)},\n'
lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '#pragma once', '']
# Gen output file
lines.append('static const unsigned char info_json_gz[] PROGMEM = {')
lines.append(data)
lines.append('};')
lines.append(f'#define INFO_JSON_GZ_LEN {data_len}')
dump_lines(output_file, lines)

View file

@ -0,0 +1,283 @@
"""This script generates the XAP protocol generated header to be compiled into QMK.
"""
from qmk.casing import to_snake
from qmk.commands import dump_lines
from qmk.constants import GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE
from qmk.xap.common import latest_xap_defs, route_conditions
def _get_c_type(xap_type):
if xap_type == 'bool':
return 'bool'
elif xap_type == 'u8':
return 'uint8_t'
elif xap_type == 'u16':
return 'uint16_t'
elif xap_type == 'u32':
return 'uint32_t'
elif xap_type == 'u64':
return 'uint64_t'
elif xap_type == 'struct':
return 'struct'
elif xap_type == 'string':
return 'const char *'
return 'unknown'
def _get_c_size(xap_type):
if xap_type == 'u8':
return 'sizeof(uint8_t)'
elif xap_type == 'u16':
return 'sizeof(uint16_t)'
elif xap_type == 'u32':
return 'sizeof(uint32_t)'
elif xap_type == 'u64':
return 8
elif xap_type == 'u8[32]':
return 32
return 0
def _get_route_type(container):
if 'routes' in container:
return 'XAP_ROUTE'
elif 'return_execute' in container:
return 'XAP_EXECUTE'
elif 'return_value' in container:
if container['return_type'] == 'u8':
return 'XAP_VALUE'
elif 'return_constant' in container:
if container['return_type'] == 'u8':
return 'XAP_CONST_MEM'
elif container['return_type'] == 'u16':
return 'XAP_CONST_MEM'
elif container['return_type'] == 'u32':
return 'XAP_CONST_MEM'
elif container['return_type'] == 'struct':
return 'XAP_CONST_MEM'
elif container['return_type'] == 'string':
return 'XAP_CONST_MEM'
elif 'return_getter' in container:
if container['return_type'] == 'u32':
return 'XAP_GETTER'
return 'UNSUPPORTED'
def _append_routing_table_declaration(lines, container, container_id, route_stack):
route_stack.append(container)
route_name = to_snake('_'.join([r['define'] for r in route_stack]))
if 'routes' in container:
pass
elif 'return_execute' in container:
execute = container['return_execute']
lines.append(f'bool xap_respond_{execute}(xap_token_t token, const uint8_t *data, size_t data_len);')
# elif 'return_value' in container:
# value = container['return_value']
# return_type = container['return_type']
# lines.append('')
# lines.append(f'{_get_c_type(return_type)} {value} = 0;')
elif 'return_constant' in container:
if container['return_type'] == 'u8':
constant = container['return_constant']
lines.append('')
lines.append(f'static const uint8_t {route_name}_data PROGMEM = {constant};')
elif container['return_type'] == 'u16':
constant = container['return_constant']
lines.append('')
lines.append(f'static const uint16_t {route_name}_data PROGMEM = {constant};')
elif container['return_type'] == 'u32':
constant = container['return_constant']
lines.append('')
lines.append(f'static const uint32_t {route_name}_data PROGMEM = {constant};')
elif container['return_type'] == 'struct':
lines.append('')
lines.append(f'static const {route_name}_t {route_name}_data PROGMEM = {{')
for constant in container['return_constant']:
lines.append(f' {constant},')
lines.append('};')
elif container['return_type'] == 'string':
constant = container['return_constant']
lines.append('')
lines.append(f'static const char {route_name}_str[] PROGMEM = {constant};')
elif 'return_getter' in container:
if container['return_type'] == 'u32':
lines.append('')
lines.append(f'extern uint32_t {route_name}_getter(void);')
elif container['return_type'] == 'struct':
pass
route_stack.pop()
def _append_routing_table_entry_flags(lines, container, container_id, route_stack):
pem_map = {
None: 'ROUTE_PERMISSIONS_INSECURE',
'secure': 'ROUTE_PERMISSIONS_SECURE',
'ignore': 'ROUTE_PERMISSIONS_IGNORE',
}
is_secure = pem_map[container.get('permissions', None)]
lines.append(' .flags = {')
lines.append(f' .type = {_get_route_type(container)},')
lines.append(f' .secure = {is_secure},')
lines.append(' },')
def _append_routing_table_entry_route(lines, container, container_id, route_stack):
route_name = to_snake('_'.join([r['define'] for r in route_stack]))
lines.append(f' .child_routes = {route_name}_table,')
lines.append(f' .child_routes_len = sizeof({route_name}_table)/sizeof(xap_route_t),')
def _append_routing_table_entry_execute(lines, container, container_id, route_stack):
value = container['return_execute']
lines.append(f' .handler = xap_respond_{value},')
def _append_routing_table_entry_value(lines, container, container_id, route_stack):
value = container['return_value']
lines.append(f' .const_data = &{value},')
lines.append(f' .const_data_len = sizeof({value}),')
def _append_routing_table_entry_u32getter(lines, container, container_id, route_stack):
route_name = to_snake('_'.join([r['define'] for r in route_stack]))
lines.append(f' .u32getter = &{route_name}_getter,')
def _append_routing_table_entry_const_data(lines, container, container_id, route_stack):
route_name = to_snake('_'.join([r['define'] for r in route_stack]))
lines.append(f' .const_data = &{route_name}_data,')
lines.append(f' .const_data_len = sizeof({route_name}_data),')
def _append_routing_table_entry_string(lines, container, container_id, route_stack):
route_name = to_snake('_'.join([r['define'] for r in route_stack]))
lines.append(f' .const_data = {route_name}_str,')
lines.append(f' .const_data_len = sizeof({route_name}_str) - 1,')
def _append_routing_table_entry(lines, container, container_id, route_stack):
route_stack.append(container)
route_name = '_'.join([r['define'] for r in route_stack])
condition = route_conditions(route_stack)
if condition:
lines.append(f'#if {condition}')
lines.append(f' [{route_name}] = {{')
_append_routing_table_entry_flags(lines, container, container_id, route_stack)
if 'routes' in container:
_append_routing_table_entry_route(lines, container, container_id, route_stack)
elif 'return_execute' in container:
_append_routing_table_entry_execute(lines, container, container_id, route_stack)
elif 'return_value' in container:
_append_routing_table_entry_value(lines, container, container_id, route_stack)
elif 'return_constant' in container:
if container['return_type'] == 'u8':
_append_routing_table_entry_const_data(lines, container, container_id, route_stack)
elif container['return_type'] == 'u16':
_append_routing_table_entry_const_data(lines, container, container_id, route_stack)
elif container['return_type'] == 'u32':
_append_routing_table_entry_const_data(lines, container, container_id, route_stack)
elif container['return_type'] == 'struct':
_append_routing_table_entry_const_data(lines, container, container_id, route_stack)
elif container['return_type'] == 'string':
_append_routing_table_entry_string(lines, container, container_id, route_stack)
elif 'return_getter' in container:
if container['return_type'] == 'u32':
_append_routing_table_entry_u32getter(lines, container, container_id, route_stack)
lines.append(' },')
if condition:
lines.append(f'#endif // {condition}')
route_stack.pop()
def _append_routing_tables(lines, container, container_id=None, route_stack=None):
"""Handles building the list of the XAP routes, combining parent and child names together, as well as the route number.
"""
if route_stack is None:
route_stack = [container]
else:
route_stack.append(container)
route_name = to_snake('_'.join([r['define'] for r in route_stack]))
condition = route_conditions(route_stack)
if 'routes' in container:
for route_id in container['routes']:
route = container['routes'][route_id]
_append_routing_tables(lines, route, route_id, route_stack)
for route_id in container['routes']:
route = container['routes'][route_id]
_append_routing_table_declaration(lines, route, route_id, route_stack)
lines.append('')
if condition:
lines.append(f'#if {condition}')
lines.append(f'static const xap_route_t {route_name}_table[] PROGMEM = {{')
for route_id in container['routes']:
route = container['routes'][route_id]
_append_routing_table_entry(lines, route, route_id, route_stack)
lines.append('};')
if condition:
lines.append(f'#endif // {condition}')
lines.append('')
route_stack.pop()
def _append_broadcast_messages(lines, container):
"""TODO:
"""
broadcast_messages = container.get('broadcast_messages', {})
broadcast_prefix = broadcast_messages['define_prefix']
for key, value in broadcast_messages['messages'].items():
define = value.get('define')
name = to_snake(f'{broadcast_prefix}_{define}')
if 'return_type' in value:
ret_type = _get_c_type(value['return_type'])
lines.append(f'void {name}({ret_type} value) {{ xap_broadcast({key}, &value, sizeof(value)); }}')
else:
lines.append(f'void {name}(const void *data, size_t length){{ xap_broadcast({key}, data, length); }}')
def generate_inline(output_file):
"""Generates the XAP protocol header file, generated during normal build.
"""
xap_defs = latest_xap_defs()
# Preamble
lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '']
# Add all the generated code
_append_broadcast_messages(lines, xap_defs)
_append_routing_tables(lines, xap_defs)
dump_lines(output_file, lines)

View file

@ -81,6 +81,10 @@ void eeconfig_init_quantum(void) {
// properly re-initialized.
via_eeprom_set_valid(false);
eeconfig_init_via();
#elif defined(XAP_ENABLE)
// TODO: define XAP reset behaviour
void dynamic_keymap_reset(void);
dynamic_keymap_reset();
#endif
eeconfig_init_kb();

View file

@ -552,6 +552,10 @@ void quantum_task(void) {
#ifdef SECURE_ENABLE
secure_task();
#endif
#ifdef XAP_ENABLE
xap_event_task();
#endif
}
/** \brief Keyboard task: Do keyboard routine jobs

View file

@ -213,6 +213,10 @@ extern layer_state_t layer_state;
# include "joystick.h"
#endif
#ifdef XAP_ENABLE
# include "xap.h"
#endif
#ifdef VIA_ENABLE
# include "via.h"
#endif

172
quantum/xap/xap.c Normal file
View file

@ -0,0 +1,172 @@
/* Copyright 2021 Nick Brassel (@tzarc)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <quantum.h>
#include <xap.h>
#include "secure.h"
#include "info_json_gz.h"
bool get_info_json_chunk(uint16_t offset, uint8_t *data, uint8_t data_len) {
if (offset + data_len > INFO_JSON_GZ_LEN) {
data_len = INFO_JSON_GZ_LEN - offset;
}
memcpy_P(data, &info_json_gz[offset], data_len);
return true;
}
#define QSTR2(z) #z
#define QSTR(z) QSTR2(z)
typedef enum xap_route_type_t {
XAP_UNUSED = 0, // "Unused" needs to be zero -- undefined routes (through preprocessor) will be skipped
XAP_ROUTE,
XAP_EXECUTE,
XAP_VALUE,
XAP_GETTER,
XAP_CONST_MEM,
TOTAL_XAP_ROUTE_TYPES
} xap_route_type_t;
#define XAP_ROUTE_TYPE_BIT_COUNT 3
typedef enum xap_route_secure_t {
ROUTE_PERMISSIONS_INSECURE,
ROUTE_PERMISSIONS_SECURE,
ROUTE_PERMISSIONS_IGNORE,
} xap_route_secure_t;
#define XAP_ROUTE_SECURE_BIT_COUNT 2
typedef struct __attribute__((packed)) xap_route_flags_t {
xap_route_type_t type : XAP_ROUTE_TYPE_BIT_COUNT;
xap_route_secure_t secure : XAP_ROUTE_SECURE_BIT_COUNT;
} xap_route_flags_t;
_Static_assert(TOTAL_XAP_ROUTE_TYPES <= (1 << (XAP_ROUTE_TYPE_BIT_COUNT)), "Number of XAP route types is too large for XAP_ROUTE_TYPE_BITS.");
_Static_assert(sizeof(xap_route_flags_t) == 1, "xap_route_flags_t is not length of 1");
typedef struct xap_route_t xap_route_t;
struct __attribute__((packed)) xap_route_t {
const xap_route_flags_t flags;
union {
// XAP_ROUTE
struct {
const xap_route_t *child_routes;
const uint8_t child_routes_len;
};
// XAP_EXECUTE
bool (*handler)(xap_token_t token, const uint8_t *data, size_t data_len);
// XAP_GETTER
uint32_t (*u32getter)(void);
// XAP_VALUE / XAP_CONST_MEM
struct {
const void * const_data;
const uint8_t const_data_len;
};
};
};
#include <xap_generated.inl>
bool xap_pre_execute_route(xap_token_t token, const xap_route_t *route) {
#ifdef SECURE_ENABLE
if (!secure_is_unlocked() && (route->flags.secure == ROUTE_PERMISSIONS_SECURE)) {
xap_respond_failure(token, XAP_RESPONSE_FLAG_SECURE_FAILURE);
return true;
}
if (secure_is_unlocking() && (route->flags.type != XAP_ROUTE) && (route->flags.secure != ROUTE_PERMISSIONS_IGNORE)) {
xap_respond_failure(token, XAP_RESPONSE_FLAG_UNLOCK_IN_PROGRESS);
return true;
}
// TODO: XAP messages extend unlocked timeout?
secure_activity_event();
#endif
return false;
}
void xap_execute_route(xap_token_t token, const xap_route_t *routes, size_t max_routes, const uint8_t *data, size_t data_len) {
if (data_len == 0) return;
xap_identifier_t id = data[0];
if (id < max_routes) {
xap_route_t route;
memcpy_P(&route, &routes[id], sizeof(xap_route_t));
if (xap_pre_execute_route(token, &route)) {
return;
}
switch (route.flags.type) {
case XAP_ROUTE:
if (route.child_routes != NULL && route.child_routes_len > 0 && data_len > 0) {
xap_execute_route(token, route.child_routes, route.child_routes_len, &data[1], data_len - 1);
return;
}
break;
case XAP_EXECUTE:
if (route.handler != NULL) {
bool ok = (route.handler)(token, data_len == 1 ? NULL : &data[1], data_len - 1);
if (ok) return;
}
break;
case XAP_GETTER:
if (route.u32getter != NULL) {
const uint32_t ret = (route.u32getter)();
xap_respond_data(token, &ret, sizeof(ret));
return;
}
break;
case XAP_VALUE:
xap_respond_data(token, route.const_data, route.const_data_len);
return;
case XAP_CONST_MEM:
xap_respond_data_P(token, route.const_data, route.const_data_len);
return;
default:
break;
}
}
// Nothing got handled, so we respond with failure.
xap_respond_failure(token, XAP_RESPONSE_FLAG_FAILED);
}
void xap_receive(xap_token_t token, const uint8_t *data, size_t length) {
xap_execute_route(token, xap_route_table, sizeof(xap_route_table) / sizeof(xap_route_t), data, length);
}
void xap_event_task(void) {
#ifdef SECURE_ENABLE
static secure_status_t last_status = -1;
secure_status_t status = secure_get_status();
if (last_status != status) {
last_status = status;
xap_broadcast_secure_status(status);
}
#endif
}

40
quantum/xap/xap.h Normal file
View file

@ -0,0 +1,40 @@
/* Copyright 2021 Nick Brassel (@tzarc)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stdlib.h>
#include <stdint.h>
#include <xap_generated.h>
#ifndef XAP_SUBSYSTEM_VERSION_KB
# define XAP_SUBSYSTEM_VERSION_KB 0
#endif
#ifndef XAP_SUBSYSTEM_VERSION_USER
# define XAP_SUBSYSTEM_VERSION_USER 0
#endif
void xap_respond_failure(xap_token_t token, xap_response_flags_t response_flags);
bool xap_respond_u32(xap_token_t token, uint32_t value);
bool xap_respond_data(xap_token_t token, const void *data, size_t length);
bool xap_respond_data_P(xap_token_t token, const void *data, size_t length);
void xap_send(xap_token_t token, xap_response_flags_t response_flags, const void *data, size_t length);
void xap_broadcast(uint8_t type, const void *data, size_t length);
void xap_event_task(void);

155
quantum/xap/xap_handlers.c Normal file
View file

@ -0,0 +1,155 @@
/* Copyright 2021 Nick Brassel (@tzarc)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <quantum.h>
#include <xap.h>
#include "hardware_id.h"
#include "secure.h"
#ifndef SECURE_ENABLE
# define secure_get_status() SECURE_UNLOCKED
# define secure_request_unlock()
# define secure_lock()
#endif
void xap_respond_success(xap_token_t token) {
xap_send(token, XAP_RESPONSE_FLAG_SUCCESS, NULL, 0);
}
void xap_respond_failure(xap_token_t token, xap_response_flags_t response_flags) {
xap_send(token, response_flags, NULL, 0);
}
bool xap_respond_data(xap_token_t token, const void *data, size_t length) {
xap_send(token, XAP_RESPONSE_FLAG_SUCCESS, data, length);
return true;
}
bool xap_respond_data_P(xap_token_t token, const void *data, size_t length) {
uint8_t blob[length];
memcpy_P(blob, data, length);
return xap_respond_data(token, blob, length);
}
bool xap_respond_u32(xap_token_t token, uint32_t value) {
xap_send(token, XAP_RESPONSE_FLAG_SUCCESS, &value, sizeof(value));
return true;
}
uint32_t xap_route_qmk_ffffffffffffffff_getter(void) {
return 0x12345678;
}
bool xap_respond_get_info_json_chunk(xap_token_t token, const void *data, size_t length) {
if (length != sizeof(uint16_t)) {
return false;
}
uint16_t offset = *((uint16_t *)data);
xap_route_qmk_info_query_t ret = {0};
bool get_info_json_chunk(uint16_t offset, uint8_t * data, uint8_t data_len);
get_info_json_chunk(offset, (uint8_t *)&ret, sizeof(ret));
return xap_respond_data(token, &ret, sizeof(ret));
}
bool xap_respond_secure_status(xap_token_t token, const void *data, size_t length) {
uint8_t ret = secure_get_status();
return xap_respond_data(token, &ret, sizeof(ret));
}
bool xap_respond_secure_unlock(xap_token_t token, const void *data, size_t length) {
secure_request_unlock();
return xap_respond_data(token, NULL, 0);
}
bool xap_respond_secure_lock(xap_token_t token, const void *data, size_t length) {
secure_lock();
return xap_respond_data(token, NULL, 0);
}
// TODO: how to set this if "custom" is just an empty stub
#ifndef BOOTLOADER_JUMP_SUPPORTED
# define BOOTLOADER_JUMP_SUPPORTED
#endif
#ifdef BOOTLOADER_JUMP_SUPPORTED
bool xap_respond_request_bootloader_jump(xap_token_t token, const void *data, size_t length) {
extern uint8_t secure_status;
uint8_t ret = secure_status == 2;
// TODO: post to deferred queue so this request can return?
bool res = xap_respond_data(token, &ret, sizeof(ret));
reset_keyboard();
return res;
}
#endif
bool xap_respond_get_hardware_id(xap_token_t token, const void *data, size_t length) {
hardware_id_t ret = get_hardware_id();
return xap_respond_data(token, &ret, sizeof(ret));
}
#if ((defined(DYNAMIC_KEYMAP_ENABLE)))
bool xap_respond_dynamic_keymap_get_keycode(xap_token_t token, const void *data, size_t length) {
if (length != sizeof(xap_route_dynamic_keymap_get_keymap_keycode_arg_t)) {
return false;
}
xap_route_dynamic_keymap_get_keymap_keycode_arg_t *arg = (xap_route_dynamic_keymap_get_keymap_keycode_arg_t *)data;
uint16_t keycode = dynamic_keymap_get_keycode(arg->layer, arg->row, arg->column);
return xap_respond_data(token, &keycode, sizeof(keycode));
}
bool xap_respond_dynamic_keymap_set_keycode(xap_token_t token, const void *data, size_t length) {
if (length != sizeof(xap_route_dynamic_keymap_set_keymap_keycode_arg_t)) {
return false;
}
xap_route_dynamic_keymap_set_keymap_keycode_arg_t *arg = (xap_route_dynamic_keymap_set_keymap_keycode_arg_t *)data;
dynamic_keymap_set_keycode(arg->layer, arg->row, arg->column, arg->keycode);
xap_respond_success(token);
return true;
}
#endif
#if ((defined(ENCODER_MAP_ENABLE)))
bool xap_respond_dynamic_encoder_get_keycode(xap_token_t token, const void *data, size_t length) {
if (length != sizeof(xap_route_dynamic_encoder_get_encoder_keycode_arg_t)) {
return false;
}
xap_route_dynamic_encoder_get_encoder_keycode_arg_t *arg = (xap_route_dynamic_encoder_get_encoder_keycode_arg_t *)data;
uint16_t keycode = dynamic_keymap_get_encoder(arg->layer, arg->encoder, arg->clockwise);
return xap_respond_data(token, &keycode, sizeof(keycode));
}
bool xap_respond_dynamic_encoder_set_keycode(xap_token_t token, const void *data, size_t length) {
if (length != sizeof(xap_route_dynamic_encoder_set_encoder_keycode_arg_t)) {
return false;
}
xap_route_dynamic_encoder_set_encoder_keycode_arg_t *arg = (xap_route_dynamic_encoder_set_encoder_keycode_arg_t *)data;
dynamic_keymap_set_encoder(arg->layer, arg->encoder, arg->clockwise, arg->keycode);
xap_respond_success(token);
return true;
}
#endif

View file

@ -2,8 +2,10 @@
appdirs
argcomplete
colorama
fnvhash
hid
hjson
Jinja2
jsonschema>=4
milc>=1.4.2
pygments

View file

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

View file

@ -53,6 +53,10 @@ extern keymap_config_t keymap_config;
# include "joystick.h"
#endif
#ifdef XAP_ENABLE
# include "xap.h"
#endif
/* ---------------------------------------------------------
* Global interface variables and declarations
* ---------------------------------------------------------
@ -313,6 +317,9 @@ typedef struct {
#ifdef RAW_ENABLE
usb_driver_config_t raw_driver;
#endif
#ifdef XAP_ENABLE
usb_driver_config_t xap_driver;
#endif
#ifdef MIDI_ENABLE
usb_driver_config_t midi_driver;
#endif
@ -350,6 +357,14 @@ static usb_driver_configs_t drivers = {
.raw_driver = QMK_USB_DRIVER_CONFIG(RAW, 0, false),
#endif
#ifdef XAP_ENABLE
# define XAP_IN_CAPACITY 4
# define XAP_OUT_CAPACITY 4
# define XAP_IN_MODE USB_EP_MODE_TYPE_INTR
# define XAP_OUT_MODE USB_EP_MODE_TYPE_INTR
.xap_driver = QMK_USB_DRIVER_CONFIG(XAP, 0, false),
#endif
#ifdef MIDI_ENABLE
# define MIDI_STREAM_IN_CAPACITY 4
# define MIDI_STREAM_OUT_CAPACITY 4
@ -1115,6 +1130,69 @@ void raw_hid_task(void) {
#endif
#ifdef XAP_ENABLE
extern void xap_receive(xap_token_t token, const uint8_t *data, size_t length);
void xap_send_base(uint8_t *data, uint8_t length) {
// TODO: implement variable size packet
if (length != XAP_EPSIZE) {
return;
}
chnWrite(&drivers.xap_driver.driver, data, length);
}
void xap_send(xap_token_t token, xap_response_flags_t response_flags, const void *data, size_t length) {
uint8_t rdata[XAP_EPSIZE] = {0};
xap_response_header_t *header = (xap_response_header_t *)&rdata[0];
header->token = token;
if (length > (XAP_EPSIZE - sizeof(xap_response_header_t))) response_flags &= ~(XAP_RESPONSE_FLAG_SUCCESS);
header->flags = response_flags;
if (response_flags & (XAP_RESPONSE_FLAG_SUCCESS)) {
header->length = (uint8_t)length;
if (data != NULL) {
memcpy(&rdata[sizeof(xap_response_header_t)], data, length);
}
}
xap_send_base(rdata, sizeof(rdata));
}
void xap_broadcast(uint8_t type, const void *data, size_t length) {
uint8_t rdata[XAP_EPSIZE] = {0};
xap_broadcast_header_t *header = (xap_broadcast_header_t *)&rdata[0];
header->token = XAP_BROADCAST_TOKEN;
header->type = type;
if (length > (XAP_EPSIZE - sizeof(xap_broadcast_header_t))) return;
header->length = (uint8_t)length;
if (data != NULL) {
memcpy(&rdata[sizeof(xap_broadcast_header_t)], data, length);
}
xap_send_base(rdata, sizeof(rdata));
}
void xap_receive_base(const void *data) {
const uint8_t * u8data = (const uint8_t *)data;
xap_request_header_t *header = (xap_request_header_t *)&u8data[0];
if (header->length <= (XAP_EPSIZE - sizeof(xap_request_header_t))) {
xap_receive(header->token, &u8data[sizeof(xap_request_header_t)], header->length);
}
}
void xap_task(void) {
uint8_t buffer[XAP_EPSIZE];
size_t size = 0;
do {
size_t size = chnReadTimeout(&drivers.xap_driver.driver, buffer, sizeof(buffer), TIME_IMMEDIATE);
if (size > 0) {
xap_receive_base(buffer);
}
} while (size > 0);
}
#endif // XAP_ENABLE
#ifdef MIDI_ENABLE
void send_midi_packet(MIDI_EventPacket_t *event) {

View file

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

View file

@ -322,6 +322,30 @@ const USB_Descriptor_HIDReport_Datatype_t PROGMEM RawReport[] = {
};
#endif
#ifdef XAP_ENABLE
const USB_Descriptor_HIDReport_Datatype_t PROGMEM XapReport[] = {
HID_RI_USAGE_PAGE(16, 0xFF51), // Vendor Defined ('Q')
HID_RI_USAGE(8, 0x58), // Vendor Defined ('X')
HID_RI_COLLECTION(8, 0x01), // Application
// Data to host
HID_RI_USAGE(8, 0x62), // Vendor Defined
HID_RI_LOGICAL_MINIMUM(8, 0x00),
HID_RI_LOGICAL_MAXIMUM(16, 0x00FF),
HID_RI_REPORT_COUNT(8, XAP_EPSIZE),
HID_RI_REPORT_SIZE(8, 0x08),
HID_RI_INPUT(8, HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE),
// Data from host
HID_RI_USAGE(8, 0x63), // Vendor Defined
HID_RI_LOGICAL_MINIMUM(8, 0x00),
HID_RI_LOGICAL_MAXIMUM(16, 0x00FF),
HID_RI_REPORT_COUNT(8, XAP_EPSIZE),
HID_RI_REPORT_SIZE(8, 0x08),
HID_RI_OUTPUT(8, HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE | HID_IOF_NON_VOLATILE),
HID_RI_END_COLLECTION(0),
};
#endif // XAP_ENABLE
#ifdef CONSOLE_ENABLE
const USB_Descriptor_HIDReport_Datatype_t PROGMEM ConsoleReport[] = {
HID_RI_USAGE_PAGE(16, 0xFF31), // Vendor Defined (PJRC Teensy compatible)
@ -554,6 +578,56 @@ const USB_Descriptor_Configuration_t PROGMEM ConfigurationDescriptor = {
},
#endif
#ifdef XAP_ENABLE
/*
* QMK XAP
*/
.Xap_Interface = {
.Header = {
.Size = sizeof(USB_Descriptor_Interface_t),
.Type = DTYPE_Interface
},
.InterfaceNumber = XAP_INTERFACE,
.AlternateSetting = 0x00,
.TotalEndpoints = 2,
.Class = HID_CSCP_HIDClass,
.SubClass = HID_CSCP_NonBootSubclass,
.Protocol = HID_CSCP_NonBootProtocol,
.InterfaceStrIndex = NO_DESCRIPTOR
},
.Xap_HID = {
.Header = {
.Size = sizeof(USB_HID_Descriptor_HID_t),
.Type = HID_DTYPE_HID
},
.HIDSpec = VERSION_BCD(1, 1, 1),
.CountryCode = 0x00,
.TotalReportDescriptors = 1,
.HIDReportType = HID_DTYPE_Report,
.HIDReportLength = sizeof(XapReport)
},
.Xap_INEndpoint = {
.Header = {
.Size = sizeof(USB_Descriptor_Endpoint_t),
.Type = DTYPE_Endpoint
},
.EndpointAddress = (ENDPOINT_DIR_IN | XAP_IN_EPNUM),
.Attributes = (EP_TYPE_INTERRUPT | ENDPOINT_ATTR_NO_SYNC | ENDPOINT_USAGE_DATA),
.EndpointSize = XAP_EPSIZE,
.PollingIntervalMS = 0x01
},
.Xap_OUTEndpoint = {
.Header = {
.Size = sizeof(USB_Descriptor_Endpoint_t),
.Type = DTYPE_Endpoint
},
.EndpointAddress = (ENDPOINT_DIR_OUT | XAP_OUT_EPNUM),
.Attributes = (EP_TYPE_INTERRUPT | ENDPOINT_ATTR_NO_SYNC | ENDPOINT_USAGE_DATA),
.EndpointSize = XAP_EPSIZE,
.PollingIntervalMS = 0x01
},
#endif
#if defined(MOUSE_ENABLE) && !defined(MOUSE_SHARED_EP)
/*
* Mouse
@ -1148,6 +1222,14 @@ uint16_t get_usb_descriptor(const uint16_t wValue, const uint16_t wIndex, const
break;
#endif
#ifdef XAP_ENABLE
case XAP_INTERFACE:
Address = &ConfigurationDescriptor.Xap_HID;
Size = sizeof(USB_HID_Descriptor_HID_t);
break;
#endif
#ifdef CONSOLE_ENABLE
case CONSOLE_INTERFACE:
Address = &ConfigurationDescriptor.Console_HID;
@ -1205,6 +1287,14 @@ uint16_t get_usb_descriptor(const uint16_t wValue, const uint16_t wIndex, const
break;
#endif
#ifdef XAP_ENABLE
case XAP_INTERFACE:
Address = &XapReport;
Size = sizeof(XapReport);
break;
#endif
#ifdef CONSOLE_ENABLE
case CONSOLE_INTERFACE:
Address = &ConsoleReport;

View file

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

View file

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

View file

@ -35,6 +35,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
# include "raw_hid.h"
#endif
#ifdef XAP_ENABLE
# include "xap.h"
# include <string.h>
#endif
#if defined(CONSOLE_ENABLE)
# define RBUF_SIZE 128
# include "ring_buffer.h"
@ -60,6 +65,10 @@ enum usb_interfaces {
RAW_INTERFACE = NEXT_INTERFACE,
#endif
#ifdef XAP_ENABLE
XAP_INTERFACE = NEXT_INTERFACE,
#endif
#if defined(SHARED_EP_ENABLE) && !defined(KEYBOARD_SHARED_EP)
SHARED_INTERFACE = NEXT_INTERFACE,
#endif
@ -137,7 +146,7 @@ void raw_hid_send(uint8_t *data, uint8_t length) {
}
uint8_t *temp = data;
for (uint8_t i = 0; i < 4; i++) {
for (uint8_t i = 0; i < (RAW_BUFFER_SIZE / RAW_EPSIZE); i++) {
while (!usbInterruptIsReady4()) {
usbPoll();
}
@ -164,6 +173,85 @@ void raw_hid_task(void) {
}
#endif
/*------------------------------------------------------------------*
* XAP
*------------------------------------------------------------------*/
#ifdef XAP_ENABLE
# define XAP_BUFFER_SIZE 64
# define XAP_EPSIZE 8
static uint8_t xap_output_buffer[XAP_BUFFER_SIZE];
static uint8_t xap_output_received_bytes = 0;
extern void xap_receive(xap_token_t token, const uint8_t *data, size_t length);
void xap_send_base(uint8_t *data, uint8_t length) {
if (length != XAP_BUFFER_SIZE) {
return;
}
uint8_t *temp = data;
for (uint8_t i = 0; i < (XAP_BUFFER_SIZE / XAP_EPSIZE); i++) {
while (!usbInterruptIsReady4()) {
usbPoll();
}
usbSetInterrupt4(temp, 8);
temp += 8;
}
while (!usbInterruptIsReady4()) {
usbPoll();
}
usbSetInterrupt4(0, 0);
}
void xap_send(xap_token_t token, xap_response_flags_t response_flags, const void *data, size_t length) {
uint8_t rdata[XAP_BUFFER_SIZE] = {0};
xap_response_header_t *header = (xap_response_header_t *)&rdata[0];
header->token = token;
if (length > (XAP_BUFFER_SIZE - sizeof(xap_response_header_t))) response_flags &= ~(XAP_RESPONSE_FLAG_SUCCESS);
header->flags = response_flags;
if (response_flags & (XAP_RESPONSE_FLAG_SUCCESS)) {
header->length = (uint8_t)length;
if (data != NULL) {
memcpy(&rdata[sizeof(xap_response_header_t)], data, length);
}
}
xap_send_base(rdata, sizeof(rdata));
}
void xap_broadcast(uint8_t type, const void *data, size_t length) {
uint8_t rdata[XAP_BUFFER_SIZE] = {0};
xap_broadcast_header_t *header = (xap_broadcast_header_t *)&rdata[0];
header->token = XAP_BROADCAST_TOKEN;
header->type = type;
if (length > (XAP_BUFFER_SIZE - sizeof(xap_broadcast_header_t))) return;
header->length = (uint8_t)length;
if (data != NULL) {
memcpy(&rdata[sizeof(xap_broadcast_header_t)], data, length);
}
xap_send_base(rdata, sizeof(rdata));
}
void xap_receive_base(const void *data) {
const uint8_t * u8data = (const uint8_t *)data;
xap_request_header_t *header = (xap_request_header_t *)&u8data[0];
if (header->length <= (XAP_BUFFER_SIZE - sizeof(xap_request_header_t))) {
xap_receive(header->token, &u8data[sizeof(xap_request_header_t)], header->length);
}
}
void xap_task(void) {
if (xap_output_received_bytes == XAP_BUFFER_SIZE) {
xap_receive_base(xap_output_buffer);
xap_output_received_bytes = 0;
}
}
#endif
/*------------------------------------------------------------------*
* Console
*------------------------------------------------------------------*/
@ -402,6 +490,24 @@ void usbFunctionWriteOut(uchar *data, uchar len) {
raw_output_received_bytes += len;
}
#endif
#ifdef XAP_ENABLE
// Data from host must be divided every 8bytes
if (len != 8) {
dprint("XAP: invalid length\n");
xap_output_received_bytes = 0;
return;
}
if (xap_output_received_bytes + len > XAP_BUFFER_SIZE) {
dprint("XAP: buffer full\n");
xap_output_received_bytes = 0;
} else {
for (uint8_t i = 0; i < 8; i++) {
xap_output_buffer[xap_output_received_bytes + i] = data[i];
}
xap_output_received_bytes += len;
}
#endif
}
/*------------------------------------------------------------------*
@ -624,6 +730,29 @@ const PROGMEM uchar raw_hid_report[] = {
};
#endif
#ifdef XAP_ENABLE
const PROGMEM uchar xap_report[] = {
0x06, 0x51, 0xFF, // Usage Page (Vendor Defined)
0x09, 0x58, // Usage (Vendor Defined)
0xA1, 0x01, // Collection (Application)
// Data to host
0x09, 0x62, // Usage (Vendor Defined)
0x15, 0x00, // Logical Minimum (0)
0x26, 0xFF, 0x00, // Logical Maximum (255)
0x95, XAP_BUFFER_SIZE, // Report Count
0x75, 0x08, // Report Size (8)
0x81, 0x02, // Input (Data, Variable, Absolute)
// Data from host
0x09, 0x63, // Usage (Vendor Defined)
0x15, 0x00, // Logical Minimum (0)
0x26, 0xFF, 0x00, // Logical Maximum (255)
0x95, XAP_BUFFER_SIZE, // Report Count
0x75, 0x08, // Report Size (8)
0x91, 0x02, // Output (Data, Variable, Absolute)
0xC0 // End Collection
};
#endif
#if defined(CONSOLE_ENABLE)
const PROGMEM uchar console_hid_report[] = {
0x06, 0x31, 0xFF, // Usage Page (Vendor Defined - PJRC Teensy compatible)
@ -823,6 +952,56 @@ const PROGMEM usbConfigurationDescriptor_t usbConfigurationDescriptor = {
},
# endif
# if defined(XAP_ENABLE)
/*
* XAP
*/
.xapInterface = {
.header = {
.bLength = sizeof(usbInterfaceDescriptor_t),
.bDescriptorType = USBDESCR_INTERFACE
},
.bInterfaceNumber = XAP_INTERFACE,
.bAlternateSetting = 0x00,
.bNumEndpoints = 2,
.bInterfaceClass = 0x03,
.bInterfaceSubClass = 0x00,
.bInterfaceProtocol = 0x00,
.iInterface = 0x00
},
.xapHID = {
.header = {
.bLength = sizeof(usbHIDDescriptor_t),
.bDescriptorType = USBDESCR_HID
},
.bcdHID = 0x0101,
.bCountryCode = 0x00,
.bNumDescriptors = 1,
.bDescriptorType = USBDESCR_HID_REPORT,
.wDescriptorLength = sizeof(xap_report)
},
.xapINEndpoint = {
.header = {
.bLength = sizeof(usbEndpointDescriptor_t),
.bDescriptorType = USBDESCR_ENDPOINT
},
.bEndpointAddress = (USBRQ_DIR_DEVICE_TO_HOST | USB_CFG_EP4_NUMBER),
.bmAttributes = 0x03,
.wMaxPacketSize = XAP_EPSIZE,
.bInterval = USB_POLLING_INTERVAL_MS
},
.xapOUTEndpoint = {
.header = {
.bLength = sizeof(usbEndpointDescriptor_t),
.bDescriptorType = USBDESCR_ENDPOINT
},
.bEndpointAddress = (USBRQ_DIR_HOST_TO_DEVICE | USB_CFG_EP4_NUMBER),
.bmAttributes = 0x03,
.wMaxPacketSize = XAP_EPSIZE,
.bInterval = USB_POLLING_INTERVAL_MS
},
# endif
# ifdef SHARED_EP_ENABLE
/*
* Shared
@ -975,6 +1154,13 @@ USB_PUBLIC usbMsgLen_t usbFunctionDescriptor(struct usbRequest *rq) {
break;
#endif
#if defined(XAP_ENABLE)
case XAP_INTERFACE:
usbMsgPtr = (usbMsgPtr_t)&usbConfigurationDescriptor.xapHID;
len = sizeof(usbHIDDescriptor_t);
break;
#endif
#ifdef SHARED_EP_ENABLE
case SHARED_INTERFACE:
usbMsgPtr = (usbMsgPtr_t)&usbConfigurationDescriptor.sharedHID;
@ -1007,6 +1193,13 @@ USB_PUBLIC usbMsgLen_t usbFunctionDescriptor(struct usbRequest *rq) {
break;
#endif
#if defined(XAP_ENABLE)
case XAP_INTERFACE:
usbMsgPtr = (usbMsgPtr_t)xap_report;
len = sizeof(xap_report);
break;
#endif
#ifdef SHARED_EP_ENABLE
case SHARED_INTERFACE:
usbMsgPtr = (usbMsgPtr_t)shared_hid_report;

View file

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