mirror of https://github.com/nirenjan/libx52.git
Compare commits
585 Commits
Author | SHA1 | Date |
---|---|---|
|
1902ca0d27 | |
|
6330d28c4d | |
|
5c37c4a9db | |
|
863e43e4ad | |
|
49c57f4a6a | |
|
b0b9123a2e | |
|
50a911160f | |
|
7a56af032b | |
|
c46cec3138 | |
|
21050e40a8 | |
|
9e2e8cb8ff | |
|
5f4dfe4c01 | |
|
0870518598 | |
|
d7b4a694fa | |
|
326ac992ac | |
|
d3973a0abf | |
|
ebca9566d7 | |
|
03c0376e7c | |
|
d4412aba3e | |
|
e08c46f6c4 | |
|
708ace20be | |
|
4b411d6767 | |
|
8b2c0e4a2f | |
|
b810c457f9 | |
|
ccabb5c953 | |
|
b2f292bf58 | |
|
7f59984357 | |
|
c2c2e91089 | |
|
d60ab7e1e4 | |
|
2b7c643537 | |
|
d96b86a817 | |
|
fea095dc50 | |
|
1dcadb9428 | |
|
ecfb865c58 | |
|
b1c7a16eac | |
|
b38a75462d | |
|
619d516ccc | |
|
88159d4fc5 | |
|
c3a4fea139 | |
|
1d1e5781c2 | |
|
1a02ad22d9 | |
|
b822d3aed8 | |
|
dfa78ff2a9 | |
|
d3d32cf278 | |
|
b0f9006594 | |
|
35f5da6b50 | |
|
41812e3c1e | |
|
95a10b5ac9 | |
|
d0f0232dae | |
|
ba9348b888 | |
|
0ac3a3d0c4 | |
|
552e638b27 | |
|
44fba0c6cd | |
|
a119fe2c60 | |
|
8c8f261c80 | |
|
6a8dff0a17 | |
|
8f9ab9cefd | |
|
3168061c99 | |
|
24ce9bc97b | |
|
251ccfde0d | |
|
611bc9d965 | |
|
69426c62d8 | |
|
054df1ab7a | |
|
527d4d5a1e | |
|
c87c7caa68 | |
|
0f7cd3fdb3 | |
|
33e940606c | |
|
f0ad185421 | |
|
98822190ed | |
|
cf6811d923 | |
|
5ab4784b21 | |
|
385f1ca574 | |
|
cf89e3d610 | |
|
e9d4e81a4d | |
|
3896fa0d3e | |
|
f6c6db6c61 | |
|
2119e00647 | |
|
f4a8e7c4d5 | |
|
417641dee0 | |
|
86f599fc6e | |
|
09740e0fe9 | |
|
7448334824 | |
|
f2b0110380 | |
|
87bf5881dd | |
|
e82f9032eb | |
|
2fe7b8af43 | |
|
20abe8974c | |
|
116b9e2c0c | |
|
7b8c71dd35 | |
|
eb98804607 | |
|
d8a5a2c3b8 | |
|
5a78492140 | |
|
abc74d6e37 | |
|
3225d37e6e | |
|
931f945133 | |
|
18c0c72c74 | |
|
293ba0a99d | |
|
52429e8dc3 | |
|
6c3efa44f5 | |
|
6d78ab1940 | |
|
3c006d0929 | |
|
6c17e73284 | |
|
581d2c0bbd | |
|
8d38c4d16b | |
|
41be44cc94 | |
|
2cc3cc5bfe | |
|
8fd16d544a | |
|
1c0d98c474 | |
|
ad178d3f6d | |
|
d9eed14e0d | |
|
fec67b5994 | |
|
247e98c5dc | |
|
9a39e971f1 | |
|
e7d91fd3a4 | |
|
f51985dd20 | |
|
b45dc59ae0 | |
|
aef1b6fade | |
|
794b09e766 | |
|
ba936df6f8 | |
|
5f21ccd2e9 | |
|
3374bb98dc | |
|
00ed62b72e | |
|
a5a25c307c | |
|
fe54730447 | |
|
4d2736e03c | |
|
4f427b2ebe | |
|
e8abbd0374 | |
|
d8fc859e44 | |
|
4365e86f2a | |
|
16e53b897f | |
|
8deb6a1513 | |
|
c56f715155 | |
|
025a06351a | |
|
53957d0813 | |
|
2a8ca8424e | |
|
276b512478 | |
|
7f29f5f5fe | |
|
42850bc4cd | |
|
b9e5f34aa4 | |
|
016851478a | |
|
8874a282aa | |
|
3a81acf828 | |
|
fa1d54f9da | |
|
7d757dd40f | |
|
627c1fb004 | |
|
f2884c57b7 | |
|
5fcac86999 | |
|
a3cc0adb84 | |
|
f34f84a3ee | |
|
0f83cd5a95 | |
|
2290900da6 | |
|
91f378c4fc | |
|
e0f6813028 | |
|
0899df60c2 | |
|
76b1b99717 | |
|
38dfc7d7b0 | |
|
1174f7f1c4 | |
|
a39945f461 | |
|
4c9ef85223 | |
|
52d6920352 | |
|
5be91b6e50 | |
|
ff10525028 | |
|
874c46705a | |
|
3d15da385f | |
|
2b664513a7 | |
|
a09a8bee84 | |
|
e7af5df69b | |
|
86960e7e20 | |
|
0cf6f247be | |
|
7f5b5a2eaf | |
|
65f4ec9659 | |
|
0fae24b5d0 | |
|
f5331cdef3 | |
|
e968656672 | |
|
446fec3b9f | |
|
f6cfc59cb6 | |
|
dc72e43f1e | |
|
d9c1c80163 | |
|
699f663df3 | |
|
3efdce5abe | |
|
38917ed6e5 | |
|
78e4f3334f | |
|
e358aa9688 | |
|
2cdf22b8c1 | |
|
dbd683a98b | |
|
377aea90b5 | |
|
9047485204 | |
|
23ff7c7202 | |
|
51913094cb | |
|
abdf47d721 | |
|
ac68ee07e5 | |
|
23fa0daf4f | |
|
a94b079cf5 | |
|
711e4385c1 | |
|
3b2378a54b | |
|
7f30863e5d | |
|
34adeaec45 | |
|
e3bccd3ac3 | |
|
0eeab91a8d | |
|
8db1be2ba8 | |
|
7a4d63adc1 | |
|
06b8d15dda | |
|
50906b0a92 | |
|
009fba0151 | |
|
386174d2a4 | |
|
d003e7f7c4 | |
|
018852a012 | |
|
77606ae906 | |
|
602071612d | |
|
f422202ec8 | |
|
bd2dbbb9cc | |
|
52232b1a14 | |
|
e0d15961e0 | |
|
e4d1b6aff2 | |
|
9941234bbe | |
|
9970a8edc4 | |
|
2cecd1890a | |
|
bd682cd5c7 | |
|
88955418b8 | |
|
0fa638fa16 | |
|
499cad666f | |
|
ae13480717 | |
|
6175dcabe6 | |
|
c45a84bd38 | |
|
3bca8da541 | |
|
50f77119ff | |
|
ac8bb6cdd9 | |
|
c9cb89f833 | |
|
b81d89aad3 | |
|
98cc439f05 | |
|
e54f6037d4 | |
|
1b598c2d78 | |
|
13f54588a6 | |
|
9fa1a428a4 | |
|
3a68148472 | |
|
82c778c7de | |
|
27eb123062 | |
|
0d407d77fe | |
|
ab946b4a1a | |
|
5a283672c4 | |
|
2ce9ff2280 | |
|
81002444d7 | |
|
8545e28d09 | |
|
e32f836485 | |
|
fc9bebbe5a | |
|
738879f79f | |
|
fa298455aa | |
|
19859b79c5 | |
|
cbe7f00a5a | |
|
fc8e7b6b95 | |
|
f82c31a6eb | |
|
4b1d524d39 | |
|
50dc946c31 | |
|
fd79166a89 | |
|
dcd878b7cc | |
|
a28c622941 | |
|
36dfdd0ad3 | |
|
16b2cf7348 | |
|
3e2b960c0e | |
|
b294a1a950 | |
|
2c522b9a66 | |
|
7116af8f66 | |
|
d8c6c8d574 | |
|
d41762df11 | |
|
bea668b87e | |
|
121c86a190 | |
|
e3758a2f29 | |
|
b9ef8a82d6 | |
|
abb366d89c | |
|
59c4643474 | |
|
1df4f29d4e | |
|
7f554d7ac6 | |
|
482c5980ab | |
|
b0150c46b8 | |
|
4388eceec0 | |
|
d3c55da89d | |
|
3409a7bad6 | |
|
1fa4cb4eb4 | |
|
dcb5b60cdf | |
|
d77342ced9 | |
|
1119fe3373 | |
|
4f39078998 | |
|
53f9c33ffa | |
|
55c1fadba6 | |
|
9fb2d246c6 | |
|
9486d1dbe4 | |
|
ed654f501a | |
|
104fcb46f9 | |
|
798714dd1c | |
|
453f9517d9 | |
|
869d564aa3 | |
|
1d5e1073ce | |
|
22d4218189 | |
|
5108e34ce8 | |
|
afb442d9c4 | |
|
f6136fcef0 | |
|
c8ad37b3f7 | |
|
81cb7367f8 | |
|
b766fb75fa | |
|
597c73ab35 | |
|
46bd78bdd9 | |
|
4982071764 | |
|
62894dea43 | |
|
a0b7769dab | |
|
2c40785c2b | |
|
4bd3ae69fe | |
|
16cb1e4698 | |
|
9ab3cce73e | |
|
4f18aa3dc8 | |
|
cf6c458fae | |
|
63a2f465d2 | |
|
9d3acfd35a | |
|
aebd5e14f9 | |
|
329274e6c9 | |
|
bcc90ac24e | |
|
1cbad472df | |
|
f963991161 | |
|
945ddc63a3 | |
|
729bbcaf90 | |
|
4311c020a0 | |
|
c40847b833 | |
|
3981b873e0 | |
|
87ad48a37f | |
|
7b423f4ea0 | |
|
efd984ef63 | |
|
108b7e2522 | |
|
681a8e8aa1 | |
|
57cda79320 | |
|
8388f3308e | |
|
c4696f6055 | |
|
34b023b1fa | |
|
e9167b4c20 | |
|
3afe999fe8 | |
|
9f37cde784 | |
|
bf9b1bdfbd | |
|
f0ed2f39e3 | |
|
c4acd0ce49 | |
|
3eaee7b8f4 | |
|
e1915bc734 | |
|
21a5da3c70 | |
|
f754533a67 | |
|
16a7801e59 | |
|
02165a8712 | |
|
8b49b91267 | |
|
dbacc27164 | |
|
1e2dd5699f | |
|
3b8b98e74c | |
|
65c889827a | |
|
09eb7d31e8 | |
|
37162510ac | |
|
fffb0bb69e | |
|
49d162fa07 | |
|
1efcaf8970 | |
|
071162a907 | |
|
c96ba7fec4 | |
|
92b0eb584f | |
|
2cb3474861 | |
|
4d93df1d58 | |
|
c9ffb415c8 | |
|
d53e56c491 | |
|
23a980e250 | |
|
5715b19326 | |
|
e70a1b74e9 | |
|
2d46b395a1 | |
|
0cf977b751 | |
|
cdd5e773e2 | |
|
88d57958f9 | |
|
85d2fc3522 | |
|
af49ce6500 | |
|
f5145de36b | |
|
4f22983739 | |
|
5b7afa6ae1 | |
|
780d9b4da4 | |
|
95bc71859b | |
|
01e815fc3b | |
|
99bfb7d36a | |
|
1188bea444 | |
|
758d1d05d2 | |
|
bac20b410d | |
|
89c233e244 | |
|
88f02bc5da | |
|
543aec85b1 | |
|
011bb737af | |
|
8e77d6f09b | |
|
b6ebdef7ef | |
|
cdc6a594e4 | |
|
dbfe26f709 | |
|
a7d5b7e34d | |
|
cb96d297ab | |
|
c2c852cee1 | |
|
79bd8466c1 | |
|
7ae5cad0cc | |
|
a43cbc83a5 | |
|
57f7758dd1 | |
|
e7d14d7b53 | |
|
a16b1822aa | |
|
127ab10995 | |
|
2db24e8759 | |
|
e5ce827d7e | |
|
b0a07fe364 | |
|
a711f0a882 | |
|
7889124217 | |
|
f9be0b3172 | |
|
374fd94fcd | |
|
c86e3f027a | |
|
973348e537 | |
|
491e5dffeb | |
|
aff5576106 | |
|
d9ae8d4b79 | |
|
93f1091b95 | |
|
6dc5d51461 | |
|
dfdf6468bc | |
|
fd6afde59c | |
|
132b72f562 | |
|
d89cce807b | |
|
482943e7a3 | |
|
0b6bc8f074 | |
|
cc8d6e9344 | |
|
97743d4ebd | |
|
db8629a6a9 | |
|
84a7e0fe30 | |
|
94a262f13a | |
|
2ea6dcd748 | |
|
254bf6baaa | |
|
1b6736c0f8 | |
|
ec9443dcdd | |
|
3949550b65 | |
|
1c822f9d6b | |
|
45f009ac90 | |
|
be1f7e0d5a | |
|
16b4ad693b | |
|
665dba187d | |
|
74b828a790 | |
|
0204103ccd | |
|
34dc1b8a32 | |
|
cb050f2c30 | |
|
79b1f930b8 | |
|
d45b1f7bfd | |
|
116f7b3a57 | |
|
06fa56bb9f | |
|
848d70fcf2 | |
|
e49261c8d6 | |
|
9dc92eb52e | |
|
42f416af1d | |
|
aa259bf343 | |
|
946916f456 | |
|
152a3e7932 | |
|
f9639a9a00 | |
|
243b0330af | |
|
a5b69124a4 | |
|
dc80a0f2f1 | |
|
a7caba19df | |
|
40b2e9bdac | |
|
40c14fed24 | |
|
1d51429f10 | |
|
9e581bf051 | |
|
b1139806f5 | |
|
7dcd3049ec | |
|
e31f1e442b | |
|
0913212ecc | |
|
02c24cc964 | |
|
46cba64e6b | |
|
86642e5b16 | |
|
3845c81229 | |
|
6f3f8d7c46 | |
|
d4afbd6de2 | |
|
e5ea621899 | |
|
f3270def9d | |
|
fb222dda89 | |
|
82fa0cea28 | |
|
21d6b503a7 | |
|
d54e02be5a | |
|
acdcebc52e | |
|
52abd335ab | |
|
6b89a9d7f9 | |
|
533a472b10 | |
|
780447122c | |
|
b5f4e72148 | |
|
2e96378f80 | |
|
b9a7e5de1f | |
|
3eb837df1b | |
|
994c39ce3c | |
|
8f7c262ea6 | |
|
44d46b70e0 | |
|
e0d7cb8341 | |
|
2c2dbb3c42 | |
|
d9732d498d | |
|
b776101cb6 | |
|
0a8db132dc | |
|
f6bf25d66f | |
|
991218a8b0 | |
|
0a45bd5ddf | |
|
55963ba824 | |
|
a2496d5d28 | |
|
c79373676f | |
|
e463d9b890 | |
|
f0a0a7dcaf | |
|
7bc0ba522c | |
|
74eeb27ad4 | |
|
dc352c58da | |
|
45c66a4f1a | |
|
ea14d1132d | |
|
dc7300db26 | |
|
59652c0aff | |
|
f0e6836195 | |
|
262d125cd4 | |
|
9070d88588 | |
|
5c69289cff | |
|
e053e1ac1c | |
|
793cd519a2 | |
|
0f7b5e5668 | |
|
1efd88d770 | |
|
a7806c43b9 | |
|
8892b3ef7e | |
|
56faafca1e | |
|
4532c0d868 | |
|
c9dd29199d | |
|
6d8d5a4fd4 | |
|
1b9c52ea07 | |
|
ae97d58bd5 | |
|
e5229b6aa2 | |
|
aaab4c6b1d | |
|
95e933e27c | |
|
15b8abf3c6 | |
|
30622eefa8 | |
|
bbe86d554f | |
|
b39c13fe1b | |
|
ea2927859b | |
|
710ef00109 | |
|
224b08a8e2 | |
|
bc9001705d | |
|
abaa36f35e | |
|
6528459645 | |
|
db19e4dcae | |
|
059ec6af1a | |
|
b0309b1b40 | |
|
320e3c85dc | |
|
ce9f58c15a | |
|
3849024816 | |
|
f0b2e6fecc | |
|
b18391b3a6 | |
|
67e6e68fb3 | |
|
4dcba4bfe1 | |
|
367a367ff9 | |
|
e752be9805 | |
|
b45c9fd9a2 | |
|
7614e2f961 | |
|
450cdbabee | |
|
090fbe6a3b | |
|
c87e785a18 | |
|
326075406a | |
|
7ddde96cc6 | |
|
0ad71bd24f | |
|
b7141a3e8b | |
|
f2ee9707cb | |
|
531c92ecab | |
|
b7fe3e484c | |
|
f8a7257b54 | |
|
cd4fca0d2e | |
|
ebf566d9be | |
|
21f5440349 | |
|
9dcc3507ed | |
|
5d0684a154 | |
|
f4a81aba83 | |
|
8813be2de2 | |
|
87fe93f6e9 | |
|
dbac37b8ac | |
|
b72eba6d4c | |
|
c1f3b6abdf | |
|
edfff0a5f6 | |
|
77cf6c490f | |
|
032dda1dd4 | |
|
5e4a6dc826 | |
|
d688334eb9 | |
|
995c5b3c63 | |
|
2ccfd79bd6 | |
|
548f2e9357 | |
|
4a1ca0badb | |
|
0a541e18ba | |
|
b5d33226c5 | |
|
0ad08ac623 | |
|
e54dfd94d2 | |
|
b752c3e46b | |
|
46f9995959 | |
|
84774018bf |
|
@ -0,0 +1 @@
|
|||
/version-info ident
|
|
@ -0,0 +1,30 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Run '...'
|
||||
2. Scroll down to '....'
|
||||
3. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots/Logs**
|
||||
* If applicable, add screenshots to help explain your problem.
|
||||
* Attach the detailed X52 daemon logs.
|
||||
|
||||
**Environment (please complete the following information):**
|
||||
- Output of `x52bugreport`
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
name: Generic issue
|
||||
about: Generic issue that isn't a bug report or feature request
|
||||
title: ''
|
||||
labels: question
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
#!/bin/bash
|
||||
# Run the build and tests
|
||||
set -e
|
||||
|
||||
./autogen.sh
|
||||
mkdir build
|
||||
cd build
|
||||
../configure
|
||||
make -j V=0
|
||||
make -j check V=0
|
||||
make -j distcheck
|
||||
|
||||
# Print bugreport output
|
||||
./x52bugreport
|
||||
|
||||
# Make sure that there are no changes to the source code
|
||||
# This may happen if the source have changed with differences to the
|
||||
# translation files and templates. Enabling this will allow us to catch
|
||||
# missing/modified translations.
|
||||
git diff --exit-code
|
|
@ -0,0 +1,9 @@
|
|||
#!/bin/bash
|
||||
# Generate Doxygen documentation
|
||||
set -e
|
||||
|
||||
./autogen.sh
|
||||
mkdir build
|
||||
cd build
|
||||
../configure --prefix=/usr --sysconfdir=/etc --localstatedir=/var
|
||||
make docs/.stamp
|
|
@ -0,0 +1,15 @@
|
|||
#!/bin/bash -x
|
||||
# Install dependencies to build and test on Ubuntu runners
|
||||
brew install \
|
||||
autoconf \
|
||||
automake \
|
||||
libtool \
|
||||
pkg-config \
|
||||
python3 \
|
||||
gettext \
|
||||
libusb \
|
||||
hidapi \
|
||||
doxygen \
|
||||
cmocka
|
||||
|
||||
exit 0
|
|
@ -0,0 +1,19 @@
|
|||
#!/bin/bash
|
||||
# Install dependencies to build and test on Ubuntu runners
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y \
|
||||
autoconf \
|
||||
automake \
|
||||
libtool \
|
||||
pkg-config \
|
||||
python3 \
|
||||
gettext \
|
||||
autopoint \
|
||||
libusb-1.0-0-dev \
|
||||
libhidapi-dev \
|
||||
libevdev-dev \
|
||||
doxygen \
|
||||
libcmocka-dev \
|
||||
faketime
|
||||
|
||||
exit 0
|
|
@ -0,0 +1,4 @@
|
|||
#!/bin/bash
|
||||
# Install dependencies to build kernel modules on Ubuntu runners
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y linux-headers-$(uname -r)
|
|
@ -0,0 +1,17 @@
|
|||
# Number of days of inactivity before an issue becomes stale
|
||||
daysUntilStale: 180
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: 14
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- pinned
|
||||
- security
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: stale
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity in the past 6 months. It will be closed within 2 weeks
|
||||
if no further activity occurs. Thank you for your contributions.
|
||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||
closeComment: false
|
|
@ -0,0 +1,40 @@
|
|||
name: Build/Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
- '!gh-pages'
|
||||
paths-ignore:
|
||||
- 'kernel_module/**'
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: "!(contains(github.event.head_commit.message, '[ci skip]') || contains(github.event.head_commit.message, '[skip ci]'))"
|
||||
name: ${{ join(matrix.*, '/') }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
continue-on-error: ${{ startsWith(matrix.os, 'macos-') || (matrix.os == 'ubuntu-24.04') }}
|
||||
env:
|
||||
CC: ${{ matrix.cc }}
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: ['ubuntu-20.04', 'ubuntu-22.04', 'ubuntu-22.04', 'macos-13', 'macos-14']
|
||||
cc: ['gcc', 'clang']
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install dependencies (Ubuntu)
|
||||
run: ./.github/scripts/install-dependencies-ubuntu.sh
|
||||
if: ${{ startsWith(matrix.os, 'ubuntu-') }}
|
||||
|
||||
- name: Install dependencies (MacOS)
|
||||
run: ./.github/scripts/install-dependencies-macos.sh
|
||||
if: ${{ startsWith(matrix.os, 'macos-') }}
|
||||
|
||||
- name: Build and Test
|
||||
run: ./.github/scripts/build-and-test.sh
|
|
@ -0,0 +1,55 @@
|
|||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [master]
|
||||
schedule:
|
||||
- cron: '30 7 * * 1,3,5'
|
||||
|
||||
jobs:
|
||||
analyse:
|
||||
name: Analyse
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
fetch-depth: 2
|
||||
|
||||
- name: Install dependencies
|
||||
run: ./.github/scripts/install-dependencies-ubuntu.sh
|
||||
|
||||
# If this run was triggered by a pull request event, then checkout
|
||||
# the head of the pull request instead of the merge commit.
|
||||
- run: git checkout HEAD^2
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
# Override language selection by uncommenting this and choosing your languages
|
||||
# with:
|
||||
# languages: go, javascript, csharp, python, cpp, java
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
|
@ -0,0 +1,30 @@
|
|||
name: Doxygen
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
|
||||
jobs:
|
||||
doxygen:
|
||||
if: "!(contains(github.event.head_commit.message, '[doxy skip]') || contains(github.event.head_commit.message, '[skip doxy]'))"
|
||||
runs-on: 'ubuntu-latest'
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install dependencies
|
||||
run: ./.github/scripts/install-dependencies-ubuntu.sh
|
||||
|
||||
- name: Generate Doxygen documentation
|
||||
run: ./.github/scripts/build-doxygen.sh
|
||||
|
||||
- name: Dump generated files
|
||||
run: find ./build -type f -print
|
||||
|
||||
- name: Deploy generated documentation to Github pages
|
||||
uses: peaceiris/actions-gh-pages@v4
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./build/docs/html
|
|
@ -0,0 +1,31 @@
|
|||
name: Kernel Module
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ '*' ]
|
||||
paths:
|
||||
- 'kernel_module/**'
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: "!(contains(github.event.head_commit.message, '[ci skip]') || contains(github.event.head_commit.message, '[skip ci]'))"
|
||||
name: ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: ['ubuntu-20.04', 'ubuntu-22.04']
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install kernel dependencies
|
||||
run: ./.github/scripts/install-kernel-dependencies.sh
|
||||
|
||||
- name: Build kernel module
|
||||
run: |
|
||||
cd kernel_module
|
||||
make
|
|
@ -0,0 +1,48 @@
|
|||
name: Create Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Upload Release Asset
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install dependencies
|
||||
run: ./.github/scripts/install-dependencies-ubuntu.sh
|
||||
|
||||
- name: Build project
|
||||
run: ./.github/scripts/build-and-test.sh
|
||||
|
||||
- name: Find release tarball
|
||||
id: find_release
|
||||
run: |
|
||||
echo "::set-output name=path::$(find $PWD -name 'libx52*.tar*')"
|
||||
echo "::set-output name=asset::$(find . -name 'libx52*.tar*' -exec basename {} \; | sed 's/libx52-\(.*\)\.tar/libx52_\1.orig.tar/')"
|
||||
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: Release ${{ github.ref }}
|
||||
draft: false
|
||||
prerelease: false
|
||||
|
||||
- name: Upload Release Tarball
|
||||
id: upload-release-tarball
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ${{ steps.find_release.outputs.path }}
|
||||
asset_name: ${{ steps.find_release.outputs.asset }}
|
||||
asset_content_type: application/gzip
|
|
@ -1,10 +1,21 @@
|
|||
# Compiled object files
|
||||
*.ko
|
||||
*.o
|
||||
*.mod.*
|
||||
*.mod*
|
||||
|
||||
# Compiled executables
|
||||
# Generated objects (source, executables, tarballs, etc.)
|
||||
a.out
|
||||
x52cli*
|
||||
x52test*
|
||||
x52evtest*
|
||||
libx52/test_*
|
||||
libx52test*
|
||||
libx52util/util_char_map.c
|
||||
udev/*.rules
|
||||
x52d*
|
||||
!daemon/x52d*.*
|
||||
test-*
|
||||
libx52-*.tar.gz
|
||||
|
||||
# Module files
|
||||
modules.order
|
||||
|
@ -14,3 +25,53 @@ Module.symvers
|
|||
|
||||
# Vim swap files
|
||||
.*.swp
|
||||
|
||||
# Autotools objects
|
||||
.deps
|
||||
.dirstamp
|
||||
.libs
|
||||
ar-lib
|
||||
autom4te.cache
|
||||
m4
|
||||
compile
|
||||
config.*
|
||||
configure
|
||||
depcomp
|
||||
install-sh
|
||||
libtool
|
||||
ltmain.sh
|
||||
missing
|
||||
Makefile
|
||||
Makefile.in
|
||||
*.la
|
||||
*.lo
|
||||
*.m4
|
||||
stamp-h1
|
||||
tap-driver.sh
|
||||
test-driver
|
||||
tests/test-suite.log
|
||||
tests/**/*.log
|
||||
tests/**/*.trs
|
||||
*.pc
|
||||
|
||||
# Autotools Gettext objects
|
||||
po/Makevars.template
|
||||
po/*.in
|
||||
po/*.sin
|
||||
po/*.sed
|
||||
po/*.header
|
||||
po/Rules-quot
|
||||
ABOUT-NLS
|
||||
po/*.gmo
|
||||
po/*.mo
|
||||
po/POTFILES
|
||||
po/stamp-po
|
||||
|
||||
# Doxygen files
|
||||
Doxyfile
|
||||
docs/.stamp
|
||||
docs/html
|
||||
docs/man
|
||||
|
||||
# Build directory
|
||||
/build/
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
Contribution Instructions
|
||||
=========================
|
||||
|
||||
This project welcomes contributions. Contributions are managed through GitHub
|
||||
pull requests.
|
||||
|
||||
# Issue guidelines
|
||||
|
||||
If you find an issue with any code in this project, feel free to open an issue.
|
||||
Templates exist for bug reports and feature requests.
|
||||
|
||||
# Pull request guidelines
|
||||
|
||||
* Fork this repository.
|
||||
* Create a branch off master, and make your commits in that branch.
|
||||
* Test your changes locally before raising a pull request.
|
||||
|
||||
# Commit guidelines
|
||||
|
||||
* Isolate each commit to a single component/folder as far as possible
|
||||
* Use a standard message template. I follow the template from [this
|
||||
link](https://codeinthehole.com/tips/a-useful-template-for-commit-messages/)
|
||||
* Ensure that you have any necessary tests included to test your changes.
|
||||
|
||||
## Additional commit requirements
|
||||
|
||||
* Commits should have a `Signed-off-by` line. You can use `git commit -s` to
|
||||
automatically append this to your message.
|
||||
* Any generated objects should be included in `.gitignore`.
|
||||
* A commit should be self-contained, i.e., if I check out any commit in a clean
|
||||
workspace, I should be able to run `./.github/scripts/build-and-test.sh` and
|
||||
not encounter any failures.
|
||||
* Any additional dependencies should be called out in `INSTALL.md`
|
||||
|
||||
# Contribution License
|
||||
|
||||
The project is licensed under GPLv2, with a linking exception. Your
|
||||
contributions will also be licensed the same way. However, you maintain the
|
||||
copyright to your contributions.
|
||||
|
||||
If this is your first contribution to this project, you may update the `AUTHORS`
|
||||
file with your full name. Please keep the change to this file in a separate
|
||||
commit.
|
||||
|
|
@ -0,0 +1,194 @@
|
|||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based upon [Keep a Changelog].
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.3.2] - 2024-06-09
|
||||
### Added
|
||||
- Updated bug report utility to add details about build host details and
|
||||
compiler information.
|
||||
|
||||
### Fixed
|
||||
- Updated syntax check for calloc calls. See
|
||||
[#52](https://github.com/nirenjan/libx52/issues/52)
|
||||
- Fixed a tooling bug where running make check on a system without cmocka
|
||||
library installed would fail during daemon testing.
|
||||
- Cleaned up daemon protocol documentation
|
||||
|
||||
### Changed
|
||||
- Moved socket code around to make it easier to reuse the communication logic
|
||||
out in both client(s) and server.
|
||||
|
||||
## 0.3.1 - 2024-06-08
|
||||
|
||||
**Important:** Tag 0.3.1 has a bad Version file and should not be used. This has
|
||||
been superseded by 0.3.2 with corrected metadata. The changes from the previous
|
||||
release are the same.
|
||||
|
||||
## [0.3.0] - 2022-12-25
|
||||
### Added
|
||||
- Bug report utility to make it easier to gather system and build information
|
||||
when reporting issues.
|
||||
- Communication infrastructure to communicate with the daemon. This includes the
|
||||
`x52ctl` utility which can be used either interactively or non-interactively
|
||||
from a separate program.
|
||||
- Links to prebuilt packages in Ubuntu PPA and Arch Linux AUR
|
||||
- Ability to change mouse scroll direction. See
|
||||
[#45](https://github.com/nirenjan/libx52/issues/45)
|
||||
|
||||
### Changed
|
||||
- Renamed project from `x52pro-linux` to `libx52`
|
||||
|
||||
### Fixed
|
||||
- Removed dependency on `rsync` during `make install`
|
||||
- Reduced default logging level of daemon to error only. See
|
||||
[#38](https://github.com/nirenjan/libx52/issues/38)
|
||||
- Fixed daemon crash when disconnecting/reconnecting the joystick. See
|
||||
[#43](https://github.com/nirenjan/libx52/issues/43)
|
||||
|
||||
## [0.2.3] - 2021-09-20
|
||||
### Added
|
||||
- CI for macOS 11 (Big Sur)
|
||||
- Virtual mouse driver (on Linux only)
|
||||
|
||||
### Fixed
|
||||
- Device erratic behavior when running daemon. See
|
||||
[#33](https://github.com/nirenjan/libx52/issues/33).
|
||||
- `make install` on OpenSUSE Tumbleweed. See
|
||||
[#35](https://github.com/nirenjan/libx52/issues/35).
|
||||
|
||||
## [0.2.2] - 2021-09-03
|
||||
### Added
|
||||
- IO library to read and parse events from a supported joystick.
|
||||
- Event test utility which displays the events similar to evtest.
|
||||
- Daemon to control and update the X52 joystick.
|
||||
- Import pinelog library for daemon logging.
|
||||
- Import [inih](https://github.com/benhoyt/inih) library sources for daemon
|
||||
configuration parsing.
|
||||
|
||||
### Changed
|
||||
- Linux kernel driver to correctly handle the X52/X52 Pro. This is not required
|
||||
for users running kernels with at least the following versions:
|
||||
- 5.9+
|
||||
- 5.8.10+
|
||||
- 5.4.66+
|
||||
- 4.19.146+
|
||||
- Make udev rules customizable at build time, so that the right input group can
|
||||
be used in the actual rules file. This allows systems such as openSUSE which
|
||||
use `input` as the group for input devices to behave the same as Ubuntu and
|
||||
other similar systems.
|
||||
- Code layout changed to improve parallel builds.
|
||||
- x52cli tests modified to use cmocka tests.
|
||||
|
||||
## [0.2.1] - 2020-06-28
|
||||
### Added
|
||||
- Connect/Disconnect methods in libx52. These allow for dynamically connecting
|
||||
or disconnecting from a supported joystick without having to reinitialize the
|
||||
library.
|
||||
- Internationalization for the following:
|
||||
* libx52
|
||||
* x52test
|
||||
- Doxygen generation of HTML documentation for libx52 methods.
|
||||
- Tests for libx52 that run on all supported platforms.
|
||||
|
||||
### Changed
|
||||
- libx52_init no longer fails when a supported joystick is not connected.
|
||||
- Tests now use [TAP].
|
||||
- Python build scripts now use Python 3.
|
||||
|
||||
### Fixed
|
||||
- Error reporting in x52cli and x52test commands.
|
||||
- Handling of very large time_t values in `libx52_set_clock`
|
||||
- Secondary and tertiary clock setting when primary clock is set to local time
|
||||
and local timezone is observing daylight savings time (summer time). See
|
||||
[#20](https://github.com/nirenjan/libx52/issues/20).
|
||||
|
||||
## [0.2.0] - 2020-04-14
|
||||
### Changed
|
||||
- `libx52_init` now returns a `libx52_error_code`, and returns the
|
||||
`libx52_device` pointer in an output parameter.
|
||||
- All libx52 APIs now return a `libx52_error_code` indicating the error.
|
||||
- libx52 now checks the version of libusb and calls the appropriate method
|
||||
to set logging level.
|
||||
- x52test has an option to not sleep between consecutive calls to the libx52
|
||||
APIs.
|
||||
|
||||
### Fixed
|
||||
- `libx52_write_time` handling of large timezone offsets.
|
||||
|
||||
## [0.1.2] - 2017-08-17
|
||||
### Added
|
||||
- Autotools based unit tests - tests run on Linux only
|
||||
- libusb mock library for use by test programs
|
||||
- License file and usage clarification
|
||||
- Automatic builds on Ubuntu Trusty (14.04) with both GCC and clang on Travis
|
||||
- Enhanced documentation for libx52
|
||||
- Support for X52 (non-Pro) version
|
||||
- New raw time and date APIs for libx52
|
||||
- Support raw time and date commands in x52cli
|
||||
- Unicode translation points for halfwidth CJK and Katakana symbols
|
||||
|
||||
### Changed
|
||||
- Update Python character map generator to comply with PEP-8 guidelines
|
||||
|
||||
### Fixed
|
||||
- Compilation on OSX
|
||||
|
||||
## [0.1.1] - 2016-05-06
|
||||
### Added
|
||||
- Manpage for x52cli
|
||||
- Manpages for libx52 in RONN format
|
||||
- Unicode translation points for Latin, Greek and mathematical symbols
|
||||
- Travis-CI based automatic compilation
|
||||
|
||||
### Changed
|
||||
- libx52 clock API will return -EAGAIN if no update is needed.
|
||||
- x52test accepts a list of tests to run, defaulting to ALL
|
||||
- UTF-8 parser rewritten in Python
|
||||
|
||||
## [0.1.0] - 2015-12-09
|
||||
### Added
|
||||
- Support for semantic LED names instead of numbers in libx52
|
||||
- Simpler API to control clocks
|
||||
- Add documentation for X52 design and USB interface
|
||||
- Add CLI application to interface with libx52
|
||||
- Add test application to test all aspects of libx52
|
||||
- Add API to convert UTF-8 string to X52 character map
|
||||
|
||||
### Changed
|
||||
- Migrate project to autotools
|
||||
|
||||
### Deprecated
|
||||
- Mark kernel driver as proof-of-concept and unsuitable for production
|
||||
|
||||
## [0.0.2] - 2014-10-18
|
||||
### Added
|
||||
- Proposed design documentation for MFD pages and input mapping
|
||||
|
||||
### Fixed
|
||||
- Kernel module compilation on Linux v3.5 and later
|
||||
|
||||
## [0.0.1] - 2012-10-25
|
||||
### Added
|
||||
- Kernel module for Saitek X52 Pro Joystick
|
||||
- First release of userspace application
|
||||
|
||||
|
||||
[Keep a Changelog]: http://keepachangelog.com/en/1.0.0/
|
||||
[Semantic Versioning]: http://semver.org/spec/v2.0.0.html
|
||||
[TAP]: https://testanything.org
|
||||
[Unreleased]: https://github.com/nirenjan/libx52/compare/v0.3.2...HEAD
|
||||
[0.3.2]: https://github.com/nirenjan/libx52/compare/v0.3.0...v0.3.2
|
||||
[0.3.0]: https://github.com/nirenjan/libx52/compare/v0.2.3...v0.3.0
|
||||
[0.2.3]: https://github.com/nirenjan/libx52/compare/v0.2.2...v0.2.3
|
||||
[0.2.2]: https://github.com/nirenjan/libx52/compare/v0.2.1...v0.2.2
|
||||
[0.2.1]: https://github.com/nirenjan/libx52/compare/v0.2.0...v0.2.1
|
||||
[0.2.0]: https://github.com/nirenjan/libx52/compare/v0.1.2...v0.2.0
|
||||
[0.1.2]: https://github.com/nirenjan/libx52/compare/v0.1.1...v0.1.2
|
||||
[0.1.1]: https://github.com/nirenjan/libx52/compare/v0.1.0...v0.1.1
|
||||
[0.1.0]: https://github.com/nirenjan/libx52/compare/v0.0.2...v0.1.0
|
||||
[0.0.2]: https://github.com/nirenjan/libx52/compare/v0.0.1...v0.0.2
|
||||
[0.0.1]: https://github.com/nirenjan/libx52/releases/tag/v0.0.1
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,227 @@
|
|||
<doxygenlayout version="1.0">
|
||||
<!-- Generated by doxygen 1.8.17 -->
|
||||
<!-- Navigation index tabs for HTML output -->
|
||||
<navindex>
|
||||
<tab type="mainpage" visible="yes" title=""/>
|
||||
<tab type="pages" visible="yes" title="" intro=""/>
|
||||
<tab type="modules" visible="yes" title="" intro=""/>
|
||||
<tab type="namespaces" visible="yes" title="">
|
||||
<tab type="namespacelist" visible="yes" title="" intro=""/>
|
||||
<tab type="namespacemembers" visible="yes" title="" intro=""/>
|
||||
</tab>
|
||||
<tab type="interfaces" visible="yes" title="">
|
||||
<tab type="interfacelist" visible="yes" title="" intro=""/>
|
||||
<tab type="interfaceindex" visible="$ALPHABETICAL_INDEX" title=""/>
|
||||
<tab type="interfacehierarchy" visible="yes" title="" intro=""/>
|
||||
</tab>
|
||||
<tab type="classes" visible="yes" title="">
|
||||
<tab type="classlist" visible="yes" title="" intro=""/>
|
||||
<tab type="classindex" visible="$ALPHABETICAL_INDEX" title=""/>
|
||||
<tab type="hierarchy" visible="yes" title="" intro=""/>
|
||||
<tab type="classmembers" visible="yes" title="" intro=""/>
|
||||
</tab>
|
||||
<tab type="structs" visible="yes" title="">
|
||||
<tab type="structlist" visible="yes" title="" intro=""/>
|
||||
<tab type="structindex" visible="$ALPHABETICAL_INDEX" title=""/>
|
||||
</tab>
|
||||
<tab type="exceptions" visible="yes" title="">
|
||||
<tab type="exceptionlist" visible="yes" title="" intro=""/>
|
||||
<tab type="exceptionindex" visible="$ALPHABETICAL_INDEX" title=""/>
|
||||
<tab type="exceptionhierarchy" visible="yes" title="" intro=""/>
|
||||
</tab>
|
||||
<tab type="files" visible="yes" title="">
|
||||
<tab type="filelist" visible="yes" title="" intro=""/>
|
||||
<tab type="globals" visible="yes" title="" intro=""/>
|
||||
</tab>
|
||||
<tab type="examples" visible="yes" title="" intro=""/>
|
||||
<tab type="user" url="https://github.com/nirenjan/libx52" title="View on GitHub" />
|
||||
</navindex>
|
||||
|
||||
<!-- Layout definition for a class page -->
|
||||
<class>
|
||||
<briefdescription visible="yes"/>
|
||||
<includes visible="$SHOW_INCLUDE_FILES"/>
|
||||
<inheritancegraph visible="$CLASS_GRAPH"/>
|
||||
<collaborationgraph visible="$COLLABORATION_GRAPH"/>
|
||||
<memberdecl>
|
||||
<nestedclasses visible="yes" title=""/>
|
||||
<publictypes title=""/>
|
||||
<services title=""/>
|
||||
<interfaces title=""/>
|
||||
<publicslots title=""/>
|
||||
<signals title=""/>
|
||||
<publicmethods title=""/>
|
||||
<publicstaticmethods title=""/>
|
||||
<publicattributes title=""/>
|
||||
<publicstaticattributes title=""/>
|
||||
<protectedtypes title=""/>
|
||||
<protectedslots title=""/>
|
||||
<protectedmethods title=""/>
|
||||
<protectedstaticmethods title=""/>
|
||||
<protectedattributes title=""/>
|
||||
<protectedstaticattributes title=""/>
|
||||
<packagetypes title=""/>
|
||||
<packagemethods title=""/>
|
||||
<packagestaticmethods title=""/>
|
||||
<packageattributes title=""/>
|
||||
<packagestaticattributes title=""/>
|
||||
<properties title=""/>
|
||||
<events title=""/>
|
||||
<privatetypes title=""/>
|
||||
<privateslots title=""/>
|
||||
<privatemethods title=""/>
|
||||
<privatestaticmethods title=""/>
|
||||
<privateattributes title=""/>
|
||||
<privatestaticattributes title=""/>
|
||||
<friends title=""/>
|
||||
<related title="" subtitle=""/>
|
||||
<membergroups visible="yes"/>
|
||||
</memberdecl>
|
||||
<detaileddescription title=""/>
|
||||
<memberdef>
|
||||
<inlineclasses title=""/>
|
||||
<typedefs title=""/>
|
||||
<enums title=""/>
|
||||
<services title=""/>
|
||||
<interfaces title=""/>
|
||||
<constructors title=""/>
|
||||
<functions title=""/>
|
||||
<related title=""/>
|
||||
<variables title=""/>
|
||||
<properties title=""/>
|
||||
<events title=""/>
|
||||
</memberdef>
|
||||
<allmemberslink visible="yes"/>
|
||||
<usedfiles visible="$SHOW_USED_FILES"/>
|
||||
<authorsection visible="yes"/>
|
||||
</class>
|
||||
|
||||
<!-- Layout definition for a namespace page -->
|
||||
<namespace>
|
||||
<briefdescription visible="yes"/>
|
||||
<memberdecl>
|
||||
<nestednamespaces visible="yes" title=""/>
|
||||
<constantgroups visible="yes" title=""/>
|
||||
<interfaces visible="yes" title=""/>
|
||||
<classes visible="yes" title=""/>
|
||||
<structs visible="yes" title=""/>
|
||||
<exceptions visible="yes" title=""/>
|
||||
<typedefs title=""/>
|
||||
<sequences title=""/>
|
||||
<dictionaries title=""/>
|
||||
<enums title=""/>
|
||||
<functions title=""/>
|
||||
<variables title=""/>
|
||||
<membergroups visible="yes"/>
|
||||
</memberdecl>
|
||||
<detaileddescription title=""/>
|
||||
<memberdef>
|
||||
<inlineclasses title=""/>
|
||||
<typedefs title=""/>
|
||||
<sequences title=""/>
|
||||
<dictionaries title=""/>
|
||||
<enums title=""/>
|
||||
<functions title=""/>
|
||||
<variables title=""/>
|
||||
</memberdef>
|
||||
<authorsection visible="yes"/>
|
||||
</namespace>
|
||||
|
||||
<!-- Layout definition for a file page -->
|
||||
<file>
|
||||
<briefdescription visible="yes"/>
|
||||
<includes visible="$SHOW_INCLUDE_FILES"/>
|
||||
<includegraph visible="$INCLUDE_GRAPH"/>
|
||||
<includedbygraph visible="$INCLUDED_BY_GRAPH"/>
|
||||
<sourcelink visible="yes"/>
|
||||
<memberdecl>
|
||||
<interfaces visible="yes" title=""/>
|
||||
<classes visible="yes" title=""/>
|
||||
<structs visible="yes" title=""/>
|
||||
<exceptions visible="yes" title=""/>
|
||||
<namespaces visible="yes" title=""/>
|
||||
<constantgroups visible="yes" title=""/>
|
||||
<defines title=""/>
|
||||
<typedefs title=""/>
|
||||
<sequences title=""/>
|
||||
<dictionaries title=""/>
|
||||
<enums title=""/>
|
||||
<functions title=""/>
|
||||
<variables title=""/>
|
||||
<membergroups visible="yes"/>
|
||||
</memberdecl>
|
||||
<detaileddescription title=""/>
|
||||
<memberdef>
|
||||
<inlineclasses title=""/>
|
||||
<defines title=""/>
|
||||
<typedefs title=""/>
|
||||
<sequences title=""/>
|
||||
<dictionaries title=""/>
|
||||
<enums title=""/>
|
||||
<functions title=""/>
|
||||
<variables title=""/>
|
||||
</memberdef>
|
||||
<authorsection/>
|
||||
</file>
|
||||
|
||||
<!-- Layout definition for a group page -->
|
||||
<group>
|
||||
<briefdescription visible="yes"/>
|
||||
<groupgraph visible="$GROUP_GRAPHS"/>
|
||||
<memberdecl>
|
||||
<nestedgroups visible="yes" title=""/>
|
||||
<dirs visible="yes" title=""/>
|
||||
<files visible="yes" title=""/>
|
||||
<namespaces visible="yes" title=""/>
|
||||
<classes visible="yes" title=""/>
|
||||
<defines title=""/>
|
||||
<typedefs title=""/>
|
||||
<sequences title=""/>
|
||||
<dictionaries title=""/>
|
||||
<enums title=""/>
|
||||
<enumvalues title=""/>
|
||||
<functions title=""/>
|
||||
<variables title=""/>
|
||||
<signals title=""/>
|
||||
<publicslots title=""/>
|
||||
<protectedslots title=""/>
|
||||
<privateslots title=""/>
|
||||
<events title=""/>
|
||||
<properties title=""/>
|
||||
<friends title=""/>
|
||||
<membergroups visible="yes"/>
|
||||
</memberdecl>
|
||||
<detaileddescription title=""/>
|
||||
<memberdef>
|
||||
<pagedocs/>
|
||||
<inlineclasses title=""/>
|
||||
<defines title=""/>
|
||||
<typedefs title=""/>
|
||||
<sequences title=""/>
|
||||
<dictionaries title=""/>
|
||||
<enums title=""/>
|
||||
<enumvalues title=""/>
|
||||
<functions title=""/>
|
||||
<variables title=""/>
|
||||
<signals title=""/>
|
||||
<publicslots title=""/>
|
||||
<protectedslots title=""/>
|
||||
<privateslots title=""/>
|
||||
<events title=""/>
|
||||
<properties title=""/>
|
||||
<friends title=""/>
|
||||
</memberdef>
|
||||
<authorsection visible="yes"/>
|
||||
</group>
|
||||
|
||||
<!-- Layout definition for a directory page -->
|
||||
<directory>
|
||||
<briefdescription visible="yes"/>
|
||||
<directorygraph visible="yes"/>
|
||||
<memberdecl>
|
||||
<dirs visible="yes"/>
|
||||
<files visible="yes"/>
|
||||
</memberdecl>
|
||||
<detaileddescription title=""/>
|
||||
</directory>
|
||||
</doxygenlayout>
|
|
@ -0,0 +1,100 @@
|
|||
Installation instructions for libx52
|
||||
==========================================
|
||||
|
||||
Build has been tested on the following operating systems (x86-64 only):
|
||||
|
||||
* Ubuntu 20.04 LTS
|
||||
* Ubuntu 22.04 LTS
|
||||
* macOS Big Sur 11
|
||||
* macOS Monterey 12
|
||||
|
||||
# Prerequisites
|
||||
|
||||
## Required Packages
|
||||
|
||||
* automake
|
||||
* autoconf
|
||||
* autopoint
|
||||
* gettext
|
||||
* hidapi + headers
|
||||
* libtool
|
||||
* libusb-1.0 + headers
|
||||
* libevdev + headers (on Linux)
|
||||
* pkg-config
|
||||
* python3 (3.6 or greater)
|
||||
* git (not required for builds, but necessary to clone the repository)
|
||||
|
||||
### Installation instructions
|
||||
|
||||
| Platform | Install instructions |
|
||||
| -------- | -------------------- |
|
||||
| Ubuntu | `sudo apt-get install automake autoconf gettext autopoint libhidapi-dev libevdev-dev libtool libusb-1.0-0-dev pkg-config python3 git` |
|
||||
| MacOS + Homebrew | `brew install automake autoconf gettext hidapi libtool libusb pkg-config python3 git` |
|
||||
| Arch Linux | `pacman -S base-devel libusb hidapi libevdev python git` |
|
||||
| Fedora | `sudo dnf install autoconf automake gettext-devel findutils libtool hidapi-devel libusb-devel libevdev-devel pkg-config python3 git` |
|
||||
|
||||
## Optional Packages
|
||||
|
||||
* doxygen - to generate HTML documentation and man pages
|
||||
* libcmocka (1.1 or greater) + headers - to run unit tests
|
||||
|
||||
# Installation Instructions
|
||||
|
||||
1. Clone the repository
|
||||
```
|
||||
git clone https://github.com/nirenjan/libx52.git
|
||||
```
|
||||
|
||||
2. Run autogen.sh
|
||||
```
|
||||
cd ./libx52
|
||||
./autogen.sh
|
||||
```
|
||||
|
||||
3. Run the following commands:
|
||||
```
|
||||
./configure --prefix=/usr --localstatedir=/var --sysconfdir=/etc
|
||||
make && sudo make install
|
||||
```
|
||||
|
||||
You may want to remove or edit the `--prefix=/usr` option, most users prefer
|
||||
non-distro binaries in `/usr/local` (default without `--prefix`) or `/opt`.
|
||||
|
||||
## Configuration options
|
||||
|
||||
### udev
|
||||
|
||||
The configuration system should automatically detect the udev rules directory,
|
||||
but you can override it by using the following argument to `configure`:
|
||||
|
||||
```
|
||||
--with-udevrulesdir=/path/to/udev/rules.d
|
||||
```
|
||||
|
||||
### Input group
|
||||
|
||||
The udev rules that are installed provide read/write access to members of the
|
||||
input devices group. This defaults to `plugdev`, but can be modified using
|
||||
the following argument to `configure`:
|
||||
|
||||
```
|
||||
--with-input-group=group
|
||||
```
|
||||
|
||||
### Systemd support
|
||||
|
||||
The X52 daemon can run either as a foreground process, or it can daemonize
|
||||
itself to run in the background. Typical deployments with systemd will have it
|
||||
run in the foreground, and disable timestamps in the logs, since those are
|
||||
inserted automatically by journald.
|
||||
|
||||
Systemd support is enabled by default, but can be disabled with the
|
||||
`--disable-systemd` argument to `configure`
|
||||
|
||||
It is also possible to configure the directory in which the service file is
|
||||
installed with the following option. This is ignored if you have specified
|
||||
`--disable-systemd`.
|
||||
|
||||
```
|
||||
--with-systemdsystemunitdir=/path/to/systemd/system
|
||||
```
|
|
@ -337,3 +337,23 @@ proprietary programs. If your program is a subroutine library, you may
|
|||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
||||
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
X52Pro-Linux is licensed under the terms of the GNU General Public License with
|
||||
the following clarification and special exception.
|
||||
|
||||
Linking this module statically or dynamically with other modules is making a
|
||||
combined work based on this module. Thus, the terms and conditions of the GNU
|
||||
General Public License cover the whole combination.
|
||||
|
||||
As a special exception, the copyright holders of this module give you
|
||||
permission to link this module with independent modules to produce an
|
||||
executable, regardless of the license terms of these independent modules, and
|
||||
to copy and distribute the resulting executable under terms of your choice,
|
||||
provided that you also meet, for each linked independent module, the terms and
|
||||
conditions of the license of that module. An independent module is a module
|
||||
which is not derived from or based on this module. If you modify this module,
|
||||
you may extend this exception to your version of the module, but you are not
|
||||
obliged to do so. If you do not wish to do so, delete this exception statement
|
||||
from your version.
|
|
@ -0,0 +1,110 @@
|
|||
# Top level Automake for libx52
|
||||
#
|
||||
# Copyright (C) 2012-2018 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
|
||||
ACLOCAL_AMFLAGS = -I m4
|
||||
|
||||
# Build any support libraries first
|
||||
SUBDIRS = lib
|
||||
|
||||
if USE_NLS
|
||||
SUBDIRS += po
|
||||
endif
|
||||
|
||||
#######################################################################
|
||||
# Defaults
|
||||
#######################################################################
|
||||
bin_PROGRAMS =
|
||||
check_PROGRAMS =
|
||||
lib_LTLIBRARIES =
|
||||
check_LTLIBRARIES =
|
||||
pkgconfig_DATA =
|
||||
TESTS =
|
||||
EXTRA_DIST =
|
||||
CLEANFILES =
|
||||
BUILT_SOURCES =
|
||||
|
||||
x52includedir = $(includedir)/libx52
|
||||
x52include_HEADERS =
|
||||
|
||||
LOG_DRIVER = env AM_TAP_AWK='$(AWK)' $(SHELL) $(top_srcdir)/tap-driver.sh
|
||||
|
||||
########################################################################
|
||||
# Get build version
|
||||
########################################################################
|
||||
BUILT_SOURCES += version-info.h
|
||||
CLEANFILES += version-info.h
|
||||
|
||||
version-info.h: ${top_srcdir}/version-info
|
||||
CC=${CC} ${top_srcdir}/version-info ${top_srcdir} >$@
|
||||
|
||||
########################################################################
|
||||
# Include automake stubs
|
||||
########################################################################
|
||||
include libx52/Makefile.am
|
||||
include libx52util/Makefile.am
|
||||
include libx52io/Makefile.am
|
||||
include libusbx52/Makefile.am
|
||||
|
||||
include cli/Makefile.am
|
||||
include joytest/Makefile.am
|
||||
include evtest/Makefile.am
|
||||
include daemon/Makefile.am
|
||||
include udev/Makefile.am
|
||||
|
||||
include bugreport/Makefile.am
|
||||
include docs/Makefile.am
|
||||
|
||||
#######################################################################
|
||||
# Doxygen support
|
||||
#######################################################################
|
||||
if HAVE_DOXYGEN
|
||||
DXGEN = $(DXGEN_@AM_V@)
|
||||
DXGEN_ = $(DXGEN_@AM_DEFAULT_V@)
|
||||
DXGEN_0 = @printf " DXGEN $<\n";
|
||||
|
||||
SYSCONFDIR=@sysconfdir@
|
||||
LOCALSTATEDIR=@localstatedir@
|
||||
export SYSCONFDIR
|
||||
export LOCALSTATEDIR
|
||||
docs/.stamp: Doxyfile
|
||||
$(DXGEN)$(DOXYGEN) $<
|
||||
$(AM_V_at)touch $@
|
||||
|
||||
all-local: docs/.stamp
|
||||
clean-local:
|
||||
rm -rf $(top_builddir)/docs
|
||||
|
||||
man1_MANS = docs/man/man1/x52cli.1 docs/man/man1/x52bugreport.1
|
||||
$(man1_MANS): docs/.stamp
|
||||
|
||||
# Install Doxygen generated HTML documentation and manpages
|
||||
install-data-local:
|
||||
$(INSTALL) -d $(DESTDIR)$(docdir)
|
||||
cp -R -P $(top_builddir)/docs/html $(DESTDIR)$(docdir)
|
||||
|
||||
uninstall-local:
|
||||
rm -rf $(DESTDIR)$(docdir)
|
||||
|
||||
endif
|
||||
|
||||
# Extra files that need to be in the distribution
|
||||
EXTRA_DIST += \
|
||||
ABOUT-NLS \
|
||||
AUTHORS \
|
||||
ChangeLog.md \
|
||||
CONTRIBUTING.md \
|
||||
Doxyfile.in \
|
||||
DoxygenLayout.xml \
|
||||
INSTALL.md \
|
||||
LICENSE \
|
||||
README.md \
|
||||
config.rpath \
|
||||
version-info \
|
||||
Version \
|
||||
gettext.h \
|
||||
usb-ids.h \
|
||||
po/README.md
|
||||
|
47
README.md
47
README.md
|
@ -1,6 +1,10 @@
|
|||
Saitek X52Pro joystick driver for Linux
|
||||
=======================================
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
This project adds a new driver for the Saitek/MadCatz X52 Pro flight
|
||||
control system. The X52 pro is a HOTAS (hand on throttle and stick)
|
||||
with 7 axes, 39 buttons, 1 hat and 1 thumbstick and a multi-function
|
||||
|
@ -8,16 +12,37 @@ display which is programmable.
|
|||
|
||||
Currently, only Windows drivers are available from Saitek PLC, which
|
||||
led me to develop a new Linux driver which can program the MFD and
|
||||
the individual LEDs on the joystick. Although the standard usbhid
|
||||
driver is capable of reading the joystick, it is not sufficient to
|
||||
really utilize all the capabilities of this system.
|
||||
the individual LEDs on the joystick. The standard usbhid driver is
|
||||
capable of reading the joystick, but it cannot control the MFD or LEDs.
|
||||
|
||||
This project is currently a work-in-progress. However a high level
|
||||
outline of the current objectives are listed below:
|
||||
Most of the extra functionality can be handled from userspace. See
|
||||
the individual folders for README information.
|
||||
|
||||
* Write a kernel module and export sysfs interfaces to act as a
|
||||
driver.
|
||||
* Write a userspace program that can configure the kernel module
|
||||
and create custom button mappings to keyboard or mouse events.
|
||||
* Add interrupt handling and export a /dev/input/jsX interface.
|
||||
* Allow userspace programs to register callbacks on MFD button events.
|
||||
**Note:** This repository currently only provides commandline interfaces to
|
||||
control the MFD and LEDs. If you are not comfortable working in the commandline,
|
||||
then the [gx52](https://gitlab.com/leinardi/gx52) project might be a better fit
|
||||
for your needs as it provides a graphical interface to control the MFD and LEDs.
|
||||
|
||||
# Installing released versions
|
||||
|
||||
Beginning from version v0.2.3, prebuilt packages are available on Ubuntu PPA and
|
||||
the Arch User Repository.
|
||||
|
||||
## Ubuntu
|
||||
|
||||
This project has been released as a PPA on Ubuntu. To install the package, run
|
||||
the following commands in the terminal.
|
||||
|
||||
```
|
||||
sudo apt-add-repository ppa:nirenjan/libx52
|
||||
sudo apt update
|
||||
sudo apt install libx52-1
|
||||
```
|
||||
|
||||
## Arch Linux
|
||||
|
||||
This is available on the [AUR](https://aur.archlinux.org/packages/libx52)
|
||||
|
||||
# Building and installing from source
|
||||
|
||||
See [INSTALL.md](INSTALL.md)
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
#!/bin/sh -x
|
||||
|
||||
autoreconf --install
|
|
@ -0,0 +1,24 @@
|
|||
# Automake for libx52-bugreport
|
||||
#
|
||||
# Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
|
||||
bin_PROGRAMS += x52bugreport
|
||||
|
||||
# Bug report program that reports the build and linked library versions
|
||||
x52bugreport_SOURCES = bugreport/bugreport.c
|
||||
x52bugreport_CFLAGS = \
|
||||
-I$(top_srcdir)/libx52io \
|
||||
@LIBUSB_CFLAGS@ \
|
||||
@HIDAPI_CFLAGS@ \
|
||||
$(WARN_CFLAGS)
|
||||
|
||||
x52bugreport_LDFLAGS = \
|
||||
@LIBUSB_LIBS@ \
|
||||
@HIDAPI_LIBS@ \
|
||||
$(WARN_LDFLAGS)
|
||||
|
||||
x52bugreport_LDADD = libx52io.la
|
||||
|
||||
EXTRA_DIST += bugreport/bugreport.dox
|
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* libx52 bugreport utility
|
||||
*
|
||||
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <sys/utsname.h>
|
||||
|
||||
#include "libusb.h"
|
||||
#include "hidapi.h"
|
||||
#include "libx52io.h"
|
||||
#include "version-info.h"
|
||||
|
||||
static void print_sysinfo(void)
|
||||
{
|
||||
struct utsname uts;
|
||||
|
||||
puts("");
|
||||
puts("System info:");
|
||||
puts("============");
|
||||
if (uname(&uts) < 0) {
|
||||
printf("Unable to get system info: %s\n", strerror(errno));
|
||||
} else {
|
||||
printf("%s %s %s (%s)\n", uts.sysname, uts.release, uts.machine, uts.version);
|
||||
}
|
||||
}
|
||||
|
||||
static void print_devinfo(void)
|
||||
{
|
||||
libx52io_context *ctx;
|
||||
int rc;
|
||||
|
||||
puts("");
|
||||
puts("Device info:");
|
||||
puts("============");
|
||||
|
||||
rc = libx52io_init(&ctx);
|
||||
if (rc != LIBX52IO_SUCCESS) {
|
||||
puts(libx52io_strerror(rc));
|
||||
return;
|
||||
}
|
||||
|
||||
rc = libx52io_open(ctx);
|
||||
if (rc != LIBX52IO_SUCCESS) {
|
||||
puts(libx52io_strerror(rc));
|
||||
goto devinfo_cleanup;
|
||||
}
|
||||
|
||||
printf("Device ID: vendor 0x%04x product 0x%04x version 0x%04x\n",
|
||||
libx52io_get_vendor_id(ctx),
|
||||
libx52io_get_product_id(ctx),
|
||||
libx52io_get_device_version(ctx));
|
||||
printf("Device name: '%s' '%s'\n",
|
||||
libx52io_get_manufacturer_string(ctx),
|
||||
libx52io_get_product_string(ctx));
|
||||
printf("Serial number: '%s'\n",
|
||||
libx52io_get_serial_number_string(ctx));
|
||||
|
||||
libx52io_close(ctx);
|
||||
devinfo_cleanup:
|
||||
libx52io_exit(ctx);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
const struct libusb_version *libusb;
|
||||
|
||||
puts("libx52 bugreport");
|
||||
puts("================");
|
||||
printf("Package version: %s\n", VERSION);
|
||||
printf("Build version: %s\n", BUILD_VERSION);
|
||||
printf("Built on: %s\n", BUILD_HOST);
|
||||
printf("Compiler: %s\n", BUILD_COMPILER);
|
||||
printf("Build date: %s\n", BUILD_DATE);
|
||||
printf("version-info %s\n", BUILD_VERSION_INFO_IDENT);
|
||||
|
||||
puts("");
|
||||
puts("Built against:");
|
||||
puts("==============");
|
||||
|
||||
printf("libusb API version: 0x%08x\n", LIBUSB_API_VERSION);
|
||||
#if defined HID_API_VERSION_STR
|
||||
printf("hidapi version: %s\n", HID_API_VERSION_STR);
|
||||
#endif
|
||||
|
||||
libusb = libusb_get_version();
|
||||
puts("");
|
||||
puts("System versions:");
|
||||
puts("================");
|
||||
printf("libusb: %d.%d.%d.%d%s (%s)\n",
|
||||
libusb->major, libusb->minor, libusb->micro, libusb->nano,
|
||||
libusb->rc, libusb->describe);
|
||||
#if defined HID_API_VERSION_STR
|
||||
printf("hidapi: %s\n", hid_version_str());
|
||||
#endif
|
||||
|
||||
print_sysinfo();
|
||||
print_devinfo();
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
/**
|
||||
@page x52bugreport System information utility for bug reports
|
||||
|
||||
\htmlonly
|
||||
<b>x52bugreport</b> - System information utility for bug reports
|
||||
\endhtmlonly
|
||||
|
||||
# SYNOPSIS
|
||||
|
||||
<tt>\b x52bugreport</tt>
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
\b x52bugreport is a utility that collects and reports information on the
|
||||
current system and build environment. The reported information can be provided
|
||||
when raising a bug report on https://github.com/nirenjan/libx52/issues.
|
||||
|
||||
# USAGE
|
||||
|
||||
\b x52bugreport
|
||||
|
||||
*/
|
|
@ -0,0 +1,27 @@
|
|||
# Automake for x52cli
|
||||
#
|
||||
# Copyright (C) 2012-2018 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
|
||||
bin_PROGRAMS += x52cli
|
||||
|
||||
# Command line utility that front ends the core library
|
||||
x52cli_SOURCES = cli/x52_cli.c
|
||||
x52cli_CFLAGS = -I $(top_srcdir)/libx52 $(WARN_CFLAGS)
|
||||
x52cli_LDFLAGS = $(WARN_LDFLAGS)
|
||||
x52cli_LDADD = libx52.la
|
||||
|
||||
if HAVE_CMOCKA
|
||||
TESTS += test-cli
|
||||
check_PROGRAMS += test-cli
|
||||
|
||||
test_cli_SOURCES = cli/x52_cli.c cli/test_x52_cli.c
|
||||
test_cli_CFLAGS = @CMOCKA_CFLAGS@ -DX52_CLI_TESTING -I $(top_srcdir)/libx52
|
||||
test_cli_LDFLAGS = @CMOCKA_LIBS@ $(WARN_LDFLAGS)
|
||||
|
||||
# Add a dependency on test_x52_cli_tests.c
|
||||
cli/test_x52_cli.c: cli/test_x52_cli_tests.c
|
||||
endif
|
||||
|
||||
EXTRA_DIST += cli/test_x52_cli_tests.c
|
|
@ -0,0 +1,181 @@
|
|||
/*
|
||||
* Saitek X52 Pro MFD & LED driver - CLI test harness
|
||||
*
|
||||
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <setjmp.h>
|
||||
#include <cmocka.h>
|
||||
|
||||
#include "libx52.h"
|
||||
|
||||
extern int run_main(int argc, char **argv);
|
||||
|
||||
/* Wrapper functions for libx52 */
|
||||
int libx52_init(libx52_device **dev)
|
||||
{
|
||||
function_called();
|
||||
*dev = NULL;
|
||||
return mock();
|
||||
}
|
||||
|
||||
int libx52_connect(libx52_device *dev)
|
||||
{
|
||||
function_called();
|
||||
return mock();
|
||||
}
|
||||
|
||||
int libx52_update(libx52_device *dev)
|
||||
{
|
||||
return LIBX52_SUCCESS;
|
||||
}
|
||||
|
||||
void libx52_exit(libx52_device *dev)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const char *libx52_strerror(libx52_error_code rc)
|
||||
{
|
||||
function_called();
|
||||
return "";
|
||||
}
|
||||
|
||||
int libx52_set_text(libx52_device *x52, uint8_t line, const char *text, uint8_t length)
|
||||
{
|
||||
function_called();
|
||||
assert_ptr_equal(x52, NULL);
|
||||
check_expected(line);
|
||||
check_expected(text);
|
||||
check_expected(length);
|
||||
|
||||
return mock();
|
||||
}
|
||||
|
||||
int libx52_set_led_state(libx52_device *x52, libx52_led_id id, libx52_led_state state)
|
||||
{
|
||||
function_called();
|
||||
assert_ptr_equal(x52, NULL);
|
||||
check_expected(id);
|
||||
check_expected(state);
|
||||
|
||||
return mock();
|
||||
}
|
||||
|
||||
int libx52_set_clock(libx52_device *x52, time_t time, int local)
|
||||
{
|
||||
function_called();
|
||||
assert_ptr_equal(x52, NULL);
|
||||
check_expected(time);
|
||||
check_expected(local);
|
||||
|
||||
return mock();
|
||||
}
|
||||
|
||||
int libx52_set_clock_timezone(libx52_device *x52, libx52_clock_id clock, int offset)
|
||||
{
|
||||
function_called();
|
||||
assert_ptr_equal(x52, NULL);
|
||||
check_expected(clock);
|
||||
check_expected(offset);
|
||||
|
||||
return mock();
|
||||
}
|
||||
|
||||
int libx52_set_clock_format(libx52_device *x52, libx52_clock_id clock, libx52_clock_format format)
|
||||
{
|
||||
function_called();
|
||||
assert_ptr_equal(x52, NULL);
|
||||
check_expected(clock);
|
||||
check_expected(format);
|
||||
|
||||
return mock();
|
||||
}
|
||||
|
||||
int libx52_set_time(libx52_device *x52, uint8_t hour, uint8_t minute)
|
||||
{
|
||||
function_called();
|
||||
assert_ptr_equal(x52, NULL);
|
||||
check_expected(hour);
|
||||
check_expected(minute);
|
||||
|
||||
return mock();
|
||||
}
|
||||
|
||||
int libx52_set_date(libx52_device *x52, uint8_t dd, uint8_t mm, uint8_t yy)
|
||||
{
|
||||
function_called();
|
||||
assert_ptr_equal(x52, NULL);
|
||||
check_expected(dd);
|
||||
check_expected(mm);
|
||||
check_expected(yy);
|
||||
|
||||
return mock();
|
||||
}
|
||||
|
||||
int libx52_set_date_format(libx52_device *x52, libx52_date_format format)
|
||||
{
|
||||
function_called();
|
||||
assert_ptr_equal(x52, NULL);
|
||||
check_expected(format);
|
||||
|
||||
return mock();
|
||||
}
|
||||
|
||||
int libx52_set_brightness(libx52_device *x52, uint8_t mfd, uint16_t brightness)
|
||||
{
|
||||
function_called();
|
||||
assert_ptr_equal(x52, NULL);
|
||||
check_expected(mfd);
|
||||
check_expected(brightness);
|
||||
|
||||
return mock();
|
||||
}
|
||||
|
||||
int libx52_set_shift(libx52_device *x52, uint8_t state)
|
||||
{
|
||||
function_called();
|
||||
assert_ptr_equal(x52, NULL);
|
||||
check_expected(state);
|
||||
|
||||
return mock();
|
||||
}
|
||||
|
||||
int libx52_set_blink(libx52_device *x52, uint8_t state)
|
||||
{
|
||||
function_called();
|
||||
assert_ptr_equal(x52, NULL);
|
||||
check_expected(state);
|
||||
|
||||
return mock();
|
||||
}
|
||||
|
||||
int libx52_vendor_command(libx52_device *x52, uint16_t index, uint16_t value)
|
||||
{
|
||||
function_called();
|
||||
assert_ptr_equal(x52, NULL);
|
||||
check_expected(index);
|
||||
check_expected(value);
|
||||
|
||||
return mock();
|
||||
}
|
||||
|
||||
#include "test_x52_cli_tests.c"
|
||||
#define TEST_LIST
|
||||
|
||||
const struct CMUnitTest tests[] = {
|
||||
#include "test_x52_cli_tests.c"
|
||||
};
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
cmocka_set_message_output(CM_OUTPUT_TAP);
|
||||
cmocka_run_group_tests(tests, NULL, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,547 @@
|
|||
/*
|
||||
* Saitek X52 Pro MFD & LED driver
|
||||
*
|
||||
* Copyright (C) 2015-2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
@page x52cli Command Line Interface to libx52
|
||||
|
||||
\htmlonly
|
||||
<b>x52cli</b> - Command line interface to libx52
|
||||
\endhtmlonly
|
||||
|
||||
# SYNOPSIS
|
||||
<tt>\b x52cli \a commands [\a command-options]</tt>
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
\b x52cli is a command line interface to the X52 library that allows you to set
|
||||
the LEDs and different parameters on the multifunction display (MFD).
|
||||
|
||||
Running \b x52cli without any arguments will display a brief help message.
|
||||
|
||||
# COMMANDS
|
||||
|
||||
Commands are not case sensitive.
|
||||
|
||||
- <tt>\b bri { \b mfd | \b led } < \a brightness >
|
||||
</tt>\n \manonly \fR \endmanonly
|
||||
Set the brightness of the \b MFD or <b>LED</b>s. \a brightness can be any
|
||||
numeric value between 0 and 128. Higher values are accepted, but may not have
|
||||
the desired effect.
|
||||
|
||||
- <tt>\b mfd < \a line > < \a text > </tt>\n \manonly \fR \endmanonly
|
||||
Set the text on the MFD \a line. \a line can be \c 0, \c 1 or \c 2, and refers
|
||||
to the first, second or third line of the multifunction display respectively.
|
||||
\a text cannot have embedded NUL characters (0x00) and must correspond with
|
||||
the character map fo the MFD. \a text should be quoted in order to preserve
|
||||
embedded whitespace. To pass raw hex values, use \b printf(1) as shown in the
|
||||
\ref x52cli_examples section. Note that \a text is limited to a length of 16
|
||||
characters. While you can pass in longer strings, they will be silenty
|
||||
truncated.
|
||||
|
||||
- <tt>\b led < \a led-id > < \a state > </tt>\n \manonly \fR \endmanonly
|
||||
Set the LED \a led-id to \a state. See \ref x52cli_leds for a list of
|
||||
supported values.
|
||||
|
||||
- <tt>\b blink { \b on | \b off } </tt>\n \manonly \fR \endmanonly
|
||||
Turn the \b blink state \b on or \b off.
|
||||
|
||||
- <tt>\b shift { \b on | \b off } </tt>\n \manonly \fR \endmanonly
|
||||
Turn the \b shift indicator in the MFD \b on or \b off.
|
||||
|
||||
- <tt>\b clock { \b local | \b gmt } { \b 12hr | \b 24hr }
|
||||
{ \b ddmmyy | \b mmddyy | \b yymmdd }</tt>\n \manonly \fR \endmanonly
|
||||
Set the clock 1 display to the current \b local or \b GMT time and date.
|
||||
Clock can be configured to display in either \b 12hr or \b 24hr mode. Date
|
||||
can be displayed in one of the following formats: \b DD-MM-YY, \b MM-DD-YY,
|
||||
or \b YY-MM-DD.
|
||||
|
||||
- <tt>\b offset { \b 2 | \b 3 } < \a offset-val >
|
||||
{ \b 12hr | \b 24hr }</tt>\n \manonly \fR \endmanonly
|
||||
Set the offset for clock \b 2 or \b 3 and configure them to display in either
|
||||
\b 12hr or \b 24hr mode. \a offset-val is in minutes east of \b UTC and can
|
||||
range from \a -1440 to \a +1440.
|
||||
|
||||
- <tt>\b time < \a hour > < \a minute >
|
||||
{ \b 12hr | \b 24hr }</tt>\n \manonly \fR \endmanonly
|
||||
Set the time for clock 1 to <em>hour:minute</em> and configure it to display
|
||||
in \b 12hr or \b 24hr mode.
|
||||
|
||||
- <tt>\b date <\a dd > <\a mm > <\a yy>
|
||||
{ \b ddmmyy | \b mmddyy | \b yymmdd }</tt>\n \manonly \fR \endmanonly
|
||||
Set the date on the MFD to the values represented by \a dd, \a mm and \a yy in
|
||||
the requested format.
|
||||
|
||||
- <tt>\b raw < \a wIndex > < \a wValue ></tt>\n \manonly \fR \endmanonly
|
||||
Send a raw vendor control request to the connected joystick.\n
|
||||
\warning You should only use the raw command if you know what you are doing.
|
||||
Sending an invalid control sequence can potentially damage or destroy your
|
||||
device.
|
||||
|
||||
@section x52cli_leds LEDs
|
||||
This is the list of LED IDs and corresponding states supported by the X52 Pro.
|
||||
Note that the \b on state is only allowed for the \b fire and \b throttle LEDs,
|
||||
and they do not support the \b red, \b amber and \b green states. The remaining
|
||||
LEDs do not support the \b on state, but support all the other states.
|
||||
|
||||
\note The \b led command is only supported on the X52 Pro, not on the X52.
|
||||
|
||||
## LED IDs
|
||||
|
||||
- \b fire
|
||||
- \b a
|
||||
- \b b
|
||||
- \b d
|
||||
- \b e
|
||||
- \b t1
|
||||
- \b t2
|
||||
- \b t3
|
||||
- \b pov
|
||||
- \b clutch
|
||||
- \b throttle
|
||||
|
||||
## States
|
||||
|
||||
- \b off
|
||||
- \b on
|
||||
- \b red
|
||||
- \b amber
|
||||
- \b green
|
||||
|
||||
# LIMITATIONS
|
||||
|
||||
\b x52cli does not maintain any state between invocations. As a result the
|
||||
\b clock command will reset the relative offsets for clocks 2 and 3 back to 0
|
||||
and configure them to be in 12 hour mode. To work around this, use the \b date
|
||||
and \b time commands instead to manually configure the date and time.
|
||||
|
||||
\note The device does not have an internal clock; as a result, the MFD
|
||||
display will not advance automatically. You must call the \b clock or \b date
|
||||
and \b time commands periodically to update the time on the device. However, if
|
||||
you are running \b x52d and the clock manager is enabled, then \b x52d will
|
||||
manage and automatically update the clock on the X52 MFD display.
|
||||
|
||||
# PERMISSIONS
|
||||
|
||||
You must have write permissions to the USB device in order to use the \b libx52
|
||||
library, and by extension, \b x52cli.
|
||||
|
||||
The simplest method to obtain such permissions is to run \b x52cli as root,
|
||||
possibly through \b sudo(8)
|
||||
|
||||
@section x52cli_examples EXAMPLES
|
||||
|
||||
- Turn off the T1 LED.
|
||||
> <tt>\b x52cli led t1 off</tt>
|
||||
|
||||
- Turn the B LED to Amber.
|
||||
> <tt>\b x52cli led B amber</tt>
|
||||
|
||||
- Set line 1 of the MFD to display "Hello World"
|
||||
> <tt>\b x52cli mfd 0 "Hello World"</tt>
|
||||
|
||||
- Set line 2 of the MFD to display "¿Cómo Estás?"
|
||||
> <tt>\b x52cli mfd 1 "$(printf '\\x9FC\\xE2mo Est\\xE0s?')"</tt>
|
||||
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include "config.h"
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "libx52.h"
|
||||
|
||||
struct string_map {
|
||||
const char *key;
|
||||
union {
|
||||
libx52_clock_id clock_id;
|
||||
libx52_clock_format clock_format;
|
||||
libx52_date_format date_format;
|
||||
libx52_led_id led_id;
|
||||
libx52_led_state led_state;
|
||||
int int_val;
|
||||
intptr_t ptr_val;
|
||||
} value;
|
||||
};
|
||||
|
||||
// Max 4 arguments for now
|
||||
#define MAX_ARGS 4
|
||||
|
||||
typedef int (*handler_cb)(libx52_device *x52, void *args[]);
|
||||
struct command_handler {
|
||||
handler_cb handler;
|
||||
int num_args;
|
||||
const struct string_map *maps[MAX_ARGS];
|
||||
const char *help;
|
||||
};
|
||||
|
||||
#define SAVE_ARGUMENT(type, name, arg) type name = (type)(uintptr_t)(arg)
|
||||
#define PARSE_ARGUMENT(type, arg) ((type)(uintptr_t)(arg))
|
||||
|
||||
#define MAP(name) name##_map
|
||||
#define DEFINE_MAP(name) static const struct string_map MAP(name)[]
|
||||
#define MAP_CLOCK_ID(id) {.key = #id, .value.clock_id = LIBX52_CLOCK_ ## id}
|
||||
#define MAP_CLOCK_FORMAT(fmt) {.key = #fmt, .value.clock_format = LIBX52_CLOCK_FORMAT_ ## fmt}
|
||||
#define MAP_DATE_FORMAT(fmt) {.key = #fmt, .value.date_format = LIBX52_DATE_FORMAT_ ## fmt}
|
||||
#define MAP_LED_ID(id) {.key = #id, .value.led_id = LIBX52_LED_ ## id}
|
||||
#define MAP_LED_STATE(state) {.key = #state, .value.led_state = LIBX52_LED_STATE_ ## state}
|
||||
#define MAP_INT(str, val) {.key = str, .value.int_val = val}
|
||||
#define MAP_TERMINATOR MAP_INT(NULL, -1)
|
||||
|
||||
/**
|
||||
* Parse a string and match it with a corresponding value
|
||||
* Maps are arrays which contain a terminating entry with a NULL key.
|
||||
*
|
||||
* Return 0 if no match
|
||||
*/
|
||||
static int map_lookup(const struct string_map *map, const char *str, struct string_map *result)
|
||||
{
|
||||
const struct string_map *map_index = map;
|
||||
|
||||
/* Search through the map for a matching key */
|
||||
while (map_index->key) {
|
||||
if (!strcasecmp(str, map_index->key)) {
|
||||
result->value = map_index->value;
|
||||
return 1;
|
||||
}
|
||||
|
||||
map_index++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Map for LED state */
|
||||
DEFINE_MAP(led_state) = {
|
||||
MAP_LED_STATE(OFF),
|
||||
MAP_LED_STATE(ON),
|
||||
MAP_LED_STATE(RED),
|
||||
MAP_LED_STATE(AMBER),
|
||||
MAP_LED_STATE(GREEN),
|
||||
MAP_TERMINATOR
|
||||
};
|
||||
|
||||
/* Map for LED identifier */
|
||||
DEFINE_MAP(led_id) = {
|
||||
MAP_LED_ID(FIRE),
|
||||
MAP_LED_ID(A),
|
||||
MAP_LED_ID(B),
|
||||
MAP_LED_ID(D),
|
||||
MAP_LED_ID(E),
|
||||
MAP_LED_ID(T1),
|
||||
MAP_LED_ID(T2),
|
||||
MAP_LED_ID(T3),
|
||||
MAP_LED_ID(POV),
|
||||
MAP_LED_ID(CLUTCH),
|
||||
MAP_LED_ID(THROTTLE),
|
||||
MAP_TERMINATOR
|
||||
};
|
||||
|
||||
/* Map for date format */
|
||||
DEFINE_MAP(date_format) = {
|
||||
MAP_DATE_FORMAT(DDMMYY),
|
||||
MAP_DATE_FORMAT(MMDDYY),
|
||||
MAP_DATE_FORMAT(YYMMDD),
|
||||
MAP_TERMINATOR
|
||||
};
|
||||
|
||||
/* Map for brightness setting */
|
||||
DEFINE_MAP(brightness_targets) = {
|
||||
MAP_INT( "mfd", 1 ),
|
||||
MAP_INT( "led", 0 ),
|
||||
MAP_TERMINATOR
|
||||
};
|
||||
|
||||
/* Map for blink/shift on/off */
|
||||
DEFINE_MAP(on_off) = {
|
||||
MAP_INT( "off", 0 ),
|
||||
MAP_INT( "on", 1 ),
|
||||
MAP_TERMINATOR
|
||||
};
|
||||
|
||||
/* Map for clock 0 timezone */
|
||||
DEFINE_MAP(clock0_timezone) = {
|
||||
MAP_INT( "gmt", 0 ),
|
||||
MAP_INT( "local", 1 ),
|
||||
MAP_TERMINATOR
|
||||
};
|
||||
|
||||
/* Map for identifying the clock for the timezone */
|
||||
DEFINE_MAP(clocks) = {
|
||||
MAP_CLOCK_ID(1),
|
||||
MAP_CLOCK_ID(2),
|
||||
MAP_CLOCK_ID(3),
|
||||
MAP_TERMINATOR
|
||||
};
|
||||
|
||||
/* Map for identifying the time format */
|
||||
DEFINE_MAP(time_format) = {
|
||||
MAP_CLOCK_FORMAT(12HR),
|
||||
MAP_CLOCK_FORMAT(24HR),
|
||||
MAP_TERMINATOR
|
||||
};
|
||||
|
||||
static int update_led(libx52_device *x52, void *args[])
|
||||
{
|
||||
return libx52_set_led_state(x52,
|
||||
PARSE_ARGUMENT(libx52_led_id, args[0]),
|
||||
PARSE_ARGUMENT(libx52_led_state, args[1]));
|
||||
}
|
||||
|
||||
static int update_bri(libx52_device *x52, void *args[])
|
||||
{
|
||||
unsigned long int brightness = strtoul(args[1], NULL, 0);
|
||||
|
||||
return libx52_set_brightness(x52,
|
||||
PARSE_ARGUMENT(uint8_t, args[0]), (uint16_t)brightness);
|
||||
}
|
||||
|
||||
static int update_mfd(libx52_device *x52, void *args[])
|
||||
{
|
||||
uint8_t line = (uint8_t)strtoul(args[0], NULL, 0);
|
||||
uint8_t length = strlen(args[1]);
|
||||
|
||||
return libx52_set_text(x52, line, args[1], length);
|
||||
}
|
||||
|
||||
static int update_blink(libx52_device *x52, void *args[])
|
||||
{
|
||||
return libx52_set_blink(x52, PARSE_ARGUMENT(int, args[0]));
|
||||
}
|
||||
|
||||
static int update_shift(libx52_device *x52, void *args[])
|
||||
{
|
||||
return libx52_set_shift(x52, PARSE_ARGUMENT(int, args[0]));
|
||||
}
|
||||
|
||||
static int update_clock(libx52_device *x52, void *args[])
|
||||
{
|
||||
int rc;
|
||||
rc = libx52_set_clock(x52, time(NULL),
|
||||
PARSE_ARGUMENT(int, args[0]));
|
||||
|
||||
if (!rc) {
|
||||
rc = libx52_set_clock_format(x52, LIBX52_CLOCK_1,
|
||||
PARSE_ARGUMENT(libx52_clock_format, args[1]));
|
||||
}
|
||||
|
||||
if (!rc) {
|
||||
rc = libx52_set_date_format(x52,
|
||||
PARSE_ARGUMENT(libx52_date_format, args[2]));
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int update_offset(libx52_device *x52, void *args[])
|
||||
{
|
||||
int offset = (int)strtol(args[1], NULL, 0);
|
||||
int rc;
|
||||
SAVE_ARGUMENT(libx52_clock_id, clock, args[0]);
|
||||
|
||||
rc = libx52_set_clock_timezone(x52, clock, offset);
|
||||
|
||||
if (!rc) {
|
||||
rc = libx52_set_clock_format(x52, clock,
|
||||
PARSE_ARGUMENT(libx52_clock_format, args[2]));
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int update_time(libx52_device *x52, void *args[])
|
||||
{
|
||||
int hh = (int)strtol(args[0], NULL, 0);
|
||||
int mm = (int)strtol(args[1], NULL, 0);
|
||||
int rc;
|
||||
|
||||
/* Set the time value */
|
||||
rc = libx52_set_time(x52, hh, mm);
|
||||
if (!rc) {
|
||||
rc = libx52_set_clock_format(x52, LIBX52_CLOCK_1,
|
||||
PARSE_ARGUMENT(libx52_clock_format, args[2]));
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int update_date(libx52_device *x52, void *args[])
|
||||
{
|
||||
int dd = (int)strtol(args[0], NULL, 0);
|
||||
int mm = (int)strtol(args[1], NULL, 0);
|
||||
int yy = (int)strtol(args[2], NULL, 0);
|
||||
int rc;
|
||||
|
||||
/* Set the date value */
|
||||
rc = libx52_set_date(x52, dd, mm, yy);
|
||||
if (!rc) {
|
||||
rc = libx52_set_date_format(x52,
|
||||
PARSE_ARGUMENT(libx52_date_format, args[3]));
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int update_raw(libx52_device *x52, void *args[])
|
||||
{
|
||||
uint16_t wIndex = (uint16_t)strtoul(args[0], NULL, 0);
|
||||
uint16_t wValue = (uint16_t)strtoul(args[1], NULL, 0);
|
||||
|
||||
return libx52_vendor_command(x52, wIndex, wValue);
|
||||
}
|
||||
|
||||
/* Commands for CLI */
|
||||
#define COMMANDS \
|
||||
X(led, LED_STATE, "<led-id> <state>", 2, \
|
||||
MAP(led_id), MAP(led_state)) \
|
||||
X(bri, BRIGHTNESS, "{mfd | led} <brightness level>", 2, \
|
||||
MAP(brightness_targets), NULL) \
|
||||
X(mfd, MFD_TEXT, "<line> <text in quotes>", 2, \
|
||||
NULL, NULL) \
|
||||
X(blink, BLINK, "{ on | off }", 1, \
|
||||
MAP(on_off)) \
|
||||
X(shift, SHIFT, "{ on | off }", 1, \
|
||||
MAP(on_off)) \
|
||||
X(clock, CLOCK, \
|
||||
"{local | gmt} {12hr | 24hr} {ddmmyy | mmddyy | yymmdd}", \
|
||||
3, MAP(clock0_timezone), MAP(time_format), MAP(date_format)) \
|
||||
X(offset, OFFSET, \
|
||||
"{2 | 3} <offset from clock 1 in minutes> {12hr | 24hr}", \
|
||||
3, MAP(clocks), NULL, MAP(time_format)) \
|
||||
X(time, TIME, \
|
||||
"<hour> <minute> {12hr | 24hr}", 3, \
|
||||
NULL, NULL, MAP(time_format)) \
|
||||
X(date, DATE, \
|
||||
"<dd> <mm> <yy> {ddmmyy | mmddyy | yymmdd}", 4, \
|
||||
NULL, NULL, NULL, MAP(date_format)) \
|
||||
X(raw, RAW, "<wIndex> <wValue>", 2, NULL, NULL)
|
||||
|
||||
/* Enums for command identification */
|
||||
#define X(cmd, en, help, args, ...) X52_CTL_CMD_ ## en,
|
||||
enum {
|
||||
COMMANDS
|
||||
|
||||
X52_CTL_CMD_MAX
|
||||
};
|
||||
#undef X
|
||||
|
||||
/* Map for commands */
|
||||
#define X(cmd, en, help, args, ...) MAP_INT( #cmd, X52_CTL_CMD_ ## en),
|
||||
DEFINE_MAP(command) = {
|
||||
COMMANDS
|
||||
|
||||
MAP_TERMINATOR
|
||||
};
|
||||
#undef X
|
||||
|
||||
/* Command handlers */
|
||||
#define X(cmd, en, help, args, ...) \
|
||||
[X52_CTL_CMD_ ## en] = { \
|
||||
update_ ## cmd, \
|
||||
args, \
|
||||
{ __VA_ARGS__ }, \
|
||||
#cmd " " help \
|
||||
},
|
||||
const struct command_handler handlers[X52_CTL_CMD_MAX] = {
|
||||
COMMANDS
|
||||
};
|
||||
#undef X
|
||||
|
||||
static void do_help(const struct command_handler *cmd)
|
||||
{
|
||||
int i;
|
||||
if (cmd) {
|
||||
fprintf(stderr, "Command usage: %s\n", cmd->help);
|
||||
} else {
|
||||
printf("\nCommands:\n");
|
||||
for (i = 0; i < X52_CTL_CMD_MAX; i++) {
|
||||
printf("\t%s\n", handlers[i].help);
|
||||
}
|
||||
|
||||
printf("\nWARNING: raw command may damage your device\n\n");
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef X52_CLI_TESTING
|
||||
int run_main(int argc, char **argv)
|
||||
#else
|
||||
int main(int argc, char **argv)
|
||||
#endif
|
||||
{
|
||||
libx52_device *x52;
|
||||
struct string_map result;
|
||||
const struct command_handler *cmd;
|
||||
int i;
|
||||
void *args[MAX_ARGS];
|
||||
int rc;
|
||||
|
||||
if (argc < 2) {
|
||||
fprintf(stderr, "Usage: %s <command> [arguments]\n", argv[0]);
|
||||
do_help(NULL);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!map_lookup(command_map, argv[1], &result)) {
|
||||
fprintf(stderr, "Unsupported command %s\n", argv[1]);
|
||||
do_help(NULL);
|
||||
return 1;
|
||||
}
|
||||
|
||||
cmd = &handlers[result.value.int_val];
|
||||
if (cmd->num_args > argc - 2) {
|
||||
fprintf(stderr, "Insufficient arguments for command %s\n", argv[1]);
|
||||
do_help(cmd);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Clear the arguments array */
|
||||
memset(args, 0, sizeof(args));
|
||||
|
||||
for (i = 0; i < cmd->num_args; i++) {
|
||||
if (cmd->maps[i]) {
|
||||
if (!map_lookup(cmd->maps[i], argv[2+i], &result)) {
|
||||
fprintf(stderr, "Invalid argument %s\n", argv[2+i]);
|
||||
return 1;
|
||||
}
|
||||
args[i] = (void *)result.value.ptr_val;
|
||||
} else {
|
||||
args[i] = argv[2+i];
|
||||
}
|
||||
}
|
||||
|
||||
/* Initialize libx52 */
|
||||
rc = libx52_init(&x52);
|
||||
|
||||
if (rc != LIBX52_SUCCESS) {
|
||||
fprintf(stderr, "Error initializing X52 library: %s\n", libx52_strerror(rc));
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Make sure we are connected to the joystick */
|
||||
rc = libx52_connect(x52);
|
||||
if (rc != LIBX52_SUCCESS) {
|
||||
fprintf(stderr, "Error connecting to joystick: %s\n", libx52_strerror(rc));
|
||||
return 1;
|
||||
}
|
||||
|
||||
rc = (*(cmd->handler))(x52, args);
|
||||
if (rc != LIBX52_SUCCESS) {
|
||||
fprintf(stderr, "Error: %s\n", libx52_strerror(rc));
|
||||
rc = 1;
|
||||
}
|
||||
|
||||
libx52_update(x52);
|
||||
|
||||
libx52_exit(x52);
|
||||
|
||||
return rc;
|
||||
}
|
|
@ -0,0 +1,146 @@
|
|||
# Autoconf settings for libx52
|
||||
#
|
||||
# Copyright (C) 2012-2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
|
||||
AC_INIT([libx52], [m4_esyscmd_s([cat ./Version])], [nirenjan@gmail.com])
|
||||
AC_CONFIG_MACRO_DIR([m4])
|
||||
AM_INIT_AUTOMAKE([-Wall foreign subdir-objects])
|
||||
AC_REQUIRE_AUX_FILE([tap-driver.sh])
|
||||
AC_PROG_CC
|
||||
AC_PROG_CC_STDC
|
||||
AC_PROG_AWK
|
||||
AC_PROG_SED
|
||||
AC_PROG_MKDIR_P
|
||||
AM_PROG_AR
|
||||
AM_PATH_PYTHON([3.5])
|
||||
LT_INIT
|
||||
PKG_PROG_PKG_CONFIG
|
||||
PKG_INSTALLDIR
|
||||
AX_COMPILER_FLAGS
|
||||
AC_CANONICAL_HOST
|
||||
AX_GCC_FUNC_ATTRIBUTE([constructor])
|
||||
AX_GCC_FUNC_ATTRIBUTE([destructor])
|
||||
AX_GCC_FUNC_ATTRIBUTE([format])
|
||||
AX_GCC_FUNC_ATTRIBUTE([noreturn])
|
||||
AC_C_TYPEOF
|
||||
|
||||
AC_MSG_NOTICE([Detected host OS is ${host_os}])
|
||||
build_linux=no
|
||||
# Detect target system
|
||||
case "${host_os}" in
|
||||
linux*)
|
||||
build_linux=yes
|
||||
;;
|
||||
esac
|
||||
AM_CONDITIONAL([LINUX], [test "x${build_linux}" = "xyes"])
|
||||
|
||||
# Internationalization
|
||||
AM_GNU_GETTEXT([external])
|
||||
AM_GNU_GETTEXT_VERSION(0.19)
|
||||
AM_CONDITIONAL([USE_NLS], [test "x${USE_NLS}" == "xyes"])
|
||||
|
||||
# Check for libusb-1.0
|
||||
PKG_CHECK_MODULES([LIBUSB], [libusb-1.0])
|
||||
AC_SUBST([LIBUSB_PC], [libusb-1.0])
|
||||
|
||||
# systemd support
|
||||
PKG_CHECK_MODULES([SYSTEMD], [systemd], [have_systemd=yes], [have_systemd=no])
|
||||
AC_ARG_ENABLE([systemd],
|
||||
[AS_HELP_STRING([--disable-systemd], [Disable systemd support])]
|
||||
)
|
||||
AC_ARG_WITH([systemdsystemunitdir],
|
||||
[AS_HELP_STRING([--with-systemdsystemunitdir=DIR], [Directory for systemd unit files])],
|
||||
[systemdsystemunitdir=$withval],
|
||||
[systemdsystemunitdir=$($PKG_CONFIG --variable=systemdsystemunitdir systemd)]
|
||||
)
|
||||
AC_SUBST([systemdsystemunitdir], [$systemdsystemunitdir])
|
||||
AM_CONDITIONAL([HAVE_SYSTEMD], [test "x$have_systemd" = "xyes" -a "x$enable_systemd" != "xno"])
|
||||
AM_COND_IF([HAVE_SYSTEMD],,
|
||||
[
|
||||
AC_MSG_NOTICE([systemd not found or disabled. Enabling timestamps in logs])
|
||||
AX_APPEND_FLAG([-DPINELOG_SHOW_DATE=1], [PINELOG_CFLAGS])
|
||||
]
|
||||
)
|
||||
|
||||
# evdev support
|
||||
# This is only on Linux machines, so we need to set an automake conditional
|
||||
PKG_CHECK_MODULES([EVDEV], [libevdev], [have_evdev=yes], [have_evdev=no])
|
||||
AM_CONDITIONAL([HAVE_EVDEV], [test "x$have_evdev" = "xyes"])
|
||||
|
||||
# Pinelog configuration
|
||||
AX_APPEND_FLAG([-DPINELOG_SHOW_LEVEL=1], [PINELOG_CFLAGS])
|
||||
AX_APPEND_FLAG([-DPINELOG_SHOW_BACKTRACE=1], [PINELOG_CFLAGS])
|
||||
AX_APPEND_FLAG([-DPINELOG_BUFFER_SZ=1024], [PINELOG_CFLAGS])
|
||||
AC_SUBST([PINELOG_CFLAGS])
|
||||
|
||||
# Check for hidapi. This uses a different pkg-config file on Linux vs other
|
||||
# hosts, so check accordingly
|
||||
AM_COND_IF([LINUX], [hidapi_backend=hidapi-hidraw], [hidapi_backend=hidapi])
|
||||
PKG_CHECK_MODULES([HIDAPI], [${hidapi_backend}])
|
||||
AC_SUBST([HIDAPI_PC], [${hidapi_backend}])
|
||||
|
||||
# Check for pthreads
|
||||
ACX_PTHREAD
|
||||
|
||||
# make distcheck doesn't work if some files are installed outside $prefix.
|
||||
# Check for a prefix ending in /_inst, if this is found, we can assume this
|
||||
# to be a make distcheck, and disable some of the installcheck stuff.
|
||||
AS_CASE([$prefix], [*/_inst],
|
||||
[AC_MSG_NOTICE([[Prefix ends in /_inst; this looks like a 'make distcheck']])
|
||||
is_make_distcheck=yes])
|
||||
AM_CONDITIONAL([IS_MAKE_DISTCHECK], [test "x$is_make_distcheck" = xyes])
|
||||
AC_MSG_CHECKING([final decision IS_MAKE_DISTCHECK (running "make distcheck"?)])
|
||||
AM_COND_IF([IS_MAKE_DISTCHECK], [AC_MSG_RESULT([yes])], [AC_MSG_RESULT([no])])
|
||||
|
||||
# udev support
|
||||
PKG_CHECK_MODULES([UDEV], [udev], [have_udev=yes], [have_udev=no])
|
||||
AM_CONDITIONAL([HAVE_UDEV], [test "x$have_udev" = xyes])
|
||||
AC_ARG_WITH([udevrulesdir],
|
||||
AS_HELP_STRING([--with-udevrulesdir=DIR], [Directory for udev rules]),
|
||||
[udevrulesdir=$withval],
|
||||
[udevrulesdir=$($PKG_CONFIG --variable=udevdir udev)"/rules.d"])
|
||||
AC_SUBST([udevrulesdir], [$udevrulesdir])
|
||||
|
||||
AC_ARG_WITH([input-group],
|
||||
AS_HELP_STRING([--with-input-group=GROUP], [Group allowed to access input devices]),
|
||||
[input_group=$withval],
|
||||
[input_group=plugdev])
|
||||
AC_SUBST([input_group], [$input_group])
|
||||
|
||||
# Doxygen Support
|
||||
AC_CHECK_PROGS([DOXYGEN], [doxygen])
|
||||
AM_CONDITIONAL([HAVE_DOXYGEN], [test -n "$DOXYGEN"])
|
||||
AM_COND_IF([HAVE_DOXYGEN],
|
||||
[AC_CONFIG_FILES([Doxyfile])],
|
||||
[AC_MSG_WARN(["Doxygen not found; continuing without doxygen support"])])
|
||||
|
||||
# cmocka unit tests
|
||||
PKG_CHECK_MODULES([CMOCKA], [cmocka >= 1.1], [have_cmocka=yes], [have_cmocka=no])
|
||||
AM_CONDITIONAL([HAVE_CMOCKA], [test "x$have_cmocka" = xyes])
|
||||
AM_COND_IF([HAVE_CMOCKA], [],
|
||||
[AC_MSG_WARN(["cmocka not found; disabling unit test build"])])
|
||||
|
||||
# Check for the presence of tm_gmtoff in struct tm. If we have this, then we
|
||||
# can use it to determine the true GMT offset
|
||||
AC_CHECK_MEMBERS([struct tm.tm_gmtoff],,
|
||||
[AC_MSG_WARN(["Cannot find tm_gmtoff in struct tm, using slower method"])],
|
||||
[#define _GNU_SOURCE
|
||||
#include <time.h>
|
||||
])
|
||||
|
||||
# Configuration headers
|
||||
AC_CONFIG_HEADERS([config.h])
|
||||
|
||||
AC_CONFIG_FILES([ po/Makefile.in
|
||||
Makefile
|
||||
lib/Makefile
|
||||
libx52/libx52.pc
|
||||
libx52io/libx52io.pc
|
||||
libx52util/libx52util.pc
|
||||
lib/pinelog/Makefile
|
||||
lib/inih/Makefile
|
||||
udev/60-saitek-x52-x52pro.rules
|
||||
])
|
||||
AC_OUTPUT
|
|
@ -0,0 +1,145 @@
|
|||
# Automake for x52d
|
||||
#
|
||||
# Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
bin_PROGRAMS += x52d x52ctl
|
||||
|
||||
# Service daemon that manages the X52 device
|
||||
x52d_SOURCES = \
|
||||
daemon/x52d_main.c \
|
||||
daemon/x52d_config_parser.c \
|
||||
daemon/x52d_config_dump.c \
|
||||
daemon/x52d_config.c \
|
||||
daemon/x52d_device.c \
|
||||
daemon/x52d_client.c \
|
||||
daemon/x52d_clock.c \
|
||||
daemon/x52d_mouse.c \
|
||||
daemon/x52d_notify.c \
|
||||
daemon/x52d_led.c \
|
||||
daemon/x52d_command.c \
|
||||
daemon/x52d_comm_internal.c \
|
||||
daemon/x52d_comm_client.c
|
||||
|
||||
x52d_CFLAGS = \
|
||||
-I $(top_srcdir) \
|
||||
-I $(top_srcdir)/libx52io \
|
||||
-I $(top_srcdir)/libx52 \
|
||||
-I $(top_srcdir)/libx52util \
|
||||
-I $(top_srcdir)/lib/pinelog \
|
||||
-I $(top_srcdir)/lib/inih \
|
||||
-DSYSCONFDIR=\"$(sysconfdir)\" \
|
||||
-DLOCALEDIR=\"$(localedir)\" \
|
||||
-DLOGDIR=\"$(localstatedir)/log\" \
|
||||
-DRUNDIR=\"$(localstatedir)/run\" \
|
||||
@PTHREAD_CFLAGS@ $(WARN_CFLAGS)
|
||||
|
||||
x52d_LDFLAGS = @PTHREAD_LIBS@ $(WARN_LDFLAGS)
|
||||
x52d_LDADD = \
|
||||
lib/pinelog/libpinelog.la \
|
||||
lib/inih/libinih.la \
|
||||
libx52.la \
|
||||
@LTLIBINTL@
|
||||
|
||||
if HAVE_EVDEV
|
||||
x52d_SOURCES += \
|
||||
daemon/x52d_io.c \
|
||||
daemon/x52d_mouse_evdev.c
|
||||
|
||||
x52d_CFLAGS += -DHAVE_EVDEV @EVDEV_CFLAGS@
|
||||
x52d_LDFLAGS += @EVDEV_LIBS@
|
||||
x52d_LDADD += libx52io.la
|
||||
endif
|
||||
|
||||
lib_LTLIBRARIES += libx52dcomm.la
|
||||
|
||||
# Client library to communicate with X52 daemon
|
||||
libx52dcomm_la_SOURCES = \
|
||||
daemon/x52d_comm_client.c \
|
||||
daemon/x52d_comm_internal.c
|
||||
libx52dcomm_la_CFLAGS = \
|
||||
-I $(top_srcdir) \
|
||||
-DSYSCONFDIR=\"$(sysconfdir)\" \
|
||||
-DLOCALEDIR=\"$(localedir)\" \
|
||||
-DLOGDIR=\"$(localstatedir)/log\" \
|
||||
-DRUNDIR=\"$(localstatedir)/run\" \
|
||||
$(WARN_CFLAGS)
|
||||
libx52dcomm_la_LDFLAGS = $(WARN_LDFLAGS)
|
||||
|
||||
x52include_HEADERS += daemon/x52dcomm.h
|
||||
|
||||
x52ctl_SOURCES = daemon/x52ctl.c
|
||||
x52ctl_CFLAGS = \
|
||||
-I $(top_srcdir) \
|
||||
$(WARN_CFLAGS)
|
||||
x52ctl_LDFLAGS = $(WARN_LDFLAGS)
|
||||
x52ctl_LDADD = libx52dcomm.la @LTLIBINTL@
|
||||
|
||||
x52dconfdir = @sysconfdir@/x52d
|
||||
x52dconf_DATA = daemon/x52d.conf
|
||||
|
||||
install-exec-hook:
|
||||
$(MKDIR_P) $(DESTDIR)$(localstatedir)/log
|
||||
$(MKDIR_P) $(DESTDIR)$(localstatedir)/run
|
||||
|
||||
EXTRA_DIST += \
|
||||
daemon/daemon.dox \
|
||||
daemon/protocol.dox \
|
||||
daemon/x52d.service.in \
|
||||
daemon/x52d_client.h \
|
||||
daemon/x52d_clock.h \
|
||||
daemon/x52d_config.def \
|
||||
daemon/x52d_config.h \
|
||||
daemon/x52d_const.h \
|
||||
daemon/x52d_device.h \
|
||||
daemon/x52d_io.h \
|
||||
daemon/x52d_mouse.h \
|
||||
daemon/x52d_notify.h \
|
||||
daemon/x52d_command.h \
|
||||
daemon/x52dcomm.h \
|
||||
daemon/x52dcomm-internal.h \
|
||||
daemon/x52d.conf
|
||||
|
||||
# Test cases
|
||||
EXTRA_DIST += \
|
||||
daemon/test_daemon_comm.py \
|
||||
daemon/tests/config/args.tc \
|
||||
daemon/tests/config/clock.tc \
|
||||
daemon/tests/config/led.tc \
|
||||
daemon/tests/config/mouse.tc \
|
||||
daemon/tests/logging/error.tc \
|
||||
daemon/tests/logging/global.tc \
|
||||
daemon/tests/logging/module.tc \
|
||||
daemon/tests/cli.tc
|
||||
|
||||
TESTS += daemon/test_daemon_comm.py
|
||||
|
||||
if HAVE_CMOCKA
|
||||
check_PROGRAMS += x52d-mouse-test
|
||||
|
||||
x52d_mouse_test_SOURCES = \
|
||||
daemon/x52d_mouse_test.c \
|
||||
daemon/x52d_mouse.c
|
||||
x52d_mouse_test_CFLAGS = \
|
||||
-DLOCALEDIR='"$(localedir)"' \
|
||||
-I $(top_srcdir) \
|
||||
-I $(top_srcdir)/libx52 \
|
||||
-I $(top_srcdir)/libx52io \
|
||||
-I $(top_srcdir)/lib/pinelog \
|
||||
$(WARN_CFLAGS) @CMOCKA_CFLAGS@
|
||||
x52d_mouse_test_LDFLAGS = @CMOCKA_LIBS@ $(WARN_LDFLAGS)
|
||||
x52d_mouse_test_LDADD = \
|
||||
lib/pinelog/libpinelog.la \
|
||||
@LTLIBINTL@
|
||||
|
||||
TESTS += x52d-mouse-test
|
||||
endif
|
||||
|
||||
if HAVE_SYSTEMD
|
||||
if !IS_MAKE_DISTCHECK
|
||||
SED_ARGS = s,%bindir%,$(bindir),g
|
||||
x52d.service: daemon/x52d.service.in
|
||||
$(AM_V_GEN) $(SED) -e '$(SED_ARGS)' $< > $@
|
||||
systemdsystemunit_DATA = x52d.service
|
||||
endif
|
||||
endif
|
|
@ -0,0 +1,47 @@
|
|||
/**
|
||||
@page x52d X52 driver daemon
|
||||
|
||||
\b x52d is a daemon program that manages the X52 device in a similar fashion to
|
||||
the Windows X52 driver. It currently manages the following:
|
||||
|
||||
- LED state
|
||||
- LED brightness
|
||||
- MFD brightness
|
||||
- Clock display on MFD
|
||||
|
||||
# Command line arguments
|
||||
|
||||
- \c -f - Run daemon in foreground (default: no)
|
||||
- \c -v - Increase logging verbosity (default: log warnings)
|
||||
- \c -q - Reduce logging verbosity to minimum (default: no)
|
||||
- \c -l - Path to log file
|
||||
- \c -c - Path to configuration file
|
||||
- \c -p - Path to PID file
|
||||
- \c -o - Configuration override - only applied during startup
|
||||
- \c -s - Path to command socket (see \ref x52d_protocol)
|
||||
- \c -b - Path to notify socket
|
||||
|
||||
# Configuration file
|
||||
|
||||
\b x52d can be controlled by means of a configuration file. The default location
|
||||
of the configuration file is in `$(SYSCONFDIR)/x52d/x52d.conf`. The configuration
|
||||
file is an INI style file, and the default configuration is as listed below:
|
||||
|
||||
\include x52d.conf
|
||||
|
||||
## Configuration overrides
|
||||
|
||||
Configuration overrides are a means of testing a configuration parameter for a
|
||||
single instance of \b x52d, or to override the default configuration. The syntax
|
||||
for an override is \c section.key=value, where \c section, \c key and \c value
|
||||
correspond to the configuration \b section, \b key and \b value respectively.
|
||||
|
||||
For example, to override the secondary clock timezone to US Eastern Time, use
|
||||
the following syntax. Note that while the section and key are case-insensitive,
|
||||
the value may be case-sensitive, depending on which parameter is being
|
||||
overridden.
|
||||
|
||||
@code{.unparsed}
|
||||
-o clock.secondary=America/New_York
|
||||
@endcode
|
||||
*/
|
|
@ -0,0 +1,308 @@
|
|||
/**
|
||||
@page x52d_protocol X52 daemon socket communication protocol
|
||||
|
||||
The X52 daemon creates a Unix domain stream socket, by default at
|
||||
`$(LOCALSTATEDIR)/run/x52d.cmd` and listens for connection requests from
|
||||
clients at this location. This can be overridden by passing the -s flag when
|
||||
starting the daemon.
|
||||
|
||||
# Protocol Overview
|
||||
|
||||
\b x52d requires that clients send it commands as a series of NUL terminated
|
||||
strings, without any interleaving space. The command should be sent in a
|
||||
single `send` call, and the client may expect a response in a single `recv`
|
||||
call.
|
||||
|
||||
The `send` call must send exactly the number of bytes in the command text.
|
||||
Extra bytes will be treated as additional arguments, which would cause the
|
||||
command to fail. It is recommended that the `recv` call uses a 1024 byte buffer
|
||||
to read the data. Responses will never exceed this length.
|
||||
|
||||
# Responses
|
||||
|
||||
The daemon sends the response as a series of NUL terminated strings, without
|
||||
any interleaving space. The first string is always one of the following:
|
||||
|
||||
- \c OK
|
||||
- \c ERR
|
||||
- \c DATA
|
||||
|
||||
This determines whether the request was successful or not, and subsequent
|
||||
strings describe the action, error or requested data.
|
||||
|
||||
# Examples
|
||||
|
||||
## Reloading configuration
|
||||
|
||||
- \b send <tt>config\0reload\0</tt>
|
||||
- \b recv <tt>OK\0config\0reload\0</tt>
|
||||
|
||||
## Reading mouse speed
|
||||
|
||||
- \b send <tt>config\0get\0mouse\0speed\0</tt>
|
||||
- \b recv <tt>DATA\0mouse\0speed\010\0</tt>
|
||||
|
||||
## Sending an invalid command
|
||||
|
||||
- \b send <tt>config reload</tt>
|
||||
- \b recv <tt>ERR\0Unknown command 'config reload'\0</tt>
|
||||
|
||||
# Commands
|
||||
|
||||
\b x52d commands are arranged in a hierarchical fashion as follows:
|
||||
|
||||
```
|
||||
<command-group> [<sub-command-group> [<sub-command-group> [...]]] <command> [<arguments>]
|
||||
```
|
||||
|
||||
The list of supported commands are shown below:
|
||||
|
||||
- @subpage proto_config
|
||||
- @subpage proto_logging
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
@page proto_config Configuration management
|
||||
|
||||
The \c config commands deal with \b x52d configuration subsystem, and have the
|
||||
following subcommands.
|
||||
|
||||
@tableofcontents
|
||||
|
||||
# Load configuration
|
||||
|
||||
The `config load` subgroup allows you to load a configuration from a given file
|
||||
(discarding anything that was already in memory), or reload the configuration
|
||||
from the command-line specified configuration file (or default configuration
|
||||
file if no option was given on the command line.)
|
||||
|
||||
## Load from file
|
||||
|
||||
\b Arguments
|
||||
|
||||
- `config`
|
||||
- `load`
|
||||
- \a path-to-file
|
||||
|
||||
\b Returns
|
||||
|
||||
- `OK`
|
||||
- `config`
|
||||
- `load`
|
||||
- \a path-to-file
|
||||
|
||||
\b Error
|
||||
|
||||
- `ERR`
|
||||
- <tt>Invalid file '/none' for 'config load' command</tt>
|
||||
|
||||
## Reload system configuration
|
||||
|
||||
\b Arguments
|
||||
|
||||
- `config`
|
||||
- `reload`
|
||||
|
||||
\b Returns
|
||||
|
||||
- `OK`
|
||||
- `config`
|
||||
- `reload`
|
||||
|
||||
# Save configuration
|
||||
|
||||
The `config save` subgroup requests the \b x52d daemon to save the current state
|
||||
of the configuration to disk. This is either the system configuration file, or
|
||||
may be a user specified configuration file. Note that this will be created with
|
||||
the permissions of the running daemon, which may be running as root.
|
||||
|
||||
## Dump configuration to file
|
||||
|
||||
\b Arguments
|
||||
|
||||
- `config`
|
||||
- `dump`
|
||||
- \a path-to-file
|
||||
|
||||
\b Returns
|
||||
|
||||
- `OK`
|
||||
- `config`
|
||||
- `dump`
|
||||
- \a path-to-file
|
||||
|
||||
\b Error
|
||||
|
||||
- `ERR`
|
||||
- <tt>Invalid file '/none' for 'config dump' command</tt>
|
||||
|
||||
## Save system configuration
|
||||
|
||||
\b Arguments
|
||||
|
||||
- `config`
|
||||
- `save`
|
||||
|
||||
\b Returns
|
||||
|
||||
- `OK`
|
||||
- `config`
|
||||
- `save`
|
||||
|
||||
# Retrieve configuration parameter
|
||||
|
||||
The `config get` command requests a specific configuration value, given the
|
||||
section and the key. Refer to \ref x52d for the section and key names, as these
|
||||
are derived from the base configuration.
|
||||
|
||||
\b Arguments
|
||||
|
||||
- `config`
|
||||
- `get`
|
||||
- \a section
|
||||
- \a key
|
||||
|
||||
\b Returns
|
||||
|
||||
- `DATA`
|
||||
- \a section
|
||||
- \a key
|
||||
- \a value
|
||||
|
||||
\b Example
|
||||
|
||||
```
|
||||
DATA\0mouse\0enabled\0true\0
|
||||
```
|
||||
|
||||
<b>Error example</b>
|
||||
|
||||
```
|
||||
ERR\0Error getting 'foo.bar'\0
|
||||
```
|
||||
|
||||
# Set configuration parameter
|
||||
|
||||
The `config set` command requests the \b x52d daemon to set the given (section,
|
||||
key) parameter to the given value. The daemon will treat it the same way as if
|
||||
it was being read from the configuration file, i.e., it will follow identical
|
||||
parsing logic, including ignoring unknown keys and not reporting errors for them.
|
||||
|
||||
A side effect of this is that the client could request a set for any arbitrary
|
||||
section and key pair, and if that pair was not recognized, it would be ignored,
|
||||
but the daemon would still send an `OK` response.
|
||||
|
||||
This will set the value within the configuration memory structures, and will
|
||||
immediately invoke the relevant callback to update the rest of the threads or
|
||||
device state.
|
||||
|
||||
\b Arguments
|
||||
|
||||
- `config`
|
||||
- `set`
|
||||
- \a section
|
||||
- \a key
|
||||
- \a value
|
||||
|
||||
\b Returns
|
||||
|
||||
- `OK`
|
||||
- `config`
|
||||
- `set`
|
||||
- \a section
|
||||
- \a key
|
||||
- \a value
|
||||
|
||||
<b>Error example</b>
|
||||
|
||||
```
|
||||
ERR\0Error 22 setting 'led.fire'='none': Invalid argument\0
|
||||
```
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
@page proto_logging Logging management
|
||||
|
||||
The \c logging commands allow the user to fine tune the logging configuration
|
||||
of \c x52d as well as adjust the log levels for either all the modules, or for
|
||||
each of the modules individually.
|
||||
|
||||
While the `-v` and `-q` command line options allow you to either increase the
|
||||
logging verbosity or suppress it entirely, they are required to be specified at
|
||||
program startup. On the other hand, having the `logging` commands allows the
|
||||
user to fine tune the logging while the daemon is running.
|
||||
|
||||
@tableofcontents
|
||||
|
||||
# Modules
|
||||
|
||||
\c x52d is split into several modules as far as logging is concerned. The list
|
||||
of modules is below:
|
||||
|
||||
- \c Config
|
||||
- \c Cllient
|
||||
- \c Clock
|
||||
- \c Command
|
||||
- \c Device
|
||||
- \c IO
|
||||
- \c LED
|
||||
- \c Mouse
|
||||
- \c Notify
|
||||
|
||||
# Logging levels
|
||||
|
||||
The following is a list of supported logging levels. Each level logs the ones
|
||||
above it as well as the current level
|
||||
|
||||
- \c none - Disable logging entirely
|
||||
- \c fatal - Log fatal messages
|
||||
- \c error - Log error messages
|
||||
- \c warning - Log warning messages
|
||||
- \c info - Log informational messages
|
||||
- \c debug - Log debug messages
|
||||
- \c trace - Log trace messages - useful for tracing program flow.
|
||||
- \c default - Not a level, but used when configuring module log levels, makes
|
||||
the module log level fallback to the global log level.
|
||||
|
||||
# Show logging configuration
|
||||
|
||||
The `logging show` command takes in an optional module name, as listed in the
|
||||
Modules section above. It returns the module name, if specified, and the log
|
||||
level for that module. If the module is configured to fallback to the global
|
||||
level, then it will return the global level.
|
||||
|
||||
\b Arguments
|
||||
|
||||
- `logging`
|
||||
- `show`
|
||||
- \a module-name (Optional)
|
||||
|
||||
\b Returns
|
||||
|
||||
- `DATA`
|
||||
- <tt>\a module-name</tt> (if specified)
|
||||
- \a log-level
|
||||
|
||||
# Set logging configuration
|
||||
|
||||
The `logging set` command takes in the optional module name and the log level
|
||||
and sets the log level for that module, if specified, or the global level
|
||||
otherwise.
|
||||
|
||||
\b Arguments
|
||||
|
||||
- `logging`
|
||||
- `set`
|
||||
- \a module-name (Optional)
|
||||
- \a log-level
|
||||
|
||||
\b Returns
|
||||
|
||||
- `OK`
|
||||
- `logging`
|
||||
- `set`
|
||||
- <tt>\a module-name</tt> (if specified)
|
||||
- \a log-level
|
||||
*/
|
|
@ -0,0 +1,203 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Test communication with x52d and verify that the behavior is as expected"""
|
||||
# pylint: disable=consider-using-f-string
|
||||
|
||||
import glob
|
||||
import os
|
||||
import os.path
|
||||
import platform
|
||||
import shlex
|
||||
import signal
|
||||
import subprocess
|
||||
import tempfile
|
||||
import time
|
||||
import sys
|
||||
|
||||
class TestCase:
|
||||
"""TestCase class handles an individual test case"""
|
||||
def __init__(self, data):
|
||||
"""Create a new test case"""
|
||||
self.desc = None
|
||||
self.in_cmd = None
|
||||
self.exp_resp = None
|
||||
self.parse(data)
|
||||
|
||||
def parse(self, data):
|
||||
"""Parses a string of the following form:
|
||||
<description>
|
||||
space separated input line, possibly quoted
|
||||
space separated expected response, possibly quoted
|
||||
"""
|
||||
self.desc, in_cmd, exp_resp = data.splitlines()
|
||||
self.in_cmd = shlex.split(in_cmd)
|
||||
self.exp_resp = '\0'.join(shlex.split(exp_resp)).encode() + b'\0'
|
||||
|
||||
def execute(self, index, suite):
|
||||
"""Execute the test case and return the result in TAP format"""
|
||||
def dump_failed(name, value):
|
||||
"""Dump the failed test case"""
|
||||
print("# {}".format(name))
|
||||
for argv in value.decode().split('\0'):
|
||||
print("#\t {}".format(argv))
|
||||
print()
|
||||
|
||||
def print_result(passed):
|
||||
"""Print the test case result and description"""
|
||||
out = "ok {} - {}".format(index+1, self.desc)
|
||||
if not passed:
|
||||
out = "not " + out
|
||||
print(out)
|
||||
|
||||
cmd = [suite.find_control_program(),
|
||||
'-s', suite.command, '--',
|
||||
*self.in_cmd]
|
||||
|
||||
testcase = subprocess.run(cmd, stdout=subprocess.PIPE, check=False)
|
||||
if testcase.returncode != 0:
|
||||
print_result(False)
|
||||
print("# x52ctl returned code: {}".format(testcase.returncode))
|
||||
dump_failed("Expected", self.exp_resp)
|
||||
dump_failed("Got", testcase.stdout)
|
||||
elif testcase.stdout != self.exp_resp:
|
||||
print_result(False)
|
||||
dump_failed("Expected", self.exp_resp)
|
||||
dump_failed("Got", testcase.stdout)
|
||||
else:
|
||||
print_result(True)
|
||||
|
||||
|
||||
class Test:
|
||||
"""Test class runs a series of unit tests"""
|
||||
|
||||
def __init__(self):
|
||||
"""Create a new instance of the Test class"""
|
||||
self.program = self.find_daemon_program()
|
||||
self.tmpdir = tempfile.TemporaryDirectory() # pylint: disable=consider-using-with
|
||||
self.command = os.path.join(self.tmpdir.name, "x52d.cmd")
|
||||
self.notify = os.path.join(self.tmpdir.name, "x52d.notify")
|
||||
self.daemon = None
|
||||
self.testcases = []
|
||||
|
||||
def __enter__(self):
|
||||
"""Context manager entry"""
|
||||
self.launch_daemon()
|
||||
return self
|
||||
|
||||
def __exit__(self, *exc):
|
||||
"""Context manager exit"""
|
||||
self.terminate_daemon()
|
||||
self.tmpdir.cleanup()
|
||||
|
||||
@staticmethod
|
||||
def find_daemon_program():
|
||||
"""Find the daemon program. This script should be run from the
|
||||
root of the build directory"""
|
||||
daemon_candidates = glob.glob('**/x52d', recursive=True)
|
||||
if not daemon_candidates:
|
||||
print("Bail out! Unable to find X52 daemon.")
|
||||
sys.exit(1)
|
||||
|
||||
return os.path.realpath(daemon_candidates[0])
|
||||
|
||||
@staticmethod
|
||||
def find_control_program():
|
||||
"""Find the control program. This script should be run from the
|
||||
root of the build directory"""
|
||||
ctl_candidates = glob.glob('**/x52ctl', recursive=True)
|
||||
if not ctl_candidates:
|
||||
print("Bail out! Unable to find x52ctl.")
|
||||
sys.exit(1)
|
||||
|
||||
return os.path.realpath(ctl_candidates[0])
|
||||
|
||||
def launch_daemon(self):
|
||||
"""Launch an instance of the running daemon"""
|
||||
if self.daemon is not None:
|
||||
# We've already started the daemon, check if it is still running
|
||||
if self.daemon.poll() is None:
|
||||
return
|
||||
|
||||
self.daemon = None
|
||||
|
||||
daemon_cmdline = [
|
||||
self.program,
|
||||
"-f", # Run in foreground
|
||||
"-q", # Quiet logging
|
||||
"-c", os.path.join(self.tmpdir.name, "x52d.cfg"), # Default config file
|
||||
"-l", os.path.join(self.tmpdir.name, "x52d.log"), # Output logs to log file
|
||||
"-p", os.path.join(self.tmpdir.name, "x52d.pid"), # PID file
|
||||
"-s", self.command, # Command socket path
|
||||
"-b", self.notify, # Notification socket path
|
||||
]
|
||||
|
||||
# Create empty config file
|
||||
with open(daemon_cmdline[4], 'w', encoding='utf-8'):
|
||||
pass
|
||||
|
||||
self.daemon = subprocess.Popen(daemon_cmdline) # pylint: disable=consider-using-with
|
||||
|
||||
print("# Sleeping 2 seconds for daemon to start")
|
||||
time.sleep(2)
|
||||
|
||||
def terminate_daemon(self):
|
||||
"""Terminate a running daemon"""
|
||||
if self.daemon is None:
|
||||
return
|
||||
|
||||
# Send a SIGTERM to the daemon
|
||||
os.kill(self.daemon.pid, signal.SIGTERM)
|
||||
try:
|
||||
self.daemon.wait(timeout=15)
|
||||
except subprocess.TimeoutExpired:
|
||||
# Forcibly kill the running process
|
||||
self.daemon.kill()
|
||||
finally:
|
||||
self.daemon = None
|
||||
|
||||
def append(self, testcase):
|
||||
"""Add one testcase to the test case list"""
|
||||
self.testcases.append(testcase)
|
||||
|
||||
def extend(self, testcases):
|
||||
"""Add one or more testcases to the test case list"""
|
||||
self.testcases.extend(testcases)
|
||||
|
||||
@staticmethod
|
||||
def dump_failed(name, value):
|
||||
"""Dump the failed test case"""
|
||||
print("# {}".format(name))
|
||||
for argv in value.decode().split('\0'):
|
||||
print("#\t {}".format(argv))
|
||||
print()
|
||||
|
||||
def run_tests(self):
|
||||
"""Run test cases"""
|
||||
print("1..{}".format(len(self.testcases)))
|
||||
for index, testcase in enumerate(self.testcases):
|
||||
testcase.execute(index, self)
|
||||
|
||||
def find_and_parse_testcase_files(self):
|
||||
"""Find and parse *.tc files"""
|
||||
basedir = os.path.dirname(os.path.realpath(__file__))
|
||||
pattern = os.path.join(basedir, 'tests', '**', '*.tc')
|
||||
tc_files = sorted(glob.glob(pattern, recursive=True))
|
||||
|
||||
for tc_file in tc_files:
|
||||
with open(tc_file, encoding='utf-8') as tc_fd:
|
||||
# Test cases are separated by blank lines
|
||||
testcases = tc_fd.read().split('\n\n')
|
||||
self.extend(TestCase(tc_data) for tc_data in testcases)
|
||||
|
||||
def main():
|
||||
"""Main routine adds test cases to the Test class and runs them"""
|
||||
# Only run the tests on Linux platform
|
||||
if platform.system() != 'Linux':
|
||||
print('1..0 # Skipping tests on', platform.system())
|
||||
return
|
||||
|
||||
with Test() as test:
|
||||
test.find_and_parse_testcase_files()
|
||||
test.run_tests()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -0,0 +1,3 @@
|
|||
Check for invalid command
|
||||
foo
|
||||
ERR "Unknown command 'foo'"
|
|
@ -0,0 +1,83 @@
|
|||
Configuration with insufficient arguments
|
||||
config
|
||||
ERR "Insufficient arguments for 'config' command"
|
||||
|
||||
Load with invalid file argument
|
||||
config load ''
|
||||
ERR "Invalid file '' for 'config load' command"
|
||||
|
||||
Load with nonexistent file argument
|
||||
config load /nonexistent
|
||||
ERR "Invalid file '/nonexistent' for 'config load' command"
|
||||
|
||||
Load with empty file argument
|
||||
config load /dev/null
|
||||
OK config load /dev/null
|
||||
|
||||
Load with extra arguments
|
||||
config load /dev/null ''
|
||||
ERR "Unexpected arguments for 'config load' command; got 4, expected 3"
|
||||
|
||||
Load with missing argument
|
||||
config load
|
||||
ERR "Unexpected arguments for 'config load' command; got 2, expected 3"
|
||||
|
||||
Reload configuration
|
||||
config reload
|
||||
OK config reload
|
||||
|
||||
Reload configuration with extra arguments
|
||||
config reload ''
|
||||
ERR "Unexpected arguments for 'config reload' command; got 3, expected 2"
|
||||
|
||||
Dump configuration with insufficient arguments
|
||||
config dump
|
||||
ERR "Unexpected arguments for 'config dump' command; got 2, expected 3"
|
||||
|
||||
Dump configuration with invalid file
|
||||
config dump ''
|
||||
ERR "Invalid file '' for 'config dump' command"
|
||||
|
||||
Dump configuration with extra arguments
|
||||
config dump /dev/null ''
|
||||
ERR "Unexpected arguments for 'config dump' command; got 4, expected 3"
|
||||
|
||||
Dump configuration to /dev/null
|
||||
config dump /dev/null
|
||||
OK config dump /dev/null
|
||||
|
||||
Save configuration
|
||||
config save
|
||||
OK config save
|
||||
|
||||
Save configuration with extra arguments
|
||||
config save ''
|
||||
ERR "Unexpected arguments for 'config save' command; got 3, expected 2"
|
||||
|
||||
Config command with empty subcommand
|
||||
config ''
|
||||
ERR "Unknown subcommand '' for 'config' command"
|
||||
|
||||
Config command with unknown subcommand
|
||||
config foo
|
||||
ERR "Unknown subcommand 'foo' for 'config' command"
|
||||
|
||||
Get configuration with fewer arguments
|
||||
config get
|
||||
ERR "Unexpected arguments for 'config get' command; got 2, expected 4"
|
||||
|
||||
Get configuration with extra arguments
|
||||
config get foo bar baz
|
||||
ERR "Unexpected arguments for 'config get' command; got 5, expected 4"
|
||||
|
||||
Set configuration with fewer arguments
|
||||
config set
|
||||
ERR "Unexpected arguments for 'config set' command; got 2, expected 5"
|
||||
|
||||
Set configuration with extra arguments
|
||||
config set foo bar baz quux
|
||||
ERR "Unexpected arguments for 'config set' command; got 6, expected 5"
|
||||
|
||||
Get configuration of unknown parameter
|
||||
config get foo bar
|
||||
ERR "Error getting 'foo.bar'"
|
|
@ -0,0 +1,183 @@
|
|||
Set clock enabled to false
|
||||
config set clock enabled false
|
||||
OK config set clock enabled false
|
||||
|
||||
Verify clock enabled was set to false
|
||||
config get clock enabled
|
||||
DATA clock enabled false
|
||||
|
||||
Set clock enabled to true
|
||||
config set clock enabled true
|
||||
OK config set clock enabled true
|
||||
|
||||
Verify clock enabled was set to true
|
||||
config get clock enabled
|
||||
DATA clock enabled true
|
||||
|
||||
Set clock PrimaryIsLocal to no
|
||||
config set clock PrimaryIsLocal no
|
||||
OK config set clock PrimaryIsLocal no
|
||||
|
||||
Verify clock PrimaryIsLocal was set to false (no)
|
||||
config get clock PrimaryIsLocal
|
||||
DATA clock PrimaryIsLocal false
|
||||
|
||||
Set clock PrimaryIsLocal to yes
|
||||
config set clock PrimaryIsLocal yes
|
||||
OK config set clock PrimaryIsLocal yes
|
||||
|
||||
Verify clock PrimaryIsLocal was set to true (yes)
|
||||
config get clock PrimaryIsLocal
|
||||
DATA clock PrimaryIsLocal true
|
||||
|
||||
Set clock PrimaryIsLocal to invalid value
|
||||
config set clock PrimaryIsLocal foo
|
||||
ERR "Error 22 setting 'clock.PrimaryIsLocal'='foo': Invalid argument"
|
||||
|
||||
Verify clock PrimaryIsLocal was not changed
|
||||
config get clock PrimaryIsLocal
|
||||
DATA clock PrimaryIsLocal true
|
||||
|
||||
Set clock secondary to America/Los_Angeles
|
||||
config set clock secondary America/Los_Angeles
|
||||
OK config set clock secondary America/Los_Angeles
|
||||
|
||||
Verify clock secondary was set to America/Los_Angeles
|
||||
config get clock secondary
|
||||
DATA clock secondary America/Los_Angeles
|
||||
|
||||
Set clock secondary to UTC
|
||||
config set clock secondary UTC
|
||||
OK config set clock secondary UTC
|
||||
|
||||
Verify clock secondary was set to UTC
|
||||
config get clock secondary
|
||||
DATA clock secondary UTC
|
||||
|
||||
Set clock tertiary to America/Los_Angeles
|
||||
config set clock tertiary America/Los_Angeles
|
||||
OK config set clock tertiary America/Los_Angeles
|
||||
|
||||
Verify clock tertiary was set to America/Los_Angeles
|
||||
config get clock tertiary
|
||||
DATA clock tertiary America/Los_Angeles
|
||||
|
||||
Set clock tertiary to UTC
|
||||
config set clock tertiary UTC
|
||||
OK config set clock tertiary UTC
|
||||
|
||||
Verify clock tertiary was set to UTC
|
||||
config get clock tertiary
|
||||
DATA clock tertiary UTC
|
||||
|
||||
Set clock formatprimary to 24hr
|
||||
config set clock formatprimary 24hr
|
||||
OK config set clock formatprimary 24hr
|
||||
|
||||
Verify clock formatprimary was set to 24hr
|
||||
config get clock formatprimary
|
||||
DATA clock formatprimary '24 hour'
|
||||
|
||||
Set clock formatprimary to 12hr
|
||||
config set clock formatprimary 12hr
|
||||
OK config set clock formatprimary 12hr
|
||||
|
||||
Verify clock formatprimary was set to 12hr
|
||||
config get clock formatprimary
|
||||
DATA clock formatprimary '12 hour'
|
||||
|
||||
Set clock formatsecondary to 24hr
|
||||
config set clock formatsecondary 24hr
|
||||
OK config set clock formatsecondary 24hr
|
||||
|
||||
Verify clock formatsecondary was set to 24hr
|
||||
config get clock formatsecondary
|
||||
DATA clock formatsecondary '24 hour'
|
||||
|
||||
Set clock formatsecondary to 12hr
|
||||
config set clock formatsecondary 12hr
|
||||
OK config set clock formatsecondary 12hr
|
||||
|
||||
Verify clock formatsecondary was set to 12hr
|
||||
config get clock formatsecondary
|
||||
DATA clock formatsecondary '12 hour'
|
||||
|
||||
Set clock formattertiary to 24
|
||||
config set clock formattertiary 24
|
||||
OK config set clock formattertiary 24
|
||||
|
||||
Verify clock formattertiary was set to 24
|
||||
config get clock formattertiary
|
||||
DATA clock formattertiary '24 hour'
|
||||
|
||||
Set clock formattertiary to 12
|
||||
config set clock formattertiary 12
|
||||
OK config set clock formattertiary 12
|
||||
|
||||
Verify clock formattertiary was set to 12
|
||||
config get clock formattertiary
|
||||
DATA clock formattertiary '12 hour'
|
||||
|
||||
Set clock formattertiary to invalid value
|
||||
config set clock formattertiary '12 hour'
|
||||
ERR "Error 22 setting 'clock.formattertiary'='12 hour': Invalid argument"
|
||||
|
||||
Verify clock formattertiary was not changed
|
||||
config get clock formattertiary
|
||||
DATA clock formattertiary '12 hour'
|
||||
|
||||
Set clock dateformat to yymmdd
|
||||
config set clock dateformat yymmdd
|
||||
OK config set clock dateformat yymmdd
|
||||
|
||||
Verify clock dateformat was set to yymmdd
|
||||
config get clock dateformat
|
||||
DATA clock dateformat YY-MM-DD
|
||||
|
||||
Set clock dateformat to mmddyy
|
||||
config set clock dateformat mmddyy
|
||||
OK config set clock dateformat mmddyy
|
||||
|
||||
Verify clock dateformat was set to mmddyy
|
||||
config get clock dateformat
|
||||
DATA clock dateformat MM-DD-YY
|
||||
|
||||
Set clock dateformat to ddmmyy
|
||||
config set clock dateformat ddmmyy
|
||||
OK config set clock dateformat ddmmyy
|
||||
|
||||
Verify clock dateformat was set to ddmmyy
|
||||
config get clock dateformat
|
||||
DATA clock dateformat DD-MM-YY
|
||||
|
||||
Set clock dateformat to yy-mm-dd
|
||||
config set clock dateformat yy-mm-dd
|
||||
OK config set clock dateformat yy-mm-dd
|
||||
|
||||
Verify clock dateformat was set to yy-mm-dd
|
||||
config get clock dateformat
|
||||
DATA clock dateformat YY-MM-DD
|
||||
|
||||
Set clock dateformat to mm-dd-yy
|
||||
config set clock dateformat mm-dd-yy
|
||||
OK config set clock dateformat mm-dd-yy
|
||||
|
||||
Verify clock dateformat was set to mm-dd-yy
|
||||
config get clock dateformat
|
||||
DATA clock dateformat MM-DD-YY
|
||||
|
||||
Set clock dateformat to dd-mm-yy
|
||||
config set clock dateformat dd-mm-yy
|
||||
OK config set clock dateformat dd-mm-yy
|
||||
|
||||
Verify clock dateformat was set to dd-mm-yy
|
||||
config get clock dateformat
|
||||
DATA clock dateformat DD-MM-YY
|
||||
|
||||
Set clock dateformat to invalid value
|
||||
config set clock dateformat foo-bar-baz
|
||||
ERR "Error 22 setting 'clock.dateformat'='foo-bar-baz': Invalid argument"
|
||||
|
||||
Verify clock dateformat was not changed
|
||||
config get clock dateformat
|
||||
DATA clock dateformat DD-MM-YY
|
|
@ -0,0 +1,47 @@
|
|||
Set LED Fire to off
|
||||
config set led fire off
|
||||
OK config set led fire off
|
||||
|
||||
Verify LED Fire was set to off
|
||||
config get led fire
|
||||
DATA led fire off
|
||||
|
||||
Set LED Fire to on
|
||||
config set led fire on
|
||||
OK config set led fire on
|
||||
|
||||
Verify LED Fire was set to on
|
||||
config get led fire
|
||||
DATA led fire on
|
||||
|
||||
Set LED Fire to red TODO: This passes since the LED parser doesn't know the LED name
|
||||
config set led fire red
|
||||
OK config set led fire red
|
||||
|
||||
Verify LED Fire was set to red
|
||||
config get led fire
|
||||
DATA led fire red
|
||||
|
||||
Set LED Fire to amber TODO: This passes since the LED parser doesn't know the LED name
|
||||
config set led fire amber
|
||||
OK config set led fire amber
|
||||
|
||||
Verify LED Fire was set to amber
|
||||
config get led fire
|
||||
DATA led fire amber
|
||||
|
||||
Set LED Fire to green TODO: This passes since the LED parser doesn't know the LED name
|
||||
config set led fire green
|
||||
OK config set led fire green
|
||||
|
||||
Verify LED Fire was set to green
|
||||
config get led fire
|
||||
DATA led fire green
|
||||
|
||||
Set LED Fire to invalid value
|
||||
config set led fire foo
|
||||
ERR "Error 22 setting 'led.fire'='foo': Invalid argument"
|
||||
|
||||
Verify LED Fire was not changed
|
||||
config get led fire
|
||||
DATA led fire green
|
|
@ -0,0 +1,47 @@
|
|||
Set mouse speed to 32
|
||||
config set mouse speed 32
|
||||
OK config set mouse speed 32
|
||||
|
||||
Verify mouse speed is set to 32
|
||||
config get mouse speed
|
||||
DATA mouse speed 32
|
||||
|
||||
Set mouse speed to invalid value
|
||||
config set mouse speed off
|
||||
ERR "Error 22 setting 'mouse.speed'='off': Invalid argument"
|
||||
|
||||
Verify mouse speed is unchanged
|
||||
config get mouse speed
|
||||
DATA mouse speed 32
|
||||
|
||||
Set mouse speed to 33 (Exceeds max speed)
|
||||
config set mouse speed 33
|
||||
OK config set mouse speed 33
|
||||
|
||||
Set mouse speed to 20 (In multiplier range)
|
||||
config set mouse speed 20
|
||||
OK config set mouse speed 20
|
||||
|
||||
Set mouse speed to negative value
|
||||
config set mouse speed -1
|
||||
OK config set mouse speed -1
|
||||
|
||||
Reset mouse speed to minimum
|
||||
config set mouse speed 0
|
||||
OK config set mouse speed 0
|
||||
|
||||
Set mouse reverse scroll to enabled
|
||||
config set mouse reversescroll true
|
||||
OK config set mouse reversescroll true
|
||||
|
||||
Check if reverse scrolling is enabled
|
||||
config get mouse reversescroll
|
||||
DATA mouse reversescroll true
|
||||
|
||||
Set mouse reverse scroll to disabled
|
||||
config set mouse reversescroll false
|
||||
OK config set mouse reversescroll false
|
||||
|
||||
Check if reverse scrolling is disabled
|
||||
config get mouse reversescroll
|
||||
DATA mouse reversescroll false
|
|
@ -0,0 +1,15 @@
|
|||
Logging with insufficient arguments
|
||||
logging
|
||||
ERR "Insufficient arguments for 'logging' command"
|
||||
|
||||
Get logging level with extra arguments
|
||||
logging show foo bar
|
||||
ERR "Unexpected arguments for 'logging show' command; got 4, expected 2 or 3"
|
||||
|
||||
Set logging level with insufficient arguments
|
||||
logging set
|
||||
ERR "Unexpected arguments for 'logging set' command; got 2, expected 3 or 4"
|
||||
|
||||
Invalid logging subcommand
|
||||
logging foo
|
||||
ERR "Unknown subcommand 'foo' for 'logging' command"
|
|
@ -0,0 +1,15 @@
|
|||
Set global logging level to error
|
||||
logging set error
|
||||
OK logging set error
|
||||
|
||||
Get global logging level (should be error)
|
||||
logging show
|
||||
DATA global error
|
||||
|
||||
Set global logging level to default - should return error
|
||||
logging set default
|
||||
ERR "'default' level is not valid without a module"
|
||||
|
||||
Set global logging level to unknown value - should return error
|
||||
logging set foo
|
||||
ERR "Unknown level 'foo' for 'logging set' command"
|
|
@ -0,0 +1,27 @@
|
|||
Set config module logging level to error
|
||||
logging set config error
|
||||
OK logging set config error
|
||||
|
||||
Get config module logging level
|
||||
logging show config
|
||||
DATA config error
|
||||
|
||||
Get configuration for invalid module
|
||||
logging show foo
|
||||
ERR "Invalid module 'foo'"
|
||||
|
||||
Set module logging level for invalid module
|
||||
logging set foo error
|
||||
ERR "Invalid module 'foo'"
|
||||
|
||||
Set invalid logging level for module
|
||||
logging set config foo
|
||||
ERR "Unknown level 'foo' for 'logging set' command"
|
||||
|
||||
Set module logging level to default
|
||||
logging set config default
|
||||
OK logging set config default
|
||||
|
||||
Get module logging level (should be error - same as global)
|
||||
logging show config
|
||||
DATA config error
|
|
@ -0,0 +1,188 @@
|
|||
/*
|
||||
* Saitek X52 Pro MFD & LED driver - Daemon controller
|
||||
*
|
||||
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
@page x52ctl Command Line controller to X52 daemon
|
||||
|
||||
\htmlonly
|
||||
<b>x52ctl</b> - Command line controller to X52 daemon
|
||||
\endhtmlonly
|
||||
|
||||
# SYNOPSIS
|
||||
<tt>\b x52ctl [\a -i] [\a -s socket-path] [command] </tt>
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
x52ctl is a program that can be used to communicate with the X52 daemon. It can
|
||||
be used either as a one-shot program that can be run from another program or
|
||||
script, or it can be run interactively.
|
||||
|
||||
Commands are sent to the running daemon, and responses are written to standard
|
||||
output.
|
||||
|
||||
If not running interactively, then you must specify a command, or the program
|
||||
will exit with a failure exit code. If running interactively, the program will
|
||||
request input and send that to the daemon, until the user either enters the
|
||||
string "quit", or terminates input by using Ctrl+D.
|
||||
|
||||
# OPTIONS
|
||||
|
||||
- <tt>\b -i</tt>
|
||||
Run in interactive mode. Any additional non-option arguments are ignored.
|
||||
|
||||
- <tt>\b -s < \a socket-path ></tt>
|
||||
Use the socket at the given path. If this is not specified, then it uses a
|
||||
default socket.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "x52d_const.h"
|
||||
#include "x52dcomm.h"
|
||||
|
||||
#define APP_NAME "x52ctl"
|
||||
#if HAVE_FUNC_ATTRIBUTE_NORETURN
|
||||
__attribute__((noreturn))
|
||||
#endif
|
||||
static void usage(int exit_code)
|
||||
{
|
||||
fprintf(stderr, _("Usage: %s [-i] [-s socket-path] [command]\n"), APP_NAME);
|
||||
exit(exit_code);
|
||||
}
|
||||
|
||||
static int send_command(int sock_fd, int argc, char **argv)
|
||||
{
|
||||
int rc;
|
||||
char buffer[1024];
|
||||
int buflen;
|
||||
|
||||
buflen = x52d_format_command(argc, (const char **)argv, buffer, sizeof(buffer));
|
||||
if (buflen < 0) {
|
||||
if (errno == E2BIG) {
|
||||
fprintf(stderr, _("Argument length too long\n"));
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
rc = x52d_send_command(sock_fd, buffer, buflen, sizeof(buffer));
|
||||
if (rc >= 0) {
|
||||
if (write(STDOUT_FILENO, buffer, rc) < 0) {
|
||||
perror("write");
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
perror("x52d_send_command");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
bool interactive = false;
|
||||
char *socket_path = NULL;
|
||||
int opt;
|
||||
int sock_fd;
|
||||
int rc = EXIT_SUCCESS;
|
||||
|
||||
char buffer[1024];
|
||||
|
||||
/*
|
||||
* Parse command line arguments
|
||||
*
|
||||
* -i Interactive
|
||||
* -s Socket path
|
||||
*/
|
||||
while ((opt = getopt(argc, argv, "is:h")) != -1) {
|
||||
switch (opt) {
|
||||
case 'i':
|
||||
interactive = true;
|
||||
break;
|
||||
|
||||
case 's':
|
||||
socket_path = optarg;
|
||||
break;
|
||||
|
||||
case 'h':
|
||||
usage(EXIT_SUCCESS);
|
||||
break;
|
||||
|
||||
default:
|
||||
usage(EXIT_FAILURE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!interactive && optind >= argc) {
|
||||
usage(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/* Connect to the socket */
|
||||
sock_fd = x52d_dial_command(socket_path);
|
||||
if (sock_fd < 0) {
|
||||
perror("x52d_dial_command");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (interactive) {
|
||||
if (optind < argc) {
|
||||
fprintf(stderr,
|
||||
_("Running in interactive mode, ignoring extra arguments\n"));
|
||||
}
|
||||
|
||||
fputs("> ", stdout);
|
||||
while (fgets(buffer, sizeof(buffer), stdin) != NULL) {
|
||||
int sargc;
|
||||
char *sargv[512] = { 0 };
|
||||
int pos;
|
||||
|
||||
if (strcasecmp(buffer, "quit\n") == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Break the buffer into argc/argv */
|
||||
sargc = 0;
|
||||
pos = 0;
|
||||
while (buffer[pos]) {
|
||||
if (isspace(buffer[pos])) {
|
||||
buffer[pos] = '\0';
|
||||
pos++;
|
||||
} else {
|
||||
sargv[sargc] = &buffer[pos];
|
||||
sargc++;
|
||||
for (; buffer[pos] && !isspace(buffer[pos]); pos++);
|
||||
}
|
||||
}
|
||||
|
||||
if (send_command(sock_fd, sargc, sargv)) {
|
||||
rc = EXIT_FAILURE;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
fputs("\n> ", stdout);
|
||||
}
|
||||
|
||||
} else {
|
||||
if (send_command(sock_fd, argc - optind, &argv[optind])) {
|
||||
rc = EXIT_FAILURE;
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
cleanup:
|
||||
close(sock_fd);
|
||||
return rc;
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
#######################################################################
|
||||
# X52 Daemon Configuration
|
||||
######################################################################
|
||||
|
||||
# The settings below are the defaults. Note that the section and key
|
||||
# strings are case insensitive, but the values are not necessarily so,
|
||||
# especially for those referring to paths or timezone names.
|
||||
|
||||
######################################################################
|
||||
# Clock Settings
|
||||
######################################################################
|
||||
[Clock]
|
||||
|
||||
# Enabled controls whether the clock is enabled or not. Set this to no to
|
||||
# disable the clock update. Keep in mind that if the clock was originally
|
||||
# enabled on the X52, then disabling it here won't make the clock disappear on
|
||||
# the MFD. You will need to unplug and reattach the X52 to make the clock
|
||||
# disappear
|
||||
Enabled=yes
|
||||
|
||||
# PrimaryIsLocal controls whether the primary clock displays local time or UTC.
|
||||
# Set this to yes to display local time, no for UTC.
|
||||
PrimaryIsLocal=yes
|
||||
|
||||
# Secondary controls the timezone of the secondary clock. Use the standard
|
||||
# timezone name as defined by the Olson time database.
|
||||
Secondary=UTC
|
||||
|
||||
# Tertiary controls the timezone of the tertiary clock. Use the standard
|
||||
# timezone name as defined by the Olson time database.
|
||||
Tertiary=UTC
|
||||
|
||||
# PrimaryFormat controls the clock format of the primary clock. This is
|
||||
# either 12hr or 24hr, and can be abbreviated to 12 or 24
|
||||
FormatPrimary=12hr
|
||||
|
||||
# SecondaryFormat controls the clock format of the secondary clock. This is
|
||||
# either 12hr or 24hr, and can be abbreviated to 12 or 24
|
||||
FormatSecondary=12hr
|
||||
|
||||
# TertiaryFormat controls the clock format of the tertiary clock. This is
|
||||
# either 12hr or 24hr, and can be abbreviated to 12 or 24
|
||||
FormatTertiary=12hr
|
||||
|
||||
# DateFormat controls the format of the date display. This can be one of
|
||||
# ddmmyy, mmddyy or yymmdd. Alternate representations of these are
|
||||
# dd-mm-yy, mm-dd-yy or yy-mm-dd respectively.
|
||||
DateFormat=ddmmyy
|
||||
|
||||
######################################################################
|
||||
# LED Settings - only applicable to X52Pro
|
||||
######################################################################
|
||||
[LED]
|
||||
|
||||
# The LED settings map a color code or state to the corresponding LED.
|
||||
Fire=on
|
||||
Throttle=on
|
||||
A=green
|
||||
B=green
|
||||
D=green
|
||||
E=green
|
||||
T1=green
|
||||
T2=green
|
||||
T3=green
|
||||
POV=green
|
||||
Clutch=green
|
||||
|
||||
######################################################################
|
||||
# Brightness Settings
|
||||
######################################################################
|
||||
[Brightness]
|
||||
|
||||
# The brightness settings map the brightness value to the LEDs/MFD.
|
||||
MFD=128
|
||||
LED=128
|
||||
|
||||
######################################################################
|
||||
# Mouse - only valid on Linux
|
||||
######################################################################
|
||||
[Mouse]
|
||||
|
||||
# Enabled controls whether the virtual mouse is enabled or not.
|
||||
Enabled=yes
|
||||
|
||||
# Speed is proportional to the speed of updates to the virtual mouse
|
||||
Speed=0
|
||||
|
||||
# ReverseScroll reverses the direction of the virtual scroll wheel
|
||||
ReverseScroll=no
|
||||
|
||||
######################################################################
|
||||
# Profiles - only valid on Linux
|
||||
######################################################################
|
||||
[Profiles]
|
||||
# TODO: Profiles are used to map the buttons and axis to keyboard events, and
|
||||
# can be used to write macros. This is a placeholder only for now, and is not
|
||||
# supported yet.
|
||||
|
||||
# Directory is the location of the folder containing the individual profiles.
|
||||
Directory=/etc/x52d/profiles.d
|
||||
|
||||
# ClutchEnabled determines if the clutch button is treated specially
|
||||
ClutchEnabled=no
|
||||
|
||||
# ClutchLatched controls if the clutch button (if enabled) is a latched button
|
||||
# (press once to enter clutch mode, press again to exit clutch mode), or must
|
||||
# be held down to remain in clutch mode.
|
||||
ClutchLatched=no
|
||||
|
||||
##################
|
||||
#X52 Input Servic#
|
||||
#Version 0.2.2 #
|
||||
#OS: Linux #
|
||||
##################
|
|
@ -0,0 +1,10 @@
|
|||
[Unit]
|
||||
Description=X52 driver daemon
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=%bindir%/x52d -f -v
|
||||
ExecReload=kill -HUP $MAINPID
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
|
@ -0,0 +1,146 @@
|
|||
/*
|
||||
* Saitek X52 Pro MFD & LED driver - Client handling
|
||||
*
|
||||
* Copyright (C) 2022 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "pinelog.h"
|
||||
#include "x52d_client.h"
|
||||
#include "x52dcomm-internal.h"
|
||||
|
||||
void x52d_client_init(int client_fd[X52D_MAX_CLIENTS])
|
||||
{
|
||||
for (int i = 0; i < X52D_MAX_CLIENTS; i++) {
|
||||
client_fd[i] = INVALID_CLIENT;
|
||||
}
|
||||
}
|
||||
|
||||
bool x52d_client_register(int client_fd[X52D_MAX_CLIENTS], int sock_fd)
|
||||
{
|
||||
int fd;
|
||||
int i;
|
||||
|
||||
fd = accept(sock_fd, NULL, NULL);
|
||||
if (fd < 0) {
|
||||
PINELOG_ERROR(_("Error accepting client connection on socket fd %d: %s"),
|
||||
sock_fd, strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (x52d_set_socket_nonblocking(fd) < 0) {
|
||||
PINELOG_ERROR(_("Error marking client fd %d as nonblocking: %s"),
|
||||
fd, strerror(errno));
|
||||
goto error;
|
||||
}
|
||||
|
||||
for (i = 0; i < X52D_MAX_CLIENTS; i++) {
|
||||
if (client_fd[i] == INVALID_CLIENT) {
|
||||
PINELOG_TRACE("Accepted client %d on socket %d, slot %d", fd, sock_fd, i);
|
||||
client_fd[i] = fd;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* At this point, we've looped through the entirity of client_fd, but
|
||||
* have not registered an empty slot. We need to close the socket and
|
||||
* tell the caller that we haven't been able to register the client.
|
||||
*/
|
||||
PINELOG_TRACE("Maximum connections reached, closing socket %d", fd);
|
||||
error:
|
||||
close(fd);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool x52d_client_deregister(int client_fd[X52D_MAX_CLIENTS], int fd)
|
||||
{
|
||||
bool deregistered = false;
|
||||
|
||||
for (int i = 0; i < X52D_MAX_CLIENTS; i++) {
|
||||
if (client_fd[i] == fd) {
|
||||
client_fd[i] = INVALID_CLIENT;
|
||||
deregistered = true;
|
||||
close(fd);
|
||||
|
||||
PINELOG_TRACE("Disconnected client %d from socket", fd);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return deregistered;
|
||||
}
|
||||
|
||||
bool x52d_client_error(int client_fd[X52D_MAX_CLIENTS], int fd)
|
||||
{
|
||||
int error;
|
||||
socklen_t errlen = sizeof(error);
|
||||
|
||||
getsockopt(fd, SOL_SOCKET, SO_ERROR, (void *)&error, &errlen);
|
||||
PINELOG_ERROR(_("Error when polling socket: FD %d, error %d, len %lu"),
|
||||
fd, error, (unsigned long int)errlen);
|
||||
return x52d_client_deregister(client_fd, fd);
|
||||
}
|
||||
|
||||
int x52d_client_poll(int client_fd[X52D_MAX_CLIENTS], struct pollfd pfd[MAX_CONN], int listen_fd)
|
||||
{
|
||||
int pfd_count;
|
||||
int rc;
|
||||
|
||||
memset(pfd, 0, sizeof(*pfd) * MAX_CONN);
|
||||
|
||||
pfd_count = 1;
|
||||
pfd[0].fd = listen_fd;
|
||||
pfd[0].events = POLLIN | POLLERR;
|
||||
for (int i = 0; i < X52D_MAX_CLIENTS; i++) {
|
||||
if (client_fd[i] != INVALID_CLIENT) {
|
||||
pfd[pfd_count].fd = client_fd[i];
|
||||
pfd[pfd_count].events = POLLIN | POLLERR | POLLHUP;
|
||||
pfd_count++;
|
||||
}
|
||||
}
|
||||
|
||||
PINELOG_TRACE("Polling %d file descriptors", pfd_count);
|
||||
|
||||
retry_poll:
|
||||
rc = poll(pfd, pfd_count, -1);
|
||||
if (rc < 0) {
|
||||
if (errno == EINTR) {
|
||||
goto retry_poll;
|
||||
}
|
||||
PINELOG_ERROR(_("Error %d when polling %d descriptors: %s"),
|
||||
errno, pfd_count, strerror(errno));
|
||||
} else if (rc == 0) {
|
||||
PINELOG_INFO(_("Timed out when polling"));
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
void x52d_client_handle(int client_fd[X52D_MAX_CLIENTS], struct pollfd *pfd, int listen_fd, x52d_poll_handler handler)
|
||||
{
|
||||
for (int i = 0; i < MAX_CONN; i++) {
|
||||
if (pfd[i].revents & POLLHUP) {
|
||||
/* Remote hungup */
|
||||
x52d_client_deregister(client_fd, pfd[i].fd);
|
||||
} else if (pfd[i].revents & POLLERR) {
|
||||
/* Error reading from the socket */
|
||||
x52d_client_error(client_fd, pfd[i].fd);
|
||||
} else if (pfd[i].revents & POLLIN) {
|
||||
if (pfd[i].fd == listen_fd) {
|
||||
x52d_client_register(client_fd, listen_fd);
|
||||
} else {
|
||||
if (handler != NULL) {
|
||||
handler(pfd[i].fd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Saitek X52 Pro MFD & LED driver - Client handling
|
||||
*
|
||||
* Copyright (C) 2022 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#ifndef X52D_CLIENT_H
|
||||
#define X52D_CLIENT_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <poll.h>
|
||||
|
||||
#include "x52d_const.h"
|
||||
|
||||
#define MAX_CONN (X52D_MAX_CLIENTS + 1)
|
||||
|
||||
#define INVALID_CLIENT -1
|
||||
|
||||
typedef void (*x52d_poll_handler)(int);
|
||||
|
||||
void x52d_client_init(int client_fd[X52D_MAX_CLIENTS]);
|
||||
bool x52d_client_register(int client_fd[X52D_MAX_CLIENTS], int sock_fd);
|
||||
bool x52d_client_deregister(int client_fd[X52D_MAX_CLIENTS], int fd);
|
||||
bool x52d_client_error(int client_fd[X52D_MAX_CLIENTS], int fd);
|
||||
int x52d_client_poll(int client_fd[X52D_MAX_CLIENTS], struct pollfd pfd[MAX_CONN], int listen_fd);
|
||||
void x52d_client_handle(int client_fd[X52D_MAX_CLIENTS], struct pollfd *pfd, int listen_fd, x52d_poll_handler handler);
|
||||
|
||||
#endif //!defined X52D_CLIENT_H
|
|
@ -0,0 +1,211 @@
|
|||
/*
|
||||
* Saitek X52 Pro MFD & LED driver - Clock manager
|
||||
*
|
||||
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <pthread.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
#define PINELOG_MODULE X52D_MOD_CLOCK
|
||||
#include "pinelog.h"
|
||||
#include "x52d_config.h"
|
||||
#include "x52d_clock.h"
|
||||
#include "x52d_const.h"
|
||||
#include "x52d_device.h"
|
||||
|
||||
static bool clock_enabled = false;
|
||||
static int clock_primary_is_local = false;
|
||||
|
||||
void x52d_cfg_set_Clock_Enabled(bool enabled)
|
||||
{
|
||||
PINELOG_DEBUG(_("Setting clock enable to %s"),
|
||||
enabled ? _("on") : _("off"));
|
||||
clock_enabled = enabled;
|
||||
}
|
||||
|
||||
void x52d_cfg_set_Clock_PrimaryIsLocal(bool param)
|
||||
{
|
||||
PINELOG_DEBUG(_("Setting %s clock timezone to %s"),
|
||||
libx52_clock_id_to_str(LIBX52_CLOCK_1),
|
||||
param ? _("local") : _("UTC"));
|
||||
clock_primary_is_local = !!param;
|
||||
}
|
||||
|
||||
static int get_tz_offset(const char *tz)
|
||||
{
|
||||
char *orig_tz = NULL;
|
||||
char *orig_tz_copy = NULL;
|
||||
time_t t;
|
||||
struct tm tmp;
|
||||
struct tm *timeval;
|
||||
char *new_tz = NULL;
|
||||
size_t new_tz_len;
|
||||
int offset = 0;
|
||||
|
||||
new_tz_len = strlen(tz) + 2;
|
||||
new_tz = malloc(new_tz_len);
|
||||
if (new_tz == NULL) {
|
||||
PINELOG_WARN(_("Unable to allocate memory for timezone. Falling back to UTC"));
|
||||
goto cleanup;
|
||||
}
|
||||
snprintf(new_tz, new_tz_len, ":%s", tz);
|
||||
|
||||
orig_tz = getenv("TZ");
|
||||
if (orig_tz != NULL) {
|
||||
/* TZ was set in the environment */
|
||||
orig_tz_copy = strdup(orig_tz);
|
||||
if (orig_tz_copy == NULL) {
|
||||
PINELOG_WARN(_("Unable to backup timezone environment. Falling back to UTC"));
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
setenv("TZ", new_tz, true);
|
||||
t = time(NULL);
|
||||
tzset();
|
||||
timeval = localtime_r(&t, &tmp);
|
||||
if (timeval != NULL) {
|
||||
#if HAVE_STRUCT_TM_TM_GMTOFF
|
||||
/* If valid, then timeval.tm_gmtoff contains the offset in seconds east
|
||||
* of GMT. Divide by 60 to get the offset in minutes east of GMT.
|
||||
*/
|
||||
offset = (int)(timeval->tm_gmtoff / 60);
|
||||
#else
|
||||
/* The compiler does not provide tm_gmtoff. Fallback to using the
|
||||
* timezone variable, which is in seconds west of GMT. Divide by -60 to
|
||||
* get the offset in minutes east of GMT.
|
||||
*
|
||||
* ============
|
||||
* XXX NOTE XXX
|
||||
* ============
|
||||
* timezone is always the default (non-summer) timezone offset from GMT.
|
||||
* Therefore, this may not be accurate during the summer time months
|
||||
* for the region in question.
|
||||
*/
|
||||
offset = (int)(timezone / -60);
|
||||
#endif
|
||||
}
|
||||
|
||||
cleanup:
|
||||
if (orig_tz == NULL) {
|
||||
unsetenv("TZ");
|
||||
} else {
|
||||
setenv("TZ", orig_tz_copy, true);
|
||||
free(orig_tz_copy);
|
||||
}
|
||||
|
||||
if (new_tz != NULL) {
|
||||
free(new_tz);
|
||||
}
|
||||
|
||||
tzset();
|
||||
PINELOG_TRACE("Offset for timezone '%s' is %d", tz, offset);
|
||||
return offset;
|
||||
}
|
||||
|
||||
static void set_clock_offset(libx52_clock_id id, const char *param)
|
||||
{
|
||||
if (clock_enabled) {
|
||||
PINELOG_DEBUG(_("Setting %s clock timezone to %s"),
|
||||
libx52_clock_id_to_str(id), param);
|
||||
x52d_dev_set_clock_timezone(id, get_tz_offset(param));
|
||||
}
|
||||
}
|
||||
|
||||
void x52d_cfg_set_Clock_Secondary(char* param)
|
||||
{
|
||||
set_clock_offset(LIBX52_CLOCK_2, param);
|
||||
}
|
||||
|
||||
void x52d_cfg_set_Clock_Tertiary(char* param)
|
||||
{
|
||||
set_clock_offset(LIBX52_CLOCK_3, param);
|
||||
}
|
||||
|
||||
static void set_clock_format(libx52_clock_id id, libx52_clock_format fmt)
|
||||
{
|
||||
if (clock_enabled) {
|
||||
PINELOG_DEBUG(_("Setting %s clock format to %s"),
|
||||
libx52_clock_id_to_str(id), libx52_clock_format_to_str(fmt));
|
||||
x52d_dev_set_clock_format(id, fmt);
|
||||
}
|
||||
}
|
||||
|
||||
void x52d_cfg_set_Clock_FormatPrimary(libx52_clock_format fmt)
|
||||
{
|
||||
set_clock_format(LIBX52_CLOCK_1, fmt);
|
||||
}
|
||||
|
||||
void x52d_cfg_set_Clock_FormatSecondary(libx52_clock_format fmt)
|
||||
{
|
||||
set_clock_format(LIBX52_CLOCK_2, fmt);
|
||||
}
|
||||
|
||||
void x52d_cfg_set_Clock_FormatTertiary(libx52_clock_format fmt)
|
||||
{
|
||||
set_clock_format(LIBX52_CLOCK_3, fmt);
|
||||
}
|
||||
|
||||
void x52d_cfg_set_Clock_DateFormat(libx52_date_format fmt)
|
||||
{
|
||||
if (clock_enabled) {
|
||||
PINELOG_DEBUG(_("Setting date format to %s"), libx52_date_format_to_str(fmt));
|
||||
x52d_dev_set_date_format(fmt);
|
||||
}
|
||||
}
|
||||
|
||||
static pthread_t clock_thr;
|
||||
|
||||
static void * x52_clock_thr(void *param)
|
||||
{
|
||||
int rc;
|
||||
|
||||
PINELOG_INFO(_("Starting X52 clock manager thread"));
|
||||
for (;;) {
|
||||
time_t cur_time;
|
||||
|
||||
sleep(1);
|
||||
if (!clock_enabled) {
|
||||
/* Clock thread is disabled, check again next time */
|
||||
continue;
|
||||
}
|
||||
|
||||
if (time(&cur_time) < 0) {
|
||||
PINELOG_WARN(_("Error %d retrieving current time: %s"),
|
||||
errno, strerror(errno));
|
||||
continue;
|
||||
}
|
||||
rc = x52d_dev_set_clock(cur_time, clock_primary_is_local);
|
||||
if (rc == LIBX52_SUCCESS) {
|
||||
// Device manager will update the clock, this is only for debugging
|
||||
PINELOG_TRACE("Setting X52 clock to %ld", cur_time);
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void x52d_clock_init(void)
|
||||
{
|
||||
int rc;
|
||||
|
||||
PINELOG_TRACE("Initializing clock manager");
|
||||
rc = pthread_create(&clock_thr, NULL, x52_clock_thr, NULL);
|
||||
if (rc != 0) {
|
||||
PINELOG_FATAL(_("Error %d initializing clock thread: %s"),
|
||||
rc, strerror(rc));
|
||||
}
|
||||
}
|
||||
|
||||
void x52d_clock_exit(void)
|
||||
{
|
||||
PINELOG_INFO(_("Shutting down X52 clock manager thread"));
|
||||
pthread_cancel(clock_thr);
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* Saitek X52 Pro MFD & LED driver - Clock manager
|
||||
*
|
||||
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#ifndef X52D_CLOCK_H
|
||||
#define X52D_CLOCK_H
|
||||
|
||||
void x52d_clock_init(void);
|
||||
void x52d_clock_exit(void);
|
||||
|
||||
#endif // !defined X52D_CLOCK_H
|
|
@ -0,0 +1,177 @@
|
|||
/*
|
||||
* Saitek X52 Pro MFD & LED driver - Client communication library
|
||||
*
|
||||
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "x52dcomm.h"
|
||||
#include "x52dcomm-internal.h"
|
||||
|
||||
static int _setup_socket(struct sockaddr_un *remote, int len)
|
||||
{
|
||||
int sock;
|
||||
int saved_errno;
|
||||
|
||||
/* Create a socket */
|
||||
sock = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (sock == -1) {
|
||||
/* Failure creating the socket, abort early */
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Connect to the socket */
|
||||
if (connect(sock, (struct sockaddr *)remote, (socklen_t)len) == -1) {
|
||||
/* Failure connecting to the socket. Cleanup */
|
||||
saved_errno = errno;
|
||||
/* close may modify errno, so we save it prior to the call */
|
||||
close(sock);
|
||||
sock = -1;
|
||||
errno = saved_errno;
|
||||
}
|
||||
|
||||
return sock;
|
||||
}
|
||||
|
||||
int x52d_dial_command(const char *sock_path)
|
||||
{
|
||||
int len;
|
||||
struct sockaddr_un remote;
|
||||
|
||||
len = x52d_setup_command_sock(sock_path, &remote);
|
||||
if (len < 0) {
|
||||
/* Error when setting up sockaddr */
|
||||
return -1;
|
||||
}
|
||||
|
||||
return _setup_socket(&remote, len);
|
||||
}
|
||||
|
||||
int x52d_dial_notify(const char *sock_path)
|
||||
{
|
||||
int len;
|
||||
struct sockaddr_un remote;
|
||||
|
||||
len = x52d_setup_notify_sock(sock_path, &remote);
|
||||
if (len < 0) {
|
||||
/* Error when setting up sockaddr */
|
||||
return -1;
|
||||
}
|
||||
|
||||
return _setup_socket(&remote, len);
|
||||
}
|
||||
|
||||
int x52d_format_command(int argc, const char **argv, char *buffer, size_t buflen)
|
||||
{
|
||||
int msglen;
|
||||
int i;
|
||||
|
||||
if (argc == 0 || argv == NULL || buffer == NULL || buflen < X52D_BUFSZ) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset(buffer, 0, buflen);
|
||||
msglen = 0;
|
||||
for (i = 0; i < argc; i++) {
|
||||
int arglen = strlen(argv[i]) + 1;
|
||||
if ((size_t)(msglen + arglen) >= buflen) {
|
||||
errno = E2BIG;
|
||||
return -1;
|
||||
}
|
||||
|
||||
memcpy(&buffer[msglen], argv[i], arglen);
|
||||
msglen += arglen;
|
||||
}
|
||||
|
||||
return msglen;
|
||||
}
|
||||
|
||||
int x52d_send_command(int sock_fd, char *buffer, size_t bufin, size_t bufout)
|
||||
{
|
||||
int rc;
|
||||
|
||||
for (;;) {
|
||||
/*
|
||||
* Unix sockets should have sufficient capacity to send the full
|
||||
* datagram in a single message. Assume that is the case.
|
||||
*/
|
||||
rc = send(sock_fd, buffer, bufin, 0);
|
||||
if (rc < 0) {
|
||||
// Error
|
||||
if (errno == EINTR) {
|
||||
// System call interrupted due to signal. Try again
|
||||
continue;
|
||||
} else {
|
||||
// Failed. Return early
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
/* Wait till we get a response */
|
||||
for (;;) {
|
||||
rc = recv(sock_fd, buffer, bufout, 0);
|
||||
if (rc < 0) {
|
||||
// Error
|
||||
if (errno == EINTR) {
|
||||
// System call interrupted due to signal. Try again
|
||||
continue;
|
||||
} else {
|
||||
// Failed. Return early
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int x52d_recv_notification(int sock_fd, x52d_notify_callback_fn callback)
|
||||
{
|
||||
int rc;
|
||||
char buffer[X52D_BUFSZ];
|
||||
int argc;
|
||||
char *argv[X52D_BUFSZ];
|
||||
|
||||
if (callback == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Wait till we get a response */
|
||||
for (;;) {
|
||||
rc = recv(sock_fd, buffer, sizeof(buffer), 0);
|
||||
if (rc < 0) {
|
||||
// Error
|
||||
if (errno == EINTR) {
|
||||
// System call interrupted due to signal. Try again
|
||||
continue;
|
||||
} else {
|
||||
// Failed. Return early
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
/* Split into individual arguments */
|
||||
x52d_split_args(&argc, argv, buffer, rc);
|
||||
|
||||
return callback(argc, argv);
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
/*
|
||||
* Saitek X52 Pro MFD & LED driver - Client communication library
|
||||
*
|
||||
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "x52dcomm-internal.h"
|
||||
#include "x52d_const.h"
|
||||
|
||||
const char * x52d_command_sock_path(const char *sock_path)
|
||||
{
|
||||
if (sock_path == NULL) {
|
||||
sock_path = X52D_SOCK_COMMAND;
|
||||
}
|
||||
|
||||
return sock_path;
|
||||
}
|
||||
|
||||
const char * x52d_notify_sock_path(const char *sock_path)
|
||||
{
|
||||
if (sock_path == NULL) {
|
||||
sock_path = X52D_SOCK_NOTIFY;
|
||||
}
|
||||
|
||||
return sock_path;
|
||||
}
|
||||
|
||||
static int _setup_sockaddr(struct sockaddr_un *remote, const char *sock_path)
|
||||
{
|
||||
int len;
|
||||
if (remote == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
len = strlen(sock_path);
|
||||
if ((size_t)len >= sizeof(remote->sun_path)) {
|
||||
/* Socket path will not fit inside sun_path */
|
||||
errno = E2BIG;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Setup the sockaddr structure */
|
||||
memset(remote, 0, sizeof(*remote));
|
||||
remote->sun_family = AF_UNIX;
|
||||
/* We've already verified that sock_path will fit, so we don't need strncpy */
|
||||
strcpy(remote->sun_path, sock_path);
|
||||
len += sizeof(remote->sun_family);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
int x52d_setup_command_sock(const char *sock_path, struct sockaddr_un *remote)
|
||||
{
|
||||
return _setup_sockaddr(remote, x52d_command_sock_path(sock_path));
|
||||
}
|
||||
|
||||
int x52d_setup_notify_sock(const char *sock_path, struct sockaddr_un *remote)
|
||||
{
|
||||
return _setup_sockaddr(remote, x52d_notify_sock_path(sock_path));
|
||||
}
|
||||
|
||||
int x52d_set_socket_nonblocking(int sock_fd)
|
||||
{
|
||||
int flags;
|
||||
|
||||
/* Mark the socket as non-blocking */
|
||||
flags = fcntl(sock_fd, F_GETFL);
|
||||
if (flags < 0) {
|
||||
goto sock_failure;
|
||||
}
|
||||
if (fcntl(sock_fd, F_SETFL, flags | O_NONBLOCK) < 0) {
|
||||
goto sock_failure;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
sock_failure:
|
||||
close(sock_fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int x52d_listen_socket(struct sockaddr_un *local, int len, int sock_fd)
|
||||
{
|
||||
/* Cleanup any existing socket */
|
||||
unlink(local->sun_path);
|
||||
if (bind(sock_fd, (struct sockaddr *)local, (socklen_t)len) < 0) {
|
||||
/* Failure binding socket */
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (listen(sock_fd, X52D_MAX_CLIENTS) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void x52d_split_args(int *argc, char **argv, char *buffer, int buflen)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
while (i < buflen) {
|
||||
if (buffer[i]) {
|
||||
argv[*argc] = buffer + i;
|
||||
(*argc)++;
|
||||
for (; i < buflen && buffer[i]; i++);
|
||||
// At this point, buffer[i] = '\0'
|
||||
// Skip to the next character.
|
||||
i++;
|
||||
} else {
|
||||
// We should never reach here, unless we have two NULs in a row
|
||||
argv[*argc] = buffer + i;
|
||||
(*argc)++;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,480 @@
|
|||
/*
|
||||
* Saitek X52 Pro MFD & LED driver - Command processor
|
||||
*
|
||||
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <poll.h>
|
||||
#include <signal.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#define PINELOG_MODULE X52D_MOD_COMMAND
|
||||
#include "pinelog.h"
|
||||
#include "x52d_const.h"
|
||||
#include "x52d_command.h"
|
||||
#include "x52d_config.h"
|
||||
#include "x52d_client.h"
|
||||
#include "x52dcomm-internal.h"
|
||||
|
||||
static int client_fd[X52D_MAX_CLIENTS];
|
||||
|
||||
static pthread_t command_thr;
|
||||
static int command_sock_fd;
|
||||
static const char *command_sock;
|
||||
|
||||
#if defined __has_attribute
|
||||
# if __has_attribute(format)
|
||||
__attribute((format(printf, 4, 5)))
|
||||
# endif
|
||||
#endif
|
||||
static void response_formatted(char *buffer, int *buflen, const char *type,
|
||||
const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
char response[X52D_BUFSZ];
|
||||
int resplen;
|
||||
int typelen;
|
||||
|
||||
typelen = strlen(type) + 1;
|
||||
strcpy(response, type);
|
||||
resplen = typelen;
|
||||
|
||||
if (*fmt) {
|
||||
va_start(ap, fmt);
|
||||
resplen += vsnprintf(response + typelen, sizeof(response) - typelen, fmt, ap) + 1;
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
memcpy(buffer, response, resplen);
|
||||
*buflen = resplen;
|
||||
}
|
||||
|
||||
static void response_strings(char *buffer, int *buflen, const char *type, int count, ...)
|
||||
{
|
||||
va_list ap;
|
||||
char response[X52D_BUFSZ];
|
||||
int resplen;
|
||||
int arglen;
|
||||
int i;
|
||||
char *arg;
|
||||
|
||||
arglen = strlen(type) + 1;
|
||||
strcpy(response, type);
|
||||
resplen = arglen;
|
||||
|
||||
va_start(ap, count);
|
||||
for (i = 0; i < count; i++) {
|
||||
arg = va_arg(ap, char *);
|
||||
arglen = strlen(arg) + 1;
|
||||
if ((size_t)(arglen + resplen) >= sizeof(response)) {
|
||||
PINELOG_ERROR("Too many arguments for response_strings %s", type);
|
||||
break;
|
||||
}
|
||||
|
||||
strcpy(response + resplen, arg);
|
||||
resplen += arglen;
|
||||
}
|
||||
va_end(ap);
|
||||
|
||||
memcpy(buffer, response, resplen);
|
||||
*buflen = resplen;
|
||||
}
|
||||
|
||||
#define NUMARGS(...) (sizeof((const char *[]){__VA_ARGS__}) / sizeof(const char *))
|
||||
#define ERR(...) response_strings(buffer, buflen, "ERR", NUMARGS(__VA_ARGS__), ##__VA_ARGS__)
|
||||
#define ERR_fmt(fmt, ...) response_formatted(buffer, buflen, "ERR", fmt, ##__VA_ARGS__)
|
||||
|
||||
#define OK(...) response_strings(buffer, buflen, "OK", NUMARGS(__VA_ARGS__), ##__VA_ARGS__)
|
||||
#define OK_fmt(fmt, ...) response_formatted(buffer, buflen, "OK", fmt, ##__VA_ARGS__)
|
||||
|
||||
#define DATA(...) response_strings(buffer, buflen, "DATA", NUMARGS(__VA_ARGS__), ##__VA_ARGS__)
|
||||
|
||||
#define MATCH(idx, cmd) if (strcasecmp(argv[idx], cmd) == 0)
|
||||
|
||||
static bool check_file(const char *file_path, int mode)
|
||||
{
|
||||
if (*file_path == '\0') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mode && access(file_path, mode)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void cmd_config(char *buffer, int *buflen, int argc, char **argv)
|
||||
{
|
||||
if (argc < 2) {
|
||||
ERR("Insufficient arguments for 'config' command");
|
||||
return;
|
||||
}
|
||||
|
||||
MATCH(1, "load") {
|
||||
if (argc == 3) {
|
||||
if (!check_file(argv[2], R_OK)) {
|
||||
ERR_fmt("Invalid file '%s' for 'config load' command", argv[2]);
|
||||
return;
|
||||
}
|
||||
|
||||
x52d_config_load(argv[2]);
|
||||
x52d_config_apply();
|
||||
OK("config", "load", argv[2]);
|
||||
} else {
|
||||
// Invalid number of args
|
||||
ERR_fmt("Unexpected arguments for 'config load' command; got %d, expected 3", argc);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
MATCH(1, "reload") {
|
||||
if (argc == 2) {
|
||||
raise(SIGHUP);
|
||||
OK("config", "reload");
|
||||
} else {
|
||||
ERR_fmt("Unexpected arguments for 'config reload' command; got %d, expected 2", argc);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
MATCH(1, "dump") {
|
||||
if (argc == 3) {
|
||||
if (!check_file(argv[2], 0)) {
|
||||
ERR_fmt("Invalid file '%s' for 'config dump' command", argv[2]);
|
||||
return;
|
||||
}
|
||||
|
||||
x52d_config_save(argv[2]);
|
||||
OK("config", "dump", argv[2]);
|
||||
} else {
|
||||
ERR_fmt("Unexpected arguments for 'config dump' command; got %d, expected 3", argc);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
MATCH(1, "save") {
|
||||
if (argc == 2) {
|
||||
raise(SIGUSR1);
|
||||
OK("config", "save");
|
||||
} else {
|
||||
ERR_fmt("Unexpected arguments for 'config save' command; got %d, expected 2", argc);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
MATCH(1, "set") {
|
||||
if (argc == 5) {
|
||||
int rc = x52d_config_set(argv[2], argv[3], argv[4]);
|
||||
if (rc != 0) {
|
||||
ERR_fmt("Error %d setting '%s.%s'='%s': %s", rc,
|
||||
argv[2], argv[3], argv[4], strerror(rc));
|
||||
} else {
|
||||
x52d_config_apply_immediate(argv[2], argv[3]);
|
||||
OK("config", "set", argv[2], argv[3], argv[4]);
|
||||
}
|
||||
} else {
|
||||
ERR_fmt("Unexpected arguments for 'config set' command; got %d, expected 5", argc);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
MATCH(1, "get") {
|
||||
if (argc == 4) {
|
||||
const char *rv = x52d_config_get(argv[2], argv[3]);
|
||||
if (rv == NULL) {
|
||||
ERR_fmt("Error getting '%s.%s'", argv[2], argv[3]);
|
||||
} else {
|
||||
DATA(argv[2], argv[3], rv);
|
||||
}
|
||||
} else {
|
||||
ERR_fmt("Unexpected arguments for 'config get' command; got %d, expected 4", argc);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ERR_fmt("Unknown subcommand '%s' for 'config' command", argv[1]);
|
||||
}
|
||||
|
||||
struct level_map {
|
||||
int level;
|
||||
const char *string;
|
||||
};
|
||||
|
||||
static int lmap_get_level(const struct level_map *map, const char *string, int notfound)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; map[i].string != NULL; i++) {
|
||||
if (strcasecmp(map[i].string, string) == 0) {
|
||||
return map[i].level;
|
||||
}
|
||||
}
|
||||
|
||||
return notfound;
|
||||
}
|
||||
|
||||
static const char *lmap_get_string(const struct level_map *map, int level)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; map[i].string != NULL; i++) {
|
||||
if (map[i].level == level) {
|
||||
return map[i].string;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int array_find_index(const char **array, int nmemb, const char *string)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < nmemb; i++) {
|
||||
if (strcasecmp(array[i], string) == 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return nmemb;
|
||||
}
|
||||
|
||||
static void cmd_logging(char *buffer, int *buflen, int argc, char **argv)
|
||||
{
|
||||
static const char *modules[X52D_MOD_MAX] = {
|
||||
[X52D_MOD_CONFIG] = "config",
|
||||
[X52D_MOD_CLOCK] = "clock",
|
||||
[X52D_MOD_DEVICE] = "device",
|
||||
[X52D_MOD_IO] = "io",
|
||||
[X52D_MOD_LED] = "led",
|
||||
[X52D_MOD_MOUSE] = "mouse",
|
||||
[X52D_MOD_COMMAND] = "command",
|
||||
[X52D_MOD_CLIENT] = "client",
|
||||
[X52D_MOD_NOTIFY] = "notify",
|
||||
};
|
||||
|
||||
// This corresponds to the levels in pinelog
|
||||
static const struct level_map loglevels[] = {
|
||||
{PINELOG_LVL_NOTSET, "default"},
|
||||
{PINELOG_LVL_NONE, "none"},
|
||||
{PINELOG_LVL_FATAL, "fatal"},
|
||||
{PINELOG_LVL_ERROR, "error"},
|
||||
{PINELOG_LVL_WARNING, "warning"},
|
||||
{PINELOG_LVL_INFO, "info"},
|
||||
{PINELOG_LVL_DEBUG, "debug"},
|
||||
{PINELOG_LVL_TRACE, "trace"},
|
||||
{0, NULL},
|
||||
};
|
||||
|
||||
if (argc < 2) {
|
||||
ERR("Insufficient arguments for 'logging' command");
|
||||
return;
|
||||
}
|
||||
|
||||
// logging show [module]
|
||||
MATCH(1, "show") {
|
||||
if (argc == 2) {
|
||||
// Show default logging level
|
||||
DATA("global", lmap_get_string(loglevels, pinelog_get_level()));
|
||||
} else if (argc == 3) {
|
||||
int module = array_find_index(modules, X52D_MOD_MAX, argv[2]);
|
||||
if (module == X52D_MOD_MAX) {
|
||||
ERR_fmt("Invalid module '%s'", argv[2]);
|
||||
} else {
|
||||
DATA(argv[2], lmap_get_string(loglevels, pinelog_get_module_level(module)));
|
||||
}
|
||||
} else {
|
||||
ERR_fmt("Unexpected arguments for 'logging show' command; got %d, expected 2 or 3", argc);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// logging set [module] <level>
|
||||
MATCH(1, "set") {
|
||||
if (argc == 3) {
|
||||
int level = lmap_get_level(loglevels, argv[2], INT_MAX);
|
||||
if (level == INT_MAX) {
|
||||
ERR_fmt("Unknown level '%s' for 'logging set' command", argv[2]);
|
||||
} else if (level == PINELOG_LVL_NOTSET) {
|
||||
ERR("'default' level is not valid without a module");
|
||||
} else {
|
||||
pinelog_set_level(level);
|
||||
OK("logging", "set", argv[2]);
|
||||
}
|
||||
} else if (argc == 4) {
|
||||
int level = lmap_get_level(loglevels, argv[3], INT_MAX);
|
||||
int module = array_find_index(modules, X52D_MOD_MAX, argv[2]);
|
||||
|
||||
if (module == X52D_MOD_MAX) {
|
||||
ERR_fmt("Invalid module '%s'", argv[2]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (level == INT_MAX) {
|
||||
ERR_fmt("Unknown level '%s' for 'logging set' command", argv[3]);
|
||||
} else {
|
||||
pinelog_set_module_level(module, level);
|
||||
OK("logging", "set", argv[2], argv[3]);
|
||||
}
|
||||
} else {
|
||||
ERR_fmt("Unexpected arguments for 'logging set' command; got %d, expected 3 or 4", argc);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
ERR_fmt("Unknown subcommand '%s' for 'logging' command", argv[1]);
|
||||
}
|
||||
|
||||
static void command_parser(char *buffer, int *buflen)
|
||||
{
|
||||
int argc = 0;
|
||||
char *argv[X52D_BUFSZ] = { 0 };
|
||||
|
||||
x52d_split_args(&argc, argv, buffer, *buflen);
|
||||
|
||||
MATCH(0, "config") {
|
||||
cmd_config(buffer, buflen, argc, argv);
|
||||
} else MATCH(0, "logging") {
|
||||
cmd_logging(buffer, buflen, argc, argv);
|
||||
} else {
|
||||
ERR_fmt("Unknown command '%s'", argv[0]);
|
||||
}
|
||||
}
|
||||
|
||||
static void client_handler(int fd)
|
||||
{
|
||||
char buffer[X52D_BUFSZ] = { 0 };
|
||||
int sent;
|
||||
int rc;
|
||||
|
||||
rc = recv(fd, buffer, sizeof(buffer), 0);
|
||||
if (rc < 0) {
|
||||
PINELOG_ERROR(_("Error reading from client %d: %s"),
|
||||
fd, strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse and handle command.
|
||||
command_parser(buffer, &rc);
|
||||
|
||||
PINELOG_TRACE("Sending %d bytes in response '%s'", rc, buffer);
|
||||
sent = send(fd, buffer, rc, 0);
|
||||
if (sent != rc) {
|
||||
PINELOG_ERROR(_("Short write to client %d; expected %d bytes, wrote %d bytes"),
|
||||
fd, rc, sent);
|
||||
}
|
||||
}
|
||||
|
||||
int x52d_command_loop(int sock_fd)
|
||||
{
|
||||
struct pollfd pfd[MAX_CONN];
|
||||
int rc;
|
||||
|
||||
rc = x52d_client_poll(client_fd, pfd, sock_fd);
|
||||
if (rc <= 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
x52d_client_handle(client_fd, pfd, sock_fd, client_handler);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void * x52d_command_thread(void *param)
|
||||
{
|
||||
for (;;) {
|
||||
if (x52d_command_loop(command_sock_fd) < 0) {
|
||||
PINELOG_FATAL(_("Error %d during command loop: %s"),
|
||||
errno, strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int x52d_command_init(const char *sock_path)
|
||||
{
|
||||
int sock_fd;
|
||||
int len;
|
||||
struct sockaddr_un local;
|
||||
|
||||
x52d_client_init(client_fd);
|
||||
|
||||
command_sock = sock_path;
|
||||
command_sock_fd = -1;
|
||||
|
||||
len = x52d_setup_command_sock(command_sock, &local);
|
||||
if (len < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
sock_fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (sock_fd < 0) {
|
||||
/* Failure creating the socket. Abort early */
|
||||
PINELOG_ERROR(_("Error creating command socket: %s"), strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
command_sock_fd = sock_fd;
|
||||
|
||||
/* Mark the socket as non-blocking */
|
||||
if (x52d_set_socket_nonblocking(sock_fd) < 0) {
|
||||
PINELOG_ERROR(_("Error marking command socket as nonblocking: %s"),
|
||||
strerror(errno));
|
||||
goto sock_failure;
|
||||
}
|
||||
|
||||
if (x52d_listen_socket(&local, len, sock_fd) < 0) {
|
||||
PINELOG_ERROR(_("Error listening on command socket: %s"), strerror(errno));
|
||||
goto listen_failure;
|
||||
}
|
||||
|
||||
PINELOG_INFO(_("Starting command processing thread"));
|
||||
pthread_create(&command_thr, NULL, x52d_command_thread, NULL);
|
||||
|
||||
return 0;
|
||||
|
||||
listen_failure:
|
||||
unlink(local.sun_path);
|
||||
sock_failure:
|
||||
if (command_sock_fd >= 0) {
|
||||
close(command_sock_fd);
|
||||
command_sock_fd = -1;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void x52d_command_exit(void)
|
||||
{
|
||||
PINELOG_INFO(_("Shutting down command processing thread"));
|
||||
pthread_cancel(command_thr);
|
||||
|
||||
// Close the socket and remove the socket file
|
||||
if (command_sock_fd >= 0) {
|
||||
command_sock = x52d_command_sock_path(command_sock);
|
||||
PINELOG_TRACE("Closing command socket %s", command_sock);
|
||||
|
||||
close(command_sock_fd);
|
||||
command_sock_fd = -1;
|
||||
|
||||
unlink(command_sock);
|
||||
command_sock = NULL;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* Saitek X52 Pro MFD & LED driver - Command processor
|
||||
*
|
||||
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#ifndef X52D_COMMAND_H
|
||||
#define X52D_COMMAND_H
|
||||
|
||||
int x52d_command_init(const char *sock_path);
|
||||
void x52d_command_exit(void);
|
||||
int x52d_command_loop(int sock_fd);
|
||||
|
||||
#endif // !defined X52D_COMMAND_H
|
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* Saitek X52 Pro MFD & LED driver - Configuration parser
|
||||
*
|
||||
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include <errno.h>
|
||||
|
||||
#define PINELOG_MODULE X52D_MOD_CONFIG
|
||||
#include "pinelog.h"
|
||||
#include "x52d_config.h"
|
||||
#include "x52d_const.h"
|
||||
|
||||
static struct x52d_config x52d_config;
|
||||
|
||||
void x52d_config_load(const char *cfg_file)
|
||||
{
|
||||
int rc;
|
||||
|
||||
if (cfg_file == NULL) {
|
||||
cfg_file = X52D_SYS_CFG_FILE;
|
||||
}
|
||||
|
||||
rc = x52d_config_set_defaults(&x52d_config);
|
||||
if (rc != 0) {
|
||||
PINELOG_FATAL(_("Error %d setting configuration defaults: %s"),
|
||||
rc, strerror(rc));
|
||||
}
|
||||
|
||||
rc = x52d_config_load_file(&x52d_config, cfg_file);
|
||||
if (rc != 0) {
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// Apply overrides
|
||||
rc = x52d_config_apply_overrides(&x52d_config);
|
||||
x52d_config_clear_overrides();
|
||||
if (rc != 0) {
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
void x52d_config_save(const char *cfg_file)
|
||||
{
|
||||
int rc;
|
||||
|
||||
if (cfg_file == NULL) {
|
||||
cfg_file = X52D_SYS_CFG_FILE;
|
||||
}
|
||||
|
||||
rc = x52d_config_save_file(&x52d_config, cfg_file);
|
||||
if (rc != 0) {
|
||||
PINELOG_ERROR(_("Error %d saving configuration file: %s"),
|
||||
rc, strerror(rc));
|
||||
}
|
||||
}
|
||||
|
||||
int x52d_config_set(const char *section, const char *key, const char *value)
|
||||
{
|
||||
if (section == NULL || key == NULL || value == NULL) {
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
PINELOG_TRACE("Processing config set '%s.%s'='%s'", section, key, value);
|
||||
|
||||
return x52d_config_process_kv(&x52d_config, section, key, value);
|
||||
}
|
||||
|
||||
const char *x52d_config_get(const char *section, const char *key)
|
||||
{
|
||||
const char *value;
|
||||
if (section == NULL || key == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
value = x52d_config_get_param(&x52d_config, section, key);
|
||||
PINELOG_TRACE("Processed config get '%s.%s'='%s'", section, key, value);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/* Callback stubs
|
||||
* TODO: Remove the ones below when their implementation is complete
|
||||
*/
|
||||
void x52d_cfg_set_Profiles_Directory(char* param) { (void)param; }
|
||||
void x52d_cfg_set_Profiles_ClutchEnabled(bool param) { (void)param; }
|
||||
void x52d_cfg_set_Profiles_ClutchLatched(bool param) { (void)param; }
|
||||
|
||||
void x52d_config_apply_immediate(const char *section, const char *key)
|
||||
{
|
||||
#define CFG(c_sec, c_key, name, parser, def) \
|
||||
if (!strcasecmp(section, #c_sec) && !strcasecmp(key, #c_key)) { \
|
||||
PINELOG_TRACE("Invoking " #c_sec "." #c_key " callback"); \
|
||||
x52d_cfg_set_ ## c_sec ## _ ## c_key(x52d_config . name); \
|
||||
} else
|
||||
|
||||
#include "x52d_config.def"
|
||||
// Dummy to capture the trailing else
|
||||
// Wrap it in braces in case tracing has been disabled
|
||||
{ PINELOG_TRACE("Ignoring apply_immediate(%s.%s)", section, key); }
|
||||
}
|
||||
|
||||
void x52d_config_apply(void)
|
||||
{
|
||||
#define CFG(section, key, name, parser, def) \
|
||||
PINELOG_TRACE("Calling configuration callback for " #section "." #key); \
|
||||
x52d_cfg_set_ ## section ## _ ## key(x52d_config . name);
|
||||
#include "x52d_config.def"
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
/**********************************************************************
|
||||
* X52 Daemon Configuration
|
||||
*********************************************************************/
|
||||
|
||||
// The settings below are the defaults. Note that the section and key
|
||||
// strings are case insensitive, but the values are not necessarily so,
|
||||
// especially for those referring to paths or timezone names.
|
||||
|
||||
/* CFG(section, key, name, parser/dumper type, default) */
|
||||
/**********************************************************************
|
||||
* Clock Settings
|
||||
*********************************************************************/
|
||||
|
||||
// Enabled controls whether the clock is enabled or not. Set this to no to
|
||||
// disable the clock update. Keep in mind that if the clock was originally
|
||||
// enabled on the X52, then disabling it here won't make the clock disappear
|
||||
// on the MFD. You will need to unplug and reattach the X52 to make the
|
||||
// clock disappear
|
||||
CFG(Clock, Enabled, clock_enabled, bool, true)
|
||||
|
||||
// PrimaryIsLocal controls whether the primary clock displays local time or UTC.
|
||||
// Set this to yes to display local time, no for UTC.
|
||||
CFG(Clock, PrimaryIsLocal, primary_clock_local, bool, true)
|
||||
|
||||
// Secondary controls the timezone of the secondary clock. Use the standard
|
||||
// timezone name as defined by the Olson time database.
|
||||
CFG(Clock, Secondary, clock_2_tz, string, UTC)
|
||||
|
||||
// Tertiary controls the timezone of the tertiary clock. Use the standard
|
||||
// timezone name as defined by the Olson time database.
|
||||
CFG(Clock, Tertiary, clock_3_tz, string, UTC)
|
||||
|
||||
// Clock format for the primary clock
|
||||
CFG(Clock, FormatPrimary, clock_format[LIBX52_CLOCK_1], clock_format, 12hr)
|
||||
|
||||
// Clock format for the secondary clock
|
||||
CFG(Clock, FormatSecondary, clock_format[LIBX52_CLOCK_2], clock_format, 12hr)
|
||||
|
||||
// Clock format for the tertiary clock
|
||||
CFG(Clock, FormatTertiary, clock_format[LIBX52_CLOCK_3], clock_format, 12hr)
|
||||
|
||||
// Date format for the date display
|
||||
CFG(Clock, DateFormat, date_format, date_format, ddmmyy)
|
||||
|
||||
/**********************************************************************
|
||||
* LED Settings - only applicable to X52Pro
|
||||
*********************************************************************/
|
||||
// The LED settings map a color code or state to the corresponding LED.
|
||||
CFG(LED, Fire, leds[LIBX52_LED_FIRE], led, on)
|
||||
CFG(LED, Throttle, leds[LIBX52_LED_THROTTLE], led, on)
|
||||
CFG(LED, A, leds[LIBX52_LED_A], led, green)
|
||||
CFG(LED, B, leds[LIBX52_LED_B], led, green)
|
||||
CFG(LED, D, leds[LIBX52_LED_D], led, green)
|
||||
CFG(LED, E, leds[LIBX52_LED_E], led, green)
|
||||
CFG(LED, T1, leds[LIBX52_LED_T1], led, green)
|
||||
CFG(LED, T2, leds[LIBX52_LED_T2], led, green)
|
||||
CFG(LED, T3, leds[LIBX52_LED_T3], led, green)
|
||||
CFG(LED, POV, leds[LIBX52_LED_POV], led, green)
|
||||
CFG(LED, Clutch, leds[LIBX52_LED_CLUTCH], led, green)
|
||||
|
||||
/**********************************************************************
|
||||
* Brightness Settings
|
||||
*********************************************************************/
|
||||
// The brightness settings map the brightness value to the LEDs/MFD.
|
||||
CFG(Brightness, MFD, brightness[0], int, 128)
|
||||
CFG(Brightness, LED, brightness[1], int, 128)
|
||||
|
||||
/**********************************************************************
|
||||
* Mouse Settings
|
||||
*********************************************************************/
|
||||
// Enabled controls whether the virtual mouse is enabled or not.
|
||||
CFG(Mouse, Enabled, mouse_enabled, bool, true)
|
||||
|
||||
// Speed is a value that is proportional to the speed of updates to the
|
||||
// virtual mouse
|
||||
CFG(Mouse, Speed, mouse_speed, int, 0)
|
||||
|
||||
// ReverseScroll controls the scrolling direction
|
||||
CFG(Mouse, ReverseScroll, mouse_reverse_scroll, bool, false)
|
||||
|
||||
/**********************************************************************
|
||||
* Profiles - only valid on Linux
|
||||
*********************************************************************/
|
||||
// Directory is the location of the folder containing the individual profiles.
|
||||
CFG(Profiles, Directory, profiles_dir, string, /etc/x52d/profiles.d)
|
||||
|
||||
// ClutchEnabled determines if the clutch button is treated specially
|
||||
CFG(Profiles, ClutchEnabled, clutch_enabled, bool, false)
|
||||
|
||||
// ClutchLatched controls if the clutch button (if enabled) is a latched button
|
||||
// (press once to enter clutch mode, press again to exit clutch mode), or must
|
||||
// be held down to remain in clutch mode.
|
||||
CFG(Profiles, ClutchLatched, clutch_latched, bool, false)
|
||||
|
||||
#undef CFG
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* Saitek X52 Pro MFD & LED driver - Configuration parser header
|
||||
*
|
||||
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#ifndef X52D_CONFIG_H
|
||||
#define X52D_CONFIG_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <limits.h>
|
||||
#include "libx52.h"
|
||||
|
||||
/**
|
||||
* @brief Configuration structure
|
||||
*
|
||||
* Keep this in sync with the sample configuration
|
||||
*/
|
||||
struct x52d_config {
|
||||
bool clock_enabled;
|
||||
bool primary_clock_local;
|
||||
|
||||
// Since we don't have a _MAX identifier for libx52_clock_id, use
|
||||
// the maximum clock ID + 1 as the length
|
||||
libx52_clock_format clock_format[LIBX52_CLOCK_3 + 1];
|
||||
libx52_date_format date_format;
|
||||
|
||||
char clock_2_tz[NAME_MAX];
|
||||
char clock_3_tz[NAME_MAX];
|
||||
|
||||
// Since we don't have a _MAX identifier for libx52_led_id, hardcode
|
||||
// the length in the following declaration.
|
||||
libx52_led_state leds[21];
|
||||
|
||||
int brightness[2];
|
||||
|
||||
bool mouse_enabled;
|
||||
int mouse_speed;
|
||||
bool mouse_reverse_scroll;
|
||||
|
||||
bool clutch_enabled;
|
||||
bool clutch_latched;
|
||||
|
||||
char profiles_dir[NAME_MAX];
|
||||
};
|
||||
|
||||
/* Callback functions for configuration */
|
||||
// These functions are defined in the individual modules
|
||||
void x52d_cfg_set_Clock_Enabled(bool param);
|
||||
void x52d_cfg_set_Clock_PrimaryIsLocal(bool param);
|
||||
void x52d_cfg_set_Clock_Secondary(char* param);
|
||||
void x52d_cfg_set_Clock_Tertiary(char* param);
|
||||
void x52d_cfg_set_Clock_FormatPrimary(libx52_clock_format param);
|
||||
void x52d_cfg_set_Clock_FormatSecondary(libx52_clock_format param);
|
||||
void x52d_cfg_set_Clock_FormatTertiary(libx52_clock_format param);
|
||||
void x52d_cfg_set_Clock_DateFormat(libx52_date_format param);
|
||||
void x52d_cfg_set_LED_Fire(libx52_led_state param);
|
||||
void x52d_cfg_set_LED_Throttle(libx52_led_state param);
|
||||
void x52d_cfg_set_LED_A(libx52_led_state param);
|
||||
void x52d_cfg_set_LED_B(libx52_led_state param);
|
||||
void x52d_cfg_set_LED_D(libx52_led_state param);
|
||||
void x52d_cfg_set_LED_E(libx52_led_state param);
|
||||
void x52d_cfg_set_LED_T1(libx52_led_state param);
|
||||
void x52d_cfg_set_LED_T2(libx52_led_state param);
|
||||
void x52d_cfg_set_LED_T3(libx52_led_state param);
|
||||
void x52d_cfg_set_LED_POV(libx52_led_state param);
|
||||
void x52d_cfg_set_LED_Clutch(libx52_led_state param);
|
||||
void x52d_cfg_set_Brightness_MFD(uint16_t param);
|
||||
void x52d_cfg_set_Brightness_LED(uint16_t param);
|
||||
void x52d_cfg_set_Mouse_Enabled(bool param);
|
||||
void x52d_cfg_set_Mouse_Speed(int param);
|
||||
void x52d_cfg_set_Mouse_ReverseScroll(bool param);
|
||||
void x52d_cfg_set_Profiles_Directory(char* param);
|
||||
void x52d_cfg_set_Profiles_ClutchEnabled(bool param);
|
||||
void x52d_cfg_set_Profiles_ClutchLatched(bool param);
|
||||
|
||||
int x52d_config_process_kv(void *user, const char *section, const char *key, const char *value);
|
||||
const char *x52d_config_get_param(struct x52d_config *cfg, const char *section, const char *key);
|
||||
|
||||
int x52d_config_set_defaults(struct x52d_config *cfg);
|
||||
|
||||
int x52d_config_load_file(struct x52d_config *cfg, const char *cfg_file);
|
||||
|
||||
int x52d_config_save_override(const char *override_str);
|
||||
|
||||
int x52d_config_apply_overrides(struct x52d_config *cfg);
|
||||
|
||||
void x52d_config_clear_overrides(void);
|
||||
|
||||
void x52d_config_load(const char *cfg_file);
|
||||
void x52d_config_apply_immediate(const char *section, const char *key);
|
||||
void x52d_config_apply(void);
|
||||
|
||||
int x52d_config_save_file(struct x52d_config *cfg, const char *cfg_file);
|
||||
void x52d_config_save(const char *cfg_file);
|
||||
|
||||
int x52d_config_set(const char *section, const char *key, const char *value);
|
||||
const char *x52d_config_get(const char *section, const char *key);
|
||||
|
||||
#endif // !defined X52D_CONFIG_H
|
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
* Saitek X52 Pro MFD & LED driver - Configuration dumper
|
||||
*
|
||||
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define PINELOG_MODULE X52D_MOD_CONFIG
|
||||
#include "pinelog.h"
|
||||
#include "libx52.h"
|
||||
#include "x52d_config.h"
|
||||
#include "x52d_const.h"
|
||||
|
||||
// Create a pointer "name" of type "type", which stores the value of the
|
||||
// corresponding element within the config struct.
|
||||
#define CONFIG_PTR(type, name) type name = (type)((uintptr_t)cfg + offset)
|
||||
|
||||
// Check if the parameters are all valid
|
||||
#define CHECK_PARAMS() do { if (cfg == NULL || section == NULL || key == NULL) { return NULL; } } while(0)
|
||||
|
||||
static const char * bool_dumper(const char *section, const char *key, const struct x52d_config *cfg, size_t offset)
|
||||
{
|
||||
CONFIG_PTR(bool *, config);
|
||||
CHECK_PARAMS();
|
||||
|
||||
PINELOG_TRACE("Printing bool value %s.%s from offset %lu value = %d",
|
||||
section, key, offset, *config);
|
||||
return *config ? "true" : "false";
|
||||
}
|
||||
|
||||
static const char * string_dumper(const char *section, const char *key, struct x52d_config *cfg, size_t offset)
|
||||
{
|
||||
CONFIG_PTR(char *, config);
|
||||
CHECK_PARAMS();
|
||||
|
||||
PINELOG_TRACE("Printing string value %s.%s from offset %lu value = %s",
|
||||
section, key, offset, config);
|
||||
return config;
|
||||
}
|
||||
|
||||
static const char * int_dumper(const char *section, const char *key, struct x52d_config *cfg, size_t offset)
|
||||
{
|
||||
static char dump[256];
|
||||
CONFIG_PTR(int *, config);
|
||||
CHECK_PARAMS();
|
||||
|
||||
PINELOG_TRACE("Printing int value %s.%s from offset %lu value = %d",
|
||||
section, key, offset, *config);
|
||||
snprintf(dump, sizeof(dump), "%d", *config);
|
||||
|
||||
return dump;
|
||||
}
|
||||
|
||||
static const char * led_dumper(const char *section, const char *key, struct x52d_config *cfg, size_t offset)
|
||||
{
|
||||
CONFIG_PTR(libx52_led_state *, config);
|
||||
CHECK_PARAMS();
|
||||
|
||||
PINELOG_TRACE("Printing led value %s.%s from offset %lu value = %d",
|
||||
section, key, offset, *config);
|
||||
return libx52_led_state_to_str(*config);
|
||||
}
|
||||
|
||||
static const char * clock_format_dumper(const char *section, const char *key, struct x52d_config *cfg, size_t offset)
|
||||
{
|
||||
CONFIG_PTR(libx52_clock_format *, config);
|
||||
CHECK_PARAMS();
|
||||
|
||||
PINELOG_TRACE("Printing clock format value %s.%s from offset %lu value = %d",
|
||||
section, key, offset, *config);
|
||||
return libx52_clock_format_to_str(*config);
|
||||
}
|
||||
|
||||
static const char * date_format_dumper(const char *section, const char *key, struct x52d_config *cfg, size_t offset)
|
||||
{
|
||||
CONFIG_PTR(libx52_date_format *, config);
|
||||
CHECK_PARAMS();
|
||||
|
||||
PINELOG_TRACE("Printing date format value %s.%s from offset %lu value = %d",
|
||||
section, key, offset, *config);
|
||||
return libx52_date_format_to_str(*config);
|
||||
}
|
||||
|
||||
#undef CHECK_PARAMS
|
||||
#undef CONFIG_PTR
|
||||
|
||||
int x52d_config_save_file(struct x52d_config *cfg, const char *cfg_file)
|
||||
{
|
||||
FILE *cfg_fp;
|
||||
char *current_section = NULL;
|
||||
const char *value;
|
||||
|
||||
if (cfg == NULL || cfg_file == NULL) {
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
cfg_fp = fopen(cfg_file, "w");
|
||||
if (cfg_fp == NULL) {
|
||||
PINELOG_ERROR(_("Unable to save config file %s - code %d: %s"),
|
||||
cfg_file, errno, strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
|
||||
PINELOG_TRACE("Saving configuration to file %s", cfg_file);
|
||||
#define CFG(section, key, name, type, def) do { \
|
||||
if (current_section == NULL || strcasecmp(current_section, #section)) { \
|
||||
if (current_section != NULL) { \
|
||||
free(current_section); \
|
||||
} \
|
||||
current_section = strdup(#section); \
|
||||
PINELOG_TRACE("Printing section header %s", #section); \
|
||||
fprintf(cfg_fp, "[%s]\n", #section); \
|
||||
} \
|
||||
PINELOG_TRACE("Dumping " #section "." #key " to file %s", cfg_file); \
|
||||
value = type ## _dumper(#section, #key, cfg, offsetof(struct x52d_config, name)); \
|
||||
if (value == NULL) { \
|
||||
PINELOG_ERROR(_("Failed to dump %s.%s to config file %s"), \
|
||||
#section, #key, cfg_file); \
|
||||
goto exit_dump; \
|
||||
} else { \
|
||||
fprintf(cfg_fp, "%s = %s\n", #key, value); \
|
||||
} \
|
||||
} while (0);
|
||||
#include "x52d_config.def"
|
||||
|
||||
exit_dump:
|
||||
free(current_section);
|
||||
fclose(cfg_fp);
|
||||
return (value == NULL);
|
||||
}
|
||||
|
||||
const char *x52d_config_get_param(struct x52d_config *cfg, const char *section, const char *key)
|
||||
{
|
||||
#define CFG(section_c, key_c, name, type, def) do { \
|
||||
if (strcasecmp(section, #section_c) == 0 && strcasecmp(key, #key_c) == 0) { \
|
||||
return type ## _dumper(section, key, cfg, offsetof(struct x52d_config, name)); \
|
||||
} \
|
||||
} while (0);
|
||||
#include "x52d_config.def"
|
||||
|
||||
return NULL;
|
||||
}
|
|
@ -0,0 +1,365 @@
|
|||
/*
|
||||
* Saitek X52 Pro MFD & LED driver - Configuration parser
|
||||
*
|
||||
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define PINELOG_MODULE X52D_MOD_CONFIG
|
||||
#include "ini.h"
|
||||
#include "pinelog.h"
|
||||
#include "x52d_config.h"
|
||||
#include "x52d_const.h"
|
||||
|
||||
/* Parser function typedef */
|
||||
typedef int (*parser_fn)(struct x52d_config *, size_t, const char *);
|
||||
|
||||
// Check if the parameters are all valid
|
||||
#define CHECK_PARAMS() do { if (cfg == NULL || value == NULL) { return EINVAL; } } while(0)
|
||||
|
||||
// Create a pointer "name" of type "type", which stores the pointer to the
|
||||
// corresponding element within the config struct.
|
||||
#define CONFIG_PTR(type, name) type name = (type)((uintptr_t)cfg + offset)
|
||||
|
||||
static int bool_parser(struct x52d_config *cfg, size_t offset, const char *value)
|
||||
{
|
||||
CONFIG_PTR(bool *, config);
|
||||
CHECK_PARAMS();
|
||||
|
||||
if (!strcasecmp(value, "yes") || !strcasecmp(value, "true")) {
|
||||
*config = true;
|
||||
} else if (!strcasecmp(value, "no") || !strcasecmp(value, "false")) {
|
||||
*config = false;
|
||||
} else {
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int string_parser(struct x52d_config *cfg, size_t offset, const char *value)
|
||||
{
|
||||
CONFIG_PTR(char *, config);
|
||||
CHECK_PARAMS();
|
||||
|
||||
/* String parameters are all NAME_MAX len */
|
||||
strncpy(config, value, NAME_MAX-1);
|
||||
config[NAME_MAX-1] = '\0';
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int int_parser(struct x52d_config *cfg, size_t offset, const char *value)
|
||||
{
|
||||
CONFIG_PTR(int *, config);
|
||||
char *endptr;
|
||||
int retval;
|
||||
|
||||
CHECK_PARAMS();
|
||||
|
||||
errno = 0;
|
||||
retval = strtol(value, &endptr, 0);
|
||||
if (errno != 0) {
|
||||
return errno;
|
||||
}
|
||||
if (*endptr != '\0') {
|
||||
// Invalid characters in string
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
*config = retval;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int led_parser(struct x52d_config *cfg, size_t offset, const char *value)
|
||||
{
|
||||
CONFIG_PTR(libx52_led_state *, config);
|
||||
CHECK_PARAMS();
|
||||
|
||||
#define MATCH_STATE(val) if (!strcasecmp(value, #val)) { *config = LIBX52_LED_STATE_ ## val ; }
|
||||
MATCH_STATE(OFF)
|
||||
else MATCH_STATE(ON)
|
||||
else MATCH_STATE(RED)
|
||||
else MATCH_STATE(AMBER)
|
||||
else MATCH_STATE(GREEN)
|
||||
else return EINVAL;
|
||||
#undef MATCH_STATE
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int clock_format_parser(struct x52d_config *cfg, size_t offset, const char *value)
|
||||
{
|
||||
CONFIG_PTR(libx52_clock_format *, config);
|
||||
CHECK_PARAMS();
|
||||
|
||||
if (!strcasecmp(value, "12hr") || !strcasecmp(value, "12")) {
|
||||
*config = LIBX52_CLOCK_FORMAT_12HR;
|
||||
} else if (!strcasecmp(value, "24hr") || !strcasecmp(value, "24")) {
|
||||
*config = LIBX52_CLOCK_FORMAT_24HR;
|
||||
} else {
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int date_format_parser(struct x52d_config *cfg, size_t offset, const char *value)
|
||||
{
|
||||
CONFIG_PTR(libx52_date_format *, config);
|
||||
CHECK_PARAMS();
|
||||
|
||||
if (!strcasecmp(value, "ddmmyy") || !strcasecmp(value, "dd-mm-yy")) {
|
||||
*config = LIBX52_DATE_FORMAT_DDMMYY;
|
||||
} else if (!strcasecmp(value, "mmddyy") || !strcasecmp(value, "mm-dd-yy")) {
|
||||
*config = LIBX52_DATE_FORMAT_MMDDYY;
|
||||
} else if (!strcasecmp(value, "yymmdd") || !strcasecmp(value, "yy-mm-dd")) {
|
||||
*config = LIBX52_DATE_FORMAT_YYMMDD;
|
||||
} else {
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#undef CHECK_PARAMS
|
||||
#undef CONFIG_PTR
|
||||
|
||||
/* Map for config->param */
|
||||
#define CFG(section, key, name, type, def) {#section, #key, type ## _parser, offsetof(struct x52d_config, name)},
|
||||
static const struct config_map {
|
||||
const char *section;
|
||||
const char *key;
|
||||
parser_fn parser;
|
||||
size_t offset;
|
||||
} config_map[] = {
|
||||
#include "x52d_config.def"
|
||||
|
||||
// Terminating entry
|
||||
{NULL, NULL, NULL, 0}
|
||||
};
|
||||
|
||||
int x52d_config_process_kv(void *user, const char *section, const char *key, const char *value)
|
||||
{
|
||||
int i;
|
||||
int rc = 0;
|
||||
bool found = false;
|
||||
struct x52d_config *cfg = (struct x52d_config*)user;
|
||||
|
||||
for (i = 0; config_map[i].key != NULL; i++) {
|
||||
rc = 0;
|
||||
if (!strcasecmp(config_map[i].key, key) &&
|
||||
!strcasecmp(config_map[i].section, section)) {
|
||||
found = true;
|
||||
PINELOG_TRACE("Setting '%s.%s'='%s'",
|
||||
config_map[i].section, config_map[i].key, value);
|
||||
rc = config_map[i].parser(cfg, config_map[i].offset, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
// Print error message, but continue
|
||||
PINELOG_INFO(_("Ignoring unknown key '%s.%s'"), section, key);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set configuration defaults
|
||||
*
|
||||
* @param[in] cfg Pointer to config struct
|
||||
*
|
||||
* @returns 0 on success, non-zero error code on failure
|
||||
*/
|
||||
int x52d_config_set_defaults(struct x52d_config *cfg) {
|
||||
int rc;
|
||||
|
||||
if (cfg == NULL) {
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
PINELOG_TRACE("Setting configuration defaults");
|
||||
#define CFG(section, key, name, parser, def) \
|
||||
rc = x52d_config_process_kv(cfg, #section, #key, #def); \
|
||||
if (rc != 0) { \
|
||||
return rc; \
|
||||
}
|
||||
#include "x52d_config.def"
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int x52d_config_load_file(struct x52d_config *cfg, const char *cfg_file)
|
||||
{
|
||||
int rc;
|
||||
if (cfg == NULL || cfg_file == NULL) {
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
PINELOG_TRACE("Loading configuration from file %s", cfg_file);
|
||||
rc = ini_parse(cfg_file, x52d_config_process_kv, cfg);
|
||||
if (rc < 0) {
|
||||
PINELOG_ERROR(_("Failed processing configuration file %s - code %d"),
|
||||
cfg_file, rc);
|
||||
return EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct x52d_config_override {
|
||||
char *section;
|
||||
char *key;
|
||||
char *value;
|
||||
struct x52d_config_override *next;
|
||||
};
|
||||
|
||||
static struct x52d_config_override *override_head;
|
||||
static struct x52d_config_override *override_tail;
|
||||
|
||||
int x52d_config_save_override(const char *override_str)
|
||||
{
|
||||
// Parse override string of the form section.key=value
|
||||
struct x52d_config_override *override;
|
||||
char *string = NULL;
|
||||
char *free_ptr = NULL;
|
||||
char *ptr;
|
||||
int rc;
|
||||
|
||||
PINELOG_TRACE("Allocating memory (%lu bytes) for override structure", sizeof(*override));
|
||||
override = calloc(1, sizeof(*override));
|
||||
if (override == NULL) {
|
||||
PINELOG_ERROR(_("Failed to allocate memory for override structure"));
|
||||
rc = ENOMEM;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
errno = 0;
|
||||
PINELOG_TRACE("Duplicating override string");
|
||||
string = strdup(override_str);
|
||||
if (string == NULL) {
|
||||
PINELOG_ERROR(_("Failed to allocate memory for override string"));
|
||||
rc = errno;
|
||||
goto cleanup;
|
||||
}
|
||||
free_ptr = string;
|
||||
|
||||
override->section = string;
|
||||
// Ensure that the string is of the form ([^.]+\.[^=]+=.*)
|
||||
ptr = strchr(string, '.');
|
||||
if (ptr == NULL || ptr == string) {
|
||||
// No section found
|
||||
PINELOG_ERROR(_("No section found in override string '%s'"), string);
|
||||
rc = EINVAL;
|
||||
goto cleanup;
|
||||
}
|
||||
// Reset the . to NUL
|
||||
*ptr = '\0';
|
||||
ptr++;
|
||||
PINELOG_TRACE("Splitting override string to '%s' and '%s'", string, ptr);
|
||||
string = ptr;
|
||||
|
||||
override->key = string;
|
||||
ptr = strchr(string, '=');
|
||||
if (ptr == NULL || ptr == string) {
|
||||
// No key found
|
||||
PINELOG_ERROR(_("No key found in override string '%s'"), string);
|
||||
rc = EINVAL;
|
||||
goto cleanup;
|
||||
}
|
||||
// Reset the = to NUL
|
||||
*ptr = '\0';
|
||||
ptr++;
|
||||
PINELOG_TRACE("Splitting override string to '%s' and '%s'", string, ptr);
|
||||
|
||||
if (*ptr == '\0') {
|
||||
// No value found
|
||||
PINELOG_ERROR(_("No value found in override string '%s'"), string);
|
||||
rc = EINVAL;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
override->value = ptr;
|
||||
|
||||
// Add the override to the linked list
|
||||
if (override_tail != NULL) {
|
||||
PINELOG_TRACE("Linking override to list tail");
|
||||
override_tail->next = override;
|
||||
}
|
||||
PINELOG_TRACE("Setting list tail to override");
|
||||
override_tail = override;
|
||||
|
||||
if (override_head == NULL) {
|
||||
PINELOG_TRACE("Setting list head to override");
|
||||
override_head = override;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
cleanup:
|
||||
if (free_ptr != NULL) {
|
||||
free(free_ptr);
|
||||
}
|
||||
if (override != NULL) {
|
||||
free(override);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
int x52d_config_apply_overrides(struct x52d_config *cfg)
|
||||
{
|
||||
int rc;
|
||||
struct x52d_config_override *tmp = override_head;
|
||||
|
||||
if (cfg == NULL) {
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
while (tmp != NULL) {
|
||||
PINELOG_TRACE("Processing override '%s.%s=%s'",
|
||||
tmp->section,
|
||||
tmp->key,
|
||||
tmp->value);
|
||||
rc = x52d_config_process_kv(cfg,
|
||||
tmp->section,
|
||||
tmp->key,
|
||||
tmp->value);
|
||||
if (rc != 0) {
|
||||
PINELOG_ERROR(_("Error processing override '%s.%s=%s'"),
|
||||
tmp->section,
|
||||
tmp->key,
|
||||
tmp->value);
|
||||
return rc;
|
||||
}
|
||||
tmp = tmp->next;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void x52d_config_clear_overrides(void)
|
||||
{
|
||||
struct x52d_config_override *tmp;
|
||||
while (override_head != NULL) {
|
||||
tmp = override_head;
|
||||
override_head = override_head->next;
|
||||
PINELOG_TRACE("Freeing override '%s.%s=%s'",
|
||||
tmp->section,
|
||||
tmp->key,
|
||||
tmp->value);
|
||||
free(tmp);
|
||||
}
|
||||
|
||||
override_tail = NULL;
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Saitek X52 Pro MFD & LED driver - Application constants
|
||||
*
|
||||
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#ifndef X52D_CONST_H
|
||||
#define X52D_CONST_H
|
||||
|
||||
#define X52D_APP_NAME "x52d"
|
||||
|
||||
#define X52D_LOG_FILE LOGDIR "/" X52D_APP_NAME ".log"
|
||||
|
||||
#define X52D_SYS_CFG_FILE SYSCONFDIR "/" X52D_APP_NAME "/" X52D_APP_NAME ".conf"
|
||||
|
||||
#define X52D_PID_FILE RUNDIR "/" X52D_APP_NAME ".pid"
|
||||
|
||||
#define X52D_SOCK_COMMAND RUNDIR "/" X52D_APP_NAME ".cmd"
|
||||
#define X52D_SOCK_NOTIFY RUNDIR "/" X52D_APP_NAME ".notify"
|
||||
|
||||
#include "gettext.h"
|
||||
#define N_(x) gettext_noop(x)
|
||||
#define _(x) gettext(x)
|
||||
|
||||
#define X52D_MAX_CLIENTS 63
|
||||
|
||||
enum {
|
||||
X52D_MOD_CONFIG,
|
||||
X52D_MOD_CLOCK,
|
||||
X52D_MOD_DEVICE,
|
||||
X52D_MOD_IO,
|
||||
X52D_MOD_LED,
|
||||
X52D_MOD_MOUSE,
|
||||
X52D_MOD_COMMAND,
|
||||
X52D_MOD_CLIENT,
|
||||
X52D_MOD_NOTIFY,
|
||||
|
||||
X52D_MOD_MAX
|
||||
};
|
||||
|
||||
#endif // !defined X52D_CONST_H
|
|
@ -0,0 +1,187 @@
|
|||
/*
|
||||
* Saitek X52 Pro MFD & LED driver - Device manager
|
||||
*
|
||||
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include <unistd.h>
|
||||
#include <pthread.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define PINELOG_MODULE X52D_MOD_DEVICE
|
||||
#include "x52d_const.h"
|
||||
#include "x52d_config.h"
|
||||
#include "x52d_device.h"
|
||||
#include "x52d_notify.h"
|
||||
#include "libx52.h"
|
||||
#include "pinelog.h"
|
||||
|
||||
static libx52_device *x52_dev;
|
||||
|
||||
static pthread_mutex_t device_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
static pthread_t device_thr;
|
||||
static volatile bool device_update_needed;
|
||||
|
||||
static void *x52_dev_thr(void *param)
|
||||
{
|
||||
int rc;
|
||||
|
||||
#define DEV_ACQ_DELAY 5 // seconds
|
||||
#define DEV_UPD_DELAY 50000 // microseconds
|
||||
|
||||
PINELOG_INFO(_("Starting X52 device manager thread"));
|
||||
for (;;) {
|
||||
if (!libx52_is_connected(x52_dev)) {
|
||||
PINELOG_TRACE("Attempting to connect to X52 device");
|
||||
rc = libx52_connect(x52_dev);
|
||||
if (rc != LIBX52_SUCCESS) {
|
||||
if (rc != LIBX52_ERROR_NO_DEVICE) {
|
||||
PINELOG_ERROR(_("Error %d connecting to device: %s"),
|
||||
rc, libx52_strerror(rc));
|
||||
} else {
|
||||
PINELOG_TRACE("No compatible X52 device found");
|
||||
}
|
||||
PINELOG_TRACE("Sleeping for %d seconds before trying to acquire device again", DEV_ACQ_DELAY);
|
||||
sleep(DEV_ACQ_DELAY);
|
||||
} else {
|
||||
/* Successfully connected */
|
||||
PINELOG_INFO(_("Device connected, writing configuration"));
|
||||
X52D_NOTIFY("CONNECTED");
|
||||
x52d_config_apply();
|
||||
}
|
||||
} else {
|
||||
if (!device_update_needed) {
|
||||
usleep(DEV_UPD_DELAY);
|
||||
continue;
|
||||
}
|
||||
|
||||
(void)x52d_dev_update();
|
||||
}
|
||||
}
|
||||
|
||||
#undef DEV_ACQ_DELAY
|
||||
#undef DEV_UPD_DELAY
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void x52d_dev_init(void)
|
||||
{
|
||||
int rc;
|
||||
PINELOG_INFO(_("Initializing libx52"));
|
||||
rc = libx52_init(&x52_dev);
|
||||
|
||||
if (rc != LIBX52_SUCCESS) {
|
||||
PINELOG_FATAL(_("Failure %d initializing libx52: %s"),
|
||||
rc, libx52_strerror(rc));
|
||||
}
|
||||
|
||||
// Create and initialize the thread
|
||||
pthread_create(&device_thr, NULL, x52_dev_thr, NULL);
|
||||
}
|
||||
|
||||
void x52d_dev_exit(void)
|
||||
{
|
||||
// Shutdown any threads
|
||||
PINELOG_INFO(_("Shutting down X52 device manager thread"));
|
||||
pthread_cancel(device_thr);
|
||||
|
||||
libx52_exit(x52_dev);
|
||||
}
|
||||
|
||||
#define WRAP_LIBX52(func) \
|
||||
int rc; \
|
||||
pthread_mutex_lock(&device_mutex); \
|
||||
rc = func; \
|
||||
pthread_mutex_unlock(&device_mutex); \
|
||||
if (rc != LIBX52_SUCCESS) { \
|
||||
if (rc != LIBX52_ERROR_TRY_AGAIN) { \
|
||||
PINELOG_ERROR(_("Error %d when updating X52 parameter: %s"), \
|
||||
rc, libx52_strerror(rc)); \
|
||||
} \
|
||||
} else { \
|
||||
device_update_needed = true; \
|
||||
} \
|
||||
return rc
|
||||
|
||||
int x52d_dev_set_text(uint8_t line, const char *text, uint8_t length)
|
||||
{
|
||||
WRAP_LIBX52(libx52_set_text(x52_dev, line, text, length));
|
||||
}
|
||||
int x52d_dev_set_led_state(libx52_led_id led, libx52_led_state state)
|
||||
{
|
||||
if (libx52_check_feature(x52_dev, LIBX52_FEATURE_LED) != LIBX52_ERROR_NOT_SUPPORTED) {
|
||||
WRAP_LIBX52(libx52_set_led_state(x52_dev, led, state));
|
||||
}
|
||||
|
||||
// If the target device does not support setting individual LEDs,
|
||||
// then ignore the set and let the caller think it succeeded.
|
||||
PINELOG_TRACE("Ignoring set LED state call as the device does not support it");
|
||||
return LIBX52_SUCCESS;
|
||||
}
|
||||
int x52d_dev_set_clock(time_t time, int local)
|
||||
{
|
||||
WRAP_LIBX52(libx52_set_clock(x52_dev, time, local));
|
||||
}
|
||||
int x52d_dev_set_clock_timezone(libx52_clock_id clock, int offset)
|
||||
{
|
||||
WRAP_LIBX52(libx52_set_clock_timezone(x52_dev, clock, offset));
|
||||
}
|
||||
int x52d_dev_set_clock_format(libx52_clock_id clock, libx52_clock_format format)
|
||||
{
|
||||
WRAP_LIBX52(libx52_set_clock_format(x52_dev, clock, format));
|
||||
}
|
||||
int x52d_dev_set_time(uint8_t hour, uint8_t minute)
|
||||
{
|
||||
WRAP_LIBX52(libx52_set_time(x52_dev, hour, minute));
|
||||
}
|
||||
int x52d_dev_set_date(uint8_t dd, uint8_t mm, uint8_t yy)
|
||||
{
|
||||
WRAP_LIBX52(libx52_set_date(x52_dev, dd, mm, yy));
|
||||
}
|
||||
int x52d_dev_set_date_format(libx52_date_format format)
|
||||
{
|
||||
WRAP_LIBX52(libx52_set_date_format(x52_dev, format));
|
||||
}
|
||||
int x52d_dev_set_brightness(uint8_t mfd, uint16_t brightness)
|
||||
{
|
||||
WRAP_LIBX52(libx52_set_brightness(x52_dev, mfd, brightness));
|
||||
}
|
||||
int x52d_dev_set_shift(uint8_t state)
|
||||
{
|
||||
WRAP_LIBX52(libx52_set_shift(x52_dev, state));
|
||||
}
|
||||
int x52d_dev_set_blink(uint8_t state)
|
||||
{
|
||||
WRAP_LIBX52(libx52_set_blink(x52_dev, state));
|
||||
}
|
||||
|
||||
int x52d_dev_update(void)
|
||||
{
|
||||
int rc;
|
||||
|
||||
pthread_mutex_lock(&device_mutex);
|
||||
rc = libx52_update(x52_dev);
|
||||
pthread_mutex_unlock(&device_mutex);
|
||||
|
||||
if (rc != LIBX52_SUCCESS) {
|
||||
if (rc == LIBX52_ERROR_NO_DEVICE) {
|
||||
// Detach from the existing device, the next thread run will
|
||||
// pick it up.
|
||||
PINELOG_TRACE("Disconnecting detached device");
|
||||
libx52_disconnect(x52_dev);
|
||||
X52D_NOTIFY("DISCONNECTED");
|
||||
} else {
|
||||
PINELOG_ERROR(_("Error %d when updating X52 device: %s"),
|
||||
rc, libx52_strerror(rc));
|
||||
}
|
||||
} else {
|
||||
device_update_needed = false;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Saitek X52 Pro MFD & LED driver - Device manager header
|
||||
*
|
||||
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#ifndef X52D_DEVICE_H
|
||||
#define X52D_DEVICE_H
|
||||
|
||||
#include "libx52.h"
|
||||
|
||||
void x52d_dev_init(void);
|
||||
void x52d_dev_exit(void);
|
||||
|
||||
/* Wrapper methods for libx52 calls */
|
||||
int x52d_dev_set_text(uint8_t line, const char *text, uint8_t length);
|
||||
int x52d_dev_set_led_state(libx52_led_id led, libx52_led_state state);
|
||||
int x52d_dev_set_clock(time_t time, int local);
|
||||
int x52d_dev_set_clock_timezone(libx52_clock_id clock, int offset);
|
||||
int x52d_dev_set_clock_format(libx52_clock_id clock, libx52_clock_format format);
|
||||
int x52d_dev_set_time(uint8_t hour, uint8_t minute);
|
||||
int x52d_dev_set_date(uint8_t dd, uint8_t mm, uint8_t yy);
|
||||
int x52d_dev_set_date_format(libx52_date_format format);
|
||||
int x52d_dev_set_brightness(uint8_t mfd, uint16_t brightness);
|
||||
int x52d_dev_set_shift(uint8_t state);
|
||||
int x52d_dev_set_blink(uint8_t state);
|
||||
int x52d_dev_update(void);
|
||||
|
||||
#endif // !defined X52D_DEVICE_H
|
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* Saitek X52 Pro MFD & LED driver - I/O driver
|
||||
*
|
||||
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include <stdbool.h>
|
||||
#include <unistd.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#include "x52d_const.h"
|
||||
#include "x52d_config.h"
|
||||
#include "x52d_io.h"
|
||||
#include "x52d_mouse.h"
|
||||
#include "libx52io.h"
|
||||
|
||||
#define PINELOG_MODULE X52D_MOD_IO
|
||||
#include "pinelog.h"
|
||||
|
||||
static libx52io_context *io_ctx;
|
||||
|
||||
static pthread_t io_thr;
|
||||
|
||||
static void process_report(libx52io_report *report, libx52io_report *prev)
|
||||
{
|
||||
// TODO: Process changes
|
||||
x52d_mouse_report_event(report);
|
||||
memcpy(prev, report, sizeof(*prev));
|
||||
}
|
||||
|
||||
static void *x52_io_thr(void *param)
|
||||
{
|
||||
int rc;
|
||||
libx52io_report report;
|
||||
libx52io_report prev_report;
|
||||
|
||||
#define IO_READ_TIMEOUT 50 /* milliseconds */
|
||||
#define IO_ACQ_TIMEOUT 5 /* seconds */
|
||||
PINELOG_INFO(_("Starting X52 I/O thread"));
|
||||
|
||||
// Reset the previous report, so that process_report can handle changes.
|
||||
memset(&prev_report, 0, sizeof(prev_report));
|
||||
|
||||
for (;;) {
|
||||
rc = libx52io_read_timeout(io_ctx, &report, IO_READ_TIMEOUT);
|
||||
switch (rc) {
|
||||
case LIBX52IO_SUCCESS:
|
||||
// Found a report
|
||||
process_report(&report, &prev_report);
|
||||
break;
|
||||
|
||||
case LIBX52IO_ERROR_TIMEOUT:
|
||||
// No report received within the timeout
|
||||
break;
|
||||
|
||||
case LIBX52IO_ERROR_NO_DEVICE:
|
||||
PINELOG_TRACE("Device disconnected, trying to connect");
|
||||
rc = libx52io_open(io_ctx);
|
||||
if (rc != LIBX52IO_SUCCESS) {
|
||||
if (rc != LIBX52IO_ERROR_NO_DEVICE) {
|
||||
PINELOG_ERROR(_("Error %d opening X52 I/O device: %s"),
|
||||
rc, libx52io_strerror(rc));
|
||||
} else {
|
||||
PINELOG_TRACE("No compatible X52 I/O device found. Sleeping %d seconds before trying again.",
|
||||
IO_ACQ_TIMEOUT);
|
||||
}
|
||||
sleep(IO_ACQ_TIMEOUT);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
PINELOG_ERROR(_("Error %d reading from X52 I/O device: %s"),
|
||||
rc, libx52io_strerror(rc));
|
||||
|
||||
/*
|
||||
* Possibly disconnected, better to force disconnect now, and try
|
||||
* to reconnect later
|
||||
*/
|
||||
libx52io_close(io_ctx);
|
||||
|
||||
/* Report a NULL report to reset the mouse to default state */
|
||||
x52d_mouse_report_event(NULL);
|
||||
break;
|
||||
}
|
||||
}
|
||||
#undef IO_READ_TIMEOUT
|
||||
#undef IO_ACQ_TIMEOUT
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void x52d_io_init(void)
|
||||
{
|
||||
int rc;
|
||||
|
||||
PINELOG_TRACE("Initializing I/O driver");
|
||||
rc = libx52io_init(&io_ctx);
|
||||
if (rc != LIBX52IO_SUCCESS) {
|
||||
PINELOG_FATAL(_("Error %d initializing X52 I/O library: %s"),
|
||||
rc, libx52io_strerror(rc));
|
||||
}
|
||||
|
||||
rc = pthread_create(&io_thr, NULL, x52_io_thr, NULL);
|
||||
if (rc != 0) {
|
||||
PINELOG_FATAL(_("Error %d initializing I/O driver thread: %s"),
|
||||
rc, strerror(rc));
|
||||
}
|
||||
}
|
||||
|
||||
void x52d_io_exit(void)
|
||||
{
|
||||
PINELOG_INFO(_("Shutting down X52 I/O driver thread"));
|
||||
pthread_cancel(io_thr);
|
||||
|
||||
libx52io_exit(io_ctx);
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* Saitek X52 Pro MFD & LED driver - I/O driver
|
||||
*
|
||||
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#ifndef X52D_IO_H
|
||||
#define X52D_IO_H
|
||||
|
||||
void x52d_io_init(void);
|
||||
void x52d_io_exit(void);
|
||||
|
||||
#endif // !defined X52D_IO_H
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* Saitek X52 Pro MFD & LED driver - Clock manager
|
||||
*
|
||||
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <pthread.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
#define PINELOG_MODULE X52D_MOD_LED
|
||||
#include "pinelog.h"
|
||||
#include "x52d_config.h"
|
||||
#include "x52d_const.h"
|
||||
#include "x52d_device.h"
|
||||
|
||||
#define SET_LED_STATE(led, state) \
|
||||
PINELOG_TRACE("Setting LED %s state to %s (%d)", \
|
||||
libx52_led_id_to_str(LIBX52_LED_ ## led), \
|
||||
libx52_led_state_to_str(state), state); \
|
||||
x52d_dev_set_led_state(LIBX52_LED_ ## led, state);
|
||||
|
||||
void x52d_cfg_set_LED_Fire(libx52_led_state state)
|
||||
{
|
||||
SET_LED_STATE(FIRE, state);
|
||||
}
|
||||
|
||||
void x52d_cfg_set_LED_Throttle(libx52_led_state state)
|
||||
{
|
||||
SET_LED_STATE(THROTTLE, state);
|
||||
}
|
||||
|
||||
void x52d_cfg_set_LED_A(libx52_led_state state)
|
||||
{
|
||||
SET_LED_STATE(A, state);
|
||||
}
|
||||
|
||||
void x52d_cfg_set_LED_B(libx52_led_state state)
|
||||
{
|
||||
SET_LED_STATE(B, state);
|
||||
}
|
||||
|
||||
void x52d_cfg_set_LED_D(libx52_led_state state)
|
||||
{
|
||||
SET_LED_STATE(D, state);
|
||||
}
|
||||
|
||||
void x52d_cfg_set_LED_E(libx52_led_state state)
|
||||
{
|
||||
SET_LED_STATE(E, state);
|
||||
}
|
||||
|
||||
void x52d_cfg_set_LED_T1(libx52_led_state state)
|
||||
{
|
||||
SET_LED_STATE(T1, state);
|
||||
}
|
||||
|
||||
void x52d_cfg_set_LED_T2(libx52_led_state state)
|
||||
{
|
||||
SET_LED_STATE(T2, state);
|
||||
}
|
||||
|
||||
void x52d_cfg_set_LED_T3(libx52_led_state state)
|
||||
{
|
||||
SET_LED_STATE(T3, state);
|
||||
}
|
||||
|
||||
void x52d_cfg_set_LED_POV(libx52_led_state state)
|
||||
{
|
||||
SET_LED_STATE(POV, state);
|
||||
}
|
||||
|
||||
void x52d_cfg_set_LED_Clutch(libx52_led_state state)
|
||||
{
|
||||
SET_LED_STATE(CLUTCH, state);
|
||||
}
|
||||
|
||||
#define SET_BRIGHTNESS(mfd, brightness) \
|
||||
PINELOG_TRACE("Setting %s brightness to %u", mfd ? "MFD" : "LED", brightness); \
|
||||
x52d_dev_set_brightness(mfd, brightness);
|
||||
|
||||
void x52d_cfg_set_Brightness_MFD(uint16_t brightness)
|
||||
{
|
||||
SET_BRIGHTNESS(1, brightness);
|
||||
}
|
||||
|
||||
void x52d_cfg_set_Brightness_LED(uint16_t brightness)
|
||||
{
|
||||
SET_BRIGHTNESS(0, brightness);
|
||||
}
|
|
@ -0,0 +1,381 @@
|
|||
/*
|
||||
* Saitek X52 Pro MFD & LED driver - Service daemon
|
||||
*
|
||||
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "x52d_clock.h"
|
||||
#include "x52d_const.h"
|
||||
#include "x52d_config.h"
|
||||
#include "x52d_device.h"
|
||||
#include "x52d_io.h"
|
||||
#include "x52d_mouse.h"
|
||||
#include "x52d_command.h"
|
||||
#include "x52d_notify.h"
|
||||
#include "x52dcomm-internal.h"
|
||||
#include "x52dcomm.h"
|
||||
#include "pinelog.h"
|
||||
|
||||
static volatile int flag_quit;
|
||||
|
||||
static void termination_handler(int signum)
|
||||
{
|
||||
flag_quit = signum;
|
||||
}
|
||||
|
||||
static volatile bool flag_reload;
|
||||
static void reload_handler(int signum)
|
||||
{
|
||||
flag_reload = true;
|
||||
}
|
||||
|
||||
static volatile bool flag_save_cfg;
|
||||
static void save_config_handler(int signum)
|
||||
{
|
||||
flag_save_cfg = true;
|
||||
}
|
||||
|
||||
static void set_log_file(bool foreground, const char *log_file)
|
||||
{
|
||||
int rc = 0;
|
||||
if (log_file != NULL) {
|
||||
rc = pinelog_set_output_file(log_file);
|
||||
} else {
|
||||
if (foreground) {
|
||||
rc = pinelog_set_output_stream(stdout);
|
||||
} else {
|
||||
rc = pinelog_set_output_file(X52D_LOG_FILE);
|
||||
}
|
||||
}
|
||||
|
||||
if (rc != 0) {
|
||||
fprintf(stderr, _("Error %d setting log file: %s\n"), rc, strerror(rc));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
static void listen_signal(int signum, void (*handler)(int))
|
||||
{
|
||||
struct sigaction action;
|
||||
int rc;
|
||||
|
||||
action.sa_handler = handler;
|
||||
sigemptyset(&action.sa_mask);
|
||||
action.sa_flags = SA_RESTART;
|
||||
|
||||
rc = sigaction(signum, &action, NULL);
|
||||
if (rc < 0) {
|
||||
PINELOG_FATAL(_("Error %d installing handler for signal %d: %s"),
|
||||
errno, signum, strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
#if HAVE_FUNC_ATTRIBUTE_NORETURN
|
||||
__attribute__((noreturn))
|
||||
#endif
|
||||
static void usage(int exit_code)
|
||||
{
|
||||
fprintf(stderr,
|
||||
_("Usage: %s [-f] [-v] [-q]\n"
|
||||
"\t[-l log-file] [-o override]\n"
|
||||
"\t[-c config-file] [-p pid-file]\n"
|
||||
"\t[-s command-socket-path]\n"
|
||||
"\t[-b notify-socket-path]\n"),
|
||||
X52D_APP_NAME);
|
||||
exit(exit_code);
|
||||
}
|
||||
|
||||
static void start_daemon(bool foreground, const char *pid_file)
|
||||
{
|
||||
pid_t pid;
|
||||
FILE *pid_fd;
|
||||
|
||||
if (pid_file == NULL) {
|
||||
pid_file = X52D_PID_FILE;
|
||||
}
|
||||
|
||||
if (!foreground) {
|
||||
/* Check if there is an existing daemon process running */
|
||||
pid_fd = fopen(pid_file, "r");
|
||||
if (pid_fd != NULL) {
|
||||
int rc;
|
||||
|
||||
/* File exists, read the PID and check if it exists */
|
||||
rc = fscanf(pid_fd, "%u", &pid);
|
||||
fclose(pid_fd);
|
||||
|
||||
if (rc != 1) {
|
||||
perror("fscanf");
|
||||
} else {
|
||||
rc = kill(pid, 0);
|
||||
if (rc == 0 || (rc < 0 && errno == EPERM)) {
|
||||
PINELOG_FATAL(_("Daemon is already running as PID %u"), pid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Fork off the parent process */
|
||||
pid = fork();
|
||||
if (pid < 0) {
|
||||
/* Error occurred during first fork */
|
||||
perror("fork");
|
||||
exit(EXIT_FAILURE);
|
||||
} else if (pid > 0) {
|
||||
/* Terminate the parent process */
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
/* Make child process a session leader */
|
||||
if (setsid() < 0) {
|
||||
perror("setsid");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
/* Initialize signal handlers. This step is the same whether in foreground
|
||||
* or background mode
|
||||
*/
|
||||
listen_signal(SIGINT, termination_handler);
|
||||
listen_signal(SIGTERM, termination_handler);
|
||||
listen_signal(SIGQUIT, termination_handler);
|
||||
listen_signal(SIGHUP, reload_handler);
|
||||
listen_signal(SIGUSR1, save_config_handler);
|
||||
|
||||
if (!foreground) {
|
||||
/* Fork off for the second time */
|
||||
pid = fork();
|
||||
if (pid < 0) {
|
||||
/* Error occurred during second fork */
|
||||
perror("fork");
|
||||
exit(EXIT_FAILURE);
|
||||
} else if (pid > 0) {
|
||||
/* Terminate the parent */
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
/* Write the PID to the pid_file */
|
||||
pid_fd = fopen(pid_file, "w");
|
||||
if (pid_fd == NULL) {
|
||||
/* Unable to open PID file */
|
||||
perror("fopen");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (fprintf(pid_fd, "%u\n", getpid()) < 0) {
|
||||
perror("fprintf");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (fclose(pid_fd) != 0) {
|
||||
perror("fclose");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/* Set new file permissions */
|
||||
umask(0);
|
||||
|
||||
/* Change the working directory */
|
||||
if (chdir("/")) {
|
||||
/* Error changing the directory */
|
||||
perror("chdir");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/* Close all open file descriptors */
|
||||
for (int x = sysconf(_SC_OPEN_MAX); x >= 0; x--) {
|
||||
close(x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int verbosity = 0;
|
||||
bool quiet = false;
|
||||
bool foreground = false;
|
||||
char *log_file = NULL;
|
||||
char *conf_file = NULL;
|
||||
const char *pid_file = NULL;
|
||||
const char *command_sock = NULL;
|
||||
const char *notify_sock = NULL;
|
||||
int opt;
|
||||
int rc;
|
||||
sigset_t sigblockset;
|
||||
|
||||
/* Initialize gettext */
|
||||
#if ENABLE_NLS
|
||||
setlocale(LC_ALL, "");
|
||||
bindtextdomain(PACKAGE, LOCALEDIR);
|
||||
textdomain(PACKAGE);
|
||||
#endif
|
||||
|
||||
/* Set system defaults */
|
||||
pinelog_init(X52D_MOD_MAX);
|
||||
pinelog_set_level(PINELOG_LVL_WARNING);
|
||||
|
||||
/*
|
||||
* Parse command line arguments
|
||||
*
|
||||
* -f run in foreground
|
||||
* -c path to config file
|
||||
* -o option overrides
|
||||
* -v verbose logging
|
||||
* -q silent behavior
|
||||
* -l path to log file
|
||||
* -p path to PID file (only used if running in background)
|
||||
* -s path to command socket
|
||||
* -b path to notify socket
|
||||
*/
|
||||
while ((opt = getopt(argc, argv, "fvql:o:c:p:s:b:h")) != -1) {
|
||||
switch (opt) {
|
||||
case 'f':
|
||||
foreground = true;
|
||||
break;
|
||||
|
||||
case 'v':
|
||||
if (!quiet) {
|
||||
if (verbosity <= PINELOG_LVL_TRACE) {
|
||||
verbosity++;
|
||||
pinelog_set_level(pinelog_get_level() + 1);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'q':
|
||||
quiet = true;
|
||||
pinelog_set_level(PINELOG_LVL_ERROR);
|
||||
break;
|
||||
|
||||
case 'l':
|
||||
log_file = optarg;
|
||||
break;
|
||||
|
||||
case 'o':
|
||||
if (x52d_config_save_override(optarg)) {
|
||||
fprintf(stderr,
|
||||
_("Unable to parse configuration override '%s'\n"),
|
||||
optarg);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'c':
|
||||
conf_file = optarg;
|
||||
break;
|
||||
|
||||
case 'p':
|
||||
pid_file = optarg;
|
||||
break;
|
||||
|
||||
case 's':
|
||||
command_sock = optarg;
|
||||
break;
|
||||
|
||||
case 'b':
|
||||
notify_sock = optarg;
|
||||
break;
|
||||
|
||||
case 'h':
|
||||
usage(EXIT_SUCCESS);
|
||||
break;
|
||||
|
||||
default:
|
||||
usage(EXIT_FAILURE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
PINELOG_DEBUG(_("Foreground = %s"), foreground ? _("true") : _("false"));
|
||||
PINELOG_DEBUG(_("Quiet = %s"), quiet ? _("true") : _("false"));
|
||||
PINELOG_DEBUG(_("Verbosity = %d"), verbosity);
|
||||
PINELOG_DEBUG(_("Log file = %s"), log_file);
|
||||
PINELOG_DEBUG(_("Config file = %s"), conf_file);
|
||||
PINELOG_DEBUG(_("PID file = %s"), pid_file);
|
||||
PINELOG_DEBUG(_("Command socket = %s"), command_sock);
|
||||
PINELOG_DEBUG(_("Notify socket = %s"), notify_sock);
|
||||
|
||||
start_daemon(foreground, pid_file);
|
||||
|
||||
set_log_file(foreground, log_file);
|
||||
x52d_config_load(conf_file);
|
||||
|
||||
// Disable pthread signals
|
||||
sigfillset(&sigblockset);
|
||||
rc = pthread_sigmask(SIG_BLOCK, &sigblockset, NULL);
|
||||
if (rc != 0) {
|
||||
PINELOG_FATAL(_("Error %d blocking signals on child threads: %s"),
|
||||
errno, strerror(errno));
|
||||
}
|
||||
|
||||
// Start device threads
|
||||
x52d_dev_init();
|
||||
x52d_clock_init();
|
||||
if (x52d_command_init(command_sock) < 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
x52d_notify_init(notify_sock);
|
||||
#if defined(HAVE_EVDEV)
|
||||
x52d_io_init();
|
||||
x52d_mouse_evdev_init();
|
||||
#endif
|
||||
|
||||
// Re-enable signals
|
||||
rc = pthread_sigmask(SIG_UNBLOCK, &sigblockset, NULL);
|
||||
if (rc != 0) {
|
||||
PINELOG_FATAL(_("Error %d unblocking signals on child threads: %s"),
|
||||
errno, strerror(errno));
|
||||
}
|
||||
|
||||
// Apply configuration
|
||||
x52d_config_apply();
|
||||
|
||||
flag_quit = 0;
|
||||
while(!flag_quit) {
|
||||
pause();
|
||||
|
||||
/* Check if we need to reload configuration */
|
||||
if (flag_reload) {
|
||||
PINELOG_INFO(_("Reloading X52 configuration"));
|
||||
x52d_config_load(conf_file);
|
||||
x52d_config_apply();
|
||||
flag_reload = false;
|
||||
}
|
||||
|
||||
if (flag_save_cfg) {
|
||||
PINELOG_INFO(_("Saving X52 configuration to disk"));
|
||||
x52d_config_save(conf_file);
|
||||
flag_save_cfg = false;
|
||||
}
|
||||
}
|
||||
|
||||
PINELOG_INFO(_("Received termination signal %s"), strsignal(flag_quit));
|
||||
|
||||
cleanup:
|
||||
// Stop device threads
|
||||
x52d_clock_exit();
|
||||
x52d_dev_exit();
|
||||
x52d_command_exit();
|
||||
x52d_notify_exit();
|
||||
#if defined(HAVE_EVDEV)
|
||||
x52d_mouse_evdev_exit();
|
||||
x52d_io_exit();
|
||||
#endif
|
||||
|
||||
// Remove the PID file
|
||||
PINELOG_TRACE("Removing PID file %s", pid_file);
|
||||
unlink(pid_file);
|
||||
|
||||
PINELOG_INFO(_("Shutting down X52 daemon"));
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Saitek X52 Pro MFD & LED driver - Mouse driver
|
||||
*
|
||||
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define PINELOG_MODULE X52D_MOD_MOUSE
|
||||
#include "pinelog.h"
|
||||
#include "x52d_config.h"
|
||||
#include "x52d_const.h"
|
||||
#include "x52d_mouse.h"
|
||||
|
||||
// Mouse speed is the delay in microseconds between subsequent mouse reports
|
||||
#define DEFAULT_MOUSE_DELAY 70000
|
||||
#define MOUSE_DELAY_DELTA 5000
|
||||
#define MOUSE_DELAY_MIN 10000
|
||||
#define MAX_MOUSE_MULT 5
|
||||
|
||||
volatile int mouse_delay = DEFAULT_MOUSE_DELAY;
|
||||
volatile int mouse_mult = MOUSE_MULT_FACTOR;
|
||||
volatile int mouse_scroll_dir = 1;
|
||||
|
||||
void x52d_cfg_set_Mouse_Enabled(bool enabled)
|
||||
{
|
||||
PINELOG_DEBUG(_("Setting mouse enable to %s"),
|
||||
enabled ? _("on") : _("off"));
|
||||
#if defined HAVE_EVDEV
|
||||
x52d_mouse_evdev_thread_control(enabled);
|
||||
#endif
|
||||
}
|
||||
|
||||
void x52d_cfg_set_Mouse_Speed(int speed)
|
||||
{
|
||||
int new_delay;
|
||||
int new_mult;
|
||||
|
||||
int max_base_speed = (DEFAULT_MOUSE_DELAY - MOUSE_DELAY_MIN) / MOUSE_DELAY_DELTA;
|
||||
int max_speed = max_base_speed + MAX_MOUSE_MULT * MOUSE_MULT_FACTOR;
|
||||
|
||||
if (speed < 0 || speed > max_speed) {
|
||||
PINELOG_INFO(_("Ignoring mouse speed %d outside supported range (0-%d)"),
|
||||
speed, max_speed);
|
||||
return;
|
||||
} else if (speed <= max_base_speed) {
|
||||
new_delay = DEFAULT_MOUSE_DELAY - speed * MOUSE_DELAY_DELTA;
|
||||
new_mult = MOUSE_MULT_FACTOR;
|
||||
} else {
|
||||
// speed between max_base_speed & max_speed
|
||||
new_delay = MOUSE_DELAY_MIN;
|
||||
new_mult = MOUSE_MULT_FACTOR + (speed - max_base_speed);
|
||||
}
|
||||
|
||||
PINELOG_DEBUG(_("Setting mouse speed to %d (delay %d ms, multiplier %f)"),
|
||||
speed, new_delay / 1000, new_mult / (double)MOUSE_MULT_FACTOR);
|
||||
mouse_delay = new_delay;
|
||||
mouse_mult = new_mult;
|
||||
}
|
||||
|
||||
void x52d_cfg_set_Mouse_ReverseScroll(bool enabled)
|
||||
{
|
||||
PINELOG_DEBUG(_("Setting mouse reverse scroll to %s"),
|
||||
enabled ? _("on") : _("off"));
|
||||
|
||||
if (enabled) {
|
||||
mouse_scroll_dir = -1;
|
||||
} else {
|
||||
mouse_scroll_dir = 1;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Saitek X52 Pro MFD & LED driver - Mouse driver
|
||||
*
|
||||
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#ifndef X52D_MOUSE_H
|
||||
#define X52D_MOUSE_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "libx52io.h"
|
||||
|
||||
extern volatile int mouse_delay;
|
||||
extern volatile int mouse_mult;
|
||||
extern volatile int mouse_scroll_dir;
|
||||
|
||||
#define MOUSE_MULT_FACTOR 4
|
||||
|
||||
void x52d_mouse_evdev_thread_control(bool enabled);
|
||||
void x52d_mouse_evdev_init(void);
|
||||
void x52d_mouse_evdev_exit(void);
|
||||
void x52d_mouse_report_event(libx52io_report *report);
|
||||
|
||||
#endif // !defined X52D_MOUSE_H
|
|
@ -0,0 +1,248 @@
|
|||
/*
|
||||
* Saitek X52 Pro MFD & LED driver - Mouse driver
|
||||
*
|
||||
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "libevdev/libevdev.h"
|
||||
#include "libevdev/libevdev-uinput.h"
|
||||
#include "libx52io.h"
|
||||
|
||||
#include "pinelog.h"
|
||||
#include "x52d_config.h"
|
||||
#include "x52d_const.h"
|
||||
#include "x52d_mouse.h"
|
||||
|
||||
static pthread_t mouse_thr;
|
||||
static bool mouse_thr_enabled = false;
|
||||
|
||||
static struct libevdev_uinput *mouse_uidev;
|
||||
static bool mouse_uidev_created = false;
|
||||
|
||||
static volatile libx52io_report old_report;
|
||||
static volatile libx52io_report new_report;
|
||||
|
||||
static int report_button_change(int button, int index)
|
||||
{
|
||||
int rc = 1;
|
||||
bool old_button = old_report.button[index];
|
||||
bool new_button = new_report.button[index];
|
||||
|
||||
if (old_button != new_button) {
|
||||
rc = libevdev_uinput_write_event(mouse_uidev, EV_KEY, button,
|
||||
(int)new_button);
|
||||
if (rc != 0) {
|
||||
PINELOG_ERROR(_("Error writing mouse button event (button %d, state %d)"),
|
||||
button, (int)new_button);
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int report_wheel(void)
|
||||
{
|
||||
int rc = 1;
|
||||
int wheel = 0;
|
||||
bool scroll_up = new_report.button[LIBX52IO_BTN_MOUSE_SCROLL_UP];
|
||||
bool scroll_dn = new_report.button[LIBX52IO_BTN_MOUSE_SCROLL_DN];
|
||||
|
||||
if (scroll_up) {
|
||||
// Scroll up event
|
||||
wheel = 1 * mouse_scroll_dir;
|
||||
} else if (scroll_dn) {
|
||||
// Scroll down event
|
||||
wheel = -1 * mouse_scroll_dir;
|
||||
}
|
||||
|
||||
if (wheel != 0) {
|
||||
rc = libevdev_uinput_write_event(mouse_uidev, EV_REL, REL_WHEEL, wheel);
|
||||
if (rc != 0) {
|
||||
PINELOG_ERROR(_("Error writing mouse wheel event %d"), wheel);
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int report_axis(int axis, int index)
|
||||
{
|
||||
int rc = 1;
|
||||
|
||||
int axis_val = new_report.axis[index];
|
||||
|
||||
/*
|
||||
* Axis value ranges from 0 to 15, with the default midpoint at 8.
|
||||
* We need to translate this to a range of -7 to +7. Since the midpoint
|
||||
* is slightly off-center, we will shift the values left, and subtract
|
||||
* 15, effectively, giving us a range of -15 to +15. Shifting right again
|
||||
* will reduce the range to -7 to +7, and effectively ignore the reported
|
||||
* values of 7 and 8.
|
||||
*/
|
||||
axis_val = ((axis_val << 1) - 15) >> 1;
|
||||
|
||||
/*
|
||||
* Factor in the multiplicative factor for the axis. This deliberately
|
||||
* uses integer division, since the uinput event only accepts integers.
|
||||
* For the speed purposes, this should be good enough.
|
||||
*/
|
||||
axis_val = (axis_val * mouse_mult) / MOUSE_MULT_FACTOR;
|
||||
|
||||
if (axis_val) {
|
||||
rc = libevdev_uinput_write_event(mouse_uidev, EV_REL, axis, axis_val);
|
||||
if (rc != 0) {
|
||||
PINELOG_ERROR(_("Error writing mouse axis event (axis %d, value %d)"),
|
||||
axis, axis_val);
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void report_sync(void)
|
||||
{
|
||||
int rc;
|
||||
rc = libevdev_uinput_write_event(mouse_uidev, EV_SYN, SYN_REPORT, 0);
|
||||
if (rc != 0) {
|
||||
PINELOG_ERROR(_("Error writing mouse sync event"));
|
||||
} else {
|
||||
memcpy((void *)&old_report, (void *)&new_report, sizeof(old_report));
|
||||
}
|
||||
}
|
||||
|
||||
static void reset_reports(void)
|
||||
{
|
||||
memset((void *)&old_report, 0, sizeof(old_report));
|
||||
/* Set the default thumbstick values to the mid-point */
|
||||
old_report.axis[LIBX52IO_AXIS_THUMBX] = 8;
|
||||
old_report.axis[LIBX52IO_AXIS_THUMBY] = 8;
|
||||
memcpy((void *)&new_report, (void *)&old_report, sizeof(new_report));
|
||||
}
|
||||
|
||||
static void * x52_mouse_thr(void *param)
|
||||
{
|
||||
bool state_changed;
|
||||
|
||||
PINELOG_INFO(_("Starting X52 virtual mouse driver thread"));
|
||||
for (;;) {
|
||||
state_changed = false;
|
||||
state_changed |= (0 == report_axis(REL_X, LIBX52IO_AXIS_THUMBX));
|
||||
state_changed |= (0 == report_axis(REL_Y, LIBX52IO_AXIS_THUMBY));
|
||||
|
||||
if (state_changed) {
|
||||
report_sync();
|
||||
}
|
||||
|
||||
usleep(mouse_delay);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void x52d_mouse_thr_init(void)
|
||||
{
|
||||
int rc;
|
||||
|
||||
PINELOG_TRACE("Initializing virtual mouse driver");
|
||||
rc = pthread_create(&mouse_thr, NULL, x52_mouse_thr, NULL);
|
||||
if (rc != 0) {
|
||||
PINELOG_FATAL(_("Error %d initializing mouse thread: %s"),
|
||||
rc, strerror(rc));
|
||||
}
|
||||
}
|
||||
|
||||
static void x52d_mouse_thr_exit(void)
|
||||
{
|
||||
PINELOG_INFO(_("Shutting down X52 virtual mouse driver thread"));
|
||||
pthread_cancel(mouse_thr);
|
||||
}
|
||||
|
||||
void x52d_mouse_evdev_thread_control(bool enabled)
|
||||
{
|
||||
if (!mouse_uidev_created) {
|
||||
PINELOG_INFO(_("Virtual mouse not created. Ignoring thread state change"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (enabled) {
|
||||
if (mouse_thr_enabled) {
|
||||
PINELOG_TRACE("Ignoring re-enable mouse thread");
|
||||
return;
|
||||
} else {
|
||||
reset_reports();
|
||||
x52d_mouse_thr_init();
|
||||
}
|
||||
} else {
|
||||
if (!mouse_thr_enabled) {
|
||||
PINELOG_TRACE("Ignoring re-disable mouse thread");
|
||||
return;
|
||||
} else {
|
||||
x52d_mouse_thr_exit();
|
||||
}
|
||||
}
|
||||
mouse_thr_enabled = enabled;
|
||||
}
|
||||
|
||||
void x52d_mouse_report_event(libx52io_report *report)
|
||||
{
|
||||
bool state_changed;
|
||||
if (report) {
|
||||
memcpy((void *)&new_report, report, sizeof(new_report));
|
||||
|
||||
if (!mouse_uidev_created || !mouse_thr_enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
state_changed = false;
|
||||
state_changed |= (0 == report_button_change(BTN_LEFT, LIBX52IO_BTN_MOUSE_PRIMARY));
|
||||
state_changed |= (0 == report_button_change(BTN_RIGHT, LIBX52IO_BTN_MOUSE_SECONDARY));
|
||||
state_changed |= (0 == report_wheel());
|
||||
|
||||
if (state_changed) {
|
||||
report_sync();
|
||||
}
|
||||
} else {
|
||||
reset_reports();
|
||||
}
|
||||
}
|
||||
|
||||
void x52d_mouse_evdev_init(void)
|
||||
{
|
||||
int rc;
|
||||
struct libevdev *dev;
|
||||
|
||||
/* Create a new mouse device */
|
||||
dev = libevdev_new();
|
||||
libevdev_set_name(dev, "X52 virtual mouse");
|
||||
libevdev_enable_event_type(dev, EV_REL);
|
||||
libevdev_enable_event_code(dev, EV_REL, REL_X, NULL);
|
||||
libevdev_enable_event_code(dev, EV_REL, REL_Y, NULL);
|
||||
libevdev_enable_event_code(dev, EV_REL, REL_WHEEL, NULL);
|
||||
libevdev_enable_event_type(dev, EV_KEY);
|
||||
libevdev_enable_event_code(dev, EV_KEY, BTN_LEFT, NULL);
|
||||
libevdev_enable_event_code(dev, EV_KEY, BTN_RIGHT, NULL);
|
||||
|
||||
rc = libevdev_uinput_create_from_device(dev, LIBEVDEV_UINPUT_OPEN_MANAGED,
|
||||
&mouse_uidev);
|
||||
if (rc != 0) {
|
||||
PINELOG_ERROR(_("Error %d creating X52 virtual mouse: %s"),
|
||||
-rc, strerror(-rc));
|
||||
} else {
|
||||
mouse_uidev_created = true;
|
||||
}
|
||||
}
|
||||
|
||||
void x52d_mouse_evdev_exit(void)
|
||||
{
|
||||
x52d_mouse_evdev_thread_control(false);
|
||||
mouse_uidev_created = false;
|
||||
libevdev_uinput_destroy(mouse_uidev);
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
* Saitek X52 Pro MFD & LED driver - Mouse driver test harness
|
||||
*
|
||||
* Copyright (C) 2022 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <setjmp.h>
|
||||
#include <cmocka.h>
|
||||
|
||||
#define PINELOG_MODULE X52D_MOD_MOUSE
|
||||
#include "pinelog.h"
|
||||
#include "x52d_config.h"
|
||||
#include "x52d_const.h"
|
||||
#include "x52d_mouse.h"
|
||||
|
||||
#if defined HAVE_EVDEV
|
||||
/* Stub for evdev */
|
||||
void x52d_mouse_evdev_thread_control(bool enabled)
|
||||
{
|
||||
function_called();
|
||||
check_expected(enabled);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void test_mouse_thread_enabled(void **state)
|
||||
{
|
||||
#if defined HAVE_EVDEV
|
||||
expect_function_calls(x52d_mouse_evdev_thread_control, 1);
|
||||
expect_value(x52d_mouse_evdev_thread_control, enabled, true);
|
||||
#endif
|
||||
|
||||
x52d_cfg_set_Mouse_Enabled(true);
|
||||
}
|
||||
|
||||
static void test_mouse_thread_disabled(void **state)
|
||||
{
|
||||
#if defined HAVE_EVDEV
|
||||
expect_function_calls(x52d_mouse_evdev_thread_control, 1);
|
||||
expect_value(x52d_mouse_evdev_thread_control, enabled, false);
|
||||
#endif
|
||||
|
||||
x52d_cfg_set_Mouse_Enabled(false);
|
||||
}
|
||||
|
||||
static void test_mouse_speed_negative(void **state)
|
||||
{
|
||||
int orig_mouse_delay = mouse_delay;
|
||||
int orig_mouse_mult = mouse_mult;
|
||||
|
||||
x52d_cfg_set_Mouse_Speed(-1);
|
||||
assert_int_equal(mouse_delay, orig_mouse_delay);
|
||||
assert_int_equal(mouse_mult, orig_mouse_mult);
|
||||
}
|
||||
|
||||
/* The following tests are dependent on the values in x52d_mouse.c */
|
||||
static void test_mouse_speed_0(void **state)
|
||||
{
|
||||
x52d_cfg_set_Mouse_Speed(0);
|
||||
assert_int_equal(mouse_delay, 70000);
|
||||
assert_int_equal(mouse_mult, 4);
|
||||
}
|
||||
|
||||
static void test_mouse_speed_mid_base(void **state)
|
||||
{
|
||||
x52d_cfg_set_Mouse_Speed(6);
|
||||
assert_int_equal(mouse_delay, 40000);
|
||||
assert_int_equal(mouse_mult, 4);
|
||||
}
|
||||
|
||||
static void test_mouse_speed_max_base(void **state)
|
||||
{
|
||||
x52d_cfg_set_Mouse_Speed(12);
|
||||
assert_int_equal(mouse_delay, 10000);
|
||||
assert_int_equal(mouse_mult, 4);
|
||||
}
|
||||
|
||||
static void test_mouse_speed_min_hyper(void **state)
|
||||
{
|
||||
x52d_cfg_set_Mouse_Speed(13);
|
||||
assert_int_equal(mouse_delay, 10000);
|
||||
assert_int_equal(mouse_mult, 5);
|
||||
}
|
||||
|
||||
static void test_mouse_speed_mid_hyper(void **state)
|
||||
{
|
||||
x52d_cfg_set_Mouse_Speed(22);
|
||||
assert_int_equal(mouse_delay, 10000);
|
||||
assert_int_equal(mouse_mult, 14);
|
||||
}
|
||||
|
||||
static void test_mouse_speed_max_hyper(void **state)
|
||||
{
|
||||
x52d_cfg_set_Mouse_Speed(32);
|
||||
assert_int_equal(mouse_delay, 10000);
|
||||
assert_int_equal(mouse_mult, 24);
|
||||
}
|
||||
|
||||
static void test_mouse_speed_above_max(void **state)
|
||||
{
|
||||
int orig_mouse_delay = mouse_delay;
|
||||
int orig_mouse_mult = mouse_mult;
|
||||
|
||||
x52d_cfg_set_Mouse_Speed(33);
|
||||
assert_int_equal(mouse_delay, orig_mouse_delay);
|
||||
assert_int_equal(mouse_mult, orig_mouse_mult);
|
||||
}
|
||||
|
||||
static void test_mouse_reverse_scroll_enabled(void **state)
|
||||
{
|
||||
x52d_cfg_set_Mouse_ReverseScroll(true);
|
||||
assert_int_equal(mouse_scroll_dir, -1);
|
||||
}
|
||||
|
||||
static void test_mouse_reverse_scroll_disabled(void **state)
|
||||
{
|
||||
x52d_cfg_set_Mouse_ReverseScroll(false);
|
||||
assert_int_equal(mouse_scroll_dir, 1);
|
||||
}
|
||||
|
||||
const struct CMUnitTest tests[] = {
|
||||
cmocka_unit_test(test_mouse_thread_enabled),
|
||||
cmocka_unit_test(test_mouse_thread_disabled),
|
||||
cmocka_unit_test(test_mouse_speed_negative),
|
||||
cmocka_unit_test(test_mouse_speed_0),
|
||||
cmocka_unit_test(test_mouse_speed_mid_base),
|
||||
cmocka_unit_test(test_mouse_speed_max_base),
|
||||
cmocka_unit_test(test_mouse_speed_min_hyper),
|
||||
cmocka_unit_test(test_mouse_speed_mid_hyper),
|
||||
cmocka_unit_test(test_mouse_speed_max_hyper),
|
||||
cmocka_unit_test(test_mouse_speed_above_max),
|
||||
cmocka_unit_test(test_mouse_reverse_scroll_enabled),
|
||||
cmocka_unit_test(test_mouse_reverse_scroll_disabled),
|
||||
};
|
||||
|
||||
int main(void)
|
||||
{
|
||||
cmocka_set_message_output(CM_OUTPUT_TAP);
|
||||
cmocka_run_group_tests(tests, NULL, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,213 @@
|
|||
/*
|
||||
* Saitek X52 Pro MFD & LED driver - Notification manager
|
||||
*
|
||||
* Copyright (C) 2022 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#define PINELOG_MODULE X52D_MOD_NOTIFY
|
||||
#include "pinelog.h"
|
||||
#include "x52d_const.h"
|
||||
#include "x52d_notify.h"
|
||||
#include "x52d_client.h"
|
||||
#include "x52dcomm.h"
|
||||
#include "x52dcomm-internal.h"
|
||||
|
||||
static pthread_t notify_thr;
|
||||
static pthread_t notify_listen;
|
||||
static pthread_mutex_t notify_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
static int notify_pipe[2];
|
||||
static int notify_sock;
|
||||
|
||||
static int client_fd[X52D_MAX_CLIENTS];
|
||||
|
||||
/* Bind and listen to the notify socket */
|
||||
static int listen_notify(const char *notify_sock_path)
|
||||
{
|
||||
int sock_fd;
|
||||
int len;
|
||||
struct sockaddr_un local;
|
||||
|
||||
len = x52d_setup_notify_sock(notify_sock_path, &local);
|
||||
if (len < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
sock_fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (sock_fd < 0) {
|
||||
/* Failure creating the socket. Abort early */
|
||||
PINELOG_ERROR(_("Error creating notification socket: %s"), strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (x52d_set_socket_nonblocking(sock_fd) < 0) {
|
||||
PINELOG_ERROR(_("Error marking notification socket as nonblocking: %s"), strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (x52d_listen_socket(&local, len, sock_fd) < 0) {
|
||||
PINELOG_ERROR(_("Error listening on notification socket: %s"), strerror(errno));
|
||||
goto listen_failure;
|
||||
}
|
||||
|
||||
return sock_fd;
|
||||
|
||||
listen_failure:
|
||||
unlink(local.sun_path);
|
||||
close(sock_fd);
|
||||
PINELOG_FATAL(_("Error setting up notification socket"));
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void * x52_notify_thr(void * param)
|
||||
{
|
||||
char buffer[X52D_BUFSZ];
|
||||
uint16_t bufsiz;
|
||||
int rc;
|
||||
|
||||
for (;;) {
|
||||
read_pipe_size:
|
||||
rc = read(notify_pipe[0], &bufsiz, sizeof(bufsiz));
|
||||
if (rc < 0) {
|
||||
if (errno == EINTR) {
|
||||
goto read_pipe_size;
|
||||
} else {
|
||||
PINELOG_ERROR(_("Error %d reading from pipe: %s"),
|
||||
errno, strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
if (rc < 0) {
|
||||
// Error condition, try again
|
||||
continue;
|
||||
}
|
||||
|
||||
read_pipe_data:
|
||||
rc = read(notify_pipe[0], buffer, bufsiz);
|
||||
if (rc < 0) {
|
||||
if (errno == EINTR) {
|
||||
goto read_pipe_data;
|
||||
} else {
|
||||
PINELOG_ERROR(_("Error %d reading from pipe: %s"),
|
||||
errno, strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
if (rc < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int i = 0; i < X52D_MAX_CLIENTS; i++) {
|
||||
// Broadcast to every connected client
|
||||
if (client_fd[i] != INVALID_CLIENT) {
|
||||
write_client_notification:
|
||||
rc = write(client_fd[i], buffer, bufsiz);
|
||||
if (rc < 0 && errno == EINTR) {
|
||||
goto write_client_notification;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void x52d_notify_send(int argc, const char **argv)
|
||||
{
|
||||
char buffer[X52D_BUFSZ + sizeof(uint16_t)];
|
||||
uint16_t bufsiz;
|
||||
uint16_t written;
|
||||
int rc;
|
||||
|
||||
bufsiz = (uint16_t)x52d_format_command(argc, argv, buffer + sizeof(uint16_t), X52D_BUFSZ);
|
||||
memcpy(buffer, &bufsiz, sizeof(bufsiz));
|
||||
|
||||
pthread_mutex_lock(¬ify_mutex);
|
||||
written = 0;
|
||||
while (written < bufsiz) {
|
||||
rc = write(notify_pipe[1], buffer + written, bufsiz - written);
|
||||
if (rc < 0) {
|
||||
if (errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
PINELOG_ERROR(_("Error %d writing notification pipe: %s"),
|
||||
errno, strerror(errno));
|
||||
} else {
|
||||
written += rc;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(¬ify_mutex);
|
||||
}
|
||||
|
||||
static void client_handler(int fd)
|
||||
{
|
||||
char buffer[X52D_BUFSZ] = { 0 };
|
||||
int rc;
|
||||
|
||||
rc = recv(fd, buffer, sizeof(buffer), 0);
|
||||
PINELOG_TRACE("Received and discarded %d bytes from notification client %d", rc, fd);
|
||||
}
|
||||
|
||||
static void * x52_notify_loop(void * param)
|
||||
{
|
||||
struct pollfd pfd[MAX_CONN];
|
||||
int rc;
|
||||
|
||||
for (;;) {
|
||||
rc = x52d_client_poll(client_fd, pfd, notify_sock);
|
||||
if (rc <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
x52d_client_handle(client_fd, pfd, notify_sock, client_handler);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void x52d_notify_init(const char *notify_sock_path)
|
||||
{
|
||||
int rc;
|
||||
|
||||
PINELOG_TRACE("Initializing notification manager");
|
||||
x52d_client_init(client_fd);
|
||||
|
||||
PINELOG_TRACE("Creating notifications pipe");
|
||||
rc = pipe(notify_pipe);
|
||||
if (rc != 0) {
|
||||
PINELOG_FATAL(_("Error %d creating notification pipe: %s"),
|
||||
errno, strerror(errno));
|
||||
}
|
||||
|
||||
PINELOG_TRACE("Opening notification listener socket");
|
||||
notify_sock = listen_notify(notify_sock_path);
|
||||
|
||||
rc = pthread_create(¬ify_thr, NULL, x52_notify_thr, NULL);
|
||||
if (rc != 0) {
|
||||
PINELOG_FATAL(_("Error %d initializing notify thread: %s"),
|
||||
rc, strerror(rc));
|
||||
}
|
||||
|
||||
rc = pthread_create(¬ify_listen, NULL, x52_notify_loop, NULL);
|
||||
if (rc != 0) {
|
||||
PINELOG_FATAL(_("Error %d initializing notify listener: %s"),
|
||||
rc, strerror(rc));
|
||||
}
|
||||
}
|
||||
|
||||
void x52d_notify_exit(void)
|
||||
{
|
||||
close(notify_pipe[0]);
|
||||
close(notify_pipe[1]);
|
||||
close(notify_sock);
|
||||
|
||||
pthread_cancel(notify_thr);
|
||||
pthread_cancel(notify_listen);
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Saitek X52 Pro MFD & LED driver - Notification manager
|
||||
*
|
||||
* Copyright (C) 2022 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#ifndef X52D_NOTIFY_H
|
||||
#define X52D_NOTIFY_H
|
||||
|
||||
void x52d_notify_init(const char *notify_sock_path);
|
||||
void x52d_notify_exit(void);
|
||||
void x52d_notify_send(int argc, const char **argv);
|
||||
|
||||
#define X52D_NOTIFY(...) do { \
|
||||
const char *argv ## __LINE__ [] = {__VA_ARGS__}; \
|
||||
x52d_notify_send(sizeof(argv ## __LINE__ )/sizeof(argv ## __LINE__ [0]), argv ## __LINE__ ); \
|
||||
} while(0)
|
||||
|
||||
#endif // !defined X52D_NOTIFY_H
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Saitek X52 Pro MFD & LED driver - communication library interal functions
|
||||
*
|
||||
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#ifndef X52DCOMM_INTERNAL_H
|
||||
#define X52DCOMM_INTERNAL_H
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
|
||||
#define X52D_BUFSZ 1024
|
||||
|
||||
const char *x52d_command_sock_path(const char *sock_path);
|
||||
int x52d_setup_command_sock(const char *sock_path, struct sockaddr_un *remote);
|
||||
const char *x52d_notify_sock_path(const char *sock_path);
|
||||
int x52d_setup_notify_sock(const char *sock_path, struct sockaddr_un *remote);
|
||||
int x52d_set_socket_nonblocking(int sock_fd);
|
||||
int x52d_listen_socket(struct sockaddr_un *local, int len, int sock_fd);
|
||||
void x52d_split_args(int *argc, char **argv, char *buffer, int buflen);
|
||||
|
||||
#endif // !defined X52DCOMM_INTERNAL_H
|
|
@ -0,0 +1,158 @@
|
|||
/*
|
||||
* Saitek X52 Pro MFD & LED driver
|
||||
*
|
||||
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file x52dcomm.h
|
||||
* @brief Functions, structures and enumerations for the Saitek X52 MFD & LED
|
||||
* daemon communication library.
|
||||
*
|
||||
* This file contains the type, enum and function prototypes for the Saitek X52
|
||||
* daemon communication library. These functions allow a client application to
|
||||
* communicate with a running X52 daemon, execute commands and retrieve data.
|
||||
*
|
||||
* @author Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*/
|
||||
#ifndef X52DCOMM_H
|
||||
#define X52DCOMM_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @defgroup x52dcomm Daemon communication
|
||||
*
|
||||
* These functions are used to communicate with the X52 daemon.
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Open a connection to the daemon command socket.
|
||||
*
|
||||
* This method opens a socket connection to the daemon command socket. This
|
||||
* socket allows the client to issue commands and retrieve data. The \p sock_path
|
||||
* parameter may be NULL, in which case, it will use the default socket path.
|
||||
*
|
||||
* The client will need to use the returned descriptor to communicate with the
|
||||
* daemon using \ref x52d_send_command. Once finished, the client may use the
|
||||
* \c close(2) method to close the file descriptor.
|
||||
*
|
||||
* @param[in] sock_path Path to the daemon command socket.
|
||||
*
|
||||
* @returns Non-negative socket file descriptor on success.
|
||||
* @returns -1 on failure, and set \c errno accordingly.
|
||||
*
|
||||
* @exception E2BIG returned if the passed socket path is too big
|
||||
*/
|
||||
int x52d_dial_command(const char *sock_path);
|
||||
|
||||
/**
|
||||
* @brief Open a connection to the daemon notify socket.
|
||||
*
|
||||
* This method opens a socket connection to the daemon notify socket. This
|
||||
* socket allows the client to receive notifications from the daemon. Thej
|
||||
* \p sock_path parameter may be NULL, in which case, it will use the default
|
||||
* socket path.
|
||||
*
|
||||
* The client will need to use the returned descriptor to communicate with the
|
||||
* daemon using \ref x52d_recv_notification. Once finished, the client may use
|
||||
* the \c close(2) method to close the file descriptor.
|
||||
*
|
||||
* @param[in] sock_path Path to the daemon command socket.
|
||||
*
|
||||
* @returns Non-negative socket file descriptor on success.
|
||||
* @returns -1 on failure, and set \c errno accordingly.
|
||||
*
|
||||
* @exception E2BIG returned if the passed socket path is too big
|
||||
*/
|
||||
int x52d_dial_notify(const char *sock_path);
|
||||
|
||||
/**
|
||||
* @brief Format a series of command strings into a buffer
|
||||
*
|
||||
* The client sends the command and parameters as a series of NUL terminated
|
||||
* strings. This function concatenates the commands into a single buffer that
|
||||
* can be passed to \ref x52d_send_command.
|
||||
*
|
||||
* \p buffer should be at least 1024 bytes long.
|
||||
*
|
||||
* @param[in] argc Number of arguments to fit in the buffer
|
||||
* @param[in] argv Pointer to an array of arguments.
|
||||
* @param[out] buffer Buffer to store the formatted command
|
||||
* @param[in] buflen Length of the buffer
|
||||
*
|
||||
* @returns number of bytes in the formatted command
|
||||
* @returns -1 on an error condition, and \c errno is set accordingly.
|
||||
*/
|
||||
int x52d_format_command(int argc, const char **argv, char *buffer, size_t buflen);
|
||||
|
||||
/**
|
||||
* @brief Send a command to the daemon and retrieve the response.
|
||||
*
|
||||
* The client sends the command and parameters as a series of NUL terminated
|
||||
* strings, and retrieves the response in the same manner. Depending on the
|
||||
* result, the return status is either a positive integer or -1, and \c errno
|
||||
* is set accordingly.
|
||||
*
|
||||
* \p buffer should contain sufficient space to accomodate the returned
|
||||
* response string.
|
||||
*
|
||||
* This is a blocking function and will not return until either a response is
|
||||
* received from the server, or an exception condition occurs.
|
||||
*
|
||||
* @param[in] sock_fd Socket descriptor returned from
|
||||
* \ref x52d_dial_command
|
||||
*
|
||||
* @param[inout] buffer Pointer to the string containing the command and
|
||||
* parameters. This is also used to save the returned
|
||||
* response.
|
||||
*
|
||||
* @param[in] bufin Length of the command in the input buffer
|
||||
* @param[in] bufout Maximum length of the response
|
||||
*
|
||||
* @returns number of bytes returned from the server
|
||||
* @returns -1 on an error condition, and \c errno is set accordingly.
|
||||
*/
|
||||
int x52d_send_command(int sock_fd, char *buffer, size_t bufin, size_t bufout);
|
||||
|
||||
/**
|
||||
* @brief Notification callback function type
|
||||
*/
|
||||
typedef int (* x52d_notify_callback_fn)(int argc, char **argv);
|
||||
|
||||
/**
|
||||
* @brief Receive a notification from the daemon
|
||||
*
|
||||
* This function blocks until it receives a notification from the daemon. Once
|
||||
* it receives a notification successfully, it will call the callback function
|
||||
* with the arguments as string pointers. It will return the return value of
|
||||
* the callback function, if it was called.
|
||||
*
|
||||
* This is a blocking function and will not return until either a notification
|
||||
* is received from the server, or an exception condition occurs.
|
||||
*
|
||||
* @param[in] sock_fd Socket descriptor returned from
|
||||
* \ref x52d_dial_notify
|
||||
*
|
||||
* @param[in] callback Pointer to the callback function
|
||||
*
|
||||
* @returns return code of the callback function on success
|
||||
* @returns -1 on an error condition, and \c errno is set accordingly.
|
||||
*/
|
||||
int x52d_recv_notification(int sock_fd, x52d_notify_callback_fn callback);
|
||||
|
||||
/** @} */
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif // !defined X52DCOMM_H
|
||||
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
# Automake for documentation
|
||||
#
|
||||
# Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
|
||||
EXTRA_DIST += \
|
||||
docs/main.dox \
|
||||
docs/caveats.dox \
|
||||
docs/integration.dox
|
|
@ -0,0 +1,21 @@
|
|||
/**
|
||||
@page libx52_caveats Caveats
|
||||
|
||||
@section general General Caveats
|
||||
|
||||
libx52 uses [libusb](https://libusb.info) under the hood. Therefore, any
|
||||
[caveats](http://libusb.sourceforge.net/api-1.0/libusb_caveats.html) that apply
|
||||
to libusb also apply to libx52.
|
||||
|
||||
@section threads Thread Safety
|
||||
|
||||
libx52 is not designed to be thread-safe. The application must ensure that
|
||||
calls to libx52 from multiple threads are protected by a semaphore/mutex.
|
||||
|
||||
@section leds LED Support
|
||||
|
||||
The Saitek X52 does not support controlling the color of individual LEDs. This
|
||||
is only supported on the X52 Pro. The application can use \ref
|
||||
libx52_check_feature to verify support.
|
||||
|
||||
*/
|
|
@ -0,0 +1,35 @@
|
|||
LibUSB X52 stub library
|
||||
=======================
|
||||
|
||||
The libusbx52 stub library is a convenience library to help test the libusb
|
||||
functions used by libx52 and associated code. It simulates the behavior of the
|
||||
libusb functions used by the libx52 library, but doesn't actually control any
|
||||
real hardware, and simply updates a few in-memory data structures.
|
||||
|
||||
The idea behind `libusbx52.so` is to use it as an LD_PRELOAD library, where it
|
||||
will override the real functions used by `libx52.so`. The use case for this
|
||||
scenario is in an automated testing environment, where a test runner could set
|
||||
up the list of devices manually and simulate various scenarios.
|
||||
|
||||
# Design Overview
|
||||
|
||||
Unfortunately, the automake infrastructure does not support the use of
|
||||
LD_PRELOAD because it is deemed "non-portable" in the automake sense. As a
|
||||
result, this is now up to a test runner application to implement a method to
|
||||
control the data passed between two processes.
|
||||
|
||||
# Data Structures
|
||||
|
||||
The server process is responsible for setting up the initial set of USB devices.
|
||||
As far as libx52 is concerned, the only fields that it uses in the USB
|
||||
descriptor are the idVendor and idProduct fields. These are written to a file
|
||||
that is read by the libusbx52 stubs to populate the device list.
|
||||
|
||||
Once the file has been written by the server, the libusb_init stub function in
|
||||
the client can read the file and populate the internal data structures as
|
||||
necessary.
|
||||
|
||||
The client can also write to a separate file to record the USB communication
|
||||
sent across to the simulated device.
|
||||
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
Key mappings for X52 Pro
|
||||
========================
|
||||
|
||||
# Key map table
|
||||
|
||||
One simple technique to map the individual keys is as follows:
|
||||
|
||||
| Key | Mode 1 | Mode 2 | Mode 3 | Mode 1 S | Mode 2 S | Mode 3 S |
|
||||
| ----- | -------- | -------- | -------- | -------- | -------- | -------- |
|
||||
| Trigg | L Ctrl | `<- M1` | `<- M1` | `<- M1S` | `<- M2S` | `<- M3S` |
|
||||
| FIRE | SPACE | `<- M1` | `<- M1` | `<- M1S` | `<- M2S` | `<- M3S` |
|
||||
| ... | | | | | | |
|
||||
| Pinky | -SHIFT- | -SHIFT- | -SHIFT- | -SHIFT- | -SHIFT- | -SHIFT- |
|
||||
| D | Tab | `<- M1` | `<- M1` | `<- M1S` | `<- M2S` | `<- M3S` |
|
||||
| E | Joy E | `<- M1` | `<- M1` | `<- M1S` | `<- M2S` | `<- M3S` |
|
||||
|
||||
The `<- M*` mappings indicate that the key map in the corresponding mode is the
|
||||
same as in the linked mode. By default, the keys in modes 2 and 3 are linked
|
||||
with the keymap in mode 1, and the keys in the +shift modes are linked with the
|
||||
keymap in their primary mode, i.e., mode 1 + shift keymaps are linked with mode
|
||||
1 keymaps, mode 2 + shift keymaps with mode 2, and mode 3 + shift keymaps with
|
||||
mode 3.
|
||||
|
||||
Also, it is not necessary to have a specific key bound to each button, instead
|
||||
it can behave as the same button being pressed, and the application can take
|
||||
action accordingly.
|
||||
|
||||
# Shift Keys
|
||||
|
||||
The key mapper software for Windows allows the user to map any button to a
|
||||
keystroke or a set of keystrokes. The keystroke sent is dependent on the mode
|
||||
and if a shift key is pressed. Although the mapper does allow creation of
|
||||
several shift keys, this goes to a point of diminishing returns.
|
||||
|
||||
The application design therefore works on the principle of allowing a single
|
||||
shift key, mapped to the Pinky switch on the stick. The user can choose to
|
||||
disable the shift mode, reverting to only the mode selector to select the key
|
||||
maps.
|
||||
|
||||
One additional possibility is to have the shift button latched, i.e., the first
|
||||
press and release turns on the shift mode, and the next press and release turns
|
||||
it off. Compare this with the unlatched case where the user must press and hold
|
||||
the shift button to trigger the shift mode. By default, the shift key is
|
||||
unlatched, although a user can enable it on a per-profile basis.
|
||||
|
||||
Finally, when the shift mode is entered, this must be indicated by sending the
|
||||
control message to turn on the SHIFT indicator in the MFD. Similarly, when the
|
||||
shift mode is exited, the application must turn off the SHIFT indicator.
|
||||
|
||||
# Clutch button
|
||||
|
||||
The clutch feature is an optional enhancement to allow switching of profiles on
|
||||
the fly, without having to exit the game the user is in. This button (marked
|
||||
`i`) reports as a regular button, but the Windows driver allows the user to
|
||||
select it as a clutch. The clutch can also be latched.
|
||||
|
||||
When the clutch button is enabled, entering clutch mode should be signaled by
|
||||
blinking the clutch and POV hat LEDs. There is a specific message to enable
|
||||
and disable the blinking. In clutch mode, the hat can be used to select
|
||||
different profiles displayed on the MFD screen by going in the north and south
|
||||
directions. The current profile can be cleared by moving the hat west, and the
|
||||
selected profile can be enabled by moving the hat east.
|
||||
|
||||
When in clutch mode, the other buttons do not send their mappings to the
|
||||
applicaton, instead, they are captured by the driver and the driver application
|
||||
will display the current mapping on the MFD. The mode selector however, will
|
||||
work as expected, by changing the display to highlight the current mode. The
|
||||
application will also determine the current mapping by examining the mode.
|
||||
|
||||
Clutch mode can only be exited by means of the clutch button. If the clutch
|
||||
button is disabled, then the clutch button behaves as any normal button and can
|
||||
be mapped to a keypress.
|
|
@ -0,0 +1,64 @@
|
|||
X52 MFD pages
|
||||
=============
|
||||
|
||||
The Saitek SDK for Windows allows the programmer to create multiple
|
||||
"pages" on the MFD display which can be toggled using the PgUp and PgDn
|
||||
controls below the MFD. In addition to the pages, the software also
|
||||
allows the programmer to write more than 16 characters to each line and
|
||||
will automatically scroll those lines.
|
||||
|
||||
# Features
|
||||
|
||||
* Create multiple pages, each page containing up to 3 lines of text and
|
||||
each line containing upto 256 characters.
|
||||
* Automatically shift the text by 1 character every 250 ms, iff the text
|
||||
exceeds 16 characters.
|
||||
* Support callbacks on a per-page basis for the Up, Down and Select
|
||||
buttons, to allow the programmer to create custom handlers.
|
||||
* Support a summary page listing all the active pages.
|
||||
|
||||
# API
|
||||
|
||||
```c
|
||||
x52_mfd_create_page(uint8_t *page_id,
|
||||
char *description,
|
||||
x52_page_callback *handler)
|
||||
|
||||
x52_mfd_write_line(uint8_t page_id,
|
||||
uint8_t line,
|
||||
char *text,
|
||||
uint8_t length)
|
||||
|
||||
x52_mfd_update_page(uint8_t page_id,
|
||||
uint8_t activate)
|
||||
|
||||
x52_mfd_delete_page(uint8_t page_id)
|
||||
|
||||
typedef void (*x52_page_callback)(uint8_t page_id,
|
||||
uint8_t event,
|
||||
uint8_t state)
|
||||
```
|
||||
|
||||
## Steps
|
||||
|
||||
1. Create a page
|
||||
2. Write the individual lines of text to the page
|
||||
3. Update the page with activate parameter set to 1
|
||||
4. (Optional) Deactivate the page with activate parameter set to 0
|
||||
5. Delete the page
|
||||
|
||||
## Callback
|
||||
|
||||
The callback API is a function that takes three arguments as follows:
|
||||
|
||||
1. A page ID (8 bits) which identifies the current page. This allows
|
||||
a single callback handler to handle multiple pages.
|
||||
2. An 8-bit event identifier. Currently, this is defined to be one of:
|
||||
* `X52_MFD_EVENT_UP`
|
||||
* `X52_MFD_EVENT_DN`
|
||||
* `X52_MFD_EVENT_SEL`
|
||||
3. An 8-bit state identifier. Currently, this is defined to be one of:
|
||||
* `X52_MFD_BUTTON_STATE_UP`
|
||||
* `X52_MFD_BUTTON_STATE_DN`
|
||||
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
/**
|
||||
@page libx52_integration Integration
|
||||
|
||||
libx52 performs all its operations with a device context pointer. The
|
||||
application must call \ref libx52_init before performing any operation, and
|
||||
must call \ref libx52_exit prior to terminating.
|
||||
|
||||
Also, the application must call \ref libx52_connect to ensure that it connects
|
||||
to a supported joystick. This function must be called after \ref libx52_init
|
||||
|
||||
<b>Example Initialization/Deinitialization Code</b>
|
||||
|
||||
@code{.c}
|
||||
#include <libx52.h>
|
||||
|
||||
libx52_device* init_libx52(void)
|
||||
{
|
||||
libx52_device *dev;
|
||||
int rc;
|
||||
|
||||
// Initialize the libx52 library
|
||||
rc = libx52_init(&dev);
|
||||
if (rc != LIBX52_SUCCESS) {
|
||||
fputs(libx52_strerror(rc), stderr);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Connect to the supported joystick
|
||||
rc = libx52_connect(dev);
|
||||
if (rc != LIBX52_SUCCESS) {
|
||||
fputs(libx52_strerror(rc), stderr);
|
||||
// A failure usually just means that there is no joystick connected
|
||||
// Look at the return codes for more information.
|
||||
}
|
||||
|
||||
return dev;
|
||||
}
|
||||
|
||||
// Close the library and any associated devices
|
||||
libx52_exit(dev);
|
||||
@endcode
|
||||
|
||||
# Joystick Updates
|
||||
|
||||
Most libx52 functions to update the joystick state do not directly write to the
|
||||
connected joystick, but only update internal data structures within the device
|
||||
context. In order to actually update the joystick, the application must call
|
||||
\ref libx52_update. This function writes the updates to the joystick and resets
|
||||
any internal state.
|
||||
|
||||
\b Example
|
||||
|
||||
@code{.c}
|
||||
libx52_set_text(dev, 0, " Saitek ", 16);
|
||||
libx52_set_text(dev, 1, " X52 Flight ", 16);
|
||||
libx52_set_text(dev, 2, " Control System ", 16);
|
||||
libx52_update(dev);
|
||||
@endcode
|
||||
|
||||
# Error handling
|
||||
|
||||
Most libx52 functions return a standard \ref libx52_error_code integer value
|
||||
that indicates the status of the operation. As long as the operation succeeded,
|
||||
the function will return \ref LIBX52_SUCCESS. Other values returned indicate a
|
||||
failure of some sort.
|
||||
|
||||
\ref libx52_strerror can convert the return code into a descriptive string that
|
||||
may be displayed to users.
|
||||
|
||||
## Internationalization of error strings
|
||||
|
||||
\ref libx52_strerror automatically handles internationalization. As long as your
|
||||
application sets up the locale correctly, and the error strings have been
|
||||
translated to that locale, the returned strings will correspond to the translated
|
||||
values for your locale.
|
||||
|
||||
*/
|
|
@ -0,0 +1,52 @@
|
|||
/**
|
||||
@mainpage Saitek X52/X52Pro drivers for Linux/Unix
|
||||
|
||||
@section intro Introduction
|
||||
|
||||
libx52 is an open source library that allows you to communicate with a
|
||||
Saitek X52 or Saitek X52Pro joystick on Linux and Unix machines. Saitek
|
||||
only provides Windows drivers for their joysticks, necessitating the need
|
||||
for this project for users to be able to control the LEDs and MFD text on
|
||||
a Linux or Unix device.
|
||||
|
||||
@section start Getting Started
|
||||
|
||||
@subsection install Installation
|
||||
|
||||
libx52 is available on Ubuntu as a PPA. Follow the instructions below to
|
||||
install on Ubuntu:
|
||||
|
||||
@code{.unparsed}
|
||||
sudo add-apt-repository nirenjan:libx52
|
||||
sudo apt update
|
||||
sudo apt install -y libx52-1
|
||||
@endcode
|
||||
|
||||
@subsection building Building from source
|
||||
|
||||
Please follow the instructions in [INSTALL.md](https://github.com/nirenjan/libx52/blob/master/INSTALL.md) specific to your system to build from source.
|
||||
|
||||
@section features Features
|
||||
|
||||
libx52 supports setting the following parameters on the joystick
|
||||
|
||||
1. Text on the multifunction display (MFD).
|
||||
2. All 3 clocks on the MFD.
|
||||
4. Date display on the MFD.
|
||||
5. Blinking of the POV hat and clutch LEDs (both blinking or not).
|
||||
6. Shift indicator on the MFD.
|
||||
7. Setting the color of the individual LEDs (*X52 Pro only*).
|
||||
|
||||
@section api Application Programming Interface
|
||||
|
||||
See the @ref libx52_integration page for details on how to integrate libx52
|
||||
with your application.
|
||||
|
||||
See the documentation for the following files for a complete list of all
|
||||
functions.
|
||||
|
||||
- libx52.h
|
||||
- libx52io.h
|
||||
- libx52util.h
|
||||
|
||||
*/
|
|
@ -0,0 +1,110 @@
|
|||
Saitek X52 Controller Map
|
||||
=========================
|
||||
|
||||
This page documents the HID Report format and how to decode the report to map
|
||||
to axis/buttons for the Saitek X52.
|
||||
|
||||
> **Note:** I am not sure that the button indices match with real hardware. The
|
||||
> indices are guessed by comparing the Test screen printed in the manuals for
|
||||
> both the X52 and X52 Pro, and my own
|
||||
> [documentation](x52pro_controller_map.md). I also looked at the [source code
|
||||
> of this project](https://github.com/xythobuz/Saitek-X52-PPM) to figure out
|
||||
> possible mappings. Given that the above project only uses PID 0x0255, it is
|
||||
> entirely possible that the report format for the X52 with PID 0x075c is
|
||||
> different.
|
||||
|
||||
Axis
|
||||
====
|
||||
* 0 - X axis
|
||||
* 1 - Y axis
|
||||
* 2 - Twist axis
|
||||
* 3 - Throttle axis
|
||||
* 4 - Clutch button rotary (rotary X)
|
||||
* 5 - E button rotary (rotary Y)
|
||||
* 6 - Slider
|
||||
|
||||
Buttons
|
||||
=======
|
||||
* 0 - Trigger primary
|
||||
* 1 - FIRE/LAUNCH
|
||||
* 2 - A
|
||||
* 3 - B
|
||||
* 4 - C
|
||||
* 5 - SHIFT
|
||||
* 6 - D
|
||||
* 7 - E
|
||||
* 8 - T1 Up
|
||||
* 9 - T1 Down
|
||||
* 10 - T2 Up
|
||||
* 11 - T2 Down
|
||||
* 12 - T3 Up
|
||||
* 13 - T3 Down
|
||||
* 14 - Trigger secondary
|
||||
* 15 - Stick 4-way N
|
||||
* 16 - Stick 4-way E
|
||||
* 17 - Stick 4-way S
|
||||
* 18 - Stick 4-way W
|
||||
* 19 - Throttle 4-way N (pull up)
|
||||
* 20 - Throttle 4-way E
|
||||
* 21 - Throttle 4-way S (push down)
|
||||
* 22 - Throttle 4-way W
|
||||
* 23 - MODE 1
|
||||
* 24 - MODE 2
|
||||
* 25 - MODE 3
|
||||
* 26 - FUNCTION
|
||||
* 27 - START/STOP/Down
|
||||
* 28 - RESET/Up
|
||||
* 29 - CLUTCH (i)
|
||||
* 30 - Mouse button - primary
|
||||
* 31 - Mouse button - secondary
|
||||
* 32 - Mouse wheel down
|
||||
* 33 - Mouse wheel up
|
||||
|
||||
The function button toggles between the clock and stopwatch on
|
||||
the MFD. If in stopwatch mode, the start/stop button starts or
|
||||
stops the stopwatch. If already stopped, the start/stop button
|
||||
will restart the stopwatch. The reset button will stop the
|
||||
stopwatch, if running, and also reset the display to 00:00.
|
||||
|
||||
If the MFD is in clock mode, then the start/stop button cycles
|
||||
backwards through the three on-board clock displays, while the
|
||||
reset button cycles forward through the displays.
|
||||
|
||||
If the primary clock has not been set using the vendor API,
|
||||
then the MFD will not display anything in clock mode. The
|
||||
start/stop and reset buttons will also not change anything
|
||||
on the display. Stopwatch mode is not affected by this.
|
||||
|
||||
Refer to the vendor API documentation for details on how to
|
||||
configure the MFD display.
|
||||
|
||||
USB Report Format
|
||||
=================
|
||||
The X52 reports 14 bytes of data everytime a joystick event occurs.
|
||||
|
||||
The 15 bytes are laid out in little endian format as shown below:
|
||||
|
||||
* 11-bits of X axis position
|
||||
* 11-bits of Y axis position
|
||||
* 10-bits of twist axis position
|
||||
* 8 bits of throttle position
|
||||
* 8 bits of rotary X position
|
||||
* 8 bits of rotary Y position
|
||||
* 8 bits of slider position
|
||||
* 34 bits of button information
|
||||
* 1 bits of padding
|
||||
* 4 bits of hat position
|
||||
* 4 bits of thumbstick X position
|
||||
* 4 bits of thumbstick Y position
|
||||
|
||||
A report would look like the following:
|
||||
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| X axis data | Y axis data | Rz axis data |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Throttle | Rx axis data | Ry axis data | Slider data |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Buttons 7-0 | Buttons 15-8 | Buttons 23-16 | Buttons 31-24 |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Hat |///|Btn| MouseY| MouseX|
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
@ -0,0 +1,60 @@
|
|||
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
|
||||
0x09, 0x04, // Usage (Joystick)
|
||||
0xA1, 0x01, // Collection (Application)
|
||||
0x09, 0x01, // Usage (Pointer)
|
||||
0xA1, 0x00, // Collection (Physical)
|
||||
0x09, 0x30, // Usage (X)
|
||||
0x09, 0x31, // Usage (Y)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x26, 0xFF, 0x07, // Logical Maximum (2047)
|
||||
0x75, 0x0B, // Report Size (11)
|
||||
0x95, 0x02, // Report Count (2)
|
||||
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
0x09, 0x35, // Usage (Rz)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x26, 0xFF, 0x03, // Logical Maximum (1023)
|
||||
0x75, 0x0A, // Report Size (10)
|
||||
0x95, 0x01, // Report Count (1)
|
||||
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
0x09, 0x32, // Usage (Z)
|
||||
0x09, 0x33, // Usage (Rx)
|
||||
0x09, 0x34, // Usage (Ry)
|
||||
0x09, 0x36, // Usage (Slider)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x26, 0xFF, 0x00, // Logical Maximum (255)
|
||||
0x75, 0x08, // Report Size (8)
|
||||
0x95, 0x04, // Report Count (4)
|
||||
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
0x05, 0x09, // Usage Page (Button)
|
||||
0x19, 0x01, // Usage Minimum (0x01)
|
||||
0x29, 0x22, // Usage Maximum (0x22)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x25, 0x01, // Logical Maximum (1)
|
||||
0x95, 0x22, // Report Count (34)
|
||||
0x75, 0x01, // Report Size (1)
|
||||
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
0x75, 0x02, // Report Size (2)
|
||||
0x95, 0x01, // Report Count (1)
|
||||
0x81, 0x01, // Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
|
||||
0x09, 0x39, // Usage (Hat switch)
|
||||
0x15, 0x01, // Logical Minimum (1)
|
||||
0x25, 0x08, // Logical Maximum (8)
|
||||
0x35, 0x00, // Physical Minimum (0)
|
||||
0x46, 0x3B, 0x01, // Physical Maximum (315)
|
||||
0x66, 0x14, 0x00, // Unit (System: English Rotation, Length: Centimeter)
|
||||
0x75, 0x04, // Report Size (4)
|
||||
0x95, 0x01, // Report Count (1)
|
||||
0x81, 0x42, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,Null State)
|
||||
0x05, 0x05, // Usage Page (Game Ctrls)
|
||||
0x09, 0x24, // Usage (Move Right/Left)
|
||||
0x09, 0x26, // Usage (Move Up/Down)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x25, 0x0F, // Logical Maximum (15)
|
||||
0x75, 0x04, // Report Size (4)
|
||||
0x95, 0x02, // Report Count (2)
|
||||
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
0xC0, // End Collection
|
||||
0xC0, // End Collection
|
||||
|
||||
// 119 bytes
|
|
@ -97,5 +97,5 @@ A report would look like the following:
|
|||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Buttons 7-0 | Buttons 15-8 | Buttons 23-16 | Buttons 31-24 |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|/| Btns 38-32 | Hat |///////| MouseX| MouseY|
|
||||
|/| Btns 38-32 | Hat |///////| MouseY| MouseX|
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
@ -0,0 +1,64 @@
|
|||
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
|
||||
0x09, 0x04, // Usage (Joystick)
|
||||
0xA1, 0x01, // Collection (Application)
|
||||
0x09, 0x01, // Usage (Pointer)
|
||||
0xA1, 0x00, // Collection (Physical)
|
||||
0x09, 0x30, // Usage (X)
|
||||
0x09, 0x31, // Usage (Y)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x26, 0xFF, 0x03, // Logical Maximum (1023)
|
||||
0x75, 0x0A, // Report Size (10)
|
||||
0x95, 0x02, // Report Count (2)
|
||||
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
0x75, 0x02, // Report Size (2)
|
||||
0x95, 0x01, // Report Count (1)
|
||||
0x81, 0x01, // Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
0x09, 0x35, // Usage (Rz)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x26, 0xFF, 0x03, // Logical Maximum (1023)
|
||||
0x75, 0x0A, // Report Size (10)
|
||||
0x95, 0x01, // Report Count (1)
|
||||
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
0x09, 0x32, // Usage (Z)
|
||||
0x09, 0x33, // Usage (Rx)
|
||||
0x09, 0x34, // Usage (Ry)
|
||||
0x09, 0x36, // Usage (Slider)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x26, 0xFF, 0x00, // Logical Maximum (255)
|
||||
0x75, 0x08, // Report Size (8)
|
||||
0x95, 0x04, // Report Count (4)
|
||||
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
0x05, 0x09, // Usage Page (Button)
|
||||
0x19, 0x01, // Usage Minimum (0x01)
|
||||
0x29, 0x27, // Usage Maximum (0x27)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x25, 0x01, // Logical Maximum (1)
|
||||
0x95, 0x27, // Report Count (39)
|
||||
0x75, 0x01, // Report Size (1)
|
||||
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
0x75, 0x05, // Report Size (5)
|
||||
0x95, 0x01, // Report Count (1)
|
||||
0x81, 0x01, // Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
|
||||
0x09, 0x39, // Usage (Hat switch)
|
||||
0x15, 0x01, // Logical Minimum (1)
|
||||
0x25, 0x08, // Logical Maximum (8)
|
||||
0x35, 0x00, // Physical Minimum (0)
|
||||
0x46, 0x3B, 0x01, // Physical Maximum (315)
|
||||
0x66, 0x14, 0x00, // Unit (System: English Rotation, Length: Centimeter)
|
||||
0x75, 0x04, // Report Size (4)
|
||||
0x95, 0x01, // Report Count (1)
|
||||
0x81, 0x42, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,Null State)
|
||||
0x05, 0x05, // Usage Page (Game Ctrls)
|
||||
0x09, 0x24, // Usage (Move Right/Left)
|
||||
0x09, 0x26, // Usage (Move Up/Down)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x25, 0x0F, // Logical Maximum (15)
|
||||
0x75, 0x04, // Report Size (4)
|
||||
0x95, 0x02, // Report Count (2)
|
||||
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
0xC0, // End Collection
|
||||
0xC0, // End Collection
|
||||
|
||||
// 125 bytes
|
||||
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 104 KiB |
|
@ -0,0 +1,13 @@
|
|||
# Automake for x52evtest
|
||||
#
|
||||
# Copyright (C) 2012-2020 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
|
||||
bin_PROGRAMS += x52evtest
|
||||
|
||||
# Event test utility that works similarly to the Linux evtest
|
||||
x52evtest_SOURCES = evtest/ev_test.c
|
||||
x52evtest_CFLAGS = -I $(top_srcdir)/libx52io -I $(top_srcdir) -DLOCALEDIR=\"$(localedir)\" $(WARN_CFLAGS)
|
||||
x52evtest_LDFLAGS = $(WARN_LDFLAGS)
|
||||
x52evtest_LDADD = libx52io.la
|
|
@ -0,0 +1,182 @@
|
|||
/*
|
||||
* Saitek X52 Pro MFD & LED driver - Event test utility
|
||||
*
|
||||
* Copyright (C) 2012-2020 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <signal.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#include "libx52io.h"
|
||||
#include "gettext.h"
|
||||
|
||||
/*
|
||||
Output Format
|
||||
=============
|
||||
|
||||
Driver version is 0.0.0
|
||||
Device ID: bus 0x3 vendor 0x06a3 product 0x0762 version 0x110
|
||||
Device name: "Saitek X52 Pro Flight Control System"
|
||||
Testing ... (interrupt to exit)
|
||||
|
||||
Event @ 1594431236.817842, ABS_X, value 512
|
||||
Event @ 1594431236.817842, ABS_Y, value 511
|
||||
Event @ 1594431236.817842, BTN_TRIGGER, value 1
|
||||
Event @ 1594431236.817842, BTN_MODE_1, value 1
|
||||
|
||||
Event @ 1594431236.847810, BTN_MODE_1, value 0
|
||||
|
||||
Event @ 1594431236.877802, BTN_MODE_2, value 1
|
||||
|
||||
*/
|
||||
|
||||
static bool exit_loop = false;
|
||||
|
||||
static void signal_handler(int sig)
|
||||
{
|
||||
exit_loop = true;
|
||||
}
|
||||
|
||||
/* Denoising - reduce event noise due to adjacent values being reported */
|
||||
static bool denoise = true;
|
||||
|
||||
/* For i18n */
|
||||
#define _(x) gettext(x)
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
libx52io_context *ctx;
|
||||
libx52io_report last, curr;
|
||||
int32_t denoise_mask[LIBX52IO_AXIS_MAX] = { 0 };
|
||||
int rc;
|
||||
#define CHECK_RC() do { \
|
||||
if (rc != LIBX52IO_SUCCESS) { \
|
||||
fprintf(stderr, "%s\n", libx52io_strerror(rc)); \
|
||||
return rc; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
/* Initialize gettext */
|
||||
#if ENABLE_NLS
|
||||
setlocale(LC_ALL, "");
|
||||
bindtextdomain(PACKAGE, LOCALEDIR);
|
||||
textdomain(LOCALEDIR);
|
||||
#endif
|
||||
|
||||
memset(&last, 0, sizeof(last));
|
||||
memset(&curr, 0, sizeof(curr));
|
||||
|
||||
/* Initialize libx52io */
|
||||
rc = libx52io_init(&ctx);
|
||||
CHECK_RC();
|
||||
|
||||
/* Make sure that we have a device to connect to */
|
||||
rc = libx52io_open(ctx);
|
||||
CHECK_RC();
|
||||
|
||||
/* Initialize denoising */
|
||||
if (denoise) {
|
||||
for (int i = LIBX52IO_AXIS_X; i < LIBX52IO_AXIS_MAX; i++) {
|
||||
int32_t min, max;
|
||||
rc = libx52io_get_axis_range(ctx, i, &min, &max);
|
||||
CHECK_RC();
|
||||
|
||||
/*
|
||||
* Denoising algorithm ignores the last few bits of the axis,
|
||||
* and is based on the maximum value of the axis. The mask is
|
||||
* ~(max >> 6) which will do nothing for the axis with a small
|
||||
* range, but reduce the noise on those with a larger range.
|
||||
*/
|
||||
denoise_mask[i] = ~(max >> 6);
|
||||
}
|
||||
}
|
||||
|
||||
/* Set up the signal handler to terminate the loop on SIGTERM or SIGINT */
|
||||
exit_loop = false;
|
||||
signal(SIGTERM, signal_handler);
|
||||
signal(SIGINT, signal_handler);
|
||||
|
||||
/* Print the driver version and the connected device */
|
||||
printf(_("Device ID: vendor 0x%04x product 0x%04x version 0x%04x\n"),
|
||||
libx52io_get_vendor_id(ctx),
|
||||
libx52io_get_product_id(ctx),
|
||||
libx52io_get_device_version(ctx));
|
||||
printf(_("Device name: \"%s %s\"\n"),
|
||||
libx52io_get_manufacturer_string(ctx),
|
||||
libx52io_get_product_string(ctx));
|
||||
printf(_("Serial number: \"%s\"\n"), libx52io_get_serial_number_string(ctx));
|
||||
puts(_("Testing (interrupt to exit)\n"));
|
||||
|
||||
/* Wait until we get an event */
|
||||
while (!exit_loop) {
|
||||
struct timeval tv;
|
||||
bool printed = false;
|
||||
|
||||
/* Wait for 1 second before timing out */
|
||||
rc = libx52io_read_timeout(ctx, &curr, 1000);
|
||||
if (rc == LIBX52IO_ERROR_TIMEOUT) {
|
||||
continue;
|
||||
} else if (rc != LIBX52IO_SUCCESS) {
|
||||
/* Some other error while reading. Abort the loop */
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* Successful read, compare the current report against the previous
|
||||
* one and display the result
|
||||
*/
|
||||
if (memcmp(&last, &curr, sizeof(curr)) == 0) {
|
||||
/* No change, ignore the output */
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Get the current timeval - we don't need a timezone */
|
||||
gettimeofday(&tv, NULL);
|
||||
for (int axis = 0; axis < LIBX52IO_AXIS_MAX; axis++) {
|
||||
if (last.axis[axis] != curr.axis[axis]) {
|
||||
/* Account for denoising */
|
||||
if (denoise) {
|
||||
int32_t last_v = last.axis[axis] & denoise_mask[axis];
|
||||
int32_t curr_v = curr.axis[axis] & denoise_mask[axis];
|
||||
|
||||
if (last_v == curr_v) {
|
||||
/* Within the noise threshold */
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
printf(_("Event @ %ld.%06ld: %s, value %d\n"),
|
||||
(long int)tv.tv_sec, (long int)tv.tv_usec,
|
||||
libx52io_axis_to_str(axis), curr.axis[axis]);
|
||||
printed = true;
|
||||
}
|
||||
}
|
||||
for (int btn = 0; btn < LIBX52IO_BUTTON_MAX; btn++) {
|
||||
if (last.button[btn] != curr.button[btn]) {
|
||||
printf(_("Event @ %ld.%06ld: %s, value %d\n"),
|
||||
(long int)tv.tv_sec, (long int)tv.tv_usec,
|
||||
libx52io_button_to_str(btn), curr.button[btn]);
|
||||
printed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (printed) {
|
||||
puts("");
|
||||
}
|
||||
|
||||
memcpy(&last, &curr, sizeof(curr));
|
||||
}
|
||||
|
||||
/* Close and exit the libx52io library */
|
||||
libx52io_close(ctx);
|
||||
libx52io_exit(ctx);
|
||||
}
|
|
@ -0,0 +1,297 @@
|
|||
/* Convenience header for conditional use of GNU <libintl.h>.
|
||||
Copyright (C) 1995-1998, 2000-2002, 2004-2006, 2009-2016 Free Software
|
||||
Foundation, Inc.
|
||||
|
||||
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 3 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/>. */
|
||||
|
||||
#ifndef _LIBGETTEXT_H
|
||||
#define _LIBGETTEXT_H 1
|
||||
|
||||
#ifndef ENABLE_NLS
|
||||
#define ENABLE_NLS 0
|
||||
#endif
|
||||
|
||||
/* NLS can be disabled through the configure --disable-nls option. */
|
||||
#if ENABLE_NLS
|
||||
|
||||
/* Get declarations of GNU message catalog functions. */
|
||||
# include <locale.h>
|
||||
# include <libintl.h>
|
||||
|
||||
/* You can set the DEFAULT_TEXT_DOMAIN macro to specify the domain used by
|
||||
the gettext() and ngettext() macros. This is an alternative to calling
|
||||
textdomain(), and is useful for libraries. */
|
||||
# ifdef DEFAULT_TEXT_DOMAIN
|
||||
# undef gettext
|
||||
# define gettext(Msgid) \
|
||||
dgettext (DEFAULT_TEXT_DOMAIN, Msgid)
|
||||
# undef ngettext
|
||||
# define ngettext(Msgid1, Msgid2, N) \
|
||||
dngettext (DEFAULT_TEXT_DOMAIN, Msgid1, Msgid2, N)
|
||||
# endif
|
||||
|
||||
#else
|
||||
|
||||
/* Solaris /usr/include/locale.h includes /usr/include/libintl.h, which
|
||||
chokes if dcgettext is defined as a macro. So include it now, to make
|
||||
later inclusions of <locale.h> a NOP. We don't include <libintl.h>
|
||||
as well because people using "gettext.h" will not include <libintl.h>,
|
||||
and also including <libintl.h> would fail on SunOS 4, whereas <locale.h>
|
||||
is OK. */
|
||||
#if defined(__sun)
|
||||
# include <locale.h>
|
||||
#endif
|
||||
|
||||
/* Many header files from the libstdc++ coming with g++ 3.3 or newer include
|
||||
<libintl.h>, which chokes if dcgettext is defined as a macro. So include
|
||||
it now, to make later inclusions of <libintl.h> a NOP. */
|
||||
#if defined(__cplusplus) && defined(__GNUG__) && (__GNUC__ >= 3)
|
||||
# include <cstdlib>
|
||||
# if (__GLIBC__ >= 2 && !defined __UCLIBC__) || _GLIBCXX_HAVE_LIBINTL_H
|
||||
# include <libintl.h>
|
||||
# endif
|
||||
#endif
|
||||
|
||||
/* Disabled NLS.
|
||||
The casts to 'const char *' serve the purpose of producing warnings
|
||||
for invalid uses of the value returned from these functions.
|
||||
On pre-ANSI systems without 'const', the config.h file is supposed to
|
||||
contain "#define const". */
|
||||
# undef gettext
|
||||
# define gettext(Msgid) ((const char *) (Msgid))
|
||||
# undef dgettext
|
||||
# define dgettext(Domainname, Msgid) (gettext (Msgid))
|
||||
# undef dcgettext
|
||||
# define dcgettext(Domainname, Msgid, Category) \
|
||||
(dgettext (Domainname, Msgid))
|
||||
# undef ngettext
|
||||
# define ngettext(Msgid1, Msgid2, N) \
|
||||
((N) == 1 \
|
||||
? ((const char *) (Msgid1)) \
|
||||
: ((const char *) (Msgid2)))
|
||||
# undef dngettext
|
||||
# define dngettext(Domainname, Msgid1, Msgid2, N) \
|
||||
(ngettext (Msgid1, Msgid2, N))
|
||||
# undef dcngettext
|
||||
# define dcngettext(Domainname, Msgid1, Msgid2, N, Category) \
|
||||
(dngettext (Domainname, Msgid1, Msgid2, N))
|
||||
# undef textdomain
|
||||
# define textdomain(Domainname) ((const char *) (Domainname))
|
||||
# undef bindtextdomain
|
||||
# define bindtextdomain(Domainname, Dirname) \
|
||||
(void)((void) (Domainname), (const char *) (Dirname))
|
||||
# undef bind_textdomain_codeset
|
||||
# define bind_textdomain_codeset(Domainname, Codeset) \
|
||||
(void)((void) (Domainname), (const char *) (Codeset))
|
||||
|
||||
#endif
|
||||
|
||||
/* Prefer gnulib's setlocale override over libintl's setlocale override. */
|
||||
#ifdef GNULIB_defined_setlocale
|
||||
# undef setlocale
|
||||
# define setlocale rpl_setlocale
|
||||
#endif
|
||||
|
||||
/* A pseudo function call that serves as a marker for the automated
|
||||
extraction of messages, but does not call gettext(). The run-time
|
||||
translation is done at a different place in the code.
|
||||
The argument, String, should be a literal string. Concatenated strings
|
||||
and other string expressions won't work.
|
||||
The macro's expansion is not parenthesized, so that it is suitable as
|
||||
initializer for static 'char[]' or 'const char[]' variables. */
|
||||
#define gettext_noop(String) String
|
||||
|
||||
/* The separator between msgctxt and msgid in a .mo file. */
|
||||
#define GETTEXT_CONTEXT_GLUE "\004"
|
||||
|
||||
/* Pseudo function calls, taking a MSGCTXT and a MSGID instead of just a
|
||||
MSGID. MSGCTXT and MSGID must be string literals. MSGCTXT should be
|
||||
short and rarely need to change.
|
||||
The letter 'p' stands for 'particular' or 'special'. */
|
||||
#ifdef DEFAULT_TEXT_DOMAIN
|
||||
# define pgettext(Msgctxt, Msgid) \
|
||||
pgettext_aux (DEFAULT_TEXT_DOMAIN, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, LC_MESSAGES)
|
||||
#else
|
||||
# define pgettext(Msgctxt, Msgid) \
|
||||
pgettext_aux (NULL, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, LC_MESSAGES)
|
||||
#endif
|
||||
#define dpgettext(Domainname, Msgctxt, Msgid) \
|
||||
pgettext_aux (Domainname, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, LC_MESSAGES)
|
||||
#define dcpgettext(Domainname, Msgctxt, Msgid, Category) \
|
||||
pgettext_aux (Domainname, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, Category)
|
||||
#ifdef DEFAULT_TEXT_DOMAIN
|
||||
# define npgettext(Msgctxt, Msgid, MsgidPlural, N) \
|
||||
npgettext_aux (DEFAULT_TEXT_DOMAIN, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, MsgidPlural, N, LC_MESSAGES)
|
||||
#else
|
||||
# define npgettext(Msgctxt, Msgid, MsgidPlural, N) \
|
||||
npgettext_aux (NULL, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, MsgidPlural, N, LC_MESSAGES)
|
||||
#endif
|
||||
#define dnpgettext(Domainname, Msgctxt, Msgid, MsgidPlural, N) \
|
||||
npgettext_aux (Domainname, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, MsgidPlural, N, LC_MESSAGES)
|
||||
#define dcnpgettext(Domainname, Msgctxt, Msgid, MsgidPlural, N, Category) \
|
||||
npgettext_aux (Domainname, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, MsgidPlural, N, Category)
|
||||
|
||||
#ifdef __GNUC__
|
||||
__inline
|
||||
#else
|
||||
#ifdef __cplusplus
|
||||
inline
|
||||
#endif
|
||||
#endif
|
||||
static const char *
|
||||
pgettext_aux (const char *domain,
|
||||
const char *msg_ctxt_id, const char *msgid,
|
||||
int category)
|
||||
{
|
||||
const char *translation = dcgettext (domain, msg_ctxt_id, category);
|
||||
if (translation == msg_ctxt_id)
|
||||
return msgid;
|
||||
else
|
||||
return translation;
|
||||
}
|
||||
|
||||
#ifdef __GNUC__
|
||||
__inline
|
||||
#else
|
||||
#ifdef __cplusplus
|
||||
inline
|
||||
#endif
|
||||
#endif
|
||||
static const char *
|
||||
npgettext_aux (const char *domain,
|
||||
const char *msg_ctxt_id, const char *msgid,
|
||||
const char *msgid_plural, unsigned long int n,
|
||||
int category)
|
||||
{
|
||||
const char *translation =
|
||||
dcngettext (domain, msg_ctxt_id, msgid_plural, n, category);
|
||||
if (translation == msg_ctxt_id || translation == msgid_plural)
|
||||
return (n == 1 ? msgid : msgid_plural);
|
||||
else
|
||||
return translation;
|
||||
}
|
||||
|
||||
/* The same thing extended for non-constant arguments. Here MSGCTXT and MSGID
|
||||
can be arbitrary expressions. But for string literals these macros are
|
||||
less efficient than those above. */
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#if (((__GNUC__ >= 3 || __GNUG__ >= 2) && !defined __STRICT_ANSI__) \
|
||||
/* || __STDC_VERSION__ >= 199901L */ )
|
||||
# define _LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS 1
|
||||
#else
|
||||
# define _LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS 0
|
||||
#endif
|
||||
|
||||
#if !_LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS
|
||||
#include <stdlib.h>
|
||||
#endif
|
||||
|
||||
#define pgettext_expr(Msgctxt, Msgid) \
|
||||
dcpgettext_expr (NULL, Msgctxt, Msgid, LC_MESSAGES)
|
||||
#define dpgettext_expr(Domainname, Msgctxt, Msgid) \
|
||||
dcpgettext_expr (Domainname, Msgctxt, Msgid, LC_MESSAGES)
|
||||
|
||||
#ifdef __GNUC__
|
||||
__inline
|
||||
#else
|
||||
#ifdef __cplusplus
|
||||
inline
|
||||
#endif
|
||||
#endif
|
||||
static const char *
|
||||
dcpgettext_expr (const char *domain,
|
||||
const char *msgctxt, const char *msgid,
|
||||
int category)
|
||||
{
|
||||
size_t msgctxt_len = strlen (msgctxt) + 1;
|
||||
size_t msgid_len = strlen (msgid) + 1;
|
||||
const char *translation;
|
||||
#if _LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS
|
||||
char msg_ctxt_id[msgctxt_len + msgid_len];
|
||||
#else
|
||||
char buf[1024];
|
||||
char *msg_ctxt_id =
|
||||
(msgctxt_len + msgid_len <= sizeof (buf)
|
||||
? buf
|
||||
: (char *) malloc (msgctxt_len + msgid_len));
|
||||
if (msg_ctxt_id != NULL)
|
||||
#endif
|
||||
{
|
||||
int found_translation;
|
||||
memcpy (msg_ctxt_id, msgctxt, msgctxt_len - 1);
|
||||
msg_ctxt_id[msgctxt_len - 1] = '\004';
|
||||
memcpy (msg_ctxt_id + msgctxt_len, msgid, msgid_len);
|
||||
translation = dcgettext (domain, msg_ctxt_id, category);
|
||||
found_translation = (translation != msg_ctxt_id);
|
||||
#if !_LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS
|
||||
if (msg_ctxt_id != buf)
|
||||
free (msg_ctxt_id);
|
||||
#endif
|
||||
if (found_translation)
|
||||
return translation;
|
||||
}
|
||||
return msgid;
|
||||
}
|
||||
|
||||
#define npgettext_expr(Msgctxt, Msgid, MsgidPlural, N) \
|
||||
dcnpgettext_expr (NULL, Msgctxt, Msgid, MsgidPlural, N, LC_MESSAGES)
|
||||
#define dnpgettext_expr(Domainname, Msgctxt, Msgid, MsgidPlural, N) \
|
||||
dcnpgettext_expr (Domainname, Msgctxt, Msgid, MsgidPlural, N, LC_MESSAGES)
|
||||
|
||||
#ifdef __GNUC__
|
||||
__inline
|
||||
#else
|
||||
#ifdef __cplusplus
|
||||
inline
|
||||
#endif
|
||||
#endif
|
||||
static const char *
|
||||
dcnpgettext_expr (const char *domain,
|
||||
const char *msgctxt, const char *msgid,
|
||||
const char *msgid_plural, unsigned long int n,
|
||||
int category)
|
||||
{
|
||||
size_t msgctxt_len = strlen (msgctxt) + 1;
|
||||
size_t msgid_len = strlen (msgid) + 1;
|
||||
const char *translation;
|
||||
#if _LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS
|
||||
char msg_ctxt_id[msgctxt_len + msgid_len];
|
||||
#else
|
||||
char buf[1024];
|
||||
char *msg_ctxt_id =
|
||||
(msgctxt_len + msgid_len <= sizeof (buf)
|
||||
? buf
|
||||
: (char *) malloc (msgctxt_len + msgid_len));
|
||||
if (msg_ctxt_id != NULL)
|
||||
#endif
|
||||
{
|
||||
int found_translation;
|
||||
memcpy (msg_ctxt_id, msgctxt, msgctxt_len - 1);
|
||||
msg_ctxt_id[msgctxt_len - 1] = '\004';
|
||||
memcpy (msg_ctxt_id + msgctxt_len, msgid, msgid_len);
|
||||
translation = dcngettext (domain, msg_ctxt_id, msgid_plural, n, category);
|
||||
found_translation = !(translation == msg_ctxt_id || translation == msgid_plural);
|
||||
#if !_LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS
|
||||
if (msg_ctxt_id != buf)
|
||||
free (msg_ctxt_id);
|
||||
#endif
|
||||
if (found_translation)
|
||||
return translation;
|
||||
}
|
||||
return (n == 1 ? msgid : msgid_plural);
|
||||
}
|
||||
|
||||
#endif /* _LIBGETTEXT_H */
|
|
@ -0,0 +1,20 @@
|
|||
# Automake for x52test
|
||||
#
|
||||
# Copyright (C) 2012-2018 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
|
||||
bin_PROGRAMS += x52test
|
||||
|
||||
# Test utility that exercises all the library functions
|
||||
x52test_SOURCES = \
|
||||
joytest/x52_test.c \
|
||||
joytest/x52_test_mfd.c \
|
||||
joytest/x52_test_led.c \
|
||||
joytest/x52_test_clock.c
|
||||
x52test_CFLAGS = -I $(top_srcdir)/libx52 -I $(top_srcdir) -DLOCALEDIR=\"$(localedir)\" $(WARN_CFLAGS)
|
||||
x52test_LDFLAGS = $(WARN_LDFLAGS)
|
||||
x52test_LDADD = libx52.la
|
||||
|
||||
# Extra files that need to be in the distribution
|
||||
EXTRA_DIST += joytest/x52_test_common.h
|
|
@ -0,0 +1,261 @@
|
|||
/*
|
||||
* Saitek X52 Pro MFD & LED driver
|
||||
*
|
||||
* Copyright (C) 2012-2015 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include "config.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <signal.h>
|
||||
#include <string.h>
|
||||
#include <locale.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "libx52.h"
|
||||
#include "x52_test_common.h"
|
||||
libx52_device *dev;
|
||||
int test_exit;
|
||||
bool nodelay;
|
||||
|
||||
void test_cleanup(void)
|
||||
{
|
||||
if (libx52_check_feature(dev, LIBX52_FEATURE_LED) == LIBX52_SUCCESS) {
|
||||
/* X52 Pro */
|
||||
libx52_set_text(dev, 0, " Saitek X52 Pro ", 16);
|
||||
libx52_set_text(dev, 1, " Flight ", 16);
|
||||
libx52_set_text(dev, 2, " Control System ", 16);
|
||||
|
||||
libx52_set_led_state(dev, LIBX52_LED_FIRE, LIBX52_LED_STATE_ON);
|
||||
libx52_set_led_state(dev, LIBX52_LED_THROTTLE, LIBX52_LED_STATE_ON);
|
||||
libx52_set_led_state(dev, LIBX52_LED_A, LIBX52_LED_STATE_GREEN);
|
||||
libx52_set_led_state(dev, LIBX52_LED_B, LIBX52_LED_STATE_GREEN);
|
||||
libx52_set_led_state(dev, LIBX52_LED_D, LIBX52_LED_STATE_GREEN);
|
||||
libx52_set_led_state(dev, LIBX52_LED_E, LIBX52_LED_STATE_GREEN);
|
||||
libx52_set_led_state(dev, LIBX52_LED_T1, LIBX52_LED_STATE_GREEN);
|
||||
libx52_set_led_state(dev, LIBX52_LED_T2, LIBX52_LED_STATE_GREEN);
|
||||
libx52_set_led_state(dev, LIBX52_LED_T3, LIBX52_LED_STATE_GREEN);
|
||||
libx52_set_led_state(dev, LIBX52_LED_POV, LIBX52_LED_STATE_GREEN);
|
||||
libx52_set_led_state(dev, LIBX52_LED_CLUTCH, LIBX52_LED_STATE_GREEN);
|
||||
} else {
|
||||
libx52_set_text(dev, 0, " Saitek ", 16);
|
||||
libx52_set_text(dev, 1, " X52 Flight ", 16);
|
||||
libx52_set_text(dev, 2, " Control System ", 16);
|
||||
}
|
||||
|
||||
libx52_set_blink(dev, 0);
|
||||
libx52_set_shift(dev, 0);
|
||||
|
||||
libx52_set_clock(dev, time(NULL), 1); // Display local time
|
||||
libx52_set_clock_timezone(dev, LIBX52_CLOCK_2, 0); // GMT
|
||||
libx52_set_clock_timezone(dev, LIBX52_CLOCK_3, 330); // IST
|
||||
|
||||
libx52_set_clock_format(dev, LIBX52_CLOCK_1, LIBX52_CLOCK_FORMAT_24HR);
|
||||
libx52_set_clock_format(dev, LIBX52_CLOCK_2, LIBX52_CLOCK_FORMAT_24HR);
|
||||
libx52_set_clock_format(dev, LIBX52_CLOCK_3, LIBX52_CLOCK_FORMAT_24HR);
|
||||
|
||||
libx52_set_date_format(dev, LIBX52_DATE_FORMAT_MMDDYY);
|
||||
|
||||
libx52_set_brightness(dev, 1, 128);
|
||||
libx52_set_brightness(dev, 0, 128);
|
||||
|
||||
libx52_update(dev);
|
||||
}
|
||||
|
||||
void print_banner(const char *message)
|
||||
{
|
||||
size_t len = strlen(message);
|
||||
size_t i;
|
||||
|
||||
putchar('\n');
|
||||
putchar('\t');
|
||||
for (i = 0; i < len + 2; i++) {
|
||||
putchar('=');
|
||||
}
|
||||
putchar('\n');
|
||||
|
||||
putchar('\t');
|
||||
putchar(' ');
|
||||
puts(message);
|
||||
|
||||
putchar('\t');
|
||||
for (i = 0; i < len + 2; i++) {
|
||||
putchar('=');
|
||||
}
|
||||
putchar('\n');
|
||||
}
|
||||
|
||||
static void signal_handler(int sig)
|
||||
{
|
||||
test_exit = -sig;
|
||||
}
|
||||
|
||||
#define TESTS \
|
||||
X(brightness, bri, gettext_noop("Test brightness scale (~ 1m)")) \
|
||||
X(leds, led, gettext_noop("Test LED states (~ 45s)")) \
|
||||
X(mfd_text, mfd1, gettext_noop("Test MFD string display (~ 30s)")) \
|
||||
X(mfd_display, mfd2, gettext_noop("Test MFD displays all characters (~ 2m 15s)")) \
|
||||
X(blink_n_shift, blink, gettext_noop("Test the blink and shift commands (< 10s)")) \
|
||||
X(clock, clock, gettext_noop("Test the clock commands (~1m)"))
|
||||
|
||||
enum {
|
||||
#define X(en, kw, desc) TEST_BIT_ ## en,
|
||||
TESTS
|
||||
#undef X
|
||||
};
|
||||
|
||||
enum {
|
||||
#define X(en, kw, desc) TEST_ ## en = (1 << TEST_BIT_ ## en),
|
||||
TESTS
|
||||
#undef X
|
||||
};
|
||||
|
||||
#define X(en, kw, desc) | TEST_ ## en
|
||||
const int TEST_ALL = 0 TESTS;
|
||||
#undef X
|
||||
|
||||
static int run_tests(int test_set)
|
||||
{
|
||||
#define RUN_TEST(tst) if (test_set & TEST_ ## tst) { rc = test_ ## tst (); if (rc) break; }
|
||||
|
||||
int rc = 0;
|
||||
|
||||
puts(_("x52test is a suite of tests to write to the X52 Pro device\n"
|
||||
"and test the extra functionality available in the LEDs and MFD\n"));
|
||||
|
||||
if (test_set == TEST_ALL) {
|
||||
puts(_("These tests take roughly 6 minutes to run"));
|
||||
}
|
||||
puts(_("Press Enter to begin the tests, press Ctrl-C to abort anytime"));
|
||||
|
||||
getc(stdin);
|
||||
|
||||
rc = libx52_init(&dev);
|
||||
if (rc != LIBX52_SUCCESS) {
|
||||
fprintf(stderr, _("Unable to initialize X52 library: %s\n"), libx52_strerror(rc));
|
||||
return 1;
|
||||
}
|
||||
|
||||
signal(SIGTERM, signal_handler);
|
||||
signal(SIGINT, signal_handler);
|
||||
test_exit = 0;
|
||||
|
||||
do {
|
||||
#define X(en, cmd, desc) RUN_TEST(en)
|
||||
TESTS
|
||||
#undef X
|
||||
} while (0);
|
||||
|
||||
if (rc == LIBX52_SUCCESS) {
|
||||
puts(_("All tests completed successfully"));
|
||||
} else if (rc > 0) {
|
||||
fprintf(stderr, _("Got error %s\n"), libx52_strerror(rc));
|
||||
} else {
|
||||
fprintf(stderr, _("Received %s signal, quitting...\n"), strsignal(-rc));
|
||||
}
|
||||
|
||||
if (rc <= 0) test_cleanup();
|
||||
libx52_exit(dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void underline(const char *msg) {
|
||||
size_t i;
|
||||
puts(msg);
|
||||
for (i = 0; i < strlen(msg); i++) {
|
||||
putchar('=');
|
||||
}
|
||||
putchar('\n');
|
||||
}
|
||||
|
||||
static void usage(void)
|
||||
{
|
||||
puts(_("These are the available tests with a description and\n"
|
||||
"approximate runtime. Not specifying any tests will run\n"
|
||||
"all the tests\n"));
|
||||
|
||||
underline(_("List of tests:"));
|
||||
|
||||
#define X(en, cmd, desc) puts("\t" #cmd "\t" desc);
|
||||
TESTS
|
||||
#undef X
|
||||
|
||||
puts("");
|
||||
}
|
||||
|
||||
struct test_map {
|
||||
const char *cmd;
|
||||
int test_bitmap;
|
||||
};
|
||||
const struct test_map tests[] = {
|
||||
#define X(en, cmd, desc) { #cmd, TEST_ ##en },
|
||||
TESTS
|
||||
#undef X
|
||||
{ NULL, 0 }
|
||||
};
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int test_list;
|
||||
int i;
|
||||
const struct test_map *test;
|
||||
int found;
|
||||
|
||||
/* Initialize gettext */
|
||||
#if ENABLE_NLS
|
||||
setlocale(LC_ALL, "");
|
||||
bindtextdomain(PACKAGE, LOCALEDIR);
|
||||
textdomain(PACKAGE);
|
||||
#endif
|
||||
|
||||
/* Usage: x52test [list of tests] */
|
||||
if (argc == 1) {
|
||||
/* Run all tests, if none specified */
|
||||
test_list = TEST_ALL;
|
||||
} else {
|
||||
/* Initialize the test list to run no tests, the commands
|
||||
* will enable the selective tests
|
||||
*/
|
||||
test_list = 0;
|
||||
}
|
||||
|
||||
for (i = 1; i < argc; i++) {
|
||||
if (!strcmp(argv[i], "help") ||
|
||||
!strcmp(argv[i], "--help")) {
|
||||
|
||||
/* Display help string and exit */
|
||||
printf(_("Usage: %s [list of tests]\n\n"), argv[0]);
|
||||
usage();
|
||||
return 0;
|
||||
} else {
|
||||
found = 0;
|
||||
for (test = tests; test->cmd; test++) {
|
||||
if (!strcmp(argv[i], test->cmd)) {
|
||||
test_list |= test->test_bitmap;
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found == 0) {
|
||||
printf(_("Unrecognized test identifier: %s\n\n"), argv[i]);
|
||||
usage();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Initialize the nodelay variable */
|
||||
nodelay = (getenv("LD_PRELOAD") != NULL || getenv("NO_DELAY") != NULL);
|
||||
|
||||
if (test_list) {
|
||||
i = run_tests(test_list);
|
||||
} else {
|
||||
puts(_("Not running any tests"));
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Saitek X52 Pro MFD & LED driver
|
||||
*
|
||||
* Copyright (C) 2012-2015 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include "config.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "libx52.h"
|
||||
#include "x52_test_common.h"
|
||||
|
||||
int test_clock(void)
|
||||
{
|
||||
time_t i;
|
||||
time_t start;
|
||||
time_t end;
|
||||
|
||||
#define TEST_CLOCK_LOOP(bump) do { \
|
||||
for (i = start; i < end; i += bump) { \
|
||||
TEST(clock, i, 0); \
|
||||
if (!nodelay) usleep(250000); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
print_banner(_("Clock"));
|
||||
puts(_("This tests the clock display"));
|
||||
|
||||
/* Start from midnight Jan 1 2015 GMT */
|
||||
start = 1420070400;
|
||||
|
||||
puts(_("\nTesting clock time minute display for 90 minutes"));
|
||||
end = start + 90 * 60;
|
||||
TEST(clock_format, LIBX52_CLOCK_1, LIBX52_CLOCK_FORMAT_12HR);
|
||||
TEST_CLOCK_LOOP(60);
|
||||
|
||||
end = start + 36 * 60 * 60;
|
||||
puts(_("\nTesting clock time hour display for 36 hours, 12 hour mode"));
|
||||
TEST_CLOCK_LOOP(3600);
|
||||
|
||||
puts(_("\nTesting clock time hour display for 36 hours, 24 hour mode"));
|
||||
TEST(clock_format, LIBX52_CLOCK_1, LIBX52_CLOCK_FORMAT_24HR);
|
||||
TEST_CLOCK_LOOP(3600);
|
||||
|
||||
end = start + 31 * 24 * 60 * 60;
|
||||
puts(_("\nTesting clock date display for 31 days, dd-mm-yy"));
|
||||
TEST(date_format, LIBX52_DATE_FORMAT_DDMMYY);
|
||||
TEST_CLOCK_LOOP(86400);
|
||||
|
||||
puts(_("\nTesting clock date display for 31 days, mm-dd-yy"));
|
||||
TEST(date_format, LIBX52_DATE_FORMAT_MMDDYY);
|
||||
TEST_CLOCK_LOOP(86400);
|
||||
|
||||
puts(_("\nTesting clock date display for 31 days, yy-mm-dd"));
|
||||
TEST(date_format, LIBX52_DATE_FORMAT_YYMMDD);
|
||||
TEST_CLOCK_LOOP(86400);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Saitek X52 Pro MFD & LED driver
|
||||
*
|
||||
* Copyright (C) 2012-2015 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#ifndef X52_TEST_COMMON_H
|
||||
#define X52_TEST_COMMON_H
|
||||
|
||||
#include "config.h"
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include "libx52.h"
|
||||
#include "gettext.h"
|
||||
|
||||
extern libx52_device *dev;
|
||||
extern int test_exit;
|
||||
extern bool nodelay;
|
||||
|
||||
#define TEST(tst, ...) do { \
|
||||
int rc; \
|
||||
rc = ( libx52_set_ ## tst (dev, __VA_ARGS__) ); \
|
||||
if (rc) { \
|
||||
fprintf(stderr, _("\n%s(%s) failed with %d(%s)\n"), #tst, #__VA_ARGS__, rc, libx52_strerror(rc)); \
|
||||
return rc; \
|
||||
} \
|
||||
if (test_exit) return test_exit; \
|
||||
rc = libx52_update(dev); \
|
||||
if (rc) { \
|
||||
fprintf(stderr, _("\nupdate failed with %d(%s)\n"), rc, libx52_strerror(rc)); \
|
||||
return rc; \
|
||||
} \
|
||||
if (test_exit) return test_exit; \
|
||||
} while (0)
|
||||
|
||||
/* For i18n */
|
||||
#define _(x) gettext(x)
|
||||
|
||||
void test_cleanup(void);
|
||||
|
||||
void print_banner(const char *message);
|
||||
|
||||
int test_brightness(void);
|
||||
int test_mfd_display(void);
|
||||
int test_mfd_text(void);
|
||||
int test_leds(void);
|
||||
int test_blink_n_shift(void);
|
||||
int test_clock(void);
|
||||
|
||||
#endif /* X52_TEST_COMMON_H */
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* Saitek X52 Pro MFD & LED driver
|
||||
*
|
||||
* Copyright (C) 2012-2015 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include "config.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "libx52.h"
|
||||
#include "x52_test_common.h"
|
||||
|
||||
#define TEST_LED(name, state) do { \
|
||||
printf(_("LED %s - %s\n"), #name, #state); \
|
||||
TEST(led_state, LIBX52_LED_ ## name, LIBX52_LED_STATE_ ## state); \
|
||||
if (!nodelay) usleep(500000); \
|
||||
} while(0)
|
||||
|
||||
#define TEST_LED_MONO(name) do { \
|
||||
printf(_("\nTesting LED %s\n"), #name); \
|
||||
if (!nodelay) sleep(2); \
|
||||
TEST_LED(name, OFF); \
|
||||
TEST_LED(name, ON); \
|
||||
} while(0)
|
||||
|
||||
#define TEST_LED_COLOR(name) do {\
|
||||
printf(_("\nTesting LED %s\n"), #name); \
|
||||
if (!nodelay) sleep(2); \
|
||||
TEST_LED(name, OFF); \
|
||||
TEST_LED(name, RED); \
|
||||
TEST_LED(name, AMBER); \
|
||||
TEST_LED(name, GREEN); \
|
||||
} while(0)
|
||||
|
||||
int test_leds(void)
|
||||
{
|
||||
print_banner(_("LEDs"));
|
||||
|
||||
{ /* Check if the connected device supports individual LED control */
|
||||
int rc;
|
||||
rc = libx52_check_feature(dev, LIBX52_FEATURE_LED);
|
||||
if (rc == LIBX52_ERROR_NOT_SUPPORTED) {
|
||||
puts(_("Skipping LED tests since the device does not support LED control"));
|
||||
return 0;
|
||||
} else if (rc != LIBX52_SUCCESS) {
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
puts(_("This cycles the LEDs through all possible states"));
|
||||
|
||||
TEST_LED_MONO(FIRE);
|
||||
TEST_LED_COLOR(A);
|
||||
TEST_LED_COLOR(B);
|
||||
TEST_LED_COLOR(D);
|
||||
TEST_LED_COLOR(E);
|
||||
TEST_LED_COLOR(T1);
|
||||
TEST_LED_COLOR(T2);
|
||||
TEST_LED_COLOR(T3);
|
||||
TEST_LED_COLOR(POV);
|
||||
TEST_LED_COLOR(CLUTCH);
|
||||
TEST_LED_MONO(THROTTLE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define TEST_BLINK_OR_SHIFT(type) do { \
|
||||
printf(_("\nTesting %s\n"), #type); \
|
||||
if (!nodelay) sleep(1); \
|
||||
puts(#type " ON"); \
|
||||
TEST(type, 1); \
|
||||
if (!nodelay) sleep(2); \
|
||||
puts(#type " OFF"); \
|
||||
TEST(type, 0); \
|
||||
} while (0)
|
||||
|
||||
int test_blink_n_shift(void)
|
||||
{
|
||||
print_banner(_("Blink & Shift"));
|
||||
puts(_("This tests the blink indicator and shift functionality"));
|
||||
|
||||
TEST_BLINK_OR_SHIFT(blink);
|
||||
TEST_BLINK_OR_SHIFT(shift);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* Saitek X52 Pro MFD & LED driver
|
||||
*
|
||||
* Copyright (C) 2012-2015 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include "config.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "libx52.h"
|
||||
#include "x52_test_common.h"
|
||||
|
||||
#define TEST_BRIGHTNESS(mfd, value) TEST(brightness, mfd, value)
|
||||
|
||||
int test_brightness(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
print_banner(_("Brightness"));
|
||||
|
||||
puts(_("This test cycles through the MFD and LED brightness scales"));
|
||||
|
||||
fputs("\n |+---+---+---+---+---+---+---+---+|", stdout);
|
||||
fputs(_("\nMFD: "), stdout);
|
||||
for (i = 0; i < 129; i++) {
|
||||
if (!(i & 3)) fputs("#", stdout);
|
||||
fflush(stdout);
|
||||
TEST_BRIGHTNESS(1, i);
|
||||
if (!nodelay) usleep(250000);
|
||||
}
|
||||
fputs(_("\nLED: "), stdout);
|
||||
for (i = 0; i < 129; i++) {
|
||||
if (!(i & 3)) fputs("#", stdout);
|
||||
fflush(stdout);
|
||||
TEST_BRIGHTNESS(0, i);
|
||||
if (!nodelay) usleep(250000);
|
||||
}
|
||||
|
||||
fputs("\n\n", stdout);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int test_mfd_text(void)
|
||||
{
|
||||
int i;
|
||||
int j;
|
||||
char str[32];
|
||||
|
||||
print_banner(_("MFD text"));
|
||||
puts(_("This test tests the character displays of the MFD\n"));
|
||||
|
||||
for (i = 0; i < 256; i += 16) {
|
||||
j = snprintf(str, 16, "0x%02x - 0x%02x", i, i + 0xf);
|
||||
printf(_("Writing characters %s\n"), str);
|
||||
TEST(text, 0, str, j);
|
||||
|
||||
memset(str, ' ', 32);
|
||||
for (j = 0; j < 16; j++) {
|
||||
str[j << 1] = i + j;
|
||||
}
|
||||
|
||||
TEST(text, 1, str, 16);
|
||||
TEST(text, 2, str + 16, 16);
|
||||
|
||||
if (!nodelay) sleep(2);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int test_mfd_display(void)
|
||||
{
|
||||
int i;
|
||||
char str[16];
|
||||
|
||||
print_banner(_("MFD display"));
|
||||
puts(_("This test checks if the display elements can display all characters\n"
|
||||
"You should see the display cycling through each character, with every\n"
|
||||
"cell displaying the same character\n"));
|
||||
|
||||
for (i = 0; i < 256; i++) {
|
||||
printf(_("Testing character 0x%02x..."), i);
|
||||
|
||||
memset(str, i, 16);
|
||||
|
||||
TEST(text, 0, str, 16);
|
||||
TEST(text, 1, str, 16);
|
||||
TEST(text, 2, str, 16);
|
||||
|
||||
puts(_("OK"));
|
||||
if (!nodelay) usleep(500000);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue