Compare commits
760 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
c14768182c | ||
|
|
ef7e2135bf | ||
|
|
2b58e259de | ||
|
|
ba03e8feb8 | ||
|
|
7771c6b8da | ||
|
|
04c9285ee6 | ||
|
|
bf4141e4b8 | ||
|
|
233315f668 | ||
|
|
8332fb12f1 | ||
|
|
594e69f9b7 | ||
|
|
0aac0ce495 | ||
|
|
e24b4858a4 | ||
|
|
cf2b459e48 | ||
|
|
895179fdad | ||
|
|
fe3e8792eb | ||
|
|
1916641e2e | ||
|
|
3ea0737be9 | ||
|
|
c67373a830 | ||
|
|
41cbf4d353 | ||
|
|
7a4c14f7b8 | ||
|
|
f52a4d464a | ||
|
|
aa71592a31 | ||
|
|
dc6e8f8dcb | ||
|
|
1a4836246f | ||
|
|
ab80b0742f | ||
|
|
6926aeed20 | ||
|
|
f95e42e4b9 | ||
|
|
82bee6b86e | ||
|
|
bd9bc8bcff | ||
|
|
8a6d364304 | ||
|
|
64d99a3e05 | ||
|
|
59f54b76f6 | ||
|
|
6f36d91281 | ||
|
|
e9f1fa01fc | ||
|
|
0d3a5d266b | ||
|
|
cc28cee8bd | ||
|
|
6ebf0c2bb2 | ||
|
|
77c658c94e | ||
|
|
df64a51372 | ||
|
|
0657c6cc74 | ||
|
|
f116905e85 | ||
|
|
e515741efa | ||
|
|
d516abdc92 | ||
|
|
ece565de1c | ||
|
|
eb83d1a835 | ||
|
|
7d0f15d738 | ||
|
|
8010da0891 | ||
|
|
aa500c35c4 | ||
|
|
2af33a0416 | ||
|
|
9b4ed6eb62 | ||
|
|
47c1ca9fe4 | ||
|
|
3cd624daf0 | ||
|
|
fa16864a3e | ||
|
|
bfc31b06d5 | ||
|
|
d854f7da1b | ||
|
|
6d746b21a5 | ||
|
|
226c083a51 | ||
|
|
de59d00949 | ||
|
|
7ff01f12e9 | ||
|
|
fbc248177a | ||
|
|
fc3d7653f5 | ||
|
|
2dc70ea6af | ||
|
|
01345b28a5 | ||
|
|
4eeacea832 | ||
|
|
6bf0fdd117 | ||
|
|
7fcde7df17 | ||
|
|
543b0b817f | ||
|
|
5a7d31fdf6 | ||
|
|
301c532b65 | ||
|
|
df7ae4d014 | ||
|
|
96ceef1bdb | ||
|
|
bc72b93625 | ||
|
|
03b338bdfa | ||
|
|
7a51cd1c70 | ||
|
|
0449a4b06b | ||
|
|
bc46fa2b1e | ||
|
|
759ddeb270 | ||
|
|
538e111e78 | ||
|
|
45ab568f7a | ||
|
|
b32089843a | ||
|
|
d960aacf4f | ||
|
|
1c48ab227d | ||
|
|
6528ec95c2 | ||
|
|
53ee6015d0 | ||
|
|
ad150903af |
12
.cargo-husky/hooks/pre-commit
Executable file
@@ -0,0 +1,12 @@
|
||||
#!/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 test --all'
|
||||
cargo test --all
|
||||
echo '+cargo cranky'
|
||||
cargo cranky
|
||||
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
@@ -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
|
||||
21
.gitignore
vendored
@@ -1,2 +1,21 @@
|
||||
/target
|
||||
vendor.tar.xz
|
||||
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 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
|
||||
- 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
|
||||
|
||||
|
||||
591
CHANGELOG.md
@@ -5,14 +5,597 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [v5.0.6]
|
||||
- Revert egui update due to a lot of issues arising from window closing.
|
||||
|
||||
## [v5.0.5]
|
||||
- Resync. A release was made that was missing some commits.
|
||||
|
||||
## [v5.0.4]
|
||||
### Changed
|
||||
- Added G834JZ led config
|
||||
- Fix in ROGCC to apply the actual effect changed
|
||||
- Re-enable all fan curves (available) in ROGCC
|
||||
- Update smithay-client-toolkit
|
||||
|
||||
## [v5.0.3]
|
||||
### Changed
|
||||
- Fix and error in platform ppt value gets
|
||||
- Fix to asusctl CLI where an incorrect enum variant was used in throttle check
|
||||
- Turn some error messages in to warning or info to prevent confusion
|
||||
- Re-add the keyboard power settings in rogcc
|
||||
- Add two new aura dbus properties for providing some basic info on aura modes/power
|
||||
|
||||
## [v5.0.2]
|
||||
### Changed
|
||||
- Fan-curves: nuke a few async deadlocks
|
||||
- Anime: force power/wakeup disabled to prevent idiotic random wakes
|
||||
|
||||
## [v5.0.1]
|
||||
### Changed
|
||||
- Fix setting next fan profile
|
||||
- Fix the assud.service
|
||||
- Fix dbus signature of some power setting types for some keyboards
|
||||
|
||||
## [v5.0.0]
|
||||
### Added
|
||||
- Gnome 45 plugin
|
||||
- Support for G513RW LED modes
|
||||
- Support Rog Ally LED modes (basic)
|
||||
- Add on_lid_closed and on_external_power_changed events for running certain tasks
|
||||
- Anime dbus: add:
|
||||
- SetOffWhenUnplugged, also add asusctl CLI option
|
||||
- SetOffWhenSuspended, also add asusctl CLI option
|
||||
- SetOffWhenLidClosed, also add asusctl CLI option
|
||||
- Anime: add brightness_on_battery config option
|
||||
- Platform: add `post_animation_sound`, kernel 6.7+ requires patch
|
||||
- Add changing of CPU energy perfromance preference in relation to throttle_thermal_policy. This means that the CPU correctly behaves according to throttle_thermal_policy (and platform profile use is *removed*)
|
||||
- Add setting of throttle_thermal_policy on power plug/unplug
|
||||
|
||||
### Changed
|
||||
- asusd: remove set_image_brightness for anime
|
||||
- asusd: refactor how certain things like display enable/builtins are toggled
|
||||
- Refactor sleep/shutdown tasks
|
||||
- rog-control-center: ensure brightness slider works correctly
|
||||
- Update `smithay-client-toolkit` for fix to issue #407
|
||||
- Remove the "sleep" animations from Anime to stop preventing the display-off
|
||||
- Anime:
|
||||
- Ensure display is off when lid is closed and option is set
|
||||
- Ensure display is off when on battery and option is set
|
||||
- Ensure builtin animations run instead of custom animations if option is set
|
||||
|
||||
### Breaking
|
||||
- DBUS stuff. Again. All of it.
|
||||
|
||||
## [v4.7.2]
|
||||
### Added
|
||||
- Support for G733PZ LED modes
|
||||
- Support for G713RC LED modes
|
||||
|
||||
### Changed
|
||||
- Fix loading of fan curves from stored settings
|
||||
|
||||
## [v4.7.1]
|
||||
### Changed
|
||||
- Fixes to asusctl CLI tool to show fan curves
|
||||
- Fixes to asusd to ensure fan curve defaults are loaded if the config file fails
|
||||
- Further refine the asusctl CLI for fan-curve control
|
||||
- Fixes to AniMe detection
|
||||
- Fixes to aura config creation/loading
|
||||
|
||||
### Added
|
||||
- Support for GV601V LED modes
|
||||
|
||||
## [v4.7.0]
|
||||
### Added
|
||||
- Support for FX507Z LED modes
|
||||
- Support for GL503V LED modes
|
||||
- Support for G733C LED modes
|
||||
- Support for GV601VI LED modes
|
||||
- Support for FX505G LED modes
|
||||
- Support for GA402X LED modes
|
||||
- Support for G634J LED modes (layout is in progress)
|
||||
- Support the Rear Glow on some laptops
|
||||
- Added field to aura_support to determine which LED power zones are supported. This will need folks to contribute data.
|
||||
- Support M16 matrix display
|
||||
- Custom images
|
||||
- Pixel gifs
|
||||
- Power options
|
||||
- Builtin animations
|
||||
- In-progress simulators for GA402, GU604 animatrix, optional build and takes a single arg
|
||||
- Add `model_override` option to anime config, this is handy for forcing a model for "Unknown" anime, and for simulators
|
||||
- Add `mini_led_mode` support to asusd and zbus crates (requires kernel patch https://lkml.org/lkml/2023/6/19/1264)
|
||||
- Add `mini_led_mode` toggle to rog-control-center GUI, tray, notifications
|
||||
- Add generation of typescript types from the rust types used via dbus using typeshare
|
||||
- Add generation of introspection XML from asusd dbus
|
||||
- Add a reworked gnome extension to the main repo under `desktop-extensions/gnome/`. This was done to better keep the extension in sync with work done on asusd, especially around breaking dbus
|
||||
- Add support for the mid fan custom curves on some laptops
|
||||
### Changed
|
||||
- Move FX506HC to FX506H in arua DB to catch full series of this range
|
||||
- Move FX506LH to FX506L in arua DB to catch full series of this range
|
||||
- Move G513I* to G513I in arua DB to catch full series of this range
|
||||
- Remove notification handle tracking limit, fixes KDE issue with profile notif
|
||||
- Rename daemon and daemon-user crates to asusd and asusd-user to not be confusing in workspace naming
|
||||
- Prevent the multiple notifications from a profile change from occuring (too many functions with side effects!)
|
||||
- Apply keyboard brightness when setting a mode
|
||||
- Update GL503 led config
|
||||
- Arua LED power control has been heavily refactored for 0x19b6+ devices
|
||||
- Rog Control Center:
|
||||
- Added option to enable/disable system tray
|
||||
- Added button to fully quit app (exits from background)
|
||||
- Moved application settings to new page
|
||||
- Aura LED power refactor is now taken advantage of in RCC, exposing all settings
|
||||
### BREAKING
|
||||
- All Anime related DBUS methods/notifs are changed
|
||||
- All dbus interfaces that handled an enum have now been forced to use the enum as String type, not uint or similar, this unfortunately breaks a heap of stuff but has the benefit of allowing asusctl to use crates to generate a typescript (or other) binding to the types being used by zbus for the proxies. The implication here is that there will be an eventual tighter integration with the gnome extension and maybe KDE also.
|
||||
|
||||
## [v4.6.2]
|
||||
- Fix rog-control-center not reopening if `startup_in_background` is set
|
||||
|
||||
## [v4.6.1]
|
||||
### Added
|
||||
- Support for G733Z LED modes
|
||||
- Support for GU604V LED modes
|
||||
- Support for GX650P LED modes
|
||||
- Support for GV604I LED modes
|
||||
- Support for FX516P LED modes (this laptop still has further issues, will require a patched kernel when patch is ready)
|
||||
- Add device code for the Z13 ACRNM keyboard (requires kernel patch, in progress)
|
||||
- Support for GV301VIC LED modes
|
||||
- Add device code for the plain Z13 keyboard (requires kernel patch, in progress)
|
||||
- Support for GV301V LED modes
|
||||
### Changed
|
||||
- Adjustments to Anime system events thread
|
||||
- Add "sleep" animetion config options to anime config
|
||||
- rog-control-center dark/light mode persistency
|
||||
- Adjustments to keyboard detection
|
||||
- Better support of using supergfxctl when available (tray icon and menu)
|
||||
- Check supergfx version before enabling use in tray (require 5.1.0+)
|
||||
- Update allowed Aura modes on asusd restart if changed
|
||||
- Set tray icon for dgpu to "On" if in Vfio mode to prevent confusion
|
||||
- Add support for Logout/Reboot in notification for KDE
|
||||
|
||||
## [v4.6.0]
|
||||
### Added
|
||||
- Support for GL703GE keyboard layout
|
||||
- Support for G533Z modes and keyboard layout
|
||||
### Changed
|
||||
- Better handling of `/etc/asusd` not existing
|
||||
- Better handling of non-existant config files
|
||||
- Move all config handling to generic traits for better consistency
|
||||
- Re-parse all configs to RON format
|
||||
- Move fan-curve config to own config file
|
||||
- Added option to set `disable_nvidia_powerd_on_battery`
|
||||
- Add short log entry to throttle_thermal_policy change detection
|
||||
- ROGCC: Don't notify user if changing to same mux mode
|
||||
- ROGCC: Add CLI opt for loading a keyboard layout for testing, with live-reload on file change
|
||||
- ROGCC: Add CLI opt for viewing all layout files + filenames to help find a layout matching your laptop
|
||||
+ Both of these options would hopefully be temporary and replaced with a "wizard" GUI helper
|
||||
- Fix profile controller not detecting if platform_profile is changed
|
||||
- Fix remove the leftover initial config writes on `new()` for some controllers to prevent resetting settings on startup
|
||||
+ refactor the loading of systemd curve defaults and config file
|
||||
### BREAKING
|
||||
- Rename aura dbus method from `per_key_raw` to `direct_addressing_raw` and add doc comment
|
||||
- Changes to aura.conf:
|
||||
- Changes to asusd-ledmodes.toml:
|
||||
+ Rename `standard` to `basic_modes`
|
||||
+ Rename `multizone` to `basic_zones`
|
||||
+ Raname `per_key` to `advanced` and change type from `bool` to `AdvancedAuraType`
|
||||
+ Removed `prod_family`
|
||||
+ Split all entries to `board_name` (separating `board_names`) (now a huge file)
|
||||
+ removed `asusd-ledmodes.toml` in favour of `aura_support.ron` due to an unsupported type in toml
|
||||
- Rename and adjust `LedSupportedFunctions` to closely match the above
|
||||
|
||||
## [v4.5.8]
|
||||
### Changed
|
||||
- Fix incorrect stop/start order of nvidia-powerd on AC plug/unplug
|
||||
|
||||
## [v4.5.7]
|
||||
### Changed
|
||||
- ROGCC: Don't notify user if changing to same mux mode
|
||||
-
|
||||
|
||||
## [v4.5.7]
|
||||
### Changed
|
||||
- ROGCC: Don't notify user if changing to same mux mode
|
||||
- asusd: don't block on systemd-unit change: removes all shoddy external command calls in favour of async dbus calls
|
||||
|
||||
## [v4.5.6]
|
||||
### Changed
|
||||
- Fix tasks not always running correctly on boot/sleep/wake/shutdown by finishing the move to async
|
||||
- Change how the profile/fan change task monitors changes due to TUF laptops behaving slightly different
|
||||
- ROGCC: Better handle the use of GPU MUX without supergfxd
|
||||
- ROGCC: Track if reboot required when not using supergfxd
|
||||
- Add env var for logging levels to daemon and gui (`RUST_LOG=<error|warn|info|debug|trace>`)
|
||||
- ROGCC: Very basic support for running a command on AC/Battery switching, this is in config at `~/.config/rog/rog-control-center.cfg`, and for now must be edited by hand and ROGCC restarted (run ROGCC in BG to use effectively)
|
||||
+ Run ROGCC from terminal to see errors of the AC/Battery command
|
||||
+ Support for editing via ROGCC GUI will come in future
|
||||
+ This is ideal for userspace tasks
|
||||
- asusd: Very basic support for running a command on AC/Battery switching, this is in config at `/etc/asusd/asusd.conf`. A restart of asusd is not required if edited.
|
||||
+ This is ideal for tasks that require root access (BE SAFE!)
|
||||
- The above AC/Battery commands are probably best set to run a script for more complex tasks
|
||||
- asusd: check if nvidia-powerd enabled before toggling
|
||||
|
||||
## [v4.5.5]
|
||||
### Changed
|
||||
- remove an unwrap() causing panic on main ROGCC thread
|
||||
|
||||
## [v4.5.4]
|
||||
### Changed
|
||||
- ROGCC:: Allow ROGCC to run without supergfxd
|
||||
- ROGCC: Tray/notifs now reads dGPU status directly via supergfx crate (supergfxd not required)
|
||||
- Add rust-toolchain to force minimum rust version
|
||||
|
||||
## [v4.5.3]
|
||||
### Changed
|
||||
- Adjust how fan graph in ROGCC works, deny incorrect graphs
|
||||
- Fix to apply the fan curve change in ROGCC to the correct profile
|
||||
- Support for G713RS LED modes (Author: Peter Ivanov)
|
||||
- Support for G713RM LED modes (Author: maxbachmann)
|
||||
- Fix VivoBook detection
|
||||
- Update dependencies to get latest winit crate (fixes various small issues)
|
||||
|
||||
## [v4.5.2]
|
||||
### Changed
|
||||
- Update dependencies and bump version
|
||||
|
||||
## [v4.5.1]
|
||||
### Added
|
||||
- Support for FA506IE LED modes (Author: Herohtar)
|
||||
### Changed
|
||||
- Add a basic system tray with dGPU status and gpu mode switch actions
|
||||
- Fixup some notifications in ROGCC
|
||||
- Add config options for notifications for ROGCC
|
||||
- Share states with tray process in ROGCC
|
||||
- Share tates with tray process in ROGCC
|
||||
|
||||
## [v4.5.0]
|
||||
### Added
|
||||
- intofy watches on:
|
||||
- `charge_control_end_threshold`
|
||||
- `panel_od`
|
||||
- `gpu_mux_mode`
|
||||
- `platform_profile`
|
||||
- keyboard brightness
|
||||
- These allow for updating any associated config and sending dbus notifications.
|
||||
- New dbus methods
|
||||
- `DgpuDisable`
|
||||
- `SetDgpuDisable`
|
||||
- `NotifyDgpuDisable`
|
||||
- `EgpuEnable`
|
||||
- `SetEgpuEnable`
|
||||
- `NotifyEgpuEnable`
|
||||
- `MainsOnline` (This is AC, check if plugged in or not)
|
||||
- `NotifyMainsOnline`
|
||||
- `nvidia-powerd.service` will now enable or disable depending on the AC power state
|
||||
and on resume/boot (hybrid boot). It has been proven that this nvidia daemon can be
|
||||
problematic when on battery, not allowing the dgpu to suspend within decent time and
|
||||
sometimes blocking it completely.
|
||||
- Notification to rog-control-center of dGPU state change
|
||||
### Changed
|
||||
- Use loops to ensure that mutex is gained for LED changes.
|
||||
- asusctl now uses tokio for async runtime. This helps simplify some code.
|
||||
- Properly fix notifs used in rog-control-center
|
||||
### Breaking
|
||||
- DBUS: all charge control methods renamed to:
|
||||
- `ChargeControlEndThreshold`
|
||||
- `SetChargeControlEndThreshold`
|
||||
- `NotifyChargeControlEndThreshold`
|
||||
- DBUS: all panel overdrive methods renamed to:
|
||||
- `PanelOd` (from PanelOverdrive)
|
||||
- `SetPanelOd`
|
||||
- `NotifyPanelOd`
|
||||
- Path `/org/asuslinux/Charge` changed to `/org/asuslinux/Power`
|
||||
|
||||
## [v4.4.0] - 2022-08-29
|
||||
### Added
|
||||
- Support for per-key config has been added to `asusd-user`. At the moment it is
|
||||
basic with only a few effects done. Please see the manual for more information.
|
||||
- Support for unzoned and per-zone effects on some laptops. As above.
|
||||
- Added three effects to use with Zoned or Per-Key:
|
||||
+ Static, Breathe, Flicker. More to come.
|
||||
- Support for G713RS LED modes
|
||||
- Support for TUF laptop RGB (kernel patches required, these are submitted upstream)
|
||||
### Changed
|
||||
- Create new rog-platform crate to manage all i/o in a universal way
|
||||
+ kbd-led handling (requires kernel patches, TUF specific)
|
||||
+ platform handling (asus-nb-wmi)
|
||||
+ power (basic, can be extended in future)
|
||||
+ hidraw
|
||||
+ usbraw
|
||||
- Refactor how ROGCC handles IPC for background open, run-in-bg
|
||||
- Refactor daemon task creation to be simpler (for development)
|
||||
- Rename dpu_only to gpu_mux. Update all related messages and info.
|
||||
### Breaking
|
||||
- DBUS: rename path `/org/asuslinux/RogBios` to `/org/asuslinux/Platform`
|
||||
- DBUS: renamed `dedicated_graphic_mode` to `gpu_mux_mode` (`GpuMuxMode`)
|
||||
- DBUS: renamed `set_dedicated_graphic_mode` to `set_gpu_mux_mode` (`SetGpuMuxMode`)
|
||||
+ The methods above take an enum: 0 = Discrete, 1 = Optimus
|
||||
|
||||
## [4.3.4] - 2022-08-03
|
||||
### Bugfix
|
||||
- ROGCC: Remove power setting from correct array
|
||||
|
||||
## [4.3.3] - 2022-08-02
|
||||
### Added
|
||||
- `rog-control-center` has now been moved in to the main workspace due to
|
||||
the heavy dependencies on most of the rog crates
|
||||
- Preliminary support of TUF RGB keyboards + power states
|
||||
- Support for G713RW LED modes (Author: jarvis2709)
|
||||
- Support for G713IC LED modes
|
||||
### Changed
|
||||
- The udev rules have been changed to make asusd load with all gamer variants when asus-nb-wmi is loaded
|
||||
- TUF, ROG, Zephyrus, Strix
|
||||
|
||||
## [4.3.0] - 2022-07-21
|
||||
### Added
|
||||
- Clear command for anime `asusctl anime --clear` will clear the display
|
||||
- Re-added support for LED power states on `0x1866` type keyboards
|
||||
### Changed
|
||||
- Make rog-anime more error tolerent. Remove various asserts and return errors instead
|
||||
- Return error if a pixel-gif is larger than the anime-display dimensions
|
||||
- Both Anime and Aura dbus interfaces are changed a little
|
||||
- Aura power has changed, all power related settings are now in one method
|
||||
- Anime methods will now return an error (if errored)
|
||||
- /org/asuslinux/Led renamed to /org/asuslinux/Aura
|
||||
|
||||
## [4.2.1] - 2022-07-18
|
||||
### Added
|
||||
- Add panel overdrive support (autodetects if supported)
|
||||
- Add detection of dgpu_disable and egpu_enable for diagnostic
|
||||
### Changed
|
||||
- Fixed save and restore of multizone LED settings
|
||||
- Create defaults for multizone
|
||||
|
||||
## [4.2.0] - 2022-07-16
|
||||
### Added
|
||||
- Support for GA402 Anime Matrix display (Author: I-Al-Istannen & Luke Jones)
|
||||
- Support for power-config of all LED zones. See `asusctrl led-power --help` (Author: Luke Jones, With much help from: @MNS26)
|
||||
- Full support for multizone LED <logo, keyboard, lightbar> (Author: Luke Jones, With much help from: @MNS26)
|
||||
- Add ability to load extra data from `/etc/asusd/asusd-user-ledmodes.toml` for LED support if file exits
|
||||
- Support for G513IM LED modes
|
||||
- Support for GX703HS LED modes
|
||||
### Changed
|
||||
- Dbus interface for Aura config has been changed, all power control is done with `SetLedsEnabled` and `SetLedsDisabled`
|
||||
- Data for anime-matrix now requires passing the laptop model as enum
|
||||
- Extra unit tests for anime stuff to help verify things
|
||||
|
||||
### Added
|
||||
- Support for GA503R LED modes
|
||||
### Changed
|
||||
- Refactor LED and AniMe tasks
|
||||
- Reload keyboard brightness on resume from sleep/hiber
|
||||
|
||||
## [4.1.1] - 2022-06-21
|
||||
### Changed
|
||||
- Fixes to anime matrix system thread cancelation
|
||||
|
||||
## [4.1.0] - 2022-06-20
|
||||
### Changed
|
||||
- Huge refactor to use zbus 2.2 + zvariant 3.0 in system-daemon.
|
||||
- Daemons with tasks now use `smol` for async ops.
|
||||
- Fixes to fan-curve settings from CLI (Author: Armas Span)
|
||||
- Add brightness to anime zbus notification
|
||||
- Adjust how threads in AniMe matrix controller work
|
||||
- Use proper power-state packet for keyboard LED's (Author: Martin Piffault)
|
||||
### Added
|
||||
- Support for GA402R LED modes
|
||||
- Support for GU502LV LED modes
|
||||
- Support for G512 LED modes
|
||||
- Support for G513IC LED modes (Author: dada513)
|
||||
- Support for G513QM LED modes (Author: Martin Piffault)
|
||||
- Add side-LED toggle support (Author: Martin Piffault)
|
||||
- Support reloading keyboard mode on wake (from sleep/hiber)
|
||||
- Support reloading charge-level on wake (from sleep/hiber)
|
||||
- Support running AniMe animation blocks on wake/sleep and boot/shutdown events
|
||||
|
||||
# [4.0.7] - 2021-12-19
|
||||
### Changed
|
||||
- Fix incorrect power-profile validation
|
||||
- Update asusd-ledmodes.toml to support Asus Rog Strix G15 G513QE (@LordVicky)
|
||||
- Update patch notes and links
|
||||
|
||||
# [4.0.6] - 2021-11-01
|
||||
### Changed
|
||||
- Fix CLI for bios toggles
|
||||
### Added
|
||||
- Extra commands for AniMe: pixel-image, gif, pixel-gif
|
||||
|
||||
# [4.0.5] - 2021-10-27
|
||||
### Changed
|
||||
- Convert fan curve percentage to 0-255 expected by kernel driver only if '%' char is used, otherwise the expected range for fan power is 0-255
|
||||
- Use correct error in daemon for invalid charging limit
|
||||
- Enforce charging limit values in range 20-100
|
||||
### Added
|
||||
- LED modes for G513QR
|
||||
|
||||
# [4.0.4] - 2021-10-02
|
||||
### Changed
|
||||
- Add missing Profile commands
|
||||
- Spawn tasks on individual threads to prevent blocking
|
||||
- Don't force fan-curve default on reload
|
||||
- Begin obsoleting the graphics switch command in favour of supergfxctl
|
||||
- Slim down the notification daemon to pure ASUS notifications
|
||||
|
||||
# [4.0.3] - 2021-09-16
|
||||
### Changed
|
||||
- Don't show fan-curve warning if fan-curve available
|
||||
- Add G713QR to Strix led-modes
|
||||
- Fix part of CLI fan-curve control
|
||||
|
||||
# [4.0.2] - 2021-09-14
|
||||
### Changed
|
||||
- Backup old configs to *-old if parse fails
|
||||
- Prevent some types of crashes related to unpatched kernels
|
||||
- Add better help for graphics errors
|
||||
- Add better help for asusctl general errors
|
||||
- Implement fan-curve dbus API
|
||||
- Implement partial fan-curve control via CLI tool
|
||||
+ Set fan curve for profile + fan gpu/cpu
|
||||
|
||||
# [4.0.1] - 2021-09-11
|
||||
### Changed
|
||||
- Fix asusd-ledmodes.toml
|
||||
|
||||
# [4.0.0] - 2021-09-10
|
||||
### Added
|
||||
- AniMe:
|
||||
+ Support 8bit RGB, RGBA, 16bit Greyscalw, RGB, RGBA
|
||||
+ add `AsusImage` type for slanted-template pixel-perfect images
|
||||
+ `BREAKING:` plain `Image` with time period is changed and old anime configs break as a result (sorry)
|
||||
- LED:
|
||||
+ By popular request LED prev/next cycle is added
|
||||
+ Add led modes for GX551Q
|
||||
### BREAKING CHANGES
|
||||
- Graphics control:
|
||||
+ graphics control is pulled out of asusd and moved to new package; https://gitlab.com/asus-linux/supergfxctl
|
||||
- Proflies:
|
||||
+ profiles now depend on power-profile-daemon plus kernel patches for support of platform_profile
|
||||
- if your system supports fan-curves you will also require upcoming kernel patches for this
|
||||
+ profiles are now moved to a new file
|
||||
+ fan-curves are only partially completed due to this release needing to be done sooner
|
||||
|
||||
# [3.7.2] - 2021-08-02
|
||||
### Added
|
||||
- Enable multizone support on Strix 513IH
|
||||
- Add G513QY ledmodes
|
||||
### Changed
|
||||
- Fix missing CLI command help for some supported options
|
||||
- Fix incorrectly selecting profile by name, where the active profile was being copied to the selected profile
|
||||
- Add `asusd` version back to `asusctl -v` report
|
||||
- Fix various clippy warnings
|
||||
|
||||
# [3.7.1] - 2021-06-11
|
||||
### Changed
|
||||
- Refine graphics mode switching:
|
||||
+ Disallow switching to compute or vfio mode unless existing mode is "Integrated"
|
||||
|
||||
# [3.7.0] - 2021-06-06
|
||||
### Changed
|
||||
- Set PM to auto for Nvidia always
|
||||
- Extra info output for gfx dev scan
|
||||
- Extra info in log for G-Sync to help prevent user confusion around gfx switching
|
||||
- Add GA503Q led modes
|
||||
- Added ability to fade in/out gifs and images for anime. This does break anime configs. See manual for details.
|
||||
- Added task to CtrlLed to set the keyboard LED brightness on wake from suspend
|
||||
+ requires a kernel patch which will be upstreamed and in fedora rog kernel
|
||||
- Make gfx change from nvidia to vfio/compute also force-change to integrated _then_
|
||||
to requested mode
|
||||
- Fix invalid gfx status when switching from some modes
|
||||
- Fix copy over of serde skipped config values on config reload
|
||||
|
||||
# [3.6.1] - 2021-05-25
|
||||
### Changed
|
||||
- Bugfix: write correct fan modes for profiles
|
||||
- Bugfix: apply created profiles
|
||||
|
||||
# [3.6.1] - 2021-05-25
|
||||
### Changed
|
||||
- Bugfix for cycling through profiles
|
||||
|
||||
# [3.6.0] - 2021-05-24
|
||||
### Changed
|
||||
- Add GX550L led modes
|
||||
- Don't save compute/vfio modes. Option in config for this is removed.
|
||||
- Store a temporary non-serialised option in config for if compute/vfio is active
|
||||
for informational purposes only (will not apply on boot)
|
||||
- Save state for LEDs enabled + sleep animation enabled
|
||||
- Save state for AnimMe enabled + boot animation enabled
|
||||
- Add extra config options and dbus methods
|
||||
- Add power state signals for anime and led
|
||||
- Refactor to use channels for dbus signal handler send/recv
|
||||
- Split out profiles independant parts to a rog-profiles crate
|
||||
- Cleanup dependencies
|
||||
- Fix some dbus Supported issues
|
||||
|
||||
# [3.5.2] - 2021-05-15
|
||||
### Changed
|
||||
- Bugfix: prevent the hang on compute/integrated mode change
|
||||
|
||||
# [3.5.1] - 2021-04-25
|
||||
### Changed
|
||||
+ Anime:
|
||||
- Fix using multiple configs
|
||||
|
||||
# [3.5.0] - 2021-04-25
|
||||
### Changed
|
||||
+ Keyboard:
|
||||
- Split out all aura functionality that isn't dependent on the daemon in to a
|
||||
new crate `rog-aura` (incomplete)
|
||||
- Keyboard LED control now includes:
|
||||
+ Enable/disable LED's while laptop is awake
|
||||
+ Enable/disable LED animation while laptop is suspended and AC plugged in
|
||||
- Properly reload the last used keyboard mode on boot
|
||||
+ Graphics:
|
||||
- Correctly enable compute mode for nvidia plus no-reboot or logout if switching
|
||||
from vfio/integrated/compute.
|
||||
- Add asusd config option to not save compute/vfio mode switch.
|
||||
+ Anime:
|
||||
- Enable basic multiple user anime configs (asusd-user must still be restarted)
|
||||
+ Profiles:
|
||||
- Enable dbus methods for freq min/max, fan curve, fan preset, CPU turbo enable.
|
||||
These options will apply to the active profile if no profile name is specified.
|
||||
|
||||
# [3.4.1] - 2021-04-11
|
||||
### Changed
|
||||
- Fix anime init sequence
|
||||
|
||||
# [3.4.0] - 2021-04-11
|
||||
### Changed
|
||||
- Revert zbus to 1.9.1
|
||||
- Use enum to show power states, and catch missing pci path for nvidia.
|
||||
- Partial user-daemon for anime/per-key done, `asusd-user`. Includes asusd-user systemd unit.
|
||||
- user-daemon provides dbus emthods to insert anime actions, remove from index, set leds on/off
|
||||
+ Config file is stored in `~/.config/rog/rog-user.cfg`
|
||||
- AniMe display parts split out to individual crate in preparation for publishing
|
||||
on crates.io
|
||||
|
||||
# [3.3.0] - 2021-04-3
|
||||
### Changed
|
||||
- Add ledmodes for G733QS
|
||||
- Add ledmodes for GA401Q
|
||||
- Default to vfio disabled in configuration. Will now hard-error if enabled and
|
||||
the kernel modules are builtin. To enable vfio switching `"gfx_vfio_enable": false,`
|
||||
must be changed to `true` in `/etc/asusd/asusd.conf`
|
||||
|
||||
# [3.2.4] - 2021-03-24
|
||||
### Changed
|
||||
- Ignore vfio-builtin error if switching to integrated
|
||||
|
||||
# [3.2.3] - 2021-03-24
|
||||
### Changed
|
||||
- Better handling of session tracking
|
||||
### Added
|
||||
- List all profile data
|
||||
- Get active profile name
|
||||
- Get active profile data
|
||||
|
||||
# [3.2.2] - 2021-03-23
|
||||
### Changed
|
||||
- Fix brightness control, again, for non-RGB keyboards
|
||||
|
||||
# [3.2.1] - 2021-03-21
|
||||
### Changed
|
||||
- Fix brightness control
|
||||
- Large cleanup of code relating to LED controls
|
||||
|
||||
# [3.2.0] - 2021-03-21
|
||||
### Changed
|
||||
- Refactor keyboard LED handling
|
||||
- Added --list for profiles (Thanks @aqez)
|
||||
- Added --remove for profiles (Thanks @aqez)
|
||||
- Added a graphics mode: vfio. This attaches Nvidia devices to vfio module.
|
||||
### Broken
|
||||
- Per-key LED modes, which need thinking about how to go ahead with for future
|
||||
|
||||
# [3.1.7] - 2021-03-11
|
||||
### Changed
|
||||
- Refactor many parts of daemon
|
||||
- Switch out session monitoring to logind-zbus
|
||||
- Switch out session monitoring to logind-zbus
|
||||
|
||||
# [3.1.6] - 2021-03-11
|
||||
### Changed
|
||||
- Graphics switching will now wait until all users logged out before switching
|
||||
- Graphics switching will now wait until all users logged out before switching
|
||||
|
||||
### Changed
|
||||
- Further tweaks to gfx switching
|
||||
@@ -175,6 +758,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
- Fix small deadlock with awaits
|
||||
|
||||
## [1.0.0] - 2020-08-13
|
||||
|
||||
- Major fork and refactor to use asus-hid patch for ASUS N-Key device
|
||||
## [1.0.0]
|
||||
|
||||
4359
Cargo.lock
generated
54
Cargo.toml
@@ -1,16 +1,64 @@
|
||||
[workspace]
|
||||
members = ["asusctl", "asus-notify", "daemon", "rog-types", "rog-dbus"]
|
||||
members = ["asusctl", "asusd", "asusd-user", "config-traits", "cpuctl", "dmi-id", "rog-platform", "rog-dbus", "rog-anime", "rog-aura", "rog-profiles", "rog-control-center", "simulators"]
|
||||
default-members = ["asusctl", "asusd", "asusd-user", "cpuctl", "rog-control-center"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
version = "5.0.6"
|
||||
|
||||
[workspace.dependencies]
|
||||
async-trait = "^0.1"
|
||||
tokio = { version = "^1.23.0", default-features = false, features = ["macros", "sync"]}
|
||||
concat-idents = "^1.1"
|
||||
dirs = "^4.0"
|
||||
smol = "^1.3"
|
||||
|
||||
zbus = "~3.14.1"
|
||||
logind-zbus = { version = "~3.1" } #, 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 = "^0.7"
|
||||
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", 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
|
||||
debug = true
|
||||
opt-level = 1
|
||||
|
||||
[profile.bench]
|
||||
debug = false
|
||||
opt-level = 3
|
||||
|
||||
[workspace.dependencies.cargo-husky]
|
||||
version = "1"
|
||||
default-features = false
|
||||
features = ["user-hooks"]
|
||||
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",
|
||||
]
|
||||
|
||||
439
MANUAL.md
Normal file
@@ -0,0 +1,439 @@
|
||||
# asusctrl manual
|
||||
|
||||
`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.
|
||||
|
||||
## Programs Available
|
||||
|
||||
- `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.
|
||||
|
||||
The functionality that `asusd` exposes is:
|
||||
|
||||
- anime control
|
||||
- led keyboard control (aura)
|
||||
- charge limiting
|
||||
- bios/efivar control
|
||||
- power profile switching
|
||||
- fan curves (if supported, this is auto-detected)
|
||||
|
||||
each of these will be detailed in sections.
|
||||
|
||||
### AniMe control
|
||||
|
||||
Controller for the fancy AniMe matrix display on the lid of some machines. This controller is a work in progress.
|
||||
|
||||
#### Config options
|
||||
|
||||
If you have an AniMe device a few system-level config options are enabled for you in `/etc/asusd/anime.conf`;
|
||||
|
||||
1. `"system": [],`: currently unused, is intended to be a default continuous sequence in future versions
|
||||
2. `"boot": [],`: a sequence that plays on system boot (when asusd is loaded)
|
||||
3. `"wake": [],`: a sequence that plays when waking from suspend
|
||||
4. `"shutdown": [],`: a sequence that plays when shutdown begins
|
||||
5. `"brightness": <FLOAT>`: global brightness control, where `<FLOAT> is 0.0-1.0
|
||||
|
||||
Some default examples are provided but are minimal. The full range of configuration options will be covered in another section of this manual.
|
||||
|
||||
### Led keyboard control
|
||||
|
||||
The LED controller (e.g, aura) enables setting many of the factory modes available if a laptop supports them. It also enables per-key RGB settings but this is a WIP and will likely be similar to how AniMe sequences can be created.
|
||||
|
||||
#### Supported laptops
|
||||
|
||||
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
|
||||
|
||||
Almost all modern ASUS laptops have charging limit control now. This can be controlled in `/etc/asusd/asusd.conf`.
|
||||
|
||||
```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 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
|
||||
|
||||
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.
|
||||
|
||||
A common use of asusctl is to bind the `fn+f5` (fan) key to `asusctl profile -n` to cycle through the 3 profiles:
|
||||
1. Balanced
|
||||
2. Performance
|
||||
3. Quiet
|
||||
|
||||
#### Fan curves
|
||||
|
||||
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 .
|
||||
|
||||
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
|
||||
|
||||
There is one more controller; the support controller. The sole pupose of this controller is to querie all the other controllers for information about their support level for the host laptop. Returns a json string.
|
||||
|
||||
## 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.
|
||||
|
||||
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"`
|
||||
|
||||
An AniMe config itself is a file with contents:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "<FILENAME>",
|
||||
"anime": []
|
||||
}
|
||||
```
|
||||
|
||||
`<FILENAME>` is used as a reference internally. `"anime": []` is an array of sequences (WIP).
|
||||
|
||||
##### "anime" array options
|
||||
|
||||
Each object in the array can be one of:
|
||||
|
||||
1. AsusAnimation
|
||||
2. ImageAnimation
|
||||
3. Image
|
||||
4. Pause
|
||||
|
||||
##### 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>",
|
||||
"time": <TIME>,
|
||||
"brightness": <FLOAT>
|
||||
}
|
||||
```
|
||||
|
||||
##### 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.
|
||||
|
||||
```json
|
||||
"ImageAnimation": {
|
||||
"file": "<FILE_PATH>",
|
||||
"scale": <FLOAT>,
|
||||
"angle": <FLOAT>,
|
||||
"translation": [
|
||||
<FLOAT>,
|
||||
<FLOAT>
|
||||
],
|
||||
"time": <TIME>,
|
||||
"brightness": <FLOAT>
|
||||
}
|
||||
},
|
||||
```
|
||||
|
||||
##### Image
|
||||
|
||||
`Image` currently requires 8bit greyscale png. It will be able to use most in future.
|
||||
|
||||
```json
|
||||
{
|
||||
"Image": {
|
||||
"file": "<FILE_PATH>",
|
||||
"scale": <FLOAT>,
|
||||
"angle": <FLOAT>,
|
||||
"translation": [
|
||||
<FLOAT>,
|
||||
<FLOAT>
|
||||
],
|
||||
"time": <TIME>,
|
||||
"brightness": <FLOAT>
|
||||
}
|
||||
},
|
||||
```
|
||||
|
||||
##### Pause
|
||||
|
||||
A `Pause` is handy for after an `Image` to hold the `Image` on the AniMe for a period.
|
||||
|
||||
```json
|
||||
{
|
||||
"Pause": {
|
||||
"secs": <INT>,
|
||||
"nanos": <INT>
|
||||
}
|
||||
},
|
||||
```
|
||||
|
||||
##### Options for objects
|
||||
|
||||
**<FILE_PATH>**
|
||||
|
||||
Must be full path: `"/usr/share/asusd/anime/asus/gaming/Controller.gif"` or `/home/luke/Downloads/random.gif`.
|
||||
|
||||
**<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
|
||||
- `translation`: Shift the image X -/+, and y -/+
|
||||
|
||||
**<TIME>**
|
||||
|
||||
Time is the length of time to run the gif for:
|
||||
```json
|
||||
"time": {
|
||||
"Time": {
|
||||
"secs": 5,
|
||||
"nanos": 0
|
||||
}
|
||||
},
|
||||
```
|
||||
A cycle is how many gif loops to run:
|
||||
```json
|
||||
"time": {
|
||||
"Cycles": 2
|
||||
},
|
||||
```
|
||||
`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`. 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.
|
||||
|
||||
Most commands are self-explanatory.
|
||||
|
||||
### CLI Usage and help
|
||||
|
||||
Commands are given by:
|
||||
|
||||
```
|
||||
asusctl <option> <command> <command-options>
|
||||
```
|
||||
|
||||
Help is available through:
|
||||
|
||||
```
|
||||
asusctl --help
|
||||
asusctl <command> --help
|
||||
```
|
||||
|
||||
Some commands may have subcommands:
|
||||
|
||||
```
|
||||
asusctl <command> <subcommand> --help
|
||||
```
|
||||
|
||||
### Keybinds
|
||||
|
||||
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)
|
||||
|
||||
---
|
||||
|
||||
ASUS and ROG Trademark is either a US registered trademark or trademark of ASUSTeK Computer Inc. in the United States and/or other countries.
|
||||
|
||||
Reference to any ASUS products, services, processes, or other information and/or use of ASUS Trademarks does not constitute or imply endorsement, sponsorship, or recommendation thereof by ASUS.
|
||||
|
||||
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.
|
||||
|
||||
---
|
||||
103
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,19 +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_N := asus-notify
|
||||
LEDCFG := asusd-ledmodes.toml
|
||||
X11CFG := 90-nvidia-screen-G05.conf
|
||||
PMRULES := 90-asusd-nvidia-pm.rules
|
||||
BIN_U := asusd-user
|
||||
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
|
||||
@@ -39,39 +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_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"
|
||||
$(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
|
||||
@@ -82,14 +112,39 @@ vendor:
|
||||
echo 'directory = "vendor"' >> .cargo/config
|
||||
mv .cargo/config ./cargo-config
|
||||
rm -rf .cargo
|
||||
tar pcfJ vendor_asus-nb-ctrl_$(VERSION).tar.xz vendor
|
||||
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
|
||||
|
||||
introspect:
|
||||
gdbus introspect --system -d org.asuslinux.Daemon -o /org/asuslinux/Platform -x > bindings/dbus-xml/org-asuslinux-platform-4.xml
|
||||
gdbus introspect --system -d org.asuslinux.Daemon -o /org/asuslinux/Aura -x > bindings/dbus-xml/org-asuslinux-aura-4.xml
|
||||
gdbus introspect --system -d org.asuslinux.Daemon -o /org/asuslinux/Anime -x > bindings/dbus-xml/org-asuslinux-anime-4.xml
|
||||
gdbus introspect --system -d org.asuslinux.Daemon -o /org/asuslinux/FanCurves -x > bindings/dbus-xml/org-asuslinux-fan-curves-4.xml
|
||||
xmlstarlet ed -L -O -d '//interface[@name="org.freedesktop.DBus.Introspectable"]' bindings/dbus-xml/org-asuslinux-*
|
||||
xmlstarlet ed -L -O -d '//interface[@name="org.freedesktop.DBus.Properties"]' bindings/dbus-xml/org-asuslinux-*
|
||||
xmlstarlet ed -L -O -d '//interface[@name="org.freedesktop.DBus.Peer"]' bindings/dbus-xml/org-asuslinux-*
|
||||
|
||||
build:
|
||||
ifeq ($(VENDORED),1)
|
||||
cargo vendor
|
||||
@echo "version = $(VERSION)"
|
||||
tar pxf vendor_asus-nb-ctrl_$(VERSION).tar.xz
|
||||
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
|
||||
|
||||
297
README.md
@@ -1,37 +1,37 @@
|
||||
# ASUS NB Ctrl
|
||||
# `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.
|
||||
|
||||
**NOTICE:**
|
||||
Now includes a GUI, `rog-control-center`.
|
||||
|
||||
This app is developed and tested on fedora only. Support is not provided for Arch or Arch based distros.
|
||||
## Kernel support
|
||||
|
||||
**NOTICE:**
|
||||
The following is *not* required for 5.11 kernel versions, as this version includes
|
||||
all the required patches.
|
||||
---
|
||||
This program requires the kernel patch [here](https://www.spinics.net/lists/linux-input/msg68977.html) to be applied.
|
||||
Alternatively you may use the dkms module for 'hid-asus-rog` from one of the
|
||||
repositories [here](https://download.opensuse.org/repositories/home:/luke_nukem:/asus/).
|
||||
**The minimum supported kernel version is 6.6**
|
||||
|
||||
The patch enables the following in kernel:
|
||||
## Goals
|
||||
|
||||
- All hotkeys (FN+Key combos)
|
||||
- Control of keyboard brightness using FN+Key combos (not RGB)
|
||||
- FN+F5 (fan) to toggle fan modes
|
||||
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.
|
||||
|
||||
You will not get RGB control in kernel (yet), and `asusd` + `asusctl` is required
|
||||
to change modes and RGB settings.
|
||||
1. Provide safe dbus interface
|
||||
2. Respect the users resources: be small, light, and fast
|
||||
|
||||
Many other patches for these laptops, AMD and Intel based, are working their way
|
||||
in to the kernel.
|
||||
Point 4? asusd currently uses a tiny fraction of cpu time, and less than 1Mb of ram, the way
|
||||
a system-level daemon should. Languages such as JS and python should never be used for system level daemons (please stop).
|
||||
|
||||
## Keyboard LEDs
|
||||
|
||||
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/WTHnqabm)
|
||||
|
||||
## SUPPORTED LAPTOPS
|
||||
|
||||
@@ -41,126 +41,77 @@ 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] GUI app (includes tray and notifications)
|
||||
- [X] Setting/modifying built-in LED modes
|
||||
- [X] Per-key LED setting
|
||||
- [X] Fancy LED modes (See examples)
|
||||
- [X] Saving settings for reload
|
||||
- [X] Logging - required for journalctl
|
||||
- [X] AniMatrix display on G14 models that include it
|
||||
- [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] Fancy fan control on G14 + G15 thanks to @Yarn1
|
||||
- [X] Graphics mode switching between iGPU, dGPU, and On-Demand
|
||||
- [X] Fan curve control on supported laptops (G14/G15, some TUF like FA507)
|
||||
- [X] Toggle bios setting for boot/POST sound
|
||||
- [X] Toggle bios setting for "dedicated gfx" mode on supported laptops (g-sync)
|
||||
- [X] Toggle GPU MUX (g-sync, or called MUX on 2022+ laptops)
|
||||
|
||||
# FUNCTIONS
|
||||
# GUI
|
||||
|
||||
## Graphics switching
|
||||
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.
|
||||
|
||||
A new feature has been added to enable switching graphics modes. This can be disabled
|
||||
in the config with `"manage_gfx": false,`. Additionally there is an extra setting
|
||||
for laptops capable of g-sync dedicated gfx mode to enable the graphics switching
|
||||
to switch on dedicated gfx for "nvidia" mode.
|
||||
|
||||
The CLI option for this does not require root until it asks for it, and provides
|
||||
instructions.
|
||||
|
||||
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/`
|
||||
|
||||
### Power management udev rule
|
||||
|
||||
If you have installed the Nvidia driver manually you will require the
|
||||
`data/90-asusd-nvidia-pm.rules` udev rule to be installed in `/etc/udev/rules.d/`.
|
||||
|
||||
### fedora and openSUSE
|
||||
|
||||
You *may* need a file `/etc/dracut.conf.d/90-nvidia-dracut-G05.conf` installed
|
||||
to stop dracut including the nvidia modules in the ramdisk. This is espeically
|
||||
true if you manually installed the nvidia drivers.
|
||||
|
||||
```
|
||||
# filename /etc/dracut.conf.d/90-nvidia-dracut-G05.conf
|
||||
# Omit the nvidia driver from the ramdisk, to avoid needing to regenerate
|
||||
# the ramdisk on updates, and to ensure the power-management udev rules run
|
||||
# on module load
|
||||
omit_drivers+=" nvidia nvidia-drm nvidia-modeset nvidia-uvm "
|
||||
```
|
||||
|
||||
and run `dracut -f` after creating it.
|
||||
|
||||
## KEYBOARD BACKLIGHT MODES
|
||||
|
||||
Models GA401, GA502, GU502 support LED brightness change only (no RGB).
|
||||
|
||||
If you model isn't getting the correct led modes, you can edit the file
|
||||
`/etc/asusd/asusd-ledmodes.toml`, the LED Mode numbers are as follows:
|
||||
|
||||
```
|
||||
0 STATIC
|
||||
1 BREATHING
|
||||
2 STROBE
|
||||
3 RAINBOW
|
||||
4 STAR
|
||||
5 RAIN
|
||||
6 HIGHLIGHT
|
||||
7 LASER
|
||||
8 RIPPLE
|
||||
10 PULSE
|
||||
11 COMET
|
||||
12 FLASH
|
||||
13 MULTISTATIC
|
||||
255 PER_KEY
|
||||
```
|
||||
|
||||
use `cat /sys/class/dmi/id/product_name` to get details about your laptop.
|
||||
|
||||
# Keybinds
|
||||
|
||||
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`.
|
||||

|
||||

|
||||

|
||||
|
||||
# BUILDING
|
||||
|
||||
Requirements are rust >= 1.40 installed from rustup.io if the distro provided version is too old, and `make`.
|
||||
Requirements are rust >= 1.57 installed from rustup.io if the distro provided version is too old, and `make`.
|
||||
|
||||
**Ubuntu*:** `apt install libclang-dev libudev-dev`
|
||||
**Ubuntu (unsuported):**
|
||||
|
||||
**fedora:** `dnf install clang-devel systemd-devel`
|
||||
apt install libgtk-3-dev libpango1.0-dev libgdk-pixbuf-2.0-dev libglib2.0-dev cmake libclang-dev libudev-dev libayatana-appindicator3-1
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
source "$HOME/.cargo/env"
|
||||
make
|
||||
sudo make install
|
||||
|
||||
**popos (unsuported):**
|
||||
|
||||
sudo apt install cmake libclang-dev libudev-dev libgtk-3-dev libclang-dev libglib2.0-dev libatkmm-1.6-dev libpangomm-1.4-dev librust-gdk-pixbuf-dev
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
source "$HOME/.cargo/env"
|
||||
make
|
||||
sudo make install
|
||||
|
||||
|
||||
**fedora:**
|
||||
|
||||
dnf install cmake clang-devel systemd-devel glib2-devel cairo-devel atkmm-devel pangomm-devel gdk-pixbuf2-devel gtk3-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 systemd-devel clang-devel llvm-devel gdk-pixbuf-devel cairo-devel pango-devel freetype-devel gtk3-devel libexpat-devel libayatana-indicator3-7
|
||||
make
|
||||
sudo make install
|
||||
|
||||
## Installing
|
||||
- Fedora copr = https://copr.fedorainfracloud.org/coprs/lukenukem/asus-linux/
|
||||
- openSUSE = https://download.opensuse.org/repositories/home:/luke_nukem:/asus/
|
||||
- Ubuntu = not supported due to packaging woes, but you can build and install on your own.
|
||||
|
||||
Packaging and auto-builds are available [here](https://build.opensuse.org/package/show/home:luke_nukem:asus/asus-nb-ctrl)
|
||||
|
||||
Download repositories are available [here](https://download.opensuse.org/repositories/home:/luke_nukem:/asus/)
|
||||
|
||||
Alternatively check the releases page for f33 RPM.
|
||||
|
||||
---
|
||||
|
||||
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.
|
||||
@@ -173,116 +124,38 @@ $ 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/`.
|
||||
|
||||
## Updating
|
||||
# Contributing
|
||||
|
||||
If there has been a config file format change your config will be overwritten. This will
|
||||
become less of an issue once the feature set is nailed down. Work is happening to enable
|
||||
parsing of older configs and transferring settings to new.
|
||||
See `CONTRIBUTING.md`. Additionally, also do `cargo clean` and `cargo test` on first checkout to ensure the commit hooks are used (via `cargo-husky`).
|
||||
|
||||
# USAGE
|
||||
Generation of the bindings with `make bindings` requires `typeshare` to be installed.
|
||||
|
||||
**NOTE! Fan mode toggling requires a newer kernel**. I'm unsure when the patches
|
||||
required for it got merged - I've tested with the 5.6.6 kernel and above only.
|
||||
To see if the fan-mode changed cat either:
|
||||
Dbus introsepction XML requires with `make introspection` requires `anime_sim` to be running before starting `asusd`.
|
||||
|
||||
- `cat /sys/devices/platform/asus-nb-wmi/throttle_thermal_policy` or
|
||||
- `cat /sys/devices/platform/asus-nb-wmi/fan_boost_mode`
|
||||
|
||||
The numbers are 0 = Normal/Balanced, 1 = Boost, 2 = Silent.
|
||||
|
||||
Running the program as a daemon manually will require root. Standard (non-daemon)
|
||||
mode expects to be communicating with the daemon mode over dbus.
|
||||
|
||||
Commands are given by:
|
||||
|
||||
```
|
||||
asusctl <option> <command> <command-options>
|
||||
```
|
||||
|
||||
Help is available through:
|
||||
|
||||
```
|
||||
asusctl --help
|
||||
asusctl <command> --help
|
||||
```
|
||||
|
||||
Some commands may have subcommands:
|
||||
|
||||
```
|
||||
asusctl <command> <subcommand> --help
|
||||
```
|
||||
|
||||
## Daemon mode
|
||||
|
||||
If the daemon service is enabled then on boot the following will be reloaded from save:
|
||||
|
||||
- LED brightness
|
||||
- Last used built-in mode
|
||||
- fan-boost/thermal mode
|
||||
- battery charging limit
|
||||
|
||||
The daemon also saves the settings per mode as the keyboard does not do this
|
||||
itself - this means cycling through modes with the Aura keys will use the
|
||||
settings that were used via CLI.
|
||||
|
||||
Daemon mode creates a config file at `/etc/asusd/asusd.conf` which you can edit a
|
||||
little of. Most parts will be byte arrays, but you can adjust things like
|
||||
`mode_performance`.
|
||||
|
||||
## 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
|
||||
```
|
||||
# OTHER
|
||||
|
||||
## DBUS Input
|
||||
## AniMe Matrix simulator
|
||||
|
||||
See [README_DBUS.md](./README_DBUS.md).
|
||||
|
||||
## AniMe input
|
||||
|
||||
You will want to look at what MeuMeu has done with [https://github.com/Meumeu/ZephyrusBling/](https://github.com/Meumeu/ZephyrusBling/)
|
||||
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.
|
||||
|
||||
## Notes:
|
||||
|
||||
- If charge limit or fan modes are not working, then you may require a kernel newer than 5.6.10.
|
||||
- AniMe device check is performed on start, if your device has one it will be detected.
|
||||
- GA14/GA401 and GA15/GA502/GU502, You will need kernel [patches](https://lab.retarded.farm/zappel/asus-rog-zephyrus-g14/-/tree/master/kernel_patches), these are on their way to the kernel upstream.
|
||||
- On fedora manually installed Nvidia driver requires a dracut config as follows:
|
||||
```
|
||||
# filename/etc/dracut.conf.d/90-nvidia-dracut-G05.conf
|
||||
# Omit the nvidia driver from the ramdisk, to avoid needing to regenerate
|
||||
# the ramdisk on updates, and to ensure the power-management udev rules run
|
||||
# on module load
|
||||
omit_drivers+=" nvidia nvidia-drm nvidia-modeset nvidia-uvm "
|
||||
```
|
||||
|
||||
# License
|
||||
# License & Trademarks
|
||||
|
||||
Mozilla Public License 2 (MPL-2.0)
|
||||
|
||||
# Credits
|
||||
---
|
||||
|
||||
- [flukejones](https://github.com/flukejones/), project maintainer.
|
||||
- [tuxuser](https://github.com/tuxuser/)
|
||||
- [aspann](https://github.com/aspann)
|
||||
- [meumeu](https://github.com/Meumeu)
|
||||
- Anyone missed? Please contact me
|
||||
ASUS and ROG Trademark is either a US registered trademark or trademark of ASUSTeK Computer Inc. in the United States and/or other countries.
|
||||
|
||||
Reference to any ASUS products, services, processes, or other information and/or use of ASUS Trademarks does not constitute or imply endorsement, sponsorship, or recommendation thereof by ASUS.
|
||||
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
115
README_DBUS.md
@@ -1,115 +0,0 @@
|
||||
# DBUS Guide
|
||||
|
||||
**WARNING: In progress updates**
|
||||
|
||||
Interface name = org.asuslinux.Daemon
|
||||
|
||||
Paths:
|
||||
- `/org/asuslinux/Gfx`
|
||||
+ `SetVendor` (string)
|
||||
+ `NotifyVendor` (recv vendor label string)
|
||||
- `/org/asuslinux/Led`
|
||||
+ `LedMode` (AuraMode as json)
|
||||
+ `LedModes` (array[AuraMode] as json)
|
||||
+ `SetLedMode` (AuraMode -> json)
|
||||
+ `NotifyLed` (recv json data)
|
||||
- `/org/asuslinux/Anime`
|
||||
+ `SetAnime` (byte array data)
|
||||
- `/org/asuslinux/Charge`
|
||||
+ `Limit` (u8)
|
||||
+ `SetLimit` (u8)
|
||||
+ `NotifyCharge` (recv i8)
|
||||
- `/org/asuslinux/Profile`
|
||||
+ `Profile` (recv current profile data as json string)
|
||||
+ `Profiles` (recv profiles data as json string (map))
|
||||
+ `SetProfile` (event -> json)
|
||||
+ `NotifyProfile` (recv current profile name)
|
||||
|
||||
All `Notify*` methods are signals.
|
||||
|
||||
### SetLed
|
||||
|
||||
This method expects a string of JSON as input. The JSON is of format such:
|
||||
|
||||
```
|
||||
{
|
||||
"Static": {
|
||||
"colour": [ 255, 0, 0]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The possible contents of a mode are:
|
||||
|
||||
- `"colour": [u8, u8, u8],`
|
||||
- `"speed": <String>,` <Low, Med, High>
|
||||
- `"direction": <String>,` <Up, Down, Left, Right>
|
||||
|
||||
Modes may or may not be available for a specific laptop (TODO: dbus getter for
|
||||
supported modes). Modes are:
|
||||
|
||||
- `"Static": { "colour": <colour> },`
|
||||
- `"Pulse": { "colour": <colour> },`
|
||||
- `"Comet": { "colour": <colour> },`
|
||||
- `"Flash": { "colour": <colour> },`
|
||||
- `"Strobe": { "speed": <speed> },`
|
||||
- `"Rain": { "speed": <speed> },`
|
||||
- `"Laser": { "colour": <colour>, "speed": <speed> },`
|
||||
- `"Ripple": { "colour": <colour>, "speed": <speed> },`
|
||||
- `"Highlight": { "colour": <colour>, "speed": <speed> },`
|
||||
- `"Rainbow": { "direction": <direction>, "speed": <speed> },`
|
||||
- `"Breathe": { "colour": <colour>, "colour2": <colour>, "speed": <speed> },`
|
||||
- `"Star": { "colour": <colour>, "colour2": <colour>, "speed": <speed> },`
|
||||
- `"MultiStatic": { "colour1": <colour>, "colour2": <colour>, , "colour3": <colour>, "colour4": <colour> },`
|
||||
|
||||
Additionally to the above there is `"RGB": [[u8; 64]; 11]` which is for per-key
|
||||
setting of LED's but this requires some refactoring to make it easily useable over
|
||||
dbus.
|
||||
|
||||
Lastly, there is `"LedBrightness": <u8>` which accepts 0-3 for off, low, med, high.
|
||||
|
||||
### SetFanMode
|
||||
|
||||
Accepts an integer from the following:
|
||||
|
||||
- `0`: Normal
|
||||
- `1`: Boost mode
|
||||
- `2`: Silent mode
|
||||
|
||||
## dbus-send examples:
|
||||
|
||||
```
|
||||
dbus-send --system --type=method_call --dest=org.asuslinux.Daemon /org/asuslinux/Profile org.asuslinux.Daemon.NextProfile
|
||||
```
|
||||
|
||||
## dbus-send examples OUTDATED
|
||||
|
||||
```
|
||||
dbus-send --system --type=method_call --dest=org.asuslinux.Daemon /org/asuslinux/Daemon org.asuslinux.Daemon.SetKeyBacklight string:'{"Static": {"colour": [ 80, 0, 40]}}'
|
||||
```
|
||||
|
||||
```
|
||||
dbus-send --system --type=method_call --dest=org.asuslinux.Daemon /org/asuslinux/Daemon org.asuslinux.Daemon.SetKeyBacklight string:'{"Star":{"colour":[0,255,255],"colour2":[0,0,0],"speed":"Med"}}'
|
||||
```
|
||||
|
||||
**Note:** setting colour2 to `[0,0,255]` activates random star colour. Colour2 has no effect on the
|
||||
mode otherwise.
|
||||
```
|
||||
dbus-send --system --type=method_call --dest=org.asuslinux.Daemon /org/asuslinux/Daemon org.asuslinux.Daemon.SetKeyBacklight string:'{"Star":{"colour":[0,255,255],"colour2":[0,0,255],"speed":"Med"}}'
|
||||
```
|
||||
|
||||
```
|
||||
dbus-send --system --type=method_call --dest=org.asuslinux.Daemon /org/asuslinux/Daemon org.asuslinux.Daemon.SetKeyBacklight string:'{"LedBrightness":3}'
|
||||
```
|
||||
|
||||
```
|
||||
dbus-send --system --type=method_call --dest=org.asuslinux.Daemon /org/asuslinux/Daemon org.asuslinux.Daemon.SetFanMode byte:'2'
|
||||
```
|
||||
|
||||
Monitoring dbus while sending commands via `rog-core` will give you the json structure if you are otherwise unsure, e.g: `dbus-monitor --system |grep -A2 asuslinux`.
|
||||
|
||||
## Getting an introspection .xml
|
||||
|
||||
```
|
||||
dbus-send --system --print-reply --dest=org.asuslinux.Daemon /org/asuslinux/Charge org.freedesktop.DBus.Introspectable.Introspect > xml/asusd-charge.xml
|
||||
```
|
||||
7
TODO.md
@@ -1,7 +0,0 @@
|
||||
# TODO
|
||||
|
||||
- There is lots of code duplication. This should be turned in to macros (dbus stuff etc)
|
||||
- Add a little more information to profile notifications such as freq min/max, fan curves
|
||||
- Finish splitting out controllers to own crates
|
||||
- Finish move to zbus in client when zbus has client signal watch
|
||||
- Consider a rename again because the project is getting a lot less ASUS centric
|
||||
@@ -1,18 +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" }
|
||||
daemon = { path = "../daemon" }
|
||||
|
||||
[dependencies.notify-rust]
|
||||
version = "^4.0"
|
||||
default-features = false
|
||||
features = ["z"]
|
||||
@@ -1,110 +0,0 @@
|
||||
use daemon::config::Profile;
|
||||
use notify_rust::{Hint, Notification, NotificationHandle};
|
||||
use rog_dbus::{DbusProxies, Signals};
|
||||
use std::error::Error;
|
||||
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 mut cfg = Config::read_new()?;
|
||||
// let mut last_profile = String::new();
|
||||
|
||||
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);
|
||||
loop {
|
||||
std::thread::sleep(Duration::from_millis(100));
|
||||
recv.next_signal().unwrap();
|
||||
|
||||
if let Ok(mut lock) = signals.gfx_vendor.lock() {
|
||||
if let Some(vendor) = lock.take() {
|
||||
if let Some(notif) = last_gfx_notif.take() {
|
||||
notif.close();
|
||||
}
|
||||
let x = do_notif(&format!("Graphics mode changed to {}", vendor))?;
|
||||
last_gfx_notif = Some(x);
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(mut lock) = signals.charge.lock() {
|
||||
if let Some(limit) = lock.take() {
|
||||
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(mut lock) = signals.profile.lock() {
|
||||
if let Some(profile) = lock.take() {
|
||||
if let Some(notif) = last_profile_notif.take() {
|
||||
notif.close();
|
||||
}
|
||||
if let Ok(profile) = serde_json::from_str(&profile) {
|
||||
let profile: Profile = profile;
|
||||
if let Ok(name) = proxies.profile().active_profile_name() {
|
||||
let x = do_thermal_notif(&profile, &name)?;
|
||||
last_profile_notif = Some(x);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(mut lock) = signals.led_mode.lock() {
|
||||
if let Some(ledmode) = lock.take() {
|
||||
if let Some(notif) = last_led_notif.take() {
|
||||
notif.close();
|
||||
}
|
||||
let x = do_notif(&format!(
|
||||
"Keyboard LED mode changed to {}",
|
||||
<&str>::from(&ledmode)
|
||||
))?;
|
||||
last_led_notif = Some(x);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn do_thermal_notif(profile: &Profile, label: &str) -> Result<NotificationHandle, Box<dyn Error>> {
|
||||
let fan = profile.fan_preset;
|
||||
let turbo = if profile.turbo { "enabled" } else { "disabled" };
|
||||
let icon = match fan {
|
||||
0 => "asus_notif_yellow",
|
||||
1 => "asus_notif_red",
|
||||
2 => "asus_notif_green",
|
||||
_ => "asus_notif_red",
|
||||
};
|
||||
let x = Notification::new()
|
||||
.summary("ASUS ROG")
|
||||
.body(&format!(
|
||||
"Thermal profile changed to {}, turbo {}",
|
||||
label.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,21 +1,26 @@
|
||||
[package]
|
||||
name = "asusctl"
|
||||
version = "3.1.3"
|
||||
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]
|
||||
# serialisation
|
||||
serde_json = "^1.0"
|
||||
rog_anime = { path = "../rog-anime" }
|
||||
rog_aura = { path = "../rog-aura" }
|
||||
rog_dbus = { path = "../rog-dbus" }
|
||||
rog_types = { path = "../rog-types" }
|
||||
daemon = { path = "../daemon" }
|
||||
gumdrop = "^0.8"
|
||||
yansi-term = "^0.1"
|
||||
rog_profiles = { path = "../rog-profiles" }
|
||||
rog_platform = { path = "../rog-platform" }
|
||||
asusd = { path = "../asusd" }
|
||||
dmi_id = { path = "../dmi-id" }
|
||||
|
||||
gumdrop.workspace = true
|
||||
toml.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
tinybmp = "^0.2.3"
|
||||
rog_dbus = { path = "../rog-dbus" }
|
||||
gif.workspace = true
|
||||
tinybmp.workspace = true
|
||||
glam.workspace = true
|
||||
rog_dbus = { path = "../rog-dbus" }
|
||||
|
||||
cargo-husky.workspace = true
|
||||
@@ -1,42 +0,0 @@
|
||||
use rog_dbus::AuraDbusClient;
|
||||
use rog_types::anime_matrix::{AniMeImageBuffer, AniMePacketType, HEIGHT, WIDTH};
|
||||
use tinybmp::{Bmp, Pixel};
|
||||
|
||||
fn main() {
|
||||
let (client, _) = AuraDbusClient::new().unwrap();
|
||||
|
||||
let bmp =
|
||||
Bmp::from_slice(include_bytes!("non-skewed_r.bmp")).expect("Failed to parse BMP image");
|
||||
let pixels: Vec<Pixel> = bmp.into_iter().collect();
|
||||
//assert_eq!(pixels.len(), 56 * 56);
|
||||
|
||||
// Try an outline, top and right
|
||||
let mut matrix = AniMeImageBuffer::new();
|
||||
|
||||
// Aligned left
|
||||
for (i, px) in pixels.iter().enumerate() {
|
||||
if (px.x as usize / 2) < WIDTH && (px.y as usize) < HEIGHT && px.x % 2 == 0 {
|
||||
let mut c = px.color as u32;
|
||||
matrix.get_mut()[px.y as usize][px.x as usize / 2] = c as u8;
|
||||
}
|
||||
}
|
||||
|
||||
// Throw an alignment border up
|
||||
// {
|
||||
// let tmp = matrix.get_mut();
|
||||
// for x in tmp[0].iter_mut() {
|
||||
// *x = 0xff;
|
||||
// }
|
||||
// for row in tmp.iter_mut() {
|
||||
// row[row.len() - 1] = 0xff;
|
||||
// }
|
||||
// }
|
||||
|
||||
matrix.debug_print();
|
||||
|
||||
let mut matrix: AniMePacketType = AniMePacketType::from(matrix);
|
||||
// println!("{:?}", matrix[0].to_vec());
|
||||
// println!("{:?}", matrix[1].to_vec());
|
||||
|
||||
//client.proxies().anime().set_brightness(&mut matrix).unwrap();
|
||||
}
|
||||
36
asusctl/examples/anime-diag-png.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
use std::path::Path;
|
||||
use std::process::exit;
|
||||
|
||||
use rog_anime::usb::get_anime_type;
|
||||
use rog_anime::{AnimeDiagonal, AnimeType};
|
||||
use rog_dbus::RogDbusClientBlocking;
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let (client, _) = RogDbusClientBlocking::new().unwrap();
|
||||
|
||||
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(),
|
||||
AnimeType::GA401,
|
||||
)?;
|
||||
|
||||
let anime_type = get_anime_type()?;
|
||||
|
||||
client
|
||||
.proxies()
|
||||
.anime()
|
||||
.write(matrix.into_data_buffer(anime_type)?)
|
||||
.unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
38
asusctl/examples/anime-diag.rs
Normal file
@@ -0,0 +1,38 @@
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
|
||||
use rog_anime::usb::get_anime_type;
|
||||
use rog_anime::{AnimeDiagonal, AnimeType};
|
||||
use rog_dbus::RogDbusClientBlocking;
|
||||
|
||||
// In usable data:
|
||||
// Top row start at 1, ends at 32
|
||||
|
||||
// 74w x 36h diagonal used by the windows app
|
||||
|
||||
fn main() {
|
||||
let (client, _) = RogDbusClientBlocking::new().unwrap();
|
||||
|
||||
for step in (2..50).rev() {
|
||||
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).step_by(step) {
|
||||
for i in &mut matrix.get_mut()[c] {
|
||||
*i = 50;
|
||||
}
|
||||
}
|
||||
|
||||
let anime_type = get_anime_type().unwrap();
|
||||
client
|
||||
.proxies()
|
||||
.anime()
|
||||
.write(matrix.into_data_buffer(anime_type).unwrap())
|
||||
.unwrap();
|
||||
sleep(Duration::from_millis(300));
|
||||
}
|
||||
}
|
||||
46
asusctl/examples/anime-gif.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
use std::env;
|
||||
use std::path::Path;
|
||||
use std::thread::sleep;
|
||||
|
||||
use rog_anime::usb::get_anime_type;
|
||||
use rog_anime::{ActionData, ActionLoader, Sequences};
|
||||
use rog_dbus::RogDbusClientBlocking;
|
||||
|
||||
fn main() {
|
||||
let (client, _) = RogDbusClientBlocking::new().unwrap();
|
||||
|
||||
let args: Vec<String> = env::args().collect();
|
||||
if args.len() != 3 {
|
||||
println!("Please supply filepath and brightness");
|
||||
return;
|
||||
}
|
||||
|
||||
let path = Path::new(&args[1]);
|
||||
let brightness = args[2].parse::<f32>().unwrap();
|
||||
let anime_type = get_anime_type().unwrap();
|
||||
let mut seq = Sequences::new(anime_type);
|
||||
seq.insert(
|
||||
0,
|
||||
&ActionLoader::AsusAnimation {
|
||||
file: path.into(),
|
||||
time: rog_anime::AnimTime::Infinite,
|
||||
brightness,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
loop {
|
||||
for action in seq.iter() {
|
||||
if let ActionData::Animation(frames) = action {
|
||||
for frame in frames.frames() {
|
||||
client
|
||||
.proxies()
|
||||
.anime()
|
||||
.write(frame.frame().clone())
|
||||
.unwrap();
|
||||
sleep(frame.delay());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
47
asusctl/examples/anime-grid.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use rog_anime::usb::get_anime_type;
|
||||
use rog_anime::{AnimeDataBuffer, AnimeGrid};
|
||||
use rog_dbus::RogDbusClientBlocking;
|
||||
|
||||
// In usable data:
|
||||
// Top row start at 1, ends at 32
|
||||
|
||||
// 74w x 36h diagonal used by the windows app
|
||||
|
||||
fn main() {
|
||||
let (client, _) = RogDbusClientBlocking::new().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;
|
||||
}
|
||||
row[row.len() - i] = 0x22;
|
||||
if i > 5 {
|
||||
row[row.len() - i + 5] = 0x22;
|
||||
}
|
||||
if i > 10 {
|
||||
row[row.len() - i + 10] = 0x22;
|
||||
}
|
||||
|
||||
if i > 15 {
|
||||
row[row.len() - i + 15] = 0x22;
|
||||
}
|
||||
|
||||
if i > 20 {
|
||||
row[row.len() - i + 20] = 0x22;
|
||||
}
|
||||
|
||||
if i > 25 {
|
||||
row[row.len() - i + 25] = 0x22;
|
||||
}
|
||||
}
|
||||
|
||||
let matrix = <AnimeDataBuffer>::try_from(matrix).unwrap();
|
||||
|
||||
client.proxies().anime().write(matrix).unwrap();
|
||||
}
|
||||
131
asusctl/examples/anime-outline.rs
Normal file
@@ -0,0 +1,131 @@
|
||||
use rog_anime::usb::get_anime_type;
|
||||
use rog_anime::AnimeDataBuffer;
|
||||
use rog_dbus::RogDbusClientBlocking;
|
||||
|
||||
// In usable data:
|
||||
// Top row start at 1, ends at 32
|
||||
|
||||
fn main() {
|
||||
let (client, _) = RogDbusClientBlocking::new().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.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.data_mut()[1244] = 100; // end
|
||||
println!("{:?}", &matrix);
|
||||
|
||||
client.proxies().anime().write(matrix).unwrap();
|
||||
}
|
||||
41
asusctl/examples/anime-png.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
use std::convert::TryFrom;
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
use std::path::Path;
|
||||
use std::process::exit;
|
||||
|
||||
use rog_anime::usb::get_anime_type;
|
||||
use rog_anime::{AnimeDataBuffer, AnimeImage, Vec2};
|
||||
use rog_dbus::RogDbusClientBlocking;
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let (client, _) = RogDbusClientBlocking::new().unwrap();
|
||||
|
||||
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(),
|
||||
args[3].parse::<f32>().unwrap(),
|
||||
Vec2::new(
|
||||
args[4].parse::<f32>().unwrap(),
|
||||
args[5].parse::<f32>().unwrap(),
|
||||
),
|
||||
args[6].parse::<f32>().unwrap(),
|
||||
anime_type,
|
||||
)?;
|
||||
|
||||
client
|
||||
.proxies()
|
||||
.anime()
|
||||
.write(<AnimeDataBuffer>::try_from(&matrix)?)
|
||||
.unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
51
asusctl/examples/anime-spinning.rs
Normal file
@@ -0,0 +1,51 @@
|
||||
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::usb::get_anime_type;
|
||||
use rog_anime::{AnimeDataBuffer, AnimeImage, Vec2};
|
||||
use rog_dbus::RogDbusClientBlocking;
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let (client, _) = RogDbusClientBlocking::new().unwrap();
|
||||
|
||||
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(),
|
||||
args[3].parse::<f32>().unwrap(),
|
||||
Vec2::new(
|
||||
args[4].parse::<f32>().unwrap(),
|
||||
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.update();
|
||||
|
||||
client
|
||||
.proxies()
|
||||
.anime()
|
||||
.write(<AnimeDataBuffer>::try_from(&matrix)?)
|
||||
.unwrap();
|
||||
sleep(Duration::from_micros(500));
|
||||
}
|
||||
}
|
||||
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));
|
||||
}
|
||||
}
|
||||
68
asusctl/examples/aura-zoned-breathe.rs
Normal file
@@ -0,0 +1,68 @@
|
||||
//! Using a combination of key-colour array plus a key layout to generate
|
||||
//! outputs.
|
||||
|
||||
use rog_aura::advanced::LedCode;
|
||||
use rog_aura::effects::{AdvancedEffects, Effect};
|
||||
use rog_aura::layouts::KeyLayout;
|
||||
use rog_aura::Colour;
|
||||
use rog_dbus::RogDbusClientBlocking;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let layout = KeyLayout::default_layout();
|
||||
|
||||
let (client, _) = RogDbusClientBlocking::new().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();
|
||||
|
||||
client.proxies().aura().direct_addressing_raw(packets)?;
|
||||
std::thread::sleep(std::time::Duration::from_millis(33));
|
||||
}
|
||||
}
|
||||
BIN
asusctl/examples/controller.gif
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
asusctl/examples/doom.png
Normal file
|
After Width: | Height: | Size: 140 KiB |
BIN
asusctl/examples/ferris.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 18 KiB |
BIN
asusctl/examples/nudoom.png
Normal file
|
After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 7.6 KiB |
BIN
asusctl/examples/rust.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 7.6 KiB |
154
asusctl/src/anime_cli.rs
Normal file
@@ -0,0 +1,154 @@
|
||||
use gumdrop::Options;
|
||||
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 = "set global base brightness value <Off, Low, Med, High>"
|
||||
)]
|
||||
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 = "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)]
|
||||
pub struct AnimeImage {
|
||||
#[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,
|
||||
}
|
||||
|
||||
#[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,
|
||||
}
|
||||
366
asusctl/src/aura_cli.rs
Normal file
@@ -0,0 +1,366 @@
|
||||
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(meta = "", help = "Use with awake option <true/false>")]
|
||||
pub keyboard: Option<bool>,
|
||||
#[options(meta = "", help = "Use with awake option <true/false>")]
|
||||
pub lightbar: Option<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<u8>,
|
||||
}
|
||||
impl LedBrightness {
|
||||
pub fn new(level: Option<u8>) -> Self {
|
||||
LedBrightness { level }
|
||||
}
|
||||
|
||||
pub fn level(&self) -> Option<u8> {
|
||||
self.level
|
||||
}
|
||||
}
|
||||
impl FromStr for LedBrightness {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let s = s.to_lowercase();
|
||||
match s.as_str() {
|
||||
"off" => Ok(LedBrightness { level: Some(0x00) }),
|
||||
"low" => Ok(LedBrightness { level: Some(0x01) }),
|
||||
"med" => Ok(LedBrightness { level: Some(0x02) }),
|
||||
"high" => Ok(LedBrightness { level: Some(0x03) }),
|
||||
_ => {
|
||||
print!("Invalid argument, must be one of: off, low, med, high");
|
||||
Err(Error::ParseBrightness)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl ToString for LedBrightness {
|
||||
fn to_string(&self) -> String {
|
||||
let s = match self.level {
|
||||
Some(0x00) => "low",
|
||||
Some(0x01) => "med",
|
||||
Some(0x02) => "high",
|
||||
_ => "unknown",
|
||||
};
|
||||
s.to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Options, Default)]
|
||||
pub struct SingleSpeed {
|
||||
#[options(help = "print help message")]
|
||||
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")]
|
||||
help: bool,
|
||||
#[options(no_long, meta = "", help = "set the direction: up, down, left, right")]
|
||||
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)]
|
||||
pub struct SingleColour {
|
||||
#[options(help = "print help message")]
|
||||
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)]
|
||||
pub struct SingleColourSpeed {
|
||||
#[options(help = "print help message")]
|
||||
help: bool,
|
||||
#[options(no_long, meta = "", help = "set the RGB value e.g, ff00ff")]
|
||||
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)]
|
||||
pub struct TwoColourSpeed {
|
||||
#[options(help = "print help message")]
|
||||
help: bool,
|
||||
#[options(no_long, meta = "", help = "set the first RGB value e.g, ff00ff")]
|
||||
pub colour: Colour,
|
||||
#[options(no_long, meta = "", help = "set the second RGB value e.g, ff00ff")]
|
||||
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 MultiZone {
|
||||
#[options(help = "print help message")]
|
||||
help: bool,
|
||||
#[options(short = "a", meta = "", help = "set the RGB value e.g, ff00ff")]
|
||||
pub colour1: Colour,
|
||||
#[options(short = "b", meta = "", help = "set the RGB value e.g, ff00ff")]
|
||||
pub colour2: Colour,
|
||||
#[options(short = "c", meta = "", help = "set the RGB value e.g, ff00ff")]
|
||||
pub colour3: Colour,
|
||||
#[options(short = "d", meta = "", help = "set the RGB value e.g, ff00ff")]
|
||||
pub colour4: Colour,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Options)]
|
||||
pub struct MultiColourSpeed {
|
||||
#[options(help = "print help message")]
|
||||
help: bool,
|
||||
#[options(short = "a", meta = "", help = "set the RGB value e.g, ff00ff")]
|
||||
pub colour1: Colour,
|
||||
#[options(short = "b", meta = "", help = "set the RGB value e.g, ff00ff")]
|
||||
pub colour2: Colour,
|
||||
#[options(short = "c", meta = "", help = "set the RGB value e.g, ff00ff")]
|
||||
pub colour3: Colour,
|
||||
#[options(short = "d", meta = "", help = "set the RGB value e.g, ff00ff")]
|
||||
pub colour4: Colour,
|
||||
#[options(no_long, meta = "", help = "set the speed: low, med, high")]
|
||||
pub speed: Speed,
|
||||
}
|
||||
|
||||
/// 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),
|
||||
#[options(help = "pulse between one or two colours")]
|
||||
Breathe(TwoColourSpeed),
|
||||
#[options(help = "strobe through all colours")]
|
||||
Strobe(SingleSpeed),
|
||||
#[options(help = "rainbow cycling in one of four directions")]
|
||||
Rainbow(SingleSpeedDirection),
|
||||
#[options(help = "rain pattern mimicking raindrops")]
|
||||
Stars(TwoColourSpeed),
|
||||
#[options(help = "rain pattern of three preset colours")]
|
||||
Rain(SingleSpeed),
|
||||
#[options(help = "pressed keys are highlighted to fade")]
|
||||
Highlight(SingleColourSpeed),
|
||||
#[options(help = "pressed keys generate horizontal laser")]
|
||||
Laser(SingleColourSpeed),
|
||||
#[options(help = "pressed keys ripple outwards like a splash")]
|
||||
Ripple(SingleColourSpeed),
|
||||
#[options(help = "set a rapid pulse")]
|
||||
Pulse(SingleColour),
|
||||
#[options(help = "set a vertical line zooming from left")]
|
||||
Comet(SingleColour),
|
||||
#[options(help = "set a wide vertical line zooming from left")]
|
||||
Flash(SingleColour),
|
||||
}
|
||||
|
||||
impl Default for SetAuraBuiltin {
|
||||
fn default() -> Self {
|
||||
SetAuraBuiltin::Static(SingleColour::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&SingleColour> for AuraEffect {
|
||||
fn from(aura: &SingleColour) -> Self {
|
||||
Self {
|
||||
colour1: aura.colour,
|
||||
zone: aura.zone,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&SingleSpeed> for AuraEffect {
|
||||
fn from(aura: &SingleSpeed) -> Self {
|
||||
Self {
|
||||
speed: aura.speed,
|
||||
zone: aura.zone,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&SingleColourSpeed> for AuraEffect {
|
||||
fn from(aura: &SingleColourSpeed) -> Self {
|
||||
Self {
|
||||
colour1: aura.colour,
|
||||
speed: aura.speed,
|
||||
zone: aura.zone,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&TwoColourSpeed> for AuraEffect {
|
||||
fn from(aura: &TwoColourSpeed) -> Self {
|
||||
Self {
|
||||
colour1: aura.colour,
|
||||
colour2: aura.colour2,
|
||||
zone: aura.zone,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&SingleSpeedDirection> for AuraEffect {
|
||||
fn from(aura: &SingleSpeedDirection) -> Self {
|
||||
Self {
|
||||
speed: aura.speed,
|
||||
direction: aura.direction,
|
||||
zone: aura.zone,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&SetAuraBuiltin> for AuraEffect {
|
||||
fn from(aura: &SetAuraBuiltin) -> Self {
|
||||
match aura {
|
||||
SetAuraBuiltin::Static(x) => {
|
||||
let mut data: AuraEffect = x.into();
|
||||
data.mode = AuraModeNum::Static;
|
||||
data
|
||||
}
|
||||
SetAuraBuiltin::Breathe(x) => {
|
||||
let mut data: AuraEffect = x.into();
|
||||
data.mode = AuraModeNum::Breathe;
|
||||
data
|
||||
}
|
||||
SetAuraBuiltin::Strobe(x) => {
|
||||
let mut data: AuraEffect = x.into();
|
||||
data.mode = AuraModeNum::Strobe;
|
||||
data
|
||||
}
|
||||
SetAuraBuiltin::Rainbow(x) => {
|
||||
let mut data: AuraEffect = x.into();
|
||||
data.mode = AuraModeNum::Rainbow;
|
||||
data
|
||||
}
|
||||
SetAuraBuiltin::Stars(x) => {
|
||||
let mut data: AuraEffect = x.into();
|
||||
data.mode = AuraModeNum::Star;
|
||||
data
|
||||
}
|
||||
SetAuraBuiltin::Rain(x) => {
|
||||
let mut data: AuraEffect = x.into();
|
||||
data.mode = AuraModeNum::Rain;
|
||||
data
|
||||
}
|
||||
SetAuraBuiltin::Highlight(x) => {
|
||||
let mut data: AuraEffect = x.into();
|
||||
data.mode = AuraModeNum::Highlight;
|
||||
data
|
||||
}
|
||||
SetAuraBuiltin::Laser(x) => {
|
||||
let mut data: AuraEffect = x.into();
|
||||
data.mode = AuraModeNum::Laser;
|
||||
data
|
||||
}
|
||||
SetAuraBuiltin::Ripple(x) => {
|
||||
let mut data: AuraEffect = x.into();
|
||||
data.mode = AuraModeNum::Ripple;
|
||||
data
|
||||
}
|
||||
SetAuraBuiltin::Pulse(x) => {
|
||||
let mut data: AuraEffect = x.into();
|
||||
data.mode = AuraModeNum::Pulse;
|
||||
data
|
||||
}
|
||||
SetAuraBuiltin::Comet(x) => {
|
||||
let mut data: AuraEffect = x.into();
|
||||
data.mode = AuraModeNum::Comet;
|
||||
data
|
||||
}
|
||||
SetAuraBuiltin::Flash(x) => {
|
||||
let mut data: AuraEffect = x.into();
|
||||
data.mode = AuraModeNum::Flash;
|
||||
data
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
115
asusctl/src/cli_opts.rs
Normal file
@@ -0,0 +1,115 @@
|
||||
use gumdrop::Options;
|
||||
use rog_platform::platform::PlatformPolicy;
|
||||
|
||||
use crate::anime_cli::AnimeCommand;
|
||||
use crate::aura_cli::{LedBrightness, LedPowerCommand1, LedPowerCommand2, SetAuraBuiltin};
|
||||
use crate::fan_curve_cli::FanCurveCommand;
|
||||
|
||||
#[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(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<PlatformPolicy>,
|
||||
}
|
||||
|
||||
#[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
@@ -0,0 +1,49 @@
|
||||
use gumdrop::Options;
|
||||
use rog_platform::platform::PlatformPolicy;
|
||||
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<PlatformPolicy>,
|
||||
|
||||
#[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>,
|
||||
}
|
||||
1071
asusctl/src/main.rs
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
|
||||
14
asusd-user/README.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# daemon-user
|
||||
|
||||
This crate is for the binary of `asusd-user` and its helper lib.
|
||||
|
||||
The purpose of `asusd-user` is to run in userland and provide the user + third-party apps an interface for such things as creating AniMe sequences (and more in future, see todo list).
|
||||
|
||||
`asusd-user` should try to be as simple as possible while allowing a decent degree of control.
|
||||
|
||||
## TODO
|
||||
|
||||
- [ ] CLI for basic settings/interaction
|
||||
- [ ] RGB keyboard per-key programs
|
||||
- [ ] User profiles (fan, cpu etc). These would be replacing the system-daemon profiles only when the user is active, otherwise system-daemon defaults to system settings.
|
||||
- [ ] Audio EQ visualiser - for use with anime + keyboard lighting
|
||||
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::advanced::LedCode;
|
||||
use rog_aura::effects::{AdvancedEffects as AuraSequences, Breathe, DoomFlicker, Effect, Static};
|
||||
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 {}
|
||||
369
asusd-user/src/ctrl_anime.rs
Normal file
@@ -0,0 +1,369 @@
|
||||
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 config_traits::StdConfig;
|
||||
use rog_anime::error::AnimeError;
|
||||
use rog_anime::{ActionData, ActionLoader, AnimTime, Fade, Sequences, Vec2};
|
||||
use rog_dbus::RogDbusClientBlocking;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use zbus::dbus_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 {
|
||||
Timer,
|
||||
Count,
|
||||
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>>`
|
||||
pub struct CtrlAnimeInner<'a> {
|
||||
sequences: Sequences,
|
||||
client: RogDbusClientBlocking<'a>,
|
||||
do_early_return: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl<'a> CtrlAnimeInner<'static> {
|
||||
pub fn new(
|
||||
sequences: Sequences,
|
||||
client: RogDbusClientBlocking<'static>,
|
||||
do_early_return: Arc<AtomicBool>,
|
||||
) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
sequences,
|
||||
client,
|
||||
do_early_return,
|
||||
})
|
||||
}
|
||||
|
||||
/// 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(());
|
||||
}
|
||||
|
||||
for action in self.sequences.iter() {
|
||||
match action {
|
||||
ActionData::Animation(frames) => {
|
||||
rog_anime::run_animation(frames, &|output| {
|
||||
if self.do_early_return.load(Ordering::Acquire) {
|
||||
return Ok(true); // Do safe exit
|
||||
}
|
||||
self.client
|
||||
.proxies()
|
||||
.anime()
|
||||
.write(output)
|
||||
.map_err(|e| AnimeError::Dbus(format!("{}", e)))
|
||||
.map(|_| false)
|
||||
});
|
||||
}
|
||||
ActionData::Image(image) => {
|
||||
self.client
|
||||
.proxies()
|
||||
.anime()
|
||||
.write(image.as_ref().clone())
|
||||
.ok();
|
||||
}
|
||||
ActionData::Pause(duration) => {
|
||||
let start = Instant::now();
|
||||
'pause: loop {
|
||||
if self.do_early_return.load(Ordering::SeqCst) {
|
||||
return Ok(());
|
||||
}
|
||||
if Instant::now().duration_since(start) > *duration {
|
||||
break 'pause;
|
||||
}
|
||||
sleep(Duration::from_millis(1));
|
||||
}
|
||||
}
|
||||
ActionData::AudioEq
|
||||
| ActionData::SystemInfo
|
||||
| ActionData::TimeDate
|
||||
| ActionData::Matrix => {}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CtrlAnime<'a> {
|
||||
config: Arc<Mutex<ConfigAnime>>,
|
||||
client: RogDbusClientBlocking<'a>,
|
||||
inner: Arc<Mutex<CtrlAnimeInner<'a>>>,
|
||||
/// Must be the same Atomic as in CtrlAnimeInner
|
||||
inner_early_return: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl CtrlAnime<'static> {
|
||||
pub fn new(
|
||||
config: Arc<Mutex<ConfigAnime>>,
|
||||
inner: Arc<Mutex<CtrlAnimeInner<'static>>>,
|
||||
client: RogDbusClientBlocking<'static>,
|
||||
inner_early_return: Arc<AtomicBool>,
|
||||
) -> Result<Self, Error> {
|
||||
Ok(CtrlAnime {
|
||||
config,
|
||||
client,
|
||||
inner,
|
||||
inner_early_return,
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
// The pattern for a zbus method is:
|
||||
// - Get config lock if required
|
||||
// - Set inner_early_return to stop the inner run loop temporarily
|
||||
// - Do actions
|
||||
// - Write config if required
|
||||
// - Unset inner_early_return
|
||||
#[dbus_interface(name = "org.asuslinux.Daemon")]
|
||||
impl CtrlAnime<'static> {
|
||||
pub fn insert_asus_gif(
|
||||
&mut self,
|
||||
index: u32,
|
||||
file: &str,
|
||||
time: Timer,
|
||||
brightness: f32,
|
||||
) -> zbus::fdo::Result<String> {
|
||||
if let Ok(mut config) = self.config.try_lock() {
|
||||
let time: AnimTime = time.into();
|
||||
let file = Path::new(&file);
|
||||
let action = ActionLoader::AsusAnimation {
|
||||
file: file.into(),
|
||||
brightness,
|
||||
time,
|
||||
};
|
||||
|
||||
// Must make the inner run loop return early
|
||||
self.inner_early_return.store(true, Ordering::SeqCst);
|
||||
|
||||
if let Ok(mut controller) = self.inner.lock() {
|
||||
controller
|
||||
.sequences
|
||||
.insert(index as usize, &action)
|
||||
.map_err(|err| zbus::fdo::Error::Failed(err.to_string()))?;
|
||||
}
|
||||
config.anime.push(action);
|
||||
config.write();
|
||||
|
||||
let json = serde_json::to_string_pretty(&*config).expect("Parse config to JSON failed");
|
||||
|
||||
// Release the inner run loop again
|
||||
self.inner_early_return.store(false, Ordering::SeqCst);
|
||||
return Ok(json);
|
||||
}
|
||||
Err(zbus::fdo::Error::Failed("UserConfig lock fail".into()))
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn insert_image_gif(
|
||||
&mut self,
|
||||
index: u32,
|
||||
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 time: AnimTime = time.into();
|
||||
let file = Path::new(&file);
|
||||
let translation = Vec2::new(xy.0, xy.1);
|
||||
let action = ActionLoader::ImageAnimation {
|
||||
file: file.into(),
|
||||
scale,
|
||||
angle,
|
||||
translation,
|
||||
brightness,
|
||||
time,
|
||||
};
|
||||
|
||||
// Must make the inner run loop return early
|
||||
self.inner_early_return.store(true, Ordering::SeqCst);
|
||||
|
||||
if let Ok(mut controller) = self.inner.lock() {
|
||||
controller
|
||||
.sequences
|
||||
.insert(index as usize, &action)
|
||||
.map_err(|err| zbus::fdo::Error::Failed(err.to_string()))?;
|
||||
}
|
||||
config.anime.push(action);
|
||||
config.write();
|
||||
|
||||
let json =
|
||||
serde_json::to_string_pretty(&*config.anime).expect("Parse config to JSON failed");
|
||||
|
||||
// Release the inner run loop again
|
||||
self.inner_early_return.store(false, Ordering::SeqCst);
|
||||
return Ok(json);
|
||||
}
|
||||
Err(zbus::fdo::Error::Failed("UserConfig lock fail".into()))
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn insert_image(
|
||||
&mut self,
|
||||
index: u32,
|
||||
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 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
|
||||
self.inner_early_return.store(true, Ordering::SeqCst);
|
||||
|
||||
if let Ok(mut controller) = self.inner.lock() {
|
||||
controller
|
||||
.sequences
|
||||
.insert(index as usize, &action)
|
||||
.map_err(|err| zbus::fdo::Error::Failed(err.to_string()))?;
|
||||
}
|
||||
config.anime.push(action);
|
||||
config.write();
|
||||
|
||||
let json =
|
||||
serde_json::to_string_pretty(&*config.anime).expect("Parse config to JSON failed");
|
||||
|
||||
// Release the inner run loop again
|
||||
self.inner_early_return.store(false, Ordering::SeqCst);
|
||||
return Ok(json);
|
||||
}
|
||||
Err(zbus::fdo::Error::Failed("UserConfig lock fail".into()))
|
||||
}
|
||||
|
||||
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 = ActionLoader::Pause(Duration::from_millis(millis));
|
||||
// Must make the inner run loop return early
|
||||
self.inner_early_return.store(true, Ordering::SeqCst);
|
||||
|
||||
if let Ok(mut controller) = self.inner.lock() {
|
||||
controller
|
||||
.sequences
|
||||
.insert(index as usize, &action)
|
||||
.map_err(|err| zbus::fdo::Error::Failed(err.to_string()))?;
|
||||
}
|
||||
config.anime.push(action);
|
||||
config.write();
|
||||
|
||||
let json =
|
||||
serde_json::to_string_pretty(&*config.anime).expect("Parse config to JSON failed");
|
||||
|
||||
// Release the inner run loop again
|
||||
self.inner_early_return.store(false, Ordering::SeqCst);
|
||||
return Ok(json);
|
||||
}
|
||||
Err(zbus::fdo::Error::Failed("UserConfig lock fail".into()))
|
||||
}
|
||||
|
||||
pub fn remove_item(&mut self, index: u32) -> zbus::fdo::Result<String> {
|
||||
if let Ok(mut config) = self.config.try_lock() {
|
||||
// Must make the inner run loop return early
|
||||
self.inner_early_return.store(true, Ordering::SeqCst);
|
||||
|
||||
if let Ok(mut controller) = self.inner.lock() {
|
||||
controller.sequences.remove_item(index as usize);
|
||||
}
|
||||
if (index as usize) < config.anime.len() {
|
||||
config.anime.remove(index as usize);
|
||||
}
|
||||
config.write();
|
||||
|
||||
let json =
|
||||
serde_json::to_string_pretty(&*config.anime).expect("Parse config to JSON failed");
|
||||
|
||||
// Release the inner run loop again
|
||||
self.inner_early_return.store(false, Ordering::SeqCst);
|
||||
return Ok(json);
|
||||
}
|
||||
Err(zbus::fdo::Error::Failed("UserConfig lock fail".into()))
|
||||
}
|
||||
|
||||
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().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().set_enable_display(on).ok();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
118
asusd-user/src/daemon.rs
Normal file
@@ -0,0 +1,118 @@
|
||||
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::LaptopLedData;
|
||||
use rog_aura::layouts::KeyLayout;
|
||||
use rog_dbus::{RogDbusClientBlocking, 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 (client, _) = RogDbusClientBlocking::new()?;
|
||||
let supported = client
|
||||
.proxies()
|
||||
.platform()
|
||||
.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));
|
||||
|
||||
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, client, early_return.clone()).unwrap(),
|
||||
));
|
||||
// Need new client object for dbus control part
|
||||
let (client, _) = RogDbusClientBlocking::new().unwrap();
|
||||
let anime_control =
|
||||
CtrlAnime::new(anime_config, inner.clone(), client, 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 = LaptopLedData::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());
|
||||
|
||||
executor
|
||||
.spawn(async move {
|
||||
// Create server
|
||||
let (client, _) = RogDbusClientBlocking::new().unwrap();
|
||||
// let connection = Connection::session().await.unwrap();
|
||||
// connection.request_name(DBUS_NAME).await.unwrap();
|
||||
|
||||
loop {
|
||||
aura_config.aura.next_state(&layout);
|
||||
let packets = aura_config.aura.create_packets();
|
||||
|
||||
client
|
||||
.proxies()
|
||||
.aura()
|
||||
.direct_addressing_raw(packets)
|
||||
.unwrap();
|
||||
std::thread::sleep(std::time::Duration::from_millis(33));
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
// }
|
||||
|
||||
loop {
|
||||
smol::block_on(executor.tick());
|
||||
}
|
||||
}
|
||||
45
asusd-user/src/error.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
use std::fmt;
|
||||
|
||||
use rog_anime::error::AnimeError;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Io(std::io::Error),
|
||||
ConfigLoadFail,
|
||||
ConfigLockFail,
|
||||
XdgVars,
|
||||
Anime(AnimeError),
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
// This trait requires `fmt` with this exact signature.
|
||||
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"),
|
||||
Error::ConfigLockFail => write!(f, "Failed to lock user config"),
|
||||
Error::XdgVars => write!(f, "XDG environment vars appear unset"),
|
||||
Error::Anime(err) => write!(f, "Anime error: {}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {}
|
||||
|
||||
impl From<std::io::Error> for Error {
|
||||
fn from(err: std::io::Error) -> Self {
|
||||
Error::Io(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AnimeError> for Error {
|
||||
fn from(err: AnimeError) -> Self {
|
||||
Error::Anime(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Error> for zbus::fdo::Error {
|
||||
fn from(err: Error) -> Self {
|
||||
zbus::fdo::Error::Failed(format!("Anime zbus error: {}", err))
|
||||
}
|
||||
}
|
||||
9
asusd-user/src/lib.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
pub mod config;
|
||||
|
||||
pub mod error;
|
||||
|
||||
pub mod ctrl_anime;
|
||||
|
||||
pub mod zbus_anime;
|
||||
|
||||
pub static VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
73
asusd-user/src/zbus_anime.rs
Normal file
@@ -0,0 +1,73 @@
|
||||
//! # `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`.
|
||||
//!
|
||||
//! You may prefer to adapt it, instead of using it verbatim.
|
||||
//!
|
||||
//! More information can be found in the
|
||||
//! [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),
|
||||
//! (`org.freedesktop.DBus.*`) for which the following zbus proxies can be used:
|
||||
//!
|
||||
//! * [`zbus::fdo::PeerProxy`]
|
||||
//! * [`zbus::fdo::IntrospectableProxy`]
|
||||
//! * [`zbus::fdo::PropertiesProxy`]
|
||||
//!
|
||||
//! …consequently `zbus-xmlgen` did not generate code for the above interfaces.
|
||||
#![allow(clippy::too_many_arguments)]
|
||||
|
||||
use zbus::dbus_proxy;
|
||||
|
||||
#[dbus_proxy(
|
||||
interface = "org.asuslinux.Daemon",
|
||||
default_path = "/org/asuslinux/Anime"
|
||||
)]
|
||||
trait Daemon {
|
||||
/// InsertAsusGif method
|
||||
fn insert_asus_gif(
|
||||
&self,
|
||||
index: u32,
|
||||
file: &str,
|
||||
time: u32,
|
||||
count: u32,
|
||||
brightness: f64,
|
||||
) -> zbus::Result<String>;
|
||||
|
||||
/// InsertImage method
|
||||
fn insert_image(
|
||||
&self,
|
||||
index: u32,
|
||||
file: &str,
|
||||
scale: f64,
|
||||
angle: f64,
|
||||
xy: &(f64, f64),
|
||||
brightness: f64,
|
||||
) -> zbus::Result<String>;
|
||||
|
||||
/// InsertImageGif method
|
||||
fn insert_image_gif(
|
||||
&self,
|
||||
index: u32,
|
||||
file: &str,
|
||||
scale: f64,
|
||||
angle: f64,
|
||||
xy: &(f64, f64),
|
||||
time: u32,
|
||||
count: u32,
|
||||
brightness: f64,
|
||||
) -> zbus::Result<String>;
|
||||
|
||||
/// InsertPause method
|
||||
fn insert_pause(&self, index: u32, millis: u64) -> zbus::Result<String>;
|
||||
|
||||
/// RemoveItem method
|
||||
fn remove_item(&self, index: u32) -> zbus::Result<String>;
|
||||
|
||||
/// SetState method
|
||||
fn set_state(&self, on: bool) -> zbus::Result<()>;
|
||||
}
|
||||
45
asusd/Cargo.toml
Normal file
@@ -0,0 +1,45 @@
|
||||
[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_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
|
||||
|
||||
async-trait.workspace = true
|
||||
tokio.workspace = true
|
||||
|
||||
# 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
|
||||
100
asusd/src/config.rs
Normal file
@@ -0,0 +1,100 @@
|
||||
use config_traits::{StdConfig, StdConfigLoad2};
|
||||
use rog_platform::platform::PlatformPolicy;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
const CONFIG_FILE: &str = "asusd.ron";
|
||||
|
||||
#[derive(Deserialize, Serialize, Default, Debug)]
|
||||
pub struct Config {
|
||||
/// 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: PlatformPolicy,
|
||||
pub platform_policy_on_battery: PlatformPolicy,
|
||||
pub platform_policy_on_ac: PlatformPolicy,
|
||||
//
|
||||
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 StdConfig for Config {
|
||||
fn new() -> Self {
|
||||
Config {
|
||||
charge_control_end_threshold: 100,
|
||||
disable_nvidia_powerd_on_battery: true,
|
||||
platform_policy_on_battery: PlatformPolicy::Quiet,
|
||||
platform_policy_on_ac: PlatformPolicy::Performance,
|
||||
ac_command: String::new(),
|
||||
bat_command: String::new(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn config_dir() -> std::path::PathBuf {
|
||||
std::path::PathBuf::from(crate::CONFIG_PATH_BASE)
|
||||
}
|
||||
|
||||
fn file_name(&self) -> String {
|
||||
CONFIG_FILE.to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
impl StdConfigLoad2<Config462, Config472> for Config {}
|
||||
|
||||
#[derive(Deserialize, Serialize, Default, Debug)]
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct Config462 {
|
||||
/// Save charge limit for restoring on boot
|
||||
pub bat_charge_limit: u8,
|
||||
pub panel_od: bool,
|
||||
pub disable_nvidia_powerd_on_battery: bool,
|
||||
pub ac_command: String,
|
||||
pub bat_command: String,
|
||||
}
|
||||
|
||||
impl From<Config462> for Config {
|
||||
fn from(c: Config462) -> Self {
|
||||
Self {
|
||||
charge_control_end_threshold: c.bat_charge_limit,
|
||||
panel_od: c.panel_od,
|
||||
disable_nvidia_powerd_on_battery: true,
|
||||
ac_command: String::new(),
|
||||
bat_command: String::new(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
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)]
|
||||
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 config_dir() -> std::path::PathBuf {
|
||||
std::path::PathBuf::from(crate::CONFIG_PATH_BASE)
|
||||
}
|
||||
|
||||
fn file_name(&self) -> String {
|
||||
CONFIG_FILE.to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
288
asusd/src/ctrl_anime/mod.rs
Normal file
@@ -0,0 +1,288 @@
|
||||
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() {
|
||||
unsafe { Node::Usb(usb.unwrap_unchecked()) }
|
||||
} else if hid.is_some() {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
432
asusd/src/ctrl_anime/trait_impls.rs
Normal file
@@ -0,0 +1,432 @@
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
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::{dbus_interface, CacheProperties, Connection, SignalContext};
|
||||
|
||||
use super::CtrlAnime;
|
||||
use crate::error::RogError;
|
||||
|
||||
pub const ANIME_ZBUS_NAME: &str = "Anime";
|
||||
pub const ANIME_ZBUS_PATH: &str = "/org/asuslinux/Anime";
|
||||
|
||||
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
|
||||
#[async_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.
|
||||
#[dbus_interface(name = "org.asuslinux.Daemon")]
|
||||
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<()> {
|
||||
let lock = self.0.lock().await;
|
||||
lock.thread_exit.store(true, Ordering::SeqCst);
|
||||
lock.write_data_buffer(input).map_err(|err| {
|
||||
warn!("ctrl_anime::run_animation:callback {}", err);
|
||||
err
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set base brightness level
|
||||
#[dbus_interface(property)]
|
||||
async fn brightness(&self) -> Brightness {
|
||||
let lock = self.0.lock().await;
|
||||
lock.config.display_brightness
|
||||
}
|
||||
|
||||
/// Set base brightness level
|
||||
#[dbus_interface(property)]
|
||||
async fn set_brightness(&self, brightness: Brightness) {
|
||||
let mut lock = self.0.lock().await;
|
||||
lock.node
|
||||
.write_bytes(&pkt_set_brightness(brightness))
|
||||
.map_err(|err| {
|
||||
warn!("ctrl_anime::set_brightness {}", err);
|
||||
})
|
||||
.ok();
|
||||
lock.node
|
||||
.write_bytes(&pkt_set_enable_display(brightness != Brightness::Off))
|
||||
.map_err(|err| {
|
||||
warn!("ctrl_anime::set_brightness {}", err);
|
||||
})
|
||||
.ok();
|
||||
|
||||
lock.config.display_enabled = brightness != Brightness::Off;
|
||||
lock.config.display_brightness = brightness;
|
||||
lock.config.write();
|
||||
}
|
||||
|
||||
#[dbus_interface(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
|
||||
#[dbus_interface(property)]
|
||||
async fn set_builtins_enabled(&self, enabled: bool) {
|
||||
let mut lock = self.0.lock().await;
|
||||
lock.node
|
||||
.set_builtins_enabled(enabled, lock.config.display_brightness)
|
||||
.map_err(|err| {
|
||||
warn!("ctrl_anime::set_builtins_enabled {}", err);
|
||||
})
|
||||
.ok();
|
||||
|
||||
if !enabled {
|
||||
let data = vec![255u8; lock.anime_type.data_length()];
|
||||
if let Ok(tmp) = AnimeDataBuffer::from_vec(lock.anime_type, data).map_err(|err| {
|
||||
warn!("ctrl_anime::set_builtins_enabled {}", err);
|
||||
}) {
|
||||
lock.node
|
||||
.write_bytes(tmp.data())
|
||||
.map_err(|err| {
|
||||
warn!("ctrl_anime::set_builtins_enabled {}", err);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
lock.config.builtin_anims_enabled = enabled;
|
||||
lock.config.write();
|
||||
if enabled {
|
||||
lock.thread_exit.store(true, Ordering::Release);
|
||||
}
|
||||
}
|
||||
|
||||
#[dbus_interface(property)]
|
||||
async fn builtin_animations(&self) -> Animations {
|
||||
let lock = self.0.lock().await;
|
||||
lock.config.builtin_anims
|
||||
}
|
||||
|
||||
/// Set which builtin animation is used for each stage
|
||||
#[dbus_interface(property)]
|
||||
async fn set_builtin_animations(&self, settings: Animations) {
|
||||
let mut lock = self.0.lock().await;
|
||||
lock.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();
|
||||
lock.node
|
||||
.write_bytes(&pkt_set_enable_powersave_anim(true))
|
||||
.map_err(|err| {
|
||||
warn!("ctrl_anime::run_animation:callback {}", err);
|
||||
})
|
||||
.ok();
|
||||
lock.config.display_enabled = true;
|
||||
lock.config.builtin_anims = settings;
|
||||
lock.config.write();
|
||||
}
|
||||
|
||||
#[dbus_interface(property)]
|
||||
async fn enable_display(&self) -> bool {
|
||||
let lock = self.0.lock().await;
|
||||
lock.config.display_enabled
|
||||
}
|
||||
|
||||
/// Set whether the AniMe is enabled at all
|
||||
#[dbus_interface(property)]
|
||||
async fn set_enable_display(&self, enabled: bool) {
|
||||
let mut lock = self.0.lock().await;
|
||||
lock.node
|
||||
.write_bytes(&pkt_set_enable_display(enabled))
|
||||
.map_err(|err| {
|
||||
warn!("ctrl_anime::run_animation:callback {}", err);
|
||||
})
|
||||
.ok();
|
||||
lock.config.display_enabled = enabled;
|
||||
lock.config.write();
|
||||
}
|
||||
|
||||
#[dbus_interface(property)]
|
||||
async fn off_when_unplugged(&self) -> bool {
|
||||
let lock = self.0.lock().await;
|
||||
lock.config.off_when_unplugged
|
||||
}
|
||||
|
||||
/// Set if to turn the AniMe Matrix off when external power is unplugged
|
||||
#[dbus_interface(property)]
|
||||
async fn set_off_when_unplugged(&self, enabled: bool) {
|
||||
let mut lock = self.0.lock().await;
|
||||
let manager = get_logind_manager().await;
|
||||
let pow = manager.on_external_power().await.unwrap_or_default();
|
||||
|
||||
lock.node
|
||||
.write_bytes(&pkt_set_enable_display(!pow && !enabled))
|
||||
.map_err(|err| {
|
||||
warn!("create_sys_event_tasks::off_when_lid_closed {}", err);
|
||||
})
|
||||
.ok();
|
||||
|
||||
lock.config.off_when_unplugged = enabled;
|
||||
lock.config.write();
|
||||
}
|
||||
|
||||
#[dbus_interface(property)]
|
||||
async fn off_when_suspended(&self) -> bool {
|
||||
let lock = self.0.lock().await;
|
||||
lock.config.off_when_suspended
|
||||
}
|
||||
|
||||
/// Set if to turn the AniMe Matrix off when the laptop is suspended
|
||||
#[dbus_interface(property)]
|
||||
async fn set_off_when_suspended(&self, enabled: bool) {
|
||||
let mut lock = self.0.lock().await;
|
||||
lock.config.off_when_suspended = enabled;
|
||||
lock.config.write();
|
||||
}
|
||||
|
||||
#[dbus_interface(property)]
|
||||
async fn off_when_lid_closed(&self) -> bool {
|
||||
let lock = self.0.lock().await;
|
||||
lock.config.off_when_lid_closed
|
||||
}
|
||||
|
||||
/// Set if to turn the AniMe Matrix off when the lid is closed
|
||||
#[dbus_interface(property)]
|
||||
async fn set_off_when_lid_closed(&self, enabled: bool) {
|
||||
let mut lock = self.0.lock().await;
|
||||
let manager = get_logind_manager().await;
|
||||
let lid = manager.lid_closed().await.unwrap_or_default();
|
||||
|
||||
lock.node
|
||||
.write_bytes(&pkt_set_enable_display(lid && !enabled))
|
||||
.map_err(|err| {
|
||||
warn!("create_sys_event_tasks::off_when_lid_closed {}", err);
|
||||
})
|
||||
.ok();
|
||||
|
||||
lock.config.off_when_lid_closed = enabled;
|
||||
lock.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 {
|
||||
let lock = self.0.lock().await;
|
||||
lock.thread_exit.store(true, Ordering::SeqCst);
|
||||
CtrlAnime::run_thread(self.0.clone(), lock.cache.system.clone(), false).await;
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the device state as stored by asusd
|
||||
// #[dbus_interface(property)]
|
||||
async fn device_state(&self) -> DeviceState {
|
||||
let lock = self.0.lock().await;
|
||||
DeviceState::from(&lock.config)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
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 lock = inner.lock().await;
|
||||
if lock.config.display_enabled {
|
||||
lock.node
|
||||
.write_bytes(&pkt_set_enable_powersave_anim(
|
||||
!(sleeping && lock.config.off_when_suspended),
|
||||
))
|
||||
.map_err(|err| {
|
||||
warn!("create_sys_event_tasks::off_when_suspended {}", err);
|
||||
})
|
||||
.ok();
|
||||
|
||||
lock.thread_exit.store(true, Ordering::Release); // ensure clean slate
|
||||
lock.node
|
||||
.write_bytes(&pkt_set_enable_display(
|
||||
!(sleeping && lock.config.off_when_suspended),
|
||||
))
|
||||
.map_err(|err| {
|
||||
warn!("create_sys_event_tasks::off_when_suspended {}", err);
|
||||
})
|
||||
.ok();
|
||||
|
||||
if !sleeping && !lock.config.builtin_anims_enabled {
|
||||
CtrlAnime::run_thread(inner.clone(), lock.cache.wake.clone(), true)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
move |shutting_down| {
|
||||
// on_shutdown
|
||||
let inner = inner2.clone();
|
||||
async move {
|
||||
let lock = inner.lock().await;
|
||||
if lock.config.display_enabled && !lock.config.builtin_anims_enabled {
|
||||
if shutting_down {
|
||||
CtrlAnime::run_thread(inner.clone(), lock.cache.shutdown.clone(), true)
|
||||
.await;
|
||||
} else {
|
||||
CtrlAnime::run_thread(inner.clone(), lock.cache.boot.clone(), true)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
move |lid_closed| {
|
||||
let inner = inner3.clone();
|
||||
// on lid change
|
||||
async move {
|
||||
let lock = inner.lock().await;
|
||||
if lock.config.off_when_lid_closed {
|
||||
if lock.config.builtin_anims_enabled {
|
||||
lock.node
|
||||
.write_bytes(&pkt_set_enable_powersave_anim(!lid_closed))
|
||||
.map_err(|err| {
|
||||
warn!("create_sys_event_tasks::off_when_suspended {}", err);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
lock.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 lock = inner.lock().await;
|
||||
if lock.config.off_when_unplugged {
|
||||
if lock.config.builtin_anims_enabled {
|
||||
lock.node
|
||||
.write_bytes(&pkt_set_enable_powersave_anim(power_plugged))
|
||||
.map_err(|err| {
|
||||
warn!("create_sys_event_tasks::off_when_suspended {}", err);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
lock.node
|
||||
.write_bytes(&pkt_set_enable_display(power_plugged))
|
||||
.map_err(|err| {
|
||||
warn!("create_sys_event_tasks::off_when_unplugged {}", err);
|
||||
})
|
||||
.ok();
|
||||
} else {
|
||||
lock.node
|
||||
.write_bytes(&pkt_set_brightness(lock.config.brightness_on_battery))
|
||||
.map_err(|err| {
|
||||
warn!("create_sys_event_tasks::off_when_unplugged {}", err);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
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(())
|
||||
}
|
||||
}
|
||||
388
asusd/src/ctrl_aura/config.rs
Normal file
@@ -0,0 +1,388 @@
|
||||
use std::collections::{BTreeMap, HashSet};
|
||||
|
||||
use config_traits::{StdConfig, StdConfigLoad};
|
||||
use log::{debug, warn};
|
||||
use rog_aura::aura_detection::{LaptopLedData, ASUS_KEYBOARD_DEVICES};
|
||||
use rog_aura::power::AuraPower;
|
||||
use rog_aura::usb::{AuraDevRog1, AuraDevTuf, AuraDevice, AuraPowerDev};
|
||||
use rog_aura::{AuraEffect, AuraModeNum, AuraZone, Direction, LedBrightness, Speed, GRADIENT};
|
||||
use rog_platform::hid_raw::HidRaw;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
const CONFIG_FILE: &str = "aura.ron";
|
||||
|
||||
/// Enable/disable LED control in various states such as
|
||||
/// when the device is awake, suspended, shutting down or
|
||||
/// booting.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum AuraPowerConfig {
|
||||
AuraDevTuf(HashSet<AuraDevTuf>),
|
||||
AuraDevRog1(HashSet<AuraDevRog1>),
|
||||
AuraDevRog2(AuraPower),
|
||||
}
|
||||
|
||||
impl AuraPowerConfig {
|
||||
/// Invalid for TUF laptops
|
||||
pub fn to_bytes(control: &Self) -> [u8; 4] {
|
||||
match control {
|
||||
AuraPowerConfig::AuraDevTuf(_) => [0, 0, 0, 0],
|
||||
AuraPowerConfig::AuraDevRog1(c) => {
|
||||
let c: Vec<AuraDevRog1> = c.iter().copied().collect();
|
||||
AuraDevRog1::to_bytes(&c)
|
||||
}
|
||||
AuraPowerConfig::AuraDevRog2(c) => c.to_bytes(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_tuf_bool_array(control: &Self) -> Option<[bool; 5]> {
|
||||
if let Self::AuraDevTuf(c) = control {
|
||||
return Some([
|
||||
true,
|
||||
c.contains(&AuraDevTuf::Boot),
|
||||
c.contains(&AuraDevTuf::Awake),
|
||||
c.contains(&AuraDevTuf::Sleep),
|
||||
c.contains(&AuraDevTuf::Keyboard),
|
||||
]);
|
||||
}
|
||||
|
||||
if let Self::AuraDevRog1(c) = control {
|
||||
return Some([
|
||||
true,
|
||||
c.contains(&AuraDevRog1::Boot),
|
||||
c.contains(&AuraDevRog1::Awake),
|
||||
c.contains(&AuraDevRog1::Sleep),
|
||||
c.contains(&AuraDevRog1::Keyboard),
|
||||
]);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn set_tuf(&mut self, power: AuraDevTuf, on: bool) {
|
||||
if let Self::AuraDevTuf(p) = self {
|
||||
if on {
|
||||
p.insert(power);
|
||||
} else {
|
||||
p.remove(&power);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_0x1866(&mut self, power: AuraDevRog1, on: bool) {
|
||||
if let Self::AuraDevRog1(p) = self {
|
||||
if on {
|
||||
p.insert(power);
|
||||
} else {
|
||||
p.remove(&power);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_0x19b6(&mut self, power: AuraPower) {
|
||||
if let Self::AuraDevRog2(p) = self {
|
||||
*p = power;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&AuraPowerConfig> for AuraPowerDev {
|
||||
fn from(config: &AuraPowerConfig) -> Self {
|
||||
match config {
|
||||
AuraPowerConfig::AuraDevTuf(d) => AuraPowerDev {
|
||||
tuf: d.iter().copied().collect(),
|
||||
..Default::default()
|
||||
},
|
||||
AuraPowerConfig::AuraDevRog1(d) => AuraPowerDev {
|
||||
old_rog: d.iter().copied().collect(),
|
||||
..Default::default()
|
||||
},
|
||||
AuraPowerConfig::AuraDevRog2(d) => AuraPowerDev {
|
||||
rog: d.clone(),
|
||||
..Default::default()
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
// #[serde(default)]
|
||||
pub struct AuraConfig {
|
||||
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: AuraPowerConfig,
|
||||
}
|
||||
|
||||
impl StdConfig for AuraConfig {
|
||||
/// Detect the keyboard type and load from default DB if data available
|
||||
fn new() -> Self {
|
||||
warn!("AuraConfig: creating new config");
|
||||
let mut prod_id = AuraDevice::Unknown;
|
||||
for prod in ASUS_KEYBOARD_DEVICES {
|
||||
if HidRaw::new(prod.into()).is_ok() {
|
||||
prod_id = prod;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Self::from_default_support(prod_id, &LaptopLedData::get_data())
|
||||
}
|
||||
|
||||
fn config_dir() -> std::path::PathBuf {
|
||||
std::path::PathBuf::from(crate::CONFIG_PATH_BASE)
|
||||
}
|
||||
|
||||
fn file_name(&self) -> String {
|
||||
CONFIG_FILE.to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
impl StdConfigLoad for AuraConfig {}
|
||||
|
||||
impl AuraConfig {
|
||||
pub fn from_default_support(prod_id: AuraDevice, support_data: &LaptopLedData) -> Self {
|
||||
// create a default config here
|
||||
let enabled = if prod_id.is_new_style() {
|
||||
AuraPowerConfig::AuraDevRog2(AuraPower::new_all_on())
|
||||
} else if prod_id.is_tuf_style() {
|
||||
AuraPowerConfig::AuraDevTuf(HashSet::from([
|
||||
AuraDevTuf::Awake,
|
||||
AuraDevTuf::Boot,
|
||||
AuraDevTuf::Sleep,
|
||||
AuraDevTuf::Keyboard,
|
||||
]))
|
||||
} else {
|
||||
AuraPowerConfig::AuraDevRog1(HashSet::from([
|
||||
AuraDevRog1::Awake,
|
||||
AuraDevRog1::Boot,
|
||||
AuraDevRog1::Sleep,
|
||||
AuraDevRog1::Keyboard,
|
||||
AuraDevRog1::Lightbar,
|
||||
]))
|
||||
};
|
||||
let mut config = AuraConfig {
|
||||
brightness: LedBrightness::Med,
|
||||
current_mode: AuraModeNum::Static,
|
||||
builtins: BTreeMap::new(),
|
||||
multizone: None,
|
||||
multizone_on: false,
|
||||
enabled,
|
||||
};
|
||||
|
||||
for n in &support_data.basic_modes {
|
||||
debug!("AuraConfig: 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::aura_detection::LaptopLedData;
|
||||
use rog_aura::usb::AuraDevice;
|
||||
use rog_aura::{AuraEffect, AuraModeNum, AuraZone, Colour};
|
||||
|
||||
use super::AuraConfig;
|
||||
|
||||
#[test]
|
||||
fn set_multizone_4key_config() {
|
||||
let mut config =
|
||||
AuraConfig::from_default_support(AuraDevice::X19b6, &LaptopLedData::default());
|
||||
|
||||
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::from_default_support(AuraDevice::X19b6, &LaptopLedData::default());
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
363
asusd/src/ctrl_aura/controller.rs
Normal file
@@ -0,0 +1,363 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use config_traits::{StdConfig, StdConfigLoad};
|
||||
use dmi_id::DMIID;
|
||||
use log::{info, warn};
|
||||
use rog_aura::advanced::{LedUsbPackets, UsbPackets};
|
||||
use rog_aura::aura_detection::{LaptopLedData, ASUS_KEYBOARD_DEVICES};
|
||||
use rog_aura::usb::{AuraDevice, LED_APPLY, LED_SET};
|
||||
use rog_aura::{AuraEffect, Direction, LedBrightness, Speed, GRADIENT, LED_MSG_LEN};
|
||||
use rog_platform::hid_raw::HidRaw;
|
||||
use rog_platform::keyboard_led::KeyboardLed;
|
||||
|
||||
use super::config::{AuraConfig, AuraPowerConfig};
|
||||
use crate::error::RogError;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum LEDNode {
|
||||
KbdLed(KeyboardLed),
|
||||
Rog(HidRaw),
|
||||
None,
|
||||
}
|
||||
|
||||
pub struct CtrlKbdLed {
|
||||
// TODO: config stores the keyboard type as an AuraPower, use or update this
|
||||
pub led_prod: AuraDevice,
|
||||
pub led_node: LEDNode,
|
||||
pub sysfs_node: KeyboardLed,
|
||||
pub supported_data: LaptopLedData,
|
||||
pub per_key_mode_active: bool,
|
||||
pub config: AuraConfig,
|
||||
}
|
||||
|
||||
impl CtrlKbdLed {
|
||||
pub fn new(supported_basic_modes: LaptopLedData) -> Result<Self, RogError> {
|
||||
let mut led_prod = AuraDevice::Unknown;
|
||||
let mut usb_node = None;
|
||||
for prod in ASUS_KEYBOARD_DEVICES {
|
||||
match HidRaw::new(prod.into()) {
|
||||
Ok(node) => {
|
||||
led_prod = prod;
|
||||
usb_node = Some(node);
|
||||
info!(
|
||||
"Looked for keyboard controller 0x{}: Found",
|
||||
<&str>::from(prod)
|
||||
);
|
||||
break;
|
||||
}
|
||||
Err(err) => info!(
|
||||
"Looked for keyboard controller 0x{}: {err}",
|
||||
<&str>::from(prod)
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
let rgb_led = KeyboardLed::new()?;
|
||||
|
||||
if usb_node.is_none() && !rgb_led.has_kbd_rgb_mode() {
|
||||
let dmi = DMIID::new().unwrap_or_default();
|
||||
if dmi.dmi_family.contains("TUF") {
|
||||
warn!(
|
||||
"kbd_rgb_mode was not found in the /sys/. You require a minimum 6.1 kernel \
|
||||
and a supported TUF laptop"
|
||||
);
|
||||
}
|
||||
return Err(RogError::NoAuraKeyboard);
|
||||
}
|
||||
|
||||
let led_node = if let Some(rog) = usb_node {
|
||||
info!("Found ROG USB keyboard");
|
||||
LEDNode::Rog(rog)
|
||||
} else if rgb_led.has_kbd_rgb_mode() {
|
||||
info!("Found TUF keyboard");
|
||||
LEDNode::KbdLed(rgb_led.clone())
|
||||
} else {
|
||||
LEDNode::None
|
||||
};
|
||||
|
||||
// New loads data fromt he DB also
|
||||
let mut config_init = AuraConfig::new();
|
||||
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();
|
||||
// only reuse a zone mode if the mode is supported
|
||||
for mode in loaded {
|
||||
if supported_basic_modes.basic_modes.contains(&mode.mode) {
|
||||
new_set.push(mode.clone());
|
||||
}
|
||||
}
|
||||
*mode.1 = new_set;
|
||||
}
|
||||
}
|
||||
*multizone_loaded = multizone_init;
|
||||
}
|
||||
|
||||
let ctrl = CtrlKbdLed {
|
||||
led_prod,
|
||||
led_node, // on TUF this is the same as rgb_led / kd_brightness
|
||||
sysfs_node: rgb_led, // If was none then we already returned above
|
||||
supported_data: supported_basic_modes,
|
||||
per_key_mode_active: false,
|
||||
config: config_loaded,
|
||||
};
|
||||
Ok(ctrl)
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
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 = AuraPowerConfig::to_bytes(&self.config.enabled);
|
||||
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::{LaptopLedData, PowerZones};
|
||||
use rog_aura::usb::AuraDevice;
|
||||
use rog_aura::{AuraModeNum, AuraZone};
|
||||
use rog_platform::keyboard_led::KeyboardLed;
|
||||
|
||||
use super::CtrlKbdLed;
|
||||
use crate::ctrl_aura::config::AuraConfig;
|
||||
use crate::ctrl_aura::controller::LEDNode;
|
||||
|
||||
#[test]
|
||||
fn create_multizone_if_no_config() {
|
||||
// Checking to ensure set_mode errors when unsupported modes are tried
|
||||
let config = AuraConfig::from_default_support(AuraDevice::X19b6, &LaptopLedData::default());
|
||||
let supported_basic_modes = LaptopLedData {
|
||||
board_name: String::new(),
|
||||
layout_name: "ga401".to_owned(),
|
||||
basic_modes: vec![AuraModeNum::Static],
|
||||
basic_zones: vec![],
|
||||
advanced_type: rog_aura::AdvancedAuraType::None,
|
||||
power_zones: vec![PowerZones::Keyboard, PowerZones::RearGlow],
|
||||
};
|
||||
let mut controller = CtrlKbdLed {
|
||||
led_prod: AuraDevice::X19b6,
|
||||
led_node: LEDNode::None,
|
||||
sysfs_node: KeyboardLed::default(),
|
||||
supported_data: supported_basic_modes,
|
||||
per_key_mode_active: false,
|
||||
config,
|
||||
};
|
||||
|
||||
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]
|
||||
fn next_mode_create_multizone_if_no_config() {
|
||||
// Checking to ensure set_mode errors when unsupported modes are tried
|
||||
let config = AuraConfig::from_default_support(AuraDevice::X19b6, &LaptopLedData::default());
|
||||
let supported_basic_modes = LaptopLedData {
|
||||
board_name: String::new(),
|
||||
layout_name: "ga401".to_owned(),
|
||||
basic_modes: vec![AuraModeNum::Static],
|
||||
basic_zones: vec![AuraZone::Key1, AuraZone::Key2],
|
||||
advanced_type: rog_aura::AdvancedAuraType::None,
|
||||
power_zones: vec![PowerZones::Keyboard, PowerZones::RearGlow],
|
||||
};
|
||||
let mut controller = CtrlKbdLed {
|
||||
led_prod: AuraDevice::X19b6,
|
||||
led_node: LEDNode::None,
|
||||
sysfs_node: KeyboardLed::default(),
|
||||
supported_data: supported_basic_modes,
|
||||
per_key_mode_active: false,
|
||||
config,
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
4
asusd/src/ctrl_aura/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
pub mod config;
|
||||
pub mod controller;
|
||||
/// Implements `CtrlTask`, `Reloadable`, `ZbusRun`
|
||||
pub mod trait_impls;
|
||||
291
asusd/src/ctrl_aura/trait_impls.rs
Normal file
@@ -0,0 +1,291 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use config_traits::StdConfig;
|
||||
use log::{debug, error, info, warn};
|
||||
use rog_aura::advanced::UsbPackets;
|
||||
use rog_aura::aura_detection::PowerZones;
|
||||
use rog_aura::usb::{AuraDevice, AuraPowerDev};
|
||||
use rog_aura::{AuraEffect, AuraModeNum, AuraZone, LedBrightness};
|
||||
use zbus::export::futures_util::lock::{Mutex, MutexGuard};
|
||||
use zbus::export::futures_util::StreamExt;
|
||||
use zbus::fdo::Error as ZbErr;
|
||||
use zbus::{dbus_interface, Connection, 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/Aura";
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CtrlAuraZbus(pub Arc<Mutex<CtrlKbdLed>>);
|
||||
|
||||
impl CtrlAuraZbus {
|
||||
fn update_config(lock: &mut CtrlKbdLed) -> Result<(), RogError> {
|
||||
let bright = lock.sysfs_node.get_brightness()?;
|
||||
lock.config.read();
|
||||
lock.config.brightness = bright.into();
|
||||
lock.config.write();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl crate::ZbusRun for CtrlAuraZbus {
|
||||
async fn add_to_server(self, server: &mut Connection) {
|
||||
Self::add_to_server_helper(self, AURA_ZBUS_PATH, server).await;
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 CtrlAuraZbus {
|
||||
/// Return the device type for this Aura keyboard
|
||||
#[dbus_interface(property)]
|
||||
async fn device_type(&self) -> AuraDevice {
|
||||
let ctrl = self.0.lock().await;
|
||||
ctrl.led_prod
|
||||
}
|
||||
|
||||
/// Return the current LED brightness
|
||||
#[dbus_interface(property)]
|
||||
async fn brightness(&self) -> Result<LedBrightness, ZbErr> {
|
||||
let ctrl = self.0.lock().await;
|
||||
Ok(ctrl.sysfs_node.get_brightness().map(|n| n.into())?)
|
||||
}
|
||||
|
||||
/// Set the keyboard brightness level (0-3)
|
||||
#[dbus_interface(property)]
|
||||
async fn set_brightness(&mut self, brightness: LedBrightness) -> Result<(), ZbErr> {
|
||||
let ctrl = self.0.lock().await;
|
||||
Ok(ctrl.sysfs_node.set_brightness(brightness.into())?)
|
||||
}
|
||||
|
||||
/// Total levels of brightness available
|
||||
#[dbus_interface(property)]
|
||||
async fn supported_brightness(&self) -> Vec<LedBrightness> {
|
||||
vec![
|
||||
LedBrightness::Off,
|
||||
LedBrightness::Low,
|
||||
LedBrightness::Med,
|
||||
LedBrightness::High,
|
||||
]
|
||||
}
|
||||
|
||||
/// The total available modes
|
||||
#[dbus_interface(property)]
|
||||
async fn supported_basic_modes(&self) -> Result<Vec<AuraModeNum>, ZbErr> {
|
||||
let ctrl = self.0.lock().await;
|
||||
Ok(ctrl.config.builtins.keys().cloned().collect())
|
||||
}
|
||||
|
||||
#[dbus_interface(property)]
|
||||
async fn supported_basic_zones(&self) -> Result<Vec<AuraZone>, ZbErr> {
|
||||
let ctrl = self.0.lock().await;
|
||||
Ok(ctrl.supported_data.basic_zones.clone())
|
||||
}
|
||||
|
||||
#[dbus_interface(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
|
||||
#[dbus_interface(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.
|
||||
#[dbus_interface(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.sysfs_node
|
||||
.set_brightness(ctrl.config.brightness.into())?;
|
||||
ctrl.config.write();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The current mode data
|
||||
#[dbus_interface(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.
|
||||
#[dbus_interface(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.sysfs_node
|
||||
.set_brightness(ctrl.config.brightness.into())?;
|
||||
ctrl.config.set_builtin(effect);
|
||||
ctrl.config.write();
|
||||
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?)
|
||||
#[dbus_interface(property)]
|
||||
async fn led_power(&self) -> AuraPowerDev {
|
||||
let ctrl = self.0.lock().await;
|
||||
AuraPowerDev::from(&ctrl.config.enabled)
|
||||
}
|
||||
|
||||
/// 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.
|
||||
#[dbus_interface(property)]
|
||||
async fn set_led_power(&mut self, options: (AuraPowerDev, bool)) -> Result<(), ZbErr> {
|
||||
let enabled = options.1;
|
||||
let options = options.0;
|
||||
let mut ctrl = self.0.lock().await;
|
||||
for p in options.tuf {
|
||||
ctrl.config.enabled.set_tuf(p, enabled);
|
||||
}
|
||||
for p in options.old_rog {
|
||||
ctrl.config.enabled.set_0x1866(p, enabled);
|
||||
}
|
||||
ctrl.config.enabled.set_0x19b6(options.rog);
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl CtrlTask for CtrlAuraZbus {
|
||||
fn zbus_path() -> &'static str {
|
||||
AURA_ZBUS_PATH
|
||||
}
|
||||
|
||||
async fn create_tasks(&self, _: SignalContext<'static>) -> Result<(), RogError> {
|
||||
let load_save = |start: bool, mut lock: MutexGuard<'_, CtrlKbdLed>| {
|
||||
// If waking up
|
||||
if !start {
|
||||
info!("CtrlKbdLedTask reloading brightness and modes");
|
||||
lock.sysfs_node
|
||||
.set_brightness(lock.config.brightness.into())
|
||||
.map_err(|e| error!("CtrlKbdLedTask: {e}"))
|
||||
.ok();
|
||||
lock.write_current_config_mode()
|
||||
.map_err(|e| error!("CtrlKbdLedTask: {e}"))
|
||||
.ok();
|
||||
} else if start {
|
||||
Self::update_config(&mut lock)
|
||||
.map_err(|e| error!("CtrlKbdLedTask: {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);
|
||||
}
|
||||
},
|
||||
move |_shutting_down| {
|
||||
let inner3 = inner3.clone();
|
||||
async move {
|
||||
let lock = inner3.lock().await;
|
||||
load_save(false, lock);
|
||||
}
|
||||
},
|
||||
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.sysfs_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);
|
||||
}
|
||||
})
|
||||
.await;
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl crate::Reloadable for CtrlAuraZbus {
|
||||
async fn reload(&mut self) -> Result<(), RogError> {
|
||||
let mut ctrl = self.0.lock().await;
|
||||
debug!("CtrlKbdLedZbus: reloading keyboard mode");
|
||||
ctrl.write_current_config_mode()?;
|
||||
debug!("CtrlKbdLedZbus: reloading power states");
|
||||
ctrl.set_power_states().map_err(|err| warn!("{err}")).ok();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
311
asusd/src/ctrl_fancurves.rs
Normal file
@@ -0,0 +1,311 @@
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use config_traits::{StdConfig, StdConfigLoad};
|
||||
use futures_lite::StreamExt;
|
||||
use log::{debug, error, info, warn};
|
||||
use rog_platform::platform::{PlatformPolicy, RogPlatform};
|
||||
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::{dbus_interface, Connection, SignalContext};
|
||||
|
||||
use crate::error::RogError;
|
||||
use crate::{CtrlTask, CONFIG_PATH_BASE};
|
||||
|
||||
const MOD_NAME: &str = "FanCurveZbus";
|
||||
pub const FAN_CURVE_ZBUS_NAME: &str = "FanCurves";
|
||||
pub const FAN_CURVE_ZBUS_PATH: &str = "/org/asuslinux/FanCurves";
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Default)]
|
||||
pub struct FanCurveConfig {
|
||||
pub balanced: Vec<CurveData>,
|
||||
pub performance: Vec<CurveData>,
|
||||
pub quiet: Vec<CurveData>,
|
||||
#[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>>,
|
||||
fan_curves: Arc<Mutex<FanCurveProfiles>>,
|
||||
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!("{MOD_NAME}: Device has profile control available");
|
||||
find_fan_curve_node()?;
|
||||
info!("{MOD_NAME}: Device has fan curves available");
|
||||
let mut config = FanCurveConfig::new();
|
||||
let mut fan_curves = FanCurveProfiles::default();
|
||||
|
||||
// Only do defaults if the config doesn't already exist
|
||||
if !config.file_path().exists() {
|
||||
info!("{MOD_NAME}: Fetching default fan curves");
|
||||
|
||||
for this in [
|
||||
PlatformPolicy::Balanced,
|
||||
PlatformPolicy::Performance,
|
||||
PlatformPolicy::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.
|
||||
let next = PlatformPolicy::get_next_profile(this);
|
||||
platform.set_throttle_thermal_policy(next.into())?;
|
||||
|
||||
let active = platform
|
||||
.get_throttle_thermal_policy()
|
||||
.map_or(PlatformPolicy::Balanced, |t| t.into());
|
||||
|
||||
info!("{MOD_NAME}: {active:?}:");
|
||||
for curve in fan_curves.get_fan_curves_for(active) {
|
||||
info!("{}", String::from(curve));
|
||||
}
|
||||
}
|
||||
config.write();
|
||||
} else {
|
||||
info!("{MOD_NAME}: Fan curves previously stored, loading...");
|
||||
config = config.load();
|
||||
fan_curves.balanced = config.balanced.clone();
|
||||
fan_curves.performance = config.performance.clone();
|
||||
fan_curves.quiet = config.quiet.clone();
|
||||
}
|
||||
|
||||
return Ok(Self {
|
||||
config: Arc::new(Mutex::new(config)),
|
||||
fan_curves: Arc::new(Mutex::new(fan_curves)),
|
||||
platform,
|
||||
});
|
||||
}
|
||||
|
||||
Err(ProfileError::NotSupported.into())
|
||||
}
|
||||
|
||||
pub async fn update_profiles_from_config(&self) {
|
||||
self.fan_curves.lock().await.balanced = self.config.lock().await.balanced.clone();
|
||||
self.fan_curves.lock().await.performance = self.config.lock().await.performance.clone();
|
||||
self.fan_curves.lock().await.quiet = self.config.lock().await.quiet.clone();
|
||||
}
|
||||
|
||||
/// Because this locks both config and fan_curves, it means nothing else can
|
||||
/// hold a lock across this function call. Stupid choice to do this and
|
||||
/// needs to be fixed.
|
||||
pub async fn update_config_from_profiles(&self) {
|
||||
self.config.lock().await.balanced = self.fan_curves.lock().await.balanced.clone();
|
||||
self.config.lock().await.performance = self.fan_curves.lock().await.performance.clone();
|
||||
self.config.lock().await.quiet = self.fan_curves.lock().await.quiet.clone();
|
||||
}
|
||||
}
|
||||
|
||||
#[dbus_interface(name = "org.asuslinux.Daemon")]
|
||||
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: PlatformPolicy,
|
||||
enabled: bool,
|
||||
) -> zbus::fdo::Result<()> {
|
||||
self.fan_curves
|
||||
.lock()
|
||||
.await
|
||||
.set_profile_curves_enabled(profile, enabled);
|
||||
self.fan_curves
|
||||
.lock()
|
||||
.await
|
||||
.write_profile_curve_to_platform(profile, &mut find_fan_curve_node()?)?;
|
||||
self.update_config_from_profiles().await;
|
||||
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: PlatformPolicy,
|
||||
fan: FanCurvePU,
|
||||
enabled: bool,
|
||||
) -> zbus::fdo::Result<()> {
|
||||
self.fan_curves
|
||||
.lock()
|
||||
.await
|
||||
.set_profile_fan_curve_enabled(profile, fan, enabled);
|
||||
self.fan_curves
|
||||
.lock()
|
||||
.await
|
||||
.write_profile_curve_to_platform(profile, &mut find_fan_curve_node()?)?;
|
||||
self.update_config_from_profiles().await;
|
||||
self.config.lock().await.write();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the fan-curve data for the currently active PlatformPolicy
|
||||
async fn fan_curve_data(
|
||||
&mut self,
|
||||
profile: PlatformPolicy,
|
||||
) -> zbus::fdo::Result<Vec<CurveData>> {
|
||||
let curve = self
|
||||
.fan_curves
|
||||
.lock()
|
||||
.await
|
||||
.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: PlatformPolicy,
|
||||
curve: CurveData,
|
||||
) -> zbus::fdo::Result<()> {
|
||||
self.fan_curves
|
||||
.lock()
|
||||
.await
|
||||
.save_fan_curve(curve, profile)?;
|
||||
self.fan_curves
|
||||
.lock()
|
||||
.await
|
||||
.write_profile_curve_to_platform(profile, &mut find_fan_curve_node()?)?;
|
||||
self.update_config_from_profiles().await;
|
||||
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 set_active_curve_to_defaults(&mut self) -> zbus::fdo::Result<()> {
|
||||
let active = self.platform.get_throttle_thermal_policy()?;
|
||||
self.fan_curves
|
||||
.lock()
|
||||
.await
|
||||
.set_active_curve_to_defaults(active.into(), &mut find_fan_curve_node()?)?;
|
||||
self.update_config_from_profiles().await;
|
||||
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: PlatformPolicy) -> zbus::fdo::Result<()> {
|
||||
let active = self
|
||||
.platform
|
||||
.get_throttle_thermal_policy()
|
||||
.unwrap_or(PlatformPolicy::Balanced.into());
|
||||
|
||||
self.platform.set_throttle_thermal_policy(profile.into())?;
|
||||
self.fan_curves
|
||||
.lock()
|
||||
.await
|
||||
.set_active_curve_to_defaults(active.into(), &mut find_fan_curve_node()?)?;
|
||||
self.platform.set_throttle_thermal_policy(active)?;
|
||||
|
||||
self.update_config_from_profiles().await;
|
||||
self.config.lock().await.write();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
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.fan_curves.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!("{MOD_NAME}: get_throttle_thermal_policy error: {e}");
|
||||
}) {
|
||||
if profile != config.lock().await.current {
|
||||
fan_curves
|
||||
.lock()
|
||||
.await
|
||||
.write_profile_curve_to_platform(
|
||||
profile.into(),
|
||||
&mut find_fan_curve_node().unwrap(),
|
||||
)
|
||||
.map_err(|e| {
|
||||
warn!("{MOD_NAME}: write_profile_curve_to_platform, {}", e)
|
||||
})
|
||||
.ok();
|
||||
config.lock().await.current = profile;
|
||||
}
|
||||
}
|
||||
}
|
||||
dbg!("STREAM ENDED");
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
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();
|
||||
// if let Ok(mut device) = find_fan_curve_node() {
|
||||
// // There is a possibility that the curve was default zeroed, so this call
|
||||
// // initialises the data from system read and we need to save it
|
||||
// // after
|
||||
// self.fan_curves
|
||||
// .lock()
|
||||
// .await
|
||||
// .write_profile_curve_to_platform(active, &mut device)?;
|
||||
// }
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
709
asusd/src/ctrl_platform.rs
Normal file
@@ -0,0 +1,709 @@
|
||||
use std::process::Command;
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use config_traits::StdConfig;
|
||||
use log::{debug, error, info, warn};
|
||||
use rog_platform::cpu::{CPUControl, CPUGovernor};
|
||||
use rog_platform::platform::{GpuMode, PlatformPolicy, Properties, RogPlatform};
|
||||
use rog_platform::power::AsusPower;
|
||||
use zbus::export::futures_util::lock::Mutex;
|
||||
use zbus::fdo::Error as FdoErr;
|
||||
use zbus::{dbus_interface, Connection, ObjectServer, SignalContext};
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::ctrl_anime::trait_impls::{CtrlAnimeZbus, ANIME_ZBUS_NAME, ANIME_ZBUS_PATH};
|
||||
use crate::ctrl_aura::trait_impls::{CtrlAuraZbus, AURA_ZBUS_NAME, AURA_ZBUS_PATH};
|
||||
use crate::ctrl_fancurves::{CtrlFanCurveZbus, FAN_CURVE_ZBUS_NAME, FAN_CURVE_ZBUS_PATH};
|
||||
use crate::error::RogError;
|
||||
use crate::{task_watch_item, task_watch_item_notify, CtrlTask};
|
||||
|
||||
const ZBUS_PATH: &str = "/org/asuslinux/Platform";
|
||||
|
||||
macro_rules! platform_get_value {
|
||||
($self:ident, $property:tt, $prop_name:literal) => {
|
||||
concat_idents::concat_idents!(has = has_, $property {
|
||||
if $self.platform.has() {
|
||||
concat_idents::concat_idents!(get = get_, $property {
|
||||
$self.platform
|
||||
.get()
|
||||
.map_err(|err| {
|
||||
warn!("RogPlatform: {}: {}", $prop_name, err);
|
||||
FdoErr::Failed(format!("RogPlatform: {}: {}", $prop_name, err))
|
||||
})
|
||||
})
|
||||
} else {
|
||||
info!("RogPlatform: {} not supported", $prop_name);
|
||||
return Err(FdoErr::NotSupported(format!("RogPlatform: {} not supported", $prop_name)));
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! platform_get_value_if_some {
|
||||
($self:ident, $property:tt, $prop_name:literal, $default:expr) => {
|
||||
concat_idents::concat_idents!(has = has_, $property {
|
||||
if $self.platform.has() {
|
||||
let lock = $self.config.lock().await;
|
||||
Ok(lock.$property.unwrap_or($default))
|
||||
} else {
|
||||
info!("RogPlatform: {} not supported", $prop_name);
|
||||
return Err(FdoErr::NotSupported(format!("RogPlatform: {} not supported", $prop_name)));
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! platform_set_bool {
|
||||
($self:ident, $property:tt, $prop_name:literal, $new_value:expr) => {
|
||||
concat_idents::concat_idents!(has = has_, $property {
|
||||
if $self.platform.has() {
|
||||
concat_idents::concat_idents!(set = set_, $property {
|
||||
$self.platform.set($new_value).map_err(|err| {
|
||||
error!("RogPlatform: {} {err}", $prop_name);
|
||||
FdoErr::NotSupported(format!("RogPlatform: {} {err}", $prop_name))
|
||||
})?;
|
||||
});
|
||||
let mut lock = $self.config.lock().await;
|
||||
lock.$property = $new_value;
|
||||
lock.write();
|
||||
Ok(())
|
||||
} else {
|
||||
info!("RogPlatform: {} not supported", $prop_name);
|
||||
Err(FdoErr::NotSupported(format!("RogPlatform: {} not supported", $prop_name)))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Intended only for setting platform object values where the value isn't
|
||||
/// retained across boots
|
||||
macro_rules! platform_set_with_min_max {
|
||||
($self:ident, $property:tt, $prop_name:literal, $new_value:expr, $min_value:expr, $max_value:expr) => {
|
||||
if !($min_value..=$max_value).contains(&$new_value) {
|
||||
Err(FdoErr::Failed(
|
||||
format!("RogPlatform: {} value not in range {}=..={}", $prop_name, $min_value, $max_value)
|
||||
))
|
||||
} else {
|
||||
concat_idents::concat_idents!(has = has_, $property {
|
||||
if $self.platform.has() {
|
||||
concat_idents::concat_idents!(set = set_, $property {
|
||||
$self.platform.set($new_value).map_err(|err| {
|
||||
error!("RogPlatform: {} {err}", $prop_name);
|
||||
FdoErr::NotSupported(format!("RogPlatform: {} {err}", $prop_name))
|
||||
})?;
|
||||
});
|
||||
let mut lock = $self.config.lock().await;
|
||||
lock.$property = Some($new_value);
|
||||
lock.write();
|
||||
} else {
|
||||
error!("RogPlatform: {} not supported", $prop_name);
|
||||
return Err(FdoErr::NotSupported(format!("RogPlatform: {} not supported", $prop_name)));
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CtrlPlatform {
|
||||
power: AsusPower,
|
||||
platform: RogPlatform,
|
||||
cpu_control: Option<CPUControl>,
|
||||
config: Arc<Mutex<Config>>,
|
||||
}
|
||||
|
||||
impl CtrlPlatform {
|
||||
pub fn new(config: Arc<Mutex<Config>>) -> Result<Self, RogError> {
|
||||
let platform = RogPlatform::new()?;
|
||||
let power = AsusPower::new()?;
|
||||
|
||||
if !platform.has_gpu_mux_mode() {
|
||||
info!("G-Sync Switchable Graphics or GPU MUX not detected");
|
||||
info!("Standard graphics switching will still work.");
|
||||
}
|
||||
|
||||
Ok(CtrlPlatform {
|
||||
power,
|
||||
platform,
|
||||
config,
|
||||
cpu_control: CPUControl::new()
|
||||
.map_err(|e| error!("Couldn't get CPU control sysfs: {e}"))
|
||||
.ok(),
|
||||
})
|
||||
}
|
||||
|
||||
fn set_gfx_mode(&self, mode: GpuMode) -> Result<(), RogError> {
|
||||
self.platform.set_gpu_mux_mode(mode.to_mux_attr())?;
|
||||
// self.update_initramfs(enable)?;
|
||||
if mode == GpuMode::Discrete {
|
||||
info!("Set system-level graphics mode: Dedicated Nvidia");
|
||||
} else {
|
||||
info!("Set system-level graphics mode: Optimus");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run_ac_or_bat_cmd(&self, power_plugged: bool) {
|
||||
let prog: Vec<String> = if power_plugged {
|
||||
// AC ONLINE
|
||||
self.config
|
||||
.lock()
|
||||
.await
|
||||
.ac_command
|
||||
.split_whitespace()
|
||||
.map(|s| s.to_string())
|
||||
.collect()
|
||||
} else {
|
||||
// BATTERY
|
||||
self.config
|
||||
.lock()
|
||||
.await
|
||||
.bat_command
|
||||
.split_whitespace()
|
||||
.map(|s| s.to_string())
|
||||
.collect()
|
||||
};
|
||||
if prog.len() > 1 {
|
||||
let mut cmd = Command::new(&prog[0]);
|
||||
for arg in prog.iter().skip(1) {
|
||||
cmd.arg(arg);
|
||||
}
|
||||
if let Err(e) = cmd.spawn() {
|
||||
if power_plugged {
|
||||
error!("AC power command error: {e}");
|
||||
} else {
|
||||
error!("Battery power command error: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_and_set_epp(&self, profile: PlatformPolicy) {
|
||||
info!("PlatformPolicy setting EPP");
|
||||
if let Some(cpu) = self.cpu_control.as_ref() {
|
||||
if let Ok(epp) = cpu.get_available_epp() {
|
||||
debug!("Available EPP: {epp:?}");
|
||||
if epp.contains(&profile.into()) {
|
||||
debug!("Setting {profile:?}");
|
||||
cpu.set_epp(profile.into()).ok();
|
||||
} else if let Ok(gov) = cpu.get_governor() {
|
||||
if gov != CPUGovernor::Powersave {
|
||||
warn!("powersave governor is not is use, you should use it.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn update_policy_ac_or_bat(&self, power_plugged: bool) {
|
||||
let profile = if power_plugged {
|
||||
self.config.lock().await.platform_policy_on_ac
|
||||
} else {
|
||||
self.config.lock().await.platform_policy_on_battery
|
||||
};
|
||||
self.platform
|
||||
.set_throttle_thermal_policy(profile.into())
|
||||
.ok();
|
||||
self.check_and_set_epp(profile);
|
||||
}
|
||||
}
|
||||
|
||||
#[dbus_interface(name = "org.asuslinux.Daemon")]
|
||||
impl CtrlPlatform {
|
||||
/// Returns a list of property names that this system supports
|
||||
async fn supported_properties(&self) -> Vec<Properties> {
|
||||
let mut supported = Vec::new();
|
||||
|
||||
macro_rules! platform_name {
|
||||
($property:tt, $prop_name:ty) => {
|
||||
concat_idents::concat_idents!(has = has_, $property {
|
||||
if self.platform.has() {
|
||||
supported.push($prop_name.to_owned());
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! power_name {
|
||||
($property:tt, $prop_name:ty) => {
|
||||
concat_idents::concat_idents!(has = has_, $property {
|
||||
if self.power.has() {
|
||||
supported.push($prop_name.to_owned());
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: automate this
|
||||
power_name!(
|
||||
charge_control_end_threshold,
|
||||
Properties::ChargeControlEndThreshold
|
||||
);
|
||||
|
||||
platform_name!(dgpu_disable, Properties::DgpuDisable);
|
||||
platform_name!(gpu_mux_mode, Properties::GpuMuxMode);
|
||||
platform_name!(post_animation_sound, Properties::PostAnimationSound);
|
||||
platform_name!(panel_od, Properties::PanelOd);
|
||||
platform_name!(mini_led_mode, Properties::MiniLedMode);
|
||||
platform_name!(egpu_enable, Properties::EgpuEnable);
|
||||
platform_name!(throttle_thermal_policy, Properties::PlatformPolicy);
|
||||
|
||||
platform_name!(ppt_pl1_spl, Properties::PptPl1Spl);
|
||||
platform_name!(ppt_pl2_sppt, Properties::PptPl2Sppt);
|
||||
platform_name!(ppt_fppt, Properties::PptFppt);
|
||||
platform_name!(ppt_apu_sppt, Properties::PptApuSppt);
|
||||
platform_name!(ppt_platform_sppt, Properties::PptPlatformSppt);
|
||||
platform_name!(nv_dynamic_boost, Properties::NvDynamicBoost);
|
||||
platform_name!(nv_temp_target, Properties::NvTempTarget);
|
||||
|
||||
supported
|
||||
}
|
||||
|
||||
async fn supported_interfaces(
|
||||
&self,
|
||||
#[zbus(object_server)] server: &ObjectServer,
|
||||
) -> Vec<String> {
|
||||
let mut interfaces = Vec::default();
|
||||
if server
|
||||
.interface::<_, CtrlAnimeZbus>(ANIME_ZBUS_PATH)
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
interfaces.push(ANIME_ZBUS_NAME.to_owned());
|
||||
}
|
||||
if server
|
||||
.interface::<_, CtrlAuraZbus>(AURA_ZBUS_PATH)
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
interfaces.push(AURA_ZBUS_NAME.to_owned());
|
||||
}
|
||||
if server
|
||||
.interface::<_, CtrlFanCurveZbus>(FAN_CURVE_ZBUS_PATH)
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
interfaces.push(FAN_CURVE_ZBUS_NAME.to_owned());
|
||||
}
|
||||
interfaces
|
||||
}
|
||||
|
||||
#[dbus_interface(property)]
|
||||
fn charge_control_end_threshold(&self) -> Result<u8, FdoErr> {
|
||||
let limit = self.power.get_charge_control_end_threshold()?;
|
||||
Ok(limit)
|
||||
}
|
||||
|
||||
#[dbus_interface(property)]
|
||||
async fn set_charge_control_end_threshold(&mut self, limit: u8) -> Result<(), FdoErr> {
|
||||
if !(20..=100).contains(&limit) {
|
||||
return Err(RogError::ChargeLimit(limit))?;
|
||||
}
|
||||
self.power.set_charge_control_end_threshold(limit)?;
|
||||
self.config.lock().await.charge_control_end_threshold = limit;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[dbus_interface(property)]
|
||||
fn gpu_mux_mode(&self) -> Result<u8, FdoErr> {
|
||||
self.platform.get_gpu_mux_mode().map_err(|err| {
|
||||
warn!("RogPlatform: set_gpu_mux_mode {err}");
|
||||
FdoErr::NotSupported("RogPlatform: set_gpu_mux_mode not supported".to_owned())
|
||||
})
|
||||
}
|
||||
|
||||
#[dbus_interface(property)]
|
||||
async fn set_gpu_mux_mode(&mut self, mode: u8) -> Result<(), FdoErr> {
|
||||
if self.platform.has_gpu_mux_mode() {
|
||||
self.set_gfx_mode(mode.into()).map_err(|err| {
|
||||
warn!("RogPlatform: set_gpu_mux_mode {}", err);
|
||||
FdoErr::Failed(format!("RogPlatform: set_gpu_mux_mode: {err}"))
|
||||
})
|
||||
} else {
|
||||
Err(FdoErr::NotSupported(
|
||||
"RogPlatform: set_gpu_mux_mode not supported".to_owned(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Toggle to next platform_profile. Names provided by `Profiles`.
|
||||
/// If fan-curves are supported will also activate a fan curve for profile.
|
||||
async fn next_throttle_thermal_policy(
|
||||
&mut self,
|
||||
#[zbus(signal_context)] ctxt: SignalContext<'_>,
|
||||
) -> Result<(), FdoErr> {
|
||||
let policy: PlatformPolicy =
|
||||
platform_get_value!(self, throttle_thermal_policy, "throttle_thermal_policy")
|
||||
.map(|n| n.into())?;
|
||||
let policy = PlatformPolicy::next(&policy);
|
||||
|
||||
if self.platform.has_throttle_thermal_policy() {
|
||||
self.check_and_set_epp(policy);
|
||||
self.platform
|
||||
.set_throttle_thermal_policy(policy.into())
|
||||
.map_err(|err| {
|
||||
warn!("RogPlatform: throttle_thermal_policy {}", err);
|
||||
FdoErr::Failed(format!("RogPlatform: throttle_thermal_policy: {err}"))
|
||||
})?;
|
||||
self.config.lock().await.platform_policy_to_restore = policy;
|
||||
Ok(self.throttle_thermal_policy_changed(&ctxt).await?)
|
||||
} else {
|
||||
Err(FdoErr::NotSupported(
|
||||
"RogPlatform: throttle_thermal_policy not supported".to_owned(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[dbus_interface(property)]
|
||||
fn throttle_thermal_policy(&self) -> Result<PlatformPolicy, FdoErr> {
|
||||
platform_get_value!(self, throttle_thermal_policy, "throttle_thermal_policy")
|
||||
.map(|n| n.into())
|
||||
}
|
||||
|
||||
#[dbus_interface(property)]
|
||||
async fn set_throttle_thermal_policy(&mut self, policy: PlatformPolicy) -> Result<(), FdoErr> {
|
||||
// TODO: watch for external changes
|
||||
if self.platform.has_throttle_thermal_policy() {
|
||||
self.check_and_set_epp(policy);
|
||||
self.config.lock().await.platform_policy_to_restore = policy;
|
||||
self.platform
|
||||
.set_throttle_thermal_policy(policy.into())
|
||||
.map_err(|err| {
|
||||
warn!("RogPlatform: throttle_thermal_policy {}", err);
|
||||
FdoErr::Failed(format!("RogPlatform: throttle_thermal_policy: {err}"))
|
||||
})
|
||||
} else {
|
||||
Err(FdoErr::NotSupported(
|
||||
"RogPlatform: throttle_thermal_policy not supported".to_owned(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[dbus_interface(property)]
|
||||
fn post_animation_sound(&self) -> Result<bool, FdoErr> {
|
||||
platform_get_value!(self, post_animation_sound, "post_animation_sound")
|
||||
}
|
||||
|
||||
#[dbus_interface(property)]
|
||||
async fn set_post_animation_sound(&mut self, on: bool) -> Result<(), FdoErr> {
|
||||
if self.platform.has_post_animation_sound() {
|
||||
self.platform.set_post_animation_sound(on).map_err(|err| {
|
||||
warn!("RogPlatform: set_post_animation_sound {}", err);
|
||||
FdoErr::Failed(format!("RogPlatform: set_post_animation_sound: {err}"))
|
||||
})
|
||||
} else {
|
||||
Err(FdoErr::NotSupported(
|
||||
"RogPlatform: set_post_animation_sound not supported".to_owned(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the `panel_od` value from platform. Updates the stored value in
|
||||
/// internal config also.
|
||||
#[dbus_interface(property)]
|
||||
fn panel_od(&self) -> Result<bool, FdoErr> {
|
||||
platform_get_value!(self, panel_od, "panel_od")
|
||||
}
|
||||
|
||||
#[dbus_interface(property)]
|
||||
async fn set_panel_od(&mut self, overdrive: bool) -> Result<(), FdoErr> {
|
||||
platform_set_bool!(self, panel_od, "panel_od", overdrive)
|
||||
}
|
||||
|
||||
/// Get the `panel_od` value from platform. Updates the stored value in
|
||||
/// internal config also.
|
||||
#[dbus_interface(property)]
|
||||
fn mini_led_mode(&self) -> Result<bool, FdoErr> {
|
||||
platform_get_value!(self, mini_led_mode, "mini_led_mode")
|
||||
}
|
||||
|
||||
#[dbus_interface(property)]
|
||||
async fn set_mini_led_mode(&mut self, on: bool) -> Result<(), FdoErr> {
|
||||
platform_set_bool!(self, mini_led_mode, "mini_led_mode", on)
|
||||
}
|
||||
|
||||
#[dbus_interface(property)]
|
||||
fn dgpu_disable(&self) -> Result<bool, FdoErr> {
|
||||
platform_get_value!(self, dgpu_disable, "dgpu_disable")
|
||||
}
|
||||
|
||||
#[dbus_interface(property)]
|
||||
fn egpu_enable(&self) -> Result<bool, FdoErr> {
|
||||
platform_get_value!(self, egpu_enable, "egpu_enable")
|
||||
}
|
||||
|
||||
/// ************************************************************************
|
||||
#[dbus_interface(property)]
|
||||
async fn ppt_pl1_spl(&self) -> Result<u8, FdoErr> {
|
||||
platform_get_value_if_some!(self, ppt_pl1_spl, "ppt_pl1_spl", 5)
|
||||
}
|
||||
|
||||
#[dbus_interface(property)]
|
||||
async fn set_ppt_pl1_spl(&mut self, value: u8) -> Result<(), FdoErr> {
|
||||
platform_set_with_min_max!(self, ppt_pl1_spl, "ppt_pl1_spl", value, 5, 250)
|
||||
}
|
||||
|
||||
#[dbus_interface(property)]
|
||||
async fn ppt_pl2_sppt(&self) -> Result<u8, FdoErr> {
|
||||
platform_get_value_if_some!(self, ppt_pl2_sppt, "ppt_pl2_sppt", 5)
|
||||
}
|
||||
|
||||
#[dbus_interface(property)]
|
||||
async fn set_ppt_pl2_sppt(&mut self, value: u8) -> Result<(), FdoErr> {
|
||||
platform_set_with_min_max!(self, ppt_pl2_sppt, "ppt_pl2_sppt", value, 5, 250)
|
||||
}
|
||||
|
||||
#[dbus_interface(property)]
|
||||
async fn ppt_fppt(&self) -> Result<u8, FdoErr> {
|
||||
platform_get_value_if_some!(self, ppt_fppt, "ppt_fppt", 5)
|
||||
}
|
||||
|
||||
#[dbus_interface(property)]
|
||||
async fn set_ppt_fppt(&mut self, value: u8) -> Result<(), FdoErr> {
|
||||
platform_set_with_min_max!(self, ppt_fppt, "ppt_fppt", value, 5, 250)
|
||||
}
|
||||
|
||||
#[dbus_interface(property)]
|
||||
async fn ppt_apu_sppt(&self) -> Result<u8, FdoErr> {
|
||||
platform_get_value_if_some!(self, ppt_apu_sppt, "ppt_apu_sppt", 5)
|
||||
}
|
||||
|
||||
#[dbus_interface(property)]
|
||||
async fn set_ppt_apu_sppt(&mut self, value: u8) -> Result<(), FdoErr> {
|
||||
platform_set_with_min_max!(self, ppt_apu_sppt, "ppt_apu_sppt", value, 5, 130)
|
||||
}
|
||||
|
||||
#[dbus_interface(property)]
|
||||
async fn ppt_platform_sppt(&self) -> Result<u8, FdoErr> {
|
||||
platform_get_value_if_some!(self, ppt_platform_sppt, "ppt_platform_sppt", 5)
|
||||
}
|
||||
|
||||
#[dbus_interface(property)]
|
||||
async fn set_ppt_platform_sppt(&mut self, value: u8) -> Result<(), FdoErr> {
|
||||
platform_set_with_min_max!(self, ppt_platform_sppt, "ppt_platform_sppt", value, 5, 130)
|
||||
}
|
||||
|
||||
#[dbus_interface(property)]
|
||||
async fn nv_dynamic_boost(&self) -> Result<u8, FdoErr> {
|
||||
platform_get_value_if_some!(self, nv_dynamic_boost, "nv_dynamic_boost", 5)
|
||||
}
|
||||
|
||||
#[dbus_interface(property)]
|
||||
async fn set_nv_dynamic_boost(&mut self, value: u8) -> Result<(), FdoErr> {
|
||||
platform_set_with_min_max!(self, nv_dynamic_boost, "nv_dynamic_boost", value, 5, 25)
|
||||
}
|
||||
|
||||
#[dbus_interface(property)]
|
||||
async fn nv_temp_target(&self) -> Result<u8, FdoErr> {
|
||||
platform_get_value_if_some!(self, nv_temp_target, "nv_temp_target", 5)
|
||||
}
|
||||
|
||||
#[dbus_interface(property)]
|
||||
async fn set_nv_temp_target(&mut self, value: u8) -> Result<(), FdoErr> {
|
||||
platform_set_with_min_max!(self, nv_temp_target, "nv_temp_target", value, 5, 87)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl crate::ZbusRun for CtrlPlatform {
|
||||
async fn add_to_server(self, server: &mut Connection) {
|
||||
Self::add_to_server_helper(self, ZBUS_PATH, server).await;
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl crate::Reloadable for CtrlPlatform {
|
||||
async fn reload(&mut self) -> Result<(), RogError> {
|
||||
if self.platform.has_panel_od() {
|
||||
self.platform
|
||||
.set_panel_od(self.config.lock().await.panel_od)?;
|
||||
}
|
||||
|
||||
if self.platform.has_mini_led_mode() {
|
||||
self.platform
|
||||
.set_mini_led_mode(self.config.lock().await.mini_led_mode)?;
|
||||
}
|
||||
|
||||
if self.power.has_charge_control_end_threshold() {
|
||||
self.power.set_charge_control_end_threshold(
|
||||
self.config.lock().await.charge_control_end_threshold,
|
||||
)?;
|
||||
}
|
||||
|
||||
if let Ok(power_plugged) = self.power.get_online() {
|
||||
if self.platform.has_throttle_thermal_policy() {
|
||||
self.update_policy_ac_or_bat(power_plugged > 0).await;
|
||||
}
|
||||
self.run_ac_or_bat_cmd(power_plugged > 0).await;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl CtrlPlatform {
|
||||
task_watch_item!(panel_od platform);
|
||||
|
||||
task_watch_item!(mini_led_mode platform);
|
||||
|
||||
task_watch_item!(charge_control_end_threshold power);
|
||||
|
||||
task_watch_item_notify!(post_animation_sound platform);
|
||||
|
||||
task_watch_item_notify!(dgpu_disable platform);
|
||||
|
||||
task_watch_item_notify!(egpu_enable platform);
|
||||
|
||||
// NOTE: see note further below
|
||||
task_watch_item_notify!(gpu_mux_mode platform);
|
||||
|
||||
task_watch_item_notify!(ppt_pl1_spl platform);
|
||||
|
||||
task_watch_item_notify!(ppt_pl2_sppt platform);
|
||||
|
||||
task_watch_item_notify!(ppt_fppt platform);
|
||||
|
||||
task_watch_item_notify!(ppt_apu_sppt platform);
|
||||
|
||||
task_watch_item_notify!(ppt_platform_sppt platform);
|
||||
|
||||
task_watch_item_notify!(nv_dynamic_boost platform);
|
||||
|
||||
task_watch_item_notify!(nv_temp_target platform);
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl CtrlTask for CtrlPlatform {
|
||||
fn zbus_path() -> &'static str {
|
||||
ZBUS_PATH
|
||||
}
|
||||
|
||||
async fn create_tasks(&self, signal_ctxt: SignalContext<'static>) -> Result<(), RogError> {
|
||||
let platform1 = self.clone();
|
||||
let platform2 = self.clone();
|
||||
let platform3 = self.clone();
|
||||
self.create_sys_event_tasks(
|
||||
move |sleeping| {
|
||||
let platform1 = platform1.clone();
|
||||
async move {
|
||||
info!("RogPlatform reloading panel_od");
|
||||
if !sleeping && platform1.platform.has_panel_od() {
|
||||
platform1
|
||||
.platform
|
||||
.set_panel_od(platform1.config.lock().await.panel_od)
|
||||
.map_err(|err| {
|
||||
warn!("CtrlCharge: panel_od {}", err);
|
||||
err
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
if sleeping && platform1.power.has_charge_control_end_threshold() {
|
||||
platform1.config.lock().await.charge_control_end_threshold = platform1
|
||||
.power
|
||||
.get_charge_control_end_threshold()
|
||||
.unwrap_or(100);
|
||||
} else if !sleeping && platform1.power.has_charge_control_end_threshold() {
|
||||
platform1
|
||||
.power
|
||||
.set_charge_control_end_threshold(
|
||||
platform1.config.lock().await.charge_control_end_threshold,
|
||||
)
|
||||
.ok();
|
||||
}
|
||||
if let Ok(power_plugged) = platform1.power.get_online() {
|
||||
if !sleeping && platform1.platform.has_throttle_thermal_policy() {
|
||||
platform1.update_policy_ac_or_bat(power_plugged > 0).await;
|
||||
}
|
||||
if !sleeping {
|
||||
platform1.run_ac_or_bat_cmd(power_plugged > 0).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
move |shutting_down| {
|
||||
let platform2 = platform2.clone();
|
||||
async move {
|
||||
info!("RogPlatform reloading panel_od");
|
||||
let lock = platform2.config.lock().await;
|
||||
if !shutting_down && platform2.platform.has_panel_od() {
|
||||
platform2
|
||||
.platform
|
||||
.set_panel_od(lock.panel_od)
|
||||
.map_err(|err| {
|
||||
warn!("CtrlCharge: panel_od {}", err);
|
||||
err
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
},
|
||||
move |_lid_closed| {
|
||||
// on lid change
|
||||
async move {}
|
||||
},
|
||||
move |power_plugged| {
|
||||
let platform3 = platform3.clone();
|
||||
// power change
|
||||
async move {
|
||||
if platform3.platform.has_throttle_thermal_policy() {
|
||||
platform3.update_policy_ac_or_bat(power_plugged).await;
|
||||
}
|
||||
platform3.run_ac_or_bat_cmd(power_plugged).await;
|
||||
}
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
// This spawns a new task for every item.
|
||||
// TODO: find a better way to manage this
|
||||
self.watch_panel_od(signal_ctxt.clone()).await?;
|
||||
self.watch_mini_led_mode(signal_ctxt.clone()).await?;
|
||||
self.watch_charge_control_end_threshold(signal_ctxt.clone())
|
||||
.await?;
|
||||
|
||||
self.watch_dgpu_disable(signal_ctxt.clone()).await?;
|
||||
self.watch_egpu_enable(signal_ctxt.clone()).await?;
|
||||
|
||||
// NOTE: Can't have this as a watch because on a write to it, it reverts back to
|
||||
// booted-with value as it does not actually change until reboot.
|
||||
self.watch_gpu_mux_mode(signal_ctxt.clone()).await?;
|
||||
self.watch_post_animation_sound(signal_ctxt.clone()).await?;
|
||||
|
||||
self.watch_ppt_pl1_spl(signal_ctxt.clone()).await?;
|
||||
self.watch_ppt_pl2_sppt(signal_ctxt.clone()).await?;
|
||||
self.watch_ppt_fppt(signal_ctxt.clone()).await?;
|
||||
self.watch_ppt_apu_sppt(signal_ctxt.clone()).await?;
|
||||
self.watch_ppt_platform_sppt(signal_ctxt.clone()).await?;
|
||||
self.watch_nv_dynamic_boost(signal_ctxt.clone()).await?;
|
||||
self.watch_nv_temp_target(signal_ctxt.clone()).await?;
|
||||
|
||||
let watch_throttle_thermal_policy = self.platform.monitor_throttle_thermal_policy()?;
|
||||
let ctrl = self.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
use futures_lite::StreamExt;
|
||||
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() {
|
||||
// this blocks
|
||||
debug!("Platform: watch_throttle_thermal_policy changed");
|
||||
if let Ok(profile) = ctrl
|
||||
.platform
|
||||
.get_throttle_thermal_policy()
|
||||
.map(PlatformPolicy::from)
|
||||
.map_err(|e| {
|
||||
error!("Platform: get_throttle_thermal_policy error: {e}");
|
||||
})
|
||||
{
|
||||
ctrl.check_and_set_epp(profile);
|
||||
ctrl.config.lock().await.platform_policy_to_restore = profile;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
142
asusd/src/daemon.rs
Normal file
@@ -0,0 +1,142 @@
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
use std::io::Write;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
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::controller::CtrlKbdLed;
|
||||
use asusd::ctrl_aura::trait_impls::CtrlAuraZbus;
|
||||
use asusd::ctrl_fancurves::CtrlFanCurveZbus;
|
||||
use asusd::ctrl_platform::CtrlPlatform;
|
||||
use asusd::{print_board_info, CtrlTask, Reloadable, ZbusRun, DBUS_NAME};
|
||||
use config_traits::{StdConfig, StdConfigLoad2};
|
||||
use log::{error, info, warn};
|
||||
use rog_aura::aura_detection::LaptopLedData;
|
||||
use tokio::time::sleep;
|
||||
use zbus::SignalContext;
|
||||
|
||||
#[tokio::main]
|
||||
async 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();
|
||||
|
||||
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-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?;
|
||||
|
||||
let config = Config::new().load();
|
||||
let config = Arc::new(Mutex::new(config));
|
||||
|
||||
// supported.add_to_server(&mut connection).await;
|
||||
|
||||
match CtrlPlatform::new(config.clone()) {
|
||||
Ok(ctrl) => {
|
||||
let sig_ctx = CtrlPlatform::signal_context(&connection)?;
|
||||
start_tasks(ctrl, &mut connection, sig_ctx).await?;
|
||||
}
|
||||
Err(err) => {
|
||||
error!("CtrlPlatform: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
|
||||
let laptop = LaptopLedData::get_data();
|
||||
// CtrlKbdLed deviates from the config pattern above due to requiring a keyboard
|
||||
// detection first
|
||||
match CtrlKbdLed::new(laptop) {
|
||||
Ok(ctrl) => {
|
||||
let zbus = CtrlAuraZbus(Arc::new(Mutex::new(ctrl)));
|
||||
let sig_ctx = CtrlAuraZbus::signal_context(&connection)?;
|
||||
start_tasks(zbus, &mut connection, sig_ctx).await?;
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Keyboard control: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
sleep(Duration::from_millis(1000)).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn start_tasks<T>(
|
||||
mut zbus: T,
|
||||
connection: &mut Connection,
|
||||
signal_ctx: SignalContext<'static>,
|
||||
) -> Result<(), Box<dyn Error>>
|
||||
where
|
||||
T: ZbusRun + Reloadable + CtrlTask + Clone,
|
||||
{
|
||||
let task = zbus.clone();
|
||||
|
||||
zbus.reload()
|
||||
.await
|
||||
.unwrap_or_else(|err| warn!("Controller error: {}", err));
|
||||
zbus.add_to_server(connection).await;
|
||||
|
||||
task.create_tasks(signal_ctx).await.ok();
|
||||
Ok(())
|
||||
}
|
||||
135
asusd/src/error.rs
Normal file
@@ -0,0 +1,135 @@
|
||||
use std::convert::From;
|
||||
use std::fmt;
|
||||
|
||||
use config_traits::ron;
|
||||
use rog_anime::error::AnimeError;
|
||||
use rog_platform::error::PlatformError;
|
||||
use rog_profiles::error::ProfileError;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum RogError {
|
||||
ParseVendor,
|
||||
ParseLed,
|
||||
MissingProfile(String),
|
||||
Udev(String, std::io::Error),
|
||||
Path(String, std::io::Error),
|
||||
Read(String, std::io::Error),
|
||||
Write(String, std::io::Error),
|
||||
NotSupported,
|
||||
NotFound(String),
|
||||
DoTask(String),
|
||||
MissingFunction(String),
|
||||
MissingLedBrightNode(String, std::io::Error),
|
||||
ReloadFail(String),
|
||||
Profiles(ProfileError),
|
||||
Initramfs(String),
|
||||
Modprobe(String),
|
||||
Io(std::io::Error),
|
||||
Zbus(zbus::Error),
|
||||
ChargeLimit(u8),
|
||||
AuraEffectNotSupported,
|
||||
NoAuraKeyboard,
|
||||
NoAuraNode,
|
||||
Anime(AnimeError),
|
||||
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 {
|
||||
match self {
|
||||
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),
|
||||
RogError::Udev(deets, error) => write!(f, "udev {}: {}", deets, error),
|
||||
RogError::Path(path, error) => write!(f, "Path {}: {}", path, error),
|
||||
RogError::Read(path, error) => write!(f, "Read {}: {}", path, error),
|
||||
RogError::Write(path, error) => write!(f, "Write {}: {}", path, error),
|
||||
RogError::NotSupported => write!(f, "Not supported"),
|
||||
RogError::NotFound(deets) => write!(f, "Not found: {}", deets),
|
||||
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, "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::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::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<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<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)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for RogError {
|
||||
fn from(err: std::io::Error) -> Self {
|
||||
RogError::Io(err)
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
zbus::fdo::Error::Failed(format!("{}", err))
|
||||
}
|
||||
}
|
||||
285
asusd/src/lib.rs
Normal file
@@ -0,0 +1,285 @@
|
||||
#![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;
|
||||
|
||||
pub mod error;
|
||||
|
||||
use std::future::Future;
|
||||
use std::time::Duration;
|
||||
|
||||
use async_trait::async_trait;
|
||||
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);
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait Reloadable {
|
||||
async fn reload(&mut self) -> Result<(), RogError>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait ZbusRun {
|
||||
async fn add_to_server(self, server: &mut Connection);
|
||||
|
||||
async fn add_to_server_helper(
|
||||
iface: impl zbus::Interface,
|
||||
path: &str,
|
||||
server: &mut Connection,
|
||||
) {
|
||||
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
|
||||
#[async_trait]
|
||||
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.
|
||||
async fn create_tasks(&self, signal: SignalContext<'static>) -> Result<(), RogError>;
|
||||
|
||||
// /// 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.
|
||||
async fn create_sys_event_tasks<
|
||||
Fut1,
|
||||
Fut2,
|
||||
Fut3,
|
||||
Fut4,
|
||||
F1: Send + 'static,
|
||||
F2: Send + 'static,
|
||||
F3: Send + 'static,
|
||||
F4: Send + 'static,
|
||||
>(
|
||||
&self,
|
||||
mut on_prepare_for_sleep: F1,
|
||||
mut on_prepare_for_shutdown: F2,
|
||||
mut on_lid_change: F3,
|
||||
mut on_external_power_change: F4,
|
||||
) where
|
||||
F1: FnMut(bool) -> Fut1,
|
||||
F2: FnMut(bool) -> Fut2,
|
||||
F3: FnMut(bool) -> Fut3,
|
||||
F4: FnMut(bool) -> Fut4,
|
||||
Fut1: Future<Output = ()> + Send,
|
||||
Fut2: Future<Output = ()> + Send,
|
||||
Fut3: Future<Output = ()> + Send,
|
||||
Fut4: Future<Output = ()> + Send,
|
||||
{
|
||||
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;
|
||||
}
|
||||
57
bindings/dbus-xml/org-asuslinux-anime-4.xml
Normal file
@@ -0,0 +1,57 @@
|
||||
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
|
||||
<node>
|
||||
<interface name="org.asuslinux.Daemon">
|
||||
<!--
|
||||
Writes a data stream of length. Will force system thread to exit until
|
||||
it is restarted
|
||||
-->
|
||||
<method name="Write">
|
||||
<arg name="input" type="(ays)" direction="in"/>
|
||||
</method>
|
||||
<!--
|
||||
The main loop is the base system set action if the user isn't running
|
||||
the user daemon
|
||||
-->
|
||||
<method name="RunMainLoop">
|
||||
<arg name="start" type="b" direction="in"/>
|
||||
</method>
|
||||
<!--
|
||||
Get the device state as stored by asusd
|
||||
-->
|
||||
<method name="DeviceState">
|
||||
<arg type="(bub(ssss)bbbu)" direction="out"/>
|
||||
</method>
|
||||
<!--
|
||||
Set base brightness level
|
||||
-->
|
||||
<!--
|
||||
Set base brightness level
|
||||
-->
|
||||
<property name="Brightness" type="u" access="readwrite"/>
|
||||
<!--
|
||||
Set which builtin animation is used for each stage
|
||||
-->
|
||||
<property name="BuiltinAnimations" type="(ssss)" access="readwrite"/>
|
||||
<!--
|
||||
Enable the builtin animations or not. This is quivalent to "Powersave
|
||||
animations" in Armory crate
|
||||
-->
|
||||
<property name="BuiltinsEnabled" type="b" access="readwrite"/>
|
||||
<!--
|
||||
Set whether the AniMe is enabled at all
|
||||
-->
|
||||
<property name="EnableDisplay" type="b" access="readwrite"/>
|
||||
<!--
|
||||
Set if to turn the AniMe Matrix off when the lid is closed
|
||||
-->
|
||||
<property name="OffWhenLidClosed" type="b" access="readwrite"/>
|
||||
<!--
|
||||
Set if to turn the AniMe Matrix off when the laptop is suspended
|
||||
-->
|
||||
<property name="OffWhenSuspended" type="b" access="readwrite"/>
|
||||
<!--
|
||||
Set if to turn the AniMe Matrix off when external power is unplugged
|
||||
-->
|
||||
<property name="OffWhenUnplugged" type="b" access="readwrite"/>
|
||||
</interface>
|
||||
</node>
|
||||
67
bindings/dbus-xml/org-asuslinux-aura-4.xml
Normal file
@@ -0,0 +1,67 @@
|
||||
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
|
||||
<node>
|
||||
<interface name="org.asuslinux.Daemon">
|
||||
<!--
|
||||
Get the data set for every mode available
|
||||
-->
|
||||
<method name="AllModeData">
|
||||
<arg type="a{u(uu(yyy)(yyy)ss)}" direction="out"/>
|
||||
</method>
|
||||
<!--
|
||||
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
|
||||
-->
|
||||
<method name="DirectAddressingRaw">
|
||||
<arg name="data" type="aay" direction="in"/>
|
||||
</method>
|
||||
<!--
|
||||
Return the current LED brightness
|
||||
-->
|
||||
<!--
|
||||
Set the keyboard brightness level (0-3)
|
||||
-->
|
||||
<property name="Brightness" type="u" access="readwrite"/>
|
||||
<!--
|
||||
Return the device type for this Aura keyboard
|
||||
-->
|
||||
<property name="DeviceType" type="s" access="read"/>
|
||||
<!--
|
||||
The current mode data
|
||||
-->
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<property name="LedMode" type="u" access="readwrite"/>
|
||||
<!--
|
||||
The current mode data
|
||||
-->
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<property name="LedModeData" type="(uu(yyy)(yyy)ss)" access="readwrite"/>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<property name="LedPower" type="(ayay((ubbbb)(ubbbb)(ubbbb)(ubbbb)(ubbbb)))" access="readwrite"/>
|
||||
<!--
|
||||
The total available modes
|
||||
-->
|
||||
<property name="SupportedBasicModes" type="au" access="read"/>
|
||||
<property name="SupportedBasicZones" type="au" access="read"/>
|
||||
<!--
|
||||
Total levels of brightness available
|
||||
-->
|
||||
<property name="SupportedBrightness" type="au" access="read"/>
|
||||
<property name="SupportedPowerZones" type="au" access="read"/>
|
||||
</interface>
|
||||
</node>
|
||||
56
bindings/dbus-xml/org-asuslinux-fan-curves-4.xml
Normal file
@@ -0,0 +1,56 @@
|
||||
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
|
||||
<node>
|
||||
<interface name="org.asuslinux.Daemon">
|
||||
<!--
|
||||
Set all fan curves for a profile to enabled status. Will also activate a
|
||||
fan curve if in the same profile mode
|
||||
-->
|
||||
<method name="SetFanCurvesEnabled">
|
||||
<arg name="profile" type="s" direction="in"/>
|
||||
<arg name="enabled" type="b" direction="in"/>
|
||||
</method>
|
||||
<!--
|
||||
Set a single fan curve for a profile to enabled status. Will also
|
||||
activate a fan curve if in the same profile mode
|
||||
-->
|
||||
<method name="SetProfileFanCurveEnabled">
|
||||
<arg name="profile" type="s" direction="in"/>
|
||||
<arg name="fan" type="s" direction="in"/>
|
||||
<arg name="enabled" type="b" direction="in"/>
|
||||
</method>
|
||||
<!--
|
||||
Get the fan-curve data for the currently active PlatformPolicy
|
||||
-->
|
||||
<method name="FanCurveData">
|
||||
<arg name="profile" type="s" direction="in"/>
|
||||
<arg type="a(s(yyyyyyyy)(yyyyyyyy)b)" direction="out"/>
|
||||
</method>
|
||||
<!--
|
||||
Set the fan curve for the specified profile.
|
||||
Will also activate the fan curve if the user is in the same mode.
|
||||
-->
|
||||
<method name="SetFanCurve">
|
||||
<arg name="profile" type="s" direction="in"/>
|
||||
<arg name="curve" type="(s(yyyyyyyy)(yyyyyyyy)b)" direction="in"/>
|
||||
</method>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<method name="SetActiveCurveToDefaults">
|
||||
</method>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<method name="ResetProfileCurves">
|
||||
<arg name="profile" type="s" direction="in"/>
|
||||
</method>
|
||||
</interface>
|
||||
</node>
|
||||
46
bindings/dbus-xml/org-asuslinux-platform-4.xml
Normal file
@@ -0,0 +1,46 @@
|
||||
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
|
||||
<node>
|
||||
<interface name="org.asuslinux.Daemon">
|
||||
<!--
|
||||
Returns a list of property names that this system supports
|
||||
-->
|
||||
<method name="SupportedProperties">
|
||||
<arg type="as" direction="out"/>
|
||||
</method>
|
||||
<method name="SupportedInterfaces">
|
||||
<arg type="as" direction="out"/>
|
||||
</method>
|
||||
<!--
|
||||
Toggle to next platform_profile. Names provided by `Profiles`.
|
||||
If fan-curves are supported will also activate a fan curve for profile.
|
||||
-->
|
||||
<method name="NextThrottleThermalPolicy">
|
||||
</method>
|
||||
<property name="ChargeControlEndThreshold" type="y" access="readwrite"/>
|
||||
<property name="DgpuDisable" type="b" access="read"/>
|
||||
<property name="EgpuEnable" type="b" access="read"/>
|
||||
<property name="GpuMuxMode" type="y" access="readwrite"/>
|
||||
<!--
|
||||
Get the `panel_od` value from platform. Updates the stored value in
|
||||
internal config also.
|
||||
-->
|
||||
<property name="MiniLedMode" type="b" access="readwrite"/>
|
||||
<property name="NvDynamicBoost" type="y" access="readwrite"/>
|
||||
<property name="NvTempTarget" type="y" access="readwrite"/>
|
||||
<!--
|
||||
Get the `panel_od` value from platform. Updates the stored value in
|
||||
internal config also.
|
||||
-->
|
||||
<property name="PanelOd" type="b" access="readwrite"/>
|
||||
<property name="PostAnimationSound" type="b" access="readwrite"/>
|
||||
<property name="PptApuSppt" type="y" access="readwrite"/>
|
||||
<property name="PptFppt" type="y" access="readwrite"/>
|
||||
<!--
|
||||
************************************************************************
|
||||
-->
|
||||
<property name="PptPl1Spl" type="y" access="readwrite"/>
|
||||
<property name="PptPl2Sppt" type="y" access="readwrite"/>
|
||||
<property name="PptPlatformSppt" type="y" access="readwrite"/>
|
||||
<property name="ThrottleThermalPolicy" type="s" access="readwrite"/>
|
||||
</interface>
|
||||
</node>
|
||||
57
bindings/ts/anime.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
Generated by typeshare 1.7.0
|
||||
*/
|
||||
|
||||
export enum AnimBooting {
|
||||
GlitchConstruction = "GlitchConstruction",
|
||||
StaticEmergence = "StaticEmergence",
|
||||
}
|
||||
|
||||
export enum AnimAwake {
|
||||
BinaryBannerScroll = "BinaryBannerScroll",
|
||||
RogLogoGlitch = "RogLogoGlitch",
|
||||
}
|
||||
|
||||
export enum AnimSleeping {
|
||||
BannerSwipe = "BannerSwipe",
|
||||
Starfield = "Starfield",
|
||||
}
|
||||
|
||||
export enum AnimShutdown {
|
||||
GlitchOut = "GlitchOut",
|
||||
SeeYa = "SeeYa",
|
||||
}
|
||||
|
||||
export interface Animations {
|
||||
boot: AnimBooting;
|
||||
awake: AnimAwake;
|
||||
sleep: AnimSleeping;
|
||||
shutdown: AnimShutdown;
|
||||
}
|
||||
|
||||
/** Base LED brightness of the display */
|
||||
export enum Brightness {
|
||||
Off = "Off",
|
||||
Low = "Low",
|
||||
Med = "Med",
|
||||
High = "High",
|
||||
}
|
||||
|
||||
export interface DeviceState {
|
||||
display_enabled: boolean;
|
||||
display_brightness: Brightness;
|
||||
builtin_anims_enabled: boolean;
|
||||
builtin_anims: Animations;
|
||||
off_when_unplugged: boolean;
|
||||
off_when_suspended: boolean;
|
||||
off_when_lid_closed: boolean;
|
||||
brightness_on_battery: Brightness;
|
||||
}
|
||||
|
||||
export enum AnimeType {
|
||||
GA401 = "GA401",
|
||||
GA402 = "GA402",
|
||||
GU604 = "GU604",
|
||||
Unknown = "Unknown",
|
||||
}
|
||||
|
||||
233
bindings/ts/aura.ts
Normal file
@@ -0,0 +1,233 @@
|
||||
/*
|
||||
Generated by typeshare 1.7.0
|
||||
*/
|
||||
|
||||
/** Represents the per-key raw USB packets */
|
||||
export type UsbPackets = number[][];
|
||||
|
||||
/**
|
||||
* A `UsbPackets` contains all data to change the full set of keyboard
|
||||
* key colours individually.
|
||||
*
|
||||
* Each row of the internal array is a full HID packet that can be sent
|
||||
* to the keyboard EC. One row controls one group of keys, these keys are not
|
||||
* necessarily all on the same row of the keyboard, with some splitting between
|
||||
* two rows.
|
||||
*/
|
||||
export interface LedUsbPackets {
|
||||
/** The packet data used to send data to the USB keyboard */
|
||||
usb_packets: UsbPackets;
|
||||
/**
|
||||
* Wether or not this packet collection is zoned. The determines which
|
||||
* starting bytes are used and what the indexing is for lightbar RGB
|
||||
* colours
|
||||
*/
|
||||
zoned: boolean;
|
||||
}
|
||||
|
||||
export interface Colour {
|
||||
r: number;
|
||||
g: number;
|
||||
b: number;
|
||||
}
|
||||
|
||||
/** Enum of modes that convert to the actual number required by a USB HID packet */
|
||||
export enum AuraModeNum {
|
||||
Static = "Static",
|
||||
Breathe = "Breathe",
|
||||
Strobe = "Strobe",
|
||||
Rainbow = "Rainbow",
|
||||
Star = "Star",
|
||||
Rain = "Rain",
|
||||
Highlight = "Highlight",
|
||||
Laser = "Laser",
|
||||
Ripple = "Ripple",
|
||||
Pulse = "Pulse",
|
||||
Comet = "Comet",
|
||||
Flash = "Flash",
|
||||
}
|
||||
|
||||
/** Base effects have no zoning, while multizone is 1-4 */
|
||||
export enum AuraZone {
|
||||
/** Used if keyboard has no zones, or if setting all */
|
||||
None = "None",
|
||||
/** Leftmost zone */
|
||||
Key1 = "Key1",
|
||||
/** Zone after leftmost */
|
||||
Key2 = "Key2",
|
||||
/** Zone second from right */
|
||||
Key3 = "Key3",
|
||||
/** Rightmost zone */
|
||||
Key4 = "Key4",
|
||||
/** Logo on the lid (or elsewhere?) */
|
||||
Logo = "Logo",
|
||||
/** The left part of a lightbar (typically on the front of laptop) */
|
||||
BarLeft = "BarLeft",
|
||||
/** The right part of a lightbar */
|
||||
BarRight = "BarRight",
|
||||
}
|
||||
|
||||
export enum Speed {
|
||||
Low = "Low",
|
||||
Med = "Med",
|
||||
High = "High",
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for Rainbow mode.
|
||||
*
|
||||
* Enum corresponds to the required integer value
|
||||
*/
|
||||
export enum Direction {
|
||||
Right = "Right",
|
||||
Left = "Left",
|
||||
Up = "Up",
|
||||
Down = "Down",
|
||||
}
|
||||
|
||||
/**
|
||||
* Default factory modes structure. This easily converts to an USB HID packet
|
||||
* with:
|
||||
* ```rust
|
||||
* // let bytes: [u8; LED_MSG_LEN] = mode.into();
|
||||
* ```
|
||||
*/
|
||||
export interface AuraEffect {
|
||||
/** The effect type */
|
||||
mode: AuraModeNum;
|
||||
/** `AuraZone::None` for no zone or zoneless keyboards */
|
||||
zone: AuraZone;
|
||||
/** Primary colour for all modes */
|
||||
colour1: Colour;
|
||||
/** Secondary colour in some modes like Breathing or Stars */
|
||||
colour2: Colour;
|
||||
/** One of three speeds for modes that support speed (most that animate) */
|
||||
speed: Speed;
|
||||
/** Up, down, left, right. Only Rainbow mode seems to use this */
|
||||
direction: Direction;
|
||||
}
|
||||
|
||||
/** The powerr zones this laptop supports */
|
||||
export enum PowerZones {
|
||||
/** The logo on some laptop lids */
|
||||
Logo = "Logo",
|
||||
/** The full keyboard (not zones) */
|
||||
Keyboard = "Keyboard",
|
||||
/** The lightbar, typically on the front of the laptop */
|
||||
Lightbar = "Lightbar",
|
||||
/** The leds that may be placed around the edge of the laptop lid */
|
||||
Lid = "Lid",
|
||||
/** The led strip on the rear of some laptops */
|
||||
RearGlow = "RearGlow",
|
||||
}
|
||||
|
||||
export interface KbAuraPowerState {
|
||||
zone: PowerZones;
|
||||
boot: boolean;
|
||||
awake: boolean;
|
||||
sleep: boolean;
|
||||
shutdown: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Track and control the Aura keyboard power state
|
||||
*
|
||||
* # Bits for newer 0x18c6, 0x19B6, 0x1a30, keyboard models
|
||||
*
|
||||
* | Byte 1 | Byte 2 | Byte 3 | Byte 4 | Label |
|
||||
* |--------|---------|---------|---------|----------|
|
||||
* |00000001| 00000000| 00000000| 00000000|boot_logo_|
|
||||
* |00000010| 00000000| 00000000| 00000000|boot_keyb_|
|
||||
* |00000100| 00000000| 00000000| 00000000|awake_logo|
|
||||
* |00001000| 00000000| 00000000| 00000000|awake_keyb|
|
||||
* |00010000| 00000000| 00000000| 00000000|sleep_logo|
|
||||
* |00100000| 00000000| 00000000| 00000000|sleep_keyb|
|
||||
* |01000000| 00000000| 00000000| 00000000|shut_logo_|
|
||||
* |10000000| 00000000| 00000000| 00000000|shut_keyb_|
|
||||
* |00000000| 00000010| 00000000| 00000000|boot_bar__|
|
||||
* |00000000| 00000100| 00000000| 00000000|awake_bar_|
|
||||
* |00000000| 00001000| 00000000| 00000000|sleep_bar_|
|
||||
* |00000000| 00010000| 00000000| 00000000|shut_bar__|
|
||||
* |00000000| 00000000| 00000001| 00000000|boot_lid__|
|
||||
* |00000000| 00000000| 00000010| 00000000|awkae_lid_|
|
||||
* |00000000| 00000000| 00000100| 00000000|sleep_lid_|
|
||||
* |00000000| 00000000| 00001000| 00000000|shut_lid__|
|
||||
* |00000000| 00000000| 00000000| 00000001|boot_rear_|
|
||||
* |00000000| 00000000| 00000000| 00000010|awake_rear|
|
||||
* |00000000| 00000000| 00000000| 00000100|sleep_rear|
|
||||
* |00000000| 00000000| 00000000| 00001000|shut_rear_|
|
||||
*/
|
||||
export interface AuraPower {
|
||||
keyboard: KbAuraPowerState;
|
||||
logo: KbAuraPowerState;
|
||||
lightbar: KbAuraPowerState;
|
||||
lid: KbAuraPowerState;
|
||||
rear_glow: KbAuraPowerState;
|
||||
}
|
||||
|
||||
export enum AuraDevTuf {
|
||||
Boot = "Boot",
|
||||
Awake = "Awake",
|
||||
Sleep = "Sleep",
|
||||
Keyboard = "Keyboard",
|
||||
}
|
||||
|
||||
/**
|
||||
* # Bits for older 0x1866 keyboard model
|
||||
*
|
||||
* Keybord and Lightbar require Awake, Boot and Sleep apply to both
|
||||
* Keybord and Lightbar regardless of if either are enabled (or Awake is
|
||||
* enabled)
|
||||
*
|
||||
* | Byte 1 | Byte 2 | Byte 3 | function | hex |
|
||||
* |------------|------------|------------|----------|----------|
|
||||
* | 0000, 0000 | 0000, 0000 | 0000, 0010 | Awake | 00,00,02 |
|
||||
* | 0000, 1000 | 0000, 0000 | 0000, 0000 | Keyboard | 08,00,00 |
|
||||
* | 0000, 0100 | 0000, 0101 | 0000, 0000 | Lightbar | 04,05,00 |
|
||||
* | 1100, 0011 | 0001, 0010 | 0000, 1001 | Boot/Sht | c3,12,09 |
|
||||
* | 0011, 0000 | 0000, 1000 | 0000, 0100 | Sleep | 30,08,04 |
|
||||
* | 1111, 1111 | 0001, 1111 | 0000, 1111 | all on | |
|
||||
*/
|
||||
export enum AuraDevRog1 {
|
||||
Awake = "Awake",
|
||||
Keyboard = "Keyboard",
|
||||
Lightbar = "Lightbar",
|
||||
Boot = "Boot",
|
||||
Sleep = "Sleep",
|
||||
}
|
||||
|
||||
/** This struct is intended as a helper to pass args to generic dbus interface */
|
||||
export interface AuraPowerDev {
|
||||
/**
|
||||
* TUF laptops use a similar style of control to the older ROG devices but
|
||||
* through WMI
|
||||
*/
|
||||
tuf: AuraDevTuf[];
|
||||
/**
|
||||
* Pre-0x19b6 devices use a different smaller scheme to the newer ROG
|
||||
* devices
|
||||
*/
|
||||
old_rog: AuraDevRog1[];
|
||||
/** ASUS standardised control scheme from 2020 onwards */
|
||||
rog: AuraPower;
|
||||
}
|
||||
|
||||
export enum LedBrightness {
|
||||
Off = "Off",
|
||||
Low = "Low",
|
||||
Med = "Med",
|
||||
High = "High",
|
||||
}
|
||||
|
||||
export enum AuraDevice {
|
||||
Tuf = "Tuf",
|
||||
X1854 = "X1854",
|
||||
X1869 = "X1869",
|
||||
X1866 = "X1866",
|
||||
X18c6 = "X18c6",
|
||||
X19b6 = "X19b6",
|
||||
X1a30 = "X1a30",
|
||||
X1abe = "X1abe",
|
||||
Unknown = "Unknown",
|
||||
}
|
||||
|
||||
55
bindings/ts/platform.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
Generated by typeshare 1.7.0
|
||||
*/
|
||||
|
||||
export enum CPUGovernor {
|
||||
Performance = "Performance",
|
||||
Powersave = "Powersave",
|
||||
BadValue = "BadValue",
|
||||
}
|
||||
|
||||
export enum CPUEPP {
|
||||
Default = "Default",
|
||||
Performance = "Performance",
|
||||
BalancePerformance = "BalancePerformance",
|
||||
BalancePower = "BalancePower",
|
||||
Power = "Power",
|
||||
}
|
||||
|
||||
export enum GpuMode {
|
||||
Discrete = "Discrete",
|
||||
Optimus = "Optimus",
|
||||
Integrated = "Integrated",
|
||||
Egpu = "Egpu",
|
||||
Vfio = "Vfio",
|
||||
Ultimate = "Ultimate",
|
||||
Error = "Error",
|
||||
NotSupported = "NotSupported",
|
||||
}
|
||||
|
||||
/** `throttle_thermal_policy` in asus_wmi */
|
||||
export enum PlatformPolicy {
|
||||
Balanced = "Balanced",
|
||||
Performance = "Performance",
|
||||
Quiet = "Quiet",
|
||||
}
|
||||
|
||||
/** CamelCase names of the properties. Intended for use with DBUS */
|
||||
export enum Properties {
|
||||
ChargeControlEndThreshold = "ChargeControlEndThreshold",
|
||||
DgpuDisable = "DgpuDisable",
|
||||
GpuMuxMode = "GpuMuxMode",
|
||||
PostAnimationSound = "PostAnimationSound",
|
||||
PanelOd = "PanelOd",
|
||||
MiniLedMode = "MiniLedMode",
|
||||
EgpuEnable = "EgpuEnable",
|
||||
PlatformPolicy = "PlatformPolicy",
|
||||
PptPl1Spl = "PptPl1Spl",
|
||||
PptPl2Sppt = "PptPl2Sppt",
|
||||
PptFppt = "PptFppt",
|
||||
PptApuSppt = "PptApuSppt",
|
||||
PptPlatformSppt = "PptPlatformSppt",
|
||||
NvDynamicBoost = "NvDynamicBoost",
|
||||
NvTempTarget = "NvTempTarget",
|
||||
}
|
||||
|
||||
24
bindings/ts/profiles.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
Generated by typeshare 1.7.0
|
||||
*/
|
||||
|
||||
export enum FanCurvePU {
|
||||
CPU = "CPU",
|
||||
GPU = "GPU",
|
||||
MID = "MID",
|
||||
}
|
||||
|
||||
export interface CurveData {
|
||||
fan: FanCurvePU;
|
||||
pwm: [number, number, number, number, number, number, number, number];
|
||||
temp: [number, number, number, number, number, number, number, number];
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
/** Main purpose of `FanCurves` is to enable restoring state on system boot */
|
||||
export interface FanCurveProfiles {
|
||||
balanced: CurveData[];
|
||||
performance: CurveData[];
|
||||
quiet: CurveData[];
|
||||
}
|
||||
|
||||
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
@@ -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.
|
||||
328
config-traits/src/lib.rs
Normal file
@@ -0,0 +1,328 @@
|
||||
//! `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)
|
||||
.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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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
@@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "cpuctl"
|
||||
license = "MPL-2.0"
|
||||
edition = "2021"
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
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,44 +0,0 @@
|
||||
[package]
|
||||
name = "daemon"
|
||||
version = "3.1.7"
|
||||
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_types = { path = "../rog-types" }
|
||||
rog_dbus = { path = "../rog-dbus" }
|
||||
rusb = "^0.7"
|
||||
udev = "^0.6"
|
||||
|
||||
# cli and logging
|
||||
log = "^0.4"
|
||||
env_logger = "^0.8"
|
||||
|
||||
zbus = "^2.0.0-beta.3"
|
||||
zvariant = "^2.5"
|
||||
logind-zbus = "*"
|
||||
|
||||
# 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"] }
|
||||
# cpu power management
|
||||
intel-pstate = "^0.2"
|
||||
@@ -1,206 +0,0 @@
|
||||
use log::{error, info, warn};
|
||||
use rog_fan_curve::Curve;
|
||||
use rog_types::{aura_modes::AuraModes, 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";
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct Config {
|
||||
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,
|
||||
pub kbd_backlight_modes: Vec<AuraModes>,
|
||||
pub power_profiles: BTreeMap<String, Profile>,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
let mut pwr = BTreeMap::new();
|
||||
pwr.insert("normal".into(), Profile::new(0, 100, true, 0, None));
|
||||
pwr.insert("boost".into(), Profile::new(0, 100, true, 1, None));
|
||||
pwr.insert("silent".into(), Profile::new(0, 100, true, 2, None));
|
||||
|
||||
Config {
|
||||
gfx_mode: GfxVendors::Hybrid,
|
||||
gfx_managed: true,
|
||||
active_profile: "normal".into(),
|
||||
toggle_profiles: vec!["normal".into(), "boost".into(), "silent".into()],
|
||||
curr_fan_mode: 0,
|
||||
bat_charge_limit: 100,
|
||||
kbd_led_brightness: 1,
|
||||
kbd_backlight_mode: 0,
|
||||
kbd_backlight_modes: Vec::new(),
|
||||
power_profiles: pwr,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
/// `load` will attempt to read the config, and panic if the dir is missing
|
||||
pub fn load(supported_led_modes: &[u8]) -> Self {
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(&CONFIG_PATH)
|
||||
.unwrap_or_else(|_| {
|
||||
panic!(
|
||||
"The file {} or directory /etc/asusd/ is missing",
|
||||
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 Config::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::<ConfigV301>(&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::<ConfigV222>(&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::<ConfigV212>(&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, &supported_led_modes)
|
||||
}
|
||||
|
||||
fn create_default(file: &mut File, supported_led_modes: &[u8]) -> Self {
|
||||
// create a default config here
|
||||
let mut config = Config::default();
|
||||
|
||||
for n in supported_led_modes {
|
||||
config.kbd_backlight_modes.push(AuraModes::from(*n))
|
||||
}
|
||||
|
||||
// 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));
|
||||
}
|
||||
|
||||
pub fn set_mode_data(&mut self, mode: AuraModes) {
|
||||
let byte: u8 = (&mode).into();
|
||||
for (index, n) in self.kbd_backlight_modes.iter().enumerate() {
|
||||
if byte == u8::from(n) {
|
||||
// Consume it, OMNOMNOMNOM
|
||||
self.kbd_backlight_modes[index] = mode;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_led_mode_data(&self, num: u8) -> Option<&AuraModes> {
|
||||
for mode in &self.kbd_backlight_modes {
|
||||
if u8::from(mode) == num {
|
||||
return Some(mode);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct Profile {
|
||||
pub min_percentage: u8,
|
||||
pub max_percentage: u8,
|
||||
pub turbo: bool,
|
||||
pub fan_preset: u8,
|
||||
pub fan_curve: Option<Curve>,
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub type CPUSettings = Profile;
|
||||
|
||||
impl Default for Profile {
|
||||
fn default() -> Self {
|
||||
Profile {
|
||||
min_percentage: 0,
|
||||
max_percentage: 100,
|
||||
turbo: false,
|
||||
fan_preset: 0,
|
||||
fan_curve: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Profile {
|
||||
pub fn new(
|
||||
min_percentage: u8,
|
||||
max_percentage: u8,
|
||||
turbo: bool,
|
||||
fan_preset: u8,
|
||||
fan_curve: Option<Curve>,
|
||||
) -> Self {
|
||||
Profile {
|
||||
min_percentage,
|
||||
max_percentage,
|
||||
turbo,
|
||||
fan_preset,
|
||||
fan_curve,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
use rog_types::{aura_modes::AuraModes, gfx_vendors::GfxVendors};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::config::{Config, Profile};
|
||||
|
||||
/// for parsing old v2.1.2 config
|
||||
#[derive(Deserialize)]
|
||||
pub(crate) struct ConfigV212 {
|
||||
gfx_managed: bool,
|
||||
bat_charge_limit: u8,
|
||||
active_profile: String,
|
||||
toggle_profiles: Vec<String>,
|
||||
power_profiles: BTreeMap<String, Profile>,
|
||||
power_profile: u8,
|
||||
kbd_led_brightness: u8,
|
||||
kbd_backlight_mode: u8,
|
||||
kbd_backlight_modes: Vec<AuraModes>,
|
||||
}
|
||||
|
||||
impl ConfigV212 {
|
||||
pub(crate) fn into_current(self) -> Config {
|
||||
Config {
|
||||
gfx_mode: GfxVendors::Hybrid,
|
||||
gfx_managed: self.gfx_managed,
|
||||
active_profile: self.active_profile,
|
||||
toggle_profiles: self.toggle_profiles,
|
||||
curr_fan_mode: self.power_profile,
|
||||
bat_charge_limit: self.bat_charge_limit,
|
||||
kbd_led_brightness: self.kbd_led_brightness,
|
||||
kbd_backlight_mode: self.kbd_backlight_mode,
|
||||
kbd_backlight_modes: self.kbd_backlight_modes,
|
||||
power_profiles: self.power_profiles,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// for parsing old v2.2.2 config
|
||||
#[derive(Deserialize)]
|
||||
pub(crate) struct ConfigV222 {
|
||||
gfx_managed: bool,
|
||||
bat_charge_limit: u8,
|
||||
active_profile: String,
|
||||
toggle_profiles: Vec<String>,
|
||||
power_profiles: BTreeMap<String, Profile>,
|
||||
power_profile: u8,
|
||||
kbd_led_brightness: u8,
|
||||
kbd_backlight_mode: u8,
|
||||
kbd_backlight_modes: Vec<AuraModes>,
|
||||
}
|
||||
|
||||
impl ConfigV222 {
|
||||
pub(crate) fn into_current(self) -> Config {
|
||||
Config {
|
||||
gfx_mode: GfxVendors::Hybrid,
|
||||
gfx_managed: self.gfx_managed,
|
||||
active_profile: self.active_profile,
|
||||
toggle_profiles: self.toggle_profiles,
|
||||
curr_fan_mode: self.power_profile,
|
||||
bat_charge_limit: self.bat_charge_limit,
|
||||
kbd_led_brightness: self.kbd_led_brightness,
|
||||
kbd_backlight_mode: self.kbd_backlight_mode,
|
||||
kbd_backlight_modes: self.kbd_backlight_modes,
|
||||
power_profiles: self.power_profiles,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub(crate) struct ConfigV301 {
|
||||
pub gfx_managed: bool,
|
||||
pub gfx_nv_mode_is_dedicated: bool,
|
||||
pub active_profile: String,
|
||||
pub toggle_profiles: Vec<String>,
|
||||
// TODO: remove power_profile
|
||||
#[serde(skip)]
|
||||
pub curr_fan_mode: u8,
|
||||
pub bat_charge_limit: u8,
|
||||
pub kbd_led_brightness: u8,
|
||||
pub kbd_backlight_mode: u8,
|
||||
pub kbd_backlight_modes: Vec<AuraModes>,
|
||||
pub power_profiles: BTreeMap<String, Profile>,
|
||||
}
|
||||
|
||||
impl ConfigV301 {
|
||||
pub(crate) fn into_current(self) -> Config {
|
||||
Config {
|
||||
gfx_mode: GfxVendors::Hybrid,
|
||||
gfx_managed: self.gfx_managed,
|
||||
active_profile: self.active_profile,
|
||||
toggle_profiles: self.toggle_profiles,
|
||||
curr_fan_mode: self.curr_fan_mode,
|
||||
bat_charge_limit: self.bat_charge_limit,
|
||||
kbd_led_brightness: self.kbd_led_brightness,
|
||||
kbd_backlight_mode: self.kbd_backlight_mode,
|
||||
kbd_backlight_modes: self.kbd_backlight_modes,
|
||||
power_profiles: self.power_profiles,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,264 +0,0 @@
|
||||
const INIT_STR: &str = "ASUS Tech.Inc.";
|
||||
const PACKET_SIZE: usize = 640;
|
||||
|
||||
// Only these two packets must be 17 bytes
|
||||
const DEV_PAGE: u8 = 0x5e;
|
||||
// These bytes are in [1] position of the array
|
||||
const WRITE: u8 = 0xc0;
|
||||
const INIT: u8 = 0xc2;
|
||||
const SET: u8 = 0xc3;
|
||||
const APPLY: u8 = 0xc4;
|
||||
|
||||
// Used to turn the panel on and off
|
||||
// The next byte can be 0x03 for "on" and 0x00 for "off"
|
||||
const ON_OFF: u8 = 0x04;
|
||||
|
||||
use log::{error, info, warn};
|
||||
use rog_types::{
|
||||
anime_matrix::{
|
||||
AniMeDataBuffer, AniMeImageBuffer, AniMePacketType, ANIME_PANE1_PREFIX, ANIME_PANE2_PREFIX,
|
||||
},
|
||||
error::AuraError,
|
||||
};
|
||||
use rusb::{Device, DeviceHandle};
|
||||
use std::error::Error;
|
||||
use std::time::Duration;
|
||||
use zbus::dbus_interface;
|
||||
|
||||
use crate::GetSupported;
|
||||
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct AnimeSupportedFunctions(pub bool);
|
||||
|
||||
impl GetSupported for CtrlAnimeDisplay {
|
||||
type A = AnimeSupportedFunctions;
|
||||
|
||||
fn get_supported() -> Self::A {
|
||||
AnimeSupportedFunctions(CtrlAnimeDisplay::get_device(0x0b05, 0x193b).is_ok())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CtrlAnimeDisplay {
|
||||
handle: DeviceHandle<rusb::GlobalContext>,
|
||||
}
|
||||
|
||||
//AnimatrixWrite
|
||||
pub trait Dbus {
|
||||
/// Write an image 34x56 pixels. Each pixel is 0-255 greyscale.
|
||||
fn write_image(&self, input: AniMeImageBuffer);
|
||||
|
||||
/// Write a direct stream of data
|
||||
fn write_direct(&self, input: AniMeDataBuffer);
|
||||
|
||||
fn set_on_off(&self, status: bool);
|
||||
|
||||
fn set_boot_on_off(&self, status: bool);
|
||||
}
|
||||
|
||||
impl crate::ZbusAdd for CtrlAnimeDisplay {
|
||||
fn add_to_server(self, server: &mut zbus::ObjectServer) {
|
||||
server
|
||||
.at("/org/asuslinux/Anime", self)
|
||||
.map_err(|err| {
|
||||
warn!("CtrlAnimeDisplay: add_to_server {}", err);
|
||||
err
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
#[dbus_interface(name = "org.asuslinux.Daemon")]
|
||||
impl Dbus for CtrlAnimeDisplay {
|
||||
/// Writes a 34x56 image
|
||||
fn write_image(&self, input: AniMeImageBuffer) {
|
||||
self.write_image_buffer(input)
|
||||
.map_or_else(|err| warn!("{}", err), |()| info!("Writing image to Anime"));
|
||||
}
|
||||
|
||||
/// Writes a data stream of length
|
||||
fn write_direct(&self, input: AniMeDataBuffer) {
|
||||
self.write_data_buffer(input)
|
||||
.map_or_else(|err| warn!("{}", err), |()| info!("Writing data to Anime"));
|
||||
}
|
||||
|
||||
fn set_on_off(&self, status: bool) {
|
||||
let mut buffer = [0u8; PACKET_SIZE];
|
||||
buffer[0] = DEV_PAGE;
|
||||
buffer[1] = WRITE;
|
||||
buffer[2] = ON_OFF;
|
||||
|
||||
if status {
|
||||
buffer[3] = 0x03;
|
||||
} else {
|
||||
buffer[3] = 0x00;
|
||||
}
|
||||
|
||||
self.write_bytes(&buffer);
|
||||
}
|
||||
|
||||
fn set_boot_on_off(&self, status: bool) {
|
||||
let status_str = if status { "on" } else { "off" };
|
||||
|
||||
self.do_set_boot(status).map_or_else(
|
||||
|err| warn!("{}", err),
|
||||
|()| info!("Turning {} the AniMe at boot/shutdown", status_str),
|
||||
);
|
||||
self.do_apply().map_or_else(
|
||||
|err| warn!("{}", err),
|
||||
|()| info!("Turning {} the AniMe at boot/shutdown", status_str),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl CtrlAnimeDisplay {
|
||||
#[inline]
|
||||
pub fn new() -> Result<CtrlAnimeDisplay, Box<dyn Error>> {
|
||||
// We don't expect this ID to ever change
|
||||
let device = CtrlAnimeDisplay::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 ctrl = CtrlAnimeDisplay { handle: device };
|
||||
ctrl.do_initialization()?;
|
||||
|
||||
Ok(ctrl)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
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)
|
||||
}
|
||||
|
||||
/// Should only be used if the bytes you are writing are verified correct
|
||||
#[inline]
|
||||
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),
|
||||
},
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
fn write_data_buffer(&self, buffer: AniMeDataBuffer) -> Result<(), AuraError> {
|
||||
let mut image = AniMePacketType::from(buffer);
|
||||
image[0][..7].copy_from_slice(&ANIME_PANE1_PREFIX);
|
||||
image[1][..7].copy_from_slice(&ANIME_PANE2_PREFIX);
|
||||
|
||||
for row in image.iter() {
|
||||
self.write_bytes(row);
|
||||
}
|
||||
self.do_flush()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write an Animatrix image
|
||||
///
|
||||
/// The expected USB input here is *two* Vectors, 640 bytes in length. The two vectors
|
||||
/// are each one half of the full image write.
|
||||
///
|
||||
/// After each write a flush is written, it is assumed that this tells the device to
|
||||
/// go ahead and display the written bytes
|
||||
///
|
||||
/// # Note:
|
||||
/// The vectors are expected to contain the full sequence of bytes as follows
|
||||
///
|
||||
/// - Write pane 1: 0x5e 0xc0 0x02 0x01 0x00 0x73 0x02 .. <led brightness>
|
||||
/// - Write pane 2: 0x5e 0xc0 0x02 0x74 0x02 0x73 0x02 .. <led brightness>
|
||||
///
|
||||
/// Where led brightness is 0..255, low to high
|
||||
#[inline]
|
||||
fn write_image_buffer(&self, buffer: AniMeImageBuffer) -> Result<(), AuraError> {
|
||||
let mut image = AniMePacketType::from(buffer);
|
||||
image[0][..7].copy_from_slice(&ANIME_PANE1_PREFIX);
|
||||
image[1][..7].copy_from_slice(&ANIME_PANE2_PREFIX);
|
||||
|
||||
for row in image.iter() {
|
||||
self.write_bytes(row);
|
||||
}
|
||||
self.do_flush()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn do_initialization(&self) -> Result<(), AuraError> {
|
||||
let mut init = [0; PACKET_SIZE];
|
||||
init[0] = DEV_PAGE; // This is the USB page we're using throughout
|
||||
for (idx, byte) in INIT_STR.as_bytes().iter().enumerate() {
|
||||
init[idx + 1] = *byte
|
||||
}
|
||||
self.write_bytes(&init);
|
||||
|
||||
// clear the init array and write other init message
|
||||
for ch in init.iter_mut() {
|
||||
*ch = 0;
|
||||
}
|
||||
init[0] = DEV_PAGE; // write it to be sure?
|
||||
init[1] = INIT;
|
||||
|
||||
self.write_bytes(&init);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn do_flush(&self) -> Result<(), AuraError> {
|
||||
let mut flush = [0; PACKET_SIZE];
|
||||
flush[0] = DEV_PAGE;
|
||||
flush[1] = WRITE;
|
||||
flush[2] = 0x03;
|
||||
|
||||
self.write_bytes(&flush);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn do_set_boot(&self, status: bool) -> Result<(), AuraError> {
|
||||
let mut flush = [0; PACKET_SIZE];
|
||||
flush[0] = DEV_PAGE;
|
||||
flush[1] = SET;
|
||||
flush[2] = 0x01;
|
||||
flush[3] = if status { 0x00 } else { 0x80 };
|
||||
|
||||
self.write_bytes(&flush);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn do_apply(&self) -> Result<(), AuraError> {
|
||||
let mut flush = [0; PACKET_SIZE];
|
||||
flush[0] = DEV_PAGE;
|
||||
flush[1] = APPLY;
|
||||
flush[2] = 0x01;
|
||||
flush[3] = 0x80;
|
||||
|
||||
self.write_bytes(&flush);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
use crate::{config::Config, error::RogError, GetSupported};
|
||||
//use crate::dbus::DbusEvents;
|
||||
use log::{info, warn};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use zbus::dbus_interface;
|
||||
|
||||
static BAT_CHARGE_PATH: &str = "/sys/class/power_supply/BAT0/charge_control_end_threshold";
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct ChargeSupportedFunctions {
|
||||
pub charge_level_set: bool,
|
||||
}
|
||||
|
||||
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("/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,352 +0,0 @@
|
||||
use crate::error::RogError;
|
||||
use crate::{
|
||||
config::{Config, Profile},
|
||||
GetSupported,
|
||||
};
|
||||
use log::{info, warn};
|
||||
use rog_types::profile::{FanLevel, ProfileEvent};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use zbus::dbus_interface;
|
||||
|
||||
static FAN_TYPE_1_PATH: &str = "/sys/devices/platform/asus-nb-wmi/throttle_thermal_policy";
|
||||
static FAN_TYPE_2_PATH: &str = "/sys/devices/platform/asus-nb-wmi/fan_boost_mode";
|
||||
static AMD_BOOST_PATH: &str = "/sys/devices/system/cpu/cpufreq/boost";
|
||||
|
||||
pub struct CtrlFanAndCPU {
|
||||
pub path: &'static str,
|
||||
config: Arc<Mutex<Config>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct FanCpuSupportedFunctions {
|
||||
pub stock_fan_modes: bool,
|
||||
pub min_max_freq: bool,
|
||||
pub fan_curve_set: bool,
|
||||
}
|
||||
|
||||
impl GetSupported for CtrlFanAndCPU {
|
||||
type A = FanCpuSupportedFunctions;
|
||||
|
||||
fn get_supported() -> Self::A {
|
||||
FanCpuSupportedFunctions {
|
||||
stock_fan_modes: CtrlFanAndCPU::get_fan_path().is_ok(),
|
||||
min_max_freq: intel_pstate::PState::new().is_ok(),
|
||||
fan_curve_set: rog_fan_curve::Board::from_board_name().is_some(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DbusFanAndCpu {
|
||||
inner: Arc<Mutex<CtrlFanAndCPU>>,
|
||||
}
|
||||
|
||||
impl DbusFanAndCpu {
|
||||
pub fn new(inner: Arc<Mutex<CtrlFanAndCPU>>) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
#[dbus_interface(name = "org.asuslinux.Daemon")]
|
||||
impl DbusFanAndCpu {
|
||||
/// Set profile details
|
||||
fn set_profile(&self, profile: String) {
|
||||
if let Ok(event) = serde_json::from_str(&profile) {
|
||||
if let Ok(mut ctrl) = self.inner.try_lock() {
|
||||
if let Ok(mut cfg) = ctrl.config.clone().try_lock() {
|
||||
cfg.read();
|
||||
ctrl.handle_profile_event(&event, &mut cfg)
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
if let Some(profile) = cfg.power_profiles.get(&cfg.active_profile) {
|
||||
if let Ok(json) = serde_json::to_string(profile) {
|
||||
self.notify_profile(&json)
|
||||
.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) {
|
||||
if let Ok(json) = serde_json::to_string(profile) {
|
||||
self.notify_profile(&json)
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetch the active profile name
|
||||
fn active_profile_name(&mut self) -> String {
|
||||
if let Ok(ctrl) = self.inner.try_lock() {
|
||||
if let Ok(mut cfg) = ctrl.config.try_lock() {
|
||||
cfg.read();
|
||||
return cfg.active_profile.clone();
|
||||
}
|
||||
}
|
||||
"Failed".to_string()
|
||||
}
|
||||
|
||||
/// Fetch the active profile details
|
||||
fn profile(&mut self) -> String {
|
||||
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) {
|
||||
if let Ok(json) = serde_json::to_string(profile) {
|
||||
return json;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"Failed".to_string()
|
||||
}
|
||||
|
||||
fn profiles(&mut self) -> String {
|
||||
if let Ok(ctrl) = self.inner.try_lock() {
|
||||
if let Ok(mut cfg) = ctrl.config.try_lock() {
|
||||
cfg.read();
|
||||
if let Ok(json) = serde_json::to_string(&cfg.power_profiles) {
|
||||
return json;
|
||||
}
|
||||
}
|
||||
}
|
||||
"Failed".to_string()
|
||||
}
|
||||
|
||||
#[dbus_interface(signal)]
|
||||
fn notify_profile(&self, profile: &str) -> zbus::Result<()> {}
|
||||
}
|
||||
|
||||
impl crate::ZbusAdd for DbusFanAndCpu {
|
||||
fn add_to_server(self, server: &mut zbus::ObjectServer) {
|
||||
server
|
||||
.at("/org/asuslinux/Profile", self)
|
||||
.map_err(|err| {
|
||||
warn!("DbusFanAndCpu: add_to_server {}", err);
|
||||
err
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::Reloadable for CtrlFanAndCPU {
|
||||
fn reload(&mut self) -> Result<(), RogError> {
|
||||
if let Ok(mut config) = self.config.clone().try_lock() {
|
||||
let profile = config.active_profile.clone();
|
||||
self.set(&profile, &mut config)?;
|
||||
// info!(
|
||||
// "Reloaded fan mode: {:?}",
|
||||
// FanLevel::from(config.power_profile)
|
||||
// );
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl CtrlFanAndCPU {
|
||||
pub fn new(config: Arc<Mutex<Config>>) -> Result<Self, RogError> {
|
||||
let path = CtrlFanAndCPU::get_fan_path()?;
|
||||
info!("Device has thermal throttle control");
|
||||
Ok(CtrlFanAndCPU { path, config })
|
||||
}
|
||||
|
||||
fn get_fan_path() -> Result<&'static str, RogError> {
|
||||
if Path::new(FAN_TYPE_1_PATH).exists() {
|
||||
Ok(FAN_TYPE_1_PATH)
|
||||
} else if Path::new(FAN_TYPE_2_PATH).exists() {
|
||||
Ok(FAN_TYPE_2_PATH)
|
||||
} else {
|
||||
Err(RogError::MissingFunction(
|
||||
"Fan mode not available, you may require a v5.8.10 series kernel or newer".into(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// 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(&new_profile, config)?;
|
||||
|
||||
info!("Profile was changed: {}", &new_profile);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_fan_mode(&mut self, preset: u8, config: &mut Config) -> Result<(), RogError> {
|
||||
let mode = config.active_profile.clone();
|
||||
let mut fan_ctrl = OpenOptions::new()
|
||||
.write(true)
|
||||
.open(self.path)
|
||||
.map_err(|err| RogError::Path(self.path.into(), err))?;
|
||||
config.read();
|
||||
let mut mode_config = config
|
||||
.power_profiles
|
||||
.get_mut(&mode)
|
||||
.ok_or_else(|| RogError::MissingProfile(mode.clone()))?;
|
||||
config.curr_fan_mode = preset;
|
||||
mode_config.fan_preset = preset;
|
||||
config.write();
|
||||
fan_ctrl
|
||||
.write_all(format!("{}\n", preset).as_bytes())
|
||||
.map_err(|err| RogError::Write(self.path.into(), err))?;
|
||||
info!("Fan mode set to: {:?}", FanLevel::from(preset));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_profile_event(
|
||||
&mut self,
|
||||
event: &ProfileEvent,
|
||||
config: &mut Config,
|
||||
) -> Result<(), RogError> {
|
||||
match event {
|
||||
ProfileEvent::Toggle => self.do_next_profile(config)?,
|
||||
ProfileEvent::ChangeMode(mode) => {
|
||||
self.set_fan_mode(*mode, config)?;
|
||||
let mode = config.active_profile.clone();
|
||||
self.set_pstate_for_fan_mode(&mode, config)?;
|
||||
self.set_fan_curve_for_fan_mode(&mode, config)?;
|
||||
}
|
||||
ProfileEvent::Cli(command) => {
|
||||
let profile_key = match command.profile.as_ref() {
|
||||
Some(k) => k.clone(),
|
||||
None => config.active_profile.clone(),
|
||||
};
|
||||
|
||||
let mut profile = if command.create {
|
||||
config
|
||||
.power_profiles
|
||||
.entry(profile_key.clone())
|
||||
.or_insert_with(Profile::default)
|
||||
} else {
|
||||
config
|
||||
.power_profiles
|
||||
.get_mut(&profile_key)
|
||||
.ok_or_else(|| RogError::MissingProfile(profile_key.clone()))?
|
||||
};
|
||||
|
||||
if command.turbo.is_some() {
|
||||
profile.turbo = command.turbo.unwrap();
|
||||
}
|
||||
if let Some(min_perc) = command.min_percentage {
|
||||
profile.min_percentage = min_perc;
|
||||
}
|
||||
if let Some(max_perc) = command.max_percentage {
|
||||
profile.max_percentage = max_perc;
|
||||
}
|
||||
if let Some(ref preset) = command.fan_preset {
|
||||
profile.fan_preset = preset.into();
|
||||
}
|
||||
if let Some(ref curve) = command.curve {
|
||||
profile.fan_curve = Some(curve.clone());
|
||||
}
|
||||
|
||||
self.set(&profile_key, config)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set(&mut self, profile: &str, config: &mut Config) -> Result<(), RogError> {
|
||||
let mode_config = config
|
||||
.power_profiles
|
||||
.get(profile)
|
||||
.ok_or_else(|| RogError::MissingProfile(profile.into()))?;
|
||||
let mut fan_ctrl = OpenOptions::new()
|
||||
.write(true)
|
||||
.open(self.path)
|
||||
.map_err(|err| RogError::Path(self.path.into(), err))?;
|
||||
config.curr_fan_mode = mode_config.fan_preset;
|
||||
fan_ctrl
|
||||
.write_all(format!("{}\n", mode_config.fan_preset).as_bytes())
|
||||
.map_err(|err| RogError::Write(self.path.into(), err))?;
|
||||
|
||||
self.set_pstate_for_fan_mode(profile, config)?;
|
||||
self.set_fan_curve_for_fan_mode(profile, config)?;
|
||||
|
||||
config.active_profile = profile.into();
|
||||
|
||||
config.write();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_pstate_for_fan_mode(&self, mode: &str, config: &mut Config) -> Result<(), RogError> {
|
||||
info!("Setting pstate");
|
||||
let mode_config = config
|
||||
.power_profiles
|
||||
.get(mode)
|
||||
.ok_or_else(|| RogError::MissingProfile(mode.into()))?;
|
||||
|
||||
// Set CPU pstate
|
||||
if let Ok(pstate) = intel_pstate::PState::new() {
|
||||
pstate.set_min_perf_pct(mode_config.min_percentage)?;
|
||||
pstate.set_max_perf_pct(mode_config.max_percentage)?;
|
||||
pstate.set_no_turbo(!mode_config.turbo)?;
|
||||
info!(
|
||||
"Intel CPU Power: min: {}%, max: {}%, turbo: {}",
|
||||
mode_config.min_percentage, mode_config.max_percentage, mode_config.turbo
|
||||
);
|
||||
} else {
|
||||
info!("Setting pstate for AMD CPU");
|
||||
// must be AMD CPU
|
||||
let mut file = OpenOptions::new()
|
||||
.write(true)
|
||||
.open(AMD_BOOST_PATH)
|
||||
.map_err(|err| RogError::Path(self.path.into(), err))?;
|
||||
|
||||
let boost = if mode_config.turbo { "1" } else { "0" }; // opposite of Intel
|
||||
file.write_all(boost.as_bytes())
|
||||
.map_err(|err| RogError::Write(AMD_BOOST_PATH.into(), err))?;
|
||||
info!("AMD CPU Turbo: {}", boost);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_fan_curve_for_fan_mode(&self, mode: &str, config: &Config) -> Result<(), RogError> {
|
||||
let mode_config = &config
|
||||
.power_profiles
|
||||
.get(mode)
|
||||
.ok_or_else(|| RogError::MissingProfile(mode.into()))?;
|
||||
|
||||
if let Some(ref curve) = mode_config.fan_curve {
|
||||
use rog_fan_curve::{Board, Fan};
|
||||
if let Some(board) = Board::from_board_name() {
|
||||
curve.apply(board, Fan::Cpu)?;
|
||||
curve.apply(board, Fan::Gpu)?;
|
||||
} else {
|
||||
warn!("Fan curve unsupported on this board.")
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
use std::fmt;
|
||||
use std::{error, process::ExitStatus};
|
||||
|
||||
use crate::error::RogError;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum GfxError {
|
||||
ParseVendor,
|
||||
Bus(String, std::io::Error),
|
||||
DisplayManagerAction(String, ExitStatus),
|
||||
DisplayManagerTimeout(String),
|
||||
GsyncModeActive,
|
||||
}
|
||||
|
||||
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::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"
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for GfxError {}
|
||||
|
||||
impl From<GfxError> for RogError {
|
||||
fn from(err: GfxError) -> Self {
|
||||
RogError::GfxSwitching(err)
|
||||
}
|
||||
}
|
||||
@@ -1,591 +0,0 @@
|
||||
use ctrl_gfx::error::GfxError;
|
||||
use ctrl_gfx::*;
|
||||
use ctrl_rog_bios::CtrlRogBios;
|
||||
use log::{error, info, warn};
|
||||
use logind_zbus::{
|
||||
types::{SessionClass, SessionInfo, SessionType},
|
||||
ManagerProxy, SessionProxy,
|
||||
};
|
||||
use rog_types::gfx_vendors::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 zbus::{dbus_interface, Connection};
|
||||
|
||||
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>>>>,
|
||||
}
|
||||
|
||||
trait Dbus {
|
||||
fn vendor(&self) -> String;
|
||||
fn power(&self) -> String;
|
||||
fn set_vendor(&mut self, vendor: String);
|
||||
fn notify_gfx(&self, vendor: &str) -> zbus::Result<()>;
|
||||
fn notify_action(&self, action: &str) -> zbus::Result<()>;
|
||||
}
|
||||
|
||||
#[dbus_interface(name = "org.asuslinux.Daemon")]
|
||||
impl Dbus for CtrlGraphics {
|
||||
fn vendor(&self) -> String {
|
||||
self.get_gfx_mode()
|
||||
.map(|gfx| gfx.into())
|
||||
.unwrap_or_else(|err| format!("Get vendor failed: {}", err))
|
||||
}
|
||||
|
||||
fn power(&self) -> String {
|
||||
Self::get_runtime_status().unwrap_or_else(|err| format!("Get power status failed: {}", err))
|
||||
}
|
||||
|
||||
fn set_vendor(&mut self, vendor: String) {
|
||||
if let Ok(tmp) = GfxVendors::from_str(&vendor) {
|
||||
info!("GFX: Switching gfx mode to {}", vendor);
|
||||
let msg = self.set_gfx_config(tmp).unwrap_or_else(|err| {
|
||||
error!("GFX: {}", err);
|
||||
format!("Failed: {}", err.to_string())
|
||||
});
|
||||
self.notify_gfx(&vendor)
|
||||
.unwrap_or_else(|err| warn!("GFX: {}", err));
|
||||
self.notify_action(&msg)
|
||||
.unwrap_or_else(|err| warn!("GFX: {}", err));
|
||||
}
|
||||
}
|
||||
|
||||
#[dbus_interface(signal)]
|
||||
fn notify_gfx(&self, vendor: &str) -> zbus::Result<()> {}
|
||||
|
||||
#[dbus_interface(signal)]
|
||||
fn notify_action(&self, action: &str) -> zbus::Result<()> {}
|
||||
}
|
||||
|
||||
impl ZbusAdd for CtrlGraphics {
|
||||
fn add_to_server(self, server: &mut zbus::ObjectServer) {
|
||||
server
|
||||
.at("/org/asuslinux/Gfx", self)
|
||||
.map_err(|err| {
|
||||
warn!("GFX: CtrlGraphics: add_to_server {}", err);
|
||||
err
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
fn save_gfx_mode(vendor: GfxVendors, config: Arc<Mutex<Config>>) {
|
||||
if let Ok(mut config) = config.lock() {
|
||||
config.gfx_mode = vendor;
|
||||
config.write();
|
||||
}
|
||||
// TODO: Error here
|
||||
}
|
||||
|
||||
/// 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() {
|
||||
return Ok(config.gfx_mode);
|
||||
}
|
||||
// TODO: Error here
|
||||
Ok(GfxVendors::Hybrid)
|
||||
}
|
||||
|
||||
fn get_runtime_status() -> Result<String, RogError> {
|
||||
const PATH: &str = "/sys/bus/pci/devices/0000:01:00.0/power/runtime_status";
|
||||
let buf = std::fs::read_to_string(PATH).map_err(|err| RogError::Read(PATH.into(), err))?;
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
|
||||
fn write_modprobe_conf() -> Result<(), RogError> {
|
||||
info!("GFX: Writing {}", MODPROBE_PATH);
|
||||
|
||||
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(MODPROBE_BASE)
|
||||
.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))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn log_uses_of_nvidia() {
|
||||
// lsof /dev/nvidia*
|
||||
let mut cmd = Command::new("lsof");
|
||||
cmd.arg("/dev/nvidia*");
|
||||
|
||||
match cmd.output() {
|
||||
Ok(output) => {
|
||||
if !output.status.success() {
|
||||
error!(
|
||||
"Failed to list uses of nvidia devices: {}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
} else if output.status.success() {
|
||||
warn!("GFX: {}", String::from_utf8_lossy(&output.stdout));
|
||||
}
|
||||
}
|
||||
Err(err) => error!("GFX: Failed to list uses of nvidia devices: {}", err),
|
||||
}
|
||||
}
|
||||
|
||||
fn do_driver_action(driver: &str, action: &str) -> Result<(), RogError> {
|
||||
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| RogError::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("Permission denied\n".as_bytes()) {
|
||||
let msg = format!(
|
||||
"{} {} failed: {:?}",
|
||||
action,
|
||||
driver,
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
warn!("GFX: {}", msg);
|
||||
warn!("GFX: It may be safe to ignore the above error, run `lsmod |grep nvidia` to confirm modules loaded");
|
||||
return Ok(());
|
||||
}
|
||||
if count >= MAX_TRIES {
|
||||
let msg = format!(
|
||||
"{} {} failed: {:?}",
|
||||
action,
|
||||
driver,
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
return Err(RogError::Modprobe(msg));
|
||||
}
|
||||
} else if output.status.success() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
count += 1;
|
||||
std::thread::sleep(std::time::Duration::from_millis(250));
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
/// 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
|
||||
pub fn do_vendor_tasks(
|
||||
vendor: GfxVendors,
|
||||
devices: &[GraphicsDevice],
|
||||
bus: &PciBus,
|
||||
) -> Result<(), RogError> {
|
||||
Self::write_xorg_conf(vendor)?;
|
||||
Self::write_modprobe_conf()?; // TODO: Not required here, should put in startup?
|
||||
|
||||
// Rescan before doing remove or add drivers
|
||||
bus.rescan()
|
||||
.map_err(|err| GfxError::Bus("bus rescan error".into(), err))?;
|
||||
|
||||
match vendor {
|
||||
GfxVendors::Nvidia | GfxVendors::Hybrid | GfxVendors::Compute => {
|
||||
for driver in NVIDIA_DRIVERS.iter() {
|
||||
Self::do_driver_action(driver, "modprobe").map_err(|err| {
|
||||
Self::log_uses_of_nvidia();
|
||||
err
|
||||
})?;
|
||||
}
|
||||
}
|
||||
// TODO: compute mode, needs different setup
|
||||
// GfxVendors::Compute => {}
|
||||
GfxVendors::Integrated => {
|
||||
for driver in NVIDIA_DRIVERS.iter() {
|
||||
Self::do_driver_action(driver, "rmmod")?;
|
||||
}
|
||||
Self::unbind_remove_nvidia(&devices)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn graphical_session_active(
|
||||
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 => {
|
||||
if session_proxy.get_active()? {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
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>,
|
||||
) -> 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) {
|
||||
warn!("GFX: Sessions list changed");
|
||||
warn!("GFX: Old list:\n{:?}\nNew list:\n{:?}", &sessions, &tmp);
|
||||
sessions = tmp;
|
||||
}
|
||||
|
||||
if !Self::graphical_session_active(&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: all graphical user sessions ended, continuing");
|
||||
Self::do_display_manager_action("stop")?;
|
||||
|
||||
match Self::wait_display_manager_state("inactive") {
|
||||
Ok(_) => info!("GFX: display-manager stopped"),
|
||||
Err(err) => {
|
||||
warn!("GFX: {}", err);
|
||||
warn!("GFX: Retry stop display manager");
|
||||
Self::do_display_manager_action("stop")?;
|
||||
Self::wait_display_manager_state("inactive")?;
|
||||
}
|
||||
}
|
||||
|
||||
Self::do_vendor_tasks(vendor, &devices, &bus)?;
|
||||
Self::do_display_manager_action("start")?;
|
||||
|
||||
if Self::wait_display_manager_state("active").is_err() {
|
||||
error!("GFX: display-manager failed to start normally, attempting restart");
|
||||
Self::do_display_manager_action("restart")?;
|
||||
Self::wait_display_manager_state("active")?;
|
||||
}
|
||||
info!("GFX: display-manager started");
|
||||
|
||||
let v: &str = vendor.into();
|
||||
info!("GFX: Graphics mode changed to {} successfully", v);
|
||||
Ok(format!("Graphics mode changed to {} successfully", v))
|
||||
}
|
||||
|
||||
/// 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<String, RogError> {
|
||||
if let Ok(gsync) = CtrlRogBios::get_gfx_mode() {
|
||||
if gsync == 1 {
|
||||
return Err(GfxError::GsyncModeActive.into());
|
||||
}
|
||||
}
|
||||
|
||||
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: {}", err);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
// Save selected mode in case of reboot
|
||||
Self::save_gfx_mode(vendor, self.config.clone());
|
||||
|
||||
let _join: JoinHandle<()> = std::thread::spawn(move || {
|
||||
Self::fire_starter(vendor, devices, bus, rx)
|
||||
.map_err(|err| {
|
||||
error!("GFX: {}", err);
|
||||
})
|
||||
.ok();
|
||||
// clear the tx/rx when done
|
||||
if let Ok(mut lock) = killer.try_lock() {
|
||||
*lock = None;
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: undo if failed? Save last mode, catch errors...
|
||||
let v: &str = vendor.into();
|
||||
Ok(format!("Graphics mode changed to {} successfully", v))
|
||||
}
|
||||
|
||||
// if CtrlRogBios::has_dedicated_gfx_toggle() {
|
||||
// if let Ok(config) = self.config.clone().try_lock() {
|
||||
// // Switch to dedicated if config says to do so
|
||||
// if config.gfx_nv_mode_is_dedicated && vendor == GfxVendors::Nvidia {
|
||||
// CtrlRogBios::set_gfx_mode(true)
|
||||
// .unwrap_or_else(|err| warn!("GFX: Gfx controller: {}", err));
|
||||
// } else if let Ok(ded) = CtrlRogBios::get_gfx_mode() {
|
||||
// // otherwise if switching to non-Nvidia mode turn off dedicated mode
|
||||
// if ded == 1 && vendor != GfxVendors::Nvidia {
|
||||
// CtrlRogBios::set_gfx_mode(false)
|
||||
// .unwrap_or_else(|err| warn!("GFX: Gfx controller: {}", err));
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
fn auto_power(&mut self) -> Result<(), RogError> {
|
||||
let vendor = self.get_gfx_mode()?;
|
||||
let devices = self.nvidia.clone();
|
||||
let bus = self.bus.clone();
|
||||
Self::do_vendor_tasks(vendor, &devices, &bus)?;
|
||||
Self::toggle_fallback_service(vendor)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
pub mod error;
|
||||
|
||||
pub mod gfx;
|
||||
|
||||
pub mod system;
|
||||
|
||||
const NVIDIA_DRIVERS: [&str; 4] = ["nvidia_drm", "nvidia_modeset", "nvidia_uvm", "nvidia"];
|
||||
|
||||
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
|
||||
options nvidia-drm modeset=1
|
||||
"#;
|
||||
|
||||
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"
|
||||
Option "AllowExternalGpus""#;
|
||||
|
||||
static PRIMARY_GPU_NVIDIA: &[u8] = br#"
|
||||
Option "PrimaryGPU" "true""#;
|
||||
|
||||
static PRIMARY_GPU_END: &[u8] = br#"
|
||||
EndSection"#;
|
||||
@@ -1,156 +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 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,569 +0,0 @@
|
||||
// Only these two packets must be 17 bytes
|
||||
static LED_APPLY: [u8; 17] = [0x5d, 0xb4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
||||
static LED_SET: [u8; 17] = [0x5d, 0xb5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
||||
|
||||
static KBD_BRIGHT_PATH: &str = "/sys/class/leds/asus::kbd_backlight/brightness";
|
||||
|
||||
use crate::{
|
||||
config::Config,
|
||||
error::RogError,
|
||||
laptops::{match_laptop, HELP_ADDRESS},
|
||||
};
|
||||
use log::{error, info, warn};
|
||||
use rog_types::{
|
||||
aura_brightness_bytes,
|
||||
aura_modes::{AuraModes, PER_KEY},
|
||||
fancy::KeyColourArray,
|
||||
LED_MSG_LEN,
|
||||
};
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::{Read, Write};
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use zbus::dbus_interface;
|
||||
|
||||
use crate::GetSupported;
|
||||
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct LedSupportedFunctions {
|
||||
pub brightness_set: bool,
|
||||
pub stock_led_modes: Option<Vec<u8>>,
|
||||
pub per_key_led_mode: bool,
|
||||
}
|
||||
|
||||
impl GetSupported for CtrlKbdBacklight {
|
||||
type A = LedSupportedFunctions;
|
||||
|
||||
fn get_supported() -> Self::A {
|
||||
// let mode = <&str>::from(&<AuraModes>::from(*mode));
|
||||
let mut stock_led_modes = None;
|
||||
let mut per_key_led_mode = false;
|
||||
if let Some(laptop) = match_laptop() {
|
||||
let modes = laptop.supported_modes().to_vec();
|
||||
if modes.contains(&PER_KEY) {
|
||||
per_key_led_mode = true;
|
||||
let modes = modes.iter().filter(|x| **x != PER_KEY).copied().collect();
|
||||
stock_led_modes = Some(modes);
|
||||
} else {
|
||||
stock_led_modes = Some(modes);
|
||||
}
|
||||
}
|
||||
|
||||
LedSupportedFunctions {
|
||||
brightness_set: CtrlKbdBacklight::get_kbd_bright_path().is_ok(),
|
||||
stock_led_modes,
|
||||
per_key_led_mode,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CtrlKbdBacklight {
|
||||
led_node: Option<String>,
|
||||
#[allow(dead_code)]
|
||||
kbd_node: Option<String>,
|
||||
pub bright_node: String,
|
||||
supported_modes: Vec<u8>,
|
||||
flip_effect_write: bool,
|
||||
config: Arc<Mutex<Config>>,
|
||||
}
|
||||
|
||||
pub struct DbusKbdBacklight {
|
||||
inner: Arc<Mutex<CtrlKbdBacklight>>,
|
||||
}
|
||||
|
||||
impl DbusKbdBacklight {
|
||||
pub fn new(inner: Arc<Mutex<CtrlKbdBacklight>>) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
trait Dbus {
|
||||
fn set_led(&mut self, data: String);
|
||||
fn ledmode(&self) -> String;
|
||||
fn notify_led(&self, data: &str) -> zbus::Result<()>;
|
||||
}
|
||||
|
||||
impl crate::ZbusAdd for DbusKbdBacklight {
|
||||
fn add_to_server(self, server: &mut zbus::ObjectServer) {
|
||||
server
|
||||
.at("/org/asuslinux/Led", self)
|
||||
.map_err(|err| {
|
||||
error!("DbusKbdBacklight: add_to_server {}", err);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
#[dbus_interface(name = "org.asuslinux.Daemon")]
|
||||
impl DbusKbdBacklight {
|
||||
fn set_led_mode(&mut self, data: String) {
|
||||
if let Ok(data) = serde_json::from_str(&data) {
|
||||
if let Ok(mut ctrl) = self.inner.try_lock() {
|
||||
if let Ok(mut cfg) = ctrl.config.clone().try_lock() {
|
||||
match &data {
|
||||
AuraModes::PerKey(_) => {
|
||||
ctrl.do_command(data, &mut cfg)
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
}
|
||||
_ => {
|
||||
if let Ok(json) = serde_json::to_string(&data) {
|
||||
match ctrl.do_command(data, &mut cfg) {
|
||||
Ok(_) => {
|
||||
self.notify_led(&json).ok();
|
||||
}
|
||||
Err(err) => {
|
||||
warn!("{}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
warn!("SetKeyBacklight could not deserialise");
|
||||
}
|
||||
}
|
||||
|
||||
fn next_led_mode(&self) {
|
||||
if let Ok(mut ctrl) = self.inner.try_lock() {
|
||||
if let Ok(mut cfg) = ctrl.config.clone().try_lock() {
|
||||
ctrl.toggle_mode(false, &mut cfg)
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
|
||||
if let Some(mode) = cfg.get_led_mode_data(cfg.kbd_backlight_mode) {
|
||||
if let Ok(json) = serde_json::to_string(&mode) {
|
||||
self.notify_led(&json)
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn prev_led_mode(&self) {
|
||||
if let Ok(mut ctrl) = self.inner.try_lock() {
|
||||
if let Ok(mut cfg) = ctrl.config.clone().try_lock() {
|
||||
ctrl.toggle_mode(true, &mut cfg)
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
|
||||
if let Some(mode) = cfg.get_led_mode_data(cfg.kbd_backlight_mode) {
|
||||
if let Ok(json) = serde_json::to_string(&mode) {
|
||||
self.notify_led(&json)
|
||||
.unwrap_or_else(|err| warn!("{}", err));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the current mode data
|
||||
fn led_mode(&self) -> String {
|
||||
if let Ok(ctrl) = self.inner.try_lock() {
|
||||
if let Ok(cfg) = ctrl.config.clone().try_lock() {
|
||||
if let Some(mode) = cfg.get_led_mode_data(cfg.kbd_backlight_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
|
||||
fn led_modes(&self) -> String {
|
||||
if let Ok(ctrl) = self.inner.try_lock() {
|
||||
if let Ok(cfg) = ctrl.config.clone().try_lock() {
|
||||
if let Ok(json) = serde_json::to_string(&cfg.kbd_backlight_modes) {
|
||||
return json;
|
||||
}
|
||||
}
|
||||
}
|
||||
warn!("SetKeyBacklight could not deserialise");
|
||||
"SetKeyBacklight could not deserialise".to_string()
|
||||
}
|
||||
|
||||
/// Return the current LED brightness
|
||||
fn led_brightness(&self) -> i8 {
|
||||
if let Ok(ctrl) = self.inner.try_lock() {
|
||||
if let Ok(cfg) = ctrl.config.clone().try_lock() {
|
||||
return cfg.kbd_led_brightness as i8;
|
||||
}
|
||||
}
|
||||
warn!("SetKeyBacklight could not deserialise");
|
||||
-1
|
||||
}
|
||||
|
||||
#[dbus_interface(signal)]
|
||||
fn notify_led(&self, data: &str) -> zbus::Result<()>;
|
||||
}
|
||||
|
||||
impl crate::Reloadable for CtrlKbdBacklight {
|
||||
fn reload(&mut self) -> Result<(), RogError> {
|
||||
// set current mode (if any)
|
||||
if let Ok(mut config) = self.config.clone().try_lock() {
|
||||
if self.supported_modes.len() > 1 {
|
||||
if self.supported_modes.contains(&config.kbd_backlight_mode) {
|
||||
let mode = config
|
||||
.get_led_mode_data(config.kbd_backlight_mode)
|
||||
.ok_or(RogError::NotSupported)?
|
||||
.to_owned();
|
||||
self.write_mode(&mode)?;
|
||||
info!("Reloaded last used mode");
|
||||
} else {
|
||||
warn!(
|
||||
"An unsupported mode was set: {}, reset to first mode available",
|
||||
<&str>::from(&<AuraModes>::from(config.kbd_backlight_mode))
|
||||
);
|
||||
for (idx, mode) in config.kbd_backlight_modes.iter_mut().enumerate() {
|
||||
if !self.supported_modes.contains(&mode.into()) {
|
||||
config.kbd_backlight_modes.remove(idx);
|
||||
config.write();
|
||||
break;
|
||||
}
|
||||
}
|
||||
config.kbd_backlight_mode = self.supported_modes[0];
|
||||
// TODO: do a recursive call with a boxed dyn future later
|
||||
let mode = config
|
||||
.get_led_mode_data(config.kbd_backlight_mode)
|
||||
.ok_or(RogError::NotSupported)?
|
||||
.to_owned();
|
||||
self.write_mode(&mode)?;
|
||||
info!("Reloaded last used mode");
|
||||
}
|
||||
}
|
||||
|
||||
// Reload brightness
|
||||
let bright = config.kbd_led_brightness;
|
||||
let bytes = aura_brightness_bytes(bright);
|
||||
self.write_bytes(&bytes)?;
|
||||
info!("Reloaded last used brightness");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::CtrlTask for CtrlKbdBacklight {
|
||||
fn do_task(&mut self) -> Result<(), 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))?;
|
||||
if let Some(num) = char::from(buf[0]).to_digit(10) {
|
||||
if let Ok(mut config) = self.config.clone().try_lock() {
|
||||
if config.kbd_led_brightness != num as u8 {
|
||||
config.read();
|
||||
config.kbd_led_brightness = num as u8;
|
||||
config.write();
|
||||
}
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
Err(RogError::ParseLED)
|
||||
}
|
||||
}
|
||||
|
||||
impl CtrlKbdBacklight {
|
||||
#[inline]
|
||||
pub fn new(
|
||||
id_product: &str,
|
||||
condev_iface: Option<&String>,
|
||||
supported_modes: Vec<u8>,
|
||||
config: Arc<Mutex<Config>>,
|
||||
) -> Result<Self, RogError> {
|
||||
// TODO: return error if *all* nodes are None
|
||||
let led_node = Self::get_node_failover(id_product, None, Self::scan_led_node).map_or_else(
|
||||
|err| {
|
||||
warn!("led_node: {}", err);
|
||||
None
|
||||
},
|
||||
Some,
|
||||
);
|
||||
|
||||
let kbd_node = Self::get_node_failover(id_product, condev_iface, Self::scan_kbd_node)
|
||||
.map_or_else(
|
||||
|err| {
|
||||
warn!("kbd_node: {}", err);
|
||||
None
|
||||
},
|
||||
Some,
|
||||
);
|
||||
|
||||
let bright_node = Self::get_kbd_bright_path();
|
||||
|
||||
if led_node.is_none() && kbd_node.is_none() && Self::get_kbd_bright_path().is_err() {
|
||||
return Err(RogError::MissingFunction(
|
||||
"All keyboard features missing, you may require a v5.11 series kernel or newer"
|
||||
.into(),
|
||||
));
|
||||
}
|
||||
|
||||
let ctrl = CtrlKbdBacklight {
|
||||
// Using `ok` here so we can continue without keyboard features but
|
||||
// still get brightness control at least... maybe...
|
||||
led_node,
|
||||
kbd_node,
|
||||
// TODO: Check for existance
|
||||
bright_node: bright_node?.to_owned(),
|
||||
supported_modes,
|
||||
flip_effect_write: false,
|
||||
config,
|
||||
};
|
||||
Ok(ctrl)
|
||||
}
|
||||
|
||||
fn get_kbd_bright_path() -> Result<&'static str, RogError> {
|
||||
if Path::new(KBD_BRIGHT_PATH).exists() {
|
||||
Ok(KBD_BRIGHT_PATH)
|
||||
} else {
|
||||
Err(RogError::MissingFunction(
|
||||
"Keyboard features missing, you may require a v5.11 series kernel or newer".into(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn get_node_failover(
|
||||
id_product: &str,
|
||||
iface: Option<&String>,
|
||||
fun: fn(&str, Option<&String>) -> Result<String, RogError>,
|
||||
) -> Result<String, RogError> {
|
||||
match fun(id_product, iface) {
|
||||
Ok(o) => return Ok(o),
|
||||
Err(e) => {
|
||||
warn!("Looking for node: {}", e.to_string());
|
||||
}
|
||||
}
|
||||
Err(RogError::NotFound(format!("{}, {:?}", id_product, iface)))
|
||||
}
|
||||
|
||||
fn scan_led_node(id_product: &str, _: Option<&String>) -> 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
warn!("Did not find a hidraw node for LED control, your device may be unsupported or require a kernel patch, see: {}", HELP_ADDRESS);
|
||||
Err(RogError::MissingFunction(
|
||||
"ASUS LED device node not found".into(),
|
||||
))
|
||||
}
|
||||
|
||||
fn scan_kbd_node(id_product: &str, iface: Option<&String>) -> Result<String, RogError> {
|
||||
let mut enumerator = udev::Enumerator::new().map_err(|err| {
|
||||
warn!("{}", err);
|
||||
RogError::Udev("enumerator failed".into(), err)
|
||||
})?;
|
||||
enumerator.match_subsystem("input").map_err(|err| {
|
||||
warn!("{}", err);
|
||||
RogError::Udev("match_subsystem failed".into(), err)
|
||||
})?;
|
||||
enumerator
|
||||
.match_property("ID_MODEL_ID", id_product)
|
||||
.map_err(|err| {
|
||||
warn!("{}", err);
|
||||
RogError::Udev("match_property failed".into(), err)
|
||||
})?;
|
||||
|
||||
for device in enumerator
|
||||
.scan_devices()
|
||||
.map_err(|err| {
|
||||
warn!("{}", err);
|
||||
err
|
||||
})
|
||||
.map_err(|err| {
|
||||
warn!("{}", err);
|
||||
RogError::Udev("scan_devices failed".into(), err)
|
||||
})?
|
||||
{
|
||||
if let Some(dev_node) = device.devnode() {
|
||||
if let Some(inum) = device.property_value("ID_USB_INTERFACE_NUM") {
|
||||
if let Some(iface) = iface {
|
||||
if inum == iface.as_str() {
|
||||
info!("Using device at: {:?} for keyboard polling", dev_node);
|
||||
return Ok(dev_node.to_string_lossy().to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
warn!("Did not find keyboard consumer device node, if expected functions are missing please file an issue at {}", HELP_ADDRESS);
|
||||
Err(RogError::MissingFunction(
|
||||
"ASUS keyboard 'Consumer Device' node not found".into(),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn do_command(&mut self, mode: AuraModes, config: &mut Config) -> Result<(), RogError> {
|
||||
self.set_and_save(mode, config)
|
||||
}
|
||||
|
||||
/// 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: AuraModes, config: &mut Config) -> Result<(), RogError> {
|
||||
match mode {
|
||||
AuraModes::LedBrightness(n) => {
|
||||
let bytes: [u8; LED_MSG_LEN] = (&mode).into();
|
||||
self.write_bytes(&bytes)?;
|
||||
config.read();
|
||||
config.kbd_led_brightness = n;
|
||||
config.write();
|
||||
info!("LED brightness set to {:#?}", n);
|
||||
}
|
||||
AuraModes::PerKey(v) => {
|
||||
if v.is_empty() || v[0].is_empty() {
|
||||
let bytes = KeyColourArray::get_init_msg();
|
||||
self.write_bytes(&bytes)?;
|
||||
} else {
|
||||
self.write_effect(&v)?;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
config.read();
|
||||
let mode_num: u8 = u8::from(&mode);
|
||||
self.write_mode(&mode)?;
|
||||
config.kbd_backlight_mode = mode_num;
|
||||
config.set_mode_data(mode);
|
||||
config.write();
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn toggle_mode(&mut self, reverse: bool, config: &mut Config) -> Result<(), RogError> {
|
||||
let current = config.kbd_backlight_mode;
|
||||
if let Some(idx) = self.supported_modes.iter().position(|v| *v == current) {
|
||||
let mut idx = idx;
|
||||
// goes past end of array
|
||||
if reverse {
|
||||
if idx == 0 {
|
||||
idx = self.supported_modes.len() - 1;
|
||||
} else {
|
||||
idx -= 1;
|
||||
}
|
||||
} else {
|
||||
idx += 1;
|
||||
if idx == self.supported_modes.len() {
|
||||
idx = 0;
|
||||
}
|
||||
}
|
||||
let next = self.supported_modes[idx];
|
||||
|
||||
config.read();
|
||||
if let Some(data) = config.get_led_mode_data(next) {
|
||||
self.write_mode(&data)?;
|
||||
config.kbd_backlight_mode = next;
|
||||
}
|
||||
config.write();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn write_mode(&mut self, mode: &AuraModes) -> Result<(), RogError> {
|
||||
let mode_num: u8 = u8::from(mode);
|
||||
if !self.supported_modes.contains(&mode_num) {
|
||||
return Err(RogError::NotSupported);
|
||||
}
|
||||
match mode {
|
||||
AuraModes::PerKey(v) => {
|
||||
if v.is_empty() || v[0].is_empty() {
|
||||
let bytes = KeyColourArray::get_init_msg();
|
||||
self.write_bytes(&bytes)?;
|
||||
} else {
|
||||
self.write_effect(v)?;
|
||||
}
|
||||
}
|
||||
AuraModes::MultiStatic(_) | AuraModes::MultiBreathe(_) => {
|
||||
let bytes: [[u8; LED_MSG_LEN]; 4] = mode.into();
|
||||
for array in bytes.iter() {
|
||||
self.write_bytes(array)?;
|
||||
}
|
||||
self.write_bytes(&LED_SET)?;
|
||||
// Changes won't persist unless apply is set
|
||||
self.write_bytes(&LED_APPLY)?;
|
||||
return Ok(());
|
||||
}
|
||||
_ => {
|
||||
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,344 +0,0 @@
|
||||
use crate::{config::Config, error::RogError, GetSupported};
|
||||
use log::{error, info, warn};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
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;
|
||||
|
||||
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>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct RogBiosSupportedFunctions {
|
||||
pub post_sound_toggle: bool,
|
||||
pub dedicated_gfx_toggle: bool,
|
||||
}
|
||||
|
||||
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("/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() {
|
||||
if let Ok(l) = line {
|
||||
if !modules.contains(&l.as_ref()) {
|
||||
buf.append(&mut l.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,54 +0,0 @@
|
||||
use log::warn;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use zbus::dbus_interface;
|
||||
|
||||
use crate::{
|
||||
ctrl_anime::{AnimeSupportedFunctions, CtrlAnimeDisplay},
|
||||
ctrl_charge::{ChargeSupportedFunctions, CtrlCharge},
|
||||
ctrl_fan_cpu::{CtrlFanAndCPU, FanCpuSupportedFunctions},
|
||||
ctrl_leds::{CtrlKbdBacklight, LedSupportedFunctions},
|
||||
ctrl_rog_bios::{CtrlRogBios, RogBiosSupportedFunctions},
|
||||
GetSupported,
|
||||
};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
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) -> String {
|
||||
serde_json::to_string_pretty(self).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::ZbusAdd for SupportedFunctions {
|
||||
fn add_to_server(self, server: &mut zbus::ObjectServer) {
|
||||
server
|
||||
.at("/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 {
|
||||
keyboard_led: CtrlKbdBacklight::get_supported(),
|
||||
anime_ctrl: CtrlAnimeDisplay::get_supported(),
|
||||
charge_ctrl: CtrlCharge::get_supported(),
|
||||
fan_cpu_ctrl: CtrlFanAndCPU::get_supported(),
|
||||
rog_bios_ctrl: CtrlRogBios::get_supported(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,196 +0,0 @@
|
||||
use daemon::ctrl_charge::CtrlCharge;
|
||||
use daemon::ctrl_fan_cpu::{CtrlFanAndCPU, DbusFanAndCpu};
|
||||
use daemon::ctrl_leds::{CtrlKbdBacklight, DbusKbdBacklight};
|
||||
use daemon::laptops::match_laptop;
|
||||
use daemon::{
|
||||
config::Config, ctrl_supported::SupportedFunctions, laptops::print_board_info, GetSupported,
|
||||
};
|
||||
use daemon::{ctrl_anime::CtrlAnimeDisplay, ctrl_gfx::gfx::CtrlGraphics};
|
||||
|
||||
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;
|
||||
|
||||
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-dbus v{}", rog_dbus::VERSION);
|
||||
info!("rog-types v{}", rog_types::VERSION);
|
||||
|
||||
start_daemon()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Timing is such that:
|
||||
// - interrupt write is minimum 1ms (sometimes lower)
|
||||
// - read interrupt must timeout, minimum of 1ms
|
||||
// - for a single usb packet, 2ms total.
|
||||
// - to maintain constant times of 1ms, per-key colours should use
|
||||
// the effect endpoint so that the complete colour block is written
|
||||
// as fast as 1ms per row of the matrix inside it. (10ms total time)
|
||||
fn start_daemon() -> Result<(), Box<dyn Error>> {
|
||||
let supported = SupportedFunctions::get_supported();
|
||||
print_board_info();
|
||||
println!("{}", serde_json::to_string_pretty(&supported).unwrap());
|
||||
|
||||
let laptop = match_laptop();
|
||||
let config = if let Some(laptop) = laptop.as_ref() {
|
||||
Config::load(laptop.supported_modes())
|
||||
} else {
|
||||
Config::load(&[])
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
supported.add_to_server(&mut object_server);
|
||||
|
||||
let enable_gfx_switching = config.gfx_managed;
|
||||
let config = Arc::new(Mutex::new(config));
|
||||
|
||||
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 CtrlAnimeDisplay::new() {
|
||||
Ok(ctrl) => {
|
||||
ctrl.add_to_server(&mut object_server);
|
||||
}
|
||||
Err(err) => {
|
||||
error!("AniMe control: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
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(vendor) = ctrl.get_gfx_mode() {
|
||||
if ded == 1 && vendor != GfxVendors::Nvidia {
|
||||
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, &devices, &bus)?;
|
||||
} else if ded == 0 {
|
||||
info!("Dedicated GFX toggle is off");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ctrl.reload()
|
||||
.unwrap_or_else(|err| error!("Gfx controller: {}", err));
|
||||
ctrl.add_to_server(&mut object_server);
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Gfx control: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Collect tasks for task thread
|
||||
let mut tasks: Vec<Arc<Mutex<dyn CtrlTask + Send>>> = Vec::new();
|
||||
|
||||
if let Ok(mut ctrl) = CtrlFanAndCPU::new(config.clone()).map_err(|err| {
|
||||
error!("Profile control: {}", err);
|
||||
}) {
|
||||
ctrl.reload()
|
||||
.unwrap_or_else(|err| warn!("Profile control: {}", err));
|
||||
let tmp = Arc::new(Mutex::new(ctrl));
|
||||
DbusFanAndCpu::new(tmp).add_to_server(&mut object_server);
|
||||
};
|
||||
|
||||
if let Some(laptop) = laptop {
|
||||
if let Ok(ctrl) = CtrlKbdBacklight::new(
|
||||
laptop.usb_product(),
|
||||
laptop.condev_iface(),
|
||||
laptop.supported_modes().to_owned(),
|
||||
config,
|
||||
)
|
||||
.map_err(|err| {
|
||||
error!("Keyboard control: {}", err);
|
||||
err
|
||||
}) {
|
||||
let tmp = Arc::new(Mutex::new(ctrl));
|
||||
DbusKbdBacklight::new(tmp.clone()).add_to_server(&mut object_server);
|
||||
tasks.push(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: implement messaging between threads to check fails
|
||||
// These tasks generally read a sys path or file to check for a
|
||||
// change
|
||||
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() {
|
||||
if let Ok(mut lock) = ctrl.try_lock() {
|
||||
lock.do_task()
|
||||
.map_err(|err| {
|
||||
warn!("do_task error: {}", err);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
object_server
|
||||
.with("/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 {
|
||||
if let Err(err) = object_server.try_handle_next() {
|
||||
eprintln!("{}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
use intel_pstate::PStateError;
|
||||
use rog_fan_curve::CurveError;
|
||||
use rog_types::error::GraphicsError;
|
||||
use std::convert::From;
|
||||
use std::fmt;
|
||||
|
||||
use crate::ctrl_gfx::error::GfxError;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum RogError {
|
||||
ParseFanLevel,
|
||||
ParseVendor,
|
||||
ParseLED,
|
||||
MissingProfile(String),
|
||||
Udev(String, std::io::Error),
|
||||
Path(String, std::io::Error),
|
||||
Read(String, std::io::Error),
|
||||
Write(String, std::io::Error),
|
||||
NotSupported,
|
||||
NotFound(String),
|
||||
IntelPstate(PStateError),
|
||||
FanCurve(CurveError),
|
||||
DoTask(String),
|
||||
MissingFunction(String),
|
||||
MissingLedBrightNode(String, std::io::Error),
|
||||
ReloadFail(String),
|
||||
GfxSwitching(GfxError),
|
||||
Initramfs(String),
|
||||
Modprobe(String),
|
||||
Command(String, std::io::Error),
|
||||
Zbus(zbus::Error),
|
||||
}
|
||||
|
||||
impl fmt::Display for RogError {
|
||||
// This trait requires `fmt` with this exact signature.
|
||||
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),
|
||||
RogError::Udev(deets, error) => write!(f, "udev {}: {}", deets, error),
|
||||
RogError::Path(path, error) => write!(f, "Path {}: {}", path, error),
|
||||
RogError::Read(path, error) => write!(f, "Read {}: {}", path, error),
|
||||
RogError::Write(path, error) => write!(f, "Write {}: {}", path, error),
|
||||
RogError::NotSupported => write!(f, "Not supported"),
|
||||
RogError::NotFound(deets) => write!(f, "Not found: {}", deets),
|
||||
RogError::IntelPstate(err) => write!(f, "Intel pstate error: {}", err),
|
||||
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::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::Zbus(detail) => write!(f, "Zbus error: {}", detail),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for RogError {}
|
||||
|
||||
impl From<PStateError> for RogError {
|
||||
fn from(err: PStateError) -> Self {
|
||||
RogError::IntelPstate(err)
|
||||
}
|
||||
}
|
||||
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<zbus::Error> for RogError {
|
||||
fn from(err: zbus::Error) -> Self {
|
||||
RogError::Zbus(err)
|
||||
}
|
||||
}
|
||||
@@ -1,151 +0,0 @@
|
||||
use log::{info, warn};
|
||||
use rog_types::aura_modes::{AuraModes, BREATHING, STATIC};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Read;
|
||||
|
||||
pub static LEDMODE_CONFIG_PATH: &str = "/etc/asusd/asusd-ledmodes.toml";
|
||||
|
||||
pub static HELP_ADDRESS: &str = "https://gitlab.com/asus-linux/asus-nb-ctrl";
|
||||
|
||||
static LAPTOP_DEVICES: [u16; 4] = [0x1866, 0x1869, 0x1854, 0x19b6];
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LaptopBase {
|
||||
usb_product: String,
|
||||
condev_iface: Option<String>, // required for finding the Consumer Device interface
|
||||
supported_modes: Vec<u8>,
|
||||
}
|
||||
|
||||
impl LaptopBase {
|
||||
pub fn usb_product(&self) -> &str {
|
||||
&self.usb_product
|
||||
}
|
||||
pub fn condev_iface(&self) -> Option<&String> {
|
||||
self.condev_iface.as_ref()
|
||||
}
|
||||
pub fn supported_modes(&self) -> &[u8] {
|
||||
&self.supported_modes
|
||||
}
|
||||
}
|
||||
|
||||
pub fn match_laptop() -> Option<LaptopBase> {
|
||||
for device in rusb::devices().expect("Couldn't get device").iter() {
|
||||
let device_desc = device
|
||||
.device_descriptor()
|
||||
.expect("Couldn't get device descriptor");
|
||||
if device_desc.vendor_id() == 0x0b05 && LAPTOP_DEVICES.contains(&device_desc.product_id()) {
|
||||
let prod_str = format!("{:x?}", device_desc.product_id());
|
||||
|
||||
if device_desc.product_id() == 0x1854 {
|
||||
let mut laptop = laptop(prod_str, None);
|
||||
if laptop.supported_modes.is_empty() {
|
||||
laptop.supported_modes = vec![STATIC, BREATHING];
|
||||
}
|
||||
return Some(laptop);
|
||||
}
|
||||
|
||||
let laptop = laptop(prod_str, Some("02".to_owned()));
|
||||
return Some(laptop);
|
||||
}
|
||||
}
|
||||
warn!(
|
||||
"Unsupported laptop, please request support at {}",
|
||||
HELP_ADDRESS
|
||||
);
|
||||
warn!("Continuing with minimal support");
|
||||
None
|
||||
}
|
||||
|
||||
fn laptop(prod: String, condev_iface: Option<String>) -> LaptopBase {
|
||||
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");
|
||||
|
||||
let mut laptop = LaptopBase {
|
||||
usb_product: prod,
|
||||
condev_iface,
|
||||
supported_modes: vec![],
|
||||
};
|
||||
|
||||
if let Some(modes) = LEDModeGroup::load_from_config() {
|
||||
if let Some(led_modes) = modes.matcher(&prod_family, &board_name) {
|
||||
laptop.supported_modes = led_modes;
|
||||
return laptop;
|
||||
}
|
||||
}
|
||||
laptop
|
||||
}
|
||||
|
||||
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(&<AuraModes>::from(*mode));
|
||||
info!("- {}", mode);
|
||||
}
|
||||
info!(
|
||||
"If these modes are incorrect or missing please request support at {}",
|
||||
HELP_ADDRESS
|
||||
);
|
||||
} else {
|
||||
info!("No RGB control available");
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
struct LEDModeGroup {
|
||||
led_modes: Vec<LEDModes>,
|
||||
}
|
||||
|
||||
impl LEDModeGroup {
|
||||
/// Consumes the LEDModes
|
||||
fn matcher(self, prod_family: &str, board_name: &str) -> Option<Vec<u8>> {
|
||||
for led_modes in self.led_modes {
|
||||
if prod_family.contains(&led_modes.prod_family) {
|
||||
for board in led_modes.board_names {
|
||||
if board_name.contains(&board) {
|
||||
info!("Matched to {} {}", led_modes.prod_family, board);
|
||||
return Some(led_modes.led_modes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn load_from_config() -> Option<Self> {
|
||||
if let Ok(mut file) = OpenOptions::new().read(true).open(&LEDMODE_CONFIG_PATH) {
|
||||
let mut buf = String::new();
|
||||
if let Ok(l) = file.read_to_string(&mut buf) {
|
||||
if l == 0 {
|
||||
warn!("{} is empty", LEDMODE_CONFIG_PATH);
|
||||
} else {
|
||||
return Some(toml::from_str(&buf).unwrap_or_else(|_| {
|
||||
panic!("Could not deserialise {}", LEDMODE_CONFIG_PATH)
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
warn!("Does {} exist?", LEDMODE_CONFIG_PATH);
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
struct LEDModes {
|
||||
prod_family: String,
|
||||
board_names: Vec<String>,
|
||||
led_modes: Vec<u8>,
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
#![deny(unused_must_use)]
|
||||
/// Configuration loading, saving
|
||||
pub mod config;
|
||||
pub(crate) mod config_old;
|
||||
/// Control of AniMe matrix display
|
||||
pub mod ctrl_anime;
|
||||
/// Control of battery charge level
|
||||
pub mod ctrl_charge;
|
||||
/// 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_fan_cpu;
|
||||
/// GPU switching and power
|
||||
pub mod ctrl_gfx;
|
||||
/// Keyboard LED brightness control, RGB, and LED display modes
|
||||
pub mod ctrl_leds;
|
||||
/// 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(&mut 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,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,59 +0,0 @@
|
||||
[[led_modes]]
|
||||
prod_family = "Zephyrus S"
|
||||
board_names = ["GX502", "GX701", "G531", "GL531", "G532"]
|
||||
led_modes = [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 255]
|
||||
|
||||
[[led_modes]]
|
||||
prod_family = "Zephyrus M"
|
||||
board_names = ["GU502GV"]
|
||||
led_modes = [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 255]
|
||||
|
||||
[[led_modes]]
|
||||
prod_family = "ROG Zephyrus M15"
|
||||
board_names = ["GU502LW"]
|
||||
led_modes = [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 255]
|
||||
|
||||
[[led_modes]]
|
||||
prod_family = "ROG Zephyrus M15"
|
||||
board_names = ["GU502LU"]
|
||||
led_modes = [0, 1, 2, 10]
|
||||
|
||||
[[led_modes]]
|
||||
prod_family = "Zephyrus"
|
||||
board_names = ["GM501GM", "GX531"]
|
||||
led_modes = [0, 1, 2, 3, 10, 13]
|
||||
|
||||
[[led_modes]]
|
||||
prod_family = "ROG Strix"
|
||||
board_names = ["G531GW"]
|
||||
led_modes = [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 255]
|
||||
|
||||
[[led_modes]]
|
||||
prod_family = "ROG Strix"
|
||||
board_names = ["GX531", "G512LV", "G712LV"]
|
||||
led_modes = [0, 1, 2, 3, 10, 13, 14]
|
||||
|
||||
[[led_modes]]
|
||||
prod_family = "ROG Strix"
|
||||
board_names = ["G512LI", "G712LI", "G531GD"]
|
||||
led_modes = [0, 1, 2, 3, 10]
|
||||
|
||||
[[led_modes]]
|
||||
prod_family = "Strix"
|
||||
board_names = ["G731GV", "G731GW", "G531GV"]
|
||||
led_modes = [0, 1, 2, 3, 13, 14]
|
||||
|
||||
[[led_modes]]
|
||||
prod_family = "Strix"
|
||||
board_names = ["G731GT", "G731GU", "G531GT", "G531GU"]
|
||||
led_modes = [0, 1, 2, 3]
|
||||
|
||||
[[led_modes]]
|
||||
prod_family = "Strix Scar"
|
||||
board_names = ["G531", "G731"]
|
||||
led_modes = [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 255]
|
||||
|
||||
[[led_modes]]
|
||||
prod_family = "ROG"
|
||||
board_names = ["GL553VE"]
|
||||
led_modes = [0, 1, 2, 13, 14]
|
||||