mirror of
https://gitlab.com/asus-linux/asusctl.git
synced 2026-01-22 17:33:19 +01:00
Compare commits
802 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f5d5681b49 | ||
|
|
1c8e50843b | ||
|
|
487d140bd5 | ||
|
|
661ea8d3bf | ||
|
|
28d1ed6ab3 | ||
|
|
903b978e86 | ||
|
|
519f6bd46b | ||
|
|
a94a8ca28d | ||
|
|
f9dca2da5d | ||
|
|
df88ff1acb | ||
|
|
cb5aa0f170 | ||
|
|
4ea79f966e | ||
|
|
b8bc1a01b3 | ||
|
|
0e5d1815bd | ||
|
|
64e8cb65d0 | ||
|
|
3142353f98 | ||
|
|
484ca692ad | ||
|
|
1ebdfada96 | ||
|
|
3bc9dfcda1 | ||
|
|
895e5d2ca3 | ||
|
|
564992719e | ||
|
|
a737d240be | ||
|
|
d89c1ebf26 | ||
|
|
be7686bb46 | ||
|
|
4f70055f85 | ||
|
|
91ca049298 | ||
|
|
635d0378ac | ||
|
|
1c729316f7 | ||
|
|
4701c019a8 | ||
|
|
ca0d8bda4b | ||
|
|
a271ffbb10 | ||
|
|
00babaf949 | ||
|
|
2f844ac151 | ||
|
|
5178bf1d1a | ||
|
|
116afb9b6c | ||
|
|
4468a58487 | ||
|
|
2f73577e91 | ||
|
|
c1cffc8f59 | ||
|
|
6d8f85c154 | ||
|
|
0674e7f61c | ||
|
|
fde2f3ba15 | ||
|
|
70493d1a93 | ||
|
|
c20d0a76a0 | ||
|
|
e6952e241a | ||
|
|
b40812928a | ||
|
|
8cdc9773c9 | ||
|
|
8d30282edf | ||
|
|
4ba44560a9 | ||
|
|
cdc9ca7b58 | ||
|
|
7eae7c5664 | ||
|
|
739a0ffa63 | ||
|
|
637360095c | ||
|
|
4b34ab83fb | ||
|
|
ac605cbc00 | ||
|
|
4b38e5daa6 | ||
|
|
1c007b4216 | ||
|
|
193f9dfa1e | ||
|
|
1366422d96 | ||
|
|
4e778a3d28 | ||
|
|
be05508110 | ||
|
|
9119229d41 | ||
|
|
5c43c31331 | ||
|
|
014604724f | ||
|
|
7d076368e9 | ||
|
|
5d6ed5c365 | ||
|
|
a2b8f0f93c | ||
|
|
5fe8416c65 | ||
|
|
1b4d7a95af | ||
|
|
e8627fde4c | ||
|
|
6b0edc6da1 | ||
|
|
f6ad631a0f | ||
|
|
f6393a3926 | ||
|
|
d51384c3a1 | ||
|
|
78f18959fb | ||
|
|
7a661a585e | ||
|
|
f4f7a1e648 | ||
|
|
b6e3e5e823 | ||
|
|
41b1bd23d6 | ||
|
|
69458a0595 | ||
|
|
5fd107df27 | ||
|
|
2558057e9f | ||
|
|
8111daaf1d | ||
|
|
672acb234f | ||
|
|
9725062fb9 | ||
|
|
c7b1624313 | ||
|
|
67b97f1d43 | ||
|
|
6498fd1349 | ||
|
|
e371229b6c | ||
|
|
0fac33a8ff | ||
|
|
b0da062577 | ||
|
|
ca41bd59de | ||
|
|
efcad3f6f9 | ||
|
|
fa2255cbaf | ||
|
|
02b9bac899 | ||
|
|
a1fcf5023c | ||
|
|
2f8ea80e6d | ||
|
|
b798cf6a4e | ||
|
|
3da848d131 | ||
|
|
a88c33c201 | ||
|
|
7b0f037cba | ||
|
|
91b1456d06 | ||
|
|
c3b02a2bb0 | ||
|
|
8e4b7d53f4 | ||
|
|
a44145f487 | ||
|
|
bb7b3a81fb | ||
|
|
19607d71c3 | ||
|
|
96f281d789 | ||
|
|
7613eded95 | ||
|
|
50eccd2b1d | ||
|
|
ba54007102 | ||
|
|
a028f5375f | ||
|
|
9ec02cd727 | ||
|
|
4b46ece09a | ||
|
|
086bbd0908 | ||
|
|
c94eaa473e | ||
|
|
b1b809834b | ||
|
|
84183288ec | ||
|
|
86cbef83b6 | ||
|
|
006fb632c4 | ||
|
|
e3636ed8ce | ||
|
|
cfd207f251 | ||
|
|
d4c68546e7 | ||
|
|
6f4a7e16dc | ||
|
|
f64253d633 | ||
|
|
124c17aadc | ||
|
|
ab40f9fcbf | ||
|
|
5cdfa5a8d4 | ||
|
|
ce870cd5ed | ||
|
|
4541d2e1ba | ||
|
|
b525411fd3 | ||
|
|
a867496f13 | ||
|
|
f421b8ee3b | ||
|
|
82780feb4b | ||
|
|
1e5443e206 | ||
|
|
027a591d26 | ||
|
|
e90375828d | ||
|
|
75b4d67072 | ||
|
|
9aa332de3b | ||
|
|
5efd7fc6a7 | ||
|
|
0aafe24a02 | ||
|
|
dda6d343d9 | ||
|
|
6f39307080 | ||
|
|
ef63789faa | ||
|
|
c422e77ba6 | ||
|
|
3c234dd3c4 | ||
|
|
c420dd820a | ||
|
|
a3e6fec163 | ||
|
|
9b4e76be87 | ||
|
|
7b2125cbdf | ||
|
|
694a644cc6 | ||
|
|
922aa0c352 | ||
|
|
c06f78990f | ||
|
|
5c8bb6e6ea | ||
|
|
993131d0a9 | ||
|
|
0a69c23288 | ||
|
|
f6e4cc0626 | ||
|
|
1f696508e7 | ||
|
|
fa043adc99 | ||
|
|
b9c2d929b3 | ||
|
|
eda1e920df | ||
|
|
0de2c9e424 | ||
|
|
e88e7be8ae | ||
|
|
e470d3acc0 | ||
|
|
f5b3f0bc38 | ||
|
|
19497c94e0 | ||
|
|
670eee23e8 | ||
|
|
26309776e9 | ||
|
|
a7d5057976 | ||
|
|
8eb9b1d4eb | ||
|
|
71ee9e43ba | ||
|
|
f1b0e1288a | ||
|
|
35c7fd10b3 | ||
|
|
4c50dc259c | ||
|
|
0fd0aeff88 | ||
|
|
fd37f41ef1 | ||
|
|
1307997122 | ||
|
|
85187d2d8d | ||
|
|
e29a568195 | ||
|
|
6c375a9951 | ||
|
|
4641e19c43 | ||
|
|
91f0c2ea14 | ||
|
|
b4a8cb9de2 | ||
|
|
67bbcdb964 | ||
|
|
8acfe0a9e8 | ||
|
|
5f6e6ec382 | ||
|
|
cf92526d87 | ||
|
|
f290594562 | ||
|
|
423bd54f79 | ||
|
|
ea0eaef8a6 | ||
|
|
ef62a26148 | ||
|
|
2f916fa4e5 | ||
|
|
e42fd10404 | ||
|
|
93edd1b632 | ||
|
|
6957a08d83 | ||
|
|
98569b98e7 | ||
|
|
573568d6e2 | ||
|
|
3ec37a4dac | ||
|
|
11483b28a6 | ||
|
|
2cce83d164 | ||
|
|
42b92f3b87 | ||
|
|
b652fd15a2 | ||
|
|
9154aaa97c | ||
|
|
9e65921c0a | ||
|
|
8d8b5e5f51 | ||
|
|
9418b63454 | ||
|
|
2d396a49da | ||
|
|
a6d89a622b | ||
|
|
51bcb0082b | ||
|
|
eb54250e4d | ||
|
|
eafc831231 | ||
|
|
c625e97b7b | ||
|
|
fea56879d6 | ||
|
|
03052e129b | ||
|
|
3dbc893905 | ||
|
|
50152961c7 | ||
|
|
8be0e7e6bf | ||
|
|
d6d4a00fc3 | ||
|
|
611140716c | ||
|
|
a09f7b5275 | ||
|
|
823958492e | ||
|
|
668135eab3 | ||
|
|
622e07505d | ||
|
|
5c159d2294 | ||
|
|
6cfa09a02b | ||
|
|
95666cc40b | ||
|
|
458d58c87c | ||
|
|
7328ebdda3 | ||
|
|
14d043bbc3 | ||
|
|
98dec6403c | ||
|
|
3546f5bd21 | ||
|
|
db1683de46 | ||
|
|
669c8ac3c7 | ||
|
|
4f3a6ce1c6 | ||
|
|
6d3918ccf0 | ||
|
|
b002187b39 | ||
|
|
13965b2261 | ||
|
|
b182fbd323 | ||
|
|
19b28f202c | ||
|
|
ed51a7fa14 | ||
|
|
c94358a8f3 | ||
|
|
2a8ca0a39a | ||
|
|
83455a5ba3 | ||
|
|
bc776deb70 | ||
|
|
59b1059aee | ||
|
|
dbe80d1914 | ||
|
|
e325ebed18 | ||
|
|
a743bda6e0 | ||
|
|
15e6782e10 | ||
|
|
6b058c9922 | ||
|
|
fc8879ac24 | ||
|
|
4d2d5707a1 | ||
|
|
439c830311 | ||
|
|
91223d4ced | ||
|
|
7b17a13ce7 | ||
|
|
fca7d23a31 | ||
|
|
0122138b3b | ||
|
|
7b495e7587 | ||
|
|
970cf9ae59 | ||
|
|
ad990c6ae1 | ||
|
|
cd8cc013a4 | ||
|
|
acf41c1783 | ||
|
|
03c9f06569 | ||
|
|
036a5018e0 | ||
|
|
81529b7374 | ||
|
|
2289af3ef6 | ||
|
|
633ffdf962 | ||
|
|
cb88c9f0e2 | ||
|
|
8b77078a6f | ||
|
|
3d6d92ae7d | ||
|
|
55723b7b77 | ||
|
|
7796ba0603 | ||
|
|
d3aababef5 | ||
|
|
4611c08085 | ||
|
|
0a008a653a | ||
|
|
cd5daa17d0 | ||
|
|
a0529e0efd | ||
|
|
3e0aeea6c6 | ||
|
|
e2fb1d44b5 | ||
|
|
04543eeca0 | ||
|
|
68ee62fef1 | ||
|
|
e523e4e9a2 | ||
|
|
ea2d80cc44 | ||
|
|
40e00c4739 | ||
|
|
cdc42193d1 | ||
|
|
3a18506510 | ||
|
|
fa671e53d8 | ||
|
|
002dc8516d | ||
|
|
2a38f69cc4 | ||
|
|
a14a37d0da | ||
|
|
b105ff5180 | ||
|
|
d202fcd97a | ||
|
|
15732ecd82 | ||
|
|
8508110ba0 | ||
|
|
cafb64d57b | ||
|
|
7515eafc45 | ||
|
|
b6c6f10bdf | ||
|
|
35352a8a7c | ||
|
|
0c3bebdeb9 | ||
|
|
1394c12967 | ||
|
|
cbc1f6f5bb | ||
|
|
7ae0f896cf | ||
|
|
fb0374512d | ||
|
|
14f031ad34 | ||
|
|
bee5508099 | ||
|
|
c741204200 | ||
|
|
858c9841a7 | ||
|
|
fdc7d88a70 | ||
|
|
da3017bb89 | ||
|
|
641e762e80 | ||
|
|
25ecfda095 | ||
|
|
31af8f9511 | ||
|
|
8db783d9b4 | ||
|
|
45a354880a | ||
|
|
ca1c67e803 | ||
|
|
c819fa458a | ||
|
|
869ab90299 | ||
|
|
c40029f5e7 | ||
|
|
e864dfb0e7 | ||
|
|
476b394add | ||
|
|
4ea5480e66 | ||
|
|
cfc46a2b70 | ||
|
|
235763a615 | ||
|
|
6e19c16e70 | ||
|
|
6ea550b6ff | ||
|
|
dd30c8092b | ||
|
|
2bd751f841 | ||
|
|
7a6aafded7 | ||
|
|
940b93a75f | ||
|
|
5c70fec29a | ||
|
|
8ac505e0dd | ||
|
|
3bdb03b1d8 | ||
|
|
4ac4909881 | ||
|
|
ec5e6d2e7c | ||
|
|
5600c51ba0 | ||
|
|
cb5856c4dc | ||
|
|
aad6f6350b | ||
|
|
1ec45a6449 | ||
|
|
bb612283fe | ||
|
|
bcba11d4ec | ||
|
|
7eab94bc7f | ||
|
|
e73bbedb41 | ||
|
|
a83ccbd33d | ||
|
|
b5b7799018 | ||
|
|
24ecb92621 | ||
|
|
a811417f5d | ||
|
|
b012a01cad | ||
|
|
e6a9c88695 | ||
|
|
048a7afa55 | ||
|
|
53b854ef6d | ||
|
|
fac635d07d | ||
|
|
9cc62d63c9 | ||
|
|
ab3007d53d | ||
|
|
00839aaa6f | ||
|
|
5133d398eb | ||
|
|
90b711c7b9 | ||
|
|
ea5e5db490 | ||
|
|
ef6ca9e51e | ||
|
|
022a144705 | ||
|
|
8011ba3009 | ||
|
|
d93b870726 | ||
|
|
e4f79a3e6f | ||
|
|
54273cfb60 | ||
|
|
0dd4b2d6b5 | ||
|
|
a2d850bbcb | ||
|
|
19f82493de | ||
|
|
cbce854d1b | ||
|
|
ee0600d50d | ||
|
|
3d145ab9bd | ||
|
|
1cbffedaeb | ||
|
|
e3ecaa92bd | ||
|
|
067738b94f | ||
|
|
29b22cd18e | ||
|
|
c2aa81bfe3 | ||
|
|
5e9c612269 | ||
|
|
8dcb209026 | ||
|
|
bdb6c5b2ff | ||
|
|
a318fbceec | ||
|
|
8feacf863a | ||
|
|
0c62582515 | ||
|
|
3c575e4d2a | ||
|
|
dbfd73da5e | ||
|
|
b1ee449b97 | ||
|
|
245c035dc9 | ||
|
|
07daa0df61 | ||
|
|
c7893b16f9 | ||
|
|
8e8681c190 | ||
|
|
b26c6a55f0 | ||
|
|
93d472fe74 | ||
|
|
5469c73f11 | ||
|
|
ad95765954 | ||
|
|
e42a5bc3e9 | ||
|
|
28347e87eb | ||
|
|
b34cb672c3 | ||
|
|
559ddc9a22 | ||
|
|
a8c014881f | ||
|
|
f417032ed9 | ||
|
|
616fb3aea6 | ||
|
|
6e6e057995 | ||
|
|
085e63ebab | ||
|
|
fdadffcdde | ||
|
|
5f51527dd7 | ||
|
|
39525980a0 | ||
|
|
83a0b570e0 | ||
|
|
2bfbce36b0 | ||
|
|
2705b08dca | ||
|
|
2fca7a09c4 | ||
|
|
ef0da62c55 | ||
|
|
9dab120bcf | ||
|
|
14bf07ba79 | ||
|
|
e76d01eaed | ||
|
|
072a066f28 | ||
|
|
165c6f8ab3 | ||
|
|
5728a9af62 | ||
|
|
17a880b2c5 | ||
|
|
6ba9b9d75d | ||
|
|
eb1f6c83ce | ||
|
|
af653ea405 | ||
|
|
bc13891cdf | ||
|
|
aad4dc909e | ||
|
|
ad79adcbfa | ||
|
|
73b1a7050a | ||
|
|
762bfea102 | ||
|
|
b41fdf5cfe | ||
|
|
bf13ebebd3 | ||
|
|
3a73e3a526 | ||
|
|
1211400d7b | ||
|
|
ff9edb9876 | ||
|
|
20f8251dd3 | ||
|
|
902dfed67c | ||
|
|
5e08b4416d | ||
|
|
44c3ab7294 | ||
|
|
be40474f79 | ||
|
|
3654da2ff8 | ||
|
|
60bbad4ab6 | ||
|
|
0a59d5c041 | ||
|
|
1506fc44d3 | ||
|
|
aad31610f2 | ||
|
|
a6fe7645e9 | ||
|
|
4f8745ae19 | ||
|
|
5f4e950819 | ||
|
|
efc752cce6 | ||
|
|
37553a5fdd | ||
|
|
cd5a85a843 | ||
|
|
7385844a9b | ||
|
|
0b71104833 | ||
|
|
688e3a7358 | ||
|
|
58ff566d65 | ||
|
|
1332ac803c | ||
|
|
ba1d3f045d | ||
|
|
e0ed52092a | ||
|
|
921637f979 | ||
|
|
f6498337fe | ||
|
|
3a640a3269 | ||
|
|
e938f1f9ec | ||
|
|
600d0ae3d9 | ||
|
|
8569edf684 | ||
|
|
52af4ad2b2 | ||
|
|
cde1b4f252 | ||
|
|
2a4754cfc4 | ||
|
|
51c97fa350 | ||
|
|
c968dce009 | ||
|
|
b2b6707f2e | ||
|
|
7939b00aa3 | ||
|
|
30550aaa91 | ||
|
|
7ea1f41286 | ||
|
|
9608d190b9 | ||
|
|
3b9cf474a7 | ||
|
|
283cb7e589 | ||
|
|
5d87747d96 | ||
|
|
56285916cd | ||
|
|
a44a1bfa89 | ||
|
|
0c97cf710d | ||
|
|
62c7338b2d | ||
|
|
af24623178 | ||
|
|
facb7f7f49 | ||
|
|
e38ab624e9 | ||
|
|
d76cb3b95a | ||
|
|
910f529a9b | ||
|
|
7583d070d3 | ||
|
|
1f85e30e42 | ||
|
|
d1bdf4dc7e | ||
|
|
79b108ceb7 | ||
|
|
7d14e8d900 | ||
|
|
493d61cf19 | ||
|
|
fb08d83999 | ||
|
|
71241b7127 | ||
|
|
09963534d8 | ||
|
|
64322044ac | ||
|
|
1a132d847f | ||
|
|
952a974e83 | ||
|
|
ebbfa58a76 | ||
|
|
414d610bd2 | ||
|
|
bff98ddf7b | ||
|
|
97481cd45e | ||
|
|
4f39c01139 | ||
|
|
40987ecd5d | ||
|
|
f378c54815 | ||
|
|
a8a99ac1d1 | ||
|
|
503aa20257 | ||
|
|
8c67836650 | ||
|
|
3fc839820e | ||
|
|
0ef524a94b | ||
|
|
f8cfacda47 | ||
|
|
f3876100ae | ||
|
|
fa1feaf9d9 | ||
|
|
45641c928d | ||
|
|
eba9dc8a52 | ||
|
|
a32527d1df | ||
|
|
1f697b5ff1 | ||
|
|
92009ef96c | ||
|
|
3fe5896596 | ||
|
|
f8cdde2adf | ||
|
|
033f2141ef | ||
|
|
f86bab6f8c | ||
|
|
4951bce961 | ||
|
|
fb92d65fa0 | ||
|
|
24fa075a44 | ||
|
|
a0f7cf3acd | ||
|
|
d35707f2e4 | ||
|
|
b20bde8116 | ||
|
|
308fba9413 | ||
|
|
45268bfb2b | ||
|
|
004982cea7 | ||
|
|
5ab24a624e | ||
|
|
700633e080 | ||
|
|
773c9902a5 | ||
|
|
e05d5bd143 | ||
|
|
3e244d7d3d | ||
|
|
ba4589f986 | ||
|
|
083134fc73 | ||
|
|
3cc04fba60 | ||
|
|
3a00e4f1a3 | ||
|
|
eb78fb613c | ||
|
|
d0b9aee85a | ||
|
|
3e94ef05fb | ||
|
|
fbb025875b | ||
|
|
ae816bd13c | ||
|
|
14f0693511 | ||
|
|
de7fb4a942 | ||
|
|
4164b4645d | ||
|
|
649b14fd0d | ||
|
|
6d97ef13a1 | ||
|
|
7abad979c8 | ||
|
|
0ec1574219 | ||
|
|
03042dd5c3 | ||
|
|
3330e4973f | ||
|
|
5e06aeabe9 | ||
|
|
e99d8766fc | ||
|
|
8f65b7e334 | ||
|
|
5a54b830bf | ||
|
|
85e08510f7 | ||
|
|
d56eeb7fb2 | ||
|
|
bbc520a7f2 | ||
|
|
10e43c64ca | ||
|
|
38be25174a | ||
|
|
2584d69930 | ||
|
|
523f39cf9c | ||
|
|
669760223e | ||
|
|
71ec13fa9f | ||
|
|
409528b286 | ||
|
|
17df3cf01d | ||
|
|
808a1d2470 | ||
|
|
840c500b5e | ||
|
|
42dc360d16 | ||
|
|
f6183597c9 | ||
|
|
79a45c4f10 | ||
|
|
19370215c0 | ||
|
|
030dd661b8 | ||
|
|
1fc12d9855 | ||
|
|
6c1b2b70ea | ||
|
|
23f9af35bf | ||
|
|
526626b80c | ||
|
|
10eaaac54b | ||
|
|
901a3ddcc9 | ||
|
|
e6ebf72a11 | ||
|
|
cd7e748c88 | ||
|
|
a313359ef6 | ||
|
|
f222eef6b7 | ||
|
|
e6f3aeb851 | ||
|
|
22605e57cc | ||
|
|
02fb7addf4 | ||
|
|
bdbb403a0e | ||
|
|
7a8bede92f | ||
|
|
a71a40b509 | ||
|
|
42fc5a5392 | ||
|
|
5017a0ea9b | ||
|
|
05f7b0060f | ||
|
|
1043da5328 | ||
|
|
959ad35afa | ||
|
|
1e10255d01 | ||
|
|
c72693bc53 | ||
|
|
2297aad5e5 | ||
|
|
f39c0db680 | ||
|
|
23353c77f3 | ||
|
|
fee1486db6 | ||
|
|
39c4253b24 | ||
|
|
84e924f46a | ||
|
|
43364398b4 | ||
|
|
3f09f221f4 | ||
|
|
4ed922154b | ||
|
|
c0e36295b7 | ||
|
|
ef04549c8e | ||
|
|
a117c300d5 | ||
|
|
6956f7dffc | ||
|
|
fe49913550 | ||
|
|
89ddade2a3 | ||
|
|
bcb3d53ade | ||
|
|
9ab9028d79 | ||
|
|
8de6447562 | ||
|
|
2512cea19d | ||
|
|
eabe78af71 | ||
|
|
16c4ee609e | ||
|
|
37d586c5de | ||
|
|
e66847c263 | ||
|
|
a2c8a226a4 | ||
|
|
f3f6fadfe2 | ||
|
|
ff76c356c5 | ||
|
|
a22808aff3 | ||
|
|
f39fd6dfbb | ||
|
|
95598f2a76 | ||
|
|
535e104ccc | ||
|
|
522cee42e5 | ||
|
|
51656dc13f | ||
|
|
abd412b5d5 | ||
|
|
4d8c05cd92 | ||
|
|
f2ca8081af | ||
|
|
7539011be5 | ||
|
|
5117427143 | ||
|
|
e95986b830 | ||
|
|
5311972345 | ||
|
|
967295fba7 | ||
|
|
5403c5fb4f | ||
|
|
65986c3114 | ||
|
|
13a90b00f3 | ||
|
|
2ee7fc9910 | ||
|
|
a0a0efabbb | ||
|
|
9a50278b98 | ||
|
|
9519a35e32 | ||
|
|
578d5fd541 | ||
|
|
642bc5dda1 | ||
|
|
88274abdb5 | ||
|
|
aea65f5c5f | ||
|
|
edfbfde13b | ||
|
|
dcc676d60a | ||
|
|
561f61116c | ||
|
|
6a4594466b | ||
|
|
af216ee08c | ||
|
|
e493113450 | ||
|
|
74e1d5bdc4 | ||
|
|
5c0ad3e590 | ||
|
|
6e872ecab9 | ||
|
|
46a4cde77f | ||
|
|
fc7c444107 | ||
|
|
822438e0d2 | ||
|
|
de43a37e9e | ||
|
|
d4b2d2f403 | ||
|
|
6e33eab136 | ||
|
|
1e4bc85fee | ||
|
|
31fff75f08 | ||
|
|
f0620154c8 | ||
|
|
711aa1e4be | ||
|
|
854f2d75b3 | ||
|
|
a85e2f6130 | ||
|
|
bac2ba6f09 | ||
|
|
47e5270f9c | ||
|
|
68cbf09e9f | ||
|
|
9f18c88153 | ||
|
|
c6caafdcb7 | ||
|
|
b22a3e1a59 | ||
|
|
b6934bbf63 | ||
|
|
3cd6eb13a9 | ||
|
|
272be2aaad | ||
|
|
99dd6ce77f | ||
|
|
fc14455da4 | ||
|
|
26a52dae23 | ||
|
|
75864d33a6 | ||
|
|
63a97b6665 | ||
|
|
21a37a3bb0 | ||
|
|
de586b5368 | ||
|
|
ba40c3f739 | ||
|
|
31eff037a2 | ||
|
|
630dee0b2a | ||
|
|
9110f06ed5 | ||
|
|
2b0eceaa9d | ||
|
|
c96e1babe5 | ||
|
|
9388cbde5d | ||
|
|
e739cddd6a | ||
|
|
8cee6e0fc4 | ||
|
|
bcf516afeb | ||
|
|
b0e3e81b7f | ||
|
|
ce6a1215a3 | ||
|
|
f54c1dc7d0 | ||
|
|
43aaae8d47 | ||
|
|
ca463a2944 | ||
|
|
20e22589dc | ||
|
|
38d047cb8a | ||
|
|
1d977199f3 | ||
|
|
5041019d77 | ||
|
|
aa3835d3b3 | ||
|
|
678505811d | ||
|
|
a925cbaed5 | ||
|
|
7d2201d873 | ||
|
|
c0c1608d44 | ||
|
|
7bc6c83a04 | ||
|
|
3f0df82f2d | ||
|
|
328ff0251b | ||
|
|
c52582a413 | ||
|
|
3aa6eee306 | ||
|
|
7d47faba0e | ||
|
|
f6fb477898 | ||
|
|
8963960d4b | ||
|
|
ef973f676b | ||
|
|
812f9ea30e | ||
|
|
ff843b1241 | ||
|
|
59e7af149d | ||
|
|
6f14c85287 | ||
|
|
ac0dec4dbf | ||
|
|
e3d192412e | ||
|
|
9fadb6db30 | ||
|
|
f8a1b71866 | ||
|
|
c0a55acba7 | ||
|
|
4f232de634 | ||
|
|
d351ebdaa0 | ||
|
|
ab195e1d84 | ||
|
|
7041d77256 | ||
|
|
99dd052d54 | ||
|
|
de9942609e | ||
|
|
b939a9d331 | ||
|
|
0a565a7a5c | ||
|
|
bfaa478a4a | ||
|
|
f895a5623e | ||
|
|
b7c869bd64 | ||
|
|
5f677bc3b9 | ||
|
|
ccfadc2fcb | ||
|
|
c6cc304a42 | ||
|
|
3d41a7978a | ||
|
|
43fc467d58 | ||
|
|
6aba60f604 | ||
|
|
a13dedf500 | ||
|
|
cb490f23e9 | ||
|
|
7dc8a743b9 | ||
|
|
52f3b5a7bf | ||
|
|
e89e7ca10f | ||
|
|
2431dd9e93 | ||
|
|
453d3091c1 | ||
|
|
8db37491f3 | ||
|
|
cf915b9e00 | ||
|
|
326ca37847 | ||
|
|
498e604531 | ||
|
|
60b7f3be69 | ||
|
|
6ceb5cf939 | ||
|
|
0ed97db4c1 | ||
|
|
8fcd05c2bb | ||
|
|
6de4590f27 | ||
|
|
b097fd4da9 | ||
|
|
2a8e05707d | ||
|
|
a54e112978 | ||
|
|
5785eb981d | ||
|
|
49234af08b | ||
|
|
8f5717def8 | ||
|
|
9e55c0b2ca | ||
|
|
dd6ee91364 | ||
|
|
efbb838ca1 | ||
|
|
daf7d39d41 | ||
|
|
28b1194924 | ||
|
|
73d95ca187 | ||
|
|
00b7c6482f | ||
|
|
29c26e8c89 | ||
|
|
fb124dd228 | ||
|
|
0599be02dc | ||
|
|
81a88263a9 | ||
|
|
cea1fd2540 | ||
|
|
f2ced3bc7c | ||
|
|
dc2a05894b | ||
|
|
9ee962ad09 | ||
|
|
eb7ef0af4f | ||
|
|
dc51120c27 | ||
|
|
fc4c2c4346 | ||
|
|
3f6be037c1 | ||
|
|
0a80c97f02 | ||
|
|
88abafc728 | ||
|
|
ac880a0363 | ||
|
|
baebd51d99 | ||
|
|
226620eb53 | ||
|
|
1b34079d14 | ||
|
|
8deeffcdad | ||
|
|
b7e45d7305 | ||
|
|
d9077db234 | ||
|
|
439b006342 | ||
|
|
ffa74d52e5 | ||
|
|
6ccdd703e6 | ||
|
|
bb910344b8 | ||
|
|
b9c4ff9ca7 | ||
|
|
62a18d4e57 | ||
|
|
f520e381a9 | ||
|
|
1dd543ddf3 | ||
|
|
a7ef63bd8a | ||
|
|
db43c0f2a4 | ||
|
|
f0e5bb4ad1 | ||
|
|
36bba75c50 | ||
|
|
b2dc610c0b | ||
|
|
42d0eb0aba |
18
.cargo-husky/hooks/pre-commit
Executable file
18
.cargo-husky/hooks/pre-commit
Executable file
@@ -0,0 +1,18 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
echo 'find -name \*.slint | xargs slint-tr-extractor -o rog-control-center/translations/en/rog-control-center.po'
|
||||
find -name \*.slint | xargs slint-tr-extractor -o rog-control-center/translations/en/rog-control-center.po
|
||||
|
||||
echo '+cargo +nightly fmt --all -- --check'
|
||||
cargo +nightly fmt --all -- --check
|
||||
|
||||
echo '+cargo clippy --all -- -D warnings'
|
||||
cargo clippy --all -- -D warnings
|
||||
|
||||
echo '+cargo test --all'
|
||||
cargo test --all
|
||||
|
||||
echo '+cargo cranky'
|
||||
cargo cranky
|
||||
10
.cargo-husky/hooks/pre-push
Executable file
10
.cargo-husky/hooks/pre-push
Executable file
@@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
echo '+cargo +nightly fmt --all -- --check'
|
||||
cargo +nightly fmt --all -- --check
|
||||
echo '+cargo clippy --all -- -D warnings'
|
||||
cargo clippy --all -- -D warnings
|
||||
echo '+cargo cranky'
|
||||
cargo cranky
|
||||
23
.editorconfig
Normal file
23
.editorconfig
Normal file
@@ -0,0 +1,23 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.rs]
|
||||
indent_size = 4
|
||||
|
||||
[tests/**/*.rs]
|
||||
charset = utf-8
|
||||
end_of_line = unset
|
||||
indent_size = unset
|
||||
indent_style = unset
|
||||
trim_trailing_whitespace = unset
|
||||
insert_final_newline = unset
|
||||
15
.gitignore
vendored
15
.gitignore
vendored
@@ -2,5 +2,20 @@
|
||||
vendor.tar.xz
|
||||
cargo-config
|
||||
.idea
|
||||
vendor
|
||||
vendor-*
|
||||
vendor_*
|
||||
.vscode-ctags
|
||||
.vscode
|
||||
.~lock.*
|
||||
*.ods#
|
||||
|
||||
# gnome extension
|
||||
node-modules
|
||||
bindings/ts/*.d.ts
|
||||
bindings/ts/*.js.map
|
||||
desktop-extensions/gnome*/dist
|
||||
desktop-extensions/gnome*/@types/gir-generated
|
||||
desktop-extensions/gnome*/node_modules
|
||||
desktop-extensions/gnome*/schemas/gschemas.compiled
|
||||
desktop-extensions/gnome*/*.zip
|
||||
|
||||
@@ -1,26 +1,85 @@
|
||||
image: rustdocker/rust:stable
|
||||
image: rust:latest
|
||||
|
||||
.rust_cache: &rust_cache
|
||||
cache:
|
||||
# key: $CI_COMMIT_REF_SLUG
|
||||
paths:
|
||||
# Don't include `incremental` to save space
|
||||
# Debug
|
||||
- target/debug/build/
|
||||
- target/debug/deps/
|
||||
- target/debug/.fingerprint/
|
||||
- target/debug/.cargo-lock
|
||||
# Release
|
||||
- target/release/build/
|
||||
- target/release/deps/
|
||||
- target/release/.fingerprint/
|
||||
- target/release/.cargo-lock
|
||||
|
||||
before_script:
|
||||
- apt-get update -qq && apt-get install -y -qq libdbus-1-dev libclang-dev libudev-dev
|
||||
- apt-get update -qq && apt-get install -y -qq libinput-dev libseat-dev libudev-dev libgtk-3-dev grep llvm clang libclang-dev libsdl2-dev libsdl2-gfx-dev
|
||||
|
||||
stages:
|
||||
- test
|
||||
- build
|
||||
- format
|
||||
- check
|
||||
- test
|
||||
- release
|
||||
- deploy
|
||||
|
||||
format:
|
||||
except:
|
||||
- tags
|
||||
<<: *rust_cache
|
||||
script:
|
||||
- echo "nightly" > rust-toolchain
|
||||
- rustup component add rustfmt
|
||||
- cargo fmt --check
|
||||
|
||||
check:
|
||||
except:
|
||||
- tags
|
||||
<<: *rust_cache
|
||||
script:
|
||||
- rustup component add clippy
|
||||
- cargo check
|
||||
# deny currently catches too much
|
||||
#- cargo install cargo-deny && cargo deny
|
||||
- cargo install cargo-cranky && cargo cranky
|
||||
|
||||
test:
|
||||
except:
|
||||
- tags
|
||||
<<: *rust_cache
|
||||
script:
|
||||
- cargo check #+nightly check --features "clippy"
|
||||
- mkdir -p .git/hooks > /dev/null
|
||||
- cargo test --all
|
||||
|
||||
build:
|
||||
release:
|
||||
only:
|
||||
- main
|
||||
- tags
|
||||
<<: *rust_cache
|
||||
script:
|
||||
- cargo install cargo-vendor-filterer
|
||||
- make && make vendor
|
||||
artifacts:
|
||||
paths:
|
||||
- vendor_asus-nb-ctrl_*.tar.xz
|
||||
- cargo-config
|
||||
- vendor_asusctl*.tar.xz
|
||||
- cargo-config
|
||||
|
||||
pages:
|
||||
stage: deploy
|
||||
only:
|
||||
- tags
|
||||
<<: *rust_cache
|
||||
script:
|
||||
- cargo doc --document-private-items --no-deps --workspace
|
||||
- rm -rf public
|
||||
- mkdir public
|
||||
- cp -R target/doc/* public
|
||||
- cp extra/index.html public
|
||||
artifacts:
|
||||
paths:
|
||||
- public
|
||||
|
||||
variables:
|
||||
GIT_SUBMODULE_STRATEGY: normal
|
||||
|
||||
|
||||
32
.gitlab/issue_templates/default.md
Normal file
32
.gitlab/issue_templates/default.md
Normal file
@@ -0,0 +1,32 @@
|
||||
## Issue description
|
||||
|
||||
(** I can not support distros which are outdated by default. This includes Ubuntu at least 50% of the time, and definitely includes Mint. **)
|
||||
(Summarize the bug encountered)
|
||||
|
||||
## Steps to reproduce
|
||||
|
||||
(How can the issue be reproduced)
|
||||
|
||||
## What is the current bug behavior?
|
||||
|
||||
(What actually happens)
|
||||
|
||||
## What is the expected correct behavior?
|
||||
|
||||
(What you should see instead)
|
||||
|
||||
## Relevant logs and/or screenshots
|
||||
|
||||
(run `journalctl -b -u asusd > ~/asusd.log` and attach `~/asusd.log`)
|
||||
|
||||
(Paste any relevant logs - use code blocks (```) to format console output, logs, and code, as
|
||||
it's very hard to read otherwise.)
|
||||
|
||||
## System details
|
||||
|
||||
- Distro:
|
||||
- Kernel: (`uname -r`)
|
||||
- Desktop:
|
||||
- Xorg or wayland: ??
|
||||
|
||||
/label ~bug ~reproducable ~needs-investigation
|
||||
805
CHANGELOG.md
805
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
5586
Cargo.lock
generated
5586
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
85
Cargo.toml
85
Cargo.toml
@@ -1,16 +1,93 @@
|
||||
[workspace]
|
||||
members = ["asusctl", "asus-notify", "daemon", "daemon-user", "rog-types", "rog-dbus", "rog-anime", "rog-aura", "rog-profiles"]
|
||||
members = [
|
||||
"asusctl",
|
||||
"asusd",
|
||||
"asusd-user",
|
||||
"config-traits",
|
||||
"cpuctl",
|
||||
"dmi-id",
|
||||
"rog-platform",
|
||||
"rog-dbus",
|
||||
"rog-anime",
|
||||
"rog-aura",
|
||||
"rog-profiles",
|
||||
"rog-control-center",
|
||||
"rog-slash",
|
||||
"simulators",
|
||||
]
|
||||
default-members = [
|
||||
"asusctl",
|
||||
"asusd",
|
||||
"asusd-user",
|
||||
"cpuctl",
|
||||
"rog-control-center",
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
version = "6.0.0"
|
||||
rust-version = "1.77"
|
||||
|
||||
[workspace.dependencies]
|
||||
tokio = { version = "^1.36.0", default-features = false, features = [
|
||||
"macros",
|
||||
"sync",
|
||||
"time",
|
||||
"rt-multi-thread",
|
||||
] }
|
||||
concat-idents = "^1.1"
|
||||
dirs = "^4.0"
|
||||
smol = "^1.3"
|
||||
mio = "0.8.11"
|
||||
|
||||
zbus = "~4.1.2"
|
||||
logind-zbus = { version = "~4.0.2" } #, default-features = false, features = ["non_blocking"] }
|
||||
|
||||
serde = "^1.0"
|
||||
serde_derive = "^1.0"
|
||||
serde_json = "^1.0"
|
||||
toml = "^0.5.10"
|
||||
ron = "*"
|
||||
typeshare = "1.0.0"
|
||||
|
||||
log = "^0.4"
|
||||
env_logger = "^0.10.0"
|
||||
|
||||
glam = { version = "^0.22", features = ["serde"] }
|
||||
gumdrop = "^0.8"
|
||||
udev = { version = "^0.8", features = ["mio"] }
|
||||
rusb = "^0.9"
|
||||
inotify = "^0.10.0"
|
||||
|
||||
png_pong = "^0.8"
|
||||
pix = "^0.13"
|
||||
tinybmp = "^0.4.0"
|
||||
gif = "^0.12.0"
|
||||
|
||||
versions = "4.1"
|
||||
|
||||
notify-rust = { git = "https://github.com/flukejones/notify-rust.git", rev = "54176413b81189a3e4edbdc20a0b4f7e2e35c063", default-features = false, features = [
|
||||
"z",
|
||||
] }
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
# thin = 57s, asusd = 9.0M
|
||||
# fat = 72s, asusd = 6.4M
|
||||
lto = "fat"
|
||||
debug = false
|
||||
opt-level = 3
|
||||
panic = "abort"
|
||||
codegen-units = 1
|
||||
|
||||
[profile.dev]
|
||||
debug = false
|
||||
opt-level = 1
|
||||
codegen-units = 16
|
||||
|
||||
[profile.bench]
|
||||
debug = false
|
||||
opt-level = 3
|
||||
opt-level = 3
|
||||
|
||||
[workspace.dependencies.cargo-husky]
|
||||
version = "1"
|
||||
default-features = false
|
||||
features = ["user-hooks"]
|
||||
|
||||
121
Cranky.toml
Normal file
121
Cranky.toml
Normal file
@@ -0,0 +1,121 @@
|
||||
# https://github.com/ericseppanen/cargo-cranky
|
||||
# cargo install cargo-cranky && cargo cranky
|
||||
|
||||
error = [
|
||||
"clippy::all",
|
||||
"clippy::await_holding_lock",
|
||||
"clippy::bool_to_int_with_if",
|
||||
"clippy::char_lit_as_u8",
|
||||
"clippy::checked_conversions",
|
||||
"clippy::dbg_macro",
|
||||
"clippy::debug_assert_with_mut_call",
|
||||
"clippy::disallowed_methods",
|
||||
"clippy::disallowed_script_idents",
|
||||
"clippy::doc_link_with_quotes",
|
||||
"clippy::doc_markdown",
|
||||
"clippy::empty_enum",
|
||||
"clippy::enum_glob_use",
|
||||
"clippy::equatable_if_let",
|
||||
"clippy::exit",
|
||||
"clippy::expl_impl_clone_on_copy",
|
||||
"clippy::explicit_deref_methods",
|
||||
"clippy::explicit_into_iter_loop",
|
||||
"clippy::explicit_iter_loop",
|
||||
"clippy::fallible_impl_from",
|
||||
"clippy::filter_map_next",
|
||||
"clippy::flat_map_option",
|
||||
"clippy::float_cmp_const",
|
||||
"clippy::fn_params_excessive_bools",
|
||||
"clippy::fn_to_numeric_cast_any",
|
||||
"clippy::from_iter_instead_of_collect",
|
||||
"clippy::if_let_mutex",
|
||||
"clippy::implicit_clone",
|
||||
"clippy::imprecise_flops",
|
||||
"clippy::index_refutable_slice",
|
||||
"clippy::inefficient_to_string",
|
||||
"clippy::invalid_upcast_comparisons",
|
||||
"clippy::iter_not_returning_iterator",
|
||||
"clippy::iter_on_empty_collections",
|
||||
"clippy::iter_on_single_items",
|
||||
"clippy::large_digit_groups",
|
||||
"clippy::large_stack_arrays",
|
||||
"clippy::large_types_passed_by_value",
|
||||
"clippy::let_unit_value",
|
||||
"clippy::linkedlist",
|
||||
"clippy::lossy_float_literal",
|
||||
"clippy::macro_use_imports",
|
||||
"clippy::manual_assert",
|
||||
"clippy::manual_instant_elapsed",
|
||||
"clippy::manual_ok_or",
|
||||
"clippy::manual_string_new",
|
||||
"clippy::map_err_ignore",
|
||||
"clippy::map_flatten",
|
||||
"clippy::map_unwrap_or",
|
||||
"clippy::match_on_vec_items",
|
||||
"clippy::match_same_arms",
|
||||
"clippy::match_wild_err_arm",
|
||||
"clippy::match_wildcard_for_single_variants",
|
||||
"clippy::mem_forget",
|
||||
"clippy::mismatched_target_os",
|
||||
"clippy::mismatching_type_param_order",
|
||||
"clippy::missing_enforced_import_renames",
|
||||
# "clippy::missing_errors_doc",
|
||||
"clippy::missing_safety_doc",
|
||||
"clippy::mut_mut",
|
||||
"clippy::mutex_integer",
|
||||
"clippy::needless_borrow",
|
||||
"clippy::needless_continue",
|
||||
"clippy::needless_for_each",
|
||||
"clippy::needless_pass_by_value",
|
||||
"clippy::negative_feature_names",
|
||||
"clippy::nonstandard_macro_braces",
|
||||
"clippy::option_option",
|
||||
"clippy::path_buf_push_overwrite",
|
||||
"clippy::ptr_as_ptr",
|
||||
"clippy::rc_mutex",
|
||||
"clippy::ref_option_ref",
|
||||
"clippy::rest_pat_in_fully_bound_structs",
|
||||
"clippy::same_functions_in_if_condition",
|
||||
"clippy::semicolon_if_nothing_returned",
|
||||
"clippy::single_match_else",
|
||||
"clippy::str_to_string",
|
||||
"clippy::string_add_assign",
|
||||
"clippy::string_add",
|
||||
"clippy::string_lit_as_bytes",
|
||||
"clippy::string_to_string",
|
||||
"clippy::todo",
|
||||
"clippy::trailing_empty_array",
|
||||
"clippy::trait_duplication_in_bounds",
|
||||
"clippy::unimplemented",
|
||||
"clippy::unnecessary_wraps",
|
||||
"clippy::unnested_or_patterns",
|
||||
"clippy::unused_peekable",
|
||||
"clippy::unused_rounding",
|
||||
# "clippy::unused_self",
|
||||
"clippy::useless_transmute",
|
||||
"clippy::verbose_file_reads",
|
||||
"clippy::zero_sized_map_values",
|
||||
"elided_lifetimes_in_paths",
|
||||
"future_incompatible",
|
||||
"nonstandard_style",
|
||||
"rust_2018_idioms",
|
||||
"rust_2021_prelude_collisions",
|
||||
"rustdoc::missing_crate_level_docs",
|
||||
"semicolon_in_expressions_from_macros",
|
||||
"trivial_numeric_casts",
|
||||
"unused_extern_crates",
|
||||
"unused_import_braces",
|
||||
"unused_lifetimes",
|
||||
]
|
||||
|
||||
allow = [
|
||||
# TODO(emilk): enable more lints
|
||||
"clippy::cloned_instead_of_copied",
|
||||
"clippy::derive_partial_eq_without_eq",
|
||||
"clippy::type_complexity",
|
||||
"clippy::undocumented_unsafe_blocks",
|
||||
"trivial_casts",
|
||||
"unsafe_op_in_unsafe_fn", # `unsafe_op_in_unsafe_fn` may become the default in future Rust versions: https://github.com/rust-lang/rust/issues/71668
|
||||
"unused_qualifications",
|
||||
]
|
||||
|
||||
321
MANUAL.md
321
MANUAL.md
@@ -1,5 +1,7 @@
|
||||
# asusctrl manual
|
||||
|
||||
**NOTE:** this manual is in need of an update in some places. If you find issues please file issue reports.
|
||||
|
||||
`asusd` is a utility for Linux to control many aspects of various ASUS laptops
|
||||
but can also be used with non-asus laptops with reduced features.
|
||||
|
||||
@@ -8,65 +10,22 @@ but can also be used with non-asus laptops with reduced features.
|
||||
- `asusd`: The main system daemon. It is autostarted by a udev rule and systemd unit.
|
||||
- `asusd-user`: The user level daemon. Currently will run an anime sequence, with RGB keyboard sequences soon.
|
||||
- `asusctl`: The CLI for interacting with the system daemon
|
||||
- `asus-notify`: A notification daemon with a user systemd unit that can be enabled.
|
||||
|
||||
## `asusd`
|
||||
|
||||
`asusd` is the main system-level daemon which will control/load/save various settings in a safe way for the user, along with exposing a *safe* dbus interface for these interactions. This section covers only the daemon plus the various configuration file options.
|
||||
`asusd` is the main system-level daemon which will control/load/save various settings in a safe way for the user, along with exposing a _safe_ dbus interface for these interactions. This section covers only the daemon plus the various configuration file options.
|
||||
|
||||
The functionality that `asusd` exposes is:
|
||||
|
||||
- graphics switching
|
||||
- anime control
|
||||
- led keyboard control (aura)
|
||||
- charge limiting
|
||||
- bios/efivar control
|
||||
- profiles (fan/cpu)
|
||||
- power profile switching
|
||||
- fan curves (if supported, this is auto-detected)
|
||||
|
||||
each of these will be detailed in sections.
|
||||
|
||||
### Graphics switching
|
||||
|
||||
`asusd` can switch graphics modes between:
|
||||
- `integrated`, uses the iGPU only and force-disables the dGPU
|
||||
- `compute`, enables Nvidia without Xorg. Useful for ML/Cuda
|
||||
- `hybrid`, enables Nvidia prime-offload mode
|
||||
- `nvidia`, uses the Nvidia gpu only
|
||||
- `vfio`, binds the Nvidia gpu to vfio for VM pass-through
|
||||
|
||||
Switching to/from Hybrid and Nvidia modes requires a logout only (no reboot). Switching between integrated/compute/vfio does not require a logout and is instant.
|
||||
|
||||
#### Required actions in distro
|
||||
|
||||
**Rebootless note:** You must edit `/etc/default/grub` to remove `nvidia-drm.modeset=1`
|
||||
from the line `GRUB_CMDLINE_LINUX=` and then recreate your grub config. In fedora
|
||||
you can do this with `sudo grub2-mkconfig -o /etc/grub2.cfg` - other distro may be
|
||||
similar but with a different config location. It's possible that graphics driver updates
|
||||
may change this.
|
||||
|
||||
This switcher conflicts with other gpu switchers like optimus-manager, suse-prime
|
||||
or ubuntu-prime, system76-power, and bbswitch. If you have issues with `asusd`
|
||||
always defaulting to `integrated` mode on boot then you will need to check for
|
||||
stray configs blocking nvidia modules from loading in:
|
||||
- `/etc/modprobe.d/`
|
||||
- `/usr/lib/modprope.d/`
|
||||
|
||||
#### Config options
|
||||
|
||||
1. `"gfx_mode": "<MODE>",`: MODE can be <Integrated, Hybrid, Compute, Nvidia, vfio>
|
||||
2. `"gfx_last_mode": "Nvidia",`: currently unused
|
||||
3. `"gfx_managed": true,`: enable or disable graphics switching controller
|
||||
4. `"gfx_vfio_enable": false,`: enable vfio switching for Nvidia GPU passthrough
|
||||
5. `"gfx_save_compute_vfio": false,`: wether or not to save the vfio state (so it sticks between boots)
|
||||
|
||||
#### Graphics switching notes
|
||||
|
||||
**G-Sync note:** Some laptops are capable of using the dGPU as the sole GPU in the system which is generally to enable g-sync on the laptop display panel. This is controlled by the bios/efivar control and will be covered in that section.
|
||||
|
||||
**vfio note:** The vfio modules *must not* be compiled into the kernel, they need
|
||||
to be separate modules. If you don't plan to use vfio mode then you can ignore this
|
||||
otherwise you may need a custom built kernel.
|
||||
|
||||
### AniMe control
|
||||
|
||||
Controller for the fancy AniMe matrix display on the lid of some machines. This controller is a work in progress.
|
||||
@@ -89,35 +48,7 @@ The LED controller (e.g, aura) enables setting many of the factory modes availab
|
||||
|
||||
#### Supported laptops
|
||||
|
||||
Models GA401, GA502, GU502 support LED brightness change only (no RGB). However the GA401Q model can actually use three modes; static, breathe, and pulse, plus also use red to control the LED brightness intensity.
|
||||
|
||||
All models that have any form of LED mode control need to be enabled via the config file at `/etc/asusd/asusd-ledmodes.toml`. Unfortunately ASUS doesn't provide any easy way to find all the supported modes for all laptops (not even through Armory Crate and its various files, that progrma downloads only the required settings for the laptop it runs on) so each model must be added as needed.
|
||||
|
||||
#### Config options
|
||||
|
||||
The defaults are located at `/etc/asusd/asusd-ledmodes.toml`, and on `asusd` start it creates `/etc/asusd/aura.conf` whcih stores the per-mode settings. If you edit the defaults file you must remove `/etc/asusd/aura.conf` and restart `asusd.service` with `systemctl restart asusd`.
|
||||
|
||||
##### /etc/asusd/asusd-ledmodes.toml
|
||||
|
||||
Example:
|
||||
```toml
|
||||
[[led_data]]
|
||||
prod_family = "ROG Zephyrus M15"
|
||||
board_names = ["GU502LU"]
|
||||
standard = ["Static", "Breathe", "Strobe", "Pulse"]
|
||||
multizone = false
|
||||
per_key = false
|
||||
```
|
||||
|
||||
1. `prod_family`: you can find this in `journalctl -b -u asusd`, or `cat /sys/class/dmi/id/product_name`. It should be copied as written. There can be multiple `led-data` groups of the same `prod_family` with differing `board_names`.
|
||||
2. `board_names`: is an array of board names in this product family. Find this in the journal as above or by `cat /sys/class/dmi/id/board_name`.
|
||||
3. `standard` are the factory preset modes, the names should corrospond to Armory Crate names
|
||||
4. `multizone`: some keyboards have 4 zones of LED control, this enables setting a colour in each zone. The keyboard must support this or it has no effect.
|
||||
5. `per_key`: enable per-key RGB effects. The keyboard must support this or it has no effect.
|
||||
|
||||
##### /etc/asusd/aura.conf
|
||||
|
||||
This file can be manually edited if desired, but the `asusctl` CLI tool, or dbus methods are the preferred method. Any manual changes to this file mean that the `asusd.service` will need to be restarted, or you need to cycle between modes to force a reload.
|
||||
There are over 60 supported laptops as of 01-01-2023. Please see [the rog-aura crate readme for further details](/rog-aura/README.md).
|
||||
|
||||
### Charge control
|
||||
|
||||
@@ -126,68 +57,42 @@ Almost all modern ASUS laptops have charging limit control now. This can be cont
|
||||
```json
|
||||
"bat_charge_limit": 80,
|
||||
```
|
||||
|
||||
where the number is a percentage.
|
||||
|
||||
### Bios control
|
||||
|
||||
Some options that you find in Armory Crate are available under this controller, so far there is:
|
||||
|
||||
- POST sound: this is the sound you here on bios boot post
|
||||
- G-Sync: this controls if the dGPU (Nvidia) is the *only* GPU, making it the main GPU and disabling the iGPU
|
||||
- POST sound: this is the sound you hear on bios boot post
|
||||
- GPU MUX: this controls if the dGPU is the _only_ GPU, making it the main GPU and disabling the iGPU
|
||||
|
||||
These options are not written to the config file as they are stored in efivars. The only way to change these is to use the exposed safe dbus methods, or use the `asusctl` CLI tool.
|
||||
|
||||
### Profiles
|
||||
|
||||
Profiles provide a method setting up various basic CPU and fan settings in profile blocks which can then be switched between or cycled through. The CPU controls so far are:
|
||||
asusctl can support setting a power profile via platform_profile drivers. This requires [power-profiles-daemon](https://gitlab.freedesktop.org/hadess/power-profiles-daemon) v0.10.0 minimum. It also requires the kernel patch for platform_profile support to be applied form [here](https://lkml.org/lkml/2021/8/18/1022) - this patch is merged to 5.15 kernel upstream.
|
||||
|
||||
- Min/Max percentage of CPU frequency (Intel only for now)
|
||||
- CPU turbo boost enable or disable
|
||||
- Fan presets. These are 0: Normal, 1: Boost, 2: Silent.
|
||||
- Fan curves, override fan-preset. AMD only.
|
||||
A common use of asusctl is to bind the `fn+f5` (fan) key to `asusctl profile -n` to cycle through the 3 profiles:
|
||||
|
||||
#### Config options
|
||||
|
||||
Example:
|
||||
```json
|
||||
"toggle_profiles": [
|
||||
"normal",
|
||||
"boost",
|
||||
"silent"
|
||||
],
|
||||
"power_profiles": {
|
||||
"boost": {
|
||||
"min_percentage": 0,
|
||||
"max_percentage": 100,
|
||||
"turbo": true,
|
||||
"fan_preset": 1,
|
||||
"fan_curve": null
|
||||
},
|
||||
"normal": {
|
||||
"min_percentage": 0,
|
||||
"max_percentage": 100,
|
||||
"turbo": true,
|
||||
"fan_preset": 0,
|
||||
"fan_curve": null
|
||||
},
|
||||
"silent": {
|
||||
"min_percentage": 0,
|
||||
"max_percentage": 100,
|
||||
"turbo": true,
|
||||
"fan_preset": 2,
|
||||
"fan_curve": null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
1. `"toggle_profiles": [],`: these are the profile names that will be cycled through when using a provided next/prev dbus method.
|
||||
2. `"power_profiles": {}`: all the available profiles.
|
||||
1. Balanced
|
||||
2. Performance
|
||||
3. Quiet
|
||||
|
||||
#### Fan curves
|
||||
|
||||
**fan_curve note:** This is a WIP. Currently it relies on `acpi_call` kernel module which is ancient and hacky, not intended for this purpose. A proper kernel driver is in progress.
|
||||
Fan curve support requires a laptop that supports it (this is detected automatically) and the kernel patch from [here](https://lkml.org/lkml/2021/10/23/250) which is accepted for the 5.17 kernel release .
|
||||
|
||||
See [this document](https://github.com/cronosun/atrofac/blob/master/ADVANCED.md#limits) for details on the string format required, e.g, `"fan_curve": "30c:0%,40c:5%,50c:10%,60c:20%,70c:35%,80c:55%,90c:65%,100c:65%"`.
|
||||
The fan curve format can be of varying formats:
|
||||
|
||||
- `30c:0%,40c:5%,50c:10%,60c:20%,70c:35%,80c:55%,90c:65%,100c:65%"`
|
||||
- `30:0,40:5,50:10,60:20,70:35,80:55,90:65,100:65"`
|
||||
- `30 0,40 5,50 10,60 20,70 35,80 55,90 65,100 65"`
|
||||
- `30 0 40 5 50 10 60 20 70 35 80 55 90 65 100 65"`
|
||||
|
||||
the order must always be the same "temperature:percentage", lowest from left to rigth being highest.
|
||||
|
||||
The config file is located at `/etc/asusd/profile.conf` and is self-descriptive. On first run it is populated with the system EC defaults.
|
||||
|
||||
### Support controller
|
||||
|
||||
@@ -195,12 +100,121 @@ There is one more controller; the support controller. The sole pupose of this co
|
||||
|
||||
## asusd-user
|
||||
|
||||
`asusd-user` is a usermode daemon. The intended purpose is to provide a method for users to run there own custom per-key keyboard effects and modes, AniMe sequences, and possibly their own profiles - all without overwriting the *base* system config. As such some parts of the system daemon will migrate to the user daemon over time with the expectation that the Linux system runs both.
|
||||
`asusd-user` is a usermode daemon. The intended purpose is to provide a method for users to run there own custom per-key keyboard effects and modes, AniMe sequences, and possibly their own profiles - all without overwriting the _base_ system config. As such some parts of the system daemon will migrate to the user daemon over time with the expectation that the Linux system runs both.
|
||||
|
||||
As of now only AniMe is active in this with configuration in `~/.config/rog/`. On first run defaults are created that are intended to work as examples.
|
||||
|
||||
The main config is `~/.config/rog/rog-user.cfg`
|
||||
|
||||
#### Config options: Aura, per-key and zoned
|
||||
|
||||
I'm unsure of how many laptops this works on, so please try it.
|
||||
|
||||
`led_type: Key` works only on actual per-key RGB keyboards.
|
||||
|
||||
`led_type: Zone` works on zoned laptops.
|
||||
|
||||
`led_type: Zone` set to `None` works on zoned ROG laptops, unzoned ROG laptops, and TUF laptops (and yes this does mean an audio EQ can be done now).
|
||||
|
||||
`~/.config/rog/rog-user.cfg` contains a setting `"active_aura": "<FILENAME>"` where `<FILENAME>` is the name of the Aura config to use, located in the same directory and without the file postfix, e.g, `"active_anime": "aura-default"`
|
||||
|
||||
An Aura config itself is a file with contents:
|
||||
|
||||
```ron
|
||||
(
|
||||
name: "aura-default",
|
||||
aura: (
|
||||
effects: [
|
||||
Breathe((
|
||||
led: W,
|
||||
start_colour1: (255, 0, 20),
|
||||
start_colour2: (20, 255, 0),
|
||||
speed: Low,
|
||||
)),
|
||||
Breathe((
|
||||
led: A,
|
||||
start_colour1: (255, 0, 20),
|
||||
start_colour2: (20, 255, 0),
|
||||
speed: Low,
|
||||
)),
|
||||
Breathe((
|
||||
led: S,
|
||||
start_colour1: (255, 0, 20),
|
||||
start_colour2: (20, 255, 0),
|
||||
speed: Low,
|
||||
)),
|
||||
Breathe((
|
||||
led: D,
|
||||
start_colour1: (255, 0, 20),
|
||||
start_colour2: (20, 255, 0),
|
||||
speed: Low,
|
||||
)),
|
||||
Breathe((
|
||||
led: F,
|
||||
start_colour1: (255, 0, 0),
|
||||
start_colour2: (255, 0, 0),
|
||||
speed: High,
|
||||
)),
|
||||
Static((
|
||||
led: RCtrl,
|
||||
colour: (0, 0, 255),
|
||||
)),
|
||||
Static((
|
||||
led: LCtrl,
|
||||
colour: (0, 0, 255),
|
||||
)),
|
||||
Static((
|
||||
led: Esc,
|
||||
colour: (0, 0, 255),
|
||||
)),
|
||||
DoomFlicker((
|
||||
led: N9,
|
||||
start_colour: (0, 0, 255),
|
||||
max_percentage: 80,
|
||||
min_percentage: 40,
|
||||
)),
|
||||
],
|
||||
zoned: false,
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
If your laptop supports multizone, `"led"` can also be `"Zone": <one of the following>`
|
||||
|
||||
- `SingleZone` // Keyboards with only one zone
|
||||
- `ZonedKbLeft` // keyboard left
|
||||
- `ZonedKbLeftMid` // keyboard left-middle
|
||||
- `ZonedKbRightMid` // etc
|
||||
- `ZonedKbRight`
|
||||
- `LightbarRight`
|
||||
- `LightbarRightCorner`
|
||||
- `LightbarRightBottom`
|
||||
- `LightbarLeftBottom`
|
||||
- `LightbarLeftCorner`
|
||||
- `LightbarLeft`
|
||||
|
||||
Single zone example:
|
||||
|
||||
```ron
|
||||
(
|
||||
name: "aura-default",
|
||||
aura: (
|
||||
effects: [
|
||||
DoomFlicker((
|
||||
led: SingleZone,
|
||||
start_colour: (200, 40, 5),
|
||||
max_percentage: 80,
|
||||
min_percentage: 40,
|
||||
)),
|
||||
],
|
||||
zoned: true,
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
At the moment there are only three effects available as shown in the example. More will come in the future
|
||||
but this may take me some time.
|
||||
|
||||
#### Config options: AniMe
|
||||
|
||||
`~/.config/rog/rog-user.cfg` contains a setting `"active_anime": "<FILENAME>"` where `<FILENAME>` is the name of the AniMe config to use, located in the same directory and without the file postfix, e.g, `"active_anime": "anime-doom"`
|
||||
@@ -228,6 +242,7 @@ Each object in the array can be one of:
|
||||
##### AsusAnimation
|
||||
|
||||
`AsusAnimation` is specifically for running the gif files that Armory Crate comes with. `asusctl` includes all of these in `/usr/share/asusd/anime/asus/`
|
||||
|
||||
```json
|
||||
"AsusAnimation": {
|
||||
"file": "<FILE_PATH>",
|
||||
@@ -236,9 +251,21 @@ Each object in the array can be one of:
|
||||
}
|
||||
```
|
||||
|
||||
##### AsusImage
|
||||
|
||||
Virtually the same as `AsusAnimation` but for png files, typically created in the same "slanted" style using a template (`diagonal-template.png`) as the ASUS gifs for pixel perfection.
|
||||
|
||||
```json
|
||||
"AsusImage": {
|
||||
"file": "<FILE_PATH>",
|
||||
"time": <TIME>,
|
||||
"brightness": <FLOAT>
|
||||
}
|
||||
```
|
||||
|
||||
##### ImageAnimation
|
||||
|
||||
`ImageAnimation` can play *any* gif of any size.
|
||||
`ImageAnimation` can play _any_ gif of any size.
|
||||
|
||||
```json
|
||||
"ImageAnimation": {
|
||||
@@ -269,6 +296,7 @@ Each object in the array can be one of:
|
||||
<FLOAT>,
|
||||
<FLOAT>
|
||||
],
|
||||
"time": <TIME>,
|
||||
"brightness": <FLOAT>
|
||||
}
|
||||
},
|
||||
@@ -296,6 +324,7 @@ Must be full path: `"/usr/share/asusd/anime/asus/gaming/Controller.gif"` or `/ho
|
||||
**<FLOAT>**
|
||||
|
||||
A number from 0.0-1.0.
|
||||
|
||||
- `brightness`: If it is brightness it is combined with the system daemon global brightness
|
||||
- `scale`: 1.0 is the original size with lower number shrinking, larger growing
|
||||
- `angle`: Rotation angle in radians
|
||||
@@ -304,6 +333,7 @@ A number from 0.0-1.0.
|
||||
**<TIME>**
|
||||
|
||||
Time is the length of time to run the gif for:
|
||||
|
||||
```json
|
||||
"time": {
|
||||
"Time": {
|
||||
@@ -312,24 +342,52 @@ Time is the length of time to run the gif for:
|
||||
}
|
||||
},
|
||||
```
|
||||
|
||||
A cycle is how many gif loops to run:
|
||||
|
||||
```json
|
||||
"time": {
|
||||
"Cycles": 2
|
||||
},
|
||||
```
|
||||
`Infinite` means that this gif will never end:
|
||||
|
||||
`Infinite` means that this gif will never end:
|
||||
|
||||
```json
|
||||
"time": "Infinite",
|
||||
```
|
||||
|
||||
`Fade` allows an image or gif to fade in and out, and remain at max brightness to n time:
|
||||
|
||||
```json
|
||||
"time": {
|
||||
"Fade": {
|
||||
"fade_in": {
|
||||
"secs": 2,
|
||||
"nanos": 0
|
||||
},
|
||||
"show_for": {
|
||||
"secs": 1,
|
||||
"nanos": 0
|
||||
},
|
||||
"fade_out": {
|
||||
"secs": 2,
|
||||
"nanos": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
```
|
||||
|
||||
`show_for` can be `null`, if it is `null` then the `show_for` becomes `gif_time_length - fade_in - fade_out`.
|
||||
This is period for which the gif or image will be max brightness (as set).
|
||||
|
||||
**<INT>**
|
||||
|
||||
A plain non-float integer.
|
||||
|
||||
## asusctl
|
||||
|
||||
`asusctl` is a commandline interface which intends to be the main method of interacting with `asusd`. I can be used in any place a terminal app can be used.
|
||||
`asusctl` is a commandline interface which intends to be the main method of interacting with `asusd`. It can be used in any place a terminal app can be used.
|
||||
|
||||
This program will query `asusd` for the `Support` level of the laptop and show or hide options according to this support level.
|
||||
|
||||
@@ -360,26 +418,19 @@ asusctl <command> <subcommand> --help
|
||||
|
||||
To switch to next/previous Aura modes you will need to bind both the aura keys (if available) to one of:
|
||||
**Next**
|
||||
|
||||
```
|
||||
asusctl led-mode -n
|
||||
```
|
||||
|
||||
**Previous**
|
||||
|
||||
```
|
||||
asusctl led-mode -p
|
||||
```
|
||||
|
||||
To switch Fan/Thermal profiles you need to bind the Fn+F5 key to `asusctl profile -n`.
|
||||
|
||||
## User NOTIFICATIONS via dbus
|
||||
|
||||
If you have a notifications handler set up, or are using KDE or Gnome then you
|
||||
can enable the user service to get basic notifications when something changes.
|
||||
|
||||
```
|
||||
systemctl --user enable asus-notify.service
|
||||
systemctl --user start asus-notify.service
|
||||
```
|
||||
|
||||
# License & Trademarks
|
||||
|
||||
Mozilla Public License 2 (MPL-2.0)
|
||||
@@ -392,4 +443,4 @@ Reference to any ASUS products, services, processes, or other information and/or
|
||||
|
||||
The use of ROG and ASUS trademarks within this website and associated tools and libraries is only to provide a recognisable identifier to users to enable them to associate that these tools will work with ASUS ROG laptops.
|
||||
|
||||
---
|
||||
---
|
||||
|
||||
92
Makefile
92
Makefile
@@ -1,4 +1,4 @@
|
||||
VERSION := $(shell grep -Pm1 'version = "(\d.\d.\d)"' daemon/Cargo.toml | cut -d'"' -f2)
|
||||
VERSION := $(shell /usr/bin/grep -Pm1 'version = "(\d.\d.\d)"' Cargo.toml | cut -d'"' -f2)
|
||||
|
||||
INSTALL = install
|
||||
INSTALL_PROGRAM = ${INSTALL} -D -m 0755
|
||||
@@ -11,20 +11,23 @@ datarootdir = $(prefix)/share
|
||||
libdir = $(exec_prefix)/lib
|
||||
zshcpl = $(datarootdir)/zsh/site-functions
|
||||
|
||||
BIN_ROG := rog-control-center
|
||||
BIN_C := asusctl
|
||||
BIN_D := asusd
|
||||
BIN_U := asusd-user
|
||||
BIN_N := asus-notify
|
||||
LEDCFG := asusd-ledmodes.toml
|
||||
X11CFG := 90-nvidia-screen-G05.conf
|
||||
PMRULES := 90-asusd-nvidia-pm.rules
|
||||
LEDCFG := aura_support.ron
|
||||
|
||||
SRC := Cargo.toml Cargo.lock Makefile $(shell find -type f -wholename '**/src/*.rs')
|
||||
|
||||
STRIP_BINARIES ?= 0
|
||||
|
||||
DEBUG ?= 0
|
||||
ifeq ($(DEBUG),0)
|
||||
ARGS += --release
|
||||
TARGET = release
|
||||
else
|
||||
ARGS += --profile dev
|
||||
TARGET = debug
|
||||
endif
|
||||
|
||||
VENDORED ?= 0
|
||||
@@ -40,43 +43,65 @@ clean:
|
||||
distclean:
|
||||
rm -rf .cargo vendor vendor.tar.xz
|
||||
|
||||
install:
|
||||
$(INSTALL_PROGRAM) "./target/release/$(BIN_C)" "$(DESTDIR)$(bindir)/$(BIN_C)"
|
||||
$(INSTALL_PROGRAM) "./target/release/$(BIN_D)" "$(DESTDIR)$(bindir)/$(BIN_D)"
|
||||
$(INSTALL_PROGRAM) "./target/release/$(BIN_U)" "$(DESTDIR)$(bindir)/$(BIN_U)"
|
||||
$(INSTALL_PROGRAM) "./target/release/$(BIN_N)" "$(DESTDIR)$(bindir)/$(BIN_N)"
|
||||
$(INSTALL_DATA) "./data/$(PMRULES)" "$(DESTDIR)$(libdir)/udev/rules.d/$(PMRULES)"
|
||||
install-program:
|
||||
$(INSTALL_PROGRAM) "./target/$(TARGET)/$(BIN_ROG)" "$(DESTDIR)$(bindir)/$(BIN_ROG)"
|
||||
|
||||
$(INSTALL_PROGRAM) "./target/$(TARGET)/$(BIN_C)" "$(DESTDIR)$(bindir)/$(BIN_C)"
|
||||
$(INSTALL_PROGRAM) "./target/$(TARGET)/$(BIN_D)" "$(DESTDIR)$(bindir)/$(BIN_D)"
|
||||
$(INSTALL_PROGRAM) "./target/$(TARGET)/$(BIN_U)" "$(DESTDIR)$(bindir)/$(BIN_U)"
|
||||
|
||||
install-data:
|
||||
$(INSTALL_DATA) "./rog-control-center/data/$(BIN_ROG).desktop" "$(DESTDIR)$(datarootdir)/applications/$(BIN_ROG).desktop"
|
||||
$(INSTALL_DATA) "./rog-control-center/data/$(BIN_ROG).png" "$(DESTDIR)$(datarootdir)/icons/hicolor/512x512/apps/$(BIN_ROG).png"
|
||||
cd rog-aura/data/layouts && find . -type f -name "*.ron" -exec $(INSTALL_DATA) "{}" "$(DESTDIR)$(datarootdir)/rog-gui/layouts/{}" \;
|
||||
|
||||
$(INSTALL_DATA) "./data/$(BIN_D).rules" "$(DESTDIR)$(libdir)/udev/rules.d/99-$(BIN_D).rules"
|
||||
$(INSTALL_DATA) "./data/$(LEDCFG)" "$(DESTDIR)/etc/asusd/$(LEDCFG)"
|
||||
$(INSTALL_DATA) "./rog-aura/data/$(LEDCFG)" "$(DESTDIR)$(datarootdir)/asusd/$(LEDCFG)"
|
||||
$(INSTALL_DATA) "./data/$(BIN_D).conf" "$(DESTDIR)$(datarootdir)/dbus-1/system.d/$(BIN_D).conf"
|
||||
$(INSTALL_DATA) "./data/$(X11CFG)" "$(DESTDIR)$(datarootdir)/X11/xorg.conf.d/$(X11CFG)"
|
||||
|
||||
$(INSTALL_DATA) "./data/$(BIN_D).service" "$(DESTDIR)$(libdir)/systemd/system/$(BIN_D).service"
|
||||
$(INSTALL_DATA) "./data/$(BIN_N).service" "$(DESTDIR)$(libdir)/systemd/user/$(BIN_N).service"
|
||||
$(INSTALL_DATA) "./data/$(BIN_U).service" "$(DESTDIR)$(libdir)/systemd/user/$(BIN_U).service"
|
||||
|
||||
$(INSTALL_DATA) "./data/icons/asus_notif_yellow.png" "$(DESTDIR)$(datarootdir)/icons/hicolor/512x512/apps/asus_notif_yellow.png"
|
||||
$(INSTALL_DATA) "./data/icons/asus_notif_green.png" "$(DESTDIR)$(datarootdir)/icons/hicolor/512x512/apps/asus_notif_green.png"
|
||||
$(INSTALL_DATA) "./data/icons/asus_notif_blue.png" "$(DESTDIR)$(datarootdir)/icons/hicolor/512x512/apps/asus_notif_blue.png"
|
||||
$(INSTALL_DATA) "./data/icons/asus_notif_red.png" "$(DESTDIR)$(datarootdir)/icons/hicolor/512x512/apps/asus_notif_red.png"
|
||||
$(INSTALL_DATA) "./data/_asusctl" "$(DESTDIR)$(zshcpl)/_asusctl"
|
||||
$(INSTALL_DATA) "./data/completions/asusctl.fish" "$(DESTDIR)$(datarootdir)/fish/vendor_completions.d/asusctl.fish"
|
||||
cd data && find "./anime" -type f -exec install -Dm 755 "{}" "$(DESTDIR)$(datarootdir)/asusd/{}" \;
|
||||
$(INSTALL_DATA) "./data/icons/asus_notif_orange.png" "$(DESTDIR)$(datarootdir)/icons/hicolor/512x512/apps/asus_notif_orange.png"
|
||||
$(INSTALL_DATA) "./data/icons/asus_notif_white.png" "$(DESTDIR)$(datarootdir)/icons/hicolor/512x512/apps/asus_notif_white.png"
|
||||
|
||||
$(INSTALL_DATA) "./data/icons/scalable/gpu-compute.svg" "$(DESTDIR)$(datarootdir)/icons/hicolor/scalable/status/gpu-compute.svg"
|
||||
$(INSTALL_DATA) "./data/icons/scalable/gpu-hybrid.svg" "$(DESTDIR)$(datarootdir)/icons/hicolor/scalable/status/gpu-hybrid.svg"
|
||||
$(INSTALL_DATA) "./data/icons/scalable/gpu-integrated.svg" "$(DESTDIR)$(datarootdir)/icons/hicolor/scalable/status/gpu-integrated.svg"
|
||||
$(INSTALL_DATA) "./data/icons/scalable/gpu-nvidia.svg" "$(DESTDIR)$(datarootdir)/icons/hicolor/scalable/status/gpu-nvidia.svg"
|
||||
$(INSTALL_DATA) "./data/icons/scalable/gpu-vfio.svg" "$(DESTDIR)$(datarootdir)/icons/hicolor/scalable/status/gpu-vfio.svg"
|
||||
$(INSTALL_DATA) "./data/icons/scalable/notification-reboot.svg" "$(DESTDIR)$(datarootdir)/icons/hicolor/scalable/status/notification-reboot.svg"
|
||||
|
||||
cd rog-anime/data && find "./anime" -type f -exec $(INSTALL_DATA) "{}" "$(DESTDIR)$(datarootdir)/asusd/{}" \;
|
||||
|
||||
install: install-program install-data
|
||||
|
||||
uninstall:
|
||||
rm -f "$(DESTDIR)$(bindir)/$(BIN_ROG)"
|
||||
rm -r "$(DESTDIR)$(datarootdir)/applications/$(BIN_ROG).desktop"
|
||||
rm -r "$(DESTDIR)$(datarootdir)/icons/hicolor/512x512/apps/$(BIN_ROG).png"
|
||||
|
||||
rm -f "$(DESTDIR)$(bindir)/$(BIN_C)"
|
||||
rm -f "$(DESTDIR)$(bindir)/$(BIN_D)"
|
||||
rm -f "$(DESTDIR)$(bindir)/$(BIN_N)"
|
||||
rm -f "$(DESTDIR)$(libdir)/udev/rules.d/$(PMRULES)"
|
||||
rm -f "$(DESTDIR)$(libdir)/udev/rules.d/99-$(BIN_D).rules"
|
||||
rm -f "$(DESTDIR)/etc/asusd/$(LEDCFG)"
|
||||
rm -f "$(DESTDIR)$(datarootdir)/dbus-1/system.d/$(BIN_D).conf"
|
||||
rm -f "$(DESTDIR)$(datarootdir)/X11/xorg.conf.d/$(X11CFG)"
|
||||
rm -f "$(DESTDIR)$(libdir)/systemd/system/$(BIN_D).service"
|
||||
rm -r "$(DESTDIR)$(libdir)/systemd/user/$(BIN_N).service"
|
||||
rm -r "$(DESTDIR)$(datarootdir)/icons/hicolor/512x512/apps/asus_notif_yellow.png"
|
||||
rm -r "$(DESTDIR)$(datarootdir)/icons/hicolor/512x512/apps/asus_notif_green.png"
|
||||
rm -r "$(DESTDIR)$(datarootdir)/icons/hicolor/512x512/apps/asus_notif_red.png"
|
||||
rm -f "$(DESTDIR)$(zshcpl)/_asusctl"
|
||||
rm -f "$(DESTDIR)$(datarootdir)/fish/vendor_completions.d/asusctl.fish"
|
||||
rm -r "$(DESTDIR)$(datarootdir)/icons/hicolor/scalable/status/gpu-compute.svg"
|
||||
rm -r "$(DESTDIR)$(datarootdir)/icons/hicolor/scalable/status/gpu-hybrid.svg"
|
||||
rm -r "$(DESTDIR)$(datarootdir)/icons/hicolor/scalable/status/gpu-integrated.svg"
|
||||
rm -r "$(DESTDIR)$(datarootdir)/icons/hicolor/scalable/status/gpu-nvidia.svg"
|
||||
rm -r "$(DESTDIR)$(datarootdir)/icons/hicolor/scalable/status/gpu-vfio.svg"
|
||||
rm -r "$(DESTDIR)$(datarootdir)/icons/hicolor/scalable/status/notification-reboot.svg"
|
||||
rm -rf "$(DESTDIR)$(datarootdir)/asusd"
|
||||
rm -rf "$(DESTDIR)$(datarootdir)/rog-gui"
|
||||
|
||||
update:
|
||||
cargo update
|
||||
@@ -87,14 +112,33 @@ vendor:
|
||||
echo 'directory = "vendor"' >> .cargo/config
|
||||
mv .cargo/config ./cargo-config
|
||||
rm -rf .cargo
|
||||
rm -rf vendor
|
||||
cargo vendor-filterer --platform x86_64-unknown-linux-gnu vendor
|
||||
tar pcfJ vendor_asusctl_$(VERSION).tar.xz vendor
|
||||
rm -rf vendor
|
||||
|
||||
bindings:
|
||||
typeshare ./rog-anime/src/ --lang=typescript --output-file=bindings/ts/anime.ts
|
||||
typeshare ./rog-aura/src/ --lang=typescript --output-file=bindings/ts/aura.ts
|
||||
typeshare ./rog-profiles/src/ --lang=typescript --output-file=bindings/ts/profiles.ts
|
||||
typeshare ./rog-platform/src/ --lang=typescript --output-file=bindings/ts/platform.ts
|
||||
|
||||
translate:
|
||||
find -name \*.slint | xargs slint-tr-extractor -o rog-control-center/translations/en/rog-control-center.po
|
||||
|
||||
build:
|
||||
ifeq ($(VENDORED),1)
|
||||
cargo vendor
|
||||
@echo "version = $(VERSION)"
|
||||
tar pxf vendor_asusctl_$(VERSION).tar.xz
|
||||
endif
|
||||
cargo build $(ARGS)
|
||||
ifneq ($(STRIP_BINARIES),0)
|
||||
strip -s ./target/$(TARGET)/$(BIN_C)
|
||||
strip -s ./target/$(TARGET)/$(BIN_D)
|
||||
strip -s ./target/$(TARGET)/$(BIN_U)
|
||||
strip -s ./target/$(TARGET)/$(BIN_ROG)
|
||||
endif
|
||||
|
||||
.PHONY: all clean distclean install uninstall update build
|
||||
|
||||
.PHONY: all clean distclean install uninstall update build bindings
|
||||
|
||||
123
README.md
123
README.md
@@ -1,31 +1,37 @@
|
||||
# `asusctl` for ASUS ROG
|
||||
|
||||
[](https://www.paypal.com/donate/?hosted_button_id=4V2DEPS7K6APC) - [Asus Linux Website](https://asus-linux.org/)
|
||||
[Become a Patron!](https://www.patreon.com/bePatron?u=7602281) - [Asus Linux Website](https://asus-linux.org/)
|
||||
|
||||
**WARNING:** Many features are developed in tandem with kernel patches. If you see a feature is missing you either need a patched kernel or latest release.
|
||||
|
||||
`asusd` is a utility for Linux to control many aspects of various ASUS laptops
|
||||
but can also be used with non-asus laptops with reduced features.
|
||||
|
||||
Now includes a GUI, `rog-control-center`.
|
||||
|
||||
## Kernel support
|
||||
|
||||
**The minimum supported kernel version is 6.10**, which will contain the patches from [here](https://lore.kernel.org/platform-driver-x86/20240404001652.86207-1-luke@ljones.dev/). Z13 devices will need [these](https://lore.kernel.org/linux-input/20240416090402.31057-1-luke@ljones.dev/T/#t)
|
||||
|
||||
## Goals
|
||||
|
||||
1. To provide an interface for rootless control of some system functions most users wish to control such as fan speeds, keyboard LEDs, graphics modes.
|
||||
2. Enable third-party apps to use the above with dbus methods
|
||||
3. To make the above as easy as possible for new users
|
||||
4. Respect the users resources: be small, light, and fast
|
||||
The main goal of this work is to provide a safe and easy to use abstraction over various laptop features via DBUS, and to provide some helpful defaults and other behaviour such as toggling throttle/profile on AC/battery change.
|
||||
|
||||
Point 3 means that the list of supported distros is very narrow - fedora is explicitly
|
||||
supported, while Ubuntu and openSUSE are level-2 support. All other distros are *not*
|
||||
supported (while asusd might still run fine on them). For best support use fedora 32+ Workstation.
|
||||
1. Provide safe dbus interface
|
||||
2. Respect the users resources: be small, light, and fast
|
||||
|
||||
Point 4? asusd currently uses a tiny fraction of cpu time, and less than 1Mb of ram, the way
|
||||
a system-level daemon should.
|
||||
a system-level daemon should. Languages such as JS and python should never be used for system level daemons (please stop).
|
||||
|
||||
**NOTICE:**
|
||||
## Keyboard LEDs
|
||||
|
||||
Various patches are required for keyboard support. See [this post](https://asus-linux.org/blog/updates-2021-05-06/) for details on status and which kernels will have which patches.
|
||||
The level of support for laptops is dependent on folks submitting data to include in [`./rog-aura/data/layouts/aura_support.ron`](./rog-aura/data/layouts/aura_support.ron), typically installed in `/usr/share/asusd/aura_support.ron`. This is because the controller used for keyboards and LEDs is used across many years and many laptop models, all with different firmware configurations - the only way to track this is with the file mentioned above. Why not just enable all by default? Because it confuses people.
|
||||
|
||||
See the [rog-aura readme](./rog-aura/README.md) for more details.
|
||||
|
||||
## Discord
|
||||
|
||||
[Discord server link](https://discord.gg/ngbdKabAnP)
|
||||
[Discord server link](https://discord.gg/z8y99XqPb7)
|
||||
|
||||
## SUPPORTED LAPTOPS
|
||||
|
||||
@@ -36,65 +42,96 @@ to this:
|
||||
Bus 001 Device 002: ID 0b05:1866 ASUSTek Computer, Inc. N-KEY Device
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```
|
||||
Bus 003 Device 002: ID 0b05:19b6 ASUSTek Computer, Inc. [unknown]
|
||||
```
|
||||
|
||||
then it may work without tweaks. Technically all other functions except the LED
|
||||
and AniMe parts should work regardless of your latop make. Eventually this project
|
||||
will probably suffer another rename once it becomes generic enough to do so.
|
||||
and AniMe parts should work regardless of your latop make.
|
||||
|
||||
## Implemented
|
||||
|
||||
- [X] System daemon
|
||||
- [X] User notifications daemon
|
||||
- [X] Setting/modifying built-in LED modes
|
||||
- [X] Per-key LED setting
|
||||
- [X] Fancy LED modes (See examples) (currently being reworked)
|
||||
- [X] Saving settings for reload
|
||||
- [X] Logging - required for journalctl
|
||||
- [X] AniMatrix display on G14 models that include it (currently being reworked)
|
||||
- [X] Set battery charge limit (with kernel supporting this)
|
||||
- [X] Fan curve control on G14 + G15 thanks to @Yarn1
|
||||
- [X] Graphics mode switching between iGPU, dGPU, on-demand, and vfio (for VM pass-through)
|
||||
+ [X] Requires only a logout/login
|
||||
- [X] Toggle bios setting for boot/POST sound
|
||||
- [X] Toggle bios setting for "dedicated gfx" mode on supported laptops (g-sync)
|
||||
The list is a bit outdated as many features have been enabled in the Linux kernel with upstream patches and then supported in asusctl suite.
|
||||
|
||||
- [x] System daemon
|
||||
- [x] GUI app (includes tray and notifications)
|
||||
- [x] Setting/modifying built-in LED modes
|
||||
- [x] Per-key LED setting
|
||||
- [x] Fancy LED modes (See examples) (currently being reworked)
|
||||
- [x] AniMatrix display on G14 and M16 models that include it
|
||||
- [x] Set battery charge limit (with kernel supporting this)
|
||||
- [x] Fan curve control on supported laptops (G14/G15, some TUF like FA507)
|
||||
- [x] Toggle bios setting for boot/POST sound
|
||||
- [x] Toggle GPU MUX (g-sync, or called MUX on 2022+ laptops)
|
||||
|
||||
# GUI
|
||||
|
||||
A gui is now in the repo - ROG Control Center. At this time it is still a WIP, but it has almost all features in place already.
|
||||
|
||||
# BUILDING
|
||||
|
||||
Requirements are rust >= 1.47 installed from rustup.io if the distro provided version is too old, and `make`.
|
||||
Rust and cargo are required, they can be installed from [rustup.rs](https://rustup.rs/) or from the distro repos if newer than 1.75.
|
||||
|
||||
**Ubuntu*:** `apt install libclang-dev libudev-dev`
|
||||
**fedora:**
|
||||
|
||||
**fedora:** `dnf install clang-devel systemd-devel`
|
||||
dnf install cmake clang-devel libinput-devel libseat-devel libgbm-devel libxkbcommon-devel systemd-devel \
|
||||
libdrm-devel expat-devel pcre2-devel libzstd-devel libappindicator-gtk3
|
||||
make
|
||||
sudo make install
|
||||
|
||||
**openSUSE:**
|
||||
|
||||
Works with KDE Plasma (without GTK packages)
|
||||
|
||||
zypper in -t pattern devel_basis
|
||||
zypper in rustup make cmake libinput-devel libseat-devel libgbm-devel systemd-devel clang-devel llvm-devel gdk-pixbuf-devel cairo-devel pango-devel freetype-devel libexpat-devel libayatana-indicator3-7
|
||||
make
|
||||
sudo make install
|
||||
|
||||
**Ubuntu, Popos (unsuported):**
|
||||
|
||||
instructions removed as outdated
|
||||
|
||||
## Installing
|
||||
|
||||
Download repositories are available [here](https://download.opensuse.org/repositories/home:/luke_nukem:/asus/) for the latest versions of Fedora, Ubuntu, and openSUSE.
|
||||
- Fedora copr = https://copr.fedorainfracloud.org/coprs/lukenukem/asus-linux/
|
||||
- openSUSE = https://download.opensuse.org/repositories/home:/luke_nukem:/asus/
|
||||
|
||||
---
|
||||
|
||||
Run `make` then `sudo make install` then reboot.
|
||||
=======
|
||||
|
||||
The default init method is to use the udev rule, this ensures that the service is
|
||||
started when the device is initialised and ready.
|
||||
|
||||
You may also need to activate the service for debian install. If running Pop!\_OS, I suggest disabling `system76-power` gnome-shell extension and systemd service.
|
||||
|
||||
## Upgrading
|
||||
|
||||
If you are upgrading from a previous installed version, you will need to restart the service or reboot.
|
||||
|
||||
```
|
||||
$ systemctl daemon-reload && systemctl restart asusd
|
||||
```
|
||||
|
||||
You may also need to activate the service for debian install. If running Pop!_OS, I suggest disabling `system76-power` gnome-shell extension and systemd service.
|
||||
|
||||
If you would like to run this daemon on another non-ASUS laptop you can. You'll
|
||||
have all features available except the LED and AniMe control (further controllers
|
||||
can be added on request). You will need to install the alternative service from
|
||||
`data/asusd-alt.service`.
|
||||
|
||||
## Uninstalling
|
||||
|
||||
Run `sudo make uninstall` in the source repo, and remove `/etc/asusd/`.
|
||||
|
||||
# Contributing
|
||||
|
||||
See `CONTRIBUTING.md`. Additionally, also do `cargo clean` and `cargo test` on first checkout to ensure the commit hooks are used (via `cargo-husky`).
|
||||
|
||||
Generation of the bindings with `make bindings` requires `typeshare` to be installed.
|
||||
|
||||
Dbus introsepction XML requires with `make introspection` requires `anime_sim` to be running before starting `asusd`.
|
||||
|
||||
# OTHER
|
||||
|
||||
## AniMe Matrix simulator
|
||||
|
||||
A simulator using SDL2 can be built using `cargo build --package rog_simulators` and run with `./target/debug/anime_sim`. Once started `asusd` will need restarting to pick it up. If running this sim on a laptop _with_ the display, the simulated display will be used instead of the physical display.
|
||||
|
||||
## Supporting more laptops
|
||||
|
||||
Please file a support request.
|
||||
@@ -111,4 +148,4 @@ Reference to any ASUS products, services, processes, or other information and/or
|
||||
|
||||
The use of ROG and ASUS trademarks within this website and associated tools and libraries is only to provide a recognisable identifier to users to enable them to associate that these tools will work with ASUS ROG laptops.
|
||||
|
||||
---
|
||||
---
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
[package]
|
||||
name = "asus-notify"
|
||||
version = "3.0.0"
|
||||
authors = ["Luke D Jones <luke@ljones.dev>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
# serialisation
|
||||
serde_json = "^1.0"
|
||||
rog_dbus = { path = "../rog-dbus" }
|
||||
rog_profiles = { path = "../rog-profiles" }
|
||||
rog_types = { path = "../rog-types" }
|
||||
daemon = { path = "../daemon" }
|
||||
|
||||
[dependencies.notify-rust]
|
||||
version = "^4.3"
|
||||
default-features = false
|
||||
features = ["z"]
|
||||
@@ -1,110 +0,0 @@
|
||||
use notify_rust::{Hint, Notification, NotificationHandle};
|
||||
use rog_dbus::{DbusProxies, Signals};
|
||||
use rog_profiles::profiles::{FanLevel, Profile};
|
||||
use std::error::Error;
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("asus-notify version {}", env!("CARGO_PKG_VERSION"));
|
||||
println!(" daemon version {}", daemon::VERSION);
|
||||
println!(" rog-dbus version {}", rog_dbus::VERSION);
|
||||
|
||||
let (proxies, conn) = DbusProxies::new()?;
|
||||
let signals = Signals::new(&proxies)?;
|
||||
|
||||
let mut last_profile_notif: Option<NotificationHandle> = None;
|
||||
let mut last_led_notif: Option<NotificationHandle> = None;
|
||||
let mut last_gfx_notif: Option<NotificationHandle> = None;
|
||||
let mut last_chrg_notif: Option<NotificationHandle> = None;
|
||||
|
||||
let recv = proxies.setup_recv(conn);
|
||||
let mut err_count = 0;
|
||||
loop {
|
||||
sleep(Duration::from_millis(100));
|
||||
if let Err(err) = recv.next_signal() {
|
||||
if err_count < 3 {
|
||||
err_count += 1;
|
||||
println!("{}", err);
|
||||
}
|
||||
if err_count == 3 {
|
||||
err_count += 1;
|
||||
println!("Max error count reached. Spooling silently.");
|
||||
}
|
||||
sleep(Duration::from_millis(2000));
|
||||
continue;
|
||||
}
|
||||
err_count = 0;
|
||||
|
||||
if let Ok(vendor) = signals.gfx_vendor.try_recv() {
|
||||
if let Some(notif) = last_gfx_notif.take() {
|
||||
notif.close();
|
||||
}
|
||||
let x = do_notif(&format!(
|
||||
"Graphics mode changed to {}",
|
||||
<&str>::from(vendor)
|
||||
))?;
|
||||
last_gfx_notif = Some(x);
|
||||
}
|
||||
|
||||
if let Ok(limit) = signals.charge.try_recv() {
|
||||
if let Some(notif) = last_chrg_notif.take() {
|
||||
notif.close();
|
||||
}
|
||||
let x = do_notif(&format!("Battery charge limit changed to {}", limit))?;
|
||||
last_chrg_notif = Some(x);
|
||||
}
|
||||
|
||||
if let Ok(profile) = signals.profile.try_recv() {
|
||||
if let Some(notif) = last_profile_notif.take() {
|
||||
notif.close();
|
||||
}
|
||||
let x = do_thermal_notif(&profile)?;
|
||||
last_profile_notif = Some(x);
|
||||
}
|
||||
|
||||
if let Ok(ledmode) = signals.led_mode.try_recv() {
|
||||
if let Some(notif) = last_led_notif.take() {
|
||||
notif.close();
|
||||
}
|
||||
let x = do_notif(&format!(
|
||||
"Keyboard LED mode changed to {}",
|
||||
ledmode.mode_name()
|
||||
))?;
|
||||
last_led_notif = Some(x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn do_thermal_notif(profile: &Profile) -> Result<NotificationHandle, Box<dyn Error>> {
|
||||
let fan = profile.fan_preset;
|
||||
let turbo = if profile.turbo { "enabled" } else { "disabled" };
|
||||
let icon = match fan {
|
||||
FanLevel::Normal => "asus_notif_yellow",
|
||||
FanLevel::Boost => "asus_notif_red",
|
||||
FanLevel::Silent => "asus_notif_green",
|
||||
};
|
||||
let x = Notification::new()
|
||||
.summary("ASUS ROG")
|
||||
.body(&format!(
|
||||
"Thermal profile changed to {}, turbo {}",
|
||||
profile.name.to_uppercase(),
|
||||
turbo
|
||||
))
|
||||
.hint(Hint::Resident(true))
|
||||
.timeout(2000)
|
||||
.hint(Hint::Category("device".into()))
|
||||
//.hint(Hint::Transient(true))
|
||||
.icon(icon)
|
||||
.show()?;
|
||||
Ok(x)
|
||||
}
|
||||
|
||||
fn do_notif(body: &str) -> Result<NotificationHandle, Box<dyn Error>> {
|
||||
let x = Notification::new()
|
||||
.summary("ASUS ROG")
|
||||
.body(body)
|
||||
.timeout(2000)
|
||||
.show()?;
|
||||
Ok(x)
|
||||
}
|
||||
@@ -1,23 +1,30 @@
|
||||
[package]
|
||||
name = "asusctl"
|
||||
version = "3.5.0"
|
||||
license = "MPL-2.0"
|
||||
authors = ["Luke D Jones <luke@ljones.dev>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
edition = "2021"
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
rog_anime = { path = "../rog-anime" }
|
||||
rog_slash = { path = "../rog-slash" }
|
||||
rog_aura = { path = "../rog-aura" }
|
||||
rog_dbus = { path = "../rog-dbus" }
|
||||
rog_profiles = { path = "../rog-profiles" }
|
||||
rog_types = { path = "../rog-types" }
|
||||
rog_fan_curve = { version = "^0.1", features = ["serde"] }
|
||||
gumdrop = "^0.8"
|
||||
yansi-term = "^0.1"
|
||||
rog_platform = { path = "../rog-platform" }
|
||||
asusd = { path = "../asusd" }
|
||||
dmi_id = { path = "../dmi-id" }
|
||||
|
||||
gumdrop.workspace = true
|
||||
toml.workspace = true
|
||||
zbus.workspace = true
|
||||
tokio.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
tinybmp = "^0.2.3"
|
||||
glam = "0.14.0"
|
||||
gif.workspace = true
|
||||
tinybmp.workspace = true
|
||||
glam.workspace = true
|
||||
rog_dbus = { path = "../rog-dbus" }
|
||||
gif = "^0.11.2"
|
||||
tokio.workspace = true
|
||||
|
||||
cargo-husky.workspace = true
|
||||
@@ -1,26 +1,34 @@
|
||||
use std::{env, error::Error, path::Path, process::exit};
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
use std::path::Path;
|
||||
use std::process::exit;
|
||||
|
||||
use rog_anime::{AnimeDataBuffer, AnimeDiagonal};
|
||||
use rog_dbus::RogDbusClient;
|
||||
use rog_anime::usb::get_anime_type;
|
||||
use rog_anime::{AnimeDiagonal, AnimeType};
|
||||
use rog_dbus::zbus_anime::AnimeProxyBlocking;
|
||||
use zbus::blocking::Connection;
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let (client, _) = RogDbusClient::new().unwrap();
|
||||
let conn = Connection::system().unwrap();
|
||||
let proxy = AnimeProxyBlocking::new(&conn).unwrap();
|
||||
|
||||
let args: Vec<String> = env::args().into_iter().collect();
|
||||
let args: Vec<String> = env::args().collect();
|
||||
if args.len() != 3 {
|
||||
println!("Usage: <filepath> <brightness>");
|
||||
println!("e.g, asusctl/examples/doom_large.png 0.8");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
let matrix =
|
||||
AnimeDiagonal::from_png(Path::new(&args[1]), None, args[2].parse::<f32>().unwrap())?;
|
||||
let matrix = AnimeDiagonal::from_png(
|
||||
Path::new(&args[1]),
|
||||
None,
|
||||
args[2].parse::<f32>().unwrap(),
|
||||
AnimeType::GA401,
|
||||
)?;
|
||||
|
||||
client
|
||||
.proxies()
|
||||
.anime()
|
||||
.write(<AnimeDataBuffer>::from(&matrix))
|
||||
.unwrap();
|
||||
let anime_type = get_anime_type()?;
|
||||
|
||||
proxy.write(matrix.into_data_buffer(anime_type)?).unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
use std::{thread::sleep, time::Duration};
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
|
||||
use rog_anime::{AnimeDataBuffer, AnimeDiagonal};
|
||||
use rog_dbus::RogDbusClient;
|
||||
use rog_anime::usb::get_anime_type;
|
||||
use rog_anime::{AnimeDiagonal, AnimeType};
|
||||
use rog_dbus::zbus_anime::AnimeProxyBlocking;
|
||||
use zbus::blocking::Connection;
|
||||
|
||||
// In usable data:
|
||||
// Top row start at 1, ends at 32
|
||||
@@ -9,24 +12,27 @@ use rog_dbus::RogDbusClient;
|
||||
// 74w x 36h diagonal used by the windows app
|
||||
|
||||
fn main() {
|
||||
let (client, _) = RogDbusClient::new().unwrap();
|
||||
let conn = Connection::system().unwrap();
|
||||
let proxy = AnimeProxyBlocking::new(&conn).unwrap();
|
||||
|
||||
for step in (2..50).rev() {
|
||||
let mut matrix = AnimeDiagonal::new(None);
|
||||
for c in (0..60).into_iter().step_by(step) {
|
||||
let mut matrix = AnimeDiagonal::new(AnimeType::GA401, None);
|
||||
for c in (0..60).step_by(step) {
|
||||
for i in matrix.get_mut().iter_mut() {
|
||||
i[c] = 50;
|
||||
}
|
||||
}
|
||||
|
||||
for c in (0..35).into_iter().step_by(step) {
|
||||
for i in matrix.get_mut()[c].iter_mut() {
|
||||
for c in (0..35).step_by(step) {
|
||||
for i in &mut matrix.get_mut()[c] {
|
||||
*i = 50;
|
||||
}
|
||||
}
|
||||
|
||||
let m = <AnimeDataBuffer>::from(&matrix);
|
||||
client.proxies().anime().write(m).unwrap();
|
||||
let anime_type = get_anime_type().unwrap();
|
||||
proxy
|
||||
.write(matrix.into_data_buffer(anime_type).unwrap())
|
||||
.unwrap();
|
||||
sleep(Duration::from_millis(300));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
use std::{env, path::Path, thread::sleep};
|
||||
use std::env;
|
||||
use std::path::Path;
|
||||
use std::thread::sleep;
|
||||
|
||||
use rog_anime::{ActionData, AnimeAction, Sequences};
|
||||
use rog_dbus::RogDbusClient;
|
||||
use rog_anime::usb::get_anime_type;
|
||||
use rog_anime::{ActionData, ActionLoader, Sequences};
|
||||
use rog_dbus::zbus_anime::AnimeProxyBlocking;
|
||||
use zbus::blocking::Connection;
|
||||
|
||||
fn main() {
|
||||
let (client, _) = RogDbusClient::new().unwrap();
|
||||
let conn = Connection::system().unwrap();
|
||||
let proxy = AnimeProxyBlocking::new(&conn).unwrap();
|
||||
|
||||
let args: Vec<String> = env::args().into_iter().collect();
|
||||
let args: Vec<String> = env::args().collect();
|
||||
if args.len() != 3 {
|
||||
println!("Please supply filepath and brightness");
|
||||
return;
|
||||
@@ -14,10 +19,11 @@ fn main() {
|
||||
|
||||
let path = Path::new(&args[1]);
|
||||
let brightness = args[2].parse::<f32>().unwrap();
|
||||
let mut seq = Sequences::new();
|
||||
let anime_type = get_anime_type().unwrap();
|
||||
let mut seq = Sequences::new(anime_type);
|
||||
seq.insert(
|
||||
0,
|
||||
&AnimeAction::AsusAnimation {
|
||||
&ActionLoader::AsusAnimation {
|
||||
file: path.into(),
|
||||
time: rog_anime::AnimTime::Infinite,
|
||||
brightness,
|
||||
@@ -29,11 +35,7 @@ fn main() {
|
||||
for action in seq.iter() {
|
||||
if let ActionData::Animation(frames) = action {
|
||||
for frame in frames.frames() {
|
||||
client
|
||||
.proxies()
|
||||
.anime()
|
||||
.write(frame.frame().clone())
|
||||
.unwrap();
|
||||
proxy.write(frame.frame().clone()).unwrap();
|
||||
sleep(frame.delay());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use rog_anime::usb::get_anime_type;
|
||||
use rog_anime::{AnimeDataBuffer, AnimeGrid};
|
||||
use rog_dbus::RogDbusClient;
|
||||
use rog_dbus::zbus_anime::AnimeProxyBlocking;
|
||||
use zbus::blocking::Connection;
|
||||
|
||||
// In usable data:
|
||||
// Top row start at 1, ends at 32
|
||||
@@ -7,15 +11,17 @@ use rog_dbus::RogDbusClient;
|
||||
// 74w x 36h diagonal used by the windows app
|
||||
|
||||
fn main() {
|
||||
let (client, _) = RogDbusClient::new().unwrap();
|
||||
let mut matrix = AnimeGrid::new(None);
|
||||
let conn = Connection::system().unwrap();
|
||||
let proxy = AnimeProxyBlocking::new(&conn).unwrap();
|
||||
|
||||
let anime_type = get_anime_type().unwrap();
|
||||
let mut matrix = AnimeGrid::new(anime_type);
|
||||
let tmp = matrix.get_mut();
|
||||
|
||||
let mut i = 0;
|
||||
for (y, row) in tmp.iter_mut().enumerate() {
|
||||
if y % 2 == 0 && i + 1 != row.len() - 1 {
|
||||
i += 1;
|
||||
dbg!(i);
|
||||
}
|
||||
row[row.len() - i] = 0x22;
|
||||
if i > 5 {
|
||||
@@ -38,7 +44,7 @@ fn main() {
|
||||
}
|
||||
}
|
||||
|
||||
let matrix = <AnimeDataBuffer>::from(matrix);
|
||||
let matrix = <AnimeDataBuffer>::try_from(matrix).unwrap();
|
||||
|
||||
client.proxies().anime().write(matrix).unwrap();
|
||||
proxy.write(matrix).unwrap();
|
||||
}
|
||||
|
||||
@@ -1,129 +1,133 @@
|
||||
use rog_anime::usb::get_anime_type;
|
||||
use rog_anime::AnimeDataBuffer;
|
||||
use rog_dbus::RogDbusClient;
|
||||
use rog_dbus::zbus_anime::AnimeProxyBlocking;
|
||||
use zbus::blocking::Connection;
|
||||
|
||||
// In usable data:
|
||||
// Top row start at 1, ends at 32
|
||||
|
||||
fn main() {
|
||||
let (client, _) = RogDbusClient::new().unwrap();
|
||||
let mut matrix = AnimeDataBuffer::new();
|
||||
matrix.get_mut()[1] = 100; // start = 1
|
||||
for n in matrix.get_mut()[2..32].iter_mut() {
|
||||
let conn = Connection::system().unwrap();
|
||||
let proxy = AnimeProxyBlocking::new(&conn).unwrap();
|
||||
let anime_type = get_anime_type().unwrap();
|
||||
let mut matrix = AnimeDataBuffer::new(anime_type);
|
||||
matrix.data_mut()[1] = 100; // start = 1
|
||||
for n in matrix.data_mut()[2..32].iter_mut() {
|
||||
*n = 250;
|
||||
}
|
||||
matrix.get_mut()[32] = 100; // end
|
||||
matrix.get_mut()[34] = 100; // start x = 0
|
||||
matrix.get_mut()[66] = 100; // end
|
||||
matrix.get_mut()[69] = 100; // start x = 1
|
||||
matrix.get_mut()[101] = 100; // end
|
||||
matrix.get_mut()[102] = 100; // start
|
||||
matrix.get_mut()[134] = 100; // end
|
||||
matrix.get_mut()[137] = 100; // start
|
||||
matrix.get_mut()[169] = 100; // end
|
||||
matrix.get_mut()[170] = 100; // start
|
||||
matrix.get_mut()[202] = 100; // end
|
||||
matrix.get_mut()[204] = 100; // start
|
||||
matrix.get_mut()[236] = 100; // end
|
||||
matrix.get_mut()[237] = 100; // start
|
||||
matrix.get_mut()[268] = 100; // end
|
||||
matrix.get_mut()[270] = 100; // start
|
||||
matrix.get_mut()[301] = 100; // end
|
||||
matrix.get_mut()[302] = 100; // start
|
||||
matrix.get_mut()[332] = 100; // end
|
||||
matrix.get_mut()[334] = 100; // start
|
||||
matrix.get_mut()[364] = 100; // end
|
||||
matrix.get_mut()[365] = 100; // start
|
||||
matrix.get_mut()[394] = 100; // end
|
||||
matrix.get_mut()[396] = 100; // start
|
||||
matrix.get_mut()[425] = 100; // end
|
||||
matrix.get_mut()[426] = 100; // start
|
||||
matrix.get_mut()[454] = 100; // end
|
||||
matrix.get_mut()[456] = 100; // start
|
||||
matrix.get_mut()[484] = 100; // end
|
||||
matrix.get_mut()[485] = 100; // start
|
||||
matrix.get_mut()[512] = 100; // end
|
||||
matrix.get_mut()[514] = 100; // start
|
||||
matrix.get_mut()[541] = 100; // end
|
||||
matrix.get_mut()[542] = 100; // start
|
||||
matrix.get_mut()[568] = 100; // end
|
||||
matrix.get_mut()[570] = 100; // start
|
||||
matrix.get_mut()[596] = 100; // end
|
||||
matrix.get_mut()[597] = 100; // start
|
||||
matrix.get_mut()[622] = 100; // end
|
||||
matrix.get_mut()[624] = 100; // start
|
||||
matrix.get_mut()[649] = 100; // end
|
||||
matrix.get_mut()[650] = 100; // start
|
||||
matrix.get_mut()[674] = 100; // end
|
||||
matrix.get_mut()[676] = 100; // start
|
||||
matrix.get_mut()[700] = 100; // end
|
||||
matrix.get_mut()[701] = 100; // start
|
||||
matrix.get_mut()[724] = 100; // end
|
||||
matrix.get_mut()[726] = 100; // start
|
||||
matrix.get_mut()[749] = 100; // end
|
||||
matrix.get_mut()[750] = 100; // start
|
||||
matrix.get_mut()[772] = 100; // end
|
||||
matrix.get_mut()[774] = 100; // start
|
||||
matrix.get_mut()[796] = 100; // end
|
||||
matrix.get_mut()[797] = 100; // start
|
||||
matrix.get_mut()[818] = 100; // end
|
||||
matrix.get_mut()[820] = 100; // start
|
||||
matrix.get_mut()[841] = 100; // end
|
||||
matrix.get_mut()[842] = 100; // start
|
||||
matrix.get_mut()[862] = 100; // end
|
||||
matrix.get_mut()[864] = 100; // start
|
||||
matrix.get_mut()[884] = 100; // end
|
||||
matrix.get_mut()[885] = 100; // start
|
||||
matrix.get_mut()[904] = 100; // end
|
||||
matrix.get_mut()[906] = 100; // start
|
||||
matrix.get_mut()[925] = 100; // end
|
||||
matrix.get_mut()[926] = 100; // start
|
||||
matrix.get_mut()[944] = 100; // end
|
||||
matrix.get_mut()[946] = 100; // start
|
||||
matrix.get_mut()[964] = 100; // end
|
||||
matrix.get_mut()[965] = 100; // start
|
||||
matrix.get_mut()[982] = 100; // end
|
||||
matrix.get_mut()[984] = 100; // start
|
||||
matrix.get_mut()[1001] = 100; // end
|
||||
matrix.get_mut()[1002] = 100; // start
|
||||
matrix.get_mut()[1018] = 100; // end
|
||||
matrix.get_mut()[1020] = 100; // start
|
||||
matrix.get_mut()[1036] = 100; // end
|
||||
matrix.get_mut()[1037] = 100; // start
|
||||
matrix.get_mut()[1052] = 100; // end
|
||||
matrix.get_mut()[1054] = 100; // start
|
||||
matrix.get_mut()[1069] = 100; // end
|
||||
matrix.get_mut()[1070] = 100; // start
|
||||
matrix.get_mut()[1084] = 100; // end
|
||||
matrix.get_mut()[1086] = 100; // start
|
||||
matrix.get_mut()[1100] = 100; // end
|
||||
matrix.get_mut()[1101] = 100; // start
|
||||
matrix.get_mut()[1114] = 100; // end
|
||||
matrix.get_mut()[1116] = 100; // start
|
||||
matrix.get_mut()[1129] = 100; // end
|
||||
matrix.get_mut()[1130] = 100; // start
|
||||
matrix.get_mut()[1142] = 100; // end
|
||||
matrix.get_mut()[1144] = 100; // start
|
||||
matrix.get_mut()[1156] = 100; // end
|
||||
matrix.get_mut()[1157] = 100; // start
|
||||
matrix.get_mut()[1168] = 100; // end
|
||||
matrix.get_mut()[1170] = 100; // start
|
||||
matrix.get_mut()[1181] = 100; // end
|
||||
matrix.get_mut()[1182] = 100; // start
|
||||
matrix.get_mut()[1192] = 100; // end
|
||||
matrix.get_mut()[1194] = 100; // start
|
||||
matrix.get_mut()[1204] = 100; // end
|
||||
matrix.get_mut()[1205] = 100; // start
|
||||
matrix.get_mut()[1214] = 100; // end
|
||||
matrix.get_mut()[1216] = 100; // start
|
||||
matrix.get_mut()[1225] = 100; // end
|
||||
matrix.get_mut()[1226] = 100; // start
|
||||
matrix.get_mut()[1234] = 100; // end
|
||||
matrix.get_mut()[1236] = 100; // start
|
||||
for n in matrix.get_mut()[1237..1244].iter_mut() {
|
||||
matrix.data_mut()[32] = 100; // end
|
||||
matrix.data_mut()[34] = 100; // start x = 0
|
||||
matrix.data_mut()[66] = 100; // end
|
||||
matrix.data_mut()[69] = 100; // start x = 1
|
||||
matrix.data_mut()[101] = 100; // end
|
||||
matrix.data_mut()[102] = 100; // start
|
||||
matrix.data_mut()[134] = 100; // end
|
||||
matrix.data_mut()[137] = 100; // start
|
||||
matrix.data_mut()[169] = 100; // end
|
||||
matrix.data_mut()[170] = 100; // start
|
||||
matrix.data_mut()[202] = 100; // end
|
||||
matrix.data_mut()[204] = 100; // start
|
||||
matrix.data_mut()[236] = 100; // end
|
||||
matrix.data_mut()[237] = 100; // start
|
||||
matrix.data_mut()[268] = 100; // end
|
||||
matrix.data_mut()[270] = 100; // start
|
||||
matrix.data_mut()[301] = 100; // end
|
||||
matrix.data_mut()[302] = 100; // start
|
||||
matrix.data_mut()[332] = 100; // end
|
||||
matrix.data_mut()[334] = 100; // start
|
||||
matrix.data_mut()[364] = 100; // end
|
||||
matrix.data_mut()[365] = 100; // start
|
||||
matrix.data_mut()[394] = 100; // end
|
||||
matrix.data_mut()[396] = 100; // start
|
||||
matrix.data_mut()[425] = 100; // end
|
||||
matrix.data_mut()[426] = 100; // start
|
||||
matrix.data_mut()[454] = 100; // end
|
||||
matrix.data_mut()[456] = 100; // start
|
||||
matrix.data_mut()[484] = 100; // end
|
||||
matrix.data_mut()[485] = 100; // start
|
||||
matrix.data_mut()[512] = 100; // end
|
||||
matrix.data_mut()[514] = 100; // start
|
||||
matrix.data_mut()[541] = 100; // end
|
||||
matrix.data_mut()[542] = 100; // start
|
||||
matrix.data_mut()[568] = 100; // end
|
||||
matrix.data_mut()[570] = 100; // start
|
||||
matrix.data_mut()[596] = 100; // end
|
||||
matrix.data_mut()[597] = 100; // start
|
||||
matrix.data_mut()[622] = 100; // end
|
||||
matrix.data_mut()[624] = 100; // start
|
||||
matrix.data_mut()[649] = 100; // end
|
||||
matrix.data_mut()[650] = 100; // start
|
||||
matrix.data_mut()[674] = 100; // end
|
||||
matrix.data_mut()[676] = 100; // start
|
||||
matrix.data_mut()[700] = 100; // end
|
||||
matrix.data_mut()[701] = 100; // start
|
||||
matrix.data_mut()[724] = 100; // end
|
||||
matrix.data_mut()[726] = 100; // start
|
||||
matrix.data_mut()[749] = 100; // end
|
||||
matrix.data_mut()[750] = 100; // start
|
||||
matrix.data_mut()[772] = 100; // end
|
||||
matrix.data_mut()[774] = 100; // start
|
||||
matrix.data_mut()[796] = 100; // end
|
||||
matrix.data_mut()[797] = 100; // start
|
||||
matrix.data_mut()[818] = 100; // end
|
||||
matrix.data_mut()[820] = 100; // start
|
||||
matrix.data_mut()[841] = 100; // end
|
||||
matrix.data_mut()[842] = 100; // start
|
||||
matrix.data_mut()[862] = 100; // end
|
||||
matrix.data_mut()[864] = 100; // start
|
||||
matrix.data_mut()[884] = 100; // end
|
||||
matrix.data_mut()[885] = 100; // start
|
||||
matrix.data_mut()[904] = 100; // end
|
||||
matrix.data_mut()[906] = 100; // start
|
||||
matrix.data_mut()[925] = 100; // end
|
||||
matrix.data_mut()[926] = 100; // start
|
||||
matrix.data_mut()[944] = 100; // end
|
||||
matrix.data_mut()[946] = 100; // start
|
||||
matrix.data_mut()[964] = 100; // end
|
||||
matrix.data_mut()[965] = 100; // start
|
||||
matrix.data_mut()[982] = 100; // end
|
||||
matrix.data_mut()[984] = 100; // start
|
||||
matrix.data_mut()[1001] = 100; // end
|
||||
matrix.data_mut()[1002] = 100; // start
|
||||
matrix.data_mut()[1018] = 100; // end
|
||||
matrix.data_mut()[1020] = 100; // start
|
||||
matrix.data_mut()[1036] = 100; // end
|
||||
matrix.data_mut()[1037] = 100; // start
|
||||
matrix.data_mut()[1052] = 100; // end
|
||||
matrix.data_mut()[1054] = 100; // start
|
||||
matrix.data_mut()[1069] = 100; // end
|
||||
matrix.data_mut()[1070] = 100; // start
|
||||
matrix.data_mut()[1084] = 100; // end
|
||||
matrix.data_mut()[1086] = 100; // start
|
||||
matrix.data_mut()[1100] = 100; // end
|
||||
matrix.data_mut()[1101] = 100; // start
|
||||
matrix.data_mut()[1114] = 100; // end
|
||||
matrix.data_mut()[1116] = 100; // start
|
||||
matrix.data_mut()[1129] = 100; // end
|
||||
matrix.data_mut()[1130] = 100; // start
|
||||
matrix.data_mut()[1142] = 100; // end
|
||||
matrix.data_mut()[1144] = 100; // start
|
||||
matrix.data_mut()[1156] = 100; // end
|
||||
matrix.data_mut()[1157] = 100; // start
|
||||
matrix.data_mut()[1168] = 100; // end
|
||||
matrix.data_mut()[1170] = 100; // start
|
||||
matrix.data_mut()[1181] = 100; // end
|
||||
matrix.data_mut()[1182] = 100; // start
|
||||
matrix.data_mut()[1192] = 100; // end
|
||||
matrix.data_mut()[1194] = 100; // start
|
||||
matrix.data_mut()[1204] = 100; // end
|
||||
matrix.data_mut()[1205] = 100; // start
|
||||
matrix.data_mut()[1214] = 100; // end
|
||||
matrix.data_mut()[1216] = 100; // start
|
||||
matrix.data_mut()[1225] = 100; // end
|
||||
matrix.data_mut()[1226] = 100; // start
|
||||
matrix.data_mut()[1234] = 100; // end
|
||||
matrix.data_mut()[1236] = 100; // start
|
||||
for n in matrix.data_mut()[1237..1244].iter_mut() {
|
||||
*n = 250;
|
||||
}
|
||||
matrix.get_mut()[1244] = 100; // end
|
||||
matrix.data_mut()[1244] = 100; // end
|
||||
println!("{:?}", &matrix);
|
||||
|
||||
client.proxies().anime().write(matrix).unwrap();
|
||||
proxy.write(matrix).unwrap();
|
||||
}
|
||||
|
||||
@@ -1,20 +1,26 @@
|
||||
use std::{env, error::Error, path::Path, process::exit};
|
||||
use std::convert::TryFrom;
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
use std::path::Path;
|
||||
use std::process::exit;
|
||||
|
||||
use rog_anime::{
|
||||
AnimeDataBuffer, {AnimeImage, Vec2},
|
||||
};
|
||||
use rog_dbus::RogDbusClient;
|
||||
use rog_anime::usb::get_anime_type;
|
||||
use rog_anime::{AnimeDataBuffer, AnimeImage, Vec2};
|
||||
use rog_dbus::zbus_anime::AnimeProxyBlocking;
|
||||
use zbus::blocking::Connection;
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let (client, _) = RogDbusClient::new().unwrap();
|
||||
let conn = Connection::system().unwrap();
|
||||
let proxy = AnimeProxyBlocking::new(&conn).unwrap();
|
||||
|
||||
let args: Vec<String> = env::args().into_iter().collect();
|
||||
let args: Vec<String> = env::args().collect();
|
||||
if args.len() != 7 {
|
||||
println!("Usage: <filepath> <scale> <angle> <x pos> <y pos> <brightness>");
|
||||
println!("e.g, asusctl/examples/doom_large.png 0.9 0.4 0.0 0.0 0.8");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
let anime_type = get_anime_type()?;
|
||||
let matrix = AnimeImage::from_png(
|
||||
Path::new(&args[1]),
|
||||
args[2].parse::<f32>().unwrap(),
|
||||
@@ -24,13 +30,10 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
args[5].parse::<f32>().unwrap(),
|
||||
),
|
||||
args[6].parse::<f32>().unwrap(),
|
||||
anime_type,
|
||||
)?;
|
||||
|
||||
client
|
||||
.proxies()
|
||||
.anime()
|
||||
.write(<AnimeDataBuffer>::from(&matrix))
|
||||
.unwrap();
|
||||
proxy.write(<AnimeDataBuffer>::try_from(&matrix)?).unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,22 +1,29 @@
|
||||
use std::{
|
||||
env, error::Error, f32::consts::PI, path::Path, process::exit, thread::sleep, time::Duration,
|
||||
};
|
||||
use std::convert::TryFrom;
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
use std::f32::consts::PI;
|
||||
use std::path::Path;
|
||||
use std::process::exit;
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
|
||||
use rog_anime::{
|
||||
AnimeDataBuffer, {AnimeImage, Vec2},
|
||||
};
|
||||
use rog_dbus::RogDbusClient;
|
||||
use rog_anime::usb::get_anime_type;
|
||||
use rog_anime::{AnimeDataBuffer, AnimeImage, Vec2};
|
||||
use rog_dbus::zbus_anime::AnimeProxyBlocking;
|
||||
use zbus::blocking::Connection;
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let (client, _) = RogDbusClient::new().unwrap();
|
||||
let conn = Connection::system().unwrap();
|
||||
let proxy = AnimeProxyBlocking::new(&conn).unwrap();
|
||||
|
||||
let args: Vec<String> = env::args().into_iter().collect();
|
||||
let args: Vec<String> = env::args().collect();
|
||||
if args.len() != 7 {
|
||||
println!("Usage: <filepath> <scale> <angle> <x pos> <y pos> <brightness>");
|
||||
println!("e.g, asusctl/examples/doom_large.png 0.9 0.4 0.0 0.0 0.8");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
let anime_type = get_anime_type()?;
|
||||
let mut matrix = AnimeImage::from_png(
|
||||
Path::new(&args[1]),
|
||||
args[2].parse::<f32>().unwrap(),
|
||||
@@ -26,20 +33,17 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
args[5].parse::<f32>().unwrap(),
|
||||
),
|
||||
args[6].parse::<f32>().unwrap(),
|
||||
anime_type,
|
||||
)?;
|
||||
|
||||
loop {
|
||||
matrix.angle += 0.05;
|
||||
if matrix.angle > PI * 2.0 {
|
||||
matrix.angle = 0.0
|
||||
matrix.angle = 0.0;
|
||||
}
|
||||
matrix.update();
|
||||
|
||||
client
|
||||
.proxies()
|
||||
.anime()
|
||||
.write(<AnimeDataBuffer>::from(&matrix))
|
||||
.unwrap();
|
||||
proxy.write(<AnimeDataBuffer>::try_from(&matrix)?).unwrap();
|
||||
sleep(Duration::from_micros(500));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
use rog_aura::{GX502Layout, Key, KeyColourArray, KeyLayout};
|
||||
use rog_dbus::RogDbusClient;
|
||||
use std::collections::LinkedList;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Ball {
|
||||
position: (i32, i32),
|
||||
direction: (i32, i32),
|
||||
trail: LinkedList<(i32, i32)>,
|
||||
}
|
||||
impl Ball {
|
||||
fn new(x: i32, y: i32, trail_len: u32) -> Self {
|
||||
let mut trail = LinkedList::new();
|
||||
for _ in 1..=trail_len {
|
||||
trail.push_back((x, y));
|
||||
}
|
||||
|
||||
Ball {
|
||||
position: (x, y),
|
||||
direction: (1, 1),
|
||||
trail,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::if_same_then_else)]
|
||||
fn update(&mut self, key_map: &[[Key; 17]]) {
|
||||
let pos = self.position;
|
||||
let dir = self.direction;
|
||||
|
||||
if pos.0 + dir.0 > key_map[pos.1 as usize].len() as i32 - 1 || pos.0 + dir.0 < 0 {
|
||||
self.direction.0 *= -1;
|
||||
} else if key_map[(pos.1) as usize][(pos.0 + dir.0) as usize] == Key::None {
|
||||
self.direction.0 *= -1;
|
||||
}
|
||||
|
||||
if pos.1 + dir.1 > key_map.len() as i32 - 1 || pos.1 + dir.1 < 0 {
|
||||
self.direction.1 *= -1;
|
||||
} else if key_map[(pos.1 + dir.1) as usize][(pos.0) as usize] == Key::None {
|
||||
self.direction.1 *= -1;
|
||||
}
|
||||
|
||||
self.trail.pop_front();
|
||||
self.trail.push_back(self.position);
|
||||
|
||||
self.position.0 += self.direction.0;
|
||||
self.position.1 += self.direction.1;
|
||||
|
||||
if self.position.0 > key_map[self.position.1 as usize].len() as i32 {
|
||||
self.position.0 = key_map[self.position.1 as usize].len() as i32 - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let (dbus, _) = RogDbusClient::new()?;
|
||||
|
||||
let mut colours = KeyColourArray::new();
|
||||
|
||||
let layout = GX502Layout::default();
|
||||
|
||||
let mut balls = [Ball::new(2, 1, 12), Ball::new(4, 6, 12)];
|
||||
|
||||
dbus.proxies().led().init_effect()?;
|
||||
|
||||
let rows = layout.get_rows();
|
||||
loop {
|
||||
for (n, ball) in balls.iter_mut().enumerate() {
|
||||
ball.update(rows);
|
||||
for (i, pos) in ball.trail.iter().enumerate() {
|
||||
if let Some(c) = colours.key(rows[pos.1 as usize][pos.0 as usize]) {
|
||||
*c.0 = 0;
|
||||
*c.1 = 0;
|
||||
*c.2 = 0;
|
||||
if n == 0 {
|
||||
*c.0 = i as u8 * (255 / ball.trail.len() as u8);
|
||||
} else if n == 1 {
|
||||
*c.1 = i as u8 * (255 / ball.trail.len() as u8);
|
||||
} else if n == 2 {
|
||||
*c.2 = i as u8 * (255 / ball.trail.len() as u8);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(c) = colours.key(rows[ball.position.1 as usize][ball.position.0 as usize]) {
|
||||
*c.0 = 255;
|
||||
*c.1 = 255;
|
||||
*c.2 = 255;
|
||||
};
|
||||
}
|
||||
dbus.proxies().led().set_per_key(&colours)?;
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||
}
|
||||
}
|
||||
113
asusctl/examples/aura-rgb-ball.rs-
Normal file
113
asusctl/examples/aura-rgb-ball.rs-
Normal file
@@ -0,0 +1,113 @@
|
||||
//! Very bad rushed example. The better way to do this would be to have
|
||||
//! the balles move on their own square grid, then translate that to the
|
||||
//! key layout via shape by pitch etc.
|
||||
use rog_aura::{
|
||||
layouts::{KeyLayout, KeyRow},
|
||||
KeyColourArray,
|
||||
};
|
||||
use rog_dbus::RogDbusClientBlocking;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Ball {
|
||||
position: (f32, f32),
|
||||
direction: (f32, f32),
|
||||
trail: VecDeque<(f32, f32)>,
|
||||
}
|
||||
impl Ball {
|
||||
fn new(x: f32, y: f32, trail_len: u32) -> Self {
|
||||
let mut trail = VecDeque::new();
|
||||
for _ in 1..=trail_len {
|
||||
trail.push_back((x, y));
|
||||
}
|
||||
|
||||
Ball {
|
||||
position: (x, y),
|
||||
direction: (1.0, 1.0),
|
||||
trail,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::if_same_then_else)]
|
||||
fn update(&mut self, key_map: &[KeyRow]) {
|
||||
self.position.0 += self.direction.0;
|
||||
self.position.1 += self.direction.1;
|
||||
|
||||
if self.position.1.abs() as usize >= key_map.len() {
|
||||
self.direction.1 *= -1.0;
|
||||
self.position.1 += self.direction.1;
|
||||
self.direction.0 *= -1.0;
|
||||
self.position.0 += self.direction.0;
|
||||
}
|
||||
if self.position.0.abs() as usize >= key_map[self.position.1.abs() as usize].row_ref().len()
|
||||
{
|
||||
self.direction.1 *= -1.0;
|
||||
self.position.1 += self.direction.1;
|
||||
}
|
||||
if self.position.0 as usize >= key_map[self.position.1.abs() as usize].row_ref().len() {
|
||||
self.direction.0 *= -1.0;
|
||||
self.position.0 += self.direction.0;
|
||||
}
|
||||
|
||||
let pos = self.position;
|
||||
|
||||
if pos.1 == key_map[pos.1.abs() as usize].row_ref().len() as f32 - 1.0 || pos.1 <= 0.0 {
|
||||
self.direction.0 *= -1.0;
|
||||
} else if key_map[(pos.1) as usize].row_ref()[(pos.0) as usize].is_placeholder() {
|
||||
self.direction.0 *= -1.0;
|
||||
}
|
||||
|
||||
if pos.0 == key_map.len() as f32 - 1.0 || pos.0 <= 0.0 {
|
||||
self.direction.1 *= -1.0;
|
||||
} else if key_map[(pos.1) as usize].row_ref()[(pos.0) as usize].is_placeholder() {
|
||||
self.direction.1 *= -1.0;
|
||||
}
|
||||
|
||||
self.trail.pop_front();
|
||||
self.trail.push_back(self.position);
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let (dbus, _) = RogDbusClientBlocking::new()?;
|
||||
|
||||
let mut colours = KeyColourArray::new();
|
||||
let layout = KeyLayout::gx502_layout();
|
||||
|
||||
let mut balls = [Ball::new(2.0, 1.0, 12), Ball::new(5.0, 2.0, 12)];
|
||||
// let mut balls = [Ball::new(2, 1, 12)];
|
||||
|
||||
loop {
|
||||
for (n, ball) in balls.iter_mut().enumerate() {
|
||||
ball.update(layout.rows_ref());
|
||||
for (i, pos) in ball.trail.iter().enumerate() {
|
||||
if let Some(c) = colours
|
||||
.rgb_for_key(layout.rows_ref()[pos.1.abs() as usize].row_ref()[pos.0 as usize])
|
||||
{
|
||||
c[0] = 0;
|
||||
c[1] = 0;
|
||||
c[2] = 0;
|
||||
if n == 0 {
|
||||
c[0] = i as u8 * (255 / ball.trail.len() as u8);
|
||||
} else if n == 1 {
|
||||
c[1] = i as u8 * (255 / ball.trail.len() as u8);
|
||||
} else if n == 2 {
|
||||
c[2] = i as u8 * (255 / ball.trail.len() as u8);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(c) = colours.rgb_for_key(
|
||||
layout.rows_ref()[ball.position.1.abs() as usize].row_ref()
|
||||
[ball.position.0 as usize],
|
||||
) {
|
||||
c[0] = 255;
|
||||
c[1] = 255;
|
||||
c[2] = 255;
|
||||
};
|
||||
}
|
||||
dbus.proxies().led().direct_addressing_raw(colours.get())?;
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_millis(150));
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
use rog_aura::{GX502Layout, KeyColourArray, KeyLayout};
|
||||
use rog_dbus::RogDbusClient;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let (dbus, _) = RogDbusClient::new()?;
|
||||
|
||||
let layout = GX502Layout::default();
|
||||
|
||||
dbus.proxies().led().init_effect()?;
|
||||
let rows = layout.get_rows();
|
||||
|
||||
let mut column = 0;
|
||||
loop {
|
||||
let mut key_colours = KeyColourArray::new();
|
||||
for row in rows {
|
||||
if let Some(c) = key_colours.key(row[column as usize]) {
|
||||
*c.0 = 255;
|
||||
};
|
||||
}
|
||||
if column == rows[0].len() - 1 {
|
||||
column = 0
|
||||
} else {
|
||||
column += 1;
|
||||
}
|
||||
|
||||
dbus.proxies().led().set_per_key(&key_colours)?;
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
use rog_aura::{GX502Layout, Key, KeyColourArray, KeyLayout};
|
||||
use rog_dbus::RogDbusClient;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let (dbus, _) = RogDbusClient::new()?;
|
||||
|
||||
let mut key_colours = KeyColourArray::new();
|
||||
let layout = GX502Layout::default();
|
||||
|
||||
dbus.proxies().led().init_effect()?;
|
||||
let rows = layout.get_rows();
|
||||
loop {
|
||||
for (r, row) in rows.iter().enumerate() {
|
||||
for (k, key) in row.iter().enumerate() {
|
||||
if let Some(c) = key_colours.key(*key) {
|
||||
*c.0 = 255;
|
||||
};
|
||||
// Last key of previous row
|
||||
if k == 0 {
|
||||
if r == 0 {
|
||||
let k = &rows[rows.len() - 1][rows[rows.len() - 1].len() - 1];
|
||||
if let Some(c) = key_colours.key(*k) {
|
||||
*c.0 = 0;
|
||||
};
|
||||
} else {
|
||||
let k = &rows[r - 1][rows[r - 1].len() - 1];
|
||||
if let Some(c) = key_colours.key(*k) {
|
||||
*c.0 = 0;
|
||||
};
|
||||
}
|
||||
} else {
|
||||
let k = &rows[r][k - 1];
|
||||
if let Some(c) = key_colours.key(*k) {
|
||||
*c.0 = 0;
|
||||
};
|
||||
}
|
||||
if let Some(c) = key_colours.key(Key::Up) {
|
||||
*c.0 = 255;
|
||||
};
|
||||
*key_colours.key(Key::Left).unwrap().0 = 255;
|
||||
*key_colours.key(Key::Right).unwrap().0 = 255;
|
||||
*key_colours.key(Key::Down).unwrap().0 = 255;
|
||||
|
||||
*key_colours.key(Key::W).unwrap().0 = 255;
|
||||
*key_colours.key(Key::A).unwrap().0 = 255;
|
||||
*key_colours.key(Key::S).unwrap().0 = 255;
|
||||
*key_colours.key(Key::D).unwrap().0 = 255;
|
||||
|
||||
dbus.proxies().led().set_per_key(&key_colours)?;
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
use rog_aura::{Key, KeyColourArray};
|
||||
use rog_dbus::RogDbusClient;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let (dbus, _) = RogDbusClient::new()?;
|
||||
|
||||
let mut key_colours = KeyColourArray::new();
|
||||
|
||||
dbus.proxies().led().init_effect()?;
|
||||
loop {
|
||||
let count = 49;
|
||||
for _ in 0..count {
|
||||
*key_colours.key(Key::Rog).unwrap().0 += 5;
|
||||
*key_colours.key(Key::L).unwrap().0 += 5;
|
||||
*key_colours.key(Key::I).unwrap().0 += 5;
|
||||
*key_colours.key(Key::N).unwrap().0 += 5;
|
||||
*key_colours.key(Key::U).unwrap().0 += 5;
|
||||
*key_colours.key(Key::X).unwrap().0 += 5;
|
||||
dbus.proxies().led().set_per_key(&key_colours)?;
|
||||
}
|
||||
for _ in 0..count {
|
||||
*key_colours.key(Key::Rog).unwrap().0 -= 5;
|
||||
*key_colours.key(Key::L).unwrap().0 -= 5;
|
||||
*key_colours.key(Key::I).unwrap().0 -= 5;
|
||||
*key_colours.key(Key::N).unwrap().0 -= 5;
|
||||
*key_colours.key(Key::U).unwrap().0 -= 5;
|
||||
*key_colours.key(Key::X).unwrap().0 -= 5;
|
||||
dbus.proxies().led().set_per_key(&key_colours)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
use rog_aura::{GX502Layout, KeyColourArray, KeyLayout};
|
||||
use rog_dbus::RogDbusClient;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let (dbus, _) = RogDbusClient::new()?;
|
||||
|
||||
let mut key_colours = KeyColourArray::new();
|
||||
let layout = GX502Layout::default();
|
||||
|
||||
dbus.proxies().led().init_effect()?;
|
||||
let rows = layout.get_rows();
|
||||
|
||||
let mut fade = 50;
|
||||
let mut flip = false;
|
||||
loop {
|
||||
for row in rows {
|
||||
for (k, key) in row.iter().enumerate() {
|
||||
if let Some(c) = key_colours.key(*key) {
|
||||
*c.0 = 255 / fade / (k + 1) as u8;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
dbus.proxies().led().set_per_key(&key_colours)?;
|
||||
|
||||
if flip {
|
||||
if fade > 1 {
|
||||
fade -= 1;
|
||||
} else {
|
||||
flip = !flip;
|
||||
}
|
||||
} else if fade < 17 {
|
||||
fade += 1;
|
||||
} else {
|
||||
flip = !flip;
|
||||
}
|
||||
}
|
||||
}
|
||||
69
asusctl/examples/aura-zoned-breathe.rs
Normal file
69
asusctl/examples/aura-zoned-breathe.rs
Normal file
@@ -0,0 +1,69 @@
|
||||
//! Using a combination of key-colour array plus a key layout to generate
|
||||
//! outputs.
|
||||
|
||||
use rog_aura::effects::{AdvancedEffects, Effect};
|
||||
use rog_aura::keyboard::{KeyLayout, LedCode};
|
||||
use rog_aura::Colour;
|
||||
use rog_dbus::zbus_aura::AuraProxyBlocking;
|
||||
use zbus::blocking::Connection;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let layout = KeyLayout::default_layout();
|
||||
|
||||
let conn = Connection::system().unwrap();
|
||||
let proxy = AuraProxyBlocking::new(&conn).unwrap();
|
||||
|
||||
let mut seq = AdvancedEffects::new(true);
|
||||
|
||||
// let zone = Effect::Breathe(rog_aura::effects::Breathe::new(
|
||||
// RgbAddress::Single,
|
||||
// Colour(166, 127, 166),
|
||||
// Colour(127, 155, 20),
|
||||
// rog_aura::Speed::High,
|
||||
// ));
|
||||
// seq.push(zone);
|
||||
|
||||
// let zone = Effect::DoomLightFlash(rog_aura::effects::DoomLightFlash::new(
|
||||
// RgbAddress::Single,
|
||||
// Colour(200, 0, 0),
|
||||
// 80,
|
||||
// 10,
|
||||
// ));
|
||||
// seq.push(zone);
|
||||
|
||||
let zone = Effect::DoomFlicker(rog_aura::effects::DoomFlicker::new(
|
||||
LedCode::SingleZone,
|
||||
Colour {
|
||||
r: 200,
|
||||
g: 110,
|
||||
b: 0,
|
||||
},
|
||||
100,
|
||||
10,
|
||||
));
|
||||
seq.push(zone);
|
||||
|
||||
// let zone = Effect::Breathe(rog_aura::effects::Breathe::new(
|
||||
// RgbAddress::KeyboardCenterLeft,
|
||||
// Colour(16, 127, 255),
|
||||
// Colour(127, 15, 20),
|
||||
// rog_aura::Speed::Low,
|
||||
// ));
|
||||
// seq.push(zone);
|
||||
|
||||
// let zone = Effect::Breathe(rog_aura::effects::Breathe::new(
|
||||
// RgbAddress::LightbarRightCorner,
|
||||
// Colour(0, 255, 255),
|
||||
// Colour(255, 0, 255),
|
||||
// rog_aura::Speed::Med,
|
||||
// ));
|
||||
// seq.push(zone);
|
||||
|
||||
loop {
|
||||
seq.next_state(&layout);
|
||||
let packets = seq.create_packets();
|
||||
|
||||
proxy.direct_addressing_raw(packets)?;
|
||||
std::thread::sleep(std::time::Duration::from_millis(33));
|
||||
}
|
||||
}
|
||||
@@ -1,76 +1,88 @@
|
||||
use gumdrop::Options;
|
||||
use rog_aura::error::Error;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum AnimeStatusValue {
|
||||
On,
|
||||
Off,
|
||||
}
|
||||
impl FromStr for AnimeStatusValue {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let s = s.to_lowercase();
|
||||
match s.as_str() {
|
||||
"on" => Ok(AnimeStatusValue::On),
|
||||
"off" => Ok(AnimeStatusValue::Off),
|
||||
_ => {
|
||||
print!("Invalid argument, must be one of: on, off");
|
||||
Err(Error::ParseAnime)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<AnimeStatusValue> for bool {
|
||||
fn from(value: AnimeStatusValue) -> Self {
|
||||
match value {
|
||||
AnimeStatusValue::On => true,
|
||||
AnimeStatusValue::Off => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Options)]
|
||||
pub struct AnimeLeds {
|
||||
#[options(help = "print help message")]
|
||||
help: bool,
|
||||
#[options(
|
||||
no_long,
|
||||
required,
|
||||
short = "b",
|
||||
meta = "",
|
||||
help = "set all leds brightness value"
|
||||
)]
|
||||
led_brightness: u8,
|
||||
}
|
||||
impl AnimeLeds {
|
||||
pub fn led_brightness(&self) -> u8 {
|
||||
self.led_brightness
|
||||
}
|
||||
}
|
||||
use rog_anime::usb::{AnimAwake, AnimBooting, AnimShutdown, AnimSleeping, Brightness};
|
||||
use rog_anime::AnimeType;
|
||||
|
||||
#[derive(Options)]
|
||||
pub struct AnimeCommand {
|
||||
#[options(help = "print help message")]
|
||||
pub help: bool,
|
||||
#[options(meta = "", help = "override the display type")]
|
||||
pub override_type: Option<AnimeType>,
|
||||
#[options(meta = "", help = "enable/disable the display")]
|
||||
pub enable_display: Option<bool>,
|
||||
#[options(meta = "", help = "enable/disable the builtin run/powersave animation")]
|
||||
pub enable_powersave_anim: Option<bool>,
|
||||
#[options(
|
||||
meta = "",
|
||||
help = "turn on/off the panel (accept/reject write requests)"
|
||||
help = "set global base brightness value <Off, Low, Med, High>"
|
||||
)]
|
||||
pub turn: Option<AnimeStatusValue>,
|
||||
#[options(meta = "", help = "turn on/off the panel at boot (with Asus effect)")]
|
||||
pub boot: Option<AnimeStatusValue>,
|
||||
pub brightness: Option<Brightness>,
|
||||
#[options(help = "clear the display")]
|
||||
pub clear: bool,
|
||||
#[options(
|
||||
no_short,
|
||||
meta = "",
|
||||
help = "turn the anime off when external power is unplugged"
|
||||
)]
|
||||
pub off_when_unplugged: Option<bool>,
|
||||
#[options(
|
||||
no_short,
|
||||
meta = "",
|
||||
help = "turn the anime off when the laptop suspends"
|
||||
)]
|
||||
pub off_when_suspended: Option<bool>,
|
||||
#[options(
|
||||
no_short,
|
||||
meta = "",
|
||||
help = "turn the anime off when the lid is closed"
|
||||
)]
|
||||
pub off_when_lid_closed: Option<bool>,
|
||||
#[options(no_short, meta = "", help = "Off with his head!!!")]
|
||||
pub off_with_his_head: Option<bool>,
|
||||
#[options(command)]
|
||||
pub command: Option<AnimeActions>,
|
||||
}
|
||||
|
||||
#[derive(Options)]
|
||||
pub enum AnimeActions {
|
||||
#[options(help = "change all leds brightness")]
|
||||
Leds(AnimeLeds),
|
||||
#[options(help = "display an 8bit greyscale png")]
|
||||
#[options(help = "display a PNG image")]
|
||||
Image(AnimeImage),
|
||||
#[options(help = "display a diagonal/pixel-perfect PNG")]
|
||||
PixelImage(AnimeImageDiagonal),
|
||||
#[options(help = "display an animated GIF")]
|
||||
Gif(AnimeGif),
|
||||
#[options(help = "display an animated diagonal/pixel-perfect GIF")]
|
||||
PixelGif(AnimeGifDiagonal),
|
||||
#[options(help = "change which builtin animations are shown")]
|
||||
SetBuiltins(Builtins),
|
||||
}
|
||||
|
||||
#[derive(Options)]
|
||||
pub struct Builtins {
|
||||
#[options(help = "print help message")]
|
||||
pub help: bool,
|
||||
#[options(
|
||||
meta = "",
|
||||
help = "Default is used if unspecified, <default:GlitchConstruction, StaticEmergence>"
|
||||
)]
|
||||
pub boot: AnimBooting,
|
||||
#[options(
|
||||
meta = "",
|
||||
help = "Default is used if unspecified, <default:BinaryBannerScroll, RogLogoGlitch>"
|
||||
)]
|
||||
pub awake: AnimAwake,
|
||||
#[options(
|
||||
meta = "",
|
||||
help = "Default is used if unspecified, <default:BannerSwipe, Starfield>"
|
||||
)]
|
||||
pub sleep: AnimSleeping,
|
||||
#[options(
|
||||
meta = "",
|
||||
help = "Default is used if unspecified, <default:GlitchOut, SeeYa>"
|
||||
)]
|
||||
pub shutdown: AnimShutdown,
|
||||
#[options(meta = "", help = "set/apply the animations <true/false>")]
|
||||
pub set: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Options)]
|
||||
@@ -90,3 +102,53 @@ pub struct AnimeImage {
|
||||
#[options(meta = "", default = "1.0", help = "brightness 0.0-1.0")]
|
||||
pub bright: f32,
|
||||
}
|
||||
|
||||
#[derive(Options)]
|
||||
pub struct AnimeImageDiagonal {
|
||||
#[options(help = "print help message")]
|
||||
pub help: bool,
|
||||
#[options(meta = "", help = "full path to the png to display")]
|
||||
pub path: String,
|
||||
#[options(meta = "", default = "1.0", help = "brightness 0.0-1.0")]
|
||||
pub bright: f32,
|
||||
}
|
||||
|
||||
#[derive(Options)]
|
||||
pub struct AnimeGif {
|
||||
#[options(help = "print help message")]
|
||||
pub help: bool,
|
||||
#[options(meta = "", help = "full path to the png to display")]
|
||||
pub path: String,
|
||||
#[options(meta = "", default = "1.0", help = "scale 1.0 == normal")]
|
||||
pub scale: f32,
|
||||
#[options(meta = "", default = "0.0", help = "x position (float)")]
|
||||
pub x_pos: f32,
|
||||
#[options(meta = "", default = "0.0", help = "y position (float)")]
|
||||
pub y_pos: f32,
|
||||
#[options(meta = "", default = "0.0", help = "the angle in radians")]
|
||||
pub angle: f32,
|
||||
#[options(meta = "", default = "1.0", help = "brightness 0.0-1.0")]
|
||||
pub bright: f32,
|
||||
#[options(
|
||||
meta = "",
|
||||
default = "1",
|
||||
help = "how many loops to play - 0 is infinite"
|
||||
)]
|
||||
pub loops: u32,
|
||||
}
|
||||
|
||||
#[derive(Options)]
|
||||
pub struct AnimeGifDiagonal {
|
||||
#[options(help = "print help message")]
|
||||
pub help: bool,
|
||||
#[options(meta = "", help = "full path to the png to display")]
|
||||
pub path: String,
|
||||
#[options(meta = "", default = "1.0", help = "brightness 0.0-1.0")]
|
||||
pub bright: f32,
|
||||
#[options(
|
||||
meta = "",
|
||||
default = "1",
|
||||
help = "how many loops to play - 0 is infinite"
|
||||
)]
|
||||
pub loops: u32,
|
||||
}
|
||||
|
||||
@@ -1,17 +1,72 @@
|
||||
use gumdrop::Options;
|
||||
use rog_aura::{error::Error, AuraEffect, AuraModeNum, AuraZone, Colour, Direction, Speed};
|
||||
use std::str::FromStr;
|
||||
|
||||
use gumdrop::Options;
|
||||
use rog_aura::error::Error;
|
||||
use rog_aura::{AuraEffect, AuraModeNum, AuraZone, Colour, Direction, Speed};
|
||||
|
||||
#[derive(Options, Debug)]
|
||||
pub struct LedPowerCommand1 {
|
||||
#[options(help = "print help message")]
|
||||
pub help: bool,
|
||||
#[options(meta = "", help = "Control if LEDs enabled while awake <true/false>")]
|
||||
pub awake: Option<bool>,
|
||||
#[options(help = "Use with awake option, if excluded defaults to false")]
|
||||
pub keyboard: bool,
|
||||
#[options(help = "Use with awake option, if excluded defaults to false")]
|
||||
pub lightbar: bool,
|
||||
#[options(meta = "", help = "Control boot animations <true/false>")]
|
||||
pub boot: Option<bool>,
|
||||
#[options(meta = "", help = "Control suspend animations <true/false>")]
|
||||
pub sleep: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Options, Debug)]
|
||||
pub struct LedPowerCommand2 {
|
||||
#[options(help = "print help message")]
|
||||
pub help: bool,
|
||||
#[options(command)]
|
||||
pub command: Option<SetAuraZoneEnabled>,
|
||||
}
|
||||
|
||||
#[derive(Options, Debug)]
|
||||
pub enum SetAuraZoneEnabled {
|
||||
/// Applies to both old and new models
|
||||
#[options(help = "")]
|
||||
Keyboard(AuraPowerStates),
|
||||
#[options(help = "")]
|
||||
Logo(AuraPowerStates),
|
||||
#[options(help = "")]
|
||||
Lightbar(AuraPowerStates),
|
||||
#[options(help = "")]
|
||||
Lid(AuraPowerStates),
|
||||
#[options(help = "")]
|
||||
RearGlow(AuraPowerStates),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Options)]
|
||||
pub struct AuraPowerStates {
|
||||
#[options(help = "print help message")]
|
||||
pub help: bool,
|
||||
#[options(help = "defaults to false if option unused")]
|
||||
pub boot: bool,
|
||||
#[options(help = "defaults to false if option unused")]
|
||||
pub awake: bool,
|
||||
#[options(help = "defaults to false if option unused")]
|
||||
pub sleep: bool,
|
||||
#[options(help = "defaults to false if option unused")]
|
||||
pub shutdown: bool,
|
||||
}
|
||||
|
||||
#[derive(Options)]
|
||||
pub struct LedBrightness {
|
||||
level: Option<u32>,
|
||||
level: Option<u8>,
|
||||
}
|
||||
impl LedBrightness {
|
||||
pub fn new(level: Option<u32>) -> Self {
|
||||
pub fn new(level: Option<u8>) -> Self {
|
||||
LedBrightness { level }
|
||||
}
|
||||
|
||||
pub fn level(&self) -> Option<u32> {
|
||||
pub fn level(&self) -> Option<u8> {
|
||||
self.level
|
||||
}
|
||||
}
|
||||
@@ -40,7 +95,7 @@ impl ToString for LedBrightness {
|
||||
Some(0x02) => "high",
|
||||
_ => "unknown",
|
||||
};
|
||||
s.to_string()
|
||||
s.to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +105,14 @@ pub struct SingleSpeed {
|
||||
help: bool,
|
||||
#[options(no_long, meta = "WORD", help = "set the speed: low, med, high")]
|
||||
pub speed: Speed,
|
||||
#[options(
|
||||
no_long,
|
||||
meta = "",
|
||||
help = "set the zone for this effect e.g, 0, 1, one, logo, lightbar-left"
|
||||
)]
|
||||
pub zone: AuraZone,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Options, Default)]
|
||||
pub struct SingleSpeedDirection {
|
||||
#[options(help = "print help message")]
|
||||
@@ -59,6 +121,12 @@ pub struct SingleSpeedDirection {
|
||||
pub direction: Direction,
|
||||
#[options(no_long, meta = "", help = "set the speed: low, med, high")]
|
||||
pub speed: Speed,
|
||||
#[options(
|
||||
no_long,
|
||||
meta = "",
|
||||
help = "set the zone for this effect e.g, 0, 1, one, logo, lightbar-left"
|
||||
)]
|
||||
pub zone: AuraZone,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Options)]
|
||||
@@ -67,6 +135,12 @@ pub struct SingleColour {
|
||||
help: bool,
|
||||
#[options(no_long, meta = "", help = "set the RGB value e.g, ff00ff")]
|
||||
pub colour: Colour,
|
||||
#[options(
|
||||
no_long,
|
||||
meta = "",
|
||||
help = "set the zone for this effect e.g, 0, 1, one, logo, lightbar-left"
|
||||
)]
|
||||
pub zone: AuraZone,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Options)]
|
||||
@@ -77,6 +151,12 @@ pub struct SingleColourSpeed {
|
||||
pub colour: Colour,
|
||||
#[options(no_long, meta = "", help = "set the speed: low, med, high")]
|
||||
pub speed: Speed,
|
||||
#[options(
|
||||
no_long,
|
||||
meta = "",
|
||||
help = "set the zone for this effect e.g, 0, 1, one, logo, lightbar-left"
|
||||
)]
|
||||
pub zone: AuraZone,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Options, Default)]
|
||||
@@ -89,10 +169,16 @@ pub struct TwoColourSpeed {
|
||||
pub colour2: Colour,
|
||||
#[options(no_long, meta = "", help = "set the speed: low, med, high")]
|
||||
pub speed: Speed,
|
||||
#[options(
|
||||
no_long,
|
||||
meta = "",
|
||||
help = "set the zone for this effect e.g, 0, 1, one, logo, lightbar-left"
|
||||
)]
|
||||
pub zone: AuraZone,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Options)]
|
||||
pub struct MultiColour {
|
||||
pub struct MultiZone {
|
||||
#[options(help = "print help message")]
|
||||
help: bool,
|
||||
#[options(short = "a", meta = "", help = "set the RGB value e.g, ff00ff")]
|
||||
@@ -124,36 +210,33 @@ pub struct MultiColourSpeed {
|
||||
/// Byte value for setting the built-in mode.
|
||||
///
|
||||
/// Enum corresponds to the required integer value
|
||||
// NOTE: The option names here must match those in rog-aura crate
|
||||
#[derive(Options)]
|
||||
pub enum SetAuraBuiltin {
|
||||
#[options(help = "set a single static colour")]
|
||||
Static(SingleColour),
|
||||
Static(SingleColour), // 0
|
||||
#[options(help = "pulse between one or two colours")]
|
||||
Breathe(TwoColourSpeed),
|
||||
Breathe(TwoColourSpeed), // 1
|
||||
#[options(help = "strobe through all colours")]
|
||||
Strobe(SingleSpeed),
|
||||
Strobe(SingleSpeed), // 2
|
||||
#[options(help = "rainbow cycling in one of four directions")]
|
||||
Rainbow(SingleSpeedDirection),
|
||||
Rainbow(SingleSpeedDirection), // 3
|
||||
#[options(help = "rain pattern mimicking raindrops")]
|
||||
Star(TwoColourSpeed),
|
||||
Stars(TwoColourSpeed), // 4
|
||||
#[options(help = "rain pattern of three preset colours")]
|
||||
Rain(SingleSpeed),
|
||||
Rain(SingleSpeed), // 5
|
||||
#[options(help = "pressed keys are highlighted to fade")]
|
||||
Highlight(SingleColourSpeed),
|
||||
Highlight(SingleColourSpeed), // 6
|
||||
#[options(help = "pressed keys generate horizontal laser")]
|
||||
Laser(SingleColourSpeed),
|
||||
Laser(SingleColourSpeed), // 7
|
||||
#[options(help = "pressed keys ripple outwards like a splash")]
|
||||
Ripple(SingleColourSpeed),
|
||||
Ripple(SingleColourSpeed), // 8
|
||||
#[options(help = "set a rapid pulse")]
|
||||
Pulse(SingleColour),
|
||||
Pulse(SingleColour), // 10
|
||||
#[options(help = "set a vertical line zooming from left")]
|
||||
Comet(SingleColour),
|
||||
Comet(SingleColour), // 11
|
||||
#[options(help = "set a wide vertical line zooming from left")]
|
||||
Flash(SingleColour),
|
||||
#[options(help = "4-zone multi-colour")]
|
||||
MultiStatic(MultiColour),
|
||||
#[options(help = "4-zone multi-colour breathing")]
|
||||
MultiBreathe(MultiColourSpeed),
|
||||
Flash(SingleColour), // 12
|
||||
}
|
||||
|
||||
impl Default for SetAuraBuiltin {
|
||||
@@ -166,6 +249,7 @@ impl From<&SingleColour> for AuraEffect {
|
||||
fn from(aura: &SingleColour) -> Self {
|
||||
Self {
|
||||
colour1: aura.colour,
|
||||
zone: aura.zone,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
@@ -175,6 +259,7 @@ impl From<&SingleSpeed> for AuraEffect {
|
||||
fn from(aura: &SingleSpeed) -> Self {
|
||||
Self {
|
||||
speed: aura.speed,
|
||||
zone: aura.zone,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
@@ -185,6 +270,7 @@ impl From<&SingleColourSpeed> for AuraEffect {
|
||||
Self {
|
||||
colour1: aura.colour,
|
||||
speed: aura.speed,
|
||||
zone: aura.zone,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
@@ -195,6 +281,7 @@ impl From<&TwoColourSpeed> for AuraEffect {
|
||||
Self {
|
||||
colour1: aura.colour,
|
||||
colour2: aura.colour2,
|
||||
zone: aura.zone,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
@@ -205,6 +292,7 @@ impl From<&SingleSpeedDirection> for AuraEffect {
|
||||
Self {
|
||||
speed: aura.speed,
|
||||
direction: aura.direction,
|
||||
zone: aura.zone,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
@@ -233,7 +321,7 @@ impl From<&SetAuraBuiltin> for AuraEffect {
|
||||
data.mode = AuraModeNum::Rainbow;
|
||||
data
|
||||
}
|
||||
SetAuraBuiltin::Star(x) => {
|
||||
SetAuraBuiltin::Stars(x) => {
|
||||
let mut data: AuraEffect = x.into();
|
||||
data.mode = AuraModeNum::Star;
|
||||
data
|
||||
@@ -273,55 +361,6 @@ impl From<&SetAuraBuiltin> for AuraEffect {
|
||||
data.mode = AuraModeNum::Flash;
|
||||
data
|
||||
}
|
||||
_ => AuraEffect::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&SetAuraBuiltin> for Vec<AuraEffect> {
|
||||
fn from(aura: &SetAuraBuiltin) -> Vec<AuraEffect> {
|
||||
let mut zones = vec![AuraEffect::default(); 4];
|
||||
match aura {
|
||||
SetAuraBuiltin::MultiStatic(data) => {
|
||||
zones[0].mode = AuraModeNum::Static;
|
||||
zones[0].zone = AuraZone::One;
|
||||
zones[0].colour1 = data.colour1;
|
||||
|
||||
zones[1].mode = AuraModeNum::Static;
|
||||
zones[1].zone = AuraZone::Two;
|
||||
zones[1].colour1 = data.colour2;
|
||||
|
||||
zones[2].mode = AuraModeNum::Static;
|
||||
zones[2].zone = AuraZone::Three;
|
||||
zones[2].colour1 = data.colour3;
|
||||
|
||||
zones[3].mode = AuraModeNum::Static;
|
||||
zones[3].zone = AuraZone::Four;
|
||||
zones[3].colour1 = data.colour4;
|
||||
}
|
||||
SetAuraBuiltin::MultiBreathe(data) => {
|
||||
zones[0].mode = AuraModeNum::Breathe;
|
||||
zones[0].zone = AuraZone::One;
|
||||
zones[0].colour1 = data.colour1;
|
||||
zones[0].speed = data.speed;
|
||||
|
||||
zones[1].mode = AuraModeNum::Breathe;
|
||||
zones[1].zone = AuraZone::Two;
|
||||
zones[1].colour1 = data.colour2;
|
||||
zones[1].speed = data.speed;
|
||||
|
||||
zones[2].mode = AuraModeNum::Breathe;
|
||||
zones[2].zone = AuraZone::Three;
|
||||
zones[2].colour1 = data.colour3;
|
||||
zones[2].speed = data.speed;
|
||||
|
||||
zones[3].mode = AuraModeNum::Breathe;
|
||||
zones[3].zone = AuraZone::Four;
|
||||
zones[3].colour1 = data.colour4;
|
||||
zones[3].speed = data.speed;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
zones
|
||||
}
|
||||
}
|
||||
|
||||
118
asusctl/src/cli_opts.rs
Normal file
118
asusctl/src/cli_opts.rs
Normal file
@@ -0,0 +1,118 @@
|
||||
use gumdrop::Options;
|
||||
use rog_platform::platform::ThrottlePolicy;
|
||||
|
||||
use crate::anime_cli::AnimeCommand;
|
||||
use crate::aura_cli::{LedBrightness, LedPowerCommand1, LedPowerCommand2, SetAuraBuiltin};
|
||||
use crate::fan_curve_cli::FanCurveCommand;
|
||||
use crate::slash_cli::SlashCommand;
|
||||
|
||||
#[derive(Default, Options)]
|
||||
pub struct CliStart {
|
||||
#[options(help_flag, help = "print help message")]
|
||||
pub help: bool,
|
||||
#[options(help = "show program version number")]
|
||||
pub version: bool,
|
||||
#[options(help = "show supported functions of this laptop")]
|
||||
pub show_supported: bool,
|
||||
#[options(meta = "", help = "<off, low, med, high>")]
|
||||
pub kbd_bright: Option<LedBrightness>,
|
||||
#[options(help = "Toggle to next keyboard brightness")]
|
||||
pub next_kbd_bright: bool,
|
||||
#[options(help = "Toggle to previous keyboard brightness")]
|
||||
pub prev_kbd_bright: bool,
|
||||
#[options(meta = "", help = "Set your battery charge limit <20-100>")]
|
||||
pub chg_limit: Option<u8>,
|
||||
#[options(command)]
|
||||
pub command: Option<CliCommand>,
|
||||
}
|
||||
|
||||
#[derive(Options)]
|
||||
pub enum CliCommand {
|
||||
#[options(help = "Set the keyboard lighting from built-in modes")]
|
||||
LedMode(LedModeCommand),
|
||||
#[options(help = "Set the LED power states")]
|
||||
LedPow1(LedPowerCommand1),
|
||||
#[options(help = "Set the LED power states")]
|
||||
LedPow2(LedPowerCommand2),
|
||||
#[options(help = "Set or select platform_profile")]
|
||||
Profile(ProfileCommand),
|
||||
#[options(help = "Set, select, or modify fan curves if supported")]
|
||||
FanCurve(FanCurveCommand),
|
||||
#[options(help = "Set the graphics mode (obsoleted by supergfxctl)")]
|
||||
Graphics(GraphicsCommand),
|
||||
#[options(name = "anime", help = "Manage AniMe Matrix")]
|
||||
Anime(AnimeCommand),
|
||||
#[options(name = "slash", help = "Manage Slash Ledbar")]
|
||||
Slash(SlashCommand),
|
||||
#[options(help = "Change bios settings")]
|
||||
Bios(BiosCommand),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Options)]
|
||||
pub struct ProfileCommand {
|
||||
#[options(help = "print help message")]
|
||||
pub help: bool,
|
||||
|
||||
#[options(help = "toggle to next profile in list")]
|
||||
pub next: bool,
|
||||
|
||||
#[options(help = "list available profiles")]
|
||||
pub list: bool,
|
||||
|
||||
#[options(help = "get profile")]
|
||||
pub profile_get: bool,
|
||||
|
||||
#[options(meta = "", help = "set the active profile")]
|
||||
pub profile_set: Option<ThrottlePolicy>,
|
||||
}
|
||||
|
||||
#[derive(Options)]
|
||||
pub struct LedModeCommand {
|
||||
#[options(help = "print help message")]
|
||||
pub help: bool,
|
||||
#[options(help = "switch to next aura mode")]
|
||||
pub next_mode: bool,
|
||||
#[options(help = "switch to previous aura mode")]
|
||||
pub prev_mode: bool,
|
||||
#[options(command)]
|
||||
pub command: Option<SetAuraBuiltin>,
|
||||
}
|
||||
|
||||
#[derive(Options)]
|
||||
pub struct GraphicsCommand {
|
||||
#[options(help = "print help message")]
|
||||
pub help: bool,
|
||||
}
|
||||
|
||||
#[derive(Options, Debug)]
|
||||
pub struct BiosCommand {
|
||||
#[options(help = "print help message")]
|
||||
pub help: bool,
|
||||
#[options(
|
||||
meta = "",
|
||||
short = "S",
|
||||
no_long,
|
||||
help = "set bios POST sound: asusctl -S <true/false>"
|
||||
)]
|
||||
pub post_sound_set: Option<bool>,
|
||||
#[options(no_long, short = "s", help = "read bios POST sound")]
|
||||
pub post_sound_get: bool,
|
||||
#[options(
|
||||
meta = "",
|
||||
short = "D",
|
||||
no_long,
|
||||
help = "Switch GPU MUX mode: 0 = Discrete, 1 = Optimus, reboot required"
|
||||
)]
|
||||
pub gpu_mux_mode_set: Option<u8>,
|
||||
#[options(no_long, short = "d", help = "get GPU mode")]
|
||||
pub gpu_mux_mode_get: bool,
|
||||
#[options(
|
||||
meta = "",
|
||||
short = "O",
|
||||
no_long,
|
||||
help = "Set device panel overdrive <true/false>"
|
||||
)]
|
||||
pub panel_overdrive_set: Option<bool>,
|
||||
#[options(no_long, short = "o", help = "get panel overdrive")]
|
||||
pub panel_overdrive_get: bool,
|
||||
}
|
||||
49
asusctl/src/fan_curve_cli.rs
Normal file
49
asusctl/src/fan_curve_cli.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
use gumdrop::Options;
|
||||
use rog_platform::platform::ThrottlePolicy;
|
||||
use rog_profiles::fan_curve_set::CurveData;
|
||||
use rog_profiles::FanCurvePU;
|
||||
|
||||
#[derive(Debug, Clone, Options)]
|
||||
pub struct FanCurveCommand {
|
||||
#[options(help = "print help message")]
|
||||
pub help: bool,
|
||||
|
||||
#[options(help = "get enabled fan profiles")]
|
||||
pub get_enabled: bool,
|
||||
|
||||
#[options(help = "set the active profile's fan curve to default")]
|
||||
pub default: bool,
|
||||
|
||||
#[options(
|
||||
meta = "",
|
||||
help = "profile to modify fan-curve for. Shows data if no options provided"
|
||||
)]
|
||||
pub mod_profile: Option<ThrottlePolicy>,
|
||||
|
||||
#[options(
|
||||
meta = "",
|
||||
help = "enable or disable <true/false> fan all curves for a profile. `--mod_profile` \
|
||||
required"
|
||||
)]
|
||||
pub enable_fan_curves: Option<bool>,
|
||||
|
||||
#[options(
|
||||
meta = "",
|
||||
help = "enable or disable <true/false> a single fan curve for a profile. `--mod_profile` \
|
||||
and `--fan` required"
|
||||
)]
|
||||
pub enable_fan_curve: Option<bool>,
|
||||
|
||||
#[options(
|
||||
meta = "",
|
||||
help = "select fan <cpu/gpu/mid> to modify. `--mod_profile` required"
|
||||
)]
|
||||
pub fan: Option<FanCurvePU>,
|
||||
|
||||
#[options(
|
||||
meta = "",
|
||||
help = "data format = 30c:1%,49c:2%,59c:3%,69c:4%,79c:31%,89c:49%,99c:56%,109c:58%. \
|
||||
`--mod-profile` required. If '%' is omitted the fan range is 0-255"
|
||||
)]
|
||||
pub data: Option<CurveData>,
|
||||
}
|
||||
1244
asusctl/src/main.rs
1244
asusctl/src/main.rs
File diff suppressed because it is too large
Load Diff
@@ -1,53 +0,0 @@
|
||||
use gumdrop::Options;
|
||||
use rog_fan_curve::{Curve, Fan};
|
||||
use rog_profiles::profiles::FanLevel;
|
||||
|
||||
#[derive(Debug, Clone, Options)]
|
||||
pub struct ProfileCommand {
|
||||
#[options(help = "print help message")]
|
||||
pub help: bool,
|
||||
#[options(help = "toggle to next profile in list")]
|
||||
pub next: bool,
|
||||
#[options(help = "create the profile if it doesn't exist")]
|
||||
pub create: bool,
|
||||
#[options(meta = "", help = "remove a profile by name")]
|
||||
pub remove: Option<String>,
|
||||
#[options(help = "list available profiles")]
|
||||
pub list: bool,
|
||||
#[options(help = "get active profile name")]
|
||||
pub active_name: bool,
|
||||
#[options(help = "get active profile data")]
|
||||
pub active_data: bool,
|
||||
#[options(help = "get all profile data")]
|
||||
pub profiles_data: bool,
|
||||
|
||||
// Options for profile
|
||||
#[options(meta = "", help = "enable or disable cpu turbo")]
|
||||
pub turbo: Option<bool>,
|
||||
#[options(meta = "", help = "set min cpu scaling (intel)")]
|
||||
pub min_percentage: Option<u8>,
|
||||
#[options(meta = "", help = "set max cpu scaling (intel)")]
|
||||
pub max_percentage: Option<u8>,
|
||||
|
||||
#[options(meta = "", help = "<silent, normal, boost>")]
|
||||
pub fan_preset: Option<FanLevel>,
|
||||
#[options(
|
||||
meta = "",
|
||||
parse(try_from_str = "parse_fan_curve"),
|
||||
help = "set fan curve"
|
||||
)]
|
||||
pub curve: Option<Curve>,
|
||||
#[options(free)]
|
||||
pub profile: Option<String>,
|
||||
}
|
||||
|
||||
fn parse_fan_curve(data: &str) -> Result<Curve, String> {
|
||||
let curve = Curve::from_config_str(data)?;
|
||||
if let Err(err) = curve.check_safety(Fan::Cpu) {
|
||||
return Err(format!("Unsafe curve {:?}", err));
|
||||
}
|
||||
if let Err(err) = curve.check_safety(Fan::Gpu) {
|
||||
return Err(format!("Unsafe curve {:?}", err));
|
||||
}
|
||||
Ok(curve)
|
||||
}
|
||||
20
asusctl/src/slash_cli.rs
Normal file
20
asusctl/src/slash_cli.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
use gumdrop::Options;
|
||||
use rog_slash::SlashMode;
|
||||
|
||||
#[derive(Options)]
|
||||
pub struct SlashCommand {
|
||||
#[options(help = "print help message")]
|
||||
pub help: bool,
|
||||
#[options(help = "Enable the Slash Ledbar")]
|
||||
pub enable: bool,
|
||||
#[options(help = "Ddisable the Slash Ledbar")]
|
||||
pub disable: bool,
|
||||
#[options(meta = "", help = "Set brightness value <0-255>")]
|
||||
pub brightness: Option<u8>,
|
||||
#[options(meta = "", help = "Set interval value <0-255>")]
|
||||
pub interval: Option<u8>,
|
||||
#[options(help = "Set SlashMode (so 'list' for all options)")]
|
||||
pub slash_mode: Option<SlashMode>,
|
||||
#[options(help = "list available animations")]
|
||||
pub list: bool,
|
||||
}
|
||||
35
asusd-user/Cargo.toml
Normal file
35
asusd-user/Cargo.toml
Normal file
@@ -0,0 +1,35 @@
|
||||
[package]
|
||||
name = "asusd-user"
|
||||
license = "MPL-2.0"
|
||||
version.workspace = true
|
||||
authors = ["Luke D Jones <luke@ljones.dev>"]
|
||||
edition = "2021"
|
||||
description = "Usermode daemon for user settings, anime, per-key lighting"
|
||||
|
||||
[[bin]]
|
||||
name = "asusd-user"
|
||||
path = "src/daemon.rs"
|
||||
|
||||
[dependencies]
|
||||
dirs.workspace = true
|
||||
smol.workspace = true
|
||||
|
||||
# serialisation
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
serde_derive.workspace = true
|
||||
|
||||
rog_anime = { path = "../rog-anime" }
|
||||
rog_aura = { path = "../rog-aura" }
|
||||
rog_dbus = { path = "../rog-dbus" }
|
||||
rog_platform = { path = "../rog-platform" }
|
||||
config-traits = { path = "../config-traits" }
|
||||
|
||||
zbus.workspace = true
|
||||
|
||||
# cli and logging
|
||||
log.workspace = true
|
||||
env_logger.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
cargo-husky.workspace = true
|
||||
232
asusd-user/src/config.rs
Normal file
232
asusd-user/src/config.rs
Normal file
@@ -0,0 +1,232 @@
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
|
||||
use config_traits::{StdConfig, StdConfigLoad};
|
||||
use rog_anime::{ActionLoader, AnimTime, AnimeType, Fade, Sequences as AnimeSequences, Vec2};
|
||||
use rog_aura::effects::{AdvancedEffects as AuraSequences, Breathe, DoomFlicker, Effect, Static};
|
||||
use rog_aura::keyboard::LedCode;
|
||||
use rog_aura::{Colour, Speed};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
use crate::error::Error;
|
||||
|
||||
const ROOT_CONF_DIR: &str = "rog";
|
||||
|
||||
fn root_conf_dir() -> PathBuf {
|
||||
let mut dir = dirs::config_dir().unwrap_or_else(|| PathBuf::from("/tmp"));
|
||||
dir.push(ROOT_CONF_DIR);
|
||||
dir
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct ConfigAnime {
|
||||
pub name: String,
|
||||
pub anime: Vec<ActionLoader>,
|
||||
}
|
||||
|
||||
impl ConfigAnime {
|
||||
pub fn create(&self, anime_type: AnimeType) -> Result<AnimeSequences, Error> {
|
||||
let mut seq = AnimeSequences::new(anime_type);
|
||||
|
||||
for (idx, action) in self.anime.iter().enumerate() {
|
||||
seq.insert(idx, action)?;
|
||||
}
|
||||
|
||||
Ok(seq)
|
||||
}
|
||||
|
||||
pub fn set_name(mut self, name: String) -> Self {
|
||||
self.name = name;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ConfigAnime {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
name: "anime-default".to_owned(),
|
||||
anime: vec![
|
||||
ActionLoader::AsusImage {
|
||||
file: "/usr/share/asusd/anime/custom/diagonal-template.png".into(),
|
||||
brightness: 1.0,
|
||||
time: AnimTime::Fade(Fade::new(
|
||||
Duration::from_secs(2),
|
||||
None,
|
||||
Duration::from_secs(2),
|
||||
)),
|
||||
},
|
||||
ActionLoader::AsusAnimation {
|
||||
file: "/usr/share/asusd/anime/asus/rog/Sunset.gif".into(),
|
||||
brightness: 0.5,
|
||||
time: AnimTime::Fade(Fade::new(
|
||||
Duration::from_secs(6),
|
||||
None,
|
||||
Duration::from_secs(3),
|
||||
)),
|
||||
},
|
||||
ActionLoader::ImageAnimation {
|
||||
file: "/usr/share/asusd/anime/custom/sonic-run.gif".into(),
|
||||
scale: 0.9,
|
||||
angle: 0.65,
|
||||
translation: Vec2::default(),
|
||||
brightness: 0.5,
|
||||
time: AnimTime::Fade(Fade::new(
|
||||
Duration::from_secs(2),
|
||||
Some(Duration::from_secs(2)),
|
||||
Duration::from_secs(2),
|
||||
)),
|
||||
},
|
||||
ActionLoader::Image {
|
||||
file: "/usr/share/asusd/anime/custom/rust.png".into(),
|
||||
scale: 1.0,
|
||||
angle: 0.0,
|
||||
translation: Vec2::default(),
|
||||
time: AnimTime::Fade(Fade::new(
|
||||
Duration::from_secs(2),
|
||||
Some(Duration::from_secs(1)),
|
||||
Duration::from_secs(2),
|
||||
)),
|
||||
brightness: 0.6,
|
||||
},
|
||||
ActionLoader::Pause(Duration::from_secs(1)),
|
||||
ActionLoader::ImageAnimation {
|
||||
file: "/usr/share/asusd/anime/custom/sonic-wait.gif".into(),
|
||||
scale: 0.9,
|
||||
angle: 0.0,
|
||||
translation: Vec2::new(3.0, 2.0),
|
||||
brightness: 0.5,
|
||||
time: AnimTime::Count(2),
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StdConfig for ConfigAnime {
|
||||
fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
fn file_name(&self) -> String {
|
||||
format!("{}.ron", self.name)
|
||||
}
|
||||
|
||||
fn config_dir() -> std::path::PathBuf {
|
||||
root_conf_dir()
|
||||
}
|
||||
}
|
||||
|
||||
impl StdConfigLoad for ConfigAnime {}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct ConfigAura {
|
||||
pub name: String,
|
||||
pub aura: AuraSequences,
|
||||
}
|
||||
|
||||
impl ConfigAura {
|
||||
pub fn set_name(mut self, name: String) -> Self {
|
||||
self.name = name;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ConfigAura {
|
||||
fn default() -> Self {
|
||||
let mut seq = AuraSequences::new(false);
|
||||
let mut key = Effect::Breathe(Breathe::new(
|
||||
LedCode::W,
|
||||
Colour {
|
||||
r: 255,
|
||||
g: 0,
|
||||
b: 20,
|
||||
},
|
||||
Colour {
|
||||
r: 20,
|
||||
g: 255,
|
||||
b: 0,
|
||||
},
|
||||
Speed::Low,
|
||||
));
|
||||
|
||||
seq.push(key.clone());
|
||||
key.set_led(LedCode::A);
|
||||
seq.push(key.clone());
|
||||
key.set_led(LedCode::S);
|
||||
seq.push(key.clone());
|
||||
key.set_led(LedCode::D);
|
||||
seq.push(key);
|
||||
|
||||
let key = Effect::Breathe(Breathe::new(
|
||||
LedCode::F,
|
||||
Colour { r: 255, g: 0, b: 0 },
|
||||
Colour { r: 255, g: 0, b: 0 },
|
||||
Speed::High,
|
||||
));
|
||||
seq.push(key);
|
||||
|
||||
let mut key = Effect::Static(Static::new(LedCode::RCtrl, Colour { r: 0, g: 0, b: 255 }));
|
||||
seq.push(key.clone());
|
||||
key.set_led(LedCode::LCtrl);
|
||||
seq.push(key.clone());
|
||||
key.set_led(LedCode::Esc);
|
||||
seq.push(key);
|
||||
|
||||
let key = Effect::DoomFlicker(DoomFlicker::new(
|
||||
LedCode::N9,
|
||||
Colour { r: 0, g: 0, b: 255 },
|
||||
80,
|
||||
40,
|
||||
));
|
||||
seq.push(key);
|
||||
|
||||
Self {
|
||||
name: "aura-default".to_owned(),
|
||||
aura: seq,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StdConfig for ConfigAura {
|
||||
fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
fn file_name(&self) -> String {
|
||||
format!("{}.ron", self.name)
|
||||
}
|
||||
|
||||
fn config_dir() -> std::path::PathBuf {
|
||||
root_conf_dir()
|
||||
}
|
||||
}
|
||||
|
||||
impl StdConfigLoad for ConfigAura {}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize)]
|
||||
#[serde(default)]
|
||||
pub struct ConfigBase {
|
||||
/// Name of active anime config file in the user config directory
|
||||
pub active_anime: Option<String>,
|
||||
/// Name of active aura config file in the user config directory
|
||||
pub active_aura: Option<String>,
|
||||
}
|
||||
|
||||
impl StdConfig for ConfigBase {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
active_anime: Some("anime-default".to_owned()),
|
||||
active_aura: Some("aura-default".to_owned()),
|
||||
}
|
||||
}
|
||||
|
||||
fn file_name(&self) -> String {
|
||||
"rog-user.ron".to_owned()
|
||||
}
|
||||
|
||||
fn config_dir() -> std::path::PathBuf {
|
||||
root_conf_dir()
|
||||
}
|
||||
}
|
||||
|
||||
impl StdConfigLoad for ConfigBase {}
|
||||
@@ -1,21 +1,54 @@
|
||||
use rog_anime::{ActionData, AnimTime, AnimeAction, Sequences, Vec2};
|
||||
use rog_dbus::RogDbusClient;
|
||||
//use crate::dbus::DbusEvents;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
use std::{
|
||||
path::Path,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Mutex,
|
||||
},
|
||||
};
|
||||
use std::{sync::Arc, thread::sleep, time::Instant};
|
||||
use zbus::dbus_interface;
|
||||
use zvariant::ObjectPath;
|
||||
use zvariant_derive::Type;
|
||||
use std::path::Path;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread::sleep;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use crate::{error::Error, user_config::UserAnimeConfig};
|
||||
use config_traits::StdConfig;
|
||||
use rog_anime::error::AnimeError;
|
||||
use rog_anime::{ActionData, ActionLoader, AnimTime, Fade, Sequences, Vec2};
|
||||
use rog_dbus::zbus_anime::AnimeProxyBlocking;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use zbus::interface;
|
||||
use zbus::zvariant::{ObjectPath, Type};
|
||||
|
||||
use crate::config::ConfigAnime;
|
||||
use crate::error::Error;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, Type)]
|
||||
pub struct Timer {
|
||||
type_of: TimeType,
|
||||
/// If time type is Timer then this is milliseonds, otherwise it is
|
||||
/// animation loop count
|
||||
count: u64,
|
||||
/// Used only for `TimeType::Timer`, milliseonds to fade the image in for
|
||||
fade_in: u64,
|
||||
/// Used only for `TimeType::Timer`, milliseonds to fade the image out for
|
||||
fade_out: u64,
|
||||
}
|
||||
|
||||
impl From<Timer> for AnimTime {
|
||||
fn from(time: Timer) -> Self {
|
||||
match time.type_of {
|
||||
TimeType::Timer => {
|
||||
if time.fade_in != 0 && time.fade_out != 0 {
|
||||
let fade_in = Duration::from_millis(time.fade_in);
|
||||
let fade_out = Duration::from_millis(time.fade_out);
|
||||
let show_for = if time.count != 0 {
|
||||
Some(Duration::from_millis(time.count))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
AnimTime::Fade(Fade::new(fade_in, show_for, fade_out))
|
||||
} else {
|
||||
AnimTime::Time(Duration::from_millis(time.count))
|
||||
}
|
||||
}
|
||||
TimeType::Count => AnimTime::Count(time.count as u32),
|
||||
TimeType::Infinite => AnimTime::Infinite,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, Type)]
|
||||
pub enum TimeType {
|
||||
@@ -24,19 +57,19 @@ pub enum TimeType {
|
||||
Infinite,
|
||||
}
|
||||
|
||||
/// The inner object exists to allow the zbus proxy to share it with a runner thread
|
||||
/// and a zbus server behind `Arc<Mutex<T>>`
|
||||
/// The inner object exists to allow the zbus proxy to share it with a runner
|
||||
/// thread and a zbus server behind `Arc<Mutex<T>>`
|
||||
pub struct CtrlAnimeInner<'a> {
|
||||
sequences: Sequences,
|
||||
client: RogDbusClient<'a>,
|
||||
do_early_return: &'a AtomicBool,
|
||||
client: AnimeProxyBlocking<'a>,
|
||||
do_early_return: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl<'a> CtrlAnimeInner<'static> {
|
||||
pub fn new(
|
||||
sequences: Sequences,
|
||||
client: RogDbusClient<'static>,
|
||||
do_early_return: &'static AtomicBool,
|
||||
client: AnimeProxyBlocking<'static>,
|
||||
do_early_return: Arc<AtomicBool>,
|
||||
) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
sequences,
|
||||
@@ -44,8 +77,10 @@ impl<'a> CtrlAnimeInner<'static> {
|
||||
do_early_return,
|
||||
})
|
||||
}
|
||||
/// To be called on each main loop iteration to pump out commands to the anime
|
||||
pub fn run(&self) -> Result<(), Error> {
|
||||
|
||||
/// To be called on each main loop iteration to pump out commands to the
|
||||
/// anime
|
||||
pub fn run(&'a self) -> Result<(), Error> {
|
||||
if self.do_early_return.load(Ordering::SeqCst) {
|
||||
return Ok(());
|
||||
}
|
||||
@@ -53,39 +88,18 @@ impl<'a> CtrlAnimeInner<'static> {
|
||||
for action in self.sequences.iter() {
|
||||
match action {
|
||||
ActionData::Animation(frames) => {
|
||||
let mut count = 0;
|
||||
let start = Instant::now();
|
||||
'animation: loop {
|
||||
for frame in frames.frames() {
|
||||
if self.do_early_return.load(Ordering::SeqCst) {
|
||||
return Ok(());
|
||||
}
|
||||
self.client
|
||||
.proxies()
|
||||
.anime()
|
||||
.write(frame.frame().clone())
|
||||
.unwrap();
|
||||
if let AnimTime::Time(time) = frames.duration() {
|
||||
if Instant::now().duration_since(start) > time {
|
||||
break 'animation;
|
||||
}
|
||||
}
|
||||
sleep(frame.delay());
|
||||
rog_anime::run_animation(frames, &|output| {
|
||||
if self.do_early_return.load(Ordering::Acquire) {
|
||||
return Ok(true); // Do safe exit
|
||||
}
|
||||
if let AnimTime::Cycles(times) = frames.duration() {
|
||||
count += 1;
|
||||
if count >= times {
|
||||
break 'animation;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.client
|
||||
.write(output)
|
||||
.map_err(|e| AnimeError::Dbus(format!("{}", e)))
|
||||
.map(|_| false)
|
||||
});
|
||||
}
|
||||
ActionData::Image(image) => {
|
||||
self.client
|
||||
.proxies()
|
||||
.anime()
|
||||
.write(image.as_ref().clone())
|
||||
.unwrap();
|
||||
self.client.write(image.as_ref().clone()).ok();
|
||||
}
|
||||
ActionData::Pause(duration) => {
|
||||
let start = Instant::now();
|
||||
@@ -99,10 +113,10 @@ impl<'a> CtrlAnimeInner<'static> {
|
||||
sleep(Duration::from_millis(1));
|
||||
}
|
||||
}
|
||||
ActionData::AudioEq => {}
|
||||
ActionData::SystemInfo => {}
|
||||
ActionData::TimeDate => {}
|
||||
ActionData::Matrix => {}
|
||||
ActionData::AudioEq
|
||||
| ActionData::SystemInfo
|
||||
| ActionData::TimeDate
|
||||
| ActionData::Matrix => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,19 +125,19 @@ impl<'a> CtrlAnimeInner<'static> {
|
||||
}
|
||||
|
||||
pub struct CtrlAnime<'a> {
|
||||
config: Arc<Mutex<UserAnimeConfig>>,
|
||||
client: RogDbusClient<'a>,
|
||||
config: Arc<Mutex<ConfigAnime>>,
|
||||
client: AnimeProxyBlocking<'a>,
|
||||
inner: Arc<Mutex<CtrlAnimeInner<'a>>>,
|
||||
/// Must be the same Atomic as in CtrlAnimeInner
|
||||
inner_early_return: &'a AtomicBool,
|
||||
inner_early_return: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl<'a> CtrlAnime<'static> {
|
||||
impl CtrlAnime<'static> {
|
||||
pub fn new(
|
||||
config: Arc<Mutex<UserAnimeConfig>>,
|
||||
config: Arc<Mutex<ConfigAnime>>,
|
||||
inner: Arc<Mutex<CtrlAnimeInner<'static>>>,
|
||||
client: RogDbusClient<'static>,
|
||||
inner_early_return: &'static AtomicBool,
|
||||
client: AnimeProxyBlocking<'static>,
|
||||
inner_early_return: Arc<AtomicBool>,
|
||||
) -> Result<Self, Error> {
|
||||
Ok(CtrlAnime {
|
||||
config,
|
||||
@@ -133,12 +147,14 @@ impl<'a> CtrlAnime<'static> {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn add_to_server(self, server: &mut zbus::ObjectServer) {
|
||||
pub async fn add_to_server(self, server: &mut zbus::Connection) {
|
||||
server
|
||||
.object_server()
|
||||
.at(
|
||||
&ObjectPath::from_str_unchecked("/org/asuslinux/Anime"),
|
||||
self,
|
||||
)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
println!("CtrlAnime: add_to_server {}", err);
|
||||
err
|
||||
@@ -153,24 +169,19 @@ impl<'a> CtrlAnime<'static> {
|
||||
// - Do actions
|
||||
// - Write config if required
|
||||
// - Unset inner_early_return
|
||||
#[dbus_interface(name = "org.asuslinux.Daemon")]
|
||||
#[interface(name = "org.asuslinux.Daemon")]
|
||||
impl CtrlAnime<'static> {
|
||||
pub fn insert_asus_gif(
|
||||
&mut self,
|
||||
index: u32,
|
||||
file: String,
|
||||
time: TimeType,
|
||||
count: u32,
|
||||
file: &str,
|
||||
time: Timer,
|
||||
brightness: f32,
|
||||
) -> zbus::fdo::Result<String> {
|
||||
if let Ok(mut config) = self.config.try_lock() {
|
||||
let time: AnimTime = match time {
|
||||
TimeType::Timer => AnimTime::Time(Duration::from_millis(count as u64)),
|
||||
TimeType::Count => AnimTime::Cycles(count),
|
||||
TimeType::Infinite => AnimTime::Infinite,
|
||||
};
|
||||
let time: AnimTime = time.into();
|
||||
let file = Path::new(&file);
|
||||
let action = AnimeAction::AsusAnimation {
|
||||
let action = ActionLoader::AsusAnimation {
|
||||
file: file.into(),
|
||||
brightness,
|
||||
time,
|
||||
@@ -186,7 +197,7 @@ impl CtrlAnime<'static> {
|
||||
.map_err(|err| zbus::fdo::Error::Failed(err.to_string()))?;
|
||||
}
|
||||
config.anime.push(action);
|
||||
config.write()?;
|
||||
config.write();
|
||||
|
||||
let json = serde_json::to_string_pretty(&*config).expect("Parse config to JSON failed");
|
||||
|
||||
@@ -201,23 +212,18 @@ impl CtrlAnime<'static> {
|
||||
pub fn insert_image_gif(
|
||||
&mut self,
|
||||
index: u32,
|
||||
file: String,
|
||||
file: &str,
|
||||
scale: f32,
|
||||
angle: f32,
|
||||
xy: (f32, f32),
|
||||
time: TimeType,
|
||||
count: u32,
|
||||
time: Timer,
|
||||
brightness: f32,
|
||||
) -> zbus::fdo::Result<String> {
|
||||
if let Ok(mut config) = self.config.try_lock() {
|
||||
let time: AnimTime = match time {
|
||||
TimeType::Timer => AnimTime::Time(Duration::from_millis(count as u64)),
|
||||
TimeType::Count => AnimTime::Cycles(count),
|
||||
TimeType::Infinite => AnimTime::Infinite,
|
||||
};
|
||||
let time: AnimTime = time.into();
|
||||
let file = Path::new(&file);
|
||||
let translation = Vec2::new(xy.0, xy.1);
|
||||
let action = AnimeAction::ImageAnimation {
|
||||
let action = ActionLoader::ImageAnimation {
|
||||
file: file.into(),
|
||||
scale,
|
||||
angle,
|
||||
@@ -236,7 +242,7 @@ impl CtrlAnime<'static> {
|
||||
.map_err(|err| zbus::fdo::Error::Failed(err.to_string()))?;
|
||||
}
|
||||
config.anime.push(action);
|
||||
config.write()?;
|
||||
config.write();
|
||||
|
||||
let json =
|
||||
serde_json::to_string_pretty(&*config.anime).expect("Parse config to JSON failed");
|
||||
@@ -248,23 +254,27 @@ impl CtrlAnime<'static> {
|
||||
Err(zbus::fdo::Error::Failed("UserConfig lock fail".into()))
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn insert_image(
|
||||
&mut self,
|
||||
index: u32,
|
||||
file: String,
|
||||
file: &str,
|
||||
scale: f32,
|
||||
angle: f32,
|
||||
xy: (f32, f32),
|
||||
time: Timer,
|
||||
brightness: f32,
|
||||
) -> zbus::fdo::Result<String> {
|
||||
if let Ok(mut config) = self.config.try_lock() {
|
||||
let file = Path::new(&file);
|
||||
let action = AnimeAction::Image {
|
||||
let time = time.into();
|
||||
let action = ActionLoader::Image {
|
||||
file: file.into(),
|
||||
scale,
|
||||
angle,
|
||||
translation: Vec2::new(xy.0, xy.1),
|
||||
brightness,
|
||||
time,
|
||||
};
|
||||
|
||||
// Must make the inner run loop return early
|
||||
@@ -277,7 +287,7 @@ impl CtrlAnime<'static> {
|
||||
.map_err(|err| zbus::fdo::Error::Failed(err.to_string()))?;
|
||||
}
|
||||
config.anime.push(action);
|
||||
config.write()?;
|
||||
config.write();
|
||||
|
||||
let json =
|
||||
serde_json::to_string_pretty(&*config.anime).expect("Parse config to JSON failed");
|
||||
@@ -291,7 +301,7 @@ impl CtrlAnime<'static> {
|
||||
|
||||
pub fn insert_pause(&mut self, index: u32, millis: u64) -> zbus::fdo::Result<String> {
|
||||
if let Ok(mut config) = self.config.try_lock() {
|
||||
let action = AnimeAction::Pause(Duration::from_millis(millis));
|
||||
let action = ActionLoader::Pause(Duration::from_millis(millis));
|
||||
// Must make the inner run loop return early
|
||||
self.inner_early_return.store(true, Ordering::SeqCst);
|
||||
|
||||
@@ -302,7 +312,7 @@ impl CtrlAnime<'static> {
|
||||
.map_err(|err| zbus::fdo::Error::Failed(err.to_string()))?;
|
||||
}
|
||||
config.anime.push(action);
|
||||
config.write()?;
|
||||
config.write();
|
||||
|
||||
let json =
|
||||
serde_json::to_string_pretty(&*config.anime).expect("Parse config to JSON failed");
|
||||
@@ -325,7 +335,7 @@ impl CtrlAnime<'static> {
|
||||
if (index as usize) < config.anime.len() {
|
||||
config.anime.remove(index as usize);
|
||||
}
|
||||
config.write()?;
|
||||
config.write();
|
||||
|
||||
let json =
|
||||
serde_json::to_string_pretty(&*config.anime).expect("Parse config to JSON failed");
|
||||
@@ -340,13 +350,13 @@ impl CtrlAnime<'static> {
|
||||
pub fn set_state(&mut self, on: bool) -> zbus::fdo::Result<()> {
|
||||
// Operations here need to be in specific order
|
||||
if on {
|
||||
self.client.proxies().anime().toggle_on(on)?;
|
||||
self.client.set_enable_display(on).ok();
|
||||
// Let the inner loop run
|
||||
self.inner_early_return.store(false, Ordering::SeqCst);
|
||||
} else {
|
||||
// Must make the inner run loop return early
|
||||
self.inner_early_return.store(true, Ordering::SeqCst);
|
||||
self.client.proxies().anime().toggle_on(on)?;
|
||||
self.client.set_enable_display(on).ok();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
123
asusd-user/src/daemon.rs
Normal file
123
asusd-user/src/daemon.rs
Normal file
@@ -0,0 +1,123 @@
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use asusd_user::config::*;
|
||||
use asusd_user::ctrl_anime::{CtrlAnime, CtrlAnimeInner};
|
||||
use config_traits::{StdConfig, StdConfigLoad};
|
||||
use rog_anime::usb::get_anime_type;
|
||||
use rog_aura::aura_detection::LedSupportData;
|
||||
use rog_aura::keyboard::KeyLayout;
|
||||
use rog_dbus::zbus_anime::AnimeProxyBlocking;
|
||||
use rog_dbus::zbus_aura::AuraProxyBlocking;
|
||||
use rog_dbus::zbus_platform::PlatformProxyBlocking;
|
||||
use rog_dbus::DBUS_NAME;
|
||||
use smol::Executor;
|
||||
use zbus::Connection;
|
||||
|
||||
#[cfg(not(feature = "local_data"))]
|
||||
const DATA_DIR: &str = "/usr/share/rog-gui/";
|
||||
#[cfg(feature = "local_data")]
|
||||
const DATA_DIR: &str = env!("CARGO_MANIFEST_DIR");
|
||||
const BOARD_NAME: &str = "/sys/class/dmi/id/board_name";
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut logger = env_logger::Builder::new();
|
||||
logger
|
||||
.parse_default_env()
|
||||
.target(env_logger::Target::Stdout)
|
||||
.format(|buf, record| writeln!(buf, "{}: {}", record.level(), record.args()))
|
||||
.init();
|
||||
|
||||
println!(" user daemon v{}", asusd_user::VERSION);
|
||||
println!(" rog-anime v{}", rog_anime::VERSION);
|
||||
println!(" rog-dbus v{}", rog_dbus::VERSION);
|
||||
println!("rog-platform v{}", rog_platform::VERSION);
|
||||
|
||||
let conn = zbus::blocking::Connection::system().unwrap();
|
||||
let platform_proxy = PlatformProxyBlocking::new(&conn).unwrap();
|
||||
|
||||
let supported = platform_proxy
|
||||
.supported_interfaces()
|
||||
.unwrap_or_default()
|
||||
.contains(&"Anime".to_string());
|
||||
let config = ConfigBase::new().load();
|
||||
let executor = Executor::new();
|
||||
|
||||
let early_return = Arc::new(AtomicBool::new(false));
|
||||
// Set up the anime data and run loop/thread
|
||||
if supported {
|
||||
if let Some(cfg) = config.active_anime {
|
||||
let anime_type = get_anime_type()?;
|
||||
let anime_config = ConfigAnime::new().set_name(cfg).load();
|
||||
let anime = anime_config.create(anime_type)?;
|
||||
let anime_config = Arc::new(Mutex::new(anime_config));
|
||||
|
||||
let anime_proxy_blocking = AnimeProxyBlocking::new(&conn).unwrap();
|
||||
executor
|
||||
.spawn(async move {
|
||||
// Create server
|
||||
let mut connection = Connection::session().await.unwrap();
|
||||
connection.request_name(DBUS_NAME).await.unwrap();
|
||||
|
||||
// Inner behind mutex required for thread safety
|
||||
let inner = Arc::new(Mutex::new(
|
||||
CtrlAnimeInner::new(
|
||||
anime,
|
||||
anime_proxy_blocking.clone(),
|
||||
early_return.clone(),
|
||||
)
|
||||
.unwrap(),
|
||||
));
|
||||
// Need new client object for dbus control part
|
||||
let anime_control = CtrlAnime::new(
|
||||
anime_config,
|
||||
inner.clone(),
|
||||
anime_proxy_blocking,
|
||||
early_return,
|
||||
)
|
||||
.unwrap();
|
||||
anime_control.add_to_server(&mut connection).await;
|
||||
loop {
|
||||
if let Ok(inner) = inner.clone().try_lock() {
|
||||
inner.run().ok();
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
}
|
||||
|
||||
// if supported.keyboard_led.per_key_led_mode {
|
||||
if let Some(cfg) = config.active_aura {
|
||||
let mut aura_config = ConfigAura::new().set_name(cfg).load();
|
||||
// let baord_name = std::fs::read_to_string(BOARD_NAME)?;
|
||||
|
||||
let led_support = LedSupportData::get_data("");
|
||||
|
||||
let layout = KeyLayout::find_layout(led_support, PathBuf::from(DATA_DIR))
|
||||
.map_err(|e| {
|
||||
println!("{BOARD_NAME}, {e}");
|
||||
})
|
||||
.unwrap_or_else(|_| KeyLayout::default_layout());
|
||||
|
||||
let aura_proxy_blocking = AuraProxyBlocking::new(&conn).unwrap();
|
||||
executor
|
||||
.spawn(async move {
|
||||
loop {
|
||||
aura_config.aura.next_state(&layout);
|
||||
let packets = aura_config.aura.create_packets();
|
||||
|
||||
aura_proxy_blocking.direct_addressing_raw(packets).unwrap();
|
||||
std::thread::sleep(std::time::Duration::from_millis(33));
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
// }
|
||||
|
||||
loop {
|
||||
smol::block_on(executor.tick());
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ pub enum Error {
|
||||
|
||||
impl fmt::Display for Error {
|
||||
// This trait requires `fmt` with this exact signature.
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Error::Io(err) => write!(f, "Failed to open: {}", err),
|
||||
Error::ConfigLoadFail => write!(f, "Failed to load user config"),
|
||||
@@ -1,4 +1,4 @@
|
||||
pub mod user_config;
|
||||
pub mod config;
|
||||
|
||||
pub mod error;
|
||||
|
||||
@@ -6,6 +6,4 @@ pub mod ctrl_anime;
|
||||
|
||||
pub mod zbus_anime;
|
||||
|
||||
pub static DBUS_NAME: &str = "org.asuslinux.Daemon";
|
||||
|
||||
pub static VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
@@ -1,7 +1,8 @@
|
||||
//! # DBus interface proxy for: `org.asuslinux.Daemon`
|
||||
//! # `DBus` interface proxy for: `org.asuslinux.Daemon`
|
||||
//!
|
||||
//! This code was generated by `zbus-xmlgen` `1.0.0` from DBus introspection data.
|
||||
//! Source: `Interface '/org/asuslinux/Anime' from service 'org.asuslinux.Daemon' on session bus`.
|
||||
//! This code was generated by `zbus-xmlgen` `1.0.0` from `DBus` introspection
|
||||
//! data. Source: `Interface '/org/asuslinux/Anime' from service
|
||||
//! 'org.asuslinux.Daemon' on session bus`.
|
||||
//!
|
||||
//! You may prefer to adapt it, instead of using it verbatim.
|
||||
//!
|
||||
@@ -9,8 +10,8 @@
|
||||
//! [Writing a client proxy](https://dbus.pages.freedesktop.org/zbus/client.html)
|
||||
//! section of the zbus documentation.
|
||||
//!
|
||||
//! This DBus object implements
|
||||
//! [standard DBus interfaces](https://dbus.freedesktop.org/doc/dbus-specification.html),
|
||||
//! This `DBus` object implements
|
||||
//! [standard `DBus` interfaces](https://dbus.freedesktop.org/doc/dbus-specification.html),
|
||||
//! (`org.freedesktop.DBus.*`) for which the following zbus proxies can be used:
|
||||
//!
|
||||
//! * [`zbus::fdo::PeerProxy`]
|
||||
@@ -20,9 +21,12 @@
|
||||
//! …consequently `zbus-xmlgen` did not generate code for the above interfaces.
|
||||
#![allow(clippy::too_many_arguments)]
|
||||
|
||||
use zbus::dbus_proxy;
|
||||
use zbus::proxy;
|
||||
|
||||
#[dbus_proxy(interface = "org.asuslinux.Daemon")]
|
||||
#[proxy(
|
||||
interface = "org.asuslinux.Daemon",
|
||||
default_path = "/org/asuslinux/Anime"
|
||||
)]
|
||||
trait Daemon {
|
||||
/// InsertAsusGif method
|
||||
fn insert_asus_gif(
|
||||
48
asusd/Cargo.toml
Normal file
48
asusd/Cargo.toml
Normal file
@@ -0,0 +1,48 @@
|
||||
[package]
|
||||
name = "asusd"
|
||||
license = "MPL-2.0"
|
||||
version.workspace = true
|
||||
readme = "README.md"
|
||||
authors = ["Luke <luke@ljones.dev>"]
|
||||
repository = "https://gitlab.com/asus-linux/asus-nb-ctrl"
|
||||
homepage = "https://gitlab.com/asus-linux/asus-nb-ctrl"
|
||||
description = "A daemon app for ASUS GX502 and similar laptops to control missing features"
|
||||
edition = "2021"
|
||||
|
||||
[[bin]]
|
||||
name = "asusd"
|
||||
path = "src/daemon.rs"
|
||||
|
||||
[dependencies]
|
||||
config-traits = { path = "../config-traits" }
|
||||
rog_anime = { path = "../rog-anime", features = ["dbus"] }
|
||||
rog_slash = { path = "../rog-slash", features = ["dbus"] }
|
||||
rog_aura = { path = "../rog-aura", features = ["dbus"] }
|
||||
rog_platform = { path = "../rog-platform" }
|
||||
rog_profiles = { path = "../rog-profiles" }
|
||||
dmi_id = { path = "../dmi-id" }
|
||||
futures-lite = "*"
|
||||
udev.workspace = true
|
||||
inotify.workspace = true
|
||||
|
||||
mio.workspace = true
|
||||
tokio.workspace = true
|
||||
# console-subscriber = "0.2.0"
|
||||
|
||||
# cli and logging
|
||||
log.workspace = true
|
||||
env_logger.workspace = true
|
||||
|
||||
zbus.workspace = true
|
||||
logind-zbus.workspace = true
|
||||
|
||||
# serialisation
|
||||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
|
||||
concat-idents.workspace = true
|
||||
|
||||
systemd-zbus = "*"
|
||||
|
||||
[dev-dependencies]
|
||||
cargo-husky.workspace = true
|
||||
228
asusd/src/config.rs
Normal file
228
asusd/src/config.rs
Normal file
@@ -0,0 +1,228 @@
|
||||
use config_traits::{StdConfig, StdConfigLoad3};
|
||||
use rog_platform::cpu::CPUEPP;
|
||||
use rog_platform::platform::ThrottlePolicy;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
const CONFIG_FILE: &str = "asusd.ron";
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, PartialEq, PartialOrd)]
|
||||
pub struct Config {
|
||||
/// Save charge limit for restoring on boot/resume
|
||||
pub charge_control_end_threshold: u8,
|
||||
pub panel_od: bool,
|
||||
pub boot_sound: bool,
|
||||
pub mini_led_mode: bool,
|
||||
pub disable_nvidia_powerd_on_battery: bool,
|
||||
/// An optional command/script to run when power is changed to AC
|
||||
pub ac_command: String,
|
||||
/// An optional command/script to run when power is changed to battery
|
||||
pub bat_command: String,
|
||||
/// Set true if energy_performance_preference should be set if the
|
||||
/// throttle/platform profile is changed
|
||||
pub throttle_policy_linked_epp: bool,
|
||||
/// Which throttle/profile to use on battery power
|
||||
pub throttle_policy_on_battery: ThrottlePolicy,
|
||||
/// Which throttle/profile to use on AC power
|
||||
pub throttle_policy_on_ac: ThrottlePolicy,
|
||||
/// The energy_performance_preference for this throttle/platform profile
|
||||
pub throttle_quiet_epp: CPUEPP,
|
||||
/// The energy_performance_preference for this throttle/platform profile
|
||||
pub throttle_balanced_epp: CPUEPP,
|
||||
/// The energy_performance_preference for this throttle/platform profile
|
||||
pub throttle_performance_epp: CPUEPP,
|
||||
/// Defaults to `None` if not supported
|
||||
pub ppt_pl1_spl: Option<u8>,
|
||||
/// Defaults to `None` if not supported
|
||||
pub ppt_pl2_sppt: Option<u8>,
|
||||
/// Defaults to `None` if not supported
|
||||
pub ppt_fppt: Option<u8>,
|
||||
/// Defaults to `None` if not supported
|
||||
pub ppt_apu_sppt: Option<u8>,
|
||||
/// Defaults to `None` if not supported
|
||||
pub ppt_platform_sppt: Option<u8>,
|
||||
/// Defaults to `None` if not supported
|
||||
pub nv_dynamic_boost: Option<u8>,
|
||||
/// Defaults to `None` if not supported
|
||||
pub nv_temp_target: Option<u8>,
|
||||
/// Temporary state for AC/Batt
|
||||
#[serde(skip)]
|
||||
pub last_power_plugged: u8,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
charge_control_end_threshold: 100,
|
||||
panel_od: false,
|
||||
boot_sound: false,
|
||||
mini_led_mode: false,
|
||||
disable_nvidia_powerd_on_battery: true,
|
||||
ac_command: Default::default(),
|
||||
bat_command: Default::default(),
|
||||
throttle_policy_linked_epp: true,
|
||||
throttle_policy_on_battery: ThrottlePolicy::Quiet,
|
||||
throttle_policy_on_ac: ThrottlePolicy::Performance,
|
||||
throttle_quiet_epp: CPUEPP::Power,
|
||||
throttle_balanced_epp: CPUEPP::BalancePower,
|
||||
throttle_performance_epp: CPUEPP::Performance,
|
||||
ppt_pl1_spl: Default::default(),
|
||||
ppt_pl2_sppt: Default::default(),
|
||||
ppt_fppt: Default::default(),
|
||||
ppt_apu_sppt: Default::default(),
|
||||
ppt_platform_sppt: Default::default(),
|
||||
nv_dynamic_boost: Default::default(),
|
||||
nv_temp_target: Default::default(),
|
||||
last_power_plugged: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StdConfig for Config {
|
||||
fn new() -> Self {
|
||||
Config {
|
||||
charge_control_end_threshold: 100,
|
||||
disable_nvidia_powerd_on_battery: true,
|
||||
throttle_policy_on_battery: ThrottlePolicy::Quiet,
|
||||
throttle_policy_on_ac: ThrottlePolicy::Performance,
|
||||
ac_command: String::new(),
|
||||
bat_command: String::new(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn file_name(&self) -> String {
|
||||
CONFIG_FILE.to_owned()
|
||||
}
|
||||
|
||||
fn config_dir() -> std::path::PathBuf {
|
||||
std::path::PathBuf::from(crate::CONFIG_PATH_BASE)
|
||||
}
|
||||
}
|
||||
|
||||
impl StdConfigLoad3<Config472, Config506, Config507> for Config {}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct Config507 {
|
||||
/// Save charge limit for restoring on boot
|
||||
pub charge_control_end_threshold: u8,
|
||||
pub panel_od: bool,
|
||||
pub mini_led_mode: bool,
|
||||
pub disable_nvidia_powerd_on_battery: bool,
|
||||
pub ac_command: String,
|
||||
pub bat_command: String,
|
||||
pub platform_policy_linked_epp: bool,
|
||||
pub platform_policy_on_battery: ThrottlePolicy,
|
||||
pub platform_policy_on_ac: ThrottlePolicy,
|
||||
//
|
||||
pub ppt_pl1_spl: Option<u8>,
|
||||
pub ppt_pl2_sppt: Option<u8>,
|
||||
pub ppt_fppt: Option<u8>,
|
||||
pub ppt_apu_sppt: Option<u8>,
|
||||
pub ppt_platform_sppt: Option<u8>,
|
||||
pub nv_dynamic_boost: Option<u8>,
|
||||
pub nv_temp_target: Option<u8>,
|
||||
}
|
||||
|
||||
impl From<Config507> for Config {
|
||||
fn from(c: Config507) -> Self {
|
||||
Self {
|
||||
charge_control_end_threshold: c.charge_control_end_threshold,
|
||||
panel_od: c.panel_od,
|
||||
boot_sound: false,
|
||||
disable_nvidia_powerd_on_battery: c.disable_nvidia_powerd_on_battery,
|
||||
ac_command: c.ac_command,
|
||||
bat_command: c.bat_command,
|
||||
mini_led_mode: c.mini_led_mode,
|
||||
throttle_policy_linked_epp: true,
|
||||
throttle_policy_on_battery: c.platform_policy_on_battery,
|
||||
throttle_policy_on_ac: c.platform_policy_on_ac,
|
||||
throttle_quiet_epp: CPUEPP::Power,
|
||||
throttle_balanced_epp: CPUEPP::BalancePower,
|
||||
throttle_performance_epp: CPUEPP::Performance,
|
||||
ppt_pl1_spl: c.ppt_pl1_spl,
|
||||
ppt_pl2_sppt: c.ppt_pl2_sppt,
|
||||
ppt_fppt: c.ppt_fppt,
|
||||
ppt_apu_sppt: c.ppt_apu_sppt,
|
||||
ppt_platform_sppt: c.ppt_platform_sppt,
|
||||
nv_dynamic_boost: c.nv_dynamic_boost,
|
||||
nv_temp_target: c.nv_temp_target,
|
||||
last_power_plugged: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct Config506 {
|
||||
/// Save charge limit for restoring on boot
|
||||
pub charge_control_end_threshold: u8,
|
||||
pub panel_od: bool,
|
||||
pub mini_led_mode: bool,
|
||||
pub disable_nvidia_powerd_on_battery: bool,
|
||||
pub ac_command: String,
|
||||
pub bat_command: String,
|
||||
/// Restored on boot as well as when power is plugged
|
||||
#[serde(skip)]
|
||||
pub platform_policy_to_restore: ThrottlePolicy,
|
||||
pub platform_policy_on_battery: ThrottlePolicy,
|
||||
pub platform_policy_on_ac: ThrottlePolicy,
|
||||
//
|
||||
pub ppt_pl1_spl: Option<u8>,
|
||||
pub ppt_pl2_sppt: Option<u8>,
|
||||
pub ppt_fppt: Option<u8>,
|
||||
pub ppt_apu_sppt: Option<u8>,
|
||||
pub ppt_platform_sppt: Option<u8>,
|
||||
pub nv_dynamic_boost: Option<u8>,
|
||||
pub nv_temp_target: Option<u8>,
|
||||
}
|
||||
|
||||
impl From<Config506> for Config {
|
||||
fn from(c: Config506) -> Self {
|
||||
Self {
|
||||
charge_control_end_threshold: c.charge_control_end_threshold,
|
||||
panel_od: c.panel_od,
|
||||
boot_sound: false,
|
||||
disable_nvidia_powerd_on_battery: c.disable_nvidia_powerd_on_battery,
|
||||
ac_command: c.ac_command,
|
||||
bat_command: c.bat_command,
|
||||
mini_led_mode: c.mini_led_mode,
|
||||
throttle_policy_linked_epp: true,
|
||||
throttle_policy_on_battery: c.platform_policy_on_battery,
|
||||
throttle_policy_on_ac: c.platform_policy_on_ac,
|
||||
throttle_quiet_epp: CPUEPP::Power,
|
||||
throttle_balanced_epp: CPUEPP::BalancePower,
|
||||
throttle_performance_epp: CPUEPP::Performance,
|
||||
ppt_pl1_spl: c.ppt_pl1_spl,
|
||||
ppt_pl2_sppt: c.ppt_pl2_sppt,
|
||||
ppt_fppt: c.ppt_fppt,
|
||||
ppt_apu_sppt: c.ppt_apu_sppt,
|
||||
ppt_platform_sppt: c.ppt_platform_sppt,
|
||||
nv_dynamic_boost: c.nv_dynamic_boost,
|
||||
nv_temp_target: c.nv_temp_target,
|
||||
last_power_plugged: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct Config472 {
|
||||
/// Save charge limit for restoring on boot
|
||||
pub bat_charge_limit: u8,
|
||||
pub panel_od: bool,
|
||||
pub mini_led_mode: bool,
|
||||
pub disable_nvidia_powerd_on_battery: bool,
|
||||
pub ac_command: String,
|
||||
pub bat_command: String,
|
||||
}
|
||||
|
||||
impl From<Config472> for Config {
|
||||
fn from(c: Config472) -> Self {
|
||||
Self {
|
||||
charge_control_end_threshold: c.bat_charge_limit,
|
||||
panel_od: c.panel_od,
|
||||
disable_nvidia_powerd_on_battery: true,
|
||||
ac_command: c.ac_command,
|
||||
bat_command: c.bat_command,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
229
asusd/src/ctrl_anime/config.rs
Normal file
229
asusd/src/ctrl_anime/config.rs
Normal file
@@ -0,0 +1,229 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use config_traits::{StdConfig, StdConfigLoad2};
|
||||
use rog_anime::error::AnimeError;
|
||||
use rog_anime::usb::Brightness;
|
||||
use rog_anime::{
|
||||
ActionData, ActionLoader, AnimTime, Animations, AnimeType, DeviceState, Fade, Vec2,
|
||||
};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
const CONFIG_FILE: &str = "anime.ron";
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct AnimeConfigV460 {
|
||||
pub system: Vec<ActionLoader>,
|
||||
pub boot: Vec<ActionLoader>,
|
||||
pub wake: Vec<ActionLoader>,
|
||||
pub sleep: Vec<ActionLoader>,
|
||||
pub shutdown: Vec<ActionLoader>,
|
||||
pub brightness: f32,
|
||||
}
|
||||
|
||||
impl From<AnimeConfigV460> for AnimeConfig {
|
||||
fn from(c: AnimeConfigV460) -> AnimeConfig {
|
||||
AnimeConfig {
|
||||
system: c.system,
|
||||
boot: c.boot,
|
||||
wake: c.wake,
|
||||
shutdown: c.shutdown,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
pub struct AnimeConfigV472 {
|
||||
pub model_override: Option<AnimeType>,
|
||||
pub system: Vec<ActionLoader>,
|
||||
pub boot: Vec<ActionLoader>,
|
||||
pub wake: Vec<ActionLoader>,
|
||||
pub sleep: Vec<ActionLoader>,
|
||||
pub shutdown: Vec<ActionLoader>,
|
||||
pub brightness: f32,
|
||||
pub display_enabled: bool,
|
||||
pub display_brightness: Brightness,
|
||||
pub builtin_anims_enabled: bool,
|
||||
pub builtin_anims: Animations,
|
||||
}
|
||||
|
||||
impl From<AnimeConfigV472> for AnimeConfig {
|
||||
fn from(c: AnimeConfigV472) -> AnimeConfig {
|
||||
AnimeConfig {
|
||||
system: c.system,
|
||||
boot: c.boot,
|
||||
wake: c.wake,
|
||||
shutdown: c.shutdown,
|
||||
model_override: c.model_override,
|
||||
display_enabled: c.display_enabled,
|
||||
display_brightness: c.display_brightness,
|
||||
builtin_anims_enabled: c.builtin_anims_enabled,
|
||||
builtin_anims: c.builtin_anims,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Default)]
|
||||
pub struct AnimeConfigCached {
|
||||
pub system: Vec<ActionData>,
|
||||
pub boot: Vec<ActionData>,
|
||||
pub wake: Vec<ActionData>,
|
||||
pub shutdown: Vec<ActionData>,
|
||||
}
|
||||
|
||||
impl AnimeConfigCached {
|
||||
pub fn init_from_config(
|
||||
&mut self,
|
||||
config: &AnimeConfig,
|
||||
anime_type: AnimeType,
|
||||
) -> Result<(), AnimeError> {
|
||||
let mut sys = Vec::with_capacity(config.system.len());
|
||||
for ani in &config.system {
|
||||
sys.push(ActionData::from_anime_action(anime_type, ani)?);
|
||||
}
|
||||
self.system = sys;
|
||||
|
||||
let mut boot = Vec::with_capacity(config.boot.len());
|
||||
for ani in &config.boot {
|
||||
boot.push(ActionData::from_anime_action(anime_type, ani)?);
|
||||
}
|
||||
self.boot = boot;
|
||||
|
||||
let mut wake = Vec::with_capacity(config.wake.len());
|
||||
for ani in &config.wake {
|
||||
wake.push(ActionData::from_anime_action(anime_type, ani)?);
|
||||
}
|
||||
self.wake = wake;
|
||||
|
||||
let mut shutdown = Vec::with_capacity(config.shutdown.len());
|
||||
for ani in &config.shutdown {
|
||||
shutdown.push(ActionData::from_anime_action(anime_type, ani)?);
|
||||
}
|
||||
self.shutdown = shutdown;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Config for base system actions for the anime display
|
||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
pub struct AnimeConfig {
|
||||
pub model_override: Option<AnimeType>,
|
||||
pub system: Vec<ActionLoader>,
|
||||
pub boot: Vec<ActionLoader>,
|
||||
pub wake: Vec<ActionLoader>,
|
||||
pub shutdown: Vec<ActionLoader>,
|
||||
// pub brightness: f32,
|
||||
pub display_enabled: bool,
|
||||
pub display_brightness: Brightness,
|
||||
pub builtin_anims_enabled: bool,
|
||||
pub off_when_unplugged: bool,
|
||||
pub off_when_suspended: bool,
|
||||
pub off_when_lid_closed: bool,
|
||||
pub brightness_on_battery: Brightness,
|
||||
pub builtin_anims: Animations,
|
||||
}
|
||||
|
||||
impl Default for AnimeConfig {
|
||||
fn default() -> Self {
|
||||
AnimeConfig {
|
||||
model_override: None,
|
||||
system: Vec::new(),
|
||||
boot: Vec::new(),
|
||||
wake: Vec::new(),
|
||||
shutdown: Vec::new(),
|
||||
// brightness: 1.0,
|
||||
display_enabled: true,
|
||||
display_brightness: Brightness::Med,
|
||||
builtin_anims_enabled: true,
|
||||
off_when_unplugged: true,
|
||||
off_when_suspended: true,
|
||||
off_when_lid_closed: true,
|
||||
brightness_on_battery: Brightness::Low,
|
||||
builtin_anims: Animations::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StdConfig for AnimeConfig {
|
||||
fn new() -> Self {
|
||||
Self::create_default()
|
||||
}
|
||||
|
||||
fn file_name(&self) -> String {
|
||||
CONFIG_FILE.to_owned()
|
||||
}
|
||||
|
||||
fn config_dir() -> std::path::PathBuf {
|
||||
std::path::PathBuf::from(crate::CONFIG_PATH_BASE)
|
||||
}
|
||||
}
|
||||
|
||||
impl StdConfigLoad2<AnimeConfigV460, AnimeConfigV472> for AnimeConfig {}
|
||||
|
||||
impl From<&AnimeConfig> for DeviceState {
|
||||
fn from(config: &AnimeConfig) -> Self {
|
||||
DeviceState {
|
||||
display_enabled: config.display_enabled,
|
||||
display_brightness: config.display_brightness,
|
||||
builtin_anims_enabled: config.builtin_anims_enabled,
|
||||
builtin_anims: config.builtin_anims,
|
||||
off_when_unplugged: config.off_when_unplugged,
|
||||
off_when_suspended: config.off_when_suspended,
|
||||
off_when_lid_closed: config.off_when_lid_closed,
|
||||
brightness_on_battery: config.brightness_on_battery,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AnimeConfig {
|
||||
// fn clamp_config_brightness(mut config: &mut AnimeConfig) {
|
||||
// if config.brightness < 0.0 || config.brightness > 1.0 {
|
||||
// warn!(
|
||||
// "Clamped brightness to [0.0 ; 1.0], was {}",
|
||||
// config.brightness
|
||||
// );
|
||||
// config.brightness = f32::max(0.0, f32::min(1.0, config.brightness));
|
||||
// }
|
||||
// }
|
||||
|
||||
fn create_default() -> Self {
|
||||
// create a default config here
|
||||
AnimeConfig {
|
||||
system: vec![],
|
||||
boot: vec![ActionLoader::ImageAnimation {
|
||||
file: "/usr/share/asusd/anime/custom/sonic-run.gif".into(),
|
||||
scale: 0.9,
|
||||
angle: 0.65,
|
||||
translation: Vec2::default(),
|
||||
brightness: 1.0,
|
||||
time: AnimTime::Fade(Fade::new(
|
||||
Duration::from_secs(2),
|
||||
Some(Duration::from_secs(2)),
|
||||
Duration::from_secs(2),
|
||||
)),
|
||||
}],
|
||||
wake: vec![ActionLoader::ImageAnimation {
|
||||
file: "/usr/share/asusd/anime/custom/sonic-run.gif".into(),
|
||||
scale: 0.9,
|
||||
angle: 0.65,
|
||||
translation: Vec2::default(),
|
||||
brightness: 1.0,
|
||||
time: AnimTime::Fade(Fade::new(
|
||||
Duration::from_secs(2),
|
||||
Some(Duration::from_secs(2)),
|
||||
Duration::from_secs(2),
|
||||
)),
|
||||
}],
|
||||
shutdown: vec![ActionLoader::ImageAnimation {
|
||||
file: "/usr/share/asusd/anime/custom/sonic-wait.gif".into(),
|
||||
scale: 0.9,
|
||||
angle: 0.0,
|
||||
translation: Vec2::new(3.0, 2.0),
|
||||
brightness: 1.0,
|
||||
time: AnimTime::Infinite,
|
||||
}],
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
290
asusd/src/ctrl_anime/mod.rs
Normal file
290
asusd/src/ctrl_anime/mod.rs
Normal file
@@ -0,0 +1,290 @@
|
||||
pub mod config;
|
||||
/// Implements `CtrlTask`, Reloadable, `ZbusRun`
|
||||
pub mod trait_impls;
|
||||
|
||||
use std::convert::TryFrom;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::thread::sleep;
|
||||
|
||||
use ::zbus::export::futures_util::lock::Mutex;
|
||||
use log::{error, info, warn};
|
||||
use rog_anime::error::AnimeError;
|
||||
use rog_anime::usb::{
|
||||
get_anime_type, pkt_flush, pkt_set_brightness, pkt_set_enable_display,
|
||||
pkt_set_enable_powersave_anim, pkts_for_init, Brightness,
|
||||
};
|
||||
use rog_anime::{ActionData, AnimeDataBuffer, AnimePacketType, AnimeType};
|
||||
use rog_platform::hid_raw::HidRaw;
|
||||
use rog_platform::usb_raw::USBRaw;
|
||||
|
||||
use self::config::{AnimeConfig, AnimeConfigCached};
|
||||
use crate::error::RogError;
|
||||
|
||||
enum Node {
|
||||
Usb(USBRaw),
|
||||
Hid(HidRaw),
|
||||
}
|
||||
|
||||
impl Node {
|
||||
pub fn write_bytes(&self, message: &[u8]) -> Result<(), RogError> {
|
||||
// TODO: map and pass on errors
|
||||
match self {
|
||||
Node::Usb(u) => {
|
||||
u.write_bytes(message).ok();
|
||||
}
|
||||
Node::Hid(h) => {
|
||||
h.write_bytes(message).ok();
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_builtins_enabled(&self, enabled: bool, bright: Brightness) -> Result<(), RogError> {
|
||||
self.write_bytes(&pkt_set_enable_powersave_anim(enabled))?;
|
||||
self.write_bytes(&pkt_set_enable_display(enabled))?;
|
||||
self.write_bytes(&pkt_set_brightness(bright))?;
|
||||
self.write_bytes(&pkt_set_enable_powersave_anim(enabled))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CtrlAnime {
|
||||
// node: HidRaw,
|
||||
node: Node,
|
||||
anime_type: AnimeType,
|
||||
cache: AnimeConfigCached,
|
||||
config: AnimeConfig,
|
||||
// set to force thread to exit
|
||||
thread_exit: Arc<AtomicBool>,
|
||||
// Set to false when the thread exits
|
||||
thread_running: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl CtrlAnime {
|
||||
#[inline]
|
||||
pub fn new(config: AnimeConfig) -> Result<CtrlAnime, RogError> {
|
||||
let usb = USBRaw::new(0x193b).ok();
|
||||
let hid = HidRaw::new("193b").ok();
|
||||
let node = if usb.is_some() {
|
||||
info!("Anime using the USB interface");
|
||||
unsafe { Node::Usb(usb.unwrap_unchecked()) }
|
||||
} else if hid.is_some() {
|
||||
info!("Anime using the HID interface");
|
||||
unsafe { Node::Hid(hid.unwrap_unchecked()) }
|
||||
} else {
|
||||
return Err(RogError::Anime(AnimeError::NoDevice));
|
||||
};
|
||||
|
||||
// TODO: something better to set wakeups disabled
|
||||
// if matches!(node, Node::Usb(_)) {
|
||||
// if let Ok(mut enumerator) = udev::Enumerator::new() {
|
||||
// enumerator.match_subsystem("usb").ok();
|
||||
// enumerator.match_attribute("idProduct", "193b").ok();
|
||||
|
||||
// if let Ok(mut enumer) = enumerator.scan_devices() {
|
||||
// if let Some(mut dev) = enumer.next() {
|
||||
// dev.set_attribute_value("power/wakeup", "disabled").ok();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
let mut anime_type = get_anime_type()?;
|
||||
if let AnimeType::Unknown = anime_type {
|
||||
if let Some(model) = config.model_override {
|
||||
warn!("Overriding the Animatrix type as {model:?}");
|
||||
anime_type = model;
|
||||
}
|
||||
}
|
||||
|
||||
info!("Device has an AniMe Matrix display: {anime_type:?}");
|
||||
let mut cache = AnimeConfigCached::default();
|
||||
cache.init_from_config(&config, anime_type)?;
|
||||
|
||||
let ctrl = CtrlAnime {
|
||||
node,
|
||||
anime_type,
|
||||
cache,
|
||||
config,
|
||||
thread_exit: Arc::new(AtomicBool::new(false)),
|
||||
thread_running: Arc::new(AtomicBool::new(false)),
|
||||
};
|
||||
ctrl.do_initialization()?;
|
||||
|
||||
Ok(ctrl)
|
||||
}
|
||||
|
||||
// let device = CtrlAnime::get_device(0x0b05, 0x193b)?;
|
||||
|
||||
/// Start an action thread. This is classed as a singleton and there should
|
||||
/// be only one running - so the thread uses atomics to signal run/exit.
|
||||
///
|
||||
/// Because this also writes to the usb device, other write tries (display
|
||||
/// only) *must* get the mutex lock and set the `thread_exit` atomic.
|
||||
async fn run_thread(inner: Arc<Mutex<CtrlAnime>>, actions: Vec<ActionData>, mut once: bool) {
|
||||
if actions.is_empty() {
|
||||
warn!("AniMe system actions was empty");
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(lock) = inner.try_lock() {
|
||||
lock.node
|
||||
.write_bytes(&pkt_set_enable_powersave_anim(false))
|
||||
.map_err(|err| {
|
||||
warn!("rog_anime::run_animation:callback {}", err);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
// Loop rules:
|
||||
// - Lock the mutex **only when required**. That is, the lock must be held for
|
||||
// the shortest duration possible.
|
||||
// - An AtomicBool used for thread exit should be checked in every loop,
|
||||
// including nested
|
||||
|
||||
// The only reason for this outer thread is to prevent blocking while waiting
|
||||
// for the next spawned thread to exit
|
||||
// TODO: turn this in to async task (maybe? COuld still risk blocking main
|
||||
// thread)
|
||||
std::thread::Builder::new()
|
||||
.name("AniMe system thread start".into())
|
||||
.spawn(move || {
|
||||
info!("AniMe new system thread started");
|
||||
// Getting copies of these Atomics is done *in* the thread to ensure
|
||||
// we don't block other threads/main
|
||||
let thread_exit;
|
||||
let thread_running;
|
||||
let anime_type;
|
||||
loop {
|
||||
if let Some(lock) = inner.try_lock() {
|
||||
thread_exit = lock.thread_exit.clone();
|
||||
thread_running = lock.thread_running.clone();
|
||||
anime_type = lock.anime_type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// First two loops are to ensure we *do* aquire a lock on the mutex
|
||||
// The reason the loop is required is because the USB writes can block
|
||||
// for up to 10ms. We can't fail to get the atomics.
|
||||
while thread_running.load(Ordering::SeqCst) {
|
||||
// Make any running loop exit first
|
||||
thread_exit.store(true, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
info!("AniMe no previous system thread running (now)");
|
||||
thread_exit.store(false, Ordering::SeqCst);
|
||||
thread_running.store(true, Ordering::SeqCst);
|
||||
'main: loop {
|
||||
for action in &actions {
|
||||
if thread_exit.load(Ordering::SeqCst) {
|
||||
break 'main;
|
||||
}
|
||||
match action {
|
||||
ActionData::Animation(frames) => {
|
||||
rog_anime::run_animation(frames, &|frame| {
|
||||
if thread_exit.load(Ordering::Acquire) {
|
||||
info!("rog-anime: animation sub-loop was asked to exit");
|
||||
return Ok(true); // Do safe exit
|
||||
}
|
||||
inner
|
||||
.try_lock()
|
||||
.map(|lock| {
|
||||
lock.write_data_buffer(frame)
|
||||
.map_err(|err| {
|
||||
warn!(
|
||||
"rog_anime::run_animation:callback {}",
|
||||
err
|
||||
);
|
||||
})
|
||||
.ok();
|
||||
false // Don't exit yet
|
||||
})
|
||||
.map_or_else(
|
||||
|| {
|
||||
warn!("rog_anime::run_animation:callback failed");
|
||||
Err(AnimeError::NoFrames)
|
||||
},
|
||||
Ok,
|
||||
)
|
||||
});
|
||||
if thread_exit.load(Ordering::Acquire) {
|
||||
info!("rog-anime: sub-loop exited and main loop exiting now");
|
||||
break 'main;
|
||||
}
|
||||
}
|
||||
ActionData::Image(image) => {
|
||||
once = false;
|
||||
if let Some(lock) = inner.try_lock() {
|
||||
lock.write_data_buffer(image.as_ref().clone())
|
||||
.map_err(|e| error!("{}", e))
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
ActionData::Pause(duration) => sleep(*duration),
|
||||
ActionData::AudioEq
|
||||
| ActionData::SystemInfo
|
||||
| ActionData::TimeDate
|
||||
| ActionData::Matrix => {}
|
||||
}
|
||||
}
|
||||
if thread_exit.load(Ordering::SeqCst) {
|
||||
break 'main;
|
||||
}
|
||||
if once || actions.is_empty() {
|
||||
break 'main;
|
||||
}
|
||||
}
|
||||
// Clear the display on exit
|
||||
if let Some(lock) = inner.try_lock() {
|
||||
if let Ok(data) =
|
||||
AnimeDataBuffer::from_vec(anime_type, vec![0u8; anime_type.data_length()])
|
||||
.map_err(|e| error!("{}", e))
|
||||
{
|
||||
lock.write_data_buffer(data)
|
||||
.map_err(|err| {
|
||||
warn!("rog_anime::run_animation:callback {}", err);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
lock.node
|
||||
.write_bytes(&pkt_set_enable_powersave_anim(
|
||||
lock.config.builtin_anims_enabled,
|
||||
))
|
||||
.map_err(|err| {
|
||||
warn!("rog_anime::run_animation:callback {}", err);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
// Loop ended, set the atmonics
|
||||
thread_running.store(false, Ordering::SeqCst);
|
||||
info!("AniMe system thread exited");
|
||||
})
|
||||
.map(|err| info!("AniMe system thread: {:?}", err))
|
||||
.ok();
|
||||
}
|
||||
|
||||
/// Write only a data packet. This will modify the leds brightness using the
|
||||
/// global brightness set in config.
|
||||
fn write_data_buffer(&self, mut buffer: AnimeDataBuffer) -> Result<(), RogError> {
|
||||
for led in buffer.data_mut().iter_mut() {
|
||||
let mut bright = *led as f32;
|
||||
if bright > 254.0 {
|
||||
bright = 254.0;
|
||||
}
|
||||
*led = bright as u8;
|
||||
}
|
||||
let data = AnimePacketType::try_from(buffer)?;
|
||||
for row in &data {
|
||||
self.node.write_bytes(row)?;
|
||||
}
|
||||
self.node.write_bytes(&pkt_flush())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn do_initialization(&self) -> Result<(), RogError> {
|
||||
let pkts = pkts_for_init();
|
||||
self.node.write_bytes(&pkts[0])?;
|
||||
self.node.write_bytes(&pkts[1])?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
522
asusd/src/ctrl_anime/trait_impls.rs
Normal file
522
asusd/src/ctrl_anime/trait_impls.rs
Normal file
@@ -0,0 +1,522 @@
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
|
||||
use config_traits::StdConfig;
|
||||
use log::warn;
|
||||
use logind_zbus::manager::ManagerProxy;
|
||||
use rog_anime::usb::{
|
||||
pkt_set_brightness, pkt_set_builtin_animations, pkt_set_enable_display,
|
||||
pkt_set_enable_powersave_anim, Brightness,
|
||||
};
|
||||
use rog_anime::{Animations, AnimeDataBuffer, DeviceState};
|
||||
use zbus::export::futures_util::lock::Mutex;
|
||||
use zbus::{interface, CacheProperties, Connection, SignalContext};
|
||||
|
||||
use super::config::AnimeConfig;
|
||||
use super::CtrlAnime;
|
||||
use crate::error::RogError;
|
||||
|
||||
pub const ANIME_ZBUS_NAME: &str = "Anime";
|
||||
pub const ANIME_ZBUS_PATH: &str = "/org/asuslinux";
|
||||
|
||||
async fn get_logind_manager<'a>() -> ManagerProxy<'a> {
|
||||
let connection = Connection::system()
|
||||
.await
|
||||
.expect("Controller could not create dbus connection");
|
||||
|
||||
ManagerProxy::builder(&connection)
|
||||
.cache_properties(CacheProperties::No)
|
||||
.build()
|
||||
.await
|
||||
.expect("Controller could not create ManagerProxy")
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CtrlAnimeZbus(pub Arc<Mutex<CtrlAnime>>);
|
||||
|
||||
/// The struct with the main dbus methods requires this trait
|
||||
impl crate::ZbusRun for CtrlAnimeZbus {
|
||||
async fn add_to_server(self, server: &mut Connection) {
|
||||
Self::add_to_server_helper(self, ANIME_ZBUS_PATH, server).await;
|
||||
}
|
||||
}
|
||||
|
||||
// None of these calls can be guarnateed to succeed unless we loop until okay
|
||||
// If the try_lock *does* succeed then any other thread trying to lock will not
|
||||
// grab it until we finish.
|
||||
#[interface(name = "org.asuslinux.Anime")]
|
||||
impl CtrlAnimeZbus {
|
||||
/// Writes a data stream of length. Will force system thread to exit until
|
||||
/// it is restarted
|
||||
async fn write(&self, input: AnimeDataBuffer) -> zbus::fdo::Result<()> {
|
||||
self.0
|
||||
.lock()
|
||||
.await
|
||||
.thread_exit
|
||||
.store(true, Ordering::SeqCst);
|
||||
self.0
|
||||
.lock()
|
||||
.await
|
||||
.write_data_buffer(input)
|
||||
.map_err(|err| {
|
||||
warn!("ctrl_anime::run_animation:callback {}", err);
|
||||
err
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set base brightness level
|
||||
#[zbus(property)]
|
||||
async fn brightness(&self) -> Brightness {
|
||||
self.0.lock().await.config.display_brightness
|
||||
}
|
||||
|
||||
/// Set base brightness level
|
||||
#[zbus(property)]
|
||||
async fn set_brightness(&self, brightness: Brightness) {
|
||||
self.0
|
||||
.lock()
|
||||
.await
|
||||
.node
|
||||
.write_bytes(&pkt_set_brightness(brightness))
|
||||
.map_err(|err| {
|
||||
warn!("ctrl_anime::set_brightness {}", err);
|
||||
})
|
||||
.ok();
|
||||
self.0
|
||||
.lock()
|
||||
.await
|
||||
.node
|
||||
.write_bytes(&pkt_set_enable_display(brightness != Brightness::Off))
|
||||
.map_err(|err| {
|
||||
warn!("ctrl_anime::set_brightness {}", err);
|
||||
})
|
||||
.ok();
|
||||
|
||||
self.0.lock().await.config.display_enabled = brightness != Brightness::Off;
|
||||
self.0.lock().await.config.display_brightness = brightness;
|
||||
self.0.lock().await.config.write();
|
||||
}
|
||||
|
||||
#[zbus(property)]
|
||||
async fn builtins_enabled(&self) -> bool {
|
||||
let lock = self.0.lock().await;
|
||||
lock.config.builtin_anims_enabled
|
||||
}
|
||||
|
||||
/// Enable the builtin animations or not. This is quivalent to "Powersave
|
||||
/// animations" in Armory crate
|
||||
#[zbus(property)]
|
||||
async fn set_builtins_enabled(&self, enabled: bool) {
|
||||
let brightness = self.0.lock().await.config.display_brightness;
|
||||
self.0
|
||||
.lock()
|
||||
.await
|
||||
.node
|
||||
.set_builtins_enabled(enabled, brightness)
|
||||
.map_err(|err| {
|
||||
warn!("ctrl_anime::set_builtins_enabled {}", err);
|
||||
})
|
||||
.ok();
|
||||
|
||||
if !enabled {
|
||||
let anime_type = self.0.lock().await.anime_type;
|
||||
let data = vec![255u8; anime_type.data_length()];
|
||||
if let Ok(tmp) = AnimeDataBuffer::from_vec(anime_type, data).map_err(|err| {
|
||||
warn!("ctrl_anime::set_builtins_enabled {}", err);
|
||||
}) {
|
||||
self.0
|
||||
.lock()
|
||||
.await
|
||||
.node
|
||||
.write_bytes(tmp.data())
|
||||
.map_err(|err| {
|
||||
warn!("ctrl_anime::set_builtins_enabled {}", err);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
self.0.lock().await.config.builtin_anims_enabled = enabled;
|
||||
self.0.lock().await.config.write();
|
||||
if enabled {
|
||||
self.0
|
||||
.lock()
|
||||
.await
|
||||
.thread_exit
|
||||
.store(true, Ordering::Release);
|
||||
}
|
||||
}
|
||||
|
||||
#[zbus(property)]
|
||||
async fn builtin_animations(&self) -> Animations {
|
||||
self.0.lock().await.config.builtin_anims
|
||||
}
|
||||
|
||||
/// Set which builtin animation is used for each stage
|
||||
#[zbus(property)]
|
||||
async fn set_builtin_animations(&self, settings: Animations) {
|
||||
self.0
|
||||
.lock()
|
||||
.await
|
||||
.node
|
||||
.write_bytes(&pkt_set_builtin_animations(
|
||||
settings.boot,
|
||||
settings.awake,
|
||||
settings.sleep,
|
||||
settings.shutdown,
|
||||
))
|
||||
.map_err(|err| {
|
||||
warn!("ctrl_anime::run_animation:callback {}", err);
|
||||
})
|
||||
.ok();
|
||||
self.0
|
||||
.lock()
|
||||
.await
|
||||
.node
|
||||
.write_bytes(&pkt_set_enable_powersave_anim(true))
|
||||
.map_err(|err| {
|
||||
warn!("ctrl_anime::run_animation:callback {}", err);
|
||||
})
|
||||
.ok();
|
||||
self.0.lock().await.config.display_enabled = true;
|
||||
self.0.lock().await.config.builtin_anims = settings;
|
||||
self.0.lock().await.config.write();
|
||||
}
|
||||
|
||||
#[zbus(property)]
|
||||
async fn enable_display(&self) -> bool {
|
||||
self.0.lock().await.config.display_enabled
|
||||
}
|
||||
|
||||
/// Set whether the AniMe is enabled at all
|
||||
#[zbus(property)]
|
||||
async fn set_enable_display(&self, enabled: bool) {
|
||||
self.0
|
||||
.lock()
|
||||
.await
|
||||
.node
|
||||
.write_bytes(&pkt_set_enable_display(enabled))
|
||||
.map_err(|err| {
|
||||
warn!("ctrl_anime::run_animation:callback {}", err);
|
||||
})
|
||||
.ok();
|
||||
self.0.lock().await.config.display_enabled = enabled;
|
||||
self.0.lock().await.config.write();
|
||||
}
|
||||
|
||||
#[zbus(property)]
|
||||
async fn off_when_unplugged(&self) -> bool {
|
||||
self.0.lock().await.config.off_when_unplugged
|
||||
}
|
||||
|
||||
/// Set if to turn the AniMe Matrix off when external power is unplugged
|
||||
#[zbus(property)]
|
||||
async fn set_off_when_unplugged(&self, enabled: bool) {
|
||||
let manager = get_logind_manager().await;
|
||||
let pow = manager.on_external_power().await.unwrap_or_default();
|
||||
|
||||
self.0
|
||||
.lock()
|
||||
.await
|
||||
.node
|
||||
.write_bytes(&pkt_set_enable_display(!pow && !enabled))
|
||||
.map_err(|err| {
|
||||
warn!("create_sys_event_tasks::off_when_lid_closed {}", err);
|
||||
})
|
||||
.ok();
|
||||
|
||||
self.0.lock().await.config.off_when_unplugged = enabled;
|
||||
self.0.lock().await.config.write();
|
||||
}
|
||||
|
||||
#[zbus(property)]
|
||||
async fn off_when_suspended(&self) -> bool {
|
||||
self.0.lock().await.config.off_when_suspended
|
||||
}
|
||||
|
||||
/// Set if to turn the AniMe Matrix off when the laptop is suspended
|
||||
#[zbus(property)]
|
||||
async fn set_off_when_suspended(&self, enabled: bool) {
|
||||
self.0.lock().await.config.off_when_suspended = enabled;
|
||||
self.0.lock().await.config.write();
|
||||
}
|
||||
|
||||
#[zbus(property)]
|
||||
async fn off_when_lid_closed(&self) -> bool {
|
||||
self.0.lock().await.config.off_when_lid_closed
|
||||
}
|
||||
|
||||
/// Set if to turn the AniMe Matrix off when the lid is closed
|
||||
#[zbus(property)]
|
||||
async fn set_off_when_lid_closed(&self, enabled: bool) {
|
||||
let manager = get_logind_manager().await;
|
||||
let lid = manager.lid_closed().await.unwrap_or_default();
|
||||
|
||||
self.0
|
||||
.lock()
|
||||
.await
|
||||
.node
|
||||
.write_bytes(&pkt_set_enable_display(lid && !enabled))
|
||||
.map_err(|err| {
|
||||
warn!("create_sys_event_tasks::off_when_lid_closed {}", err);
|
||||
})
|
||||
.ok();
|
||||
|
||||
self.0.lock().await.config.off_when_lid_closed = enabled;
|
||||
self.0.lock().await.config.write();
|
||||
}
|
||||
|
||||
/// The main loop is the base system set action if the user isn't running
|
||||
/// the user daemon
|
||||
async fn run_main_loop(&self, start: bool) {
|
||||
if start {
|
||||
self.0
|
||||
.lock()
|
||||
.await
|
||||
.thread_exit
|
||||
.store(true, Ordering::SeqCst);
|
||||
CtrlAnime::run_thread(
|
||||
self.0.clone(),
|
||||
self.0.lock().await.cache.system.clone(),
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the device state as stored by asusd
|
||||
// #[zbus(property)]
|
||||
async fn device_state(&self) -> DeviceState {
|
||||
DeviceState::from(&self.0.lock().await.config)
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::CtrlTask for CtrlAnimeZbus {
|
||||
fn zbus_path() -> &'static str {
|
||||
ANIME_ZBUS_PATH
|
||||
}
|
||||
|
||||
async fn create_tasks(&self, _: SignalContext<'static>) -> Result<(), RogError> {
|
||||
let inner1 = self.0.clone();
|
||||
let inner2 = self.0.clone();
|
||||
let inner3 = self.0.clone();
|
||||
let inner4 = self.0.clone();
|
||||
self.create_sys_event_tasks(
|
||||
move |sleeping| {
|
||||
// on_sleep
|
||||
let inner = inner1.clone();
|
||||
async move {
|
||||
let config = inner.lock().await.config.clone();
|
||||
if config.display_enabled {
|
||||
inner
|
||||
.lock()
|
||||
.await
|
||||
.thread_exit
|
||||
.store(true, Ordering::Release); // ensure clean slate
|
||||
|
||||
inner
|
||||
.lock()
|
||||
.await
|
||||
.node
|
||||
.write_bytes(&pkt_set_enable_display(
|
||||
!(sleeping && config.off_when_suspended),
|
||||
))
|
||||
.map_err(|err| {
|
||||
warn!("create_sys_event_tasks::off_when_suspended {}", err);
|
||||
})
|
||||
.ok();
|
||||
|
||||
if config.builtin_anims_enabled {
|
||||
inner
|
||||
.lock()
|
||||
.await
|
||||
.node
|
||||
.write_bytes(&pkt_set_enable_powersave_anim(
|
||||
!(sleeping && config.off_when_suspended),
|
||||
))
|
||||
.map_err(|err| {
|
||||
warn!("create_sys_event_tasks::off_when_suspended {}", err);
|
||||
})
|
||||
.ok();
|
||||
} else if !sleeping && !config.builtin_anims_enabled {
|
||||
// Run custom wake animation
|
||||
inner
|
||||
.lock()
|
||||
.await
|
||||
.node
|
||||
.write_bytes(&pkt_set_enable_powersave_anim(false))
|
||||
.ok(); // ensure builtins are disabled
|
||||
|
||||
CtrlAnime::run_thread(
|
||||
inner.clone(),
|
||||
inner.lock().await.cache.wake.clone(),
|
||||
true,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
move |shutting_down| {
|
||||
// on_shutdown
|
||||
let inner = inner2.clone();
|
||||
async move {
|
||||
let AnimeConfig {
|
||||
display_enabled,
|
||||
builtin_anims_enabled,
|
||||
..
|
||||
} = inner.lock().await.config;
|
||||
if display_enabled && !builtin_anims_enabled {
|
||||
if shutting_down {
|
||||
CtrlAnime::run_thread(
|
||||
inner.clone(),
|
||||
inner.lock().await.cache.shutdown.clone(),
|
||||
true,
|
||||
)
|
||||
.await;
|
||||
} else {
|
||||
CtrlAnime::run_thread(
|
||||
inner.clone(),
|
||||
inner.lock().await.cache.boot.clone(),
|
||||
true,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
move |lid_closed| {
|
||||
let inner = inner3.clone();
|
||||
// on lid change
|
||||
async move {
|
||||
let AnimeConfig {
|
||||
off_when_lid_closed,
|
||||
builtin_anims_enabled,
|
||||
..
|
||||
} = inner.lock().await.config;
|
||||
if off_when_lid_closed {
|
||||
if builtin_anims_enabled {
|
||||
inner
|
||||
.lock()
|
||||
.await
|
||||
.node
|
||||
.write_bytes(&pkt_set_enable_powersave_anim(!lid_closed))
|
||||
.map_err(|err| {
|
||||
warn!("create_sys_event_tasks::off_when_suspended {}", err);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
inner
|
||||
.lock()
|
||||
.await
|
||||
.node
|
||||
.write_bytes(&pkt_set_enable_display(!lid_closed))
|
||||
.map_err(|err| {
|
||||
warn!("create_sys_event_tasks::off_when_lid_closed {}", err);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
},
|
||||
move |power_plugged| {
|
||||
let inner = inner4.clone();
|
||||
// on power change
|
||||
async move {
|
||||
let AnimeConfig {
|
||||
off_when_unplugged,
|
||||
builtin_anims_enabled,
|
||||
brightness_on_battery,
|
||||
..
|
||||
} = inner.lock().await.config;
|
||||
if off_when_unplugged {
|
||||
if builtin_anims_enabled {
|
||||
inner
|
||||
.lock()
|
||||
.await
|
||||
.node
|
||||
.write_bytes(&pkt_set_enable_powersave_anim(power_plugged))
|
||||
.map_err(|err| {
|
||||
warn!("create_sys_event_tasks::off_when_suspended {}", err);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
inner
|
||||
.lock()
|
||||
.await
|
||||
.node
|
||||
.write_bytes(&pkt_set_enable_display(power_plugged))
|
||||
.map_err(|err| {
|
||||
warn!("create_sys_event_tasks::off_when_unplugged {}", err);
|
||||
})
|
||||
.ok();
|
||||
} else {
|
||||
inner
|
||||
.lock()
|
||||
.await
|
||||
.node
|
||||
.write_bytes(&pkt_set_brightness(brightness_on_battery))
|
||||
.map_err(|err| {
|
||||
warn!("create_sys_event_tasks::off_when_unplugged {}", err);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::Reloadable for CtrlAnimeZbus {
|
||||
async fn reload(&mut self) -> Result<(), RogError> {
|
||||
if let Some(lock) = self.0.try_lock() {
|
||||
let anim = &lock.config.builtin_anims;
|
||||
// Set builtins
|
||||
if lock.config.builtin_anims_enabled {
|
||||
lock.node.write_bytes(&pkt_set_builtin_animations(
|
||||
anim.boot,
|
||||
anim.awake,
|
||||
anim.sleep,
|
||||
anim.shutdown,
|
||||
))?;
|
||||
}
|
||||
// Builtins enabled or na?
|
||||
lock.node.set_builtins_enabled(
|
||||
lock.config.builtin_anims_enabled,
|
||||
lock.config.display_brightness,
|
||||
)?;
|
||||
|
||||
let manager = get_logind_manager().await;
|
||||
let lid_closed = manager.lid_closed().await.unwrap_or_default();
|
||||
let power_plugged = manager.on_external_power().await.unwrap_or_default();
|
||||
|
||||
let turn_off = (lid_closed && lock.config.off_when_lid_closed)
|
||||
|| (!power_plugged && lock.config.off_when_unplugged);
|
||||
lock.node
|
||||
.write_bytes(&pkt_set_enable_display(!turn_off))
|
||||
.map_err(|err| {
|
||||
warn!("create_sys_event_tasks::reload {}", err);
|
||||
})
|
||||
.ok();
|
||||
|
||||
if turn_off || !lock.config.display_enabled {
|
||||
lock.node.write_bytes(&pkt_set_enable_display(false))?;
|
||||
// early return so we don't run animation thread
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if !lock.config.builtin_anims_enabled && !lock.cache.boot.is_empty() {
|
||||
lock.node
|
||||
.write_bytes(&pkt_set_enable_powersave_anim(false))
|
||||
.ok();
|
||||
|
||||
let action = lock.cache.boot.clone();
|
||||
CtrlAnime::run_thread(self.0.clone(), action, true).await;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
276
asusd/src/ctrl_aura/config.rs
Normal file
276
asusd/src/ctrl_aura/config.rs
Normal file
@@ -0,0 +1,276 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use config_traits::{StdConfig, StdConfigLoad};
|
||||
use log::{debug, info, warn};
|
||||
use rog_aura::aura_detection::LedSupportData;
|
||||
use rog_aura::keyboard::LaptopAuraPower;
|
||||
use rog_aura::{
|
||||
AuraDeviceType, AuraEffect, AuraModeNum, AuraZone, Direction, LedBrightness, Speed, GRADIENT,
|
||||
};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Deserialize, Serialize, Default, Debug, Clone)]
|
||||
// #[serde(default)]
|
||||
pub struct AuraConfig {
|
||||
pub config_name: String,
|
||||
pub brightness: LedBrightness,
|
||||
pub current_mode: AuraModeNum,
|
||||
pub builtins: BTreeMap<AuraModeNum, AuraEffect>,
|
||||
pub multizone: Option<BTreeMap<AuraModeNum, Vec<AuraEffect>>>,
|
||||
pub multizone_on: bool,
|
||||
pub enabled: LaptopAuraPower,
|
||||
}
|
||||
|
||||
impl StdConfig for AuraConfig {
|
||||
/// Detect the keyboard type and load from default DB if data available
|
||||
fn new() -> Self {
|
||||
panic!("This should not be used");
|
||||
}
|
||||
|
||||
fn file_name(&self) -> String {
|
||||
if self.config_name.is_empty() {
|
||||
panic!("Config file name should not be empty");
|
||||
}
|
||||
self.config_name.to_owned()
|
||||
}
|
||||
|
||||
fn config_dir() -> std::path::PathBuf {
|
||||
std::path::PathBuf::from(crate::CONFIG_PATH_BASE)
|
||||
}
|
||||
}
|
||||
|
||||
impl StdConfigLoad for AuraConfig {}
|
||||
|
||||
impl AuraConfig {
|
||||
/// Detect the keyboard type and load from default DB if data available
|
||||
pub fn new(prod_id: &str) -> Self {
|
||||
info!("Setting up AuraConfig for {prod_id:?}");
|
||||
// create a default config here
|
||||
let device_type = AuraDeviceType::from(prod_id);
|
||||
if device_type == AuraDeviceType::Unknown {
|
||||
warn!("idProduct:{prod_id:?} is unknown");
|
||||
}
|
||||
let support_data = LedSupportData::get_data(prod_id);
|
||||
let enabled = LaptopAuraPower::new(device_type, &support_data);
|
||||
let mut config = AuraConfig {
|
||||
config_name: format!("aura_{prod_id}.ron"),
|
||||
brightness: LedBrightness::Med,
|
||||
current_mode: AuraModeNum::Static,
|
||||
builtins: BTreeMap::new(),
|
||||
multizone: None,
|
||||
multizone_on: false,
|
||||
enabled,
|
||||
};
|
||||
|
||||
for n in &support_data.basic_modes {
|
||||
debug!("creating default for {n}");
|
||||
config
|
||||
.builtins
|
||||
.insert(*n, AuraEffect::default_with_mode(*n));
|
||||
|
||||
if !support_data.basic_zones.is_empty() {
|
||||
let mut default = vec![];
|
||||
for (i, tmp) in support_data.basic_zones.iter().enumerate() {
|
||||
default.push(AuraEffect {
|
||||
mode: *n,
|
||||
zone: *tmp,
|
||||
colour1: *GRADIENT.get(i).unwrap_or(&GRADIENT[0]),
|
||||
colour2: *GRADIENT.get(GRADIENT.len() - i).unwrap_or(&GRADIENT[6]),
|
||||
speed: Speed::Med,
|
||||
direction: Direction::Left,
|
||||
});
|
||||
}
|
||||
if let Some(m) = config.multizone.as_mut() {
|
||||
m.insert(*n, default);
|
||||
} else {
|
||||
let mut tmp = BTreeMap::new();
|
||||
tmp.insert(*n, default);
|
||||
config.multizone = Some(tmp);
|
||||
}
|
||||
}
|
||||
}
|
||||
config
|
||||
}
|
||||
|
||||
/// Set the mode data, current mode, and if multizone enabled.
|
||||
///
|
||||
/// Multipurpose, will accept `AuraEffect` with zones and put in the correct
|
||||
/// store.
|
||||
pub fn set_builtin(&mut self, effect: AuraEffect) {
|
||||
self.current_mode = effect.mode;
|
||||
if effect.zone() == AuraZone::None {
|
||||
self.builtins.insert(*effect.mode(), effect);
|
||||
self.multizone_on = false;
|
||||
} else {
|
||||
if let Some(multi) = self.multizone.as_mut() {
|
||||
if let Some(fx) = multi.get_mut(effect.mode()) {
|
||||
for fx in fx.iter_mut() {
|
||||
if fx.zone == effect.zone {
|
||||
*fx = effect;
|
||||
return;
|
||||
}
|
||||
}
|
||||
fx.push(effect);
|
||||
} else {
|
||||
multi.insert(*effect.mode(), vec![effect]);
|
||||
}
|
||||
} else {
|
||||
let mut tmp = BTreeMap::new();
|
||||
tmp.insert(*effect.mode(), vec![effect]);
|
||||
self.multizone = Some(tmp);
|
||||
}
|
||||
self.multizone_on = true;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_multizone(&self, aura_type: AuraModeNum) -> Option<&[AuraEffect]> {
|
||||
if let Some(multi) = &self.multizone {
|
||||
return multi.get(&aura_type).map(|v| v.as_slice());
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use rog_aura::{AuraEffect, AuraModeNum, AuraZone, Colour};
|
||||
|
||||
use super::AuraConfig;
|
||||
|
||||
#[test]
|
||||
fn set_multizone_4key_config() {
|
||||
let mut config = AuraConfig::new("19b6");
|
||||
|
||||
let effect = AuraEffect {
|
||||
colour1: Colour {
|
||||
r: 0xff,
|
||||
g: 0x00,
|
||||
b: 0xff,
|
||||
},
|
||||
zone: AuraZone::Key1,
|
||||
..Default::default()
|
||||
};
|
||||
config.set_builtin(effect);
|
||||
|
||||
assert!(config.multizone.is_some());
|
||||
|
||||
let effect = AuraEffect {
|
||||
colour1: Colour {
|
||||
r: 0x00,
|
||||
g: 0xff,
|
||||
b: 0xff,
|
||||
},
|
||||
zone: AuraZone::Key2,
|
||||
..Default::default()
|
||||
};
|
||||
config.set_builtin(effect);
|
||||
|
||||
let effect = AuraEffect {
|
||||
colour1: Colour {
|
||||
r: 0xff,
|
||||
g: 0xff,
|
||||
b: 0x00,
|
||||
},
|
||||
zone: AuraZone::Key3,
|
||||
..Default::default()
|
||||
};
|
||||
config.set_builtin(effect);
|
||||
|
||||
let effect = AuraEffect {
|
||||
colour1: Colour {
|
||||
r: 0x00,
|
||||
g: 0xff,
|
||||
b: 0x00,
|
||||
},
|
||||
zone: AuraZone::Key4,
|
||||
..Default::default()
|
||||
};
|
||||
let effect_clone = effect.clone();
|
||||
config.set_builtin(effect);
|
||||
// This should replace existing
|
||||
config.set_builtin(effect_clone);
|
||||
|
||||
let res = config.multizone.unwrap();
|
||||
let sta = res.get(&AuraModeNum::Static).unwrap();
|
||||
assert_eq!(sta.len(), 4);
|
||||
assert_eq!(
|
||||
sta[0].colour1,
|
||||
Colour {
|
||||
r: 0xff,
|
||||
g: 0x00,
|
||||
b: 0xff
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
sta[1].colour1,
|
||||
Colour {
|
||||
r: 0x00,
|
||||
g: 0xff,
|
||||
b: 0xff
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
sta[2].colour1,
|
||||
Colour {
|
||||
r: 0xff,
|
||||
g: 0xff,
|
||||
b: 0x00
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
sta[3].colour1,
|
||||
Colour {
|
||||
r: 0x00,
|
||||
g: 0xff,
|
||||
b: 0x00
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_multizone_multimode_config() {
|
||||
let mut config = AuraConfig::new("19b6");
|
||||
|
||||
let effect = AuraEffect {
|
||||
zone: AuraZone::Key1,
|
||||
..Default::default()
|
||||
};
|
||||
config.set_builtin(effect);
|
||||
|
||||
assert!(config.multizone.is_some());
|
||||
|
||||
let effect = AuraEffect {
|
||||
zone: AuraZone::Key2,
|
||||
mode: AuraModeNum::Breathe,
|
||||
..Default::default()
|
||||
};
|
||||
config.set_builtin(effect);
|
||||
|
||||
let effect = AuraEffect {
|
||||
zone: AuraZone::Key3,
|
||||
mode: AuraModeNum::Comet,
|
||||
..Default::default()
|
||||
};
|
||||
config.set_builtin(effect);
|
||||
|
||||
let effect = AuraEffect {
|
||||
zone: AuraZone::Key4,
|
||||
mode: AuraModeNum::Pulse,
|
||||
..Default::default()
|
||||
};
|
||||
config.set_builtin(effect);
|
||||
|
||||
let res = config.multizone.unwrap();
|
||||
let sta = res.get(&AuraModeNum::Static).unwrap();
|
||||
assert_eq!(sta.len(), 1);
|
||||
|
||||
let sta = res.get(&AuraModeNum::Breathe).unwrap();
|
||||
assert_eq!(sta.len(), 1);
|
||||
|
||||
let sta = res.get(&AuraModeNum::Comet).unwrap();
|
||||
assert_eq!(sta.len(), 1);
|
||||
|
||||
let sta = res.get(&AuraModeNum::Pulse).unwrap();
|
||||
assert_eq!(sta.len(), 1);
|
||||
}
|
||||
}
|
||||
458
asusd/src/ctrl_aura/controller.rs
Normal file
458
asusd/src/ctrl_aura/controller.rs
Normal file
@@ -0,0 +1,458 @@
|
||||
use std::collections::{BTreeMap, HashSet};
|
||||
|
||||
use config_traits::{StdConfig, StdConfigLoad};
|
||||
use inotify::Inotify;
|
||||
use log::{debug, info, warn};
|
||||
use rog_aura::aura_detection::LedSupportData;
|
||||
use rog_aura::keyboard::{LedUsbPackets, UsbPackets};
|
||||
use rog_aura::usb::{LED_APPLY, LED_SET};
|
||||
use rog_aura::{
|
||||
AuraDeviceType, AuraEffect, Direction, LedBrightness, Speed, GRADIENT, LED_MSG_LEN,
|
||||
};
|
||||
use rog_platform::hid_raw::HidRaw;
|
||||
use rog_platform::keyboard_led::KeyboardLed;
|
||||
use zbus::zvariant::OwnedObjectPath;
|
||||
|
||||
use super::config::AuraConfig;
|
||||
use crate::ctrl_aura::manager::{dbus_path_for_dev, dbus_path_for_tuf};
|
||||
use crate::error::RogError;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum LEDNode {
|
||||
/// Brightness and/or TUF RGB controls
|
||||
KbdLed(KeyboardLed),
|
||||
/// Raw HID handle
|
||||
Rog(KeyboardLed, HidRaw),
|
||||
}
|
||||
|
||||
impl LEDNode {
|
||||
// TODO: move various methods upwards to this
|
||||
pub fn set_brightness(&self, value: u8) -> Result<(), RogError> {
|
||||
match self {
|
||||
LEDNode::KbdLed(k) => k.set_brightness(value)?,
|
||||
LEDNode::Rog(k, _) => k.set_brightness(value)?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_brightness(&self) -> Result<u8, RogError> {
|
||||
Ok(match self {
|
||||
LEDNode::KbdLed(k) => k.get_brightness()?,
|
||||
LEDNode::Rog(k, _) => k.get_brightness()?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn monitor_brightness(&self) -> Result<Inotify, RogError> {
|
||||
Ok(match self {
|
||||
LEDNode::KbdLed(k) => k.monitor_brightness()?,
|
||||
LEDNode::Rog(k, _) => k.monitor_brightness()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Individual controller for one Aura device
|
||||
pub struct CtrlKbdLed {
|
||||
pub led_type: AuraDeviceType,
|
||||
pub led_node: LEDNode,
|
||||
pub supported_data: LedSupportData, // TODO: is storing this really required?
|
||||
pub per_key_mode_active: bool,
|
||||
pub config: AuraConfig,
|
||||
pub dbus_path: OwnedObjectPath,
|
||||
}
|
||||
|
||||
impl CtrlKbdLed {
|
||||
pub fn find_all() -> Result<Vec<Self>, RogError> {
|
||||
info!("Searching for all Aura devices");
|
||||
let mut devices = Vec::new();
|
||||
let mut found = HashSet::new(); // track and ensure we use only one hidraw per prod_id
|
||||
|
||||
let mut enumerator = udev::Enumerator::new().map_err(|err| {
|
||||
warn!("{}", err);
|
||||
err
|
||||
})?;
|
||||
|
||||
enumerator.match_subsystem("hidraw").map_err(|err| {
|
||||
warn!("{}", err);
|
||||
err
|
||||
})?;
|
||||
|
||||
for end_point in enumerator.scan_devices()? {
|
||||
// usb_device gives us a product and vendor ID
|
||||
if let Some(usb_device) =
|
||||
end_point.parent_with_subsystem_devtype("usb", "usb_device")?
|
||||
{
|
||||
// The asus_wmi driver latches MCU that controls the USB endpoints
|
||||
if let Some(parent) = end_point.parent() {
|
||||
if let Some(driver) = parent.driver() {
|
||||
// There is a tree of devices added so filter by driver
|
||||
if driver != "asus" {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Device is something like 002, while its parent is the MCU
|
||||
// Think of it like the device is an endpoint of the USB device attached
|
||||
let mut prod_id = String::new();
|
||||
if let Some(usb_id) = usb_device.attribute_value("idProduct") {
|
||||
prod_id = usb_id.to_string_lossy().to_string();
|
||||
let aura_dev = AuraDeviceType::from(prod_id.as_str());
|
||||
if aura_dev == AuraDeviceType::Unknown || found.contains(&aura_dev) {
|
||||
log::debug!("Unknown or invalid device: {usb_id:?}, skipping");
|
||||
continue;
|
||||
}
|
||||
found.insert(aura_dev);
|
||||
}
|
||||
|
||||
let dev_node = if let Some(dev_node) = usb_device.devnode() {
|
||||
dev_node
|
||||
} else {
|
||||
debug!("Device has no devnode, skipping");
|
||||
continue;
|
||||
};
|
||||
info!("AuraControl found device at: {:?}", dev_node);
|
||||
let dbus_path = dbus_path_for_dev(&usb_device).unwrap_or_default();
|
||||
let dev = HidRaw::from_device(end_point)?;
|
||||
let mut dev = Self::from_hidraw(dev, dbus_path)?;
|
||||
dev.config = Self::init_config(&prod_id);
|
||||
devices.push(dev);
|
||||
}
|
||||
}
|
||||
|
||||
// Check for a TUF laptop LED. Assume there is only ever one.
|
||||
if let Ok(tuf_kbd) = KeyboardLed::new() {
|
||||
if tuf_kbd.has_kbd_rgb_mode() {
|
||||
info!("AuraControl found a TUF laptop keyboard");
|
||||
let ctrl = CtrlKbdLed {
|
||||
led_type: AuraDeviceType::LaptopTuf,
|
||||
led_node: LEDNode::KbdLed(tuf_kbd),
|
||||
supported_data: LedSupportData::get_data("tuf"),
|
||||
per_key_mode_active: false,
|
||||
config: Self::init_config("tuf"),
|
||||
dbus_path: dbus_path_for_tuf(),
|
||||
};
|
||||
devices.push(ctrl);
|
||||
}
|
||||
}
|
||||
|
||||
info!("Found {} Aura devices", devices.len());
|
||||
|
||||
Ok(devices)
|
||||
}
|
||||
|
||||
/// The generated data from this function has a default config. This config
|
||||
/// should be overwritten. The reason for the default config is because
|
||||
/// of async issues between this and udev/hidraw
|
||||
pub fn from_hidraw(device: HidRaw, dbus_path: OwnedObjectPath) -> Result<Self, RogError> {
|
||||
let rgb_led = KeyboardLed::new()?;
|
||||
let prod_id = AuraDeviceType::from(device.prod_id());
|
||||
if prod_id == AuraDeviceType::Unknown {
|
||||
log::error!("{} is AuraDevice::Unknown", device.prod_id());
|
||||
return Err(RogError::NoAuraNode);
|
||||
}
|
||||
|
||||
// New loads data from the DB also
|
||||
// let config = Self::init_config(prod_id, data);
|
||||
|
||||
let data = LedSupportData::get_data(device.prod_id());
|
||||
let ctrl = CtrlKbdLed {
|
||||
led_type: prod_id,
|
||||
led_node: LEDNode::Rog(rgb_led, device),
|
||||
supported_data: data.clone(),
|
||||
per_key_mode_active: false,
|
||||
config: AuraConfig::default(),
|
||||
dbus_path,
|
||||
};
|
||||
Ok(ctrl)
|
||||
}
|
||||
|
||||
pub fn init_config(prod_id: &str) -> AuraConfig {
|
||||
// New loads data from the DB also
|
||||
let mut config_init = AuraConfig::new(prod_id);
|
||||
// config_init.set_filename(prod_id);
|
||||
let mut config_loaded = config_init.clone().load();
|
||||
// update the initialised data with what we loaded from disk
|
||||
for mode in &mut config_init.builtins {
|
||||
// update init values from loaded values if they exist
|
||||
if let Some(loaded) = config_loaded.builtins.get(mode.0) {
|
||||
*mode.1 = loaded.clone();
|
||||
}
|
||||
}
|
||||
// Then replace just incase the initialised data contains new modes added
|
||||
config_loaded.builtins = config_init.builtins;
|
||||
|
||||
if let (Some(mut multizone_init), Some(multizone_loaded)) =
|
||||
(config_init.multizone, config_loaded.multizone.as_mut())
|
||||
{
|
||||
for mode in multizone_init.iter_mut() {
|
||||
// update init values from loaded values if they exist
|
||||
if let Some(loaded) = multizone_loaded.get(mode.0) {
|
||||
let mut new_set = Vec::new();
|
||||
let data = LedSupportData::get_data(prod_id);
|
||||
// only reuse a zone mode if the mode is supported
|
||||
for mode in loaded {
|
||||
if data.basic_modes.contains(&mode.mode) {
|
||||
new_set.push(mode.clone());
|
||||
}
|
||||
}
|
||||
*mode.1 = new_set;
|
||||
}
|
||||
}
|
||||
*multizone_loaded = multizone_init;
|
||||
}
|
||||
|
||||
config_loaded
|
||||
}
|
||||
|
||||
/// Set combination state for boot animation/sleep animation/all leds/keys
|
||||
/// leds/side leds LED active
|
||||
pub(super) fn set_power_states(&mut self) -> Result<(), RogError> {
|
||||
if let LEDNode::KbdLed(_platform) = &mut self.led_node {
|
||||
// TODO: tuf bool array
|
||||
// if let Some(pwr) =
|
||||
// AuraPowerConfig::to_tuf_bool_array(&self.config.enabled) {
|
||||
// let buf = [1, pwr[1] as u8, pwr[2] as u8, pwr[3] as u8,
|
||||
// pwr[4] as u8]; platform.set_kbd_rgb_state(&buf)?;
|
||||
// }
|
||||
} else if let LEDNode::Rog(_, hid_raw) = &self.led_node {
|
||||
let bytes = self.config.enabled.to_bytes(self.led_type);
|
||||
let message = [0x5d, 0xbd, 0x01, bytes[0], bytes[1], bytes[2], bytes[3]];
|
||||
|
||||
hid_raw.write_bytes(&message)?;
|
||||
hid_raw.write_bytes(&LED_SET)?;
|
||||
// Changes won't persist unless apply is set
|
||||
hid_raw.write_bytes(&LED_APPLY)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write an effect block. This is for per-key, but can be repurposed to
|
||||
/// write the raw factory mode packets - when doing this it is expected that
|
||||
/// only the first `Vec` (`effect[0]`) is valid.
|
||||
pub fn write_effect_block(&mut self, effect: &UsbPackets) -> Result<(), RogError> {
|
||||
if self.config.brightness == LedBrightness::Off {
|
||||
self.config.brightness = LedBrightness::Med;
|
||||
self.config.write();
|
||||
}
|
||||
|
||||
let pkt_type = effect[0][1];
|
||||
const PER_KEY_TYPE: u8 = 0xbc;
|
||||
|
||||
if pkt_type != PER_KEY_TYPE {
|
||||
self.per_key_mode_active = false;
|
||||
if let LEDNode::Rog(_, hid_raw) = &self.led_node {
|
||||
hid_raw.write_bytes(&effect[0])?;
|
||||
hid_raw.write_bytes(&LED_SET)?;
|
||||
// hid_raw.write_bytes(&LED_APPLY)?;
|
||||
}
|
||||
} else {
|
||||
if !self.per_key_mode_active {
|
||||
if let LEDNode::Rog(_, hid_raw) = &self.led_node {
|
||||
let init = LedUsbPackets::get_init_msg();
|
||||
hid_raw.write_bytes(&init)?;
|
||||
}
|
||||
self.per_key_mode_active = true;
|
||||
}
|
||||
if let LEDNode::Rog(_, hid_raw) = &self.led_node {
|
||||
for row in effect.iter() {
|
||||
hid_raw.write_bytes(row)?;
|
||||
}
|
||||
} else if let LEDNode::KbdLed(tuf) = &self.led_node {
|
||||
for row in effect.iter() {
|
||||
let r = row[9];
|
||||
let g = row[10];
|
||||
let b = row[11];
|
||||
tuf.set_kbd_rgb_mode(&[0, 0, r, g, b, 0])?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_mode(&mut self, mode: &AuraEffect) -> Result<(), RogError> {
|
||||
if let LEDNode::KbdLed(platform) = &self.led_node {
|
||||
let buf = [
|
||||
1,
|
||||
mode.mode as u8,
|
||||
mode.colour1.r,
|
||||
mode.colour1.g,
|
||||
mode.colour1.b,
|
||||
mode.speed as u8,
|
||||
];
|
||||
platform.set_kbd_rgb_mode(&buf)?;
|
||||
} else if let LEDNode::Rog(_, hid_raw) = &self.led_node {
|
||||
let bytes: [u8; LED_MSG_LEN] = mode.into();
|
||||
hid_raw.write_bytes(&bytes)?;
|
||||
hid_raw.write_bytes(&LED_SET)?;
|
||||
// Changes won't persist unless apply is set
|
||||
hid_raw.write_bytes(&LED_APPLY)?;
|
||||
} else {
|
||||
return Err(RogError::NoAuraKeyboard);
|
||||
}
|
||||
self.per_key_mode_active = false;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn write_current_config_mode(&mut self) -> Result<(), RogError> {
|
||||
if self.config.multizone_on {
|
||||
let mode = self.config.current_mode;
|
||||
let mut create = false;
|
||||
// There is no multizone config for this mode so create one here
|
||||
// using the colours of rainbow if it exists, or first available
|
||||
// mode, or random
|
||||
if self.config.multizone.is_none() {
|
||||
create = true;
|
||||
} else if let Some(multizones) = self.config.multizone.as_ref() {
|
||||
if !multizones.contains_key(&mode) {
|
||||
create = true;
|
||||
}
|
||||
}
|
||||
if create {
|
||||
info!("No user-set config for zone founding, attempting a default");
|
||||
self.create_multizone_default()?;
|
||||
}
|
||||
|
||||
if let Some(multizones) = self.config.multizone.as_mut() {
|
||||
if let Some(set) = multizones.get(&mode) {
|
||||
for mode in set.clone() {
|
||||
self.write_mode(&mode)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let mode = self.config.current_mode;
|
||||
if let Some(effect) = self.config.builtins.get(&mode).cloned() {
|
||||
self.write_mode(&effect)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create a default for the `current_mode` if multizone and no config
|
||||
/// exists.
|
||||
fn create_multizone_default(&mut self) -> Result<(), RogError> {
|
||||
let mut default = vec![];
|
||||
for (i, tmp) in self.supported_data.basic_zones.iter().enumerate() {
|
||||
default.push(AuraEffect {
|
||||
mode: self.config.current_mode,
|
||||
zone: *tmp,
|
||||
colour1: *GRADIENT.get(i).unwrap_or(&GRADIENT[0]),
|
||||
colour2: *GRADIENT.get(GRADIENT.len() - i).unwrap_or(&GRADIENT[6]),
|
||||
speed: Speed::Med,
|
||||
direction: Direction::Left,
|
||||
});
|
||||
}
|
||||
if default.is_empty() {
|
||||
return Err(RogError::AuraEffectNotSupported);
|
||||
}
|
||||
|
||||
if let Some(multizones) = self.config.multizone.as_mut() {
|
||||
multizones.insert(self.config.current_mode, default);
|
||||
} else {
|
||||
let mut tmp = BTreeMap::new();
|
||||
tmp.insert(self.config.current_mode, default);
|
||||
self.config.multizone = Some(tmp);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use rog_aura::aura_detection::LedSupportData;
|
||||
use rog_aura::{AuraDeviceType, AuraModeNum, AuraZone, PowerZones};
|
||||
use rog_platform::hid_raw::HidRaw;
|
||||
use rog_platform::keyboard_led::KeyboardLed;
|
||||
use zbus::zvariant::OwnedObjectPath;
|
||||
|
||||
use super::CtrlKbdLed;
|
||||
use crate::ctrl_aura::config::AuraConfig;
|
||||
use crate::ctrl_aura::controller::LEDNode;
|
||||
|
||||
#[test]
|
||||
#[ignore = "Unable to run in CI as the HIDRAW device is required"]
|
||||
fn create_multizone_if_no_config() {
|
||||
// Checking to ensure set_mode errors when unsupported modes are tried
|
||||
let config = AuraConfig::new("19b6");
|
||||
let supported_basic_modes = LedSupportData {
|
||||
device_name: String::new(),
|
||||
product_id: String::new(),
|
||||
layout_name: "ga401".to_owned(),
|
||||
basic_modes: vec![AuraModeNum::Static],
|
||||
basic_zones: vec![],
|
||||
advanced_type: rog_aura::keyboard::AdvancedAuraType::None,
|
||||
power_zones: vec![PowerZones::Keyboard, PowerZones::RearGlow],
|
||||
};
|
||||
let mut controller = CtrlKbdLed {
|
||||
led_type: AuraDeviceType::LaptopPost2021,
|
||||
led_node: LEDNode::Rog(KeyboardLed::default(), HidRaw::new("19b6").unwrap()),
|
||||
supported_data: supported_basic_modes,
|
||||
per_key_mode_active: false,
|
||||
config,
|
||||
dbus_path: OwnedObjectPath::default(),
|
||||
};
|
||||
|
||||
assert!(controller.config.multizone.is_none());
|
||||
assert!(controller.create_multizone_default().is_err());
|
||||
assert!(controller.config.multizone.is_none());
|
||||
|
||||
controller.supported_data.basic_zones.push(AuraZone::Key1);
|
||||
controller.supported_data.basic_zones.push(AuraZone::Key2);
|
||||
assert!(controller.create_multizone_default().is_ok());
|
||||
assert!(controller.config.multizone.is_some());
|
||||
|
||||
let m = controller.config.multizone.unwrap();
|
||||
assert!(m.contains_key(&AuraModeNum::Static));
|
||||
let e = m.get(&AuraModeNum::Static).unwrap();
|
||||
assert_eq!(e.len(), 2);
|
||||
assert_eq!(e[0].zone, AuraZone::Key1);
|
||||
assert_eq!(e[1].zone, AuraZone::Key2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "Unable to run in CI as the HIDRAW device is required"]
|
||||
// TODO: use sim device
|
||||
fn next_mode_create_multizone_if_no_config() {
|
||||
// Checking to ensure set_mode errors when unsupported modes are tried
|
||||
let config = AuraConfig::new("19b6");
|
||||
let supported_basic_modes = LedSupportData {
|
||||
device_name: String::new(),
|
||||
product_id: String::new(),
|
||||
layout_name: "ga401".to_owned(),
|
||||
basic_modes: vec![AuraModeNum::Static],
|
||||
basic_zones: vec![AuraZone::Key1, AuraZone::Key2],
|
||||
advanced_type: rog_aura::keyboard::AdvancedAuraType::None,
|
||||
power_zones: vec![PowerZones::Keyboard, PowerZones::RearGlow],
|
||||
};
|
||||
let mut controller = CtrlKbdLed {
|
||||
led_type: AuraDeviceType::LaptopPost2021,
|
||||
led_node: LEDNode::Rog(KeyboardLed::default(), HidRaw::new("19b6").unwrap()),
|
||||
supported_data: supported_basic_modes,
|
||||
per_key_mode_active: false,
|
||||
config,
|
||||
dbus_path: OwnedObjectPath::default(),
|
||||
};
|
||||
|
||||
assert!(controller.config.multizone.is_none());
|
||||
controller.config.multizone_on = true;
|
||||
// This is called in toggle_mode. It will error here because we have no
|
||||
// keyboard node in tests.
|
||||
assert_eq!(
|
||||
controller
|
||||
.write_current_config_mode()
|
||||
.unwrap_err()
|
||||
.to_string(),
|
||||
"No supported Aura keyboard"
|
||||
);
|
||||
assert!(controller.config.multizone.is_some());
|
||||
|
||||
let m = controller.config.multizone.unwrap();
|
||||
assert!(m.contains_key(&AuraModeNum::Static));
|
||||
let e = m.get(&AuraModeNum::Static).unwrap();
|
||||
assert_eq!(e.len(), 2);
|
||||
assert_eq!(e[0].zone, AuraZone::Key1);
|
||||
assert_eq!(e[1].zone, AuraZone::Key2);
|
||||
}
|
||||
}
|
||||
187
asusd/src/ctrl_aura/manager.rs
Normal file
187
asusd/src/ctrl_aura/manager.rs
Normal file
@@ -0,0 +1,187 @@
|
||||
// Plan:
|
||||
// - Manager has udev monitor on USB looking for ROG devices
|
||||
// - If a device is found, add it to watch
|
||||
// - Add it to Zbus server
|
||||
// - If udev sees device removed then remove the zbus path
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use log::{debug, error, info, warn};
|
||||
use mio::{Events, Interest, Poll, Token};
|
||||
use rog_aura::AuraDeviceType;
|
||||
use rog_platform::hid_raw::HidRaw;
|
||||
use tokio::task::spawn_blocking;
|
||||
use udev::{Device, MonitorBuilder};
|
||||
use zbus::object_server::SignalContext;
|
||||
use zbus::zvariant::{ObjectPath, OwnedObjectPath};
|
||||
use zbus::Connection;
|
||||
|
||||
use crate::ctrl_aura::controller::CtrlKbdLed;
|
||||
use crate::ctrl_aura::trait_impls::{CtrlAuraZbus, AURA_ZBUS_PATH};
|
||||
use crate::error::RogError;
|
||||
use crate::{CtrlTask, Reloadable};
|
||||
|
||||
pub struct AuraManager {
|
||||
_connection: Connection,
|
||||
}
|
||||
|
||||
impl AuraManager {
|
||||
pub async fn new(connection: Connection) -> Result<Self, RogError> {
|
||||
let conn_copy = connection.clone();
|
||||
let mut interfaces = HashSet::new();
|
||||
|
||||
// Do the initial keyboard detection:
|
||||
let all = CtrlKbdLed::find_all()?;
|
||||
for ctrl in all {
|
||||
let path = ctrl.dbus_path.clone();
|
||||
interfaces.insert(path.clone()); // ensure we record the initial stuff
|
||||
let sig_ctx = CtrlAuraZbus::signal_context(&connection)?;
|
||||
let sig_ctx2 = sig_ctx.clone();
|
||||
let zbus = CtrlAuraZbus::new(ctrl, sig_ctx);
|
||||
start_tasks(zbus, connection.clone(), sig_ctx2, path).await?;
|
||||
}
|
||||
|
||||
let manager = Self {
|
||||
_connection: connection,
|
||||
};
|
||||
|
||||
// detect all plugged in aura devices (eventually)
|
||||
// only USB devices are detected for here
|
||||
spawn_blocking(move || {
|
||||
let mut monitor = MonitorBuilder::new()?.match_subsystem("hidraw")?.listen()?;
|
||||
let mut poll = Poll::new()?;
|
||||
let mut events = Events::with_capacity(1024);
|
||||
poll.registry()
|
||||
.register(&mut monitor, Token(0), Interest::READABLE)?;
|
||||
|
||||
loop {
|
||||
if poll.poll(&mut events, None).is_err() {
|
||||
continue;
|
||||
}
|
||||
for event in monitor.iter() {
|
||||
let parent = if let Some(parent) =
|
||||
event.parent_with_subsystem_devtype("usb", "usb_device")?
|
||||
{
|
||||
parent
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let action = if let Some(action) = event.action() {
|
||||
action
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let id_product = if let Some(id_product) = parent.attribute_value("idProduct") {
|
||||
id_product.to_string_lossy()
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let path = if let Some(path) = dbus_path_for_dev(&parent) {
|
||||
path
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let aura_device = AuraDeviceType::from(&*id_product);
|
||||
if aura_device == AuraDeviceType::Unknown {
|
||||
warn!("idProduct:{id_product:?} is unknown, not using");
|
||||
continue;
|
||||
}
|
||||
|
||||
if action == "remove" {
|
||||
if interfaces.remove(&path) {
|
||||
info!("AuraManager removing: {path:?}");
|
||||
let conn_copy = conn_copy.clone();
|
||||
tokio::spawn(async move {
|
||||
let res = conn_copy
|
||||
.object_server()
|
||||
.remove::<CtrlAuraZbus, _>(&path)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!("Failed to remove {path:?}, {e:?}");
|
||||
e
|
||||
})?;
|
||||
info!("AuraManager removed: {path:?}, {res}");
|
||||
Ok::<(), RogError>(())
|
||||
});
|
||||
}
|
||||
} else if action == "add" {
|
||||
if interfaces.contains(&path) {
|
||||
debug!("Already a ctrl at {path:?}");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Need to check the driver is asus to prevent using hid_generic
|
||||
if let Some(p2) = event.parent() {
|
||||
if let Some(driver) = p2.driver() {
|
||||
// There is a tree of devices added so filter by driver
|
||||
if driver != "asus" {
|
||||
debug!("{id_product:?} driver was not asus, skipping");
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(dev_node) = event.devnode() {
|
||||
if let Ok(raw) = HidRaw::from_device(event.device())
|
||||
.map_err(|e| error!("device path error: {e:?}"))
|
||||
{
|
||||
if let Ok(mut ctrl) = CtrlKbdLed::from_hidraw(raw, path.clone()) {
|
||||
ctrl.config = CtrlKbdLed::init_config(&id_product);
|
||||
interfaces.insert(path.clone());
|
||||
info!("AuraManager starting device at: {dev_node:?}, {path:?}");
|
||||
let sig_ctx = CtrlAuraZbus::signal_context(&conn_copy)?;
|
||||
let zbus = CtrlAuraZbus::new(ctrl, sig_ctx);
|
||||
let sig_ctx = CtrlAuraZbus::signal_context(&conn_copy)?;
|
||||
let conn_copy = conn_copy.clone();
|
||||
tokio::spawn(async move {
|
||||
start_tasks(zbus, conn_copy.clone(), sig_ctx, path).await
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
// Required for return type on spawn
|
||||
#[allow(unreachable_code)]
|
||||
Ok::<(), RogError>(())
|
||||
});
|
||||
Ok(manager)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn dbus_path_for_dev(parent: &Device) -> Option<OwnedObjectPath> {
|
||||
if let Some(filename) = super::filename_partial(parent) {
|
||||
return Some(
|
||||
ObjectPath::from_str_unchecked(&format!("{AURA_ZBUS_PATH}/{filename}")).into(),
|
||||
);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub(crate) fn dbus_path_for_tuf() -> OwnedObjectPath {
|
||||
ObjectPath::from_str_unchecked(&format!("{AURA_ZBUS_PATH}/tuf")).into()
|
||||
}
|
||||
|
||||
async fn start_tasks(
|
||||
mut zbus: CtrlAuraZbus,
|
||||
connection: Connection,
|
||||
_signal_ctx: SignalContext<'static>,
|
||||
path: OwnedObjectPath,
|
||||
) -> Result<(), RogError> {
|
||||
// let task = zbus.clone();
|
||||
// let signal_ctx = signal_ctx.clone();
|
||||
zbus.reload()
|
||||
.await
|
||||
.unwrap_or_else(|err| warn!("Controller error: {}", err));
|
||||
connection.object_server().at(path, zbus).await.unwrap();
|
||||
// TODO: skip this until we keep handles to tasks so they can be killed
|
||||
// task.create_tasks(signal_ctx).await
|
||||
Ok(())
|
||||
}
|
||||
29
asusd/src/ctrl_aura/mod.rs
Normal file
29
asusd/src/ctrl_aura/mod.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use udev::Device;
|
||||
use zbus::zvariant::{ObjectPath, OwnedObjectPath};
|
||||
|
||||
pub mod config;
|
||||
pub mod controller;
|
||||
pub mod manager;
|
||||
/// Implements `CtrlTask`, `Reloadable`, `ZbusRun`
|
||||
pub mod trait_impls;
|
||||
|
||||
/// Returns only the Device details concatenated in a form usable for
|
||||
/// adding/appending to a filename
|
||||
pub(super) fn filename_partial(parent: &Device) -> Option<OwnedObjectPath> {
|
||||
if let Some(id_product) = parent.attribute_value("idProduct") {
|
||||
let id_product = id_product.to_string_lossy();
|
||||
let path = if let Some(devnum) = parent.attribute_value("devnum") {
|
||||
let devnum = devnum.to_string_lossy();
|
||||
if let Some(devpath) = parent.attribute_value("devpath") {
|
||||
let devpath = devpath.to_string_lossy();
|
||||
format!("{id_product}_{devnum}_{devpath}")
|
||||
} else {
|
||||
format!("{id_product}_{devnum}")
|
||||
}
|
||||
} else {
|
||||
format!("{id_product}")
|
||||
};
|
||||
return Some(ObjectPath::from_str_unchecked(&path).into());
|
||||
}
|
||||
None
|
||||
}
|
||||
295
asusd/src/ctrl_aura/trait_impls.rs
Normal file
295
asusd/src/ctrl_aura/trait_impls.rs
Normal file
@@ -0,0 +1,295 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use config_traits::StdConfig;
|
||||
use log::{debug, error, info, warn};
|
||||
use rog_aura::keyboard::{LaptopAuraPower, UsbPackets};
|
||||
use rog_aura::{AuraDeviceType, AuraEffect, AuraModeNum, AuraZone, LedBrightness, PowerZones};
|
||||
use zbus::export::futures_util::lock::{Mutex, MutexGuard};
|
||||
use zbus::export::futures_util::StreamExt;
|
||||
use zbus::fdo::Error as ZbErr;
|
||||
use zbus::{interface, SignalContext};
|
||||
|
||||
use super::controller::CtrlKbdLed;
|
||||
use crate::error::RogError;
|
||||
use crate::CtrlTask;
|
||||
|
||||
pub const AURA_ZBUS_NAME: &str = "Aura";
|
||||
pub const AURA_ZBUS_PATH: &str = "/org/asuslinux";
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CtrlAuraZbus(Arc<Mutex<CtrlKbdLed>>, SignalContext<'static>);
|
||||
|
||||
impl CtrlAuraZbus {
|
||||
pub fn new(controller: CtrlKbdLed, signal: SignalContext<'static>) -> Self {
|
||||
Self(Arc::new(Mutex::new(controller)), signal)
|
||||
}
|
||||
|
||||
fn update_config(lock: &mut CtrlKbdLed) -> Result<(), RogError> {
|
||||
let bright = lock.led_node.get_brightness()?;
|
||||
lock.config.read();
|
||||
lock.config.brightness = bright.into();
|
||||
lock.config.write();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// The main interface for changing, reading, or notfying
|
||||
///
|
||||
/// LED commands are split between Brightness, Modes, Per-Key
|
||||
#[interface(name = "org.asuslinux.Aura")]
|
||||
impl CtrlAuraZbus {
|
||||
/// Return the device type for this Aura keyboard
|
||||
#[zbus(property)]
|
||||
async fn device_type(&self) -> AuraDeviceType {
|
||||
let ctrl = self.0.lock().await;
|
||||
ctrl.led_type
|
||||
}
|
||||
|
||||
/// Return the current LED brightness
|
||||
#[zbus(property)]
|
||||
async fn brightness(&self) -> Result<LedBrightness, ZbErr> {
|
||||
let ctrl = self.0.lock().await;
|
||||
Ok(ctrl.led_node.get_brightness().map(|n| n.into())?)
|
||||
}
|
||||
|
||||
/// Set the keyboard brightness level (0-3)
|
||||
#[zbus(property)]
|
||||
async fn set_brightness(&mut self, brightness: LedBrightness) -> Result<(), ZbErr> {
|
||||
let ctrl = self.0.lock().await;
|
||||
Ok(ctrl.led_node.set_brightness(brightness.into())?)
|
||||
}
|
||||
|
||||
/// Total levels of brightness available
|
||||
#[zbus(property)]
|
||||
async fn supported_brightness(&self) -> Vec<LedBrightness> {
|
||||
vec![
|
||||
LedBrightness::Off,
|
||||
LedBrightness::Low,
|
||||
LedBrightness::Med,
|
||||
LedBrightness::High,
|
||||
]
|
||||
}
|
||||
|
||||
/// The total available modes
|
||||
#[zbus(property)]
|
||||
async fn supported_basic_modes(&self) -> Result<Vec<AuraModeNum>, ZbErr> {
|
||||
let ctrl = self.0.lock().await;
|
||||
Ok(ctrl.config.builtins.keys().cloned().collect())
|
||||
}
|
||||
|
||||
#[zbus(property)]
|
||||
async fn supported_basic_zones(&self) -> Result<Vec<AuraZone>, ZbErr> {
|
||||
let ctrl = self.0.lock().await;
|
||||
Ok(ctrl.supported_data.basic_zones.clone())
|
||||
}
|
||||
|
||||
#[zbus(property)]
|
||||
async fn supported_power_zones(&self) -> Result<Vec<PowerZones>, ZbErr> {
|
||||
let ctrl = self.0.lock().await;
|
||||
Ok(ctrl.supported_data.power_zones.clone())
|
||||
}
|
||||
|
||||
/// The current mode data
|
||||
#[zbus(property)]
|
||||
async fn led_mode(&self) -> Result<AuraModeNum, ZbErr> {
|
||||
let ctrl = self.0.lock().await;
|
||||
Ok(ctrl.config.current_mode)
|
||||
}
|
||||
|
||||
/// Set an Aura effect if the effect mode or zone is supported.
|
||||
///
|
||||
/// On success the aura config file is read to refresh cached values, then
|
||||
/// the effect is stored and config written to disk.
|
||||
#[zbus(property)]
|
||||
async fn set_led_mode(&mut self, num: AuraModeNum) -> Result<(), ZbErr> {
|
||||
let mut ctrl = self.0.lock().await;
|
||||
ctrl.config.current_mode = num;
|
||||
ctrl.write_current_config_mode()?;
|
||||
if ctrl.config.brightness == LedBrightness::Off {
|
||||
ctrl.config.brightness = LedBrightness::Med;
|
||||
}
|
||||
ctrl.led_node
|
||||
.set_brightness(ctrl.config.brightness.into())?;
|
||||
ctrl.config.write();
|
||||
|
||||
self.led_mode_data_invalidate(&self.1).await.ok();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The current mode data
|
||||
#[zbus(property)]
|
||||
async fn led_mode_data(&self) -> Result<AuraEffect, ZbErr> {
|
||||
let ctrl = self.0.lock().await;
|
||||
let mode = ctrl.config.current_mode;
|
||||
match ctrl.config.builtins.get(&mode) {
|
||||
Some(effect) => Ok(effect.clone()),
|
||||
None => Err(ZbErr::Failed("Could not get the current effect".into())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Set an Aura effect if the effect mode or zone is supported.
|
||||
///
|
||||
/// On success the aura config file is read to refresh cached values, then
|
||||
/// the effect is stored and config written to disk.
|
||||
#[zbus(property)]
|
||||
async fn set_led_mode_data(&mut self, effect: AuraEffect) -> Result<(), ZbErr> {
|
||||
let mut ctrl = self.0.lock().await;
|
||||
if !ctrl.supported_data.basic_modes.contains(&effect.mode)
|
||||
|| effect.zone != AuraZone::None
|
||||
&& !ctrl.supported_data.basic_zones.contains(&effect.zone)
|
||||
{
|
||||
return Err(ZbErr::NotSupported(format!(
|
||||
"The Aura effect is not supported: {effect:?}"
|
||||
)));
|
||||
}
|
||||
|
||||
ctrl.write_mode(&effect)?;
|
||||
if ctrl.config.brightness == LedBrightness::Off {
|
||||
ctrl.config.brightness = LedBrightness::Med;
|
||||
}
|
||||
ctrl.led_node
|
||||
.set_brightness(ctrl.config.brightness.into())?;
|
||||
ctrl.config.set_builtin(effect);
|
||||
ctrl.config.write();
|
||||
|
||||
self.led_mode_invalidate(&self.1).await.ok();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the data set for every mode available
|
||||
async fn all_mode_data(&self) -> BTreeMap<AuraModeNum, AuraEffect> {
|
||||
let ctrl = self.0.lock().await;
|
||||
ctrl.config.builtins.clone()
|
||||
}
|
||||
|
||||
// As property doesn't work for AuraPowerDev (complexity of serialization?)
|
||||
#[zbus(property)]
|
||||
async fn led_power(&self) -> LaptopAuraPower {
|
||||
let ctrl = self.0.lock().await;
|
||||
ctrl.config.enabled.clone()
|
||||
}
|
||||
|
||||
/// Set a variety of states, input is array of enum.
|
||||
/// `enabled` sets if the sent array should be disabled or enabled
|
||||
///
|
||||
/// For Modern ROG devices the "enabled" flag is ignored.
|
||||
#[zbus(property)]
|
||||
async fn set_led_power(&mut self, options: LaptopAuraPower) -> Result<(), ZbErr> {
|
||||
let mut ctrl = self.0.lock().await;
|
||||
for opt in options.states {
|
||||
let zone = opt.zone;
|
||||
for config in ctrl.config.enabled.states.iter_mut() {
|
||||
if config.zone == zone {
|
||||
*config = opt;
|
||||
}
|
||||
}
|
||||
}
|
||||
ctrl.config.write();
|
||||
Ok(ctrl.set_power_states().map_err(|e| {
|
||||
warn!("{}", e);
|
||||
e
|
||||
})?)
|
||||
}
|
||||
|
||||
/// On machine that have some form of either per-key keyboard or per-zone
|
||||
/// this can be used to write custom effects over dbus. The input is a
|
||||
/// nested `Vec<Vec<8>>` where `Vec<u8>` is a raw USB packet
|
||||
async fn direct_addressing_raw(&self, data: UsbPackets) -> Result<(), ZbErr> {
|
||||
let mut ctrl = self.0.lock().await;
|
||||
ctrl.write_effect_block(&data)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl CtrlTask for CtrlAuraZbus {
|
||||
fn zbus_path() -> &'static str {
|
||||
"/org/asuslinux"
|
||||
}
|
||||
|
||||
async fn create_tasks(&self, _: SignalContext<'static>) -> Result<(), RogError> {
|
||||
let load_save =
|
||||
|start: bool, mut lock: MutexGuard<'_, CtrlKbdLed>| -> Result<(), RogError> {
|
||||
// If waking up
|
||||
if !start {
|
||||
info!("CtrlKbdLedTask reloading brightness and modes");
|
||||
lock.led_node
|
||||
.set_brightness(lock.config.brightness.into())
|
||||
.map_err(|e| {
|
||||
error!("CtrlKbdLedTask: {e}");
|
||||
e
|
||||
})?;
|
||||
lock.write_current_config_mode().map_err(|e| {
|
||||
error!("CtrlKbdLedTask: {e}");
|
||||
e
|
||||
})?;
|
||||
} else if start {
|
||||
Self::update_config(&mut lock).map_err(|e| {
|
||||
error!("CtrlKbdLedTask: {e}");
|
||||
e
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
|
||||
let inner1 = self.0.clone();
|
||||
let inner3 = self.0.clone();
|
||||
self.create_sys_event_tasks(
|
||||
move |sleeping| {
|
||||
let inner1 = inner1.clone();
|
||||
async move {
|
||||
let lock = inner1.lock().await;
|
||||
load_save(sleeping, lock).unwrap(); // unwrap as we want to
|
||||
// bomb out of the task
|
||||
}
|
||||
},
|
||||
move |_shutting_down| {
|
||||
let inner3 = inner3.clone();
|
||||
async move {
|
||||
let lock = inner3.lock().await;
|
||||
load_save(false, lock).unwrap(); // unwrap as we want to
|
||||
// bomb out of the task
|
||||
}
|
||||
},
|
||||
move |_lid_closed| {
|
||||
// on lid change
|
||||
async move {}
|
||||
},
|
||||
move |_power_plugged| {
|
||||
// power change
|
||||
async move {}
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
let ctrl2 = self.0.clone();
|
||||
let ctrl = self.0.lock().await;
|
||||
let watch = ctrl.led_node.monitor_brightness()?;
|
||||
tokio::spawn(async move {
|
||||
let mut buffer = [0; 32];
|
||||
watch
|
||||
.into_event_stream(&mut buffer)
|
||||
.unwrap()
|
||||
.for_each(|_| async {
|
||||
if let Some(lock) = ctrl2.try_lock() {
|
||||
load_save(true, lock).unwrap(); // unwrap as we want to
|
||||
// bomb out of the task
|
||||
}
|
||||
})
|
||||
.await;
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::Reloadable for CtrlAuraZbus {
|
||||
async fn reload(&mut self) -> Result<(), RogError> {
|
||||
let mut ctrl = self.0.lock().await;
|
||||
debug!("reloading keyboard mode");
|
||||
ctrl.write_current_config_mode()?;
|
||||
debug!("reloading power states");
|
||||
ctrl.set_power_states().map_err(|err| warn!("{err}")).ok();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
286
asusd/src/ctrl_fancurves.rs
Normal file
286
asusd/src/ctrl_fancurves.rs
Normal file
@@ -0,0 +1,286 @@
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use config_traits::{StdConfig, StdConfigLoad};
|
||||
use futures_lite::StreamExt;
|
||||
use log::{debug, error, info, warn};
|
||||
use rog_platform::platform::{RogPlatform, ThrottlePolicy};
|
||||
use rog_profiles::error::ProfileError;
|
||||
use rog_profiles::fan_curve_set::CurveData;
|
||||
use rog_profiles::{find_fan_curve_node, FanCurvePU, FanCurveProfiles};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use tokio::sync::Mutex;
|
||||
use zbus::{interface, Connection, SignalContext};
|
||||
|
||||
use crate::error::RogError;
|
||||
use crate::{CtrlTask, CONFIG_PATH_BASE};
|
||||
|
||||
pub const FAN_CURVE_ZBUS_NAME: &str = "FanCurves";
|
||||
pub const FAN_CURVE_ZBUS_PATH: &str = "/org/asuslinux";
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Default)]
|
||||
pub struct FanCurveConfig {
|
||||
pub profiles: FanCurveProfiles,
|
||||
#[serde(skip)]
|
||||
pub current: u8,
|
||||
}
|
||||
|
||||
impl StdConfig for FanCurveConfig {
|
||||
/// Create a new config. The defaults are zeroed so the device must be read
|
||||
/// to get the actual device defaults.
|
||||
fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
fn file_name(&self) -> String {
|
||||
"fan_curves.ron".to_owned()
|
||||
}
|
||||
|
||||
fn config_dir() -> std::path::PathBuf {
|
||||
PathBuf::from(CONFIG_PATH_BASE)
|
||||
}
|
||||
}
|
||||
|
||||
impl StdConfigLoad for FanCurveConfig {}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CtrlFanCurveZbus {
|
||||
config: Arc<Mutex<FanCurveConfig>>,
|
||||
platform: RogPlatform,
|
||||
}
|
||||
|
||||
// Non-zbus-derive impl
|
||||
impl CtrlFanCurveZbus {
|
||||
pub fn new() -> Result<Self, RogError> {
|
||||
let platform = RogPlatform::new()?;
|
||||
if platform.has_throttle_thermal_policy() {
|
||||
info!("Device has profile control available");
|
||||
find_fan_curve_node()?;
|
||||
info!("Device has fan curves available");
|
||||
let mut config = FanCurveConfig::new().load();
|
||||
let mut fan_curves = FanCurveProfiles::default();
|
||||
|
||||
// Only do defaults if the config doesn't already exist\
|
||||
if config.profiles.balanced.is_empty() || !config.file_path().exists() {
|
||||
info!("Fetching default fan curves");
|
||||
|
||||
let current = platform.get_throttle_thermal_policy()?;
|
||||
for this in [
|
||||
ThrottlePolicy::Balanced,
|
||||
ThrottlePolicy::Performance,
|
||||
ThrottlePolicy::Quiet,
|
||||
] {
|
||||
// For each profile we need to switch to it before we
|
||||
// can read the existing values from hardware. The ACPI method used
|
||||
// for this is what limits us.
|
||||
platform.set_throttle_thermal_policy(this.into())?;
|
||||
let mut dev = find_fan_curve_node()?;
|
||||
fan_curves.set_active_curve_to_defaults(this, &mut dev)?;
|
||||
|
||||
info!("{this:?}:");
|
||||
for curve in fan_curves.get_fan_curves_for(this) {
|
||||
info!("{}", String::from(curve));
|
||||
}
|
||||
}
|
||||
platform.set_throttle_thermal_policy(current)?;
|
||||
config.profiles = fan_curves;
|
||||
config.write();
|
||||
} else {
|
||||
info!("Fan curves previously stored, loading...");
|
||||
config = config.load();
|
||||
}
|
||||
|
||||
return Ok(Self {
|
||||
config: Arc::new(Mutex::new(config)),
|
||||
platform,
|
||||
});
|
||||
}
|
||||
|
||||
Err(ProfileError::NotSupported.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[interface(name = "org.asuslinux.FanCurves")]
|
||||
impl CtrlFanCurveZbus {
|
||||
/// Set all fan curves for a profile to enabled status. Will also activate a
|
||||
/// fan curve if in the same profile mode
|
||||
async fn set_fan_curves_enabled(
|
||||
&mut self,
|
||||
profile: ThrottlePolicy,
|
||||
enabled: bool,
|
||||
) -> zbus::fdo::Result<()> {
|
||||
self.config
|
||||
.lock()
|
||||
.await
|
||||
.profiles
|
||||
.set_profile_curves_enabled(profile, enabled);
|
||||
self.config
|
||||
.lock()
|
||||
.await
|
||||
.profiles
|
||||
.write_profile_curve_to_platform(profile, &mut find_fan_curve_node()?)?;
|
||||
self.config.lock().await.write();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set a single fan curve for a profile to enabled status. Will also
|
||||
/// activate a fan curve if in the same profile mode
|
||||
async fn set_profile_fan_curve_enabled(
|
||||
&mut self,
|
||||
profile: ThrottlePolicy,
|
||||
fan: FanCurvePU,
|
||||
enabled: bool,
|
||||
) -> zbus::fdo::Result<()> {
|
||||
self.config
|
||||
.lock()
|
||||
.await
|
||||
.profiles
|
||||
.set_profile_fan_curve_enabled(profile, fan, enabled);
|
||||
self.config
|
||||
.lock()
|
||||
.await
|
||||
.profiles
|
||||
.write_profile_curve_to_platform(profile, &mut find_fan_curve_node()?)?;
|
||||
self.config.lock().await.write();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the fan-curve data for the currently active ThrottlePolicy
|
||||
async fn fan_curve_data(
|
||||
&mut self,
|
||||
profile: ThrottlePolicy,
|
||||
) -> zbus::fdo::Result<Vec<CurveData>> {
|
||||
let curve = self
|
||||
.config
|
||||
.lock()
|
||||
.await
|
||||
.profiles
|
||||
.get_fan_curves_for(profile)
|
||||
.to_vec();
|
||||
Ok(curve)
|
||||
}
|
||||
|
||||
/// Set the fan curve for the specified profile.
|
||||
/// Will also activate the fan curve if the user is in the same mode.
|
||||
async fn set_fan_curve(
|
||||
&mut self,
|
||||
profile: ThrottlePolicy,
|
||||
curve: CurveData,
|
||||
) -> zbus::fdo::Result<()> {
|
||||
self.config
|
||||
.lock()
|
||||
.await
|
||||
.profiles
|
||||
.save_fan_curve(curve, profile)?;
|
||||
let active: ThrottlePolicy = self.platform.get_throttle_thermal_policy()?.into();
|
||||
if active == profile {
|
||||
self.config
|
||||
.lock()
|
||||
.await
|
||||
.profiles
|
||||
.write_profile_curve_to_platform(profile, &mut find_fan_curve_node()?)?;
|
||||
}
|
||||
self.config.lock().await.write();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Reset the stored (self) and device curves to the defaults of the
|
||||
/// platform.
|
||||
///
|
||||
/// Each platform_profile has a different default and the default can be
|
||||
/// read only for the currently active profile.
|
||||
async fn set_curves_to_defaults(&mut self, profile: ThrottlePolicy) -> zbus::fdo::Result<()> {
|
||||
let active = self.platform.get_throttle_thermal_policy()?;
|
||||
self.platform.set_throttle_thermal_policy(profile.into())?;
|
||||
self.config
|
||||
.lock()
|
||||
.await
|
||||
.profiles
|
||||
.set_active_curve_to_defaults(profile, &mut find_fan_curve_node()?)?;
|
||||
self.platform.set_throttle_thermal_policy(active)?;
|
||||
self.config.lock().await.write();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Reset the stored (self) and device curve to the defaults of the
|
||||
/// platform.
|
||||
///
|
||||
/// Each platform_profile has a different default and the defualt can be
|
||||
/// read only for the currently active profile.
|
||||
async fn reset_profile_curves(&self, profile: ThrottlePolicy) -> zbus::fdo::Result<()> {
|
||||
let active = self.platform.get_throttle_thermal_policy()?;
|
||||
|
||||
self.platform.set_throttle_thermal_policy(profile.into())?;
|
||||
self.config
|
||||
.lock()
|
||||
.await
|
||||
.profiles
|
||||
.set_active_curve_to_defaults(active.into(), &mut find_fan_curve_node()?)?;
|
||||
self.platform.set_throttle_thermal_policy(active)?;
|
||||
|
||||
self.config.lock().await.write();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::ZbusRun for CtrlFanCurveZbus {
|
||||
async fn add_to_server(self, server: &mut Connection) {
|
||||
Self::add_to_server_helper(self, FAN_CURVE_ZBUS_PATH, server).await;
|
||||
}
|
||||
}
|
||||
|
||||
impl CtrlTask for CtrlFanCurveZbus {
|
||||
fn zbus_path() -> &'static str {
|
||||
FAN_CURVE_ZBUS_PATH
|
||||
}
|
||||
|
||||
async fn create_tasks(&self, _signal_ctxt: SignalContext<'static>) -> Result<(), RogError> {
|
||||
let watch_throttle_thermal_policy = self.platform.monitor_throttle_thermal_policy()?;
|
||||
let platform = self.platform.clone();
|
||||
let config = self.config.clone();
|
||||
let fan_curves = self.config.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut buffer = [0; 32];
|
||||
if let Ok(mut stream) = watch_throttle_thermal_policy.into_event_stream(&mut buffer) {
|
||||
while (stream.next().await).is_some() {
|
||||
debug!("watch_throttle_thermal_policy changed");
|
||||
if let Ok(profile) = platform.get_throttle_thermal_policy().map_err(|e| {
|
||||
error!("get_throttle_thermal_policy error: {e}");
|
||||
}) {
|
||||
if profile != config.lock().await.current {
|
||||
fan_curves
|
||||
.lock()
|
||||
.await
|
||||
.profiles
|
||||
.write_profile_curve_to_platform(
|
||||
profile.into(),
|
||||
&mut find_fan_curve_node().unwrap(),
|
||||
)
|
||||
.map_err(|e| warn!("write_profile_curve_to_platform, {}", e))
|
||||
.ok();
|
||||
config.lock().await.current = profile;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::Reloadable for CtrlFanCurveZbus {
|
||||
/// Fetch the active profile and use that to set all related components up
|
||||
async fn reload(&mut self) -> Result<(), RogError> {
|
||||
let active = self.platform.get_throttle_thermal_policy()?.into();
|
||||
let mut config = self.config.lock().await;
|
||||
if let Ok(mut device) = find_fan_curve_node() {
|
||||
config
|
||||
.profiles
|
||||
.write_profile_curve_to_platform(active, &mut device)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
1021
asusd/src/ctrl_platform.rs
Normal file
1021
asusd/src/ctrl_platform.rs
Normal file
File diff suppressed because it is too large
Load Diff
51
asusd/src/ctrl_slash/config.rs
Normal file
51
asusd/src/ctrl_slash/config.rs
Normal file
@@ -0,0 +1,51 @@
|
||||
use config_traits::{StdConfig, StdConfigLoad};
|
||||
use rog_slash::{DeviceState, SlashMode};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
const CONFIG_FILE: &str = "slash.ron";
|
||||
|
||||
/// Config for base system actions for the anime display
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
pub struct SlashConfig {
|
||||
pub slash_enabled: bool,
|
||||
pub slash_brightness: u8,
|
||||
pub slash_interval: u8,
|
||||
pub slash_mode: SlashMode,
|
||||
}
|
||||
|
||||
impl Default for SlashConfig {
|
||||
fn default() -> Self {
|
||||
SlashConfig {
|
||||
slash_enabled: true,
|
||||
slash_brightness: 255,
|
||||
slash_interval: 0,
|
||||
slash_mode: SlashMode::Bounce,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl StdConfig for SlashConfig {
|
||||
fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
fn file_name(&self) -> String {
|
||||
CONFIG_FILE.to_owned()
|
||||
}
|
||||
|
||||
fn config_dir() -> std::path::PathBuf {
|
||||
std::path::PathBuf::from(crate::CONFIG_PATH_BASE)
|
||||
}
|
||||
}
|
||||
|
||||
impl StdConfigLoad for SlashConfig {}
|
||||
|
||||
impl From<&SlashConfig> for DeviceState {
|
||||
fn from(config: &SlashConfig) -> Self {
|
||||
DeviceState {
|
||||
slash_enabled: config.slash_enabled,
|
||||
slash_brightness: config.slash_brightness,
|
||||
slash_interval: config.slash_interval,
|
||||
slash_mode: config.slash_mode,
|
||||
}
|
||||
}
|
||||
}
|
||||
106
asusd/src/ctrl_slash/mod.rs
Normal file
106
asusd/src/ctrl_slash/mod.rs
Normal file
@@ -0,0 +1,106 @@
|
||||
pub mod config;
|
||||
pub mod trait_impls;
|
||||
|
||||
use rog_platform::hid_raw::HidRaw;
|
||||
use rog_platform::usb_raw::USBRaw;
|
||||
use rog_slash::error::SlashError;
|
||||
use rog_slash::usb::{get_slash_type, pkt_set_mode, pkt_set_options, pkts_for_init};
|
||||
use rog_slash::{SlashMode, SlashType};
|
||||
|
||||
use crate::ctrl_slash::config::SlashConfig;
|
||||
use crate::error::RogError;
|
||||
|
||||
enum Node {
|
||||
Usb(USBRaw),
|
||||
Hid(HidRaw),
|
||||
}
|
||||
|
||||
impl Node {
|
||||
pub fn write_bytes(&self, message: &[u8]) -> Result<(), RogError> {
|
||||
// TODO: map and pass on errors
|
||||
match self {
|
||||
Node::Usb(u) => {
|
||||
u.write_bytes(message).ok();
|
||||
}
|
||||
Node::Hid(h) => {
|
||||
h.write_bytes(message).ok();
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CtrlSlash {
|
||||
// node: HidRaw,
|
||||
node: Node,
|
||||
config: SlashConfig,
|
||||
// slash_type: SlashType,
|
||||
// // set to force thread to exit
|
||||
// thread_exit: Arc<AtomicBool>,
|
||||
// // Set to false when the thread exits
|
||||
// thread_running: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl CtrlSlash {
|
||||
#[inline]
|
||||
pub fn new(config: SlashConfig) -> Result<CtrlSlash, RogError> {
|
||||
let slash_type = get_slash_type()?;
|
||||
if matches!(slash_type, SlashType::Unknown | SlashType::Unsupported) {
|
||||
return Err(RogError::Slash(SlashError::NoDevice));
|
||||
}
|
||||
|
||||
let usb = USBRaw::new(rog_slash::usb::PROD_ID).ok();
|
||||
let hid = HidRaw::new(rog_slash::usb::PROD_ID_STR).ok();
|
||||
let node = if usb.is_some() {
|
||||
unsafe { Node::Usb(usb.unwrap_unchecked()) }
|
||||
} else if hid.is_some() {
|
||||
unsafe { Node::Hid(hid.unwrap_unchecked()) }
|
||||
} else {
|
||||
return Err(RogError::NotSupported);
|
||||
};
|
||||
|
||||
let ctrl = CtrlSlash {
|
||||
node,
|
||||
config,
|
||||
// slash_type,
|
||||
// thread_exit: Arc::new(AtomicBool::new(false)),
|
||||
// thread_running: Arc::new(AtomicBool::new(false)),
|
||||
};
|
||||
ctrl.do_initialization()?;
|
||||
|
||||
Ok(ctrl)
|
||||
}
|
||||
|
||||
fn do_initialization(&self) -> Result<(), RogError> {
|
||||
let init_packets = pkts_for_init();
|
||||
self.node.write_bytes(&init_packets[0])?;
|
||||
self.node.write_bytes(&init_packets[1])?;
|
||||
|
||||
// Apply config upon initialization
|
||||
let option_packets = pkt_set_options(
|
||||
self.config.slash_enabled,
|
||||
self.config.slash_brightness,
|
||||
self.config.slash_interval,
|
||||
);
|
||||
self.node.write_bytes(&option_packets)?;
|
||||
|
||||
let mode_packets = pkt_set_mode(self.config.slash_mode);
|
||||
self.node.write_bytes(&mode_packets[0])?;
|
||||
self.node.write_bytes(&mode_packets[1])?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_options(&self, enabled: bool, brightness: u8, interval: u8) -> Result<(), RogError> {
|
||||
let command_packets = pkt_set_options(enabled, brightness, interval);
|
||||
self.node.write_bytes(&command_packets)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_slash_mode(&self, slash_mode: SlashMode) -> Result<(), RogError> {
|
||||
let command_packets = pkt_set_mode(slash_mode);
|
||||
self.node.write_bytes(&command_packets[0])?;
|
||||
self.node.write_bytes(&command_packets[1])?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
161
asusd/src/ctrl_slash/trait_impls.rs
Normal file
161
asusd/src/ctrl_slash/trait_impls.rs
Normal file
@@ -0,0 +1,161 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use config_traits::StdConfig;
|
||||
use log::warn;
|
||||
use rog_slash::usb::{pkt_set_mode, pkt_set_options};
|
||||
use rog_slash::{DeviceState, SlashMode};
|
||||
use zbus::export::futures_util::lock::Mutex;
|
||||
use zbus::{interface, Connection, SignalContext};
|
||||
|
||||
use crate::ctrl_slash::CtrlSlash;
|
||||
use crate::error::RogError;
|
||||
|
||||
pub const SLASH_ZBUS_NAME: &str = "Slash";
|
||||
pub const SLASH_ZBUS_PATH: &str = "/org/asuslinux";
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CtrlSlashZbus(pub Arc<Mutex<CtrlSlash>>);
|
||||
|
||||
/// The struct with the main dbus methods requires this trait
|
||||
impl crate::ZbusRun for CtrlSlashZbus {
|
||||
async fn add_to_server(self, server: &mut Connection) {
|
||||
Self::add_to_server_helper(self, SLASH_ZBUS_PATH, server).await;
|
||||
}
|
||||
}
|
||||
|
||||
#[interface(name = "org.asuslinux.Slash")]
|
||||
impl CtrlSlashZbus {
|
||||
/// Get enabled or not
|
||||
#[zbus(property)]
|
||||
async fn enabled(&self) -> bool {
|
||||
let lock = self.0.lock().await;
|
||||
lock.config.slash_enabled
|
||||
}
|
||||
|
||||
/// Set enabled true or false
|
||||
async fn set_enabled(&self, enabled: bool) {
|
||||
let mut lock = self.0.lock().await;
|
||||
let brightness = if enabled && lock.config.slash_brightness == 0 {
|
||||
0x88
|
||||
} else {
|
||||
lock.config.slash_brightness
|
||||
};
|
||||
lock.node
|
||||
.write_bytes(&pkt_set_options(
|
||||
enabled,
|
||||
brightness,
|
||||
lock.config.slash_interval,
|
||||
))
|
||||
.map_err(|err| {
|
||||
warn!("ctrl_slash::set_options {}", err);
|
||||
})
|
||||
.ok();
|
||||
|
||||
lock.config.slash_enabled = enabled;
|
||||
lock.config.slash_brightness = brightness;
|
||||
lock.config.write();
|
||||
}
|
||||
|
||||
/// Get brightness level
|
||||
#[zbus(property)]
|
||||
async fn brightness(&self) -> u8 {
|
||||
let lock = self.0.lock().await;
|
||||
lock.config.slash_brightness
|
||||
}
|
||||
|
||||
/// Set brightness level
|
||||
async fn set_brightness(&self, brightness: u8) {
|
||||
let mut lock = self.0.lock().await;
|
||||
let enabled = brightness > 0;
|
||||
lock.node
|
||||
.write_bytes(&pkt_set_options(
|
||||
enabled,
|
||||
brightness,
|
||||
lock.config.slash_interval,
|
||||
))
|
||||
.map_err(|err| {
|
||||
warn!("ctrl_slash::set_options {}", err);
|
||||
})
|
||||
.ok();
|
||||
|
||||
lock.config.slash_enabled = enabled;
|
||||
lock.config.slash_brightness = brightness;
|
||||
lock.config.write();
|
||||
}
|
||||
|
||||
#[zbus(property)]
|
||||
async fn interval(&self) -> u8 {
|
||||
let lock = self.0.lock().await;
|
||||
lock.config.slash_interval
|
||||
}
|
||||
|
||||
/// Set interval between slash animations (0-255)
|
||||
async fn set_interval(&self, interval: u8) {
|
||||
let mut lock = self.0.lock().await;
|
||||
lock.node
|
||||
.write_bytes(&pkt_set_options(
|
||||
lock.config.slash_enabled,
|
||||
lock.config.slash_brightness,
|
||||
interval,
|
||||
))
|
||||
.map_err(|err| {
|
||||
warn!("ctrl_slash::set_options {}", err);
|
||||
})
|
||||
.ok();
|
||||
|
||||
lock.config.slash_interval = interval;
|
||||
lock.config.write();
|
||||
}
|
||||
|
||||
#[zbus(property)]
|
||||
async fn slash_mode(&self) -> u8 {
|
||||
let lock = self.0.lock().await;
|
||||
lock.config.slash_interval
|
||||
}
|
||||
|
||||
/// Set interval between slash animations (0-255)
|
||||
async fn set_slash_mode(&self, slash_mode: SlashMode) {
|
||||
let mut lock = self.0.lock().await;
|
||||
|
||||
let command_packets = pkt_set_mode(slash_mode);
|
||||
|
||||
lock.node
|
||||
.write_bytes(&command_packets[0])
|
||||
.map_err(|err| {
|
||||
warn!("ctrl_slash::set_options {}", err);
|
||||
})
|
||||
.ok();
|
||||
lock.node
|
||||
.write_bytes(&command_packets[1])
|
||||
.map_err(|err| {
|
||||
warn!("ctrl_slash::set_options {}", err);
|
||||
})
|
||||
.ok();
|
||||
|
||||
lock.config.slash_mode = slash_mode;
|
||||
lock.config.write();
|
||||
}
|
||||
|
||||
/// Get the device state as stored by asusd
|
||||
// #[zbus(property)]
|
||||
async fn device_state(&self) -> DeviceState {
|
||||
let lock = self.0.lock().await;
|
||||
DeviceState::from(&lock.config)
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::CtrlTask for CtrlSlashZbus {
|
||||
fn zbus_path() -> &'static str {
|
||||
SLASH_ZBUS_PATH
|
||||
}
|
||||
|
||||
async fn create_tasks(&self, _: SignalContext<'static>) -> Result<(), RogError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::Reloadable for CtrlSlashZbus {
|
||||
async fn reload(&mut self) -> Result<(), RogError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
136
asusd/src/daemon.rs
Normal file
136
asusd/src/daemon.rs
Normal file
@@ -0,0 +1,136 @@
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
use std::sync::Arc;
|
||||
|
||||
use ::zbus::export::futures_util::lock::Mutex;
|
||||
use ::zbus::Connection;
|
||||
use asusd::config::Config;
|
||||
use asusd::ctrl_anime::config::AnimeConfig;
|
||||
use asusd::ctrl_anime::trait_impls::CtrlAnimeZbus;
|
||||
use asusd::ctrl_anime::CtrlAnime;
|
||||
use asusd::ctrl_aura::manager::AuraManager;
|
||||
use asusd::ctrl_fancurves::CtrlFanCurveZbus;
|
||||
use asusd::ctrl_platform::CtrlPlatform;
|
||||
use asusd::ctrl_slash::config::SlashConfig;
|
||||
use asusd::ctrl_slash::trait_impls::CtrlSlashZbus;
|
||||
use asusd::ctrl_slash::CtrlSlash;
|
||||
use asusd::{print_board_info, start_tasks, CtrlTask, DBUS_NAME};
|
||||
use config_traits::{StdConfig, StdConfigLoad, StdConfigLoad2, StdConfigLoad3};
|
||||
use log::{error, info};
|
||||
use zbus::fdo::ObjectManager;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// console_subscriber::init();
|
||||
let mut logger = env_logger::Builder::new();
|
||||
logger
|
||||
.parse_default_env()
|
||||
.target(env_logger::Target::Stdout)
|
||||
.format_timestamp(None)
|
||||
.init();
|
||||
|
||||
let is_service = match env::var_os("IS_SERVICE") {
|
||||
Some(val) => val == "1",
|
||||
None => false,
|
||||
};
|
||||
|
||||
if !is_service {
|
||||
println!("asusd schould be only run from the right systemd service");
|
||||
println!(
|
||||
"do not run in your terminal, if you need an logs please use journalctl -b -u asusd"
|
||||
);
|
||||
println!("asusd will now exit");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
info!(" daemon v{}", asusd::VERSION);
|
||||
info!(" rog-anime v{}", rog_anime::VERSION);
|
||||
info!(" rog-slash v{}", rog_slash::VERSION);
|
||||
info!(" rog-aura v{}", rog_aura::VERSION);
|
||||
info!(" rog-profiles v{}", rog_profiles::VERSION);
|
||||
info!("rog-platform v{}", rog_platform::VERSION);
|
||||
|
||||
start_daemon().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The actual main loop for the daemon
|
||||
async fn start_daemon() -> Result<(), Box<dyn Error>> {
|
||||
// let supported = SupportedFunctions::get_supported();
|
||||
print_board_info();
|
||||
// println!("{:?}", supported.supported_functions());
|
||||
|
||||
// Start zbus server
|
||||
let mut connection = Connection::system().await?;
|
||||
connection
|
||||
.object_server()
|
||||
.at("/org/asuslinux", ObjectManager)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let config = Config::new().load();
|
||||
let cfg_path = config.file_path();
|
||||
let config = Arc::new(Mutex::new(config));
|
||||
|
||||
// supported.add_to_server(&mut connection).await;
|
||||
|
||||
match CtrlFanCurveZbus::new() {
|
||||
Ok(ctrl) => {
|
||||
let sig_ctx = CtrlFanCurveZbus::signal_context(&connection)?;
|
||||
start_tasks(ctrl, &mut connection, sig_ctx).await?;
|
||||
}
|
||||
Err(err) => {
|
||||
error!("FanCurves: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
match CtrlPlatform::new(
|
||||
config.clone(),
|
||||
&cfg_path,
|
||||
CtrlPlatform::signal_context(&connection)?,
|
||||
) {
|
||||
Ok(ctrl) => {
|
||||
let sig_ctx = CtrlPlatform::signal_context(&connection)?;
|
||||
start_tasks(ctrl, &mut connection, sig_ctx).await?;
|
||||
}
|
||||
Err(err) => {
|
||||
error!("CtrlPlatform: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
match CtrlAnime::new(AnimeConfig::new().load()) {
|
||||
Ok(ctrl) => {
|
||||
let zbus = CtrlAnimeZbus(Arc::new(Mutex::new(ctrl)));
|
||||
let sig_ctx = CtrlAnimeZbus::signal_context(&connection)?;
|
||||
start_tasks(zbus, &mut connection, sig_ctx).await?;
|
||||
}
|
||||
Err(err) => {
|
||||
info!("AniMe control: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
match CtrlSlash::new(SlashConfig::new().load()) {
|
||||
Ok(ctrl) => {
|
||||
let zbus = CtrlSlashZbus(Arc::new(Mutex::new(ctrl)));
|
||||
// Currently, the Slash has no need for a loop watching power events, however,
|
||||
// it could be cool to have the slash do some power-on/off animation
|
||||
// (It has a built-in power on animation which plays when u plug in the power
|
||||
// supply)
|
||||
let sig_ctx = CtrlSlashZbus::signal_context(&connection)?;
|
||||
start_tasks(zbus, &mut connection, sig_ctx).await?;
|
||||
}
|
||||
Err(err) => {
|
||||
info!("AniMe control: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
let _ = AuraManager::new(connection.clone()).await?;
|
||||
|
||||
// Request dbus name after finishing initalizing all functions
|
||||
connection.request_name(DBUS_NAME).await?;
|
||||
|
||||
loop {
|
||||
// This is just a blocker to idle and ensure the reator reacts
|
||||
connection.executor().tick().await;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
use rog_fan_curve::CurveError;
|
||||
use rog_profiles::error::ProfileError;
|
||||
use rog_types::error::GraphicsError;
|
||||
use std::convert::From;
|
||||
use std::fmt;
|
||||
|
||||
use crate::ctrl_gfx::error::GfxError;
|
||||
use config_traits::ron;
|
||||
use rog_anime::error::AnimeError;
|
||||
use rog_platform::error::PlatformError;
|
||||
use rog_profiles::error::ProfileError;
|
||||
use rog_slash::error::SlashError;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum RogError {
|
||||
ParseFanLevel,
|
||||
ParseVendor,
|
||||
ParseLed,
|
||||
MissingProfile(String),
|
||||
@@ -18,25 +18,32 @@ pub enum RogError {
|
||||
Write(String, std::io::Error),
|
||||
NotSupported,
|
||||
NotFound(String),
|
||||
FanCurve(CurveError),
|
||||
DoTask(String),
|
||||
MissingFunction(String),
|
||||
MissingLedBrightNode(String, std::io::Error),
|
||||
ReloadFail(String),
|
||||
GfxSwitching(GfxError),
|
||||
Profiles(ProfileError),
|
||||
Initramfs(String),
|
||||
Modprobe(String),
|
||||
Command(String, std::io::Error),
|
||||
Io(std::io::Error),
|
||||
Zbus(zbus::Error),
|
||||
ChargeLimit(u8),
|
||||
AuraEffectNotSupported,
|
||||
NoAuraKeyboard,
|
||||
NoAuraNode,
|
||||
Anime(AnimeError),
|
||||
Slash(SlashError),
|
||||
Platform(PlatformError),
|
||||
SystemdUnitAction(String),
|
||||
SystemdUnitWaitTimeout(String),
|
||||
Command(String, std::io::Error),
|
||||
ParseRon(ron::Error),
|
||||
}
|
||||
|
||||
impl fmt::Display for RogError {
|
||||
// This trait requires `fmt` with this exact signature.
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
RogError::ParseFanLevel => write!(f, "Parse profile error"),
|
||||
RogError::ParseVendor => write!(f, "Parse gfx vendor error"),
|
||||
RogError::ParseLed => write!(f, "Parse LED error"),
|
||||
RogError::MissingProfile(profile) => write!(f, "Profile does not exist {}", profile),
|
||||
@@ -46,45 +53,71 @@ impl fmt::Display for RogError {
|
||||
RogError::Write(path, error) => write!(f, "Write {}: {}", path, error),
|
||||
RogError::NotSupported => write!(f, "Not supported"),
|
||||
RogError::NotFound(deets) => write!(f, "Not found: {}", deets),
|
||||
RogError::FanCurve(err) => write!(f, "Custom fan-curve error: {}", err),
|
||||
RogError::DoTask(deets) => write!(f, "Task error: {}", deets),
|
||||
RogError::MissingFunction(deets) => write!(f, "Missing functionality: {}", deets),
|
||||
RogError::MissingLedBrightNode(path, error) => write!(f, "Led node at {} is missing, please check you have the required patch or dkms module installed: {}", path, error),
|
||||
RogError::ReloadFail(deets) => write!(f, "Task error: {}", deets),
|
||||
RogError::GfxSwitching(deets) => write!(f, "Graphics switching error: {}", deets),
|
||||
RogError::MissingLedBrightNode(path, error) => write!(
|
||||
f,
|
||||
"Led node at {} is missing, please check you have the required patch or dkms \
|
||||
module installed: {}",
|
||||
path, error
|
||||
),
|
||||
RogError::ReloadFail(deets) => write!(f, "Reload error: {}", deets),
|
||||
RogError::Profiles(deets) => write!(f, "Profile error: {}", deets),
|
||||
RogError::Initramfs(detail) => write!(f, "Initiramfs error: {}", detail),
|
||||
RogError::Modprobe(detail) => write!(f, "Modprobe error: {}", detail),
|
||||
RogError::Command(func, error) => write!(f, "Command exec error: {}: {}", func, error),
|
||||
RogError::Io(detail) => write!(f, "std::io error: {}", detail),
|
||||
RogError::Zbus(detail) => write!(f, "Zbus error: {}", detail),
|
||||
RogError::ChargeLimit(value) => {
|
||||
write!(f, "Invalid charging limit, not in range 20-100%: {}", value)
|
||||
}
|
||||
RogError::AuraEffectNotSupported => write!(f, "Aura effect not supported"),
|
||||
RogError::NoAuraKeyboard => write!(f, "No supported Aura keyboard"),
|
||||
RogError::NoAuraNode => write!(f, "No Aura keyboard node found"),
|
||||
RogError::Anime(deets) => write!(f, "AniMe Matrix error: {}", deets),
|
||||
RogError::Slash(deets) => write!(f, "Slash error: {}", deets),
|
||||
RogError::Platform(deets) => write!(f, "Asus Platform error: {}", deets),
|
||||
RogError::SystemdUnitAction(action) => {
|
||||
write!(f, "systemd unit action {} failed", action)
|
||||
}
|
||||
RogError::SystemdUnitWaitTimeout(state) => {
|
||||
write!(
|
||||
f,
|
||||
"Timed out waiting for systemd unit change {} state",
|
||||
state
|
||||
)
|
||||
}
|
||||
RogError::Command(func, error) => write!(f, "Command exec error: {}: {}", func, error),
|
||||
RogError::ParseRon(error) => write!(f, "Parse config error: {}", error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for RogError {}
|
||||
|
||||
impl From<CurveError> for RogError {
|
||||
fn from(err: CurveError) -> Self {
|
||||
RogError::FanCurve(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<GraphicsError> for RogError {
|
||||
fn from(err: GraphicsError) -> Self {
|
||||
match err {
|
||||
GraphicsError::ParseVendor => RogError::GfxSwitching(GfxError::ParseVendor),
|
||||
GraphicsError::ParsePower => RogError::GfxSwitching(GfxError::ParsePower),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ProfileError> for RogError {
|
||||
fn from(err: ProfileError) -> Self {
|
||||
RogError::Profiles(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AnimeError> for RogError {
|
||||
fn from(err: AnimeError) -> Self {
|
||||
RogError::Anime(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SlashError> for RogError {
|
||||
fn from(err: SlashError) -> Self {
|
||||
RogError::Slash(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PlatformError> for RogError {
|
||||
fn from(err: PlatformError) -> Self {
|
||||
RogError::Platform(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<zbus::Error> for RogError {
|
||||
fn from(err: zbus::Error) -> Self {
|
||||
RogError::Zbus(err)
|
||||
@@ -97,6 +130,12 @@ impl From<std::io::Error> for RogError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ron::Error> for RogError {
|
||||
fn from(err: ron::Error) -> Self {
|
||||
RogError::ParseRon(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RogError> for zbus::fdo::Error {
|
||||
#[inline]
|
||||
fn from(err: RogError) -> Self {
|
||||
311
asusd/src/lib.rs
Normal file
311
asusd/src/lib.rs
Normal file
@@ -0,0 +1,311 @@
|
||||
#![deny(unused_must_use)]
|
||||
/// Configuration loading, saving
|
||||
pub mod config;
|
||||
/// Control of anime matrix display
|
||||
pub mod ctrl_anime;
|
||||
/// Keyboard LED brightness control, RGB, and LED display modes
|
||||
pub mod ctrl_aura;
|
||||
/// Control platform profiles + fan-curves if available
|
||||
pub mod ctrl_fancurves;
|
||||
/// Control ASUS bios function such as boot sound, Optimus/Dedicated gfx mode
|
||||
pub mod ctrl_platform;
|
||||
/// Control of Slash led bar
|
||||
pub mod ctrl_slash;
|
||||
|
||||
pub mod error;
|
||||
|
||||
use std::future::Future;
|
||||
use std::time::Duration;
|
||||
|
||||
use dmi_id::DMIID;
|
||||
use futures_lite::stream::StreamExt;
|
||||
use log::{debug, info, warn};
|
||||
use logind_zbus::manager::ManagerProxy;
|
||||
use tokio::time::sleep;
|
||||
use zbus::zvariant::ObjectPath;
|
||||
use zbus::{CacheProperties, Connection, SignalContext};
|
||||
|
||||
use crate::error::RogError;
|
||||
|
||||
const CONFIG_PATH_BASE: &str = "/etc/asusd/";
|
||||
pub static DBUS_NAME: &str = "org.asuslinux.Daemon";
|
||||
pub static DBUS_PATH: &str = "/org/asuslinux/Daemon";
|
||||
pub static DBUS_IFACE: &str = "org.asuslinux.Daemon";
|
||||
|
||||
/// This macro adds a function which spawns an `inotify` task on the passed in
|
||||
/// `Executor`.
|
||||
///
|
||||
/// The generated function is `watch_<name>()`. Self requires the following
|
||||
/// methods to be available:
|
||||
/// - `<name>() -> SomeValue`, functionally is a getter, but is allowed to have
|
||||
/// side effects.
|
||||
/// - `notify_<name>(SignalContext, SomeValue)`
|
||||
///
|
||||
/// In most cases if `SomeValue` is stored in a config then `<name>()` getter is
|
||||
/// expected to update it. The getter should *never* write back to the path or
|
||||
/// attribute that is being watched or an infinite loop will occur.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// impl RogPlatform {
|
||||
/// task_watch_item!(panel_od platform);
|
||||
/// task_watch_item!(gpu_mux_mode platform);
|
||||
/// }
|
||||
/// ```\
|
||||
/// // TODO: this is kind of useless if it can't trigger some action
|
||||
#[macro_export]
|
||||
macro_rules! task_watch_item {
|
||||
($name:ident $self_inner:ident) => {
|
||||
concat_idents::concat_idents!(fn_name = watch_, $name {
|
||||
async fn fn_name(
|
||||
&self,
|
||||
signal_ctxt: SignalContext<'static>,
|
||||
) -> Result<(), RogError> {
|
||||
use zbus::export::futures_util::StreamExt;
|
||||
|
||||
let ctrl = self.clone();
|
||||
concat_idents::concat_idents!(watch_fn = monitor_, $name {
|
||||
match self.$self_inner.watch_fn() {
|
||||
Ok(watch) => {
|
||||
tokio::spawn(async move {
|
||||
let mut buffer = [0; 32];
|
||||
watch.into_event_stream(&mut buffer).unwrap().for_each(|_| async {
|
||||
if let Ok(value) = ctrl.$name() { // get new value from zbus method
|
||||
concat_idents::concat_idents!(notif_fn = $name, _changed {
|
||||
ctrl.notif_fn(&signal_ctxt).await.ok();
|
||||
});
|
||||
let mut lock = ctrl.config.lock().await;
|
||||
lock.$name = value;
|
||||
lock.write();
|
||||
}
|
||||
}).await;
|
||||
});
|
||||
}
|
||||
Err(e) => info!("inotify watch failed: {}. You can ignore this if your device does not support the feature", e),
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! task_watch_item_notify {
|
||||
($name:ident $self_inner:ident) => {
|
||||
concat_idents::concat_idents!(fn_name = watch_, $name {
|
||||
async fn fn_name(
|
||||
&self,
|
||||
signal_ctxt: SignalContext<'static>,
|
||||
) -> Result<(), RogError> {
|
||||
use zbus::export::futures_util::StreamExt;
|
||||
|
||||
let ctrl = self.clone();
|
||||
concat_idents::concat_idents!(watch_fn = monitor_, $name {
|
||||
match self.$self_inner.watch_fn() {
|
||||
Ok(watch) => {
|
||||
tokio::spawn(async move {
|
||||
let mut buffer = [0; 32];
|
||||
watch.into_event_stream(&mut buffer).unwrap().for_each(|_| async {
|
||||
concat_idents::concat_idents!(notif_fn = $name, _changed {
|
||||
ctrl.notif_fn(&signal_ctxt).await.ok();
|
||||
});
|
||||
}).await;
|
||||
});
|
||||
}
|
||||
Err(e) => info!("inotify watch failed: {}. You can ignore this if your device does not support the feature", e),
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
pub fn print_board_info() {
|
||||
let dmi = DMIID::new().unwrap_or_default();
|
||||
info!("Product family: {}", dmi.product_family);
|
||||
info!("Board name: {}", dmi.board_name);
|
||||
}
|
||||
|
||||
pub trait Reloadable {
|
||||
fn reload(&mut self) -> impl Future<Output = Result<(), RogError>> + Send;
|
||||
}
|
||||
|
||||
pub trait ReloadAndNotify {
|
||||
type Data: Send;
|
||||
|
||||
fn reload_and_notify(
|
||||
&mut self,
|
||||
signal_context: &SignalContext<'static>,
|
||||
data: Self::Data,
|
||||
) -> impl Future<Output = Result<(), RogError>> + Send;
|
||||
}
|
||||
|
||||
pub trait ZbusRun {
|
||||
fn add_to_server(self, server: &mut Connection) -> impl Future<Output = ()> + Send;
|
||||
|
||||
fn add_to_server_helper(
|
||||
iface: impl zbus::Interface,
|
||||
path: &str,
|
||||
server: &mut Connection,
|
||||
) -> impl Future<Output = ()> + Send {
|
||||
async move {
|
||||
server
|
||||
.object_server()
|
||||
.at(&ObjectPath::from_str_unchecked(path), iface)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
warn!("{}: add_to_server {}", path, err);
|
||||
err
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Set up a task to run on the async executor
|
||||
pub trait CtrlTask {
|
||||
fn zbus_path() -> &'static str;
|
||||
|
||||
fn signal_context(connection: &Connection) -> Result<SignalContext<'static>, zbus::Error> {
|
||||
SignalContext::new(connection, Self::zbus_path())
|
||||
}
|
||||
|
||||
/// Implement to set up various tasks that may be required, using the
|
||||
/// `Executor`. No blocking loops are allowed, or they must be run on a
|
||||
/// separate thread.
|
||||
fn create_tasks(
|
||||
&self,
|
||||
signal: SignalContext<'static>,
|
||||
) -> impl Future<Output = Result<(), RogError>> + Send;
|
||||
|
||||
// /// Create a timed repeating task
|
||||
// async fn repeating_task(&self, millis: u64, mut task: impl FnMut() + Send +
|
||||
// 'static) { use std::time::Duration;
|
||||
// use tokio::time;
|
||||
// let mut timer = time::interval(Duration::from_millis(millis));
|
||||
// tokio::spawn(async move {
|
||||
// timer.tick().await;
|
||||
// task();
|
||||
// });
|
||||
// }
|
||||
|
||||
/// Free helper method to create tasks to run on: sleep, wake, shutdown,
|
||||
/// boot
|
||||
///
|
||||
/// The closures can potentially block, so execution time should be the
|
||||
/// minimal possible such as save a variable.
|
||||
fn create_sys_event_tasks<Fut1, Fut2, Fut3, Fut4, F1, F2, F3, F4>(
|
||||
&self,
|
||||
mut on_prepare_for_sleep: F1,
|
||||
mut on_prepare_for_shutdown: F2,
|
||||
mut on_lid_change: F3,
|
||||
mut on_external_power_change: F4,
|
||||
) -> impl Future<Output = ()> + Send
|
||||
where
|
||||
F1: FnMut(bool) -> Fut1 + Send + 'static,
|
||||
F2: FnMut(bool) -> Fut2 + Send + 'static,
|
||||
F3: FnMut(bool) -> Fut3 + Send + 'static,
|
||||
F4: FnMut(bool) -> Fut4 + Send + 'static,
|
||||
Fut1: Future<Output = ()> + Send,
|
||||
Fut2: Future<Output = ()> + Send,
|
||||
Fut3: Future<Output = ()> + Send,
|
||||
Fut4: Future<Output = ()> + Send,
|
||||
{
|
||||
async {
|
||||
let connection = Connection::system()
|
||||
.await
|
||||
.expect("Controller could not create dbus connection");
|
||||
|
||||
let manager = ManagerProxy::builder(&connection)
|
||||
.cache_properties(CacheProperties::No)
|
||||
.build()
|
||||
.await
|
||||
.expect("Controller could not create ManagerProxy");
|
||||
|
||||
let manager1 = manager.clone();
|
||||
tokio::spawn(async move {
|
||||
if let Ok(mut notif) = manager1.receive_prepare_for_shutdown().await {
|
||||
while let Some(event) = notif.next().await {
|
||||
// blocks thread :|
|
||||
if let Ok(args) = event.args() {
|
||||
debug!("Doing on_prepare_for_shutdown({})", args.start);
|
||||
on_prepare_for_shutdown(args.start).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let manager2 = manager.clone();
|
||||
tokio::spawn(async move {
|
||||
if let Ok(mut notif) = manager2.receive_prepare_for_sleep().await {
|
||||
while let Some(event) = notif.next().await {
|
||||
// blocks thread :|
|
||||
if let Ok(args) = event.args() {
|
||||
debug!("Doing on_prepare_for_sleep({})", args.start);
|
||||
on_prepare_for_sleep(args.start).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let manager3 = manager.clone();
|
||||
tokio::spawn(async move {
|
||||
let mut last_power = manager3.on_external_power().await.unwrap_or_default();
|
||||
|
||||
loop {
|
||||
if let Ok(next) = manager3.on_external_power().await {
|
||||
if next != last_power {
|
||||
last_power = next;
|
||||
on_external_power_change(next).await;
|
||||
}
|
||||
}
|
||||
sleep(Duration::from_secs(2)).await;
|
||||
}
|
||||
});
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut last_lid = manager.lid_closed().await.unwrap_or_default();
|
||||
// need to loop on these as they don't emit signals
|
||||
loop {
|
||||
if let Ok(next) = manager.lid_closed().await {
|
||||
if next != last_lid {
|
||||
last_lid = next;
|
||||
on_lid_change(next).await;
|
||||
}
|
||||
}
|
||||
sleep(Duration::from_secs(2)).await;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait GetSupported {
|
||||
type A;
|
||||
|
||||
fn get_supported() -> Self::A;
|
||||
}
|
||||
|
||||
pub async fn start_tasks<T>(
|
||||
mut zbus: T,
|
||||
connection: &mut Connection,
|
||||
signal_ctx: SignalContext<'static>,
|
||||
) -> Result<(), RogError>
|
||||
where
|
||||
T: ZbusRun + Reloadable + CtrlTask + Clone,
|
||||
{
|
||||
let zbus_clone = zbus.clone();
|
||||
|
||||
zbus.reload()
|
||||
.await
|
||||
.unwrap_or_else(|err| warn!("Controller error: {}", err));
|
||||
zbus.add_to_server(connection).await;
|
||||
|
||||
zbus_clone.create_tasks(signal_ctx).await.ok();
|
||||
Ok(())
|
||||
}
|
||||
18
config-traits/Cargo.toml
Normal file
18
config-traits/Cargo.toml
Normal file
@@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "config-traits"
|
||||
license = "MPL-2.0"
|
||||
authors = ["Luke D Jones <luke@ljones.dev>"]
|
||||
edition = "2021"
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
serde_json.workspace = true
|
||||
toml.workspace = true
|
||||
ron.workspace = true
|
||||
|
||||
log.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
cargo-husky.workspace = true
|
||||
10
config-traits/README.md
Normal file
10
config-traits/README.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# config-traits
|
||||
|
||||
`config_traits` is a crate that broke out from the requirement to manage various
|
||||
different config files, including parsing from different formats and updating
|
||||
them from previous versions where fields or names are changed in some way.
|
||||
|
||||
The end canonical file format is `.ron` as this supports rust types well, and includes
|
||||
the ability to add commenting, and is less verbose than `json`. Currently the crate will
|
||||
also try to parse from `json` and `toml` if the `ron` parsing fails, then update to `ron`
|
||||
format.
|
||||
343
config-traits/src/lib.rs
Normal file
343
config-traits/src/lib.rs
Normal file
@@ -0,0 +1,343 @@
|
||||
//! `config_traits` is a crate that broke out from the requirement to manage
|
||||
//! various different config files, including parsing from different formats and
|
||||
//! updating them from previous versions where fields or names are changed in
|
||||
//! some way.
|
||||
//!
|
||||
//! The end canonical file format is `.ron` as this supports rust types well,
|
||||
//! and includes the ability to add commenting, and is less verbose than `json`.
|
||||
//! Currently the crate will also try to parse from `json` and `toml` if the
|
||||
//! `ron` parsing fails, then update to `ron` format.
|
||||
|
||||
use std::fs::{self, create_dir, File, OpenOptions};
|
||||
use std::io::{Read, Write};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use log::{error, warn};
|
||||
pub use ron;
|
||||
use ron::ser::PrettyConfig;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
|
||||
/// Config file helper traits. Only `new()` and `file_name()` are required to be
|
||||
/// implemented, the rest are intended to be free methods.
|
||||
pub trait StdConfig
|
||||
where
|
||||
Self: Serialize + DeserializeOwned,
|
||||
{
|
||||
/// Taking over the standard `new()` to ensure things can be generic
|
||||
fn new() -> Self;
|
||||
|
||||
/// Return the config files names, such as `wibble.cfg`
|
||||
fn file_name(&self) -> String;
|
||||
|
||||
/// Return the full path to the directory the config file resides in
|
||||
fn config_dir() -> PathBuf;
|
||||
|
||||
/// Return the full path to the config file
|
||||
fn file_path(&self) -> PathBuf {
|
||||
let mut config = Self::config_dir();
|
||||
if !config.exists() {
|
||||
create_dir(config.as_path())
|
||||
.unwrap_or_else(|e| panic!("Could not create {:?} {e}", Self::config_dir()));
|
||||
}
|
||||
config.push(self.file_name());
|
||||
let mut do_rename = !config.exists();
|
||||
let mut cfg_old = config.clone();
|
||||
// Migrating all configs to .ron format, so we do need to check for older ones
|
||||
if do_rename {
|
||||
warn!("Config {cfg_old:?} does not exist, looking for .cfg next");
|
||||
cfg_old.pop();
|
||||
let tmp = self.file_name();
|
||||
let parts: Vec<_> = tmp.split('.').collect();
|
||||
cfg_old.push(format!("{}.cfg", parts[0]));
|
||||
}
|
||||
if do_rename && cfg_old.exists() {
|
||||
// Now we gotta rename it
|
||||
warn!("Renaming {cfg_old:?} to {config:?}");
|
||||
std::fs::rename(&cfg_old, &config).unwrap_or_else(|err| {
|
||||
error!(
|
||||
"Could not rename. Please remove {} then restart service: Error {}",
|
||||
self.file_name(),
|
||||
err
|
||||
);
|
||||
});
|
||||
do_rename = false;
|
||||
}
|
||||
if do_rename && !cfg_old.exists() {
|
||||
warn!("Config {cfg_old:?} does not exist, looking for .conf next");
|
||||
cfg_old.pop();
|
||||
let tmp = self.file_name();
|
||||
let parts: Vec<_> = tmp.split('.').collect();
|
||||
cfg_old.push(format!("{}.conf", parts[0]));
|
||||
}
|
||||
if do_rename && cfg_old.exists() {
|
||||
// Now we gotta rename it
|
||||
warn!("Renaming {cfg_old:?} to {config:?}");
|
||||
std::fs::rename(&cfg_old, &config).unwrap_or_else(|err| {
|
||||
error!(
|
||||
"Could not rename. Please remove {} then restart service: Error {}",
|
||||
self.file_name(),
|
||||
err
|
||||
);
|
||||
});
|
||||
}
|
||||
config
|
||||
}
|
||||
|
||||
/// Directly open the config file for read and write. If the config file
|
||||
/// does not exist it is created, including the directories the file
|
||||
/// resides in.
|
||||
fn file_open(&self) -> File {
|
||||
OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.truncate(false)
|
||||
.open(self.file_path())
|
||||
.unwrap_or_else(|e| panic!("Could not open {:?} {e}", self.file_path()))
|
||||
}
|
||||
|
||||
/// Open and parse the config file to self from ron format
|
||||
fn read(&mut self) {
|
||||
if let Ok(data) = fs::read_to_string(self.file_path()) {
|
||||
if data.is_empty() {
|
||||
warn!("File is empty {:?}", self.file_path());
|
||||
} else if let Ok(data) = ron::from_str(&data) {
|
||||
*self = data;
|
||||
} else {
|
||||
warn!("Could not deserialise {:?}", self.file_path());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Open and parse the config file to self from ron format
|
||||
fn read_new(&self) -> Option<Self> {
|
||||
if let Ok(data) = fs::read_to_string(self.file_path()) {
|
||||
if data.is_empty() {
|
||||
warn!("File is empty {:?}", self.file_path());
|
||||
} else if let Ok(data) = ron::from_str(&data) {
|
||||
return Some(data);
|
||||
} else {
|
||||
warn!("Could not deserialise {:?}", self.file_path());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Write the config file data to pretty ron format
|
||||
fn write(&self) {
|
||||
let mut file = match File::create(self.file_path()) {
|
||||
Ok(data) => data,
|
||||
Err(e) => {
|
||||
error!(
|
||||
"Couldn't overwrite config {:?}, error: {e}",
|
||||
self.file_path()
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let ron = match ron::ser::to_string_pretty(&self, PrettyConfig::new().depth_limit(4)) {
|
||||
Ok(data) => data,
|
||||
Err(e) => {
|
||||
error!("Parse {:?} to RON failed, error: {e}", self.file_path());
|
||||
return;
|
||||
}
|
||||
};
|
||||
file.write_all(ron.as_bytes())
|
||||
.unwrap_or_else(|err| error!("Could not write config: {}", err));
|
||||
}
|
||||
|
||||
/// Renames the existing file to `<file>-old`
|
||||
fn rename_file_old(&self) {
|
||||
warn!(
|
||||
"Renaming {} to {}-old and recreating config",
|
||||
self.file_name(),
|
||||
self.file_name()
|
||||
);
|
||||
let mut cfg_old = self.file_path().to_string_lossy().to_string();
|
||||
cfg_old.push_str("-old");
|
||||
std::fs::rename(self.file_path(), cfg_old).unwrap_or_else(|err| {
|
||||
error!(
|
||||
"Could not rename. Please remove {} then restart service: Error {}",
|
||||
self.file_name(),
|
||||
err
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! std_config_load {
|
||||
($trait_name:ident: $($generic:ident),*) => {
|
||||
/// Base trait for loading/parsing. This is intended to be used to help update
|
||||
/// configs to new versions
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// use std::path::PathBuf;
|
||||
/// use serde::{Deserialize, Serialize};
|
||||
/// use config_traits::{StdConfig, StdConfigLoad2};
|
||||
///
|
||||
/// #[derive(Deserialize, Serialize, Debug)]
|
||||
/// struct FanCurveConfigOld {}
|
||||
///
|
||||
/// #[derive(Deserialize, Serialize, Debug)]
|
||||
/// struct FanCurveConfigOlder {}
|
||||
///
|
||||
/// #[derive(Deserialize, Serialize, Debug)]
|
||||
/// struct FanCurveConfig {}
|
||||
///
|
||||
/// impl From<FanCurveConfigOld> for FanCurveConfig {
|
||||
/// fn from(_: FanCurveConfigOld) -> Self { Self {} }
|
||||
/// }
|
||||
///
|
||||
/// impl From<FanCurveConfigOlder> for FanCurveConfig {
|
||||
/// fn from(_: FanCurveConfigOlder) -> Self { Self {} }
|
||||
/// }
|
||||
///
|
||||
/// impl StdConfig for FanCurveConfig {
|
||||
/// fn new() -> Self { Self {} }
|
||||
///
|
||||
/// fn file_name(&self) -> std::string::String { "test_name.conf".to_owned() }
|
||||
///
|
||||
/// fn config_dir() -> PathBuf { PathBuf::from("/tmp") }
|
||||
/// }
|
||||
///
|
||||
/// impl StdConfigLoad2<FanCurveConfigOld, FanCurveConfigOlder> for FanCurveConfig {}
|
||||
/// ```
|
||||
///
|
||||
/// If all of the generics fails to parse, then the old config is renamed and a
|
||||
/// new one created
|
||||
pub trait $trait_name<$($generic),*>
|
||||
where
|
||||
Self: $crate::StdConfig +std::fmt::Debug + DeserializeOwned + Serialize,
|
||||
$($generic: DeserializeOwned + Into<Self>),*
|
||||
{
|
||||
fn load(mut self) -> Self {
|
||||
let mut file = self.file_open();
|
||||
let mut buf = String::new();
|
||||
if let Ok(read_len) = file.read_to_string(&mut buf) {
|
||||
if read_len != 0 {
|
||||
if let Ok(data) = ron::from_str(&buf) {
|
||||
self = data;
|
||||
log::info!("Parsed RON for {:?}", std::any::type_name::<Self>());
|
||||
} else if let Ok(data) = serde_json::from_str(&buf) {
|
||||
self = data;
|
||||
log::info!("Parsed JSON for {:?}", std::any::type_name::<Self>());
|
||||
} else if let Ok(data) = toml::from_str(&buf) {
|
||||
self = data;
|
||||
log::info!("Parsed TOML for {:?}", std::any::type_name::<Self>());
|
||||
} $(else if let Ok(data) = ron::from_str::<$generic>(&buf) {
|
||||
self = data.into();
|
||||
log::info!("New version failed, trying previous: Parsed RON for {:?}", std::any::type_name::<$generic>());
|
||||
} else if let Ok(data) = serde_json::from_str::<$generic>(&buf) {
|
||||
self = data.into();
|
||||
log::info!("New version failed, trying previous: Parsed JSON for {:?}", std::any::type_name::<$generic>());
|
||||
} else if let Ok(data) = toml::from_str::<$generic>(&buf) {
|
||||
self = data.into();
|
||||
log::info!("Newvious version failed, trying previous: Parsed TOML for {:?}", std::any::type_name::<$generic>());
|
||||
})* else {
|
||||
self.rename_file_old();
|
||||
self = Self::new();
|
||||
}
|
||||
} else {
|
||||
error!("Config file {} zero read length", self.file_name());
|
||||
}
|
||||
}
|
||||
self.write();
|
||||
self
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
std_config_load!(StdConfigLoad:);
|
||||
std_config_load!(StdConfigLoad1: T1);
|
||||
std_config_load!(StdConfigLoad2: T1, T2);
|
||||
std_config_load!(StdConfigLoad3: T1, T2, T3);
|
||||
std_config_load!(StdConfigLoad4: T1, T2, T3, T4);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[test]
|
||||
fn check_macro_from_1() {
|
||||
#[derive(serde::Deserialize, serde::Serialize, Debug)]
|
||||
struct Test {}
|
||||
|
||||
#[derive(serde::Deserialize, serde::Serialize, Debug)]
|
||||
struct Old1 {}
|
||||
|
||||
impl crate::StdConfig for Test {
|
||||
fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
|
||||
fn file_name(&self) -> String {
|
||||
String::new()
|
||||
}
|
||||
|
||||
fn config_dir() -> PathBuf {
|
||||
PathBuf::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Old1> for Test {
|
||||
fn from(_: Old1) -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::StdConfigLoad1<Old1> for Test {}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_macro_from_3() {
|
||||
#[derive(serde::Deserialize, serde::Serialize, Debug)]
|
||||
struct Test {}
|
||||
|
||||
#[derive(serde::Deserialize, serde::Serialize, Debug)]
|
||||
struct Old1 {}
|
||||
|
||||
#[derive(serde::Deserialize, serde::Serialize, Debug)]
|
||||
struct Old2 {}
|
||||
|
||||
#[derive(serde::Deserialize, serde::Serialize, Debug)]
|
||||
struct Old3 {}
|
||||
|
||||
impl crate::StdConfig for Test {
|
||||
fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
|
||||
fn file_name(&self) -> String {
|
||||
String::new()
|
||||
}
|
||||
|
||||
fn config_dir() -> PathBuf {
|
||||
PathBuf::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Old1> for Test {
|
||||
fn from(_: Old1) -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Old2> for Test {
|
||||
fn from(_: Old2) -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Old3> for Test {
|
||||
fn from(_: Old3) -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::StdConfigLoad3<Old1, Old2, Old3> for Test {}
|
||||
}
|
||||
}
|
||||
7
cpuctl/Cargo.toml
Normal file
7
cpuctl/Cargo.toml
Normal file
@@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "cpuctl"
|
||||
license = "MPL-2.0"
|
||||
edition = "2021"
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
14
cpuctl/src/lib.rs
Normal file
14
cpuctl/src/lib.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
pub fn add(left: usize, right: usize) -> usize {
|
||||
left + right
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let result = add(2, 2);
|
||||
assert_eq!(result, 4);
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
[package]
|
||||
name = "daemon-user"
|
||||
version = "1.1.1"
|
||||
authors = ["Luke D Jones <luke@ljones.dev>"]
|
||||
edition = "2018"
|
||||
description = "Usermode daemon for user settings, anime, per-key lighting"
|
||||
|
||||
[lib]
|
||||
name = "rog_user"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "asusd-user"
|
||||
path = "src/daemon.rs"
|
||||
|
||||
[dependencies]
|
||||
# serialisation
|
||||
serde = "^1.0"
|
||||
serde_json = "^1.0"
|
||||
serde_derive = "^1.0"
|
||||
|
||||
rog_anime = { path = "../rog-anime" }
|
||||
rog_dbus = { path = "../rog-dbus" }
|
||||
rog_types = { path = "../rog-types" }
|
||||
|
||||
dirs = "3.0.1"
|
||||
|
||||
zbus = "^1.9.1"
|
||||
zvariant = "^2.6"
|
||||
zvariant_derive = "^2.6"
|
||||
@@ -1,74 +0,0 @@
|
||||
use rog_dbus::RogDbusClient;
|
||||
use rog_user::{
|
||||
ctrl_anime::{CtrlAnime, CtrlAnimeInner},
|
||||
user_config::*,
|
||||
DBUS_NAME,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use std::thread;
|
||||
use zbus::{fdo, Connection};
|
||||
|
||||
use std::sync::atomic::AtomicBool;
|
||||
|
||||
/// The anime loop needs an atomic to make it exit early if required
|
||||
static ANIME_INNER_EARLY_RETURN: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!(" user daemon v{}", rog_user::VERSION);
|
||||
println!(" rog-anime v{}", rog_anime::VERSION);
|
||||
println!(" rog-dbus v{}", rog_dbus::VERSION);
|
||||
println!(" rog-types v{}", rog_types::VERSION);
|
||||
|
||||
let (client, _) = RogDbusClient::new().unwrap();
|
||||
let supported = client.proxies().supported().get_supported_functions()?;
|
||||
|
||||
let mut config = UserConfig::new();
|
||||
config.load_config()?;
|
||||
|
||||
let anime_config = UserAnimeConfig::load_config(config.active_anime)?;
|
||||
let anime = anime_config.create_anime()?;
|
||||
|
||||
let anime_config = Arc::new(Mutex::new(anime_config));
|
||||
|
||||
// Create server
|
||||
let connection = Connection::new_session()?;
|
||||
fdo::DBusProxy::new(&connection)?
|
||||
.request_name(DBUS_NAME, fdo::RequestNameFlags::ReplaceExisting.into())?;
|
||||
let mut server = zbus::ObjectServer::new(&connection);
|
||||
|
||||
// Set up the anime data and run loop/thread
|
||||
if supported.anime_ctrl.0 {
|
||||
// Inner behind mutex required for thread safety
|
||||
let inner = Arc::new(Mutex::new(CtrlAnimeInner::new(
|
||||
anime,
|
||||
client,
|
||||
&ANIME_INNER_EARLY_RETURN,
|
||||
)?));
|
||||
// Need new client object for dbus control part
|
||||
let (client, _) = RogDbusClient::new().unwrap();
|
||||
let anime_control = CtrlAnime::new(
|
||||
anime_config,
|
||||
inner.clone(),
|
||||
client,
|
||||
&ANIME_INNER_EARLY_RETURN,
|
||||
)?;
|
||||
anime_control.add_to_server(&mut server);
|
||||
// Thread using inner
|
||||
let _anime_thread = thread::Builder::new()
|
||||
.name("Anime User".into())
|
||||
.spawn(move || loop {
|
||||
if let Ok(inner) = inner.try_lock() {
|
||||
inner.run().unwrap();
|
||||
}
|
||||
})?;
|
||||
}
|
||||
|
||||
if supported.keyboard_led.per_key_led_mode {}
|
||||
|
||||
loop {
|
||||
if let Err(err) = server.try_handle_next() {
|
||||
println!("{}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,203 +0,0 @@
|
||||
use std::{
|
||||
fs::{create_dir, OpenOptions},
|
||||
io::{Read, Write},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use rog_anime::{AnimTime, AnimeAction, Sequences, Vec2};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
use crate::error::Error;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct UserAnimeConfig {
|
||||
pub name: String,
|
||||
pub anime: Vec<AnimeAction>,
|
||||
}
|
||||
|
||||
impl UserAnimeConfig {
|
||||
pub fn create_anime(&self) -> Result<Sequences, Error> {
|
||||
let mut seq = Sequences::new();
|
||||
|
||||
for (idx, action) in self.anime.iter().enumerate() {
|
||||
seq.insert(idx, action)?;
|
||||
}
|
||||
|
||||
Ok(seq)
|
||||
}
|
||||
|
||||
pub fn write(&self) -> Result<(), Error> {
|
||||
let mut path = if let Some(dir) = dirs::config_dir() {
|
||||
dir
|
||||
} else {
|
||||
return Err(Error::XdgVars);
|
||||
};
|
||||
|
||||
path.push("rog");
|
||||
if !path.exists() {
|
||||
create_dir(path.clone())?;
|
||||
}
|
||||
let name = self.name.clone();
|
||||
path.push(name + ".cfg");
|
||||
|
||||
let mut file = OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.open(&path)?;
|
||||
|
||||
let json = serde_json::to_string_pretty(&self).unwrap();
|
||||
file.write_all(json.as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load_config(name: String) -> Result<UserAnimeConfig, Error> {
|
||||
let mut path = if let Some(dir) = dirs::config_dir() {
|
||||
dir
|
||||
} else {
|
||||
return Err(Error::XdgVars);
|
||||
};
|
||||
|
||||
path.push("rog");
|
||||
if !path.exists() {
|
||||
create_dir(path.clone())?;
|
||||
}
|
||||
|
||||
path.push(name.clone() + ".cfg");
|
||||
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(&path)?;
|
||||
|
||||
let mut buf = String::new();
|
||||
|
||||
if let Ok(read_len) = file.read_to_string(&mut buf) {
|
||||
if read_len == 0 {
|
||||
let default = UserAnimeConfig {
|
||||
name,
|
||||
..Default::default()
|
||||
};
|
||||
let json = serde_json::to_string_pretty(&default).unwrap();
|
||||
file.write_all(json.as_bytes())?;
|
||||
return Ok(default);
|
||||
} else if let Ok(data) = serde_json::from_str::<UserAnimeConfig>(&buf) {
|
||||
return Ok(data);
|
||||
}
|
||||
}
|
||||
Err(Error::ConfigLoadFail)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for UserAnimeConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
name: "default".to_string(),
|
||||
anime: vec![
|
||||
AnimeAction::AsusAnimation {
|
||||
file: "/usr/share/asusd/anime/asus/rog/Sunset.gif".into(),
|
||||
brightness: 0.5,
|
||||
time: AnimTime::Cycles(1),
|
||||
},
|
||||
AnimeAction::ImageAnimation {
|
||||
file: "/usr/share/asusd/anime/custom/sonic-run.gif".into(),
|
||||
scale: 0.9,
|
||||
angle: 0.65,
|
||||
translation: Vec2::default(),
|
||||
brightness: 0.5,
|
||||
time: AnimTime::Time(Duration::from_secs(5)),
|
||||
},
|
||||
AnimeAction::Image {
|
||||
file: "/usr/share/asusd/anime/custom/rust.png".into(),
|
||||
scale: 1.0,
|
||||
angle: 0.0,
|
||||
translation: Vec2::default(),
|
||||
brightness: 0.6,
|
||||
},
|
||||
AnimeAction::Pause(Duration::from_secs(6)),
|
||||
AnimeAction::ImageAnimation {
|
||||
file: "/usr/share/asusd/anime/custom/sonic-wait.gif".into(),
|
||||
scale: 0.9,
|
||||
angle: 0.0,
|
||||
translation: Vec2::new(3.0, 2.0),
|
||||
brightness: 0.5,
|
||||
time: AnimTime::Cycles(2),
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize)]
|
||||
pub struct UserConfig {
|
||||
/// Name of active anime config file in the user config directory
|
||||
pub active_anime: String,
|
||||
}
|
||||
|
||||
impl UserConfig {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
active_anime: "anime-default".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_config(&mut self) -> Result<(), Error> {
|
||||
let mut path = if let Some(dir) = dirs::config_dir() {
|
||||
dir
|
||||
} else {
|
||||
return Err(Error::XdgVars);
|
||||
};
|
||||
|
||||
path.push("rog");
|
||||
if !path.exists() {
|
||||
create_dir(path.clone())?;
|
||||
}
|
||||
|
||||
path.push("rog-user.cfg");
|
||||
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(&path)?;
|
||||
|
||||
let mut buf = String::new();
|
||||
|
||||
if let Ok(read_len) = file.read_to_string(&mut buf) {
|
||||
if read_len == 0 {
|
||||
let json = serde_json::to_string_pretty(&self).unwrap();
|
||||
file.write_all(json.as_bytes())?;
|
||||
} else if let Ok(data) = serde_json::from_str::<UserConfig>(&buf) {
|
||||
self.active_anime = data.active_anime;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write(&self) -> Result<(), Error> {
|
||||
let mut path = if let Some(dir) = dirs::config_dir() {
|
||||
dir
|
||||
} else {
|
||||
return Err(Error::XdgVars);
|
||||
};
|
||||
|
||||
path.push("rog");
|
||||
if !path.exists() {
|
||||
create_dir(path.clone())?;
|
||||
}
|
||||
|
||||
path.push("rog-user.cfg");
|
||||
|
||||
let mut file = OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.open(&path)?;
|
||||
|
||||
let json = serde_json::to_string_pretty(&self).unwrap();
|
||||
file.write_all(json.as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
[package]
|
||||
name = "daemon"
|
||||
version = "3.6.0"
|
||||
license = "MPL-2.0"
|
||||
readme = "README.md"
|
||||
authors = ["Luke <luke@ljones.dev>"]
|
||||
repository = "https://gitlab.com/asus-linux/asus-nb-ctrl"
|
||||
homepage = "https://gitlab.com/asus-linux/asus-nb-ctrl"
|
||||
description = "A daemon app for ASUS GX502 and similar laptops to control missing features"
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
name = "daemon"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "asusd"
|
||||
path = "src/daemon.rs"
|
||||
|
||||
[dependencies]
|
||||
rog_anime = { path = "../rog-anime" }
|
||||
rog_aura = { path = "../rog-aura" }
|
||||
rog_types = { path = "../rog-types" }
|
||||
rog_profiles = { path = "../rog-profiles" }
|
||||
rog_dbus = { path = "../rog-dbus" }
|
||||
rusb = "^0.8"
|
||||
udev = "^0.6"
|
||||
|
||||
# cli and logging
|
||||
log = "^0.4"
|
||||
env_logger = "^0.8"
|
||||
|
||||
zbus = "^1.9.1"
|
||||
zvariant = "^2.6"
|
||||
zvariant_derive = { version = "^2.6" }
|
||||
logind-zbus = "^0.7.1"
|
||||
|
||||
# serialisation
|
||||
serde = "^1.0"
|
||||
serde_derive = "^1.0"
|
||||
serde_json = "^1.0"
|
||||
toml = "^0.5"
|
||||
|
||||
# Device control
|
||||
sysfs-class = "^0.1.2" # used for backlight control and baord ID
|
||||
rog_fan_curve = { version = "0.1", features = ["serde"] }
|
||||
@@ -1,169 +0,0 @@
|
||||
use log::{error, info, warn};
|
||||
use rog_profiles::profiles::{FanLevel, Profile};
|
||||
use rog_types::gfx_vendors::GfxVendors;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::collections::BTreeMap;
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::io::{Read, Write};
|
||||
|
||||
use crate::config_old::*;
|
||||
use crate::VERSION;
|
||||
|
||||
pub static CONFIG_PATH: &str = "/etc/asusd/asusd.conf";
|
||||
pub static AURA_CONFIG_PATH: &str = "/etc/asusd/asusd.conf";
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct Config {
|
||||
pub gfx_mode: GfxVendors,
|
||||
/// Only for informational purposes.
|
||||
#[serde(skip)]
|
||||
pub gfx_tmp_mode: Option<GfxVendors>,
|
||||
pub gfx_managed: bool,
|
||||
pub gfx_vfio_enable: bool,
|
||||
pub active_profile: String,
|
||||
pub toggle_profiles: Vec<String>,
|
||||
#[serde(skip)]
|
||||
pub curr_fan_mode: u8,
|
||||
pub bat_charge_limit: u8,
|
||||
pub power_profiles: BTreeMap<String, Profile>,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
let mut pwr = BTreeMap::new();
|
||||
pwr.insert(
|
||||
"normal".into(),
|
||||
Profile::new(
|
||||
"normal".into(),
|
||||
0,
|
||||
100,
|
||||
true,
|
||||
FanLevel::Normal,
|
||||
"".to_string(),
|
||||
),
|
||||
);
|
||||
pwr.insert(
|
||||
"boost".into(),
|
||||
Profile::new(
|
||||
"boost".into(),
|
||||
0,
|
||||
100,
|
||||
true,
|
||||
FanLevel::Boost,
|
||||
"".to_string(),
|
||||
),
|
||||
);
|
||||
pwr.insert(
|
||||
"silent".into(),
|
||||
Profile::new(
|
||||
"silent".into(),
|
||||
0,
|
||||
100,
|
||||
true,
|
||||
FanLevel::Silent,
|
||||
"".to_string(),
|
||||
),
|
||||
);
|
||||
|
||||
Config {
|
||||
gfx_mode: GfxVendors::Hybrid,
|
||||
gfx_tmp_mode: None,
|
||||
gfx_managed: true,
|
||||
gfx_vfio_enable: false,
|
||||
active_profile: "normal".into(),
|
||||
toggle_profiles: vec!["normal".into(), "boost".into(), "silent".into()],
|
||||
curr_fan_mode: 0,
|
||||
bat_charge_limit: 100,
|
||||
power_profiles: pwr,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
/// `load` will attempt to read the config, and panic if the dir is missing
|
||||
pub fn load() -> Self {
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(&CONFIG_PATH)
|
||||
.unwrap_or_else(|_| panic!("The directory /etc/asusd/ is missing")); // okay to cause panic here
|
||||
let mut buf = String::new();
|
||||
if let Ok(read_len) = file.read_to_string(&mut buf) {
|
||||
if read_len == 0 {
|
||||
return Config::create_default(&mut file);
|
||||
} else {
|
||||
if let Ok(data) = serde_json::from_str(&buf) {
|
||||
return data;
|
||||
} else if let Ok(data) = serde_json::from_str::<ConfigV352>(&buf) {
|
||||
let config = data.into_current();
|
||||
config.write();
|
||||
info!("Updated config version to: {}", VERSION);
|
||||
return config;
|
||||
} else if let Ok(data) = serde_json::from_str::<ConfigV341>(&buf) {
|
||||
let config = data.into_current();
|
||||
config.write();
|
||||
info!("Updated config version to: {}", VERSION);
|
||||
return config;
|
||||
} else if let Ok(data) = serde_json::from_str::<ConfigV324>(&buf) {
|
||||
let config = data.into_current();
|
||||
config.write();
|
||||
info!("Updated config version to: {}", VERSION);
|
||||
return config;
|
||||
} else if let Ok(data) = serde_json::from_str::<ConfigV317>(&buf) {
|
||||
let config = data.into_current();
|
||||
config.write();
|
||||
info!("Updated config version to: {}", VERSION);
|
||||
return config;
|
||||
}
|
||||
warn!("Could not deserialise {}", CONFIG_PATH);
|
||||
panic!("Please remove {} then restart asusd", CONFIG_PATH);
|
||||
}
|
||||
}
|
||||
Config::create_default(&mut file)
|
||||
}
|
||||
|
||||
fn create_default(file: &mut File) -> Self {
|
||||
let config = Config::default();
|
||||
// Should be okay to unwrap this as is since it is a Default
|
||||
let json = serde_json::to_string_pretty(&config).unwrap();
|
||||
file.write_all(json.as_bytes())
|
||||
.unwrap_or_else(|_| panic!("Could not write {}", CONFIG_PATH));
|
||||
config
|
||||
}
|
||||
|
||||
pub fn read(&mut self) {
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.open(&CONFIG_PATH)
|
||||
.unwrap_or_else(|err| panic!("Error reading {}: {}", CONFIG_PATH, err));
|
||||
let mut buf = String::new();
|
||||
if let Ok(l) = file.read_to_string(&mut buf) {
|
||||
if l == 0 {
|
||||
warn!("File is empty {}", CONFIG_PATH);
|
||||
} else {
|
||||
let x: Config = serde_json::from_str(&buf)
|
||||
.unwrap_or_else(|_| panic!("Could not deserialise {}", CONFIG_PATH));
|
||||
*self = x;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_new() -> Result<Config, Box<dyn std::error::Error>> {
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.open(&CONFIG_PATH)
|
||||
.unwrap_or_else(|err| panic!("Error reading {}: {}", CONFIG_PATH, err));
|
||||
let mut buf = String::new();
|
||||
file.read_to_string(&mut buf)?;
|
||||
let x: Config = serde_json::from_str(&buf)?;
|
||||
Ok(x)
|
||||
}
|
||||
|
||||
pub fn write(&self) {
|
||||
let mut file = File::create(CONFIG_PATH).expect("Couldn't overwrite config");
|
||||
let json = serde_json::to_string_pretty(self).expect("Parse config to JSON failed");
|
||||
file.write_all(json.as_bytes())
|
||||
.unwrap_or_else(|err| error!("Could not write config: {}", err));
|
||||
}
|
||||
}
|
||||
@@ -1,237 +0,0 @@
|
||||
use crate::VERSION;
|
||||
use log::{error, info, warn};
|
||||
use rog_anime::{error::AnimeError, ActionData, AnimTime, AnimeAction, Vec2};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::io::{Read, Write};
|
||||
use std::time::Duration;
|
||||
|
||||
pub static ANIME_CONFIG_PATH: &str = "/etc/asusd/anime.conf";
|
||||
pub static ANIME_CACHE_PATH: &str = "/etc/asusd/anime-cache.conf";
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct AnimeConfigV341 {
|
||||
pub system: Option<AnimeAction>,
|
||||
pub boot: Option<AnimeAction>,
|
||||
pub suspend: Option<AnimeAction>,
|
||||
pub shutdown: Option<AnimeAction>,
|
||||
}
|
||||
|
||||
impl AnimeConfigV341 {
|
||||
pub(crate) fn into_current(self) -> AnimeConfig {
|
||||
AnimeConfig {
|
||||
system: if let Some(ani) = self.system {
|
||||
vec![ani]
|
||||
} else {
|
||||
vec![]
|
||||
},
|
||||
boot: if let Some(ani) = self.boot {
|
||||
vec![ani]
|
||||
} else {
|
||||
vec![]
|
||||
},
|
||||
wake: if let Some(ani) = self.suspend {
|
||||
vec![ani]
|
||||
} else {
|
||||
vec![]
|
||||
},
|
||||
shutdown: if let Some(ani) = self.shutdown {
|
||||
vec![ani]
|
||||
} else {
|
||||
vec![]
|
||||
},
|
||||
brightness: 1.0,
|
||||
awake_enabled: true,
|
||||
boot_anim_enabled: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct AnimeConfigV352 {
|
||||
pub system: Vec<AnimeAction>,
|
||||
pub boot: Vec<AnimeAction>,
|
||||
pub wake: Vec<AnimeAction>,
|
||||
pub shutdown: Vec<AnimeAction>,
|
||||
pub brightness: f32,
|
||||
}
|
||||
|
||||
impl AnimeConfigV352 {
|
||||
pub(crate) fn into_current(self) -> AnimeConfig {
|
||||
AnimeConfig {
|
||||
system: self.system,
|
||||
boot: self.boot,
|
||||
wake: self.wake,
|
||||
shutdown: self.shutdown,
|
||||
brightness: 1.0,
|
||||
awake_enabled: true,
|
||||
boot_anim_enabled: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Default)]
|
||||
pub struct AnimeConfigCached {
|
||||
pub system: Vec<ActionData>,
|
||||
pub boot: Vec<ActionData>,
|
||||
pub wake: Vec<ActionData>,
|
||||
pub shutdown: Vec<ActionData>,
|
||||
}
|
||||
|
||||
impl AnimeConfigCached {
|
||||
pub fn init_from_config(&mut self, config: &AnimeConfig) -> Result<(), AnimeError> {
|
||||
let mut sys = Vec::with_capacity(config.system.len());
|
||||
for ani in config.system.iter() {
|
||||
sys.push(ActionData::from_anime_action(ani)?);
|
||||
}
|
||||
self.system = sys;
|
||||
|
||||
let mut boot = Vec::with_capacity(config.boot.len());
|
||||
for ani in config.boot.iter() {
|
||||
boot.push(ActionData::from_anime_action(ani)?);
|
||||
}
|
||||
self.boot = boot;
|
||||
|
||||
let mut wake = Vec::with_capacity(config.wake.len());
|
||||
for ani in config.wake.iter() {
|
||||
wake.push(ActionData::from_anime_action(ani)?);
|
||||
}
|
||||
self.wake = wake;
|
||||
|
||||
let mut shutdown = Vec::with_capacity(config.shutdown.len());
|
||||
for ani in config.shutdown.iter() {
|
||||
shutdown.push(ActionData::from_anime_action(ani)?);
|
||||
}
|
||||
self.shutdown = shutdown;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Config for base system actions for the anime display
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct AnimeConfig {
|
||||
pub system: Vec<AnimeAction>,
|
||||
pub boot: Vec<AnimeAction>,
|
||||
pub wake: Vec<AnimeAction>,
|
||||
pub shutdown: Vec<AnimeAction>,
|
||||
pub brightness: f32,
|
||||
pub awake_enabled: bool,
|
||||
pub boot_anim_enabled: bool,
|
||||
}
|
||||
|
||||
impl Default for AnimeConfig {
|
||||
fn default() -> Self {
|
||||
AnimeConfig {
|
||||
system: Vec::new(),
|
||||
boot: Vec::new(),
|
||||
wake: Vec::new(),
|
||||
shutdown: Vec::new(),
|
||||
brightness: 1.0,
|
||||
awake_enabled: true,
|
||||
boot_anim_enabled: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AnimeConfig {
|
||||
/// `load` will attempt to read the config, and panic if the dir is missing
|
||||
pub fn load() -> Self {
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(&ANIME_CONFIG_PATH)
|
||||
.unwrap_or_else(|_| {
|
||||
panic!(
|
||||
"The file {} or directory /etc/asusd/ is missing",
|
||||
ANIME_CONFIG_PATH
|
||||
)
|
||||
}); // okay to cause panic here
|
||||
let mut buf = String::new();
|
||||
if let Ok(read_len) = file.read_to_string(&mut buf) {
|
||||
if read_len == 0 {
|
||||
return AnimeConfig::create_default(&mut file);
|
||||
} else {
|
||||
if let Ok(data) = serde_json::from_str(&buf) {
|
||||
return data;
|
||||
} else if let Ok(data) = serde_json::from_str::<AnimeConfigV341>(&buf) {
|
||||
let config = data.into_current();
|
||||
config.write();
|
||||
info!("Updated config version to: {}", VERSION);
|
||||
return config;
|
||||
} else if let Ok(data) = serde_json::from_str::<AnimeConfigV352>(&buf) {
|
||||
let config = data.into_current();
|
||||
config.write();
|
||||
info!("Updated config version to: {}", VERSION);
|
||||
return config;
|
||||
}
|
||||
warn!("Could not deserialise {}", ANIME_CONFIG_PATH);
|
||||
panic!("Please remove {} then restart asusd", ANIME_CONFIG_PATH);
|
||||
}
|
||||
}
|
||||
AnimeConfig::create_default(&mut file)
|
||||
}
|
||||
|
||||
fn create_default(file: &mut File) -> Self {
|
||||
// create a default config here
|
||||
let config = AnimeConfig {
|
||||
system: vec![],
|
||||
boot: vec![AnimeAction::ImageAnimation {
|
||||
file: "/usr/share/asusd/anime/custom/sonic-run.gif".into(),
|
||||
scale: 0.9,
|
||||
angle: 0.65,
|
||||
translation: Vec2::default(),
|
||||
brightness: 1.0,
|
||||
time: AnimTime::Time(Duration::from_secs(5)),
|
||||
}],
|
||||
wake: vec![AnimeAction::ImageAnimation {
|
||||
file: "/usr/share/asusd/anime/custom/sonic-run.gif".into(),
|
||||
scale: 0.9,
|
||||
angle: 0.65,
|
||||
translation: Vec2::default(),
|
||||
brightness: 1.0,
|
||||
time: AnimTime::Time(Duration::from_secs(5)),
|
||||
}],
|
||||
shutdown: vec![AnimeAction::ImageAnimation {
|
||||
file: "/usr/share/asusd/anime/custom/sonic-wait.gif".into(),
|
||||
scale: 0.9,
|
||||
angle: 0.0,
|
||||
translation: Vec2::new(3.0, 2.0),
|
||||
brightness: 1.0,
|
||||
time: AnimTime::Infinite,
|
||||
}],
|
||||
brightness: 1.0,
|
||||
awake_enabled: true,
|
||||
boot_anim_enabled: true,
|
||||
};
|
||||
// Should be okay to unwrap this as is since it is a Default
|
||||
let json = serde_json::to_string_pretty(&config).unwrap();
|
||||
file.write_all(json.as_bytes())
|
||||
.unwrap_or_else(|_| panic!("Could not write {}", ANIME_CONFIG_PATH));
|
||||
config
|
||||
}
|
||||
|
||||
pub fn read(&mut self) {
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.open(&ANIME_CONFIG_PATH)
|
||||
.unwrap_or_else(|err| panic!("Error reading {}: {}", ANIME_CONFIG_PATH, err));
|
||||
let mut buf = String::new();
|
||||
if let Ok(l) = file.read_to_string(&mut buf) {
|
||||
if l == 0 {
|
||||
warn!("File is empty {}", ANIME_CONFIG_PATH);
|
||||
} else {
|
||||
let x: AnimeConfig = serde_json::from_str(&buf)
|
||||
.unwrap_or_else(|_| panic!("Could not deserialise {}", ANIME_CONFIG_PATH));
|
||||
*self = x;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(&self) {
|
||||
let mut file = File::create(ANIME_CONFIG_PATH).expect("Couldn't overwrite config");
|
||||
let json = serde_json::to_string_pretty(self).expect("Parse config to JSON failed");
|
||||
file.write_all(json.as_bytes())
|
||||
.unwrap_or_else(|err| error!("Could not write config: {}", err));
|
||||
}
|
||||
}
|
||||
@@ -1,267 +0,0 @@
|
||||
use crate::laptops::LaptopLedData;
|
||||
use log::{error, info, warn};
|
||||
use rog_aura::{AuraEffect, AuraModeNum, AuraZone, LedBrightness};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::collections::BTreeMap;
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::io::{Read, Write};
|
||||
|
||||
pub static AURA_CONFIG_PATH: &str = "/etc/asusd/aura.conf";
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct AuraConfigV320 {
|
||||
pub brightness: u32,
|
||||
pub current_mode: AuraModeNum,
|
||||
pub builtins: BTreeMap<AuraModeNum, AuraEffect>,
|
||||
pub multizone: Option<AuraMultiZone>,
|
||||
}
|
||||
|
||||
impl AuraConfigV320 {
|
||||
pub(crate) fn into_current(self) -> AuraConfig {
|
||||
AuraConfig {
|
||||
brightness: <LedBrightness>::from(self.brightness),
|
||||
current_mode: self.current_mode,
|
||||
builtins: self.builtins,
|
||||
multizone: self.multizone,
|
||||
awake_enabled: true,
|
||||
sleep_anim_enabled: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct AuraConfigV352 {
|
||||
pub brightness: LedBrightness,
|
||||
pub current_mode: AuraModeNum,
|
||||
pub builtins: BTreeMap<AuraModeNum, AuraEffect>,
|
||||
pub multizone: Option<AuraMultiZone>,
|
||||
}
|
||||
|
||||
impl AuraConfigV352 {
|
||||
pub(crate) fn into_current(self) -> AuraConfig {
|
||||
AuraConfig {
|
||||
brightness: self.brightness,
|
||||
current_mode: self.current_mode,
|
||||
builtins: self.builtins,
|
||||
multizone: self.multizone,
|
||||
awake_enabled: true,
|
||||
sleep_anim_enabled: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct AuraConfig {
|
||||
pub brightness: LedBrightness,
|
||||
pub current_mode: AuraModeNum,
|
||||
pub builtins: BTreeMap<AuraModeNum, AuraEffect>,
|
||||
pub multizone: Option<AuraMultiZone>,
|
||||
pub awake_enabled: bool,
|
||||
pub sleep_anim_enabled: bool,
|
||||
}
|
||||
|
||||
impl Default for AuraConfig {
|
||||
fn default() -> Self {
|
||||
AuraConfig {
|
||||
brightness: LedBrightness::Med,
|
||||
current_mode: AuraModeNum::Static,
|
||||
builtins: BTreeMap::new(),
|
||||
multizone: None,
|
||||
awake_enabled: true,
|
||||
sleep_anim_enabled: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AuraConfig {
|
||||
/// `load` will attempt to read the config, and panic if the dir is missing
|
||||
pub fn load(supported_led_modes: &LaptopLedData) -> Self {
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(&AURA_CONFIG_PATH)
|
||||
.unwrap_or_else(|_| {
|
||||
panic!(
|
||||
"The file {} or directory /etc/asusd/ is missing",
|
||||
AURA_CONFIG_PATH
|
||||
)
|
||||
}); // okay to cause panic here
|
||||
let mut buf = String::new();
|
||||
if let Ok(read_len) = file.read_to_string(&mut buf) {
|
||||
if read_len == 0 {
|
||||
return AuraConfig::create_default(&mut file, &supported_led_modes);
|
||||
} else {
|
||||
if let Ok(data) = serde_json::from_str(&buf) {
|
||||
return data;
|
||||
} else if let Ok(data) = serde_json::from_str::<AuraConfigV320>(&buf) {
|
||||
let config = data.into_current();
|
||||
config.write();
|
||||
info!("Updated AuraConfig version");
|
||||
return config;
|
||||
} else if let Ok(data) = serde_json::from_str::<AuraConfigV352>(&buf) {
|
||||
let config = data.into_current();
|
||||
config.write();
|
||||
info!("Updated AuraConfig version");
|
||||
return config;
|
||||
}
|
||||
warn!("Could not deserialise {}", AURA_CONFIG_PATH);
|
||||
panic!("Please remove {} then restart asusd", AURA_CONFIG_PATH);
|
||||
}
|
||||
}
|
||||
AuraConfig::create_default(&mut file, &supported_led_modes)
|
||||
}
|
||||
|
||||
fn create_default(file: &mut File, support_data: &LaptopLedData) -> Self {
|
||||
// create a default config here
|
||||
let mut config = AuraConfig::default();
|
||||
|
||||
for n in &support_data.standard {
|
||||
config
|
||||
.builtins
|
||||
.insert(*n, AuraEffect::default_with_mode(*n));
|
||||
}
|
||||
|
||||
// Should be okay to unwrap this as is since it is a Default
|
||||
let json = serde_json::to_string(&config).unwrap();
|
||||
file.write_all(json.as_bytes())
|
||||
.unwrap_or_else(|_| panic!("Could not write {}", AURA_CONFIG_PATH));
|
||||
config
|
||||
}
|
||||
|
||||
pub fn read(&mut self) {
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.open(&AURA_CONFIG_PATH)
|
||||
.unwrap_or_else(|err| panic!("Error reading {}: {}", AURA_CONFIG_PATH, err));
|
||||
let mut buf = String::new();
|
||||
if let Ok(l) = file.read_to_string(&mut buf) {
|
||||
if l == 0 {
|
||||
warn!("File is empty {}", AURA_CONFIG_PATH);
|
||||
} else {
|
||||
let x: AuraConfig = serde_json::from_str(&buf)
|
||||
.unwrap_or_else(|_| panic!("Could not deserialise {}", AURA_CONFIG_PATH));
|
||||
*self = x;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(&self) {
|
||||
let mut file = File::create(AURA_CONFIG_PATH).expect("Couldn't overwrite config");
|
||||
let json = serde_json::to_string_pretty(self).expect("Parse config to JSON failed");
|
||||
file.write_all(json.as_bytes())
|
||||
.unwrap_or_else(|err| error!("Could not write config: {}", err));
|
||||
}
|
||||
|
||||
/// Multipurpose, will accecpt AuraEffect with zones and put in the correct store
|
||||
pub fn set_builtin(&mut self, effect: AuraEffect) {
|
||||
match effect.zone() {
|
||||
AuraZone::None => {
|
||||
self.builtins.insert(*effect.mode(), effect);
|
||||
}
|
||||
_ => {
|
||||
if let Some(multi) = self.multizone.as_mut() {
|
||||
multi.set(effect)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_multizone(&self, aura_type: AuraModeNum) -> Option<&[AuraEffect; 4]> {
|
||||
if let Some(multi) = &self.multizone {
|
||||
if aura_type == AuraModeNum::Static {
|
||||
return Some(multi.static_());
|
||||
} else if aura_type == AuraModeNum::Breathe {
|
||||
return Some(multi.breathe());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct AuraMultiZone {
|
||||
static_: [AuraEffect; 4],
|
||||
breathe: [AuraEffect; 4],
|
||||
}
|
||||
|
||||
impl AuraMultiZone {
|
||||
pub fn set(&mut self, effect: AuraEffect) {
|
||||
if effect.mode == AuraModeNum::Static {
|
||||
match effect.zone {
|
||||
AuraZone::None => {}
|
||||
AuraZone::One => self.static_[0] = effect,
|
||||
AuraZone::Two => self.static_[1] = effect,
|
||||
AuraZone::Three => self.static_[2] = effect,
|
||||
AuraZone::Four => self.static_[3] = effect,
|
||||
}
|
||||
} else if effect.mode == AuraModeNum::Breathe {
|
||||
match effect.zone {
|
||||
AuraZone::None => {}
|
||||
AuraZone::One => self.breathe[0] = effect,
|
||||
AuraZone::Two => self.breathe[1] = effect,
|
||||
AuraZone::Three => self.breathe[2] = effect,
|
||||
AuraZone::Four => self.breathe[3] = effect,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn static_(&self) -> &[AuraEffect; 4] {
|
||||
&self.static_
|
||||
}
|
||||
|
||||
pub fn breathe(&self) -> &[AuraEffect; 4] {
|
||||
&self.breathe
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AuraMultiZone {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
static_: [
|
||||
AuraEffect {
|
||||
mode: AuraModeNum::Static,
|
||||
zone: AuraZone::One,
|
||||
..Default::default()
|
||||
},
|
||||
AuraEffect {
|
||||
mode: AuraModeNum::Static,
|
||||
zone: AuraZone::Two,
|
||||
..Default::default()
|
||||
},
|
||||
AuraEffect {
|
||||
mode: AuraModeNum::Static,
|
||||
zone: AuraZone::Three,
|
||||
..Default::default()
|
||||
},
|
||||
AuraEffect {
|
||||
mode: AuraModeNum::Static,
|
||||
zone: AuraZone::Four,
|
||||
..Default::default()
|
||||
},
|
||||
],
|
||||
breathe: [
|
||||
AuraEffect {
|
||||
mode: AuraModeNum::Breathe,
|
||||
zone: AuraZone::One,
|
||||
..Default::default()
|
||||
},
|
||||
AuraEffect {
|
||||
mode: AuraModeNum::Breathe,
|
||||
zone: AuraZone::Two,
|
||||
..Default::default()
|
||||
},
|
||||
AuraEffect {
|
||||
mode: AuraModeNum::Breathe,
|
||||
zone: AuraZone::Three,
|
||||
..Default::default()
|
||||
},
|
||||
AuraEffect {
|
||||
mode: AuraModeNum::Breathe,
|
||||
zone: AuraZone::Four,
|
||||
..Default::default()
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
use rog_fan_curve::Curve;
|
||||
use rog_profiles::profiles::Profile;
|
||||
use rog_types::gfx_vendors::GfxVendors;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::config::Config;
|
||||
|
||||
/// for parsing old v3.1.7 config
|
||||
#[derive(Deserialize)]
|
||||
pub(crate) struct ConfigV317 {
|
||||
pub gfx_mode: GfxVendors,
|
||||
pub gfx_managed: bool,
|
||||
pub active_profile: String,
|
||||
pub toggle_profiles: Vec<String>,
|
||||
#[serde(skip)]
|
||||
pub curr_fan_mode: u8,
|
||||
pub bat_charge_limit: u8,
|
||||
pub kbd_led_brightness: u8,
|
||||
pub kbd_backlight_mode: u8,
|
||||
#[serde(skip)]
|
||||
pub kbd_backlight_modes: Option<bool>,
|
||||
pub power_profiles: BTreeMap<String, ProfileV317>,
|
||||
}
|
||||
|
||||
impl ConfigV317 {
|
||||
pub(crate) fn into_current(self) -> Config {
|
||||
Config {
|
||||
gfx_mode: GfxVendors::Hybrid,
|
||||
gfx_tmp_mode: None,
|
||||
gfx_managed: self.gfx_managed,
|
||||
gfx_vfio_enable: false,
|
||||
active_profile: self.active_profile,
|
||||
toggle_profiles: self.toggle_profiles,
|
||||
curr_fan_mode: self.curr_fan_mode,
|
||||
bat_charge_limit: self.bat_charge_limit,
|
||||
power_profiles: ProfileV317::transform_map(self.power_profiles),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct ConfigV324 {
|
||||
pub gfx_mode: GfxVendors,
|
||||
pub gfx_managed: bool,
|
||||
pub active_profile: String,
|
||||
pub toggle_profiles: Vec<String>,
|
||||
#[serde(skip)]
|
||||
pub curr_fan_mode: u8,
|
||||
pub bat_charge_limit: u8,
|
||||
pub power_profiles: BTreeMap<String, ProfileV317>,
|
||||
}
|
||||
|
||||
impl ConfigV324 {
|
||||
pub(crate) fn into_current(self) -> Config {
|
||||
Config {
|
||||
gfx_mode: GfxVendors::Hybrid,
|
||||
gfx_tmp_mode: None,
|
||||
gfx_managed: self.gfx_managed,
|
||||
gfx_vfio_enable: false,
|
||||
active_profile: self.active_profile,
|
||||
toggle_profiles: self.toggle_profiles,
|
||||
curr_fan_mode: self.curr_fan_mode,
|
||||
bat_charge_limit: self.bat_charge_limit,
|
||||
power_profiles: ProfileV317::transform_map(self.power_profiles),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct ConfigV341 {
|
||||
pub gfx_mode: GfxVendors,
|
||||
pub gfx_managed: bool,
|
||||
pub gfx_vfio_enable: bool,
|
||||
pub active_profile: String,
|
||||
pub toggle_profiles: Vec<String>,
|
||||
#[serde(skip)]
|
||||
pub curr_fan_mode: u8,
|
||||
pub bat_charge_limit: u8,
|
||||
pub power_profiles: BTreeMap<String, ProfileV317>,
|
||||
}
|
||||
|
||||
impl ConfigV341 {
|
||||
pub(crate) fn into_current(self) -> Config {
|
||||
Config {
|
||||
gfx_mode: GfxVendors::Hybrid,
|
||||
gfx_tmp_mode: None,
|
||||
gfx_managed: self.gfx_managed,
|
||||
gfx_vfio_enable: false,
|
||||
active_profile: self.active_profile,
|
||||
toggle_profiles: self.toggle_profiles,
|
||||
curr_fan_mode: self.curr_fan_mode,
|
||||
bat_charge_limit: self.bat_charge_limit,
|
||||
power_profiles: ProfileV317::transform_map(self.power_profiles),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct ConfigV352 {
|
||||
pub gfx_mode: GfxVendors,
|
||||
pub gfx_last_mode: GfxVendors,
|
||||
pub gfx_managed: bool,
|
||||
pub gfx_vfio_enable: bool,
|
||||
pub gfx_save_compute_vfio: bool,
|
||||
pub active_profile: String,
|
||||
pub toggle_profiles: Vec<String>,
|
||||
#[serde(skip)]
|
||||
pub curr_fan_mode: u8,
|
||||
pub bat_charge_limit: u8,
|
||||
pub power_profiles: BTreeMap<String, ProfileV317>,
|
||||
}
|
||||
|
||||
impl ConfigV352 {
|
||||
pub(crate) fn into_current(self) -> Config {
|
||||
Config {
|
||||
gfx_mode: GfxVendors::Hybrid,
|
||||
gfx_tmp_mode: None,
|
||||
gfx_managed: self.gfx_managed,
|
||||
gfx_vfio_enable: false,
|
||||
active_profile: self.active_profile,
|
||||
toggle_profiles: self.toggle_profiles,
|
||||
curr_fan_mode: self.curr_fan_mode,
|
||||
bat_charge_limit: self.bat_charge_limit,
|
||||
power_profiles: ProfileV317::transform_map(self.power_profiles),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct ProfileV317 {
|
||||
pub min_percentage: u8,
|
||||
pub max_percentage: u8,
|
||||
pub turbo: bool,
|
||||
pub fan_preset: u8,
|
||||
pub fan_curve: Option<Curve>,
|
||||
}
|
||||
|
||||
impl ProfileV317 {
|
||||
fn into_current(self, name: String) -> Profile {
|
||||
Profile {
|
||||
name,
|
||||
min_percentage: self.min_percentage,
|
||||
max_percentage: self.max_percentage,
|
||||
turbo: self.turbo,
|
||||
fan_preset: self.fan_preset.into(),
|
||||
fan_curve: self
|
||||
.fan_curve
|
||||
.map_or_else(|| "".to_string(), |c| c.as_config_string()),
|
||||
}
|
||||
}
|
||||
|
||||
fn transform_map(map: BTreeMap<String, ProfileV317>) -> BTreeMap<String, Profile> {
|
||||
let mut new_map = BTreeMap::new();
|
||||
map.iter().for_each(|(k, v)| {
|
||||
new_map.insert(k.to_string(), v.clone().into_current(k.to_string()));
|
||||
});
|
||||
new_map
|
||||
}
|
||||
}
|
||||
@@ -1,467 +0,0 @@
|
||||
use log::{error, info, warn};
|
||||
use logind_zbus::ManagerProxy;
|
||||
use rog_anime::{
|
||||
usb::{
|
||||
pkt_for_apply, pkt_for_flush, pkt_for_set_boot, pkt_for_set_on, pkts_for_init, PROD_ID,
|
||||
VENDOR_ID,
|
||||
},
|
||||
ActionData, AnimTime, AnimeDataBuffer, AnimePacketType, AnimePowerStates, ANIME_DATA_LEN,
|
||||
};
|
||||
use rog_types::supported::AnimeSupportedFunctions;
|
||||
use rusb::{Device, DeviceHandle};
|
||||
use std::{
|
||||
error::Error,
|
||||
sync::{Arc, Mutex},
|
||||
thread::sleep,
|
||||
time::Instant,
|
||||
};
|
||||
use std::{
|
||||
sync::atomic::{AtomicBool, Ordering},
|
||||
time::Duration,
|
||||
};
|
||||
use zbus::{dbus_interface, Connection};
|
||||
use zvariant::ObjectPath;
|
||||
|
||||
use crate::{
|
||||
config_anime::{AnimeConfig, AnimeConfigCached},
|
||||
error::RogError,
|
||||
GetSupported,
|
||||
};
|
||||
|
||||
impl GetSupported for CtrlAnime {
|
||||
type A = AnimeSupportedFunctions;
|
||||
|
||||
fn get_supported() -> Self::A {
|
||||
AnimeSupportedFunctions(CtrlAnime::get_device(VENDOR_ID, PROD_ID).is_ok())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CtrlAnime {
|
||||
handle: DeviceHandle<rusb::GlobalContext>,
|
||||
cache: AnimeConfigCached,
|
||||
config: AnimeConfig,
|
||||
// set to force thread to exit
|
||||
thread_exit: Arc<AtomicBool>,
|
||||
// Set to false when the thread exits
|
||||
thread_running: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl CtrlAnime {
|
||||
#[inline]
|
||||
pub fn new(config: AnimeConfig) -> Result<CtrlAnime, Box<dyn Error>> {
|
||||
// We don't expect this ID to ever change
|
||||
let device = CtrlAnime::get_device(0x0b05, 0x193b)?;
|
||||
|
||||
let mut device = device.open()?;
|
||||
device.reset()?;
|
||||
|
||||
device.set_auto_detach_kernel_driver(true).map_err(|err| {
|
||||
error!("Auto-detach kernel driver failed: {}", err);
|
||||
err
|
||||
})?;
|
||||
|
||||
device.claim_interface(0).map_err(|err| {
|
||||
error!("Could not claim device interface: {}", err);
|
||||
err
|
||||
})?;
|
||||
|
||||
info!("Device has an AniMe Matrix display");
|
||||
let mut cache = AnimeConfigCached::default();
|
||||
cache.init_from_config(&config)?;
|
||||
|
||||
let ctrl = CtrlAnime {
|
||||
handle: device,
|
||||
cache,
|
||||
config,
|
||||
thread_exit: Arc::new(AtomicBool::new(false)),
|
||||
thread_running: Arc::new(AtomicBool::new(false)),
|
||||
};
|
||||
ctrl.do_initialization();
|
||||
|
||||
Ok(ctrl)
|
||||
}
|
||||
|
||||
fn get_device(vendor: u16, product: u16) -> Result<Device<rusb::GlobalContext>, rusb::Error> {
|
||||
for device in rusb::devices()?.iter() {
|
||||
let device_desc = device.device_descriptor()?;
|
||||
if device_desc.vendor_id() == vendor && device_desc.product_id() == product {
|
||||
return Ok(device);
|
||||
}
|
||||
}
|
||||
Err(rusb::Error::NoDevice)
|
||||
}
|
||||
|
||||
/// Start an action thread. This is classed as a singleton and there should be only
|
||||
/// one running - so the thread uses atomics to signal run/exit.
|
||||
///
|
||||
/// Because this also writes to the usb device, other write tries (display only) *must*
|
||||
/// get the mutex lock and set the thread_exit atomic.
|
||||
fn run_thread(inner: Arc<Mutex<CtrlAnime>>, actions: Vec<ActionData>, mut once: bool) {
|
||||
if actions.is_empty() {
|
||||
warn!("AniMe system actions was empty");
|
||||
return;
|
||||
}
|
||||
// Loop rules:
|
||||
// - Lock the mutex **only when required**. That is, the lock must be held for the shortest duration possible.
|
||||
// - An AtomicBool used for thread exit should be checked in every loop, including nested
|
||||
|
||||
// The only reason for this outer thread is to prevent blocking while waiting for the
|
||||
// next spawned thread to exit
|
||||
std::thread::Builder::new()
|
||||
.name("AniMe system thread start".into())
|
||||
.spawn(move || {
|
||||
info!("AniMe system thread started");
|
||||
// Getting copies of these Atomics is done *in* the thread to ensure
|
||||
// we don't block other threads/main
|
||||
let thread_exit;
|
||||
let thread_running;
|
||||
// First two loops are to ensure we *do* aquire a lock on the mutex
|
||||
// The reason the loop is required is because the USB writes can block
|
||||
// for up to 10ms. We can't fail to get the atomics.
|
||||
loop {
|
||||
if let Ok(lock) = inner.try_lock() {
|
||||
thread_exit = lock.thread_exit.clone();
|
||||
thread_running = lock.thread_running.clone();
|
||||
// Make any running loop exit first
|
||||
thread_exit.store(true, Ordering::SeqCst);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
loop {
|
||||
// wait for other threads to set not running so we know they exited
|
||||
if !thread_running.load(Ordering::SeqCst) {
|
||||
thread_exit.store(false, Ordering::SeqCst);
|
||||
info!("AniMe forced a thread to exit");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
'main: loop {
|
||||
if thread_exit.load(Ordering::SeqCst) {
|
||||
break 'main;
|
||||
}
|
||||
for action in actions.iter() {
|
||||
match action {
|
||||
ActionData::Animation(frames) => {
|
||||
let mut count = 0;
|
||||
let start = Instant::now();
|
||||
'animation: loop {
|
||||
for frame in frames.frames() {
|
||||
if let Ok(lock) = inner.try_lock() {
|
||||
lock.write_data_buffer(frame.frame().clone());
|
||||
}
|
||||
if let AnimTime::Time(time) = frames.duration() {
|
||||
if Instant::now().duration_since(start) > time {
|
||||
break 'animation;
|
||||
}
|
||||
}
|
||||
sleep(frame.delay());
|
||||
// Need to check for early exit condition here or it might run
|
||||
// until end of gif or time
|
||||
if thread_exit.load(Ordering::SeqCst) {
|
||||
break 'main;
|
||||
}
|
||||
}
|
||||
if let AnimTime::Cycles(times) = frames.duration() {
|
||||
count += 1;
|
||||
if count >= times {
|
||||
break 'animation;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ActionData::Image(image) => {
|
||||
once = false;
|
||||
if let Ok(lock) = inner.try_lock() {
|
||||
lock.write_data_buffer(image.as_ref().clone())
|
||||
}
|
||||
}
|
||||
ActionData::Pause(duration) => sleep(*duration),
|
||||
ActionData::AudioEq => {}
|
||||
ActionData::SystemInfo => {}
|
||||
ActionData::TimeDate => {}
|
||||
ActionData::Matrix => {}
|
||||
}
|
||||
}
|
||||
if once || actions.is_empty() {
|
||||
break 'main;
|
||||
}
|
||||
}
|
||||
// Clear the display on exit
|
||||
if let Ok(lock) = inner.try_lock() {
|
||||
let data = AnimeDataBuffer::from_vec([0u8; ANIME_DATA_LEN].to_vec());
|
||||
lock.write_data_buffer(data);
|
||||
}
|
||||
// Loop ended, set the atmonics
|
||||
thread_exit.store(false, Ordering::SeqCst);
|
||||
thread_running.store(false, Ordering::SeqCst);
|
||||
info!("AniMe system thread exited");
|
||||
})
|
||||
.map(|err| info!("AniMe system thread: {:?}", err))
|
||||
.ok();
|
||||
}
|
||||
|
||||
fn write_bytes(&self, message: &[u8]) {
|
||||
match self.handle.write_control(
|
||||
0x21, // request_type
|
||||
0x09, // request
|
||||
0x35e, // value
|
||||
0x00, // index
|
||||
message,
|
||||
Duration::from_millis(200),
|
||||
) {
|
||||
Ok(_) => {}
|
||||
Err(err) => match err {
|
||||
rusb::Error::Timeout => {}
|
||||
_ => error!("Failed to write to led interrupt: {}", err),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Write only a data packet. This will modify the leds brightness using the
|
||||
/// global brightness set in config.
|
||||
fn write_data_buffer(&self, mut buffer: AnimeDataBuffer) {
|
||||
for led in buffer.get_mut()[7..].iter_mut() {
|
||||
let mut bright = *led as f32 * self.config.brightness;
|
||||
if bright > 254.0 {
|
||||
bright = 254.0;
|
||||
}
|
||||
*led = bright as u8;
|
||||
}
|
||||
let data = AnimePacketType::from(buffer);
|
||||
for row in data.iter() {
|
||||
self.write_bytes(row);
|
||||
}
|
||||
self.write_bytes(&pkt_for_flush());
|
||||
}
|
||||
|
||||
fn do_initialization(&self) {
|
||||
let pkts = pkts_for_init();
|
||||
self.write_bytes(&pkts[0]);
|
||||
self.write_bytes(&pkts[1]);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CtrlAnimeTask<'a> {
|
||||
inner: Arc<Mutex<CtrlAnime>>,
|
||||
_c: Connection,
|
||||
manager: ManagerProxy<'a>,
|
||||
}
|
||||
|
||||
impl<'a> CtrlAnimeTask<'a> {
|
||||
pub fn new(inner: Arc<Mutex<CtrlAnime>>) -> Self {
|
||||
let connection = Connection::new_system().unwrap();
|
||||
|
||||
let manager = ManagerProxy::new(&connection).unwrap();
|
||||
|
||||
let c1 = inner.clone();
|
||||
// Run this action when the system starts shutting down
|
||||
manager
|
||||
.connect_prepare_for_shutdown(move |shutdown| {
|
||||
if shutdown {
|
||||
'outer: loop {
|
||||
if let Ok(lock) = c1.try_lock() {
|
||||
lock.thread_exit.store(true, Ordering::SeqCst);
|
||||
CtrlAnime::run_thread(c1.clone(), lock.cache.shutdown.clone(), false);
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.map_err(|err| {
|
||||
warn!("CtrlAnimeTask: new() {}", err);
|
||||
err
|
||||
})
|
||||
.ok();
|
||||
|
||||
let c1 = inner.clone();
|
||||
// Run this action when the system wakes up from sleep
|
||||
manager
|
||||
.connect_prepare_for_sleep(move |sleep| {
|
||||
if !sleep {
|
||||
// wait a fraction for things to wake up properly
|
||||
std::thread::sleep(Duration::from_millis(100));
|
||||
'outer: loop {
|
||||
if let Ok(lock) = c1.try_lock() {
|
||||
lock.thread_exit.store(true, Ordering::SeqCst);
|
||||
CtrlAnime::run_thread(c1.clone(), lock.cache.wake.clone(), true);
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.map_err(|err| {
|
||||
warn!("CtrlAnimeTask: new() {}", err);
|
||||
err
|
||||
})
|
||||
.ok();
|
||||
|
||||
Self {
|
||||
inner,
|
||||
_c: connection,
|
||||
manager,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> crate::CtrlTask for CtrlAnimeTask<'a> {
|
||||
fn do_task(&self) -> Result<(), RogError> {
|
||||
if let Ok(mut lock) = self.inner.try_lock() {
|
||||
// Refresh the config and cache incase the user has edited it
|
||||
let config = AnimeConfig::load();
|
||||
lock.cache
|
||||
.init_from_config(&config)
|
||||
.map_err(|err| {
|
||||
warn!("CtrlAnimeTask: do_task {}", err);
|
||||
err
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
// Check for signals on each task iteration, this will run the callbacks
|
||||
// if any signal is recieved
|
||||
self.manager.next_signal()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CtrlAnimeReloader(pub Arc<Mutex<CtrlAnime>>);
|
||||
|
||||
impl crate::Reloadable for CtrlAnimeReloader {
|
||||
fn reload(&mut self) -> Result<(), RogError> {
|
||||
if let Ok(lock) = self.0.try_lock() {
|
||||
lock.write_bytes(&pkt_for_set_on(lock.config.awake_enabled));
|
||||
lock.write_bytes(&pkt_for_apply());
|
||||
lock.write_bytes(&pkt_for_set_boot(lock.config.boot_anim_enabled));
|
||||
lock.write_bytes(&pkt_for_apply());
|
||||
|
||||
let action = lock.cache.boot.clone();
|
||||
CtrlAnime::run_thread(self.0.clone(), action, true);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CtrlAnimeZbus(pub Arc<Mutex<CtrlAnime>>);
|
||||
|
||||
/// The struct with the main dbus methods requires this trait
|
||||
impl crate::ZbusAdd for CtrlAnimeZbus {
|
||||
fn add_to_server(self, server: &mut zbus::ObjectServer) {
|
||||
server
|
||||
.at(
|
||||
&ObjectPath::from_str_unchecked("/org/asuslinux/Anime"),
|
||||
self,
|
||||
)
|
||||
.map_err(|err| {
|
||||
warn!("CtrlAnimeDisplay: add_to_server {}", err);
|
||||
err
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
// None of these calls can be guarnateed to succeed unless we loop until okay
|
||||
// If the try_lock *does* succeed then any other thread trying to lock will not grab it
|
||||
// until we finish.
|
||||
#[dbus_interface(name = "org.asuslinux.Daemon")]
|
||||
impl CtrlAnimeZbus {
|
||||
/// Writes a data stream of length. Will force system thread to exit until it is restarted
|
||||
fn write(&self, input: AnimeDataBuffer) {
|
||||
'outer: loop {
|
||||
if let Ok(lock) = self.0.try_lock() {
|
||||
lock.thread_exit.store(true, Ordering::SeqCst);
|
||||
lock.write_data_buffer(input);
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_brightness(&self, bright: f32) {
|
||||
'outer: loop {
|
||||
if let Ok(mut lock) = self.0.try_lock() {
|
||||
let mut bright = bright;
|
||||
if bright < 0.0 {
|
||||
bright = 0.0
|
||||
} else if bright > 254.0 {
|
||||
bright = 254.0;
|
||||
}
|
||||
lock.config.brightness = bright;
|
||||
lock.config.write();
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_on_off(&self, status: bool) {
|
||||
'outer: loop {
|
||||
if let Ok(mut lock) = self.0.try_lock() {
|
||||
lock.write_bytes(&pkt_for_set_on(status));
|
||||
lock.config.awake_enabled = status;
|
||||
lock.config.write();
|
||||
|
||||
let states = AnimePowerStates {
|
||||
enabled: lock.config.awake_enabled,
|
||||
boot_anim_enabled: lock.config.boot_anim_enabled,
|
||||
};
|
||||
self.notify_power_states(&states)
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_boot_on_off(&self, on: bool) {
|
||||
'outer: loop {
|
||||
if let Ok(mut lock) = self.0.try_lock() {
|
||||
lock.write_bytes(&pkt_for_set_boot(on));
|
||||
lock.write_bytes(&pkt_for_apply());
|
||||
lock.config.boot_anim_enabled = on;
|
||||
lock.config.write();
|
||||
|
||||
let states = AnimePowerStates {
|
||||
enabled: lock.config.awake_enabled,
|
||||
boot_anim_enabled: lock.config.boot_anim_enabled,
|
||||
};
|
||||
self.notify_power_states(&states)
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The main loop is the base system set action if the user isn't running
|
||||
/// the user daemon
|
||||
fn run_main_loop(&self, start: bool) {
|
||||
if start {
|
||||
'outer: loop {
|
||||
if let Ok(lock) = self.0.try_lock() {
|
||||
lock.thread_exit.store(true, Ordering::SeqCst);
|
||||
CtrlAnime::run_thread(self.0.clone(), lock.cache.system.clone(), false);
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[dbus_interface(property)]
|
||||
fn awake_enabled(&self) -> bool {
|
||||
if let Ok(ctrl) = self.0.try_lock() {
|
||||
return ctrl.config.awake_enabled;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
#[dbus_interface(property)]
|
||||
fn boot_enabled(&self) -> bool {
|
||||
if let Ok(ctrl) = self.0.try_lock() {
|
||||
return ctrl.config.boot_anim_enabled;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
#[dbus_interface(signal)]
|
||||
fn notify_power_states(&self, data: &AnimePowerStates) -> zbus::Result<()>;
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
use crate::{config::Config, error::RogError, GetSupported};
|
||||
//use crate::dbus::DbusEvents;
|
||||
use log::{info, warn};
|
||||
use rog_types::supported::ChargeSupportedFunctions;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use zbus::dbus_interface;
|
||||
use zvariant::ObjectPath;
|
||||
|
||||
static BAT_CHARGE_PATH: &str = "/sys/class/power_supply/BAT0/charge_control_end_threshold";
|
||||
|
||||
impl GetSupported for CtrlCharge {
|
||||
type A = ChargeSupportedFunctions;
|
||||
|
||||
fn get_supported() -> Self::A {
|
||||
ChargeSupportedFunctions {
|
||||
charge_level_set: CtrlCharge::get_battery_path().is_ok(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CtrlCharge {
|
||||
config: Arc<Mutex<Config>>,
|
||||
}
|
||||
|
||||
#[dbus_interface(name = "org.asuslinux.Daemon")]
|
||||
impl CtrlCharge {
|
||||
pub fn set_limit(&mut self, limit: u8) {
|
||||
if let Ok(mut config) = self.config.try_lock() {
|
||||
self.set(limit, &mut config)
|
||||
.map_err(|err| {
|
||||
warn!("CtrlCharge: set_limit {}", err);
|
||||
err
|
||||
})
|
||||
.ok();
|
||||
self.notify_charge(limit)
|
||||
.map_err(|err| {
|
||||
warn!("CtrlCharge: set_limit {}", err);
|
||||
err
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn limit(&self) -> i8 {
|
||||
if let Ok(config) = self.config.try_lock() {
|
||||
return config.bat_charge_limit as i8;
|
||||
}
|
||||
-1
|
||||
}
|
||||
|
||||
#[dbus_interface(signal)]
|
||||
pub fn notify_charge(&self, limit: u8) -> zbus::Result<()> {}
|
||||
}
|
||||
|
||||
impl crate::ZbusAdd for CtrlCharge {
|
||||
fn add_to_server(self, server: &mut zbus::ObjectServer) {
|
||||
server
|
||||
.at(
|
||||
&ObjectPath::from_str_unchecked("/org/asuslinux/Charge"),
|
||||
self,
|
||||
)
|
||||
.map_err(|err| {
|
||||
warn!("CtrlCharge: add_to_server {}", err);
|
||||
err
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::Reloadable for CtrlCharge {
|
||||
fn reload(&mut self) -> Result<(), RogError> {
|
||||
if let Ok(mut config) = self.config.try_lock() {
|
||||
config.read();
|
||||
self.set(config.bat_charge_limit, &mut config)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl CtrlCharge {
|
||||
pub fn new(config: Arc<Mutex<Config>>) -> Result<Self, RogError> {
|
||||
CtrlCharge::get_battery_path()?;
|
||||
Ok(CtrlCharge { config })
|
||||
}
|
||||
|
||||
fn get_battery_path() -> Result<&'static str, RogError> {
|
||||
if Path::new(BAT_CHARGE_PATH).exists() {
|
||||
Ok(BAT_CHARGE_PATH)
|
||||
} else {
|
||||
Err(RogError::MissingFunction(
|
||||
"Charge control not available, you may require a v5.8.10 series kernel or newer"
|
||||
.into(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn set(&self, limit: u8, config: &mut Config) -> Result<(), RogError> {
|
||||
if !(20..=100).contains(&limit) {
|
||||
warn!(
|
||||
"Unable to set battery charge limit, must be between 20-100: requested {}",
|
||||
limit
|
||||
);
|
||||
}
|
||||
|
||||
let mut file = OpenOptions::new()
|
||||
.write(true)
|
||||
.open(BAT_CHARGE_PATH)
|
||||
.map_err(|err| RogError::Path(BAT_CHARGE_PATH.into(), err))?;
|
||||
file.write_all(limit.to_string().as_bytes())
|
||||
.map_err(|err| RogError::Write(BAT_CHARGE_PATH.into(), err))?;
|
||||
info!("Battery charge limit: {}", limit);
|
||||
|
||||
config.read();
|
||||
config.bat_charge_limit = limit;
|
||||
config.write();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,642 +0,0 @@
|
||||
use ::zbus::Connection;
|
||||
use ctrl_gfx::error::GfxError;
|
||||
use ctrl_gfx::*;
|
||||
use ctrl_rog_bios::CtrlRogBios;
|
||||
use log::{error, info, warn};
|
||||
use logind_zbus::{
|
||||
types::{SessionClass, SessionInfo, SessionState, SessionType},
|
||||
ManagerProxy, SessionProxy,
|
||||
};
|
||||
use rog_types::gfx_vendors::{GfxPower, GfxRequiredUserAction, GfxVendors};
|
||||
use std::{io::Write, ops::Add, path::Path, time::Instant};
|
||||
use std::{iter::FromIterator, thread::JoinHandle};
|
||||
use std::{process::Command, thread::sleep, time::Duration};
|
||||
use std::{str::FromStr, sync::mpsc};
|
||||
use std::{sync::Arc, sync::Mutex};
|
||||
use sysfs_class::{PciDevice, SysClass};
|
||||
use system::{GraphicsDevice, PciBus};
|
||||
|
||||
use crate::*;
|
||||
|
||||
const THREAD_TIMEOUT_MSG: &str = "GFX: thread time exceeded 3 minutes, exiting";
|
||||
|
||||
pub struct CtrlGraphics {
|
||||
bus: PciBus,
|
||||
_amd: Vec<GraphicsDevice>,
|
||||
_intel: Vec<GraphicsDevice>,
|
||||
nvidia: Vec<GraphicsDevice>,
|
||||
#[allow(dead_code)]
|
||||
other: Vec<GraphicsDevice>,
|
||||
config: Arc<Mutex<Config>>,
|
||||
thread_kill: Arc<Mutex<Option<mpsc::Sender<bool>>>>,
|
||||
}
|
||||
|
||||
impl Reloadable for CtrlGraphics {
|
||||
fn reload(&mut self) -> Result<(), RogError> {
|
||||
self.auto_power()?;
|
||||
info!("GFX: Reloaded gfx mode: {:?}", self.get_gfx_mode()?);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl CtrlGraphics {
|
||||
pub fn new(config: Arc<Mutex<Config>>) -> std::io::Result<CtrlGraphics> {
|
||||
let bus = PciBus::new()?;
|
||||
info!("GFX: Rescanning PCI bus");
|
||||
bus.rescan()?;
|
||||
let devs = PciDevice::all()?;
|
||||
|
||||
let functions = |parent: &PciDevice| -> Vec<PciDevice> {
|
||||
let mut functions = Vec::new();
|
||||
if let Some(parent_slot) = parent.id().split('.').next() {
|
||||
for func in devs.iter() {
|
||||
if let Some(func_slot) = func.id().split('.').next() {
|
||||
if func_slot == parent_slot {
|
||||
info!("GFX: {}: Function for {}", func.id(), parent.id());
|
||||
functions.push(func.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
functions
|
||||
};
|
||||
|
||||
let mut amd = Vec::new();
|
||||
let mut intel = Vec::new();
|
||||
let mut nvidia = Vec::new();
|
||||
let mut other = Vec::new();
|
||||
for dev in devs.iter() {
|
||||
let c = dev.class()?;
|
||||
if 0x03 == (c >> 16) & 0xFF {
|
||||
match dev.vendor()? {
|
||||
0x1002 => {
|
||||
info!("GFX: {}: AMD graphics", dev.id());
|
||||
amd.push(GraphicsDevice::new(dev.id().to_owned(), functions(&dev)));
|
||||
}
|
||||
0x10DE => {
|
||||
info!("GFX: {}: NVIDIA graphics", dev.id());
|
||||
nvidia.push(GraphicsDevice::new(dev.id().to_owned(), functions(&dev)));
|
||||
}
|
||||
0x8086 => {
|
||||
info!("GFX: {}: Intel graphics", dev.id());
|
||||
intel.push(GraphicsDevice::new(dev.id().to_owned(), functions(&dev)));
|
||||
}
|
||||
vendor => {
|
||||
info!("GFX: {}: Other({:X}) graphics", dev.id(), vendor);
|
||||
other.push(GraphicsDevice::new(dev.id().to_owned(), functions(&dev)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(CtrlGraphics {
|
||||
bus,
|
||||
_amd: amd,
|
||||
_intel: intel,
|
||||
nvidia,
|
||||
other,
|
||||
config,
|
||||
thread_kill: Arc::new(Mutex::new(None)),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn bus(&self) -> PciBus {
|
||||
self.bus.clone()
|
||||
}
|
||||
|
||||
pub fn devices(&self) -> Vec<GraphicsDevice> {
|
||||
self.nvidia.clone()
|
||||
}
|
||||
|
||||
/// Save the selected `Vendor` mode to config
|
||||
fn save_gfx_mode(vendor: GfxVendors, config: Arc<Mutex<Config>>) {
|
||||
if let Ok(mut config) = config.lock() {
|
||||
config.gfx_mode = vendor;
|
||||
config.write();
|
||||
}
|
||||
}
|
||||
|
||||
/// Associated method to get which vendor mode is set
|
||||
pub fn get_gfx_mode(&self) -> Result<GfxVendors, RogError> {
|
||||
if let Ok(config) = self.config.lock() {
|
||||
if let Some(mode) = config.gfx_tmp_mode {
|
||||
return Ok(mode);
|
||||
}
|
||||
return Ok(config.gfx_mode);
|
||||
}
|
||||
// TODO: Error here
|
||||
Ok(GfxVendors::Hybrid)
|
||||
}
|
||||
|
||||
pub(super) fn get_runtime_status() -> Result<GfxPower, RogError> {
|
||||
let path = Path::new("/sys/bus/pci/devices/0000:01:00.0/power/runtime_status");
|
||||
if path.exists() {
|
||||
let buf = std::fs::read_to_string(path).map_err(|err| {
|
||||
RogError::Read(
|
||||
"/sys/bus/pci/devices/0000:01:00.0/power/runtime_status".to_string(),
|
||||
err,
|
||||
)
|
||||
})?;
|
||||
Ok(GfxPower::from_str(&buf)?)
|
||||
} else {
|
||||
Ok(GfxPower::Off)
|
||||
}
|
||||
}
|
||||
|
||||
/// Some systems have a fallback service to load nouveau if nvidia fails
|
||||
fn toggle_fallback_service(vendor: GfxVendors) -> Result<(), RogError> {
|
||||
let action = if vendor == GfxVendors::Nvidia {
|
||||
info!("GFX: Enabling nvidia-fallback.service");
|
||||
"enable"
|
||||
} else {
|
||||
info!("GFX: Disabling nvidia-fallback.service");
|
||||
"disable"
|
||||
};
|
||||
|
||||
let status = Command::new("systemctl")
|
||||
.arg(action)
|
||||
.arg("nvidia-fallback.service")
|
||||
.status()
|
||||
.map_err(|err| RogError::Command("systemctl".into(), err))?;
|
||||
|
||||
if !status.success() {
|
||||
// Error is ignored in case this service is removed
|
||||
warn!(
|
||||
"systemctl: {} (ignore warning if service does not exist!)",
|
||||
status
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write the appropriate xorg config for the chosen mode
|
||||
fn write_xorg_conf(vendor: GfxVendors) -> Result<(), RogError> {
|
||||
let text = if vendor == GfxVendors::Nvidia {
|
||||
[PRIMARY_GPU_BEGIN, PRIMARY_GPU_NVIDIA, PRIMARY_GPU_END].concat()
|
||||
} else {
|
||||
[PRIMARY_GPU_BEGIN, PRIMARY_GPU_END].concat()
|
||||
};
|
||||
|
||||
if !Path::new(XORG_PATH).exists() {
|
||||
std::fs::create_dir(XORG_PATH).map_err(|err| RogError::Write(XORG_PATH.into(), err))?;
|
||||
}
|
||||
|
||||
let file = XORG_PATH.to_string().add(XORG_FILE);
|
||||
info!("GFX: Writing {}", file);
|
||||
let mut file = std::fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.write(true)
|
||||
.open(&file)
|
||||
.map_err(|err| RogError::Write(file, err))?;
|
||||
|
||||
file.write_all(&text)
|
||||
.and_then(|_| file.sync_all())
|
||||
.map_err(|err| RogError::Write(MODPROBE_PATH.into(), err))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Creates the full modprobe.conf required for vfio pass-through
|
||||
fn get_vfio_conf(devices: &[GraphicsDevice]) -> Vec<u8> {
|
||||
let mut vifo = MODPROBE_VFIO.to_vec();
|
||||
for (d_count, dev) in devices.iter().enumerate() {
|
||||
for (f_count, func) in dev.functions().iter().enumerate() {
|
||||
let vendor = func.vendor().unwrap();
|
||||
let device = func.device().unwrap();
|
||||
unsafe {
|
||||
vifo.append(format!("{:x}", vendor).as_mut_vec());
|
||||
}
|
||||
vifo.append(&mut vec![b':']);
|
||||
unsafe {
|
||||
vifo.append(format!("{:x}", device).as_mut_vec());
|
||||
}
|
||||
if f_count < dev.functions().len() - 1 {
|
||||
vifo.append(&mut vec![b',']);
|
||||
}
|
||||
}
|
||||
if d_count < dev.functions().len() - 1 {
|
||||
vifo.append(&mut vec![b',']);
|
||||
}
|
||||
}
|
||||
let mut conf = MODPROBE_INTEGRATED.to_vec();
|
||||
conf.append(&mut vifo);
|
||||
conf
|
||||
}
|
||||
|
||||
fn write_modprobe_conf(vendor: GfxVendors, devices: &[GraphicsDevice]) -> Result<(), RogError> {
|
||||
info!("GFX: Writing {}", MODPROBE_PATH);
|
||||
let content = match vendor {
|
||||
GfxVendors::Nvidia | GfxVendors::Hybrid => {
|
||||
let mut base = MODPROBE_BASE.to_vec();
|
||||
base.append(&mut MODPROBE_DRM_MODESET.to_vec());
|
||||
base
|
||||
}
|
||||
GfxVendors::Vfio => Self::get_vfio_conf(devices),
|
||||
GfxVendors::Integrated => MODPROBE_INTEGRATED.to_vec(),
|
||||
GfxVendors::Compute => MODPROBE_BASE.to_vec(),
|
||||
};
|
||||
|
||||
let mut file = std::fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.write(true)
|
||||
.open(MODPROBE_PATH)
|
||||
.map_err(|err| RogError::Path(MODPROBE_PATH.into(), err))?;
|
||||
|
||||
file.write_all(&content)
|
||||
.and_then(|_| file.sync_all())
|
||||
.map_err(|err| RogError::Write(MODPROBE_PATH.into(), err))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unbind_remove_nvidia(devices: &[GraphicsDevice]) -> Result<(), RogError> {
|
||||
// Unbind NVIDIA graphics devices and their functions
|
||||
let unbinds = devices.iter().map(|dev| dev.unbind());
|
||||
// Remove NVIDIA graphics devices and their functions
|
||||
let removes = devices.iter().map(|dev| dev.remove());
|
||||
Result::from_iter(unbinds.chain(removes))
|
||||
.map_err(|err| RogError::Command("device unbind error".into(), err))
|
||||
}
|
||||
|
||||
fn unbind_only(devices: &[GraphicsDevice]) -> Result<(), RogError> {
|
||||
let unbinds = devices.iter().map(|dev| dev.unbind());
|
||||
Result::from_iter(unbinds)
|
||||
.map_err(|err| RogError::Command("device unbind error".into(), err))
|
||||
}
|
||||
|
||||
fn do_driver_action(driver: &str, action: &str) -> Result<(), GfxError> {
|
||||
let mut cmd = Command::new(action);
|
||||
cmd.arg(driver);
|
||||
|
||||
let mut count = 0;
|
||||
const MAX_TRIES: i32 = 6;
|
||||
loop {
|
||||
if count > MAX_TRIES {
|
||||
let msg = format!("{} {} failed for unknown reason", action, driver);
|
||||
error!("GFX: {}", msg);
|
||||
return Ok(()); //Err(RogError::Modprobe(msg));
|
||||
}
|
||||
|
||||
let output = cmd
|
||||
.output()
|
||||
.map_err(|err| GfxError::Command(format!("{:?}", cmd), err))?;
|
||||
if !output.status.success() {
|
||||
if output
|
||||
.stderr
|
||||
.ends_with("is not currently loaded\n".as_bytes())
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
if output.stderr.ends_with("is builtin.\n".as_bytes()) {
|
||||
return Err(GfxError::VfioBuiltin);
|
||||
}
|
||||
if output.stderr.ends_with("Permission denied\n".as_bytes()) {
|
||||
warn!(
|
||||
"{} {} failed: {:?}",
|
||||
action,
|
||||
driver,
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
warn!("GFX: It may be safe to ignore the above error, run `lsmod |grep {}` to confirm modules loaded", driver);
|
||||
return Ok(());
|
||||
}
|
||||
if String::from_utf8_lossy(&output.stderr)
|
||||
.contains(&format!("Module {} not found", driver))
|
||||
{
|
||||
return Err(GfxError::MissingModule(driver.into()));
|
||||
}
|
||||
if count >= MAX_TRIES {
|
||||
let msg = format!(
|
||||
"{} {} failed: {:?}",
|
||||
action,
|
||||
driver,
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
return Err(GfxError::Modprobe(msg));
|
||||
}
|
||||
} else if output.status.success() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
count += 1;
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
}
|
||||
}
|
||||
|
||||
fn do_display_manager_action(action: &str) -> Result<(), RogError> {
|
||||
let mut cmd = Command::new("systemctl");
|
||||
cmd.arg(action);
|
||||
cmd.arg(DISPLAY_MANAGER);
|
||||
|
||||
let status = cmd
|
||||
.status()
|
||||
.map_err(|err| RogError::Command(format!("{:?}", cmd), err))?;
|
||||
if !status.success() {
|
||||
let msg = format!(
|
||||
"systemctl {} {} failed: {:?}",
|
||||
action, DISPLAY_MANAGER, status
|
||||
);
|
||||
return Err(GfxError::DisplayManagerAction(msg, status).into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn wait_display_manager_state(state: &str) -> Result<(), RogError> {
|
||||
let mut cmd = Command::new("systemctl");
|
||||
cmd.arg("is-active");
|
||||
cmd.arg(DISPLAY_MANAGER);
|
||||
|
||||
let mut count = 0;
|
||||
|
||||
while count <= 5 {
|
||||
let output = cmd
|
||||
.output()
|
||||
.map_err(|err| RogError::Command(format!("{:?}", cmd), err))?;
|
||||
if output.stdout.starts_with(state.as_bytes()) {
|
||||
return Ok(());
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||
count += 1;
|
||||
}
|
||||
Err(GfxError::DisplayManagerTimeout(state.into()).into())
|
||||
}
|
||||
|
||||
/// Determine if we need to logout/thread. Integrated<->Vfio mode does not
|
||||
/// require logout.
|
||||
fn logout_required(&self, vendor: GfxVendors) -> GfxRequiredUserAction {
|
||||
if let Ok(config) = self.config.lock() {
|
||||
let current = config.gfx_mode;
|
||||
if matches!(
|
||||
current,
|
||||
GfxVendors::Integrated | GfxVendors::Vfio | GfxVendors::Compute
|
||||
) && matches!(
|
||||
vendor,
|
||||
GfxVendors::Integrated | GfxVendors::Vfio | GfxVendors::Compute
|
||||
) {
|
||||
return GfxRequiredUserAction::None;
|
||||
}
|
||||
}
|
||||
GfxRequiredUserAction::Logout
|
||||
}
|
||||
|
||||
/// Write the config changes and add/remove drivers and devices depending
|
||||
/// on selected mode:
|
||||
///
|
||||
/// Tasks:
|
||||
/// - write xorg config
|
||||
/// - write modprobe config
|
||||
/// - rescan for devices
|
||||
/// + add drivers
|
||||
/// + or remove drivers and devices
|
||||
///
|
||||
/// The daemon needs direct access to this function when it detects that the
|
||||
pub fn do_vendor_tasks(
|
||||
vendor: GfxVendors,
|
||||
vfio_enable: bool,
|
||||
devices: &[GraphicsDevice],
|
||||
bus: &PciBus,
|
||||
) -> Result<(), RogError> {
|
||||
// Rescan before doing remove or add drivers
|
||||
bus.rescan()?;
|
||||
//
|
||||
Self::write_xorg_conf(vendor)?;
|
||||
// Write different modprobe to enable boot control to work
|
||||
Self::write_modprobe_conf(vendor, devices)?;
|
||||
|
||||
match vendor {
|
||||
GfxVendors::Nvidia | GfxVendors::Hybrid | GfxVendors::Compute => {
|
||||
if vfio_enable {
|
||||
for driver in VFIO_DRIVERS.iter() {
|
||||
Self::do_driver_action(driver, "rmmod")?;
|
||||
}
|
||||
}
|
||||
for driver in NVIDIA_DRIVERS.iter() {
|
||||
Self::do_driver_action(driver, "modprobe")?;
|
||||
}
|
||||
}
|
||||
GfxVendors::Vfio => {
|
||||
if vfio_enable {
|
||||
Self::do_driver_action("nouveau", "rmmod")?;
|
||||
for driver in NVIDIA_DRIVERS.iter() {
|
||||
Self::do_driver_action(driver, "rmmod")?;
|
||||
}
|
||||
Self::unbind_only(&devices)?;
|
||||
Self::do_driver_action("vfio-pci", "modprobe")?;
|
||||
} else {
|
||||
return Err(GfxError::VfioDisabled.into());
|
||||
}
|
||||
}
|
||||
GfxVendors::Integrated => {
|
||||
Self::do_driver_action("nouveau", "rmmod")?;
|
||||
if vfio_enable {
|
||||
for driver in VFIO_DRIVERS.iter() {
|
||||
Self::do_driver_action(driver, "rmmod")?;
|
||||
}
|
||||
}
|
||||
for driver in NVIDIA_DRIVERS.iter() {
|
||||
Self::do_driver_action(driver, "rmmod")?;
|
||||
}
|
||||
Self::unbind_remove_nvidia(&devices)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check if the user has any graphical uiser sessions that are active or online
|
||||
fn graphical_user_sessions_exist(
|
||||
connection: &Connection,
|
||||
sessions: &[SessionInfo],
|
||||
) -> Result<bool, RogError> {
|
||||
for session in sessions {
|
||||
let session_proxy = SessionProxy::new(connection, session)?;
|
||||
if session_proxy.get_class()? == SessionClass::User {
|
||||
match session_proxy.get_type()? {
|
||||
SessionType::X11 | SessionType::Wayland | SessionType::MIR => {
|
||||
match session_proxy.get_state()? {
|
||||
SessionState::Online | SessionState::Active => return Ok(true),
|
||||
SessionState::Closing | SessionState::Invalid => {}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
/// Spools until all user sessions are ended then switches to requested mode
|
||||
fn fire_starter(
|
||||
vendor: GfxVendors,
|
||||
devices: Vec<GraphicsDevice>,
|
||||
bus: PciBus,
|
||||
thread_stop: mpsc::Receiver<bool>,
|
||||
config: Arc<Mutex<Config>>,
|
||||
) -> Result<String, RogError> {
|
||||
info!("GFX: display-manager thread started");
|
||||
|
||||
const SLEEP_PERIOD: Duration = Duration::from_millis(100);
|
||||
let start_time = Instant::now();
|
||||
|
||||
let connection = Connection::new_system()?;
|
||||
let manager = ManagerProxy::new(&connection)?;
|
||||
let mut sessions = manager.list_sessions()?;
|
||||
|
||||
loop {
|
||||
let tmp = manager.list_sessions()?;
|
||||
if !tmp.iter().eq(&sessions) {
|
||||
info!("GFX thread: Sessions list changed");
|
||||
sessions = tmp;
|
||||
}
|
||||
|
||||
if !Self::graphical_user_sessions_exist(&connection, &sessions)? {
|
||||
break;
|
||||
}
|
||||
|
||||
if let Ok(stop) = thread_stop.try_recv() {
|
||||
if stop {
|
||||
return Ok("Graphics mode change was cancelled".into());
|
||||
}
|
||||
}
|
||||
// exit if 3 minutes pass
|
||||
if Instant::now().duration_since(start_time).as_secs() > 180 {
|
||||
warn!("{}", THREAD_TIMEOUT_MSG);
|
||||
return Ok(THREAD_TIMEOUT_MSG.into());
|
||||
}
|
||||
|
||||
// Don't spin at max speed
|
||||
sleep(SLEEP_PERIOD);
|
||||
}
|
||||
|
||||
info!("GFX thread: all graphical user sessions ended, continuing");
|
||||
Self::do_display_manager_action("stop")?;
|
||||
Self::wait_display_manager_state("inactive")?;
|
||||
|
||||
let vfio_enable = if let Ok(mut config) = config.try_lock() {
|
||||
// Since we have a lock, reset tmp to none. This thread should only ever run
|
||||
// for Integrated, Hybrid, or Nvidia. Tmp is also only for informational
|
||||
config.gfx_tmp_mode = None;
|
||||
//
|
||||
config.gfx_vfio_enable
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
Self::do_vendor_tasks(vendor, vfio_enable, &devices, &bus)?;
|
||||
Self::do_display_manager_action("restart")?;
|
||||
// Save selected mode in case of reboot
|
||||
Self::save_gfx_mode(vendor, config);
|
||||
info!("GFX thread: display-manager started");
|
||||
|
||||
let v: &str = vendor.into();
|
||||
info!("GFX thread: Graphics mode changed to {} successfully", v);
|
||||
Ok(format!("Graphics mode changed to {} successfully", v))
|
||||
}
|
||||
|
||||
/// Before starting a new thread the old one *must* be cancelled
|
||||
fn cancel_thread(&self) {
|
||||
if let Ok(lock) = self.thread_kill.lock() {
|
||||
if let Some(tx) = lock.as_ref() {
|
||||
// Cancel the running thread
|
||||
info!("GFX: Cancelling previous thread");
|
||||
tx.send(true)
|
||||
.map_err(|err| {
|
||||
warn!("GFX thread: {}", err);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The thread is used only in cases where a logout is required
|
||||
fn setup_thread(&mut self, vendor: GfxVendors) {
|
||||
let config = self.config.clone();
|
||||
let devices = self.nvidia.clone();
|
||||
let bus = self.bus.clone();
|
||||
let (tx, rx) = mpsc::channel();
|
||||
if let Ok(mut lock) = self.thread_kill.lock() {
|
||||
*lock = Some(tx);
|
||||
}
|
||||
let killer = self.thread_kill.clone();
|
||||
|
||||
let _join: JoinHandle<()> = std::thread::spawn(move || {
|
||||
Self::fire_starter(vendor, devices, bus, rx, config)
|
||||
.map_err(|err| {
|
||||
error!("GFX: {}", err);
|
||||
})
|
||||
.ok();
|
||||
// clear the tx/rx when done
|
||||
if let Ok(mut lock) = killer.try_lock() {
|
||||
*lock = None;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Initiates a mode change by starting a thread that will wait until all
|
||||
/// graphical sessions are exited before performing the tasks required
|
||||
/// to switch modes.
|
||||
///
|
||||
/// For manually calling (not on boot/startup) via dbus
|
||||
pub fn set_gfx_config(
|
||||
&mut self,
|
||||
vendor: GfxVendors,
|
||||
) -> Result<GfxRequiredUserAction, RogError> {
|
||||
if let Ok(gsync) = CtrlRogBios::get_gfx_mode() {
|
||||
if gsync == 1 {
|
||||
return Err(GfxError::GsyncModeActive.into());
|
||||
}
|
||||
}
|
||||
|
||||
let vfio_enable = if let Ok(config) = self.config.try_lock() {
|
||||
config.gfx_vfio_enable
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if !vfio_enable && matches!(vendor, GfxVendors::Vfio) {
|
||||
return Err(GfxError::VfioDisabled.into());
|
||||
}
|
||||
|
||||
// Must always cancel any thread running
|
||||
self.cancel_thread();
|
||||
// determine which method we need here
|
||||
let action_required = self.logout_required(vendor);
|
||||
if matches!(action_required, GfxRequiredUserAction::Logout) {
|
||||
// Yeah need the thread to check if all users are logged out
|
||||
info!("GFX: mode change requires a logout to complete");
|
||||
self.setup_thread(vendor);
|
||||
} else {
|
||||
// Okay cool, we can switch on/off vfio
|
||||
info!("GFX: mode change does not require logout");
|
||||
let devices = self.nvidia.clone();
|
||||
let bus = self.bus.clone();
|
||||
Self::do_vendor_tasks(vendor, vfio_enable, &devices, &bus)?;
|
||||
info!("GFX: Graphics mode changed to {}", <&str>::from(vendor));
|
||||
if matches!(vendor, GfxVendors::Vfio | GfxVendors::Compute) {
|
||||
if let Ok(mut config) = self.config.try_lock() {
|
||||
config.gfx_tmp_mode = Some(vendor);
|
||||
};
|
||||
}
|
||||
}
|
||||
// TODO: undo if failed? Save last mode, catch errors...
|
||||
Ok(action_required)
|
||||
}
|
||||
|
||||
/// Used only on boot to set correct mode
|
||||
fn auto_power(&mut self) -> Result<(), RogError> {
|
||||
let vendor = self.get_gfx_mode()?;
|
||||
let devices = self.nvidia.clone();
|
||||
let bus = self.bus.clone();
|
||||
|
||||
let vfio_enable = if let Ok(config) = self.config.try_lock() {
|
||||
config.gfx_vfio_enable
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
Self::do_vendor_tasks(vendor, vfio_enable, &devices, &bus)?;
|
||||
Self::toggle_fallback_service(vendor)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
use std::fmt;
|
||||
use std::{error, process::ExitStatus};
|
||||
|
||||
use crate::error::RogError;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum GfxError {
|
||||
ParseVendor,
|
||||
ParsePower,
|
||||
Bus(String, std::io::Error),
|
||||
DisplayManagerAction(String, ExitStatus),
|
||||
DisplayManagerTimeout(String),
|
||||
GsyncModeActive,
|
||||
VfioBuiltin,
|
||||
VfioDisabled,
|
||||
MissingModule(String),
|
||||
Modprobe(String),
|
||||
Command(String, std::io::Error),
|
||||
}
|
||||
|
||||
impl fmt::Display for GfxError {
|
||||
// This trait requires `fmt` with this exact signature.
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
GfxError::ParseVendor => write!(f, "Could not parse vendor name"),
|
||||
GfxError::ParsePower => write!(f, "Could not parse dGPU power status"),
|
||||
GfxError::Bus(func, error) => write!(f, "Bus error: {}: {}", func, error),
|
||||
GfxError::DisplayManagerAction(action, status) => {
|
||||
write!(f, "Display-manager action {} failed: {}", action, status)
|
||||
}
|
||||
GfxError::DisplayManagerTimeout(state) => {
|
||||
write!(f, "Timed out waiting for display-manager {} state", state)
|
||||
}
|
||||
GfxError::GsyncModeActive => write!(
|
||||
f,
|
||||
"Can not switch gfx modes when dedicated/G-Sync mode is active"
|
||||
),
|
||||
GfxError::VfioBuiltin => write!(
|
||||
f,
|
||||
"Can not switch to vfio mode if the modules are built in to kernel"
|
||||
),
|
||||
GfxError::VfioDisabled => {
|
||||
write!(f, "Can not switch to vfio mode if disabled in config file")
|
||||
}
|
||||
GfxError::MissingModule(m) => write!(f, "The module {} is missing", m),
|
||||
GfxError::Modprobe(detail) => write!(f, "Modprobe error: {}", detail),
|
||||
GfxError::Command(func, error) => write!(f, "Command exec error: {}: {}", func, error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for GfxError {}
|
||||
|
||||
impl From<GfxError> for RogError {
|
||||
fn from(err: GfxError) -> Self {
|
||||
RogError::GfxSwitching(err)
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
pub mod error;
|
||||
|
||||
pub mod controller;
|
||||
|
||||
pub mod system;
|
||||
|
||||
pub mod zbus_gfx;
|
||||
|
||||
const NVIDIA_DRIVERS: [&str; 4] = ["nvidia_drm", "nvidia_modeset", "nvidia_uvm", "nvidia"];
|
||||
|
||||
const VFIO_DRIVERS: [&str; 5] = [
|
||||
"vfio-pci",
|
||||
"vfio_iommu_type1",
|
||||
"vfio_virqfd",
|
||||
"vfio_mdev",
|
||||
"vfio",
|
||||
];
|
||||
|
||||
const DISPLAY_MANAGER: &str = "display-manager.service";
|
||||
|
||||
const MODPROBE_PATH: &str = "/etc/modprobe.d/asusd.conf";
|
||||
|
||||
static MODPROBE_BASE: &[u8] = br#"# Automatically generated by asusd
|
||||
# If you have issues with i2c_nvidia_gpu, copy the 2 lines below to a
|
||||
# new blacklist file and uncomment
|
||||
#blacklist i2c_nvidia_gpu
|
||||
#alias i2c_nvidia_gpu off
|
||||
blacklist nouveau
|
||||
alias nouveau off
|
||||
options nvidia NVreg_DynamicPowerManagement=0x02
|
||||
"#;
|
||||
|
||||
static MODPROBE_DRM_MODESET: &[u8] = br#"
|
||||
options nvidia-drm modeset=1
|
||||
"#;
|
||||
|
||||
static MODPROBE_INTEGRATED: &[u8] = br#"# Automatically generated by asusd
|
||||
blacklist i2c_nvidia_gpu
|
||||
blacklist nvidia
|
||||
blacklist nvidia-drm
|
||||
blacklist nvidia-modeset
|
||||
blacklist nouveau
|
||||
alias nouveau off
|
||||
"#;
|
||||
|
||||
static MODPROBE_VFIO: &[u8] = br#"options vfio-pci ids="#;
|
||||
|
||||
const XORG_FILE: &str = "90-nvidia-primary.conf";
|
||||
const XORG_PATH: &str = "/etc/X11/xorg.conf.d/";
|
||||
|
||||
static PRIMARY_GPU_BEGIN: &[u8] = br#"# Automatically generated by asusd
|
||||
Section "OutputClass"
|
||||
Identifier "nvidia"
|
||||
MatchDriver "nvidia-drm"
|
||||
Driver "nvidia"
|
||||
Option "AllowEmptyInitialConfiguration" "true""#;
|
||||
|
||||
static PRIMARY_GPU_NVIDIA: &[u8] = br#"
|
||||
Option "PrimaryGPU" "true""#;
|
||||
|
||||
static PRIMARY_GPU_END: &[u8] = br#"
|
||||
EndSection"#;
|
||||
@@ -1,160 +0,0 @@
|
||||
use log::{error, info, warn};
|
||||
use std::fs::read_to_string;
|
||||
use std::{fs::write, io, path::PathBuf};
|
||||
use sysfs_class::{PciDevice, SysClass};
|
||||
|
||||
pub struct Module {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl Module {
|
||||
fn parse(line: &str) -> io::Result<Module> {
|
||||
let mut parts = line.split(' ');
|
||||
|
||||
let name = parts
|
||||
.next()
|
||||
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "module name not found"))?;
|
||||
|
||||
Ok(Module {
|
||||
name: name.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn all() -> io::Result<Vec<Module>> {
|
||||
let mut modules = Vec::new();
|
||||
|
||||
let data = read_to_string("/proc/modules")?;
|
||||
for line in data.lines() {
|
||||
let module = Module::parse(line)?;
|
||||
modules.push(module);
|
||||
}
|
||||
|
||||
Ok(modules)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PciBus {
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
impl PciBus {
|
||||
pub fn new() -> io::Result<PciBus> {
|
||||
let path = PathBuf::from("/sys/bus/pci");
|
||||
if path.is_dir() {
|
||||
Ok(PciBus { path })
|
||||
} else {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::NotFound,
|
||||
"pci directory not found",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Will rescan the device tree, which adds all removed devices back
|
||||
pub fn rescan(&self) -> io::Result<()> {
|
||||
write(self.path.join("rescan"), "1")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct GraphicsDevice {
|
||||
_id: String,
|
||||
functions: Vec<PciDevice>,
|
||||
}
|
||||
|
||||
impl GraphicsDevice {
|
||||
pub fn new(id: String, functions: Vec<PciDevice>) -> GraphicsDevice {
|
||||
GraphicsDevice { _id: id, functions }
|
||||
}
|
||||
|
||||
pub fn exists(&self) -> bool {
|
||||
self.functions.iter().any(|func| func.path().exists())
|
||||
}
|
||||
|
||||
pub fn functions(&self) -> &[PciDevice] {
|
||||
&self.functions
|
||||
}
|
||||
|
||||
pub fn unbind(&self) -> Result<(), std::io::Error> {
|
||||
for func in self.functions.iter() {
|
||||
if func.path().exists() {
|
||||
match func.driver() {
|
||||
Ok(driver) => {
|
||||
info!("{}: Unbinding {}", driver.id(), func.id());
|
||||
unsafe {
|
||||
driver.unbind(&func).map_err(|err| {
|
||||
error!("gfx unbind: {}", err);
|
||||
err
|
||||
})?;
|
||||
}
|
||||
}
|
||||
Err(err) => match err.kind() {
|
||||
io::ErrorKind::NotFound => (),
|
||||
_ => {
|
||||
error!("gfx driver: {:?}, {}", func.path(), err);
|
||||
return Err(err);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn rebind(&self) -> Result<(), std::io::Error> {
|
||||
for func in self.functions.iter() {
|
||||
if func.path().exists() {
|
||||
match func.driver() {
|
||||
Ok(driver) => {
|
||||
info!("{}: Binding {}", driver.id(), func.id());
|
||||
unsafe {
|
||||
driver.bind(&func).map_err(|err| {
|
||||
error!("gfx bind: {}", err);
|
||||
err
|
||||
})?;
|
||||
}
|
||||
}
|
||||
Err(err) => match err.kind() {
|
||||
io::ErrorKind::NotFound => (),
|
||||
_ => {
|
||||
error!("gfx driver: {:?}, {}", func.path(), err);
|
||||
return Err(err);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn remove(&self) -> Result<(), std::io::Error> {
|
||||
for func in self.functions.iter() {
|
||||
if func.path().exists() {
|
||||
match func.driver() {
|
||||
Ok(driver) => {
|
||||
error!("{}: in use by {}", func.id(), driver.id());
|
||||
}
|
||||
Err(why) => match why.kind() {
|
||||
std::io::ErrorKind::NotFound => {
|
||||
info!("{}: Removing", func.id());
|
||||
unsafe {
|
||||
// ignore errors and carry on
|
||||
if let Err(err) = func.remove() {
|
||||
error!("gfx remove: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
error!("Remove device failed");
|
||||
}
|
||||
},
|
||||
}
|
||||
} else {
|
||||
warn!("{}: Already removed", func.id());
|
||||
}
|
||||
}
|
||||
info!("Removed all gfx devices");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
use ::zbus::dbus_interface;
|
||||
use log::{error, info, warn};
|
||||
use rog_types::gfx_vendors::{GfxPower, GfxRequiredUserAction, GfxVendors};
|
||||
use zvariant::ObjectPath;
|
||||
|
||||
use crate::ZbusAdd;
|
||||
|
||||
use super::controller::CtrlGraphics;
|
||||
|
||||
#[dbus_interface(name = "org.asuslinux.Daemon")]
|
||||
impl CtrlGraphics {
|
||||
fn vendor(&self) -> zbus::fdo::Result<GfxVendors> {
|
||||
self.get_gfx_mode().map_err(|err| {
|
||||
error!("GFX: {}", err);
|
||||
zbus::fdo::Error::Failed(format!("GFX fail: {}", err))
|
||||
})
|
||||
}
|
||||
|
||||
fn power(&self) -> zbus::fdo::Result<GfxPower> {
|
||||
Self::get_runtime_status().map_err(|err| {
|
||||
error!("GFX: {}", err);
|
||||
zbus::fdo::Error::Failed(format!("GFX fail: {}", err))
|
||||
})
|
||||
}
|
||||
|
||||
fn set_vendor(&mut self, vendor: GfxVendors) -> zbus::fdo::Result<GfxRequiredUserAction> {
|
||||
info!("GFX: Switching gfx mode to {}", <&str>::from(vendor));
|
||||
let msg = self.set_gfx_config(vendor).map_err(|err| {
|
||||
error!("GFX: {}", err);
|
||||
zbus::fdo::Error::Failed(format!("GFX fail: {}", err))
|
||||
})?;
|
||||
self.notify_gfx(&vendor)
|
||||
.unwrap_or_else(|err| warn!("GFX: {}", err));
|
||||
self.notify_action(&msg)
|
||||
.unwrap_or_else(|err| warn!("GFX: {}", err));
|
||||
Ok(msg)
|
||||
}
|
||||
|
||||
#[dbus_interface(signal)]
|
||||
fn notify_gfx(&self, vendor: &GfxVendors) -> zbus::Result<()> {}
|
||||
|
||||
#[dbus_interface(signal)]
|
||||
fn notify_action(&self, action: &GfxRequiredUserAction) -> zbus::Result<()> {}
|
||||
}
|
||||
|
||||
impl ZbusAdd for CtrlGraphics {
|
||||
fn add_to_server(self, server: &mut zbus::ObjectServer) {
|
||||
server
|
||||
.at(&ObjectPath::from_str_unchecked("/org/asuslinux/Gfx"), self)
|
||||
.map_err(|err| {
|
||||
warn!("GFX: CtrlGraphics: add_to_server {}", err);
|
||||
err
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
@@ -1,343 +0,0 @@
|
||||
// Only these two packets must be 17 bytes
|
||||
static KBD_BRIGHT_PATH: &str = "/sys/class/leds/asus::kbd_backlight/brightness";
|
||||
|
||||
use crate::{
|
||||
config_aura::AuraConfig,
|
||||
error::RogError,
|
||||
laptops::{LaptopLedData, ASUS_KEYBOARD_DEVICES},
|
||||
};
|
||||
use log::{info, warn};
|
||||
use rog_aura::{
|
||||
usb::{
|
||||
LED_APPLY, LED_AWAKE_OFF_SLEEP_OFF, LED_AWAKE_OFF_SLEEP_ON, LED_AWAKE_ON_SLEEP_OFF,
|
||||
LED_AWAKE_ON_SLEEP_ON, LED_SET,
|
||||
},
|
||||
AuraEffect, LedBrightness, LED_MSG_LEN,
|
||||
};
|
||||
use rog_types::supported::LedSupportedFunctions;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::{Read, Write};
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use crate::GetSupported;
|
||||
|
||||
impl GetSupported for CtrlKbdLed {
|
||||
type A = LedSupportedFunctions;
|
||||
|
||||
fn get_supported() -> Self::A {
|
||||
// let mode = <&str>::from(&<AuraModes>::from(*mode));
|
||||
let multizone_led_mode = false;
|
||||
let per_key_led_mode = false;
|
||||
let laptop = LaptopLedData::get_data();
|
||||
let stock_led_modes = laptop.standard;
|
||||
|
||||
LedSupportedFunctions {
|
||||
brightness_set: CtrlKbdLed::get_kbd_bright_path().is_some(),
|
||||
stock_led_modes,
|
||||
multizone_led_mode,
|
||||
per_key_led_mode,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CtrlKbdLed {
|
||||
pub led_node: Option<String>,
|
||||
pub bright_node: String,
|
||||
pub supported_modes: LaptopLedData,
|
||||
pub flip_effect_write: bool,
|
||||
pub config: AuraConfig,
|
||||
}
|
||||
|
||||
pub struct CtrlKbdLedTask(pub Arc<Mutex<CtrlKbdLed>>);
|
||||
|
||||
impl crate::CtrlTask for CtrlKbdLedTask {
|
||||
fn do_task(&self) -> Result<(), RogError> {
|
||||
if let Ok(mut lock) = self.0.try_lock() {
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.open(&lock.bright_node)
|
||||
.map_err(|err| match err.kind() {
|
||||
std::io::ErrorKind::NotFound => {
|
||||
RogError::MissingLedBrightNode((&lock.bright_node).into(), err)
|
||||
}
|
||||
_ => RogError::Path((&lock.bright_node).into(), err),
|
||||
})?;
|
||||
let mut buf = [0u8; 1];
|
||||
file.read_exact(&mut buf)
|
||||
.map_err(|err| RogError::Read("buffer".into(), err))?;
|
||||
if let Some(num) = char::from(buf[0]).to_digit(10) {
|
||||
if lock.config.brightness != num.into() {
|
||||
lock.config.read();
|
||||
lock.config.brightness = num.into();
|
||||
lock.config.write();
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
return Err(RogError::ParseLed);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CtrlKbdLedReloader(pub Arc<Mutex<CtrlKbdLed>>);
|
||||
|
||||
impl crate::Reloadable for CtrlKbdLedReloader {
|
||||
fn reload(&mut self) -> Result<(), RogError> {
|
||||
if let Ok(mut ctrl) = self.0.try_lock() {
|
||||
let current = ctrl.config.current_mode;
|
||||
if let Some(mode) = ctrl.config.builtins.get(¤t).cloned() {
|
||||
ctrl.do_command(mode).ok();
|
||||
}
|
||||
|
||||
ctrl.set_states_enabled(ctrl.config.awake_enabled, ctrl.config.sleep_anim_enabled)
|
||||
.map_err(|err| warn!("{}", err))
|
||||
.ok();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CtrlKbdLedZbus(pub Arc<Mutex<CtrlKbdLed>>);
|
||||
|
||||
impl CtrlKbdLedZbus {
|
||||
pub fn new(inner: Arc<Mutex<CtrlKbdLed>>) -> Self {
|
||||
Self(inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl CtrlKbdLed {
|
||||
#[inline]
|
||||
pub fn new(supported_modes: LaptopLedData, config: AuraConfig) -> Result<Self, RogError> {
|
||||
// TODO: return error if *all* nodes are None
|
||||
let mut led_node = None;
|
||||
for prod in ASUS_KEYBOARD_DEVICES.iter() {
|
||||
match Self::find_led_node(prod) {
|
||||
Ok(node) => {
|
||||
led_node = Some(node);
|
||||
break;
|
||||
}
|
||||
Err(err) => warn!("led_node: {}", err),
|
||||
}
|
||||
}
|
||||
|
||||
let bright_node = Self::get_kbd_bright_path();
|
||||
|
||||
if led_node.is_none() && bright_node.is_none() {
|
||||
return Err(RogError::MissingFunction(
|
||||
"All keyboard features missing, you may require a v5.11 series kernel or newer"
|
||||
.into(),
|
||||
));
|
||||
}
|
||||
|
||||
if bright_node.is_none() {
|
||||
return Err(RogError::MissingFunction(
|
||||
"No brightness control, you may require a v5.11 series kernel or newer".into(),
|
||||
));
|
||||
}
|
||||
|
||||
let ctrl = CtrlKbdLed {
|
||||
led_node,
|
||||
bright_node: bright_node.unwrap(), // If was none then we already returned above
|
||||
supported_modes,
|
||||
flip_effect_write: false,
|
||||
config,
|
||||
};
|
||||
Ok(ctrl)
|
||||
}
|
||||
|
||||
fn get_kbd_bright_path() -> Option<String> {
|
||||
if Path::new(KBD_BRIGHT_PATH).exists() {
|
||||
return Some(KBD_BRIGHT_PATH.to_string());
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub(super) fn get_brightness(&self) -> Result<u8, RogError> {
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.open(&self.bright_node)
|
||||
.map_err(|err| match err.kind() {
|
||||
std::io::ErrorKind::NotFound => {
|
||||
RogError::MissingLedBrightNode((&self.bright_node).into(), err)
|
||||
}
|
||||
_ => RogError::Path((&self.bright_node).into(), err),
|
||||
})?;
|
||||
let mut buf = [0u8; 1];
|
||||
file.read_exact(&mut buf)
|
||||
.map_err(|err| RogError::Read("buffer".into(), err))?;
|
||||
Ok(buf[0])
|
||||
}
|
||||
|
||||
pub(super) fn set_brightness(&self, brightness: LedBrightness) -> Result<(), RogError> {
|
||||
let path = Path::new(&self.bright_node);
|
||||
let mut file =
|
||||
OpenOptions::new()
|
||||
.write(true)
|
||||
.open(&path)
|
||||
.map_err(|err| match err.kind() {
|
||||
std::io::ErrorKind::NotFound => {
|
||||
RogError::MissingLedBrightNode((&self.bright_node).into(), err)
|
||||
}
|
||||
_ => RogError::Path((&self.bright_node).into(), err),
|
||||
})?;
|
||||
file.write_all(&[brightness.as_char_code()])
|
||||
.map_err(|err| RogError::Read("buffer".into(), err))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set if awake/on LED active, and/or sleep animation active
|
||||
pub(super) fn set_states_enabled(&self, awake: bool, sleep: bool) -> Result<(), RogError> {
|
||||
let bytes = if awake && sleep {
|
||||
LED_AWAKE_ON_SLEEP_ON
|
||||
} else if awake && !sleep {
|
||||
LED_AWAKE_ON_SLEEP_OFF
|
||||
} else if !awake && sleep {
|
||||
LED_AWAKE_OFF_SLEEP_ON
|
||||
} else if !awake && !sleep {
|
||||
LED_AWAKE_OFF_SLEEP_OFF
|
||||
} else {
|
||||
LED_AWAKE_ON_SLEEP_ON
|
||||
};
|
||||
self.write_bytes(&bytes)?;
|
||||
self.write_bytes(&LED_SET)?;
|
||||
// Changes won't persist unless apply is set
|
||||
self.write_bytes(&LED_APPLY)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn find_led_node(id_product: &str) -> Result<String, RogError> {
|
||||
let mut enumerator = udev::Enumerator::new().map_err(|err| {
|
||||
warn!("{}", err);
|
||||
RogError::Udev("enumerator failed".into(), err)
|
||||
})?;
|
||||
enumerator.match_subsystem("hidraw").map_err(|err| {
|
||||
warn!("{}", err);
|
||||
RogError::Udev("match_subsystem failed".into(), err)
|
||||
})?;
|
||||
|
||||
for device in enumerator.scan_devices().map_err(|err| {
|
||||
warn!("{}", err);
|
||||
RogError::Udev("scan_devices failed".into(), err)
|
||||
})? {
|
||||
if let Some(parent) = device
|
||||
.parent_with_subsystem_devtype("usb", "usb_device")
|
||||
.map_err(|err| {
|
||||
warn!("{}", err);
|
||||
RogError::Udev("parent_with_subsystem_devtype failed".into(), err)
|
||||
})?
|
||||
{
|
||||
if parent
|
||||
.attribute_value("idProduct")
|
||||
.ok_or_else(|| RogError::NotFound("LED idProduct".into()))?
|
||||
== id_product
|
||||
{
|
||||
if let Some(dev_node) = device.devnode() {
|
||||
info!("Using device at: {:?} for LED control", dev_node);
|
||||
return Ok(dev_node.to_string_lossy().to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(RogError::MissingFunction(
|
||||
"ASUS LED device node not found".into(),
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn do_command(&mut self, mode: AuraEffect) -> Result<(), RogError> {
|
||||
self.set_and_save(mode)
|
||||
}
|
||||
|
||||
/// Should only be used if the bytes you are writing are verified correct
|
||||
#[inline]
|
||||
fn write_bytes(&self, message: &[u8]) -> Result<(), RogError> {
|
||||
if let Some(led_node) = &self.led_node {
|
||||
if let Ok(mut file) = OpenOptions::new().write(true).open(led_node) {
|
||||
// println!("write: {:02x?}", &message);
|
||||
return file
|
||||
.write_all(message)
|
||||
.map_err(|err| RogError::Write("write_bytes".into(), err));
|
||||
}
|
||||
}
|
||||
Err(RogError::NotSupported)
|
||||
}
|
||||
|
||||
/// Write an effect block
|
||||
#[inline]
|
||||
fn _write_effect(&mut self, effect: &[Vec<u8>]) -> Result<(), RogError> {
|
||||
if self.flip_effect_write {
|
||||
for row in effect.iter().rev() {
|
||||
self.write_bytes(row)?;
|
||||
}
|
||||
} else {
|
||||
for row in effect.iter() {
|
||||
self.write_bytes(row)?;
|
||||
}
|
||||
}
|
||||
self.flip_effect_write = !self.flip_effect_write;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Used to set a builtin mode and save the settings for it
|
||||
///
|
||||
/// This needs to be universal so that settings applied by dbus stick
|
||||
#[inline]
|
||||
fn set_and_save(&mut self, mode: AuraEffect) -> Result<(), RogError> {
|
||||
self.config.read();
|
||||
self.write_mode(&mode)?;
|
||||
self.config.current_mode = *mode.mode();
|
||||
self.config.set_builtin(mode);
|
||||
self.config.write();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn toggle_mode(&mut self, reverse: bool) -> Result<(), RogError> {
|
||||
let current = self.config.current_mode;
|
||||
if let Some(idx) = self
|
||||
.supported_modes
|
||||
.standard
|
||||
.iter()
|
||||
.position(|v| *v == current)
|
||||
{
|
||||
let mut idx = idx;
|
||||
// goes past end of array
|
||||
if reverse {
|
||||
if idx == 0 {
|
||||
idx = self.supported_modes.standard.len() - 1;
|
||||
} else {
|
||||
idx -= 1;
|
||||
}
|
||||
} else {
|
||||
idx += 1;
|
||||
if idx == self.supported_modes.standard.len() {
|
||||
idx = 0;
|
||||
}
|
||||
}
|
||||
let next = self.supported_modes.standard[idx];
|
||||
|
||||
self.config.read();
|
||||
if let Some(data) = self.config.builtins.get(&next) {
|
||||
self.write_mode(&data)?;
|
||||
self.config.current_mode = next;
|
||||
}
|
||||
self.config.write();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn write_mode(&self, mode: &AuraEffect) -> Result<(), RogError> {
|
||||
if !self.supported_modes.standard.contains(&mode.mode()) {
|
||||
return Err(RogError::NotSupported);
|
||||
}
|
||||
let bytes: [u8; LED_MSG_LEN] = mode.into();
|
||||
self.write_bytes(&bytes)?;
|
||||
self.write_bytes(&LED_SET)?;
|
||||
// Changes won't persist unless apply is set
|
||||
self.write_bytes(&LED_APPLY)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
pub mod controller;
|
||||
pub mod zbus;
|
||||
@@ -1,165 +0,0 @@
|
||||
use log::{error, warn};
|
||||
use rog_aura::{AuraEffect, LedBrightness, LedPowerStates};
|
||||
use zbus::dbus_interface;
|
||||
use zvariant::ObjectPath;
|
||||
|
||||
use super::controller::CtrlKbdLedZbus;
|
||||
|
||||
impl crate::ZbusAdd for CtrlKbdLedZbus {
|
||||
fn add_to_server(self, server: &mut zbus::ObjectServer) {
|
||||
server
|
||||
.at(&ObjectPath::from_str_unchecked("/org/asuslinux/Led"), self)
|
||||
.map_err(|err| {
|
||||
error!("DbusKbdLed: add_to_server {}", err);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
/// The main interface for changing, reading, or notfying signals
|
||||
///
|
||||
/// LED commands are split between Brightness, Modes, Per-Key
|
||||
#[dbus_interface(name = "org.asuslinux.Daemon")]
|
||||
impl CtrlKbdLedZbus {
|
||||
/// Set the keyboard brightness level (0-3)
|
||||
fn set_brightness(&mut self, brightness: LedBrightness) {
|
||||
if let Ok(ctrl) = self.0.try_lock() {
|
||||
ctrl.set_brightness(brightness)
|
||||
.map_err(|err| warn!("{}", err))
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the keyboard LED to enabled while the device is awake
|
||||
fn set_awake_enabled(&mut self, enabled: bool) {
|
||||
if let Ok(mut ctrl) = self.0.try_lock() {
|
||||
ctrl.set_states_enabled(enabled, ctrl.config.sleep_anim_enabled)
|
||||
.map_err(|err| warn!("{}", err))
|
||||
.ok();
|
||||
ctrl.config.awake_enabled = enabled;
|
||||
ctrl.config.write();
|
||||
|
||||
let states = LedPowerStates {
|
||||
enabled: ctrl.config.awake_enabled,
|
||||
sleep_anim_enabled: ctrl.config.sleep_anim_enabled,
|
||||
};
|
||||
self.notify_power_states(&states)
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the keyboard LED suspend animation to enabled while the device is suspended
|
||||
fn set_sleep_enabled(&mut self, enabled: bool) {
|
||||
if let Ok(mut ctrl) = self.0.try_lock() {
|
||||
ctrl.set_states_enabled(ctrl.config.awake_enabled, enabled)
|
||||
.map_err(|err| warn!("{}", err))
|
||||
.ok();
|
||||
ctrl.config.sleep_anim_enabled = enabled;
|
||||
ctrl.config.write();
|
||||
let states = LedPowerStates {
|
||||
enabled: ctrl.config.awake_enabled,
|
||||
sleep_anim_enabled: ctrl.config.sleep_anim_enabled,
|
||||
};
|
||||
self.notify_power_states(&states)
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
}
|
||||
}
|
||||
|
||||
fn set_led_mode(&mut self, effect: AuraEffect) {
|
||||
if let Ok(mut ctrl) = self.0.try_lock() {
|
||||
match ctrl.do_command(effect) {
|
||||
Ok(_) => {
|
||||
if let Some(mode) = ctrl.config.builtins.get(&ctrl.config.current_mode) {
|
||||
self.notify_led(mode.clone())
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
warn!("{}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn next_led_mode(&self) {
|
||||
if let Ok(mut ctrl) = self.0.try_lock() {
|
||||
ctrl.toggle_mode(false)
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
|
||||
if let Some(mode) = ctrl.config.builtins.get(&ctrl.config.current_mode) {
|
||||
self.notify_led(mode.clone())
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn prev_led_mode(&self) {
|
||||
if let Ok(mut ctrl) = self.0.try_lock() {
|
||||
ctrl.toggle_mode(true)
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
|
||||
if let Some(mode) = ctrl.config.builtins.get(&ctrl.config.current_mode) {
|
||||
self.notify_led(mode.clone())
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[dbus_interface(property)]
|
||||
fn awake_enabled(&self) -> bool {
|
||||
if let Ok(ctrl) = self.0.try_lock() {
|
||||
return ctrl.config.awake_enabled;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
#[dbus_interface(property)]
|
||||
fn sleep_enabled(&self) -> bool {
|
||||
if let Ok(ctrl) = self.0.try_lock() {
|
||||
return ctrl.config.sleep_anim_enabled;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Return the current mode data
|
||||
#[dbus_interface(property)]
|
||||
fn led_mode(&self) -> String {
|
||||
if let Ok(ctrl) = self.0.try_lock() {
|
||||
if let Some(mode) = ctrl.config.builtins.get(&ctrl.config.current_mode) {
|
||||
if let Ok(json) = serde_json::to_string(&mode) {
|
||||
return json;
|
||||
}
|
||||
}
|
||||
}
|
||||
warn!("SetKeyBacklight could not deserialise");
|
||||
"SetKeyBacklight could not deserialise".to_string()
|
||||
}
|
||||
|
||||
/// Return a list of available modes
|
||||
#[dbus_interface(property)]
|
||||
fn led_modes(&self) -> String {
|
||||
if let Ok(ctrl) = self.0.try_lock() {
|
||||
if let Ok(json) = serde_json::to_string(&ctrl.config.builtins) {
|
||||
return json;
|
||||
}
|
||||
}
|
||||
warn!("SetKeyBacklight could not deserialise");
|
||||
"SetKeyBacklight could not serialise".to_string()
|
||||
}
|
||||
|
||||
/// Return the current LED brightness
|
||||
#[dbus_interface(property)]
|
||||
fn led_brightness(&self) -> i8 {
|
||||
if let Ok(ctrl) = self.0.try_lock() {
|
||||
return ctrl.get_brightness().map(|n| n as i8).unwrap_or(-1);
|
||||
}
|
||||
warn!("SetKeyBacklight could not serialise");
|
||||
-1
|
||||
}
|
||||
|
||||
#[dbus_interface(signal)]
|
||||
fn notify_led(&self, data: AuraEffect) -> zbus::Result<()>;
|
||||
|
||||
#[dbus_interface(signal)]
|
||||
fn notify_power_states(&self, data: &LedPowerStates) -> zbus::Result<()>;
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
use crate::error::RogError;
|
||||
use crate::{config::Config, GetSupported};
|
||||
use log::info;
|
||||
use rog_profiles::profiles::Profile;
|
||||
use rog_types::supported::FanCpuSupportedFunctions;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
|
||||
pub struct CtrlFanAndCpu {
|
||||
pub config: Arc<Mutex<Config>>,
|
||||
}
|
||||
|
||||
impl GetSupported for CtrlFanAndCpu {
|
||||
type A = FanCpuSupportedFunctions;
|
||||
|
||||
fn get_supported() -> Self::A {
|
||||
FanCpuSupportedFunctions {
|
||||
stock_fan_modes: Profile::get_fan_path().is_ok(),
|
||||
min_max_freq: Profile::get_intel_supported(),
|
||||
fan_curve_set: rog_fan_curve::Board::from_board_name().is_some(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::Reloadable for CtrlFanAndCpu {
|
||||
fn reload(&mut self) -> Result<(), RogError> {
|
||||
if let Ok(mut cfg) = self.config.clone().try_lock() {
|
||||
let active = cfg.active_profile.clone();
|
||||
if let Some(existing) = cfg.power_profiles.get_mut(&active) {
|
||||
existing.set_system_all()?;
|
||||
cfg.write();
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl CtrlFanAndCpu {
|
||||
pub fn new(config: Arc<Mutex<Config>>) -> Result<Self, RogError> {
|
||||
Profile::get_fan_path()?;
|
||||
info!("Device has thermal throttle control");
|
||||
Ok(CtrlFanAndCpu { config })
|
||||
}
|
||||
|
||||
/// Toggle to next profile in list
|
||||
pub(super) fn do_next_profile(&mut self, config: &mut Config) -> Result<(), RogError> {
|
||||
config.read();
|
||||
|
||||
let mut i = config
|
||||
.toggle_profiles
|
||||
.iter()
|
||||
.position(|x| x == &config.active_profile)
|
||||
.map(|i| i + 1)
|
||||
.unwrap_or(0);
|
||||
if i >= config.toggle_profiles.len() {
|
||||
i = 0;
|
||||
}
|
||||
|
||||
let new_profile = config
|
||||
.toggle_profiles
|
||||
.get(i)
|
||||
.unwrap_or(&config.active_profile)
|
||||
.clone();
|
||||
|
||||
self.set_active(&new_profile)?;
|
||||
|
||||
info!("Profile was changed: {}", &new_profile);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn set_active(&mut self, profile: &str) -> Result<(), RogError> {
|
||||
if let Ok(mut cfg) = self.config.clone().try_lock() {
|
||||
cfg.read();
|
||||
|
||||
if let Some(existing) = cfg.power_profiles.get_mut(profile) {
|
||||
existing.set_system_all()?;
|
||||
cfg.active_profile = existing.name.clone();
|
||||
cfg.write();
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn new_or_modify(&mut self, profile: &Profile) -> Result<(), RogError> {
|
||||
if let Ok(mut cfg) = self.config.clone().try_lock() {
|
||||
cfg.read();
|
||||
|
||||
if let Some(existing) = cfg.power_profiles.get_mut(&profile.name) {
|
||||
*existing = profile.clone();
|
||||
existing.set_system_all()?;
|
||||
} else {
|
||||
cfg.power_profiles
|
||||
.insert(profile.name.clone(), profile.clone());
|
||||
}
|
||||
cfg.active_profile = profile.name.clone();
|
||||
cfg.write();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
pub mod zbus;
|
||||
|
||||
pub mod controller;
|
||||
@@ -1,170 +0,0 @@
|
||||
use log::warn;
|
||||
use rog_profiles::profiles::Profile;
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use zbus::{dbus_interface, fdo::Error};
|
||||
use zvariant::ObjectPath;
|
||||
|
||||
use super::controller::CtrlFanAndCpu;
|
||||
|
||||
pub struct FanAndCpuZbus {
|
||||
inner: Arc<Mutex<CtrlFanAndCpu>>,
|
||||
}
|
||||
|
||||
impl FanAndCpuZbus {
|
||||
pub fn new(inner: Arc<Mutex<CtrlFanAndCpu>>) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
#[dbus_interface(name = "org.asuslinux.Daemon")]
|
||||
impl FanAndCpuZbus {
|
||||
/// Create new profile and make active
|
||||
fn set_profile(&self, profile: String) {
|
||||
if let Ok(mut ctrl) = self.inner.try_lock() {
|
||||
ctrl.set_active(&profile)
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
// Do notification
|
||||
if let Ok(cfg) = ctrl.config.clone().try_lock() {
|
||||
// Do notify
|
||||
if let Some(profile) = cfg.power_profiles.get(&cfg.active_profile) {
|
||||
self.notify_profile(&profile)
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// New or modify profile details and make active, will create if it does not exist
|
||||
fn new_or_modify(&self, profile: Profile) {
|
||||
if let Ok(mut ctrl) = self.inner.try_lock() {
|
||||
ctrl.new_or_modify(&profile)
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
// Do notification
|
||||
if let Ok(cfg) = ctrl.config.clone().try_lock() {
|
||||
// Do notify
|
||||
if let Some(profile) = cfg.power_profiles.get(&cfg.active_profile) {
|
||||
self.notify_profile(&profile)
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetch the active profile name
|
||||
fn next_profile(&mut self) {
|
||||
if let Ok(mut ctrl) = self.inner.try_lock() {
|
||||
if let Ok(mut cfg) = ctrl.config.clone().try_lock() {
|
||||
cfg.read();
|
||||
ctrl.do_next_profile(&mut cfg)
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
if let Some(profile) = cfg.power_profiles.get(&cfg.active_profile) {
|
||||
self.notify_profile(&profile)
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetch the active profile name
|
||||
fn active_name(&mut self) -> zbus::fdo::Result<String> {
|
||||
if let Ok(ctrl) = self.inner.try_lock() {
|
||||
if let Ok(mut cfg) = ctrl.config.try_lock() {
|
||||
cfg.read();
|
||||
return Ok(cfg.active_profile.clone());
|
||||
}
|
||||
}
|
||||
Err(Error::Failed(
|
||||
"Failed to get active profile name".to_string(),
|
||||
))
|
||||
}
|
||||
|
||||
// TODO: Profile can't implement Type because of Curve
|
||||
/// Fetch the active profile details
|
||||
fn active_data(&mut self) -> zbus::fdo::Result<Profile> {
|
||||
if let Ok(ctrl) = self.inner.try_lock() {
|
||||
if let Ok(mut cfg) = ctrl.config.try_lock() {
|
||||
cfg.read();
|
||||
if let Some(profile) = cfg.power_profiles.get(&cfg.active_profile) {
|
||||
return Ok(profile.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(Error::Failed(
|
||||
"Failed to get active profile details".to_string(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Fetch all profile data
|
||||
fn profiles(&mut self) -> zbus::fdo::Result<Vec<Profile>> {
|
||||
if let Ok(ctrl) = self.inner.try_lock() {
|
||||
if let Ok(mut cfg) = ctrl.config.try_lock() {
|
||||
cfg.read();
|
||||
return Ok(cfg.power_profiles.values().cloned().collect());
|
||||
}
|
||||
}
|
||||
Err(Error::Failed(
|
||||
"Failed to get all profile details".to_string(),
|
||||
))
|
||||
}
|
||||
|
||||
fn profile_names(&self) -> zbus::fdo::Result<Vec<String>> {
|
||||
if let Ok(ctrl) = self.inner.try_lock() {
|
||||
if let Ok(mut cfg) = ctrl.config.try_lock() {
|
||||
cfg.read();
|
||||
let profile_names = cfg.power_profiles.keys().cloned().collect::<Vec<String>>();
|
||||
return Ok(profile_names);
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::Failed("Failed to get all profile names".to_string()))
|
||||
}
|
||||
|
||||
fn remove(&self, profile: &str) -> zbus::fdo::Result<()> {
|
||||
if let Ok(ctrl) = self.inner.try_lock() {
|
||||
if let Ok(mut cfg) = ctrl.config.try_lock() {
|
||||
cfg.read();
|
||||
|
||||
if !cfg.power_profiles.contains_key(profile) {
|
||||
return Err(Error::Failed("Invalid profile specified".to_string()));
|
||||
}
|
||||
|
||||
if cfg.power_profiles.keys().len() == 1 {
|
||||
return Err(Error::Failed("Cannot delete the last profile".to_string()));
|
||||
}
|
||||
|
||||
if cfg.active_profile == *profile {
|
||||
return Err(Error::Failed(
|
||||
"Cannot delete the active profile".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
cfg.power_profiles.remove(profile);
|
||||
cfg.write();
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::Failed("Failed to lock configuration".to_string()))
|
||||
}
|
||||
|
||||
#[dbus_interface(signal)]
|
||||
fn notify_profile(&self, profile: &Profile) -> zbus::Result<()> {}
|
||||
}
|
||||
|
||||
impl crate::ZbusAdd for FanAndCpuZbus {
|
||||
fn add_to_server(self, server: &mut zbus::ObjectServer) {
|
||||
server
|
||||
.at(
|
||||
&ObjectPath::from_str_unchecked("/org/asuslinux/Profile"),
|
||||
self,
|
||||
)
|
||||
.map_err(|err| {
|
||||
warn!("DbusFanAndCpu: add_to_server {}", err);
|
||||
err
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
@@ -1,340 +0,0 @@
|
||||
use crate::{config::Config, error::RogError, GetSupported};
|
||||
use log::{error, info, warn};
|
||||
use rog_types::supported::RogBiosSupportedFunctions;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::BufRead;
|
||||
use std::io::{Read, Write};
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use zbus::dbus_interface;
|
||||
use zvariant::ObjectPath;
|
||||
|
||||
const INITRAMFS_PATH: &str = "/usr/sbin/update-initramfs";
|
||||
const DRACUT_PATH: &str = "/usr/bin/dracut";
|
||||
|
||||
static ASUS_SWITCH_GRAPHIC_MODE: &str =
|
||||
"/sys/firmware/efi/efivars/AsusSwitchGraphicMode-607005d5-3f75-4b2e-98f0-85ba66797a3e";
|
||||
static ASUS_POST_LOGO_SOUND: &str =
|
||||
"/sys/firmware/efi/efivars/AsusPostLogoSound-607005d5-3f75-4b2e-98f0-85ba66797a3e";
|
||||
|
||||
pub struct CtrlRogBios {
|
||||
_config: Arc<Mutex<Config>>,
|
||||
}
|
||||
|
||||
impl GetSupported for CtrlRogBios {
|
||||
type A = RogBiosSupportedFunctions;
|
||||
|
||||
fn get_supported() -> Self::A {
|
||||
RogBiosSupportedFunctions {
|
||||
post_sound_toggle: CtrlRogBios::check_path_exists(ASUS_POST_LOGO_SOUND).is_ok(),
|
||||
dedicated_gfx_toggle: CtrlRogBios::check_path_exists(ASUS_SWITCH_GRAPHIC_MODE).is_ok(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[dbus_interface(name = "org.asuslinux.Daemon")]
|
||||
impl CtrlRogBios {
|
||||
pub fn set_dedicated_graphic_mode(&mut self, dedicated: bool) {
|
||||
self.set_gfx_mode(dedicated)
|
||||
.map_err(|err| {
|
||||
warn!("CtrlRogBios: set_asus_switch_graphic_mode {}", err);
|
||||
err
|
||||
})
|
||||
.ok();
|
||||
self.notify_dedicated_graphic_mode(dedicated)
|
||||
.map_err(|err| {
|
||||
warn!("CtrlRogBios: notify_asus_switch_graphic_mode {}", err);
|
||||
err
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
pub fn dedicated_graphic_mode(&self) -> i8 {
|
||||
Self::get_gfx_mode()
|
||||
.map_err(|err| {
|
||||
warn!("CtrlRogBios: get_gfx_mode {}", err);
|
||||
err
|
||||
})
|
||||
.unwrap_or(-1)
|
||||
}
|
||||
|
||||
#[dbus_interface(signal)]
|
||||
pub fn notify_dedicated_graphic_mode(&self, dedicated: bool) -> zbus::Result<()> {}
|
||||
|
||||
// // // // // // // // // //
|
||||
|
||||
pub fn set_post_boot_sound(&mut self, on: bool) {
|
||||
Self::set_boot_sound(on)
|
||||
.map_err(|err| {
|
||||
warn!("CtrlRogBios: set_post_boot_sound {}", err);
|
||||
err
|
||||
})
|
||||
.ok();
|
||||
self.notify_post_boot_sound(on)
|
||||
.map_err(|err| {
|
||||
warn!("CtrlRogBios: notify_post_boot_sound {}", err);
|
||||
err
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
pub fn post_boot_sound(&self) -> i8 {
|
||||
Self::get_boot_sound()
|
||||
.map_err(|err| {
|
||||
warn!("CtrlRogBios: get_boot_sound {}", err);
|
||||
err
|
||||
})
|
||||
.unwrap_or(-1)
|
||||
}
|
||||
|
||||
#[dbus_interface(signal)]
|
||||
pub fn notify_post_boot_sound(&self, dedicated: bool) -> zbus::Result<()> {}
|
||||
}
|
||||
|
||||
impl crate::ZbusAdd for CtrlRogBios {
|
||||
fn add_to_server(self, server: &mut zbus::ObjectServer) {
|
||||
server
|
||||
.at(
|
||||
&ObjectPath::from_str_unchecked("/org/asuslinux/RogBios"),
|
||||
self,
|
||||
)
|
||||
.map_err(|err| {
|
||||
warn!("CtrlRogBios: add_to_server {}", err);
|
||||
err
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::Reloadable for CtrlRogBios {
|
||||
fn reload(&mut self) -> Result<(), RogError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl CtrlRogBios {
|
||||
pub fn new(config: Arc<Mutex<Config>>) -> Result<Self, RogError> {
|
||||
match CtrlRogBios::check_path_exists(ASUS_SWITCH_GRAPHIC_MODE) {
|
||||
Ok(_) => {
|
||||
CtrlRogBios::set_path_mutable(ASUS_SWITCH_GRAPHIC_MODE)?;
|
||||
}
|
||||
Err(err) => {
|
||||
info!("ROG Switchable Graphics (bios) not detected: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
match CtrlRogBios::check_path_exists(ASUS_POST_LOGO_SOUND) {
|
||||
Ok(_) => {
|
||||
CtrlRogBios::set_path_mutable(ASUS_POST_LOGO_SOUND)?;
|
||||
}
|
||||
Err(err) => {
|
||||
info!("ROG boot sound toggle (bios) not detected: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(CtrlRogBios { _config: config })
|
||||
}
|
||||
|
||||
fn set_path_mutable(path: &str) -> Result<(), RogError> {
|
||||
let output = Command::new("/usr/bin/chattr")
|
||||
.arg("-i")
|
||||
.arg(path)
|
||||
.output()
|
||||
.map_err(|err| RogError::Path(path.into(), err))?;
|
||||
info!("Set {} writeable: status: {}", path, output.status);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_path_exists(path: &str) -> Result<(), RogError> {
|
||||
if Path::new(path).exists() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(RogError::MissingFunction(path.into()))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_dedicated_gfx_toggle() -> bool {
|
||||
if CtrlRogBios::check_path_exists(ASUS_SWITCH_GRAPHIC_MODE).is_ok() {
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn get_gfx_mode() -> Result<i8, RogError> {
|
||||
let path = ASUS_SWITCH_GRAPHIC_MODE;
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.open(path)
|
||||
.map_err(|err| RogError::Path(path.into(), err))?;
|
||||
|
||||
let mut data = Vec::new();
|
||||
file.read_to_end(&mut data)
|
||||
.map_err(|err| RogError::Read(path.into(), err))?;
|
||||
|
||||
let idx = data.len() - 1;
|
||||
Ok(data[idx] as i8)
|
||||
}
|
||||
|
||||
pub(super) fn set_gfx_mode(&self, dedicated: bool) -> Result<(), RogError> {
|
||||
let path = ASUS_SWITCH_GRAPHIC_MODE;
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open(path)
|
||||
.map_err(|err| RogError::Path(path.into(), err))?;
|
||||
|
||||
let mut data = Vec::new();
|
||||
file.read_to_end(&mut data).unwrap();
|
||||
|
||||
let idx = data.len() - 1;
|
||||
if dedicated {
|
||||
data[idx] = 1;
|
||||
info!("Set system-level graphics mode: Dedicated Nvidia");
|
||||
} else {
|
||||
data[idx] = 0;
|
||||
info!("Set system-level graphics mode: Optimus");
|
||||
}
|
||||
file.write_all(&data)
|
||||
.map_err(|err| RogError::Path(path.into(), err))?;
|
||||
|
||||
self.update_initramfs(dedicated)?;
|
||||
|
||||
// if let Ok(ded) = CtrlRogBios::get_gfx_mode() {
|
||||
// if let Ok(vendor) = CtrlGraphics::get_vendor() {
|
||||
// if ded == 1 && vendor != "nvidia" {
|
||||
// warn!("Dedicated GFX toggle is on but driver mode is not nvidia \nSetting to nvidia driver mode");
|
||||
// CtrlGraphics::set_gfx_config(&GfxVendors::Nvidia)
|
||||
// .unwrap_or_else(|err| warn!("Gfx controller: {}", err));
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_boot_sound() -> Result<i8, RogError> {
|
||||
let path = ASUS_POST_LOGO_SOUND;
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.open(path)
|
||||
.map_err(|err| RogError::Path(path.into(), err))?;
|
||||
|
||||
let mut data = Vec::new();
|
||||
file.read_to_end(&mut data)
|
||||
.map_err(|err| RogError::Read(path.into(), err))?;
|
||||
|
||||
let idx = data.len() - 1;
|
||||
Ok(data[idx] as i8)
|
||||
}
|
||||
|
||||
pub(super) fn set_boot_sound(on: bool) -> Result<(), RogError> {
|
||||
let path = ASUS_POST_LOGO_SOUND;
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open(path)
|
||||
.map_err(|err| RogError::Path(path.into(), err))?;
|
||||
|
||||
let mut data = Vec::new();
|
||||
file.read_to_end(&mut data)
|
||||
.map_err(|err| RogError::Read(path.into(), err))?;
|
||||
|
||||
let idx = data.len() - 1;
|
||||
if on {
|
||||
data[idx] = 1;
|
||||
info!("Set boot POST sound on");
|
||||
} else {
|
||||
data[idx] = 0;
|
||||
info!("Set boot POST sound off");
|
||||
}
|
||||
file.write_all(&data)
|
||||
.map_err(|err| RogError::Path(path.into(), err))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// required for g-sync mode
|
||||
fn update_initramfs(&self, dedicated: bool) -> Result<(), RogError> {
|
||||
let mut initfs_cmd = None;
|
||||
|
||||
if Path::new(INITRAMFS_PATH).exists() {
|
||||
let mut cmd = Command::new("update-initramfs");
|
||||
cmd.arg("-u");
|
||||
initfs_cmd = Some(cmd);
|
||||
info!("Using initramfs update command 'update-initramfs'");
|
||||
} else if Path::new(DRACUT_PATH).exists() {
|
||||
let mut cmd = Command::new("dracut");
|
||||
cmd.arg("-f");
|
||||
cmd.arg("-q");
|
||||
initfs_cmd = Some(cmd);
|
||||
info!("Using initramfs update command 'dracut'");
|
||||
}
|
||||
|
||||
if let Some(mut cmd) = initfs_cmd {
|
||||
info!("Updating initramfs");
|
||||
|
||||
// If switching to Nvidia dedicated we need these modules included
|
||||
if Path::new(DRACUT_PATH).exists() && dedicated {
|
||||
cmd.arg("--add-drivers");
|
||||
cmd.arg("nvidia nvidia-drm nvidia-modeset nvidia-uvm");
|
||||
info!("System uses dracut, forcing nvidia modules to be included in init");
|
||||
} else if Path::new(INITRAMFS_PATH).exists() {
|
||||
let modules = vec![
|
||||
"nvidia\n",
|
||||
"nvidia-drm\n",
|
||||
"nvidia-modeset\n",
|
||||
"nvidia-uvm\n",
|
||||
];
|
||||
|
||||
let module_include = Path::new("/etc/initramfs-tools/modules");
|
||||
|
||||
if dedicated {
|
||||
let mut file = std::fs::OpenOptions::new()
|
||||
.append(true)
|
||||
.open(module_include)
|
||||
.map_err(|err| {
|
||||
RogError::Write(module_include.to_string_lossy().to_string(), err)
|
||||
})?;
|
||||
// add nvidia modules to module_include
|
||||
file.write_all(modules.concat().as_bytes()).unwrap();
|
||||
} else {
|
||||
let file = std::fs::OpenOptions::new()
|
||||
.read(true)
|
||||
.open(module_include)
|
||||
.map_err(|err| {
|
||||
RogError::Write(module_include.to_string_lossy().to_string(), err)
|
||||
})?;
|
||||
|
||||
let mut buf = Vec::new();
|
||||
// remove modules
|
||||
for line in std::io::BufReader::new(file).lines().flatten() {
|
||||
if !modules.contains(&line.as_str()) {
|
||||
buf.append(&mut line.as_bytes().to_vec());
|
||||
}
|
||||
}
|
||||
|
||||
let file = std::fs::OpenOptions::new()
|
||||
.write(true)
|
||||
.open(module_include)
|
||||
.map_err(|err| {
|
||||
RogError::Write(module_include.to_string_lossy().to_string(), err)
|
||||
})?;
|
||||
std::io::BufWriter::new(file).write_all(&buf).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
let status = cmd
|
||||
.status()
|
||||
.map_err(|err| RogError::Write(format!("{:?}", cmd), err))?;
|
||||
if !status.success() {
|
||||
error!("Ram disk update failed");
|
||||
return Err(RogError::Initramfs("Ram disk update failed".into()));
|
||||
} else {
|
||||
info!("Successfully updated initramfs");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
use log::warn;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use zbus::dbus_interface;
|
||||
use zvariant::ObjectPath;
|
||||
use zvariant_derive::Type;
|
||||
|
||||
use crate::{
|
||||
ctrl_anime::CtrlAnime, ctrl_charge::CtrlCharge, ctrl_leds::controller::CtrlKbdLed,
|
||||
ctrl_profiles::controller::CtrlFanAndCpu, ctrl_rog_bios::CtrlRogBios, GetSupported,
|
||||
};
|
||||
|
||||
use rog_types::supported::{
|
||||
AnimeSupportedFunctions, ChargeSupportedFunctions, FanCpuSupportedFunctions,
|
||||
LedSupportedFunctions, RogBiosSupportedFunctions,
|
||||
};
|
||||
|
||||
#[derive(Serialize, Deserialize, Type)]
|
||||
pub struct SupportedFunctions {
|
||||
pub anime_ctrl: AnimeSupportedFunctions,
|
||||
pub charge_ctrl: ChargeSupportedFunctions,
|
||||
pub fan_cpu_ctrl: FanCpuSupportedFunctions,
|
||||
pub keyboard_led: LedSupportedFunctions,
|
||||
pub rog_bios_ctrl: RogBiosSupportedFunctions,
|
||||
}
|
||||
|
||||
#[dbus_interface(name = "org.asuslinux.Daemon")]
|
||||
impl SupportedFunctions {
|
||||
fn supported_functions(&self) -> &SupportedFunctions {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::ZbusAdd for SupportedFunctions {
|
||||
fn add_to_server(self, server: &mut zbus::ObjectServer) {
|
||||
server
|
||||
.at(
|
||||
&ObjectPath::from_str_unchecked("/org/asuslinux/Supported"),
|
||||
self,
|
||||
)
|
||||
.map_err(|err| {
|
||||
warn!("SupportedFunctions: add_to_server {}", err);
|
||||
err
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
impl GetSupported for SupportedFunctions {
|
||||
type A = SupportedFunctions;
|
||||
|
||||
fn get_supported() -> Self::A {
|
||||
SupportedFunctions {
|
||||
anime_ctrl: CtrlAnime::get_supported(),
|
||||
keyboard_led: CtrlKbdLed::get_supported(),
|
||||
charge_ctrl: CtrlCharge::get_supported(),
|
||||
fan_cpu_ctrl: CtrlFanAndCpu::get_supported(),
|
||||
rog_bios_ctrl: CtrlRogBios::get_supported(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,228 +0,0 @@
|
||||
use daemon::ctrl_leds::controller::{
|
||||
CtrlKbdLed, CtrlKbdLedReloader, CtrlKbdLedTask, CtrlKbdLedZbus,
|
||||
};
|
||||
use daemon::{
|
||||
config::Config, ctrl_supported::SupportedFunctions, laptops::print_board_info, GetSupported,
|
||||
};
|
||||
use daemon::{config_anime::AnimeConfig, config_aura::AuraConfig, ctrl_charge::CtrlCharge};
|
||||
use daemon::{ctrl_anime::*, ctrl_gfx::controller::CtrlGraphics};
|
||||
use daemon::{
|
||||
ctrl_profiles::{controller::CtrlFanAndCpu, zbus::FanAndCpuZbus},
|
||||
laptops::LaptopLedData,
|
||||
};
|
||||
|
||||
use daemon::{CtrlTask, Reloadable, ZbusAdd};
|
||||
use log::LevelFilter;
|
||||
use log::{error, info, warn};
|
||||
use rog_dbus::DBUS_NAME;
|
||||
use rog_types::gfx_vendors::GfxVendors;
|
||||
use std::error::Error;
|
||||
use std::io::Write;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use daemon::ctrl_rog_bios::CtrlRogBios;
|
||||
use std::convert::Into;
|
||||
use zbus::fdo;
|
||||
use zbus::Connection;
|
||||
use zvariant::ObjectPath;
|
||||
|
||||
pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut logger = env_logger::Builder::new();
|
||||
logger
|
||||
.target(env_logger::Target::Stdout)
|
||||
.format(|buf, record| writeln!(buf, "{}: {}", record.level(), record.args()))
|
||||
.filter(None, LevelFilter::Info)
|
||||
.init();
|
||||
|
||||
info!(" daemon v{}", daemon::VERSION);
|
||||
info!(" rog-anime v{}", rog_anime::VERSION);
|
||||
info!(" rog-aura v{}", rog_aura::VERSION);
|
||||
info!(" rog-dbus v{}", rog_dbus::VERSION);
|
||||
info!("rog-profiles v{}", rog_profiles::VERSION);
|
||||
info!(" rog-types v{}", rog_types::VERSION);
|
||||
|
||||
start_daemon()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The actual main loop for the daemon
|
||||
fn start_daemon() -> Result<(), Box<dyn Error>> {
|
||||
let supported = SupportedFunctions::get_supported();
|
||||
print_board_info();
|
||||
println!("{}", serde_json::to_string_pretty(&supported).unwrap());
|
||||
|
||||
// Collect tasks for task thread
|
||||
let mut tasks: Vec<Box<dyn CtrlTask + Send>> = Vec::new();
|
||||
// Start zbus server
|
||||
let connection = Connection::new_system()?;
|
||||
fdo::DBusProxy::new(&connection)?
|
||||
.request_name(DBUS_NAME, fdo::RequestNameFlags::ReplaceExisting.into())?;
|
||||
let mut object_server = zbus::ObjectServer::new(&connection);
|
||||
|
||||
let config = Config::load();
|
||||
let enable_gfx_switching = config.gfx_managed;
|
||||
let config = Arc::new(Mutex::new(config));
|
||||
|
||||
supported.add_to_server(&mut object_server);
|
||||
|
||||
match CtrlRogBios::new(config.clone()) {
|
||||
Ok(mut ctrl) => {
|
||||
// Do a reload of any settings
|
||||
ctrl.reload()
|
||||
.unwrap_or_else(|err| warn!("Battery charge limit: {}", err));
|
||||
// Then register to dbus server
|
||||
ctrl.add_to_server(&mut object_server);
|
||||
}
|
||||
Err(err) => {
|
||||
error!("rog_bios_control: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
match CtrlCharge::new(config.clone()) {
|
||||
Ok(mut ctrl) => {
|
||||
// Do a reload of any settings
|
||||
ctrl.reload()
|
||||
.unwrap_or_else(|err| warn!("Battery charge limit: {}", err));
|
||||
// Then register to dbus server
|
||||
ctrl.add_to_server(&mut object_server);
|
||||
}
|
||||
Err(err) => {
|
||||
error!("charge_control: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
match CtrlFanAndCpu::new(config.clone()) {
|
||||
Ok(mut ctrl) => {
|
||||
ctrl.reload()
|
||||
.unwrap_or_else(|err| warn!("Profile control: {}", err));
|
||||
let tmp = Arc::new(Mutex::new(ctrl));
|
||||
FanAndCpuZbus::new(tmp).add_to_server(&mut object_server);
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Profile control: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
match CtrlAnime::new(AnimeConfig::load()) {
|
||||
Ok(ctrl) => {
|
||||
let inner = Arc::new(Mutex::new(ctrl));
|
||||
|
||||
let mut reload = CtrlAnimeReloader(inner.clone());
|
||||
reload
|
||||
.reload()
|
||||
.unwrap_or_else(|err| warn!("AniMe: {}", err));
|
||||
|
||||
let zbus = CtrlAnimeZbus(inner.clone());
|
||||
zbus.add_to_server(&mut object_server);
|
||||
|
||||
tasks.push(Box::new(CtrlAnimeTask::new(inner)));
|
||||
}
|
||||
Err(err) => {
|
||||
error!("AniMe control: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
let laptop = LaptopLedData::get_data();
|
||||
let aura_config = AuraConfig::load(&laptop);
|
||||
match CtrlKbdLed::new(laptop, aura_config) {
|
||||
Ok(ctrl) => {
|
||||
let inner = Arc::new(Mutex::new(ctrl));
|
||||
|
||||
let mut reload = CtrlKbdLedReloader(inner.clone());
|
||||
reload
|
||||
.reload()
|
||||
.unwrap_or_else(|err| warn!("Keyboard LED control: {}", err));
|
||||
|
||||
CtrlKbdLedZbus::new(inner.clone()).add_to_server(&mut object_server);
|
||||
let task = CtrlKbdLedTask(inner);
|
||||
tasks.push(Box::new(task));
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Keyboard control: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
// Graphics switching requires some checks on boot specifically for g-sync capable laptops
|
||||
if enable_gfx_switching {
|
||||
match CtrlGraphics::new(config.clone()) {
|
||||
Ok(mut ctrl) => {
|
||||
// Need to check if a laptop has the dedicated gfx switch
|
||||
if CtrlRogBios::has_dedicated_gfx_toggle() {
|
||||
if let Ok(ded) = CtrlRogBios::get_gfx_mode() {
|
||||
if let Ok(config) = config.lock() {
|
||||
if ded == 1 {
|
||||
warn!("Dedicated GFX toggle is on but driver mode is not nvidia \nSetting to nvidia driver mode");
|
||||
let devices = ctrl.devices();
|
||||
let bus = ctrl.bus();
|
||||
CtrlGraphics::do_vendor_tasks(
|
||||
GfxVendors::Nvidia,
|
||||
false,
|
||||
&devices,
|
||||
&bus,
|
||||
)?;
|
||||
} else if ded == 0 {
|
||||
info!("Dedicated GFX toggle is off");
|
||||
let devices = ctrl.devices();
|
||||
let bus = ctrl.bus();
|
||||
CtrlGraphics::do_vendor_tasks(
|
||||
config.gfx_mode,
|
||||
false,
|
||||
&devices,
|
||||
&bus,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ctrl.reload()
|
||||
.unwrap_or_else(|err| error!("Gfx controller: {}", err));
|
||||
ctrl.add_to_server(&mut object_server);
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Gfx control: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: implement messaging between threads to check fails
|
||||
|
||||
// Run tasks
|
||||
let handle = std::thread::Builder::new()
|
||||
.name("asusd watch".to_string())
|
||||
.spawn(move || loop {
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
|
||||
for ctrl in tasks.iter() {
|
||||
ctrl.do_task()
|
||||
.map_err(|err| {
|
||||
warn!("do_task error: {}", err);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
});
|
||||
|
||||
// Run zbus server
|
||||
object_server
|
||||
.with(
|
||||
&ObjectPath::from_str_unchecked("/org/asuslinux/Charge"),
|
||||
|obj: &CtrlCharge| {
|
||||
let x = obj.limit();
|
||||
obj.notify_charge(x as u8)
|
||||
},
|
||||
)
|
||||
.map_err(|err| {
|
||||
warn!("object_server notify_charge error: {}", err);
|
||||
})
|
||||
.ok();
|
||||
|
||||
// Loop to check errors and iterate zbus server
|
||||
loop {
|
||||
if let Err(err) = &handle {
|
||||
error!("{}", err);
|
||||
}
|
||||
if let Err(err) = object_server.try_handle_next() {
|
||||
error!("{}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
use log::{info, warn};
|
||||
use rog_aura::AuraModeNum;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Read;
|
||||
|
||||
pub const ASUS_LED_MODE_CONF: &str = "/etc/asusd/asusd-ledmodes.toml";
|
||||
pub const ASUS_KEYBOARD_DEVICES: [&str; 4] = ["1866", "1869", "1854", "19b6"];
|
||||
|
||||
pub fn print_board_info() {
|
||||
let dmi = sysfs_class::DmiId::default();
|
||||
let board_name = dmi.board_name().expect("Could not get board_name");
|
||||
let prod_name = dmi.product_name().expect("Could not get product_name");
|
||||
let prod_family = dmi.product_family().expect("Could not get product_family");
|
||||
|
||||
info!("Product name: {}", prod_name.trim());
|
||||
info!("Product family: {}", prod_family.trim());
|
||||
info!("Board name: {}", board_name.trim());
|
||||
}
|
||||
|
||||
pub fn print_modes(supported_modes: &[u8]) {
|
||||
if !supported_modes.is_empty() {
|
||||
info!("Supported Keyboard LED modes are:");
|
||||
for mode in supported_modes {
|
||||
let mode = <&str>::from(&<AuraModeNum>::from(*mode));
|
||||
info!("- {}", mode);
|
||||
}
|
||||
info!(
|
||||
"If these modes are incorrect you can edit {}",
|
||||
ASUS_LED_MODE_CONF
|
||||
);
|
||||
} else {
|
||||
info!("No RGB control available");
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
struct LedSupportFile {
|
||||
led_data: Vec<LaptopLedData>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct LaptopLedData {
|
||||
pub prod_family: String,
|
||||
pub board_names: Vec<String>,
|
||||
pub standard: Vec<AuraModeNum>,
|
||||
pub multizone: bool,
|
||||
pub per_key: bool,
|
||||
}
|
||||
|
||||
impl LaptopLedData {
|
||||
pub fn get_data() -> Self {
|
||||
let dmi = sysfs_class::DmiId::default();
|
||||
let board_name = dmi.board_name().expect("Could not get board_name");
|
||||
let prod_family = dmi.product_family().expect("Could not get product_family");
|
||||
|
||||
if let Some(modes) = LedSupportFile::load_from_config() {
|
||||
if let Some(data) = modes.matcher(&prod_family, &board_name) {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
info!("Using generic LED control for keyboard brightness only");
|
||||
LaptopLedData {
|
||||
prod_family,
|
||||
board_names: vec![board_name],
|
||||
standard: vec![],
|
||||
multizone: false,
|
||||
per_key: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LedSupportFile {
|
||||
/// Consumes the LEDModes
|
||||
fn matcher(self, prod_family: &str, board_name: &str) -> Option<LaptopLedData> {
|
||||
for config in self.led_data {
|
||||
if prod_family.contains(&config.prod_family) {
|
||||
for board in &config.board_names {
|
||||
if board_name.contains(board) {
|
||||
info!("Matched to {} {}", config.prod_family, board);
|
||||
return Some(config);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn load_from_config() -> Option<Self> {
|
||||
if let Ok(mut file) = OpenOptions::new().read(true).open(&ASUS_LED_MODE_CONF) {
|
||||
let mut buf = String::new();
|
||||
if let Ok(l) = file.read_to_string(&mut buf) {
|
||||
if l == 0 {
|
||||
warn!("{} is empty", ASUS_LED_MODE_CONF);
|
||||
} else {
|
||||
return Some(toml::from_str(&buf).unwrap_or_else(|_| {
|
||||
panic!("Could not deserialise {}", ASUS_LED_MODE_CONF)
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
warn!("Does {} exist?", ASUS_LED_MODE_CONF);
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
#![deny(unused_must_use)]
|
||||
/// Configuration loading, saving
|
||||
pub mod config;
|
||||
pub mod config_anime;
|
||||
pub mod config_aura;
|
||||
pub(crate) mod config_old;
|
||||
/// Control of AniMe matrix display
|
||||
pub mod ctrl_anime;
|
||||
/// Control of battery charge level
|
||||
pub mod ctrl_charge;
|
||||
/// GPU switching and power
|
||||
pub mod ctrl_gfx;
|
||||
/// Keyboard LED brightness control, RGB, and LED display modes
|
||||
pub mod ctrl_leds;
|
||||
/// Control CPU min/max freq and turbo, fan mode, fan curves
|
||||
///
|
||||
/// Intel machines can control:
|
||||
/// - CPU min/max frequency
|
||||
/// - CPU turbo enable/disable
|
||||
/// - Fan mode (normal, boost, silent)
|
||||
///
|
||||
/// AMD machines can control:
|
||||
/// - CPU turbo enable/disable
|
||||
/// - Fan mode (normal, boost, silent)
|
||||
/// - Fan min/max RPM curve
|
||||
pub mod ctrl_profiles;
|
||||
/// Control ASUS bios function such as boot sound, Optimus/Dedicated gfx mode
|
||||
pub mod ctrl_rog_bios;
|
||||
/// Laptop matching to determine capabilities
|
||||
pub mod laptops;
|
||||
|
||||
/// Fetch all supported functions for the laptop
|
||||
pub mod ctrl_supported;
|
||||
|
||||
mod error;
|
||||
|
||||
use crate::error::RogError;
|
||||
use config::Config;
|
||||
use zbus::ObjectServer;
|
||||
|
||||
pub static VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
pub trait Reloadable {
|
||||
fn reload(&mut self) -> Result<(), RogError>;
|
||||
}
|
||||
|
||||
pub trait ZbusAdd {
|
||||
fn add_to_server(self, server: &mut ObjectServer);
|
||||
}
|
||||
|
||||
pub trait CtrlTask {
|
||||
fn do_task(&self) -> Result<(), RogError>;
|
||||
}
|
||||
|
||||
pub trait CtrlTaskComplex {
|
||||
type A;
|
||||
|
||||
fn do_task(&mut self, config: &mut Config, event: Self::A);
|
||||
}
|
||||
|
||||
pub trait GetSupported {
|
||||
type A;
|
||||
|
||||
fn get_supported() -> Self::A;
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
# Enable runtime PM for NVIDIA VGA/3D controller devices on driver bind
|
||||
ACTION=="bind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030000", TEST=="power/control", ATTR{power/control}="auto"
|
||||
ACTION=="bind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030200", TEST=="power/control", ATTR{power/control}="auto"
|
||||
|
||||
# Disable runtime PM for NVIDIA VGA/3D controller devices on driver unbind
|
||||
ACTION=="unbind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030000", TEST=="power/control", ATTR{power/control}="on"
|
||||
ACTION=="unbind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030200", TEST=="power/control", ATTR{power/control}="on"
|
||||
@@ -1,4 +0,0 @@
|
||||
Section "ServerLayout"
|
||||
Identifier "layout"
|
||||
Option "AllowNVIDIAGPUScreens"
|
||||
EndSection
|
||||
@@ -1,50 +0,0 @@
|
||||
function _asusctl() {
|
||||
local line
|
||||
|
||||
_arguments -C \
|
||||
{-h,--help}'[print help message]' \
|
||||
{-v,--version}'[print version number]' \
|
||||
{-k,--kbd-bright}':[Set keyboard brightness (off, low, med, high)]' \
|
||||
{-p,--pwr-profile}':[Set power profile (silent, normal, boost)]' \
|
||||
{-c,--chg-limit}':[Set charging limit (20-100)]' \
|
||||
': :((led-mode\:"Set the keyboard lighting from built-in modes" profile\:"Create and configure profiles" graphics\:"Set the graphics mode"))' \
|
||||
'*::arg:->args'
|
||||
case $line[1] in
|
||||
led-mode)
|
||||
_arguments ': :((static\:"set a single static colour"
|
||||
breathe\:"pulse between one or two colours"
|
||||
strobe\:"strobe through all colours"
|
||||
rainbow\:"rainbow cycling in one of four directions"
|
||||
star\:"rain pattern mimicking raindrops"
|
||||
rain\:"rain pattern of three preset colours"
|
||||
highlight\:"pressed keys are highlighted to fade"
|
||||
laser\:"pressed keys generate horizontal laser"
|
||||
ripple\:"pressed keys ripple outwards like a splash"
|
||||
pulse\:"set a rapid pulse"
|
||||
comet\:"set a vertical line zooming from left"
|
||||
flash\:"set a wide vertical line zooming from left"
|
||||
multi-static\:"4-zone multi-colour"))' \
|
||||
{-h,--help}'[print help message]' \
|
||||
'-c:[set the RGB value e.g, ff00ff]' \
|
||||
'-s:[set the speed (low, med, high)]'
|
||||
;;
|
||||
profile)
|
||||
_arguments {-h,--help}'[print help message]' \
|
||||
{-c,--create}"[create the profile if it doesn't exist]" \
|
||||
{-t,--turbo}':[enable or disable cpu turbo]' \
|
||||
{-m,--min-percentage}':[set min cpu scaling (intel)]' \
|
||||
{-M,--max-percentage}':[set max cpu scaling (intel)]' \
|
||||
{-p,--preset}':[<silent, normal, boost>]' \
|
||||
{-C,--curve}':[set fan curve]'
|
||||
|
||||
;;
|
||||
graphics)
|
||||
_arguments {-h,--help}'[print help message]' \
|
||||
{-m,--mode}':[Set graphics mode (nvidia, hybrid, compute, integrated)]' \
|
||||
{-g,--get}'[Get the current mode]' \
|
||||
{-p,--pow}'[Get the current power status]' \
|
||||
{-f,--force}'[Do not ask for confirmation]'
|
||||
;;
|
||||
esac
|
||||
}
|
||||
compdef _asusctl asusctl
|
||||
@@ -1,14 +0,0 @@
|
||||
[Unit]
|
||||
Description=ASUS Notifications
|
||||
StartLimitInterval=200
|
||||
StartLimitBurst=2
|
||||
|
||||
[Service]
|
||||
ExecStartPre=/usr/bin/sleep 2
|
||||
ExecStart=/usr/bin/asus-notify
|
||||
Restart=on-failure
|
||||
RestartSec=1
|
||||
Type=simple
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
@@ -1,12 +0,0 @@
|
||||
[Unit]
|
||||
Description=ASUS Notebook Control
|
||||
After=basic.target syslog.target
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/bin/asusd
|
||||
Restart=on-failure
|
||||
Type=dbus
|
||||
BusName=org.asuslinux.Daemon
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -1,97 +0,0 @@
|
||||
[[led_data]]
|
||||
prod_family = "Zephyrus S"
|
||||
board_names = ["GX502", "GX701", "G531", "GL531", "G532"]
|
||||
standard = ["Static", "Breathe", "Strobe", "Rainbow", "Star", "Rain", "Highlight", "Laser", "Ripple", "Pulse", "Comet", "Flash"]
|
||||
multizone = false
|
||||
per_key = true
|
||||
|
||||
[[led_data]]
|
||||
prod_family = "Zephyrus M"
|
||||
board_names = ["GU502GV"]
|
||||
standard = ["Static", "Breathe", "Strobe", "Rainbow", "Star", "Rain", "Highlight", "Laser", "Ripple", "Pulse", "Comet", "Flash"]
|
||||
multizone = false
|
||||
per_key = true
|
||||
|
||||
[[led_data]]
|
||||
prod_family = "ROG Zephyrus M15"
|
||||
board_names = ["GU502LW"]
|
||||
standard = ["Static", "Breathe", "Strobe", "Rainbow", "Star", "Rain", "Highlight", "Laser", "Ripple", "Pulse", "Comet", "Flash"]
|
||||
multizone = false
|
||||
per_key = true
|
||||
|
||||
[[led_data]]
|
||||
prod_family = "ROG Zephyrus M15"
|
||||
board_names = ["GU502LU"]
|
||||
standard = ["Static", "Breathe", "Strobe", "Pulse"]
|
||||
multizone = false
|
||||
per_key = false
|
||||
|
||||
[[led_data]]
|
||||
prod_family = "Zephyrus"
|
||||
board_names = ["GM501GM", "GX531"]
|
||||
standard = ["Static", "Breathe", "Strobe", "Rainbow", "Pulse"]
|
||||
multizone = true
|
||||
per_key = false
|
||||
|
||||
[[led_data]]
|
||||
prod_family = "ROG Strix"
|
||||
board_names = ["G531GW", "G733QS", "G733QR"]
|
||||
standard = ["Static", "Breathe", "Strobe", "Rainbow", "Star", "Rain", "Highlight", "Laser", "Ripple", "Pulse", "Comet", "Flash"]
|
||||
multizone = false
|
||||
per_key = true
|
||||
|
||||
[[led_data]]
|
||||
prod_family = "ROG Strix"
|
||||
board_names = ["GX531", "G512LV", "G712LV", "G712LW"]
|
||||
standard = ["Static", "Breathe", "Strobe", "Rainbow", "Pulse"]
|
||||
multizone = true
|
||||
per_key = false
|
||||
|
||||
[[led_data]]
|
||||
prod_family = "ROG Strix"
|
||||
board_names = ["G512LI", "G712LI", "G531GD"]
|
||||
standard = ["Static", "Breathe", "Strobe", "Rainbow", "Pulse"]
|
||||
multizone = false
|
||||
per_key = false
|
||||
|
||||
[[led_data]]
|
||||
prod_family = "Strix"
|
||||
board_names = ["G731GV", "G731GW", "G531GV"]
|
||||
standard = ["Static", "Breathe", "Strobe", "Rainbow"]
|
||||
multizone = true
|
||||
per_key = false
|
||||
|
||||
[[led_data]]
|
||||
prod_family = "Strix"
|
||||
board_names = ["G731GT", "G731GU", "G531GT", "G531GU"]
|
||||
standard = ["Static", "Breathe", "Strobe", "Rainbow"]
|
||||
multizone = false
|
||||
per_key = false
|
||||
|
||||
[[led_data]]
|
||||
prod_family = "Strix Scar"
|
||||
board_names = ["G531", "G731"]
|
||||
standard = ["Static", "Breathe", "Strobe", "Rainbow", "Star", "Rain", "Highlight", "Laser", "Ripple", "Pulse", "Comet", "Flash"]
|
||||
multizone = true
|
||||
per_key = true
|
||||
|
||||
[[led_data]]
|
||||
prod_family = "ROG"
|
||||
board_names = ["GL553VE"]
|
||||
standard = ["Static", "Breathe", "Strobe"]
|
||||
multizone = true
|
||||
per_key = false
|
||||
|
||||
[[led_data]]
|
||||
prod_family = "ROG Zephyrus G14"
|
||||
board_names = ["GA401Q"]
|
||||
standard = ["Static", "Breathe", "Pulse"]
|
||||
multizone = false
|
||||
per_key = false
|
||||
|
||||
[[led_data]]
|
||||
prod_family = "ROG Zephyrus"
|
||||
board_names = ["GX550L"]
|
||||
standard = ["Static", "Breathe", "Strobe", "Rainbow", "Star", "Rain", "Highlight", "Laser", "Ripple", "Pulse", "Comet", "Flash"]
|
||||
multizone = false
|
||||
per_key = true
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user